/* <copyright>
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.
</copyright> */

// RDGE namespaces
var RDGE = RDGE || {};

// default render proc
// render scene nodes
RDGE.DefaultRender = function () {
    // setup the default shader
    this.renderer = RDGE.globals.engine.getContext().renderer;
    //this.shaderProgram = this.renderer.defaultShader;
    this.jshaderProgram = new RDGE.jshader();
    this.jshaderProgram.def = this.renderer.defaultShaderDefintion;
    this.jshaderProgram.init();
};

RDGE.DefaultRender.prototype.process = function (context, trNode, parent) {
};

function renderObject(trNode, renderCtx, parent) {
    this.node = trNode ? trNode : RDGE.createTransformNode();
    this.context = renderCtx ? renderCtx : new RDGE.RenderContext();
    this.parent = parent ? parent : null;
};


// Scene graph contains set of tools for manipulating the graph
RDGE.SceneGraph = function (scene) {
    if (scene == undefined || scene == null)
        scene = {};

    this.scene = scene;

    if (this.scene.root != undefined)
        this.scene = this.scene.root;
    else
        this.scene = { 'children': [] };

    this.tick = 0;
    this.lastTick = -1;
    this.frustumCulling = true;

    // the main light for the scene - also casts shadows
    this.mainLight = new RDGE.shadowLight();

    this.bckTypes = RDGE.rdgeConstants.categoryEnumeration;

    this.renderList = new Array(this.bckTypes.MAX_CAT);
    this.renderList[this.bckTypes.BACKGROUND] = []; //BACKGROUND
    this.renderList[this.bckTypes.OPAQUE] = []; //OPAQUE
    this.renderList[this.bckTypes.TRANSPARENT] = []; //TRANSPARENT
    this.renderList[this.bckTypes.ADDITIVE] = []; //ADDITIVE
    this.renderList[this.bckTypes.TRANSLUCENT] = []; //TRANSLUCENT
    this.renderList[this.bckTypes.FOREGROUND] = []; //FOREGROUND

    // a list of shadow geometry
    this.shadowRenderList = [];

    // scene traversal functor for creating a culled list of shadow geometry
    this.shadowCuller = null;

    // define passes to render geometry and handle post-processing
    this.defaultPassDef =
    {
        // a pass can have children that will receive their parents output as input

        // this pass renders the depth map to an off-screen target - from the shadow lights view
        // you can specify what your output should be
        // @param name      - this tells jshader's of child passes (which receive the parents output as input)
        //                    what the sampler2d uniform name will be for this output texture
        // @param type      - the type of output could be tex2d or target
        // @param width     - optional width of the render target
        // @param height    - optional height of the render target
        // @param mips      - optional flag indicating whether the render target will support mip-mapping

        // the geometry pass
        'name': "geoPass",
        'geometrySet': "ALL"
        //      'outputs':[{ 'name':"u_mainRT", 'type':"target", 'width':1024, 'height':1024, 'mips':false }],
        //      'children':
        //      [
        //          // shadow pass
        //          {
        //              'outputs':[{ 'name':"u_shadowDepthMap", 'type':"target", 'width':1024, 'height':1024, 'mips':false }],
        //              // custom parameter
        //              'name':"shadowDepthMap",
        //              'shader': RDGE.rdgeDepthMapShaderDef,
        //              'technique':"shadowDepthMap",
        //              'geometrySet':"SHADOW",
        //              'children':
        //              [
        //                  // create shadow rt
        //                  {
        //                      'outputs':[{ 'name':"u_shadowMap", 'type':"target", 'width':1024, 'height':1024, 'mips':false }],
        //                      'name':"shadowMap",
        //                      'shader': RDGE.rdgeShadowMapShader,
        //                      'clearColor' : [1.0,1.0,1.0,1.0],
        //                      'geometrySet':"SHADOW",
        //                  }
        //              ]
        //          },
        //          // glow pass
        //          {
        //              'outputs':[{ 'name':"sTexture", 'type':"target", 'width':1024, 'height':1024, 'mips':false }],
        //              'name':"glowMap",
        //              'shader': RDGE.rdgeGlowMapShader,
        //              'clearColor' : [0.0,0.0,0.0,1.0],
        //              'technique':"createGlowMap",
        //              'geometrySet':"ALL",
        //              'children':
        //                  [
        //                      {   // the blur pass at half resolution
        //                          'name':"blurQuarter",
        //                          'geometrySet':"SCREEN_QUAD",
        //                          'shader': RDGE.rdgeGaussianBlurShader,
        //                          'outputs':[{ 'name':"sTexture", 'type':"target", 'width':256, 'height':256, 'mips':false }],
        //                          'children':
        //                          [
        //                              {   // the blur pass at half resolution
        //                                  'name':"blurThreeQuarter",
        //                                  'geometrySet':"SCREEN_QUAD",
        //                                  'shader': RDGE.rdgeGaussianBlurShader,
        //                                  'outputs':[{ 'name':"sTexture", 'type':"target", 'width':128, 'height':128, 'mips':false }],
        //                                  'children':
        //                                  [
        //                                       {  // the blur pass at quarter resolution
        //                                          'name':"blurFull",
        //                                          'geometrySet':"SCREEN_QUAD",
        //                                          'shader': RDGE.rdgeGaussianBlurShader,
        //                                          'outputs':[{ 'name':"u_glowFinal", 'type':"target", 'width':1024, 'height':1024, 'mips':false }]
        //                                      }
        //                                  ]
        //                              }
        //                          ]
        //                      }
        //                  ]
        //          },
        //          // depth map in view space
        //          {
        //              'name':"depth_map",
        //              'shader': RDGE.rdgeDepthMapShaderDef,
        //              'technique': "depthMap",
        //              'outputs':[{ 'name':"u_depthMap", 'type':"target", 'width':1024, 'height':1024, 'mips':false }],
        //              'geometrySet':"ALL",
        //              'children' :
        //              [
        //                  // get the normals in view space
        //                  {
        //                      'name':"normals",
        //                      'shader': RDGE.rdgeViewSpaceNormalsShader,
        //                      'outputs':[{ 'name':"u_normalsRT", 'type':"target", 'width':1024, 'height':1024, 'mips':false }],
        //                      'geometrySet':"ALL",
        //                      'children' :
        //                      [
        //                          // techniques requiring depth and normals in view space go here
        //
        //                          // SSAO map
        //                          {
        //                              'name':"SSAO",
        //                              'shader': RDGE.rdgeSSAOShader,
        //                              'outputs':[{ 'name':"u_ssaoRT", 'type':"target", 'width':1024, 'height':1024, 'mips':false }],
        //                              'textures':[{ 'type':"tex2d", 'data':"u_depthMap" }],
        //                              'geometrySet':"SCREEN_QUAD",
        //                              'preRender': function()
        //                               {
        //                                   this.shader.ssao.u_cameraFTR.set(this.renderer.cameraManager().getActiveCamera().getFTR());
        //                               }
        //                          }
        //                      ]
        //                  }
        //              ]
        //          },
        //
        //          // final pass must always be last in the list
        //          {
        //              // this final pass has no output, its shader, however, will render its input (the previous pass's output)
        //              // to the screen-quad geometry setup under the 'renderList' object
        //              'name':"finalPass",
        //              'geometrySet':"SCREEN_QUAD",
        //              'textures':[{ 'type':"tex2d", 'data':"u_glowFinal" }, { 'type':"tex2d", 'data':"u_ssaoRT" }, { 'type':"tex2d", 'data':"u_shadowMap" }],
        //              'shader': RDGE.rdgeScreenQuadShaderDef,
        //          }
        //      ]
    };

    // a graph of render passes to process in order to produce a final output
    this.renderGraph = new RDGE.jpassGraph(this.defaultPassDef);
    //this.renderGraph.shadowDepthMap.camera = this.mainLight;

    this.animstack = [];
    this.pushAnim = function (anim) {
        this.animstack.push(anim);
    };

    this.popAnim = function (anim) {
        this.animstack.pop();
    };

    this.playAnim = function (anim) {
        this.popAnim();
        this.pushAnim(anim);
    };

    this.stopAllAnims = function () {
        this.animstack = [];
    };

    this.jdefShaderProgram = new RDGE.jshader();
    this.jdefShaderProgram.def = RDGE.rdgeDefaultShaderDefintion;
    this.jdefShaderProgram.init();

    //this.defaultRenderProc = new RDGE.DefaultRender();

    mapping = new Array();
    mapping.process = function (trNode, parent) {
        mapping[trNode.name] = trNode;
    };
    this.Traverse(mapping);

    this.findNode = function (name) {
        return mapping[name];
    };

    /*
    *   scene traversal functor for finding a node by name
    */
    findNodeByName = function (nodeName) {
        this.result = null;
        this.process = function (node, parent) {
            if (node.name == nodeName) {
                this.result = node;
            }
            return true;
        }

        this.init = function () { this.result = null; }
    };

    /*
    *   scene traversal functor for creating a list of node with a given name
    */
    buildNodeList = function (nodeName) {
        this.result = [];
        this.process = function (node, parent) {
            if (node.name == nodeName) {
                this.result.push(node);
            }
            return true;
        }
        this.init = function () { this.result = []; }
    };

    /*
    *   scene traversal functor for creating a list of nodes based on a regular expression
    */
    buildNodeListRegex = function (re) {
        this.result = [];
        this.process = function (node, parent) {
            if (re.test(node.name)) {
                this.result.push(node);
            }
            return true;
        }
        this.init = function () { this.result = []; }
    };

    /*
    *   scene traversal functor for importing a previously exported json scene
    */
    importScene = function () {
        this.renderer = RDGE.globals.engine.getContext().renderer;

        this.process = function (node, parent) {
            node.parent = parent;

            if (node.nodeType === RDGE.rdgeConstants.nodeType.TRNODE) {
                node.transformNodeTemplate = undefined;
                RDGE.verifyTransformNode(node);

                if (node.materialNode) {
                    node.materialNode.materialNodeTemplate = undefined;
                    RDGE.verifyMaterialNode(node.materialNode);

                    if (node.materialNode.shaderProgram) {
                        var shader = new RDGE.jshader();
                        shader.def = JSON.parse(node.materialNode.shaderProgram);
                        shader.init();
                        node.materialNode.shaderProgram = shader;
                    }

                    var texList = node.materialNode.textureList;
                    for (var i = 0, len = texList.length; i < len; ++i) {
                        texList[i].handle = this.renderer.getTextureByName(texList[i].handle.lookUpName, texList[i].handle.texparams.wrap, texList[i].handle.texparams.mips);
                    }

                    var lights = node.materialNode.lightChannel;
                    for (var i = 0, len = lights.length; i < len; ++i) {
                        if (lights[i]) {
                            lights[i].lightNodeTemplate = undefined;
                            RDGE.verifyLightNode(lights[i]);
                        }
                    }
                }

                // load meshes into context
                for (var i = 0, len = node.meshes.length; i < len; ++i) {
                    var mesh = RDGE.globals.meshMan.getModelByName(node.meshes[i].mesh.name);
                    //                  mesh.primitive.built = false;
                    this.renderer.createPrimitive(mesh.primitive);
                }
            }

            return true;
        };

        this.init = function () { this.result = []; }
    };

    /*
    *   helper comparison functions
    */
    __compareLessThan = function (a, b) {
        return a < b;
    };
    __compareGreaterThan = function (a, b) {
        return a > b;
    };

    /*
    *   scene traversal functor for creating a sorted list
    */
    insertIntoSortedList = function (list, item, comparator) {
        // insert at the end
        list.push(item);

        var len = list.length;

        // get the active camera
        var cam = RDGE.globals.engine.getContext().renderer.cameraManager().getActiveCamera();

        // camera z plane
        var look = [cam.world[8], cam.world[9], cam.world[10]];
        // to object vector
        var toObject = [cam.world[12] - item.node.world[12], cam.world[13] - item.node.world[13], cam.world[14] - item.node.world[14]];

        // get the distance from object to cameras' 'z' plane
        item.depth = RDGE.vec3.dot(look, toObject);

        // walk down the list of object moving the current item into place until the comparison fails
        var i = len - 1;
        var temp = null;
        for (; i > 0; --i) {
            if (comparator(item.depth, list[i - 1].depth)) {
                temp = list[i - 1]
                list[i - 1] = list[i];
                list[i] = temp;
            }
            else {
                break;
            }
        }
    };

    /*
    *   Helper function to generate a culled list of geometry for shadow mapping from the mainLights point of view
    */
    shadowCullPass = function (cameraLight, sortCat, compareFunc) {
        this.result = [];
        this.activeCam = cameraLight;
        this.bucketType = sortCat;
        this.compare = compareFunc;

        this.process = function (node, parent) {
            // test visibility
            if (node.bbox_world && node.bbox_world.isValid()) {
                if (!node.bbox_world.isVisible(this.activeCam.frustum)) {
                    return false;
                }
            }

            if (node.materialNode && node.materialNode.sortCategory == this.bucketType) {
                insertIntoSortedList(this.result, { 'context': new RDGE.RenderContext(), 'node': node, 'parent': parent }, this.compare);
            }

            return true;
        }
        this.init = function () { this.result = []; }
    };

    /*
    *   Helper function to create a list sorted front to back as would be used by an opaque render list
    *   note: this is an insertion sort designed to create a new sorted list, not sort an existing list (optimization)
    */
    insertFrontToBack = function (list, item) {
        insertIntoSortedList(list, item, __compareLessThan);
    };

    /*
    *   Helper function to create a list sorted back to front as would be used by an transparent render list
    *   note: this is an insertion sort designed to create a new sorted list, not sort an existing list (optimization)
    */
    insertBackToFront = function (list, item) {
        insertIntoSortedList(list, item, __compareGreaterThan);
    };

    // sort map
    this.sortFunc = [];
    this.sortFunc[this.bckTypes.BACKGROUND] = function (list, item) { list.push(item); };
    this.sortFunc[this.bckTypes.OPAQUE] = insertFrontToBack;
    this.sortFunc[this.bckTypes.TRANSPARENT] = insertBackToFront;
    this.sortFunc[this.bckTypes.ADDITIVE] = insertBackToFront;
    this.sortFunc[this.bckTypes.TRANSLUCENT] = insertBackToFront;
    this.sortFunc[this.bckTypes.FOREGROUND] = function (list, item) { list.push(item); };
};

