QT向量实现GJK碰撞检测算法几何图形二维版本

上述代码可以处理3D几何图形的碰撞检测,但不能直接用于2D平面图形。让我解释原因并提供修改方案:

问题分析:

  1. QVector3D是3D向量:所有计算都是三维的
  2. 支持函数是3D的:Minkowski差和单形演算基于三维
  3. 需要2D实现:对于平面图形,应该使用QVector2D

修改为2D版本的GJK算法:

cpp 复制代码
#ifndef GJK2D_H
#define GJK2D_H

#include <QVector2D>
#include <QList>
#include <cmath>

class GJK2D
{
public:
    // 2D凸体接口
    class ConvexShape2D {
    public:
        virtual ~ConvexShape2D() {}
        virtual QVector2D getFarthestPointInDirection(const QVector2D& direction) const = 0;
    };

    // 测试两个2D凸体是否相交
    static bool checkCollision(const ConvexShape2D& shapeA, const ConvexShape2D& shapeB);
    
    // 计算最近距离(带EPA)
    static float getDistance(const ConvexShape2D& shapeA, const ConvexShape2D& shapeB,
                            QVector2D* closestPointA = nullptr,
                            QVector2D* closestPointB = nullptr);

private:
    // 辅助函数
    static QVector2D support(const ConvexShape2D& shapeA, const ConvexShape2D& shapeB,
                            const QVector2D& direction);
    static bool containsOrigin(QList<QVector2D>& simplex);
    static bool updateSimplex(QList<QVector2D>& simplex, QVector2D& direction);
    static bool lineCase(QList<QVector2D>& simplex, QVector2D& direction);
    static bool triangleCase(QList<QVector2D>& simplex, QVector2D& direction);
    static float cross(const QVector2D& a, const QVector2D& b);
    static bool sameDirection(const QVector2D& a, const QVector2D& b);
};

#endif // GJK2D_H
cpp 复制代码
#include "GJK2D.h"
#include <QDebug>

QVector2D GJK2D::support(const ConvexShape2D& shapeA, const ConvexShape2D& shapeB,
                         const QVector2D& direction)
{
    QVector2D pointA = shapeA.getFarthestPointInDirection(direction);
    QVector2D pointB = shapeB.getFarthestPointInDirection(-direction);
    return pointA - pointB;
}

bool GJK2D::checkCollision(const ConvexShape2D& shapeA, const ConvexShape2D& shapeB)
{
    // 初始方向
    QVector2D direction(1.0f, 0.0f);
    
    // 获取初始支持点
    QVector2D a = support(shapeA, shapeB, direction);
    
    // 如果第一个点就在原点另一侧
    if (QVector2D::dotProduct(a, direction) < 0) {
        return false;
    }
    
    QVector2D b = support(shapeA, shapeB, -a);
    if (QVector2D::dotProduct(b, -a) < 0) {
        return false;
    }
    
    // 构建初始单形(线)
    QList<QVector2D> simplex;
    simplex.append(b);
    simplex.append(a);
    
    // 搜索方向垂直于AB
    QVector2D ab = b - a;
    QVector2D ao = -a;
    direction = QVector2D(-ab.y(), ab.x());
    
    // 确保方向指向原点
    if (QVector2D::dotProduct(direction, ao) < 0) {
        direction = -direction;
    }
    
    const int maxIterations = 32;
    
    for (int i = 0; i < maxIterations; ++i) {
        QVector2D c = support(shapeA, shapeB, direction);
        
        // 如果新点没有超越原点
        if (QVector2D::dotProduct(c, direction) <= 0) {
            return false;
        }
        
        simplex.append(c);
        
        if (containsOrigin(simplex)) {
            return true;
        }
    }
    
    return false;
}

bool GJK2D::containsOrigin(QList<QVector2D>& simplex)
{
    if (simplex.size() == 2) {
        return lineCase(simplex, lastDirection);
    } else if (simplex.size() == 3) {
        return triangleCase(simplex, lastDirection);
    }
    return false;
}

