點、線、面相關的算法(1)

轉載原文:http://xuejx.blog.sohu.com/62019268.html


/* 需要包含的頭文件 */

#i nclude <cmath >

/* 常用的常量定義 */
const double INF = 1E200    
const double EP = 1E-10
const int MAXV = 300
const double PI = 3.14159265

/* 基本幾何結構 */
struct POINT
{
double x;
double y; POINT(double a=0, double b=0) { x=a; y=b;} //constructor
};
struct LINESEG
{
POINT s;
POINT e; LINESEG(POINT a, POINT b) { s=a; e=b;}
LINESEG() { }
};
struct LINE               // 直線的解析方程 a*x+b*y+c=0      爲統一表示,約定 a >= 0
{
       double a;
       double b;
       double c; LINE(double d1=1, double d2=-1, double d3=0) {a=d1; b=d2; c=d3;}
};

/********************\
*                        *
*       點的基本運算         *
*                        *
\********************/

double dist(POINT p1,POINT p2)                    // 返回兩點之間歐氏距離
{
return( sqrt( (p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y) ) );
}
bool equal_point(POINT p1,POINT p2)               // 判斷兩個點是否重合  
{
return ( (abs(p1.x-p2.x)<EP)&&(abs(p1.y-p2.y)<EP) );
}

/******************************************************************************
r=multiply(sp,ep,op),得到(sp-op)*(ep-op)的叉積
r>0:ep在矢量opsp的逆時針方向;
r=0:opspep三點共線;
r<0:ep在矢量opsp的順時針方向
*******************************************************************************/

double multiply(POINT sp,POINT ep,POINT op)
{
return((sp.x-op.x)*(ep.y-op.y)-(ep.x-op.x)*(sp.y-op.y));
}

/*******************************************************************************
r=dotmultiply(p1,p2,op),得到矢量(p1-op)和(p2-op)的點積,如果兩個矢量都非零矢量
r<0:兩矢量夾角爲銳角;r=0:兩矢量夾角爲直角;r>0:兩矢量夾角爲鈍角
*******************************************************************************/
double dotmultiply(POINT p1,POINT p2,POINT p0)
{
return ((p1.x-p0.x)*(p2.x-p0.x)+(p1.y-p0.y)*(p2.y-p0.y));
}

/* 判斷點p是否在線段l上,條件:(p在線段l所在的直線上)&& (點p在以線段l爲對角線的矩形內) */
bool online(LINESEG l,POINT p)
{
return((multiply(l.e,p,l.s)==0)
&&( ( (p.x-l.s.x)*(p.x-l.e.x)<=0 )&&( (p.y-l.s.y)*(p.y-l.e.y)<=0 ) ) );
}

// 返回點p以點o爲圓心逆時針旋轉alpha(單位:弧度)後所在的位置
POINT rotate(POINT o,double alpha,POINT p)
{
POINT tp;
p.x-=o.x;
p.y-=o.y;
tp.x=p.x*cos(alpha)-p.y*sin(alpha)+o.x;
tp.y=p.y*cos(alpha)+p.x*sin(alpha)+o.y;
return tp;
}

/* 返回頂角在o點,起始邊爲os,終止邊爲oe的夾角(單位:弧度)
角度小於pi,返回正值
角度大於pi,返回負值
可以用於求線段之間的夾角
*/
double angle(POINT o,POINT s,POINT e)
{
double cosfi,fi,norm;
double dsx = s.x - o.x;
double dsy = s.y - o.y;
double dex = e.x - o.x;
double dey = e.y - o.y;

cosfi=dsx*dex+dsy*dey;
norm=(dsx*dsx+dey*dey)*(dex*dex+dey*dey);
cosfi /= sqrt( norm );

if (cosfi >=      1.0 ) return 0;
      if (cosfi <= -1.0 ) return -3.1415926;

fi=acos(cosfi);
if (dsx*dey-dsy*dex>0) return fi;          // 說明矢量os 在矢量 oe的順時針方向
      return -fi;
}


       /*****************************\
      *                                 *
      *          線段及直線的基本運算       *
      *                                 *
      \*****************************/

