From b816474dd50d86c2d5adb47ebbf5aab9227211da Mon Sep 17 00:00:00 2001 From: PS Date: Fri, 9 Oct 2020 22:08:51 -0700 Subject: [PATCH] Successfully transitioned to discrete animations --- .../foldhaus_panel_animation_timeline.h | 199 +++++++++------- src/app/engine/animation/foldhaus_animation.h | 222 +++++++++++++----- src/app/foldhaus_app.cpp | 73 +++--- src/gs_libs/gs_types.cpp | 22 ++ 4 files changed, 347 insertions(+), 169 deletions(-) diff --git a/src/app/editor/panels/foldhaus_panel_animation_timeline.h b/src/app/editor/panels/foldhaus_panel_animation_timeline.h index 17a469b..3c2140b 100644 --- a/src/app/editor/panels/foldhaus_panel_animation_timeline.h +++ b/src/app/editor/panels/foldhaus_panel_animation_timeline.h @@ -34,11 +34,12 @@ GetXPositionFromFrameInAnimationPanel (u32 Frame, rect2 PanelBounds, frame_range } internal gs_list_handle -AddAnimationBlockAtCurrentTime (u32 AnimationProcHandle, u32 Layer, animation_system* System) +AddAnimationBlockAtCurrentTime (u32 AnimationProcHandle, u32 LayerHandle, animation_system* System) { u32 NewBlockStart = System->CurrentFrame; u32 NewBlockEnd = NewBlockStart + SecondsToFrames(3, *System); - gs_list_handle Result = AddAnimationBlock(NewBlockStart, NewBlockEnd, AnimationProcHandle, Layer, System); + animation* ActiveAnim = AnimationSystem_GetActiveAnimation(System); + gs_list_handle Result = Animation_AddBlock(ActiveAnim, NewBlockStart, NewBlockEnd, AnimationProcHandle, LayerHandle); return Result; } @@ -58,7 +59,8 @@ FOLDHAUS_INPUT_COMMAND_PROC(DeleteAnimationBlockCommand) { if(ListHandleIsValid(State->SelectedAnimationBlockHandle)) { - RemoveAnimationBlock(State->SelectedAnimationBlockHandle, &State->AnimationSystem); + animation* ActiveAnim = AnimationSystem_GetActiveAnimation(&State->AnimationSystem); + Animation_RemoveBlock(ActiveAnim, State->SelectedAnimationBlockHandle); State->SelectedAnimationBlockHandle = {0}; } } @@ -135,6 +137,8 @@ OPERATION_RENDER_PROC(UpdateDragAnimationClip) { drag_animation_clip_state* OpState = (drag_animation_clip_state*)Operation.OpStateMemory; + animation* ActiveAnim = AnimationSystem_GetActiveAnimation(&State->AnimationSystem); + r32 ClipInitialStartFrameXPercent = FrameToPercentRange(OpState->ClipRange.Min, OpState->VisibleRange); u32 ClipInitialStartFrameXPosition = LerpR32(ClipInitialStartFrameXPercent, OpState->TimelineBounds.Min.x, @@ -149,7 +153,7 @@ OPERATION_RENDER_PROC(UpdateDragAnimationClip) u32 FrameAtMouseX = GetFrameFromPointInAnimationPanel(Mouse.Pos, OpState->TimelineBounds, OpState->VisibleRange); s32 FrameOffset = (s32)FrameAtMouseX - (s32)FrameAtMouseDownX; - animation_block* AnimationBlock = State->AnimationSystem.Blocks.GetElementWithHandle(State->SelectedAnimationBlockHandle); + animation_block* AnimationBlock = ActiveAnim->Blocks.GetElementWithHandle(State->SelectedAnimationBlockHandle); if (!AnimationBlock) { EndCurrentOperationMode(State, {}, Mouse, Context); @@ -161,9 +165,9 @@ OPERATION_RENDER_PROC(UpdateDragAnimationClip) s32 NewStartFrame = OpState->ClipRange.Min + FrameOffset; if (FrameOffset < 0) { - for (u32 i = 0; i < State->AnimationSystem.Blocks.Used; i++) + for (u32 i = 0; i < ActiveAnim->Blocks.Used; i++) { - gs_list_entry* OtherBlockEntry = State->AnimationSystem.Blocks.GetEntryAtIndex(i); + gs_list_entry* OtherBlockEntry = ActiveAnim->Blocks.GetEntryAtIndex(i); if (EntryIsFree(OtherBlockEntry)) { continue; } animation_block OtherBlock = OtherBlockEntry->Value; NewStartFrame = AttemptToSnapPosition(NewStartFrame, OtherBlock.Range.Max); @@ -183,9 +187,9 @@ OPERATION_RENDER_PROC(UpdateDragAnimationClip) r32 NewEndFrame = OpState->ClipRange.Max + FrameOffset; if (FrameOffset > 0) { - for (u32 i = 0; i < State->AnimationSystem.Blocks.Used; i++) + for (u32 i = 0; i < ActiveAnim->Blocks.Used; i++) { - gs_list_entry* OtherBlockEntry = State->AnimationSystem.Blocks.GetEntryAtIndex(i); + gs_list_entry* OtherBlockEntry = ActiveAnim->Blocks.GetEntryAtIndex(i); if (EntryIsFree(OtherBlockEntry)) { continue; } animation_block OtherBlock = OtherBlockEntry->Value; NewEndFrame = AttemptToSnapPosition(NewEndFrame, OtherBlock.Range.Min); @@ -204,9 +208,9 @@ OPERATION_RENDER_PROC(UpdateDragAnimationClip) { u32 NewStartFrame = OpState->ClipRange.Min + FrameOffset; u32 NewEndFrame = OpState->ClipRange.Max + FrameOffset; - for (u32 i = 0; i < State->AnimationSystem.Blocks.Used; i++) + for (u32 i = 0; i < ActiveAnim->Blocks.Used; i++) { - gs_list_entry* OtherBlockEntry = State->AnimationSystem.Blocks.GetEntryAtIndex(i); + gs_list_entry* OtherBlockEntry = ActiveAnim->Blocks.GetEntryAtIndex(i); if (EntryIsFree(OtherBlockEntry)) { continue; } animation_block OtherBlock = OtherBlockEntry->Value; @@ -228,8 +232,8 @@ OPERATION_RENDER_PROC(UpdateDragAnimationClip) AnimationBlock->Range.Max = NewEndFrame; } - s32 PlayableStartFrame = State->AnimationSystem.PlayableRange.Min; - s32 PlayableEndFrame = State->AnimationSystem.PlayableRange.Max; + s32 PlayableStartFrame = ActiveAnim->PlayableRange.Min; + s32 PlayableEndFrame = ActiveAnim->PlayableRange.Max; AnimationBlock->Range.Min = (u32)Clamp(PlayableStartFrame, (s32)AnimationBlock->Range.Min, PlayableEndFrame); AnimationBlock->Range.Max = (u32)Clamp(PlayableStartFrame, (s32)AnimationBlock->Range.Max, PlayableEndFrame); } @@ -243,6 +247,7 @@ SelectAndBeginDragAnimationBlock(gs_list_handle BlockHandle, frame_range Visible { SelectAnimationBlock(BlockHandle, State); + animation* ActiveAnim = AnimationSystem_GetActiveAnimation(&State->AnimationSystem); operation_mode* DragAnimationClipMode = ActivateOperationModeWithCommands(&State->Modes, DragAnimationClipCommands, UpdateDragAnimationClip); drag_animation_clip_state* OpState = CreateOperationState(DragAnimationClipMode, @@ -251,17 +256,20 @@ SelectAndBeginDragAnimationBlock(gs_list_handle BlockHandle, frame_range Visible OpState->TimelineBounds = TimelineBounds; OpState->VisibleRange = VisibleRange; - animation_block* SelectedBlock = State->AnimationSystem.Blocks.GetElementWithHandle(BlockHandle); + animation_block* SelectedBlock = ActiveAnim->Blocks.GetElementWithHandle(BlockHandle); OpState->ClipRange = SelectedBlock->Range; } // ------------------- FOLDHAUS_INPUT_COMMAND_PROC(AddAnimationBlockCommand) { + animation* ActiveAnim = AnimationSystem_GetActiveAnimation(&State->AnimationSystem); + panel_and_bounds ActivePanel = GetPanelContainingPoint(Mouse.Pos, &State->PanelSystem, State->WindowBounds); - frame_range Range = State->AnimationSystem.PlayableRange; + frame_range Range = ActiveAnim->PlayableRange; u32 MouseDownFrame = GetFrameFromPointInAnimationPanel(Mouse.Pos, ActivePanel.Bounds, Range); - gs_list_handle NewBlockHandle = AddAnimationBlock(MouseDownFrame, MouseDownFrame + SecondsToFrames(3, State->AnimationSystem), 4, State->SelectedAnimationLayer, &State->AnimationSystem); + + gs_list_handle NewBlockHandle = Animation_AddBlock(ActiveAnim, MouseDownFrame, MouseDownFrame + SecondsToFrames(3, State->AnimationSystem), 4, State->SelectedAnimationLayer); SelectAnimationBlock(NewBlockHandle, State); } @@ -277,8 +285,9 @@ internal void AnimationTimeline_Init(panel* Panel, app_state* State, context Context) { // TODO: :FreePanelMemory + animation* ActiveAnim = AnimationSystem_GetActiveAnimation(&State->AnimationSystem); animation_timeline_state* TimelineState = PushStruct(&State->Permanent, animation_timeline_state); - TimelineState->VisibleRange = State->AnimationSystem.PlayableRange; + TimelineState->VisibleRange = ActiveAnim->PlayableRange; Panel->PanelStateMemory = (u8*)TimelineState; } @@ -300,8 +309,6 @@ DrawFrameBar (animation_system* AnimationSystem, ui_interface Interface, frame_r r32 BarHeight = Rect2Height(BarBounds); r32 BarWidth = Rect2Width(BarBounds); - PushRenderQuad2D(Interface.RenderBuffer, BarBounds.Min, BarBounds.Max, v4{.16f, .16f, .16f, 1.f}); - // Mouse clicked inside frame nubmer bar -> change current frame on timeline if (MouseButtonTransitionedDown(Interface.Mouse.LeftButtonState) && PointIsInRect(BarBounds, Interface.Mouse.DownPos)) @@ -309,6 +316,8 @@ DrawFrameBar (animation_system* AnimationSystem, ui_interface Interface, frame_r StartDragTimeMarker(BarBounds, VisibleFrames, State); } + PushRenderQuad2D(Interface.RenderBuffer, BarBounds.Min, BarBounds.Max, v4{.16f, .16f, .16f, 1.f}); + // Frame Ticks u32 TickCount = 10; for (u32 Tick = 0; Tick < TickCount; Tick++) @@ -323,7 +332,7 @@ DrawFrameBar (animation_system* AnimationSystem, ui_interface Interface, frame_r } // Time Slider - if (FrameIsInRange(AnimationSystem->CurrentFrame, VisibleFrames)) + if (FrameIsInRange(VisibleFrames, AnimationSystem->CurrentFrame)) { r32 FrameAtPercentVisibleRange = FrameToPercentRange(AnimationSystem->CurrentFrame, VisibleFrames); r32 SliderX = LerpR32(FrameAtPercentVisibleRange, BarBounds.Min.x, BarBounds.Max.x); @@ -340,91 +349,117 @@ DrawFrameBar (animation_system* AnimationSystem, ui_interface Interface, frame_r } } -internal frame_range -DrawTimelineRangeBar (animation_system* AnimationSystem, animation_timeline_state* TimelineState, ui_interface Interface, rect2 BarBounds) +internal bool +MinMaxRangeSlider(v2 HandleValues, rect2 SliderBounds, r32 MinValue, r32 MaxValue, ui_interface Interface, v2* OutHandleValues) { - frame_range Result = {0}; + // Should Update only gets set to true when the user is finished interacting (ie. on mouse up) + // this allows the continuous use of the value of a handle while it is being dragged, and allows + // for you to know when exactly to update the stored value - r32 BarHeight = Rect2Height(BarBounds); - r32 BarWidth = Rect2Width(BarBounds); - PushRenderQuad2D(Interface.RenderBuffer, BarBounds.Min, BarBounds.Max, v4{.16f, .16f, .16f, 1.f}); + bool ShouldUpdate = false; + *OutHandleValues = HandleValues; - r32 PlayableFrames = (r32)GetFrameCount(AnimationSystem->PlayableRange); - v2 SliderBarDim = v2{25, BarHeight}; + v4 BGColor = v4{.16f, .16f, .16f, 1.f}; + v4 HandleColor = v4{.8f, .8f, .8f, 1.f}; - // Convert Frames To Pixels - r32 VisibleMinPercentPlayable = FrameToPercentRange(TimelineState->VisibleRange.Min, AnimationSystem->PlayableRange); - r32 VisibleMaxPercentPlayable = FrameToPercentRange(TimelineState->VisibleRange.Max, AnimationSystem->PlayableRange); - v2 RangeMinSliderMin = v2{BarBounds.Min.x + (VisibleMinPercentPlayable * Rect2Width(BarBounds)), BarBounds.Min.y}; - v2 RangeMaxSliderMin = v2{BarBounds.Min.x + (VisibleMaxPercentPlayable * Rect2Width(BarBounds)) - 25, BarBounds.Min.y}; - - rect2 SliderBarRange = rect2{ RangeMinSliderMin, RangeMinSliderMin + SliderBarDim }; + v2 HandleDim = v2{25, Rect2Height(SliderBounds)}; + r32 MinHandleX = RemapR32(HandleValues.x, MinValue, MaxValue, SliderBounds.Min.x, SliderBounds.Max.x); + r32 MaxHandleX = RemapR32(HandleValues.y, MinValue, MaxValue, SliderBounds.Min.x, SliderBounds.Max.x); + rect2 MinHandleBounds = MakeRect2CenterDim(v2{ MinHandleX, Rect2Center(SliderBounds).y }, HandleDim); + rect2 MaxHandleBounds = MakeRect2CenterDim(v2{ MaxHandleX, Rect2Center(SliderBounds).y }, HandleDim); + // Drag the handles if (MouseButtonHeldDown(Interface.Mouse.LeftButtonState) || MouseButtonTransitionedUp(Interface.Mouse.LeftButtonState)) { v2 MouseDragOffset = Interface.Mouse.Pos - Interface.Mouse.DownPos; - if (PointIsInRect(SliderBarRange, Interface.Mouse.DownPos)) + + // TODO(pjs): We need to make sure that the min handle is always the lower one, etc. + // TODO(pjs): We need to range clamp the handles + if (PointIsInRect(MinHandleBounds, Interface.Mouse.DownPos)) { - r32 NewSliderX = RangeMinSliderMin.x + MouseDragOffset.x; - RangeMinSliderMin.x = Clamp(BarBounds.Min.x, NewSliderX, RangeMaxSliderMin.x - SliderBarDim.x); + MinHandleBounds = Rect2TranslateX(MinHandleBounds, MouseDragOffset.x); } - if (PointIsInRect(SliderBarRange, Interface.Mouse.DownPos)) + else if (PointIsInRect(MaxHandleBounds, Interface.Mouse.DownPos)) { - r32 NewSliderX = RangeMaxSliderMin.x + MouseDragOffset.x; - RangeMaxSliderMin.x = Clamp(RangeMinSliderMin.x + SliderBarDim.x, NewSliderX, BarBounds.Max.x - SliderBarDim.x); + MaxHandleBounds = Rect2TranslateX(MaxHandleBounds, MouseDragOffset.x); } } + + // Draw Background + PushRenderQuad2D(Interface.RenderBuffer, SliderBounds.Min, SliderBounds.Max, BGColor); - v2 RangeMinSliderMax = v2{RangeMinSliderMin.x + 25, BarBounds.Max.y}; - v2 RangeMaxSliderMax = v2{RangeMaxSliderMin.x + 25, BarBounds.Max.y}; - PushRenderQuad2D(Interface.RenderBuffer, RangeMinSliderMin, RangeMinSliderMax, v4{.8f, .8f, .8f, 1.f}); - PushRenderQuad2D(Interface.RenderBuffer, RangeMaxSliderMin, RangeMaxSliderMax, v4{.8f, .8f, .8f, 1.f}); + // Draw Handles + PushRenderQuad2D(Interface.RenderBuffer, MinHandleBounds.Min, MinHandleBounds.Max, HandleColor); + PushRenderQuad2D(Interface.RenderBuffer, MaxHandleBounds.Min, MaxHandleBounds.Max, HandleColor); - // Convert Pixels Back To Frames and store - VisibleMinPercentPlayable = (RangeMinSliderMin.x - BarBounds.Min.x) / BarWidth; - VisibleMaxPercentPlayable = (RangeMaxSliderMax.x - BarBounds.Min.x) / BarWidth; - u32 VisibleFrameCount = GetFrameCount(AnimationSystem->PlayableRange); - Result.Min = VisibleMinPercentPlayable * VisibleFrameCount; - Result.Max = VisibleMaxPercentPlayable * VisibleFrameCount; + // Update the output range value + r32 MinHandleXOut = Rect2Center(MinHandleBounds).x; + r32 MaxHandleXOut = Rect2Center(MaxHandleBounds).x; + + r32 MinHandleValue = RemapR32(MinHandleXOut, SliderBounds.Min.x, SliderBounds.Max.x, MinValue, MaxValue); + r32 MaxHandleValue = RemapR32(MaxHandleXOut, SliderBounds.Min.x, SliderBounds.Max.x, MinValue, MaxValue); + + *OutHandleValues = v2{ Min(MinHandleValue, MaxHandleValue), Max(MinHandleValue, MaxHandleValue) }; if (MouseButtonTransitionedUp(Interface.Mouse.LeftButtonState)) { - TimelineState->VisibleRange = Result; + ShouldUpdate = true; } - return Result; + return ShouldUpdate; +} + + +internal frame_range +DrawTimelineRangeBar (animation_system* AnimationSystem, animation Animation, animation_timeline_state* TimelineState, ui_interface Interface, rect2 BarBounds) +{ + frame_range VisibleRangeAfterInteraction = {}; + r32 MinFrame = (r32)Animation.PlayableRange.Min; + r32 MaxFrame = (r32)Animation.PlayableRange.Max; + + v2 RangeHandles = v2{ (r32)TimelineState->VisibleRange.Min, (r32)TimelineState->VisibleRange.Max }; + + bool ApplyUpdate = MinMaxRangeSlider(RangeHandles, BarBounds, MinFrame, MaxFrame, Interface, &RangeHandles); + VisibleRangeAfterInteraction.Min = (s32)RangeHandles.x; + VisibleRangeAfterInteraction.Max = (s32)RangeHandles.y; + + if (ApplyUpdate) + { + TimelineState->VisibleRange = VisibleRangeAfterInteraction; + } + + return VisibleRangeAfterInteraction; } #define LAYER_HEIGHT 52 -internal u32 -DrawLayerMenu(animation_system* AnimationSystem, ui_interface Interface, rect2 PanelDim, u32 SelectedAnimationLayer) +internal void +DrawLayerMenu(animation_system* AnimationSystem, animation ActiveAnim, ui_interface Interface, rect2 PanelDim, u32* SelectedAnimationLayer) { v2 LayerDim = { Rect2Width(PanelDim), LAYER_HEIGHT }; v2 LayerListMin = PanelDim.Min + v2{0, 24}; - for (u32 LayerIndex = 0; LayerIndex < AnimationSystem->LayersCount; LayerIndex++) + for (u32 i = 0; i < ActiveAnim.Layers.Count; i++) { - anim_layer* Layer = AnimationSystem->Layers + LayerIndex; + anim_layer* Layer = ActiveAnim.Layers.Values + i; + rect2 LayerBounds = {0}; - LayerBounds.Min = { LayerListMin.x, LayerListMin.y + (LayerDim.y * LayerIndex) }; + LayerBounds.Min = { LayerListMin.x, LayerListMin.y + (LayerDim.y * i) }; LayerBounds.Max = LayerBounds.Min + LayerDim; if (MouseButtonTransitionedDown(Interface.Mouse.LeftButtonState) && PointIsInRect(LayerBounds, Interface.Mouse.Pos)) { - SelectedAnimationLayer = LayerIndex; + *SelectedAnimationLayer = i; } v2 LayerTextPos = { LayerBounds.Min.x + 6, LayerBounds.Max.y - 16}; - if (SelectedAnimationLayer == LayerIndex) + if (*SelectedAnimationLayer == i) { PushRenderBoundingBox2D(Interface.RenderBuffer, LayerBounds.Min, LayerBounds.Max, 1, WhiteV4); } DrawString(Interface.RenderBuffer, Layer->Name, Interface.Style.Font, LayerTextPos, WhiteV4); } - - return SelectedAnimationLayer; } internal rect2 @@ -458,19 +493,22 @@ DrawAnimationTimeline (animation_system* AnimationSystem, animation_timeline_sta gs_string Tempgs_string = PushString(State->Transient, 256); gs_list_handle Result = SelectedBlockHandle; + // TODO(pjs): Animation Selection + animation CurrAnimation = AnimationSystem->Animations.Values[0]; + rect2 LayerMenuBounds, TimelineBounds; RectVSplitAtDistanceFromLeft(PanelBounds, 256, &LayerMenuBounds, &TimelineBounds); // In Top To Bottom Order - rect2 TimelineFrameBarBounds, TimelineBlockDisplayBounds, TimelineRangeBarBounds; + rect2 TimelineFrameBarBounds; + rect2 TimelineBlockDisplayBounds; + rect2 TimelineRangeBarBounds; RectHSplitAtDistanceFromTop(TimelineBounds, 32, &TimelineFrameBarBounds, &TimelineBounds); RectHSplitAtDistanceFromBottom(TimelineBounds, 24, &TimelineBlockDisplayBounds, &TimelineRangeBarBounds); - State->SelectedAnimationLayer = DrawLayerMenu(AnimationSystem, *Interface, LayerMenuBounds, State->SelectedAnimationLayer); + DrawLayerMenu(AnimationSystem, CurrAnimation, *Interface, LayerMenuBounds, &State->SelectedAnimationLayer); - frame_range AdjustedViewRange = {0}; - AdjustedViewRange = DrawTimelineRangeBar(AnimationSystem, TimelineState, *Interface, TimelineRangeBarBounds); - s32 VisibleFrameCount = AdjustedViewRange.Max - AdjustedViewRange.Min; + frame_range AdjustedViewRange = DrawTimelineRangeBar(AnimationSystem, CurrAnimation, TimelineState, *Interface, TimelineRangeBarBounds); DrawFrameBar(AnimationSystem, *Interface, AdjustedViewRange, TimelineFrameBarBounds, State); @@ -479,17 +517,17 @@ DrawAnimationTimeline (animation_system* AnimationSystem, animation_timeline_sta // Animation Blocks b32 MouseDownAndNotHandled = MouseButtonTransitionedDown(Interface->Mouse.LeftButtonState); gs_list_handle DragBlockHandle = {0}; - for (u32 i = 0; i < AnimationSystem->Blocks.Used; i++) + for (u32 i = 0; i < CurrAnimation.Blocks.Used; i++) { - gs_list_entry* AnimationBlockEntry = AnimationSystem->Blocks.GetEntryAtIndex(i); + gs_list_entry* AnimationBlockEntry = CurrAnimation.Blocks.GetEntryAtIndex(i); if (EntryIsFree(AnimationBlockEntry)) { continue; } gs_list_handle CurrentBlockHandle = AnimationBlockEntry->Handle; animation_block AnimationBlockAt = AnimationBlockEntry->Value; // If either end is in the range, we should draw it - b32 RangeIsVisible = (FrameIsInRange(AnimationBlockAt.Range.Min, AdjustedViewRange) || - FrameIsInRange(AnimationBlockAt.Range.Max, AdjustedViewRange)); + b32 RangeIsVisible = (FrameIsInRange(AdjustedViewRange, AnimationBlockAt.Range.Min) || + FrameIsInRange(AdjustedViewRange, AnimationBlockAt.Range.Max)); // If neither end is in the range, but the ends surround the visible range, // we should still draw it. RangeIsVisible |= (AnimationBlockAt.Range.Min <= AdjustedViewRange.Min && @@ -516,7 +554,7 @@ DrawAnimationTimeline (animation_system* AnimationSystem, animation_timeline_sta } // Time Slider - if (FrameIsInRange(AnimationSystem->CurrentFrame, AdjustedViewRange)) + if (FrameIsInRange(AdjustedViewRange, AnimationSystem->CurrentFrame)) { r32 FrameAtPercentVisibleRange = FrameToPercentRange(AnimationSystem->CurrentFrame, AdjustedViewRange); r32 SliderX = LerpR32(FrameAtPercentVisibleRange, TimelineBounds.Min.x, TimelineBounds.Max.x); @@ -554,7 +592,7 @@ animation_clip GlobalAnimationClips[] = { }; internal void -DrawAnimationClipsList(rect2 PanelBounds, ui_interface* Interface, u32 SelectedAnimationLayer, animation_system* AnimationSystem) +DrawAnimationClipsList(rect2 PanelBounds, ui_interface* Interface, u32 SelectedAnimationLayerHandle, animation_system* AnimationSystem) { ui_layout Layout = ui_CreateLayout(*Interface, PanelBounds); for (s32 i = 0; i < GlobalAnimationClipsCount; i++) @@ -563,7 +601,7 @@ DrawAnimationClipsList(rect2 PanelBounds, ui_interface* Interface, u32 SelectedA gs_string ClipName = MakeString(Clip.Name, Clip.NameLength); if (ui_LayoutListEntry(Interface, &Layout, ClipName, i)) { - AddAnimationBlockAtCurrentTime(i + 1, SelectedAnimationLayer, AnimationSystem); + AddAnimationBlockAtCurrentTime(i + 1, SelectedAnimationLayerHandle, AnimationSystem); } } } @@ -574,14 +612,15 @@ internal void AnimationTimeline_Render(panel Panel, rect2 PanelBounds, render_command_buffer* RenderBuffer, app_state* State, context Context) { animation_timeline_state* TimelineState = (animation_timeline_state*)Panel.PanelStateMemory; + // TODO(pjs): SelectedAnimationBlockHandle should be a property of animation_timeline_state + // unless its used elsewhere. Audit later gs_list_handle SelectedBlockHandle = State->SelectedAnimationBlockHandle; - ui_interface* Interface = &State->Interface; animation_system* AnimationSystem = &State->AnimationSystem; rect2 TitleBarBounds, PanelContentsBounds; - RectHSplitAtDistanceFromTop(PanelBounds, Interface->Style.RowHeight, &TitleBarBounds, &PanelContentsBounds); rect2 AnimationListBounds, TimelineBounds; + RectHSplitAtDistanceFromTop(PanelBounds, Interface->Style.RowHeight, &TitleBarBounds, &PanelContentsBounds); RectVSplitAtDistanceFromLeft(PanelContentsBounds, 300, &AnimationListBounds, &TimelineBounds); ui_FillRect(Interface, TitleBarBounds, Interface->Style.PanelBGColors[0]); @@ -589,13 +628,15 @@ AnimationTimeline_Render(panel Panel, rect2 PanelBounds, render_command_buffer* ui_StartRow(&TitleBarLayout, 3); { if (ui_LayoutButton(Interface, &TitleBarLayout, MakeString("Pause"))) - { - State->AnimationSystem.TimelineShouldAdvance = true; - } - if (ui_LayoutButton(Interface, &TitleBarLayout, MakeString("Play"))) { State->AnimationSystem.TimelineShouldAdvance = false; } + + if (ui_LayoutButton(Interface, &TitleBarLayout, MakeString("Play"), (State->AnimationSystem.TimelineShouldAdvance ? PinkV4 : BlackV4), v4{.3f, .3f, .3f, 1.0f}, TealV4)) + { + State->AnimationSystem.TimelineShouldAdvance = true; + } + if (ui_LayoutButton(Interface, &TitleBarLayout, MakeString("Stop"))) { State->AnimationSystem.TimelineShouldAdvance = false; diff --git a/src/app/engine/animation/foldhaus_animation.h b/src/app/engine/animation/foldhaus_animation.h index a7444b5..8da6b92 100644 --- a/src/app/engine/animation/foldhaus_animation.h +++ b/src/app/engine/animation/foldhaus_animation.h @@ -35,27 +35,47 @@ struct anim_layer blend_mode BlendMode; }; +struct anim_layer_array +{ + anim_layer* Values; + u32 Count; + u32 CountMax; +}; + struct animation { - anim_layer* Layers; - u32 LayersCount; - u32 LayersMax; - + anim_layer_array Layers; gs_list Blocks; frame_range PlayableRange; }; +struct animation_array +{ + animation* Values; + u32 Count; + u32 CountMax; +}; + +struct animation_frame +{ + // NOTE(pjs): These are all parallel arrays of equal length + animation_block* Blocks; + b8* BlocksFilled; + + u32 BlocksCountMax; + u32 BlocksCount; +}; + #define ANIMATION_SYSTEM_LAYERS_MAX 128 #define ANIMATION_SYSTEM_BLOCKS_MAX 128 struct animation_system { gs_memory_arena* Storage; + animation_array Animations; - gs_list Blocks; - anim_layer* Layers; - u32 LayersCount; - u32 LayersMax; + frame_range PlayableRange_; + gs_list Blocks_; // NOTE(Peter): The frame currently being displayed/processed. you // can see which frame you're on by looking at the time slider on the timeline @@ -65,10 +85,107 @@ struct animation_system r32 SecondsPerFrame; b32 TimelineShouldAdvance; - // Timeline - frame_range PlayableRange; }; +////////////////////////// +// +// Anim Layers Array + +internal anim_layer_array +AnimLayerArray_Create(gs_memory_arena* Storage, u32 CountMax) +{ + anim_layer_array Result = {0}; + Result.CountMax = CountMax; + Result.Values = PushArray(Storage, anim_layer, Result.CountMax); + return Result; +} + +internal u32 +AnimLayerArray_Push(anim_layer_array* Array, anim_layer Value) +{ + Assert(Array->Count < Array->CountMax); + u32 Index = Array->Count++; + Array->Values[Index] = Value; + return Index; +} + +internal u32 +AnimLayerArray_Remove(anim_layer_array* Array, u32 Index) +{ + Assert(Index < Array->Count); + for (u32 i = Index; i < Array->Count - 1; i++) + { + Array->Values[i] = Array->Values[i + 1]; + } +} + +////////////////////////// +// +// Animation Array + +internal animation_array +AnimationArray_Create(gs_memory_arena* Storage, u32 CountMax) +{ + animation_array Result = {0}; + Result.CountMax = CountMax; + Result.Values = PushArray(Storage, animation, Result.CountMax); + return Result; +} + +internal u32 +AnimationArray_Push(animation_array* Array, animation Value) +{ + Assert(Array->Count < Array->CountMax); + u32 Index = Array->Count++; + Array->Values[Index] = Value; + return Index; +} + +////////////////////////// +// +// Animation + +internal u32 +Animation_AddLayer(animation* Animation, anim_layer Layer) +{ + return AnimLayerArray_Push(&Animation->Layers, Layer); +} + +internal u32 +Animation_AddLayer (animation* Animation, gs_string Name, blend_mode BlendMode, animation_system* System) +{ + anim_layer NewLayer = {0}; + NewLayer.Name = PushStringF(System->Storage, 256, "%S", Name); + NewLayer.BlendMode = BlendMode; + + return Animation_AddLayer(Animation, NewLayer); +} + +internal void +Animation_RemoveLayer (animation* Animation, u32 LayerIndex) +{ + AnimLayerArray_Remove(&Animation->Layers, LayerIndex); + for (u32 i = Animation->Blocks.Used -= 1; i >= 0; i--) + { + gs_list_entry* Entry = Animation->Blocks.GetEntryAtIndex(i); + if (EntryIsFree(Entry)) { continue; } + + animation_block* Block = &Entry->Value; + if (Block->Layer > LayerIndex) + { + Block->Layer -= 1; + } + else if (Block->Layer == LayerIndex) + { + Animation->Blocks.FreeElementAtIndex(i); + } + } +} + +////////////////////////// +// +// + internal u32 SecondsToFrames(r32 Seconds, animation_system System) { @@ -77,7 +194,7 @@ SecondsToFrames(r32 Seconds, animation_system System) } inline b32 -FrameIsInRange(s32 Frame, frame_range Range) +FrameIsInRange(frame_range Range, s32 Frame) { b32 Result = (Frame >= Range.Min) && (Frame <= Range.Max); return Result; @@ -123,67 +240,64 @@ ClampFrameToRange(s32 Frame, frame_range Range) // Blocks internal gs_list_handle -AddAnimationBlock(u32 StartFrame, s32 EndFrame, u32 AnimationProcHandle, u32 Layer, animation_system* AnimationSystem) +Animation_AddBlock(animation* Animation, u32 StartFrame, s32 EndFrame, u32 AnimationProcHandle, u32 LayerIndex) { - gs_list_handle Result = {0}; + Assert(LayerIndex < Animation->Layers.Count); + animation_block NewBlock = {0}; NewBlock.Range.Min = StartFrame; NewBlock.Range.Max = EndFrame; NewBlock.AnimationProcHandle = AnimationProcHandle; - NewBlock.Layer = Layer; - Result = AnimationSystem->Blocks.PushElementOnList(NewBlock); + NewBlock.Layer = LayerIndex; + + gs_list_handle Result = Animation->Blocks.PushElementOnList(NewBlock); return Result; } internal void -RemoveAnimationBlock(gs_list_handle AnimationBlockHandle, animation_system* AnimationSystem) +Animation_RemoveBlock(animation* Animation, gs_list_handle AnimationBlockHandle) { Assert(ListHandleIsValid(AnimationBlockHandle)); - AnimationSystem->Blocks.FreeElementWithHandle(AnimationBlockHandle); + Animation->Blocks.FreeElementWithHandle(AnimationBlockHandle); } // Layers -internal u32 -AddLayer (gs_string Name, animation_system* AnimationSystem, blend_mode BlendMode = BlendMode_Overwrite) + +// System + +internal animation* +AnimationSystem_GetActiveAnimation(animation_system* System) { - // NOTE(Peter): If this assert fires its time to make the layer buffer system - // resizable. - Assert(AnimationSystem->LayersCount < AnimationSystem->LayersMax); + // TODO(pjs): need a way to specify the active animation + return System->Animations.Values + 0; +} + +internal animation_frame +AnimationSystem_CalculateAnimationFrame(animation_system* System, gs_memory_arena* Arena) +{ + animation* ActiveAnim = AnimationSystem_GetActiveAnimation(System); + + animation_frame Result = {0}; + Result.BlocksCountMax = ActiveAnim->Layers.Count; + Result.Blocks = PushArray(Arena, animation_block, Result.BlocksCountMax); + Result.BlocksFilled = PushArray(Arena, b8, Result.BlocksCountMax); + + for (u32 i = 0; i < ActiveAnim->Blocks.Used; i++) + { + gs_list_entry* BlockEntry = ActiveAnim->Blocks.GetEntryAtIndex(i); + if (EntryIsFree(BlockEntry)) { continue; } + + animation_block Block = BlockEntry->Value; + + if (FrameIsInRange(Block.Range, System->CurrentFrame)){ continue; } + + Result.BlocksFilled[Block.Layer] = true; + Result.Blocks[Block.Layer] = Block; + Result.BlocksCount++; + } - u32 Result = 0; - Result = AnimationSystem->LayersCount++; - anim_layer* NewLayer = AnimationSystem->Layers + Result; - *NewLayer = {0}; - NewLayer->Name = MakeString(PushArray(AnimationSystem->Storage, char, Name.Length), Name.Length); - PrintF(&NewLayer->Name, "%S", Name); - NewLayer->BlendMode = BlendMode; return Result; } -internal void -RemoveLayer (u32 LayerIndex, animation_system* AnimationSystem) -{ - Assert(LayerIndex < AnimationSystem->LayersMax); - Assert(LayerIndex < AnimationSystem->LayersCount); - for (u32 i = LayerIndex; i < AnimationSystem->LayersCount - 1; i++) - { - AnimationSystem->Layers[i] = AnimationSystem->Layers[i + 1]; - } - for (u32 i = AnimationSystem->Blocks.Used -= 1; i >= 0; i--) - { - gs_list_entry* Entry = AnimationSystem->Blocks.GetEntryAtIndex(i); - if (EntryIsFree(Entry)) { continue; } - animation_block* Block = &Entry->Value; - if (Block->Layer > LayerIndex) - { - Block->Layer -= 1; - } - else if (Block->Layer == LayerIndex) - { - AnimationSystem->Blocks.FreeElementAtIndex(i); - } - } - AnimationSystem->LayersCount -= 1; -} #define FOLDHAUS_ANIMATION #endif // FOLDHAUS_ANIMATION \ No newline at end of file diff --git a/src/app/foldhaus_app.cpp b/src/app/foldhaus_app.cpp index ed12a0a..e2b4756 100644 --- a/src/app/foldhaus_app.cpp +++ b/src/app/foldhaus_app.cpp @@ -136,16 +136,25 @@ INITIALIZE_APPLICATION(InitializeApplication) 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; - State->AnimationSystem.PlayableRange.Min = 0; - State->AnimationSystem.PlayableRange.Max = SecondsToFrames(15, State->AnimationSystem); - State->AnimationSystem.LayersMax = 32; - State->AnimationSystem.Layers = PushArray(&State->Permanent, anim_layer, State->AnimationSystem.LayersMax); - AddLayer(MakeString("Base Layer"), &State->AnimationSystem, BlendMode_Overwrite); - AddLayer(MakeString("Color Layer"), &State->AnimationSystem, BlendMode_Multiply); - AddLayer(MakeString("Sparkles"), &State->AnimationSystem, BlendMode_Add); + + animation Anim = {0}; + 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); + + AnimationArray_Push(&State->AnimationSystem.Animations, Anim); + + } // End Animation Playground @@ -230,14 +239,18 @@ UPDATE_AND_RENDER(UpdateAndRender) HandleInput(State, State->WindowBounds, InputQueue, Context->Mouse, *Context); - 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 > State->AnimationSystem.PlayableRange.Max) - { - State->AnimationSystem.CurrentFrame = 0; + { + 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; + } } } @@ -247,31 +260,18 @@ UPDATE_AND_RENDER(UpdateAndRender) State->AnimationSystem.LastUpdatedFrame = CurrentFrame; r32 FrameTime = CurrentFrame * State->AnimationSystem.SecondsPerFrame; - u32 CurrentBlocksMax = State->AnimationSystem.LayersCount; - b8* CurrentBlocksFilled = PushArray(State->Transient, b8, CurrentBlocksMax); - ZeroArray(CurrentBlocksFilled, b8, CurrentBlocksMax); - animation_block* CurrentBlocks = PushArray(State->Transient, animation_block, CurrentBlocksMax); + animation_frame CurrFrame = AnimationSystem_CalculateAnimationFrame(&State->AnimationSystem, State->Transient); - for (u32 i = 0; i < State->AnimationSystem.Blocks.Used; i++) - { - gs_list_entry* BlockEntry = State->AnimationSystem.Blocks.GetEntryAtIndex(i); - if (EntryIsFree(BlockEntry)) { continue; } - animation_block Block = BlockEntry->Value; - if (CurrentFrame < Block.Range.Min || CurrentFrame > Block.Range.Max) { continue; } - CurrentBlocksFilled[Block.Layer] = true; - CurrentBlocks[Block.Layer] = Block; - } - - led_buffer* LayerLEDBuffers = PushArray(State->Transient, led_buffer, CurrentBlocksMax); + 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 < CurrentBlocksMax; Layer++) + for (u32 Layer = 0; Layer < CurrFrame.BlocksCountMax; Layer++) { - if (!CurrentBlocksFilled[Layer]) { continue; } - animation_block Block = CurrentBlocks[Layer]; + if (!CurrFrame.BlocksFilled[Layer]) { continue; } + animation_block Block = CurrFrame.Blocks[Layer]; // Prep Temp Buffer LayerLEDBuffers[Layer] = *AssemblyLedBuffer; @@ -287,11 +287,12 @@ UPDATE_AND_RENDER(UpdateAndRender) // Consolidate Temp Buffers // We do this in reverse order so that they go from top to bottom - for (u32 Layer = 0; Layer < CurrentBlocksMax; Layer++) + animation* ActiveAnim = AnimationSystem_GetActiveAnimation(&State->AnimationSystem); + for (u32 Layer = 0; Layer < CurrFrame.BlocksCountMax; Layer++) { - if (!CurrentBlocksFilled[Layer]) { continue; } + if (!CurrFrame.BlocksFilled[Layer]) { continue; } - switch (State->AnimationSystem.Layers[Layer].BlendMode) + switch (ActiveAnim->Layers.Values[Layer].BlendMode) { case BlendMode_Overwrite: { diff --git a/src/gs_libs/gs_types.cpp b/src/gs_libs/gs_types.cpp index 4db796a..bbcb97a 100644 --- a/src/gs_libs/gs_types.cpp +++ b/src/gs_libs/gs_types.cpp @@ -743,6 +743,15 @@ internal rect2 MakeRect2MinDim(v2 Min, v2 Dim) return Result; } +internal rect2 MakeRect2CenterDim(v2 Center, v2 Dim) +{ + v2 HalfDim = Dim / 2; + rect2 Result = {0}; + Result.Min = Center - HalfDim; + Result.Max = Center + HalfDim; + return Result; +} + internal b32 ValueInRangeR32(r32 Min, r32 Max, r32 V) { return ((V >= Min) && (V <= Max)); @@ -2442,6 +2451,19 @@ FreeMemoryArena(gs_memory_arena* Arena) #define PushArray(arena, type, count) (type*)(PushSize_((arena), sizeof(type) * (count), FileNameAndLineNumberString).Memory) #define PushString(arena, length) MakeString(PushArray((arena), char, (length)), 0, (length)); +internal gs_string +PushStringF(gs_memory_arena* Arena, u32 MaxLength, char* Format, ...) +{ + gs_string Result = PushString(Arena, MaxLength); + + va_list Args; + va_start(Args, Format); + PrintFArgsList(&Result, Format, Args); + va_end(Args); + + return Result; +} + internal void ClearArena(gs_memory_arena* Arena) {