mirror of
https://github.com/MapMakersAndProgrammers/alternativa3d-archive.git
synced 2025-10-26 09:49:07 -07:00
974 lines
31 KiB
Plaintext
974 lines
31 KiB
Plaintext
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
|
||
* <table>
|
||
* <tr><td><code>INCHES</code></td><td>переводить в дюймы</td></tr>
|
||
* <tr><td><code>FEET</code></td><td>переводить в футы</td></tr>
|
||
* <tr><td><code>MILES</code></td><td>переводить в мили</td></tr>
|
||
* <tr><td><code>MILLIMETERS</code></td><td>переводить в миллиметры</td></tr>
|
||
* <tr><td><code>CENTIMETERS</code></td><td>переводить в сантиметры</td></tr>
|
||
* <tr><td><code>METERS</code></td><td>переводить в метры</td></tr>
|
||
* <tr><td><code>KILOMETERS</code></td><td>переводить в километры</td></tr>
|
||
* </table>
|
||
*/
|
||
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;
|
||
} |