This JSONOBJ parser will take obj js models for three.js and parse them for Away3D BroomStick. I created this class because I thought it would be nice to share assets between the two engines. I also think other people can use it. I think the majority of people would like to have models ready for both environments and this will cut down the time it takes to create models for both environments. I also found that the Three.js python script that converts OBJ models into javascript also does a great deal of work so some of the work that is done using the normal away3d obj parser does not need to be done if you use this script. This demo is simply a proof of concept it is not a finished product or class. It currently does not support materials although I don’t believe it will take me that long to port that functionality over. This parser does support quads and triangles and I don’t believe the current obj parse in away3d supports quads or it has to have uv data. I would like to clean up the code and offer it to the away3d team for inclusion in their code base. I would also like to suggest to Fabrice the creator of preFab to allow prefab to also export in the JSONOBJ three.js format. Their are many similarities in these projects and I think it would be nice to see the projects working and communicating more between each other maybe they already do. Some things that I would like to point out about the three.js format it supports morphing targets. If you are familiar with the project then you might already know the morphing abilities were used in the ROME an interactive film by Chris Milk relased at GoogleIO. I of course have not had time to examine this and port this functionality over but do believe it is possible. I also love the new functionality of the native json parsing in FP11 stage3D native JSON support is a big deal it also seems to handle garbage collection much better than the previous versions of the flash player. It makes json parsing stupid human proof. Just give it the JSON and receive an object and then your off and running.

I also noticed that their is a new webGL framework J3D that will parse Unity3D scenes I am curious to play around with this because I think it would be nice for Away3d to have this functionality as well.

So how does the Three.js obj converter work? Simple place the converter in the directory of the obj then open a command prompt navigate to the directory and type the following in. Obviously you will need python to be setup on your box and your original OBJ file in that directory for this to work.

python convert_obj_three.py -i head.obj -o head.js -t ascii

The away3D parser that I have built does not currently support the binary format that the python converter can build. I will examine this later and I am confident we can support the binary format as well in the future after all away3D does this with its own awd format.

After the model is converted to JavaScript you will have to remove the following information in the JavaScript file.

// Converted from: output.obj
//  vertices: 20244
//  faces: 14292
//  normals: 14292
//  colors: 0
//  uvs: 0
//  materials: 2
//  edges: 0
//
//  Generated with OBJ -> Three.js converter
//  http://github.com/alteredq/three.js/blob/master/utils/exporters/convert_obj_three.py

This information is output by the converter and is not needed by the JavaScript just remove it then you will be able to use the same model in both JavaScript and Flash Away3D.

To create this parser I had to edit the Parsers.as file to include the JSONOBJ Parser:

package away3d.loaders.parsers
{
	import away3d.loaders.misc.SingleFileLoader;
 
	public class Parsers
	{
		/**
		 * A list of all parsers that come bundled with Away3D. Use it to quickly
		 * enable support for all bundled parsers to the ResourceManager file format
		 * auto-detect feature, using ResourceManager.addParsers():
		 * 
		 * <code>AssetLibrary.enableParsers(Parsers.ALL_BUNDLED);</code>
		 * 
		 * Beware however that this requires all parser classes to be included in the
		 * SWF file, which will add 50-100 kb to the file. When only a limited set of
		 * file formats are used, SWF file size can be saved by adding the parsers
		 * individually using AssetLibrary.enableParser()
		 * 
		 * A third way is to specify a parser for each loaded file, thereby bypassing
		 * the auto-detection mechanisms altogether, while at the same time allowing
		 * any properties that are unique to that parser to be set for that load.
		 * 
		 * @see away3d.loading.AssetLibrary.enableParser()
		 * 
		 * The bundled parsers are:
		 * 
		 * <ul>
		 * <li>Away Data version 1 ASCII and version 2 binary (.awd). AWD1 BSP unsupported</li>
		 * <li>AC3D (.ac)</li>
		 * <li>Collada (.dae)</li>
		 * <li>Quake 2 MD2 models (.md2)</li>
		 * <li>Doom 3 MD5 meshes (.md5mesh)</li>
		 * <li>Doom 3 MD5 animation clips (.md5anim)</li>
		 * <li>Wavefront OBJ (.obj)</li>
		 * <li>3DMax (.3ds)</li>
		 * <li>Images (.jpg, .png)</li>
		 * </ul>
		*/
		public static const ALL_BUNDLED : Vector.<Class> = Vector.<Class>([
			AC3DParser, AWDParser, Max3DSParser,
			MD2Parser, MD5AnimParser, MD5MeshParser, OBJParser, JSONOBJParser
		]);
 
 
		/**
		 * Short-hand function for SingleFileLoader.enableParsers(Parsers.ALL_BUNDLED).
		*/
		public static function enableAllBundled() : void
		{
			SingleFileLoader.enableParsers(ALL_BUNDLED);
		}
	}
}

