4coder/test_data/lots_of_files/handmade_world.cpp

321 lines
9.7 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);
}
}