/* ======================================================================== $File: $ $Date: $ $Revision: $ $Creator: Casey Muratori $ $Notice: (C) Copyright 2014 by Molly Rocket, Inc. All Rights Reserved. $ ======================================================================== */ #include "handmade.h" #include "handmade_render_group.cpp" #include "handmade_world.cpp" #include "handmade_sim_region.cpp" #include "handmade_entity.cpp" #include "handmade_asset.cpp" #include "handmade_audio.cpp" #include "handmade_meta.cpp" struct add_low_entity_result { low_entity *Low; u32 LowIndex; }; internal add_low_entity_result AddLowEntity(game_state *GameState, entity_type Type, world_position P) { Assert(GameState->LowEntityCount < ArrayCount(GameState->LowEntities)); uint32 EntityIndex = GameState->LowEntityCount++; low_entity *EntityLow = GameState->LowEntities + EntityIndex; *EntityLow = {}; EntityLow->Sim.Type = Type; EntityLow->Sim.Collision = GameState->NullCollision; EntityLow->P = NullPosition(); ChangeEntityLocation(&GameState->WorldArena, GameState->World, EntityIndex, EntityLow, P); add_low_entity_result Result; Result.Low = EntityLow; Result.LowIndex = EntityIndex; // TODO(casey): Do we need to have a begin/end paradigm for adding // entities so that they can be brought into the high set when they // are added and are in the camera region? return(Result); } internal add_low_entity_result AddGroundedEntity(game_state *GameState, entity_type Type, world_position P, sim_entity_collision_volume_group *Collision) { add_low_entity_result Entity = AddLowEntity(GameState, Type, P); Entity.Low->Sim.Collision = Collision; return(Entity); } inline world_position ChunkPositionFromTilePosition(world *World, int32 AbsTileX, int32 AbsTileY, int32 AbsTileZ, v3 AdditionalOffset = V3(0, 0, 0)) { world_position BasePos = {}; real32 TileSideInMeters = 1.4f; real32 TileDepthInMeters = 3.0f; v3 TileDim = V3(TileSideInMeters, TileSideInMeters, TileDepthInMeters); v3 Offset = Hadamard(TileDim, V3((real32)AbsTileX, (real32)AbsTileY, (real32)AbsTileZ)); world_position Result = MapIntoChunkSpace(World, BasePos, AdditionalOffset + Offset); Assert(IsCanonical(World, Result.Offset_)); return(Result); } internal add_low_entity_result AddStandardRoom(game_state *GameState, uint32 AbsTileX, uint32 AbsTileY, uint32 AbsTileZ) { world_position P = ChunkPositionFromTilePosition(GameState->World, AbsTileX, AbsTileY, AbsTileZ); add_low_entity_result Entity = AddGroundedEntity(GameState, EntityType_Space, P, GameState->StandardRoomCollision); AddFlags(&Entity.Low->Sim, EntityFlag_Traversable); return(Entity); } internal add_low_entity_result AddWall(game_state *GameState, uint32 AbsTileX, uint32 AbsTileY, uint32 AbsTileZ) { world_position P = ChunkPositionFromTilePosition(GameState->World, AbsTileX, AbsTileY, AbsTileZ); add_low_entity_result Entity = AddGroundedEntity(GameState, EntityType_Wall, P, GameState->WallCollision); AddFlags(&Entity.Low->Sim, EntityFlag_Collides); return(Entity); } internal add_low_entity_result AddStair(game_state *GameState, uint32 AbsTileX, uint32 AbsTileY, uint32 AbsTileZ) { world_position P = ChunkPositionFromTilePosition(GameState->World, AbsTileX, AbsTileY, AbsTileZ); add_low_entity_result Entity = AddGroundedEntity(GameState, EntityType_Stairwell, P, GameState->StairCollision); AddFlags(&Entity.Low->Sim, EntityFlag_Collides); Entity.Low->Sim.WalkableDim = Entity.Low->Sim.Collision->TotalVolume.Dim.xy; Entity.Low->Sim.WalkableHeight = GameState->TypicalFloorHeight; return(Entity); } internal void InitHitPoints(low_entity *EntityLow, uint32 HitPointCount) { Assert(HitPointCount <= ArrayCount(EntityLow->Sim.HitPoint)); EntityLow->Sim.HitPointMax = HitPointCount; for(uint32 HitPointIndex = 0; HitPointIndex < EntityLow->Sim.HitPointMax; ++HitPointIndex) { hit_point *HitPoint = EntityLow->Sim.HitPoint + HitPointIndex; HitPoint->Flags = 0; HitPoint->FilledAmount = HIT_POINT_SUB_COUNT; } } internal add_low_entity_result AddSword(game_state *GameState) { add_low_entity_result Entity = AddLowEntity(GameState, EntityType_Sword, NullPosition()); Entity.Low->Sim.Collision = GameState->SwordCollision; AddFlags(&Entity.Low->Sim, EntityFlag_Moveable); return(Entity); } internal add_low_entity_result AddPlayer(game_state *GameState) { world_position P = GameState->CameraP; add_low_entity_result Entity = AddGroundedEntity(GameState, EntityType_Hero, P, GameState->PlayerCollision); AddFlags(&Entity.Low->Sim, EntityFlag_Collides|EntityFlag_Moveable); InitHitPoints(Entity.Low, 3); add_low_entity_result Sword = AddSword(GameState); Entity.Low->Sim.Sword.Index = Sword.LowIndex; if(GameState->CameraFollowingEntityIndex == 0) { GameState->CameraFollowingEntityIndex = Entity.LowIndex; } return(Entity); } internal add_low_entity_result AddMonstar(game_state *GameState, uint32 AbsTileX, uint32 AbsTileY, uint32 AbsTileZ) { world_position P = ChunkPositionFromTilePosition(GameState->World, AbsTileX, AbsTileY, AbsTileZ); add_low_entity_result Entity = AddGroundedEntity(GameState, EntityType_Monstar, P, GameState->MonstarCollision); AddFlags(&Entity.Low->Sim, EntityFlag_Collides|EntityFlag_Moveable); InitHitPoints(Entity.Low, 3); return(Entity); } internal add_low_entity_result AddFamiliar(game_state *GameState, uint32 AbsTileX, uint32 AbsTileY, uint32 AbsTileZ) { world_position P = ChunkPositionFromTilePosition(GameState->World, AbsTileX, AbsTileY, AbsTileZ); add_low_entity_result Entity = AddGroundedEntity(GameState, EntityType_Familiar, P, GameState->FamiliarCollision); AddFlags(&Entity.Low->Sim, EntityFlag_Collides|EntityFlag_Moveable); return(Entity); } internal void DrawHitpoints(sim_entity *Entity, render_group *PieceGroup) { if(Entity->HitPointMax >= 1) { v2 HealthDim = {0.2f, 0.2f}; real32 SpacingX = 1.5f*HealthDim.x; v2 HitP = {-0.5f*(Entity->HitPointMax - 1)*SpacingX, -0.25f}; v2 dHitP = {SpacingX, 0.0f}; for(uint32 HealthIndex = 0; HealthIndex < Entity->HitPointMax; ++HealthIndex) { hit_point *HitPoint = Entity->HitPoint + HealthIndex; v4 Color = {1.0f, 0.0f, 0.0f, 1.0f}; if(HitPoint->FilledAmount == 0) { Color = V4(0.2f, 0.2f, 0.2f, 1.0f); } PushRect(PieceGroup, V3(HitP, 0), HealthDim, Color); HitP += dHitP; } } } internal void ClearCollisionRulesFor(game_state *GameState, uint32 StorageIndex) { // TODO(casey): Need to make a better data structure that allows // removal of collision rules without searching the entire table // NOTE(casey): One way to make removal easy would be to always // add _both_ orders of the pairs of storage indices to the // hash table, so no matter which position the entity is in, // you can always find it. Then, when you do your first pass // through for removal, you just remember the original top // of the free list, and when you're done, do a pass through all // the new things on the free list, and remove the reverse of // those pairs. for(uint32 HashBucket = 0; HashBucket < ArrayCount(GameState->CollisionRuleHash); ++HashBucket) { for(pairwise_collision_rule **Rule = &GameState->CollisionRuleHash[HashBucket]; *Rule; ) { if(((*Rule)->StorageIndexA == StorageIndex) || ((*Rule)->StorageIndexB == StorageIndex)) { pairwise_collision_rule *RemovedRule = *Rule; *Rule = (*Rule)->NextInHash; RemovedRule->NextInHash = GameState->FirstFreeCollisionRule; GameState->FirstFreeCollisionRule = RemovedRule; } else { Rule = &(*Rule)->NextInHash; } } } } internal void AddCollisionRule(game_state *GameState, uint32 StorageIndexA, uint32 StorageIndexB, bool32 CanCollide) { // TODO(casey): Collapse this with ShouldCollide if(StorageIndexA > StorageIndexB) { uint32 Temp = StorageIndexA; StorageIndexA = StorageIndexB; StorageIndexB = Temp; } // TODO(casey): BETTER HASH FUNCTION pairwise_collision_rule *Found = 0; uint32 HashBucket = StorageIndexA & (ArrayCount(GameState->CollisionRuleHash) - 1); for(pairwise_collision_rule *Rule = GameState->CollisionRuleHash[HashBucket]; Rule; Rule = Rule->NextInHash) { if((Rule->StorageIndexA == StorageIndexA) && (Rule->StorageIndexB == StorageIndexB)) { Found = Rule; break; } } if(!Found) { Found = GameState->FirstFreeCollisionRule; if(Found) { GameState->FirstFreeCollisionRule = Found->NextInHash; } else { Found = PushStruct(&GameState->WorldArena, pairwise_collision_rule); } Found->NextInHash = GameState->CollisionRuleHash[HashBucket]; GameState->CollisionRuleHash[HashBucket] = Found; } if(Found) { Found->StorageIndexA = StorageIndexA; Found->StorageIndexB = StorageIndexB; Found->CanCollide = CanCollide; } } sim_entity_collision_volume_group * MakeSimpleGroundedCollision(game_state *GameState, real32 DimX, real32 DimY, real32 DimZ) { // TODO(casey): NOT WORLD ARENA! Change to using the fundamental types arena, etc. sim_entity_collision_volume_group *Group = PushStruct(&GameState->WorldArena, sim_entity_collision_volume_group); Group->VolumeCount = 1; Group->Volumes = PushArray(&GameState->WorldArena, Group->VolumeCount, sim_entity_collision_volume); Group->TotalVolume.OffsetP = V3(0, 0, 0.5f*DimZ); Group->TotalVolume.Dim = V3(DimX, DimY, DimZ); Group->Volumes[0] = Group->TotalVolume; return(Group); } sim_entity_collision_volume_group * MakeNullCollision(game_state *GameState) { // TODO(casey): NOT WORLD ARENA! Change to using the fundamental types arena, etc. sim_entity_collision_volume_group *Group = PushStruct(&GameState->WorldArena, sim_entity_collision_volume_group); Group->VolumeCount = 0; Group->Volumes = 0; Group->TotalVolume.OffsetP = V3(0, 0, 0); // TODO(casey): Should this be negative? Group->TotalVolume.Dim = V3(0, 0, 0); return(Group); } internal task_with_memory * BeginTaskWithMemory(transient_state *TranState) { task_with_memory *FoundTask = 0; for(uint32 TaskIndex = 0; TaskIndex < ArrayCount(TranState->Tasks); ++TaskIndex) { task_with_memory *Task = TranState->Tasks + TaskIndex; if(!Task->BeingUsed) { FoundTask = Task; Task->BeingUsed = true; Task->MemoryFlush = BeginTemporaryMemory(&Task->Arena); break; } } return(FoundTask); } internal void EndTaskWithMemory(task_with_memory *Task) { EndTemporaryMemory(Task->MemoryFlush); CompletePreviousWritesBeforeFutureWrites; Task->BeingUsed = false; } struct fill_ground_chunk_work { transient_state *TranState; game_state *GameState; ground_buffer *GroundBuffer; world_position ChunkP; task_with_memory *Task; }; internal PLATFORM_WORK_QUEUE_CALLBACK(FillGroundChunkWork) { TIMED_FUNCTION(); fill_ground_chunk_work *Work = (fill_ground_chunk_work *)Data; loaded_bitmap *Buffer = &Work->GroundBuffer->Bitmap; Buffer->AlignPercentage = V2(0.5f, 0.5f); Buffer->WidthOverHeight = 1.0f; real32 Width = Work->GameState->World->ChunkDimInMeters.x; real32 Height = Work->GameState->World->ChunkDimInMeters.y; Assert(Width == Height); v2 HalfDim = 0.5f*V2(Width, Height); // TODO(casey): Decide what our pushbuffer size is! render_group *RenderGroup = AllocateRenderGroup(Work->TranState->Assets, &Work->Task->Arena, 0, true); BeginRender(RenderGroup); Orthographic(RenderGroup, Buffer->Width, Buffer->Height, (Buffer->Width - 2) / Width); Clear(RenderGroup, V4(1.0f, 0.0f, 1.0f, 1.0f)); for(int32 ChunkOffsetY = -1; ChunkOffsetY <= 1; ++ChunkOffsetY) { for(int32 ChunkOffsetX = -1; ChunkOffsetX <= 1; ++ChunkOffsetX) { int32 ChunkX = Work->ChunkP.ChunkX + ChunkOffsetX; int32 ChunkY = Work->ChunkP.ChunkY + ChunkOffsetY; int32 ChunkZ = Work->ChunkP.ChunkZ; // TODO(casey): Make random number generation more systemic // TODO(casey): Look into wang hashing or some other spatial seed generation "thing"! random_series Series = RandomSeed(139*ChunkX + 593*ChunkY + 329*ChunkZ); v4 Color; DEBUG_IF(GroundChunks_Checkerboards) { Color = V4(1, 0, 0, 1); if((ChunkX % 2) == (ChunkY % 2)) { Color = V4(0, 0, 1, 1); } } else { Color = {1, 1, 1, 1}; } v2 Center = V2(ChunkOffsetX*Width, ChunkOffsetY*Height); for(uint32 GrassIndex = 0; GrassIndex < 100; ++GrassIndex) { bitmap_id Stamp = GetRandomBitmapFrom(Work->TranState->Assets, RandomChoice(&Series, 2) ? Asset_Grass : Asset_Stone, &Series); v2 P = Center + Hadamard(HalfDim, V2(RandomBilateral(&Series), RandomBilateral(&Series))); PushBitmap(RenderGroup, Stamp, 2.0f, V3(P, 0.0f), Color); } } } for(int32 ChunkOffsetY = -1; ChunkOffsetY <= 1; ++ChunkOffsetY) { for(int32 ChunkOffsetX = -1; ChunkOffsetX <= 1; ++ChunkOffsetX) { int32 ChunkX = Work->ChunkP.ChunkX + ChunkOffsetX; int32 ChunkY = Work->ChunkP.ChunkY + ChunkOffsetY; int32 ChunkZ = Work->ChunkP.ChunkZ; // TODO(casey): Make random number generation more systemic // TODO(casey): Look into wang hashing or some other spatial seed generation "thing"! random_series Series = RandomSeed(139*ChunkX + 593*ChunkY + 329*ChunkZ); v2 Center = V2(ChunkOffsetX*Width, ChunkOffsetY*Height); for(uint32 GrassIndex = 0; GrassIndex < 50; ++GrassIndex) { bitmap_id Stamp = GetRandomBitmapFrom(Work->TranState->Assets, Asset_Tuft, &Series); v2 P = Center + Hadamard(HalfDim, V2(RandomBilateral(&Series), RandomBilateral(&Series))); PushBitmap(RenderGroup, Stamp, 0.1f, V3(P, 0.0f)); } } } Assert(AllResourcesPresent(RenderGroup)); RenderGroupToOutput(RenderGroup, Buffer); EndRender(RenderGroup); EndTaskWithMemory(Work->Task); } internal void FillGroundChunk(transient_state *TranState, game_state *GameState, ground_buffer *GroundBuffer, world_position *ChunkP) { task_with_memory *Task = BeginTaskWithMemory(TranState); if(Task) { fill_ground_chunk_work *Work = PushStruct(&Task->Arena, fill_ground_chunk_work); Work->Task = Task; Work->TranState = TranState; Work->GameState = GameState; Work->GroundBuffer = GroundBuffer; Work->ChunkP = *ChunkP; GroundBuffer->P = *ChunkP; Platform.AddEntry(TranState->LowPriorityQueue, FillGroundChunkWork, Work); } } internal void ClearBitmap(loaded_bitmap *Bitmap) { if(Bitmap->Memory) { int32 TotalBitmapSize = Bitmap->Width*Bitmap->Height*BITMAP_BYTES_PER_PIXEL; ZeroSize(TotalBitmapSize, Bitmap->Memory); } } internal loaded_bitmap MakeEmptyBitmap(memory_arena *Arena, int32 Width, int32 Height, bool32 ClearToZero = true) { loaded_bitmap Result = {}; Result.AlignPercentage = V2(0.5f, 0.5f); Result.WidthOverHeight = SafeRatio1((r32)Width, (r32)Height); Result.Width = Width; Result.Height = Height; Result.Pitch = Result.Width*BITMAP_BYTES_PER_PIXEL; int32 TotalBitmapSize = Width*Height*BITMAP_BYTES_PER_PIXEL; Result.Memory = PushSize(Arena, TotalBitmapSize, 16); if(ClearToZero) { ClearBitmap(&Result); } return(Result); } internal void MakeSphereNormalMap(loaded_bitmap *Bitmap, real32 Roughness, real32 Cx = 1.0f, real32 Cy = 1.0f) { real32 InvWidth = 1.0f / (real32)(Bitmap->Width - 1); real32 InvHeight = 1.0f / (real32)(Bitmap->Height - 1); uint8 *Row = (uint8 *)Bitmap->Memory; for(int32 Y = 0; Y < Bitmap->Height; ++Y) { uint32 *Pixel = (uint32 *)Row; for(int32 X = 0; X < Bitmap->Width; ++X) { v2 BitmapUV = {InvWidth*(real32)X, InvHeight*(real32)Y}; real32 Nx = Cx*(2.0f*BitmapUV.x - 1.0f); real32 Ny = Cy*(2.0f*BitmapUV.y - 1.0f); real32 RootTerm = 1.0f - Nx*Nx - Ny*Ny; v3 Normal = {0, 0.707106781188f, 0.707106781188f}; real32 Nz = 0.0f; if(RootTerm >= 0.0f) { Nz = SquareRoot(RootTerm); Normal = V3(Nx, Ny, Nz); } v4 Color = {255.0f*(0.5f*(Normal.x + 1.0f)), 255.0f*(0.5f*(Normal.y + 1.0f)), 255.0f*(0.5f*(Normal.z + 1.0f)), 255.0f*Roughness}; *Pixel++ = (((uint32)(Color.a + 0.5f) << 24) | ((uint32)(Color.r + 0.5f) << 16) | ((uint32)(Color.g + 0.5f) << 8) | ((uint32)(Color.b + 0.5f) << 0)); } Row += Bitmap->Pitch; } } internal void MakeSphereDiffuseMap(loaded_bitmap *Bitmap, real32 Cx = 1.0f, real32 Cy = 1.0f) { real32 InvWidth = 1.0f / (real32)(Bitmap->Width - 1); real32 InvHeight = 1.0f / (real32)(Bitmap->Height - 1); uint8 *Row = (uint8 *)Bitmap->Memory; for(int32 Y = 0; Y < Bitmap->Height; ++Y) { uint32 *Pixel = (uint32 *)Row; for(int32 X = 0; X < Bitmap->Width; ++X) { v2 BitmapUV = {InvWidth*(real32)X, InvHeight*(real32)Y}; real32 Nx = Cx*(2.0f*BitmapUV.x - 1.0f); real32 Ny = Cy*(2.0f*BitmapUV.y - 1.0f); real32 RootTerm = 1.0f - Nx*Nx - Ny*Ny; real32 Alpha = 0.0f; if(RootTerm >= 0.0f) { Alpha = 1.0f; } v3 BaseColor = {0.0f, 0.0f, 0.0f}; Alpha *= 255.0f; v4 Color = {Alpha*BaseColor.x, Alpha*BaseColor.y, Alpha*BaseColor.z, Alpha}; *Pixel++ = (((uint32)(Color.a + 0.5f) << 24) | ((uint32)(Color.r + 0.5f) << 16) | ((uint32)(Color.g + 0.5f) << 8) | ((uint32)(Color.b + 0.5f) << 0)); } Row += Bitmap->Pitch; } } internal void MakePyramidNormalMap(loaded_bitmap *Bitmap, real32 Roughness) { real32 InvWidth = 1.0f / (real32)(Bitmap->Width - 1); real32 InvHeight = 1.0f / (real32)(Bitmap->Height - 1); uint8 *Row = (uint8 *)Bitmap->Memory; for(int32 Y = 0; Y < Bitmap->Height; ++Y) { uint32 *Pixel = (uint32 *)Row; for(int32 X = 0; X < Bitmap->Width; ++X) { v2 BitmapUV = {InvWidth*(real32)X, InvHeight*(real32)Y}; int32 InvX = (Bitmap->Width - 1) - X; real32 Seven = 0.707106781188f; v3 Normal = {0, 0, Seven}; if(X < Y) { if(InvX < Y) { Normal.x = -Seven; } else { Normal.y = Seven; } } else { if(InvX < Y) { Normal.y = -Seven; } else { Normal.x = Seven; } } v4 Color = {255.0f*(0.5f*(Normal.x + 1.0f)), 255.0f*(0.5f*(Normal.y + 1.0f)), 255.0f*(0.5f*(Normal.z + 1.0f)), 255.0f*Roughness}; *Pixel++ = (((uint32)(Color.a + 0.5f) << 24) | ((uint32)(Color.r + 0.5f) << 16) | ((uint32)(Color.g + 0.5f) << 8) | ((uint32)(Color.b + 0.5f) << 0)); } Row += Bitmap->Pitch; } } internal game_assets * DEBUGGetGameAssets(game_memory *Memory) { game_assets *Assets = 0; transient_state *TranState = (transient_state *)Memory->TransientStorage; if(TranState->IsInitialized) { Assets = TranState->Assets; } return(Assets); } #if HANDMADE_INTERNAL game_memory *DebugGlobalMemory; #endif extern "C" GAME_UPDATE_AND_RENDER(GameUpdateAndRender) { Platform = Memory->PlatformAPI; #if HANDMADE_INTERNAL DebugGlobalMemory = Memory; #endif TIMED_FUNCTION(); Assert((&Input->Controllers[0].Terminator - &Input->Controllers[0].Buttons[0]) == (ArrayCount(Input->Controllers[0].Buttons))); uint32 GroundBufferWidth = 256; uint32 GroundBufferHeight = 256; Assert(sizeof(game_state) <= Memory->PermanentStorageSize); game_state *GameState = (game_state *)Memory->PermanentStorage; if(!GameState->IsInitialized) { uint32 TilesPerWidth = 17; uint32 TilesPerHeight = 9; GameState->EffectsEntropy = RandomSeed(1234); GameState->TypicalFloorHeight = 3.0f; // TODO(casey): Remove this! real32 PixelsToMeters = 1.0f / 42.0f; v3 WorldChunkDimInMeters = {PixelsToMeters*(real32)GroundBufferWidth, PixelsToMeters*(real32)GroundBufferHeight, GameState->TypicalFloorHeight}; InitializeArena(&GameState->WorldArena, Memory->PermanentStorageSize - sizeof(game_state), (uint8 *)Memory->PermanentStorage + sizeof(game_state)); InitializeAudioState(&GameState->AudioState, &GameState->WorldArena); // NOTE(casey): Reserve entity slot 0 for the null entity AddLowEntity(GameState, EntityType_Null, NullPosition()); GameState->World = PushStruct(&GameState->WorldArena, world); world *World = GameState->World; InitializeWorld(World, WorldChunkDimInMeters); real32 TileSideInMeters = 1.4f; real32 TileDepthInMeters = GameState->TypicalFloorHeight; GameState->NullCollision = MakeNullCollision(GameState); GameState->SwordCollision = MakeSimpleGroundedCollision(GameState, 1.0f, 0.5f, 0.1f); GameState->StairCollision = MakeSimpleGroundedCollision(GameState, TileSideInMeters, 2.0f*TileSideInMeters, 1.1f*TileDepthInMeters); GameState->PlayerCollision = MakeSimpleGroundedCollision(GameState, 1.0f, 0.5f, 1.2f); GameState->MonstarCollision = MakeSimpleGroundedCollision(GameState, 1.0f, 0.5f, 0.5f); GameState->FamiliarCollision = MakeSimpleGroundedCollision(GameState, 1.0f, 0.5f, 0.5f); GameState->WallCollision = MakeSimpleGroundedCollision(GameState, TileSideInMeters, TileSideInMeters, TileDepthInMeters); GameState->StandardRoomCollision = MakeSimpleGroundedCollision(GameState, TilesPerWidth*TileSideInMeters, TilesPerHeight*TileSideInMeters, 0.9f*TileDepthInMeters); random_series Series = RandomSeed(1234); uint32 ScreenBaseX = 0; uint32 ScreenBaseY = 0; uint32 ScreenBaseZ = 0; uint32 ScreenX = ScreenBaseX; uint32 ScreenY = ScreenBaseY; uint32 AbsTileZ = ScreenBaseZ; // TODO(casey): Replace all this with real world generation! bool32 DoorLeft = false; bool32 DoorRight = false; bool32 DoorTop = false; bool32 DoorBottom = false; bool32 DoorUp = false; bool32 DoorDown = false; for(uint32 ScreenIndex = 0; ScreenIndex < 2000; ++ScreenIndex) { #if 1 uint32 DoorDirection = RandomChoice(&Series, (DoorUp || DoorDown) ? 2 : 4); #else uint32 DoorDirection = RandomChoice(&Series, 2); #endif // DoorDirection = 3; bool32 CreatedZDoor = false; if(DoorDirection == 3) { CreatedZDoor = true; DoorDown = true; } else if(DoorDirection == 2) { CreatedZDoor = true; DoorUp = true; } else if(DoorDirection == 1) { DoorRight = true; } else { DoorTop = true; } AddStandardRoom(GameState, ScreenX*TilesPerWidth + TilesPerWidth/2, ScreenY*TilesPerHeight + TilesPerHeight/2, AbsTileZ); for(uint32 TileY = 0; TileY < TilesPerHeight; ++TileY) { for(uint32 TileX = 0; TileX < TilesPerWidth; ++TileX) { uint32 AbsTileX = ScreenX*TilesPerWidth + TileX; uint32 AbsTileY = ScreenY*TilesPerHeight + TileY; bool32 ShouldBeDoor = false; if((TileX == 0) && (!DoorLeft || (TileY != (TilesPerHeight/2)))) { ShouldBeDoor = true; } if((TileX == (TilesPerWidth - 1)) && (!DoorRight || (TileY != (TilesPerHeight/2)))) { ShouldBeDoor = true; } if((TileY == 0) && (!DoorBottom || (TileX != (TilesPerWidth/2)))) { ShouldBeDoor = true; } if((TileY == (TilesPerHeight - 1)) && (!DoorTop || (TileX != (TilesPerWidth/2)))) { ShouldBeDoor = true; } if(ShouldBeDoor) { AddWall(GameState, AbsTileX, AbsTileY, AbsTileZ); } else if(CreatedZDoor) { if(((AbsTileZ % 2) && (TileX == 10) && (TileY == 5)) || (!(AbsTileZ % 2) && (TileX == 4) && (TileY == 5))) { AddStair(GameState, AbsTileX, AbsTileY, DoorDown ? AbsTileZ - 1 : AbsTileZ); } } } } DoorLeft = DoorRight; DoorBottom = DoorTop; if(CreatedZDoor) { DoorDown = !DoorDown; DoorUp = !DoorUp; } else { DoorUp = false; DoorDown = false; } DoorRight = false; DoorTop = false; if(DoorDirection == 3) { AbsTileZ -= 1; } else if(DoorDirection == 2) { AbsTileZ += 1; } else if(DoorDirection == 1) { ScreenX += 1; } else { ScreenY += 1; } } #if 0 while(GameState->LowEntityCount < (ArrayCount(GameState->LowEntities) - 16)) { uint32 Coordinate = 1024 + GameState->LowEntityCount; AddWall(GameState, Coordinate, Coordinate, Coordinate); } #endif world_position NewCameraP = {}; uint32 CameraTileX = ScreenBaseX*TilesPerWidth + 17/2; uint32 CameraTileY = ScreenBaseY*TilesPerHeight + 9/2; uint32 CameraTileZ = ScreenBaseZ; NewCameraP = ChunkPositionFromTilePosition(GameState->World, CameraTileX, CameraTileY, CameraTileZ); GameState->CameraP = NewCameraP; AddMonstar(GameState, CameraTileX - 3, CameraTileY + 2, CameraTileZ); for(int FamiliarIndex = 0; FamiliarIndex < 1; ++FamiliarIndex) { int32 FamiliarOffsetX = RandomBetween(&Series, -7, 7); int32 FamiliarOffsetY = RandomBetween(&Series, -3, -1); if((FamiliarOffsetX != 0) || (FamiliarOffsetY != 0)) { AddFamiliar(GameState, CameraTileX + FamiliarOffsetX, CameraTileY + FamiliarOffsetY, CameraTileZ); } } GameState->IsInitialized = true; } // NOTE(casey): Transient initialization Assert(sizeof(transient_state) <= Memory->TransientStorageSize); transient_state *TranState = (transient_state *)Memory->TransientStorage; if(!TranState->IsInitialized) { InitializeArena(&TranState->TranArena, Memory->TransientStorageSize - sizeof(transient_state), (uint8 *)Memory->TransientStorage + sizeof(transient_state)); TranState->HighPriorityQueue = Memory->HighPriorityQueue; TranState->LowPriorityQueue = Memory->LowPriorityQueue; for(uint32 TaskIndex = 0; TaskIndex < ArrayCount(TranState->Tasks); ++TaskIndex) { task_with_memory *Task = TranState->Tasks + TaskIndex; Task->BeingUsed = false; SubArena(&Task->Arena, &TranState->TranArena, Megabytes(1)); } TranState->Assets = AllocateGameAssets(&TranState->TranArena, Megabytes(16), TranState); // GameState->Music = PlaySound(&GameState->AudioState, GetFirstSoundFrom(TranState->Assets, Asset_Music)); // TODO(casey): Pick a real number here! TranState->GroundBufferCount = 256; TranState->GroundBuffers = PushArray(&TranState->TranArena, TranState->GroundBufferCount, ground_buffer); for(uint32 GroundBufferIndex = 0; GroundBufferIndex < TranState->GroundBufferCount; ++GroundBufferIndex) { ground_buffer *GroundBuffer = TranState->GroundBuffers + GroundBufferIndex; GroundBuffer->Bitmap = MakeEmptyBitmap(&TranState->TranArena, GroundBufferWidth, GroundBufferHeight, false); GroundBuffer->P = NullPosition(); } GameState->TestDiffuse = MakeEmptyBitmap(&TranState->TranArena, 256, 256, false); GameState->TestNormal = MakeEmptyBitmap(&TranState->TranArena, GameState->TestDiffuse.Width, GameState->TestDiffuse.Height, false); MakeSphereNormalMap(&GameState->TestNormal, 0.0f); MakeSphereDiffuseMap(&GameState->TestDiffuse); // MakePyramidNormalMap(&GameState->TestNormal, 0.0f); TranState->EnvMapWidth = 512; TranState->EnvMapHeight = 256; for(uint32 MapIndex = 0; MapIndex < ArrayCount(TranState->EnvMaps); ++MapIndex) { environment_map *Map = TranState->EnvMaps + MapIndex; uint32 Width = TranState->EnvMapWidth; uint32 Height = TranState->EnvMapHeight; for(uint32 LODIndex = 0; LODIndex < ArrayCount(Map->LOD); ++LODIndex) { Map->LOD[LODIndex] = MakeEmptyBitmap(&TranState->TranArena, Width, Height, false); Width >>= 1; Height >>= 1; } } TranState->IsInitialized = true; } DEBUG_IF(GroundChunks_RecomputeOnEXEChange) { if(Memory->ExecutableReloaded) { for(uint32 GroundBufferIndex = 0; GroundBufferIndex < TranState->GroundBufferCount; ++GroundBufferIndex) { ground_buffer *GroundBuffer = TranState->GroundBuffers + GroundBufferIndex; GroundBuffer->P = NullPosition(); } } } world *World = GameState->World; #if 0 // // NOTE(casey): // { v2 MusicVolume; MusicVolume.y = SafeRatio0((r32)Input->MouseX, (r32)Buffer->Width); MusicVolume.x = 1.0f - MusicVolume.y; ChangeVolume(&GameState->AudioState, GameState->Music, 0.01f, MusicVolume); } #endif for(int ControllerIndex = 0; ControllerIndex < ArrayCount(Input->Controllers); ++ControllerIndex) { game_controller_input *Controller = GetController(Input, ControllerIndex); controlled_hero *ConHero = GameState->ControlledHeroes + ControllerIndex; if(ConHero->EntityIndex == 0) { if(Controller->Start.EndedDown) { *ConHero = {}; ConHero->EntityIndex = AddPlayer(GameState).LowIndex; } } else { ConHero->dZ = 0.0f; ConHero->ddP = {}; ConHero->dSword = {}; if(Controller->IsAnalog) { // NOTE(casey): Use analog movement tuning ConHero->ddP = V2(Controller->StickAverageX, Controller->StickAverageY); } else { // NOTE(casey): Use digital movement tuning if(Controller->MoveUp.EndedDown) { ConHero->ddP.y = 1.0f; } if(Controller->MoveDown.EndedDown) { ConHero->ddP.y = -1.0f; } if(Controller->MoveLeft.EndedDown) { ConHero->ddP.x = -1.0f; } if(Controller->MoveRight.EndedDown) { ConHero->ddP.x = 1.0f; } } if(Controller->Start.EndedDown) { ConHero->dZ = 3.0f; } ConHero->dSword = {}; if(Controller->ActionUp.EndedDown) { ChangeVolume(&GameState->AudioState, GameState->Music, 10.0f, V2(1.0f, 1.0f)); ConHero->dSword = V2(0.0f, 1.0f); } if(Controller->ActionDown.EndedDown) { ChangeVolume(&GameState->AudioState, GameState->Music, 10.0f, V2(0.0f, 0.0f)); ConHero->dSword = V2(0.0f, -1.0f); } if(Controller->ActionLeft.EndedDown) { ChangeVolume(&GameState->AudioState, GameState->Music, 5.0f, V2(1.0f, 0.0f)); ConHero->dSword = V2(-1.0f, 0.0f); } if(Controller->ActionRight.EndedDown) { ChangeVolume(&GameState->AudioState, GameState->Music, 5.0f, V2(0.0f, 1.0f)); ConHero->dSword = V2(1.0f, 0.0f); } } } // // NOTE(casey): Render // temporary_memory RenderMemory = BeginTemporaryMemory(&TranState->TranArena); loaded_bitmap DrawBuffer_ = {}; loaded_bitmap *DrawBuffer = &DrawBuffer_; DrawBuffer->Width = Buffer->Width; DrawBuffer->Height = Buffer->Height; DrawBuffer->Pitch = Buffer->Pitch; DrawBuffer->Memory = Buffer->Memory; DEBUG_IF(Renderer_TestWeirdDrawBufferSize) { // NOTE(casey): Enable this to test weird buffer sizes in the renderer! DrawBuffer->Width = 1279; DrawBuffer->Height = 719; } v2 MouseP = {Input->MouseX, Input->MouseY}; // TODO(casey): Decide what our pushbuffer size is! render_group *RenderGroup = AllocateRenderGroup(TranState->Assets, &TranState->TranArena, Megabytes(4), false); BeginRender(RenderGroup); real32 WidthOfMonitor = 0.635f; // NOTE(casey): Horizontal measurement of monitor in meters real32 MetersToPixels = (real32)DrawBuffer->Width*WidthOfMonitor; real32 FocalLength = 0.6f; real32 DistanceAboveGround = 9.0f; Perspective(RenderGroup, DrawBuffer->Width, DrawBuffer->Height, MetersToPixels, FocalLength, DistanceAboveGround); Clear(RenderGroup, V4(0.25f, 0.25f, 0.25f, 0.0f)); v2 ScreenCenter = {0.5f*(real32)DrawBuffer->Width, 0.5f*(real32)DrawBuffer->Height}; rectangle2 ScreenBounds = GetCameraRectangleAtTarget(RenderGroup); rectangle3 CameraBoundsInMeters = RectMinMax(V3(ScreenBounds.Min, 0.0f), V3(ScreenBounds.Max, 0.0f)); CameraBoundsInMeters.Min.z = -3.0f*GameState->TypicalFloorHeight; CameraBoundsInMeters.Max.z = 1.0f*GameState->TypicalFloorHeight; // NOTE(casey): Ground chunk rendering for(uint32 GroundBufferIndex = 0; GroundBufferIndex < TranState->GroundBufferCount; ++GroundBufferIndex) { ground_buffer *GroundBuffer = TranState->GroundBuffers + GroundBufferIndex; if(IsValid(GroundBuffer->P)) { loaded_bitmap *Bitmap = &GroundBuffer->Bitmap; v3 Delta = Subtract(GameState->World, &GroundBuffer->P, &GameState->CameraP); if((Delta.z >= -1.0f) && (Delta.z < 1.0f)) { real32 GroundSideInMeters = World->ChunkDimInMeters.x; PushBitmap(RenderGroup, Bitmap, 1.0f*GroundSideInMeters, Delta); DEBUG_IF(GroundChunks_Outlines) { PushRectOutline(RenderGroup, Delta, V2(GroundSideInMeters, GroundSideInMeters), V4(1.0f, 1.0f, 0.0f, 1.0f)); } } } } // NOTE(casey): Ground chunk updating { world_position MinChunkP = MapIntoChunkSpace(World, GameState->CameraP, GetMinCorner(CameraBoundsInMeters)); world_position MaxChunkP = MapIntoChunkSpace(World, GameState->CameraP, GetMaxCorner(CameraBoundsInMeters)); 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) { world_position ChunkCenterP = CenteredChunkPoint(ChunkX, ChunkY, ChunkZ); v3 RelP = Subtract(World, &ChunkCenterP, &GameState->CameraP); // TODO(casey): This is super inefficient fix it! real32 FurthestBufferLengthSq = 0.0f; ground_buffer *FurthestBuffer = 0; for(uint32 GroundBufferIndex = 0; GroundBufferIndex < TranState->GroundBufferCount; ++GroundBufferIndex) { ground_buffer *GroundBuffer = TranState->GroundBuffers + GroundBufferIndex; if(AreInSameChunk(World, &GroundBuffer->P, &ChunkCenterP)) { FurthestBuffer = 0; break; } else if(IsValid(GroundBuffer->P)) { v3 RelP = Subtract(World, &GroundBuffer->P, &GameState->CameraP); real32 BufferLengthSq = LengthSq(RelP.xy); if(FurthestBufferLengthSq < BufferLengthSq) { FurthestBufferLengthSq = BufferLengthSq; FurthestBuffer = GroundBuffer; } } else { FurthestBufferLengthSq = Real32Maximum; FurthestBuffer = GroundBuffer; } } if(FurthestBuffer) { FillGroundChunk(TranState, GameState, FurthestBuffer, &ChunkCenterP); } } } } } } // TODO(casey): How big do we actually want to expand here? // TODO(casey): Do we want to simulate upper floors, etc.? v3 SimBoundsExpansion = {15.0f, 15.0f, 0.0f}; rectangle3 SimBounds = AddRadiusTo(CameraBoundsInMeters, SimBoundsExpansion); temporary_memory SimMemory = BeginTemporaryMemory(&TranState->TranArena); world_position SimCenterP = GameState->CameraP; sim_region *SimRegion = BeginSim(&TranState->TranArena, GameState, GameState->World, SimCenterP, SimBounds, Input->dtForFrame); v3 CameraP = Subtract(World, &GameState->CameraP, &SimCenterP); PushRectOutline(RenderGroup, V3(0.0f, 0.0f, 0.0f), GetDim(ScreenBounds), V4(1.0f, 1.0f, 0.0f, 1)); // PushRectOutline(RenderGroup, V3(0.0f, 0.0f, 0.0f), GetDim(CameraBoundsInMeters).xy, V4(1.0f, 1.0f, 1.0f, 1)); PushRectOutline(RenderGroup, V3(0.0f, 0.0f, 0.0f), GetDim(SimBounds).xy, V4(0.0f, 1.0f, 1.0f, 1)); PushRectOutline(RenderGroup, V3(0.0f, 0.0f, 0.0f), GetDim(SimRegion->Bounds).xy, V4(1.0f, 0.0f, 1.0f, 1)); // TODO(casey): Move this out into handmade_entity.cpp! for(uint32 EntityIndex = 0; EntityIndex < SimRegion->EntityCount; ++EntityIndex) { sim_entity *Entity = SimRegion->Entities + EntityIndex; if(Entity->Updatable) { real32 dt = Input->dtForFrame; // TODO(casey): This is incorrect, should be computed after update!!!! real32 ShadowAlpha = 1.0f - 0.5f*Entity->P.z; if(ShadowAlpha < 0) { ShadowAlpha = 0.0f; } move_spec MoveSpec = DefaultMoveSpec(); v3 ddP = {}; // TODO(casey): Probably indicates we want to separate update and render // for entities sometime soon? v3 CameraRelativeGroundP = GetEntityGroundPoint(Entity) - CameraP; real32 FadeTopEndZ = 0.75f*GameState->TypicalFloorHeight; real32 FadeTopStartZ = 0.5f*GameState->TypicalFloorHeight; real32 FadeBottomStartZ = -2.0f*GameState->TypicalFloorHeight; real32 FadeBottomEndZ = -2.25f*GameState->TypicalFloorHeight;;; RenderGroup->GlobalAlpha = 1.0f; if(CameraRelativeGroundP.z > FadeTopStartZ) { RenderGroup->GlobalAlpha = Clamp01MapToRange(FadeTopEndZ, CameraRelativeGroundP.z, FadeTopStartZ); } else if(CameraRelativeGroundP.z < FadeBottomStartZ) { RenderGroup->GlobalAlpha = Clamp01MapToRange(FadeBottomEndZ, CameraRelativeGroundP.z, FadeBottomStartZ); } // // NOTE(casey): Pre-physics entity work // hero_bitmap_ids HeroBitmaps = {}; asset_vector MatchVector = {}; MatchVector.E[Tag_FacingDirection] = Entity->FacingDirection; asset_vector WeightVector = {}; WeightVector.E[Tag_FacingDirection] = 1.0f; HeroBitmaps.Head = GetBestMatchBitmapFrom(TranState->Assets, Asset_Head, &MatchVector, &WeightVector); HeroBitmaps.Cape = GetBestMatchBitmapFrom(TranState->Assets, Asset_Cape, &MatchVector, &WeightVector); HeroBitmaps.Torso = GetBestMatchBitmapFrom(TranState->Assets, Asset_Torso, &MatchVector, &WeightVector); switch(Entity->Type) { case EntityType_Hero: { // TODO(casey): Now that we have some real usage examples, let's solidify // the positioning system! for(uint32 ControlIndex = 0; ControlIndex < ArrayCount(GameState->ControlledHeroes); ++ControlIndex) { controlled_hero *ConHero = GameState->ControlledHeroes + ControlIndex; if(Entity->StorageIndex == ConHero->EntityIndex) { if(ConHero->dZ != 0.0f) { Entity->dP.z = ConHero->dZ; } MoveSpec.UnitMaxAccelVector = true; MoveSpec.Speed = 50.0f; MoveSpec.Drag = 8.0f; ddP = V3(ConHero->ddP, 0); if((ConHero->dSword.x != 0.0f) || (ConHero->dSword.y != 0.0f)) { sim_entity *Sword = Entity->Sword.Ptr; if(Sword && IsSet(Sword, EntityFlag_Nonspatial)) { Sword->DistanceLimit = 5.0f; MakeEntitySpatial(Sword, Entity->P, Entity->dP + 5.0f*V3(ConHero->dSword, 0)); AddCollisionRule(GameState, Sword->StorageIndex, Entity->StorageIndex, false); PlaySound(&GameState->AudioState, GetRandomSoundFrom(TranState->Assets, Asset_Bloop, &GameState->EffectsEntropy)); } } } } } break; case EntityType_Sword: { MoveSpec.UnitMaxAccelVector = false; MoveSpec.Speed = 0.0f; MoveSpec.Drag = 0.0f; if(Entity->DistanceLimit == 0.0f) { ClearCollisionRulesFor(GameState, Entity->StorageIndex); MakeEntityNonSpatial(Entity); } } break; case EntityType_Familiar: { sim_entity *ClosestHero = 0; real32 ClosestHeroDSq = Square(10.0f); // NOTE(casey): Ten meter maximum search! DEBUG_IF(AI_Familiar_FollowsHero) { // TODO(casey): Make spatial queries easy for things! sim_entity *TestEntity = SimRegion->Entities; for(uint32 TestEntityIndex = 0; TestEntityIndex < SimRegion->EntityCount; ++TestEntityIndex, ++TestEntity) { if(TestEntity->Type == EntityType_Hero) { real32 TestDSq = LengthSq(TestEntity->P - Entity->P); if(ClosestHeroDSq > TestDSq) { ClosestHero = TestEntity; ClosestHeroDSq = TestDSq; } } } } if(ClosestHero && (ClosestHeroDSq > Square(3.0f))) { real32 Acceleration = 0.5f; real32 OneOverLength = Acceleration / SquareRoot(ClosestHeroDSq); ddP = OneOverLength*(ClosestHero->P - Entity->P); } MoveSpec.UnitMaxAccelVector = true; MoveSpec.Speed = 50.0f; MoveSpec.Drag = 8.0f; } break; } if(!IsSet(Entity, EntityFlag_Nonspatial) && IsSet(Entity, EntityFlag_Moveable)) { MoveEntity(GameState, SimRegion, Entity, Input->dtForFrame, &MoveSpec, ddP); } RenderGroup->Transform.OffsetP = GetEntityGroundPoint(Entity); // // NOTE(casey): Post-physics entity work // switch(Entity->Type) { case EntityType_Hero: { // TODO(casey): Z!!! real32 HeroSizeC = 2.5f; PushBitmap(RenderGroup, GetFirstBitmapFrom(TranState->Assets, Asset_Shadow), HeroSizeC*1.0f, V3(0, 0, 0), V4(1, 1, 1, ShadowAlpha)); PushBitmap(RenderGroup, HeroBitmaps.Torso, HeroSizeC*1.2f, V3(0, 0, 0)); PushBitmap(RenderGroup, HeroBitmaps.Cape, HeroSizeC*1.2f, V3(0, 0, 0)); PushBitmap(RenderGroup, HeroBitmaps.Head, HeroSizeC*1.2f, V3(0, 0, 0)); DrawHitpoints(Entity, RenderGroup); DEBUG_IF(Particles_Test) { for(u32 ParticleSpawnIndex = 0; ParticleSpawnIndex < 3; ++ParticleSpawnIndex) { particle *Particle = GameState->Particles + GameState->NextParticle++; if(GameState->NextParticle >= ArrayCount(GameState->Particles)) { GameState->NextParticle = 0; } Particle->P = V3(RandomBetween(&GameState->EffectsEntropy, -0.05f, 0.05f), 0, 0); Particle->dP = V3(RandomBetween(&GameState->EffectsEntropy, -0.01f, 0.01f), 7.0f*RandomBetween(&GameState->EffectsEntropy, 0.7f, 1.0f), 0.0f); Particle->ddP = V3(0.0f, -9.8f, 0.0f); Particle->Color = V4(RandomBetween(&GameState->EffectsEntropy, 0.75f, 1.0f), RandomBetween(&GameState->EffectsEntropy, 0.75f, 1.0f), RandomBetween(&GameState->EffectsEntropy, 0.75f, 1.0f), 1.0f); Particle->dColor = V4(0, 0, 0, -0.25f); asset_vector MatchVector = {}; asset_vector WeightVector = {}; char Nothings[] = "NOTHINGS"; MatchVector.E[Tag_UnicodeCodepoint] = (r32)Nothings[RandomChoice(&GameState->EffectsEntropy, ArrayCount(Nothings) - 1)]; WeightVector.E[Tag_UnicodeCodepoint] = 1.0f; Particle->BitmapID = HeroBitmaps.Head; //GetBestMatchBitmapFrom(TranState->Assets, Asset_Font, // &MatchVector, &WeightVector); // Particle->BitmapID = GetRandomBitmapFrom(TranState->Assets, Asset_Font, &GameState->EffectsEntropy); } // NOTE(casey): Particle system test ZeroStruct(GameState->ParticleCels); r32 GridScale = 0.25f; r32 InvGridScale = 1.0f / GridScale; v3 GridOrigin = {-0.5f*GridScale*PARTICLE_CEL_DIM, 0.0f, 0.0f}; for(u32 ParticleIndex = 0; ParticleIndex < ArrayCount(GameState->Particles); ++ParticleIndex) { particle *Particle = GameState->Particles + ParticleIndex; v3 P = InvGridScale*(Particle->P - GridOrigin); s32 X = TruncateReal32ToInt32(P.x); s32 Y = TruncateReal32ToInt32(P.y); if(X < 0) {X = 0;} if(X > (PARTICLE_CEL_DIM - 1)) {X = (PARTICLE_CEL_DIM - 1);} if(Y < 0) {Y = 0;} if(Y > (PARTICLE_CEL_DIM - 1)) {Y = (PARTICLE_CEL_DIM - 1);} particle_cel *Cel = &GameState->ParticleCels[Y][X]; real32 Density = Particle->Color.a; Cel->Density += Density; Cel->VelocityTimesDensity += Density*Particle->dP; } DEBUG_IF(Particles_ShowGrid) { for(u32 Y = 0; Y < PARTICLE_CEL_DIM; ++Y) { for(u32 X = 0; X < PARTICLE_CEL_DIM; ++X) { particle_cel *Cel = &GameState->ParticleCels[Y][X]; real32 Alpha = Clamp01(0.1f*Cel->Density); PushRect(RenderGroup, GridScale*V3((r32)X, (r32)Y, 0) + GridOrigin, GridScale*V2(1.0f, 1.0f), V4(Alpha, Alpha, Alpha, 1.0f)); } } } for(u32 ParticleIndex = 0; ParticleIndex < ArrayCount(GameState->Particles); ++ParticleIndex) { particle *Particle = GameState->Particles + ParticleIndex; v3 P = InvGridScale*(Particle->P - GridOrigin); s32 X = TruncateReal32ToInt32(P.x); s32 Y = TruncateReal32ToInt32(P.y); if(X < 1) {X = 1;} if(X > (PARTICLE_CEL_DIM - 2)) {X = (PARTICLE_CEL_DIM - 2);} if(Y < 1) {Y = 1;} if(Y > (PARTICLE_CEL_DIM - 2)) {Y = (PARTICLE_CEL_DIM - 2);} particle_cel *CelCenter = &GameState->ParticleCels[Y][X]; particle_cel *CelLeft = &GameState->ParticleCels[Y][X - 1]; particle_cel *CelRight = &GameState->ParticleCels[Y][X + 1]; particle_cel *CelDown = &GameState->ParticleCels[Y - 1][X]; particle_cel *CelUp = &GameState->ParticleCels[Y + 1][X]; v3 Dispersion = {}; real32 Dc = 1.0f; Dispersion += Dc*(CelCenter->Density - CelLeft->Density)*V3(-1.0f, 0.0f, 0.0f); Dispersion += Dc*(CelCenter->Density - CelRight->Density)*V3(1.0f, 0.0f, 0.0f); Dispersion += Dc*(CelCenter->Density - CelDown->Density)*V3(0.0f, -1.0f, 0.0f); Dispersion += Dc*(CelCenter->Density - CelUp->Density)*V3(0.0f, 1.0f, 0.0f); v3 ddP = Particle->ddP + Dispersion; // NOTE(casey): Simulate the particle forward in time Particle->P += (0.5f*Square(Input->dtForFrame)*Input->dtForFrame*ddP + Input->dtForFrame*Particle->dP); Particle->dP += Input->dtForFrame*ddP; Particle->Color += Input->dtForFrame*Particle->dColor; if(Particle->P.y < 0.0f) { r32 CoefficientOfRestitution = 0.3f; r32 CoefficientOfFriction = 0.7f; Particle->P.y = -Particle->P.y; Particle->dP.y = -CoefficientOfRestitution*Particle->dP.y; Particle->dP.x = CoefficientOfFriction*Particle->dP.x; } // TODO(casey): Shouldn't we just clamp colors in the renderer?? v4 Color; Color.r = Clamp01(Particle->Color.r); Color.g = Clamp01(Particle->Color.g); Color.b = Clamp01(Particle->Color.b); Color.a = Clamp01(Particle->Color.a); if(Color.a > 0.9f) { Color.a = 0.9f*Clamp01MapToRange(1.0f, Color.a, 0.9f); } // NOTE(casey): Render the particle PushBitmap(RenderGroup, Particle->BitmapID, 1.0f, Particle->P, Color); } } } break; case EntityType_Wall: { PushBitmap(RenderGroup, GetFirstBitmapFrom(TranState->Assets, Asset_Tree), 2.5f, V3(0, 0, 0)); } break; case EntityType_Stairwell: { PushRect(RenderGroup, V3(0, 0, 0), Entity->WalkableDim, V4(1, 0.5f, 0, 1)); PushRect(RenderGroup, V3(0, 0, Entity->WalkableHeight), Entity->WalkableDim, V4(1, 1, 0, 1)); } break; case EntityType_Sword: { PushBitmap(RenderGroup, GetFirstBitmapFrom(TranState->Assets, Asset_Shadow), 0.5f, V3(0, 0, 0), V4(1, 1, 1, ShadowAlpha)); PushBitmap(RenderGroup, GetFirstBitmapFrom(TranState->Assets, Asset_Sword), 0.5f, V3(0, 0, 0)); } break; case EntityType_Familiar: { Entity->tBob += dt; if(Entity->tBob > Tau32) { Entity->tBob -= Tau32; } real32 BobSin = Sin(2.0f*Entity->tBob); PushBitmap(RenderGroup, GetFirstBitmapFrom(TranState->Assets, Asset_Shadow), 2.5f, V3(0, 0, 0), V4(1, 1, 1, (0.5f*ShadowAlpha) + 0.2f*BobSin)); PushBitmap(RenderGroup, HeroBitmaps.Head, 2.5f, V3(0, 0, 0.25f*BobSin)); } break; case EntityType_Monstar: { PushBitmap(RenderGroup, GetFirstBitmapFrom(TranState->Assets, Asset_Shadow), 4.5f, V3(0, 0, 0), V4(1, 1, 1, ShadowAlpha)); PushBitmap(RenderGroup, HeroBitmaps.Torso, 4.5f, V3(0, 0, 0)); DrawHitpoints(Entity, RenderGroup); } break; case EntityType_Space: { DEBUG_IF(Simulation_UseSpaceOutlines) { for(uint32 VolumeIndex = 0; VolumeIndex < Entity->Collision->VolumeCount; ++VolumeIndex) { sim_entity_collision_volume *Volume = Entity->Collision->Volumes + VolumeIndex; PushRectOutline(RenderGroup, Volume->OffsetP - V3(0, 0, 0.5f*Volume->Dim.z), Volume->Dim.xy, V4(0, 0.5f, 1.0f, 1)); } } } break; default: { InvalidCodePath; } break; } if(DEBUG_UI_ENABLED) { debug_id EntityDebugID = DEBUG_POINTER_ID(GameState->LowEntities + Entity->StorageIndex); for(uint32 VolumeIndex = 0; VolumeIndex < Entity->Collision->VolumeCount; ++VolumeIndex) { sim_entity_collision_volume *Volume = Entity->Collision->Volumes + VolumeIndex; v3 LocalMouseP = Unproject(RenderGroup, MouseP); if((LocalMouseP.x > -0.5f*Volume->Dim.x) && (LocalMouseP.x < 0.5f*Volume->Dim.x) && (LocalMouseP.y > -0.5f*Volume->Dim.y) && (LocalMouseP.y < 0.5f*Volume->Dim.y)) { DEBUG_HIT(EntityDebugID, LocalMouseP.z); } v4 OutlineColor; if(DEBUG_HIGHLIGHTED(EntityDebugID, &OutlineColor)) { PushRectOutline(RenderGroup, Volume->OffsetP - V3(0, 0, 0.5f*Volume->Dim.z), Volume->Dim.xy, OutlineColor, 0.05f); } } if(DEBUG_REQUESTED(EntityDebugID)) { DEBUG_BEGIN_DATA_BLOCK("Simulation Entity", EntityDebugID); DEBUG_VALUE(Entity->StorageIndex); DEBUG_VALUE(Entity->Updatable); DEBUG_VALUE(Entity->Type); DEBUG_VALUE(Entity->P); DEBUG_VALUE(Entity->dP); DEBUG_VALUE(Entity->DistanceLimit); DEBUG_VALUE(Entity->FacingDirection); DEBUG_VALUE(Entity->tBob); DEBUG_VALUE(Entity->dAbsTileZ); DEBUG_VALUE(Entity->HitPointMax); DEBUG_VALUE(HeroBitmaps.Torso); #if 0 DEBUG_BEGIN_ARRAY(Entity->HitPoint); for(u32 HitPointIndex = 0; HitPointIndex < Entity->HitPointMax; ++HitPointIndex) { DEBUG_VALUE(Entity->HitPoint[HitPointIndex]); } DEBUG_END_ARRAY(); DEBUG_VALUE(Entity->Sword); #endif DEBUG_VALUE(Entity->WalkableDim); DEBUG_VALUE(Entity->WalkableHeight); DEBUG_END_DATA_BLOCK(); } } } } RenderGroup->GlobalAlpha = 1.0f; #if 0 GameState->Time += Input->dtForFrame; v3 MapColor[] = { {1, 0, 0}, {0, 1, 0}, {0, 0, 1}, }; for(uint32 MapIndex = 0; MapIndex < ArrayCount(TranState->EnvMaps); ++MapIndex) { environment_map *Map = TranState->EnvMaps + MapIndex; loaded_bitmap *LOD = Map->LOD + 0; bool32 RowCheckerOn = false; int32 CheckerWidth = 16; int32 CheckerHeight = 16; rectangle2i ClipRect = {0, 0, LOD->Width, LOD->Height}; for(int32 Y = 0; Y < LOD->Height; Y += CheckerHeight) { bool32 CheckerOn = RowCheckerOn; for(int32 X = 0; X < LOD->Width; X += CheckerWidth) { v4 Color = CheckerOn ? V4(MapColor[MapIndex], 1.0f) : V4(0, 0, 0, 1); v2 MinP = V2i(X, Y); v2 MaxP = MinP + V2i(CheckerWidth, CheckerHeight); DrawRectangle(LOD, MinP, MaxP, Color, ClipRect, true); DrawRectangle(LOD, MinP, MaxP, Color, ClipRect, false); CheckerOn = !CheckerOn; } RowCheckerOn = !RowCheckerOn; } } TranState->EnvMaps[0].Pz = -1.5f; TranState->EnvMaps[1].Pz = 0.0f; TranState->EnvMaps[2].Pz = 1.5f; DrawBitmap(TranState->EnvMaps[0].LOD + 0, &TranState->GroundBuffers[TranState->GroundBufferCount - 1].Bitmap, 125.0f, 50.0f, 1.0f); // Angle = 0.0f; // TODO(casey): Let's add a perp operator!!! v2 Origin = ScreenCenter; real32 Angle = 0.1f*GameState->Time; #if 1 v2 Disp = {100.0f*Cos(5.0f*Angle), 100.0f*Sin(3.0f*Angle)}; #else v2 Disp = {}; #endif #if 1 v2 XAxis = 100.0f*V2(Cos(10.0f*Angle), Sin(10.0f*Angle)); v2 YAxis = Perp(XAxis); #else v2 XAxis = {100.0f, 0}; v2 YAxis = {0, 100.0f}; #endif uint32 PIndex = 0; real32 CAngle = 5.0f*Angle; #if 0 v4 Color = V4(0.5f+0.5f*Sin(CAngle), 0.5f+0.5f*Sin(2.9f*CAngle), 0.5f+0.5f*Cos(9.9f*CAngle), 0.5f+0.5f*Sin(10.0f*CAngle)); #else v4 Color = V4(1.0f, 1.0f, 1.0f, 1.0f); #endif CoordinateSystem(RenderGroup, Disp + Origin - 0.5f*XAxis - 0.5f*YAxis, XAxis, YAxis, Color, &GameState->TestDiffuse, &GameState->TestNormal, TranState->EnvMaps + 2, TranState->EnvMaps + 1, TranState->EnvMaps + 0); v2 MapP = {0.0f, 0.0f}; for(uint32 MapIndex = 0; MapIndex < ArrayCount(TranState->EnvMaps); ++MapIndex) { environment_map *Map = TranState->EnvMaps + MapIndex; loaded_bitmap *LOD = Map->LOD + 0; XAxis = 0.5f * V2((real32)LOD->Width, 0.0f); YAxis = 0.5f * V2(0.0f, (real32)LOD->Height); CoordinateSystem(RenderGroup, MapP, XAxis, YAxis, V4(1.0f, 1.0f, 1.0f, 1.0f), LOD, 0, 0, 0, 0); MapP += YAxis + V2(0.0f, 6.0f); } #endif Orthographic(RenderGroup, DrawBuffer->Width, DrawBuffer->Height, 1.0f); PushRectOutline(RenderGroup, V3(MouseP, 0.0f), V2(2.0f, 2.0f)); TiledRenderGroupToOutput(TranState->HighPriorityQueue, RenderGroup, DrawBuffer); EndRender(RenderGroup); // TODO(casey): Make sure we hoist the camera update out to a place where the renderer // can know about the location of the camera at the end of the frame so there isn't // a frame of lag in camera updating compared to the hero. EndSim(SimRegion, GameState); EndTemporaryMemory(SimMemory); EndTemporaryMemory(RenderMemory); CheckArena(&GameState->WorldArena); CheckArena(&TranState->TranArena); } extern "C" GAME_GET_SOUND_SAMPLES(GameGetSoundSamples) { game_state *GameState = (game_state *)Memory->PermanentStorage; transient_state *TranState = (transient_state *)Memory->TransientStorage; OutputPlayingSounds(&GameState->AudioState, SoundBuffer, TranState->Assets, &TranState->TranArena); } #if HANDMADE_INTERNAL #include "handmade_debug.cpp" #else extern "C" DEBUG_GAME_FRAME_END(DEBUGGameFrameEnd) { return(0); } #endif