package away3d.loaders { import away3d.containers.ObjectContainer3D; import away3d.arcane; import away3d.core.base.*; import away3d.core.utils.*; import away3d.materials.BitmapFileMaterial; import flash.events.*; import flash.net.*; use namespace arcane; /** * File loader for the OBJ file format.
*
* note: Multiple objects support and autoload mtls are supported since Away v 2.1.
* Class tested with the following 3D apps:
* - Strata CX mac 5.5
* - Biturn ver 0.87b4 PC
* - LightWave 3D OBJ Export v2.1 PC
* - Max2Obj Version 4.0 PC
* - AC3D 6.2.025 mac
* - Carrara (file provided)
* - Hexagon (file provided)
* - geometry supported tags: f,v,vt, g
* - geometry unsupported tags:vn,ka, kd r g b, kd, ks r g b,ks,ke r g b,ke,d alpha,d,tr alpha,tr,ns s,ns,illum n,illum,map_Ka,map_Bump
* - mtl unsupported tags: kd,ka,ks,ns,tr
*
* export from apps as polygon group or mesh as .obj file.
* added support for 3dmax negative vertexes references */ public class Obj extends AbstractParser { private var ini:Init; public var mesh:Mesh; private var mtlPath:String; private var aMeshes:Array = []; private var aSources:Array; private var aMats:Array; private var vertices:Array = []; private var uvs:Array = []; private var scaling:Number; private function parseObj(data:String):void { var lines:Array = data.split('\n'); var trunk:Array; var isNew:Boolean = true; var group:ObjectContainer3D; vertices = [new Vertex()]; uvs = [new UV()]; var isNeg:Boolean; var myPattern:RegExp = new RegExp("-","g"); var face0:Array; var face1:Array; var face2:Array; var face3:Array; for each (var line:String in lines) { trunk = line.replace(" "," ").replace(" "," ").replace(" "," ").split(" "); switch (trunk[0]) { case "g": group = new ObjectContainer3D(); group.name = trunk[1]; if (container == null) { if(aMeshes.length == 1){ container = new ObjectContainer3D(aMeshes[0].mesh); } else{ container = new ObjectContainer3D(); } } (container as ObjectContainer3D).addChild(group); isNew = true; break; case "usemtl": aMeshes[aMeshes.length-1].materialid = trunk[1]; break; case "v": if(isNew) { generateNewMesh(); isNew = false; if(group != null){ group.addChild(mesh); } } vertices.push(new Vertex(-parseFloat(trunk[1]) * scaling, parseFloat(trunk[2]) * scaling, -parseFloat(trunk[3]) * scaling)); break; case "vt": uvs.push(new UV(parseFloat(trunk[1]), parseFloat(trunk[2]))); break; case "f": isNew = true; if(trunk[1].indexOf("-") == -1) { face0 = trysplit(trunk[1], "/"); face1 = trysplit(trunk[2], "/"); face2 = trysplit(trunk[3], "/"); if (trunk[4] != null){ face3 = trysplit(trunk[4], "/"); }else{ face3 = null; } isNeg = false; } else { face0 = trysplit(trunk[1].replace(myPattern, "") , "/"); face1 = trysplit(trunk[2].replace(myPattern, "") , "/"); face2 = trysplit(trunk[3].replace(myPattern, "") , "/"); if (trunk[4] != null){ face3 = trysplit(trunk[4].replace(myPattern, "") , "/"); } else{ face3 = null; } isNeg = true; } try{ if (face3 != null && face3.length>0 && !isNaN(parseInt(face3[0])) ){ if(isNeg){ mesh.addFace(new Face(vertices[vertices.length - parseInt(face1[0])], vertices[vertices.length - parseInt(face0[0])], vertices[vertices.length - parseInt(face3[0])], null, checkUV(1, uvs[uvs.length - parseInt(face1[1])]), checkUV(2, uvs[uvs.length - parseInt(face0[1])]), checkUV(3, uvs[uvs.length - parseInt(face3[1])]) )); mesh.addFace(new Face(vertices[vertices.length - parseInt(face2[0])], vertices[vertices.length - parseInt(face1[0])], vertices[vertices.length - parseInt(face3[0])], null, checkUV(1, uvs[uvs.length - parseInt(face2[1])]), checkUV(2, uvs[uvs.length - parseInt(face1[1])]), checkUV(3, uvs[uvs.length - parseInt(face3[1])]) )); } else { mesh.addFace(new Face(vertices[parseInt(face1[0])], vertices[parseInt(face0[0])], vertices[parseInt(face3[0])], null, checkUV(1, uvs[parseInt(face1[1])]), checkUV(2, uvs[parseInt(face0[1])]), checkUV(3, uvs[parseInt(face3[1])]) )); mesh.addFace(new Face(vertices[parseInt(face2[0])], vertices[parseInt(face1[0])], vertices[parseInt(face3[0])], null, checkUV(1, uvs[parseInt(face2[1])]), checkUV(2, uvs[parseInt(face1[1])]), checkUV(3, uvs[parseInt(face3[1])]) )); } } else { if(isNeg){ mesh.addFace(new Face(vertices[vertices.length - parseInt(face2[0])], vertices[vertices.length- parseInt(face1[0])], vertices[vertices.length- parseInt(face0[0])], null, checkUV(1, uvs[uvs.length - parseInt(face2[1])]), checkUV(2, uvs[uvs.length - parseInt(face1[1])]), checkUV(3, uvs[uvs.length - parseInt(face0[1])]) )); } else { mesh.addFace(new Face(vertices[parseInt(face2[0])], vertices[parseInt(face1[0])], vertices[parseInt(face0[0])], null, checkUV(1, uvs[parseInt(face2[1])]), checkUV(2, uvs[parseInt(face1[1])]), checkUV(3, uvs[parseInt(face0[1])]) )); } } }catch(e:Error){ trace("Error while parsing obj file: unvalid face f "+face0+","+face1+","+face2+","+face3); } break; } } vertices = null; uvs = null; if (container == null) container = mesh; } private function checkUV(id:int, uv:UV = null):UV { if(uv == null){ switch(id){ case 1: return new UV(0, 1); break; case 2: return new UV(.5, 0); break; case 3: return new UV(1, 1); break; } } return uv; } private static function trysplit(source:String, by:String):Array { if (source == null) return null; if (source.indexOf(by) == -1) return [source]; return source.split(by); } private function checkMtl(data:String):void { var index:int = data.indexOf("mtllib"); if(index != -1){ aSources = []; loadMtl (parseUrl(index, data)); } } private function errorMtl (event:Event):void { trace("Obj MTL LOAD ERROR: unable to load .mtl file at "+mtlPath); } private function mtlProgress (event:Event):void { //NOT BUILDED IN YET //trace( (event.target.bytesLoaded / event.target.bytesTotal) *100); } private function loadMtl(url:String):void { var loader:URLLoader = new URLLoader(); loader.addEventListener(Event.COMPLETE, parseMtl); loader.addEventListener(IOErrorEvent.IO_ERROR, errorMtl); loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, errorMtl); loader.addEventListener(ProgressEvent.PROGRESS, mtlProgress); loader.load(new URLRequest(mtlPath+url)); } private function parseUrl(index:Number, data:String):String { return data.substring(index+7,data.indexOf(".mtl")+4); } private function parseMtl (event:Event):void { var loader:URLLoader = URLLoader(event.target); var lines:Array = event.target.data.split('\n'); var trunk:Array; var i:int; var j:int var _face:Face; var mat:BitmapFileMaterial; aMats = []; for each (var line:String in lines) { trunk = line.split(" "); switch (trunk[0]) { case "newmtl": aSources.push({material:null, materialid:trunk[1]}); break; case "map_Kd": mat = checkDoubleMaterials(mtlPath+trunk[1]); aSources[aSources.length-1].material = mat; //aSources[aSources.length-1].material = new BitmapFileMaterial(baseUrl+trunk[1]); break; } } for(j = 0;j 1 || container != null) (container as ObjectContainer3D).addChild(mesh); } /** * Creates a new Obj object. Not intended for direct use, use the static parse or load methods. * * @param data The binary data of a loaded file. * @param urlbase The url of the .obj file, required to compose the url mtl adres and be able access the bitmap sources relative to mtl location. * @param init [optional] An initialisation object for specifying default instance properties. * * @see away3d.loaders.Obj#parse() * @see away3d.loaders.Obj#load() */ public function Obj(data:*, init:Object = null) { var dataString:String = Cast.string(data); ini = Init.parse(init); mtlPath = ini.getString("mtlPath", ""); scaling = ini.getNumber("scaling", 1) * 10; parseObj(dataString); checkMtl(dataString); } /** * Creates a 3d mesh object from the raw ascii data of a obj file. * * @param data The ascii data of a loaded file. * @param init [optional] An initialisation object for specifying default instance properties. * * @return A 3d mesh object representation of the obj file. */ public static function parse(data:*, init:Object = null):Object3D { return Object3DLoader.parseGeometry(data, Obj, init).handle; } /** * Loads and parses a obj file into a 3d mesh object. * * @param url The url location of the file to load. * @param init [optional] An initialisation object for specifying default instance properties. * @return A 3d loader object that can be used as a placeholder in a scene while the file is loading. */ public static function load(url:String, init:Object = null):Object3DLoader { //mtlPath as model folder if (url) { var _pathArray :Array = url.split("/"); var _imageName :String = _pathArray.pop(); var _mtlPath :String = (_pathArray.length>0)?_pathArray.join("/")+"/":_pathArray.join("/"); if (init){ init.mtlPath = (init.mtlPath)?init.mtlPath:_mtlPath; }else { init = { mtlPath:_mtlPath }; } } return Object3DLoader.loadGeometry(url, Obj, false, init); } } }