Anim tweak

This commit is contained in:
2026-05-07 19:37:30 -05:00
parent 2be0fe9f06
commit 6d876bb767
3 changed files with 130 additions and 75 deletions
+46 -25
View File
@@ -12,64 +12,85 @@ void animationInit(animation_t *anim) {
memoryZero(anim, sizeof(animation_t)); 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, animation_t *anim,
animationproperty_t *prop,
float_t time, float_t time,
float_t value, float_t value,
easingtype_t easing easingtype_t easing
) { ) {
assertTrue( assertTrue(
anim->keyframeCount < ANIMATION_KEYFRAME_COUNT_MAX, prop->keyframeCount < ANIMATION_KEYFRAME_COUNT_MAX,
"Keyframe count exceeds ANIMATION_KEYFRAME_COUNT_MAX" "Keyframe count exceeds ANIMATION_KEYFRAME_COUNT_MAX"
); );
uint8_t i = anim->keyframeCount++; uint8_t i = prop->keyframeCount++;
anim->keyframes[i].time = time; prop->keyframes[i].time = time;
anim->keyframes[i].value = value; prop->keyframes[i].value = value;
anim->keyframes[i].easing = easing; prop->keyframes[i].easing = easing;
if(time > anim->duration) anim->duration = time; if(time > anim->duration) anim->duration = time;
} }
float_t animationGetValue(const animation_t *anim) { static float_t animationPropertyGetValue(
if(anim->keyframeCount == 0) return 0.0f; 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(prop->keyframeCount == 1) return prop->keyframes[0].value;
if(anim->time <= anim->keyframes[0].time) return anim->keyframes[0].value; if(time <= prop->keyframes[0].time) return prop->keyframes[0].value;
if(anim->time >= anim->keyframes[last].time) { if(time >= prop->keyframes[last].time) return prop->keyframes[last].value;
return anim->keyframes[last].value;
}
for(uint8_t i = 0; i < last; i++) { for(uint8_t i = 0; i < last; i++) {
const keyframe_t *a = &anim->keyframes[i]; const keyframe_t *a = &prop->keyframes[i];
const keyframe_t *b = &anim->keyframes[i + 1]; const keyframe_t *b = &prop->keyframes[i + 1];
if(anim->time < a->time || anim->time >= b->time) continue; if(time < a->time || time >= b->time) continue;
float_t t = (anim->time - a->time) / (b->time - a->time); float_t t = (time - a->time) / (b->time - a->time);
t = easingApply(a->easing, t); t = easingApply(a->easing, t);
return a->value + (b->value - a->value) * 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) { void animationUpdate(animation_t *anim, float_t delta) {
if(anim->complete && !anim->loop) return animationGetValue(anim); if(
(anim->flags & ANIMATION_FLAG_FINISHED) &&
!(anim->flags & ANIMATION_FLAG_LOOP_ENABLED)
) return;
anim->flags |= ANIMATION_FLAG_STARTED;
anim->time += delta; anim->time += delta;
if(anim->duration > 0.0f && anim->time >= anim->duration) { 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); anim->time = fmodf(anim->time, anim->duration);
} else { } else {
anim->time = anim->duration; 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) { void animationReset(animation_t *anim) {
anim->time = 0.0f; anim->time = 0.0f;
anim->complete = false; anim->flags &= ~(ANIMATION_FLAG_FINISHED | ANIMATION_FLAG_STARTED);
} }
+40 -28
View File
@@ -7,57 +7,69 @@
#include "keyframe.h" #include "keyframe.h"
#include "error/error.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 { typedef struct {
float_t *target;
keyframe_t keyframes[ANIMATION_KEYFRAME_COUNT_MAX]; keyframe_t keyframes[ANIMATION_KEYFRAME_COUNT_MAX];
uint8_t keyframeCount; uint8_t keyframeCount;
} animationproperty_t;
typedef struct {
animationproperty_t properties[ANIMATION_PROPERTY_COUNT_MAX];
uint8_t propertyCount;
float_t time; float_t time;
float_t duration; float_t duration;
bool_t loop; animationflags_t flags;
bool_t complete;
} animation_t; } animation_t;
/** /**
* Zeroes an animation struct. * Zeroes an animation struct.
*
* @param anim The animation to initialize.
*/ */
void animationInit(animation_t *anim); void animationInit(animation_t *anim);
/** /**
* Appends a keyframe. Keyframes must be added in ascending time order. * Adds a new animated property. The caller owns target and must keep it valid
* * for the lifetime of the animation.
* @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.
*/ */
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, animation_t *anim,
animationproperty_t *prop,
float_t time, float_t time,
float_t value, float_t value,
easingtype_t easing easingtype_t easing
); );
/** /**
* Advances the animation by delta seconds and returns the current value. * Advances the animation by delta seconds and writes interpolated values to
* * each property's target pointer.
* @param anim The animation to update.
* @param delta Seconds elapsed since last update.
* @return Interpolated value at the new time.
*/ */
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. * Resets the animation to the beginning, clearing Started and Finished flags.
*
* @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.
*/ */
void animationReset(animation_t *anim); 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;
}
@@ -11,12 +11,25 @@
static scriptproto_t MODULE_ANIMATION_PROTO; 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( static inline animation_t *moduleAnimationGet(
const jerry_call_info_t *callInfo const jerry_call_info_t *callInfo
) { ) {
return (animation_t *)scriptProtoGetValue( return (animation_t *)moduleAnimationGetWrapper(callInfo);
&MODULE_ANIMATION_PROTO, callInfo->this_value
);
} }
// Extracts an easingtype_t from a JS value. Accepts either a plain number // Extracts an easingtype_t from a JS value. Accepts either a plain number
@@ -129,13 +142,18 @@ static jerry_value_t moduleAnimationFireComplete(
} }
moduleBaseFunction(moduleAnimationConstructor) { moduleBaseFunction(moduleAnimationConstructor) {
animation_t *anim = (animation_t *)memoryAllocate(sizeof(animation_t)); jsAnimation_t *janim = (jsAnimation_t *)memoryAllocate(sizeof(jsAnimation_t));
animationInit(anim); animationInit(&janim->anim);
janim->value = 0.0f;
if(argc > 0 && jerry_value_is_boolean(args[argc - 1])) { 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])) { if(argc > 0 && jerry_value_is_array(args[0])) {
jerry_length_t len = jerry_array_length(args[0]); jerry_length_t len = jerry_array_length(args[0]);
for(jerry_length_t i = 0; i < len; i++) { for(jerry_length_t i = 0; i < len; i++) {
@@ -160,7 +178,7 @@ moduleBaseFunction(moduleAnimationConstructor) {
jerry_value_free(eVal); jerry_value_free(eVal);
jerry_value_free(kf); 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. // Store the JS keyframes array for onReach callback detection.
@@ -176,29 +194,29 @@ moduleBaseFunction(moduleAnimationConstructor) {
jerry_value_free(falseVal); jerry_value_free(falseVal);
jerry_object_set_native_ptr( jerry_object_set_native_ptr(
callInfo->this_value, &MODULE_ANIMATION_PROTO.info, anim callInfo->this_value, &MODULE_ANIMATION_PROTO.info, janim
); );
return jerry_undefined(); return jerry_undefined();
} }
moduleBaseFunction(moduleAnimationUpdate) { moduleBaseFunction(moduleAnimationUpdate) {
animation_t *anim = moduleAnimationGet(callInfo); jsAnimation_t *janim = moduleAnimationGetWrapper(callInfo);
if(!anim) return moduleBaseThrow("Invalid Animation instance"); if(!janim) return moduleBaseThrow("Invalid Animation instance");
if(argc < 1 || !jerry_value_is_number(args[0])) { if(argc < 1 || !jerry_value_is_number(args[0])) {
return moduleBaseThrow("update() expects a number delta"); 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 delta = (float_t)jerry_value_as_number(args[0]);
float_t value = animationUpdate(anim, delta); animationUpdate(&janim->anim, delta);
jerry_value_t kfResult = moduleAnimationFireKeyframes( 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; if(jerry_value_is_exception(kfResult)) return kfResult;
jerry_value_free(kfResult); jerry_value_free(kfResult);
if(anim->complete) { if(animationIsFinished(&janim->anim)) {
jerry_value_t cResult = moduleAnimationFireComplete( jerry_value_t cResult = moduleAnimationFireComplete(
callInfo->this_value callInfo->this_value
); );
@@ -206,13 +224,13 @@ moduleBaseFunction(moduleAnimationUpdate) {
jerry_value_free(cResult); jerry_value_free(cResult);
} }
return jerry_number((double)value); return jerry_number((double)janim->value);
} }
moduleBaseFunction(moduleAnimationGetValue) { moduleBaseFunction(moduleAnimationGetValue) {
animation_t *anim = moduleAnimationGet(callInfo); jsAnimation_t *janim = moduleAnimationGetWrapper(callInfo);
if(!anim) return moduleBaseThrow("Invalid Animation instance"); if(!janim) return moduleBaseThrow("Invalid Animation instance");
return jerry_number((double)animationGetValue(anim)); return jerry_number((double)janim->value);
} }
moduleBaseFunction(moduleAnimationReset) { moduleBaseFunction(moduleAnimationReset) {
@@ -231,19 +249,23 @@ moduleBaseFunction(moduleAnimationReset) {
moduleBaseFunction(moduleAnimationGetComplete) { moduleBaseFunction(moduleAnimationGetComplete) {
animation_t *anim = moduleAnimationGet(callInfo); 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) { moduleBaseFunction(moduleAnimationGetLoop) {
animation_t *anim = moduleAnimationGet(callInfo); 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) { moduleBaseFunction(moduleAnimationSetLoop) {
animation_t *anim = moduleAnimationGet(callInfo); animation_t *anim = moduleAnimationGet(callInfo);
if(!anim) return moduleBaseThrow("Invalid Animation instance"); if(!anim) return moduleBaseThrow("Invalid Animation instance");
if(argc < 1) return moduleBaseThrow("Expected boolean"); 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(); return jerry_undefined();
} }
@@ -261,7 +283,7 @@ static void moduleAnimation(void) {
scriptProtoInit( scriptProtoInit(
&MODULE_ANIMATION_PROTO, &MODULE_ANIMATION_PROTO,
"Animation", "Animation",
sizeof(animation_t), sizeof(jsAnimation_t),
moduleAnimationConstructor moduleAnimationConstructor
); );