gpu-curtains
Version:
gpu-curtains is a 3D WebGPU rendering engine. It can be used as a standalone 3D engine, but also includes extra classes focused on mapping 3d objects to DOM elements; It allows users to synchronize values such as position, sizing, or scale between them.
204 lines (203 loc) • 11 kB
JavaScript
import { Quat } from "../../math/Quat.mjs";
import { Vec3 } from "../../math/Vec3.mjs";
import { Vec2 } from "../../math/Vec2.mjs";
//#region src/extras/animations/KeyframesAnimation.ts
const tempVec2 = new Vec2();
const tempVec3 = new Vec3();
const tempQuat = new Quat();
/**
* Helper class to use for transformation (position, scale or rotation) and morph targets weights animations (can also be used for skin joint matrices animations using only the `onAfterUpdate` callback).
*
* This class is not made to be used alone but should rather be handled by a {@link extras/animations/TargetsAnimationsManager.TargetsAnimationsManager | TargetsAnimationsManager} instance instead.
*/
var KeyframesAnimation = class {
/**
* KeyframesAnimation constructor
* @param parameters - {@link KeyframesAnimationParams | Parameters} used to create this {@link KeyframesAnimation}.
*/
constructor({ label = "", inputIndex = null, keyframes = null, values = null, path = null, type = null, inputValue = null, interpolation = "LINEAR" } = {}) {
this.label = label;
this.keyframes = keyframes;
this.values = values;
this.path = path;
this.type = type;
this.inputValue = inputValue;
this.interpolation = interpolation;
this.inputIndex = inputIndex;
this.onAfterUpdate = null;
this.duration = this.keyframes ? this.keyframes[this.keyframes.length - 1] : 0;
}
/**
* Add a {@link BufferBindingInput} to the {@link inputValue} array. Use for weights animations.
* @param input - {@link BufferBindingInput} to add.
*/
addBindingInput(input) {
if (!this.inputValue) this.inputValue = [];
this.inputValue.push(input);
}
/**
* Get a cubic spline interpolation value.
* @param t - Current time value to use in the [0, 1] range.
* @param prevComponentValue - Previous value to use for interpolation.
* @param nextComponentValue - Next value to use for interpolation.
* @param prevOutputTangentValue - Previous output tangent value to use for interpolation.
* @param nextInputTangentValue - Previous output tangent value to use for interpolation.
*/
getCubicSplineComponentValue(t, prevComponentValue, nextComponentValue, prevOutputTangentValue, nextInputTangentValue) {
const t2 = t * t;
const t3 = t2 * t;
return (2 * t3 - 3 * t2 + 1) * prevComponentValue + (t3 - 2 * t2 + t) * prevOutputTangentValue + (-2 * t3 + 3 * t2) * nextComponentValue + (t3 - t2) * nextInputTangentValue;
}
/**
* Get the index from which to return a value from the {@link values} array based on an index in the {@link keyframes} array and the size of the component to animate.
* @param index - Index in the {@link keyframes} array to use.
* @param size - Size of the component to animate in the {@link values} array.
*/
getIndexFromInterpolation(index = 0, size = 1) {
return this.interpolation === "CUBICSPLINE" ? index * 3 * size + size : index * size;
}
/**
* Update the {@link inputValue} based on the current time given, the {@link path}, {@link type} and {@link interpolation} used and the {@link keyframes} and {@link values}.
* @param target - {@link Object3D} to update.
* @param currentTime - Current time in seconds.
*/
update(target, currentTime = 0) {
if (!this.keyframes || !this.values || !this.path || !this.type || this.inputValue === null) return;
const nextTimeIndex = this.keyframes.findIndex((t) => t >= currentTime);
if (nextTimeIndex === -1) return;
const previousTimeIndex = nextTimeIndex - 1;
if (previousTimeIndex === -1) return;
const nextTime = this.keyframes[nextTimeIndex];
const previousTime = this.keyframes[previousTimeIndex];
const interpolatedTime = (currentTime - previousTime) / (nextTime - previousTime);
const deltaTime = nextTime - previousTime;
if (this.type === "scalar") {
const prevIndex = this.getIndexFromInterpolation(previousTimeIndex, 1);
const nextIndex = this.getIndexFromInterpolation(nextTimeIndex, 1);
const value = this.values[prevIndex];
this.inputValue = value;
if (this.interpolation === "LINEAR") {
const nextValue = this.values[nextIndex];
this.inputValue += (nextValue - value) * interpolatedTime;
} else if (this.interpolation === "CUBICSPLINE") {
const nextValue = this.values[nextIndex];
const previousOutputTangent = this.values[prevIndex + 1];
const nextInputTangent = this.values[nextIndex - 1];
this.inputValue = this.getCubicSplineComponentValue(interpolatedTime, value, nextValue, deltaTime * previousOutputTangent[0], deltaTime * nextInputTangent[0]);
}
} else if (this.inputValue instanceof Quat) {
const prevIndex = this.getIndexFromInterpolation(previousTimeIndex, 4);
const nextIndex = this.getIndexFromInterpolation(nextTimeIndex, 4);
this.inputValue.setFromArray([
this.values[prevIndex],
this.values[prevIndex + 1],
this.values[prevIndex + 2],
this.values[prevIndex + 3]
]);
if (this.interpolation === "LINEAR" || this.interpolation === "CUBICSPLINE") {
tempQuat.setFromArray([
this.values[nextIndex],
this.values[nextIndex + 1],
this.values[nextIndex + 2],
this.values[nextIndex + 3]
]);
if (this.interpolation === "CUBICSPLINE") {
const previousOutputTangent = [
this.values[prevIndex + 4],
this.values[prevIndex + 5],
this.values[prevIndex + 6],
this.values[prevIndex + 7]
];
const nextInputTangent = [
this.values[nextIndex - 4],
this.values[nextIndex - 3],
this.values[nextIndex - 2],
this.values[nextIndex - 1]
];
const cubicValue = [
this.getCubicSplineComponentValue(interpolatedTime, this.inputValue.elements[0], tempQuat.elements[0], deltaTime * previousOutputTangent[0], deltaTime * nextInputTangent[0]),
this.getCubicSplineComponentValue(interpolatedTime, this.inputValue.elements[1], tempQuat.elements[1], deltaTime * previousOutputTangent[1], deltaTime * nextInputTangent[1]),
this.getCubicSplineComponentValue(interpolatedTime, this.inputValue.elements[2], tempQuat.elements[2], deltaTime * previousOutputTangent[2], deltaTime * nextInputTangent[2]),
this.getCubicSplineComponentValue(interpolatedTime, this.inputValue.elements[3], tempQuat.elements[3], deltaTime * previousOutputTangent[3], deltaTime * nextInputTangent[3])
];
this.inputValue.setFromArray(cubicValue).normalize();
} else this.inputValue.slerp(tempQuat, interpolatedTime);
}
if (this.path === "rotation") target.shouldUpdateModelMatrix();
} else if (this.inputValue instanceof Vec2) {
const prevIndex = this.getIndexFromInterpolation(previousTimeIndex, 2);
const nextIndex = this.getIndexFromInterpolation(nextTimeIndex, 2);
this.inputValue.set(this.values[prevIndex], this.values[prevIndex + 1]);
if (this.interpolation === "LINEAR" || this.interpolation === "CUBICSPLINE") {
tempVec2.set(this.values[nextIndex], this.values[nextIndex + 1]);
if (this.interpolation === "CUBICSPLINE") {
const previousOutputTangent = [this.values[prevIndex + 2], this.values[prevIndex + 3]];
const nextInputTangent = [this.values[nextIndex - 2], this.values[nextIndex - 1]];
const cubicValue = [this.getCubicSplineComponentValue(interpolatedTime, this.inputValue.x, tempVec2.x, deltaTime * previousOutputTangent[0], deltaTime * nextInputTangent[0]), this.getCubicSplineComponentValue(interpolatedTime, this.inputValue.y, tempVec2.y, deltaTime * previousOutputTangent[1], deltaTime * nextInputTangent[1])];
this.inputValue.set(cubicValue[0], cubicValue[1]);
} else this.inputValue.lerp(tempVec2, interpolatedTime);
}
} else if (this.inputValue instanceof Vec3) {
const prevIndex = this.getIndexFromInterpolation(previousTimeIndex, 3);
const nextIndex = this.getIndexFromInterpolation(nextTimeIndex, 3);
this.inputValue.set(this.values[prevIndex], this.values[prevIndex + 1], this.values[prevIndex + 2]);
if (this.interpolation === "LINEAR" || this.interpolation === "CUBICSPLINE") {
tempVec3.set(this.values[nextIndex], this.values[nextIndex + 1], this.values[nextIndex + 2]);
if (this.interpolation === "CUBICSPLINE") {
const previousOutputTangent = [
this.values[prevIndex + 3],
this.values[prevIndex + 4],
this.values[prevIndex + 5]
];
const nextInputTangent = [
this.values[nextIndex - 3],
this.values[nextIndex - 2],
this.values[nextIndex - 1]
];
const cubicValue = [
this.getCubicSplineComponentValue(interpolatedTime, this.inputValue.x, tempVec3.x, deltaTime * previousOutputTangent[0], deltaTime * nextInputTangent[0]),
this.getCubicSplineComponentValue(interpolatedTime, this.inputValue.y, tempVec3.y, deltaTime * previousOutputTangent[1], deltaTime * nextInputTangent[1]),
this.getCubicSplineComponentValue(interpolatedTime, this.inputValue.z, tempVec3.z, deltaTime * previousOutputTangent[2], deltaTime * nextInputTangent[2])
];
this.inputValue.set(cubicValue[0], cubicValue[1], cubicValue[2]);
} else this.inputValue.lerp(tempVec3, interpolatedTime);
}
} else if (this.path === "weights" && this.inputValue.length) {
const inputLength = this.inputValue.length;
const prevIndex = this.getIndexFromInterpolation(previousTimeIndex, inputLength);
const nextIndex = this.getIndexFromInterpolation(nextTimeIndex, inputLength);
for (let i = 0; i < inputLength; i++) {
const value = this.values[prevIndex + i];
this.inputValue[i].value = value;
if (this.interpolation === "LINEAR") {
const nextValue = this.values[nextIndex + i];
this.inputValue[i].value += (nextValue - value) * interpolatedTime;
} else if (this.interpolation === "CUBICSPLINE") {
const nextValue = this.values[nextIndex + i];
const previousOutputTangent = this.values[prevIndex + i + 1];
const nextInputTangent = this.values[nextIndex + i - 1];
this.inputValue[i].value = this.getCubicSplineComponentValue(interpolatedTime, value, nextValue, deltaTime * previousOutputTangent[0], deltaTime * nextInputTangent[0]);
}
}
} else if (this.inputValue.length) {
const inputLength = this.inputValue.length;
const prevIndex = this.getIndexFromInterpolation(previousTimeIndex, inputLength);
const nextIndex = this.getIndexFromInterpolation(nextTimeIndex, inputLength);
for (let i = 0; i < inputLength; i++) {
const value = this.values[prevIndex + i];
this.inputValue[i] = value;
if (this.interpolation === "LINEAR") {
const nextValue = this.values[nextIndex + i];
this.inputValue[i] += (nextValue - value) * interpolatedTime;
} else if (this.interpolation === "CUBICSPLINE") {
const nextValue = this.values[nextIndex + i];
const previousOutputTangent = this.values[prevIndex + i + 1];
const nextInputTangent = this.values[nextIndex + i - 1];
this.inputValue[i] = this.getCubicSplineComponentValue(interpolatedTime, value, nextValue, deltaTime * previousOutputTangent[0], deltaTime * nextInputTangent[0]);
}
}
}
}
};
//#endregion
export { KeyframesAnimation };