目录
原理
一. 四叉树
1. 树的结构
2. 数据的插入
3. 数据的删除
4. multimap的作用
5. 树结点的结构定义
二. 碰撞检测
1. 碰撞本质
2. 碰撞检测机制的原理
3. 与四叉树的结合
源码实现
测试案例
测试代码
运行结果:
结语
原理
一. 四叉树
1. 树的结构
①树的每一个结点要么没有子结点,要么就有四个子结点,结点可以不存放数据。
②在此次实现中,数据只存放于叶子结点,叶子结点中可存放多个数据,并使用链表存放数据。
③每个数据视情况可插入到多个叶子结点中,因此,为了方便管理,树中增加multimap来存放每个数据分别在哪些结点中的链表的哪个位置。
④本次实现的四叉树是为碰撞检测服务的,因此结点存在容量上限,在创建四叉树时需要指定结点的容量上限,创建后可以修改,当修改后的容量不小于修改前的容量,整棵四叉树结构不变,数据也不变,只对后来的数据有影响;当修改后的容量小于修改前的容量时,重构整棵四叉树。
2. 数据的插入
当插入数据时,找到对应要插入的叶子节点,然后插入其链表末尾,并将新插入的链表位置及其所在结点保存到multimap。当结点的数据量超过容量上限,则为该结点新增四个子结点,将其数据分别插入到对应的子结点中,并清空当前结点的所有数据(非叶子结点不存放数据)。
3. 数据的删除
当删除数据时,可提供对应的数据,这样就会将其从所在的所有结点中删除;也可提供迭代器,将指定结点中删除该数据。同时更新multimap。删除操作不对树结构有影响。
4. multimap的作用
①保存每份数据分别在哪些结点以及这些结点中的链表的位置;
②当复制树内容时,就遍历multimap的key重新插入到新的树结构当中;
③查找数据时,可以通过multimap来快速获得结点及数据在链表中的位置,以此生成迭代器返回。
5. 树结点的结构定义

二. 碰撞检测
1. 碰撞本质
碰撞的本质,是两个图形存在“交集”,即存在重合的地方。
2. 碰撞检测机制的原理
①指定某一个区域,对该区域内的所有物体两两之间进行判断是否碰撞,假设物体数量为n,则时间复杂度为O(n²),这个时间复杂度暂时无法降低。既然碰撞检测消耗的时间会随着物体数量呈指数上升,那就只能减少检测的数量了。
②如何减少数量?这就要用到分治思维,将一个区域划分为几个子区域,然后对这几个子区域内的物体进行碰撞检测。
3. 与四叉树的结合
四叉树的每一个结点都对应一个碰撞检测区域,碰撞检测的根区域作为四叉树的根结点。当物体超出结点容量时,则将结点分裂成四个子结点,也即将该结点代表的区域划分为四等分,然后将这些涉及到的物体重新插入到这四个子结点。
实例演示:
①假设四叉树结点容量为5,现在区域内有以下物体:

则四叉树结构如下图:

②插入新结点“6”如图:

新增的结点插入到根结点,由于超出了结点容量,将区域划分四份,并分裂根结点
区域划分结果:

子结点1、2、3、4分别是
(区域1)、
(区域2)、
(区域3)、
(区域4)。由图看出,①在区域4,②和⑥在区域3,③在区域2,⑤在区域1,而④在区域1和区域2,因此四叉树结构如下图:

