topical media & game development
graphic-o3d-samples-o3djs-serialization.js / js
/*
* Copyright 2009, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
@fileoverview This file provides support for deserializing (loading)
transform graphs from JSON files.
o3djs.provide('o3djs.serialization');
A Module for handling events related to o3d and various browsers.
@namespace
o3djs.serialization = o3djs.serialization || {};
The oldest supported version of the serializer. It isn't necessary to
increment this version whenever the format changes. Only change it when the
deserializer becomes incapable of deserializing an older version.
@type {number}
o3djs.serialization.supportedVersion = 5;
A Deserializer incrementally deserializes a transform graph.
@constructor
parameter: {!o3d.Pack} pack The pack to deserialize into.
parameter: {!Object} json An object tree conforming to the JSON rules.
o3djs.serialization.Deserializer = function(pack, json) {
The pack to deserialize into.
@type {!o3d.Pack}
this.pack = pack;
An object tree conforming to the JSON rules.
@type {!Object}
this.json = json;
The archive from which assets referenced from JSON are retreived.
@type {o3djs.io.ArchiveInfo}
this.archiveInfo = null;
A map from classname to a function that will create
instances of objects. Add entries to support additional classes.
@type {!Object}
this.createCallbacks = {
'o3d.VertexBuffer': function(deserializer, json) {
var object = deserializer.pack.createObject('o3d.VertexBuffer');
if ('custom' in json) {
var rawData = deserializer.archiveInfo.getFileByURI(
'vertex-buffers.bin');
object.set(rawData,
json.custom.binaryRange[0],
json.custom.binaryRange[1] - json.custom.binaryRange[0]);
for (var i = 0; i < json.custom.fields.length; ++i) {
deserializer.addObject(json.custom.fields[i], object.fields[i]);
}
}
return object;
},
'o3d.SourceBuffer': function(deserializer, json) {
var object = deserializer.pack.createObject('o3d.SourceBuffer');
if ('custom' in json) {
var rawData = deserializer.archiveInfo.getFileByURI(
'vertex-buffers.bin');
object.set(rawData,
json.custom.binaryRange[0],
json.custom.binaryRange[1] - json.custom.binaryRange[0]);
for (var i = 0; i < json.custom.fields.length; ++i) {
deserializer.addObject(json.custom.fields[i], object.fields[i]);
}
}
return object;
},
'o3d.IndexBuffer': function(deserializer, json) {
var object = deserializer.pack.createObject('o3d.IndexBuffer');
if ('custom' in json) {
var rawData = deserializer.archiveInfo.getFileByURI(
'index-buffers.bin');
object.set(rawData,
json.custom.binaryRange[0],
json.custom.binaryRange[1] - json.custom.binaryRange[0]);
for (var i = 0; i < json.custom.fields.length; ++i) {
deserializer.addObject(json.custom.fields[i], object.fields[i]);
}
}
return object;
},
'o3d.Texture2D': function(deserializer, json) {
if ('o3d.uri' in json.params) {
var uri = json.params['o3d.uri'].value;
var rawData = deserializer.archiveInfo.getFileByURI(uri);
if (!rawData) {
throw 'Could not find texture ' + uri + ' in the archive';
}
return deserializer.pack.createTextureFromRawData(
rawData,
true);
} else {
return deserializer.pack.createTexture2D(
json.custom.width,
json.custom.height,
json.custom.format,
json.custom.levels,
json.custom.renderSurfacesEnabled);
}
},
'o3d.TextureCUBE': function(deserializer, json) {
if ('o3d.uri' in json.params) {
var uri = json.params['o3d.uri'].value;
var rawData = deserializer.archiveInfo.getFileByURI(uri);
if (!rawData) {
throw 'Could not find texture ' + uri + ' in the archive';
}
return deserializer.pack.createTextureFromRawData(
rawData,
true);
} else {
return deserializer.pack.createTextureCUBE(
json.custom.edgeLength,
json.custom.format,
json.custom.levels,
json.custom.renderSurfacesEnabled);
}
}
};
A map from classname to a function that will initialize
instances of the given class from JSON data. Add entries to support
additional classes.
@type {!Object}
this.initCallbacks = {
'o3d.Curve': function(deserializer, object, json) {
if ('custom' in json) {
var rawData = deserializer.archiveInfo.getFileByURI('curve-keys.bin');
object.set(rawData,
json.custom.binaryRange[0],
json.custom.binaryRange[1] - json.custom.binaryRange[0]);
}
},
'o3d.Effect': function(deserializer, object, json) {
var uriParam = object.getParam('o3d.uri');
if (uriParam) {
var rawData = deserializer.archiveInfo.getFileByURI(uriParam.value);
if (!rawData) {
throw 'Cannot find shader ' + uriParam.value + ' in archive.';
}
if (!object.loadFromFXString(rawData.stringValue)) {
throw 'Cannot load shader ' + uriParam.value + ' in archive.';
}
}
},
'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]);
}
},
'o3d.SkinEval': function(deserializer, object, json) {
if ('custom' in json) {
for (var i = 0; i < json.custom.vertexStreams.length; ++i) {
var streamJson = json.custom.vertexStreams[i];
var field = deserializer.getObjectById(streamJson.stream.field);
object.setVertexStream(streamJson.stream.semantic,
streamJson.stream.semanticIndex,
field,
streamJson.stream.startIndex);
if ('bind' in streamJson) {
var source = deserializer.getObjectById(streamJson.bind);
object.bindStream(source,
streamJson.stream.semantic,
streamJson.stream.semanticIndex);
}
}
}
},
'o3d.StreamBank': function(deserializer, object, json) {
if ('custom' in json) {
for (var i = 0; i < json.custom.vertexStreams.length; ++i) {
var streamJson = json.custom.vertexStreams[i];
var field = deserializer.getObjectById(streamJson.stream.field);
object.setVertexStream(streamJson.stream.semantic,
streamJson.stream.semanticIndex,
field,
streamJson.stream.startIndex);
if ('bind' in streamJson) {
var source = deserializer.getObjectById(streamJson.bind);
object.bindStream(source,
streamJson.stream.semantic,
streamJson.stream.semanticIndex);
}
}
}
}
};
if (!('version' in json)) {
throw 'Version in JSON file was missing.';
}
if (json.version < o3djs.serialization.supportedVersion) {
throw 'Version in JSON file was ' + json.version +
' but expected at least version ' +
o3djs.serialization.supportedVersion + '.';
}
if (!('objects' in json)) {
throw 'Objects array in JSON file was missing.';
}
An array of all objects deserialized so far, indexed by object id. Id zero
means null.
@type {!Array.<Object>}
@private
this.objectsById_ = [null];
An array of objects deserialized so far, indexed by position in the JSON.
@type {!Array.<Object>}
@private
this.objectsByIndex_ = [];
Array of all classes present in the JSON.
@type {!Array.<string>}
@private
this.classNames_ = [];
for (var className in json.objects) {
this.classNames_.push(className);
}
The current phase_ of deserialization. In phase_ 0, objects
are created and their ids registered. In phase_ 1, objects are
initialized from JSON data.
@type {number}
@private
this.phase_ = 0;
Index of the next class to be deserialized in classNames_.
@type {number}
@private
this.nextClassIndex_ = 0;
Index of the next object of the current class to be deserialized.
@type {number}
@private
this.nextObjectIndex_ = 0;
Index of the next object to be deserialized in objectsByIndex_.
@type {number}
@private
this.globalObjectIndex_ = 0;
};
Get the object with the given id.
parameter: {number} id The id to lookup.
returns: {Object} The object with the given id.
o3djs.serialization.Deserializer.prototype.getObjectById = function(id) {
return this.objectsById_[id];
};
When a creation or init callback creates an object that the Deserializer
is not aware of, it can associate it with an id using this function, so that
references to the object can be resolved.
parameter: {number} id The is of the object.
parameter: {!Object} object The object to register.
o3djs.serialization.Deserializer.prototype.addObject = function(
id, object) {
this.objectsById_[id] = object;
};
Deserialize a value. Identifies reference values and converts
their object id into an object reference. Otherwise returns the
value unchanged.
parameter: {*} valueJson The JSON representation of the value.
returns: {*} The JavaScript representation of the value.
o3djs.serialization.Deserializer.prototype.deserializeValue = function(
valueJson) {
if (typeof(valueJson) === 'object') {
if (valueJson === null) {
return null;
}
if ('length' in valueJson) {
for (var i = 0; i != valueJson.length; ++i) {
valueJson[i] = this.deserializeValue(valueJson[i]);
}
return valueJson;
}
var refId = valueJson['ref'];
if (refId !== undefined) {
var referenced = this.objectsById_[refId];
if (referenced === undefined) {
throw 'Could not find object with id ' + refId + '.';
}
return referenced;
}
}
return valueJson;
};
Sets the value of a param on an object or binds a param to another.
parameter: {!Object} object The object holding the param.
parameter: {string} paramName The name of the param.
parameter: {*} propertyJson The JSON representation of the value.
@private
o3djs.serialization.Deserializer.prototype.setParamValue_ = function(
object, paramName, propertyJson) {
var param = object.getParam(paramName);
if (param === null)
return;
var valueJson = propertyJson['value'];
if (valueJson !== undefined) {
param.value = this.deserializeValue(valueJson);
}
var bindId = propertyJson['bind'];
if (bindId !== undefined) {
var referenced = this.objectsById_[bindId];
if (referenced === undefined) {
throw 'Could not find output param with id ' + bindId + '.';
}
param.bind(referenced);
}
};
Creates a param on an object and adds it's id so that other objects can
reference it.
parameter: {!Object} object The object to hold the param.
parameter: {string} paramName The name of the param.
parameter: {*} propertyJson The JSON representation of the value.
@private
o3djs.serialization.Deserializer.prototype.createAndIdentifyParam_ =
function(object, paramName, propertyJson) {
var propertyClass = propertyJson['class'];
var param;
if (propertyClass !== undefined) {
param = object.createParam(paramName, propertyClass);
} else {
param = object.getParam(paramName);
}
var paramId = propertyJson['id'];
if (paramId !== undefined && param !== null) {
this.objectsById_[paramId] = param;
}
};
First pass: create all objects and additional params. We need two
passes to support references to objects that appear later in the
JSON.
parameter: {number} amountOfWork The number of loop iterations to perform of
this phase_.
@private
o3djs.serialization.Deserializer.prototype.createObjectsPhase_ =
function(amountOfWork) {
for (; this.nextClassIndex_ < this.classNames_.length;
++this.nextClassIndex_) {
var className = this.classNames_[this.nextClassIndex_];
var classJson = this.json.objects[className];
var numObjects = classJson.length;
for (; this.nextObjectIndex_ < numObjects; ++this.nextObjectIndex_) {
if (amountOfWork-- <= 0)
return;
var objectJson = classJson[this.nextObjectIndex_];
var object = undefined;
if ('id' in objectJson) {
object = this.objectsById_[objectJson.id];
}
if (object === undefined) {
if (className in this.createCallbacks) {
object = this.createCallbacks[className](this, objectJson);
} else {
object = this.pack.createObject(className);
}
}
this.objectsByIndex_[this.globalObjectIndex_++] = object;
if ('id' in objectJson) {
this.objectsById_[objectJson.id] = object;
}
if ('params' in objectJson) {
if ('length' in objectJson.params) {
for (var paramIndex = 0; paramIndex != objectJson.params.length;
++paramIndex) {
var paramJson = objectJson.params[paramIndex];
this.createAndIdentifyParam_(object, paramIndex, paramJson);
}
} else {
for (var paramName in objectJson.params) {
var paramJson = objectJson.params[paramName];
this.createAndIdentifyParam_(object, paramName, paramJson);
}
}
}
}
this.nextObjectIndex_ = 0;
}
if (this.nextClassIndex_ === this.classNames_.length) {
this.nextClassIndex_ = 0;
this.nextObjectIndex_ = 0;
this.globalObjectIndex_ = 0;
++this.phase_;
}
};
Second pass: set property and parameter values and bind parameters.
parameter: {number} amountOfWork The number of loop iterations to perform of
this phase_.
@private
o3djs.serialization.Deserializer.prototype.setPropertiesPhase_ = function(
amountOfWork) {
for (; this.nextClassIndex_ < this.classNames_.length;
++this.nextClassIndex_) {
var className = this.classNames_[this.nextClassIndex_];
var classJson = this.json.objects[className];
var numObjects = classJson.length;
for (; this.nextObjectIndex_ < numObjects; ++this.nextObjectIndex_) {
if (amountOfWork-- <= 0)
return;
var objectJson = classJson[this.nextObjectIndex_];
var object = this.objectsByIndex_[this.globalObjectIndex_++];
if ('properties' in objectJson) {
for (var propertyName in objectJson.properties) {
if (propertyName in object) {
var propertyJson = objectJson.properties[propertyName];
var propertyValue = this.deserializeValue(propertyJson);
object[propertyName] = propertyValue;
}
};
}
if ('params' in objectJson) {
if ('length' in objectJson.params) {
for (var paramIndex = 0; paramIndex != objectJson.params.length;
++paramIndex) {
var paramJson = objectJson.params[paramIndex];
this.setParamValue_(object, paramIndex, paramJson);
}
} else {
for (var paramName in objectJson.params) {
var paramJson = objectJson.params[paramName];
this.setParamValue_(object, paramName, paramJson);
}
}
}
if (className in this.initCallbacks) {
this.initCallbacks[className](this, object, objectJson);
}
}
this.nextObjectIndex_ = 0;
}
if (this.nextClassIndex_ === this.classNames_.length) {
this.nextClassIndex_ = 0;
this.nextObjectIndex_ = 0;
this.globalObjectIndex_ = 0;
++this.phase_;
}
};
Perform a certain number of iterations of the deserializer. Keep calling this
function until it returns false.
parameter: {number} opt_amountOfWork The number of loop iterations to run. If
not specified, runs the deserialization to completion.
returns: {boolean} Whether work remains to be done.
o3djs.serialization.Deserializer.prototype.run = function(
opt_amountOfWork) {
if (!opt_amountOfWork) {
while (this.run(10000)) {
}
return false;
} else {
switch (this.phase_) {
case 0:
this.createObjectsPhase_(opt_amountOfWork);
break;
case 1:
this.setPropertiesPhase_(opt_amountOfWork);
break;
}
return this.phase_ < 2;
}
};
Deserializes (loads) a transform graph in the background. Invokes
a callback function on completion passing the pack and the thrown
exception on failure or the pack and a null exception on success.
parameter: {!o3d.Pack} pack The pack to create the deserialized objects
in.
parameter: {!Object} json An object tree conforming to the JSON rules.
parameter: {number} time The amount of the time (in seconds) the deserializer
should aim to complete in.
parameter: {!function(o3d.Pack, *): void} callback The function that
is called on completion. The second parameter is null on success or
the thrown exception on failure.
o3djs.serialization.Deserializer.prototype.runBackground = function(
pack, json, time, callback) {
var workToDo = json.objects.length * 2;
var timerCallbacks = time * 60;
var amountPerCallback = workToDo / timerCallbacks;
var intervalId;
function deserializeMore() {
try {
if (!this.run(amountPerCallback)) {
window.clearInterval(intervalId);
callback(pack, null);
}
} catch(e) {
window.clearInterval(intervalId);
callback(pack, e);
}
}
intervalId = window.setInterval(deserializeMore, 1000 / 60);
};
Creates a deserializer that will incrementally deserialize a
transform graph. The deserializer object has a method
called run that does a fixed amount of work and returns.
It returns true until the transform graph is fully deserialized.
It returns false from then on.
parameter: {!o3d.Pack} pack The pack to create the deserialized
objects in.
parameter: {!Object} json An object tree conforming to the JSON rules.
returns: {!o3djs.serialization.Deserializer} A deserializer object.
o3djs.serialization.createDeserializer = function(pack, json) {
return new o3djs.serialization.Deserializer(pack, json);
};
Deserializes a transform graph.
parameter: {!o3d.Pack} pack The pack to create the deserialized
objects in.
parameter: {!Object} json An object tree conforming to the JSON rules.
o3djs.serialization.deserialize = function(pack, json) {
var deserializer = o3djs.serialization.createDeserializer(pack, json);
deserializer.run();
};
Deserializes a single json object named 'scene.json' from a loaded
o3djs.io.ArchiveInfo.
parameter: {!o3djs.io.archiveInfo} archiveInfo Archive to load from.
parameter: {string} sceneJsonUri The relative URI of the scene JSON file within
the archive.
parameter: {!o3d.Pack} pack The pack to create the deserialized objects
in.
parameter: {!o3d.Transform} parent Transform to parent loaded stuff from.
parameter: {opt_animSource: !o3d.ParamFloat} opt_options Options. opt_animSource
is an optional ParamFloat that will be bound as the source for all
animation in the scene.
o3djs.serialization.deserializeArchive = function(archiveInfo,
sceneJsonUri,
pack,
parent,
opt_options) {
var jsonFile = archiveInfo.getFileByURI(sceneJsonUri);
if (!jsonFile) {
throw 'Could not find ' + sceneJsonUri + ' in archive';
}
var parsed = eval('(' + jsonFile.stringValue + ')');
var deserializer = o3djs.serialization.createDeserializer(pack, parsed);
deserializer.addObject(parsed.o3d_rootObject_root, parent);
deserializer.archiveInfo = archiveInfo;
deserializer.run();
var objects = pack.getObjects('o3d.animSourceOwner', 'o3d.ParamObject');
if (objects.length > 0) {
// Rebind the output connections of the animSource to the user's param.
if (opt_options && opt_options.opt_animSource) {
var animSource = objects[0].getParam('animSource');
var outputConnections = animSource.outputConnections;
for (var ii = 0; ii < outputConnections.length; ++ii) {
outputConnections[ii].bind(opt_options.opt_animSource);
}
}
// Remove special object from pack.
for (var ii = 0; ii < objects.length; ++ii) {
pack.removeObject(objects[ii]);
}
}
};
(C) Æliens
20/2/2008
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.