/* ======================================================================== $File: $ $Date: $ $Revision: $ $Creator: Casey Muratori $ $Notice: (C) Copyright 2015 by Molly Rocket, Inc. All Rights Reserved. $ ======================================================================== */ #include "test_asset_builder.h" #pragma pack(push, 1) struct bitmap_header { uint16 FileType; uint32 FileSize; uint16 Reserved1; uint16 Reserved2; uint32 BitmapOffset; uint32 Size; int32 Width; int32 Height; uint16 Planes; uint16 BitsPerPixel; uint32 Compression; uint32 SizeOfBitmap; int32 HorzResolution; int32 VertResolution; uint32 ColorsUsed; uint32 ColorsImportant; uint32 RedMask; uint32 GreenMask; uint32 BlueMask; }; struct WAVE_header { uint32 RIFFID; uint32 Size; uint32 WAVEID; }; #define RIFF_CODE(a, b, c, d) (((uint32)(a) << 0) | ((uint32)(b) << 8) | ((uint32)(c) << 16) | ((uint32)(d) << 24)) enum { WAVE_ChunkID_fmt = RIFF_CODE('f', 'm', 't', ' '), WAVE_ChunkID_data = RIFF_CODE('d', 'a', 't', 'a'), WAVE_ChunkID_RIFF = RIFF_CODE('R', 'I', 'F', 'F'), WAVE_ChunkID_WAVE = RIFF_CODE('W', 'A', 'V', 'E'), }; struct WAVE_chunk { uint32 ID; uint32 Size; }; struct WAVE_fmt { uint16 wFormatTag; uint16 nChannels; uint32 nSamplesPerSec; uint32 nAvgBytesPerSec; uint16 nBlockAlign; uint16 wBitsPerSample; uint16 cbSize; uint16 wValidBitsPerSample; uint32 dwChannelMask; uint8 SubFormat[16]; }; #pragma pack(pop) struct entire_file { u32 ContentsSize; void *Contents; }; entire_file ReadEntireFile(char *FileName) { entire_file Result = {}; FILE *In = fopen(FileName, "rb"); if(In) { fseek(In, 0, SEEK_END); Result.ContentsSize = ftell(In); fseek(In, 0, SEEK_SET); Result.Contents = malloc(Result.ContentsSize); fread(Result.Contents, Result.ContentsSize, 1, In); fclose(In); } else { printf("ERROR: Cannot open file %s.\n", FileName); } return(Result); } internal loaded_bitmap LoadBMP(char *FileName) { loaded_bitmap Result = {}; entire_file ReadResult = ReadEntireFile(FileName); if(ReadResult.ContentsSize != 0) { Result.Free = ReadResult.Contents; bitmap_header *Header = (bitmap_header *)ReadResult.Contents; uint32 *Pixels = (uint32 *)((uint8 *)ReadResult.Contents + Header->BitmapOffset); Result.Memory = Pixels; Result.Width = Header->Width; Result.Height = Header->Height; Assert(Result.Height >= 0); Assert(Header->Compression == 3); // NOTE(casey): If you are using this generically for some reason, // please remember that BMP files CAN GO IN EITHER DIRECTION and // the height will be negative for top-down. // (Also, there can be compression, etc., etc... DON'T think this // is complete BMP loading code because it isn't!!) // NOTE(casey): Byte order in memory is determined by the Header itself, // so we have to read out the masks and convert the pixels ourselves. uint32 RedMask = Header->RedMask; uint32 GreenMask = Header->GreenMask; uint32 BlueMask = Header->BlueMask; uint32 AlphaMask = ~(RedMask | GreenMask | BlueMask); bit_scan_result RedScan = FindLeastSignificantSetBit(RedMask); bit_scan_result GreenScan = FindLeastSignificantSetBit(GreenMask); bit_scan_result BlueScan = FindLeastSignificantSetBit(BlueMask); bit_scan_result AlphaScan = FindLeastSignificantSetBit(AlphaMask); Assert(RedScan.Found); Assert(GreenScan.Found); Assert(BlueScan.Found); Assert(AlphaScan.Found); int32 RedShiftDown = (int32)RedScan.Index; int32 GreenShiftDown = (int32)GreenScan.Index; int32 BlueShiftDown = (int32)BlueScan.Index; int32 AlphaShiftDown = (int32)AlphaScan.Index; uint32 *SourceDest = Pixels; for(int32 Y = 0; Y < Header->Height; ++Y) { for(int32 X = 0; X < Header->Width; ++X) { uint32 C = *SourceDest; v4 Texel = {(real32)((C & RedMask) >> RedShiftDown), (real32)((C & GreenMask) >> GreenShiftDown), (real32)((C & BlueMask) >> BlueShiftDown), (real32)((C & AlphaMask) >> AlphaShiftDown)}; Texel = SRGB255ToLinear1(Texel); #if 1 Texel.rgb *= Texel.a; #endif Texel = Linear1ToSRGB255(Texel); *SourceDest++ = (((uint32)(Texel.a + 0.5f) << 24) | ((uint32)(Texel.r + 0.5f) << 16) | ((uint32)(Texel.g + 0.5f) << 8) | ((uint32)(Texel.b + 0.5f) << 0)); } } } Result.Pitch = Result.Width*BITMAP_BYTES_PER_PIXEL; #if 0 Result.Memory = (uint8 *)Result.Memory + Result.Pitch*(Result.Height - 1); Result.Pitch = -Result.Pitch; #endif return(Result); } internal loaded_font * LoadFont(char *FileName, char *FontName, int PixelHeight) { loaded_font *Font = (loaded_font *)malloc(sizeof(loaded_font)); AddFontResourceExA(FileName, FR_PRIVATE, 0); Font->Win32Handle = CreateFontA(PixelHeight, 0, 0, 0, FW_NORMAL, // NOTE(casey): Weight FALSE, // NOTE(casey): Italic FALSE, // NOTE(casey): Underline FALSE, // NOTE(casey): Strikeout DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH|FF_DONTCARE, FontName); SelectObject(GlobalFontDeviceContext, Font->Win32Handle); GetTextMetrics(GlobalFontDeviceContext, &Font->TextMetric); Font->MinCodePoint = INT_MAX; Font->MaxCodePoint = 0; // NOTE(casey): 5k characters should be more than enough for _anybody_! Font->MaxGlyphCount = 5000; Font->GlyphCount = 0; u32 GlyphIndexFromCodePointSize = ONE_PAST_MAX_FONT_CODEPOINT*sizeof(u32); Font->GlyphIndexFromCodePoint = (u32 *)malloc(GlyphIndexFromCodePointSize); memset(Font->GlyphIndexFromCodePoint, 0, GlyphIndexFromCodePointSize); Font->Glyphs = (hha_font_glyph *)malloc(sizeof(hha_font_glyph)*Font->MaxGlyphCount); size_t HorizontalAdvanceSize = sizeof(r32)*Font->MaxGlyphCount*Font->MaxGlyphCount; Font->HorizontalAdvance = (r32 *)malloc(HorizontalAdvanceSize); memset(Font->HorizontalAdvance, 0, HorizontalAdvanceSize); Font->OnePastHighestCodepoint = 0; // NOTE(casey): Reserve space for the null glyph Font->GlyphCount = 1; Font->Glyphs[0].UnicodeCodePoint = 0; Font->Glyphs[0].BitmapID.Value = 0; return(Font); } internal void FinalizeFontKerning(loaded_font *Font) { SelectObject(GlobalFontDeviceContext, Font->Win32Handle); DWORD KerningPairCount = GetKerningPairsW(GlobalFontDeviceContext, 0, 0); KERNINGPAIR *KerningPairs = (KERNINGPAIR *)malloc(KerningPairCount*sizeof(KERNINGPAIR)); GetKerningPairsW(GlobalFontDeviceContext, KerningPairCount, KerningPairs); for(DWORD KerningPairIndex = 0; KerningPairIndex < KerningPairCount; ++KerningPairIndex) { KERNINGPAIR *Pair = KerningPairs + KerningPairIndex; if((Pair->wFirst < ONE_PAST_MAX_FONT_CODEPOINT) && (Pair->wSecond < ONE_PAST_MAX_FONT_CODEPOINT)) { u32 First = Font->GlyphIndexFromCodePoint[Pair->wFirst]; u32 Second = Font->GlyphIndexFromCodePoint[Pair->wSecond]; if((First != 0) && (Second != 0)) { Font->HorizontalAdvance[First*Font->MaxGlyphCount + Second] += (r32)Pair->iKernAmount; } } } free(KerningPairs); } internal void FreeFont(loaded_font *Font) { if(Font) { DeleteObject(Font->Win32Handle); free(Font->Glyphs); free(Font->HorizontalAdvance); free(Font->GlyphIndexFromCodePoint); free(Font); } } internal void InitializeFontDC(void) { GlobalFontDeviceContext = CreateCompatibleDC(GetDC(0)); BITMAPINFO Info = {}; Info.bmiHeader.biSize = sizeof(Info.bmiHeader); Info.bmiHeader.biWidth = MAX_FONT_WIDTH; Info.bmiHeader.biHeight = MAX_FONT_HEIGHT; Info.bmiHeader.biPlanes = 1; Info.bmiHeader.biBitCount = 32; Info.bmiHeader.biCompression = BI_RGB; Info.bmiHeader.biSizeImage = 0; Info.bmiHeader.biXPelsPerMeter = 0; Info.bmiHeader.biYPelsPerMeter = 0; Info.bmiHeader.biClrUsed = 0; Info.bmiHeader.biClrImportant = 0; HBITMAP Bitmap = CreateDIBSection(GlobalFontDeviceContext, &Info, DIB_RGB_COLORS, &GlobalFontBits, 0, 0); SelectObject(GlobalFontDeviceContext, Bitmap); SetBkColor(GlobalFontDeviceContext, RGB(0, 0, 0)); } internal loaded_bitmap LoadGlyphBitmap(loaded_font *Font, u32 CodePoint, hha_asset *Asset) { loaded_bitmap Result = {}; u32 GlyphIndex = Font->GlyphIndexFromCodePoint[CodePoint]; #if USE_FONTS_FROM_WINDOWS SelectObject(GlobalFontDeviceContext, Font->Win32Handle); memset(GlobalFontBits, 0x00, MAX_FONT_WIDTH*MAX_FONT_HEIGHT*sizeof(u32)); wchar_t CheesePoint = (wchar_t)CodePoint; SIZE Size; GetTextExtentPoint32W(GlobalFontDeviceContext, &CheesePoint, 1, &Size); int PreStepX = 128; int BoundWidth = Size.cx + 2*PreStepX; if(BoundWidth > MAX_FONT_WIDTH) { BoundWidth = MAX_FONT_WIDTH; } int BoundHeight = Size.cy; if(BoundHeight > MAX_FONT_HEIGHT) { BoundHeight = MAX_FONT_HEIGHT; } // PatBlt(DeviceContext, 0, 0, Width, Height, BLACKNESS); // SetBkMode(DeviceContext, TRANSPARENT); SetTextColor(GlobalFontDeviceContext, RGB(255, 255, 255)); TextOutW(GlobalFontDeviceContext, PreStepX, 0, &CheesePoint, 1); s32 MinX = 10000; s32 MinY = 10000; s32 MaxX = -10000; s32 MaxY = -10000; u32 *Row = (u32 *)GlobalFontBits + (MAX_FONT_HEIGHT - 1)*MAX_FONT_WIDTH; for(s32 Y = 0; Y < BoundHeight; ++Y) { u32 *Pixel = Row; for(s32 X = 0; X < BoundWidth; ++X) { #if 0 COLORREF RefPixel = GetPixel(GlobalFontDeviceContext, X, Y); Assert(RefPixel == *Pixel); #endif if(*Pixel != 0) { if(MinX > X) { MinX = X; } if(MinY > Y) { MinY = Y; } if(MaxX < X) { MaxX = X; } if(MaxY < Y) { MaxY = Y; } } ++Pixel; } Row -= MAX_FONT_WIDTH; } r32 KerningChange = 0; if(MinX <= MaxX) { int Width = (MaxX - MinX) + 1; int Height = (MaxY - MinY) + 1; Result.Width = Width + 2; Result.Height = Height + 2; Result.Pitch = Result.Width*BITMAP_BYTES_PER_PIXEL; Result.Memory = malloc(Result.Height*Result.Pitch); Result.Free = Result.Memory; memset(Result.Memory, 0, Result.Height*Result.Pitch); u8 *DestRow = (u8 *)Result.Memory + (Result.Height - 1 - 1)*Result.Pitch; u32 *SourceRow = (u32 *)GlobalFontBits + (MAX_FONT_HEIGHT - 1 - MinY)*MAX_FONT_WIDTH; for(s32 Y = MinY; Y <= MaxY; ++Y) { u32 *Source = (u32 *)SourceRow + MinX; u32 *Dest = (u32 *)DestRow + 1; for(s32 X = MinX; X <= MaxX; ++X) { #if 0 COLORREF Pixel = GetPixel(GlobalFontDeviceContext, X, Y); Assert(Pixel == *Source); #else u32 Pixel = *Source; #endif r32 Gray = (r32)(Pixel & 0xFF); v4 Texel = {255.0f, 255.0f, 255.0f, Gray}; Texel = SRGB255ToLinear1(Texel); Texel.rgb *= Texel.a; Texel = Linear1ToSRGB255(Texel); *Dest++ = (((uint32)(Texel.a + 0.5f) << 24) | ((uint32)(Texel.r + 0.5f) << 16) | ((uint32)(Texel.g + 0.5f) << 8) | ((uint32)(Texel.b + 0.5f) << 0)); ++Source; } DestRow -= Result.Pitch; SourceRow -= MAX_FONT_WIDTH; } Asset->Bitmap.AlignPercentage[0] = (1.0f) / (r32)Result.Width; Asset->Bitmap.AlignPercentage[1] = (1.0f + (MaxY - (BoundHeight - Font->TextMetric.tmDescent))) / (r32)Result.Height; KerningChange = (r32)(MinX - PreStepX); } #if 0 ABC ThisABC; GetCharABCWidthsW(GlobalFontDeviceContext, CodePoint, CodePoint, &ThisABC); r32 CharAdvance = (r32)(ThisABC.abcA + ThisABC.abcB + ThisABC.abcC); #else INT ThisWidth; GetCharWidth32W(GlobalFontDeviceContext, CodePoint, CodePoint, &ThisWidth); r32 CharAdvance = (r32)ThisWidth; #endif for(u32 OtherGlyphIndex = 0; OtherGlyphIndex < Font->MaxGlyphCount; ++OtherGlyphIndex) { Font->HorizontalAdvance[GlyphIndex*Font->MaxGlyphCount + OtherGlyphIndex] += CharAdvance - KerningChange; if(OtherGlyphIndex != 0) { Font->HorizontalAdvance[OtherGlyphIndex*Font->MaxGlyphCount + GlyphIndex] += KerningChange; } } #else entire_file TTFFile = ReadEntireFile(FileName); if(TTFFile.ContentsSize != 0) { stbtt_fontinfo Font; stbtt_InitFont(&Font, (u8 *)TTFFile.Contents, stbtt_GetFontOffsetForIndex((u8 *)TTFFile.Contents, 0)); int Width, Height, XOffset, YOffset; u8 *MonoBitmap = stbtt_GetCodepointBitmap(&Font, 0, stbtt_ScaleForPixelHeight(&Font, 128.0f), CodePoint, &Width, &Height, &XOffset, &YOffset); Result.Width = Width; Result.Height = Height; Result.Pitch = Result.Width*BITMAP_BYTES_PER_PIXEL; Result.Memory = malloc(Height*Result.Pitch); Result.Free = Result.Memory; u8 *Source = MonoBitmap; u8 *DestRow = (u8 *)Result.Memory + (Height - 1)*Result.Pitch; for(s32 Y = 0; Y < Height; ++Y) { u32 *Dest = (u32 *)DestRow; for(s32 X = 0; X < Width; ++X) { u8 Gray = *Source++; u8 Alpha = 0xFF; *Dest++ = ((Alpha << 24) | (Gray << 16) | (Gray << 8) | (Gray << 0)); } DestRow -= Result.Pitch; } stbtt_FreeBitmap(MonoBitmap, 0); free(TTFFile.Contents); } #endif return(Result); } struct riff_iterator { uint8 *At; uint8 *Stop; }; inline riff_iterator ParseChunkAt(void *At, void *Stop) { riff_iterator Iter; Iter.At = (uint8 *)At; Iter.Stop = (uint8 *)Stop; return(Iter); } inline riff_iterator NextChunk(riff_iterator Iter) { WAVE_chunk *Chunk = (WAVE_chunk *)Iter.At; uint32 Size = (Chunk->Size + 1) & ~1; Iter.At += sizeof(WAVE_chunk) + Size; return(Iter); } inline bool32 IsValid(riff_iterator Iter) { bool32 Result = (Iter.At < Iter.Stop); return(Result); } inline void * GetChunkData(riff_iterator Iter) { void *Result = (Iter.At + sizeof(WAVE_chunk)); return(Result); } inline uint32 GetType(riff_iterator Iter) { WAVE_chunk *Chunk = (WAVE_chunk *)Iter.At; uint32 Result = Chunk->ID; return(Result); } inline uint32 GetChunkDataSize(riff_iterator Iter) { WAVE_chunk *Chunk = (WAVE_chunk *)Iter.At; uint32 Result = Chunk->Size; return(Result); } struct loaded_sound { uint32 SampleCount; // NOTE(casey): This is the sample count divided by 8 uint32 ChannelCount; int16 *Samples[2]; void *Free; }; internal loaded_sound LoadWAV(char *FileName, u32 SectionFirstSampleIndex, u32 SectionSampleCount) { loaded_sound Result = {}; entire_file ReadResult = ReadEntireFile(FileName); if(ReadResult.ContentsSize != 0) { Result.Free = ReadResult.Contents; WAVE_header *Header = (WAVE_header *)ReadResult.Contents; Assert(Header->RIFFID == WAVE_ChunkID_RIFF); Assert(Header->WAVEID == WAVE_ChunkID_WAVE); uint32 ChannelCount = 0; uint32 SampleDataSize = 0; int16 *SampleData = 0; for(riff_iterator Iter = ParseChunkAt(Header + 1, (uint8 *)(Header + 1) + Header->Size - 4); IsValid(Iter); Iter = NextChunk(Iter)) { switch(GetType(Iter)) { case WAVE_ChunkID_fmt: { WAVE_fmt *fmt = (WAVE_fmt *)GetChunkData(Iter); Assert(fmt->wFormatTag == 1); // NOTE(casey): Only support PCM Assert(fmt->nSamplesPerSec == 48000); Assert(fmt->wBitsPerSample == 16); Assert(fmt->nBlockAlign == (sizeof(int16)*fmt->nChannels)); ChannelCount = fmt->nChannels; } break; case WAVE_ChunkID_data: { SampleData = (int16 *)GetChunkData(Iter); SampleDataSize = GetChunkDataSize(Iter); } break; } } Assert(ChannelCount && SampleData); Result.ChannelCount = ChannelCount; u32 SampleCount = SampleDataSize / (ChannelCount*sizeof(int16)); if(ChannelCount == 1) { Result.Samples[0] = SampleData; Result.Samples[1] = 0; } else if(ChannelCount == 2) { Result.Samples[0] = SampleData; Result.Samples[1] = SampleData + SampleCount; #if 0 for(uint32 SampleIndex = 0; SampleIndex < SampleCount; ++SampleIndex) { SampleData[2*SampleIndex + 0] = (int16)SampleIndex; SampleData[2*SampleIndex + 1] = (int16)SampleIndex; } #endif for(uint32 SampleIndex = 0; SampleIndex < SampleCount; ++SampleIndex) { int16 Source = SampleData[2*SampleIndex]; SampleData[2*SampleIndex] = SampleData[SampleIndex]; SampleData[SampleIndex] = Source; } } else { Assert(!"Invalid channel count in WAV file"); } // TODO(casey): Load right channels! b32 AtEnd = true; Result.ChannelCount = 1; if(SectionSampleCount) { Assert((SectionFirstSampleIndex + SectionSampleCount) <= SampleCount); AtEnd = ((SectionFirstSampleIndex + SectionSampleCount) == SampleCount); SampleCount = SectionSampleCount; for(uint32 ChannelIndex = 0; ChannelIndex < Result.ChannelCount; ++ChannelIndex) { Result.Samples[ChannelIndex] += SectionFirstSampleIndex; } } if(AtEnd) { for(uint32 ChannelIndex = 0; ChannelIndex < Result.ChannelCount; ++ChannelIndex) { for(u32 SampleIndex = SampleCount; SampleIndex < (SampleCount + 8); ++SampleIndex) { Result.Samples[ChannelIndex][SampleIndex] = 0; } } } Result.SampleCount = SampleCount; } return(Result); } internal void BeginAssetType(game_assets *Assets, asset_type_id TypeID) { Assert(Assets->DEBUGAssetType == 0); Assets->DEBUGAssetType = Assets->AssetTypes + TypeID; Assets->DEBUGAssetType->TypeID = TypeID; Assets->DEBUGAssetType->FirstAssetIndex = Assets->AssetCount; Assets->DEBUGAssetType->OnePastLastAssetIndex = Assets->DEBUGAssetType->FirstAssetIndex; } struct added_asset { u32 ID; hha_asset *HHA; asset_source *Source; }; internal added_asset AddAsset(game_assets *Assets) { Assert(Assets->DEBUGAssetType); Assert(Assets->DEBUGAssetType->OnePastLastAssetIndex < ArrayCount(Assets->Assets)); u32 Index = Assets->DEBUGAssetType->OnePastLastAssetIndex++; asset_source *Source = Assets->AssetSources + Index; hha_asset *HHA = Assets->Assets + Index; HHA->FirstTagIndex = Assets->TagCount; HHA->OnePastLastTagIndex = HHA->FirstTagIndex; Assets->AssetIndex = Index; added_asset Result; Result.ID = Index; Result.HHA = HHA; Result.Source = Source; return(Result); } internal bitmap_id AddBitmapAsset(game_assets *Assets, char *FileName, r32 AlignPercentageX = 0.5f, r32 AlignPercentageY = 0.5f) { added_asset Asset = AddAsset(Assets); Asset.HHA->Bitmap.AlignPercentage[0] = AlignPercentageX; Asset.HHA->Bitmap.AlignPercentage[1] = AlignPercentageY; Asset.Source->Type = AssetType_Bitmap; Asset.Source->Bitmap.FileName = FileName; bitmap_id Result = {Asset.ID}; return(Result); } internal bitmap_id AddCharacterAsset(game_assets *Assets, loaded_font *Font, u32 Codepoint) { added_asset Asset = AddAsset(Assets); Asset.HHA->Bitmap.AlignPercentage[0] = 0.0f; // NOTE(casey): Set later by extraction Asset.HHA->Bitmap.AlignPercentage[1] = 0.0f; // NOTE(casey): Set later by extraction Asset.Source->Type = AssetType_FontGlyph; Asset.Source->Glyph.Font = Font; Asset.Source->Glyph.Codepoint = Codepoint; bitmap_id Result = {Asset.ID}; Assert(Font->GlyphCount < Font->MaxGlyphCount); u32 GlyphIndex = Font->GlyphCount++; hha_font_glyph *Glyph = Font->Glyphs + GlyphIndex; Glyph->UnicodeCodePoint = Codepoint; Glyph->BitmapID = Result; Font->GlyphIndexFromCodePoint[Codepoint] = GlyphIndex; if(Font->OnePastHighestCodepoint <= Codepoint) { Font->OnePastHighestCodepoint = Codepoint + 1; } return(Result); } internal sound_id AddSoundAsset(game_assets *Assets, char *FileName, u32 FirstSampleIndex = 0, u32 SampleCount = 0) { added_asset Asset = AddAsset(Assets); Asset.HHA->Sound.SampleCount = SampleCount; Asset.HHA->Sound.Chain = HHASoundChain_None; Asset.Source->Type = AssetType_Sound; Asset.Source->Sound.FileName = FileName; Asset.Source->Sound.FirstSampleIndex = FirstSampleIndex; sound_id Result = {Asset.ID}; return(Result); } internal font_id AddFontAsset(game_assets *Assets, loaded_font *Font) { added_asset Asset = AddAsset(Assets); Asset.HHA->Font.OnePastHighestCodepoint = Font->OnePastHighestCodepoint; Asset.HHA->Font.GlyphCount = Font->GlyphCount; Asset.HHA->Font.AscenderHeight = (r32)Font->TextMetric.tmAscent; Asset.HHA->Font.DescenderHeight = (r32)Font->TextMetric.tmDescent; Asset.HHA->Font.ExternalLeading = (r32)Font->TextMetric.tmExternalLeading; Asset.Source->Type = AssetType_Font; Asset.Source->Font.Font = Font; font_id Result = {Asset.ID}; return(Result); } internal void AddTag(game_assets *Assets, asset_tag_id ID, real32 Value) { Assert(Assets->AssetIndex); hha_asset *HHA = Assets->Assets + Assets->AssetIndex; ++HHA->OnePastLastTagIndex; hha_tag *Tag = Assets->Tags + Assets->TagCount++; Tag->ID = ID; Tag->Value = Value; } internal void EndAssetType(game_assets *Assets) { Assert(Assets->DEBUGAssetType); Assets->AssetCount = Assets->DEBUGAssetType->OnePastLastAssetIndex; Assets->DEBUGAssetType = 0; Assets->AssetIndex = 0; } internal void WriteHHA(game_assets *Assets, char *FileName) { FILE *Out = fopen(FileName, "wb"); if(Out) { hha_header Header = {}; Header.MagicValue = HHA_MAGIC_VALUE; Header.Version = HHA_VERSION; Header.TagCount = Assets->TagCount; Header.AssetTypeCount = Asset_Count; // TODO(casey): Do we really want to do this? Sparseness! Header.AssetCount = Assets->AssetCount; u32 TagArraySize = Header.TagCount*sizeof(hha_tag); u32 AssetTypeArraySize = Header.AssetTypeCount*sizeof(hha_asset_type); u32 AssetArraySize = Header.AssetCount*sizeof(hha_asset); Header.Tags = sizeof(Header); Header.AssetTypes = Header.Tags + TagArraySize; Header.Assets = Header.AssetTypes + AssetTypeArraySize; fwrite(&Header, sizeof(Header), 1, Out); fwrite(Assets->Tags, TagArraySize, 1, Out); fwrite(Assets->AssetTypes, AssetTypeArraySize, 1, Out); fseek(Out, AssetArraySize, SEEK_CUR); for(u32 AssetIndex = 1; AssetIndex < Header.AssetCount; ++AssetIndex) { asset_source *Source = Assets->AssetSources + AssetIndex; hha_asset *Dest = Assets->Assets + AssetIndex; Dest->DataOffset = ftell(Out); if(Source->Type == AssetType_Sound) { loaded_sound WAV = LoadWAV(Source->Sound.FileName, Source->Sound.FirstSampleIndex, Dest->Sound.SampleCount); Dest->Sound.SampleCount = WAV.SampleCount; Dest->Sound.ChannelCount = WAV.ChannelCount; for(u32 ChannelIndex = 0; ChannelIndex < WAV.ChannelCount; ++ChannelIndex) { fwrite(WAV.Samples[ChannelIndex], Dest->Sound.SampleCount*sizeof(s16), 1, Out); } free(WAV.Free); } else if(Source->Type == AssetType_Font) { loaded_font *Font = Source->Font.Font; FinalizeFontKerning(Font); u32 GlyphsSize = Font->GlyphCount*sizeof(hha_font_glyph); fwrite(Font->Glyphs, GlyphsSize, 1, Out); u8 *HorizontalAdvance = (u8 *)Font->HorizontalAdvance; for(u32 GlyphIndex = 0; GlyphIndex < Font->GlyphCount; ++GlyphIndex) { u32 HorizontalAdvanceSliceSize = sizeof(r32)*Font->GlyphCount; fwrite(HorizontalAdvance, HorizontalAdvanceSliceSize, 1, Out); HorizontalAdvance += sizeof(r32)*Font->MaxGlyphCount; } } else { loaded_bitmap Bitmap; if(Source->Type == AssetType_FontGlyph) { Bitmap = LoadGlyphBitmap(Source->Glyph.Font, Source->Glyph.Codepoint, Dest); } else { Assert(Source->Type == AssetType_Bitmap); Bitmap = LoadBMP(Source->Bitmap.FileName); } Dest->Bitmap.Dim[0] = Bitmap.Width; Dest->Bitmap.Dim[1] = Bitmap.Height; Assert((Bitmap.Width*4) == Bitmap.Pitch); fwrite(Bitmap.Memory, Bitmap.Width*Bitmap.Height*4, 1, Out); free(Bitmap.Free); } } fseek(Out, (u32)Header.Assets, SEEK_SET); fwrite(Assets->Assets, AssetArraySize, 1, Out); fclose(Out); } else { printf("ERROR: Couldn't open file :(\n"); } } internal void Initialize(game_assets *Assets) { Assets->TagCount = 1; Assets->AssetCount = 1; Assets->DEBUGAssetType = 0; Assets->AssetIndex = 0; Assets->AssetTypeCount = Asset_Count; memset(Assets->AssetTypes, 0, sizeof(Assets->AssetTypes)); } internal void WriteFonts(void) { game_assets Assets_; game_assets *Assets = &Assets_; Initialize(Assets); loaded_font *Fonts[] = { LoadFont("c:/Windows/Fonts/arial.ttf", "Arial", 128), LoadFont("c:/Windows/Fonts/LiberationMono-Regular.ttf", "Liberation Mono", 20), }; BeginAssetType(Assets, Asset_FontGlyph); for(u32 FontIndex = 0; FontIndex < ArrayCount(Fonts); ++FontIndex) { loaded_font *Font = Fonts[FontIndex]; AddCharacterAsset(Assets, Font, ' '); for(u32 Character = '!'; Character <= '~'; ++Character) { AddCharacterAsset(Assets, Font, Character); } // NOTE(casey): Kanji OWL!!!!!!! AddCharacterAsset(Assets, Font, 0x5c0f); AddCharacterAsset(Assets, Font, 0x8033); AddCharacterAsset(Assets, Font, 0x6728); AddCharacterAsset(Assets, Font, 0x514e); } EndAssetType(Assets); // TODO(casey): This is kinda janky, because it means you have to get this // order right always! BeginAssetType(Assets, Asset_Font); AddFontAsset(Assets, Fonts[0]); AddTag(Assets, Tag_FontType, FontType_Default); AddFontAsset(Assets, Fonts[1]); AddTag(Assets, Tag_FontType, FontType_Debug); EndAssetType(Assets); WriteHHA(Assets, "testfonts.hha"); } internal void WriteHero(void) { game_assets Assets_; game_assets *Assets = &Assets_; Initialize(Assets); real32 AngleRight = 0.0f*Tau32; real32 AngleBack = 0.25f*Tau32; real32 AngleLeft = 0.5f*Tau32; real32 AngleFront = 0.75f*Tau32; r32 HeroAlign[] = {0.5f, 0.156682029f}; BeginAssetType(Assets, Asset_Head); AddBitmapAsset(Assets, "test/test_hero_right_head.bmp", HeroAlign[0], HeroAlign[1]); AddTag(Assets, Tag_FacingDirection, AngleRight); AddBitmapAsset(Assets, "test/test_hero_back_head.bmp", HeroAlign[0], HeroAlign[1]); AddTag(Assets, Tag_FacingDirection, AngleBack); AddBitmapAsset(Assets, "test/test_hero_left_head.bmp", HeroAlign[0], HeroAlign[1]); AddTag(Assets, Tag_FacingDirection, AngleLeft); AddBitmapAsset(Assets, "test/test_hero_front_head.bmp", HeroAlign[0], HeroAlign[1]); AddTag(Assets, Tag_FacingDirection, AngleFront); EndAssetType(Assets); BeginAssetType(Assets, Asset_Cape); AddBitmapAsset(Assets, "test/test_hero_right_cape.bmp", HeroAlign[0], HeroAlign[1]); AddTag(Assets, Tag_FacingDirection, AngleRight); AddBitmapAsset(Assets, "test/test_hero_back_cape.bmp", HeroAlign[0], HeroAlign[1]); AddTag(Assets, Tag_FacingDirection, AngleBack); AddBitmapAsset(Assets, "test/test_hero_left_cape.bmp", HeroAlign[0], HeroAlign[1]); AddTag(Assets, Tag_FacingDirection, AngleLeft); AddBitmapAsset(Assets, "test/test_hero_front_cape.bmp", HeroAlign[0], HeroAlign[1]); AddTag(Assets, Tag_FacingDirection, AngleFront); EndAssetType(Assets); BeginAssetType(Assets, Asset_Torso); AddBitmapAsset(Assets, "test/test_hero_right_torso.bmp", HeroAlign[0], HeroAlign[1]); AddTag(Assets, Tag_FacingDirection, AngleRight); AddBitmapAsset(Assets, "test/test_hero_back_torso.bmp", HeroAlign[0], HeroAlign[1]); AddTag(Assets, Tag_FacingDirection, AngleBack); AddBitmapAsset(Assets, "test/test_hero_left_torso.bmp", HeroAlign[0], HeroAlign[1]); AddTag(Assets, Tag_FacingDirection, AngleLeft); AddBitmapAsset(Assets, "test/test_hero_front_torso.bmp", HeroAlign[0], HeroAlign[1]); AddTag(Assets, Tag_FacingDirection, AngleFront); EndAssetType(Assets); WriteHHA(Assets, "test1.hha"); } internal void WriteNonHero(void) { game_assets Assets_; game_assets *Assets = &Assets_; Initialize(Assets); BeginAssetType(Assets, Asset_Shadow); AddBitmapAsset(Assets, "test/test_hero_shadow.bmp", 0.5f, 0.156682029f); EndAssetType(Assets); BeginAssetType(Assets, Asset_Tree); AddBitmapAsset(Assets, "test2/tree00.bmp", 0.493827164f, 0.295652181f); EndAssetType(Assets); BeginAssetType(Assets, Asset_Sword); AddBitmapAsset(Assets, "test2/rock03.bmp", 0.5f, 0.65625f); EndAssetType(Assets); BeginAssetType(Assets, Asset_Grass); AddBitmapAsset(Assets, "test2/grass00.bmp"); AddBitmapAsset(Assets, "test2/grass01.bmp"); EndAssetType(Assets); BeginAssetType(Assets, Asset_Tuft); AddBitmapAsset(Assets, "test2/tuft00.bmp"); AddBitmapAsset(Assets, "test2/tuft01.bmp"); AddBitmapAsset(Assets, "test2/tuft02.bmp"); EndAssetType(Assets); BeginAssetType(Assets, Asset_Stone); AddBitmapAsset(Assets, "test2/ground00.bmp"); AddBitmapAsset(Assets, "test2/ground01.bmp"); AddBitmapAsset(Assets, "test2/ground02.bmp"); AddBitmapAsset(Assets, "test2/ground03.bmp"); EndAssetType(Assets); WriteHHA(Assets, "test2.hha"); } internal void WriteSounds(void) { game_assets Assets_; game_assets *Assets = &Assets_; Initialize(Assets); BeginAssetType(Assets, Asset_Bloop); AddSoundAsset(Assets, "test3/bloop_00.wav"); AddSoundAsset(Assets, "test3/bloop_01.wav"); AddSoundAsset(Assets, "test3/bloop_02.wav"); AddSoundAsset(Assets, "test3/bloop_03.wav"); AddSoundAsset(Assets, "test3/bloop_04.wav"); EndAssetType(Assets); BeginAssetType(Assets, Asset_Crack); AddSoundAsset(Assets, "test3/crack_00.wav"); EndAssetType(Assets); BeginAssetType(Assets, Asset_Drop); AddSoundAsset(Assets, "test3/drop_00.wav"); EndAssetType(Assets); BeginAssetType(Assets, Asset_Glide); AddSoundAsset(Assets, "test3/glide_00.wav"); EndAssetType(Assets); u32 OneMusicChunk = 10*48000; u32 TotalMusicSampleCount = 7468095; BeginAssetType(Assets, Asset_Music); for(u32 FirstSampleIndex = 0; FirstSampleIndex < TotalMusicSampleCount; FirstSampleIndex += OneMusicChunk) { u32 SampleCount = TotalMusicSampleCount - FirstSampleIndex; if(SampleCount > OneMusicChunk) { SampleCount = OneMusicChunk; } sound_id ThisMusic = AddSoundAsset(Assets, "test3/music_test.wav", FirstSampleIndex, SampleCount); if((FirstSampleIndex + OneMusicChunk) < TotalMusicSampleCount) { Assets->Assets[ThisMusic.Value].Sound.Chain = HHASoundChain_Advance; } } EndAssetType(Assets); BeginAssetType(Assets, Asset_Puhp); AddSoundAsset(Assets, "test3/puhp_00.wav"); AddSoundAsset(Assets, "test3/puhp_01.wav"); EndAssetType(Assets); WriteHHA(Assets, "test3.hha"); } int main(int ArgCount, char **Args) { InitializeFontDC(); WriteFonts(); WriteNonHero(); WriteHero(); WriteSounds(); }