基础知识填坑---矢量的叉积

来源:互联网 发布:国行a1533支持什么网络 编辑:程序博客网 时间:2024/05/16 19:19

在看到有人讨论如何判断线段相交的问题的时候,发现自己有矢量这块基础知识的缺失,因此写篇笔记填个坑,以便记忆。


一、矢量的基本知识

1.矢量的概念:如果一条线段的端点是有次序之分的,我们把这种线段成为有向线段(directed segment)。如果有向线段p1p2的起点p1在坐标原点,则将其称为矢量(vector)p2。
2.矢量加减法:设二维矢量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 )。

3.矢量的叉积:计算矢量叉积是与直线和线段相关算法的核心部分。设矢量P = ( x1, y1 ),Q = ( x2, y2 ),则矢量叉积定义为由(0,0)、p1、p2和p1+p2所组成的平行四边形的带符号的面积,即:P × Q = x1*y2 - x2*y1,其结果是一个标量。显然有性质 P × Q = - ( Q × P ) 和 P × ( - Q ) = - ( P × Q )。
叉积的另一个非常重要性质是可以通过它的符号判断两矢量相互之间的顺逆时针关系:
  若 P × Q > 0 , 则P在Q的顺时针方向。
  若 P × Q < 0 , 则P在Q的逆时针方向。
  若 P × Q = 0 , 则P与Q共线,但可能同向也可能反向。
4.折线段的拐向判断:折线段的拐向判断方法可以直接由矢量叉积的性质推出。对于有公共端点的线段p0p1和p1p2,通过计算(p2 - p0) × (p1 - p0)的符号便可以确定折线段的拐向:
  若(p2 - p0) × (p1 - p0) > 0,则p0p1在p1点拐向右侧后得到p1p2。
  若(p2 - p0) × (p1 - p0) < 0,则p0p1在p1点拐向左侧后得到p1p2。
  若(p2 - p0) × (p1 - p0) = 0,则p0、p1、p2三点共线。
这一条判断也可用来判断点在线段或直线的哪一测。


二、判断两条直线是否相交

  第一个可能会想到的办法,就是判断斜率,这个在中学时代就学过了,不过斜率需要考虑垂直的特殊情况,比较麻烦。计算两个向量的叉积或许是一个更好的办法,如果两个向量叉乘为0,则是平行或者重合的,否则两直线相交。这里贴出来一个便于理解原理的代码如下:

#include <opencv2\highgui\highgui.hpp>#include <opencv2\opencv.hpp>using namespace std;using namespace cv; struct point{int x;int y;};struct v{point start;point end;};int crossProduct(v* v1, v* v2){v vt1, vt2;int result = 0;vt1.start.x = v1->start.x;vt1.start.y = v1->start.y;vt1.end.x = v1->end.x - v1->start.x;vt1.end.y = v1->end.y - v1->start.y;vt2.start.x = v2->start.x;vt2.start.y = v2->start.y;vt2.end.x = v2->end.x - v2->start.x;vt2.end.y = v2->end.y - v2->start.y;result = vt1.end.x * vt2.end.y - vt2.end.x * vt1.end.y;return result;}int main(){Point p1end(1, 2);Point p1start(0, 0);Point p2end(2, 1);Point p2start(0, 0);v pt1, pt2;pt1.end.x = p1end.x;pt1.end.y = p1end.y;pt1.start.x = p1start.x;pt1.start.y = p1start.y;pt2.end.x = p2end.x;pt2.end.y = p2end.y;pt2.start.x = p2start.x;pt2.start.y = p2start.y;cout << "CrossProduct: " << crossProduct(&pt1, &pt2) << endl;system("pause");return 0;}

输出结果为: CrossProduct: -3


如图所示,向量P1,P2的叉乘就是图中平行四边形的面积=3,负号就表示向量P1在P2的逆时针方向。


三、判断两线段相交
          经典方法,就是跨立试验了,即如果一条线段跨过另一条线段,则线段的两个端点分别在另一条线段的两侧。但是,还需要检测边界情况,即两条线段中可能某条线段的某个端点正好落在另一条线段上。
程序模拟如下:

