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);
}
}
}