/*
*  functor must have 'process(node, parent)' function defined, it takes a scene transform node, and the parent
*/
RDGE.SceneGraph.prototype.Traverse = function (functor, isDepthFirst) {
    if (this.scene == null) {
        window.console.log("traversing a NULL scene!");
        return;
    }

    if (functor.init)
        functor.init();

    if (isDepthFirst) {
        this._TraverseDFHelper(functor, this.scene);
    }
    else {
        this._TraverseBFHelper(functor, this.scene);
    }
};

/*
* adds a transform node under the root of the scene
*/
RDGE.SceneGraph.prototype.addNode = function (trNode) {
    RDGE.verifyTransformNode(trNode);

    this.scene.children.push({ transformNode: trNode });
};

/*
* adds a transform node under the root of the scene
*/
RDGE.SceneGraph.prototype.insertUnder = function (targetNode, newNode) {
    RDGE.verifyTransformNode(targetNode);

    targetNode.insertAsChild(newNode);
};

/*
* adds a transform node under the root of the scene
*/
RDGE.SceneGraph.prototype.insertAbove = function (targetNode, newNode) {
    RDGE.verifyTransformNode(targetNode);

    targetNode.insertAsParent(newNode);
};

/*
* locates a node by name
* @return returns the node if found or null otherwise
*/
RDGE.SceneGraph.prototype.getNode = function (nodeName) {
    var functor = new findNodeByName(nodeName);
    this.Traverse(functor, true);

    return functor.result;
};