int direction(point* pi, point* pj, point* pk){point p1, p2;p1.x = pk->x - pi->x;p1.y = pk->y - pi->y;p2.x = pj->x - pi->x;p2.y = pj->y - pi->y;return crossProduct(&p1, &p2);}int onSegment(point* pi, point* pj, point* pk){int minx, miny, maxx, maxy;if (pi->x > pj->x){minx = pj->x;maxx = pi->x; }else{minx = pi->x;maxx = pj->x;}if (pi->y > pj->y){miny = pj->y;maxy = pi->y; }else{miny = pi->y;maxy = pj->y;}if (minx <= pk->x && pk->x <= maxx && miny <= pk->y && pk->y <= maxy)return 1;elsereturn 0;}int segmentIntersect(point* p1, point* p2, point* p3, point* p4){int d1 = direction(p3, p4, p1);int d2 = direction(p3, p4, p2);int d3 = direction(p1, p2, p3);int d4 = direction(p1, p2, p4);if (d1 * d2 < 0 && d3 * d4 < 0)return 1;else if (!d1 && onSegment(p3, p4, p1))return 1;else if (!d2 && onSegment(p3, p4, p2))return 1;else if (!d3 && onSegment(p1, p2, p3))return 1;else if (!d4 && onSegment(p1, p2, p4))return 1;elsereturn 0;}

实际上,如果想改进上述算法,还可以在跨立试验前加一步,就是先做快速排斥试验。那就是,先分别判断以两条线段为对角线的矩形是否相交,如果不相交,则两个线段肯定不相交。


四.判断两条线段相交,然后计算交点

设一条线段为L0=P1P2, 另一条线段或直线为L1=Q1Q2, 要计算的就是L0和L1的交点。
1.首先判断L0和L1是否相交(方法已在前文讨论过), 如果不相交则没有交点, 否则说明L0和L1一定有交点, 下面就将L0和L1都看作直线来考虑.
2.如果P1和P2横坐标相同, 即L0平行于Y轴
     a)若L1也平行于Y轴
        i.若P1的纵坐标和Q1的纵坐标相同, 说明L0和L1共线, 假如L1是直线的话他们有无穷的交点, 假如L1是线段的话可用"计算两条共线线段的交点"的算法求他们的交点(该方法在前文已讨论过);
        ii.否则说明L0和L1平行, 他们没有交点;
     b)若L1不平行于Y轴, 则交点横坐标为P1的横坐标, 代入到L1的直线方程中可以计算出交点纵坐标;
3.如果P1和P2横坐标不同, 但是Q1和Q2横坐标相同, 即L1平行于Y轴, 则交点横坐标为Q1的横坐标, 代入到L0的直线方程中可以计算出交点纵坐标;
4.如果P1和P2纵坐标相同, 即L0平行于X轴
     a)若L1也平行于X轴,
         i.若P1的横坐标和Q1的横坐标相同, 说明L0和L1共线, 假如L1是直线的话他们有无穷的交点, 假如L1是线段的话可用"计算两条共线线段的交点"的算法求他们的交点(该方法在前文已讨论过);
         ii.否则说明L0和L1平行, 他们没有交点;
     b)若L1不平行于X轴, 则交点纵坐标为P1的纵坐标, 代入到L1的直线方程中可以计算出交点横坐标;
5.如果P1和P2纵坐标不同, 但是Q1和Q2纵坐标相同, 即L1平行于X轴, 则交点纵坐标为Q1的纵坐标, 代入到L0的直线方程中可以计算出交点横坐标;
6.剩下的情况就是L1和L0的斜率均存在且不为0的情况
     a)计算出L0的斜率K0, L1的斜率K1;
     b)如果K1 = K2
         i.如果Q1在L0上, 则说明L0和L1共线, 假如L1是直线的话有无穷交点, 假如L1是线段的话可用"计算两条共线线段的交点"的算法求他们的交点(该方法在前文已讨论过);
         ii.如果Q1不在L0上, 则说明L0和L1平行, 他们没有交点.
     c)联立两直线的方程组可以解出交点来


这个算法并不复杂, 但是要分情况讨论清楚, 尤其是当两条线段共线的情况需要单独考虑, 所以在前文将求两条共线线段的算法单独写出来. 另外, 一开始就先利用矢量叉乘判断线段与线段(或直线)是否相交, 如果结果是相交, 那么在后面就可以将线段全部看作直线来考虑. 需要注意的是, 我们可以将直线或线段方程改写为ax+by+c=0的形式, 这样一来上述过程的部分步骤可以合并, 缩短了代码长度, 但是由于先要求出参数, 这种算法将花费更多的时间.


鄙人才疏学浅,尚有不足还望不惜赐教。

0 0