playcanvas
Version:
PlayCanvas WebGL game engine
222 lines (219 loc) • 6.85 kB
JavaScript
import { Curve } from '../../../core/math/curve.js';
import { CurveSet } from '../../../core/math/curve-set.js';
import { Vec3 } from '../../../core/math/vec3.js';
import { Asset } from '../../asset/asset.js';
import { ComponentSystem } from '../system.js';
import { ParticleSystemComponent } from './component.js';
import { ParticleSystemComponentData } from './data.js';
import { particleChunksGLSL } from '../../../scene/shader-lib/glsl/collections/particle-chunks-glsl.js';
import { particleChunksWGSL } from '../../../scene/shader-lib/wgsl/collections/particle-chunks-wgsl.js';
import { SHADERLANGUAGE_GLSL, SHADERLANGUAGE_WGSL } from '../../../platform/graphics/constants.js';
import { ShaderChunks } from '../../../scene/shader-lib/shader-chunks.js';
const _schema = [
'enabled',
'autoPlay',
'numParticles',
'lifetime',
'rate',
'rate2',
'startAngle',
'startAngle2',
'loop',
'preWarm',
'lighting',
'halfLambert',
'intensity',
'depthWrite',
'noFog',
'depthSoftening',
'sort',
'blendType',
'stretch',
'alignToMotion',
'emitterShape',
'emitterExtents',
'emitterExtentsInner',
'emitterRadius',
'emitterRadiusInner',
'initialVelocity',
'wrap',
'wrapBounds',
'localSpace',
'screenSpace',
'colorMapAsset',
'normalMapAsset',
'mesh',
'meshAsset',
'renderAsset',
'orientation',
'particleNormal',
'localVelocityGraph',
'localVelocityGraph2',
'velocityGraph',
'velocityGraph2',
'rotationSpeedGraph',
'rotationSpeedGraph2',
'radialSpeedGraph',
'radialSpeedGraph2',
'scaleGraph',
'scaleGraph2',
'colorGraph',
'colorGraph2',
'alphaGraph',
'alphaGraph2',
'colorMap',
'normalMap',
'animTilesX',
'animTilesY',
'animStartFrame',
'animNumFrames',
'animNumAnimations',
'animIndex',
'randomizeAnimIndex',
'animSpeed',
'animLoop',
'layers'
];
class ParticleSystemComponentSystem extends ComponentSystem {
constructor(app){
super(app);
this.id = 'particlesystem';
this.ComponentType = ParticleSystemComponent;
this.DataType = ParticleSystemComponentData;
this.schema = _schema;
this.propertyTypes = {
emitterExtents: 'vec3',
emitterExtentsInner: 'vec3',
particleNormal: 'vec3',
wrapBounds: 'vec3',
localVelocityGraph: 'curveset',
localVelocityGraph2: 'curveset',
velocityGraph: 'curveset',
velocityGraph2: 'curveset',
colorGraph: 'curveset',
colorGraph2: 'curveset',
alphaGraph: 'curve',
alphaGraph2: 'curve',
rotationSpeedGraph: 'curve',
rotationSpeedGraph2: 'curve',
radialSpeedGraph: 'curve',
radialSpeedGraph2: 'curve',
scaleGraph: 'curve',
scaleGraph2: 'curve'
};
this.on('beforeremove', this.onBeforeRemove, this);
this.app.systems.on('update', this.onUpdate, this);
ShaderChunks.get(app.graphicsDevice, SHADERLANGUAGE_GLSL).add(particleChunksGLSL);
ShaderChunks.get(app.graphicsDevice, SHADERLANGUAGE_WGSL).add(particleChunksWGSL);
}
initializeComponentData(component, _data, properties) {
const data = {};
properties = [];
const types = this.propertyTypes;
if (_data.mesh instanceof Asset || typeof _data.mesh === 'number') {
_data.meshAsset = _data.mesh;
delete _data.mesh;
}
for(const prop in _data){
if (_data.hasOwnProperty(prop)) {
properties.push(prop);
data[prop] = _data[prop];
}
if (types[prop] === 'vec3') {
if (Array.isArray(data[prop])) {
data[prop] = new Vec3(data[prop][0], data[prop][1], data[prop][2]);
}
} else if (types[prop] === 'curve') {
if (!(data[prop] instanceof Curve)) {
const t = data[prop].type;
data[prop] = new Curve(data[prop].keys);
data[prop].type = t;
}
} else if (types[prop] === 'curveset') {
if (!(data[prop] instanceof CurveSet)) {
const t = data[prop].type;
data[prop] = new CurveSet(data[prop].keys);
data[prop].type = t;
}
}
if (data.layers && Array.isArray(data.layers)) {
data.layers = data.layers.slice(0);
}
}
super.initializeComponentData(component, data, properties);
}
cloneComponent(entity, clone) {
const source = entity.particlesystem.data;
const schema = this.schema;
const data = {};
for(let i = 0, len = schema.length; i < len; i++){
const prop = schema[i];
let sourceProp = source[prop];
if (sourceProp instanceof Vec3 || sourceProp instanceof Curve || sourceProp instanceof CurveSet) {
sourceProp = sourceProp.clone();
data[prop] = sourceProp;
} else if (prop === 'layers') {
data.layers = source.layers.slice(0);
} else {
if (sourceProp !== null && sourceProp !== undefined) {
data[prop] = sourceProp;
}
}
}
return this.addComponent(clone, data);
}
onUpdate(dt) {
const components = this.store;
const stats = this.app.stats.particles;
const composition = this.app.scene.layers;
for(let i = 0; i < composition.layerList.length; i++){
composition.layerList[i].requiresLightCube = false;
}
for(const id in components){
if (components.hasOwnProperty(id)) {
const component = components[id];
const entity = component.entity;
const data = component.data;
if (data.enabled && entity.enabled) {
const emitter = entity.particlesystem.emitter;
if (!emitter?.meshInstance.visible) continue;
if (emitter.lighting) {
const layers = data.layers;
for(let i = 0; i < layers.length; i++){
const layer = composition.getLayerById(layers[i]);
if (layer) {
layer.requiresLightCube = true;
}
}
}
if (!data.paused) {
let numSteps = 0;
emitter.simTime += dt;
if (emitter.simTime >= emitter.fixedTimeStep) {
numSteps = Math.floor(emitter.simTime / emitter.fixedTimeStep);
emitter.simTime -= numSteps * emitter.fixedTimeStep;
}
if (numSteps) {
numSteps = Math.min(numSteps, emitter.maxSubSteps);
for(let i = 0; i < numSteps; i++){
emitter.addTime(emitter.fixedTimeStep, false);
}
stats._updatesPerFrame += numSteps;
stats._frameTime += emitter._addTimeTime;
emitter._addTimeTime = 0;
}
emitter.finishFrame();
}
}
}
}
}
onBeforeRemove(entity, component) {
component.onBeforeRemove();
}
destroy() {
super.destroy();
this.app.systems.off('update', this.onUpdate, this);
}
}
export { ParticleSystemComponentSystem };