321 lines
9.4 KiB
C++
321 lines
9.4 KiB
C++
|
/* ========================================================================
|
||
|
$File: $
|
||
|
$Date: $
|
||
|
$Revision: $
|
||
|
$Creator: Casey Muratori $
|
||
|
$Notice: (C) Copyright 2015 by Molly Rocket, Inc. All Rights Reserved. $
|
||
|
======================================================================== */
|
||
|
|
||
|
// TODO(casey): Think about what the real safe margin is!
|
||
|
#define TILE_CHUNK_SAFE_MARGIN (INT32_MAX/64)
|
||
|
#define TILE_CHUNK_UNINITIALIZED INT32_MAX
|
||
|
|
||
|
#define TILES_PER_CHUNK 8
|
||
|
|
||
|
inline world_position
|
||
|
NullPosition(void)
|
||
|
{
|
||
|
world_position Result = {};
|
||
|
|
||
|
Result.ChunkX = TILE_CHUNK_UNINITIALIZED;
|
||
|
|
||
|
return(Result);
|
||
|
}
|
||
|
|
||
|
inline bool32
|
||
|
IsValid(world_position P)
|
||
|
{
|
||
|
bool32 Result = (P.ChunkX != TILE_CHUNK_UNINITIALIZED);
|
||
|
return(Result);
|
||
|
}
|
||
|
|
||
|
inline bool32
|
||
|
IsCanonical(real32 ChunkDim, real32 TileRel)
|
||
|
{
|
||
|
// TODO(casey): Fix floating point math so this can be exact?
|
||
|
real32 Epsilon = 0.01f;
|
||
|
bool32 Result = ((TileRel >= -(0.5f*ChunkDim + Epsilon)) &&
|
||
|
(TileRel <= (0.5f*ChunkDim + Epsilon)));
|
||
|
|
||
|
return(Result);
|
||
|
}
|
||
|
|
||
|
inline bool32
|
||
|
IsCanonical(world *World, v3 Offset)
|
||
|
{
|
||
|
bool32 Result = (IsCanonical(World->ChunkDimInMeters.x, Offset.x) &&
|
||
|
IsCanonical(World->ChunkDimInMeters.y, Offset.y) &&
|
||
|
IsCanonical(World->ChunkDimInMeters.z, Offset.z));
|
||
|
|
||
|
return(Result);
|
||
|
}
|
||
|
|
||
|
inline bool32
|
||
|
AreInSameChunk(world *World, world_position *A, world_position *B)
|
||
|
{
|
||
|
Assert(IsCanonical(World, A->Offset_));
|
||
|
Assert(IsCanonical(World, B->Offset_));
|
||
|
|
||
|
bool32 Result = ((A->ChunkX == B->ChunkX) &&
|
||
|
(A->ChunkY == B->ChunkY) &&
|
||
|
(A->ChunkZ == B->ChunkZ));
|
||
|
|
||
|
return(Result);
|
||
|
}
|
||
|
|
||
|
inline world_chunk *
|
||
|
GetWorldChunk(world *World, int32 ChunkX, int32 ChunkY, int32 ChunkZ,
|
||
|
memory_arena *Arena = 0)
|
||
|
{
|
||
|
TIMED_FUNCTION();
|
||
|
|
||
|
Assert(ChunkX > -TILE_CHUNK_SAFE_MARGIN);
|
||
|
Assert(ChunkY > -TILE_CHUNK_SAFE_MARGIN);
|
||
|
Assert(ChunkZ > -TILE_CHUNK_SAFE_MARGIN);
|
||
|
Assert(ChunkX < TILE_CHUNK_SAFE_MARGIN);
|
||
|
Assert(ChunkY < TILE_CHUNK_SAFE_MARGIN);
|
||
|
Assert(ChunkZ < TILE_CHUNK_SAFE_MARGIN);
|
||
|
|
||
|
// TODO(casey): BETTER HASH FUNCTION!!!!
|
||
|
uint32 HashValue = 19*ChunkX + 7*ChunkY + 3*ChunkZ;
|
||
|
uint32 HashSlot = HashValue & (ArrayCount(World->ChunkHash) - 1);
|
||
|
Assert(HashSlot < ArrayCount(World->ChunkHash));
|
||
|
|
||
|
world_chunk *Chunk = World->ChunkHash + HashSlot;
|
||
|
do
|
||
|
{
|
||
|
if((ChunkX == Chunk->ChunkX) &&
|
||
|
(ChunkY == Chunk->ChunkY) &&
|
||
|
(ChunkZ == Chunk->ChunkZ))
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if(Arena && (Chunk->ChunkX != TILE_CHUNK_UNINITIALIZED) && (!Chunk->NextInHash))
|
||
|
{
|
||
|
Chunk->NextInHash = PushStruct(Arena, world_chunk);
|
||
|
Chunk = Chunk->NextInHash;
|
||
|
Chunk->ChunkX = TILE_CHUNK_UNINITIALIZED;
|
||
|
}
|
||
|
|
||
|
if(Arena && (Chunk->ChunkX == TILE_CHUNK_UNINITIALIZED))
|
||
|
{
|
||
|
Chunk->ChunkX = ChunkX;
|
||
|
Chunk->ChunkY = ChunkY;
|
||
|
Chunk->ChunkZ = ChunkZ;
|
||
|
|
||
|
Chunk->NextInHash = 0;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
Chunk = Chunk->NextInHash;
|
||
|
} while(Chunk);
|
||
|
|
||
|
return(Chunk);
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
InitializeWorld(world *World, v3 ChunkDimInMeters)
|
||
|
{
|
||
|
World->ChunkDimInMeters = ChunkDimInMeters;
|
||
|
World->FirstFree = 0;
|
||
|
|
||
|
for(uint32 ChunkIndex = 0;
|
||
|
ChunkIndex < ArrayCount(World->ChunkHash);
|
||
|
++ChunkIndex)
|
||
|
{
|
||
|
World->ChunkHash[ChunkIndex].ChunkX = TILE_CHUNK_UNINITIALIZED;
|
||
|
World->ChunkHash[ChunkIndex].FirstBlock.EntityCount = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
inline void
|
||
|
RecanonicalizeCoord(real32 ChunkDim, int32 *Tile, real32 *TileRel)
|
||
|
{
|
||
|
// TODO(casey): Need to do something that doesn't use the divide/multiply method
|
||
|
// for recanonicalizing because this can end up rounding back on to the tile
|
||
|
// you just came from.
|
||
|
|
||
|
// NOTE(casey): Wrapping IS NOT ALLOWED, so all coordinates are assumed to be
|
||
|
// within the safe margin!
|
||
|
// TODO(casey): Assert that we are nowhere near the edges of the world.
|
||
|
|
||
|
int32 Offset = RoundReal32ToInt32(*TileRel / ChunkDim);
|
||
|
*Tile += Offset;
|
||
|
*TileRel -= Offset*ChunkDim;
|
||
|
|
||
|
Assert(IsCanonical(ChunkDim, *TileRel));
|
||
|
}
|
||
|
|
||
|
inline world_position
|
||
|
MapIntoChunkSpace(world *World, world_position BasePos, v3 Offset)
|
||
|
{
|
||
|
world_position Result = BasePos;
|
||
|
|
||
|
Result.Offset_ += Offset;
|
||
|
RecanonicalizeCoord(World->ChunkDimInMeters.x, &Result.ChunkX, &Result.Offset_.x);
|
||
|
RecanonicalizeCoord(World->ChunkDimInMeters.y, &Result.ChunkY, &Result.Offset_.y);
|
||
|
RecanonicalizeCoord(World->ChunkDimInMeters.z, &Result.ChunkZ, &Result.Offset_.z);
|
||
|
|
||
|
return(Result);
|
||
|
}
|
||
|
|
||
|
inline v3
|
||
|
Subtract(world *World, world_position *A, world_position *B)
|
||
|
{
|
||
|
v3 dTile = {(real32)A->ChunkX - (real32)B->ChunkX,
|
||
|
(real32)A->ChunkY - (real32)B->ChunkY,
|
||
|
(real32)A->ChunkZ - (real32)B->ChunkZ};
|
||
|
|
||
|
v3 Result = Hadamard(World->ChunkDimInMeters, dTile) + (A->Offset_ - B->Offset_);
|
||
|
|
||
|
return(Result);
|
||
|
}
|
||
|
|
||
|
inline world_position
|
||
|
CenteredChunkPoint(uint32 ChunkX, uint32 ChunkY, uint32 ChunkZ)
|
||
|
{
|
||
|
world_position Result = {};
|
||
|
|
||
|
Result.ChunkX = ChunkX;
|
||
|
Result.ChunkY = ChunkY;
|
||
|
Result.ChunkZ = ChunkZ;
|
||
|
|
||
|
return(Result);
|
||
|
}
|
||
|
|
||
|
inline world_position
|
||
|
CenteredChunkPoint(world_chunk *Chunk)
|
||
|
{
|
||
|
world_position Result = CenteredChunkPoint(Chunk->ChunkX, Chunk->ChunkY, Chunk->ChunkZ);
|
||
|
|
||
|
return(Result);
|
||
|
}
|
||
|
|
||
|
inline void
|
||
|
ChangeEntityLocationRaw(memory_arena *Arena, world *World, uint32 LowEntityIndex,
|
||
|
world_position *OldP, world_position *NewP)
|
||
|
{
|
||
|
TIMED_FUNCTION();
|
||
|
|
||
|
// TODO(casey): If this moves an entity into the camera bounds, should it automatically
|
||
|
// go into the high set immediately?
|
||
|
// If it moves _out_ of the camera bounds, should it be removed from the high set
|
||
|
// immediately?
|
||
|
|
||
|
Assert(!OldP || IsValid(*OldP));
|
||
|
Assert(!NewP || IsValid(*NewP));
|
||
|
|
||
|
if(OldP && NewP && AreInSameChunk(World, OldP, NewP))
|
||
|
{
|
||
|
// NOTE(casey): Leave entity where it is
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if(OldP)
|
||
|
{
|
||
|
// NOTE(casey): Pull the entity out of its old entity block
|
||
|
world_chunk *Chunk = GetWorldChunk(World, OldP->ChunkX, OldP->ChunkY, OldP->ChunkZ);
|
||
|
Assert(Chunk);
|
||
|
if(Chunk)
|
||
|
{
|
||
|
bool32 NotFound = true;
|
||
|
world_entity_block *FirstBlock = &Chunk->FirstBlock;
|
||
|
for(world_entity_block *Block = FirstBlock;
|
||
|
Block && NotFound;
|
||
|
Block = Block->Next)
|
||
|
{
|
||
|
for(uint32 Index = 0;
|
||
|
(Index < Block->EntityCount) && NotFound;
|
||
|
++Index)
|
||
|
{
|
||
|
if(Block->LowEntityIndex[Index] == LowEntityIndex)
|
||
|
{
|
||
|
Assert(FirstBlock->EntityCount > 0);
|
||
|
Block->LowEntityIndex[Index] =
|
||
|
FirstBlock->LowEntityIndex[--FirstBlock->EntityCount];
|
||
|
if(FirstBlock->EntityCount == 0)
|
||
|
{
|
||
|
if(FirstBlock->Next)
|
||
|
{
|
||
|
world_entity_block *NextBlock = FirstBlock->Next;
|
||
|
*FirstBlock = *NextBlock;
|
||
|
|
||
|
NextBlock->Next = World->FirstFree;
|
||
|
World->FirstFree = NextBlock;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
NotFound = false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if(NewP)
|
||
|
{
|
||
|
// NOTE(casey): Insert the entity into its new entity block
|
||
|
world_chunk *Chunk = GetWorldChunk(World, NewP->ChunkX, NewP->ChunkY, NewP->ChunkZ, Arena);
|
||
|
Assert(Chunk);
|
||
|
|
||
|
world_entity_block *Block = &Chunk->FirstBlock;
|
||
|
if(Block->EntityCount == ArrayCount(Block->LowEntityIndex))
|
||
|
{
|
||
|
// NOTE(casey): We're out of room, get a new block!
|
||
|
world_entity_block *OldBlock = World->FirstFree;
|
||
|
if(OldBlock)
|
||
|
{
|
||
|
World->FirstFree = OldBlock->Next;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
OldBlock = PushStruct(Arena, world_entity_block);
|
||
|
}
|
||
|
|
||
|
*OldBlock = *Block;
|
||
|
Block->Next = OldBlock;
|
||
|
Block->EntityCount = 0;
|
||
|
}
|
||
|
|
||
|
Assert(Block->EntityCount < ArrayCount(Block->LowEntityIndex));
|
||
|
Block->LowEntityIndex[Block->EntityCount++] = LowEntityIndex;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
internal void
|
||
|
ChangeEntityLocation(memory_arena *Arena, world *World,
|
||
|
uint32 LowEntityIndex, low_entity *LowEntity,
|
||
|
world_position NewPInit)
|
||
|
{
|
||
|
TIMED_FUNCTION();
|
||
|
|
||
|
world_position *OldP = 0;
|
||
|
world_position *NewP = 0;
|
||
|
|
||
|
if(!IsSet(&LowEntity->Sim, EntityFlag_Nonspatial) && IsValid(LowEntity->P))
|
||
|
{
|
||
|
OldP = &LowEntity->P;
|
||
|
}
|
||
|
|
||
|
if(IsValid(NewPInit))
|
||
|
{
|
||
|
NewP = &NewPInit;
|
||
|
}
|
||
|
|
||
|
ChangeEntityLocationRaw(Arena, World, LowEntityIndex, OldP, NewP);
|
||
|
|
||
|
if(NewP)
|
||
|
{
|
||
|
LowEntity->P = *NewP;
|
||
|
ClearFlags(&LowEntity->Sim, EntityFlag_Nonspatial);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
LowEntity->P = NullPosition();
|
||
|
AddFlags(&LowEntity->Sim, EntityFlag_Nonspatial);
|
||
|
}
|
||
|
}
|
||
|
|