====== 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 ????; }