123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770 |
- import { FactorGradient, ColorGradient, Color3Gradient, GradientHelper } from "../Misc/gradients.js";
- import { Observable } from "../Misc/observable.js";
- import { Vector3, Matrix, Vector4, TmpVectors } from "../Maths/math.vector.js";
- import { VertexBuffer, Buffer } from "../Buffers/buffer.js";
- import { RawTexture } from "../Materials/Textures/rawTexture.js";
- import { EngineStore } from "../Engines/engineStore.js";
- import { BaseParticleSystem } from "./baseParticleSystem.js";
- import { Particle } from "./particle.js";
- import { DrawWrapper } from "../Materials/drawWrapper.js";
- import "../Shaders/particles.fragment.js";
- import "../Shaders/particles.vertex.js";
- import { Color4, Color3, TmpColors } from "../Maths/math.color.js";
- import "../Engines/Extensions/engine.alpha.js";
- import { addClipPlaneUniforms, prepareStringDefinesForClipPlanes, bindClipPlane } from "../Materials/clipPlaneMaterialHelper.js";
- import { BindFogParameters, BindLogDepth } from "../Materials/materialHelper.functions.js";
- import { BoxParticleEmitter } from "./EmitterTypes/boxParticleEmitter.js";
- import { Clamp, Lerp, RandomRange } from "../Maths/math.scalar.functions.js";
- import { PrepareSamplersForImageProcessing, PrepareUniformsForImageProcessing } from "../Materials/imageProcessingConfiguration.functions.js";
- /**
- * This represents a thin 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.
- * This thin version contains a limited subset of the total features in order to provide users with a way to get particles but with a smaller footprint
- * @example https://doc.babylonjs.com/features/featuresDeepDive/particles/particle_system/particle_system_intro
- */
- export class ThinParticleSystem extends BaseParticleSystem {
- /**
- * Sets a callback that will be triggered when the system is disposed
- */
- set onDispose(callback) {
- if (this._onDisposeObserver) {
- this.onDisposeObservable.remove(this._onDisposeObserver);
- }
- this._onDisposeObserver = this.onDisposeObservable.add(callback);
- }
- /** Gets or sets a boolean indicating that ramp gradients must be used
- * @see https://doc.babylonjs.com/features/featuresDeepDive/particles/particle_system/particle_system_intro#ramp-gradients
- */
- get useRampGradients() {
- return this._useRampGradients;
- }
- set useRampGradients(value) {
- if (this._useRampGradients === value) {
- return;
- }
- this._useRampGradients = value;
- this._resetEffect();
- }
- /**
- * Gets the current list of active particles
- */
- get particles() {
- return this._particles;
- }
- /**
- * Gets the number of particles active at the same time.
- * @returns The number of active particles.
- */
- getActiveCount() {
- return this._particles.length;
- }
- /**
- * Returns the string "ParticleSystem"
- * @returns a string containing the class name
- */
- getClassName() {
- return "ParticleSystem";
- }
- /**
- * Gets a boolean indicating that the system is stopping
- * @returns true if the system is currently stopping
- */
- isStopping() {
- return this._stopped && this.isAlive();
- }
- /**
- * Gets the custom effect used to render the particles
- * @param blendMode Blend mode for which the effect should be retrieved
- * @returns The effect
- */
- getCustomEffect(blendMode = 0) {
- return this._customWrappers[blendMode]?.effect ?? this._customWrappers[0].effect;
- }
- _getCustomDrawWrapper(blendMode = 0) {
- return this._customWrappers[blendMode] ?? this._customWrappers[0];
- }
- /**
- * Sets the custom effect used to render the particles
- * @param effect The effect to set
- * @param blendMode Blend mode for which the effect should be set
- */
- setCustomEffect(effect, blendMode = 0) {
- this._customWrappers[blendMode] = new DrawWrapper(this._engine);
- this._customWrappers[blendMode].effect = effect;
- if (this._customWrappers[blendMode].drawContext) {
- this._customWrappers[blendMode].drawContext.useInstancing = this._useInstancing;
- }
- }
- /**
- * Observable that will be called just before the particles are drawn
- */
- get onBeforeDrawParticlesObservable() {
- if (!this._onBeforeDrawParticlesObservable) {
- this._onBeforeDrawParticlesObservable = new Observable();
- }
- return this._onBeforeDrawParticlesObservable;
- }
- /**
- * Gets the name of the particle vertex shader
- */
- get vertexShaderName() {
- return "particles";
- }
- /**
- * Gets the vertex buffers used by the particle system
- */
- get vertexBuffers() {
- return this._vertexBuffers;
- }
- /**
- * Gets the index buffer used by the particle system (or null if no index buffer is used (if _useInstancing=true))
- */
- get indexBuffer() {
- return this._indexBuffer;
- }
- /**
- * Instantiates a particle system.
- * 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.
- * @param name The name of the particle system
- * @param capacity The max number of particles alive at the same time
- * @param sceneOrEngine The scene the particle system belongs to or the engine to use if no scene
- * @param customEffect a custom effect used to change the way particles are rendered by default
- * @param isAnimationSheetEnabled Must be true if using a spritesheet to animate the particles texture
- * @param epsilon Offset used to render the particles
- */
- constructor(name, capacity, sceneOrEngine, customEffect = null, isAnimationSheetEnabled = false, epsilon = 0.01) {
- super(name);
- this._emitterInverseWorldMatrix = Matrix.Identity();
- /**
- * @internal
- */
- this._inheritedVelocityOffset = new Vector3();
- /**
- * An event triggered when the system is disposed
- */
- this.onDisposeObservable = new Observable();
- /**
- * An event triggered when the system is stopped
- */
- this.onStoppedObservable = new Observable();
- this._particles = new Array();
- this._stockParticles = new Array();
- this._newPartsExcess = 0;
- this._vertexBuffers = {};
- this._scaledColorStep = new Color4(0, 0, 0, 0);
- this._colorDiff = new Color4(0, 0, 0, 0);
- this._scaledDirection = Vector3.Zero();
- this._scaledGravity = Vector3.Zero();
- this._currentRenderId = -1;
- this._useInstancing = false;
- this._started = false;
- this._stopped = false;
- this._actualFrame = 0;
- /** @internal */
- this._currentEmitRate1 = 0;
- /** @internal */
- this._currentEmitRate2 = 0;
- /** @internal */
- this._currentStartSize1 = 0;
- /** @internal */
- this._currentStartSize2 = 0;
- /** Indicates that the update of particles is done in the animate function */
- this.updateInAnimate = true;
- this._rawTextureWidth = 256;
- this._useRampGradients = false;
- /**
- * Specifies if the particles are updated in emitter local space or world space
- */
- this.isLocal = false;
- /** Indicates that the particle system is CPU based */
- this.isGPU = false;
- /** @internal */
- this._onBeforeDrawParticlesObservable = null;
- /** @internal */
- this._emitFromParticle = (particle) => {
- // Do nothing
- };
- // start of sub system methods
- /**
- * "Recycles" one of the particle by copying it back to the "stock" of particles and removing it from the active list.
- * Its lifetime will start back at 0.
- * @param particle
- */
- this.recycleParticle = (particle) => {
- // move particle from activeParticle list to stock particles
- const lastParticle = this._particles.pop();
- if (lastParticle !== particle) {
- lastParticle.copyTo(particle);
- }
- this._stockParticles.push(lastParticle);
- };
- this._createParticle = () => {
- let particle;
- if (this._stockParticles.length !== 0) {
- particle = this._stockParticles.pop();
- particle._reset();
- }
- else {
- particle = new Particle(this);
- }
- this._prepareParticle(particle);
- return particle;
- };
- this._capacity = capacity;
- this._epsilon = epsilon;
- this._isAnimationSheetEnabled = isAnimationSheetEnabled;
- if (!sceneOrEngine || sceneOrEngine.getClassName() === "Scene") {
- this._scene = sceneOrEngine || EngineStore.LastCreatedScene;
- this._engine = this._scene.getEngine();
- this.uniqueId = this._scene.getUniqueId();
- this._scene.particleSystems.push(this);
- }
- else {
- this._engine = sceneOrEngine;
- this.defaultProjectionMatrix = Matrix.PerspectiveFovLH(0.8, 1, 0.1, 100, this._engine.isNDCHalfZRange);
- }
- if (this._engine.getCaps().vertexArrayObject) {
- this._vertexArrayObject = null;
- }
- // Setup the default processing configuration to the scene.
- this._attachImageProcessingConfiguration(null);
- // eslint-disable-next-line @typescript-eslint/naming-convention
- this._customWrappers = { 0: new DrawWrapper(this._engine) };
- this._customWrappers[0].effect = customEffect;
- this._drawWrappers = [];
- this._useInstancing = this._engine.getCaps().instancedArrays;
- this._createIndexBuffer();
- this._createVertexBuffers();
- // Default emitter type
- this.particleEmitterType = new BoxParticleEmitter();
- let noiseTextureData = null;
- // Update
- this.updateFunction = (particles) => {
- let noiseTextureSize = null;
- if (this.noiseTexture) {
- // We need to get texture data back to CPU
- noiseTextureSize = this.noiseTexture.getSize();
- this.noiseTexture.getContent()?.then((data) => {
- noiseTextureData = data;
- });
- }
- const sameParticleArray = particles === this._particles;
- for (let index = 0; index < particles.length; index++) {
- const particle = particles[index];
- let scaledUpdateSpeed = this._scaledUpdateSpeed;
- const previousAge = particle.age;
- particle.age += scaledUpdateSpeed;
- // Evaluate step to death
- if (particle.age > particle.lifeTime) {
- const diff = particle.age - previousAge;
- const oldDiff = particle.lifeTime - previousAge;
- scaledUpdateSpeed = (oldDiff * scaledUpdateSpeed) / diff;
- particle.age = particle.lifeTime;
- }
- const ratio = particle.age / particle.lifeTime;
- // Color
- if (this._colorGradients && this._colorGradients.length > 0) {
- GradientHelper.GetCurrentGradient(ratio, this._colorGradients, (currentGradient, nextGradient, scale) => {
- if (currentGradient !== particle._currentColorGradient) {
- particle._currentColor1.copyFrom(particle._currentColor2);
- nextGradient.getColorToRef(particle._currentColor2);
- particle._currentColorGradient = currentGradient;
- }
- Color4.LerpToRef(particle._currentColor1, particle._currentColor2, scale, particle.color);
- });
- }
- else {
- particle.colorStep.scaleToRef(scaledUpdateSpeed, this._scaledColorStep);
- particle.color.addInPlace(this._scaledColorStep);
- if (particle.color.a < 0) {
- particle.color.a = 0;
- }
- }
- // Angular speed
- if (this._angularSpeedGradients && this._angularSpeedGradients.length > 0) {
- GradientHelper.GetCurrentGradient(ratio, this._angularSpeedGradients, (currentGradient, nextGradient, scale) => {
- if (currentGradient !== particle._currentAngularSpeedGradient) {
- particle._currentAngularSpeed1 = particle._currentAngularSpeed2;
- particle._currentAngularSpeed2 = nextGradient.getFactor();
- particle._currentAngularSpeedGradient = currentGradient;
- }
- particle.angularSpeed = Lerp(particle._currentAngularSpeed1, particle._currentAngularSpeed2, scale);
- });
- }
- particle.angle += particle.angularSpeed * scaledUpdateSpeed;
- // Direction
- let directionScale = scaledUpdateSpeed;
- /// Velocity
- if (this._velocityGradients && this._velocityGradients.length > 0) {
- GradientHelper.GetCurrentGradient(ratio, this._velocityGradients, (currentGradient, nextGradient, scale) => {
- if (currentGradient !== particle._currentVelocityGradient) {
- particle._currentVelocity1 = particle._currentVelocity2;
- particle._currentVelocity2 = nextGradient.getFactor();
- particle._currentVelocityGradient = currentGradient;
- }
- directionScale *= Lerp(particle._currentVelocity1, particle._currentVelocity2, scale);
- });
- }
- particle.direction.scaleToRef(directionScale, this._scaledDirection);
- /// Limit velocity
- if (this._limitVelocityGradients && this._limitVelocityGradients.length > 0) {
- GradientHelper.GetCurrentGradient(ratio, this._limitVelocityGradients, (currentGradient, nextGradient, scale) => {
- if (currentGradient !== particle._currentLimitVelocityGradient) {
- particle._currentLimitVelocity1 = particle._currentLimitVelocity2;
- particle._currentLimitVelocity2 = nextGradient.getFactor();
- particle._currentLimitVelocityGradient = currentGradient;
- }
- const limitVelocity = Lerp(particle._currentLimitVelocity1, particle._currentLimitVelocity2, scale);
- const currentVelocity = particle.direction.length();
- if (currentVelocity > limitVelocity) {
- particle.direction.scaleInPlace(this.limitVelocityDamping);
- }
- });
- }
- /// Drag
- if (this._dragGradients && this._dragGradients.length > 0) {
- GradientHelper.GetCurrentGradient(ratio, this._dragGradients, (currentGradient, nextGradient, scale) => {
- if (currentGradient !== particle._currentDragGradient) {
- particle._currentDrag1 = particle._currentDrag2;
- particle._currentDrag2 = nextGradient.getFactor();
- particle._currentDragGradient = currentGradient;
- }
- const drag = Lerp(particle._currentDrag1, particle._currentDrag2, scale);
- this._scaledDirection.scaleInPlace(1.0 - drag);
- });
- }
- if (this.isLocal && particle._localPosition) {
- particle._localPosition.addInPlace(this._scaledDirection);
- Vector3.TransformCoordinatesToRef(particle._localPosition, this._emitterWorldMatrix, particle.position);
- }
- else {
- particle.position.addInPlace(this._scaledDirection);
- }
- // Noise
- if (noiseTextureData && noiseTextureSize && particle._randomNoiseCoordinates1) {
- const fetchedColorR = this._fetchR(particle._randomNoiseCoordinates1.x, particle._randomNoiseCoordinates1.y, noiseTextureSize.width, noiseTextureSize.height, noiseTextureData);
- const fetchedColorG = this._fetchR(particle._randomNoiseCoordinates1.z, particle._randomNoiseCoordinates2.x, noiseTextureSize.width, noiseTextureSize.height, noiseTextureData);
- const fetchedColorB = this._fetchR(particle._randomNoiseCoordinates2.y, particle._randomNoiseCoordinates2.z, noiseTextureSize.width, noiseTextureSize.height, noiseTextureData);
- const force = TmpVectors.Vector3[0];
- const scaledForce = TmpVectors.Vector3[1];
- force.copyFromFloats((2 * fetchedColorR - 1) * this.noiseStrength.x, (2 * fetchedColorG - 1) * this.noiseStrength.y, (2 * fetchedColorB - 1) * this.noiseStrength.z);
- force.scaleToRef(scaledUpdateSpeed, scaledForce);
- particle.direction.addInPlace(scaledForce);
- }
- // Gravity
- this.gravity.scaleToRef(scaledUpdateSpeed, this._scaledGravity);
- particle.direction.addInPlace(this._scaledGravity);
- // Size
- if (this._sizeGradients && this._sizeGradients.length > 0) {
- GradientHelper.GetCurrentGradient(ratio, this._sizeGradients, (currentGradient, nextGradient, scale) => {
- if (currentGradient !== particle._currentSizeGradient) {
- particle._currentSize1 = particle._currentSize2;
- particle._currentSize2 = nextGradient.getFactor();
- particle._currentSizeGradient = currentGradient;
- }
- particle.size = Lerp(particle._currentSize1, particle._currentSize2, scale);
- });
- }
- // Remap data
- if (this._useRampGradients) {
- if (this._colorRemapGradients && this._colorRemapGradients.length > 0) {
- GradientHelper.GetCurrentGradient(ratio, this._colorRemapGradients, (currentGradient, nextGradient, scale) => {
- const min = Lerp(currentGradient.factor1, nextGradient.factor1, scale);
- const max = Lerp(currentGradient.factor2, nextGradient.factor2, scale);
- particle.remapData.x = min;
- particle.remapData.y = max - min;
- });
- }
- if (this._alphaRemapGradients && this._alphaRemapGradients.length > 0) {
- GradientHelper.GetCurrentGradient(ratio, this._alphaRemapGradients, (currentGradient, nextGradient, scale) => {
- const min = Lerp(currentGradient.factor1, nextGradient.factor1, scale);
- const max = Lerp(currentGradient.factor2, nextGradient.factor2, scale);
- particle.remapData.z = min;
- particle.remapData.w = max - min;
- });
- }
- }
- if (this._isAnimationSheetEnabled) {
- particle.updateCellIndex();
- }
- // Update the position of the attached sub-emitters to match their attached particle
- particle._inheritParticleInfoToSubEmitters();
- if (particle.age >= particle.lifeTime) {
- // Recycle by swapping with last particle
- this._emitFromParticle(particle);
- if (particle._attachedSubEmitters) {
- particle._attachedSubEmitters.forEach((subEmitter) => {
- subEmitter.particleSystem.disposeOnStop = true;
- subEmitter.particleSystem.stop();
- });
- particle._attachedSubEmitters = null;
- }
- this.recycleParticle(particle);
- if (sameParticleArray) {
- index--;
- }
- continue;
- }
- }
- };
- }
- serialize(serializeTexture) {
- throw new Error("Method not implemented.");
- }
- /**
- * 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
- */
- clone(name, newEmitter, cloneTexture = false) {
- throw new Error("Method not implemented.");
- }
- _addFactorGradient(factorGradients, gradient, factor, factor2) {
- const newGradient = new FactorGradient(gradient, factor, factor2);
- factorGradients.push(newGradient);
- factorGradients.sort((a, b) => {
- if (a.gradient < b.gradient) {
- return -1;
- }
- else if (a.gradient > b.gradient) {
- return 1;
- }
- return 0;
- });
- }
- _removeFactorGradient(factorGradients, gradient) {
- if (!factorGradients) {
- return;
- }
- let index = 0;
- for (const factorGradient of factorGradients) {
- if (factorGradient.gradient === gradient) {
- factorGradients.splice(index, 1);
- break;
- }
- index++;
- }
- }
- /**
- * Adds a new life time gradient
- * @param gradient defines the gradient to use (between 0 and 1)
- * @param factor defines the life time factor to affect to the specified gradient
- * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
- * @returns the current particle system
- */
- addLifeTimeGradient(gradient, factor, factor2) {
- if (!this._lifeTimeGradients) {
- this._lifeTimeGradients = [];
- }
- this._addFactorGradient(this._lifeTimeGradients, gradient, factor, factor2);
- return this;
- }
- /**
- * Remove a specific life time gradient
- * @param gradient defines the gradient to remove
- * @returns the current particle system
- */
- removeLifeTimeGradient(gradient) {
- this._removeFactorGradient(this._lifeTimeGradients, gradient);
- return this;
- }
- /**
- * Adds a new size gradient
- * @param gradient defines the gradient to use (between 0 and 1)
- * @param factor defines the size factor to affect to the specified gradient
- * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
- * @returns the current particle system
- */
- addSizeGradient(gradient, factor, factor2) {
- if (!this._sizeGradients) {
- this._sizeGradients = [];
- }
- this._addFactorGradient(this._sizeGradients, gradient, factor, factor2);
- return this;
- }
- /**
- * Remove a specific size gradient
- * @param gradient defines the gradient to remove
- * @returns the current particle system
- */
- removeSizeGradient(gradient) {
- this._removeFactorGradient(this._sizeGradients, gradient);
- return this;
- }
- /**
- * Adds a new color remap gradient
- * @param gradient defines the gradient to use (between 0 and 1)
- * @param min defines the color remap minimal range
- * @param max defines the color remap maximal range
- * @returns the current particle system
- */
- addColorRemapGradient(gradient, min, max) {
- if (!this._colorRemapGradients) {
- this._colorRemapGradients = [];
- }
- this._addFactorGradient(this._colorRemapGradients, gradient, min, max);
- return this;
- }
- /**
- * Remove a specific color remap gradient
- * @param gradient defines the gradient to remove
- * @returns the current particle system
- */
- removeColorRemapGradient(gradient) {
- this._removeFactorGradient(this._colorRemapGradients, gradient);
- return this;
- }
- /**
- * Adds a new alpha remap gradient
- * @param gradient defines the gradient to use (between 0 and 1)
- * @param min defines the alpha remap minimal range
- * @param max defines the alpha remap maximal range
- * @returns the current particle system
- */
- addAlphaRemapGradient(gradient, min, max) {
- if (!this._alphaRemapGradients) {
- this._alphaRemapGradients = [];
- }
- this._addFactorGradient(this._alphaRemapGradients, gradient, min, max);
- return this;
- }
- /**
- * Remove a specific alpha remap gradient
- * @param gradient defines the gradient to remove
- * @returns the current particle system
- */
- removeAlphaRemapGradient(gradient) {
- this._removeFactorGradient(this._alphaRemapGradients, gradient);
- return this;
- }
- /**
- * Adds a new angular speed gradient
- * @param gradient defines the gradient to use (between 0 and 1)
- * @param factor defines the angular speed to affect to the specified gradient
- * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
- * @returns the current particle system
- */
- addAngularSpeedGradient(gradient, factor, factor2) {
- if (!this._angularSpeedGradients) {
- this._angularSpeedGradients = [];
- }
- this._addFactorGradient(this._angularSpeedGradients, gradient, factor, factor2);
- return this;
- }
- /**
- * Remove a specific angular speed gradient
- * @param gradient defines the gradient to remove
- * @returns the current particle system
- */
- removeAngularSpeedGradient(gradient) {
- this._removeFactorGradient(this._angularSpeedGradients, gradient);
- return this;
- }
- /**
- * Adds a new velocity gradient
- * @param gradient defines the gradient to use (between 0 and 1)
- * @param factor defines the velocity to affect to the specified gradient
- * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
- * @returns the current particle system
- */
- addVelocityGradient(gradient, factor, factor2) {
- if (!this._velocityGradients) {
- this._velocityGradients = [];
- }
- this._addFactorGradient(this._velocityGradients, gradient, factor, factor2);
- return this;
- }
- /**
- * Remove a specific velocity gradient
- * @param gradient defines the gradient to remove
- * @returns the current particle system
- */
- removeVelocityGradient(gradient) {
- this._removeFactorGradient(this._velocityGradients, gradient);
- return this;
- }
- /**
- * Adds a new limit velocity gradient
- * @param gradient defines the gradient to use (between 0 and 1)
- * @param factor defines the limit velocity value to affect to the specified gradient
- * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
- * @returns the current particle system
- */
- addLimitVelocityGradient(gradient, factor, factor2) {
- if (!this._limitVelocityGradients) {
- this._limitVelocityGradients = [];
- }
- this._addFactorGradient(this._limitVelocityGradients, gradient, factor, factor2);
- return this;
- }
- /**
- * Remove a specific limit velocity gradient
- * @param gradient defines the gradient to remove
- * @returns the current particle system
- */
- removeLimitVelocityGradient(gradient) {
- this._removeFactorGradient(this._limitVelocityGradients, gradient);
- return this;
- }
- /**
- * Adds a new drag gradient
- * @param gradient defines the gradient to use (between 0 and 1)
- * @param factor defines the drag value to affect to the specified gradient
- * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
- * @returns the current particle system
- */
- addDragGradient(gradient, factor, factor2) {
- if (!this._dragGradients) {
- this._dragGradients = [];
- }
- this._addFactorGradient(this._dragGradients, gradient, factor, factor2);
- return this;
- }
- /**
- * Remove a specific drag gradient
- * @param gradient defines the gradient to remove
- * @returns the current particle system
- */
- removeDragGradient(gradient) {
- this._removeFactorGradient(this._dragGradients, gradient);
- return this;
- }
- /**
- * Adds a new emit rate gradient (please note that this will only work if you set the targetStopDuration property)
- * @param gradient defines the gradient to use (between 0 and 1)
- * @param factor defines the emit rate value to affect to the specified gradient
- * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
- * @returns the current particle system
- */
- addEmitRateGradient(gradient, factor, factor2) {
- if (!this._emitRateGradients) {
- this._emitRateGradients = [];
- }
- this._addFactorGradient(this._emitRateGradients, gradient, factor, factor2);
- return this;
- }
- /**
- * Remove a specific emit rate gradient
- * @param gradient defines the gradient to remove
- * @returns the current particle system
- */
- removeEmitRateGradient(gradient) {
- this._removeFactorGradient(this._emitRateGradients, gradient);
- return this;
- }
- /**
- * Adds a new start size gradient (please note that this will only work if you set the targetStopDuration property)
- * @param gradient defines the gradient to use (between 0 and 1)
- * @param factor defines the start size value to affect to the specified gradient
- * @param factor2 defines an additional factor used to define a range ([factor, factor2]) with main value to pick the final value from
- * @returns the current particle system
- */
- addStartSizeGradient(gradient, factor, factor2) {
- if (!this._startSizeGradients) {
- this._startSizeGradients = [];
- }
- this._addFactorGradient(this._startSizeGradients, gradient, factor, factor2);
- return this;
- }
- /**
- * Remove a specific start size gradient
- * @param gradient defines the gradient to remove
- * @returns the current particle system
- */
- removeStartSizeGradient(gradient) {
- this._removeFactorGradient(this._startSizeGradients, gradient);
- return this;
- }
- _createRampGradientTexture() {
- if (!this._rampGradients || !this._rampGradients.length || this._rampGradientsTexture || !this._scene) {
- return;
- }
- const data = new Uint8Array(this._rawTextureWidth * 4);
- const tmpColor = TmpColors.Color3[0];
- for (let x = 0; x < this._rawTextureWidth; x++) {
- const ratio = x / this._rawTextureWidth;
- GradientHelper.GetCurrentGradient(ratio, this._rampGradients, (currentGradient, nextGradient, scale) => {
- Color3.LerpToRef(currentGradient.color, nextGradient.color, scale, tmpColor);
- data[x * 4] = tmpColor.r * 255;
- data[x * 4 + 1] = tmpColor.g * 255;
- data[x * 4 + 2] = tmpColor.b * 255;
- data[x * 4 + 3] = 255;
- });
- }
- this._rampGradientsTexture = RawTexture.CreateRGBATexture(data, this._rawTextureWidth, 1, this._scene, false, false, 1);
- }
- /**
- * Gets the current list of ramp gradients.
- * You must use addRampGradient and removeRampGradient to update this list
- * @returns the list of ramp gradients
- */
- getRampGradients() {
- return this._rampGradients;
- }
- /** Force the system to rebuild all gradients that need to be resync */
- forceRefreshGradients() {
- this._syncRampGradientTexture();
- }
- _syncRampGradientTexture() {
- if (!this._rampGradients) {
- return;
- }
- this._rampGradients.sort((a, b) => {
- if (a.gradient < b.gradient) {
- return -1;
- }
- else if (a.gradient > b.gradient) {
- return 1;
- }
- return 0;
- });
- if (this._rampGradientsTexture) {
- this._rampGradientsTexture.dispose();
- this._rampGradientsTexture = null;
- }
- this._createRampGradientTexture();
- }
- /**
- * Adds a new ramp gradient used to remap particle colors
- * @param gradient defines the gradient to use (between 0 and 1)
- * @param color defines the color to affect to the specified gradient
- * @returns the current particle system
- */
- addRampGradient(gradient, color) {
- if (!this._rampGradients) {
- this._rampGradients = [];
- }
- const rampGradient = new Color3Gradient(gradient, color);
- this._rampGradients.push(rampGradient);
- this._syncRampGradientTexture();
- return this;
- }
- /**
- * Remove a specific ramp gradient
- * @param gradient defines the gradient to remove
- * @returns the current particle system
- */
- removeRampGradient(gradient) {
- this._removeGradientAndTexture(gradient, this._rampGradients, this._rampGradientsTexture);
- this._rampGradientsTexture = null;
- if (this._rampGradients && this._rampGradients.length > 0) {
- this._createRampGradientTexture();
- }
- return this;
- }
- /**
- * Adds a new color gradient
- * @param gradient defines the gradient to use (between 0 and 1)
- * @param color1 defines the color to affect to the specified gradient
- * @param color2 defines an additional color used to define a range ([color, color2]) with main color to pick the final color from
- * @returns this particle system
- */
- addColorGradient(gradient, color1, color2) {
- if (!this._colorGradients) {
- this._colorGradients = [];
- }
- const colorGradient = new ColorGradient(gradient, color1, color2);
- this._colorGradients.push(colorGradient);
- this._colorGradients.sort((a, b) => {
- if (a.gradient < b.gradient) {
- return -1;
- }
- else if (a.gradient > b.gradient) {
- return 1;
- }
- return 0;
- });
- return this;
- }
- /**
- * Remove a specific color gradient
- * @param gradient defines the gradient to remove
- * @returns this particle system
- */
- removeColorGradient(gradient) {
- if (!this._colorGradients) {
- return this;
- }
- let index = 0;
- for (const colorGradient of this._colorGradients) {
- if (colorGradient.gradient === gradient) {
- this._colorGradients.splice(index, 1);
- break;
- }
- index++;
- }
- return this;
- }
- /**
- * Resets the draw wrappers cache
- */
- resetDrawCache() {
- for (const drawWrappers of this._drawWrappers) {
- if (drawWrappers) {
- for (const drawWrapper of drawWrappers) {
- drawWrapper?.dispose();
- }
- }
- }
- this._drawWrappers = [];
- }
- _fetchR(u, v, width, height, pixels) {
- u = Math.abs(u) * 0.5 + 0.5;
- v = Math.abs(v) * 0.5 + 0.5;
- const wrappedU = (u * width) % width | 0;
- const wrappedV = (v * height) % height | 0;
- const position = (wrappedU + wrappedV * width) * 4;
- return pixels[position] / 255;
- }
- _reset() {
- this._resetEffect();
- }
- _resetEffect() {
- if (this._vertexBuffer) {
- this._vertexBuffer.dispose();
- this._vertexBuffer = null;
- }
- if (this._spriteBuffer) {
- this._spriteBuffer.dispose();
- this._spriteBuffer = null;
- }
- if (this._vertexArrayObject) {
- this._engine.releaseVertexArrayObject(this._vertexArrayObject);
- this._vertexArrayObject = null;
- }
- this._createVertexBuffers();
- }
- _createVertexBuffers() {
- this._vertexBufferSize = this._useInstancing ? 10 : 12;
- if (this._isAnimationSheetEnabled) {
- this._vertexBufferSize += 1;
- }
- if (!this._isBillboardBased ||
- this.billboardMode === 8 ||
- this.billboardMode === 9) {
- this._vertexBufferSize += 3;
- }
- if (this._useRampGradients) {
- this._vertexBufferSize += 4;
- }
- const engine = this._engine;
- const vertexSize = this._vertexBufferSize * (this._useInstancing ? 1 : 4);
- this._vertexData = new Float32Array(this._capacity * vertexSize);
- this._vertexBuffer = new Buffer(engine, this._vertexData, true, vertexSize);
- let dataOffset = 0;
- const positions = this._vertexBuffer.createVertexBuffer(VertexBuffer.PositionKind, dataOffset, 3, this._vertexBufferSize, this._useInstancing);
- this._vertexBuffers[VertexBuffer.PositionKind] = positions;
- dataOffset += 3;
- const colors = this._vertexBuffer.createVertexBuffer(VertexBuffer.ColorKind, dataOffset, 4, this._vertexBufferSize, this._useInstancing);
- this._vertexBuffers[VertexBuffer.ColorKind] = colors;
- dataOffset += 4;
- const options = this._vertexBuffer.createVertexBuffer("angle", dataOffset, 1, this._vertexBufferSize, this._useInstancing);
- this._vertexBuffers["angle"] = options;
- dataOffset += 1;
- const size = this._vertexBuffer.createVertexBuffer("size", dataOffset, 2, this._vertexBufferSize, this._useInstancing);
- this._vertexBuffers["size"] = size;
- dataOffset += 2;
- if (this._isAnimationSheetEnabled) {
- const cellIndexBuffer = this._vertexBuffer.createVertexBuffer("cellIndex", dataOffset, 1, this._vertexBufferSize, this._useInstancing);
- this._vertexBuffers["cellIndex"] = cellIndexBuffer;
- dataOffset += 1;
- }
- if (!this._isBillboardBased ||
- this.billboardMode === 8 ||
- this.billboardMode === 9) {
- const directionBuffer = this._vertexBuffer.createVertexBuffer("direction", dataOffset, 3, this._vertexBufferSize, this._useInstancing);
- this._vertexBuffers["direction"] = directionBuffer;
- dataOffset += 3;
- }
- if (this._useRampGradients) {
- const rampDataBuffer = this._vertexBuffer.createVertexBuffer("remapData", dataOffset, 4, this._vertexBufferSize, this._useInstancing);
- this._vertexBuffers["remapData"] = rampDataBuffer;
- dataOffset += 4;
- }
- let offsets;
- if (this._useInstancing) {
- const spriteData = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]);
- this._spriteBuffer = new Buffer(engine, spriteData, false, 2);
- offsets = this._spriteBuffer.createVertexBuffer("offset", 0, 2);
- }
- else {
- offsets = this._vertexBuffer.createVertexBuffer("offset", dataOffset, 2, this._vertexBufferSize, this._useInstancing);
- dataOffset += 2;
- }
- this._vertexBuffers["offset"] = offsets;
- this.resetDrawCache();
- }
- _createIndexBuffer() {
- if (this._useInstancing) {
- this._linesIndexBufferUseInstancing = this._engine.createIndexBuffer(new Uint32Array([0, 1, 1, 3, 3, 2, 2, 0, 0, 3]));
- return;
- }
- const indices = [];
- const indicesWireframe = [];
- let index = 0;
- for (let count = 0; count < this._capacity; count++) {
- indices.push(index);
- indices.push(index + 1);
- indices.push(index + 2);
- indices.push(index);
- indices.push(index + 2);
- indices.push(index + 3);
- indicesWireframe.push(index, index + 1, index + 1, index + 2, index + 2, index + 3, index + 3, index, index, index + 3);
- index += 4;
- }
- this._indexBuffer = this._engine.createIndexBuffer(indices);
- this._linesIndexBuffer = this._engine.createIndexBuffer(indicesWireframe);
- }
- /**
- * Gets the maximum number of particles active at the same time.
- * @returns The max number of active particles.
- */
- getCapacity() {
- return this._capacity;
- }
- /**
- * Gets whether there are still active particles in the system.
- * @returns True if it is alive, otherwise false.
- */
- isAlive() {
- return this._alive;
- }
- /**
- * Gets if the system has been started. (Note: this will still be true after stop is called)
- * @returns True if it has been started, otherwise false.
- */
- isStarted() {
- return this._started;
- }
- /** @internal */
- _preStart() {
- // Do nothing
- }
- /**
- * 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.targetStopDuration && this._hasTargetStopDurationDependantGradient()) {
- // eslint-disable-next-line no-throw-literal
- throw "Particle system started with a targetStopDuration dependant gradient (eg. startSizeGradients) but no targetStopDuration set";
- }
- if (delay) {
- setTimeout(() => {
- this.start(0);
- }, delay);
- return;
- }
- this._started = true;
- this._stopped = false;
- this._actualFrame = 0;
- this._preStart();
- // Reset emit gradient so it acts the same on every start
- if (this._emitRateGradients) {
- if (this._emitRateGradients.length > 0) {
- this._currentEmitRateGradient = this._emitRateGradients[0];
- this._currentEmitRate1 = this._currentEmitRateGradient.getFactor();
- this._currentEmitRate2 = this._currentEmitRate1;
- }
- if (this._emitRateGradients.length > 1) {
- this._currentEmitRate2 = this._emitRateGradients[1].getFactor();
- }
- }
- // Reset start size gradient so it acts the same on every start
- if (this._startSizeGradients) {
- if (this._startSizeGradients.length > 0) {
- this._currentStartSizeGradient = this._startSizeGradients[0];
- this._currentStartSize1 = this._currentStartSizeGradient.getFactor();
- this._currentStartSize2 = this._currentStartSize1;
- }
- if (this._startSizeGradients.length > 1) {
- this._currentStartSize2 = this._startSizeGradients[1].getFactor();
- }
- }
- if (this.preWarmCycles) {
- if (this.emitter?.getClassName().indexOf("Mesh") !== -1) {
- this.emitter.computeWorldMatrix(true);
- }
- const noiseTextureAsProcedural = this.noiseTexture;
- if (noiseTextureAsProcedural && noiseTextureAsProcedural.onGeneratedObservable) {
- noiseTextureAsProcedural.onGeneratedObservable.addOnce(() => {
- setTimeout(() => {
- for (let index = 0; index < this.preWarmCycles; index++) {
- this.animate(true);
- noiseTextureAsProcedural.render();
- }
- });
- });
- }
- else {
- for (let index = 0; index < this.preWarmCycles; index++) {
- this.animate(true);
- }
- }
- }
- // Animations
- if (this.beginAnimationOnStart && this.animations && this.animations.length > 0 && this._scene) {
- this._scene.beginAnimation(this, this.beginAnimationFrom, this.beginAnimationTo, this.beginAnimationLoop);
- }
- }
- /**
- * Stops the particle system.
- * @param stopSubEmitters if true it will stop the current system and all created sub-Systems if false it will stop the current root system only, this param is used by the root particle system only. The default value is true.
- */
- stop(stopSubEmitters = true) {
- if (this._stopped) {
- return;
- }
- this.onStoppedObservable.notifyObservers(this);
- this._stopped = true;
- this._postStop(stopSubEmitters);
- }
- /** @internal */
- _postStop(stopSubEmitters) {
- // Do nothing
- }
- // Animation sheet
- /**
- * Remove all active particles
- */
- reset() {
- this._stockParticles.length = 0;
- this._particles.length = 0;
- }
- /**
- * @internal (for internal use only)
- */
- _appendParticleVertex(index, particle, offsetX, offsetY) {
- let offset = index * this._vertexBufferSize;
- this._vertexData[offset++] = particle.position.x + this.worldOffset.x;
- this._vertexData[offset++] = particle.position.y + this.worldOffset.y;
- this._vertexData[offset++] = particle.position.z + this.worldOffset.z;
- this._vertexData[offset++] = particle.color.r;
- this._vertexData[offset++] = particle.color.g;
- this._vertexData[offset++] = particle.color.b;
- this._vertexData[offset++] = particle.color.a;
- this._vertexData[offset++] = particle.angle;
- this._vertexData[offset++] = particle.scale.x * particle.size;
- this._vertexData[offset++] = particle.scale.y * particle.size;
- if (this._isAnimationSheetEnabled) {
- this._vertexData[offset++] = particle.cellIndex;
- }
- if (!this._isBillboardBased) {
- if (particle._initialDirection) {
- let initialDirection = particle._initialDirection;
- if (this.isLocal) {
- Vector3.TransformNormalToRef(initialDirection, this._emitterWorldMatrix, TmpVectors.Vector3[0]);
- initialDirection = TmpVectors.Vector3[0];
- }
- if (initialDirection.x === 0 && initialDirection.z === 0) {
- initialDirection.x = 0.001;
- }
- this._vertexData[offset++] = initialDirection.x;
- this._vertexData[offset++] = initialDirection.y;
- this._vertexData[offset++] = initialDirection.z;
- }
- else {
- let direction = particle.direction;
- if (this.isLocal) {
- Vector3.TransformNormalToRef(direction, this._emitterWorldMatrix, TmpVectors.Vector3[0]);
- direction = TmpVectors.Vector3[0];
- }
- if (direction.x === 0 && direction.z === 0) {
- direction.x = 0.001;
- }
- this._vertexData[offset++] = direction.x;
- this._vertexData[offset++] = direction.y;
- this._vertexData[offset++] = direction.z;
- }
- }
- else if (this.billboardMode === 8 || this.billboardMode === 9) {
- this._vertexData[offset++] = particle.direction.x;
- this._vertexData[offset++] = particle.direction.y;
- this._vertexData[offset++] = particle.direction.z;
- }
- if (this._useRampGradients && particle.remapData) {
- this._vertexData[offset++] = particle.remapData.x;
- this._vertexData[offset++] = particle.remapData.y;
- this._vertexData[offset++] = particle.remapData.z;
- this._vertexData[offset++] = particle.remapData.w;
- }
- if (!this._useInstancing) {
- if (this._isAnimationSheetEnabled) {
- if (offsetX === 0) {
- offsetX = this._epsilon;
- }
- else if (offsetX === 1) {
- offsetX = 1 - this._epsilon;
- }
- if (offsetY === 0) {
- offsetY = this._epsilon;
- }
- else if (offsetY === 1) {
- offsetY = 1 - this._epsilon;
- }
- }
- this._vertexData[offset++] = offsetX;
- this._vertexData[offset++] = offsetY;
- }
- }
- /** @internal */
- _prepareParticle(particle) {
- //Do nothing
- }
- _update(newParticles) {
- // Update current
- this._alive = this._particles.length > 0;
- if (this.emitter.position) {
- const emitterMesh = this.emitter;
- this._emitterWorldMatrix = emitterMesh.getWorldMatrix();
- }
- else {
- const emitterPosition = this.emitter;
- this._emitterWorldMatrix = Matrix.Translation(emitterPosition.x, emitterPosition.y, emitterPosition.z);
- }
- this._emitterWorldMatrix.invertToRef(this._emitterInverseWorldMatrix);
- this.updateFunction(this._particles);
- // Add new ones
- let particle;
- for (let index = 0; index < newParticles; index++) {
- if (this._particles.length === this._capacity) {
- break;
- }
- particle = this._createParticle();
- this._particles.push(particle);
- // Life time
- if (this.targetStopDuration && this._lifeTimeGradients && this._lifeTimeGradients.length > 0) {
- const ratio = Clamp(this._actualFrame / this.targetStopDuration);
- GradientHelper.GetCurrentGradient(ratio, this._lifeTimeGradients, (currentGradient, nextGradient) => {
- const factorGradient1 = currentGradient;
- const factorGradient2 = nextGradient;
- const lifeTime1 = factorGradient1.getFactor();
- const lifeTime2 = factorGradient2.getFactor();
- const gradient = (ratio - factorGradient1.gradient) / (factorGradient2.gradient - factorGradient1.gradient);
- particle.lifeTime = Lerp(lifeTime1, lifeTime2, gradient);
- });
- }
- else {
- particle.lifeTime = RandomRange(this.minLifeTime, this.maxLifeTime);
- }
- // Emitter
- const emitPower = RandomRange(this.minEmitPower, this.maxEmitPower);
- if (this.startPositionFunction) {
- this.startPositionFunction(this._emitterWorldMatrix, particle.position, particle, this.isLocal);
- }
- else {
- this.particleEmitterType.startPositionFunction(this._emitterWorldMatrix, particle.position, particle, this.isLocal);
- }
- if (this.isLocal) {
- if (!particle._localPosition) {
- particle._localPosition = particle.position.clone();
- }
- else {
- particle._localPosition.copyFrom(particle.position);
- }
- Vector3.TransformCoordinatesToRef(particle._localPosition, this._emitterWorldMatrix, particle.position);
- }
- if (this.startDirectionFunction) {
- this.startDirectionFunction(this._emitterWorldMatrix, particle.direction, particle, this.isLocal);
- }
- else {
- this.particleEmitterType.startDirectionFunction(this._emitterWorldMatrix, particle.direction, particle, this.isLocal, this._emitterInverseWorldMatrix);
- }
- if (emitPower === 0) {
- if (!particle._initialDirection) {
- particle._initialDirection = particle.direction.clone();
- }
- else {
- particle._initialDirection.copyFrom(particle.direction);
- }
- }
- else {
- particle._initialDirection = null;
- }
- particle.direction.scaleInPlace(emitPower);
- // Size
- if (!this._sizeGradients || this._sizeGradients.length === 0) {
- particle.size = RandomRange(this.minSize, this.maxSize);
- }
- else {
- particle._currentSizeGradient = this._sizeGradients[0];
- particle._currentSize1 = particle._currentSizeGradient.getFactor();
- particle.size = particle._currentSize1;
- if (this._sizeGradients.length > 1) {
- particle._currentSize2 = this._sizeGradients[1].getFactor();
- }
- else {
- particle._currentSize2 = particle._currentSize1;
- }
- }
- // Size and scale
- particle.scale.copyFromFloats(RandomRange(this.minScaleX, this.maxScaleX), RandomRange(this.minScaleY, this.maxScaleY));
- // Adjust scale by start size
- if (this._startSizeGradients && this._startSizeGradients[0] && this.targetStopDuration) {
- const ratio = this._actualFrame / this.targetStopDuration;
- GradientHelper.GetCurrentGradient(ratio, this._startSizeGradients, (currentGradient, nextGradient, scale) => {
- if (currentGradient !== this._currentStartSizeGradient) {
- this._currentStartSize1 = this._currentStartSize2;
- this._currentStartSize2 = nextGradient.getFactor();
- this._currentStartSizeGradient = currentGradient;
- }
- const value = Lerp(this._currentStartSize1, this._currentStartSize2, scale);
- particle.scale.scaleInPlace(value);
- });
- }
- // Angle
- if (!this._angularSpeedGradients || this._angularSpeedGradients.length === 0) {
- particle.angularSpeed = RandomRange(this.minAngularSpeed, this.maxAngularSpeed);
- }
- else {
- particle._currentAngularSpeedGradient = this._angularSpeedGradients[0];
- particle.angularSpeed = particle._currentAngularSpeedGradient.getFactor();
- particle._currentAngularSpeed1 = particle.angularSpeed;
- if (this._angularSpeedGradients.length > 1) {
- particle._currentAngularSpeed2 = this._angularSpeedGradients[1].getFactor();
- }
- else {
- particle._currentAngularSpeed2 = particle._currentAngularSpeed1;
- }
- }
- particle.angle = RandomRange(this.minInitialRotation, this.maxInitialRotation);
- // Velocity
- if (this._velocityGradients && this._velocityGradients.length > 0) {
- particle._currentVelocityGradient = this._velocityGradients[0];
- particle._currentVelocity1 = particle._currentVelocityGradient.getFactor();
- if (this._velocityGradients.length > 1) {
- particle._currentVelocity2 = this._velocityGradients[1].getFactor();
- }
- else {
- particle._currentVelocity2 = particle._currentVelocity1;
- }
- }
- // Limit velocity
- if (this._limitVelocityGradients && this._limitVelocityGradients.length > 0) {
- particle._currentLimitVelocityGradient = this._limitVelocityGradients[0];
- particle._currentLimitVelocity1 = particle._currentLimitVelocityGradient.getFactor();
- if (this._limitVelocityGradients.length > 1) {
- particle._currentLimitVelocity2 = this._limitVelocityGradients[1].getFactor();
- }
- else {
- particle._currentLimitVelocity2 = particle._currentLimitVelocity1;
- }
- }
- // Drag
- if (this._dragGradients && this._dragGradients.length > 0) {
- particle._currentDragGradient = this._dragGradients[0];
- particle._currentDrag1 = particle._currentDragGradient.getFactor();
- if (this._dragGradients.length > 1) {
- particle._currentDrag2 = this._dragGradients[1].getFactor();
- }
- else {
- particle._currentDrag2 = particle._currentDrag1;
- }
- }
- // Color
- if (!this._colorGradients || this._colorGradients.length === 0) {
- const step = RandomRange(0, 1.0);
- Color4.LerpToRef(this.color1, this.color2, step, particle.color);
- this.colorDead.subtractToRef(particle.color, this._colorDiff);
- this._colorDiff.scaleToRef(1.0 / particle.lifeTime, particle.colorStep);
- }
- else {
- particle._currentColorGradient = this._colorGradients[0];
- particle._currentColorGradient.getColorToRef(particle.color);
- particle._currentColor1.copyFrom(particle.color);
- if (this._colorGradients.length > 1) {
- this._colorGradients[1].getColorToRef(particle._currentColor2);
- }
- else {
- particle._currentColor2.copyFrom(particle.color);
- }
- }
- // Sheet
- if (this._isAnimationSheetEnabled) {
- particle._initialStartSpriteCellID = this.startSpriteCellID;
- particle._initialEndSpriteCellID = this.endSpriteCellID;
- particle._initialSpriteCellLoop = this.spriteCellLoop;
- }
- // Inherited Velocity
- particle.direction.addInPlace(this._inheritedVelocityOffset);
- // Ramp
- if (this._useRampGradients) {
- particle.remapData = new Vector4(0, 1, 0, 1);
- }
- // Noise texture coordinates
- if (this.noiseTexture) {
- if (particle._randomNoiseCoordinates1) {
- particle._randomNoiseCoordinates1.copyFromFloats(Math.random(), Math.random(), Math.random());
- particle._randomNoiseCoordinates2.copyFromFloats(Math.random(), Math.random(), Math.random());
- }
- else {
- particle._randomNoiseCoordinates1 = new Vector3(Math.random(), Math.random(), Math.random());
- particle._randomNoiseCoordinates2 = new Vector3(Math.random(), Math.random(), Math.random());
- }
- }
- // Update the position of the attached sub-emitters to match their attached particle
- particle._inheritParticleInfoToSubEmitters();
- }
- }
- /**
- * @internal
- */
- static _GetAttributeNamesOrOptions(isAnimationSheetEnabled = false, isBillboardBased = false, useRampGradients = false) {
- const attributeNamesOrOptions = [VertexBuffer.PositionKind, VertexBuffer.ColorKind, "angle", "offset", "size"];
- if (isAnimationSheetEnabled) {
- attributeNamesOrOptions.push("cellIndex");
- }
- if (!isBillboardBased) {
- attributeNamesOrOptions.push("direction");
- }
- if (useRampGradients) {
- attributeNamesOrOptions.push("remapData");
- }
- return attributeNamesOrOptions;
- }
- /**
- * @internal
- */
- static _GetEffectCreationOptions(isAnimationSheetEnabled = false, useLogarithmicDepth = false, applyFog = false) {
- const effectCreationOption = ["invView", "view", "projection", "textureMask", "translationPivot", "eyePosition"];
- addClipPlaneUniforms(effectCreationOption);
- if (isAnimationSheetEnabled) {
- effectCreationOption.push("particlesInfos");
- }
- if (useLogarithmicDepth) {
- effectCreationOption.push("logarithmicDepthConstant");
- }
- if (applyFog) {
- effectCreationOption.push("vFogInfos");
- effectCreationOption.push("vFogColor");
- }
- return effectCreationOption;
- }
- /**
- * Fill the defines array according to the current settings of the particle system
- * @param defines Array to be updated
- * @param blendMode blend mode to take into account when updating the array
- */
- fillDefines(defines, blendMode) {
- if (this._scene) {
- prepareStringDefinesForClipPlanes(this, this._scene, defines);
- if (this.applyFog && this._scene.fogEnabled && this._scene.fogMode !== 0) {
- defines.push("#define FOG");
- }
- }
- if (this._isAnimationSheetEnabled) {
- defines.push("#define ANIMATESHEET");
- }
- if (this.useLogarithmicDepth) {
- defines.push("#define LOGARITHMICDEPTH");
- }
- if (blendMode === BaseParticleSystem.BLENDMODE_MULTIPLY) {
- defines.push("#define BLENDMULTIPLYMODE");
- }
- if (this._useRampGradients) {
- defines.push("#define RAMPGRADIENT");
- }
- if (this._isBillboardBased) {
- defines.push("#define BILLBOARD");
- switch (this.billboardMode) {
- case 2:
- defines.push("#define BILLBOARDY");
- break;
- case 8:
- case 9:
- defines.push("#define BILLBOARDSTRETCHED");
- if (this.billboardMode === 9) {
- defines.push("#define BILLBOARDSTRETCHED_LOCAL");
- }
- break;
- case 7:
- defines.push("#define BILLBOARDMODE_ALL");
- break;
- default:
- break;
- }
- }
- if (this._imageProcessingConfiguration) {
- this._imageProcessingConfiguration.prepareDefines(this._imageProcessingConfigurationDefines);
- defines.push(this._imageProcessingConfigurationDefines.toString());
- }
- }
- /**
- * Fill the uniforms, attributes and samplers arrays according to the current settings of the particle system
- * @param uniforms Uniforms array to fill
- * @param attributes Attributes array to fill
- * @param samplers Samplers array to fill
- */
- fillUniformsAttributesAndSamplerNames(uniforms, attributes, samplers) {
- attributes.push(...ThinParticleSystem._GetAttributeNamesOrOptions(this._isAnimationSheetEnabled, this._isBillboardBased &&
- this.billboardMode !== 8 &&
- this.billboardMode !== 9, this._useRampGradients));
- uniforms.push(...ThinParticleSystem._GetEffectCreationOptions(this._isAnimationSheetEnabled, this.useLogarithmicDepth, this.applyFog));
- samplers.push("diffuseSampler", "rampSampler");
- if (this._imageProcessingConfiguration) {
- PrepareUniformsForImageProcessing(uniforms, this._imageProcessingConfigurationDefines);
- PrepareSamplersForImageProcessing(samplers, this._imageProcessingConfigurationDefines);
- }
- }
- /**
- * @internal
- */
- _getWrapper(blendMode) {
- const customWrapper = this._getCustomDrawWrapper(blendMode);
- if (customWrapper?.effect) {
- return customWrapper;
- }
- const defines = [];
- this.fillDefines(defines, blendMode);
- // Effect
- const currentRenderPassId = this._engine._features.supportRenderPasses ? this._engine.currentRenderPassId : 0;
- let drawWrappers = this._drawWrappers[currentRenderPassId];
- if (!drawWrappers) {
- drawWrappers = this._drawWrappers[currentRenderPassId] = [];
- }
- let drawWrapper = drawWrappers[blendMode];
- if (!drawWrapper) {
- drawWrapper = new DrawWrapper(this._engine);
- if (drawWrapper.drawContext) {
- drawWrapper.drawContext.useInstancing = this._useInstancing;
- }
- drawWrappers[blendMode] = drawWrapper;
- }
- const join = defines.join("\n");
- if (drawWrapper.defines !== join) {
- const attributesNamesOrOptions = [];
- const effectCreationOption = [];
- const samplers = [];
- this.fillUniformsAttributesAndSamplerNames(effectCreationOption, attributesNamesOrOptions, samplers);
- drawWrapper.setEffect(this._engine.createEffect("particles", attributesNamesOrOptions, effectCreationOption, samplers, join), join);
- }
- return drawWrapper;
- }
- /**
- * Animates the particle system for the current frame by emitting new particles and or animating the living ones.
- * @param preWarmOnly will prevent the system from updating the vertex buffer (default is false)
- */
- animate(preWarmOnly = false) {
- if (!this._started) {
- return;
- }
- if (!preWarmOnly && this._scene) {
- // Check
- if (!this.isReady()) {
- return;
- }
- if (this._currentRenderId === this._scene.getFrameId()) {
- return;
- }
- this._currentRenderId = this._scene.getFrameId();
- }
- this._scaledUpdateSpeed = this.updateSpeed * (preWarmOnly ? this.preWarmStepOffset : this._scene?.getAnimationRatio() || 1);
- // Determine the number of particles we need to create
- let newParticles;
- if (this.manualEmitCount > -1) {
- newParticles = this.manualEmitCount;
- this._newPartsExcess = 0;
- this.manualEmitCount = 0;
- }
- else {
- let rate = this.emitRate;
- if (this._emitRateGradients && this._emitRateGradients.length > 0 && this.targetStopDuration) {
- const ratio = this._actualFrame / this.targetStopDuration;
- GradientHelper.GetCurrentGradient(ratio, this._emitRateGradients, (currentGradient, nextGradient, scale) => {
- if (currentGradient !== this._currentEmitRateGradient) {
- this._currentEmitRate1 = this._currentEmitRate2;
- this._currentEmitRate2 = nextGradient.getFactor();
- this._currentEmitRateGradient = currentGradient;
- }
- rate = Lerp(this._currentEmitRate1, this._currentEmitRate2, scale);
- });
- }
- newParticles = (rate * this._scaledUpdateSpeed) >> 0;
- this._newPartsExcess += rate * this._scaledUpdateSpeed - newParticles;
- }
- if (this._newPartsExcess > 1.0) {
- newParticles += this._newPartsExcess >> 0;
- this._newPartsExcess -= this._newPartsExcess >> 0;
- }
- this._alive = false;
- if (!this._stopped) {
- this._actualFrame += this._scaledUpdateSpeed;
- if (this.targetStopDuration && this._actualFrame >= this.targetStopDuration) {
- this.stop();
- }
- }
- else {
- newParticles = 0;
- }
- this._update(newParticles);
- // Stopped?
- if (this._stopped) {
- if (!this._alive) {
- this._started = false;
- if (this.onAnimationEnd) {
- this.onAnimationEnd();
- }
- if (this.disposeOnStop && this._scene) {
- this._scene._toBeDisposed.push(this);
- }
- }
- }
- if (!preWarmOnly) {
- // Update VBO
- let offset = 0;
- for (let index = 0; index < this._particles.length; index++) {
- const particle = this._particles[index];
- this._appendParticleVertices(offset, particle);
- offset += this._useInstancing ? 1 : 4;
- }
- if (this._vertexBuffer) {
- this._vertexBuffer.updateDirectly(this._vertexData, 0, this._particles.length);
- }
- }
- if (this.manualEmitCount === 0 && this.disposeOnStop) {
- this.stop();
- }
- }
- _appendParticleVertices(offset, particle) {
- this._appendParticleVertex(offset++, particle, 0, 0);
- if (!this._useInstancing) {
- this._appendParticleVertex(offset++, particle, 1, 0);
- this._appendParticleVertex(offset++, particle, 1, 1);
- this._appendParticleVertex(offset++, particle, 0, 1);
- }
- }
- /**
- * Rebuilds the particle system.
- */
- rebuild() {
- if (this._engine.getCaps().vertexArrayObject) {
- this._vertexArrayObject = null;
- }
- this._createIndexBuffer();
- this._spriteBuffer?._rebuild();
- this._createVertexBuffers();
- this.resetDrawCache();
- }
- /**
- * Is this system ready to be used/rendered
- * @returns true if the system is ready
- */
- isReady() {
- if (!this.emitter || (this._imageProcessingConfiguration && !this._imageProcessingConfiguration.isReady()) || !this.particleTexture || !this.particleTexture.isReady()) {
- return false;
- }
- if (this.blendMode !== BaseParticleSystem.BLENDMODE_MULTIPLYADD) {
- if (!this._getWrapper(this.blendMode).effect.isReady()) {
- return false;
- }
- }
- else {
- if (!this._getWrapper(BaseParticleSystem.BLENDMODE_MULTIPLY).effect.isReady()) {
- return false;
- }
- if (!this._getWrapper(BaseParticleSystem.BLENDMODE_ADD).effect.isReady()) {
- return false;
- }
- }
- return true;
- }
- _render(blendMode) {
- const drawWrapper = this._getWrapper(blendMode);
- const effect = drawWrapper.effect;
- const engine = this._engine;
- // Render
- engine.enableEffect(drawWrapper);
- const viewMatrix = this.defaultViewMatrix ?? this._scene.getViewMatrix();
- effect.setTexture("diffuseSampler", this.particleTexture);
- effect.setMatrix("view", viewMatrix);
- effect.setMatrix("projection", this.defaultProjectionMatrix ?? this._scene.getProjectionMatrix());
- if (this._isAnimationSheetEnabled && this.particleTexture) {
- const baseSize = this.particleTexture.getBaseSize();
- effect.setFloat3("particlesInfos", this.spriteCellWidth / baseSize.width, this.spriteCellHeight / baseSize.height, this.spriteCellWidth / baseSize.width);
- }
- effect.setVector2("translationPivot", this.translationPivot);
- effect.setFloat4("textureMask", this.textureMask.r, this.textureMask.g, this.textureMask.b, this.textureMask.a);
- if (this._isBillboardBased && this._scene) {
- const camera = this._scene.activeCamera;
- effect.setVector3("eyePosition", camera.globalPosition);
- }
- if (this._rampGradientsTexture) {
- if (!this._rampGradients || !this._rampGradients.length) {
- this._rampGradientsTexture.dispose();
- this._rampGradientsTexture = null;
- }
- effect.setTexture("rampSampler", this._rampGradientsTexture);
- }
- const defines = effect.defines;
- if (this._scene) {
- bindClipPlane(effect, this, this._scene);
- if (this.applyFog) {
- BindFogParameters(this._scene, undefined, effect);
- }
- }
- if (defines.indexOf("#define BILLBOARDMODE_ALL") >= 0) {
- viewMatrix.invertToRef(TmpVectors.Matrix[0]);
- effect.setMatrix("invView", TmpVectors.Matrix[0]);
- }
- if (this._vertexArrayObject !== undefined) {
- if (this._scene?.forceWireframe) {
- engine.bindBuffers(this._vertexBuffers, this._linesIndexBufferUseInstancing, effect);
- }
- else {
- if (!this._vertexArrayObject) {
- this._vertexArrayObject = this._engine.recordVertexArrayObject(this._vertexBuffers, null, effect);
- }
- this._engine.bindVertexArrayObject(this._vertexArrayObject, this._scene?.forceWireframe ? this._linesIndexBufferUseInstancing : this._indexBuffer);
- }
- }
- else {
- if (!this._indexBuffer) {
- // Use instancing mode
- engine.bindBuffers(this._vertexBuffers, this._scene?.forceWireframe ? this._linesIndexBufferUseInstancing : null, effect);
- }
- else {
- engine.bindBuffers(this._vertexBuffers, this._scene?.forceWireframe ? this._linesIndexBuffer : this._indexBuffer, effect);
- }
- }
- // Log. depth
- if (this.useLogarithmicDepth && this._scene) {
- BindLogDepth(defines, effect, this._scene);
- }
- // image processing
- if (this._imageProcessingConfiguration && !this._imageProcessingConfiguration.applyByPostProcess) {
- this._imageProcessingConfiguration.bind(effect);
- }
- // Draw order
- switch (blendMode) {
- case BaseParticleSystem.BLENDMODE_ADD:
- engine.setAlphaMode(1);
- break;
- case BaseParticleSystem.BLENDMODE_ONEONE:
- engine.setAlphaMode(6);
- break;
- case BaseParticleSystem.BLENDMODE_STANDARD:
- engine.setAlphaMode(2);
- break;
- case BaseParticleSystem.BLENDMODE_MULTIPLY:
- engine.setAlphaMode(4);
- break;
- }
- if (this._onBeforeDrawParticlesObservable) {
- this._onBeforeDrawParticlesObservable.notifyObservers(effect);
- }
- if (this._useInstancing) {
- if (this._scene?.forceWireframe) {
- engine.drawElementsType(6, 0, 10, this._particles.length);
- }
- else {
- engine.drawArraysType(7, 0, 4, this._particles.length);
- }
- }
- else {
- if (this._scene?.forceWireframe) {
- engine.drawElementsType(1, 0, this._particles.length * 10);
- }
- else {
- engine.drawElementsType(0, 0, this._particles.length * 6);
- }
- }
- return this._particles.length;
- }
- /**
- * Renders the particle system in its current state.
- * @returns the current number of particles
- */
- render() {
- // Check
- if (!this.isReady() || !this._particles.length) {
- return 0;
- }
- const engine = this._engine;
- if (engine.setState) {
- engine.setState(false);
- if (this.forceDepthWrite) {
- engine.setDepthWrite(true);
- }
- }
- let outparticles = 0;
- if (this.blendMode === BaseParticleSystem.BLENDMODE_MULTIPLYADD) {
- outparticles = this._render(BaseParticleSystem.BLENDMODE_MULTIPLY) + this._render(BaseParticleSystem.BLENDMODE_ADD);
- }
- else {
- outparticles = this._render(this.blendMode);
- }
- this._engine.unbindInstanceAttributes();
- this._engine.setAlphaMode(0);
- return outparticles;
- }
- /** @internal */
- _onDispose(disposeAttachedSubEmitters = false, disposeEndSubEmitters = false) {
- // Do Nothing
- }
- /**
- * Disposes the particle system and free the associated resources
- * @param disposeTexture defines if the particle texture must be disposed as well (true by default)
- * @param disposeAttachedSubEmitters defines if the attached sub-emitters must be disposed as well (false by default)
- * @param disposeEndSubEmitters defines if the end type sub-emitters must be disposed as well (false by default)
- */
- dispose(disposeTexture = true, disposeAttachedSubEmitters = false, disposeEndSubEmitters = false) {
- this.resetDrawCache();
- if (this._vertexBuffer) {
- this._vertexBuffer.dispose();
- this._vertexBuffer = null;
- }
- if (this._spriteBuffer) {
- this._spriteBuffer.dispose();
- this._spriteBuffer = null;
- }
- if (this._indexBuffer) {
- this._engine._releaseBuffer(this._indexBuffer);
- this._indexBuffer = null;
- }
- if (this._linesIndexBuffer) {
- this._engine._releaseBuffer(this._linesIndexBuffer);
- this._linesIndexBuffer = null;
- }
- if (this._linesIndexBufferUseInstancing) {
- this._engine._releaseBuffer(this._linesIndexBufferUseInstancing);
- this._linesIndexBufferUseInstancing = null;
- }
- if (this._vertexArrayObject) {
- this._engine.releaseVertexArrayObject(this._vertexArrayObject);
- this._vertexArrayObject = null;
- }
- if (disposeTexture && this.particleTexture) {
- this.particleTexture.dispose();
- this.particleTexture = null;
- }
- if (disposeTexture && this.noiseTexture) {
- this.noiseTexture.dispose();
- this.noiseTexture = null;
- }
- if (this._rampGradientsTexture) {
- this._rampGradientsTexture.dispose();
- this._rampGradientsTexture = null;
- }
- this._onDispose(disposeAttachedSubEmitters, disposeEndSubEmitters);
- if (this._onBeforeDrawParticlesObservable) {
- this._onBeforeDrawParticlesObservable.clear();
- }
- // Remove from scene
- if (this._scene) {
- const index = this._scene.particleSystems.indexOf(this);
- if (index > -1) {
- this._scene.particleSystems.splice(index, 1);
- }
- this._scene._activeParticleSystems.dispose();
- }
- // Callback
- this.onDisposeObservable.notifyObservers(this);
- this.onDisposeObservable.clear();
- this.onStoppedObservable.clear();
- this.reset();
- }
- }
- //# sourceMappingURL=thinParticleSystem.js.map
|