You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AABB/三角形相交时Y轴角落MTV计算错误,如何修复?

AABB与三角形相交的MTV计算Y轴角落结果错误的修复方案

我在计算AABB(轴对齐包围盒)与三角形相交的MTV(最小平移向量)时,Y轴角落处的结果不正确。

当前结果:
当前结果

以下是原代码:

public static bool BoxTriangleIntersection(Box box, Triangle triangle, out Vector3 minTranslation) {
    minTranslation = Vector3.Zero;

    var boxCenter = box.GetCenter();
    var boxExtents = box.GetHalfExtent();
            
    var aPointRelativeToBox = triangle[0] - boxCenter;
    var bPointRelativeToBox = triangle[1] - boxCenter;
    var cPointRelativeToBox = triangle[2] - boxCenter;

    var triangleEdges = new[] {
        triangle[1] - triangle[0],
        triangle[2] - triangle[1],
        triangle[0] - triangle[2]
    };

    for (var i = 0; i < 3; i++) {
        var projectionAxis = new Vector3(0, -triangleEdges[i].Z, triangleEdges[i].Y);
        var testPointA = Vector3.Dot(aPointRelativeToBox, projectionAxis);
        var testPointB = Vector3.Dot(bPointRelativeToBox, projectionAxis);
        var testPointC = Vector3.Dot(cPointRelativeToBox, projectionAxis);
        var radius = boxExtents.Y * Math.Abs(triangleEdges[i].Z) + boxExtents.Z * Math.Abs(triangleEdges[i].Y);
        if (Math.Max(-MathBindings.Max(testPointA, testPointB, testPointC),
            MathBindings.Min(testPointA, testPointB, testPointC)) > radius) {
            return false;
        }
    }

    for (var i = 0; i < 3; i++) {
        var projectionAxis = new Vector3(triangleEdges[i].Z, 0, -triangleEdges[i].X);
        var testPointA = Vector3.Dot(aPointRelativeToBox, projectionAxis);
        var testPointB = Vector3.Dot(bPointRelativeToBox, projectionAxis);
        var testPointC = Vector3.Dot(cPointRelativeToBox, projectionAxis);
        var radius = boxExtents.X * Math.Abs(triangleEdges[i].Z) + boxExtents.Z * Math.Abs(triangleEdges[i].X);
        if (Math.Max(-MathBindings.Max(testPointA, testPointB, testPointC),
                MathBindings.Min(testPointA, testPointB, testPointC)) > radius) {
            return false;
        }
    }
            
    for (var i = 0; i < 3; i++) {
        var projectionAxis = new Vector3(-triangleEdges[i].Y, triangleEdges[i].X, 0);
        var testPointA = Vector3.Dot(aPointRelativeToBox, projectionAxis);
        var testPointB = Vector3.Dot(bPointRelativeToBox, projectionAxis);
        var testPointC = Vector3.Dot(cPointRelativeToBox, projectionAxis);
        var radius = boxExtents.X * Math.Abs(triangleEdges[i].Y) + boxExtents.Y * Math.Abs(triangleEdges[i].X);
        if (Math.Max(-MathBindings.Max(testPointA, testPointB, testPointC),
            MathBindings.Min(testPointA, testPointB, testPointC)) > radius) {
            return false;
        }
    }

    for (var i = 0; i < 3; i++) {
        if (MathBindings.Max(aPointRelativeToBox[i], bPointRelativeToBox[i], cPointRelativeToBox[i]) < -boxExtents[i] ||
            MathBindings.Min(aPointRelativeToBox[i], bPointRelativeToBox[i], cPointRelativeToBox[i]) > boxExtents[i]) {
            return false;
        }
    }

    var planeNormal = triangle.GetNormal().Normalized();
    var planeDistance = Math.Abs(Vector3.Dot(planeNormal, aPointRelativeToBox));
    var projectionIntervalRadius = boxExtents.X * Math.Abs(planeNormal.X) +
                                    boxExtents.Y * Math.Abs(planeNormal.Y) +
                                    boxExtents.Z * Math.Abs(planeNormal.Z);

    minTranslation = planeNormal * (projectionIntervalRadius - planeDistance);
            
    return planeDistance <= projectionIntervalRadius;
}

问题根源

