《你被追尾了》中預告了加速碰撞檢測的算法——四叉樹(for 2D),所以本文就來學習一下.
分析首先是為什麼要使用四叉樹進行優化,其實《你被追尾了》中已經說了,這裡簡單複習一下,碰撞檢測是一種比較昂貴的操作. 假設有100個對象需要進行碰撞檢測,那麼兩兩進行碰撞檢測需要進行 100 x 100 = 10000 次碰撞檢測,檢測的次數實在太多,消耗大量CPU資源而引起遊戲卡幀。一種優化途徑是減少非必要的碰撞檢測的次數。比如兩個物體位於屏幕的左上角和右下角,顯然是不可能發生碰撞的,因此不需要檢測它們之間是否會發生碰撞。這正是四叉樹發揮作用的地方。
什麼是四叉樹(Quadtree)四叉樹是一種將一塊2D矩形區域(理解為遊戲沙盒)分割為更易於管理的子區域的數據結構.
四叉樹是二叉樹的擴展——將2個子節點變為4個子節點. 四叉樹的根節點是初始的尚未被劃分的一整塊2D區域.
在下面所有的圖中, 紅色的小方塊代表物體(例如賽車).
然後,我們將一個物體放入初始的2D區域(即四叉樹的根節點)
當越來越多的物體被放入該區域(記做 R,region)的時候,就會導致該區域(節點)的分裂(split). 具體多到什麼程度開始分裂,你可以在程序中進行自定義. 例如我設定為1,則表示只要有物體放入,我就對R 進行分裂. 顯然,這個數字的大小代表四叉樹算法的惰性.
該節點將最終分裂為4(因為是四叉樹嘛~)個子節點(子節點記做SR,sub region). 然後每個物體都將根據它們所處的坐標被劃分進某個SR. 如果一個物體不能完全放入任何一個SR的話,將被歸入 R
例如上圖中,物體 A 將歸入 R, 而除了 A 之外的所有物體都被歸入了相應的 SR
然後,隨著越來越多的物體加入,SR 可能也會分裂. 就像下圖這樣, 左上角的 SR 繼續分裂為了4個子節點.
正如你所見,A、B、C、D 四個物體處在不同的象限,所以絕逼不可能發生碰撞. 這就不需要對這四個物體之間進行昂貴的碰撞檢測,從而優化了遊戲的性能.
知道了四叉樹的思想之後,我們不難給出如下實現. 首先,我先說一下我想做出什麼效果? 就是如下圖所示
就是能實時(其實是每一幀)展示出 四叉樹的樣子,以及填充發生碰撞的小球對(ball pair). 框中的小球和邊界都是彈性碰撞,小球碰撞時彼此互相穿過.
網上有使用 js 實現的版本,我這裡使用 Win 32 API 實現 UI 界面.
以下代碼在 vs2010 下調試通過
Object.h
#include "Rectangle.h"
#include <graphics.h>
#define OBJECT_WIDTH 20
#define OBJECT_HEIGHT 20
class Object : public Bound
{
double vx, vy;
int color;
int id;
public:
Object(double x = 0, double y = 0)
{
this->x = x;
this->y = y;
this->w = rand() % OBJECT_WIDTH + 1;
this->h = this->w;
this->vx = (rand() % 10 + 1) * (rand() & 1 ? 1 : -1);
this->vy = (rand() % 10 + 1) * (rand() & 1 ? 1 : -1);
color = YELLOW;
}
~Object() {}
friend bool collisionWithBound(Object object);
friend void reflect(Object &object);
void setId(int id)
{
this->id = id;
}
void setColor(int color)
{
this->color = color;
}
void paint()
{
setfillcolor(color);
solidrectangle(x, y, x + w, y + h);
}
bool collision(Object &o)
{
bool ans = x < o.x + o.w && o.x < x + w && y < o.y + o.h && o.y < y + h;
if (ans)
{
o.color = color = RED;
}
return ans;
}
void move()
{
x += vx;
y += vy;
}
bool operator == (const Object &o)
{
return id == o.id;
}
};QuadTree.h
#include <list>
using namespace std;
#include "Object.h"
typedef list<Object>::iterator LIT;
class QuadTreeNode
{
public:
void clear();
void insert(Object);
list<Object> retrieve(Object &);
void setLevel(int level) { this->level = level; }
void setBound(Bound bound) { this->bound = bound; }
QuadTreeNode();
QuadTreeNode(int, Bound);
~QuadTreeNode();
private:
int level;
list<Object> objects;
Bound bound;
QuadTreeNode *nodes[4];
const static int MAX_OBJECTS = 5;
const static int MAX_LEVELS = 5;
void split();
int getIndex(Object &);
};
struct Line
{
double sx, sy, ex, ey;
Line(double sx = 0, double sy = 0, double ex = 0, double ey = 0):sx(sx), sy(sy), ex(ex), ey(ey){}
void paint();
};Rectangle.h
class Bound
{
protected:
double x, y, w, h;
public:
Bound(double x = 0, double y = 0, double w = 0, double h = 0):x(x), y(y), w(w), h(h){}
double getx()
{
return x;
}
double gety()
{
return y;
}
double getw()
{
return w;
}
double geth()
{
return h;
}
};QuadTree.cpp
#include "QuadTree.h"
#include <string.h>
Line lines[10005];
int top;
QuadTreeNode::QuadTreeNode()
{
memset(nodes, 0, sizeof(nodes));
}
QuadTreeNode::QuadTreeNode(int level, Bound bound)
{
this->level = level;
this->bound = bound;
memset(nodes, 0, sizeof(nodes));
}
QuadTreeNode::~QuadTreeNode()
{
clear();
}
void QuadTreeNode::clear()
{
objects.clear();
if (nodes[0])
{
for (int i = 0; i < 4; i++)
{
nodes[i]->clear();
nodes[i] = 0;
}
}
}
void QuadTreeNode::split()
{
double w = bound.getw() / 2.0;
double h = bound.geth() / 2.0;
double x = bound.getx();
double y = bound.gety();
nodes[0] = new QuadTreeNode(level + 1, Bound(x + w, y, w, h));
nodes[1] = new QuadTreeNode(level + 1, Bound(x, y, w, h));
nodes[2] = new QuadTreeNode(level + 1, Bound(x, y + h, w, h));
nodes[3] = new QuadTreeNode(level + 1, Bound(x + w, y + h, w, h));
lines[top].sx = bound.getx() + bound.getw() / 2.0; lines[top].sy = bound.gety(); lines[top].ex = bound.getx() + bound.getw() / 2.0; lines[top].ey = bound.gety() + bound.geth();
lines[top + 1].sx = bound.getx(); lines[top + 1].sy = bound.gety() + bound.geth() / 2.0; lines[top + 1].ex = bound.getx() + bound.getw(); lines[top + 1].ey = bound.gety() + bound.geth() / 2.0;
lines[top + 2].sx = bound.getx(); lines[top + 2].sy = bound.gety(); lines[top + 2].ex = bound.getx() + bound.getw(); lines[top + 2].ey = bound.gety();
lines[top + 3].sx = bound.getx(); lines[top + 3].sy = bound.gety() + bound.geth(); lines[top + 3].ex = bound.getx() + bound.getw(); lines[top + 3].ey = bound.gety() + bound.geth();
lines[top + 4].sx = bound.getx(); lines[top + 4].sy = bound.gety(); lines[top + 4].ex = bound.getx(); lines[top + 4].ey = bound.gety() + bound.geth();
lines[top + 5].sx = bound.getx() + bound.getw(); lines[top + 5].sy = bound.gety(); lines[top + 5].ex = bound.getx() + bound.getw(); lines[top + 5].ey = bound.gety() + bound.geth();
top += 6;
}
int QuadTreeNode::getIndex(Object &object)
{
int ans = -1;
double hmid = bound.getx() + bound.getw() / 2.0, vmid = bound.gety() + bound.geth() / 2.0;
if (object.getx() + object.getw() < hmid)
{
if (object.gety() + object.geth() / 2.0 < vmid)
{
return 1;
}
else if (object.gety() > vmid)
{
return 2;
}
}
else if (object.getx() > hmid)
{
if (object.gety() + object.geth() / 2.0 < vmid)
{
return 0;
}
else if (object.gety() > vmid)
{
return 3;
}
}
return ans;
}
void QuadTreeNode::insert(Object object)
{
if (nodes[0])
{
int index = getIndex(object);
if (~index)
{
nodes[index]->insert(object);
return;
}
}
objects.push_back(object);
if (objects.size() > QuadTreeNode::MAX_OBJECTS && level < QuadTreeNode::MAX_LEVELS)
{
if (!nodes[0])
{
split();
}
for (LIT lit = objects.begin(); lit != objects.end();)
{
int index = getIndex(*lit);
if (~index)
{
nodes[index]->insert(*lit);
lit = objects.erase(lit);
}
else
{
++lit;
}
}
}
}
list<Object> QuadTreeNode::retrieve(Object &object)
{
list<Object> ans;
int index;
if (nodes[0] && ~(index = getIndex(object)))
{
ans = nodes[index]->retrieve(object);
}
for (LIT lit = objects.begin(); lit != objects.end(); lit++)
{
ans.push_back(*lit);
}
return ans;
}四叉樹Demo.cpp
#include <stdio.h>
#include <graphics.h>
#include <time.h>
#include <MMSystem.h>
#pragma comment(lib, "winmm.lib")
#include "QuadTree.h"
#define WIDTH 600
#define HEIGHT 800
#define OBJECT_NUM 20
Object objects[OBJECT_NUM];
QuadTreeNode quad;
extern int top;
extern Line lines[];
HWND hWnd;
void initGame();
void _move();
void drawGame();
int main()
{
// 秋華の戀
mciSendString("open 1.mp3 alias start", NULL, NULL, NULL);
mciSendString("play start repeat", NULL, NULL, NULL);
initGame();
SetTimer(hWnd, 666, 50, (TIMERPROC)_move);
while (1)
{
drawGame();
}
return 0;
}
void initGame()
{
srand(time(0));
hWnd = initgraph(WIDTH, HEIGHT);
setbkcolor(RGB(0, 255, 255));
cleardevice();
for (int i = 0; i < OBJECT_NUM; i++)
{
objects[i] = Object(rand() % (WIDTH - OBJECT_WIDTH), rand() % (HEIGHT - OBJECT_HEIGHT));
objects[i].setId(i);
}
quad.setLevel(0);
quad.setBound(Bound(0, 0, WIDTH, HEIGHT));
}
bool collisionWithBound(Object object)
{
return object.x <= 0 || object.x + object.w >= WIDTH || object.y <= 0 || object.y + object.h >= HEIGHT;
}
void reflect(Object &object)
{
if (object.x < 0)
{
object.x = 0;
object.vx = -object.vx;
}
else if (object.x + object.w > WIDTH)
{
object.x = WIDTH - object.w;
object.vx = -object.vx;
}
if (object.y < 0)
{
object.y = 0;
object.vy = -object.vy;
}
else if (object.y + object.h > HEIGHT)
{
object.y = HEIGHT - object.h;
object.vy = -object.vy;
}
}
void _move()
{
for (int i = 0; i < OBJECT_NUM; i++)
{
objects[i].move();
if (collisionWithBound(objects[i]))
{
reflect(objects[i]);
}
}
}
void Line::paint()
{
setlinecolor(BLUE);
setlinestyle(PS_DASH);
line((int) sx, (int) sy, (int) ex, (int) ey);
}
void drawGame()
{
BeginBatchDraw();
cleardevice();
quad.clear();
top = 0;
for (int i = 0; i < OBJECT_NUM; i++)
{
quad.insert(objects[i]);
}
for (int i = 0; i < OBJECT_NUM; i++)
{
objects[i].setColor(YELLOW);
list<Object> candidate = quad.retrieve(objects[i]);
for (LIT lit = candidate.begin(); lit != candidate.end(); lit++)
{
if (objects[i] == *lit)
{
continue;
}
objects[i].collision(*lit);
}
}
for (int i = 0; i < OBJECT_NUM; i++)
{
objects[i].paint();
}
for (int i = 0; i < top; i++)
{
lines[i].paint();
}
EndBatchDraw();
}運行效果
綺麗ですね~!!!