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