/*
* locates a node by name
* @return returns a list of all node with the name requested
*/
RDGE.SceneGraph.prototype.getNodes = function (nodeName) {
    var functor = new buildNodeList(nodeName);
    this.Traverse(functor, true);

    return functor.result;
};

/*
* locates a node by name
* @return returns a list of all node with the name requested
*/
RDGE.SceneGraph.prototype.getNodesRegex = function (re) {
    if (typeof re == "string") {
        re = new RegExp(re);
    }

    var functor = new buildNodeListRegex(re);
    this.Traverse(functor, true);

    return functor.result;
};

/*
*  Depth first Traverse helper
*/
RDGE.SceneGraph.prototype._TraverseDFHelper = function (functor, node, parent) {
    if (node.children != 'undefined') {
        var queue = [];

        queue.push({ 'node': node, 'parent': null });

        while (queue.length > 0) {
            // pop the head and process it
            var trNode = queue.pop();
            var tr = trNode.node;
            var parent = trNode.parent;

            if (tr.transformNode !== undefined) {
                tr = tr.transformNode;
                if (functor.process(tr, parent) === false) {
                    continue;
                }
            }

            if (tr.children === undefined)
                continue;

            // push on kids
            for (var kid = 0; kid < tr.children.length; ++kid) {
                queue.push({ 'node': tr.children[kid], 'parent': tr });
            }
        }
    }
};

