UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

252 lines (185 loc) • 7.75 kB
import { assert } from "../../../../core/assert.js"; import { ResourceAccessKind } from "../../../../core/model/ResourceAccessKind.js"; import { ResourceAccessSpecification } from "../../../../core/model/ResourceAccessSpecification.js"; import { ImageRGBADataLoader } from "../../../asset/loaders/image/ImageRGBADataLoader.js"; import { System } from "../../../ecs/System.js"; import { Transform } from "../../../ecs/transform/Transform.js"; import { make_bvh_visibility_builder } from "../../render/make_bvh_visibility_builder.js"; import { StandardFrameBuffers } from "../../StandardFrameBuffers.js"; import { ParticleEmitter } from "../particular/engine/emitter/ParticleEmitter.js"; import { ParticleEmitterFlag } from "../particular/engine/emitter/ParticleEmitterFlag.js"; import { ParticularEngine } from "../particular/engine/ParticularEngine.js"; export class ParticleEmitterSystem extends System { dependencies = [ParticleEmitter, Transform]; components_used = [ ResourceAccessSpecification.from(ParticleEmitter, ResourceAccessKind.Read | ResourceAccessKind.Write) ]; /** * * @type {RenderLayer|null} */ renderLayer = null; __handlers = []; /** * * @extends {System.<ParticleEmitter>} * @constructor * @param {Engine} engine */ constructor(engine) { super(); assert.defined(engine, 'engine'); assert.equal(engine.isEngine, true, 'engine.isEngine !== true'); /** * * @type {GraphicsEngine} */ this.graphicsEngine = engine.graphics; /** * * @type {AssetManager} */ this.assetManager = engine.assetManager; /** * * @type {ParticularEngine} */ this.particleEngine = new ParticularEngine(engine.assetManager, engine.graphics.getMaterialManager()); } async startup(entityManager) { const am = this.assetManager; if (!am.hasLoaderForType('image')) { await am.registerLoader('image', new ImageRGBADataLoader()); } const self = this; this.entityManager = entityManager; const graphicsEngine = this.graphicsEngine; const renderLayer = graphicsEngine.layers.create('particles-system'); this.renderLayer = renderLayer; renderLayer.buildVisibleSet = make_bvh_visibility_builder( this.entityManager, this.particleEngine.bvh, (destination, offset, entity, ecd) => { const component = ecd.getComponent(entity, ParticleEmitter); destination[offset] = component.mesh; return 1; } ); renderLayer.visibleSet.onAdded.add((points) => { /** * * @type {ParticleEmitter} */ const emitter = points.__meep_ecs_component; // wake up emitter.clearFlag(ParticleEmitterFlag.Sleeping); // console.log('Added', emitter, renderLayer.visibleSet.version); }); renderLayer.visibleSet.onRemoved.add((points) => { /** * * @type {ParticleEmitter} */ const emitter = points.__meep_ecs_component; emitter.setFlag(ParticleEmitterFlag.Sleeping); // console.log('Removed', emitter, renderLayer.visibleSet.version); }); const depthBuffer = graphicsEngine.frameBuffers.getById(StandardFrameBuffers.ColorAndDepth); const depthTexture = depthBuffer.renderTarget.depthTexture; this.particleEngine.setDepthTexture(depthTexture); function updateViewportSize() { const size = graphicsEngine.viewport.size; const pixelRatio = graphicsEngine.computeTotalPixelRatio(); self.particleEngine.setViewportSize(size.x * pixelRatio, size.y * pixelRatio); } graphicsEngine.viewport.size.process(updateViewportSize); graphicsEngine.pixelRatio.onChanged.add(updateViewportSize); function preRenderHook(renderer, camera, scene) { //update camera self.particleEngine.setCamera(camera); //update shaders self.particleEngine.shaderManager.update(); const visibleSet = renderLayer.visibleSet; const visible_emitter_count = visibleSet.size; if (visible_emitter_count > 0) { depthBuffer.referenceCount++; self.graphicsEngine.on.postRender.addOne(function () { depthBuffer.referenceCount--; }); } for (let i = 0; i < visible_emitter_count; i++) { /** * * @type {Object3D} */ const points = visibleSet.elements[i]; /** * * @type {ParticleEmitter} */ const emitter = points.__meep_ecs_component; //update particle geometry emitter.update(); if (emitter.getFlag(ParticleEmitterFlag.DepthSorting)) { /* sort particles. NOTE: It is important that update is done first before sort, as sort assumes that all particles in the pool are alive. If this assumption is broken - corruption of the pool may occur */ emitter.sort(camera); } } } graphicsEngine.on.preRender.add(preRenderHook); } async shutdown(entityManager) { this.graphicsEngine.layers.remove(this.renderLayer); } /** * * @param {ParticleEmitter} emitter * @param {Transform} transform * @param entity */ link(emitter, transform, entity) { function handlePositionChange(x, y, z) { emitter.position.set(x, y, z); } transform.position.process(handlePositionChange); function handleRotationChange(x, y, z, w) { emitter.rotation.set(x, y, z, w); } transform.rotation.process(handleRotationChange); function handleScaleChange(x, y, z) { emitter.scale.set(x, y, z); } transform.scale.process(handleScaleChange); this.__handlers[entity] = { handlePositionChange, handleRotationChange, handleScaleChange }; //initialize emitter as suspended to prevent needless updates emitter.setFlag(ParticleEmitterFlag.Sleeping); emitter.id = entity; // emitter.bvhLeaf.entity = entity; //this line makes emitter selectable via bounding box in editor this.particleEngine.add(emitter); } /** * * @param {ParticleEmitter} emitter * @param {Transform} transform * @param entity */ unlink(emitter, transform, entity) { const handler = this.__handlers[entity]; transform.position.onChanged.remove(handler.handlePositionChange); transform.rotation.onChanged.remove(handler.handleRotationChange); transform.scale.onChanged.remove(handler.handleScaleChange); delete this.__handlers[entity]; this.particleEngine.remove(emitter); emitter.dispose(); } update(timeDelta) { this.particleEngine.advance(timeDelta); } }