您當前的位置:首頁 > 農業

線性物件與三角形的相交測試

作者:由 chopper 發表于 農業時間:2020-04-25

本篇文章介紹一種三維空間下線性物件與三角形的相交測試演算法和實現,線性物件包括:直接、射線和線段,它們可以用類似的形式來表示,先以直接為例來闡述演算法,射線和線段是直接的特殊情況。

文章目錄:

演算法介紹

原始碼實現

參考

演算法介紹

在三維空間上,判斷線性物件

P+t\vec{d}

與三角形

\Delta {{V}_{0}}{{V}_{1}}{{V}_{2}}

是否相交,若相交,求出交點,三角形所在的平面是

\pi

線性物件與三角形的相交測試

圖1。 直線與三角形的四種關係

三維空間的模型通常是由一個個三角形面片構成的,線性物件與三角形的相交檢測也顯得特別的重要。如圖1所示,一條直線與三角形可能有四種關係:1)直線

{{L}_{0}}

與三角形相交;2)直線

{{L}_{1}}

與三角形所在的平面相交,但與三角形不相交;3)直線

{{L}_{2}}

與在三角形所在的平面上;4)直線

{{L}_{3}}

與三角形所在的平面平行。 一種樸素的檢測演算法是計算線性物件與三角形所在的平面的交點,再判斷交點是否在三角形內。如果交點在三角形內,則線性物件與三角形相交;否則,不相交。這種演算法效率不僅效率較低,而且更容易造成浮點數誤差。因為計算機中的浮點數佔用位元組有限,能表示的精度有限,有效數較少,求直線與平面的交點,由於對同一個變數多次的乘法或者除法操作,造成誤差的疊加,最後計算出來的結果由於精度問題,可能造成較大的誤差。例如,三角形頂點的座標數值較大,計算的直線與平面的交點,會由於精度誤差導致求出的交點偏離平面,即可以檢測到交點不在平面上。

Möller&Trumbore(1997)[1]提出了一種更加高效的射線與三角形相交測試的演算法,而且乘法或者除法操作造成的誤差疊加較少,精度損失更小,因此效能上更加健壯。

三角形內的任意一點都可以用它相對於三角形的頂點的位置來定義:

T(u,v)=(1-u-v){{V}_{0}}+u{{V}_{1}}+v{{V}_{2}}\tag{1}

其中,

(u,v)\in D=\{(u,v):u\ge 0,v\ge 0,u+v\le 1\}

,稱為重心座標(barycentric coordinate)。直線可以用引數方程表示為

P+t\vec{d}

,因此計算直線與三角的交點的等式為

P+t\vec{d}=\left( 1-u-v \right){{V}_{0}}+u{{V}_{1}}+v{{V}_{2}} \tag{2}\\

整理,得

\left[ \begin{matrix}    -\vec{d} & {{V}_{1}}-{{V}_{0}} & {{V}_{2}}-{{V}_{0}}  \\ \end{matrix} \right]\left[ \begin{matrix}    t  \\    u  \\    v  \\ \end{matrix} \right]=\left[ P-{{V}_{0}} \right] \tag{3}\\

這相當於是一個齊次線性方程組,

(t,u,v)

是它的解,可以採用克拉姆法則來求解,得到

\left[ \begin{matrix}    t  \\    u  \\    v  \\ \end{matrix} \right]=\frac{1}{\left| \begin{matrix}    -\vec{d} & {{E}_{1}} & {{E}_{2}}  \\ \end{matrix} \right|}\left[ \begin{matrix}    |T & {{E}_{1}} & {{E}_{2}}|  \\    |-\vec{d} & T & {{E}_{2}}|  \\    |-\vec{d} & {{E}_{1}} & T|  \\ \end{matrix} \right] \tag{4}\\

其中,

T=P-{{V}_{0}},{{E}_{1}}={{V}_{1}}-{{V}_{0}},{{E}_{2}}={{V}_{2}}-{{V}_{0}}

,依據線性代數的知識,易知

\left| \begin{matrix}    A & B & C  \\ \end{matrix} \right|=(A\times B)\cdot C

=-(A\times C)\cdot B=-(C\times B)\cdot A

,因此等式(4)可以寫成:

\left[ \begin{matrix}    t  \\    u  \\    v  \\ \end{matrix} \right]=\frac{1}{\left( \vec{d}\times {{E}_{2}} \right)\cdot {{E}_{1}}}\left[ \begin{matrix}    \left( T\times {{E}_{1}} \right)\cdot {{E}_{2}}  \\    \left( \vec{d}\times {{E}_{2}} \right)\cdot T  \\    \left( T\times {{E}_{1}} \right)\cdot \vec{d}  \\ \end{matrix} \right] \tag{5}\\

如果得到的解

(u,v)\in D

,那麼直線與平面的交點位於三角形內;否則,位於三角形外。注意,若

u=0

v=0

u+v=1

