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.
193 lines (192 loc) • 6.91 kB
JavaScript
import { generateUUID } from "../../utils/utils.mjs";
import { isRenderer } from "../../core/renderers/utils.mjs";
//#region src/extras/animations/TargetsAnimationsManager.ts
/**
* Class used to help synchronize and run {@link KeyframesAnimation} for a given list of {@link Object3D}. Mostly used internally when loading glTF files, but could be used externally as well.
*/
var TargetsAnimationsManager = class {
/** @ignore */
#startTime;
/** @ignore */
#currentTime;
/** @ignore */
#deltaTime;
/** @ignore */
#count;
/** @ignore */
#maxCount;
/**
* TargetsAnimationsManager constructor
* @param renderer - {@link Renderer} or {@link GPUCurtains} class object used to create this {@link TargetsAnimationsManager}.
* @param parameters - {@link TargetsAnimationsManagerParams | parameters} used to create this {@link TargetsAnimationsManager}.
*/
constructor(renderer, { label = "", targets = [] } = {}) {
this.uuid = generateUUID();
this.inputIndices = [];
this.setRenderer(renderer);
this.label = label;
this.targets = [];
this.duration = 0;
this.timeScale = 1;
this.#startTime = performance.now();
this.#currentTime = performance.now();
this.#deltaTime = 0;
this.#count = 0;
this.#count = 0;
this.#maxCount = Infinity;
this.isPlaying = false;
this.siblings = /* @__PURE__ */ new Map();
if (targets && targets.length) this.targets = [...this.targets, ...targets];
}
/**
* Set the current {@link TargetsAnimationsManager.renderer | renderer} to use with this {@link TargetsAnimationsManager}. Can be set to `null` to detach from the current {@link TargetsAnimationsManager.renderer | renderer}.
* @param renderer
*/
setRenderer(renderer) {
if (this.renderer) {
this.renderer.animations.delete(this.uuid);
this.renderer.animations.forEach((animation) => animation.siblings.delete(this.uuid));
}
if (renderer) {
renderer = isRenderer(renderer, "TargetsAnimationsManager");
this.renderer = renderer;
this.renderer.animations.set(this.uuid, this);
if (this.inputIndices.length) this.#setSiblings();
}
}
/**
* Add a new {@link Target} to the {@link targets} array based on an {@link Object3D}.
* @param object - {@link Object3D} to use for the {@link Target}.
*/
addTarget(object) {
const target = {
object,
animations: []
};
this.targets.push(target);
return target;
}
/**
* Add new {@link Target | targets} to the {@link targets} array based on an array of {@link Object3D}.
* @param objects - array of {@link Object3D} to use for the {@link Target | targets}.
*/
addTargets(objects) {
objects.forEach((object) => this.addTarget(object));
}
/**
* Add a {@link KeyframesAnimation} to a {@link Target#animations | target animations} array based on an {@link Object3D}.
* @param object - {@link Object3D} to use for the {@link Target}.
* @param animation - {@link KeyframesAnimation} to add.
*/
addTargetAnimation(object, animation) {
this.duration = Math.max(this.duration, animation.duration);
let target = this.getTargetByObject3D(object);
if (!target) target = this.addTarget(object);
target.animations.push(animation);
if (animation.inputIndex !== null && !this.inputIndices.includes(animation.inputIndex)) this.inputIndices.push(animation.inputIndex);
this.#setSiblings();
}
/**
* Set the {@link TargetsAnimationsManager} siblings by comparing {@link inputIndices} arrays.
* @private
*/
#setSiblings() {
this.siblings = /* @__PURE__ */ new Map();
this.renderer.animations.forEach((animation) => {
if (animation.uuid !== this.uuid && JSON.stringify(animation.inputIndices) === JSON.stringify(this.inputIndices)) {
this.siblings.set(animation.uuid, animation);
animation.siblings.set(this.uuid, this);
} else animation.siblings.delete(this.uuid);
});
}
/**
* Get a {@link Target} from the {@link targets} array based on an {@link Object3D}.
* @param object - {@link Object3D} to use to find the {@link Target}.
* @returns - {@link Target} found if any.
*/
getTargetByObject3D(object) {
return this.targets.find((target) => target.object.object3DIndex === object.object3DIndex);
}
/**
* Get the first animation from the {@link targets} array that matches the {@link Object3D} and {@link KeyframesAnimation#path | path} given.
* @param object - {@link Object3D} to use to find the {@link KeyframesAnimation}.
* @param path - {@link KeyframesAnimation#path | path} to use to find the {@link KeyframesAnimation}.
* @returns - {@link KeyframesAnimation} found if any.
*/
getAnimationByObject3DAndPath(object, path) {
const target = this.getTargetByObject3D(object);
if (target) return target.animations.find((animation) => animation.path === path);
else return null;
}
/**
* Play or resume the {@link TargetsAnimationsManager}.
*/
play() {
this.isPlaying = true;
}
/**
* Play the {@link TargetsAnimationsManager} once.
*/
playOnce() {
this.#maxCount = 1;
this.play();
}
/**
* Pause the {@link TargetsAnimationsManager}.
*/
pause() {
this.isPlaying = false;
this.#startTime = -1;
}
/**
* Stop the {@link TargetsAnimationsManager} and reset all the animations values to last keyframe.
*/
stop() {
this.isPlaying = false;
this.#count = 0;
if (!this.siblings.size) this.#startTime = 0;
this.targets.forEach((target) => target.animations.forEach((animation) => animation.update(target.object, Math.min(animation.duration, this.duration))));
this.renderer.onAfterRenderScene.add(() => {
this.targets.forEach((target) => {
target.animations.forEach((animation) => {
if (animation.onAfterUpdate) animation.onAfterUpdate();
});
});
}, { once: true });
}
/**
* {@link stop | Stop} the {@link TargetsAnimationsManager} at the end of the next animation loop.
*/
stopAtEndOfLoop() {
this.#maxCount = this.#count + 1;
}
/**
* Update all the {@link targets} animations.
*/
update() {
if (!this.isPlaying) return;
if (this.#startTime === -1) this.#startTime = performance.now() - this.#deltaTime;
else if (this.#startTime === 0) this.#startTime = performance.now();
this.#currentTime = performance.now();
this.#deltaTime = this.#currentTime - this.#startTime;
const time = this.#deltaTime * this.timeScale / 1e3;
const currentTime = time % this.duration;
this.#count = Math.floor(time / this.duration);
if (this.#count >= this.#maxCount) {
this.stop();
return;
}
this.targets.forEach((target) => target.animations.forEach((animation) => animation.update(target.object, currentTime)));
}
/**
* Call all the {@link targets} animations {@link KeyframesAnimation#onAfterUpdate | onAfterUpdate} callbacks.
*/
onAfterUpdate() {
if (!this.isPlaying) return;
this.targets.forEach((target) => target.animations.forEach((animation) => {
if (animation.onAfterUpdate) animation.onAfterUpdate();
}));
}
};
//#endregion
export { TargetsAnimationsManager };