playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
222 lines (221 loc) • 6.3 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 !== void 0) {
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
};