js小球与边框碰撞反弹_四叉树在碰撞检测中的应用

本文介绍了如何利用四叉树优化2D碰撞检测,避免大量的无效检测,提高游戏性能。四叉树将2D区域划分为子区域,随着物体增加,节点分裂以容纳更多物体。当物体分布于不同象限时,碰撞检测得以简化。通过实例展示了四叉树的实时演示和弹性碰撞效果。
摘要由CSDN通过智能技术生成

缘起

《你被追尾了》中预告了加速碰撞检测的算法——四叉树(for 2D),所以本文就来学习一下.

分析

首先是为什么要使用四叉树进行优化,其实《你被追尾了》中已经说了,这里简单复习一下,碰撞检测是一种比较昂贵的操作. 假设有100个对象需要进行碰撞检测,那么两两进行碰撞检测需要进行 100 x 100 = 10000 次碰撞检测,检测的次数实在太多,消耗大量CPU资源而引起游戏卡帧。一种优化途径是减少非必要的碰撞检测的次数。比如两个物体位于屏幕的左上角和右下角,显然是不可能发生碰撞的,因此不需要检测它们之间是否会发生碰撞。这正是四叉树发挥作用的地方。

什么是四叉树(Quadtree)

四叉树是一种将一块2D矩形区域(理解为游戏沙盒)分割为更易于管理的子区域的数据结构.

四叉树是二叉树的扩展——将2个子节点变为4个子节点.  四叉树的根节点是初始的尚未被划分的一整块2D区域.

在下面所有的图中, 红色的小方块代表物体(例如赛车).

9905bed89d3d1726b796c258cd6a5d63.png

然后,我们将一个物体放入初始的2D区域(即四叉树的根节点)

5f6b13ef99b917eab263d70dda0bab61.png

当越来越多的物体被放入该区域(记做 R,region)的时候,就会导致该区域(节点)的分裂(split).  具体多到什么程度开始分裂,你可以在程序中进行自定义. 例如我设定为1,则表示只要有物体放入,我就对R 进行分裂. 显然,这个数字的大小代表四叉树算法的惰性.

该节点将最终分裂为4(因为是四叉树嘛~)个子节点(子节点记做SR,sub region). 然后每个物体都将根据它们所处的坐标被划分进某个SR. 如果一个物体不能完全放入任何一个SR的话,将被归入 R

2ead191656bd4eb66d57b21499fad026.png

例如上图中,物体 A 将归入 R, 而除了 A 之外的所有物体都被归入了相应的 SR

然后,随着越来越多的物体加入,SR 可能也会分裂. 就像下图这样, 左上角的 SR 继续分裂为了4个子节点.

904646ffef0a94c4d8e6b53cd66df48b.png

正如你所见,A、B、C、D 四个物体处在不同的象限,所以绝逼不可能发生碰撞. 这就不需要对这四个物体之间进行昂贵的碰撞检测,从而优化了游戏的性能.

知道了四叉树的思想之后,我们不难给出如下实现.  首先,我先说一下我想做出什么效果? 就是如下图所示

346c435a3ca828147a535fe789ff6f21.png

就是能实时(其实是每一帧)展示出 四叉树的样子,以及填充发生碰撞的小球对(ball pair). 框中的小球和边界都是弹性碰撞,小球碰撞时彼此互相穿过.

网上有使用 js 实现的版本,我这里使用 Win 32 API 实现 UI 界面.

70ec2bdf7bc9f8ade1e1b996572a5119.png

以下代码在 vs2010  下调试通过

Object.h

#include "Rectangle.h"
#include 

#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         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 
using namespace std;
#include "Object.h"
typedef list::iterator LIT;class QuadTreeNode
{public:void clear();void insert(Object); list 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 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 

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()     {
        if (object.gety() + object.geth() / 2.0         {
            return 1;
        }
        else if (object.gety() > vmid)
        {
            return 2;
        }
    }
    else if (object.getx() > hmid) 
    {
        if (object.gety() + object.geth() / 2.0         {
            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     {
        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 QuadTreeNode::retrieve(Object &object)
{list 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 
#include 
#include 
#include 
#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     {
        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     {
        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     {
        quad.insert(objects[i]);
    }
    for (int i = 0; i     {
        objects[i].setColor(YELLOW); 
        list 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     {
        objects[i].paint();
    }for (int i = 0; i     {
        lines[i].paint();
    }
    EndBatchDraw();
}

运行效果

47f2b83bbf6cd54687d1cb4ee7dc5eef.png

綺麗ですね~!!!

d0c42bcf33bf9ac8eb06c9eeee76f313.gif

温馨提示

如果你喜欢本文,请分享到朋友圈,想要获得更多信息,请关注ACM算法日常

636edd4906306da941e0da4e72d41984.png

点赞的时候,请宠溺一点
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值