// // File: foldhaus_animation.h // Author: Peter Slattery // Creation Date: 2020-01-01 // #ifndef FOLDHAUS_ANIMATION #define ANIMATION_PROC(name) void name(led_buffer* Leds, assembly Assembly, r32 Time, gs_memory_arena* Transient, u8* UserData) typedef ANIMATION_PROC(animation_proc); struct frame_range { s32 Min; s32 Max; }; struct animation_pattern_handle { s32 IndexPlusOne; }; // NOTE(pjs): An animation block is a time range paired with an // animation_pattern (see below). While a timeline's current time // is within the range of a block, that particular block's animation // will run struct animation_block { frame_range Range; animation_pattern_handle AnimationProcHandle; u32 Layer; }; struct animation_block_array { u32* Generations; animation_block* Values; u32 Count; u32 CountMax; }; enum blend_mode { BlendMode_Overwrite, BlendMode_Add, BlendMode_Multiply, BlendMode_Count, }; // TODO(pjs): This really doesn't belong here global gs_const_string BlendModeStrings[] = { ConstString("Overwrite"), ConstString("Add"), ConstString("Multiply"), ConstString("Count"), }; struct anim_layer { gs_string Name; blend_mode BlendMode; }; struct anim_layer_array { anim_layer* Values; u32 Count; u32 CountMax; }; // NOTE(pjs): An animation is a stack of layers, each of which // is a timeline of animation blocks. struct animation { gs_string Name; anim_layer_array Layers; // TODO(pjs): Pretty sure Blocks_ should be obsolete and // Layers should contain their own blocks animation_block_array Blocks_; frame_range PlayableRange; // The information / path to the file where this animation is to be saved / where it is loaded from gs_file_info FileInfo; }; struct animation_handle { s32 Index; }; internal bool IsValid (animation_handle H) { return H.Index >= 0; } internal void Clear (animation_handle* H) { H->Index = -1; } internal bool AnimHandlesAreEqual (animation_handle A, animation_handle B) { return A.Index == B.Index; } struct animation_array { animation* Values; u32 Count; u32 CountMax; }; struct animation_layer_frame { animation_block Hot; bool HasHot; animation_block NextHot; bool HasNextHot; r32 NextHotOpacity; blend_mode BlendMode; }; // NOTE(pjs): This is an evaluated frame - across all layers in an // animation, these are the blocks that need to be run struct animation_frame { animation_layer_frame* Layers; u32 LayersCount; }; enum animation_repeat_mode { AnimationRepeat_Single, AnimationRepeat_Loop, AnimationRepeat_Invalid, }; global gs_const_string AnimationRepeatModeStrings[] = { ConstString("Repeat Single"), ConstString("Loop"), ConstString("Invalid"), }; struct animation_fade_group { animation_handle From; animation_handle To; r32 FadeElapsed; r32 FadeDuration; }; #define ANIMATION_SYSTEM_LAYERS_MAX 128 #define ANIMATION_SYSTEM_BLOCKS_MAX 128 struct animation_system { gs_memory_arena* Storage; animation_array Animations; animation_repeat_mode RepeatMode; // 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 // panel animation_handle ActiveAnimationHandle_; animation_fade_group ActiveFadeGroup; s32 CurrentFrame; s32 LastUpdatedFrame; r32 SecondsPerFrame; b32 TimelineShouldAdvance; }; // NOTE(pjs): A Pattern is a named procedure which can be used as // an element of an animation. Patterns are sequenced on a timeline // and blended via layers to create an animation struct animation_pattern { char* Name; s32 NameLength; animation_proc* Proc; }; struct animation_pattern_array { animation_pattern* Values; u32 Count; u32 CountMax; }; // Serialization enum animation_field { AnimField_FileIdent, AnimField_AnimName, AnimField_LayersCount, AnimField_BlocksCount, AnimField_PlayableRange, AnimField_PlayableRangeMin, AnimField_PlayableRangeMax, AnimField_LayersArray, AnimField_Layer, AnimField_LayerName, AnimField_LayerBlendMode, AnimField_BlocksArray, AnimField_Block, AnimField_BlockFrameRange, AnimField_BlockFrameRangeMin, AnimField_BlockFrameRangeMax, AnimField_BlockLayerIndex, AnimField_BlockAnimName, AnimField_Count, }; global gs_const_string AnimationFieldStrings[] = { ConstString("lumenarium_animation_file"), // AnimField_FileIdent ConstString("animation_name"),// AnimField_AnimName ConstString("layers_count"),// AnimField_LayersCount ConstString("blocks_count"),// AnimField_BlocksCount ConstString("playable_range"),// AnimField_PlayableRange ConstString("min"),// AnimField_PlayableRangeMin ConstString("max"),// AnimField_PlayableRangeMax ConstString("layers"),// AnimField_LayersArray ConstString("layer"),// AnimField_Layer ConstString("name"),// AnimField_LayerName ConstString("blend"),// AnimField_LayerBlendMode ConstString("blocks"),// AnimField_BlocksArray ConstString("block"),// AnimField_Block ConstString("frame_range"),// AnimField_BlockFrameRange ConstString("min"),// AnimField_BlockFrameRangeMin ConstString("max"),// AnimField_BlockFrameRangeMax ConstString("layer_index"),// AnimField_BlockLayerIndex ConstString("animation_name"),// AnimField_BlockAnimName }; ////////////////////////// // // Patterns List internal animation_pattern_array Patterns_Create(gs_memory_arena* Arena, s32 CountMax) { animation_pattern_array Result = {0}; Result.CountMax = CountMax; Result.Values = PushArray(Arena, animation_pattern, Result.CountMax); return Result; } #define Patterns_PushPattern(array, proc) Patterns_PushPattern_((array), (proc), Stringify(proc), sizeof(Stringify(proc)) - 1) internal void Patterns_PushPattern_(animation_pattern_array* Array, animation_proc* Proc, char* Name, u32 NameLength) { Assert(Array->Count < Array->CountMax); animation_pattern Pattern = {0}; Pattern.Name = Name; Pattern.NameLength = NameLength; Pattern.Proc = Proc; Array->Values[Array->Count++] = Pattern; } internal animation_pattern_handle Patterns_IndexToHandle(s32 Index) { animation_pattern_handle Result = {}; Result.IndexPlusOne = Index + 1; return Result; } internal animation_pattern Patterns_GetPattern(animation_pattern_array Patterns, animation_pattern_handle Handle) { animation_pattern Result = {0}; if (Handle.IndexPlusOne > 0) { u32 Index = Handle.IndexPlusOne - 1; Assert(Index < Patterns.Count); Result = Patterns.Values[Index]; } return Result; } ////////////////////////// // // Anim Block Array internal animation_block_array AnimBlockArray_Create(gs_memory_arena* Storage, u32 CountMax) { animation_block_array Result = {0}; Result.CountMax = CountMax; Result.Values = PushArray(Storage, animation_block, Result.CountMax); Result.Generations = PushArray(Storage, u32, Result.CountMax); return Result; } internal handle AnimBlockArray_Push(animation_block_array* Array, animation_block Value) { Assert(Array->Count < Array->CountMax); handle Result = {0}; Result.Index = Array->Count++; // NOTE(pjs): pre-increment so that generation 0 is always invalid Result.Generation = ++Array->Generations[Result.Index]; Array->Values[Result.Index] = Value; return Result; } internal void AnimBlockArray_Remove(animation_block_array* Array, handle Handle) { Assert(Handle.Index < Array->Count); Assert(Handle_IsValid(Handle)); Array->Generations[Handle.Index]++; } internal void AnimBlockArray_RemoveAt(animation_block_array* Array, u32 Index) { Assert(Index < Array->Count); handle Handle = {}; Handle.Index = Index; Handle.Generation = Array->Generations[Index]; AnimBlockArray_Remove(Array, Handle); } ////////////////////////// // // 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 void 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 animation_handle AnimationArray_Push(animation_array* Array, animation Value) { Assert(Array->Count < Array->CountMax); animation_handle Result = {0}; Result.Index = Array->Count++; Array->Values[Result.Index] = Value; return Result; } internal animation* AnimationArray_Get(animation_array Array, animation_handle Handle) { animation* Result = 0; if (IsValid(Handle) && Handle.Index < (s32)Array.Count) { Result = Array.Values + Handle.Index; } return Result; } internal animation* AnimationArray_GetSafe(animation_array Array, animation_handle Handle) { Assert(IsValid(Handle)); Assert(Handle.Index < (s32)Array.Count); return AnimationArray_Get(Array, Handle); } ////////////////////////// // // Animation internal handle Animation_AddBlock(animation* Animation, u32 StartFrame, s32 EndFrame, animation_pattern_handle AnimationProcHandle, u32 LayerIndex) { Assert(LayerIndex < Animation->Layers.Count); animation_block NewBlock = {0}; NewBlock.Range.Min = StartFrame; NewBlock.Range.Max = EndFrame; NewBlock.AnimationProcHandle = AnimationProcHandle; NewBlock.Layer = LayerIndex; handle Handle = AnimBlockArray_Push(&Animation->Blocks_, NewBlock); return Handle; } internal void Animation_RemoveBlock(animation* Animation, handle AnimHandle) { AnimBlockArray_Remove(&Animation->Blocks_, AnimHandle); } internal animation_block* Animation_GetBlockFromHandle(animation* Animation, handle AnimHandle) { animation_block* Result = 0; if (AnimHandle.Generation != 0 && Animation->Blocks_.Generations[AnimHandle.Index] == AnimHandle.Generation) { Result = Animation->Blocks_.Values + AnimHandle.Index; } return Result; } 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_.Count - 1; i >= 0; i--) { animation_block* Block = Animation->Blocks_.Values + i; if (Block->Layer > LayerIndex) { Block->Layer -= 1; } else if (Block->Layer == LayerIndex) { AnimBlockArray_RemoveAt(&Animation->Blocks_, i); } } } ////////////////////////// // // internal u32 SecondsToFrames(r32 Seconds, animation_system System) { u32 Result = Seconds * (1.0f / System.SecondsPerFrame); return Result; } inline bool FrameIsInRange(frame_range Range, s32 Frame) { bool Result = (Frame >= Range.Min) && (Frame <= Range.Max); return Result; } internal u32 GetFrameCount(frame_range Range) { u32 Result = (u32)Max(0, Range.Max - Range.Min); return Result; } internal r32 FrameToPercentRange(s32 Frame, frame_range Range) { r32 Result = (r32)(Frame - Range.Min); Result = Result / GetFrameCount(Range); return Result; } internal s32 PercentToFrameInRange(r32 Percent, frame_range Range) { s32 Result = Range.Min + (s32)(Percent * GetFrameCount(Range)); return Result; } internal s32 ClampFrameToRange(s32 Frame, frame_range Range) { s32 Result = Frame; if (Result < Range.Min) { Result = Range.Min; } else if (Result > Range.Max) { Result = Range.Max; } return Result; } // Blocks // Layers // Fade Group internal bool AnimationFadeGroup_ShouldRender (animation_fade_group FadeGroup) { return IsValid(FadeGroup.From); } internal void AnimationFadeGroup_Advance(animation_fade_group* Group) { Group->From = Group->To; Clear(&Group->To); Group->FadeElapsed = 0; Group->FadeDuration = 0; } internal void AnimationFadeGroup_Update(animation_fade_group* Group, r32 DeltaTime) { if (IsValid(Group->To)) { Group->FadeElapsed += DeltaTime; if (Group->FadeElapsed >= Group->FadeDuration) { AnimationFadeGroup_Advance(Group); } } } internal void AnimationFadeGroup_FadeTo(animation_fade_group* Group, animation_handle To, r32 Duration) { // complete current fade if there is one in progress if (IsValid(Group->To)) { AnimationFadeGroup_Advance(Group); } Group->To = To; Group->FadeDuration = Duration; } // System struct animation_system_desc { gs_memory_arena* Storage; u32 AnimArrayCount; r32 SecondsPerFrame; }; internal animation_system AnimationSystem_Init(animation_system_desc Desc) { animation_system Result = {}; Result.Storage = Desc.Storage; Result.Animations = AnimationArray_Create(Result.Storage, Desc.AnimArrayCount); Result.SecondsPerFrame = Desc.SecondsPerFrame; Clear(&Result.ActiveFadeGroup.From); Clear(&Result.ActiveFadeGroup.To); Result.ActiveFadeGroup.FadeElapsed = 0; return Result; } internal animation* AnimationSystem_GetActiveAnimation(animation_system* System) { return AnimationArray_Get(System->Animations, System->ActiveFadeGroup.From); } internal animation_frame AnimationSystem_CalculateAnimationFrame(animation_system* System, animation* Animation, gs_memory_arena* Arena) { animation_frame Result = {0}; Result.LayersCount = Animation->Layers.Count; Result.Layers = PushArray(Arena, animation_layer_frame, Result.LayersCount); ZeroArray(Result.Layers, animation_layer_frame, Result.LayersCount); for (u32 l = 0; l < Animation->Layers.Count; l++) { animation_layer_frame* Layer = Result.Layers + l; Layer->BlendMode = Animation->Layers.Values[l].BlendMode; } for (u32 i = 0; i < Animation->Blocks_.Count; i++) { animation_block Block = Animation->Blocks_.Values[i]; if (FrameIsInRange(Block.Range, System->CurrentFrame)) { animation_layer_frame* Layer = Result.Layers + Block.Layer; if (Layer->HasHot) { // NOTE(pjs): With current implementation, we don't allow // animations to hvae more than 2 concurrent blocks in the // timeline Assert(!Layer->HasNextHot); // NOTE(pjs): Make sure that Hot comes before NextHot if (Layer->Hot.Range.Min < Block.Range.Min) { Layer->NextHot = Block; } else { Layer->NextHot = Layer->Hot; Layer->Hot = Block; } Layer->HasNextHot = true; frame_range BlendRange = {}; BlendRange.Min = Layer->NextHot.Range.Min; BlendRange.Max = Layer->Hot.Range.Max; Layer->NextHotOpacity = FrameToPercentRange(System->CurrentFrame, BlendRange); } else { Layer->Hot = Block; Layer->NextHotOpacity = 0.0f; Layer->HasHot = true; } } } return Result; } internal void AnimationSystem_Update(animation_system* System, r32 DeltaTime) { if (!System->TimelineShouldAdvance) { return; } if (!AnimationFadeGroup_ShouldRender(System->ActiveFadeGroup)) { return; } AnimationFadeGroup_Update(&System->ActiveFadeGroup, DeltaTime); animation* ActiveAnim = AnimationSystem_GetActiveAnimation(System); // 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 System->CurrentFrame += 1; // Loop back to the beginning if (System->CurrentFrame > ActiveAnim->PlayableRange.Max) { switch (System->RepeatMode) { case AnimationRepeat_Single: { System->CurrentFrame = 0; }break; case AnimationRepeat_Loop: { // TODO(pjs): }break; InvalidDefaultCase; } } } inline bool AnimationSystem_NeedsRender(animation_system System) { bool Result = (System.CurrentFrame != System.LastUpdatedFrame); return Result; } #define FOLDHAUS_ANIMATION #endif // FOLDHAUS_ANIMATION