package org.papervision3d.objects.parsers { import org.papervision3d.core.animation.channel.Channel3D; import flash.events.Event; import flash.events.IOErrorEvent; import flash.events.ProgressEvent; import flash.system.Capabilities; import flash.utils.ByteArray; import flash.utils.Dictionary; 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.IAnimatable; import org.papervision3d.core.animation.IAnimationProvider; import org.papervision3d.core.animation.channel.controller.MorphWeightChannel3D; import org.papervision3d.core.animation.channel.geometry.VertexChannel3D; import org.papervision3d.core.animation.channel.transform.*; import org.papervision3d.core.animation.clip.AnimationClip3D; import org.papervision3d.core.animation.key.LinearCurveKey3D; import org.papervision3d.core.animation.curve.Curve3D; import org.papervision3d.core.controller.*; 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; /** * The DAE class represents a parsed COLLADA 1.4.1 file. * *

Typical use case:

*
	 * var dae :DAE = new DAE();
	 * 
	 * dae.addEventListener(FileLoadEvent.LOAD_COMPLETE, myOnLoadCompleteHandler);
	 * 
	 * dae.load( "path/to/collada" );
	 * 
* *

Its possible to pass you own materials via a MaterialsList:

*
	 * var materials :MaterialsList = new MaterialsList();
	 * 
	 * materials.addMaterial( new ColorMaterial(), "MyMaterial" );
	 * 
	 * var dae :DAE = new DAE();
	 * 
	 * dae.addEventListener(FileLoadEvent.LOAD_COMPLETE, myOnLoadCompleteHandler);
	 * 
	 * dae.load( "path/to/collada", materials );
	 * 
*

Note that in above case you need the material names as specified in your 3D modelling application. * The material names can also be found by looking at the COLLADA file: find the xml elements * <instance_material symbol="MyMaterialName" target="SomeTarget" />. The material names are specified * by the symbol attribute of this element.

* *

A COLLADA file can contain animations. Animations take a long time to parse, hence * animations are parsed asynchroniously. Listen for FileLoadEvent.ANIMATIONS_COMPLETE and * FileLoadEvent.ANIMATIONS_PROGRESS:

*
	 * var dae :DAE = new DAE();
	 * 
	 * dae.addEventListener(FileLoadEvent.LOAD_COMPLETE, myOnLoadCompleteHandler);
	 * dae.addEventListener(FileLoadEvent.ANIMATIONS_COMPLETE, myOnAnimationsCompleteHandler);
	 * dae.addEventListener(FileLoadEvent.ANIMATIONS_PROGRESS, myOnAnimationsProgressHandler);
	 * 
	 * dae.load( "path/to/collada" );
	 * 
* * @author Tim Knip */ public class DAE extends DisplayObject3D implements IAnimatable, IAnimationProvider, IControllerProvider { use namespace collada; public static const ROOTNODE_NAME:String = "COLLADA_Scene"; /** 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; /** change this to a value > 0 if you're DAE is picking the wrong coordinates */ public var forceCoordSet : int = -1; /** The loaded XML. */ public var COLLADA:XML; /** The filename - if applicable. */ public var filename:String; /** The filetitle - if applicable. */ public var fileTitle:String; /** Base url. */ public var baseUrl:String; /** The COLLADA parser. */ public var parser:DaeReader; /** The DaeDocument. @see org.ascollada.core.DaeDocument */ public var document:DaeDocument; /** */ protected var _animation : AnimationController; /** */ protected var _colladaID:Dictionary; /** */ protected var _colladaSID:Dictionary; /** */ protected var _colladaIDToObject:Object; /** */ protected var _colladaSIDToObject:Object; /** */ protected var _objectToNode:Object; /** */ protected var _rootNode:DisplayObject3D; /** */ protected var _autoPlay:Boolean; /** */ protected var _rightHanded:Boolean; /** */ protected var _controllers:Array; protected var _playerType:String; protected var _loop:Boolean = false; protected var _fileSearchPaths : Array; /** * 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, loop:Boolean=false) { super(name); _autoPlay = autoPlay; _rightHanded = Papervision3D.useRIGHTHANDED; _loop = loop; _playerType = Capabilities.playerType; } /** * Gets / sets the animation controller. * * @see org.papervision3d.core.controller.AnimationController */ public function set animation(value : AnimationController) : void { _animation = value; } public function get animation() : AnimationController { return _animation; } /** * Gets all controlllers. * * @return Array of controllers. * * @see org.papervision3d.core.controller.IObjectController * @see org.papervision3d.core.controller.AnimationController * @see org.papervision3d.core.controller.MorphController * @see org.papervision3d.core.controller.SkinController */ public function get controllers() : Array { return _controllers; } public function set controllers(value : Array) : void { _controllers = value; } /** * Pauses the animation. */ public function pause():void { if(_animation) { _animation.pause(); } } /** * Plays the animation. * * @param clip Clip to play. Default is "all" * @param loop Whether the animation should loop. Default is true. */ public function play(clip:String="all", loop:Boolean=true):void { if(_animation) { _animation.play(clip, loop); } } /** * Resumes a paused animation. * * @param loop Whether the animation should loop. Defaults is true. */ public function resume(loop : Boolean=true):void { if(_animation) { _animation.resume(loop); } } /** * Stops the animation. */ public function stop():void { if(_animation) { _animation.stop(); } } /** * Whether the animation is playing. This property is read-only. * * @return True when playing. */ public function get playing() : Boolean { return _animation ? _animation.playing : false; } /** * */ public function addFileSearchPath(path : String) : void { _fileSearchPaths = _fileSearchPaths || new Array(); if(_fileSearchPaths.indexOf(path) == -1) { // remove trailing slash path = path.charAt(path.length-1) == "/" ? path.substr(0, path.length-1) : path; _fileSearchPaths.push(path); } } /** * */ override public function clone() : DisplayObject3D { var object : DisplayObject3D = super.clone(); var dae : DAE = new DAE(_autoPlay, this.name + _numClone, _loop); dae.addChild(object); var controller : IObjectController; var i : int, j : int; dae.controllers = new Array(); // Clone controllers for(i = 0; i < _controllers.length; i++) { controller = _controllers[i]; if(controller is SkinController) { var skin : SkinController = SkinController(controller); var skinTarget : DisplayObject3D = dae.getChildByName(skin.target.name+"_"+_numClone, true); var clonedSkin : SkinController = skinTarget ? new SkinController(skinTarget as Skin3D) : null; if(!clonedSkin) continue; clonedSkin.bindShapeMatrix = Matrix3D.clone(skin.bindShapeMatrix); for(j = 0; j < skin.joints.length; j++) { var joint : DisplayObject3D = skin.joints[j]; var jc : DisplayObject3D = dae.getChildByName(joint.name+"_"+_numClone, true); clonedSkin.joints.push(jc); clonedSkin.invBindMatrices.push(Matrix3D.clone(skin.invBindMatrices[j])); clonedSkin.vertexWeights.push(skin.vertexWeights[j]); } dae.controllers.push(clonedSkin); } else if(controller is MorphController) { var morph : MorphController = MorphController(controller); var morphTarget : DisplayObject3D = dae.getChildByName(morph.target.name+"_"+_numClone, true); var clonedMorph : MorphController = morphTarget ? new MorphController(morphTarget as TriangleMesh3D, morph.normalized) : null; if(!clonedMorph) continue; for(j = 0; j < morph.targets.length; j++) { clonedMorph.addMorphTarget(morph.targets[j], morph.weights[j]); } dae.controllers.push(clonedMorph); } else if(controller is AnimationController) { var animation : AnimationController = AnimationController(controller).clone(); for each(var ch : Channel3D in animation.channels) { if(ch is TransformChannel3D) { var obj : DisplayObject3D = getObjectByTransform(this, TransformChannel3D(ch).transform); if(obj) { obj = dae.getChildByName(obj.name+"_"+_numClone, true); if(obj) { TransformChannel3D(ch).transform = obj.transform; } } } } dae.animation = animation; dae.controllers.push(animation); if(_autoPlay) { animation.play(); } } } _numClone++; return dae; } private static var _numClone : int = 0; private function getObjectByTransform(object : DisplayObject3D, transform : Matrix3D) : DisplayObject3D { var child : DisplayObject3D; if(object.transform === transform) { return object; } for each(child in object.children) { var o : DisplayObject3D = getObjectByTransform(child, transform); if(o) { return o; } } 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, asynchronousParsing : Boolean = false):void { this.materials = materials || new MaterialsList(); buildFileInfo(asset); this.parser = new DaeReader(asynchronousParsing); this.parser.addEventListener(Event.COMPLETE, onParseComplete); this.parser.addEventListener(ProgressEvent.PROGRESS, onParseProgress); this.parser.addEventListener(IOErrorEvent.IO_ERROR, onParseError); addFileSearchPath(this.baseUrl); if(asset is XML) { this.COLLADA = asset as XML; this.parser.loadDocument(asset, _fileSearchPaths); } else if(asset is ByteArray) { this.COLLADA = new XML(ByteArray(asset)); this.parser.loadDocument(asset, _fileSearchPaths); } else if(asset is String) { this.filename = String(asset); this.parser.read(this.filename, _fileSearchPaths); } 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; } /** * Project. * * @param parent * @param renderSessionData * * @return Number */ public override function project(parent:DisplayObject3D, renderSessionData:RenderSessionData):Number { // update controllers if(_controllers) { for each(var controller:IObjectController in _controllers) { controller.update(); } } return super.project(parent, renderSessionData); } /** * */ protected function buildAnimatedTransforms(object : DisplayObject3D, node : DaeNode, channels : Array, bakeChannels : Boolean=true) : void { var transform : DaeTransform = node.transforms.length ? node.transforms[0] : null; var channel : DaeChannel = channels[0]; var multiChannel : TransformStackChannel3D = new TransformStackChannel3D(object.transform); var isBaked : Boolean = false; var i : int, j : int, k : int; if( transform && transform.type == "matrix" && channels.length == 1 && node.transforms.length == 1 && transform.sid == channel.syntax.targetSID) { // the node has a single element which gets aninated by a single channel. isBaked = true; } for(i = 0; i < node.transforms.length; i++) { transform = node.transforms[i]; channel = null; for(j = 0; j < channels.length; j++) { if(channels[j].syntax.targetSID == transform.sid) { channel = channels[j]; break; } } var c : TransformChannel3D; var orig : Array = transform.values; var axis : Number3D = new Number3D(orig[0], orig[1], orig[2]); var input : Array = channel ? channel.sampler.input.values : null; var output : Array = channel ? channel.sampler.output.values : null; switch(transform.type) { case "rotate": c = new RotationChannel3D(axis); var rc : Curve3D = new Curve3D(); if(channel && channel.syntax.member == "ANGLE") { for(j = 0; j < input.length; j++) { rc.addKey(new LinearCurveKey3D(input[j], output[j] * (Math.PI/180))); } } else { rc.addKey(new LinearCurveKey3D(0, orig[3] * (Math.PI/180))); } c.addCurve(rc); break; case "scale": c = new ScaleChannel3D(null); var sc0 : Curve3D = new Curve3D(); var sc1 : Curve3D = new Curve3D(); var sc2 : Curve3D = new Curve3D(); if(channel && channel.syntax.member == "X") { for(j = 0; j < input.length; j++) { sc0.addKey(new LinearCurveKey3D(input[j], output[j])); sc1.addKey(new LinearCurveKey3D(input[j], orig[1])); sc2.addKey(new LinearCurveKey3D(input[j], orig[2])); } } else if(channel && channel.syntax.member == "Y") { for(j = 0; j < input.length; j++) { sc0.addKey(new LinearCurveKey3D(input[j], orig[0])); sc1.addKey(new LinearCurveKey3D(input[j], output[j])); sc2.addKey(new LinearCurveKey3D(input[j], orig[2])); } } else if(channel && channel.syntax.member == "Z") { for(j = 0; j < input.length; j++) { sc0.addKey(new LinearCurveKey3D(input[j], orig[0])); sc1.addKey(new LinearCurveKey3D(input[j], orig[1])); sc2.addKey(new LinearCurveKey3D(input[j], output[j])); } } else { sc0.addKey(new LinearCurveKey3D(0, orig[0])); sc1.addKey(new LinearCurveKey3D(0, orig[1])); sc2.addKey(new LinearCurveKey3D(0, orig[2])); } c.addCurve(sc0); c.addCurve(sc1); c.addCurve(sc2); break; case "translate": c = new TranslationChannel3D(null); var tc0 : Curve3D = new Curve3D(); var tc1 : Curve3D = new Curve3D(); var tc2 : Curve3D = new Curve3D(); if(channel && channel.syntax.member == "X") { for(j = 0; j < input.length; j++) { tc0.addKey(new LinearCurveKey3D(input[j], output[j])); tc1.addKey(new LinearCurveKey3D(input[j], orig[1])); tc2.addKey(new LinearCurveKey3D(input[j], orig[2])); } } else if(channel && channel.syntax.member == "Y") { for(j = 0; j < input.length; j++) { tc0.addKey(new LinearCurveKey3D(input[j], orig[0])); tc1.addKey(new LinearCurveKey3D(input[j], output[j])); tc2.addKey(new LinearCurveKey3D(input[j], orig[2])); } } else if(channel && channel.syntax.member == "Z") { for(j = 0; j < input.length; j++) { tc0.addKey(new LinearCurveKey3D(input[j], orig[0])); tc1.addKey(new LinearCurveKey3D(input[j], orig[1])); tc2.addKey(new LinearCurveKey3D(input[j], output[j])); } } else if(channel) { for(j = 0; j < input.length; j++) { tc0.addKey(new LinearCurveKey3D(input[j], output[j][0])); tc1.addKey(new LinearCurveKey3D(input[j], output[j][1])); tc2.addKey(new LinearCurveKey3D(input[j], output[j][2])); } } else { tc0.addKey(new LinearCurveKey3D(0, orig[0])); tc1.addKey(new LinearCurveKey3D(0, orig[1])); tc2.addKey(new LinearCurveKey3D(0, orig[2])); } c.addCurve(tc0); c.addCurve(tc1); c.addCurve(tc2); break; case "matrix": c = new MatrixChannel3D((isBaked?object.transform:null)); var mc : Array = new Array(12); for(j = 0; j < 12; j++) { mc[j] = new Curve3D(); } if(channel && channel.syntax.isFullAccess) { for(j = 0; j < input.length; j++) { for(k = 0; k < 12; k++) { mc[k].addKey(new LinearCurveKey3D(input[j], output[j][k])); } } } else if(channel && channel.syntax.isArrayAccess) { if(channel.syntax.arrayIndex0 >= 0 && channel.syntax.arrayIndex1 >= 0) { var prop : int = (channel.syntax.arrayIndex1 * 4) + channel.syntax.arrayIndex0; for(j = 0; j < input.length; j++) { for(k = 0; k < 12; k++) { var data : Number = (k != prop) ? orig[k] : output[j]; mc[k].addKey(new LinearCurveKey3D(input[j], data)); } } } else { for(k = 0; k < 12; k++) { mc[k].addKey(new LinearCurveKey3D(0, orig[k])); } } } else { for(k = 0; k < 12; k++) { mc[k].addKey(new LinearCurveKey3D(0, orig[k])); } } for(j = 0; j < 12; j++) { c.addCurve(mc[j]); } if(isBaked) { // we can leave early this._animation.addChannel(c); return; } break; default: trace("unhandled transform type: " + transform.type); continue; } if(c) { c.target = object; multiChannel.addChannel(c); } } if(bakeChannels) { var sampleDuration : Number = 0.1; var numSamples : int = (multiChannel.endTime - multiChannel.startTime) / sampleDuration; var matrixChannel : MatrixChannel3D = multiChannel.bake(numSamples); this._animation.addChannel(matrixChannel); matrixChannel.target = object; matrixChannel.transform = object.transform; } else { multiChannel.target = object; this._animation.addChannel(multiChannel); } } /** * Builds animated vertices if needed. * NOTE: this is a Feeling specific feature. Its not part of the COLLADA 1.4.1 spec. * * @param target * @param vertices */ protected function buildAnimatedVertices(target : TriangleMesh3D, vertices : DaeVertices) : void { var channels : Array = (vertices && vertices.source) ? this.document.animatables[ vertices.source.id ] : null; var i : int; if(channels && channels.length) { for(i = 0; i < channels.length; i++) { var channel : DaeChannel = channels[i]; var vertexIndex : int = Math.floor(channel.syntax.arrayIndex0 / 3); var vertexProp : int = channel.syntax.arrayIndex0 % 3; var vertexChannel : VertexChannel3D = new VertexChannel3D(target.geometry, vertexIndex, vertexProp); var curve : Curve3D = new Curve3D(); for(var j:int = 0; j < channel.sampler.input.values.length; j++) { var time : Number = channel.sampler.input.values[j]; var data : Number = channel.sampler.output.values[j]; curve.addKey(new LinearCurveKey3D(time, data)); } if(vertexChannel.addCurve(curve)) { this._animation.addChannel(vertexChannel); } else { PaperLogger.warning("DAE#buildAnimatedVertices : invalid curve for channel " + channel.id + " for object " + target.name); } } } } /** * */ protected function buildAnimationClips() : void { if(!this.document || !this.document.animation_clips) { return; } var daeClip : DaeAnimationClip; for each(daeClip in this.document.animation_clips) { if(daeClip.name && daeClip.end > daeClip.start) { var clip : AnimationClip3D = new AnimationClip3D(daeClip.name, daeClip.start, daeClip.end); this._animation.addClip(clip); } } } /** * Builds the _animation for an object and its children. * * @param object */ protected function buildAnimations(object : DisplayObject3D) : void { var node : DaeNode = _objectToNode[ object ]; var child : DisplayObject3D; if(node) { var channels : Array = this.document.animatables[node.id]; if(channels && channels.length) { buildAnimatedTransforms(object, node, channels); } } for each(child in object.children) { buildAnimations(child); } } /** * Links the controllers to the objects. * * @param instance */ protected function buildControllers(instance : DisplayObject3D=null) : void { var node : DaeNode = _objectToNode[instance]; var instanceController : DaeInstanceController; var controller : DaeController; var morphController : MorphController; var skinController : SkinController; instance = instance || _rootNode; if(node) { // loop over all controller instances for this node for each(instanceController in node.controllers) { controller = this.document.controllers[ instanceController.url ]; if(controller && controller.morph) { morphController = buildMorphController(instance as TriangleMesh3D, controller.morph); if(morphController) { _controllers.push(morphController); } } else if(controller && controller.skin) { skinController = buildSkinController(instance as Skin3D, controller.skin); if(skinController) { _controllers.push(skinController); for each(var c:IObjectController in _controllers) { if(c === skinController) { continue; } else if(c is MorphController && MorphController(c).target === skinController.target) { skinController.input = MorphController(c); } } } } } } // recurse children for each(var child : DisplayObject3D in instance.children) { buildControllers(child); } } /** * * @param asset * @return */ protected 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 = ""; } } /** * */ protected function buildGeometry(target : TriangleMesh3D, daeGeometry : DaeGeometry, daeBindMaterial : DaeBindMaterial) : void { var daePrimitive : DaePrimitive; var daeInstanceMaterial : DaeInstanceMaterial; var vertexStart : int = target.geometry.vertices.length; var i : int; if(daeGeometry.mesh) { target.geometry.vertices = target.geometry.vertices.concat( buildVertices(daeGeometry.mesh) ); for(i = 0; i < daeGeometry.mesh.primitives.length; i++) { daePrimitive = daeGeometry.mesh.primitives[i]; daeInstanceMaterial = daeBindMaterial ? daeBindMaterial.getInstanceMaterialBySymbol(daePrimitive.material) : null; buildPrimitive(target, daePrimitive, daeInstanceMaterial, vertexStart); } // builds vertex aninations if any available buildAnimatedVertices(target, daeGeometry.mesh.vertices); } else if(daeGeometry.convex_mesh) { PaperLogger.warning("[DAE] Don't know yet how to create a convex_mesh."); } } /** * */ protected function buildGeometryLines(target : Lines3D, daeGeometry : DaeGeometry, daeBindMaterial : DaeBindMaterial) : void { var i : int, j : int, k : int; if(daeBindMaterial) { // TODO: handle spline materials } for(i = 0; i < daeGeometry.splines.length; i++) { var spline:DaeSpline = daeGeometry.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(target, target.material as LineMaterial, DEFAULT_LINE_WIDTH, v0, v1); target.addLine(line); } } } /** * */ protected function buildMaterialInstance(daeInstanceMaterial : DaeInstanceMaterial, outBVI : DaeBindVertexInput) : MaterialObject3D { if(!daeInstanceMaterial) { return null; } var material : MaterialObject3D = this.materials.getMaterialByName(daeInstanceMaterial.target); var daeMaterial : DaeMaterial = this.document.materials[ daeInstanceMaterial.target ]; var daeEffect : DaeEffect = daeMaterial ? this.document.effects[ daeMaterial.effect ] : null; var daeLambert : DaeLambert = daeEffect ? daeEffect.color as DaeLambert : null; var daeColorOrTexture : DaeColorOrTexture = daeLambert ? daeLambert.diffuse || daeLambert.emission : null; var daeTexture : DaeTexture = daeColorOrTexture ? daeColorOrTexture.texture : null; var daeImage : DaeImage = (daeEffect && daeEffect.texture_url) ? this.document.images[daeEffect.texture_url] : null; var daeBVI : DaeBindVertexInput; if(daeTexture && daeTexture.texture) { daeBVI = daeInstanceMaterial.findBindVertexInput(daeTexture.texcoord); outBVI.input_set = daeBVI ? daeBVI.input_set : -1; } if(material) { // material already exists in #materials return material; } if(daeImage && daeImage.bitmapData) { material = new BitmapMaterial(daeImage.bitmapData.clone()); material.tiled = true; } else if(daeColorOrTexture && daeColorOrTexture.color) { var r : int = daeColorOrTexture.color[0] * 0xff; var g : int = daeColorOrTexture.color[1] * 0xff; var b : int = daeColorOrTexture.color[2] * 0xff; var rgb : uint = r << 16 | g << 8 | b; if(daeEffect.wireframe) { material = new WireframeMaterial(rgb, daeColorOrTexture.color[3]); } else { material = new ColorMaterial(rgb, daeColorOrTexture.color[3]); } } if(material) { if(daeEffect && daeEffect.double_sided) { material.doubleSided = true; } this.materials.addMaterial(material, daeInstanceMaterial.target); } return material; } /** * Builds a Matrix3D from a node's transform array. @see org.ascollada.core.DaeNode#transforms * * @param node * * @return */ protected 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.calculateMultiply(matrix, stack[i]); return matrix; } /** * * @param node * @return */ protected 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 */ protected 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; } /** * */ protected function buildMesh(node : DaeNode) : DisplayObject3D { var mesh : DisplayObject3D; var daeInstanceGeometry : DaeInstanceGeometry; var daeGeometry : DaeGeometry; var daeInstanceController : DaeInstanceController; var daeController : DaeController; var daeMorphController : DaeController; var daeBindMaterial : DaeBindMaterial; var i : int; // handle instance geometries for(i = 0; i < node.geometries.length; i++) { daeInstanceGeometry = node.geometries[i]; daeBindMaterial = daeInstanceGeometry.bindMaterial; daeGeometry = this.document.geometries[ daeInstanceGeometry.url ]; if(daeGeometry.mesh || daeGeometry.convex_mesh) { mesh = new TriangleMesh3D(null, [], [], node.name); buildGeometry(mesh as TriangleMesh3D, daeGeometry, daeBindMaterial); } else if(daeGeometry.spline && daeGeometry.splines) { mesh = new Lines3D(new LineMaterial(DEFAULT_LINE_COLOR), node.name); buildGeometryLines(mesh as Lines3D, daeGeometry, daeBindMaterial); } } if(mesh) { mesh.geometry.ready = true; return mesh; } // handle geometries instantiated by controllers for(i = 0; i < node.controllers.length; i++) { daeGeometry = null; daeInstanceController = node.controllers[i]; daeBindMaterial = daeInstanceController.bindMaterial; daeController = this.document.controllers[ daeInstanceController.url ]; if(daeController.skin) { daeGeometry = this.document.geometries[ daeController.skin.source ]; if(!daeGeometry) { // geometry may be output of a morph controller daeMorphController = this.document.controllers[ daeController.skin.source ]; if(daeMorphController.morph) { daeGeometry = this.document.geometries[ daeMorphController.morph.source ]; } } mesh = new Skin3D(null, [], [], node.name); } else if(daeController.morph) { mesh = new TriangleMesh3D(null, [], [], node.name); daeGeometry = this.document.geometries[ daeController.morph.source ]; } if(daeGeometry) { buildGeometry(mesh as TriangleMesh3D, daeGeometry, daeBindMaterial); } if(mesh) { if(daeMorphController) { var morphController : MorphController = buildMorphController(mesh as TriangleMesh3D, daeMorphController.morph); if(morphController) { _controllers.push(morphController); } } mesh.geometry.ready = true; return mesh; } } if(mesh is TriangleMesh3D) { mesh.geometry.ready = true; } return mesh; } /** * Builds a morph controller. * * @param instance * @param morph * @param bindMaterial * * @return */ protected function buildMorphController(instance : TriangleMesh3D, morph : DaeMorph) : MorphController { var controller : MorphController = new MorphController(instance); var daeGeometry : DaeGeometry; var i : int; for(i = 0; i < morph.targets.values.length; i++) { var target : String = morph.targets.values[i]; var weight : Number = morph.weights.values[i]; daeGeometry = this.document.geometries[ target ]; var targetMesh : TriangleMesh3D = new TriangleMesh3D(null, [], [], instance.name + " " + target); buildGeometry(targetMesh, daeGeometry, null); controller.addMorphTarget(targetMesh, weight); } // Check whether morph weights are animated var channels : Array = this.document.animatables[ morph.weights.id ]; if(channels) { for(i = 0; i < channels.length; i++) { var channel : DaeChannel = channels[i]; var index : int = channel.syntax.arrayIndex0; var track : MorphWeightChannel3D = new MorphWeightChannel3D(controller, index); var curve : Curve3D = new Curve3D(); var input : Array = channel.sampler.input.values; var output : Array = channel.sampler.output.values; var j : int; for(j = 0; j < input.length; j++) { curve.addKey(new LinearCurveKey3D(input[j], output[j])); } if(track.addCurve(curve)) { this._animation.addChannel(track); } else { PaperLogger.warning("DAE#buildMorphController : invalid animation curve..."); } } } return controller; } /** * Builds a DisplayObject3D from a node. @see org.ascollada.core.DaeNode * * @param node * * @return The created DisplayObject3D. @see org.papervision3d.objects.DisplayObject3D */ protected function buildNode(node:DaeNode, parent:DisplayObject3D):void { var instance:DisplayObject3D; var i:int; if(node.geometries.length || node.controllers.length) { // the node instantiates some kind of geometry instance = buildMesh(node); } else { // no geometry, simply create a DisplayObject3D 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 a primitive. * * @param mesh * @param daePrimitive * @param daeInstanceMaterial * @param vertexStart */ protected function buildPrimitive(mesh : TriangleMesh3D, daePrimitive : DaePrimitive, daeInstanceMaterial : DaeInstanceMaterial, vertexStart : int):void { var material : MaterialObject3D; var texcoords : Array = new Array(), texCoordSet : Array; var daeBVI : DaeBindVertexInput = new DaeBindVertexInput(this.document); var idx0 : int, idx1 : int, idx2 : int; var v0 : Vertex3D, v1 : Vertex3D, v2 : Vertex3D; var t0 : NumberUV, t1 : NumberUV, t2 : NumberUV; var hasUV : Boolean = false; var i : int, j : int, k : int; daeBVI.input_set = -1; daeBVI.input_semantic = "TEXCOORD"; material = buildMaterialInstance(daeInstanceMaterial, daeBVI) || MaterialObject3D.DEFAULT; if(material is AbstractLightShadeMaterial || material is ShadedMaterial) { material.registerObject(mesh); } // choose the correct texcoord set if(this.forceCoordSet >= 0) { // forced texCoordSet = daePrimitive.getTexCoords(this.forceCoordSet); } else if(daeBVI.input_set < 0) { // no BindVertexInput defined, lets use the default texcoords texCoordSet = daePrimitive.getFirstInput("TEXCOORD"); } else { // BindVertexInput is defined, select the specified texcoord set. texCoordSet = daePrimitive.getTexCoords(daeBVI.input_set); } texCoordSet = texCoordSet || new Array(); // texture coords for( i = 0; i < texCoordSet.length; i++ ) { if( !texCoordSet[i] || texCoordSet[i].length < 2) continue; if(Papervision3D.useRIGHTHANDED) { texcoords.push(new NumberUV(1.0-texCoordSet[i][0], texCoordSet[i][1])); } else { texcoords.push(new NumberUV(texCoordSet[i][0], texCoordSet[i][1])); } } hasUV = (texcoords.length == daePrimitive.vertices.length); switch(daePrimitive.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 < daePrimitive.vertices.length; i += 2) { v0 = geometry.vertices[ daePrimitive.vertices[i] ]; v1 = geometry.vertices[ daePrimitive.vertices[i+1] ]; t0 = hasUV ? texcoords[ i ] : new NumberUV(); t1 = hasUV ? texcoords[ i+1 ] : new NumberUV(); } */ break; // polygon with *no* holes case ASCollada.DAE_POLYLIST_ELEMENT: for(i = 0, k = 0; i < daePrimitive.vcount.length; i++) { var poly:Array = new Array(); var uvs:Array = new Array(); for( j = 0; j < daePrimitive.vcount[i]; j++ ) { uvs.push( (hasUV ? texcoords[ k ] : new NumberUV()) ); poly.push( mesh.geometry.vertices[daePrimitive.vertices[k++]] ); } v0 = poly[0]; t0 = uvs[0]; for(j = 1; j < poly.length - 1; j++) { v1 = poly[j]; v2 = poly[j+1]; t1 = uvs[j]; t2 = uvs[j+1]; if( v0 is Vertex3D && v1 is Vertex3D && v2 is Vertex3D) { mesh.geometry.faces.push(new Triangle3D(mesh, [v0, v1, v2], material, [t0, t1, t2])); } else { PaperLogger.error("" +daePrimitive.name+ " "+ poly.length +" "+daePrimitive.vertices.length+" "+mesh.geometry.vertices.length); } } } break; // polygons *with* holes (but holes not yet processed...) case ASCollada.DAE_POLYGONS_ELEMENT: for(i = 0, k = 0; i < daePrimitive.polygons.length; i++) { var p:Array = daePrimitive.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( mesh.geometry.vertices[daePrimitive.vertices[k++]] ); } v0 = np[0]; t0 = nuv[0]; for(j = 1; j < np.length - 1; j++) { v1 = np[j]; v2 = np[j+1]; t1 = nuv[j]; t2 = nuv[j+1]; mesh.geometry.faces.push(new Triangle3D(mesh, [v0, v1, v2], material, [t0, t1, t2])); } } break; // simple triangles case ASCollada.DAE_TRIANGLES_ELEMENT: default: for(i = 0; i < daePrimitive.vertices.length; i += 3) { idx0 = vertexStart + daePrimitive.vertices[i]; idx1 = vertexStart + daePrimitive.vertices[i+1]; idx2 = vertexStart + daePrimitive.vertices[i+2]; v0 = mesh.geometry.vertices[ idx0 ]; v1 = mesh.geometry.vertices[ idx1 ]; v2 = mesh.geometry.vertices[ idx2 ]; t0 = hasUV ? texcoords[ i+0 ] : new NumberUV(); t1 = hasUV ? texcoords[ i+1 ] : new NumberUV(); t2 = hasUV ? texcoords[ i+2 ] : new NumberUV(); mesh.geometry.faces.push(new Triangle3D(mesh, [v0, v1, v2], material, [t0, t1, t2])); } break; } } /** * Builds the scene. */ protected function buildScene():void { _controllers = new Array(); this._animation = new AnimationController(); _controllers.push(this._animation); _rootNode = new DisplayObject3D(ROOTNODE_NAME); for(var i:int = 0; i < this.document.vscene.nodes.length; i++) { buildNode(this.document.vscene.nodes[i], _rootNode); } this.addChild(_rootNode); // build controllers buildControllers(); if(this.yUp) { } else { _rootNode.rotationX = -90; _rootNode.rotationY = _rightHanded ? 0 : 180; } if(!_rightHanded) _rootNode.scaleX = -_rootNode.scaleX; // _animation stuff onParseAnimationsComplete(); this.document.destroy(); this.document = null; this.COLLADA = null; if(this.parser) { if(this.parser.document) { this.parser.document.destroy(); this.parser.document = null; } this.parser = null; } dispatchEvent(new FileLoadEvent(FileLoadEvent.LOAD_COMPLETE, this.filename)); } /** * Builds a skin controller. * * @param instance * @param skin */ protected function buildSkinController(instance : DisplayObject3D, skin : DaeSkin) : SkinController { var i:int; var found:Object = new Object(); if(!skin) { return null; } 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; } return controller; } /** * Builds vertices from a COLLADA mesh. * * @param mesh The COLLADA mesh. @see org.ascollada.core.DaeMesh * * @return Array of Vertex3D */ protected function buildVertices(mesh:DaeMesh):Array { var vertices:Array = new Array(); for( var i:int = 0; i < mesh.vertices.source.values.length; i++ ) vertices.push(new Vertex3D(mesh.vertices.source.values[i][0], mesh.vertices.source.values[i][1], mesh.vertices.source.values[i][2])); return vertices; } /** * Called when the parser completed parsing animations. * * @param event */ protected function onParseAnimationsComplete(event:Event=null):void { buildAnimations(this); buildAnimationClips(); if(this._animation.numChannels > 0) { if(_autoPlay) { this._animation.play("all", _loop); } PaperLogger.info( "animations COMPLETE " + this._animation); } dispatchEvent(new FileLoadEvent(FileLoadEvent.ANIMATIONS_COMPLETE, this.filename)); } /** * Called on parse animations progress. * * @param event */ protected function onParseAnimationsProgress(event:ProgressEvent):void { PaperLogger.info( "animations #" + event.bytesLoaded + " of " + event.bytesTotal); dispatchEvent(new FileLoadEvent(FileLoadEvent.ANIMATIONS_PROGRESS, this.filename, event.bytesLoaded, event.bytesTotal)); } /** * Called when the DaeReader completed parsing. * * @param event */ protected function onParseComplete(event:Event):void { var reader:DaeReader = event.target as DaeReader; this.document = reader.document; _colladaID = new Dictionary(true); _colladaSID = new Dictionary(true); _colladaIDToObject = new Object(); _colladaSIDToObject = new Object(); _objectToNode = new Object(); if(this.parser.hasEventListener(Event.COMPLETE)) this.parser.removeEventListener(Event.COMPLETE, onParseComplete); if(this.parser.hasEventListener(ProgressEvent.PROGRESS)) this.parser.removeEventListener(ProgressEvent.PROGRESS, onParseProgress); if(this.parser.hasEventListener(IOErrorEvent.IO_ERROR)) this.parser.removeEventListener(IOErrorEvent.IO_ERROR, onParseError); buildScene(); } /** * Called on parsing error (invalid file name) * * @param event */ protected function onParseError(event:IOErrorEvent):void{ dispatchEvent(event); } /** * Called on parsing progress. * * @param event */ protected function onParseProgress(event:ProgressEvent):void { var message : String = this.parser.parseMessage; dispatchEvent(new FileLoadEvent(FileLoadEvent.LOAD_PROGRESS, this.filename, event.bytesLoaded, event.bytesTotal, message, null, true, false)); } /** Whether the COLLADA uses Y-up, Z-up otherwise. */ public function get yUp():Boolean { if(this.document){ return (this.document.asset.yUp == ASCollada.DAE_Y_UP); }else{ return false; } } public function set rootNode(value : DisplayObject3D) : void { _rootNode = value; } public function get rootNode() : DisplayObject3D { return _rootNode; } } }