1. 程式人生 > 其它 >.net 判斷座標是否在範圍內_學習|判斷一個點是否在三角形內

.net 判斷座標是否在範圍內_學習|判斷一個點是否在三角形內

技術標籤:.net 判斷座標是否在範圍內

作者:山海亦可平

連結:https://blog.nowcoder.net/n/385dfbad3f2a4eaaabc50347c472aa50

來源:牛客網

數學基礎

向量點乘(Dot Product)

點乘比較簡單,是相應元素的乘積的和:

V1( x1, y1)+ V2(x2, y2) = x1*x2 + y1*y2

注意結果不是一個向量,而是一個標量(Scalar)。點乘有什麼用呢,我們有:

A·B= |A||B|Cos(θ)。

θ是向量A和向量B見的夾角。這裡|A|我們稱為向量A的模(norm),也就是A的長度, 在二維空間中就是|A| = sqrt(

53f0c17a3e8767207c61904afdb0bf9b.png

)。這樣我們就和容易計算兩條線的夾角:Cos(θ) = A·B / (|A||B|)


這可以告訴我們如果點乘的結果,簡稱點積,為0的話就表示這兩個向量垂直。當兩向量平行時,點積有最大值.另外,點乘運算不僅限於2維空間,他可以推廣到任意維空間.

叉乘(cross product)

相對於點乘,叉乘可能更有用吧。2維空間中的叉乘是:V1(x1, y1) X V2(x2, y2) = x1y2 -y1x2

看起來像個標量,事實上叉乘的結果是個向量,方向在z軸上。上述結果是它的模。在二維空間裡,讓我們暫時忽略它的方向,將結果看成一個向量,那麼這個結果類似於上述的點積,我們有:A x B = |A||B|Sin(θ)

然而角度 θ和上面點乘的角度有一點點不同,他是有正負的,是指從A到B的角度。另外還有一個有用的特徵那就是叉積的絕對值就是A和B為兩邊說形成的平行四邊形的面積。也就是AB所包圍三角形面積的兩倍。這個還是很顯然的啦。在計算面積時,我們要經常用到叉積。

一次牛客的比賽中遇到了這個題,當時是用下面的方法一來實現的,之後看了些部落格,發現還有好幾種更好的方法,當時實現起來程式碼還顯得特別不好看2333,太菜了。

376f042807e87b06551adec492516393.png

判斷點在三角形內

面積法:

利用面積,如圖中

f8391875d1eed06bf4f90abf20cad1ca.png

,求三角形面積的方法就可以用上面提到的利用叉積就行了,注意記得加上絕對值,因為叉積可能為負。還有種簡單的方法是利用內角和為

19a74423bf08d5e8b4bbd585bd8a4424.png

但效率低下,就不管了。

同側法:

首先看一下這個問題,如何判斷某兩個點在某條直線的同一側

34f5abe023afb3f10d29bfe50488c25b.png

根據向量的叉乘以及右手螺旋定則,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不在三角形內部。

一個不知道怎麼命名的方法

該方法也用到了向量。對於三角形ABC和一點P,可以有如下的向量表示:

1059f88d58eaf0892ecbdd4571a483d3.png

p點在三角形內部的充分必要條件是:1 >= u >= 0, 1 >= v >= 0, u+v <= 1。

已知A,B,C,P四個點的座標,可以求出u,v,把上面的式子分別點乘向量AC和向量AB

8edfd033a126edbc39ea77b72dab258f.png

解方程得到:

6f7248d5bf87ffacaf6b8c49e5f35b92.png

解出u,v後只需要看他們是否滿足“1 >= u >= 0, 1 >= v >= 0, u+v <= 1”,如滿足,則,p 在三角形內。

(u = 0時,p在AB上, v = 0時,p在AC上,兩者均為0時,p和A重合)

又一個不知道怎麼命名的演算法:

該演算法和演算法2類似,可以看作是對演算法2的簡化,也是用到向量的叉乘。假設三角形的三個點按照順時針(或者逆時針)順序是A,B,C。對於某一點P,求出三個向量PA,PB,PC, 然後計算以下三個叉乘(^表示叉乘符號):

t1 = PA^PB,
t2 = PB^PC,
t3 = PC^PA,

如果t1,t2,t3同號(同正或同負),那麼P在三角形內部,否則在外部。

經過測試,演算法4最快,演算法3次之,接著演算法2,演算法1最慢。直觀的從計算量上來看,也是演算法4的計算量最少。

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<string>
#include<cmath>
#include<ctime>
#include<iostream>
#include<algorithm>
#include<stack>
#include<queue>
#include<vector>
#include<list>
#include<map>
#include<set>
using namespace std;
typedef long long ll;
//類定義:二維向量
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
{
public:
 Vector2d pointA_, pointB_, pointC_;
  
public:
 Triangle(Vector2d point1, Vector2d point2, Vector2d point3)
 :pointA_(point1), pointB_(point2), pointC_(point3){}
 //記得判斷三點是否共線
  
 Triangle():pointA_(), pointB_(), pointC_(){};
 //計算三角形面積
 double ComputeTriangleArea()
 {
 //依據兩個向量的叉乘來計算
 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;
 }
 // t1 = PA^PB, t2 = PB^PC,  t3 = PC^PA, t1,t2,t3 同號則 P在三角形內部
 bool IsPointInTriangle4(const Vector2d pointP)
 {
 Vector2d PA = pointA_.Minus(pointP);
 Vector2d PB = pointB_.Minus(pointP);
 Vector2d PC = pointC_.Minus(pointP);
 double t1 = PA.CrossProduct(PB);
 double t2 = PB.CrossProduct(PC);
 double t3 = PC.CrossProduct(PA);
 return t1*t2 >= 0 && t1*t3 >= 0 &&t2*t3>=0;
 }
};
 
int main(){
 Triangle a;
 while(scanf("%lf%lf%lf%lf%lf%lf",&a.pointA_.x_,&a.pointA_.y_,&a.pointB_.x_,&a.pointB_.y_,&a.pointC_.x_,&a.pointC_.y_)!=EOF){
 Vector2d p;
 scanf("%lf%lf",&p.x_,&p.y_);
 if(a.IsPointInTriangle1(p)) printf("YESn");
 else printf("NOn");
 }
 return 0;
} 

檢視作者更多部落格:https://blog.nowcoder.net/phh

歡迎關注公眾號:牛客NOIP競賽學