bool GJK2D::lineCase(QList<QVector2D>& simplex, QVector2D& direction)
{
    QVector2D a = simplex.last();
    QVector2D b = simplex.first();
    
    QVector2D ab = b - a;
    QVector2D ao = -a;
    
    if (sameDirection(ab, ao)) {
        // 原点在AB之外
        QVector2D perpendicular = QVector2D(-ab.y(), ab.x());
        direction = sameDirection(perpendicular, ao) ? perpendicular : -perpendicular;
        return false;
    } else {
        // 原点靠近A点
        simplex.removeFirst();
        direction = ao;
        return false;
    }
}

bool GJK2D::triangleCase(QList<QVector2D>& simplex, QVector2D& direction)
{
    QVector2D a = simplex[2]; // 最新点
    QVector2D b = simplex[1];
    QVector2D c = simplex[0];
    
    QVector2D ab = b - a;
    QVector2D ac = c - a;
    QVector2D ao = -a;
    
    QVector2D abPerp = tripleProduct(ac, ab, ab);
    QVector2D acPerp = tripleProduct(ab, ac, ac);
    
    if (sameDirection(abPerp, ao)) {
        // 原点在AB区域
        simplex.removeAt(0); // 移除C
        direction = abPerp;
        return false;
    }
    
    if (sameDirection(acPerp, ao)) {
        // 原点在AC区域
        simplex.removeAt(1); // 移除B
        direction = acPerp;
        return false;
    }
    
    // 原点在三角形内部
    return true;
}

QVector2D GJK2D::tripleProduct(const QVector2D& a, const QVector2D& b, const QVector2D& c)
{
    // 计算 (a × b) × c
    float dotAC = QVector2D::dotProduct(a, c);
    float dotBC = QVector2D::dotProduct(b, c);
    return b * dotAC - a * dotBC;
}

bool GJK2D::sameDirection(const QVector2D& a, const QVector2D& b)
{
    return QVector2D::dotProduct(a, b) > 0;
}

float GJK2D::cross(const QVector2D& a, const QVector2D& b)
{
    return a.x() * b.y() - a.y() * b.x();
}

float GJK2D::getDistance(const ConvexShape2D& shapeA, const ConvexShape2D& shapeB,
                        QVector2D* closestPointA, QVector2D* closestPointB)
{
    // 这里实现EPA算法获取最近距离
    // 简化实现
    bool collision = checkCollision(shapeA, shapeB);
    return collision ? 0.0f : 1.0f;
}

2D形状示例:

cpp 复制代码
// 2D凸多边形
#ifndef POLYGON2D_H
#define POLYGON2D_H

#include "GJK2D.h"
#include <QVector2D>
#include <QList>

class Polygon2D : public GJK2D::ConvexShape2D
{
public:
    Polygon2D(const QList<QVector2D>& vertices) : m_vertices(vertices) {}
    
    QVector2D getFarthestPointInDirection(const QVector2D& direction) const override
    {
        float maxDot = -FLT_MAX;
        QVector2D farthestPoint;
        
        for (const auto& vertex : m_vertices) {
            float dot = QVector2D::dotProduct(vertex, direction);
            if (dot > maxDot) {
                maxDot = dot;
                farthestPoint = vertex;
            }
        }
        
        return farthestPoint;
    }
    
    // 工具函数:创建矩形
    static Polygon2D createRectangle(float x, float y, float width, float height)
    {
        QList<QVector2D> vertices;
        vertices.append(QVector2D(x, y));
        vertices.append(QVector2D(x + width, y));
        vertices.append(QVector2D(x + width, y + height));
        vertices.append(QVector2D(x, y + height));
        return Polygon2D(vertices);
    }
    
