這是乙個三維空間中的平面問題(三角形確定乙個平面),假設三角形的三個頂點為a(xa, ya, za)、b(xb, yb, zb)、c(xc, yc, zc),另外乙個頂點為p(xp, yp, zp)。
問題:判斷頂點 p 是否位於abc組成的三角形上(內部和邊界)。
方法1:效率較低的方法。
(1) 利用面積判斷。如果頂點落在三角形上,那麼頂點p分別和abc三點連線後組成的三個小三角形的面積之和一定等於三角形abc的面積,否則p位於三角形abc之外。三角形面積的求法有海**式,叉積法等。
(2) 利用角度判斷。連線頂點p和三角形的三個頂點abc,每兩條邊的夾角之和如果等於 2*pi,則 p 位於三角形上,否則位於三角形外。兩個向量之間的夾角可以利用向量的點積來求,不過要求反三角函式。
方法2:同側檢測:判定頂點是否位於三角形三條邊的同側。
如果頂點p位於三角形的內部,那麼按順時針或者逆時針將三角形的三條邊形成的向量首尾相接(ab, bc, ca),頂點p一定是位於三個向量的同側。如果頂點相對於三向量的位置發生了改變,比如逆時針時由左側變為了右側,則頂點一定位於三角形的外部。
如何判定頂點p位於向量的哪側呢?我們知道向量的叉積是乙個新的向量,具有方向。所以,通過向量叉積即可確定乙個頂點位於乙個向量的哪一側。
那麼又如何確定順時針還是逆時針呢?其實,不需考慮順時針還是逆時針。只要頂點 p 與三角形除卻判斷的向量(比如ab)外餘下的那個頂點(比如c)位於同一側,就可以判定p的相對位置。
接下來就是如何確定兩點位於乙個向量的同側?因為向量的點積可以確定兩個向量夾角的大小,當兩個向量的點積大於0時,夾角小於 pi/2,可認為同向,否則兩向量反向。於是,確定兩個頂點p、c是否位於線段ab同側的方法為:
構造三個向量:v1 = b - a,v2 = p - a,v3 = c - a;
判斷p、c相對於ab的方向,做叉積:vp = v1 x v2, vc = v3 x v1;
判斷vp、vc是否同向,做點積:d = vp ・ vc;
如果 d >= 0,說明叉積向量同向,進而推出p、c位於ab的同側。
最後,如果針對三角形的三條邊,p都和另外的乙個頂點同向的話,那麼p一定位於三角形上。
所以整個演算法的流程如下:
function sameside(p1,p2, a,b)演算法沒有開根號和反三角函式的求解,所以效率相對較高。cp1 = crossproduct(b-a, p1-a)
cp2 = crossproduct(b-a, p2-a)
if dotproduct(cp1, cp2) >= 0 then return true
else return false
function pointin********(p, a,b,c)
if sameside(p,a, b,c) and sameside(p,b, a,c) and sameside(p,c, a,b) then
return true
else
return false
方法3:重心法(barycentric technique)
在三角形確定的平面上的任意一點p,都可以使用乙個重心座標的形式來表示p的座標:
p = ua + vb + wc,u + v + w = 1;
消去w:
p - c = u(a - c) + v(b - c)
上式可以理解為,平面上的任意乙個向量都能表示成兩個不共線的向量的線性形式。如果p位於三角形上,那麼u、v需要滿足:
0 <= u, v <= 1 && u + v <= 1
為簡化書寫,我們做以下標記:
v2 = p - c, v0 = a - c, v1 = b - c 進而
v2 = u*v0 + v*v1
接下來就是求得u、v。因為v0、v1和v2是已知向量,可以直接利用座標來求解。但是三維空間中,可以得到三個(xyz)關於u、v的方程,而我們只需要兩個方程即可,如何選擇需要進一步的做些判斷,如果選擇方程組不合理可能無法解出u、v。
這裡有個避免上述選擇的做法,就是利用向量的點積,將向量轉化為實數,方程兩邊同時點積乙個向量:
(v2) ・ v0 = (u * v0 + v * v1) ・ v0
(v2) ・ v1 = (u * v0 + v * v1) ・ v1
進而 v2 ・ v0 = u * (v0 ・ v0) + v * (v1 ・ v0)
v2 ・ v1 = u * (v0 ・ v1) + v * (v1 ・ v1)
利用線性方程組的求解方法可以得到u、v:
u = ((v1・v1)(v2・v0)-(v1・v0)(v2・v1)) / ((v0・v0)(v1・v1) - (v0・v1)(v1・v0))
v = ((v0・v0)(v2・v1)-(v0・v1)(v2・v0)) / ((v0・v0)(v1・v1) - (v0・v1)(v1・v0))
此處,看到出現了除法,不能保證除數不等於0。可以分析什麼情況下除數為0,預先判斷即可。
(v0・v0)(v1・v1) - (v0・v1)(v1・v0) = 0
上式中如果 v0 或 v1 等於 0,則三角形便會退化成線段或者點的情況,這變成了點位於線段上的問題。
當v0、v1都不為0時,由內積公式,上式轉變為:
(v0・v1)(v0・v1) / ((v0・v0)(v1・v1))
= |v0|^2 * |v1|^2 * (cos)^2 / (|v0|^2 * |v1|^2)
= (cos)^2
= 1
進而可以知道:
= 0 或者 = pi
v0和v1的夾角無論是哪一種情況,都表示v0和v1是共線的。剩下的又變成了頂點是否位於線段上的問題。
當 (v0・v0)(v1・v1) - (v0・v1)(v1・v0) != 0 時,便可求解u、v,進而確定p相對於三角形的位置。
演算法的描述如下:
function pointin********(p, a,b,c)
v0 = a - c; v1 = b -c; v2 = p - c;
dot00 = dot(v0, v0); dot01 = dot(v0, v1); dot11 = dot(v1, v1);
invdemon = dot00 * dot11 - dot01 * dot01;
if (invdemon < epsilon && invdemon > -epsilon)
return pointinlinesegment(p, a, b) || pointinlinesegment(p, a, c) || pointinlinesegment(p, b, c)
invdemon = 1 / invdemon;
dot02 = dot(v0, v2); dot12 = dot(v1, v2);
u = (dot11*dot02 - dot01*dot12) * invdemon;
v = (dot00*dot12 - dot01*dot02) * invdemon;
return 0<= u、v <= 1 && u + v <= 1
頂點是否位於線段上的判定演算法pointinlinesegment,參看前篇博文。
重心法相較方法2中的同側比較運算要少,所以更高效些。詳細討論見[1].
參考:[1]
判斷點是否位於三角形內部?
判斷點是否位於三角形內部?思路一基於基向量。將三角形的兩個邊看作從同乙個頂點發出的兩個向量。其長度就是變成。要知道三角形內部的每乙個點都可以由這兩個向量的配上權重相加表示。x 1v1 2v2 x lambda 1 v 1 lambda 2 v 2 x 1 v 1 2 v2 而且可以知道 1,2 0,...
判斷點是否在三角形內
概述 給定三角形abc和一點p x,y,z 判斷點p是否在abc內。這是遊戲設計中乙個常見的問題。需要注意的是,這裡假定點和三角形位於同乙個平面內。本文介紹三種不同的方法,由淺入深 一 內角和法 連線點p和三角形的三個頂點得到三條線段pa,pb和pc,求出這三條線段與三角形各邊的夾角,如果所有夾角之...
判斷點是否在三角形內
給定三角形abc和一點p x,y,z 判斷點p是否在abc內。這是遊戲設計中乙個常見的問題。需要注意的是,這裡假定點和三角形位於同乙個平面內。本文介紹三種不同的方法,由淺入深 連線點p和三角形的三個頂點得到三條線段pa,pb和pc,求出這三條線段與三角形各邊的夾角,如果所有夾角之和為180度,那麼點...