/*
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.
*/
var GeomObj = require("js/lib/geom/geom-obj").GeomObj;
var Line = require("js/lib/geom/line").Line;
var Rectangle = require("js/lib/geom/rectangle").Rectangle;
var Circle = require("js/lib/geom/circle").Circle;
var MaterialsModel = require("js/models/materials-model").MaterialsModel;
var worldCounter = 0;
///////////////////////////////////////////////////////////////////////
// Class GLWorld
// Manages display in a canvas
///////////////////////////////////////////////////////////////////////
var World = function GLWorld( canvas, use3D, preserveDrawingBuffer ) {
///////////////////////////////////////////////////////////////////////
// Instance variables
///////////////////////////////////////////////////////////////////////
// flag to do the drawing with WebGL
this._useWebGL = false;
if(use3D) {
this._useWebGL = use3D;
}
this._canvas = canvas;
if (this._useWebGL)
{
if(preserveDrawingBuffer)
{
this._glContext = canvas.getContext("experimental-webgl", {preserveDrawingBuffer: true});
}
else
{
this._glContext = canvas.getContext("experimental-webgl");
}
}
else
{
this._2DContext = canvas.getContext( "2d" );
}
this._viewportWidth = canvas.width;
this._viewportHeight = canvas.height;
// view parameters
this._fov = 45.0;
this._zNear = 0.1;
this._zFar = 100.0;
this._viewDist = 5.0;
// default light parameters
this._ambientLightColor = [0.1, 0.1, 0.1, 1.0];
this._diffuseLightColor = [0.1, 0.1, 0.1, 1.0];
this._specularLightColor = [0.6, 0.6, 0.6, 1.0];
this._pointLightLoc = [0.0, 0.0, 0.05];
// default material properties. Material properties should be overridden
// by the materials used by the objects
this._materialShininess = 20.0;
this._geomRoot = undefined;
this._cameraMat = Matrix.I(4);
this._cameraMat[14] = 5.0;
this._cameraMatInv = Matrix.I(4);
this._cameraMatInv[14] = -5.0;
this._camera = null;
// keep a flag indicating whether a render has been completed.
// this allows us to turn off automatic updating if there are
// no animated materials
this._firstRender = true;
this._worldCount = worldCounter;
worldCounter++;
// keep a counter for generating node names
this._nodeCounter = 0;
// for sending notifications to listeners
this._notifier = new Notifier();
///////////////////////////////////////////////////////////////////////
// Property accessors
///////////////////////////////////////////////////////////////////////
this.getGLContext = function() { return this._glContext; };
this.setGLContext = function(gl) { this._glContext = gl; };
this.get2DContext = function() { return this._2DContext; };
this.set2DContext = function(c) { this._2DContext = c; };
this.getCanvas = function() { return this._canvas; };
this.setCanvas = function(c) { this._canvas = c; };
this.getShaderProgram = function() { return this._shaderProgram; };
this.getViewportWidth = function() { return this._viewportWidth; };
this.getViewportHeight = function() { return this._viewportHeight; };
this.getAspect = function() { return this._viewportWidth/this._viewportHeight; };
this.getGeomRoot = function() { return this._geomRoot; };
this.getZNear = function() { return this._zNear; };
this.getZFar = function() { return this._zFar; };
this.getFOV = function() { return this._fov; };
this.getCamera = function() { return this._camera; };
this.getCameraMat = function() { return this._cameraMat.slice(0); };
this.setCameraMat = function(c) { this._cameraMat = c.slice(0); this._cameraMatInv = glmat4.inverse(c, []); };
this.getCameraMatInverse = function() { return this._cameraMatInv.slice(0); };
this.getViewDistance = function() { return this._viewDist; };
this.getRootNode = function() { return this._rootNode; };
this.setRootNode = function(r) { this._rootNode = r; };
this.isWebGL = function() { return this._useWebGL; };
this.getRenderer = function() { return this.renderer; };
// Flag to play/pause animation at authortime
this._previewAnimation = true;
////////////////////////////////////////////////////////////////////////////////////
// RDGE
// local variables
this.myScene = null;
this.elapsed = 0;
this.light = null;
this.light2 = null;
this.fillShader = null;
this.strokeShader = null;
this.renderer = null;
// keep an array of texture maps that need to be loaded
this._texMapsToLoad = [];
this._allMapsLoaded = true;
// this is the node to which objects get hung
this._rootNode = null;
// set up the camera matrix
var camMat = Matrix.I(4);
camMat[14] = this.getViewDistance();
this.setCameraMat( camMat );
// post-load processing of the scene
this.init = function()
{
var ctx1 = RDGE.globals.engine.ctxMan.handleToObject(this._canvas.rdgeCtxHandle),
ctx2 = RDGE.globals.engine.getContext();
if (ctx1 != ctx2) console.log( "***** different contexts *****" );
this.renderer = ctx1.renderer;
this.renderer._world = this;
// create a camera, set its perspective, and then point it at the origin
var cam = new RDGE.camera();
this._camera = cam;
cam.setPerspective(this.getFOV(), this.getAspect(), this.getZNear(), this.getZFar());
cam.setLookAt([0, 0, this.getViewDistance()], [0, 0, 0], RDGE.vec3.up());
// make this camera the active camera
this.renderer.cameraManager().setActiveCamera(cam);
// change clear color
//this.renderer.setClearFlags(RDGE.globals.engine.getContext().DEPTH_BUFFER_BIT);
this.renderer.setClearColor([0.0, 0.0, 0.0, 0.0]);
//this.renderer.NinjaWorld = this;
// create an empty scene graph
this.myScene = new RDGE.SceneGraph();
// create some lights
// light 1
this.light = RDGE.createLightNode("myLight");
this.light.setPosition([0,0,1.2]);
this.light.setDiffuseColor([0.75,0.9,1.0,1.0]);
// light 2
this.light2 = RDGE.createLightNode("myLight2");
this.light2.setPosition([-0.5,0,1.2]);
this.light2.setDiffuseColor([1.0,0.9,0.75,1.0]);
// create a light transform
var lightTr = RDGE.createTransformNode("lightTr");
// create and attach a material - materials hold the light data
lightTr.attachMaterial(RDGE.createMaterialNode("lights"));
// enable light channels 1, 2 - channel 0 is used by the default shader
lightTr.materialNode.enableLightChannel(1, this.light);
lightTr.materialNode.enableLightChannel(2, this.light2);
// all added objects are parented to the light node
this._rootNode = lightTr;
// add the light node to the scene
this.myScene.addNode(lightTr);
// Add the scene to the engine - necessary if you want the engine to draw for you
//RDGE.globals.engine.AddScene("myScene" + this._canvas.id, this.myScene);
var name = this._canvas.getAttribute( "data-RDGE-id" );
RDGE.globals.engine.AddScene("myScene" + name, this.myScene);
};
// main code for handling user interaction and updating the scene
this.update = function(dt)
{
if (!dt) dt = 0.2;
dt = 0.01; // use our own internal throttle
this.elapsed += dt;
if (this._useWebGL)
{
// changed the global position uniform of light 0, another way to change behavior of a light
RDGE.rdgeGlobalParameters.u_light0Pos.set([5 * Math.cos(this.elapsed), 5 * Math.sin(this.elapsed), 20]);
// orbit the light nodes around the boxes
this.light.setPosition([1.2*Math.cos(this.elapsed*2.0), 1.2*Math.sin(this.elapsed*2.0), 1.2*Math.cos(this.elapsed*2.0)]);
this.light2.setPosition([-1.2*Math.cos(this.elapsed*2.0), 1.2*Math.sin(this.elapsed*2.0), -1.2*Math.cos(this.elapsed)]);
}
this.updateMaterials( this.getGeomRoot(), this.elapsed );
// now update all the nodes in the scene
if (this._useWebGL)
this.myScene.update(dt);
};
// defining the draw function to control how the scene is rendered
this.draw = function()
{
if (this._useWebGL)
{
RDGE.globals.engine.setContext( this._canvas.rdgeid );
var ctx = RDGE.globals.engine.getContext();
var renderer = ctx.renderer;
if (renderer.unloadedTextureCount <= 0)
{
renderer.disableCulling();
renderer._clear();
this.myScene.render();
if (this._firstRender)
{
this._notifier.sendNotification( this._notifier.FIRST_RENDER );
if (this._canvas.task)
{
this._firstRender = false;
if (!this.hasAnimatedMaterials() || !this._previewAnimation)
{
this._canvas.task.stop();
//this._renderCount = 10;
}
}
}
else if (this._renderCount >= 0)
{
if (this._canvas.task)
{
this._renderCount--;
if (this._renderCount <= 0)
{
this._canvas.task.stop();
}
}
}
}
}
else
{
this.render();
}
};
this.onRunState = function() {
// console.log( "GLWorld.onRunState" );
this.restartRenderLoop();
};
this.onLoadState = function() {
// console.log( "GLWorld.onLoadState" );
};
this.textureToLoad = function( texture )
{
if (!texture.previouslyReferenced)
{
var name = texture.lookUpName;
texture._world = this;
texture.callback = this.textureMapLoaded;
this._texMapsToLoad[name] = true;
this._allMapsLoaded = false;
// stop the draw loop until all textures have been loaded
this._canvas.task.stop();
}
};
this.textureMapLoaded = function( texture )
{
var world = texture._world;
if (!world) {
console.log( "**** loaded texture does not have world defined ****" );
return;
}
var name = texture.lookUpName;
if (!world._texMapsToLoad[name]) {
console.log( "loaded an unregistered texture map: " + name );
}
else {
//console.log( "loaded a registered texture map: " + name );
world._texMapsToLoad[name] = undefined;
}
// check if all the texture maps are loaded. if so, resume the render loop
world._allMapsLoaded = world.allTextureMapsLoaded();
if (world._allMapsLoaded) {
world._canvas.task.start();
}
};
this.allTextureMapsLoaded = function() {
for (var name in this._texMapsToLoad) {
var needsLoad = this._texMapsToLoad[name];
if (needsLoad) return false;
}
return true;
};
this.textureLoadedCallback = function( name ) {
// console.log( "*** material texture loaded: " + name );
var world = this._world;
if (!world) {
console.log( "**** world not defined for loaded texture map: " + name );
}
else {
world.textureMapLoaded( name );
}
};
this.hasAnimatedMaterials = function() {
var root = this.getGeomRoot();
var rtnVal = false;
if (root) {
rtnVal = this.hHasAnimatedMaterials( root );
this._hasAnimatedMaterials = rtnVal;
}
else
{
// currently...
// we set this case to true - cloud materials create a
// world with no objects but cloud materials animate.
// TODO - find a better way to do this
rtnVal = true;
this._hasAnimatedMaterials = true;
}
return rtnVal;
};
this.hHasAnimatedMaterials = function( obj ) {
if (obj) {
if (obj.getFillMaterial()) {
if (obj.getFillMaterial().isAnimated()) return true;
}
if (obj.getStrokeMaterial()) {
if (obj.getStrokeMaterial().isAnimated()) return true;
}
// do the sibling
var hasAnim = false;
if (obj.getNext()) hasAnim = this.hHasAnimatedMaterials( obj.getNext() );
if (hasAnim) return true;
if (obj.getChild()) hasAnim = this.hHasAnimatedMaterials( obj.getChild() );
if (hasAnim) return true;
}
return false;
};
this.generateUniqueNodeID = function() {
var str = "" + this._nodeCounter;
this._nodeCounter++;
return str;
};
this.addListener = function( obj, callbackFunc, calleeData )
{
this._notifier.addListener( obj, callbackFunc, calleeData );
}
this.removeListener = function( obj )
{
this._notifier.removeListener( obj );
}
// start RDGE passing your runtime object, and false to indicate we don't need a an initialization state
// in the case of a procedurally built scene an init state is not needed for loading data
this._canvas.rdgeid = this._canvas.getAttribute( "data-RDGE-id" );
if (this._useWebGL) {
rdgeStarted = true;
RDGE.globals.engine.unregisterCanvas( this._canvas );
RDGE.globals.engine.registerCanvas(this._canvas, this);
RDGE.RDGEStart( this._canvas );
this._canvas.task.stop()
}
};
///////////////////////////////////////////////////////////////////////
// Property Accessors
///////////////////////////////////////////////////////////////////////
World.prototype.getGeomRoot = function() {
return this._geomRoot;
};
///////////////////////////////////////////////////////////////////////
// Methods
///////////////////////////////////////////////////////////////////////
World.prototype.updateObject = function (obj) {
if (!this._useWebGL) return;
var prims = obj.getPrimitiveArray();
var materialNodes = obj.getMaterialNodeArray();
if (prims.length != materialNodes.length)
throw new Error("inconsistent material and primitive counts");
var nPrims = prims.length;
var ctrTrNode;
if (nPrims > 0)
{
ctrTrNode = obj.getTransformNode();
if (ctrTrNode == null) {
ctrTrNode = RDGE.createTransformNode("objRootNode_" + this._nodeCounter++);
this._rootNode.insertAsChild( ctrTrNode );
obj.setTransformNode( ctrTrNode );
}
ctrTrNode.meshes.forEach(
function(thisMesh) {
RDGE.globals.meshMan.deleteMesh(thisMesh.mesh.name);
}
);
ctrTrNode.meshes = [];
ctrTrNode.attachMeshNode(this.renderer.id + "_prim_" + this._nodeCounter++, prims[0]);
ctrTrNode.attachMaterial(materialNodes[0]);
}
var children = ctrTrNode.children;
for (var i = 1; i < nPrims; i++)
{
// get the next primitive
var prim = prims[i];
// get a previously created transform node. If the transform has not been created, create it
var childTrNode;
if (children && children.length >= i) {
childTrNode = children[i-1].transformNode;
childTrNode.meshes.forEach(
function(thisMesh)
{
RDGE.globals.meshMan.deleteMesh(thisMesh.mesh.name);
}
);
childTrNode.meshes = [];
}
else
{
childTrNode = RDGE.createTransformNode("objNode_" + this._nodeCounter++);
ctrTrNode.insertAsChild(childTrNode);
}
// attach the instanced box goe
childTrNode.attachMeshNode(this.renderer.id + "_prim_" + this._nodeCounter++, prim);
childTrNode.attachMaterial(materialNodes[i]);
}
// send a notification that the tree has changed
this._notifier.sendNotification( this._notifier.OBJECT_CHANGE );
};
World.prototype.addObject = function( obj )
{
if (!obj) return;
try {
// undefine all the links of the object
obj.setChild( undefined );
obj.setNext( undefined );
obj.setPrev( undefined );
obj.setParent( undefined );
obj.setWorld( this );
if (this._geomRoot == null)
{
this._geomRoot = obj;
}
else
{
var go = this._geomRoot;
while (go.getNext()) go = go.getNext();
go.setNext( obj );
obj.setPrev( go );
}
// build the WebGL buffers
if (this._useWebGL) {
obj.buildBuffers();
this.restartRenderLoop();
}
// send a notification that the tree has changed
this._notifier.sendNotification( this._notifier.OBJECT_CHANGE );
}
catch(e) {
alert( "Exception in GLWorld.addObject " + e );
}
};
World.prototype.restartRenderLoop = function()
{
//console.log( "restartRenderLoop" );
this._firstRender = true;
this._renderCount = -1;
if (this._canvas.task)
{
if (this._allMapsLoaded)
{
//console.log( "starting task" );
this._canvas.task.start();
}
else
{
//console.log( "stopping task" );
this._canvas.task.stop();
}
}
};
World.prototype.stop = function()
{
if (this._canvas && this._canvas.task)
this._canvas.task.stop();
}
World.prototype.start = function()
{
if (this._canvas && this._canvas.task)
this._canvas.task.start();
}
//append to the list of objects if obj doesn't already exist
//if obj exists, then don't add to list of objects
World.prototype.addIfNewObject = function (obj) {
if (!obj) return;
try {
obj.setWorld(this);
if (this._geomRoot == null)
{
this._geomRoot = obj;
}
else if (this._geomRoot !== obj)
{
var go = this._geomRoot;
while (go.getNext() && go.getNext() !== obj)
{
go = go.getNext();
}
if (go.getNext() === null)
{
// undefine all the links of the object
obj.setChild(undefined);
obj.setNext(undefined);
obj.setPrev(undefined);
obj.setParent(undefined);
go.setNext(obj);
obj.setPrev(go);
}
}
// build the WebGL buffers
if (this._useWebGL) {
obj.buildBuffers();
this.restartRenderLoop();
}
// send a notification that the tree has changed
this._notifier.sendNotification( this._notifier.OBJECT_CHANGE );
} catch (e) {
alert("Exception in GLWorld.addIfNewObject " + e);
}
};
World.prototype.clearTree = function()
{
this._notifier.sendNotification( this._notifier.OBJECT_DELETE );
if (this._useWebGL)
{
var root = this._rootNode;
root.children = new Array();
RDGE.globals.engine.unregisterCanvas( this._canvas.rdgeid );
this.update( 0 );
this.draw();
}
};
World.prototype.updateMaterials = function( obj, time ) {
if (!obj) return;
var matArray = obj.getMaterialArray();
if (matArray) {
var n = matArray.length;
for (var i=0; i 0)) {
mat = this._matStack[ this._matStack.length-1];
}
return mat;
};
World.prototype.popMatrix = function() {
if (this._matStack.length == 0) {
throw "Invalid popMatrix!";
}
return this._matStack.pop();
};
World.prototype.setMVMatrix = function() {
var mat = this.stackTop();
if (mat) {
var gl = this._glContext;
//var mvMatrix = this._cameraMatInv.multiply(mat);
var mvMatrix = glmat4.multiply( this._cameraMatInv, mat, []);
//var mat2 = mat.multiply( this._cameraMatInv );
gl.uniformMatrix4fv(this._shaderProgram.mvMatrixUniform, false, new Float32Array(mvMatrix));
var normalMatrix = mat3.create();
// RDGE.mat4.toInverseMat3(mvMatrix, normalMatrix);
// RDGE.mat4.toInverseMat3(new Float32Array(mvMatrix.flatten()), normalMatrix);
RDGE.mat4.toInverseMat3(new Float32Array(mvMatrix), normalMatrix);
mat3.transpose(normalMatrix);
gl.uniformMatrix3fv(this._shaderProgram.nMatrixUniform, false, normalMatrix);
}
};
World.prototype.makePerspectiveMatrix = function() {
return Matrix.makePerspective( this.getFOV(), this.getAspect(), this.getZNear(), this.getZFar() );
};
World.prototype.render = function()
{
if (!this._useWebGL)
{
// clear the context
var ctx = this.get2DContext();
if (!ctx) return;
ctx.clearRect(0, 0, this.getViewportWidth(), this.getViewportHeight());
// render the geometry
var root = this.getGeomRoot();
this.hRender( root );
}
else
{
RDGE.globals.engine.setContext( this._canvas.rdgeid );
//this.draw();
this.restartRenderLoop();
}
};
World.prototype.hRender = function( obj )
{
if (!obj) return;
obj.render();
this.hRender( obj.getChild() );
this.hRender( obj.getNext() );
};
World.prototype.setViewportFromCanvas = function(canvas) {
this._viewportWidth = canvas.width;
this._viewportHeight = canvas.height;
if (this._useWebGL) {
this._glContext.viewportWidth = canvas.width;
this._glContext.viewportHeight = canvas.height;
this.getCamera().setPerspective(this.getFOV(), this.getAspect(), this.getZNear(), this.getZFar());
this.renderer.setViewPort(0, 0, canvas.width, canvas.height);
}
};
World.prototype.getShapeFromPoint = function( offsetX, offsetY ) {
var x = offsetX/this._canvas.width;
var y = offsetY/this._canvas.height;
var go = this._geomRoot;
if(go.collidesWithPoint(x,y)) {
// console.log("collision found");
return go;
}
while (go.getNext()) {
go = go.getNext();
if(go.collidesWithPoint(x,y)) {
// console.log("collision found");
return go;
}
}
};
World.prototype.exportJSON = function ()
{
// world properties
var worldObj =
{
'version' : 1.1,
'id' : this.getCanvas().getAttribute( "data-RDGE-id" ),
'fov' : this._fov,
'zNear' : this._zNear,
'zFar' : this._zFar,
'viewDist' : this._viewDist,
'webGL' : this._useWebGL
};
// RDGE scenegraph
if (this._useWebGL)
worldObj.scenedata = this.myScene.exportJSON();
// object data
var strArray = [];
this.exportObjectsJSON( this._geomRoot, worldObj );
// You would think that the RDGE export function
// would not be destructive of the data. You would be wrong...
// We need to rebuild everything
if (this._useWebGL) {
if (worldObj.children && (worldObj.children.length >= 1)) {
this.rebuildTree(this._geomRoot);
this.restartRenderLoop();
}
}
// convert the object to a string
var jStr = JSON.stringify( worldObj );
// prepend some version information to the string.
// this string is also used to differentiate between JSON
// and pre-JSON versions of fileIO.
// the ending ';' in the version string is necessary
jStr = "v1.0;" + jStr;
return jStr;
};
World.prototype.rebuildTree = function (obj)
{
if (!obj) return;
obj.buildBuffers();
if (obj.getChild()) {
this.rebuildTree( obj.getChild () );
}
if (obj.getNext())
this.rebuildTree( obj.getNext() );
};
World.prototype.exportObjectsJSON = function( obj, parentObj )
{
if (!obj) return;
var jObj = obj.exportJSON();
if (!parentObj.children) parentObj.children = [];
parentObj.children.push( jObj );
if (obj.getChild()) {
this.exportObjectsJSON( obj.getChild (), jObj );
}
if (obj.getNext())
this.exportObjectsJSON( obj.getNext(), parentObj );
}
World.prototype.findTransformNodeByMaterial = function( materialNode, trNode )
{
//if (trNode == null) trNode = this._ctrNode;
if (trNode == null) trNode = this._rootNode;
if ( trNode.transformNode && (materialNode == trNode.transformNode.materialNode)) return trNode;
var rtnNode;
if (trNode.children != null)
{
var nKids = trNode.children.length;
for (var i=0; i