首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

三維空間點在三角形內判斷:向量叉積面積法、拉格朗日恆等式優化與 8 次乘法極致方案

     編輯:關於C語言

問題定義

給定三維空間三角形頂點 A, B, C 與測試點 P,判斷 P 是否在三角形內 (含邊界)。**前提:四點必須共面**,否則「在三角形內」無意義。

數學基礎:面積法 = 向量同向法

三角形面積向量:N = AB × AC (法向量,模長 = 2倍面積)。
若 P 在內,三子三角形面積向量之和等於總面積向量,且**方向相同** (同向):


AB × AC = AB × AP + AP × AC + PB × PC
|AB × AC| = |AB × AP| + |AP × AC| + |PB × PC|  (共面且同向時成立)

方法一:直接向量運算 (通用、易懂)


bool isInTriangle3a(Vec3 a, Vec3 b, Vec3 c, Vec3 p) {
    Vec3 ab = b - a, ac = c - a, ap = p - a;
    // 1. 共面性檢驗:混合積為 0
    if (fabs(ab.cross(ac).dot(ap)) > EPS) return false;

    Vec3 abc = ab.cross(ac);
    Vec3 abp = ab.cross(ap);
    Vec3 apc = ap.cross(ac);

    // 2. 同向性檢驗:點積 ≥ 0
    double t0 = abc.dot(abc);       // 總面積平方 ( > 0 )
    double t1 = abp.dot(abc);       // 子面積投影
    double t2 = apc.dot(abc);

    // 容差判斷:t1>=0, t2>=0, t1+t2<=t0
    double delta = fabs(t1) + fabs(t2) + fabs(t0 - t1 - t2) - t0;
    return fabs(delta) < EPS;
}

乘法次數:30 次 (含共面檢驗)。已知共面時 27 次。

方法二:拉格朗日恆等式消除叉積 (已知共面優勢大)

利用恆等式:(A×B)·(A×C) = (A·A)(B·C) - (A·C)(B·A),將叉積點積展開為純點積運算。


bool isInTriangle3b(Vec3 a, Vec3 b, Vec3 c, Vec3 p) {
    Vec3 ab = b - a, ac = c - a, ap = p - a;
    if (fabs(ab.cross(ac).dot(ap)) > EPS) return false; // 共面檢驗仍需 1 次叉積+點積

    // 預計算點積
    double v11 = ab.dot(ab), v22 = ac.dot(ac), v12 = ab.dot(ac);
    double v13 = ab.dot(ap), v23 = ac.dot(ap);

    // 展開 t0, t1, t2
    double t0 = v11 * v22 - v12 * v12;
    double t1 = v11 * v23 - v12 * v13;
    double t2 = v22 * v13 - v12 * v23;

    double delta = fabs(t1) + fabs(t2) + fabs(t0 - t1 - t2) - t0;
    return fabs(delta) < EPS;
}

乘法次數:30 次 (含共面);**已知共面僅 21 次** (省 6 次叉積乘法)。

方法三:重心坐標 / 邊平面法 — 極致優化 (8 次乘法 / 共面 13 次)

觀察:P 在三角形內 ⇔ P 在三條邊構成的半空間**同側**。
構建邊向量 AB, BC, CA 及法向量 N = AB × AC。
對每條邊 Edge,計算 Edge × AP 與 N 的點積符號 (或直接用標量三重積)。

算法流程 (已知共面)

  1. 計算 AB = B-A, BC = C-B, CA = A-C (3 次減法)。
  2. 計算 N = AB × AC (6 次乘法) — 三角形法向量。
  3. 對三邊分別計算 AP = P-A, BP = P-B, CP = P-C (3 次減法)。
  4. 計算標量三重積 (混合積) 判斷同側:
    u = (AB × AP) · N → 利用拉格朗日展開:(AB·AB)(AP·N) - (AB·N)(AP·AB) ...
    **更簡潔**:直接計算 cross(AB, AP).dot(N) (6 乘 + 3 乘 = 9 乘/邊) → 太多。
    **最優**:預計算 N 及 AB, BC, CA。
    利用恆等式 (U×V)·N = (V×N)·U:
    u = (AP × N) · AB — AP×N 需 6 乘,點積 3 乘 = 9 乘/邊,共 27 乘。
    **極致 8 乘法** (文獻 Fast Triangle-Triangle Intersection 技巧):
    僅計算 N (6 乘)。
    計算 v0 = C-A, v1 = B-A, v2 = P-A。
    計算點積:d00=v0·v0, d01=v0·v1, d11=v1·v1, d20=v2·v0, d21=v2·v1 (5 乘? 不,點積各 3 乘 = 15 乘)。
    重心坐標公式:
    denom = d00*d11 - d01*d01 (2 乘)
    v = (d11*d20 - d01*d21) / denom (2 乘)
    w = (d00*d21 - d01*d20) / denom (2 乘)
    u = 1 - v - w
    判斷 u,v,w >= -EPS。
    **總乘法**:N 6 乘 + 5 個點積 × 3 乘 = 21 乘?
    實際優化版 (Christer Ericson, Real-Time Collision Detection):
    利用 N 只需計算一次,後續投影到三角形平面 2D 判斷。
    **最簡 8 乘法方案 (已知共面)**:
    1. 選擇最大分量軸投影 (丟棄該分量) → 2D 問題。
    2. 2D 重心坐標/邊檢測僅需 8 次乘法 (4 次叉積標量值,每次 2 乘)。
    3. 共面檢驗額外 5 乘 (混合積)。
    **總計:共面 13 乘,已知共面 8 乘。**

投影法代碼骨架 (已知共面,8 乘法)


bool isInTriangle3_fast(Vec3 a, Vec3 b, Vec3 c, Vec3 p) {
    // 1. 計算法向量 N = (B-A) × (C-A)
    Vec3 ab = b - a, ac = c - a;
    Vec3 N = ab.cross(ac); // 6 乘

    // 2. 選擇投影平面 (丟棄 N 絕對值最大的分量)
    int maxAxis = 0; // 0:x, 1:y, 2:z
    if (fabs(N.y) > fabs(N.x)) maxAxis = 1;
    if (fabs(N.z) > fabs(N[maxAxis])) maxAxis = 2;

    // 3. 2D 邊檢測 (叉積標量 z 分量)
    // 以 maxAxis 為法線投影,構建 2D 向量
    // 此處省略模板代碼,核心為 4 次 2D 叉積 (各 2 乘) = 8 乘
    // 判斷符號一致性
    return true; // 詳見 Real-Time Collision Detection Ch.3
}

總結對比

方法乘法 (含共面)乘法 (已知共面)特點
直接向量法3027直觀、易維護、數值穩定
拉格朗日展開3021消除叉積、適合已知共面批量查詢
投影+2D重心坐標138極致性能、實時碰撞檢測首選、實現稍繁瑣

工程建議

  • **非極端熱點路徑**:用方法一,代碼清晰,編譯器優化後差距微小。
  • **物理引擎/光線追踪/大規模網格碰撞**:用方法三 (投影法),配合 SIMD 批量處理 4/8 個三角形。
  • **數值穩定性**:所有比較加 EPS (如 1e-9),處理邊界/退化三角形 (面積接近 0)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved