热门标签 | HotTags
当前位置:  开发笔记 > IOS > 正文

C++/STL实现判断平面内两条线段的位置关系代码示例

这篇文章主要介绍了C++STL实现判断平面内两条线段的位置关系代码示例,具有一定参考价值,需要的朋友可以了解下。

概念

平面内两条线段位置关系的判定在很多领域都有着广泛的应用,比如游戏、CAD、图形处理等,而两线段交点的求解又是该算法中重要的一环。本文将尽可能用通俗的语言详细的描述一种主流且性能较高的判定算法。

外积,又称叉积,是向量代数(解析几何)中的一个概念。两个二维向量v1(x1,y1)和v2(x2,y2)的外积v1×v2=x1y2-y1x2。如果由v1到v2是顺时针转动,外积为负,反之为正,为0表示二者方向相同(平行)。此外,文中涉及行例式和方程组的概念,请参阅线性代数的相关内容。

为方便计算,对坐标点的大小比较作如下定义:x坐标较大的点为大,x坐标相等但y坐标较大的为大,x与y都相等的点相等。一条线段中较小的一端为起点,较大的一端为终点。

问题

给定两条线段的端点坐标,求其位置关系,并求出交点(如果存在)。

分析

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

重合可分为:完全重合、一端重合、部分重合三种情况。显然,两条线段的起止点都相同即为完全重合;只有起点相同或只有终点相同的为一端重合(注意:坐标较小的一条线段的终点与坐标较大的一条线段的起点相同时应判定为相交)。要判断是否部分重合,必须先判断是否平行。设线段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也平行则两线段共线(三点共线)。在共线的前提下,若起点较小的线段终点大于起点较大的线段起点,则判定为部分重合。

