我已经阅读了很多有关sutherland hodgman多边形裁剪算法的文章,并了解了总体思路。但是,当我看到它的实际实现时(如下面的例子),我对诸如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盗取的代码:))
答案 0 :(得分:1)
您说您了解一般的想法,大概是通过阅读Sutherland Hodgman Algorithm之类的东西。这就从高层次上确切地解释了inside
和intersection
的作用。
关于它们如何实现目标的细节,这全都只是教科书的线性代数。
inside
正在测试(cp2 - cp2)
和(p - cp1)
的正负号,如果正负号严格大于零,则返回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
使用此处列出的精确算法Line Intersection from Two Points查找两个向量相交的点。特别是,我们会在紧随“行列式可以写为:”之后紧随其后的公式。
在该公式中,n1
是(x1 y2 - y1 x2)
,n2
是(x3 y4 - y3 x4)
,而n3
是1 / ((x1 - x2) (y3 - y4) - (y1 - y2) (x3 - x4))
-编辑-
要覆盖评论中提出的问题,这里是我能给出的完整解释,以解释为什么inside()
的返回值是对叉积符号的检验。
我要稍微切线,显示我的年龄,并注意,叉乘积公式具有非常简单的记忆辅助功能。您只需要记住伍兹和克劳瑟的文字冒险游戏Colossal Cave中的第一个魔语。 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
后缀,然后查看其余字母。这是神奇的词。然后,您垂直向下走,在逐行移动时,将{{1}替换为x
,将y
替换为y
,将z
替换为z
回到业务上,x
和xc
扩展名右边的两个术语都包含yc
或z1
。但是我们知道这两个都是零,因为我们的输入向量在z2
平面中,因此具有零的xy
分量。这就是为什么我们可以完全省去计算这两个项的原因,因为我们知道它们将为零。
这与叉积的定义一致100%,结果矢量始终垂直于两个输入矢量。因此,如果两个输入向量都在z
平面中,我们知道输出向量必须垂直于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;
最后,该术语的符号与点p在剪切多边形的内部还是外部有什么关系?您完全正确,所有剪辑都发生在2d return (cp2.x-cp1.x)*(p.y-cp1.y) > (cp2.y-cp1.y)*(p.x-cp1.x);
平面中,那么为什么我们要完全涉及3d操作呢?
要意识到的重要一点是3d中的叉积公式不是可交换的。就两个向量操作数之间的角度而言,它们的顺序很重要。维基百科页面上Cross Product的第一张图片完美地展示了它。在该图中,如果从上方向下看,则在评估xy
交叉a
时,从b
到a
的最短角度方向是逆时针方向。在这种情况下,假设正b
在页面上移动,将导致交叉积具有z
正值。但是,如果评估z
十字b
,则从a
到b
的最短角距离是顺时针,并且十字积具有负a
值。 / p>
回顾算法本身的Wikipedia页面,您已经获得了蓝色的“ clipping”线,该线沿裁剪多边形逆时针运行。如果您认为该矢量在逆时针方向上始终具有正值,则对于裁剪多边形中的任意一对相邻顶点,它始终为z
。
请牢记这一点,想象一下如果您站在cp2 - cp1
时鼻子直指cp1
会看到什么。裁剪多边形的内部将在您的左侧,外部在右侧。现在考虑两个点cp2
和p1
。我们将说p2
在剪切多边形内,而p1
在剪切多边形内。这意味着将鼻子指向p2
的最快方法是逆时针旋转,而指向p1
的最快方法是顺时针旋转。
因此,通过研究叉积的符号,我们实际上是在问“我们要从当前边沿顺时针还是逆时针旋转以查看该点”,这等同于询问该点是否在剪切多边形内或外面。
我将添加最后一条建议。如果您对此类事物,3D渲染或涉及对真实世界的数学表示进行建模的程序完全感兴趣,可以参加线性代数的坚实课程,涵盖交叉乘积,点乘积,向量,矩阵以及它们之间的相互作用将是您可以做的最好的事情之一。它将为大量使用计算机完成的工作提供非常的坚实基础。