@needle-tools/engine
Version:
Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.
151 lines (133 loc) • 5.44 kB
text/typescript
import { serializable } from "../engine/engine_serialization_decorator.js";
/**
* Keyframe is a representation of a keyframe in an AnimationCurve.
*/
export class Keyframe {
time: number = 0;
value: number = 0;
inTangent: number = Infinity;
inWeight?: number;
outTangent: number = Infinity;
outWeight?: number;
weightedMode?: number;
constructor(time: number = 0, value: number = 0) {
this.time = time;
this.value = value;
}
}
/**
* AnimationCurve is a representation of a curve that can be used to animate values over time.
*/
export class AnimationCurve {
/**
* Creates an animation curve that goes from the `from` value to the `to` value over the given `duration`.
*/
static linearFromTo(from: number, to: number, duration: number): AnimationCurve {
const curve = new AnimationCurve();
const keyframe1 = new Keyframe();
keyframe1.time = 0;
keyframe1.value = from;
const keyframe2 = new Keyframe();
keyframe2.time = duration;
keyframe2.value = to;
curve.keys.push(keyframe1, keyframe2);
return curve;
}
/** Creates an animation curve with just one keyframe */
static constant(value: number): AnimationCurve {
const curve = new AnimationCurve();
const keyframe = new Keyframe();
keyframe.time = 0;
keyframe.value = value;
curve.keys.push(keyframe);
return curve;
}
/**
* The keyframes that define the curve.
*/
keys: Array<Keyframe> = [];
/**
* Clones this AnimationCurve and returns a new instance with the same keyframes (the keyframes are also cloned).
*/
clone() {
const curve = new AnimationCurve();
curve.keys = this.keys?.map(k => {
const key = new Keyframe();
key.time = k.time;
key.value = k.value;
key.inTangent = k.inTangent;
key.inWeight = k.inWeight;
key.outTangent = k.outTangent;
key.outWeight = k.outWeight;
key.weightedMode = k.weightedMode;
return key;
}) || [];
return curve;
}
/** The duration of the curve, which is the time of the last keyframe. */
get duration(): number {
if (!this.keys || this.keys.length == 0) return 0;
return this.keys[this.keys.length - 1].time;
}
/** Evaluates the curve at the given time and returns the value of the curve at that time.
* @param time The time at which to evaluate the curve.
* @returns The value of the curve at the given time.
*/
evaluate(time: number): number {
if (!this.keys || this.keys.length == 0) return 0;
if (this.keys.length === 1) {
return this.keys[0].value;
}
// if the first keyframe time is already greater than the time we want to evaluate
// then we dont need to iterate
if (this.keys[0].time >= time) {
return this.keys[0].value;
}
for (let i = 0; i < this.keys.length; i++) {
const kf = this.keys[i];
if (kf.time <= time) {
const hasNextKeyframe = i + 1 < this.keys.length;
if (hasNextKeyframe) {
const nextKf = this.keys[i + 1];
// if the next
if (nextKf.time < time) continue;
// tangents are set to Infinity if interpolation is set to constant - in that case we should always return the floored value
if (!isFinite(kf.outTangent) || !isFinite(nextKf.inTangent)) return kf.value;
return AnimationCurve.interpolateValue(time, kf, nextKf);
}
else {
return kf.value;
}
}
}
return this.keys[this.keys.length - 1].value;
}
static interpolateValue(time: number, keyframe1: Keyframe, keyframe2: Keyframe): number {
const startTime1 = keyframe1.time;
const startValue1 = keyframe1.value;
const outTangent1 = keyframe1.outTangent;
const startTime2 = keyframe2.time;
const startValue2 = keyframe2.value;
const inTangent2 = keyframe2.inTangent;
// could be precomputed and stored in the keyframes maybe
const timeDifference = startTime2 - startTime1;
const timeDifferenceSquared = timeDifference * timeDifference;
const timeDifferenceCubed = timeDifferenceSquared * timeDifference;
const a = ((outTangent1 + inTangent2) * timeDifference - 2 * (startValue2 - startValue1)) / timeDifferenceCubed;
const b = (3 * (startValue2 - startValue1) - (inTangent2 + 2 * outTangent1) * timeDifference) / timeDifferenceSquared;
const c = outTangent1;
const d = startValue1;
const timeDelta = time - startTime1;
const timeDeltaSquared = timeDelta * timeDelta;
const timeDeltaCubed = timeDeltaSquared * timeDelta;
return a * timeDeltaCubed + b * timeDeltaSquared + c * timeDelta + d;
}
}