/*
*  Depth first post process Traverse helper
*/
RDGE.SceneGraph.prototype._TraverseDFPostOrderHelper = function (functor, node, parent) {
    if (node.children != 'undefined') {
        var queue = [];

        queue.push({ 'node': node, 'parent': null });

        var top = queue.length;

        while (top > 0) {

            top = queue.length;

            var topNode = tr.children[top - 1];

            var len = topNode.children == undefined ? 0 : topNode.children.length;

            // push on children
            for (var child = 0; child < len; ++child) {
                queue.push({ 'node': tr.children[child], 'parent': tr });
            }

            // did we push anything on?
            if (len > 0)
                continue;

            // pop the head and process it
            var trNode = queue.pop();
            var tr = trNode.node;
            var parent = trNode.parent;

            if (tr.transformNode !== undefined) {
                tr = tr.transformNode;
                functor.process(tr, parent);
            }
        }
    }
};

/*
*  Depth first post process Traverse helper
*/
RDGE.SceneGraph.prototype.BuildBVHHelper = function (node) {
    if (node.children != 'undefined') {
        if (node.bbox_world)
            node.bbox_world.reset();
        else
            node.bbox_world = new RDGE.box();

        if (node.local == undefined) {
            node.local = RDGE.mat4.identity();
        }

        var queue = [];

        var idIndex = 0;
        node.id = "root";
        queue.push({ 'node': node, 'xfrm': RDGE.mat4.identity(), 'parent': null, 'visited': false });

        var top = queue.length;
        var topIndex = 0;

        while (top > 0) {
            // update the top
            top = queue.length;
            topIndex = top - 1;

            var curNode = queue[topIndex].node.transformNode == undefined ? queue[topIndex].node : queue[topIndex].node.transformNode;
            var parentXfrm = queue[topIndex].xfrm;
            var parent = queue[topIndex].parent;
            var visited = queue[topIndex].visited;
            if (curNode.id == undefined) curNode.id = "id" + idIndex;

            if (!visited) {
                // Copy the parent's world mat and setup bounding box
                if (curNode.local !== undefined) {

                    if (curNode.bbox_world)
                        curNode.bbox_world.reset();
                    else
                        curNode.bbox_world = new RDGE.box();

                    var bbox = this.GetBBoxForNode(curNode);

                    // transform child node by parent
                    curNode.world = RDGE.mat4.mul(curNode.local, parentXfrm);

                    if (bbox) {
                        // update bounding box position
                        curNode.bbox_world = bbox.transform(curNode.world);
                    }

                    // make sure the nodes have a bounding volume so they dont impede the propagation
                    if (!bbox || !bbox.isValid()) {
                        var dummybb = new RDGE.box();
                        dummybb.set(0, 0);
                        curNode.bbox_world = dummybb;
                    }

                }

                // child count
                var len = curNode.children == undefined ? 0 : curNode.children.length;

                // push on children
                for (var child = 0; child < len; ++child) {
                    queue.push({ 'node': curNode.children[child], 'xfrm': curNode.world, 'parent': curNode, 'visited': false });
                }

                idIndex++;

                // did we push anything on if so then this is not a leaf and we dont pop?
                if (len > 0)
                    continue;
            }

            // propagate the bounding volume up the hierarchy
            if (parent && parent.bbox_world && parent.bbox_world.isValid()
                && curNode.bbox_world && curNode.bbox_world.isValid()) {
                parent.bbox_world.addBox(curNode.bbox_world);
            }

            // remove top node
            queue.pop();

            // update the top
            top = queue.length;

            if (top > 0) {
                // if the previous node in the stack/queue was the parent of this node, then mark the parent as visited
                var prevNode = queue[top - 1].node.transformNode == undefined ? queue[top - 1].node : queue[top - 1].node.transformNode;

                if (prevNode.id == parent.id)
                    queue[top - 1].visited = true;
            }
        }
    }
};


