/* <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> */ var MaterialParser = require("js/lib/rdge/materials/material-parser").MaterialParser; var Material = require("js/lib/rdge/materials/material").Material; var UberMaterial = function UberMaterial() { /////////////////////////////////////////////////////////////////////// // Instance variables /////////////////////////////////////////////////////////////////////// this._name = "UberMaterial"; this._shaderName = "uber"; this.getShaderName = function () { return this._shaderName; }; // set some default values this._ambientColor = [0.0, 0.0, 0.0, 1.0]; this._diffuseColor = [1.0, 1.0, 1.0, 1.0]; this._specularColor = [1.0, 1.0, 1.0, 1.0]; this._specularPower = 32.0; this._environmentAmount = 0.2; // 0 .. 1 // set the default maps this._diffuseMapOb = { 'texture': 'assets/images/rocky-diffuse.jpg', 'wrap': 'REPEAT' }; this._normalMapOb = { 'texture': 'assets/images/rocky-normal.jpg', 'wrap': 'REPEAT' }; this._specularMapOb = { 'texture': 'assets/images/rocky-spec.jpg', 'wrap': 'REPEAT' }; this._environmentMapOb = { 'texture': 'assets/images/silver.png', 'wrap': 'CLAMP', 'envReflection': this._environmentAmount }; this._useDiffuseMap = true; this._useNormalMap = true; this._useSpecularMap = true; this._useEnvironmentMap = true; this._useLights = [true, true, true, true]; this._MAX_LIGHTS = 4; /////////////////////////////////////////////////////////////////////// // Material Property Accessors /////////////////////////////////////////////////////////////////////// this._propNames = ["ambientColor", "diffuseColor", "specularColor", "specularPower", "diffuseMap", "normalMap", "specularMap", "environmentMap", "environmentAmount"]; this._propLabels = ["Ambient Color", "Diffuse Color", "Specular Color", "Specular Power", "Texture Map", "Bump Map", "Specular Map", "Environment Map", "Environment Map Amount"]; this._propTypes = ["color", "color", "color", "float", "file", "file", "file", "file", "float"]; this._propValues = []; this._propValues[this._propNames[0]] = this._ambientColor.slice(0); this._propValues[this._propNames[1]] = this._diffuseColor.slice(0); this._propValues[this._propNames[2]] = this._specularColor.slice(0); this._propValues[this._propNames[3]] = this._specularPower; this._propValues[this._propNames[4]] = this._diffuseMapOb['texture']; this._propValues[this._propNames[5]] = this._normalMapOb['texture']; this._propValues[this._propNames[6]] = this._specularMapOb['texture']; this._propValues[this._propNames[7]] = this._environmentMapOb['texture']; this._propValues[this._propNames[8]] = this._environmentMapOb['envReflection']; this.setProperty = function (prop, value) { if (prop == "color") prop = "ambientColor"; var valid = this.validateProperty(prop, value); if (valid) { this._propValues[prop] = value; switch (prop) { case "diffuseMap": this.updateDiffuseMap(); break; case "normalMap": this.updateNormalMap(); break; case "specularMap": this.updateSpecularMap(); break; case "environmentMap": this.updateEnvironmentMap(); break; case "environmentAmount": this.updateEnvironmentAmount(value); break; case "specularPower": this.updateSpecularPower(value); break; case "ambientColor": this.updateAmbientColor(value); break; case "diffuseColor": this.updateDiffuseColor(value); break; case "specularColor": this.updateSpecularColor(value); break; } } }; /////////////////////////////////////////////////////////////////////// // define the 4 lights this._lights = [ { 'type': 'point', // can be 'directional', 'point' or 'spot' 'spotInnerCutoff': 14.0, // fragments in the inner cutoff 'cone' are full intensity. 'spotOuterCutoff': 15.0, // fragments outside the outer cutoff 'cone' are unlit. 'position': [8.0, 2.0, 8.0], // light position; ignored for directional lights 'direction': [-1.0, -1.0, -1.0], // light direction; ignored for point lights 'attenuation': [1.0, 0.025, 0.00125], // light attenuation; constant, linear, quadratic 'diffuseColor': [1.0, 0.5, 0.5, 1.0], // diffuse light color 'specularColor': [1.0, 1.0, 1.0, 1.0] // specular light color }, { 'type': 'point', 'spotInnerCutoff': 9.0, 'spotOuterCutoff': 20.0, 'position': [-8.0, 2.0, 8.0], 'direction': [1.0, -1.0, -1.0], 'attenuation': [1.0, 0.025, 0.00125], 'diffuseColor': [0.5, 1.0, 0.5, 1.0], 'specularColor': [1.0, 1.0, 1.0, 1.0] }, { 'type': 'point', 'spotInnerCutoff': 9.0, 'spotOuterCutoff': 20.0, 'position': [-8.0, 2.0, -8.0], 'direction': [1.0, -1.0, 1.0], 'attenuation': [1.0, 0.25, 0.0125], 'diffuseColor': [0.5, 0.5, 1.0, 1.0], 'specularColor': [1.0, 1.0, 1.0, 1.0] }, { 'type': 'point', 'spotInnerCutoff': 9.0, 'spotOuterCutoff': 20.0, 'position': [8.0, 4.0, -8.0], 'direction': [-1.0, -1.0, 1.0], 'attenuation': [1.0, 0.25, 0.0125], 'diffuseColor': [1.0, 1.0, 0.5, 1.0], 'specularColor': [1.0, 1.0, 1.0, 1.0] } ]; this._ubershaderCaps = { // ubershader material properties. 'material': { 'ambientColor': this._ambientColor, // material ambient color 'diffuseColor': this._diffuseColor, // material diffuse color 'specularColor': this._specularColor, // material specular color 'specularPower': this._specularPower // material specular power (shininess) }, // ubershader supports up to four lights. 'lighting': { 'light0': this._lights[0], 'light1': this._lights[1], 'light2': this._lights[2], 'light3': this._lights[3] }, // uvTransform can be used to scale or offset the texture coordinates. 'uvTransform': [2.0, 0, 0, 0, 0, 2.0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1], // optional diffuse map 'diffuseMap': this._diffuseMapOb, // optional normal map 'normalMap': this._normalMapOb, // optional specular map 'specularMap': this._specularMapOb, // optional environment map 'environmentMap': this._environmentMapOb }; this.updateAmbientColor = function () { this._ambientColor = this._propValues['ambientColor'].slice(0); var material = this._materialNode; if (material) { var technique = material.shaderProgram.defaultTechnique; technique.u_ambientColor.set(this._ambientColor); } }; this.updateDiffuseColor = function () { this._diffuseColor = this._propValues['diffuseColor'].slice(0); var material = this._materialNode; if (material) { var technique = material.shaderProgram.defaultTechnique; technique.u_diffuseColor.set(this._diffuseColor); } }; this.updateSpecularColor = function (value) { this._specularColor = this._propValues['specularColor']; var material = this._materialNode; if (material) { var technique = material.shaderProgram.defaultTechnique; technique.u_specularColor.set(this._specularColor); } }; this.updateSpecularPower = function (value) { this._specularPower = this._propValues['specularPower']; var material = this._materialNode; if (material) { var technique = material.shaderProgram.defaultTechnique; technique.u_specularPower.set([this._specularPower]); } }; this.updateEnvironmentAmount = function (value) { this._environmentMapOb.envReflectionAmount = value; var material = this._materialNode; if (material) { var technique = material.shaderProgram.defaultTechnique; technique.u_envReflection.set([this._environmentMapOb.envReflection]); } }; this.updateEnvironmentMap = function () { var value = this._propValues["environmentMap"]; this._environmentMapOb.texture = value; if ((value == null) || (value.length == 0)) { if (this._useEnvironmentMap) { this._useEnvironmentMap = false; this.rebuildShader(); } } else { if (!this._useEnvironmentMap) { this._useEnvironmentMap = true; this.rebuildShader(); } else { var material = this._materialNode; if (material) { var technique = material.shaderProgram.defaultTechnique; var renderer = RDGE.globals.engine.getContext().renderer; if (renderer && technique) { var tex = renderer.getTextureByName(value, caps.environmentMap.wrap); this.registerTexture(tex); technique.s_environmentMap.set(tex); } } } } }; this.updateDiffuseMap = function (value) { var value = this._propValues["diffuseMap"]; this._diffuseMapOb.texture = value; if ((value == null) || (value.length == 0)) { if (this._useDiffuseMap) { this._useDiffuseMap = false; this.rebuildShader(); } } else { if (!this._useDiffuseMap) { this._useDiffuseMap = true; this.rebuildShader(); } else { var material = this._materialNode; if (material) { var technique = material.shaderProgram.defaultTechnique; var renderer = RDGE.globals.engine.getContext().renderer; if (renderer && technique) { var tex = renderer.getTextureByName(value, caps.diffuseMap.wrap); this.registerTexture(tex); technique.s_diffuseMap.set(tex); } } } } }; this.updateSpecularMap = function () { var value = this._propValues["specularMap"]; this._specularMapOb.texture = value; if ((value == null) || (value.length == 0)) { if (this._useSpecularMap) { this._useSpecularMap = false; this.rebuildShader(); } } else { if (!this._useSpecularMap) { this._useSpecularMap = true; this.rebuildShader(); } else { var material = this._materialNode; if (material) { var technique = material.shaderProgram.defaultTechnique; var renderer = RDGE.globals.engine.getContext().renderer; if (renderer && technique) { var tex = renderer.getTextureByName(value, caps.specularMap.wrap); this.registerTexture(tex); technique.s_specularMap.set(tex); } } } } }; this.updateNormalMap = function (value) { var value = this._propValues["normalMap"]; this._normalMapOb.texture = value; if ((value == null) || (value.length == 0)) { if (this._useNormalMap) { this._useNormalMap = false; this.rebuildShader(); } } else { if (!this._useNormalMap) { this._useNormalMap = true; this.rebuildShader(); } else { var material = this._materialNode; if (material) { var technique = material.shaderProgram.defaultTechnique; var renderer = RDGE.globals.engine.getContext().renderer; if (renderer && technique) { var tex = renderer.getTextureByName(value, caps.normalMap.wrap); this.registerTexture(tex); technique.s_normalMap.set(tex); } } } } }; // duplcate method requirde this.dup = function () { // allocate a new uber material var newMat = new UberMaterial(); newMat._useDiffuseMap = this._useDiffuseMap; newMat._useEnvironmentMap = this._useEnvironmentMap; newMat._useLights = this._useLights; newMat._useNormalMap = this._useNormalMap; newMat._useSpecularMap = this._useSpecularMap; newMat.rebuildShader(); // copy over the current values; var propNames = [], propValues = [], propTypes = [], propLabels = []; this.getAllProperties(propNames, propValues, propTypes, propLabels); var n = propNames.length; for (var i = 0; i < n; i++) newMat.setProperty(propNames[i], propValues[i]); return newMat; }; this.init = function (world) { // save the world if (world) this.setWorld(world); // set up the shader this._shader = this.buildUberShader(this._ubershaderCaps); // set up the material node this._materialNode = RDGE.createMaterialNode("uberMaterial" + "_" + world.generateUniqueNodeID()); this._materialNode.setShader(this._shader); }; this.importJSON = function (jObj) { if (this.getShaderName() != jObj.material) throw new Error("ill-formed material"); this.setName(jObj.name); if (jObj.materialProps) { var ambientColor = jObj.materialProps.ambientColor; this.setProperty("ambientColor", ambientColor); var diffuseColor = jObj.materialProps.diffuseColor; this.setProperty("diffuseColor", diffuseColor); var specularColor = jObj.materialProps.specularColor; this.setProperty("specularColor", specularColor); var specularPower = jObj.materialProps.specularPower; this.setProperty("specularPower", specularPower); } var lightArray = jObj.lights; if (lightArray) { this._lights = []; for (var i = 0; i < this._MAX_LIGHTS; i++) { var lightObj = lightArray[i]; if (lightObj) { var type = lightObj['light' + i]; if (type) { var light = new Object; switch (type) { case "directional": light.direction = lightObj['light' + i + 'Dir']; break; case "spot": light.position = lightObj['light' + i + 'Pos']; light['spotInnerCutoff'] = lightObj['light' + i + 'OuterSpotCutoff']; light['spotOuterCutoff'] = lightObj['light' + i + 'InnerSpotCutoff']; break; case "point": light.position = lightObj['light' + i + 'Pos']; light.attenuation = lightObj['light' + i + 'Attenuation']; break; default: throw new Error("unrecognized light type on import: " + type); break; } // common to all lights light.diffuseColor = lightObj['light' + i + 'Color']; light.specularColor = lightObj['light' + i + 'SpecularColor']; // push the light this._lights.push(light); } else this._lights[i] = 'undefined'; } } if (this._lights.length > 0) { this._ubershaderCaps.lighting = { 'light0': this._lights[0], 'light1': this._lights[1], 'light2': this._lights[2], 'light3': this._lights[3] } } } var diffuseMap = jObj['diffuseMap']; if (diffuseMap) { this.setProperty("diffuseMap", diffuseMap); } var normalMap = jObj['normalMap']; if (normalMap) { this.setProperty("normalMap", normalMap); } var specularMap = jObj['specularMap']; if (specularMap) { this.setProperty("specularMap", specularMap); } var environmentMap = jObj['environmentMap']; if (environmentMap) { this.setProperty("environmentMap", environmentMap); this.setProperty("environmentAmount", jObj['environmentAmount']); } this.rebuildShader(); }; this.exportJSON = function () { // every material needs the base type and instance name var caps = this._ubershaderCaps; var jObj = { 'material': this.getShaderName(), 'name': this.getName() }; // export the material properties if (typeof caps.material != 'undefined') { jObj.materialProps = { 'ambientColor': this._ambientColor, 'diffuseColor': this._diffuseColor, 'specularColor': this._specularColor, 'specularPower': this._specularPower }; } if (typeof caps.lighting != 'undefined') { var lightArray = []; for (var i = 0; i < this._MAX_LIGHTS; i++) { var light = caps.lighting["light" + i]; if (typeof light != "undefined") { var lightObj = {} lightObj['light' + i] = light.type; // output the light secific data if (light.type === 'directional') { lightObj['light' + i + 'Dir'] = light['direction']; } else if (light.type === 'spot') { lightObj['light' + i + 'Pos'] = light['position']; lightObj['light' + i + 'SpotInnerCutoff'] = light['spotInnerCutoff']; lightObj['light' + i + 'SpotOuterCutoff'] = light['spotOuterCutoff']; } else // light.type === 'point' { lightObj['light' + i + 'Pos'] = (light['position'] || [0, 0, 0]); lightObj['light' + i + 'Attenuation'] = (light['attenuation'] || [1, 0, 0]); } // common to all lights lightObj['light' + i + 'Color'] = (light['diffuseColor'] || [1, 1, 1, 1]); lightObj['light' + i + 'SpecularColor'] = (light['specularColor'] || [1, 1, 1, 1]); lightArray.push(lightObj); } } if (lightArray.length > 0) jObj.lights = lightArray; } if (typeof caps.diffuseMap != 'undefined') { jObj['diffuseMap'] = caps.diffuseMap.texture; } if (typeof caps.normalMap != 'undefined') { jObj['normalMap'] = caps.normalMap.texture; } if (typeof caps.specularMap != 'undefined') { jObj['specularMap'] = caps.specularMap.texture; } if (typeof caps.environmentMap != 'undefined') { jObj['environmentMap'] = caps.environmentMap.texture; jObj['environmentAmount'] = caps.environmentMap.envReflection; } return jObj; }; this.buildUberShader = function (caps) { var preproc = ""; var paramBlock = {}; paramBlock['u_uvMatrix'] = { 'type': 'mat4' }; if (typeof caps.material != 'undefined') { preproc += '#define MATERIAL\n'; paramBlock['u_ambientColor'] = { 'type': 'vec4' }; paramBlock['u_diffuseColor'] = { 'type': 'vec4' }; paramBlock['u_specularColor'] = { 'type': 'vec4' }; paramBlock['u_specularPower'] = { 'type': 'float' }; } if (typeof caps.lighting != 'undefined') { preproc += '#define LIGHTING\n'; preproc += '#define SPECULAR\n'; for (var i = 0; i < 4; ++i) { var light = caps.lighting['light' + i]; var t; if (typeof light != 'undefined') { switch (light.type) { case 'directional': t = 0; break; case 'point': t = 1; break; case 'spot': t = 2; break; } preproc += '#define LIGHT_' + i + ' ' + t + '\n'; preproc += '#define LIGHT_' + i + '_SPECULAR\n'; if (t == 0 || t == 2) { paramBlock['u_light' + i + 'Dir'] = { 'type': 'vec3' }; } if (t == 2) { paramBlock['u_light' + i + 'Spot'] = { 'type': 'vec2' }; } paramBlock['u_light' + i + 'Pos'] = { 'type': 'vec3' }; paramBlock['u_light' + i + 'Color'] = { 'type': 'vec4' }; paramBlock['u_light' + i + 'Atten'] = { 'type': 'vec3' }; paramBlock['u_light' + i + 'Specular'] = { 'type': 'vec4' }; } } } if (typeof caps.diffuseMap != 'undefined') { preproc += '#define DIFFUSE_MAP\n'; paramBlock['s_diffuseMap'] = { 'type': 'tex2d' }; } if (typeof caps.normalMap != 'undefined') { preproc += '#define NORMAL_MAP\n'; paramBlock['s_normalMap'] = { 'type': 'tex2d' }; } if (typeof caps.specularMap != 'undefined') { preproc += '#define SPECULAR_MAP\n'; paramBlock['s_specMap'] = { 'type': 'tex2d' }; } if (typeof caps.environmentMap != 'undefined') { preproc += '#define ENVIRONMENT_MAP\n'; paramBlock['s_envMap'] = { 'type': 'tex2d' }; paramBlock['u_envReflection'] = { 'type': 'float' }; } // load the shaders as text var uberVShader, uberFShader; var r = new XMLHttpRequest(); r.open('GET', "assets/shaders/ub_vshader.glsl", false); r.send(null); if (r.status == 200) { uberVShader = r.responseText; } r.open('GET', "assets/shaders/ub_fshader.glsl", false); r.send(null); if (r.status == 200) { uberFShader = r.responseText; } // prefix preprocessor settings var vshader = preproc + uberVShader; var fshader = preproc + uberFShader; // build output jshader var uberJShader = new RDGE.jshader(); uberJShader.def = { 'shaders': { 'defaultVShader': vshader, 'defaultFShader': fshader }, 'techniques': { 'defaultTechnique': [{ 'vshader': 'defaultVShader', 'fshader': 'defaultFShader', 'attributes': { 'a_pos': { 'type': 'vec3' }, 'a_normal': { 'type': 'vec3' }, 'a_texcoord': { 'type': 'vec2' } }, 'params': paramBlock, 'states': { 'depthEnable': true, 'blendEnable': false, 'culling': true, 'cullFace': "FRONT" } }] } }; // initialize the jshader try { uberJShader.init(); } catch (e) { console.log("error initializing the uber shader: " + e); } // initialize shader parameters var technique = uberJShader.defaultTechnique; if (typeof caps.material != 'undefined') { technique.u_ambientColor.set(caps.material.ambientColor); technique.u_diffuseColor.set(caps.material.diffuseColor); technique.u_specularColor.set(caps.material.specularColor); technique.u_specularPower.set([caps.material.specularPower]); } if (typeof caps.lighting != 'undefined') { for (i = 0; i < 4; ++i) { var light = caps.lighting["light" + i]; if (typeof light != "undefined") { if (light.type == 'directional') { paramBlock['u_light' + i + 'Dir'] = { 'type': 'vec3' }; technique['u_light' + i + 'Dir'].set(light['direction'] || [0, 0, 1]); } else if (light.type == 'spot') { paramBlock['u_light' + i + 'Spot'] = { 'type': 'vec2' }; technique['u_light' + i + 'Pos'].set(light['position'] || [0, 0, 0]); var deg2Rad = Math.PI / 180; technique['u_light' + i + 'Spot'].set([Math.cos((light['spotInnerCutoff'] || 45.0) * deg2Rad), Math.cos((light['spotOuterCutoff'] || 90.0) * deg2Rad)]); technique['u_light' + i + 'Atten'].set(light['attenuation'] || [1, 0, 0]); } else { technique['u_light' + i + 'Pos'].set(light['position'] || [0, 0, 0]); technique['u_light' + i + 'Atten'].set(light['attenuation'] || [1, 0, 0]); } technique['u_light' + i + 'Color'].set(light['diffuseColor'] || [1, 1, 1, 1]); technique['u_light' + i + 'Specular'].set(light['specularColor'] || [1, 1, 1, 1]); } } } technique.u_uvMatrix.set(caps.uvTransform || RDGE.mat4.identity()); var renderer = RDGE.globals.engine.getContext().renderer; if (this._useDiffuseMap) { var tex = renderer.getTextureByName(caps.diffuseMap.texture, caps.diffuseMap.wrap, caps.diffuseMap.mips); this.registerTexture(tex); technique.s_diffuseMap.set(tex); } if (this._useNormalMap) { var tex = renderer.getTextureByName(caps.normalMap.texture, caps.normalMap.wrap, caps.normalMap.mips); this.registerTexture(tex); technique.s_normalMap.set(tex); } if (this._useSpecularMap) { var tex = renderer.getTextureByName(caps.specularMap.texture, caps.specularMap.wrap); this.registerTexture(tex); technique.s_specMap.set(tex); } if (this._useEnvironmentMap) { var tex = renderer.getTextureByName(caps.environmentMap.texture, caps.environmentMap.wrap); this.registerTexture(tex); technique.s_envMap.set(tex); technique.u_envReflection.set([caps.environmentMap.envReflection || 1.0]); } return uberJShader; }; this.rebuildShader = function () { this._ubershaderCaps['material']['ambientColor'] = this._ambientColor; this._ubershaderCaps['material']['diffuseColor'] = this._diffuseColor; this._ubershaderCaps['material']['specularColor'] = this._specularColor; this._ubershaderCaps['material']['specularPower'] = this._specularPower; var useDiffuse = this._useDiffuseMap; if (!useDiffuse) { if (typeof this._ubershaderCaps['diffuseMap'] != 'undefined') { delete this._ubershaderCaps['diffuseMap']; } } else { this._ubershaderCaps['diffuseMap'] = this._diffuseMapOb; } var useNormal = this._useNormalMap; if (!useNormal) { if (typeof this._ubershaderCaps['normalMap'] != 'undefined') { delete this._ubershaderCaps['normalMap']; } } else { this._ubershaderCaps['normalMap'] = this._normalMapOb; } var useSpecular = this._useSpecularMap; if (!useSpecular) { if (typeof this._ubershaderCaps['specularMap'] != 'undefined') { delete this._ubershaderCaps['specularMap']; } } else { this._ubershaderCaps['specularMap'] = this._specularMapOb; } var useEnvironment = this._useEnvironmentMap; if (!useEnvironment) { if (typeof this._ubershaderCaps['environmentMap'] != 'undefined') { delete this._ubershaderCaps['environmentMap']; } } else { this._ubershaderCaps['environmentMap'] = this._environmentMapOb; } for (i = 0; i < 4; ++i) { var useLight = this._useLights[i]; if (!useLight) { if (typeof this._ubershaderCaps['lighting']['light' + i] != 'undefined') { delete this._ubershaderCaps['lighting']['light' + i]; } } else { this._ubershaderCaps['lighting']['light' + i] = this._lights[i]; } } var material = this._materialNode; if (material) { material.setShader(buildUbershader(this._ubershaderCaps)); } }; }; UberMaterial.prototype = new Material(); if (typeof exports === "object") { exports.UberMaterial = UberMaterial; }