====== Ray Cast と三角形交差判定(重心座標と連立方程式) ======
==== 1. 問題設定 ====
Ray Cast では、次の問いを解きます。
''Ray(半直線)が、三角形の内部に当たっているか?''
これは次の2つの点が一致するかどうか、という問題です。
* Ray 上の点
* 三角形上の点
----
==== 2. Ray の数式表現 ====
Ray は、次の一次式で表されます。
\[
P(t) = \mathbf{origin} + t\,\mathbf{ray}
\]
ここでの意味は次のとおりです。
* $\mathbf{origin}$ :Ray の開始点(位置ベクトル)
* $\mathbf{ray}$ :Ray の向き(方向ベクトル)
* $t$ :Ray 上をどれだけ進んだかを表す実数
Ray は半直線なので、次の条件が付きます。
\[
t \ge 0
\]
----
==== 3. 三角形の数式表現(重心座標) ====
三角形の3頂点を次のように置きます。
* $v_0$ :基準となる頂点
* $v_1$ :2つ目の頂点
* $v_2$ :3つ目の頂点
まず、$v_0$ から他の頂点へ向かう辺ベクトルを定義します。
\[
\begin{cases}
\mathbf{edge1} = v_1 - v_0 \\
\mathbf{edge2} = v_2 - v_0
\end{cases}
\]
この2本のベクトルで、三角形の平面が張られます。
----
==== 4. 三角形上の点の表し方 ====
三角形上の任意の点 $Q$ は、次の形で表せます。
\[
Q(u, v) = v_0 + u\,\mathbf{edge1} + v\,\mathbf{edge2}
\]
この式は、
* $v_0$ を出発点として
* $\mathbf{edge1}$ 方向に $u$ 倍
* $\mathbf{edge2}$ 方向に $v$ 倍
進んだ点を意味します。
----
==== 5. なぜ「三角形の内部条件」が必要か ====
$u, v$ を自由に動かすと、点 $Q$ は次の範囲を動きます。
\[
0 \le u \le 1, \quad 0 \le v \le 1
\]
この範囲は、三角形ではなく平行四辺形になります。
そこで、次の条件を追加します。
\[
\begin{aligned}
u &\ge 0 \\
v &\ge 0 \\
u + v &\le 1
\end{aligned}
\]
この条件を満たす点だけが、三角形の内部になります。
----
==== 6. Ray と三角形が交わる条件 ====
Ray と三角形が交わるとき、次の式が成り立ちます。
\[
\mathbf{origin} + t\,\mathbf{ray}
= v_0 + u\,\mathbf{edge1} + v\,\mathbf{edge2}
\]
これは「Ray 上の点」と「三角形上の点」が同じ位置である、という意味です。
----
==== 7. 連立方程式への変形 ====
上の式を整理すると、次の形になります。
\[
t\,\mathbf{ray}
= (v_0 - \mathbf{origin})
+ u\,\mathbf{edge1}
+ v\,\mathbf{edge2}
\]
ここで、次を定義します。
\[
\mathbf{d} = \mathbf{origin} - v_0
\]
未知数は $u, v, t$ の3つです。
----
==== 8. 3元一次連立方程式 ====
$x, y, z$ 成分ごとに書き出すと、次の連立方程式になります。
\[
\begin{cases}
u\,\mathbf{edge1}_x + v\,\mathbf{edge2}_x + t(-\mathbf{ray}_x) = d_x \\
u\,\mathbf{edge1}_y + v\,\mathbf{edge2}_y + t(-\mathbf{ray}_y) = d_y \\
u\,\mathbf{edge1}_z + v\,\mathbf{edge2}_z + t(-\mathbf{ray}_z) = d_z
\end{cases}
\]
----
==== 9. 解の意味 ====
この連立方程式を解くことで、次が分かります。
* $u, v$ :交点が三角形のどの位置か(重心座標)
* $t$ :Ray の開始点から交点までの距離
----
==== 10. 当たり判定条件 ====
Ray が三角形に当たっているためには、次がすべて成り立つ必要があります。
\[
\begin{aligned}
t &\ge 0 \\
u &\ge 0 \\
v &\ge 0 \\
u + v &\le 1
\end{aligned}
\]
これらは次を意味します。
* $t \ge 0$ :Ray が前方向に伸びている
* $u, v$ の条件:交点が三角形の外に出ていない
----
==== 11. まとめ ====
Ray Cast の三角形判定は、
* Ray と三角形を数式で表し
* それらを連立方程式として一致させ
* 解が条件を満たすかを調べる
という、純粋に数学的な問題として解くことができます。
===== じゃぁ、解いてみよう。 =====
=== まず行列式を計算する関数を作る ===
float Math::Det(XMFLOAT3 a, XMFLOAT3 b, XMFLOAT3 c) {
// 3x3 行列の行列式
// | a.x a.y a.z |
// | b.x b.y b.z |
// | c.x c.y c.z |
return /* TODO: 1つ目の + 項 */
+ /* TODO: 2つ目の + 項 */
+ /* TODO: 3つ目の + 項 */
- /* TODO: 1つ目の - 項 */
- /* TODO: 2つ目の - 項 */
- /* TODO: 3つ目の - 項 */;
}
==== つぎに、連立方程式を上の手順で解いてゆくぅ ====
bool Math::Intersect( XMFLOAT3 origin, XMFLOAT3 ray, XMFLOAT3 v0, XMFLOAT3 v1, XMFLOAT3 v2) { // ---- ベクトルの準備 ----
// Ray の始点・方向、三角形の頂点を XMVECTOR に変換
// (DirectXMath で計算するため)
XMVECTOR vOrigin = XMLoadFloat3(&origin); // Ray の開始点 O
XMVECTOR vRay = XMLoadFloat3(&ray); // Ray の方向ベクトル
XMVECTOR vV0 = XMLoadFloat3(&v0); // 三角形の頂点 v0
XMVECTOR vV1 = XMLoadFloat3(&v1); // 三角形の頂点 v1
XMVECTOR vV2 = XMLoadFloat3(&v2); // 三角形の頂点 v2
//--------------------------------------
// 三角形の 2 本の辺ベクトルを作る
//--------------------------------------
// edge1 = v1 - v0 (三角形の一辺)
// edge2 = v2 - v0 (三角形のもう一辺)
XMVECTOR vEdge1 = ;
XMVECTOR vEdge2 = ;
// 行列式計算のため、XMFLOAT3 に戻す
XMFLOAT3 edge1;
XMFLOAT3 edge2;
XMStoreFloat3(&edge1, vEdge1);
XMStoreFloat3(&edge2, vEdge2);
//--------------------------------------
// d = origin - v0
//--------------------------------------
// 三角形の基準点 v0 から、Ray の開始点 origin へのベクトル
// 連立方程式
// t*ray = (v0 - origin) + u*edge1 + v*edge2
// を作るための準備
XMFLOAT3 d;
XMStoreFloat3(&d, );
//--------------------------------------
// ray を反転(-ray)
//--------------------------------------
// 連立方程式を
// u*edge1 + v*edge2 + t*(-ray) = d
// の形にそろえるため、ray に -1 を掛ける
ray = {
ray.x * -1.0f,
ray.y * -1.0f,
ray.z * -1.0f
};
//--------------------------------------
// 連立方程式の分母(行列式)
//--------------------------------------
// denom = det(edge1, edge2, -ray)
// → 3 本のベクトルが作る行列の行列式
// → 0 なら、平面と Ray が平行で交点を持たない
float denom = Det();
// 平行(解なし)の判定
if (denom <= 0.0f)
{
// Ray が三角形の平面と交差しない
return false;
}
//--------------------------------------
// クラメルの公式で u, v, t を求める
//--------------------------------------
// u = det(d, edge2, -ray) / denom
// → 交点が edge1 方向にどれだけ進んだか(重心座標 u)
float u = Det() / denom;
// v = det(edge1, d, -ray) / denom
// → 交点が edge2 方向にどれだけ進んだか(重心座標 v)
float v = Det() / denom;
// t = det(edge1, edge2, d) / denom
// → Ray の開始点から交点までの距離
float t = Det() / denom;
//--------------------------------------
// 三角形内部 + Ray の向き 判定
//--------------------------------------
// t >= 0 : Ray の前方向に交点がある
// u >= 0 : v0 → v1 方向に外れていない
// v >= 0 : v0 → v2 方向に外れていない
// u + v <= 1 : 三角形の内部に入っている
if (/*複合条件*/)
{
// Ray が三角形の内部にヒット
return ????;
}
// 条件を満たさない → 当たっていない
return ????;
}