最近在做一個3D模型布爾運算相關的工程。因為模型是靠三角形面片拼合而成的,所以需要一種算法解決三維空間內三角形和線段的相交判斷問題。有幸能在外網搜到了這個文章,其中詳細介紹了如何利用普呂克坐標來實現對三角形和線段的相交判定,甚至還包括了直線對三角形、線段對線段、直線對直線的判定,算法實現簡單,特此翻譯並記錄,用以備忘。
知識儲備
三角形相交種類
首先來思考一下三角形和線段相交有多少種情況:
從三角形角度 | 從線段角度 | ||
---|---|---|---|
非共面情況 | 共面情況 | ||
不相交 | |||
線段交於三角形一個點 | 三角形經過線段一個端點 | ||
交於三角形一邊 | 三角形穿過線段內部 | ||
線段穿過三角形內 | 線段穿過三角形兩邊 | ||
線段和三角形一邊重合 | |||
線段穿過三角形一個邊一個頂點 |
普呂克坐標(Plücker Coordinates)
網絡上可以百度的內容中的普呂克坐標,大多都是指用d和m兩個參數來表示三維歐幾里得空間中的有向直線。
其中d是直線的方向向量,決定了直線的方向;而m是直線上兩個點與坐標原點形成的兩個向量的叉乘,也就是在數值上等於以這兩個向量為鄰邊畫出的平行四邊形的面積。而由於可以根據d來計算出這直線上兩個點的距離,那么,
m=平行四邊形的面積=直線上兩點距離*直線到坐標原點距離。也就是說,d和m一個確定了直線的方向,一個確定了直線到原點的距離;並且按照叉乘結果是平面法向量的定義,m確定了直線和坐標原點所在的平面。綜合以上,可以唯一確定三維空間里的一條直線。(以上是我自己的個人理解,不知道對不對)
六元組表示法
而在文章中使用到的不是d和m,而是普呂克坐標的另一種表示方法:L = (L[0], L[1], L[2], L[3], L[4], L[5])。
首先假設有兩個直線上面的點 p = (px, py, pz) and q = (qx, qy, qz)。根據如此構造一個2*4矩陣:
而六元組的每個參數都等於這個矩陣的2*2子矩陣行列式。也就是說:
相交判別算法
假設a[6]和b[6]分別是普呂克坐標表示的兩個直線。判斷兩個直線是否相交的算法如下:
當且僅當side(a,b)==0時,兩條直線平行或相交,也可以說是共面或異面相交。
(不知道這式子怎么來的,結果大於0小於0也沒說意義是什么,只說等於0在后面用於判別時好像用處不大……以后有機會去查查)
原文中還提到了利用這個式子來驗證直線到普呂克坐標的映射關系:對於一個六元組X,如果:
- side(X,X) = 0 and X[2], X[4] and X[5] 都不是0,那么X可以對應一個三維坐標系中的一條直線
- side(X,X) = 0 and X[2] = X[4] = X[5] = 0,那么它無法對應三維坐標系中的一條直線,或者說對應的是無限遠處的一條直線
也就是說直線和普呂克坐標點是單射、一一映射的關系。
算法剖析
非共面情況
首先,文章讓我們如圖構建直線L2、L3、L4:
L2和L3是穿過線段端點和三角形公共頂點的直線。我們可以如下選擇公共頂點:
- 如果線段和三角形是互相穿過或者線和三角形一邊相交,那么這個公共頂點可以隨便選。
- 如果線段交於三角形其中一點,那么這個公共頂點是其余兩個頂點的其中一個。
L2、L3分別是公共頂點和線段兩個端點形成的直線,L4是公共頂點對面的邊所在的直線。進行如下side運算:
若s1和s2異號,那么返回判斷結果不相交;如果同號,則線段和三角形相交。如果其中一個為0,那么線段其中一個端點在三角形上。
共面情況
如果線段和三角形共面,那么線段所在直線也一定和三角形相交。故問題改變為判定線段和線段相交。
其中有個特殊情況,如果線段和三角形三條邊都不相交,那么線段要么完全在三角形外,要么包含在三角形內。
文章中按如圖生成一個平面外的點,並因此構造直線L1、L2。問題變成判定直線和三角形不共面時相交
故共面情況時我們要解決兩個分問題。
線段和線段相交判斷
設l1、l2所在直線為s1、s2。如果能知道直線和線段的相交判斷,那么分別對l1和s2、l2和s1進行一次該判斷,只有兩次判斷都為相交時,l1和l2相交。
線段和直線相交判斷
如圖構造一個平面外的點,構造直線L1、L2,進行如下side計算:
得到結果對應如下情況:
- s1、s2異號,線段和直線不相交
- s1、s2同號,直線穿過線段
- s1等於s2等於0,線段被包含在直線中
- s1,s2其中一個是0,直線穿過線段一個端點。
判定直線和三角形不共面時相交
令三角形三條邊所在直線為e1,e2,e3.進行如下side判斷:
根據我們之前構造的兩個直線L1,L2,他們應該都要交於三角形內部,才能最終判定線段包含在三角形內。如圖:
它的判斷條件是S1、S2、S3同號。
程序設計和實現
函數清單
- 線段和三角形共面判斷函數judgeCoplanar()
- 三點共面判斷函數judheLine()
- 普呂克生成函數generatePC()
- 普呂克計算函數side()
- 直線和線段判斷函數instLineSeg()
- 線段和線段判斷函數instSegSeg()
- 主程序接口lineTriIsct()
源碼
(暫缺,代碼在公司內網電腦里,嘻嘻,加注釋一共兩百行)
后記
一開始接觸到這個普呂克坐標法的時候感覺很稀奇,想着它居然能識別這么多相交情況真是太棒了!結果所有程序都實現回頭整理時才發現,包括不相交這個程序只能識別5種:
- 不相交
- 線段穿過三角形內
- 交於線段一個端點
- 線段被包含於三角形內
- 重合三角形一條邊
- 其他種類相交
檢查算法原理時發現,在判斷**線段和線段相交**情況時,子問題**線段和直線的相交**的判斷結果被大量丟失,返回的判斷結果只剩下"相交"和"不相交"了,這可能是原文存在的一些待改進之處,這讓該方法有種大炮打蚊子,而且還打的不准的感覺。如果有機會能更多理解普呂克坐標的原理的話,說不定能讓算法變得更完美吧!