1. 數學分析
1) 參數化直線
還記得我們在學習向量時介紹的位移向量嗎?參數化直線的原理和他相同。其實所謂參數化直線,就是通過一個參數t來表示直線上的一個線段,當t為0時,則取線段的一端的端點,當t取1時,則取到另一端的端點。
核心原理就是向量加法的幾何意義,Vd是加數,通過t(從0到1)去乘以這個Vd,則可以描述向量p,而p在某個t時的座標值,就是從點P1到點P2的線段在t時的座標值。注意這裡我們使用的是圖中的p = p1 + t * vd,這樣t的取值是0到1,如果是p = p1 + t * v',那t的取值就是0到|Vd|,還需要去先算|Vd|,沒有必要這麼做,而且0和1是特殊的數字,運算方便。
所以參數化方程為:P(x,y,z) = p0 + v*t
我們為什麼要用參數化的形式表示線段呢,因為在求兩個線段的交點時,只要求兩個參數方程組成的方程組,並求出t1和t2,只要他們都在0到1之間,那麼就可以知道他們是有交點的。比如:
線段1:
x = 1 + 2*t1;
y = 3 + 4*t1;
線段2:
x = 5 + 6*t2;
y = 7 + 8*t2;
四個方程,四個未知數,可求得t1,t2,如果他們都在0和1之間,那他們相交。
具體怎麼求?忘記上一章所講的求逆矩陣的嗎?要先算出行列式,然後。。。
注意,這裡我們有很多的情況還沒有考慮,比如:
1. 兩條直線平行,沒有交點
2. 兩條線段在同一直線上,但互不重疊
3. 兩條線段在同一直線上,部分重疊
4. 兩條線段在同一直線上,只有一點重疊
在我的實現函數中,我把這4種特殊情況都看做是不相交或者說是無意義,當他們平行或共線時都會返回回傳碼0。因為這時計算出來的t1和t2以及交點都是沒有任何意義的。
2) 3D平面
我們如何定義一個3D平面呢,看:
如果你認真看圖了,那麼無需我多說了,只要有一個點P0,和平面的法線向量n,就可以確定一個平面了。
定義:對於空間中的任意一點P,如果線段P->P0與法線向量n垂直,則點P在平面上。
還記得向量點積嗎?其最最重要的性質就是其幾何意義:可以確定兩個向量之間的夾角。當兩個向量的點積為0時,這兩個向量互相垂直。
所以:n.(P->P0) = 0
展開:<a, b, c> . <x-x0, y-y0, z-z0> = 0
最終,可以拿到平面的點-法線表示的形式:a*(x-x0) + b*(y-y0) + c(z-z0) = 0
我們可以另d = (-a*x0 - b*y0 - c*z0)
那麼上面的形式可以化為:
a*x + b*y + c*z + d = 0
這個就是3D平面的通用形式,以通用形式計算兩個平面之間的交線非常方便。
再擴充一下思維,就可以知道,對於任意一點<x,y,z>,a*(x-x0) + b*(y-y0) + c(z-z0)的值與0的關係,可以判斷出,這個點在平面的哪一邊。而這個性質則非常非常的重要,我打算先舉個執行個體再給出答案。
還是參照,這是個特殊的平面,是X-Z平面,所以法線向量n為<0,1,0>,平面上的一點,我們選原點<0, 0, 0>,那麼現在我們來取任意一點,比如(5, 5, 5),那麼把這些資料代入a*(x-x0) + b*(y-y0) + c(z-z0),得:
1*(5-0) = 5 > 0,所以點(5, 5, 5)在平面的正方向。
再取點(5, -5, 5),則1*(-5-0) = -5 < 0 ,所以點(5, -5, 5)在該平面的負方向。
因為這個概念經常要用到,所以在舉了一點的例子之後我想再通過理論證明一下,請看:
這幅圖是的平面版,其實任意一個3D點與平面的關係都可以找到這樣一個角度來看。這裡的X軸就是X-Z平面的水平視角中的線,Y軸的正半軸就是這個平面的法線向量,這個圖左上提示了大家,u.v = |u| * |v| * Cos(theta)。左下角則根據向量範數的求法,給出了u.v是否大於0,取決於Cos(theta)的結果。圖上4種顏色的點,標出了任意一點可能在平面附近的位置。還記得餘弦函數線嗎?不多說了。到此我們已經證明了在任意3D卦限的某點,與某3D平面,計算u.v > 0 則在平面正方向,u.v < 0 則在負方向。
3) 參數化直線與3D平面的交點
其實就是解方程組,把直線方程和3D平面方程放在一起進行簡化。
參數化直線:
x = x0 + vx * t
y = y0 + vy * t
z = z0 + vz * t
其中x0, y0, z0為參數化直線上一直的一點
3D平面:
a*x + b*y + c*z + d = 0
a = nx
b = ny
c = nz
d = -nx*x0' -ny*y0' - nz*z0'
其中x0', y0', z0'是3D平面上已知的一點,所以加了'來區分直線上已知的一點
將這幾個方程代入做整理變換可以得到t的表示形式:
t = -(a*x0 + b*y0 + c*z0 + d) / (a*vx + b*vy + c*vz)
通過這個t就可以知道線段與平面是否存在交點了,不用我再說了吧,看t是不是在0和1之間即可。
這幾個方程也可以表示出x, y, z,即交點的座標:
x = x0 + vx*t
y = y0 + vy*t
z = z0 + vz*t
哈哈,其實不就是把t代回原線段的形式了麼。。。
2. 代碼實現
1) 結構定義
首先我們定義表示參數化直線和3D平面的結構:
typedef struct PARAMLINE2D_TYPE // 參數化2D直線<br />{<br />POINT2D p0;<br />POINT2D p1;<br />VECTOR2D v;<br />} PARAMLINE2D, *PARAMLINE2D_PTR;</p><p>typedef struct PARAMLINE3D_TYPE // 參數化3D直線<br />{<br />POINT3D p0;<br />POINT3D p1;<br />VECTOR3D v;<br />} PARAMLINE3D, *PARAMLINE3D_PTR;</p><p>typedef struct PLANE3D_TYPE // 3D平面<br />{<br />POINT3D p0;<br />VECTOR3D n;<br />} PLANE3D, *PLANE3D_PTR;
2) 常用函數實現
void _CPPYIN_Math::ParamLineCreate(POINT2D_PTR p0, POINT2D_PTR p1, PARAMLINE2D_PTR p)<br />{<br />p->p0.x = p0->x;<br />p->p0.y = p0->y;<br />p->p1.x = p1->x;<br />p->p1.y = p1->y;<br />p->v.x = p1->x - p0->x;<br />p->v.y = p1->y - p0->y;<br />}</p><p>void _CPPYIN_Math::ParamLineCreate(POINT3D_PTR p0, POINT3D_PTR p1, PARAMLINE3D_PTR p)<br />{<br />p->p0.x = p0->x;<br />p->p0.y = p0->y;<br />p->p0.z = p0->z;<br />p->p1.x = p1->x;<br />p->p1.y = p1->y;<br />p->p1.z = p1->z;<br />p->v.x = p1->x - p0->x;<br />p->v.y = p1->y - p0->y;<br />p->v.z = p1->z - p0->z;<br />}</p><p>void _CPPYIN_Math::ParamLineGetPoint(PARAMLINE2D_PTR p, double t, POINT2D_PTR pt)<br />{<br />pt->x = p->p0.x + p->v.x * t;<br />pt->y = p->p0.y + p->v.y * t;<br />}</p><p>void _CPPYIN_Math::ParamLineGetPoint(PARAMLINE3D_PTR p, double t, POINT3D_PTR pt)<br />{<br />pt->x = p->p0.x + p->v.x * t;<br />pt->y = p->p0.y + p->v.y * t;<br />pt->z = p->p0.z + p->v.z * t;<br />}</p><p>int _CPPYIN_Math::ParamLineIntersect(PARAMLINE2D_PTR p1, PARAMLINE2D_PTR p2, double *t1, double *t2) // 0 平行或共線 1 線段相交 2 線段不相交<br />{<br />double det_p1p2 = p1->v.x * p2->v.y - p1->v.y * p2->v.x;<br />if (abs(det_p1p2) <= EPSILON)<br />return 0;</p><p>*t1 = (p2->v.x*(p1->p0.y - p2->p0.y) - p2->v.y*(p1->p0.x - p2->p0.x)) /det_p1p2;<br />*t2 = (p1->v.x*(p1->p0.y - p2->p0.y) - p1->v.y*(p1->p0.x - p2->p0.x)) /det_p1p2;</p><p>if ((*t1>=0) && (*t1<=1) && (*t2>=0) && (*t2<=1))<br />return 1;<br />else<br />return 2;<br />}</p><p>int _CPPYIN_Math::ParamLineIntersect(PARAMLINE2D_PTR p1, PARAMLINE2D_PTR p2, POINT2D_PTR pt) // 0 平行或共線 1 線段相交 2 線段不相交<br />{<br />double t1, t2, det_p1p2 = (p1->v.x*p2->v.y - p1->v.y*p2->v.x);</p><p>if (abs(det_p1p2) <= EPSILON)<br />return 0;</p><p>t1 = (p2->v.x*(p1->p0.y - p2->p0.y) - p2->v.y*(p1->p0.x - p2->p0.x))/det_p1p2;<br />t2 = (p1->v.x*(p1->p0.y - p2->p0.y) - p1->v.y*(p1->p0.x - p2->p0.x))/det_p1p2;</p><p>pt->x = p1->p0.x + p1->v.x*t1;<br />pt->y = p1->p0.y + p1->v.y*t1;</p><p>if ((t1>=0) && (t1<=1) && (t2>=0) && (t2<=1))<br />return 1;<br />else<br />return 2;<br />}</p><p>void _CPPYIN_Math::PlaneCreate(PLANE3D_PTR plane, POINT3D_PTR p0, VECTOR3D_PTR normal, int normalize)<br />{<br />plane->p0.x = p0->x;<br />plane->p0.y = p0->y;<br />plane->p0.z = p0->z;</p><p>if (normalize)<br />{<br />VECTOR3D n;<br />VectorNormalize(normal, &n);<br />plane->n.x = n.x;<br />plane->n.y = n.y;<br />plane->n.z = n.z;<br />}<br />else<br />{<br />plane->n.x = normal->x;<br />plane->n.y = normal->y;<br />plane->n.z = normal->z;<br />}<br />}</p><p>double _CPPYIN_Math::PlaneWithPoint(POINT3D_PTR pt, PLANE3D_PTR plane)<br />{<br />double hs = plane->n.x*(pt->x - plane->p0.x) +<br />plane->n.y*(pt->y - plane->p0.y) +<br />plane->n.z*(pt->z - plane->p0.z);</p><p>return hs;<br />}</p><p>int _CPPYIN_Math::PlaneParamLineInterset(PARAMLINE3D_PTR pline, PLANE3D_PTR plane, double *t, POINT3D_PTR pt) // 0 不相交 1 相交 2 相交在延長線上 3 線段在平面上<br />{<br />// 求直線與平面法線向量的點積<br />double plane_dot_line = VectorDot(&pline->v, &plane->n);</p><p>if (abs(plane_dot_line) <= EPSILON) // 點積為0, 直線與面法線垂直,要麼平行於平面,要不就在平面上,下面拿直線上一點測試<br />{<br />if (abs(PlaneWithPoint(&pline->p0, plane)) <= EPSILON)<br />return 3;<br />else<br />return 0;<br />}</p><p>// 求t<br />*t = -(plane->n.x*pline->p0.x +<br /> plane->n.y*pline->p0.y +<br /> plane->n.z*pline->p0.z -<br /> plane->n.x*plane->p0.x -<br /> plane->n.y*plane->p0.y -<br /> plane->n.z*plane->p0.z) / (plane_dot_line);</p><p>pt->x = pline->p0.x + pline->v.x*(*t);<br />pt->y = pline->p0.y + pline->v.y*(*t);<br />pt->z = pline->p0.z + pline->v.z*(*t);</p><p>if (*t>=0.0 && *t<=1.0)<br /> return 1;<br />else<br /> return 2;<br />}
3. 代碼下載
完整項目代碼下載:>>點擊進入下載頁<<