API Docs for: v2.1.0
Show:

File: src\node\class.particles.js

/*
 * Copyright (c) 2014 Gwennael Buchet
 *
 * License/Terms of Use
 *
 * Permission is hereby granted, free of charge and for the term of intellectual property rights on the Software, to any
 * person obtaining a copy of this software and associated documentation files (the "Software"), to use, copy, modify
 * and propagate free of charge, anywhere in the world, all or part of the Software subject to the following mandatory
 * conditions:
 *
 *   •    The above copyright notice and this permission notice shall be included in all copies or substantial portions
 *   of the Software.
 *
 *  Any failure to comply with the above shall automatically terminate the license and be construed as a breach of these
 *  Terms of Use causing significant harm to Gwennael Buchet.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 *  WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
 *  OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 *  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *  SOFTWARE.
 *
 *  Except as contained in this notice, the name of Gwennael Buchet shall not be used in advertising or otherwise to promote
 *  the use or other dealings in this Software without prior written authorization from Gwennael Buchet.
 *
 *  These Terms of Use are subject to French law.
 */

"use strict";

/**
 * @module Animation
 * @submodule ParticleSystem
 * @class CGSGParticle
 * @constructor
 * @param node {CGSGNode}
 * @type {CGSGParticle}
 * @author Gwennael Buchet (gwennael.buchet@gmail.com)
 */
var CGSGParticle = CGSGObject.extend(
    {
        initialize : function(node) {
            /**
             * @property node
             * @type {CGSGNode}
             */
            this.node = node;

            this.node.isClickable = false;
            this.node.isResizable = false;
            this.node.isDraggable = false;

            /**
             * A void* property to let the developer store whatever he needs (new properties, ...)
             * @property userData
             * @type {*}
             * @default null
             */
            this.userData = null;

            this.init();
        },

        /**
         * Initialize attributes of this particle
         * @public
         * @method init
         */
        init : function() {
            //this.direction = new CGSGVector2D(1, 0);
            this.position = new CGSGPosition(0.0, 0.0);
            this.mass = 1000;
            this.initVelocity(new CGSGVector2D(1.0, 1.0));
            this.checkCTL = null;
            this.isAlive = true;
            this.age = 0;

            //this._gravity = new CGSGVector2D(0.0, 0.0);
            //this._forceTotal = new CGSGVector2D(0.0, 0.0);
            this._acceleration = new CGSGVector2D(0.0, 0.0);

            this.speedThreshold = 0.0;
        },

        /**
         * @public
         * @method initPosition
         * @param {Number} x
         * @param {Number} y
         */
        initPosition : function(x, y) {
            this.position.x = x;
            this.position.y = y;
            this.node.translateTo(x, y);
        },

        /**
         * @public
         * @method initVelocity
         * @param {CGSGVector2D} velocity
         */
        initVelocity : function(velocity) {
            this.velocity = velocity.copy();
            this.velocity.normalize();
        },

        /**
         * @public
         * @method initSpeedThreshold
         * @param {Number} st
         */
        initSpeedThreshold : function(st) {
            this.speedThreshold = st;
        },

        /**
         * update the particle position with an Euler integration
         * TODO : externalize the process to choose between RK4 and Euler integration
         * @public
         * @method updatePosition
         * @param {Number} deltaTime
         * @param {Number} acceleration
         * @return {*}
         */
        updatePosition : function(deltaTime, acceleration) {
            if (isNaN(deltaTime)) {
                deltaTime = 1.0;
            }

            deltaTime += this.speedThreshold;

            this._acceleration.x = acceleration.x / this.mass;
            this._acceleration.y = acceleration.y / this.mass;

            this.velocity.x += this._acceleration.x * deltaTime;
            this.velocity.y += this._acceleration.y * deltaTime;

            this.position.x += this.velocity.x * deltaTime;
            this.position.y += this.velocity.y * deltaTime;

            if (this.node !== null) {
                this.node.translateTo(this.position.x, this.position.y);
            }

            //increment age of the particle
            this.age += deltaTime;

            //check the viablity of the particle
            if (cgsgExist(this.checkCTL)) {
                this.isAlive = this.checkCTL(this);
            }
            return this.isAlive;
        }
    }
);

