通过向量扩展二维多边形
Posted
技术标签:
【中文标题】通过向量扩展二维多边形【英文标题】:Extend 2d polygon by vector 【发布时间】:2016-10-08 11:04:09 【问题描述】:我有凸多边形,我想通过沿矢量投影来扩展它们,如下所示:
(左侧为原始多边形和矢量,右侧为所需结果。)
我的多边形存储为一系列逆时针缠绕的点。我想要找到的是我需要从中投影的“起点”和“终点”点,如下面的圆圈顶点所示。
(绿色箭头表示多边形的缠绕,给出每条边的“方向”。)
我最初的计划是确定要使用哪些点,方法是从每个点投射一条带有矢量方向的射线,并找到射线不与边缘相交的第一个和最后一个点。但是,这似乎很昂贵。
有没有一种方法可以使用边缘方向与矢量方向或类似的技巧来确定从哪些点延伸?
【问题讨论】:
@ChristianHackl 我的代码是 C++,但我决定使用图片而不是代码。我也不确定。如果不合适,我会删除它。 IMO 这是不合适的,因为您正在寻找理论解决方案。这很好,因为您将纸上的概念与其在 C++ 代码中的实现分开。但是你不应该在同一个问题中同时问这两个问题。这个概念与语言无关。只有在您尝试在 C++ 中实现并遇到问题后,您才应该提出 C++ 问题。 @ChristianHackl 我同意。标签已移除。 :) 【参考方案1】:查看向量方向落在边缘方向之间的点。
换句话说,取三个向量:
从顶点引出的边 的翻译向量 与通向顶点的边相对如果它们在逆时针方向上是这样的,即如果第二个向量在第一个和第三个之间,这是一个“内部”点。
为了确定一个向量是否位于其他两个向量之间,请使用叉积,例如here.
【讨论】:
这对我来说看起来更直观。我寻找所有“外部”的点,对吗? @Claytorpedo 是的,完全正确。您标记的两个点是最左边和最右边的“外部”点。此外,您还必须处理平移向量与其中一条边完全平行的情况。【参考方案2】:是的,你可以。您想沿 x、y 投影。所以正常是y,-x。现在旋转(atan2,或者如果你了解旋转矩阵,你可以直接使用它)。要投影的点以及现在的最小和最大 x,您还可以通过始终沿轴进行投影然后旋转回来来加快投影速度。
【讨论】:
我不明白。我如何用矢量的法线旋转点,我在寻找什么结果?我是在用边缘和向量的法线做点积,并寻找某个结果吗? θ = atan2(ny, nx);然后对于所有点,rx = x * cos(theta)-y*sin(theta), ry = x * sin(theta) + y * cos(theta)。现在它们被旋转,得到最大值和最小值,这些是你在扫描时扩展的点。 谢谢。在您的回答中,您提到只是检查 min/max rx。是否需要计算 ry,如果需要,如何在二维中区分 min/max? 我们旋转移除一个维度。我们只需要检查 x 中的 max/min。 ry 不是必需的,不。 这个问题不需要三角函数。【参考方案3】:n.m. answered 我提出并描绘了这个问题,但是在编程时我很快注意到有一种常见情况,即所有顶点都是“外部”顶点(这可以很容易地在三角形上看到,也可能发生在其他多边形上) .
文字说明。
我使用的解决方案是查看通往和离开每个顶点的边的法线向量。我们要扩展的顶点是至少有一条边法线与我们要扩展的增量向量的最小角度小于 90 度的顶点。
逆时针缠绕的多边形上的向外边缘法线可以通过以下方式找到:
normal = (currentVertex.y - nextVertex.y, nextVertex.x - currentVertex.x)
请注意,由于我们不关心确切的角度,因此我们不需要对法线进行归一化(制作单位向量),这样可以节省平方根。
为了将其与 delta 向量进行比较,我们使用点积:
dot = edgeNormal.dot(deltaVector)
如果结果大于零,则最小角度为锐角(小于 90)。如果结果正好为零,则向量是垂直的。如果结果小于零,则最小角度为钝角。当向量垂直时值得注意,因为它可以避免向扩展多边形添加额外的顶点。
如果您想像我一样想象角度如何与点积一起工作,只需查看反余弦图(通常您通过 acos(dot) 获得角度)。
现在我们可以找到在其边缘法线和增量向量之间具有一个锐角和一个非锐角最小角度的顶点。这些顶点的“锐边”上的所有内容都添加了增量向量,“钝边”上的所有内容都保持不变。两个边界顶点本身是重复的,一个扩展一个保持不变,除非“钝边”完全垂直于增量向量(在这种情况下,我们只需要扩展顶点,因为否则我们将在同一行上有两个顶点)。
这是此解决方案的 C++ 代码。
它可能看起来有点长,但实际上很简单,并且有很多 cmets,所以希望它不难理解。
它是我的 Polygon 类的一部分,它有一个逆时针缠绕顶点的 std::vector。 units::Coordinate 是浮点数,units::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 向量而制成的。
请注意,这个解决方案仍然只对凸多边形有效。
【讨论】:
以上是关于通过向量扩展二维多边形的主要内容,如果未能解决你的问题,请参考以下文章