用QT实现GJK算法(吉尔伯特-约翰逊-基尔蒂距离算法)。这里使用QVector3D作为向量类。
cpp
#ifndef GJKALGORITHM_H
#define GJKALGORITHM_H
#include <QVector3D>
#include <QList>
#include <cmath>
class GJKAlgorithm
{
public:
// 凸体接口(需要实现支持函数)
class ConvexShape {
public:
virtual ~ConvexShape() {}
virtual QVector3D getFarthestPointInDirection(const QVector3D& direction) const = 0;
};
// 测试两个凸体是否相交
static bool checkCollision(const ConvexShape& shapeA, const ConvexShape& shapeB);
// 计算两个凸体之间的距离
static float getDistance(const ConvexShape& shapeA, const ConvexShape& shapeB,
QVector3D* closestPointA = nullptr,
QVector3D* closestPointB = nullptr);
private:
// 辅助函数
static QVector3D support(const ConvexShape& shapeA, const ConvexShape& shapeB,
const QVector3D& direction);
static bool containsOrigin(QList<QVector3D>& simplex);
static bool updateSimplex(QList<QVector3D>& simplex, QVector3D& direction);
static bool lineCase(QList<QVector3D>& simplex, QVector3D& direction);
static bool triangleCase(QList<QVector3D>& simplex, QVector3D& direction);
static bool tetrahedronCase(QList<QVector3D>& simplex, QVector3D& direction);
static float signedVolume(const QVector3D& a, const QVector3D& b,
const QVector3D& c, const QVector3D& d);
};
#endif // GJKALGORITHM_H
cpp
#include "GJKAlgorithm.h"
#include <QDebug>
// 实现部分
QVector3D GJKAlgorithm::support(const ConvexShape& shapeA, const ConvexShape& shapeB,
const QVector3D& direction)
{
// 计算Minkowski差的支持点
QVector3D pointA = shapeA.getFarthestPointInDirection(direction);
QVector3D pointB = shapeB.getFarthestPointInDirection(-direction);
return pointA - pointB;
}
bool GJKAlgorithm::checkCollision(const ConvexShape& shapeA, const ConvexShape& shapeB)
{
// 初始化方向向量(任意方向)
QVector3D direction(1.0f, 0.0f, 0.0f);
// 获取初始支持点
QVector3D simplexPoint = support(shapeA, shapeB, direction);
// 构建初始单形(1个点)
QList<QVector3D> simplex;
simplex.append(simplexPoint);
// 更新搜索方向(指向原点)
direction = -simplexPoint;
const int maxIterations = 32;
int iteration = 0;
while (iteration++ < maxIterations) {
// 获取新的支持点
QVector3D newPoint = support(shapeA, shapeB, direction);
// 如果新点在方向上的投影小于等于0,则没有碰撞
if (QVector3D::dotProduct(newPoint, direction) <= 0.0f) {
return false;
}
// 添加到单形
simplex.append(newPoint);
// 检查单形是否包含原点
if (containsOrigin(simplex)) {
return true;
}
}
return false;
}
bool GJKAlgorithm::containsOrigin(QList<QVector3D>& simplex)
{
QVector3D direction;
switch (simplex.size()) {
case 2: // 线
return lineCase(simplex, direction);
case 3: // 三角形
return triangleCase(simplex, direction);
case 4: // 四面体
return tetrahedronCase(simplex, direction);
default:
return false;
}
}
bool GJKAlgorithm::updateSimplex(QList<QVector3D>& simplex, QVector3D& direction)
{
switch (simplex.size()) {
case 2:
return lineCase(simplex, direction);
case 3:
return triangleCase(simplex, direction);
case 4:
return tetrahedronCase(simplex, direction);
default:
return false;
}
}
bool GJKAlgorithm::lineCase(QList<QVector3D>& simplex, QVector3D& direction)
{
QVector3D a = simplex.last();
QVector3D b = simplex.first();
QVector3D ab = b - a;
QVector3D ao = -a;
// 如果AB指向A->B,AO在AB方向上
if (QVector3D::dotProduct(ab, ao) > 0) {
// 原点在AB的垂直方向上
direction = QVector3D::crossProduct(QVector3D::crossProduct(ab, ao), ab);
if (direction.lengthSquared() == 0) {
// 共线情况
direction = QVector3D::crossProduct(ab, QVector3D(1, 0, 0));
if (direction.lengthSquared() == 0) {
direction = QVector3D::crossProduct(ab, QVector3D(0, 1, 0));
}
}
} else {
// 原点靠近A点
simplex.clear();
simplex.append(a);
direction = ao;
}
return false;
}
bool GJKAlgorithm::triangleCase(QList<QVector3D>& simplex, QVector3D& direction)
{
QVector3D a = simplex[2]; // 最新点
QVector3D b = simplex[1];
QVector3D c = simplex[0];
QVector3D ab = b - a;
QVector3D ac = c - a;
QVector3D ao = -a;
// 计算三角形法线
QVector3D abc = QVector3D::crossProduct(ab, ac);
// 检查原点在三角形的哪一侧
if (QVector3D::dotProduct(QVector3D::crossProduct(abc, ac), ao) > 0) {
// 原点在AC边外
if (QVector3D::dotProduct(ac, ao) > 0) {
simplex.clear();
simplex.append(c);
simplex.append(a);
direction = QVector3D::crossProduct(QVector3D::crossProduct(ac, ao), ac);
} else {
return lineCase(simplex, direction);
}
} else {
if (QVector3D::dotProduct(QVector3D::crossProduct(ab, abc), ao) > 0) {
// 原点在AB边外
return lineCase(simplex, direction);
} else {
// 原点在三角形上方或下方
if (QVector3D::dotProduct(abc, ao) > 0) {
// 原点在上方
simplex.clear();
simplex.append(a);
simplex.append(b);
simplex.append(c);
direction = abc;
} else {
// 原点在下方
simplex.clear();
simplex.append(a);
simplex.append(c);
simplex.append(b);
direction = -abc;
}
}
}
return false;
}
bool GJKAlgorithm::tetrahedronCase(QList<QVector3D>& simplex, QVector3D& direction)
{
QVector3D a = simplex[3];
QVector3D b = simplex[2];
QVector3D c = simplex[1];
QVector3D d = simplex[0];
// 检查四面体是否包含原点
float volume1 = signedVolume(a, b, c, d);
float volume2 = signedVolume(a, b, c, QVector3D(0, 0, 0));
// 根据有向体积判断原点位置
// 这里简化处理,实际需要检查所有面
// 如果四面体包含原点,则发生碰撞
if ((volume1 > 0 && volume2 > 0) || (volume1 < 0 && volume2 < 0)) {
return true;
}
// 简化处理:返回最近的面
// 实际实现需要更精确的最近点计算
simplex.removeFirst(); // 移除最旧的点
return triangleCase(simplex, direction);
}
float GJKAlgorithm::signedVolume(const QVector3D& a, const QVector3D& b,
const QVector3D& c, const QVector3D& d)
{
QVector3D ab = b - a;
QVector3D ac = c - a;
QVector3D ad = d - a;
return QVector3D::dotProduct(QVector3D::crossProduct(ab, ac), ad);
}
float GJKAlgorithm::getDistance(const ConvexShape& shapeA, const ConvexShape& shapeB,
QVector3D* closestPointA, QVector3D* closestPointB)
{
// 扩展GJK实现,计算最近距离(EPA算法)
// 这里简化实现,返回碰撞检测结果
bool collision = checkCollision(shapeA, shapeB);
if (collision) {
// 如果碰撞,距离为0
return 0.0f;
}
// 这里应该实现EPA算法来获取最近距离
// 简化实现返回一个估计值
return 1.0f; // 需要完整实现
}
cpp
// 示例:实现一个凸体类(AABB包围盒)
#ifndef AABBSHAPE_H
#define AABBSHAPE_H
#include "GJKAlgorithm.h"
#include <QVector3D>
class AABBShape : public GJKAlgorithm::ConvexShape
{
public:
AABBShape(const QVector3D& min, const QVector3D& max)
: m_min(min), m_max(max) {}
QVector3D getFarthestPointInDirection(const QVector3D& direction) const override
{
QVector3D point;
point.setX((direction.x() >= 0) ? m_max.x() : m_min.x());
point.setY((direction.y() >= 0) ? m_max.y() : m_min.y());
point.setZ((direction.z() >= 0) ? m_max.z() : m_min.z());
return point;
}
QVector3D getCenter() const {
return (m_min + m_max) * 0.5f;
}
QVector3D getExtents() const {
return (m_max - m_min) * 0.5f;
}
private:
QVector3D m_min;
QVector3D m_max;
};
#endif // AABBSHAPE_H
cpp
// 示例:实现一个球体
#ifndef SPHERESHAPE_H
#define SPHERESHAPE_H
#include "GJKAlgorithm.h"
#include <QVector3D>
#include <cmath>
class SphereShape : public GJKAlgorithm::ConvexShape
{
public:
SphereShape(const QVector3D& center, float radius)
: m_center(center), m_radius(radius) {}
QVector3D getFarthestPointInDirection(const QVector3D& direction) const override
{
if (direction.lengthSquared() == 0) {
return m_center + QVector3D(m_radius, 0, 0);
}
QVector3D normalizedDir = direction.normalized();
return m_center + normalizedDir * m_radius;
}
float getRadius() const { return m_radius; }
QVector3D getCenter() const { return m_center; }
private:
QVector3D m_center;
float m_radius;
};
#endif // SPHERESHAPE_H
cpp
// 使用示例
#include "GJKAlgorithm.h"
#include "AABBShape.h"
#include "SphereShape.h"
void testGJK()
{
// 创建两个AABB
AABBShape box1(QVector3D(0, 0, 0), QVector3D(2, 2, 2));
AABBShape box2(QVector3D(1, 1, 1), QVector3D(3, 3, 3));
// 检测碰撞
bool collision = GJKAlgorithm::checkCollision(box1, box2);
qDebug() << "AABB-AABB Collision:" << collision;
// 创建球体
SphereShape sphere1(QVector3D(0, 0, 0), 1.0f);
SphereShape sphere2(QVector3D(1.5f, 0, 0), 1.0f);
// 检测碰撞
collision = GJKAlgorithm::checkCollision(sphere1, sphere2);
qDebug() << "Sphere-Sphere Collision:" << collision;
// 混合检测
collision = GJKAlgorithm::checkCollision(box1, sphere1);
qDebug() << "AABB-Sphere Collision:" << collision;
}
这个实现提供了:
- GJK算法核心:使用Minkowski差和单形演算检测碰撞
- 凸体接口:定义了支持函数接口
- 示例形状:实现了AABB和球体的支持函数
- 碰撞检测:返回布尔值表示是否碰撞
要使用这个实现:
- 继承GJKAlgorithm::ConvexShape实现你的形状
- 实现getFarthestPointInDirection方法
- 调用GJKAlgorithm::checkCollision检测碰撞
注意:这个实现包含了基本的GJK碰撞检测。对于完整的距离计算和最近点信息,需要实现EPA(扩展多边形算法)。