From: Matthias Braun Date: Mon, 29 Nov 2004 16:03:33 +0000 (+0000) Subject: started work on a collision grid class to speedup collision detection. Doesn't work... X-Git-Url: https://git.verplant.org/?a=commitdiff_plain;h=c43ea9b0d5372718888138ea2cc4abcd5acb5299;p=supertux.git started work on a collision grid class to speedup collision detection. Doesn't work for moving objects yet, but brings speed in area42 from 5fps back to 100fps on my box (still I get 300-400fps in other levels) SVN-Revision: 2227 --- diff --git a/TODO b/TODO index 49cd9f6fc..a6bc743a9 100644 --- a/TODO +++ b/TODO @@ -117,6 +117,8 @@ L: low priority - New forest tileset - Badguy sprites - Tux's buttjump animation +[M] after picking up a star the salcon music isn't played anymore, but the + levelmusic restarts. [M] Save score on per-level basis to make high-score [M] Save time on per-level basis to make low-time-score diff --git a/lib/special/moving_object.h b/lib/special/moving_object.h index d04c3ef3c..b5c476496 100644 --- a/lib/special/moving_object.h +++ b/lib/special/moving_object.h @@ -26,6 +26,7 @@ #include "math/rectangle.h" class Sector; +class CollisionGrid; namespace SuperTux { diff --git a/src/collision_grid.cpp b/src/collision_grid.cpp new file mode 100644 index 000000000..9e11b6073 --- /dev/null +++ b/src/collision_grid.cpp @@ -0,0 +1,233 @@ +#include + +#include +#include "collision_grid.h" +#include "special/collision.h" +#include "sector.h" + +static const float DELTA = .001; + +CollisionGrid::CollisionGrid(float newwidth, float newheight) + : width(newwidth), height(newheight), cell_width(128), cell_height(128) +{ + cells_x = size_t(width / cell_width) + 1; + cells_y = size_t(height / cell_height) + 1; + grid.resize(cells_x * cells_y); +} + +CollisionGrid::~CollisionGrid() +{ + for(GridEntries::iterator i = grid.begin(); i != grid.end(); ++i) { + GridEntry* entry = *i; + while(entry) { + GridEntry* nextentry = entry->next; + delete entry; + entry = nextentry; + } + } +} + +void +CollisionGrid::add_object(MovingObject* object) +{ +#ifdef DEBUG + // make sure the object isn't already in the grid + for(Objects::iterator i = objects.begin(); i != objects.end(); ++i) { + ObjectWrapper* wrapper = *i; + if(wrapper->object == object) + assert(false); + } + assert(object != 0); +#endif + + ObjectWrapper* wrapper = new ObjectWrapper; + wrapper->object = object; + wrapper->timestamp = 0; + wrapper->dest = object->bbox; + objects.push_back(wrapper); + wrapper->id = objects.size()-1; + + const Rectangle& bbox = object->bbox; + for(float y = bbox.p1.y; y < bbox.p2.y; y += cell_height) { + for(float x = bbox.p1.x; x < bbox.p2.x; x += cell_width) { + int gridx = int(x / cell_width); + int gridy = int(y / cell_height); + if(gridx < 0 || gridy < 0 + || gridx >= int(cells_x) || gridy >= int(cells_y)) { + std::cerr << "Object out of range: " << gridx << ", " << gridy << "\n"; + continue; + } + GridEntry* entry = new GridEntry; + entry->object_wrapper = wrapper; + entry->next = grid[gridy*cells_x + gridx]; + grid[gridy*cells_x + gridx] = entry; + } + } +} + +void +CollisionGrid::remove_object(MovingObject* object) +{ + ObjectWrapper* wrapper = 0; + for(Objects::iterator i = objects.begin(); i != objects.end(); ++i) { + if((*i)->object == object) { + wrapper = *i; + objects.erase(i); + break; + } + } + assert(wrapper != 0); + + const Rectangle& bbox = wrapper->dest; + for(float y = bbox.p1.y; y < bbox.p2.y; y += cell_height) { + for(float x = bbox.p1.x; x < bbox.p2.x; x += cell_width) { + int gridx = int(x / cell_width); + int gridy = int(y / cell_height); + if(gridx < 0 || gridy < 0 + || gridx >= int(cells_x) || gridy >= int(cells_y)) { + std::cerr << "Object out of range: " << gridx << ", " << gridy << "\n"; + continue; + } + remove_object_from_gridcell(gridy*cells_x + gridx, object); + } + } + + delete wrapper; +} + +void +CollisionGrid::move_object(MovingObject* object) +{ + const Rectangle& bbox = object->bbox; + for(float y = bbox.p1.y; y < bbox.p2.y; y += cell_height) { + for(float x = bbox.p1.x; x < bbox.p2.x; x += cell_width) { + int gridx = int(x / cell_width); + int gridy = int(y / cell_height); + if(gridx < 0 || gridy < 0 + || gridx >= int(cells_x) || gridy >= int(cells_y)) { + std::cerr << "Object out of range: " << gridx << ", " << gridy << "\n"; + continue; + } + // TODO + } + } +} + +void +CollisionGrid::check_collisions() +{ + for(Objects::iterator i = objects.begin(); i != objects.end(); ++i) { + ObjectWrapper* wrapper = *i; + MovingObject* object = wrapper->object; + if(!object->is_valid()) + continue; + if(object->get_flags() & GameObject::FLAG_NO_COLLDET) { + object->bbox.move(object->movement); + object->movement = Vector(0, 0); + continue; + } + + // hack for now... + Sector::current()->collision_tilemap(object, 0); + + collide_object(wrapper); + + object->bbox.move(object->get_movement()); + object->movement = Vector(0, 0); + } +} + +void +CollisionGrid::collide_object(ObjectWrapper* wrapper) +{ + static int timestamp = 0; + timestamp++; + + const Rectangle& bbox = wrapper->object->bbox; + for(float y = bbox.p1.y; y < bbox.p2.y; y += cell_height) { + for(float x = bbox.p1.x; x < bbox.p2.x; x += cell_width) { + int gridx = int(x / cell_width); + int gridy = int(y / cell_height); + if(gridx < 0 || gridy < 0 + || gridx >= int(cells_x) || gridy >= int(cells_y)) { + std::cerr << "Object out of range: " << gridx << ", " << gridy << "\n"; + continue; + } + + for(GridEntry* entry = grid[gridy*cells_x + gridx]; entry; + entry = entry->next) { + ObjectWrapper* wrapper2 = entry->object_wrapper; + // only check each object once (even if it is in multiple cells) + if(wrapper2->timestamp == timestamp) + continue; + // don't collide with objects we already collided with + if(wrapper2->id <= wrapper->id) + continue; + + wrapper->timestamp = timestamp; + collide_object_object(wrapper, wrapper2); + } + } + } +} + +void +CollisionGrid::collide_object_object(ObjectWrapper* wrapper, + ObjectWrapper* wrapper2) +{ + CollisionHit hit; + MovingObject* object1 = wrapper->object; + MovingObject* object2 = wrapper2->object; + + Rectangle dest1 = object1->get_bbox(); + dest1.move(object1->get_movement()); + Rectangle dest2 = object2->get_bbox(); + dest2.move(object2->get_movement()); + + Vector movement = object1->get_movement() - object2->get_movement(); + if(Collision::rectangle_rectangle(hit, dest1, movement, dest2)) { + HitResponse response1 = object1->collision(*object2, hit); + hit.normal *= -1; + HitResponse response2 = object2->collision(*object1, hit); + + if(response1 != CONTINUE) { + if(response1 == ABORT_MOVE) + object1->movement = Vector(0, 0); + if(response2 == CONTINUE) + object2->movement += hit.normal * (hit.depth + DELTA); + } else if(response2 != CONTINUE) { + if(response2 == ABORT_MOVE) + object2->movement = Vector(0, 0); + if(response1 == CONTINUE) + object1->movement += -hit.normal * (hit.depth + DELTA); + } else { + object1->movement += -hit.normal * (hit.depth/2 + DELTA); + object2->movement += hit.normal * (hit.depth/2 + DELTA); + } + } +} + +void +CollisionGrid::remove_object_from_gridcell(int gridcell, MovingObject* object) +{ + GridEntry* lastentry = 0; + GridEntry* entry = grid[gridcell]; + + while(entry) { + if(entry->object_wrapper->object == object) { + if(lastentry == 0) { + grid[gridcell] = entry->next; + } else { + lastentry->next = entry->next; + } + delete entry; + return; + } + + lastentry = entry; + entry = entry->next; + }; + + std::cerr << "Couldn't find object in cell.\n"; +} + diff --git a/src/collision_grid.h b/src/collision_grid.h new file mode 100644 index 000000000..09cc87afd --- /dev/null +++ b/src/collision_grid.h @@ -0,0 +1,64 @@ +#ifndef __COLLISION_GRID_H__ +#define __COLLISION_GRID_H__ + +#include +#include "special/moving_object.h" + +using namespace SuperTux; + +/** + * A rectangular grid to keep track of all moving game objects. It allows fast + * queries for all objects in a rectangular area. + */ +class CollisionGrid +{ +public: + CollisionGrid(float width, float height); + ~CollisionGrid(); + + void add_object(MovingObject* object); + void remove_object(MovingObject* object); + void move_object(MovingObject* object); + + void check_collisions(); + +private: + struct ObjectWrapper + { + MovingObject* object; + Rectangle dest; + /** (pseudo) timestamp. When reading from the grid the timestamp is + * changed so that you can easily avoid reading an object multiple times + * when it is in several cells that you check. + */ + int timestamp; + /// index in the objects vector + int id; + }; + + /** Element for the single linked list in each grid cell */ + struct GridEntry + { + GridEntry* next; + ObjectWrapper* object_wrapper; + }; + + void remove_object_from_gridcell(int gridcell, MovingObject* object); + void collide_object(ObjectWrapper* wrapper); + void collide_object_object(ObjectWrapper* wrapper, ObjectWrapper* wrapper2); + + typedef std::vector GridEntries; + GridEntries grid; + typedef std::vector Objects; + Objects objects; + size_t cells_x, cells_y; + float width; + float height; + float cell_width; + float cell_height; +}; + +extern CollisionGrid* bla; + +#endif + diff --git a/src/object/oneup.cpp b/src/object/oneup.cpp index 0f2a41018..a576bcfe3 100644 --- a/src/object/oneup.cpp +++ b/src/object/oneup.cpp @@ -4,6 +4,7 @@ #include "resources.h" #include "player.h" #include "scene.h" +#include "sector.h" #include "special/sprite_manager.h" #include "video/drawing_context.h" @@ -23,6 +24,9 @@ OneUp::~OneUp() void OneUp::action(float elapsed_time) { + if(!Sector::current()->inside(bbox)) + remove_me(); + movement = physic.get_movement(elapsed_time); } diff --git a/src/sector.cpp b/src/sector.cpp index 6b3abe1e9..2a56628ce 100644 --- a/src/sector.cpp +++ b/src/sector.cpp @@ -42,6 +42,7 @@ #include "gameloop.h" #include "resources.h" #include "statistics.h" +#include "collision_grid.h" #include "special/collision.h" #include "math/rectangle.h" #include "math/aatriangle.h" @@ -73,6 +74,8 @@ Sector::Sector() song_title = "Mortimers_chipdisko.mod"; player = new Player(); add_object(player); + + grid = new CollisionGrid(32000, 32000); } Sector::~Sector() @@ -80,6 +83,8 @@ Sector::~Sector() update_game_objects(); assert(gameobjects_new.size() == 0); + delete grid; + for(GameObjects::iterator i = gameobjects.begin(); i != gameobjects.end(); ++i) { delete *i; @@ -485,29 +490,42 @@ Sector::update_game_objects() /** cleanup marked objects */ for(std::vector::iterator i = gameobjects.begin(); i != gameobjects.end(); /* nothing */) { - if((*i)->is_valid() == false) { - Bullet* bullet = dynamic_cast (*i); - if(bullet) { - bullets.erase( - std::remove(bullets.begin(), bullets.end(), bullet), - bullets.end()); - } - delete *i; - i = gameobjects.erase(i); - } else { + GameObject* object = *i; + + if(object->is_valid()) { ++i; + continue; + } + + Bullet* bullet = dynamic_cast (object); + if(bullet) { + bullets.erase( + std::remove(bullets.begin(), bullets.end(), bullet), + bullets.end()); + } + MovingObject* movingobject = dynamic_cast (object); + if(movingobject) { + grid->remove_object(movingobject); } + delete *i; + i = gameobjects.erase(i); } /* add newly created objects */ for(std::vector::iterator i = gameobjects_new.begin(); i != gameobjects_new.end(); ++i) { - Bullet* bullet = dynamic_cast (*i); + GameObject* object = *i; + + Bullet* bullet = dynamic_cast (object); if(bullet) bullets.push_back(bullet); - TileMap* tilemap = dynamic_cast (*i); + MovingObject* movingobject = dynamic_cast (object); + if(movingobject) + grid->add_object(movingobject); + + TileMap* tilemap = dynamic_cast (object); if(tilemap && tilemap->is_solid()) { if(solids == 0) { solids = tilemap; @@ -516,7 +534,7 @@ Sector::update_game_objects() } } - Camera* camera = dynamic_cast (*i); + Camera* camera = dynamic_cast (object); if(camera) { if(this->camera != 0) { std::cerr << "Warning: Multiple cameras added. Ignoring."; @@ -525,7 +543,7 @@ Sector::update_game_objects() this->camera = camera; } - gameobjects.push_back(*i); + gameobjects.push_back(object); } gameobjects_new.clear(); } @@ -674,6 +692,9 @@ Sector::collision_object(MovingObject* object1, MovingObject* object2) void Sector::collision_handler() { +#if 0 + grid->check_collisions(); +#else for(std::vector::iterator i = gameobjects.begin(); i != gameobjects.end(); ++i) { GameObject* gameobject = *i; @@ -709,6 +730,7 @@ Sector::collision_handler() movingobject->bbox.move(movingobject->get_movement()); movingobject->movement = Vector(0, 0); } +#endif } bool diff --git a/src/sector.h b/src/sector.h index ee0851436..5fb49f6ef 100644 --- a/src/sector.h +++ b/src/sector.h @@ -40,19 +40,11 @@ class Lisp; class Writer; } -class InteractiveObject; -class Background; class Player; class Camera; -class Trampoline; -class FlyingPlatform; class TileMap; -class Upgrade; class Bullet; -class SmokeCloud; -class Particles; -class BadGuy; -class Tile; +class CollisionGrid; struct SpawnPoint { @@ -115,8 +107,10 @@ public: /** Get total number of badguys */ int get_total_badguys(); -private: + // make this private again soon void collision_tilemap(MovingObject* object, int depth); + +private: void collision_object(MovingObject* object1, MovingObject* object2); void load_music(); @@ -142,8 +136,6 @@ private: std::vector bullets; public: // TODO make this private again - typedef std::vector InteractiveObjects; - InteractiveObjects interactive_objects; typedef std::vector GameObjects; GameObjects gameobjects; @@ -157,6 +149,8 @@ private: SpawnPoints spawnpoints; int currentmusic; + + CollisionGrid* grid; }; #endif