summaryrefslogtreecommitdiffstats
path: root/o3d/samples
diff options
context:
space:
mode:
authorpathorn@chromium.org <pathorn@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-16 20:31:14 +0000
committerpathorn@chromium.org <pathorn@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-16 20:31:14 +0000
commitf18031bef950ee217798ba1e20a0f20cef9c3571 (patch)
treecb001d43c5bcd8422d5e17ce7e4b9efa881e5d67 /o3d/samples
parent4dcbc1b552d96791b13dc3d876868dae3f8ad09d (diff)
downloadchromium_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.html343
-rw-r--r--o3d/samples/o3d-webgl/base.js4
-rw-r--r--o3d/samples/o3d-webgl/client.js9
-rw-r--r--o3d/samples/o3d-webgl/counter.js814
-rw-r--r--o3d/samples/o3d-webgl/curve.js902
-rw-r--r--o3d/samples/o3d-webgl/function.js161
-rw-r--r--o3d/samples/o3d-webgl/param_operation.js338
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;
+};