diff options
author | pathorn@chromium.org <pathorn@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-16 20:31:14 +0000 |
---|---|---|
committer | pathorn@chromium.org <pathorn@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-16 20:31:14 +0000 |
commit | f18031bef950ee217798ba1e20a0f20cef9c3571 (patch) | |
tree | cb001d43c5bcd8422d5e17ce7e4b9efa881e5d67 /o3d/samples | |
parent | 4dcbc1b552d96791b13dc3d876868dae3f8ad09d (diff) | |
download | chromium_src-f18031bef950ee217798ba1e20a0f20cef9c3571.zip chromium_src-f18031bef950ee217798ba1e20a0f20cef9c3571.tar.gz chromium_src-f18031bef950ee217798ba1e20a0f20cef9c3571.tar.bz2 |
This adds four files ported from their C++ counterparts: animation.js, counter.js, curve.js and param_object.js.
BUG=
TEST=o3d-webgl-samples/animations.html works
Review URL: http://codereview.chromium.org/2856032
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@52731 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'o3d/samples')
-rw-r--r-- | o3d/samples/o3d-webgl-samples/animation.html | 343 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/base.js | 4 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/client.js | 9 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/counter.js | 814 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/curve.js | 902 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/function.js | 161 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/param_operation.js | 338 |
7 files changed, 2571 insertions, 0 deletions
diff --git a/o3d/samples/o3d-webgl-samples/animation.html b/o3d/samples/o3d-webgl-samples/animation.html new file mode 100644 index 0000000..f7ffd81 --- /dev/null +++ b/o3d/samples/o3d-webgl-samples/animation.html @@ -0,0 +1,343 @@ +<!-- +Copyright 2010, 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. +--> + +<!-- +O3D Animation. + +Shows various things being animated by O3D. +--> +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" + "http://www.w3.org/TR/html4/loose.dtd"> +<html> +<head> +<meta http-equiv="content-type" content="text/html; charset=UTF-8"> +<title> +Animation. +</title> +<!-- Include sample javascript library functions--> +<script type="text/javascript" src="../o3d-webgl/base.js"></script> +<script type="text/javascript" src="../o3djs/base.js"></script> + +<!-- Our javascript code --> +<script type="text/javascript" id="o3dscript"> +o3djs.base.o3d = o3d; +o3djs.require('o3djs.webgl'); +o3djs.require('o3djs.math'); +o3djs.require('o3djs.material'); +o3djs.require('o3djs.rendergraph'); +o3djs.require('o3djs.primitives'); + +// Events +// init() once the page has finished loading. +window.onload = init; + +// global variables +var g_framesRendered = 0; +var g_o3d; +var g_math; +var g_client; +var g_viewInfo; +var g_pack; +var g_finished = false; // for selenium +var g_groupTransforms = []; +var GROUPS_ACROSS = 2; +var UNITS_ACROSS_GROUP = 2; +var TOTAL_ACROSS = GROUPS_ACROSS * UNITS_ACROSS_GROUP; +var HALF_WIDTH = TOTAL_ACROSS * 0.0; +var UNIT_SPACING = 200; +// Set this to true to run a semi-automated test of counter callbacks. +var RUN_COUNTER_TESTS = location.search.indexOf("test")!=-1; + +/** + * Creates an oscillating animation to animate a single float from 0 to + * endOutput over endInput seconds with a little bit of ease in, ease out. + * + * @param {!o3d.Pack} pack Pack to associate created objects with. + * @param {!o3d.ParamObject} paramObject Object that has param to animate. + * @param {string} paramName Name of the param to animate. + * @param {number} endInput Number of seconds to take to get + * @param {number} endOutput Target value. + */ +function attachParamFloatAnimation(pack, + paramObject, + paramName, + endInput, + endOutput) { + // Create a FunctionEval through which to evaluate the curve. + var functionEval = pack.createObject('FunctionEval'); + + // Bind the param we want to get its value from our FunctionEval's output. + paramObject.getParam(paramName).bind(functionEval.getParam('output')); + + // Create a curve + var curve = pack.createObject('Curve'); + + // Set the functionEval to use the curve as it's function. + functionEval.functionObject = curve; + + // Create 2 keys for the curve. + var key1 = curve.createKey('BezierCurveKey'); + key1.input = 0; + key1.output = 0; + key1.outTangent = [0, endInput * 2 / 3]; + var key2 = curve.createKey('BezierCurveKey'); + key2.inTangent = [endInput / 3, endOutput]; + key2.input = endInput; + key2.output = endOutput; + + // Set the curve to oscillate. + curve.postInfinity = g_o3d.Curve.OSCILLATE; + curve.preInfinity = g_o3d.Curve.LINEAR; + + // Make a SecondCounter to provide an input to the functionEval. + var counter = pack.createObject('SecondCounter'); + + // Bind the counter's count to the input of the FunctionEval. + functionEval.getParam('input').bind(counter.getParam('count')); + return counter; +} + +function runCounterTest(counter) { + counter.count = 0; + counter.addCallback(.5, function(){ + document.body.appendChild(document.createTextNode( + " a="+counter.count)); + counter.count = counter.count; + counter.countMode = counter.forward?o3d.Counter.CONTINUOUS + :o3d.Counter.ONCE; + counter.end = 9.5; + counter.addCallback(9.5, function(){ + document.body.appendChild(document.createTextNode(" Test finished:"+ + "Stop at 9.5 count="+counter.count+" running="+counter.running)); + if (counter.running == false) { + counter.running = true; + counter.multiplier = 1; + counter.countMode = o3d.Counter.CONTINUOUS; + counter.removeAllCallbacks(); + counter.setCount(0); + } + }) + counter.start = -3; + }); + counter.addCallback(1, function(){ + document.body.appendChild(document.createTextNode( + " b="+counter.count)); + counter.count = counter.forward?1.5:.5; + }); + counter.addCallback(1.5, function(){ + document.body.appendChild(document.createTextNode( + " c="+counter.count)); + }); + counter.addCallback(2, function(){ + document.body.appendChild(document.createTextNode( + " d="+counter.count)); + counter.forward = false; + counter.multiplier = 10; + }); + counter.addCallback(-2, function(){ + counter.forward = true; + counter.removeCallback(.5); + counter.removeCallback(-2); + counter.removeCallback(2); + }); + //counter.advance(100); +} + +/** + * Creates an oscillating animation to animate a single float of a float4 from 0 + * to endOutput over endInput seconds with a little bit of ease in, ease out. + * + * @param {!o3d.Pack} pack Pack to associate created objects with. + * @param {!o3d.ParamObject} paramObject Object that has param to animate. + * @param {string} paramName Name of the param Float4 to animate. + * @param {string} innerParamName Name of the individual float to animate. + * @param {number} endInput Duration of animation. + * @param {number} endOutput Target value. + * @return {!o3d.ParamObject} The created ParamOperation. + */ +function attach1FloatOfFloat4Animation(pack, + paramObject, + paramName, + innerParamName, + endInput, + endOutput) { + + var paramOp = pack.createObject('ParamOp4FloatsToFloat4'); + paramObject.getParam(paramName).bind(paramOp.getParam('output')); + attachParamFloatAnimation(pack, paramOp, innerParamName, endInput, endOutput); + return paramOp; +} + +/** + * Creates an animation to animate one of the 9 values of a TRSToMatrix4 and + * binds it to a transform. + * + * @param {!o3d.Pack} pack Pack to associate created objects with. + * @param {!o3d.Transform} transform Transform to animate. + * @param {number} endInput Duration of animation. + * @param {number} endOutput Target value. + * @param {string} paramName Name of param to animate. + * @return {!o3d.TRSToMatrix4} The created TRSToMatrix4. + */ +function attachTRSAnimation(pack, transform, endInput, endOutput, paramName) { + var trs = pack.createObject('TRSToMatrix4'); + transform.getParam('localMatrix').bind(trs.getParam('output')); + var c = attachParamFloatAnimation(pack, trs, paramName, endInput, endOutput); + if (RUN_COUNTER_TESTS) + runCounterTest(c); + return trs; +} + +/** + * Creates the client area. + */ +function init() { + o3djs.webgl.makeClients(initStep2); +} + +/** + * Initializes O3D and sets up some shapes with animations. + */ +function initStep2(clientElements) { + // Initializes global variables and libraries. + var o3dElement = clientElements[0]; + g_o3d = o3dElement.o3d; + g_math = o3djs.math; + g_client = o3dElement.client; + + // Creates a pack to manage our resources/assets + g_pack = g_client.createPack(); + + g_viewInfo = o3djs.rendergraph.createBasicView( + g_pack, + g_client.root, + g_client.renderGraphRoot); + + // Create our projection matrix, with a vertical field of view of 45 + // degrees a near clipping plane of 0.1 and far clipping plane of 10000. + g_viewInfo.drawContext.projection = g_math.matrix4.perspective( + g_math.degToRad(45), + g_client.width / g_client.height, + 0.1, + 10000); + + // Set our view + g_viewInfo.drawContext.view = g_math.matrix4.lookAt( + [100, 50, 400], // Eye. + [0, 0, 0], // Target. + [0, 1, 0]); // Up. + + // Create a basic material + var material = o3djs.material.createBasicMaterial( + g_pack, + g_viewInfo, + [1, 1, 1, 1], + true); + + var data = [ { paramName: 'translateY', + endOutput: 50, + color: [1, 0, 0, 1] }, + { paramName: 'rotateY', + endOutput: Math.PI, + color: [0, 1, 0, 1] }, + { paramName: 'scaleY', + endOutput: 3, + color: [1, 1, 0, 1] }, + { paramName: '', + endOutput: Math.PI * 3, + color: [1, 1, 0, 1] } ]; + + for (var ii = 0; ii < 4; ++ii) { + var xPos = (ii - 1.5) * 100; + // Create a shape. + var shape; + switch (ii) { + case 0: + case 2: + shape = o3djs.primitives.createSphere(g_pack, material, 40, 10, 12); + break; + case 1: + case 3: + shape = o3djs.primitives.createCube(g_pack, material, 60); + break; + } + + var transform = g_pack.createObject('Transform'); + transform.parent = g_client.root; + transform.addShape(shape); + + // Change the color of each one + transform.createParam('diffuse', 'ParamFloat4').value = data[ii].color; + + switch (ii) { + case 0: + case 1: + case 2: + var trs = attachTRSAnimation(g_pack, + transform, + ii * 0.6 + 0.5, + data[ii].endOutput, + data[ii].paramName); + + // space them out. + trs.translateX = xPos; + break; + + case 3: { + var paramOp = attach1FloatOfFloat4Animation(g_pack, + transform, + 'diffuse', + 'input3', + 0.5, + 1); + paramOp.input1 = 1; + paramOp.input2 = 1; + transform.translate(xPos, 0, 0); + break; + } + } + } + + g_finished = true; // for selenium +} + +</script> +</head> +<body> +<h1>Animation</h1> +Once the scene is setup no interaction with o3d is needed for the animations +to run. +<br/> +<!-- Start of O3D plugin --> +<div id="o3d" style="width: 800px; height: 600px"></div> +<!-- End of O3D plugin --> +</body> +</html> diff --git a/o3d/samples/o3d-webgl/base.js b/o3d/samples/o3d-webgl/base.js index ce46219..09ff8ea 100644 --- a/o3d/samples/o3d-webgl/base.js +++ b/o3d/samples/o3d-webgl/base.js @@ -269,5 +269,9 @@ o3d.include('shape'); o3d.include('effect'); o3d.include('material'); o3d.include('archive_request'); +o3d.include('param_operation'); +o3d.include('function'); +o3d.include('counter'); +o3d.include('curve'); diff --git a/o3d/samples/o3d-webgl/client.js b/o3d/samples/o3d-webgl/client.js index ebb28f4..ebbcb8f 100644 --- a/o3d/samples/o3d-webgl/client.js +++ b/o3d/samples/o3d-webgl/client.js @@ -97,6 +97,7 @@ o3d.Renderer.clients_ = []; o3d.Renderer.renderClients = function() { for (var i = 0; i < o3d.Renderer.clients_.length; ++i) { var client = o3d.Renderer.clients_[i]; + client.counter_manager_.tick(); if (client.renderMode == o3d.Client.RENDERMODE_CONTINUOUS) { client.render(); } @@ -220,6 +221,7 @@ o3d.Client = function() { this.clientId = o3d.Client.nextId++; this.packs_ = [tempPack]; this.clientInfo = tempPack.createObject('ClientInfo'); + this.counter_manager_ = new o3d.CounterManager; if (o3d.Renderer.clients_.length == 0) o3d.Renderer.installRenderInterval(); @@ -291,6 +293,12 @@ o3d.Client.prototype.root = null; */ o3d.Client.prototype.packs_ = []; +/** + * Keeps track of all counters associated with this client. + * @type {o3d.CounterManager} + */ +o3d.Client.prototype.counter_manager_ = null; + /** * Function that gets called when the client encounters an error. @@ -439,6 +447,7 @@ o3d.Client.prototype.renderMode = o3d.Client.RENDERMODE_CONTINUOUS; o3d.Client.prototype.render = function() { // Synthesize a render event. var render_event = new o3d.RenderEvent; + this.counter_manager_.advanceRenderFrameCounters(); this.clearStateStack_(); diff --git a/o3d/samples/o3d-webgl/counter.js b/o3d/samples/o3d-webgl/counter.js new file mode 100644 index 0000000..b3c3190 --- /dev/null +++ b/o3d/samples/o3d-webgl/counter.js @@ -0,0 +1,814 @@ +/* + * Copyright 2010, 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. + */ + +/** + * Per-client list of counters/timers. + * @constructor + */ +o3d.CounterManager = function() { + /** + * Map from counter type to list of counters in this client. + * @private + * @type {!Object} + */ + this.counterMap_ = {}; +}; + +/** + * Contains the time of the last call to tick, so we know the amount to pass + * into advanceCounters_ + * lastUpdated is initialized on the first call to tick(). + * @type {Date} + */ +o3d.CounterManager.prototype.lastUpdated = null; + +/** + * @param {string} type Which counter_type_ to advance. + * @param {number} amount Amount to advance counters. + */ +o3d.CounterManager.prototype.advanceCounters = function(type, amount) { + var counterArrayRef = this.counterMap_[type]; + if (!counterArrayRef) + return; + var length = counterArrayRef.length; + var counterArray = []; + for (var i = 0; i < length; i++) { + counterArray[i] = counterArrayRef[i]; + } + for (var i = 0; i < length; ++i) { + if (counterArray[i].running) { + counterArray[i].advance(amount); + } + } +}; + +/** + * Static method to increments all global counters. RenderFrameCounter should + * not get updated here. + */ +o3d.CounterManager.prototype.tick = function() { + this.advanceCounters("TickCounter", 1.0); + var now = new Date(); + var deltaSeconds = 0; + if (this.lastUpdated != null) { + deltaSeconds = (now - this.lastUpdated) / 1000.0; + } + this.lastUpdated = now; + this.advanceCounters("SecondCounter", deltaSeconds); +}; + +/** + * Updates RenderFrameCounter objects by 1.0, similarly to TickCounter. + * As opposed to tick(), this should only be when the canvas is rendered, so + * any canvases which are not automatically rendererd may not advance this. + */ +o3d.CounterManager.prototype.advanceRenderFrameCounters = function() { + this.advanceCounters("RenderFrameCounter", 1.0); +}; + +/** + * Register a counter, using its className to decide which type of counter. + * @param {!o3d.Counter} counter Instance of some sub-type of o3d.Counter + */ +o3d.CounterManager.prototype.registerCounter = function(counter) { + var type = counter.counter_type_; + var arr = this.counterMap_[type]; + if (!arr) { + arr = this.counterMap_[type] = []; + } + arr.push(counter); +}; + +/** + * Unregister a counter previously registered by registerCounter. + * @param {!o3d.Counter} counter Instance of some sub-type of o3d.Counter + */ +o3d.CounterManager.prototype.unregisterCounter = function(counter) { + var type = counter.counter_type_; + var arr = this.counterMap_[type]; + if (arr) { + o3d.removeFromArray(arr, counter); + } +}; + +/** + * A Counter counts seconds, ticks or render frames depending on the type of + * counter. You can set where it starts counting from and where it stops + * counting at, whether or not it is running or paused and how it loops or does + * not loop. You can also give it callbacks to call at specific count values. + * @constructor + * @extends {o3d.ParamObject} + */ +o3d.Counter = function() { + o3d.ParamObject.call(this); + + /** + * Last called callback in the forward direction (start_count > end_count) + * @type {number} + * @private + */ + this.next_callback_ = -1; + + /** + * Last called callback in the backward direction (start_count < end_count) + * @type {number} + * @private + */ + this.prev_callback_ = -1; + + /** + * Keeps track of last count in case this.count_ is bound to another param. + * @type {number} + * @private + */ + this.old_count_ = 0; + + /** + * Keeps track of last end_count to prevent double-firing of callbacks if + * start_count is the same. + * @type {number} + * @private + */ + this.last_call_callbacks_end_count_ = 0; + + /** + * List of callbacks for this counter, in sorted order. + * There can only be one callback per timer value. + * @type {Array.<o3d.Counter.CallbackInfo>} + * @private + */ + this.callbacks_ = []; + + /** + * Pointer to CounterManager which owns this counter, so it can be added + * to the list of counters by setting this.running = true. + * @type {o3d.CounterManager} + */ + this.counter_manager_ = null; + + /** + * Cached copy of this.getParam('running') + * @type {o3d.ParamBoolean} + * @private + */ + this.running_param_ = this.createParam("running", "ParamBoolean"); + + /** + * Cached copy of this.getParam('count') + * @type {o3d.ParamFloat} + * @private + */ + this.count_param_ = this.createParam("count", "ParamFloat"); + + this.multiplier = 1.0; + + this.forward = true; + + this.countMode = o3d.Counter.CONTINUOUS; + + // this.running must be set in the sub class due to how o3d.inherit works. +}; +o3d.inherit('Counter', 'ParamObject'); + +/** + * Controls the direction of the counter. If false, Counter.callCallbacks_ + * will be called with start_count > end_count. + * @default true + * @type {boolean} + */ +o3d.ParamObject.setUpO3DParam_(o3d.Counter, "forward", "ParamBoolean"); + +/** + * The start value for this counter. + * @default 0 + * @type {number} + */ +o3d.ParamObject.setUpO3DParam_(o3d.Counter, "start", "ParamFloat"); + +/** + * The end value for this counter. + * @default 0 + * @type {number} + */ +o3d.ParamObject.setUpO3DParam_(o3d.Counter, "end", "ParamFloat"); + +/** + * Can be used to switch the direction of the counter or control what happens + * when end_count is reached. + * @see o3d.Counter.CONTINUOUS + * @see o3d.Counter.ONCE + * @see o3d.Counter.CYCLE + * @see o3d.Counter.OSCILLATE + * @default o3d.Counter.CONTINUOUS + * @type {o3d.Counter.CountMode} + */ +o3d.ParamObject.setUpO3DParam_(o3d.Counter, "countMode", "ParamInteger"); + +/** + * The time multiplier for this counter. + * @default 1 + * @type {number} + */ +o3d.ParamObject.setUpO3DParam_(o3d.Counter, "multiplier", "ParamFloat"); + +/** + * Whether or not this counter is running. + * Special setter to allow destroying a stopped Counter without leaking mem. + * Defaults to true in SecondCounter, TickCounter and RenderFrameCounter. + * @default false + * @type {boolean} + */ +o3d.Counter.prototype.__defineSetter__("running", + function(newRunning) { + var oldRunning = this.running_param_.value; + if (this.counter_manager_ && oldRunning != newRunning) { + if (newRunning == false) { + this.counter_manager_.unregisterCounter(this); + } else { + this.counter_manager_.registerCounter(this); + } + } + this.running_param_.value = newRunning; + } +); +o3d.Counter.prototype.__defineGetter__("running", + function() { + return this.running_param_.value; + } +); + +/** + * The current count for this counter. The setter is equivalent to calling + * setCount(value) + * @default 0 + * @type {number} + */ +o3d.Counter.prototype.__defineSetter__("count", + function(value) { + this.setCount(value); + } +); +o3d.Counter.prototype.__defineGetter__("count", + function() { + return this.count_param_.value; + } +); + +o3d.Counter.prototype.__defineSetter__("gl", + function(new_gl) { + var old_running = this.running; + this.running = false; + this.gl_ = new_gl; + if (this.gl_ && this.gl_.client) { + this.counter_manager_ = this.gl_.client.counter_manager_; + } + this.running = old_running; + } +); + +o3d.Counter.prototype.__defineGetter__("gl", + function() { + return this.gl_; + } +); + +/** + * @type {number} + */ +o3d.Counter.CountMode = goog.typedef; + +/** + * Continiuously goes up, ignoring the value of end_count. This is the default. + * @type {o3d.Counter.CountMode} + */ +o3d.Counter.CONTINUOUS = 0; +/** + * Sets running=false when end is reached and stops. + * @type {o3d.Counter.CountMode} + */ +o3d.Counter.ONCE = 1; +/** + * Resets count to start_count whenever end is reached. + * @type {o3d.Counter.CountMode} + */ +o3d.Counter.CYCLE = 2; +/** + * Switches direction whenever end or start is reached. + * @type {o3d.Counter.CountMode} + * @see direction + */ +o3d.Counter.OSCILLATE = 3; + +/** + * The counter type is used by o3d.CounterManager to decide which array to + * put the counter in, and how much to call advanceAmount by during each call + * to o3d.CounterManager.tick() or advanceRenderFrameCounters(). + * "Counter" means that it will only be advanced manually. + * @type {string} + */ +o3d.Counter.prototype.counter_type_ = "Counter"; + +/** + * Resets the counter back to the start or end time depending on the forward + * setting and also resets the Callback state. + * Note: Reset does not change the running state of the counter. + */ +o3d.Counter.prototype.reset = function() { + this.setCount(this.forward ? this.start : this.end); +}; + +/** + * Sets the current count value for this counter as well as the resetting the + * state of the callbacks. + * @param {number} count The current value of the counter + */ +o3d.Counter.prototype.setCount = function(value) { + this.count_param_.value = value; + this.old_count_ = value; + this.next_callback_ = -1; + this.prev_callback_ = -1; +}; + + +/** + * Advances the counter the given amount. The actual amount advanced depends on + * the forward and multiplier settings. The formula is + * new_count = count + advance_amount * multiplier * (forward ? 1.0 : -1.0); + * + * Any callbacks that fall in the range between the counter's current count and + * the amount advanced will be called. This function is normally called + * automatically by the client if the counter is set to running = true. but you + * can call it manually. + * + * @param {number} advance_amount Amount to advance count. + */ +o3d.Counter.prototype.advance = function(advance_amount) { + var queue = []; + var old_count = this.count_param_.value; + + // Update the count. + if (this.count_param_.inputConnection) { + this.callCallbacks_(this.old_count_, old_count, queue); + } else { + var direction = this.forward; + var start_count = this.start; + var end_count = this.end; + var multiplier = this.multiplier; + var delta = (direction ? advance_amount : -advance_amount) * multiplier; + var period = end_count - start_count; + + var mode = this.countMode; + if (period >= 0.0) { + // end > start + var new_count = old_count + delta; + if (delta >= 0.0) { + switch (mode) { + case o3d.Counter.ONCE: { + if (new_count >= end_count) { + new_count = end_count; + this.running = false; + } + break; + } + case o3d.Counter.CYCLE: { + while (new_count >= end_count) { + this.callCallbacks_(old_count, end_count, queue); + if (period == 0.0) { + break; + } + old_count = start_count; + new_count -= period; + } + break; + } + case o3d.Counter.OSCILLATE: { + while (delta > 0.0) { + new_count = old_count + delta; + if (new_count < end_count) { + break; + } + this.callCallbacks_(old_count, end_count, queue); + direction = !direction; + var amount = end_count - old_count; + delta -= amount; + old_count = end_count; + new_count = end_count; + if (delta <= 0.0 || period == 0.0) { + break; + } + new_count -= delta; + if (new_count > start_count) { + break; + } + this.callCallbacks_(old_count, start_count, queue); + direction = !direction; + amount = old_count - start_count; + delta -= amount; + old_count = start_count; + new_count = start_count; + } + this.forward = direction; + break; + } + case o3d.Counter.CONTINUOUS: + default: + break; + } + this.callCallbacks_(old_count, new_count, queue); + this.count_param_.value = new_count; + } else if (delta < 0.0) { + switch (mode) { + case o3d.Counter.ONCE: { + if (new_count <= start_count) { + new_count = start_count; + this.running = false ; + } + break; + } + case o3d.Counter.CYCLE: { + while (new_count <= start_count) { + this.callCallbacks_(old_count, start_count, queue); + if (period == 0.0) { + break; + } + old_count = end_count; + new_count += period; + } + break; + } + case o3d.Counter.OSCILLATE: { + while (delta < 0.0) { + new_count = old_count + delta; + if (new_count > start_count) { + break; + } + this.callCallbacks_(old_count, start_count, queue); + direction = !direction; + var amount = old_count - start_count; + delta += amount; + old_count = start_count; + new_count = start_count; + if (delta >= 0.0 || period == 0.0) { + break; + } + new_count -= delta; + if (new_count < end_count) { + break; + } + this.callCallbacks_(old_count, end_count, queue); + direction = !direction; + amount = end_count - old_count; + delta += amount; + old_count = end_count; + new_count = end_count; + } + this.forward = direction; + break; + } + case o3d.Counter.CONTINUOUS: + default: + break; + } + this.callCallbacks_(old_count, new_count, queue); + this.count_param_.value = new_count; + } + } else if (period < 0.0) { + // start > end + period = -period; + var new_count = old_count - delta; + if (delta > 0.0) { + switch (mode) { + case o3d.Counter.ONCE: { + if (new_count <= end_count) { + new_count = end_count; + this.running = false; + } + break; + } + case o3d.Counter.CYCLE: { + while (new_count <= end_count) { + this.callCallbacks_(old_count, end_count, queue); + old_count = start_count; + new_count += period; + } + break; + } + case o3d.Counter.OSCILLATE: { + while (delta > 0.0) { + new_count = old_count - delta; + if (new_count > end_count) { + break; + } + this.callCallbacks_(old_count, end_count, queue); + direction = !direction; + var amount = old_count - end_count; + delta -= amount; + old_count = end_count; + new_count = end_count; + if (delta <= 0.0) { + break; + } + new_count += delta; + if (new_count < start_count) { + break; + } + this.callCallbacks_(old_count, start_count, queue); + direction = !direction; + amount = start_count - old_count; + delta -= amount; + old_count = start_count; + new_count = start_count; + } + this.forward = direction; + break; + } + case o3d.Counter.CONTINUOUS: + default: + break; + } + this.callCallbacks_(old_count, new_count, queue); + this.count_param_.value = new_count; + } else if (delta < 0.0) { + switch (mode) { + case o3d.Counter.ONCE: { + if (new_count >= start_count) { + new_count = start_count; + this.running = false; + } + break; + } + case o3d.Counter.CYCLE: { + while (new_count >= start_count) { + this.callCallbacks_(old_count, start_count, queue); + old_count = end_count; + new_count -= period; + } + break; + } + case o3d.Counter.OSCILLATE: { + while (delta < 0.0) { + new_count = old_count - delta; + if (new_count < start_count) { + break; + } + this.callCallbacks_(old_count, start_count, queue); + direction = !direction; + var amount = start_count - old_count; + delta += amount; + old_count = start_count; + new_count = start_count; + if (delta >= 0.0) { + break; + } + new_count += delta; + if (new_count > end_count) { + break; + } + this.callCallbacks_(old_count, end_count, queue); + direction = !direction; + amount = old_count - end_count; + delta += amount; + old_count = end_count; + new_count = end_count; + } + this.forward = direction; + break; + } + case o3d.Counter.CONTINUOUS: + default: + break; + } + this.callCallbacks_(old_count, new_count, queue); + this.count_param_.value = new_count; + } + } + } + this.old_count_ = old_count; + for (var i = 0; i < queue.length; i++) { + queue[i](); + } +}; + +/** + * @param {number} start_count Starts exactly at this count. + * @param {number} end_count Calls callbacks up to but not including this. + * @private + */ +o3d.Counter.prototype.callCallbacks_ = function(start_count, end_count, queue) { + if (end_count > start_count) { + // Going forward. + // If next_callback is not valid, find the first possible callback. + if (this.next_callback_ < 0 || + start_count != this.last_call_callbacks_end_count_) { + this.next_callback_ = 0; + while (this.next_callback_ != this.callbacks_.length && + this.callbacks_[this.next_callback_].count < start_count) { + ++this.next_callback_; + } + } + + // add callbacks until we get to some callback past end_count. + while (this.next_callback_ < this.callbacks_.length) { + if (this.callbacks_[this.next_callback_].count > end_count) { + break; + } + queue.push(this.callbacks_[this.next_callback_].callback); + ++this.next_callback_; + } + this.prev_callback_ = -1; + this.last_call_callbacks_end_count_ = end_count; + } else if (end_count < start_count) { + // Going backward. + // If prev_callback is not valid, find the first possible callback. + if (this.prev_callback_ < 0 || + start_count != this.last_call_callbacks_end_count_) { + this.prev_callback_ = this.callbacks_.length - 1; + while (this.prev_callback_ >= 0 && + this.callbacks_[this.prev_callback_].count > start_count) { + --this.prev_callback_; + } + } + + // add callbacks until we get to some callback past end_count. + while (this.prev_callback_ >= 0) { + if (this.callbacks_[this.prev_callback_].count < end_count) { + break; + } + queue.push(this.callbacks_[this.prev_callback_].callback); + --this.prev_callback_; + } + + this.next_callback_ = -1; + this.last_call_callbacks_end_count_ = end_count; + } +}; + +/** + * Adds a callback for a given count value. Only one callback can be added to a + * specific count value. If another callback is added with the same count value + * the previous callback for that value will be replaced. Note: A callback at + * start will only get called when counting backward, a callback at end will + * only get called counting forward. + * @param {number} count Count at which to call callback. + * @param {function()} callback Callback to call at given count. + */ +o3d.Counter.prototype.addCallback = function(count, callback) { + this.next_callback_ = -1; + this.prev_callback_ = -1; + var end = this.callbacks_.length; + var iter = 0; + while (iter != end) { + var current = this.callbacks_[iter]; + if (current.count == count) { + // Did the o3d plugin overwrite existing callbacks here? + current.callback = callback; + return; + } else if (current.count > count) { + break; + } + ++iter; + } + var rest = this.callbacks_.splice(iter, this.callbacks_.length - iter); + this.callbacks_.push(new o3d.Counter.CallbackInfo(count, callback)); + this.callbacks_.push.apply(this.callbacks_, rest); +}; + +/** + * Removes a callback for a given count value. + * @param {number} count Count to remove callback for. + * @return {boolean} true if there was a callback for that count, false if + * there was not a callback for that count. + */ +o3d.Counter.prototype.removeCallback = function(count) { + var end = this.callbacks_.length; + for (var iter = 0; iter != end; ++iter) { + if (this.callbacks_[iter].count == count) { + this.next_callback_ = -1; + this.prev_callback_ = -1; + this.callbacks_.splice(iter, 1); + return true; + } + } + return false; +}; + +/** + * Removes all the callbacks on this counter. + * Does not affect running state: set running=false to allow this counter to be + * garbage collected. + */ +o3d.Counter.prototype.removeAllCallbacks = function() { + this.callbacks_ = []; + this.next_callback_ = -1; + this.prev_callback_ = -1; +}; + +/** + * Class to hold onto the state of a callback function at a specific count. + * @constructor + * @param {number} count Count at which this callback will get called. + * @param {function()} callback Function to call when count is reached. + */ +o3d.Counter.CallbackInfo = function(count, callback) { + /** + * Count at which this callback will get called. + * @type {number} + */ + this.count = count; + + /** + * Function to call when count is reached. + * @type {function()} + */ + this.callback = callback; + + /** + * Prevents recursion of the same callback, in case it decides to advance + * the counter. + * @type {boolean} + * @private + */ + this.called_ = false; +}; + +/** + * Run this callback without the possibility for recursion. + */ +o3d.Counter.CallbackInfo.prototype.run = function() { + if (!this.called_) { + this.called_ = true; + this.callback(); + this.called_ = false; + } +}; + +/** + * Counter to count seconds. Logic is implemented in CounterManager.tick() + * @constructor + * @extends {o3d.Counter} + */ +o3d.SecondCounter = function() { + o3d.Counter.call(this); + this.running = true; +}; + +o3d.inherit("SecondCounter", "Counter"); + +o3d.SecondCounter.prototype.counter_type_ = "SecondCounter"; + +/** + * Counter to count frames. Logic is implemented in + * CounterManager.advanceRenderFrameCounters() + * @constructor + * @extends {o3d.Counter} + */ +o3d.RenderFrameCounter = function() { + o3d.Counter.call(this); + this.running = true; +}; + +o3d.inherit("RenderFrameCounter", "Counter"); + +o3d.RenderFrameCounter.prototype.counter_type_ = "RenderFrameCounter"; + +/** + * Counter to count ticks. Logic is implemented in CounterManager.tick() + * @constructor + * @extends {o3d.Counter} + */ +o3d.TickCounter = function() { + o3d.Counter.call(this); + this.running = true; +}; + +o3d.inherit("TickCounter", "Counter"); + +o3d.TickCounter.prototype.counter_type_ = "TickCounter"; diff --git a/o3d/samples/o3d-webgl/curve.js b/o3d/samples/o3d-webgl/curve.js new file mode 100644 index 0000000..68573092 --- /dev/null +++ b/o3d/samples/o3d-webgl/curve.js @@ -0,0 +1,902 @@ +/* + * Copyright 2010, 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. + */ + +/** + * A CurveKey prepresents a key on an Curve. + * + * @constructor + * @extends {o3d.ObjectBase} + */ +o3d.CurveKey = function(owner) { + o3d.ObjectBase.call(this); + /** + * The only owner of this CurveKey (cannot be shared). + * @private + * @type {!o3d.Curve} + */ + this.owner_ = owner; + + /** + * See input setter. + * @private + * @type {number} + */ + this.input_ = 0; + + /** + * See input setter. + * @private + * @type {number} + */ + this.output_ = 0; +}; +o3d.inherit('CurveKey', 'ObjectBase'); + +/** + * Destroys this key, removing it from its owner. + * + */ +o3d.CurveKey.prototype.destroy = function() { + o3d.removeFromArray(this.owner_.keys, this); + this.owner_.invalidateCache_(); +}; + +/** + * Computes the value within the range from this.input to next_key.input. + * Defaults to the LinearCurveKey implementation which is continuous. + * @param {number} offset Offset within the key (function input - this.input) + * @param {o3d.CurveKey} next_key The next key in the set, can not be null. + * @return {number} return + */ +o3d.CurveKey.prototype.getOutputAtOffset = function(offset, next_key) { + var input_span = next_key.input - this.input; + var output_span = next_key.output - this.output; + return this.output + offset / input_span * output_span; +}; + +/** + * Input for this key. This curve key will apply until the next key's input. + * @type {number} + */ +o3d.CurveKey.prototype.__defineGetter__("input", function() { + return this.input_; +}); +o3d.CurveKey.prototype.__defineSetter__("input", function(new_input) { + if (new_input != this.input_) { + this.input_ = new_input; + this.owner_.invalidateCache_(); + } +}); + +/** + * Output corresponding to input. + * @type {number} + */ +o3d.CurveKey.prototype.__defineGetter__("output", function() { + return this.output_; +}); +o3d.CurveKey.prototype.__defineSetter__("output", function(new_output) { + if (new_output != this.output_) { + this.output_ = new_output; + this.owner_.invalidateCache_(); + } +}); + +/** + * An CurveKey that holds its output (is not interpolated between this key + * and the next.) + * + * Other discontinuous CurveKey classes must be derived from StepCurveKey. + * + * @constructor + * @extends {o3d.CurveKey} + */ +o3d.StepCurveKey = function(owner) { + o3d.CurveKey.call(this, owner); + owner.num_step_keys_++; +}; +o3d.inherit('StepCurveKey', 'CurveKey'); + +/** + * Simple discontinuous implementation. + * @param {number} offset Ignored: constant output within a StepCurveKey + * @param {o3d.CurveKey} next_key Ignored: discontinuous + * @return {number} output + */ +o3d.StepCurveKey.prototype.getOutputAtOffset = function(offset, next_key) { + return this.output; +}; + +/** + * Specialized destroy method to update the num_step_keys_ of the owner_. + */ +o3d.StepCurveKey.prototype.destroy = function() { + o3d.CurveKey.prototype.destroy.call(this); + this.owner_.num_step_keys_--; +}; + +/** + * An CurveKey that linearly interpolates between this key and the next key. + * + * @constructor + * @extends {o3d.CurveKey} + */ +o3d.LinearCurveKey = function(owner) { + o3d.CurveKey.call(this, owner); +}; +o3d.inherit('LinearCurveKey', 'CurveKey'); + +/** + * An CurveKey that uses a bezier curve for interpolation between this key + * and the next. + * + * @constructor + * @extends {o3d.CurveKey} + */ +o3d.BezierCurveKey = function(owner) { + o3d.CurveKey.call(this, owner); + + /** + * See input setter. + * @private + * @type {Array.<number>} + */ + this.in_tangent_ = [0, 0]; + + /** + * See input setter. + * @private + * @type {Array.<number>} + */ + this.out_tangent_ = [0, 0]; +}; +o3d.inherit('BezierCurveKey', 'CurveKey'); + +/** + * Tangent for values approaching this key. Do not set elements in the array + * directly as that will prevent calling invalidateCache_. + * @type {Array.<number>} + */ +o3d.BezierCurveKey.prototype.__defineGetter__("inTangent", function() { + return this.in_tangent_; +}); +o3d.BezierCurveKey.prototype.__defineSetter__("inTangent", function(new_t) { + if (new_t != this.in_tangent_) { + this.in_tangent_ = new_t; + this.owner_.invalidateCache_(); + } +}); + +/** + * Tangent for values approaching the next key. Do not set elements in the array + * directly as that will prevent calling invalidateCache_. + * @type {Array.<number>} + */ +o3d.BezierCurveKey.prototype.__defineGetter__("outTangent", function() { + return this.out_tangent_; +}); +o3d.BezierCurveKey.prototype.__defineSetter__("outTangent", function(new_t) { + if (new_t != this.out_tangent_) { + this.out_tangent_ = new_t; + this.owner_.invalidateCache_(); + } +}); + +/** + * Uses iterative method to accurately pin-point the 't' of the Bezier + * equation that corresponds to the current time. + * @param {number} control_point_0_x this.input + * @param {number} control_point_1_x this.outTangent + * @param {number} control_point_2_x next.inTangent + * @param {number} control_point_3_x next.input + * @param {number} input Absolute input value relative to span + * @param {number} initial_guess Starting point assuming mostly linear. + * @private + */ +o3d.BezierCurveKey.findT_ = function(control_point_0_x, + control_point_1_x, + control_point_2_x, + control_point_3_x, + input, + initial_guess) { + var local_tolerance = 0.001; + var high_t = 1.0; + var low_t = 0.0; + + // TODO: Optimize here, start with a more intuitive value than 0.5 + // (comment left over from original code) + var mid_t = 0.5; + if (initial_guess <= 0.1) { + mid_t = 0.1; // clamp to 10% or 90%, because if miss, the cost is + // too high. + } else if (initial_guess >= 0.9) { + mid_t = 0.9; + } else { + mid_t = initial_guess; + } + var once = true; + while ((high_t-low_t) > local_tolerance) { + if (once) { + once = false; + } else { + mid_t = (high_t - low_t) / 2.0 + low_t; + } + var ti = 1.0 - mid_t; // (1 - t) + var calculated_time = control_point_0_x * ti * ti * ti + + 3 * control_point_1_x * mid_t * ti * ti + + 3 * control_point_2_x * mid_t * mid_t * ti + + control_point_3_x * mid_t * mid_t * mid_t; + if (Math.abs(calculated_time - input) <= local_tolerance) { + break; // If we 'fall' very close, we like it and break. + } + if (calculated_time > input) { + high_t = mid_t; + } else { + low_t = mid_t; + } + } + return mid_t; +}; + +/** + * Computes the value of the Bezier curve within the range from this.input + * to next_key.input. + * @param {number} offset Offset within the key (function input - this.input) + * @param {o3d.CurveKey} next_key The next key in the set, can not be null. + * @return {number} return + */ +o3d.BezierCurveKey.prototype.getOutputAtOffset = function(offset, next_key) { + var input_span = next_key.input - this.input; + var output_span = next_key.output - this.output; + var in_tangent; + + // We check bezier first because it's the most likely match for another + // bezier key. + if (next_key.inTangent) { + in_tangent = next_key.inTangent; + } else { + in_tangent = [next_key.input - input_span / 3.0, + next_key.output - output_span / 3.0]; + } + + // Do a bezier calculation. + var t = offset / input_span; + t = o3d.BezierCurveKey.findT_(this.input, + this.outTangent[0], + in_tangent[0], + next_key.input, + this.input + offset, + t); + var b = this.outTangent[1]; + var c = in_tangent[1]; + var ti = 1.0 - t; + var br = 3.0; + var cr = 3.0; + return this.output * ti * ti * ti + br * b * ti * ti * t + + cr * c * ti * t * t + next_key.output * t * t * t; +}; + +/** + * A Curve stores a bunch of CurveKeys and given a value + * representing an input point on a curve returns the output of the curve for + * that input. Curve is data only. It is used by 1 or more + * FunctionEval objects or by direct use from javascript. + * + * @constructor + * @extends {o3d.Function} + */ +o3d.Curve = function() { + o3d.Function.call(this, this.evaluate); + + /** + * The behavior of the curve before the first key. + * @see o3d.Curve.CONSTANT + * @see o3d.Curve.LINEAR + * @see o3d.Curve.CYCLE + * @see o3d.Curve.CYCLE_RELATIVE + * @see o3d.Curve.OSCILLATE + * @type {o3d.Curve.Infinity} + * @default CONSTANT + */ + this.preInfinity = o3d.Curve.CONSTANT; + + /** + * The behavior of the curve before the first key. + * @see o3d.Curve.CONSTANT + * @see o3d.Curve.LINEAR + * @see o3d.Curve.CYCLE + * @see o3d.Curve.CYCLE_RELATIVE + * @see o3d.Curve.OSCILLATE + * @type {o3d.Curve.Infinity} + * @default CONSTANT + */ + this.postInfinity = o3d.Curve.CONSTANT; + + /** + * Whether or not a cache is used to speed up evaluation of this Curve. + */ + this.useCache = true; + + /** + * Data for sampleRate setter. + * @type {number} + * @private + */ + this.sample_rate_ = o3d.Curve.kDefaultSampleRate; + + /** + * The keys for this curve. + * + * This property is read-only. + * @type {!Array.<!o3d.CurveKey>} + */ + this.keys = []; + + /** + * Keep track if a new key has been added which hasn't been sorted. + * @type {boolean} + * @private + */ + this.sorted_ = true; + + /** + * True if the curve needs to checks for discontinuity errors before the next + * evauluation. + * @see updateCurveInfo_ + * @type {boolean} + * @private + */ + this.check_discontinuity_ = false; + + /** + * Keep track if any discontinuous (steps or gaps) keys are in the mix. + * Call isDiscontinuous to access this value, which updates it if necessary. + * @type {boolean} + * @private + */ + this.discontinuous_= false; + + /** + * @type {number} Number of step keys--used to speed up updateCurveInfo_ + * discontinuity check if it's non-zero. + * @private + */ + this.num_step_keys_ = 0; + +}; + +o3d.inherit('Curve', 'Function'); + +/** + * Constant representing the fastest possible sample period. More samples take + * more computation initially and more memroy. + */ +o3d.Curve.kMinimumSampleRate = 1.0 / 240.0; + +/** + * By default, sample 30 times per curve key. + */ +o3d.Curve.kDefaultSampleRate = 1.0 / 30.0; + +/** + * Gets the sample rate for the cache. By default Animation data is + * cached so that using the animation is fast. To do this the keys that + * represent the animation are sampled. The higher the frequency of the + * samples the closer the cache will match the actual keys. + * The default is 1/30 (30hz). You can set it anywhere from 1/240th (240hz) to + * any larger value. Note: Setting the sample rate has the side effect of + * invalidating the cache thereby causing it to get rebuilt. + * Must be 1/240 or greater. Default = 1/30. + * + * @type {number} + */ +o3d.Curve.prototype.__defineGetter__("sampleRate", function() { + return this.sample_rate_; +}); + +o3d.Curve.prototype.__defineSetter__("sampleRate", function(rate) { + if (rate < o3d.Curve.kMinimumSampleRate) { + rate = o3d.Curve.kMinimumSampleRate; + this.gl.client.error_callback( + "attempt to set sample rate to " + rate + + " which is lower than the minimum of " + o3d.Curve.kMinimumSampleRate); + } else if (rate != this.sample_rate_) { + this.sample_rate_ = new_sample_rate; + this.invalidateCache_(); + } +}); + +/** + * @type {number} + */ +o3d.Curve.Infinity = goog.typedef; + +/** + * Uses the output value of the first or last animation key. + * @type {o3d.Curve.Infinity} + */ +o3d.Curve.CONSTANT = 0; + +/** + * Takes the distance between the closest animation key input value and the + * evaluation time. Multiplies this distance against the instant slope at the + * closest animation key and offsets the result with the closest animation key + * output value. + * @type {o3d.Curve.Infinity} + */ +o3d.Curve.LINEAR = 1; + +/** + * Cycles over the first and last keys using: + * input = (input - first) % (last - first) + first; + * Note that in CYCLE mode you can never get the end output because a cycle + * goes from start to end exclusive of end. + * @type {o3d.Curve.Infinity} + */ +o3d.Curve.CYCLE = 2; + +/** + * Same as cycle except the offset of the entire cycle is added to each + * consecutive cycle. + * @type {o3d.Curve.Infinity} + */ +o3d.Curve.CYCLE_RELATIVE = 3; + +/** + * Ping Pongs between the first and last keys. + * @type {o3d.Curve.Infinity} + */ +o3d.Curve.OSCILLATE = 4; + +/** + * Deserializes from the curve data given a RawData object. + * + * @param {!o3d.RawData} rawData contains curve data + * @param {number} opt_offset is a byte offset from the start of raw_data + * @param {number} opt_length is the byte length of the data to set + * @return {boolean} True if operation was successful. + * + */ +o3d.Curve.prototype.set = function(rawData, opt_offset, opt_length) { + o3d.notImplemented(); +}; + +/** + * Creates a new key for this curve. + * @param {string} keyType name of key class to create. Valid type names are: + * <li> 'StepCurveKey', + * <li> 'LinearCurveKey', + * <li> 'BezierCurveKey', + * @return {!o3d.CurveKey} The created key. + * + */ +o3d.Curve.prototype.createKey = function(keyType) { + var newKey = new (o3d[keyType]) (this); + this.keys.push(newKey); + return newKey; +}; + +/** + * Adds 1 or more LinearKeys to this Curve. + * + * Example: + * <pre> + * // Creates 2 keys. + * // 1 key at 0 with an output of 10 + * // 1 key at 20 with an output of 30 + * curve.addLinearKeys([0,10,20,30]); + * </pre>. + * + * @param {!Array.<number>} values Array of input, output pairs. + * Length must be a multiple of 2 + */ +o3d.Curve.prototype.addLinearKeys = function(values) { + var kNumLinearKeyValues = 2; + if (values.length % kNumLinearKeyValues != 0) { + this.gl.client.error_callback( + "addLinearKeys: expected multiple of 2 values got "+values.size()); + return; + } + for (var i = 0; i < values.length; i += kNumLinearKeyValues) { + var newKey = this.createKey("LinearKey"); + newKey.input = values[i]; + newKey.output = values[i+1]; + } + this.sorted_ = false; +}; + +/** + * Adds 1 or more StepKeys to this Curve. + * + * Example: + * <pre> + * // Creates 2 keys. + * // 1 key at 0 with an output of 10 + * // 1 key at 20 with an output of 30 + * curve.addStepKeys([0,10,20,30]); + * </pre>. + * + * @param {!Array.<number>} values Array of input, output pairs. + * Length must be a multiple of 2 + */ +o3d.Curve.prototype.addStepKeys = function(values) { + var kNumStepKeyValues = 2; + if (values.length % kNumStepKeyValues != 0) { + this.gl.client.error_callback( + "addStepKeys: expected multiple of 2 values got "+values.size()); + return; + } + for (var i = 0; i < values.length; i += kNumStepKeyValues) { + var newKey = this.createKey("StepKey"); + newKey.input = values[i]; + newKey.output = values[i+1]; + } + this.sorted_ = false; +}; + +/** + * Adds 1 or more BezierKeys to this Curve. + * + * Example: + * <pre> + * // Creates 2 keys. + * // 1 key at 0 with an output of 10, in tangent of 1,9, out tangent 9,0.5 + * // 1 key at 20 with an output of 30, in tangent of 30, 3, out tangent 4, 28 + * curve.addBezierKeys([0,10,1,9,9,0.5,2,30,3,4,28]); + * </pre>. + * + * @param {!Array.<number>} values Array of tuples of the form (input, output, + * inTangent[0], inTangent[1], outTangent[0], outTangent[1]). + * Length must be a multiple of 6. + */ +o3d.Curve.prototype.addBezierKeys = function(values) { + var kNumBezierKeyValues = 6; + if (values.length % kNumBezierKeyValues != 0) { + this.gl.client.error_callback( + "addBezierKeys: expected multiple of 6 values got "+values.size()); + return; + } + for (var ii = 0; ii < values.length; ii += kNumBezierKeyValues) { + var newKey = this.createKey("BezierKey"); + newKey.input = values[i]; + newKey.output = values[i+1]; + newKey.inTangent[0] = values[i+2]; + newKey.inTangent[1] = values[i+3]; + newKey.outTangent[0] = values[i+4]; + newKey.outTangent[1] = values[i+5]; + } + this.sorted_ = false; +}; + +/** + * Force updating the cache or checking discontinuity. + * @private + */ +o3d.Curve.prototype.invalidateCache_ = function() { + this.check_valid_ = false; + this.check_discontinuity_ = true; +}; + +/** + * Returns whether or not the curve is discontinuous. A discontinuous curve + * takes more time to evaluate. + * @return {boolean} True if the curve is discontinuous. + */ +o3d.Curve.prototype.isDiscontinuous = function() { + this.updateCurveInfo_(); + return this.discontinuous_; +}; + +/** + * Sorts keys (if sorted_ is false) and updates discontinuous_ + * (if check_discontinuity_ is true). + * Called automatically when necessary in evaluate and isDiscontinuous. + * @private + */ +o3d.Curve.prototype.updateCurveInfo_ = function() { + if (!this.sorted_) { + // resort keys + this.keys.sort(); + this.sorted_ = true; + this.invalidateCache_(); + } + if (this.check_discontinuity_) { + // Mark the curve as discontinuous if any 2 keys share the same input and + // if their outputs are different. + this.check_discontinuity_ = false; + var keys_size = this.keys.length; + this.discontinuous_ = (this.num_step_keys_ > 0 && + this.num_step_keys_ != keys_size); + if (!this.discontinuous_ && keys_size > 1) { + for (var ii = 0; ii < keys_size - 1; ++ii) { + if (this.keys[ii].input == this.keys[ii + 1].input && + this.keys[ii].output != this.keys[ii + 1].output) { + this.discontinuous_ = true; + break; + } + } + } + } +}; + +/** + * @param {number} input Guaranteed to be between the first and last key. + * @param {object} context Generic cache to speed up adjacent computations. + * @return {number} Final output value + * @private + */ +o3d.Curve.prototype.getOutputInSpan_ = function(input, context) { + var keys = this.keys; + var keys_size = keys.length; + if (input < keys[0].input) { + this.gl.client.error_callback( + "Curve.getOutputInSpan_: input is lower than any key"); + return 0; + } + + if (input >= keys[keys_size-1].input) { + return keys[keys_size-1].output; + } + + // use the keys directly. + var start = 0; + var end = keys_size; + var key_index; + var found = false; + + var kKeysToSearch = 3; + + // See if the context already has a index to the correct key. + if (context) { + key_index = context.curve_last_key_index_; + // is that index in range. + if (key_index < end - 1) { + // Are we between these keys. + if (keys[key_index].input <= input && + keys[key_index + 1].input > input) { + // Yes! + found = true; + } else { + // No, so check which way we need to go. + if (input > keys[key_index].input) { + // search forward a few keys. If it's not within a few keys give up. + var check_end = key_index + kKeysToSearch; + if (check_end > end) { + check_end = end; + } + for (++key_index; key_index < check_end; ++key_index) { + if (keys[key_index].input <= input && + keys[key_index + 1].input > input) { + // Yes! + found = true; + break; + } + } + } else if (key_index > 0) { + // search backward a few keys. If it's not within a few keys give up. + var check_end = key_index - kKeysToSearch; + if (check_end < 0) { + check_end = 0; + } + for (--key_index; key_index >= check_end; --key_index) { + if (keys[key_index].input <= input && + keys[key_index + 1].input > input) { + // Yes! + found = true; + break; + } + } + } + } + } + } + + if (!found) { + // TODO: If we assume the most common case is sampled keys and + // constant intervals we can make a quick guess where that key is. + + // Find the current the keys that cover our input. + while (start <= end) { + var mid = (start + end) / 2; + if (input > keys[mid].input) { + start = mid + 1; + } else { + if (mid == 0) { + break; + } + end = mid - 1; + } + } + + end = keys_size; + while (start < end) { + if (keys[start].input > input) { + break; + } + ++start; + } + if (start <= 0 || start >= end) { + this.gl.client.error_callback( + "Curve.getOutputInSpan_: start is outside range."); + } + + key_index = start - 1; + } + + var key = keys[key_index]; + if (context) { + context.curve_last_key_index_ = key_index; + } + if (key_index+1 >= keys_size || !keys[key_index+1]) { + this.gl.client.error_callback( + "Curve.getOutputInSpan_: next key is null: index is "+key_index+ + "; size is "+keys_size); + return key.output; + } else { + return key.getOutputAtOffset(input - key.input, keys[key_index+1]); + } +}; + +/** + * Evaluates a point on this bezier curve corresponding to input. + * + * @param {number} input Input value to evaluate. + * @param {number} context Context of the last evaluation. + * @return {number} output value + */ +o3d.Curve.prototype.evaluate = function(input, context) { + var keys = this.keys; + var keys_size = keys.length; + + if (keys_size == 0) { + return 0.0; + } + + if (keys_size == 1) { + return keys[0].output; + } + + this.updateCurveInfo_(); + + var start_input = keys[0].input; + var end_input = keys[keys_size-1].input; + var input_span = end_input - start_input; + var start_output = keys[0].output; + var end_output = keys[keys_size-1].output; + var output_delta = end_output - start_output; + + var kEpsilon = 0.00001; + + var output_offset = 0.0; + // check for pre-infinity + if (input < start_input) { + if (input_span <= 0.0) { + return start_output; + } + var pre_infinity_offset = start_input - input; + switch (this.preInfinity) { + case o3d.Curve.CONSTANT: + return start_output; + case o3d.Curve.LINEAR: { + var second_key = keys[1]; + var input_delta = second_key.input - start_input; + if (input_delta > kEpsilon) { + return start_output - pre_infinity_offset * + (second_key.output - start_output) / input_delta; + } else { + return start_output; + } + } + case o3d.Curve.CYCLE: { + var cycle_count = Math.ceil(pre_infinity_offset / input_span); + input += cycle_count * input_span; + input = start_input + (input - start_input) % input_span; + break; + } + case o3d.Curve.CYCLE_RELATIVE: { + var cycle_count = Math.ceil(pre_infinity_offset / input_span); + input += cycle_count * input_span; + input = start_input + (input - start_input) % input_span; + output_offset -= cycle_count * output_delta; + break; + } + case o3d.Curve.OSCILLATE: { + var cycle_count = Math.ceil(pre_infinity_offset / (2.0 * input_span)); + input += cycle_count * 2.0 * input_span; + input = end_input - Math.abs(input - end_input); + break; + } + default: + this.gl.client.error_callback( + "Curve: invalid value "+this.preInfinity+"for pre-infinity"); + return start_output; + } + } else if (input >= end_input) { + // check for post-infinity + if (input_span <= 0.0) { + return end_output; + } + var post_infinity_offset = input - end_input; + switch (this.postInfinity) { + case o3d.Curve.CONSTANT: + return end_output; + case o3d.Curve.LINEAR: { + var next_to_last_key = keys[keys_size - 2]; + var input_delta = end_input - next_to_last_key.input; + if (input_delta > kEpsilon) { + return end_output + post_infinity_offset * + (end_output - next_to_last_key.output) / + input_delta; + } else { + return end_output; + } + } + case o3d.Curve.CYCLE: { + var cycle_count = Math.ceil(post_infinity_offset / input_span); + input -= cycle_count * input_span; + input = start_input + (input - start_input) % input_span; + break; + } + case o3d.Curve.CYCLE_RELATIVE: { + var cycle_count = Math.floor((input - start_input) / input_span); + input -= cycle_count * input_span; + input = start_input + (input - start_input) % input_span; + output_offset += cycle_count * output_delta; + break; + } + case o3d.Curve.OSCILLATE: { + var cycle_count = Math.ceil(post_infinity_offset / (2.0 * + input_span)); + input -= cycle_count * 2.0 * input_span; + input = start_input + Math.abs(input - start_input); + break; + } + default: + this.gl.client.error_callback( + "Curve.invalid value "+this.postInfinity+"for post-infinity"); + return end_output; + } + } + + // At this point input should be between start_input and end_input + // inclusive. + + // If we are at end_input then just return end_output since we can't + // interpolate end_input to anything past it. + if (input >= end_input) { + return end_output + output_offset; + } + + // TODO(pathorn): Implement curve cache in javascript. + // See 'void Curve::CreateCache' in o3d/core/cross/curve.cc + + return this.getOutputInSpan_(input, context) + output_offset; +}; diff --git a/o3d/samples/o3d-webgl/function.js b/o3d/samples/o3d-webgl/function.js new file mode 100644 index 0000000..ea1f819 --- /dev/null +++ b/o3d/samples/o3d-webgl/function.js @@ -0,0 +1,161 @@ +/* + * Copyright 2010, 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. + */ + +/** + * A Function is a class that has an Evaluate method. + * Evaluate takes 1 input and returns 1 output. + * @param {function(number)=} opt_func evaluate function to use (unless this + * class is extended + * @constructor + * @extends {o3d.NamedObject} + */ +o3d.Function = function(opt_func) { + o3d.NamedObject.call(this); + this.func_ = opt_func; +}; +o3d.inherit('Function', 'NamedObject'); + +/** + * Gets an output for this function for the given input. + * @param {number} input Input to get output at. + * @param {object=} opt_context Cached state from the previous evaluate call. + * @return {number} The output for the given input. + */ +o3d.Function.prototype.evaluate = function(input, opt_context) { + this.func_.call(this, input, opt_context); +}; + +/** + * A FunctionEval evaluates a Function through parameters. + * @constructor + * @extends {o3d.ParamObject} + */ +o3d.FunctionEval = function() { + o3d.ParamObject.call(this); + + /** + * Read/write input value to the function. + * @private + * @type {o3d.ParamFloat} + */ + this.input_param_ = this.createParam("input", "ParamFloat"); + + /** + * ParamFunction whose value is a o3d.Function or subclass. + * @private + * @type {o3d.ParamFunction} + */ + this.func_param_ = this.createParam("functionObject", "ParamFunction"); + + /** + * Read-only output value. Value is cached if input does not change. + * @private + * @type {o3d.ParamFloatOutput} + */ + this.output_param_ = this.createParam("output", "ParamFloatOutput"); + + /** + * Context value to allow faster evaluation for adjacent input values. + * Used by the o3d.Function itself, but stored here to allow multiple + * FunctionEval objects to share the same Function object. + * @private + * @type {object} + */ + this.func_context_ = {}; + + /** + * Last input value to check if cache needs to be invalidated. + * @private + * @type {number} + */ + this.last_input_value_ = null; + + /** + * Cache of the last output value. + * @private + * @type {number} + */ + this.last_output_value_ = null; +}; +o3d.inherit('FunctionEval', 'ParamObject'); + +/** + * Read-only output value. Value is cached if input does not change. + * @private + * @type {o3d.ParamFloatOutput} + */ +o3d.FunctionEval.prototype.__defineGetter__("output", + function() { + return this.output_param_.value; + }); + +/** + * Read/write input value to the function. + * @type {o3d.ParamFloat} + */ +o3d.FunctionEval.prototype.__defineGetter__("input", + function() { + return this.input_param_.value; + }); +o3d.FunctionEval.prototype.__defineSetter__("input", + function(newInput) { + this.input_param_.value = newInput; + }); + +/** + * o3d.Function object (or subclass) with evaluate function. + * @private + * @type {o3d.Function} + */ +o3d.FunctionEval.prototype.__defineGetter__("functionObject", + function() { + return this.func_param_.value; + }); +o3d.FunctionEval.prototype.__defineSetter__("functionObject", + function(newFunc) { + this.func_param_.value = newFunc; + }); + +/** + * Called by o3d.Param*Output whenever its value gets read. + * This function should be responsible for caching the last value if necessary. + * @return {number} The result of evaluating the function. + */ +o3d.FunctionEval.prototype.updateOutputs = function() { + var new_input_value = this.input_param_.value; + if (this.last_input_value_ != new_input_value) { + this.last_output_value_ = + this.func_param_.value.evaluate(this.last_input_value_, + this.func_context_); + this.last_input_value_ = new_input_value; + } + return this.last_output_value_; +}; diff --git a/o3d/samples/o3d-webgl/param_operation.js b/o3d/samples/o3d-webgl/param_operation.js new file mode 100644 index 0000000..2de2300 --- /dev/null +++ b/o3d/samples/o3d-webgl/param_operation.js @@ -0,0 +1,338 @@ +/* + * Copyright 2010, 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. + */ + + +/** + * Acts like ParamFloat, but has an unsettable value which always calls + * this.owner_.updateOutputs to find the updated output value. + * @constructor + * @extends {o3d.ParamFloat} + */ +o3d.ParamFloatOutput = function() { + o3d.ParamFloat.call(this); +}; +o3d.inherit('ParamFloatOutput', 'ParamFloat'); +o3d.ParamFloatOutput.prototype.__defineGetter__("value", + function() { + return this.owner_.updateOutputs(); + } +); +o3d.ParamFloatOutput.prototype.__defineSetter__("value", + function(value) {} +); + +/** + * Acts like ParamFloat2, but has an unsettable value which always calls + * this.owner_.updateOutputs to find the updated output value. + * @constructor + * @extends {o3d.ParamFloat2} + */ +o3d.ParamFloat2Output = function() { + o3d.ParamFloat2.call(this); +}; +o3d.inherit('ParamFloat2Output', 'ParamFloat2'); +o3d.ParamFloat2Output.prototype.__defineGetter__("value", + function() { + return this.owner_.updateOutputs(); + } +); +o3d.ParamFloat2Output.prototype.__defineSetter__("value", + function(value) {} +); + +/** + * Acts like ParamFloat3, but has an unsettable value which always calls + * this.owner_.updateOutputs to find the updated output value. + * @constructor + * @extends {o3d.ParamFloat3} + */ +o3d.ParamFloat3Output = function() { + o3d.ParamFloat3.call(this); +}; +o3d.inherit('ParamFloat3Output', 'ParamFloat3'); +o3d.ParamFloat3Output.prototype.__defineGetter__("value", + function() { + return this.owner_.updateOutputs(); + } +); +o3d.ParamFloat3Output.prototype.__defineSetter__("value", + function(value) {} +); + +/** + * Acts like ParamFloat4, but has an unsettable value which always calls + * this.owner_.updateOutputs to find the updated output value. + * @constructor + * @extends {o3d.ParamFloat4} + */ +o3d.ParamFloat4Output = function() { + o3d.ParamFloat4.call(this); +}; +o3d.inherit('ParamFloat4Output', 'ParamFloat4'); +o3d.ParamFloat4Output.prototype.__defineGetter__("value", + function() { + return this.owner_.updateOutputs(); + } +); +o3d.ParamFloat4Output.prototype.__defineSetter__("value", + function(value) {} +); + +/** + * Acts like ParamMatrix4, but has an unsettable value which always calls + * this.owner_.updateOutputs to find the updated output value. + * @constructor + * @extends {o3d.ParamMatrix4} + */ +o3d.ParamMatrix4Output = function() { + o3d.ParamMatrix4.call(this); +}; +o3d.inherit('ParamMatrix4Output', 'ParamMatrix4'); +o3d.ParamMatrix4Output.prototype.__defineGetter__("value", + function() { + return this.owner_.updateOutputs(); + } +); +o3d.ParamMatrix4Output.prototype.__defineSetter__("value", + function(value) {} +); + +/** + * A Param operation that takes 2 floats to produce a float2. + * @constructor + * @extends {o3d.ParamObject} + */ +o3d.ParamOp2FloatsToFloat2 = function() { + o3d.ParamObject.call(this); + this.last_output_value_ = [0,0]; +}; +o3d.inherit('ParamOp2FloatsToFloat2', 'ParamObject'); + +(function(){ + for (var i = 0; i < 2; i++) { + o3d.ParamObject.setUpO3DParam_( + o3d.ParamOp2FloatsToFloat2, "input"+i, "ParamFloat"); + } + o3d.ParamObject.setUpO3DParam_( + o3d.ParamOp2FloatsToFloat2, "output", "ParamMatrix4Output"); +})(); + +/** + * Called by o3d.Param*Output whenever its value gets read. + * @return {Array<number>} 2-element array equal to [input0,input1] + */ +o3d.ParamOp2FloatsToFloat2.prototype.updateOutputs = function() { + this.last_output_value_[0] = this.getParam("input0").value; + this.last_output_value_[1] = this.getParam("input1").value; + return this.last_output_value_; +}; + +/** + * A Param operation that takes 3 floats to produce a float3. + * @constructor + * @extends {o3d.ParamObject} + */ +o3d.ParamOp3FloatsToFloat3 = function() { + o3d.ParamObject.call(this); + this.last_output_value_ = [0,0,0]; +}; +o3d.inherit('ParamOp3FloatsToFloat3', 'ParamObject'); + +(function(){ + for (var i = 0; i < 3; i++) { + o3d.ParamObject.setUpO3DParam_( + o3d.ParamOp3FloatsToFloat3, "input"+i, "ParamFloat"); + } + o3d.ParamObject.setUpO3DParam_( + o3d.ParamOp3FloatsToFloat3, "output", "ParamMatrix4Output"); +})(); + +/** + * Called by o3d.Param*Output whenever its value gets read. + * @return {Array<number>} 3-element array equal to [input0,input1,input2] + */ +o3d.ParamOp3FloatsToFloat3.prototype.updateOutputs = function() { + this.last_output_value_[0] = this.getParam("input0").value; + this.last_output_value_[1] = this.getParam("input1").value; + this.last_output_value_[2] = this.getParam("input2").value; + return this.last_output_value_; +}; + +/** + * A Param operation that takes 4 floats to produce a float4. + * @constructor + * @extends {o3d.ParamObject} + */ +o3d.ParamOp4FloatsToFloat4 = function() { + o3d.ParamObject.call(this); + this.last_output_value_ = [0,0,0,0]; +}; +o3d.inherit('ParamOp4FloatsToFloat4', 'ParamObject'); + +(function(){ + for (var i = 0; i < 4; i++) { + o3d.ParamObject.setUpO3DParam_( + o3d.ParamOp4FloatsToFloat4, "input"+i, "ParamFloat"); + } + o3d.ParamObject.setUpO3DParam_( + o3d.ParamOp4FloatsToFloat4, "output", "ParamMatrix4Output"); +})(); + +/** + * Called by o3d.Param*Output whenever its value gets read. + * @return {Array<number>} 4-element array equal to + * [input0,input1,input2,input3] + */ +o3d.ParamOp4FloatsToFloat4.prototype.updateOutputs = function() { + this.last_output_value_[0] = this.getParam("input0").value; + this.last_output_value_[1] = this.getParam("input1").value; + this.last_output_value_[2] = this.getParam("input2").value; + this.last_output_value_[3] = this.getParam("input3").value; + return this.last_output_value_; +}; + +/** + * A Param operation that takes 16 floats to produce a 4-by-4 matrix. + * @constructor + * @extends {o3d.ParamObject} + */ +o3d.ParamOp16FloatsToMatrix4 = function() { + o3d.ParamObject.call(this); + this.last_output_value_ = [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]; +}; +o3d.inherit('ParamOp16FloatsToMatrix4', 'ParamObject'); + +(function(){ + for (var i = 0; i < 16; i++) { + o3d.ParamObject.setUpO3DParam_( + o3d.ParamOp16FloatsToMatrix4, "input"+i, "ParamFloat"); + } + o3d.ParamObject.setUpO3DParam_( + o3d.ParamOp16FloatsToMatrix4, "output", "ParamMatrix4Output"); +})(); + +/** + * Called by o3d.Param*Output whenever its value gets read. + * @return {Array<Array<number>>} 4x4 array equal to + * [[i0,i1,i2,i3],[i4,i5,i6,i7],[i8,i9,i10,i11],[i12,i13,i14,i15]] + */ +o3d.ParamOp16FloatsToMatrix4.prototype.updateOutputs = function() { + for (var i = 0; i < 16; i++) { + this.last_output_value_[i/4][i%4] = this.getParam("input"+i).value; + } + return this.last_output_value_; +}; + +/** + * A Param operation that takes 9 floats to produce a 4-by-4 matrix. + * The 9 floats encode a translation vector, angles of rotation around + * the x, y, and z axes, and three scaling factors. The resulting + * transformation scales first, then then rotates around the z-axis, + * then the y-axis, then the x-axis, then translates. + * @param {string} name Tne name of the parameter. + * @param {string} className The param class name. + * @param {number} numElements The number of Elements if the param is an array. + * @param {string} sasClassName The sas class name if the param is an sas type. + * @param {string} semantic The relevant semantic. + * @constructor + * @extends {o3d.ParamObject} + */ +o3d.TRSToMatrix4 = function() { + o3d.ParamObject.call(this); + + this.rotateX = 0; + this.rotateY = 0; + this.rotateZ = 0; + + this.translateX = 0; + this.translateY = 0; + this.translateZ = 0; + + this.scaleX = 1; + this.scaleY = 1; + this.scaleZ = 1; + + this.last_output_value_ = [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]; + + this.createParam("output", "ParamMatrix4Output"); +}; +o3d.inherit('TRSToMatrix4', 'ParamObject'); + +(function(){ + var proplist = ["rotateX", "rotateY", "rotateZ", + "translateX", "translateY", "translateZ", + "scaleX", "scaleY", "scaleZ"]; + for (var i = 0; i < proplist.length; i++) { + o3d.ParamObject.setUpO3DParam_(o3d.TRSToMatrix4, proplist[i], "ParamFloat"); + } + o3d.ParamObject.setUpO3DParam_( + o3d.TRSToMatrix4, "output", "ParamMatrix4Output"); +})(); + +/** + * Called by o3d.Param*Output whenever its value gets read. + * @return {matrix4} 4x4 array equal to Translate * Rotate * Scale. + */ +o3d.TRSToMatrix4.prototype.updateOutputs = function () { + var ret = this.last_output_value_; + var rX = this.rotateX; + var rY = this.rotateY; + var rZ = this.rotateZ; + var sX = this.scaleX; + var sY = this.scaleY; + var sZ = this.scaleZ; + var sinX = Math.sin(rX); + var cosX = Math.cos(rX); + var sinY = Math.sin(rY); + var cosY = Math.cos(rY); + var sinZ = Math.sin(rZ); + var cosZ = Math.cos(rZ); + var cosZSinY = cosZ * sinY; + var sinZSinY = sinZ * sinY; + + ret[0].splice(0, 4, cosZ * cosY * sX, + sinZ * cosY * sX, + -sinY * sX, + 0); + ret[1].splice(0, 4, (cosZSinY * sinX - sinZ * cosX) * sY, + (sinZSinY * sinX + cosZ * cosX) * sY, + cosY * sinX * sY, + 0); + ret[2].splice(0, 4, (cosZSinY * cosX + sinZ * sinX) * sZ, + (sinZSinY * cosX - cosZ * sinX) * sZ, + cosY * cosX * sZ, + 0); + ret[3].splice(0, 4, this.translateX, + this.translateY, + this.translateZ, + 1); + return ret; +}; |