Then I created the following Class to do the parsing:

package away3d.loaders.parsers
{
	import away3d.arcane;
	import away3d.core.base.Geometry;
	import away3d.core.base.SubGeometry;
	import away3d.core.base.data.UV;
	import away3d.core.base.data.Vertex;
	import away3d.entities.Mesh;
	import away3d.library.assets.BitmapDataAsset;
	import away3d.loaders.misc.ResourceDependency;
	import away3d.materials.BitmapMaterial;
	import away3d.materials.methods.BasicSpecularMethod;
 
	import flash.display.BitmapData;
	import flash.net.URLRequest;
	import flash.utils.ByteArray;
 
	use namespace arcane;
 
	/**
	 * JSONOBJParser provides a parser for the JSONOBJ data type.
	 */
	public class JSONOBJParser extends ParserBase
	{
		private var _startedParsing : Boolean;
		private const LIMIT:uint = 64998;
 
		private var _currentObject : ObjectGroup;
		private var _currentGroup : Group;
		private var _currentMaterialGroup : MaterialGroup;
		private var _objects : Vector.<ObjectGroup>;
		private var _materialIDs : Vector.<String>;
		private var _materialLoaded : Vector.<LoadedMaterial>;
		private var _materialSpecularData : Vector.<SpecularData>;
		private var _meshes : Vector.<Mesh>;
		private var _lastMtlID:String;
		private var _objectIndex : uint;
		private var _realIndices : Array;
		private var _vertexIndex : uint;
		private var jsonOBJ:Object;
		private var _mtlLib : Boolean;
		private var _mtlLibLoaded : Boolean = true;
		private var isQuad: Boolean = false;
		private var _idCount : uint;
		private var _activeMaterialID:String = "";
 
		private var _vertices : Vector.<Vertex>;
		private var _vertexNormals : Vector.<Vertex>;
		private var _uvs : Vector.<UV>;
 
		private var _scale : Number;
 
		/**
		 * Creates a new JSONOBJParser object.
		 * @param uri The url or id of the data or file to be parsed.
		 * @param extra The holder for extra contextual data that the parser might need.
		 */
		public function JSONOBJParser(scale:Number = 1)
		{
			super(ParserDataFormat.PLAIN_TEXT);
			_scale = scale;
		}
 
		/**
		 * Scaling factor applied directly to vertices data
		 * @param value The scaling factor.
		 */
		public function set scale(value:Number):void
		{
			_scale = value;
		}
 
		/**
		 * Indicates whether or not a given file extension is supported by the parser.
		 * @param extension The file extension of a potential file to be parsed.
		 * @return Whether or not the given file type is supported.
		 */
		public static function supportsType(extension : String) : Boolean
		{
			extension = extension.toLowerCase();
			return extension == "js";
		}
 
		/**
		 * Tests whether a data block can be parsed by the parser.
		 * @param data The data block to potentially be parsed.
		 * @return Whether or not the given data is supported.
		 */
		public static function supportsData(data : *) : Boolean
		{
			var content : String = String(data);
			var hasModel : Boolean = content.indexOf("model") != -1;
			return hasModel;
		}
 
 
		/**
		 * @inheritDoc
		 */
		override arcane function resolveDependency(resourceDependency:ResourceDependency):void
		{
			if (resourceDependency.id == 'mtl') {
				var ba : ByteArray = resourceDependency.data;
 
				parseMtl(ba.readUTFBytes(ba.bytesAvailable));
			}
			else {
				if (resourceDependency.assets.length != 1)
					return;
 
				var asset:BitmapDataAsset = resourceDependency.assets[0] as BitmapDataAsset;
 
				if (asset){
					var lm:LoadedMaterial = new LoadedMaterial();
					lm.materialID = resourceDependency.id;
					lm.bitmapData = isBitmapDataValid(asset.bitmapData)? asset.bitmapData : defaultBitmapData ;
 
					_materialLoaded.push(lm);
 
					if(_meshes.length>0)
						applyMaterial(lm);
				}
			}
		}
 
		/**
		* @inheritDoc
		*/
		override arcane function resolveDependencyFailure(resourceDependency:ResourceDependency):void
		{
			var lm:LoadedMaterial = new LoadedMaterial();
			lm.materialID = resourceDependency.id;
			lm.bitmapData = defaultBitmapData;
 
			_materialLoaded.push(lm);
 
			if(_meshes.length>0)
				applyMaterial(lm);
		}
 
		public function trimJSON( s:String ):String
		{
        	//trim the fat and make the JS validateable JSON
 
			var myPattern:RegExp = /var model = /gi;
			s = s.replace(myPattern, '{"model":');
			myPattern = /postMessage\( model \);/gi;
			s = s.replace(myPattern, '');
			myPattern = /close\(\);/gi;
			s = s.replace(myPattern, '');
			myPattern = /};/gi;
			s = s.replace(myPattern, '}}');
			return s.replace( /^([\s|\t|\n]+)?(.*)([\s|\t|\n]+)?$/gm, "$2" );
		}
 
 
		/**
		* @inheritDoc
		*/
		override protected function proceedParsing() : Boolean
		{
			var line:String;
			var creturn:String = String.fromCharCode(10);
			var trunk:Array;
 
			if(_textData.indexOf(creturn) == -1)
				creturn = String.fromCharCode(13);
 
			if(!_startedParsing){
				_startedParsing = true;
				_vertices = new Vector.<Vertex>();
				_vertexNormals = new Vector.<Vertex>();
				_materialIDs = new Vector.<String>();
				_materialLoaded = new Vector.<LoadedMaterial>();
				_meshes = new Vector.<Mesh>();
				_uvs = new Vector.<UV>();
				jsonOBJ = JSON.parse(trimJSON(_textData));
				_objects = new Vector.<ObjectGroup>();
				_objectIndex = 0;
			}
 
			createObject();
			createGroup();
			parseFace();
			parseVertex();
			parseUV() ;
			parseVertexNormal();
			translate();
			applyMaterials();
			return PARSING_DONE;
		}
 
 
		/**
		* Converts the parsed data into an Away3D scenegraph structure
		*/
		private function translate() :void
		{
			var groups : Vector.<Group> = _objects[_objectIndex].groups;
			var numGroups : uint = groups.length;
			var materialGroups : Vector.<MaterialGroup>;
			var numMaterialGroups : uint;
			var geometry : Geometry;
			var mesh : Mesh;
			var meshid:uint;
 
			var m : uint;
			var sm : uint;
			var bmMaterial:BitmapMaterial;
 
			for (var g : uint = 0; g < numGroups; ++g) {
				geometry = new Geometry();
				materialGroups = groups[g].materialGroups;
				numMaterialGroups = materialGroups.length;
				for (m = 0; m < numMaterialGroups; ++m) {
					translateMaterialGroup(materialGroups[m], geometry);
				}
				bmMaterial = new BitmapMaterial(defaultBitmapData);
				mesh = new Mesh(bmMaterial, geometry);
				meshid = _meshes.length;
				mesh.name = "jsonObj"+meshid;
				_meshes[meshid] = mesh;
 
				if(groups[g].materialID != ""){
					bmMaterial.name = groups[g].materialID+"~"+mesh.name;
				} else {
					bmMaterial.name = _lastMtlID+"~"+mesh.name;
				}
 
				if(mesh.subMeshes.length >1){
					for (sm = 1; sm<mesh.subMeshes.length; ++sm)
						mesh.subMeshes[sm].material = bmMaterial;
				}
 
				finalizeAsset(mesh);
			}
		}
 
		/* If no uv's are found (often seen case with obj format) parser generates a new set of default uv's */
		private function addDefaultUVs(vertices : Vector.<Number>, uvs: Vector.<Number>) :Vector.<Number>
		{
			var j:uint = 0;
			for (var i :uint = 0; i < vertices.length; i+=3){
				if(j == 0){
					uvs.push(0, 1);
				} else if(j == 1){
					uvs.push(.5, 0);
				} else{
					uvs.push(1, 1);
				}
 
				j = (j+1>2)? 0 : j++;
			}
 
			return uvs;
		}
 
		/**
		 * Translates an obj's material group to a subgeometry.
		 * @param materialGroup The material group data to convert.
		 * @param geometry The Geometry to contain the converted SubGeometry.
		 */
		private function translateMaterialGroup(materialGroup : MaterialGroup, geometry : Geometry) : void
		{
			var faces : Vector.<FaceData> = materialGroup.faces;
			var face : FaceData;
			var numFaces : uint = faces.length;
			var numVerts : uint;
 
			var vertices:Vector.<Number> = new Vector.<Number>();
			var uvs:Vector.<Number> = new Vector.<Number>();
			var normals:Vector.<Number> = new Vector.<Number>();
			var indices:Vector.<uint> = new Vector.<uint>();
 
			_realIndices = [];
			_vertexIndex = 0;
 
			var j:uint;
			for (var i : uint = 0; i < numFaces; ++i) {
				face = faces[i];
				numVerts = face.indexIds.length - 1;
				for (j = 1; j < numVerts; ++j) {
					translateVertexData(face, j, vertices, uvs, indices, normals);
					translateVertexData(face, 0, vertices, uvs, indices, normals);
					translateVertexData(face, j+1, vertices, uvs, indices, normals);
				}
			}
 
			var vlength:uint = vertices.length;
 
			if(vlength > 0){
 
				if(vlength <= LIMIT){
 
					buildSubGeometry(geometry, vertices, uvs, indices, normals);
 
				} else {
 
					var nvertices:Vector.<Number> = new Vector.<Number>();
					var nuvs:Vector.<Number> = new Vector.<Number>();
					var nnormals:Vector.<Number> = new Vector.<Number>();
					var nindices:Vector.<uint> = new Vector.<uint>();
 
					var ind:uint;
					var vind:uint;
					var uvind:uint;
 
					vlength = 0;
 
					for (i = 0; i < indices.length; ++i) {
 
						if(vlength+3 > LIMIT){
							vlength = 0;
							buildSubGeometry(geometry, nvertices, nuvs, nindices, nnormals);
							nvertices = new Vector.<Number>();
							nuvs = new Vector.<Number>();
							nnormals = new Vector.<Number>();
							nindices = new Vector.<uint>();
						}
 
						ind = indices[i];
						vind = ind*3;
						uvind = ind*2;
						nindices.push(nvertices.length/3);
						nvertices.push(vertices[vind], vertices[vind+1], vertices[vind+2]);
						try{
						nuvs.push(uvs[uvind], uvs[uvind+1]);
						}catch(e:Error){
							trace('no uv data');
						}
						if(normals[vind]) nnormals.push(normals[vind], normals[vind+1], normals[vind+2]);
 
						vlength+=3;
 
 
					}
 
					buildSubGeometry(geometry, nvertices, nuvs, nindices, nnormals);
 
				}
			}
		}
 
		private function buildSubGeometry(geometry:Geometry, vertices:Vector.<Number>, uvs:Vector.<Number>, indices:Vector.<uint>, normals:Vector.<Number>):void
		{
			if(vertices.length == 0) return;
 
			var subGeom : SubGeometry = new SubGeometry();
			subGeom.autoDeriveVertexTangents = true;
 
			if(uvs.length == 0 && vertices.length > 0)
				uvs = addDefaultUVs(vertices, uvs);
 
			subGeom.updateVertexData(vertices);
			subGeom.updateIndexData(indices);
			subGeom.updateUVData(uvs);
 
			var deriveVN:Boolean = normals.length>0? true :false;
			subGeom.autoDeriveVertexNormals = deriveVN;
 
			if(deriveVN) subGeom.updateVertexNormalData(normals);
 
			geometry.addSubGeometry(subGeom);
		}
 
		private function translateVertexData(face : FaceData, vertexIndex : int, vertices:Vector.<Number>, uvs:Vector.<Number>, indices:Vector.<uint>, normals:Vector.<Number>) : void
		{
			var index : uint;
			var vertex : Vertex;
			var vertexNormal : Vertex;
			var uv : UV;
 
			if (!_realIndices[face.indexIds[vertexIndex]]) {
				index = _vertexIndex;
				_realIndices[face.indexIds[vertexIndex]] = ++_vertexIndex;
				vertex = _vertices[face.vertexIndices[vertexIndex]-1];
				vertices.push(vertex.x * _scale, vertex.y * _scale, vertex.z * _scale);
				if (face.normalIndices.length > 0) {
					try{
					vertexNormal = _vertexNormals[face.normalIndices[vertexIndex]-1];
					normals.push(vertexNormal.x, vertexNormal.y, vertexNormal.z);
					}catch(e:Error){
						trace(e);
					}
				}
 
				if (face.uvIndices.length > 0 ){
 
					try {
						uv = _uvs[face.uvIndices[vertexIndex]-1];
						uvs.push(uv.u, uv.v);
 
					} catch(e:Error) {
 
						switch(vertexIndex){
							case 0:
								uvs.push(0, 1);
								break;
							case 1:
								uvs.push(.5, 0);
								break;
							case 2:
								uvs.push(1, 1);
						}
					}
 
				}
 
			} else {
				index = _realIndices[face.indexIds[vertexIndex]] - 1;
			}
			indices.push(index);
		}
 
 
		/**
		 * Creates a new object group.
		 * @param trunk The data block containing the object tag and its parameters
		 */
		private function createObject() : void
		{
			_currentGroup = null;
			_currentMaterialGroup = null;
			_objects.push(_currentObject = new ObjectGroup());
			_currentObject.name = 'g0';
			//if (trunk) _currentObject.name = trunk[1];
		}
 
 
		/**
		 * Creates a new group.
		 * @param trunk The data block containing the group tag and its parameters
		 */
		private function createGroup() : void
		{
			//if (!_currentObject) createObject(null);
			_currentGroup = new Group();
 
			_currentGroup.materialID = '';
			_currentGroup.name = 'g0';
 
			//if (trunk) _currentGroup.name = trunk[1];
			_currentObject.groups.push(_currentGroup);
 
			createMaterialGroup(null);
		}
 
		/**
		 * Creates a new material group.
		 * @param trunk The data block containing the material tag and its parameters
		 */
		private function createMaterialGroup(trunk : Array) : void
		{
			_currentMaterialGroup = new MaterialGroup();
			//if (trunk) _currentMaterialGroup.url = trunk[1];
			_currentGroup.materialGroups.push(_currentMaterialGroup);
		}
 
		/**
		 * Reads the next vertex coordinates.
		 * @param trunk The data block containing the vertex tag and its parameters
		 */
		private function parseVertex() : void
		{
			var currentVert:uint =0;
			for(var i:uint = 0; i < jsonOBJ.model.vertices.length; i++){
			 if(currentVert == i){
				 _vertices.push(new Vertex(parseFloat(jsonOBJ.model.vertices[i]), parseFloat(jsonOBJ.model.vertices[i+1]), -parseFloat(jsonOBJ.model.vertices[i+2])));
				 currentVert = i+3;
			 }
			}
		}
 
		/**
		 * Reads the next uv coordinates.
		 * @param trunk The data block containing the uv tag and its parameters
		 */
		private function parseUV() : void
		{
			var currentVert:uint =0;
 
			for(var i:uint = 0; i < jsonOBJ.model.uvs[0].length; i++){
				if(currentVert == i){
					_uvs.push(new UV(parseFloat(jsonOBJ.model.uvs[0][i]),parseFloat(jsonOBJ.model.uvs[0][i+1])));
					currentVert = i+2;
				}
			}
		}
 
		/**
		 * Reads the next vertex normal coordinates.
		 * @param trunk The data block containing the vertex normal tag and its parameters
		 */
		private function parseVertexNormal() : void
		{
			var currentVert:uint =0;
			for(var i:uint = 0; i < jsonOBJ.model.normals.length; i++){
				if(currentVert == i){
					_vertexNormals.push(new Vertex(parseFloat(jsonOBJ.model.normals[i]), parseFloat(jsonOBJ.model.normals[i+1]), -parseFloat(jsonOBJ.model.normals[i+2])));
					currentVert = i+3;
				}
			}
		}
 
		/**
		 * Reads the next face's indices.
		 * @param trunk The data block containing the face tag and its parameters
		 */
		private function parseFace() : void
		{	
			var currentVert:uint =1;
			for(var i:uint = 0; i < jsonOBJ.model.faces.length; i++){
				var id:Number = jsonOBJ.model.faces[0];
				var face : FaceData = new FaceData();
				var num1:int;
				var num2:int;
				var num3:int;
 
				var num4:int;
				var num5:int;
				var num6:int;
 
				var num7:int;
				var num8:int;
				var num9:int;
				if(id === jsonOBJ.model.faces[11]){
				if(currentVert == i){
					num1 = parseInt(jsonOBJ.model.faces[i])+1;
					num2 = parseInt(jsonOBJ.model.faces[i+1])+1;
					num3 = parseInt(jsonOBJ.model.faces[i+2])+1;
 
					num4 = parseInt(jsonOBJ.model.faces[i+4])+1;
					num5 = parseInt(jsonOBJ.model.faces[i+5])+1;
					num6 = parseInt(jsonOBJ.model.faces[i+6])+1;
 
					num7 = parseInt(jsonOBJ.model.faces[i+7])+1;
					num8 = parseInt(jsonOBJ.model.faces[i+8])+1;
					num9 = parseInt(jsonOBJ.model.faces[i+9])+1;
 
					face.vertexIndices.push(num1, num2,num3);
					face.uvIndices.push(num4,num5,num6)
					face.normalIndices.push(num7, num8, num9)
					face.indexIds.push(num1+'/'+num4+'/'+num7,num2+'/'+num5+'/'+num8,num3+'/'+num6+'/'+num9);
					_currentMaterialGroup.faces.push(face);
					currentVert = i+11;
				}
				}else{
					isQuad = true;
					// converts Quad data to triangles //
					if(currentVert == i){
					num1 = parseInt(jsonOBJ.model.faces[i])+1;
					num2 = parseInt(jsonOBJ.model.faces[i+1])+1;
					num3 = parseInt(jsonOBJ.model.faces[i+2])+1;
					num4 = parseInt(jsonOBJ.model.faces[i+3])+1;
 
					num5 = parseInt(jsonOBJ.model.faces[i+5])+1;
					num6 = parseInt(jsonOBJ.model.faces[i+6])+1;
					num7 = parseInt(jsonOBJ.model.faces[i+7])+1;
					num8 = parseInt(jsonOBJ.model.faces[i+8])+1;
 
					face.vertexIndices.push(num1, num2,num3);
					face.vertexIndices.push(num1, num3,num4);
					face.normalIndices.push(num5, num6, num7);
					face.normalIndices.push(num5, num7, num8);
 
					face.indexIds.push(num1+'/'+'/'+num5,num2+'/'+'/'+num6,num3+'/'+'/'+num7);
					face.indexIds.push(num1+'/'+'/'+num5,num3+'/'+'/'+num7,num4+'/'+'/'+num8);
 
					_currentMaterialGroup.faces.push(face);
					currentVert = i+10;
					}
				}
			}
 
		}
 
		private function parseMtl(data : String):void
		{
			var materialDefinitions:Array = data.split('newmtl');
			var lines:Array;
			var trunk:Array;
			var j:uint;
 
			var basicSpecularMethod:BasicSpecularMethod;
			var useSpecular:Boolean;
			var useColor:Boolean;
			var diffuseColor:uint;
			var ambientColor:uint;
			var specularColor:uint;
			var specular:Number;
			var alpha:Number;
			var mapkd:String;
 
			for(var i:uint = 0;i< materialDefinitions.length;++i){
 
				lines = materialDefinitions[i].split('\r').join("").split('\n');
 
				if(lines.length == 1)
					lines = materialDefinitions[i].split(String.fromCharCode(13));
 
				diffuseColor = ambientColor = specularColor = 0xFFFFFF;
				specular = 0;
				useSpecular = false;
				useColor = false;
				alpha = 1;
				mapkd = "";
 
				for(j = 0;j< lines.length;++j){
					lines[j] = lines[j].replace(/\s+$/,"");
 
					if(lines[j].substring(0,1) != "#" && lines[j] != ""){
						trunk = lines[j].split(" ");
 
						if(String(trunk[0]).charCodeAt(0) == 9 || String(trunk[0]).charCodeAt(0) == 32)
							trunk[0] = trunk[0].substring(1, trunk[0].length);
 
						if(j == 0){
 
							_lastMtlID = trunk.join("");
 
						} else {
 
							switch (trunk[0]) {
 
								case "Ka":
									if(trunk[1] && !isNaN(Number(trunk[1])) && trunk[2] && !isNaN(Number(trunk[2])) && trunk[3] && !isNaN(Number(trunk[3])))
										ambientColor = trunk[1]*255 < < 16 | trunk[2]*255 < < 8 | trunk[3] *255;
									break;
 
								case "Ks":
									if(trunk[1] && !isNaN(Number(trunk[1])) && trunk[2] && !isNaN(Number(trunk[2])) && trunk[3] && !isNaN(Number(trunk[3]))){
										specularColor = trunk[1]*255 < < 16 | trunk[2]*255 < < 8 | trunk[3]*255;
										useSpecular = true;
									}
									break;
 
								case "Ns":
									if(trunk[1] && !isNaN(Number(trunk[1]))) specular = Number(trunk[1]) * 0.001;
									if(specular == 0) useSpecular = false;
									break;
 
								case "Kd":
									if(trunk[1] && !isNaN(Number(trunk[1])) && trunk[2] && !isNaN(Number(trunk[2])) && trunk[3] && !isNaN(Number(trunk[3]))){
										diffuseColor = trunk[1]*255 < < 16 | trunk[2]*255 < < 8 | trunk[3]*255;
										useColor = true;
									}
									break;
 
								case "tr":
								case "d":
									if(trunk[1] && !isNaN(Number(trunk[1]))) alpha = Number(trunk[1]);
									break;
 
								case "map_Kd":
									mapkd = parseMapKdString(trunk);
									mapkd = mapkd.replace(/\\/g, "/");
							}
						}
					}
				}
 
				if(mapkd != ""){
 
					if(useSpecular){
 
						basicSpecularMethod = new BasicSpecularMethod();
						basicSpecularMethod.specularColor = specularColor;
						basicSpecularMethod.specular = specular;
 
						var specularData:SpecularData = new SpecularData();
						specularData.basicSpecularMethod = basicSpecularMethod;
						specularData.materialID = _lastMtlID;
 
						if(! _materialSpecularData)
							_materialSpecularData  = new Vector.<SpecularData>();
 
						_materialSpecularData.push(specularData);
					}
 
					_dependencies.push(new ResourceDependency(_lastMtlID, new URLRequest(mapkd), null , this));
 
 
				} else if(useColor && !isNaN(diffuseColor)){
 
					var lm:LoadedMaterial = new LoadedMaterial();
					lm.materialID = _lastMtlID;
 
					if(alpha == 0)
						trace("Warning: an alpha value of 0 was found in mtl color tag (Tr or d) ref:"+_lastMtlID+", mesh(es) using it will be invisible!");
 
					lm.bitmapData = new BitmapData(256, 256, (alpha == 1)? false : true, diffuseColor); 
					lm.ambientColor = ambientColor;
 
					if(useSpecular){
						basicSpecularMethod = new BasicSpecularMethod();
						basicSpecularMethod.specularColor = specularColor;
						basicSpecularMethod.specular = specular;
						lm.specularMethod = basicSpecularMethod;
					}
 
					_materialLoaded.push(lm);
 
					if(_meshes.length>0)
						applyMaterial(lm);
 
				}
			}
 
			_mtlLibLoaded = true;
		}
 
		private function parseMapKdString(trunk:Array):String
		{
			var url:String = "";
			var i:int;
			var breakflag:Boolean;
 
			for(i = 1; i < trunk.length;) {
				switch(trunk[i]) {
					case "-blendu" :
					case "-blendv" :
					case "-cc" :
					case "-clamp" :
					case "-texres" :
						i += 2;		//Skip ahead 1 attribute
						break;
					case "-mm" :
						i += 3;		//Skip ahead 2 attributes
						break;
					case "-o" :
					case "-s" :
					case "-t" :
						i += 4;		//Skip ahead 3 attributes
						continue;
					default :
						breakflag = true;
						break;
				}
 
				if(breakflag)
					break;
			}
 
			//Reconstruct URL/filename
			for(i; i < trunk.length; i++) {
				url += trunk[i];
				url += " ";
			}
 
			//Remove the extraneous space and/or newline from the right side
			url = url.replace(/\s+$/,"");
 
			return url;
		}
 
		private function loadMtl(mtlurl:String):void
		{
			_dependencies.push( new ResourceDependency('mtl', new URLRequest(mtlurl), null, this, true));
			pauseAndRetrieveDependencies();
		}
 
		private function applyMaterial(lm:LoadedMaterial) : void
		{
			var meshID:String;
			var decomposeID:Array;
			var mesh:Mesh;
			var mat:BitmapMaterial;
			var j:uint;
			var specularData:SpecularData;
 
			for(var i:uint = 0; i < _meshes.length;++i){
				mesh = _meshes[i];
				decomposeID = mesh.material.name.split("~");
 
				if(decomposeID[0] == lm.materialID){
					mesh.material.name = decomposeID[1];
					mat = BitmapMaterial(mesh.material);
					mat.bitmapData = lm.bitmapData;
					mat.ambientColor = lm.ambientColor;
 
					if(lm.specularMethod){
						mat.specularMethod = lm.specularMethod;
					} else if(_materialSpecularData){
						for(j = 0;j< _materialSpecularData.length;++j){
							specularData = _materialSpecularData[j];
							if(specularData.materialID == lm.materialID){
								mat.specularMethod = specularData.basicSpecularMethod;
								mat.ambientColor = specularData.ambientColor;
								_materialSpecularData.splice(j,1);
								break;
							}
						}
					}
 
					_meshes.splice(i, 1);
					--i;
				}
			}
		}
 
		private function applyMaterials() : void
		{
			if(_materialLoaded.length == 0)
				return;
 
			for(var i:uint = 0; i < _materialLoaded.length;++i)
				applyMaterial(_materialLoaded[i]);
		}
 
	}
}
 
