前言
两个凸多边形的重叠问题就是对两个凸多边形求相交部分的问题。约定凸多边形指它的边界和内部,凸多边形仍用顶点坐标的逆时针方向序列确定。
设给出的两个凸多边形 P 和 Q 的顶点序列分别是 P1,P2,…,PL 和 Q1,Q2,…,Qm。
假设 P 的边界上不包含 Q 的顶点 ,Q 的边界也不包含 P 的顶点。有两个问题需要解决:
- 如何
有次序
地求出各边的所有交点 , - 如何
排列
求出的交点
和原凸多边形的顶点
, 形成新的凸多边形的顶点序列。
求交点
为了有次序
地求出交点 , 可以在两个多边形边上交替地前进
,原则是在哪个多边形的边上可能有交点就等待 , 在另一个多边形的边上前进。
前进的原则可以自己随便定义,但是得满足以下两个要求(后面会解释):
- 两个多边形向前走的机会相同
- 所有情况都要覆盖
初始从 对边
的求交 开始 , 注意所有求交是线段的求交
。这里规定了
。
设已经算得 的交点
- 依可能性判定接下去计算的是
的交点还是
的交点
如果是算第一个,就是 P 多边形前进。算第二个就是 Q 多边形前进。 - 此外还需考虑 之间的夹角是否大于 180 度
强调:初始从对边 的求交开始 , 注意所有求交是线段的求交。
前进情况
的相对位置总共有 8 种,分别如下:
上图中对于第一行的四种情况可分为一类,第二行的四种情况就是将第一行的 P Q 两条线段换了下位置。
记忆第一行的四种情况可以采取以下方式:
- 将 “短的” 看作 0,“长的” 看作 1
- 这样可以上述四种情况可以分别记成 00 01 10 11 形式
对于 P Q 线段的相对位置总共就 8 种情况,对于选哪个前进我们刚才说了两个原则:
- 两个多边形向前走的机会相同。在这 8 种情况中我们不能定义成 5 种是 P 前进,3 种是 Q 前进。前进方案是任意定义的。图中是一种定义方案,概率均为 1/2。而我们也可以定义成第一行全部是 P 前进,第二行全部是 Q 前进。区别是在编程的实现不同。
- 所有情况都要覆盖。不能遗漏某种情况,这里是对编程的时候说的。
夹角
在上图中我们不难发现,对于第一行的四种情况单纯只按 的相对位置来划分的话,是无法与第二行的四种情况区分开来的。比如 情形(1) 和 情形(8)。
此图中的 情形 8 和上图中的 情形 8 都是一种类型,要注意辨别。
此时我们就要利用 (矢量叉乘)的 z 分量(用右手定则判断方向)来判断。
- 如果该分量大于等于 0,则是前四种情形
- 否则是后四种情形
伪代码
按照第一张图约定的前进方法,我们可以写出如下伪代码。
/* P、Q为多边形顶点数组,l、m为顶点个数*/
void Advance(PONIT P[],int l,POINT Q[],int m){
int s;
s = vector3(P,Q,i,j); //s 置成 PiPi-1 与 QjQj-1 的叉乘的 z 值; vector3 是 unity 的一个 API
if (s >= 0){
if((left(P,i,Q,j) && left(Q,j,P,i)) || (right(P,i,Q,j) && left(Q,j,P,i))){
if (i < l) i++;
else i = 1;
}else {
if(j < m) j++;
else j = 1;
}
}else{
if((right(P,i,Q,j) && left(Q,j,P,i)) || (right(P,i,Q,j) && right(Q,j,P,i))){
if (i<l) i++;
else i = 1;
} else{
if(j < m) j++;
else j = 1;
}
}
}
排列交点和顶点
为了正确排列求出的交点并加入原两个凸多边形部分顶点以形成相交的凸多边形,可以在每求出一个交点时进行一次输出。
求出的第一个交点可做一下记录,如果在以后交替前进求交点的过程中再次求出与第一次求得相同的交点,就知道整个求交过程已经结束了。
求得的交点不是第一个时,为形成交得凸多边形顶点序列,要区分边 是进入多边形 Q,还是走出 Q 两种情况。(这决定了我们选择 )
-
正进入多边形 Q ,此时应先输出 上次求得交点后的
多边形Q
上的各顶点,再输出本次交点。 -
是走出多边形 Q ,此时应先输出 上次求得交点后的
多边形P
上的各顶点,再输出本次交点。
区分这两种情况,可通过检查 在直线 的左侧还是右侧
来确定。
伪代码
/* P、Q为多边形顶点数组,l、m为顶点个数*/
void Output(PONIT P[],int l,POINT Q[],int m){
POINT r0;
int t;
if(本过程第一次调用){
r0 = intersect(P,i,Q,j);//r0置成Pi-1Pi与Qj-1Qj之交点
if (left(P,i,Q,j)) //Pi在Qj-1Qj左
t = i;
else
t = j;
}else {
if (left(P,i,Q,j)) {//若Pi在Qj-1Qj左,输出Q中t至j-1顶点,输出Pi-1Pi与Qj-1Qj之交点
out(Q,t,j-1);
t = i;
}else {//输出P中t至i-1顶点,输出Pi-1Pi与Qj-1Qj之交点
out(P,t,i-1);
t = j;
}
}
}
交点个数
多边形 P 和 Q 的交点个数决定了我们算法循环次数的上限。
多边形 P 和 Q,P 中的每一条边与 P 相交最多两个交点,故 Q 最多与 P 交 2m 个交点,同理,P 最多与 Q 交 2l 个交点。因此 2(l+m) 步是足够的。
完整伪代码
两个凸多边形求交的完整算法:
/* P、Q为多边形顶点数组,l、m为多边形边数 */
void Convex_polygon_intersection(POINT P[],int l,POINT Q[],int m)
{
int i,j,k,P0,Q0;
i=1;j=1;k=1;P0=P[l];Q0=Q[m];//初始化
while (k<=2*(l+m)&& (求得的交点不是第一个交点R0))//交替前进求交
{
if (Pi-1Pi与Qj-1Qj相交) Output(P,l,Q,m);//若相交,则调用Output
Advance(P, l, Q, m);
k++;
}
if(找到过交点) return ;
else{
if(inner(Q,m,P[1])) printf("P在Q中");
else if (inner(P,l,Q[1])) printf("Q在P中");
else printf("P与Q分离");
}
}