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

GJK碰撞检测算法异常:模型缩放后无法识别碰撞问题排查

GJK碰撞检测缩放模型后失效问题排查

我使用glm库实现GJK碰撞检测算法时遇到异常:当通过模型矩阵放大模型尺寸后,算法无法正确识别碰撞——即使物体部分相互嵌入,也不会判定为碰撞。

无碰撞状态
碰撞状态
图1:无碰撞;图2:存在碰撞

相关代码实现

struct Vertex
{
    glm::vec3 pos;
    glm::vec3 color;
    glm::vec2 texCoord;
};

struct Mesh
{
    std::vector<Vertex> vertices;
    std::vector<uint32_t> indices;
    std::vector<glm::dvec3> hitBox;
    glm::dmat4 model;
    glm::dmat4 invModel;

    void setModel(const glm::dmat4 &model)
    {
        this->model = model;
        this->invModel = glm::inverse(model);
    }
};

enum class SimplexType 
{
    Zero,
    Point,
    Line,
    Triangle,
    Tetrahedron
};

struct Simplex
{
    std::list<glm::dvec3> points{};

    Simplex() = default;

    Simplex(std::initializer_list<glm::dvec3> list) 
    {
        for (const auto& v : list) 
        {
            points.push_back(v);
            if (points.size() > 4) 
            {
                points.pop_front();
            }
        }
    }

    void push_front(const glm::dvec3 &point) 
    {
        points.push_front(point);
        if (points.size() > 4) 
        {
            points.pop_back();
        }
    }

    glm::dvec3 operator[](size_t i) const 
    {
        auto it = points.begin();
        for (size_t k = 0; k < i; k++) 
        {
            ++it;
        }
        return *it;

    }

    [[nodiscard]] size_t size() const { return points.size(); }

    [[nodiscard]] auto begin() const { return points.begin(); }

    [[nodiscard]] auto end() const { return points.end(); }

    [[nodiscard]] SimplexType type() const { return static_cast<SimplexType>(points.size()); }
};

struct NextSimplex
{
    const Simplex newSimplex;
    const glm::dvec3 newDirection;
    const bool finishSearching;
};

class RigidBody
{
public:
    Mesh mesh;

    glm::dvec3 findFurthestPoint(const glm::dvec3 &direction)
    {
        glm::dvec3 maxPoint = glm::dvec3(0.0, 0.0, 0.0);
        double maxDistance = 0.0;

        glm::dvec3 transformedDirection = glm::normalize(this->mesh.invModel * glm::dvec4(direction, 0.0));

        for (auto &it : mesh.hitBox)
        {
            double distance = glm::dot(it, transformedDirection);

            if (distance > maxDistance)
            {
                maxDistance = distance;
                maxPoint = it;
            }
        }

        return mesh.model * glm::dvec4(maxPoint, 1.0);
    }

    glm::dvec3 support(RigidBody &obj, const glm::dvec3 &direction)
    {
        glm::dvec3 p1 = findFurthestPoint(direction);
        glm::dvec3 p2 = obj.findFurthestPoint(-direction);

        return p1 - p2;
    }

    NextSimplex lineCase(const Simplex &points)
    {
        Simplex newPoints(points);
        glm::dvec3 newDirection;

        glm::dvec3 a = points[0];
        glm::dvec3 b = points[1];

        glm::dvec3 ab = b - a;
        glm::dvec3 ao = -a;

        if (glm::dot(ab, ao) > 0)
        {
            newDirection = glm::cross(glm::cross(ab, ao), ab);
        }
        else
        {
            newPoints = Simplex{ a };
            newDirection = ao;
        }

        return NextSimplex{ newPoints, newDirection, false };
    }

    NextSimplex triangleCase(const Simplex &points)
    {
        Simplex newPoints(points);
        glm::dvec3 newDirection{};

        glm::dvec3 a = points[0];
        glm::dvec3 b = points[1];
        glm::dvec3 c = points[2];

        glm::dvec3 ab = b - a;
        glm::dvec3 ac = c - a;
        glm::dvec3 ao = -a;

        glm::dvec3 abc = glm::cross(ab, ac);

        if (glm::dot(glm::cross(abc, ac), ao) > 0)
        {
            if (glm::dot(ac, ao) > 0)
            {
                newPoints = Simplex{ a, c };
                newDirection = glm::cross(glm::cross(ac, ao), ac);
            }
            else
            {
                return lineCase(Simplex{ a, b });
            }
        }
        else
        {
            if (glm::dot(glm::cross(ab, abc), ao) > 0)
            {
                return lineCase(Simplex{ a, b });
            }
            else
            {
                if (glm::dot(abc, ao) > 0)
                {
                    newDirection = abc;
                }
                else
                {
                    newDirection = -abc;
                    newPoints = Simplex{ a, c, b };
                }
            }
        }

        return NextSimplex{ newPoints, newDirection, false };
    }

