计算几何基础——矢量和叉积

矢量

      如果一条线段的端点是有次序之分的话,那么这种线段就称为 有向线段,如果有向线段p1p2的起点p1在坐标的原点,则可以把它称为矢量 p2

矢量的加减

      设二维矢量 P = (x1, y1), Q = (x2, y2),则 P + Q = (x1 + x2, y1 + y2), P - Q = (x1 - x2, y1 - y2),且有 P + Q = Q + P, P - Q = -(Q - P)

矢量叉积

      设矢量 P = (x1, y1), Q = (x2, y2),则 P * Q = x1 * y2 - x2 * y1; 其结果是一个由 (0, 0), P, Q, P + Q 所组成的平行四边形的 带符号的面积,P * Q = -(Q * P), P * (- Q) = -(P * Q)

      叉积的一个非常重要的性质是可以通过它的符号来判断两矢量相互之间的顺逆时针关系:

            若 P * Q > 0,则 P 在 Q 的顺时针方向

            若 P * Q < 0, 则 P 在 Q 的逆时针方向

            若 P * Q = 0,则 P 与 Q 共线,但不确定 P, Q 的方向是否相同

折线段的拐向判断

      如图,假设有折线段 p0p1p2 ,要确定 p1p2 相对于 p0p1 是向左拐还是向右拐,可以通过计算(p2 - p0)*(p1 - p0) 的符号来确定折线的拐向(点 p2 - p0 实际上就是向量 p2,但这里要注意线段和矢量的区别)


判断点是否在线段上

      设点 Q = (x, y), P1 = (x1, y1), P2 = (x2, y2),若 (Q - P1)*(P2 - P1) = 0 且 min(x1, x2) <= x <= max(x1, x2) && min(y1, y2) <= y <= max(y1, y2),则点 Q 在线段 P1P2 上

判断两线段是否相交

1)快速排斥试验

      设以线段 P1P2 为对角线的矩形为 R,设以线段 Q1Q2 为对角线的矩形为 T,若 R、T 不相交,则两线段不可能相交

      假设 P1 = (x1, y1), P2 = (x2, y2), Q1 = (x3, y3), Q2 = (x4, y4),设矩形 R 的 x 坐标的最小边界为 minRX = min(x1, x2),以此类推,将矩形表示为 R = (minRX, minRY, maxRX, maxRY) 的形式,若两矩形相交,则相交的部分构成了一个新的矩形 F,如图所示,我们可以知道 F 的 minFX = max(minRX, minTX), minFY = max(minRY, minTY), maxFX = min(maxRX, maxTX), maxFY = min(maxRY, maxTX),得到 F 的各个值之后,只要判断矩形 F 是否成立就知道 R 和 T 到底有没有相交了,若 minFX > maxFX 或 minFY > maxFy 则 F 无法构成,RT不相交,否则 RT相交


2)跨立试验

      若 P1P2 跨立 Q1Q2,则 P1,P2 分别在 Q1Q2 所在直线的两端,则有 (P1 - Q1)*(Q2 - Q1) * (Q2 - Q1)*(P2 - Q1) > 0,当 (P1 - Q1)*(Q2 - Q1) = 0 时,说明 (P1 - Q1) 与 (Q2 - Q1) 共线,但由于已经经过快速排斥试验,所以 Q1 必为 P1P2 与 Q1Q2 的交点,依然满足线段相交的条件,则跨立试验可改为:

      当 (P1 - Q1)*(Q2 - Q1) * (Q2 - Q1)*(P2 - Q1) >= 0,则 P1P2 跨立 Q1Q2,当 Q1Q2 也跨立 P1P2 的时候,则 P1P2 相交

      (注意式子中被隔开的 * 代表两个叉积的值的相乘,而另外的两个 * 则代表计算矢量叉积)

规范相交:两条线段恰有一个不是端点的公共点。

即如果一条线段的一个端点恰在另一条线段上则不视为相交;如果两条线段部分重合,也不视为相交。

非规范相交:两条线段存在公共部分。(上述两种情况都可视为非规范相交)

其中a~f是非规范相交; g,h是不相交; a~c有唯一的交点;d~f有无数个交点。


判断线段是否相交,如果是规范相交则求出交点坐标p并返回1,如果是非规范相交则直接返回2,否则返回0;

struct Point
{
    double x, y;
};
const double eps=1e-6;
int dblcmp(double d)    //精确度
{
    if (fabs(d)<eps)
    {
        return 0;
    }
    return d>0 ? 1 : -1;
}
double xmult(Point p0, Point p1, Point p2)    //叉乘
{
    return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);
}
double dmult(Point p0, Point p1, Point p2)    //点乘
{
    return (p2.x-p0.x)*(p1.x-p0.x)+(p2.y-p0.y)*(p1.y-p0.y);
}

int segcross(Point a, Point b, Point c, Point d, Point &p)    //判断线段相交
{
    double s1, s2, s3,s4;
    int d1, d2, d3, d4;
    d1=dblcmp(s1=xmult(a,b,c));
    d2=dblcmp(s2=xmult(a,b,d));
    d3=dblcmp(s3=xmult(c,d,a));
    d4=dblcmp(s4=xmult(c,d,b));

    //若规范相交则求交点的代码
    //d1^d2==-2相当于d1*d2<0;
    //跨立实验
    if ((d1^d2)==-2 && (d3^d4)==-2)    //"(d1^d2)"这个小括号是必须的,否则会错
    {
        p.x=(c.x*s2-d.x*s1)/(s2-s1);
        p.y=(c.y*s2-d.y*s1)/(s2-s1);
        return 1;    
    }

    //判断非规范相交
    //d1==0, 则证明a,b,c三点共线; 
    //如果dblcmp(dmult(c,a,b))<0, 则说明点c在点a,b的中间;
    //如果dblcmp(dmult(c,a,b))==0,则说明点c与线段ab的端点a,或者b重合。
    //如果dblcmp(dmult(c,a,b))==0,则说明点c在线段ab的外面。
    if (d1==0 && dblcmp(dmult(c,a,b))<=0 ||
        d2==0 && dblcmp(dmult(d,a,b))<=0 ||
        d3==0 && dblcmp(dmult(a,c,d))<=0 ||
        d4==0 && dblcmp(dmult(b,c,d))<=0)
    {
        return 2;    
    }
    return 0;
}



原文地址:https://www.cnblogs.com/lgh1992314/p/5835045.html