summaryrefslogtreecommitdiffstats
path: root/o3d
diff options
context:
space:
mode:
authorpathorn@chromium.org <pathorn@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-30 22:22:32 +0000
committerpathorn@chromium.org <pathorn@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-07-30 22:22:32 +0000
commitf997d14a7debb50eeaf28a42355956f635e6a89f (patch)
tree52e1a7c6138184c6c869f84f3bb117287d162b2f /o3d
parentaaf260fe5efef2e29e0a038c8e9ce1c699b6c4cb (diff)
downloadchromium_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.html8
-rw-r--r--o3d/samples/o3d-webgl-samples/skinning.html277
-rw-r--r--o3d/samples/o3d-webgl/base.js2
-rw-r--r--o3d/samples/o3d-webgl/curve.js12
-rw-r--r--o3d/samples/o3d-webgl/param.js9
-rw-r--r--o3d/samples/o3d-webgl/primitive.js15
-rw-r--r--o3d/samples/o3d-webgl/skin.js666
-rw-r--r--o3d/samples/o3d-webgl/stream.js18
-rw-r--r--o3d/samples/o3d-webgl/stream_bank.js45
-rw-r--r--o3d/samples/o3d-webgl/transform.js25
-rw-r--r--o3d/samples/o3djs/serialization.js10
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]);
+ }
}
},