    NextSimplex tetrahedronCase(const Simplex &points)
    {
        glm::dvec3 a = points[0];
        glm::dvec3 b = points[1];
        glm::dvec3 c = points[2];
        glm::dvec3 d = points[3];

        glm::dvec3 ab = b - a;
        glm::dvec3 ac = c - a;
        glm::dvec3 ad = d - a;
        glm::dvec3 ao = -a;

        glm::dvec3 abc = glm::cross(ab, ac);
        glm::dvec3 acd = glm::cross(ac, ad);
        glm::dvec3 adb = glm::cross(ad, ab);

        if (glm::dot(abc, ao) > 0)
        {
            return triangleCase(Simplex{ a, b, c });
        }

        if (glm::dot(acd, ao) > 0)
        {
            return triangleCase(Simplex{ a, c, d });
        }

        if (glm::dot(adb, ao) > 0)
        {
            return triangleCase(Simplex{ a, d, b });
        }

        return NextSimplex(points, glm::dvec3(), true);
    }

    NextSimplex nextSimplex(const Simplex &points)
    {
        switch (points.type())
        {
        case SimplexType::Line:
            return lineCase(points);
        case SimplexType::Triangle:
            return triangleCase(points);
        case SimplexType::Tetrahedron:
            return tetrahedronCase(points);

        default:
            throw std::logic_error("Simplex is not line, triangle or tetrahedron");
        }
    }

    std::pair<bool, Simplex> checkGJKCollision(RigidBody &obj)
    {
        glm::dvec3 support = this->support(obj, glm::dvec3(1.0, 0.0, 0.0));

        Simplex points{};
        points.push_front(support);

        glm::dvec3 direction = -support;

        size_t iters = 0;
        while (++iters < mesh.vertices.size() + obj.mesh.vertices.size())
        {
            support = this->support(obj, direction);

            if (glm::dot(support, direction) <= 0)
                return std::make_pair(false, points);

            points.push_front(support);

            NextSimplex nextSimplex = this->nextSimplex(points);
            direction = nextSimplex.newDirection;
            points = nextSimplex.newSimplex;

            if (nextSimplex.finishSearching)
            {
                return std::make_pair(true, points);
            }
        }

        return std::make_pair(false, points);
    }
};

重点函数(问题疑似位置)

glm::dvec3 findFurthestPoint(const glm::dvec3 &direction)
{
    glm::dvec3 maxPoint = glm::dvec3(0.0, 0.0, 0.0);
    double maxDistance = 0.0;

    glm::dvec3 transformedDirection = glm::normalize(this->mesh.invModel * glm::dvec4(direction, 0.0));

    for (auto &it : mesh.hitBox)
    {
        double distance = glm::dot(it, transformedDirection);

        if (distance > maxDistance)
        {
            maxDistance = distance;
            maxPoint = it;
        }
    }

    return mesh.model * glm::dvec4(maxPoint, 1.0);
}

关键观察与疑问

如果修改findFurthestPoint函数,在循环中先将hitBox中的点it乘以模型矩阵,再用glm::dot(..., direction)计算点积,算法就能正常工作。我怀疑问题出在invModel矩阵的处理上,但无法定位具体错误,恳请排查。

模型矩阵创建代码

auto modelMatrix = glm::translate(glm::dmat4(1.0), glm::dvec3(0.0, 0.0, 0.0));
modelMatrix = glm::scale(modelMatrix, glm::dvec3(1.0, 1.5, 1.5));

box2.mesh.setModel(modelMatrix);
auto cameraPos = camera.position;
cameraPos.z -= 2.0;
modelMatrix = glm::translate(glm::dmat4(1.0), glm::dvec3(cameraPos));
modelMatrix = glm::rotate(modelMatrix, objAngleX, glm::dvec3(1.0, 0.0, 0.0));
modelMatrix = glm::rotate(modelMatrix, objAngleZ, glm::dvec3(0.0, 0.0, 1.0));
modelMatrix = glm::scale(modelMatrix, glm::dvec3(0.3 + objX, 1.0, 0.3 + objZ));
box1.mesh.setModel(modelMatrix);

isCollision = box1.checkGJKCollision(box2).first; 

内容的提问来源于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 03:40:53