diff --git a/assembly_parser.cpp b/assembly_parser.cpp new file mode 100644 index 0000000..f46dc79 --- /dev/null +++ b/assembly_parser.cpp @@ -0,0 +1,206 @@ + +internal assembly_token +ParseToken (tokenizer* Tokenizer) +{ + assembly_token Result = {}; + Result.Token = Tokenizer->At; + Result.Length = 1; + Tokenizer->At++; + + if (*Result.Token == ':'){ Result.Type = AssemblyToken_Colon; } + else if (*Result.Token == ';'){ Result.Type = AssemblyToken_SemiColon; } + else if (*Result.Token =='{'){ Result.Type = AssemblyToken_LeftCurlyBrace; } + else if (*Result.Token =='}'){ Result.Type = AssemblyToken_RightCurlyBrace; } + else if (*Result.Token ==','){ Result.Type = AssemblyToken_Comma; } + else if (IsNumericExtended(*Result.Token)) + { + while(*Tokenizer->At && IsNumericExtended(*Tokenizer->At)) { Tokenizer->At++; } + Result.Type = AssemblyToken_Number; + Result.Length = Tokenizer->At - Result.Token; + } + else if (*Result.Token =='\"') + { + while(*Tokenizer->At && *Tokenizer->At != '\"') { Tokenizer->At++; } + Result.Token++; // Skip the quote + Result.Type = AssemblyToken_String; + Result.Length = (Tokenizer->At - Result.Token) - 1; + } + else if (*Result.Token == '(') + { + while(*Tokenizer->At && *Tokenizer->At != ')') { Tokenizer->At++; } + Result.Token++; // Skip the paren + Result.Type = AssemblyToken_Vector; + Result.Length = (Tokenizer->At - Result.Token) - 1; + } + else if (CharArraysEqualUpToLength(Result.Token, LED_STRIP_IDENTIFIER, CharArrayLength(LED_STRIP_IDENTIFIER))) + { + Result.Type = AssemblyToken_LEDStrip; + Result.Length = CharArrayLength(LED_STRIP_IDENTIFIER); + Tokenizer->At += Result.Length - 1; + } + else if (CharArraysEqualUpToLength(Result.Token, END_ASSEMBLY_FILE_IDENTIFIER, CharArrayLength(END_ASSEMBLY_FILE_IDENTIFIER))) + { + Result.Type = AssemblyToken_EndOfFile; + Result.Length = CharArrayLength(END_ASSEMBLY_FILE_IDENTIFIER); + Tokenizer->At += Result.Length - 1; + } + else + { + Result.Type = AssemblyToken_Identifier; + while(*Tokenizer->At && !IsWhitespace(*Tokenizer->At)) { Tokenizer->At++; } + } + + return Result; +} + +internal v3 +ParseAssemblyVector (char* String) +{ + v3 Result = {}; + + tokenizer Tokenizer = {}; + Tokenizer.At = String; + + EatWhitespace(&Tokenizer); + Result.x = ParseFloatUnsafe(Tokenizer.At); + EatPastCharacter(&Tokenizer, ','); + + EatWhitespace(&Tokenizer); + Result.y = ParseFloatUnsafe(Tokenizer.At); + EatPastCharacter(&Tokenizer, ','); + + EatWhitespace(&Tokenizer); + Result.z = ParseFloatUnsafe(Tokenizer.At); + EatPastCharacter(&Tokenizer, ','); + + return Result; +} + +internal void +ParseAssemblyFileHeader (tokenizer* Tokenizer, assembly_definition* Definition) +{ + if (CharArraysEqualUpToLength(Tokenizer->At, LED_STRIP_COUNT_IDENTIFIER, CharArrayLength(LED_STRIP_COUNT_IDENTIFIER))) + { + Tokenizer->At += CharArrayLength(LED_STRIP_COUNT_IDENTIFIER); + EatWhitespace(Tokenizer); + assembly_token CountToken = ParseToken(Tokenizer); + if (CountToken.Type == AssemblyToken_Number) + { + Definition->LEDStripSize = ParseSignedIntUnsafe(CountToken.Token); + } + else + { + InvalidCodePath; + } + EatWhitespace(Tokenizer); + } + else + { + // TODO(Peter): Handle corrupted files, try to recover? + InvalidCodePath; + } + +} + +internal void +ParseLEDStrip (tokenizer* Tokenizer, assembly_definition* Assembly) +{ + led_strip_definition* LEDStripDef = Assembly->LEDStrips + Assembly->LEDStripCount; + Assembly->LEDStripCount++; + + // Control Box Index + while (*Tokenizer->At && !IsNumericExtended(*Tokenizer->At)) { Tokenizer->At++; } + assembly_token BoxIDToken = ParseToken(Tokenizer); + Assert(BoxIDToken.Type == AssemblyToken_Number); + LEDStripDef->ControlBoxID = ParseSignedIntUnsafe(BoxIDToken.Token); + + // Start Universe + EatPastCharacter(Tokenizer, ','); + EatWhitespace(Tokenizer); + assembly_token StartUniverseToken = ParseToken(Tokenizer); + Assert(BoxIDToken.Type == AssemblyToken_Number); + LEDStripDef->StartUniverse = ParseSignedIntUnsafe(StartUniverseToken.Token); + + // Start Channel + EatPastCharacter(Tokenizer, ','); + EatWhitespace(Tokenizer); + assembly_token StartChannelToken = ParseToken(Tokenizer); + Assert(BoxIDToken.Type == AssemblyToken_Number); + LEDStripDef->StartChannel = ParseSignedIntUnsafe(StartChannelToken.Token); + + // Strip Type + // TODO(Peter): This is unused for now, and would be a branch point for parsing + // the rest of the info. Fix this. + EatPastCharacter(Tokenizer, ','); + EatWhitespace(Tokenizer); + if (CharArraysEqualUpToLength(Tokenizer->At, INTERPOLATE_POINTS_IDENTIFIER, CharArrayLength(INTERPOLATE_POINTS_IDENTIFIER))) + { + LEDStripDef->InterpolationType = StripInterpolate_Points; + + // Start Position + EatPastCharacter(Tokenizer, ','); + EatWhitespace(Tokenizer); + assembly_token StartPositionToken = ParseToken(Tokenizer); + Assert(StartPositionToken.Type == AssemblyToken_Vector); + LEDStripDef->InterpolatePositionStart = ParseAssemblyVector(StartPositionToken.Token); + + // End Position + EatPastCharacter(Tokenizer, ','); + EatWhitespace(Tokenizer); + assembly_token EndPositionToken = ParseToken(Tokenizer); + Assert(EndPositionToken.Type == AssemblyToken_Vector); + LEDStripDef->InterpolatePositionEnd = ParseAssemblyVector(EndPositionToken.Token); + + // LEDs Per Strip + EatPastCharacter(Tokenizer, ','); + EatWhitespace(Tokenizer); + assembly_token LEDsPerStripToken = ParseToken(Tokenizer); + Assert(BoxIDToken.Type == AssemblyToken_Number); + LEDStripDef->LEDsPerStrip = ParseSignedIntUnsafe(LEDsPerStripToken.Token); + } + + EatPastCharacter(Tokenizer, '}'); + EatWhitespace(Tokenizer); +} + +internal assembly_definition +ParseAssemblyFile (char* File, memory_arena* Storage) +{ + assembly_definition Result = {}; + + tokenizer Tokenizer = {}; + Tokenizer.At = File; + + ParseAssemblyFileHeader(&Tokenizer, &Result); + + Result.LEDStrips = PushArray(Storage, led_strip_definition, + Result.LEDStripSize); + EatWhitespace(&Tokenizer); + + while(*Tokenizer.At) + { + EatWhitespace(&Tokenizer); + assembly_token Token = ParseToken(&Tokenizer); + + if (Token.Type != AssemblyToken_EndOfFile) + { + switch (Token.Type) + { + case AssemblyToken_LEDStrip: + { + ParseLEDStrip(&Tokenizer, &Result); + } break; + + default: + { + InvalidCodePath; + } break; + } + } + else + { + break; + } + } + return Result; +} diff --git a/assembly_parser.h b/assembly_parser.h new file mode 100644 index 0000000..a465de4 --- /dev/null +++ b/assembly_parser.h @@ -0,0 +1,60 @@ +#define LED_STRIP_COUNT_IDENTIFIER "led_strip_count" + +#define LED_STRIP_IDENTIFIER "led_strip" + +#define INTERPOLATE_POINTS_IDENTIFIER "INTERPOLATE_POINTS" + +#define END_ASSEMBLY_FILE_IDENTIFIER "END_OF_ASSEMBLY_FILE" + +enum assembly_token_type +{ + AssemblyToken_Colon, + AssemblyToken_SemiColon, + AssemblyToken_LeftCurlyBrace, + AssemblyToken_RightCurlyBrace, + AssemblyToken_Comma, + + AssemblyToken_Number, + AssemblyToken_String, + AssemblyToken_Vector, + + AssemblyToken_LEDStrip, + + AssemblyToken_Identifier, + + AssemblyToken_EndOfFile +}; + +struct assembly_token +{ + char* Token; + s32 Length; + assembly_token_type Type; +}; + +enum strip_interpolation_type +{ + StripInterpolate_Boxes, + StripInterpolate_Points, +}; + +struct led_strip_definition +{ + s32 ControlBoxID; + s32 StartUniverse, StartChannel; + + strip_interpolation_type InterpolationType; + // Interpolate Boxes + s32 StartBoxIndex, EndBoxIndex; + // Interpolate Positions + v3 InterpolatePositionStart, InterpolatePositionEnd; + // Universal Interpolation + s32 LEDsPerStrip; +}; + +struct assembly_definition +{ + s32 LEDStripSize; + s32 LEDStripCount; + led_strip_definition* LEDStrips; +}; \ No newline at end of file diff --git a/foldhaus_app.cpp b/foldhaus_app.cpp new file mode 100644 index 0000000..e275087 --- /dev/null +++ b/foldhaus_app.cpp @@ -0,0 +1,884 @@ +#include "foldhaus_platform.h" +#include "foldhaus_app.h" + +internal v4 +MouseToWorldRay(r32 MouseX, r32 MouseY, camera* Camera, r32 WindowWidth, r32 WindowHeight) +{ + DEBUG_TRACK_SCOPE(MouseToWorldRay); + r32 X = ((2.0f * MouseX) / WindowWidth) - 1; + r32 Y = ((2.0f * MouseY) / WindowHeight) - 1; + + v4 ScreenPos = v4{X, Y, -1, 1}; + + m44 InverseProjection = {}; + Inverse(GetCameraPerspectiveProjectionMatrix(*Camera), &InverseProjection); + + m44 InverseModelView = {}; + Inverse(GetCameraModelViewMatrix(*Camera), &InverseModelView); + InverseModelView = Transpose(InverseModelView); + + v4 ClipSpacePos = InverseProjection * ScreenPos; + v4 WorldPosition = InverseModelView * ClipSpacePos; + return WorldPosition; +} + +internal void +PushLEDBufferOnList (led_buffer* List, led_buffer* Entry) +{ + if (List->Next) + { + PushLEDBufferOnList(List->Next, Entry); + } + else + { + List->Next = Entry; + } +} + +internal led_buffer* +RemoveLEDBufferFromList (led_buffer* List, led_buffer* Entry) +{ + led_buffer* ListHead = 0; + if (List != Entry && List->Next) + { + ListHead = RemoveLEDBufferFromList(List->Next, Entry); + } + else if (List == Entry) + { + ListHead = Entry->Next; + } + else + { + // NOTE(Peter): Trying to remove an entry from a list that doesn't contain it + InvalidCodePath; + } + return ListHead; +} + +internal void +ConstructAssemblyFromDefinition (assembly_definition Definition, + string AssemblyName, + v3 RootPosition, + r32 Scale, + context Context, + app_state* State) +{ + Assert(State->AssembliesUsed < ASSEMBLY_LIST_LENGTH); + + assembly* Assembly = State->AssemblyList + State->AssembliesUsed++; + + // 1. Find # of LEDs, # of Universes + s32 UniversesUsedByLEDs[2048]; // TODO(Peter): find the max universe number and size these accordingly + s32 ChannelsInUniverse[2048]; + GSZeroMemory(UniversesUsedByLEDs, sizeof(s32) * 2048); + GSZeroMemory(ChannelsInUniverse, sizeof(s32) * 2048); + s32 UniverseCount = 0; + s32 LEDCount = 0; + + for (s32 StripIdx = 0; StripIdx < Definition.LEDStripCount; StripIdx++) + { + led_strip_definition StripDef = Definition.LEDStrips[StripIdx]; + + s32 ChannelsPerStrip = StripDef.LEDsPerStrip * 3; + s32 UniversesPerStrip = IntegerDivideRoundUp(ChannelsPerStrip, 512); + s32 ChannelsAllocated = 0; + for (s32 UniverseIdx = 0; UniverseIdx < UniversesPerStrip; UniverseIdx++) + { + s32 UniverseID = StripDef.StartUniverse + UniverseIdx; + s32 UniverseIndex = -1; + + for (s32 RegisteredUniverse = 0; RegisteredUniverse < UniverseCount; RegisteredUniverse++) + { + if (UniversesUsedByLEDs[RegisteredUniverse] == UniverseID) + { + UniverseIndex = RegisteredUniverse; + break; + } + } + if (UniverseIndex < 0) + { + UniverseIndex = UniverseCount++; + } + + s32 ChannelsRequested = GSMin(STREAM_BODY_SIZE, ChannelsPerStrip - ChannelsAllocated); + ChannelsAllocated += ChannelsRequested; + ChannelsInUniverse[UniverseIndex] += ChannelsRequested; + Assert(ChannelsInUniverse[UniverseIndex] <= 512); + + UniversesUsedByLEDs[UniverseIndex++] = UniverseID; + } + + LEDCount += StripDef.LEDsPerStrip; + } + + sacn_add_universes_result AddedUniverses = SACNAddUniverses(UniversesUsedByLEDs, UniverseCount, &State->SACN, Context); + + Assembly->MemorySize = CalculateMemorySizeForAssembly(LEDCount, AssemblyName.Length); + memory_arena TemporaryAssemblyArena = AllocateNonGrowableArenaWithSpace(Context.PlatformAlloc, Assembly->MemorySize); + Assembly->MemoryBase = TemporaryAssemblyArena.CurrentRegion->Base; + + Assembly->Universes = AddedUniverses.NewUniverseBuffer; + Assembly->SendBuffer = AddedUniverses.NewSendBuffer; + + Assembly->Name = MakeString(PushArray(&TemporaryAssemblyArena, char, AssemblyName.Length), AssemblyName.Length); + CopyStringTo(AssemblyName, &Assembly->Name); + + led_buffer* LEDBuffer = PushStruct(&TemporaryAssemblyArena, led_buffer); + LEDBuffer->Next = 0; + LEDBuffer->Count = 0; + LEDBuffer->Max = LEDCount; + LEDBuffer->LEDs = PushArray(&TemporaryAssemblyArena, led, LEDCount); + LEDBuffer->Colors = PushArray(&TemporaryAssemblyArena, sacn_pixel, LEDCount); + + Assembly->LEDBuffer = LEDBuffer; + + if (State->LEDBufferList) + { + PushLEDBufferOnList(State->LEDBufferList, LEDBuffer); + } + else + { + State->LEDBufferList = LEDBuffer; + } + State->TotalLEDsCount += LEDCount; + + // Add LEDs + for (s32 StripIdx = 0; StripIdx < Definition.LEDStripCount; StripIdx++) + { + led_strip_definition StripDef = Definition.LEDStrips[StripIdx]; + + v3 WS_StripStart = {}; + v3 WS_StripEnd = {}; + s32 LEDsInStripCount = 0; + + switch(StripDef.InterpolationType) + { + case StripInterpolate_Points: + { + WS_StripStart= StripDef.InterpolatePositionStart * Scale; + WS_StripEnd= StripDef.InterpolatePositionEnd * Scale; + LEDsInStripCount = StripDef.LEDsPerStrip; + + }break; + + default: + { + InvalidCodePath; + }break; + } + + sacn_universe* CurrentUniverse = SACNGetUniverse(StripDef.StartUniverse, &State->SACN); + s32 ChannelsUsed = 0; + CurrentUniverse->BeginPixelCopyFromOffset = LEDBuffer->Count * sizeof(sacn_pixel); + + r32 Percent = 0; + r32 PercentStep = 1 / (r32)LEDsInStripCount; + for (s32 Step = 0; Step < LEDsInStripCount; Step++) + { + v3 LEDPosition = Lerp(WS_StripStart, WS_StripEnd, Percent); + s32 LEDIndex = LEDBuffer->Count++; + Assert(LEDIndex < LEDCount); + + led* LED = LEDBuffer->LEDs + LEDIndex; + sacn_pixel* LEDColor = LEDBuffer->Colors + LEDIndex; + + LED->Position = LEDPosition; + LED->PositionMatrix = GetPositionM44(V4(LED->Position, 1)); + LED->Index = LEDIndex; + + Percent += PercentStep; + + ChannelsUsed += 3; + if (ChannelsUsed > STREAM_BODY_SIZE) + { + ChannelsUsed -= STREAM_BODY_SIZE; + CurrentUniverse = SACNGetUniverse(CurrentUniverse->Universe + 1, &State->SACN); + CurrentUniverse->BeginPixelCopyFromOffset = (LEDBuffer->Count + sizeof(sacn_pixel)) - ChannelsUsed; + } + } + } +} + +struct draw_leds_job_data +{ + led* LEDs; + sacn_pixel* Colors; + s32 StartIndex; + s32 OnePastLastIndex; + + render_quad_batch_constructor* Batch; + + m44 FaceCameraMatrix; + m44 ModelViewMatrix; + r32 LEDHalfWidth; +}; + +internal void +DrawLEDsInBufferRangeJob (s32 ThreadID, void* JobData) +{ + DEBUG_TRACK_FUNCTION; + + draw_leds_job_data* Data = (draw_leds_job_data*)JobData; + + s32 DrawCommandsCount = Data->OnePastLastIndex - Data->StartIndex; + + r32 HalfWidth = Data->LEDHalfWidth; + + v4 P0_In = v4{-HalfWidth, -HalfWidth, 0, 1}; + v4 P1_In = v4{HalfWidth, -HalfWidth, 0, 1}; + v4 P2_In = v4{HalfWidth, HalfWidth, 0, 1}; + v4 P3_In = v4{-HalfWidth, HalfWidth, 0, 1}; + + v2 UV0 = v2{0, 0}; + v2 UV1 = v2{1, 0}; + v2 UV2 = v2{1, 1}; + v2 UV3 = v2{0, 1}; + + led* LED = Data->LEDs + Data->StartIndex; + for (s32 LEDIdx = 0; + LEDIdx < DrawCommandsCount; + LEDIdx++) + { + sacn_pixel SACNColor = Data->Colors[LED->Index]; + v4 Color = v4{SACNColor.R / 255.f, SACNColor.G / 255.f, SACNColor.B / 255.f, 1.0f}; + + m44 ModelMatrix = Data->FaceCameraMatrix * LED->PositionMatrix;// * Data->FaceCameraMatrix; + + v4 P0 = ModelMatrix * P0_In; + v4 P1 = ModelMatrix * P1_In; + v4 P2 = ModelMatrix * P2_In; + v4 P3 = ModelMatrix * P3_In; + + PushTri3DOnBatch(Data->Batch, P0, P1, P2, UV0, UV1, UV2, Color); + PushTri3DOnBatch(Data->Batch, P0, P2, P3, UV0, UV2, UV3, Color); + + LED++; + } +} + +struct send_sacn_job_data +{ + streaming_acn SACN; + sacn_universe_buffer UniverseList; + s32 StartUniverse; + s32 OnePastLastUniverse; + platform_send_to* PlatformSendTo; +}; + +internal void +SendSACNBufferData (s32 ThreadID, void* JobData) +{ + DEBUG_TRACK_FUNCTION; + + send_sacn_job_data* Data = (send_sacn_job_data*)JobData; + + sacn_universe* SendUniverse = Data->UniverseList.Universes + Data->StartUniverse; + for (s32 UniverseIdx = Data->StartUniverse; UniverseIdx < Data->OnePastLastUniverse; UniverseIdx++) + { + SACNSendDataToUniverse(&Data->SACN, SendUniverse, Data->PlatformSendTo); + SendUniverse++; + } +} + +internal void +LoadAssembly (app_state* State, context Context, char* Path) +{ + platform_memory_result TestAssemblyFile = Context.PlatformReadEntireFile(Path); + if (TestAssemblyFile.Size <= 0) + { + InvalidCodePath; + } + assembly_definition AssemblyDefinition = ParseAssemblyFile((char*)TestAssemblyFile.Base, State->Transient); + Context.PlatformFree(TestAssemblyFile.Base, TestAssemblyFile.Size); + + string PathString = MakeStringLiteral(Path); + s32 IndexOfLastSlash = FastLastIndexOfChar(PathString.Memory, PathString.Length, '\\'); + string FileName = Substring(PathString, IndexOfLastSlash + 1); + + r32 Scale = 100; + ConstructAssemblyFromDefinition(AssemblyDefinition, FileName, v3{0, 0, 0}, Scale, Context, State); +} + +internal void +UnloadAssembly (s32 AssemblyIndex, app_state* State, context Context) +{ + assembly Assembly = State->AssemblyList[AssemblyIndex]; + SACNRemoveUniverseAndSendBuffer(&State->SACN, Assembly.Universes, Assembly.SendBuffer); + + State->LEDBufferList = RemoveLEDBufferFromList(State->LEDBufferList, Assembly.LEDBuffer); + + s32 LEDsInAssembly = Assembly.LEDBuffer->Count; + s32 MemoryRequiredForAssembly = CalculateMemorySizeForAssembly(LEDsInAssembly, Assembly.Name.Length); + Context.PlatformFree((u8*)Assembly.LEDBuffer, MemoryRequiredForAssembly); + + State->TotalLEDsCount -= LEDsInAssembly; + + if (AssemblyIndex != (State->AssembliesUsed - 1)) + { + State->AssemblyList[AssemblyIndex] = State->AssemblyList[State->AssembliesUsed - 1]; + } + State->AssembliesUsed -= 1; +} + +//////////////////////////////////////////////////////////////////////// + +internal render_texture* +PushTexture (app_state* State) +{ + render_texture* Result = 0; + + if (State->LoadedTexturesUsed < State->LoadedTexturesSize) + { + Result = State->LoadedTextures + State->LoadedTexturesUsed++; + } + else + { + // TODO(Peter): Be able to grow this array + for (s32 i = 0; i < State->LoadedTexturesUsed; i++) + { + if (State->LoadedTextures[i].Handle == 0) + { + Result = State->LoadedTextures + i; + } + } + } + + Assert(Result); + return Result; +} + +internal render_texture* +StoreTexture (app_state* State, u8* Memory, s32 Width, s32 Height, s32 BytesPerPixel, s32 Stride) +{ + render_texture* Result = PushTexture(State); + Result->Memory = Memory; + Result->Handle = 0; + Result->Width = Width; + Result->Height = Height; + Result->BytesPerPixel = BytesPerPixel; + Result->Stride = Stride; +} + +internal void +RemoveTexture (app_state* State, s32 Index) +{ + State->LoadedTextures[Index].Handle = 0; + // TODO(Peter): Free the memory it was using +} + +internal void +RemoveTexture (app_state* State, render_texture* Texture) +{ + Texture->Handle = 0; + // TODO(Peter): Free the memory it was using +} + +RELOAD_STATIC_DATA(ReloadStaticData) +{ + app_state* State = (app_state*)Context.MemoryBase; + + GlobalDebugServices = DebugServices; + + if (State->InputCommandRegistry.Size > 0) + { + RegisterKeyPressCommand(&State->InputCommandRegistry, KeyCode_Delete, false, KeyCode_Invalid, + DeleteSelectedChannelOrPattern); + RegisterKeyPressCommand(&State->InputCommandRegistry, KeyCode_MouseLeftButton, true, KeyCode_Invalid, + CameraMouseControl); + RegisterKeyPressCommand(&State->InputCommandRegistry, KeyCode_U, false, KeyCode_Invalid, ToggleUniverseDebugView); + RegisterMouseWheelCommand(&State->InputCommandRegistry, CameraMouseZoom); + RegisterKeyPressCommand(&State->InputCommandRegistry, KeyCode_A, false, KeyCode_Invalid, AddNode); + } +} + +INITIALIZE_APPLICATION(InitializeApplication) +{ + app_state* State = (app_state*)Context.MemoryBase; + u8* MemoryCursor = Context.MemoryBase + sizeof(app_state); + s32 PermanentStorageSize = Megabytes(32); + s32 TransientStorageSize = Context.MemorySize - PermanentStorageSize; + State->Permanent = BootstrapArenaIntoMemory(MemoryCursor, PermanentStorageSize); + State->Transient = BootstrapArenaIntoMemory(MemoryCursor + PermanentStorageSize, TransientStorageSize); + + InitMemoryArena(&State->SACNMemory, 0, 0, Context.PlatformAlloc); + + State->LoadedTexturesSize = 8; + State->LoadedTextures = PushArray(State->Permanent, render_texture, State->LoadedTexturesSize); + State->LoadedTexturesUsed = 0; + + InitializeInputCommandRegistry(&State->InputCommandRegistry, 32, State->Permanent); + + // TODO(Peter): put in InitializeInterface? + r32 FontSize = 14; + { + platform_memory_result FontFile = Context.PlatformReadEntireFile("Anonymous Pro.ttf"); + if (FontFile.Size) + { + stbtt_fontinfo StbFont; + if (stbtt_InitFont(&StbFont, FontFile.Base, stbtt_GetFontOffsetForIndex(FontFile.Base, 0))) + { + bitmap_font* Font = PushStruct(State->Permanent, bitmap_font); + Font->Atlas = PushTexture(State); + RenderStbttToBitmapFont(Font, StbFont, FontSize, 512, State->Permanent, State->Transient ); + State->Interface.Font = Font; + State->Font = Font; + Font->Atlas->Handle = Context.PlatformGetGPUTextureHandle(Font->Atlas->Memory, Font->Atlas->Width, Font->Atlas->Height); + } + } else {} + } + + State->Interface.FontSize = FontSize; + State->Interface.PanelBGColors[0] = v4{.3f, .3f, .3f, 1}; + State->Interface.PanelBGColors[1] = v4{.4f, .4f, .4f, 1}; + State->Interface.PanelBGColors[2] = v4{.5f, .5f, .5f, 1}; + State->Interface.PanelBGColors[3] = v4{.6f, .6f, .6f, 1}; + State->Interface.ButtonColor_Inactive = BlackV4; + State->Interface.ButtonColor_Active = v4{.1f, .1f, .1f, 1}; + State->Interface.ButtonColor_Selected = v4{.1f, .1f, .3f, 1}; + State->Interface.TextColor = WhiteV4; + State->Interface.Margin = v2{5, 5}; + + State->SACN = InitializeSACN(Context.PlatformAlloc, Context); + + State->Camera.FieldOfView = DegreesToRadians(45.0f); + State->Camera.AspectRatio = (r32)Context.WindowWidth / (r32)Context.WindowHeight; + State->Camera.Near = 1.0f; + State->Camera.Far = 100.0f; + State->Camera.Position = v3{0, 0, -250}; + State->Camera.LookAt = v3{0, 0, 0}; + + InitLEDPatternSystem(&State->PatternSystem, State->Permanent, + 32, Megabytes(4)); + InitLEDChannelSystem(&State->ChannelSystem, State->Permanent, + sizeof(led_channel) * 32); + +#if 1 + char Path[] = "radialumia.fold"; + LoadAssembly(State, Context, Path); +#endif + + State->InterfaceState.AddingPattern = false; + State->InterfaceState.PatternSelectorStart = 0; + State->InterfaceState.ChannelSelected = -1; + + State->InterfaceYMax = 200; + State->PixelsToWorldScale = .01f; + State->Camera_StartDragPos = {}; + + State->UniverseOutputDisplayOffset = v2{0, 0}; + State->UniverseOutputDisplayZoom = 1.0f; + + GlobalDebugServices->Interface.RenderSculpture = true; + + State->NodeList = AllocateNodeList(State->Permanent, Kilobytes(64)); + + State->NodeInteraction = NewNodeInteraction(); + State->NodeRenderSettings.PortDim = v2{20, 15}; + State->NodeRenderSettings.PortStep = State->NodeRenderSettings.PortDim.y + 10; + State->NodeRenderSettings.PortColors[MemberType_r32] = RedV4; + State->NodeRenderSettings.PortColors[MemberType_s32] = GreenV4; + State->NodeRenderSettings.PortColors[MemberType_v4] = BlueV4; + State->NodeRenderSettings.Font = State->Font; + + State->OutputNode = PushOutputNodeOnList(State->NodeList, v2{500, 250}, State->Transient); + + ReloadStaticData(Context, GlobalDebugServices); +} + +UPDATE_AND_RENDER(UpdateAndRender) +{ + app_state* State = (app_state*)Context.MemoryBase; + + ExecuteAllRegisteredCommands(&State->InputCommandRegistry, Input, State); + + UpdateOutputNodeCalculations(State->OutputNode, State->NodeList, State->Transient, + State->LEDBufferList->LEDs, + State->LEDBufferList->Colors, + State->LEDBufferList->Count); + + /* + patterns_update_list Temp_PatternsNeedUpdate = UpdateAllChannels(&State->ChannelSystem, + Context.DeltaTime, + State->Transient); + + UpdateAllPatterns(&Temp_PatternsNeedUpdate, &State->PatternSystem, State->LEDBufferList, Context.DeltaTime, State->Transient); + */ + + { + // NOTE(Peter): We know that these two lists should be maintained together. Each element in the list is one sculpture's worth of + // information, and should always be evaluated in pairs. + sacn_universe_buffer* UniverseList = State->SACN.UniverseBuffer; + led_buffer* LEDBuffer = State->LEDBufferList; + while (UniverseList && LEDBuffer) + { + for (s32 U = 0; U < UniverseList->Used; U++) + { + sacn_universe* UniverseOne = UniverseList->Universes + U; + Assert(UniverseOne->BeginPixelCopyFromOffset >= 0); + + u8* LEDColorBuffer = (u8*)LEDBuffer->Colors + UniverseOne->BeginPixelCopyFromOffset; + u8* SACNSendBuffer = UniverseOne->StartPositionInSendBuffer + STREAM_HEADER_SIZE; + + GSMemCopy(LEDColorBuffer, SACNSendBuffer, STREAM_BODY_SIZE); + } + UniverseList = UniverseList->Next; + LEDBuffer = LEDBuffer->Next; + } + Assert(!LEDBuffer && !UniverseList); + } + + DEBUG_IF(GlobalDebugServices->Interface.SendSACNData) + { + if (++State->SACN.SequenceIterator == 0) // Never use 0 after the first one + { + ++State->SACN.SequenceIterator; + } + + + sacn_universe_buffer* UniverseList = State->SACN.UniverseBuffer; + while (UniverseList) + { + s32 JobCount = 2; + s32 UniversesPerJob = UniverseList->Used / JobCount; + send_sacn_job_data* SACNData = PushArray(State->Transient, send_sacn_job_data, JobCount); + for (s32 i = 0; i < JobCount; i++) + { + SACNData[i].SACN = State->SACN; + SACNData[i].UniverseList = *UniverseList; + SACNData[i].StartUniverse = i * UniversesPerJob; + SACNData[i].OnePastLastUniverse = (i * UniversesPerJob) + UniversesPerJob; + if (SACNData[i].OnePastLastUniverse > UniverseList->Used) + { + SACNData[i].OnePastLastUniverse = UniverseList->Used; + } + Context.GeneralWorkQueue->PushWorkOnQueue( + Context.GeneralWorkQueue, + SendSACNBufferData, + SACNData + i); + } + UniverseList = UniverseList->Next; + } + } + + //////////////////////////////// + // Render Assembly + /////////////////////////////// + if (Context.WindowIsVisible) + { + State->Camera.AspectRatio = (r32)Context.WindowWidth / (r32)Context.WindowHeight; + + m44 ModelViewMatrix = GetCameraModelViewMatrix(State->Camera); + m44 ProjectionMatrix = GetCameraPerspectiveProjectionMatrix(State->Camera); + + RenderBuffer->ViewWidth = Context.WindowWidth; + RenderBuffer->ViewHeight = Context.WindowHeight; + + r32 LEDHalfWidth = .5f; + + PushRenderPerspective(RenderBuffer, Context.WindowWidth, Context.WindowHeight, State->Camera); + PushRenderClearScreen(RenderBuffer); + + DEBUG_IF(GlobalDebugServices->Interface.RenderSculpture) // DebugServices RenderSculpture Toggle + { + s32 JobsNeeded = IntegerDivideRoundUp(State->TotalLEDsCount, LED_BUFFER_SIZE); + + draw_leds_job_data* JobDataBank = PushArray(State->Transient, draw_leds_job_data, JobsNeeded); + s32 JobDataBankUsed = 0; + + // TODO(Peter): Pretty sure this isn't working right now + m44 FaceCameraMatrix = GetLookAtMatrix(v4{0, 0, 0, 1}, V4(State->Camera.Position, 1)); + FaceCameraMatrix = FaceCameraMatrix; + + render_quad_batch_constructor BatchConstructor = PushRenderQuad3DBatch(RenderBuffer, State->TotalLEDsCount); + + led_buffer* LEDBuffer = State->LEDBufferList; + s32 LEDBufferLEDsAssignedToJobs = 0; + + for (s32 Job = 0; Job < JobsNeeded; Job++) + { + draw_leds_job_data* JobData = JobDataBank + JobDataBankUsed++; + JobData->LEDs = LEDBuffer->LEDs; + JobData->Colors = LEDBuffer->Colors; + JobData->StartIndex = LEDBufferLEDsAssignedToJobs; + JobData->OnePastLastIndex = GSMin(JobData->StartIndex + LED_BUFFER_SIZE, LEDBuffer->Count); + + LEDBufferLEDsAssignedToJobs += JobData->OnePastLastIndex - JobData->StartIndex; + + // New + JobData->Batch = &BatchConstructor; + + JobData->FaceCameraMatrix = FaceCameraMatrix; + JobData->ModelViewMatrix = ModelViewMatrix; + JobData->LEDHalfWidth = LEDHalfWidth; + + Context.GeneralWorkQueue->PushWorkOnQueue( + Context.GeneralWorkQueue, + DrawLEDsInBufferRangeJob, + JobData); + + Assert(LEDBufferLEDsAssignedToJobs <= LEDBuffer->Count); // We should never go OVER the number of leds in the buffer + if (LEDBufferLEDsAssignedToJobs == LEDBuffer->Count) + { + LEDBuffer = LEDBuffer->Next; + LEDBufferLEDsAssignedToJobs = 0; + } + } + + Context.GeneralWorkQueue->DoQueueWorkUntilDone(Context.GeneralWorkQueue, 0); + Context.GeneralWorkQueue->ResetWorkQueue(Context.GeneralWorkQueue); + } + + /////////////////////////////////////// + // Interface + ////////////////////////////////////// + + DEBUG_TRACK_SCOPE(DrawInterface); + + PushRenderOrthographic(RenderBuffer, Context.WindowWidth, Context.WindowHeight); + + // Universe Data View + if (State->DrawUniverseOutputDisplay) + { + DEBUG_TRACK_SCOPE(DrawUniverseOutputDisplay); + + string TitleBarString = InitializeString(PushArray(State->Transient, char, 64), 64); + + v2 DisplayArea_Dimension = v2{600, 600}; + v2 DisplayContents_Offset = State->UniverseOutputDisplayOffset; + v2 DisplayArea_TopLeft = v2{300, Context.WindowHeight - 50} + DisplayContents_Offset; + v2 UniverseDisplayDimension = v2{100, 100} * State->UniverseOutputDisplayZoom; + v2 Padding = v2{25, 50} * State->UniverseOutputDisplayZoom; + + v2 UniverseDisplayTopLeft = DisplayArea_TopLeft; + + sacn_universe_buffer* UniverseList = State->SACN.UniverseBuffer; + while(UniverseList) + { + for (s32 UniverseIdx = 0; + UniverseIdx < UniverseList->Used; + UniverseIdx++) + { + sacn_universe* Universe = UniverseList->Universes + UniverseIdx; + + DrawSACNUniversePixels(RenderBuffer, Universe, + UniverseDisplayTopLeft, UniverseDisplayDimension); + + if (State->UniverseOutputDisplayZoom > .5f) + { + v2 TitleDisplayStart = UniverseDisplayTopLeft + v2{0, 12}; + PrintF(&TitleBarString, "Universe %d", Universe->Universe); + DrawString(RenderBuffer, TitleBarString, State->Interface.Font, 12, + TitleDisplayStart, WhiteV4); + } + + UniverseDisplayTopLeft.x += UniverseDisplayDimension.x + Padding.x; + if (UniverseDisplayTopLeft.x > DisplayArea_TopLeft.x + DisplayArea_Dimension.x) + { + UniverseDisplayTopLeft.x = DisplayArea_TopLeft.x; + UniverseDisplayTopLeft.y -= UniverseDisplayDimension.y + Padding.y; + } + + if (UniverseDisplayTopLeft.y < DisplayArea_TopLeft.y - DisplayArea_Dimension.y) + { + break; + } + } + UniverseList = UniverseList->Next; + } + } + + /////////////////////////////////////// + // Menu Bar + ////////////////////////////////////// + + r32 TopBarHeight = 40; + { + panel_result TopBarPanel = EvaluatePanel(RenderBuffer, + v2{0, Context.WindowHeight - TopBarHeight}, + v2{Context.WindowWidth, Context.WindowHeight}, + 0, State->Interface, Input); + + v2 ButtonDim = v2{200, State->Interface.Font->NewLineYOffset + 10}; + v2 ButtonPos = v2{State->Interface.Margin.x, Context.WindowHeight - (ButtonDim.y + 10)}; + button_result LoadAssemblyBtn = EvaluateButton(RenderBuffer, ButtonPos, ButtonPos + ButtonDim, + MakeStringLiteral("Load Assembly"), + State->Interface, Input); + + string InterfaceString = MakeString(PushArray(State->Transient, char, 256), 256); + for (int i = 0; i < State->AssembliesUsed; i++) + { + PrintF(&InterfaceString, "Unload %.*s", State->AssemblyList[i].Name.Length, State->AssemblyList[i].Name.Memory); + + ButtonPos.x += ButtonDim.x + 10; + button_result UnloadAssemblyBtn = EvaluateButton(RenderBuffer, ButtonPos, ButtonPos + ButtonDim, + InterfaceString, State->Interface, Input); + + if (UnloadAssemblyBtn.Pressed) + { + UnloadAssembly(i, State, Context); + } + } + + if (LoadAssemblyBtn.Pressed) + { + char FilePath[256]; + b32 Success = Context.PlatformGetFilePath(FilePath, 256); + if (Success) + { + LoadAssembly(State, Context, FilePath); + } + } + } + + /////////////////////////////////////// + // Figuring Out Nodes + ////////////////////////////////////// + { + v2 MousePos = v2{(r32)Input.New->MouseX, (r32)Input.New->MouseY}; + v2 LastFrameMousePos = v2{(r32)Input.Old->MouseX, (r32)Input.Old->MouseY}; + + if (KeyTransitionedDown(Input, KeyCode_MouseLeftButton)) + { + node_offset Node = GetNodeUnderPoint(State->NodeList, MousePos, State->NodeRenderSettings); + if (Node.Node) + { + State->NodeInteraction = GetNodeInteractionType(Node.Node, Node.Offset, MousePos, State->NodeRenderSettings); + } + } + else if (KeyTransitionedUp(Input, KeyCode_MouseLeftButton)) + { + if (IsDraggingNodePort(State->NodeInteraction)) + { + TryConnectNodes(State->NodeInteraction, MousePos, State->NodeList, State->NodeRenderSettings); + } + State->NodeInteraction = NewNodeInteraction(); + } + + UpdateDraggingNode(MousePos, State->NodeInteraction, State->NodeList, + State->NodeRenderSettings); + UpdateDraggingNodePort(MousePos, State->NodeInteraction, State->NodeList, + State->NodeRenderSettings, RenderBuffer); + UpdateDraggingNodeValue(MousePos, LastFrameMousePos, State->NodeInteraction, State->NodeList, State->NodeRenderSettings, State); + + RenderNodeList(State->NodeList, State->NodeRenderSettings, RenderBuffer); + + ResetNodesUpdateState(State->NodeList); + + if (State->InterfaceShowNodeList) + { + v2 TopLeft = State->NodeListMenuPosition; + + // Title Bar + PushRenderQuad2D(RenderBuffer, v2{TopLeft.x, TopLeft.y - 30}, v2{TopLeft.x + 300, TopLeft.y}, + v4{.3f, .3f, .3f, 1.f}); + DrawString(RenderBuffer, MakeStringLiteral("Nodes List"), State->Font, 14, + v2{TopLeft.x, TopLeft.y - 25}, WhiteV4); + TopLeft.y -= 30; + + for (s32 i = 0; i < NodeSpecificationsCount; i++) + { + node_specification Spec = NodeSpecifications[i]; + + button_result Button = EvaluateButton(RenderBuffer, v2{TopLeft.x, TopLeft.y - 30}, v2{TopLeft.x + 300, TopLeft.y}, + MakeStringLiteral(Spec.Name), State->Interface, Input); + if (Button.Pressed) + { + PushNodeOnListFromSpecification(State->NodeList, Spec, MousePos, State->Permanent); + } + + TopLeft.y -= 30; + } + + if (KeyTransitionedDown(Input, KeyCode_MouseLeftButton) || + KeyTransitionedDown(Input, KeyCode_Esc)) + { + State->InterfaceShowNodeList = false; + } + } + } + + if (State->ColorPickerEditValue != 0) + { + b32 ShouldClose = EvaluateColorPicker(RenderBuffer, State->ColorPickerEditValue, + v2{200, 200}, State->Interface, Input); + + if (ShouldClose || + KeyTransitionedDown(Input, KeyCode_Esc)) + { + State->ColorPickerEditValue = 0; + } + } + +#if 0 + /////////////////////////////////////// + // Current Patterns Panel + ////////////////////////////////////// + + r32 LeftPanelRightEdge = DrawLeftHandInterface(State, Input, Context.WindowHeight - TopBarHeight, RenderBuffer); + + if (State->InterfaceState.ChannelSelected >= 0) + { + led_channel* ActiveChannel = GetChannelByIndex(State->InterfaceState.ChannelSelected, + State->ChannelSystem); + + button_result OperationButtonState = EvaluateButton( + RenderBuffer, + v2{Context.WindowWidth - 150, 500}, + v2{Context.WindowWidth - 50, 550}, + MakeStringLiteral(PatternSelectorOperationsText[ActiveChannel->BlendMode]), + State->Interface, + Input); + + if (OperationButtonState.Pressed) + { + State->InterfaceState.ChooseOperationPanelOpen = !State->InterfaceState.ChooseOperationPanelOpen; + } + + if (State->InterfaceState.ChooseOperationPanelOpen) + { + s32 StringLength = 128; + s32 OperationsStart = PatternSelectorCombine_Invalid + 1; + s32 OperationsOnePastLast = PatternSelectorCombine_Count; + s32 OperationsCount = (OperationsOnePastLast - OperationsStart); + string* OperationChoices = PushArray(State->Transient, string, OperationsCount); + + for (s32 Choice = OperationsStart; + Choice < OperationsOnePastLast; + Choice++) + { + s32 Index = Choice - OperationsStart; + PushString(&OperationChoices[Index], State->Transient, StringLength); + CopyCharArrayToString(PatternSelectorOperationsText[Choice], + &OperationChoices[Index]); + } + + v2 Min = v2{Context.WindowWidth - 250, 250}; + v2 Max = v2{Context.WindowWidth - 50, 500}; + + scroll_list_result OperationChoice = DrawSelectableOptionsList(RenderBuffer, Min, Max, OperationChoices, OperationsCount, + 0, ActiveChannel->BlendMode - 1, + State->Interface, Input); + if (OperationChoice.IndexSelected + 1 > (int)ChannelBlend_Invalid && + OperationChoice.IndexSelected + 1 < (int)ChannelBlend_Count) + { + ActiveChannel->BlendMode = (channel_blend_mode)(OperationChoice.IndexSelected + 1); + } + } + } + +#endif + + DrawDebugInterface(RenderBuffer, 25, + State->Interface, Context.WindowWidth, Context.WindowHeight - TopBarHeight, + Context.DeltaTime, State->Camera, Input, State->Transient); + } + + ClearArena(State->Transient); + EndDebugFrame(GlobalDebugServices); +} + +CLEANUP_APPLICATION(CleanupApplication) +{ + app_state* State = (app_state*)Context.MemoryBase; + SACNCleanup(&State->SACN, Context); +} \ No newline at end of file diff --git a/foldhaus_app.h b/foldhaus_app.h new file mode 100644 index 0000000..20f3c1e --- /dev/null +++ b/foldhaus_app.h @@ -0,0 +1,126 @@ +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb/stb_truetype.h" + +#include "../meta/gs_meta_lexer.h" + +#include "gs_font.h" +#include "interface.h" + +#include "foldhaus_network_ordering.h" +#include "foldhaus_sacn.h" + +#define LED_BUFFER_SIZE 256 +struct led +{ + s32 Index; + v3 Position; + m44 PositionMatrix; +}; + +struct led_buffer +{ + sacn_pixel* Colors; + led* LEDs; + s32 Count; + s32 Max; + + led_buffer* Next; +}; + +#define CalculateMemorySizeForAssembly(leds, name_length) ((sizeof(led) + sizeof(sacn_pixel)) * (leds)) + sizeof(led_buffer) + name_length; +struct assembly +{ + s32 MemorySize; + u8* MemoryBase; + + string Name; + string FilePath; + led_buffer* LEDBuffer; + + // Memory managed by the SACN system + sacn_universe_buffer* Universes; + sacn_send_buffer* SendBuffer; +}; + +#include "assembly_parser.h" +//#include "foldhaus_environment.h" + +// TODO(Peter): remove this, and get pattern_system.h outta here!! +typedef struct led_pattern led_pattern; + +#include "foldhaus_node.h" + +#include "foldhaus_patterns.h" +#include "foldhaus_channel.h" +#include "foldhaus_patterns.cpp" +#include "foldhaus_channel.cpp" + +#include "assembly_parser.cpp" + +#include "test_patterns.h" +#include "patterns_registry.h" +#include "foldhaus_interface.h" + +typedef struct app_state app_state; + +#include "foldhaus_debug_visuals.h" +#include "foldhaus_command_dispatch.h" + +#include "generated/foldhaus_nodes_generated.cpp" + +struct app_state +{ + memory_arena* Permanent; + memory_arena* Transient; + memory_arena SACNMemory; + + render_texture* LoadedTextures; + s32 LoadedTexturesSize; + s32 LoadedTexturesUsed; + + camera Camera; + + input_command_registry InputCommandRegistry; + + streaming_acn SACN; + s32 TotalLEDsCount; + led_buffer* LEDBufferList; + + // TODO(Peter): Make this dynamic. WE want them contiguous in memory since we'll be accessing them + // mostly by looping through them. On the other hand, I don't expect there to ever be more than 100 + // of them at once. +#define ASSEMBLY_LIST_LENGTH 32 + assembly AssemblyList[ASSEMBLY_LIST_LENGTH]; + s32 AssembliesUsed; + + //environment Environment; + led_channel_system ChannelSystem; + led_pattern_system PatternSystem; + + bitmap_font* Font; + interface_state InterfaceState; + interface_config Interface; + + r32 InterfaceYMax; + r32 PixelsToWorldScale; + v4 Camera_StartDragPos; + + b32 DrawUniverseOutputDisplay; + v2 UniverseOutputDisplayOffset; + r32 UniverseOutputDisplayZoom; + + b32 InterfaceShowNodeList; + v2 NodeListMenuPosition; + + node_list* NodeList; + node_interaction NodeInteraction; + node_render_settings NodeRenderSettings; + interface_node* OutputNode; + + v4* ColorPickerEditValue; +}; + +#include "foldhaus_sacn_view.cpp" +#include "foldhaus_command_dispatch.cpp" +#include "foldhaus_node.cpp" +#include "foldhaus_interface.cpp" diff --git a/foldhaus_channel.cpp b/foldhaus_channel.cpp new file mode 100644 index 0000000..9b1dd8e --- /dev/null +++ b/foldhaus_channel.cpp @@ -0,0 +1,261 @@ +inline void +SetTransition(pattern_transition* Transition, r32 Duration, r32 Elapsed) +{ + Transition->Duration = Duration; + Transition->TimeElapsed = Elapsed; +} + +internal void +InitLEDChannelSystem (led_channel_system* System, memory_arena* ParentStorage, s32 StorageSize) +{ + System->Channels = 0; + System->ChannelCount = 0; + InitMemoryArena(&System->Storage, PushSize(ParentStorage, StorageSize), StorageSize, 0); + System->FreeList = 0; +} + +inline void +ResetChannel (led_channel* Channel, led_channel_system* ChannelSystem) +{ + if (Channel->Patterns == 0) + { + Channel->Patterns = PushArray(&ChannelSystem->Storage, + pattern_index_id_key, + CHANNEL_MAX_PATTERNS); + } + Channel->ActivePatterns = 0; + Channel->ActivePatternIndex = -1; + + SetTransition(&Channel->Transition, 3, 0); + + Channel->BlendMode = ChannelBlend_Override; + Channel->ChannelID = ChannelIDAccumulator++; + + Channel->Next = 0; +} + +internal led_channel* +AddLEDChannel (led_channel_system* ChannelSystem) +{ + led_channel* Channel = 0; + + if (ChannelSystem->FreeList) + { + Channel = ChannelSystem->FreeList; + ChannelSystem->FreeList = ChannelSystem->FreeList->Next; + } + else + { + Channel = PushStruct(&ChannelSystem->Storage, led_channel); + } + ResetChannel(Channel, ChannelSystem); + + Channel->Next = ChannelSystem->Channels; + ChannelSystem->Channels = Channel; + ChannelSystem->ChannelCount++; + + return Channel; +} + +internal b32 +RemoveLEDChannel_ (led_channel* PrevChannel, led_channel* ToRemove, led_channel_system* ChannelSystem) +{ + b32 Result = true; + + if (PrevChannel->Next == ToRemove) + { + PrevChannel->Next = ToRemove->Next; + ToRemove->Next = ChannelSystem->FreeList; + ChannelSystem->FreeList = ToRemove; + ChannelSystem->ChannelCount--; + Result = true; + } + else + { + Result = false; + } + + return Result; +} + +internal b32 +RemoveLEDChannel (led_channel* Channel, led_channel_system* ChannelSystem) +{ + b32 Result = true; + + led_channel* Cursor = ChannelSystem->Channels; + for (s32 i = 0; + (Cursor->Next != Channel && i < ChannelSystem->ChannelCount); + Cursor = Cursor->Next, i++){} + + Result = RemoveLEDChannel_(Cursor, Channel, ChannelSystem); + + return Result; +} + +internal b32 +RemoveLEDChannel (s32 Index, led_channel_system* ChannelSystem) +{ + Assert(Index < ChannelSystem->ChannelCount); + b32 Result = true; + + if (Index == 0) + { + led_channel* FirstChannel = ChannelSystem->Channels; + ChannelSystem->Channels = FirstChannel->Next; + FirstChannel->Next = ChannelSystem->FreeList; + ChannelSystem->FreeList = FirstChannel; + ChannelSystem->ChannelCount--; + Result = true; + } + else + { + led_channel* PrevChannel = ChannelSystem->Channels; + for (s32 i = 0; i < Index - 1; i++) + { + PrevChannel = PrevChannel->Next; + } + + Result = RemoveLEDChannel_(PrevChannel, PrevChannel->Next, ChannelSystem); + } + + return Result; +} + +internal led_channel* +GetChannelByIndex (s32 Index, led_channel_system ChannelSystem) +{ + Assert(Index < ChannelSystem.ChannelCount); + led_channel* Result = ChannelSystem.Channels; + + for (s32 i = 0; i < Index; i++) + { + Result = Result->Next; + } + + return Result; +} + +internal void +AddPatternKeyToChannel (pattern_index_id_key Key, led_channel* Channel) +{ + Assert(Channel->ActivePatterns < CHANNEL_MAX_PATTERNS); + Channel->Patterns[Channel->ActivePatterns++] = Key; +} + +internal b32 +RemovePatternKeyFromChannel (pattern_index_id_key Key, led_channel* Channel) +{ + b32 Result = false; + + s32 RemoveFrom = -1; + for (s32 i = 0; i < Channel->ActivePatterns; i++) + { + if (Channel->Patterns[i].ID == Key.ID) + { + RemoveFrom = i; + } + } + + if (RemoveFrom >= 0) + { + for (s32 j = 0; j < Channel->ActivePatterns; j++) + { + Channel->Patterns[j] = Channel->Patterns[j + 1]; + } + Channel->ActivePatterns--; + Result = true; + } + + return Result; +} + +internal void +PushPatternKeyOnUpdateList (pattern_index_id_key Key, + pattern_push_color_proc* PushColorProc, + patterns_update_list* UpdateList, + memory_arena* Storage) +{ + if (UpdateList->Used >= UpdateList->Size) + { + if (!UpdateList->Next) + { + UpdateList->Next = PushStruct(Storage, patterns_update_list); + UpdateList->Next->Size = UpdateList->Size; + UpdateList->Next->Used = 0; + UpdateList->Next->Patterns = PushArray(Storage, pattern_update_list_entry, UpdateList->Next->Size); + } + PushPatternKeyOnUpdateList(Key, PushColorProc, UpdateList->Next, Storage); + } + else + { + pattern_update_list_entry Entry = {}; + Entry.Key = Key; + Entry.PushColorProc = PushColorProc; + UpdateList->Patterns[UpdateList->Used++] = Entry; + } +} + +internal void +UpdateChannel (led_channel* Channel, + patterns_update_list* PatternsNeedUpdateList, + r32 DeltaTime, + memory_arena* Storage) +{ + // Update Transition + Channel->Transition.TimeElapsed += DeltaTime; + if (Channel->Transition.TimeElapsed >= Channel->Transition.Duration || + (Channel->ActivePatternIndex < 0 && Channel->ActivePatterns > 0)) + { + Channel->Transition.TimeElapsed -= Channel->Transition.Duration; + Channel->ActivePatternIndex++; + if (Channel->ActivePatternIndex >= Channel->ActivePatterns) + { + Channel->ActivePatternIndex = 0; + } + } + + // Create Active Pattern List + if (Channel->ActivePatterns > 0) + { + Assert(Channel->ActivePatternIndex >= 0 && Channel->ActivePatternIndex < Channel->ActivePatterns); + + pattern_push_color_proc* PushColorProc = 0; + switch (Channel->BlendMode) + { + case ChannelBlend_Override: { PushColorProc = PushColor_Override; } break; + case ChannelBlend_Add: { PushColorProc = PushColor_Add; } break; + case ChannelBlend_Multiply: { PushColorProc = PushColor_Multiply; } break; + default: + { + InvalidCodePath; + }break; + } + + PushPatternKeyOnUpdateList(Channel->Patterns[Channel->ActivePatternIndex], + PushColorProc, + PatternsNeedUpdateList, Storage); + } +} + +internal patterns_update_list +UpdateAllChannels (led_channel_system* ChannelSystem, r32 DeltaTime, memory_arena* Transient) +{ + patterns_update_list Result = {}; + // NOTE(Peter): The initial size of this array is ChannelCount * 2 b/c at the moment, in the worst case, + // we need to update the two patterns a channel is blending between, and there isn't a case where we'd + // update 3 per channel + Result.Size = ChannelSystem->ChannelCount * 2; + Result.Used = 0; + Result.Patterns = PushArray(Transient, pattern_update_list_entry, Result.Size); + Result.Next = 0; + + for (led_channel* Channel = ChannelSystem->Channels; + Channel; + Channel = Channel->Next) + { + UpdateChannel(Channel, &Result, DeltaTime, Transient); + } + + return Result; +} diff --git a/foldhaus_channel.h b/foldhaus_channel.h new file mode 100644 index 0000000..0a2c722 --- /dev/null +++ b/foldhaus_channel.h @@ -0,0 +1,98 @@ +#if 0 // USAGE CODE + +{ + channel* FirstChannel = AddChannel(State, State->Channels); + + channel* ActiveChannel = GetChannelAtIndex(State->Channels, ActiveIndex); + AddPattern(State, ActiveChannel, PatternSpec); + + patterns_need_update UpdatePatternsList = UdpateAllChannels(ChannelSystem, State->Transient); + + for (s32 i = 0; i < UpdatePatternsList.Count; i++) + { + UpdateActivePatterns(State, Patterns, UpdatePatternsList.PatternIDs[i]); + } + + pattern* Pattern = ...; + DeletePattern(State, Pattern); + + RemoveChannel(FirstChannel, State->Channels); + RemoveChannel(2, State->Channels); +} +#endif + +struct pattern_transition +{ + r32 Duration; + r32 TimeElapsed; +}; + +enum channel_blend_mode +{ + ChannelBlend_Invalid, + + ChannelBlend_Override, + ChannelBlend_Add, + ChannelBlend_Multiply, + + ChannelBlend_Count, +}; + +char* METAChannelBlendModeNames[] = { + "Invalid", //ChannelBlend_Invalid + "Override", //ChannelBlend_Override + "Add", // ChannelBlend_Add + "Multiply", //ChannelBlend_Multiply + "Count", //ChannelBlend_Count +}; + +global_variable s32 ChannelIDAccumulator; + +// TODO(Peter): This number is gonna have to get bigger +#define CHANNEL_MAX_PATTERNS 8 +struct led_channel +{ + s32 ChannelID; + + pattern_index_id_key* Patterns; + // TODO(Peter): Rename this once we get patterns in their own system. All patterns in this + // list are active. ATM this is just here for legacy reasons. + s32 ActivePatterns; + // TODO(Peter): and this should probably be CurrentPatternIndex, or HotPatternIndex + s32 ActivePatternIndex; + + // TODO(Peter): extend this to be able to have different kinds of transitions + // including, most importantly, being able to fade between patterns + pattern_transition Transition; + channel_blend_mode BlendMode; + + led_channel* Next; +}; + +struct led_channel_system +{ + led_channel* Channels; + s32 ChannelCount; + + // TODO(Peter): think about ways this can give back to the main pool + // Like, if we just stay within initial storage, no problem. But if we grow really big, + // then shrink really small, we should probably give some of it back to the main app. + // maybe by keeping track of FreeList size... + // TODO(Peter): Also need to think about how memory_arenas get bigger... + memory_arena Storage; + led_channel* FreeList; +}; + +struct pattern_update_list_entry +{ + pattern_index_id_key Key; + pattern_push_color_proc* PushColorProc; +}; + +struct patterns_update_list +{ + pattern_update_list_entry* Patterns; + s32 Size; + s32 Used; + patterns_update_list* Next; +}; diff --git a/foldhaus_command_dispatch.cpp b/foldhaus_command_dispatch.cpp new file mode 100644 index 0000000..a96944c --- /dev/null +++ b/foldhaus_command_dispatch.cpp @@ -0,0 +1,89 @@ +internal void +InitializeInputCommandRegistry (input_command_registry* CommandRegistry, + s32 Size, + memory_arena* Storage) +{ + CommandRegistry->Commands = PushArray(Storage, input_command, Size); + CommandRegistry->Size = Size; + CommandRegistry->Used = 0; +} + +internal input_command* +FindExistingCommand (input_command_registry* CommandRegistry, key_code Key, key_code Mdfr) +{ + input_command* Result = 0; + + for (s32 Cmd = 0; Cmd < CommandRegistry->Used; Cmd++) + { + input_command* Command = CommandRegistry->Commands + Cmd; + if (Command->Key == Key && Command->Mdfr == Mdfr) + { + Result = Command; + break; + } + } + + return Result; +} + +internal void +RegisterKeyPressCommand (input_command_registry* CommandRegistry, + key_code Key, + b32 Held, + key_code Mdfr, + input_command_proc* Proc) +{ + input_command* Command = FindExistingCommand(CommandRegistry, Key, Mdfr); + + if (!Command) + { + Assert(CommandRegistry->Size > CommandRegistry->Used); + Assert(Mdfr == KeyCode_Invalid || Mdfr == KeyCode_LeftShift || Mdfr == KeyCode_RightShift || + Mdfr == KeyCode_LeftCtrl || Mdfr == KeyCode_RightCtrl || Mdfr == KeyCode_Alt); + Command = CommandRegistry->Commands + CommandRegistry->Used++; + } + + Command->Key = Key; + Command->Held = Held; + Command->Mdfr = Mdfr; + Command->Proc = Proc; +} + +internal void +RegisterMouseWheelCommand (input_command_registry* CommandRegistry, + input_command_proc* Proc) +{ + CommandRegistry->MouseWheelCommand = Proc; +} + +internal void +ExecuteAllRegisteredCommands (input_command_registry* CommandRegistry, + input Input, + app_state* State) +{ + if (Input.New->MouseScroll != 0) + { + CommandRegistry->MouseWheelCommand(State, Input); + } + + for (s32 i = 0; i < CommandRegistry->Used; i++) + { + input_command Command = CommandRegistry->Commands[i]; + if (Command.Held) + { + if (KeyDown(Input, Command.Key) && + (Command.Mdfr == KeyCode_Invalid || KeyDown(Input, Command.Mdfr))) + { + Command.Proc(State, Input); + } + } + else + { + if (KeyTransitionedDown(Input, Command.Key) && + (Command.Mdfr == KeyCode_Invalid || KeyDown(Input, Command.Mdfr))) + { + Command.Proc(State, Input); + } + } + } +} diff --git a/foldhaus_command_dispatch.h b/foldhaus_command_dispatch.h new file mode 100644 index 0000000..255622f --- /dev/null +++ b/foldhaus_command_dispatch.h @@ -0,0 +1,30 @@ +#if USAGE_CODE + +RegisterInputCommand(KeyCode_UpArrow, SACNView_DrawNextUniverse, Context); +RegisterInputCommand(KeyCode_MouseLeftButton, PanCamera, Context); + +ExecuteRegisteredCommands(Context, Input); + +#endif // USAGE_CODE + +#define FOLDHAUS_INPUT_COMMAND_PROC(name) void name(app_state* State, input Input) +typedef FOLDHAUS_INPUT_COMMAND_PROC(input_command_proc); + +// TODO(Peter): At the moment these are all key press commands. Need a way to differentiate between +// press and hold. Probably add a second array to input_command_Registry +struct input_command +{ + key_code Key; + b32 Held; + key_code Mdfr; + input_command_proc* Proc; +}; + +struct input_command_registry +{ + input_command* Commands; + s32 Size; + s32 Used; + + input_command_proc* MouseWheelCommand; +}; diff --git a/foldhaus_debug.h b/foldhaus_debug.h new file mode 100644 index 0000000..6bfaf03 --- /dev/null +++ b/foldhaus_debug.h @@ -0,0 +1,244 @@ +#define SCOPE_NAME_LENGTH 256 +struct scope_time_record +{ + char ScopeName_[SCOPE_NAME_LENGTH]; + string ScopeName; + + u32 Duration_Cycles; +}; + +struct debug_interface +{ + b32 ShowCameraMouse; + b32 ShowTrackedScopes; + b32 RenderSculpture; + b32 SendSACNData; +}; + +typedef s64 debug_timing_proc(); + +#define HISTOGRAM_DEPTH 10 +struct debug_histogram_entry +{ + char ScopeName_[SCOPE_NAME_LENGTH]; + string ScopeName; + + u32 PerFrame_Cycles[HISTOGRAM_DEPTH]; + u32 PerFrame_CallCount[HISTOGRAM_DEPTH]; + s32 CurrentFrame; + + // NOTE(Peter): Cached Values, recalculated ever frame + u32 Average_Cycles; + u32 Average_CallCount; + u32 Total_Cycles; + u32 Total_CallCount; +}; + +#define SCOPE_HISTOGRAM_SIZE 512 +struct debug_services +{ + s32 TrackedScopesCount; + s32 TrackedScopesMax; + scope_time_record* TrackedScopes; + + s64 PerformanceCountFrequency; + memory_arena DebugStorage; + + debug_interface Interface; + + debug_timing_proc* GetWallClock; + + debug_histogram_entry ScopeHistogram[SCOPE_HISTOGRAM_SIZE]; + s32 ScopeHistogramUsed; +}; + +internal void +InitDebugServices (debug_services* Services, u8* Memory, s32 MemorySize, s32 TrackedScopesMax, s64 PerformanceCountFrequency) +{ + InitMemoryArena(&Services->DebugStorage, Memory, MemorySize, 0); + + Services->TrackedScopesCount = 0; + Services->TrackedScopesMax = TrackedScopesMax; + Services->TrackedScopes = PushArray(&Services->DebugStorage, scope_time_record, TrackedScopesMax); + + Services->Interface.RenderSculpture = true; + + Services->PerformanceCountFrequency = PerformanceCountFrequency; + + Services->Interface.ShowCameraMouse = false; + Services->Interface.ShowTrackedScopes = false; + Services->Interface.RenderSculpture = true; + Services->Interface.SendSACNData = false; + + Services->ScopeHistogramUsed = 0; +} + +internal s32 +DEBUGFindScopeHistogram (debug_services* Services, string Name) +{ + s32 Result = -1; + for (s32 i = 0; i < SCOPE_HISTOGRAM_SIZE; i++) + { + if (StringsEqual(Services->ScopeHistogram[i].ScopeName, Name)) + { + Result = i; + break; + } + } + return Result; +} + +internal s32 +DEBUGAddScopeHistogram (debug_services* Services, scope_time_record Record) +{ + Assert(Services->ScopeHistogramUsed < SCOPE_HISTOGRAM_SIZE); + + s32 Result = Services->ScopeHistogramUsed++; + + debug_histogram_entry* Entry = Services->ScopeHistogram + Result; + Entry->ScopeName = MakeString(Entry->ScopeName_, 256); + + Entry->CurrentFrame = 0; + Entry->Average_Cycles = 0; + Entry->Average_CallCount = 0; + Entry->Total_Cycles = 0; + Entry->Total_CallCount = 0; + + CopyStringTo(Record.ScopeName, &Entry->ScopeName); + + return Result; +} + +internal void +DEBUGRecordScopeInHistogram (debug_services* Services, s32 Index, scope_time_record Record) +{ + debug_histogram_entry* Entry = Services->ScopeHistogram + Index; + s32 FrameIndex = Entry->CurrentFrame; + if (FrameIndex >= 0 && FrameIndex < HISTOGRAM_DEPTH) + { + Entry->PerFrame_Cycles[FrameIndex] += Record.Duration_Cycles; + Entry->PerFrame_CallCount[FrameIndex]++; + } +} + +internal void +DEBUGCacheScopeHistogramValues (debug_histogram_entry* Histogram) +{ + Histogram->Total_Cycles = 0; + Histogram->Total_CallCount = 0; + + // TODO(Peter): This doesn't account for the first frames when the histogram isn't full + for (s32 i = 0; i < HISTOGRAM_DEPTH; i++) + { + Histogram->Total_Cycles += Histogram->PerFrame_Cycles[i]; + Histogram->Total_CallCount += Histogram->PerFrame_CallCount[i]; + } + + Histogram->Average_Cycles = (Histogram->Total_Cycles / HISTOGRAM_DEPTH); + Histogram->Average_CallCount = (Histogram->Total_CallCount / HISTOGRAM_DEPTH); +} + +internal void +DEBUGCollateScopeRecords (debug_services* Services) +{ + for (s32 i = 0; i < Services->TrackedScopesCount; i++) + { + scope_time_record Record = Services->TrackedScopes[i]; + s32 Index = DEBUGFindScopeHistogram(Services, Record.ScopeName); + if (Index < 0) + { + Index = DEBUGAddScopeHistogram(Services, Record); + } + + DEBUGRecordScopeInHistogram(Services, Index, Record); + } + + for (s32 h = 0; h < Services->ScopeHistogramUsed; h++) + { + DEBUGCacheScopeHistogramValues(Services->ScopeHistogram + h); + } +} + +internal void +EndDebugFrame (debug_services* Services) +{ + DEBUGCollateScopeRecords(Services); + + GSZeroMemory((u8*)Services->TrackedScopes, sizeof(scope_time_record) * Services->TrackedScopesMax); + Services->TrackedScopesCount = 0; + + for (s32 i = 0; i < Services->ScopeHistogramUsed; i++) + { + s32 NewFrame = Services->ScopeHistogram[i].CurrentFrame + 1; + if (NewFrame >= HISTOGRAM_DEPTH) + { + NewFrame = 0; + } + Services->ScopeHistogram[i].CurrentFrame = NewFrame; + Services->ScopeHistogram[i].PerFrame_Cycles[NewFrame] = 0; + Services->ScopeHistogram[i].PerFrame_CallCount[NewFrame] = 0; + } +} + +internal scope_time_record* +PushScopeRecord(string ScopeName, debug_services* Services) +{ + scope_time_record* Result = 0; + + s32 OnePastIndex = InterlockedIncrement((long*)&Services->TrackedScopesCount); + Assert(OnePastIndex <= Services->TrackedScopesMax); + Result = Services->TrackedScopes + OnePastIndex - 1; + + Result->ScopeName = MakeString(Result->ScopeName_, SCOPE_NAME_LENGTH); + CopyStringTo(ScopeName, &Result->ScopeName); + + return Result; +} + +internal void +LogScopeTime (debug_services* Services, string ScopeName, u64 CyclesElapsed) +{ + scope_time_record* Record = PushScopeRecord(ScopeName, Services); + Record->Duration_Cycles = CyclesElapsed; +} + +internal r32 DEBUGGetSecondsElapsed (s64 Start, s64 End, r32 PerformanceCountFrequency) +{ + r32 Result = ((r32)(End - Start) / (r32)PerformanceCountFrequency); + return Result; +} + +#if 1 +#define DEBUG_TRACK_FUNCTION scope_tracker ScopeTracker (__FUNCTION__, GlobalDebugServices) +#define DEBUG_TRACK_SCOPE(name) scope_tracker ScopeTracker_##name (#name, GlobalDebugServices) +#else +#define DEBUG_TRACK_FUNCTION +#define DEBUG_TRACK_SCOPE(name) +#endif +struct scope_tracker +{ + s64 ScopeStart; + + char ScopeName_[SCOPE_NAME_LENGTH]; + string ScopeName; + + debug_services* DebugServices; + + scope_tracker(char* ScopeName, debug_services* DebugServices) + { + this->ScopeName = MakeString(this->ScopeName_, SCOPE_NAME_LENGTH); + CopyCharArrayToString(ScopeName, &this->ScopeName); + this->ScopeStart = DebugServices->GetWallClock(); + this->DebugServices = DebugServices; + } + + ~scope_tracker() + { + s64 ScopeEnd = DebugServices->GetWallClock(); + u32 CyclesElapsed = (u32)(ScopeEnd - this->ScopeStart); +#if 0 + r32 SecondsElapsed = DEBUGGetSecondsElapsed(this->ScopeStart, ScopeEnd, DebugServices->PerformanceCountFrequency); +#endif + LogScopeTime(DebugServices, ScopeName, CyclesElapsed); + } +}; diff --git a/foldhaus_debug_visuals.h b/foldhaus_debug_visuals.h new file mode 100644 index 0000000..7b9e7ed --- /dev/null +++ b/foldhaus_debug_visuals.h @@ -0,0 +1,138 @@ +internal void +DrawDebugInterface (render_command_buffer* RenderBuffer, r32 StartX, interface_config Interface, r32 WindowWidth, r32 WindowHeight, r32 DeltaTime, camera Camera, input Input, memory_arena* Transient) +{ + DEBUG_TRACK_SCOPE(DrawDebugInterface); + + v2 TopOfDebugView = v2{StartX, WindowHeight - (Interface.Font->NewLineYOffset + 5)}; + v2 TopOfScreenLinePos = TopOfDebugView; + + arena_snapshot StartTempMemory = TakeSnapshotOfArena(*Transient); + + string DebugString = InitializeString(PushArray(Transient, char, 256), 256); + + if (GlobalDebugServices->Interface.ShowCameraMouse || GlobalDebugServices->Interface.ShowTrackedScopes) + { + PushRenderQuad2D(RenderBuffer, + v2{TopOfDebugView.x, TopOfDebugView.y - 500}, + v2{TopOfDebugView.x + 700, TopOfDebugView.y}, + v4{0, 0, 0, .8f}); + } + + r32 FramesPerSecond = 1.0f / DeltaTime; + PrintF(&DebugString, "Framerate: %.*f s %d fps", + 5, DeltaTime, + (u32)FramesPerSecond); + DrawString(RenderBuffer, DebugString, Interface.Font, Interface.FontSize, TopOfScreenLinePos, WhiteV4); + + v2 ButtonDim = v2{200, Interface.Font->NewLineYOffset + 10}; + TopOfScreenLinePos.y -= ButtonDim.y + 10; + v2 ButtonPos = TopOfScreenLinePos; + button_result CameraBtn = EvaluateButton(RenderBuffer, ButtonPos, ButtonPos + ButtonDim, + MakeStringLiteral("Camera"), Interface, Input); + + ButtonPos.x += ButtonDim.x + 10; + button_result ScopeTimeBtn = EvaluateButton(RenderBuffer, ButtonPos, ButtonPos + ButtonDim, + MakeStringLiteral("Scope Time"), Interface, Input); + ButtonPos.x += ButtonDim.x + 10; + button_result RenderSculptureBtn = EvaluateButton(RenderBuffer, ButtonPos, ButtonPos + ButtonDim, + MakeStringLiteral("Visualize"), Interface, Input); + + ButtonPos.x += ButtonDim.x + 10; + + string SACNButtonString; + if (GlobalDebugServices->Interface.SendSACNData) + { + SACNButtonString = MakeStringLiteral("Turn SACN Off"); + } + else + { + SACNButtonString = MakeStringLiteral("Turn SACN On"); + } + + button_result SendSACNDataBtn = EvaluateButton(RenderBuffer, ButtonPos, ButtonPos + ButtonDim, + SACNButtonString, Interface, Input); + + TopOfScreenLinePos.y -= Interface.Font->NewLineYOffset + 10; + + if (CameraBtn.Pressed) + { + GlobalDebugServices->Interface.ShowCameraMouse = !GlobalDebugServices->Interface.ShowCameraMouse; + } + + if (ScopeTimeBtn.Pressed) + { + GlobalDebugServices->Interface.ShowTrackedScopes = !GlobalDebugServices->Interface.ShowTrackedScopes; + } + + if (RenderSculptureBtn.Pressed) + { + GlobalDebugServices->Interface.RenderSculpture = + !GlobalDebugServices->Interface.RenderSculpture; + } + + if (SendSACNDataBtn.Pressed) + { + GlobalDebugServices->Interface.SendSACNData = !GlobalDebugServices->Interface.SendSACNData; + } + + if (GlobalDebugServices->Interface.ShowCameraMouse) + { + PrintF(&DebugString, "Camera x=%.*f y=%.*f z=%.*f LookAt x=%.*f y=%.*f z=%.*f", + 3, Camera.Position.x, + 3, Camera.Position.y, + 3, Camera.Position.z, + 3, Camera.LookAt.x, + 3, Camera.LookAt.y, + 3, Camera.LookAt.z); + DrawString(RenderBuffer, DebugString, Interface.Font, Interface.FontSize, TopOfScreenLinePos, v4{1.0f, 1.0f, 1.0f, 1.0f}); + TopOfScreenLinePos.y -= Interface.Font->NewLineYOffset; + } + + if (GlobalDebugServices->Interface.ShowTrackedScopes) + { + r32 ColumnsStartX = TopOfScreenLinePos.x; + + for (s32 i = 0; i < GlobalDebugServices->ScopeHistogramUsed; i++) + { + v2 Register = v2{ColumnsStartX, TopOfScreenLinePos.y}; + + s32 CurrentFrame = GlobalDebugServices->ScopeHistogram[i].CurrentFrame - 1; + if (CurrentFrame < 0) { CurrentFrame = HISTOGRAM_DEPTH - 1; } + + u64 CyclesPerHit = GlobalDebugServices->ScopeHistogram[i].PerFrame_Cycles[CurrentFrame]; + r32 SecondsPerHit = (r32)CyclesPerHit / (r32)GlobalDebugServices->PerformanceCountFrequency; + + // Column 1 + PrintF(&DebugString, "%.*s", + GlobalDebugServices->ScopeHistogram[i].ScopeName.Length, + GlobalDebugServices->ScopeHistogram[i].ScopeName.Memory); + r32 ColumnOneX = DrawString(RenderBuffer, DebugString, Interface.Font, Interface.FontSize, + Register, WhiteV4).x; + Register.x += GSMax(ColumnOneX - Register.x, 250.f); + + // Column 2 + PrintF(&DebugString, "%d hits", GlobalDebugServices->ScopeHistogram[i].PerFrame_CallCount[CurrentFrame]); + r32 ColumnTwoX = DrawString(RenderBuffer, DebugString, Interface.Font, Interface.FontSize, + Register, WhiteV4).x; + Register.x += GSMax(ColumnTwoX - Register.x, 150.f); + + // Column 3 + PrintF(&DebugString, "%lld cycles", CyclesPerHit); + r32 ColumnThreeX = DrawString(RenderBuffer, DebugString, Interface.Font, Interface.FontSize, + Register, WhiteV4).x; + Register.x += GSMax(ColumnThreeX - Register.x, 200.f); + + PrintF(&DebugString, "%f sec", SecondsPerHit); + r32 ColumnFourX = DrawString(RenderBuffer, DebugString, Interface.Font, Interface.FontSize, + Register, WhiteV4).x; + Register.x += GSMax(ColumnFourX - Register.x, 200.f); + + TopOfScreenLinePos.y -= Interface.Font->NewLineYOffset; + + + } + } + + ZeroArenaToSnapshot(Transient, StartTempMemory); + ClearArenaToSnapshot(Transient, StartTempMemory); +} diff --git a/foldhaus_interface.cpp b/foldhaus_interface.cpp new file mode 100644 index 0000000..251241f --- /dev/null +++ b/foldhaus_interface.cpp @@ -0,0 +1,278 @@ + + +// NOTE(Peter): returns the rightmost bound of the panel +internal r32 +DrawLeftHandInterface (app_state* State, input Input, r32 WindowHeight, render_command_buffer* RenderBuffer) +{ + DEBUG_TRACK_FUNCTION; + + s32 StringLength = 128; + + panel_result LeftHandPanel = EvaluatePanel(RenderBuffer, v2{0, 0}, v2{250, WindowHeight}, + MakeStringLiteral("Channel Ops"), 0, State->Interface, Input); + + r32 ListHeight = (LeftHandPanel.ChildMax.y - LeftHandPanel.ChildMin.y) / 2; + panel_result ChannelListPanel = EvaluatePanel(RenderBuffer, &LeftHandPanel, + ListHeight, MakeStringLiteral("Channels"), + State->Interface, Input); + panel_result PatternsListPanel = EvaluatePanel(RenderBuffer, &LeftHandPanel, ListHeight, MakeStringLiteral("Patterns"), + State->Interface, Input); + + // NOTE(Peter): have to do this before we open it otherwise, it'll just close again immediately + // due to the mouse being pressed, outside the box, on the frame it is opened; + if (State->InterfaceState.AddingPattern && + State->InterfaceState.ChannelSelected >= 0) + { + v2 PatternSelectorDim = v2{300, 200}; + v2 PatternSelectorPosition = PatternsListPanel.NextPanelMin; + PatternSelectorPosition.y = PatternsListPanel.ChildMax.y - PatternSelectorDim.y; + + panel_result AddPatternPanel = EvaluatePanel( + RenderBuffer, + PatternSelectorPosition, PatternSelectorPosition + PatternSelectorDim, + MakeStringLiteral("Add Pattern"), 0, State->Interface, Input); + + if (KeyTransitionedDown(Input, KeyCode_MouseLeftButton) && + !PointIsInRange(v2{(r32)Input.New->MouseX, (r32)Input.New->MouseY}, + AddPatternPanel.ChildMin, AddPatternPanel.ChildMax)) + { + State->InterfaceState.AddingPattern = false; + } + + s32 PatternsCount = sizeof(PatternRegistry)/sizeof(PatternRegistry[0]); + string* PatternNames = PushArray(State->Transient, string, PatternsCount); + + for (s32 i = 0; i < PatternsCount; i++) + { + PushString(&PatternNames[i], State->Transient, StringLength); + CopyCharArrayToString(PatternRegistry[i].Name, &PatternNames[i]); + } + + scroll_list_result PatternsResult = DrawOptionsList(RenderBuffer, AddPatternPanel.ChildMin, + AddPatternPanel.ChildMax, + PatternNames, + PatternsCount, + State->InterfaceState.PatternSelectorStart, + State->Interface, + Input); + if (PatternsResult.IndexSelected >= 0) + { + led_channel* ActiveChannel = GetChannelByIndex(State->InterfaceState.ChannelSelected, + State->ChannelSystem); + pattern_index_id_key PatternKey = AddPattern( + &State->PatternSystem, + &PatternRegistry[PatternsResult.IndexSelected]); + AddPatternKeyToChannel(PatternKey, ActiveChannel); + } + + State->InterfaceState.PatternSelectorStart = PatternsResult.StartIndex; + } + + s32 ChannelCount = State->ChannelSystem.ChannelCount; + // NOTE(Peter): adding one to the channel count here so that we can tack on the '+ Add Channel' lable at the end. + // NOTE(Peter): I think I've spelled lable as label all throughout here now... oops. + s32 ChannelLabelsCount = ChannelCount + 1; + string* ChannelTitles = PushArray(State->Transient, string, ChannelLabelsCount); + + led_channel* Channel = State->ChannelSystem.Channels; + for (s32 ChannelIdx = 0; ChannelIdx < ChannelCount; ChannelIdx++) + { + PushString(&ChannelTitles[ChannelIdx], State->Transient, StringLength); + PrintF(&ChannelTitles[ChannelIdx], "Channel %d", Channel->ChannelID); + Channel = Channel->Next; + } + + ChannelTitles[ChannelCount] = MakeStringLiteral("+ Add Channel"); + + scroll_list_result ChannelList = DrawSelectableOptionsList( + RenderBuffer, + ChannelListPanel.ChildMin, + ChannelListPanel.ChildMax, + ChannelTitles, + ChannelLabelsCount, + State->InterfaceState.ChannelSelectorStart, + State->InterfaceState.ChannelSelected, + State->Interface, Input); + + State->InterfaceState.ChannelSelectorStart = ChannelList.StartIndex; + if (ChannelList.Selection == Selection_Selected) + { + if (ChannelList.IndexSelected >= ChannelCount) + { + led_channel* NewChannel = AddLEDChannel(&State->ChannelSystem); + } + else + { + State->InterfaceState.SelectionType = InterfaceSelection_Channel; + State->InterfaceState.ChannelSelected = ChannelList.IndexSelected; + State->InterfaceState.ActiveChannelPatternSelected = -1; + } + } + else if (ChannelList.Selection == Selection_Deselected) + { + State->InterfaceState.ChannelSelected = -1; + State->InterfaceState.ActiveChannelPatternSelected = -1; + } + + s32 ActiveChannelPatternCount = 0; + s32 PatternLabelsCount = 1; + string* PatternTitles = PushArray(State->Transient, string, 1);; + + if (State->InterfaceState.ChannelSelected >= 0) + { + led_channel* ActiveChannel = GetChannelByIndex(State->InterfaceState.ChannelSelected, + State->ChannelSystem); + ActiveChannelPatternCount = ActiveChannel->ActivePatterns; + + // NOTE(Peter): We're just growing the PatternTitles array allocated above. + // IMPORTANT(Peter): make sure no allocations happen between the PushArray to PatternTitles and this one vvv + PushArray(State->Transient, string, ActiveChannelPatternCount); + PatternLabelsCount += ActiveChannelPatternCount; + + for (s32 P = 0; P < ActiveChannelPatternCount; P++) + { + led_pattern* Pattern = FindPatternAndUpdateIDKey(&ActiveChannel->Patterns[P], + &State->PatternSystem); + + PushString(&PatternTitles[P], State->Transient, StringLength); + PrintF(&PatternTitles[P], "%s %d", Pattern->Name, ActiveChannel->Patterns[P].ID); + + Pattern++; + } + } + + PatternTitles[ActiveChannelPatternCount]= MakeStringLiteral("+ Add Pattern"); + + scroll_list_result PatternsList = DrawSelectableOptionsList( + RenderBuffer, + PatternsListPanel.ChildMin, + PatternsListPanel.ChildMax, + PatternTitles, + PatternLabelsCount, + State->InterfaceState.ActiveChannelPatternSelectorStart, + State->InterfaceState.ActiveChannelPatternSelected, + State->Interface, Input + ); + + State->InterfaceState.ActiveChannelPatternSelectorStart = PatternsList.StartIndex; + if (PatternsList.Selection == Selection_Selected) + { + if (PatternsList.IndexSelected >= ActiveChannelPatternCount) + { + State->InterfaceState.AddingPattern = true; + } + else if (PatternsList.IndexSelected >= 0) + { + State->InterfaceState.SelectionType = InterfaceSelection_Pattern; + OutputDebugStringA("Pattern\n"); + State->InterfaceState.ActiveChannelPatternSelected = PatternsList.IndexSelected; + } + } + else if (PatternsList.Selection == Selection_Deselected) + { + State->InterfaceState.SelectionType = InterfaceSelection_None; + OutputDebugStringA("None\n"); + State->InterfaceState.ActiveChannelPatternSelected = -1; + } + + return LeftHandPanel.NextPanelMin.x; +} + +FOLDHAUS_INPUT_COMMAND_PROC(DeleteSelectedChannelOrPattern) +{ + if (State->InterfaceState.ChannelSelected >= 0) + { + switch (State->InterfaceState.SelectionType) + { + case InterfaceSelection_Channel: + { + led_channel* DeleteCandidate = GetChannelByIndex( + State->InterfaceState.ChannelSelected, + State->ChannelSystem); + + for (s32 i = 0; i < DeleteCandidate->ActivePatterns; i++) + { + RemovePattern(DeleteCandidate->Patterns[i], + &State->PatternSystem); + } + + RemoveLEDChannel(State->InterfaceState.ChannelSelected, &State->ChannelSystem); + State->InterfaceState.ChannelSelected--; + + }break; + + case InterfaceSelection_Pattern: + { + if (State->InterfaceState.ActiveChannelPatternSelected >= 0) + { + led_channel* ActiveChannel = GetChannelByIndex(State->InterfaceState.ChannelSelected, + State->ChannelSystem); + s32 KeyIndex = State->InterfaceState.ActiveChannelPatternSelected; + pattern_index_id_key Key = ActiveChannel->Patterns[KeyIndex]; + if (RemovePattern(Key, &State->PatternSystem)) + { + RemovePatternKeyFromChannel(Key, ActiveChannel); + } + } + }break; + } + } +} + +FOLDHAUS_INPUT_COMMAND_PROC(CameraMouseControl) +{ + if (State->NodeInteraction.NodeOffset >= 0) { return; } + + if (KeyTransitionedDown(Input, KeyCode_MouseLeftButton)) + { + State->Camera_StartDragPos = V4(State->Camera.Position, 1); + } + + if (Input.MouseDownY > State->InterfaceYMax) + { + if (!State->DrawUniverseOutputDisplay) + { + v2 DeltaPos = v2{ + (r32)(Input.New->MouseX - Input.MouseDownX), + (r32)(Input.New->MouseY - Input.MouseDownY) + }; + + m44 XRotation = GetXRotation(-DeltaPos.y * State->PixelsToWorldScale); + m44 YRotation = GetYRotation(DeltaPos.x * State->PixelsToWorldScale); + m44 Combined = XRotation * YRotation; + + State->Camera.Position = V3(Combined * State->Camera_StartDragPos); + } + else + { + v2 DeltaPos = v2{ + (r32)(Input.New->MouseX - Input.Old->MouseX), + (r32)(Input.New->MouseY - Input.Old->MouseY) + }; + + State->UniverseOutputDisplayOffset += DeltaPos; + } + } +} + +FOLDHAUS_INPUT_COMMAND_PROC(CameraMouseZoom) +{ + if (State->DrawUniverseOutputDisplay) + { + r32 DeltaZoom = (r32)(Input.New->MouseScroll) / 120; + State->UniverseOutputDisplayZoom = GSClamp(0.1f, State->UniverseOutputDisplayZoom + DeltaZoom, 4.f); + } + +} + +FOLDHAUS_INPUT_COMMAND_PROC(ToggleUniverseDebugView) +{ + State->DrawUniverseOutputDisplay = !State->DrawUniverseOutputDisplay; +} + + +FOLDHAUS_INPUT_COMMAND_PROC(AddNode) +{ + State->InterfaceShowNodeList = true; + State->NodeListMenuPosition = v2{(r32)Input.New->MouseX, (r32)Input.New->MouseY}; +} \ No newline at end of file diff --git a/foldhaus_interface.h b/foldhaus_interface.h new file mode 100644 index 0000000..9775024 --- /dev/null +++ b/foldhaus_interface.h @@ -0,0 +1,23 @@ +enum interface_selection_type +{ + InterfaceSelection_None, + InterfaceSelection_Channel, + InterfaceSelection_Pattern, +}; + +struct interface_state +{ + b32 AddingPattern; + + s32 ChannelSelectorStart; + s32 ChannelSelected; + + s32 ActiveChannelPatternSelectorStart; + s32 ActiveChannelPatternSelected; + + s32 PatternSelectorStart; + + b32 ChooseOperationPanelOpen; + + interface_selection_type SelectionType; +}; diff --git a/foldhaus_memory.h b/foldhaus_memory.h new file mode 100644 index 0000000..9dca997 --- /dev/null +++ b/foldhaus_memory.h @@ -0,0 +1,324 @@ +#ifndef GS_MEMORY_H + +#ifndef GS_LANGUAGE_H + +typedef uint8_t u8; +typedef int8_t s8; +typedef uint32_t u32; +typedef int32_t s32; + +internal void +GSMemSet (u8* Base, s32 Value, s32 Count) +{ + u8* Cursor = Base; + for (s32 i = 0; i < Count; i++) + { + *Cursor++ = Value; + } +} + +internal void +GSMemCopy (u8* Source, u8* Destination, s32 Count) +{ + u8* Src = Source; + u8* Dst = Destination; + + for (s32 i = 0; i < Count; i++) + { + *Dst++ = *Src++; + } +} + +#endif // GS_LANGUAGE_H + +#ifndef GS_PLATFORM_H + +#define PLATFORM_MEMORY_NO_ERROR 0 +#define PLATFORM_MEMORY_ERROR 1 +struct platform_memory_result +{ + s32 Size; + u8* Base; + s32 Error; +}; + +#define PLATFORM_ALLOC(name) platform_memory_result name(s32 Size) +typedef PLATFORM_ALLOC(platform_alloc); + +// Returns 1 if successful, 0 otherwise +#define PLATFORM_FREE(name) b32 name(u8* Memory, s32 Size) +typedef PLATFORM_FREE(platform_free); + +#endif // GS_PLATFORM_H + +#if !defined Assert && defined DEBUG +#define Assert(expression) if(!(expression)){ *((int *)0) = 5; } +#define InvalidCodePath Assert(0) +#endif + +#define MEMORY_REGION_PAGE_SIZE Megabytes(1) +struct memory_region +{ + u8* Base; + u32 Size; + u32 Used; + memory_region* PreviousRegion; +}; + +struct memory_arena +{ + memory_region* CurrentRegion; + platform_alloc* PlatformAlloc; +}; + +internal memory_region* +BootstrapRegionOntoMemory (u8* Memory, s32 Size) +{ + Assert(Size > sizeof(memory_region)); + memory_region* Result = (memory_region*)Memory; + Result->Base = Memory + sizeof(memory_region); + Result->Size = Size - sizeof(memory_region); + Result->Used = 0; + return Result; +} + +#define PushStruct(arena, type) (type*)PushSize_(arena, sizeof(type)) +#define PushArray(arena, type, count) (type*)PushSize_(arena, sizeof(type)*count) +#define PushSize(arena, size) PushSize_(arena, size) +static u8* +PushSize_ (memory_arena* Arena, u32 Size) +{ + memory_region* PushOntoRegion = Arena->CurrentRegion; + + if (!PushOntoRegion || PushOntoRegion->Used + Size > PushOntoRegion->Size) + { + // NOTE(Peter): we only search backwards if the item doesn't already fit in the most recent spot. This way, memory allocated + // one after another is more likely to be contiguous. You can expect that two allocations performed back to back are also next + // to eachother in memory most of the time. + if (PushOntoRegion) + { + // NOTE(Peter): Search backwards through previous regions to see if there is a region allocated that has enough room + // to fit this allocation + memory_region* PreviousRegion = Arena->CurrentRegion->PreviousRegion; + while (PreviousRegion) + { + if (PreviousRegion->Used + Size <= PreviousRegion->Size) + { + PushOntoRegion = PreviousRegion; + break; + } + PreviousRegion = PreviousRegion->PreviousRegion; + } + } + + if (!PushOntoRegion || PushOntoRegion->Used + Size > PushOntoRegion->Size) + { + if (Arena->PlatformAlloc != 0) + { + // NOTE(Peter): Probably want to have this be a multiple of some minimum size so that we aren't constantly + // allocating new pages. + s32 SizeNeeded = Size + sizeof(memory_region); + s32 RegionPagesNeeded = IntegerDivideRoundUp(SizeNeeded, MEMORY_REGION_PAGE_SIZE); + s32 SizeToAllocate = RegionPagesNeeded * MEMORY_REGION_PAGE_SIZE; + + platform_memory_result AllocResult = Arena->PlatformAlloc(SizeToAllocate); + Assert(AllocResult.Error == PLATFORM_MEMORY_NO_ERROR); + Assert(AllocResult.Size >= SizeNeeded); + + memory_region* NewRegion = BootstrapRegionOntoMemory(AllocResult.Base, AllocResult.Size); + NewRegion->PreviousRegion = Arena->CurrentRegion; + Arena->CurrentRegion = NewRegion; + PushOntoRegion = Arena->CurrentRegion; + } + else + { + // NOTE(Peter): We ran out of memory in a memory arena that cannot/should not grow + InvalidCodePath; + } + } + } + + u8* Result = PushOntoRegion->Base + PushOntoRegion->Used; + PushOntoRegion->Used += Size; + + return Result; +} + +static void +InitMemoryArena (memory_arena* Arena, u8* Base, u32 Size, platform_alloc* PlatformAlloc) +{ + if (Base) + { + Arena->CurrentRegion = BootstrapRegionOntoMemory(Base, Size); + } + Arena->PlatformAlloc = PlatformAlloc; +} + +static memory_arena* +BootstrapArenaIntoMemory (u8* Memory, u32 Size) +{ + Assert(Size > sizeof(memory_arena)); + // NOTE(Peter): takes in a block of memory, places a memory arena at the head, and gives + // the arena access to the rest of the block to use. + memory_arena* Result = (memory_arena*)Memory; + InitMemoryArena(Result, Memory + sizeof(memory_arena), Size - sizeof(memory_arena), 0); + return Result; +} + +static memory_arena +AllocateNonGrowableArenaWithSpace(platform_alloc* PlatformAlloc, s32 SizeNeeded) +{ + // TODO(Peter): This causes a leak currently. If you don't free the whole region later, you'll end up with + // the memory_region still being in memory. Should probably just make the first memory region be a member + // variable, not a pointer, in the memory_arena struct. + + memory_arena Result = {}; + + s32 AllocateSize = SizeNeeded + sizeof(memory_region); + platform_memory_result Memory = PlatformAlloc(AllocateSize); + Assert(Memory.Error == PLATFORM_MEMORY_NO_ERROR); + Assert(Memory.Size == AllocateSize); + + InitMemoryArena(&Result, Memory.Base, Memory.Size, 0); + + return Result; +} + +static void +ClearMemoryRegion (memory_region* Region) +{ + Region->Used = 0; +} + +static void +ClearArena (memory_arena* Arena) +{ + memory_region* CurrentRegion = Arena->CurrentRegion; + while (CurrentRegion) + { + ClearMemoryRegion(CurrentRegion); + CurrentRegion = CurrentRegion->PreviousRegion; + } +} + +struct arena_snapshot +{ + memory_region* CurrentRegion; + u32 UsedAtSnapshot; +}; + +static arena_snapshot +TakeSnapshotOfArena (memory_arena Arena) +{ + arena_snapshot Result = {}; + Result.CurrentRegion = Arena.CurrentRegion; + Result.UsedAtSnapshot = Arena.CurrentRegion->Used; + return Result; +}; + +static void +ZeroArenaToSnapshot (memory_arena* Arena, arena_snapshot Snapshot) +{ + memory_region* RegionCursor = Arena->CurrentRegion; + while (RegionCursor && RegionCursor != Snapshot.CurrentRegion) + { + GSZeroMemory(RegionCursor->Base, RegionCursor->Size); + RegionCursor = RegionCursor->PreviousRegion; + } + + Assert(RegionCursor == Snapshot.CurrentRegion); + GSZeroMemory(RegionCursor->Base + Snapshot.UsedAtSnapshot, + RegionCursor->Used - Snapshot.UsedAtSnapshot); +} + +static void +ClearArenaToSnapshot (memory_arena* Arena, arena_snapshot Snapshot) +{ + memory_region* RegionCursor = Arena->CurrentRegion; + while (RegionCursor && RegionCursor != Snapshot.CurrentRegion) + { + RegionCursor->Used = 0; + RegionCursor = RegionCursor->PreviousRegion; + } + + Assert(RegionCursor == Snapshot.CurrentRegion); + RegionCursor->Used = Snapshot.UsedAtSnapshot; +} + +// +// Tracked Array Implementation +// + +#define ARRAY_CHECKSUM 0x51bada7b +struct array_header_ +{ + u32 Size; + s32 ElementMax; + s32 ElementCount; + s32 ElementSize; + u32 Checksum; +}; + +#define gs_PushArray(arena, type, size) (type*)gs_PushArray_(arena, sizeof(type), size) + +static u8* +gs_PushArray_ (memory_arena* Arena, u32 StepSize, u32 Count) +{ + u32 ArrayFootprint = sizeof(array_header_) + (StepSize * Count); + array_header_* Header = (array_header_*)PushSize_(Arena, ArrayFootprint); + + array_header_* Body = Header + 1; + u8* Result = (u8*)(Body); + + Header->Size = Count * StepSize; + Header->ElementMax = Count; + Header->ElementSize = StepSize; + Header->ElementCount = 0; + Header->Checksum = ARRAY_CHECKSUM; + + return Result; +} + +#define gs_ArrayHeader_(array) (((array_header_*)array) - 1) + +#ifdef DEBUG +#define gs_ArrayCheck(array) Assert(!array || gs_ArrayHeader_(array)->Checksum == ARRAY_CHECKSUM) +#else +#define gs_ArrayCheck(array) +#endif + +#define gs_ArrayCount(array) gs_ArrayCount_((u8*)array) +static s32 +gs_ArrayCount_ (u8* Base) +{ + gs_ArrayCheck(Base); + return gs_ArrayHeader_(Base)->ElementCount; +} + +#define gs_ArrayMax(array) gs_ArrayMax_((u8*)array) +static s32 +gs_ArrayMax_ (u8* Base) +{ + gs_ArrayCheck(Base); + return gs_ArrayHeader_(Base)->ElementMax; +} + +#define gs_ArrayAdd(array) ( gs_PushArrayElement_((u8*)array), (array) + (gs_ArrayCount(array) - 1) ) +#define gs_ArrayPush(array, ele) *( gs_ArrayAdd(array) ) = (ele) + +static void* +gs_PushArrayElement_ (u8* Base) +{ + gs_ArrayCheck(Base); + Assert(gs_ArrayHeader_(Base)->ElementCount + 1 <= gs_ArrayHeader_(Base)->ElementMax); + + void* Result = (void*)(Base + (gs_ArrayHeader_(Base)->ElementCount * gs_ArrayHeader_(Base)->ElementSize)); + gs_ArrayHeader_(Base)->ElementCount++; + + return Result; +} + + + +#define GS_MEMORY_H +#endif // GS_MEMORY_H \ No newline at end of file diff --git a/foldhaus_network_ordering.h b/foldhaus_network_ordering.h new file mode 100644 index 0000000..22d0aab --- /dev/null +++ b/foldhaus_network_ordering.h @@ -0,0 +1,117 @@ +//Packs a u8 to a known big endian buffer +inline u8* PackB1(u8* ptr, u8 val) +{ + *ptr = val; + return ptr + sizeof(val); +} + +//Unpacks a u8 from a known big endian buffer +inline u8 UpackB1(const u8* ptr) +{ + return *ptr; +} + +//Packs a u8 to a known little endian buffer +inline u8* PackL1(u8* ptr, u8 val) +{ + *ptr = val; + return ptr + sizeof(val); +} + +//Unpacks a u8 from a known little endian buffer +inline u8 UpackL1(const u8* ptr) +{ + return *ptr; +} + +//Packs a u16 to a known big endian buffer +inline u8* PackB2(u8* ptr, u16 val) +{ + ptr[1] = (u8)(val & 0xff); + ptr[0] = (u8)((val & 0xff00) >> 8); + return ptr + sizeof(val); +} + +//Unpacks a u16 from a known big endian buffer +inline u16 UpackB2(const u8* ptr) +{ + return (u16)(ptr[1] | ptr[0] << 8); +} + +//Packs a u16 to a known little endian buffer +inline u8* PackL2(u8* ptr, u16 val) +{ + *((u16*)ptr) = val; + return ptr + sizeof(val); +} + +//Unpacks a u16 from a known little endian buffer +inline u16 UpackL2(const u8* ptr) +{ + return *((u16*)ptr); +} + +//Packs a u32 to a known big endian buffer +inline u8* PackB4(u8* ptr, u32 val) +{ + ptr[3] = (u8) (val & 0xff); + ptr[2] = (u8)((val & 0xff00) >> 8); + ptr[1] = (u8)((val & 0xff0000) >> 16); + ptr[0] = (u8)((val & 0xff000000) >> 24); + return ptr + sizeof(val); +} + +//Unpacks a u32 from a known big endian buffer +inline u32 UpackB4(const u8* ptr) +{ + return (u32)(ptr[3] | (ptr[2] << 8) | (ptr[1] << 16) | (ptr[0] << 24)); +} + +//Packs a u32 to a known little endian buffer +inline u8* PackL4(u8* ptr, u32 val) +{ + *((u32*)ptr) = val; + return ptr + sizeof(val); +} + +//Unpacks a u32 from a known little endian buffer +inline u32 UpackL4(const u8* ptr) +{ + return *((u32*)ptr); +} + +//Packs a u64 to a known big endian buffer +inline u8* PackB8(u8* ptr, u64 val) +{ + ptr[7] = (u8) (val & 0xff); + ptr[6] = (u8)((val & 0xff00) >> 8); + ptr[5] = (u8)((val & 0xff0000) >> 16); + ptr[4] = (u8)((val & 0xff000000) >> 24); + ptr[3] = (u8)((val & 0xff00000000) >> 32); + ptr[2] = (u8)((val & 0xff0000000000) >> 40); + ptr[1] = (u8)((val & 0xff000000000000) >> 48); + ptr[0] = (u8)((val & 0xff00000000000000) >> 56); + return ptr + sizeof(val); +} + +//Unpacks a uint64 from a known big endian buffer +inline u64 UpackB8(const u8* ptr) +{ + return ((u64)ptr[7]) | (((u64)ptr[6]) << 8) | (((u64)ptr[5]) << 16) | + (((u64)ptr[4]) << 24) | (((u64)ptr[3]) << 32) | + (((u64)ptr[2]) << 40) | (((u64)ptr[1]) << 48) | + (((u64)ptr[0]) << 56); +} + +//Packs a u64 to a known little endian buffer +inline u8* PackL8(u8* ptr, u64 val) +{ + *((u64*)ptr) = val; + return ptr + sizeof(val); +} + +//Unpacks a u64 from a known little endian buffer +inline u64 UpackL8(const u8* ptr) +{ + return *((u64*)ptr); +} diff --git a/foldhaus_node.cpp b/foldhaus_node.cpp new file mode 100644 index 0000000..fcf19a6 --- /dev/null +++ b/foldhaus_node.cpp @@ -0,0 +1,1079 @@ +inline s32 +GetNodeMemorySize (interface_node Node) +{ + s32 Result = sizeof(interface_node) + (sizeof(node_connection) * Node.ConnectionsCount) + sizeof(Node.Name); + return Result; +} + +internal node_list_iterator +GetNodeListIterator(node_list List) +{ + node_list_iterator Result = {}; + Result.List = List; + Result.At = (interface_node*)List.Memory; + return Result; +} + +internal b32 +NodeIteratorIsValid(node_list_iterator Iter) +{ + b32 Result = (Iter.At != 0); + Result &= (((u8*)Iter.At - Iter.List.Memory) < Iter.List.Used); + return Result; +} + +internal s32 +GetCurrentOffset (node_list_iterator Iter) +{ + s32 Result = (u8*)Iter.At - Iter.List.Memory; + return Result; +} + +internal void +Next (node_list_iterator* Iter) +{ + s32 SkipAmount = GetNodeMemorySize(*Iter->At); + if (((u8*)Iter->At - Iter->List.Memory) + SkipAmount < Iter->List.Used) + { + Iter->At = (interface_node*)((u8*)Iter->At + SkipAmount); + } + else if (Iter->List.Next) + { + Iter->List = *Iter->List.Next; + Iter->At = (interface_node*)Iter->List.Memory; + } + else + { + Iter->At = 0; + } +} + +internal node_list* +AllocateNodeList (memory_arena* Storage, s32 Size) +{ + node_list* Result = PushStruct(Storage, node_list); + Result->Memory = PushSize(Storage, Size); + Result->Max = Size; + Result->Used = 0; + Result->Next = 0; + return Result; +} + +internal interface_node* +PushNodeOnList (node_list* List, s32 NameLength, s32 ConnectionsCount, v2 Min, v2 Dim, memory_arena* Storage) +{ + interface_node* Result = 0; + + if (List->Used >= List->Max) + { + if (!List->Next) + { + List->Next = AllocateNodeList(Storage, List->Max); + } + Result = PushNodeOnList(List->Next, NameLength, ConnectionsCount, Min, Dim, Storage); + } + else + { + Result = (interface_node*)(List->Memory + List->Used); + Result->Name = MakeString((char*)(Result + 1), NameLength); + + Result->ConnectionsCount = ConnectionsCount; + Result->Connections = (node_connection*)(Result->Name.Memory + NameLength); + + Result->Min = Min; + Result->MinAfterUpdate = Min; + Result->Dim = Dim; + + List->Used += GetNodeMemorySize(*Result); + } + + return Result; +} + +internal void +InitializeNodeConnection (node_connection* Connection, struct_member_type Type, b32 DirectionMask) +{ + Connection->Type = Type; + Connection->UpstreamNodeOffset = -1; + Connection->UpstreamNodePortIndex = -1; + Connection->DownstreamNodeOffset = -1; + Connection->DownstreamNodePortIndex = -1; + Connection->DirectionMask = DirectionMask; + switch (Type) + { + case MemberType_s32: + { + Connection->S32Value = 0; + }break; + + case MemberType_r32: + { + Connection->R32Value = 0; + }break; + + case MemberType_v4: + { + Connection->V4Value = v4{0, 0, 0, 1}; + }break; + + case MemberType_NODE_COLOR_BUFFER: + { + Connection->LEDsValue = {}; + }break; + + InvalidDefaultCase; + } +} + +internal void +PushNodeOnListFromSpecification (node_list* List, node_specification Spec, v2 Min, memory_arena* Storage) +{ + // TODO(Peter): Calculate the size of a node; + interface_node* Node = PushNodeOnList(List, + Spec.NameLength, + Spec.MemberListLength, + Min, + v2{150, 150}, + Storage); + Node->Type = Spec.Type; + + CopyCharArrayToString(Spec.Name, Spec.NameLength, &Node->Name); + + node_struct_member* MemberList = Spec.MemberList; + for (s32 MemberIdx = 0; MemberIdx < Spec.MemberListLength; MemberIdx++) + { + node_struct_member Member = MemberList[MemberIdx]; + InitializeNodeConnection(Node->Connections + MemberIdx, Member.Type, Member.IsInput); + } + +} + +internal interface_node* +PushOutputNodeOnList (node_list* List, v2 Min, memory_arena* Storage) +{ + string OutputNodeName = MakeStringLiteral("Output"); + interface_node* Node = PushNodeOnList(List, + OutputNodeName.Length, + 1, + Min, + v2{125, 150}, + Storage); + Node->Type = NodeType_OutputNode; + CopyStringTo(OutputNodeName, &Node->Name); + InitializeNodeConnection(Node->Connections, MemberType_NODE_COLOR_BUFFER, IsInputMember); + + return Node; +} + +internal interface_node* +GetNodeAtOffset (node_list* List, s32 Offset) +{ + DEBUG_TRACK_FUNCTION; + + interface_node* Node = 0; + if (Offset <= List->Used) + { + Node = (interface_node*)(List->Memory + Offset); + } + else if (List->Next) + { + Node = GetNodeAtOffset(List->Next, Offset - List->Max); + } + return Node; +} + +internal node_interaction +NewNodeInteraction () +{ + node_interaction Result = {}; + Result.NodeOffset = -1; + Result.InputPort = -1; + Result.InputValue = -1; + Result.OutputPort = -1; + Result.OutputValue = -1; + return Result; +} + +internal rect +CalculateNodeBounds (interface_node* Node, node_render_settings Settings) +{ + rect Result = {}; + Result.Min = Node->Min; + Result.Max = Node->Min + Node->Dim + v2{0, NODE_HEADER_HEIGHT}; + return Result; +} + +internal rect +CalculateNodeInputPortBounds (interface_node* Node, s32 Index, node_render_settings RenderSettings) +{ + rect Result = {}; + + Result.Min = v2{ + Node->Min.x, + Node->Min.y + Node->Dim.y - ((RenderSettings.PortStep * (Index + 1)) + NODE_HEADER_HEIGHT)}; + Result.Max = Result.Min + RenderSettings.PortDim; + + return Result; +} + +internal rect +CalculateNodeInputValueBounds (interface_node* Node, s32 Index, node_render_settings RenderSettings) +{ + rect Result = {}; + rect Port = CalculateNodeInputPortBounds(Node, Index, RenderSettings); + Result.Min = v2{Port.Max.x, Port.Min.y}; + Result.Max = Result.Min + v2{RenderSettings.PortDim.x * 2, RenderSettings.PortDim.y}; + return Result; +} + +internal rect +CalculateNodeOutputPortBounds (interface_node* Node, s32 Index, node_render_settings RenderSettings) +{ + rect Result = {}; + Result.Min = v2{ + Node->Min.x + Node->Dim.x - RenderSettings.PortDim.x, + Node->Min.y + Node->Dim.y - ((RenderSettings.PortStep * (Index + 1)) + NODE_HEADER_HEIGHT)}; + Result.Max = Result.Min + RenderSettings.PortDim; + return Result; +} + +internal rect +CalculateNodeOutputValueBounds (interface_node* Node, s32 Index, node_render_settings RenderSettings) +{ + rect Result = {}; + rect Port = CalculateNodeOutputPortBounds(Node, Index, RenderSettings); + Result.Min = v2{Port.Min.x - (RenderSettings.PortDim.x * 2), Port.Min.y}; + Result.Max = v2{Port.Min.x, Port.Max.y}; + return Result; +} + +internal rect +GetBoundsOfPortConnectedToInput (interface_node* Node, s32 PortIndex, node_list* NodeList, node_render_settings RenderSettings) +{ + interface_node* ConnectedNode = GetNodeAtOffset(NodeList, Node->Connections[PortIndex].UpstreamNodeOffset); + rect Result = CalculateNodeOutputPortBounds(ConnectedNode, Node->Connections[PortIndex].UpstreamNodePortIndex, RenderSettings); + return Result; +} + +internal rect +GetBoundsOfPortConnectedToOutput (interface_node* Node, s32 PortIndex, node_list* NodeList, node_render_settings RenderSettings) +{ + interface_node* ConnectedNode = GetNodeAtOffset(NodeList, Node->Connections[PortIndex].DownstreamNodeOffset); + rect Result = CalculateNodeInputPortBounds(ConnectedNode, Node->Connections[PortIndex].DownstreamNodePortIndex, RenderSettings); + return Result; +} + +internal rect +CalculateNodeDragHandleBounds (rect NodeBounds, s32 Index, node_render_settings RenderSettings) +{ + rect Result {}; + v2 HorizontalOffset = v2{Width(NodeBounds) / 3, 0}; + Result.Min = v2{NodeBounds.Min.x, NodeBounds.Max.y - NODE_HEADER_HEIGHT} + (HorizontalOffset * Index); + Result.Max = Result.Min + v2{HorizontalOffset.x, NODE_HEADER_HEIGHT}; + return Result; +} + +internal b32 +IsDraggingNode (node_interaction Interaction) +{ + b32 Result = ((Interaction.NodeOffset >= 0) && + (Interaction.InputPort < 0 && Interaction.InputValue < 0) && + (Interaction.OutputPort < 0 && Interaction.InputValue < 0)); + return Result; +} + +internal b32 +IsDraggingNodePort (node_interaction Interaction) +{ + b32 Result = ((Interaction.NodeOffset >= 0) && + (Interaction.InputPort >= 0 || Interaction.OutputPort >= 0) && + (Interaction.InputValue < 0 && Interaction.OutputValue < 0)); + return Result; +} + +internal b32 +IsDraggingNodeValue (node_interaction Interaction) +{ + b32 Result = ((Interaction.NodeOffset >= 0) && + (Interaction.InputPort < 0 && Interaction.OutputPort < 0) && + (Interaction.InputValue >= 0 || Interaction.OutputValue >= 0)); + return Result; +} + +internal b32 +IsDraggingNodeInput (node_interaction Interaction) +{ + b32 Result = ((Interaction.NodeOffset >= 0) && + (Interaction.InputPort >= 0 || Interaction.InputValue >= 0) && + (Interaction.OutputPort < 0 && Interaction.OutputValue < 0)); + return Result; +} + +internal b32 +IsDraggingNodeInputPort (node_interaction Interaction) +{ + b32 Result = ((Interaction.NodeOffset >= 0) && + (Interaction.InputPort >= 0) && (Interaction.InputValue < 0) && + (Interaction.OutputPort < 0 && Interaction.OutputValue < 0)); + return Result; +} + +internal b32 +IsDraggingNodeInputValue (node_interaction Interaction) +{ + b32 Result = ((Interaction.NodeOffset >= 0) && + (Interaction.InputPort < 0) && (Interaction.InputValue >= 0) && + (Interaction.OutputPort < 0 && Interaction.OutputValue < 0)); + return Result; +} + +internal b32 +IsDraggingNodeOutput (node_interaction Interaction) +{ + b32 Result = ((Interaction.NodeOffset >= 0) && + (Interaction.OutputPort >= 0 || Interaction.OutputValue >= 0) && + (Interaction.InputPort < 0 && Interaction.InputValue < 0)); + return Result; +} + +internal b32 +IsDraggingNodeOutputPort (node_interaction Interaction) +{ + b32 Result = ((Interaction.NodeOffset >= 0) && + (Interaction.OutputPort >= 0) && (Interaction.OutputValue < 0) && + (Interaction.InputPort < 0 && Interaction.InputValue < 0)); + return Result; +} + +internal b32 +IsDraggingNodeOutputValue (node_interaction Interaction) +{ + b32 Result = ((Interaction.NodeOffset >= 0) && + (Interaction.OutputPort < 0) && (Interaction.OutputValue >= 0) && + (Interaction.InputPort < 0 && Interaction.InputValue < 0)); + return Result; +} + +internal b32 +ConnectionIsConnected (node_connection Connection) +{ + b32 Result = (Connection.UpstreamNodeOffset >= 0) || (Connection.DownstreamNodeOffset >= 0); + return Result; +} + +internal b32 +ConnectionIsConnected (interface_node* Node, s32 Index) +{ + b32 Result = ConnectionIsConnected(Node->Connections[Index]); + return Result; +} + +internal b32 +ConnectionHasUpstreamConnection (node_connection Connection) +{ + b32 Result = (Connection.UpstreamNodeOffset >= 0); + return Result; +} + +internal b32 +ConnectionHasUpstreamConnection (interface_node* Node, s32 Index) +{ + b32 Result = ConnectionHasUpstreamConnection(Node->Connections[Index]); + return Result; +} + +internal b32 +ConnectionHasDownstreamConnection (node_connection Connection) +{ + b32 Result = (Connection.DownstreamNodeOffset >= 0); + return Result; +} + +internal b32 +ConnectionHasDownstreamConnection (interface_node* Node, s32 Index) +{ + b32 Result = ConnectionHasDownstreamConnection(Node->Connections[Index]); + return Result; +} + +internal b32 +ConnectionIsInput (node_connection Connection) +{ + b32 Result = (Connection.DirectionMask & IsInputMember) > 0; + return Result; +} + +internal b32 +ConnectionIsInput (interface_node* Node, s32 ConnectionIdx) +{ + return ConnectionIsInput(Node->Connections[ConnectionIdx]); +} + +internal b32 +ConnectionIsOutput (node_connection Connection) +{ + b32 Result = (Connection.DirectionMask & IsOutputMember) > 0; + return Result; +} + +internal b32 +ConnectionIsOutput (interface_node* Node, s32 ConnectionIdx) +{ + return ConnectionIsOutput(Node->Connections[ConnectionIdx]); +} + + +internal b32 +CheckForRecursion (node_list* NodeList, s32 LookForNode, interface_node* StartNode) +{ + DEBUG_TRACK_FUNCTION; + b32 Result = false; + + for (s32 Connection = 0; Connection < StartNode->ConnectionsCount; Connection++) + { + if (!ConnectionIsOutput(StartNode->Connections[Connection])) { continue; } + + if (StartNode->Connections[Connection].DownstreamNodeOffset == LookForNode) + { + Result = true; + break; + } + + if (StartNode->Connections[Connection].DownstreamNodeOffset >= 0) + { + interface_node* NextNode = GetNodeAtOffset(NodeList, StartNode->Connections[Connection].DownstreamNodeOffset); + Result = CheckForRecursion(NodeList, LookForNode, NextNode); + if (Result) { break; } + } + } + + return Result; +} + +internal b32 +PortTypesMatch (interface_node* UpstreamNode, s32 UpstreamNode_OutputPort, interface_node* DownstreamNode, s32 DownstreamNode_InputPort) +{ + Assert(ConnectionIsOutput(UpstreamNode, UpstreamNode_OutputPort)); + Assert(ConnectionIsInput(DownstreamNode, DownstreamNode_InputPort)); + b32 Result = UpstreamNode->Connections[UpstreamNode_OutputPort].Type == DownstreamNode->Connections[DownstreamNode_InputPort].Type; + return Result; +} + +internal void +ConnectNodes(node_list* NodeList, + s32 UpstreamNodeOffset, s32 UpstreamNodePort, + s32 DownstreamNodeOffset, s32 DownstreamNodePort) +{ + interface_node* DownstreamNode = GetNodeAtOffset(NodeList, DownstreamNodeOffset); + if (!CheckForRecursion(NodeList, UpstreamNodeOffset, DownstreamNode)) + { + interface_node* UpstreamNode = GetNodeAtOffset(NodeList, UpstreamNodeOffset); + if (PortTypesMatch(UpstreamNode, UpstreamNodePort, + DownstreamNode, DownstreamNodePort)) + { + Assert(ConnectionIsOutput(UpstreamNode, UpstreamNodePort)); + Assert(ConnectionIsInput(DownstreamNode, DownstreamNodePort)); + + DownstreamNode->Connections[DownstreamNodePort].UpstreamNodeOffset = UpstreamNodeOffset; + DownstreamNode->Connections[DownstreamNodePort].UpstreamNodePortIndex = UpstreamNodePort; + UpstreamNode->Connections[UpstreamNodePort].DownstreamNodeOffset = DownstreamNodeOffset; + UpstreamNode->Connections[UpstreamNodePort].DownstreamNodePortIndex = DownstreamNodePort; + } + } +} + +internal void +UnconnectNodes (node_list* NodeList, s32 DownstreamNodeOffset, s32 DownstreamNode_OutputPort, s32 UpstreamNodeOffset, s32 UpstreamNode_InputPort) +{ + interface_node* DownstreamNode = GetNodeAtOffset(NodeList, DownstreamNodeOffset); + interface_node* UpstreamNode = GetNodeAtOffset(NodeList, UpstreamNodeOffset); + + Assert(ConnectionIsOutput(DownstreamNode, DownstreamNode_OutputPort)); + Assert(ConnectionIsInput(UpstreamNode, UpstreamNode_InputPort)); + + DownstreamNode->Connections[DownstreamNode_OutputPort].DownstreamNodeOffset = -1; + DownstreamNode->Connections[DownstreamNode_OutputPort].DownstreamNodePortIndex = -1; + UpstreamNode->Connections[UpstreamNode_InputPort].UpstreamNodeOffset = -1; + UpstreamNode->Connections[UpstreamNode_InputPort].UpstreamNodePortIndex = -1; +} + +internal node_offset +GetNodeUnderPoint (node_list* NodeList, v2 Point, node_render_settings RenderSettings) +{ + DEBUG_TRACK_FUNCTION; + + node_offset Result = {0, -1}; + + node_list_iterator NodeIter = GetNodeListIterator(*NodeList); + while (NodeIteratorIsValid(NodeIter)) + { + interface_node* Node = NodeIter.At; + rect NodeBounds = CalculateNodeBounds(Node, RenderSettings); + if (PointIsInRect(Point, NodeBounds)) + { + Result.Node = Node; + Result.Offset = (s32)((u8*)NodeIter.At - NodeList->Memory); + break; + } + Next(&NodeIter); + } + + return Result; +} + +internal node_interaction +GetNodeInteractionType (interface_node* ActiveNode, s32 NodeOffset, v2 MousePos, node_render_settings RenderSettings) +{ + DEBUG_TRACK_FUNCTION; + + node_interaction Interaction = NewNodeInteraction(); + + Interaction.NodeOffset = NodeOffset; + Interaction.MouseOffset = ActiveNode->Min - MousePos; + + rect NodeBounds = CalculateNodeBounds(ActiveNode, RenderSettings); + + + for (s32 Connection = 0; Connection < ActiveNode->ConnectionsCount; Connection++) + { + // Inputs + if (ConnectionIsInput(ActiveNode, Connection)) + { + rect InputBounds = CalculateNodeInputPortBounds(ActiveNode, Connection, RenderSettings); + rect ValueBounds = CalculateNodeInputValueBounds(ActiveNode, Connection, RenderSettings); + if (PointIsInRect(MousePos, InputBounds)) + { + Interaction.InputPort = Connection; + Interaction.MouseOffset = MousePos - InputBounds.Min; + } + else if(PointIsInRect(MousePos, ValueBounds)) + { + Interaction.InputValue = Connection; + Interaction.MouseOffset = MousePos - ValueBounds.Min; + } + } + + // Outputs + if (ConnectionIsOutput(ActiveNode, Connection)) + { + rect OutputBounds = CalculateNodeOutputPortBounds(ActiveNode, Connection, RenderSettings); + rect ValueBounds = CalculateNodeOutputValueBounds(ActiveNode, Connection, RenderSettings); + if (PointIsInRect(MousePos, OutputBounds)) + { + Interaction.OutputPort = Connection; + Interaction.MouseOffset = MousePos - OutputBounds.Min; + } + else if(PointIsInRect(MousePos, ValueBounds)) + { + Interaction.OutputValue = Connection; + Interaction.MouseOffset = MousePos - ValueBounds.Min; + } + } + } + + // Drag Handles + rect DragUpstreamHandleBounds = CalculateNodeDragHandleBounds(NodeBounds, 0, RenderSettings); + rect DragAllHandleBounds = CalculateNodeDragHandleBounds(NodeBounds, 1, RenderSettings); + rect DragDownstreamHandleBounds = CalculateNodeDragHandleBounds(NodeBounds, 2, RenderSettings); + if (PointIsInRect(MousePos, DragUpstreamHandleBounds)) + { + Interaction.Flags = NodeInteraction_AllUpstream; + } + else if (PointIsInRect(MousePos, DragAllHandleBounds)) + { + Interaction.Flags = NodeInteraction_AllUpstream | NodeInteraction_AllDownstream; + } + else if (PointIsInRect(MousePos, DragDownstreamHandleBounds)) + { + Interaction.Flags = NodeInteraction_AllDownstream; + } + + return Interaction; +} + +internal void +TryConnectNodes (node_interaction Interaction, v2 Point, node_list* NodeList, node_render_settings RenderSettings) +{ + DEBUG_TRACK_FUNCTION; + + if (IsDraggingNodeOutput(Interaction)) + { + node_offset UpstreamNodeOffset = GetNodeUnderPoint(NodeList, Point, RenderSettings); + if (UpstreamNodeOffset.Node) + { + for (s32 Connection = 0; Connection < UpstreamNodeOffset.Node->ConnectionsCount; Connection++) + { + if (ConnectionIsOutput(UpstreamNodeOffset.Node, Connection)) { continue; } + + rect InputBounds = CalculateNodeInputPortBounds(UpstreamNodeOffset.Node, Connection, RenderSettings); + if (PointIsInRect(Point, InputBounds)) + { + ConnectNodes(NodeList, Interaction.NodeOffset, Interaction.OutputPort, + UpstreamNodeOffset.Offset, Connection); + break; + } + } + } + } + else if (IsDraggingNodeInput(Interaction)) + { + node_offset DownstreamNodeOffset = GetNodeUnderPoint(NodeList, Point, RenderSettings); + if (DownstreamNodeOffset.Node) + { + for (s32 Connection = 0; Connection < DownstreamNodeOffset.Node->ConnectionsCount; Connection++) + { + if (ConnectionIsInput(DownstreamNodeOffset.Node, Connection)) { continue; } + + rect OutputBounds = CalculateNodeOutputPortBounds(DownstreamNodeOffset.Node, Connection, RenderSettings); + if (PointIsInRect(Point, OutputBounds)) + { + ConnectNodes(NodeList, + DownstreamNodeOffset.Offset, Connection, + Interaction.NodeOffset, Interaction.InputPort); + break; + } + } + } + } +} + +internal void +PlaceNode (node_list* NodeList, interface_node* Node, v2 Position, b32 Flags) +{ + DEBUG_TRACK_FUNCTION; + + v2 Offset = Position - Node->Min; + + if (Flags & NodeInteraction_AllDownstream) + { + for (s32 Connection = 0; Connection < Node->ConnectionsCount; Connection++) + { + if (!ConnectionIsOutput(Node, Connection)) { continue; } + + s32 ConnectionOffset = Node->Connections[Connection].DownstreamNodeOffset; + if (ConnectionOffset >= 0) + { + interface_node* ConnectedNode = GetNodeAtOffset(NodeList, ConnectionOffset); + v2 CurrPos = ConnectedNode->Min; + v2 NewPos = CurrPos + Offset; + // NOTE(Peter): Have to negate the all downstream component so it doesn't turn around and try + // to move this node again. + PlaceNode(NodeList, ConnectedNode, NewPos, Flags & ~NodeInteraction_AllDownstream); + } + } + } + + if (Flags & NodeInteraction_AllUpstream) + { + for (s32 Connection = 0; Connection < Node->ConnectionsCount; Connection++) + { + if (!ConnectionIsInput(Node, Connection)) { continue; } + + s32 ConnectionOffset = Node->Connections[Connection].UpstreamNodeOffset; + if (ConnectionOffset >= 0) + { + interface_node* ConnectedNode = GetNodeAtOffset(NodeList, ConnectionOffset); + v2 CurrPos = ConnectedNode->Min; + v2 NewPos = CurrPos + Offset; + // NOTE(Peter): Have to negate the all upstream component so it doesn't turn around and try + // to move this node again. + PlaceNode(NodeList, ConnectedNode, NewPos, Flags & ~NodeInteraction_AllUpstream); + } + } + } + + Node->MinAfterUpdate = Position; +} + +internal void +UpdateDraggingNode (v2 MousePos, node_interaction Interaction, node_list* NodeList, node_render_settings RenderSettings) +{ + DEBUG_TRACK_FUNCTION; + + if (IsDraggingNode(Interaction)) + { + interface_node* ActiveNode = GetNodeAtOffset(NodeList, Interaction.NodeOffset); + PlaceNode(NodeList, ActiveNode, MousePos + Interaction.MouseOffset, Interaction.Flags); + } +} + +internal void +UpdateDraggingNodePort (v2 MousePos, node_interaction Interaction, node_list* NodeList, node_render_settings RenderSettings, render_command_buffer* RenderBuffer) +{ + DEBUG_TRACK_FUNCTION; + + if (IsDraggingNodePort(Interaction)) + { + interface_node* ActiveNode = GetNodeAtOffset(NodeList, Interaction.NodeOffset); + rect PortBounds = {}; + if (IsDraggingNodeInput(Interaction)) + { + PortBounds = CalculateNodeInputPortBounds(ActiveNode, Interaction.InputPort, RenderSettings); + } + else if (IsDraggingNodeOutput(Interaction)) + { + PortBounds = CalculateNodeOutputPortBounds(ActiveNode, Interaction.OutputPort, RenderSettings); + } + + v2 PortCenter = CalculateRectCenter(PortBounds); + PushRenderLine2D(RenderBuffer, PortCenter, MousePos, 1, WhiteV4); + } +} + +internal void +UpdateDraggingNodeValue (v2 MousePos, v2 LastFrameMousePos, node_interaction Interaction, node_list* NodeList, node_render_settings RenderSettings, app_state* State) +{ + DEBUG_TRACK_FUNCTION; + + if(IsDraggingNodeValue(Interaction)) + { + v2 MouseDelta = MousePos - LastFrameMousePos; + interface_node* Node = GetNodeAtOffset(NodeList, Interaction.NodeOffset); + + node_connection* Connection = 0; + if (IsDraggingNodeInputValue(Interaction)) + { + Connection = Node->Connections + Interaction.InputValue; + Assert(ConnectionIsInput(*Connection)); + } + else if (IsDraggingNodeOutputValue(Interaction)) + { + Connection = Node->Connections + Interaction.OutputValue; + Assert(ConnectionIsOutput(*Connection)); + } + Assert(Connection); + + switch (Connection->Type) + { + case MemberType_s32: + { + Connection->S32Value += (s32)(MouseDelta.y * .05f); + }break; + + case MemberType_r32: + { + Connection->R32Value += (MouseDelta.y * .05f); + }break; + + case MemberType_v4: + { + State->ColorPickerEditValue = &Connection->V4Value; + }break; + + case MemberType_NODE_COLOR_BUFFER: {} break; // NOTE(Peter): Unused for now + InvalidDefaultCase; + } + } +} + + +internal void UpdateNodeCalculation (interface_node* Node, node_list* NodeList, memory_arena* Transient, + led* LEDs, sacn_pixel* ColorsInit, s32 LEDCount); + +internal void +UpdateNodesConnectedUpstream (interface_node* Node, node_list* NodeList, memory_arena* Transient, + led* LEDs, sacn_pixel* ColorsInit, s32 LEDCount) +{ + for (s32 ConnectionIdx = 0; ConnectionIdx < Node->ConnectionsCount; ConnectionIdx++) + { + node_connection* Connection = 0; + if (ConnectionIsInput(Node->Connections[ConnectionIdx])) + { + Connection = Node->Connections + ConnectionIdx; + + if (ConnectionHasUpstreamConnection(*Connection)) + { + interface_node* UpstreamNode = GetNodeAtOffset(NodeList, Connection->UpstreamNodeOffset); + if (!UpstreamNode->UpdatedThisFrame) + { + UpdateNodeCalculation(UpstreamNode, NodeList, Transient, LEDs, ColorsInit, LEDCount); + } + switch (Connection->Type) + { + case MemberType_s32: + { + Connection->S32Value = UpstreamNode->Connections[Connection->UpstreamNodePortIndex].S32Value; + }break; + + case MemberType_r32: + { + Connection->R32Value = UpstreamNode->Connections[Connection->UpstreamNodePortIndex].R32Value; + }break; + + case MemberType_v4: + { + Connection->V4Value = UpstreamNode->Connections[Connection->UpstreamNodePortIndex].V4Value; + }break; + + case MemberType_NODE_COLOR_BUFFER: + { + Connection->LEDsValue = UpstreamNode->Connections[Connection->UpstreamNodePortIndex].LEDsValue; + }break; + + InvalidDefaultCase; + } + } + } + } +} + +internal void +UpdateNodeCalculation (interface_node* Node, node_list* NodeList, memory_arena* Transient, + led* LEDs, sacn_pixel* ColorsInit, s32 LEDCount) +{ + DEBUG_TRACK_FUNCTION; + + // NOTE(Peter): Have to subtract one here so that we account for the + // NodeType_OutputNode entry in the enum + node_specification Spec = NodeSpecifications[Node->Type - 1]; + node_struct_member* MemberList = Spec.MemberList; + + // NOTE(Peter): We do this at the beginning in case there is a node connected to this one + // which has a connection that is both an Input and Output. In that case, if UpdatedThisFrame + // were not set before hand, the two nodes would pingpong back and forth trying to get + // eachother to update. + Node->UpdatedThisFrame = true; + + sacn_pixel* Colors = ColorsInit; + u8* NodeData = PushArray(Transient, u8, Spec.DataStructSize); + + UpdateNodesConnectedUpstream(Node, NodeList, Transient, LEDs, Colors, LEDCount); + + for (s32 ConnectionIdx = 0; ConnectionIdx < Node->ConnectionsCount; ConnectionIdx++) + { + node_connection* Connection = 0; + + // TODO(Peter): We're currently passing in a pointer to the leds array for every single + // NODE_COLOR_BUFFER. We shouldn't do that, and just require each data structure that + // needs the leds to request that as its own member/parameter. + if (ConnectionIsInput(Node->Connections[ConnectionIdx]) || + Connection->Type == MemberType_NODE_COLOR_BUFFER) + { + Connection = Node->Connections + ConnectionIdx; + switch (Connection->Type) + { + case MemberType_s32: + { + GSMemCopy(&Connection->S32Value, (NodeData + MemberList[ConnectionIdx].Offset), sizeof(s32)); + }break; + + case MemberType_r32: + { + GSMemCopy(&Connection->R32Value, (NodeData + MemberList[ConnectionIdx].Offset), sizeof(r32)); + }break; + + case MemberType_v4: + { + GSMemCopy(&Connection->V4Value, (NodeData + MemberList[ConnectionIdx].Offset), sizeof(v4)); + }break; + + case MemberType_NODE_COLOR_BUFFER: + { + Connection->LEDsValue.LEDs = LEDs; + Connection->LEDsValue.LEDCount = LEDCount; + + if (Connection->LEDsValue.Colors == 0) + { + sacn_pixel* ColorsCopy = PushArray(Transient, sacn_pixel, LEDCount); + GSMemCopy(ColorsInit, ColorsCopy, sizeof(sacn_pixel) * LEDCount); + Connection->LEDsValue.Colors = ColorsCopy; + } + + GSMemCopy(&Connection->LEDsValue, (NodeData + MemberList[ConnectionIdx].Offset), sizeof(node_led_color_connection)); + }break; + + InvalidDefaultCase; + } + } + } + + CallNodeProc(Node, NodeData, LEDs, Colors, LEDCount); + + for (s32 ConnectionIdx = 0; ConnectionIdx < Node->ConnectionsCount; ConnectionIdx++) + { + node_connection* Connection = 0; + if (ConnectionIsOutput(Node, ConnectionIdx)) + { + Connection = Node->Connections + ConnectionIdx; + } + else + { + continue; + } + + switch (Connection->Type) + { + case MemberType_s32: + { + GSMemCopy((NodeData + MemberList[ConnectionIdx].Offset), &Connection->S32Value, sizeof(s32)); + }break; + + case MemberType_r32: + { + GSMemCopy((NodeData + MemberList[ConnectionIdx].Offset), &Connection->R32Value, sizeof(r32)); + }break; + + case MemberType_v4: + { + GSMemCopy((NodeData + MemberList[ConnectionIdx].Offset), &Connection->V4Value, sizeof(v4)); + }break; + + case MemberType_NODE_COLOR_BUFFER: + { + node_led_color_connection* Value = (node_led_color_connection*)(NodeData + MemberList[ConnectionIdx].Offset); + }break; + + InvalidDefaultCase; + } + } +} + +internal void +UpdateOutputNodeCalculations (interface_node* OutputNode, node_list* NodeList, memory_arena* Transient, led* LEDs, sacn_pixel* Colors, s32 LEDCount) +{ + Assert(OutputNode->Type == NodeType_OutputNode); + UpdateNodesConnectedUpstream(OutputNode, NodeList, Transient, LEDs, Colors, LEDCount); + + node_connection ColorsConnection = OutputNode->Connections[0]; + if (ColorsConnection.LEDsValue.Colors) + { + GSMemCopy(ColorsConnection.LEDsValue.Colors, Colors, sizeof(sacn_pixel) * LEDCount); + } +} + +internal void +UpdateAllNodeCalculations (node_list* NodeList, memory_arena* Transient, led* LEDs, sacn_pixel* Colors, s32 LEDCount) +{ + node_list_iterator NodeIter = GetNodeListIterator(*NodeList); + while (NodeIteratorIsValid(NodeIter)) + { + interface_node* Node = NodeIter.At; + if (!Node->UpdatedThisFrame) + { + UpdateNodeCalculation(Node, NodeList, Transient, LEDs, Colors, LEDCount); + } + Next(&NodeIter); + } +} + +internal void +DrawValueDisplay (render_command_buffer* RenderBuffer, rect Bounds, node_connection Value, bitmap_font* Font) +{ + PushRenderQuad2D(RenderBuffer, Bounds.Min, Bounds.Max, BlackV4); + + char Buffer[32]; + string String = MakeString(Buffer, 32); + + switch (Value.Type) + { + case MemberType_s32: + { + PrintF(&String, "%.*d", 4, Value.S32Value); + DrawString(RenderBuffer, String, Font, Font->PixelHeight, Bounds.Min + v2{2, 2}, WhiteV4); + }break; + + case MemberType_r32: + { + PrintF(&String, "%.*f", 4, Value.R32Value); + DrawString(RenderBuffer, String, Font, Font->PixelHeight, Bounds.Min + v2{2, 2}, WhiteV4); + }break; + + case MemberType_v4: + { + PushRenderQuad2D(RenderBuffer, Bounds.Min + v2{2, 2}, Bounds.Max - v2{2, 2}, Value.V4Value); + }break; + + case MemberType_NODE_COLOR_BUFFER: + { + PrintF(&String, "LEDs"); + DrawString(RenderBuffer, String, Font, Font->PixelHeight, Bounds.Min + v2{2, 2}, WhiteV4); + }break; + + InvalidDefaultCase; + } +} + +internal void +DrawPort (render_command_buffer* RenderBuffer, rect Bounds, v4 Color) +{ + PushRenderQuad2D(RenderBuffer, Bounds.Min, Bounds.Max, Color); +} + +internal void +RenderNodeList (node_list* NodeList, node_render_settings RenderSettings, render_command_buffer* RenderBuffer) +{ + DEBUG_TRACK_FUNCTION; + + node_list_iterator NodeIter = GetNodeListIterator(*NodeList); + while (NodeIteratorIsValid(NodeIter)) + { + interface_node* Node = NodeIter.At; + Node->Min = Node->MinAfterUpdate; + + rect NodeBounds = CalculateNodeBounds(Node, RenderSettings); + + PushRenderQuad2D(RenderBuffer, NodeBounds.Min, NodeBounds.Max, v4{.5f, .5f, .5f, 1.f}); + + DrawString(RenderBuffer, Node->Name, RenderSettings.Font, RenderSettings.Font->PixelHeight, + v2{NodeBounds.Min.x + 5, NodeBounds.Max.y - (RenderSettings.Font->PixelHeight + NODE_HEADER_HEIGHT + 5)}, + WhiteV4); + + for (s32 Connection = 0; Connection < Node->ConnectionsCount; Connection++) + { + // Inputs + if (ConnectionIsInput(Node, Connection)) + { + rect PortBounds = CalculateNodeInputPortBounds(Node, Connection, RenderSettings); + v4 PortColor = RenderSettings.PortColors[Node->Connections[Connection].Type]; + DrawPort(RenderBuffer, PortBounds, PortColor); + + rect ValueBounds = CalculateNodeInputValueBounds(Node, Connection, RenderSettings); + DrawValueDisplay(RenderBuffer, ValueBounds, Node->Connections[Connection], RenderSettings.Font); + + // NOTE(Peter): its way easier to draw the connection on the input port b/c its a 1:1 relationship, + // whereas output ports might have many connections, they really only know about the most recent one + // Not sure if this is a problem. We mostly do everything backwards here, starting at the + // most downstream node and working back up to find dependencies. + if (ConnectionHasUpstreamConnection(Node, Connection)) + { + rect ConnectedPortBounds = GetBoundsOfPortConnectedToInput(Node, Connection, NodeList, RenderSettings); + v2 InputCenter = CalculateRectCenter(PortBounds); + v2 OutputCenter = CalculateRectCenter(ConnectedPortBounds); + PushRenderLine2D(RenderBuffer, OutputCenter, InputCenter, 1, WhiteV4); + } + } + + // Outputs + if (ConnectionIsOutput(Node, Connection)) + { + rect PortBounds = CalculateNodeOutputPortBounds(Node, Connection, RenderSettings); + v4 PortColor = RenderSettings.PortColors[Node->Connections[Connection].Type]; + DrawPort(RenderBuffer, PortBounds, PortColor); + + rect ValueBounds = CalculateNodeOutputValueBounds(Node, Connection, RenderSettings); + DrawValueDisplay(RenderBuffer, ValueBounds, Node->Connections[Connection], RenderSettings.Font); + } + + for (s32 Button = 0; Button < 3; Button++) + { + rect ButtonRect = CalculateNodeDragHandleBounds(NodeBounds, Button, RenderSettings); + PushRenderQuad2D(RenderBuffer, ButtonRect.Min, ButtonRect.Max, DragButtonColors[Button]); + } + } + + Next(&NodeIter); + } +} + +internal void +ResetNodesUpdateState (node_list* NodeList) +{ + node_list_iterator NodeIter = GetNodeListIterator(*NodeList); + while (NodeIteratorIsValid(NodeIter)) + { + interface_node* Node = NodeIter.At; + Node->UpdatedThisFrame = false; + Next(&NodeIter); + } +} + diff --git a/foldhaus_node.h b/foldhaus_node.h new file mode 100644 index 0000000..01abdcc --- /dev/null +++ b/foldhaus_node.h @@ -0,0 +1,182 @@ +typedef enum node_type node_type; + +#define IsInputMember 1 << 0 +#define IsOutputMember 1 << 1 + +#define NODE_COLOR_BUFFER \ +led* LEDs; \ +sacn_pixel* Colors; \ +s32 LEDCount; + +#define NAMED_NODE_COLOR_BUFFER(name) \ +led* name##LEDs; \ +sacn_pixel* name##Colors; \ +s32 name##LEDCount; + +#define NODE_COLOR_BUFFER_INOUT NODE_COLOR_BUFFER +#define NODE_COLOR_BUFFER_IN(name) NAMED_NODE_COLOR_BUFFER(name) +#define NODE_COLOR_BUFFER_OUT(name) NAMED_NODE_COLOR_BUFFER(name) + +enum mouse_node_interaction +{ + MouseNodeInteraction_None, + MouseNodeInteraction_DragNode, + MouseNodeInteraction_DragInput, + + MouseNodeInteraction_Count, +}; + +enum node_port_direction +{ + NodePort_Input, + NodePort_Output, +}; + +// TODO(Peter): Generate this +enum struct_member_type +{ + MemberType_s32, + MemberType_r32, + MemberType_v4, + MemberType_NODE_COLOR_BUFFER, + MemberTypeCount, +}; + +struct node_led_color_connection +{ + NODE_COLOR_BUFFER; +}; + +struct node_connection +{ + struct_member_type Type; + // NOTE(Peter): Offset from the head of the node list that the connected node + // is stored at. See GetNodeAtOffset for example of how this is used + s32 UpstreamNodeOffset; + s32 UpstreamNodePortIndex; + s32 DownstreamNodeOffset; + s32 DownstreamNodePortIndex; + b32 DirectionMask; + + union + { + s32 S32Value; + r32 R32Value; + v4 V4Value; + node_led_color_connection LEDsValue; + }; +}; + +// TODO(Peter): cant decide if this needs to be dynamic or just a really big number +// reevaluate once you have some examples +#define NODE_CONNECTIONS_MAX 8 +struct interface_node +{ + string Name; + + v2 Min, Dim; + v2 MinAfterUpdate; + + s32 ConnectionsCount; + node_connection* Connections; + + node_type Type; + b32 UpdatedThisFrame; +}; + +struct node_list +{ + u8* Memory; + s32 Max; + s32 Used; + + node_list* Next; +}; + +struct node_offset +{ + interface_node* Node; + s32 Offset; +}; + +struct node_list_iterator +{ + node_list List; + interface_node* At; +}; + +enum node_interaction_flag +{ + NodeInteraction_AllUpstream = 0x1, + NodeInteraction_AllDownstream = 0x2, +}; + +struct node_interaction +{ + s32 NodeOffset; + v2 MouseOffset; + b32 Flags; + + s32 InputPort; + s32 InputValue; + s32 OutputPort; + s32 OutputValue; +}; + +struct node_struct_member +{ + struct_member_type Type; + char* Name; + u64 Offset; + b32 IsInput; +}; + +struct node_specification +{ + node_type Type; + + char* Name; + s32 NameLength; + + node_struct_member* MemberList; + s32 DataStructSize; + + s32 MemberListLength; + b32 IsPattern; +}; + +struct node_render_settings +{ + v2 PortDim; + r32 PortStep; + v4 PortColors[MemberTypeCount]; + bitmap_font* Font; +}; + +v4 DragButtonColors[] = { + v4{.7f, .7f, .7f, 1}, + BlackV4, + v4{.7f, .7f, .7f, 1}, +}; + +#define NODE_HEADER_HEIGHT 20 + +/////////////////////////////////////////////// +// Pre Processor Macros +/////////////////////////////////////////////// + +#define NODE_STRUCT(data_name) \ +struct data_name + +#define NODE_PATTERN_STRUCT(data_name) \ +struct data_name + +#define NODE_PROC(proc_name, input_type) \ +void proc_name(input_type* Data) + +#define NODE_PATTERN_PROC(proc_name, input_type) \ +void proc_name(input_type* Data, led* LEDs, sacn_pixel* Colors, s32 LEDCount) + +#define NODE_IN(type, name) type name +#define NODE_OUT(type, name) type name + diff --git a/foldhaus_patterns.cpp b/foldhaus_patterns.cpp new file mode 100644 index 0000000..69945dd --- /dev/null +++ b/foldhaus_patterns.cpp @@ -0,0 +1,247 @@ +NODE_STRUCT(multiply_data) +{ + NODE_IN(r32, A); + NODE_IN(r32, B); + NODE_OUT(r32, Result); +}; + +NODE_PROC(MultiplyNodeProc, multiply_data) +{ + Data->Result = Data->A * Data->B; +} + +NODE_STRUCT(add_data) +{ + NODE_IN(v4, A); + NODE_IN(v4, B); + NODE_OUT(v4, Result); +}; + +NODE_PROC(AddNodeProc, add_data) +{ + Data->Result = Data->A + Data->B; +} + +////////////////////////////////////// +// +// OLD - Pre Node System +// +/////////////////////////////////////// + +PATTERN_PUSH_COLOR_PROC(PushColor_Override) +{ + Colors[LED->Index].R = R; + Colors[LED->Index].G = G; + Colors[LED->Index].B = B; +} + +PATTERN_PUSH_COLOR_PROC(PushColor_Add) +{ + Colors[LED->Index].R += R; + Colors[LED->Index].G += G; + Colors[LED->Index].B += B; +} + +PATTERN_PUSH_COLOR_PROC(PushColor_Multiply) +{ + Colors[LED->Index].R *= R; + Colors[LED->Index].G *= G; + Colors[LED->Index].B *= B; +} + +inline u32 +PackColorStruct (u8* Channels) +{ + u32 Result = 0; + Result |= (*Channels++ << 24); + Result |= (*Channels++ << 16); + Result |= (*Channels++ << 8); + Result |= (255 << 0); // Alpha + return Result; +}; + +inline u8 +ToColorU8 (r32 V) +{ + return (u8)(V * 255.f); +} + +inline u32 +PackColorStructFromVector (v4 Color) +{ + u32 Result = ((ToColorU8(Color.r) << 24) | + (ToColorU8(Color.g) << 16) | + (ToColorU8(Color.b) << 8) | + (ToColorU8(Color.a) << 0)); + return Result; +} + +internal void +InitLEDPatternSystem (led_pattern_system* PatternSystem, memory_arena* ParentStorage, + s32 MaxPatternsCount, s32 PatternWorkingMemoryStorageSize) +{ + PatternSystem->Patterns = PushArray(ParentStorage, led_pattern, MaxPatternsCount); + PatternSystem->PatternsUsed = 0; + PatternSystem->PatternsMax = MaxPatternsCount; + + InitMemoryArena(&PatternSystem->PatternWorkingMemoryStorage, PushSize(ParentStorage, PatternWorkingMemoryStorageSize), PatternWorkingMemoryStorageSize, 0); +} + +internal pattern_index_id_key +AddPattern (led_pattern_system* PatternSystem, + pattern_registry_entry* PatternSpec) +{ + Assert(PatternSystem->PatternsUsed < PatternSystem->PatternsMax); + + pattern_index_id_key Result = {}; + + led_pattern* NewPattern = &PatternSystem->Patterns[PatternSystem->PatternsUsed]; + NewPattern->ID = PatternSystem->IDAccumulator++; + NewPattern->Name = PatternSpec->Name; + NewPattern->UpdateProc = PatternSpec->Update; + PatternSpec->Init(NewPattern, &PatternSystem->PatternWorkingMemoryStorage); + + Result.Index = PatternSystem->PatternsUsed++; + Result.ID = NewPattern->ID; + + return Result; +} + +internal b32 +RemovePattern (pattern_index_id_key Key, led_pattern_system* PatternSystem) +{ + b32 Result = false; + s32 ActualIndex = -1; + + if (PatternSystem->Patterns[Key.Index].ID == Key.ID) + { + ActualIndex = Key.Index; + } + else + { + for (s32 i = 0; i < PatternSystem->PatternsUsed; i++) + { + if (PatternSystem->Patterns[i].ID == Key.ID) + { + ActualIndex = i; + break; + } + } + } + + if (ActualIndex >= 0) + { + for (s32 j = ActualIndex; j < PatternSystem->PatternsUsed - 1; j++) + { + PatternSystem->Patterns[j] = PatternSystem->Patterns[j + 1]; + } + + PatternSystem->PatternsUsed--; + + Result = true; + } + else + { + Result = false; + } + + return Result; +} + +internal led_pattern* +FindPatternAndUpdateIDKey (pattern_index_id_key* Key, led_pattern_system* PatternSystem) +{ + led_pattern* Result = 0; + + if (Key->Index < PatternSystem->PatternsUsed && + PatternSystem->Patterns[Key->Index].ID == Key->ID) + { + Result = &PatternSystem->Patterns[Key->Index]; + } + else + { + for (s32 i = 0; i < PatternSystem->PatternsUsed; i++) + { + if (PatternSystem->Patterns[i].ID == Key->ID) + { + Result = &PatternSystem->Patterns[i]; + Key->Index = i; + break; + } + } + } + + return Result; +} + +#if 0 +internal void +UpdateAllPatterns_ (patterns_update_list* UpdateList, + led_pattern_system* PatternSystem, + pattern_led* LEDs, + s32 LEDsCount, + r32 DeltaTime, + memory_arena* Transient) +{ + pattern_push_color_proc* PushColorProc = 0; + + for (s32 PatternKeyIdx = 0; PatternKeyIdx < UpdateList->Used; PatternKeyIdx++) + { + pattern_update_list_entry ListEntry = UpdateList->Patterns[PatternKeyIdx]; + pattern_index_id_key Key = ListEntry.Key; + led_pattern* Pattern = FindPatternAndUpdateIDKey(&Key, PatternSystem); + + if (!Pattern) + { + Pattern = FindPatternAndUpdateIDKey(&Key, PatternSystem); + } + + Pattern->UpdateProc(LEDs, LEDsCount, + Pattern->Memory, + DeltaTime, ListEntry.PushColorProc); + } + + if (UpdateList->Next) + { + UpdateAllPatterns(UpdateList->Next, PatternSystem, LEDs, LEDsCount, DeltaTime, Transient); + } +} +#endif + +internal void +UpdateAllPatterns (patterns_update_list* UpdateList, + led_pattern_system* PatternSystem, + led_buffer* LEDBuffer, + r32 DeltaTime, + memory_arena* Transient) +{ + pattern_push_color_proc* PushColorProc = 0; + + for (s32 PatternKeyIdx = 0; PatternKeyIdx < UpdateList->Used; PatternKeyIdx++) + { + pattern_update_list_entry ListEntry = UpdateList->Patterns[PatternKeyIdx]; + pattern_index_id_key Key = ListEntry.Key; + led_pattern* Pattern = FindPatternAndUpdateIDKey(&Key, PatternSystem); + + if (!Pattern) + { + Pattern = FindPatternAndUpdateIDKey(&Key, PatternSystem); + } + + led_buffer* LEDBufferIter = LEDBuffer; + while(LEDBufferIter) + { + Pattern->UpdateProc(LEDBufferIter->LEDs, LEDBufferIter->Colors, LEDBufferIter->Count, + Pattern->Memory, + DeltaTime, ListEntry.PushColorProc); + LEDBufferIter = LEDBufferIter->Next; + } + } + + if (UpdateList->Next) + { + UpdateAllPatterns(UpdateList->Next, PatternSystem, LEDBuffer, DeltaTime, Transient); + } +} + + diff --git a/foldhaus_patterns.h b/foldhaus_patterns.h new file mode 100644 index 0000000..0026d50 --- /dev/null +++ b/foldhaus_patterns.h @@ -0,0 +1,78 @@ +typedef s32 pattern_id; + +typedef struct pattern_led pattern_led; + +#define PATTERN_INIT_PROC(name) void name(led_pattern* Pattern, memory_arena* Storage) +typedef PATTERN_INIT_PROC(pattern_init_proc); + +#define PATTERN_PUSH_COLOR_PROC(name) void name(led* LED, sacn_pixel* Colors, u8 R, u8 G, u8 B) +typedef PATTERN_PUSH_COLOR_PROC(pattern_push_color_proc); + +#define PATTERN_UPDATE_PROC(name) void name(led* LEDs, sacn_pixel* Colors, s32 LEDCount, void* Memory, r32 DeltaTime, pattern_push_color_proc PushColor) +typedef PATTERN_UPDATE_PROC(pattern_update_proc); + +struct pattern_registry_entry +{ + char* Name; + pattern_init_proc* Init; + pattern_update_proc* Update; +}; + +struct pattern_led +{ + s32 LocationInSendBuffer; + u32 Color; + v3 Position; +}; + +enum pattern_selector_combine_operation +{ + PatternSelectorCombine_Invalid, + + PatternSelectorCombine_Override, + PatternSelectorCombine_Add, + PatternSelectorCombine_Multiply, + + PatternSelectorCombine_Count, +}; + +char* PatternSelectorOperationsText[] = { + "Invalid", + + "Override", + "Add", + "Multiply", + + "Count", +}; + +struct pattern_index_id_key +{ + s32 Index; + pattern_id ID; +}; + +struct led_pattern +{ + pattern_id ID; + + void* Memory; + + pattern_update_proc* UpdateProc; + char* Name; +}; + +struct led_pattern_system +{ + // TODO(Peter): Need to think about how this grows + led_pattern* Patterns; + s32 PatternsUsed; + s32 PatternsMax; + + // TODO(Peter): Need to think about how this can have a free list as well of some sort. + // It might be inexpensive enough to just compress this memory whenever we remove a pattern + memory_arena PatternWorkingMemoryStorage; + + + pattern_id IDAccumulator; +}; diff --git a/foldhaus_platform.h b/foldhaus_platform.h new file mode 100644 index 0000000..cb3087e --- /dev/null +++ b/foldhaus_platform.h @@ -0,0 +1,115 @@ +#include "gs_language.h" +#include "gs_platform.h" + +#include "foldhaus_memory.h" +#include "gs_string.h" +#include "gs_input.h" +#include "foldhaus_debug.h" + +global_variable debug_services* GlobalDebugServices; + +#include "gs_vector_matrix.h" +#include "foldhaus_renderer.h" + +typedef struct context context; + + +// Application Functions + +#define INITIALIZE_APPLICATION(name) void name(context Context) +typedef INITIALIZE_APPLICATION(initialize_application); + +#define UPDATE_AND_RENDER(name) void name(context Context, input Input, render_command_buffer* RenderBuffer) +typedef UPDATE_AND_RENDER(update_and_render); + +#define RELOAD_STATIC_DATA(name) void name(context Context, debug_services* DebugServices) +typedef RELOAD_STATIC_DATA(reload_static_data); + +#define CLEANUP_APPLICATION(name) void name(context Context) +typedef CLEANUP_APPLICATION(cleanup_application); + +// Platform Functions + +// Worker Threads + +#define THREADED_WORK_PROC(name) void name(s32 ThreadID, void* Data) +typedef THREADED_WORK_PROC(threaded_work_proc); + +typedef struct work_queue work_queue; + +#define PUSH_WORK_ON_QUEUE(name) void name(work_queue* Queue, threaded_work_proc* WorkProc, void* Data) +typedef PUSH_WORK_ON_QUEUE(push_work_on_queue); + +#define DO_QUEUE_WORK_UNTIL_DONE(name) void name(work_queue* Queue, s32 ThreadID) +typedef DO_QUEUE_WORK_UNTIL_DONE(do_queue_work_until_done); + +#define RESET_WORK_QUEUE(name) void name(work_queue* Queue) +typedef RESET_WORK_QUEUE(reset_work_queue); + +struct worker_thread_job +{ + void* Data; + threaded_work_proc* WorkProc; +}; + +struct work_queue +{ + HANDLE SemaphoreHandle; + + u32 JobsMax; + u32 volatile JobsCount; + u32 volatile NextJobIndex; + u32 volatile JobsCompleted; + worker_thread_job Jobs[256]; + + // Work Queue + push_work_on_queue* PushWorkOnQueue; + do_queue_work_until_done* DoQueueWorkUntilDone; + reset_work_queue* ResetWorkQueue; +}; + +RESET_WORK_QUEUE(ResetWorkQueue) +{ + for (u32 i = 0; i < Queue->JobsMax; i++) + { + Queue->Jobs[i].Data = 0; + Queue->Jobs[i].WorkProc = 0; + } + + Queue->JobsCount = 0; + Queue->NextJobIndex = 0; + Queue->JobsCompleted = 0; +} + +struct context +{ + u8* MemoryBase; + u32 MemorySize; + + b32 WindowIsVisible; + r32 WindowWidth; + r32 WindowHeight; + r32 DeltaTime; + + // Application Services + initialize_application* InitializeApplication; + reload_static_data* ReloadStaticData; + update_and_render* UpdateAndRender; + cleanup_application* CleanupApplication; + + // Platform Services + work_queue* GeneralWorkQueue; + + platform_alloc* PlatformAlloc; + platform_free* PlatformFree; + platform_read_entire_file* PlatformReadEntireFile; + platform_write_entire_file* PlatformWriteEntireFile; + platform_get_file_path* PlatformGetFilePath; + platform_get_gpu_texture_handle* PlatformGetGPUTextureHandle; + + platform_get_socket_handle* PlatformGetSocketHandle; + platform_get_send_address* PlatformGetSendAddress; + platform_set_socket_option* PlatformSetSocketOption; + platform_send_to* PlatformSendTo; + platform_close_socket* PlatformCloseSocket; +}; diff --git a/foldhaus_renderer.cpp b/foldhaus_renderer.cpp new file mode 100644 index 0000000..ce4e736 --- /dev/null +++ b/foldhaus_renderer.cpp @@ -0,0 +1,175 @@ +internal render_command_buffer +AllocateRenderCommandBuffer (u8* Memory, s32 Size) +{ + render_command_buffer Result = {}; + Result.CommandMemory = Memory; + Result.CommandMemoryUsed = 0; + Result.CommandMemorySize = Size; + return Result; +} + +internal void +Render3DQuadBatch (u8* CommandData, s32 TriCount) +{ + DEBUG_TRACK_FUNCTION; + + v4* Vertecies = (v4*)(CommandData + BATCH_3D_VERTECIES_OFFSET(TriCount)); + v2* UVs = (v2*)(CommandData + BATCH_3D_UVS_OFFSET(TriCount)); + v4* Colors = (v4*)(CommandData + BATCH_3D_COLORS_OFFSET(TriCount)); + +#if IMMEDIATE_MODE_RENDERING + glBegin(GL_TRIANGLES); + for (s32 Tri = 0; Tri < TriCount; Tri++) + { + v4 P0 = Vertecies[BATCH_3D_VERTEX_INDEX(Tri, 0)]; + v4 P1 = Vertecies[BATCH_3D_VERTEX_INDEX(Tri, 1)]; + v4 P2 = Vertecies[BATCH_3D_VERTEX_INDEX(Tri, 2)]; + v2 UV0 = UVs[BATCH_3D_UV_INDEX(Tri, 0)]; + v2 UV1 = UVs[BATCH_3D_UV_INDEX(Tri, 1)]; + v2 UV2 = UVs[BATCH_3D_UV_INDEX(Tri, 2)]; + v4 C0 = Colors[BATCH_3D_COLOR_INDEX(Tri, 0)]; + v4 C1 = Colors[BATCH_3D_COLOR_INDEX(Tri, 1)]; + v4 C2 = Colors[BATCH_3D_COLOR_INDEX(Tri, 2)]; + + OpenGLDraw3DTri(P0, P1, P2, UV0, UV1, UV2, C0, C1, C2); + } + glEnd(); +#else + OpenGLRenderTriBuffer((u8*)Vertecies, 4, UVs, 2, Colors, 4, TriCount * 3); +#endif +} + +internal void +Render2DQuadBatch (u8* CommandData, s32 QuadCount) +{ + DEBUG_TRACK_FUNCTION; + + v2* Vertecies = (v2*)(CommandData + BATCH_2D_VERTECIES_OFFSET(QuadCount)); + v2* UVs = (v2*)(CommandData + BATCH_2D_UVS_OFFSET(QuadCount)); + v4* Colors = (v4*)(CommandData + BATCH_2D_COLORS_OFFSET(QuadCount)); + +#if IMMEDIATE_MODE_RENDERING + for (s32 Quad = 0; Quad < QuadCount; Quad++) + { + for (s32 Tri = 0; Tri < 2; Tri++) + { + v2 P0 = Vertecies[BATCH_2D_VERTEX_INDEX(Quad, Tri, 0)]; + v2 P1 = Vertecies[BATCH_2D_VERTEX_INDEX(Quad, Tri, 1)]; + v2 P2 = Vertecies[BATCH_2D_VERTEX_INDEX(Quad, Tri, 2)]; + v2 UV0 = UVs[BATCH_2D_UV_INDEX(Quad, Tri, 0)]; + v2 UV1 = UVs[BATCH_2D_UV_INDEX(Quad, Tri, 1)]; + v2 UV2 = UVs[BATCH_2D_UV_INDEX(Quad, Tri, 2)]; + v4 C0 = Colors[BATCH_2D_COLOR_INDEX(Quad, Tri, 0)]; + v4 C1 = Colors[BATCH_2D_COLOR_INDEX(Quad, Tri, 1)]; + v4 C2 = Colors[BATCH_2D_COLOR_INDEX(Quad, Tri, 2)]; + + OpenGLDraw2DTri(P0, P1, P2, UV0, UV1, UV2, C0, C1, C2); + } + } +#else + OpenGLRenderTriBuffer((u8*)Vertecies, 2, UVs, 2, Colors, 4, QuadCount * 2 * 3); +#endif +} + +internal void +RenderCommandBuffer (render_command_buffer CommandBuffer) +{ + DEBUG_TRACK_FUNCTION; + + glViewport(0, 0, CommandBuffer.ViewWidth, CommandBuffer.ViewHeight); + + glMatrixMode(GL_TEXTURE_2D); + glLoadIdentity(); + + glClearColor(0.1f, 0.1f, 0.1f, 1); + + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glDisable(GL_TEXTURE_2D); + b32 GLTextureEnabled = false; + + u8* CurrentPosition = CommandBuffer.CommandMemory; + while(CurrentPosition < CommandBuffer.CommandMemory + CommandBuffer.CommandMemoryUsed) + { + render_command_header* CommandHeader = (render_command_header*)CurrentPosition; + CurrentPosition += sizeof(render_command_header); + switch (CommandHeader->Type) + { + case RenderCommand_render_command_set_render_mode: + { + render_command_set_render_mode* Command = (render_command_set_render_mode*)(CommandHeader + 1); + + LoadModelView(Command->ModelView.E); + LoadProjection(Command->Projection.E); + + if (Command->UseDepthBuffer) + { + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LESS); + } + else + { + glDisable(GL_DEPTH_TEST); + } + + CurrentPosition += sizeof(render_command_set_render_mode); + }break; + + case RenderCommand_render_command_clear_screen: + { + render_command_clear_screen* Command = (render_command_clear_screen*)(CommandHeader + 1); + + ClearRenderBuffer(); + + CurrentPosition += sizeof(render_command_clear_screen); + }break; + + case RenderCommand_render_batch_command_quad_2d: + { + render_batch_command_quad_2d* Command = (render_batch_command_quad_2d*)(CommandHeader + 1); + + if (GLTextureEnabled) { glDisable(GL_TEXTURE_2D); GLTextureEnabled = false; } + u8* CommandData = (u8*)(Command + 1); + Render2DQuadBatch(CommandData, Command->QuadCount); + + CurrentPosition += sizeof(render_batch_command_quad_2d) + Command->DataSize; + }break; + + case RenderCommand_render_batch_command_quad_3d: + { + render_batch_command_quad_3d* Command = (render_batch_command_quad_3d*)(CommandHeader + 1); + + if (GLTextureEnabled) { glDisable(GL_TEXTURE_2D); GLTextureEnabled = false; } + u8* CommandData = (u8*)(Command + 1); + Render3DQuadBatch(CommandData, Command->QuadCount * 2); + + CurrentPosition += sizeof(render_batch_command_quad_3d) + Command->DataSize; + }break; + + case RenderCommand_render_batch_command_texture_2d: + { + render_batch_command_texture_2d* Command = (render_batch_command_texture_2d*)(CommandHeader + 1); + + if (!GLTextureEnabled) { glEnable(GL_TEXTURE_2D); GLTextureEnabled = true; } + Assert(Command->Texture.Handle > 0); + glBindTexture(GL_TEXTURE_2D, Command->Texture.Handle); + u8* CommandData = (u8*)(Command + 1); + Render2DQuadBatch(CommandData, Command->QuadCount); + + CurrentPosition += sizeof(render_batch_command_texture_2d) + Command->DataSize; + }break; + + default: + { + InvalidCodePath; + }break; + } + } +} + +internal void +ClearRenderBuffer (render_command_buffer* Buffer) +{ + Buffer->CommandMemoryUsed = 0; +} \ No newline at end of file diff --git a/foldhaus_renderer.h b/foldhaus_renderer.h new file mode 100644 index 0000000..b286c57 --- /dev/null +++ b/foldhaus_renderer.h @@ -0,0 +1,552 @@ +#define IMMEDIATE_MODE_RENDERING 0 + +struct camera +{ + r32 FieldOfView; + r32 AspectRatio; + r32 Near, Far; + v3 Position; + v3 LookAt; +}; + +inline m44 +GetCameraModelViewMatrix (camera Camera) +{ + // Forward + v4 CamForward = V4(Normalize(Camera.Position - Camera.LookAt), 0); + // Right + v4 CamRight = Normalize(Cross(v4{0, 1, 0, 0}, CamForward)); + // Up + v4 CamUp = Normalize(Cross(CamForward, CamRight)); + + r32 X = Camera.Position.x; + r32 Y = Camera.Position.y; + r32 Z = Camera.Position.z; + + m44 RotationMatrix = M44( + CamRight.x, CamUp.x, CamForward.x, 0, + CamRight.y, CamUp.y, CamForward.y, 0, + CamRight.z, CamUp.z, CamForward.z, 0, + 0, 0, 0, 1); + + m44 PositionMatrix = M44( + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + -X, -Y, -Z, 1 + ); + + m44 ModelViewMatrix = PositionMatrix * RotationMatrix; + + return ModelViewMatrix; +} + +inline m44 +GetCameraPerspectiveProjectionMatrix(camera Camera) +{ + r32 Top = Camera.Near * GSTan((Camera.FieldOfView / 2.0f)); + r32 Bottom = -Top; + r32 Right = Top * Camera.AspectRatio; + r32 Left = -Right; + + r32 A = ((Right + Left) / (Right - Left)); + r32 B = ((Top + Bottom) / (Top - Bottom)); + r32 C = -((Camera.Far + Camera.Near) / (Camera.Far - Camera.Near)); + r32 D = -((2 * Camera.Far * Camera.Near) / (Camera.Far - Camera.Near)); + + r32 E = ((2 * Camera.Near) / (Right - Left)); + r32 F = ((2 * Camera.Near) / (Top - Bottom)); + + m44 PerspectiveProjectionMatrix = + { + E, 0, A, 0, + 0, F, B, 0, + 0, 0, C, D, + 0, 0, -1, 0 + }; + + return PerspectiveProjectionMatrix; +} + +// Render Commands +// Discriminated Union +enum render_command_type +{ + RenderCommand_Invalid, + + RenderCommand_render_command_clear_screen, + RenderCommand_render_command_set_render_mode, + + RenderCommand_render_batch_command_quad_2d, + RenderCommand_render_batch_command_quad_3d, + RenderCommand_render_batch_command_texture_2d, + + RenderCommand_render_command_texture_3d, + + RenderCommand_Count +}; + +struct render_command_header +{ + render_command_type Type; +}; + +// NOTE(Peter): Just to keep with the rest of the system +struct render_command_clear_screen {}; + +struct render_quad_2d +{ + v2 Min, Max; +}; + +struct render_quad_3d +{ + v4 P0, P1, P2, P3; +}; + +struct render_texture +{ + // TODO(Peter): Is all this necessary? + u8* Memory; + s32 Handle; + s32 Width; + s32 Height; + s32 BytesPerPixel; + s32 Stride; +}; + +#define BATCH_3D_SIZE(tricount) (((sizeof(v4) + sizeof(v2) + sizeof(v4)) * 3) * tricount) +#define BATCH_3D_VERTECIES_OFFSET(tricount) (0 * tricount) +#define BATCH_3D_UVS_OFFSET(tricount) (BATCH_3D_VERTECIES_OFFSET(tricount) + ((sizeof(v4) * 3) * tricount)) +#define BATCH_3D_COLORS_OFFSET(tricount) (BATCH_3D_UVS_OFFSET(tricount) + ((sizeof(v2) * 3) * tricount)) +#define BATCH_3D_VERTEX_INDEX(tri, v) ((tri * 3) + v) +#define BATCH_3D_UV_INDEX(tri, v) ((tri * 3) + v) +#define BATCH_3D_COLOR_INDEX(tri, v) ((tri * 3) + v) + +#define BATCH_2D_SIZE(quadcount) (((sizeof(v2) + sizeof(v2) + sizeof(v4)) * 3) * 2 * quadcount) +#define BATCH_2D_VERTECIES_OFFSET(quadcount) (0 * quadcount) +#define BATCH_2D_UVS_OFFSET(quadcount) (BATCH_2D_VERTECIES_OFFSET(quadcount) + ((sizeof(v2) * 3) * 2 * quadcount)) +#define BATCH_2D_COLORS_OFFSET(quadcount) (BATCH_2D_UVS_OFFSET(quadcount) + ((sizeof(v2) * 3) * 2 * quadcount)) +#define BATCH_2D_VERTEX_INDEX(quad, tri, v) ((quad * 6) + (tri * 3) + v) +#define BATCH_2D_UV_INDEX(quad, tri, v) ((quad * 6) + (tri * 3) + v) +#define BATCH_2D_COLOR_INDEX(quad, tri, v) ((quad * 6) + (tri * 3) + v) + +struct render_quad_batch_constructor +{ + s32 Max; + s32 Count; + + v4* Vertecies; + v2* UVs; + v4* ColorsV; +}; + +struct render_batch_command_quad_2d +{ + s32 QuadCount; + s32 DataSize; + // NOTE(Peter): The data immediately follows the command in memory +}; + +struct render_batch_command_quad_3d +{ + s32 QuadCount; + s32 DataSize; + // NOTE(Peter): The data immediately follows the command in memory +}; + +struct render_command_texture_2d +{ + render_quad_2d Quad; + render_quad_2d UV; + v4 Color; + render_texture Texture; +}; + +struct render_batch_command_texture_2d +{ + s32 QuadCount; + s32 DataSize; + render_texture Texture; +}; + +struct render_command_texture_3d +{ + render_quad_3d Quad; + v4 Color; + render_texture Texture; +}; + +struct render_command_set_render_mode +{ + m44 ModelView; + m44 Projection; + r32 ViewWidth, ViewHeight; + b32 UseDepthBuffer; +}; + +struct render_command_buffer +{ + u8* CommandMemory; + s32 CommandMemoryUsed; + s32 CommandMemorySize; + + s32 ViewWidth; + s32 ViewHeight; +}; + +/// +// Utility +/// + +internal u32 +PackColorStructU8 (u8 R, u8 G, u8 B, u8 A) +{ + u32 Result = (u32)(A << 24 | + R << 16 | + G << 8 | + B<< 0); + return Result; +} + +internal u32 +PackColorStructR32 (r32 In_R, r32 In_G, r32 In_B, r32 In_A) +{ + Assert ((In_R >= 0.0f && In_R <= 1.0f) && + (In_G >= 0.0f && In_G <= 1.0f) && + (In_B >= 0.0f && In_B <= 1.0f) && + (In_A >= 0.0f && In_A <= 1.0f)); + + u8 R = (u8)(255 * In_R); + u8 G = (u8)(255 * In_G); + u8 B = (u8)(255 * In_B); + u8 A = (u8)(255 * In_A); + + u32 Result = (u32)(A << 24 | + R << 16 | + G << 8 | + B<< 0); + return Result; +} + +// Batch + +internal s32 +PushQuad3DBatch (render_command_buffer* Buffer, render_quad_batch_constructor* Constructor, s32 QuadCount, u8* MemStart, b32 UseIntegerColor = false) +{ + s32 TriCount = QuadCount * 2; + s32 DataSize = BATCH_3D_SIZE(TriCount); + Assert(Buffer->CommandMemoryUsed + DataSize <= Buffer->CommandMemorySize); + + Constructor->Max = TriCount; + Constructor->Count = 0; + + Constructor->Vertecies = (v4*)(MemStart + BATCH_3D_VERTECIES_OFFSET(TriCount)); + Constructor->UVs = (v2*)(MemStart + BATCH_3D_UVS_OFFSET(TriCount)); + Constructor->ColorsV = (v4*)(MemStart + BATCH_3D_COLORS_OFFSET(TriCount)); + + Buffer->CommandMemoryUsed += DataSize; + return DataSize; +} + +internal s32 +PushQuad2DBatch (render_command_buffer* Buffer, render_quad_batch_constructor* Constructor, s32 QuadCount, u8* MemStart) +{ + s32 DataSize = BATCH_2D_SIZE(QuadCount); + Assert(Buffer->CommandMemoryUsed + DataSize <= Buffer->CommandMemorySize); + + GSZeroMemory(MemStart, DataSize); + + Constructor->Max = QuadCount; + Constructor->Count = 0; + Constructor->Vertecies = (v4*)(MemStart + BATCH_2D_VERTECIES_OFFSET(QuadCount)); + Constructor->UVs = (v2*)(MemStart + BATCH_2D_UVS_OFFSET(QuadCount)); + Constructor->ColorsV = (v4*)(MemStart + BATCH_2D_COLORS_OFFSET(QuadCount)); + + Buffer->CommandMemoryUsed += DataSize; + return DataSize; +} + +internal s32 +ThreadSafeIncrementQuadConstructorCount (render_quad_batch_constructor* Constructor) +{ + s32 Result = InterlockedIncrement((long*)&Constructor->Count); + // NOTE(Peter): Have to decrement the value by one. + // Interlocked Increment acts as (++Constructor->Count), not (Constructor->Count++) which + // is what we wanted; + // This was causing the first triangle to be garbage data. + Result -= 1; + return Result; +} + +inline void +PushTri3DOnBatch (render_quad_batch_constructor* Constructor, v4 P0, v4 P1, v4 P2, v2 UV0, v2 UV1, v2 UV2, v4 Color) +{ + s32 Tri = ThreadSafeIncrementQuadConstructorCount(Constructor); + // Vertecies + Constructor->Vertecies[BATCH_3D_VERTEX_INDEX(Tri, 0)] = P0; + Constructor->Vertecies[BATCH_3D_VERTEX_INDEX(Tri, 1)] = P1; + Constructor->Vertecies[BATCH_3D_VERTEX_INDEX(Tri, 2)] = P2; + // UVs + Constructor->UVs[BATCH_3D_UV_INDEX(Tri, 0)] = UV0; + Constructor->UVs[BATCH_3D_UV_INDEX(Tri, 1)] = UV1; + Constructor->UVs[BATCH_3D_UV_INDEX(Tri, 2)] = UV1; + // Color V0 + Constructor->ColorsV[BATCH_3D_COLOR_INDEX(Tri, 0)] = Color; + Constructor->ColorsV[BATCH_3D_COLOR_INDEX(Tri, 1)] = Color; + Constructor->ColorsV[BATCH_3D_COLOR_INDEX(Tri, 2)] = Color; +}; + +internal void +PushQuad3DOnBatch (render_quad_batch_constructor* Constructor, v4 P0, v4 P1, v4 P2, v4 P3, v2 UVMin, v2 UVMax, v4 Color) +{ + Assert(Constructor->Count < Constructor->Max); + PushTri3DOnBatch(Constructor, P0, P1, P2, UVMin, v2{UVMax.x, UVMin.y}, UVMax, Color); + PushTri3DOnBatch(Constructor, P0, P2, P3, UVMin, UVMax, v2{UVMin.x, UVMax.y}, Color); +} + +internal void +PushQuad3DOnBatch (render_quad_batch_constructor* Constructor, v4 P0, v4 P1, v4 P2, v4 P3, v4 Color) +{ + PushQuad3DOnBatch(Constructor, P0, P1, P2, P3, v2{0, 0}, v2{1, 1}, Color); +} + +internal void +PushQuad2DOnBatch (render_quad_batch_constructor* Constructor, + v2 P0, v2 P1, v2 P2, v2 P3, + v2 UV0, v2 UV1, v2 UV2, v2 UV3, + v4 C0, v4 C1, v4 C2, v4 C3) +{ + s32 Quad = ThreadSafeIncrementQuadConstructorCount(Constructor); + v2* Vertecies = (v2*)Constructor->Vertecies; + + // Tri 1 + Vertecies[BATCH_2D_VERTEX_INDEX(Quad, 0, 0)] = P0; + Vertecies[BATCH_2D_VERTEX_INDEX(Quad, 0, 1)] = P1; + Vertecies[BATCH_2D_VERTEX_INDEX(Quad, 0, 2)] = P2; + + // Tri 2 + Vertecies[BATCH_2D_VERTEX_INDEX(Quad, 1, 0)] = P0; + Vertecies[BATCH_2D_VERTEX_INDEX(Quad, 1, 1)] = P2; + Vertecies[BATCH_2D_VERTEX_INDEX(Quad, 1, 2)] = P3; + + // Tri 1 UVs + Constructor->UVs[BATCH_2D_UV_INDEX(Quad, 0, 0)] = UV0; + Constructor->UVs[BATCH_2D_UV_INDEX(Quad, 0, 1)] = UV1; + Constructor->UVs[BATCH_2D_UV_INDEX(Quad, 0, 2)] = UV2; + // Tri 2 UVs + Constructor->UVs[BATCH_2D_UV_INDEX(Quad, 1, 0)] = UV0; + Constructor->UVs[BATCH_2D_UV_INDEX(Quad, 1, 1)] = UV2; + Constructor->UVs[BATCH_2D_UV_INDEX(Quad, 1, 2)] = UV3; + + // Tri 1 Colors + Constructor->ColorsV[BATCH_2D_COLOR_INDEX(Quad, 0, 0)] = C0; + Constructor->ColorsV[BATCH_2D_COLOR_INDEX(Quad, 0, 1)] = C1; + Constructor->ColorsV[BATCH_2D_COLOR_INDEX(Quad, 0, 2)] = C2; + // Tri 2 Colors + Constructor->ColorsV[BATCH_2D_COLOR_INDEX(Quad, 1, 0)] = C0; + Constructor->ColorsV[BATCH_2D_COLOR_INDEX(Quad, 1, 1)] = C2; + Constructor->ColorsV[BATCH_2D_COLOR_INDEX(Quad, 1, 2)] = C3; +} + +internal void +PushQuad2DOnBatch (render_quad_batch_constructor* Constructor, v2 P0, v2 P1, v2 P2, v2 P3, v2 UVMin, v2 UVMax, v4 Color) +{ + s32 Quad = ThreadSafeIncrementQuadConstructorCount(Constructor); + v2* Vertecies = (v2*)Constructor->Vertecies; + + // Tri 1 + Vertecies[BATCH_2D_VERTEX_INDEX(Quad, 0, 0)] = P0; + Vertecies[BATCH_2D_VERTEX_INDEX(Quad, 0, 1)] = P1; + Vertecies[BATCH_2D_VERTEX_INDEX(Quad, 0, 2)] = P2; + + // Tri 2 + Vertecies[BATCH_2D_VERTEX_INDEX(Quad, 1, 0)] = P0; + Vertecies[BATCH_2D_VERTEX_INDEX(Quad, 1, 1)] = P2; + Vertecies[BATCH_2D_VERTEX_INDEX(Quad, 1, 2)] = P3; + + // Tri 1 UVs + Constructor->UVs[BATCH_2D_UV_INDEX(Quad, 0, 0)] = UVMin; + Constructor->UVs[BATCH_2D_UV_INDEX(Quad, 0, 1)] = v2{UVMax.x, UVMin.y}; + Constructor->UVs[BATCH_2D_UV_INDEX(Quad, 0, 2)] = UVMax; + // Tri 2 UVs + Constructor->UVs[BATCH_2D_UV_INDEX(Quad, 1, 0)] = UVMin; + Constructor->UVs[BATCH_2D_UV_INDEX(Quad, 1, 1)] = UVMax; + Constructor->UVs[BATCH_2D_UV_INDEX(Quad, 1, 2)] = v2{UVMin.x, UVMax.y}; + + // Tri 1 Colors + Constructor->ColorsV[BATCH_2D_COLOR_INDEX(Quad, 0, 0)] = Color; + Constructor->ColorsV[BATCH_2D_COLOR_INDEX(Quad, 0, 1)] = Color; + Constructor->ColorsV[BATCH_2D_COLOR_INDEX(Quad, 0, 2)] = Color; + // Tri 2 Colors + Constructor->ColorsV[BATCH_2D_COLOR_INDEX(Quad, 1, 0)] = Color; + Constructor->ColorsV[BATCH_2D_COLOR_INDEX(Quad, 1, 1)] = Color; + Constructor->ColorsV[BATCH_2D_COLOR_INDEX(Quad, 1, 2)] = Color; +} + +internal void +PushQuad2DOnBatch (render_quad_batch_constructor* Constructor, v2 Min, v2 Max, v4 Color) +{ + PushQuad2DOnBatch(Constructor, v2{Min.x, Min.y}, v2{Max.x, Min.y}, v2{Max.x, Max.y}, v2{Min.x, Max.y}, + v2{0, 0}, v2{1, 1}, Color); +} + +internal void +PushLine2DOnBatch (render_quad_batch_constructor* Constructor, v2 P0, v2 P1, r32 Thickness, v4 Color) +{ + r32 HalfThickness = Thickness / 2.0f; + v2 Perpendicular = Normalize(PerpendicularCCW(P1 - P0)) * HalfThickness; + + PushQuad2DOnBatch(Constructor, P0 - Perpendicular, P1 - Perpendicular, P1 + Perpendicular, P0 + Perpendicular, + v2{0, 0}, v2{1, 1}, Color); +} + +// Commands +#define PushRenderCommand(buffer, type) (type*) PushRenderCommand_(buffer, RenderCommand_##type, sizeof(type) + sizeof(render_command_header)) + +internal u8* +PushRenderCommand_ (render_command_buffer* CommandBuffer, render_command_type CommandType, s32 CommandSize) +{ + Assert(CommandBuffer->CommandMemoryUsed + CommandSize <= CommandBuffer->CommandMemorySize); + + render_command_header* Header = (render_command_header*)(CommandBuffer->CommandMemory + CommandBuffer->CommandMemoryUsed); + Header->Type = CommandType; + + u8* Result = (u8*)(Header + 1); + CommandBuffer->CommandMemoryUsed += CommandSize; + + return Result; +} + +internal void +PushRenderPerspective (render_command_buffer* Buffer, s32 ViewWidth, s32 ViewHeight, camera Camera) +{ + render_command_set_render_mode* Command = PushRenderCommand(Buffer, render_command_set_render_mode); + Command->ModelView = GetCameraModelViewMatrix(Camera); + Command->Projection = GetCameraPerspectiveProjectionMatrix(Camera); + Command->ViewWidth; + Command->ViewHeight; + Command->UseDepthBuffer = true; +} + +internal void +PushRenderOrthographic (render_command_buffer* Buffer, s32 ViewWidth, s32 ViewHeight) +{ + render_command_set_render_mode* Command = PushRenderCommand(Buffer, render_command_set_render_mode); + Command->ModelView = m44{ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + }; + + r32 a = 2.0f / ViewWidth; + r32 b = 2.0f / ViewHeight; + Command->Projection = m44{ + a, 0, 0, 0, + 0, b, 0, 0, + 0, 0, 1, 0, + -1, -1, 0, 1 + }; + Command->ViewWidth; + Command->ViewHeight; + Command->UseDepthBuffer = false;; +} + +internal void +PushRenderClearScreen (render_command_buffer* Buffer) +{ + render_command_clear_screen* Command = PushRenderCommand(Buffer, render_command_clear_screen); +} + +internal render_quad_batch_constructor +PushRenderQuad2DBatch(render_command_buffer* Buffer, s32 QuadCount) +{ + render_quad_batch_constructor Result = {}; + + render_batch_command_quad_2d* Command = PushRenderCommand(Buffer, render_batch_command_quad_2d); + Command->QuadCount = QuadCount; + Command->DataSize = PushQuad2DBatch(Buffer, &Result, QuadCount, (u8*)(Command + 1)); + + return Result; +} + +internal void +PushRenderQuad2D (render_command_buffer* Buffer, v2 Min, v2 Max, v4 Color) +{ + render_quad_batch_constructor Batch = PushRenderQuad2DBatch(Buffer, 1); + PushQuad2DOnBatch(&Batch, Min, Max, Color); +} + +internal void +PushRenderLine2D (render_command_buffer* Buffer, v2 P0, v2 P1, r32 Thickness, v4 Color) +{ + render_quad_batch_constructor Batch = PushRenderQuad2DBatch(Buffer, 1); + PushLine2DOnBatch(&Batch, P0, P1, Thickness, Color); +} + + +internal render_quad_batch_constructor +PushRenderQuad3DBatch(render_command_buffer* Buffer, s32 QuadCount) +{ + render_quad_batch_constructor Result = {}; + + render_batch_command_quad_3d* Command = PushRenderCommand(Buffer, render_batch_command_quad_3d); + Command->QuadCount = QuadCount; + Command->DataSize = PushQuad3DBatch(Buffer, &Result, QuadCount, (u8*)(Command + 1)); + + return Result; +} + +internal void +PushRenderQuad3D (render_command_buffer* Buffer, v4 A, v4 B, v4 C, v4 D, v4 Color) +{ + render_quad_batch_constructor Batch = PushRenderQuad3DBatch(Buffer, 1); + PushQuad3DOnBatch(&Batch, A, B, C, D, Color); +} + +internal void +PushRenderCameraFacingQuad (render_command_buffer* Buffer, v4 Center, v2 Dimensions, v4 Color) +{ + // TODO(Peter): Turn this into an actual camera facing quad + v4 A = v4{Center.x - Dimensions.x, Center.y - Dimensions.y, Center.z, Center.w}; + v4 B = v4{Center.x + Dimensions.x, Center.y - Dimensions.y, Center.z, Center.w}; + v4 C = v4{Center.x + Dimensions.x, Center.y + Dimensions.y, Center.z, Center.w}; + v4 D = v4{Center.x - Dimensions.x, Center.y + Dimensions.y, Center.z, Center.w}; + + PushRenderQuad3D(Buffer, A, B, C, D, Color); +} + +internal render_quad_batch_constructor +PushRenderTexture2DBatch(render_command_buffer* Buffer, s32 QuadCount, + render_texture Texture) +{ + render_quad_batch_constructor Result = {}; + + render_batch_command_texture_2d* Command = PushRenderCommand(Buffer, render_batch_command_texture_2d); + Command->QuadCount = QuadCount; + Command->DataSize = PushQuad2DBatch(Buffer, &Result, QuadCount, (u8*)(Command + 1)); + Command->Texture = Texture; + + return Result; +} + +internal void +PushRenderTexture2D (render_command_buffer* Buffer, v2 Min, v2 Max, v4 Color, + v2 UVMin, v2 UVMax, + render_texture* Texture) +{ + render_quad_batch_constructor Batch = PushRenderTexture2DBatch(Buffer, 1, *Texture); + PushQuad2DOnBatch(&Batch, v2{Min.x, Min.y}, v2{Max.x, Min.y}, v2{Max.x, Max.y}, v2{Min.x, Max.y}, + UVMin, UVMax, Color); +} + +internal void +PushRenderBoundingBox2D (render_command_buffer* Buffer, v2 Min, v2 Max, r32 Thickness, v4 Color) +{ + render_quad_batch_constructor Batch = PushRenderQuad2DBatch(Buffer, 4); + PushQuad2DOnBatch(&Batch, Min, v2{Min.x + Thickness, Max.y}, Color); + PushQuad2DOnBatch(&Batch, v2{Min.x, Max.y - Thickness}, Max, Color); + PushQuad2DOnBatch(&Batch, v2{Max.x - Thickness, Min.y}, Max, Color); + PushQuad2DOnBatch(&Batch, Min, v2{Max.x, Min.y + Thickness}, Color); +} diff --git a/foldhaus_sacn.h b/foldhaus_sacn.h new file mode 100644 index 0000000..c0bb2d8 --- /dev/null +++ b/foldhaus_sacn.h @@ -0,0 +1,606 @@ +#define NETWORKINTID_INVALID -1 + +#define DEFAULT_STREAMING_ACN_PORT 5568 + +#define IP_ADDRESS_BYTES 16 +#define STARTCODE_DMX 0 + +/* + * a description of the address space being used + */ +#define PREAMBLE_SIZE_ADDR 0 +#define POSTAMBLE_SIZE_ADDR 2 +#define ACN_IDENTIFIER_ADDR 4 +#define ROOT_FLAGS_AND_LENGTH_ADDR 16 +#define ROOT_VECTOR_ADDR 18 +#define CID_ADDR 22 +#define FRAMING_FLAGS_AND_LENGTH_ADDR 38 +#define FRAMING_VECTOR_ADDR 40 +#define SOURCE_NAME_ADDR 44 +#define PRIORITY_ADDR 108 +#define RESERVED_ADDR 109 +#define SEQ_NUM_ADDR 111 +#define OPTIONS_ADDR 112 +#define UNIVERSE_ADDR 113 +#define DMP_FLAGS_AND_LENGTH_ADDR 115 +#define DMP_VECTOR_ADDR 117 +#define DMP_ADDRESS_AND_DATA_ADDR 118 +#define FIRST_PROPERTY_ADDRESS_ADDR 119 +#define ADDRESS_INC_ADDR 121 +#define PROP_COUNT_ADDR 123 +#define START_CODE_ADDR 125 +#define PROP_VALUES_ADDR (START_CODE_ADDR + 1) + +/* + * common sizes + */ +#define STREAM_HEADER_SIZE 126 +#define STREAM_BODY_SIZE 512 + +#define SOURCE_NAME_SIZE 64 +#define RLP_PREAMBLE_SIZE 16 +#define RLP_POSTAMBLE_SIZE 0 +#define ACN_IDENTIFIER_SIZE 12 + +/* + * data definitions + */ +#define ACN_IDENTIFIER "ASC-E1.17\0\0\0" +#define ROOT_VECTOR 4 +#define FRAMING_VECTOR 2 +#define DMP_VECTOR 2 +#define ADDRESS_AND_DATA_FORMAT 0xa1 +#define ADDRESS_INC 1 +#define DMP_FIRST_PROPERTY_ADDRESS_FORCE 0 +#define RESERVED_VALUE 0 + +//for support of the early draft +#define DRAFT_STREAM_HEADER_SIZE 90 +#define DRAFT_SOURCE_NAME_SIZE 32 + +//for support of the early draft +#define DRAFT_ROOT_VECTOR 3 + +const u32 VHD_MAXFLAGBYTES = 7; //The maximum amount of bytes used to pack the flags, len, and vector +const u32 VHD_MAXLEN = 0x0fffff; //The maximum packet length is 20 bytes long +const u32 VHD_MAXMINLENGTH = 4095; //The highest length that will fit in the "smallest" length pack + +//Defines for the VHD flags +const u8 VHD_L_FLAG = 0x80; +const u8 VHD_V_FLAG = 0x40; +const u8 VHD_H_FLAG = 0x20; +const u8 VHD_D_FLAG = 0x10; + +#define CID_Bytes 16 +struct cid +{ + u8 Bytes[CID_Bytes]; +}; + +struct sacn_universe +{ + s16 Universe; + + u8* StartPositionInSendBuffer; + s32 SizeInSendBuffer; + s32 OffsetInSendBuffer; + + s32 BeginPixelCopyFromOffset; + + platform_network_address_handle SendAddress; +}; + +struct sacn_send_buffer +{ + u8* Memory; + s32 Size; + sacn_send_buffer* Next; +}; + +struct sacn_universe_buffer +{ + sacn_universe* Universes; + s32 Used; + s32 Max; + sacn_universe_buffer* Next; +}; + +struct streaming_acn +{ + memory_arena Memory; + + // These get created and freed together + sacn_universe_buffer* UniverseBuffer; + sacn_send_buffer* SendBuffer; + + platform_socket_handle SendSocket; + cid CID; + + s32 SequenceIterator; +}; + + +// SACN Data Header Functions +internal void InitStreamHeader (u8* Buffer, s32 BufferSize, u16 SlotCount, u8 StartCode, u16 Universe, u8 Priority, u16 Reserved, u8 Options, const char* SourceName, cid CID); +internal void SetStreamHeaderSequence_ (u8* Buffer, u8 Sequence, b32 Draft); +internal void VHD_PackFlags_(u8* Buffer, b32 InheritVec, b32 InheritHead, b32 InheritData); +internal u8* VHD_PackLength_(u8* Buffer, u32 Length, b32 IncludeLength); +internal cid StringToCID_ (const char* String); + +#define CalculateSendBufferSize(UniverseCount) ((UniverseCount * (STREAM_HEADER_SIZE + STREAM_BODY_SIZE)) + sizeof(sacn_send_buffer)) +#define CalculateUniverseBufferSize(UniverseCount) ((UniverseCount * sizeof(sacn_universe)) + sizeof(sacn_universe_buffer)) + +// Utility + +struct sacn_pixel +{ + u8 R; + u8 G; + u8 B; +}; + +// + +internal sacn_universe* +SACNGetUniverse (s32 UniverseNumber, streaming_acn* SACN) +{ + sacn_universe* Result = 0; + + sacn_universe_buffer* Header = SACN->UniverseBuffer; + while (Header) + { + sacn_universe* Cursor = Header->Universes; + for (s32 i = 0; i < Header->Used; i++) + { + if (Cursor->Universe == UniverseNumber) + { + Result = Cursor; + break; + } + Cursor++; + } + Header = Header->Next; + } + + return Result; +} + +internal void +SACNPushSendBufferOnList (sacn_send_buffer* ListHead, sacn_send_buffer* NewBuffer) +{ + if (ListHead->Next) + { + SACNPushSendBufferOnList(ListHead->Next, NewBuffer); + } + else + { + ListHead->Next = NewBuffer; + } +} + +internal sacn_send_buffer* +SACNRemoveSendBufferFromList (sacn_send_buffer* List, sacn_send_buffer* Entry) +{ + sacn_send_buffer* ListHead = 0; + if (List != Entry && List->Next) + { + ListHead = SACNRemoveSendBufferFromList(List->Next, Entry); + } + else if (List == Entry) + { + ListHead = Entry->Next; + } + else + { + // NOTE(Peter): Trying to remove an entry from a list that doesn't contain it + InvalidCodePath; + } + return ListHead; +} + +internal void +SACNPushUniverseBufferOnList (sacn_universe_buffer* ListHead, sacn_universe_buffer* NewBuffer) +{ + if (ListHead->Next) + { + SACNPushUniverseBufferOnList(ListHead->Next, NewBuffer); + } + else + { + ListHead->Next = NewBuffer; + } +} + +internal sacn_universe_buffer* +SACNRemoveUniverseBufferFromList (sacn_universe_buffer* List, sacn_universe_buffer* Entry) +{ + sacn_universe_buffer* ListHead = 0; + if (List != Entry && List->Next) + { + ListHead = SACNRemoveUniverseBufferFromList(List->Next, Entry); + } + else if (List == Entry) + { + ListHead = Entry->Next; + } + else + { + // NOTE(Peter): Trying to remove an entry from a list that doesn't contain it + InvalidCodePath; + } + return ListHead; +} + +struct sacn_add_universes_result +{ + sacn_send_buffer* NewSendBuffer; + sacn_universe_buffer* NewUniverseBuffer; +}; +internal sacn_add_universes_result +SACNAddUniverses(s32* Universes, s32 UniversesLength, streaming_acn* SACN, context Context) +{ + sacn_add_universes_result Result = {}; + + // Determine which universes are already registered and not to be readded. + // NOTE(Peter): This might create funky behaviour if two sculptures start sending data to the same universe + // but I'm not sure its incorrect behavior. I think, eventually, we will want to spit out a report from + // this function that says what universes were duplicated. We might want to display this information to the user + // in a way that they don't have to exit out of every single time they load the software. Not sure + s32 UniversesToAdd = 0; + for (s32 i = 0; i < UniversesLength; i++) + { + sacn_universe* UniverseExists = SACNGetUniverse(Universes[i], SACN); + if (UniverseExists) + { + Universes[i] = -1; + } + else + { + UniversesToAdd++; + } + } + + // Push On New Send and Universe Buffers + s32 SendBufferSize = CalculateSendBufferSize(UniversesToAdd); + u8* SendBufferMemory = PushArray(&SACN->Memory, u8, SendBufferSize); + sacn_send_buffer* SendBufferHeader = (sacn_send_buffer*)SendBufferMemory; + SendBufferHeader->Memory = (u8*)(SendBufferHeader + 1); + SendBufferHeader->Size = SendBufferSize - sizeof(sacn_send_buffer); + SendBufferHeader->Next = 0; + if (SACN->SendBuffer) + { + SACNPushSendBufferOnList(SACN->SendBuffer, SendBufferHeader); + } + else + { + SACN->SendBuffer = SendBufferHeader; + } + + s32 UniverseBufferSize = CalculateUniverseBufferSize(UniversesToAdd); + u8* UniverseBufferMemory = PushArray(&SACN->Memory, u8, UniverseBufferSize); + sacn_universe_buffer* UniverseBufferHeader = (sacn_universe_buffer*)UniverseBufferMemory; + UniverseBufferHeader->Universes = (sacn_universe*)(UniverseBufferHeader + 1); + UniverseBufferHeader->Used = 0; + UniverseBufferHeader->Max = UniversesToAdd; + if (SACN->UniverseBuffer) + { + SACNPushUniverseBufferOnList(SACN->UniverseBuffer, UniverseBufferHeader); + } + else + { + SACN->UniverseBuffer = UniverseBufferHeader; + } + + // Add each of the valid universes + for (s32 j = 0; j < UniversesLength; j++) + { + if (Universes[j] >= 0) + { + Assert(UniverseBufferHeader->Used < UniverseBufferHeader->Max); + s32 Index = UniverseBufferHeader->Used++; + s32 UniverseID = Universes[j]; + + UniverseBufferHeader->Universes[Index].Universe = UniverseID; + UniverseBufferHeader->Universes[Index].SizeInSendBuffer = STREAM_HEADER_SIZE + STREAM_BODY_SIZE; + UniverseBufferHeader->Universes[Index].BeginPixelCopyFromOffset = -1; + + // Configure how the universe looks into the pixel color buffer + s32 SendBufferOffset = (Index * (STREAM_HEADER_SIZE + STREAM_BODY_SIZE)); + u8* SendBufferStartPosition = SendBufferHeader->Memory + SendBufferOffset; + UniverseBufferHeader->Universes[Index].OffsetInSendBuffer = SendBufferOffset; + UniverseBufferHeader->Universes[Index].StartPositionInSendBuffer = SendBufferStartPosition; + + // Set up the Send Address + u8 MulticastAddressBuffer[IP_ADDRESS_BYTES]; + GSMemSet(MulticastAddressBuffer, 0, IP_ADDRESS_BYTES); + MulticastAddressBuffer[12] = 239; + MulticastAddressBuffer[13] = 255; + PackB2(MulticastAddressBuffer + 14, UniverseID); + u_long V4Address = (u_long)UpackB4(MulticastAddressBuffer + IP_ADDRESS_BYTES - sizeof(u32)); + + GSMemSet(&UniverseBufferHeader->Universes[Index].SendAddress, 0, sizeof(sockaddr_in)); + UniverseBufferHeader->Universes[Index].SendAddress = Context.PlatformGetSendAddress( + AF_INET, + HostToNetU16(DEFAULT_STREAMING_ACN_PORT), + HostToNetU32(V4Address)); + +#if 0 // Old Net Code + UniverseBufferHeader->Universes[Index].SendAddress.sin_family = AF_INET; + UniverseBufferHeader->Universes[Index].SendAddress.sin_port = HostToNetU16(DEFAULT_STREAMING_ACN_PORT); + UniverseBufferHeader->Universes[Index].SendAddress.sin_addr.s_addr = HostToNetU32(V4Address); +#endif + + s32 SlotCount = 512; + InitStreamHeader(UniverseBufferHeader->Universes[Index].StartPositionInSendBuffer, + UniverseBufferHeader->Universes[Index].SizeInSendBuffer, + SlotCount, + STARTCODE_DMX, + UniverseID, + 0, + 0, // Reserved + 0, // Options + "Source 1", + SACN->CID + ); + } + } + + Result.NewUniverseBuffer = UniverseBufferHeader; + Result.NewSendBuffer= SendBufferHeader; + return Result; +} + +internal void +SACNRemoveUniverseAndSendBuffer(streaming_acn* SACN, sacn_universe_buffer* Universes, sacn_send_buffer* SendBuffer) +{ + SACN->UniverseBuffer = SACNRemoveUniverseBufferFromList(SACN->UniverseBuffer, Universes); + SACN->SendBuffer = SACNRemoveSendBufferFromList(SACN->SendBuffer, SendBuffer); +} + +internal streaming_acn +InitializeSACN (platform_alloc* PlatformAlloc, context Context) +{ + streaming_acn SACN = {}; + + InitMemoryArena(&SACN.Memory, 0, 0, PlatformAlloc); + + SACN.SendSocket = Context.PlatformGetSocketHandle(AF_INET, SOCK_DGRAM, 0); + int Multicast_TimeToLive = 20; + int Error = Context.PlatformSetSocketOption(SACN.SendSocket, IPPROTO_IP, IP_MULTICAST_TTL, + (const char*)(&Multicast_TimeToLive), sizeof(Multicast_TimeToLive)); + SACN.CID = StringToCID_ ("{67F9D986-544E-4abb-8986-D5F79382586C}"); + + SACN.UniverseBuffer = 0; + SACN.SendBuffer = 0; + + return SACN; +} + +internal void +SACNSendDataToUniverse (streaming_acn* SACN, sacn_universe* Universe, platform_send_to* PlatformSendTo) +{ + //DEBUG_TRACK_FUNCTION; + + u8* StartPositionInSendBuffer = (u8*)Universe->StartPositionInSendBuffer; + SetStreamHeaderSequence_(StartPositionInSendBuffer, SACN->SequenceIterator, false); + + PlatformSendTo(SACN->SendSocket, Universe->SendAddress, (const char*)StartPositionInSendBuffer, Universe->SizeInSendBuffer, 0); +#if 0 // Old Network Code + // TODO(Peter): HUGE NOTE!!!!!!!! + // This needs to be put on a separate thread. The sendto call is really slowing us down. + s32 LengthSent = sendto(SACN->SendSocket, (const char*)StartPositionInSendBuffer, Universe->SizeInSendBuffer, + 0, (sockaddr*)(&Universe->SendAddress), sizeof(sockaddr_in)); + + if (LengthSent == SOCKET_ERROR) + { + s32 LastSocketError = WSAGetLastError(); + InvalidCodePath; + } +#endif +} + +internal void +SACNCleanup(streaming_acn* SACN, context Context) +{ + Context.PlatformCloseSocket(SACN->SendSocket); +} + + +/////////////////////////////////////////////// +// +// SACN Data Header Functions +// +/////////////////////////////////////////////// + +internal void +InitStreamHeader (u8* Buffer, s32 BufferSize, + u16 SlotCount, + u8 StartCode, + u16 Universe, + u8 Priority, + u16 Reserved, + u8 Options, + const char* SourceName, + cid CID + ) +{ + + u8* Cursor = Buffer; + + // Preamble Size + Cursor = PackB2(Cursor, RLP_PREAMBLE_SIZE); + Cursor = PackB2(Cursor, RLP_POSTAMBLE_SIZE); + + memcpy(Cursor, ACN_IDENTIFIER, ACN_IDENTIFIER_SIZE); + Cursor += ACN_IDENTIFIER_SIZE; + + // TODO(Peter): If you never use this anywhere else, go back and remove the parameters + VHD_PackFlags_(Cursor, false, false, false); + Cursor = VHD_PackLength_(Cursor, + STREAM_HEADER_SIZE - RLP_PREAMBLE_SIZE + SlotCount, + false); + + // root vector + Cursor = PackB4(Cursor, ROOT_VECTOR); + + // CID Pack + for (s32 i = 0; i < CID_Bytes; i++) + { + *Cursor++ = CID.Bytes[i]; + } + + VHD_PackFlags_(Cursor, false, false, false); + Cursor = VHD_PackLength_(Cursor, + STREAM_HEADER_SIZE - FRAMING_FLAGS_AND_LENGTH_ADDR + SlotCount, + false); + + // framing vector + Cursor = PackB4(Cursor, FRAMING_VECTOR); + + // framing source name + strncpy((char*)Cursor, SourceName, SOURCE_NAME_SIZE); + Cursor[SOURCE_NAME_SIZE - 1] = '\0'; + Cursor += SOURCE_NAME_SIZE; + + // priority + Cursor = PackB1(Cursor, Priority); + + // reserved + Cursor = PackB2(Cursor, Reserved); + + // Sequence # is always set to 0/NONE at the beginning, but it is incremented when sending data + Cursor = PackB1(Cursor, 0); + + // Options + Cursor = PackB1(Cursor, Options); + + // Universe + Cursor = PackB2(Cursor, Universe); + + VHD_PackFlags_(Cursor, false, false, false); + Cursor = VHD_PackLength_(Cursor, + STREAM_HEADER_SIZE - DMP_FLAGS_AND_LENGTH_ADDR + SlotCount, + false); + + // DMP Vector + Cursor = PackB1(Cursor, DMP_VECTOR); + + // DMP Address and data type + Cursor = PackB1(Cursor, ADDRESS_AND_DATA_FORMAT); + + // DMP first property address + Cursor = PackB2(Cursor, 0); + + // DMP Address Increment + Cursor = PackB2(Cursor, ADDRESS_INC); + + // Property Value Count -- Includes one byte for start code + Cursor = PackB2(Cursor, SlotCount + 1); + + Cursor = PackB1(Cursor, StartCode); + + s32 DiffSize = Cursor - Buffer; + if (Cursor - Buffer != STREAM_HEADER_SIZE) + { + InvalidCodePath; + } +} + +internal void +SetStreamHeaderSequence_ (u8* Buffer, u8 Sequence, b32 Draft) +{ + DEBUG_TRACK_FUNCTION; + PackB1(Buffer + SEQ_NUM_ADDR, Sequence); +} + +internal void +VHD_PackFlags_(u8* Buffer, b32 InheritVec, b32 InheritHead, b32 InheritData) +{ + u8* Cursor = Buffer; + u8 NewByte = UpackB1(Cursor) & 0x8f; + + if (!InheritVec) { NewByte |= VHD_V_FLAG; } + if (!InheritHead) { NewByte |= VHD_H_FLAG; } + if (!InheritData) { NewByte |= VHD_D_FLAG; } + + PackB1(Cursor, NewByte); +} + +internal u8* +VHD_PackLength_(u8* Buffer, u32 Length, b32 IncludeLength) +{ + u8* Cursor = Buffer; + u32 AdjustedLength = Length; + if (IncludeLength) + { + if (Length + 1 > VHD_MAXMINLENGTH) + { + AdjustedLength += 2; + } + else + { + AdjustedLength += 1; + } + } + + // Mask out the length bits to keep flags intact + u8 NewByte = UpackB1(Cursor) & 0x70; + if (AdjustedLength > VHD_MAXMINLENGTH) + { + NewByte |= VHD_L_FLAG; + } + + u8 PackBuffer[4]; + PackB4(PackBuffer, AdjustedLength); + if (AdjustedLength <= VHD_MAXMINLENGTH) + { + NewByte |= (PackBuffer[2] & 0x0f); + Cursor = PackB1(Cursor, NewByte); + Cursor = PackB1(Cursor, PackBuffer[3]); + } + else + { + NewByte |= (PackBuffer[1] & 0x0f); + Cursor = PackB1(Cursor, PackBuffer[2]); + Cursor = PackB1(Cursor, PackBuffer[3]); + } + + return Cursor; +} + +internal cid +StringToCID_ (const char* String) +{ + cid Result = {}; + + const char* Src = String; + u8* Dest = &Result.Bytes[0]; + b32 FirstNibble = true; + + while(*Src && (Dest - &Result.Bytes[0] < CID_Bytes)) + { + u8 Offset = 0; + if ((*Src >= 0x30) && (*Src <= 0x39)){ Offset = 0x30; } + else if ((*Src >= 0x41) && (*Src <= 0x46)) { Offset = 0x37; } + else if ((*Src >= 0x61) && (*Src <= 0x66)) { Offset = 0x66; } + + if (Offset != 0) + { + if (FirstNibble) + { + *Dest = (u8)(*Src - Offset); + *Dest <<= 4; + FirstNibble = false; + } + else + { + *Dest |= (*Src - Offset); + Dest++; + FirstNibble = true; + } + } + Src++; + } + + return Result; +} diff --git a/foldhaus_sacn_view.cpp b/foldhaus_sacn_view.cpp new file mode 100644 index 0000000..d7ab536 --- /dev/null +++ b/foldhaus_sacn_view.cpp @@ -0,0 +1,33 @@ +internal void +DrawSACNUniversePixels (render_command_buffer* RenderBuffer, sacn_universe* ToDraw, + v2 TopLeft, v2 Dimension) +{ + Assert(ToDraw); + + s32 PixelsPerRow = 21; + r32 PixelDim = Dimension.x / PixelsPerRow; + v2 PixelSize = v2{PixelDim, PixelDim}; + + v2 PixelRegister = TopLeft; + v4 DisplayColor = {0, 0, 0, 1}; + + s32 PixelsToDraw = ToDraw->SizeInSendBuffer - STREAM_HEADER_SIZE; + render_quad_batch_constructor BatchConstructor = PushRenderQuad2DBatch(RenderBuffer, PixelsToDraw); + + u8* ColorCursor = (u8*)ToDraw->StartPositionInSendBuffer + STREAM_HEADER_SIZE; + s32 PixelsDrawn = 0; + for (s32 i = 0; i < PixelsToDraw; i++) + { + PixelRegister.x = TopLeft.x + (PixelsDrawn % PixelsPerRow) * PixelDim; + PixelRegister.y = TopLeft.y - (PixelsDrawn / PixelsPerRow) * PixelDim; + + r32 Value = *ColorCursor++ / 255.f; + DisplayColor.r = Value; + DisplayColor.g = Value; + DisplayColor.b = Value; + + PushQuad2DOnBatch(&BatchConstructor, PixelRegister, PixelRegister + PixelSize, DisplayColor); + + ++PixelsDrawn; + } +} \ No newline at end of file diff --git a/foldhaus_util_radialumia_file_converter.cpp b/foldhaus_util_radialumia_file_converter.cpp new file mode 100644 index 0000000..be1c22e --- /dev/null +++ b/foldhaus_util_radialumia_file_converter.cpp @@ -0,0 +1,367 @@ +#define DEBUG +#define DEBUG_TRACK_SCOPE(name) + +#include +#include +#include + +#include "gs/gs_language.h" +#include "gs/gs_string.h" +#include "../meta/gs_meta_lexer.h" +#include "gs/gs_vector.h" + +#define STRING_BUFFER_SIZE 512 +struct string_buffer +{ + char* Memory; + s32 Size; + string_buffer* Next; +}; + +struct string_writer +{ + char* Cursor; + s32 UsedInString; + string_buffer* Buffer; +}; + +internal string_buffer* +GrowStringBuffer (string_buffer* Buffer) +{ + string_buffer* Result; + if (Buffer->Next) + { + Result = GrowStringBuffer(Buffer->Next); + } + else + { + Result = (string_buffer*)malloc(sizeof(string_buffer)); + Result->Memory = (char*)malloc(sizeof(char) * STRING_BUFFER_SIZE); + memset(Result->Memory, 0, STRING_BUFFER_SIZE); + Result->Size = STRING_BUFFER_SIZE; + Result->Next = 0; + + Buffer->Next = Result; + } + return Result; +} + +internal void +WriteString(string_writer* Writer, char* String, s32 Length) +{ + char* Src = String; + char* Dst = Writer->Cursor; + s32 LengthWritten = 0; + + while (*Src && Writer->UsedInString < Writer->Buffer->Size &&LengthWritten < Length) + { + LengthWritten++; + *Dst++ = *Src++; + Writer->UsedInString++; + } + + Writer->Cursor = Dst; + + if (*Src && Writer->UsedInString == Writer->Buffer->Size) + { + *(Dst - 1) = 0; // Null terminate the buffer + Writer->Buffer = GrowStringBuffer(Writer->Buffer); + Writer->Cursor = Writer->Buffer->Memory; + Writer->UsedInString = 0; + WriteString(Writer, (Src - 1), (Length - LengthWritten) + 1); + } +} + +struct control_box_pairs +{ + s32 Start; + s32 End; +}; + +struct extra_strips +{ + s32 BoxID; + v3 Start; + v3 End; +}; + +struct control_box +{ + s32 ID; + s32 Neighbors[6]; + r32 X; + r32 Y; + r32 Z; + char* Address; +}; + +int main(int ArgCount, char* Args[]) +{ + FILE* OldFilePtr = fopen("F:/data/radia_old.fold", "r"); + if (!OldFilePtr) + { + InvalidCodePath; + } + + fseek(OldFilePtr, 0, SEEK_END); + s32 OldFileSize = ftell(OldFilePtr); + fseek(OldFilePtr, 0, SEEK_SET); + + char* OldFile = (char*)malloc(sizeof(char) * OldFileSize); + fread(OldFile, 1, OldFileSize, OldFilePtr); + + fclose(OldFilePtr); + + s32 ControlBoxPairsUsed = 0; + control_box_pairs* ControlBoxPairs = (control_box_pairs*)malloc(sizeof(control_box_pairs) * 512); + s32 ControlBoxesUsed = 0; + control_box* ControlBoxes = (control_box*)malloc(sizeof(control_box) * 64); + s32 ExtraStripsUsed = 0; + extra_strips* ExtraStrips = (extra_strips*)malloc(sizeof(extra_strips) * (42 * 4)); + + control_box_pairs* NextControlBoxPair = &ControlBoxPairs[0]; + control_box* NextControlBox = &ControlBoxes[0]; + extra_strips* NextStrip = &ExtraStrips[0]; + + tokenizer Tokenizer = {}; + Tokenizer.At = OldFile; + while(*Tokenizer.At) + { + // Parse a Control Box + memset(NextControlBox->Neighbors, -1, 6); + s32 NeighborsAdded = 0; + + control_box_pairs* StartPair = NextControlBoxPair; + s32 PairsCount = 0; + + if (StringsEqual(Tokenizer.At, "EOF")) + { + break; + } + + EatToCharacterInclusive(&Tokenizer, '{'); + EatWhitespace(&Tokenizer); + Assert(StringsEqual(Tokenizer.At, "neighbors: [")); + Tokenizer.At += StringLength("neighbors: ["); + + // Parse Neighbors + while(*Tokenizer.At && *Tokenizer.At != ']') + { + s32 NeighborIndex = ParseSignedInt(Tokenizer.At); + NextControlBox->Neighbors[NeighborsAdded++] = NeighborIndex; + NextControlBoxPair->End = NeighborIndex; + NextControlBoxPair++; + PairsCount++; + ControlBoxPairsUsed++; + + EatNumber(&Tokenizer); + if (*Tokenizer.At == ']') + { + Tokenizer.At += 2; // Eat past "];" + break; + } + else + { + EatToCharacterInclusive(&Tokenizer, ','); + EatWhitespace(&Tokenizer); + } + } + + EatWhitespace(&Tokenizer); + + //Parse IP + Assert(StringsEqual(Tokenizer.At, "ip: ")); + Tokenizer.At += StringLength("ip: "); + NextControlBox->Address = (char*)malloc(sizeof(char) * 13); + memcpy(NextControlBox->Address, Tokenizer.At, 13); + Tokenizer.At += 13; + Tokenizer.At++; // Eat past ";" + + // Parse X + EatWhitespace(&Tokenizer); + Assert(StringsEqual(Tokenizer.At, "x: ")); + Tokenizer.At += StringLength("x: "); + NextControlBox->X = ParseFloat(Tokenizer.At); + EatToCharacterInclusive(&Tokenizer, ';'); + // Parse Y + EatWhitespace(&Tokenizer); + Assert(StringsEqual(Tokenizer.At, "y: ")); + Tokenizer.At += StringLength("y: "); + NextControlBox->Y = ParseFloat(Tokenizer.At); + EatToCharacterInclusive(&Tokenizer, ';'); + // Parse Z + EatWhitespace(&Tokenizer); + Assert(StringsEqual(Tokenizer.At, "z: ")); + Tokenizer.At += StringLength("z: "); + NextControlBox->Z = ParseFloat(Tokenizer.At); + EatToCharacterInclusive(&Tokenizer, ';'); + + // Parse ID + EatWhitespace(&Tokenizer); + Assert(StringsEqual(Tokenizer.At, "id: ")); + Tokenizer.At += StringLength("id: "); + NextControlBox->ID = ParseSignedInt(Tokenizer.At); + EatToCharacterInclusive(&Tokenizer, ';'); + + control_box_pairs* PairCursor = StartPair; + for(s32 i = 0; i < PairsCount; i++) + { + PairCursor->Start = NextControlBox->ID; + PairCursor++; + } + + NextControlBox++; + ControlBoxesUsed++; + + EatToCharacterInclusive(&Tokenizer, ';'); + EatWhitespace(&Tokenizer); + } + + // Add Spikes + + +#define SPIKE_LEDS 346 + for (s32 sp = 0; sp < ControlBoxesUsed; sp++) + { + control_box* Box = &ControlBoxes[sp]; + + control_box* NeighborA = &ControlBoxes[Box->Neighbors[0]]; + control_box* NeighborB = &ControlBoxes[Box->Neighbors[1]]; + + v3 SpikeCenter = v3{Box->X, Box->Y, Box->Z}; + v3 StripPitch = Normalize(SpikeCenter) * ((2.f/8.f) / SPIKE_LEDS); + v3 ToNA = Normalize(v3{NeighborA->X, NeighborA->Y, NeighborA->Z} - SpikeCenter); + v3 ToNB = Normalize(v3{NeighborB->X, NeighborB->Y, NeighborB->Z} - SpikeCenter); + + v3 StripAOutStart = SpikeCenter + (ToNA * .01f); + v3 StripAOutEnd = StripAOutStart + (StripPitch * SPIKE_LEDS); + + v3 StripBOutStart = SpikeCenter + (ToNB * .01f); + v3 StripBOutEnd = StripBOutStart + (StripPitch * SPIKE_LEDS); + + v3 StripAInStart = StripAOutEnd - (ToNA * .02f); + v3 StripAInEnd = StripAOutStart - (ToNA * .02f); + + v3 StripBInStart = StripBOutEnd - (ToNA * .02f); + v3 StripBInEnd = StripBOutStart - (ToNA * .02f); + + NextStrip->BoxID = Box->ID; + NextStrip->Start = StripAOutStart; + NextStrip->End = StripAOutEnd; + NextStrip++; + ExtraStripsUsed++; + + NextStrip->BoxID = Box->ID; + NextStrip->Start = StripAInStart; + NextStrip->End = StripAInEnd; + NextStrip++; + ExtraStripsUsed++; + + NextStrip->BoxID = Box->ID; + NextStrip->Start = StripBOutStart; + NextStrip->End = StripBOutEnd; + NextStrip++; + ExtraStripsUsed++; + + NextStrip->BoxID = Box->ID; + NextStrip->Start = StripBInStart; + NextStrip->End = StripBInEnd; + NextStrip++; + ExtraStripsUsed++; + } + + + string_buffer OutputFileBuffer = {}; + OutputFileBuffer.Memory = (char*)malloc(sizeof(char) * STRING_BUFFER_SIZE); + OutputFileBuffer.Size = STRING_BUFFER_SIZE; + OutputFileBuffer.Next = 0; + + string_writer RefWriter = {}; + RefWriter.Cursor = OutputFileBuffer.Memory; + RefWriter.UsedInString = 0; + RefWriter.Buffer = &OutputFileBuffer; + string_writer* Writer = &RefWriter; + + char StringBuffer[512]; + s32 Len = 0; + + Len = sprintf_s(StringBuffer, 512, "control_box_count %d\n", ControlBoxesUsed); + WriteString(Writer, StringBuffer, Len); + Len = sprintf_s(StringBuffer, 512, "led_strip_count %d\n\n", ControlBoxPairsUsed); + WriteString(Writer, StringBuffer, Len); + + for (s32 c = 0; c < ControlBoxesUsed; c++) + { + control_box* Box = ControlBoxes + c; + Len = sprintf_s(StringBuffer, 512, + "control_box { %d, \"%s\", (%f, %f, %f) }\n", + Box->ID, Box->Address, + Box->X, Box->Y, Box->Z); + WriteString(Writer, StringBuffer, Len); + } + + WriteString(Writer, "\n", 1); + +#define UNIVERSES_PER_BOX 25 + s32 UniversesPerBox[64]; + for (s32 u = 0; u < 64; u++) + { + UniversesPerBox[u] = UNIVERSES_PER_BOX * u; + } + + char LEDStripFormatString[] = "led_strip { %d, %d, %d, INTERPOLATE_POINTS, (%f, %f, %f), (%f, %f, %f), 144 } \n"; + for (s32 s = 0; s < ControlBoxPairsUsed; s++) + { + control_box_pairs* Pair = ControlBoxPairs + s; + + s32 Universe = UniversesPerBox[Pair->Start]; + UniversesPerBox[Pair->Start]++; + + r32 StartX = ControlBoxes[Pair->Start].X; + r32 StartY = ControlBoxes[Pair->Start].Y; + r32 StartZ = ControlBoxes[Pair->Start].Z; + + r32 EndX = ControlBoxes[Pair->End].X; + r32 EndY = ControlBoxes[Pair->End].Y; + r32 EndZ = ControlBoxes[Pair->End].Z; + + Len = sprintf_s(StringBuffer, 512, + LEDStripFormatString, + Pair->Start, Universe, 0, + StartX, StartY, StartZ, + EndX, EndY, EndZ); + WriteString(Writer, StringBuffer, Len); + } + + WriteString(Writer, "\n", 1); + + for (s32 sp = 0; sp < ExtraStripsUsed; sp++) + { + extra_strips* Strip = ExtraStrips + sp; + + s32 Universe = UniversesPerBox[Strip->BoxID]; + UniversesPerBox[Strip->BoxID]++; + + Len = sprintf_s(StringBuffer, 512, + LEDStripFormatString, + Strip->BoxID, Universe, 0, + Strip->Start.x, Strip->Start.y, Strip->Start.z, + Strip->End.x, Strip->End.y, Strip->End.z); + WriteString(Writer, StringBuffer, Len); + } + + WriteString(Writer, "END_OF_ASSEMBLY_FILE", StringLength("END_OF_ASSEMBLY_FILE")); + + *Writer->Cursor = 0; + + FILE* OutputFile = fopen("F:/data/radialumia.fold", "w"); + string_buffer* BufferCursor = &OutputFileBuffer; + while(BufferCursor) + { + fprintf(OutputFile, BufferCursor->Memory); + BufferCursor = BufferCursor->Next; + } + fclose(OutputFile); + + return 0; +} \ No newline at end of file diff --git a/generated/foldhaus_nodes_generated.cpp b/generated/foldhaus_nodes_generated.cpp new file mode 100644 index 0000000..a99e932 --- /dev/null +++ b/generated/foldhaus_nodes_generated.cpp @@ -0,0 +1,68 @@ +enum node_type +{ +NodeType_OutputNode, +NodeType_MultiplyNodeProc, +NodeType_AddNodeProc, +NodeType_FloatValueProc, +NodeType_SolidColorProc, +NodeType_MultiplyPatterns, +NodeType_VerticalColorFadeProc, +NodeType_Count, +}; + +node_struct_member MemberList_multiply_data[] = { +{ MemberType_r32, "A", (u64)&((multiply_data*)0)->A, IsInputMember }, +{ MemberType_r32, "B", (u64)&((multiply_data*)0)->B, IsInputMember }, +{ MemberType_r32, "Result", (u64)&((multiply_data*)0)->Result, IsOutputMember}, +}; + +node_struct_member MemberList_add_data[] = { +{ MemberType_v4, "A", (u64)&((add_data*)0)->A, IsInputMember }, +{ MemberType_v4, "B", (u64)&((add_data*)0)->B, IsInputMember }, +{ MemberType_v4, "Result", (u64)&((add_data*)0)->Result, IsOutputMember}, +}; + +node_struct_member MemberList_float_value_data[] = { +{ MemberType_r32, "Value", (u64)&((float_value_data*)0)->Value, IsInputMember }, +{ MemberType_r32, "Result", (u64)&((float_value_data*)0)->Result, IsOutputMember}, +}; + +node_struct_member MemberList_solid_color_data[] = { +{ MemberType_v4, "Color", (u64)&((solid_color_data*)0)->Color, IsInputMember }, +{ MemberType_NODE_COLOR_BUFFER, "LEDs", (u64)&((solid_color_data*)0)->LEDs, IsInputMember | IsOutputMember}, +}; + +node_struct_member MemberList_multiply_patterns_data[] = { +{ MemberType_NODE_COLOR_BUFFER, "ALEDs", (u64)&((multiply_patterns_data*)0)->ALEDs, IsInputMember }, +{ MemberType_NODE_COLOR_BUFFER, "BLEDs", (u64)&((multiply_patterns_data*)0)->BLEDs, IsInputMember }, +{ MemberType_NODE_COLOR_BUFFER, "ResultLEDs", (u64)&((multiply_patterns_data*)0)->ResultLEDs, IsOutputMember}, +}; + +node_struct_member MemberList_vertical_color_fade_data[] = { +{ MemberType_v4, "Color", (u64)&((vertical_color_fade_data*)0)->Color, IsInputMember }, +{ MemberType_r32, "Min", (u64)&((vertical_color_fade_data*)0)->Min, IsInputMember }, +{ MemberType_r32, "Max", (u64)&((vertical_color_fade_data*)0)->Max, IsInputMember }, +}; + +node_specification NodeSpecifications[] = { +{ NodeType_MultiplyNodeProc, "MultiplyNodeProc", 16, MemberList_multiply_data, 12, 3, false}, +{ NodeType_AddNodeProc, "AddNodeProc", 11, MemberList_add_data, 48, 3, false}, +{ NodeType_FloatValueProc, "FloatValueProc", 14, MemberList_float_value_data, 8, 2, false}, +{ NodeType_SolidColorProc, "SolidColorProc", 14, MemberList_solid_color_data, 36, 2, false}, +{ NodeType_MultiplyPatterns, "MultiplyPatterns", 16, MemberList_multiply_patterns_data, 60, 3, false}, +{ NodeType_VerticalColorFadeProc, "VerticalColorFadeProc", 21, MemberList_vertical_color_fade_data, 24, 3, true}, +}; +s32 NodeSpecificationsCount = 6; + +internal void CallNodeProc(interface_node* Node, u8* Data, led* LEDs, sacn_pixel* Colors, s32 LEDCount) +{ +switch (Node->Type) +{ +case NodeType_MultiplyNodeProc: { MultiplyNodeProc((multiply_data*)Data); } break; +case NodeType_AddNodeProc: { AddNodeProc((add_data*)Data); } break; +case NodeType_FloatValueProc: { FloatValueProc((float_value_data*)Data); } break; +case NodeType_SolidColorProc: { SolidColorProc((solid_color_data*)Data); } break; +case NodeType_MultiplyPatterns: { MultiplyPatterns((multiply_patterns_data*)Data); } break; +case NodeType_VerticalColorFadeProc: { VerticalColorFadeProc((vertical_color_fade_data*)Data, LEDs, Colors, LEDCount); } break; +} +} diff --git a/generated/foldhaus_nodes_generated.h b/generated/foldhaus_nodes_generated.h new file mode 100644 index 0000000..fb892e7 --- /dev/null +++ b/generated/foldhaus_nodes_generated.h @@ -0,0 +1,22 @@ +#define GENERATED_NODE_TYPES \ +NodeType_MultiplyNodeProc, \ +NodeType_AddNodeProc, \ +NodeType_FloatValueProc, \ +NodeType_SolidColorProc, \ +NodeType_VerticalColorFadeProc + +#define GENERATED_NODE_SPECS \ +{ NodeType_MultiplyNodeProc, "MultiplyNodeProc", 16, MemberList_multiply_data, 12, 3, 2, 1, false}, \ +{ NodeType_AddNodeProc, "AddNodeProc", 11, MemberList_add_data, 48, 3, 2, 1, false}, \ +{ NodeType_FloatValueProc, "FloatValueProc", 14, MemberList_float_value_data, 8, 2, 1, 1, false}, \ +{ NodeType_SolidColorProc, "SolidColorProc", 14, MemberList_solid_color_data, 16, 1, 1, 0, true}, \ +{ NodeType_VerticalColorFadeProc, "VerticalColorFadeProc", 21, MemberList_vertical_color_fade_data, 24, 3, 3, 0, true} +#define GENERATED_NODE_SPECS_COUNT 5 + +#define EVALUATE_GENERATED_NODES \ +case NodeType_MultiplyNodeProc: { MultiplyNodeProc((multiply_data*)NodeData); } break; \ +case NodeType_AddNodeProc: { AddNodeProc((add_data*)NodeData); } break; \ +case NodeType_FloatValueProc: { FloatValueProc((float_value_data*)NodeData); } break; \ +case NodeType_SolidColorProc: { SolidColorProc((solid_color_data*)NodeData, LEDs, Colors, LEDCount); } break; \ +case NodeType_VerticalColorFadeProc: { VerticalColorFadeProc((vertical_color_fade_data*)NodeData, LEDs, Colors, LEDCount); } break; \ + diff --git a/gs_font.h b/gs_font.h new file mode 100644 index 0000000..95f3e70 --- /dev/null +++ b/gs_font.h @@ -0,0 +1,6 @@ +#ifndef GS_FONT_H + + + +#define GS_FONT_H +#endif \ No newline at end of file diff --git a/gs_input.h b/gs_input.h new file mode 100644 index 0000000..df008e5 --- /dev/null +++ b/gs_input.h @@ -0,0 +1,4 @@ +#ifndef GS_INPUT_H + +#define GS_INPUT_H +#endif diff --git a/gs_language.h b/gs_language.h new file mode 100644 index 0000000..a8319af --- /dev/null +++ b/gs_language.h @@ -0,0 +1,400 @@ +#ifndef GS_LANGUAGE_H + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) +#include +#include + +// TODO(Peter): Get rid of stdio and math.h +#include +#include + +#elif defined(__APPLE__) && defined(__MAC__) +// TODO(Peter): + +#else // Std lib +#include + +#endif + +#define internal static +#define local_persist static +#define global_variable static + + +#if !defined(GS_TYPES) + +#define GSINT64(s) (s) ## L +#define GSUINT64(s) (s) ## UL + +typedef signed char b8; +typedef short int b16; +typedef int b32; +typedef long long int b64; + +typedef unsigned char u8; +typedef unsigned short int u16; +typedef unsigned int u32; +typedef unsigned long long int u64; + +typedef signed char s8; +typedef short int s16; +typedef int s32; +typedef long long int s64; + +typedef float r32; +typedef double r64; + +#define INT8_MIN (-128) +#define INT16_MIN (-32767-1) +#define INT32_MIN (-2147483647-1) +#define INT64_MIN (-GSINT64(9223372036854775807)-1) + +#define INT8_MAX (127) +#define INT16_MAX (32767) +#define INT32_MAX (2147483647) +#define INT64_MAX (GSINT64(9223372036854775807)) + +#define UINT8_MAX (255) +#define UINT16_MAX (65535) +#define UINT32_MAX (4294967295U) +#define UINT64_MAX (GSUINT64(18446744073709551615)) + +#define FLOAT_MIN (1.175494351e-38F) +#define FLOAT_MAX (3.402823466e+38F) +#define DOUBLE_MIN (2.2250738585072014e-308) +#define DOUBLE_MAX (1.7976931348623158e+308) + +#define Kilobytes(Value) ((Value) * 1024) +#define Megabytes(Value) (Kilobytes(Value) * 1024) +#define Gigabytes(Value) (Megabytes(Value) * 1024) +#define Terabytes(Value) (Gigabytes(Value) * 1024) + +#define PI 3.14159265359 +#define PI_OVER_180 0.01745329251f + +#define GS_TYPES +#endif + + +#ifdef DEBUG + +static void DebugPrint(char* Format, ...); + +#if !defined(Assert) +// NOTE(peter): this writes to address 0 which is always illegal and will cause a crash +#define Assert(expression) if(!(expression)){ *((int *)0) = 5; } +#endif + +#define DEBUG_IF(condition) if (condition) + +#define InvalidCodePath Assert(0) +#define InvalidDefaultCase default: { Assert(0); } +#define DebugBreak __debugbreak() + +#define STBI_ASSERT(x) Assert(x) + +#ifdef GS_TEST_SUTE +#define TestClean(v, c) SuccessCount += Test(v, c, &TestCount) +internal s32 +Test(b32 Result, char* Description, s32* Count) +{ + char* Passed = (Result ? "Success" : "Failed"); + if (!Result) + DebugPrint("%s:\n................................................%s\n\n", Description, Passed); + + *Count = *Count + 1; + return (Result ? 1 : 0); +} +#endif // GS_TEST_SUTE + +#else + +#define Assert(expression) +#define InvalidCodePath +#define DEBUG_IF(condition) + +//#define DEBUG_TRACK_SCOPE(a) + +#endif // DEBUG + +#ifndef GS_LANGUAGE_MATH + +#define GSZeroStruct(data) GSZeroMemory_((u8*)(&(data)), sizeof(data)) +#define GSZeroMemory(mem, size) GSZeroMemory_((u8*)(mem), (size)) +static void +GSZeroMemory_ (u8* Memory, s32 Size) +{ + for (int i = 0; i < Size; i++) { Memory[i] = 0; } +} + +#define GSMemCopy(from, to, size) GSMemCopy_((u8*)from, (u8*)to, size) +static void +GSMemCopy_ (u8* From, u8* To, s32 Size) +{ + for (int i = 0; i < Size; i++) { To[i] = From[i]; } +} + +#define GSMemSet(buffer, value, size) GSMemSet_((u8*)buffer, value, size) +internal void +GSMemSet_ (u8* Buffer, u8 Value, s32 Length) +{ + u8* Cursor = Buffer; + for (s32 i = 0; i < Length; i++) + { + *Cursor++ = Value; + } +} + +#define GSMinDef(type) static type GSMin(type A, type B) { return (A < B ? A : B); } +GSMinDef(s8) +GSMinDef(s16) +GSMinDef(s32) +GSMinDef(s64) +GSMinDef(u8) +GSMinDef(u16) +GSMinDef(u32) +GSMinDef(u64) +GSMinDef(r32) +GSMinDef(r64) +#undef GSMinDef + +#define GSMaxDef(type) static type GSMax(type A, type B) { return (A > B ? A : B); } +GSMaxDef(s8) +GSMaxDef(s16) +GSMaxDef(s32) +GSMaxDef(s64) +GSMaxDef(u8) +GSMaxDef(u16) +GSMaxDef(u32) +GSMaxDef(u64) +GSMaxDef(r32) +GSMaxDef(r64) +#undef GSMaxDef + +#define GSClampDef(type) static type GSClamp(type Min, type V, type Max) { \ + type Result = V; \ + if (V < Min) { Result = Min; } \ + if (V > Max) { Result = Max; } \ + return Result; \ +} +GSClampDef(s8) +GSClampDef(s16) +GSClampDef(s32) +GSClampDef(s64) +GSClampDef(u8) +GSClampDef(u16) +GSClampDef(u32) +GSClampDef(u64) +GSClampDef(r32) +GSClampDef(r64) +#undef GSClampDef + +#define GSClamp01Def(type) static type GSClamp01(type V) { \ + type Min = 0; type Max = 1; \ + type Result = V; \ + if (V < Min) { Result = Min; } \ + if (V > Max) { Result = Max; } \ + return Result; \ +} +GSClamp01Def(r32) +GSClamp01Def(r64) +#undef GSClamp01Def + +#define GSAbsDef(type) static type GSAbs(type A) { return (A < 0 ? -A : A); } +GSAbsDef(s8) +GSAbsDef(s16) +GSAbsDef(s32) +GSAbsDef(s64) +GSAbsDef(r32) +GSAbsDef(r64) +#undef GSAbsDef + +#define GSPowDef(type) static type GSPow(type N, s32 Power) { \ + type Result = N; \ + for(s32 i = 1; i < Power; i++) { Result *= N; } \ + return Result; \ +} +GSPowDef(s8) +GSPowDef(s16) +GSPowDef(s32) +GSPowDef(s64) +GSPowDef(u8) +GSPowDef(u16) +GSPowDef(u32) +GSPowDef(u64) +GSPowDef(r32) +GSPowDef(r64) +#undef GSPowDef + + +#define GSLerpDef(type) type GSLerp(type A, type B, type Percent) { return (A * (1.0f - Percent))+(B * Percent);} +GSLerpDef(r32) +GSLerpDef(r64) +#undef GSLerpDef + +static r32 GSSqrt(r32 V) +{ + r32 Result = _mm_cvtss_f32(_mm_sqrt_ss(_mm_set_ss(V))); + return Result; +} +#if 0 +// TODO(Peter): Need a way to split the input into two f32's to supply to _mm_sqrt_sd +static r64 GSSqrt(r64 V) +{ + r64 Result = _mm_cvtsd_f64(_mm_sqrt_sd(_mm_set_sd(V))); + return Result; +} +#endif + +static r32 DegreesToRadians (r32 Degrees) { return Degrees * PI_OVER_180; } +static r64 DegreesToRadians (r64 Degrees) { return Degrees * PI_OVER_180; } + +#define GSIsPowerOfTwoDef(type) static type IsPowerOfTwo(type V) { return (V & (V - 1)) == 0; } +GSIsPowerOfTwoDef(u8); +GSIsPowerOfTwoDef(u16); +GSIsPowerOfTwoDef(u32); +GSIsPowerOfTwoDef(u64); +#undef GSIsPowerOfTwoDef + +#define GSIsOddDef(type) inline type IsOdd(type V) { return (V & 1); } +GSIsOddDef(u8); +GSIsOddDef(u16); +GSIsOddDef(u32); +GSIsOddDef(u64); +GSIsOddDef(s8); +GSIsOddDef(s16); +GSIsOddDef(s32); +GSIsOddDef(s64); +#undef GSIsOddDef + +#define GSIntDivideRoundUpDef(type) static type IntegerDivideRoundUp (type A, type B) { r32 Result = (r32)A / (r32)B; Result += .99999f; return (type)Result; } +GSIntDivideRoundUpDef(u8); +GSIntDivideRoundUpDef(u16); +GSIntDivideRoundUpDef(u32); +GSIntDivideRoundUpDef(u64); +GSIntDivideRoundUpDef(s8); +GSIntDivideRoundUpDef(s16); +GSIntDivideRoundUpDef(s32); +GSIntDivideRoundUpDef(s64); +#undef GSIntDivideRoundUpDef + +#define GSTrigFunctionDef(name, type, func) static type name(type V) { return func(V); } +GSTrigFunctionDef(GSSin, r32, sinf); +GSTrigFunctionDef(GSSin, r64, sin); +GSTrigFunctionDef(GSCos, r32, cosf); +GSTrigFunctionDef(GSCos, r64, cos); +GSTrigFunctionDef(GSTan, r32, tanf); +GSTrigFunctionDef(GSTan, r64, tan); +#undef GSTrigFunctionDef + +static u8 +RoundToNearestPowerOfTwo (u8 V) +{ + u8 Result = 0; + + if (IsPowerOfTwo(V)) + { + Result = V; + } + else + { + Result = V - 1; + Result |= Result >> 1; + Result |= Result >> 2; + Result |= Result >> 4; + Result += 1; + } + + return Result; +} + +static u16 +RoundToNearestPowerOfTwo (u16 V) +{ + u16 Result = 0; + + if (IsPowerOfTwo(V)) + { + Result = V; + } + else + { + Result = V - 1; + Result |= Result >> 1; + Result |= Result >> 2; + Result |= Result >> 4; + Result |= Result >> 8; + Result += 1; + } + + return Result; +} + +static u32 +RoundToNearestPowerOfTwo (u32 V) +{ + u32 Result = 0; + + if (IsPowerOfTwo(V)) + { + Result = V; + } + else + { + Result = V - 1; + Result |= Result >> 1; + Result |= Result >> 2; + Result |= Result >> 4; + Result |= Result >> 8; + Result |= Result >> 16; + Result += 1; + } + + return Result; +} + +static u64 +RoundToNearestPowerOfTwo (u64 V) +{ + u64 Result = 0; + + if (IsPowerOfTwo(V)) + { + Result = V; + } + else + { + Result = V - 1; + Result |= Result >> 1; + Result |= Result >> 2; + Result |= Result >> 4; + Result |= Result >> 8; + Result |= Result >> 16; + Result |= Result >> 32; + Result += 1; + } + + return Result; +} + +#define GS_LANGUAGE_MATH +#endif // GS_LANGUAGE_MATH + +static u32 +HostToNetU32(u32 In) +{ + unsigned char *s = (unsigned char *)&In; + u32 Result = (u32)(s[0] << 24 | s[1] << 16 | s[2] << 8 | s[3]); + return Result; +} + +static u16 +HostToNetU16(u16 In) +{ + unsigned char *s = (unsigned char *)&In; + u16 Result = (u16)(s[0] << 8 | s[1]); + return Result; +} + +#define GS_LANGUAGE_H +#endif diff --git a/gs_memory.h b/gs_memory.h new file mode 100644 index 0000000..9d1541d --- /dev/null +++ b/gs_memory.h @@ -0,0 +1,461 @@ +#ifndef GS_MEMORY_H + +#define ArenaZeroStruct(data_ptr) ArenaZeroStruct_((u8*)data_ptr, sizeof(*data_ptr)) +inline void +ArenaZeroStruct_ (u8* Base, s32 Size) +{ + u8* Iter = Base; + for (s32 i = 0; i < Size; i++) { *Iter++ = 0; } +} + +struct grow_arena_result +{ + u8* Base; + s32 Size; +}; + +#define GROW_ARENA_MEMORY(name) grow_arena_result name(s32 Size) +typedef GROW_ARENA_MEMORY(grow_arena_memory); + +#define FREE_ARENA_MEMORY(name) b32 name(u8* Base, s32 Size) +typedef FREE_ARENA_MEMORY(free_arena_memory); + +struct memory_region_header +{ + memory_region_header* Prev; + s32 Size; + s32 Used; + u8* Base; +}; + +inline b32 +RegionCanFitSize (memory_region_header* Header, s32 Size) +{ + b32 Result = (Header->Used + Size) <= Header->Size; + return Result; +} + +inline b32 +AddressIsInRegion (memory_region_header* Header, u8* Address) +{ + b32 Result = (Header->Base <= Address) && (Header->Base + Header->Used > Address); + return Result; +} + +#ifndef DEFAULT_MEMORY_ALIGNMENT +#define DEFAULT_MEMORY_ALIGNMENT (2 * sizeof(void*)) +#endif + +b32 GSMemIsPowerOfTwo (u64 Address) +{ + return (Address & (Address - 1)) == 0; +} + +u64 AlignForward (u64 Base, u64 Align) +{ + u64 P, A, Modulo; + + Assert(GSMemIsPowerOfTwo(Align)); + + P = Base; + A = Align; + Modulo = P & (A - 1); + + if (Modulo != 0) + { + P = P + (A - Modulo); + } + + return P; +} + +////////////////////////////// +// Heap Memory +////////////////////////////// + +// heap_memory_arena +// a growable memory arena that has two ways to interact with it: push and clear. +// Push: returns a free region of continguous memory. If the arenas GrowArenaProc function is set, this may +// get called in order to obtain enough free memory to fulfil the push request +// Clear: clears the entire memory arena. If the arena has been grown at any point, those subsequent +// regions of memory will be freed back to the system. +struct heap_memory_arena +{ + memory_region_header* CurrentRegion; + + s32 RegionMemorySize; + grow_arena_memory* GrowArenaProc; + free_arena_memory* FreeArenaMemoryProc; +}; + +static void +GrowHeapArena (heap_memory_arena* Arena, s32 RequestedSize) +{ + if (Arena->GrowArenaProc) + { + Assert(Arena->RegionMemorySize > 0); + + s32 GrowthSize = GSMax(RequestedSize, Arena->RegionMemorySize); + grow_arena_result NewMemory = Arena->GrowArenaProc(GrowthSize + sizeof(memory_region_header)); + Assert(NewMemory.Size > 0); + + memory_region_header* Header = (memory_region_header*)NewMemory.Base; + Header->Base = (u8*)NewMemory.Base + sizeof(memory_region_header); + Header->Size = NewMemory.Size - sizeof(memory_region_header); + Header->Used = 0; + Header->Prev = Arena->CurrentRegion; + Arena->CurrentRegion = Header; + } + else + { + InvalidCodePath; + } +} + +#define PushStruct(arena, type) (type*)PushSize_(arena, sizeof(type)) +#define PushArray(arena, type, count) (type*)PushSize_(arena, sizeof(type)*count) +static u8* +PushSize_ (heap_memory_arena* Arena, s32 Size) +{ + if (!Arena->CurrentRegion) { GrowHeapArena(Arena, Size); } + + u8* CurrPointer = Arena->CurrentRegion->Base + Arena->CurrentRegion->Used; + u64 Offset = AlignForward((u64)CurrPointer, DEFAULT_MEMORY_ALIGNMENT); + Offset -= (u64)(Arena->CurrentRegion->Base + Arena->CurrentRegion->Used); + + if (!RegionCanFitSize(Arena->CurrentRegion, Size + Offset)) + { + // TODO(Peter): There might be empty space in the current region, its just not big enough for the + // requested size. We should search backwards to see if there is enough space in a previous region + // before growing the arena. + + GrowHeapArena(Arena, Size + Offset); + } + + u8* Result = Arena->CurrentRegion->Base + Arena->CurrentRegion->Used + Offset; + Arena->CurrentRegion->Used += Size + Offset; + + GSZeroMemory(Result, Size); + + return Result; +} + +static void +InitHeapMemoryArena (heap_memory_arena* Arena, s32 RegionMemorySize, + grow_arena_memory* GrowProc, free_arena_memory* FreeProc) +{ + ArenaZeroStruct(Arena); + Arena->RegionMemorySize = RegionMemorySize; + Arena->GrowArenaProc = GrowProc; + Arena->FreeArenaMemoryProc = FreeProc; +} + +static void +InitHeapMemoryArena (heap_memory_arena* Arena, u8* Base, s32 Size, + s32 RegionMemorySize = 0, + grow_arena_memory* GrowProc = 0, + free_arena_memory* FreeProc = 0) +{ + Assert(Size > sizeof(memory_region_header)); + + Arena->CurrentRegion = (memory_region_header*)Base; + Arena->CurrentRegion->Base = Base + sizeof(memory_region_header); + Arena->CurrentRegion->Size = Size - sizeof(memory_region_header); + Arena->CurrentRegion->Used = 0; + Arena->CurrentRegion->Prev = 0; + + Arena->RegionMemorySize = RegionMemorySize; + Arena->GrowArenaProc = GrowProc; + Arena->FreeArenaMemoryProc = FreeProc; +} + +static void +ClearHeapMemoryArena (heap_memory_arena* Arena) +{ + if (!Arena->CurrentRegion) { return; } + + memory_region_header* CurrentHead = Arena->CurrentRegion; + + if (CurrentHead->Prev) + { + Assert(Arena->FreeArenaMemoryProc); + while(CurrentHead->Prev) + { + memory_region_header* PrevHead = CurrentHead->Prev; + Arena->FreeArenaMemoryProc((u8*)CurrentHead, CurrentHead->Size + sizeof(memory_region_header)); + CurrentHead = PrevHead; + } + + Arena->CurrentRegion = CurrentHead; + } + + Arena->CurrentRegion->Used = 0; +} + + +////////////////////////////// +// Stack Memory +////////////////////////////// + +struct stack_memory_region +{ + stack_memory_region* Prev; +}; + +// stack_memory_arena +// Push: returns a free region of continguous memory. If the arenas GrowArenaProc function is set, this may +// get called in order to obtain enough free memory to fulfil the push request +// Pop: frees the last region allocated on the stack, returning it to the region of memory available to +// be used. +// Clear: clears the entire memory arena. If the arena has been grown at any point, those subsequent +// regions of memory will be freed back to the system. +struct stack_memory_arena +{ + memory_region_header* CurrentRegion; + stack_memory_region* UsedList; + + s32 RegionMemorySize; + grow_arena_memory* GrowArenaProc; + free_arena_memory* FreeArenaMemoryProc; +}; + +static u8* +PushSize_ (stack_memory_arena* Arena, s32 Size) +{ + if (!Arena->CurrentRegion || + !RegionCanFitSize(Arena->CurrentRegion, Size)) + { + if (Arena->GrowArenaProc) + { + Assert(Arena->RegionMemorySize > 0); + + grow_arena_result NewMemory = Arena->GrowArenaProc(Arena->RegionMemorySize + sizeof(memory_region_header)); + Assert(NewMemory.Size > 0); + + memory_region_header* Header = (memory_region_header*)NewMemory.Base; + Header->Base = (u8*)NewMemory.Base + sizeof(memory_region_header); + Header->Size = NewMemory.Size - sizeof(memory_region_header); + Header->Used = 0; + Header->Prev = Arena->CurrentRegion; + Arena->CurrentRegion = Header; + } + else + { + InvalidCodePath; + } + } + + u8* Region = Arena->CurrentRegion->Base + Arena->CurrentRegion->Used; + stack_memory_region* UsedListHeader = (stack_memory_region*)Region; + UsedListHeader->Prev = Arena->UsedList; + Arena->UsedList = UsedListHeader; + + u8* Result = Region + sizeof(stack_memory_region); + Arena->CurrentRegion->Used += Size + sizeof(stack_memory_region); + + return Result; +} + +// NOTE(Peter): Returns size available after the Pop operation +static s32 +PopLast (stack_memory_arena* Arena) +{ + s32 Result = Arena->CurrentRegion->Size - Arena->CurrentRegion->Used; + + if (Arena->UsedList) + { + u8* LastHead = (u8*)Arena->UsedList; + + if (!AddressIsInRegion(Arena->CurrentRegion, LastHead) && + Arena->FreeArenaMemoryProc) + { + memory_region_header* PrevHeader = Arena->CurrentRegion->Prev; + Arena->FreeArenaMemoryProc((u8*)Arena->CurrentRegion, + Arena->CurrentRegion->Size + sizeof(memory_region_header)); + Arena->CurrentRegion = PrevHeader; + + } + + Assert(LastHead >= Arena->CurrentRegion->Base && + LastHead <= Arena->CurrentRegion->Base + Arena->CurrentRegion->Size); + + stack_memory_region* PrevAlloc = Arena->UsedList->Prev; + + s32 SizeUsed = LastHead - Arena->CurrentRegion->Base; + Arena->CurrentRegion->Used = SizeUsed; + Result = Arena->CurrentRegion->Size - Arena->CurrentRegion->Used; + + Arena->UsedList = PrevAlloc; + } + + return Result; +} + +static void +InitStackMemoryArena (stack_memory_arena* Arena, s32 RegionMemorySize, + grow_arena_memory* GrowProc, free_arena_memory* FreeProc) +{ + ArenaZeroStruct(Arena); + Arena->RegionMemorySize = RegionMemorySize; + Arena->GrowArenaProc = GrowProc; + Arena->FreeArenaMemoryProc = FreeProc; +} + +static void +InitStackMemoryArena (stack_memory_arena* Arena, u8* Base, s32 Size, + s32 RegionMemorySize = 0, + grow_arena_memory* GrowProc = 0, + free_arena_memory* FreeProc = 0) +{ + Assert(Size > sizeof(memory_region_header)); + + Arena->CurrentRegion = (memory_region_header*)Base; + Arena->CurrentRegion->Base = Base + sizeof(memory_region_header); + Arena->CurrentRegion->Size = Size - sizeof(memory_region_header); + Arena->CurrentRegion->Used = 0; + Arena->CurrentRegion->Prev = 0; + + Arena->RegionMemorySize = RegionMemorySize; + Arena->GrowArenaProc = GrowProc; + Arena->FreeArenaMemoryProc = FreeProc; +} + +static void +ClearStackMemoryArena (stack_memory_arena* Arena) +{ + if (!Arena->CurrentRegion) { return; } + + memory_region_header* CurrentHead = Arena->CurrentRegion; + + if (CurrentHead->Prev) + { + Assert(Arena->FreeArenaMemoryProc); + while(CurrentHead->Prev) + { + memory_region_header* PrevHead = CurrentHead->Prev; + Arena->FreeArenaMemoryProc((u8*)CurrentHead, CurrentHead->Size + sizeof(memory_region_header)); + CurrentHead = PrevHead; + } + + Arena->CurrentRegion = CurrentHead; + } + + Arena->CurrentRegion->Used = 0; + Arena->UsedList = 0; +} + +////////////////////////////// +// Pool Memory +////////////////////////////// + +struct chunk_header +{ + chunk_header* Prev; +}; + +struct pool_memory_arena +{ + memory_region_header* CurrentRegion; + s32 ChunkSize; + chunk_header* FreeList; + + s32 RegionMemorySize; + grow_arena_memory* GrowArenaProc; + free_arena_memory* FreeArenaMemoryProc; +}; + +struct chunk_result +{ + s32 Size; + u8* Base; +}; + +static chunk_result +PushChunk (pool_memory_arena* Arena) +{ + chunk_result Result = {}; + + if (Arena->FreeList) + { + Result.Base = (u8*)Arena->FreeList; + Result.Size = Arena->ChunkSize; + + Arena->FreeList = Arena->FreeList->Prev; + } + else + { + if (!RegionCanFitSize(Arena->CurrentRegion, Arena->ChunkSize)) + { + if (Arena->GrowArenaProc) + { + grow_arena_result NewMemory = Arena->GrowArenaProc(Arena->RegionMemorySize + sizeof(memory_region_header)); + Assert(NewMemory.Size > 0); + + memory_region_header* Header = (memory_region_header*)NewMemory.Base; + Header->Base = (u8*)NewMemory.Base + sizeof(memory_region_header); + Header->Size = NewMemory.Size - sizeof(memory_region_header); + Header->Used = 0; + Header->Prev = Arena->CurrentRegion; + Arena->CurrentRegion = Header; + } + else + { + InvalidCodePath; + } + } + + Result.Base = Arena->CurrentRegion->Base + Arena->CurrentRegion->Used; + Result.Size = Arena->ChunkSize; + + Arena->CurrentRegion->Used += Arena->ChunkSize; + } + + return Result; +} + +static void +FreeChunk (pool_memory_arena* Arena, u8* Base, s32 Size) +{ + Assert(Arena->ChunkSize == Size); + + chunk_header* Header = (chunk_header*)Base; + Header->Prev = Arena->FreeList; + Arena->FreeList = Header; +} + +static void +InitPoolMemoryArena (pool_memory_arena* Arena, s32 ChunkSize, s32 ChunksPerRegion, + grow_arena_memory* GrowProc, free_arena_memory* FreeProc) +{ + Assert(ChunkSize > sizeof(chunk_header)); + + ArenaZeroStruct(Arena); + Arena->ChunkSize = ChunkSize; + Arena->RegionMemorySize = ChunkSize * ChunksPerRegion; + Arena->GrowArenaProc = GrowProc; + Arena->FreeArenaMemoryProc = FreeProc; +} + +static void +InitStackMemoryArena (pool_memory_arena* Arena, u8* Base, s32 Size, + s32 ChunkSize, s32 ChunksPerRegion, + grow_arena_memory* GrowProc = 0, + free_arena_memory* FreeProc = 0) +{ + Assert(Size > sizeof(memory_region_header)); + Assert(Size % ChunkSize == ChunksPerRegion); + + Arena->CurrentRegion = (memory_region_header*)Base; + Arena->CurrentRegion->Base = Base + sizeof(memory_region_header); + Arena->CurrentRegion->Size = Size - sizeof(memory_region_header); + Arena->CurrentRegion->Used = 0; + Arena->CurrentRegion->Prev = 0; + + Arena->ChunkSize = ChunkSize; + Arena->RegionMemorySize = ChunkSize * ChunksPerRegion; + Arena->GrowArenaProc = GrowProc; + Arena->FreeArenaMemoryProc = FreeProc; +} + +#define GS_MEMORY_H +#endif // GS_MEMORY_H diff --git a/gs_platform.h b/gs_platform.h new file mode 100644 index 0000000..b958ea2 --- /dev/null +++ b/gs_platform.h @@ -0,0 +1,171 @@ +#ifndef GS_PLATFORM_H + +struct platform_memory_result +{ + u8* Base; + s32 Size; + s32 Error; +}; + +#define PLATFORM_MEMORY_NO_ERROR 0 + +#define PLATFORM_ALLOC(name) platform_memory_result name(s32 Size) +typedef PLATFORM_ALLOC(platform_alloc); + +#define PLATFORM_FREE(name) b32 name(u8* Base, s32 Size) +typedef PLATFORM_FREE(platform_free); + +#define PLATFORM_READ_ENTIRE_FILE(name) platform_memory_result name(char* Path) +typedef PLATFORM_READ_ENTIRE_FILE(platform_read_entire_file); + +#define PLATFORM_WRITE_ENTIRE_FILE(name) b32 name(char* Path, u8* Contents, s32 Size) +typedef PLATFORM_WRITE_ENTIRE_FILE(platform_write_entire_file); + +#define PLATFORM_GET_FILE_PATH(name) +typedef PLATFORM_GET_FILE_PATH(platform_get_file_path); + +#define PLATFORM_GET_GPU_TEXTURE_HANDLE(name) +typedef PLATFORM_GET_GPU_TEXTURE_HANDLE(platform_get_gpu_texture_handle); + +#define PLATFORM_GET_SOCKET_HANDLE(name) +typedef PLATFORM_GET_SOCKET_HANDLE(platform_get_socket_handle); + +#define PLATFORM_GET_SEND_ADDRESS(name) +typedef PLATFORM_GET_SEND_ADDRESS(platform_get_send_address); + +#define PLATFORM_SET_SOCKET_OPTION(name) +typedef PLATFORM_SET_SOCKET_OPTION(platform_set_socket_option); + +#define PLATFORM_SEND_TO(name) +typedef PLATFORM_SEND_TO(platform_send_to); + +#define PLATFORM_CLOSE_SOCKET(name) +typedef PLATFORM_CLOSE_SOCKET(platform_close_socket); + + +enum key_code +{ + KeyCode_Invalid, + + KeyCode_Esc, + + KeyCode_Space, + KeyCode_Tab, + KeyCode_CapsLock, + KeyCode_LeftShift, KeyCode_RightShift, + KeyCode_LeftCtrl, KeyCode_RightCtrl, + KeyCode_Fn, + KeyCode_Alt, + KeyCode_PageUp, KeyCode_PageDown, + KeyCode_Backspace, KeyCode_Delete, + KeyCode_Enter, + + // Function Keys + KeyCode_F0, KeyCode_F1, KeyCode_F2, KeyCode_F3, KeyCode_F4, KeyCode_F5, KeyCode_F6, KeyCode_F7, + KeyCode_F8, KeyCode_F9, KeyCode_F10, KeyCode_F11, KeyCode_F12, + + // Letters + KeyCode_a, KeyCode_b, KeyCode_c, KeyCode_d, KeyCode_e, KeyCode_f, KeyCode_g, KeyCode_h, + KeyCode_i, KeyCode_j, KeyCode_k, KeyCode_l, KeyCode_m, KeyCode_n, KeyCode_o, KeyCode_p, + KeyCode_q, KeyCode_r, KeyCode_s, KeyCode_t, KeyCode_u, KeyCode_v, KeyCode_w, KeyCode_x, + KeyCode_y, KeyCode_z, + + KeyCode_A, KeyCode_B, KeyCode_C, KeyCode_D, KeyCode_E, KeyCode_F, KeyCode_G, KeyCode_H, + KeyCode_I, KeyCode_J, KeyCode_K, KeyCode_L, KeyCode_M, KeyCode_N, KeyCode_O, KeyCode_P, + KeyCode_Q, KeyCode_R, KeyCode_S, KeyCode_T, KeyCode_U, KeyCode_V, KeyCode_W, KeyCode_X, + KeyCode_Y, KeyCode_Z, + + // Numbers + KeyCode_0, KeyCode_1, KeyCode_2, KeyCode_3, KeyCode_4, KeyCode_5, KeyCode_6, KeyCode_7, + KeyCode_8, KeyCode_9, + + KeyCode_Num0, KeyCode_Num1, KeyCode_Num2, KeyCode_Num3, KeyCode_Num4, KeyCode_Num5, + KeyCode_Num6, KeyCode_Num7, KeyCode_Num8, KeyCode_Num9, + + // Symbols + KeyCode_Bang, KeyCode_At, KeyCode_Pound, KeyCode_Dollar, KeyCode_Percent, KeyCode_Carrot, + KeyCode_Ampersand, KeyCode_Star, KeyCode_LeftParen, KeyCode_RightParen, KeyCode_Minus, KeyCode_Plus, + KeyCode_Equals, KeyCode_Underscore, KeyCode_LeftBrace, KeyCode_RightBrace, KeyCode_LeftBracket, + KeyCode_RightBracket, KeyCode_Colon, KeyCode_SemiColon, KeyCode_SingleQuote, KeyCode_DoubleQuote, + KeyCode_ForwardSlash, KeyCode_Backslash, KeyCode_Pipe, KeyCode_Comma, KeyCode_Period, + KeyCode_QuestionMark, KeyCode_LessThan, KeyCode_GreaterThan, KeyCode_Tilde, KeyCode_BackQuote, + + // Arrows + KeyCode_UpArrow, + KeyCode_DownArrow, + KeyCode_LeftArrow, + KeyCode_RightArrow, + + // Mouse + // NOTE(Peter): Including this here so we can utilize the same KeyDown, KeyUp etc. functions + KeyCode_MouseLeftButton, + KeyCode_MouseMiddleButton, + KeyCode_MouseRightButton, + + KeyCode_Count, +}; + +enum modifier_flags +{ + Modifier_Shift = 1 << 0, + Modifier_Ctrl = 1 << 1, + Modifier_Alt = 1 << 2, + Modifier_Sys = 1 << 3, // NOTE(Peter): this is the windows key +}; + +#define INPUT_FRAME_STRING_LENGTH 32 +struct input_frame +{ + b32 KeysDown[(int)KeyCode_Count]; + s32 StringInputUsed; + char StringInput[INPUT_FRAME_STRING_LENGTH]; +}; + +struct input +{ + input_frame Frames[2]; + input_frame* New; + input_frame* Old; +}; + +internal void InitializeInput (input* Input); +internal void SwapInputBuffers (input* Input); + +internal void +InitializeInput (input* Input) +{ + *(Input) = {}; + Input->New = &Input->Frames[0]; + Input->Old = &Input->Frames[1]; +} + +internal void +SwapInputBuffers (input* Input) +{ + input_frame* NowOld = Input->New; + Input->New = Input->Old; + Input->Old = NowOld; + + for (s32 Key = 0; Key < KeyCode_Count; Key++) { Input->New->KeysDown[Key] = false; } + Input->New->StringInputUsed = 0; +} + +internal b32 +KeyDown (input Input, key_code Key) +{ + return Input.New->KeysDown[Key]; +} + +internal b32 +KeyTransitionedDown (input Input, key_code Key) +{ + return Input.New->KeysDown[Key] && !Input.Old->KeysDown[Key]; +} + +internal b32 +KeyTransitionedUp (input Input, key_code Key) +{ + return !Input.New->KeysDown[Key] && Input.Old->KeysDown[Key]; +} +#define GS_PLATFORM_H +#endif // GS_PLATFORM_H \ No newline at end of file diff --git a/gs_string.h b/gs_string.h new file mode 100644 index 0000000..13ec4cf --- /dev/null +++ b/gs_string.h @@ -0,0 +1,1709 @@ +//////////////////////////////////////////////////////////////// +// String +//////////////////////////////////////////////////////////////// + +struct string +{ + char* Memory; + s32 Length; + s32 Max; +}; + +//////////////////////////////////////////////////////////////// +// String Tokenizing +//////////////////////////////////////////////////////////////// + +struct tokenizer +{ + char* At; + char* Memory; + s32 MemoryLength; +}; + + +enum token_type +{ + Token_Error, + + Token_LeftParen, + Token_RightParen, + Token_LeftSquareBracket, + Token_RightSquareBracket, + Token_LeftCurlyBracket, + Token_RightCurlyBracket, + Token_Semicolon, + Token_Operator, + Token_Comma, + Token_Period, + Token_PointerReference, + + Token_PoundDefine, + Token_PoundUndef, + Token_PoundInclude, + Token_PoundIfDef, + Token_PoundIfNDef, + Token_PoundIf, + Token_PoundElif, + Token_PoundElse, + Token_PoundEndif, + Token_PoundError, + Token_PoundPragma, + + Token_Number, + Token_Char, + Token_String, + Token_Identifier, + + Token_Comment, + Token_MultilineComment, + + Token_Unknown, + Token_EndOfStream, +}; + +char* TokenNames[] = { + "Token_Error ", + "Token_LeftParen ", + "Token_RightParen ", + "Token_LeftSquareBracket ", + "Token_RightSquareBracket ", + "Token_LeftCurlyBracket ", + "Token_RightCurlyBracket ", + "Token_Semicolon ", + "Token_Operator ", + "Token_Comma ", + "Token_Period ", + "Token_PointerReference ", + "Token_PoundDefine ", + "Token_PoundUndef ", + "Token_PoundInclude ", + "Token_PoundIfDef ", + "Token_PoundIfNDef ", + "Token_PoundIf ", + "Token_PoundElif ", + "Token_PoundElse ", + "Token_PoundEndif ", + "Token_PoundError ", + "Token_PoundPragma ", + "Token_Number ", + "Token_Char ", + "Token_String ", + "Token_Identifier ", + "Token_Comment ", + "Token_MultilineComment ", + "Token_Unknown ", + "Token_EndOfStream ", +}; + +struct token +{ + token_type Type; + char* Text; + s32 TextLength; + + token* Next; +}; + +//////////////////////////////////////////////////////////////// +// String Memory +//////////////////////////////////////////////////////////////// + +struct slot_header +{ + slot_header* Next; + s32 Size; +}; + +struct slot_arena +{ + u8* Memory; + s32 SlotSize; + s32 SlotCount; + slot_header* FreeList; +}; + +struct contiguous_slot_count_result +{ + s32 Count; + slot_header* LastContiguousSlot; +}; + + +//////////////////////////////////////////////////////////////// +// String Function Declarations +//////////////////////////////////////////////////////////////// + +// Utility +#if !defined GS_LANGUAGE_H + +static void GSZeroMemory (u8* Memory, s32 Size); +static s32 GSMin (s32 A, s32 B); +static s32 GSAbs (s32 A); +static float GSAbsF (float A); +static float GSPowF (float N, s32 Power); + +#endif + +// Setup + +#ifdef GS_MEMORY_H +#define PushString(str, arena, size) (str)->Memory = PushArray(arena, char, size); (str)->Length = 0; (str)->Max = size; +#endif + +static void InitializeString (string* String, char* Data, s32 DataSize); +static string InitializeString (char* Data, s32 DataSize); +static void ClearString (string* String); + +// Character Values +static bool IsSlash (char C); +static bool IsNewline (char C); +static bool IsWhitespace (char C); +static bool IsAlpha (char C); +static bool IsUpper (char C); +static bool IsLower (char C); +static bool IsNumeric (char C); +static bool IsNumericExtended (char C); +static bool ToUpper (char C); +static bool ToLower (char C); +static bool IsAlphaNumeric (char C); +static bool IsOperator (char C); + +// Tokenizing +static b32 AtValidToken(tokenizer Tokenizer); +static char* EatToNewLine(char* C); +static void EatToNewLine(tokenizer* T); +static char* EatWhitespace(char* C); +static void EatWhitespace(tokenizer* T); +static char* EatToWhitespace(char* C); +static void EatToWhitespace(tokenizer* T); +static char* EatToCharacter(char* C, char Char); +static void EatToCharacter(tokenizer* T, char Char); +static char* EatPastCharacter(char* C, char Char); +static void EatPastCharacter(tokenizer* T, char Char); +static char* EatNumber(char* C); +static void EatNumber(tokenizer* T); + +// Char/Char Array +static u32 CharToUInt (char C); +static s32 CharArrayLength (char* CharArray); +static bool CharArraysEqual (char* A, s32 ALength, char* B, s32 BLength); +static bool CharArraysEqualUnsafe (char* A, char* B); +static void ReverseCharArray (char* Array, s32 Length); +#define FirstIndexOfChar(array, find) IndexOfChar(array, 0, find) +static s32 IndexOfChar (char* Array, s32 Start, char Find); +#define FastLastIndexOfChar(array, len, find) FastReverseIndexOfChar(array, len, 0, find) +static s32 FastReverseIndexOfChar (char* Array, s32 Length, s32 OffsetFromEnd, char Find); +#define LastIndexOfChar(array, find) ReverseIndexOfChar(array, 0, find) +static s32 ReverseIndexOfChar (char* Array, s32 OffsetFromEnd, char Find); +static b32 CharArrayContains(char* Array, char* CheckFor); +static b32 CharArrayContainsSafe(char* Array, s32 ArrayLength, char* CheckFor, s32 CheckForLength); + +// String +static string MakeString (char* Array, s32 Length, s32 Max); +static string MakeString (char* Array, s32 Length); +static string MakeString (char* Array); +static string MakeStringLiteral(char* Data); + +static bool StringsEqual (string A, string B); +static bool StringEqualsCharArray (string String, char* CharArray); +static bool StringEqualsCharArray (string String, char* CharArray, s32 CharArrayLength); +static s32 FindFirstChar (string String, char C); + +static void SetStringToChar (string* Dest, char C, s32 Count); +static void SetStringToCharArray (string* Dest, char* Source); + +static void ConcatString (string* Dest, string Source); +static void ConcatString (string* Dest, string Source, s32 Length); +static void ConcatCharArrayToString (string* Dest, char* Source); +static void ConcatCharArrayToString (string* Dest, char* Source, s32 SourceLength); + +static void CopyStringTo (string Source, string* Dest); +static s32 CopyStringToCharArray (string Source, char* Dest, s32 DestLength); +static void CopyCharArrayToString (char* Src, string* Dest); +static void CopyCharArrayToString (char* Src, s32 SrcLength, string* Dest); +static s32 CopyCharArray (char* Source, char* Dest, s32 DestLength); +static s32 CopyCharArrayAt (char* Source, char* Dest, s32 DestLength, s32 Offset); + +static void InsertChar (string* String, char Char, s32 Index); +static void InsertStringAt (string* Dest, string Source, s32 At); +static void RemoveCharAt (string* String, s32 Index); + +static string Substring (string* String, s32 Start, s32 End); +static string Substring (string* String, s32 Start); + +static void NullTerminate (string* String); + + +// Parsing +static u32 ParseUnsignedInt (char* String, s32 Length); +static s32 UIntToString (u32 Int, char* String, s32 Length); +static s32 ParseSignedInt (char* String, s32 Length); +static s32 IntToString (s32 Int, char* String, s32 Length); +static s32 IntToString (s32 Int, char* String, s32 Length, s32 Offset); +static float ParseFloat (char* String, s32 Length); +static s32 FloatToString(float Float, char *String, s32 Length, s32 AfterPoint); + +// Print F + +#define PrintString(str, format, ...) snprintf(str.Memory, str.Max, format, __VA_ARGS__); str.Length = CharArrayLength(str.Memory); + +//////////////////////////////////////////////////////////////// +// String Memory Function Declarations +//////////////////////////////////////////////////////////////// + +static s32 CalculateSlotCountFromSize (s32 RequestedSize, s32 SlotSize); +static bool SlotsAreContiguous (slot_header* First, slot_header* Second); +static contiguous_slot_count_result CountContiguousSlots (slot_header* First); +static slot_header* GetSlotAtOffset(slot_header* First, s32 Offset); +static slot_header* InsertSlotIntoList (slot_header* NewSlot, slot_header* ListStart); +static void AllocStringFromStringArena (string* String, s32 Size, slot_arena* Storage); +static string AllocStringFromStringArena (s32 Size, slot_arena* Storage); +static void FreeToStringArena (string* String, slot_arena* Storage); +static void ReallocFromStringArena (string* String, s32 NewSize, slot_arena* Storage); + +//////////////////////////////////////////////////////////////// +// String Utility Functions +//////////////////////////////////////////////////////////////// + +#if !defined GS_LANGUAGE_H + +static void +GSZeroMemory (u8* Memory, s32 Size) +{ + for (int i = 0; i < Size; i++) { Memory[i] = 0; } +} + +static s32 +GSMin (s32 A, s32 B) +{ + return (A < B ? A : B); +} + +static s32 +GSAbs (s32 A) +{ + return (A < 0 ? -A : A); +} + +static float +GSAbs (float A) +{ + return (A < 0 ? -A : A); +} + +static float +GSPow (float N, s32 Power) +{ + float Result = N; + for(s32 i = 1; i < Power; i++) { Result *= N; } + return Result; +} + +#endif + +//////////////////////////////////////////////////////////////// +// Init and Clear +//////////////////////////////////////////////////////////////// + +static void +InitializeString (string* String, char* Data, s32 DataSize) +{ + String->Memory = Data; + String->Max = DataSize; + String->Length = 0; +} + +static string +InitializeString (char* Data, s32 DataSize) +{ + string Result = {}; + Result.Memory = Data; + Result.Max = DataSize; + Result.Length = 0; + return Result; +} + +static void +ClearString (string* String) +{ + String->Memory = 0; + String->Max = 0; + String->Length = 0; +} + +//////////////////////////////////////////////////////////////// +// Char Value Types +//////////////////////////////////////////////////////////////// + +static bool IsSlash (char C) { return ((C == '\\') || (C == '/')); } +static bool IsNewline (char C) { return (C == '\n') || (C == '\r'); } +static bool IsWhitespace (char C) { return (C == ' ') || (C == '\t') || IsNewline(C); } +static bool IsAlpha (char C) +{ + // TODO(Peter): support UTF8 chars + return ((C >= 'A') && (C <= 'Z')) || ((C >= 'a') && (C <= 'z')) || (C == '_'); +} +static bool IsUpper (char C) +{ + return ((C >= 'A') && (C <= 'Z')); +} +static bool IsLower (char C) +{ + return ((C >= 'a') && (C <= 'z')); +} +static bool IsNumeric (char C) +{ + return (C >= '0') && (C <= '9'); +} +static bool IsNumericExtended (char C) +{ + return (IsNumeric(C) || (C == 'x') || (C == 'f') || (C == '.')); +} +static bool IsAlphaNumeric (char C) +{ + return IsAlpha(C) || IsNumeric(C); +} +static bool IsOperator (char C) +{ + return ((C == '+') || + (C == '-') || + (C == '*') || + (C == '/') || + (C == '=') || + (C == '%') || + (C == '<') || + (C == '>')); +} +//////////////////////////////////////////////////////////////// +// Tokenizing +//////////////////////////////////////////////////////////////// + +static b32 +AtValidToken(tokenizer Tokenizer) +{ + b32 Result = *Tokenizer.At && Tokenizer.At < (Tokenizer.Memory + Tokenizer.MemoryLength); + return Result; +} + +static char* +EatToNewLine(char* C) +{ + char* Result = C; + while (*Result && !IsNewline(*Result)) { Result++; } + if (*Result) { Result++; } // NOTE(Peter): eat past the newline character + return Result; +} + +static void +EatToNewLine(tokenizer* T) +{ + while (*T->At && !IsNewline(*T->At)) { T->At++; } + if (*T->At) { T->At++; } // NOTE(Peter): eat past the newline character +} + +static char* +EatWhitespace(char* C) +{ + char* Result = C; + while (*Result && IsWhitespace(*Result)) { Result++; } + return Result; +} + +static void +EatWhitespace(tokenizer* T) +{ + while (*T->At && IsWhitespace(*T->At)) { T->At++; } +} + +static char* +EatToWhitespace(char* C) +{ + char* Result = C; + while (*Result && !IsWhitespace(*Result)) { Result++; } + return Result; +} + +static void +EatToWhitespace(tokenizer* T) +{ + while (*T->At && !IsWhitespace(*T->At)) { T->At++; } +} + +static char* +EatToCharacter(char* C, char Char) +{ + char* Result = C; + while (*Result && *Result != Char) { Result++; } + return Result; +} + +static void +EatToCharacter(tokenizer* T, char Char) +{ + while (*T->At && *T->At != Char) { T->At++; } +} + +static char* +EatPastCharacter(char* C, char Char) +{ + char* Result = EatToCharacter(C, Char); + if (*Result && *Result == Char) { Result++; } + return Result; +} + +static void +EatPastCharacter(tokenizer* T, char Char) +{ + EatToCharacter(T, Char); + if (*T->At && *T->At == Char) { T->At++; } +} + +static char* +EatNumber(char* C) +{ + char* Result = C; + while (*Result && IsNumericExtended(*Result)) { Result++; } + return Result; +} + +static void +EatNumber(tokenizer* T) +{ + while (*T->At && IsNumericExtended(*T->At)) { T->At++; } +} + +//////////////////////////////////////////////////////////////// +// Basic Char Operations +//////////////////////////////////////////////////////////////// + +static u32 CharToUInt (char C) { + u32 Result = (C - '0'); + return Result; +} + +static s32 +CharArrayLength (char* Array) +{ + char* C = Array; + s32 Result = 0; + while (*C) + { + *C++; + Result++; + } + return Result; +} + +static s32 +NullTerminatedCharArrayLength (char* CharArray) +{ + char* Iter = CharArray; + while (*Iter) + { + *Iter++; + } + return (Iter - CharArray); +} + +static bool +CharArraysEqual (char* A, s32 ALength, char* B, s32 BLength) +{ + bool Result = false; + if (ALength == BLength) + { + Result = true; + char* AIter = A; + char* BIter = B; + for (s32 i = 0; i < ALength; i++) + { + if(*AIter++ != *BIter++) + { + Result = false; + break; + } + } + } + return Result; +} + +static bool +CharArraysEqualUnsafe (char* A, char* B) +{ + bool Result = true; + + char* AIter = A; + char* BIter = B; + while(*AIter && *BIter) + { + if(*AIter++ != *BIter++) + { + Result = false; + break; + } + } + + if((*AIter && !*BIter) || (!*AIter && *BIter)) + { + Result = false; + } + + return Result; +} + +static bool +CharArraysEqualUpToLength (char* A, char* B, s32 Length) +{ + bool Result = true; + + char* AIter = A; + char* BIter = B; + for (s32 i = 0; i < Length; i++) + { + if(*AIter++ != *BIter++) + { + Result = false; + break; + } + } + + return Result; +} + +static void +ReverseCharArray (char* Array, s32 Length) +{ + char* ForwardIter = Array; + char* BackwardIter = Array + Length - 1; + for (s32 i = 0; i < (Length / 2); i++) + { + char F = *ForwardIter; + char B = *BackwardIter; + *ForwardIter++ = B; + *BackwardIter-- = F; + } +} + +static s32 +IndexOfChar (char* Array, s32 After, char Find) +{ + s32 Result = -1; + + s32 Counter = After; + char* Iter = Array + After; + while (*Iter) + { + if (*Iter == Find) + { + Result = Counter; + break; + } + Counter++; + *Iter++; + } + + return Result; +} + +static s32 +FastReverseIndexOfChar (char* Array, s32 Length, s32 OffsetFromEnd, char Find) +{ + s32 Result = -1; + + s32 Counter = Length - OffsetFromEnd; + char* Iter = Array + Length - OffsetFromEnd; + for (int i = 0; i < (Length - OffsetFromEnd); i++) + { + if (*Iter == Find) + { + Result = Counter; + break; + } + + *Iter--; + Counter--; + } + + return Result; +} + +static s32 +ReverseIndexOfChar (char* Array, s32 OffsetFromEnd, char Find) +{ + s32 StringLength = NullTerminatedCharArrayLength(Array); + return FastReverseIndexOfChar(Array, StringLength, OffsetFromEnd, Find); +} + +static b32 +CharArrayContains(char* Array, char* CheckFor) +{ + b32 Result = false; + + char* Src = Array; + while (*Src) + { + if (*Src == *CheckFor) + { + char* A = CheckFor; + char* B = Src; + while (*B && *A && *A == *B) + { + *B++; *A++; + } + + if (*A == 0) + { + Result = true; + break; + } + } + + Src++; + } + + return Result; +} + +static b32 +CharArrayContainsSafe(char* Array, s32 ArrayLength, char* CheckFor, s32 CheckForLength) +{ + b32 Result = false; + + if (ArrayLength >= CheckForLength) + { + char* Src = Array; + for (s32 s = 0; s < ArrayLength; s++) + { + if (*Src == *CheckFor && (s + CheckForLength <= ArrayLength)) + { + char* A = CheckFor; + char* B = Src; + for (s32 d = 0; d < CheckForLength; d++) + { + if (*B != *A) { break; } + *B++; *A++; + } + + if (*A == 0) + { + Result = true; + break; + } + } + + Src++; + } + } + + return Result; +} + +//////////////////////////////////////////////////////////////// +// Basic String Operations +//////////////////////////////////////////////////////////////// + +static bool +StringsEqual (string A, string B) +{ + bool Result = false; + + if (A.Length == B.Length) + { + Result = true; + char* AIter = A.Memory; + char* BIter = B.Memory; + for (s32 i = 0; i < A.Length; i++) + { + if (*AIter++ != *BIter++) + { + Result = false; + break; + } + } + } + + return Result; +} + +static string +MakeString (char* Array, s32 Length, s32 Max) +{ + string Result = {}; + Result.Memory = Array; + Result.Length = Length; + Result.Max = Max; + return Result; +} + +static string +MakeString (char* Array, s32 Length) +{ + string Result = {}; + Result.Memory = Array; + Result.Length = Length; + Result.Max = Length; + return Result; +} + +static string +MakeString (char* Array) +{ + s32 Length = CharArrayLength (Array); + return MakeString(Array, Length); +} + +static string +MakeStringLiteral (char* String) +{ + string Result = {}; + Result.Memory = String; + Result.Max = CharArrayLength(String); + Result.Length = Result.Max; + return Result; +} + +static bool +StringEqualsCharArray (string String, char* CharArray) +{ + bool Result = true; + + char* S = String.Memory; + char* C = CharArray; + + s32 Count = 0; + while (*C && Count < String.Length) + { + if (*C++ != *S++) + { + Result = false; + break; + } + Count++; + } + + return Result; +} + +static bool +StringEqualsCharArray (string String, char* CharArray, s32 CharArrayLength) +{ + bool Result = false; + + if (CharArrayLength == String.Length) + { + Result = true; + + char* S = String.Memory; + char* C = CharArray; + for (s32 i = 0; i < String.Length; i++) + { + if (*C++ != *S++) + { + Result = false; + break; + } + } + } + + return Result; +} + +static s32 +FindFirstChar (string String, char C) +{ + s32 Result = -1; + + char* Iter = String.Memory; + for (int i = 0; i < String.Length; i++) + { + if (*Iter++ == C) + { + Result = i; + break; + } + } + + return Result; +} + +static void +SetStringToChar (string* Dest, char C, s32 Count) +{ + Assert(Count <= Dest->Max); + + char* Iter = Dest->Memory; + for (int i = 0; i < Count; i++) + { + *Iter++ = C; + } + Dest->Length = Count; +} + +static void +SetStringToCharArray (string* Dest, char* Source) +{ + Dest->Length = 0; + + char* Src = Source; + char* Dst = Dest->Memory; + while (*Src && Dest->Length < Dest->Max) + { + *Dst++ = *Src++; + Dest->Length++; + } +} + +static void +ConcatString (string* Dest, string Source) +{ + Assert((Dest->Length + Source.Length) <= Dest->Max); + + char* Dst = Dest->Memory + Dest->Length; + char* Src = Source.Memory; + for (s32 i = 0; i < Source.Length; i++) + { + *Dst++ = *Src++; + Dest->Length++; + } +} + +static void +ConcatString (string* Dest, string Source, s32 Length) +{ + Assert(Length < Source.Length); + Assert((Dest->Length + Length) <= Dest->Max); + + char* Dst = Dest->Memory + Dest->Length; + char* Src = Source.Memory; + for (s32 i = 0; i < Length; i++) + { + *Dst++ = *Src++; + Dest->Length++; + } +} + +static void +ConcatCharArrayToString (string* Dest, char* Source) +{ + Assert(CharArrayLength(Source) + Dest->Length <= Dest->Max); + + char* Dst = Dest->Memory + Dest->Length; + char* Src = Source; + while (Dest->Length < Dest->Max && + *Src) + { + *Dst++ = *Src++; + Dest->Length++; + } +} + +static void +ConcatCharArrayToString (string* Dest, char* Source, s32 SourceLength) +{ + Assert(SourceLength + Dest->Length <= Dest->Max); + + char* Dst = Dest->Memory + Dest->Length; + char* Src = Source; + for (int i = 0; i < SourceLength && Dest->Length < Dest->Max; i++) + { + *Dst++ = *Src++; + Dest->Length++; + } +} + +static void +CopyStringTo (string Source, string* Dest) +{ + char* Src = Source.Memory; + char* Dst = Dest->Memory; + s32 CopyLength = GSMin(Source.Length, Dest->Max); + for (int i = 0; i < CopyLength; i++) + { + *Dst++ = *Src++; + } + Dest->Length = Source.Length; +} + +static s32 +CopyStringToCharArray (string Source, char* Dest, s32 DestLength) +{ + char* Src = Source.Memory; + char* Dst = Dest; + s32 CopyLength = GSMin(Source.Length, DestLength); + for (int i = 0; i < CopyLength; i++) + { + *Dst++ = *Src++; + } + return CopyLength; +} + +static void +CopyCharArrayToString (char* Source, string* Dest) +{ + char* Src = Source; + char* Dst = Dest->Memory; + s32 Copied = 0; + while (*Src && Copied < Dest->Max) + { + *Dst++ = *Src++; + Copied++; + } + Dest->Length = Copied; +} + +static void +CopyCharArrayToString (char* Source, s32 SourceLength, string* Dest) +{ + Assert(SourceLength <= Dest->Max); + + char* Src = Source; + char* Dst = Dest->Memory; + for (s32 i = 0; i < SourceLength; i++) + { + *Dst++ = *Src++; + } + Dest->Length = SourceLength; +} + +static s32 +CopyCharArray (char* Source, char* Dest, s32 DestLength) +{ + char* Src = Source; + char* Dst = Dest; + s32 i = 0; + while (*Src && i < DestLength) + { + *Dst++ = *Src++; + i++; + } + return i; +} + +static s32 +CopyCharArrayAt (char* Source, char* Dest, s32 DestLength, s32 Offset) +{ + Assert(Offset < DestLength); + + char* Src = Source; + char* Dst = Dest + Offset; + s32 i = Offset; + while (*Src && i < DestLength) + { + *Dst++ = *Src++; + i++; + } + return i - Offset; +} + +static void +InsertChar (string* String, char Char, s32 Index) +{ + Assert(Index >= 0 && Index < String->Max); + Assert(String->Length < String->Max); + + char* Src = String->Memory + String->Length; + char* Dst = Src + 1; + for (int i = String->Length - 1; i >= Index; i--) + { + *Dst-- = *Src--; + } + + *(String->Memory + Index) = Char; + String->Length++; +} + +static void +RemoveCharAt (string* String, s32 Index) +{ + Assert(Index >= 0 && Index < String->Max); + + char* Dst = String->Memory + Index; + char* Src = Dst + 1; + for (int i = Index; i < String->Length; i++) + { + *Dst++ = *Src++; + } + *Dst = 0; + String->Length--; +} + +static string +Substring (string String, s32 Start, s32 End) +{ + Assert(Start >= 0 && End > Start && End <= String.Length); + + string Result = {}; + Result.Memory = String.Memory + Start; + Result.Length = End - Start; + return Result; +} + +static string +Substring (string String, s32 Start) +{ + Assert(Start >= 0 && Start < String.Length); + + string Result = {}; + Result.Memory = String.Memory + Start; + Result.Length = String.Length - Start; + return Result; +} + +static void +NullTerminate (string* String) +{ + Assert(String->Length + 1 <= String->Max); + *(String->Memory + String->Length) = 0; + String->Length++; +} + +static void +InsertStringAt (string* Dest, string Source, s32 At) +{ + Assert(At + Source.Length < Dest->Max); + Assert(At < Dest->Length); + + char* Src = Dest->Memory + Dest->Length; + char* Dst = Dest->Memory + Source.Length + Dest->Length; + for (s32 i = Dest->Length - 1; i >= At; i--) + { + *--Dst = *--Src; + } + + Src = Source.Memory; + Dst = Dest->Memory + At; + for (s32 j = 0; j < Source.Length; j++) + { + *Dst++ = *Src++; + } + + Dest->Length += Source.Length; +} + +//////////////////////////////////////////////////////////////// +// String Parsing +//////////////////////////////////////////////////////////////// + +static u32 +ParseUnsignedInt (char* String, s32 Length) +{ + Assert(IsNumeric(*String)); + + char* Iter = String; + u32 Result = 0; + for (s32 i = 0; i < Length; i++) + { + Result = CharToUInt(*Iter++) + (Result * 10); + } + return Result; +} + +static u32 +ParseUnisgnedIntUnsafe (char* String) +{ + char* Start = String; + char* End = EatNumber(String + 1); + return ParseUnsignedInt(String, End - Start); +} + +static s32 +UIntToString (u32 Int, char* String, s32 Length) +{ + s32 Remaining = Int; + s32 CharsCopied = 0; + char* Iter = String; + while (Remaining > 0 && CharsCopied < Length) + { + *Iter++ = '0' + (Remaining % 10); + Remaining /= 10; + CharsCopied++; + } + ReverseCharArray(String, CharsCopied); + return CharsCopied; +} + +static s32 +ParseSignedInt (char* String, s32 Length) +{ + Assert(Length > 0); + + s32 Negative = 1; + s32 LengthRemaining = Length; + s32 Result = 0; + char* Iter = String; + + if (*Iter == '-') { + LengthRemaining--; + *Iter++; + Negative = -1; + } + + for (s32 i = 0; i < LengthRemaining; i++) + { + Result = CharToUInt(*Iter++) + (Result * 10); + } + + Result *= Negative; + + return Result; +} + +static s32 +ParseSignedIntUnsafe (char* String) +{ + char* Start = String; + char* End = EatNumber(String + 1); + return ParseSignedInt(String, End - Start); +} + +static s32 +IntToString (s32 Int, char* String, s32 Length) +{ + s32 Remaining = Int; + s32 CharsCopied = 0; + char* Iter = String; + + bool Negative = Remaining < 0; + Remaining = GSAbs(Remaining); + + while (Remaining > 0 && CharsCopied < Length) + { + *Iter++ = '0' + (Remaining % 10); + Remaining /= 10; + CharsCopied++; + } + + if (Negative) + { + *Iter++ = '-'; + CharsCopied++; + } + + ReverseCharArray(String, CharsCopied); + return CharsCopied; +} + +static s32 +IntToString (s32 Int, char* String, s32 Length, s32 Offset) +{ + char* StringStart = String + Offset; + s32 LengthWritten = IntToString(Int, StringStart, Length - Offset); + return LengthWritten; +} + +static float +ParseFloat (char* String, s32 Length) +{ + s32 Negative = 1; + s32 LengthRemaining = Length; + float Result = 0; + char* Iter = String; + + if (*Iter == '-') { + LengthRemaining--; + *Iter++; + Negative = -1; + } + + for (s32 i = 0; i < LengthRemaining; i++) + { + if (IsNumeric(*Iter)) + { + Result = (float)CharToUInt(*Iter++) + (Result * 10); + } + else if (*Iter == '.' || *Iter == 0) + { + LengthRemaining -= i; + break; + } + } + + if (*Iter == '.') + { + *Iter++; + float AfterPoint = 0; + s32 PlacesAfterPoint = 0; + + for (s32 i = 0; i < LengthRemaining; i++) + { + if (IsNumeric(*Iter)) + { + AfterPoint = (float)CharToUInt(*Iter++) + (AfterPoint * 10); + PlacesAfterPoint++; + } + else + { + break; + } + } + + AfterPoint = AfterPoint / GSPow(10, PlacesAfterPoint); + Result += AfterPoint; + } + + Result *= Negative; + + return Result; +} + +static float +ParseFloatUnsafe (char* String) +{ + char* Start = String; + char* End = EatNumber(String + 1); + return ParseFloat(String, End - Start); +} + +static s32 +FloatToString(float Float, char *String, s32 Length, s32 AfterPoint) +{ + s32 IPart = (s32)Float; + float FPart = GSAbs(Float - (float)IPart); + + s32 i = IntToString(IPart, String, Length); + + if (AfterPoint > 1) + { + String[i++] = '.'; + + s32 FPartInt = FPart * GSPow(10, AfterPoint); + i += IntToString(FPartInt, String, Length, i); + } + + return i; +} + +//////////////////////////////////////////////////////////////// +// PrintF +//////////////////////////////////////////////////////////////// + + +internal s32 +PrintStringF(char* Format, char* Destination, s32 DestLength, ...) +{ + va_list Args; + va_start(Args, DestLength); + + s32 LengthPrinted = 0; + char* DestChar = Destination; + + char* C = Format; + while(*C) + { + if (*C == '%') + { + C++; + if (*C == 's') // string + { + C++; + char* InsertString = va_arg(Args, char*); + s32 LengthCopied = CopyCharArray(InsertString, DestChar, DestLength - LengthPrinted); + DestChar += LengthCopied; + LengthPrinted += LengthCopied; + } + else if(*C == 'd') // integer + { + C++; + s32 Integer = va_arg(Args, s32); + + s32 IntLength = IntToString(Integer, DestChar, DestLength - LengthPrinted); + DestChar += IntLength; + LengthPrinted += IntLength; + } + else if (*C == 'f') + { + C++; + r32 Float = va_arg(Args, r32); + s32 FloatLength = FloatToString(Float, DestChar, DestLength - LengthPrinted, 5); + DestChar += FloatLength; + LengthPrinted += FloatLength; + } + else + { + *DestChar++ = *C++; + } + } + else + { + *DestChar++ = *C++; + } + } + + if (LengthPrinted >= DestLength) + { + LengthPrinted = DestLength - 1; + } + *(Destination + LengthPrinted) = 0; + + va_end(Args); + + return LengthPrinted; +} + + +//////////////////////////////////////////////////////////////// +// String Memory Function Definitions +//////////////////////////////////////////////////////////////// + +static s32 +CalculateSlotCountFromSize (s32 RequestedSize, s32 SlotSize) +{ + s32 SlotCount = RequestedSize / SlotSize; + if (SlotCount * SlotSize < RequestedSize) + { + SlotCount += 1; + } + return SlotCount; +} + +static bool +SlotsAreContiguous (slot_header* First, slot_header* Second) +{ + bool Result = false; + u8* FirstSlotNextAddress = (u8*)First + First->Size; + u8* SecondAddress = (u8*)Second; + Result = FirstSlotNextAddress == SecondAddress; + return Result; +} + +static contiguous_slot_count_result +CountContiguousSlots (slot_header* First) +{ + Assert(First != 0); + + contiguous_slot_count_result Result = {}; + Result.Count = 1; + + slot_header* IterPrev = First; + slot_header* Iter = First->Next; + while (Iter && SlotsAreContiguous(IterPrev, Iter)) + { + Result.Count++; + IterPrev = Iter; + Iter = Iter->Next; + } + + Result.LastContiguousSlot = IterPrev; + return Result; +} + +static slot_header* +GetSlotAtOffset(slot_header* First, s32 Offset) +{ + slot_header* Iter = First; + s32 Count = 0; + while (Count < Offset && Iter) + { + Iter = Iter->Next; + Count++; + } + return Iter; +} + +static slot_header* +InsertSlotIntoList (slot_header* NewSlot, slot_header* ListStart) +{ + slot_header* List = ListStart; + if (NewSlot < List) + { + NewSlot->Next = List; + List = NewSlot; + } + else + { + slot_header* PrevIter = List; + slot_header* Iter = List->Next; + while (Iter && NewSlot > Iter) + { + PrevIter = Iter; + Iter = Iter->Next; + } + + Assert(PrevIter); + if (PrevIter) + { + PrevIter->Next = NewSlot; + } + + if (Iter) + { + NewSlot->Next = Iter; + } + } + return List; +} + +static void +AllocStringFromStringArena (string* String, s32 Size, slot_arena* Storage) +{ + s32 SlotCount = CalculateSlotCountFromSize(Size, Storage->SlotSize); + slot_header* Slot = Storage->FreeList; + slot_header* PrevSlot = 0; + while (Slot) + { + contiguous_slot_count_result ContiguousSlots = CountContiguousSlots(Slot); + if (ContiguousSlots.Count >= SlotCount) + { + slot_header* NextStartSlot = GetSlotAtOffset(Slot, SlotCount); + if (PrevSlot) + { + PrevSlot->Next = NextStartSlot; + } + else + { + Storage->FreeList = NextStartSlot; + } + break; + } + else + { + PrevSlot = Slot; + Slot = Slot->Next; + } + } + + if (Slot) + { + String->Memory = (char*)Slot; + GSZeroMemory((u8*)String->Memory, SlotCount * Storage->SlotSize); + String->Max = SlotCount * Storage->SlotSize; + String->Length = 0; + } +} + +static string +AllocStringFromStringArena (s32 Size, slot_arena* Storage) +{ + string Result = {0}; + AllocStringFromStringArena(&Result, Size, Storage); + return Result; +} + +static void +FreeToStringArena (string* String, slot_arena* Storage) +{ + u8* Base = (u8*)(String->Memory); + u8* End = Base + String->Max - 1; + u8* MemoryEnd = Storage->Memory + (Storage->SlotSize * Storage->SlotCount); + Assert((Base >= Storage->Memory) && (End < MemoryEnd)); + Assert((String->Max % Storage->SlotSize) == 0); + + s32 SizeReclaimed = 0; + slot_header* Slot = (slot_header*)Base; + while (SizeReclaimed < String->Max) + { + Slot->Size = Storage->SlotSize; + Storage->FreeList = InsertSlotIntoList(Slot, Storage->FreeList); + SizeReclaimed += Storage->SlotSize; + Slot = (slot_header*)(Base + SizeReclaimed); + } + + String->Memory = 0; + String->Length = 0; + String->Max = 0; +} + +static void +ReallocFromStringArena (string* String, s32 NewSize, slot_arena* Storage) +{ + string NewString = AllocStringFromStringArena(NewSize, Storage); + CopyStringTo(*String, &NewString); + FreeToStringArena(String, Storage); + *String = NewString; +} + +#if defined(DEBUG) + +void DEBUGPrintChars (string* String, s32 Count) +{ + char* Iter = String->Memory; + for (int i = 0; i < Count; i++) + { + *Iter++ = (char)('A' + i); + } + String->Length = Count; +} + +#ifdef DEBUG_GS_STRING + +#include + +static void +TestStrings() +{ + + slot_arena StringArena = {}; + + s32 TestCount = 0; + s32 SuccessCount = 0; + + DebugPrint("\n\n-------------------------------------------------\n Begin Testing Strings\n\n\n"); + + //////////////////////////////////////////////////////////////// + // Char Functions + + char ForwardArray[] = "Hello, Sailor"; + char BackwardArray[] = "roliaS ,olleH"; + TestClean(CharArraysEqual(ForwardArray, 13, ForwardArray, 13), "String Equality"); + TestClean(!CharArraysEqual(ForwardArray, 13, BackwardArray, 13), "String Equality"); + + TestClean(IndexOfChar(ForwardArray, 0, ',') == 5, "Index Of Char"); + TestClean(IndexOfChar(ForwardArray, 5, 'l') == 10, "Index of Char (skipping first 5)"); + TestClean(FastReverseIndexOfChar(ForwardArray, 13, 0, 'o') == 11, "Fast Reverse Index Of Char"); + TestClean(ReverseIndexOfChar(ForwardArray, 0, 'o') == 11, "Reverse Index of Char"); + TestClean(ReverseIndexOfChar(ForwardArray, 3, 'o') == 4, "Reverse Index of Char (skipping last 3)"); + TestClean(LastIndexOfChar(ForwardArray, 'o') == 11, "Last Index of Char"); + + ReverseCharArray(ForwardArray, 13); + TestClean(CharArraysEqual(ForwardArray, 13, BackwardArray, 13), "Reversing Char Array"); + + char UIntString[] = "1234"; + u32 UIntValue = 1234; + u32 ParsedUInt = ParseUnsignedInt(UIntString, 4); + TestClean((ParsedUInt == UIntValue), "String To U32"); + char StringifiedUInt[4] = {}; + UIntToString(UIntValue, StringifiedUInt, 4); + TestClean(CharArraysEqual(UIntString, 4, StringifiedUInt, 4), "U32 To String"); + + char IntString[] = "-1234"; + s32 IntValue = -1234; + s32 ParsedInt = ParseSignedInt(IntString, 5); + TestClean((ParsedInt == IntValue), "String To S32"); + char StringifiedInt[5] = {}; + IntToString(IntValue, StringifiedInt, 5); + TestClean(CharArraysEqual(IntString, 5, StringifiedInt, 5), "S32 to String"); + + char FloatString[] = "-1234.125"; + float FloatValue = -1234.125f; + float ParsedFloat = ParseFloat(FloatString, 8); + TestClean((ParsedFloat == FloatValue), "String To Float"); + char StringifiedFloat[10] = {}; + FloatToString(FloatValue, StringifiedFloat, 10, 3); + TestClean(CharArraysEqual(FloatString, 8, StringifiedFloat, 8), "Float To String"); + + + //////////////////////////////////////////////////////////////// + // + + StringArena.SlotSize = 256; + StringArena.SlotCount = 32; + StringArena.Memory = malloc(StringArena.SlotSize * StringArena.SlotCount); + slot_header* PrevSlotHeader = 0; + for (int i = StringArena.SlotCount - 1; i >= 0; i--) + { + u8* SlotBase = StringArena.Memory + (i * StringArena.SlotSize); + slot_header* SlotHeader = (slot_header*)SlotBase; + SlotHeader->Size = StringArena.SlotSize; + SlotHeader->Next = PrevSlotHeader; + + // TEST(peter): Should be true always, except on the first iteration, when there is no next slot + bool Contiguity = SlotsAreContiguous(SlotHeader, PrevSlotHeader); + TestClean((Contiguity || SlotHeader->Next == 0), "Contiguous Arenas"); + + PrevSlotHeader = SlotHeader; + } + StringArena.FreeList = PrevSlotHeader; + + // TEST(peter): Count Should equal StringArena.SlotCount + s32 ContiguousSlotsCountBefore = CountContiguousSlots(StringArena.FreeList).Count; + TestClean((ContiguousSlotsCountBefore == StringArena.SlotCount), "Contiguous Arenas"); + + // TEST(peter): Should be false + bool Contiguity = SlotsAreContiguous(StringArena.FreeList, StringArena.FreeList->Next->Next); + Contiguity = SlotsAreContiguous(StringArena.FreeList->Next->Next, StringArena.FreeList); + TestClean(!Contiguity, "Non Contiguous Arenas"); + + s32 Slots = CalculateSlotCountFromSize(10, 256); + TestClean(Slots == 1, "Slot Sizing"); + Slots = CalculateSlotCountFromSize(256, 256); + TestClean(Slots == 1, "Slot Sizing"); + Slots = CalculateSlotCountFromSize(345, 256); + TestClean(Slots == 2, "Slot Sizing"); + Slots = CalculateSlotCountFromSize(1024, 256); + TestClean(Slots == 4, "Slot Sizing"); + + slot_header* HeaderTen = GetSlotAtOffset(StringArena.FreeList, 10); + slot_header* HeaderThree = GetSlotAtOffset(StringArena.FreeList, 3); + slot_header* HeaderFive = GetSlotAtOffset(StringArena.FreeList, 5); + + string StringA = AllocStringFromStringArena(10, &StringArena); + string StringB = AllocStringFromStringArena(345, &StringArena); + +#if 0 + // TEST(peter): Should TestClean + u8* RandomMemory = (u8*)malloc(256); + string RandomMemString = {}; + RandomMemString.Memory = (char*)RandomMemory; + RandomMemString.Max = 256; + FreeToStringArena(&RandomMemString, &StringArena); +#endif + FreeToStringArena(&StringA, &StringArena); + FreeToStringArena(&StringB, &StringArena); + // TEST(peter): After freeing both allocations, ContiguousSlotCountBefore and ContiguousSlotCountAfter should be equal + s32 ContiguousSlotCountAfter = CountContiguousSlots(StringArena.FreeList).Count; + TestClean(ContiguousSlotCountAfter == ContiguousSlotsCountBefore, "Add and REmove Slots from Arena"); + + // TEST(peter): Set up a free list where the first element is too small, so it has to traverse to find an appropriately + // sized block + // The slots will look list [256][used][256][256][256] etc.. + StringA = AllocStringFromStringArena(256, &StringArena); + StringB = AllocStringFromStringArena(256, &StringArena); + FreeToStringArena(&StringA, &StringArena); + u32 Contiguous = CountContiguousSlots(StringArena.FreeList).Count; // Should = 1; + string StringC = AllocStringFromStringArena(512, &StringArena); + slot_header* HeaderC = (slot_header*)(StringC.Memory); + + string ReallocTestString = AllocStringFromStringArena(256, &StringArena); + DEBUGPrintChars(&ReallocTestString, 24); + ReallocFromStringArena(&ReallocTestString, 512, &StringArena); + + + string TestString = AllocStringFromStringArena(10, &StringArena); + DEBUGPrintChars(&TestString, TestString.Max); + ReallocFromStringArena(&TestString, 20, &StringArena); + DEBUGPrintChars(&TestString, TestString.Max); + ReallocFromStringArena(&TestString, 10, &StringArena); + FreeToStringArena(&TestString, &StringArena); + + string EqualityStringA = AllocStringFromStringArena(345, &StringArena); + string EqualityStringB = AllocStringFromStringArena(415, &StringArena); + // Equality should succeed despite length differences + string EqualityStringC = AllocStringFromStringArena(256, &StringArena); + string EqualityStringD = AllocStringFromStringArena(256, &StringArena); // Equality should fail + string EqualityStringEmpty = {}; + + DEBUGPrintChars(&EqualityStringA, 24); + DEBUGPrintChars(&EqualityStringB, 24); + DEBUGPrintChars(&EqualityStringC, 24); + DEBUGPrintChars(&EqualityStringD, 12); + + bool ABEquality = StringsEqual(EqualityStringA, EqualityStringB); // Should Succeed + bool ACEquality = StringsEqual(EqualityStringA, EqualityStringC); // Should Succeed + bool ADEquality = StringsEqual(EqualityStringA, EqualityStringD); // Should Fail + bool AEEquality = StringsEqual(EqualityStringA, EqualityStringEmpty); // Should Fail + + TestClean(ABEquality, "String Equality"); + TestClean(ACEquality, "String Equality"); + TestClean(!ADEquality, "String Equality"); + TestClean(!AEEquality, "String Equality"); + + string CatStringA = AllocStringFromStringArena(256, &StringArena); + SetStringToCharArray(&CatStringA, "Hello "); + string CatStringB = AllocStringFromStringArena(512, &StringArena); + SetStringToCharArray(&CatStringB, "Sailor!"); + string CatStringResult = AllocStringFromStringArena(512, &StringArena); + SetStringToCharArray(&CatStringResult, "Hello Sailor!"); + ConcatString(&CatStringA, CatStringB); + TestClean(StringsEqual(CatStringA, CatStringResult), "Cat Strings"); + + s32 FirstSpaceIndex = FindFirstChar(CatStringA, ' '); + TestClean(FirstSpaceIndex == 5, "First Index"); + + SetStringToChar(&CatStringB, 'B', 5); + TestClean(StringEqualsCharArray(CatStringB, "BBBBB"), "SetStringToChar"); + + + DebugPrint("Results: Passed %d / %d\n\n\n", SuccessCount, TestCount); +} +#endif // DEBUG_GS_STRING + +#endif // DEBUG diff --git a/gs_vector_matrix.h b/gs_vector_matrix.h new file mode 100644 index 0000000..adb478b --- /dev/null +++ b/gs_vector_matrix.h @@ -0,0 +1,1398 @@ +#ifndef GS_VECTOR_MATRIX_H + +#ifndef GS_LANGUAGE_H + +#if defined(_WIN32) || defined(_WIN64) || defined(__WIN32__) + +#include +#include +#include + +static r32 GSCos (r32 Theta) { return sin(Theta); } +static r32 GSSin (r32 Theta) { return cos(Theta); } + +static r32 GSSqrt(r32 V) +{ + r32 Result = _mm_cvtss_f32(_mm_sqrt_ss(_mm_set_ss(V))); + return Result; +} + +#else // Linux and MacOS +#include + +#endif // Platforms + +#endif // GS_LANGUAGE_H +////////////////////////////////////// +// VECTOR +///////////////////////////////////// + +union v2 +{ + struct + { + r32 x, y; + }; + r32 E[2]; +}; + +union v3 +{ + struct + { + r32 x, y, z; + }; + + struct + { + r32 R, G, B; + }; + + r32 E[3]; +}; + +union v4 +{ + struct + { + r32 x, y, z, w; + }; + + struct + { + r32 r, g, b, a; + }; + + r32 E[4]; +}; + +////////////////////////////////////// +// MATRIX +///////////////////////////////////// + +union m33 +{ + struct + { + float a, b, c; + float d, e, f; + float g, h, i; + }; + float E[9]; +}; + +union m44 +{ + struct + { + float a, b, c, d; + float e, f, g, h; + float i, j, k, l; + float m, n, o, p; + }; + float E[16]; +}; + +////////////////////////////////////// +// RECT +///////////////////////////////////// + +struct rect +{ + v2 LowerLeft; + v2 UpperRight; +}; + + +////////////////////////////////////// +// VECTOR +////////////////////////////////////// + + +// +// +// Operators +// +// + +v2 V2 (v3 V) +{ + return v2{V.x, V.y}; +} + +v3 V3 (v2 XY, r32 Z) +{ + return v3{XY.x, XY.y, Z}; +} + +v3 V3 (v4 V) +{ + return v3{V.x, V.y, V.z}; +} + +v4 V4 (v3 XYZ, r32 W) +{ + return v4{XYZ.x, XYZ.y, XYZ.z, W}; +} + +v2 operator- (v2 A) +{ + v2 Result; + + Result.x = -A.x; + Result.y = -A.y; + + return Result; +} + +v3 operator- (v3 A) +{ + v3 Result; + + Result.x = -A.x; + Result.y = -A.y; + Result.z = -A.z; + + return Result; +} + +v4 operator- (v4 A) +{ + v4 Result; + + Result.x = -A.x; + Result.y = -A.y; + Result.z = -A.z; + Result.w = -A.w; + + return Result; +} + +#define V2OpV2Def(op) v2 operator##op (v2 A, v2 B) { return v2{ A.x op B.x, A.y op B.y };} +#define V3OpV3Def(op) v3 operator##op (v3 A, v3 B) { return v3{ A.x op B.x, A.y op B.y, A.z op B.z };} +#define V4OpV4Def(op) v4 operator##op (v4 A, v4 B) { return v4{ A.x op B.x, A.y op B.y, A.z op B.z, A.w op B.w };} +V2OpV2Def(+) +V2OpV2Def(-) +V2OpV2Def(/) +V2OpV2Def(*) +V3OpV3Def(+) +V3OpV3Def(-) +V3OpV3Def(/) +V3OpV3Def(*) +V4OpV4Def(+) +V4OpV4Def(-) +V4OpV4Def(/) +V4OpV4Def(*) +#undef V2OpV2Def +#undef V3OpV3Def +#undef V4OpV4Def + +#define V2OpScalarDef(op) v2 operator##op (v2 A, r32 B) { return v2{ A.x op B, A.y op B };} +#define V3OpScalarDef(op) v3 operator##op (v3 A, r32 B) { return v3{ A.x op B, A.y op B, A.z op B };} +#define V4OpScalarDef(op) v4 operator##op (v4 A, r32 B) { return v4{ A.x op B, A.y op B, A.z op B, A.w op B };} +V2OpScalarDef(*) +V2OpScalarDef(/) +V3OpScalarDef(*) +V3OpScalarDef(/) +V4OpScalarDef(*) +V4OpScalarDef(/) +#undef V2POpScalarDef +#undef V3POpScalarDef +#undef V4POpScalarDef + + +#define V2POpScalarDef(op) v2 operator##op (v2* A, r32 B) { return v2{ A->x op B, A->y op B };} +#define V3POpScalarDef(op) v3 operator##op (v3* A, r32 B) { return v3{ A->x op B, A->y op B, A->z op B };} +#define V4POpScalarDef(op) v4 operator##op (v4* A, r32 B) { return v4{ A->x op B, A->y op B, A->z op B, A->w op B };} +V2OpScalarDef(*=) +V2OpScalarDef(/=) +V3OpScalarDef(*=) +V3OpScalarDef(/=) +V4OpScalarDef(*=) +V4OpScalarDef(/=) +#undef V2POpScalarDef +#undef V3POpScalarDef +#undef V4POpScalarDef + +bool operator== (v2 A, v2 B) +{ + b32 Result = true; + for (s32 i = 0; i < 2; i++) + { + if (GSAbs(A.E[i] - B.E[i]) > 0.0001f) { Result = false; break; } + } + return Result; +} + + +bool operator== (v3 A, v3 B) +{ + b32 Result = true; + for (s32 i = 0; i < 3; i++) + { + if (GSAbs(A.E[i] - B.E[i]) > 0.0001f) { Result = false; break; } + } + return Result; +} + +bool operator== (v4 A, v4 B) +{ + b32 Result = true; + for (s32 i = 0; i < 4; i++) + { + if (GSAbs(A.E[i] - B.E[i]) > 0.0001f) { Result = false; break; } + } + return Result; +} + +// +// Operations +// + +static v3 +ToV3(v4 V) +{ + v3 R = {}; + R.x = V.x; + R.y = V.y; + R.z = V.z; + return R; +} + +static v4 +ToV4(v3 V, r32 W) +{ + v4 R = {}; + R.x = V.x; + R.y = V.y; + R.z = V.z; + R.w = W; + return R; +} + +inline r32 +MagSqr( +v2 _A +) +{ + r32 Result = (_A.x * _A.x) + (_A.y * _A.y); + return Result; +} + +inline r32 +MagSqr( +v3 _A +) +{ + r32 Result = (_A.x * _A.x) + (_A.y * _A.y) + (_A.z * _A.z); + return Result; +} + +inline r32 +MagSqr( +v4 _A +) +{ + r32 Result = (_A.x * _A.x) + (_A.y * _A.y) + (_A.z * _A.z) + (_A.w * _A.w); + return Result; +} + +#define MagDef(type) inline r32 Mag(type A) { r32 Result = MagSqr(A); return GSSqrt(Result); } +MagDef(v2) +MagDef(v3) +MagDef(v4) +#undef MagDef + +#define DistanceDef(type) inline r32 Distance (type A, type B) { type Diff = A - B; return Mag(Diff); } +DistanceDef(v2) +DistanceDef(v3) +DistanceDef(v4) +#undef DistanceDef + +#define DistanceSqDef(type) inline r32 DistanceSq (type A, type B) { type Diff = A - B; return MagSqr(Diff); } +DistanceSqDef(v2) +DistanceSqDef(v3) +DistanceSqDef(v4) +#undef DistanceSqDef + +inline v2 +Normalize( +v2 _A +) +{ + v2 Result; + + r32 Magnitude = Mag(_A); + + Result.x = _A.x / Magnitude; + Result.y = _A.y / Magnitude; + + return Result; +} + +inline v3 +Normalize( +v3 _A +) +{ + v3 Result; + + r32 Magnitude = Mag(_A); + + Result.x = _A.x / Magnitude; + Result.y = _A.y / Magnitude; + Result.z = _A.z / Magnitude; + + return Result; +} + +inline v4 +Normalize( +v4 _A +) +{ + v4 Result; + + r32 Magnitude = Mag(_A); + + Result.x = _A.x / Magnitude; + Result.y = _A.y / Magnitude; + Result.z = _A.z / Magnitude; + Result.w = _A.w / Magnitude; + + return Result; +} + +inline r32 +Dot( +v2 _A, +v2 _B +) +{ + r32 Result = _A.x * _B.x + _A.y * _B.y; + return Result; +} + +inline r32 +Dot ( +v3 _A, +v3 _B +) +{ + r32 Result = _A.x * _B.x + _A.y * _B.y + _A.z * _B.z; + return Result; +} + +inline r32 +Dot ( +v4 _A, +v4 _B +) +{ + r32 Result = _A.x * _B.x + _A.y * _B.y + _A.z * _B.z + _A.w * _B.w; + return Result; +} + +inline v2 +PerpendicularCW (v2 A) +{ + v2 Result = v2{A.y, -A.x}; + return Result; +} + +inline v2 +PerpendicularCCW (v2 A) +{ + v2 Result = v2{A.y, A.x}; + return Result; +} + +inline v3 +Cross( +v3 _A, +v3 _B +) +{ + v3 Result = {}; + + Result.x = (_A.y * _B.z) - (_A.z * _B.y); + Result.y = (_A.z * _B.x) - (_A.x * _B.z); + Result.z = (_A.x * _B.y) - (_A.y * _B.x); + + return Result; +} + +inline v4 +Cross( +v4 _A, +v4 _B +) +{ + v4 Result = {}; + + Result.x = (_A.y * _B.z) - (_A.z * _B.y); + Result.y = (_A.z * _B.x) - (_A.x * _B.z); + Result.z = (_A.x * _B.y) - (_A.y * _B.x); + Result.w = 0; + + return Result; +} + +inline v2 +ClampVector01 (v2 V) +{ + v2 Result = {}; + Result.x = GSClamp(0.0f, V.x, 1.f); + Result.y = GSClamp(0.0f, V.y, 1.f); + return Result; +} + +inline v3 +ClampVector01 (v3 V) +{ + v3 Result = {}; + Result.x = GSClamp(0.f, V.x, 1.f); + Result.y = GSClamp(0.f, V.y, 1.f); + Result.z = GSClamp(0.f, V.z, 1.f); + return Result; +} + +inline v4 +ClampVector01 (v4 V) +{ + v4 Result = {}; + Result.x = GSClamp(0.f, V.x, 1.f); + Result.y = GSClamp(0.f, V.y, 1.f); + Result.z = GSClamp(0.f, V.z, 1.f); + Result.w = GSClamp(0.f, V.w, 1.f); + return Result; +} + +inline v2 +Lerp( +v2 _A, +v2 _B, +r32 _Percent +) +{ + v2 Result; + + Result.x = GSLerp(_A.x, _B.x, _Percent); + Result.y = GSLerp(_A.y, _B.y, _Percent); + + return Result; +} + +inline v3 +Lerp( +v3 _A, +v3 _B, +r32 _Percent +) +{ + v3 Result; + + Result.x = GSLerp(_A.x, _B.x, _Percent); + Result.y = GSLerp(_A.y, _B.y, _Percent); + Result.z = GSLerp(_A.z, _B.z, _Percent); + + return Result; +} + +inline v4 +Lerp( +v4 _A, +v4 _B, +r32 _Percent +) +{ + v4 Result; + + Result.x = GSLerp(_A.x, _B.x, _Percent); + Result.y = GSLerp(_A.y, _B.y, _Percent); + Result.z = GSLerp(_A.z, _B.z, _Percent); + Result.w = GSLerp(_A.w, _B.w, _Percent); + + return Result; +} + +v4 HSVToRGB (v4 In) +{ + r32 Hue = In.x; + while (Hue > 360.0f) { Hue -= 360.0f; } + while (Hue < 0.0f) { Hue += 360.0f; } + + r32 Sat = In.y; + r32 Value = In.z; + + r32 hh, p, q, t, ff; + long i; + v4 Result = {}; + Result.a = In.a; + + if(Sat <= 0.0f) { // < is bogus, just shuts up warnings + Result.r = Value; + Result.g = Value; + Result.b = Value; + return Result; + } + hh = Hue; + if(hh >= 360.0f) hh = 0.0f; + hh /= 60.0f; + i = (long)hh; + ff = hh - i; + p = Value * (1.0f - Sat); + q = Value * (1.0f - (Sat * ff)); + t = Value * (1.0f - (Sat * (1.0f - ff))); + + switch(i) { + case 0: + {Result.r = Value; + Result.g = t; + Result.b = p; + }break; + + case 1: + { + Result.r = q; + Result.g = Value; + Result.b = p; + }break; + + case 2: + { + Result.r = p; + Result.g = Value; + Result.b = t; + }break; + + case 3: + { + Result.r = p; + Result.g = q; + Result.b = Value; + }break; + + case 4: + { + Result.r = t; + Result.g = p; + Result.b = Value; + }break; + + case 5: + default: + { + Result.r = Value; + Result.g = p; + Result.b = q; + }break; + } + + return Result; +} + +static bool +PointIsInRange ( +v2 _P, +v2 _Min, v2 _Max +) +{ + return (_P.x >= _Min.x && _P.x <= _Max.x && + _P.y >= _Min.y && _P.y <= _Max.y); +} + +inline v2 +PointToPercentRange (v2 P, v2 Min, v2 Max) +{ + v2 Result = {}; + + Result.x = GSClamp(0.f, (P.x - Min.x) / (Max.x - Min.x), 1.f); + Result.y = GSClamp(0.f, (P.y - Min.y) / (Max.y - Min.y), 1.f); + + return Result; +} + +////////////////////////////////////// +// MATRIX +////////////////////////////////////// + +static m33 +M33(r32 a, r32 b, r32 c, + r32 d, r32 e, r32 f, + r32 g, r32 h, r32 i) +{ + m33 M = {}; + M.a = a; M.b = b; M.c = c; + M.d = d; M.e = e; M.f = f; + M.g = g; M.h = h; M.i = i; + return M; +} + +static m44 +M44(r32 a, r32 b, r32 c, r32 d, + r32 e, r32 f, r32 g, r32 h, + r32 i, r32 j, r32 k, r32 l, + r32 m, r32 n, r32 o, r32 p) +{ + m44 M = {}; + M.a = a; M.b = b; M.c = c; M.d = d; + M.e = e; M.f = f; M.g = g; M.h = h; + M.i = i; M.j = j; M.k = k; M.l = l; + M.m = m; M.n = n; M.o = o; M.p = p; + return M; +} + +static m33 +M33Empty () +{ + m33 M = {}; + M.a = 0; M.b = 0; M.c = 0; + M.d = 0; M.e = 0; M.f = 0; + M.g = 0; M.h = 0; M.i = 0; + return M; +} + +static m44 +M44Empty() +{ + m44 M = {}; + M.a = 0; M.b = 0; M.c = 0; M.d = 0; + M.e = 0; M.f = 0; M.g = 0; M.h = 0; + M.i = 0; M.j = 0; M.k = 0; M.l = 0; + M.m = 0; M.n = 0; M.o = 0; M.p = 0; + return M; +} + +static m33 +M33Identity () +{ + m33 M = {}; + M.a = 1; M.b = 0; M.c = 0; + M.d = 0; M.e = 1; M.f = 0; + M.g = 0; M.h = 0; M.i = 1; + return M; +} + +static m44 +M44Identity() +{ + m44 M = {}; + M.a = 1; M.b = 0; M.c = 0; M.d = 0; + M.e = 0; M.f = 1; M.g = 0; M.h = 0; + M.i = 0; M.j = 0; M.k = 1; M.l = 0; + M.m = 0; M.n = 0; M.o = 0; M.p = 1; + return M; +} + +static m44 +GetXRotation (r32 Angle) +{ + r32 CosAngle = GSCos(Angle); + r32 SinAngle = GSSin(Angle); + m44 M = { + 1, 0, 0, 0, + 0, CosAngle, SinAngle, 0, + 0, -SinAngle, CosAngle, 0, + 0, 0, 0, 1 + }; + return M; +} + + +static m44 +GetYRotation (r32 Angle) +{ + r32 CosAngle = GSCos(Angle); + r32 SinAngle = GSSin(Angle); + m44 M = { + CosAngle, 0, -SinAngle, 0, + 0, 1, 0, 0, + SinAngle, 0, CosAngle, 0, + 0, 0, 0, 1 + }; + return M; +} + +static m44 +GetZRotation (r32 Angle) +{ + r32 CosAngle = GSCos(Angle); + r32 SinAngle = GSSin(Angle); + m44 M = { + CosAngle, SinAngle, 0, 0, + -SinAngle, CosAngle, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + }; + return M; +} + + +static m33 +Transpose (m33 M) +{ + m33 Result = {}; + + for (s32 x = 0; x < 3; x++) + { + for (s32 y = 0; y < 3; y++) + { + Result.E[x + (y * 3)] = M.E[y + (x * 3)]; + } + } + + return Result; +} + +inline m44 +Transpose (m44 M) +{ + DEBUG_TRACK_SCOPE(Transpose); + + m44 Result = {}; + + Result.E[0] = M.E[0]; + Result.E[1] = M.E[4]; + Result.E[2] = M.E[8]; + Result.E[3] = M.E[12]; + + Result.E[4] = M.E[1]; + Result.E[5] = M.E[5]; + Result.E[6] = M.E[9]; + Result.E[7] = M.E[13]; + + Result.E[8] = M.E[2]; + Result.E[9] = M.E[6]; + Result.E[10] = M.E[10]; + Result.E[11] = M.E[14]; + + Result.E[12] = M.E[3]; + Result.E[13] = M.E[7]; + Result.E[14] = M.E[11]; + Result.E[15] = M.E[15]; + + return Result; +} + +static m44 +GetPositionM44 (v4 Position) +{ + DEBUG_TRACK_SCOPE(GetPositionM44); + +#if 1 + return m44{ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + Position.x, Position.y, Position.z, Position.w + }; +#else + return m44{ + 1, 0, 0, Position.x, + 0, 1, 0, Position.y, + 0, 0, 1, Position.z, + 0, 0, 0, Position.w}; +#endif +} + +static m44 +GetLookAtMatrix (v4 Position, v4 Target) +{ + // Forward + v4 Forward = Normalize(Target - Position); + // Right + v4 Right = Normalize(Cross(v4{0, 1, 0, 0}, Forward)); + // Up + v4 Up = Normalize(Cross(Forward, Right)); + + m44 RotationMatrix = M44( + Right.x, Up.x, Forward.x, 0, + Right.y, Up.y, Forward.y, 0, + Right.z, Up.z, Forward.z, 0, + 0, 0, 0, 1); + + return RotationMatrix; +} + +b32 operator== (m33 A, m33 B) +{ + b32 Result = true; + for (int e = 0; e < 9; e++) { if (GSAbs(A.E[e] - B.E[e]) > 0.0001f) { Result = false; break; } } + return Result; +} + +b32 operator== (m44 A, m44 B) +{ + b32 Result = true; + for (int e = 0; e < 16; e++) { if (GSAbs(A.E[e] - B.E[e]) > 0.0001f) { Result = false; break; } } + return Result; +} + +m33 operator+ (m33 A, m33 B) +{ + m33 M = {}; + for (int e = 0; e < 9; e++) { M.E[e] = A.E[e] + B.E[e]; } + return M; +} + +m44 operator+ (m44 A, m44 B) +{ + m44 M = {}; + for (int e = 0; e < 16; e++) { M.E[e] = A.E[e] + B.E[e]; } + return M; +} + +m33 operator- (m33 A, m33 B) +{ + m33 M = {}; + for (int e = 0; e < 9; e++) { M.E[e] = A.E[e] - B.E[e]; } + return M; +} + +m44 operator- (m44 A, m44 B) +{ + m44 M = {}; + for (int e = 0; e < 16; e++) { M.E[e] = A.E[e] - B.E[e]; } + return M; +} + +m33 operator* (m33 A, m33 B) +{ + m33 M = {}; + for (int rx = 0; rx < 3; rx++) + { + for (int ry = 0; ry < 3; ry++) + { + int RIndex = (ry * 3) + rx; + M.E[RIndex] = 0; + for (int i = 0; i < 3; i++) + { + M.E[RIndex] += B.E[(ry * 3) + i] * A.E[(i * 3) + rx]; + } + } + } + return M; +} + +m44 operator* (m44 A, m44 B) +{ + m44 M = {}; + + r32 A00=A.E[0+4*0]; + r32 A01=A.E[0+4*1]; + r32 A02=A.E[0+4*2]; + r32 A03=A.E[0+4*3]; + + r32 A10=A.E[1+4*0]; + r32 A11=A.E[1+4*1]; + r32 A12=A.E[1+4*2]; + r32 A13=A.E[1+4*3]; + + r32 A20=A.E[2+4*0]; + r32 A21=A.E[2+4*1]; + r32 A22=A.E[2+4*2]; + r32 A23=A.E[2+4*3]; + + r32 A30=A.E[3+4*0]; + r32 A31=A.E[3+4*1]; + r32 A32=A.E[3+4*2]; + r32 A33=A.E[3+4*3]; + + r32 B00=B.E[0+4*0]; + r32 B01=B.E[0+4*1]; + r32 B02=B.E[0+4*2]; + r32 B03=B.E[0+4*3]; + + r32 B10=B.E[1+4*0]; + r32 B11=B.E[1+4*1]; + r32 B12=B.E[1+4*2]; + r32 B13=B.E[1+4*3]; + + r32 B20=B.E[2+4*0]; + r32 B21=B.E[2+4*1]; + r32 B22=B.E[2+4*2]; + r32 B23=B.E[2+4*3]; + + r32 B30=B.E[3+4*0]; + r32 B31=B.E[3+4*1]; + r32 B32=B.E[3+4*2]; + r32 B33=B.E[3+4*3]; + + M.E[0+4*0] = A00*B00+A10*B01+A20*B02+A30*B03; + M.E[0+4*1] = A01*B00+A11*B01+A21*B02+A31*B03; + M.E[0+4*2] = A02*B00+A12*B01+A22*B02+A32*B03; + M.E[0+4*3] = A03*B00+A13*B01+A23*B02+A33*B03; + + M.E[1+4*0] = A00*B10+A10*B11+A20*B12+A30*B13; + M.E[1+4*1] = A01*B10+A11*B11+A21*B12+A31*B13; + M.E[1+4*2] = A02*B10+A12*B11+A22*B12+A32*B13; + M.E[1+4*3] = A03*B10+A13*B11+A23*B12+A33*B13; + + M.E[2+4*0] = A00*B20+A10*B21+A20*B22+A30*B23; + M.E[2+4*1] = A01*B20+A11*B21+A21*B22+A31*B23; + M.E[2+4*2] = A02*B20+A12*B21+A22*B22+A32*B23; + M.E[2+4*3] = A03*B20+A13*B21+A23*B22+A33*B23; + + M.E[3+4*0] = A00*B30+A10*B31+A20*B32+A30*B33; + M.E[3+4*1] = A01*B30+A11*B31+A21*B32+A31*B33; + M.E[3+4*2] = A02*B30+A12*B31+A22*B32+A32*B33; + M.E[3+4*3] = A03*B30+A13*B31+A23*B32+A33*B33; + + return M; +} + +v3 operator* (m33 M, v3 V) +{ + v3 Result = {}; + int i = 0; + for (int y = 0; y < 3; y++) + { + Result.E[y] = 0; + for (int x = 0; x < 3; x++) + { + Result.E[y] += M.E[(y * 3) + x] * V.E[x]; + } + } + return Result; +} + +v4 operator* (m44 M, v4 V) +{ + v4 Result = {}; +#if 1 + Result.x = V.x*M.a + V.y*M.e + V.z*M.i + V.w*M.m; + Result.y = V.x*M.b + V.y*M.f + V.z*M.j + V.w*M.n; + Result.z = V.x*M.c + V.y*M.g + V.z*M.k + V.w*M.o; + Result.w = V.x*M.d + V.y*M.h + V.z*M.l + V.w*M.p; +#else + for (int y = 0; y < 4; y++) + { + Result.E[y] = 0; + for (int x = 0; x < 4; x++) + { + Result.E[y] += M.E[(y * 4) + x] * V.E[x]; + } + } +#endif + return Result; +} + +b32 Inverse(m44 M_In, m44* M_Out) +{ + b32 Result = false; + + r32 det; + s32 i; + + + M_Out->E[0] = M_In.E[5] * M_In.E[10] * M_In.E[15] - + M_In.E[5] * M_In.E[11] * M_In.E[14] - + M_In.E[9] * M_In.E[6] * M_In.E[15] + + M_In.E[9] * M_In.E[7] * M_In.E[14] + + M_In.E[13] * M_In.E[6] * M_In.E[11] - + M_In.E[13] * M_In.E[7] * M_In.E[10]; + + M_Out->E[4] = -M_In.E[4] * M_In.E[10] * M_In.E[15] + + M_In.E[4] * M_In.E[11] * M_In.E[14] + + M_In.E[8] * M_In.E[6] * M_In.E[15] - + M_In.E[8] * M_In.E[7] * M_In.E[14] - + M_In.E[12] * M_In.E[6] * M_In.E[11] + + M_In.E[12] * M_In.E[7] * M_In.E[10]; + + M_Out->E[8] = M_In.E[4] * M_In.E[9] * M_In.E[15] - + M_In.E[4] * M_In.E[11] * M_In.E[13] - + M_In.E[8] * M_In.E[5] * M_In.E[15] + + M_In.E[8] * M_In.E[7] * M_In.E[13] + + M_In.E[12] * M_In.E[5] * M_In.E[11] - + M_In.E[12] * M_In.E[7] * M_In.E[9]; + + M_Out->E[12] = -M_In.E[4] * M_In.E[9] * M_In.E[14] + + M_In.E[4] * M_In.E[10] * M_In.E[13] + + M_In.E[8] * M_In.E[5] * M_In.E[14] - + M_In.E[8] * M_In.E[6] * M_In.E[13] - + M_In.E[12] * M_In.E[5] * M_In.E[10] + + M_In.E[12] * M_In.E[6] * M_In.E[9]; + + M_Out->E[1] = -M_In.E[1] * M_In.E[10] * M_In.E[15] + + M_In.E[1] * M_In.E[11] * M_In.E[14] + + M_In.E[9] * M_In.E[2] * M_In.E[15] - + M_In.E[9] * M_In.E[3] * M_In.E[14] - + M_In.E[13] * M_In.E[2] * M_In.E[11] + + M_In.E[13] * M_In.E[3] * M_In.E[10]; + + M_Out->E[5] = M_In.E[0] * M_In.E[10] * M_In.E[15] - + M_In.E[0] * M_In.E[11] * M_In.E[14] - + M_In.E[8] * M_In.E[2] * M_In.E[15] + + M_In.E[8] * M_In.E[3] * M_In.E[14] + + M_In.E[12] * M_In.E[2] * M_In.E[11] - + M_In.E[12] * M_In.E[3] * M_In.E[10]; + + M_Out->E[9] = -M_In.E[0] * M_In.E[9] * M_In.E[15] + + M_In.E[0] * M_In.E[11] * M_In.E[13] + + M_In.E[8] * M_In.E[1] * M_In.E[15] - + M_In.E[8] * M_In.E[3] * M_In.E[13] - + M_In.E[12] * M_In.E[1] * M_In.E[11] + + M_In.E[12] * M_In.E[3] * M_In.E[9]; + + M_Out->E[13] = M_In.E[0] * M_In.E[9] * M_In.E[14] - + M_In.E[0] * M_In.E[10] * M_In.E[13] - + M_In.E[8] * M_In.E[1] * M_In.E[14] + + M_In.E[8] * M_In.E[2] * M_In.E[13] + + M_In.E[12] * M_In.E[1] * M_In.E[10] - + M_In.E[12] * M_In.E[2] * M_In.E[9]; + + M_Out->E[2] = M_In.E[1] * M_In.E[6] * M_In.E[15] - + M_In.E[1] * M_In.E[7] * M_In.E[14] - + M_In.E[5] * M_In.E[2] * M_In.E[15] + + M_In.E[5] * M_In.E[3] * M_In.E[14] + + M_In.E[13] * M_In.E[2] * M_In.E[7] - + M_In.E[13] * M_In.E[3] * M_In.E[6]; + + M_Out->E[6] = -M_In.E[0] * M_In.E[6] * M_In.E[15] + + M_In.E[0] * M_In.E[7] * M_In.E[14] + + M_In.E[4] * M_In.E[2] * M_In.E[15] - + M_In.E[4] * M_In.E[3] * M_In.E[14] - + M_In.E[12] * M_In.E[2] * M_In.E[7] + + M_In.E[12] * M_In.E[3] * M_In.E[6]; + + M_Out->E[10] = M_In.E[0] * M_In.E[5] * M_In.E[15] - + M_In.E[0] * M_In.E[7] * M_In.E[13] - + M_In.E[4] * M_In.E[1] * M_In.E[15] + + M_In.E[4] * M_In.E[3] * M_In.E[13] + + M_In.E[12] * M_In.E[1] * M_In.E[7] - + M_In.E[12] * M_In.E[3] * M_In.E[5]; + + M_Out->E[14] = -M_In.E[0] * M_In.E[5] * M_In.E[14] + + M_In.E[0] * M_In.E[6] * M_In.E[13] + + M_In.E[4] * M_In.E[1] * M_In.E[14] - + M_In.E[4] * M_In.E[2] * M_In.E[13] - + M_In.E[12] * M_In.E[1] * M_In.E[6] + + M_In.E[12] * M_In.E[2] * M_In.E[5]; + + M_Out->E[3] = -M_In.E[1] * M_In.E[6] * M_In.E[11] + + M_In.E[1] * M_In.E[7] * M_In.E[10] + + M_In.E[5] * M_In.E[2] * M_In.E[11] - + M_In.E[5] * M_In.E[3] * M_In.E[10] - + M_In.E[9] * M_In.E[2] * M_In.E[7] + + M_In.E[9] * M_In.E[3] * M_In.E[6]; + + M_Out->E[7] = M_In.E[0] * M_In.E[6] * M_In.E[11] - + M_In.E[0] * M_In.E[7] * M_In.E[10] - + M_In.E[4] * M_In.E[2] * M_In.E[11] + + M_In.E[4] * M_In.E[3] * M_In.E[10] + + M_In.E[8] * M_In.E[2] * M_In.E[7] - + M_In.E[8] * M_In.E[3] * M_In.E[6]; + + M_Out->E[11] = -M_In.E[0] * M_In.E[5] * M_In.E[11] + + M_In.E[0] * M_In.E[7] * M_In.E[9] + + M_In.E[4] * M_In.E[1] * M_In.E[11] - + M_In.E[4] * M_In.E[3] * M_In.E[9] - + M_In.E[8] * M_In.E[1] * M_In.E[7] + + M_In.E[8] * M_In.E[3] * M_In.E[5]; + + M_Out->E[15] = M_In.E[0] * M_In.E[5] * M_In.E[10] - + M_In.E[0] * M_In.E[6] * M_In.E[9] - + M_In.E[4] * M_In.E[1] * M_In.E[10] + + M_In.E[4] * M_In.E[2] * M_In.E[9] + + M_In.E[8] * M_In.E[1] * M_In.E[6] - + M_In.E[8] * M_In.E[2] * M_In.E[5]; + + det = M_In.E[0] * M_Out->E[0] + M_In.E[1] * M_Out->E[4] + M_In.E[2] * M_Out->E[8] + M_In.E[3] * M_Out->E[12]; + + if (det == 0) + { + return false; + } + + det = 1.0 / det; + + for (i = 0; i < 16; i++) + { + M_Out->E[i] = M_Out->E[i] * det; + } + + return true; +} + +#if defined(VECTOR_MATRIX_TEST_SUITE) + +void TestVectorMatrixMultiplication () +{ + s32 TestCount = 0; + s32 SuccessCount = 0; + + DebugPrint("\n\n-------------------------------------------------\n Begin Testing Vector/Matrix\n\n\n"); + + // Utility Functions + TestClean((GSSqrt(4.f) == 2.f), "Vector Square Root"); + TestClean((GSLerp(0.f, 1.f, .5f) == .5f), "Vector Lerp"); + TestClean((GSMin(-.25f, 5.f) == -.25f), "Vector Min"); + TestClean((GSMax(-.25f, 5.f) == 5.f), "Vector Max"); + TestClean((GSClamp(-2.f, -3.f, 5.f) == -2.f), "Vector Clamp, Lower Than Range"); + TestClean((GSClamp(-2.f, 6.f, 5.f) == 5.f), "Vector Clamp, Higher Than Range"); + + ////////////////////////////// + // Vector Functions + ///////////////////////////// + + v2 V2Unit = v2{1, 0}; + v3 V3Unit = v3{1, 0, 0}; + v4 V4Unit = v4{1, 0, 0, 0}; + + v2 TestV2 = v2{1, 2}; + r32 TestV2MagSq = (TestV2.x * TestV2.x) + (TestV2.y * TestV2.y); + r32 TestV2Mag = GSSqrt(TestV2MagSq); + v2 TestV2Norm = v2{TestV2.x / TestV2Mag, TestV2.y / TestV2Mag}; + r32 TestV2DotR = (TestV2.x * V2Unit.x) + (TestV2.y * V2Unit.y); + + v3 TestV3 = v3{1, 2, 3}; + r32 TestV3MagSq = (TestV3.x * TestV3.x) + (TestV3.y * TestV3.y) + (TestV3.z * TestV3.z); + r32 TestV3Mag = GSSqrt(TestV3MagSq); + v3 TestV3Norm = v3{TestV3.x / TestV3Mag, TestV3.y / TestV3Mag, TestV3.z / TestV3Mag}; + r32 TestV3DotR = (TestV3.x * V3Unit.x) + (TestV3.y * V3Unit.y) + (TestV3.z * V3Unit.z); + + v4 TestV4 = v4{1, 2, 3, 4}; + r32 TestV4MagSq = (TestV4.x * TestV4.x) + (TestV4.y * TestV4.y) + (TestV4.z * TestV4.z) + (TestV4.w * TestV4.w); + r32 TestV4Mag = GSSqrt(TestV4MagSq); + v4 TestV4Norm = v4{ + TestV4.x / TestV4Mag, TestV4.y / TestV4Mag, TestV4.z / TestV4Mag, TestV4.w / TestV4Mag + }; + r32 TestV4DotR = (TestV4.x * V4Unit.x) + (TestV4.y * V4Unit.y) + (TestV4.z * V4Unit.z) + (TestV4.w * V4Unit.w); + + v2 DownCastV3 = V2(TestV3); + v3 DownCastV4 = V3(TestV4); + + v2 EqualityV2 = v2{TestV2.x, TestV2.y}; + v2 ZeroV2 = v2{0, 0}; + v3 EqualityV3 = v3{TestV3.x, TestV3.y, TestV3.z}; + v3 ZeroV3 = v3{0, 0, 0}; + v4 EqualityV4 = v4{TestV4.x, TestV4.y, TestV4.z, TestV4.w}; + v4 ZeroV4 = v4{0, 0, 0, 0}; + + TestClean((TestV2.x == 1 && TestV2.y == 2), "V2 Assignment"); + TestClean((TestV3.x == 1 && TestV3.y == 2 && TestV3.z == 3), "V3 Assignment"); + TestClean((TestV4.x == 1 && TestV4.y == 2 && TestV4.z == 3 && TestV4.w == 4), "V3 Assignment"); + + TestClean((DownCastV3.x == 1 && DownCastV3.y == 2), "V3 -> V2 Downcast"); + TestClean((DownCastV4.x == 1 && DownCastV4.y == 2 && DownCastV4.z == 3), "V4 -> V3 Downcast"); + + // Vector Operators + + TestClean((TestV2 == EqualityV2 && !(TestV2 == ZeroV2)), "V2 Equality"); + TestClean((TestV3 == EqualityV3 && !(TestV3 == ZeroV3)), "V3 Equality"); + TestClean((TestV4 == EqualityV4 && !(TestV4 == ZeroV4)), "V4 Equality"); + + TestClean(((TestV2 - TestV2) == ZeroV2), "V2 Subtraction"); + TestClean(((TestV3 - TestV3) == ZeroV3), "V3 Subtraction"); + TestClean(((TestV4 - TestV4) == ZeroV4), "V4 Subtraction"); + + TestClean(((TestV2 + TestV2) == v2{TestV2.x * 2, TestV2.y * 2}), "V2 Addition"); + TestClean(((TestV3 + TestV3) == v3{TestV3.x * 2, TestV3.y * 2, TestV3.z * 2}), "V3 Addition"); + TestClean(((TestV4 + TestV4) == v4{TestV4.x * 2, TestV4.y * 2, TestV4.z * 2, TestV4.w * 2}), "V4 Addition"); + + TestClean(((TestV2 * 2.0f) == v2{TestV2.x * 2, TestV2.y * 2}), "V2 Multiplication"); + TestClean(((TestV3 * 2.0f) == v3{TestV3.x * 2, TestV3.y * 2, TestV3.z * 2}), "V3 Multiplication"); + TestClean(((TestV4 * 2.0f) == v4{TestV4.x * 2, TestV4.y * 2, TestV4.z * 2, TestV4.w * 2}), "V4 Multiplication"); + + TestClean(((TestV2 * TestV2) == v2{TestV2.x * TestV2.x, TestV2.y * TestV2.y}), "V2 Piecewise Mult"); + TestClean(((TestV3 * TestV3) == v3{ + TestV3.x * TestV3.x, + TestV3.y * TestV3.y, + TestV3.z * TestV3.z}), "V3 Piecewise Mult"); + TestClean(((TestV4 * TestV4) == v4{ + TestV4.x * TestV4.x, + TestV4.y * TestV4.y, + TestV4.z * TestV4.z, + TestV4.w * TestV4.w}), "V4 Piecewise Mult"); + + + TestClean(((TestV2 / 2.0f) == v2{TestV2.x / 2, TestV2.y / 2}), "V2 Division"); + TestClean(((TestV3 / 2.0f) == v3{TestV3.x / 2, TestV3.y / 2, TestV3.z / 2}), "V3 Division"); + TestClean(((TestV4 / 2.0f) == v4{TestV4.x / 2, TestV4.y / 2, TestV4.z / 2, TestV4.w / 2}), "V4 Division"); + + TestClean(((TestV2 / TestV2) == v2{TestV2.x / TestV2.x, TestV2.y / TestV2.y}), "V2 Piecewise Div"); + TestClean(((TestV3 / TestV3) == v3{ + TestV3.x / TestV3.x, + TestV3.y / TestV3.y, + TestV3.z / TestV3.z}), "V3 Piecewise Div"); + TestClean(((TestV4 / TestV4) == v4{ + TestV4.x / TestV4.x, + TestV4.y / TestV4.y, + TestV4.z / TestV4.z, + TestV4.w / TestV4.w}), "V4 Piecewise Div"); + + TestClean(((MagSqr(V2Unit) == 1) && (MagSqr(TestV2) == TestV2MagSq)), "V2 Square Mag"); + TestClean(((MagSqr(V3Unit) == 1) && (MagSqr(TestV3) == TestV3MagSq)), "V3 Square Mag"); + TestClean(((MagSqr(V4Unit) == 1) && (MagSqr(TestV4) == TestV4MagSq)), "V4 Square Mag"); + TestClean(((Mag(V2Unit) == 1) && (Mag(TestV2) == TestV2Mag)), "V2 Mag"); + TestClean(((Mag(V3Unit) == 1) && (Mag(TestV3) == TestV3Mag)), "V3 Mag"); + TestClean(((Mag(V4Unit) == 1) && (Mag(TestV4) == TestV4Mag)), "V4 Mag"); + + TestClean((DistanceSq(ZeroV2, TestV2) == TestV2MagSq), "V2 Distance Sq"); + TestClean((DistanceSq(ZeroV3, TestV3) == TestV3MagSq), "V3 Distance Sq"); + TestClean((DistanceSq(ZeroV4, TestV4) == TestV4MagSq), "V4 Distance Sq"); + TestClean((Distance(ZeroV2, TestV2) == TestV2Mag), "V2 Distance"); + TestClean((Distance(ZeroV3, TestV3) == TestV3Mag), "V3 Distance"); + TestClean((Distance(ZeroV4, TestV4) == TestV4Mag), "V4 Distance"); + + TestClean((Normalize(TestV2) == TestV2Norm), "V2 Normalize"); + TestClean((Normalize(TestV3) == TestV3Norm), "V3 Normalize"); + TestClean((Normalize(TestV4) == TestV4Norm), "V4 Normalize"); + + TestClean(((Dot(V2Unit, V2Unit) == 1) && (Dot(TestV2, V2Unit) == TestV2DotR)), "V2 Dot"); + TestClean(((Dot(V3Unit, V3Unit) == 1) && (Dot(TestV3, V3Unit) == TestV3DotR)), "V3 Dot"); + TestClean(((Dot(V4Unit, V4Unit) == 1) && (Dot(TestV4, V4Unit) == TestV4DotR)), "V4 Dot"); + + // Skipping Cross For Now + + TestClean((Lerp(v2{0, 0}, v2{1, 1}, .5f) == v2{.5f, .5f}), "V2 Lerp"); + TestClean((Lerp(v3{0, 0, 0}, v3{1, 1, 1}, .5f) == v3{.5f, .5f, .5f}), "V3 Lerp"); + TestClean((Lerp(v4{0, 0, 0, 0}, v4{1, 1, 1, 1}, .5f) == v4{.5f, .5f, .5f, .5f}), "V4 Lerp"); + + ///////////////////////////// + // Matrix + //////////////////////////// + + m33 TestM33 = m33{ + 0, 1, 2, + 3, 4, 5, + 6, 7, 8}; + + m33 EqualityM33 = {}; + for (s32 i = 0; i < 16; i++) { EqualityM33.E[i] = TestM33.E[i]; } + + m33 TransposeM33 = m33{ + 0, 3, 6, + 1, 4, 7, + 2, 5, 8}; + + m33 IdentityM33 = m33{ + 1, 0, 0, + 0, 1, 0, + 0, 0, 1}; + + m33 TestM33Squared = m33{ + 15, 18, 21, + 42, 54, 66, + 69, 90, 111 + }; + + m44 TestM44 = m44{ + 0, 1, 2, 3, + 4, 5, 6, 7, + 8, 9, 10, 11, + 12, 13, 14, 15 + }; + + m44 EqualityM44 = {}; + for (s32 i = 0; i < 16; i++) { EqualityM44.E[i] = TestM44.E[i]; } + + m44 TransposeM44 = m44{ + 0, 4, 8, 12, + 1, 5, 9, 13, + 2, 6, 10, 14, + 3, 7, 11, 15 + }; + + m44 IdentityM44 = m44{ + 1, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + }; + + m44 TestM44Squared = m44{ + 56, 62, 68, 74, + 152, 174, 196, 218, + 248, 286, 324, 362, + 344, 398, 452, 506, + }; + + TestClean(((IdentityM33 == IdentityM33) && (TestM33 == EqualityM33)), "M33 Equality"); + TestClean(((IdentityM44 == IdentityM44) && (TestM44 == EqualityM44)), "M44 Equality"); + + TestClean(((Transpose(IdentityM33) == IdentityM33) && + (Transpose(TestM33) == TransposeM33)), "M33 Transpose"); + TestClean(((Transpose(IdentityM44) == IdentityM44) && + (Transpose(TestM44) == TransposeM44)), "M44 Transpose"); + + TestClean(((TestM33 * IdentityM33) == TestM33), "M33 Identity Mult"); + TestClean(((TestM44 * IdentityM44) == TestM44), "M44 Identity Mult"); + TestClean(((TestM33 * TestM33) == TestM33Squared), "M33 Mult"); + TestClean(((TestM44 * TestM44) == TestM44Squared), "M44 Mult"); + + + // Useful Tests + v4 Right = v4{1, 0, 0, 0}; + v4 Forward = v4{0, 0, 1, 0}; + v4 Up = v4{0, 1, 0, 0}; + v4 Left = v4{-1, 0, 0, 0}; + v4 Back = v4{0, 0, -1, 0}; + v4 Down = v4{0, -1, 0, 0}; + + m44 NinetyDegreesAboutX = GetXRotation(M_PI / 2); + v4 Rotated = NinetyDegreesAboutX * Forward; + TestClean((Rotated == Up), "Rotation About X"); + + m44 NinetyDegreesAboutY = GetYRotation(M_PI / 2); + Rotated = NinetyDegreesAboutY * Right; + TestClean((Rotated == Forward), "Rotation About Y"); + + m44 NinetyDegreesAboutZ = GetZRotation(M_PI / 2); + Rotated = NinetyDegreesAboutZ * Right; + TestClean((Rotated == Down), "Rotation About Z"); + + + v4 A = v4{1, 2, 3, 4}; + m44 B = m44{ + 1, 2, 3, 4, + 5, 6, 7, 8, + 9, 1, 2, 3, + 4, 5, 6, 7}; + v4 VTest = v4{30, 70, 29, 60}; + TestClean(((B * A) == VTest), "V4 M44 Multiplication"); + + m44 C = m44{ + 9, 8, 7, 6, + 5, 4, 3, 2, + 1, 0, 9, 8, + 7, 6, 5, 4 + }; + m44 MResult = B * C; + m44 MTest = m44{ + 50, 40, 60, 50, + 138, 112, 156, 130, + 109, 94, 99, 84, + 116, 94, 132, 110 + }; + TestClean(((B * C) == MTest), "M44 Mult Test 2"); + + m44 Identity = M44Identity(); + m44 InvIdentity = {}; + Inverse(Identity, &InvIdentity); + TestClean((Identity == InvIdentity), "Inverse Identity"); + + m44 Test = m44{ + 2, 4, 6, 7, + 5, 1, 8, 8, + 1, 7, 3, 1, + 3, 9, 2, 4 + }; + m44 PreCalcTestInv = m44{ + -0.3904761904761904762f, 0.26190476190476190475f, -0.02857142857142857139f, 0.16666666666666666668f, + 0.022222222222222222212f, -0.055555555555555555549f, 0.06666666666666666667f, 0.055555555555555555547f, + -0.00317460317460317458f, 0.07936507936507936506f, 0.27619047619047619045f, -0.2222222222222222222f, + 0.24444444444444444444f, -0.1111111111111111111f, -0.26666666666666666667f, 0.1111111111111111111f + }; + m44 InvTest = {}; + Inverse(Test, &InvTest); + //TestClean((PreCalcTestInv == InvTest), "Inverse M44"); + + DebugPrint("Results: Passed %d / %d\n\n\no", SuccessCount, TestCount); +} + +#endif + +#define GS_VECTOR_MATRIX_H +#endif \ No newline at end of file diff --git a/gs_win32.h b/gs_win32.h new file mode 100644 index 0000000..dbc113d --- /dev/null +++ b/gs_win32.h @@ -0,0 +1,831 @@ +#ifndef GS_WIN32_H + +#ifdef DEBUG +#define DEBUG_GET_MESSAGE_NAME(string, message) sprintf(string, message); +#else +#define DEBUG_GET_MESSAGE_NAME(string, message) +#endif + +struct win32_state +{ + b32 Initialized; + b32 Running; +}; + +struct win32_window_info +{ + char* Name; + char* ClassName; + s32 Width; + s32 Height; + WNDPROC WindowEventsHandler; // If this is left null, Win32HandleWindowsEvents will be used +}; + +struct win32_opengl_window_info +{ + s32 ColorBits; + s32 AlphaBits; + s32 DepthBits; + HGLRC RenderContext; +}; + +struct win32_window +{ + win32_window_info Info; + + WNDCLASS Class; + HWND Handle; + HDC DeviceContext; + + // TODO(peter): Make this a union? + win32_opengl_window_info OpenGLInfo; +}; + +struct handle_window_msg_result +{ + b32 NeedsUpdate; +#ifdef DEBUG + char MessageType[128]; +#endif +}; + +global_variable win32_state GlobalWin32State; + +// Utility +internal s32 Win32StringLength(char* String); +internal s32 Win32ConcatStrings(s32 ALen, char* A, s32 BLen, char* B, s32 DestLen, char* Dest); + +// Windowing & Graphics +struct win32_offscreen_buffer +{ + u8* Memory; + s32 Width; + s32 Height; + s32 Pitch; + s32 BytesPerPixel; + BITMAPINFO Info; +}; + +internal void InitializeWin32(); +internal win32_window CreateWin32Window (char* WindowName, char* WindowClassName, s32 Width, s32 Height); +LRESULT CALLBACK Win32HandleWindowsEvents (HWND WindowHandle, UINT Msg, WPARAM wParam, LPARAM lParam); +internal handle_window_msg_result HandleWindowsMessage (HWND WindowHandle, MSG Message); +internal void Win32UpdateWindowDimension(win32_window* Window); +internal void Win32ResizeDIBSection(win32_offscreen_buffer *Buffer, int Width, int Height); +internal void Win32DisplayBufferInWindow(win32_offscreen_buffer* Buffer, win32_window Window); + +// Memory + +internal platform_memory_result Win32Alloc(s32 Size); +internal b32 Win32Free(u8* Base, s32 Size); + +// File IO +internal platform_memory_result ReadEntireFile(char* Path); +internal b32 WriteEntireFile(char* Path, u8* Contents, s32 Size); +internal FILETIME GetFileLastWriteTime(char* Path); + +// DLL +struct win32_dll_refresh +{ + FILETIME LastWriteTime; + HMODULE DLL; + + b32 IsValid; + + char SourceDLLPath[MAX_PATH]; + char WorkingDLLPath[MAX_PATH]; + char LockFilePath[MAX_PATH]; +}; + +struct executable_path +{ + char Path[MAX_PATH]; + s32 PathLength; + s32 IndexOfLastSlash; +}; + +internal executable_path GetApplicationPath(); +internal b32 LoadApplicationDLL(char* DLLName, win32_dll_refresh* DLLResult); +internal void UnloadApplicationDLL(win32_dll_refresh* DLL); +internal win32_dll_refresh InitializeDLLHotReloading(char* SourceDLLName, char* WorkingDLLFileName, char* LockFileName); +internal b32 HotLoadDLL(win32_dll_refresh* DLL); + +/// +// Utils +/// + +internal s32 +Win32StringLength(char* String) +{ + char* At = String; + while (*At) { At++; }; + return At - String; +} + +internal s32 +Win32ConcatStrings(s32 ALen, char* A, s32 BLen, char* B, s32 DestLen, char* Dest) +{ + char* Dst = Dest; + char* AAt = A; + for (s32 a = 0; a < ALen; a++) + { + *Dst++ = *AAt++; + } + char* BAt = B; + for (s32 b = 0; b < BLen; b++) + { + *Dst++ = *BAt++; + } + return Dst - Dest; +} + +/// +// Windowing +/// + +internal void +InitializeWin32 () +{ + GlobalWin32State = {}; + GlobalWin32State.Running = false; + GlobalWin32State.Initialized = true; +} + +internal win32_window +CreateWin32Window (HINSTANCE HInstance, win32_window_info Info) +{ + win32_window Result = {}; + Result.Info = Info; + + Result.Class = {}; + Result.Class.style = CS_HREDRAW | CS_VREDRAW; + if (Info.WindowEventsHandler) + { + Result.Class.lpfnWndProc = Info.WindowEventsHandler; + } + else + { + Result.Class.lpfnWndProc = Win32HandleWindowsEvents; + } + Result.Class.hInstance = HInstance; + Result.Class.lpszClassName = Info.ClassName; + + if (RegisterClass(&Result.Class)) + { + Result.Handle = CreateWindowEx( + 0, + Result.Class.lpszClassName, + Info.Name, + WS_OVERLAPPEDWINDOW | WS_VISIBLE, + CW_USEDEFAULT, + CW_USEDEFAULT, + Info.Width, + Info.Height, + 0, + 0, + HInstance, + 0); + Result.DeviceContext = GetDC(Result.Handle); + } + + return Result; +}; + +internal void +CreateOpenGLWindowContext (win32_opengl_window_info Info, win32_window* Window) +{ + // Setup pixel format + { + PIXELFORMATDESCRIPTOR PixelFormatDesc = { 0 }; + // TODO: Program seems to work perfectly fine without all other params except dwFlags. + // Can we skip other params for the sake of brevity? + PixelFormatDesc.nSize = sizeof(PIXELFORMATDESCRIPTOR); + PixelFormatDesc.nVersion = 1; + PixelFormatDesc.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER; + PixelFormatDesc.iPixelType = PFD_TYPE_RGBA;// TODO(Peter): include this in win32_opengl_window_info? + PixelFormatDesc.cColorBits = Info.ColorBits; + PixelFormatDesc.cAlphaBits = Info.AlphaBits; + PixelFormatDesc.cDepthBits = Info.DepthBits; + PixelFormatDesc.dwLayerMask = PFD_MAIN_PLANE; // TODO(Peter): include this in win32_opengl_window_info? + // + + s32 PixelFormat = ChoosePixelFormat(Window->DeviceContext, &PixelFormatDesc); + if (!PixelFormat) { InvalidCodePath; } // TODO: Log: Choose pixel format failed + if (!SetPixelFormat(Window->DeviceContext, PixelFormat, &PixelFormatDesc)) { InvalidCodePath; } // TODO: Log: Set pixel format failed + } + + // Create rendering context + { + // TODO: Create "proper" context? + // https://www.opengl.org/wiki/Creating_an_OpenGL_Context_(WGL)#Proper_Context_Creation + + Info.RenderContext = wglCreateContext(Window->DeviceContext); + wglMakeCurrent(Window->DeviceContext, Info.RenderContext); + + // TODO(Peter): do we want this? + /* + glGetIntegerv(GL_MAJOR_VERSION, ); + glGetIntegerv(GL_MINOR_VERSION, ); + (char*)glGetString(GL_VENDOR); + (char*)glGetString(GL_RENDERER); + */ + } + + Window->OpenGLInfo = Info; +} + +struct handle_window_event_result +{ + LRESULT Result; + b32 Handled; +}; + +internal handle_window_event_result +HandleWindowEventUnlessWouldUseDefault (HWND WindowHandle, UINT Msg, WPARAM wParam, LPARAM lParam) +{ + handle_window_event_result Result = {}; + Result.Handled = false; + + switch (Msg) + { + case WM_SIZE: + { + //ResizeDIBSection(); + Result.Handled = true; + }break; + + case WM_CLOSE: + { + Result.Result = DefWindowProc(WindowHandle, Msg, wParam, lParam); + Result.Handled = true; + }break; + + case WM_DESTROY: + { + GlobalWin32State.Running = false; + Result.Handled = true; + }break; + + case WM_PAINT: + { + PAINTSTRUCT PaintStruct; + HDC DeviceContext; + b32 PaintResult; + + DeviceContext = BeginPaint(WindowHandle, &PaintStruct); + PaintResult = EndPaint(WindowHandle, &PaintStruct); + Result.Handled = true; + }break; + } + + return Result; +} + +LRESULT CALLBACK +Win32HandleWindowsEvents ( +HWND WindowHandle, +UINT Msg, +WPARAM wParam, +LPARAM lParam +) +{ + handle_window_event_result EventResult = HandleWindowEventUnlessWouldUseDefault( + WindowHandle, + Msg, + wParam, + lParam); + + if (!EventResult.Handled) + { + EventResult.Result = DefWindowProc(WindowHandle, Msg, wParam, lParam); + } + + return EventResult.Result; +} + +#define WIN32_SHOULD_TRANSLATE_TO_CHAR -1 + +static int +Win32GetKeyIndex (int Win32VirtualKey, bool NumpadValid, bool TranslateToChar) +{ + int Result = WIN32_SHOULD_TRANSLATE_TO_CHAR; + + if (Win32VirtualKey == VK_ESCAPE) { Result = (int)KeyCode_Esc; } + + if (!TranslateToChar) + { + if (Win32VirtualKey == VK_SPACE) { Result = (int)KeyCode_Space; } + + } + + if (Win32VirtualKey == VK_CAPITAL) { Result = (int)KeyCode_CapsLock; } + else if (Win32VirtualKey == VK_TAB) { Result = (int)KeyCode_Tab; } + else if (Win32VirtualKey == VK_LSHIFT) { Result = (int)KeyCode_LeftShift; } + else if (Win32VirtualKey == VK_RSHIFT) { Result = (int)KeyCode_RightShift; } + else if (Win32VirtualKey == VK_LCONTROL) { Result = (int)KeyCode_LeftCtrl; } + else if (Win32VirtualKey == VK_RCONTROL) { Result = (int)KeyCode_RightCtrl; } + + // TODO(Peter): support the function key? + //else if (Win32VirtualKey == VK_) { Result = (int)KeyCode_Fn; } + + else if (Win32VirtualKey == VK_MENU) { Result = (int)KeyCode_Alt; } + else if (Win32VirtualKey == VK_PRIOR) { Result = (int)KeyCode_PageUp; } + else if (Win32VirtualKey == VK_NEXT) { Result = (int)KeyCode_PageDown; } + else if (Win32VirtualKey == VK_BACK) { Result = (int)KeyCode_Backspace; } + else if (Win32VirtualKey == VK_DELETE) { Result = (int)KeyCode_Delete; } + else if (Win32VirtualKey == VK_RETURN) { Result = (int)KeyCode_Enter; } + + else if (Win32VirtualKey == VK_F1) { Result = (int)KeyCode_F1; } + else if (Win32VirtualKey == VK_F2) { Result = (int)KeyCode_F2; } + else if (Win32VirtualKey == VK_F3) { Result = (int)KeyCode_F3; } + else if (Win32VirtualKey == VK_F4) { Result = (int)KeyCode_F4; } + else if (Win32VirtualKey == VK_F5) { Result = (int)KeyCode_F5; } + else if (Win32VirtualKey == VK_F6) { Result = (int)KeyCode_F6; } + else if (Win32VirtualKey == VK_F7) { Result = (int)KeyCode_F7; } + else if (Win32VirtualKey == VK_F8) { Result = (int)KeyCode_F8; } + else if (Win32VirtualKey == VK_F9) { Result = (int)KeyCode_F9; } + else if (Win32VirtualKey == VK_F10) { Result = (int)KeyCode_F10; } + else if (Win32VirtualKey == VK_F11) { Result = (int)KeyCode_F11; } + else if (Win32VirtualKey == VK_F12) { Result = (int)KeyCode_F12; } + + if (!TranslateToChar) + { + if (Win32VirtualKey == 0x30) { Result = (int)KeyCode_0; } + else if (Win32VirtualKey == 0x31) { Result = (int)KeyCode_1; } + else if (Win32VirtualKey == 0x32) { Result = (int)KeyCode_2; } + else if (Win32VirtualKey == 0x33) { Result = (int)KeyCode_3; } + else if (Win32VirtualKey == 0x34) { Result = (int)KeyCode_4; } + else if (Win32VirtualKey == 0x35) { Result = (int)KeyCode_5; } + else if (Win32VirtualKey == 0x36) { Result = (int)KeyCode_6; } + else if (Win32VirtualKey == 0x37) { Result = (int)KeyCode_7; } + else if (Win32VirtualKey == 0x38) { Result = (int)KeyCode_8; } + else if (Win32VirtualKey == 0x39) { Result = (int)KeyCode_9; } + + else if (Win32VirtualKey == 0x41) { Result = (int)KeyCode_A; } + else if (Win32VirtualKey == 0x42) { Result = (int)KeyCode_B; } + else if (Win32VirtualKey == 0x43) { Result = (int)KeyCode_C; } + else if (Win32VirtualKey == 0x44) { Result = (int)KeyCode_D; } + else if (Win32VirtualKey == 0x45) { Result = (int)KeyCode_E; } + else if (Win32VirtualKey == 0x46) { Result = (int)KeyCode_F; } + else if (Win32VirtualKey == 0x47) { Result = (int)KeyCode_G; } + else if (Win32VirtualKey == 0x48) { Result = (int)KeyCode_H; } + else if (Win32VirtualKey == 0x49) { Result = (int)KeyCode_I; } + else if (Win32VirtualKey == 0x4A) { Result = (int)KeyCode_J; } + else if (Win32VirtualKey == 0x4B) { Result = (int)KeyCode_K; } + else if (Win32VirtualKey == 0x4C) { Result = (int)KeyCode_L; } + else if (Win32VirtualKey == 0x4D) { Result = (int)KeyCode_M; } + else if (Win32VirtualKey == 0x4E) { Result = (int)KeyCode_N; } + else if (Win32VirtualKey == 0x4F) { Result = (int)KeyCode_O; } + else if (Win32VirtualKey == 0x50) { Result = (int)KeyCode_P; } + else if (Win32VirtualKey == 0x51) { Result = (int)KeyCode_Q; } + else if (Win32VirtualKey == 0x52) { Result = (int)KeyCode_R; } + else if (Win32VirtualKey == 0x53) { Result = (int)KeyCode_S; } + else if (Win32VirtualKey == 0x54) { Result = (int)KeyCode_T; } + else if (Win32VirtualKey == 0x55) { Result = (int)KeyCode_U; } + else if (Win32VirtualKey == 0x56) { Result = (int)KeyCode_V; } + else if (Win32VirtualKey == 0x57) { Result = (int)KeyCode_W; } + else if (Win32VirtualKey == 0x58) { Result = (int)KeyCode_X; } + else if (Win32VirtualKey == 0x59) { Result = (int)KeyCode_Y; } + else if (Win32VirtualKey == 0x5A) { Result = (int)KeyCode_Z; } + } + + if (NumpadValid) + { + if (Win32VirtualKey == VK_NUMPAD0) { Result = (int)KeyCode_Num0; } + else if (Win32VirtualKey == VK_NUMPAD1) { Result = (int)KeyCode_Num1; } + else if (Win32VirtualKey == VK_NUMPAD2) { Result = (int)KeyCode_Num2; } + else if (Win32VirtualKey == VK_NUMPAD3) { Result = (int)KeyCode_Num3; } + else if (Win32VirtualKey == VK_NUMPAD4) { Result = (int)KeyCode_Num4; } + else if (Win32VirtualKey == VK_NUMPAD5) { Result = (int)KeyCode_Num5; } + else if (Win32VirtualKey == VK_NUMPAD6) { Result = (int)KeyCode_Num6; } + else if (Win32VirtualKey == VK_NUMPAD7) { Result = (int)KeyCode_Num7; } + else if (Win32VirtualKey == VK_NUMPAD8) { Result = (int)KeyCode_Num8; } + else if (Win32VirtualKey == VK_NUMPAD9) { Result = (int)KeyCode_Num9; } + } + + if (Win32VirtualKey == VK_UP) { Result = (int)KeyCode_UpArrow; } + else if (Win32VirtualKey == VK_DOWN) { Result = (int)KeyCode_DownArrow; } + else if (Win32VirtualKey == VK_LEFT) { Result = (int)KeyCode_LeftArrow; } + else if (Win32VirtualKey == VK_RIGHT) { Result = (int)KeyCode_RightArrow; } + + return Result; +} + +internal handle_window_msg_result +HandleWindowsMessage ( +HWND WindowHandle, +MSG Message) +{ + handle_window_msg_result Result = {}; + Result.NeedsUpdate = 0; + + switch (Message.message) + { + case WM_HOTKEY: + { + DEBUG_GET_MESSAGE_NAME(Result.MessageType, "WM_HOTKEY "); + }break; + + case WM_MOUSEWHEEL: + { + DEBUG_GET_MESSAGE_NAME(Result.MessageType, "WM_MOUSEWHEEL "); + int MouseWheel = GET_WHEEL_DELTA_WPARAM(Message.wParam); + /* + Input.New->MouseScroll = MouseWheel; + Result.NeedsUpdate = true; + */ + }break; + + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + { + DEBUG_GET_MESSAGE_NAME(Result.MessageType, "WM_MOUSEBUTTON "); + + /* + Input.New->KeyStates[KeyCode_MouseLeftButton] = (GetKeyState(VK_LBUTTON) & (1 << 15)) != 0; + Input.New->KeyStates[KeyCode_MouseMiddleButton] = (GetKeyState(VK_MBUTTON) & (1 << 15)) != 0; + Input.New->KeyStates[KeyCode_MouseRightButton] = (GetKeyState(VK_RBUTTON) & (1 << 15)) != 0; + // NOTE(Peter): If you decide to support extra mouse buttons, on windows the key codes are + // VK_XBUTTON1 and VK_XBUTTON2 + + if (KeyTransitionedDown(KeyCode_MouseLeftButton, Input)) + { + Input.MouseDownX = Input.New->MouseX; + Input.MouseDownY = Input.New->MouseY; + } + Result.NeedsUpdate = true;*/ + }break; + + case WM_MOUSEMOVE: + { + DEBUG_GET_MESSAGE_NAME(Result.MessageType, "WM_MOUSEMOVE "); + POINT MousePos; + GetCursorPos(&MousePos); + ScreenToClient(WindowHandle, &MousePos); + + /* + Input.New->MouseX = MousePos.x; + Input.New->MouseY = App.WindowHeight - MousePos.y; + + Result.NeedsUpdate = true; + */ + }break; + + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + case WM_KEYDOWN: + case WM_KEYUP: + { + int VirtualKey = (int)Message.wParam; + bool KeyDown = (Message.lParam & (1 << 31)) == 0; + int KeyIndex = Win32GetKeyIndex(VirtualKey, true, true); + /* + if (KeyIndex >= 0) + { + DEBUG_GET_MESSAGE_NAME(Result.MessageType, "WM_KEYEvent "); + Input.New->KeyStates[KeyIndex] = KeyDown; + Result.NeedsUpdate = true; + } + else + { + if (Input.TranslateInputToCharValues && KeyDown) + { + // NOTE(Peter): Took this out b/c we're translating the WM_CHAR messages + // in the message pump, and if we do it here as well, character producing + // key messages get put on the message queue twice + TranslateMessage(&Message); + DispatchMessage(&Message); + } + else + { + DEBUG_GET_MESSAGE_NAME(Result.MessageType, "WM_KEYEvent "); + // NOTE(Peter): This is so that when you lift up a key that was generating a WM_CHAR, + // the app still has a chance to respond to it. + Result.NeedsUpdate = true; + } + } + */ + }break; + + case WM_CHAR: + { + DEBUG_GET_MESSAGE_NAME(Result.MessageType, "WM_CHAR "); + /* + char TranslatedChar = (char)Message.wParam; + int KeyIndex = GetKeyIndexFromChar(TranslatedChar); + + if (KeyIndex >= 0) + { + // NOTE(Peter): Always setting this to true becuase windows is stupid and doesn't + // pass the press/release bit through correctly. So now the KEYDOWN/KEYUP Messages above + // only translate the message to a WM_CHAR message if its a key down. Since we clear all + // keystates to false at the beginning of an input frame, this will make transitions + // get registered correctly. + Input.New->KeyStates[KeyIndex] = true; + Result.NeedsUpdate = true; + } + else + { + printf("Translated Char Not Recognized: %c\n", TranslatedChar); + //InvalidCodePath; + } + */ + }break; + + default: + { + DEBUG_GET_MESSAGE_NAME(Result.MessageType, "Unhandled WM Event "); + TranslateMessage(&Message); + DispatchMessage(&Message); + }break; + } + + return Result; +} + +internal void +Win32UpdateWindowDimension(win32_window* Window) +{ + RECT ClientRect; + GetClientRect(Window->Handle, &ClientRect); + Window->Info.Width = ClientRect.right - ClientRect.left; + Window->Info.Height = ClientRect.bottom - ClientRect.top; +} + +internal void +Win32ResizeDIBSection(win32_offscreen_buffer *Buffer, int Width, int Height) +{ + if(Buffer->Memory) + { + VirtualFree(Buffer->Memory, 0, MEM_RELEASE); + } + + Buffer->Width = Width; + Buffer->Height = Height; + + int BytesPerPixel = 4; + Buffer->BytesPerPixel = BytesPerPixel; + + Buffer->Info.bmiHeader.biSize = sizeof(Buffer->Info.bmiHeader); + Buffer->Info.bmiHeader.biWidth = Buffer->Width; + Buffer->Info.bmiHeader.biHeight = -Buffer->Height; // Top down, not bottom up + Buffer->Info.bmiHeader.biPlanes = 1; + Buffer->Info.bmiHeader.biBitCount = 32; + Buffer->Info.bmiHeader.biCompression = BI_RGB; + + int BitmapMemorySize = (Buffer->Width*Buffer->Height)*BytesPerPixel; + Buffer->Memory = (u8*)VirtualAlloc(0, BitmapMemorySize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + Buffer->Pitch = Width*BytesPerPixel; +} + +internal void +Win32DisplayBufferInWindow(win32_offscreen_buffer* Buffer, win32_window Window) +{ + StretchDIBits(Window.DeviceContext, + 0, 0, Buffer->Width, Buffer->Height, + 0, 0, Buffer->Width, Buffer->Height, + Buffer->Memory, + &Buffer->Info, + DIB_RGB_COLORS, SRCCOPY); +} + +/// +// Memory +/// + +internal win32_memory_op_result +Win32Alloc(s32 Size) +{ + win32_memory_op_result Result = {}; + Result.Success = false; + + Result.Base = (u8*)VirtualAlloc(NULL, Size, + MEM_COMMIT | MEM_RESERVE, + PAGE_EXECUTE_READWRITE); + if (Result.Base) + { + Result.Size = Size; + Result.Success = true; + } + + return Result; +} + +internal b32 +Win32Free(u8* Base, s32 Size) +{ + b32 Result = VirtualFree(Base, Size, MEM_RELEASE); + return Result; +} + +// File IO +internal win32_memory_op_result +ReadEntireFile(char* Path) +{ + win32_memory_op_result Result = {}; + Result.Success = false; + + HANDLE FileHandle = CreateFileA ( + Path, + GENERIC_READ, + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (FileHandle != INVALID_HANDLE_VALUE) + { + DWORD FileSize = GetFileSize(FileHandle, NULL); + Result.Base = (u8*)VirtualAlloc(NULL, FileSize, MEM_COMMIT | MEM_RESERVE, + PAGE_EXECUTE_READWRITE); + if (Result.Base) + { + Result.Size = FileSize; + + s32 BytesRead = 0; + if (ReadFile(FileHandle, (LPVOID)Result.Base, FileSize, (LPDWORD)(&BytesRead), NULL)) + { + Result.Success = true; + } + else + { + Result.Size = 0; + } + } + CloseHandle(FileHandle); + } + else + { + // TODO(Peter): failure + } + + return Result; +} + +internal b32 +WriteEntireFile (char* Path, u8* Contents, s32 Size) +{ + b32 Result = false; + HANDLE FileHandle = CreateFileA ( + Path, + GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (FileHandle != INVALID_HANDLE_VALUE) + { + DWORD BytesWritten = 0; + + b32 WriteSuccess = WriteFile(FileHandle, + Contents, Size, + &BytesWritten, + NULL); + + if (WriteSuccess && BytesWritten == (u32)Size) + { + CloseHandle(FileHandle); + Result = true; + } + else + { + Result = false; + } + } + else + { + Result = false; + } + + return Result; +} + +internal FILETIME +GetFileLastWriteTime(char* Path) +{ + FILETIME Result = {}; + + WIN32_FIND_DATA FindData = {}; + HANDLE FileHandle = FindFirstFileA(Path, &FindData); + + if (FileHandle != INVALID_HANDLE_VALUE) + { + Result = FindData.ftLastWriteTime; + FindClose(FileHandle); + } + else + { + // TODO(Peter): Error handling + } + + return Result; +} + +/// +// DLL +/// + +internal executable_path +GetApplicationPath() +{ + executable_path ExePath = {}; + ExePath.PathLength = GetModuleFileNameA(0, ExePath.Path, MAX_PATH); + + u32 CharactersScanned = 0; + u32 IndexOfLastSlash = 0; + char *Scan = ExePath.Path; + while(*Scan) + { + if (*Scan == '\\') + { + ExePath.IndexOfLastSlash = CharactersScanned + 1; + } + Scan++; + CharactersScanned++; + } + + return ExePath; +} + +internal b32 +LoadApplicationDLL(char* DLLName, win32_dll_refresh* DLLResult) +{ + b32 Success = false; + Assert(DLLResult->DLL == 0); + + DLLResult->DLL = LoadLibraryA(DLLName); // TODO(Peter): Error checking + if (DLLResult->DLL) + { + Success = true; + DLLResult->IsValid = true; + } + + return Success; +} + +internal void +UnloadApplicationDLL(win32_dll_refresh* DLL) +{ + if (DLL->DLL) + { + FreeLibrary(DLL->DLL); + } + DLL->DLL = 0; + DLL->IsValid = false; +} + +internal win32_dll_refresh +InitializeDLLHotReloading(char* SourceDLLName, + char* WorkingDLLFileName, + char* LockFileName) +{ + win32_dll_refresh Result = {}; + Result.IsValid = false; + + executable_path ExePath = GetApplicationPath(); + + Win32ConcatStrings(ExePath.IndexOfLastSlash, ExePath.Path, + Win32StringLength(SourceDLLName), SourceDLLName, + MAX_PATH, Result.SourceDLLPath); + Win32ConcatStrings(ExePath.IndexOfLastSlash, ExePath.Path, + Win32StringLength(WorkingDLLFileName), WorkingDLLFileName, + MAX_PATH, Result.WorkingDLLPath); + Win32ConcatStrings(ExePath.IndexOfLastSlash, ExePath.Path, + Win32StringLength(LockFileName), LockFileName, + MAX_PATH, Result.LockFilePath); + + return Result; + +} + +internal b32 +HotLoadDLL(win32_dll_refresh* DLL) +{ + b32 DidReload = false; + + FILETIME UpdatedLastWriteTime = GetFileLastWriteTime(DLL->SourceDLLPath); + if (CompareFileTime(&UpdatedLastWriteTime, &DLL->LastWriteTime)) + { + WIN32_FILE_ATTRIBUTE_DATA Ignored; + if (!GetFileAttributesEx(DLL->LockFilePath, GetFileExInfoStandard, &Ignored)) + { + UnloadApplicationDLL(DLL); + CopyFileA(DLL->SourceDLLPath, DLL->WorkingDLLPath, FALSE); + LoadApplicationDLL(DLL->WorkingDLLPath, DLL); + DLL->LastWriteTime = UpdatedLastWriteTime; + DidReload = true; + } + } + + return DidReload; +} + +#define GS_WIN32_H +#endif // GS_WIN32_H diff --git a/interface.h b/interface.h new file mode 100644 index 0000000..cb1dfb1 --- /dev/null +++ b/interface.h @@ -0,0 +1,586 @@ +// NOTE(Peter): This stuff was all a test to see how I could do panel splitting. Thinking about moving away +// from that for now. Might return later if necessary +// TODO(Peter): Finish this if necessary + +struct interface_region +{ + v2 Min, Max; + union + { + struct + { + interface_region* A; + interface_region* B; + }; + struct + { + interface_region* Left; + interface_region* Right; + }; + struct + { + interface_region* Top; + interface_region* Bottom; + }; + }; +}; + +struct interface_tracker +{ + memory_arena* Storage; + interface_region RootRegion; +}; + +enum interface_region_split +{ + InterfaceRegionSplit_Vertical, + InterfaceRegionSplit_Horizontal, +}; + +inline s32 +RegionWidth (interface_region Region) +{ + s32 Result = Region.Max.x - Region.Min.x; + return Result; +} + +inline s32 +RegionHeight (interface_region Region) +{ + s32 Result = Region.Max.y - Region.Min.y; + return Result; +} + +internal void +SplitRegion (interface_tracker* Tracker, interface_region* Parent, s32 SplitPosition, interface_region_split SplitDirection) +{ + if (!Parent->A) + { + interface_region* A = PushStruct(Tracker->Storage, interface_region); + A->A = 0; + A->B = 0; + Parent->A = A; + } + Parent->A->Min = Parent->Min; + Parent->A->Max = Parent->Max; + + if (!Parent->B) + { + interface_region* B = PushStruct(Tracker->Storage, interface_region); + B->A = 0; + B->B = 0; + Parent->B = B; + } + Parent->B->Min = Parent->Min; + Parent->B->Max = Parent->Max; + + switch (SplitDirection) + { + case InterfaceRegionSplit_Vertical: + { + Parent->Left->Max.x = Parent->Min.x + SplitPosition; + Parent->Right->Min.x = Parent->Min.x + SplitPosition; + }break; + + case InterfaceRegionSplit_Horizontal: + { + Parent->Bottom->Max.y = Parent->Min.y + SplitPosition; + Parent->Top->Min.y = Parent->Min.y + SplitPosition; + }break; + } +} + +internal interface_tracker +CreateInterfaceTracker (memory_arena* Storage, s32 ScreenWidth, s32 ScreenHeight) +{ + interface_tracker Result = {}; + Result.Storage = Storage; + Result.RootRegion.A = 0; + Result.RootRegion.B = 0; + Result.RootRegion.Min = v2{0, 0}; + Result.RootRegion.Max = v2{(r32)ScreenWidth, (r32)ScreenHeight}; + return Result; +} + +internal v2 +DrawString (render_command_buffer* RenderBuffer, string String, bitmap_font* Font, s32 PointSize, v2 Position, v4 Color) +{ + DEBUG_TRACK_FUNCTION; + + v2 LowerRight = Position; + + render_quad_batch_constructor BatchConstructor = PushRenderTexture2DBatch(RenderBuffer, String.Length, + *Font->Atlas); + + r32 FontScale = (r32)PointSize / Font->PixelHeight; + v2 RegisterPosition = Position; + char* C = String.Memory; + for (s32 i = 0; i < String.Length; i++) + { + s32 GlyphDataIndex = GetCharacterIndexInFont(*C, Font); + character_data Data = Font->CharacterLUT_Values[GlyphDataIndex]; + + r32 MinX = RegisterPosition.x + Data.RegisterXOffset * FontScale; + r32 MinY = RegisterPosition.y + Data.BaselineYOffset * FontScale; + r32 MaxX = MinX + (Data.X1 - Data.X0) * FontScale; + r32 MaxY = MinY + (Data.Y1 - Data.Y0) * FontScale; + + PushQuad2DOnBatch(&BatchConstructor, v2{MinX, MinY}, v2{MaxX, MinY}, v2{MaxX, MaxY}, v2{MinX, MaxY}, Data.AtlasMinUV, Data.AtlasMaxUV, Color); + + RegisterPosition.x += Data.Advance * Font->FontScale * FontScale; + C++; + } + + LowerRight.x = RegisterPosition.x; + + return LowerRight; +} + +struct interface_config +{ + v4 PanelBGColors[4]; + v4 ButtonColor_Inactive, ButtonColor_Active, ButtonColor_Selected; + v4 TextColor; + bitmap_font* Font; + r32 FontSize; + v2 Margin; +}; + +struct button_result +{ + b32 Pressed; + r32 Advance; +}; + +internal button_result +EvaluateButton_ (render_command_buffer* RenderBuffer, v2 Min, v2 Max, string Label, interface_config Config, input Input, v4 BGColor) +{ + button_result Result = {}; + Result.Pressed = false; + + v2 MousePos = v2{(r32)Input.New->MouseX, (r32)Input.New->MouseY}; + if (PointIsInRange(MousePos, Min, Max)) + { + if (KeyTransitionedDown(Input, KeyCode_MouseLeftButton)) + { + Result.Pressed = true; + } + else + { + BGColor = Config.ButtonColor_Active; + } + } + + PushRenderQuad2D(RenderBuffer, Min, Max, BGColor); + DrawString(RenderBuffer, Label, Config.Font, Config.Font->PixelHeight, Min + Config.Margin, Config.TextColor); + + Result.Advance = (Max.y - Min.y) + Config.Margin.y; + return Result; +} + +internal button_result +EvaluateButton (render_command_buffer* RenderBuffer, v2 Min, v2 Max, string Label, interface_config Config, input Input) +{ + button_result Result = EvaluateButton_(RenderBuffer, Min, Max, Label, Config, Input, Config.ButtonColor_Inactive); + return Result; +} + +internal button_result +EvaluateSelectableButton (render_command_buffer* RenderBuffer, v2 Min, v2 Max, string Label, interface_config Config, input Input, b32 Selected) +{ + v4 BGColor = Config.ButtonColor_Inactive; + if (Selected) + { + BGColor = Config.ButtonColor_Selected; + } + + button_result Result = EvaluateButton_(RenderBuffer, Min, Max, Label, Config, Input, BGColor); + return Result; +} + +struct multi_option_label_result +{ + b32 Pressed; + s32 IndexPressed; + r32 Advance; +}; + +internal multi_option_label_result +EvaluateMultiOptionLabel (render_command_buffer* RenderBuffer, + v2 Min, v2 Max, string Label, string Options[], + interface_config Config, input Input) +{ + multi_option_label_result Result = {}; + Result.Pressed = false; + + DrawString(RenderBuffer, Label, Config.Font, 14, Min + Config.Margin, Config.TextColor); + + r32 ButtonSide = (Max.y - Min.y) - (2 * Config.Margin.y); + v2 ButtonDim = v2{ButtonSide, ButtonSide}; + v2 ButtonPos = Max - (ButtonDim + Config.Margin); + + for (s32 b = 0; b < sizeof(Options) / sizeof(Options[0]); b++) + { + button_result Button = EvaluateButton(RenderBuffer, ButtonPos, ButtonPos + ButtonDim, + Options[b], Config, Input); + if (Button.Pressed) + { + Result.Pressed = true; + Result.IndexPressed = b; + } + } + + Result.Advance = (Max.y - Min.y) + Config.Margin.y; + return Result; +} + +// NOTE(Peter): returns IndexPressed = -1 if the button itself is pressed, as opposed +// to one of its options +internal multi_option_label_result +EvaluateMultiOptionButton (render_command_buffer* RenderBuffer, v2 Min, v2 Max, string Text, string Options[], b32 Selected, + interface_config Config, input Input) +{ + multi_option_label_result Result = {}; + Result.Pressed = false; + + s32 OptionsCount = sizeof(Options) / sizeof(Options[0]); + r32 ButtonSide = (Max.y - Min.y) - (2 * Config.Margin.y); + v2 ButtonDim = v2{ButtonSide, ButtonSide}; + + v2 FirstButtonPos = Max - ((ButtonDim + Config.Margin) * OptionsCount); + v2 NewMax = v2{FirstButtonPos.x - Config.Margin.x, Max.y}; + + button_result MainButton = EvaluateSelectableButton(RenderBuffer, Min, NewMax, Text, Config, Input, Selected); + if (MainButton.Pressed) + { + Result.Pressed = true; + Result.IndexPressed = -1; + } + + v2 ButtonPos = Max - (ButtonDim + Config.Margin); + + for (s32 b = 0; b < OptionsCount; b++) + { + button_result Button = EvaluateButton(RenderBuffer, ButtonPos, ButtonPos + ButtonDim, + Options[b], Config, Input); + if (Button.Pressed) + { + Result.Pressed = true; + Result.IndexPressed = b; + } + } + + Result.Advance = (Max.y - Min.y) + Config.Margin.y; + return Result; +} + +struct slider_result +{ + r32 Percent; + r32 Advance; +}; + +internal slider_result +EvaluateSlider (render_command_buffer* RenderBuffer, v2 Min, v2 Max, string Label, r32 Percent, interface_config Config, input Input) +{ + slider_result Result = {}; + + v4 BGColor = Config.ButtonColor_Inactive; + v4 FillColor = Config.ButtonColor_Selected; + + r32 DisplayPercent = Percent; + + v2 MousePos = v2{(r32)Input.New->MouseX, (r32)Input.New->MouseY}; + if (PointIsInRange(MousePos, Min, Max)) + { + BGColor = Config.ButtonColor_Active; + } + + if (KeyDown(Input, KeyCode_MouseLeftButton)) + { + v2 MouseDownPos = v2{(r32)Input.MouseDownX, (r32)Input.MouseDownY}; + if (PointIsInRange(MouseDownPos, Min, Max)) + { + r32 TempFillPercent = (MousePos.y - Min.y) / (Max.y - Min.y); + + DisplayPercent = GSClamp(0.0f, TempFillPercent, 1.0f); + } + } + + r32 FillHeight = ((Max.y - Min.y) - 4) * DisplayPercent; + + PushRenderQuad2D(RenderBuffer, Min, Max, BGColor); + PushRenderQuad2D(RenderBuffer, Min + v2{2, 2}, v2{Max.x - 2, Min.y + 2 + FillHeight}, FillColor); + + // TODO(Peter): display the actual value of the slider + + DrawString(RenderBuffer, Label, Config.Font, 14, Min, Config.TextColor); + + Result.Percent = DisplayPercent; + Result.Advance = (Max.y - Min.y) + Config.Margin.y; + + return Result; +} + +struct panel_result +{ + s32 Depth; + v2 NextPanelMin; + v2 ChildMin, ChildMax; +}; + +internal panel_result +EvaluatePanel (render_command_buffer* RenderBuffer, v2 Min, v2 Max, s32 Depth, interface_config Config, input Input) +{ + panel_result Result = {}; + + Result.Depth = Depth; + Result.ChildMin = Min + Config.Margin; + Result.ChildMax = Max - Config.Margin; + Result.NextPanelMin = v2{Max.x, Min.y}; + + v4 BG = Config.PanelBGColors[Depth]; + PushRenderQuad2D(RenderBuffer, Min, Max, BG); + + return Result; +} + +internal panel_result +EvaluatePanel (render_command_buffer* RenderBuffer, v2 Min, v2 Max, string Label, s32 Depth, interface_config Config, input Input) +{ + panel_result Result = EvaluatePanel(RenderBuffer, Min, Max, Depth, Config, Input); + + v2 TextPos = v2{ + Min.x + Config.Margin.x, + Max.y - (Config.Font->NewLineYOffset + Config.Margin.y) + }; + DrawString(RenderBuffer, Label, Config.Font, 14, TextPos, Config.TextColor); + Result.ChildMax = v2{Max.x, TextPos.y} - Config.Margin; + + return Result; +} + +internal panel_result +EvaluatePanel(render_command_buffer* RenderBuffer, panel_result* ParentPanel, r32 Height, string Title, interface_config Config, input Input) +{ + v2 Min = v2{ParentPanel->ChildMin.x, ParentPanel->ChildMax.y - Height}; + v2 Max = ParentPanel->ChildMax; + panel_result Result = EvaluatePanel(RenderBuffer, Min, Max, Title, ParentPanel->Depth + 1, Config, Input); + + ParentPanel->ChildMax.y = Min.y - Config.Margin.y; + + return Result; +} + +enum selection_state +{ + Selection_None, + Selection_Selected, + Selection_Deselected, +}; + +struct scroll_list_result +{ + s32 IndexSelected; + s32 StartIndex; + selection_state Selection; +}; + +internal scroll_list_result +DrawOptionsList(render_command_buffer* RenderBuffer, v2 Min, v2 Max, + string* Options, s32 OptionsCount, + s32 Start, interface_config Config, input Input) +{ + scroll_list_result Result = {}; + Result.IndexSelected = -1; + Result.StartIndex = Start; + Result.Selection = Selection_None; + + r32 OptionHeight = Config.Font->NewLineYOffset + (2 * Config.Margin.y); + r32 OptionOffset = OptionHeight + Config.Margin.y; + + s32 OptionsToDisplay = ((Max.y - Min.y) / OptionHeight) - 2; + OptionsToDisplay = GSMin(OptionsToDisplay, (OptionsCount - Start)); + + v2 ButtonMin = v2{Min.x, Max.y - OptionHeight}; + v2 ButtonMax = v2{Max.x, Max.y}; + + string* OptionCursor = Options + Start; + for (s32 i = 0; i < OptionsToDisplay; i++) + { + button_result Button = EvaluateButton(RenderBuffer, ButtonMin, ButtonMax, + *OptionCursor, + Config, Input); + if (Button.Pressed) + { + Result.IndexSelected = Start + i; + Result.Selection = Selection_Selected; + } + OptionCursor++; + ButtonMin.y -= OptionOffset; + ButtonMax.y -= OptionOffset; + } + + r32 HalfWidthWithMargin = ((Max.x - Min.x) / 2.0f) - Config.Margin.x; + string DownArrowString = MakeStringLiteral(" v "); + string UpArrowString = MakeStringLiteral(" ^ "); + button_result Down = EvaluateButton(RenderBuffer, Min, v2{Min.x + HalfWidthWithMargin, Min.y + OptionHeight}, + DownArrowString, Config, Input); + button_result Up = EvaluateButton(RenderBuffer, v2{Min.x + HalfWidthWithMargin + Config.Margin.x, Min.y}, + v2{Max.x, Min.y + OptionHeight}, + UpArrowString, Config, Input); + if (Down.Pressed) + { + Result.StartIndex += 1; + } + if (Up.Pressed) + { + Result.StartIndex -= 1; + } + + Result.StartIndex = GSClamp(0, Result.StartIndex, OptionsCount); + + return Result; +} + +internal scroll_list_result +DrawSelectableOptionsList(render_command_buffer* RenderBuffer, v2 Min, v2 Max, + string* Options, s32 OptionsCount, + s32 Start, s32 Selected, interface_config Config, input Input) +{ + scroll_list_result Result = {}; + Result.IndexSelected = Selected; + Result.StartIndex = Start; + Result.Selection = Selection_None; + + r32 OptionHeight = Config.Font->NewLineYOffset + (2 * Config.Margin.y); + r32 OptionOffset = OptionHeight + Config.Margin.y; + + s32 OptionsToDisplay = ((Max.y - Min.y) / OptionHeight) - 2; + OptionsToDisplay = GSMin(OptionsToDisplay, (OptionsCount - Start)); + + string* OptionCursor = 0; + OptionCursor = Options + Start; + + v2 ButtonMin = v2{Min.x, Max.y - OptionHeight}; + v2 ButtonMax = v2{Max.x, Max.y}; + + for (s32 i = 0; i < OptionsToDisplay; i++) + { + button_result Button = EvaluateSelectableButton(RenderBuffer, ButtonMin, ButtonMax, + *OptionCursor, + Config, Input, (Selected == Start + i)); + if (Button.Pressed) + { + s32 SelectedIndex = Start + i; + if (SelectedIndex == Result.IndexSelected) + { + Result.Selection = Selection_Deselected; + Result.IndexSelected = -1; + } + else + { + Result.Selection = Selection_Selected; + Result.IndexSelected = Start + i; + } + } + + OptionCursor++; + + ButtonMin.y -= OptionOffset; + ButtonMax.y -= OptionOffset; + } + + r32 HalfWidthWithMargin = ((Max.x - Min.x) / 2.0f) - Config.Margin.x; + string DownArrowString = MakeStringLiteral(" v "); + string UpArrowString = MakeStringLiteral(" ^ "); + button_result Down = EvaluateButton(RenderBuffer, Min, v2{Min.x + HalfWidthWithMargin, Min.y + OptionHeight}, + DownArrowString, Config, Input); + button_result Up = EvaluateButton(RenderBuffer, v2{Min.x + HalfWidthWithMargin + Config.Margin.x, Min.y}, + v2{Max.x, Min.y + OptionHeight}, + UpArrowString, Config, Input); + if (Down.Pressed) + { + Result.StartIndex += 1; + } + if (Up.Pressed) + { + Result.StartIndex -= 1; + } + + Result.StartIndex = GSClamp(0, Result.StartIndex, OptionsCount); + + return Result; +} + +internal r32 +EvaluateColorChannelSlider (render_command_buffer* RenderBuffer, v4 ChannelMask, v2 Min, v2 Max, r32 Current, input Input) +{ + r32 Result = Current; + + render_quad_batch_constructor Batch = PushRenderQuad2DBatch(RenderBuffer, 2); + + v4 LeftColor = ChannelMask * 0; + LeftColor.a = 1.f; + v4 RightColor = ChannelMask; + PushQuad2DOnBatch(&Batch, + Min, v2{Max.x, Min.y}, Max, v2{Min.x, Max.y}, + v2{0, 0}, v2{1, 0}, v2{1, 1}, v2{0, 1}, + LeftColor, RightColor, RightColor, LeftColor); + + if (KeyDown(Input, KeyCode_MouseLeftButton)) + { + v2 MouseDownPos = v2{(r32)Input.MouseDownX, (r32)Input.MouseDownY}; + if (PointIsInRange(MouseDownPos, Min, Max)) + { + Result = ((r32)Input.New->MouseX - Min.x) / (Max.x - Min.x); + Result = GSClamp01(Result); + } + } + + r32 DragBarWidth = 8; + v2 DragBarMin = v2{GSLerp(Min.x, Max.x, Result) - (DragBarWidth / 2), Min.y - 2}; + v2 DragBarMax = DragBarMin + v2{DragBarWidth, (Max.y - Min.y) + 4}; + + PushQuad2DOnBatch(&Batch, DragBarMin, DragBarMax, v4{.3f, .3f, .3f, 1.f}); + + return Result; +} + +internal b32 +EvaluateColorPicker (render_command_buffer* RenderBuffer, v4* Value, v2 PanelMin, interface_config Config, input Input) +{ + b32 ShouldClose = false; + + v2 PanelMax = v2{400, 500}; + if (KeyTransitionedDown(Input, KeyCode_MouseLeftButton) && + !PointIsInRange(v2{(r32)Input.New->MouseX, (r32)Input.New->MouseY}, PanelMin, PanelMax)) + { + ShouldClose = true; + } + else + { + PushRenderQuad2D(RenderBuffer, PanelMin, PanelMax, v4{.5f, .5f, .5f, 1.f}); + + v2 TitleMin = v2{PanelMin.x + 5, PanelMax.y - (Config.Font->PixelHeight + 5)}; + DrawString(RenderBuffer, MakeStringLiteral("Color Picker"), Config.Font, Config.Font->PixelHeight, + TitleMin, WhiteV4); + + v2 SliderDim = v2{(PanelMax.x - PanelMin.x) - 20, 32}; + // channel sliders + v2 SliderMin = TitleMin - v2{0, SliderDim.y + 10}; + Value->r = EvaluateColorChannelSlider(RenderBuffer, RedV4, SliderMin, SliderMin + SliderDim, Value->r, Input); + SliderMin.y -= SliderDim.y + 10; + Value->g = EvaluateColorChannelSlider(RenderBuffer, GreenV4, SliderMin, SliderMin + SliderDim, Value->g, Input); + SliderMin.y -= SliderDim.y + 10; + Value->b = EvaluateColorChannelSlider(RenderBuffer, BlueV4, SliderMin, SliderMin + SliderDim, Value->b, Input); + SliderMin.y -= SliderDim.y + 10; + Value->a = EvaluateColorChannelSlider(RenderBuffer, WhiteV4, SliderMin, SliderMin + SliderDim, Value->a, Input); + + // Output Color Display + SliderMin.y -= 100; + PushRenderQuad2D(RenderBuffer, SliderMin, SliderMin + v2{75, 75}, *Value); + } + + return ShouldClose; +} \ No newline at end of file diff --git a/patterns_registry.h b/patterns_registry.h new file mode 100644 index 0000000..bef0080 --- /dev/null +++ b/patterns_registry.h @@ -0,0 +1,6 @@ +pattern_registry_entry PatternRegistry[] = +{ + {"Solid", SolidPatternInitProc, SolidPatternUpdateProc}, + {"Rainbow", InitRainbowPatternProc, RainbowPatternProc}, + {"Radial", InitRadialProc, UpdateRadialProc}, +}; \ No newline at end of file diff --git a/sse_mathfun.h b/sse_mathfun.h new file mode 100644 index 0000000..16b22e9 --- /dev/null +++ b/sse_mathfun.h @@ -0,0 +1,711 @@ +/* SIMD (SSE1+MMX or SSE2) implementation of sin, cos, exp and log + + Inspired by Intel Approximate Math library, and based on the + corresponding algorithms of the cephes math library + + The default is to use the SSE1 version. If you define USE_SSE2 the + the SSE2 intrinsics will be used in place of the MMX intrinsics. Do + not expect any significant performance improvement with SSE2. +*/ + +/* Copyright (C) 2007 Julien Pommier + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + (this is the zlib license) +*/ + +#include + +/* yes I know, the top of this file is quite ugly */ + +#ifdef _MSC_VER /* visual c++ */ +# define ALIGN16_BEG __declspec(align(16)) +# define ALIGN16_END +#else /* gcc or icc */ +# define ALIGN16_BEG +# define ALIGN16_END __attribute__((aligned(16))) +#endif + +/* __m128 is ugly to write */ +typedef __m128 v4sf; // vector of 4 float (sse1) + +#ifdef USE_SSE2 +# include +typedef __m128i v4si; // vector of 4 int (sse2) +#else +typedef __m64 v2si; // vector of 2 int (mmx) +#endif + +/* declare some SSE constants -- why can't I figure a better way to do that? */ +#define _PS_CONST(Name, Val) \ +static const ALIGN16_BEG float _ps_##Name[4] ALIGN16_END = { Val, Val, Val, Val } +#define _PI32_CONST(Name, Val) \ +static const ALIGN16_BEG int _pi32_##Name[4] ALIGN16_END = { Val, Val, Val, Val } +#define _PS_CONST_TYPE(Name, Type, Val) \ +static const ALIGN16_BEG Type _ps_##Name[4] ALIGN16_END = { Val, Val, Val, Val } + +_PS_CONST(1 , 1.0f); +_PS_CONST(0p5, 0.5f); +/* the smallest non denormalized float number */ +_PS_CONST_TYPE(min_norm_pos, int, 0x00800000); +_PS_CONST_TYPE(mant_mask, int, 0x7f800000); +_PS_CONST_TYPE(inv_mant_mask, int, ~0x7f800000); + +_PS_CONST_TYPE(sign_mask, int, (int)0x80000000); +_PS_CONST_TYPE(inv_sign_mask, int, ~0x80000000); + +_PI32_CONST(1, 1); +_PI32_CONST(inv1, ~1); +_PI32_CONST(2, 2); +_PI32_CONST(4, 4); +_PI32_CONST(0x7f, 0x7f); + +_PS_CONST(cephes_SQRTHF, 0.707106781186547524); +_PS_CONST(cephes_log_p0, 7.0376836292E-2); +_PS_CONST(cephes_log_p1, - 1.1514610310E-1); +_PS_CONST(cephes_log_p2, 1.1676998740E-1); +_PS_CONST(cephes_log_p3, - 1.2420140846E-1); +_PS_CONST(cephes_log_p4, + 1.4249322787E-1); +_PS_CONST(cephes_log_p5, - 1.6668057665E-1); +_PS_CONST(cephes_log_p6, + 2.0000714765E-1); +_PS_CONST(cephes_log_p7, - 2.4999993993E-1); +_PS_CONST(cephes_log_p8, + 3.3333331174E-1); +_PS_CONST(cephes_log_q1, -2.12194440e-4); +_PS_CONST(cephes_log_q2, 0.693359375); + +#ifndef USE_SSE2 +typedef union xmm_mm_union { + __m128 xmm; + __m64 mm[2]; +} xmm_mm_union; + +#define COPY_XMM_TO_MM(xmm_, mm0_, mm1_) { \ + xmm_mm_union u; u.xmm = xmm_; \ + mm0_ = u.mm[0]; \ + mm1_ = u.mm[1]; \ +} + +#define COPY_MM_TO_XMM(mm0_, mm1_, xmm_) { \ + xmm_mm_union u; u.mm[0]=mm0_; u.mm[1]=mm1_; xmm_ = u.xmm; \ +} + +#endif // USE_SSE2 + +/* natural logarithm computed for 4 simultaneous float + return NaN for x <= 0 +*/ +v4sf log_ps(v4sf x) { +#ifdef USE_SSE2 + v4si emm0; +#else + v2si mm0, mm1; +#endif + v4sf one = *(v4sf*)_ps_1; + + v4sf invalid_mask = _mm_cmple_ps(x, _mm_setzero_ps()); + + x = _mm_max_ps(x, *(v4sf*)_ps_min_norm_pos); /* cut off denormalized stuff */ + +#ifndef USE_SSE2 + /* part 1: x = frexpf(x, &e); */ + COPY_XMM_TO_MM(x, mm0, mm1); + mm0 = _mm_srli_pi32(mm0, 23); + mm1 = _mm_srli_pi32(mm1, 23); +#else + emm0 = _mm_srli_epi32(_mm_castps_si128(x), 23); +#endif + /* keep only the fractional part */ + x = _mm_and_ps(x, *(v4sf*)_ps_inv_mant_mask); + x = _mm_or_ps(x, *(v4sf*)_ps_0p5); + +#ifndef USE_SSE2 + /* now e=mm0:mm1 contain the really base-2 exponent */ + mm0 = _mm_sub_pi32(mm0, *(v2si*)_pi32_0x7f); + mm1 = _mm_sub_pi32(mm1, *(v2si*)_pi32_0x7f); + v4sf e = _mm_cvtpi32x2_ps(mm0, mm1); + _mm_empty(); /* bye bye mmx */ +#else + emm0 = _mm_sub_epi32(emm0, *(v4si*)_pi32_0x7f); + v4sf e = _mm_cvtepi32_ps(emm0); +#endif + + e = _mm_add_ps(e, one); + + /* part2: + if( x < SQRTHF ) { + e -= 1; + x = x + x - 1.0; + } else { x = x - 1.0; } + */ + v4sf mask = _mm_cmplt_ps(x, *(v4sf*)_ps_cephes_SQRTHF); + v4sf tmp = _mm_and_ps(x, mask); + x = _mm_sub_ps(x, one); + e = _mm_sub_ps(e, _mm_and_ps(one, mask)); + x = _mm_add_ps(x, tmp); + + + v4sf z = _mm_mul_ps(x,x); + + v4sf y = *(v4sf*)_ps_cephes_log_p0; + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_log_p1); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_log_p2); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_log_p3); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_log_p4); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_log_p5); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_log_p6); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_log_p7); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_log_p8); + y = _mm_mul_ps(y, x); + + y = _mm_mul_ps(y, z); + + + tmp = _mm_mul_ps(e, *(v4sf*)_ps_cephes_log_q1); + y = _mm_add_ps(y, tmp); + + + tmp = _mm_mul_ps(z, *(v4sf*)_ps_0p5); + y = _mm_sub_ps(y, tmp); + + tmp = _mm_mul_ps(e, *(v4sf*)_ps_cephes_log_q2); + x = _mm_add_ps(x, y); + x = _mm_add_ps(x, tmp); + x = _mm_or_ps(x, invalid_mask); // negative arg will be NAN + return x; +} + +_PS_CONST(exp_hi, 88.3762626647949f); +_PS_CONST(exp_lo, -88.3762626647949f); + +_PS_CONST(cephes_LOG2EF, 1.44269504088896341); +_PS_CONST(cephes_exp_C1, 0.693359375); +_PS_CONST(cephes_exp_C2, -2.12194440e-4); + +_PS_CONST(cephes_exp_p0, 1.9875691500E-4); +_PS_CONST(cephes_exp_p1, 1.3981999507E-3); +_PS_CONST(cephes_exp_p2, 8.3334519073E-3); +_PS_CONST(cephes_exp_p3, 4.1665795894E-2); +_PS_CONST(cephes_exp_p4, 1.6666665459E-1); +_PS_CONST(cephes_exp_p5, 5.0000001201E-1); + +v4sf exp_ps(v4sf x) { + v4sf tmp = _mm_setzero_ps(), fx; +#ifdef USE_SSE2 + v4si emm0; +#else + v2si mm0, mm1; +#endif + v4sf one = *(v4sf*)_ps_1; + + x = _mm_min_ps(x, *(v4sf*)_ps_exp_hi); + x = _mm_max_ps(x, *(v4sf*)_ps_exp_lo); + + /* express exp(x) as exp(g + n*log(2)) */ + fx = _mm_mul_ps(x, *(v4sf*)_ps_cephes_LOG2EF); + fx = _mm_add_ps(fx, *(v4sf*)_ps_0p5); + + /* how to perform a floorf with SSE: just below */ +#ifndef USE_SSE2 + /* step 1 : cast to int */ + tmp = _mm_movehl_ps(tmp, fx); + mm0 = _mm_cvttps_pi32(fx); + mm1 = _mm_cvttps_pi32(tmp); + /* step 2 : cast back to float */ + tmp = _mm_cvtpi32x2_ps(mm0, mm1); +#else + emm0 = _mm_cvttps_epi32(fx); + tmp = _mm_cvtepi32_ps(emm0); +#endif + /* if greater, substract 1 */ + v4sf mask = _mm_cmpgt_ps(tmp, fx); + mask = _mm_and_ps(mask, one); + fx = _mm_sub_ps(tmp, mask); + + tmp = _mm_mul_ps(fx, *(v4sf*)_ps_cephes_exp_C1); + v4sf z = _mm_mul_ps(fx, *(v4sf*)_ps_cephes_exp_C2); + x = _mm_sub_ps(x, tmp); + x = _mm_sub_ps(x, z); + + z = _mm_mul_ps(x,x); + + v4sf y = *(v4sf*)_ps_cephes_exp_p0; + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_exp_p1); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_exp_p2); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_exp_p3); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_exp_p4); + y = _mm_mul_ps(y, x); + y = _mm_add_ps(y, *(v4sf*)_ps_cephes_exp_p5); + y = _mm_mul_ps(y, z); + y = _mm_add_ps(y, x); + y = _mm_add_ps(y, one); + + /* build 2^n */ +#ifndef USE_SSE2 + z = _mm_movehl_ps(z, fx); + mm0 = _mm_cvttps_pi32(fx); + mm1 = _mm_cvttps_pi32(z); + mm0 = _mm_add_pi32(mm0, *(v2si*)_pi32_0x7f); + mm1 = _mm_add_pi32(mm1, *(v2si*)_pi32_0x7f); + mm0 = _mm_slli_pi32(mm0, 23); + mm1 = _mm_slli_pi32(mm1, 23); + + v4sf pow2n; + COPY_MM_TO_XMM(mm0, mm1, pow2n); + _mm_empty(); +#else + emm0 = _mm_cvttps_epi32(fx); + emm0 = _mm_add_epi32(emm0, *(v4si*)_pi32_0x7f); + emm0 = _mm_slli_epi32(emm0, 23); + v4sf pow2n = _mm_castsi128_ps(emm0); +#endif + y = _mm_mul_ps(y, pow2n); + return y; +} + +_PS_CONST(minus_cephes_DP1, -0.78515625); +_PS_CONST(minus_cephes_DP2, -2.4187564849853515625e-4); +_PS_CONST(minus_cephes_DP3, -3.77489497744594108e-8); +_PS_CONST(sincof_p0, -1.9515295891E-4); +_PS_CONST(sincof_p1, 8.3321608736E-3); +_PS_CONST(sincof_p2, -1.6666654611E-1); +_PS_CONST(coscof_p0, 2.443315711809948E-005); +_PS_CONST(coscof_p1, -1.388731625493765E-003); +_PS_CONST(coscof_p2, 4.166664568298827E-002); +_PS_CONST(cephes_FOPI, 1.27323954473516); // 4 / M_PI + + +/* evaluation of 4 sines at onces, using only SSE1+MMX intrinsics so + it runs also on old athlons XPs and the pentium III of your grand + mother. + + The code is the exact rewriting of the cephes sinf function. + Precision is excellent as long as x < 8192 (I did not bother to + take into account the special handling they have for greater values + -- it does not return garbage for arguments over 8192, though, but + the extra precision is missing). + + Note that it is such that sinf((float)M_PI) = 8.74e-8, which is the + surprising but correct result. + + Performance is also surprisingly good, 1.33 times faster than the + macos vsinf SSE2 function, and 1.5 times faster than the + __vrs4_sinf of amd's ACML (which is only available in 64 bits). Not + too bad for an SSE1 function (with no special tuning) ! + However the latter libraries probably have a much better handling of NaN, + Inf, denormalized and other special arguments.. + + On my core 1 duo, the execution of this function takes approximately 95 cycles. + + From what I have observed on the experiments with Intel AMath lib, switching to an + SSE2 version would improve the perf by only 10%. + + Since it is based on SSE intrinsics, it has to be compiled at -O2 to + deliver full speed. +*/ +v4sf sin_ps(v4sf x) { // any x + v4sf xmm1, xmm2 = _mm_setzero_ps(), xmm3, sign_bit, y; + +#ifdef USE_SSE2 + v4si emm0, emm2; +#else + v2si mm0, mm1, mm2, mm3; +#endif + sign_bit = x; + /* take the absolute value */ + x = _mm_and_ps(x, *(v4sf*)_ps_inv_sign_mask); + /* extract the sign bit (upper one) */ + sign_bit = _mm_and_ps(sign_bit, *(v4sf*)_ps_sign_mask); + + /* scale by 4/Pi */ + y = _mm_mul_ps(x, *(v4sf*)_ps_cephes_FOPI); + +#ifdef USE_SSE2 + /* store the integer part of y in mm0 */ + emm2 = _mm_cvttps_epi32(y); + /* j=(j+1) & (~1) (see the cephes sources) */ + emm2 = _mm_add_epi32(emm2, *(v4si*)_pi32_1); + emm2 = _mm_and_si128(emm2, *(v4si*)_pi32_inv1); + y = _mm_cvtepi32_ps(emm2); + + /* get the swap sign flag */ + emm0 = _mm_and_si128(emm2, *(v4si*)_pi32_4); + emm0 = _mm_slli_epi32(emm0, 29); + /* get the polynom selection mask + there is one polynom for 0 <= x <= Pi/4 + and another one for Pi/4> precision OK for the tan_ps <<- + +checking tan on [-0.49*Pi, 0.49*Pi] +max deviation from tanf(x): 3.8147e-06 at -0.490000009841*Pi, max deviation from cephes_tan(x): +9.53674e-07 + ->> precision OK for the tan_ps <<- + +checking cot on [0.2*Pi, 0.7*Pi] +max deviation from cotf(x): 1.19209e-07 at 0.204303119606*Pi, max deviation from cephes_cot(x): +1.19209e-07 + ->> precision OK for the cot_ps <<- + +checking cot on [0.01*Pi, 0.99*Pi] +max deviation from cotf(x): 3.8147e-06 at 0.987876517942*Pi, max deviation from cephes_cot(x): +9.53674e-07 + ->> precision OK for the cot_ps <<- + +With atan_ps and atan2_ps you get pretty good precision, atan_ps max deviation is < 2e-7 and +atan2_ps max deviation is < 2.5e-7 +*/ + +/* Copyright (C) 2016 Tolga Mizrak + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + (this is the zlib license) +*/ + +#pragma once + +#ifndef _SSE_MATHFUN_EXTENSION_H_INCLUDED_ +#define _SSE_MATHFUN_EXTENSION_H_INCLUDED_ + +#ifndef USE_SSE2 +#error sse1 & mmx version not implemented +#endif + +#ifdef _MSC_VER +#pragma warning( push ) +/* warning C4838: conversion from 'double' to 'const float' requires a narrowing conversion */ +#pragma warning( disable : 4838 ) +/* warning C4305: 'initializing': truncation from 'double' to 'const float' */ +#pragma warning( disable : 4305 ) +#endif + +#include "sse_mathfun.h" + +_PS_CONST( 0, 0 ); +_PS_CONST( 2, 2 ); +_PI32_CONST( neg1, 1 ); + +_PS_CONST( tancof_p0, 9.38540185543E-3 ); +_PS_CONST( tancof_p1, 3.11992232697E-3 ); +_PS_CONST( tancof_p2, 2.44301354525E-2 ); +_PS_CONST( tancof_p3, 5.34112807005E-2 ); +_PS_CONST( tancof_p4, 1.33387994085E-1 ); +_PS_CONST( tancof_p5, 3.33331568548E-1 ); + +_PS_CONST( tancot_eps, 1.0e-4 ); + +v4sf tancot_ps( v4sf x, int cotFlag ) +{ + v4sf xmm1, xmm2 = _mm_setzero_ps(), xmm3, sign_bit, y; + +#ifdef USE_SSE2 + v4si emm2; +#else +#endif + sign_bit = x; + /* take the absolute value */ + x = _mm_and_ps( x, *(v4sf*)_ps_inv_sign_mask ); + /* extract the sign bit (upper one) */ + sign_bit = _mm_and_ps( sign_bit, *(v4sf*)_ps_sign_mask ); + + /* scale by 4/Pi */ + y = _mm_mul_ps( x, *(v4sf*)_ps_cephes_FOPI ); + +#ifdef USE_SSE2 + /* store the integer part of y in mm0 */ + emm2 = _mm_cvttps_epi32( y ); + /* j=(j+1) & (~1) (see the cephes sources) */ + emm2 = _mm_add_epi32( emm2, *(v4si*)_pi32_1 ); + emm2 = _mm_and_si128( emm2, *(v4si*)_pi32_inv1 ); + y = _mm_cvtepi32_ps( emm2 ); + + emm2 = _mm_and_si128( emm2, *(v4si*)_pi32_2 ); + emm2 = _mm_cmpeq_epi32( emm2, _mm_setzero_si128() ); + + v4sf poly_mask = _mm_castsi128_ps( emm2 ); +#else +#endif + /* The magic pass: "Extended precision modular arithmetic" + x = ((x - y * DP1) - y * DP2) - y * DP3; */ + xmm1 = *(v4sf*)_ps_minus_cephes_DP1; + xmm2 = *(v4sf*)_ps_minus_cephes_DP2; + xmm3 = *(v4sf*)_ps_minus_cephes_DP3; + xmm1 = _mm_mul_ps( y, xmm1 ); + xmm2 = _mm_mul_ps( y, xmm2 ); + xmm3 = _mm_mul_ps( y, xmm3 ); + v4sf z = _mm_add_ps( x, xmm1 ); + z = _mm_add_ps( z, xmm2 ); + z = _mm_add_ps( z, xmm3 ); + + v4sf zz = _mm_mul_ps( z, z ); + + y = *(v4sf*)_ps_tancof_p0; + y = _mm_mul_ps( y, zz ); + y = _mm_add_ps( y, *(v4sf*)_ps_tancof_p1 ); + y = _mm_mul_ps( y, zz ); + y = _mm_add_ps( y, *(v4sf*)_ps_tancof_p2 ); + y = _mm_mul_ps( y, zz ); + y = _mm_add_ps( y, *(v4sf*)_ps_tancof_p3 ); + y = _mm_mul_ps( y, zz ); + y = _mm_add_ps( y, *(v4sf*)_ps_tancof_p4 ); + y = _mm_mul_ps( y, zz ); + y = _mm_add_ps( y, *(v4sf*)_ps_tancof_p5 ); + y = _mm_mul_ps( y, zz ); + y = _mm_mul_ps( y, z ); + y = _mm_add_ps( y, z ); + + v4sf y2; + if( cotFlag ) { + y2 = _mm_xor_ps( y, *(v4sf*)_ps_sign_mask ); + /* y = _mm_rcp_ps( y ); */ + /* using _mm_rcp_ps here loses on way too much precision, better to do a div */ + y = _mm_div_ps( *(v4sf*)_ps_1, y ); + } else { + /* y2 = _mm_rcp_ps( y ); */ + /* using _mm_rcp_ps here loses on way too much precision, better to do a div */ + y2 = _mm_div_ps( *(v4sf*)_ps_1, y ); + y2 = _mm_xor_ps( y2, *(v4sf*)_ps_sign_mask ); + } + + /* select the correct result from the two polynoms */ + xmm3 = poly_mask; + y = _mm_and_ps( xmm3, y ); + y2 = _mm_andnot_ps( xmm3, y2 ); + y = _mm_or_ps( y, y2 ); + + /* update the sign */ + y = _mm_xor_ps( y, sign_bit ); + + return y; +} + +v4sf tan_ps( v4sf x ) { return tancot_ps( x, 0 ); } + +v4sf cot_ps( v4sf x ) { return tancot_ps( x, 1 ); } + +_PS_CONST( atanrange_hi, 2.414213562373095 ); +_PS_CONST( atanrange_lo, 0.4142135623730950 ); +const float PIF = 3.141592653589793238; +const float PIO2F = 1.5707963267948966192; +_PS_CONST( cephes_PIF, 3.141592653589793238 ); +_PS_CONST( cephes_PIO2F, 1.5707963267948966192 ); +_PS_CONST( cephes_PIO4F, 0.7853981633974483096 ); + +_PS_CONST( atancof_p0, 8.05374449538e-2 ); +_PS_CONST( atancof_p1, 1.38776856032E-1 ); +_PS_CONST( atancof_p2, 1.99777106478E-1 ); +_PS_CONST( atancof_p3, 3.33329491539E-1 ); + +v4sf atan_ps( v4sf x ) +{ + v4sf sign_bit, y; + + sign_bit = x; + /* take the absolute value */ + x = _mm_and_ps( x, *(v4sf*)_ps_inv_sign_mask ); + /* extract the sign bit (upper one) */ + sign_bit = _mm_and_ps( sign_bit, *(v4sf*)_ps_sign_mask ); + + /* range reduction, init x and y depending on range */ +#ifdef USE_SSE2 + /* x > 2.414213562373095 */ + v4sf cmp0 = _mm_cmpgt_ps( x, *(v4sf*)_ps_atanrange_hi ); + /* x > 0.4142135623730950 */ + v4sf cmp1 = _mm_cmpgt_ps( x, *(v4sf*)_ps_atanrange_lo ); + + /* x > 0.4142135623730950 && !( x > 2.414213562373095 ) */ + v4sf cmp2 = _mm_andnot_ps( cmp0, cmp1 ); + + /* -( 1.0/x ) */ + v4sf y0 = _mm_and_ps( cmp0, *(v4sf*)_ps_cephes_PIO2F ); + v4sf x0 = _mm_div_ps( *(v4sf*)_ps_1, x ); + x0 = _mm_xor_ps( x0, *(v4sf*)_ps_sign_mask ); + + v4sf y1 = _mm_and_ps( cmp2, *(v4sf*)_ps_cephes_PIO4F ); + /* (x-1.0)/(x+1.0) */ + v4sf x1_o = _mm_sub_ps( x, *(v4sf*)_ps_1 ); + v4sf x1_u = _mm_add_ps( x, *(v4sf*)_ps_1 ); + v4sf x1 = _mm_div_ps( x1_o, x1_u ); + + v4sf x2 = _mm_and_ps( cmp2, x1 ); + x0 = _mm_and_ps( cmp0, x0 ); + x2 = _mm_or_ps( x2, x0 ); + cmp1 = _mm_or_ps( cmp0, cmp2 ); + x2 = _mm_and_ps( cmp1, x2 ); + x = _mm_andnot_ps( cmp1, x ); + x = _mm_or_ps( x2, x ); + + y = _mm_or_ps( y0, y1 ); +#else +#error sse1 & mmx version not implemented +#endif + + v4sf zz = _mm_mul_ps( x, x ); + v4sf acc = *(v4sf*)_ps_atancof_p0; + acc = _mm_mul_ps( acc, zz ); + acc = _mm_sub_ps( acc, *(v4sf*)_ps_atancof_p1 ); + acc = _mm_mul_ps( acc, zz ); + acc = _mm_add_ps( acc, *(v4sf*)_ps_atancof_p2 ); + acc = _mm_mul_ps( acc, zz ); + acc = _mm_sub_ps( acc, *(v4sf*)_ps_atancof_p3 ); + acc = _mm_mul_ps( acc, zz ); + acc = _mm_mul_ps( acc, x ); + acc = _mm_add_ps( acc, x ); + y = _mm_add_ps( y, acc ); + + /* update the sign */ + y = _mm_xor_ps( y, sign_bit ); + + return y; +} + +v4sf atan2_ps( v4sf y, v4sf x ) +{ + v4sf x_eq_0 = _mm_cmpeq_ps( x, *(v4sf*)_ps_0 ); + v4sf x_gt_0 = _mm_cmpgt_ps( x, *(v4sf*)_ps_0 ); + v4sf x_le_0 = _mm_cmple_ps( x, *(v4sf*)_ps_0 ); + v4sf y_eq_0 = _mm_cmpeq_ps( y, *(v4sf*)_ps_0 ); + v4sf x_lt_0 = _mm_cmplt_ps( x, *(v4sf*)_ps_0 ); + v4sf y_lt_0 = _mm_cmplt_ps( y, *(v4sf*)_ps_0 ); + + v4sf zero_mask = _mm_and_ps( x_eq_0, y_eq_0 ); + v4sf zero_mask_other_case = _mm_and_ps( y_eq_0, x_gt_0 ); + zero_mask = _mm_or_ps( zero_mask, zero_mask_other_case ); + + v4sf pio2_mask = _mm_andnot_ps( y_eq_0, x_eq_0 ); + v4sf pio2_mask_sign = _mm_and_ps( y_lt_0, *(v4sf*)_ps_sign_mask ); + v4sf pio2_result = *(v4sf*)_ps_cephes_PIO2F; + pio2_result = _mm_xor_ps( pio2_result, pio2_mask_sign ); + pio2_result = _mm_and_ps( pio2_mask, pio2_result ); + + v4sf pi_mask = _mm_and_ps( y_eq_0, x_le_0 ); + v4sf pi = *(v4sf*)_ps_cephes_PIF; + v4sf pi_result = _mm_and_ps( pi_mask, pi ); + + v4sf swap_sign_mask_offset = _mm_and_ps( x_lt_0, y_lt_0 ); + swap_sign_mask_offset = _mm_and_ps( swap_sign_mask_offset, *(v4sf*)_ps_sign_mask ); + + v4sf offset0 = _mm_setzero_ps(); + v4sf offset1 = *(v4sf*)_ps_cephes_PIF; + offset1 = _mm_xor_ps( offset1, swap_sign_mask_offset ); + + v4sf offset = _mm_andnot_ps( x_lt_0, offset0 ); + offset = _mm_and_ps( x_lt_0, offset1 ); + + v4sf arg = _mm_div_ps( y, x ); + v4sf atan_result = atan_ps( arg ); + atan_result = _mm_add_ps( atan_result, offset ); + + /* select between zero_result, pio2_result and atan_result */ + + v4sf result = _mm_andnot_ps( zero_mask, pio2_result ); + atan_result = _mm_andnot_ps( pio2_mask, atan_result ); + atan_result = _mm_andnot_ps( pio2_mask, atan_result); + result = _mm_or_ps( result, atan_result ); + result = _mm_or_ps( result, pi_result ); + + return result; +} + +/* for convenience of calling simd sqrt */ +float sqrt_ps( float x ) +{ + v4sf sse_value = _mm_set_ps1( x ); + sse_value = _mm_sqrt_ps( sse_value ); + return _mm_cvtss_f32( sse_value ); +} +float rsqrt_ps( float x ) +{ + v4sf sse_value = _mm_set_ps1( x ); + sse_value = _mm_rsqrt_ps( sse_value ); + return _mm_cvtss_f32( sse_value ); +} + +/* atan2 implementation using atan, used as a reference to implement atan2_ps */ +float atan2_ref( float y, float x ) +{ + if( x == 0.0f ) { + if( y == 0.0f ) { + return 0.0f; + } + float result = _ps_cephes_PIO2F[0]; + if( y < 0.0f ) { + result = -result; + } + return result; + } + + if( y == 0.0f ) { + if( x > 0.0f ) { + return 0.0f; + } + return PIF; + } + + float offset = 0; + if( x < 0.0f ) { + offset = PIF; + if( y < 0.0f ) { + offset = -offset; + } + } + + v4sf val = _mm_set_ps1( y / x ); + val = atan_ps( val ); + return offset + _mm_cvtss_f32( val ); +} + +#ifdef _MSC_VER +#pragma warning( pop ) +#endif + +#endif diff --git a/stb/stb_truetype.h b/stb/stb_truetype.h new file mode 100644 index 0000000..3ffe4de --- /dev/null +++ b/stb/stb_truetype.h @@ -0,0 +1,4853 @@ +// stb_truetype.h - v1.19 - public domain +// authored from 2009-2016 by Sean Barrett / RAD Game Tools +// +// This library processes TrueType files: +// parse files +// extract glyph metrics +// extract glyph shapes +// render glyphs to one-channel bitmaps with antialiasing (box filter) +// render glyphs to one-channel SDF bitmaps (signed-distance field/function) +// +// Todo: +// non-MS cmaps +// crashproof on bad data +// hinting? (no longer patented) +// cleartype-style AA? +// optimize: use simple memory allocator for intermediates +// optimize: build edge-list directly from curves +// optimize: rasterize directly from curves? +// +// ADDITIONAL CONTRIBUTORS +// +// Mikko Mononen: compound shape support, more cmap formats +// Tor Andersson: kerning, subpixel rendering +// Dougall Johnson: OpenType / Type 2 font handling +// Daniel Ribeiro Maciel: basic GPOS-based kerning +// +// Misc other: +// Ryan Gordon +// Simon Glass +// github:IntellectualKitty +// Imanol Celaya +// Daniel Ribeiro Maciel +// +// Bug/warning reports/fixes: +// "Zer" on mollyrocket Fabian "ryg" Giesen +// Cass Everitt Martins Mozeiko +// stoiko (Haemimont Games) Cap Petschulat +// Brian Hook Omar Cornut +// Walter van Niftrik github:aloucks +// David Gow Peter LaValle +// David Given Sergey Popov +// Ivan-Assen Ivanov Giumo X. Clanjor +// Anthony Pesch Higor Euripedes +// Johan Duparc Thomas Fields +// Hou Qiming Derek Vinyard +// Rob Loach Cort Stratton +// Kenney Phillis Jr. github:oyvindjam +// Brian Costabile github:vassvik +// +// VERSION HISTORY +// +// 1.19 (2018-02-11) GPOS kerning, STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) user-defined fabs(); rare memory leak; remove duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use allocation userdata properly +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// variant PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// +// Full history can be found at the end of this file. +// +// LICENSE +// +// See end of file for license information. +// +// USAGE +// +// Include this file in whatever places neeed to refer to it. In ONE C/C++ +// file, write: +// #define STB_TRUETYPE_IMPLEMENTATION +// before the #include of this file. This expands out the actual +// implementation into that C/C++ file. +// +// To make the implementation private to the file that generates the implementation, +// #define STBTT_STATIC +// +// Simple 3D API (don't ship this, but it's fine for tools and quick start) +// stbtt_BakeFontBitmap() -- bake a font to a bitmap for use as texture +// stbtt_GetBakedQuad() -- compute quad to draw for a given char +// +// Improved 3D API (more shippable): +// #include "stb_rect_pack.h" -- optional, but you really want it +// stbtt_PackBegin() +// stbtt_PackSetOversampling() -- for improved quality on small fonts +// stbtt_PackFontRanges() -- pack and renders +// stbtt_PackEnd() +// stbtt_GetPackedQuad() +// +// "Load" a font file from a memory buffer (you have to keep the buffer loaded) +// stbtt_InitFont() +// stbtt_GetFontOffsetForIndex() -- indexing for TTC font collections +// stbtt_GetNumberOfFonts() -- number of fonts for TTC font collections +// +// Render a unicode codepoint to a bitmap +// stbtt_GetCodepointBitmap() -- allocates and returns a bitmap +// stbtt_MakeCodepointBitmap() -- renders into bitmap you provide +// stbtt_GetCodepointBitmapBox() -- how big the bitmap must be +// +// Character advance/positioning +// stbtt_GetCodepointHMetrics() +// stbtt_GetFontVMetrics() +// stbtt_GetFontVMetricsOS2() +// stbtt_GetCodepointKernAdvance() +// +// Starting with version 1.06, the rasterizer was replaced with a new, +// faster and generally-more-precise rasterizer. The new rasterizer more +// accurately measures pixel coverage for anti-aliasing, except in the case +// where multiple shapes overlap, in which case it overestimates the AA pixel +// coverage. Thus, anti-aliasing of intersecting shapes may look wrong. If +// this turns out to be a problem, you can re-enable the old rasterizer with +// #define STBTT_RASTERIZER_VERSION 1 +// which will incur about a 15% speed hit. +// +// ADDITIONAL DOCUMENTATION +// +// Immediately after this block comment are a series of sample programs. +// +// After the sample programs is the "header file" section. This section +// includes documentation for each API function. +// +// Some important concepts to understand to use this library: +// +// Codepoint +// Characters are defined by unicode codepoints, e.g. 65 is +// uppercase A, 231 is lowercase c with a cedilla, 0x7e30 is +// the hiragana for "ma". +// +// Glyph +// A visual character shape (every codepoint is rendered as +// some glyph) +// +// Glyph index +// A font-specific integer ID representing a glyph +// +// Baseline +// Glyph shapes are defined relative to a baseline, which is the +// bottom of uppercase characters. Characters extend both above +// and below the baseline. +// +// Current Point +// As you draw text to the screen, you keep track of a "current point" +// which is the origin of each character. The current point's vertical +// position is the baseline. Even "baked fonts" use this model. +// +// Vertical Font Metrics +// The vertical qualities of the font, used to vertically position +// and space the characters. See docs for stbtt_GetFontVMetrics. +// +// Font Size in Pixels or Points +// The preferred interface for specifying font sizes in stb_truetype +// is to specify how tall the font's vertical extent should be in pixels. +// If that sounds good enough, skip the next paragraph. +// +// Most font APIs instead use "points", which are a common typographic +// measurement for describing font size, defined as 72 points per inch. +// stb_truetype provides a point API for compatibility. However, true +// "per inch" conventions don't make much sense on computer displays +// since different monitors have different number of pixels per +// inch. For example, Windows traditionally uses a convention that +// there are 96 pixels per inch, thus making 'inch' measurements have +// nothing to do with inches, and thus effectively defining a point to +// be 1.333 pixels. Additionally, the TrueType font data provides +// an explicit scale factor to scale a given font's glyphs to points, +// but the author has observed that this scale factor is often wrong +// for non-commercial fonts, thus making fonts scaled in points +// according to the TrueType spec incoherently sized in practice. +// +// DETAILED USAGE: +// +// Scale: +// Select how high you want the font to be, in points or pixels. +// Call ScaleForPixelHeight or ScaleForMappingEmToPixels to compute +// a scale factor SF that will be used by all other functions. +// +// Baseline: +// You need to select a y-coordinate that is the baseline of where +// your text will appear. Call GetFontBoundingBox to get the baseline-relative +// bounding box for all characters. SF*-y0 will be the distance in pixels +// that the worst-case character could extend above the baseline, so if +// you want the top edge of characters to appear at the top of the +// screen where y=0, then you would set the baseline to SF*-y0. +// +// Current point: +// Set the current point where the first character will appear. The +// first character could extend left of the current point; this is font +// dependent. You can either choose a current point that is the leftmost +// point and hope, or add some padding, or check the bounding box or +// left-side-bearing of the first character to be displayed and set +// the current point based on that. +// +// Displaying a character: +// Compute the bounding box of the character. It will contain signed values +// relative to . I.e. if it returns x0,y0,x1,y1, +// then the character should be displayed in the rectangle from +// to = 32 && *text < 128) { + stbtt_aligned_quad q; + stbtt_GetBakedQuad(cdata, 512,512, *text-32, &x,&y,&q,1);//1=opengl & d3d10+,0=d3d9 + glTexCoord2f(q.s0,q.t1); glVertex2f(q.x0,q.y0); + glTexCoord2f(q.s1,q.t1); glVertex2f(q.x1,q.y0); + glTexCoord2f(q.s1,q.t0); glVertex2f(q.x1,q.y1); + glTexCoord2f(q.s0,q.t0); glVertex2f(q.x0,q.y1); + } + ++text; + } + glEnd(); +} +#endif +// +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program (this compiles): get a single bitmap, print as ASCII art +// +#if 0 +#include +#define STB_TRUETYPE_IMPLEMENTATION // force following include to generate implementation +#include "stb_truetype.h" + +char ttf_buffer[1<<25]; + +int main(int argc, char **argv) +{ + stbtt_fontinfo font; + unsigned char *bitmap; + int w,h,i,j,c = (argc > 1 ? atoi(argv[1]) : 'a'), s = (argc > 2 ? atoi(argv[2]) : 20); + + fread(ttf_buffer, 1, 1<<25, fopen(argc > 3 ? argv[3] : "c:/windows/fonts/arialbd.ttf", "rb")); + + stbtt_InitFont(&font, ttf_buffer, stbtt_GetFontOffsetForIndex(ttf_buffer,0)); + bitmap = stbtt_GetCodepointBitmap(&font, 0,stbtt_ScaleForPixelHeight(&font, s), c, &w, &h, 0,0); + + for (j=0; j < h; ++j) { + for (i=0; i < w; ++i) + putchar(" .:ioVM@"[bitmap[j*w+i]>>5]); + putchar('\n'); + } + return 0; +} +#endif +// +// Output: +// +// .ii. +// @@@@@@. +// V@Mio@@o +// :i. V@V +// :oM@@M +// :@@@MM@M +// @@o o@M +// :@@. M@M +// @@@o@@@@ +// :M@@V:@@. +// +////////////////////////////////////////////////////////////////////////////// +// +// Complete program: print "Hello World!" banner, with bugs +// +#if 0 +char buffer[24<<20]; +unsigned char screen[20][79]; + +int main(int arg, char **argv) +{ + stbtt_fontinfo font; + int i,j,ascent,baseline,ch=0; + float scale, xpos=2; // leave a little padding in case the character extends left + char *text = "Heljo World!"; // intentionally misspelled to show 'lj' brokenness + + fread(buffer, 1, 1000000, fopen("c:/windows/fonts/arialbd.ttf", "rb")); + stbtt_InitFont(&font, buffer, 0); + + scale = stbtt_ScaleForPixelHeight(&font, 15); + stbtt_GetFontVMetrics(&font, &ascent,0,0); + baseline = (int) (ascent*scale); + + while (text[ch]) { + int advance,lsb,x0,y0,x1,y1; + float x_shift = xpos - (float) floor(xpos); + stbtt_GetCodepointHMetrics(&font, text[ch], &advance, &lsb); + stbtt_GetCodepointBitmapBoxSubpixel(&font, text[ch], scale,scale,x_shift,0, &x0,&y0,&x1,&y1); + stbtt_MakeCodepointBitmapSubpixel(&font, &screen[baseline + y0][(int) xpos + x0], x1-x0,y1-y0, 79, scale,scale,x_shift,0, text[ch]); + // note that this stomps the old data, so where character boxes overlap (e.g. 'lj') it's wrong + // because this API is really for baking character bitmaps into textures. if you want to render + // a sequence of characters, you really need to render each bitmap to a temp buffer, then + // "alpha blend" that into the working buffer + xpos += (advance * scale); + if (text[ch+1]) + xpos += scale*stbtt_GetCodepointKernAdvance(&font, text[ch],text[ch+1]); + ++ch; + } + + for (j=0; j < 20; ++j) { + for (i=0; i < 78; ++i) + putchar(" .:ioVM@"[screen[j][i]>>5]); + putchar('\n'); + } + + return 0; +} +#endif + + +////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +//// +//// INTEGRATION WITH YOUR CODEBASE +//// +//// The following sections allow you to supply alternate definitions +//// of C library functions used by stb_truetype, e.g. if you don't +//// link with the C runtime library. + +#ifdef STB_TRUETYPE_IMPLEMENTATION +// #define your own (u)stbtt_int8/16/32 before including to override this +#ifndef stbtt_uint8 +typedef unsigned char stbtt_uint8; +typedef signed char stbtt_int8; +typedef unsigned short stbtt_uint16; +typedef signed short stbtt_int16; +typedef unsigned int stbtt_uint32; +typedef signed int stbtt_int32; +#endif + +typedef char stbtt__check_size32[sizeof(stbtt_int32)==4 ? 1 : -1]; +typedef char stbtt__check_size16[sizeof(stbtt_int16)==2 ? 1 : -1]; + +// e.g. #define your own STBTT_ifloor/STBTT_iceil() to avoid math.h +#ifndef STBTT_ifloor +#include +#define STBTT_ifloor(x) ((int) floor(x)) +#define STBTT_iceil(x) ((int) ceil(x)) +#endif + +#ifndef STBTT_sqrt +#include +#define STBTT_sqrt(x) sqrt(x) +#define STBTT_pow(x,y) pow(x,y) +#endif + +#ifndef STBTT_fmod +#include +#define STBTT_fmod(x,y) fmod(x,y) +#endif + +#ifndef STBTT_cos +#include +#define STBTT_cos(x) cos(x) +#define STBTT_acos(x) acos(x) +#endif + +#ifndef STBTT_fabs +#include +#define STBTT_fabs(x) fabs(x) +#endif + +// #define your own functions "STBTT_malloc" / "STBTT_free" to avoid malloc.h +#ifndef STBTT_malloc +#include +#define STBTT_malloc(x,u) ((void)(u),malloc(x)) +#define STBTT_free(x,u) ((void)(u),free(x)) +#endif + +#ifndef STBTT_assert +#include +#define STBTT_assert(x) assert(x) +#endif + +#ifndef STBTT_strlen +#include +#define STBTT_strlen(x) strlen(x) +#endif + +#ifndef STBTT_memcpy +#include +#define STBTT_memcpy memcpy +#define STBTT_memset memset +#endif +#endif + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// INTERFACE +//// +//// + +#ifndef __STB_INCLUDE_STB_TRUETYPE_H__ +#define __STB_INCLUDE_STB_TRUETYPE_H__ + +#ifdef STBTT_STATIC +#define STBTT_DEF static +#else +#define STBTT_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + + // private structure + typedef struct + { + unsigned char *data; + int cursor; + int size; + } stbtt__buf; + + ////////////////////////////////////////////////////////////////////////////// + // + // TEXTURE BAKING API + // + // If you use this API, you only have to call two functions ever. + // + + typedef struct + { + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + } stbtt_bakedchar; + + STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata); // you allocate this, it's num_chars long + // if return is positive, the first unused row of the bitmap + // if return is negative, returns the negative of the number of characters that fit + // if return is 0, no characters fit and no rows were used + // This uses a very crappy packing. + + typedef struct + { + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right + } stbtt_aligned_quad; + + STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int opengl_fillrule); // true if opengl fill rule; false if DX9 or earlier + // Call GetBakedQuad with char_index = 'character - first_char', and it + // creates the quad you need to draw and advances the current position. + // + // The coordinate system used assumes y increases downwards. + // + // Characters will extend both above and below the current position; + // see discussion of "BASELINE" above. + // + // It's inefficient; you might want to c&p it and optimize it. + + + + ////////////////////////////////////////////////////////////////////////////// + // + // NEW TEXTURE BAKING API + // + // This provides options for packing multiple fonts into one atlas, not + // perfectly but better than nothing. + + typedef struct + { + unsigned short x0,y0,x1,y1; // coordinates of bbox in bitmap + float xoff,yoff,xadvance; + float xoff2,yoff2; + } stbtt_packedchar; + + typedef struct stbtt_pack_context stbtt_pack_context; + typedef struct stbtt_fontinfo stbtt_fontinfo; +#ifndef STB_RECT_PACK_VERSION + typedef struct stbrp_rect stbrp_rect; +#endif + + STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int width, int height, int stride_in_bytes, int padding, void *alloc_context); + // Initializes a packing context stored in the passed-in stbtt_pack_context. + // Future calls using this context will pack characters into the bitmap passed + // in here: a 1-channel bitmap that is width * height. stride_in_bytes is + // the distance from one row to the next (or 0 to mean they are packed tightly + // together). "padding" is the amount of padding to leave between each + // character (normally you want '1' for bitmaps you'll use as textures with + // bilinear filtering). + // + // Returns 0 on failure, 1 on success. + + STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc); + // Cleans up the packing context and frees all memory. + +#define STBTT_POINT_SIZE(x) (-(x)) + + STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_char_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range); + // Creates character bitmaps from the font_index'th font found in fontdata (use + // font_index=0 if you don't know what that is). It creates num_chars_in_range + // bitmaps for characters with unicode values starting at first_unicode_char_in_range + // and increasing. Data for how to render them is stored in chardata_for_range; + // pass these to stbtt_GetPackedQuad to get back renderable quads. + // + // font_size is the full height of the character from ascender to descender, + // as computed by stbtt_ScaleForPixelHeight. To use a point size as computed + // by stbtt_ScaleForMappingEmToPixels, wrap the point size in STBTT_POINT_SIZE() + // and pass that result as 'font_size': + // ..., 20 , ... // font max minus min y is 20 pixels tall + // ..., STBTT_POINT_SIZE(20), ... // 'M' is 20 pixels tall + + typedef struct + { + float font_size; + int first_unicode_codepoint_in_range; // if non-zero, then the chars are continuous, and this is the first codepoint + int *array_of_unicode_codepoints; // if non-zero, then this is an array of unicode codepoints + int num_chars; + stbtt_packedchar *chardata_for_range; // output + unsigned char h_oversample, v_oversample; // don't set these, they're used internally + } stbtt_pack_range; + + STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges); + // Creates character bitmaps from multiple ranges of characters stored in + // ranges. This will usually create a better-packed bitmap than multiple + // calls to stbtt_PackFontRange. Note that you can call this multiple + // times within a single PackBegin/PackEnd. + + STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample); + // Oversampling a font increases the quality by allowing higher-quality subpixel + // positioning, and is especially valuable at smaller text sizes. + // + // This function sets the amount of oversampling for all following calls to + // stbtt_PackFontRange(s) or stbtt_PackFontRangesGatherRects for a given + // pack context. The default (no oversampling) is achieved by h_oversample=1 + // and v_oversample=1. The total number of pixels required is + // h_oversample*v_oversample larger than the default; for example, 2x2 + // oversampling requires 4x the storage of 1x1. For best results, render + // oversampled textures with bilinear filtering. Look at the readme in + // stb/tests/oversample for information about oversampled fonts + // + // To use with PackFontRangesGather etc., you must set it before calls + // call to PackFontRangesGatherRects. + + STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, // same data as above + int char_index, // character to display + float *xpos, float *ypos, // pointers to current position in screen pixel space + stbtt_aligned_quad *q, // output: quad to draw + int align_to_integer); + + STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); + STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects); + STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects); + // Calling these functions in sequence is roughly equivalent to calling + // stbtt_PackFontRanges(). If you more control over the packing of multiple + // fonts, or if you want to pack custom data into a font texture, take a look + // at the source to of stbtt_PackFontRanges() and create a custom version + // using these functions, e.g. call GatherRects multiple times, + // building up a single array of rects, then call PackRects once, + // then call RenderIntoRects repeatedly. This may result in a + // better packing than calling PackFontRanges multiple times + // (or it may not). + + // this is an opaque structure that you shouldn't mess with which holds + // all the context needed from PackBegin to PackEnd. + struct stbtt_pack_context { + void *user_allocator_context; + void *pack_info; + int width; + int height; + int stride_in_bytes; + int padding; + unsigned int h_oversample, v_oversample; + unsigned char *pixels; + void *nodes; + }; + + ////////////////////////////////////////////////////////////////////////////// + // + // FONT LOADING + // + // + + STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data); + // This function will determine the number of fonts in a font file. TrueType + // collection (.ttc) files may contain multiple fonts, while TrueType font + // (.ttf) files only contain one font. The number of fonts can be used for + // indexing with the previous function where the index is between zero and one + // less than the total fonts. If an error occurs, -1 is returned. + + STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index); + // Each .ttf/.ttc file may have more than one font. Each font has a sequential + // index number starting from 0. Call this function to get the font offset for + // a given index; it returns -1 if the index is out of range. A regular .ttf + // file will only define one font and it always be at offset 0, so it will + // return '0' for index 0, and -1 for all other indices. + + // The following structure is defined publically so you can declare one on + // the stack or as a global or etc, but you should treat it as opaque. + struct stbtt_fontinfo + { + void * userdata; + unsigned char * data; // pointer to .ttf file + int fontstart; // offset of start of font + + int numGlyphs; // number of glyphs, needed for range checking + + int loca,head,glyf,hhea,hmtx,kern,gpos; // table locations as offset from start of .ttf + int index_map; // a cmap mapping for our chosen character encoding + int indexToLocFormat; // format needed to map from glyph index to glyph + + stbtt__buf cff; // cff font data + stbtt__buf charstrings; // the charstring index + stbtt__buf gsubrs; // global charstring subroutines index + stbtt__buf subrs; // private charstring subroutines index + stbtt__buf fontdicts; // array of font dicts + stbtt__buf fdselect; // map from glyph to fontdict + }; + + STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset); + // Given an offset into the file that defines a font, this function builds + // the necessary cached info for the rest of the system. You must allocate + // the stbtt_fontinfo yourself, and stbtt_InitFont will fill it out. You don't + // need to do anything special to free it, because the contents are pure + // value data with no additional data structures. Returns 0 on failure. + + + ////////////////////////////////////////////////////////////////////////////// + // + // CHARACTER TO GLYPH-INDEX CONVERSIOn + + STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint); + // If you're going to perform multiple operations on the same character + // and you want a speed-up, call this function with the character you're + // going to process, then use glyph-based functions instead of the + // codepoint-based functions. + + + ////////////////////////////////////////////////////////////////////////////// + // + // CHARACTER PROPERTIES + // + + STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float pixels); + // computes a scale factor to produce a font whose "height" is 'pixels' tall. + // Height is measured as the distance from the highest ascender to the lowest + // descender; in other words, it's equivalent to calling stbtt_GetFontVMetrics + // and computing: + // scale = pixels / (ascent - descent) + // so if you prefer to measure height by the ascent only, use a similar calculation. + + STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels); + // computes a scale factor to produce a font whose EM size is mapped to + // 'pixels' tall. This is probably what traditional APIs compute, but + // I'm not positive. + + STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap); + // ascent is the coordinate above the baseline the font extends; descent + // is the coordinate below the baseline the font extends (i.e. it is typically negative) + // lineGap is the spacing between one row's descent and the next row's ascent... + // so you should advance the vertical position by "*ascent - *descent + *lineGap" + // these are expressed in unscaled coordinates, so you must multiply by + // the scale factor for a given size + + STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap); + // analogous to GetFontVMetrics, but returns the "typographic" values from the OS/2 + // table (specific to MS/Windows TTF files). + // + // Returns 1 on success (table present), 0 on failure. + + STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1); + // the bounding box around all possible characters + + STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing); + // leftSideBearing is the offset from the current horizontal position to the left edge of the character + // advanceWidth is the offset from the current horizontal position to the next horizontal position + // these are expressed in unscaled coordinates + + STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2); + // an additional amount to add to the 'advance' value between ch1 and ch2 + + STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1); + // Gets the bounding box of the visible part of the glyph, in unscaled coordinates + + STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing); + STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2); + STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + // as above, but takes one or more glyph indices for greater efficiency + + + ////////////////////////////////////////////////////////////////////////////// + // + // GLYPH SHAPES (you probably don't need these, but they have to go before + // the bitmaps for C declaration-order reasons) + // + +#ifndef STBTT_vmove // you can predefine these to use different values (but why?) + enum { + STBTT_vmove=1, + STBTT_vline, + STBTT_vcurve, + STBTT_vcubic + }; +#endif + +#ifndef stbtt_vertex // you can predefine this to use different values + // (we share this with other code at RAD) +#define stbtt_vertex_type short // can't use stbtt_int16 because that's not visible in the header file + typedef struct + { + stbtt_vertex_type x,y,cx,cy,cx1,cy1; + unsigned char type,padding; + } stbtt_vertex; +#endif + + STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index); + // returns non-zero if nothing is drawn for this glyph + + STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices); + STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **vertices); + // returns # of vertices and fills *vertices with the pointer to them + // these are expressed in "unscaled" coordinates + // + // The shape is a series of countours. Each one starts with + // a STBTT_moveto, then consists of a series of mixed + // STBTT_lineto and STBTT_curveto segments. A lineto + // draws a line from previous endpoint to its x,y; a curveto + // draws a quadratic bezier from previous endpoint to + // its x,y, using cx,cy as the bezier control point. + + STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *vertices); + // frees the data allocated above + + ////////////////////////////////////////////////////////////////////////////// + // + // BITMAP RENDERING + // + + STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata); + // frees the bitmap allocated below + + STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff); + // allocates a large-enough single-channel 8bpp bitmap and renders the + // specified character/glyph at the specified scale into it, with + // antialiasing. 0 is no coverage (transparent), 255 is fully covered (opaque). + // *width & *height are filled out with the width & height of the bitmap, + // which is stored left-to-right, top-to-bottom. + // + // xoff/yoff are the offset it pixel space from the glyph origin to the top-left of the bitmap + + STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff); + // the same as stbtt_GetCodepoitnBitmap, but you can specify a subpixel + // shift for the character + + STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint); + // the same as stbtt_GetCodepointBitmap, but you pass in storage for the bitmap + // in the form of 'output', with row spacing of 'out_stride' bytes. the bitmap + // is clipped to out_w/out_h bytes. Call stbtt_GetCodepointBitmapBox to get the + // width and height and positioning info for it first. + + STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint); + // same as stbtt_MakeCodepointBitmap, but you can specify a subpixel + // shift for the character + + STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint); + // same as stbtt_MakeCodepointBitmapSubpixel, but prefiltering + // is performed (see stbtt_PackSetOversampling) + + STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); + // get the bbox of the bitmap centered around the glyph origin; so the + // bitmap width is ix1-ix0, height is iy1-iy0, and location to place + // the bitmap top left is (leftSideBearing*scale,iy0). + // (Note that the bitmap uses y-increases-down, but the shape uses + // y-increases-up, so CodepointBitmapBox and CodepointBox are inverted.) + + STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + // same as stbtt_GetCodepointBitmapBox, but you can specify a subpixel + // shift for the character + + // the following functions are equivalent to the above functions, but operate + // on glyph indices instead of Unicode codepoints (for efficiency) + STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff); + STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff); + STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph); + STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph); + STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int glyph); + STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1); + STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1); + + + // @TODO: don't expose this structure + typedef struct + { + int w,h,stride; + unsigned char *pixels; + } stbtt__bitmap; + + // rasterize a shape with quadratic beziers into a bitmap + STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, // 1-channel bitmap to draw into + float flatness_in_pixels, // allowable error of curve in pixels + stbtt_vertex *vertices, // array of vertices defining shape + int num_verts, // number of vertices in above array + float scale_x, float scale_y, // scale applied to input vertices + float shift_x, float shift_y, // translation applied to input vertices + int x_off, int y_off, // another translation applied to input + int invert, // if non-zero, vertically flip shape + void *userdata); // context for to STBTT_MALLOC + + ////////////////////////////////////////////////////////////////////////////// + // + // Signed Distance Function (or Field) rendering + + STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata); + // frees the SDF bitmap allocated below + + STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); + STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff); + // These functions compute a discretized SDF field for a single character, suitable for storing + // in a single-channel texture, sampling with bilinear filtering, and testing against + // larger than some threshhold to produce scalable fonts. + // info -- the font + // scale -- controls the size of the resulting SDF bitmap, same as it would be creating a regular bitmap + // glyph/codepoint -- the character to generate the SDF for + // padding -- extra "pixels" around the character which are filled with the distance to the character (not 0), + // which allows effects like bit outlines + // onedge_value -- value 0-255 to test the SDF against to reconstruct the character (i.e. the isocontour of the character) + // pixel_dist_scale -- what value the SDF should increase by when moving one SDF "pixel" away from the edge (on the 0..255 scale) + // if positive, > onedge_value is inside; if negative, < onedge_value is inside + // width,height -- output height & width of the SDF bitmap (including padding) + // xoff,yoff -- output origin of the character + // return value -- a 2D array of bytes 0..255, width*height in size + // + // pixel_dist_scale & onedge_value are a scale & bias that allows you to make + // optimal use of the limited 0..255 for your application, trading off precision + // and special effects. SDF values outside the range 0..255 are clamped to 0..255. + // + // Example: + // scale = stbtt_ScaleForPixelHeight(22) + // padding = 5 + // onedge_value = 180 + // pixel_dist_scale = 180/5.0 = 36.0 + // + // This will create an SDF bitmap in which the character is about 22 pixels + // high but the whole bitmap is about 22+5+5=32 pixels high. To produce a filled + // shape, sample the SDF at each pixel and fill the pixel if the SDF value + // is greater than or equal to 180/255. (You'll actually want to antialias, + // which is beyond the scope of this example.) Additionally, you can compute + // offset outlines (e.g. to stroke the character border inside & outside, + // or only outside). For example, to fill outside the character up to 3 SDF + // pixels, you would compare against (180-36.0*3)/255 = 72/255. The above + // choice of variables maps a range from 5 pixels outside the shape to + // 2 pixels inside the shape to 0..255; this is intended primarily for apply + // outside effects only (the interior range is needed to allow proper + // antialiasing of the font at *smaller* sizes) + // + // The function computes the SDF analytically at each SDF pixel, not by e.g. + // building a higher-res bitmap and approximating it. In theory the quality + // should be as high as possible for an SDF of this size & representation, but + // unclear if this is true in practice (perhaps building a higher-res bitmap + // and computing from that can allow drop-out prevention). + // + // The algorithm has not been optimized at all, so expect it to be slow + // if computing lots of characters or very large sizes. + + + + ////////////////////////////////////////////////////////////////////////////// + // + // Finding the right font... + // + // You should really just solve this offline, keep your own tables + // of what font is what, and don't try to get it out of the .ttf file. + // That's because getting it out of the .ttf file is really hard, because + // the names in the file can appear in many possible encodings, in many + // possible languages, and e.g. if you need a case-insensitive comparison, + // the details of that depend on the encoding & language in a complex way + // (actually underspecified in truetype, but also gigantic). + // + // But you can use the provided functions in two possible ways: + // stbtt_FindMatchingFont() will use *case-sensitive* comparisons on + // unicode-encoded names to try to find the font you want; + // you can run this before calling stbtt_InitFont() + // + // stbtt_GetFontNameString() lets you get any of the various strings + // from the file yourself and do your own comparisons on them. + // You have to have called stbtt_InitFont() first. + + + STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags); + // returns the offset (not index) of the font that matches, or -1 if none + // if you use STBTT_MACSTYLE_DONTCARE, use a font name like "Arial Bold". + // if you use any other flag, use a font name like "Arial"; this checks + // the 'macStyle' header field; i don't know if fonts set this consistently +#define STBTT_MACSTYLE_DONTCARE 0 +#define STBTT_MACSTYLE_BOLD 1 +#define STBTT_MACSTYLE_ITALIC 2 +#define STBTT_MACSTYLE_UNDERSCORE 4 +#define STBTT_MACSTYLE_NONE 8 // <= not same as 0, this makes us check the bitfield is 0 + + STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2); + // returns 1/0 whether the first string interpreted as utf8 is identical to + // the second string interpreted as big-endian utf16... useful for strings from next func + + STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID); + // returns the string (which may be big-endian double byte, e.g. for unicode) + // and puts the length in bytes in *length. + // + // some of the values for the IDs are below; for more see the truetype spec: + // http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6name.html + // http://www.microsoft.com/typography/otspec/name.htm + + enum { // platformID + STBTT_PLATFORM_ID_UNICODE =0, + STBTT_PLATFORM_ID_MAC =1, + STBTT_PLATFORM_ID_ISO =2, + STBTT_PLATFORM_ID_MICROSOFT =3 + }; + + enum { // encodingID for STBTT_PLATFORM_ID_UNICODE + STBTT_UNICODE_EID_UNICODE_1_0 =0, + STBTT_UNICODE_EID_UNICODE_1_1 =1, + STBTT_UNICODE_EID_ISO_10646 =2, + STBTT_UNICODE_EID_UNICODE_2_0_BMP=3, + STBTT_UNICODE_EID_UNICODE_2_0_FULL=4 + }; + + enum { // encodingID for STBTT_PLATFORM_ID_MICROSOFT + STBTT_MS_EID_SYMBOL =0, + STBTT_MS_EID_UNICODE_BMP =1, + STBTT_MS_EID_SHIFTJIS =2, + STBTT_MS_EID_UNICODE_FULL =10 + }; + + enum { // encodingID for STBTT_PLATFORM_ID_MAC; same as Script Manager codes + STBTT_MAC_EID_ROMAN =0, STBTT_MAC_EID_ARABIC =4, + STBTT_MAC_EID_JAPANESE =1, STBTT_MAC_EID_HEBREW =5, + STBTT_MAC_EID_CHINESE_TRAD =2, STBTT_MAC_EID_GREEK =6, + STBTT_MAC_EID_KOREAN =3, STBTT_MAC_EID_RUSSIAN =7 + }; + + enum { // languageID for STBTT_PLATFORM_ID_MICROSOFT; same as LCID... + // problematic because there are e.g. 16 english LCIDs and 16 arabic LCIDs + STBTT_MS_LANG_ENGLISH =0x0409, STBTT_MS_LANG_ITALIAN =0x0410, + STBTT_MS_LANG_CHINESE =0x0804, STBTT_MS_LANG_JAPANESE =0x0411, + STBTT_MS_LANG_DUTCH =0x0413, STBTT_MS_LANG_KOREAN =0x0412, + STBTT_MS_LANG_FRENCH =0x040c, STBTT_MS_LANG_RUSSIAN =0x0419, + STBTT_MS_LANG_GERMAN =0x0407, STBTT_MS_LANG_SPANISH =0x0409, + STBTT_MS_LANG_HEBREW =0x040d, STBTT_MS_LANG_SWEDISH =0x041D + }; + + enum { // languageID for STBTT_PLATFORM_ID_MAC + STBTT_MAC_LANG_ENGLISH =0 , STBTT_MAC_LANG_JAPANESE =11, + STBTT_MAC_LANG_ARABIC =12, STBTT_MAC_LANG_KOREAN =23, + STBTT_MAC_LANG_DUTCH =4 , STBTT_MAC_LANG_RUSSIAN =32, + STBTT_MAC_LANG_FRENCH =1 , STBTT_MAC_LANG_SPANISH =6 , + STBTT_MAC_LANG_GERMAN =2 , STBTT_MAC_LANG_SWEDISH =5 , + STBTT_MAC_LANG_HEBREW =10, STBTT_MAC_LANG_CHINESE_SIMPLIFIED =33, + STBTT_MAC_LANG_ITALIAN =3 , STBTT_MAC_LANG_CHINESE_TRAD =19 + }; + +#ifdef __cplusplus +} +#endif + +#endif // __STB_INCLUDE_STB_TRUETYPE_H__ + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +//// +//// IMPLEMENTATION +//// +//// + +#ifdef STB_TRUETYPE_IMPLEMENTATION + +#ifndef STBTT_MAX_OVERSAMPLE +#define STBTT_MAX_OVERSAMPLE 8 +#endif + +#if STBTT_MAX_OVERSAMPLE > 255 +#error "STBTT_MAX_OVERSAMPLE cannot be > 255" +#endif + +typedef int stbtt__test_oversample_pow2[(STBTT_MAX_OVERSAMPLE & (STBTT_MAX_OVERSAMPLE-1)) == 0 ? 1 : -1]; + +#ifndef STBTT_RASTERIZER_VERSION +#define STBTT_RASTERIZER_VERSION 2 +#endif + +#ifdef _MSC_VER +#define STBTT__NOTUSED(v) (void)(v) +#else +#define STBTT__NOTUSED(v) (void)sizeof(v) +#endif + +////////////////////////////////////////////////////////////////////////// +// +// stbtt__buf helpers to parse data from file +// + +static stbtt_uint8 stbtt__buf_get8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor++]; +} + +static stbtt_uint8 stbtt__buf_peek8(stbtt__buf *b) +{ + if (b->cursor >= b->size) + return 0; + return b->data[b->cursor]; +} + +static void stbtt__buf_seek(stbtt__buf *b, int o) +{ + STBTT_assert(!(o > b->size || o < 0)); + b->cursor = (o > b->size || o < 0) ? b->size : o; +} + +static void stbtt__buf_skip(stbtt__buf *b, int o) +{ + stbtt__buf_seek(b, b->cursor + o); +} + +static stbtt_uint32 stbtt__buf_get(stbtt__buf *b, int n) +{ + stbtt_uint32 v = 0; + int i; + STBTT_assert(n >= 1 && n <= 4); + for (i = 0; i < n; i++) + v = (v << 8) | stbtt__buf_get8(b); + return v; +} + +static stbtt__buf stbtt__new_buf(const void *p, size_t size) +{ + stbtt__buf r; + STBTT_assert(size < 0x40000000); + r.data = (stbtt_uint8*) p; + r.size = (int) size; + r.cursor = 0; + return r; +} + +#define stbtt__buf_get16(b) stbtt__buf_get((b), 2) +#define stbtt__buf_get32(b) stbtt__buf_get((b), 4) + +static stbtt__buf stbtt__buf_range(const stbtt__buf *b, int o, int s) +{ + stbtt__buf r = stbtt__new_buf(NULL, 0); + if (o < 0 || s < 0 || o > b->size || s > b->size - o) return r; + r.data = b->data + o; + r.size = s; + return r; +} + +static stbtt__buf stbtt__cff_get_index(stbtt__buf *b) +{ + int count, start, offsize; + start = b->cursor; + count = stbtt__buf_get16(b); + if (count) { + offsize = stbtt__buf_get8(b); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(b, offsize * count); + stbtt__buf_skip(b, stbtt__buf_get(b, offsize) - 1); + } + return stbtt__buf_range(b, start, b->cursor - start); +} + +static stbtt_uint32 stbtt__cff_int(stbtt__buf *b) +{ + int b0 = stbtt__buf_get8(b); + if (b0 >= 32 && b0 <= 246) return b0 - 139; + else if (b0 >= 247 && b0 <= 250) return (b0 - 247)*256 + stbtt__buf_get8(b) + 108; + else if (b0 >= 251 && b0 <= 254) return -(b0 - 251)*256 - stbtt__buf_get8(b) - 108; + else if (b0 == 28) return stbtt__buf_get16(b); + else if (b0 == 29) return stbtt__buf_get32(b); + STBTT_assert(0); + return 0; +} + +static void stbtt__cff_skip_operand(stbtt__buf *b) { + int v, b0 = stbtt__buf_peek8(b); + STBTT_assert(b0 >= 28); + if (b0 == 30) { + stbtt__buf_skip(b, 1); + while (b->cursor < b->size) { + v = stbtt__buf_get8(b); + if ((v & 0xF) == 0xF || (v >> 4) == 0xF) + break; + } + } else { + stbtt__cff_int(b); + } +} + +static stbtt__buf stbtt__dict_get(stbtt__buf *b, int key) +{ + stbtt__buf_seek(b, 0); + while (b->cursor < b->size) { + int start = b->cursor, end, op; + while (stbtt__buf_peek8(b) >= 28) + stbtt__cff_skip_operand(b); + end = b->cursor; + op = stbtt__buf_get8(b); + if (op == 12) op = stbtt__buf_get8(b) | 0x100; + if (op == key) return stbtt__buf_range(b, start, end-start); + } + return stbtt__buf_range(b, 0, 0); +} + +static void stbtt__dict_get_ints(stbtt__buf *b, int key, int outcount, stbtt_uint32 *out) +{ + int i; + stbtt__buf operands = stbtt__dict_get(b, key); + for (i = 0; i < outcount && operands.cursor < operands.size; i++) + out[i] = stbtt__cff_int(&operands); +} + +static int stbtt__cff_index_count(stbtt__buf *b) +{ + stbtt__buf_seek(b, 0); + return stbtt__buf_get16(b); +} + +static stbtt__buf stbtt__cff_index_get(stbtt__buf b, int i) +{ + int count, offsize, start, end; + stbtt__buf_seek(&b, 0); + count = stbtt__buf_get16(&b); + offsize = stbtt__buf_get8(&b); + STBTT_assert(i >= 0 && i < count); + STBTT_assert(offsize >= 1 && offsize <= 4); + stbtt__buf_skip(&b, i*offsize); + start = stbtt__buf_get(&b, offsize); + end = stbtt__buf_get(&b, offsize); + return stbtt__buf_range(&b, 2+(count+1)*offsize+start, end - start); +} + +////////////////////////////////////////////////////////////////////////// +// +// accessors to parse data from file +// + +// on platforms that don't allow misaligned reads, if we want to allow +// truetype fonts that aren't padded to alignment, define ALLOW_UNALIGNED_TRUETYPE + +#define ttBYTE(p) (* (stbtt_uint8 *) (p)) +#define ttCHAR(p) (* (stbtt_int8 *) (p)) +#define ttFixed(p) ttLONG(p) + +static stbtt_uint16 ttUSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_int16 ttSHORT(stbtt_uint8 *p) { return p[0]*256 + p[1]; } +static stbtt_uint32 ttULONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } +static stbtt_int32 ttLONG(stbtt_uint8 *p) { return (p[0]<<24) + (p[1]<<16) + (p[2]<<8) + p[3]; } + +#define stbtt_tag4(p,c0,c1,c2,c3) ((p)[0] == (c0) && (p)[1] == (c1) && (p)[2] == (c2) && (p)[3] == (c3)) +#define stbtt_tag(p,str) stbtt_tag4(p,str[0],str[1],str[2],str[3]) + +static int stbtt__isfont(stbtt_uint8 *font) +{ + // check the version number + if (stbtt_tag4(font, '1',0,0,0)) return 1; // TrueType 1 + if (stbtt_tag(font, "typ1")) return 1; // TrueType with type 1 font -- we don't support this! + if (stbtt_tag(font, "OTTO")) return 1; // OpenType with CFF + if (stbtt_tag4(font, 0,1,0,0)) return 1; // OpenType 1.0 + if (stbtt_tag(font, "true")) return 1; // Apple specification for TrueType fonts + return 0; +} + +// @OPTIMIZE: binary search +static stbtt_uint32 stbtt__find_table(stbtt_uint8 *data, stbtt_uint32 fontstart, const char *tag) +{ + stbtt_int32 num_tables = ttUSHORT(data+fontstart+4); + stbtt_uint32 tabledir = fontstart + 12; + stbtt_int32 i; + for (i=0; i < num_tables; ++i) { + stbtt_uint32 loc = tabledir + 16*i; + if (stbtt_tag(data+loc+0, tag)) + return ttULONG(data+loc+8); + } + return 0; +} + +static int stbtt_GetFontOffsetForIndex_internal(unsigned char *font_collection, int index) +{ + // if it's just a font, there's only one valid index + if (stbtt__isfont(font_collection)) + return index == 0 ? 0 : -1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + stbtt_int32 n = ttLONG(font_collection+8); + if (index >= n) + return -1; + return ttULONG(font_collection+12+index*4); + } + } + return -1; +} + +static int stbtt_GetNumberOfFonts_internal(unsigned char *font_collection) +{ + // if it's just a font, there's only one valid font + if (stbtt__isfont(font_collection)) + return 1; + + // check if it's a TTC + if (stbtt_tag(font_collection, "ttcf")) { + // version 1? + if (ttULONG(font_collection+4) == 0x00010000 || ttULONG(font_collection+4) == 0x00020000) { + return ttLONG(font_collection+8); + } + } + return 0; +} + +static stbtt__buf stbtt__get_subrs(stbtt__buf cff, stbtt__buf fontdict) +{ + stbtt_uint32 subrsoff = 0, private_loc[2] = { 0, 0 }; + stbtt__buf pdict; + stbtt__dict_get_ints(&fontdict, 18, 2, private_loc); + if (!private_loc[1] || !private_loc[0]) return stbtt__new_buf(NULL, 0); + pdict = stbtt__buf_range(&cff, private_loc[1], private_loc[0]); + stbtt__dict_get_ints(&pdict, 19, 1, &subrsoff); + if (!subrsoff) return stbtt__new_buf(NULL, 0); + stbtt__buf_seek(&cff, private_loc[1]+subrsoff); + return stbtt__cff_get_index(&cff); +} + +static int stbtt_InitFont_internal(stbtt_fontinfo *info, unsigned char *data, int fontstart) +{ + stbtt_uint32 cmap, t; + stbtt_int32 i,numTables; + + info->data = data; + info->fontstart = fontstart; + info->cff = stbtt__new_buf(NULL, 0); + + cmap = stbtt__find_table(data, fontstart, "cmap"); // required + info->loca = stbtt__find_table(data, fontstart, "loca"); // required + info->head = stbtt__find_table(data, fontstart, "head"); // required + info->glyf = stbtt__find_table(data, fontstart, "glyf"); // required + info->hhea = stbtt__find_table(data, fontstart, "hhea"); // required + info->hmtx = stbtt__find_table(data, fontstart, "hmtx"); // required + info->kern = stbtt__find_table(data, fontstart, "kern"); // not required + info->gpos = stbtt__find_table(data, fontstart, "GPOS"); // not required + + if (!cmap || !info->head || !info->hhea || !info->hmtx) + return 0; + if (info->glyf) { + // required for truetype + if (!info->loca) return 0; + } else { + // initialization for CFF / Type2 fonts (OTF) + stbtt__buf b, topdict, topdictidx; + stbtt_uint32 cstype = 2, charstrings = 0, fdarrayoff = 0, fdselectoff = 0; + stbtt_uint32 cff; + + cff = stbtt__find_table(data, fontstart, "CFF "); + if (!cff) return 0; + + info->fontdicts = stbtt__new_buf(NULL, 0); + info->fdselect = stbtt__new_buf(NULL, 0); + + // @TODO this should use size from table (not 512MB) + info->cff = stbtt__new_buf(data+cff, 512*1024*1024); + b = info->cff; + + // read the header + stbtt__buf_skip(&b, 2); + stbtt__buf_seek(&b, stbtt__buf_get8(&b)); // hdrsize + + // @TODO the name INDEX could list multiple fonts, + // but we just use the first one. + stbtt__cff_get_index(&b); // name INDEX + topdictidx = stbtt__cff_get_index(&b); + topdict = stbtt__cff_index_get(topdictidx, 0); + stbtt__cff_get_index(&b); // string INDEX + info->gsubrs = stbtt__cff_get_index(&b); + + stbtt__dict_get_ints(&topdict, 17, 1, &charstrings); + stbtt__dict_get_ints(&topdict, 0x100 | 6, 1, &cstype); + stbtt__dict_get_ints(&topdict, 0x100 | 36, 1, &fdarrayoff); + stbtt__dict_get_ints(&topdict, 0x100 | 37, 1, &fdselectoff); + info->subrs = stbtt__get_subrs(b, topdict); + + // we only support Type 2 charstrings + if (cstype != 2) return 0; + if (charstrings == 0) return 0; + + if (fdarrayoff) { + // looks like a CID font + if (!fdselectoff) return 0; + stbtt__buf_seek(&b, fdarrayoff); + info->fontdicts = stbtt__cff_get_index(&b); + info->fdselect = stbtt__buf_range(&b, fdselectoff, b.size-fdselectoff); + } + + stbtt__buf_seek(&b, charstrings); + info->charstrings = stbtt__cff_get_index(&b); + } + + t = stbtt__find_table(data, fontstart, "maxp"); + if (t) + info->numGlyphs = ttUSHORT(data+t+4); + else + info->numGlyphs = 0xffff; + + // find a cmap encoding table we understand *now* to avoid searching + // later. (todo: could make this installable) + // the same regardless of glyph. + numTables = ttUSHORT(data + cmap + 2); + info->index_map = 0; + for (i=0; i < numTables; ++i) { + stbtt_uint32 encoding_record = cmap + 4 + 8 * i; + // find an encoding we understand: + switch(ttUSHORT(data+encoding_record)) { + case STBTT_PLATFORM_ID_MICROSOFT: + switch (ttUSHORT(data+encoding_record+2)) { + case STBTT_MS_EID_UNICODE_BMP: + case STBTT_MS_EID_UNICODE_FULL: + // MS/Unicode + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + break; + case STBTT_PLATFORM_ID_UNICODE: + // Mac/iOS has these + // all the encodingIDs are unicode, so we don't bother to check it + info->index_map = cmap + ttULONG(data+encoding_record+4); + break; + } + } + if (info->index_map == 0) + return 0; + + info->indexToLocFormat = ttUSHORT(data+info->head + 50); + return 1; +} + +STBTT_DEF int stbtt_FindGlyphIndex(const stbtt_fontinfo *info, int unicode_codepoint) +{ + stbtt_uint8 *data = info->data; + stbtt_uint32 index_map = info->index_map; + + stbtt_uint16 format = ttUSHORT(data + index_map + 0); + if (format == 0) { // apple byte encoding + stbtt_int32 bytes = ttUSHORT(data + index_map + 2); + if (unicode_codepoint < bytes-6) + return ttBYTE(data + index_map + 6 + unicode_codepoint); + return 0; + } else if (format == 6) { + stbtt_uint32 first = ttUSHORT(data + index_map + 6); + stbtt_uint32 count = ttUSHORT(data + index_map + 8); + if ((stbtt_uint32) unicode_codepoint >= first && (stbtt_uint32) unicode_codepoint < first+count) + return ttUSHORT(data + index_map + 10 + (unicode_codepoint - first)*2); + return 0; + } else if (format == 2) { + STBTT_assert(0); // @TODO: high-byte mapping for japanese/chinese/korean + return 0; + } else if (format == 4) { // standard mapping for windows fonts: binary search collection of ranges + stbtt_uint16 segcount = ttUSHORT(data+index_map+6) >> 1; + stbtt_uint16 searchRange = ttUSHORT(data+index_map+8) >> 1; + stbtt_uint16 entrySelector = ttUSHORT(data+index_map+10); + stbtt_uint16 rangeShift = ttUSHORT(data+index_map+12) >> 1; + + // do a binary search of the segments + stbtt_uint32 endCount = index_map + 14; + stbtt_uint32 search = endCount; + + if (unicode_codepoint > 0xffff) + return 0; + + // they lie from endCount .. endCount + segCount + // but searchRange is the nearest power of two, so... + if (unicode_codepoint >= ttUSHORT(data + search + rangeShift*2)) + search += rangeShift*2; + + // now decrement to bias correctly to find smallest + search -= 2; + while (entrySelector) { + stbtt_uint16 end; + searchRange >>= 1; + end = ttUSHORT(data + search + searchRange*2); + if (unicode_codepoint > end) + search += searchRange*2; + --entrySelector; + } + search += 2; + + { + stbtt_uint16 offset, start; + stbtt_uint16 item = (stbtt_uint16) ((search - endCount) >> 1); + + STBTT_assert(unicode_codepoint <= ttUSHORT(data + endCount + 2*item)); + start = ttUSHORT(data + index_map + 14 + segcount*2 + 2 + 2*item); + if (unicode_codepoint < start) + return 0; + + offset = ttUSHORT(data + index_map + 14 + segcount*6 + 2 + 2*item); + if (offset == 0) + return (stbtt_uint16) (unicode_codepoint + ttSHORT(data + index_map + 14 + segcount*4 + 2 + 2*item)); + + return ttUSHORT(data + offset + (unicode_codepoint-start)*2 + index_map + 14 + segcount*6 + 2 + 2*item); + } + } else if (format == 12 || format == 13) { + stbtt_uint32 ngroups = ttULONG(data+index_map+12); + stbtt_int32 low,high; + low = 0; high = (stbtt_int32)ngroups; + // Binary search the right group. + while (low < high) { + stbtt_int32 mid = low + ((high-low) >> 1); // rounds down, so low <= mid < high + stbtt_uint32 start_char = ttULONG(data+index_map+16+mid*12); + stbtt_uint32 end_char = ttULONG(data+index_map+16+mid*12+4); + if ((stbtt_uint32) unicode_codepoint < start_char) + high = mid; + else if ((stbtt_uint32) unicode_codepoint > end_char) + low = mid+1; + else { + stbtt_uint32 start_glyph = ttULONG(data+index_map+16+mid*12+8); + if (format == 12) + return start_glyph + unicode_codepoint-start_char; + else // format == 13 + return start_glyph; + } + } + return 0; // not found + } + // @TODO + STBTT_assert(0); + return 0; +} + +STBTT_DEF int stbtt_GetCodepointShape(const stbtt_fontinfo *info, int unicode_codepoint, stbtt_vertex **vertices) +{ + return stbtt_GetGlyphShape(info, stbtt_FindGlyphIndex(info, unicode_codepoint), vertices); +} + +static void stbtt_setvertex(stbtt_vertex *v, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy) +{ + v->type = type; + v->x = (stbtt_int16) x; + v->y = (stbtt_int16) y; + v->cx = (stbtt_int16) cx; + v->cy = (stbtt_int16) cy; +} + +static int stbtt__GetGlyfOffset(const stbtt_fontinfo *info, int glyph_index) +{ + int g1,g2; + + STBTT_assert(!info->cff.size); + + if (glyph_index >= info->numGlyphs) return -1; // glyph index out of range + if (info->indexToLocFormat >= 2) return -1; // unknown index->glyph map format + + if (info->indexToLocFormat == 0) { + g1 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2) * 2; + g2 = info->glyf + ttUSHORT(info->data + info->loca + glyph_index * 2 + 2) * 2; + } else { + g1 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4); + g2 = info->glyf + ttULONG (info->data + info->loca + glyph_index * 4 + 4); + } + + return g1==g2 ? -1 : g1; // if length is 0, return -1 +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1); + +STBTT_DEF int stbtt_GetGlyphBox(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + if (info->cff.size) { + stbtt__GetGlyphInfoT2(info, glyph_index, x0, y0, x1, y1); + } else { + int g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 0; + + if (x0) *x0 = ttSHORT(info->data + g + 2); + if (y0) *y0 = ttSHORT(info->data + g + 4); + if (x1) *x1 = ttSHORT(info->data + g + 6); + if (y1) *y1 = ttSHORT(info->data + g + 8); + } + return 1; +} + +STBTT_DEF int stbtt_GetCodepointBox(const stbtt_fontinfo *info, int codepoint, int *x0, int *y0, int *x1, int *y1) +{ + return stbtt_GetGlyphBox(info, stbtt_FindGlyphIndex(info,codepoint), x0,y0,x1,y1); +} + +STBTT_DEF int stbtt_IsGlyphEmpty(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt_int16 numberOfContours; + int g; + if (info->cff.size) + return stbtt__GetGlyphInfoT2(info, glyph_index, NULL, NULL, NULL, NULL) == 0; + g = stbtt__GetGlyfOffset(info, glyph_index); + if (g < 0) return 1; + numberOfContours = ttSHORT(info->data + g); + return numberOfContours == 0; +} + +static int stbtt__close_shape(stbtt_vertex *vertices, int num_vertices, int was_off, int start_off, + stbtt_int32 sx, stbtt_int32 sy, stbtt_int32 scx, stbtt_int32 scy, stbtt_int32 cx, stbtt_int32 cy) +{ + if (start_off) { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+scx)>>1, (cy+scy)>>1, cx,cy); + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, sx,sy,scx,scy); + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve,sx,sy,cx,cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline,sx,sy,0,0); + } + return num_vertices; +} + +static int stbtt__GetGlyphShapeTT(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + stbtt_int16 numberOfContours; + stbtt_uint8 *endPtsOfContours; + stbtt_uint8 *data = info->data; + stbtt_vertex *vertices=0; + int num_vertices=0; + int g = stbtt__GetGlyfOffset(info, glyph_index); + + *pvertices = NULL; + + if (g < 0) return 0; + + numberOfContours = ttSHORT(data + g); + + if (numberOfContours > 0) { + stbtt_uint8 flags=0,flagcount; + stbtt_int32 ins, i,j=0,m,n, next_move, was_off=0, off, start_off=0; + stbtt_int32 x,y,cx,cy,sx,sy, scx,scy; + stbtt_uint8 *points; + endPtsOfContours = (data + g + 10); + ins = ttUSHORT(data + g + 10 + numberOfContours * 2); + points = data + g + 10 + numberOfContours * 2 + 2 + ins; + + n = 1+ttUSHORT(endPtsOfContours + numberOfContours*2-2); + + m = n + 2*numberOfContours; // a loose bound on how many vertices we might need + vertices = (stbtt_vertex *) STBTT_malloc(m * sizeof(vertices[0]), info->userdata); + if (vertices == 0) + return 0; + + next_move = 0; + flagcount=0; + + // in first pass, we load uninterpreted data into the allocated array + // above, shifted to the end of the array so we won't overwrite it when + // we create our final data starting from the front + + off = m - n; // starting offset for uninterpreted data, regardless of how m ends up being calculated + + // first load flags + + for (i=0; i < n; ++i) { + if (flagcount == 0) { + flags = *points++; + if (flags & 8) + flagcount = *points++; + } else + --flagcount; + vertices[off+i].type = flags; + } + + // now load x coordinates + x=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 2) { + stbtt_int16 dx = *points++; + x += (flags & 16) ? dx : -dx; // ??? + } else { + if (!(flags & 16)) { + x = x + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].x = (stbtt_int16) x; + } + + // now load y coordinates + y=0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + if (flags & 4) { + stbtt_int16 dy = *points++; + y += (flags & 32) ? dy : -dy; // ??? + } else { + if (!(flags & 32)) { + y = y + (stbtt_int16) (points[0]*256 + points[1]); + points += 2; + } + } + vertices[off+i].y = (stbtt_int16) y; + } + + // now convert them to our format + num_vertices=0; + sx = sy = cx = cy = scx = scy = 0; + for (i=0; i < n; ++i) { + flags = vertices[off+i].type; + x = (stbtt_int16) vertices[off+i].x; + y = (stbtt_int16) vertices[off+i].y; + + if (next_move == i) { + if (i != 0) + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + + // now start the new one + start_off = !(flags & 1); + if (start_off) { + // if we start off with an off-curve point, then when we need to find a point on the curve + // where we can start, and we need to save some state for when we wraparound. + scx = x; + scy = y; + if (!(vertices[off+i+1].type & 1)) { + // next point is also a curve point, so interpolate an on-point curve + sx = (x + (stbtt_int32) vertices[off+i+1].x) >> 1; + sy = (y + (stbtt_int32) vertices[off+i+1].y) >> 1; + } else { + // otherwise just use the next point as our start point + sx = (stbtt_int32) vertices[off+i+1].x; + sy = (stbtt_int32) vertices[off+i+1].y; + ++i; // we're using point i+1 as the starting point, so skip it + } + } else { + sx = x; + sy = y; + } + stbtt_setvertex(&vertices[num_vertices++], STBTT_vmove,sx,sy,0,0); + was_off = 0; + next_move = 1 + ttUSHORT(endPtsOfContours+j*2); + ++j; + } else { + if (!(flags & 1)) { // if it's a curve + if (was_off) // two off-curve control points in a row means interpolate an on-curve midpoint + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, (cx+x)>>1, (cy+y)>>1, cx, cy); + cx = x; + cy = y; + was_off = 1; + } else { + if (was_off) + stbtt_setvertex(&vertices[num_vertices++], STBTT_vcurve, x,y, cx, cy); + else + stbtt_setvertex(&vertices[num_vertices++], STBTT_vline, x,y,0,0); + was_off = 0; + } + } + } + num_vertices = stbtt__close_shape(vertices, num_vertices, was_off, start_off, sx,sy,scx,scy,cx,cy); + } else if (numberOfContours == -1) { + // Compound shapes. + int more = 1; + stbtt_uint8 *comp = data + g + 10; + num_vertices = 0; + vertices = 0; + while (more) { + stbtt_uint16 flags, gidx; + int comp_num_verts = 0, i; + stbtt_vertex *comp_verts = 0, *tmp = 0; + float mtx[6] = {1,0,0,1,0,0}, m, n; + + flags = ttSHORT(comp); comp+=2; + gidx = ttSHORT(comp); comp+=2; + + if (flags & 2) { // XY values + if (flags & 1) { // shorts + mtx[4] = ttSHORT(comp); comp+=2; + mtx[5] = ttSHORT(comp); comp+=2; + } else { + mtx[4] = ttCHAR(comp); comp+=1; + mtx[5] = ttCHAR(comp); comp+=1; + } + } + else { + // @TODO handle matching point + STBTT_assert(0); + } + if (flags & (1<<3)) { // WE_HAVE_A_SCALE + mtx[0] = mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + } else if (flags & (1<<6)) { // WE_HAVE_AN_X_AND_YSCALE + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = mtx[2] = 0; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } else if (flags & (1<<7)) { // WE_HAVE_A_TWO_BY_TWO + mtx[0] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[1] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[2] = ttSHORT(comp)/16384.0f; comp+=2; + mtx[3] = ttSHORT(comp)/16384.0f; comp+=2; + } + + // Find transformation scales. + m = (float) STBTT_sqrt(mtx[0]*mtx[0] + mtx[1]*mtx[1]); + n = (float) STBTT_sqrt(mtx[2]*mtx[2] + mtx[3]*mtx[3]); + + // Get indexed glyph. + comp_num_verts = stbtt_GetGlyphShape(info, gidx, &comp_verts); + if (comp_num_verts > 0) { + // Transform vertices. + for (i = 0; i < comp_num_verts; ++i) { + stbtt_vertex* v = &comp_verts[i]; + stbtt_vertex_type x,y; + x=v->x; y=v->y; + v->x = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->y = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + x=v->cx; y=v->cy; + v->cx = (stbtt_vertex_type)(m * (mtx[0]*x + mtx[2]*y + mtx[4])); + v->cy = (stbtt_vertex_type)(n * (mtx[1]*x + mtx[3]*y + mtx[5])); + } + // Append vertices. + tmp = (stbtt_vertex*)STBTT_malloc((num_vertices+comp_num_verts)*sizeof(stbtt_vertex), info->userdata); + if (!tmp) { + if (vertices) STBTT_free(vertices, info->userdata); + if (comp_verts) STBTT_free(comp_verts, info->userdata); + return 0; + } + if (num_vertices > 0) STBTT_memcpy(tmp, vertices, num_vertices*sizeof(stbtt_vertex)); + STBTT_memcpy(tmp+num_vertices, comp_verts, comp_num_verts*sizeof(stbtt_vertex)); + if (vertices) STBTT_free(vertices, info->userdata); + vertices = tmp; + STBTT_free(comp_verts, info->userdata); + num_vertices += comp_num_verts; + } + // More components ? + more = flags & (1<<5); + } + } else if (numberOfContours < 0) { + // @TODO other compound variations? + STBTT_assert(0); + } else { + // numberOfCounters == 0, do nothing + } + + *pvertices = vertices; + return num_vertices; +} + +typedef struct +{ + int bounds; + int started; + float first_x, first_y; + float x, y; + stbtt_int32 min_x, max_x, min_y, max_y; + + stbtt_vertex *pvertices; + int num_vertices; +} stbtt__csctx; + +#define STBTT__CSCTX_INIT(bounds) {bounds,0, 0,0, 0,0, 0,0,0,0, NULL, 0} + +static void stbtt__track_vertex(stbtt__csctx *c, stbtt_int32 x, stbtt_int32 y) +{ + if (x > c->max_x || !c->started) c->max_x = x; + if (y > c->max_y || !c->started) c->max_y = y; + if (x < c->min_x || !c->started) c->min_x = x; + if (y < c->min_y || !c->started) c->min_y = y; + c->started = 1; +} + +static void stbtt__csctx_v(stbtt__csctx *c, stbtt_uint8 type, stbtt_int32 x, stbtt_int32 y, stbtt_int32 cx, stbtt_int32 cy, stbtt_int32 cx1, stbtt_int32 cy1) +{ + if (c->bounds) { + stbtt__track_vertex(c, x, y); + if (type == STBTT_vcubic) { + stbtt__track_vertex(c, cx, cy); + stbtt__track_vertex(c, cx1, cy1); + } + } else { + stbtt_setvertex(&c->pvertices[c->num_vertices], type, x, y, cx, cy); + c->pvertices[c->num_vertices].cx1 = (stbtt_int16) cx1; + c->pvertices[c->num_vertices].cy1 = (stbtt_int16) cy1; + } + c->num_vertices++; +} + +static void stbtt__csctx_close_shape(stbtt__csctx *ctx) +{ + if (ctx->first_x != ctx->x || ctx->first_y != ctx->y) + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->first_x, (int)ctx->first_y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rmove_to(stbtt__csctx *ctx, float dx, float dy) +{ + stbtt__csctx_close_shape(ctx); + ctx->first_x = ctx->x = ctx->x + dx; + ctx->first_y = ctx->y = ctx->y + dy; + stbtt__csctx_v(ctx, STBTT_vmove, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rline_to(stbtt__csctx *ctx, float dx, float dy) +{ + ctx->x += dx; + ctx->y += dy; + stbtt__csctx_v(ctx, STBTT_vline, (int)ctx->x, (int)ctx->y, 0, 0, 0, 0); +} + +static void stbtt__csctx_rccurve_to(stbtt__csctx *ctx, float dx1, float dy1, float dx2, float dy2, float dx3, float dy3) +{ + float cx1 = ctx->x + dx1; + float cy1 = ctx->y + dy1; + float cx2 = cx1 + dx2; + float cy2 = cy1 + dy2; + ctx->x = cx2 + dx3; + ctx->y = cy2 + dy3; + stbtt__csctx_v(ctx, STBTT_vcubic, (int)ctx->x, (int)ctx->y, (int)cx1, (int)cy1, (int)cx2, (int)cy2); +} + +static stbtt__buf stbtt__get_subr(stbtt__buf idx, int n) +{ + int count = stbtt__cff_index_count(&idx); + int bias = 107; + if (count >= 33900) + bias = 32768; + else if (count >= 1240) + bias = 1131; + n += bias; + if (n < 0 || n >= count) + return stbtt__new_buf(NULL, 0); + return stbtt__cff_index_get(idx, n); +} + +static stbtt__buf stbtt__cid_get_glyph_subrs(const stbtt_fontinfo *info, int glyph_index) +{ + stbtt__buf fdselect = info->fdselect; + int nranges, start, end, v, fmt, fdselector = -1, i; + + stbtt__buf_seek(&fdselect, 0); + fmt = stbtt__buf_get8(&fdselect); + if (fmt == 0) { + // untested + stbtt__buf_skip(&fdselect, glyph_index); + fdselector = stbtt__buf_get8(&fdselect); + } else if (fmt == 3) { + nranges = stbtt__buf_get16(&fdselect); + start = stbtt__buf_get16(&fdselect); + for (i = 0; i < nranges; i++) { + v = stbtt__buf_get8(&fdselect); + end = stbtt__buf_get16(&fdselect); + if (glyph_index >= start && glyph_index < end) { + fdselector = v; + break; + } + start = end; + } + } + if (fdselector == -1) stbtt__new_buf(NULL, 0); + return stbtt__get_subrs(info->cff, stbtt__cff_index_get(info->fontdicts, fdselector)); +} + +static int stbtt__run_charstring(const stbtt_fontinfo *info, int glyph_index, stbtt__csctx *c) +{ + int in_header = 1, maskbits = 0, subr_stack_height = 0, sp = 0, v, i, b0; + int has_subrs = 0, clear_stack; + float s[48]; + stbtt__buf subr_stack[10], subrs = info->subrs, b; + float f; + +#define STBTT__CSERR(s) (0) + + // this currently ignores the initial width value, which isn't needed if we have hmtx + b = stbtt__cff_index_get(info->charstrings, glyph_index); + while (b.cursor < b.size) { + i = 0; + clear_stack = 1; + b0 = stbtt__buf_get8(&b); + switch (b0) { + // @TODO implement hinting + case 0x13: // hintmask + case 0x14: // cntrmask + if (in_header) + maskbits += (sp / 2); // implicit "vstem" + in_header = 0; + stbtt__buf_skip(&b, (maskbits + 7) / 8); + break; + + case 0x01: // hstem + case 0x03: // vstem + case 0x12: // hstemhm + case 0x17: // vstemhm + maskbits += (sp / 2); + break; + + case 0x15: // rmoveto + in_header = 0; + if (sp < 2) return STBTT__CSERR("rmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-2], s[sp-1]); + break; + case 0x04: // vmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("vmoveto stack"); + stbtt__csctx_rmove_to(c, 0, s[sp-1]); + break; + case 0x16: // hmoveto + in_header = 0; + if (sp < 1) return STBTT__CSERR("hmoveto stack"); + stbtt__csctx_rmove_to(c, s[sp-1], 0); + break; + + case 0x05: // rlineto + if (sp < 2) return STBTT__CSERR("rlineto stack"); + for (; i + 1 < sp; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + // hlineto/vlineto and vhcurveto/hvcurveto alternate horizontal and vertical + // starting from a different place. + + case 0x07: // vlineto + if (sp < 1) return STBTT__CSERR("vlineto stack"); + goto vlineto; + case 0x06: // hlineto + if (sp < 1) return STBTT__CSERR("hlineto stack"); + for (;;) { + if (i >= sp) break; + stbtt__csctx_rline_to(c, s[i], 0); + i++; + vlineto: + if (i >= sp) break; + stbtt__csctx_rline_to(c, 0, s[i]); + i++; + } + break; + + case 0x1F: // hvcurveto + if (sp < 4) return STBTT__CSERR("hvcurveto stack"); + goto hvcurveto; + case 0x1E: // vhcurveto + if (sp < 4) return STBTT__CSERR("vhcurveto stack"); + for (;;) { + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, 0, s[i], s[i+1], s[i+2], s[i+3], (sp - i == 5) ? s[i + 4] : 0.0f); + i += 4; + hvcurveto: + if (i + 3 >= sp) break; + stbtt__csctx_rccurve_to(c, s[i], 0, s[i+1], s[i+2], (sp - i == 5) ? s[i+4] : 0.0f, s[i+3]); + i += 4; + } + break; + + case 0x08: // rrcurveto + if (sp < 6) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x18: // rcurveline + if (sp < 8) return STBTT__CSERR("rcurveline stack"); + for (; i + 5 < sp - 2; i += 6) + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + if (i + 1 >= sp) return STBTT__CSERR("rcurveline stack"); + stbtt__csctx_rline_to(c, s[i], s[i+1]); + break; + + case 0x19: // rlinecurve + if (sp < 8) return STBTT__CSERR("rlinecurve stack"); + for (; i + 1 < sp - 6; i += 2) + stbtt__csctx_rline_to(c, s[i], s[i+1]); + if (i + 5 >= sp) return STBTT__CSERR("rlinecurve stack"); + stbtt__csctx_rccurve_to(c, s[i], s[i+1], s[i+2], s[i+3], s[i+4], s[i+5]); + break; + + case 0x1A: // vvcurveto + case 0x1B: // hhcurveto + if (sp < 4) return STBTT__CSERR("(vv|hh)curveto stack"); + f = 0.0; + if (sp & 1) { f = s[i]; i++; } + for (; i + 3 < sp; i += 4) { + if (b0 == 0x1B) + stbtt__csctx_rccurve_to(c, s[i], f, s[i+1], s[i+2], s[i+3], 0.0); + else + stbtt__csctx_rccurve_to(c, f, s[i], s[i+1], s[i+2], 0.0, s[i+3]); + f = 0.0; + } + break; + + case 0x0A: // callsubr + if (!has_subrs) { + if (info->fdselect.size) + subrs = stbtt__cid_get_glyph_subrs(info, glyph_index); + has_subrs = 1; + } + // fallthrough + case 0x1D: // callgsubr + if (sp < 1) return STBTT__CSERR("call(g|)subr stack"); + v = (int) s[--sp]; + if (subr_stack_height >= 10) return STBTT__CSERR("recursion limit"); + subr_stack[subr_stack_height++] = b; + b = stbtt__get_subr(b0 == 0x0A ? subrs : info->gsubrs, v); + if (b.size == 0) return STBTT__CSERR("subr not found"); + b.cursor = 0; + clear_stack = 0; + break; + + case 0x0B: // return + if (subr_stack_height <= 0) return STBTT__CSERR("return outside subr"); + b = subr_stack[--subr_stack_height]; + clear_stack = 0; + break; + + case 0x0E: // endchar + stbtt__csctx_close_shape(c); + return 1; + + case 0x0C: { // two-byte escape + float dx1, dx2, dx3, dx4, dx5, dx6, dy1, dy2, dy3, dy4, dy5, dy6; + float dx, dy; + int b1 = stbtt__buf_get8(&b); + switch (b1) { + // @TODO These "flex" implementations ignore the flex-depth and resolution, + // and always draw beziers. + case 0x22: // hflex + if (sp < 7) return STBTT__CSERR("hflex stack"); + dx1 = s[0]; + dx2 = s[1]; + dy2 = s[2]; + dx3 = s[3]; + dx4 = s[4]; + dx5 = s[5]; + dx6 = s[6]; + stbtt__csctx_rccurve_to(c, dx1, 0, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, -dy2, dx6, 0); + break; + + case 0x23: // flex + if (sp < 13) return STBTT__CSERR("flex stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = s[10]; + dy6 = s[11]; + //fd is s[12] + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + case 0x24: // hflex1 + if (sp < 9) return STBTT__CSERR("hflex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dx4 = s[5]; + dx5 = s[6]; + dy5 = s[7]; + dx6 = s[8]; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, 0); + stbtt__csctx_rccurve_to(c, dx4, 0, dx5, dy5, dx6, -(dy1+dy2+dy5)); + break; + + case 0x25: // flex1 + if (sp < 11) return STBTT__CSERR("flex1 stack"); + dx1 = s[0]; + dy1 = s[1]; + dx2 = s[2]; + dy2 = s[3]; + dx3 = s[4]; + dy3 = s[5]; + dx4 = s[6]; + dy4 = s[7]; + dx5 = s[8]; + dy5 = s[9]; + dx6 = dy6 = s[10]; + dx = dx1+dx2+dx3+dx4+dx5; + dy = dy1+dy2+dy3+dy4+dy5; + if (STBTT_fabs(dx) > STBTT_fabs(dy)) + dy6 = -dy; + else + dx6 = -dx; + stbtt__csctx_rccurve_to(c, dx1, dy1, dx2, dy2, dx3, dy3); + stbtt__csctx_rccurve_to(c, dx4, dy4, dx5, dy5, dx6, dy6); + break; + + default: + return STBTT__CSERR("unimplemented"); + } + } break; + + default: + if (b0 != 255 && b0 != 28 && (b0 < 32 || b0 > 254)) + return STBTT__CSERR("reserved operator"); + + // push immediate + if (b0 == 255) { + f = (float)(stbtt_int32)stbtt__buf_get32(&b) / 0x10000; + } else { + stbtt__buf_skip(&b, -1); + f = (float)(stbtt_int16)stbtt__cff_int(&b); + } + if (sp >= 48) return STBTT__CSERR("push stack overflow"); + s[sp++] = f; + clear_stack = 0; + break; + } + if (clear_stack) sp = 0; + } + return STBTT__CSERR("no endchar"); + +#undef STBTT__CSERR +} + +static int stbtt__GetGlyphShapeT2(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + // runs the charstring twice, once to count and once to output (to avoid realloc) + stbtt__csctx count_ctx = STBTT__CSCTX_INIT(1); + stbtt__csctx output_ctx = STBTT__CSCTX_INIT(0); + if (stbtt__run_charstring(info, glyph_index, &count_ctx)) { + *pvertices = (stbtt_vertex*)STBTT_malloc(count_ctx.num_vertices*sizeof(stbtt_vertex), info->userdata); + output_ctx.pvertices = *pvertices; + if (stbtt__run_charstring(info, glyph_index, &output_ctx)) { + STBTT_assert(output_ctx.num_vertices == count_ctx.num_vertices); + return output_ctx.num_vertices; + } + } + *pvertices = NULL; + return 0; +} + +static int stbtt__GetGlyphInfoT2(const stbtt_fontinfo *info, int glyph_index, int *x0, int *y0, int *x1, int *y1) +{ + stbtt__csctx c = STBTT__CSCTX_INIT(1); + int r = stbtt__run_charstring(info, glyph_index, &c); + if (x0) *x0 = r ? c.min_x : 0; + if (y0) *y0 = r ? c.min_y : 0; + if (x1) *x1 = r ? c.max_x : 0; + if (y1) *y1 = r ? c.max_y : 0; + return r ? c.num_vertices : 0; +} + +STBTT_DEF int stbtt_GetGlyphShape(const stbtt_fontinfo *info, int glyph_index, stbtt_vertex **pvertices) +{ + if (!info->cff.size) + return stbtt__GetGlyphShapeTT(info, glyph_index, pvertices); + else + return stbtt__GetGlyphShapeT2(info, glyph_index, pvertices); +} + +STBTT_DEF void stbtt_GetGlyphHMetrics(const stbtt_fontinfo *info, int glyph_index, int *advanceWidth, int *leftSideBearing) +{ + stbtt_uint16 numOfLongHorMetrics = ttUSHORT(info->data+info->hhea + 34); + if (glyph_index < numOfLongHorMetrics) { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*glyph_index); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*glyph_index + 2); + } else { + if (advanceWidth) *advanceWidth = ttSHORT(info->data + info->hmtx + 4*(numOfLongHorMetrics-1)); + if (leftSideBearing) *leftSideBearing = ttSHORT(info->data + info->hmtx + 4*numOfLongHorMetrics + 2*(glyph_index - numOfLongHorMetrics)); + } +} + +static int stbtt__GetGlyphKernInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint8 *data = info->data + info->kern; + stbtt_uint32 needle, straw; + int l, r, m; + + // we only look at the first table. it must be 'horizontal' and format 0. + if (!info->kern) + return 0; + if (ttUSHORT(data+2) < 1) // number of tables, need at least 1 + return 0; + if (ttUSHORT(data+8) != 1) // horizontal flag must be set in format + return 0; + + l = 0; + r = ttUSHORT(data+10) - 1; + needle = glyph1 << 16 | glyph2; + while (l <= r) { + m = (l + r) >> 1; + straw = ttULONG(data+18+(m*6)); // note: unaligned read + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else + return ttSHORT(data+22+(m*6)); + } + return 0; +} + +static stbtt_int32 stbtt__GetCoverageIndex(stbtt_uint8 *coverageTable, int glyph) +{ + stbtt_uint16 coverageFormat = ttUSHORT(coverageTable); + switch(coverageFormat) { + case 1: { + stbtt_uint16 glyphCount = ttUSHORT(coverageTable + 2); + + // Binary search. + stbtt_int32 l=0, r=glyphCount-1, m; + int straw, needle=glyph; + while (l <= r) { + stbtt_uint8 *glyphArray = coverageTable + 4; + stbtt_uint16 glyphID; + m = (l + r) >> 1; + glyphID = ttUSHORT(glyphArray + 2 * m); + straw = glyphID; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + return m; + } + } + } break; + + case 2: { + stbtt_uint16 rangeCount = ttUSHORT(coverageTable + 2); + stbtt_uint8 *rangeArray = coverageTable + 4; + + // Binary search. + stbtt_int32 l=0, r=rangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *rangeRecord; + m = (l + r) >> 1; + rangeRecord = rangeArray + 6 * m; + strawStart = ttUSHORT(rangeRecord); + strawEnd = ttUSHORT(rangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else { + stbtt_uint16 startCoverageIndex = ttUSHORT(rangeRecord + 4); + return startCoverageIndex + glyph - strawStart; + } + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +static stbtt_int32 stbtt__GetGlyphClass(stbtt_uint8 *classDefTable, int glyph) +{ + stbtt_uint16 classDefFormat = ttUSHORT(classDefTable); + switch(classDefFormat) + { + case 1: { + stbtt_uint16 startGlyphID = ttUSHORT(classDefTable + 2); + stbtt_uint16 glyphCount = ttUSHORT(classDefTable + 4); + stbtt_uint8 *classDef1ValueArray = classDefTable + 6; + + if (glyph >= startGlyphID && glyph < startGlyphID + glyphCount) + return (stbtt_int32)ttUSHORT(classDef1ValueArray + 2 * (glyph - startGlyphID)); + + classDefTable = classDef1ValueArray + 2 * glyphCount; + } break; + + case 2: { + stbtt_uint16 classRangeCount = ttUSHORT(classDefTable + 2); + stbtt_uint8 *classRangeRecords = classDefTable + 4; + + // Binary search. + stbtt_int32 l=0, r=classRangeCount-1, m; + int strawStart, strawEnd, needle=glyph; + while (l <= r) { + stbtt_uint8 *classRangeRecord; + m = (l + r) >> 1; + classRangeRecord = classRangeRecords + 6 * m; + strawStart = ttUSHORT(classRangeRecord); + strawEnd = ttUSHORT(classRangeRecord + 2); + if (needle < strawStart) + r = m - 1; + else if (needle > strawEnd) + l = m + 1; + else + return (stbtt_int32)ttUSHORT(classRangeRecord + 4); + } + + classDefTable = classRangeRecords + 6 * classRangeCount; + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + } break; + } + + return -1; +} + +// Define to STBTT_assert(x) if you want to break on unimplemented formats. +#define STBTT_GPOS_TODO_assert(x) + +static stbtt_int32 stbtt__GetGlyphGPOSInfoAdvance(const stbtt_fontinfo *info, int glyph1, int glyph2) +{ + stbtt_uint16 lookupListOffset; + stbtt_uint8 *lookupList; + stbtt_uint16 lookupCount; + stbtt_uint8 *data; + stbtt_int32 i; + + if (!info->gpos) return 0; + + data = info->data + info->gpos; + + if (ttUSHORT(data+0) != 1) return 0; // Major version 1 + if (ttUSHORT(data+2) != 0) return 0; // Minor version 0 + + lookupListOffset = ttUSHORT(data+8); + lookupList = data + lookupListOffset; + lookupCount = ttUSHORT(lookupList); + + for (i=0; i> 1; + pairValue = pairValueArray + (2 + valueRecordPairSizeInBytes) * m; + secondGlyph = ttUSHORT(pairValue); + straw = secondGlyph; + if (needle < straw) + r = m - 1; + else if (needle > straw) + l = m + 1; + else { + stbtt_int16 xAdvance = ttSHORT(pairValue + 2); + return xAdvance; + } + } + } break; + + case 2: { + stbtt_uint16 valueFormat1 = ttUSHORT(table + 4); + stbtt_uint16 valueFormat2 = ttUSHORT(table + 6); + + stbtt_uint16 classDef1Offset = ttUSHORT(table + 8); + stbtt_uint16 classDef2Offset = ttUSHORT(table + 10); + int glyph1class = stbtt__GetGlyphClass(table + classDef1Offset, glyph1); + int glyph2class = stbtt__GetGlyphClass(table + classDef2Offset, glyph2); + + stbtt_uint16 class1Count = ttUSHORT(table + 12); + stbtt_uint16 class2Count = ttUSHORT(table + 14); + STBTT_assert(glyph1class < class1Count); + STBTT_assert(glyph2class < class2Count); + + // TODO: Support more formats. + STBTT_GPOS_TODO_assert(valueFormat1 == 4); + if (valueFormat1 != 4) return 0; + STBTT_GPOS_TODO_assert(valueFormat2 == 0); + if (valueFormat2 != 0) return 0; + + if (glyph1class >= 0 && glyph1class < class1Count && glyph2class >= 0 && glyph2class < class2Count) { + stbtt_uint8 *class1Records = table + 16; + stbtt_uint8 *class2Records = class1Records + 2 * (glyph1class * class2Count); + stbtt_int16 xAdvance = ttSHORT(class2Records + 2 * glyph2class); + return xAdvance; + } + } break; + + default: { + // There are no other cases. + STBTT_assert(0); + break; + }; + } + } + break; + }; + + default: + // TODO: Implement other stuff. + break; + } + } + + return 0; +} + +STBTT_DEF int stbtt_GetGlyphKernAdvance(const stbtt_fontinfo *info, int g1, int g2) +{ + int xAdvance = 0; + + if (info->gpos) + xAdvance += stbtt__GetGlyphGPOSInfoAdvance(info, g1, g2); + + if (info->kern) + xAdvance += stbtt__GetGlyphKernInfoAdvance(info, g1, g2); + + return xAdvance; +} + +STBTT_DEF int stbtt_GetCodepointKernAdvance(const stbtt_fontinfo *info, int ch1, int ch2) +{ + if (!info->kern && !info->gpos) // if no kerning table, don't waste time looking up both codepoint->glyphs + return 0; + return stbtt_GetGlyphKernAdvance(info, stbtt_FindGlyphIndex(info,ch1), stbtt_FindGlyphIndex(info,ch2)); +} + +STBTT_DEF void stbtt_GetCodepointHMetrics(const stbtt_fontinfo *info, int codepoint, int *advanceWidth, int *leftSideBearing) +{ + stbtt_GetGlyphHMetrics(info, stbtt_FindGlyphIndex(info,codepoint), advanceWidth, leftSideBearing); +} + +STBTT_DEF void stbtt_GetFontVMetrics(const stbtt_fontinfo *info, int *ascent, int *descent, int *lineGap) +{ + if (ascent ) *ascent = ttSHORT(info->data+info->hhea + 4); + if (descent) *descent = ttSHORT(info->data+info->hhea + 6); + if (lineGap) *lineGap = ttSHORT(info->data+info->hhea + 8); +} + +STBTT_DEF int stbtt_GetFontVMetricsOS2(const stbtt_fontinfo *info, int *typoAscent, int *typoDescent, int *typoLineGap) +{ + int tab = stbtt__find_table(info->data, info->fontstart, "OS/2"); + if (!tab) + return 0; + if (typoAscent ) *typoAscent = ttSHORT(info->data+tab + 68); + if (typoDescent) *typoDescent = ttSHORT(info->data+tab + 70); + if (typoLineGap) *typoLineGap = ttSHORT(info->data+tab + 72); + return 1; +} + +STBTT_DEF void stbtt_GetFontBoundingBox(const stbtt_fontinfo *info, int *x0, int *y0, int *x1, int *y1) +{ + *x0 = ttSHORT(info->data + info->head + 36); + *y0 = ttSHORT(info->data + info->head + 38); + *x1 = ttSHORT(info->data + info->head + 40); + *y1 = ttSHORT(info->data + info->head + 42); +} + +STBTT_DEF float stbtt_ScaleForPixelHeight(const stbtt_fontinfo *info, float height) +{ + int fheight = ttSHORT(info->data + info->hhea + 4) - ttSHORT(info->data + info->hhea + 6); + return (float) height / fheight; +} + +STBTT_DEF float stbtt_ScaleForMappingEmToPixels(const stbtt_fontinfo *info, float pixels) +{ + int unitsPerEm = ttUSHORT(info->data + info->head + 18); + return pixels / unitsPerEm; +} + +STBTT_DEF void stbtt_FreeShape(const stbtt_fontinfo *info, stbtt_vertex *v) +{ + STBTT_free(v, info->userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// antialiasing software rasterizer +// + +STBTT_DEF void stbtt_GetGlyphBitmapBoxSubpixel(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y,float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + int x0=0,y0=0,x1,y1; // =0 suppresses compiler warning + if (!stbtt_GetGlyphBox(font, glyph, &x0,&y0,&x1,&y1)) { + // e.g. space character + if (ix0) *ix0 = 0; + if (iy0) *iy0 = 0; + if (ix1) *ix1 = 0; + if (iy1) *iy1 = 0; + } else { + // move to integral bboxes (treating pixels as little squares, what pixels get touched)? + if (ix0) *ix0 = STBTT_ifloor( x0 * scale_x + shift_x); + if (iy0) *iy0 = STBTT_ifloor(-y1 * scale_y + shift_y); + if (ix1) *ix1 = STBTT_iceil ( x1 * scale_x + shift_x); + if (iy1) *iy1 = STBTT_iceil (-y0 * scale_y + shift_y); + } +} + +STBTT_DEF void stbtt_GetGlyphBitmapBox(const stbtt_fontinfo *font, int glyph, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, glyph, scale_x, scale_y,0.0f,0.0f, ix0, iy0, ix1, iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBoxSubpixel(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, float shift_x, float shift_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetGlyphBitmapBoxSubpixel(font, stbtt_FindGlyphIndex(font,codepoint), scale_x, scale_y,shift_x,shift_y, ix0,iy0,ix1,iy1); +} + +STBTT_DEF void stbtt_GetCodepointBitmapBox(const stbtt_fontinfo *font, int codepoint, float scale_x, float scale_y, int *ix0, int *iy0, int *ix1, int *iy1) +{ + stbtt_GetCodepointBitmapBoxSubpixel(font, codepoint, scale_x, scale_y,0.0f,0.0f, ix0,iy0,ix1,iy1); +} + +////////////////////////////////////////////////////////////////////////////// +// +// Rasterizer + +typedef struct stbtt__hheap_chunk +{ + struct stbtt__hheap_chunk *next; +} stbtt__hheap_chunk; + +typedef struct stbtt__hheap +{ + struct stbtt__hheap_chunk *head; + void *first_free; + int num_remaining_in_head_chunk; +} stbtt__hheap; + +static void *stbtt__hheap_alloc(stbtt__hheap *hh, size_t size, void *userdata) +{ + if (hh->first_free) { + void *p = hh->first_free; + hh->first_free = * (void **) p; + return p; + } else { + if (hh->num_remaining_in_head_chunk == 0) { + int count = (size < 32 ? 2000 : size < 128 ? 800 : 100); + stbtt__hheap_chunk *c = (stbtt__hheap_chunk *) STBTT_malloc(sizeof(stbtt__hheap_chunk) + size * count, userdata); + if (c == NULL) + return NULL; + c->next = hh->head; + hh->head = c; + hh->num_remaining_in_head_chunk = count; + } + --hh->num_remaining_in_head_chunk; + return (char *) (hh->head) + sizeof(stbtt__hheap_chunk) + size * hh->num_remaining_in_head_chunk; + } +} + +static void stbtt__hheap_free(stbtt__hheap *hh, void *p) +{ + *(void **) p = hh->first_free; + hh->first_free = p; +} + +static void stbtt__hheap_cleanup(stbtt__hheap *hh, void *userdata) +{ + stbtt__hheap_chunk *c = hh->head; + while (c) { + stbtt__hheap_chunk *n = c->next; + STBTT_free(c, userdata); + c = n; + } +} + +typedef struct stbtt__edge { + float x0,y0, x1,y1; + int invert; +} stbtt__edge; + + +typedef struct stbtt__active_edge +{ + struct stbtt__active_edge *next; +#if STBTT_RASTERIZER_VERSION==1 + int x,dx; + float ey; + int direction; +#elif STBTT_RASTERIZER_VERSION==2 + float fx,fdx,fdy; + float direction; + float sy; + float ey; +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif +} stbtt__active_edge; + +#if STBTT_RASTERIZER_VERSION == 1 +#define STBTT_FIXSHIFT 10 +#define STBTT_FIX (1 << STBTT_FIXSHIFT) +#define STBTT_FIXMASK (STBTT_FIX-1) + +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + if (!z) return z; + + // round dx down to avoid overshooting + if (dxdy < 0) + z->dx = -STBTT_ifloor(STBTT_FIX * -dxdy); + else + z->dx = STBTT_ifloor(STBTT_FIX * dxdy); + + z->x = STBTT_ifloor(STBTT_FIX * e->x0 + z->dx * (start_point - e->y0)); // use z->dx so when we offset later it's by the same amount + z->x -= off_x * STBTT_FIX; + + z->ey = e->y1; + z->next = 0; + z->direction = e->invert ? 1 : -1; + return z; +} +#elif STBTT_RASTERIZER_VERSION == 2 +static stbtt__active_edge *stbtt__new_active(stbtt__hheap *hh, stbtt__edge *e, int off_x, float start_point, void *userdata) +{ + stbtt__active_edge *z = (stbtt__active_edge *) stbtt__hheap_alloc(hh, sizeof(*z), userdata); + float dxdy = (e->x1 - e->x0) / (e->y1 - e->y0); + STBTT_assert(z != NULL); + //STBTT_assert(e->y0 <= start_point); + if (!z) return z; + z->fdx = dxdy; + z->fdy = dxdy != 0.0f ? (1.0f/dxdy) : 0.0f; + z->fx = e->x0 + dxdy * (start_point - e->y0); + z->fx -= off_x; + z->direction = e->invert ? 1.0f : -1.0f; + z->sy = e->y0; + z->ey = e->y1; + z->next = 0; + return z; +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#if STBTT_RASTERIZER_VERSION == 1 +// note: this routine clips fills that extend off the edges... ideally this +// wouldn't happen, but it could happen if the truetype glyph bounding boxes +// are wrong, or if the user supplies a too-small bitmap +static void stbtt__fill_active_edges(unsigned char *scanline, int len, stbtt__active_edge *e, int max_weight) +{ + // non-zero winding fill + int x0=0, w=0; + + while (e) { + if (w == 0) { + // if we're currently at zero, we need to record the edge start point + x0 = e->x; w += e->direction; + } else { + int x1 = e->x; w += e->direction; + // if we went to zero, we need to draw + if (w == 0) { + int i = x0 >> STBTT_FIXSHIFT; + int j = x1 >> STBTT_FIXSHIFT; + + if (i < len && j >= 0) { + if (i == j) { + // x0,x1 are the same pixel, so compute combined coverage + scanline[i] = scanline[i] + (stbtt_uint8) ((x1 - x0) * max_weight >> STBTT_FIXSHIFT); + } else { + if (i >= 0) // add antialiasing for x0 + scanline[i] = scanline[i] + (stbtt_uint8) (((STBTT_FIX - (x0 & STBTT_FIXMASK)) * max_weight) >> STBTT_FIXSHIFT); + else + i = -1; // clip + + if (j < len) // add antialiasing for x1 + scanline[j] = scanline[j] + (stbtt_uint8) (((x1 & STBTT_FIXMASK) * max_weight) >> STBTT_FIXSHIFT); + else + j = len; // clip + + for (++i; i < j; ++i) // fill pixels between x0 and x1 + scanline[i] = scanline[i] + (stbtt_uint8) max_weight; + } + } + } + } + + e = e->next; + } +} + +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0; + int max_weight = (255 / vsubsample); // weight per vertical scanline + int s; // vertical subsample index + unsigned char scanline_data[512], *scanline; + + if (result->w > 512) + scanline = (unsigned char *) STBTT_malloc(result->w, userdata); + else + scanline = scanline_data; + + y = off_y * vsubsample; + e[n].y0 = (off_y + result->h) * (float) vsubsample + 1; + + while (j < result->h) { + STBTT_memset(scanline, 0, result->w); + for (s=0; s < vsubsample; ++s) { + // find center of pixel for this scanline + float scan_y = y + 0.5f; + stbtt__active_edge **step = &active; + + // update all active edges; + // remove all active edges that terminate before the center of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + z->x += z->dx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + } + + // resort the list if needed + for(;;) { + int changed=0; + step = &active; + while (*step && (*step)->next) { + if ((*step)->x > (*step)->next->x) { + stbtt__active_edge *t = *step; + stbtt__active_edge *q = t->next; + + t->next = q->next; + q->next = t; + *step = q; + changed = 1; + } + step = &(*step)->next; + } + if (!changed) break; + } + + // insert all edges that start before the center of this scanline -- omit ones that also end on this scanline + while (e->y0 <= scan_y) { + if (e->y1 > scan_y) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y, userdata); + if (z != NULL) { + // find insertion point + if (active == NULL) + active = z; + else if (z->x < active->x) { + // insert at front + z->next = active; + active = z; + } else { + // find thing to insert AFTER + stbtt__active_edge *p = active; + while (p->next && p->next->x < z->x) + p = p->next; + // at this point, p->next->x is NOT < z->x + z->next = p->next; + p->next = z; + } + } + } + ++e; + } + + // now process all active edges in XOR fashion + if (active) + stbtt__fill_active_edges(scanline, result->w, active, max_weight); + + ++y; + } + STBTT_memcpy(result->pixels + j * result->stride, scanline, result->w); + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} + +#elif STBTT_RASTERIZER_VERSION == 2 + +// the edge passed in here does not cross the vertical line at x or the vertical line at x+1 +// (i.e. it has already been clipped to those) +static void stbtt__handle_clipped_edge(float *scanline, int x, stbtt__active_edge *e, float x0, float y0, float x1, float y1) +{ + if (y0 == y1) return; + STBTT_assert(y0 < y1); + STBTT_assert(e->sy <= e->ey); + if (y0 > e->ey) return; + if (y1 < e->sy) return; + if (y0 < e->sy) { + x0 += (x1-x0) * (e->sy - y0) / (y1-y0); + y0 = e->sy; + } + if (y1 > e->ey) { + x1 += (x1-x0) * (e->ey - y1) / (y1-y0); + y1 = e->ey; + } + + if (x0 == x) + STBTT_assert(x1 <= x+1); + else if (x0 == x+1) + STBTT_assert(x1 >= x); + else if (x0 <= x) + STBTT_assert(x1 <= x); + else if (x0 >= x+1) + STBTT_assert(x1 >= x+1); + else + STBTT_assert(x1 >= x && x1 <= x+1); + + if (x0 <= x && x1 <= x) + scanline[x] += e->direction * (y1-y0); + else if (x0 >= x+1 && x1 >= x+1) + ; + else { + STBTT_assert(x0 >= x && x0 <= x+1 && x1 >= x && x1 <= x+1); + scanline[x] += e->direction * (y1-y0) * (1-((x0-x)+(x1-x))/2); // coverage = 1 - average x position + } +} + +static void stbtt__fill_active_edges_new(float *scanline, float *scanline_fill, int len, stbtt__active_edge *e, float y_top) +{ + float y_bottom = y_top+1; + + while (e) { + // brute force every pixel + + // compute intersection points with top & bottom + STBTT_assert(e->ey >= y_top); + + if (e->fdx == 0) { + float x0 = e->fx; + if (x0 < len) { + if (x0 >= 0) { + stbtt__handle_clipped_edge(scanline,(int) x0,e, x0,y_top, x0,y_bottom); + stbtt__handle_clipped_edge(scanline_fill-1,(int) x0+1,e, x0,y_top, x0,y_bottom); + } else { + stbtt__handle_clipped_edge(scanline_fill-1,0,e, x0,y_top, x0,y_bottom); + } + } + } else { + float x0 = e->fx; + float dx = e->fdx; + float xb = x0 + dx; + float x_top, x_bottom; + float sy0,sy1; + float dy = e->fdy; + STBTT_assert(e->sy <= y_bottom && e->ey >= y_top); + + // compute endpoints of line segment clipped to this scanline (if the + // line segment starts on this scanline. x0 is the intersection of the + // line with y_top, but that may be off the line segment. + if (e->sy > y_top) { + x_top = x0 + dx * (e->sy - y_top); + sy0 = e->sy; + } else { + x_top = x0; + sy0 = y_top; + } + if (e->ey < y_bottom) { + x_bottom = x0 + dx * (e->ey - y_top); + sy1 = e->ey; + } else { + x_bottom = xb; + sy1 = y_bottom; + } + + if (x_top >= 0 && x_bottom >= 0 && x_top < len && x_bottom < len) { + // from here on, we don't have to range check x values + + if ((int) x_top == (int) x_bottom) { + float height; + // simple case, only spans one pixel + int x = (int) x_top; + height = sy1 - sy0; + STBTT_assert(x >= 0 && x < len); + scanline[x] += e->direction * (1-((x_top - x) + (x_bottom-x))/2) * height; + scanline_fill[x] += e->direction * height; // everything right of this pixel is filled + } else { + int x,x1,x2; + float y_crossing, step, sign, area; + // covers 2+ pixels + if (x_top > x_bottom) { + // flip scanline vertically; signed area is the same + float t; + sy0 = y_bottom - (sy0 - y_top); + sy1 = y_bottom - (sy1 - y_top); + t = sy0, sy0 = sy1, sy1 = t; + t = x_bottom, x_bottom = x_top, x_top = t; + dx = -dx; + dy = -dy; + t = x0, x0 = xb, xb = t; + } + + x1 = (int) x_top; + x2 = (int) x_bottom; + // compute intersection with y axis at x1+1 + y_crossing = (x1+1 - x0) * dy + y_top; + + sign = e->direction; + // area of the rectangle covered from y0..y_crossing + area = sign * (y_crossing-sy0); + // area of the triangle (x_top,y0), (x+1,y0), (x+1,y_crossing) + scanline[x1] += area * (1-((x_top - x1)+(x1+1-x1))/2); + + step = sign * dy; + for (x = x1+1; x < x2; ++x) { + scanline[x] += area + step/2; + area += step; + } + y_crossing += dy * (x2 - (x1+1)); + + STBTT_assert(STBTT_fabs(area) <= 1.01f); + + scanline[x2] += area + sign * (1-((x2-x2)+(x_bottom-x2))/2) * (sy1-y_crossing); + + scanline_fill[x2] += sign * (sy1-sy0); + } + } else { + // if edge goes outside of box we're drawing, we require + // clipping logic. since this does not match the intended use + // of this library, we use a different, very slow brute + // force implementation + int x; + for (x=0; x < len; ++x) { + // cases: + // + // there can be up to two intersections with the pixel. any intersection + // with left or right edges can be handled by splitting into two (or three) + // regions. intersections with top & bottom do not necessitate case-wise logic. + // + // the old way of doing this found the intersections with the left & right edges, + // then used some simple logic to produce up to three segments in sorted order + // from top-to-bottom. however, this had a problem: if an x edge was epsilon + // across the x border, then the corresponding y position might not be distinct + // from the other y segment, and it might ignored as an empty segment. to avoid + // that, we need to explicitly produce segments based on x positions. + + // rename variables to clearly-defined pairs + float y0 = y_top; + float x1 = (float) (x); + float x2 = (float) (x+1); + float x3 = xb; + float y3 = y_bottom; + + // x = e->x + e->dx * (y-y_top) + // (y-y_top) = (x - e->x) / e->dx + // y = (x - e->x) / e->dx + y_top + float y1 = (x - x0) / dx + y_top; + float y2 = (x+1 - x0) / dx + y_top; + + if (x0 < x1 && x3 > x2) { // three segments descending down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x1 && x0 > x2) { // three segments descending down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x1 && x3 > x1) { // two segments across x, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x3 < x1 && x0 > x1) { // two segments across x, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x1,y1); + stbtt__handle_clipped_edge(scanline,x,e, x1,y1, x3,y3); + } else if (x0 < x2 && x3 > x2) { // two segments across x+1, down-right + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else if (x3 < x2 && x0 > x2) { // two segments across x+1, down-left + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x2,y2); + stbtt__handle_clipped_edge(scanline,x,e, x2,y2, x3,y3); + } else { // one segment + stbtt__handle_clipped_edge(scanline,x,e, x0,y0, x3,y3); + } + } + } + } + e = e->next; + } +} + +// directly AA rasterize edges w/o supersampling +static void stbtt__rasterize_sorted_edges(stbtt__bitmap *result, stbtt__edge *e, int n, int vsubsample, int off_x, int off_y, void *userdata) +{ + stbtt__hheap hh = { 0, 0, 0 }; + stbtt__active_edge *active = NULL; + int y,j=0, i; + float scanline_data[129], *scanline, *scanline2; + + STBTT__NOTUSED(vsubsample); + + if (result->w > 64) + scanline = (float *) STBTT_malloc((result->w*2+1) * sizeof(float), userdata); + else + scanline = scanline_data; + + scanline2 = scanline + result->w; + + y = off_y; + e[n].y0 = (float) (off_y + result->h) + 1; + + while (j < result->h) { + // find center of pixel for this scanline + float scan_y_top = y + 0.0f; + float scan_y_bottom = y + 1.0f; + stbtt__active_edge **step = &active; + + STBTT_memset(scanline , 0, result->w*sizeof(scanline[0])); + STBTT_memset(scanline2, 0, (result->w+1)*sizeof(scanline[0])); + + // update all active edges; + // remove all active edges that terminate before the top of this scanline + while (*step) { + stbtt__active_edge * z = *step; + if (z->ey <= scan_y_top) { + *step = z->next; // delete from list + STBTT_assert(z->direction); + z->direction = 0; + stbtt__hheap_free(&hh, z); + } else { + step = &((*step)->next); // advance through list + } + } + + // insert all edges that start before the bottom of this scanline + while (e->y0 <= scan_y_bottom) { + if (e->y0 != e->y1) { + stbtt__active_edge *z = stbtt__new_active(&hh, e, off_x, scan_y_top, userdata); + if (z != NULL) { + STBTT_assert(z->ey >= scan_y_top); + // insert at front + z->next = active; + active = z; + } + } + ++e; + } + + // now process all active edges + if (active) + stbtt__fill_active_edges_new(scanline, scanline2+1, result->w, active, scan_y_top); + + { + float sum = 0; + for (i=0; i < result->w; ++i) { + float k; + int m; + sum += scanline2[i]; + k = scanline[i] + sum; + k = (float) STBTT_fabs(k)*255 + 0.5f; + m = (int) k; + if (m > 255) m = 255; + result->pixels[j*result->stride + i] = (unsigned char) m; + } + } + // advance all the edges + step = &active; + while (*step) { + stbtt__active_edge *z = *step; + z->fx += z->fdx; // advance to position for current scanline + step = &((*step)->next); // advance through list + } + + ++y; + ++j; + } + + stbtt__hheap_cleanup(&hh, userdata); + + if (scanline != scanline_data) + STBTT_free(scanline, userdata); +} +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + +#define STBTT__COMPARE(a,b) ((a)->y0 < (b)->y0) + +static void stbtt__sort_edges_ins_sort(stbtt__edge *p, int n) +{ + int i,j; + for (i=1; i < n; ++i) { + stbtt__edge t = p[i], *a = &t; + j = i; + while (j > 0) { + stbtt__edge *b = &p[j-1]; + int c = STBTT__COMPARE(a,b); + if (!c) break; + p[j] = p[j-1]; + --j; + } + if (i != j) + p[j] = t; + } +} + +static void stbtt__sort_edges_quicksort(stbtt__edge *p, int n) +{ + /* threshhold for transitioning to insertion sort */ + while (n > 12) { + stbtt__edge t; + int c01,c12,c,m,i,j; + + /* compute median of three */ + m = n >> 1; + c01 = STBTT__COMPARE(&p[0],&p[m]); + c12 = STBTT__COMPARE(&p[m],&p[n-1]); + /* if 0 >= mid >= end, or 0 < mid < end, then use mid */ + if (c01 != c12) { + /* otherwise, we'll need to swap something else to middle */ + int z; + c = STBTT__COMPARE(&p[0],&p[n-1]); + /* 0>mid && midn => n; 0 0 */ + /* 0n: 0>n => 0; 0 n */ + z = (c == c12) ? 0 : n-1; + t = p[z]; + p[z] = p[m]; + p[m] = t; + } + /* now p[m] is the median-of-three */ + /* swap it to the beginning so it won't move around */ + t = p[0]; + p[0] = p[m]; + p[m] = t; + + /* partition loop */ + i=1; + j=n-1; + for(;;) { + /* handling of equality is crucial here */ + /* for sentinels & efficiency with duplicates */ + for (;;++i) { + if (!STBTT__COMPARE(&p[i], &p[0])) break; + } + for (;;--j) { + if (!STBTT__COMPARE(&p[0], &p[j])) break; + } + /* make sure we haven't crossed */ + if (i >= j) break; + t = p[i]; + p[i] = p[j]; + p[j] = t; + + ++i; + --j; + } + /* recurse on smaller side, iterate on larger */ + if (j < (n-i)) { + stbtt__sort_edges_quicksort(p,j); + p = p+i; + n = n-i; + } else { + stbtt__sort_edges_quicksort(p+i, n-i); + n = j; + } + } +} + +static void stbtt__sort_edges(stbtt__edge *p, int n) +{ + stbtt__sort_edges_quicksort(p, n); + stbtt__sort_edges_ins_sort(p, n); +} + +typedef struct +{ + float x,y; +} stbtt__point; + +static void stbtt__rasterize(stbtt__bitmap *result, stbtt__point *pts, int *wcount, int windings, float scale_x, float scale_y, float shift_x, float shift_y, int off_x, int off_y, int invert, void *userdata) +{ + float y_scale_inv = invert ? -scale_y : scale_y; + stbtt__edge *e; + int n,i,j,k,m; +#if STBTT_RASTERIZER_VERSION == 1 + int vsubsample = result->h < 8 ? 15 : 5; +#elif STBTT_RASTERIZER_VERSION == 2 + int vsubsample = 1; +#else +#error "Unrecognized value of STBTT_RASTERIZER_VERSION" +#endif + // vsubsample should divide 255 evenly; otherwise we won't reach full opacity + + // now we have to blow out the windings into explicit edge lists + n = 0; + for (i=0; i < windings; ++i) + n += wcount[i]; + + e = (stbtt__edge *) STBTT_malloc(sizeof(*e) * (n+1), userdata); // add an extra one as a sentinel + if (e == 0) return; + n = 0; + + m=0; + for (i=0; i < windings; ++i) { + stbtt__point *p = pts + m; + m += wcount[i]; + j = wcount[i]-1; + for (k=0; k < wcount[i]; j=k++) { + int a=k,b=j; + // skip the edge if horizontal + if (p[j].y == p[k].y) + continue; + // add edge from j to k to the list + e[n].invert = 0; + if (invert ? p[j].y > p[k].y : p[j].y < p[k].y) { + e[n].invert = 1; + a=j,b=k; + } + e[n].x0 = p[a].x * scale_x + shift_x; + e[n].y0 = (p[a].y * y_scale_inv + shift_y) * vsubsample; + e[n].x1 = p[b].x * scale_x + shift_x; + e[n].y1 = (p[b].y * y_scale_inv + shift_y) * vsubsample; + ++n; + } + } + + // now sort the edges by their highest point (should snap to integer, and then by x) + //STBTT_sort(e, n, sizeof(e[0]), stbtt__edge_compare); + stbtt__sort_edges(e, n); + + // now, traverse the scanlines and find the intersections on each scanline, use xor winding rule + stbtt__rasterize_sorted_edges(result, e, n, vsubsample, off_x, off_y, userdata); + + STBTT_free(e, userdata); +} + +static void stbtt__add_point(stbtt__point *points, int n, float x, float y) +{ + if (!points) return; // during first pass, it's unallocated + points[n].x = x; + points[n].y = y; +} + +// tesselate until threshhold p is happy... @TODO warped to compensate for non-linear stretching +static int stbtt__tesselate_curve(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float objspace_flatness_squared, int n) +{ + // midpoint + float mx = (x0 + 2*x1 + x2)/4; + float my = (y0 + 2*y1 + y2)/4; + // versus directly drawn line + float dx = (x0+x2)/2 - mx; + float dy = (y0+y2)/2 - my; + if (n > 16) // 65536 segments on one curve better be enough! + return 1; + if (dx*dx+dy*dy > objspace_flatness_squared) { // half-pixel error allowed... need to be smaller if AA + stbtt__tesselate_curve(points, num_points, x0,y0, (x0+x1)/2.0f,(y0+y1)/2.0f, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_curve(points, num_points, mx,my, (x1+x2)/2.0f,(y1+y2)/2.0f, x2,y2, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x2,y2); + *num_points = *num_points+1; + } + return 1; +} + +static void stbtt__tesselate_cubic(stbtt__point *points, int *num_points, float x0, float y0, float x1, float y1, float x2, float y2, float x3, float y3, float objspace_flatness_squared, int n) +{ + // @TODO this "flatness" calculation is just made-up nonsense that seems to work well enough + float dx0 = x1-x0; + float dy0 = y1-y0; + float dx1 = x2-x1; + float dy1 = y2-y1; + float dx2 = x3-x2; + float dy2 = y3-y2; + float dx = x3-x0; + float dy = y3-y0; + float longlen = (float) (STBTT_sqrt(dx0*dx0+dy0*dy0)+STBTT_sqrt(dx1*dx1+dy1*dy1)+STBTT_sqrt(dx2*dx2+dy2*dy2)); + float shortlen = (float) STBTT_sqrt(dx*dx+dy*dy); + float flatness_squared = longlen*longlen-shortlen*shortlen; + + if (n > 16) // 65536 segments on one curve better be enough! + return; + + if (flatness_squared > objspace_flatness_squared) { + float x01 = (x0+x1)/2; + float y01 = (y0+y1)/2; + float x12 = (x1+x2)/2; + float y12 = (y1+y2)/2; + float x23 = (x2+x3)/2; + float y23 = (y2+y3)/2; + + float xa = (x01+x12)/2; + float ya = (y01+y12)/2; + float xb = (x12+x23)/2; + float yb = (y12+y23)/2; + + float mx = (xa+xb)/2; + float my = (ya+yb)/2; + + stbtt__tesselate_cubic(points, num_points, x0,y0, x01,y01, xa,ya, mx,my, objspace_flatness_squared,n+1); + stbtt__tesselate_cubic(points, num_points, mx,my, xb,yb, x23,y23, x3,y3, objspace_flatness_squared,n+1); + } else { + stbtt__add_point(points, *num_points,x3,y3); + *num_points = *num_points+1; + } +} + +// returns number of contours +static stbtt__point *stbtt_FlattenCurves(stbtt_vertex *vertices, int num_verts, float objspace_flatness, int **contour_lengths, int *num_contours, void *userdata) +{ + stbtt__point *points=0; + int num_points=0; + + float objspace_flatness_squared = objspace_flatness * objspace_flatness; + int i,n=0,start=0, pass; + + // count how many "moves" there are to get the contour count + for (i=0; i < num_verts; ++i) + if (vertices[i].type == STBTT_vmove) + ++n; + + *num_contours = n; + if (n == 0) return 0; + + *contour_lengths = (int *) STBTT_malloc(sizeof(**contour_lengths) * n, userdata); + + if (*contour_lengths == 0) { + *num_contours = 0; + return 0; + } + + // make two passes through the points so we don't need to realloc + for (pass=0; pass < 2; ++pass) { + float x=0,y=0; + if (pass == 1) { + points = (stbtt__point *) STBTT_malloc(num_points * sizeof(points[0]), userdata); + if (points == NULL) goto error; + } + num_points = 0; + n= -1; + for (i=0; i < num_verts; ++i) { + switch (vertices[i].type) { + case STBTT_vmove: + // start the next contour + if (n >= 0) + (*contour_lengths)[n] = num_points - start; + ++n; + start = num_points; + + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x,y); + break; + case STBTT_vline: + x = vertices[i].x, y = vertices[i].y; + stbtt__add_point(points, num_points++, x, y); + break; + case STBTT_vcurve: + stbtt__tesselate_curve(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + case STBTT_vcubic: + stbtt__tesselate_cubic(points, &num_points, x,y, + vertices[i].cx, vertices[i].cy, + vertices[i].cx1, vertices[i].cy1, + vertices[i].x, vertices[i].y, + objspace_flatness_squared, 0); + x = vertices[i].x, y = vertices[i].y; + break; + } + } + (*contour_lengths)[n] = num_points - start; + } + + return points; + error: + STBTT_free(points, userdata); + STBTT_free(*contour_lengths, userdata); + *contour_lengths = 0; + *num_contours = 0; + return NULL; +} + +STBTT_DEF void stbtt_Rasterize(stbtt__bitmap *result, float flatness_in_pixels, stbtt_vertex *vertices, int num_verts, float scale_x, float scale_y, float shift_x, float shift_y, int x_off, int y_off, int invert, void *userdata) +{ + float scale = scale_x > scale_y ? scale_y : scale_x; + int winding_count = 0; + int *winding_lengths = NULL; + stbtt__point *windings = stbtt_FlattenCurves(vertices, num_verts, flatness_in_pixels / scale, &winding_lengths, &winding_count, userdata); + if (windings) { + stbtt__rasterize(result, windings, winding_lengths, winding_count, scale_x, scale_y, shift_x, shift_y, x_off, y_off, invert, userdata); + STBTT_free(winding_lengths, userdata); + STBTT_free(windings, userdata); + } +} + +STBTT_DEF void stbtt_FreeBitmap(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + int ix0,iy0,ix1,iy1; + stbtt__bitmap gbm; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) { + STBTT_free(vertices, info->userdata); + return NULL; + } + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,&ix1,&iy1); + + // now we get the size + gbm.w = (ix1 - ix0); + gbm.h = (iy1 - iy0); + gbm.pixels = NULL; // in case we error + + if (width ) *width = gbm.w; + if (height) *height = gbm.h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + if (gbm.w && gbm.h) { + gbm.pixels = (unsigned char *) STBTT_malloc(gbm.w * gbm.h, info->userdata); + if (gbm.pixels) { + gbm.stride = gbm.w; + + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0, iy0, 1, info->userdata); + } + } + STBTT_free(vertices, info->userdata); + return gbm.pixels; +} + +STBTT_DEF unsigned char *stbtt_GetGlyphBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int glyph, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y, 0.0f, 0.0f, glyph, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int glyph) +{ + int ix0,iy0; + stbtt_vertex *vertices; + int num_verts = stbtt_GetGlyphShape(info, glyph, &vertices); + stbtt__bitmap gbm; + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale_x, scale_y, shift_x, shift_y, &ix0,&iy0,0,0); + gbm.pixels = output; + gbm.w = out_w; + gbm.h = out_h; + gbm.stride = out_stride; + + if (gbm.w && gbm.h) + stbtt_Rasterize(&gbm, 0.35f, vertices, num_verts, scale_x, scale_y, shift_x, shift_y, ix0,iy0, 1, info->userdata); + + STBTT_free(vertices, info->userdata); +} + +STBTT_DEF void stbtt_MakeGlyphBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, glyph); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmapSubpixel(const stbtt_fontinfo *info, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphBitmapSubpixel(info, scale_x, scale_y,shift_x,shift_y, stbtt_FindGlyphIndex(info,codepoint), width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int oversample_x, int oversample_y, float *sub_x, float *sub_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixelPrefilter(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, oversample_x, oversample_y, sub_x, sub_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF void stbtt_MakeCodepointBitmapSubpixel(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int codepoint) +{ + stbtt_MakeGlyphBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, shift_x, shift_y, stbtt_FindGlyphIndex(info,codepoint)); +} + +STBTT_DEF unsigned char *stbtt_GetCodepointBitmap(const stbtt_fontinfo *info, float scale_x, float scale_y, int codepoint, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetCodepointBitmapSubpixel(info, scale_x, scale_y, 0.0f,0.0f, codepoint, width,height,xoff,yoff); +} + +STBTT_DEF void stbtt_MakeCodepointBitmap(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, int codepoint) +{ + stbtt_MakeCodepointBitmapSubpixel(info, output, out_w, out_h, out_stride, scale_x, scale_y, 0.0f,0.0f, codepoint); +} + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-CRAPPY packing to keep source code small + +static int stbtt_BakeFontBitmap_internal(unsigned char *data, int offset, // font location (use offset=0 for plain .ttf) + float pixel_height, // height of font in pixels + unsigned char *pixels, int pw, int ph, // bitmap to be filled in + int first_char, int num_chars, // characters to bake + stbtt_bakedchar *chardata) +{ + float scale; + int x,y,bottom_y, i; + stbtt_fontinfo f; + f.userdata = NULL; + if (!stbtt_InitFont(&f, data, offset)) + return -1; + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + x=y=1; + bottom_y = 1; + + scale = stbtt_ScaleForPixelHeight(&f, pixel_height); + + for (i=0; i < num_chars; ++i) { + int advance, lsb, x0,y0,x1,y1,gw,gh; + int g = stbtt_FindGlyphIndex(&f, first_char + i); + stbtt_GetGlyphHMetrics(&f, g, &advance, &lsb); + stbtt_GetGlyphBitmapBox(&f, g, scale,scale, &x0,&y0,&x1,&y1); + gw = x1-x0; + gh = y1-y0; + if (x + gw + 1 >= pw) + y = bottom_y, x = 1; // advance to next row + if (y + gh + 1 >= ph) // check if it fits vertically AFTER potentially moving to next row + return -i; + STBTT_assert(x+gw < pw); + STBTT_assert(y+gh < ph); + stbtt_MakeGlyphBitmap(&f, pixels+x+y*pw, gw,gh,pw, scale,scale, g); + chardata[i].x0 = (stbtt_int16) x; + chardata[i].y0 = (stbtt_int16) y; + chardata[i].x1 = (stbtt_int16) (x + gw); + chardata[i].y1 = (stbtt_int16) (y + gh); + chardata[i].xadvance = scale * advance; + chardata[i].xoff = (float) x0; + chardata[i].yoff = (float) y0; + x = x + gw + 1; + if (y+gh+1 > bottom_y) + bottom_y = y+gh+1; + } + return bottom_y; +} + +STBTT_DEF void stbtt_GetBakedQuad(const stbtt_bakedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int opengl_fillrule) +{ + float d3d_bias = opengl_fillrule ? 0 : -0.5f; + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_bakedchar *b = chardata + char_index; + int round_x = STBTT_ifloor((*xpos + b->xoff) + 0.5f); + int round_y = STBTT_ifloor((*ypos + b->yoff) + 0.5f); + + q->x0 = round_x + d3d_bias; + q->y0 = round_y + d3d_bias; + q->x1 = round_x + b->x1 - b->x0 + d3d_bias; + q->y1 = round_y + b->y1 - b->y0 + d3d_bias; + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// rectangle packing replacement routines if you don't have stb_rect_pack.h +// + +#ifndef STB_RECT_PACK_VERSION + +typedef int stbrp_coord; + +//////////////////////////////////////////////////////////////////////////////////// +// // +// // +// COMPILER WARNING ?!?!? // +// // +// // +// if you get a compile warning due to these symbols being defined more than // +// once, move #include "stb_rect_pack.h" before #include "stb_truetype.h" // +// // +//////////////////////////////////////////////////////////////////////////////////// + +typedef struct +{ + int width,height; + int x,y,bottom_y; +} stbrp_context; + +typedef struct +{ + unsigned char x; +} stbrp_node; + +struct stbrp_rect +{ + stbrp_coord x,y; + int id,w,h,was_packed; +}; + +static void stbrp_init_target(stbrp_context *con, int pw, int ph, stbrp_node *nodes, int num_nodes) +{ + con->width = pw; + con->height = ph; + con->x = 0; + con->y = 0; + con->bottom_y = 0; + STBTT__NOTUSED(nodes); + STBTT__NOTUSED(num_nodes); +} + +static void stbrp_pack_rects(stbrp_context *con, stbrp_rect *rects, int num_rects) +{ + int i; + for (i=0; i < num_rects; ++i) { + if (con->x + rects[i].w > con->width) { + con->x = 0; + con->y = con->bottom_y; + } + if (con->y + rects[i].h > con->height) + break; + rects[i].x = con->x; + rects[i].y = con->y; + rects[i].was_packed = 1; + con->x += rects[i].w; + if (con->y + rects[i].h > con->bottom_y) + con->bottom_y = con->y + rects[i].h; + } + for ( ; i < num_rects; ++i) + rects[i].was_packed = 0; +} +#endif + +////////////////////////////////////////////////////////////////////////////// +// +// bitmap baking +// +// This is SUPER-AWESOME (tm Ryan Gordon) packing using stb_rect_pack.h. If +// stb_rect_pack.h isn't available, it uses the BakeFontBitmap strategy. + +STBTT_DEF int stbtt_PackBegin(stbtt_pack_context *spc, unsigned char *pixels, int pw, int ph, int stride_in_bytes, int padding, void *alloc_context) +{ + stbrp_context *context = (stbrp_context *) STBTT_malloc(sizeof(*context) ,alloc_context); + int num_nodes = pw - padding; + stbrp_node *nodes = (stbrp_node *) STBTT_malloc(sizeof(*nodes ) * num_nodes,alloc_context); + + if (context == NULL || nodes == NULL) { + if (context != NULL) STBTT_free(context, alloc_context); + if (nodes != NULL) STBTT_free(nodes , alloc_context); + return 0; + } + + spc->user_allocator_context = alloc_context; + spc->width = pw; + spc->height = ph; + spc->pixels = pixels; + spc->pack_info = context; + spc->nodes = nodes; + spc->padding = padding; + spc->stride_in_bytes = stride_in_bytes != 0 ? stride_in_bytes : pw; + spc->h_oversample = 1; + spc->v_oversample = 1; + + stbrp_init_target(context, pw-padding, ph-padding, nodes, num_nodes); + + if (pixels) + STBTT_memset(pixels, 0, pw*ph); // background of 0 around pixels + + return 1; +} + +STBTT_DEF void stbtt_PackEnd (stbtt_pack_context *spc) +{ + STBTT_free(spc->nodes , spc->user_allocator_context); + STBTT_free(spc->pack_info, spc->user_allocator_context); +} + +STBTT_DEF void stbtt_PackSetOversampling(stbtt_pack_context *spc, unsigned int h_oversample, unsigned int v_oversample) +{ + STBTT_assert(h_oversample <= STBTT_MAX_OVERSAMPLE); + STBTT_assert(v_oversample <= STBTT_MAX_OVERSAMPLE); + if (h_oversample <= STBTT_MAX_OVERSAMPLE) + spc->h_oversample = h_oversample; + if (v_oversample <= STBTT_MAX_OVERSAMPLE) + spc->v_oversample = v_oversample; +} + +#define STBTT__OVER_MASK (STBTT_MAX_OVERSAMPLE-1) + +static void stbtt__h_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_w = w - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < h; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_w; ++i) { + total += pixels[i] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i]; + pixels[i] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < w; ++i) { + STBTT_assert(pixels[i] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i] = (unsigned char) (total / kernel_width); + } + + pixels += stride_in_bytes; + } +} + +static void stbtt__v_prefilter(unsigned char *pixels, int w, int h, int stride_in_bytes, unsigned int kernel_width) +{ + unsigned char buffer[STBTT_MAX_OVERSAMPLE]; + int safe_h = h - kernel_width; + int j; + STBTT_memset(buffer, 0, STBTT_MAX_OVERSAMPLE); // suppress bogus warning from VS2013 -analyze + for (j=0; j < w; ++j) { + int i; + unsigned int total; + STBTT_memset(buffer, 0, kernel_width); + + total = 0; + + // make kernel_width a constant in common cases so compiler can optimize out the divide + switch (kernel_width) { + case 2: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 2); + } + break; + case 3: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 3); + } + break; + case 4: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 4); + } + break; + case 5: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / 5); + } + break; + default: + for (i=0; i <= safe_h; ++i) { + total += pixels[i*stride_in_bytes] - buffer[i & STBTT__OVER_MASK]; + buffer[(i+kernel_width) & STBTT__OVER_MASK] = pixels[i*stride_in_bytes]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + break; + } + + for (; i < h; ++i) { + STBTT_assert(pixels[i*stride_in_bytes] == 0); + total -= buffer[i & STBTT__OVER_MASK]; + pixels[i*stride_in_bytes] = (unsigned char) (total / kernel_width); + } + + pixels += 1; + } +} + +static float stbtt__oversample_shift(int oversample) +{ + if (!oversample) + return 0.0f; + + // The prefilter is a box filter of width "oversample", + // which shifts phase by (oversample - 1)/2 pixels in + // oversampled space. We want to shift in the opposite + // direction to counter this. + return (float)-(oversample - 1) / (2.0f * (float)oversample); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesGatherRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k; + + k=0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + ranges[i].h_oversample = (unsigned char) spc->h_oversample; + ranges[i].v_oversample = (unsigned char) spc->v_oversample; + for (j=0; j < ranges[i].num_chars; ++j) { + int x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbtt_GetGlyphBitmapBoxSubpixel(info,glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + &x0,&y0,&x1,&y1); + rects[k].w = (stbrp_coord) (x1-x0 + spc->padding + spc->h_oversample-1); + rects[k].h = (stbrp_coord) (y1-y0 + spc->padding + spc->v_oversample-1); + ++k; + } + } + + return k; +} + +STBTT_DEF void stbtt_MakeGlyphBitmapSubpixelPrefilter(const stbtt_fontinfo *info, unsigned char *output, int out_w, int out_h, int out_stride, float scale_x, float scale_y, float shift_x, float shift_y, int prefilter_x, int prefilter_y, float *sub_x, float *sub_y, int glyph) +{ + stbtt_MakeGlyphBitmapSubpixel(info, + output, + out_w - (prefilter_x - 1), + out_h - (prefilter_y - 1), + out_stride, + scale_x, + scale_y, + shift_x, + shift_y, + glyph); + + if (prefilter_x > 1) + stbtt__h_prefilter(output, out_w, out_h, out_stride, prefilter_x); + + if (prefilter_y > 1) + stbtt__v_prefilter(output, out_w, out_h, out_stride, prefilter_y); + + *sub_x = stbtt__oversample_shift(prefilter_x); + *sub_y = stbtt__oversample_shift(prefilter_y); +} + +// rects array must be big enough to accommodate all characters in the given ranges +STBTT_DEF int stbtt_PackFontRangesRenderIntoRects(stbtt_pack_context *spc, const stbtt_fontinfo *info, stbtt_pack_range *ranges, int num_ranges, stbrp_rect *rects) +{ + int i,j,k, return_value = 1; + + // save current values + int old_h_over = spc->h_oversample; + int old_v_over = spc->v_oversample; + + k = 0; + for (i=0; i < num_ranges; ++i) { + float fh = ranges[i].font_size; + float scale = fh > 0 ? stbtt_ScaleForPixelHeight(info, fh) : stbtt_ScaleForMappingEmToPixels(info, -fh); + float recip_h,recip_v,sub_x,sub_y; + spc->h_oversample = ranges[i].h_oversample; + spc->v_oversample = ranges[i].v_oversample; + recip_h = 1.0f / spc->h_oversample; + recip_v = 1.0f / spc->v_oversample; + sub_x = stbtt__oversample_shift(spc->h_oversample); + sub_y = stbtt__oversample_shift(spc->v_oversample); + for (j=0; j < ranges[i].num_chars; ++j) { + stbrp_rect *r = &rects[k]; + if (r->was_packed) { + stbtt_packedchar *bc = &ranges[i].chardata_for_range[j]; + int advance, lsb, x0,y0,x1,y1; + int codepoint = ranges[i].array_of_unicode_codepoints == NULL ? ranges[i].first_unicode_codepoint_in_range + j : ranges[i].array_of_unicode_codepoints[j]; + int glyph = stbtt_FindGlyphIndex(info, codepoint); + stbrp_coord pad = (stbrp_coord) spc->padding; + + // pad on left and top + r->x += pad; + r->y += pad; + r->w -= pad; + r->h -= pad; + stbtt_GetGlyphHMetrics(info, glyph, &advance, &lsb); + stbtt_GetGlyphBitmapBox(info, glyph, + scale * spc->h_oversample, + scale * spc->v_oversample, + &x0,&y0,&x1,&y1); + stbtt_MakeGlyphBitmapSubpixel(info, + spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w - spc->h_oversample+1, + r->h - spc->v_oversample+1, + spc->stride_in_bytes, + scale * spc->h_oversample, + scale * spc->v_oversample, + 0,0, + glyph); + + if (spc->h_oversample > 1) + stbtt__h_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->h_oversample); + + if (spc->v_oversample > 1) + stbtt__v_prefilter(spc->pixels + r->x + r->y*spc->stride_in_bytes, + r->w, r->h, spc->stride_in_bytes, + spc->v_oversample); + + bc->x0 = (stbtt_int16) r->x; + bc->y0 = (stbtt_int16) r->y; + bc->x1 = (stbtt_int16) (r->x + r->w); + bc->y1 = (stbtt_int16) (r->y + r->h); + bc->xadvance = scale * advance; + bc->xoff = (float) x0 * recip_h + sub_x; + bc->yoff = (float) y0 * recip_v + sub_y; + bc->xoff2 = (x0 + r->w) * recip_h + sub_x; + bc->yoff2 = (y0 + r->h) * recip_v + sub_y; + } else { + return_value = 0; // if any fail, report failure + } + + ++k; + } + } + + // restore original values + spc->h_oversample = old_h_over; + spc->v_oversample = old_v_over; + + return return_value; +} + +STBTT_DEF void stbtt_PackFontRangesPackRects(stbtt_pack_context *spc, stbrp_rect *rects, int num_rects) +{ + stbrp_pack_rects((stbrp_context *) spc->pack_info, rects, num_rects); +} + +STBTT_DEF int stbtt_PackFontRanges(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, stbtt_pack_range *ranges, int num_ranges) +{ + stbtt_fontinfo info; + int i,j,n, return_value = 1; + //stbrp_context *context = (stbrp_context *) spc->pack_info; + stbrp_rect *rects; + + // flag all characters as NOT packed + for (i=0; i < num_ranges; ++i) + for (j=0; j < ranges[i].num_chars; ++j) + ranges[i].chardata_for_range[j].x0 = + ranges[i].chardata_for_range[j].y0 = + ranges[i].chardata_for_range[j].x1 = + ranges[i].chardata_for_range[j].y1 = 0; + + n = 0; + for (i=0; i < num_ranges; ++i) + n += ranges[i].num_chars; + + rects = (stbrp_rect *) STBTT_malloc(sizeof(*rects) * n, spc->user_allocator_context); + if (rects == NULL) + return 0; + + info.userdata = spc->user_allocator_context; + stbtt_InitFont(&info, fontdata, stbtt_GetFontOffsetForIndex(fontdata,font_index)); + + n = stbtt_PackFontRangesGatherRects(spc, &info, ranges, num_ranges, rects); + + stbtt_PackFontRangesPackRects(spc, rects, n); + + return_value = stbtt_PackFontRangesRenderIntoRects(spc, &info, ranges, num_ranges, rects); + + STBTT_free(rects, spc->user_allocator_context); + return return_value; +} + +STBTT_DEF int stbtt_PackFontRange(stbtt_pack_context *spc, const unsigned char *fontdata, int font_index, float font_size, + int first_unicode_codepoint_in_range, int num_chars_in_range, stbtt_packedchar *chardata_for_range) +{ + stbtt_pack_range range; + range.first_unicode_codepoint_in_range = first_unicode_codepoint_in_range; + range.array_of_unicode_codepoints = NULL; + range.num_chars = num_chars_in_range; + range.chardata_for_range = chardata_for_range; + range.font_size = font_size; + return stbtt_PackFontRanges(spc, fontdata, font_index, &range, 1); +} + +STBTT_DEF void stbtt_GetPackedQuad(const stbtt_packedchar *chardata, int pw, int ph, int char_index, float *xpos, float *ypos, stbtt_aligned_quad *q, int align_to_integer) +{ + float ipw = 1.0f / pw, iph = 1.0f / ph; + const stbtt_packedchar *b = chardata + char_index; + + if (align_to_integer) { + float x = (float) STBTT_ifloor((*xpos + b->xoff) + 0.5f); + float y = (float) STBTT_ifloor((*ypos + b->yoff) + 0.5f); + q->x0 = x; + q->y0 = y; + q->x1 = x + b->xoff2 - b->xoff; + q->y1 = y + b->yoff2 - b->yoff; + } else { + q->x0 = *xpos + b->xoff; + q->y0 = *ypos + b->yoff; + q->x1 = *xpos + b->xoff2; + q->y1 = *ypos + b->yoff2; + } + + q->s0 = b->x0 * ipw; + q->t0 = b->y0 * iph; + q->s1 = b->x1 * ipw; + q->t1 = b->y1 * iph; + + *xpos += b->xadvance; +} + +////////////////////////////////////////////////////////////////////////////// +// +// sdf computation +// + +#define STBTT_min(a,b) ((a) < (b) ? (a) : (b)) +#define STBTT_max(a,b) ((a) < (b) ? (b) : (a)) + +static int stbtt__ray_intersect_bezier(float orig[2], float ray[2], float q0[2], float q1[2], float q2[2], float hits[2][2]) +{ + float q0perp = q0[1]*ray[0] - q0[0]*ray[1]; + float q1perp = q1[1]*ray[0] - q1[0]*ray[1]; + float q2perp = q2[1]*ray[0] - q2[0]*ray[1]; + float roperp = orig[1]*ray[0] - orig[0]*ray[1]; + + float a = q0perp - 2*q1perp + q2perp; + float b = q1perp - q0perp; + float c = q0perp - roperp; + + float s0 = 0., s1 = 0.; + int num_s = 0; + + if (a != 0.0) { + float discr = b*b - a*c; + if (discr > 0.0) { + float rcpna = -1 / a; + float d = (float) STBTT_sqrt(discr); + s0 = (b+d) * rcpna; + s1 = (b-d) * rcpna; + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + if (d > 0.0 && s1 >= 0.0 && s1 <= 1.0) { + if (num_s == 0) s0 = s1; + ++num_s; + } + } + } else { + // 2*b*s + c = 0 + // s = -c / (2*b) + s0 = c / (-2 * b); + if (s0 >= 0.0 && s0 <= 1.0) + num_s = 1; + } + + if (num_s == 0) + return 0; + else { + float rcp_len2 = 1 / (ray[0]*ray[0] + ray[1]*ray[1]); + float rayn_x = ray[0] * rcp_len2, rayn_y = ray[1] * rcp_len2; + + float q0d = q0[0]*rayn_x + q0[1]*rayn_y; + float q1d = q1[0]*rayn_x + q1[1]*rayn_y; + float q2d = q2[0]*rayn_x + q2[1]*rayn_y; + float rod = orig[0]*rayn_x + orig[1]*rayn_y; + + float q10d = q1d - q0d; + float q20d = q2d - q0d; + float q0rd = q0d - rod; + + hits[0][0] = q0rd + s0*(2.0f - 2.0f*s0)*q10d + s0*s0*q20d; + hits[0][1] = a*s0+b; + + if (num_s > 1) { + hits[1][0] = q0rd + s1*(2.0f - 2.0f*s1)*q10d + s1*s1*q20d; + hits[1][1] = a*s1+b; + return 2; + } else { + return 1; + } + } +} + +static int equal(float *a, float *b) +{ + return (a[0] == b[0] && a[1] == b[1]); +} + +static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex *verts) +{ + int i; + float orig[2], ray[2] = { 1, 0 }; + float y_frac; + int winding = 0; + + orig[0] = x; + orig[1] = y; + + // make sure y never passes through a vertex of the shape + y_frac = (float) STBTT_fmod(y, 1.0f); + if (y_frac < 0.01f) + y += 0.01f; + else if (y_frac > 0.99f) + y -= 0.01f; + orig[1] = y; + + // test a ray from (-infinity,y) to (x,y) + for (i=0; i < nverts; ++i) { + if (verts[i].type == STBTT_vline) { + int x0 = (int) verts[i-1].x, y0 = (int) verts[i-1].y; + int x1 = (int) verts[i ].x, y1 = (int) verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } + if (verts[i].type == STBTT_vcurve) { + int x0 = (int) verts[i-1].x , y0 = (int) verts[i-1].y ; + int x1 = (int) verts[i ].cx, y1 = (int) verts[i ].cy; + int x2 = (int) verts[i ].x , y2 = (int) verts[i ].y ; + int ax = STBTT_min(x0,STBTT_min(x1,x2)), ay = STBTT_min(y0,STBTT_min(y1,y2)); + int by = STBTT_max(y0,STBTT_max(y1,y2)); + if (y > ay && y < by && x > ax) { + float q0[2],q1[2],q2[2]; + float hits[2][2]; + q0[0] = (float)x0; + q0[1] = (float)y0; + q1[0] = (float)x1; + q1[1] = (float)y1; + q2[0] = (float)x2; + q2[1] = (float)y2; + if (equal(q0,q1) || equal(q1,q2)) { + x0 = (int)verts[i-1].x; + y0 = (int)verts[i-1].y; + x1 = (int)verts[i ].x; + y1 = (int)verts[i ].y; + if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { + float x_inter = (y - y0) / (y1 - y0) * (x1-x0) + x0; + if (x_inter < x) + winding += (y0 < y1) ? 1 : -1; + } + } else { + int num_hits = stbtt__ray_intersect_bezier(orig, ray, q0, q1, q2, hits); + if (num_hits >= 1) + if (hits[0][0] < 0) + winding += (hits[0][1] < 0 ? -1 : 1); + if (num_hits >= 2) + if (hits[1][0] < 0) + winding += (hits[1][1] < 0 ? -1 : 1); + } + } + } + } + return winding; +} + +static float stbtt__cuberoot( float x ) +{ + if (x<0) + return -(float) STBTT_pow(-x,1.0f/3.0f); + else + return (float) STBTT_pow( x,1.0f/3.0f); +} + +// x^3 + c*x^2 + b*x + a = 0 +static int stbtt__solve_cubic(float a, float b, float c, float* r) +{ + float s = -a / 3; + float p = b - a*a / 3; + float q = a * (2*a*a - 9*b) / 27 + c; + float p3 = p*p*p; + float d = q*q + 4*p3 / 27; + if (d >= 0) { + float z = (float) STBTT_sqrt(d); + float u = (-q + z) / 2; + float v = (-q - z) / 2; + u = stbtt__cuberoot(u); + v = stbtt__cuberoot(v); + r[0] = s + u + v; + return 1; + } else { + float u = (float) STBTT_sqrt(-p/3); + float v = (float) STBTT_acos(-STBTT_sqrt(-27/p3) * q / 2) / 3; // p3 must be negative, since d is negative + float m = (float) STBTT_cos(v); + float n = (float) STBTT_cos(v-3.141592/2)*1.732050808f; + r[0] = s + u * 2 * m; + r[1] = s - u * (m + n); + r[2] = s - u * (m - n); + + //STBTT_assert( STBTT_fabs(((r[0]+a)*r[0]+b)*r[0]+c) < 0.05f); // these asserts may not be safe at all scales, though they're in bezier t parameter units so maybe? + //STBTT_assert( STBTT_fabs(((r[1]+a)*r[1]+b)*r[1]+c) < 0.05f); + //STBTT_assert( STBTT_fabs(((r[2]+a)*r[2]+b)*r[2]+c) < 0.05f); + return 3; + } +} + +STBTT_DEF unsigned char * stbtt_GetGlyphSDF(const stbtt_fontinfo *info, float scale, int glyph, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + float scale_x = scale, scale_y = scale; + int ix0,iy0,ix1,iy1; + int w,h; + unsigned char *data; + + // if one scale is 0, use same scale for both + if (scale_x == 0) scale_x = scale_y; + if (scale_y == 0) { + if (scale_x == 0) return NULL; // if both scales are 0, return NULL + scale_y = scale_x; + } + + stbtt_GetGlyphBitmapBoxSubpixel(info, glyph, scale, scale, 0.0f,0.0f, &ix0,&iy0,&ix1,&iy1); + + // if empty, return NULL + if (ix0 == ix1 || iy0 == iy1) + return NULL; + + ix0 -= padding; + iy0 -= padding; + ix1 += padding; + iy1 += padding; + + w = (ix1 - ix0); + h = (iy1 - iy0); + + if (width ) *width = w; + if (height) *height = h; + if (xoff ) *xoff = ix0; + if (yoff ) *yoff = iy0; + + // invert for y-downwards bitmaps + scale_y = -scale_y; + + { + int x,y,i,j; + float *precompute; + stbtt_vertex *verts; + int num_verts = stbtt_GetGlyphShape(info, glyph, &verts); + data = (unsigned char *) STBTT_malloc(w * h, info->userdata); + precompute = (float *) STBTT_malloc(num_verts * sizeof(float), info->userdata); + + for (i=0,j=num_verts-1; i < num_verts; j=i++) { + if (verts[i].type == STBTT_vline) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + float x1 = verts[j].x*scale_x, y1 = verts[j].y*scale_y; + float dist = (float) STBTT_sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0)); + precompute[i] = (dist == 0) ? 0.0f : 1.0f / dist; + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[j].x *scale_x, y2 = verts[j].y *scale_y; + float x1 = verts[i].cx*scale_x, y1 = verts[i].cy*scale_y; + float x0 = verts[i].x *scale_x, y0 = verts[i].y *scale_y; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float len2 = bx*bx + by*by; + if (len2 != 0.0f) + precompute[i] = 1.0f / (bx*bx + by*by); + else + precompute[i] = 0.0f; + } else + precompute[i] = 0.0f; + } + + for (y=iy0; y < iy1; ++y) { + for (x=ix0; x < ix1; ++x) { + float val; + float min_dist = 999999.0f; + float sx = (float) x + 0.5f; + float sy = (float) y + 0.5f; + float x_gspace = (sx / scale_x); + float y_gspace = (sy / scale_y); + + int winding = stbtt__compute_crossings_x(x_gspace, y_gspace, num_verts, verts); // @OPTIMIZE: this could just be a rasterization, but needs to be line vs. non-tesselated curves so a new path + + for (i=0; i < num_verts; ++i) { + float x0 = verts[i].x*scale_x, y0 = verts[i].y*scale_y; + + // check against every point here rather than inside line/curve primitives -- @TODO: wrong if multiple 'moves' in a row produce a garbage point, and given culling, probably more efficient to do within line/curve + float dist2 = (x0-sx)*(x0-sx) + (y0-sy)*(y0-sy); + if (dist2 < min_dist*min_dist) + min_dist = (float) STBTT_sqrt(dist2); + + if (verts[i].type == STBTT_vline) { + float x1 = verts[i-1].x*scale_x, y1 = verts[i-1].y*scale_y; + + // coarse culling against bbox + //if (sx > STBTT_min(x0,x1)-min_dist && sx < STBTT_max(x0,x1)+min_dist && + // sy > STBTT_min(y0,y1)-min_dist && sy < STBTT_max(y0,y1)+min_dist) + float dist = (float) STBTT_fabs((x1-x0)*(y0-sy) - (y1-y0)*(x0-sx)) * precompute[i]; + STBTT_assert(i != 0); + if (dist < min_dist) { + // check position along line + // x' = x0 + t*(x1-x0), y' = y0 + t*(y1-y0) + // minimize (x'-sx)*(x'-sx)+(y'-sy)*(y'-sy) + float dx = x1-x0, dy = y1-y0; + float px = x0-sx, py = y0-sy; + // minimize (px+t*dx)^2 + (py+t*dy)^2 = px*px + 2*px*dx*t + t^2*dx*dx + py*py + 2*py*dy*t + t^2*dy*dy + // derivative: 2*px*dx + 2*py*dy + (2*dx*dx+2*dy*dy)*t, set to 0 and solve + float t = -(px*dx + py*dy) / (dx*dx + dy*dy); + if (t >= 0.0f && t <= 1.0f) + min_dist = dist; + } + } else if (verts[i].type == STBTT_vcurve) { + float x2 = verts[i-1].x *scale_x, y2 = verts[i-1].y *scale_y; + float x1 = verts[i ].cx*scale_x, y1 = verts[i ].cy*scale_y; + float box_x0 = STBTT_min(STBTT_min(x0,x1),x2); + float box_y0 = STBTT_min(STBTT_min(y0,y1),y2); + float box_x1 = STBTT_max(STBTT_max(x0,x1),x2); + float box_y1 = STBTT_max(STBTT_max(y0,y1),y2); + // coarse culling against bbox to avoid computing cubic unnecessarily + if (sx > box_x0-min_dist && sx < box_x1+min_dist && sy > box_y0-min_dist && sy < box_y1+min_dist) { + int num=0; + float ax = x1-x0, ay = y1-y0; + float bx = x0 - 2*x1 + x2, by = y0 - 2*y1 + y2; + float mx = x0 - sx, my = y0 - sy; + float res[3],px,py,t,it; + float a_inv = precompute[i]; + if (a_inv == 0.0) { // if a_inv is 0, it's 2nd degree so use quadratic formula + float a = 3*(ax*bx + ay*by); + float b = 2*(ax*ax + ay*ay) + (mx*bx+my*by); + float c = mx*ax+my*ay; + if (a == 0.0) { // if a is 0, it's linear + if (b != 0.0) { + res[num++] = -c/b; + } + } else { + float discriminant = b*b - 4*a*c; + if (discriminant < 0) + num = 0; + else { + float root = (float) STBTT_sqrt(discriminant); + res[0] = (-b - root)/(2*a); + res[1] = (-b + root)/(2*a); + num = 2; // don't bother distinguishing 1-solution case, as code below will still work + } + } + } else { + float b = 3*(ax*bx + ay*by) * a_inv; // could precompute this as it doesn't depend on sample point + float c = (2*(ax*ax + ay*ay) + (mx*bx+my*by)) * a_inv; + float d = (mx*ax+my*ay) * a_inv; + num = stbtt__solve_cubic(b, c, d, res); + } + if (num >= 1 && res[0] >= 0.0f && res[0] <= 1.0f) { + t = res[0], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 2 && res[1] >= 0.0f && res[1] <= 1.0f) { + t = res[1], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + if (num >= 3 && res[2] >= 0.0f && res[2] <= 1.0f) { + t = res[2], it = 1.0f - t; + px = it*it*x0 + 2*t*it*x1 + t*t*x2; + py = it*it*y0 + 2*t*it*y1 + t*t*y2; + dist2 = (px-sx)*(px-sx) + (py-sy)*(py-sy); + if (dist2 < min_dist * min_dist) + min_dist = (float) STBTT_sqrt(dist2); + } + } + } + } + if (winding == 0) + min_dist = -min_dist; // if outside the shape, value is negative + val = onedge_value + pixel_dist_scale * min_dist; + if (val < 0) + val = 0; + else if (val > 255) + val = 255; + data[(y-iy0)*w+(x-ix0)] = (unsigned char) val; + } + } + STBTT_free(precompute, info->userdata); + STBTT_free(verts, info->userdata); + } + return data; +} + +STBTT_DEF unsigned char * stbtt_GetCodepointSDF(const stbtt_fontinfo *info, float scale, int codepoint, int padding, unsigned char onedge_value, float pixel_dist_scale, int *width, int *height, int *xoff, int *yoff) +{ + return stbtt_GetGlyphSDF(info, scale, stbtt_FindGlyphIndex(info, codepoint), padding, onedge_value, pixel_dist_scale, width, height, xoff, yoff); +} + +STBTT_DEF void stbtt_FreeSDF(unsigned char *bitmap, void *userdata) +{ + STBTT_free(bitmap, userdata); +} + +////////////////////////////////////////////////////////////////////////////// +// +// font name matching -- recommended not to use this +// + +// check if a utf8 string contains a prefix which is the utf16 string; if so return length of matching utf8 string +static stbtt_int32 stbtt__CompareUTF8toUTF16_bigendian_prefix(stbtt_uint8 *s1, stbtt_int32 len1, stbtt_uint8 *s2, stbtt_int32 len2) +{ + stbtt_int32 i=0; + + // convert utf16 to utf8 and compare the results while converting + while (len2) { + stbtt_uint16 ch = s2[0]*256 + s2[1]; + if (ch < 0x80) { + if (i >= len1) return -1; + if (s1[i++] != ch) return -1; + } else if (ch < 0x800) { + if (i+1 >= len1) return -1; + if (s1[i++] != 0xc0 + (ch >> 6)) return -1; + if (s1[i++] != 0x80 + (ch & 0x3f)) return -1; + } else if (ch >= 0xd800 && ch < 0xdc00) { + stbtt_uint32 c; + stbtt_uint16 ch2 = s2[2]*256 + s2[3]; + if (i+3 >= len1) return -1; + c = ((ch - 0xd800) << 10) + (ch2 - 0xdc00) + 0x10000; + if (s1[i++] != 0xf0 + (c >> 18)) return -1; + if (s1[i++] != 0x80 + ((c >> 12) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((c ) & 0x3f)) return -1; + s2 += 2; // plus another 2 below + len2 -= 2; + } else if (ch >= 0xdc00 && ch < 0xe000) { + return -1; + } else { + if (i+2 >= len1) return -1; + if (s1[i++] != 0xe0 + (ch >> 12)) return -1; + if (s1[i++] != 0x80 + ((ch >> 6) & 0x3f)) return -1; + if (s1[i++] != 0x80 + ((ch ) & 0x3f)) return -1; + } + s2 += 2; + len2 -= 2; + } + return i; +} + +static int stbtt_CompareUTF8toUTF16_bigendian_internal(char *s1, int len1, char *s2, int len2) +{ + return len1 == stbtt__CompareUTF8toUTF16_bigendian_prefix((stbtt_uint8*) s1, len1, (stbtt_uint8*) s2, len2); +} + +// returns results in whatever encoding you request... but note that 2-byte encodings +// will be BIG-ENDIAN... use stbtt_CompareUTF8toUTF16_bigendian() to compare +STBTT_DEF const char *stbtt_GetFontNameString(const stbtt_fontinfo *font, int *length, int platformID, int encodingID, int languageID, int nameID) +{ + stbtt_int32 i,count,stringOffset; + stbtt_uint8 *fc = font->data; + stbtt_uint32 offset = font->fontstart; + stbtt_uint32 nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return NULL; + + count = ttUSHORT(fc+nm+2); + stringOffset = nm + ttUSHORT(fc+nm+4); + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + if (platformID == ttUSHORT(fc+loc+0) && encodingID == ttUSHORT(fc+loc+2) + && languageID == ttUSHORT(fc+loc+4) && nameID == ttUSHORT(fc+loc+6)) { + *length = ttUSHORT(fc+loc+8); + return (const char *) (fc+stringOffset+ttUSHORT(fc+loc+10)); + } + } + return NULL; +} + +static int stbtt__matchpair(stbtt_uint8 *fc, stbtt_uint32 nm, stbtt_uint8 *name, stbtt_int32 nlen, stbtt_int32 target_id, stbtt_int32 next_id) +{ + stbtt_int32 i; + stbtt_int32 count = ttUSHORT(fc+nm+2); + stbtt_int32 stringOffset = nm + ttUSHORT(fc+nm+4); + + for (i=0; i < count; ++i) { + stbtt_uint32 loc = nm + 6 + 12 * i; + stbtt_int32 id = ttUSHORT(fc+loc+6); + if (id == target_id) { + // find the encoding + stbtt_int32 platform = ttUSHORT(fc+loc+0), encoding = ttUSHORT(fc+loc+2), language = ttUSHORT(fc+loc+4); + + // is this a Unicode encoding? + if (platform == 0 || (platform == 3 && encoding == 1) || (platform == 3 && encoding == 10)) { + stbtt_int32 slen = ttUSHORT(fc+loc+8); + stbtt_int32 off = ttUSHORT(fc+loc+10); + + // check if there's a prefix match + stbtt_int32 matchlen = stbtt__CompareUTF8toUTF16_bigendian_prefix(name, nlen, fc+stringOffset+off,slen); + if (matchlen >= 0) { + // check for target_id+1 immediately following, with same encoding & language + if (i+1 < count && ttUSHORT(fc+loc+12+6) == next_id && ttUSHORT(fc+loc+12) == platform && ttUSHORT(fc+loc+12+2) == encoding && ttUSHORT(fc+loc+12+4) == language) { + slen = ttUSHORT(fc+loc+12+8); + off = ttUSHORT(fc+loc+12+10); + if (slen == 0) { + if (matchlen == nlen) + return 1; + } else if (matchlen < nlen && name[matchlen] == ' ') { + ++matchlen; + if (stbtt_CompareUTF8toUTF16_bigendian_internal((char*) (name+matchlen), nlen-matchlen, (char*)(fc+stringOffset+off),slen)) + return 1; + } + } else { + // if nothing immediately following + if (matchlen == nlen) + return 1; + } + } + } + + // @TODO handle other encodings + } + } + return 0; +} + +static int stbtt__matches(stbtt_uint8 *fc, stbtt_uint32 offset, stbtt_uint8 *name, stbtt_int32 flags) +{ + stbtt_int32 nlen = (stbtt_int32) STBTT_strlen((char *) name); + stbtt_uint32 nm,hd; + if (!stbtt__isfont(fc+offset)) return 0; + + // check italics/bold/underline flags in macStyle... + if (flags) { + hd = stbtt__find_table(fc, offset, "head"); + if ((ttUSHORT(fc+hd+44) & 7) != (flags & 7)) return 0; + } + + nm = stbtt__find_table(fc, offset, "name"); + if (!nm) return 0; + + if (flags) { + // if we checked the macStyle flags, then just check the family and ignore the subfamily + if (stbtt__matchpair(fc, nm, name, nlen, 16, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, -1)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } else { + if (stbtt__matchpair(fc, nm, name, nlen, 16, 17)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 1, 2)) return 1; + if (stbtt__matchpair(fc, nm, name, nlen, 3, -1)) return 1; + } + + return 0; +} + +static int stbtt_FindMatchingFont_internal(unsigned char *font_collection, char *name_utf8, stbtt_int32 flags) +{ + stbtt_int32 i; + for (i=0;;++i) { + stbtt_int32 off = stbtt_GetFontOffsetForIndex(font_collection, i); + if (off < 0) return off; + if (stbtt__matches((stbtt_uint8 *) font_collection, off, (stbtt_uint8*) name_utf8, flags)) + return off; + } +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif + +STBTT_DEF int stbtt_BakeFontBitmap(const unsigned char *data, int offset, + float pixel_height, unsigned char *pixels, int pw, int ph, + int first_char, int num_chars, stbtt_bakedchar *chardata) +{ + return stbtt_BakeFontBitmap_internal((unsigned char *) data, offset, pixel_height, pixels, pw, ph, first_char, num_chars, chardata); +} + +STBTT_DEF int stbtt_GetFontOffsetForIndex(const unsigned char *data, int index) +{ + return stbtt_GetFontOffsetForIndex_internal((unsigned char *) data, index); +} + +STBTT_DEF int stbtt_GetNumberOfFonts(const unsigned char *data) +{ + return stbtt_GetNumberOfFonts_internal((unsigned char *) data); +} + +STBTT_DEF int stbtt_InitFont(stbtt_fontinfo *info, const unsigned char *data, int offset) +{ + return stbtt_InitFont_internal(info, (unsigned char *) data, offset); +} + +STBTT_DEF int stbtt_FindMatchingFont(const unsigned char *fontdata, const char *name, int flags) +{ + return stbtt_FindMatchingFont_internal((unsigned char *) fontdata, (char *) name, flags); +} + +STBTT_DEF int stbtt_CompareUTF8toUTF16_bigendian(const char *s1, int len1, const char *s2, int len2) +{ + return stbtt_CompareUTF8toUTF16_bigendian_internal((char *) s1, len1, (char *) s2, len2); +} + +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC diagnostic pop +#endif + +#endif // STB_TRUETYPE_IMPLEMENTATION + + +// FULL VERSION HISTORY +// +// 1.19 (2018-02-11) OpenType GPOS kerning (horizontal only), STBTT_fmod +// 1.18 (2018-01-29) add missing function +// 1.17 (2017-07-23) make more arguments const; doc fix +// 1.16 (2017-07-12) SDF support +// 1.15 (2017-03-03) make more arguments const +// 1.14 (2017-01-16) num-fonts-in-TTC function +// 1.13 (2017-01-02) support OpenType fonts, certain Apple fonts +// 1.12 (2016-10-25) suppress warnings about casting away const with -Wcast-qual +// 1.11 (2016-04-02) fix unused-variable warning +// 1.10 (2016-04-02) allow user-defined fabs() replacement +// fix memory leak if fontsize=0.0 +// fix warning from duplicate typedef +// 1.09 (2016-01-16) warning fix; avoid crash on outofmem; use alloc userdata for PackFontRanges +// 1.08 (2015-09-13) document stbtt_Rasterize(); fixes for vertical & horizontal edges +// 1.07 (2015-08-01) allow PackFontRanges to accept arrays of sparse codepoints; +// allow PackFontRanges to pack and render in separate phases; +// fix stbtt_GetFontOFfsetForIndex (never worked for non-0 input?); +// fixed an assert() bug in the new rasterizer +// replace assert() with STBTT_assert() in new rasterizer +// 1.06 (2015-07-14) performance improvements (~35% faster on x86 and x64 on test machine) +// also more precise AA rasterizer, except if shapes overlap +// remove need for STBTT_sort +// 1.05 (2015-04-15) fix misplaced definitions for STBTT_STATIC +// 1.04 (2015-04-15) typo in example +// 1.03 (2015-04-12) STBTT_STATIC, fix memory leak in new packing, various fixes +// 1.02 (2014-12-10) fix various warnings & compile issues w/ stb_rect_pack, C++ +// 1.01 (2014-12-08) fix subpixel position when oversampling to exactly match +// non-oversampled; STBTT_POINT_SIZE for packed case only +// 1.00 (2014-12-06) add new PackBegin etc. API, w/ support for oversampling +// 0.99 (2014-09-18) fix multiple bugs with subpixel rendering (ryg) +// 0.9 (2014-08-07) support certain mac/iOS fonts without an MS platformID +// 0.8b (2014-07-07) fix a warning +// 0.8 (2014-05-25) fix a few more warnings +// 0.7 (2013-09-25) bugfix: subpixel glyph bug fixed in 0.5 had come back +// 0.6c (2012-07-24) improve documentation +// 0.6b (2012-07-20) fix a few more warnings +// 0.6 (2012-07-17) fix warnings; added stbtt_ScaleForMappingEmToPixels, +// stbtt_GetFontBoundingBox, stbtt_IsGlyphEmpty +// 0.5 (2011-12-09) bugfixes: +// subpixel glyph renderer computed wrong bounding box +// first vertex of shape can be off-curve (FreeSans) +// 0.4b (2011-12-03) fixed an error in the font baking example +// 0.4 (2011-12-01) kerning, subpixel rendering (tor) +// bugfixes for: +// codepoint-to-glyph conversion using table fmt=12 +// codepoint-to-glyph conversion using table fmt=4 +// stbtt_GetBakedQuad with non-square texture (Zer) +// updated Hello World! sample to use kerning and subpixel +// fixed some warnings +// 0.3 (2009-06-24) cmap fmt=12, compound shapes (MM) +// userdata, malloc-from-userdata, non-zero fill (stb) +// 0.2 (2009-03-11) Fix unsigned/signed char warnings +// 0.1 (2009-03-09) First public release +// + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git a/test_patterns.h b/test_patterns.h new file mode 100644 index 0000000..7a4671b --- /dev/null +++ b/test_patterns.h @@ -0,0 +1,196 @@ +NODE_STRUCT(float_value_data) +{ + NODE_IN(r32, Value); + NODE_OUT(r32, Result); +}; + +NODE_PROC(FloatValueProc, float_value_data) +{ + Data->Result = Data->Value; +} + +NODE_STRUCT(solid_color_data) +{ + NODE_IN(v4, Color); + NODE_COLOR_BUFFER_INOUT; +}; + +NODE_PROC(SolidColorProc, solid_color_data) +{ + u8 R = (u8)(Data->Color.r * 255); + u8 G = (u8)(Data->Color.g * 255); + u8 B = (u8)(Data->Color.b * 255); + + led* LED = Data->LEDs; + for (s32 l = 0; l < Data->LEDCount; l++) + { + Assert(LED->Index >= 0 && LED->Index < Data->LEDCount); + + Data->Colors[LED->Index].R = R; + Data->Colors[LED->Index].G = G; + Data->Colors[LED->Index].B = B; + LED++; + } +} + +NODE_STRUCT(multiply_patterns_data) +{ + NODE_COLOR_BUFFER_IN(A); + NODE_COLOR_BUFFER_IN(B); + NODE_COLOR_BUFFER_OUT(Result); +}; + +NODE_PROC(MultiplyPatterns, multiply_patterns_data) +{ + led* LED = Data->ResultLEDs; + for (s32 l = 0; l < Data->ResultLEDCount; l++) + { + Assert(LED->Index >= 0 && LED->Index < Data->ResultLEDCount); + + Data->ResultColors[LED->Index].R = (Data->AColors[LED->Index].R + Data->BColors[LED->Index].R) / 2; + Data->ResultColors[LED->Index].G = (Data->AColors[LED->Index].G + Data->BColors[LED->Index].G) / 2; + Data->ResultColors[LED->Index].B = (Data->AColors[LED->Index].B + Data->BColors[LED->Index].B) / 2; + LED++; + } +} + + +NODE_PATTERN_STRUCT(vertical_color_fade_data) +{ + NODE_IN(v4, Color); + NODE_IN(r32, Min); + NODE_IN(r32, Max); +}; + +NODE_PATTERN_PROC(VerticalColorFadeProc, vertical_color_fade_data) +{ + r32 R = (Data->Color.r * 255); + r32 G = (Data->Color.g * 255); + r32 B = (Data->Color.b * 255); + + r32 Range = Data->Max - Data->Min; + + led* LED = LEDs; + for (s32 l = 0; l < LEDCount; l++) + { + r32 Amount = (LED->Position.y - Data->Min) / Range; + Amount = GSClamp01(1.0f - Amount); + + Colors[LED->Index].R = (u8)(R * Amount); + Colors[LED->Index].G = (u8)(G * Amount); + Colors[LED->Index].B = (u8)(B * Amount); + LED++; + } +} + +// ^^^ New ^^^ +// vvv Old vvv + +PATTERN_INIT_PROC(SolidPatternInitProc) +{ + Pattern->Memory = (void*)PushArray(Storage, u8, 3); + + u8* Color = (u8*)Pattern->Memory; + Color[0] = 0; + Color[1] = 0; + Color[2] = 128; +} + +PATTERN_UPDATE_PROC(SolidPatternUpdateProc) +{ + u8* Color = (u8*)Memory; + u8 R = Color[0]; + u8 G = Color[1]; + u8 B = Color[2]; + + led* LED = LEDs; + for (s32 l = 0; l < LEDCount; l++) + { + PushColor(LED++, Colors, R, G, B); + } +} + +struct rainbow_pattern_memory +{ + r32 TimeAccumulator; + r32 Period; +}; + +PATTERN_INIT_PROC(InitRainbowPatternProc) +{ + Pattern->Memory = (void*)PushStruct(Storage, rainbow_pattern_memory); + rainbow_pattern_memory* Mem = (rainbow_pattern_memory*)Pattern->Memory; + Mem->TimeAccumulator = 0; + Mem->Period = 6.0f; +} + +PATTERN_UPDATE_PROC(RainbowPatternProc) +{ + DEBUG_TRACK_SCOPE(RainbowPatternProc); + + rainbow_pattern_memory* Mem = (rainbow_pattern_memory*)Memory; + Mem->TimeAccumulator += DeltaTime; + if (Mem->TimeAccumulator >= Mem->Period) + { + Mem->TimeAccumulator -= Mem->Period; + } + + r32 Percent = Mem->TimeAccumulator / Mem->Period; + r32 HueAdd = Percent * 360.0f; + + r32 HueScale = 360.0f / 100; + + led* LED = LEDs; + for (s32 l = 0; l < LEDCount; l++) + { + r32 Hue = (LED->Position.y * HueScale) + HueAdd; + v4 Color = HSVToRGB(v4{Hue, 1, 1, 1}) * .75f; + + PushColor(LED++, Colors, (u8)(Color.r * 255), (u8)(Color.g * 255), (u8)(Color.b * 255)); + } +} + +PATTERN_INIT_PROC(InitRadialProc) +{ + Pattern->Memory = (void*)PushStruct(Storage, rainbow_pattern_memory); + rainbow_pattern_memory* Mem = (rainbow_pattern_memory*)Pattern->Memory; + Mem->TimeAccumulator = 0; + Mem->Period = 10.0; +} + +PATTERN_UPDATE_PROC(UpdateRadialProc) +{ + + rainbow_pattern_memory* Mem = (rainbow_pattern_memory*)Memory; + Mem->TimeAccumulator += DeltaTime; + if (Mem->TimeAccumulator >= Mem->Period) + { + Mem->TimeAccumulator -= Mem->Period; + } + + r32 Percent = Mem->TimeAccumulator / Mem->Period; + r32 AngleAdd = Percent * PI * 2; + r32 HueAdd = Percent * 360; + + v2 DirectionVector = v2{GSSin(AngleAdd), GSCos(AngleAdd)}; + + led* LED = LEDs; + for (s32 l = 0; l < LEDCount; l++) + { + v4 Color = {0, 0, 0, 1}; + + if (LED->Position.y >= 70) + { + v2 TwoDPos = v2{LED->Position.x, LED->Position.z}; + r32 Angle = Dot(Normalize(TwoDPos), DirectionVector) * .25f; + r32 Hue = Angle * 360 + HueAdd; + Color = HSVToRGB(v4{Hue, 1, 1, 1}) * .9f; + } + else + { + Color = HSVToRGB(v4{HueAdd, 1, 1, 1}) * .9f; + } + + PushColor(LED++, Colors, (u8)(Color.r * 255), (u8)(Color.g * 255), (u8)(Color.b * 255)); + } +} \ No newline at end of file diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..d2c9353 --- /dev/null +++ b/todo.txt @@ -0,0 +1,222 @@ +TODO FOLDHAUS + +Pick Up Where You Left Off +- In a node, you aren't filling in all the LED lists. Some of them (the outputs) are empty +when you reach the pattern proc because theyre outputs. +SOLUTION: Probably want to take the led lists out of each set of colors and just pass them in +once per data struct. + + + + +Name +x lumen lab +- Splash screen (like blender) (thisll be fun) +- x Create Image +- - Image importer (stb image? or find a png > bmp converter for the image you have) +- - Display on startup + +/Debug +x There's an extra tri/quad sometimes when the renderer starts +x Something is still happening when I reload a dll, it sometimes has an error. +x Fixing the debug system seems to have solved this too. Keep an eye out tho +x Make debug scope tracking thread safe - was throwing an error in stringsequal but that stopped. +x Keep an eye out. + +Application +x File Browsing to load sculptures +x Different Memory Layouts +- x General Purpose - ideally growable but shouldn't exhibit any size change outside of like 1000x sculptures, for assets etc. +- x SACN - growable, in its own memory. This is a central system. +- x Sculpture - per scultpure, monolithic memory allocation +- x track each sculpture in general purpose memory, linked list +- x need to move the assemblies structs back up the list when I remove an earlier one (ie. remove 0, shift 1 to 0, etc.); +- More efficient HSV <-> RGB + +x Load/Unload sculptures +- x Load Function +- x Unload Function +- x Call From Interface +- - Make sure that we offload unloading until after all jobs are completed. Otherwise they'll try and write +- to data that doesn't exist +- Save and load a session +- - Serialize Channels +- - Serialize Patterns +- Don't render if the window isn't visible + +Development +x Reloadable DLL +x Make sure Debug Info Isn't Lost When We Reload the DLL +- Fix your scope time tracker to account for threads. +- Nest scope times so you can see totals/dig in +x Darken the area behind debug text so you can see it. +- Log memory allocations + +Data Output +x output the data (check but I think we're doing this) +x Universe view - pannable grid of universes with labels + +Interface +x pattern controls +- fullscreen +- In world interface elements +- - Handles for Patterns +- - UI Popups +- - Value modifiers +- Scroll view +- Update the text system - use system fonts + +Switch To Nodes +x basic node elements +- x ports (expected value) (in/out) +- x display value +- x connections +- - evaluation nodes (nodes that we start evaluation from) +- - evaluation step (one node at a time) +- - process to execute +x reflection +- x look at struct members and figure out how to draw it (see notes at bottom) +- x associate with a process +x draw nodes on canvas +x interact +- x move around +- x reconnect +- x move connections handles (see casey) +- serialize +- delete nodes +- need a way to add a timer to the nodes system (oscillators) +- probably want to build some build-in nodes so they can have custom behavior +- - static value: color, float, int etc +- - oscillators +- - hue range + + +Patterns +x arbitrary assemblies + +x reload at runtime +- load patterns from a separate dll +x pattern initialization +- multiple active patterns +- - pattern blending +- - only update active patterns +- Parameters + +Structure +x load structure from a file +x generate arbitrary assemblies +- multiple assemblies +- motion +x reasses if control boxes are even a necessary structure + +Renderer +x Push Buffer Renderer +x Get Headers out of the command structs +x Render Text Batch Command +x Render Quads Batch Command +x vertex buffer +x depth sorting +- Mouse Picking - point at a led and see info about it +x Camera: tumble controls +- Camera: pan +- Camera: zoom +- Camera: leds always face camera + +Resource Management +x Manually Load/Unload Textures +- TODO: Need to figure out which textures are currently in graphics memory and which need to be resubmitted +- Icons + +Animation +- timeline +- create clips that play +- clips can have parameters that drive them? +- clips should have prerequesites +- - channels active +- - patterns active in the channel +- - when a clip is playing, it should just take over the whole structure + +Command Line +- select a channel/pattern +- Channel: Add/remove pattern by name +- Channel: Set Blend Mode +- Channel: Set Current Pattern +- Pattern: Edit parameter values + +Optimization +- patterns are asking to be multithreaded +- probably want to convert as much color functions to use u32 Packed Colors +- - Probably want to think about this more. What about supporting different color depths +- for different output devices? + + + +NOTES + +Reflection for Nodes + +// These used for the reflection system +#define CANVAS_STRUCT(name) struct name +#define CANVAS_INPUT(type, name) type name +#define CANVAS_OUTPUT(type, name) type name + +CANVAS_STRUCT(multiply_data) +{ + CANVAS_INPUT(r32, A); + CANVAS_INPUT(r32, B); + CANVAS_OUTPUT(r32, C); +} + +CANVAS_PROC(multiply_proc) +{ + multiply_data* Data = (multiply_data*)Data; + Data->C = Data->A * Data->B; +} + +node +{ + InputCount 2 + node_port_values* Inputs; + + OutputCount 1 + node_port_values* Outputs; +} + +node_port_value +{ + type NodeInputType_r32 + s32 MemoryOffsetFromHead; + s32 DataSize; + + r32 UnconnectedValue; + r32* ConnectedValue; // Overrides Unconnected +} + +u8* GetValueAddress (node_port_value* Port) +{ + u8* Result = &Port->UnconnectedValue; + if (Port->ConnectedValue) + { + Result = Port->ConnectedValue; + } + return Result; +} + +void UpdateCanvasElement (u8* Data, s32 DataSize, node* Node) +{ + for (s32 i = 0; i < Node->InputCount; i++) + { + GSMemCopy(GetValueAddress(&Node->Input[i]), + Data + Node->Input[i].MemoryOffsetFromHead, + Node->Input[i].DataSize); + } +} + +void InitializeMultiplyNode () +{ + node Multiply = {}; + Multiply.InputCount = 2; + Alloc Inputs + + Input[0]. +} \ No newline at end of file diff --git a/win32_foldhaus.cpp b/win32_foldhaus.cpp new file mode 100644 index 0000000..1dd9f51 --- /dev/null +++ b/win32_foldhaus.cpp @@ -0,0 +1,548 @@ +#include +#include +#include + +#include "foldhaus_platform.h" + +#include "gs_win32.h" + +#include "gs_opengl.h" +#include "foldhaus_renderer.cpp" + +global_variable b32 Running = false; +global_variable b32 WindowIsActive = false; + +char DLLName[] = "foldhaus.dll"; +char WorkingDLLName[] = "foldhaus_temp.dll"; +char DLLLockFileName[] = "lock.tmp"; + +win32_window MainWindow; + +struct worker_thread_entry +{ + b32 IsValid; + u32 Index; +}; + +struct worker_thread_info +{ + s32 ID; + HANDLE Handle; + work_queue* Queue; +}; + +PUSH_WORK_ON_QUEUE(Win32PushWorkOnQueue) +{ + Assert(Queue->JobsCount < Queue->JobsMax); + + worker_thread_job* Job = Queue->Jobs + Queue->JobsCount; + Job->WorkProc = WorkProc; + Job->Data = Data; + + // Complete Past Writes before Future Writes + _WriteBarrier(); + _mm_sfence(); + + ++Queue->JobsCount; + ReleaseSemaphore(Queue->SemaphoreHandle, 1, 0); +} + +internal worker_thread_entry +CompleteAndTakeNextJob(work_queue* Queue, worker_thread_entry Completed) +{ + if (Completed.IsValid) + { + InterlockedIncrement((LONG volatile*)&Queue->JobsCompleted); + } + + worker_thread_entry Result = {}; + Result.IsValid = false; + + u32 OriginalNextJobIndex = Queue->NextJobIndex; + while (OriginalNextJobIndex < Queue->JobsCount) + { + u32 Index = InterlockedCompareExchange((LONG volatile*)&Queue->NextJobIndex, + OriginalNextJobIndex + 1, + OriginalNextJobIndex); + if (Index == OriginalNextJobIndex) + { + Result.Index = Index; + Result.IsValid = true; + break; + } + OriginalNextJobIndex = Queue->NextJobIndex; + } + + return Result; +} + +DO_QUEUE_WORK_UNTIL_DONE(Win32DoQueueWorkUntilDone) +{ + worker_thread_entry Entry = {}; + Entry.IsValid = false; + while (Queue->JobsCompleted < Queue->JobsCount) + { + Entry = CompleteAndTakeNextJob(Queue, Entry); + if (Entry.IsValid) + { + Queue->Jobs[Entry.Index].WorkProc(ThreadID, Queue->Jobs[Entry.Index].Data); + } + } +} + +DWORD WINAPI +WorkerThreadProc (LPVOID InputThreadInfo) +{ + worker_thread_info* ThreadInfo = (worker_thread_info*)InputThreadInfo; + + worker_thread_entry Entry = {}; + Entry.IsValid = false; + while (true) + { + Entry = CompleteAndTakeNextJob(ThreadInfo->Queue, Entry); + if (Entry.IsValid) + { + ThreadInfo->Queue->Jobs[Entry.Index].WorkProc(ThreadInfo->ID, + ThreadInfo->Queue->Jobs[Entry.Index].Data); + } + else + { + WaitForSingleObjectEx(ThreadInfo->Queue->SemaphoreHandle, INFINITE, 0); + } + } + + return 0; +} + +PLATFORM_GET_GPU_TEXTURE_HANDLE(Win32GetGPUTextureHandle) +{ + s32 Handle = SubmitTexture(Width, Height, Memory); + return Handle; +} + +struct win32_socket +{ + SOCKET Socket; +}; + +#define SOCKET_DICTIONARY_GROW_SIZE 32 +s32 Win32SocketHandleMax; +s32 Win32SocketHandleCount; +win32_socket* SocketValues; + +PLATFORM_GET_SOCKET_HANDLE(Win32GetSocketHandle) +{ + if (Win32SocketHandleCount >= Win32SocketHandleMax) + { + s32 NewDictionaryMax = Win32SocketHandleMax + SOCKET_DICTIONARY_GROW_SIZE; + s32 NewDictionaryDataSize = NewDictionaryMax * sizeof(win32_socket); + platform_memory_result DictionaryMemory = Win32Alloc(NewDictionaryDataSize); + Assert(DictionaryMemory.Size > 0); + + win32_socket* NewValues = (win32_socket*)(DictionaryMemory.Base); + if (SocketValues) + { + GSMemCopy(SocketValues, NewValues, sizeof(win32_socket) * NewDictionaryMax); + Win32Free((u8*)SocketValues, sizeof(win32_socket) * Win32SocketHandleCount); + } + SocketValues = NewValues; + + Win32SocketHandleMax = NewDictionaryMax; + } + + Assert(Win32SocketHandleCount < Win32SocketHandleMax); + s32 NewSocketIndex = Win32SocketHandleCount++; + + SocketValues[NewSocketIndex].Socket = socket(AddressFamily, Type, Protocol); + + return (platform_socket_handle)NewSocketIndex; +} + +#define NETWORK_ADDRESS_DICTIONARY_GROW_SIZE 32 +s32 Win32NetworkAddressHandleMax; +s32 Win32NetworkAddressHandleCount; +sockaddr_in* NetworkAddressValues; + +PLATFORM_GET_SEND_ADDRESS_HANDLE(Win32GetSendAddress) +{ + if (Win32NetworkAddressHandleCount >= Win32NetworkAddressHandleMax) + { + s32 NewDictionaryMax = Win32NetworkAddressHandleMax + NETWORK_ADDRESS_DICTIONARY_GROW_SIZE; + s32 NewDictionaryDataSize = NewDictionaryMax * sizeof(sockaddr_in); + platform_memory_result DictionaryMemory = Win32Alloc(NewDictionaryDataSize); + Assert(DictionaryMemory.Size > 0); + + sockaddr_in* NewValues = (sockaddr_in*)(DictionaryMemory.Base); + if (NetworkAddressValues) + { + GSMemCopy(NetworkAddressValues, NewValues, sizeof(win32_socket) * NewDictionaryMax); + Win32Free((u8*)NetworkAddressValues, sizeof(win32_socket) * Win32NetworkAddressHandleCount); + } + NetworkAddressValues = NewValues; + + Win32NetworkAddressHandleMax = NewDictionaryMax; + } + + Assert(Win32NetworkAddressHandleCount < Win32NetworkAddressHandleMax); + s32 NewAddressIndex = Win32NetworkAddressHandleCount++; + + NetworkAddressValues[NewAddressIndex].sin_family = Family; + NetworkAddressValues[NewAddressIndex].sin_port = HostToNetU16(Port); + NetworkAddressValues[NewAddressIndex].sin_addr.s_addr = HostToNetU32(Address); + + return (platform_network_address_handle)NewAddressIndex; +} + +PLATFORM_SET_SOCKET_OPTION(Win32SetSocketOption) +{ + s32 SocketIndex = (s32)SocketHandle; + Assert(SocketIndex < Win32SocketHandleCount); + int Error = setsockopt(SocketValues[SocketIndex].Socket, Level, Option, OptionValue, OptionLength); + if (Error == SOCKET_ERROR) + { + Error = WSAGetLastError(); + } + + return Error; +} + +PLATFORM_SEND_TO(Win32SendTo) +{ + s32 SocketIndex = (s32)SocketHandle; + Assert(SocketIndex < Win32SocketHandleCount); + + s32 AddressIndex = (s32)AddressHandle; + Assert(AddressIndex < Win32NetworkAddressHandleCount); + + s32 LengthSent = sendto(SocketValues[SocketIndex].Socket, Buffer, BufferLength, Flags, (sockaddr*)&NetworkAddressValues[AddressIndex], sizeof(sockaddr_in)); + + if (LengthSent == SOCKET_ERROR) + { + s32 Error = WSAGetLastError(); + InvalidCodePath; + } + + return LengthSent; +} + +PLATFORM_CLOSE_SOCKET(Win32CloseSocket) +{ + s32 SocketIndex = (s32)SocketHandle; + Assert(SocketIndex < Win32SocketHandleCount); + + closesocket(SocketValues[SocketIndex].Socket); +} + +LRESULT CALLBACK +HandleWindowEvents (HWND WindowHandle, UINT Msg, WPARAM WParam, LPARAM LParam) +{ + LRESULT Result = 0; + + switch (Msg) + { + case WM_SIZE: + { + Win32UpdateWindowDimension(&MainWindow); + //Win32ResizeDIBSection(&GlobalBackbuffer, MainWindow.Info.Width, MainWindow.Info.Height); + }break; + + case WM_CLOSE: + { + Result = DefWindowProc(WindowHandle, Msg, WParam, LParam); + Running = false; + }break; + + case WM_DESTROY: + { + }break; + + case WM_PAINT: + { + PAINTSTRUCT PaintStruct; + HDC DeviceContext; + b32 PaintResult; + + DeviceContext = BeginPaint(WindowHandle, &PaintStruct); + PaintResult = EndPaint(WindowHandle, &PaintStruct); + }break; + + case WM_ACTIVATE: + { + WindowIsActive = (LOWORD(WParam) == WA_ACTIVE || LOWORD(WParam) == WA_CLICKACTIVE); + }break; + + default: + { + Result = DefWindowProc(WindowHandle, Msg, WParam, LParam); + } + } + + return Result; +} + +internal void +HandleWindowMessage (MSG Message, win32_window* Window, input_frame* InputFrame) +{ + switch (Message.message) + { + case WM_MOUSEWHEEL: + { + int MouseWheel = GET_WHEEL_DELTA_WPARAM(Message.wParam); + InputFrame->MouseScroll = MouseWheel; + }break; + + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + case WM_MBUTTONDOWN: + case WM_MBUTTONUP: + case WM_RBUTTONDOWN: + case WM_RBUTTONUP: + { + InputFrame->KeysDown[KeyCode_MouseLeftButton] = (GetKeyState(VK_LBUTTON) & (1 << 15)) != 0; + InputFrame->KeysDown[KeyCode_MouseMiddleButton] = (GetKeyState(VK_MBUTTON) & (1 << 15)) != 0; + InputFrame->KeysDown[KeyCode_MouseRightButton] = (GetKeyState(VK_RBUTTON) & (1 << 15)) != 0; + // NOTE(Peter): If you decide to support extra mouse buttons, on windows the key codes are + // VK_XBUTTON1 and VK_XBUTTON2 + }break; + + case WM_SYSKEYDOWN: + case WM_SYSKEYUP: + case WM_KEYDOWN: + case WM_KEYUP: + { + int VirtualKey = (int)Message.wParam; + bool KeyDown = (Message.lParam & (1 << 31)) == 0; + int KeyIndex = Win32GetKeyIndex(VirtualKey, true, false); + if (KeyIndex == WIN32_SHOULD_TRANSLATE_TO_CHAR) + { + TranslateMessage(&Message); + DispatchMessage(&Message); + } + else + { + InputFrame->KeysDown[KeyIndex] = KeyDown; + } + }break; + + case WM_CHAR: + { + char Char = (char)Message.wParam; + InputFrame->StringInput[InputFrame->StringInputUsed++] = Char; + }break; + + default: + { + TranslateMessage(&Message); + DispatchMessage(&Message); + }break; + } +} + +internal void +DebugPrint (char* Format, ...) +{ + char Buffer[256]; + va_list Args; + va_start(Args, Format); + PrintFInternal(Buffer, 256, Format, Args); + OutputDebugStringA(Buffer); + va_end(Args); +} + +internal void +SetApplicationLinks (context* Context, win32_dll_refresh DLL, work_queue* WorkQueue) +{ + if (DLL.IsValid) + { + Context->InitializeApplication = (initialize_application*)GetProcAddress(DLL.DLL, "InitializeApplication"); + Context->ReloadStaticData = (reload_static_data*)GetProcAddress(DLL.DLL, "ReloadStaticData"); + Context->UpdateAndRender = (update_and_render*)GetProcAddress(DLL.DLL, "UpdateAndRender"); + Context->CleanupApplication = (cleanup_application*)GetProcAddress(DLL.DLL, "CleanupApplication"); + } + else + { + Context->InitializeApplication = 0; + Context->ReloadStaticData = 0; + Context->UpdateAndRender = 0; + Context->CleanupApplication = 0; + } +} + +int WINAPI +WinMain ( +HINSTANCE HInstance, +HINSTANCE HPrevInstance, +PSTR CmdLineArgs, +INT NCmdShow +) +{ + win32_window_info MainWindowInfo = {}; + MainWindowInfo.Name = "Foldhaus"; + MainWindowInfo.ClassName = "Foldhaus Window Class"; + MainWindowInfo.Width = 1440; + MainWindowInfo.Height = 768; + MainWindowInfo.WindowEventsHandler = HandleWindowEvents; + + MainWindow = CreateWin32Window (HInstance, MainWindowInfo); + Win32UpdateWindowDimension(&MainWindow); + + win32_opengl_window_info OpenGLWindowInfo = {}; + OpenGLWindowInfo.ColorBits = 32; + OpenGLWindowInfo.AlphaBits = 8; + OpenGLWindowInfo.DepthBits = 0; + CreateOpenGLWindowContext(OpenGLWindowInfo, &MainWindow); + + s64 PerformanceCountFrequency = GetPerformanceFrequency(); + GlobalDebugServices = (debug_services*)malloc(sizeof(debug_services)); + InitDebugServices(GlobalDebugServices, (u8*)malloc(Megabytes(8)), Megabytes(8), 1000, PerformanceCountFrequency); + GlobalDebugServices->GetWallClock = GetWallClock; + + input Input; + InitializeInput(&Input); + + // + // Set up worker threads + // + + const s32 WorkerThreadCount = 2; + worker_thread_info* WorkerThreads = 0; + if (WorkerThreadCount > 0) + { + WorkerThreads = (worker_thread_info*)malloc(sizeof(worker_thread_info) * WorkerThreadCount); + } + + work_queue WorkQueue = {}; + WorkQueue.SemaphoreHandle = CreateSemaphoreEx(0, 0, WorkerThreadCount, 0, 0, SEMAPHORE_ALL_ACCESS); + WorkQueue.JobsMax = 256; + WorkQueue.NextJobIndex = 0; + WorkQueue.PushWorkOnQueue = Win32PushWorkOnQueue; + WorkQueue.DoQueueWorkUntilDone = Win32DoQueueWorkUntilDone; + WorkQueue.ResetWorkQueue = ResetWorkQueue; + + for (s32 i = 0; i < WorkerThreadCount; i++) + { + // ID = 0 is reserved for this thread + WorkerThreads[i].ID = i + 1; + WorkerThreads[i].Queue = &WorkQueue; + WorkerThreads[i].Handle = CreateThread(0, 0, &WorkerThreadProc, (void*)&WorkerThreads[i], 0, 0); + } + + platform_memory_result InitialMemory = Win32Alloc(Megabytes(64)); + context Context = {}; + Context.MemorySize = InitialMemory.Size; + Context.MemoryBase = InitialMemory.Base; + Context.WindowWidth = MainWindow.Info.Width; + Context.WindowHeight = MainWindow.Info.Height; + + // Platform functions + Context.GeneralWorkQueue = &WorkQueue; + Context.PlatformAlloc = Win32Alloc; + Context.PlatformFree = Win32Free; + Context.PlatformReadEntireFile = ReadEntireFile; + Context.PlatformWriteEntireFile = WriteEntireFile; + Context.PlatformGetFilePath = Win32SystemDialogOpenFile; + Context.PlatformGetGPUTextureHandle = Win32GetGPUTextureHandle; + Context.PlatformGetSocketHandle = Win32GetSocketHandle; + Context.PlatformGetSendAddress = Win32GetSendAddress; + Context.PlatformSetSocketOption = Win32SetSocketOption; + Context.PlatformCloseSocket = Win32CloseSocket; + + win32_dll_refresh DLLRefresh = InitializeDLLHotReloading(DLLName, WorkingDLLName, DLLLockFileName); + if (HotLoadDLL(&DLLRefresh)) + { + SetApplicationLinks(&Context, DLLRefresh, &WorkQueue); + Context.ReloadStaticData(Context, GlobalDebugServices); + } + else + { + InvalidCodePath; + } + + WSADATA WSAData; + WSAStartup(MAKEWORD(2, 2), &WSAData); + + platform_memory_result RenderMemory = Win32Alloc(Megabytes(32)); + render_command_buffer RenderBuffer = AllocateRenderCommandBuffer(RenderMemory.Base, RenderMemory.Size); + + Context.InitializeApplication(Context); + + Running = true; + Context.WindowIsVisible = true; + while (Running) + { + DEBUG_TRACK_SCOPE(MainLoop); + + SwapInputBuffers(&Input); + if (HotLoadDLL(&DLLRefresh)) + { + SetApplicationLinks(&Context, DLLRefresh, &WorkQueue); + Context.ReloadStaticData(Context, GlobalDebugServices); + } + + MSG Message; + while (PeekMessageA(&Message, MainWindow.Handle, 0, 0, PM_REMOVE)) + { + HandleWindowMessage(Message, &MainWindow, Input.New); + } + + { // Mouse Position + POINT MousePos; + GetCursorPos (&MousePos); + ScreenToClient(MainWindow.Handle, &MousePos); + Input.New->MouseX = MousePos.x; + Input.New->MouseY = MainWindow.Info.Height - MousePos.y; + + if (KeyTransitionedDown(Input, KeyCode_MouseLeftButton)) + { + Input.MouseDownX = Input.New->MouseX; + Input.MouseDownY = Input.New->MouseY; + } + } + + // TODO(Peter): We shouldn't need to do this translation. the platform layer knows about win32_windows. We should just make that the interface + // to all windows. + Context.WindowWidth = MainWindow.Info.Width; + Context.WindowHeight = MainWindow.Info.Height; + Context.DeltaTime = LastFrameSecondsElapsed; + + Context.UpdateAndRender(Context, Input, &RenderBuffer); + + RenderCommandBuffer(RenderBuffer); + ClearRenderBuffer(&RenderBuffer); + + /////////////////////////////////// + // Finish Up + ////////////////////////////////// + + HDC DeviceContext = GetDC(MainWindow.Handle); + SwapBuffers(DeviceContext); + ReleaseDC(MainWindow.Handle, DeviceContext); + + s64 FinishedWorkTime = GetWallClock(); + r32 SecondsElapsed = GetSecondsElapsed(LastFrameEnd, FinishedWorkTime, PerformanceCountFrequency); + + while (SecondsElapsed < TargetSecondsPerFrame) + { + u32 SleepTime = 1000.0f * (TargetSecondsPerFrame - SecondsElapsed); + Sleep(SleepTime); + SecondsElapsed = GetSecondsElapsed(LastFrameEnd, GetWallClock(), PerformanceCountFrequency); + } + + LastFrameSecondsElapsed = SecondsElapsed; + LastFrameEnd = GetWallClock(); + } + + Context.CleanupApplication(Context); + + s32 CleanupResult = 0; + do { + CleanupResult = WSACleanup(); + }while(CleanupResult == SOCKET_ERROR); + + for (s32 Thread = 0; Thread < WorkerThreadCount; Thread++) + { + TerminateThread(WorkerThreads[Thread].Handle, 0); + } + + return 0; +} \ No newline at end of file