1. 程式人生 > >二維幾何-點和直線

二維幾何-點和直線

這篇文章講的是二維幾何中點和直線的相關問題。

直線的引數表示。直線可以用直線上的一點P0和方向向量v表示(雖然這個向量的大小沒什麼用處)。直線上所有點P滿足P=P0+tv。其中t稱為引數。如果已知直線上的兩個不同點A和B,則方向向量為B-A,所以引數方程為A+(B-A)t。

引數方程最方便的地方在於直線、射線和線段的方程形式是一樣的。區別僅僅在於引數。直線的t沒有範圍限制。射線的t>0,線段的t在0~1之間,這樣,很多對於直線適用的公式可以很方便的用在射線和線段上。

直線交點:

設直線分別為P+tv和Q+tw,設向量u=P-Q,交點在第一條直線的引數為t1,第二條直線上的引數為t2,則x和y座標可以列出一個方程解得:

t1=cross(w,u) / cross(v,w)

t2 = cross(v,u) / cross(v,w)

呼叫前請確保兩條直線P+tv和Q+tw有唯一交點。當且僅當Cross(v,w)非0

如果P,v,Q,w的各個分量均為有理數,則交點座標也是有理數。在精度要求極高的情況下,可以考慮自定義分數類。

Point GetLineIntersection(Point P, Vector v, Point Q, Vector w)
{
    Vector u;
    double t;

    u = P - Q;
    t = Cross(w, u) / Cross(v, w);
    return P + v * t;
}

點到直線的距離:

點到直線的距離是一個常用的函式。可以用叉積算出。即用平行四邊形的面積除以底。

double DistanceToLine(Point P, Point A, Point B)
{
    Vector v1, v2;

    v1 = B - A;
    v2 = P - A;
    return fabs(Cross(v1, v2)) / Length(v1);   //如果不取絕對值,得到的是有向距離
}

點到線段的距離。

設投影點為Q,如果Q線上段AB上,則所求距離就是P點到直線AB的距離;如果Q在射線BA上,則所求距離為PA距離;否則為PB距離,判斷Q的位置可以用點積進行(參見上篇 :兩個向量的為位置關係)。

double DistanceToSegment(Point P, Point A, Point B)
{
    Vector v1, v2, v3;

    v1 = B - A;
    v2 = P - A;
    v3 = P - B;
    if(A == B)                                 //A點等於B點
        return Length(v2);
    if(dcmp(Dot(v1, v2)) < 0)                  //Q在射線BA上
        return Length(v2);
    else if(dcmp(Dot(v1, v3)) > 0)             //Q在射線AB上
        return Length(v3);
    else
        return fabs(Cross(v1, v2)) / Length(v1);
}

點在直線上的投影。

我們把直線AB寫成引數式A+tv(v為向量AB),並且設Q的引數為t0,那麼Q=A+t0v,根據PQ垂直與AB,兩個向量的點積應該為0。

因此Dot(v,P-(A+t0v))=0.根據分配率,有Dot(v,P-A)-t0*Dot(v,v)=0,這樣就可以解出t0,從而得到Q點。

Point GetLineProjection(Point P, Point A, Point B)
{
    Vector v;

    v = B - A;
    return A + v * (Dot(v, P-A) / Dot(v, v));
}

.線段相交判定。給定兩條線段,判斷是否相交。我們定義規範相交為兩線段恰好有一個公共點,且不在任何一條線段的端點。線段規範相交的充要條件是:每條線段的兩個端點都在另一條線段的兩側(這裡的兩側是指叉積的符號不同)

bool SegmentProperIntersection(Point a1, Point a2, Point b1, Point b2)
{
    double c1, c2, c3, c4;

    c1 = Cross(a2-a1, b1-a1);
    c2 = Cross(a2-a1, b2-a1);
    c3 = Cross(b2-b1, a1-b1);
    c4 = Cross(b2-b1, a2-b1);

    return (dcmp(c1) * dcmp(c2) < 0) && (dcmp(c3) * dcmp(c4) < 0);
}

如果允許在端點處相交,情況就比較複雜了。如果c1和c2都是0,表示兩線段共線,這時可能會有部分重疊的情況。如果c1和c2不都是0,則只有一種相交方法,即某個端點在另外一條線段上,為了判斷上述情況是否發生,還需要判斷一下點是否在一條線段(不包含端點)。

bool OnSegment(Point P, Point a1, Point a2)
{
    return (dcmp(Cross(a1-P, a2-P)) == 0) && (dcmp(Dot(a1-P, a2-P)) < 0);
}

判斷一個點是否在一條線段上.首先判斷是否是端點,不是的話判斷是否線上段上。

bool isPointOnSegment(Point p, Point a1, Point a2)
{
    return (p==a1) || (p== a2) || OnSegment(p, a1, a2);
}