个人亲自编写、测试,可以正常使用
 
道理看原文,这里不多说
 
网上找到的几篇基本都不能用的
 
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),则下列方程组必然成立:

  1. x0-x1=k1(x2-x1)
  2. y0-y1=k1(y2-y1)
  3. x0-x3=k2(x4-x3)
  4. y0-y3=k2(y4-y3)

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

  1. x0(y2-y1)-x1(y2-y1)=y0(x2-x1)-y1(x2-x1)
  2. x0(y4-y3)-x3(y4-y3)=y0(x4-x3)-y3(x4-x3)

将含有未知数x0和y0的项移到左边,常数项移动到右边,得:

  1. (y2-y1)x0+(x1-x2)y0=(y2-y1)x1+(x1-x2)y1
  2. (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#的更多相关文章

  1. HDU 4631 Sad Love Story 平面内最近点对

    http://acm.hdu.edu.cn/showproblem.php?pid=4631 题意: 在平面内依次加点,求每次加点后最近点对距离平方的和 因为是找平面最近点对...所以加点以后这个最短 ...

  2. HDU 1007 Quoit Design 平面内最近点对

    http://acm.hdu.edu.cn/showproblem.php?pid=1007 上半年在人人上看到过这个题,当时就知道用分治但是没有仔细想... 今年多校又出了这个...于是学习了一下平 ...

  3. poj 1106(半圆围绕圆心旋转能够覆盖平面内最多的点)

    Transmitters Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 4955   Accepted: 2624 Desc ...

  4. PCB Genesis加邮票孔(线与线)实现算法

    一.Genesis加邮票孔(线与线)实现算法 1.鼠标点击位置P点, 2.通过P点求出,垂足2个点:P1C与P2C (两个点即距离2条线段垂直的垂足点) 3.计算P1C到P2C方位角(假设置为变量PA ...

  5. asp.net mvc 碰到 XML 解析错误:找不到根元素 位置

    具体报错信息如下: XML 解析错误:找不到根元素 位置:moz-nullprincipal:{4a1d2b7c-6d07-468e-9df9-2267a0422c93} 行 1,列 1: 网上给出的 ...

  6. ArcGIS API for Silverlight 使用GeometryService求解线与线的交点

    ///画线 void btn_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { Button btn = sender as B ...

  7. {bzoj2338 [HNOI2011]数矩形 && NBUT 1453 LeBlanc}平面内找最大矩形

    思路: 枚举3个点,计算第4个点并判断是否存在,复杂度为O(N3logN)或O(N3α) 考虑矩形的对角线,两条对角线可以构成一个矩形,它们的长度和中点必须完全一样,于是将所有线段按长度和中点排序,那 ...

  8. fzu 1015 土地划分(判断线段相交+求出交点+找规律)

    链接:http://acm.fzu.edu.cn/problem.php?pid=1015  Problem 1015 土地划分 Accept: 714    Submit: 1675Time Lim ...

  9. CSS标题线(删除线贯穿线效果)实现之一二

    缘起 其实看到这个问题,心里已经默默把代码已经码好了~,不就想下面这样嘛:JSBIN代码示例 嗯,是的,我们日常确实基本上就是用的这种方式,也没啥问题呀~,来个背景色定下位就欧拉欧拉的了. 不过,因为 ...

随机推荐

  1. log4net 自定义Layout日志字段

    最近在使用log4net的时候有一个简单的需求,就是自定义个格式化输出符.这个输出符是专门用来帮我记录下业务ID.业务类型的.比如,“businessID:328593,businessType: o ...

  2. 搭建自己的PHP框架心得(二)

    h2:first-child, body>h1:first-child, body>h1:first-child+h2, body>h3:first-child, body>h ...

  3. Linux From Scratch(从零开始构建Linux系统,简称LFS)- Version 7.7(二)

    七. 构建临时系统 1. 通用编译指南 a. 确认是否正确设置了 LFS 环境变量 echo $LFS b. 假定你已经正确地设置了宿主系统的符号链接: 1)shell 使用的是 bash. 2)sh ...

  4. FusionCharts参数说明 (中文)

    FushionCharts是把抽象数据图示化的套件,使用方便,配置简单.其相关参数中文说明如下. FusionCharts Free中文开发指南第二版.pdf 功能特性 animation       ...

  5. 2016 最佳 Linux 发行版排行榜

    2015年,不管在企业市场还是个人消费市场都是 Linux非常重要的一年.作为一个自2005年起就开始使用 Linux的 Linuxer ,我门见证了 Linux在过去十年的成长.2016 Linux ...

  6. centos 7 install python spynner

    yum install python-devel yum install libXtst-devel pip install autopy pip install spynner import spy ...

  7. Excellent Articles

    Lisp The roots of lisp Recursive Functions of Symbolic Expressions and Their Computation by Machine, ...

  8. 【2016-11-3】【坚持学习】【Day18】【ADO.NET 】

    使用Connection创建数据库连接 使用Command创建命令 使用ExecuteScalar,ExecuteNonQuery,ExecuteReader方法来执行命令 使用DataReader来 ...

  9. 洛谷P1196 银河英雄传说[带权并查集]

    题目描述 公元五八○一年,地球居民迁移至金牛座α第二行星,在那里发表银河联邦 创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展. 宇宙历七九九年,银河系的两大军事集团在巴米利恩星域爆发战争.泰山 ...

  10. Jenkins学习三:介绍一些Jenkins的常用功能

    Jenkins其实就是一个工具,这个工具的作用就是调用各种其他的工具来达成你的目的. 1.备份.迁移.恢复jenkins 首先找到JENKINS_HOME,因为Jenkins的所有的数据都是以文件的形 ...