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