/**
 * A particle emitter for the cgSceneGraph Particle System
 * @class CGSGParticleEmitter
 * @extends {CGSGNode}
 * @module Animation
 * @submodule ParticleSystem
 * @constructor
 * @param {Function} nodeConstructor
 * @param {CGSGRegion} region
 * @param {Number} nbParticlesMax
 * @param {Number} velocity
 * @param {Number} angle
 * @param {Number} speed
 * @param {Number} speedThreshold
 * @param {Number} outflow
 * @type {CGSGParticleEmitter}
 * @author Gwennael Buchet (gwennael.buchet@gmail.com)
 */
var CGSGParticleEmitter = CGSGNode.extend(
    {
        initialize : function(nodeConstructor,
                              region,
                              nbParticlesMax,
                              velocity,
                              angle,
                              speed,
                              speedThreshold,
                              outflow) {
            this._super(region.position.x, region.position.y);
            this.resizeTo(region.dimension.width, region.dimension.height);

            /**
             * @property classType
             * @type {String}
             */
            this.classType = "CGSGParticleEmitter";

            this._nodeContructor = nodeConstructor;
            /**
             * the region from where the particles are emitted
             * @property region
             * @type {CGSGRegion}
             */
            this.region = region;
            /**
             * number max of particles out of the emitter on 1 frame
             * @property nbParticlesMax
             * @type {Number}
             */
            this.nbParticlesMax = nbParticlesMax;
            /**
             * @property velocity
             * @type {CGSGVector2D}
             */
            this.velocity = new CGSGVector2D(0.0, 0.0);
            if (cgsgExist(velocity)) {
                this.velocity = velocity;
            }
            /**
             * angle range of emission. a particle is emitted in the this.direction vector + or - this.angle/2 angle.
             * @property angle
             * @type {Number}
             */
            this.angle = Math.PI / 5.0;
            if (cgsgExist(angle)) {
                this.angle = angle;
            }

            /**
             * speed of a particle
             * @property speed
             * @type {Number}
             */
            this.speed = 1.0;
            if (cgsgExist(speed)) {
                this.speed = speed;
            }
            /**
             * threshold to randomize and add to the speed of a particle
             * @property speedThreshold
             * @type {Number}
             */
            this.speedThreshold = 1.0;
            if (cgsgExist(speedThreshold)) {
                this.speedThreshold = speedThreshold;
            }

            /**
             * @property outflow
             * @type {Number}
             */
            this.outflow = 0;
            if (cgsgExist(outflow)) {
                this.outflow = outflow;
            }

            this._currentFrame = this.outflow;

            //list of the particles
            this._particles = [];
            this._isPlaying = false;
            this._forces = [];
            this._acceleration = new CGSGVector2D(0.0, 0.0);

            /**
             * Gravity Force added by default with the addForce method
             * @property gravity
             * @type {Object}
             */
            this.gravity = this.addForce(new CGSGVector2D(0.0, 9.81), null);

            /**
             * Callback on end of update for 1 particle
             * @property onUpdateParticleEnd
             * @default null
             * @type {function}
             */
            this.onUpdateParticleEnd = null;
            /**
             * Callback when reinit for 1 particle
             * @property onInitParticle
             * @default null
             * @type {function}
             */
            this.onInitParticle = null;
            /**
             * Callback when reinit all particles is done
             * @property onInitParticlesEnd
             * @default null
             * @type {function}
             */
            this.onInitParticlesEnd = null;
        },

        /**
         * start the animation
         * @public
         * @method start
         */
        start : function() {
            this._isPlaying = true;
        },

        /**
         * stop the animation
         * @public
         * @method stop
         */
        stop : function() {
            this._isPlaying = false;
        },

        /**
         * reset the animation
         * @public
         * @method reset
         */
        reset : function() {
            var p;
            this._currentFrame = 0;
            //free the memory
            for (p = this._particles.length - 1 ; p >= 0 ; p--) {
                this.removeChild(this._particles[p].node, true);
                delete (this._particles[p]);
            }
            this._particles.clear();
        },

        /**
         * @public
         * @method render
         * @param context
         */
        render : function(context) {
            var f, p;
            this.beforeRender(context);

            //update the acceleration of the particles, based on the current forces
            this._acceleration.initialize(0.0, 0.0);
            for (f = this._forces.length - 1 ; f >= 0 ; f--) {
                this._acceleration.x += this._forces[f].vector.x;
                this._acceleration.y += this._forces[f].vector.y;

                if (this._forces[f].ctl !== null) {
                    this._forces[f].age++;
                    if (this._forces[f].age >= this._forces[f].ctl) {
                        this.removeForce(this._forces[f]);
                    }
                }
            }

            //updates all particles
            for (p = 0 ; p < this._particles.length ; p++) {
                if (this._isPlaying) {
                    if (!this._particles[p].isAlive || !this.updateParticle(this._particles[p])) {
                        this.initParticle(this._particles[p], p);
                    }
                }

                this._particles[p].node.render(context);
            }

            //if not all of the particles are out
            if (this._particles.length < this.nbParticlesMax) {
                this._currentFrame++;
                if (this._currentFrame >= this.outflow) {
                    this._currentFrame = 0;
                    this._createParticle(this._particles.length);
                }
            }

            //restore state
            //this.afterRender(context);
            context.restore();
        },

        /**
         * create a new particle, and add it to the emitter
         * @private
         * @method _createParticle
         * @param {Number} index
         */
        _createParticle : function(index) {
            var node = this._nodeContructor();
            var particle = new CGSGParticle(node);
            this.initParticle(particle, index);
            this.addChild(particle.node);
            this._particles.push(particle);
        },

        /**
         * @public
         * @method initParticle
         * @param {CGSGParticle} particle
         * @param {Number} index
         */
        initParticle : function(particle, index) {
            particle.init();
            //set a random position inside the region of this emitter
            particle.initPosition(Math.random() * this.region.dimension.width,
                                  Math.random() * this.region.dimension.height);

            //set a random direction inside the angle
            var velocity = this.velocity.copy();
            var halfAngle = this.angle / 2.0;
            velocity.rotate(-halfAngle + Math.random() * this.angle);
            particle.initVelocity(velocity);

            particle.initSpeedThreshold(-this.speedThreshold + Math.random() * this.speedThreshold * 2.0);

            if (this.onInitParticle !== null) {
                CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_INIT_PARTICLE,
                                           new CGSGEvent(this, {index : index, particle : particle}));
                //this.onInitParticle({index : index, particle : particle});
                //this.onInitParticle({data :{index : index, particle : particle}});
            }
        },

        /**
         * @public
         * @method updateParticle
         * @param {CGSGParticle} particle
         * @return {Boolean}
         */
        updateParticle : function(particle) {
            //first, update the position of the particle node
            var isAlive = particle.updatePosition(this.speed, this._acceleration);

            //finally, call the update method of the particle node to apply extra animations
            //if (this.onUpdateParticleEnd !== null) {
            this.onUpdateParticleEnd &&
            CGSG.eventManager.dispatch(this, cgsgEventTypes.ON_UPDATE_PARTICLE_END,
                                       new CGSGEvent(this, {particle : particle}));
            //this.onUpdateParticleEnd(particle);
            //this.onUpdateParticleEnd({data:{particle : particle}});
            //}

            return isAlive;
        },

        /**
         * Add a force to the emitter
         * @public
         * @method addForce
         * @param {CGSGVector2D} vector
         * @param {Number} ttl time to live of the force
         * @return {Object}
         */
        addForce : function(vector, ttl) {
            var force = {vector : vector, ctl : ttl, age : 0};
            this._forces.push(force);
            return force;
        },

        /**
         * Remove a previously added force
         * @public
         * @method removeForce
         * @param {Object} force
         */
        removeForce : function(force) {
            this._forces.without(force);
        }/*,

     addImpulse : function(point, force, ttl) {
     for (var p = 0; p < this._particles.length; p++) {
     this._particles[p].addImpulse(point, force, ttl);
     }
     }*/
    }
);

