715 lines
18 KiB
C
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
|