源码实现
编译环境:C++98/03
#ifndef QUAD_TREE_H
#define QUAD_TREE_H
#include // std::vector
#include // std::list
#include // std::stack
#include
测试案例
本次测试案例使用Qt框架进行可视化编程,所以测试需要Qt库
测试代码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "quadtree.h"
#define RECTANGLE_NUM 20
#define RANGE_MAX 500
#define RECTANGLE_MAX_WIDTH 100
#define RECTANGLE_MAX_HEIGHT 100
#define COLOR_INDEX (210 / RECTANGLE_NUM)
struct Rectangle;
typedef QuadTree TreeType;
typedef TreeType::pos_type PosType;
static QString format(const char *str, ...)
{
va_list arg_list;
va_start(arg_list, str);
char tmp[1024];
int len = vsprintf(tmp, str, arg_list);
tmp[len] = 0;
va_end(arg_list);
return QObject::tr(tmp);
}
struct Rectangle
{
PositionType::PositionType operator()(const PosType &p) const
{
int quad = 0;
bool left = minx < p.x;
bool right = maxx > p.x;
bool top = miny < p.y;
bool bottom = maxy > p.y;
if(left && top)
{
quad |= PositionType::LEFT_TOP;
}
if(left && bottom)
{
quad |= PositionType::LEFT_BOTTOM;
}
if(right && top)
{
quad |= PositionType::RIGHT_TOP;
}
if(right && bottom)
{
quad |= PositionType::RIGHT_BOTTOM;
}
return PositionType::PositionType(quad);
}
bool operator==(const Rectangle &p) const
{ return obj == p.obj; }
bool operator()(const Rectangle &p) const
{
long long x1 = (minx + maxx) / 2, y1 = (miny + maxy) / 2;
long long x2 = (p.minx + p.maxx) / 2, y2 = (p.miny + p.maxy) / 2;
long long width1 = maxx - minx, height1 = maxy - miny;
long long width2 = p.maxx - p.minx, height2 = p.maxy - p.miny;
long long x = abs(x1 - x2), y = abs(y1 - y2);
long long width = (width1 + width2) / 2, height = (height1 + height2) / 2;
bool ret = x < width;
ret = ret && y < height;
return ret;
}
bool operator<(const Rectangle &p) const
{ return obj < p.obj; }
friend QDebug& operator<<(QDebug &out, const Rectangle &p)
{
out << p.obj;
return out;
}
long long minx;
long long miny;
long long maxx;
long long maxy;
long long obj;
};
static void collision(Rectangle &p1, Rectangle &p2)
{
qDebug() << "collision: (" << p1.obj << ", " << p2.obj << ")";
}
struct CollisionManager
{
void collision(const Rectangle &p1, const Rectangle &p2) const
{
qDebug() << "collision: (" << p1.obj << ", " << p2.obj << ")";
}
};
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTextCodec *codec = QTextCodec::codecForName("utf-8");//设置编码格式
QTextCodec::setCodecForLocale(codec);
TreeType tree(8, PosType(0, 0), PosType(RANGE_MAX, RANGE_MAX));
QMainWindow w;
w.setWindowTitle(QObject::tr("QuadTree Test"));
w.resize(RANGE_MAX + 100, RANGE_MAX + 100);
QWidget *root = new QWidget(&w);
root->setStyleSheet("background:rgb(0,0,0)");
root->move(0, 0);
root->resize(RANGE_MAX + 100, RANGE_MAX + 100);
srand(static_cast(time(nullptr)));
for(int i = 1; i <= RECTANGLE_NUM; ++i)
{
Rectangle p;
p.minx = rand() % RANGE_MAX;
p.miny = rand() % RANGE_MAX;
p.maxx = rand() % RECTANGLE_MAX_WIDTH + p.minx + 10;
p.maxy = rand() % RECTANGLE_MAX_HEIGHT + p.miny + 10;
p.obj = i;
QWidget *handle = new QWidget(root);
handle->setStyleSheet(format("background:rgb(%d,%d,%d)", COLOR_INDEX * i, COLOR_INDEX * i, COLOR_INDEX * i));
QLabel *label = new QLabel(handle);
label->setText(QString::number(i));
label->setStyleSheet(format("color:rgb(255,255,255)"));
handle->move(static_cast(p.minx), static_cast(p.miny));
handle->resize(static_cast(p.maxx - p.minx), static_cast(p.maxy - p.miny));
qDebug() << "new obj:" << p.obj << " ---> min:(" << p.minx << ", " << p.miny << ") max:(" << p.maxx << ", " << p.maxy << ")";
tree.insert(p);
}
tree.collision_detection(collision);
w.show();
return a.exec();
}
运行结果:


结语
案例可视化有些简陋,表达可能不太准确,请见谅。如有问题,欢迎指出!