二维平面上判断点是否在三角形内

最近在项目中碰到的这个问题,在此记录一下。已知三角形的三个顶点坐标,判断某个点是否在三角形中(在三角形的边上,我们也视作在三角形中),本文给出了三种方法。

image

 

算法1

利用面积法,如上图所示,如果点P在三角形ABC的内部,则三个小三角形PAB, PBC, PAC的面积之和 = ABC的面积,反之则不相等。

已知三角形的三个顶点坐标求其面积,可以根据向量的叉乘,参考here

该算法详见后面的函数:IsPointInTriangle1

 

算法2

首先看一下这个问题,如何判断某两个点在某条直线的同一侧(代码中函数:IsPointAtSameSideOfLine)?

image

根据向量的叉乘以及右手螺旋定则,AB^AM (^表示叉乘,这里向量省略了字母上面的箭头符号)的方向为向外指出屏幕,AB^AN也是向外指出屏幕,但AB^AO的方向是向内指向屏幕,因此M,N在直线AB的同侧,M ,O在直线AB的两侧。实际计算时,只需要考虑叉积的数值正负

假设以上各点坐标为A(0,0), B(4,0), M(1,2), N(3,4), O(3,-4), 则:

AB^AM = (4,0)^(1,2) = 4*2 - 0*1 = 8

AB^AN = (4,0)^(3,4) = 4*4 – 0*3 = 16

AB^AO = (4,0)^(3,-4) = 4*-4 – 0*3 = –16

由上面的数值可知,可以根据数值的正负判断叉乘后向量的方向。即,如果叉积AB^AM 和 AB^AN的结果同号,那么M,N两点就在直线的同侧,否则不在同一侧。特殊地,如果点M在直线AB上,则AB^AM的值为0。(如果是在三维坐标系中,求出的叉积是一个向量,可以根据两个向量的点积结果正负来判断两个向量的是否指向同一侧)                                                         本文地址

 

以上的问题解决了,就很容易的用来判断某个点是否在三角形内,如果P在三角形ABC内部,则满足以下三个条件:P,A在BC的同侧、P,B在AC的同侧、PC在AB的同侧。某一个不满足则表示P不在三角形内部。

该算法详见后面的函数:IsPointInTriangle2

 

算法3

该方法也用到了向量。对于三角形ABC和一点P,可以有如下的向量表示:

image

如果p点在三角形内部,则满足:1 >= u >= 0,   1 >= v >= 0,   u+v <= 1。

 

已知A,B,C,P四个点的坐标,可以求出u,v,把上面的式子分别点乘向量AC和向量AB

image

解方程得到:

image

解出u,v后只需要看他们是否满足“1 >= u >= 0, 1 >= v >= 0, u+v <= 1”,如满足,则,p 在三角形内。

(u = 0时,p在AB上, v = 0时,p在AC上,两者均为0时,p和A重合)

该算法详见后面的函数:IsPointInTriangle3


经过测试,算法3最快,算法2次之,算法2最慢。直观的从计算量上来看,也是算法3的计算量最少。

以下是代码,定义了两个类:二维向量类 和 三角形类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
#include<string>
  #include<sstream>
  #include<fstream>
  #include<cstdio>
  #include<cstring>
  #include<iostream>
  #include<vector>
  #include<stack>
  #include<queue>
  #include<list>
  #include<algorithm>
  #include<ctime>
  #include<unordered_map>
  #include<map>
  #include<typeinfo>
  #include<cmath>
  #include<ctime>
  #include<climits>
  using  namespace  std;
 
//类定义:二维向量
class  Vector2d
{
public :
     double  x_;
     double  y_;
 
public :
     Vector2d( double  x, double  y):x_(x), y_(y){}
     Vector2d():x_(0), y_(0){}
 
     //二维向量叉乘, 叉乘的结果其实是向量,方向垂直于两个向量组成的平面,这里我们只需要其大小和方向
     double  CrossProduct( const  Vector2d vec)
     {
         return  x_*vec.y_ - y_*vec.x_;
     }
 
     double  DotProduct( const  Vector2d vec)
     {
         return  x_ * vec.x_ + y_ * vec.y_;
     }
 
     //二维向量减法
     Vector2d Minus( const  Vector2d vec) const
     {
         return  Vector2d(x_ - vec.x_, y_ - vec.y_);
     }
 