    // 工具函数:创建圆形近似(多边形)
    static Polygon2D createCircle(const QVector2D& center, float radius, int segments = 16)
    {
        QList<QVector2D> vertices;
        for (int i = 0; i < segments; ++i) {
            float angle = 2.0f * M_PI * i / segments;
            vertices.append(center + QVector2D(cos(angle), sin(angle)) * radius);
        }
        return Polygon2D(vertices);
    }

private:
    QList<QVector2D> m_vertices;
};

#endif // POLYGON2D_H
cpp 复制代码
// 2D圆形(精确实现)
#ifndef CIRCLE2D_H
#define CIRCLE2D_H

#include "GJK2D.h"
#include <QVector2D>
#include <cmath>

class Circle2D : public GJK2D::ConvexShape2D
{
public:
    Circle2D(const QVector2D& center, float radius)
        : m_center(center), m_radius(radius) {}
    
    QVector2D getFarthestPointInDirection(const QVector2D& direction) const override
    {
        if (direction.lengthSquared() == 0) {
            return m_center + QVector2D(m_radius, 0);
        }
        QVector2D normalizedDir = direction.normalized();
        return m_center + normalizedDir * m_radius;
    }
    
    float getRadius() const { return m_radius; }
    QVector2D getCenter() const { return m_center; }

private:
    QVector2D m_center;
    float m_radius;
};

使用示例:

cpp 复制代码
#include "GJK2D.h"
#include "Polygon2D.h"
#include "Circle2D.h"

void test2DGJK()
{
    // 创建两个矩形
    Polygon2D rect1 = Polygon2D::createRectangle(0, 0, 100, 100);
    Polygon2D rect2 = Polygon2D::createRectangle(50, 50, 100, 100);
    
    // 检测碰撞
    bool collision = GJK2D::checkCollision(rect1, rect2);
    qDebug() << "矩形碰撞:" << collision;
    
    // 创建圆形
    Circle2D circle1(QVector2D(100, 100), 50);
    Circle2D circle2(QVector2D(180, 100), 50);
    
    // 检测碰撞
    collision = GJK2D::checkCollision(circle1, circle2);
    qDebug() << "圆形碰撞:" << collision;
    
    // 多边形和圆形碰撞
    collision = GJK2D::checkCollision(rect1, circle1);
    qDebug() << "矩形-圆形碰撞:" << collision;
}

关键区别:

  1. 维度不同:2D使用QVector2D,3D使用QVector3D
  2. 单形大小:2D最多需要三角形(3个点),3D需要四面体(4个点)
  3. 叉积计算:2D叉积返回标量,3D叉积返回向量
  4. 方向计算:2D中获取垂直方向更简单

如果你需要同时支持2D和3D:

可以创建一个模板化的GJK实现:

cpp 复制代码
template<typename VectorType>
class GJKGeneric {
    // 根据VectorType的维度特化实现
};

// 为2D特化
template<>
class GJKGeneric<QVector2D> {
    // 2D实现
};

// 为3D特化
template<>
class GJKGeneric<QVector3D> {
    // 3D实现
};

这样你就可以根据需要使用2D或3D版本了。

相关推荐
我要升天!2 小时前
QT -- QSS界面优化
开发语言·c++·qt
JANGHIGH2 小时前
c++ 多线程(四)
开发语言·c++
flashlight_hi2 小时前
LeetCode 分类刷题:987. 二叉树的垂序遍历
数据结构·算法·leetcode
小尧嵌入式2 小时前
C++模板
开发语言·c++·算法
仰泳的熊猫2 小时前
1120 Friend Numbers
数据结构·c++·算法·pat考试
BestOrNothing_20152 小时前
C++ 成员函数运算符重载深度解析
c++·八股·运算符重载·operator·this指针·const成员函数·const引用
ALex_zry2 小时前
C++中经典的定时器库与实现方式
开发语言·c++
槿花Hibiscus2 小时前
C++基础:session实现和http server类最终组装
服务器·c++·http·muduo
仰泳的熊猫2 小时前
1116 Come on! Let‘s C
数据结构·c++·算法·pat考试