mirror of
https://github.com/ptitSeb/Serious-Engine
synced 2024-11-22 18:30:27 +01:00
445 lines
13 KiB
C++
445 lines
13 KiB
C++
/* Copyright (c) 2002-2012 Croteam Ltd. All rights reserved. */
|
|
|
|
#include "StdH.H"
|
|
|
|
#include <Engine/World/World.h>
|
|
#include <Engine/World/PhysicsProfile.h>
|
|
#include <Engine/Templates/StaticStackArray.cpp>
|
|
#include <Engine/Templates/AllocationArray.h>
|
|
#include <Engine/Templates/AllocationArray.cpp>
|
|
|
|
#define DEBUG_COLLIDEWITHALL 0
|
|
|
|
#if DEBUG_COLLIDEWITHALL
|
|
#include <Engine/Templates/DynamicContainer.h>
|
|
#include <Engine/Templates/DynamicContainer.cpp>
|
|
#endif
|
|
|
|
// allowed grid dimensions (meters)
|
|
#define GRID_MIN (-32000)
|
|
#define GRID_MAX (+32000)
|
|
|
|
#define GRID_CELLSIZE 2.0 // size of one grid cell (meters)
|
|
// number of hash table entries for grid cells
|
|
#define GRID_HASHTABLESIZE_LOG2 12 // must be even for bit-shuffling
|
|
#define GRID_HASHTABLESIZE (1<<GRID_HASHTABLESIZE_LOG2)
|
|
|
|
//#pragma inline_depth(0)
|
|
|
|
// find grid box from float coordinates
|
|
static inline void BoxToGrid(
|
|
const FLOATaabbox3D &boxEntity, INDEX &iMinX, INDEX &iMaxX, INDEX &iMinZ, INDEX &iMaxZ)
|
|
{
|
|
FLOAT fMinX = boxEntity.Min()(1);
|
|
FLOAT fMinZ = boxEntity.Min()(3);
|
|
FLOAT fMaxX = boxEntity.Max()(1);
|
|
FLOAT fMaxZ = boxEntity.Max()(3);
|
|
iMinX = INDEX(floor(fMinX/GRID_CELLSIZE));
|
|
iMinZ = INDEX(floor(fMinZ/GRID_CELLSIZE));
|
|
iMaxX = INDEX(ceil(fMaxX/GRID_CELLSIZE));
|
|
iMaxZ = INDEX(ceil(fMaxZ/GRID_CELLSIZE));
|
|
|
|
iMinX = Clamp(iMinX, (INDEX)GRID_MIN, (INDEX)GRID_MAX);
|
|
iMinZ = Clamp(iMinZ, (INDEX)GRID_MIN, (INDEX)GRID_MAX);
|
|
iMaxX = Clamp(iMaxX, (INDEX)GRID_MIN, (INDEX)GRID_MAX);
|
|
iMaxZ = Clamp(iMaxZ, (INDEX)GRID_MIN, (INDEX)GRID_MAX);
|
|
}
|
|
|
|
// key calculations
|
|
static inline ULONG MakeCode(INDEX iX, INDEX iZ)
|
|
{
|
|
return (iX<<16)|(iZ&0xffff);
|
|
}
|
|
|
|
static inline INDEX MakeKey(INDEX iX, INDEX iZ)
|
|
{
|
|
//INDEX iKey = (iX+iZ)&(GRID_HASHTABLESIZE-1); // x+z
|
|
// use absolute x and z, swap upper and lower bits in z, xor x and z
|
|
INDEX iZ2 = abs(iZ);
|
|
INDEX iKey = (iZ2>>(GRID_HASHTABLESIZE_LOG2/2)) | (
|
|
(iZ2&(GRID_HASHTABLESIZE/2-1))<<(GRID_HASHTABLESIZE_LOG2/2));
|
|
iKey = iKey^abs(iX);
|
|
iKey = iKey&(GRID_HASHTABLESIZE-1);
|
|
return iKey;
|
|
}
|
|
|
|
static inline INDEX MakeKeyFromCode(ULONG ulCode)
|
|
{
|
|
INDEX iX = SLONG(ulCode)>>16;
|
|
INDEX iZ = SLONG(SWORD(ulCode&0xffff));
|
|
return MakeKey(iX, iZ);
|
|
}
|
|
|
|
// collision grid classes
|
|
class CGridCell {
|
|
public:
|
|
ULONG gc_ulCode; // 32 bit uid of the cell (from its coordinates in grid)
|
|
INDEX gc_iNextCell; // next cell with this hash code
|
|
INDEX gc_iFirstEntry; // first entry in this cell
|
|
};
|
|
class CGridEntry {
|
|
public:
|
|
CEntity *ge_penEntity; // entity pointed to
|
|
INDEX ge_iNextEntry; // next entry in same cell
|
|
};
|
|
|
|
class CCollisionGrid {
|
|
public:
|
|
CStaticArray<INDEX> cg_aiFirstCells; // first cell for each hash entry
|
|
CAllocationArray<CGridCell> cg_agcCells; // all cells
|
|
CAllocationArray<CGridEntry> cg_ageEntries; // all entries
|
|
|
|
CCollisionGrid(void);
|
|
~CCollisionGrid(void);
|
|
void Clear(void);
|
|
// create a new grid cell in given hash table entry
|
|
INDEX CreateCell(INDEX iKey, ULONG ulCode);
|
|
// remove a cell
|
|
void RemoveCell(INDEX igc);
|
|
// get grid cell for its coordinates
|
|
INDEX FindCell(INDEX iX, INDEX iZ, BOOL bCreate);
|
|
// add entry to a given cell
|
|
void AddEntry(INDEX igc, CEntity *pen);
|
|
// remove entry from a given cell
|
|
void RemoveEntry(INDEX igc, CEntity *pen);
|
|
};
|
|
|
|
|
|
// collision grid class implementation
|
|
|
|
CCollisionGrid::CCollisionGrid(void)
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
CCollisionGrid::~CCollisionGrid(void)
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
void CCollisionGrid::Clear(void)
|
|
{
|
|
cg_aiFirstCells.Clear();
|
|
cg_agcCells.Clear();
|
|
cg_ageEntries.Clear();
|
|
|
|
cg_aiFirstCells.New(GRID_HASHTABLESIZE);
|
|
cg_agcCells.SetAllocationStep(1024);
|
|
cg_ageEntries.SetAllocationStep(1024);
|
|
|
|
// mark all cells as unused
|
|
for(INDEX iKey=0; iKey<GRID_HASHTABLESIZE; iKey++) {
|
|
cg_aiFirstCells[iKey] = -1;
|
|
}
|
|
}
|
|
|
|
// create a new grid cell in given hash table entry
|
|
INDEX CCollisionGrid::CreateCell(INDEX iKey, ULONG ulCode)
|
|
{
|
|
// find an empty cell
|
|
INDEX igc = cg_agcCells.Allocate();
|
|
CGridCell &gc = cg_agcCells[igc];
|
|
|
|
// set up the cell
|
|
gc.gc_ulCode = ulCode;
|
|
gc.gc_iFirstEntry = -1;
|
|
|
|
// link it by hash key
|
|
gc.gc_iNextCell = cg_aiFirstCells[iKey];
|
|
cg_aiFirstCells[iKey] = igc;
|
|
|
|
return igc;
|
|
}
|
|
|
|
// remove a cell
|
|
void CCollisionGrid::RemoveCell(INDEX igc)
|
|
{
|
|
// get key of the cell
|
|
CGridCell &gc = cg_agcCells[igc];
|
|
INDEX iKey = MakeKeyFromCode(gc.gc_ulCode);
|
|
|
|
// find the cell's index pointer
|
|
INDEX *pigc = &cg_aiFirstCells[iKey];
|
|
ASSERT(*pigc>=0);
|
|
while(*pigc>=0) {
|
|
CGridCell &gc = cg_agcCells[*pigc];
|
|
if (*pigc==igc) {
|
|
*pigc = gc.gc_iNextCell;
|
|
gc.gc_iNextCell = -2;
|
|
gc.gc_iFirstEntry = -1;
|
|
gc.gc_ulCode = 0x12345678;
|
|
cg_agcCells.Free(igc);
|
|
return;
|
|
}
|
|
pigc = &gc.gc_iNextCell;
|
|
}
|
|
ASSERT(FALSE);
|
|
}
|
|
|
|
// get grid cell for its coordinates
|
|
INDEX CCollisionGrid::FindCell(INDEX iX, INDEX iZ, BOOL bCreate)
|
|
{
|
|
// make uid of the cell
|
|
ASSERT(iX>=GRID_MIN && iX<=GRID_MAX);
|
|
ASSERT(iZ>=GRID_MIN && iZ<=GRID_MAX);
|
|
ULONG ulCode = MakeCode(iX, iZ);
|
|
// get the hash key for the cell
|
|
INDEX iKey = MakeKey(iX, iZ); // x+z, use lower bits
|
|
ASSERT(iKey==MakeKeyFromCode(ulCode));
|
|
// find the cell in list of cells with that key
|
|
INDEX igcFound = -1;
|
|
for (INDEX igc=cg_aiFirstCells[iKey]; igc>=0; igc = cg_agcCells[igc].gc_iNextCell) {
|
|
if (cg_agcCells[igc].gc_ulCode==ulCode) {
|
|
igcFound = igc;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// if the cell is found
|
|
if (igcFound>=0) {
|
|
// use existing one
|
|
return igcFound;
|
|
// if the cell is not found
|
|
} else {
|
|
// if new one may be created
|
|
if (bCreate) {
|
|
// create a new one
|
|
return CreateCell(iKey, ulCode);
|
|
// if new one may not be created
|
|
} else {
|
|
// return nothing
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
// add entry to a given cell
|
|
void CCollisionGrid::AddEntry(INDEX igc, CEntity *pen)
|
|
{
|
|
// find an empty entry
|
|
INDEX ige = cg_ageEntries.Allocate();
|
|
CGridEntry &ge = cg_ageEntries[ige];
|
|
|
|
// init the entry and link it in its cell
|
|
ge.ge_penEntity = pen;
|
|
CGridCell &gc = cg_agcCells[igc];
|
|
ge.ge_iNextEntry = gc.gc_iFirstEntry;
|
|
gc.gc_iFirstEntry = ige;
|
|
}
|
|
|
|
// remove entry from a given cell
|
|
void CCollisionGrid::RemoveEntry(INDEX igc, CEntity *pen)
|
|
{
|
|
CGridCell &gc = cg_agcCells[igc];
|
|
|
|
// find the entry's index pointer
|
|
INDEX *pige = &gc.gc_iFirstEntry;
|
|
ASSERT(*pige>=0);
|
|
while(*pige>=0) {
|
|
CGridEntry &ge = cg_ageEntries[*pige];
|
|
if (ge.ge_penEntity==pen) {
|
|
// remove the entry from the list
|
|
cg_ageEntries.Free(*pige);
|
|
*pige = ge.ge_iNextEntry;
|
|
ge.ge_iNextEntry = -2;
|
|
ge.ge_penEntity = NULL;
|
|
// if the cell becomes empty
|
|
if (gc.gc_iFirstEntry<0) {
|
|
// remove the cell
|
|
RemoveCell(igc);
|
|
}
|
|
return;
|
|
}
|
|
pige = &ge.ge_iNextEntry;
|
|
}
|
|
ASSERT(FALSE);
|
|
}
|
|
|
|
|
|
|
|
/* Initialize collision grid. */
|
|
void CWorld::InitCollisionGrid(void)
|
|
{
|
|
wo_pcgCollisionGrid = new CCollisionGrid;
|
|
}
|
|
/* Destroy collision grid. */
|
|
void CWorld::DestroyCollisionGrid(void)
|
|
{
|
|
delete wo_pcgCollisionGrid;
|
|
wo_pcgCollisionGrid = NULL;
|
|
}
|
|
// clear collision grid
|
|
void CWorld::ClearCollisionGrid(void)
|
|
{
|
|
wo_pcgCollisionGrid->Clear();
|
|
}
|
|
|
|
|
|
/* Add an entity to cell(s) in collision grid. */
|
|
void CWorld::AddEntityToCollisionGrid(CEntity *pen, const FLOATaabbox3D &boxEntity)
|
|
{
|
|
_pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_ADDENTITYTOGRID);
|
|
// find grid coordinates
|
|
INDEX iMinX, iMaxX, iMinZ, iMaxZ;
|
|
BoxToGrid(boxEntity, iMinX, iMaxX, iMinZ, iMaxZ);
|
|
// for each cell spanned by the entity
|
|
for(INDEX iX=iMinX; iX<=iMaxX; iX++) {
|
|
for(INDEX iZ=iMinZ; iZ<=iMaxZ; iZ++) {
|
|
// find that cell
|
|
INDEX igc = wo_pcgCollisionGrid->FindCell(iX, iZ, TRUE);
|
|
// add the entity to the cell
|
|
wo_pcgCollisionGrid->AddEntry(igc, pen);
|
|
}
|
|
}
|
|
_pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_ADDENTITYTOGRID);
|
|
}
|
|
|
|
|
|
/* Remove an entity from cell(s) in collision grid. */
|
|
void CWorld::RemoveEntityFromCollisionGrid(CEntity *pen, const FLOATaabbox3D &boxEntity)
|
|
{
|
|
_pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_REMENTITYFROMGRID);
|
|
// find grid coordinates
|
|
INDEX iMinX, iMaxX, iMinZ, iMaxZ;
|
|
BoxToGrid(boxEntity, iMinX, iMaxX, iMinZ, iMaxZ);
|
|
// for each cell spanned by the entity
|
|
for(INDEX iX=iMinX; iX<=iMaxX; iX++) {
|
|
for(INDEX iZ=iMinZ; iZ<=iMaxZ; iZ++) {
|
|
// find that cell
|
|
INDEX igc = wo_pcgCollisionGrid->FindCell(iX, iZ, FALSE);
|
|
ASSERT(igc>=0);
|
|
// remove the entity from the cell
|
|
if (igc>=0) {
|
|
wo_pcgCollisionGrid->RemoveEntry(igc, pen);
|
|
}
|
|
}
|
|
}
|
|
_pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_REMENTITYFROMGRID);
|
|
}
|
|
|
|
/* Move an entity inside cell(s) in collision grid. */
|
|
void CWorld::MoveEntityInCollisionGrid(CEntity *pen,
|
|
const FLOATaabbox3D &boxOld, const FLOATaabbox3D &boxNew)
|
|
{
|
|
_pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_MOVEENTITYINGRID);
|
|
|
|
// find grid coordinates
|
|
INDEX iOldMinX, iOldMaxX, iOldMinZ, iOldMaxZ;
|
|
BoxToGrid(boxOld, iOldMinX, iOldMaxX, iOldMinZ, iOldMaxZ);
|
|
INDEX iNewMinX, iNewMaxX, iNewMinZ, iNewMaxZ;
|
|
BoxToGrid(boxNew, iNewMinX, iNewMaxX, iNewMinZ, iNewMaxZ);
|
|
|
|
// for each cell spanned by the entity before moving but not after moving
|
|
{for(INDEX iX=iOldMinX; iX<=iOldMaxX; iX++) {
|
|
for(INDEX iZ=iOldMinZ; iZ<=iOldMaxZ; iZ++) {
|
|
if (iX>=iNewMinX && iX<=iNewMaxX
|
|
&&iZ>=iNewMinZ && iZ<=iNewMaxZ) {
|
|
continue;
|
|
}
|
|
// find that cell
|
|
INDEX igc = wo_pcgCollisionGrid->FindCell(iX, iZ, FALSE);
|
|
ASSERT(igc>=0);
|
|
// remove the entity from the cell
|
|
if (igc>=0) {
|
|
wo_pcgCollisionGrid->RemoveEntry(igc, pen);
|
|
}
|
|
}
|
|
}}
|
|
|
|
// for each cell spanned by the entity after moving but not before moving
|
|
{for(INDEX iX=iNewMinX; iX<=iNewMaxX; iX++) {
|
|
for(INDEX iZ=iNewMinZ; iZ<=iNewMaxZ; iZ++) {
|
|
if (iX>=iOldMinX && iX<=iOldMaxX
|
|
&&iZ>=iOldMinZ && iZ<=iOldMaxZ) {
|
|
continue;
|
|
}
|
|
// find that cell
|
|
INDEX igc = wo_pcgCollisionGrid->FindCell(iX, iZ, TRUE);
|
|
wo_pcgCollisionGrid->AddEntry(igc, pen);
|
|
}
|
|
}}
|
|
_pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_MOVEENTITYINGRID);
|
|
}
|
|
|
|
|
|
/* Find all entities in collision grid near given box. */
|
|
void CWorld::FindEntitiesNearBox(const FLOATaabbox3D &boxNear,
|
|
CStaticStackArray<CEntity*> &apenNearEntities)
|
|
{
|
|
|
|
#if DEBUG_COLLIDEWITHALL
|
|
apenNearEntities.PopAll();
|
|
// for each entity
|
|
{FOREACHINDYNAMICCONTAINER(wo_cenEntities, CEntity, iten) {
|
|
CEntity &en = *iten;
|
|
if (en.GetRenderType()==CEntity::RT_MODEL && en.en_pciCollisionInfo!=NULL) {
|
|
apenNearEntities.Push() = &en;
|
|
}
|
|
}}
|
|
|
|
return;
|
|
#endif
|
|
|
|
_pfPhysicsProfile.StartTimer(CPhysicsProfile::PTI_FINDENTITIESNEARBOX);
|
|
_pfPhysicsProfile.IncrementCounter(CPhysicsProfile::PCI_FINDINGNEARENTITIES);
|
|
|
|
// find grid coordinates
|
|
INDEX iMinX, iMaxX, iMinZ, iMaxZ;
|
|
BoxToGrid(boxNear, iMinX, iMaxX, iMinZ, iMaxZ);
|
|
apenNearEntities.PopAll();
|
|
|
|
// for each cell spanned by the box
|
|
{for(INDEX iX=iMinX; iX<=iMaxX; iX++) {
|
|
for(INDEX iZ=iMinZ; iZ<=iMaxZ; iZ++) {
|
|
_pfPhysicsProfile.IncrementCounter(CPhysicsProfile::PCI_NEARCELLSFOUND);
|
|
// find that cell
|
|
INDEX igc = wo_pcgCollisionGrid->FindCell(iX, iZ, FALSE);
|
|
// if the cell is empty
|
|
if (igc<0) {
|
|
// skip it
|
|
continue;
|
|
}
|
|
_pfPhysicsProfile.IncrementCounter(CPhysicsProfile::PCI_NEAROCCUPIEDCELLSFOUND);
|
|
// for each entity in the cell
|
|
for(INDEX iEntry = wo_pcgCollisionGrid->cg_agcCells[igc].gc_iFirstEntry;
|
|
iEntry>=0;
|
|
iEntry = wo_pcgCollisionGrid->cg_ageEntries[iEntry].ge_iNextEntry) {
|
|
CEntity *penEntity = wo_pcgCollisionGrid->cg_ageEntries[iEntry].ge_penEntity;
|
|
// if it is not already found
|
|
if (!(penEntity->en_ulFlags&ENF_FOUNDINGRIDSEARCH)) {
|
|
// add it
|
|
apenNearEntities.Push() = penEntity;
|
|
// mark it as found
|
|
penEntity->en_ulFlags|=ENF_FOUNDINGRIDSEARCH;
|
|
}
|
|
}
|
|
}
|
|
}}
|
|
|
|
|
|
_pfPhysicsProfile.IncrementCounter(
|
|
CPhysicsProfile::PCI_NEARENTITIESFOUND, apenNearEntities.Count());
|
|
// for each of the found entities
|
|
for(INDEX ienFound=0; ienFound<apenNearEntities.Count(); ienFound++) {
|
|
// clear found flag
|
|
apenNearEntities[ienFound]->en_ulFlags&=~ENF_FOUNDINGRIDSEARCH;
|
|
}
|
|
_pfPhysicsProfile.StopTimer(CPhysicsProfile::PTI_FINDENTITIESNEARBOX);
|
|
}
|
|
|
|
|
|
|
|
// get amount of memory used by this object
|
|
extern SLONG GetCollisionGridMemory( CCollisionGrid *pcg)
|
|
{
|
|
// no collision grid?
|
|
if( pcg==NULL) return 0;
|
|
|
|
// phew, it's here!
|
|
SLONG slUsedMemory = pcg->cg_aiFirstCells.Count() * sizeof(INDEX);
|
|
slUsedMemory += pcg->cg_agcCells.Count() * sizeof(CGridCell);
|
|
slUsedMemory += pcg->cg_ageEntries.Count() * sizeof(CGridEntry);
|
|
slUsedMemory += pcg->cg_agcCells.aa_aiFreeElements.sa_Count * sizeof(INDEX);
|
|
slUsedMemory += pcg->cg_ageEntries.aa_aiFreeElements.sa_Count * sizeof(INDEX);
|
|
return slUsedMemory;
|
|
} |