/* * Copyright 2009, Google Inc. * 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 Google Inc. 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 * OWNER 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. */ /** * @fileoverview This file contains functions to make it extremely simple * to get something on the screen in o3d. The disadvantage is it * is less flexible and creates inefficient assets. * * Example * *
 * <html><body>
 * <script type="text/javascript" src="o3djs/all.js">
 * </script>
 * <script type="text/javascript">
 * window.init = init;
 *
 * function init() {
 *   o3djs.base.makeClients(initStep2);
 * }
 *
 * function initStep2(clientElements) {
 *   var clientElement = clientElements[0];
 *
 *   // Create an o3djs.simple object to manage things in a simple way.
 *   g_simple = o3djs.simple.create(clientElement);
 *
 *   // Create a cube.
 *   g_cube = g_simple.createCube(50);
 *
 *   // DONE!
 * }
 * </script>
 * <div id="o3d" style="width: 600px; height: 600px"></div>
 * </body></html>
 * 
* * Some more examples: * * g_cube.setDiffuseColor(1, 0, 0, 1); // Cube is now red. * g_cube.transform.translate(10, 0, 0); // Cube translates. * g_cube.loadTexture('http://google.com/someimage.jpg"); // Cube is textured * * * Note: This library is only a sample. It is not meant to be some official * library. It is provided only as example code. * */ o3djs.provide('o3djs.simple'); o3djs.require('o3djs.math'); o3djs.require('o3djs.material'); o3djs.require('o3djs.effect'); o3djs.require('o3djs.shape'); o3djs.require('o3djs.util'); o3djs.require('o3djs.rendergraph'); o3djs.require('o3djs.pack'); o3djs.require('o3djs.primitives'); o3djs.require('o3djs.io'); o3djs.require('o3djs.scene'); o3djs.require('o3djs.camera'); /** * A Module for using o3d in a very simple way. * @namespace */ o3djs.simple = o3djs.simple || {}; /** * Creates an o3djs.simple library object that helps manage o3d * for the extremely simple cases. * *
 * <html><body>
 * <script type="text/javascript" src="o3djs/all.js">
 * </script>
 * <script type="text/javascript">
 * windows.onload = init;
 *
 * function init() {
 *   o3djs.base.makeClients(initStep2);
 * }
 *
 * function initStep2(clientElements) {
 *   var clientElement = clientElements[0];
 *
 *   // Create an o3djs.simple object to manage things in a simple way.
 *   g_simple = o3djs.simple.create(clientElement);
 *
 *   // Create a cube.
 *   g_cube = g_simple.createCube(50);
 *
 *   // DONE!
 * }
 * </script>
 * <div id="o3d" style="width: 600px; height: 600px"></div>
 * </body></html>
 * 
* * @param {!Element} clientObject O3D.Plugin Object. * @return {!o3djs.simple.SimpleInfo} Javascript object that hold info for the * simple library. * */ o3djs.simple.create = function(clientObject) { return new o3djs.simple.SimpleInfo(clientObject); }; /** * A SimpleInfo contains information for the simple library. * @constructor * @param {!Element} clientObject O3D.Plugin Object. */ o3djs.simple.SimpleInfo = function(clientObject) { /** * The O3D Element. * @type {!Element} */ this.clientObject = clientObject; /** * The O3D namespace object. * @type {!o3d} */ this.o3d = clientObject.o3d; /** * The client object used by the SimpleInfo * @type {!o3d.Client} */ this.client = clientObject.client; /** * The main pack for this SimpleInfo. * @type {!o3d.Pack} */ this.pack = this.client.createPack(); /** * The root transform for this SimpleInfo * @type {!o3d.Transform} */ this.root = this.pack.createObject('Transform'); /** * The ViewInfo created by this SimpleInfo. * @type {!o3djs.rendergraph.ViewInfo} */ this.viewInfo = o3djs.rendergraph.createBasicView( this.pack, this.root, this.client.renderGraphRoot); /** * The list of objects that need to have an update function called indexed by * id. * @private * @type {!Object.} */ this.updateObjects_ = { }; /** * The next available id for objects. * @private * @type {number} */ this.nextId_ = 1; // Create 1 non-textured material and 1 textured material. // // TODO: Refactor. // This is slightly backward. What we really want is to be able to request // an effect of a specific type from our shader builder but the current shader // builder expects a material to already exist. So, we create a material here // just to pass it to the shader builder, then we keep the effect it created // but throw away the material. // // TODO: Fix shader builder so it creates diffuseColorMult, // diffuseColorOffset and diffuseTexture so // diffuse = diffuseTexture * diffuseColorMult + diffuseColorOffset. var material = this.pack.createObject('Material'); o3djs.effect.attachStandardShader(this.pack, material, [0, 0, 0], 'phong'); this.nonTexturedEffect_ = material.effect; this.pack.removeObject(material); var material = this.pack.createObject('Material'); var samplerParam = material.createParam('diffuseSampler', 'ParamSampler'); o3djs.effect.attachStandardShader(this.pack, material, [0, 0, 0], 'phong'); this.texturedEffect_ = material.effect; this.pack.removeObject(material); this.globalParamObject = this.pack.createObject('ParamObject'); this.lightWorldPosParam = this.globalParamObject.createParam('lightWorldPos', 'ParamFloat3'); this.lightColorParam = this.globalParamObject.createParam('lightColor', 'ParamFloat4'); this.setLightColor(1, 1, 1, 1); this.setLightPosition(255, 150, 150); // same as camera. // Attempt to setup a resonable default perspective matrix. this.zNear = 0.1; this.zFar = 1000; this.fieldOfView = o3djs.math.degToRad(45); this.setPerspectiveMatrix_(); // Attempt to setup a resonable default view. this.cameraPosition = [250, 150, 150]; this.cameraTarget = [0, 0, 0]; this.cameraUp = [0, 1, 0]; this.setViewMatrix_(); var that = this; this.client.setRenderCallback(function(renderEvent) { var elapsedTime = Math.min(renderEvent.elapsedTime, 0.1); that.onRender_(elapsedTime); }); }; /** * Gets the next available id. * @return {number} The next available id. */ o3djs.simple.SimpleInfo.prototype.getNextId = function() { return this.nextId_++; }; /** * Creates a SimpleShape. A SimpleShape manages a transform with 1 shape that * holds 1 primitive and 1 unique material. * @param {!o3d.Shape} shape that holds 1 primitive and 1 unique material. * @return {!o3djs.simple.SimpleShape} the created SimpleShape. */ o3djs.simple.SimpleInfo.prototype.createSimpleShape = function(shape) { shape.createDrawElements(this.pack, null); var transform = this.pack.createObject('Transform'); transform.parent = this.root; transform.addShape(shape); return new o3djs.simple.SimpleShape(this, transform); }; /** * The on render handler for a SimpleInfo. * @private * @param {number} elapsedTime Time elapsed since last frame. */ o3djs.simple.SimpleInfo.prototype.onRender_ = function(elapsedTime) { for (var sid in this.updateObjects_) { var id = /** @type {number} */ (sid); this.updateObjects_[id].onUpdate(elapsedTime); } }; /** * Register an object for updating. You should not call this directly. * @param {!o3djs.simple.SimpleObject} simpleObject SimpleObject to register. */ o3djs.simple.SimpleInfo.prototype.registerObjectForUpdate = function (simpleObject) { this.updateObjects_[simpleObject.id] = simpleObject; }; /** * Unregister an object for updating. You should not call this directly. * @param {!o3djs.simple.SimpleObject} simpleObject SimpleObject to register. */ o3djs.simple.SimpleInfo.prototype.unregisterObjectForUpdate = function (simpleObject) { delete this.updateObjects_[simpleObject.id]; }; /** * Sets the perspective matrix. * @private */ o3djs.simple.SimpleInfo.prototype.setPerspectiveMatrix_ = function() { this.viewInfo.drawContext.projection = o3djs.math.matrix4.perspective( this.fieldOfView, this.client.width / this.client.height, this.zNear, this.zFar); }; /** * Sets the view matrix. * @private */ o3djs.simple.SimpleInfo.prototype.setViewMatrix_ = function() { this.viewInfo.drawContext.view = o3djs.math.matrix4.lookAt( this.cameraPosition, this.cameraTarget, this.cameraUp); }; /** * Sets the field of view * @param {number} fieldOfView in Radians. * * For degrees use setFieldOfView(o3djs.math.degToRad(degrees)). */ o3djs.simple.SimpleInfo.prototype.setFieldOfView = function(fieldOfView) { this.fieldOfView = fieldOfView; this.setPerspectiveMatrix_(); }; /** * Sets the z clip range. * @param {number} zNear near z value. * @param {number} zFar far z value. */ o3djs.simple.SimpleInfo.prototype.setZClip = function(zNear, zFar) { this.zNear = zNear; this.zFar = zFar; this.setPerspectiveMatrix_(); }; /** * Sets the light position * @param {number} x x position. * @param {number} y y position. * @param {number} z z position. */ o3djs.simple.SimpleInfo.prototype.setLightPosition = function(x, y, z) { this.lightWorldPosParam.set(x, y, z); }; /** * Sets the light color * @param {number} r red (0-1). * @param {number} g green (0-1). * @param {number} b blue (0-1). * @param {number} a alpha (0-1). */ o3djs.simple.SimpleInfo.prototype.setLightColor = function(r, g, b, a) { this.lightColorParam.set(r, g, b, a); }; /** * Sets the camera position * @param {number} x x position. * @param {number} y y position. * @param {number} z z position. */ o3djs.simple.SimpleInfo.prototype.setCameraPosition = function(x, y, z) { this.cameraPosition = [x, y, z]; this.setViewMatrix_(); }; /** * Sets the camera target * @param {number} x x position. * @param {number} y y position. * @param {number} z z position. */ o3djs.simple.SimpleInfo.prototype.setCameraTarget = function(x, y, z) { this.cameraTarget = [x, y, z]; this.setViewMatrix_(); }; /** * Sets the camera up * @param {number} x x position. * @param {number} y y position. * @param {number} z z position. */ o3djs.simple.SimpleInfo.prototype.setCameraUp = function(x, y, z) { this.cameraUp = [x, y, z]; this.setViewMatrix_(); }; /** * Create meterial from effect. * @param {!o3d.Effect} effect Effect to use for material. * @return {!o3d.Material} The created material. */ o3djs.simple.SimpleInfo.prototype.createMaterialFromEffect = function(effect) { var material = this.pack.createObject('Material'); material.drawList = this.viewInfo.performanceDrawList; material.effect = effect; effect.createUniformParameters(material); material.getParam('lightWorldPos').bind(this.lightWorldPosParam); material.getParam('lightColor').bind(this.lightColorParam); return material; }; /** * Create a new non-textured material. * @param {string} type Type of material 'phong', 'lambert', 'constant'. * @return {!o3d.Material} The created material. */ o3djs.simple.SimpleInfo.prototype.createNonTexturedMaterial = function(type) { var material = this.createMaterialFromEffect(this.nonTexturedEffect_); material.getParam('diffuse').set(1, 1, 1, 1); material.getParam('emissive').set(0, 0, 0, 1); material.getParam('ambient').set(0, 0, 0, 1); material.getParam('specular').set(1, 1, 1, 1); material.getParam('shininess').value = 20; return material; }; /** * @param {string} type Type of material 'phong', 'lambert', 'constant'. * @return {!o3d.Material} The created material. */ o3djs.simple.SimpleInfo.prototype.createTexturedMaterial = function(type) { var material = this.createMaterialFromEffect(this.texturedEffect_); var samplerParam = material.getParam('diffuseSampler'); var sampler = this.pack.createObject('Sampler'); samplerParam.value = sampler; return material; }; /** * Creates a cube and adds it to the root of this SimpleInfo's transform graph. * @param {number} size Width, height and depth of the cube. * @return {!o3djs.simple.SimpleShape} A Javascript object to manage the * shape. */ o3djs.simple.SimpleInfo.prototype.createCube = function(size) { var material = this.createNonTexturedMaterial('phong'); var shape = o3djs.primitives.createCube(this.pack, material, size); return this.createSimpleShape(shape); }; /** * Creates a box and adds it to the root of this SimpleInfo's transform graph. * @param {number} width Width of the box. * @param {number} height Height of the box. * @param {number} depth Depth of the box. * @return {!o3djs.simple.SimpleShape} A Javascript object to manage the * shape. */ o3djs.simple.SimpleInfo.prototype.createBox = function(width, height, depth) { var material = this.createNonTexturedMaterial('phong'); var shape = o3djs.primitives.createBox(this.pack, material, width, height, depth); return this.createSimpleShape(shape); }; /** * Creates a sphere and adds it to the root of this SimpleInfo's transform * graph. * @param {number} radius radius of sphere. * @param {number} smoothness determines the number of subdivisions. * @return {!o3djs.simple.SimpleShape} A Javascript object to manage the * shape. */ o3djs.simple.SimpleInfo.prototype.createSphere = function(radius, smoothness) { var material = this.createNonTexturedMaterial('phong'); var shape = o3djs.primitives.createSphere(this.pack, material, radius, smoothness * 2, smoothness); return this.createSimpleShape(shape); }; /** * Loads a scene from a URL. * @param {string} url Url of scene to load. * @param {!function(o3djs.simple.SimpleScene, *): void} callback a callback to * call when the scene is loaded. The first argument will be null if the * scene failed to load and last object will be an exception. * @return {!o3djs.io.LoadInfo} */ o3djs.simple.SimpleInfo.prototype.loadScene = function(url, callback) { var pack = this.client.createPack(); var root = pack.createObject('Transform'); var paramObject = pack.createObject('ParamObject'); var animTimeParam = paramObject.createParam('animTime', 'ParamFloat'); var that = this; var prepScene = function(pack, root, exception) { var simpleScene = null; if (exception) { pack.destroy(); } else { simpleScene = new o3djs.simple.SimpleScene( that, url, pack, root, paramObject); } callback(simpleScene, exception); }; return o3djs.scene.loadScene( this.client, pack, root, url, prepScene, /** @type {!o3djs.serialization.Options} */ ({opt_animSource: animTimeParam})); }; /** * Moves the camera so everything in the current scene is visible. */ o3djs.simple.SimpleInfo.prototype.viewAll = function() { var bbox = o3djs.util.getBoundingBoxOfTree(this.root); var target = o3djs.math.lerpVector(bbox.minExtent, bbox.maxExtent, 0.5); this.setCameraTarget(target[0], target[1], target[2]); // TODO: Refactor this so it takes a vector from the current camera // position to the center of the scene and moves the camera along that // vector away from the center of the scene until for the given fieldOfView // everything is visible. var diag = o3djs.math.distance(bbox.minExtent, bbox.maxExtent); var eye = o3djs.math.addVector(target, [ bbox.maxExtent[0], bbox.minExtent[1] + 0.5 * diag, bbox.maxExtent[2]]); this.setCameraPosition(eye[0], eye[1], eye[2]); this.setZClip(diag / 1000, diag * 10); }; /** * An object for managing things simply. * @constructor */ o3djs.simple.SimpleObject = function() { }; /** * Initializes a SimpleObject. * @param {!o3djs.simple.SimpleInfo} simpleInfo The SimpleInfo to manage this * object. * @param {!o3d.Transform} transform Transform that orients this object. */ o3djs.simple.SimpleObject.prototype.init = function(simpleInfo, transform) { /** * The SimpleInfo managing this object. * @type {!o3djs.simple.SimpleInfo} */ this.simpleInfo = simpleInfo; /** * The Id for this SimpleInfo. * @type {number} */ this.id = simpleInfo.getNextId(); /** * The Transform that orients this object. * @type {!o3d.Transform} */ this.transform = transform; /** * The update callback for this object. * @private * @type {?function(number): void} */ this.updateCallback_ = null; /** * The pick callback for this object. * @private * @type {?function(number): void} */ this.pickCallback_ = null; }; /** * Registers on an on picked callback. * @param {!function(!o3djs.simple.SimpleObject): void} onPickedCallback A * function called when this object is picked. */ o3djs.simple.SimpleObject.prototype.onPicked = function(onPickedCallback) { throw 'not implemented'; }; /** * Used to call the update callback on this object. You should not call this * directly. Use o3djs.simple.SimpleObject.setOnUpdate to add your own update * callback. * @param {number} elapsedTime ElapsedTime in seconds for this frame. * @see o3djs.simple.SimpleObject.setOnUpdate */ o3djs.simple.SimpleObject.prototype.onUpdate = function(elapsedTime) { if (this.updateCallback_) { this.updateCallback_(elapsedTime); } }; /** * Sets a function to be called every frame for this object. * @param {function(number): void} onUpdateCallback A function that is passed * the elapsed time in seconds. Pass in null to clear the callback function. * @return {(function(number): void|null)} The previous callback function. */ o3djs.simple.SimpleObject.prototype.setOnUpdate = function(onUpdateCallback) { if (onUpdateCallback) { this.simpleInfo.registerObjectForUpdate(this); } else { this.simpleInfo.unregisterObjectForUpdate(this); } var oldCallback = this.updateCallback_; this.updateCallback_ = onUpdateCallback; return oldCallback; }; /** * A SimpleShape manages a transform with 1 shape that holds 1 primitive * and 1 unique material. * @constructor * @extends {o3djs.simple.SimpleObject} * @param {!o3djs.simple.SimpleInfo} simpleInfo The SimpleInfo to manage this * shape. * @param {!o3d.Transform} transform Transform with 1 shape that holds 1 * primitive and 1 unique material. */ o3djs.simple.SimpleShape = function(simpleInfo, transform) { this.init(simpleInfo, transform); }; o3djs.simple.SimpleShape.prototype = new o3djs.simple.SimpleObject(); /** * Gets the current material for this shape. * @return {o3d.Material} the material for this SimpleShape. */ o3djs.simple.SimpleShape.prototype.getMaterial = function() { return this.transform.shapes[0].elements[0].material; }; /** * Sets the material for this SimpleShape, deleting any old one. * @param {!o3d.Material} material new material. */ o3djs.simple.SimpleShape.prototype.setMaterial = function(material) { var old_material = this.getMaterial(); if (old_material != null) { this.simpleInfo.pack.removeObject(old_material); } this.transform.shapes[0].elements[0].material = material; }; /** * Sets the diffuse color of this shape. * @param {number} r Red (0-1). * @param {number} g Green (0-1). * @param {number} b Blue (0-1). * @param {number} a Alpha (0-1). */ o3djs.simple.SimpleShape.prototype.setDiffuseColor = function(r, g, b, a) { var material = this.getMaterial(); material.getParam('diffuse').set(r, g, b, a); if (a < 1) { material.drawList = this.simpleInfo.viewInfo.zOrderedDrawList; } else { material.drawList = this.simpleInfo.viewInfo.performanceDrawList; } }; /** * Gets the texture on this shape. * @return {o3d.Texture} The texture on this shape. May be null. */ o3djs.simple.SimpleShape.prototype.getTexture = function() { var material = this.getMaterial(); var samplerParam = material.getParam('diffuseSampler'); if (samplerParam.className == 'o3d.ParamSampler') { return samplerParam.texture; } return null; }; /** * Loads a texture onto the given shape. It will replace the material * if it needs to with one that supports a texture. Note that the texture * is loaded asynchronously and so the result of this call may appear several * seconds after it is called depending on how long it takes to download the * texture. * @param {string} url Url of texture. */ o3djs.simple.SimpleShape.prototype.loadTexture = function(url) { var that = this; o3djs.io.loadTexture( this.simpleInfo.pack, url, function(texture, exception) { if (!exception) { // See if this is a textured material. var material = that.getMaterial(); if (material.effect != that.simpleInfo.texturedEffect_) { // replace the material with a textured one. var new_material = that.simpleInfo.createTexturedMaterial('phong'); new_material.copyParams(material); // Reset the effect since copy Params just copied the non-textured // one. new_material.effect = that.simpleInfo.texturedEffect_; that.setMaterial(new_material); material = new_material; } var samplerParam = material.getParam('diffuseSampler'); samplerParam.value.texture = texture; } else { alert('Load texture file returned failure. \n' + exception); } }); }; /** * An object to simply manage a scene. * @constructor * @extends {o3djs.simple.SimpleObject} * @param {!o3djs.simple.SimpleInfo} simpleInfo The SimpleInfo to manage this * scene. * @param {string} url Url scene was loaded from. * @param {!o3d.Pack} pack Pack that is managing scene. * @param {!o3d.Transform} root Root transform of scene. * @param {!o3d.ParamObject} paramObject the holds global parameters. */ o3djs.simple.SimpleScene = function( simpleInfo, url, pack, root, paramObject) { this.init(simpleInfo, root); /** * The url this scene was loaded from. * @type {string} */ this.url = url; /** * The pack managing this scene. * @type {!o3d.Pack} */ this.pack = pack; /** * The param object holding global parameters for this scene. * @type {!o3d.ParamObject} */ this.paramObject = paramObject; /** * The animation parameter for this scene. * @type {!o3d.ParamFloat} */ this.animTimeParam = paramObject.getParam('animTime'); o3djs.pack.preparePack(pack, simpleInfo.viewInfo); this.cameraInfos_ = o3djs.camera.getCameraInfos( root, simpleInfo.client.width, simpleInfo.client.height); /** * Binds a param if it exists. * @param {!o3d.ParamObject} paramObject The object that has the param. * @param {string} paramName name of param. * @param {!o3d.Param} sourceParam The param to bind to. */ var bindParam = function(paramObject, paramName, sourceParam) { var param = paramObject.getParam(paramName); if (param) { param.bind(sourceParam); } } var materials = pack.getObjectsByClassName('o3d.Material'); for (var m = 0; m < materials.length; ++m) { var material = materials[m]; bindParam(material, 'lightWorldPos', simpleInfo.lightWorldPosParam); bindParam(material, 'lightColor', simpleInfo.lightColorParam); } this.transform.parent = this.simpleInfo.root; }; o3djs.simple.SimpleScene.prototype = new o3djs.simple.SimpleObject(); /** * Sets the animation time for the scene. * @param {number} time Animation time in seconds. */ o3djs.simple.SimpleScene.prototype.setAnimTime = function(time) { this.animTimeParam.value = time; };