/* 判斷點與線段的關係,用途很廣泛
本函數是根據下面的公式寫的,P是點C到線段AB所在直線的垂足

                    AC dot AB
            r =         ---------
                     ||AB||^2
                 (Cx-Ax)(Bx-Ax) + (Cy-Ay)(By-Ay)
              = -------------------------------
                              L^2

        r has the following meaning:

            r=0          P = A
            r=1          P = B
            r<0 P is on the backward extension of AB
r>1          P is on the forward extension of AB
            0<r<1 P is interior to AB
*/
double relation(POINT p,LINESEG l)
{
LINESEG tl;
tl.s=l.s;
tl.e=p;
return dotmultiply(tl.e,l.e,l.s)/(dist(l.s,l.e)*dist(l.s,l.e));
}

// 求點C到線段AB所在直線的垂足 P
POINT perpendicular(POINT p,LINESEG l)
{
double r=relation(p,l);
POINT tp;
tp.x=l.s.x+r*(l.e.x-l.s.x);
tp.y=l.s.y+r*(l.e.y-l.s.y);
return tp;
}
/* 求點p到線段l的最短距離,並返回線段上距該點最近的點np
注意:np是線段l上到點p最近的點,不一定是垂足 */
double ptolinesegdist(POINT p,LINESEG l,POINT &np)
{
double r=relation(p,l);
if(r<0)
{
np=l.s;
return dist(p,l.s);
}
if(r>1)
{
np=l.e;
return dist(p,l.e);
}
np=perpendicular(p,l);
return dist(p,np);
}

// 求點p到線段l所在直線的距離,請注意本函數與上個函數的區別  
double ptoldist(POINT p,LINESEG l)
{
return abs(multiply(p,l.e,l.s))/dist(l.s,l.e);
}

/* 計算點到折線集的最近距離,並返回最近點.
注意:調用的是ptolineseg()函數 */
double ptopointset(int vcount,POINT pointset[],POINT p,POINT &q)
{
int i;
double cd=double(INF),td;
LINESEG l;
POINT tq,cq;

for(i=0;i<vcount-1;i++)
{
l.s=pointset[i];

l.e=pointset[i+1];
td=ptolinesegdist(p,l,tq);
if(td<cd)
{
cd=td;
cq=tq;
}
}
q=cq;
return cd;
}
/* 判斷圓是否在多邊形內.ptolineseg()函數的應用2 */
bool CircleInsidePolygon(int vcount,POINT center,double radius,POINT polygon[])
{
POINT q;
double d;
q.x=0;
q.y=0;
d=ptopointset(vcount,polygon,center,q);
if(d<radius||fabs(d-radius)<EP)
return true;
else
return false;
}

/* 返回兩個矢量l1和l2的夾角的餘弦(-1 --- 1)注意:如果想從餘弦求夾角的話,注意反餘弦函數的定義域是從 0到pi */
double cosine(LINESEG l1,LINESEG l2)
{
return (((l1.e.x-l1.s.x)*(l2.e.x-l2.s.x) +
(l1.e.y-l1.s.y)*(l2.e.y-l2.s.y))/(dist(l1.e,l1.s)*dist(l2.e,l2.s))) );
}
// 返回線段l1與l2之間的夾角 單位:弧度 範圍(-pi,pi)
double lsangle(LINESEG l1,LINESEG l2)
{
POINT o,s,e;
o.x=o.y=0;
s.x=l1.e.x-l1.s.x;
s.y=l1.e.y-l1.s.y;
e.x=l2.e.x-l2.s.x;
e.y=l2.e.y-l2.s.y;
return angle(o,s,e);
}
// 如果線段u和v相交(包括相交在端點處)時,返回true
bool intersect(LINESEG u,LINESEG v)
{
return( (max(u.s.x,u.e.x)>=min(v.s.x,v.e.x))&&                         //排斥實驗
            (max(v.s.x,v.e.x)>=min(u.s.x,u.e.x))&&
            (max(u.s.y,u.e.y)>=min(v.s.y,v.e.y))&&
            (max(v.s.y,v.e.y)>=min(u.s.y,u.e.y))&&
            (multiply(v.s,u.e,u.s)*multiply(u.e,v.e,u.s)>=0)&&             //跨立實驗
            (multiply(u.s,v.e,v.s)*multiply(v.e,u.e,v.s)>=0));
}