     //判断点M,N是否在直线AB的同一侧
     static  bool  IsPointAtSameSideOfLine( const  Vector2d &pointM, const  Vector2d &pointN,
                                         const  Vector2d &pointA, const  Vector2d &pointB)
     {
         Vector2d AB = pointB.Minus(pointA);
         Vector2d AM = pointM.Minus(pointA);
         Vector2d AN = pointN.Minus(pointA);
 
         //等于0时表示某个点在直线上
         return  AB.CrossProduct(AM) * AB.CrossProduct(AN) >= 0;
     }
};
 
//三角形类
class  Triangle
{
private :
     Vector2d pointA_, pointB_, pointC_;
 
public :
     Triangle(Vector2d point1, Vector2d point2, Vector2d point3)
         :pointA_(point1), pointB_(point2), pointC_(point3)
     {
         //todo 判断三点是否共线
     }
 
     //计算三角形面积
     double  ComputeTriangleArea()
     {
         //依据两个向量的叉乘来计算,可参考http://blog.csdn.net/zxj1988/article/details/6260576
         Vector2d AB = pointB_.Minus(pointA_);
         Vector2d BC = pointC_.Minus(pointB_);
         return  fabs (AB.CrossProduct(BC) / 2.0);
     }
 
     bool  IsPointInTriangle1( const  Vector2d pointP)
     {
         double  area_ABC = ComputeTriangleArea();
         double  area_PAB = Triangle(pointP, pointA_, pointB_).ComputeTriangleArea();
         double  area_PAC = Triangle(pointP, pointA_, pointC_).ComputeTriangleArea();
         double  area_PBC = Triangle(pointP, pointB_, pointC_).ComputeTriangleArea();
 
         if ( fabs (area_PAB + area_PBC + area_PAC - area_ABC) < 0.000001)
             return  true ;
         else  return  false ;
     }
 
     bool  IsPointInTriangle2( const  Vector2d pointP)
     {
         return  Vector2d::IsPointAtSameSideOfLine(pointP, pointA_, pointB_, pointC_) &&
             Vector2d::IsPointAtSameSideOfLine(pointP, pointB_, pointA_, pointC_) &&
             Vector2d::IsPointAtSameSideOfLine(pointP, pointC_, pointA_, pointB_);
     }
 
     bool  IsPointInTriangle3( const  Vector2d pointP)
     {
         Vector2d AB = pointB_.Minus(pointA_);
         Vector2d AC = pointC_.Minus(pointA_);
         Vector2d AP = pointP.Minus(pointA_);
         double  dot_ac_ac = AC.DotProduct(AC);
         double  dot_ac_ab = AC.DotProduct(AB);
         double  dot_ac_ap = AC.DotProduct(AP);
         double  dot_ab_ab = AB.DotProduct(AB);
         double  dot_ab_ap = AB.DotProduct(AP);
 
         double  tmp = 1.0 / (dot_ac_ac * dot_ab_ab - dot_ac_ab * dot_ac_ab);
         
         double  u = (dot_ab_ab * dot_ac_ap - dot_ac_ab * dot_ab_ap) * tmp;
         if (u < 0 || u > 1)
             return  false ;
         double  v = (dot_ac_ac * dot_ab_ap - dot_ac_ab * dot_ac_ap) * tmp;
         if (v < 0 || v > 1)
             return  false ;
 
         return  u + v <= 1;
     }
};
 
 
int  main()
{
     Triangle tri(Vector2d(0,0), Vector2d(6,6), Vector2d(12,0));
     srand ( time (0));
     for ( int  i = 0; i < 20; ++i)
     {
          Vector2d point(( rand ()*1.0 / RAND_MAX) * 12, ( rand ()*1.0 / RAND_MAX) * 6);
          cout<<point.x_<< " " <<point.y_<< ":     " ;
          cout<<tri.IsPointInTriangle1(point)<< " " ;
          cout<<tri.IsPointInTriangle2(point)<< " " ;
          cout<<tri.IsPointInTriangle3(point)<<endl;
     }
}

 

参考资料:

http://www.cnblogs.com/graphics/archive/2010/08/05/1793393.html

http://blog.csdn.net/fox64194167/article/details/8147460

 

【版权声明】转载请注明出处:http://www.cnblogs.com/TenosDoIt/p/4024413.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值