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));
}
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);
}
+40 -28
View File
@@ -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;
}
@@ -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
);