@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
181 lines (136 loc) • 4.61 kB
JavaScript
import Signal from "../../../../../core/events/signal/Signal.js";
import { ResourceAccessKind } from "../../../../../core/model/ResourceAccessKind.js";
import { ResourceAccessSpecification } from "../../../../../core/model/ResourceAccessSpecification.js";
import { convert_three_clip } from "../../../../animation/clip/ecd_bind_animation_curve.js";
import { System } from "../../../../ecs/System.js";
import { assetTypeByPath } from "../../mesh/assetTypeByPath.js";
import { SGMesh } from "./SGMesh.js";
import { SGMeshAnimationController } from "./SGMeshAnimationController.js";
export class SGMeshAnimationControllerSystem extends System {
/**
* Current time delta
* @type {number}
*/
#time_delta = 0;
/**
* Entity queue
* @type {number[]}
*/
#queue = [];
/**
* Fires when one or more animation clips are currently playing and are updated
* @readonly
* @type {Signal}
*/
onWorkDone = new Signal()
/**
* How many clips were updated during last tick
* @type {number}
*/
#cycle_clip_advance_count = 0;
/**
*
* @param {Engine} engine
*/
constructor(engine) {
super();
this.engine = engine;
this.dependencies = [
SGMeshAnimationController,
SGMesh
];
this.components_used = [
ResourceAccessSpecification.from(SGMesh, ResourceAccessKind.Read | ResourceAccessKind.Write),
ResourceAccessSpecification.from(SGMeshAnimationController, ResourceAccessKind.Read)
];
}
async #process(entity) {
const em = this.entityManager;
const ecd = em.dataset;
const mesh = ecd.getComponent(entity, SGMesh);
if (mesh.node === null) {
// requeue
this.#enqueue(entity);
return;
}
const assetType = assetTypeByPath(mesh.url);
const asset = await this.engine.assetManager.promise(mesh.url, assetType);
const three_animations = asset.animations ?? [];
const animations = three_animations.map(three_animation => {
return convert_three_clip(mesh.node, three_animation)
});
const controller = ecd.getComponent(entity, SGMeshAnimationController);
controller.bound = animations;
}
/**
*
* @param {number} entity
*/
#enqueue(entity) {
this.#queue.push(entity);
}
/**
*
* @param {SGMeshAnimationController} anim
* @param {SGMesh} mesh
* @param {number} entity
*/
async link(anim, mesh, entity) {
if (mesh.node === null) {
this.#enqueue(entity);
return;
}
await this.#process(entity);
}
/**
*
* @param {SGMeshAnimationController} controller
*/
#visit_entity(controller) {
const active = controller.active;
let active_count = active.length;
for (let i = 0; i < active_count; i++) {
const playback = active[i];
playback.time += this.#time_delta;
const clip = controller.getBoundByName(playback.clip_name);
if (clip === undefined) {
// clip doesn't exist
continue;
}
let t = playback.time;
if (t > clip.duration) {
if (playback.loop) {
t = t % clip.duration;
} else {
t = clip.duration;
// playback finished, remove active
active.splice(i, 1);
i--;
active_count--;
}
}
clip.writeAt(t);
// mark clip update
this.#cycle_clip_advance_count++;
}
}
update(td) {
this.#cycle_clip_advance_count = 0;
this.#time_delta = td;
for (let i = 0; i < 1000; i++) {
if (this.#queue.length === 0) {
break;
}
const entity = this.#queue.pop();
this.#process(entity);
}
const ecd = this.entityManager.dataset;
if (ecd === null) {
return;
}
ecd.traverseComponents(SGMeshAnimationController, this.#visit_entity, this);
if (this.#cycle_clip_advance_count > 0) {
this.onWorkDone.send0();
}
}
}