Lumenarium/src/app/foldhaus_app.cpp

397 lines
18 KiB
C++

//
// File: foldhaus_app.cpp
// Author: Peter Slattery
// Creation Date: 2020-01-01
//
#ifndef FOLDHAUS_APP_CPP
#include "foldhaus_platform.h"
#include "foldhaus_app.h"
////////////////////////////////////////////////////////////////////////
RELOAD_STATIC_DATA(ReloadStaticData)
{
app_state* State = (app_state*)Context.MemoryBase;
GlobalDebugServices = DebugServices;
}
INITIALIZE_APPLICATION(InitializeApplication)
{
app_state* State = (app_state*)Context.MemoryBase;
*State = {};
State->Permanent = CreateMemoryArena(Context.ThreadContext.Allocator);
State->Transient = Context.ThreadContext.Transient;
State->Assemblies = AssemblyArray_Create(8, &State->Permanent);
State->GlobalLog = PushStruct(State->Transient, event_log);
*State->GlobalLog = {0};
s32 CommandQueueSize = 32;
command_queue_entry* CommandQueueMemory = PushArray(&State->Permanent,
command_queue_entry,
CommandQueueSize);
State->CommandQueue = InitializeCommandQueue(CommandQueueMemory, CommandQueueSize);
// TODO(Peter): put in InitializeInterface?
r32 FontSize = 14;
{
gs_file FontFile = ReadEntireFile(Context.ThreadContext.FileHandler, ConstString("data/Anonymous Pro.ttf"));
if (FileNoError(FontFile))
{
bitmap_font* Font = PushStruct(&State->Permanent, bitmap_font);
Font->BitmapWidth = 512;
Font->BitmapHeight = 512;
Font->BitmapBytesPerPixel = 4;
Font->BitmapMemory = PushArray(&State->Permanent, u8, Font->BitmapWidth * Font->BitmapHeight * Font->BitmapBytesPerPixel);
Font->BitmapStride = Font->BitmapWidth * Font->BitmapBytesPerPixel;
ZeroMemoryBlock(Font->BitmapMemory, Font->BitmapStride * Font->BitmapHeight);
platform_font_info FontInfo = Context.PlatformGetFontInfo("Anonymous Pro", FontSize, FontWeight_Normal, false, false, false);
Font->PixelHeight = FontInfo.PixelHeight;
Font->Ascent = FontInfo.Ascent;
Font->Descent = FontInfo.Descent;
Font->Leading = FontInfo.Leading;
Font->MaxCharWidth = FontInfo.MaxCharWidth;
Font->CodepointDictionarySize = (FontInfo.CodepointOnePastLast - FontInfo.CodepointStart);
Font->CodepointDictionaryCount = 0;
Font->CodepointKeys = PushArray(&State->Permanent, char, Font->CodepointDictionarySize);
Font->CodepointValues = PushArray(&State->Permanent, codepoint_bitmap, Font->CodepointDictionarySize);
for (s32 Codepoint = FontInfo.CodepointStart;
Codepoint < FontInfo.CodepointOnePastLast;
Codepoint++)
{
u32 CodepointX, CodepointY;
GetNextCodepointOffset(Font, &CodepointX, &CodepointY);
u32 CodepointW, CodepointH;
Context.PlatformDrawFontCodepoint(
Font->BitmapMemory,
Font->BitmapWidth,
Font->BitmapHeight,
CodepointX, CodepointY,
Codepoint, FontInfo,
&CodepointW, &CodepointH);
AddCodepointToFont(Font, Codepoint, 0, 0, CodepointW, CodepointH, CodepointX, CodepointY);
}
State->Interface.Style.Font = Font;
Font->BitmapTextureHandle = Context.PlatformGetGPUTextureHandle(Font->BitmapMemory,
Font->BitmapWidth, Font->BitmapHeight);
}
else
{
LogError(State->GlobalLog, "Unable to load font");
}
}
State->Interface.Style.FontSize = FontSize;
State->Interface.Style.PanelBGColors[0] = v4{.3f, .3f, .3f, 1};
State->Interface.Style.PanelBGColors[1] = v4{.4f, .4f, .4f, 1};
State->Interface.Style.PanelBGColors[2] = v4{.5f, .5f, .5f, 1};
State->Interface.Style.PanelBGColors[3] = v4{.6f, .6f, .6f, 1};
State->Interface.Style.ButtonColor_Inactive = BlackV4;
State->Interface.Style.ButtonColor_Active = v4{.1f, .1f, .1f, 1};
State->Interface.Style.ButtonColor_Selected = v4{.1f, .1f, .3f, 1};
State->Interface.Style.TextColor = WhiteV4;
State->Interface.Style.ListBGColors[0] = v4{ .16f, .16f, .16f, 1.f };
State->Interface.Style.ListBGColors[1] = v4{ .18f, .18f, .18f, 1.f };
State->Interface.Style.ListBGHover = v4{ .22f, .22f, .22f, 1.f };
State->Interface.Style.ListBGSelected = v4{.44f, .44f, .44f, 1.f };
State->Interface.Style.Margin = v2{5, 5};
State->Interface.Style.RowHeight = ui_GetTextLineHeight(State->Interface);
State->SACN = SACN_Initialize(Context);
State->Camera.FieldOfView = 45.0f;
State->Camera.AspectRatio = RectAspectRatio(State->WindowBounds);
State->Camera.Near = .1f;
State->Camera.Far = 800.0f;
State->Camera.Position = v3{0, 0, 400};
State->Camera.LookAt = v3{0, 0, 0
};
State->LedSystem = LedSystemInitialize(Context.ThreadContext.Allocator, 128);
#if 1
gs_const_string SculpturePath = ConstString("data/blumen_lumen_silver_spring.fold");
LoadAssembly(&State->Assemblies, &State->LedSystem, State->Transient, Context, SculpturePath, State->GlobalLog);
#endif
State->PixelsToWorldScale = .01f;
GlobalDebugServices->Interface.RenderSculpture = true;
ReloadStaticData(Context, GlobalDebugServices);
State->Modes = OperationModeSystemInit(&State->Permanent, Context.ThreadContext);
{ // Animation PLAYGROUND
State->AnimationSystem = {};
State->AnimationSystem.Storage = &State->Permanent;
State->AnimationSystem.Animations = AnimationArray_Create(State->AnimationSystem.Storage, 32);
State->AnimationSystem.SecondsPerFrame = 1.f / 24.f;
animation Anim = {0};
Anim.Name = PushStringF(&State->Permanent, 256, "test_anim_one");
Anim.Layers.CountMax = 8;
Anim.Layers.Values = PushArray(State->AnimationSystem.Storage, anim_layer, Anim.Layers.CountMax);
Anim.PlayableRange.Min = 0;
Anim.PlayableRange.Max = SecondsToFrames(15, State->AnimationSystem);
Animation_AddLayer(&Anim, MakeString("Base Layer"), BlendMode_Overwrite, &State->AnimationSystem);
Animation_AddLayer(&Anim, MakeString("Color Layer"), BlendMode_Multiply, &State->AnimationSystem);
Animation_AddLayer(&Anim, MakeString("Sparkles"), BlendMode_Add, &State->AnimationSystem);
Animation_AddBlock(&Anim, 22, 123, 2, 0);
AnimationArray_Push(&State->AnimationSystem.Animations, Anim);
} // End Animation Playground
InitializePanelSystem(&State->PanelSystem);
panel* Panel = TakeNewPanel(&State->PanelSystem);
SetAndInitPanelType(Panel, PanelType_SculptureView, State, Context);
}
internal void
HandleInput (app_state* State, rect2 WindowBounds, input_queue InputQueue, mouse_state Mouse, context Context)
{
DEBUG_TRACK_FUNCTION;
b32 PanelSystemHandledInput = HandleMousePanelInteraction(&State->PanelSystem, State->WindowBounds, Mouse, State);
if (!PanelSystemHandledInput)
{
input_command_registry ActiveCommands = {};
if (State->Modes.ActiveModesCount > 0)
{
ActiveCommands = State->Modes.ActiveModes[State->Modes.ActiveModesCount - 1].Commands;
}
else
{
panel_with_layout PanelWithMouseOverIt = GetPanelContainingPoint(Mouse.Pos, &State->PanelSystem, WindowBounds);
if (!PanelWithMouseOverIt.Panel) { return; }
State->HotPanel = PanelWithMouseOverIt.Panel;
s32 PanelTypeIndex = Panel_GetCurrentTypeIndex(PanelWithMouseOverIt.Panel);
panel_definition PanelDefinition = GlobalPanelDefs[PanelTypeIndex];
if (!PanelDefinition.InputCommands) { return; }
ActiveCommands.Commands = PanelDefinition.InputCommands;
ActiveCommands.Size = sizeof(*PanelDefinition.InputCommands) / sizeof(PanelDefinition.InputCommands[0]);
ActiveCommands.Used = ActiveCommands.Size;
}
for (s32 EventIdx = 0; EventIdx < InputQueue.QueueUsed; EventIdx++)
{
input_entry Event = InputQueue.Entries[EventIdx];
// NOTE(Peter): These are in the order Down, Up, Held because we want to privalege
// Down and Up over Held. In other words, we don't want to call a Held command on the
// frame when the button was released, even if the command is registered to both events
if (KeyTransitionedDown(Event))
{
FindAndPushExistingCommand(ActiveCommands, Event, Command_Began, &State->CommandQueue);
}
else if (KeyTransitionedUp(Event))
{
FindAndPushExistingCommand(ActiveCommands, Event, Command_Ended, &State->CommandQueue);
}
else if (KeyHeldDown(Event))
{
FindAndPushExistingCommand(ActiveCommands, Event, Command_Held, &State->CommandQueue);
}
}
}
// Execute all commands in CommandQueue
for (s32 CommandIdx = State->CommandQueue.Used - 1; CommandIdx >= 0; CommandIdx--)
{
command_queue_entry* Entry = &State->CommandQueue.Commands[CommandIdx];
Entry->Command.Proc(State, Entry->Event, Mouse, Context);
}
ClearCommandQueue(&State->CommandQueue);
}
UPDATE_AND_RENDER(UpdateAndRender)
{
DEBUG_TRACK_FUNCTION;
app_state* State = (app_state*)Context->MemoryBase;
// NOTE(Peter): We do this at the beginning because all the render commands are stored in Transient,
// and need to persist beyond the end of the UpdateAndRender call. In the release version, we won't
// zero the Transient arena when we clear it so it wouldn't be a problem, but it is technically
// incorrect to clear the arena, and then access the memory later.
ClearArena(State->Transient);
Context->Mouse.CursorType = CursorType_Arrow;
PushRenderClearScreen(RenderBuffer);
State->Camera.AspectRatio = RectAspectRatio(Context->WindowBounds);
HandleInput(State, State->WindowBounds, InputQueue, Context->Mouse, *Context);
{
animation* ActiveAnim = AnimationSystem_GetActiveAnimation(&State->AnimationSystem);
if (State->AnimationSystem.TimelineShouldAdvance) {
// TODO(Peter): Revisit this. This implies that the framerate of the animation system
// is tied to the framerate of the simulation. That seems correct to me, but I'm not sure
State->AnimationSystem.CurrentFrame += 1;
// Loop back to the beginning
if (State->AnimationSystem.CurrentFrame > ActiveAnim->PlayableRange.Max)
{
State->AnimationSystem.CurrentFrame = 0;
}
}
}
s32 CurrentFrame = State->AnimationSystem.CurrentFrame;
if (CurrentFrame != State->AnimationSystem.LastUpdatedFrame)
{
State->AnimationSystem.LastUpdatedFrame = CurrentFrame;
r32 FrameTime = CurrentFrame * State->AnimationSystem.SecondsPerFrame;
animation_frame CurrFrame = AnimationSystem_CalculateAnimationFrame(&State->AnimationSystem, State->Transient);
led_buffer* LayerLEDBuffers = PushArray(State->Transient, led_buffer, CurrFrame.BlocksCountMax);
for (u32 AssemblyIndex = 0; AssemblyIndex < State->Assemblies.Count; AssemblyIndex++)
{
assembly* Assembly = &State->Assemblies.Values[AssemblyIndex];
led_buffer* AssemblyLedBuffer = LedSystemGetBuffer(&State->LedSystem, Assembly->LedBufferIndex);
for (u32 Layer = 0; Layer < CurrFrame.BlocksCountMax; Layer++)
{
if (!CurrFrame.BlocksFilled[Layer]) { continue; }
animation_block Block = CurrFrame.Blocks[Layer];
// Prep Temp Buffer
LayerLEDBuffers[Layer] = *AssemblyLedBuffer;
LayerLEDBuffers[Layer].Colors = PushArray(State->Transient, pixel, AssemblyLedBuffer->LedCount);
u32 FramesIntoBlock = CurrentFrame - Block.Range.Min;
r32 SecondsIntoBlock = FramesIntoBlock * State->AnimationSystem.SecondsPerFrame;
// :AnimProcHandle
u32 AnimationProcIndex = Block.AnimationProcHandle - 1;
animation_proc* AnimationProc = GlobalAnimationClips[AnimationProcIndex].Proc;
AnimationProc(&LayerLEDBuffers[Layer], *Assembly, SecondsIntoBlock, State->Transient);
}
// Consolidate Temp Buffers
// We do this in reverse order so that they go from top to bottom
animation* ActiveAnim = AnimationSystem_GetActiveAnimation(&State->AnimationSystem);
for (u32 Layer = 0; Layer < CurrFrame.BlocksCountMax; Layer++)
{
if (!CurrFrame.BlocksFilled[Layer]) { continue; }
switch (ActiveAnim->Layers.Values[Layer].BlendMode)
{
case BlendMode_Overwrite:
{
for (u32 LED = 0; LED < AssemblyLedBuffer->LedCount; LED++)
{
AssemblyLedBuffer->Colors[LED] = LayerLEDBuffers[Layer].Colors[LED];
}
}break;
case BlendMode_Add:
{
for (u32 LED = 0; LED < AssemblyLedBuffer->LedCount; LED++)
{
u32 R = (u32)AssemblyLedBuffer->Colors[LED].R + (u32)LayerLEDBuffers[Layer].Colors[LED].R;
u32 G = (u32)AssemblyLedBuffer->Colors[LED].G + (u32)LayerLEDBuffers[Layer].Colors[LED].G;
u32 B = (u32)AssemblyLedBuffer->Colors[LED].B + (u32)LayerLEDBuffers[Layer].Colors[LED].B;
AssemblyLedBuffer->Colors[LED].R = (u8)Min(R, (u32)255);
AssemblyLedBuffer->Colors[LED].G = (u8)Min(G, (u32)255);
AssemblyLedBuffer->Colors[LED].B = (u8)Min(B, (u32)255);
}
}break;
case BlendMode_Multiply:
{
for (u32 LED = 0; LED < AssemblyLedBuffer->LedCount; LED++)
{
r32 DR = (r32)AssemblyLedBuffer->Colors[LED].R / 255.f;
r32 DG = (r32)AssemblyLedBuffer->Colors[LED].G / 255.f;
r32 DB = (r32)AssemblyLedBuffer->Colors[LED].B / 255.f;
r32 SR = (r32)LayerLEDBuffers[Layer].Colors[LED].R / 255.f;
r32 SG = (r32)LayerLEDBuffers[Layer].Colors[LED].G / 255.f;
r32 SB = (r32)LayerLEDBuffers[Layer].Colors[LED].B / 255.f;
AssemblyLedBuffer->Colors[LED].R = (u8)((DR * SR) * 255.f);
AssemblyLedBuffer->Colors[LED].G = (u8)((DG * SG) * 255.f);
AssemblyLedBuffer->Colors[LED].B = (u8)((DB * SB) * 255.f);
}
}break;
}
}
}
}
{
// NOTE(pjs): Building data buffers to be sent out to the sculpture
// This array is used on the platform side to actually send the information
assembly_array SACNAssemblies = AssemblyArray_Filter(State->Assemblies, AssemblyFilter_OutputsViaSACN, State->Transient);
assembly_array UARTAssemblies = AssemblyArray_Filter(State->Assemblies, AssemblyFilter_OutputsViaUART, State->Transient);
SACN_BuildOutputData(&State->SACN, OutputData, SACNAssemblies, &State->LedSystem);
UART_BuildOutputData(OutputData, UARTAssemblies, &State->LedSystem);
}
PushRenderOrthographic(RenderBuffer, State->WindowBounds);
PushRenderClearScreen(RenderBuffer);
State->WindowBounds = Context->WindowBounds;
State->Interface.RenderBuffer = RenderBuffer;
State->Interface.Mouse = Context->Mouse;
panel_layout PanelsToRender = GetPanelLayout(&State->PanelSystem, State->WindowBounds, State->Transient);
DrawAllPanels(PanelsToRender, RenderBuffer, &Context->Mouse, State, *Context);
for (s32 m = 0; m < State->Modes.ActiveModesCount; m++)
{
operation_mode OperationMode = State->Modes.ActiveModes[m];
if (OperationMode.Render != 0)
{
OperationMode.Render(State, RenderBuffer, OperationMode, Context->Mouse, *Context);
}
}
Context->GeneralWorkQueue->CompleteQueueWork(Context->GeneralWorkQueue, Context->ThreadContext);
Context->GeneralWorkQueue->ResetWorkQueue(Context->GeneralWorkQueue);
// Checking for overflows
#if 0
{
DEBUG_TRACK_SCOPE(OverflowChecks);
AssertAllocationsNoOverflow(State->Permanent);
for (u32 i = 0; i < State->Assemblies.Count; i++)
{
assembly* Assembly = &State->Assemblies.Values[i];
AssertAllocationsNoOverflow(Assembly->Arena);
}
}
#endif
}
CLEANUP_APPLICATION(CleanupApplication)
{
app_state* State = (app_state*)Context.MemoryBase;
SACN_Cleanup(&State->SACN, Context);
}
#define FOLDHAUS_APP_CPP
#endif // FOLDHAUS_APP_CPP