Merge pull request #42 from makc/master

Async dae parsing by makc
This commit is contained in:
Andrey Kopysov
2012-11-29 21:39:12 -08:00
4 changed files with 151 additions and 6 deletions

View File

@@ -14,10 +14,14 @@ package alternativa.engine3d.loaders {
import alternativa.engine3d.core.Light3D;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.loaders.collada.DaeDocument;
import alternativa.engine3d.loaders.collada.DaeElement;
import alternativa.engine3d.loaders.collada.DaeGeometry;
import alternativa.engine3d.loaders.collada.DaeMaterial;
import alternativa.engine3d.loaders.collada.DaeNode;
import alternativa.engine3d.loaders.collada.DaeObject;
import alternativa.engine3d.resources.ExternalTextureResource;
import flash.utils.getTimer;
import flash.utils.setTimeout;
use namespace alternativa3d;
@@ -79,6 +83,43 @@ package alternativa.engine3d.loaders {
}
}
/**
* Method parses <code>xml</code> of collada asynchronously and fills arrays <code>objects</code>, <code>hierarchy</code>, <code>materials</code>, <code>animations</code>
* It is slower than <code>parse</code> but does not lock the thread.
* If you need to download textures, use class <code>TexturesLoader</code>.
* <p>Path to collada file should match with <code>URI</code> specification. E.g., <code>file:///C:/test.dae</code> or <code>/C:/test.dae</code> for the full paths and <code>test.dae</code>, <code>./test.dae</code> in case of relative.</p>
*
* @param onComplete Callback function accepting ParserCollada object as its argument.
* @param data <code>XML</code> data type of collada.
* @param baseURL Path to textures relative to swf-file (Or file name only in case of <code>trimPaths=true</code>).
* @param trimPaths Use file names only, without paths.
*
* @see alternativa.engine3d.loaders.TexturesLoader
* @see #objects
* @see #hierarchy
* @see #materials
*/
public function parseAsync(onComplete:Function, data:XML, baseURL:String = null, trimPaths:Boolean = false):void {
init();
var document:DaeDocument = new DaeDocument(data, 0);
if (document.scene != null) {
parseMaterials(document.materials, baseURL, trimPaths);
addNodesToQueue(document.scene.nodes, null, false);
// parse nodes internal sttructures ahead of time to avoid congestion
addElementsToQueue(document.controllers);
addElementsToQueue(document.channels);
addElementsToQueue(document.geometries);
for each (var geom:DaeGeometry in document.geometries) {
addElementsToQueue (geom.primitives);
}
addElementsToQueue(document.sources);
parseQueuedElements(onComplete);
}
}
/**
* Adds components of animated object to lists objects, parents, hierarchy, cameras, animations and to parent container.
*/
@@ -176,6 +217,90 @@ package alternativa.engine3d.loaders {
}
}
private var queue:Vector.<QueueElement> = new Vector.<QueueElement> ();
private function addNodesToQueue(nodes:Vector.<DaeNode>, parent:Object3D, skinsOnly:Boolean):void {
for (var j:int = 0; j < queue.length; j++) {
if (queue[j].element is DaeNode) {
break;
}
}
for (var i:int = nodes.length; i > 0; i--) {
var args:QueueElement = new QueueElement;
args.element = nodes[i - 1];
args.parent = parent;
args.skinsOnly = skinsOnly;
queue.splice(j, 0, args);
}
}
private function addElementsToQueue(elements:Object):void {
for each (var element:DaeElement in elements) {
var args:QueueElement = new QueueElement;
args.element = element;
queue.unshift(args);
}
}
private const ASYNC_LIMIT:int = 50;
private const ASYNC_TIMEOUT:int = 1;
private function parseQueuedElements(onComplete:Function):void {
var t:int = getTimer ();
do {
if (queue.length == 0) {
// make sure onComplete is called after parseAsync exits
setTimeout (onComplete, ASYNC_TIMEOUT, this);
return;
}
var args:QueueElement = queue.shift();
args.element.parse();
if (args.element is DaeNode) {
var node:DaeNode = args.element as DaeNode;
var parent:Object3D = args.parent;
var skinsOnly:Boolean = args.skinsOnly;
// Object to which child objects will be added.
var container:Object3D = null;
if (node.skins != null) {
// Main joint of skin
container = addObjects(node.skins, parent, node.layer);
} else {
if (!skinsOnly && !node.skinOrTopmostJoint) {
if (node.objects != null) {
container = addObjects(node.objects, parent, node.layer);
} else {
// Empty Object3D
container = new Object3D();
container.name = node.cloneString(node.name);
addObject(node.applyAnimation(node.applyTransformations(container)), parent, node.layer);
container.calculateBoundBox();
}
} else {
// Object or its parent is a skin or joint
// Create an object only if there are a child skins
if (hasSkinsInChildren(node)) {
container = new Object3D();
container.name = node.cloneString(node.name);
addObject(node.applyAnimation(node.applyTransformations(container)), parent, node.layer);
addNodesToQueue(node.nodes, container, skinsOnly || node.skinOrTopmostJoint);
container.calculateBoundBox();
}
}
}
// Parse children
if (container != null) {
addNodesToQueue(node.nodes, container, skinsOnly || node.skinOrTopmostJoint);
}
}
} while (getTimer () - t < ASYNC_LIMIT);
setTimeout (parseQueuedElements, ASYNC_TIMEOUT, onComplete);
}
private function trimPath(path:String):String {
var index:int = path.lastIndexOf("/");
return (index < 0) ? path : path.substr(index + 1);
@@ -387,3 +512,11 @@ package alternativa.engine3d.loaders {
}
}
}
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.loaders.collada.DaeElement;
class QueueElement {
public var element:DaeElement;
public var parent:Object3D;
public var skinsOnly:Boolean;
}

