計算幾何

計算幾何
基礎
向量叉乘
  • A (x1,y1)  B(x2,y2)  A×B= x1y2-x2y1
  • 叉乘判斷向量位置,
  • P×Q < 0  P Q 左側,
  • P×Q > 0  P Q 右側,
  • P×Q > 0  P Q 共線,
向量點乘
  • A (x1,y1)  B(x2,y2)  A·B=x1x2+y1y2    a*b=|a||b|cos0
凸包
Jarvis步進法: O(nH) 
  • (點少時,H是點個數) 建議 用Graham法
思路:
  • 縱座標最小的那個點一定是凸包上的點,例如圖上的 P0。
  • 從 P0 開始,按逆時針的方向,逐個找凸包上的點,每前進一步找到一個點,所以叫作步進法。
  • 怎麼找下一個點呢?利用夾角。假設現在已經找到 {P0,P1,P2} 了,要找下一個點:剩下的點分別和 P2 組成向量,設這個向量與向量P1P2的夾角爲 β 。當 β 最小時就是所要求的下一個點了,此處爲 P3 。
注意:
  • 找第二個點 P1 時,因爲已經找到的只有 P0 一個點,所以向量只能和水平線作夾角 α,當 α 最小時求得第二個點。
共線情況:如果直線 P2P3 上還有一個點 P4,即三個點共線,此時由向量P2P3 和向量P2P4 產生的兩個 β 是相同的。我們應該把 P3、P4 都當做凸包上的點,並且把距離 P2 最遠的那個點(即圖中的P4)作爲最後搜索到的點,繼續找它的下一個連接點。

Graham法: O(n㏒n) 
時間複雜度:O(n㏒n) 
思路:Graham掃描的思想和Jarris步進法類似,也是先找到凸包上的一個點,然後從那個點開始按逆時針方向逐個找凸包上的點,但它不是利用夾角。 
 
步驟:
把所有點放在二維座標系中,則縱座標最小的點一定是凸包上的點,如圖中的P0。
把所有點的座標平移一下,使 P0 作爲原點,如上圖。
計算各個點相對於 P0 的幅角 α ,按從小到大的順序對各個點排序。當 α 相同時,距離 P0 比較近的排在前面。例如上圖得到的結果爲 P1,P2,P3,P4,P5,P6,P7,P8。我們由幾何知識可以知道,結果中第一個點 P1 和最後一個點 P8 一定是凸包上的點。 
(以上是準備步驟,以下開始求凸包) 
以上,我們已經知道了凸包上的第一個點 P0 和第二個點 P1,我們把它們放在棧裏面。現在從步驟3求得的那個結果裏,把 P1 後面的那個點拿出來做當前點,即 P2 。接下來開始找第三個點:
連接P0和棧頂的那個點,得到直線 L 。看當前點是在直線 L 的右邊還是左邊。如果在直線的右邊就執行步驟5;如果在直線上,或者在直線的左邊就執行步驟6。
如果在右邊,則棧頂的那個元素不是凸包上的點,把棧頂元素出棧。執行步驟4。
當前點是凸包上的點,把它壓入棧,執行步驟7。
檢查當前的點 P2 是不是步驟3那個結果的最後一個元素。是最後一個元素的話就結束。如果不是的話就把 P2 後面那個點做當前點,返回步驟4。
最後,棧中的元素就是凸包上的點了。 
以下爲用Graham掃描法動態求解的過程: 


    
應用
規則圖形
判斷點在線段上
Q(x,y),線段P1P2 (x2-x1,y2-y1) 則向量共線   P1Q×P1P2 =0  即(x(y2-y1)-(x2-x1)y)= 0
並且 min(x1,x2)    <=x<=max(x1,x2)&&min(y1,y2)<=y<=max(y1,y2)// 在線段內部
判斷兩線段相交    
快速排斥實驗
  • 兩個線段爲對角線組成的矩形R和矩形T 若R,T不想交-->不想交,若相交進行跨立實驗判斷
  • 方法:  假設 P1 = (x1, y1), P2 = (x2, y2), Q1 = (x3, y3), Q2 = (x4, y4),設矩形 R 的 x 座標的最小邊界爲 RX1 = min(x1, x2),RX2=max(x1,x2) ,RY1=min(y1,y2)以此類推,將矩形表示爲 R = (RX1, RY1, RX2, RY2) 的形式,若兩矩形相交,則相交的部分構成了一個新的矩形 F,我們可以知道 F 的 FX1 = max(RX1, TX1), FY2 = max(RY1, TY1), 
  • FX2 = min(RX2, TX2), FY2 = min(RY2, TX2),得到 F 的各個值之後,只要判斷矩形 F 是否成立就知道 R 和 T 到底有沒有相交了,若 FX1 > FX2 或 FY1 > Fy1 則 F 無法構成,RT不相交,否則 RT相交
跨立實驗(互相跨立)
  •      Q1P1×Q1P2 * Q1Q2×Q1Q1P2 >=0 && Q1P2×P1P2 * P1P2×Q2P2 >=0 
