Files
handmadehero/handmade.c
T
2026-04-20 08:40:29 -07:00

476 lines
15 KiB
C

#include "core.h"
#include "handmade.h"
#include "sdl_handmade.h"
#include "handmade_intrinsics.h"
#include "handmade_random.h"
#include "handmade_tile.h"
#include "handmade_tile.c"
#include <string.h> /* memset */
static void GameOutputSound(game_state *GameState, game_sound_output_buffer *SoundBuffer, S32 ToneHz)
{
/*
S16 ToneVolume, *SampleOut;
S32 WavePeriod, SampleIndex;
ToneVolume = 0;
WavePeriod = SoundBuffer->SamplesPerSecond / ToneHz;
SampleOut = SoundBuffer->Samples;
for(SampleIndex = 0;
SampleIndex < SoundBuffer->SampleCount;
SampleIndex++)
{
F32 SineValue;
S16 SampleValue;
SineValue = sinf(GameState->tSine);
SampleValue = (S16)(SineValue * ToneVolume);
*SampleOut++ = SampleValue;
*SampleOut++ = SampleValue;
GameState->tSine += 2.0f * (F32)PI * 1.0f / (F32)WavePeriod;
if(GameState->tSine > 2.0f*(F32)PI) {
GameState->tSine -= 2.0f*(F32)PI;
}
}
*/
}
static void ClearBackground(game_offscreen_buffer *Buffer)
{
S32 Width = Buffer->Width;
S32 Height = Buffer->Height;
for(S32 X = 0; X < Width * Height; X++) {
U32 *Pixel = &((U32 *)Buffer->Memory)[X];
*Pixel = 0xFFFFFF;
}
}
static void DrawRectangle(game_offscreen_buffer *Buffer, F32 RealMinX, F32 RealMinY, F32 RealMaxX, F32 RealMaxY, F32 R, F32 G, F32 B)
{
S32 MinX = RoundF32toS32(RealMinX);
S32 MinY = RoundF32toS32(RealMinY);
S32 MaxX = RoundF32toS32(RealMaxX);
S32 MaxY = RoundF32toS32(RealMaxY);
S32 Width = Buffer->Width;
S32 Height = Buffer->Height;
if(MinX < 0)
MinX = 0;
if(MinY < 0)
MinY = 0;
if(MaxX > Width)
MaxX = Width;
if(MaxY > Height)
MaxY = Height;
U32 Color = (U32)((RoundF32toS32(R * 255.0f) << 16) | (RoundF32toS32(G * 255.0f) << 8) | (RoundF32toS32(B * 255.0f) << 0));
U8 *Row = ((U8 *)Buffer->Memory + MinX*Buffer->BytesPerPixel + MinY*Buffer->Pitch);
for(S32 Y = MinY; Y < MaxY; Y++) {
U32 *Pixel = (U32 *)Row;
for(S32 X = MinX; X < MaxX; X++) {
*(U32 *)Pixel = Color;
Pixel++;
}
Row += Buffer->Pitch;
}
}
static void DrawBitmap(game_offscreen_buffer *Buffer, loaded_bitmap *Bitmap, F32 RelX, F32 RelY)
{
S32 MinX = RoundF32toS32(RelX);
S32 MinY = RoundF32toS32(RelY);
S32 MaxX = RoundF32toS32(RelX + (F32)Bitmap->Width);
S32 MaxY = RoundF32toS32(RelY + (F32)Bitmap->Height);
S32 Width = Buffer->Width;
S32 Height = Buffer->Height;
if(MinX < 0)
MinX = 0;
if(MinY < 0)
MinY = 0;
if(MaxX > Width)
MaxX = Width;
if(MaxY > Height)
MaxY = Height;
U32 *SourceRow = Bitmap->Pixels + Bitmap->Width*(Bitmap->Height - 1);
U8 *DestRow = ((U8 *)Buffer->Memory + MinX*Buffer->BytesPerPixel + MinY*Buffer->Pitch);
for(S32 Y = MinY; Y < MaxY; Y++) {
U32 *Dest = (U32 *)DestRow;
U32 *Source = SourceRow;
for(S32 X = MinX; X < MaxX; X++) {
*Dest++ = *Source++;
}
DestRow += Buffer->Pitch;
SourceRow -= Bitmap->Width;
}
}
#pragma pack(push, 1)
typedef struct bitmap_header {
U16 FileType;
U32 FileSize;
U16 Reserved1;
U16 Reserved2;
U32 BitmapOffset;
U32 Size;
S32 Width;
S32 Height;
U16 Planes;
U16 BitsPerPixel;
U32 Compression;
U32 SizeOfBitmap;
S32 HorzResolution;
S32 VertResolution;
U32 ColorsUsed;
U32 ColorsImportant;
U32 RedMask;
U32 GreenMask;
U32 BlueMask;
} bitmap_header;
#pragma pack(pop)
static loaded_bitmap DEBUGLoadBMP(thread_context *Thread, debug_read_file_result (*DEBUGPlatformReadEntireFile)(thread_context *Thread, const char *), char *FileName)
{
loaded_bitmap Result;
memset(&Result, 0, sizeof(Result));
// NOTE: Byte order in memory is AA BB GG RR, bottom up.
// Littel-endian: 0xRRGGBBAA
debug_read_file_result ReadResult = DEBUGPlatformReadEntireFile(Thread, FileName);
if(ReadResult.ContentsSize > 0) {
bitmap_header *Header = (bitmap_header *)ReadResult.Contents;
U32 *Pixels = (U32 *)((U8 *)ReadResult.Contents + Header->BitmapOffset);
Result.Pixels = Pixels;
Result.Width = Header->Width;
Result.Height = Header->Height;
// TODO: Is this too outdated? As of 2026, this loads fine on little-endian.
// Maybe my BMP uses a newer format.
/*
U32 *SourceDest = Pixels;
for(S32 Y = 0; Y < Header->Height; Y++) {
for(S32 X = 0; X < Header->Width; X++) {
*SourceDest = (*SourceDest >> 8) | (*SourceDest << 24);
SourceDest++;
}
}
*/
}
return Result;
}
void UpdateAndRender(thread_context *Thread, game_memory *Memory, game_input *Input, game_offscreen_buffer *Buffer, game_sound_output_buffer *SoundBuffer)
{
ASSERT((&Input->Controllers[0].Terminator - &Input->Controllers[0].Buttons[0]) == ARRAY_SIZE(Input->Controllers[0].Buttons));
ASSERT(sizeof(game_state) <= Memory->PermanentStorageSize);
#define TILEMAP_COUNT_X 256
#define TILEMAP_COUNT_Y 256
game_state *GameState;
F32 PlayerHeight = 1.4f;
F32 PlayerWidth = 0.75f * PlayerHeight;
GameState = (game_state *)Memory->PermanentStorage;
if(!Memory->IsInitialized) {
GameState->Backdrop = DEBUGLoadBMP(Thread, Memory->DEBUGPlatformReadEntireFile, "data/background.bmp");
GameState->HeroHead = DEBUGLoadBMP(Thread, Memory->DEBUGPlatformReadEntireFile, "data/hero_head_front.bmp");;
GameState->HeroCape = DEBUGLoadBMP(Thread, Memory->DEBUGPlatformReadEntireFile, "data/hero_cape_front.bmp");;
GameState->HeroTorso = DEBUGLoadBMP(Thread, Memory->DEBUGPlatformReadEntireFile, "data/hero_torso_front.bmp");;
GameState->PlayerP.AbsTileX = 2;
GameState->PlayerP.AbsTileY = 3;
GameState->PlayerP.OffsetX = 5.0f;
GameState->PlayerP.OffsetY = 5.0f;
InitializeArena(&GameState->WorldArena, Memory->PermanentStorageSize - sizeof(game_state), (U8 *)Memory->PermanentStorage + sizeof(game_state));
GameState->World = PUSH_STRUCT(&GameState->WorldArena, world);
world *World = GameState->World;
World->TileMap = PUSH_STRUCT(&GameState->WorldArena, tile_map);
tile_map *TileMap = World->TileMap;
TileMap->ChunkShift = 4;
TileMap->ChunkMask = (1 << TileMap->ChunkShift) - 1;
TileMap->ChunkDim = (1 << TileMap->ChunkShift);
TileMap->TileChunkCountX = 128;
TileMap->TileChunkCountY = 128;
TileMap->TileChunkCountZ = 2;
TileMap->TileChunks = PUSH_ARRAY(&GameState->WorldArena, TileMap->TileChunkCountX * TileMap->TileChunkCountY * TileMap->TileChunkCountZ, tile_chunk);
TileMap->TileSideInMeters = 1.4f;
U32 RandomNumberIndex = 0;
U32 TilesPerWidth = 17;
U32 TilesPerHeight = 9;
U32 ScreenX = 0;
U32 ScreenY = 0;
U32 AbsTileZ = 0;
B32 DoorTop = FALSE;
B32 DoorBottom = FALSE;
B32 DoorLeft = FALSE;
B32 DoorRight = FALSE;
B32 DoorUp = FALSE;
B32 DoorDown = FALSE;
for(U32 ScreenIndex = 0; ScreenIndex < 100; ScreenIndex++) {
ASSERT(RandomNumberIndex < ARRAY_SIZE(RandomNumberTable));
/* TODO: Random number generator. */
U32 RandomChoice;
if(DoorUp || DoorDown) {
RandomChoice = RandomNumberTable[RandomNumberIndex++] % 2;
} else {
RandomChoice = RandomNumberTable[RandomNumberIndex++] % 3;
}
B32 CreatedZDoor = FALSE;
if(RandomChoice == 2) {
CreatedZDoor = TRUE;
if(AbsTileZ == 0) {
DoorUp = TRUE;
} else {
DoorDown = TRUE;
}
} else if(RandomChoice == 1) {
DoorRight = TRUE;
} else {
DoorTop = TRUE;
}
for(U32 TileY = 0; TileY < TilesPerHeight; TileY++) {
for(U32 TileX = 0; TileX < TilesPerWidth; TileX++) {
U32 AbsTileX = ScreenX * TilesPerWidth + TileX;
U32 AbsTileY = ScreenY * TilesPerHeight + TileY;
U32 TileValue = 1;
if((TileX == 0) && (!DoorLeft || (TileY != (TilesPerHeight/2)))) {
TileValue = 2;
}
if(((TileX == (TilesPerWidth - 1)) && (!DoorRight || (TileY != (TilesPerHeight/2))))) {
TileValue = 2;
}
if((TileY == 0) && (!DoorBottom || (TileX != (TilesPerWidth/2)))) {
TileValue = 2;
}
if((TileY == (TilesPerHeight - 1)) && (!DoorTop || (TileX != (TilesPerWidth/2)))) {
TileValue = 2;
}
if((TileX == 10) && (TileY == 6)) {
if(DoorUp) {
TileValue = 3;
}
if(DoorDown) {
TileValue = 4;
}
}
SetTileValue(&GameState->WorldArena, World->TileMap, AbsTileX, AbsTileY, AbsTileZ, TileValue);
}
}
DoorLeft = DoorRight;
DoorBottom = DoorTop;
if(CreatedZDoor) {
DoorDown = !DoorDown;
DoorUp = !DoorUp;
} else {
DoorUp = FALSE;
DoorDown = FALSE;
}
DoorTop = FALSE;
DoorRight = FALSE;
if(RandomChoice == 2) {
if(AbsTileZ == 0) {
AbsTileZ = 1;
} else {
AbsTileZ = 0;
}
} else if(RandomChoice == 1) {
ScreenX++;
} else {
ScreenY++;
}
}
Memory->IsInitialized = TRUE;
}
world *World = GameState->World;
tile_map *TileMap = World->TileMap;
S32 TileSideInPixels = 60;
F32 MetersToPixels = (F32)TileSideInPixels / TileMap->TileSideInMeters;
for(U32 ControllerIndex = 0; ControllerIndex < ARRAY_SIZE(Input->Controllers); ControllerIndex++) {
game_controller_input *Controller = GetController(Input, ControllerIndex);
if(Controller->IsAnalog) {
} else {
F32 dPlayerX = 0.0f;
F32 dPlayerY = 0.0f;
if(Controller->MoveUp.EndedDown) {
dPlayerY = 1.0f;
}
if(Controller->MoveDown.EndedDown) {
dPlayerY = -1.0f;
}
if(Controller->MoveLeft.EndedDown) {
dPlayerX = -1.0f;
}
if(Controller->MoveRight.EndedDown) {
dPlayerX = 1.0f;
}
F32 PlayerSpeed = 2.0f;
if(Controller->ActionUp.EndedDown)
PlayerSpeed = 10.0f;
dPlayerX *= PlayerSpeed;
dPlayerY *= PlayerSpeed;
tile_map_position NewPlayerP;
NewPlayerP = GameState->PlayerP;
NewPlayerP.OffsetX += Input->dtForFrame*dPlayerX;
NewPlayerP.OffsetY += Input->dtForFrame*dPlayerY;
NewPlayerP = RecannonicalizePosition(TileMap, NewPlayerP);
tile_map_position PlayerLeft;
PlayerLeft = NewPlayerP;
PlayerLeft.OffsetX -= 0.5f*PlayerWidth;
PlayerLeft = RecannonicalizePosition(TileMap, PlayerLeft);
tile_map_position PlayerRight;
PlayerRight = NewPlayerP;
PlayerRight.OffsetX += 0.5f*PlayerWidth;
PlayerRight = RecannonicalizePosition(TileMap, PlayerRight);
if(IsTileMapPointEmpty(TileMap, NewPlayerP) && IsTileMapPointEmpty(TileMap, PlayerLeft) && IsTileMapPointEmpty(TileMap, PlayerRight)) {
if(!AreOnSameTile(&GameState->PlayerP, &NewPlayerP)) {
U32 NewTileValue = GetTileValueByPos(TileMap, NewPlayerP);
if(NewTileValue == 3) {
NewPlayerP.AbsTileZ++;
} else if(NewTileValue == 4) {
NewPlayerP.AbsTileZ--;
}
}
GameState->PlayerP = NewPlayerP;
}
}
}
DrawRectangle(Buffer, 0.0f, 0.0f, (F32)Buffer->Width, (F32)Buffer->Height, 0.0f, 1.0f, 0.0f);
DrawBitmap(Buffer, &GameState->Backdrop, 0.0f, 0.0f);
F32 ScreenCenterX = 0.5f * (F32)Buffer->Width;
F32 ScreenCenterY = 0.5f * (F32)Buffer->Height;
for(S32 RelRow = -10; RelRow < 10; RelRow++) {
for(S32 RelColumn = -20; RelColumn < 20; RelColumn++) {
U32 Column = GameState->PlayerP.AbsTileX + RelColumn;
U32 Row = GameState->PlayerP.AbsTileY + RelRow;
U32 TileID = GetTileValue(TileMap, Column, Row, GameState->PlayerP.AbsTileZ);
if(TileID > 1) {
F32 Gray = 0.5f;
if(TileID == 2) {
Gray = 1.0f;
}
if(TileID > 2) {
Gray = 0.25f;
}
if((Column == GameState->PlayerP.AbsTileX) && (Row == GameState->PlayerP.AbsTileY)) {
Gray = 0.0f;
}
F32 CenterX = ScreenCenterX - MetersToPixels*GameState->PlayerP.OffsetX + (F32)RelColumn*(F32)TileSideInPixels;
F32 CenterY = ScreenCenterY + MetersToPixels*GameState->PlayerP.OffsetY - (F32)RelRow*(F32)TileSideInPixels;
F32 MinX = CenterX - 0.5f*(F32)TileSideInPixels;
F32 MinY = CenterY - 0.5f*(F32)TileSideInPixels;
F32 MaxX = CenterX + 0.5f*(F32)TileSideInPixels;
F32 MaxY = CenterY + 0.5f*(F32)TileSideInPixels;
DrawRectangle(Buffer, MinX, MinY, MaxX, MaxY, Gray, Gray, Gray);
}
}
}
F32 PlayerR = 1.0f;
F32 PlayerG = 0.0f;
F32 PlayerB = 0.0f;
F32 PlayerLeft = ScreenCenterX - 0.5f * MetersToPixels*PlayerWidth;
F32 PlayerTop = ScreenCenterY - MetersToPixels*PlayerHeight;
//DrawRectangle(Buffer, PlayerLeft, PlayerTop, PlayerLeft + MetersToPixels*PlayerWidth, PlayerTop + MetersToPixels*PlayerHeight, PlayerR, PlayerG, PlayerB);
DrawBitmap(Buffer, &GameState->HeroHead, PlayerLeft, PlayerTop);
GameOutputSound(GameState, SoundBuffer, 400);
}
/*
static void RenderWeirdGradient(game_offscreen_buffer *Buffer, S32 XOffset,
S32 YOffset)
{
S32 Width, Height, Y;
U8 *Row;
Width = Buffer->Width;
Height = Buffer->Height;
Row = (U8 *)Buffer->Memory;
for(Y = 0; Y < Height; Y++) {
U32 *Pixel = (U32 *)Row;
S32 X;
Pixel = (U32 *)Row;
for(X = 0; X < Width; X++) {
U8 Blue, Green;
Blue = (U8)(X + XOffset);
Green = (U8)(Y + YOffset);
*Pixel = (Green << 8) | Blue;
Pixel++;
}
Row += Buffer->Pitch;
}
}
*/