diff --git a/src/lib/map/DoodadManager.ts b/src/lib/map/DoodadManager.ts index d6f33a5..77f3ea5 100644 --- a/src/lib/map/DoodadManager.ts +++ b/src/lib/map/DoodadManager.ts @@ -4,6 +4,7 @@ import TextureManager from '../texture/TextureManager.js'; import { AssetHost } from '../asset.js'; import { MapAreaSpec } from './loader/types.js'; import MapLight from './light/MapLight.js'; +import ModelMesh from '../model/ModelMesh.js'; type DoodadManagerOptions = { host: AssetHost; @@ -15,6 +16,9 @@ class DoodadManager { #host: AssetHost; #modelManager: ModelManager; + #loadedAreas = new Map(); + #loadingAreas = new Map>(); + constructor(options: DoodadManagerOptions) { this.#host = options.host; @@ -25,7 +29,41 @@ class DoodadManager { }); } - async getArea(area: MapAreaSpec) { + getArea(areaId: number, area: MapAreaSpec): Promise { + const loaded = this.#loadedAreas.get(areaId); + if (loaded) { + return Promise.resolve(loaded); + } + + const alreadyLoading = this.#loadingAreas.get(areaId); + if (alreadyLoading) { + return alreadyLoading; + } + + const loading = this.#loadArea(areaId, area); + this.#loadingAreas.set(areaId, loading); + + return loading; + } + + removeArea(areaId: number) { + const group = this.#loadedAreas.get(areaId); + if (!group) { + return; + } + + for (const model of group.children) { + (model as ModelMesh).dispose(); + } + + this.#loadedAreas.delete(areaId); + } + + update(deltaTime: number) { + this.#modelManager.update(deltaTime); + } + + async #loadArea(areaId: number, area: MapAreaSpec) { const group = new THREE.Group(); group.name = 'doodads'; @@ -50,11 +88,10 @@ class DoodadManager { group.add(model); } - return group; - } + this.#loadedAreas.set(areaId, group); + this.#loadingAreas.delete(areaId); - update(deltaTime: number) { - this.#modelManager.update(deltaTime); + return group; } } diff --git a/src/lib/map/MapManager.ts b/src/lib/map/MapManager.ts index 1656361..2f0289c 100644 --- a/src/lib/map/MapManager.ts +++ b/src/lib/map/MapManager.ts @@ -282,6 +282,7 @@ class MapManager extends EventTarget { if (doodadGroup) { this.#root.remove(doodadGroup); this.#doodadGroups.delete(areaId); + this.#doodadManager.removeArea(areaId); } this.#loadedAreas.delete(areaId); @@ -313,7 +314,7 @@ class MapManager extends EventTarget { const [terrainGroup, doodadGroup] = await Promise.all([ this.#terrainManager.getArea(areaId, newArea), - this.#doodadManager.getArea(newArea), + this.#doodadManager.getArea(areaId, newArea), ]); const terrainBoundingSphere = new THREE.Box3() diff --git a/src/lib/model/ModelAnimator.ts b/src/lib/model/ModelAnimator.ts index b6c3bcc..d2e5f00 100644 --- a/src/lib/model/ModelAnimator.ts +++ b/src/lib/model/ModelAnimator.ts @@ -36,6 +36,11 @@ class ModelAnimator { return this.#sequences; } + clearAction(action: THREE.AnimationAction) { + action.stop(); + this.#mixer.uncacheAction(action.getClip()); + } + update(deltaTime: number) { this.#mixer.update(deltaTime); } diff --git a/src/lib/model/ModelMesh.ts b/src/lib/model/ModelMesh.ts index 356195a..032a272 100644 --- a/src/lib/model/ModelMesh.ts +++ b/src/lib/model/ModelMesh.ts @@ -10,6 +10,8 @@ type ModelTextureTransform = { class ModelMesh extends THREE.Mesh { #animator: ModelAnimator; + #animationActions: Set = new Set(); + #diffuseColor: THREE.Color; #emissiveColor: THREE.Color; #alpha: 1.0; @@ -51,7 +53,8 @@ class ModelMesh extends THREE.Mesh { // Automatically play all loops for (let i = 0; i < this.#animator.loops.length; i++) { - this.#animator.getLoop(this, i).play(); + const action = this.#animator.getLoop(this, i).play(); + this.#animationActions.add(action); } // Automatically play flagged sequences @@ -59,7 +62,8 @@ class ModelMesh extends THREE.Mesh { const sequence = this.#animator.sequences[i]; if (sequence.flags & 0x20) { - this.#animator.getSequence(this, i).play(); + const action = this.#animator.getSequence(this, i).play(); + this.#animationActions.add(action); } } } @@ -84,6 +88,14 @@ class ModelMesh extends THREE.Mesh { material.setTextureTransform(i, translation, rotation, scaling); } } + + dispose() { + for (const action of this.#animationActions.values()) { + this.#animator.clearAction(action); + } + + this.#animationActions.clear(); + } } export default ModelMesh;