原代码仅使用三角形平面法线方向计算MTV,但SAT(分离轴定理)要求检查所有潜在分离轴,并选择重叠量最小的轴作为MTV方向。Y轴角落的碰撞中,最小重叠往往不在三角形法线方向,而是在AABB自身轴或三角形边与AABB边的叉乘轴上,原代码忽略了这些轴的重叠计算,导致结果错误。

修复方案

核心修改点

  1. 跟踪所有分离轴的重叠量,保留最小重叠对应的平移向量
  2. 归一化所有分离轴,避免长度不一致导致的计算误差
  3. 修正各轴的重叠量与平移方向计算逻辑

修改后的代码

public static bool BoxTriangleIntersection(Box box, Triangle triangle, out Vector3 minTranslation) {
    minTranslation = Vector3.Zero;
    float minOverlap = float.MaxValue;

    var boxCenter = box.GetCenter();
    var boxExtents = box.GetHalfExtent();
            
    var a = triangle[0] - boxCenter;
    var b = triangle[1] - boxCenter;
    var c = triangle[2] - boxCenter;

    var edges = new[] {
        triangle[1] - triangle[0],
        triangle[2] - triangle[1],
        triangle[0] - triangle[2]
    };

    // 分离轴检测辅助方法:计算重叠量并更新最小MTV
    void TestAxis(Vector3 axis) {
        axis = axis.Normalized();
        if (axis == Vector3.Zero) return;

        // 计算三角形在轴上的投影区间
        float triA = Vector3.Dot(a, axis);
        float triB = Vector3.Dot(b, axis);
        float triC = Vector3.Dot(c, axis);
        float triMin = MathBindings.Min(triA, triB, triC);
        float triMax = MathBindings.Max(triA, triB, triC);

        // 计算AABB在轴上的投影区间(相对于中心,范围是[-radius, radius])
        float radius = boxExtents.X * Math.Abs(axis.X) + boxExtents.Y * Math.Abs(axis.Y) + boxExtents.Z * Math.Abs(axis.Z);

        // 检测是否分离,分离则直接终止检测
        if (triMax < -radius || triMin > radius) {
            throw new InvalidOperationException("Separated");
        }

        // 计算重叠量与平移方向
        float overlap;
        Vector3 direction;
        if (triMin < -radius) {
            // 三角形在AABB负方向外侧,向正方向平移
            overlap = triMax - (-radius);
            direction = axis;
        } else if (triMax > radius) {
            // 三角形在AABB正方向外侧,向负方向平移
            overlap = radius - triMin;
            direction = -axis;
        } else {
            // 区间包含,取最近的方向
            float distToNeg = triMin - (-radius);
            float distToPos = radius - triMax;
            if (distToNeg < distToPos) {
                overlap = distToNeg;
                direction = -axis;
            } else {
                overlap = distToPos;
                direction = axis;
            }
        }

        // 更新最小平移向量
        if (overlap < minOverlap) {
            minOverlap = overlap;
            minTranslation = direction * overlap;
        }
    }

    try {
        // 检测三角形边与AABB轴的叉乘轴(共9个)
        foreach (var edge in edges) {
            TestAxis(new Vector3(0, -edge.Z, edge.Y));
            TestAxis(new Vector3(edge.Z, 0, -edge.X));
            TestAxis(new Vector3(-edge.Y, edge.X, 0));
        }

        // 检测AABB自身的三个轴
        TestAxis(Vector3.UnitX);
        TestAxis(Vector3.UnitY);
        TestAxis(Vector3.UnitZ);

        // 检测三角形平面法线轴
        var planeNormal = triangle.GetNormal().Normalized();
        TestAxis(planeNormal);
    } catch (InvalidOperationException) {
        minTranslation = Vector3.Zero;
        return false;
    }

    return true;
}

关键修复说明

  • 归一化分离轴:确保不同轴的重叠量可以直接比较,避免轴长度差异导致的计算偏差
  • 全轴检测:覆盖SAT要求的所有潜在分离轴(三角形边叉乘AABB轴、AABB自身轴、三角形法线轴)
  • 动态跟踪最小MTV:在每个轴检测时计算对应的平移向量,保留长度最小的结果,确保得到真正的最小平移向量

内容的提问来源于stack exchange,提问作者Rede Boy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 04:15:38