You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
911 lines
29 KiB
JavaScript
911 lines
29 KiB
JavaScript
import Cartesian2 from "../Core/Cartesian2.js";
|
|
import Cartesian3 from "../Core/Cartesian3.js";
|
|
import Check from "../Core/Check.js";
|
|
import Color from "../Core/Color.js";
|
|
import defaultValue from "../Core/defaultValue.js";
|
|
import defined from "../Core/defined.js";
|
|
import destroyObject from "../Core/destroyObject.js";
|
|
import Event from "../Core/Event.js";
|
|
import JulianDate from "../Core/JulianDate.js";
|
|
import CesiumMath from "../Core/Math.js";
|
|
import Matrix4 from "../Core/Matrix4.js";
|
|
import BillboardCollection from "./BillboardCollection.js";
|
|
import CircleEmitter from "./CircleEmitter.js";
|
|
import Particle from "./Particle.js";
|
|
|
|
var defaultImageSize = new Cartesian2(1.0, 1.0);
|
|
|
|
/**
|
|
* A ParticleSystem manages the updating and display of a collection of particles.
|
|
*
|
|
* @alias ParticleSystem
|
|
* @constructor
|
|
*
|
|
* @param {Object} [options] Object with the following properties:
|
|
* @param {Boolean} [options.show=true] Whether to display the particle system.
|
|
* @param {ParticleSystem.updateCallback} [options.updateCallback] The callback function to be called each frame to update a particle.
|
|
* @param {ParticleEmitter} [options.emitter=new CircleEmitter(0.5)] The particle emitter for this system.
|
|
* @param {Matrix4} [options.modelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the particle system from model to world coordinates.
|
|
* @param {Matrix4} [options.emitterModelMatrix=Matrix4.IDENTITY] The 4x4 transformation matrix that transforms the particle system emitter within the particle systems local coordinate system.
|
|
* @param {Number} [options.emissionRate=5] The number of particles to emit per second.
|
|
* @param {ParticleBurst[]} [options.bursts] An array of {@link ParticleBurst}, emitting bursts of particles at periodic times.
|
|
* @param {Boolean} [options.loop=true] Whether the particle system should loop its bursts when it is complete.
|
|
* @param {Number} [options.scale=1.0] Sets the scale to apply to the image of the particle for the duration of its particleLife.
|
|
* @param {Number} [options.startScale] The initial scale to apply to the image of the particle at the beginning of its life.
|
|
* @param {Number} [options.endScale] The final scale to apply to the image of the particle at the end of its life.
|
|
* @param {Color} [options.color=Color.WHITE] Sets the color of a particle for the duration of its particleLife.
|
|
* @param {Color} [options.startColor] The color of the particle at the beginning of its life.
|
|
* @param {Color} [options.endColor] The color of the particle at the end of its life.
|
|
* @param {Object} [options.image] The URI, HTMLImageElement, or HTMLCanvasElement to use for the billboard.
|
|
* @param {Cartesian2} [options.imageSize=new Cartesian2(1.0, 1.0)] If set, overrides the minimumImageSize and maximumImageSize inputs that scale the particle image's dimensions in pixels.
|
|
* @param {Cartesian2} [options.minimumImageSize] Sets the minimum bound, width by height, above which to randomly scale the particle image's dimensions in pixels.
|
|
* @param {Cartesian2} [options.maximumImageSize] Sets the maximum bound, width by height, below which to randomly scale the particle image's dimensions in pixels.
|
|
* @param {Boolean} [options.sizeInMeters] Sets if the size of particles is in meters or pixels. <code>true</code> to size the particles in meters; otherwise, the size is in pixels.
|
|
* @param {Number} [options.speed=1.0] If set, overrides the minimumSpeed and maximumSpeed inputs with this value.
|
|
* @param {Number} [options.minimumSpeed] Sets the minimum bound in meters per second above which a particle's actual speed will be randomly chosen.
|
|
* @param {Number} [options.maximumSpeed] Sets the maximum bound in meters per second below which a particle's actual speed will be randomly chosen.
|
|
* @param {Number} [options.lifetime=Number.MAX_VALUE] How long the particle system will emit particles, in seconds.
|
|
* @param {Number} [options.particleLife=5.0] If set, overrides the minimumParticleLife and maximumParticleLife inputs with this value.
|
|
* @param {Number} [options.minimumParticleLife] Sets the minimum bound in seconds for the possible duration of a particle's life above which a particle's actual life will be randomly chosen.
|
|
* @param {Number} [options.maximumParticleLife] Sets the maximum bound in seconds for the possible duration of a particle's life below which a particle's actual life will be randomly chosen.
|
|
* @param {Number} [options.mass=1.0] Sets the minimum and maximum mass of particles in kilograms.
|
|
* @param {Number} [options.minimumMass] Sets the minimum bound for the mass of a particle in kilograms. A particle's actual mass will be chosen as a random amount above this value.
|
|
* @param {Number} [options.maximumMass] Sets the maximum mass of particles in kilograms. A particle's actual mass will be chosen as a random amount below this value.
|
|
* @tutorial {@link https://cesium.com/docs/tutorials/particle-systems/|Particle Systems Tutorial}
|
|
* @demo {@link https://sandcastle.cesium.com/?src=Particle%20System.html&label=Showcases|Particle Systems Tutorial Demo}
|
|
* @demo {@link https://sandcastle.cesium.com/?src=Particle%20System%20Fireworks.html&label=Showcases|Particle Systems Fireworks Demo}
|
|
*/
|
|
function ParticleSystem(options) {
|
|
options = defaultValue(options, defaultValue.EMPTY_OBJECT);
|
|
|
|
/**
|
|
* Whether to display the particle system.
|
|
* @type {Boolean}
|
|
* @default true
|
|
*/
|
|
this.show = defaultValue(options.show, true);
|
|
|
|
/**
|
|
* An array of force callbacks. The callback is passed a {@link Particle} and the difference from the last time
|
|
* @type {ParticleSystem.updateCallback}
|
|
* @default undefined
|
|
*/
|
|
this.updateCallback = options.updateCallback;
|
|
|
|
/**
|
|
* Whether the particle system should loop it's bursts when it is complete.
|
|
* @type {Boolean}
|
|
* @default true
|
|
*/
|
|
this.loop = defaultValue(options.loop, true);
|
|
|
|
/**
|
|
* The URI, HTMLImageElement, or HTMLCanvasElement to use for the billboard.
|
|
* @type {Object}
|
|
* @default undefined
|
|
*/
|
|
this.image = defaultValue(options.image, undefined);
|
|
|
|
var emitter = options.emitter;
|
|
if (!defined(emitter)) {
|
|
emitter = new CircleEmitter(0.5);
|
|
}
|
|
this._emitter = emitter;
|
|
|
|
this._bursts = options.bursts;
|
|
|
|
this._modelMatrix = Matrix4.clone(
|
|
defaultValue(options.modelMatrix, Matrix4.IDENTITY)
|
|
);
|
|
this._emitterModelMatrix = Matrix4.clone(
|
|
defaultValue(options.emitterModelMatrix, Matrix4.IDENTITY)
|
|
);
|
|
this._matrixDirty = true;
|
|
this._combinedMatrix = new Matrix4();
|
|
|
|
this._startColor = Color.clone(
|
|
defaultValue(options.color, defaultValue(options.startColor, Color.WHITE))
|
|
);
|
|
this._endColor = Color.clone(
|
|
defaultValue(options.color, defaultValue(options.endColor, Color.WHITE))
|
|
);
|
|
|
|
this._startScale = defaultValue(
|
|
options.scale,
|
|
defaultValue(options.startScale, 1.0)
|
|
);
|
|
this._endScale = defaultValue(
|
|
options.scale,
|
|
defaultValue(options.endScale, 1.0)
|
|
);
|
|
|
|
this._emissionRate = defaultValue(options.emissionRate, 5.0);
|
|
|
|
this._minimumSpeed = defaultValue(
|
|
options.speed,
|
|
defaultValue(options.minimumSpeed, 1.0)
|
|
);
|
|
this._maximumSpeed = defaultValue(
|
|
options.speed,
|
|
defaultValue(options.maximumSpeed, 1.0)
|
|
);
|
|
|
|
this._minimumParticleLife = defaultValue(
|
|
options.particleLife,
|
|
defaultValue(options.minimumParticleLife, 5.0)
|
|
);
|
|
this._maximumParticleLife = defaultValue(
|
|
options.particleLife,
|
|
defaultValue(options.maximumParticleLife, 5.0)
|
|
);
|
|
|
|
this._minimumMass = defaultValue(
|
|
options.mass,
|
|
defaultValue(options.minimumMass, 1.0)
|
|
);
|
|
this._maximumMass = defaultValue(
|
|
options.mass,
|
|
defaultValue(options.maximumMass, 1.0)
|
|
);
|
|
|
|
this._minimumImageSize = Cartesian2.clone(
|
|
defaultValue(
|
|
options.imageSize,
|
|
defaultValue(options.minimumImageSize, defaultImageSize)
|
|
)
|
|
);
|
|
this._maximumImageSize = Cartesian2.clone(
|
|
defaultValue(
|
|
options.imageSize,
|
|
defaultValue(options.maximumImageSize, defaultImageSize)
|
|
)
|
|
);
|
|
|
|
this._sizeInMeters = defaultValue(options.sizeInMeters, false);
|
|
|
|
this._lifetime = defaultValue(options.lifetime, Number.MAX_VALUE);
|
|
|
|
this._billboardCollection = undefined;
|
|
this._particles = [];
|
|
|
|
// An array of available particles that we can reuse instead of allocating new.
|
|
this._particlePool = [];
|
|
|
|
this._previousTime = undefined;
|
|
this._currentTime = 0.0;
|
|
this._carryOver = 0.0;
|
|
|
|
this._complete = new Event();
|
|
this._isComplete = false;
|
|
|
|
this._updateParticlePool = true;
|
|
this._particleEstimate = 0;
|
|
}
|
|
|
|
Object.defineProperties(ParticleSystem.prototype, {
|
|
/**
|
|
* The particle emitter for this
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {ParticleEmitter}
|
|
* @default CircleEmitter
|
|
*/
|
|
emitter: {
|
|
get: function () {
|
|
return this._emitter;
|
|
},
|
|
set: function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("value", value);
|
|
//>>includeEnd('debug');
|
|
this._emitter = value;
|
|
},
|
|
},
|
|
/**
|
|
* An array of {@link ParticleBurst}, emitting bursts of particles at periodic times.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {ParticleBurst[]}
|
|
* @default undefined
|
|
*/
|
|
bursts: {
|
|
get: function () {
|
|
return this._bursts;
|
|
},
|
|
set: function (value) {
|
|
this._bursts = value;
|
|
this._updateParticlePool = true;
|
|
},
|
|
},
|
|
/**
|
|
* The 4x4 transformation matrix that transforms the particle system from model to world coordinates.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {Matrix4}
|
|
* @default Matrix4.IDENTITY
|
|
*/
|
|
modelMatrix: {
|
|
get: function () {
|
|
return this._modelMatrix;
|
|
},
|
|
set: function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("value", value);
|
|
//>>includeEnd('debug');
|
|
this._matrixDirty =
|
|
this._matrixDirty || !Matrix4.equals(this._modelMatrix, value);
|
|
Matrix4.clone(value, this._modelMatrix);
|
|
},
|
|
},
|
|
/**
|
|
* The 4x4 transformation matrix that transforms the particle system emitter within the particle systems local coordinate system.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {Matrix4}
|
|
* @default Matrix4.IDENTITY
|
|
*/
|
|
emitterModelMatrix: {
|
|
get: function () {
|
|
return this._emitterModelMatrix;
|
|
},
|
|
set: function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("value", value);
|
|
//>>includeEnd('debug');
|
|
this._matrixDirty =
|
|
this._matrixDirty || !Matrix4.equals(this._emitterModelMatrix, value);
|
|
Matrix4.clone(value, this._emitterModelMatrix);
|
|
},
|
|
},
|
|
/**
|
|
* The color of the particle at the beginning of its life.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {Color}
|
|
* @default Color.WHITE
|
|
*/
|
|
startColor: {
|
|
get: function () {
|
|
return this._startColor;
|
|
},
|
|
set: function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("value", value);
|
|
//>>includeEnd('debug');
|
|
Color.clone(value, this._startColor);
|
|
},
|
|
},
|
|
/**
|
|
* The color of the particle at the end of its life.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {Color}
|
|
* @default Color.WHITE
|
|
*/
|
|
endColor: {
|
|
get: function () {
|
|
return this._endColor;
|
|
},
|
|
set: function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.defined("value", value);
|
|
//>>includeEnd('debug');
|
|
Color.clone(value, this._endColor);
|
|
},
|
|
},
|
|
/**
|
|
* The initial scale to apply to the image of the particle at the beginning of its life.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {Number}
|
|
* @default 1.0
|
|
*/
|
|
startScale: {
|
|
get: function () {
|
|
return this._startScale;
|
|
},
|
|
set: function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.number.greaterThanOrEquals("value", value, 0.0);
|
|
//>>includeEnd('debug');
|
|
this._startScale = value;
|
|
},
|
|
},
|
|
/**
|
|
* The final scale to apply to the image of the particle at the end of its life.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {Number}
|
|
* @default 1.0
|
|
*/
|
|
endScale: {
|
|
get: function () {
|
|
return this._endScale;
|
|
},
|
|
set: function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.number.greaterThanOrEquals("value", value, 0.0);
|
|
//>>includeEnd('debug');
|
|
this._endScale = value;
|
|
},
|
|
},
|
|
/**
|
|
* The number of particles to emit per second.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {Number}
|
|
* @default 5
|
|
*/
|
|
emissionRate: {
|
|
get: function () {
|
|
return this._emissionRate;
|
|
},
|
|
set: function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.number.greaterThanOrEquals("value", value, 0.0);
|
|
//>>includeEnd('debug');
|
|
this._emissionRate = value;
|
|
this._updateParticlePool = true;
|
|
},
|
|
},
|
|
/**
|
|
* Sets the minimum bound in meters per second above which a particle's actual speed will be randomly chosen.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {Number}
|
|
* @default 1.0
|
|
*/
|
|
minimumSpeed: {
|
|
get: function () {
|
|
return this._minimumSpeed;
|
|
},
|
|
set: function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.number.greaterThanOrEquals("value", value, 0.0);
|
|
//>>includeEnd('debug');
|
|
this._minimumSpeed = value;
|
|
},
|
|
},
|
|
/**
|
|
* Sets the maximum bound in meters per second below which a particle's actual speed will be randomly chosen.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {Number}
|
|
* @default 1.0
|
|
*/
|
|
maximumSpeed: {
|
|
get: function () {
|
|
return this._maximumSpeed;
|
|
},
|
|
set: function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.number.greaterThanOrEquals("value", value, 0.0);
|
|
//>>includeEnd('debug');
|
|
this._maximumSpeed = value;
|
|
},
|
|
},
|
|
/**
|
|
* Sets the minimum bound in seconds for the possible duration of a particle's life above which a particle's actual life will be randomly chosen.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {Number}
|
|
* @default 5.0
|
|
*/
|
|
minimumParticleLife: {
|
|
get: function () {
|
|
return this._minimumParticleLife;
|
|
},
|
|
set: function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.number.greaterThanOrEquals("value", value, 0.0);
|
|
//>>includeEnd('debug');
|
|
this._minimumParticleLife = value;
|
|
},
|
|
},
|
|
/**
|
|
* Sets the maximum bound in seconds for the possible duration of a particle's life below which a particle's actual life will be randomly chosen.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {Number}
|
|
* @default 5.0
|
|
*/
|
|
maximumParticleLife: {
|
|
get: function () {
|
|
return this._maximumParticleLife;
|
|
},
|
|
set: function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.number.greaterThanOrEquals("value", value, 0.0);
|
|
//>>includeEnd('debug');
|
|
this._maximumParticleLife = value;
|
|
this._updateParticlePool = true;
|
|
},
|
|
},
|
|
/**
|
|
* Sets the minimum mass of particles in kilograms.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {Number}
|
|
* @default 1.0
|
|
*/
|
|
minimumMass: {
|
|
get: function () {
|
|
return this._minimumMass;
|
|
},
|
|
set: function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.number.greaterThanOrEquals("value", value, 0.0);
|
|
//>>includeEnd('debug');
|
|
this._minimumMass = value;
|
|
},
|
|
},
|
|
/**
|
|
* Sets the maximum mass of particles in kilograms.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {Number}
|
|
* @default 1.0
|
|
*/
|
|
maximumMass: {
|
|
get: function () {
|
|
return this._maximumMass;
|
|
},
|
|
set: function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.number.greaterThanOrEquals("value", value, 0.0);
|
|
//>>includeEnd('debug');
|
|
this._maximumMass = value;
|
|
},
|
|
},
|
|
/**
|
|
* Sets the minimum bound, width by height, above which to randomly scale the particle image's dimensions in pixels.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {Cartesian2}
|
|
* @default new Cartesian2(1.0, 1.0)
|
|
*/
|
|
minimumImageSize: {
|
|
get: function () {
|
|
return this._minimumImageSize;
|
|
},
|
|
set: function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.object("value", value);
|
|
Check.typeOf.number.greaterThanOrEquals("value.x", value.x, 0.0);
|
|
Check.typeOf.number.greaterThanOrEquals("value.y", value.y, 0.0);
|
|
//>>includeEnd('debug');
|
|
this._minimumImageSize = value;
|
|
},
|
|
},
|
|
/**
|
|
* Sets the maximum bound, width by height, below which to randomly scale the particle image's dimensions in pixels.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {Cartesian2}
|
|
* @default new Cartesian2(1.0, 1.0)
|
|
*/
|
|
maximumImageSize: {
|
|
get: function () {
|
|
return this._maximumImageSize;
|
|
},
|
|
set: function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.object("value", value);
|
|
Check.typeOf.number.greaterThanOrEquals("value.x", value.x, 0.0);
|
|
Check.typeOf.number.greaterThanOrEquals("value.y", value.y, 0.0);
|
|
//>>includeEnd('debug');
|
|
this._maximumImageSize = value;
|
|
},
|
|
},
|
|
/**
|
|
* Gets or sets if the particle size is in meters or pixels. <code>true</code> to size particles in meters; otherwise, the size is in pixels.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {Boolean}
|
|
* @default false
|
|
*/
|
|
sizeInMeters: {
|
|
get: function () {
|
|
return this._sizeInMeters;
|
|
},
|
|
set: function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.bool("value", value);
|
|
//>>includeEnd('debug');
|
|
this._sizeInMeters = value;
|
|
},
|
|
},
|
|
/**
|
|
* How long the particle system will emit particles, in seconds.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {Number}
|
|
* @default Number.MAX_VALUE
|
|
*/
|
|
lifetime: {
|
|
get: function () {
|
|
return this._lifetime;
|
|
},
|
|
set: function (value) {
|
|
//>>includeStart('debug', pragmas.debug);
|
|
Check.typeOf.number.greaterThanOrEquals("value", value, 0.0);
|
|
//>>includeEnd('debug');
|
|
this._lifetime = value;
|
|
},
|
|
},
|
|
/**
|
|
* Fires an event when the particle system has reached the end of its lifetime.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {Event}
|
|
*/
|
|
complete: {
|
|
get: function () {
|
|
return this._complete;
|
|
},
|
|
},
|
|
/**
|
|
* When <code>true</code>, the particle system has reached the end of its lifetime; <code>false</code> otherwise.
|
|
* @memberof ParticleSystem.prototype
|
|
* @type {Boolean}
|
|
*/
|
|
isComplete: {
|
|
get: function () {
|
|
return this._isComplete;
|
|
},
|
|
},
|
|
});
|
|
|
|
function updateParticlePool(system) {
|
|
var emissionRate = system._emissionRate;
|
|
var life = system._maximumParticleLife;
|
|
|
|
var burstAmount = 0;
|
|
var bursts = system._bursts;
|
|
if (defined(bursts)) {
|
|
var length = bursts.length;
|
|
for (var i = 0; i < length; ++i) {
|
|
burstAmount += bursts[i].maximum;
|
|
}
|
|
}
|
|
|
|
var billboardCollection = system._billboardCollection;
|
|
var image = system.image;
|
|
|
|
var particleEstimate = Math.ceil(emissionRate * life + burstAmount);
|
|
var particles = system._particles;
|
|
var particlePool = system._particlePool;
|
|
var numToAdd = Math.max(
|
|
particleEstimate - particles.length - particlePool.length,
|
|
0
|
|
);
|
|
|
|
for (var j = 0; j < numToAdd; ++j) {
|
|
var particle = new Particle();
|
|
particle._billboard = billboardCollection.add({
|
|
image: image,
|
|
});
|
|
particlePool.push(particle);
|
|
}
|
|
|
|
system._particleEstimate = particleEstimate;
|
|
}
|
|
|
|
function getOrCreateParticle(system) {
|
|
// Try to reuse an existing particle from the pool.
|
|
var particle = system._particlePool.pop();
|
|
if (!defined(particle)) {
|
|
// Create a new one
|
|
particle = new Particle();
|
|
}
|
|
return particle;
|
|
}
|
|
|
|
function addParticleToPool(system, particle) {
|
|
system._particlePool.push(particle);
|
|
}
|
|
|
|
function freeParticlePool(system) {
|
|
var particles = system._particles;
|
|
var particlePool = system._particlePool;
|
|
var billboardCollection = system._billboardCollection;
|
|
|
|
var numParticles = particles.length;
|
|
var numInPool = particlePool.length;
|
|
var estimate = system._particleEstimate;
|
|
|
|
var start = numInPool - Math.max(estimate - numParticles - numInPool, 0);
|
|
for (var i = start; i < numInPool; ++i) {
|
|
var p = particlePool[i];
|
|
billboardCollection.remove(p._billboard);
|
|
}
|
|
particlePool.length = start;
|
|
}
|
|
|
|
function removeBillboard(particle) {
|
|
if (defined(particle._billboard)) {
|
|
particle._billboard.show = false;
|
|
}
|
|
}
|
|
|
|
function updateBillboard(system, particle) {
|
|
var billboard = particle._billboard;
|
|
if (!defined(billboard)) {
|
|
billboard = particle._billboard = system._billboardCollection.add({
|
|
image: particle.image,
|
|
});
|
|
}
|
|
billboard.width = particle.imageSize.x;
|
|
billboard.height = particle.imageSize.y;
|
|
billboard.position = particle.position;
|
|
billboard.sizeInMeters = system.sizeInMeters;
|
|
billboard.show = true;
|
|
|
|
// Update the color
|
|
var r = CesiumMath.lerp(
|
|
particle.startColor.red,
|
|
particle.endColor.red,
|
|
particle.normalizedAge
|
|
);
|
|
var g = CesiumMath.lerp(
|
|
particle.startColor.green,
|
|
particle.endColor.green,
|
|
particle.normalizedAge
|
|
);
|
|
var b = CesiumMath.lerp(
|
|
particle.startColor.blue,
|
|
particle.endColor.blue,
|
|
particle.normalizedAge
|
|
);
|
|
var a = CesiumMath.lerp(
|
|
particle.startColor.alpha,
|
|
particle.endColor.alpha,
|
|
particle.normalizedAge
|
|
);
|
|
billboard.color = new Color(r, g, b, a);
|
|
|
|
// Update the scale
|
|
billboard.scale = CesiumMath.lerp(
|
|
particle.startScale,
|
|
particle.endScale,
|
|
particle.normalizedAge
|
|
);
|
|
}
|
|
|
|
function addParticle(system, particle) {
|
|
particle.startColor = Color.clone(system._startColor, particle.startColor);
|
|
particle.endColor = Color.clone(system._endColor, particle.endColor);
|
|
particle.startScale = system._startScale;
|
|
particle.endScale = system._endScale;
|
|
particle.image = system.image;
|
|
particle.life = CesiumMath.randomBetween(
|
|
system._minimumParticleLife,
|
|
system._maximumParticleLife
|
|
);
|
|
particle.mass = CesiumMath.randomBetween(
|
|
system._minimumMass,
|
|
system._maximumMass
|
|
);
|
|
particle.imageSize.x = CesiumMath.randomBetween(
|
|
system._minimumImageSize.x,
|
|
system._maximumImageSize.x
|
|
);
|
|
particle.imageSize.y = CesiumMath.randomBetween(
|
|
system._minimumImageSize.y,
|
|
system._maximumImageSize.y
|
|
);
|
|
|
|
// Reset the normalizedAge and age in case the particle was reused.
|
|
particle._normalizedAge = 0.0;
|
|
particle._age = 0.0;
|
|
|
|
var speed = CesiumMath.randomBetween(
|
|
system._minimumSpeed,
|
|
system._maximumSpeed
|
|
);
|
|
Cartesian3.multiplyByScalar(particle.velocity, speed, particle.velocity);
|
|
|
|
system._particles.push(particle);
|
|
}
|
|
|
|
function calculateNumberToEmit(system, dt) {
|
|
// This emitter is finished if it exceeds it's lifetime.
|
|
if (system._isComplete) {
|
|
return 0;
|
|
}
|
|
|
|
dt = CesiumMath.mod(dt, system._lifetime);
|
|
|
|
// Compute the number of particles to emit based on the emissionRate.
|
|
var v = dt * system._emissionRate;
|
|
var numToEmit = Math.floor(v);
|
|
system._carryOver += v - numToEmit;
|
|
if (system._carryOver > 1.0) {
|
|
numToEmit++;
|
|
system._carryOver -= 1.0;
|
|
}
|
|
|
|
// Apply any bursts
|
|
if (defined(system.bursts)) {
|
|
var length = system.bursts.length;
|
|
for (var i = 0; i < length; i++) {
|
|
var burst = system.bursts[i];
|
|
var currentTime = system._currentTime;
|
|
if (defined(burst) && !burst._complete && currentTime > burst.time) {
|
|
numToEmit += CesiumMath.randomBetween(burst.minimum, burst.maximum);
|
|
burst._complete = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return numToEmit;
|
|
}
|
|
|
|
var rotatedVelocityScratch = new Cartesian3();
|
|
|
|
/**
|
|
* @private
|
|
*/
|
|
ParticleSystem.prototype.update = function (frameState) {
|
|
if (!this.show) {
|
|
return;
|
|
}
|
|
|
|
if (!defined(this._billboardCollection)) {
|
|
this._billboardCollection = new BillboardCollection();
|
|
}
|
|
|
|
if (this._updateParticlePool) {
|
|
updateParticlePool(this);
|
|
this._updateParticlePool = false;
|
|
}
|
|
|
|
// Compute the frame time
|
|
var dt = 0.0;
|
|
if (this._previousTime) {
|
|
dt = JulianDate.secondsDifference(frameState.time, this._previousTime);
|
|
}
|
|
|
|
if (dt < 0.0) {
|
|
dt = 0.0;
|
|
}
|
|
|
|
var particles = this._particles;
|
|
var emitter = this._emitter;
|
|
var updateCallback = this.updateCallback;
|
|
|
|
var i;
|
|
var particle;
|
|
|
|
// update particles and remove dead particles
|
|
var length = particles.length;
|
|
for (i = 0; i < length; ++i) {
|
|
particle = particles[i];
|
|
if (!particle.update(dt, updateCallback)) {
|
|
removeBillboard(particle);
|
|
// Add the particle back to the pool so it can be reused.
|
|
addParticleToPool(this, particle);
|
|
particles[i] = particles[length - 1];
|
|
--i;
|
|
--length;
|
|
} else {
|
|
updateBillboard(this, particle);
|
|
}
|
|
}
|
|
particles.length = length;
|
|
|
|
var numToEmit = calculateNumberToEmit(this, dt);
|
|
|
|
if (numToEmit > 0 && defined(emitter)) {
|
|
// Compute the final model matrix by combining the particle systems model matrix and the emitter matrix.
|
|
if (this._matrixDirty) {
|
|
this._combinedMatrix = Matrix4.multiply(
|
|
this.modelMatrix,
|
|
this.emitterModelMatrix,
|
|
this._combinedMatrix
|
|
);
|
|
this._matrixDirty = false;
|
|
}
|
|
|
|
var combinedMatrix = this._combinedMatrix;
|
|
|
|
for (i = 0; i < numToEmit; i++) {
|
|
// Create a new particle.
|
|
particle = getOrCreateParticle(this);
|
|
|
|
// Let the emitter initialize the particle.
|
|
this._emitter.emit(particle);
|
|
|
|
//For the velocity we need to add it to the original position and then multiply by point.
|
|
Cartesian3.add(
|
|
particle.position,
|
|
particle.velocity,
|
|
rotatedVelocityScratch
|
|
);
|
|
Matrix4.multiplyByPoint(
|
|
combinedMatrix,
|
|
rotatedVelocityScratch,
|
|
rotatedVelocityScratch
|
|
);
|
|
|
|
// Change the position to be in world coordinates
|
|
particle.position = Matrix4.multiplyByPoint(
|
|
combinedMatrix,
|
|
particle.position,
|
|
particle.position
|
|
);
|
|
|
|
// Orient the velocity in world space as well.
|
|
Cartesian3.subtract(
|
|
rotatedVelocityScratch,
|
|
particle.position,
|
|
particle.velocity
|
|
);
|
|
Cartesian3.normalize(particle.velocity, particle.velocity);
|
|
|
|
// Add the particle to the system.
|
|
addParticle(this, particle);
|
|
updateBillboard(this, particle);
|
|
}
|
|
}
|
|
|
|
this._billboardCollection.update(frameState);
|
|
this._previousTime = JulianDate.clone(frameState.time, this._previousTime);
|
|
this._currentTime += dt;
|
|
|
|
if (
|
|
this._lifetime !== Number.MAX_VALUE &&
|
|
this._currentTime > this._lifetime
|
|
) {
|
|
if (this.loop) {
|
|
this._currentTime = CesiumMath.mod(this._currentTime, this._lifetime);
|
|
if (this.bursts) {
|
|
var burstLength = this.bursts.length;
|
|
// Reset any bursts
|
|
for (i = 0; i < burstLength; i++) {
|
|
this.bursts[i]._complete = false;
|
|
}
|
|
}
|
|
} else {
|
|
this._isComplete = true;
|
|
this._complete.raiseEvent(this);
|
|
}
|
|
}
|
|
|
|
// free particles in the pool and release billboard GPU memory
|
|
if (frameState.frameNumber % 120 === 0) {
|
|
freeParticlePool(this);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Returns true if this object was destroyed; otherwise, false.
|
|
* <br /><br />
|
|
* If this object was destroyed, it should not be used; calling any function other than
|
|
* <code>isDestroyed</code> will result in a {@link DeveloperError} exception.
|
|
*
|
|
* @returns {Boolean} <code>true</code> if this object was destroyed; otherwise, <code>false</code>.
|
|
*
|
|
* @see ParticleSystem#destroy
|
|
*/
|
|
ParticleSystem.prototype.isDestroyed = function () {
|
|
return false;
|
|
};
|
|
|
|
/**
|
|
* Destroys the WebGL resources held by this object. Destroying an object allows for deterministic
|
|
* release of WebGL resources, instead of relying on the garbage collector to destroy this object.
|
|
* <br /><br />
|
|
* Once an object is destroyed, it should not be used; calling any function other than
|
|
* <code>isDestroyed</code> will result in a {@link DeveloperError} exception. Therefore,
|
|
* assign the return value (<code>undefined</code>) to the object as done in the example.
|
|
*
|
|
* @exception {DeveloperError} This object was destroyed, i.e., destroy() was called.
|
|
*
|
|
* @see ParticleSystem#isDestroyed
|
|
*/
|
|
ParticleSystem.prototype.destroy = function () {
|
|
this._billboardCollection =
|
|
this._billboardCollection && this._billboardCollection.destroy();
|
|
return destroyObject(this);
|
|
};
|
|
|
|
/**
|
|
* A function used to modify attributes of the particle at each time step. This can include force modifications,
|
|
* color, sizing, etc.
|
|
*
|
|
* @callback ParticleSystem.updateCallback
|
|
*
|
|
* @param {Particle} particle The particle being updated.
|
|
* @param {Number} dt The time in seconds since the last update.
|
|
*
|
|
* @example
|
|
* function applyGravity(particle, dt) {
|
|
* var position = particle.position;
|
|
* var gravityVector = Cesium.Cartesian3.normalize(position, new Cesium.Cartesian3());
|
|
* Cesium.Cartesian3.multiplyByScalar(gravityVector, GRAVITATIONAL_CONSTANT * dt, gravityVector);
|
|
* particle.velocity = Cesium.Cartesian3.add(particle.velocity, gravityVector, particle.velocity);
|
|
* }
|
|
*/
|
|
export default ParticleSystem;
|