/* <copyright>
This file contains proprietary software owned by Motorola Mobility, Inc.<br/>
No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/>
(c) Copyright 2011 Motorola Mobility, Inc.  All Rights Reserved.
</copyright> */

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++;
    }
};