/*
*  Breadth first Traverse helper
*/
RDGE.SceneGraph.prototype._TraverseBFHelper = function (functor, node) {

    if (node.children != 'undefined') {
        var queue = [];

        queue.push({ 'node': node, 'parent': null });

        while (queue.length > 0) {
            // pop the head and process it
            var trNode = queue.shift();
            var tr = trNode.node;
            var parent = trNode.parent;

            if (tr.transformNode !== undefined) {
                tr = tr.transformNode;
                functor.process(tr, parent);
            }

            if (tr.children === undefined)
                continue;

            // push on kids
            for (var kid = 0; kid < tr.children.length; ++kid) {
                queue.push({ 'node': tr.children[kid], 'parent': tr });
            }
        }
    }
};

/*
*  Update the scene
*/
RDGE.SceneGraph.prototype.update = function (dt) {
    var renderer = RDGE.globals.engine.getContext().renderer;
    RDGE.globals.engine.getContext().debug.mat4CallCount = 0;

    // animation update...
    var i = this.animstack.length - 1;
    while (i >= 0) {
        this.animstack[i].step(dt);
        --i;
    }

    this.BuildBVHHelper(this.scene);

    RDGE.g_particleSystemManager.update(dt);

    var activeCam = renderer.cameraManager().getActiveCamera();
    if (activeCam !== undefined && activeCam != null && activeCam.controller != null && activeCam.controller.world !== undefined) {
        activeCam.setWorld(activeCam.controller.world);
    }
    this.tick++;
};

