通过向量扩展2d多边形

时间:2016-10-08 11:04:10

标签: polygon

我有凸多边形,我想通过像这样的向量投影来扩展它们:

enter image description here

(左边的原始多边形和矢量,右边的所需结果。)

我的多边形存储为一系列具有逆时针绕组的点。我想要找到的是我需要投射的“开始”和“停止”点,如下面的圆圈顶点。 enter image description here

(绿色箭头表示多边形的绕组,给出每条边的“方向”。)

我最初的计划是通过从每个点投射具有矢量方向的光线来确定使用哪些点,并找到光线不与边缘相交的第一个和最后一个点。然而,这似乎很昂贵。

有没有办法可以使用边缘方向与矢量方向或类似技巧来确定要从哪个点延伸?

3 个答案:

答案 0 :(得分:1)

查看矢量方向落在边缘方向之间的点。

换句话说,采取三个向量:

  • 从顶点引出的边缘
  • 的翻译向量
  • 与通向顶点的边缘相反

如果它们在进入CCW时按此顺序排列,即如果第二个向量在第一个和第三个之间,则这是"内部"点。

为了确定载体是否位于两个其他载体之间,使用如所述的交叉产物。 here

答案 1 :(得分:0)

是的,你可以。你想沿着x,y投射。所以法线是y,-x。现在按此旋转(atan2,或者你可以直接理解旋转矩阵)。从最近和最大x投射的点,你也可以通过沿着轴然后向后旋转来加速投影。

答案 2 :(得分:0)

牛米。 answered我问的问题和想象的问题,但是在编程时我很快注意到有一个常见的情况,即所有顶点都是“外部”顶点(这可以在三角形上很容易看到,并且可以在其他多边形中出现)太)。

文字说明。

我使用的解决方案是查看通向和离开每个顶点的边的法线向量。我们想要扩展的顶点是至少有一个边法线的顶点,最小角度小于90度,与我们延伸的delta矢量相比。

逆时针缠绕多边形上的向外边缘法线可以通过以下方式找到:

normal = (currentVertex.y - nextVertex.y, nextVertex.x - currentVertex.x)

请注意,由于我们不关心确切的角度,因此我们不需要将法线化(使单位矢量)正常化,从而节省平方根。

要将其与delta矢量进行比较,我们使用点积:

dot = edgeNormal.dot(deltaVector)

如果结果大于零,则最小角度为锐角(小于90)。如果结果恰好为零,则向量是垂直的。如果结果小于零,则最小角度为钝角。值得注意的是,向量是垂直的,因为它允许我们避免向扩展多边形添加额外的顶点。

如果您想要像点我所做的那样想象角度如何与点积一起工作,只需看一下弧余弦图(通常通过acos(点)获得角度)。

现在我们可以找到在其边缘法线和delta矢量之间具有一个锐角和一个非锐角最小角度的顶点。这些顶点的“锐侧”上的所有东西都添加了增量矢量,“钝侧”上的所有内容都保持不变。两个边界顶点本身是重复的,有一个扩展,一个保持不变,除非“钝边”与delta矢量完全垂直(在这种情况下我们只需要扩展顶点,因为否则我们会在同一条线上有两个顶点。)

以下是此解决方案的C ++代码。

它可能看起来有点长,但它实际上非常简单并且有很多评论所以希望不应该很难遵循。

它是我的Polygon类的一部分,它有一个逆时针缠绕顶点的std :: vector。 units :: Coordinate是浮点数,而单位:: Coordinate2D是一个我觉得应该不言自明的向量类。