/**
 * A particle System object.
 * @class CGSGParticleSystem
 * @extends {CGSGNode}
 * @module Animation
 * @submodule ParticleSystem
 * @type {CGSGParticleSystem}
 * @author Gwennael Buchet (gwennael.buchet@gmail.com)
 */
var CGSGParticleSystem = CGSGNode.extend(
    {
        initialize : function(x, y) {
            this._super(x, y);

            /**
             * @property classType
             * @type {String}
             */
            this.classType = "CGSGParticleSystem";

            /**
             * list of emitters
             * @property emitters
             * @type {Array}
             */
            this.emitters = [];
            /**
             * list of attractors
             * @property attractors
             * @type {Array}
             */
            this.attractors = [];
            /**
             * list of repulsors
             * @property repulsors
             * @type {Array}
             */
            this.repulsors = [];

            this.isClickable = false;
            this.isResizable = false;
            this.isDraggable = false;
            this.isTraversable = false;

            //factory pattern
            //this.integrator = new CGSGParticleIntegratorEuler();
            //this.integrator = new CGSGParticleIntegratorRK4();
        },

        /**
         * Add a force to all emitters
         * @public
         * @method addForce
         * @param {CGSGVector2D} vector
         */
        addForce : function(vector) {
            for (var e = 0 ; e < this.emitters.length ; e++) {
                this.emitters[e].addForce(vector);
            }
        },

        /*addImpulse : function(point, force, ttl) {
         for (var e = 0; e < this.emitters.length; e++) {
         this.emitters[e].addImpulse(point, force, ttl);
         }
         },*/

        /**
         * Create a new emitter and return it
         * @public
         * @method addEmitter
         * @param {CGSGNode} node
         * @param {CGSGRegion} region
         * @param {Number} nbParticlesMax
         * @param {Number} velocity
         * @param {Number} angle
         * @param {Number} speed
         * @param {Number} speedThreshold
         * @param {Number} outflow
         * @return {CGSGParticleEmitter}
         */
        addEmitter : function(node, region, nbParticlesMax, velocity, angle, speed, speedThreshold, outflow) {
            var emitter = new CGSGParticleEmitter(node, region, nbParticlesMax, velocity, angle, speed, speedThreshold,
                                                  outflow);
            this.addChild(emitter);
            this.emitters.push(emitter);
            return emitter;
        },

        /**
         * Remove the emitter passed in parameter
         * @public
         * @method removeEmitter
         * @param {CGSGParticleEmitter} emitter
         */
        removeEmitter : function(emitter) {
            this.emitters.without(emitter);
            this.removeChild(emitter, true);
        },

        /**
         * @public
         * @method addAttractor
         * @param {CGSGPosition} position
         * @param {Number} strength
         * @param {Number} distance
         * @return {Object}
         */
        addAttractor : function(position, strength, distance) {
            var attractor = {
                position : position,
                strength : strength,
                distance : distance
            };
            this.attractors.push(attractor);
            return attractor;
        },

        /**
         * @public
         * @method removeAttractor
         * @param {Object} attractor
         */
        removeAttractor : function(attractor) {
            this.attractors.without(attractor);
        },

        /**
         * @public
         * @method addRepulsor
         * @param {CGSGPosition} position
         * @param {Number} strength
         * @param {Number} distance
         * @return {Object}
         */
        addRepulsor : function(position, strength, distance) {
            var repulsor = {
                position : position,
                strength : strength,
                distance : distance
            };
            this.repulsors.push(repulsor);
            return repulsor;
        },

        /**
         * @public
         * @method removeRepulsor
         * @param {Object} repulsor
         */
        removeRepulsor : function(repulsor) {
            this.repulsors.without(repulsor);
        },

        /**
         * override the CGSGNode 'pickNode' method to return null due to performance
         * @protected
         * @method pickNode
         * @param mousePosition
         * @param absoluteScale
         * @param ghostContext
         * @param recursively
         * @param condition
         * @return {*}
         */
        pickNode : function(mousePosition, absoluteScale, ghostContext, recursively, condition) {
            return null;
        },

        /**
         * @public
         * @method copy
         * @todo : fill the method
         * @return {CGSGParticleSystem}
         */
        copy : function() {
            var node = new CGSGParticleSystem();
            node = this._super(node);

            return node;
        }
    }
);