So, here are: Shooting the entities, Bombs and slimes, Managing crowds, If precision is needed.
The problem: we want to shoot entities. That is, we have some ray, and we want to test the entities for intersection with it.
The naive approach: test all the entities. This fails pretty quickly, as entity count increases.
So, instead let's have a grid, whose sectors contain the ray or contain nothing:
struct SRaySector { bool mHasRay; D3DXVECTOR3 mOrigin; // origin of the ray D3DXVECTOR3 mDir; // direction of the ray }; typedef CGrid<SRaySector,??,??> TRayGrid;
Now we can "put" the ray only into the sectors it crosses:
CEntityRayPicker<TRayGrid>::putRay( grid, origin, direction );
Then for each entity that can be shot:
bool shootEntity( TRayGrid& grid, CEntity& entity ) { // get our sector const SRaySector& s = grid.getSectorByPosition( entity.getPosition().x, entity.getPosition().y ); if( s.mHasRay ) { // if it has a ray, check for intersection with entity return CEntityRayPicker<TRayGrid>::intersects( entity, s ); } return false; // no ray in my sector - no intersection }
Almost whole code that is needed for this case already exists in the engine.
The problem: we want to drop bombs, or have some dangerous areas.
Again: let's have sectors that can contain bombs:
struct SBombSector { bool mHasBomb; float mBombX, mBombY; // precise location, if you need it }; typedef CGrid<SBombSector,??,??> TBombGrid;
Now we put bombs into "relevant" sectors. Then for each entity that can be blasted off:
void blastEntity( TBombGrid& grid, CEntity& entity ) { // get our sector const SBombSector& s = grid.getSectorByPosition( entity.getPosition().x, entity.getPosition().y ); if( s.mHasRay ) { // if it has a bomb - do something doSomethingWithIt(); } }
The problem: we don't want all the entities to gather into one spot.
So, lets have them "mark" the area they're in (much like dogs do). Each entity increments the "there are many of us here" counter in it's sector. If the counter exceeds some limit, the entity may set it's velocity to run away, for example. Or it can see the sector it will be in soon (by adding some if it's own velocity to position), and stop if there are already many other entities.
This is very crude approximation, though. You're invited to invent your own methods :)
So:
struct SCrowdSector { int mCounter; }; typedef CGrid<SCrowdSector,??,??> TCrowdGrid;
Then for each entity:
void walkEntity( TCrowdGrid& grid, CEntity& entity ) { const D3DXVECTOR3& pos = entity.getPosition(); // position D3DXVECTOR3& vel = entity.getVelocity(); // velocity D3DXVECTOR3 future = pos + someFactor * vel; // future position // leave mark here grid.getSectorByPosition( pos.x, pos.y ).mCounter++; // check future if( grid.getSectorByPosition( future.x, future.y ).mCounter > SOME_AMOUNT ) vel.x = vel.y = 0.0f; // stop }
The same idea could be used for fighting armies: have a sector with two (or more) counters, each entity increments it's army's counter. After that, you go through the sectors and see force balances (and act accordingly, for example, set "I should die" flag in the sector for losing army).
The problem: I don't want crappy approximations! Give me precise spatial information!
Common solution is: let sectors contain collections of pointers to the entities they contain. Also let entity contain a pointer to it's current sector, and it's index in sector's collection.
This imposes some work to be done: each time the entity moves, it must remove itself from it's previous sector, and place itself into new sector.
If you plan to do this for all your thousands and thousands of entities, be warned: when we implemented this, we observed total performance loss of 10% or more just for managing the sector-entity relations.
But, of course, you may choose to do this only for a subset of Very Important Entities.
So, our sector is like this:
struct SPreciseSector { enum { MIN_CAPACITY = 16 }; // minimum capacity SPhysicsSector(); ~SPhysicsSector(); void increaseCapacity(); // allocate bigger array, copy old data void decreaseCapacity(); // allocate smaller array, copy old data int mCapacity; // capacity of array int mCount; // entity count CEntity** mEntities; // array of pointers to entitites };
short mSectorNumber; // say, -1 for "none" short mIndexInSector;
if( entity.mSectorNumber == NONE_SECTOR ) return; // get sector SPreciseSector& s = grid.getSectorByIndex( entity.mSectorNumber ); int o = entity.mIndexInSector; assert( s.mEntities[o] == &entity ); // just in case int lastIndex = --s.mCount; if( lastIndex != o ) { // if we're not last // place last one into our place s.mEntities[o] = s.mEntities[lastIndex]; s.mEntities[o]->mIndexInSector = o; // update last one's index } if( s.mCount * 2 <= s.mCapacity ) // like this s.decreaseCapacity(); entity.mSectorNumber = NONE_SECTOR;
// get sector SPreciseSector& s = grid.getSectorByIndex( newSectorIndex ); if( s.mCount >= s.mCapacity ) s.increaseCapacity(); assert( s.mCount < s.mCapacity ); // just in case // put at end of entity list int o = s.mCount++; s.mEntities[o] = &entity; entity.mSectorNumber = newSectorNumber; entity.mIndexInSector = o;
int newSectorNumber = grid.position2index( newPosition.x, newPosition.y ); if( entity.mSectorNumber != newSectorNumber ) { removeFromOldSector(); insertIntoSector( newSectorNumber ); }
Now, with this kind of grid, you always have the precise information about who is where. So collision and stuff is the traditional one - get relevant sectors, check with other entities in these sectors. These utilities are not in the engine, but they are pretty easy to code.
Prev: Case study: selecting entities.
Next: Details: The Entities.
1.2.17