計算幾何:常見幾何元素關係的判斷以及計算
求三角形面積,海倫公式:S=sqrt(p*(p-a)*(p-b)*(p-c));S=(b*c*sina)/2
在幾何中,double二元組可以表示頂點,表示向量,表示線段,表示直線。一般直接把二元組看成向量來計算。向量最重要的兩個計算,點積和叉積具有不同幾何意義。
點積,u*v=x1*x2+y1*y2。通常用來計算兩向量之間的夾角(角度=acos(u*v)),以及一個向量在另一個向量上的投影。叉積,u^v=x1*y2-x2*y1,結果是一個向量,遵守右手定則,結果的正負就代表了方向。叉積結果的絕對值,是平行四邊形的面積;I=AB^BC時,I>0代表C在AB的左側,I<0代表C在AB的右側,I=0代表C在AB上。
需要注意的是要注意double在判斷時的誤差。由於double儲存方式的原因,double對於不能精確表示的數只能用所能表示的數中最接近的數表示。在計算幾何中,尤其要注意double的誤差。選擇合適的eps來限制精度
const double eps=1e-10;
a<0 --> a<-eps
a<=0 --> a<eps
a==0 --> fabs(a)<eps
接下來是一些常見的集合元素間位置關係判斷的模組:
1.求直線交點,應當先對兩條直線進行判斷,若相交則求交點
P a,b,c,d; //兩條直線上的兩點,P為自定義型別,代表向量(內含兩個double型別的數,表示橫縱座標)
if((a-b)^(c-d)==0){
//平行
}
else{
//相交
double k=((c-a)^(d-c))/(b-a)^(d-c));
return a+(b-a)*k;
}
2.求線段交點。分為兩種情況,若線段所在直線平行,則判斷a線段的兩個端點有沒有可能在b線段上,以及b線段的兩個端點有沒有可能在a線段上;若線段所在直線相交,先求出直線交點,再判斷交點在不在兩條線段上。
P lineinter(P a,P b,P c,P d){
... //直線球交點
}
bool onseg(P a,P b,P r){ //判斷r是否在以a、b為端點的線段上
return (a-r)^(b-r)==0 && (a-r)*(b-r)<=0;
}
P seginter(P a,P b,P c,P d){
if((a-b)^(d-c)==0){
if(onseg(a,b,c)) ...
else if(onseg(a,b,d)) ...
else if(onseg(c,d,a)) ...
else if(onseg(c,d,b)) ...
}
else{
P r=lineinter(a,b,c,d);
if(onseg(a,b,r)&&onseg(c,d,r)) return r;
}
}
3.點到線段的最短距離
點到線段的距離要麼是點到直線的距離,要麼是點到兩個端點距離的較小值。
double dis(P a,P b,P c){
if(((c-a)*(b-a))^((c-b)*(a-b))>=0) //判斷點在直線上的投影是不是線上段上
return fabs((c-a)^(c-b)/dis(a,b));
return min(dis(a,c),dis(b,c));
}
4.直線與圓求交
三種關係:無交點、一個交點、兩個交點。用圓心到直線的距離和半徑進行判斷
int circlelineinter(P a,P b,circle C,P &p1,P &p2){
point o=C.c; double r=C.r;
double d=fabs((a-o)^(b-o))/dis(a,b); //圓心到直線的距離
if(d>r) return 0;
else if(fabs(d-r)<eps){
double k=(o-a)*(b-a)/dis(a,b);
p1=a+(b-a)*k/dis(a,b);
return 1;
}
else{
double k1=(o-a)*(b-a)/dis(a,b);
p1=a+(b-a)*k1/dis(a,b);
double k2=(o-b)*(a-b)/dis(a,b);
p2=b+(a-b)*k2/dis(a,b);
return 2;
}
}
5.圓與圓相交
五種情況,內含,內切,相交,外切,相離。寫程式時,認為內切外切是相交的極限情況,兩圓之間要麼沒有交點,要麼兩個交點。首先是關於兩圓之間關係的判斷同時求交點
int circleinter(circle a,circle b,point &p1,point &p2){
if(a.r>b.r) swap(a,b);
double r=a.r,R=b.r,d=(a.c-b.c).len();
//兩圓重合
if(fabs(d)<eps&&fabs(a.c.x-b.c.x)<eps&&fabs(a.c.y-b.c.y)<eps) return 0;
if(R>r+d || R+r<d) return 0; //內含或相離
double cosang=(r*r+d*d-R*R)/(2*r*d);
double sinang=sqrt(1.0-cosang*cosang);
point o=(b.c-a.c)*(1.0/d); //a.c->b.c方向上的單位向量
o=a.c+o*(r*cosang); //向量所指向的點是兩圓相交線段的中點
point p=point(a.c.y-b.c.y,b.c.x-a.c.x)*(1.0/d);
p1=o+pt*(r*sinang);
p2=o-pt*(r*sinang);
if(fabs(p1.x-p2.x)<eps&&fabs(p1.y-p2.y)<eps) return 1; //兩交點重合
return 2;
}
兩圓求相交面積,用積分來做
double circleinter(circle a,circle b){
if(a.r>b.r) swap(a,b);
double r=a.r,R=b.r,d=(a.c-b.c).len();
if(d<=R-r) return acos(-1.0)*r*r;
if(r+R<d+eps) return 0;
double x1=(r*r+d*d-R*R)/(2*d),x2=(R*R+d*d-r*r)/(2*d);
double ans=x1*sqrt(r*r-x1*x1)+r*r*acos(x1/r);
ans+=x2*sqrt(R*R-x2*x2)+R*R*acos(x2/r);
return ans;
}
此外,務必要考慮到極限情況,這也是計算幾何繁瑣的原因,常見的極限情況:
1.直線與多邊形相交,需要判斷直線與點的相交情況
2.兩直線平行、三點共線
3.判斷實心物體是否相交,注意一個在另一個內部的情況
4.模擬物體運動,將初始的相互接觸的狀態認為是碰撞