1. 程式人生 > 實用技巧 >計算幾何:常見幾何元素關係的判斷以及計算

計算幾何:常見幾何元素關係的判斷以及計算

求三角形面積,海倫公式: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.模擬物體運動,將初始的相互接觸的狀態認為是碰撞