From 6d876bb767c3eff313c67af1a598af8cd01a2e1c Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 7 May 2026 19:37:30 -0500 Subject: [PATCH] Anim tweak --- src/dusk/animation/animation.c | 71 ++++++++++++------- src/dusk/animation/animation.h | 68 ++++++++++-------- .../script/module/animation/moduleanimation.h | 66 +++++++++++------ 3 files changed, 130 insertions(+), 75 deletions(-) diff --git a/src/dusk/animation/animation.c b/src/dusk/animation/animation.c index f9cca2b2..43f5282b 100644 --- a/src/dusk/animation/animation.c +++ b/src/dusk/animation/animation.c @@ -12,64 +12,85 @@ void animationInit(animation_t *anim) { memoryZero(anim, sizeof(animation_t)); } -void animationAddKeyframe( +animationproperty_t *animationAddProperty(animation_t *anim, float_t *target) { + assertTrue( + anim->propertyCount < ANIMATION_PROPERTY_COUNT_MAX, + "Property count exceeds ANIMATION_PROPERTY_COUNT_MAX" + ); + animationproperty_t *prop = &anim->properties[anim->propertyCount++]; + prop->target = target; + prop->keyframeCount = 0; + return prop; +} + +void animationPropertyAddKeyframe( animation_t *anim, + animationproperty_t *prop, float_t time, float_t value, easingtype_t easing ) { assertTrue( - anim->keyframeCount < ANIMATION_KEYFRAME_COUNT_MAX, + prop->keyframeCount < ANIMATION_KEYFRAME_COUNT_MAX, "Keyframe count exceeds ANIMATION_KEYFRAME_COUNT_MAX" ); - uint8_t i = anim->keyframeCount++; - anim->keyframes[i].time = time; - anim->keyframes[i].value = value; - anim->keyframes[i].easing = easing; + uint8_t i = prop->keyframeCount++; + prop->keyframes[i].time = time; + prop->keyframes[i].value = value; + prop->keyframes[i].easing = easing; if(time > anim->duration) anim->duration = time; } -float_t animationGetValue(const animation_t *anim) { - if(anim->keyframeCount == 0) return 0.0f; +static float_t animationPropertyGetValue( + const animationproperty_t *prop, + float_t time +) { + if(prop->keyframeCount == 0) return 0.0f; - uint8_t last = anim->keyframeCount - 1; + uint8_t last = prop->keyframeCount - 1; - if(anim->keyframeCount == 1) return anim->keyframes[0].value; - if(anim->time <= anim->keyframes[0].time) return anim->keyframes[0].value; - if(anim->time >= anim->keyframes[last].time) { - return anim->keyframes[last].value; - } + if(prop->keyframeCount == 1) return prop->keyframes[0].value; + if(time <= prop->keyframes[0].time) return prop->keyframes[0].value; + if(time >= prop->keyframes[last].time) return prop->keyframes[last].value; for(uint8_t i = 0; i < last; i++) { - const keyframe_t *a = &anim->keyframes[i]; - const keyframe_t *b = &anim->keyframes[i + 1]; - if(anim->time < a->time || anim->time >= b->time) continue; - float_t t = (anim->time - a->time) / (b->time - a->time); + const keyframe_t *a = &prop->keyframes[i]; + const keyframe_t *b = &prop->keyframes[i + 1]; + if(time < a->time || time >= b->time) continue; + float_t t = (time - a->time) / (b->time - a->time); t = easingApply(a->easing, t); return a->value + (b->value - a->value) * t; } - return anim->keyframes[last].value; + return prop->keyframes[last].value; } -float_t animationUpdate(animation_t *anim, float_t delta) { - if(anim->complete && !anim->loop) return animationGetValue(anim); +void animationUpdate(animation_t *anim, float_t delta) { + if( + (anim->flags & ANIMATION_FLAG_FINISHED) && + !(anim->flags & ANIMATION_FLAG_LOOP_ENABLED) + ) return; + anim->flags |= ANIMATION_FLAG_STARTED; anim->time += delta; if(anim->duration > 0.0f && anim->time >= anim->duration) { - if(anim->loop) { + if(anim->flags & ANIMATION_FLAG_LOOP_ENABLED) { anim->time = fmodf(anim->time, anim->duration); } else { anim->time = anim->duration; - anim->complete = true; + anim->flags |= ANIMATION_FLAG_FINISHED; } } - return animationGetValue(anim); + for(uint8_t i = 0; i < anim->propertyCount; i++) { + animationproperty_t *prop = &anim->properties[i]; + if(!prop->target) continue; + *prop->target = animationPropertyGetValue(prop, anim->time); + } } void animationReset(animation_t *anim) { anim->time = 0.0f; - anim->complete = false; + anim->flags &= ~(ANIMATION_FLAG_FINISHED | ANIMATION_FLAG_STARTED); } diff --git a/src/dusk/animation/animation.h b/src/dusk/animation/animation.h index 74319c11..d166ba81 100644 --- a/src/dusk/animation/animation.h +++ b/src/dusk/animation/animation.h @@ -7,57 +7,69 @@ #include "keyframe.h" #include "error/error.h" +#define ANIMATION_PROPERTY_COUNT_MAX 8 + +typedef uint8_t animationflags_t; +#define ANIMATION_FLAG_FINISHED ((animationflags_t)(1 << 0)) +#define ANIMATION_FLAG_STARTED ((animationflags_t)(1 << 1)) +#define ANIMATION_FLAG_LOOP_ENABLED ((animationflags_t)(1 << 2)) + typedef struct { + float_t *target; keyframe_t keyframes[ANIMATION_KEYFRAME_COUNT_MAX]; uint8_t keyframeCount; +} animationproperty_t; + +typedef struct { + animationproperty_t properties[ANIMATION_PROPERTY_COUNT_MAX]; + uint8_t propertyCount; float_t time; float_t duration; - bool_t loop; - bool_t complete; + animationflags_t flags; } animation_t; /** * Zeroes an animation struct. - * - * @param anim The animation to initialize. */ void animationInit(animation_t *anim); /** - * Appends a keyframe. Keyframes must be added in ascending time order. - * - * @param anim The animation to modify. - * @param time Time in seconds for this keyframe. - * @param value Value at this keyframe. - * @param easing Easing applied to the segment after this keyframe. + * Adds a new animated property. The caller owns target and must keep it valid + * for the lifetime of the animation. */ -void animationAddKeyframe( +animationproperty_t *animationAddProperty(animation_t *anim, float_t *target); + +/** + * Appends a keyframe to a property. Keyframes must be added in ascending time + * order. Updates the animation's duration. + */ +void animationPropertyAddKeyframe( animation_t *anim, + animationproperty_t *prop, float_t time, float_t value, easingtype_t easing ); /** - * Advances the animation by delta seconds and returns the current value. - * - * @param anim The animation to update. - * @param delta Seconds elapsed since last update. - * @return Interpolated value at the new time. + * Advances the animation by delta seconds and writes interpolated values to + * each property's target pointer. */ -float_t animationUpdate(animation_t *anim, float_t delta); +void animationUpdate(animation_t *anim, float_t delta); /** - * Returns the interpolated value at the current time without advancing. - * - * @param anim The animation to read. - * @return Interpolated value. - */ -float_t animationGetValue(const animation_t *anim); - -/** - * Resets the animation to the beginning. - * - * @param anim The animation to reset. + * Resets the animation to the beginning, clearing Started and Finished flags. */ void animationReset(animation_t *anim); + +static inline bool_t animationIsFinished(const animation_t *anim) { + return (anim->flags & ANIMATION_FLAG_FINISHED) != 0; +} + +static inline bool_t animationIsStarted(const animation_t *anim) { + return (anim->flags & ANIMATION_FLAG_STARTED) != 0; +} + +static inline bool_t animationIsLooping(const animation_t *anim) { + return (anim->flags & ANIMATION_FLAG_LOOP_ENABLED) != 0; +} diff --git a/src/dusk/script/module/animation/moduleanimation.h b/src/dusk/script/module/animation/moduleanimation.h index 10825035..0cd434b5 100644 --- a/src/dusk/script/module/animation/moduleanimation.h +++ b/src/dusk/script/module/animation/moduleanimation.h @@ -11,12 +11,25 @@ static scriptproto_t MODULE_ANIMATION_PROTO; +// JS animation wraps a single-property animation; value is the managed target. +typedef struct { + animation_t anim; + float_t value; +} jsAnimation_t; + +static inline jsAnimation_t *moduleAnimationGetWrapper( + const jerry_call_info_t *callInfo +) { + return (jsAnimation_t *)scriptProtoGetValue( + &MODULE_ANIMATION_PROTO, callInfo->this_value + ); +} + +// Returns the animation_t pointer (first field of jsAnimation_t — same address). static inline animation_t *moduleAnimationGet( const jerry_call_info_t *callInfo ) { - return (animation_t *)scriptProtoGetValue( - &MODULE_ANIMATION_PROTO, callInfo->this_value - ); + return (animation_t *)moduleAnimationGetWrapper(callInfo); } // Extracts an easingtype_t from a JS value. Accepts either a plain number @@ -129,13 +142,18 @@ static jerry_value_t moduleAnimationFireComplete( } moduleBaseFunction(moduleAnimationConstructor) { - animation_t *anim = (animation_t *)memoryAllocate(sizeof(animation_t)); - animationInit(anim); + jsAnimation_t *janim = (jsAnimation_t *)memoryAllocate(sizeof(jsAnimation_t)); + animationInit(&janim->anim); + janim->value = 0.0f; if(argc > 0 && jerry_value_is_boolean(args[argc - 1])) { - anim->loop = jerry_value_is_true(args[argc - 1]); + if(jerry_value_is_true(args[argc - 1])) { + janim->anim.flags |= ANIMATION_FLAG_LOOP_ENABLED; + } } + animationproperty_t *prop = animationAddProperty(&janim->anim, &janim->value); + if(argc > 0 && jerry_value_is_array(args[0])) { jerry_length_t len = jerry_array_length(args[0]); for(jerry_length_t i = 0; i < len; i++) { @@ -160,7 +178,7 @@ moduleBaseFunction(moduleAnimationConstructor) { jerry_value_free(eVal); jerry_value_free(kf); - animationAddKeyframe(anim, t, v, e); + animationPropertyAddKeyframe(&janim->anim, prop, t, v, e); } // Store the JS keyframes array for onReach callback detection. @@ -176,29 +194,29 @@ moduleBaseFunction(moduleAnimationConstructor) { jerry_value_free(falseVal); jerry_object_set_native_ptr( - callInfo->this_value, &MODULE_ANIMATION_PROTO.info, anim + callInfo->this_value, &MODULE_ANIMATION_PROTO.info, janim ); return jerry_undefined(); } moduleBaseFunction(moduleAnimationUpdate) { - animation_t *anim = moduleAnimationGet(callInfo); - if(!anim) return moduleBaseThrow("Invalid Animation instance"); + jsAnimation_t *janim = moduleAnimationGetWrapper(callInfo); + if(!janim) return moduleBaseThrow("Invalid Animation instance"); if(argc < 1 || !jerry_value_is_number(args[0])) { return moduleBaseThrow("update() expects a number delta"); } - float_t prevTime = anim->time; + float_t prevTime = janim->anim.time; float_t delta = (float_t)jerry_value_as_number(args[0]); - float_t value = animationUpdate(anim, delta); + animationUpdate(&janim->anim, delta); jerry_value_t kfResult = moduleAnimationFireKeyframes( - callInfo->this_value, prevTime, anim->time + callInfo->this_value, prevTime, janim->anim.time ); if(jerry_value_is_exception(kfResult)) return kfResult; jerry_value_free(kfResult); - if(anim->complete) { + if(animationIsFinished(&janim->anim)) { jerry_value_t cResult = moduleAnimationFireComplete( callInfo->this_value ); @@ -206,13 +224,13 @@ moduleBaseFunction(moduleAnimationUpdate) { jerry_value_free(cResult); } - return jerry_number((double)value); + return jerry_number((double)janim->value); } moduleBaseFunction(moduleAnimationGetValue) { - animation_t *anim = moduleAnimationGet(callInfo); - if(!anim) return moduleBaseThrow("Invalid Animation instance"); - return jerry_number((double)animationGetValue(anim)); + jsAnimation_t *janim = moduleAnimationGetWrapper(callInfo); + if(!janim) return moduleBaseThrow("Invalid Animation instance"); + return jerry_number((double)janim->value); } moduleBaseFunction(moduleAnimationReset) { @@ -231,19 +249,23 @@ moduleBaseFunction(moduleAnimationReset) { moduleBaseFunction(moduleAnimationGetComplete) { animation_t *anim = moduleAnimationGet(callInfo); - return anim ? jerry_boolean(anim->complete) : jerry_boolean(false); + return anim ? jerry_boolean(animationIsFinished(anim)) : jerry_boolean(false); } moduleBaseFunction(moduleAnimationGetLoop) { animation_t *anim = moduleAnimationGet(callInfo); - return anim ? jerry_boolean(anim->loop) : jerry_boolean(false); + return anim ? jerry_boolean(animationIsLooping(anim)) : jerry_boolean(false); } moduleBaseFunction(moduleAnimationSetLoop) { animation_t *anim = moduleAnimationGet(callInfo); if(!anim) return moduleBaseThrow("Invalid Animation instance"); if(argc < 1) return moduleBaseThrow("Expected boolean"); - anim->loop = jerry_value_is_true(args[0]); + if(jerry_value_is_true(args[0])) { + anim->flags |= ANIMATION_FLAG_LOOP_ENABLED; + } else { + anim->flags &= ~ANIMATION_FLAG_LOOP_ENABLED; + } return jerry_undefined(); } @@ -261,7 +283,7 @@ static void moduleAnimation(void) { scriptProtoInit( &MODULE_ANIMATION_PROTO, "Animation", - sizeof(animation_t), + sizeof(jsAnimation_t), moduleAnimationConstructor );