topical media & game development

talk show tell print

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.