多边形裁剪 - 一点点详细说明
Polygon clipping - a little elaboration?
我一直在阅读很多关于萨瑟兰霍奇曼多边形裁剪算法的信息,并了解了大致的想法。但是,当我看到它的实际实现(如下图)时,我对坐标比较感到困惑,例如intersection
和inside
方法中的坐标比较。因此,我想知道是否有人可以详细说明什么以及为什么?我看到大量解释一般概念的视频和文章,但我真的很难找到有关实现的实际细节的一些解释。
bool inside(b2Vec2 cp1, b2Vec2 cp2, b2Vec2 p) {
return (cp2.x-cp1.x)*(p.y-cp1.y) > (cp2.y-cp1.y)*(p.x-cp1.x);
}
b2Vec2 intersection(b2Vec2 cp1, b2Vec2 cp2, b2Vec2 s, b2Vec2 e) {
b2Vec2 dc( cp1.x - cp2.x, cp1.y - cp2.y );
b2Vec2 dp( s.x - e.x, s.y - e.y );
float n1 = cp1.x * cp2.y - cp1.y * cp2.x;
float n2 = s.x * e.y - s.y * e.x;
float n3 = 1.0 / (dc.x * dp.y - dc.y * dp.x);
return b2Vec2( (n1*dp.x - n2*dc.x) * n3, (n1*dp.y - n2*dc.y) * n3);
}
//http://rosettacode.org/wiki/Sutherland-Hodgman_polygon_clipping#JavaScript
//Note that this only works when fB is a convex polygon, but we know all
//fixtures in Box2D are convex, so that will not be a problem
bool findIntersectionOfFixtures(b2Fixture* fA, b2Fixture* fB, vector<b2Vec2>& outputVertices)
{
//currently this only handles polygon vs polygon
if ( fA->GetShape()->GetType() != b2Shape::e_polygon ||
fB->GetShape()->GetType() != b2Shape::e_polygon )
return false;
b2PolygonShape* polyA = (b2PolygonShape*)fA->GetShape();
b2PolygonShape* polyB = (b2PolygonShape*)fB->GetShape();
//fill subject polygon from fixtureA polygon
for (int i = 0; i < polyA->GetVertexCount(); i++)
outputVertices.push_back( fA->GetBody()->GetWorldPoint( polyA->GetVertex(i) ) );
//fill clip polygon from fixtureB polygon
vector<b2Vec2> clipPolygon;
for (int i = 0; i < polyB->GetVertexCount(); i++)
clipPolygon.push_back( fB->GetBody()->GetWorldPoint( polyB->GetVertex(i) ) );
b2Vec2 cp1 = clipPolygon[clipPolygon.size()-1];
for (int j = 0; j < clipPolygon.size(); j++) {
b2Vec2 cp2 = clipPolygon[j];
if ( outputVertices.empty() )
return false;
vector<b2Vec2> inputList = outputVertices;
outputVertices.clear();
b2Vec2 s = inputList[inputList.size() - 1]; //last on the input list
for (int i = 0; i < inputList.size(); i++) {
b2Vec2 e = inputList[i];
if (inside(cp1, cp2, e)) {
if (!inside(cp1, cp2, s)) {
outputVertices.push_back( intersection(cp1, cp2, s, e) );
}
outputVertices.push_back(e);
}
else if (inside(cp1, cp2, s)) {
outputVertices.push_back( intersection(cp1, cp2, s, e) );
}
s = e;
}
cp1 = cp2;
}
return !outputVertices.empty();
}
(代码从iForce2d :)窃取)
你说你理解了这个大致的想法,大概是通过阅读萨瑟兰霍奇曼算法之类的东西。 这在高层次上解释了inside
和intersection
做什么。
至于他们如何实现目标的细节,那都只是直接的教科书线性代数。
inside
正在测试交叉(p - cp1)
(cp2 - cp2)
的符号,并在符号严格大于零true
返回。 您可以将 return 语句重写为:
return (cp2.x-cp1.x)*(p.y-cp1.y) - (cp2.y-cp1.y)*(p.x-cp1.x) > 0;
通过将第二项移动到>
的左侧,这将为您提供左侧的叉积。
请注意,叉积通常是vec3
的叉vec3
运算,需要计算所有三个项。 但是,我们在 2d 中执行此操作,这意味着vec3
具有(x, y, 0)
的形式。 因此,我们只需要计算z
输出项,因为十字必须垂直于xy
平面,因此形式为(0, 0, value)
。
intersection
精确地使用此处列出的算法找到两个向量相交的点:从两点开始的线相交。 特别是,我们关心紧跟在文本"行列式可以写成:"之后的公式:
在该公式的上下文中,n1
(x1 y2 - y1 x2)
,n2
是(x3 y4 - y3 x4)
的,n3
是1 / ((x1 - x2) (y3 - y4) - (y1 - y2) (x3 - x4))
--编辑--
为了涵盖评论中提出的问题,这里有一个尽可能完整的解释,说明为什么inside()
的返回值是对交叉积符号的测试。
我将稍微偏离一点切线,显示我的年龄,并注意交叉乘积公式有一个非常简单的记忆辅助。 你只需要记住伍兹和克劳瑟的文字冒险游戏《巨大洞穴》中的第一个魔法词。xyzzy
.
如果你在三维中有两个向量:(x1, y1, z1)
和(x2, y2, z2)
,它们的交叉乘积(xc, yc, zc)
计算如下:
xc = y1 * z2 - z1 * y2;
yc = z1 * x2 - x1 * z2;
zc = x1 * y2 - y1 * x2;
现在,查看第一行,从术语、所有空格和运算符中删除c
、1
和2
后缀,只查看剩余的字母。 这是一个神奇的词。 然后你只需垂直向下,用y
替换x
,y
用z
替换,在你从一条线到另一行时用x
z
。
言归正传,xc
和yc
扩展右侧的术语都包含z1
或z2
。 但我们知道这两者都为零,因为我们的输入向量在xy
平面上,因此具有零z
分量。 这就是为什么我们可以完全省略计算这两个项,因为我们知道它们将为零。
这与叉积的定义 100% 一致,生成的向量始终垂直于两个输入向量。 因此,如果两个输入向量都在xy
平面上,我们知道输出向量必须垂直于xy
平面,因此具有(0, 0, z)
那么,我们对z
任期有什么看法呢?
zc = x1 * y2 - y1 * x2;
在这种情况下,向量1
cp2-cp1
,向量2
p-cp1
。 因此,将其插入上述内容,我们得到:
zc = (cp2.x-cp1.x)*(p.y-cp1.y) - (cp2.y-cp1.y)*(p.x-cp1.x);
但如前所述,我们不关心它的价值,只关心它的标志。 我们想知道这是否大于零。 因此:
return (cp2.x-cp1.x)*(p.y-cp1.y) - (cp2.y-cp1.y)*(p.x-cp1.x) > 0;
然后将其重写为:
return (cp2.x-cp1.x)*(p.y-cp1.y) > (cp2.y-cp1.y)*(p.x-cp1.x);
最后,该项的符号与点 p 是在裁剪多边形内部还是外部有什么关系? 您说得很对,所有的剪辑都发生在 2Dxy
平面上,那么为什么我们要涉及 3D 操作呢?
重要的是要认识到 3D 中的交叉乘积公式不是可交换的。 就两个向量操作数之间的角度而言,它们之间的顺序很重要。 维基百科页面上的Cross产品第一张图片完美地展示了它。 在该图中,如果您从上方向下看,在评估a
交叉b
时,从a
到b
的最短角度方向是逆时针方向。 在这种情况下,这会导致具有正z
值的交叉乘积,假设正z
上升到页面上。 但是,如果您评估b
交叉a
,则从b
到a
的最浅角距离是顺时针方向,并且交叉积具有负z
值。
回想一下算法本身的维基百科页面,你已经得到了一条蓝色的"裁剪"线,它围绕裁剪多边形逆时针工作。 如果您认为该矢量在逆时针方向上始终具有正量级,则对于裁剪多边形中的任何一对相邻折点,它将始终cp2 - cp1
。
记住这一点,想象一下,如果你站在cp1
,鼻子直指cp2
,你会看到什么。 裁剪多边形的内部将位于左侧,外部位于右侧。 现在考虑两点p1
和p2
。 我们会说p1
在剪切多边形内部,p2
在外部。 这意味着将鼻子指向p1
的最快方法是逆时针旋转,而指向p2
的最快方法是顺时针旋转。
因此,通过研究叉积的符号,我们实际上是在问"我们是从当前边缘顺时针旋转还是逆时针旋转以查看点",这相当于询问该点是在裁剪多边形内部还是外部。
我将补充最后一个建议。 如果你对这类东西感兴趣,或者3D渲染,或任何涉及对现实世界的数学表示进行建模的编程,那么参加一门很好的线性代数课程,涵盖交叉积,点积,向量,矩阵以及它们之间的相互作用,将是你能做的最好的事情之一。 它将为计算机完成的大量工作提供非常坚实的基础。
- 使用strcpy将char数组的元素复制到另一个数组
- 有符号的int和int-有没有一种方法可以在C++中区分它们
- 有一个打印语句的函数是一种糟糕的编程实践吗
- 在 c++ 中拥有一组结构的正确方法是什么?
- 有没有一种方法可以创建一个带有哈希表的数据库,该哈希表具有恒定时间查找功能
- 如何将三维尺寸不固定的三维阵列展平为一维阵列
- nlohmann-json将一个数组插入到另一个数组中
- 有没有一种方法可以在编译时获得作用域类名
- 为什么make_tie不是一件事
- 对于C++中使用智能指针的指针算术限制,有没有一种变通方法
- c++中O(n^(1/3))中一个数的除数的有效计数
- 一种在C++中读取TXT配置文件的简单方法
- 有没有一种方法可以测量c++程序的运行时内存使用情况
- 有没有一种方法可以使用placement new将堆叠对象分配给分配的内存
- 在调用接收数组的方法时,模板化数组大小是不是一种糟糕的做法
- 有没有一种方法可以通过"typedef"为重新定义的基本类型定义特征和强制转换运算符
- 多边形裁剪 - 一点点详细说明
- 将排序代码与数组合并,陷入合并,只需要一点点修正
- 对于原始数组和std::vector,明显没有对下标进行过一次结束的说明.这个问题已经得到了决定性的解决吗?
- 我的C++代码无法处理(一点点)快速的数据流量