package alternativa.engine3d.loaders { import alternativa.engine3d.*; import alternativa.engine3d.core.Face; import alternativa.engine3d.core.Mesh; import alternativa.engine3d.core.Object3D; import alternativa.engine3d.core.Surface; import alternativa.engine3d.core.Vertex; import alternativa.engine3d.materials.FillMaterial; import alternativa.engine3d.materials.TextureMaterial; import alternativa.engine3d.materials.TextureMaterialPrecision; import alternativa.engine3d.materials.WireMaterial; import alternativa.types.Matrix3D; import alternativa.types.Point3D; import alternativa.types.Texture; import alternativa.utils.ColorUtils; import alternativa.utils.MathUtils; import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.BlendMode; import flash.display.Loader; import flash.errors.IOError; import flash.events.Event; import flash.events.EventDispatcher; import flash.events.IOErrorEvent; import flash.geom.Matrix; import flash.geom.Point; import flash.net.URLLoader; import flash.net.URLLoaderDataFormat; import flash.net.URLRequest; import flash.system.LoaderContext; import flash.utils.ByteArray; import flash.utils.Endian; use namespace alternativa3d; /** * Загрузчик моделей в формате 3DS. */ public class Loader3DS extends EventDispatcher { /** * Дюймы */ public static const INCHES:Number = 1; /** * Футы */ public static const FEET:Number = 0.0833333334; /** * Мили */ public static const MILES:Number = 0.0000157828; /** * Миллиметры */ public static const MILLIMETERS:Number = 25.4000000259; /** * Сантиметры */ public static const CENTIMETERS:Number = 2.5400000025; /** * Метры */ public static const METERS:Number = 0.0254; /** * Километры */ public static const KILOMETERS:Number = 0.0000254; private static var stub:BitmapData; private var _content:Object3D; private var version:uint; private var objectDatas:Array; private var animationDatas:Array; private var materialDatas:Array; private var bitmaps:Array; private var urlLoader:URLLoader; private var data:ByteArray; private var counter:uint; private var sequence:Array; private var loader:Loader; private var context:LoaderContext; private var path:String; /** * Повтор текстуры при заливке */ public var repeat:Boolean = true; /** * Сглаживание текстур при масштабировании */ public var smooth:Boolean = false; /** * Режим наложения цветов */ public var blendMode:String = BlendMode.NORMAL; /** * Точность перспективной коррекции */ public var precision:Number = TextureMaterialPrecision.MEDIUM; /** * Определяет в какие единицы измерения перевести координаты модели. * Может быть любым значением из констант Loader3DS * * * * * * * * *
INCHESпереводить в дюймы
FEETпереводить в футы
MILESпереводить в мили
MILLIMETERSпереводить в миллиметры
CENTIMETERSпереводить в сантиметры
METERSпереводить в метры
KILOMETERSпереводить в километры
*/ public var units:Number = INCHES; /** * Уровень мобильности */ public var mobility:int = 0; /** * Загрузка модели. * * @param url адрес, откуда загружается модель * @param context */ public function load(url:String, context:LoaderContext = null):void { path = url.substring(0, url.lastIndexOf("/") + 1); this.context = context; urlLoader = new URLLoader(); urlLoader.dataFormat = URLLoaderDataFormat.BINARY; urlLoader.load(new URLRequest(url)); urlLoader.addEventListener(Event.COMPLETE, on3DSLoad); urlLoader.addEventListener(IOErrorEvent.IO_ERROR, on3DSError); urlLoader.addEventListener(IOErrorEvent.NETWORK_ERROR, on3DSError); urlLoader.addEventListener(IOErrorEvent.VERIFY_ERROR, on3DSError); // Очистка _content = null; version = 0; objectDatas = null; animationDatas = null; materialDatas = null; bitmaps = null; sequence = null; if (loader != null) { loader.contentLoaderInfo.removeEventListener(Event.COMPLETE, loadNextBitmap); loader.close(); loader = null; } } private function on3DSLoad(e:Event):void { data = urlLoader.data; data.endian = Endian.LITTLE_ENDIAN; parse3DSChunk(0, data.bytesAvailable); urlLoader.removeEventListener(Event.COMPLETE, on3DSLoad); urlLoader.removeEventListener(IOErrorEvent.IO_ERROR, on3DSError); urlLoader.removeEventListener(IOErrorEvent.NETWORK_ERROR, on3DSError); urlLoader.removeEventListener(IOErrorEvent.VERIFY_ERROR, on3DSError); urlLoader = null; } private function on3DSError(e:Event):void { _content = null; throw new IOError(IOErrorEvent(e).text); urlLoader.removeEventListener(Event.COMPLETE, on3DSLoad); urlLoader.removeEventListener(IOErrorEvent.IO_ERROR, on3DSError); urlLoader.removeEventListener(IOErrorEvent.NETWORK_ERROR, on3DSError); urlLoader.removeEventListener(IOErrorEvent.VERIFY_ERROR, on3DSError); } private function loadBitmaps():void { if (bitmaps != null) { counter = 0; sequence = new Array(); for (var filename:String in bitmaps) { sequence.push(filename); } loader = new Loader(); loader.load(new URLRequest(path + sequence[counter]), context); loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadNextBitmap); loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, loadNextBitmap); loader.contentLoaderInfo.addEventListener(IOErrorEvent.NETWORK_ERROR, loadNextBitmap); loader.contentLoaderInfo.addEventListener(IOErrorEvent.VERIFY_ERROR, loadNextBitmap); } else { buildContent(); } } private function loadNextBitmap(e:Event = null):void { if (!(e is IOErrorEvent)) { bitmaps[sequence[counter]] = Bitmap(loader.content).bitmapData; } else { if (stub == null) { var size:uint = 10; stub = new BitmapData(size, size, false, 0); for (var i:uint = 0; i < size; i++) { for (var j:uint = 0; j < size; j+=2) { stub.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF); } } } bitmaps[sequence[counter]] = stub; } counter++; if (counter < sequence.length) { loader.load(new URLRequest(path + sequence[counter]), context); } else { buildContent(); } if (e is IOErrorEvent) { throw new IOError(IOErrorEvent(e).text); } } private function buildContent():void { var i:uint; var length:uint; // Формируем связи объектов _content = new Object3D(); // Создаём материалы var materialData:MaterialData; for (var materialName:String in materialDatas) { materialData = materialDatas[materialName]; var mapData:MapData = materialData.diffuseMap; var materialMatrix:Matrix = new Matrix(); if (mapData != null) { var rot:Number = MathUtils.toRadian(mapData.rotation); var rotSin:Number = Math.sin(rot); var rotCos:Number = Math.cos(rot); materialMatrix.translate(-mapData.offsetU, mapData.offsetV); materialMatrix.translate(-0.5, -0.5); materialMatrix.rotate(-rot); materialMatrix.scale(mapData.scaleU, mapData.scaleV); materialMatrix.translate(0.5, 0.5); } materialData.matrix = materialMatrix; } // Если есть данные об анимации и иерархии объектов var objectName:String; var objectData:ObjectData; var mesh:Mesh; if (animationDatas != null) { if (objectDatas != null) { length = animationDatas.length; for (i = 0; i < length; i++) { var animationData:AnimationData = animationDatas[i]; objectName = animationData.objectName; objectData = objectDatas[objectName]; if (objectData != null && objectData.vertices != null) { // Меш mesh = new Mesh(objectName); animationData.object = mesh; buildObject(animationData); buildMesh(mesh, objectData, animationData); } else { var object:Object3D = new Object3D(objectName); animationData.object = object; buildObject(animationData); } } buildHierarchy(_content, 0, length - 1); } } else { for (objectName in objectDatas) { objectData = objectDatas[objectName]; if (objectData.vertices != null) { // Меш mesh = new Mesh(objectName); buildMesh(mesh, objectData, null); _content.addChild(mesh); } } } // Рассылаем событие о завершении dispatchEvent(new Event(Event.COMPLETE)); } private function buildObject(animationData:AnimationData):void { var object:Object3D = animationData.object; if (animationData.position != null) { object.x = animationData.position.x; object.y = animationData.position.y; object.z = animationData.position.z; } 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; } object.mobility = mobility; } private function buildMesh(mesh:Mesh, objectData:ObjectData, animationData:AnimationData):void { // Добавляем вершины var i:uint; var length:uint = objectData.vertices.length; for (i = 0; i < length; i++) { var vertexData:Point3D = objectData.vertices[i]; objectData.vertices[i] = mesh.addVertex(vertexData.x, vertexData.y, vertexData.z, i); } // Коррекция вершин if (animationData != null) { // Инвертируем матрицу objectData.matrix.invert(); // Вычитаем точку привязки из смещения матрицы objectData.matrix.d -= animationData.pivot.x; objectData.matrix.h -= animationData.pivot.y; objectData.matrix.l -= animationData.pivot.z; // Трансформируем вершины for (var key:* in mesh._vertices) { var vertex:Vertex = mesh._vertices[key]; vertex._coords.transform(objectData.matrix); vertex._coords.multiply(units); } } // Добавляем грани length = objectData.faces.length; for (i = 0; i < length; i++) { var faceData:FaceData = objectData.faces[i]; mesh.addFace([objectData.vertices[faceData.a], objectData.vertices[faceData.b], objectData.vertices[faceData.c]], i); } // Добавляем поверхности if (objectData.surfaces != null) { for (var surfaceId:String in objectData.surfaces) { var materialData:MaterialData = materialDatas[surfaceId]; var surfaceData:SurfaceData = objectData.surfaces[surfaceId]; var surface:Surface = mesh.addSurface(surfaceData.faces, surfaceId); if (materialData.diffuseMap != null || materialData.normalMap != null) { surface.material = new TextureMaterial(new Texture(bitmaps[materialData.diffuseMap.filename], materialData.diffuseMap.filename), 1 - materialData.transparency/100, repeat, smooth, blendMode, precision); length = surfaceData.faces.length; if (objectData.uvs != null) { for (i = 0; i < length; i++) { var id:uint = surfaceData.faces[i]; var fd:FaceData = objectData.faces[id]; var face:Face = mesh.getFaceById(id); var aUV:Point = objectData.uvs[fd.a]; var bUV:Point = objectData.uvs[fd.b]; var cUV:Point = objectData.uvs[fd.c]; if (aUV != null && bUV != null && cUV != null && ((bUV.x - aUV.x)*(cUV.y - aUV.y) - (bUV.y - aUV.y)*(cUV.x - aUV.x) != 0)) { face.aUV = materialData.matrix.transformPoint(aUV); face.bUV = materialData.matrix.transformPoint(bUV); face.cUV = materialData.matrix.transformPoint(cUV); } } } } else { surface.material = new FillMaterial(materialDatas[surfaceId].color, 1 - materialData.transparency/100); } } } else { // Поверхность по умолчанию var defaultSurface:Surface = mesh.addSurface(); // Добавляем грани for (var faceId:String in mesh._faces) { defaultSurface.addFace(mesh._faces[faceId]); } defaultSurface.material = new WireMaterial(0); } } private function buildHierarchy(parent:Object3D, begin:uint, end:uint):void { if (begin <= end) { var animation:AnimationData = animationDatas[begin]; var object:Object3D = animation.object; parent.addChild(object); var parentIndex:uint = animation.parentIndex; for (var i:uint = begin + 1; i <= end; i++) { animation = animationDatas[i]; if (parentIndex == animation.parentIndex) { buildHierarchy(object, begin + 1, i - 1); buildHierarchy(parent, i, end); return; } } buildHierarchy(object, begin + 1, end); } } private function parse3DSChunk(index:uint, length:uint):void { if (length > 6) { data.position = index; var chunkId:uint = data.readUnsignedShort(); var chunkLength:uint = data.readUnsignedInt(); var dataIndex:uint = index + 6; var dataLength:uint = chunkLength - 6; switch (chunkId) { // Главный case 0x4D4D: parseMainChunk(dataIndex, dataLength); break; } parse3DSChunk(index + chunkLength, length - chunkLength); } else { // Загрузка битмап loadBitmaps(); } } private function parseMainChunk(index:uint, length:uint):void { if (length > 6) { data.position = index; var chunkId:uint = data.readUnsignedShort(); var chunkLength:uint = data.readUnsignedInt(); var dataIndex:uint = index + 6; var dataLength:uint = chunkLength - 6; switch (chunkId) { // Версия case 0x0002: parseVersion(dataIndex); break; // 3D-сцена case 0x3D3D: parse3DChunk(dataIndex, dataLength); break; // Анимация case 0xB000: parseAnimationChunk(dataIndex, dataLength); break; } parseMainChunk(index + chunkLength, length - chunkLength); } } private function parseVersion(index:uint):void { data.position = index; version = data.readUnsignedInt(); } private function parse3DChunk(index:uint, length:uint):void { if (length > 6) { data.position = index; var chunkId:uint = data.readUnsignedShort(); var chunkLength:uint = data.readUnsignedInt(); var dataIndex:uint = index + 6; var dataLength:uint = chunkLength - 6; switch (chunkId) { // Материал case 0xAFFF: // Парсим материал var material:MaterialData = new MaterialData(); parseMaterialChunk(material, dataIndex, dataLength); break; // Объект case 0x4000: // Создаём данные объекта var object:ObjectData = new ObjectData(); var objectLength:uint = parseObject(object, dataIndex); // Парсим объект parseObjectChunk(object, dataIndex + objectLength, dataLength - objectLength); break; } parse3DChunk(index + chunkLength, length - chunkLength); } } private function parseMaterialChunk(material:MaterialData, index:uint, length:uint):void { if (length > 6) { data.position = index; var chunkId:uint = data.readUnsignedShort(); var chunkLength:uint = data.readUnsignedInt(); var dataIndex:uint = index + 6; var dataLength:uint = chunkLength - 6; switch (chunkId) { // Имя материала case 0xA000: parseMaterialName(material, dataIndex); break; // Ambient color case 0xA010: break; // Diffuse color case 0xA020: data.position = dataIndex + 6; material.color = ColorUtils.rgb(data.readUnsignedByte(), data.readUnsignedByte(), data.readUnsignedByte()); break; // Specular color case 0xA030: break; // Shininess percent case 0xA040: data.position = dataIndex + 6; material.glossiness = data.readUnsignedShort(); break; // Shininess strength percent case 0xA041: data.position = dataIndex + 6; material.specular = data.readUnsignedShort(); break; // Transperensy case 0xA050: data.position = dataIndex + 6; material.transparency = data.readUnsignedShort(); break; // Texture map 1 case 0xA200: material.diffuseMap = new MapData(); parseMapChunk(material.diffuseMap, dataIndex, dataLength); break; // Texture map 2 case 0xA33A: break; // Opacity map case 0xA210: break; // Bump map case 0xA230: material.normalMap = new MapData(); parseMapChunk(material.normalMap, dataIndex, dataLength); break; // Shininess map case 0xA33C: break; // Specular map case 0xA204: break; // Self-illumination map case 0xA33D: break; // Reflection map case 0xA220: break; } parseMaterialChunk(material, index + chunkLength, length - chunkLength); } } private function parseMaterialName(material:MaterialData, index:uint):void { // Создаём список материалов, если надо if (materialDatas == null) { materialDatas = new Array(); } // Получаем название материала material.name = getString(index); // Помещаем данные материала в список materialDatas[material.name] = material; } private function parseMapChunk(map:MapData, index:uint, length:uint):void { if (length > 6) { data.position = index; var chunkId:uint = data.readUnsignedShort(); var chunkLength:uint = data.readUnsignedInt(); var dataIndex:uint = index + 6; var dataLength:uint = chunkLength - 6; switch (chunkId) { // Имя файла case 0xA300: map.filename = getString(dataIndex).toLowerCase(); if (bitmaps == null) { bitmaps = new Array(); } bitmaps[map.filename] = null; break; // Масштаб по U case 0xA354: data.position = dataIndex; map.scaleU = data.readFloat(); break; // Масштаб по V case 0xA356: data.position = dataIndex; map.scaleV = data.readFloat(); break; // Смещение по U case 0xA358: data.position = dataIndex; map.offsetU = data.readFloat(); break; // Смещение по V case 0xA35A: data.position = dataIndex; map.offsetV = data.readFloat(); break; // Угол поворота case 0xA35C: data.position = dataIndex; map.rotation = data.readFloat(); break; } parseMapChunk(map, index + chunkLength, length - chunkLength); } } private function parseObject(object:ObjectData, index:uint):uint { // Создаём список объектов, если надо if (objectDatas == null) { objectDatas = new Array(); } // Получаем название объекта object.name = getString(index); // Помещаем данные объекта в список objectDatas[object.name] = object; return object.name.length + 1; } private function parseObjectChunk(object:ObjectData, index:uint, length:uint):void { if (length > 6) { data.position = index; var chunkId:uint = data.readUnsignedShort(); var chunkLength:uint = data.readUnsignedInt(); var dataIndex:uint = index + 6; var dataLength:uint = chunkLength - 6; switch (chunkId) { // Меш case 0x4100: parseMeshChunk(object, dataIndex, dataLength); break; // Источник света case 0x4600: break; // Камера case 0x4700: break; } parseObjectChunk(object, index + chunkLength, length - chunkLength); } } private function parseMeshChunk(object:ObjectData, index:uint, length:uint):void { if (length > 6) { data.position = index; var chunkId:uint = data.readUnsignedShort(); var chunkLength:uint = data.readUnsignedInt(); var dataIndex:uint = index + 6; var dataLength:uint = chunkLength - 6; switch (chunkId) { // Вершины case 0x4110: parseVertices(object, dataIndex); break; // UV case 0x4140: parseUVs(object, dataIndex); break; // Трансформация case 0x4160: parseMatrix(object, dataIndex); break; // Грани case 0x4120: var facesLength:uint = parseFaces(object, dataIndex); parseFacesChunk(object, dataIndex + facesLength, dataLength - facesLength); break; } parseMeshChunk(object, index + chunkLength, length - chunkLength); } } private function parseVertices(object:ObjectData, index:uint):void { data.position = index; var num:uint = data.readUnsignedShort(); object.vertices = new Array(); for (var i:uint = 0; i < num; i++) { object.vertices.push(new Point3D(data.readFloat(), data.readFloat(), data.readFloat())); } } private function parseUVs(object:ObjectData, index:uint):void { data.position = index; var num:uint = data.readUnsignedShort(); object.uvs = new Array(); for (var i:uint = 0; i < num; i++) { object.uvs.push(new Point(data.readFloat(), data.readFloat())); } } private function parseMatrix(object:ObjectData, index:uint):void { data.position = index; object.matrix = new Matrix3D(); object.matrix.a = data.readFloat(); object.matrix.e = data.readFloat(); object.matrix.i = data.readFloat(); object.matrix.b = data.readFloat(); object.matrix.f = data.readFloat(); object.matrix.j = data.readFloat(); object.matrix.c = data.readFloat(); object.matrix.g = data.readFloat(); object.matrix.k = data.readFloat(); object.matrix.d = data.readFloat(); object.matrix.h = data.readFloat(); object.matrix.l = data.readFloat(); } private function parseFaces(object:ObjectData, index:uint):uint { data.position = index; var num:uint = data.readUnsignedShort(); object.faces = new Array(); for (var i:uint = 0; i < num; i++) { var face:FaceData = new FaceData(); face.a = data.readUnsignedShort(); face.b = data.readUnsignedShort(); face.c = data.readUnsignedShort(); object.faces.push(face); data.position += 2; // Пропускаем флаг } return 2 + num*8; } private function parseFacesChunk(object:ObjectData, index:uint, length:uint):void { if (length > 6) { data.position = index; var chunkId:uint = data.readUnsignedShort(); var chunkLength:uint = data.readUnsignedInt(); var dataIndex:uint = index + 6; var dataLength:uint = chunkLength - 6; switch (chunkId) { // Поверхности case 0x4130: parseSurface(object, dataIndex); break; // Группы сглаживания case 0x4150: break; } parseFacesChunk(object, index + chunkLength, length - chunkLength); } } private function parseSurface(object:ObjectData, index:uint):void { // Создаём данные поверхности var surface:SurfaceData = new SurfaceData(); // Создаём список поверхностей, если надо if (object.surfaces == null) { object.surfaces = new Array(); } // Получаем название материала surface.materialName = getString(index); // Помещаем данные поверхности в список object.surfaces[surface.materialName] = surface; // Получаем грани поверхности data.position = index + surface.materialName.length + 1; var num:uint = data.readUnsignedShort(); surface.faces = new Array(); for (var i:uint = 0; i < num; i++) { surface.faces.push(data.readUnsignedShort()); } } private function parseAnimationChunk(index:uint, length:uint):void { if (length > 6) { data.position = index; var chunkId:uint = data.readUnsignedShort(); var chunkLength:uint = data.readUnsignedInt(); var dataIndex:uint = index + 6; var dataLength:uint = chunkLength - 6; switch (chunkId) { // Анимация объекта case 0xB001: case 0xB002: case 0xB003: case 0xB004: case 0xB005: case 0xB006: case 0xB007: var animation:AnimationData = new AnimationData(); if (animationDatas == null) { animationDatas = new Array(); } animationDatas.push(animation); parseObjectAnimationChunk(animation, dataIndex, dataLength); break; // Таймлайн case 0xB008: break; } parseAnimationChunk(index + chunkLength, length - chunkLength); } } private function parseObjectAnimationChunk(animation:AnimationData, index:uint, length:uint):void { if (length > 6) { data.position = index; var chunkId:uint = data.readUnsignedShort(); var chunkLength:uint = data.readUnsignedInt(); var dataIndex:uint = index + 6; var dataLength:uint = chunkLength - 6; switch (chunkId) { // Идентификация объекта и его связь case 0xB010: parseObjectAnimationInfo(animation, dataIndex); break; // Точка привязки объекта (pivot) case 0xB013: parseObjectAnimationPivot(animation, dataIndex); break; // Смещение объекта относительно родителя case 0xB020: parseObjectAnimationPosition(animation, dataIndex); break; // Поворот объекта относительно родителя (angle-axis) case 0xB021: parseObjectAnimationRotation(animation, dataIndex); break; // Масштабирование объекта относительно родителя case 0xB022: parseObjectAnimationScale(animation, dataIndex); break; } parseObjectAnimationChunk(animation, index + chunkLength, length - chunkLength); } } private function parseObjectAnimationInfo(animation:AnimationData, index:uint):void { var name:String = getString(index); data.position = index + name.length + 1 + 4; animation.objectName = name; animation.parentIndex = data.readUnsignedShort(); } private function parseObjectAnimationPivot(animation:AnimationData, index:uint):void { data.position = index; animation.pivot = new Point3D(data.readFloat(), data.readFloat(), data.readFloat()); } private function parseObjectAnimationPosition(animation:AnimationData, index:uint):void { data.position = index + 20; animation.position = new Point3D(data.readFloat(), data.readFloat(), data.readFloat()); } private function parseObjectAnimationRotation(animation:AnimationData, index:uint):void { data.position = index + 20; animation.rotation = getRotationFrom3DSAngleAxis(data.readFloat(), data.readFloat(), data.readFloat(), data.readFloat()); } private function parseObjectAnimationScale(animation:AnimationData, index:uint):void { data.position = index + 20; animation.scale = new Point3D(data.readFloat(), data.readFloat(), data.readFloat()); } public function get content():Object3D { return _content; } // Считываем строку заканчивающуюся на нулевой байт public function getString(index:uint):String { data.position = index; var charCode:uint = data.readByte(); var res:String = ""; while (charCode != 0) { res += String.fromCharCode(charCode); charCode = data.readByte(); } return res; } private function getRotationFrom3DSAngleAxis(angle:Number, x:Number, z:Number, y:Number):Point3D { var res:Point3D = new Point3D(); 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 > 0.998) { 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 < -0.998) { 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; } } } import alternativa.engine3d.core.Object3D; import alternativa.types.Matrix3D; import alternativa.types.Point3D; import flash.geom.Matrix; class MaterialData { public var name:String; public var color:uint; public var specular:uint; public var glossiness:uint; public var transparency:uint; public var diffuseMap:MapData; public var normalMap:MapData; public var matrix:Matrix; } class MapData { 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; public var vertices:Array; public var uvs:Array; public var matrix:Matrix3D; public var faces:Array; public var surfaces:Array; } class FaceData { public var a:uint; public var b:uint; public var c:uint; } class SurfaceData { public var materialName:String; public var faces:Array; } class AnimationData { public var objectName:String; public var object:Object3D; public var parentIndex:uint; public var pivot:Point3D; public var position:Point3D; public var rotation:Point3D; public var scale:Point3D; }