没有重合,就要判定两条线是否相交,主要的算法还是依靠外积。然而外积的计算开销比较大,如果不相交的情况比较多,可先做快速排斥实验:将两条线段视为两个矩形的对角线,并构造出这两个矩形。如果这两个矩形没有重叠部分(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,则说明一条线段的端点在另一条线上。

当判定两条线段相交后,就可以进行交点的求解了。当然,求交点可以用平面几何方法,列点斜式方程来完成。但这样作会难以处理斜率为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|

解毕。

C++/STL实现

#include 
#include 
using namespace std;
struct POINTF {float x; float y;};
bool Equal(float f1, float f2) {
  return (abs(f1 - f2) <1e-4f);
}
//判断两点是否相等
bool operator==(const POINTF &p1, const POINTF &p2) {
  return (Equal(p1.x, p2.x) && Equal(p1.y, p2.y));
}
//比较两点坐标大小,先比较x坐标,若相同则比较y坐标
bool operator>(const POINTF &p1, const POINTF &p2) {
  return (p1.x > p2.x || (Equal(p1.x, p2.x) && p1.y > p2.y));
}
//计算两向量外积
float operator^(const POINTF &p1, const POINTF &p2) {
  return (p1.x * p2.y - p1.y * p2.x);
}
//判定两线段位置关系,并求出交点(如果存在)。返回值列举如下:
//[有重合] 完全重合(6),1个端点重合且共线(5),部分重合(4)
//[无重合] 两端点相交(3),交于线上(2),正交(1),无交(0),参数错误(-1)
int Intersection(POINTF p1, POINTF p2, POINTF p3, POINTF p4, POINTF &Int) {
  //保证参数p1!=p2,p3!=p4
  if (p1 == p2 || p3 == p4) {
    return -1; //返回-1代表至少有一条线段首尾重合,不能构成线段
  }
  //为方便运算,保证各线段的起点在前,终点在后。
  if (p1 > p2) {
    swap(p1, p2);
  }
  if (p3 > p4) {
    swap(p3, p4);
  }
  //判定两线段是否完全重合
  if (p1 == p3 && p2 == p4) {
    return 6;
  }
  //求出两线段构成的向量
  POINTF v1 = {p2.x - p1.x, p2.y - p1.y}, v2 = {p4.x - p3.x, p4.y - p3.y};
  //求两向量外积,平行时外积为0
  float Corss = v1 ^ v2;
  //如果起点重合
  if (p1 == p3) {
    Int = p1;
    //起点重合且共线(平行)返回5;不平行则交于端点,返回3
    return (Equal(Corss, 0) &#63; 5 : 3);
  }
  //如果终点重合
  if (p2 == p4) {
    Int = p2;
    //终点重合且共线(平行)返回5;不平行则交于端点,返回3
    return (Equal(Corss, 0) &#63; 5 : 3);
  }
  //如果两线端首尾相连
  if (p1 == p4) {
    Int = p1;
    return 3;
  }
  if (p2 == p3) {
    Int = p2;
    return 3;
  }//经过以上判断,首尾点相重的情况都被排除了
  //将线段按起点坐标排序。若线段1的起点较大,则将两线段交换
  if (p1 > p3) {
    swap(p1, p3);
    swap(p2, p4);
    //更新原先计算的向量及其外积
    swap(v1, v2);
    Corss = v1 ^ v2;
  }
  //处理两线段平行的情况
  if (Equal(Corss, 0)) {
    //做向量v1(p1, p2)和vs(p1,p3)的外积,判定是否共线
    POINTF vs = {p3.x - p1.x, p3.y - p1.y};
    //外积为0则两平行线段共线,下面判定是否有重合部分
    if (Equal(v1 ^ vs, 0)) {
      //前一条线的终点大于后一条线的起点,则判定存在重合
      if (p2 > p3) {
        Int = p3;
        return 4; //返回值4代表线段部分重合
      }
    }//若三点不共线,则这两条平行线段必不共线。
    //不共线或共线但无重合的平行线均无交点
    return 0;
  } //以下为不平行的情况,先进行快速排斥试验
  //x坐标已有序,可直接比较。y坐标要先求两线段的最大和最小值
  float ymax1 = p1.y, ymin1 = p2.y, ymax2 = p3.y, ymin2 = p4.y;
  if (ymax1  p4.x || p2.x  ymax2) {
    return 0;
  }//下面进行跨立试验
  POINTF vs1 = {p1.x - p3.x, p1.y - p3.y}, vs2 = {p2.x - p3.x, p2.y - p3.y};
  POINTF vt1 = {p3.x - p1.x, p3.y - p1.y}, vt2 = {p4.x - p1.x, p4.y - p1.y};
  float s1v2, s2v2, t1v1, t2v1;
  //根据外积结果判定否交于线上
  if (Equal(s1v2 = vs1 ^ v2, 0) && p4 > p1 && p1 > p3) {
    Int = p1;
    return 2;
  }
  if (Equal(s2v2 = vs2 ^ v2, 0) && p4 > p2 && p2 > p3) {
    Int = p2;
    return 2;
  }
  if (Equal(t1v1 = vt1 ^ v1, 0) && p2 > p3 && p3 > p1) {
    Int = p3;
    return 2;
  }
  if (Equal(t2v1 = vt2 ^ v1, 0) && p2 > p4 && p4 > p1) {
    Int = p4;
    return 2;
  } //未交于线上,则判定是否相交
  if(s1v2 * s2v2 > 0 || t1v1 * t2v1 > 0) {
    return 0;
  } //以下为相交的情况,算法详见文档
  //计算二阶行列式的两个常数项
  float COnA= p1.x * v1.y - p1.y * v1.x;
  float COnB= p3.x * v2.y - p3.y * v2.x;
  //计算行列式D1和D2的值,除以系数行列式的值,得到交点坐标
  Int.x = (ConB * v1.x - ConA * v2.x) / Corss;
  Int.y = (ConB * v1.y - ConA * v2.y) / Corss;
  //正交返回1
  return 1;
}
//主函数
int main(void) {
  //随机生成100个测试数据
  for (int i = 0; i <100; ++i) {
    POINTF p1, p2, p3, p4, Int;
    p1.x = (float)(rand() % 10); p1.y = (float)(rand() % 10);
    p2.x = (float)(rand() % 10); p2.y = (float)(rand() % 10);
    p3.x = (float)(rand() % 10); p3.y = (float)(rand() % 10);
    p4.x = (float)(rand() % 10); p4.y = (float)(rand() % 10);
    int nr = Intersection(p1, p2, p3, p4, Int);
    cout <<"[(";
    cout <<(int)p1.x <<',' <<(int)p1.y <<"),(";
    cout <<(int)p2.x <<',' <<(int)p2.y <<")]--[(";
    cout <<(int)p3.x <<',' <<(int)p3.y <<"),(";
    cout <<(int)p4.x <<',' <<(int)p4.y <<")]: ";
    cout < 0) {
      cout <<'(' <

关于stl,貌似用的不多了,不是过时了,而是有控制的使用,每个项目都有自己的使用场景,根据自己的需要选择合适的技术。

总结

以上就是本文关于C++/STL实现判断平面内两条线段的位置关系代码示例的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站:

C++递归算法实例代码

C++中函数指针详解及代码分享

C/C++ 编译器优化介绍

如有不足之处,欢迎留言指出。


推荐阅读
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文介绍了如何找到并终止在8080端口上运行的进程的方法,通过使用终端命令lsof -i :8080可以获取在该端口上运行的所有进程的输出,并使用kill命令终止指定进程的运行。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文介绍了C++中省略号类型和参数个数不确定函数参数的使用方法,并提供了一个范例。通过宏定义的方式,可以方便地处理不定参数的情况。文章中给出了具体的代码实现,并对代码进行了解释和说明。这对于需要处理不定参数的情况的程序员来说,是一个很有用的参考资料。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文介绍了MyBioSource转甲状腺素蛋白定量检测ELISA试剂盒的应用方法及特点。ELISA法作为一项新技术在免疫诊断中的应用范围不断扩大,不仅适用于多种病原微生物引起的传染病、非传染病的免疫诊断,也可用于大/小分子抗原的定量检测。ELISA法具有灵敏、特异、简单、快速、稳定及易于自动化操作等特点,是一种早期诊断的良好方法,也可用于血清流行病学调查。MyBioSource转甲状腺素蛋白定量检测ELISA试剂盒使用方法包括对血清和血浆的操作要求。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文介绍了一种划分和计数油田地块的方法。根据给定的条件,通过遍历和DFS算法,将符合条件的地块标记为不符合条件的地块,并进行计数。同时,还介绍了如何判断点是否在给定范围内的方法。 ... [详细]
  • 本文讲述了作者通过点火测试男友的性格和承受能力,以考验婚姻问题。作者故意不安慰男友并再次点火,观察他的反应。这个行为是善意的玩人,旨在了解男友的性格和避免婚姻问题。 ... [详细]
  • 安卓select模态框样式改变_微软Office风格的多端(Web、安卓、iOS)组件库——Fabric UI...
    介绍FabricUI是微软开源的一套Office风格的多端组件库,共有三套针对性的组件,分别适用于web、android以及iOS,Fab ... [详细]
  • 本文介绍了多因子选股模型在实际中的构建步骤,包括风险源分析、因子筛选和体系构建,并进行了模拟实证回测。在风险源分析中,从宏观、行业、公司和特殊因素四个角度分析了影响资产价格的因素。具体包括宏观经济运行和宏经济政策对证券市场的影响,以及行业类型、行业生命周期和行业政策对股票价格的影响。 ... [详细]
author-avatar
jesusestella
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有