diff --git a/examples/canvas_particles_shapes.html b/examples/canvas_particles_shapes.html new file mode 100644 index 00000000..1622dbf6 --- /dev/null +++ b/examples/canvas_particles_shapes.html @@ -0,0 +1,334 @@ + + + + three.js canvas/webgl - geometry - text + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/js/Sparks.js b/examples/js/Sparks.js new file mode 100644 index 00000000..b673053e --- /dev/null +++ b/examples/js/Sparks.js @@ -0,0 +1,600 @@ +/* + * @author zz85 (http://github.com/zz85 http://www.lab4games.net/zz85/blog) + * + * a simple to use javascript 3d particles system inspired by FliNT and Stardust + * created with TWEEN.js and THREE.js + * + * for feature requests or bugs, please visit https://github.com/zz85/sparks.js + * + * licensed under the MIT license + */ + +var SPARKS = {}; + +/******************************** +* Emitter Class +* +* Creates and Manages Particles +*********************************/ + + +SPARKS.Emitter = function (counter) { + + this._counter = counter ? counter : new SPARKS.SteadyCounter(10); // provides number of particles to produce + + this._particles = []; + + + this._initializers = []; // use for creation of particles + this._actions = []; // uses action to update particles + this._activities = []; // not supported yet + + this._handlers = []; + + this.callbacks = {}; +}; + + +SPARKS.Emitter.prototype = { + + _TIMESTEP: 15, + _timer: null, + _lastTime: null, + _timerStep: 10, + + // run its built in timer / stepping + start: function() { + this._lastTime = Date.now(); + this._timer = setTimeout(this.step, this._timerStep, this); + this._isRunning = true; + }, + + stop: function() { + this._isRunning = false; + clearTimeout(this._timer); + }, + + isRunning: function() { + return this._isRunning & true; + }, + + // Step gets called upon by the engine + // but attempts to call update() on a regular basics + // This method is also described in http://gameclosure.com/2011/04/11/deterministic-delta-tee-in-js-games/ + step: function(emitter) { + + var time = Date.now(); + var elapsed = time - emitter._lastTime; + + while(elapsed >= emitter._TIMESTEP) { + emitter.update(emitter._TIMESTEP / 1000); + elapsed -= emitter._TIMESTEP; + } + + emitter._lastTime = time - elapsed; + + if (emitter._isRunning) + setTimeout(emitter.step, emitter._timerStep, emitter); + + }, + + + // Update particle engine in seconds, not milliseconds + update: function(time) { + + var len = this._counter.updateEmitter( this, time ); + + // Create particles + for( i = 0; i < len; i++ ) { + this.createParticle(); + } + + // Update activities + len = this._activities.length; + for ( i = 0; i < len; i++ ) + { + this_.activities[i].update( this, time ); + } + + + len = this._actions.length; + var action; + var len2 = this._particles.length; + + for( j = 0; j < len; j++ ) + { + action = this._actions[j]; + for ( i = 0; i < len2; ++i ) + { + particle = this._particles[i]; + action.update( this, particle, time ); + } + } + + + // remove dead particles + for ( i = len2; i--; ) + { + particle = this._particles[i]; + if ( particle.isDead ) + { + //particle = + this._particles.splice( i, 1 ); + this.dispatchEvent("dead", particle); + SPARKS.VectorPool.release(particle.position); // + SPARKS.VectorPool.release(particle.velocity); + + } else { + this.dispatchEvent("updated", particle); + } + } + + }, + + createParticle: function() { + var particle = new SPARKS.Particle(); + // In future, use a Particle Factory + var len = this._initializers.length, i; + + for ( i = 0; i < len; i++ ) { + this._initializers[i].initialize( this, particle ); + } + + this._particles.push( particle ); + + this.dispatchEvent("created", particle); // ParticleCreated + + return particle; + }, + + addInitializer: function (initializer) { + this._initializers.push(initializer); + }, + + addAction: function (action) { + this._actions.push(action); + }, + + addCallback: function(name, callback) { + this.callbacks[name] = callback; + }, + + dispatchEvent: function(name, args) { + var callback = this.callbacks[name]; + if (callback) { + callback(args); + } + + } + + +}; + + +// Number of particles per seconds +SPARKS.SteadyCounter = function(rate) { + this.rate = rate; + +}; + +SPARKS.SteadyCounter.prototype.updateEmitter = function(emitter, time) { + + return Math.floor(time * this.rate); +}; + + + + +/******************************** +* Particle Class +* +* Represents a single particle +*********************************/ +SPARKS.Particle = function() { + + /** + * The lifetime of the particle, in seconds. + */ + this.lifetime = 0; + + /** + * The age of the particle, in seconds. + */ + this.age = 0; + + /** + * The energy of the particle. + */ + this.energy = 1; + + /** + * Whether the particle is dead and should be removed from the stage. + */ + this.isDead = false; + + this.target = null; // tag + + /** + * For 3D + */ + + this.position = SPARKS.VectorPool.get().set(0,0,0); //new THREE.Vector3( 0, 0, 0 ); + this.velocity = SPARKS.VectorPool.get().set(0,0,0); //new THREE.Vector3( 0, 0, 0 ); + // rotation vec3 + // angVelocity vec3 + // faceAxis vec3 + +}; + + +/******************************** +* Action Classes +* +* An abstract class which have +* update function +*********************************/ +SPARKS.Action = function() { + this._priority = 0; +}; + + +SPARKS.Age = function(easing) { + this._easing = (easing == null) ? TWEEN.Easing.Linear.EaseNone : easing; +}; + +SPARKS.Age.prototype.update = function (emitter, particle, time) { + particle.age += time; + if( particle.age >= particle.lifetime ) + { + particle.energy = 0; + particle.isDead = true; + } + else + { + var t = this._easing(particle.age / particle.lifetime); + particle.energy = -1 * t + 1; + } +}; + +/* +// Mark particle as dead when particle's < 0 + +SPARKS.Death = function(easing) { + this._easing = (easing == null) ? TWEEN.Linear.EaseNone : easing; +}; + +SPARKS.Death.prototype.update = function (emitter, particle, time) { + if (particle.life <= 0) { + particle.isDead = true; + } +}; +*/ + + +SPARKS.Move = function() { + +}; + +SPARKS.Move.prototype.update = function(emitter, particle, time) { + + var p = particle.position; + var v = particle.velocity; + + p.x += v.x * time; + p.y += v.y * time; + p.z += v.z * time; + +}; + + +SPARKS.Accelerate = function(x,y,z) { + + if (x instanceof THREE.Vector3) { + this.acceleration = x; + return; + } + + this.acceleration = new THREE.Vector3(x,y,z); + +}; + +SPARKS.Accelerate.prototype.update = function(emitter, particle, time) { + var acc = this.acceleration; + + var v = particle.velocity; + + v.x += acc.x * time; + v.y += acc.y * time; + v.z += acc.z * time; + +}; + +/* Set the max ammount of x,y,z drift movements in a second */ +SPARKS.RandomDrift = function(x,y,z) { + if (x instanceof THREE.Vector3) { + this.drift = x; + return; + } + + this.drift = new THREE.Vector3(x,y,z); +} + + +SPARKS.RandomDrift.prototype.update = function(emitter, particle, time) { + var drift = this.drift; + + var v = particle.velocity; + + v.x += ( Math.random() - 0.5 ) * drift.x * time; + v.y += ( Math.random() - 0.5 ) * drift.y * time; + v.z += ( Math.random() - 0.5 ) * drift.z * time; + +}; + +/******************************** +* Zone Classes +* +* An abstract classes which have +* getLocation() function +*********************************/ +SPARKS.Zone = function() { +}; + +// TODO, contains() for Zone + +SPARKS.PointZone = function(pos) { + this.pos = pos; +}; + +SPARKS.PointZone.prototype.getLocation = function() { + return this.pos; +}; + +SPARKS.PointZone = function(pos) { + this.pos = pos; +}; + +SPARKS.PointZone.prototype.getLocation = function() { + return this.pos; +}; + +SPARKS.LineZone = function(start, end) { + this.start = start; + this.end = end; + this._length = end.clone().subSelf( start ); +}; + +SPARKS.LineZone.prototype.getLocation = function() { + var len = this._length.clone(); + + len.multiplyScalar( Math.random() ); + return len.addSelf( this.start ); + +}; + +// Basically a RectangleZone + + +SPARKS.ParallelogramZone = function(corner, side1, side2) { + this.corner = corner; + this.side1 = side1; + this.side2 = side2; +}; + +SPARKS.ParallelogramZone.prototype.getLocation = function() { + + var d1 = this.side1.clone().multiplyScalar( Math.random() ); + var d2 = this.side2.clone().multiplyScalar( Math.random() ); + d1.addSelf(d2); + return d1.addSelf( this.corner ); + +}; + + +/** + * The constructor creates a DiscZone 3D zone. + * + * @param centre The point at the center of the disc. + * @param normal A vector normal to the disc. + * @param outerRadius The outer radius of the disc. + * @param innerRadius The inner radius of the disc. This defines the hole + * in the center of the disc. If set to zero, there is no hole. + */ + +/* +// BUGGY!! +SPARKS.DiscZone = function(center, radiusNormal, outerRadius, innerRadius) { + this.center = center; + this.radiusNormal = radiusNormal; + this.outerRadius = (outerRadius==undefined) ? 0 : outerRadius; + this.innerRadius = (innerRadius==undefined) ? 0 : innerRadius; + +}; + +SPARKS.DiscZone.prototype.getLocation = function() { + var rand = Math.random(); + var _innerRadius = this.innerRadius; + var _outerRadius = this.outerRadius; + var center = this.center; + var _normal = this.radiusNormal; + + _distToOrigin = _normal.dot( center ); + + var radius = _innerRadius + (1 - rand * rand ) * ( _outerRadius - _innerRadius ); + var angle = Math.random() * SPARKS.Utils.TWOPI; + + var _distToOrigin = _normal.dot( center ); + var axes = SPARKS.Utils.getPerpendiculars( _normal.clone() ); + var _planeAxis1 = axes[0]; + var _planeAxis2 = axes[1]; + + var p = _planeAxis1.clone(); + p.multiplyScalar( radius * Math.cos( angle ) ); + var p2 = _planeAxis2.clone(); + p2.multiplyScalar( radius * Math.sin( angle ) ); + p.addSelf( p2 ); + return _center.add( p ); + +}; +*/ + +SPARKS.SphereCapZone = function(x, y, z, minr, maxr, angle) { + this.x = x; + this.y = y; + this.z = z; + this.minr = minr; + this.maxr = maxr; + this.angle = angle; +}; + +SPARKS.SphereCapZone.prototype.getLocation = function() { + var theta = Math.PI *2 * SPARKS.Utils.random(); + var r = SPARKS.Utils.random(); + + //new THREE.Vector3 + var v = SPARKS.VectorPool.get().set(r * Math.cos(theta), -1 / Math.tan(this.angle * SPARKS.Utils.DEGREE_TO_RADIAN), r * Math.sin(theta)); + + //v.length = StardustMath.interpolate(0, _minRadius, 1, _maxRadius, Math.random()); + + var i = this.minr - ((this.minr-this.maxr) * Math.random() ); + v.multiplyScalar(i); + + v.__markedForReleased = true; + + return v; +}; + + +/******************************** +* Initializer Classes +* +* Classes which initializes +* particles. Implements initialize( emitter:Emitter, particle:Particle ) +*********************************/ + +// Specifies random life between max and min +SPARKS.Lifetime = function(min, max) { + this._min = min; + + this._max = max ? max : min; + +}; + +SPARKS.Lifetime.prototype.initialize = function( emitter/*Emitter*/, particle/*Particle*/ ) { + particle.lifetime = this._min + SPARKS.Utils.random() * ( this._max - this._min ); +}; + + +SPARKS.Position = function(zone) { + this.zone = zone; +}; + +SPARKS.Position.prototype.initialize = function( emitter/*Emitter*/, particle/*Particle*/ ) { + var pos = this.zone.getLocation(); + particle.position.set(pos.x, pos.y, pos.z); +}; + +SPARKS.Velocity = function(zone) { + this.zone = zone; +}; + +SPARKS.Velocity.prototype.initialize = function( emitter/*Emitter*/, particle/*Particle*/ ) { + var pos = this.zone.getLocation(); + particle.velocity.set(pos.x, pos.y, pos.z); + if (pos.__markedForReleased) { + //console.log("release"); + SPARKS.VectorPool.release(pos); + pos.__markedForReleased = false; + } +}; + +SPARKS.Target = function(target, callback) { + this.target = target; + this.callback = callback; +}; + +SPARKS.Target.prototype.initialize = function( emitter, particle) { + + if (this.callback) { + particle.target = this.callback(); + } else { + particle.target = this.target; + } + +}; + +/******************************** +* VectorPool +* +* Reuse much of Vectors if possible +*********************************/ + +SPARKS.VectorPool = { + __pools: [], + + // Get a new Vector + get: function() { + if (this.__pools.length>0) { + return this.__pools.pop(); + } + + return this._addToPool(); + + }, + + // Release a vector back into the pool + release: function(v) { + this.__pools.push(v); + }, + + // Create a bunch of vectors and add to the pool + _addToPool: function() { + //console.log("creating some pools"); + + for (var i=0, size = 100; i < size; i++) { + this.__pools.push(new THREE.Vector3()); + } + + return new THREE.Vector3(); + + } + + + +}; + + +/******************************** +* Util Classes +* +* Classes which initializes +* particles. Implements initialize( emitter:Emitter, particle:Particle ) +*********************************/ +SPARKS.Utils = { + random: function() { + return Math.random(); + }, + DEGREE_TO_RADIAN: Math.PI / 180, + TWOPI: Math.PI * 2, + + getPerpendiculars: function(normal) { + var p1 = this.getPerpendicular( normal ); + var p2 = normal.cross( p1 ); + p2.normalize(); + return [ p1, p2 ]; + }, + + getPerpendicular: function( v ) + { + if( v.x == 0 ) + { + return new THREE.Vector3D( 1, 0, 0 ); + } + else + { + var temp = new THREE.Vector3( v.y, -v.x, 0 ); + return temp.normalize(); + } + } + +};