// Compute the normal of an edge of a polygon with counterclockwise winding, without normalizing it to a unit vector.
inline units::Coordinate2D _get_non_normalized_normal(units::Coordinate2D first, units::Coordinate2D second) {
    return units::Coordinate2D(first.y - second.y, second.x - first.x);
}
enum AngleResult {
    ACUTE,
    PERPENDICULAR,
    OBTUSE
};
// Avoid accumulative floating point errors.
// Choosing a good epsilon is extra important, since we don't normalize our vectors (so it is scale dependent).
const units::Coordinate eps = 0.001;
// Check what kind of angle the minimum angle between two vectors is.
inline AngleResult _check_min_angle(units::Coordinate2D vec1, units::Coordinate2D vec2) {
    const units::Coordinate dot = vec1.dot(vec2);
    if (std::abs(dot) <= eps)
        return PERPENDICULAR;
    if ((dot + eps) > 0)
        return ACUTE;
    return OBTUSE;
}
Polygon Polygon::extend(units::Coordinate2D delta) const {
    if (delta.isZero()) { // Isn't being moved. Just return the current polygon.
        return Polygon(*this);
    }
    const std::size_t numVerts = vertices_.size();
    if (numVerts < 3) {
        std::cerr << "Error: Cannot extend polygon (polygon invalid; must have at least three vertices).\n";
        return Polygon();
    }
    // We are interested in extending from vertices that have at least one edge normal with a minimum angle acute to the delta.
    // With a convex polygon, there will form a single contiguous range of such vertices.
    // The first and last vertex in that range may need to be duplicated, and then the vertices within the range
    // are projected along the delta to form the new polygon.
    // The first and last vertices are defined by the vertices that have only one acute edge normal.

    // Whether the minimum angle of the normal of the edge made from the last and first vertices is acute with delta.
    const AngleResult firstEdge = _check_min_angle(_get_non_normalized_normal(vertices_[numVerts-1], vertices_[0]), delta);
    const bool isFirstEdgeAcute = firstEdge == ACUTE;

    AngleResult prevEdge = firstEdge;
    AngleResult currEdge;
    bool found = false;
    std::size_t vertexInRegion;
    for (std::size_t i = 0; i < numVerts - 1; ++i) {
        currEdge = _check_min_angle(_get_non_normalized_normal(vertices_[i], vertices_[i+1]), delta);
        if (isFirstEdgeAcute != (currEdge == ACUTE)) {
            // Either crossed from inside to outside the region, or vice versa.
            // (One side of the vertex has an edge normal that is acute, the other side obtuse.)
            found = true;
            vertexInRegion = i;
            break;
        }
        prevEdge = currEdge;
    }
    if (!found) {
        // A valid polygon has two points that define where the region starts and ends.
        // If we didn't find one in the loop, the polygon is invalid.
        std::cerr << "Error: Polygon can not be extended (invalid polygon).\n";
        return Polygon();
    }
    found = false;
    std::size_t first, last;
    // If an edge being extended is perpendicular to the delta, there is no need to duplicate that vertex.
    bool shouldDuplicateFirst, shouldDuplicateLast;
    // We found either the first or last vertex for the region.
    if (isFirstEdgeAcute) {
        // It is the last vertex in the region.
        last = vertexInRegion;
        shouldDuplicateLast = currEdge != PERPENDICULAR; // currEdge is either perpendicular or obtuse.
        // Loop backwards from the end to find the first vertex.
        for (std::size_t i = numVerts - 1; i > 0; --i) {
            currEdge = _check_min_angle(_get_non_normalized_normal(vertices_[i-1], vertices_[i]), delta);
            if (currEdge != ACUTE) {
                first = i;
                shouldDuplicateFirst = currEdge != PERPENDICULAR;
                found = true;
                break;
            }
        }
        if (!found) {
            std::cerr << "Error: Polygon can not be extended (invalid polygon).\n";
            return Polygon();
        }
    } else {
        // It is the first vertex in the region.
        first = vertexInRegion;
        shouldDuplicateFirst = prevEdge != PERPENDICULAR; // prevEdge is either perpendicular or obtuse.
        // Loop forwards from the first vertex to find where it ends.
        for (std::size_t i = vertexInRegion + 1; i < numVerts - 1; ++i) {
            currEdge = _check_min_angle(_get_non_normalized_normal(vertices_[i], vertices_[i+1]), delta);
            if (currEdge != ACUTE) {
                last = i;
                shouldDuplicateLast = currEdge != PERPENDICULAR;
                found = true;
                break;
            }
        }
        if (!found) {
            // The edge normal between the last and first vertex is the only non-acute edge normal.
            last = numVerts - 1;
            shouldDuplicateLast = firstEdge != PERPENDICULAR;
        }
    }

    // Create the new polygon.
    std::vector<units::Coordinate2D> newVertices;
    newVertices.reserve(numVerts + (shouldDuplicateFirst ? 1 : 0) + (shouldDuplicateLast ? 1 : 0) );
    for (std::size_t i = 0; i < numVerts; ++i) {
        // Extend vertices in the region first-to-last inclusive. Duplicate first/last vertices if required.
        if (i == first && shouldDuplicateFirst) {
            newVertices.push_back(vertices_[i]);
            newVertices.push_back(vertices_[i] + delta);
        } else if (i == last && shouldDuplicateLast) {
            newVertices.push_back(vertices_[i] + delta);
            newVertices.push_back(vertices_[i]);
        } else {
            newVertices.push_back( isFirstEdgeAcute ? // Determine which range to use.
                ( (i <= last || i >= first) ? vertices_[i] + delta : vertices_[i] ) : // Range overlaps start/end of the array.
                ( (i <= last && i >= first) ? vertices_[i] + delta : vertices_[i] )); // Range is somewhere in the middle of the array.
        }
    }
    return Polygon(newVertices);
}

到目前为止,我用三角形,矩形,近似圆和任意凸多边形测试了这段代码,这些多边形是通过许多不同的delta向量顺序扩展近似圆而制作的。

请注意,此解决方案仅对凸多边形有效。