,則交點在三角形的邊上。

直線與三角形相交測試的演算法偽碼如下所示:

在三維空間上,判斷直線

L(t)=P+t\vec{d},t\in \left( -\infty ,+\infty  \right)

與三角形

\Delta {{V}_{0}}{{V}_{1}}{{V}_{2}}

是否相交,若相交,求出交點,三角形所在的平面是

\pi

線性物件與三角形的相交測試

如果需要求射線與三角形的交,必需限定

t

的取值範圍,

t

必需滿足條件

t\ge 0

,若計算出來的

t\prec 0

,則射線與三角形不相交。同樣,如果需要求線段與三角形的交,線段的引數方程仍然可以表示為

P+t\vec{d}

,線段的兩個端點分別是

P

P+\vec{d}

,則

t

必需滿足條件

0\le t\le 1

,因此,若計算出來的

t\prec 0

t\succ 1

,則線段與三角形不相交。

注意到,在上述的演算法偽碼中,已經把一些中間變數儲存起來,避免重複計算,可以提高演算法的速度,例如中間變數

T=P-{{V}_{0}},{{E}_{1}}={{V}_{1}}-{{V}_{0}},{{E}_{2}}={{V}_{2}}-{{V}_{0}},\vec{d}\times {{E}_{2}},T\times {{E}_{1}}

原始碼實現

基礎庫原始碼連結,參見

這裡

,下面是前面所描述的演算法的實現。

#include

“SrGeometricTools。h”

#include

“SrDataType。h”

namespace

{

class

SrSegment3D

{

public

SrPoint3D

mPoint1

SrPoint3D

mPoint2

};

class

SrRay3D

{

public

SrPoint3D

mBase

SrPoint3D

mDirection

};

class

SrLine3D

{

public

SrPoint3D

mBase

SrPoint3D

mDirection

};

/**

\brief 3D triangle class。

This is a 3D triangle class with public data members。

*/

class

SrTriangle3D

{

public

/**

\brief Default constructor, endpoints is set to (0,0)。

*/

SrTriangle3D

()

{

mPoint

0

=

mPoint

1

=

mPoint

1

=

SrVector3D

0

0

0

);

}

/**

\brief The line is initialized by three points。

*/

SrTriangle3D

const

SrPoint3D

&

p0

const

SrPoint3D

&

p1

const

SrPoint3D

&

p2

{

mPoint

0

=

p0

mPoint

1

=

p1

mPoint

2

=

p2

}

/**

\brief Destructor。 Do nothing。

*/

~

SrTriangle3D

()

{}

/**

\brief The triangle is valid if the three points are not on a line。

*/

bool

isValid

()

const

{

SrVector3D

norm

=

normal

();

if

EQUAL

norm

x

0

&&

EQUAL

norm

y

0

&&

EQUAL

norm

z

0

))

return

false

return

true

}

/**

\brief Compute the normal using Newell method devised by Martin Newell。It relates to the order of the points。

*/

const

SrVector3D

normal

()

const

{

SrVector3D

norm

=

mPoint

1

-

mPoint

0

])。

cross

mPoint

2

-

mPoint

0

]);

return

norm

}

/**

\brief Judge whether or not the line hits the triangle。

\return SR_OVERLAPPING if the line is on the plane where the triangle lies;

SR_PARALLEL if the line is parallel to the triangle。

SR_DISJOINT if the line misses the triangle;

SR_INTERSECTING if the line intersects with the triangle and intersection point is not on the edge。

\param[out] result If the line intersects with the triangle and the intersection point information is put in ‘result’。

*/

int

lineHitTest

const

SrLine3D

&

line

SrPoint3D

&

/*[OUT]*/

result

const

{

SrVector3D

ret

int

retFlag

=

linearIntersectTriangle

line

mBase

line

mDirection

ret

);

if

retFlag

!=

SR_INTERSECTING

return

retFlag

result

=

line

mBase

+

ret

z

*

line

mDirection

//check whether the intersection point is on the edge。

//if( EQUAL(ret。x+ret。y-1。0,0) ||

// EQUAL(ret。x,0) ||

// EQUAL(ret。y,0) )

// return SR_INTERSECTING_EDGE;

return

SR_INTERSECTING

}

/**

\brief Judge whether or not the ray hits the triangle。

\return SR_OVERLAPPING if the ray is on the plane where the triangle lies;

SR_PARALLEL if the ray is parallel to the triangle。

SR_DISJOINT if the ray misses the triangle;

SR_INTERSECTING if the ray intersects with the triangle and intersection point is not on the edge。

\param[out] result If the ray intersects with the triangle and the intersection point information is put in ‘result’。

*/

int

rayHitTest

const

SrRay3D

&

ray

SrPoint3D

&

/*[OUT]*/

result

const

{

SrVector3D

ret

int

retFlag

=

linearIntersectTriangle

ray

mBase

ray

mDirection

ret

);

