UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

323 lines (241 loc) • 9.44 kB
import { DataTexture, Points } from 'three'; import { AssetManager } from "../../../engine/asset/AssetManager.js"; import { ManagedAtlas } from "../../../engine/graphics/texture/atlas/ManagedTextureAtlas.js"; import { EntityObserver } from "../../../engine/ecs/EntityObserver.js"; import { Transform } from "../../../engine/ecs/transform/Transform.js"; import MinimapMarker from "../../../../../model/game/ecs/component/minimap/MinimapMarker.js"; import { MarkerGL } from "./MarkerGL.js"; import { MarkerGLAttributes } from "./MarkerGLAttributes.js"; import { MinimapWorldLayer } from "./MinimapWorldLayer.js"; import { ParticleSpecification } from "../../../engine/graphics/particles/particular/group/ParticleSpecification.js"; import { ParticleAttribute } from "../../../engine/graphics/particles/particular/group/ParticleAttribute.js"; import Vector2 from "../../../core/geom/Vector2.js"; import { writeSample2DDataToDataTexture } from "../../../engine/graphics/texture/sampler/writeSampler2DDataToDataTexture.js"; import { ParticleDataType } from "../../../engine/graphics/particles/particular/group/ParticleDataType.js"; import { ParticleAttributeType } from "../../../engine/graphics/particles/particular/group/ParticleAttributeType.js"; import { buildMaterial } from "./buildMaterial.js"; import { ParticleGroup } from "../../../engine/graphics/particles/particular/group/ParticleGroup.js"; /** * * @param {EntityComponentDataset} entityDataset * @param {AssetManager} assetManager * @param {Rectangle} worldBounds * @param {Vector2} canvasSize * @constructor */ export class MinimapMarkersGL extends MinimapWorldLayer { /** * * @param {EntityComponentDataset} entityDataset * @param {AssetManager} assetManager */ constructor(entityDataset, assetManager) { super(); const self = this; /** * * @type {EntityComponentDataset} */ this.entityDataset = entityDataset; /** * * @type {AssetManager} */ this.assetManager = assetManager; this.atlasManager = new ManagedAtlas(assetManager); //disable automatic updates as we have a dedicated pre-render method to update the atlas this.atlasManager.autoUpdate = false; this.material = buildMaterial(); /** * * @type {Map<number, MarkerGL>} */ const markers = this.markers = new Map(); const particleSpecification = new ParticleSpecification(); particleSpecification .add(new ParticleAttribute("position", ParticleAttributeType.Vector3, ParticleDataType.Float32)) .add(new ParticleAttribute("size", ParticleAttributeType.Scalar, ParticleDataType.Float32)) .add(new ParticleAttribute("aPatch", ParticleAttributeType.Vector4, ParticleDataType.Float32)) .add(new ParticleAttribute("zIndex", ParticleAttributeType.Scalar, ParticleDataType.Float32)); /** * * @type {ParticleGroup} */ const particles = this.particles = new ParticleGroup(particleSpecification); this.entityObserver = new EntityObserver([Transform, MinimapMarker], addElement, removeElement); /** * * @param {Number} entity * @param {MinimapMarker} marker * @param {Transform} transform */ function addElement(transform, marker, entity) { const reference = particles.createImmediate(); const markerGL = new MarkerGL(marker, transform, entity, reference, self); markers.set(entity, markerGL); markerGL.startup(); self.needsSorting = true; //marker added, request render update self.needsUpdate = true; } /** * * @param transform * @param marker * @param {Number} entity */ function removeElement(transform, marker, entity) { const markerGL = markers.get(entity); //update IDs of remaining markers const id = markerGL.id; particles.executeOperationRemove([id]); markerGL.shutdown(); //remove marker from the map markers.delete(entity); //marker removed, request render update self.needsRender = true; } this.shutdownHooks = []; // this.object = new Points(this.particles.geometry.getValue(), this.material); this.object.frustumCulled = false; this.viewportSize = new Vector2(); /** * * @type {boolean} */ this.needsSorting = false; } __sortByZIndex() { const particles = this.particles; //Bind attribute array directly for faster access const zIndexAttribute = particles.attributes[MarkerGLAttributes.AttributeZIndex]; const zIndexArray = zIndexAttribute.array; const particleCount = particles.size; //Stack-based implementation, avoiding recursion for performance improvement const stack = [0, particleCount - 1]; let stackPointer = 2; let i, j; while (stackPointer > 0) { stackPointer -= 2; const right = stack[stackPointer + 1]; const left = stack[stackPointer]; i = left; j = right; const pivotIndex = (left + right) >> 1; const pivot = zIndexArray[pivotIndex]; /* partition */ while (i <= j) { while (zIndexArray[i] < pivot) i++; while (zIndexArray[j] > pivot) j--; if (i <= j) { if (i !== j) { //do swap particles.swap(i, j); } i++; j--; } } /* recursion */ if (left < j) { stack[stackPointer++] = left; stack[stackPointer++] = j; } if (i < right) { stack[stackPointer++] = i; stack[stackPointer++] = right; } } } setViewportSize(x, y) { this.viewportSize.set(x, y); this.material.uniforms.resolution.value.set(x, y); this.needsRender = true; } /** * * @return {DataTexture} * @private */ __getAtlasTexture() { return this.material.uniforms.atlas.value; } handleAtlasUpdate() { writeSample2DDataToDataTexture(this.atlasManager.atlas.sampler, this.__getAtlasTexture()); this.updateSprites(); this.needsRender = true; } updateSprites() { this.markers.forEach((marker) => { const patch = marker.patch.getValue(); if (patch !== null) { /** * @type {Rectangle} */ const patchUv = patch.uv; this.particles.executeOperationWriteAttribute_Vector4( marker.id, MarkerGLAttributes.AttributePatch, patchUv.position.x, patchUv.position.y, patchUv.size.x, patchUv.size.y ); } }); this.needsRender = true; } startup() { console.log("MinimapMarkersGL startup"); const self = this; this.entityDataset.addObserver(this.entityObserver, true); const atlas = this.atlasManager.atlas; const points = this.object; function updateGeometry() { points.geometry = self.particles.geometry.getValue(); } updateGeometry(); this.particles.geometry.onChanged.add(updateGeometry); this.shutdownHooks.push(function () { self.particles.geometry.onChanged.remove(updateGeometry); }); atlas.on.painted.add(this.handleAtlasUpdate, this); this.shutdownHooks.push(() => { atlas.on.painted.remove(this.handleAtlasUpdate); }); this.handleAtlasUpdate(); this.needsRender = true; } update() { this.atlasManager.atlas.update(); if (this.needsSorting) { this.__sortByZIndex(); this.needsSorting = false; } this.needsUpdate = false; } shutdown() { console.log("MinimapMarkersGL shutdown"); this.entityObserver.disconnect(); this.markers.forEach(function (marker) { marker.shutdown(); }); this.atlasManager.reset(); this.markers.clear(); //clear out data arrays this.particles.reset(); // release atlas GPU memory this.__getAtlasTexture().dispose(); //execute shutdown hooks this.shutdownHooks.forEach(function (hook) { hook(); }); //clear out hooks this.shutdownHooks = []; } }