//      (線段u和v相交)&&(交點不是雙方的端點) 時返回true    
bool intersect_A(LINESEG u,LINESEG v)
{
return((intersect(u,v))&&
           (!online(u,v.s))&&
           (!online(u,v.e))&&
           (!online(v,u.e))&&
           (!online(v,u.s)));
}


// 線段v所在直線與線段u相交時返回true;方法:判斷線段u是否跨立線段v  
bool intersect_l(LINESEG u,LINESEG v)
{
return multiply(u.s,v.e,v.s)*multiply(v.e,u.e,v.s)>=0;
}


// 根據已知兩點座標,求過這兩點的直線解析方程: a*x+b*y+c = 0      (a >= 0)  
LINE makeline(POINT p1,POINT p2)
{
       LINE tl;
       int sign = 1;
       tl.a=p2.y-p1.y;
       if(tl.a<0)
{
sign = -1;
tl.a=sign*tl.a;
}
tl.b=sign*(p1.x-p2.x);
tl.c=sign*(p1.y*p2.x-p1.x*p2.y);
return tl;
}

// 根據直線解析方程返回直線的斜率k,水平線返回 0,豎直線返回 1e200
double slope(LINE l)
{
if(abs(l.a) < 1e-20)return 0;
if(abs(l.b) < 1e-20)return INF;
return -(l.a/l.b);
}

// 返回直線的傾斜角alpha ( 0 - pi)
double alpha(LINE l)
{
if(abs(l.a)< EP)return 0;
if(abs(l.b)< EP)return PI/2;
double k=slope(l);
if(k>0)
          return atan(k);
       else
          return PI+atan(k);
}

// 求點p關於直線l的對稱點  
POINT symmetry(LINE l,POINT p)
{
       POINT tp;
       tp.x=((l.b*l.b-l.a*l.a)*p.x-2*l.a*l.b*p.y-2*l.a*l.c)/(l.a*l.a+l.b*l.b);
       tp.y=((l.a*l.a-l.b*l.b)*p.y-2*l.a*l.b*p.x-2*l.b*l.c)/(l.a*l.a+l.b*l.b);
       return tp;
}

// 如果兩條直線 l1(a1*x+b1*y+c1 = 0), l2(a2*x+b2*y+c2 = 0)相交,返回true,且返回交點p  
bool lineintersect(LINE l1,LINE l2,POINT &p) // 是 L1,L2
{
       double d=l1.a*l2.b-l2.a*l1.b;
       if(abs(d)<EP) // 不相交
return false;
p.x = (l2.c*l1.b-l1.c*l2.b)/d;
p.y = (l2.a*l1.c-l1.a*l2.c)/d;
return true;
}

// 如果線段l1和l2相交,返回true且交點由(inter)返回,否則返回false
bool intersection(LINESEG l1,LINESEG l2,POINT &inter)
{
LINE ll1,ll2;
ll1=makeline(l1.s,l1.e);
ll2=makeline(l2.s,l2.e);
if(lineintersect(ll1,ll2,inter))
{
return online(l1,inter);
}
else
return false;
}




/******************************\
* *
* 多邊形常用算法模塊 *
* *
\******************************/

// 如果無特別說明,輸入多邊形頂點要求按逆時針排列

/*
返回值:輸入的多邊形是簡單多邊形,返回true
要 求:輸入頂點序列按逆時針排序
說 明:簡單多邊形定義:
1:循環排序中相鄰線段對的交是他們之間共有的單個點
2:不相鄰的線段不相交
本程序默認第一個條件已經滿足
*/
bool issimple(int vcount,POINT polygon[])
{
int i,cn;
LINESEG l1,l2;
for(i=0;i<vcount;i++)
{
l1.s=polygon[i];
l1.e=polygon[(i+1)%vcount];
cn=vcount-3;
while(cn)
{
l2.s=polygon[(i+2)%vcount];
l2.e=polygon[(i+3)%vcount];
if(intersect(l1,l2))
break;
cn--;
}
if(cn)
return false;
}
return true;
}

