1 計算幾何幾何函數庫 2 ------------------------------------------------------------------------------------------------------------------------------- 3 導引 4 1. 常量定義和包含文件 5 2. 基本數據結構 6 3. 精度控制 7 ㈠ 點的基本運算 8 1. 平面上兩點之間距離 9 2. 判斷兩點是否重合 10 3. 矢量叉乘 11 4. 矢量點乘 12 5. 判斷點是否在線段上 13 6. 求一點饒某點旋轉后的坐標 14 7. 求矢量夾角 15 ㈡ 線段及直線的基本運算 16 1. 點與線段的關系 17 2. 求點到線段所在直線垂線的垂足 18 3. 點到線段的最近點 19 4. 點到線段所在直線的距離 20 5. 點到折線集的最近距離 21 6. 判斷圓是否在多邊形內 22 7. 求矢量夾角余弦 23 8. 求線段之間的夾角 24 9. 判斷線段是否相交 25 10.判斷線段是否相交但不交在端點處 26 11.求點關於某直線的對稱點 27 12.判斷兩條直線是否相交及求直線交點 28 13.判斷線段是否相交,如果相交返回交點 29 ㈢ 多邊形常用算法模塊 30 1. 判斷多邊形是否簡單多邊形 31 2. 檢查多邊形頂點的凸凹性 32 3. 判斷多邊形是否凸多邊形 33 4. 求多邊形面積 34 5. 判斷多邊形頂點的排列方向 35 7. 射線法判斷點是否在多邊形內 36 8. 判斷點是否在凸多邊形內 37 9. 尋找點集的graham算法 38 10.尋找點集凸包的卷包裹法 39 11.凸包MelkMan算法的實現 40 12. 凸多邊形的直徑 41 13.求凸多邊形的重心 42 =========================================================================== 43 導引 44 /* 需要包含的頭文件 */ 45 #include <cmath > 46 /* 常量定義 */ 47 const double INF = 1E200; 48 const double EP = 1E-10; 49 const int MAXV = 300; 50 const double PI = 3.14159265; 51 /* 基本幾何結構 */ 52 struct POINT 53 { 54 double x; 55 double y; 56 POINT(double a=0, double b=0) { x=a; y=b;} 57 }; 58 struct LINESEG 59 { 60 POINT s; 61 POINT e; 62 LINESEG(POINT a, POINT b) { s=a; e=b;} 63 LINESEG() { } 64 }; 65 // 直線的解析方程 a*x+b*y+c=0 為統一表示,約定 a>= 0 66 struct LINE 67 { 68 double a; 69 double b; 70 double c; 71 LINE(double d1=1, double d2=-1, double d3=0) {a=d1; b=d2; c=d3;} 72 }; 73 //線段樹 74 struct LINETREE 75 { 76 } 77 //浮點誤差的處理 78 int dblcmp(double d) 79 { 80 if(fabs(d)<EP) 81 return 0 ; 82 return (d>0) ?1 :-1 ; 83 } 84 <一>點的基本運算 85 // 返回兩點之間歐氏距離 86 double dist(POINT p1,POINT p2) 87 { 88 return( sqrt( (p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y) ) ); 89 } 90 // 判斷兩個點是否重合 91 bool equal_point(POINT p1,POINT p2) 92 { 93 return ( (abs(p1.x-p2.x)<EP)&&(abs(p1.y-p2.y)<EP) ); 94 } 95 /*(sp-op)*(ep-op)的叉積 96 r=multiply(sp,ep,op),得到(sp-op)*(ep-op)的叉積 97 r>0:sp在矢量op ep的順時針方向; 98 r=0:op sp ep三點共線; 99 r<0: sp在矢量op ep的逆時針方向 */ 100 double multiply(POINT sp,POINT ep,POINT op) 101 { 102 return((sp.x-op.x)*(ep.y-op.y) - (ep.x-op.x)*(sp.y-op.y)); 103 } 104 double amultiply(POINT sp,POINT ep,POINT op) 105 { 106 return fabs((sp.x-op.x)*(ep.y-op.y)-(ep.x-op.x)*(sp.y-op.y)); 107 } 108 /*矢量(p1-op)和(p2-op)的點積 109 r=dotmultiply(p1,p2,op),得到矢量(p1-op)和(p2-op)的點積如果兩個矢量都非零矢量 110 r < 0: 兩矢量夾角為銳角; 111 r = 0:兩矢量夾角為直角; 112 r > 0: 兩矢量夾角為鈍角 */ 113 double dotmultiply(POINT p1,POINT p2,POINT p0) 114 { 115 return ((p1.x-p0.x)*(p2.x-p0.x) + (p1.y-p0.y)*(p2.y-p0.y)); 116 } 117 /* 判斷點p是否在線段l上 118 條件:(p在線段l所在的直線上)&& (點p在以線段l為對角線的矩形內) */ 119 bool online(LINESEG l,POINT p) 120 { 121 return ((multiply(l.e,p,l.s)==0) 122 && ( ( (p.x-l.s.x) * (p.x-l.e.x) <=0 ) && ( (p.y-l.s.y)*(p.y-l.e.y) <=0 ) ) ); 123 } 124 // 返回點p以點o為圓心逆時針旋轉alpha(單位:弧度)后所在的位置 125 POINT rotate(POINT o,double alpha,POINT p) 126 { 127 POINT tp; 128 p.x -=o.x; 129 p.y -=o.y; 130 tp.x=p.x*cos(alpha) - p.y*sin(alpha)+o.x; 131 tp.y=p.y*cos(alpha) + p.x*sin(alpha)+o.y; 132 return tp; 133 } 134 /* 返回頂角在o點,起始邊為os,終止邊為oe的夾角(單位:弧度) 135 角度小於pi,返回正值 136 角度大於pi,返回負值 137 可以用於求線段之間的夾角 */ 138 double angle(POINT o,POINT s,POINT e) 139 { 140 double cosfi,fi,norm; 141 double dsx = s.x - o.x; 142 double dsy = s.y - o.y; 143 double dex = e.x - o.x; 144 double dey = e.y - o.y; 145 cosfi=dsx*dex+dsy*dey; 146 norm=(dsx*dsx+dey*dey)*(dex*dex+dey*dey); 147 cosfi /= sqrt( norm ); 148 if (cosfi >= 1.0 ) return 0; 149 if (cosfi <= -1.0 ) return -3.1415926; 150 fi=acos(cosfi); 151 if (dsx*dey-dsy*dex>0) return fi;// 說明矢量os 在矢量 oe的順時針方向 152 return -fi; 153 } 154 <二>線段及直線的基本運算 155 /* 判斷點C在線段AB所在的直線l上垂足P的與線段AB的關系 156 本函數是根據下面的公式寫的,P是點C到線段AB所在直線的垂足 157 AC dot AB 158 r = ---------------------- 159 ||AB||^2 160 (Cx-Ax)(Bx-Ax) + (Cy-Ay)(By-Ay) 161 = ---------------------------------------------------- 162 L^2 163 r has the following meaning: 164 r=0 P = A 165 r=1 P = B 166 r<0 P is on the backward extension of AB 167 r>1 P is on the forward extension of AB 168 0<r<1 P is interior to AB 169 */ 170 double relation(POINT c,LINESEG l) 171 { 172 LINESEG tl; 173 tl.s=l.s; 174 tl.e=c; 175 return dotmultiply(tl.e,l.e,l.s)/(dist(l.s,l.e)*dist(l.s,l.e)); 176 } 177 // 求點C到線段AB所在直線的垂足 P 178 POINT perpendicular(POINT p,LINESEG l) 179 { 180 double r=relation(p,l); 181 POINT tp; 182 tp.x=l.s.x+r*(l.e.x-l.s.x); 183 tp.y=l.s.y+r*(l.e.y-l.s.y); 184 return tp; 185 } 186 /* 求點p到線段l的最短距離 187 返回線段上距該點最近的點np 注意:np是線段l上到點p最近的點,不一定是垂足 */ 188 double ptolinesegdist(POINT p,LINESEG l,POINT &np) 189 { 190 double r=relation(p,l); 191 if(r<0) 192 { 193 np=l.s; 194 return dist(p,l.s); 195 } 196 if(r>1) 197 { 198 np=l.e; 199 return dist(p,l.e); 200 } 201 np=perpendicular(p,l); 202 return dist(p,np); 203 } 204 // 求點p到線段l所在直線的距離 205 //請注意本函數與上個函數的區別 206 double ptoldist(POINT p,LINESEG l) 207 { 208 return abs(multiply(p,l.e,l.s))/dist(l.s,l.e); 209 } 210 /* 計算點到折線集的最近距離,並返回最近點. 211 注意:調用的是ptolineseg()函數 */ 212 double ptopointset(int vcount, POINT pointset[], POINT p, POINT &q) 213 { 214 int i; 215 double cd=double(INF),td; 216 LINESEG l; 217 POINT tq,cq; 218 for(i=0;i<vcount-1;i++) 219 { 220 l.s=pointset[i]; 221 l.e=pointset[i+1]; 222 td=ptolinesegdist(p,l,tq); 223 if(td<cd) 224 { 225 cd=td; 226 cq=tq; 227 } 228 } 229 q=cq; 230 return cd; 231 } 232 /* 判斷圓是否在多邊形內*/ 233 bool CircleInsidePolygon(int vcount,POINT center,double radius,POINT polygon[]) 234 { 235 POINT q; 236 double d; 237 q.x=0; 238 q.y=0; 239 d=ptopointset(vcount,polygon,center,q); 240 if(d<radius||fabs(d-radius)<EP) return true; 241 else return false; 242 } 243 /* 返回兩個矢量l1和l2的夾角的余弦 (-1 ~ 1) 244 注意:如果想從余弦求夾角的話,注意反余弦函數的值域是從 0到pi */ 245 double cosine(LINESEG l1,LINESEG l2) 246 { 247 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))) ); 248 } 249 // 返回線段l1與l2之間的夾角 250 //單位:弧度 范圍(-pi,pi) 251 double lsangle(LINESEG l1,LINESEG l2) 252 { 253 POINT o,s,e; 254 o.x=o.y=0; 255 s.x=l1.e.x-l1.s.x; 256 s.y=l1.e.y-l1.s.y; 257 e.x=l2.e.x-l2.s.x; 258 e.y=l2.e.y-l2.s.y; 259 return angle(o,s,e); 260 } 261 //判斷線段u和v相交(包括相交在端點處) 262 bool intersect(LINESEG u,LINESEG v) 263 { 264 return ( (max(u.s.x,u.e.x)>=min(v.s.x,v.e.x))&& //排斥實驗 265 (max(v.s.x,v.e.x)>=min(u.s.x,u.e.x))&& 266 (max(u.s.y,u.e.y)>=min(v.s.y,v.e.y))&& 267 (max(v.s.y,v.e.y)>=min(u.s.y,u.e.y))&& 268 (multiply(v.s,u.e,u.s)*multiply(u.e,v.e,u.s)>=0)&& //跨立實驗 269 (multiply(u.s,v.e,v.s)*multiply(v.e,u.e,v.s)>=0)); 270 } 271 // 判斷線段u和v相交(不包括雙方的端點) 272 bool intersect_A(LINESEG u,LINESEG v) 273 { 274 return ((intersect(u,v)) && 275 (!online(u,v.s)) && 276 (!online(u,v.e)) && 277 (!online(v,u.e)) && 278 (!online(v,u.s))); 279 } 280 // 判斷線段v所在直線與線段u相交 281 方法:判斷線段u是否跨立線段v 282 bool intersect_l(LINESEG u,LINESEG v) 283 { 284 return multiply(u.s,v.e,v.s)*multiply(v.e,u.e,v.s)>=0; 285 } 286 // 根據已知兩點坐標,求過這兩點的直線解析方程: a*x+b*y+c = 0 (a >= 0) 287 LINE makeline(POINT p1,POINT p2) 288 { 289 LINE tl; 290 int sign = 1; 291 tl.a=p2.y-p1.y; 292 if(tl.a<0) 293 { 294 sign = -1; 295 tl.a=sign*tl.a; 296 } 297 tl.b=sign*(p1.x-p2.x); 298 tl.c=sign*(p1.y*p2.x-p1.x*p2.y); 299 return tl; 300 } 301 // 根據直線解析方程返回直線的斜率k,水平線返回 0,豎直線返回 1e200 302 double slope(LINE l) 303 { 304 if(abs(l.a) < 1e-20)return 0; 305 if(abs(l.b) < 1e-20)return INF; 306 return -(l.a/l.b); 307 } 308 // 返回直線的傾斜角alpha ( 0 - pi) 309 // 注意:atan()返回的是 -PI/2 ~ PI/2 310 double alpha(LINE l) 311 { 312 if(abs(l.a)< EP)return 0; 313 if(abs(l.b)< EP)return PI/2; 314 double k=slope(l); 315 if(k>0) 316 return atan(k); 317 else 318 return PI+atan(k); 319 } 320 // 求點p關於直線l的對稱點 321 POINT symmetry(LINE l,POINT p) 322 { 323 POINT tp; 324 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); 325 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); 326 return tp; 327 } 328 // 如果兩條直線 l1(a1*x+b1*y+c1 = 0), l2(a2*x+b2*y+c2 = 0)相交,返回true,且返回交點p 329 bool lineintersect(LINE l1,LINE l2,POINT &p) // 是 L1,L2 330 { 331 double d=l1.a*l2.b-l2.a*l1.b; 332 if(abs(d)<EP) // 不相交 333 return false; 334 p.x = (l2.c*l1.b-l1.c*l2.b)/d; 335 p.y = (l2.a*l1.c-l1.a*l2.c)/d; 336 return true; 337 } 338 // 如果線段l1和l2相交,返回true且交點由(inter)返回,否則返回false 339 bool intersection(LINESEG l1,LINESEG l2,POINT &inter) 340 { 341 LINE ll1,ll2; 342 ll1=makeline(l1.s,l1.e); 343 ll2=makeline(l2.s,l2.e); 344 if(lineintersect(ll1,ll2,inter)) return online(l1,inter); 345 else return false; 346 } 347 <三> 多邊形常用算法模塊 348 如果無特別說明,輸入多邊形頂點要求按逆時針排列 349 // 返回多邊形面積(signed); 350 // 輸入頂點按逆時針排列時,返回正值;否則返回負值 351 double area_of_polygon(int vcount,POINT polygon[]) 352 { 353 int i; 354 double s; 355 if (vcount<3) 356 return 0; 357 s=polygon[0].y*(polygon[vcount-1].x-polygon[1].x); 358 for (i=1;i<vcount;i++) 359 s+=polygon[i].y*(polygon[(i-1)].x-polygon[(i+1)%vcount].x); 360 return s/2; 361 } 362 // 判斷頂點是否按逆時針排列 363 // 如果輸入頂點按逆時針排列,返回true 364 bool isconterclock(int vcount,POINT polygon[]) 365 { 366 return area_of_polygon(vcount,polygon)>0; 367 } 368 /*射線法判斷點q與多邊形polygon的位置關系 369 要求polygon為簡單多邊形,頂點時針排列 370 如果點在多邊形內: 返回0 371 如果點在多邊形邊上:返回1 372 如果點在多邊形外: 返回2 */ 373 int insidepolygon(POINT q) 374 { 375 int c=0,i,n; 376 LINESEG l1,l2; 377 l1.s=q; l1.e=q;l1.e.x=double(INF); 378 n=vcount; 379 for (i=0;i<vcount;i++) 380 { 381 l2.s=Polygon[i]; 382 l2.e=Polygon[(i+1)%vcount]; 383 double ee= Polygon[(i+2)%vcount].x; 384 double ss= Polygon[(i+3)%vcount].y; 385 if(online(l2,q)) 386 return 1; 387 if(intersect_A(l1,l2)) 388 c++; // 相交且不在端點 389 if(online(l1,l2.e)&& !online(l1,l2.s) && l2.e.y>l2.e.y) 390 c++;//l2的一個端點在l1上且該端點是兩端點中縱坐標較大的那個 391 if(!online(l1,l2.e)&& online(l1,l2.s) && l2.e.y<l2.e.y) 392 c++;//忽略平行邊 393 } 394 if(c%2 == 1) 395 return 0; 396 else 397 return 2; 398 } 399 //判斷點q在凸多邊形polygon內 400 // 點q是凸多邊形polygon內[包括邊上]時,返回true 401 // 注意:多邊形polygon一定要是凸多邊形 402 bool InsideConvexPolygon(int vcount,POINT polygon[],POINT q) 403 { 404 POINT p; 405 LINESEG l; 406 int i; 407 p.x=0; p.y=0; 408 for(i=0;i<vcount;i++) // 尋找一個肯定在多邊形polygon內的點p:多邊形頂點平均值 409 { 410 p.x+=polygon[i].x; 411 p.y+=polygon[i].y; 412 } 413 p.x /= vcount; 414 p.y /= vcount; 415 for(i=0;i<vcount;i++) 416 { 417 l.s=polygon[i]; 418 l.e=polygon[(i+1)%vcount]; 419 if(multiply(p,l.e,l.s)*multiply(q,l.e,l.s)<0) 420 /* 點p和點q在邊l的兩側,說明點q肯定在多邊形外 */ 421 return false; 422 } 423 return true; 424 } 425 /*尋找凸包的graham 掃描法 426 PointSet為輸入的點集; 427 ch為輸出的凸包上的點集,按照逆時針方向排列; 428 n為PointSet中的點的數目 429 len為輸出的凸包上的點的個數 */ 430 void Graham_scan(POINT PointSet[],POINT ch[],int n,int &len) 431 { 432 int i,j,k=0,top=2; 433 POINT tmp; 434 // 選取PointSet中y坐標最小的點PointSet[k],如果這樣的點有多個,則取最左邊的一個 435 for(i=1;i<n;i++) 436 if ( PointSet[i].y<PointSet[k].y || (PointSet[i].y==PointSet[k].y) 437 && (PointSet[i].x<PointSet[k].x) ) 438 k=i; 439 tmp=PointSet[0]; 440 PointSet[0]=PointSet[k]; 441 PointSet[k]=tmp; // 現在PointSet中y坐標最小的點在PointSet[0] 442 for (i=1;i<n-1;i++) /* 對頂點按照相對PointSet[0]的極角從小到大進行排序,極角相同 443 的按照距離PointSet[0]從近到遠進行排序 */ 444 { 445 k=i; 446 for (j=i+1;j<n;j++) 447 if ( multiply(PointSet[j],PointSet[k],PointSet[0])>0 || // 極角更小 448 (multiply(PointSet[j],PointSet[k],PointSet[0])==0) && /*極角相等,距離更短 */ dist(PointSet[0],PointSet[j])<dist(PointSet[0],PointSet[k]) ) 449 k=j; 450 tmp=PointSet[i]; 451 PointSet[i]=PointSet[k]; 452 PointSet[k]=tmp; 453 } 454 ch[0]=PointSet[0]; 455 ch[1]=PointSet[1]; 456 ch[2]=PointSet[2]; 457 for (i=3;i<n;i++) 458 { 459 while (multiply(PointSet[i],ch[top],ch[top-1])>=0) top--; 460 ch[++top]=PointSet[i]; 461 } 462 len=top+1; 463 } 464 // 卷包裹法求點集凸殼,參數說明同graham算法 465 void ConvexClosure(POINT PointSet[],POINT ch[],int n,int &len) 466 { 467 int top=0,i,index,first; 468 double curmax,curcos,curdis; 469 POINT tmp; 470 LINESEG l1,l2; 471 bool use[MAXV]; 472 tmp=PointSet[0]; 473 index=0; 474 // 選取y最小點,如果多於一個,則選取最左點 475 for(i=1;i<n;i++) 476 { 477 if(PointSet[i].y<tmp.y||PointSet[i].y == tmp.y&&PointSet[i].x<tmp.x) 478 { 479 index=i; 480 } 481 use[i]=false; 482 } 483 tmp=PointSet[index]; 484 first=index; 485 use[index]=true; 486 index=-1; 487 ch[top++]=tmp; 488 tmp.x-=100; 489 l1.s=tmp; 490 l1.e=ch[0]; 491 l2.s=ch[0]; 492 while(index!=first) 493 { 494 curmax=-100; 495 curdis=0; 496 // 選取與最后一條確定邊夾角最小的點,即余弦值最大者 497 for(i=0;i<n;i++) 498 { 499 if(use[i])continue; 500 l2.e=PointSet[i]; 501 curcos=cosine(l1,l2); // 根據cos值求夾角余弦,范圍在 (-1 -- 1 ) 502 if(curcos>curmax || fabs(curcos-curmax)<1e-6 && dist(l2.s,l2.e)>curdis) 503 { 504 curmax=curcos; 505 index=i; 506 curdis=dist(l2.s,l2.e); 507 } 508 } 509 use[first]=false; //清空第first個頂點標志,使最后能形成封閉的hull 510 use[index]=true; 511 ch[top++]=PointSet[index]; 512 l1.s=ch[top-2]; 513 l1.e=ch[top-1]; 514 l2.s=ch[top-1]; 515 } 516 len=top-1; 517 } 518 // 求凸多邊形的重心,要求輸入多邊形按逆時針排序 519 POINT gravitycenter(int vcount,POINT polygon[]) 520 { 521 POINT tp; 522 double x,y,s,x0,y0,cs,k; 523 x=0;y=0;s=0; 524 for(int i=1;i<vcount-1;i++) 525 { 526 x0=(polygon[0].x+polygon[i].x+polygon[i+1].x)/3; 527 y0=(polygon[0].y+polygon[i].y+polygon[i+1].y)/3; //求當前三角形的重心 528 cs=multiply(polygon[i],polygon[i+1],polygon[0])/2; 529 //三角形面積可以直接利用該公式求解 530 if(abs(s)<1e-20) 531 { 532 x=x0;y=y0;s+=cs;continue; 533 } 534 k=cs/s; //求面積比例 535 x=(x+k*x0)/(1+k); 536 y=(y+k*y0)/(1+k); 537 s += cs; 538 } 539 tp.x=x; 540 tp.y=y; 541 return tp; 542 } 543 /*所謂凸多邊形的直徑,即凸多邊形任兩個頂點的最大距離。下面的算法 544 僅耗時O(n),是一個優秀的算法。 輸入必須是一個凸多邊形,且頂點 545 必須按順序(順時針、逆時針均可)依次輸入。若輸入不是凸多邊形 546 而是一般點集,則要先求其凸包。 就是先求出所有跖對,然后求出每 547 個跖對的距離,取最大者。點數要多於5個*/ 548 void Diameter(POINT ch[],int n,double &dia) 549 { 550 int znum=0,i,j,k=1; 551 int zd[MAXV][2]; 552 double tmp; 553 while(amultiply(ch[0],ch[k+1],ch[n-1]) > amultiply(ch[0],ch[k],ch[n-1])-EP) 554 k++; 555 i=0; 556 j=k; 557 while(i<=k && j<n) 558 { 559 zd[znum][0]=i; 560 zd[znum++][1]=j; 561 while(amultiply(ch[i+1],ch[j+1],ch[i]) > amultiply(ch[i+1],ch[j],ch[i]) – EP 562 && j< n-1) 563 { 564 zd[znum][0]=i; 565 zd[znum++][1]=j; 566 j++; 567 } 568 i++; 569 } 570 dia=-1.0; 571 for(i=0;i<znum;i++) 572 { 573 printf("%d %d/n",zd[i][0],zd[i][1]); 574 tmp=dist(ch[zd[i][0]],ch[zd[i][1]]); 575 if(dia<tmp) 576 dia=tmp; 577 } 578 }