mylingo3d
Version:
Lingo3D is a React/Vue 3d game development framework that ships with a complete visual editor
94 lines • 3.57 kB
JavaScript
import { Disposable } from "@lincode/promiselikes";
import { AnimationMixer, AnimationClip, NumberKeyframeTrack, LoopRepeat, LoopOnce } from "three";
import { forceGet } from "@lincode/utils";
import { onBeforeRender } from "../../../events/onBeforeRender";
import { dt } from "../../../engine/eventLoop";
const targetMixerMap = new WeakMap();
const mixerActionMap = new WeakMap();
const mixerHandleMap = new WeakMap();
export default class AnimationManager extends Disposable {
clip;
name;
mixer;
action;
constructor(nameOrClip, target) {
super();
this.mixer = forceGet(targetMixerMap, target, () => new AnimationMixer(target));
if (typeof nameOrClip === "string")
this.name = nameOrClip;
else {
this.name = nameOrClip.name;
this.loadClip(nameOrClip);
}
}
retarget(target) {
const newClip = this.clip.clone();
const targetName = target.name + ".";
newClip.tracks = newClip.tracks.filter((track) => track.name.startsWith(targetName));
return new AnimationManager(newClip, target);
}
dispose() {
if (this.done)
return this;
super.dispose();
this.stop();
return this;
}
get duration() {
return this.clip?.duration ?? 0;
}
loadClip(clip) {
this.clip = clip;
this.action = this.mixer.clipAction(clip);
}
setTracks(data) {
const tracks = Object.entries(data).map(([property, frames]) => new NumberKeyframeTrack("." + property, Object.keys(frames).map((t) => Number(t)), Object.values(frames)));
this.clip && this.mixer.uncacheClip(this.clip);
this.loadClip(new AnimationClip(this.name, -1, tracks));
}
play({ crossFade = 0.25, repeat = true, onFinish } = {}) {
const [prevAction, prevRepeat] = mixerActionMap.get(this.mixer) ?? [];
if (prevAction?.isRunning() && this.action === prevAction) {
repeat !== prevRepeat &&
prevAction.setLoop(repeat ? LoopRepeat : LoopOnce, Infinity);
return;
}
mixerHandleMap.get(this.mixer)?.cancel();
const handle = this.watch(onBeforeRender(() => this.mixer.update(dt[0])));
mixerHandleMap.set(this.mixer, handle);
const { action } = this;
if (!action)
return;
if (prevAction && crossFade) {
action.time = 0;
action.enabled = true;
action.crossFadeFrom(prevAction, crossFade, true);
}
else
this.mixer.stopAllAction();
mixerActionMap.set(this.mixer, [action, repeat]);
action.setLoop(repeat ? LoopRepeat : LoopOnce, Infinity);
action.clampWhenFinished = true;
const handleFinish = () => onFinish?.();
this.mixer.addEventListener("finished", handleFinish);
handle.then(() => this.mixer.removeEventListener("finished", handleFinish));
action.paused && action.stop();
action.play();
}
stop() {
this.action && (this.action.paused = true);
mixerHandleMap.get(this.mixer)?.cancel();
}
getPaused() {
return this.action?.paused;
}
setPaused(val) {
this.action && (this.action.paused = val);
}
update(seconds) {
this.mixer.time = 0;
this.action && (this.action.time = 0);
this.mixer.update(seconds);
}
}
//# sourceMappingURL=AnimationManager.js.map