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

基于C++和glm的物理引擎RayCast三角相交检测错误求助

射线与三角形相交检测RayCast函数错误排查

以下是针对你的RayCast函数及相关代码的错误点排查和修正方案:


1. 索引遍历逻辑错误

这是导致检测结果错误的核心问题之一:你遍历三角形索引时,错误地使用了连续的i+0/i+1/i+2索引,而非每个三角形对应的三个独立索引(每个三角形占indices数组的3个元素)。这种写法会导致取错三角形顶点,直接引发相交检测结果错误。

错误代码片段:

for (size_t i = 0; i < mesh.indices.size() / 3; ++i)
{
    auto normal = calculateNormal(mesh.hitBox[mesh.indices[i + 0]], mesh.hitBox[mesh.indices[i + 1]], mesh.hitBox[mesh.indices[i + 2]]);
    // ...后续代码
}

修正代码:

// 按步长3遍历索引数组,每个步长对应一个三角形的三个顶点索引
for (size_t i = 0; i < mesh.indices.size(); i += 3)
{
    uint32_t idx0 = mesh.indices[i];
    uint32_t idx1 = mesh.indices[i + 1];
    uint32_t idx2 = mesh.indices[i + 2];
    auto normal = calculateNormal(mesh.hitBox[idx0], mesh.hitBox[idx1], mesh.hitBox[idx2]);
    // ...后续代码引用idx0/idx1/idx2访问顶点
}

2. 模型空间变换方向完全错误

你当前的代码试图用模型矩阵将世界空间射线转换到模型空间,但正确逻辑应该是使用模型矩阵的逆矩阵——模型矩阵是将模型空间坐标转换到世界空间的矩阵,逆矩阵才能完成反向转换(世界→模型)。同时,方向向量的齐次分量w应设为0(不受平移变换影响),点的齐次分量w设为1。

错误代码片段:

glm::dvec3 vModel = glm::dvec4(v, 1.0) * this->mesh.model;
glm::dvec3 fromModel = glm::dvec4((from - mesh.position), 1.0) * this->mesh.model;
glm::dvec3 toModel = glm::dvec4((to - mesh.position), 1.0) * this->mesh.model;

修正代码:

// 计算模型矩阵的逆矩阵,用于世界空间→模型空间的转换
glm::dmat4 modelInv = glm::inverse(this->mesh.model);
// 转换射线起点(点,w=1)
glm::dvec3 fromModel = glm::dvec3(modelInv * glm::dvec4(from, 1.0));
// 转换射线终点(点,w=1)
glm::dvec3 toModel = glm::dvec3(modelInv * glm::dvec4(to, 1.0));
// 转换射线方向向量(向量,w=0,不受平移影响)
glm::dvec3 vModel = glm::dvec3(modelInv * glm::dvec4(glm::normalize(to - from), 0.0));

3. 平面相交计算存在精度与逻辑漏洞

原planeIntersection函数未处理射线与平面平行的情况(分母为0),且判断条件仅检查k>0,未考虑浮点精度容差,同时公式推导可以更清晰直观。

错误代码片段:

std::pair<glm::dvec3, double> planeIntersection(const glm::dvec3 &start, const glm::dvec3 &end, const glm::dvec3 &point, const glm::dvec3 &normal) const
{
    double sDot = glm::dot(start, normal);
    double k = (sDot - glm::dot(point, normal)) / (sDot - glm::dot(end, normal));
    glm::dvec3 res = start + (end - start) * k;
    return std::make_pair(res, k);
}

修正代码:

std::pair<glm::dvec3, double> planeIntersection(const glm::dvec3 &start, const glm::dvec3 &end, const glm::dvec3 &point, const glm::dvec3 &normal) const
{
    glm::dvec3 dir = end - start;
    double denom = glm::dot(normal, dir);
    
    // 射线与平面平行,无有效交点
    if (std::fabs(denom) < EPS)
        return {glm::dvec3(0.0), -1.0};
    
    // 计算射线参数t:t>=0表示交点在射线起点前方
    double t = (glm::dot(normal, point) - glm::dot(normal, start)) / denom;
    glm::dvec3 res = start + dir * t;
    return {res, t};
}

4. 点在三角形内的检测存在精度损失

原代码对叉乘结果做了normalize操作,这会放大浮点精度误差,实际上我们只需要判断叉乘结果与法线的点积符号是否一致,无需归一化。同时,判断条件需要加入浮点容差,避免因精度问题导致误判。

错误代码片段:

bool isPointInsideTriangle(const glm::dvec3 &point, const glm::dvec3 &triangleNorm, const glm::dvec3 &point1, const glm::dvec3 &point2, const glm::dvec3 &point3) const
{
    double dot1 = glm::dot(glm::normalize(glm::cross(point2 - point1, point - point1)), triangleNorm);
    double dot2 = glm::dot(glm::normalize(glm::cross(point3 - point2, point - point2)), triangleNorm);
    double dot3 = glm::dot(glm::normalize(glm::cross(point1 - point3, point - point3)), triangleNorm);

    if ((dot1 >= 0.0 && dot2 >= 0.0 && dot3 >= 0.0) || (dot1 <= 0.0 && dot2 <= 0.0 && dot3 <= 0.0))
        return true;

    return false;
}

修正代码:

bool isPointInsideTriangle(const glm::dvec3 &point, const glm::dvec3 &triangleNorm, const glm::dvec3 &point1, const glm::dvec3 &point2, const glm::dvec3 &point3) const
{
    // 计算三条边与点的叉乘,再点乘法线获取符号
    glm::dvec3 cross1 = glm::cross(point2 - point1, point - point1);
    double dot1 = glm::dot(cross1, triangleNorm);
    glm::dvec3 cross2 = glm::cross(point3 - point2, point - point2);
    double dot2 = glm::dot(cross2, triangleNorm);
    glm::dvec3 cross3 = glm::cross(point1 - point3, point - point3);
    double dot3 = glm::dot(cross3, triangleNorm);

    // 加入浮点容差,判断所有点积符号一致(全非负或全非正)
    bool allNonNegative = (dot1 >= -EPS) && (dot2 >= -EPS) && (dot3 >= -EPS);
    bool allNonPositive = (dot1 <= EPS) && (dot2 <= EPS) && (dot3 <= EPS);
    return allNonNegative || allNonPositive;
}

5. 法线计算中的未定义函数问题

原代码使用了未定义的sqrAbs函数,应该替换为glm内置的glm::length2函数(返回向量长度的平方,避免开方运算,效率更高)。

错误代码片段:

if (sqrAbs(crossProduct) > EPS)

修正代码:

if (glm::length2(crossProduct) > EPS)

6. RayCast返回结果错误

原代码在检测到相交时返回mesh.position(物体位置),而非实际的交点坐标。正确做法是将模型空间的交点转换回世界空间后返回。

错误代码片段:

return std::make_pair(true, glm::dvec3(mesh.position));

修正代码:

// 将模型空间交点转换为世界空间
glm::dvec3 worldHit = glm::dvec3(this->mesh.model * glm::dvec4(intersection.first, 1.0));
return std::make_pair(true, worldHit);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 00:47:02