View File

@@ -22,7 +22,7 @@ package alternativa.engine3d.loaders.collada {
private var data:XML;
// Dictionaries to store matchings id-> DaeElement
internal var sources:Object;
public var sources:Object;
internal var arrays:Object;
internal var vertices:Object;
public var geometries:Object;
@@ -30,9 +30,11 @@ package alternativa.engine3d.loaders.collada {
internal var lights:Object;
internal var images:Object;
internal var effects:Object;
internal var controllers:Object;
public var controllers:Object;
internal var samplers:Object;
public var channels:Vector.<DaeChannel>;
public var materials:Object;
internal var logger:DaeLogger;
@@ -186,6 +188,10 @@ package alternativa.engine3d.loaders.collada {
var node:DaeNode = channel.node;
if (node != null) {
node.addChannel(channel);
if (channels == null) {
channels = new Vector.<DaeChannel>;
}
channels.push (channel);
}
}
}

View File

@@ -26,7 +26,7 @@ package alternativa.engine3d.loaders.collada {
use namespace alternativa3d;
internal var geometryVertices:Vector.<DaeVertex>;
internal var primitives:Vector.<DaePrimitive>;
public var primitives:Vector.<DaePrimitive>;
internal var geometry:Geometry;
private var vertices:DaeVertices;
@@ -46,12 +46,13 @@ package alternativa.engine3d.loaders.collada {
if (verticesXML != null) {
vertices = new DaeVertices(verticesXML, document);
document.vertices[vertices.id] = vertices;
// set primitives early for ParserCollada's parseAsync
parsePrimitives();
}
}
override protected function parseImplementation():Boolean {
if (vertices != null) {
parsePrimitives();
vertices.parse();
var numVertices:int = vertices.positions.numbers.length/vertices.positions.stride;

View File

@@ -28,9 +28,14 @@ package alternativa.engine3d.loaders.collada {
return data.@source[0];
}
// todo: profiler shows that offset getter is seriously abused in DaePrimitive's fillGeometry
private var _offset:int = -1;
public function get offset():int {
var attr:XML = data.@offset[0];
return (attr == null) ? 0 : parseInt(attr.toString(), 10);
if (_offset < 0) {
var attr:XML = data.@offset[0];
_offset = (attr == null) ? 0 : parseInt(attr.toString(), 10);
}
return _offset;
}
public function get setNum():int {