diff options
author | pathorn@chromium.org <pathorn@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-30 22:22:32 +0000 |
---|---|---|
committer | pathorn@chromium.org <pathorn@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-07-30 22:22:32 +0000 |
commit | f997d14a7debb50eeaf28a42355956f635e6a89f (patch) | |
tree | 52e1a7c6138184c6c869f84f3bb117287d162b2f /o3d | |
parent | aaf260fe5efef2e29e0a038c8e9ce1c699b6c4cb (diff) | |
download | chromium_src-f997d14a7debb50eeaf28a42355956f635e6a89f.zip chromium_src-f997d14a7debb50eeaf28a42355956f635e6a89f.tar.gz chromium_src-f997d14a7debb50eeaf28a42355956f635e6a89f.tar.bz2 |
o3d-webgl: Skin, SkinEval and VertexSource classes.
This allows the basic sproingie skinning demo to work.
BUG=none
TEST=o3d-webgl-samples/skinning.html works
Review URL: http://codereview.chromium.org/3005015
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@54397 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'o3d')
-rw-r--r-- | o3d/samples/o3d-webgl-samples/simpleviewer/simpleviewer.html | 8 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl-samples/skinning.html | 277 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/base.js | 2 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/curve.js | 12 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/param.js | 9 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/primitive.js | 15 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/skin.js | 666 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/stream.js | 18 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/stream_bank.js | 45 | ||||
-rw-r--r-- | o3d/samples/o3d-webgl/transform.js | 25 | ||||
-rw-r--r-- | o3d/samples/o3djs/serialization.js | 10 |
11 files changed, 1067 insertions, 20 deletions
diff --git a/o3d/samples/o3d-webgl-samples/simpleviewer/simpleviewer.html b/o3d/samples/o3d-webgl-samples/simpleviewer/simpleviewer.html index f3cf07f..81264c1 100644 --- a/o3d/samples/o3d-webgl-samples/simpleviewer/simpleviewer.html +++ b/o3d/samples/o3d-webgl-samples/simpleviewer/simpleviewer.html @@ -227,13 +227,17 @@ function loadFile(context, path) { g_loadingElement.innerHTML = "Loading: " + path; enableInput(false); try { - o3djs.scene.loadScene(g_client, g_pack, parent, path, callback); + var secondCounter = g_pack.createObject('SecondCounter'); + secondCounter.countMode = o3d.Counter.CYCLE; + secondCounter.start = 0; + secondCounter.end = 1; + o3djs.scene.loadScene(g_client, g_pack, parent, path, callback, + {opt_animSource: secondCounter.getParam('count')}); } catch (e) { enableInput(true); g_loadingElement.innerHTML = "loading failed : " + e; } } - return parent; } diff --git a/o3d/samples/o3d-webgl-samples/skinning.html b/o3d/samples/o3d-webgl-samples/skinning.html new file mode 100644 index 0000000..c1b75bc --- /dev/null +++ b/o3d/samples/o3d-webgl-samples/skinning.html @@ -0,0 +1,277 @@ +<!-- +Copyright 2009, Google Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +--> + +<!-- +O3D Skinning example. + +Shows a simple skinned cylinder. +--> +<!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> +Skinning. +</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.rendergraph'); +o3djs.require('o3djs.material'); +o3djs.require('o3djs.primitives'); + +// Events +// init() once the page has finished loading. +// unload() when the page is unloaded. +window.onload = init; +window.onunload = unload; + +// constants +var NUM_BONES = 11; +var BONE_SPACING = 20; + +// global variables +var g_o3d; +var g_math; +var g_client; +var g_viewInfo; +var g_pack; +var g_transforms = []; +var g_finished = false; // for selenium testing. +var g_clock = 0; +var g_timeMult = 1; + +/** + * Creates the client area. + */ +function init() { + o3djs.webgl.makeClients(initStep2); +} + +/** + * Initializes O3D and creates one shape. + * @param {Array} clientElements Array of o3d object elements. + */ +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(); + + // Create the render graph for a view. + g_viewInfo = o3djs.rendergraph.createBasicView( + g_pack, + g_client.root, + g_client.renderGraphRoot); + + // Set 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); + + // Create a material. + var material = o3djs.material.createBasicMaterial( + g_pack, + g_viewInfo, + [1, 0.2, 1, 1]); + + // Create a cylinder. + var vertexInfo = o3djs.primitives.createCylinderVertices( + 40, 200, 12, 20, + [[1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 100, 0, 1]]); + var shape = vertexInfo.createShape(g_pack, material); + + // Create an ParamArray to hold matrices for skinning. + var matrices = g_pack.createObject('ParamArray'); + + // Create a Skin to hold the influences and bind pose. + var skin = g_pack.createObject('Skin'); + + // Create a SkinEval to use the Skin. + var skinEval = g_pack.createObject('SkinEval'); + + // Tell the SkinEval which matrices and Skin to use. + skinEval.matrices = matrices; + skinEval.skin = skin; + + // Create matrices on our ParamArray. + matrices.resize(NUM_BONES, 'o3d.ParamMatrix4'); + + // Create 11 transforms for the bones and parent them into a chain. + for (var ii = 0; ii < NUM_BONES; ++ii) { + var transform = g_pack.createObject('Transform'); + g_transforms[ii] = transform; + if (ii > 0) { + transform.translate(0, BONE_SPACING, 0); + } + transform.parent = ii == 0 ? g_client.root : g_transforms[ii - 1]; + // Bind the world matrix of the transform to the corresponding matrix in the + // ParamArray. + matrices.getParam(ii).bind(transform.getParam('worldMatrix')); + // Store the inverse world matrix of each transform as the bind pose for the + // skin. + skin.setInverseBindPoseMatrix(ii, g_math.inverse( + transform.getUpdatedWorldMatrix())); + } + + // Add the cylinder to the root transform. + g_transforms[0].addShape(shape); + + // Skinning happens in world space so bind the transform of the shape + // to the SkinEval so it can put our skin in object space. + skinEval.getParam('base').bind(g_transforms[0].getParam('worldMatrix')); + + // Make our source data for skinning and setup the influences for each bone. + // We only need the position and normals. + var positions = []; + var normals = []; + var positionStream = vertexInfo.findStream(g_o3d.Stream.POSITION); + var normalStream = vertexInfo.findStream(g_o3d.Stream.NORMAL); + var numVertices = positionStream.numElements(); + for (var ii = 0; ii < numVertices; ++ii) { + // Choose the bones to influence the vertex based on its height. + // boneArea will be a fractional value between 2 bones based on the Y + // position of the vertex. In other words, if the y Position of the vertex + // 63 and the bones are 20 units apart then boneArea will = 6.15 meaning + // it's between bones 6 and 7 + var boneArea = positionStream.getElementVector(ii)[1] / BONE_SPACING; + + // Pull out the fractional part of boneArea + var influence = boneArea % 1; + + // Compute the bone indicies + var bone1 = Math.floor(boneArea); + var bone2 = bone1 + 1; + if (bone2 >= NUM_BONES) { + bone2 = NUM_BONES - 1; + } + + // Now make each vertex be influenced by these two bones. In the example + // above where boneArea was 6.15 we let bone1 influence the vertex by + // (1 - 0.15) or 0.85 and bone2 influence it by 0.15. + skin.setVertexInfluences(ii, [bone1, 1 - influence, bone2, influence]); + } + + // Create a SourceBuffer for the Skin and set the vertices on it. + var sourceBuffer = g_pack.createObject('SourceBuffer'); + var positionField = sourceBuffer.createField('FloatField', 3); + var normalField = sourceBuffer.createField('FloatField', 3); + sourceBuffer.allocateElements(numVertices); + positionField.setAt(0, positionStream.elements); + normalField.setAt(0, normalStream.elements); + + // Tell the skinEval how to use the SourceBuffer + skinEval.setVertexStream(g_o3d.Stream.POSITION, + 0, + positionField, + 0); + skinEval.setVertexStream(g_o3d.Stream.NORMAL, + 0, + normalField, + 0); + + // Bind the Primitive's position and normal streams + // to the SkinEval so the SkinEval will fill them in each frame. + var streamBank = shape.elements[0].streamBank; + streamBank.bindStream(skinEval, g_o3d.Stream.POSITION, 0); + streamBank.bindStream(skinEval, g_o3d.Stream.NORMAL, 0); + + // Setup an onrender callback for animation. + g_client.setRenderCallback(onrender); + + g_finished = true; // for selenium testing. +} + +/** + * Called every frame. + * @param {!o3d.RenderEvent} renderEvent Info for rendering. + */ +function onrender(renderEvent) { + // Get the number of seconds since the last render. + var elapsedTime = renderEvent.elapsedTime; + g_clock += elapsedTime * g_timeMult; + + var x = Math.sin(g_clock * 0.3) * 400; + var z = Math.cos(g_clock * 0.3) * 400; + var y = Math.sin(g_clock * 0.7) * 50 + 100; + + // spin the camera. + g_viewInfo.drawContext.view = g_math.matrix4.lookAt( + [x, y, z], // eye + [0, 100, 0], // target + [0, 1, 0]); // up + + // Make our bone chain bend. + var rotation = Math.PI / g_transforms.length * Math.sin(g_clock * 1); + for (var ii = 1; ii < g_transforms.length; ++ii) { + var transform = g_transforms[ii]; + transform.identity(); + transform.translate(0, BONE_SPACING, 0); + transform.rotateX(rotation); + } +} + +/** + * Removes any callbacks so they don't get called after the page has unloaded. + */ +function unload() { + if (g_client) { + g_client.cleanup(); + } +} +</script> +</head> +<body> +<h1>Skinning</h1> +<!-- 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 09ff8ea..c9876be 100644 --- a/o3d/samples/o3d-webgl/base.js +++ b/o3d/samples/o3d-webgl/base.js @@ -263,6 +263,7 @@ o3d.include('element'); o3d.include('field'); o3d.include('buffer'); o3d.include('stream'); +o3d.include('vertex_source'); o3d.include('stream_bank'); o3d.include('primitive'); o3d.include('shape'); @@ -273,5 +274,6 @@ o3d.include('param_operation'); o3d.include('function'); o3d.include('counter'); o3d.include('curve'); +o3d.include('skin'); diff --git a/o3d/samples/o3d-webgl/curve.js b/o3d/samples/o3d-webgl/curve.js index d8ec585..54c76f5 100644 --- a/o3d/samples/o3d-webgl/curve.js +++ b/o3d/samples/o3d-webgl/curve.js @@ -616,6 +616,16 @@ o3d.Curve.prototype.isDiscontinuous = function() { }; /** + * Comparator to allow sorting by keys by their input value. + * @param {o3d.CurveKey} a First input to compare. + * @param {o3d.CurveKey} b Second input to compare. + * @return {number} positive, zero, or negative (see Array.sort) + */ +o3d.Curve.compareInputs_ = function(a, b) { + return a.input - b.input; +}; + +/** * Sorts keys (if sorted_ is false) and updates discontinuous_ * (if check_discontinuity_ is true). * Called automatically when necessary in evaluate and isDiscontinuous. @@ -624,7 +634,7 @@ o3d.Curve.prototype.isDiscontinuous = function() { o3d.Curve.prototype.updateCurveInfo_ = function() { if (!this.sorted_) { // resort keys - this.keys.sort(); + this.keys.sort(o3d.Curve.compareInputs_); this.sorted_ = true; this.invalidateCache_(); } diff --git a/o3d/samples/o3d-webgl/param.js b/o3d/samples/o3d-webgl/param.js index 8e3dd8c..d30af44 100644 --- a/o3d/samples/o3d-webgl/param.js +++ b/o3d/samples/o3d-webgl/param.js @@ -443,6 +443,15 @@ o3d.inherit('ParamTransform', 'Param'); /** + * @constructor + */ +o3d.ParamVertexBufferStream = function() { + o3d.Param.call(this); + this.stream = null; +}; +o3d.inherit('ParamVertexBufferStream', 'Param'); + +/** * Base class for the types of matrix4 params that compute their own * value when asked (ProjectionParamMatrix4 etc). * @constructor diff --git a/o3d/samples/o3d-webgl/primitive.js b/o3d/samples/o3d-webgl/primitive.js index d9f7bde..17f483a 100644 --- a/o3d/samples/o3d-webgl/primitive.js +++ b/o3d/samples/o3d-webgl/primitive.js @@ -129,7 +129,16 @@ o3d.Primitive.prototype.render = function() { semantic_index < streams.length; ++semantic_index) { var gl_index = semantic + semantic_index - 1; - var stream = streams[semantic_index]; + var stream_param = streams[semantic_index]; + + while (!stream_param.owner_.updateStreams && + stream_param.inputConnection) { + stream_param = stream_param.inputConnection; + } + if (stream_param.owner_.updateStreams) { + stream_param.owner_.updateStreams(); // Triggers updating. + } + var stream = streams[semantic_index].stream; var field = stream.field; var buffer = field.buffer; @@ -344,8 +353,8 @@ o3d.Primitive.prototype.intersectRay = var streamBank = this.streamBank; var indexBuffer = this.indexBuffer; - var stream = - this.streamBank.vertex_streams_[o3d.Stream.POSITION][position_stream_index]; + var positionStreams = this.streamBank.vertex_streams_[o3d.Stream.POSITION]; + var stream = positionStreams[position_stream_index].stream; var field = stream.field; var buffer = field.buffer; diff --git a/o3d/samples/o3d-webgl/skin.js b/o3d/samples/o3d-webgl/skin.js new file mode 100644 index 0000000..ae88933 --- /dev/null +++ b/o3d/samples/o3d-webgl/skin.js @@ -0,0 +1,666 @@ +/* + * 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 Skin holds an array of matrix indices and influences for vertices in a + * skin. A Skin is data only and can be used by one or more SkinEvals to + * implement skinning. + * + * @constructor + * @extends {o3d.NamedObject} + */ +o3d.Skin = function() { + o3d.NamedObject.call(this); + + /** + * Set of influences contained in this skin. + * @type {!Array<!Array<number>>} + */ + this.influences = []; + + /** + * The array of inverse bone matrices (Array<matrix4>) + * + * @type {!Array<!Array<!Array<number>>>} + */ + this.inverseBindPoseMatrices = []; + + /** + * Whether the getHighestMatrixIndex and getHighestInfluences are up-to-date. + * + * @type {boolean} + * @private + */ + this.info_valid_ = false; + + /** + * Cache of getHighestMatrixIndex(). + * + * @type {number} + * @private + */ + this.highest_matrix_index_ = 0; + + /** + * Cache of getHighestInfluences(). + * + * @type {number} + * @private + */ + this.highest_influences_ = 0; +}; +o3d.inherit('Skin', 'NamedObject'); + +/** + * Sets the influences for an individual vertex. + * + * @param {number} vertex_index The index of the vertex to set influences for. + * @param {!Array<number>} influences An array of pairs of numbers where + * the first number is the index of the matrix to influence the vertex + * and the second number is the amount of influence where: + * 0 = no influence and 1 = 100% influence. + */ +o3d.Skin.prototype.setVertexInfluences = function( + vertex_index, influences) { + if (influences.length % 2 != 0) { + this.gl.client.error_callback("odd number of values passed into" + + "SetVertexInfluence. Even number required as they are pairs."); + return; + } + this.influences[vertex_index] = influences; + this.info_valid_ = false; +}; + +/** + * Gets the influences for an individual vertex. + * + * @param {number} vertex_index The index of the vertex to get influences from. + * @return {!Array<number>} An array of pairs of numbers where the first number + * of each pair is the index of the matrix that influence this vertex and + * the second number is the amount of influence where: + * 0 = no influence and 1 = 100% influence. + */ +o3d.Skin.prototype.getVertexInfluences = function(vertex_index) { + return this.influences[vertex_index] || []; +}; + +/** + * Update the highest influences and highest matrix index. + * @private + */ +o3d.Skin.prototype.updateInfo_ = function() { + if (!this.info_valid_) { + this.info_valid_ = true; + this.highest_matrix_index_ = 0; + this.highest_influences_ = 0; + for (var ii = 0; ii < this.influences.length; ++ii) { + var influences = this.influences[ii]; + var len = influences.length; + if (len > this.highest_influences_) { + this.highest_influences_ = len; + } + // Influences array is in pairs: even are vertices, odd are weights + for (var jj = 0; jj < influences.length; jj += 2) { + if (influences[jj] > this.highest_matrix_index_) { + this.highest_matrix_index_ = influences[jj]; + } + } + } + // this.highest_influences_ should be the number of pairs. + if (this.highest_influences_ % 2) { + this.gl.client.error_callback( + "Skin.updateInfo: Influences should not have odd length "); + } + this.highest_influences_ = Math.floor(this.highest_influences_/2); + } +}; + +/** + * Gets the highest matrix index referenced by the influences. + * + * @return {number} The highest matrix index referenced by the influences. + * @private + */ +o3d.Skin.prototype.getHighestMatrixIndex = function() { + this.updateInfo_(); + return this.highest_matrix_index_; +}; + +/** + * Gets the highest number of influences on any vertex. + * + * @return {number} The highest number of influences on any vertex. + * @private + */ +o3d.Skin.prototype.getHighestInfluences = function() { + this.updateInfo_(); + return this.highest_influences_; +}; + +/** + * Sets the inverse bind pose matrix for a particular joint/bone/transform. + * + * @param {number} index Index of bone/joint/transform. + * @param {!Array<!Array<number>>} matrix Inverse bind pose matrix for this + * joint. + */ +o3d.Skin.prototype.setInverseBindPoseMatrix = function(index, matrix) { + this.inverseBindPoseMatrices[index] = matrix; +}; + +/** + * Deserializes from the skin data given a RawData object. + * + * @param {!o3d.RawData} rawData contains skin 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.Skin.prototype.set = function(rawData, opt_offset, opt_length) { + o3d.notImplemented(); +}; + + + +/** + * A SkinEval is a VertexSource that takes its Streams, a ParamArray of 4-by-4 + * matrices and a Skin and skins the vertices in its streams storing the results + * in bound output streams. + * + * Note: Extends StreamBank, which keeps track of storing vertexStreams objects. + * The C++ Plugin had this inherit from VertexSource, but reading through the + * code, I can't find any good reason why. + * + * @constructor + * @extends {o3d.StreamBank} + */ +o3d.SkinEval = function() { + o3d.StreamBank.call(this); + + /** + * The base matrix to subtract from the matrices before skinning. + * @type {!Array<!Array<number>>} + */ + this.base = [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]; + + /** + * Temporary storage for matrix ops. + * @type {!Array<!Array<number>>} + * @private + */ + this.temp_matrix_ = [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]; + + /** + * Array of matrices representing each bone. + * @type {!Array<!Array<!Array<number>>>} + * @private + */ + this.bones_ = []; + + /** + * Cache of StreamInfo objects for input values. Saved to avoid reallocating. + * @type {!Array<o3d.SkinEval.StreamInfo>} + * @private + */ + this.input_stream_infos_ = []; + + /** + * Cache of StreamInfo objects for output values. Saved to avoid reallocating. + * @type {!Array<o3d.SkinEval.StreamInfo>} + * @private + */ + this.output_stream_infos_ = []; +}; +o3d.inherit('SkinEval', 'StreamBank'); + +/** + * The base matrix to subtract from the matrices before skinning. + * + * @type {!Array<!Array<number>>} + */ +o3d.ParamObject.setUpO3DParam_(o3d.SkinEval, "base", "ParamMatrix4"); + +/** + * The Skin to use for skinning. + * @type {Skin} + */ +o3d.ParamObject.setUpO3DParam_(o3d.SkinEval, "skin", "ParamSkin"); + +/** + * The array of matrices to use for skinning. + * @type {ParamArray} + */ +o3d.ParamObject.setUpO3DParam_(o3d.SkinEval, "matrices", "ParamArray"); + +/** + * Multiplies input by weight, and adds with and returns into output. + * + * @param {!Array<!Array<number>>} input Input matrix4 to weight. + * @param {number} weight Amount to weight input matrix by. + * @param {!Array<!Array<number>>} output The result of computing + * output += (input * weight) + * @private + */ +o3d.SkinEval.prototype.multiplyAdd_ = function(input, weight, output) { + var a0 = input[0]; + var a1 = input[1]; + var a2 = input[2]; + var a3 = input[3]; + var b0 = output[0]; + var b1 = output[1]; + var b2 = output[2]; + var b3 = output[3]; + b0[0] += a0[0] * weight; + b0[1] += a0[1] * weight; + b0[2] += a0[2] * weight; + b0[3] += a0[3] * weight; + b1[0] += a1[0] * weight; + b1[1] += a1[1] * weight; + b1[2] += a1[2] * weight; + b1[3] += a1[3] * weight; + b2[0] += a2[0] * weight; + b2[1] += a2[1] * weight; + b2[2] += a2[2] * weight; + b2[3] += a2[3] * weight; + b3[0] += a3[0] * weight; + b3[1] += a3[1] * weight; + b3[2] += a3[2] * weight; + b3[3] += a3[3] * weight; +}; + +/** + * @param {o3d.Skin} skin The skin. + * @private + */ +o3d.SkinEval.prototype.doSkinning_ = function(skin) { + var ii, jj, ll, num_streams; + var influences_array = skin.influences; + + var num_vertices = influences_array.length; + // Update our inputs, lock all the inputs and outputs and check that we have + // the same number of vertices as vertex influences. + for (ii = 0, num_streams = 0; ii < this.vertex_streams_.length; ++ii) { + var array = this.vertex_streams_[ii]; + if (array) { + for (jj = 0; jj < array.length; ++jj, ++num_streams) { + var source_param = array[jj]; + + // Make sure our upstream streams are ready + var input = source_param.inputConnection; + if (input && input.isAClassName("ParamVertexBufferStream")) { + input.updateOutputs(); // will automatically mark us as valid. + } else { + // Mark source_param as valid so we don't evaluate a second time. + // TODO(pathorn): Caching previous computed values. + } + + var source_stream = source_param.stream; + if (source_stream.getMaxVertices() != num_vertices) { + // TODO: Change semantic below to semantic_name. + this.gl.client.error_callback("SkinEval.doSkinning_: " + + "stream " + source_stream.semantic + " index " + + source_stream.semanticIndex + " in SkinEval '" + this.name + + " does not have the same number of vertices as Skin '" + + skin.name + "'"); + return; + } + + // Lock this input. + if (!this.input_stream_infos_[num_streams]) { + this.input_stream_infos_[num_streams] = new o3d.SkinEval.StreamInfo; + } + if (!this.input_stream_infos_[num_streams].init(source_stream, false)) { + var buffer_name; + if (source_stream.field.buffer) { + buffer_name = source_stream.field.buffer.name; + } + this.gl.client.error_callback("SkinEval.doSkinning_: " + + "unable to lock buffer '" + buffer_name + + " used by stream " + source_stream.semantic + " index " + + source_stream.semanticIndex + " in SkinEval '" + this.name + + "'"); + return; + } + + // Lock the outputs to this input. + var outputs = source_param.outputConnections; //ParamVector + if (!this.output_stream_infos_[num_streams]) { + this.output_stream_infos_[num_streams] = []; + } + var output_stream_info = this.output_stream_infos_[num_streams]; + output_stream_info.length = outputs.length; + + for (ll = 0; ll < outputs.length; ++ll) { + var destination_param = outputs[ll]; + if (destination_param.isAClassName('ParamVertexBufferStream')) { + // Mark destination_param valid so we don't evaluate a second time. + // TODO(pathorn): Caching previous computed values. + } else { + this.gl.client.error_callback("SkinEval.doSkinning: " + + destination_param.className + " not ParamVertexBufferStream"); + } + var destination_stream = destination_param.stream; + if (destination_stream.getMaxVertices() != num_vertices) { + this.gl.client.error_callback("SkinEval.doSkinning_: " + + "stream " + destination_stream.semantic + " index " + + destination_stream.semanticIndex + " targeted by SkinEval '" + + this.name + " does not have the same number of vertices as " + + "Skin '" + skin.name + "'"); + return; + } + + if (!output_stream_info[ll]) { + output_stream_info[ll] = new o3d.SkinEval.StreamInfo; + } + if (!output_stream_info[ll].init(destination_stream,true)) { + var buffer_name; + if (destination_stream.field.buffer) { + buffer_name = destination_stream.field.buffer.name; + } + this.gl.client.error_callback("SkinEval.doSkinning_: " + + "unable to lock buffer '" + buffer_name + + " used by stream " + destination_stream.semantic + " index " + + destination_stream.semanticIndex + " targeted by SkinEval '" + + this.name + "'"); + return; + } + } + } + } + } + + // skin. + for (ii = 0; ii < num_vertices; ++ii) { + var influences = influences_array[ii]; + if (influences.length) { + // Even are vertices, odd are weights + var this_matrix_index = influences[0]; + // Get the first matrix. + var this_weight = influences[1]; + + // combine the matrixes for this vertex. + var accumulated_matrix = + [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]]; + this.multiplyAdd_(this.bones_[this_matrix_index], + this_weight, accumulated_matrix); + var num_influences = influences.length; + for (jj = 2; jj < num_influences; jj+=2) { + var influence_matrix_index = influences[jj]; + var influence_weight = influences[jj + 1]; + this.multiplyAdd_(this.bones_[influence_matrix_index], + influence_weight, accumulated_matrix); + } + + // for each source, compute and copy to destination. + for (jj = 0; jj < num_streams; ++jj) { + var input_stream_info = this.input_stream_infos_[jj]; + input_stream_info.compute_function_(accumulated_matrix); + var output_streams = this.output_stream_infos_[jj]; + var num_output_streams = output_streams.length; + for (ll = 0; ll < num_output_streams; ++ll) { + output_streams[ll].copy_function_(input_stream_info); + } + } + } + } +}; + +/** + * Updates the Bones. + */ +o3d.SkinEval.prototype.updateBones_ = function() { + // Get our matrices. + var param_array = this.matrices; + if (!param_array) { + this.gl.client.error_callback("SkinEval.updateBones_: " + + "no matrices for SkinEval '" + this.name + "'"); + return; + } + + var the_skin = this.skin; + if (!the_skin) { + this.gl.client.error_callback("SkinEval.updateBones_: " + + "no skin specified in SkinEval '" + this.name + "'"); + return; + } + + // Make sure the bone indices are in range. + if (the_skin.getHighestMatrixIndex() >= param_array.length) { + this.gl.client.error_callback("SkinEval.updateBones_: " + + "skin '" + the_skin.name + " specified in SkinEval '" + + this.name + + "' references matrices outside the valid range in ParamArray '" + + param_array.name + "'"); + return; + } + + // Make sure the bind pose array size matches the matrices + var inverse_bind_pose_array = the_skin.inverseBindPoseMatrices; + if (inverse_bind_pose_array.length != param_array.length) { + this.gl.client.error_callback("SkinEval.updateBones_: " + + "skin '" + the_skin.name + " specified in SkinEval '" + + this.name + "' and the ParamArray '" + + param_array.name + "' do not have the same number of matrices."); + return; + } + + // Get the inverse of our base to remove from the bones. + var inverse_base = this.temp_matrix_; + o3d.Transform.inverse(this.base, inverse_base); + + for (var ii = 0; ii < param_array.length; ++ii) { + var param = param_array.getParam(ii); // ParamMatrix4 + if (!param) { + this.gl.client.error_callback("SkinEval.updateBones_: " + + "In SkinEval '" + this.name + "' param at index " + ii + + " in ParamArray '" + param_array.name + + " is not a ParamMatrix4"); + return; + } + this.bones_[ii] = [[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]]; + o3d.Transform.compose(param.value, inverse_bind_pose_array[ii], + this.bones_[ii]); + o3d.Transform.compose(inverse_base, this.bones_[ii], this.bones_[ii]); + } +}; + +/** + * Updates the VertexBuffers bound to streams on this VertexSource. + */ +o3d.SkinEval.prototype.updateStreams = function() { + this.updateBones_(); + this.doSkinning_(this.skin); + var ii; + // Unlock any buffers that were locked during skinning + for (ii = 0; ii < this.input_stream_infos_.length; ++ii) { + this.input_stream_infos_[ii].uninit(); + } + for (ii = 0; ii < this.output_stream_infos_.length; ++ii) { + var output_streams = this.output_stream_infos_[ii]; + for (var jj = 0; jj < output_streams.length; ++jj) { + output_streams[jj].uninit(); + } + } +}; + +/** + * This class helps manage each stream. Because allocating memory is slow we + * keep these around across calls and reuse them in place by calling init. + * + * @param {o3d.Stream} stream + * @param {o3d.Buffer.AccessMode} access_mode + * @constructor + * @private + */ +o3d.SkinEval.StreamInfo = function() { + this.compute_function_ = null; + this.copy_function_ = null; + this.result_ = null; + this.field_ = null; + this.values_ = null; + this.buffer_ = null; + this.index_ = 0; + this.writable_ = false; +}; + + +/** + * Initialize this StreamInfo object from the given Stream. + * + * @param {o3d.Stream} stream Stream to lock. + * @param {boolean} access_mode true if writable, false otherwise. + * @return {boolean} True if the buffer lock was successful, false if error. + */ +o3d.SkinEval.StreamInfo.prototype.init = function(stream, access_mode) { + if (this.values_ || this.buffer_) { + return false; + } + var field = stream.field; + var buffer = field.buffer; + // field must be a FloatField, but in o3d-webgl, Field is the same type so + // we can't check isAClassName. + if (!buffer) { + return false; + } + switch (field.numComponents) { + case 3: + this.copy_function_ = this.copyFloat3; + this.compute_function_ = (stream.semantic == o3d.Stream.POSITION) ? + this.computeFloat3AsPoint3 : this.computeFloat3AsVector3; + break; + case 4: + this.compute_function_ = this.computeFloat4AsVector4; + this.copy_function_ = this.copyFloat4; + break; + default: + return false; + } + + buffer.lock(); + this.field_ = field; + this.buffer_ = buffer; + this.values_ = buffer.array_; + this.index_ = this.field_.offset_; + this.writable_ = access_mode; + this.stride_ = buffer.totalComponents; + return true; +}; + +/** + * Uninitialize this StreamInfo object, and unlock the stream. + * Can be reused for another init() call. + */ +o3d.SkinEval.StreamInfo.prototype.uninit = function() { + if (this.buffer_) { + if (this.writable_) { + this.buffer_.unlock(); + } + this.buffer_ = null; + this.field_ = null; + this.values_ = null; + } +}; + +/** + * Consumes the next 3 values from this.values_. + * Multiplies the current value by the matrix and stores it in result_ and + * advances to the next value. + * + * @param {!Array<!Array<number>>} matrix matrix4 to apply to the vector3. + */ +o3d.SkinEval.StreamInfo.prototype.computeFloat3AsVector3 = function(matrix) { + var ii = this.index_; + var vec = [this.values_[ii], this.values_[ii + 1], this.values_[ii + 2], 0]; + this.result_ = o3d.Transform.multiplyVector(matrix, vec); + this.index_ = ii + this.stride_; +}; + +/** + * Consumes the next 3 values from this.values_. + * Multiplies the current value by the matrix and stores it in result_ and + * advances to the next value. + * + * @param {!Array<!Array<number>>} matrix matrix4 to apply to the vector3. + */ +o3d.SkinEval.StreamInfo.prototype.computeFloat3AsPoint3 = function(matrix) { + var ii = this.index_; + // TODO: The C++ code just dropped element 3 of the return Vector4, while + // o3d.Transform.transformPoint divides by the last value to make it 1. + // Which is the right one to use? + var point = [this.values_[ii], this.values_[ii + 1], this.values_[ii + 2], 1]; + this.result_ = o3d.Transform.multiplyVector(matrix, point); + this.index_ = ii + this.stride_; +}; + +/** + * Consumes the next 4 this.values_. + * Multiplies the current value by the matrix and stores it in result_ and + * advances to the next value. + * + * @param {!Array<!Array<number>>} matrix matrix4 to apply to the vector4. + */ +o3d.SkinEval.StreamInfo.prototype.computeFloat4AsVector4 = function(matrix) { + var ii = this.index_; + var vec = [this.values_[ii], this.values_[ii + 1], this.values_[ii + 2], + this.values_[ii + 3]]; + this.result_ = o3d.Transform.multiplyVector(matrix, vec); + this.index_ = ii + this.stride_; +}; + +/** + * Copies the Float3 result_ from source and advances to the next value. + * + * @param {!o3d.SkinEval.StreamInfo} source Source StreamInfo to copy from. + */ +o3d.SkinEval.StreamInfo.prototype.copyFloat3 = function(source) { + var ii = this.index_; + this.values_[ii] = source.result_[0]; + this.values_[ii+1] = source.result_[1]; + this.values_[ii+2] = source.result_[2]; + this.index_ = ii + this.stride_; +}; + +/** + * Copies the Float4 result_ from source and advances to the next value. + * + * @param {!o3d.SkinEval.StreamInfo} source Source StreamInfo to copy from. + */ +o3d.SkinEval.StreamInfo.prototype.copyFloat4 = function(source) { + var ii = this.index_; + this.values_[ii] = source.result_[0]; + this.values_[ii+1] = source.result_[1]; + this.values_[ii+2] = source.result_[2]; + this.values_[ii+3] = source.result_[3]; + this.index_ = ii + this.stride_; +}; + diff --git a/o3d/samples/o3d-webgl/stream.js b/o3d/samples/o3d-webgl/stream.js index 6a96aa1..0daeb39 100644 --- a/o3d/samples/o3d-webgl/stream.js +++ b/o3d/samples/o3d-webgl/stream.js @@ -99,4 +99,22 @@ o3d.Stream.prototype.semanticIndex = 0; o3d.Stream.prototype.startIndex = 0; +/** + * Gets the max number of vertices in this stream. + * + * @return {number} The maximum vertices available given the stream's settings + * and its buffer. + * @private + */ +o3d.Stream.prototype.getMaxVertices = function() { + var buffer = this.field.buffer; + if (!buffer) + return 0; + + var num_elements = buffer.numElements; + if (this.startIndex > num_elements) + return 0; + + return num_elements - this.startIndex; +}; diff --git a/o3d/samples/o3d-webgl/stream_bank.js b/o3d/samples/o3d-webgl/stream_bank.js index d0f1ca7..4d223a3 100644 --- a/o3d/samples/o3d-webgl/stream_bank.js +++ b/o3d/samples/o3d-webgl/stream_bank.js @@ -33,12 +33,13 @@ /** * The StreamBank a collection of streams that hold vertices. * @constructor + * @extends {o3d.VertexSource} */ o3d.StreamBank = function() { - o3d.NamedObject.call(this); + o3d.VertexSource.call(this); this.vertex_streams_ = []; }; -o3d.inherit('StreamBank', 'NamedObject'); +o3d.inherit('StreamBank', 'VertexSource'); /** * Array of streams. @@ -47,14 +48,14 @@ o3d.StreamBank.prototype.vertex_streams_ = []; o3d.StreamBank.prototype.__defineGetter__('vertexStreams', function() { - result = []; + var result = []; for (var i = 0; i < this.vertex_streams_.length; ++i) { var stream_array = this.vertex_streams_[i]; if (stream_array && stream_array.length) { for (var j = 0; j < stream_array.length; ++j) { var stream = stream_array[j]; if (stream) { - result.push(stream); + result.push(stream.stream); } } } @@ -79,8 +80,11 @@ o3d.StreamBank.prototype.setVertexStream = if (this.vertex_streams_[semantic] == undefined) { this.vertex_streams_[semantic] = []; } - this.vertex_streams_[semantic][semantic_index] = new o3d.Stream( - semantic, semantic_index, field, start_index); + var stream = new o3d.Stream(semantic, semantic_index, field, start_index); + var stream_param = new o3d.ParamVertexBufferStream; + stream_param.stream = stream; + stream_param.owner_ = this; + this.vertex_streams_[semantic][semantic_index] = stream_param; }; @@ -88,14 +92,35 @@ o3d.StreamBank.prototype.setVertexStream = * Searches the vertex streams bound to the StreamBank for one with the given * stream semantic. If a stream is not found then it returns null. * @param {o3d.Stream.Semantic} semantic The particular use of this stream. - * @param {o3d.Stream.Semantic} semantic_index Which index of a particular - * semantic to use. + * @param {number} semantic_index Which index of a particular semantic to use. * @return {o3d.Stream} The found stream or null if it does not exist. */ o3d.StreamBank.prototype.getVertexStream = function(semantic, semantic_index) { if (this.vertex_streams_[semantic] == undefined) { - return; + return null; + } + if (!this.vertex_streams_[semantic][semantic_index]) { + return null; + } + return this.vertex_streams_[semantic][semantic_index].stream; +}; + + +/** + * Searches the vertex streams bound to the StreamBank for one with the given + * stream semantic. If a stream is not found then it returns null. + * @param {o3d.Stream.Semantic} semantic The particular use of this stream. + * @param {number} semantic_index Which index of a particular semantic to use. + * @return {o3d.ParamVertexBufferStream} The found stream param or null if it + * does not exist. + * @override + * @protected + */ +o3d.StreamBank.prototype.getVertexStreamParam = + function(semantic, semantic_index) { + if (this.vertex_streams_[semantic] == undefined) { + return null; } return this.vertex_streams_[semantic][semantic_index]; }; @@ -113,7 +138,7 @@ o3d.StreamBank.prototype.removeVertexStream = if (this.vertex_streams_[semantic] == undefined) { return false; } - this.vertex_streams_[semantic][semantic_index] = null; + delete this.vertex_streams_[semantic][semantic_index]; return true; }; diff --git a/o3d/samples/o3d-webgl/transform.js b/o3d/samples/o3d-webgl/transform.js index 640592c..a569dc2 100644 --- a/o3d/samples/o3d-webgl/transform.js +++ b/o3d/samples/o3d-webgl/transform.js @@ -626,6 +626,31 @@ o3d.Transform.transformPoint = function(m, v) { /** + * Takes a 4-by-4 matrix and a vector with 4 entries, + * interprets the vector as a point, transforms that point by the matrix, and + * returns the result as a vector with 4 entries. + * @param {!o3djs.math.Matrix4} m The matrix. + * @param {!o3djs.math.Vector4} v The vector. + * @return {!o3djs.math.Vector4} The transformed vector. + */ +o3d.Transform.multiplyVector = function(m, v) { + var v0 = v[0]; + var v1 = v[1]; + var v2 = v[2]; + var v3 = v[3]; + var m0 = m[0]; + var m1 = m[1]; + var m2 = m[2]; + var m3 = m[3]; + + return [(v0 * m0[0] + v1 * m1[0] + v2 * m2[0] + v3 * m3[0]), + (v0 * m0[1] + v1 * m1[1] + v2 * m2[1] + v3 * m3[1]), + (v0 * m0[2] + v1 * m1[2] + v2 * m2[2] + v3 * m3[2]), + (v0 * m0[3] + v1 * m1[3] + v2 * m2[3] + v3 * m3[3])]; +}; + + +/** * Takes a 4-by-4 matrix and a vector with 3 entries, * interprets the vector as a point, transforms that point by the matrix, * returning the z-component of the result only. diff --git a/o3d/samples/o3djs/serialization.js b/o3d/samples/o3djs/serialization.js index 51e751d..c300023 100644 --- a/o3d/samples/o3djs/serialization.js +++ b/o3d/samples/o3djs/serialization.js @@ -289,10 +289,12 @@ o3djs.serialization.Deserializer = function(pack, json) { 'o3d.Skin': function(deserializer, object, json) { if ('custom' in json) { - var rawData = deserializer.archiveInfo.getFileByURI('skins.bin'); - object.set(rawData, - json.custom.binaryRange[0], - json.custom.binaryRange[1] - json.custom.binaryRange[0]); + if ('binaryRange' in json.custom) { + var rawData = deserializer.archiveInfo.getFileByURI('skins.bin'); + object.set(rawData, + json.custom.binaryRange[0], + json.custom.binaryRange[1] - json.custom.binaryRange[0]); + } } }, |