線段與圓相交
三種情況, 第一種, 兩個端點都在圓內,不相交,第二種,一個在圓外,一個圓內 一定相交;
第三種都在圓外,需要判斷;
double x,y,r;
vector<pair<double,double> > V;
typedef pair<double,double> PAIR;
PAIR yuan;
bool judge(PAIR P)// 判斷是否在圓內
{
    if( (P.first-x)*(P.first-x) + (P.second-y)*(P.second-y) -r*<=0)
        return 1;
    return 0;
}
bool Judis(PAIR P1,PAIR P2,double R) //線段與圓的關係
{

    if(judge(P1)&&judge(P2))//都在圓內 不相交
        return false;
    if(!judge(P1)&&judge(P2)||judge(P1)&&!judge(P2))//一個圓內一個圓外 相交
        return true;
    double A,B,C,dist1,dist2,angle1,angle2;//Ax+By+C=0;//(y1-y2)x +(x2-x1)y +x1y2-y1x2=0
    if(P1.first==P2.first)
        A=1,B=0,C= -P1.first;
    else if(P1.second==P2.second)
        A=0,B=1,C= -P1.second;
    else
    {
        A = P1.second-P2.second;
        B = P2.first-P1.first;
        C = P1.first*P2.second - P1.second*P2.first;
    }
    dist1 = A * yuan.first + B * yuan.second + C;
    dist1 *= dist1;
    dist2 = (A * A + B * B) * R * R;
    if (dist1 > dist2) return false;//點到直線距離大於半徑r  不相交
    angle1 = (yuan.first - P1.first) * (P2.first - P1.first) + (yuan.second - P1.second) * (P2.second - P1.second);
    angle2 = (yuan.first - P2.first) * (P1.first - P2.first) + (yuan.second - P2.second) * (P1.second - P2.second);
    if (angle1 > 0 && angle2 > 0return true;//餘弦都爲正,則是銳角 相交
    return false;//不相交
}

判斷線段和直線相交
  • 化成線段相交
判斷點在矩形內
  • 點在矩形對角線座標內
判斷線段/折現/多邊形在矩形內
  • 轉換爲所有點在矩形內
判斷矩形在矩形內
  • 上下界問題
判斷圓在矩形內
  • 圓心在矩形內
  • 圓的半徑R小於min(圓心到四個邊距離d)
不規則圖形
判斷點在多邊形
  • 多邊形問題,射線法



代碼:
https://paste.ubuntu.com/26455451/
#include <stdio.h>
#include <bits/stdc++.h>
#include <iostream>
using namespace std;

const double ESP=1e-6;
const double INFINV=1e10;
struct LineSegment{
    struct Point{
        double x,y;
    };
    Point Pt1,Pt2;
    typedef vector<Point> Vector;
    // 叉乘 |p0p1| x |p0p2| 
    double Multiply(Point p1,Point p2,Point p0)
    {
        return ( (p1.x-p0.x)*(p2.y-p0.y) - (p2.x-p0.x)*(p1.y-p0.y));    
    }
    // 點是否在邊上 
    bool IsOnline (Point P,LineSegment line)
    {
        return( ( fabs( Multiply(line.Pt1,line.Pt2,P) ) )<ESP && ( (P.x-line.Pt1.x)*(P.x-line.Pt2.x) <=0 )&&
        ( (P.y-line.Pt1.y)*(P.y-line.Pt2.y) <=0 ) ) ;
    }
    bool Intersect(LineSegment L1,LineSegment L2)
    {
        return( (max(L1.Pt1.x, L1.Pt2.x) >= min(L2.Pt1.x, L2.Pt2.x)) &&
     (max(L2.Pt1.x, L2.Pt2.x) >= min(L1.Pt1.x, L1.Pt2.x)) &&
     (max(L1.Pt1.y, L1.Pt2.y) >= min(L2.Pt1.y, L2.Pt2.y)) &&
     (max(L2.Pt1.y, L2.Pt2.y) >= min(L1.Pt1.y, L1.Pt2.y)) &&
     (Multiply(L2.Pt1, L1.Pt2, L1.Pt1) * Multiply(L1.Pt2, L2.Pt2, L1.Pt1) >= 0&&
     (Multiply(L1.Pt1, L2.Pt2, L2.Pt1) * Multiply(L2.Pt2, L1.Pt2, L2.Pt1) >= 0) );
    }
    bool main_Judge_Polygon(const Vector& poly,Point P)
    {
        int n=poly.size();
        int count=0;
        LineSegment line;
        line.Pt1=P;
        line.Pt2.y=P.y;
        line.Pt2.x= -INFINV;
        for(int i=0;i<n;i++)
        {
            LineSegment side;
            side.Pt1=poly[i];
            side.Pt2=poly[ (i+1% n];
            if( IsOnline(P,side) )
            {// 若點在邊上 直接返回 
                return true;
            }
            if( fabs(side.Pt1.y-side.Pt2.y) <ESP )
            {//判斷side是否平行於X軸 
                continue;
            }
            if( IsOnline(side.Pt1, line) )
            {
                if( side.Pt1.y > side.Pt2.y )
                    count++;
            }
            else if( IsOnline(side.Pt2,line) )
            {
                if( side.Pt2.y >side.Pt2.y) 
                    count++;
            }
            else if( Intersect (line ,side))
            {
                count++;
            }
        }
        if(count%2==1)
            return false;
        return true;
    }
}JK;

int main()
{

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