Anim tweak
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user