@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
1,007 lines (1,006 loc) • 52 kB
JavaScript
import { ThinParticleSystem } from "./thinParticleSystem.js";
import { SubEmitter } from "./subEmitter.js";
import { Color3, Color4 } from "../Maths/math.color.js";
import { Vector3 } from "../Maths/math.vector.js";
import { AbstractEngine } from "../Engines/abstractEngine.js";
import { GetClass } from "../Misc/typeStore.js";
import { SerializationHelper } from "../Misc/decorators.serialization.js";
import { MeshParticleEmitter } from "./EmitterTypes/meshParticleEmitter.js";
import { CustomParticleEmitter } from "./EmitterTypes/customParticleEmitter.js";
import { BoxParticleEmitter } from "./EmitterTypes/boxParticleEmitter.js";
import { PointParticleEmitter } from "./EmitterTypes/pointParticleEmitter.js";
import { HemisphericParticleEmitter } from "./EmitterTypes/hemisphericParticleEmitter.js";
import { SphereDirectedParticleEmitter, SphereParticleEmitter } from "./EmitterTypes/sphereParticleEmitter.js";
import { CylinderDirectedParticleEmitter, CylinderParticleEmitter } from "./EmitterTypes/cylinderParticleEmitter.js";
import { ConeDirectedParticleEmitter, ConeParticleEmitter } from "./EmitterTypes/coneParticleEmitter.js";
import { CreateConeEmitter, CreateCylinderEmitter, CreateDirectedCylinderEmitter, CreateDirectedSphereEmitter, CreateDirectedConeEmitter, CreateHemisphericEmitter, CreatePointEmitter, CreateSphereEmitter, } from "./particleSystem.functions.js";
import { Attractor } from "./attractor.js";
import { _ConnectAfter, _RemoveFromQueue } from "./Queue/executionQueue.js";
/**
* This represents a particle system in Babylon.
* Particles are often small sprites used to simulate hard-to-reproduce phenomena like fire, smoke, water, or abstract visual effects like magic glitter and faery dust.
* Particles can take different shapes while emitted like box, sphere, cone or you can write your custom function.
* @example https://doc.babylonjs.com/features/featuresDeepDive/particles/particle_system/particle_system_intro
*/
export class ParticleSystem extends ThinParticleSystem {
constructor() {
super(...arguments);
/**
* @internal
* If the particle systems emitter should be disposed when the particle system is disposed
*/
this._disposeEmitterOnDispose = false;
/**
* Specifies if the particle system should be serialized
*/
this.doNotSerialize = false;
/**
* Gets or sets a function indicating if the particle system can start.
* @returns true if the particle system can start, false otherwise.
*/
this.canStart = () => {
return true;
};
/** Flow map */
this._flowMap = null;
this._flowMapUpdate = null;
/** @internal */
this._source = null;
/** @internal */
this._blockReference = 0;
/**
* The strength of the flow map
*/
this.flowMapStrength = 1.0;
/** Attractors */
this._attractors = [];
this._attractorUpdate = null;
/**
* Gets or sets an object used to store user defined information for the particle system
*/
this.metadata = null;
/** @internal */
this._emitFromParticle = (particle) => {
if (!this._subEmitters || this._subEmitters.length === 0) {
return;
}
const templateIndex = Math.floor(Math.random() * this._subEmitters.length);
for (const subEmitter of this._subEmitters[templateIndex]) {
if (subEmitter.type === 1 /* SubEmitterType.END */) {
const subSystem = subEmitter.clone();
particle._inheritParticleInfoToSubEmitter(subSystem);
subSystem.particleSystem._rootParticleSystem = this;
this.activeSubSystems.push(subSystem.particleSystem);
subSystem.particleSystem.start();
}
}
};
}
/**
* Creates a Point Emitter for the particle system (emits directly from the emitter position)
* @param direction1 Particles are emitted between the direction1 and direction2 from within the box
* @param direction2 Particles are emitted between the direction1 and direction2 from within the box
* @returns the emitter
*/
createPointEmitter(direction1, direction2) {
const particleEmitter = CreatePointEmitter(direction1, direction2);
this.particleEmitterType = particleEmitter;
return particleEmitter;
}
/**
* Gets the NodeParticleSystemSet that this particle system belongs to.
*/
get source() {
return this._source;
}
/**
* Returns true if the particle system was generated by a node particle system set
*/
get isNodeGenerated() {
return this._source !== null;
}
/** Gets or sets the current flow map */
get flowMap() {
return this._flowMap;
}
set flowMap(value) {
if (this._flowMap === value) {
return;
}
this._flowMap = value;
if (this._flowMapUpdate) {
_RemoveFromQueue(this._flowMapUpdate);
this._flowMapUpdate = null;
}
if (value) {
this._flowMapUpdate = {
process: (particle) => {
const matrix = this.getScene()?.getTransformMatrix();
this._flowMap._processParticle(particle, this.flowMapStrength * this._tempScaledUpdateSpeed, matrix);
},
previousItem: null,
nextItem: null,
};
_ConnectAfter(this._flowMapUpdate, this._directionProcessing);
}
}
/**
* The list of attractors used to change the direction of the particles in the system.
* Please note that this is a copy of the internal array. If you want to modify it, please use the addAttractor and removeAttractor methods.
*/
get attractors() {
return this._attractors.slice(0);
}
/**
* Add an attractor to the particle system. Attractors are used to change the direction of the particles in the system.
* @param attractor The attractor to add to the particle system
*/
addAttractor(attractor) {
this._attractors.push(attractor);
if (this._attractors.length === 1) {
this._attractorUpdate = {
process: (particle) => {
for (const attractor of this._attractors) {
attractor._processParticle(particle, this);
}
},
previousItem: null,
nextItem: null,
};
_ConnectAfter(this._attractorUpdate, this._directionProcessing);
}
}
/**
* Removes an attractor from the particle system. Attractors are used to change the direction of the particles in the system.
* @param attractor The attractor to remove from the particle system
*/
removeAttractor(attractor) {
const index = this._attractors.indexOf(attractor);
if (index !== -1) {
this._attractors.splice(index, 1);
}
if (this._attractors.length === 0) {
_RemoveFromQueue(this._attractorUpdate);
}
}
/**
* Starts the particle system and begins to emit
* @param delay defines the delay in milliseconds before starting the system (this.startDelay by default)
*/
start(delay = this.startDelay) {
if (!this.canStart()) {
return;
}
super.start(delay);
}
/**
* Creates a Hemisphere Emitter for the particle system (emits along the hemisphere radius)
* @param radius The radius of the hemisphere to emit from
* @param radiusRange The range of the hemisphere to emit from [0-1] 0 Surface Only, 1 Entire Radius
* @returns the emitter
*/
createHemisphericEmitter(radius = 1, radiusRange = 1) {
const particleEmitter = CreateHemisphericEmitter(radius, radiusRange);
this.particleEmitterType = particleEmitter;
return particleEmitter;
}
/**
* Creates a Sphere Emitter for the particle system (emits along the sphere radius)
* @param radius The radius of the sphere to emit from
* @param radiusRange The range of the sphere to emit from [0-1] 0 Surface Only, 1 Entire Radius
* @returns the emitter
*/
createSphereEmitter(radius = 1, radiusRange = 1) {
const particleEmitter = CreateSphereEmitter(radius, radiusRange);
this.particleEmitterType = particleEmitter;
return particleEmitter;
}
/**
* Creates a Directed Sphere Emitter for the particle system (emits between direction1 and direction2)
* @param radius The radius of the sphere to emit from
* @param direction1 Particles are emitted between the direction1 and direction2 from within the sphere
* @param direction2 Particles are emitted between the direction1 and direction2 from within the sphere
* @returns the emitter
*/
createDirectedSphereEmitter(radius = 1, direction1 = new Vector3(0, 1.0, 0), direction2 = new Vector3(0, 1.0, 0)) {
const particleEmitter = CreateDirectedSphereEmitter(radius, direction1, direction2);
this.particleEmitterType = particleEmitter;
return particleEmitter;
}
/**
* Creates a Cylinder Emitter for the particle system (emits from the cylinder to the particle position)
* @param radius The radius of the emission cylinder
* @param height The height of the emission cylinder
* @param radiusRange The range of emission [0-1] 0 Surface only, 1 Entire Radius
* @param directionRandomizer How much to randomize the particle direction [0-1]
* @returns the emitter
*/
createCylinderEmitter(radius = 1, height = 1, radiusRange = 1, directionRandomizer = 0) {
const particleEmitter = CreateCylinderEmitter(radius, height, radiusRange, directionRandomizer);
this.particleEmitterType = particleEmitter;
return particleEmitter;
}
/**
* Creates a Directed Cylinder Emitter for the particle system (emits between direction1 and direction2)
* @param radius The radius of the cylinder to emit from
* @param height The height of the emission cylinder
* @param radiusRange the range of the emission cylinder [0-1] 0 Surface only, 1 Entire Radius (1 by default)
* @param direction1 Particles are emitted between the direction1 and direction2 from within the cylinder
* @param direction2 Particles are emitted between the direction1 and direction2 from within the cylinder
* @returns the emitter
*/
createDirectedCylinderEmitter(radius = 1, height = 1, radiusRange = 1, direction1 = new Vector3(0, 1.0, 0), direction2 = new Vector3(0, 1.0, 0)) {
const particleEmitter = CreateDirectedCylinderEmitter(radius, height, radiusRange, direction1, direction2);
this.particleEmitterType = particleEmitter;
return particleEmitter;
}
/**
* Creates a Cone Emitter for the particle system (emits from the cone to the particle position)
* @param radius The radius of the cone to emit from
* @param angle The base angle of the cone
* @returns the emitter
*/
createConeEmitter(radius = 1, angle = Math.PI / 4) {
const particleEmitter = CreateConeEmitter(radius, angle);
this.particleEmitterType = particleEmitter;
return particleEmitter;
}
/**
* Creates a Cone Emitter for the particle system (emits from the cone to the particle position)
* @param radius The radius of the cone to emit from
* @param angle The base angle of the cone
* @param direction1 Particles are emitted between the direction1 and direction2 from within the cone
* @param direction2 Particles are emitted between the direction1 and direction2 from within the cone
* @returns the emitter
*/
createDirectedConeEmitter(radius = 1, angle = Math.PI / 4, direction1 = new Vector3(0, 1.0, 0), direction2 = new Vector3(0, 1.0, 0)) {
const particleEmitter = CreateDirectedConeEmitter(radius, angle, direction1, direction2);
this.particleEmitterType = particleEmitter;
return particleEmitter;
}
/**
* Creates a Box Emitter for the particle system. (emits between direction1 and direction2 from withing the box defined by minEmitBox and maxEmitBox)
* @param direction1 Particles are emitted between the direction1 and direction2 from within the box
* @param direction2 Particles are emitted between the direction1 and direction2 from within the box
* @param minEmitBox Particles are emitted from the box between minEmitBox and maxEmitBox
* @param maxEmitBox Particles are emitted from the box between minEmitBox and maxEmitBox
* @returns the emitter
*/
createBoxEmitter(direction1, direction2, minEmitBox, maxEmitBox) {
const particleEmitter = new BoxParticleEmitter();
this.particleEmitterType = particleEmitter;
this.direction1 = direction1;
this.direction2 = direction2;
this.minEmitBox = minEmitBox;
this.maxEmitBox = maxEmitBox;
return particleEmitter;
}
_prepareSubEmitterInternalArray() {
this._subEmitters = new Array();
if (this.subEmitters) {
for (const subEmitter of this.subEmitters) {
if (subEmitter instanceof ParticleSystem) {
this._subEmitters.push([new SubEmitter(subEmitter)]);
}
else if (subEmitter instanceof SubEmitter) {
this._subEmitters.push([subEmitter]);
}
else if (subEmitter instanceof Array) {
this._subEmitters.push(subEmitter);
}
}
}
}
_stopSubEmitters() {
if (!this.activeSubSystems) {
return;
}
for (const subSystem of this.activeSubSystems) {
subSystem.stop(true);
}
this.activeSubSystems = [];
}
_removeFromRoot() {
if (!this._rootParticleSystem) {
return;
}
const index = this._rootParticleSystem.activeSubSystems.indexOf(this);
if (index !== -1) {
this._rootParticleSystem.activeSubSystems.splice(index, 1);
}
this._rootParticleSystem = null;
}
/** @internal */
_preStart() {
// Convert the subEmitters field to the constant type field _subEmitters
this._prepareSubEmitterInternalArray();
if (this._subEmitters && this._subEmitters.length != 0) {
this.activeSubSystems = [];
}
}
/** @internal */
_postStop(stopSubEmitters) {
if (stopSubEmitters) {
this._stopSubEmitters();
}
}
/** @internal */
_prepareParticle(particle) {
// Attach emitters
if (this._subEmitters && this._subEmitters.length > 0) {
const subEmitters = this._subEmitters[Math.floor(Math.random() * this._subEmitters.length)];
particle._properties.attachedSubEmitters = [];
for (const subEmitter of subEmitters) {
if (subEmitter.type === 0 /* SubEmitterType.ATTACHED */) {
const newEmitter = subEmitter.clone();
particle._properties.attachedSubEmitters.push(newEmitter);
newEmitter.particleSystem.start();
}
}
}
}
/** @internal */
_onDispose(disposeAttachedSubEmitters = false, disposeEndSubEmitters = false) {
this._removeFromRoot();
if (this.subEmitters && !this._subEmitters) {
this._prepareSubEmitterInternalArray();
}
if (disposeAttachedSubEmitters) {
if (this.particles) {
for (const particle of this.particles) {
if (particle._properties.attachedSubEmitters) {
for (let i = particle._properties.attachedSubEmitters.length - 1; i >= 0; i -= 1) {
particle._properties.attachedSubEmitters[i].dispose();
}
}
}
}
}
if (disposeEndSubEmitters) {
if (this.activeSubSystems) {
for (let i = this.activeSubSystems.length - 1; i >= 0; i -= 1) {
this.activeSubSystems[i].dispose();
}
}
}
if (this._subEmitters && this._subEmitters.length) {
for (let index = 0; index < this._subEmitters.length; index++) {
for (const subEmitter of this._subEmitters[index]) {
subEmitter.dispose();
}
}
this._subEmitters = [];
this.subEmitters = [];
}
if (this._disposeEmitterOnDispose && this.emitter && this.emitter.dispose) {
this.emitter.dispose(true);
}
}
/**
* @internal
*/
static _Parse(parsedParticleSystem, particleSystem, sceneOrEngine, rootUrl) {
let scene;
if (sceneOrEngine instanceof AbstractEngine) {
scene = null;
}
else {
scene = sceneOrEngine;
}
const internalClass = GetClass("BABYLON.Texture");
if (internalClass && scene) {
// Texture
if (parsedParticleSystem.texture) {
particleSystem.particleTexture = internalClass.Parse(parsedParticleSystem.texture, scene, rootUrl);
}
else if (parsedParticleSystem.textureName) {
particleSystem.particleTexture = new internalClass(rootUrl + parsedParticleSystem.textureName, scene, false, parsedParticleSystem.invertY !== undefined ? parsedParticleSystem.invertY : true);
particleSystem.particleTexture.name = parsedParticleSystem.textureName;
}
}
// Emitter
if (!parsedParticleSystem.emitterId && parsedParticleSystem.emitterId !== 0 && parsedParticleSystem.emitter === undefined) {
particleSystem.emitter = Vector3.Zero();
}
else if ((parsedParticleSystem.emitterId || parsedParticleSystem.emitterId === 0) && scene) {
particleSystem.emitter = scene.getLastMeshById(parsedParticleSystem.emitterId);
if (!particleSystem.emitter) {
particleSystem.emitter = Vector3.Zero();
}
}
else {
particleSystem.emitter = Vector3.FromArray(parsedParticleSystem.emitter);
}
particleSystem.isLocal = !!parsedParticleSystem.isLocal;
// Misc.
if (parsedParticleSystem.renderingGroupId !== undefined) {
particleSystem.renderingGroupId = parsedParticleSystem.renderingGroupId;
}
if (parsedParticleSystem.isBillboardBased !== undefined) {
particleSystem.isBillboardBased = parsedParticleSystem.isBillboardBased;
}
if (parsedParticleSystem.billboardMode !== undefined) {
particleSystem.billboardMode = parsedParticleSystem.billboardMode;
}
if (parsedParticleSystem.useLogarithmicDepth !== undefined) {
particleSystem.useLogarithmicDepth = parsedParticleSystem.useLogarithmicDepth;
}
// Animations
if (parsedParticleSystem.animations) {
for (let animationIndex = 0; animationIndex < parsedParticleSystem.animations.length; animationIndex++) {
const parsedAnimation = parsedParticleSystem.animations[animationIndex];
const internalClass = GetClass("BABYLON.Animation");
if (internalClass) {
particleSystem.animations.push(internalClass.Parse(parsedAnimation));
}
}
particleSystem.beginAnimationOnStart = parsedParticleSystem.beginAnimationOnStart;
particleSystem.beginAnimationFrom = parsedParticleSystem.beginAnimationFrom;
particleSystem.beginAnimationTo = parsedParticleSystem.beginAnimationTo;
particleSystem.beginAnimationLoop = parsedParticleSystem.beginAnimationLoop;
}
if (parsedParticleSystem.autoAnimate && scene) {
scene.beginAnimation(particleSystem, parsedParticleSystem.autoAnimateFrom, parsedParticleSystem.autoAnimateTo, parsedParticleSystem.autoAnimateLoop, parsedParticleSystem.autoAnimateSpeed || 1.0);
}
// Particle system
particleSystem.startDelay = parsedParticleSystem.startDelay | 0;
particleSystem.minAngularSpeed = parsedParticleSystem.minAngularSpeed;
particleSystem.maxAngularSpeed = parsedParticleSystem.maxAngularSpeed;
particleSystem.minSize = parsedParticleSystem.minSize;
particleSystem.maxSize = parsedParticleSystem.maxSize;
if (parsedParticleSystem.minScaleX) {
particleSystem.minScaleX = parsedParticleSystem.minScaleX;
particleSystem.maxScaleX = parsedParticleSystem.maxScaleX;
particleSystem.minScaleY = parsedParticleSystem.minScaleY;
particleSystem.maxScaleY = parsedParticleSystem.maxScaleY;
}
if (parsedParticleSystem.preWarmCycles !== undefined) {
particleSystem.preWarmCycles = parsedParticleSystem.preWarmCycles;
particleSystem.preWarmStepOffset = parsedParticleSystem.preWarmStepOffset;
}
if (parsedParticleSystem.minInitialRotation !== undefined) {
particleSystem.minInitialRotation = parsedParticleSystem.minInitialRotation;
particleSystem.maxInitialRotation = parsedParticleSystem.maxInitialRotation;
}
particleSystem.minLifeTime = parsedParticleSystem.minLifeTime;
particleSystem.maxLifeTime = parsedParticleSystem.maxLifeTime;
particleSystem.minEmitPower = parsedParticleSystem.minEmitPower;
particleSystem.maxEmitPower = parsedParticleSystem.maxEmitPower;
particleSystem.emitRate = parsedParticleSystem.emitRate;
particleSystem.gravity = Vector3.FromArray(parsedParticleSystem.gravity);
if (parsedParticleSystem.noiseStrength) {
particleSystem.noiseStrength = Vector3.FromArray(parsedParticleSystem.noiseStrength);
}
particleSystem.color1 = Color4.FromArray(parsedParticleSystem.color1);
particleSystem.color2 = Color4.FromArray(parsedParticleSystem.color2);
particleSystem.colorDead = Color4.FromArray(parsedParticleSystem.colorDead);
particleSystem.updateSpeed = parsedParticleSystem.updateSpeed;
particleSystem.targetStopDuration = parsedParticleSystem.targetStopDuration;
particleSystem.blendMode = parsedParticleSystem.blendMode;
if (parsedParticleSystem.colorGradients) {
for (const colorGradient of parsedParticleSystem.colorGradients) {
particleSystem.addColorGradient(colorGradient.gradient, Color4.FromArray(colorGradient.color1), colorGradient.color2 ? Color4.FromArray(colorGradient.color2) : undefined);
}
}
if (parsedParticleSystem.rampGradients) {
for (const rampGradient of parsedParticleSystem.rampGradients) {
particleSystem.addRampGradient(rampGradient.gradient, Color3.FromArray(rampGradient.color));
}
particleSystem.useRampGradients = parsedParticleSystem.useRampGradients;
}
if (parsedParticleSystem.colorRemapGradients) {
for (const colorRemapGradient of parsedParticleSystem.colorRemapGradients) {
particleSystem.addColorRemapGradient(colorRemapGradient.gradient, colorRemapGradient.factor1 !== undefined ? colorRemapGradient.factor1 : colorRemapGradient.factor, colorRemapGradient.factor2);
}
}
if (parsedParticleSystem.alphaRemapGradients) {
for (const alphaRemapGradient of parsedParticleSystem.alphaRemapGradients) {
particleSystem.addAlphaRemapGradient(alphaRemapGradient.gradient, alphaRemapGradient.factor1 !== undefined ? alphaRemapGradient.factor1 : alphaRemapGradient.factor, alphaRemapGradient.factor2);
}
}
if (parsedParticleSystem.sizeGradients) {
for (const sizeGradient of parsedParticleSystem.sizeGradients) {
particleSystem.addSizeGradient(sizeGradient.gradient, sizeGradient.factor1 !== undefined ? sizeGradient.factor1 : sizeGradient.factor, sizeGradient.factor2);
}
}
if (parsedParticleSystem.angularSpeedGradients) {
for (const angularSpeedGradient of parsedParticleSystem.angularSpeedGradients) {
particleSystem.addAngularSpeedGradient(angularSpeedGradient.gradient, angularSpeedGradient.factor1 !== undefined ? angularSpeedGradient.factor1 : angularSpeedGradient.factor, angularSpeedGradient.factor2);
}
}
if (parsedParticleSystem.velocityGradients) {
for (const velocityGradient of parsedParticleSystem.velocityGradients) {
particleSystem.addVelocityGradient(velocityGradient.gradient, velocityGradient.factor1 !== undefined ? velocityGradient.factor1 : velocityGradient.factor, velocityGradient.factor2);
}
}
if (parsedParticleSystem.dragGradients) {
for (const dragGradient of parsedParticleSystem.dragGradients) {
particleSystem.addDragGradient(dragGradient.gradient, dragGradient.factor1 !== undefined ? dragGradient.factor1 : dragGradient.factor, dragGradient.factor2);
}
}
if (parsedParticleSystem.emitRateGradients) {
for (const emitRateGradient of parsedParticleSystem.emitRateGradients) {
particleSystem.addEmitRateGradient(emitRateGradient.gradient, emitRateGradient.factor1 !== undefined ? emitRateGradient.factor1 : emitRateGradient.factor, emitRateGradient.factor2);
}
}
if (parsedParticleSystem.startSizeGradients) {
for (const startSizeGradient of parsedParticleSystem.startSizeGradients) {
particleSystem.addStartSizeGradient(startSizeGradient.gradient, startSizeGradient.factor1 !== undefined ? startSizeGradient.factor1 : startSizeGradient.factor, startSizeGradient.factor2);
}
}
if (parsedParticleSystem.lifeTimeGradients) {
for (const lifeTimeGradient of parsedParticleSystem.lifeTimeGradients) {
particleSystem.addLifeTimeGradient(lifeTimeGradient.gradient, lifeTimeGradient.factor1 !== undefined ? lifeTimeGradient.factor1 : lifeTimeGradient.factor, lifeTimeGradient.factor2);
}
}
if (parsedParticleSystem.limitVelocityGradients) {
for (const limitVelocityGradient of parsedParticleSystem.limitVelocityGradients) {
particleSystem.addLimitVelocityGradient(limitVelocityGradient.gradient, limitVelocityGradient.factor1 !== undefined ? limitVelocityGradient.factor1 : limitVelocityGradient.factor, limitVelocityGradient.factor2);
}
particleSystem.limitVelocityDamping = parsedParticleSystem.limitVelocityDamping;
}
if (parsedParticleSystem.noiseTexture && scene) {
const internalClass = GetClass("BABYLON.ProceduralTexture");
particleSystem.noiseTexture = internalClass.Parse(parsedParticleSystem.noiseTexture, scene, rootUrl);
}
// Emitter
let emitterType;
if (parsedParticleSystem.particleEmitterType) {
switch (parsedParticleSystem.particleEmitterType.type) {
case "SphereParticleEmitter":
emitterType = new SphereParticleEmitter();
break;
case "SphereDirectedParticleEmitter":
emitterType = new SphereDirectedParticleEmitter();
break;
case "ConeEmitter":
case "ConeParticleEmitter":
emitterType = new ConeParticleEmitter();
break;
case "ConeDirectedParticleEmitter":
emitterType = new ConeDirectedParticleEmitter();
break;
case "CylinderParticleEmitter":
emitterType = new CylinderParticleEmitter();
break;
case "CylinderDirectedParticleEmitter":
emitterType = new CylinderDirectedParticleEmitter();
break;
case "HemisphericParticleEmitter":
emitterType = new HemisphericParticleEmitter();
break;
case "PointParticleEmitter":
emitterType = new PointParticleEmitter();
break;
case "MeshParticleEmitter":
emitterType = new MeshParticleEmitter();
break;
case "CustomParticleEmitter":
emitterType = new CustomParticleEmitter();
break;
case "BoxEmitter":
case "BoxParticleEmitter":
default:
emitterType = new BoxParticleEmitter();
break;
}
emitterType.parse(parsedParticleSystem.particleEmitterType, scene);
}
else {
emitterType = new BoxParticleEmitter();
emitterType.parse(parsedParticleSystem, scene);
}
particleSystem.particleEmitterType = emitterType;
// Animation sheet
particleSystem.startSpriteCellID = parsedParticleSystem.startSpriteCellID;
particleSystem.endSpriteCellID = parsedParticleSystem.endSpriteCellID;
particleSystem.spriteCellLoop = parsedParticleSystem.spriteCellLoop ?? true;
particleSystem.spriteCellWidth = parsedParticleSystem.spriteCellWidth;
particleSystem.spriteCellHeight = parsedParticleSystem.spriteCellHeight;
particleSystem.spriteCellChangeSpeed = parsedParticleSystem.spriteCellChangeSpeed;
particleSystem.spriteRandomStartCell = parsedParticleSystem.spriteRandomStartCell;
particleSystem.disposeOnStop = parsedParticleSystem.disposeOnStop ?? false;
particleSystem.manualEmitCount = parsedParticleSystem.manualEmitCount ?? -1;
}
/**
* Parses a JSON object to create a particle system.
* @param parsedParticleSystem The JSON object to parse
* @param sceneOrEngine The scene or the engine to create the particle system in
* @param rootUrl The root url to use to load external dependencies like texture
* @param doNotStart Ignore the preventAutoStart attribute and does not start
* @param capacity defines the system capacity (if null or undefined the sotred capacity will be used)
* @returns the Parsed particle system
*/
static Parse(parsedParticleSystem, sceneOrEngine, rootUrl, doNotStart = false, capacity) {
const name = parsedParticleSystem.name;
let custom = null;
let program = null;
let engine;
let scene;
if (sceneOrEngine instanceof AbstractEngine) {
engine = sceneOrEngine;
}
else {
scene = sceneOrEngine;
engine = scene.getEngine();
}
if (parsedParticleSystem.customShader && engine.createEffectForParticles) {
program = parsedParticleSystem.customShader;
const defines = program.shaderOptions.defines.length > 0 ? program.shaderOptions.defines.join("\n") : "";
custom = engine.createEffectForParticles(program.shaderPath.fragmentElement, program.shaderOptions.uniforms, program.shaderOptions.samplers, defines);
}
const particleSystem = new ParticleSystem(name, capacity || parsedParticleSystem.capacity, sceneOrEngine, custom, parsedParticleSystem.isAnimationSheetEnabled);
particleSystem.customShader = program;
particleSystem._rootUrl = rootUrl;
if (parsedParticleSystem.id) {
particleSystem.id = parsedParticleSystem.id;
}
// SubEmitters
if (parsedParticleSystem.subEmitters) {
particleSystem.subEmitters = [];
for (const cell of parsedParticleSystem.subEmitters) {
const cellArray = [];
for (const sub of cell) {
cellArray.push(SubEmitter.Parse(sub, sceneOrEngine, rootUrl));
}
particleSystem.subEmitters.push(cellArray);
}
}
// Attractors
if (parsedParticleSystem.attractors) {
for (const attractor of parsedParticleSystem.attractors) {
const newAttractor = new Attractor();
newAttractor.position = Vector3.FromArray(attractor.position);
newAttractor.strength = attractor.strength;
particleSystem.addAttractor(newAttractor);
}
}
ParticleSystem._Parse(parsedParticleSystem, particleSystem, sceneOrEngine, rootUrl);
if (parsedParticleSystem.textureMask) {
particleSystem.textureMask = Color4.FromArray(parsedParticleSystem.textureMask);
}
if (parsedParticleSystem.worldOffset) {
particleSystem.worldOffset = Vector3.FromArray(parsedParticleSystem.worldOffset);
}
// Auto start
if (parsedParticleSystem.preventAutoStart) {
particleSystem.preventAutoStart = parsedParticleSystem.preventAutoStart;
}
if (parsedParticleSystem.metadata) {
particleSystem.metadata = parsedParticleSystem.metadata;
}
if (!doNotStart && !particleSystem.preventAutoStart) {
particleSystem.start();
}
return particleSystem;
}
/**
* Serializes the particle system to a JSON object
* @param serializeTexture defines if the texture must be serialized as well
* @returns the JSON object
*/
serialize(serializeTexture = false) {
const serializationObject = {};
ParticleSystem._Serialize(serializationObject, this, serializeTexture);
serializationObject.textureMask = this.textureMask.asArray();
serializationObject.customShader = this.customShader;
serializationObject.preventAutoStart = this.preventAutoStart;
serializationObject.worldOffset = this.worldOffset.asArray();
if (this.metadata) {
serializationObject.metadata = this.metadata;
}
// SubEmitters
if (this.subEmitters) {
serializationObject.subEmitters = [];
if (!this._subEmitters) {
this._prepareSubEmitterInternalArray();
}
for (const subs of this._subEmitters) {
const cell = [];
for (const sub of subs) {
if (!sub.particleSystem.doNotSerialize) {
cell.push(sub.serialize(serializeTexture));
}
}
serializationObject.subEmitters.push(cell);
}
}
// Attractors
if (this._attractors && this._attractors.length) {
serializationObject.attractors = [];
for (const attractor of this._attractors) {
serializationObject.attractors.push(attractor.serialize());
}
}
return serializationObject;
}
/**
* @internal
*/
static _Serialize(serializationObject, particleSystem, serializeTexture) {
serializationObject.name = particleSystem.name;
serializationObject.id = particleSystem.id;
serializationObject.capacity = particleSystem.getCapacity();
serializationObject.disposeOnStop = particleSystem.disposeOnStop;
serializationObject.manualEmitCount = particleSystem.manualEmitCount;
// Emitter
if (particleSystem.emitter.position) {
const emitterMesh = particleSystem.emitter;
serializationObject.emitterId = emitterMesh.id;
}
else {
const emitterPosition = particleSystem.emitter;
serializationObject.emitter = emitterPosition.asArray();
}
// Emitter
if (particleSystem.particleEmitterType) {
serializationObject.particleEmitterType = particleSystem.particleEmitterType.serialize();
}
if (particleSystem.particleTexture) {
if (serializeTexture) {
serializationObject.texture = particleSystem.particleTexture.serialize();
}
else {
serializationObject.textureName = particleSystem.particleTexture.name;
serializationObject.invertY = !!particleSystem.particleTexture._invertY;
}
}
serializationObject.isLocal = particleSystem.isLocal;
// Animations
SerializationHelper.AppendSerializedAnimations(particleSystem, serializationObject);
serializationObject.beginAnimationOnStart = particleSystem.beginAnimationOnStart;
serializationObject.beginAnimationFrom = particleSystem.beginAnimationFrom;
serializationObject.beginAnimationTo = particleSystem.beginAnimationTo;
serializationObject.beginAnimationLoop = particleSystem.beginAnimationLoop;
// Particle system
serializationObject.startDelay = particleSystem.startDelay;
serializationObject.renderingGroupId = particleSystem.renderingGroupId;
serializationObject.isBillboardBased = particleSystem.isBillboardBased;
serializationObject.billboardMode = particleSystem.billboardMode;
serializationObject.minAngularSpeed = particleSystem.minAngularSpeed;
serializationObject.maxAngularSpeed = particleSystem.maxAngularSpeed;
serializationObject.minSize = particleSystem.minSize;
serializationObject.maxSize = particleSystem.maxSize;
serializationObject.minScaleX = particleSystem.minScaleX;
serializationObject.maxScaleX = particleSystem.maxScaleX;
serializationObject.minScaleY = particleSystem.minScaleY;
serializationObject.maxScaleY = particleSystem.maxScaleY;
serializationObject.minEmitPower = particleSystem.minEmitPower;
serializationObject.maxEmitPower = particleSystem.maxEmitPower;
serializationObject.minLifeTime = particleSystem.minLifeTime;
serializationObject.maxLifeTime = particleSystem.maxLifeTime;
serializationObject.emitRate = particleSystem.emitRate;
serializationObject.gravity = particleSystem.gravity.asArray();
serializationObject.noiseStrength = particleSystem.noiseStrength.asArray();
serializationObject.color1 = particleSystem.color1.asArray();
serializationObject.color2 = particleSystem.color2.asArray();
serializationObject.colorDead = particleSystem.colorDead.asArray();
serializationObject.updateSpeed = particleSystem.updateSpeed;
serializationObject.targetStopDuration = particleSystem.targetStopDuration;
serializationObject.blendMode = particleSystem.blendMode;
serializationObject.preWarmCycles = particleSystem.preWarmCycles;
serializationObject.preWarmStepOffset = particleSystem.preWarmStepOffset;
serializationObject.minInitialRotation = particleSystem.minInitialRotation;
serializationObject.maxInitialRotation = particleSystem.maxInitialRotation;
serializationObject.startSpriteCellID = particleSystem.startSpriteCellID;
serializationObject.spriteCellLoop = particleSystem.spriteCellLoop;
serializationObject.endSpriteCellID = particleSystem.endSpriteCellID;
serializationObject.spriteCellChangeSpeed = particleSystem.spriteCellChangeSpeed;
serializationObject.spriteCellWidth = particleSystem.spriteCellWidth;
serializationObject.spriteCellHeight = particleSystem.spriteCellHeight;
serializationObject.spriteRandomStartCell = particleSystem.spriteRandomStartCell;
serializationObject.isAnimationSheetEnabled = particleSystem.isAnimationSheetEnabled;
serializationObject.useLogarithmicDepth = particleSystem.useLogarithmicDepth;
const colorGradients = particleSystem.getColorGradients();
if (colorGradients) {
serializationObject.colorGradients = [];
for (const colorGradient of colorGradients) {
const serializedGradient = {
gradient: colorGradient.gradient,
color1: colorGradient.color1.asArray(),
};
if (colorGradient.color2) {
serializedGradient.color2 = colorGradient.color2.asArray();
}
else {
serializedGradient.color2 = colorGradient.color1.asArray();
}
serializationObject.colorGradients.push(serializedGradient);
}
}
const rampGradients = particleSystem.getRampGradients();
if (rampGradients) {
serializationObject.rampGradients = [];
for (const rampGradient of rampGradients) {
const serializedGradient = {
gradient: rampGradient.gradient,
color: rampGradient.color.asArray(),
};
serializationObject.rampGradients.push(serializedGradient);
}
serializationObject.useRampGradients = particleSystem.useRampGradients;
}
const colorRemapGradients = particleSystem.getColorRemapGradients();
if (colorRemapGradients) {
serializationObject.colorRemapGradients = [];
for (const colorRemapGradient of colorRemapGradients) {
const serializedGradient = {
gradient: colorRemapGradient.gradient,
factor1: colorRemapGradient.factor1,
};
if (colorRemapGradient.factor2 !== undefined) {
serializedGradient.factor2 = colorRemapGradient.factor2;
}
else {
serializedGradient.factor2 = colorRemapGradient.factor1;
}
serializationObject.colorRemapGradients.push(serializedGradient);
}
}
const alphaRemapGradients = particleSystem.getAlphaRemapGradients();
if (alphaRemapGradients) {
serializationObject.alphaRemapGradients = [];
for (const alphaRemapGradient of alphaRemapGradients) {
const serializedGradient = {
gradient: alphaRemapGradient.gradient,
factor1: alphaRemapGradient.factor1,
};
if (alphaRemapGradient.factor2 !== undefined) {
serializedGradient.factor2 = alphaRemapGradient.factor2;
}
else {
serializedGradient.factor2 = alphaRemapGradient.factor1;
}
serializationObject.alphaRemapGradients.push(serializedGradient);
}
}
const sizeGradients = particleSystem.getSizeGradients();
if (sizeGradients) {
serializationObject.sizeGradients = [];
for (const sizeGradient of sizeGradients) {
const serializedGradient = {
gradient: sizeGradient.gradient,
factor1: sizeGradient.factor1,
};
if (sizeGradient.factor2 !== undefined) {
serializedGradient.factor2 = sizeGradient.factor2;
}
else {
serializedGradient.factor2 = sizeGradient.factor1;
}
serializationObject.sizeGradients.push(serializedGradient);
}
}
const angularSpeedGradients = particleSystem.getAngularSpeedGradients();
if (angularSpeedGradients) {
serializationObject.angularSpeedGradients = [];
for (const angularSpeedGradient of angularSpeedGradients) {
const serializedGradient = {
gradient: angularSpeedGradient.gradient,
factor1: angularSpeedGradient.factor1,
};
if (angularSpeedGradient.factor2 !== undefined) {
serializedGradient.factor2 = angularSpeedGradient.factor2;
}
else {
serializedGradient.factor2 = angularSpeedGradient.factor1;
}
serializationObject.angularSpeedGradients.push(serializedGradient);
}
}
const velocityGradients = particleSystem.getVelocityGradients();
if (velocityGradients) {
serializationObject.velocityGradients = [];
for (const velocityGradient of velocityGradients) {
const serializedGradient = {
gradient: velocityGradient.gradient,
factor1: velocityGradient.factor1,
};
if (velocityGradient.factor2 !== undefined) {
serializedGradient.factor2 = velocityGradient.factor2;
}
else {
serializedGradient.factor2 = velocityGradient.factor1;
}
serializationObject.velocityGradients.push(serializedGradient);
}
}
const dragGradients = particleSystem.getDragGradients();
if (dragGradients) {
serializationObject.dragGradients = [];
for (const dragGradient of dragGradients) {
const serializedGradient = {
gradient: dragGradient.gradient,
factor1: dragGradient.factor1,
};
if (dragGradient.factor2 !== undefined) {
serializedGradient.factor2 = dragGradient.factor2;
}
else {
serializedGradient.factor2 = dragGradient.factor1;
}
serializationObject.dragGradients.push(serializedGradient);
}
}
const emitRateGradients = particleSystem.getEmitRateGradients();
if (emitRateGradients) {
serializationObject.emitRateGradients = [];
for (const emitRateGradient of emitRateGradients) {
const serializedGradient = {
gradient: emitRateGradient.gradient,
factor1: emitRateGradient.factor1,
};
if (emitRateGradient.factor2 !== undefined) {
serializedGradient.factor2 = emitRateGradient.factor2;
}
else {
serializedGradient.factor2 = emitRateGradient.factor1;
}
serializationObject.emitRateGradients.push(serializedGradient);
}
}
const startSizeGradients = particleSystem.getStartSizeGradients();
if (startSizeGradients) {
serializationObject.startSizeGradients = [];
for (const startSizeGradient of startSizeGradients) {
const serializedGradient = {
gradient: startSizeGradient.gradient,
factor1: startSizeGradient.factor1,
};
if (startSizeGradient.factor2 !== undefined) {
serializedGradient.factor2 = startSizeGradient.factor2;
}
else {
serializedGradient.factor2 = startSizeGradient.factor1;
}
serializationObject.startSizeGradients.push(serializedGradient);
}
}
const lifeTimeGradients = particleSystem.getLifeTimeGradients();
if (lifeTimeGradients) {
serializationObject.lifeTimeGradients = [];
for (const lifeTimeGradient of lifeTimeGradients) {
const serializedGradient = {
gradient: lifeTimeGradient.gradient,
factor1: lifeTimeGradient.factor1,
};
if (lifeTimeGradient.factor2 !== undefined) {
serializedGradient.factor2 = lifeTimeGradient.factor2;
}
else {
serializedGradient.factor2 = lifeTimeGradient.factor1;
}
serializationObject.lifeTimeGradients.push(serializedGradient);
}
}
const limitVelocityGradients = particleSystem.getLimitVelocityGradients();
if (limitVelocityGradients) {
serializationObject.limitVelocityGradients = [];
for (const limitVelocityGradient of limitVelocityGradients) {
const serializedGradient = {
gradient: limitVelocityGradient.gradient,
factor1: limitVelocityGradient.factor1,
};
if (limitVelocityGradient.factor2 !== undefined) {
serializedGradient.factor2 = limitVelocityGradient.factor2;
}
else {
serializedGradient.factor2 = limitVelocityGradient.factor1;
}
serializationObject.limitVelocityGradients.push(serializedGradient);
}
serializationObject.limitVelocityDamping = particleSystem.limitVelocityDamping;
}
if (particleSystem.noiseTexture) {
serializationObject.noiseTexture = particleSystem.noiseTexture.serialize();
}
}
// Clone
/**
* Clones the particle system.
* @param name The name of the cloned object
* @param newEmitter The new emitter to use
* @param cloneTexture Also clone the textures if true
* @returns the cloned particle system
*/
clone(name, ne