4coder/test_data/lots_of_files/handmade_sim_region.cpp

792 lines
28 KiB
C++

/* ========================================================================
$File: $
$Date: $
$Revision: $
$Creator: Casey Muratori $
$Notice: (C) Copyright 2015 by Molly Rocket, Inc. All Rights Reserved. $
======================================================================== */
internal sim_entity_hash *
GetHashFromStorageIndex(sim_region *SimRegion, uint32 StorageIndex)
{
Assert(StorageIndex);
sim_entity_hash *Result = 0;
uint32 HashValue = StorageIndex;
for(uint32 Offset = 0;
Offset < ArrayCount(SimRegion->Hash);
++Offset)
{
uint32 HashMask = (ArrayCount(SimRegion->Hash) - 1);
uint32 HashIndex = ((HashValue + Offset) & HashMask);
sim_entity_hash *Entry = SimRegion->Hash + HashIndex;
if((Entry->Index == 0) || (Entry->Index == StorageIndex))
{
Result = Entry;
break;
}
}
return(Result);
}
inline sim_entity *
GetEntityByStorageIndex(sim_region *SimRegion, uint32 StorageIndex)
{
sim_entity_hash *Entry = GetHashFromStorageIndex(SimRegion, StorageIndex);
sim_entity *Result = Entry->Ptr;
return(Result);
}
inline v3
GetSimSpaceP(sim_region *SimRegion, low_entity *Stored)
{
// NOTE(casey): Map the entity into camera space
// TODO(casey): Do we want to set this to signaling NAN in
// debug mode to make sure nobody ever uses the position
// of a nonspatial entity?
v3 Result = InvalidP;
if(!IsSet(&Stored->Sim, EntityFlag_Nonspatial))
{
Result = Subtract(SimRegion->World, &Stored->P, &SimRegion->Origin);
}
return(Result);
}
internal sim_entity *
AddEntity(game_state *GameState, sim_region *SimRegion, uint32 StorageIndex, low_entity *Source, v3 *SimP);
inline void
LoadEntityReference(game_state *GameState, sim_region *SimRegion, entity_reference *Ref)
{
if(Ref->Index)
{
sim_entity_hash *Entry = GetHashFromStorageIndex(SimRegion, Ref->Index);
if(Entry->Ptr == 0)
{
Entry->Index = Ref->Index;
low_entity *LowEntity = GetLowEntity(GameState, Ref->Index);
v3 P = GetSimSpaceP(SimRegion, LowEntity);
Entry->Ptr = AddEntity(GameState, SimRegion, Ref->Index, LowEntity, &P);
}
Ref->Ptr = Entry->Ptr;
}
}
inline void
StoreEntityReference(entity_reference *Ref)
{
if(Ref->Ptr != 0)
{
Ref->Index = Ref->Ptr->StorageIndex;
}
}
internal sim_entity *
AddEntityRaw(game_state *GameState, sim_region *SimRegion, uint32 StorageIndex, low_entity *Source)
{
TIMED_FUNCTION();
Assert(StorageIndex);
sim_entity *Entity = 0;
sim_entity_hash *Entry = GetHashFromStorageIndex(SimRegion, StorageIndex);
if(Entry->Ptr == 0)
{
if(SimRegion->EntityCount < SimRegion->MaxEntityCount)
{
Entity = SimRegion->Entities + SimRegion->EntityCount++;
Entry->Index = StorageIndex;
Entry->Ptr = Entity;
if(Source)
{
// TODO(casey): This should really be a decompression step, not
// a copy!
*Entity = Source->Sim;
LoadEntityReference(GameState, SimRegion, &Entity->Sword);
Assert(!IsSet(&Source->Sim, EntityFlag_Simming));
AddFlags(&Source->Sim, EntityFlag_Simming);
}
Entity->StorageIndex = StorageIndex;
Entity->Updatable = false;
}
else
{
InvalidCodePath;
}
}
return(Entity);
}
inline bool32
EntityOverlapsRectangle(v3 P, sim_entity_collision_volume Volume, rectangle3 Rect)
{
rectangle3 Grown = AddRadiusTo(Rect, 0.5f*Volume.Dim);
bool32 Result = IsInRectangle(Grown, P + Volume.OffsetP);
return(Result);
}
internal sim_entity *
AddEntity(game_state *GameState, sim_region *SimRegion, uint32 StorageIndex, low_entity *Source, v3 *SimP)
{
sim_entity *Dest = AddEntityRaw(GameState, SimRegion, StorageIndex, Source);
if(Dest)
{
if(SimP)
{
Dest->P = *SimP;
Dest->Updatable = EntityOverlapsRectangle(Dest->P, Dest->Collision->TotalVolume, SimRegion->UpdatableBounds);
}
else
{
Dest->P = GetSimSpaceP(SimRegion, Source);
}
}
return(Dest);
}
internal sim_region *
BeginSim(memory_arena *SimArena, game_state *GameState, world *World, world_position Origin, rectangle3 Bounds, real32 dt)
{
TIMED_FUNCTION();
// TODO(casey): If entities were stored in the world, we wouldn't need the game state here!
sim_region *SimRegion = PushStruct(SimArena, sim_region);
ZeroStruct(SimRegion->Hash);
// TODO(casey): Try to make these get enforced more rigorously
// TODO(casey): Perhaps try using a dual system here, where we support
// entities larger than the max entity radius by adding them multiple times
// to the spatial partition?
SimRegion->MaxEntityRadius = 5.0f;
SimRegion->MaxEntityVelocity = 30.0f;
real32 UpdateSafetyMargin = SimRegion->MaxEntityRadius + dt*SimRegion->MaxEntityVelocity;
real32 UpdateSafetyMarginZ = 1.0f;
SimRegion->World = World;
SimRegion->Origin = Origin;
SimRegion->UpdatableBounds = AddRadiusTo(Bounds, V3(SimRegion->MaxEntityRadius,
SimRegion->MaxEntityRadius,
0.0f));
SimRegion->Bounds = AddRadiusTo(SimRegion->UpdatableBounds,
V3(UpdateSafetyMargin, UpdateSafetyMargin, UpdateSafetyMarginZ));
// TODO(casey): Need to be more specific about entity counts
SimRegion->MaxEntityCount = 4096;
SimRegion->EntityCount = 0;
SimRegion->Entities = PushArray(SimArena, SimRegion->MaxEntityCount, sim_entity);
world_position MinChunkP = MapIntoChunkSpace(World, SimRegion->Origin, GetMinCorner(SimRegion->Bounds));
world_position MaxChunkP = MapIntoChunkSpace(World, SimRegion->Origin, GetMaxCorner(SimRegion->Bounds));
for(int32 ChunkZ = MinChunkP.ChunkZ;
ChunkZ <= MaxChunkP.ChunkZ;
++ChunkZ)
{
for(int32 ChunkY = MinChunkP.ChunkY;
ChunkY <= MaxChunkP.ChunkY;
++ChunkY)
{
for(int32 ChunkX = MinChunkP.ChunkX;
ChunkX <= MaxChunkP.ChunkX;
++ChunkX)
{
world_chunk *Chunk = GetWorldChunk(World, ChunkX, ChunkY, ChunkZ);
if(Chunk)
{
for(world_entity_block *Block = &Chunk->FirstBlock;
Block;
Block = Block->Next)
{
for(uint32 EntityIndexIndex = 0;
EntityIndexIndex < Block->EntityCount;
++EntityIndexIndex)
{
uint32 LowEntityIndex = Block->LowEntityIndex[EntityIndexIndex];
low_entity *Low = GameState->LowEntities + LowEntityIndex;
if(!IsSet(&Low->Sim, EntityFlag_Nonspatial))
{
v3 SimSpaceP = GetSimSpaceP(SimRegion, Low);
if(EntityOverlapsRectangle(SimSpaceP, Low->Sim.Collision->TotalVolume, SimRegion->Bounds))
{
AddEntity(GameState, SimRegion, LowEntityIndex, Low, &SimSpaceP);
}
}
}
}
}
}
}
}
return(SimRegion);
}
internal void
EndSim(sim_region *Region, game_state *GameState)
{
TIMED_FUNCTION();
// TODO(casey): Maybe don't take a game state here, low entities should be stored
// in the world??
sim_entity *Entity = Region->Entities;
for(uint32 EntityIndex = 0;
EntityIndex < Region->EntityCount;
++EntityIndex, ++Entity)
{
low_entity *Stored = GameState->LowEntities + Entity->StorageIndex;
Assert(IsSet(&Stored->Sim, EntityFlag_Simming));
Stored->Sim = *Entity;
Assert(!IsSet(&Stored->Sim, EntityFlag_Simming));
StoreEntityReference(&Stored->Sim.Sword);
// TODO(casey): Save state back to the stored entity, once high entities
// do state decompression, etc.
world_position NewP = IsSet(Entity, EntityFlag_Nonspatial) ?
NullPosition() :
MapIntoChunkSpace(GameState->World, Region->Origin, Entity->P);
ChangeEntityLocation(&GameState->WorldArena, GameState->World, Entity->StorageIndex,
Stored, NewP);
if(Entity->StorageIndex == GameState->CameraFollowingEntityIndex)
{
world_position NewCameraP = GameState->CameraP;
NewCameraP.ChunkZ = Stored->P.ChunkZ;
DEBUG_IF(Renderer_Camera_RoomBased)
{
if(Entity->P.x > (9.0f))
{
NewCameraP = MapIntoChunkSpace(GameState->World, NewCameraP, V3(18.0f, 0.0f, 0.0f));
}
if(Entity->P.x < -(9.0f))
{
NewCameraP = MapIntoChunkSpace(GameState->World, NewCameraP, V3(-18.0f, 0.0f, 0.0f));
}
if(Entity->P.y > (5.0f))
{
NewCameraP = MapIntoChunkSpace(GameState->World, NewCameraP, V3(18.0f, 10.0f, 0.0f));
}
if(Entity->P.y < -(5.0f))
{
NewCameraP = MapIntoChunkSpace(GameState->World, NewCameraP, V3(0.0f, -10.0f, 0.0f));
}
}
else
{
// real32 CamZOffset = NewCameraP.Offset_.z;
NewCameraP = Stored->P;
// NewCameraP.Offset_.z = CamZOffset;
}
GameState->CameraP = NewCameraP;
}
}
}
struct test_wall
{
real32 X;
real32 RelX;
real32 RelY;
real32 DeltaX;
real32 DeltaY;
real32 MinY;
real32 MaxY;
v3 Normal;
};
internal bool32
TestWall(real32 WallX, real32 RelX, real32 RelY, real32 PlayerDeltaX, real32 PlayerDeltaY,
real32 *tMin, real32 MinY, real32 MaxY)
{
bool32 Hit = false;
real32 tEpsilon = 0.001f;
if(PlayerDeltaX != 0.0f)
{
real32 tResult = (WallX - RelX) / PlayerDeltaX;
real32 Y = RelY + tResult*PlayerDeltaY;
if((tResult >= 0.0f) && (*tMin > tResult))
{
if((Y >= MinY) && (Y <= MaxY))
{
*tMin = Maximum(0.0f, tResult - tEpsilon);
Hit = true;
}
}
}
return(Hit);
}
internal bool32
CanCollide(game_state *GameState, sim_entity *A, sim_entity *B)
{
bool32 Result = false;
if(A != B)
{
if(A->StorageIndex > B->StorageIndex)
{
sim_entity *Temp = A;
A = B;
B = Temp;
}
if(IsSet(A, EntityFlag_Collides) && IsSet(B, EntityFlag_Collides))
{
if(!IsSet(A, EntityFlag_Nonspatial) &&
!IsSet(B, EntityFlag_Nonspatial))
{
// TODO(casey): Property-based logic goes here
Result = true;
}
// TODO(casey): BETTER HASH FUNCTION
uint32 HashBucket = A->StorageIndex & (ArrayCount(GameState->CollisionRuleHash) - 1);
for(pairwise_collision_rule *Rule = GameState->CollisionRuleHash[HashBucket];
Rule;
Rule = Rule->NextInHash)
{
if((Rule->StorageIndexA == A->StorageIndex) &&
(Rule->StorageIndexB == B->StorageIndex))
{
Result = Rule->CanCollide;
break;
}
}
}
}
return(Result);
}
internal bool32
HandleCollision(game_state *GameState, sim_entity *A, sim_entity *B)
{
bool32 StopsOnCollision = false;
if(A->Type == EntityType_Sword)
{
AddCollisionRule(GameState, A->StorageIndex, B->StorageIndex, false);
StopsOnCollision = false;
}
else
{
StopsOnCollision = true;
}
if(A->Type > B->Type)
{
sim_entity *Temp = A;
A = B;
B = Temp;
}
if((A->Type == EntityType_Monstar) &&
(B->Type == EntityType_Sword))
{
if(A->HitPointMax > 0)
{
--A->HitPointMax;
}
}
// TODO(casey): Stairs
// Entity->AbsTileZ += HitLow->dAbsTileZ;
return(StopsOnCollision);
}
internal bool32
CanOverlap(game_state *GameState, sim_entity *Mover, sim_entity *Region)
{
bool32 Result = false;
if(Mover != Region)
{
if(Region->Type == EntityType_Stairwell)
{
Result = true;
}
}
return(Result);
}
internal void
HandleOverlap(game_state *GameState, sim_entity *Mover, sim_entity *Region, real32 dt,
real32 *Ground)
{
if(Region->Type == EntityType_Stairwell)
{
*Ground = GetStairGround(Region, GetEntityGroundPoint(Mover));
}
}
internal bool32
SpeculativeCollide(sim_entity *Mover, sim_entity *Region, v3 TestP)
{
TIMED_FUNCTION();
bool32 Result = true;
if(Region->Type == EntityType_Stairwell)
{
// TODO(casey): Needs work :)
real32 StepHeight = 0.1f;
#if 0
Result = ((AbsoluteValue(GetEntityGroundPoint(Mover).z - Ground) > StepHeight) ||
((Bary.y > 0.1f) && (Bary.y < 0.9f)));
#endif
v3 MoverGroundPoint = GetEntityGroundPoint(Mover, TestP);
real32 Ground = GetStairGround(Region, MoverGroundPoint);
Result = (AbsoluteValue(MoverGroundPoint.z - Ground) > StepHeight);
}
return(Result);
}
internal bool32
EntitiesOverlap(sim_entity *Entity, sim_entity *TestEntity, v3 Epsilon = V3(0, 0, 0))
{
TIMED_FUNCTION();
bool32 Result = false;
for(uint32 VolumeIndex = 0;
!Result && (VolumeIndex < Entity->Collision->VolumeCount);
++VolumeIndex)
{
sim_entity_collision_volume *Volume = Entity->Collision->Volumes + VolumeIndex;
for(uint32 TestVolumeIndex = 0;
!Result && (TestVolumeIndex < TestEntity->Collision->VolumeCount);
++TestVolumeIndex)
{
sim_entity_collision_volume *TestVolume = TestEntity->Collision->Volumes + TestVolumeIndex;
rectangle3 EntityRect = RectCenterDim(Entity->P + Volume->OffsetP, Volume->Dim + Epsilon);
rectangle3 TestEntityRect = RectCenterDim(TestEntity->P + TestVolume->OffsetP, TestVolume->Dim);
Result = RectanglesIntersect(EntityRect, TestEntityRect);
}
}
return(Result);
}
internal void
MoveEntity(game_state *GameState, sim_region *SimRegion, sim_entity *Entity, real32 dt,
move_spec *MoveSpec, v3 ddP)
{
TIMED_FUNCTION();
Assert(!IsSet(Entity, EntityFlag_Nonspatial));
world *World = SimRegion->World;
if(Entity->Type == EntityType_Hero)
{
int BreakHere = 5;
}
if(MoveSpec->UnitMaxAccelVector)
{
real32 ddPLength = LengthSq(ddP);
if(ddPLength > 1.0f)
{
ddP *= (1.0f / SquareRoot(ddPLength));
}
}
ddP *= MoveSpec->Speed;
// TODO(casey): ODE here!
v3 Drag = -MoveSpec->Drag*Entity->dP;
Drag.z = 0.0f;
ddP += Drag;
if(!IsSet(Entity, EntityFlag_ZSupported))
{
ddP += V3(0, 0, -9.8f); // NOTE(casey): Gravity!
}
v3 PlayerDelta = (0.5f*ddP*Square(dt) + Entity->dP*dt);
Entity->dP = ddP*dt + Entity->dP;
// TODO(casey): Upgrade physical motion routines to handle capping the
// maximum velocity?
Assert(LengthSq(Entity->dP) <= Square(SimRegion->MaxEntityVelocity));
real32 DistanceRemaining = Entity->DistanceLimit;
if(DistanceRemaining == 0.0f)
{
// TODO(casey): Do we want to formalize this number?
DistanceRemaining = 10000.0f;
}
for(uint32 Iteration = 0;
Iteration < 4;
++Iteration)
{
real32 tMin = 1.0f;
real32 tMax = 0.0f;
real32 PlayerDeltaLength = Length(PlayerDelta);
// TODO(casey): What do we want to do for epsilons here?
// Think this through for the final collision code
if(PlayerDeltaLength > 0.0f)
{
if(PlayerDeltaLength > DistanceRemaining)
{
tMin = (DistanceRemaining / PlayerDeltaLength);
}
v3 WallNormalMin = {};
v3 WallNormalMax = {};
sim_entity *HitEntityMin = 0;
sim_entity *HitEntityMax = 0;
v3 DesiredPosition = Entity->P + PlayerDelta;
// NOTE(casey): This is just an optimization to avoid enterring the
// loop in the case where the test entity is non-spatial!
if(!IsSet(Entity, EntityFlag_Nonspatial))
{
// TODO(casey): Spatial partition here!
for(uint32 TestHighEntityIndex = 0;
TestHighEntityIndex < SimRegion->EntityCount;
++TestHighEntityIndex)
{
sim_entity *TestEntity = SimRegion->Entities + TestHighEntityIndex;
// TODO(casey): Robustness!
real32 OverlapEpsilon = 0.001f;
if((IsSet(TestEntity, EntityFlag_Traversable) &&
EntitiesOverlap(Entity, TestEntity, OverlapEpsilon*V3(1, 1, 1))) ||
CanCollide(GameState, Entity, TestEntity))
{
for(uint32 VolumeIndex = 0;
VolumeIndex < Entity->Collision->VolumeCount;
++VolumeIndex)
{
sim_entity_collision_volume *Volume =
Entity->Collision->Volumes + VolumeIndex;
for(uint32 TestVolumeIndex = 0;
TestVolumeIndex < TestEntity->Collision->VolumeCount;
++TestVolumeIndex)
{
sim_entity_collision_volume *TestVolume =
TestEntity->Collision->Volumes + TestVolumeIndex;
v3 MinkowskiDiameter = {TestVolume->Dim.x + Volume->Dim.x,
TestVolume->Dim.y + Volume->Dim.y,
TestVolume->Dim.z + Volume->Dim.z};
v3 MinCorner = -0.5f*MinkowskiDiameter;
v3 MaxCorner = 0.5f*MinkowskiDiameter;
v3 Rel = ((Entity->P + Volume->OffsetP) -
(TestEntity->P + TestVolume->OffsetP));
// TODO(casey): Do we want an open inclusion at the MaxCorner?
if((Rel.z >= MinCorner.z) && (Rel.z < MaxCorner.z))
{
test_wall Walls[] =
{
{MinCorner.x, Rel.x, Rel.y, PlayerDelta.x, PlayerDelta.y, MinCorner.y, MaxCorner.y, V3(-1, 0, 0)},
{MaxCorner.x, Rel.x, Rel.y, PlayerDelta.x, PlayerDelta.y, MinCorner.y, MaxCorner.y, V3(1, 0, 0)},
{MinCorner.y, Rel.y, Rel.x, PlayerDelta.y, PlayerDelta.x, MinCorner.x, MaxCorner.x, V3(0, -1, 0)},
{MaxCorner.y, Rel.y, Rel.x, PlayerDelta.y, PlayerDelta.x, MinCorner.x, MaxCorner.x, V3(0, 1, 0)},
};
if(IsSet(TestEntity, EntityFlag_Traversable))
{
real32 tMaxTest = tMax;
bool32 HitThis = false;
v3 TestWallNormal = {};
for(uint32 WallIndex = 0;
WallIndex < ArrayCount(Walls);
++WallIndex)
{
test_wall *Wall = Walls + WallIndex;
real32 tEpsilon = 0.001f;
if(Wall->DeltaX != 0.0f)
{
real32 tResult = (Wall->X - Wall->RelX) / Wall->DeltaX;
real32 Y = Wall->RelY + tResult*Wall->DeltaY;
if((tResult >= 0.0f) && (tMaxTest < tResult))
{
if((Y >= Wall->MinY) && (Y <= Wall->MaxY))
{
tMaxTest = Maximum(0.0f, tResult - tEpsilon);
TestWallNormal = Wall->Normal;
HitThis = true;
}
}
}
}
if(HitThis)
{
tMax = tMaxTest;
WallNormalMax = TestWallNormal;
HitEntityMax = TestEntity;
}
}
else
{
real32 tMinTest = tMin;
bool32 HitThis = false;
v3 TestWallNormal = {};
for(uint32 WallIndex = 0;
WallIndex < ArrayCount(Walls);
++WallIndex)
{
test_wall *Wall = Walls + WallIndex;
real32 tEpsilon = 0.001f;
if(Wall->DeltaX != 0.0f)
{
real32 tResult = (Wall->X - Wall->RelX) / Wall->DeltaX;
real32 Y = Wall->RelY + tResult*Wall->DeltaY;
if((tResult >= 0.0f) && (tMinTest > tResult))
{
if((Y >= Wall->MinY) && (Y <= Wall->MaxY))
{
tMinTest = Maximum(0.0f, tResult - tEpsilon);
TestWallNormal = Wall->Normal;
HitThis = true;
}
}
}
}
// TODO(casey): We need a concept of stepping onto vs. stepping
// off of here so that we can prevent you from _leaving_
// stairs instead of just preventing you from getting onto them.
if(HitThis)
{
v3 TestP = Entity->P + tMinTest*PlayerDelta;
if(SpeculativeCollide(Entity, TestEntity, TestP))
{
tMin = tMinTest;
WallNormalMin = TestWallNormal;
HitEntityMin = TestEntity;
}
}
}
}
}
}
}
}
}
v3 WallNormal;
sim_entity *HitEntity;
real32 tStop;
if(tMin < tMax)
{
tStop = tMin;
HitEntity = HitEntityMin;
WallNormal = WallNormalMin;
}
else
{
tStop = tMax;
HitEntity = HitEntityMax;
WallNormal = WallNormalMax;
}
Entity->P += tStop*PlayerDelta;
DistanceRemaining -= tStop*PlayerDeltaLength;
if(HitEntity)
{
PlayerDelta = DesiredPosition - Entity->P;
bool32 StopsOnCollision = HandleCollision(GameState, Entity, HitEntity);
if(StopsOnCollision)
{
PlayerDelta = PlayerDelta - 1*Inner(PlayerDelta, WallNormal)*WallNormal;
Entity->dP = Entity->dP - 1*Inner(Entity->dP, WallNormal)*WallNormal;
}
}
else
{
break;
}
}
else
{
break;
}
}
real32 Ground = 0.0f;
// NOTE(casey): Handle events based on area overlapping
// TODO(casey): Handle overlapping precisely by moving it into the collision loop?
{
// TODO(casey): Spatial partition here!
for(uint32 TestHighEntityIndex = 0;
TestHighEntityIndex < SimRegion->EntityCount;
++TestHighEntityIndex)
{
sim_entity *TestEntity = SimRegion->Entities + TestHighEntityIndex;
if(CanOverlap(GameState, Entity, TestEntity) &&
EntitiesOverlap(Entity, TestEntity))
{
HandleOverlap(GameState, Entity, TestEntity, dt, &Ground);
}
}
}
Ground += Entity->P.z - GetEntityGroundPoint(Entity).z;
if((Entity->P.z <= Ground) ||
(IsSet(Entity, EntityFlag_ZSupported) &&
(Entity->dP.z == 0.0f)))
{
Entity->P.z = Ground;
Entity->dP.z = 0;
AddFlags(Entity, EntityFlag_ZSupported);
}
else
{
ClearFlags(Entity, EntityFlag_ZSupported);
}
if(Entity->DistanceLimit != 0.0f)
{
Entity->DistanceLimit = DistanceRemaining;
}
// TODO(casey): Change to using the acceleration vector
if((Entity->dP.x == 0.0f) && (Entity->dP.y == 0.0f))
{
// NOTE(casey): Leave FacingDirection whatever it was
}
else
{
Entity->FacingDirection = ATan2(Entity->dP.y, Entity->dP.x);
}
}