一. 射線與平面求交
設射線的起始點為P0 ,射線方向向量為,則射線的任一一點的方程可表示為
設平面的法線向量為,則平面上任一點的坐標P滿足
意為,坐標原點與平面上任一點的向量在
上的投影長度為常量d.
由以上射線和平面的方程可知,交點處坐標滿足
解得
二.射線相對包圍盒的近面與遠面
AABB盒一共有6個面,可將其中三個面分為射線的近面,另三個面視為遠面.近面和遠面不是按離射線起點或離射線某點的距離來定義的,而是看與盒的6個面的法線向量方向,與射線的方向向量方向相同的視為遠面,與射線方向向量方向相反的視為近面,即:射線方向向量與近面的法線向量的點乘<0,與遠面的法線向量的點乘>0,如果射線與盒的某個面法線向量點乘=0,則射線與盒不會相交。當然,這里不需要會去求射線與各個法線向量的乘積然后再判斷哪個為近面哪個為遠面,而是直接計算射線與AABB盒同一軸上兩個面的交點,然后再利用以下的第3點,來判斷哪個是近面交點哪個是遠面交點。
如果射線與AABB盒相交,則必會滿足如下幾點:
- 如果射線與AABB盒有交點,那么射線就一定會穿過AABB盒
- 射線必定先與三個近面中的一個相交
- 根據直線方程和1,2,其在遠面交點的t值一定比與近面交點的t值大
對以上三點,1,2都不言自明,對於第三點,想像將AABB盒的每個面進行延展,那射線將分別與AABB盒的三個近面和遠面相交,有的相交在盒上,有的相交在盒的延展面上,一共6個交點(如果相交在盒頂點則3個近面(或遠面)交點重合),3個近面交點,3個遠面交點,3個近面交點中,只有一個會相交在盒上,另外兩個相交在盒的延展面上,遠面也是一樣。而且,在三個近面交點中,在盒上相交的點的t值要比在延展面上將相交的t值最大(考慮t的正負號,而不是絕對值),同時,比在遠面上相交的任意點的t值要小。
如果不滿足此,則說明所有交點都在延展面上。
//sp,sq為射線的起始坐標,amin和amax為AABB盒的最小點和最大點坐標,其點坐標拿數組表示,x,y,z軸的分別對應索引0,1,2
//tmin和tmax分別為射線與遠面交點和近面交點的t值
bool isectSegAABB(const float* sp, const float* sq, const float* amin, const float* amax, float& tmin, float& tmax) { static const float EPS = 1e-6f; float d[3]; d[0] = sq[0] - sp[0]; d[1] = sq[1] - sp[1]; d[2] = sq[2] - sp[2]; tmin = 0.0; tmax = 1.0f; //分別與三個軸的兩個分面求交點 for (int i = 0; i < 3; i++) { //如果某個軸的分量在兩個點間沒變,則說明與這個軸垂直,只需要判斷是否在AABB盒內部,否則不可能相交 if (fabsf(d[i]) < EPS) { if (sp[i] < amin[i] || sp[i] > amax[i]) return false; } else{ //通過上面的求t值公式,求出AABB盒的每個軸上的兩個面分別與射線的交點的t值,求出一個大值為遠面交點t值,小值為近面交點t值,此時各個面的法向量n在該軸值可取為1,其它兩個軸上分值為0
const float ood = 1.0f / d[i]; float t1 = (amin[i] - sp[i]) * ood; float t2 = (amax[i] - sp[i]) * ood; if (t1 > t2) { float tmp = t1; t1 = t2; t2 = tmp; } if (t1 > tmin) tmin = t1; if (t2 < tmax) tmax = t2; if (tmin > tmax) return false; } } return true; }