/* 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. */ var MaterialParser = require("js/lib/rdge/materials/material-parser").MaterialParser; var Material = require("js/lib/rdge/materials/material").Material; var GLWorld = require("js/lib/drawing/world").World; var Texture = require("js/lib/rdge/texture").Texture; var ElementMediator = require("js/mediators/element-mediator").ElementMediator; var TagTool = require("js/tools/TagTool").TagTool; /////////////////////////////////////////////////////////////////////// // Class GLMaterial // RDGE representation of a material. /////////////////////////////////////////////////////////////////////// var CloudMaterial = function CloudMaterial() { /////////////////////////////////////////////////////////////////////// // Instance variables /////////////////////////////////////////////////////////////////////// this._name = "Cloud"; this._shaderName = "cloud"; this._texMap = 'assets/images/cloud10.png'; this._texMap = 'assets/images/cloud2.jpg'; //this._texMap = 'assets/images/CL13.png'; //this._texMap = 'assets/images/material_paint.png'; //this._texMap = 'assets/images/us_flag.png'; //this._texMap = 'assets/images/cubelight.png'; this._diffuseColor = [0.5, 0.5, 0.5, 0.5]; // base size of cloud polygons. Random adjustments made to each quad this._cloudSize = 40; this._time = 0.0; this._dTime = 0.01; // parameter initial values this._time = 0.0; this._surfaceAlpha = 0.5; // this._zmin = 2.0; // this._zmax = 5.0; this._zmin = 5.0; this._zmax = 10.0; // the adjusted zMin and zMax values are // what get sent to the shader. They are initialized // in buildGeometry this._adjustedZMin = this._zmin; this._adjustedZMax = this._zmax; /////////////////////////////////////////////////////////////////////// // Property Accessors /////////////////////////////////////////////////////////////////////// this.getName = function () { return this._name; }; this.getShaderName = function () { return this._shaderName; }; this.getTextureMap = function () { return this._propValues[this._propNames[0]] ? this._propValues[this._propNames[0]].slice() : null }; this.setTextureMap = function (m) { this._propValues[this._propNames[0]] = m ? m.slice(0) : null; this.updateTexture(); }; this.setDiffuseColor = function (c) { this._propValues[this._propNames[1]] = c.slice(0); this.updateColor(); }; this.getDiffuseColor = function () { return this._propValues[this._propNames[1]] ? this._propValues[this._propNames[1]].slice() : null; }; this.isAnimated = function () { return true; }; /////////////////////////////////////////////////////////////////////// // Material Property Accessors /////////////////////////////////////////////////////////////////////// this._propNames = ["texmap", "diffusecolor"]; this._propLabels = ["Texture map", "Diffuse Color"]; this._propTypes = ["file", "color"]; this._propValues = []; this._propValues[this._propNames[0]] = this._texMap.slice(0); this._propValues[this._propNames[1]] = this._diffuseColor.slice(); this.setProperty = function (prop, value) { if (prop === 'color') prop = 'diffusecolor'; // make sure we have legitimate imput var ok = this.validateProperty(prop, value); if (!ok) { console.log("invalid property in Radial Gradient Material:" + prop + " : " + value); } switch (prop) { case "texmap": this.setTextureMap(value); break; case "diffusecolor": this.setDiffuseColor(value); break; case "color": break; } }; /////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////// // Methods /////////////////////////////////////////////////////////////////////// // duplicate method required /**************************************************************/ this.init = function (world) { var GLWorld = require("js/lib/drawing/world").World, NJUtils = require("js/lib/NJUtils").NJUtils; // save the world if (world) this.setWorld( world ); var dstWorld = world; // create a canvas to render into var dstCanvas = this.getWorld().getCanvas(); var doc = this.getWorld().getCanvas().ownerDocument; var canvasID = "__canvas__"; //this._srcCanvas = doc.createElement(canvasID); this._srcCanvas = NJUtils.makeNJElement("canvas", canvasID, "shape", {"data-RDGE-id": NJUtils.generateRandom()}, true); srcCanvas = this._srcCanvas; srcCanvas.width = dstCanvas.width; srcCanvas.height = dstCanvas.height; ////////////////////////////////////////////////////////////////////////////////// // IS THIS NECESSARY?? // var elementModel = TagTool.makeElement(~~srcCanvas.width, ~~srcCanvas.height, // Matrix.I(4), [0,0,0], srcCanvas); // ElementMediator.addElement(srcCanvas, elementModel.data, true); ////////////////////////////////////////////////////////////////////////////////// // build the source. // the source being the world/canvas/geometry of the clouds. // the source is used to create a texture map that is then used by // the destimation. this.buildSource(); // set up the shader this._shader = new RDGE.jshader(); this._shader.def = cloudMapMaterialDef; this._shader.init(); // set up the material node this._materialNode = RDGE.createMaterialNode("cloudMapMaterial" + "_" + world.generateUniqueNodeID()); this._materialNode.setShader(this._shader); // initialize the time this._time = 0; // create the texture to map the source cloud generation world/canvas to the destination var wrap = 'REPEAT', mips = true; this._glTex = new Texture( world, this._srcCanvas, wrap, mips ); // set the shader values in the shader this.updateTexture(); this.update( 0 ); }; /**************************************************************/ this.updateTexture = function () { var material = this._materialNode; if (material) { var technique = material.shaderProgram['default']; var saveContext = RDGE.globals.engine.getContext(); var renderer = RDGE.globals.engine.getContext().renderer; if (renderer && technique) { var texMapName = this._propValues[this._propNames[0]]; var wrap = 'REPEAT', mips = true; if (this._glTex) { this._glTex.render(); var tex = this._glTex.getTexture(); if (tex) technique.u_tex0.set( tex ); } } // restore the context RDGE.globals.engine.setContext( saveContext.id ); } }; this.updateColor = function() { } this.update = function( time ) { if (this._srcWorld) { //this._srcWorld.update(); this._srcWorld.draw(); RDGE.globals.engine.setContext( this.getWorld()._canvas.rdgeid ); } var technique, renderer, tex; // update the cloud map material var material = this._materialNode; if (material) { technique = material.shaderProgram['default']; renderer = RDGE.globals.engine.getContext().renderer; if (renderer && technique) { if (this._glTex) { this._glTex.render(); tex = this._glTex.getTexture(); technique.u_tex0.set( tex ); } } } // update the source material material = this._srcMaterialNode; if (material) { technique = material.shaderProgram['default']; renderer = RDGE.globals.engine.getContext().renderer; if (renderer && technique) { technique.u_time.set( [this._time] ); this._time += this._dTime; } } }; this.buildSource = function() { // save the current RDGE context so we can reset it later var saveContext = RDGE.globals.engine.getContext(); this.getWorld().stop(); // build a world to do the rendering if (!GLWorld) GLWorld = require("js/lib/drawing/world").World; this._srcWorld = new GLWorld( this._srcCanvas, true, true ); var srcWorld = this._srcWorld; if (!this._srcCanvas) throw new Error( "No source canvas in Cloud material" ); this._srcCanvas.__GLWorld = srcWorld; // build the geometry var prim = this.buildGeometry( srcWorld, srcCanvas.width, srcCanvas.height ); // set up the shader var shader = new RDGE.jshader(); shader.def = cloudMaterialDef; shader.init(); this._srcShader = shader; // set up the material node var materialNode = RDGE.createMaterialNode("cloudMaterial" + "_" + srcWorld.generateUniqueNodeID()); materialNode.setShader(shader); this._srcMaterialNode = materialNode; // add the nodes to the tree var trNode = RDGE.createTransformNode("objRootNode_" + srcWorld._nodeCounter++); srcWorld._rootNode.insertAsChild( trNode ); trNode.attachMeshNode(srcWorld.renderer.id + "_prim_" + srcWorld._nodeCounter++, prim); trNode.attachMaterial( materialNode ); // initialize the shader uniforms this._time = 0; if (shader['default']) { var t = shader['default']; if (t) { t.u_time.set( [this._time] ); t.u_surfaceAlpha.set( [this._surfaceAlpha] ); t.u_zmin.set( [this._adjustedZMin] ); t.u_zmax.set( [this._adjustedZMax] ); var wrap = 'REPEAT', mips = true; var texMapName = this._propValues[this._propNames[0]]; var tex = srcWorld.renderer.getTextureByName(texMapName, wrap, mips ); if (tex) { srcWorld.textureToLoad( tex ); t.u_tex0.set( tex ); } } } // start the render loop on the source canvas srcWorld.restartRenderLoop(); // restore the original context RDGE.globals.engine.setContext( saveContext.id ); this.getWorld().start(); }; this.buildGeometry = function(world, canvasWidth, canvasHeight) { var RectangleGeometry = require("js/lib/geom/rectangle").RectangleGeometry; RectangleGeometry.init(); // get the normalized device coordinates (NDC) for // all position and dimensions. var vpw = world.getViewportWidth(), vph = world.getViewportHeight(); var xNDC = 0.0/vpw, yNDC = 0.0/vph, xFillNDC = canvasWidth/vpw, yFillNDC = canvasHeight/vph; var aspect = world.getAspect(); var zn = world.getZNear(), zf = world.getZFar(); var t = zn * Math.tan(world.getFOV() * Math.PI / 360.0), b = -t, r = aspect*t, l = -r; // calculate the object coordinates from their NDC coordinates var z = -world.getViewDistance(); // get the position of the origin var x = -z*(r-l)/(2.0*zn)*xNDC, y = -z*(t-b)/(2.0*zn)*yNDC; // get the x and y fill var hWidth = -z*(r-l)/(2.0*zn)*xFillNDC, hHeight = -z*(t-b)/(2.0*zn)*yFillNDC; //this.createFill([x,y], 2*xFill, 2*yFill, tlRadius, blRadius, brRadius, trRadius, fillMaterial); var ctr = [x,y], width = 2*hWidth, height = 2*hHeight; var cloudSize = width > height ? 0.25*width : 0.25*height; var left = x - hHeight, top = y - hHeight; // get the GL projection matrix so wecan calculate the z values from the user input z values var zNear = world.getZNear(), zFar = world.getZFar(); var viewDist = world.getViewDistance(); var projMat = Matrix.makePerspective( world.getFOV(), world.getAspect(), world.getZNear(), world.getZFar()); var camMat = world.getCameraMat(); var camMatInv = glmat4.inverse( camMat, [] ); var glCompleteMat = glmat4.multiply( projMat, camMatInv, [] ); var zw1_c = MathUtils.transformAndDivideHomogeneousPoint( [0,0, -zNear + viewDist], glCompleteMat )[2], zw2_c = MathUtils.transformAndDivideHomogeneousPoint( [0,0, -zFar + viewDist], glCompleteMat )[2]; var glCompleteMatInv = glmat4.inverse( glCompleteMat, [] ); var zMin = MathUtils.transformAndDivideHomogeneousPoint( [0,0, -this._zmin + viewDist], glCompleteMat )[2], zMax = MathUtils.transformAndDivideHomogeneousPoint( [0,0, -this._zmax + viewDist], glCompleteMat )[2]; zMax = -this._zmin + viewDist; zMin = -this._zmax + viewDist; dz = zMax - zMin; // the adjusted values are what get sent to the shader this._adjustedZMin = zMin; this._adjustedZMax = zMax; // build the polygons var verts = [], normals = [ [0,0,1], [0,0,1], [0,0,1], [0,0,1] ], uvs = [ [0,0], [1,0], [1,1], [0,1] ]; for ( i = 0; i < 20; i++ ) { // var x = hWidth*2*(Math.random() - 0.5), // y = hHeight*2.0*(Math.random() - 0.5), var x = left + Math.random()*width, y = top + Math.random()*height, z = zMin + Math.random()*dz; zRot = (Math.random() - 0.5) * Math.PI, sz = cloudSize * Math.random(); //x = 0.0; y = 0.0; z = 0.0; //zRot = 0.0; //z = 0; verts[0] = [-sz, -sz, 0]; verts[1] = [-sz, sz, 0]; verts[2] = [ sz, sz, 0]; verts[3] = [ sz, -sz, 0]; var rotMat = Matrix.RotationZ( zRot ); var transMat = Matrix.Translation( [x,y,z] ); var mat = glmat4.multiply( transMat, rotMat, [] ); glmat4.multiplyVec3( mat, verts[0] ); glmat4.multiplyVec3( mat, verts[1] ); glmat4.multiplyVec3( mat, verts[2] ); glmat4.multiplyVec3( mat, verts[3] ); var tmp0 = MathUtils.transformAndDivideHomogeneousPoint( verts[0], glCompleteMat ), tmp1 = MathUtils.transformAndDivideHomogeneousPoint( verts[1], glCompleteMat ), tmp2 = MathUtils.transformAndDivideHomogeneousPoint( verts[2], glCompleteMat ), tmp3 = MathUtils.transformAndDivideHomogeneousPoint( verts[3], glCompleteMat ); RectangleGeometry.addQuad( verts, normals, uvs ); } return RectangleGeometry.buildPrimitive(); }; // JSON export this.exportJSON = function () { var jObj = { 'material': this.getShaderName(), 'name': this.getName(), 'texture': this._propValues[this._propNames[0]] }; return jObj; }; this.importJSON = function (jObj) { if (this.getShaderName() != jObj.material) throw new Error("ill-formed material"); this.setName(jObj.name); try { this._propValues[this._propNames[0]] = jObj.texture; } catch (e) { throw new Error("could not import material: " + jObj); } }; }; /////////////////////////////////////////////////////////////////////////////////////// // RDGE shader // the cloud material def is used for cloud generation on the // local world created by the cloud material. var cloudMaterialDef = { 'shaders': { 'defaultVShader': "assets/shaders/Cloud.vert.glsl", 'defaultFShader': "assets/shaders/Cloud.frag.glsl" }, 'techniques': { 'default': [ { 'vshader': 'defaultVShader', 'fshader': 'defaultFShader', // attributes 'attributes': { 'vert': { 'type': 'vec3' }, 'normal': { 'type': 'vec3' }, 'texcoord': { 'type': 'vec2' } }, // parameters 'params': { 'u_tex0' : { 'type' : 'tex2d' }, 'u_time' : { 'type' : 'float' }, 'u_surfaceAlpha' : { 'type' : 'float' }, 'u_zmin' : { 'type' : 'float' }, 'u_zmax' : { 'type' : 'float' } }, // render states 'states': { 'depthEnable': true, 'offset': [1.0, 0.1] } } ] } }; // the cloud map material def is used to map the cloud image onto // the destination geometry var cloudMapMaterialDef = {'shaders': { 'defaultVShader':"assets/shaders/Basic.vert.glsl", 'defaultFShader':"assets/shaders/BasicTex.frag.glsl" }, 'techniques': { 'default': [ { 'vshader' : 'defaultVShader', 'fshader' : 'defaultFShader', // attributes 'attributes' : { 'vert' : { 'type' : 'vec3' }, 'normal' : { 'type' : 'vec3' }, 'texcoord' : { 'type' : 'vec2' } }, // parameters 'params' : { 'u_tex0' : { 'type' : 'tex2d' }, }, // render states 'states' : { 'depthEnable' : true, 'offset':[1.0, 0.1] } } ] } }; CloudMaterial.prototype = new Material(); if (typeof exports === "object") { exports.CloudMaterial = CloudMaterial; }