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

// 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);
    }
};