// value objects:
class ObjectGroup
{
	public var name : String;
	public var groups : Vector.<Group> = new Vector.<Group>();
}
 
class Group
{
	public var name : String;
	public var materialID : String;
	public var materialGroups : Vector.<MaterialGroup> = new Vector.<MaterialGroup>();
}
 
class MaterialGroup
{
	public var url : String;
	public var faces : Vector.<FaceData> = new Vector.<FaceData>();
}
 
class SpecularData
{
	import away3d.materials.methods.BasicSpecularMethod;
 
	public var materialID : String;
	public var basicSpecularMethod : BasicSpecularMethod;
	public var ambientColor:uint = 0xFFFFFF;
}
 
class LoadedMaterial
{
	import flash.display.BitmapData;
	import away3d.materials.methods.BasicSpecularMethod;
 
	public var materialID:String;
	public var bitmapData:BitmapData;
 
	public var specularMethod:BasicSpecularMethod;
	public var ambientColor:uint = 0xFFFFFF;
}
 
class FaceData
{
	public var vertexIndices : Vector.<uint> = new Vector.<uint>();
	public var uvIndices : Vector.<uint> = new Vector.<uint>();
	public var normalIndices : Vector.<uint> = new Vector.<uint>();
	public var indexIds : Vector.<String> = new Vector.<String>();	// used for real index lookups
}

This demo requires FlashPlayer 11 stage3D
check out the demo

Download the class and Modified Broomstick Code by clicking here.

ToDo list:

  • Support Materials
  • Support Multiple Shader Groups
  • Support Binary Data
  • Morphing Targets
  • Better Error Handeling

So to create this JSONOBJ class I obviously debugged the OBJ parser and borrowed a great deal of the functionality that was already built in the OBJ parser. I will still have to add materials which I don’t believe will be difficult so I have left the OBJ code in this class it will be re-factored later to work with the JSONOBJ converter. I will also need to explorer the binary format and the Morph targets in the future. I hope the community will find this useful!

Another thing I like about Away3D and three.js is the ability to use JigLib and I will have a demo out soon the shows utilizing JigLib in Away3D and in three.js

Check out JigLib AS3 / JS

To see this demo you will have to have FP11 / Stage3D formerly Molehill installed on your machine. Here is how to do that.

What is threeJS here is a good video that discuss the creation of the ROME project that utilizes threeJS