package away3d.loaders { import away3d.containers.*; import away3d.arcane; import away3d.core.base.*; import away3d.core.utils.*; import away3d.loaders.data.*; import away3d.loaders.utils.*; import away3d.materials.*; import flash.utils.ByteArray; import flash.utils.Endian; use namespace arcane; /** * File loader for the 3DS file format. */ public class Max3DS extends AbstractParser { /** @private */ arcane var ini:Init; /** An array of bytes from the 3ds files. */ private var _data:ByteArray; private var _materialData:MaterialData; private var _meshData:MeshData; private var _geometryData:GeometryData; private var _faceData:FaceData; private var averageX:Number; private var averageY:Number; private var averageZ:Number; private var numVertices:int; private var _meshMaterialData:MeshMaterialData; private var _faceListIndex:int; private var _face:Face; private var _vertex:Vertex; private var _totalChunks:int = 0; private var _parsedChunks:int = 0; //>----- Color Types -------------------------------------------------------- private const AMBIENT:String = "ambient"; private const DIFFUSE:String = "diffuse"; private const SPECULAR:String = "specular"; //>----- Main Chunks -------------------------------------------------------- private const PRIMARY:int = 0x4D4D; private const EDIT3DS:int = 0x3D3D; // Start of our actual objects private const KEYF3DS:int = 0xB000; // Start of the keyframe information //>----- General Chunks ----------------------------------------------------- private const VERSION:int = 0x0002; private const MESH_VERSION:int = 0x3D3E; private const KFVERSION:int = 0x0005; private const COLOR_F:int = 0x0010; private const COLOR_RGB:int = 0x0011; private const LIN_COLOR_24:int = 0x0012; private const LIN_COLOR_F:int = 0x0013; private const INT_PERCENTAGE:int = 0x0030; private const FLOAT_PERC:int = 0x0031; private const MASTER_SCALE:int = 0x0100; private const IMAGE_FILE:int = 0x1100; private const AMBIENT_LIGHT:int = 0X2100; //>----- Object Chunks ----------------------------------------------------- private const MESH:int = 0x4000; private const MESH_OBJECT:int = 0x4100; private const MESH_VERTICES:int = 0x4110; private const VERTEX_FLAGS:int = 0x4111; private const MESH_FACES:int = 0x4120; private const MESH_MATER:int = 0x4130; private const MESH_TEX_VERT:int = 0x4140; private const MESH_XFMATRIX:int = 0x4160; private const MESH_COLOR_IND:int = 0x4165; private const MESH_TEX_INFO:int = 0x4170; private const HEIRARCHY:int = 0x4F00; //>----- Material Chunks --------------------------------------------------- private const MATERIAL:int = 0xAFFF; private const MAT_NAME:int = 0xA000; private const MAT_AMBIENT:int = 0xA010; private const MAT_DIFFUSE:int = 0xA020; private const MAT_SPECULAR:int = 0xA030; private const MAT_SHININESS:int = 0xA040; private const MAT_FALLOFF:int = 0xA052; private const MAT_EMISSIVE:int = 0xA080; private const MAT_SHADER:int = 0xA100; private const MAT_TEXMAP:int = 0xA200; private const MAT_TEXFLNM:int = 0xA300; private const OBJ_LIGHT:int = 0x4600; private const OBJ_CAMERA:int = 0x4700; //>----- KeyFrames Chunks -------------------------------------------------- private const ANIM_HEADER:int = 0xB00A; private const ANIM_OBJ:int = 0xB002; private const ANIM_NAME:int = 0xB010; private const ANIM_POS:int = 0xB020; private const ANIM_ROT:int = 0xB021; private const ANIM_SCALE:int = 0xB022; private var texturePath:String; private var autoLoadTextures:Boolean; /** * Reference container for all materials used in the 3ds object. */ public var materialLibrary:MaterialLibrary = new MaterialLibrary(); public var animationLibrary:AnimationLibrary; public var geometryLibrary:GeometryLibrary; /** * Array of mesh data objects used for storing the parsed 3ds data structure. */ public var meshDataList:Array = []; /** * In the 3ds file only the file names of texture files are given. * If the textures are stored in a specific path, that path can be * specified through the constructor. */ private var centerMeshes:Boolean; private var material:ITriangleMaterial; private var _maxX:Number; private var _minX:Number; private var _maxY:Number; private var _minY:Number; private var _maxZ:Number; private var _minZ:Number; /** * Read id and length of 3ds chunk * * @param chunk * */ private function readChunk(chunk:Chunk3ds):void { chunk.id = _data.readUnsignedShort(); chunk.length = _data.readUnsignedInt(); chunk.bytesRead = 6; } /** * Skips past a chunk. If we don't understand the meaning of a chunk id, * we just skip past it. * * @param chunk * */ private function skipChunk(chunk:Chunk3ds):void { _data.position += chunk.length - chunk.bytesRead; chunk.bytesRead = chunk.length; } /** * Read the base 3DS object. * * @param chunk * */ private function parse3DS(chunk:Chunk3ds):void { while (chunk.bytesRead < chunk.length) { var subChunk:Chunk3ds = new Chunk3ds(); readChunk(subChunk); switch (subChunk.id) { case EDIT3DS: parseEdit3DS(subChunk); break; case KEYF3DS: skipChunk(subChunk); break; default: skipChunk(subChunk); } chunk.bytesRead += subChunk.length; } } /** * Read the Edit chunk * * @param chunk * */ private function parseEdit3DS(chunk:Chunk3ds):void { while (chunk.bytesRead < chunk.length) { var subChunk:Chunk3ds = new Chunk3ds(); readChunk(subChunk); switch (subChunk.id) { case MATERIAL: parseMaterial(subChunk); break; case MESH: _meshData = new MeshData(); readMeshName(subChunk); _meshData.geometry = geometryLibrary.addGeometry(_meshData.name); parseMesh(subChunk); meshDataList.push(_meshData); break; default: skipChunk(subChunk); } chunk.bytesRead += subChunk.length; } } private function parseMaterial(chunk:Chunk3ds):void { while (chunk.bytesRead < chunk.length) { var subChunk:Chunk3ds = new Chunk3ds(); readChunk(subChunk); switch (subChunk.id) { case MAT_NAME: readMaterialName(subChunk); break; case MAT_AMBIENT: readColor(AMBIENT); break; case MAT_DIFFUSE: readColor(DIFFUSE); break; case MAT_SPECULAR: readColor(SPECULAR); break; case MAT_TEXMAP: parseMaterial(subChunk); break; case MAT_TEXFLNM: readTextureFileName(subChunk); break; default: skipChunk(subChunk); } chunk.bytesRead += subChunk.length; } } private function readMaterialName(chunk:Chunk3ds):void { _materialData = materialLibrary.addMaterial(readASCIIZString(_data)); chunk.bytesRead = chunk.length; } private function readColor(type:String):void { _materialData.materialType = MaterialData.SHADING_MATERIAL; var color:int; var chunk:Chunk3ds = new Chunk3ds(); readChunk(chunk); switch (chunk.id) { case COLOR_RGB: color = readColorRGB(chunk); break; case COLOR_F: // TODO: write implentation code trace("COLOR_F not implemented yet"); skipChunk(chunk); break; default: skipChunk(chunk); trace("unknown ambient color format"); } switch (type) { case AMBIENT: _materialData.ambientColor = color; break; case DIFFUSE: _materialData.diffuseColor = color; break; case SPECULAR: _materialData.specularColor = color; break; } } private function readColorRGB(chunk:Chunk3ds):int { var color:int = 0; for (var i:int = 0; i < 3; ++i) { var c:int = _data.readUnsignedByte(); color += c*Math.pow(0x100, 2-i); chunk.bytesRead++; } return color; } private function readTextureFileName(chunk:Chunk3ds):void { _materialData.textureFileName = readASCIIZString(_data); _materialData.materialType = MaterialData.TEXTURE_MATERIAL; chunk.bytesRead = chunk.length; } private function parseMesh(chunk:Chunk3ds):void { while (chunk.bytesRead < chunk.length) { var subChunk:Chunk3ds = new Chunk3ds(); readChunk(subChunk); switch (subChunk.id) { case MESH_OBJECT: parseMesh(subChunk); break; case MESH_VERTICES: readMeshVertices(subChunk); break; case MESH_FACES: readMeshFaces(subChunk); parseMesh(subChunk); break; case MESH_MATER: readMeshMaterial(subChunk); break; case MESH_TEX_VERT: readMeshTexVert(subChunk); break; default: skipChunk(subChunk); } chunk.bytesRead += subChunk.length; } } private function readMeshName(chunk:Chunk3ds):void { _meshData.name = readASCIIZString(_data); chunk.bytesRead += _meshData.name.length + 1; } private function readMeshVertices(chunk:Chunk3ds):void { var numVerts:int = _data.readUnsignedShort(); chunk.bytesRead += 2; for (var i:int = 0; i < numVerts; ++i) { _meshData.geometry.vertices.push(new Vertex(-_data.readFloat(), _data.readFloat(), _data.readFloat())); chunk.bytesRead += 12; } } private function readMeshFaces(chunk:Chunk3ds):void { var numFaces:int = _data.readUnsignedShort(); chunk.bytesRead += 2; for (var i:int = 0; i < numFaces; ++i) { _faceData = new FaceData(); _faceData.v0 = _data.readUnsignedShort(); _faceData.v1 = _data.readUnsignedShort(); _faceData.v2 = _data.readUnsignedShort(); _faceData.visible = (_data.readUnsignedShort() as Boolean); chunk.bytesRead += 8; _meshData.geometry.faces.push(_faceData); } } /** * Read the Mesh Material chunk * * @param chunk * */ private function readMeshMaterial(chunk:Chunk3ds):void { var meshMaterial:MeshMaterialData = new MeshMaterialData(); meshMaterial.symbol = readASCIIZString(_data); chunk.bytesRead += meshMaterial.symbol.length +1; var numFaces:int = _data.readUnsignedShort(); chunk.bytesRead += 2; for (var i:int = 0; i < numFaces; ++i) { meshMaterial.faceList.push(_data.readUnsignedShort()); chunk.bytesRead += 2; } _meshData.geometry.materials.push(meshMaterial); } private function readMeshTexVert(chunk:Chunk3ds):void { var numUVs:int = _data.readUnsignedShort(); chunk.bytesRead += 2; for (var i:int = 0; i < numUVs; ++i) { _meshData.geometry.uvs.push(new UV(_data.readFloat(), _data.readFloat())); chunk.bytesRead += 8; } } /** * Reads a null-terminated ascii string out of a byte array. * * @param data The byte array to read from. * @return The string read, without the null-terminating character. * */ private function readASCIIZString(data:ByteArray):String { //var readLength:int = 0; // length of string to read var l:int = data.length - data.position; var tempByteArray:ByteArray = new ByteArray(); for (var i:int = 0; i < l; ++i) { var c:int = data.readByte(); if (c == 0) { break; } tempByteArray.writeByte(c); } var asciiz:String = ""; tempByteArray.position = 0; for (i = 0; i < tempByteArray.length; ++i) { asciiz += String.fromCharCode(tempByteArray.readByte()); } return asciiz; } private function buildMeshes():void { for each (_meshData in meshDataList) { //create Mesh object var mesh:Mesh = new Mesh({name:_meshData.name}); _geometryData = _meshData.geometry; var geometry:Geometry = _geometryData.geometry; if (!geometry) { geometry = _geometryData.geometry = new Geometry(); mesh.geometry = geometry; //set materialdata for each face for each (_meshMaterialData in _geometryData.materials) { for each (_faceListIndex in _meshMaterialData.faceList) { _faceData = _geometryData.faces[_faceListIndex] as FaceData; _faceData.materialData = materialLibrary[_meshMaterialData.symbol]; } } for each(_faceData in _geometryData.faces) { _face = new Face(_geometryData.vertices[_faceData.v0], _geometryData.vertices[_faceData.v1], _geometryData.vertices[_faceData.v2], _faceData.materialData.material as ITriangleMaterial, _geometryData.uvs[_faceData.v0], _geometryData.uvs[_faceData.v1], _geometryData.uvs[_faceData.v2]); geometry.addFace(_face); _faceData.materialData.elements.push(_face); } } else { mesh.geometry = geometry; } if (centerMeshes) { //center vertex points in mesh for better bounding radius calulations _maxX = -Infinity; _minX = Infinity; _maxY = -Infinity; _minY = Infinity; _maxZ = -Infinity; _minZ = Infinity; for each (_vertex in mesh.geometry.vertices) { if (_maxX < _vertex._x) _maxX = _vertex._x; if (_minX > _vertex._x) _minX = _vertex._x; if (_maxY < _vertex._y) _maxY = _vertex._y; if (_minY > _vertex._y) _minY = _vertex._y; if (_maxZ < _vertex._z) _maxZ = _vertex._z; if (_minZ > _vertex._z) _minZ = _vertex._z; } mesh.movePivot((_maxX + _minX)*.5, (_maxY + _minY)*.5, (_maxZ + _minZ)*.5); mesh.moveTo((_maxX + _minX)*.5, (_maxY + _minY)*.5, (_maxZ + _minZ)*.5); } mesh.type = ".3ds"; (container as ObjectContainer3D).addChild(mesh); } } private function buildMaterials():void { for each (_materialData in materialLibrary) { //overridden by the material property in constructor if (material) _materialData.material = material; //overridden by materials passed in contructor if (_materialData.material) continue; switch (_materialData.materialType) { case MaterialData.TEXTURE_MATERIAL: materialLibrary.loadRequired = true; break; case MaterialData.SHADING_MATERIAL: _materialData.material = new ShadingColorMaterial({ambient:_materialData.ambientColor, diffuse:_materialData.diffuseColor, specular:_materialData.specularColor}); break; case MaterialData.WIREFRAME_MATERIAL: _materialData.material = new WireColorMaterial(); break; } } } /** * Creates a new Max3DS object. Not intended for direct use, use the static parse or load methods. * * @param data The binary data of a loaded file. * @param init [optional] An initialisation object for specifying default instance properties. * * @see away3d.loaders.Max3DS#parse() * @see away3d.loaders.Max3DS#load() */ public function Max3DS(data:*, init:Object = null) { _data = Cast.bytearray(data); _data.endian = Endian.LITTLE_ENDIAN; ini = Init.parse(init); texturePath = ini.getString("texturePath", ""); autoLoadTextures = ini.getBoolean("autoLoadTextures", true); material = ini.getMaterial("material"); centerMeshes = ini.getBoolean("centerMeshes", false); var materials:Object = ini.getObject("materials") || {}; for (var name:String in materials) { _materialData = materialLibrary.addMaterial(name); _materialData.material = Cast.material(materials[name]); //determine material type if (_materialData.material is BitmapMaterial) _materialData.materialType = MaterialData.TEXTURE_MATERIAL; else if (_materialData.material is ShadingColorMaterial) _materialData.materialType = MaterialData.SHADING_MATERIAL; else if (_materialData.material is WireframeMaterial) _materialData.materialType = MaterialData.WIREFRAME_MATERIAL; } container = new ObjectContainer3D(ini); container.name = "max3ds"; materialLibrary = container.materialLibrary = new MaterialLibrary(); animationLibrary = container.animationLibrary = new AnimationLibrary(); geometryLibrary = container.geometryLibrary = new GeometryLibrary(); materialLibrary.autoLoadTextures = autoLoadTextures; materialLibrary.texturePath = texturePath; //first chunk is always the primary, so we simply read it and parse it var chunk:Chunk3ds = new Chunk3ds(); readChunk(chunk); parse3DS(chunk); //build materials buildMaterials(); //build the meshes buildMeshes(); } /** * Loads and parses a 3ds file into a 3d container 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 { //texturePath as model folder if (url) { var _pathArray :Array = url.split("/"); var _imageName :String = _pathArray.pop(); var _texturePath :String = (_pathArray.length>0)?_pathArray.join("/")+"/":_pathArray.join("/"); if (init){ init.texturePath = (init.texturePath)?init.texturePath:_texturePath; }else { init = { texturePath:_texturePath }; } } return Object3DLoader.loadGeometry(url, Max3DS, true, init); } /** * Creates a 3d container object from the raw binary data of a 3ds file. * * @param data The binary data of a loaded file. * @param init [optional] An initialisation object for specifying default instance properties. * * @return A 3d container object representation of the 3ds file. */ public static function parse(data:*, init:Object = null):ObjectContainer3D { return Object3DLoader.parseGeometry(data, Max3DS, init).handle as ObjectContainer3D; } } } class Chunk3ds { public var id:int; public var length:int; public var bytesRead:int; }