/*
*  Render the scene
*/
RDGE.SceneGraph.prototype.render = function (renderProc, forceThisProc) {
    if (this.scene.children.length == 0)
        return;

    var renderer = RDGE.globals.engine.getContext().renderer;

    RDGE.rdgeGlobalParameters.u_shadowLightFarZ.set([this.mainLight.zFar()]);

    this.renderList = this._RenderDFHelper(renderer, renderProc, this.scene, forceThisProc);

    if (this.shadowsEnabled) {
        this.Traverse(this.shadowCuller, true);
        this.shadowRenderList = this.shadowCuller.result;
    }

    this.renderGraph.render(this);

    RDGE.g_particleSystemManager.render();
};

/*
*  Returns the bbox for the passed node, if present
*/
RDGE.SceneGraph.prototype.GetBBoxForNode = function (tr) {
    var bbox = null;

    if (tr.materialNode && tr.materialNode.meshNode) {
        var mesh = tr.materialNode.meshNode.mesh;
        var model = null;

        model = RDGE.globals.meshMan.getModelByName(mesh.name);

        if (model != null)
            bbox = model.bbox;
    }

    return bbox;
};

/*
*  Depth first Traverse Render helper
*/

RDGE.SceneGraph.prototype._RenderDFHelper = function (renderer, renderProc, node, forceThisProc) {
    renderList = [];
    renderList[this.bckTypes.BACKGROUND] = []; //BACKGROUND
    renderList[this.bckTypes.OPAQUE] = []; //OPAQUE
    renderList[this.bckTypes.TRANSPARENT] = []; //TRANSPARENT
    renderList[this.bckTypes.ADDITIVE] = []; //ADDITIVE
    renderList[this.bckTypes.TRANSLUCENT] = []; //TRANSLUCENT
    renderList[this.bckTypes.FOREGROUND] = []; //FOREGROUND

    if (node.children != 'undefined') {
        var queue_bf = [];

        //
        // Render Pass
        //
        // the context - default settings
        var _Ctx = RDGE.globals.engine.defaultContext;

        // last depth in tree
        var lastAppliedID = 0;

        var isRoot = true, isVisible = true, contextDirty = true;

        queue_bf.push({ 'node': node, 'curCtx': _Ctx });

        var activeCam = renderer.cameraManager().getActiveCamera();

        while (queue_bf.length > 0) {

            isVisible = true;

            // pop the head and process it
            var trNode = queue_bf.pop();
            var tr = trNode.node;


            // default context
            var curCtx = new RDGE.RenderContext();
            curCtx.Load(trNode.curCtx);

            // flatten out matrices gather render context from material and render
            if (tr.transformNode !== undefined) {

                tr = tr.transformNode; // if transform exist we need to use it as our current node to check for kids

                if (tr.hide !== undefined && tr.hide == true) {
                    continue;
                }

                // test visibility
                if (this.frustumCulling && tr.bbox_world && tr.bbox_world.isValid()) {
                    if (!tr.bbox_world.isVisible(activeCam.frustum)) {
                        continue;
                    }
                }

                curCtx.shaderProg = this.jdefShaderProgram;

                var renderBucket = RDGE.rdgeConstants.categoryEnumeration.OPAQUE;

                // get material if its available
                if (tr.materialNode) {
                    renderBucket = tr.materialNode.sortCategory;

                    // set shader to use
                    if (tr.materialNode.shaderProgram !== undefined) {
                        curCtx.shaderProg = tr.materialNode.shaderProgram; // use whats on the node if we are not beinged forced
                    }

                    if (tr.materialNode.textureList !== undefined) {
                        curCtx.textureList = tr.materialNode.textureList.slice();
                    }

                    if (tr.materialNode.uniforms.length > 0) {
                        curCtx.uniforms = tr.materialNode.uniforms.slice();
                    }

                    var len = tr.materialNode.lightChannel.length;
                    for (var i = 0; i < len; ++i) {
                        if (tr.materialNode.lightChannel[i])
                            curCtx.lights[i] = tr.materialNode.lightChannel[i];
                    }
                }

                // push onto deferred render list
                this.sortFunc[renderBucket](renderList[renderBucket], { 'context': curCtx, 'node': tr, 'parent': parent });
            }

            if (tr.children === undefined)
                continue;

            // push on kids
            var numKids = tr.children.length;
            for (var kid = 0; kid < numKids; ++kid) {
                queue_bf.push({ 'node': tr.children[kid], 'curCtx': curCtx });
            }

        }
    }

    return renderList;
};

