package org.papervision3d.objects.parsers
{
import flash.events.Event;
import flash.events.ProgressEvent;
import flash.utils.ByteArray;
import flash.utils.Dictionary;
import flash.utils.getTimer;
import org.ascollada.ASCollada;
import org.ascollada.core.*;
import org.ascollada.fx.*;
import org.ascollada.io.DaeReader;
import org.ascollada.namespaces.*;
import org.ascollada.types.*;
import org.papervision3d.Papervision3D;
import org.papervision3d.core.animation.*;
import org.papervision3d.core.animation.channel.*;
import org.papervision3d.core.controller.IObjectController;
import org.papervision3d.core.controller.SkinController;
import org.papervision3d.core.geom.*;
import org.papervision3d.core.geom.renderables.*;
import org.papervision3d.core.log.PaperLogger;
import org.papervision3d.core.material.AbstractLightShadeMaterial;
import org.papervision3d.core.math.*;
import org.papervision3d.core.proto.*;
import org.papervision3d.core.render.data.RenderSessionData;
import org.papervision3d.events.FileLoadEvent;
import org.papervision3d.materials.*;
import org.papervision3d.materials.shaders.ShadedMaterial;
import org.papervision3d.materials.special.*;
import org.papervision3d.materials.utils.*;
import org.papervision3d.objects.DisplayObject3D;
import org.papervision3d.objects.special.Skin3D;
/**
* @author Tim Knip
*/
public class DAE extends DisplayObject3D implements IAnimationDataProvider, IAnimatable
{
use namespace collada;
/** Default line color for splines. */
public static var DEFAULT_LINE_COLOR:uint = 0xffff00;
/** Default line width for splines */
public static var DEFAULT_LINE_WIDTH:Number = 0;
/** Alternative file-extension for TGA images. Default is "png". */
public static var DEFAULT_TGA_ALTERNATIVE:String = "png";
/** */
public var COLLADA:XML;
/** */
public var filename:String;
/** */
public var fileTitle:String;
/** */
public var baseUrl:String;
/** Path where the textures should be loaded from. */
public var texturePath:String;
/** */
public var parser:DaeReader;
/** */
public var document:DaeDocument;
/** */
public function get yUp():Boolean
{
if(this.document)
return (this.document.asset.yUp == ASCollada.DAE_Y_UP)
else
return false;
}
/**
* Constructor.
*
* @param autoPlay Whether to start the animation automatically.
* @param name Optional name for the DAE.
*/
public function DAE(autoPlay:Boolean=true, name:String=null)
{
super(name);
_autoPlay = autoPlay;
_rightHanded = Papervision3D.useRIGHTHANDED;
}
/**
* Plays the animation.
*
* @param clip Optional clip name.
*/
public function play(clip:String=null):void
{
_currentFrame = 0;
_currentTime = getTimer();
_isPlaying = (_isAnimated && _channels && _channels.length);
}
/**
* Stops the animation.
*/
public function stop():void
{
_isPlaying = false;
}
/**
* Gets the default FPS.
*/
public function get fps():uint
{
return 20;
}
/**
* Gets a animation channel by its name.
*
* @param name
*
* @return the found channel.
*/
public function getAnimationChannelByName(name:String):AbstractChannel3D
{
return null;
}
/**
* Gets all animation channels for a target.
*
NOTE: when target is null, all channels for this object are returned.
*
* @param target The target to get the channels for.
*
* @return Array of AnimationChannel3D.
*/
public function getAnimationChannels(target:DisplayObject3D=null):Array
{
var channels:Array = new Array();
if(target == null)
{
for each(var array:Array in _channelsByTarget)
channels = channels.concat(array);
}
else if(_channelsByTarget[target])
{
channels = channels.concat(_channelsByTarget[target]);
}
else
return null;
return channels;
}
/**
* Gets animation channels by clip name.
*
* @param name The clip name
*
* @return Array of AnimationChannel3D.
*/
public function getAnimationChannelsByClip(name:String):Array
{
return null;
}
/**
* Loads the COLLADA.
*
* @param asset The url, an XML object or a ByteArray specifying the COLLADA file.
* @param materials An optional materialsList.
*/
public function load(asset:*, materials:MaterialsList = null):void
{
this.materials = materials || new MaterialsList();
buildFileInfo(asset);
this.parser = new DaeReader();
this.parser.addEventListener(Event.COMPLETE, onParseComplete);
this.parser.addEventListener(ProgressEvent.PROGRESS, onParseProgress);
if(asset is XML)
{
this.COLLADA = asset as XML;
this.parser.loadDocument(asset);
}
else if(asset is ByteArray)
{
this.COLLADA = new XML(ByteArray(asset));
this.parser.loadDocument(asset);
}
else if(asset is String)
{
this.filename = String(asset);
this.parser.read(this.filename);
}
else
{
throw new Error("load : unknown asset type!");
}
}
/**
* Removes a child.
*
* @param child The child to remove
*
* @return The removed child
*/
override public function removeChild(child:DisplayObject3D):DisplayObject3D
{
var object:DisplayObject3D = getChildByName(child.name, true);
if(object)
{
var parent:DisplayObject3D = DisplayObject3D(object.parent);
if(parent)
{
var removed:DisplayObject3D = parent.removeChild(object);
if(removed)
return removed;
}
}
return null;
}
/**
* Replaces a material by its name.
*
* @param material
* @param name
* @return
*/
public function replaceMaterialByName(material:MaterialObject3D, name:String):void
{
if(!this.materials)
return;
var existingMaterial:MaterialObject3D = this.materials.getMaterialByName(name);
if(!existingMaterial)
return;
if(this.material === existingMaterial)
this.material = material;
existingMaterial = this.materials.removeMaterial(existingMaterial);
material = this.materials.addMaterial(material, name);
updateMaterials(this, existingMaterial, material);
}
/**
* Sets the material for a child DisplayObject3D.
*
* @param child A child DisplayObject3D of this DAE.
* @param material The new material for the child.
* @param existingMaterial Optional existing material to be replaced.
*/
public function setChildMaterial(child:DisplayObject3D, material:MaterialObject3D, existingMaterial:MaterialObject3D=null):void
{
if(!child)
return;
if(!existingMaterial || child.material === existingMaterial)
child.material = material;
if(child.geometry && child.geometry.faces)
{
for each( var triangle:Triangle3D in child.geometry.faces )
{
if(!existingMaterial || triangle.material === existingMaterial)
triangle.material = material;
}
}
}
/**
* Sets the material for a child DisplayObject3D by the child's name.
*
* @param childName The name of the DisplayObject3D.
* @param material The new material for the child.
*/
public function setChildMaterialByName(childName:String, material:MaterialObject3D):void
{
setChildMaterial(getChildByName(childName, true), material);
}
/**
* Project.
*
* @param parent
* @param renderSessionData
*
* @return Number
*/
public override function project(parent:DisplayObject3D, renderSessionData:RenderSessionData):Number
{
// update controllers
for each(var controller:IObjectController in _controllers)
controller.update();
// update animation
if(_isPlaying && _channels)
{
var secs:Number = _currentTime / 1000;
var duration:Number = _endTime - _startTime;
var elapsed:Number = (getTimer()/1000) - secs;
if(elapsed > duration)
{
_currentTime = getTimer();
secs = _currentTime / 1000;
elapsed = 0;
}
var time:Number = elapsed / duration;
for each(var channel:AbstractChannel3D in _channels)
{
channel.updateToTime(time);
}
}
return super.project(parent, renderSessionData);
}
/**
* Builds a animation channel for an object.
*
* @param matrixStackChannel the target object's channel
* @param target The target object
* @param channel The DaeChannel
*/
private function buildAnimationChannel(target:DisplayObject3D, channel:DaeChannel):MatrixChannel3D
{
var node:DaeNode = _objectToNode[target];
if(!node)
throw new Error("Couldn't find the targeted object!");
var matrixChannel:MatrixChannel3D = new MatrixChannel3D(target, channel.syntax.targetSID);
var transform:DaeTransform = node.findMatrixBySID(channel.syntax.targetSID);
if(!transform)
{
PaperLogger.warning("Couldn't find the targeted object's transform: " + channel.syntax.targetSID);
return null;
}
var matrix:Matrix3D;
var matrixProp:String;
var arrayMember:String;
var data:Array;
var val:Number;
var i:int;
if(channel.syntax.isArrayAccess)
{
arrayMember = channel.syntax.arrayMember.join("");
switch(arrayMember)
{
case "(0)(0)":
matrixProp = "n11";
break;
case "(1)(0)":
matrixProp = "n12";
break;
case "(2)(0)":
matrixProp = "n13";
break;
case "(3)(0)":
matrixProp = "n14";
break;
case "(0)(1)":
matrixProp = "n21";
break;
case "(1)(1)":
matrixProp = "n22";
break;
case "(2)(1)":
matrixProp = "n23";
break;
case "(3)(1)":
matrixProp = "n24";
break;
case "(0)(2)":
matrixProp = "n31";
break;
case "(1)(2)":
matrixProp = "n32";
break;
case "(2)(2)":
matrixProp = "n33";
break;
case "(3)(2)":
matrixProp = "n34";
break;
default:
throw new Error(arrayMember);
}
}
switch(transform.type)
{
case "matrix":
if(channel.syntax.isFullAccess)
{
for(i = 0; i < channel.input.length; i++)
{
data = channel.output[i];
matrix = new Matrix3D(data);
matrixChannel.addKeyFrame(new AnimationKeyFrame3D("keyframe_" + i, channel.input[i], [matrix]));
}
}
else if(channel.syntax.isArrayAccess)
{
matrix = Matrix3D.clone(target.transform);
for(i = 0; i < channel.input.length; i++)
{
matrix[matrixProp] = channel.output[i];
matrixChannel.addKeyFrame(new AnimationKeyFrame3D("keyframe_" + i, channel.input[i], [matrix]));
}
}
else
{
throw new Error("Don't know how to handle this channel: " + channel.syntax);
}
break;
case "rotate":
if(channel.syntax.isFullAccess)
{
for(i = 0; i < channel.input.length; i++)
{
data = channel.output[i];
matrix = Matrix3D.rotationMatrix(data[0], data[1], data[2], data[3] * (Math.PI/180));
matrixChannel.addKeyFrame(new AnimationKeyFrame3D("keyframe_" + i, channel.input[i], [matrix]));
}
}
else if(channel.syntax.isDotAccess)
{
switch(channel.syntax.member)
{
case "ANGLE":
for(i = 0; i < channel.input.length; i++)
{
var angle:Number = channel.output[i] * (Math.PI/180);
matrix = Matrix3D.rotationMatrix(transform.values[0], transform.values[1], transform.values[2], angle);
matrixChannel.addKeyFrame(new AnimationKeyFrame3D("keyframe_" + i, channel.input[i], [matrix]));
}
break;
default:
throw new Error("Don't know how to handle this channel: " + channel.syntax);
}
}
else
{
throw new Error("Don't know how to handle this channel: " + channel.syntax);
}
break;
case "scale":
if(channel.syntax.isFullAccess)
{
for(i = 0; i < channel.input.length; i++)
{
data = channel.output[i];
matrix = Matrix3D.scaleMatrix(data[0], data[1], data[2]);
matrixChannel.addKeyFrame(new AnimationKeyFrame3D("keyframe_" + i, channel.input[i], [matrix]));
}
}
else if(channel.syntax.isDotAccess)
{
for(i = 0; i < channel.input.length; i++)
{
val = channel.output[i];
switch(channel.syntax.member)
{
case "X":
matrix = Matrix3D.scaleMatrix(val, 0, 0);
matrixChannel.addKeyFrame(new AnimationKeyFrame3D("keyframe_" + i, channel.input[i], [matrix]));
break;
case "Y":
matrix = Matrix3D.scaleMatrix(0, val, 0);
matrixChannel.addKeyFrame(new AnimationKeyFrame3D("keyframe_" + i, channel.input[i], [matrix]));
break;
case "Z":
matrix = Matrix3D.scaleMatrix(0, 0, val);
matrixChannel.addKeyFrame(new AnimationKeyFrame3D("keyframe_" + i, channel.input[i], [matrix]));
break;
default:
break;
}
}
}
else
{
throw new Error("Don't know how to handle this channel: " + channel.syntax);
}
break;
case "translate":
if(channel.syntax.isFullAccess)
{
for(i = 0; i < channel.input.length; i++)
{
data = channel.output[i];
matrix = Matrix3D.translationMatrix(data[0], data[1], data[2]);
matrixChannel.addKeyFrame(new AnimationKeyFrame3D("keyframe_" + i, channel.input[i], [matrix]));
}
}
else if(channel.syntax.isDotAccess)
{
for(i = 0; i < channel.input.length; i++)
{
val = channel.output[i];
switch(channel.syntax.member)
{
case "X":
matrix = Matrix3D.translationMatrix(val, 0, 0);
matrixChannel.addKeyFrame(new AnimationKeyFrame3D("keyframe_" + i, channel.input[i], [matrix]));
break;
case "Y":
matrix = Matrix3D.translationMatrix(0, val, 0);
matrixChannel.addKeyFrame(new AnimationKeyFrame3D("keyframe_" + i, channel.input[i], [matrix]));
break;
case "Z":
matrix = Matrix3D.translationMatrix(0, 0, val);
matrixChannel.addKeyFrame(new AnimationKeyFrame3D("keyframe_" + i, channel.input[i], [matrix]));
break;
default:
break;
}
}
}
else
{
throw new Error("Don't know how to handle this channel: " + channel.syntax);
}
break;
default:
throw new Error("Unknown transform type!");
}
return matrixChannel;
}
/**
* Build all animation channels.
*/
private function buildAnimationChannels():void
{
var target:DisplayObject3D;
var channel:DaeChannel;
var channelsByObject:Dictionary = new Dictionary(true);
var i:int;
_channelsByTarget = new Dictionary(true);
for each(var animation:DaeAnimation in this.document.animations)
{
for(i = 0; i < animation.channels.length; i++)
{
channel = animation.channels[i];
target = _colladaIDToObject[channel.syntax.targetID];
if(!target)
throw new Error("damn");
if(!channelsByObject[target])
channelsByObject[target] = new Array();
channelsByObject[target].push(channel);
}
}
for(var object:* in channelsByObject)
{
target = object as DisplayObject3D;
var channels:Array = channelsByObject[object];
var node:DaeNode = _objectToNode[target];
if(!node)
throw new Error("Couldn't find the targeted object with name '" + node.name + "'");
node.channels = channels;
if(!channels.length)
continue;
channel = channels[0];
var transform:DaeTransform = node.findMatrixBySID(channel.syntax.targetSID);
if(!transform)
{
PaperLogger.warning("Could not find a transform with SID=" + channel.syntax.targetSID);
continue;
}
// the object has a single channel
if(channels.length == 1 && transform.type == ASCollada.DAE_MATRIX_ELEMENT)
{
_channelsByTarget[target] = [buildAnimationChannel(target, channel)];
continue;
}
// the object has multiple channels, lets bake 'm into a single channel
var allTimes:Array = new Array();
var times:Array = new Array();
var lastTime:Number;
// fetch all times for all channels
for each(channel in channels)
allTimes = allTimes.concat(channel.input);
allTimes.sort(Array.NUMERIC);
// make array with unique times
for(i = 0; i < allTimes.length; i++)
{
var t:Number = allTimes[i];
if(i == 0)
times.push(t);
else if(t - lastTime > 0.01)
times.push(t);
lastTime = t;
}
// build the MatrixChannel3D's for this object
var mcs:Object = new Object();
for each(channel in channels)
{
var animationChannel:MatrixChannel3D = buildAnimationChannel(target, channel);
if(animationChannel)
mcs[ channel.syntax.targetSID ] = buildAnimationChannel(target, channel);
}
var bakedChannel:MatrixChannel3D = new MatrixChannel3D(target);
// build a baked channel
for(i = 0; i < times.length; i++)
{
var keyframeTime:Number = times[i];
var bakedMatrix:Matrix3D = Matrix3D.IDENTITY;
// loop over the DaeNode's transform-stack
for(var j:int = 0; j < node.transforms.length; j++)
{
transform = node.transforms[j];
var matrixChannel:MatrixChannel3D = mcs[ transform.sid ];
if(matrixChannel)
{
// this transform is animated, so lets determine the matrix for the current keyframeTime
var time:Number;
if(keyframeTime < matrixChannel.startTime)
time = 0;
else if(keyframeTime > matrixChannel.endTime)
time = 1;
else
time = keyframeTime / (matrixChannel.endTime - matrixChannel.startTime);
// update the channel by time, so the matrix for the current keyframe is setup
matrixChannel.updateToTime(time);
// bake the matrix
bakedMatrix = Matrix3D.multiply(bakedMatrix, target.transform);
}
else
{
// this transform isn't animated, simply bake the transform into the matrix
bakedMatrix = Matrix3D.multiply(bakedMatrix, buildMatrixFromTransform(transform));
}
}
// now we can add the baked matrix as a new keyframe
bakedChannel.addKeyFrame(new AnimationKeyFrame3D("frame_" + i, keyframeTime, [bakedMatrix]));
}
_channelsByTarget[target] = [bakedChannel];
}
}
/**
* Build a color from RGB values.
*
* @param rgb
*
* @return
*/
private function buildColor( rgb:Array ):uint
{
var r:uint = rgb[0] * 0xff;
var g:uint = rgb[1] * 0xff;
var b:uint = rgb[2] * 0xff;
return (r<<16|g<<8|b);
}
/**
* Creates the faces for a COLLADA primitive. @see org.ascollada.core.DaePrimitive
*
* @param primitive
* @param geometry
* @param voffset
*
* @return The created faces.
*/
private function buildFaces(primitive:DaePrimitive, geometry:GeometryObject3D, voffset:uint):void
{
var faces:Array = new Array();
var material:MaterialObject3D = this.materials.getMaterialByName(primitive.material);
material = material || MaterialObject3D.DEFAULT;
// retreive correct texcoord-set for the material.
var obj:DaeBindVertexInput = _textureSets[primitive.material] is DaeBindVertexInput ? _textureSets[primitive.material] : null;
var setID:int = (obj is DaeBindVertexInput) ? obj.input_set : 1;
var texCoordSet:Array = primitive.getTexCoords(setID);
var texcoords:Array = new Array();
var i:int, j:int = 0, k:int;
// texture coords
for( i = 0; i < texCoordSet.length; i++ )
texcoords.push(new NumberUV(texCoordSet[i][0], texCoordSet[i][1]));
var hasUV:Boolean = (texcoords.length == primitive.vertices.length);
var idx:Array = new Array();
var v:Array = new Array();
var uv:Array = new Array();
switch( primitive.type )
{
// Each line described by the mesh has two vertices. The first line is formed
// from first and second vertices. The second line is formed from the third and fourth
// vertices and so on.
case ASCollada.DAE_LINES_ELEMENT:
for( i = 0; i < primitive.vertices.length; i += 2 )
{
v[0] = geometry.vertices[ primitive.vertices[i] ];
v[1] = geometry.vertices[ primitive.vertices[i+1] ];
uv[0] = hasUV ? texcoords[ i ] : new NumberUV();
uv[1] = hasUV ? texcoords[ i+1 ] : new NumberUV();
//geometry.faces.push( new Triangle3D(instance, [v[0], v[1], v[1]], material, [uv[0], uv[1], uv[1]]) );
}
break;
// simple triangles
case ASCollada.DAE_TRIANGLES_ELEMENT:
for(i = 0, j = 0; i < primitive.vertices.length; i += 3, j++)
{
idx[0] = voffset + primitive.vertices[i];
idx[1] = voffset + primitive.vertices[i+1];
idx[2] = voffset + primitive.vertices[i+2];
v[0] = geometry.vertices[ idx[0] ];
v[1] = geometry.vertices[ idx[1] ];
v[2] = geometry.vertices[ idx[2] ];
uv[0] = hasUV ? texcoords[ i+0 ] : new NumberUV();
uv[1] = hasUV ? texcoords[ i+1 ] : new NumberUV();
uv[2] = hasUV ? texcoords[ i+2 ] : new NumberUV();
geometry.faces.push(new Triangle3D(null, [v[0], v[1], v[2]], material, [uv[0], uv[1], uv[2]]));
}
break;
// polygon with *no* holes
case ASCollada.DAE_POLYLIST_ELEMENT:
for( i = 0, k = 0; i < primitive.vcount.length; i++ )
{
var poly:Array = new Array();
var uvs:Array = new Array();
for( j = 0; j < primitive.vcount[i]; j++ )
{
uvs.push( (hasUV ? texcoords[ k ] : new NumberUV()) );
poly.push( geometry.vertices[primitive.vertices[k++]] );
}
if( !geometry || !geometry.faces || !geometry.vertices )
throw new Error( "no geometry" );
v[0] = poly[0];
uv[0] = uvs[0];
for( j = 1; j < poly.length - 1; j++ )
{
v[1] = poly[j];
v[2] = poly[j+1];
uv[1] = uvs[j];
uv[2] = uvs[j+1];
geometry.faces.push(new Triangle3D(null, [v[0], v[1], v[2]], material, [uv[0], uv[1], uv[2]]));
}
}
break;
// polygons *with* holes (but holes not yet processed...)
case ASCollada.DAE_POLYGONS_ELEMENT:
for(i = 0, k = 0; i < primitive.polygons.length; i++)
{
var p:Array = primitive.polygons[i];
var np:Array = new Array();
var nuv:Array = new Array();
for(j = 0; j < p.length; j++)
{
nuv.push( (hasUV ? texcoords[ k ] : new NumberUV()) );
np.push( geometry.vertices[primitive.vertices[k++]] );
}
v[0] = np[0];
uv[0] = nuv[0];
for(j = 1; j < np.length - 1; j++)
{
v[1] = np[j];
v[2] = np[j+1];
uv[1] = nuv[j];
uv[2] = nuv[j+1];
geometry.faces.push(new Triangle3D(null, [v[0], v[1], v[2]], material, [uv[0], uv[1], uv[2]]));
}
}
break;
default:
throw new Error("Don't know how to create face for a DaePrimitive with type = " + primitive.type);
}
}
/**
*
* @param asset
* @return
*/
private function buildFileInfo( asset:* ):void
{
this.filename = asset is String ? String(asset) : "./meshes/rawdata_dae";
// make sure we've got forward slashes!
this.filename = this.filename.split("\\").join("/");
if( this.filename.indexOf("/") != -1 )
{
// dae is located in a sub-directory of the swf.
var parts:Array = this.filename.split("/");
this.fileTitle = String( parts.pop() );
this.baseUrl = parts.join("/");
}
else
{
// dae is located in root directory of swf.
this.fileTitle = this.filename;
this.baseUrl = "";
}
}
/**
* Builds all COLLADA geometries.
*/
private function buildGeometries():void
{
var i:int, j:int, k:int;
_geometries = new Object();
for each(var geometry:DaeGeometry in this.document.geometries)
{
if(geometry.mesh)
{
var g:GeometryObject3D = new GeometryObject3D();
g.vertices = buildVertices(geometry.mesh);
g.faces = new Array();
if(!g.vertices.length)
continue;
for(i = 0; i < geometry.mesh.primitives.length; i++)
{
buildFaces(geometry.mesh.primitives[i], g, 0);
}
_geometries[geometry.id] = g;
}
else if(geometry.spline && geometry.splines)
{
var lines:Lines3D = new Lines3D(new LineMaterial(DEFAULT_LINE_COLOR), geometry.id);
for(i = 0; i < geometry.splines.length; i++)
{
var spline:DaeSpline = geometry.splines[i];
for(j = 0; j < spline.vertices.length; j++)
{
k = (j+1) % spline.vertices.length;
var v0:Vertex3D = new Vertex3D(spline.vertices[j][0], spline.vertices[j][1], spline.vertices[j][2]);
var v1:Vertex3D = new Vertex3D(spline.vertices[k][0], spline.vertices[k][1], spline.vertices[k][2]);
var line:Line3D = new Line3D(lines, lines.material as LineMaterial, DEFAULT_LINE_WIDTH, v0, v1);
lines.addLine(line);
}
}
_geometries[geometry.id] = lines;
}
}
}
/**
*
* @return
*/
private function buildImagePath( meshFolderPath:String, imgPath:String ):String
{
if (texturePath != null)
imgPath = texturePath + imgPath.slice( imgPath.lastIndexOf("/") + 1 );
var baseParts:Array = meshFolderPath.split("/");
var imgParts:Array = imgPath.split("/");
while( baseParts[0] == "." )
baseParts.shift();
while( imgParts[0] == "." )
imgParts.shift();
while( imgParts[0] == ".." )
{
imgParts.shift();
baseParts.pop();
}
var imgUrl:String = baseParts.length > 1 ? baseParts.join("/") : (baseParts.length?baseParts[0]:"");
imgUrl = imgUrl != "" ? imgUrl + "/" + imgParts.join("/") : imgParts.join("/");
return imgUrl;
}
/**
* Builds the materials.
*/
private function buildMaterials():void
{
_queuedMaterials = new Array();
for( var materialId:String in this.document.materials )
{
var material:MaterialObject3D = null;
var daeMaterial:DaeMaterial = this.document.materials[ materialId ];
var symbol:String = this.document.materialTargetToSymbol[ daeMaterial.id ];
// material already exists in our materialsList, no need to process
if(this.materials.getMaterialByName(symbol))
continue;
var effect:DaeEffect = document.effects[ daeMaterial.effect ];
var lambert:DaeLambert = effect.color as DaeLambert;
// save the texture-set if necessary
if(lambert && lambert.diffuse.texture)
{
_textureSets[daeMaterial.id] = lambert.diffuse.texture.texcoord;
}
// if the material has a texture, qeueu the bitmap
if(effect && effect.texture_url)
{
var image:DaeImage = document.images[effect.texture_url];
if(image)
{
var imageUrl:String = buildImagePath(this.baseUrl, image.init_from);
material = new BitmapFileMaterial();
material.doubleSided = effect.double_sided;
_queuedMaterials.push({symbol:symbol, url:imageUrl, material:material});
continue;
}
}
if(lambert && lambert.diffuse.color)
{
if(effect.wireframe)
material = new WireframeMaterial(buildColor(lambert.diffuse.color));
else
material = new ColorMaterial(buildColor(lambert.diffuse.color));
}
else
material = MaterialObject3D.DEFAULT;
material.doubleSided = effect.double_sided;
this.materials.addMaterial(material, symbol);
}
}
/**
* Builds a Matrix3D from a node's transform array. @see org.ascollada.core.DaeNode#transforms
*
* @param node
*
* @return
*/
private function buildMatrix(node:DaeNode):Matrix3D
{
var stack:Array = buildMatrixStack(node);
var matrix:Matrix3D = Matrix3D.IDENTITY;
for( var i:int = 0; i < stack.length; i++ )
matrix.calculateMultiply4x4(matrix, stack[i]);
return matrix;
}
/**
*
* @param node
* @return
*/
private function buildMatrixFromTransform(transform:DaeTransform):Matrix3D
{
var matrix:Matrix3D;
var toRadians:Number = Math.PI/180;
var v:Array = transform.values;
switch(transform.type)
{
case ASCollada.DAE_ROTATE_ELEMENT:
matrix = Matrix3D.rotationMatrix(v[0], v[1], v[2], v[3] * toRadians);
break;
case ASCollada.DAE_SCALE_ELEMENT:
matrix = Matrix3D.scaleMatrix(v[0], v[1], v[2]);
break;
case ASCollada.DAE_TRANSLATE_ELEMENT:
matrix = Matrix3D.translationMatrix(v[0], v[1], v[2]);
break;
case ASCollada.DAE_MATRIX_ELEMENT:
matrix = new Matrix3D(v);
break;
default:
throw new Error("Unknown transform type: " + transform.type);
}
return matrix;
}
/**
*
* @param node
* @return
*/
private function buildMatrixStack(node:DaeNode):Array
{
var stack:Array = new Array();
for( var i:int = 0; i < node.transforms.length; i++ )
stack.push(buildMatrixFromTransform(node.transforms[i]));
return stack;
}
/**
* Builds a DisplayObject3D from a node. @see org.ascollada.core.DaeNode
*
* @param node
*
* @return The created DisplayObject3D. @see org.papervision3d.objects.DisplayObject3D
*/
private function buildNode(node:DaeNode, parent:DisplayObject3D):void
{
var instance:DisplayObject3D;
var material:MaterialObject3D;
var i:int;
if(node.controllers.length)
{
// controllers, can be of type 'skin' or 'morph'
for(i = 0; i < node.controllers.length; i++)
{
var instanceController:DaeInstanceController = node.controllers[i];
var colladaController:DaeController = document.controllers[instanceController.url];
if(colladaController.skin)
{
instance = new Skin3D(null, [], [], node.name);
buildSkin(instance as Skin3D, colladaController.skin, instanceController.skeletons, node);
}
else if(colladaController.morph)
{
throw new Error("morph!");
}
else
throw new Error("A COLLADA controller should be of type or !");
// dunnu yet how to handle multiple controllers.
break;
}
}
else if(node.geometries.length)
{
// got geometry, so create a TriangleMesh3D
instance = new TriangleMesh3D(null, [], [], node.name);
// add all COLLADA geometries to the TriangleMesh3D
for each(var geom:DaeInstanceGeometry in node.geometries)
{
var geometry:GeometryObject3D = _geometries[ geom.url ];
if(!geometry)
continue;
if(_geometries[ geom.url ] is Lines3D)
{
instance.addChild(_geometries[ geom.url ]);
continue;
}
var materialInstances:Array = new Array();
if(geom.materials)
{
for each(var instanceMaterial:DaeInstanceMaterial in geom.materials)
{
material = this.materials.getMaterialByName(instanceMaterial.symbol);
if(material)
{
// register shaded materials with its object
if(material is AbstractLightShadeMaterial || material is ShadedMaterial)
material.registerObject(instance);
materialInstances.push(material);
}
}
}
mergeGeometries(instance.geometry, geometry.clone(instance), materialInstances);
}
}
else
{
// no geometry, simply create a Joint3D
instance = new DisplayObject3D(node.name);
}
// recurse node instances
for(i = 0; i < node.instance_nodes.length; i++)
{
var dae_node:DaeNode = document.getDaeNodeById(node.instance_nodes[i].url);
buildNode(dae_node, instance);
}
// setup the initial transform
instance.copyTransform(buildMatrix(node));
// recurse node children
for(i = 0; i < node.nodes.length; i++)
buildNode(node.nodes[i], instance);
// save COLLADA id, sid
_colladaID[instance] = node.id;
_colladaSID[instance] = node.sid;
_colladaIDToObject[node.id] = instance;
_colladaSIDToObject[node.sid] = instance;
_objectToNode[instance] = node;
instance.flipLightDirection = true;
parent.addChild(instance);
}
/**
* Builds the scene.
*/
private function buildScene():void
{
if(this.parser.hasEventListener(Event.COMPLETE))
this.parser.removeEventListener(Event.COMPLETE, onParseComplete);
if(this.parser.hasEventListener(ProgressEvent.PROGRESS))
this.parser.removeEventListener(ProgressEvent.PROGRESS, onParseProgress);
_controllers = new Array();
buildGeometries();
_rootNode = new DisplayObject3D("COLLADA_Scene");
for(var i:int = 0; i < this.document.vscene.nodes.length; i++)
{
buildNode(this.document.vscene.nodes[i], _rootNode);
}
// link the skins
linkSkins();
this.addChild(_rootNode);
if(this.yUp)
{
}
else
{
_rootNode.rotationX = -90;
_rootNode.rotationY = 180;
}
if(!_rightHanded)
_rootNode.scaleX = -_rootNode.scaleX;
// animation stuff
_currentFrame = 0;
_totalFrames = 0;
_startTime = _endTime = 0;
_channels = new Array();
_isAnimated = false;
_isPlaying = false;
// may have animations to be parsed.
if(document.numQueuedAnimations)
{
_isAnimated = true;
this.parser.addEventListener(Event.COMPLETE, onParseAnimationsComplete);
this.parser.addEventListener(ProgressEvent.PROGRESS, onParseAnimationsProgress);
this.parser.readAnimations();
}
dispatchEvent(new FileLoadEvent(FileLoadEvent.LOAD_COMPLETE, this.filename));
}
/**
* Builds a skin.
*
* @param instance
* @param colladaSkin
* @param skeletons
*/
private function buildSkin(instance:Skin3D, colladaSkin:DaeSkin, skeletons:Array, node:DaeNode):void
{
var skin:GeometryObject3D = _geometries[ colladaSkin.source ];
if(!skin)
{
// geometry can be inside a morph controller
var morphController:DaeController = this.document.controllers[colladaSkin.source];
if(morphController && morphController.morph)
{
var morph:DaeMorph = morphController.morph;
// fetch geometry
skin = _geometries[morph.source];
// fetch target geometries
for(var j:int = 0; j < morph.targets.length; j++)
{
var targetGeometry:GeometryObject3D = _geometries[morph.targets[j]];
}
}
if(!skin)
throw new Error("no geometry for source: " + colladaSkin.source);
}
mergeGeometries(instance.geometry, skin.clone(instance));
_skins[ instance ] = colladaSkin;
}
/**
* Builds vertices from a COLLADA mesh.
*
* @param mesh The COLLADA mesh. @see org.ascollada.core.DaeMesh
*
* @return Array of Vertex3D
*/
private function buildVertices(mesh:DaeMesh):Array
{
var vertices:Array = new Array();
for( var i:int = 0; i < mesh.vertices.length; i++ )
vertices.push(new Vertex3D(mesh.vertices[i][0], mesh.vertices[i][1], mesh.vertices[i][2]));
return vertices;
}
/**
* Recursively finds a child by its COLLADA is.
*
* @param id
* @param parent
*
* @return The found child.
*/
private function findChildByID(id:String, parent:DisplayObject3D = null):DisplayObject3D
{
parent = parent || this;
if(_colladaID[parent] == id)
return parent;
for each(var child:DisplayObject3D in parent.children)
{
var obj:DisplayObject3D = findChildByID(id, child);
if(obj)
return obj;
}
return null
}
/**
* Recursively finds a child by its SID.
*
* @param name
* @param parent
*
* @return The found child.
*/
private function findChildBySID(sid:String, parent:DisplayObject3D = null):DisplayObject3D
{
parent = parent || this;
if(_colladaSID[parent] == sid)
return parent;
for each(var child:DisplayObject3D in parent.children)
{
var obj:DisplayObject3D = findChildBySID(sid, child);
if(obj)
return obj;
}
return null
}
/**
* Tests whether a node has a baked transform
*
* @param node
*/
private function isBakedMatrix(node:DaeNode):Boolean
{
if(!node.transforms.length || node.transforms.length > 1)
return false;
var transform:DaeTransform = node.transforms[0];
return (transform.type == ASCollada.DAE_MATERIAL_ELEMENT);
}
/**
* Setup the skin controllers.
*/
private function linkSkin(instance:DisplayObject3D, skin:DaeSkin):void
{
var i:int;
var found:Object = new Object();
var controller:SkinController = new SkinController(instance as Skin3D);
controller.bindShapeMatrix = new Matrix3D(skin.bind_shape_matrix);
controller.joints = new Array();
controller.vertexWeights = new Array();
controller.invBindMatrices = new Array();
for(i = 0; i < skin.joints.length; i++)
{
var jointId:String = skin.joints[i];
if(found[jointId])
continue;
var joint:DisplayObject3D = _colladaIDToObject[jointId];
if(!joint)
joint = _colladaSIDToObject[jointId];
if(!joint)
throw new Error("Couldn't find the joint id = " + jointId);
var vertexWeights:Array = skin.findJointVertexWeightsByIDOrSID(jointId);
if(!vertexWeights)
throw new Error("Could not find vertex weights for joint with id = " + jointId);
var bindMatrix:Array = skin.findJointBindMatrix2(jointId);
if(!bindMatrix || bindMatrix.length != 16)
throw new Error("Could not find inverse bind matrix for joint with id = " + jointId);
controller.joints.push(joint);
controller.invBindMatrices.push(new Matrix3D(bindMatrix));
controller.vertexWeights.push(vertexWeights);
found[jointId] = true;
}
_controllers.push(controller);
}
/**
* Setup the skin controllers.
*/
private function linkSkins():void
{
_numSkins = 0;
for(var object:* in _skins)
{
var instance:TriangleMesh3D = object as TriangleMesh3D;
if(!instance)
throw new Error("Not a Skin3D?");
linkSkin(instance, _skins[object]);
_numSkins++;
}
}
/**
* Loads the next material.
*
* @param event
*/
private function loadNextMaterial(event:FileLoadEvent=null):void
{
if(event)
{
var previous:BitmapFileMaterial = event.target as BitmapFileMaterial;
if(_rightHanded && previous)
BitmapMaterialTools.mirrorBitmapX(previous.bitmap);
}
if(_queuedMaterials.length)
{
var data:Object = _queuedMaterials.shift();
var url:String = data.url;
var symbol:String = data.symbol;
url = url.replace(/\.tga/i, "."+DEFAULT_TGA_ALTERNATIVE);
var material:BitmapFileMaterial = data.material;
material.addEventListener(FileLoadEvent.LOAD_COMPLETE, loadNextMaterial);
material.addEventListener(FileLoadEvent.LOAD_ERROR, onMaterialError);
material.name = symbol;
material.texture = url + "?nc=" + Math.random();
this.materials.addMaterial(material, symbol);
}
else
{
dispatchEvent(new FileLoadEvent(FileLoadEvent.COLLADA_MATERIALS_DONE, this.filename));
buildScene();
}
}
/**
* Merge geometries.
*
* @param target The target geometry to merge to.
* @param source The source geometry
* @param material Optional material for triangles, only used when a triangle has no material.
*/
private function mergeGeometries(target:GeometryObject3D, source:GeometryObject3D, materialInstances:Array=null):void
{
if(materialInstances && materialInstances.length)
{
var firstMaterial:MaterialObject3D = materialInstances[0];
for each(var triangle:Triangle3D in source.faces)
{
var correctMaterial:Boolean = false;
for each(var material:MaterialObject3D in materialInstances)
{
if(material === triangle.material)
{
correctMaterial = true;
break;
}
}
triangle.material = correctMaterial ? triangle.material : firstMaterial;
}
}
target.vertices = target.vertices.concat(source.vertices);
target.faces = target.faces.concat(source.faces);
target.ready = true;
}
/**
* Called when a BitmapMaterial failed to load.
*
* @param event
*/
private function onMaterialError(event:Event):void
{
loadNextMaterial();
}
/**
* Called when the parser completed parsing animations.
*
* @param event
*/
private function onParseAnimationsComplete(event:Event):void
{
buildAnimationChannels();
_channels = this.getAnimationChannels() || new Array();
_currentFrame = _totalFrames = 0;
_startTime = _endTime = 0;
for each(var channel:AbstractChannel3D in _channels)
{
_totalFrames = Math.max(_totalFrames, channel.keyFrames.length);
_startTime = Math.min(_startTime, channel.startTime);
_endTime = Math.max(_endTime, channel.endTime);
}
PaperLogger.info( "animations COMPLETE (#channels: " + _channels.length + " #frames: " + _totalFrames + ", startTime: " + _startTime + " endTime: " + _endTime+ ")");
dispatchEvent(new FileLoadEvent(FileLoadEvent.ANIMATIONS_COMPLETE, this.filename));
if(_autoPlay)
play();
}
/**
* Called on parse animations progress.
*
* @param event
*/
private function onParseAnimationsProgress(event:ProgressEvent):void
{
PaperLogger.info( "animations #" + event.bytesLoaded + " of " + event.bytesTotal);
}
/**
* Called when the DaeReader completed parsing.
*
* @param event
*/
private function onParseComplete(event:Event):void
{
var reader:DaeReader = event.target as DaeReader;
this.document = reader.document;
_textureSets = new Object();
_colladaID = new Dictionary(true);
_colladaSID = new Dictionary(true);
_colladaIDToObject = new Object();
_colladaSIDToObject = new Object();
_objectToNode = new Object();
_skins = new Dictionary(true);
buildMaterials();
loadNextMaterial();
}
/**
* Called on parsing progress.
*
* @param event
*/
private function onParseProgress(event:ProgressEvent):void
{
}
/**
*
* @param do3d
* @param existingMaterial
* @param newMaterial
*/
private function updateMaterials(do3d:DisplayObject3D, existingMaterial:MaterialObject3D, newMaterial:MaterialObject3D):void
{
existingMaterial.unregisterObject(do3d);
// register shaded materials with its object
if(newMaterial is AbstractLightShadeMaterial || newMaterial is ShadedMaterial)
{
newMaterial.registerObject(do3d);
}
if( do3d.material === existingMaterial )
do3d.material = newMaterial;
if( do3d.geometry && do3d.geometry.faces && do3d.geometry.faces.length )
{
for each( var triangle:Triangle3D in do3d.geometry.faces )
{
if( triangle.material === existingMaterial )
triangle.material = newMaterial;
}
}
for each(var child:DisplayObject3D in do3d.children)
updateMaterials(child, existingMaterial, newMaterial);
}
/** */
private var _colladaID:Dictionary;
/** */
private var _colladaSID:Dictionary;
/** */
private var _colladaIDToObject:Object;
/** */
private var _colladaSIDToObject:Object;
/** */
private var _objectToNode:Object;
/** */
private var _channelsByTarget:Dictionary;
/** */
private var _geometries:Object;
/** */
private var _queuedMaterials:Array;
/** */
private var _textureSets:Object;
/** */
private var _channels:Array;
/** */
private var _skins:Dictionary;
/** */
private var _numSkins:uint;
/** */
private var _rootNode:DisplayObject3D;
/** */
private var _currentFrame:int = 0;
/** */
private var _currentTime:int;
/** */
private var _totalFrames:int = 0;
/** */
private var _startTime:Number;
/** */
private var _endTime:Number;
/** */
private var _isAnimated:Boolean = false;
/** */
private var _isPlaying:Boolean = false;
/** */
private var _autoPlay:Boolean;
/** */
private var _rightHanded:Boolean;
/** */
private var _controllers:Array;
}
}