// 返回值:按輸入順序返回多邊形頂點的凸凹性判斷,bc[i]=1,iff:第i個頂點是凸頂點
void checkconvex(int vcount,POINT polygon[],bool bc[])
{
int i,index=0;
POINT tp=polygon[0];
for(i=1;i<vcount;i++) // 尋找第一個凸頂點
{
if(polygon[i].y<tp.y||(polygon[i].y == tp.y&&polygon[i].x<tp.x))
{
tp=polygon[i];
index=i;
}
}
int count=vcount-1;
bc[index]=1;
while(count) // 判斷凸凹性
{
if(multiply(polygon[(index+1)%vcount],polygon[(index+2)%vcount],polygon[index])>=0 )
                bc[(index+1)%vcount]=1;
             else
                bc[(index+1)%vcount]=0;
             index++;
             count--;
          }
}

// 返回值:多邊形polygon是凸多邊形時,返回true  
bool isconvex(int vcount,POINT polygon[])
{
bool bc[MAXV];
checkconvex(vcount,polygon,bc);
for(int i=0;i<vcount;i++) // 逐一檢查頂點,是否全部是凸頂點
if(!bc[i])
return false;
return true;
}

// 返回多邊形面積(signed);輸入頂點按逆時針排列時,返回正值;否則返回負值
double area_of_polygon(int vcount,POINT polygon[])
{
int i;
double s;
if (vcount<3) return 0;
s=polygon[0].y*(polygon[vcount-1].x-polygon[1].x);
for (i=1;i<vcount;i++)
s+=polygon[i].y*(polygon[(i-1)].x-polygon[(i+1)%vcount].x);
return s/2;
}

// 如果輸入頂點按逆時針排列,返回true
bool isconterclock(int vcount,POINT polygon[])
{
return area_of_polygon(vcount,polygon)>0;
}
// 另一種判斷多邊形頂點排列方向的方法  
bool isccwize(int vcount,POINT polygon[])
{
       int i,index;
       POINT a,b,v;
       v=polygon[0];
       index=0;
       for(i=1;i<vcount;i++) // 找到最低且最左頂點,肯定是凸頂點
{
if(polygon[i].y<v.y||polygon[i].y == v.y && polygon[i].x<v.x)
{
index=i;
}
}
a=polygon[(index-1+vcount)%vcount]; // 頂點v的前一頂點
b=polygon[(index+1)%vcount]; // 頂點v的後一頂點
return multiply(v,b,a)>0;
}

/* 射線法判斷點q與多邊形polygon的位置關係,要求polygon爲簡單多邊形,頂點逆時針排列
       如果點在多邊形內:       返回0
       如果點在多邊形邊上: 返回1
       如果點在多邊形外: 返回2
*/
int insidepolygon(int vcount,POINT Polygon[],POINT q)
{
int c=0,i,n;
LINESEG l1,l2;
bool bintersect_a,bonline1,bonline2,bonline3;
double r1,r2;

l1.s=q;
l1.e=q;
l1.e.x=double(INF);
n=vcount;
for (i=0;i<vcount;i++)
{
l2.s=Polygon[i];
l2.e=Polygon[(i+1)%n];
if(online(l2,q))return 1; // 如果點在邊上,返回1
if ( (bintersect_a=intersect_A(l1,l2))|| // 相交且不在端點
(
(bonline1=online(l1,Polygon[(i+1)%n]))&& // 第二個端點在射線上
(
(!(bonline2=online(l1,Polygon[(i+2)%n])))&& /* 前一個端點和後一個
端點在射線兩側 */
((r1=multiply(Polygon[i],Polygon[(i+1)%n],l1.s)*multiply(Polygon[(i+1)%n],Polygon[(i+2)%n],l1.s))>0) ||    
(bonline3=online(l1,Polygon[(i+2)%n]))&&         /* 下一條邊是水平線,
前一個端點和後一個端點在射線兩側      */
          ((r2=multiply(Polygon[i],Polygon[(i+2)%n],l1.s)*multiply(Polygon[(i+2)%n],
Polygon[(i+3)%n],l1.s))>0)
            )
          )
       ) c++;
}
         if(c%2 == 1)
            return 0;
         else
            return 2;
}
// 點q是凸多邊形polygon內時,返回true;注意:多邊形polygon一定要是凸多邊形  
bool InsideConvexPolygon(int vcount,POINT polygon[],POINT q) // 可用於三角形!
{
POINT p;
LINESEG l;
int i;
p.x=0;p.y=0;
for(i=0;i<vcount;i++) // 尋找一個肯定在多邊形polygon內的點p:多邊形頂點平均值
{
p.x+=polygon[i].x;
p.y+=polygon[i].y;
}
p.x /= vcount;
p.y /= vcount;

for(i=0;i<vcount;i++)
{
l.s=polygon[i];l.e=polygon[(i+1)%vcount];
if(multiply(p,l.e,l.s)*multiply(q,l.e,l.s)<0) /* 點p和點q在邊l的兩側,說明
點q肯定在多邊形外 */
break;
}
return (i==vcount);
}

