平面内,线与线 两条线找交点 两条线段的位置关系(相交)判定与交点求解 C#
bool Equal(float f1, float f2)
{
return (Math.Abs(f1 - f2) < 1f);
}
bool dayu(Point p1, Point p2)////比较两点坐标大小,先比较x坐标,若相同则比较y坐标
{
return (p1.X > p2.X || (Equal(p1.X , p2.X) && p1.Y > p2.Y));
}
bool dengyu(Point p1, Point p2)////判断两点是否相等
{
return (Equal(p1.X, p2.X) && Equal(p1.Y, p2.Y));
}
float ji(Point p1, Point p2)////计算两向量外积
{
return (p1.X * p2.Y - p1.Y * p2.X);
} //判定两线段位置关系,并求出交点(如果存在)。返回值列举如下:
//[有重合] 完全重合(6),1个端点重合且共线(5),部分重合(4)
//[无重合] 两端点相交(3),交于线上(2),正交(1),无交(0),参数错误(-1)
int Intersection(Point p1, Point p2, Point p3, Point p4, ref Point point) {
//保证参数p1!=p2,p3!=p4
if (p1 == p2 || p3 == p4) {
return -; //返回-1代表至少有一条线段首尾重合,不能构成线段
}
//为方便运算,保证各线段的起点在前,终点在后。
if (dayu(p1,p2))
{
Point pTemp = p1;
p1 = p2;
p2 = pTemp;
// swap(p1, p2);
}
if (dayu(p3, p4)) {
Point pTemp = p3;
p3 = p4;
p4 = pTemp;
//swap(p3, p4);
}
//判定两线段是否完全重合
if (p1 == p3 && p2 == p4) {
return ;
}
//求出两线段构成的向量
Point v1 = new Point(p2.X - p1.X, p2.Y - p1.Y), v2 = new Point(p4.X - p3.X, p4.Y - p3.Y);
//求两向量外积,平行时外积为0
float Corss = ji(v1, v2);
//如果起点重合
if (dengyu(p1,p3))
{
point = p1;
//起点重合且共线(平行)返回5;不平行则交于端点,返回3
return (Equal(Corss, ) ? : );
}
//如果终点重合
if (dengyu(p2,p4)) {
point = p2;
//终点重合且共线(平行)返回5;不平行则交于端点,返回3
return (Equal(Corss, ) ? : );
}
//如果两线端首尾相连
if (dengyu(p1,p4)) {
point = p1;
return ;
}
if (dengyu(p2, p3)) {
point = p2;
return ;
}//经过以上判断,首尾点相重的情况都被排除了
//将线段按起点坐标排序。若线段1的起点较大,则将两线段交换
if(dayu(p1,p3))
{
Point pTemp = p1;
p1 = p3;
p3 = pTemp; pTemp = p2;
p2 = p4;
p4 = pTemp; pTemp = v1;
v1 = v2;
v2 = pTemp;
//swap(p1, p3);
//swap(p2, p4);
//更新原先计算的向量及其外积
//swap(v1, v2);
Corss= ji(v1, v2);
}
//处理两线段平行的情况
if(Equal(Corss,)){
//做向量v1(p1, p2)和vs(p1,p3)的外积,判定是否共线
Point vs =newPoint(p3.X - p1.X, p3.Y - p1.Y);
//外积为0则两平行线段共线,下面判定是否有重合部分
if(Equal(ji(v1, vs),)){
//前一条线的终点大于后一条线的起点,则判定存在重合
if(dayu(p2, p3)){
point = p3;
return4;//返回值4代表线段部分重合
}
}//若三点不共线,则这两条平行线段必不共线。
//不共线或共线但无重合的平行线均无交点
return0;
}//以下为不平行的情况,先进行快速排斥试验
//x坐标已有序,可直接比较。y坐标要先求两线段的最大和最小值
float ymax1 = p1.Y, ymin1 = p2.Y, ymax2 = p3.Y, ymin2 = p4.Y;
if(ymax1 < ymin1){
float fTemp = ymax1;
ymax1 = ymin1;
ymin1 = fTemp;
//swap(ymax1, ymin1);
}
if(ymax2 < ymin2){
//swap(ymax2, ymin2);
float fTemp = ymax2;
ymax2 = ymin2;
ymin2 = fTemp;
}
//如果以两线段为对角线的矩形不相交,则无交点
if(p1.X > p4.X || p2.X < p3.X || ymax1 < ymin2 || ymin1 > ymax2)
{
return0;
}//下面进行跨立试验
Point vs1 =newPoint(p1.X - p3.X, p1.Y - p3.Y), vs2 =newPoint(p2.X - p3.X, p2.Y - p3.Y);
Point vt1 =newPoint(p3.X - p1.X, p3.Y - p1.Y), vt2 =newPoint(p4.X - p1.X, p4.Y - p1.Y);
float s1v2, s2v2, t1v1, t2v1;
//根据外积结果判定否交于线上
if(Equal(s1v2 = ji(vs1, v2),)&& dayu(p4, p1)&& dayu(p1, p3)){
point = p1;
return2;
}
if(Equal(s2v2 = ji(vs2 ,v2),)&& dayu(p4 ,p2)&& dayu(p2 , p3)){
point = p2;
return2;
}
if(Equal(t1v1 = ji(vt1 , v1),)&& dayu(p2 , p3)&& dayu(p3, p1)){
point = p3;
return2;
}
if(Equal(t2v1 = ji(vt2 , v1),)&& dayu(p2 , p4)&& dayu(p4 , p1)){
point = p4;
return2;
}//未交于线上,则判定是否相交
if(s1v2 * s2v2 >|| t1v1 * t2v1 >){
return0;
}//以下为相交的情况,算法详见文档
//计算二阶行列式的两个常数项
floatConA= p1.X * v1.Y - p1.Y * v1.X;
floatConB= p3.X * v2.Y - p3.Y * v2.X;
//计算行列式D1和D2的值,除以系数行列式的值,得到交点坐标
point.X =(int)((ConB* v1.X -ConA* v2.X)/Corss);
point.Y =(int)((ConB* v1.Y -ConA* v2.Y)/Corss);
//正交返回1
return1;
}
http://www.cnblogs.com/devymex/archive/2010/08/19/1803885.html
概念
平面内两条线段位置关系的判定在很多领域都有着广泛的应用,比如游戏、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 <iostream>
#include <cmath>
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代表至少有一条线段首尾重合,不能构成线段
}
//为方便运算,保证各线段的起点在前,终点在后。
if (p1 > p2) {
swap(p1, p2);
}
if (p3 > p4) {
swap(p3, p4);
}
//判定两线段是否完全重合
if (p1 == p3 && p2 == p4) {
return ;
}
//求出两线段构成的向量
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, ) ? : );
}
//如果终点重合
if (p2 == p4) {
Int = p2;
//终点重合且共线(平行)返回5;不平行则交于端点,返回3
return (Equal(Corss, ) ? : );
}
//如果两线端首尾相连
if (p1 == p4) {
Int = p1;
return ;
}
if (p2 == p3) {
Int = p2;
return ;
}//经过以上判断,首尾点相重的情况都被排除了
//将线段按起点坐标排序。若线段1的起点较大,则将两线段交换
if (p1 > p3) {
swap(p1, p3);
swap(p2, p4);
//更新原先计算的向量及其外积
swap(v1, v2);
Corss = v1 ^ v2;
}
//处理两线段平行的情况
if (Equal(Corss, )) {
//做向量v1(p1, p2)和vs(p1,p3)的外积,判定是否共线
POINTF vs = {p3.x - p1.x, p3.y - p1.y};
//外积为0则两平行线段共线,下面判定是否有重合部分
if (Equal(v1 ^ vs, )) {
//前一条线的终点大于后一条线的起点,则判定存在重合
if (p2 > p3) {
Int = p3;
return ; //返回值4代表线段部分重合
}
}//若三点不共线,则这两条平行线段必不共线。
//不共线或共线但无重合的平行线均无交点
return ;
} //以下为不平行的情况,先进行快速排斥试验
//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 ;
}//下面进行跨立试验
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, ) && p4 > p1 && p1 > p3) {
Int = p1;
return ;
}
if (Equal(s2v2 = vs2 ^ v2, ) && p4 > p2 && p2 > p3) {
Int = p2;
return ;
}
if (Equal(t1v1 = vt1 ^ v1, ) && p2 > p3 && p3 > p1) {
Int = p3;
return ;
}
if (Equal(t2v1 = vt2 ^ v1, ) && p2 > p4 && p4 > p1) {
Int = p4;
return ;
} //未交于线上,则判定是否相交
if(s1v2 * s2v2 > || t1v1 * t2v1 > ) {
return ;
} //以下为相交的情况,算法详见文档
//计算二阶行列式的两个常数项
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 ;
}
//主函数
int main(void) {
//随机生成100个测试数据
for (int i = ; i < ; ++i) {
POINTF p1, p2, p3, p4, Int;
p1.x = (float)(rand() % ); p1.y = (float)(rand() % );
p2.x = (float)(rand() % ); p2.y = (float)(rand() % );
p3.x = (float)(rand() % ); p3.y = (float)(rand() % );
p4.x = (float)(rand() % ); p4.y = (float)(rand() % );
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 > ) {
cout << '(' << Int.x << ',' << Int.y << ')';
}
cout << endl;
}
return ;
}
作者:王雨濛;新浪微博:@吉祥村码农;来源:《程序控》博客 -- http://www.cnblogs.com/devymex/
此文章版权归作者所有(有特别声明的除外),转载必须注明作者及来源。您不能用于商业目的也不能修改原文内容。
平面内,线与线 两条线找交点 两条线段的位置关系(相交)判定与交点求解 C#的更多相关文章
- HDU 4631 Sad Love Story 平面内最近点对
http://acm.hdu.edu.cn/showproblem.php?pid=4631 题意: 在平面内依次加点,求每次加点后最近点对距离平方的和 因为是找平面最近点对...所以加点以后这个最短 ...
- HDU 1007 Quoit Design 平面内最近点对
http://acm.hdu.edu.cn/showproblem.php?pid=1007 上半年在人人上看到过这个题,当时就知道用分治但是没有仔细想... 今年多校又出了这个...于是学习了一下平 ...
- poj 1106(半圆围绕圆心旋转能够覆盖平面内最多的点)
Transmitters Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 4955 Accepted: 2624 Desc ...
- PCB Genesis加邮票孔(线与线)实现算法
一.Genesis加邮票孔(线与线)实现算法 1.鼠标点击位置P点, 2.通过P点求出,垂足2个点:P1C与P2C (两个点即距离2条线段垂直的垂足点) 3.计算P1C到P2C方位角(假设置为变量PA ...
- asp.net mvc 碰到 XML 解析错误:找不到根元素 位置
具体报错信息如下: XML 解析错误:找不到根元素 位置:moz-nullprincipal:{4a1d2b7c-6d07-468e-9df9-2267a0422c93} 行 1,列 1: 网上给出的 ...
- ArcGIS API for Silverlight 使用GeometryService求解线与线的交点
///画线 void btn_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Button btn = sender as B ...
- {bzoj2338 [HNOI2011]数矩形 && NBUT 1453 LeBlanc}平面内找最大矩形
思路: 枚举3个点,计算第4个点并判断是否存在,复杂度为O(N3logN)或O(N3α) 考虑矩形的对角线,两条对角线可以构成一个矩形,它们的长度和中点必须完全一样,于是将所有线段按长度和中点排序,那 ...
- fzu 1015 土地划分(判断线段相交+求出交点+找规律)
链接:http://acm.fzu.edu.cn/problem.php?pid=1015 Problem 1015 土地划分 Accept: 714 Submit: 1675Time Lim ...
- CSS标题线(删除线贯穿线效果)实现之一二
缘起 其实看到这个问题,心里已经默默把代码已经码好了~,不就想下面这样嘛:JSBIN代码示例 嗯,是的,我们日常确实基本上就是用的这种方式,也没啥问题呀~,来个背景色定下位就欧拉欧拉的了. 不过,因为 ...
随机推荐
- MongoDB学习笔记~自己封装的Curd操作(按需更新的先决条件)
回到目录 我们上一讲中介绍了大叔封装的Mongo仓储,其中介绍了几个不错的curd操作,而对于按需更新内部子对象,它是有条件的,即你的子对象不能为null,也就是说,我们在建立主对象时,应该为子对象赋 ...
- CentOS系统 yum 安装 iftop实时流量监控工具
一. 不多说其他的无用话题.直接看步骤: 1.要安装某个工具,首先得知道这个工具需要依赖包. eg:iftop 就依赖 以下这几个依赖包(不清楚工具依赖什么包“Google.百度”). flex ...
- Python字典的基本组成以及用法
#!/usr/bin/env python# -*- coding:utf-8 -*-"""老规矩以下方法环境2.7.x,请3.x以上版本的朋友记得格式print(输出内 ...
- python数据库操作对主机批量管理
import paramiko import MySQLdb conn = MySQLdb.connect(host=',db='host') cur = conn.cursor(cursorclas ...
- 断言(Assert)与异常(Exception)
断言是被用来检查非法情况而不是错误情况,即在该程序正常工作时绝不应该发生的非法情况,用来帮助开发人员对问题的快速定位.异常处理用于对程序发生异常情况的处理,增强程序的健壮性.容错性,减少程序使用中对用 ...
- WPF RichTextbox
WPFTextBoxAutoComplete AvalonEdit WPF SyntaxHighlightBox WinForm 下的 Fast Colored TextBox for Synta ...
- 移位操作(>>、<<)
public static void main(String[] args) { /** 移位就是二进制的数往左或右移动,因为接近计算机底层,所以速度比较快 * 8 4 2 1 * * 7 化为二进制 ...
- [No00006D]下载离线版的github for windows【以Github for Windows 3.0.110.为例】
目录 先上地址后讲原理: 原理: 11个目录的文件怎么一口气下载呢? 最后,把下好的文件批量名,同时将GitHub.exe.manifest也放到软件根目录下(与GitHub.exe同级): 今后的猜 ...
- HBASE 安装法
http://www.cnblogs.com/nexiyi/p/hbase_intro_94.html
- GL_ARRAY_BUFFER 和 GL_ELEMENT_ARRAY_BUFFER
转载请注明出处.系列教程: webgl-lesson.wysaid.org 第七话, 了解OpenGL的几种Array Buffer,实现大量顶点的批量绘制, 以及映射纹理坐标 每一话都间隔很久,其实 ...