RDGE.SceneGraph.prototype.enableShadows = function (areEnabled) {
    if (areEnabled) {
        var renderer = RDGE.globals.engine.getContext().renderer;

        this.shadowCuller = new shadowCullPass(this.mainLight, RDGE.rdgeConstants.categoryEnumeration.OPAQUE, function (a, b) { return a < b; });

        this.mainLight.init();
        // lights position and point of view
        this.mainLight.setPerspective(45.0, renderer.vpWidth / renderer.vpHeight, 1.0, 200.0);
        this.mainLight.setLookAt([-60, 17, -15], [-5, -5, 15], RDGE.vec3.up());

        // setup light params
        RDGE.rdgeGlobalParameters.u_shadowLightWorld.set(this.mainLight.world);
        RDGE.rdgeGlobalParameters.u_vShadowLight.set(this.mainLight.view);
        var shadowMatrix = RDGE.mat4.identity();
        shadowMatrix = RDGE.mat4.scale(shadowMatrix, [0.5, 0.5, 0.5]);
        shadowMatrix = RDGE.mat4.translate(shadowMatrix, [0.5, 0.5, 0.5]);
        RDGE.rdgeGlobalParameters.u_shadowBiasMatrix.set(shadowMatrix);
        var BiasProjViewMat = RDGE.mat4.mul(this.mainLight.proj, shadowMatrix);
        BiasProjViewMat = RDGE.mat4.mul(this.mainLight.view, BiasProjViewMat);
        RDGE.rdgeGlobalParameters.u_shadowBPV.set(BiasProjViewMat);

        this.shadowsEnabled = true;
    }
    else {
        this.mainLight = null;
        this.shadowsEnabled = false;
    }
};


RDGE.SceneGraph.prototype.exportJSON = function () {
    objMap = [];

    function replacer(key, value) {
        if (key == 'bbox_world') {
            return null;
        }
        //      else if(key === 'image')
        //      {
        //          return "image";
        //      }
        else if (key === 'parent') {
            return "parent";
        }

        //      for(var i = 0, len = objMap.length; i < len; ++i)
        //      {
        //          if((value && typeof value === "object" && !value.jsonExportName && value === objMap[i]) || (value && value.baseURI !== undefined))
        //          {
        //              return 'replaced';
        //          }
        //      }
        //
        //      if(value && value.baseURI === undefined && typeof value === "object" && !value.lookUpName)
        //          objMap.push(value);

        return value;
    }

    var val = { 'scene': null, 'meshes': null };
    val.scene = JSON.stringify(this.scene, replacer);

    val.meshes = RDGE.globals.meshMan.exportJSON();

    val = JSON.stringify(val);

    return val;
};

RDGE.SceneGraph.prototype.importJSON = function (jsonScene) {
    try {
        if (jsonScene) {
            var sceneImport = JSON.parse(jsonScene);

            if (sceneImport) {
                this.scene = JSON.parse(sceneImport.scene);

                if (sceneImport.meshes) {
                    RDGE.globals.meshMan.importJSON(sceneImport.meshes);
                }

                if (this.scene) {
                    // traverse the scene, re-creating missing components
                    var importer = new importScene();
                    this.Traverse(importer, true);

                    window.console.log("scene imported");
                }
            }
        }
    } catch (e) {
        window.console.error("error importing JSON scene: " + e.description);
    }
};