if

retFlag

!=

SR_INTERSECTING

return

retFlag

//check t。 t>=0

if

LESS

ret

z

0

))

return

SR_DISJOINT

result

=

ray

mBase

+

ret

z

*

ray

mDirection

//check whether the intersection point is on the edge。

//if( EQUAL(ret。x+ret。y-1。0,0) ||

// EQUAL(ret。x,0) ||

// EQUAL(ret。y,0) )

// return SR_INTERSECTING_EDGE;

return

SR_INTERSECTING

}

/**

\brief Judge whether or not the segment hits the triangle。

\return SR_OVERLAPPING if the segment is on the plane where the triangle lies;

SR_PARALLEL if the segment is parallel to the triangle。

SR_DISJOINT if the segment misses the triangle;

SR_INTERSECTING if the segment intersects with the triangle and intersection point is not on the edge。

\param[out] result If the segment intersects with the triangle and the intersection point information is put in ‘result’。

*/

int

segmentHitTest

const

SrSegment3D

&

segment

SrPoint3D

&

/*[OUT]*/

result

const

{

SrVector3D

ret

SrVector3D

direction

=

segment

mPoint2

-

segment

mPoint1

int

retFlag

=

linearIntersectTriangle

segment

mPoint1

direction

ret

);

if

retFlag

!=

SR_INTERSECTING

return

retFlag

//check t。 0<= t <=1

if

LESS

ret

z

0

||

GREATER

ret

z

1

))

return

SR_DISJOINT

result

=

segment

mPoint1

+

ret

z

*

direction

//check whether the intersection point is on the edge。

//if( EQUAL(ret。x+ret。y-1。0,0) ||

// EQUAL(ret。x,0) ||

// EQUAL(ret。y,0) )

// return SR_INTERSECTING_EDGE;

return

SR_INTERSECTING

}

public

SrPoint3D

mPoint

3

];

private

/**

\brief Judge whether or not the line hits the triangle。The line is base + t*direction。

It is not necessary for direction to be unit length。

\return SR_OVERLAPPING if the line is on the plane where the triangle lies;

SR_PARALLEL if the line is parallel to the triangle。

SR_DISJOINT if the line misses the triangle;

SR_INTERSECTING if the line intersects with the triangle。

\param[out] result return the result of (u,v,t) if the line intersects with the triangle。

*/

/*

——————————————————————————————————————

Devised by Moller and Trumbore,1997。

intersection>

Any point in a triangle can be defined in terms of its position relative

to the triangle’s vertices:

Qu,v = (1-u-v)V0 + uV1 + vV2 ,0<=u<=1,0<=v<=1,0<=u+v<=1

For the linear component–triangle intersection,

P + t*^d = Qu,v

which can be expanded and applied Cramer’s rule to:

|t| | |P-V0 V1-V0 V2-V0| |

|u| = 1/|-^d V1-V0 V2-V0| | |-^d P-V0 V2-V0| |

|v| | |-^d V1-V0 P-V0| |

| ((P-V0)×(V1-V0))·(V2-V0) |

= 1/(^d×(V2-v0))·(V1-V0) | (^d×(V2-V0))·(P-V0) |

| ((P-V0)×(V1-V0))·^d |

——————————————————————————————————————

*/

int

linearIntersectTriangle

const

SrPoint3D

&

base

const

SrVector3D

&

direction

SrVector3D

&

result

const

{

SrReal

u

v

tmp

SrVector3D

e1

e2

p

s

q

e1

=

mPoint

1

-

mPoint

0

];

e2

=

mPoint

2

-

mPoint

0

];

p

=

direction

cross

e2

);

tmp

=

p

dot

e1

);

//If the line is perpendicular to the normal of triangle。

if

EQUAL

tmp

0

))

{

//The line is on the plane。

p

=

e1

cross

e2

);

if

EQUAL

p

dot

base

-

mPoint

0

]),

0

))

return

SR_OVERLAPPING

//The line is parallel to the plane。

return

SR_PARALLEL

}

s

=

base

-

mPoint

0

];

u

=

p

dot

s

/

tmp

if

LESS

u

0

||

GREATER

u

1

))

return

SR_DISJOINT

q

=

s

cross

e1

);

v

=

q

dot

direction

/

tmp

if

LESS

v

0

||

GREATER

v

1

||

GREATER

u

+

v

1

))

return

SR_DISJOINT

result

x

=

u

result

y

=

v

result

z

=

e2

dot

q

/

tmp

return

SR_INTERSECTING

}

};

}

參考

[1] Tomas Möller, and Ben Trumbore。 “Fast, minimum storage ray-triangle intersection。” Journal of graphics tools, vol。2, no。1, pp。21-28, 1997。

[2] Philip J。 Schneider, and David H。 Eberly。 Geometric Tools for Computer Graphics。 Morgan Kaufmann, 2002。

標簽: triangle  line  SR  RET  三角形