Lumenarium/src/app/engine/animation/foldhaus_animation.h

715 lines
18 KiB
C

//
// 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_string BlendModeStrings[] = {
MakeString("Overwrite"),
MakeString("Add"),
MakeString("Multiply"),
MakeString("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