/**********************************************
尋找凸包的graham 掃描法
PointSet爲輸入的點集;
ch爲輸出的凸包上的點集,按照逆時針方向排列;
n爲PointSet中的點的數目
len爲輸出的凸包上的點的個數
**********************************************/

void Graham_scan(POINT PointSet[],POINT ch[],int n,int &len)
{
int i,j,k=0,top=2;
POINT tmp;
// 選取PointSet中y座標最小的點PointSet[k],如果這樣的點有多個,則取最左邊的一個
for(i=1;i<n;i++)
if ( PointSet[i].y<PointSet[k].y ||
(PointSet[i].y==PointSet[k].y) && (PointSet[i].x<PointSet[k].x) )
k=i;
tmp=PointSet[0];
PointSet[0]=PointSet[k];
PointSet[k]=tmp; // 現在PointSet中y座標最小的點在PointSet[0]
for (i=1;i<n-1;i++) /* 對頂點按照相對PointSet[0]的極角從小到大進行排序,極角相同
的按照距離PointSet[0]從近到遠進行排序 */
{
k=i;
for (j=i+1;j<n;j++)
if ( multiply(PointSet[j],PointSet[k],PointSet[0])>0 ||      // 極角更小    
          (multiply(PointSet[j],PointSet[k],PointSet[0])==0) && /* 極角相等,
距離更短 */             dist(PointSet[0],PointSet[j])<dist(PointSet[0],PointSet[k]) )
k=j;
tmp=PointSet[i];
PointSet[i]=PointSet[k];
PointSet[k]=tmp;
}
ch[0]=PointSet[0];
ch[1]=PointSet[1];
ch[2]=PointSet[2];
for (i=3;i<n;i++)
{
while (multiply(PointSet[i],ch[top],ch[top-1])>=0) top--;
ch[++top]=PointSet[i];
}
len=top+1;
}

// 捲包裹法求點集凸殼,參數說明同graham算法    
void ConvexClosure(POINT PointSet[],POINT ch[],int n,int &len)
{
int top=0,i,index,first;
double curmax,curcos,curdis;
POINT tmp;
LINESEG l1,l2;
bool use[MAXV];
tmp=PointSet[0];
index=0;
// 選取y最小點,如果多於一個,則選取最左點
for(i=1;i<n;i++)
{
if(PointSet[i].y<tmp.y||PointSet[i].y == tmp.y&&PointSet[i].x<tmp.x)
{
index=i;
}
use[i]=false;
}
tmp=PointSet[index];
first=index;
use[index]=true;

index=-1;
ch[top++]=tmp;
tmp.x-=100;
l1.s=tmp;
l1.e=ch[0];
l2.s=ch[0];

while(index!=first)
{
curmax=-100;
curdis=0;
// 選取與最後一條確定邊夾角最小的點,即餘弦值最大者
for(i=0;i<n;i++)
{
if(use[i])continue;
l2.e=PointSet[i];
curcos=cosine(l1,l2); // 根據cos值求夾角餘弦,範圍在 (-1 -- 1 )
if(curcos>curmax || fabs(curcos-curmax)<1e-6 && dist(l2.s,l2.e)>curdis)
{
curmax=curcos;
index=i;
curdis=dist(l2.s,l2.e);
}

}
use[first]=false;                //清空第first個頂點標誌,使最後能形成封閉的hull
use[index]=true;
ch[top++]=PointSet[index];
l1.s=ch[top-2];
l1.e=ch[top-1];
l2.s=ch[top-1];
}
len=top-1;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章