点乘、叉乘总结

转自:https://blog.csdn.net/codeball/article/details/38563049

struct point
{
    int x,y;
};
struct line
{
    point s;
    point e;
};

叉积(外积):两个二维向量v1(x1, y1)和v2(x2, y2)的叉积v1×v2=x1y2-y1x2。如果由v1到v2是顺时针转动,叉积为负,反之为正,为0表示二者方向相同(平行),注意,这里是向量,并不是点!!!!

问题解决:

给定两条线断的端点的坐标,求其位置关系,并求出交点。

分析:

两条线段的位置关系大体上可以分为三类:有重合部分、无重合部分但有交点(相交)、无交点。为避免精度问题,首先要将所有存在重合的情况排除。

重合可分为:完全重合、一端重合、部分重合三种情况。显然,两条线段的起止点都相同即为完全重合;只有起点相同或只有终点相同的为一端重合(注意:坐标较小的一条线段的终点与坐标较大的一条线段的起点相同时应判定为相交)。要判断是否部分重合,必须先判断是否平行。设线段L1(p1->p2)和L2(p3->p4),其中p1(x1, y1)为第一条线段的起点,p2(x2, y2)为第一条线段的终点,p3(x3, y3)为第二条线段的起点,p4(x4, y4)为第二段线段的终点,由此可构造两个向量:

v1(x2-x1, y2-y1),v2(x4-x3, y4-y3)
若v1与v2的外积v1×v2为0,则两条线段平行,有可能存在部分重合。再判断两条平行线段是否共线,方法是用L1的一端和L2的一端构成向量vs并与v2作外积,如果vs与v2也平行则两线段共线(三点共线)。在共线的前提下,若起点较小的线段终点大于起点较大的线段起点,则判定为部分重合。

//叉积判断两线段是否共线
int ping(point a,point b,line c)//a为一条线段的一个端点,b为另一条线段的一个端点,C为一条线段或另一条线段
{
    return (a.x - b.x)(c.e.y - c.s.y) - (a.y - b.y)(c.e.x - c.s.x);
    //返回值为0,则说明三点共线,这两条线段共线
}

int pd(point a,point b,line c)//判断是否部分重合
{
    if(ping(a,line.s,c) == 0)//在确定线段共线的基础上判断部分重合
    {
        if((a.x == line.s.x && a.y == line.s.y && b.x == line.e.x && b.y == line.e.y)||
                (a.x == line.e.x && a.y == line.e.y && b.x == line.s.x && b.y == line.s.y))
        {
            return 0;//首先判断是否是完全重合,即只要不是完全重合,则一定就是部分重合
        }
        else
            return 1;
    }
}

没有重合,就要判定两条线是否相交,主要的算法还是依靠外积。然而外积的计算开销比较大,如果不相交的情况比较多,可先做快速排斥实验:将两条线段视为两个矩形的对角线,并构造出这两个矩形。如果这两个矩形没有重叠部分(x坐标相离或y坐标相离)即可判定为不相交。

然后执行跨立试验。两条相交的线段必然相互跨立,简单的讲就是p1和p2两点位于L2的两侧且p3和p4两点位于L1的两侧,这样就可利用外积做出判断了。分别构造向量s1(p3, p1), s2(p3, p2),如果s1×v2与s2×v2异号(s1->v2与s2->v2转动的方向相反),则说明p1和p2位于L2的两侧。同理可判定p3和p4是否跨立L1。如果上述四个叉积中任何一个等于0,则说明一条线段的端点在另一条线上。

#define sign(a) ((a)>0?1:(((a)<0?-1:0)))

int kua(line l1,line l2)//跨立实验通过判断两点是否在线段的两侧来判断两线段是否交叉
{
    //sign 是为了保证不会因为做乘法得出的数太小或太大造成数据出错
    return sign(ping(l1.s,l2.s,l2)) * ping(l1.s,l2.e,l2) < 0;//在两侧,ping返回值一正,一负,相乘小于0
}

当判定两条线段相交后,就可以进行交点的求解了。当然,求交点可以用平面几何方法,列点斜式方程来完成。但这样作会难以处理斜率为0的特殊情况,且运算中会出现多次除法,很难保证精度。这里将使用向量法求解。

设交点为(x0, y0),则下列方程组必然成立:

x0-x1=k1(x2-x1)
y0-y1=k1(y2-y1)
x0-x3=k2(x4-x3)
y0-y3=k2(y4-y3)
其中k1和k2为任意不为0的常数(若为0,则说明有重合的端点,这种情况在上面已经被排除了)。1式与2式联系,3式与4式联立,消去k1和k2可得:

x0(y2-y1)-x1(y2-y1)=y0(x2-x1)-y1(x2-x1)
x0(y4-y3)-x3(y4-y3)=y0(x4-x3)-y3(x4-x3)
将含有未知数x0和y0的项移到左边,常数项移动到右边,得:

(y2-y1)x0+(x1-x2)y0=(y2-y1)x1+(x1-x2)y1
(y4-y3)x0+(x3-x4)y0=(y4-y3)x3+(x3-x4)y3
设两个常数项分别为b1和b2:

b1=(y2-y1)x1+(x1-x2)y1
b2=(y4-y3)x3+(x3-x4)y3
系数行列式为D,用b1和b2替换x0的系数所得系数行列式为D1,替换y0的系数所得系数行列式为D2,则有:

|D|=(x2-x1)(y4-y3)-(x4-x3)(y2-y1)
|D1|=b2(x2-x1)-b1(x4-x3)
|D2|=b2(y2-y1)-b1(y4-y3)
由此,可求得交点坐标为:

x0=|D1|/|D|, y0=|D2|/|D|
解毕。

//利用线性代数中的交错相乘得出结果,减少除法的运用,降低精度损失
point qjd(line l1,line l2)
{
    double d = abs((l1.e.x - l1.s.x)*(l2.e.y - l2.s.y) - (l2.e.x - l2.s.x) * (l1.e.y - l1.s.y));
    double d1 = abs(((l2.e.y-l2.s.y)*l2.s.x + (l2.s.x-l2.e.x)*l2.s.y)*(l1.e.x-l1.s.x)-((l1.e.y-l1.s.y)*l1.s.x + (l1.s.x-l1.e.x)*l1.s.y)(l2.e.x-l2.s.x));
    double d2 = abs(((l2.e.y-l2.s.y)*l2.s.x + (l2.s.x-l2.e.x)*l2.s.y)*(l1.e.y-l1.s.y)-((l1.e.y-l1.s.y)*l1.s.x + (l1.s.x-l1.e.x)*l1.s.y)(l2.e.y-l2.s.y));
    point pp;
    pp.x = d1/d;
    pp.y = d2/d;
    return pp;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值