@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
252 lines (185 loc) • 7.75 kB
JavaScript
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);
}
}