/* This file contains proprietary software owned by Motorola Mobility, Inc.
No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
(c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved.
*/ // RDGE namespaces var RDGE = RDGE || {}; /* * jpass geometry set - determines the category(s) of geometry that a pass will render * can be OR'ed together */ RDGE.jpassGeoSet = { 'BACKGROUND': 1, 'OPAQUE': 2, 'TRANSPARENT': 4, 'ADDITIVE': 8, 'TRANSLUCENT': 16, 'FOREGROUND': 32, 'ALL': 127, 'SCREEN_QUAD': 128, // a screen aligned quad - for rendering a texture to screen 'SHADOW': 256, // the opaque geometry from shadow light's point of view 'MAXSETS': 9 }; /* * The abstract base class that defines a jpass * a jpass represents a single render pass of the scene graph */ RDGE._jpassBaseClass = function () { this.context = RDGE.globals.engine.getContext(); this.renderer = RDGE.globals.engine.getContext().renderer; this.sortCats = RDGE.rdgeConstants.categoryEnumeration; this.bucketCount = RDGE.rdgeConstants.categoryEnumeration.MAX_CAT; // render order this.renderOrder = []; this.renderOrder[this.sortCats.BACKGROUND] = 0; this.renderOrder[this.sortCats.OPAQUE] = 1; this.renderOrder[this.sortCats.TRANSPARENT] = 2; this.renderOrder[this.sortCats.ADDITIVE] = 3 this.renderOrder[this.sortCats.TRANSLUCENT] = 4; this.renderOrder[this.sortCats.FOREGROUND] = 5; // the name of this pass this.name = "renderPass_" + RDGE.nodeIdGen.getId(); /* * if 0 this pass and children are culled from rendering */ this.visibility = 1; /* * called when the pass is hidden - override for customication */ this.onHide = function () { }; /* * Called by the system to hide the pass and its children */ this.hidePass = function () { this.onHide(); for (var i = 0, len = this.children.length; i < len; ++i) { this.children[i].hidePass(); } }; /* * the default output render targets that this pass will create */ this.defaultTargetOut = {}; /* * All the outputs required by the pass */ this.outputs = [ // example // {'name':"u_mainRT", 'type':"target", 'width':1024, 'height':1024, 'mips':false} ]; /* * notifies the the renderer that the viewport was modified and needs to be reset */ this.dirty = false; /* * Index of the currently selected output target */ this.outputIndex = 0; /* * outputs from the previous pass are set as inputs for this pass */ this.inputs = []; /* * other textures requested for this pass */ this.textures = []; /* * the flags that control how the pass is rendered */ this.frustum_culling = "enable"; // disable/enable frustum culling during the pass this.clear = null; // flags to clear the output target with before rendering this.clearColor = null; /* * Contains a list of geometry to be rendered, during a post process render pass this will usually by a screen quad */ this.renderList = [ // example // { 'name':'opaqeobjects', 'geo'{ 'OPAQUE':[ new renderObject(meshNode, transformNode, RenderContext)]} } ]; /* * The passes that will render after this pass */ this.children = []; /* * This shader will override all other shaders */ this.shader = null; /* * Technique of from shader to use, if null currently set technique is used */ this.technique = null; /* * determines the geometry that will be rendered during the pass */ this.geometrySet = "SCREEN_QUAD"; /* * A camera set here will override any camera active in the scene */ this.camera = null; /* * Initialize the pass */ this.init = function () { }; /* * inserts a node into the child map using the pass name as the key */ this.insertChildPass = function (jpassObj) { this.children[jpassObj.name] = jpassObj; }; /* * the scene-graph to process */ this.process = function () { // pre-defined local variables to prevent allocation var context; var shaderProg; var listCount; var len; var passes; var pass; var node; var mesh; var meshCount; var nodeIdx = 0; var meshIdx = 0; var paramIdx = 0; var passIdx = 0; var renderer = RDGE.globals.engine.getContext().renderer; //this.renderer = RDGE.globals.engine.getContext().renderer; // bind output target for rendering this.bindOutput(); // call custom pre-render step this.preRender(); var activeCam = renderer.cameraManager().getActiveCamera(); if (this.technique) { this.shader.setTechnique(this.technique); } renderer.projectionMatrix = activeCam.proj; // parameters that can be set once per pass RDGE.rdgeGlobalParameters.u_inv_viewport_width.set([1.0 / renderer.vpWidth]); RDGE.rdgeGlobalParameters.u_inv_viewport_height.set([1.0 / renderer.vpHeight]); RDGE.rdgeGlobalParameters.u_farZ.set([activeCam.zFar()]); RDGE.rdgeGlobalParameters.u_projMatrix.set(renderer.projectionMatrix); for (var bucketIdx = 0, bckCnt = this.renderList.length; bucketIdx < bckCnt; ++bucketIdx) { //var curList = this.renderList[bucketIdx]; listCount = this.renderList[bucketIdx].length; for (nodeIdx = 0; nodeIdx < listCount; ++nodeIdx) { node = this.renderList[bucketIdx][nodeIdx].node; if (node.world) { context = this.renderList[bucketIdx][nodeIdx].context; shaderProg = this.shader ? this.shader : context.shaderProg; renderer.mvMatrix = RDGE.mat4.mul4x3(node.world, activeCam.view); renderer.invMvMatrix = RDGE.mat4.inverse(renderer.mvMatrix); renderer.normalMatrix = RDGE.mat4.transpose(renderer.invMvMatrix); RDGE.rdgeGlobalParameters.u_mvMatrix.set(renderer.mvMatrix); RDGE.rdgeGlobalParameters.u_normalMatrix.set(renderer.normalMatrix); RDGE.rdgeGlobalParameters.u_worldMatrix.set(node.world); RDGE.rdgeGlobalParameters.u_viewMatrix.set(activeCam.view); RDGE.rdgeGlobalParameters.u_invViewMatrix.set(RDGE.mat4.inverse(activeCam.view)); RDGE.rdgeGlobalParameters.u_invMvMatrix.set(renderer.invMvMatrix); len = context.uniforms.length; for (paramIdx = 0; paramIdx < len; ++paramIdx) { RDGE.rdgeGlobalParameters[context.uniforms[paramIdx].name].set(context.uniforms[paramIdx].value); } // augment the texture list with any post processing textures this.updateTextureContext(context.textureList); shaderProg.setLightContext(context.lights); shaderProg.setTextureContext(context.textureList); passes = shaderProg.begin(); pass = null; for (passIdx = 0; passIdx < passes; ++passIdx) { pass = shaderProg.beginPass(passIdx); meshCount = node.meshes.length; for (meshIdx = 0; meshIdx < meshCount; ++meshIdx) { mesh = RDGE.globals.meshMan.getModelByName(node.meshes[meshIdx].mesh.name); if (mesh) renderer.drawPrimitive(mesh.primitive, pass.program, pass.attributes); } shaderProg.endPass(); this.onPassEnd(); } shaderProg.end(); } } } // call custom post render step this.postRender(); // remove the bound render target if applicable this.unbindOutput(); }; /* * handle any setup 'before' processing the geo/scenegraph * the first step in the pass */ this.preRender = function () { }; /* * handle any setup 'after' processing the geo/scenegraph * the last step in the pass */ this.postRender = function () { }; /* * Custom function to handle any processing in between jshader passes */ this.onPassEnd = function () { if (this.outputIndex + 1 < this.outputs.length) ++this.outputIndex; }; /* * Set the list of objects to render */ this.setRenderList = function (contextList) { this.renderList = contextList; }; /* * Set the render targets to use as input for the next pass */ this.setInputs = function (inputsArr) { this.inputs = inputsArr.slice(); }; /* * Augment the textureList passed in with the input textures * this will cause the textures to be bound to the jshader */ this.updateTextureContext = function (textureList) { var inputs = this.inputs.slice(); for (var i = 0; i < this.inputs.length; ++i) { textureList.push(inputs[i]); } for (var i = 0; i < this.textures.length; ++i) { if (this.textures[i].enabled) textureList.push(this.textures[i]); } }; /* * If there is an output surface this will bind it */ this.bindOutput = function () { this.outputIndex = 0; if (this.outputs[this.outputIndex]) { this.dirty = true; var oldClear = null; var fb = this.outputs[this.outputIndex].data[0].frameBuffer; this.renderer.ctx.bindFramebuffer(this.renderer.ctx.FRAMEBUFFER, fb); this.renderer.ctx.viewport(0, 0, fb.width, fb.height); if (this.clearColor) { oldClear = this.renderer.clearColor; this.renderer.setClearColor(this.clearColor); } if (this.clear) this.renderer.clear(this.clear); else this.renderer._clear(); if (this.clearColor) { this.renderer.setClearColor(oldClear); } } }; /* * If an output surface was bound this will unbind it */ this.unbindOutput = function () { if (this.dirty) { this.dirty = false; this.renderer.ctx.bindFramebuffer(this.renderer.ctx.FRAMEBUFFER, null); this.renderer.setViewPort(0, 0, this.renderer.vpWidth, this.renderer.vpHeight); } }; }; /* * The concrete class to be used when creating a scene pass */ RDGE.jpass = function (def) { // inherit from the base class this.inheritedFrom = RDGE._jpassBaseClass; this.inheritedFrom(); // setup the jpass as defined, recursively instantiated the children as they are encountered for (var obj in def) { this[obj] = def[obj]; if (obj == "children") { var childCount = this[obj].length; for (var i = 0; i < childCount; ++i) { this[obj][i] = new RDGE.jpass(this[obj][i]); } } // validate array and create the texture/render-target else if (obj == "inputs" || obj == "outputs") { // this makes sure the data type was passed as an array and converts it to an array if needed if (!this[obj].slice()) { this[obj] = [this[obj]]; } var count = this[obj].length; for (var i = 0; i < count; ++i) { // if the texture/render-target is not created yet do so now if (!this[obj][i].data) { if (this[obj][i].type == "target") { // setup the render target this[obj][i].data = [this.renderer.createRenderTargetTexture(this[obj][i].name, this[obj][i].width, this[obj][i].height, this[obj][i].mips)]; } else { // make sure data is defined this[obj][i].data = [null] } } if (!this[obj][i].handle) { this[obj][i].handle = this[obj][i].data[0]; } } } else if ("renderList" == obj) { // put the items listed into their buckets var renderList = new Array(RDGE.rdgeConstants.categoryEnumeration.MAX_CAT); renderList[RDGE.rdgeConstants.categoryEnumeration.BACKGROUND] = []; //BACKGROUND renderList[RDGE.rdgeConstants.categoryEnumeration.OPAQUE] = []; //OPAQUE renderList[RDGE.rdgeConstants.categoryEnumeration.TRANSPARENT] = []; //TRANSPARENT renderList[RDGE.rdgeConstants.categoryEnumeration.ADDITIVE] = []; //ADDITIVE renderList[RDGE.rdgeConstants.categoryEnumeration.TRANSLUCENT] = []; //TRANSLUCENT renderList[RDGE.rdgeConstants.categoryEnumeration.FOREGROUND] = []; //FOREGROUND for (var buckets in this[obj]) { // get the enumeration value (ie RDGE.rdgeConstants.categoryEnumeration.OPAQUE) var bucket = RDGE.rdgeConstants.categoryEnumeration[buckets]; // create the list for this bucket var len = this[obj][buckets].length; for (var i = 0; i < len; ++i) { // add the corresponding objects to the bucket renderList[bucket].push(this[obj][buckets][i]); } } // overwrite with the new list this[obj] = renderList; } else if ("shader" == obj) { // setup the shader if (typeof this[obj] == "string") { this.shader = new RDGE.jshader(this[obj]); } else { var finalShader = new RDGE.jshader(); finalShader.def = this[obj]; finalShader.init(); this[obj] = finalShader; } } else if ("geometrySet" == obj) { if (typeof this[obj] == "string") { var sets = this[obj].split("|"); var setValue = 0; for (var i = 0; i < sets.length; ++i) { setValue |= RDGE.jpassGeoSet[sets[i]]; if (sets[i] === "SCREEN_QUAD") { this[obj] = RDGE.jpassGeoSet[sets[i]]; this.renderList[0] = [new renderObject(RDGE.createScreenQuadNode())]; break; } } this[obj] = setValue; } } else if ("textures" == obj) { for (var i = 0, len = this[obj].length; i < len; ++i) { if (typeof this[obj][i].data == "string") { this[obj][i].name = this[obj][i].data; this[obj][i].data = [this[obj][i].data]; this[obj][i].enabled = true; } else { this[obj][i].name = this.renderer.getTextureByName(this[obj][i].data[0].lookUpName); } } } } // call initialize code this.init(); }; /* * a graph describing the hierarchy of scene pass objects to create the final composition */ RDGE.jpassGraph = function (def) { // the root pass this.root = null; // going through steps to build the pass graph if (def && typeof def == "string") { // make ajax request } else if (def) { this.root = new RDGE.jpass(def); } else { this.root = new RDGE.jpass({}); } this.find = null; this.insert = null; // an index to the opaque bucket this.OPAQUE = RDGE.rdgeConstants.categoryEnumeration.OPAQUE; this.setPassRoot = function (jpassObj) { this.root = jpassObj; }; /* * Helper function to generate the references to passes available as passGraph."passName" */ this._initHelper = function (node) { if (!node) return; this[node.name] = node; for (var child in node.children) { this._initHelper(node.children[child]); } }; this._initHelper(this.root); /* * @param parentName - the name of the parent object to insert under * @param jpassObj - the jpass object to insert */ this.insertAsChild = function (parentName, jpassObj) { this.find = parentName; this.insert = jpassObj; // create ref for this object this[jpassObj.name] = jpassObj; this._insertHelper(this.root); this.find = null; this.insert = null; }; /* * Recursive helper function for traversing the graph and insterting the node */ this._insertHelper = function (node) { if (!node) return false; if (node.name == this.find) { node.insertChildPass(this.insert); return true; } for (var child in node.children) { if (this._insertHelper(node.children[child])) { break; } } return true; }; // maps jpassGeoSet values to indices this.geoSetMap = []; var geoSetIdx = 0; for (var geoSet in RDGE.jpassGeoSet) { this.geoSetMap[geoSetIdx++] = RDGE.jpassGeoSet[geoSet]; } /* * Traverse the render graph, breadth first to produce the final render output * note: breadth first is used to allow generation of an entire level at once, * the peers in one level could, in theory, be processed in parallel */ this.render = function (sceneGraph) { var renderList = sceneGraph.renderList; var renderer = RDGE.globals.engine.getContext().renderer; // set the first pass with scene geometry this.root.setRenderList(renderList); var curRenderList = null; var queue = [{ 'parent': this.root}]; var parent = null; var setCount = this.geoSetMap.length; var maxCats = RDGE.rdgeConstants.categoryEnumeration.MAX_CAT; var idx = 0; var renderListIdx = 0; while (queue.length) { var parent = queue.shift().parent; // pull out the shadow geometry if (sceneGraph.shadowsEnabled && parent.geometrySet === RDGE.jpassGeoSet.SHADOW) { parent.setRenderList([sceneGraph.shadowRenderList]); } // screen quad needs special prep since geometry is never re-assigned, lists needs to be cleaned up else if (parent.geometrySet === RDGE.jpassGeoSet.SCREEN_QUAD) { parent.renderList[0][0].context.textureList = []; } // pull out all the sort categories that the pass requested else { curRenderList = []; renderListIdx = 0; // create the pass's render list for (idx = 0; idx < setCount; ++idx) { // assign the render list, checking to make sure the index is less than the total number of material categories if (parent.geometrySet & this.geoSetMap[idx]) { if (renderList[idx]) { curRenderList[renderListIdx++] = renderList[idx]; } } } parent.setRenderList(curRenderList); } // if this pass has a camera push it on the stack if (parent.camera) { renderer.cameraManager().pushCamera(parent.camera); } // process the geometry - all rendering occurs here parent.process(); // if this pass had a camera pop it off the stack if (parent.camera) { renderer.cameraManager().popCamera(); } // push on the children for (idx = parent.children.length - 1; idx >= 0; --idx) { if (parent.children[idx].visibility === 1) { parent.children[idx].setInputs(parent.outputs); queue.unshift({ 'parent': parent.children[idx] }); } else if (parent.children[idx].visibility === 0) { parent.children[idx].hidePass(); parent.children[idx].visibility = 2; } } } } };