/** * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. * If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. * You may add additional accurate notices of copyright ownership. * * It is desirable to notify that Covered Software was "Powered by AlternativaPlatform" with link to http://www.alternativaplatform.com/ * */ package alternativa.engine3d.loaders { import alternativa.engine3d.alternativa3d; import alternativa.engine3d.core.Light3D; import alternativa.engine3d.core.Object3D; import alternativa.engine3d.core.VertexAttributes; import alternativa.engine3d.lights.OmniLight; import alternativa.engine3d.lights.SpotLight; import alternativa.engine3d.objects.Mesh; import alternativa.engine3d.resources.ExternalTextureResource; import alternativa.engine3d.resources.Geometry; import flash.geom.Matrix; import flash.geom.Matrix3D; import flash.geom.Vector3D; import flash.utils.ByteArray; import flash.utils.Endian; use namespace alternativa3d; /** * Parser of .3ds files , that are presented as ByteArray. */ public class Parser3DS extends Parser { private static const CHUNK_MAIN:int = 0x4D4D; private static const CHUNK_VERSION:int = 0x0002; private static const CHUNK_SCENE:int = 0x3D3D; private static const CHUNK_ANIMATION:int = 0xB000; private static const CHUNK_OBJECT:int = 0x4000; private static const CHUNK_TRIMESH:int = 0x4100; private static const CHUNK_LIGHT:int = 0x4600; private static const CHUNK_CAMERA:int = 0x4700; private static const CHUNK_VERTICES:int = 0x4110; private static const CHUNK_FACES:int = 0x4120; private static const CHUNK_FACESMATERIAL:int = 0x4130; private static const CHUNK_FACESSMOOTHGROUPS:int = 0x4150; private static const CHUNK_MAPPINGCOORDS:int = 0x4140; //private static const CHUNK_OBJECTCOLOR:int = 0x4165; private static const CHUNK_TRANSFORMATION:int = 0x4160; //private static const CHUNK_MESHANIMATION:int = 0xB002; private static const CHUNK_MATERIAL:int = 0xAFFF; private var data:ByteArray; private var objectDatas:Object; private var animationDatas:Vector.; private var materialDatas:Object; /** * Performs parsing. * Result of parsing is placed in lists are follows objects, parents, materials. * @param data ByteArray correspond to content of a 3ds file. * @param texturesBaseURL Base path to texture files. After parsing diffuseMapURL and opacityMapURL properties gets string values, that consists of texturesBaseURL and file name. * @param scale Amount to multiply vertex coordinates, objects coordinates and values of objects scaling. * @param respectSmoothGroups Flag of accounting of smoothing groups. If flag set to true, then all vertices will duplicated according to smoothing groups, specified for the objects. * * @see alternativa.engine3d.loaders.ParserMaterial * @see #objects * @see #hierarchy * @see #materials */ public function parse(data:ByteArray, texturesBaseURL:String = "", scale:Number = 1, respectSmoothGroups:Boolean = false):void { if (data.bytesAvailable < 6) return; this.data = data; data.endian = Endian.LITTLE_ENDIAN; parse3DSChunk(data.position, data.bytesAvailable); objects = new Vector.(); hierarchy = new Vector.(); materials = new Vector.(); buildContent(texturesBaseURL, scale, respectSmoothGroups); this.data = null; objectDatas = null; animationDatas = null; materialDatas = null; } private function readChunkInfo(dataPosition:int):ChunkInfo { data.position = dataPosition; var chunkInfo:ChunkInfo = new ChunkInfo(); chunkInfo.id = data.readUnsignedShort(); chunkInfo.size = data.readUnsignedInt(); chunkInfo.dataSize = chunkInfo.size - 6; chunkInfo.dataPosition = data.position; chunkInfo.nextChunkPosition = dataPosition + chunkInfo.size; return chunkInfo; } private function parse3DSChunk(dataPosition:int, bytesAvailable:int):void { if (bytesAvailable < 6) return; var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); data.position = dataPosition; switch (chunkInfo.id) { // Main case CHUNK_MAIN: parseMainChunk(chunkInfo.dataPosition, chunkInfo.dataSize); break; } parse3DSChunk(chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); } private function parseMainChunk(dataPosition:int, bytesAvailable:int):void { if (bytesAvailable < 6) return; var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); switch (chunkInfo.id) { // Version case CHUNK_VERSION: //version = data.readUnsignedInt(); break; // 3D-scene case CHUNK_SCENE: parse3DChunk(chunkInfo.dataPosition, chunkInfo.dataSize); break; // Animation case CHUNK_ANIMATION: parseAnimationChunk(chunkInfo.dataPosition, chunkInfo.dataSize); break; } parseMainChunk(chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); } private function parse3DChunk(dataPosition:int, bytesAvailable:int):void { while (bytesAvailable >= 6) { var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); switch (chunkInfo.id) { // Material case CHUNK_MATERIAL: // Parse material var material:MaterialData = new MaterialData(); parseMaterialChunk(material, chunkInfo.dataPosition, chunkInfo.dataSize); break; // Object case CHUNK_OBJECT: parseObject(chunkInfo); break; } dataPosition = chunkInfo.nextChunkPosition; bytesAvailable -= chunkInfo.size; } } private function parseObject(chunkInfo:ChunkInfo):void { // Create list of objects, if it need. if (objectDatas == null) { objectDatas = {}; } // Create object data var object:ObjectData = new ObjectData(); // Get object name object.name = getString(chunkInfo.dataPosition); // Get object data to list objectDatas[object.name] = object; // Parse object var offset:int = object.name.length + 1; parseObjectChunk(object, chunkInfo.dataPosition + offset, chunkInfo.dataSize - offset); } private function parseObjectChunk(object:ObjectData, dataPosition:int, bytesAvailable:int):void { if (bytesAvailable < 6) return; var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); switch (chunkInfo.id) { // Mesh case CHUNK_TRIMESH: parseMeshChunk(object, chunkInfo.dataPosition, chunkInfo.dataSize); break; // Light source case CHUNK_LIGHT: parseLightChunk(object, chunkInfo.dataPosition, chunkInfo.dataSize); break; // Camera case CHUNK_CAMERA: parseCameraChunk(object, chunkInfo.dataSize); break; } parseObjectChunk(object, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); } private function parseMeshChunk(object:ObjectData, dataPosition:int, bytesAvailable:int):void { if (bytesAvailable < 6) return; var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); switch (chunkInfo.id) { // Vertices case CHUNK_VERTICES: parseVertices(object); break; // UV case CHUNK_MAPPINGCOORDS: parseUVs(object); break; // Transformation case CHUNK_TRANSFORMATION: parseMatrix(object); break; // Faces case CHUNK_FACES: parseFaces(object, chunkInfo); break; } parseMeshChunk(object, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); } private function parseVertices(object:ObjectData):void { var num:int = data.readUnsignedShort(); object.vertices = new Vector.(3*num, true); for (var i:int = 0, j:int = 0; i < num; i++) { object.vertices[j++] = data.readFloat(); object.vertices[j++] = data.readFloat(); object.vertices[j++] = data.readFloat(); } } private function parseUVs(object:ObjectData):void { var num:int = data.readUnsignedShort(); object.uvs = new Vector.(2*num, true); for (var i:int = 0, j:int = 0; i < num; i++) { object.uvs[j++] = data.readFloat(); object.uvs[j++] = data.readFloat(); } } private function parseMatrix(object:ObjectData):void { object.a = data.readFloat(); object.e = data.readFloat(); object.i = data.readFloat(); object.b = data.readFloat(); object.f = data.readFloat(); object.j = data.readFloat(); object.c = data.readFloat(); object.g = data.readFloat(); object.k = data.readFloat(); object.d = data.readFloat(); object.h = data.readFloat(); object.l = data.readFloat(); } private function parseFaces(object:ObjectData, chunkInfo:ChunkInfo):void { var num:int = data.readUnsignedShort(); object.smoothGroups = new Vector.(num, true); object.faces = new Vector.(3*num, true); for (var i:int = 0, j:int = 0; i < num; i++) { object.faces[j++] = data.readUnsignedShort(); object.faces[j++] = data.readUnsignedShort(); object.faces[j++] = data.readUnsignedShort(); data.position += 2; // Skip the flag of edges rendering } var offset:int = 2 + 8*num; parseFacesChunk(object, chunkInfo.dataPosition + offset, chunkInfo.dataSize - offset); } private function parseFacesChunk(object:ObjectData, dataPosition:int, bytesAvailable:int):void { if (bytesAvailable < 6) return; var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); switch (chunkInfo.id) { // Surfaces case CHUNK_FACESMATERIAL: parseSurface(object); break; // Smoothing groups. case CHUNK_FACESSMOOTHGROUPS: parseSmoothGroups(object); break; } parseFacesChunk(object, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); } private function parseSurface(object:ObjectData):void { // Create list of surfaces, if it need. if (object.surfaces == null) { object.surfaces = {}; } // Name of surface and number of faces. var sur:String = getString(data.position); var num:int = data.readUnsignedShort(); if (num > 0) { // Create surface data var surface:Vector. = new Vector.(num + 1); // Put surface data to list object.surfaces[sur] = surface; // Get faces of surface for (var i:int = 0; i < num; i++) { surface[i] = data.readUnsignedShort(); } // Also stores number of the material (starts from 1) (additionally store the serial number of material (beginning from the one)) surface[num] = (object.surfacesCount++); } } private function parseSmoothGroups(object:ObjectData):void { var num:int = object.faces.length/3; for (var i:int = 0; i < num; i++) { object.smoothGroups [i] = data.readUnsignedInt(); } } private function parseAnimationChunk(dataPosition:int, bytesAvailable:int):void { while (bytesAvailable >= 6) { var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); switch (chunkInfo.id) { // Object animation case 0xB001: // ambient o_O case 0xB002: case 0xB003: case 0xB004: // cam target case 0xB005: case 0xB006: // spot target case 0xB007: if (animationDatas == null) { animationDatas = new Vector.(); } var animation:AnimationData = new AnimationData(); animation.chunkId = chunkInfo.id; animationDatas.push(animation); parseObjectAnimationChunk(animation, chunkInfo.dataPosition, chunkInfo.dataSize); break; // Timeline case 0xB008: break; } dataPosition = chunkInfo.nextChunkPosition; bytesAvailable -= chunkInfo.size; } } private function parseObjectAnimationChunk(animation:AnimationData, dataPosition:int, bytesAvailable:int):void { if (bytesAvailable < 6) return; var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); switch (chunkInfo.id) { // Identification of object and its link case 0xB010: // Name of the object animation.objectName = getString(data.position); if ((animation.chunkId == 0xB004) || (animation.chunkId == 0xB006)) animation.objectName += "_target"; data.position += 4; // Index of parent object in plain list of scene objects. animation.parentIndex = data.readUnsignedShort(); break; // Name of dummy object case 0xB011: animation.instanceOf = animation.objectName; animation.objectName = getString(data.position); break; // Pivot case 0xB013: animation.pivot = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); break; // Offset of the object relative to its parent case 0xB020: data.position += 20; animation.position = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); break; // Rotation of object relative to its parent (angle-axis) case 0xB021: data.position += 20; animation.rotation = getRotationFrom3DSAngleAxis(data.readFloat(), data.readFloat(), data.readFloat(), data.readFloat()); break; // Scale of object relative to its parent case 0xB022: data.position += 20; animation.scale = new Vector3D(data.readFloat(), data.readFloat(), data.readFloat()); break; } parseObjectAnimationChunk(animation, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); } private function parseMaterialChunk(material:MaterialData, dataPosition:int, bytesAvailable:int):void { if (bytesAvailable < 6) return; var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); switch (chunkInfo.id) { // Name of material case 0xA000: parseMaterialName(material); break; // Ambient color case 0xA010: data.position = chunkInfo.dataPosition + 6; material.ambient = (data.readUnsignedByte() << 16) + (data.readUnsignedByte() << 8) + data.readUnsignedByte(); break; // Diffuse color case 0xA020: data.position = chunkInfo.dataPosition + 6; material.diffuse = (data.readUnsignedByte() << 16) + (data.readUnsignedByte() << 8) + data.readUnsignedByte(); break; // Specular color case 0xA030: data.position = chunkInfo.dataPosition + 6; material.specular = (data.readUnsignedByte() << 16) + (data.readUnsignedByte() << 8) + data.readUnsignedByte(); break; // Shininess percent case 0xA040: data.position = chunkInfo.dataPosition + 6; material.glossiness = data.readUnsignedShort(); break; // Shininess strength percent case 0xA041: break; // Transparensy case 0xA050: data.position = chunkInfo.dataPosition + 6; material.transparency = data.readUnsignedShort(); break; // Texture map 1 case 0xA200: parseMaterialMapData("diffuse", material, chunkInfo); break; // Texture map 2 case 0xA33A: break; // Opacity map case 0xA210: parseMaterialMapData("transparent", material, chunkInfo); break; // Bump map case 0xA230: parseMaterialMapData("bump", material, chunkInfo); break; // Specular map case 0xA204: parseMaterialMapData("specular", material, chunkInfo); break; // Shininess map case 0xA33C: parseMaterialMapData("glossiness", material, chunkInfo); break; // Self-illumination map case 0xA33D: parseMaterialMapData("emission", material, chunkInfo); break; // Reflection map case 0xA220: parseMaterialMapData("reflective", material, chunkInfo); break; } parseMaterialChunk(material, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); } private function parseMaterialMapData(channel:String, material:MaterialData, chunkInfo:ChunkInfo):void { var map:MapData = new MapData; map.channel = channel; parseMapChunk(material.name, map, chunkInfo.dataPosition, chunkInfo.dataSize); material.maps.push(map); } private function parseMaterialName(material:MaterialData):void { // Create list of materials, if it need if (materialDatas == null) { materialDatas = {}; } // Get name of material material.name = getString(data.position); // Put data of material in list materialDatas[material.name] = material; } private function parseMapChunk(materialName:String, map:MapData, dataPosition:int, bytesAvailable:int):void { if (bytesAvailable < 6) return; var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); switch (chunkInfo.id) { // File name case 0xA300: map.filename = getString(chunkInfo.dataPosition).toLowerCase(); break; case 0xA351: // Texture mapping options break; // Scale along U case 0xA354: map.scaleU = data.readFloat(); break; // Scale along V case 0xA356: map.scaleV = data.readFloat(); break; // Offset along U case 0xA358: map.offsetU = data.readFloat(); break; // Offset along V case 0xA35A: map.offsetV = data.readFloat(); break; // Rotation angle case 0xA35C: map.rotation = data.readFloat(); break; } parseMapChunk(materialName, map, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); } private function getString(index:int):String { data.position = index; var charCode:int; var res:String = ""; while ((charCode = data.readByte()) != 0) { res += String.fromCharCode(charCode); } return res; } private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Vector3D { var res:Vector3D = new Vector3D(); var s:Number = Math.sin(angle); var c:Number = Math.cos(angle); var t:Number = 1 - c; var k:Number = x*y*t + z*s; var half:Number; if (k >= 1) { half = angle/2; res.z = -2*Math.atan2(x*Math.sin(half), Math.cos(half)); res.y = -Math.PI/2; res.x = 0; return res; } if (k <= -1) { half = angle/2; res.z = 2*Math.atan2(x*Math.sin(half), Math.cos(half)); res.y = Math.PI/2; res.x = 0; return res; } res.z = -Math.atan2(y*s - x*z*t, 1 - (y*y + z*z)*t); res.y = -Math.asin(x*y*t + z*s); res.x = -Math.atan2(x*s - y*z*t, 1 - (x*x + z*z)*t); return res; } private function parseLightChunk(object:ObjectData, dataPosition:int, bytesAvailable:int):void { if (bytesAvailable < 6 + 12) return; var x:Number = data.readFloat(); var y:Number = data.readFloat(); var z:Number = data.readFloat(); object.position = new Vector3D(x, y, z); parseLightSubChunk(object, dataPosition + 12, bytesAvailable - 12); } private function parseLightSubChunk(object:ObjectData, dataPosition:int, bytesAvailable:int):void { if (bytesAvailable < 6) return; var chunkInfo:ChunkInfo = readChunkInfo(dataPosition); switch (chunkInfo.id) { // Float RGB case 0x0010: var r:int = Math.round(Math.max(0, 255*Math.min(1, data.readFloat()))); var g:int = Math.round(Math.max(0, 255*Math.min(1, data.readFloat()))); var b:int = Math.round(Math.max(0, 255*Math.min(1, data.readFloat()))); object.lightColor = r*65536 + g*256 + b; break; // Byte RGB case 0x0011: r = data.readUnsignedByte(); g = data.readUnsignedByte(); b = data.readUnsignedByte(); object.lightColor = r*65536 + g*256 + b; break; // Spot light case 0x4610: var x:Number = data.readFloat(); var y:Number = data.readFloat(); var z:Number = data.readFloat(); object.target = new Vector3D(x, y, z); object.hotspot = data.readFloat(); object.falloff = data.readFloat(); break; // Light is off case 0x4620: object.lightOff = true; break; // Attenuation is on case 0x4625: object.attenuationOn = true; break; // Inner range case 0x4659: object.innerRange = data.readFloat(); break; // Outer range case 0x465A: object.outerRange = data.readFloat(); break; // Multiplier case 0x465B: object.multiplier = data.readFloat(); break; default: break; } parseLightSubChunk(object, chunkInfo.nextChunkPosition, bytesAvailable - chunkInfo.size); } private function parseCameraChunk(object:ObjectData, bytesAvailable:int):void { if (bytesAvailable < 32) return; var x:Number = data.readFloat(); var y:Number = data.readFloat(); var z:Number = data.readFloat(); object.position = new Vector3D(x, y, z); x = data.readFloat(); y = data.readFloat(); z = data.readFloat(); object.target = new Vector3D(x, y, z); object.bank = data.readFloat(); object.lens = data.readFloat(); } private function buildContent(texturesBaseURL:String, scale:Number, respectSmoothGroups:Boolean):void { // Calculation of matrices of texture materials for (var materialName:String in materialDatas) { var materialData:MaterialData = materialDatas[materialName]; materialData.material = new ParserMaterial(); materialData.material.name = materialName; var mapData:MapData = materialData.diffuseMap; if (mapData != null) { if ((mapData.rotation != 0) || (mapData.offsetU != 0) || (mapData.offsetV != 0) || (mapData.scaleU != 1) || (mapData.scaleV != 1)) { // transformation of texture is set var materialMatrix:Matrix = new Matrix(); var rot:Number = mapData.rotation*Math.PI/180; materialMatrix.translate(-mapData.offsetU, mapData.offsetV); materialMatrix.translate(-0.5, -0.5); materialMatrix.scale(mapData.scaleU, mapData.scaleV); materialMatrix.rotate(-rot); materialMatrix.translate(0.5, 0.5); materialData.matrix = materialMatrix; } } for each (mapData in materialData.maps) { materialData.material.textures[mapData.channel] = new ExternalTextureResource(texturesBaseURL + mapData.filename); } materialData.material.colors["ambient"] = materialData.ambient; materialData.material.colors["diffuse"] = materialData.diffuse; materialData.material.colors["specular"] = materialData.specular; materialData.material.glossiness = 0.01*materialData.glossiness; materialData.material.transparency = 0.01*materialData.transparency; materials.push(materialData.material); } var objectName:String; var objectData:ObjectData; var object:Object3D; // Scene has hierarchically related objects and (or) specified data about objects transformations. if (animationDatas != null) { if (objectDatas != null) { var i:int; var length:int = animationDatas.length; var animationData:AnimationData; for (i = 0; i < length; i++) { animationData = animationDatas[i]; objectName = animationData.objectName; objectData = objectDatas[objectName]; // Check for instances if (objectData != null) { for (var j:int = i + 1; j < length; j++) { var animationData2:AnimationData = animationDatas[j]; if (objectName == animationData2.instanceOf) { animationData2.instanceOf = null; // Found match name for current part of animation, so make reference for it. var newObjectData:ObjectData = new ObjectData(); newObjectData.name = animationData2.objectName; objectDatas[animationData2.objectName] = newObjectData; newObjectData.vertices = objectData.vertices; newObjectData.uvs = objectData.uvs; newObjectData.faces = objectData.faces; newObjectData.surfaces = objectData.surfaces; newObjectData.surfacesCount = objectData.surfacesCount; newObjectData.smoothGroups = objectData.smoothGroups; newObjectData.a = objectData.a; newObjectData.b = objectData.b; newObjectData.c = objectData.c; newObjectData.d = objectData.d; newObjectData.e = objectData.e; newObjectData.f = objectData.f; newObjectData.g = objectData.g; newObjectData.h = objectData.h; newObjectData.i = objectData.i; newObjectData.j = objectData.j; newObjectData.k = objectData.k; newObjectData.l = objectData.l; newObjectData.lightColor = objectData.lightColor; newObjectData.lightOff = objectData.lightOff; newObjectData.attenuationOn = objectData.attenuationOn; newObjectData.hotspot = objectData.hotspot; newObjectData.falloff = objectData.falloff; newObjectData.innerRange = objectData.innerRange; newObjectData.outerRange = objectData.outerRange; newObjectData.multiplier = objectData.multiplier; newObjectData.position = objectData.position; newObjectData.target = objectData.target; newObjectData.bank = objectData.bank; newObjectData.lens = objectData.lens; } } } if (objectData != null) { object = buildObject3D(objectData, animationData, scale, respectSmoothGroups); } else { // Create empty Object3D object = new Object3D(); } object.name = objectName; animationData.object = object; if (animationData.position != null) { object.x = animationData.position.x*scale; object.y = animationData.position.y*scale; object.z = animationData.position.z*scale; } if (animationData.rotation != null) { object.rotationX = animationData.rotation.x; object.rotationY = animationData.rotation.y; object.rotationZ = animationData.rotation.z; } if (animationData.scale != null) { object.scaleX = animationData.scale.x; object.scaleY = animationData.scale.y; object.scaleZ = animationData.scale.z; } } // Add objects for (i = 0; i < length; i++) { animationData = animationDatas[i]; objects.push(animationData.object); if (animationData.parentIndex == 0xFFFF) { hierarchy.push(animationData.object); } else { AnimationData(animationDatas[animationData.parentIndex]).object.addChild(animationData.object); } } } // Scene has no hierarchically related objects and data about objects transformations is not specified. Only polygonal objects woll added to container. } else { for (objectName in objectDatas) { objectData = objectDatas[objectName]; if (objectData.vertices != null) { object = buildObject3D(objectData, null, scale, respectSmoothGroups); object.name = objectName; objects.push(object); hierarchy.push(object); } } } } private function buildObject3D(objectData:ObjectData, animationData:AnimationData, scale:Number, respectSmoothGroups:Boolean):Object3D { var object:Object3D; if (objectData.vertices != null) { // Create polygonal object object = new Mesh(); buildMesh(object as Mesh, objectData, animationData, scale, respectSmoothGroups); } else { if (objectData.lightColor >= 0) { // Light var innerRange:Number = 0; var outerRange:Number = 1e15; // must be Number.MAX_VALUE, but if you set radius to ~2^60 and more then SpotLight is not working. if (objectData.attenuationOn && (objectData.outerRange < Number.MAX_VALUE)) { innerRange = objectData.innerRange*scale; outerRange = objectData.outerRange*scale; } if (objectData.target != null) { var rad:Number = Math.PI/180; object = new SpotLight(objectData.lightColor, innerRange, outerRange, objectData.hotspot*rad, objectData.falloff*rad); } else { object = new OmniLight(objectData.lightColor, innerRange, outerRange); } // Light intensity Light3D(object).intensity = objectData.lightOff ? 0 : objectData.multiplier; } else { // Camera or something else object = new Object3D; } if (objectData.position) { object.x = objectData.position.x*scale; object.y = objectData.position.y*scale; object.z = objectData.position.z*scale; if (objectData.target) { // Turn object to target var dx:Number = objectData.target.x*scale - object.x; var dy:Number = objectData.target.y*scale - object.y; var dz:Number = objectData.target.z*scale - object.z; object.rotationX = (Math.atan2(dz, Math.sqrt(((dx*dx) + (dy*dy)))) - (Math.PI/2)); object.rotationY = 0; object.rotationZ = -(Math.atan2(dx, dy)); // Pitch var matrix:Matrix3D = object.matrix; matrix.prependRotation(objectData.bank, Vector3D.Z_AXIS); object.matrix = matrix; } } } return object; } private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData, scale:Number, respectSmoothGroups:Boolean):void { // Quit early if (objectData.faces == null) { return; } var vertices:Vector. = new Vector.(objectData.vertices.length/3); var faces:Vector. = new Vector.(objectData.faces.length/3); buildInitialGeometry(vertices, faces, objectData, animationData, scale); if (respectSmoothGroups) { cloneVerticesToRespectSmoothGroups(vertices, faces); } calculateVertexNormals(vertices, faces); if (materialDatas != null) { assignMaterialsToFaces(faces, objectData); cloneAndTransformVerticesToRespectUVTransforms(vertices, faces); } calculateVertexTangents(vertices, faces); // Default material for the faces without surfaces. var defaultMaterialData:MaterialData = new MaterialData; defaultMaterialData.numTriangles = 0; defaultMaterialData.material = new ParserMaterial; defaultMaterialData.material.colors["diffuse"] = 0x7F7F7F; defaultMaterialData.material.name = "default"; var indices:Vector. = collectFacesIntoSurfaces(faces, defaultMaterialData); // Put all to mesh var vec:Vector3D, vertex:Vertex; var numVertices:int = vertices.length; var byteArray:ByteArray = new ByteArray(); byteArray.endian = Endian.LITTLE_ENDIAN; for (var n:int = 0; n < numVertices; n++) { vertex = vertices [n]; byteArray.writeFloat(vertex.x); byteArray.writeFloat(vertex.y); byteArray.writeFloat(vertex.z); byteArray.writeFloat(vertex.u); byteArray.writeFloat(vertex.v); vec = vertex.normal; byteArray.writeFloat(vec.x); byteArray.writeFloat(vec.y); byteArray.writeFloat(vec.z); vec = vertex.tangent; byteArray.writeFloat(vec.x); byteArray.writeFloat(vec.y); byteArray.writeFloat(vec.z); byteArray.writeFloat(vec.w); } mesh.geometry = new Geometry; mesh.geometry._indices = indices; mesh.geometry.addVertexStream([ VertexAttributes.POSITION, VertexAttributes.POSITION, VertexAttributes.POSITION, VertexAttributes.TEXCOORDS[0], VertexAttributes.TEXCOORDS[0], VertexAttributes.NORMAL, VertexAttributes.NORMAL, VertexAttributes.NORMAL, VertexAttributes.TANGENT4, VertexAttributes.TANGENT4, VertexAttributes.TANGENT4, VertexAttributes.TANGENT4 ]); mesh.geometry._vertexStreams[0].data = byteArray; mesh.geometry._numVertices = numVertices; if (objectData.surfaces != null) { for (var key:String in objectData.surfaces) { var materialData:MaterialData = materialDatas[key]; mesh.addSurface(materialData.material, 3*materialData.indexBegin, materialData.numTriangles); } } if (defaultMaterialData.numTriangles > 0) { mesh.addSurface(defaultMaterialData.material, 3*defaultMaterialData.indexBegin, defaultMaterialData.numTriangles); } mesh.calculateBoundBox(); } private function buildInitialGeometry(vertices:Vector., faces:Vector., objectData:ObjectData, animationData:AnimationData, scale:Number):void { var correct:Boolean = false; if (animationData != null) { var a:Number = objectData.a; var b:Number = objectData.b; var c:Number = objectData.c; var d:Number = objectData.d; var e:Number = objectData.e; var f:Number = objectData.f; var g:Number = objectData.g; var h:Number = objectData.h; var i:Number = objectData.i; var j:Number = objectData.j; var k:Number = objectData.k; var l:Number = objectData.l; var det:Number = 1/(-c*f*i + b*g*i + c*e*j - a*g*j - b*e*k + a*f*k); objectData.a = (-g*j + f*k)*det; objectData.b = (c*j - b*k)*det; objectData.c = (-c*f + b*g)*det; objectData.d = (d*g*j - c*h*j - d*f*k + b*h*k + c*f*l - b*g*l)*det; objectData.e = (g*i - e*k)*det; objectData.f = (-c*i + a*k)*det; objectData.g = (c*e - a*g)*det; objectData.h = (c*h*i - d*g*i + d*e*k - a*h*k - c*e*l + a*g*l)*det; objectData.i = (-f*i + e*j)*det; objectData.j = (b*i - a*j)*det; objectData.k = (-b*e + a*f)*det; objectData.l = (d*f*i - b*h*i - d*e*j + a*h*j + b*e*l - a*f*l)*det; if (animationData.pivot != null) { objectData.d -= animationData.pivot.x; objectData.h -= animationData.pivot.y; objectData.l -= animationData.pivot.z; } correct = true; } // Creation and correcting of vertices var n:int, m:int, p:int, len:int = objectData.vertices.length; var uv:Boolean = objectData.uvs != null && objectData.uvs.length > 0; for (n = 0, m = 0, p = 0; n < len;) { var vertex:Vertex = new Vertex; if (correct) { var x:Number = objectData.vertices[n++]; var y:Number = objectData.vertices[n++]; var z:Number = objectData.vertices[n++]; vertex.x = objectData.a*x + objectData.b*y + objectData.c*z + objectData.d; vertex.y = objectData.e*x + objectData.f*y + objectData.g*z + objectData.h; vertex.z = objectData.i*x + objectData.j*y + objectData.k*z + objectData.l; } else { vertex.x = objectData.vertices[n++]; vertex.y = objectData.vertices[n++]; vertex.z = objectData.vertices[n++]; } vertex.x *= scale; vertex.y *= scale; vertex.z *= scale; if (uv) { vertex.u = objectData.uvs[m++]; vertex.v = 1 - objectData.uvs[m++]; } else { // If you leave object without uv, then the calculation of tangents is breaks x = vertex.x; y = vertex.y; var rxy:Number = 1e-5 + Math.sqrt(x*x + y*y); vertex.u = Math.atan2(rxy, vertex.z); vertex.v = Math.atan2(y, x); } vertices[p++] = vertex; } // Create faces len = objectData.faces.length; for (n = 0, p = 0; n < len;) { var face:Face = new Face(); face.a = objectData.faces[n++]; face.b = objectData.faces[n++]; face.c = objectData.faces[n++]; face.smoothGroup = objectData.smoothGroups[p]; faces[p++] = face; } } private function cloneVerticesToRespectSmoothGroups(vertices:Vector., faces:Vector.):void { // Actions with smoothing groups: // - if vertex is in faces with groups 1+2 and 3, then it is duplicated // - if vertex is in faces with groups 1+2, 3 and 1+3, then it is not duplicated var n:int, m:int, p:int, q:int, len:int, numVertices:int = vertices.length, numFaces:int = faces.length; // Calculate disjoint groups for vertices var vertexGroups:Vector.> = new Vector.>(numVertices, true); for (p = 0; p < numVertices; p++) { vertexGroups [p] = new Vector.; } for (n = 0; n < numFaces; n++) { var face:Face = Face(faces[n]); for (m = 0; m < 3; m++) { var groups:Vector. = vertexGroups [(m == 0) ? face.a : ((m == 1) ? face.b : face.c)]; var group:uint = face.smoothGroup; for (q = groups.length - 1; q >= 0; q--) { if ((group & groups [q]) > 0) { group |= groups [q]; groups.splice(q, 1); q = groups.length - 1; } } groups.push(group); } } // Clone vertices var vertexClones:Vector.> = new Vector.>(numVertices, true); for (p = 0; p < numVertices; p++) { if ((len = vertexGroups [p].length) < 1) continue; var clones:Vector. = new Vector.(len, true); vertexClones [p] = clones; clones [0] = p; var vertex0:Vertex = vertices [p]; for (m = 1; m < len; m++) { var vertex1:Vertex = new Vertex; vertex1.x = vertex0.x; vertex1.y = vertex0.y; vertex1.z = vertex0.z; vertex1.u = vertex0.u; vertex1.v = vertex0.v; clones[m] = vertices.length; vertices.push(vertex1); } } numVertices = vertices.length; // Loop on faces for (n = 0; n < numFaces; n++) { face = Face(faces [n]); group = face.smoothGroup; for (m = 0; m < 3; m++) { p = (m == 0) ? face.a : ((m == 1) ? face.b : face.c); groups = vertexGroups [p]; len = groups.length; clones = vertexClones [p]; for (q = 0; q < len; q++) { if (((group == 0) && (groups [q] == 0)) || ((group & groups [q]) > 0)) { var index:uint = clones [q]; if (group == 0) { // In case of there is no smoothing group, vertices of this face is unique groups.splice(q, 1); clones.splice(q, 1); } if (m == 0) face.a = index; else if (m == 1) face.b = index; else face.c = index; q = len; } } } } } private function cloneAndTransformVerticesToRespectUVTransforms(vertices:Vector., faces:Vector.):void { // Actions with UV transformation // if vertex in faces with different transform materials, then it is duplicated var n:int, m:int, p:int, q:int, len:int, numVertices:int = vertices.length, numFaces:int = faces.length; // Find transform materials for vertices var vertexGroups:Vector.> = new Vector.>(numVertices, true); for (p = 0; p < numVertices; p++) { vertexGroups [p] = new Vector.; } for (n = 0; n < numFaces; n++) { var face:Face = Face(faces [n]); for (m = 0; m < 3; m++) { var groups:Vector. = vertexGroups [(m == 0) ? face.a : ((m == 1) ? face.b : face.c)]; var group:uint = face.uvTransformGroup; if (groups.indexOf(group) < 0) groups.push(group); } } // Clone vertices var vertexClones:Vector.> = new Vector.>(numVertices, true); for (p = 0; p < numVertices; p++) { if ((len = vertexGroups [p].length) < 1) continue; var clones:Vector. = new Vector.(len, true); vertexClones [p] = clones; clones [0] = p; var vertex0:Vertex = vertices [p]; for (m = 1; m < len; m++) { var vertex1:Vertex = new Vertex; vertex1.x = vertex0.x; vertex1.y = vertex0.y; vertex1.z = vertex0.z; vertex1.u = vertex0.u; vertex1.v = vertex0.v; vertex1.normal = vertex0.normal; clones [m] = vertices.length; vertices.push(vertex1); } } numVertices = vertices.length; // Parse on faces, and apply the transformation for (n = 0; n < numFaces; n++) { face = Face(faces [n]); group = face.uvTransformGroup; var materialData:MaterialData = materialDatas[face.surfaceName]; for (m = 0; m < 3; m++) { p = (m == 0) ? face.a : ((m == 1) ? face.b : face.c); groups = vertexGroups [p]; len = groups.length; clones = vertexClones [p]; q = groups.indexOf(group); // must aways be in groups var index:uint = clones [q]; if (m == 0) face.a = index; else if (m == 1) face.b = index; else face.c = index; if (group > 0) { vertex0 = vertices [index]; if (vertex0.nonTransformed) { vertex0.nonTransformed = false; var u:Number = vertex0.u; var v:Number = vertex0.v; vertex0.u = materialData.matrix.a*u + materialData.matrix.b*v + materialData.matrix.tx; vertex0.v = materialData.matrix.c*u + materialData.matrix.d*v + materialData.matrix.ty; } } } } } private function calculateVertexNormals(vertices:Vector., faces:Vector.):void { var n:int, m:int, numFaces:int = faces.length; for (n = 0; n < numFaces; n++) { var face:Face = Face(faces [n]); // Calculation of average normals of vertices var vertex0:Vertex = vertices [face.a]; var vertex1:Vertex = vertices [face.b]; var vertex2:Vertex = vertices [face.c]; var deltaX1:Number = vertex1.x - vertex0.x; var deltaY1:Number = vertex1.y - vertex0.y; var deltaZ1:Number = vertex1.z - vertex0.z; var deltaX2:Number = vertex2.x - vertex0.x; var deltaY2:Number = vertex2.y - vertex0.y; var deltaZ2:Number = vertex2.z - vertex0.z; face.deltaX1 = deltaX1; face.deltaY1 = deltaY1; face.deltaZ1 = deltaZ1; face.deltaX2 = deltaX2; face.deltaY2 = deltaY2; face.deltaZ2 = deltaZ2; var normalX:Number = deltaZ2*deltaY1 - deltaY2*deltaZ1; var normalY:Number = deltaX2*deltaZ1 - deltaZ2*deltaX1; var normalZ:Number = deltaY2*deltaX1 - deltaX2*deltaY1; var normalLen:Number = 1e-5 + Math.sqrt(normalX*normalX + normalY*normalY + normalZ*normalZ); normalX = normalX/normalLen; normalY = normalY/normalLen; normalZ = normalZ/normalLen; for (m = 0; m < 3; m++) { var vertex:Vertex = (m == 0) ? vertex0 : ((m == 1) ? vertex1 : vertex2); if (vertex.normal == null) { vertex.normal = new Vector3D(normalX, normalY, normalZ); } else { var vec:Vector3D = vertex.normal; vec.x += normalX; vec.y += normalY; vec.z += normalZ; } } } } private function calculateVertexTangents(vertices:Vector., faces:Vector.):void { var n:int, m:int, numVertices:int = vertices.length, numFaces:int = faces.length; for (n = 0; n < numFaces; n++) { var face:Face = Face(faces [n]); // Calculation of average tangents of vertices var vertex0:Vertex = vertices [face.a]; var vertex1:Vertex = vertices [face.b]; var vertex2:Vertex = vertices [face.c]; var deltaU1:Number = vertex1.u - vertex0.u; var deltaV1:Number = vertex0.v - vertex1.v; var deltaU2:Number = vertex2.u - vertex0.u; var deltaV2:Number = vertex0.v - vertex2.v; // Inverse determinant is included to the formulas below as common multiplier. // Its value is insignificantly, because vectors are normalized //var invdet:Number = 1 / (deltaU1 * deltaV2 - deltaU2 * deltaV1); //if (invdet > 1e9) invdet = 1e9; else if (invdet < -1e9) invdet = -1e9; var deltaX1:Number = face.deltaX1; var deltaY1:Number = face.deltaY1; var deltaZ1:Number = face.deltaZ1; var deltaX2:Number = face.deltaX2; var deltaY2:Number = face.deltaY2; var deltaZ2:Number = face.deltaZ2; var stMatrix00:Number = (deltaV2)//*invdet; var stMatrix01:Number = -(deltaV1)//*invdet; var stMatrix10:Number = -(deltaU2)//*invdet; var stMatrix11:Number = (deltaU1)//*invdet; var tangentX:Number = stMatrix00*deltaX1 + stMatrix01*deltaX2; var tangentY:Number = stMatrix00*deltaY1 + stMatrix01*deltaY2; var tangentZ:Number = stMatrix00*deltaZ1 + stMatrix01*deltaZ2; var biTangentX:Number = stMatrix10*deltaX1 + stMatrix11*deltaX2; var biTangentY:Number = stMatrix10*deltaY1 + stMatrix11*deltaY2; var biTangentZ:Number = stMatrix10*deltaZ1 + stMatrix11*deltaZ2; var tangentLen:Number = 1e-5 + Math.sqrt(tangentX*tangentX + tangentY*tangentY + tangentZ*tangentZ); tangentX = tangentX/tangentLen; tangentY = tangentY/tangentLen; tangentZ = tangentZ/tangentLen; var biTangentLen:Number = 1e-5 + Math.sqrt(biTangentX*biTangentX + biTangentY*biTangentY + biTangentZ*biTangentZ); biTangentX = biTangentX/biTangentLen; biTangentY = biTangentY/biTangentLen; biTangentZ = biTangentZ/biTangentLen; for (m = 0; m < 3; m++) { var vertex:Vertex = (m == 0) ? vertex0 : ((m == 1) ? vertex1 : vertex2); if (vertex.tangent == null) { vertex.tangent = new Vector3D(tangentX, tangentY, tangentZ); vertex.biTangent = new Vector3D(biTangentX, biTangentY, biTangentZ); } else { var vec:Vector3D; vec = vertex.tangent; vec.x += tangentX; vec.y += tangentY; vec.z += tangentZ; vec = vertex.biTangent; vec.x += biTangentX; vec.y += biTangentY; vec.z += biTangentZ; } } } // orthonormalize TBN's var normalX:Number, normalY:Number, normalZ:Number, dot:Number, vec2:Vector3D; for (n = 0; n < numVertices; n++) { vertex = vertices [n]; if (vertex.normal == null) { vertex.normal = Vector3D.X_AXIS; vertex.tangent = Vector3D.Y_AXIS.clone(); vertex.tangent.w = 1; } else { vec = vertex.normal; vec.normalize(); normalX = vec.x; normalY = vec.y; normalZ = vec.z; vec = vertex.tangent; tangentX = vec.x; tangentY = vec.y; tangentZ = vec.z; dot = normalX*tangentX + normalY*tangentY + normalZ*tangentZ; // perform orthonormalization between normal and tangent: tangent -= normal*dot; tangentX -= normalX*dot; tangentY -= normalY*dot; tangentZ -= normalZ*dot; tangentLen = tangentX*tangentX + tangentY*tangentY + tangentZ*tangentZ; if (tangentLen > 0) { tangentLen = Math.sqrt(tangentLen); vec.x = tangentX/tangentLen; vec.y = tangentY/tangentLen; vec.z = tangentZ/tangentLen; // calculate direction of bi-normal var crossX:Number = normalY*tangentZ - normalZ*tangentY; var crossY:Number = normalZ*tangentX - normalX*tangentZ; var crossZ:Number = normalX*tangentY - normalY*tangentX; vec2 = vertex.biTangent; dot = crossX*vec2.x + crossY*vec2.y + crossZ*vec2.z; vec.w = (dot < 0) ? -1 : 1; } else { // tangent is degenerate, try to start from bi-normal vec = vertex.biTangent; biTangentX = vec.x; biTangentY = vec.y; biTangentZ = vec.z; dot = normalX*biTangentX + normalY*biTangentY + normalZ*biTangentZ; // perform orthonormalization between normal and bi-normal: biTangent -= normal*dot; biTangentX -= normalX*dot; biTangentY -= normalY*dot; biTangentZ -= normalZ*dot; biTangentLen = biTangentX*biTangentX + biTangentY*biTangentY + biTangentZ*biTangentZ; if (biTangentLen > 0) { biTangentLen = Math.sqrt(biTangentLen); vec.x = biTangentX/biTangentLen; vec.y = biTangentY/biTangentLen; vec.z = biTangentZ/biTangentLen; } else { // bi-normal is degenerate too, get any vector that is perpendicular to the normal if (normalX != 0) { vec.x = -normalY; vec.y = normalX; vec.z = 0; } else { vec.x = 0; vec.y = -normalZ; vec.z = normalY; } } // calculate tangent biTangentX = vec.x; biTangentY = vec.y; biTangentZ = vec.z; vec = vertex.tangent; vec.x = -(normalY*biTangentZ - normalZ*biTangentY); vec.y = -(normalZ*biTangentX - normalX*biTangentZ); vec.z = -(normalX*biTangentY - normalY*biTangentX); dot = biTangentX*vec.x + biTangentY*vec.y + biTangentZ*vec.z; vec.w = (dot < 0) ? -1 : 1; } } } } private function assignMaterialsToFaces(faces:Vector., objectData:ObjectData):void { // Assign materials if (objectData.surfaces != null) { for (var key:String in objectData.surfaces) { var surface:Vector. = objectData.surfaces[key]; // Get serial number of material for sorting var surfaceIndex:uint = surface.pop(); var materialData:MaterialData = materialDatas[key]; for (var n:int = 0; n < surface.length; n++) { var face:Face = faces[surface[n]]; face.surface = surfaceIndex; face.surfaceName = key; // If it need to correct UV-coordianates face.uvTransformGroup = (materialData.matrix != null) ? surfaceIndex : 0; } } } } private function sortFacesBySurface(a:Vector., left:int, right:int):void { var pivot:uint, tmp:Face; var i:int = left; var j:int = right; pivot = a[int((left + right) >> 1)].surface; while (i <= j) { while (a[i].surface < pivot) i++; while (a[j].surface > pivot) j--; if (i <= j) { tmp = a[i]; a[i] = a[j]; i++; a[j] = tmp; j--; } } if (left < j) sortFacesBySurface(a, left, j); if (i < right) sortFacesBySurface(a, i, right); } private function collectFacesIntoSurfaces(faces:Vector., defaultMaterialData:MaterialData):Vector. { var numFaces:int = faces.length; // Sort faces on materials if (numFaces) sortFacesBySurface(faces, 0, numFaces - 1); // Create indices, calculate indexBegin and numTriangles var indices:Vector. = new Vector.(numFaces*3, true); var lastMaterialData:MaterialData; for (var n:int = 0; n < numFaces; n++) { var face:Face = Face(faces [n]); var m:int = n*3; indices [m] = face.a; indices [m + 1] = face.b; indices [m + 2] = face.c; var materialData:MaterialData = defaultMaterialData; if (face.surfaceName != null) { materialData = materialDatas[face.surfaceName]; } if (lastMaterialData != materialData) { lastMaterialData = materialData; materialData.indexBegin = n; materialData.numTriangles = 1; } else { materialData.numTriangles++; } } return indices; } } } import alternativa.engine3d.core.Object3D; import alternativa.engine3d.loaders.ParserMaterial; import flash.geom.Matrix; import flash.geom.Vector3D; class MaterialData { public var name:String; public var ambient:uint; public var diffuse:uint; public var specular:uint; public var glossiness:uint; public var transparency:uint; public var matrix:Matrix; public var material:ParserMaterial; public var maps:Vector. = new Vector.(); public function get diffuseMap():MapData { for each (var map:MapData in maps) { if (map.channel == "diffuse") { return map; } } return null; } // parameters for Mesh.addSurface() public var indexBegin:uint; public var numTriangles:uint; } class MapData { public var channel:String; public var filename:String; public var scaleU:Number = 1; public var scaleV:Number = 1; public var offsetU:Number = 0; public var offsetV:Number = 0; public var rotation:Number = 0; } class ObjectData { public var name:String; // mesh public var vertices:Vector.; public var uvs:Vector.; public var faces:Vector.; public var surfaces:Object; public var surfacesCount:uint; public var smoothGroups:Vector.; public var a:Number; public var b:Number; public var c:Number; public var d:Number; public var e:Number; public var f:Number; public var g:Number; public var h:Number; public var i:Number; public var j:Number; public var k:Number; public var l:Number; // light or camera public var lightColor:int = -1; public var lightOff:Boolean; public var attenuationOn:Boolean; public var hotspot:Number = 0; public var falloff:Number = 0; public var innerRange:Number = 0; public var outerRange:Number = Number.MAX_VALUE; public var multiplier:Number = 1; public var position:Vector3D; public var target:Vector3D; public var bank:Number = 0; public var lens:Number; } class AnimationData { public var chunkId:uint; public var objectName:String; public var object:Object3D; public var parentIndex:int; public var pivot:Vector3D; public var position:Vector3D; public var rotation:Vector3D; public var scale:Vector3D; public var instanceOf:String; } class ChunkInfo { public var id:int; public var size:int; public var dataSize:int; public var dataPosition:int; public var nextChunkPosition:int; } class Vertex { public var x:Number; public var y:Number; public var z:Number; public var u:Number; public var v:Number; public var nonTransformed:Boolean = true; public var normal:Vector3D; public var tangent:Vector3D; public var biTangent:Vector3D; } class Face { public var a:uint; public var b:uint; public var c:uint; public var surface:uint; public var surfaceName:String; public var smoothGroup:uint; public var uvTransformGroup:uint; public var deltaX1:Number; public var deltaY1:Number; public var deltaZ1:Number; public var deltaX2:Number; public var deltaY2:Number; public var deltaZ2:Number; }