上述代码可以处理3D几何图形的碰撞检测,但不能直接用于2D平面图形。让我解释原因并提供修改方案:
问题分析:
- QVector3D是3D向量:所有计算都是三维的
- 支持函数是3D的:Minkowski差和单形演算基于三维
- 需要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;
}
关键区别:
- 维度不同:2D使用QVector2D,3D使用QVector3D
- 单形大小:2D最多需要三角形(3个点),3D需要四面体(4个点)
- 叉积计算:2D叉积返回标量,3D叉积返回向量
- 方向计算: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版本了。