792 lines
28 KiB
C++
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);
|
|
}
|
|
}
|