UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

181 lines (136 loc) 4.61 kB
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(); } } }