/* Copyright (c) 2012, Motorola Mobility LLC. All Rights Reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of Motorola Mobility LLC nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ var RDGE = RDGE || {}; RDGE.particle = function (def, id) { this.id = id; this.def = def; if (this.def.numFrames == undefined) { if (this.def.textureSize && this.def.frameSize) { this.def.numFrames = (this.def.textureSize[0] / this.def.frameSize[0]) * (this.def.textureSize[1] / this.def.frameSize[1]); } else { this.def.numFrames = 0; } } this.pos = RDGE.vec3.zero(); this.delta = RDGE.vec3.zero(); this.rotate = 0.0; this.age = 0.0; this.lifespan = 0.0; this.velocity = RDGE.vec3.zero(); this.gravity = RDGE.vec3.zero(); this.frame = 0; this.frameCount = 0; this.lastPos = RDGE.vec3.zero(); this.state = 0; this.hide = false; this.color = RDGE.vec4.zero(); this.randomize = function (min, max) { return min + (max - min) * Math.random(); }; this.rate = this.randomize(-1.0, 1.0); this.randomize3 = function (min, max) { return [this.randomize(min[0], max[0]), this.randomize(min[1], max[1]), this.randomize(min[2], max[2])]; }; this.spawn = function (spawnMatrix) { if (this.def.initialframe == undefined) { this.frame = this.id % this.def.numFrames; } else { this.frame = this.randomize(this.def.initialframe[0], this.def.initialframe[1]); } this.pos = this.randomize3(this.def.initialpos[0], this.def.initialpos[1]); if (this.def.worldSpace) { // calculate the initial position in world space. this.pos = RDGE.mat4.transformPoint(spawnMatrix, this.pos); } // all other values are assumed to be defined in local or world space depending on // the particles worldSpace designation. var toRadians = Math.PI / 180.0; if (this.def.initialsize) { this.size = this.randomize(this.def.initialsize[0], this.def.initialsize[1]); } else { this.size = 1.0; } this.velocity = this.randomize3(this.def.initialvel[0], this.def.initialvel[1]); this.gravity = [this.def.gravity[0], this.def.gravity[1], this.def.gravity[2]]; this.rotate = this.randomize(this.def.initialrot[0] * toRadians, this.def.initialrot[1] * toRadians); this.lifespan = this.randomize(this.def.lifespan[0], this.def.lifespan[1]); this.age = 0.0; this.delta = [0.0, 0.0, 0.0]; this.lastPos = RDGE.vec3.add(this.pos, RDGE.vec3.scale(this.velocity, -1.0 / 30.0)); this.color = [1, 1, 1, 1]; //RDGE.vec4.random( [0.0, 0.0, 0.0, 1.0], [1.0, 1.0, 1.0, 1.0] ); }; }; // double buffered array utility class RDGE.DoubleBuffer = function (arrType, size) { this.buffer = {}; this.buffer[0] = new arrType(size); this.buffer[1] = new arrType(size); this.bufferIndex = 0; this.flip = function () { this.bufferIndex = 1 - this.bufferIndex; } this.front = function () { return this.buffer[this.bufferIndex]; } this.back = function () { return this.buffer[1 - this.bufferIndex]; } }; // cycling buffer RDGE.particleBuffer = function (pdef, emitter, size) { var renderer = RDGE.globals.engine.getContext().renderer; var ctx = renderer.ctx; s_particleShader = new RDGE.jshader(); s_particleShader.def = { 'shaders': { 'defaultVShader': "assets/shaders/particle_vshader.glsl", 'defaultFShader': "assets/shaders/particle_fshader.glsl" }, 'techniques': { 'defaultTechnique': [{ 'vshader': 'defaultVShader', 'fshader': 'defaultFShader', 'attributes': { 'a_pos': { 'type': 'vec4' }, 'a_posId': { 'type': 'float' }, 'a_rotation': { 'type': 'float' }, 'a_size': { 'type': 'float' }, 'a_color': { 'type': 'vec4' } }, 'params': { 'u_projMatrix': { 'type': 'mat4' }, 'u_viewMatrix': { 'type': 'mat4' }, 'u_worldMatrix': { 'type': 'mat4' }, 'u_particleSizeX': { 'type': 'vec4' }, 'u_particleSizeY': { 'type': 'vec4' }, 'u_particleRot': { 'type': 'vec4' }, 'u_particleColors': { 'type': 'mat4' }, 'u_textureSize': { 'type': 'vec2' }, 'u_frameSize': { 'type': 'vec2' }, 's_texture0': { 'type': 'tex2d' } // 's_texture1' : { 'type' : 'tex2d' } } }] } } s_particleShader.init(); s_particleTextures = {}; this.shader = s_particleShader; this.owner = emitter; if (pdef.texture && s_particleTextures[pdef.texture] == undefined) { s_particleTextures[pdef.texture] = renderer.createTexture(pdef.texture); if (!pdef.textureSize || !pdef.frameSize) { pdef.textureSize = []; pdef.textureSize[0] = s_particleTextures[pdef.texture].image.width; pdef.textureSize[1] = s_particleTextures[pdef.texture].image.height; } if (!pdef.frameSize) { pdef.frameSize = []; pdef.frameSize[0] = s_particleTextures[pdef.texture].image.width; pdef.frameSize[1] = s_particleTextures[pdef.texture].image.height; } } if (pdef.texture2 && s_particleTextures[pdef.texture2] == undefined) { s_particleTextures[pdef.texture2] = renderer.createTexture(pdef.texture2); } this.texture = s_particleTextures[pdef.texture]; this.texture2 = s_particleTextures[pdef.texture2]; this.bounds = {}; this.bounds.min = RDGE.vec3.zero(); this.bounds.max = RDGE.vec3.zero(); this.srcBlend = pdef.srcBlend; this.dstBlend = pdef.dstBlend; this.particles = new Array(); this.posBuffer = new RDGE.DoubleBuffer(Float32Array, 16 * size); // 4 positions per particle, 4 components per position (particle age in w) this.posIdBuffer = new Float32Array(4 * size); this.sizeBuffer = new RDGE.DoubleBuffer(Float32Array, 4 * size); this.rotBuffer = new RDGE.DoubleBuffer(Float32Array, 4 * size); this.colorBuffer = new RDGE.DoubleBuffer(Float32Array, 16 * size); this.indexBuffer = new RDGE.DoubleBuffer(Uint16Array, 6 * size); this.indexBuffer.front().numIndices = 0; this.indexBuffer.back().numIndices = 0; for (i = 0; i < size; ++i) { this.particles.push(new RDGE.particle(pdef, i)); // initialize double buffers. // the first pass will init the front buffers. // the second pass will init the back buffers. for (j = 0; j < 2; ++j) { // init position buffer var pfb = this.posBuffer.front(); var i16 = i * 16; for (j = 0; j < 4; ++j) { var cmpBaseIndex = i16 + j * 4; pfb[cmpBaseIndex + 0] = 0; pfb[cmpBaseIndex + 1] = 0; pfb[cmpBaseIndex + 2] = 0; pfb[cmpBaseIndex + 3] = 1; } // init rotation buffer var i4 = i * 4; var rfb = this.rotBuffer.front(); rfb[i4 + 0] = 0; rfb[i4 + 1] = 0; rfb[i4 + 2] = 0; rfb[i4 + 3] = 0; // init rotation buffer var i4 = i * 4; var sfb = this.sizeBuffer.front(); rfb[i4 + 0] = 1; rfb[i4 + 1] = 1; rfb[i4 + 2] = 1; rfb[i4 + 3] = 1; // init color buffer var i4 = i * 4; var cfb = this.colorBuffer.front(); cfb[i4] = 0xFFFFFF; // init index buffer var ifb = this.indexBuffer.front(); var i6 = i * 6; ifb[i6 + 0] = i4 + 1; ifb[i6 + 1] = i4 + 0; ifb[i6 + 2] = i4 + 3; ifb[i6 + 3] = i4 + 1; ifb[i6 + 4] = i4 + 3; ifb[i6 + 5] = i4 + 2; // flip buffers this.posBuffer.flip(); this.rotBuffer.flip(); this.indexBuffer.flip(); } var i4 = i * 4; this.posIdBuffer[i4 + 0] = 0; this.posIdBuffer[i4 + 1] = 1; this.posIdBuffer[i4 + 2] = 2; this.posIdBuffer[i4 + 3] = 3; } this.posBufferObject = ctx.createBuffer(); ctx.bindBuffer(ctx.ARRAY_BUFFER, this.posBufferObject); ctx.bufferData(ctx.ARRAY_BUFFER, this.posBuffer.front(), ctx.DYNAMIC_DRAW); this.posIdBufferObject = ctx.createBuffer(); ctx.bindBuffer(ctx.ARRAY_BUFFER, this.posIdBufferObject); ctx.bufferData(ctx.ARRAY_BUFFER, this.posIdBuffer, ctx.DYNAMIC_DRAW); this.rotBufferObject = ctx.createBuffer(); ctx.bindBuffer(ctx.ARRAY_BUFFER, this.rotBufferObject); ctx.bufferData(ctx.ARRAY_BUFFER, this.rotBuffer.front(), ctx.DYNAMIC_DRAW); this.sizeBufferObject = ctx.createBuffer(); ctx.bindBuffer(ctx.ARRAY_BUFFER, this.sizeBufferObject); ctx.bufferData(ctx.ARRAY_BUFFER, this.sizeBuffer.front(), ctx.DYNAMIC_DRAW); this.colorBufferObject = ctx.createBuffer(); ctx.bindBuffer(ctx.ARRAY_BUFFER, this.colorBufferObject); ctx.bufferData(ctx.ARRAY_BUFFER, this.colorBuffer.front(), ctx.DYNAMIC_DRAW); this.indexBufferObject = ctx.createBuffer(); ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, this.indexBufferObject); ctx.bufferData(ctx.ELEMENT_ARRAY_BUFFER, this.indexBuffer.front(), ctx.DYNAMIC_DRAW); this.start = 0; this.end = 0; this.cap = size; this.kill = function () { this.start++; if (this.start > (this.cap - 1)) this.start = 0; }; this.emit = function (parent) { this.end++; if (this.end >= this.cap) { this.end = 0; } if (this.end == this.start) { this.kill(); } this.particles[this.end].spawn(this.owner.world); }; this.sync = function () { this.posBuffer.flip(); this.rotBuffer.flip(); this.sizeBuffer.flip(); this.colorBuffer.flip(); this.indexBuffer.flip(); // update the back buffer. var dstPosBuffer = this.posBuffer.back(); var dstRotBuffer = this.rotBuffer.back(); var dstSizeBuffer = this.sizeBuffer.back(); var dstColorBuffer = this.colorBuffer.back(); var dstIdxBuffer = this.indexBuffer.back(); var bmin = this.bounds.min; var bmax = this.bounds.max; bmin[0] = 1e10; bmin[1] = 1e10; bmin[2] = 1e10; bmax[0] = -1e10; bmax[1] = -1e10; bmax[2] = -1e10; var numIndices = 0; var i = this.start; while (1) { var x, y, z, w; var p = this.particles[i]; var age = (p.age / p.lifespan); // normalized age // combine frame number and age and store in w. // to decode : // frame = floor( w ); // age = fract( w ); var pw = Math.min(age, 0.999) + Math.floor(p.frame); if (age < 1.0) { // || (pdef.persist != undefined && pdef.persist == true)) { var px = p.pos[0]; var py = p.pos[1]; var pz = p.pos[2]; if (px > bmax[0]) { bmax[0] = px; } if (px > bmax[1]) { bmax[1] = py; } if (px > bmax[2]) { bmax[2] = pz; } if (px < bmin[0]) { bmin[0] = px; } if (px < bmin[1]) { bmin[1] = py; } if (px < bmin[2]) { bmin[2] = pz; } var i16 = i * 16; for (j = 0; j < 4; ++j) { var cmpBaseIndex = i16 + j * 4; dstPosBuffer[cmpBaseIndex + 0] = px; dstPosBuffer[cmpBaseIndex + 1] = py; dstPosBuffer[cmpBaseIndex + 2] = pz; dstPosBuffer[cmpBaseIndex + 3] = pw; } var i4 = i * 4; var r = 0.0; if (pdef.velocityAligned) { r = Math.atan2(p.delta[0], p.delta[1]); } dstRotBuffer[i4 + 0] = p.rotate + r; dstRotBuffer[i4 + 1] = p.rotate + r; dstRotBuffer[i4 + 2] = p.rotate + r; dstRotBuffer[i4 + 3] = p.rotate + r; dstSizeBuffer[i4 + 0] = p.size; dstSizeBuffer[i4 + 1] = p.size; dstSizeBuffer[i4 + 2] = p.size; dstSizeBuffer[i4 + 3] = p.size; var i16 = i * 16; for (j = 0; j < 4; ++j) { var cmpBaseIndex = i16 + j * 4; dstColorBuffer[cmpBaseIndex + 0] = p.color[0]; dstColorBuffer[cmpBaseIndex + 1] = p.color[1]; dstColorBuffer[cmpBaseIndex + 2] = p.color[2]; dstColorBuffer[cmpBaseIndex + 3] = p.color[3]; } dstIdxBuffer[numIndices + 0] = i4 + 1; dstIdxBuffer[numIndices + 1] = i4 + 0; dstIdxBuffer[numIndices + 2] = i4 + 3; dstIdxBuffer[numIndices + 3] = i4 + 1; dstIdxBuffer[numIndices + 4] = i4 + 2; dstIdxBuffer[numIndices + 5] = i4 + 3; numIndices += 6; } if (i == this.end) { break; } i++; if (i == this.cap && this.end != this.cap) { i = 0; } } dstIdxBuffer.numIndices = numIndices; if (!pdef.worldSpace) { // calculate the world space bounds from local space. // first transform our local space min-max coordinates to world space. var bminW = RDGE.mat4.mul(bmin, this.owner.world); var bmaxW = RDGE.mat4.mul(bmax, this.owner.world); // now calculate axis aligned bounds based on the world space coordinates. if (bmaxW[0] > bmax[0]) { bmax[0] = bmaxW[0]; } if (bmaxW[1] > bmax[1]) { bmax[1] = bmaxW[1]; } if (bmaxW[2] > bmax[2]) { bmax[2] = bmaxW[2]; } if (bminW[0] < bmin[0]) { bmin[0] = bminW[0]; } if (bminW[1] < bmin[1]) { bmin[1] = bminW[1]; } if (bminW[2] < bmin[2]) { bmin[2] = bminW[2]; } } }; this.update = function (dt, movers) { var i = this.start; if (pdef.persist != undefined && pdef.persist == false) { while (1) { var p = this.particles[i]; if (p.age < p.lifespan) break; this.kill(); if (i == this.end) { break; } i++; if (i == this.cap && this.end != this.cap) { i = 0; } } } i = this.start; while (1) { var p = this.particles[i]; var j = movers.length; while (j) { movers[--j].move(p, dt); } if (i == this.end) { break; } i++; if (i == this.cap && this.end != this.cap) { i = 0; } } this.sync(); }; this.render = function () { var renderer = RDGE.globals.engine.getContext().renderer; var ctx = RDGE.globals.engine.getContext().renderer.ctx; // draw using the front buffer. var srcPosBuffer = this.posBuffer.front(); var srcRotBuffer = this.rotBuffer.front(); var srcSizeBuffer = this.sizeBuffer.front(); var srcColBuffer = this.colorBuffer.front(); var srcIdxBuffer = this.indexBuffer.front(); if (srcIdxBuffer.numIndices > 0) { // set modelview matrix var shaderparms = this.shader.defaultTechnique; var activeCam = renderer.cameraManager().getActiveCamera(); if (pdef.worldSpace) { shaderparms.u_viewMatrix.set(activeCam.view); shaderparms.u_worldMatrix.set(RDGE.mat4.identity()); } else { shaderparms.u_viewMatrix.set(activeCam.view); shaderparms.u_worldMatrix.set(this.owner.world); // shaderparms.u_mvMatrix.set(RDGE.mat4.mul(this.owner.world, activeCam.view)); } shaderparms.u_projMatrix.set(activeCam.proj); shaderparms.u_particleSizeX.set(pdef.sizeX); shaderparms.u_particleSizeY.set(pdef.sizeY); shaderparms.u_particleRot.set(RDGE.vec4.scale(pdef.rotation, Math.PI / 180.0)); shaderparms.u_particleColors.set(RDGE.mat4.transpose(pdef.colors)); shaderparms.u_textureSize.set(pdef.textureSize); shaderparms.u_frameSize.set(pdef.frameSize); shaderparms.s_texture0.set(this.texture); // shaderparms.s_texture1.set(null); var passCount = this.shader.begin(); for (passIdx = 0; passIdx < passCount; ++passIdx) { this.shader.beginPass(passIdx); // setup render states if (!pdef.depthTest) { ctx.disable(ctx.DEPTH_TEST); } ctx.enable(ctx.BLEND); ctx.blendFunc(ctx[pdef.srcBlend], ctx[pdef.dstBlend]); ctx.enableVertexAttribArray(0); ctx.enableVertexAttribArray(1); ctx.enableVertexAttribArray(2); ctx.enableVertexAttribArray(3); ctx.enableVertexAttribArray(4); // update position buffer ctx.bindBuffer(ctx.ARRAY_BUFFER, this.posBufferObject); ctx.bufferSubData(ctx.ARRAY_BUFFER, 0, srcPosBuffer); ctx.bindBuffer(ctx.ARRAY_BUFFER, this.posBufferObject); ctx.vertexAttribPointer(0, 4, ctx.FLOAT, false, 0, 0); // position id buffer ctx.bindBuffer(ctx.ARRAY_BUFFER, this.posIdBufferObject); ctx.vertexAttribPointer(1, 1, ctx.FLOAT, false, 0, 0); // update rotation buffer ctx.bindBuffer(ctx.ARRAY_BUFFER, this.rotBufferObject); ctx.bufferSubData(ctx.ARRAY_BUFFER, 0, srcRotBuffer); ctx.bindBuffer(ctx.ARRAY_BUFFER, this.rotBufferObject); ctx.vertexAttribPointer(2, 1, ctx.FLOAT, false, 0, 0); // update size buffer ctx.bindBuffer(ctx.ARRAY_BUFFER, this.sizeBufferObject); ctx.bufferSubData(ctx.ARRAY_BUFFER, 0, srcSizeBuffer); ctx.bindBuffer(ctx.ARRAY_BUFFER, this.sizeBufferObject); ctx.vertexAttribPointer(3, 1, ctx.FLOAT, false, 0, 0); // update color buffer ctx.bindBuffer(ctx.ARRAY_BUFFER, this.colorBufferObject); ctx.bufferSubData(ctx.ARRAY_BUFFER, 0, srcColBuffer); ctx.bindBuffer(ctx.ARRAY_BUFFER, this.colorBufferObject); ctx.vertexAttribPointer(4, 4, ctx.FLOAT, false, 0, 0); // update index buffer ctx.bindBuffer(ctx.ELEMENT_ARRAY_BUFFER, this.indexBufferObject); ctx.bufferSubData(ctx.ELEMENT_ARRAY_BUFFER, 0, srcIdxBuffer); // draw ctx.drawElements(ctx.TRIANGLES, srcIdxBuffer.numIndices, ctx.UNSIGNED_SHORT, 0); // restore render states ctx.disable(ctx.BLEND); if (!pdef.depthTest) { ctx.enable(ctx.DEPTH_TEST); } ctx.blendFunc(ctx.ONE, ctx.ZERO); this.shader.endPass(); } this.shader.end(); } }; }; particleMoverDefault = function () { this.move = function (particle, dt) { particle.age += dt; if (particle.def.persist) { if (particle.def.cycle && particle.age > particle.lifespan * 0.99) { particle.age -= particle.lifespan * 0.99; } else { particle.age = Math.min(particle.age, particle.lifespan * 0.99); } } else { particle.age = Math.min(particle.age, particle.lifespan); } if (particle.def.frameRate > 0.0) { particle.frame += particle.def.frameRate * dt; //Math.min( particle.def.numFrames - 1, particle.frame + particle.def.frameRate * dt ); if (particle.def.frameLoop) { particle.frame = particle.frame % particle.def.numFrames; } else { particle.frame = Math.min(particle.def.numFrames - 1, particle.frame) } } var oldPos = particle.pos; var pg = particle.gravity; var pv = particle.velocity; var pp = particle.pos; var pd = particle.delta; pv[0] += pg[0] * dt; pv[1] += pg[1] * dt; pv[2] += pg[2] * dt; pp[0] = pp[0] + pv[0] * dt; pp[1] = pp[1] + pv[1] * dt; pp[2] = pp[2] + pv[2] * dt; pd[0] = pp[0] - oldPos[0]; pd[1] = pp[1] - oldPos[1]; pd[2] = pp[2] - oldPos[2]; // particle turbulence if (particle.def.turbulence) { var tMin = particle.def.turbulence[0]; var tMax = particle.def.turbulence[1]; var tx = Math.random() * (tMax[0] - tMin[0]) + tMin[0]; var ty = Math.random() * (tMax[1] - tMin[1]) + tMin[1]; var tz = Math.random() * (tMax[2] - tMin[2]) + tMin[2]; pv[0] += tx; pv[1] += ty; pv[2] += tz; } // particle jitter if (particle.def.jitter) { var jMin = particle.def.jitter[0]; var jMax = particle.def.jitter[1]; var jx = Math.random() * (jMax[0] - jMin[0]) + jMin[0]; var jy = Math.random() * (jMax[1] - jMin[1]) + jMin[1]; var jz = Math.random() * (jMax[2] - jMin[2]) + jMin[2]; pp[0] += jx; pp[1] += jy; pp[2] += jz; } }; }; RDGE.particleEmitter = function (def) { this.def = def; this.world = RDGE.mat4.identity(); this.pbuffer = new RDGE.particleBuffer(this.def.particle, this, this.def.maxParticles); this.movers = new Array(); var creatorFunc = particleMoverDefault; if (def.particle.moverFunc) { creatorFunc = eval(def.particle.moverFunc); } this.movers.push(new creatorFunc()); this.localTime = 0.0; this.lastEmitTime = 0.0; this.emitCounter = 0; this.firstUpdate = true; this.attachToNode = function (node) { this.controller = node; }; this.update = function (dt) { this.localTime += dt; if (this.firstUpdate) { this.lastEmitTime = this.localTime; this.firstUpdate = false; } if (this.def.emit != undefined && this.def.emit == false) { this.lastEmitTime = this.localTime - 1.0 / this.def.emitRate; } // this needs to be handled differently.. var maxParticles = this.def.maxParticles; var emitOnce = this.def.emitOnce; var emitCount = (this.def.emit == undefined || this.def.emit == true) ? Math.floor((this.localTime - this.lastEmitTime) * this.def.emitRate) : 0; while (emitCount--) { if (emitOnce && this.emitCounter++ >= maxParticles) { break; } this.pbuffer.emit(this.world); this.lastEmitTime = this.localTime; } this.pbuffer.update(dt, this.movers); }; }; RDGE.particleSys = function (addr) { loaded = (typeof loaded == 'undefined') ? {} : loaded; this.def = null; this.node = null; this.world = RDGE.mat4.identity(); this.emitters = {}; // load particle system definition at addr this.init = function () { if (this.def == null) { return; } for (e in this.def.emitters) { this.emitters[e] = new RDGE.particleEmitter(this.def.emitters[e]); } this.bounds = {}; this.bounds.min = RDGE.vec3.zero(); this.bounds.max = RDGE.vec3.zero(); }; if (!loaded[addr]) { var request = new XMLHttpRequest(); request.sender = this; request.onreadystatechange = function () { if (request.readyState == 4) { if (request.status == 200 || window.location.href.indexOf("http") == -1) { request.sender.def = eval("(" + request.responseText + ")"); //retrieve result as an JavaScript object loaded[addr] = request.sender.def; request.sender.init(); } else { alert("An error has occured loading particle system."); } } } request.open("GET", addr, true); request.send(null); } else { this.def = loaded[addr]; this.init(); } this.update = function (dt) { if (this.def == null) { return; } var bmin = this.bounds.min; var bmax = this.bounds.max; bmin[0] = 1e10; bmin[1] = 1e10; bmin[2] = 1e10; bmax[0] = -1e10; bmax[1] = -1e10; bmax[2] = -1e10; var parent = this.node ? this.node.world : this.world; for (em in this.emitters) { var emitter = this.emitters[em]; emitter.world = parent; emitter.update(dt); var emin = emitter.pbuffer.bounds.min; var emax = emitter.pbuffer.bounds.max; // calculate a bounds that fits all particles. if (emin[0] < bmin[0]) { bmin[0] = emin[0]; } if (emin[1] < bmin[1]) { bmin[1] = emin[1]; } if (emin[2] < bmin[2]) { bmin[2] = emin[2]; } if (emax[0] > bmax[0]) { bmax[0] = emax[0]; } if (emax[1] > bmax[1]) { bmax[1] = emax[1]; } if (emax[2] > bmax[2]) { bmax[2] = emax[2]; } } }; this.render = function () { if (this.def == null) { return; } for (em in this.emitters) { var pbuffer = this.emitters[em].pbuffer; pbuffer.render(); } }; this.attachToNode = function (node) { this.node = node; }; }; RDGE.g_particleSystemManager = new RDGE.objectManager(); RDGE.g_particleSystemManager.update = function (dt) { var i = this.objects.length - 1; while (i >= 0) { var psys = this.objects[i]; if (psys) { psys.update(dt); } i--; } }; RDGE.g_particleSystemManager.render = function (dt) { var i = 0; while (i < this.objects.length) { var psys = this.objects[i]; if (psys) { psys.render(); } i++; } };