c语言 两条线段位置,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) ? 5 : 3);

}

//如果终点重合

if (p2 == p4) {

Int = p2;

//终点重合且共线(平行)返回5;不平行则交于端点,返回3

return (Equal(Corss, 0) ? 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 < ymin1) {

swap(ymax1, ymin1);

}

if (ymax2 < ymin2) {

swap(ymax2, ymin2);

}

//如果以两线段为对角线的矩形不相交,则无交点

if (p1.x > p4.x || p2.x < p3.x || ymax1 < ymin2 || ymin1 > 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 << nr;

if (nr > 0) {

cout << '(' << Int.x << ',' << Int.y << ')';

}

cout << endl;

}

return 0;

}

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

总结

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/368731.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Thunder团队Beta周贡献分规则

小组名称&#xff1a;Thunder 项目名称&#xff1a;i阅app 组长&#xff1a;王航 成员&#xff1a;李传康、翟宇豪、邹双黛、苗威、宋雨、胡佑蓉、杨梓瑞 分配规则 规则1&#xff1a;基础分&#xff0c;拿出总分的20%&#xff08;8分&#xff09;进行均分&#xff0c;剩下的80%…

SiftingAppender:将不同的线程记录到不同的日志文件中

Logback的一项新颖功能是SiftingAppender &#xff08; JavaDoc &#xff09;。 简而言之&#xff0c;它是一个代理附加器&#xff0c;它为给定运行时属性的每个唯一值创建一个子附加器。 通常&#xff0c;此属性来自MDC 。 这是基于上面链接的官方文档的示例&#xff1a; <…

gulp webpack整合

为什么需要前端工程化&#xff1f; 前端工程化的意义在于让前端这个行业由野蛮时代进化为正规军时代&#xff0c;近年来很多相关的工具和概念诞生。好奇心日报在进行前端工程化的过程中&#xff0c;主要的挑战在于解决如下问题&#xff1a;✦ 如何管理多个项目的前端代码&…

SpringBoot 入门第一章

一、前言 Spring Boot 是由 Pivotal 团队提供的全新框架&#xff0c;其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置&#xff0c;从而使开发人员不再需要定义样板化的配置。 本系列以快速入门为主&#xff0c;可当作工具小手册阅…

C语言游戏传递小秘密,C语言的那些小秘密之链表

大多数的读者在学习编程语言的时候都不喜欢那些枯燥的文字描述&#xff0c;包括我自己在开始学习编程的时候也是这样&#xff0c;对于代码的热情远远高于文字&#xff0c;所以我在我写东西的时候也不喜欢用枯燥的文字描述来向读者讲解&#xff0c;更喜欢用代码加上适当的文字描…

【转】 VC++6.0 在Win7 64位下调试,Shift+F5无法退出

Win7 64位VC6.0调试代码无法关闭窗口解决方法  VC6.0 在64位Windows7下调试的时候&#xff0c;再结束调试&#xff0c;程序无法退出&#xff0c;只能关闭VC6.0 IDE环境。  问题描述&#xff1a;当我击F5开始一个项目的调试时&#xff0c;程序在我设置的断点处停止&#xff…

使用Infinispan创建自己的Drools和jBPM持久性

我 在这里发表的原始文章&#xff1a; 您好&#xff0c;欢迎来到我打算向您展示如何创建自己的Drools和jBPM持久性实现的帖子。 我已经为流口水对象开发了基于infinispan的持久性方案&#xff0c;并且在此过程中学到了很多东西。 如果您想做某种事情&#xff0c;我打算给您一些…

Html5 填表 表单(二) input type 各种输入, 各种用户选择,上传等等泛输入用户交互

<input> 无限制输入 type 限制输入 type 如下类型 type 后还可以跟一些属性: 如<input typetext maxlength 10> 限制文本的长度为10字节 list 可以用的时候再来查, list就是当一个建议值不够的时候添加到几个. <form> <input typ…

c语言 输出音频 单片机,单片机播放WAV格式音频的理解

CSDN账号注册了3年&#xff0c;一直没有上来过&#xff0c;更不用说写博客了。我不知道博客的具体用途&#xff0c;我只想把它当做一种心得来发表&#xff0c;可能是一些技术上的理解或者生活上的小故事。好了&#xff0c;下面我将记录我对WAV播放器的理解。很久以前就看到过某…

