一些简单的二维数学的算法。

工作中偶尔都会接触到一些二维数学相关的算法问题。

例如我在工作遇到的以下3个问题:

1. 我需要在地图上使用鼠标连线自定义一块闭合的区域出来。

具体的操作方法就是鼠标左键点击地图时,创建一个点、放开移动时根据上一个点以及鼠标的点形成连线,

最后点击右键闭包最开始的那个点,最终形成一个多边形,但是不能让线跟线出现交叉,否则最终形成的多边形就不对了。

2. 地图上有多线段图,我需要鼠标移动到线的附近时,切换鼠标光标状态。

3. 根据已有的两个垂直线段作为新的坐标系,确定一个点的坐标。

上述这3个问题都是一些常见的问题,其实大部分程序员都能够百度并最终解决这些问题,但是计算过程可能非常复杂。

其实如果使用高中向量的知识很容易解决这类问题。首先我们了解几个可能你已经忘了的向量的知识。

1. 向量点乘

A向量(x1,y1), B向量(x2,y2)。A·B = x1*x2 + y1*y2

代码如下(基于Qt的QPointF)

1 double pp(QPointF p1, QPointF p2)
2 {
3     return p1.x() * p2.x() + p1.y() * p2.y();
4 }

点乘的特性:

1. 两个垂直的向量点乘为0。

2. 同一个向量点乘为向量的模的平方。

根据向量的点乘可以用来计算点到线段的距离。

具体的题型以及解法如下:(以下类似双字母AB这样且前面没有线段两字就表示为向量)

已知线段AB,其中A点为(a1,a2),B点为(b1,b2)。以及点C(c1,c2)。计算点C到线段AB的距离。

解: 假设D为点C在线段AB的投影点,那么CD垂直于AB。其中向量AD=k*AB。 k为实数

则 CD = CA + AD = CA + k * AB

CD * AB = 0 ==> (CA + k * AB) * AB = 0  ==> k = -(CA * AB)/(AB*AB) = (AC * AB)/(AB*AB)

继而就能算出CD向量,CD向量的模即是C到线段AB的距离。代码如下:

double pLen(QPointF p)
{
    return sqrt(pp(p, p));
}
/*
点到线的距离.
如果k值 在 0-1范围之内,证明点映射在直线的范围之内,否则在范围之外。
*/
double PointToLine(QPointF p, QLineF l, double &k)
{
    /*
    AC = (p-l.p0), AB = (l.p1 - l.p0),
AD = k * AB, 其中k为实数 DC = DA + AC = AC - AD = AC - k * AB AB ⊥ DC ==> AB * DC = 0 ==>
AB * (AC - k * AB) = 0
AB * AC - k * AB * AB = 0
k = (AB * AC)
/ (AB * AB) QPointF AC = p - l.p1(); QPointF AB = l.p2() - l.p1(); k = pp(AB, AC) / pp(AB, AB); QPointF DC = AC - k * AB; return pLen(DC); }

因为k值有时候是可以用到的,所以这里将k值也作为返回值,在解决问题2的时候,仅仅判断点到线段的距离小于某个值是不够。

还需确定点在线段的投影点在线段之内。当k值等于0时,投影点在A上,等于1时在B上。

2. 向量的叉乘

A向量(x1,y1), B向量(x2,y2)。A x B = x1*y2 - y1*x2

代码如下:

double xp(QPointF p1, QPointF p2)
{
       return p1.x() * p2.y() - p1.y() * p2.x()  
}

叉乘有一个最重要的特性就是:

当两个向量共一个起点时所围成的三角形的面积是叉乘的0.5倍或者-0.5倍。

叉乘的正负取决两个向量叉乘时,哪个在前,哪个在后。

同样叉乘也可以用来计算点到线距离。

题型如下:

已知线段AB,其中A点为(a1,a2),B点为(b1,b2)。以及点C(c1,c2)。计算点C到线段AB的距离。

|AB x AC| = S(ABC) * 2 = |AB| * (C到AB距离)

C到AB的距离 = |AB X AC| / |AB|

//返回值有可能为负数,可确认点在有向线段的左边还是右边
double pointToLine2(QPointF p, QLineF l)
{
     QPointF AB = l.p2() - l.p1();
     QPointF AC = p - l.p1();
     double C_AB = xp(AB, AC);
     return C_AB / pLen(AB) 
}

 

其中 AB X AC 的结果正负可以判定C在有向线段AB的左边还是右边。

另外问题1的核心在于判断线段与线段之间的关系,使用向量的叉乘很容易解决这个问题。

已知线段AB与线段CD。判断线段AB与线段CD的关系。 

解:如果AB和线段CD相交,A点和B点一定在CD所在线的两边

同理,C点和D点在AB点的两端。

( CA x CD ) x (CB x CD) <= 0

( AB x AC ) x ( AB x AD) <= 0

满足这两个条件则 AB线段与 CD线段相交。

如果其中一个等于0, 则表示其中一个线段在另一个线段的之上

如果两个都等于0, 则表示AB与CD线段时收尾相连的。

int lineToLine(QLineF l1, QLineF l2)
{
     QPointF AC = l2.p1() - l1.p1()
     QPointF AB = l1.p2() - l1.p1()
     QPointF AD = l2.p2() - l1.p1()
     double CD_AB = xp(AC, AB) * xp(AD, AB);
     if(CD_AB > 0)
          return 0;  //不相交
     QPointF CA = - AC;
     QPointF CB = l1.p2() - l2.p1()
     QPointF CD = l2.p2() - l2.p2();
     double AB_CD = xp(CA, CD) * xp (CB, CD);
     if(AB_CD > 0)
          return 0; //不相交
     if(AB_CD == 0 && CD_AB == 0)
          return 1; //首尾相连
     return 2;  //相交
}

 

转载于:https://www.cnblogs.com/diancangtai/p/9925371.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值