UVALive3989 Ladies' Choice —— 稳定婚姻问题 Gale - Shapely算法

题目链接&#xff1a;https://vjudge.net/problem/UVALive-3989 题解&#xff1a; 题意&#xff1a;有n个男生和n个女生。每个女生对男神都有个好感度排行&#xff0c;同时每个男生对每个女生也有一个好感度排行。问&#xff1a;怎样配对&#xff0c;才能使的每个女生尽可能幸福…

通过命令行界面使用AWS ElasticMapReduce

在本文中&#xff0c;我将通过针对EMR的CLI使用AWS MapReduce服务&#xff08;称为ElasticMapReduce &#xff09;。 使用EMR的过程可以大致分为三个步骤&#xff1a; 设置并填充S3存储桶 创建并运行EMR作业 从S3存储桶中获取结果 在开始这三个高级步骤之前&#xff0c;还…

sublime 快捷键

Ctrl / 注释代码 <!-- <a href"http://www.baidu.com" target"_blank">百度</a> --> Tab 自动补全 <html tab 补全 html的全部基本标签 <a tab 自动补全为<a href…></a> 还有, 比如你忘记加<…

[UE4]关卡蓝图

转载于:https://www.cnblogs.com/timy/p/9053876.html

android 自定义 theme,Android使用Theme自定义Activity进入退出动画的方法

本文实例讲述了Android使用Theme自定义Activity进入退出动画的方法。分享给大家供大家参考&#xff0c;具体如下&#xff1a;有没有觉得Activity的默认动画太快了或者太难看了。。我原来使用Activity.overridePendingTransition来自定义Activity的进入动画&#xff0c;却发现没…

django 静态资源配置

最近在学习一个项目&#xff0c;django框架&#xff0c;但当 render 模板时&#xff0c;模板里有引入的图片就访问不到&#xff0c; 这是因为 django部署方式比较特别&#xff0c;采用静态文件路径:STATICFILES_DIRS的部署方式&#xff0c;之前你写的相对路径&#xff0c;绝对路…

MOXy的对象图– XML和JSON的输入/输出局部模型

假设您有一个要公开为RESTful服务的域模型。 问题是您只想输入/输出部分数据。 以前&#xff0c;您将创建一个代表子集的单独模型&#xff0c;然后使用代码在模型之间移动数据。 在EclipseLink 2.5.0中&#xff0c;我们有一个称为“对象图”的新功能&#xff0c;使您能够轻松地…

iframe的缺点

一、iframe会阻塞主页面的onload事件&#xff1b; 二、搜索引擎检索程序无法解读这种页面&#xff0c;不利于SEO&#xff1b; 三、会影响页面的并行加载。 并行加载&#xff1a;同一时间对同一域名下的加载数量是有限制的&#xff1a; 解决方法&#xff1a; 使用js动态给ifr…

使用可视化工具redisclient连接redis

可视化工具推荐&#xff1a;http://database.51cto.com/art/201505/477692.htm 1.连接redis服务端 1.1 设置连接密码&#xff1a;在redis根目录下&#xff0c;双击redis-cli.exe&#xff0c; 输入命令&#xff1a;redis-cli.exe -h 127.0.0.1 -p 6379 -n 1 1就是密码 1.2 使…

android 蓝牙项目代码,Android蓝牙聊天开源项目

前言基于Android Classic Bluetooth的蓝牙聊天软件&#xff0c;目前仅支持一对一实时通信、文件传输、好友添加、好友分组、好友在线状态更新等功能&#xff0c;其中消息发送支持文本、表情等方式。前景蓝牙技术作为一种小范围无线连接技术&#xff0c;能够在设备间实现方便快捷…

你必须知道的.NET之特性和属性(转)

你必须知道的.NET之特性和属性2008-10-13 来源&#xff1a;网络 1. 引言 attribute是.NET框架引入的有一技术亮点&#xff0c;因此我们有必要花点时间走进一个发现attribute登堂入室的入口。因为.NET Framework中使用了大量的定制特性来完成代码约定&#xff0c;[Serializable]…