diff --git a/src/alternativa/engine3d/loaders/ParserFBX.as b/src/alternativa/engine3d/loaders/ParserFBX.as
new file mode 100644
index 0000000..04ac72d
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/ParserFBX.as
@@ -0,0 +1,1214 @@
+package alternativa.engine3d.loaders {
+
+ import alternativa.engine3d.loaders.filmbox.*;
+ import alternativa.engine3d.loaders.filmbox.readers.*;
+ import alternativa.engine3d.loaders.filmbox.versions.*;
+ import alternativa.types.Long;
+
+ import commons.A3DMatrix;
+
+ import flash.geom.Matrix;
+ import flash.geom.Matrix3D;
+ import flash.utils.ByteArray;
+ import flash.utils.Dictionary;
+ import flash.utils.Endian;
+
+ import versions.version2.a3d.A3D2;
+ import versions.version2.a3d.animation.*;
+ import versions.version2.a3d.geometry.*;
+ import versions.version2.a3d.materials.*;
+ import versions.version2.a3d.objects.*;
+
+ /**
+ * Парсер файлов формата .fbx, предоставленных в виде ByteArray.
+ */
+ public class ParserFBX extends Parser {
+ /**
+ * Выполняет парсинг.
+ * Результаты парсинга находятся в списках objects, parents, materials и textureMaterials.
+ * @param data Файл в формате .fbx, предоставленный в виде ByteArray.
+ * @param texturesBaseURL Базовый путь к файлам текстур. Во время парсинга свойствам текстурных материалов diffuseMapURL и opacityMapURL устанавливаются строковые значения, состоящие из texturesBaseURL и имени файла.
+ * @param scale Величина, на которую умножаются координаты вершин, координаты объектов и значения масштабирования объектов.
+ * @see alternativa.engine3d.loaders.ParserMaterial
+ * @see #objects
+ * @see #parents
+ * @see #hierarchy
+ * @see #materials
+ */
+ public function parse(data:ByteArray, texturesBaseURL:String = "", scale:Number = 1):void {
+ this.texturesBaseURL = texturesBaseURL;
+ this.scale = scale;
+
+ data.endian = Endian.LITTLE_ENDIAN;
+
+ var head:String = data.readUTFBytes(5);
+ data.position -= 5;
+
+ switch (head) {
+ case "; FBX":
+ fileReader = new ReaderText(data);
+ break;
+ case "Kayda":
+ fileReader = new ReaderBinary();
+ break;
+ default:
+ fileReader = null;
+ break;
+ }
+
+ if (fileReader != null) {
+ fileVersion = new VUnknown();
+ stack = [];
+ heap = {};
+
+ while (fileReader.hasDataLeft()) {
+ stack.length = fileReader.getDepth();
+
+ fileVersion.parseCurrentRecord(fileReader, stack, heap);
+
+ if (fileVersion is VUnknown) {
+ switch ((fileVersion as VUnknown).majorVersion) {
+ case 5:
+ fileVersion = new V5();
+ break;
+ case 6:
+ fileVersion = new V6();
+ break;
+ case 7:
+ fileVersion = new V7();
+ break;
+ }
+ }
+ }
+ trace("parsed: 0 =", fileReader.getDepth());
+
+ ids = new IncrementalIDGenerator2();
+ a3d = new A3D2(new Vector.(), new Vector.(),
+ new Vector.(), new Vector.(), null, null, // cube maps, decals
+ new Vector.(), new Vector.(), new Vector.(),
+ new Vector.(), new Vector.(), new Vector.(),
+ new Vector.(), new Vector.(), new Vector.(),
+ new Vector.(), new Vector.(), null, // sprites
+ new Vector.());
+
+ var key:String, nodeNames:Dictionary = new Dictionary();
+ for (key in heap) {
+ var node:KFbxNode = heap [key] as KFbxNode;
+ if (node) {
+ convertNode(node, nodeNames [node] = heapKeyToName(key));
+ }
+ }
+ // for перебирает ключи в случайном порядке, посему парсим анимацию после всех нод
+ for (key in heap) {
+ var animation:KFbxAnimStack = heap [key] as KFbxAnimStack;
+ if (animation) {
+ convertAnimation(animation, heapKeyToName(key), nodeNames);
+ }
+ }
+
+ complete(a3d);
+ trace("converted,", hierarchy.length, "(" + objects.length + ")");
+ }
+ }
+
+ private var texturesBaseURL:String;
+ private var scale:Number;
+
+ private var fileReader:IReader;
+ private var fileVersion:IVersion;
+ private var stack:Array;
+ private var heap:Object;
+ private var ids:IncrementalIDGenerator2;
+ private var a3d:A3D2;
+
+ private function heapKeyToName(key:String):String {
+ var iodc:int = key.indexOf("::");
+ return (iodc < 0) ? key : key.substr(iodc + 2);
+ }
+
+ /** @inheritDoc */
+ override public function clean():void {
+ super.clean();
+ fileReader = null;
+ stack = null;
+ heap = null;
+ ids = null;
+ a3d = null;
+ }
+
+ private function convertAnimation(animation:KFbxAnimStack, name:String, nodeNames:Dictionary):void {
+ var tracks:Vector. = convertAnimationLayers(animation, nodeNames);
+ /* Repin: objIds are deprecated
+ var objIds:Vector. = collectAnimatedObjectIds (tracks);*/
+ if (tracks.length > 0) {
+ a3d.animationClips.push(new A3D2AnimationClip(a3d.animationClips.length, true /* loop? */, name, null
+ /*objIds*/, tracks));
+ }
+ }
+
+ /*private function collectAnimatedObjectIds (tracks:Vector.):Vector. {
+ var ids:Vector. = new Vector.;
+ for (var i:int = 0, n:int = tracks.length; i < n; i++) {
+ var name:String = a3d.animationTracks [tracks [i]].objectName;
+ var id:Long;
+
+ // find matching joints
+ for (var j:int = 0, m:int = a3d.joints.length; j < m; j++) {
+ if (name == a3d.joints [j].name) {
+ id = a3d.joints [j].id; j = m;
+ }
+ }
+
+ if (id == null) {
+ // attempt to match other 3d objects
+ for (j = 0, m = a3d.objects.length; j < m; j++) {
+ if (name == a3d.objects [j].name) {
+ id = a3d.objects [j].id; j = m;
+ }
+ }
+ }
+
+ if ((id != null) && (ids.indexOf (id) < 0)) {
+ ids.push (id);
+ }
+ }
+
+ if (ids.length > 0) {
+ return ids;
+ }
+
+ return null;
+ }*/
+
+ private function convertAnimationLayers(animation:KFbxAnimStack, nodeNames:Dictionary):Vector. {
+ // в fbx следущая структура:
+ // stack 1-n layer 1-n curveNode 1-n curve;
+ // у нас это всё дерево curveNode-ов;
+ // на выходе надо набор A3D2Track-ов.
+ var tracks:Vector. = new Vector.();
+
+ for each (var layer:KFbxAnimLayer in animation.layers) {
+ layer.fixV6();
+
+ // собираем все KFbxNode в слое
+ var nodes:Vector. = new Vector.();
+ layer.collectNodes(nodes);
+
+ for each (var node:KFbxNode in nodes) {
+ // собираем все curveNode для данного KFbxNode
+ var curves:Vector. = new Vector.();
+ layer.collectCurves(curves, node);
+
+ // времена ключей всех кривых в curves не обязаны быть согласованными;
+ // собираем и упорядочиваем все времена
+ var times:Vector. = collectKeyTimes(curves);
+
+ // создаём A3D2Track
+ tracks.push(a3d.animationTracks.length);
+ a3d.animationTracks.push(new A3D2Track(a3d.animationTracks.length,
+ calculateKeyframes(curves, times, node.transformationClone()), nodeNames [node]));
+ }
+
+ }
+
+ return tracks;
+ }
+
+ private function calculateKeyframes(curves:Vector., times:Vector.,
+ node2:KFbxNode):Vector. {
+ var nodeProperties:Object = {
+ T:"LclTranslation", R:"LclRotation", S:"LclScaling"
+ }
+
+ var frames:Vector. = new Vector.(times.length);
+
+ for (var i:int = 0, n:int = times.length; i < n; i++) {
+ var t:Number = times [int(i)];
+ for each (var curve:KFbxAnimCurveNode in curves) {
+ var property:Vector. = node2 [nodeProperties [curve.channel]];
+ if (property) {
+ for each (var terminalCurve:KFbxAnimCurveNode in curve.curveNodes) {
+
+ if (terminalCurve.KeyTime.length == 0) {
+ // this does happen for some reason :(
+ continue;
+ }
+
+ var index:int;
+ switch (terminalCurve.channel) {
+ case "X":
+ index = 0;
+ break;
+ case "Y":
+ index = 1;
+ break;
+ case "Z":
+ index = 2;
+ break;
+ default:
+ index = -1;
+ break;
+ }
+ if (index >= 0) {
+ // совпадает время ключа?
+ if ((terminalCurve.KeyTime.length > i) && (terminalCurve.KeyTime [int(i)] == t)) {
+ // да
+ property [index] = terminalCurve.KeyValueFloat [int(i)];
+ } else {
+ // нет, интерполируем
+ property [index] = terminalCurve.interpolateValue(t);
+ }
+ }
+ }
+ }
+ }
+
+ // SDK: The time unit in FBX (KTime) is 1/46186158000 of one second.
+ frames [i] = new A3D2Keyframe(t/4.6186158e10, convertMatrix(node2.calculateNodeTransformation()));
+ }
+
+ return frames;
+ }
+
+ private function collectKeyTimes(curves:Vector.):Vector. {
+ // не совпадают ли времена ключей во всех кривых?
+ var timesAreSame:Boolean;
+ var times:Vector. = curves [0].curveNodes [0].KeyTime;
+ for each (var curve:KFbxAnimCurveNode in curves) {
+ for each (var terminalCurve:KFbxAnimCurveNode in curve.curveNodes) {
+
+ if (terminalCurve.KeyTime.length == 0) {
+ // this does happen for some reason :(
+ // will be ignored later, so ignore now
+ continue;
+ }
+
+ if (terminalCurve.KeyTime.length != times.length) {
+ timesAreSame = false;
+ break;
+ } else {
+ // времена всё ещё могут быть разными
+ for (var i:int = 0, n:int = terminalCurve.KeyTime.length; i < n; i++) {
+ if (times [int(i)] != terminalCurve.KeyTime [int(i)]) {
+ timesAreSame = false;
+ break;
+ }
+ }
+ if (!timesAreSame) break;
+ }
+ }
+ if (!timesAreSame) break;
+ }
+
+ if (timesAreSame) {
+ return times;
+ }
+
+ // нет, не совпадают
+ var ts:Array = [];
+ for each (curve in curves) {
+ for each (terminalCurve in curve.curveNodes) {
+ for (i = 0, n = terminalCurve.KeyTime.length; i < n; i++) {
+ var t:Number = terminalCurve.KeyTime [int(i)];
+ if (ts.indexOf(t) < 0) {
+ ts.push(t);
+ }
+ }
+ }
+ }
+
+ ts.sort(Array.NUMERIC);
+ return Vector.(ts);
+ }
+
+ private function convertNode(node:KFbxNode, name:String):void {
+ if (node.attributes.length == 0) {
+ // пустой 3D объект
+ a3d.objects.push(new A3D2Object(-1, ids.getID(node), name, ids.getID(node.parent),
+ convertMatrix(node.calculateNodeTransformation()), node.isVisible()));
+ //trace ("Object3D:", name);
+
+ } else {
+ var nodeTransform:Matrix3D = node.calculateNodeTransformation();
+ var attrTransform:Matrix3D = node.calculateAttributesTransformation();
+
+ var useWrapperNode:Boolean = (attrTransform != null) || (node.attributes.length > 1);
+ if (useWrapperNode) {
+ // промежуточная нода для поддержки "geometric transform"
+ // аттрибутов из 3dmax или множества аттрибутов
+ a3d.objects.push(new A3D2Object(-1, ids.getID(node), null, ids.getID(node.parent),
+ convertMatrix(nodeTransform), node.isVisible()));
+
+ nodeTransform = attrTransform;
+ }
+
+ for each (var attribute:KFbxNodeAttribute in node.attributes) {
+ var id:Long = useWrapperNode ? ids.getID(new KFbxNode) : ids.getID(node);
+ var pid:Long = useWrapperNode ? ids.getID(node) : ids.getID(node.parent);
+
+ // mesh, light, и т.п.
+ var mesh:KFbxMesh = attribute as KFbxMesh;
+ if (mesh) {
+ convertMesh(mesh, node, name, id, pid, nodeTransform);
+ continue;
+ }
+
+ var light:KFbxLight = attribute as KFbxLight;
+ if (light) {
+ //nodeTransform.prependRotation (90, Vector3D.X_AXIS); TODO fix this shit
+ convertLight(light, node, name, id, pid, nodeTransform);
+ continue;
+ }
+
+ var joint:KFbxSkeleton = attribute as KFbxSkeleton;
+ if (joint) {
+ convertJoint(joint, node, name, id, pid, nodeTransform);
+ continue;
+ }
+ }
+ }
+ }
+
+ private function convertJoint(joint:KFbxSkeleton, node:KFbxNode, name:String, id:Long, pid:Long,
+ nodeTransform:Matrix3D):void {
+ a3d.joints.push(new A3D2Joint(-1, id, name, pid, convertMatrix(nodeTransform), node.isVisible()));
+ }
+
+ private function convertLight(light:KFbxLight, node:KFbxNode, name:String, id:Long, pid:Long,
+ nodeTransform:Matrix3D):void {
+ switch (light.LightType) {
+ case -1:
+ a3d.ambientLights.push(new A3D2AmbientLight(-1, convertColor(light.Color), id, light.getIntensity(),
+ name, pid, convertMatrix(nodeTransform), node.isVisible()));
+ break;
+ case 0:
+ a3d.omniLights.push(new A3D2OmniLight(light.FarAttenuationStart*scale,
+ light.FarAttenuationEnd*scale, -1, convertColor(light.Color), id, light.getIntensity(),
+ name, pid, convertMatrix(nodeTransform), node.isVisible()));
+ break;
+ case 1:
+ a3d.directionalLights.push(new A3D2DirectionalLight(-1, convertColor(light.Color), id,
+ light.getIntensity(), name, pid, convertMatrix(nodeTransform), node.isVisible()));
+ break;
+ case 2:
+ var rad:Number = Math.PI/180;
+ a3d.spotLights.push(new A3D2SpotLight(light.FarAttenuationStart*scale,
+ light.FarAttenuationEnd*scale, -1, convertColor(light.Color), rad*light.Coneangle,
+ rad*((light.HotSpot > 0) ? light.HotSpot : light.Coneangle), id, light.getIntensity(), name,
+ pid, convertMatrix(nodeTransform), node.isVisible()));
+ break;
+ }
+ }
+
+ private function convertColor(color:Vector.):uint {
+ return uint(255*color [0])*65536 + uint(255*color [1])*256 + uint(255*color [2]);
+ }
+
+ private function convertMesh(mesh:KFbxMesh, node:KFbxNode, name:String, id:Long, pid:Long,
+ nodeTransform:Matrix3D):void {
+ var mi:MaterialsInfo = prepareMaterialsInfo(mesh, node);
+ var vbr:ConvertVertexBufferResult = convertVertexBuffer(mesh, mi);
+ var ibr:ConvertIndexBufferResult = convertIndexBuffer(node,
+ convertPolygonsMap(mesh.PolygonVertexIndex, vbr.polygonVerticesMap), mi);
+ bakeTextureTransforms(mesh, vbr, ibr);
+ if (mesh.deformers.length > 0) {
+ // skins
+ var csr:ConvertSkinsResult = convertSkinsAndPatchVertexBuffer(mesh, vbr, ibr);
+ a3d.skins.push(new A3D2Skin(vbr.bboxId, id, ibr.ibufId, csr.jointBindTransforms, csr.joints, name,
+ csr.numJoints, pid, ibr.surfaces, convertMatrix(nodeTransform), Vector.([vbr.vbufId]),
+ node.isVisible()));
+ } else {
+ // meshes
+ a3d.meshes.push(new A3D2Mesh(vbr.bboxId, id, ibr.ibufId, name, pid, ibr.surfaces,
+ convertMatrix(nodeTransform), Vector.([vbr.vbufId]), node.isVisible()));
+ }
+ }
+
+ private function convertSkinsAndPatchVertexBuffer(mesh:KFbxMesh, vbr:ConvertVertexBufferResult,
+ ibr:ConvertIndexBufferResult):ConvertSkinsResult {
+ var vertexBuffer:A3D2VertexBuffer = a3d.vertexBuffers [vbr.vbufId];
+
+ var surfaceCount:int = ibr.surfaces.length;
+ var vertexCount:uint = vertexBuffer.vertexCount;
+ var maxJointsPerVertexDoubled:int = 8*2;
+
+ var jointsAndWeights:Vector. = new Vector.(maxJointsPerVertexDoubled*vertexCount, true);
+ var freeSpotOffsets:Vector. = new Vector.(vertexCount, true);
+
+ var numJoints:Vector. = new Vector.(surfaceCount, true);
+
+ // все кластеры соответствуют renderedJoints (при условии одного джоинта на кластер TODO выяснить)
+ var matrix1:Matrix3D = new Matrix3D(), matrix2:Matrix3D = new Matrix3D();
+ var jointIds:Vector. = new Vector.();
+ var jointBindTransforms:Vector. = new Vector.();
+ for each (var skin:KFbxSkin in mesh.deformers) {
+ var i:int = jointBindTransforms.length;
+ jointBindTransforms.length += skin.clusters.length;
+ for each (var cluster:KFbxCluster in skin.clusters) {
+ var jId:Long = ids.getID(cluster.jointNode);
+ jointIds [i] = jId;
+ // http://area.autodesk.com/forum/autodesk-fbx/fbx-sdk/question-about-skinning-could-you-please-help-me-jiayang/
+ // Cluster TransformLink is the global matrix of the bone(link) at the binding moment.
+ // Cluster Transform is the global matrix of the geometry at the binding moment.
+ // email from Jiayang.Xu@autodesk.com:
+ // FBX writer will write it as: Transform = TransformLink.Inverse() * Transform;
+ // But then FBX reader will do "Transform = TransformLink * Transform;" to compensate what is done in writer.
+ // email from Robert.Goulet@autodesk.com:
+ // I think this was because the inversed transform was going right into the graphic card transform in FiLMBOX for
+ // real-time rendering. Since the renderer needed the inverse matrix, that's how it got stored in the file.
+ matrix1.rawData = cluster.Transform;
+ matrix2.rawData = cluster.TransformLink;
+ jointBindTransforms [i] = new A3D2JointBindTransform(convertMatrix(matrix1), jId);
+ // weights and surfaces
+ var i3:int = i*3;
+ for (var j:int = 0, n:int = cluster.Indexes.length; j < n; j++) {
+ var weight:Number = cluster.Weights [j];
+ var affectedVertices:Vector. = vbr.vertexClonesMap [cluster.Indexes [j]];
+ for (var k:int = 0, m:int = affectedVertices.length; k < m; k++) {
+ var vertexIndex:int = affectedVertices [k];
+ var p:int = freeSpotOffsets [vertexIndex];
+ var q:int = maxJointsPerVertexDoubled*vertexIndex + p;
+ jointsAndWeights [q] = i3;
+ jointsAndWeights [q + 1] = weight;
+ freeSpotOffsets [vertexIndex] = p + 2;
+ }
+ }
+
+ i++;
+ }
+ }
+
+ n = jointIds.length;
+ var surfaceJoints:Vector. = new Vector.();
+ for (i = 0; i < surfaceCount; i++) {
+ numJoints [i] = n;
+ for (j = 0; j < n; j++) {
+ surfaceJoints.push(jointIds [j]);
+ }
+ }
+
+ // skin data converted; re-assemble vertex buffer
+ var usedJointsPerVertexDoubled:int = 0;
+ for (i = 0; i < vertexCount; i++) {
+ n = freeSpotOffsets [i];
+ if (n > usedJointsPerVertexDoubled) usedJointsPerVertexDoubled = n;
+ }
+
+ if (usedJointsPerVertexDoubled%4 > 0) {
+ usedJointsPerVertexDoubled += 4 - (usedJointsPerVertexDoubled%4);
+ }
+
+ if (usedJointsPerVertexDoubled > maxJointsPerVertexDoubled) {
+ usedJointsPerVertexDoubled = maxJointsPerVertexDoubled;
+ }
+
+ var jointsAttributesPerVertex:int = usedJointsPerVertexDoubled/4;
+
+ for (i = 0; i < jointsAttributesPerVertex; i++) {
+ vertexBuffer.attributes.push(A3D2VertexAttributes.JOINT);
+ }
+
+ var bytesIn:ByteArray = vertexBuffer.byteBuffer;
+ bytesIn.position = 0;
+ var bytesOut:ByteArray = new ByteArray();
+ bytesOut.endian = bytesIn.endian;
+ i = 0;
+ while (bytesIn.bytesAvailable) {
+ bytesIn.readBytes(bytesOut, bytesOut.position, vbr.bytesPerVertex);
+ bytesOut.position = bytesOut.length;
+ for (j = 0, k = i*maxJointsPerVertexDoubled; j < usedJointsPerVertexDoubled; j++) {
+ bytesOut.writeFloat(jointsAndWeights [k + j]);
+ }
+ i++;
+ }
+ vertexBuffer.byteBuffer = bytesOut;
+
+ // will not be used, but just to be consistent
+ vbr.bytesPerVertex += jointsAttributesPerVertex*4*4;
+
+ return new ConvertSkinsResult(jointBindTransforms, surfaceJoints, numJoints);
+ }
+
+ private function bakeTextureTransforms(mesh:KFbxMesh, vbr:ConvertVertexBufferResult,
+ ibr:ConvertIndexBufferResult):void {
+ // пока трансформация текстур не поддерживается A3D материалами, жарим uv
+ var writtenVertices:Object = new Object();
+ var bytes:ByteArray = a3d.vertexBuffers [vbr.vbufId].byteBuffer;
+
+ var texture:KFbxTexture, nonTrivialTransformationFound:Boolean;
+ for (var i:int = 0, j:int = -1; i < vbr.polygonVerticesMap.length; i++) {
+ // текстура вершины?
+ if ((i == 0) || (mesh.PolygonVertexIndex [i - 1] < 0)) {
+ j++;
+ texture = ibr.textures [j];
+ nonTrivialTransformationFound = false;
+ }
+
+ if (texture) {
+
+ var k:int = vbr.polygonVerticesMap [i];
+ if (writtenVertices [k]) continue;
+ writtenVertices [k] = true;
+
+ var T:Matrix = texture.transformation;
+ if (T == null) T = texture.calculateTextureTransformation();
+
+ if (!nonTrivialTransformationFound) {
+ var d:Number = 0, di:Number;
+ di = T.a - 1;
+ if (di > 0) d += di; else d -= di;
+ di = T.b;
+ if (di > 0) d += di; else d -= di;
+ di = T.c;
+ if (di > 0) d += di; else d -= di;
+ di = T.d - 1;
+ if (di > 0) d += di; else d -= di;
+ di = T.tx;
+ if (di > 0) d += di; else d -= di;
+ di = T.ty;
+ if (di > 0) d += di; else d -= di;
+ nonTrivialTransformationFound = (d > 1e-6);
+ }
+
+ if (nonTrivialTransformationFound) {
+ var p:int = vbr.bytesPerVertex*k + vbr.uvOffset;
+
+ bytes.position = p;
+ var u:Number = bytes.readFloat();
+ var v:Number = 1 - bytes.readFloat();
+
+ bytes.position = p;
+ bytes.writeFloat(u*T.a + v*T.b + T.tx);
+ bytes.writeFloat(1 - (u*T.c + v*T.d + T.ty));
+ } else {
+ // если тривиальная трансформация, пропускаем все вершины в этом полике
+ texture = null;
+ }
+ }
+ }
+ }
+
+ private function convertIndexBuffer(node:KFbxNode, polygons:Vector.>,
+ materialsInfo:MaterialsInfo):ConvertIndexBufferResult {
+
+ var sizeOfShort:int = 2;
+ var indicesData:ByteArray = new ByteArray();
+ indicesData.endian = Endian.LITTLE_ENDIAN;
+
+ var surfaces:Vector. = new Vector.();
+ var indexBegin:int = 0, numTriangles:int = 0;
+
+ var textures:Vector. = new Vector.(polygons [0].length);
+
+ var materialHashTable:Object = new Object(); // Dictionary?
+ var lastMaterialHash:int = -1, lastMaterial:KFbxSurfaceMaterial = new KFbxSurfaceMaterial();
+
+ var polygons0:Vector. = polygons [0];
+ var polygons1:Vector. = polygons [1];
+
+ for (var i:int = 0, n:int = polygons0.length; i <= n; i++) {
+ // выполняем цикл на 1 раз больше, чем нужно, чтобы создать последний сурфейс TODO поправить
+ var materialHash:int = (i < n) ? calculateMaterialHash(i, materialsInfo) : int.MAX_VALUE;
+
+ if ((lastMaterialHash != materialHash) && !(lastMaterialHash < 0)) {
+ // detected end of surface - get or create the material
+ if (materialHashTable [lastMaterialHash] == null) {
+ calculateMaterial(i - 1, node, materialsInfo, lastMaterial);
+ materialHashTable [lastMaterialHash] = convertMaterial(lastMaterial);
+
+ // заодно коллекционируем текстуры по полигонам
+ // (эта хрень нужна только для текстурных трансформаций)
+ var texture:KFbxTexture = lastMaterial.textures ["diffuse"];
+ if (texture != null) {
+ for (j = i - 1; (j > -1) && (textures [j] == null); j--) {
+ textures [j] = texture;
+ }
+ }
+ }
+
+ surfaces.push(new A3D2Surface(indexBegin, materialHashTable [lastMaterialHash], numTriangles));
+ indexBegin = indicesData.length/sizeOfShort;
+ numTriangles = 0;
+ }
+
+ if (i < n) {
+ for (var j:int = polygons0 [i], m:int = (i < n - 1) ? polygons0 [i + 1] : polygons1.length; j < m; j++) {
+ indicesData.writeShort(polygons1 [j]);
+ if (j%3 == 0) numTriangles++;
+ }
+
+ lastMaterialHash = materialHash;
+ }
+ }
+
+ return new ConvertIndexBufferResult(a3d.indexBuffers.push(new A3D2IndexBuffer(indicesData,
+ a3d.indexBuffers.length, indicesData.length/sizeOfShort)) - 1, surfaces, textures);
+ }
+
+ private function convertMaterial(lastMaterial:KFbxSurfaceMaterial):int {
+ return a3d.materials.push(new A3D2Material(convertTexture(lastMaterial.textures ["diffuse"]),
+ convertTexture(lastMaterial.textures ["glossiness"]), a3d.materials.length,
+ convertTexture(lastMaterial.textures ["emission"]), convertTexture(lastMaterial.textures ["bump"]),
+ convertTexture(lastMaterial.textures ["transparent"]), -1,
+ convertTexture(lastMaterial.textures ["specular"]))) - 1;
+ }
+
+ private function convertTexture(texture:KFbxTexture):int {
+ if (texture) {
+ var file:String = texture.RelativeFilename;
+ if (texturesBaseURL != "") {
+ var slash:int = file.lastIndexOf("\\");
+ if (slash < 0) slash = file.lastIndexOf("/");
+ if (slash > 1) file = file.substr(slash + 1);
+ file = texturesBaseURL + file;
+ }
+ return a3d.maps.push(new A3D2Map(0, a3d.maps.length,
+ a3d.images.push(new A3D2Image(a3d.images.length, file)) - 1)) - 1;
+ }
+ return -1;
+ }
+
+ private function prepareMaterialsInfo(mesh:KFbxMesh, node:KFbxNode):MaterialsInfo {
+ // это вынесено сюда, т.к. кроме создания материалов в convertIndexBuffer нам придётся
+ // дублировать вершины для граней с трансформирующими материалами в convertVertexBuffer
+ var info:MaterialsInfo = new MaterialsInfo();
+ var layer:KFbxLayer = mesh.layers [0];
+ info.matLayer = layer.getLayerElement(KFbxLayerElementMaterial) as KFbxLayerElementMaterial;
+ info.texLayers = new Vector.();
+ for each (var channel:String in ["diffuse", "specular", "bump"]) {
+ var texLayer:KFbxLayerElementTexture = layer.getLayerElement(KFbxLayerElementTexture, "renderChannel",
+ channel) as KFbxLayerElementTexture;
+ if (texLayer) info.texLayers.push(texLayer);
+ }
+ info.materialHashBase = 1 + Math.max(node.materials.length, node.textures.length);
+ return info;
+ }
+
+ private function calculateMaterialHash(i:int, materialsInfo:MaterialsInfo):int {
+ // хеш материала нужен для сравнения материалов поликов - т.е. не имеет права давать коллизии
+ var materialHash:int = 0;
+ // кроме ByPolygon, маппинг может быть AllSame или NoMappingInformation
+ // в обоих случаях нет нужды менять materialHash
+ if (materialsInfo.matLayer && (materialsInfo.matLayer.MappingInformationType == "ByPolygon")) {
+ materialHash = materialsInfo.matLayer.Materials [i];
+ }
+ for each (var texLayer:KFbxLayerElementTexture in materialsInfo.texLayers) {
+ if (texLayer.MappingInformationType == "ByPolygon") {
+ var textureId:int = texLayer.TextureId [i];
+ if (textureId < 0) textureId = -1;
+ materialHash = materialHash*materialsInfo.materialHashBase + textureId;
+ }
+ }
+ return materialHash;
+ }
+
+ private function calculateMaterial(i:int, node:KFbxNode, materialsInfo:MaterialsInfo,
+ material:KFbxSurfaceMaterial):void {
+ // TODO support material templates for v7
+ if (materialsInfo.matLayer) {
+ if (materialsInfo.matLayer.MappingInformationType == "AllSame") {
+ node.materials [materialsInfo.matLayer.Materials [0]].copyTo(material);
+ } else if (materialsInfo.matLayer.MappingInformationType == "ByPolygon") {
+ node.materials [materialsInfo.matLayer.Materials [i]].copyTo(material);
+ }
+ }
+ for each (var texLayer:KFbxLayerElementTexture in materialsInfo.texLayers) {
+ if (texLayer.MappingInformationType == "ByPolygon") {
+ var index:int = texLayer.TextureId [i];
+ // v5 can have mapping but no textures, or negative indices :(
+ if ((index >= 0) && (index < node.textures.length)) {
+ material.textures [texLayer.renderChannel] = node.textures [index];
+ }
+ } else if (texLayer.MappingInformationType == "AllSame") {
+ material.textures [texLayer.renderChannel] = node.textures [texLayer.TextureId [0]];
+ }
+ }
+ }
+
+ private function convertPolygonsMap(rawPolygonsData:Vector.,
+ polygonVerticesMap:Vector.):Vector.> {
+ // map [0] = pointers in map [1]
+ // map [1] = triangle indices for original polygons
+ var map:Vector.> = new Vector.>(2, true);
+ var pointers:Vector. = new Vector.();
+ map [0] = pointers;
+ var triangles:Vector. = new Vector.();
+ map [1] = triangles;
+ for (var k:int = 1, m:int = 0, n:int = rawPolygonsData.length; k < n; k++) {
+ var index:int = rawPolygonsData [k];
+ if (index < 0) {
+ // end of polygon - triangulate it
+ pointers.push(triangles.length);
+ var i:int = m, j:int = k, done:Boolean = false;
+ do {
+ i++;
+ if (i < j) {
+ triangles.push(polygonVerticesMap [i - 1], polygonVerticesMap [i], polygonVerticesMap [j]);
+ } else {
+ done = true;
+ }
+
+ j--;
+ if (i < j) {
+ triangles.push(polygonVerticesMap [j], polygonVerticesMap [j + 1], polygonVerticesMap [i]);
+ } else {
+ done = true;
+ }
+ } while (!done);
+ m = k + 1;
+ }
+ }
+ return map;
+ }
+
+ private function convertVertexBuffer(mesh:KFbxMesh, materialsInfo:MaterialsInfo):ConvertVertexBufferResult {
+ var vertices:Vector. = mesh.Vertices, polyIndex:Vector. = mesh.PolygonVertexIndex;
+
+ var uvsLayer:KFbxLayerElementUV = mesh.layers [0].getLayerElement(KFbxLayerElementUV) as KFbxLayerElementUV;
+ if (uvsLayer == null) uvsLayer = KFbxLayerElementUV.generateDefaultTextureMap(mesh);
+
+ var normalsLayer:KFbxLayerElementNormal = mesh.layers [0].getLayerElement(KFbxLayerElementNormal) as KFbxLayerElementNormal;
+ if (normalsLayer == null) normalsLayer = KFbxLayerElementNormal.generateDefaultNormals(mesh);
+
+ var uvs:Vector. = uvsLayer.UV;
+ var uvIndices:Vector. = uvsLayer.UVIndex;
+ var normals:Vector. = normalsLayer.Normals;
+
+ // переводим все возможные uvsLayer.MappingInformationType/ReferenceInformationType в "ByPolygonVertex"/"IndexToDirect"
+ var uvIndicesByPolygonVertex:Vector. = convertUVMappingToByPolygonVertex(uvsLayer.MappingInformationType,
+ uvsLayer.ReferenceInformationType, uvIndices, polyIndex, uvs);
+
+ // переводим все возможные normalsLayer.MappingInformationType в "ByPolygonVertex" c ReferenceInformationType = "IndexToDirect"
+ var normalIndicesByPolygonVertex:Vector. = convertNormalsToByPolygonVertex(normalsLayer.MappingInformationType,
+ normals, polyIndex);
+
+ var i:int, j:int, k:int;
+
+ // т.к. комбинаций координат/UV/нормалей м.б. больше чем вершин, последние надо клонировать
+ // при этом нужны карты вершин старые->новые для триангуляции поликов и цепляния костей
+ // pmap [i] = индекс вершины (или клона) на позиции i в mesh.PolygonVertexIndex
+ // vmap [i] = массив индексов вершины и клонов, соответствующих вершине i в mesh.Vertices
+ var pmap:Vector. = new Vector.(polyIndex.length);
+ var vmap:Vector.> = new Vector.>(j = vertices.length/3, true);
+ for (i = 0; i < j; i++) vmap [i] = new Vector.();
+
+ // теперь разворачиваем вертексы
+ var vertexHashBaseUV:int = 1 + uvs.length/2;
+ var vertexHashBasePolyVertices:int = 1 + Math.max(j, polyIndex.length);
+ var vertexHashTable:Dictionary = new Dictionary();
+
+ // TODO учитывать только трансформирующие материалы?
+ var lastPolygon:int = 0;
+ var lastMaterialHash:int = calculateMaterialHash(0, materialsInfo);
+
+ var polyLength:int = polyIndex.length;
+ for (i = 0; i < polyLength; i++) {
+ var vertexIdx:int = polyIndex [i];
+ if (vertexIdx < 0) vertexIdx = -vertexIdx - 1;
+
+ if ((i > 0) && (polyIndex [i - 1] < 0)) {
+ lastPolygon++;
+ lastMaterialHash = calculateMaterialHash(lastPolygon, materialsInfo);
+ }
+
+ // нужен хеш без коллизий; int уже не достаточно
+ var vertexHash:Long = Long.getLong(vertexHashBaseUV*vertexIdx + uvIndicesByPolygonVertex [i],
+ vertexHashBasePolyVertices*lastMaterialHash + normalIndicesByPolygonVertex [i]);
+
+ if (vertexHashTable [vertexHash] != null) {
+ pmap [i] = vertexHashTable [vertexHash];
+ } else {
+ if (vmap [vertexIdx].length == 0) {
+ // vertexIdx встретился первый раз
+ pmap [i] = vertexHashTable [vertexHash] = vertexIdx;
+ vmap [vertexIdx].push(vertexIdx);
+ } else {
+ // нужен клон
+ pmap [i] = vertexHashTable [vertexHash] = j;
+ vmap [vertexIdx].push(j);
+ j++;
+ }
+ }
+ }
+
+ // считаем тангенты (только для исходных вершин)
+ var tangents:Vector. = calculateVertexTangents(vertices, uvs, polyIndex, uvIndicesByPolygonVertex,
+ normalIndicesByPolygonVertex);
+
+ // наконец, пишем буффер
+ var bytesPerVertex:int = (3 + 2 + 3 + 4)*4;
+ var bytes:ByteArray = new ByteArray();
+ bytes.endian = Endian.LITTLE_ENDIAN;
+ bytes.length = bytesPerVertex*j;
+
+ var x:Number, y:Number, z:Number, cx:Number, cy:Number, cz:Number, nx:Number, ny:Number, nz:Number, tx:Number, ty:Number, tz:Number;
+ var minX:Number = +Number.MAX_VALUE, minY:Number = +Number.MAX_VALUE, minZ:Number = +Number.MAX_VALUE;
+ var maxX:Number = -Number.MAX_VALUE, maxY:Number = -Number.MAX_VALUE, maxZ:Number = -Number.MAX_VALUE;
+
+ var writtenVertices:Object = new Object();
+
+ for (i = 0; i < polyIndex.length; i++) {
+ k = pmap [i];
+ if (writtenVertices [k]) continue;
+ bytes.position = bytesPerVertex*k;
+ writtenVertices [k] = true;
+
+ j = polyIndex [i];
+ if (j < 0) j = -j - 1;
+ j *= 3;
+ bytes.writeFloat(x = scale*vertices [j]);
+ bytes.writeFloat(y = scale*vertices [j + 1]);
+ bytes.writeFloat(z = scale*vertices [j + 2]);
+
+ if (minX > x) minX = x;
+ if (maxX < x) maxX = x;
+ if (minY > y) minY = y;
+ if (maxY < y) maxY = y;
+ if (minZ > z) minZ = z;
+ if (maxZ < z) maxZ = z;
+
+ k = uvIndicesByPolygonVertex [i]*2;
+ bytes.writeFloat(uvs [k]);
+ bytes.writeFloat(1 - uvs [k + 1]);
+
+ k = normalIndicesByPolygonVertex [i]*3;
+ bytes.writeFloat(nx = normals [k]);
+ bytes.writeFloat(ny = normals [k + 1]);
+ bytes.writeFloat(nz = normals [k + 2]);
+
+ j *= 2;
+ tx = tangents [j];
+ ty = tangents [j + 1];
+ tz = tangents [j + 2];
+
+ var dot:Number = tx*nx + ty*ny + tz*nz;
+ tx -= nx*dot;
+ ty -= ny*dot;
+ tz -= nz*dot;
+ dot = Math.sqrt(tx*tx + ty*ty + tz*tz + 1e-5);
+ tx /= dot;
+ ty /= dot;
+ tz /= dot;
+
+ bytes.writeFloat(tx);
+ bytes.writeFloat(ty);
+ bytes.writeFloat(tz);
+
+ cx = ny*tz - nz*ty;
+ cy = nz*tx - nx*tz;
+ cz = nx*ty - ny*tx;
+
+ dot = cx*tangents [j + 3] + cy*tangents [j + 4] + cz*tangents [j + 5];
+
+ bytes.writeFloat((dot < 0) ? -1 : 1);
+ }
+ //trace ("bbox:", minX, minY, minZ, maxX, maxY, maxZ);
+
+ return new ConvertVertexBufferResult(a3d.vertexBuffers.push(new A3D2VertexBuffer(Vector.(
+ [
+ A3D2VertexAttributes.POSITION, A3D2VertexAttributes.TEXCOORD, A3D2VertexAttributes.NORMAL,
+ A3D2VertexAttributes.TANGENT4
+ ]), bytes, a3d.vertexBuffers.length, bytes.length/bytesPerVertex)) - 1,
+
+ (minX <= maxX) ? a3d.boxes.push(new A3D2Box(Vector.([minX, minY, minZ, maxX, maxY, maxZ]),
+ a3d.boxes.length)) - 1 : -1,
+
+ vmap, pmap, bytesPerVertex, 3*4);
+ }
+
+ private function calculateVertexTangents(vertices:Vector., uvs:Vector.,
+ polyIndex:Vector., uvIndicesByPolygonVertex:Vector.,
+ normalIndicesByPolygonVertex:Vector.):Vector. {
+
+ var tangents:Vector. = new Vector.(vertices.length*2);
+
+ var i:int = 0;
+ var x0:Number, y0:Number, z0:Number, u0:Number, v0:Number;
+ var x1:Number, y1:Number, z1:Number, u1:Number, v1:Number;
+ var x2:Number, y2:Number, z2:Number, u2:Number, v2:Number;
+ //var vn:Vector3D;
+ while (i < polyIndex.length) {
+ var a:int = polyIndex [i], a3:int = a*3;
+ var b:int = polyIndex [i + 1], b3:int = b*3;
+ var c:int = polyIndex [i + 2], c3:int;
+ if (c < 0) c = -c - 1;
+ c3 = c*3;
+
+ x0 = vertices [a3];
+ y0 = vertices [a3 + 1];
+ z0 = vertices [a3 + 2];
+ x1 = vertices [b3];
+ y1 = vertices [b3 + 1];
+ z1 = vertices [b3 + 2];
+ x2 = vertices [c3];
+ y2 = vertices [c3 + 1];
+ z2 = vertices [c3 + 2];
+
+ var deltaX1:Number = x1 - x0;
+ var deltaY1:Number = y1 - y0;
+ var deltaZ1:Number = z1 - z0;
+ var deltaX2:Number = x2 - x0;
+ var deltaY2:Number = y2 - y0;
+ var deltaZ2:Number = z2 - z0;
+
+ a = uvIndicesByPolygonVertex [i]*2;
+ b = uvIndicesByPolygonVertex [i + 1]*2;
+ c = uvIndicesByPolygonVertex [i + 2]*2;
+
+ u0 = uvs [a];
+ v0 = uvs [a + 1];
+ u1 = uvs [b];
+ v1 = uvs [b + 1];
+ u2 = uvs [c];
+ v2 = uvs [c + 1];
+
+ var deltaU1:Number = u1 - u0;
+ var deltaV1:Number = v0 - v1;
+ var deltaU2:Number = u2 - u0;
+ var deltaV2:Number = v0 - v2;
+
+ var invdet:Number = 1/(deltaU1*deltaV2 - deltaU2*deltaV1);
+ if (invdet > 1e9) invdet = 1e9; else if (invdet < -1e9) invdet = -1e9;
+
+ 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;
+
+ do {
+ c = polyIndex [i++];
+ if (c < 0) c = -c - 1;
+ c *= 6;
+ tangents [c] += tangentX;
+ tangents [c + 1] += tangentY;
+ tangents [c + 2] += tangentZ;
+ tangents [c + 3] += biTangentX;
+ tangents [c + 4] += biTangentY;
+ tangents [c + 5] += biTangentZ;
+ } while (polyIndex [i - 1] >= 0);
+ }
+
+ return tangents;
+ }
+
+ private function convertUVMappingToByPolygonVertex(mappingInformationType:String,
+ referenceInformationType:String, uvIndices:Vector., polygonVertexIndex:Vector.,
+ uvs:Vector.):Vector. {
+
+ var i:int, j:int;
+ var n:int = polygonVertexIndex.length;
+ var uvIndicesByPolygonVertex:Vector. = new Vector.(n);
+
+ if (referenceInformationType == "Direct") {
+ switch (mappingInformationType) {
+ case "ByVertice":
+ for (i = 0; i < n; i++) {
+ j = polygonVertexIndex [i];
+ if (j < 0) j = -j - 1;
+ uvIndicesByPolygonVertex [i] = j;
+ }
+ break;
+ case "ByPolygonVertex":
+ convertByPolygonDataFromDirectToIndex(uvs, uvIndicesByPolygonVertex);
+ break;
+ }
+ } else {
+ switch (mappingInformationType) {
+ case "ByVertice":
+ for (i = 0; i < n; i++) {
+ j = polygonVertexIndex [i];
+ if (j < 0) j = -j - 1;
+ uvIndicesByPolygonVertex [i] = uvIndices [j];
+ }
+ break;
+ case "ByPolygonVertex":
+ for (i = 0; i < n; i++) {
+ uvIndicesByPolygonVertex [i] = uvIndices [i];
+ }
+ break;
+ }
+ }
+ return uvIndicesByPolygonVertex;
+ }
+
+ private function convertNormalsToByPolygonVertex(mappingInformationType:String, normals:Vector.,
+ polygonVertexIndex:Vector.):Vector. {
+
+ var i:int, j:int;
+ var n:int = polygonVertexIndex.length;
+ var normalsByPolygonVertex:Vector. = new Vector.(n);
+
+ // нормали почему-то всегда "Direct" - тут мы строим индексы для иммитации "IndexToDirect"
+ switch (mappingInformationType) {
+ case "ByVertice":
+ for (i = 0; i < n; i++) {
+ j = polygonVertexIndex [i];
+ if (j < 0) j = -j - 1;
+ normalsByPolygonVertex [i] = j;
+ }
+ break;
+ case "ByPolygonVertex":
+ convertByPolygonDataFromDirectToIndex(normals, normalsByPolygonVertex);
+ break;
+ }
+
+ return normalsByPolygonVertex;
+ }
+
+ private function convertByPolygonDataFromDirectToIndex(data:Vector., /* out */
+ indices:Vector.):void {
+ var i:int, j:int, k:int, n:int = indices.length, stride:int = data.length/n;
+ // тут можно было бы сделать for (i = 0; i < n; i++) indices [i] = i; return;
+ // но тогда вершины во всех поликах будут уникальны (клонированы), поэтому придётся
+ // искать повторения данных, т.к. все экспортируют в режиме ByPolygonVertex
+ var similarDataIndices:Object = new Object();
+ for (i = 0; i < n; i++) {
+ j = i*stride;
+ // тут ожидаются коллизии: данные ЮВ в диапазоне 0..1, нормали -1..1
+ // множитель при data [j + k]: больше - память на массивы, меньше - перебор длинных массивов
+ var dataHash:int = 0;
+ for (k = 0; k < stride; k++) {
+ dataHash = 200*dataHash + int(data [j + k]*300);
+ }
+
+ if (similarDataIndices [dataHash]) {
+ var bucket:Vector. = similarDataIndices [dataHash] as Vector.;
+ var miss:Boolean = true;
+ for (var p:int = 0, q:int = bucket.length; p < q; p++) {
+ var candidate:int = bucket [p];
+ var difference:Number, differenceTotal:Number = 0;
+ for (k = 0; k < stride; k++) {
+ difference = data [j + k] - data [candidate + k];
+ if (difference > 0) {
+ differenceTotal += difference;
+ } else {
+ differenceTotal -= difference;
+ }
+ }
+ if (differenceTotal < 1e-4) {
+ // нашли повтор
+ indices [i] = candidate/stride;
+ miss = false;
+ break;
+ }
+ }
+ if (miss) {
+ indices [i] = i;
+ bucket.push(j);
+ }
+ } else {
+ indices [i] = i;
+ similarDataIndices [dataHash] = Vector.([j]);
+ }
+ }
+ }
+
+ private function convertMatrix(m:Matrix3D):A3D2Transform {
+ return convertMatrixRawData(m.rawData);
+ }
+
+ private function convertMatrixRawData(vec:Vector.):A3D2Transform {
+ return new A3D2Transform(new A3DMatrix(vec[0], vec[4], vec[8], vec[12]*scale, vec[1], vec[5], vec[9],
+ vec[13]*scale, vec[2], vec[6], vec[10], vec[14]*scale));
+ }
+ }
+}
+
+import alternativa.engine3d.loaders.filmbox.KFbxLayerElementMaterial;
+import alternativa.engine3d.loaders.filmbox.KFbxLayerElementTexture;
+import alternativa.engine3d.loaders.filmbox.KFbxTexture;
+import alternativa.types.Long;
+
+import flash.utils.Dictionary;
+
+import versions.version2.a3d.objects.A3D2JointBindTransform;
+import versions.version2.a3d.objects.A3D2Surface;
+
+// 1, IncrementalIDGenerator is Object3D-only :(
+class IncrementalIDGenerator2 {
+
+ private var lastID:uint = 0;
+ private var objects:Dictionary = new Dictionary(true);
+
+ public function getID(object:Object):Long {
+ // 2, return null for null?
+ if (object == null) return null;
+
+ var result:Long = this.objects [object];
+ if (result == null) {
+ result = this.objects [object] = Long.fromInt(this.lastID);
+ this.lastID++;
+ }
+ ;
+ return result;
+ }
+}
+
+class MaterialsInfo {
+ public var matLayer:KFbxLayerElementMaterial;
+ public var texLayers:Vector.;
+ public var materialHashBase:int;
+}
+
+class ConvertVertexBufferResult {
+ public var vbufId:int;
+ public var bboxId:int;
+
+ /**
+ * for re-mapping skin clusters.
+ * vertexClonesMap [i] = массив индексов вершины и клонов, соответствующих вершине i в mesh.Vertices
+ */
+ public var vertexClonesMap:Vector.>;
+
+ /**
+ * for re-mapping index buffer.
+ * polygonVerticesMap [i] = индекс вершины (или клона) на позиции i в mesh.PolygonVertexIndex
+ */
+ public var polygonVerticesMap:Vector.;
+
+ public var bytesPerVertex:int, uvOffset:int;
+
+ public function ConvertVertexBufferResult(vbufId:int, bboxId:int, vmap:Vector.>, pmap:Vector.,
+ bpv:int, uvo:int) {
+ this.vbufId = vbufId;
+ this.bboxId = bboxId;
+
+ this.vertexClonesMap = vmap;
+ this.polygonVerticesMap = pmap;
+ this.bytesPerVertex = bpv;
+ this.uvOffset = uvo
+ }
+}
+
+class ConvertIndexBufferResult {
+ public var ibufId:int;
+ public var surfaces:Vector.;
+ public var textures:Vector.;
+
+ public function ConvertIndexBufferResult(ibufId:int, surfaces:Vector., textures:Vector.) {
+ this.ibufId = ibufId;
+ this.surfaces = surfaces;
+ this.textures = textures;
+ }
+}
+
+class ConvertSkinsResult {
+ public var jointBindTransforms:Vector.;
+ public var joints:Vector.;
+ public var numJoints:Vector.;
+
+ public function ConvertSkinsResult(jointBindTransforms:Vector., joints:Vector.,
+ numJoints:Vector.) {
+ this.jointBindTransforms = jointBindTransforms;
+ this.joints = joints;
+ this.numJoints = numJoints;
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxAnimCurveNode.as b/src/alternativa/engine3d/loaders/filmbox/KFbxAnimCurveNode.as
new file mode 100644
index 0000000..873b342
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxAnimCurveNode.as
@@ -0,0 +1,54 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ /**
+ * @private простое дерево, на листочках ключи KFbxAnimCurve
+ */
+ public class KFbxAnimCurveNode {
+ public var node:KFbxNode;
+
+ public var channel:String;
+ public var curveNodes:Vector. = new Vector.();
+
+ public function collectNodes(nodes:Vector.):void {
+ for each (var curveNode:KFbxAnimCurveNode in curveNodes) {
+ if (curveNode.node) {
+ if (nodes.indexOf(curveNode.node) < 0) {
+ nodes.push(curveNode.node);
+ }
+ }
+ curveNode.collectNodes(nodes);
+ }
+ }
+
+ public function collectCurves(curves:Vector., targetNode:KFbxNode):void {
+ for each (var curveNode:KFbxAnimCurveNode in curveNodes) {
+ if (curveNode.node == targetNode) {
+ if (curveNode.curveNodes.length > 0) {
+ // if children are not bound to node...
+ if (curveNode.curveNodes [0].node == null) {
+ // ...collect it...
+ curves.push(curveNode);
+ } else {
+ // ...else keep drilling
+ curveNode.collectCurves(curves, targetNode);
+ }
+ }
+ }
+ }
+ }
+
+ // KFbxAnimCurve:
+ public var KeyTime:Vector. = new Vector.();
+ public var KeyValueFloat:Vector. = new Vector.();
+
+ public function interpolateValue(t:Number):Number {
+ for (var i:int = 1, n:int = KeyTime.length; i < n; i++) {
+ if (t > KeyTime [int(i)]) continue;
+ // linear for now, TODO
+ var c:Number = ( KeyTime [int(i)] - t )/( KeyTime [int(i)] - KeyTime [int(i - 1)] );
+ return KeyValueFloat [int(i)]*(1 - c) + KeyValueFloat [int(i - 1)]*c;
+ }
+ return KeyValueFloat [int(n - 1)];
+ }
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxAnimLayer.as b/src/alternativa/engine3d/loaders/filmbox/KFbxAnimLayer.as
new file mode 100644
index 0000000..a9dd6ac
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxAnimLayer.as
@@ -0,0 +1,28 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ /**
+ * @private SDK: The animation layer is a collection of animation curve nodes.
+
+ Its purpose is to store a variable number of KFbxAnimCurveNodes. The class provides different states flags (bool properties), an animatable weight, and the blending mode flag to indicate how the data on this layer is interacting with the data of the other layers during the evaluation.
+
+ */
+ public class KFbxAnimLayer extends KFbxAnimCurveNode {
+ // TODO blending support
+
+ /** в V6 один получается 1 слой на KFbxNode, но curveNode-ы не привязаны к нему. */
+ public function fixV6():void {
+ fixV6CurveNodes(this);
+ }
+
+ private function fixV6CurveNodes(parent:KFbxAnimCurveNode):void {
+ if (parent.node) {
+ for each (var child:KFbxAnimCurveNode in parent.curveNodes) {
+ if (child.curveNodes.length > 0) {
+ child.node = parent.node;
+ fixV6CurveNodes(child);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxAnimStack.as b/src/alternativa/engine3d/loaders/filmbox/KFbxAnimStack.as
new file mode 100644
index 0000000..0bf38c9
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxAnimStack.as
@@ -0,0 +1,12 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ /**
+ * @private SDK: The Animation stack is a collection of animation layers.
+
+ The Fbx document can have one or more animation stacks. Each stack can be viewed as one "take" in the previous versions of the FBX SDK. The "stack" terminology comes from the fact that the object contains 1 to n animation layers that are evaluated according to their blending modes to produce a resulting animation for a given attribute.
+
+ */
+ public class KFbxAnimStack {
+ public var layers:Vector. = new Vector.();
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxCluster.as b/src/alternativa/engine3d/loaders/filmbox/KFbxCluster.as
new file mode 100644
index 0000000..2eead95
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxCluster.as
@@ -0,0 +1,16 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ /**
+ * @private SDK: Class for clusters (links).
+ * A cluster, or link, is an entity acting on a geometry (KFbxGeometry). More precisely, the cluster acts on a subset of the geometry's control points. For each control point that the cluster acts on, the intensity of the cluster's action is modulated by a weight. The link mode (ELinkMode) specifies how the weights are taken into account.
+ */
+ public class KFbxCluster { // extends KFbxSubDeformer {
+ public var Indexes:Vector. = new Vector.;
+ public var Weights:Vector. = new Vector.;
+ public var Transform:Vector. = Vector.([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
+ public var TransformLink:Vector. = Vector.([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
+
+ // связь с джоинтом
+ public var jointNode:KFbxNode;
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxDeformer.as b/src/alternativa/engine3d/loaders/filmbox/KFbxDeformer.as
new file mode 100644
index 0000000..cc08ae1
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxDeformer.as
@@ -0,0 +1,8 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ /**
+ * @private SDK: Base class for skin deformer (KFbxSkin) and vertex cache deformer (KFbxVertexCacheDeformer).
+ */
+ public class KFbxDeformer {
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxGeometry.as b/src/alternativa/engine3d/loaders/filmbox/KFbxGeometry.as
new file mode 100644
index 0000000..92411c4
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxGeometry.as
@@ -0,0 +1,9 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ /**
+ * @private SDK: The base class of geometric objects that support control point deformations.
+ */
+ public class KFbxGeometry extends KFbxGeometryBase {
+ public var deformers:Vector. = new Vector.();
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxGeometryBase.as b/src/alternativa/engine3d/loaders/filmbox/KFbxGeometryBase.as
new file mode 100644
index 0000000..a132217
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxGeometryBase.as
@@ -0,0 +1,12 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ /**
+ * @private SDK: This class is the base class for geometric object such as meshes, NURBS and patches.
+ */
+ public class KFbxGeometryBase extends KFbxLayerContainer {
+ /**
+ * KArrayTemplate< KFbxVector4 > mControlPoints
+ */
+ public var Vertices:Vector. = new Vector.();
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxLayer.as b/src/alternativa/engine3d/loaders/filmbox/KFbxLayer.as
new file mode 100644
index 0000000..6d614a8
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxLayer.as
@@ -0,0 +1,41 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ /**
+ * @private SDK: A layer can contain one or more of the following layer elements:
+ * Normals
+ * Binormals
+ * Tangents
+ * Materials
+ * Polygon Groups
+ * UVs
+ * Vertex Colors
+ * Smoothing informations
+ * Vertex Creases
+ * Edge Creases
+ * Custom User Data
+ * Visibilities
+ * Textures (diffuse, ambient, specular, etc.) (deprecated)
+ * A typical layer for a Mesh contains Normals, UVs and Materials. A typical layer for NURBS contains only Materials. In the case of the NURBS, the NURBS' parameterization is used for the UVs; no UVs should be specified.
+ * In most cases, you only need a single layer to describe a geometry. Many applications only support what is defined on the first layer. Take this into account when you fill the layer. For example, it is legal to define the Layer 0 with the UVs and then define the model's Normals on layer 1. However if you construct a file this way, it may not be imported correctly in other applications. Store the Normals in Layer 0 to avoid problems.
+ */
+ public class KFbxLayer {
+ public var elements:Vector. = new Vector.();
+
+ public function getLayerElement(type:Class, stringProperty:String = null,
+ value:String = null):KFbxLayerElement {
+ for (var i:int = 0; i < elements.length; i++) {
+ var e:KFbxLayerElement = elements [i];
+ if (e is type) {
+ if (stringProperty == null) {
+ return e;
+ } else {
+ if (e [stringProperty] == value) {
+ return e;
+ }
+ }
+ }
+ }
+ return null;
+ }
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxLayerContainer.as b/src/alternativa/engine3d/loaders/filmbox/KFbxLayerContainer.as
new file mode 100644
index 0000000..6bb4ec4
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxLayerContainer.as
@@ -0,0 +1,9 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ /**
+ * @private SDK: Contains a collection of KFbxLayer objects.
+ */
+ public class KFbxLayerContainer extends KFbxNodeAttribute {
+ public var layers:Vector. = new Vector.();
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxLayerElement.as b/src/alternativa/engine3d/loaders/filmbox/KFbxLayerElement.as
new file mode 100644
index 0000000..c7bbbe9
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxLayerElement.as
@@ -0,0 +1,40 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ /**
+ * @private SDK: A KFbxLayerElement contains Normals, UVs or other kind of information..
+ */
+ public class KFbxLayerElement {
+ /**
+ * Mapping.
+ * "NoMappingInformation"
+ * "ByVertice"
+ * "ByPolygon"
+ * "ByPolygonVertex"
+ * "ByFace"
+ * "ByEdge"
+ * "AllSame"
+ * "ByModel"
+ *
+ * {
+ * eNONE,
+ * eBY_CONTROL_POINT,
+ * eBY_POLYGON_VERTEX,
+ * eBY_POLYGON,
+ * eBY_EDGE,
+ * eALL_SAME
+ * } EMappingMode;
+ */
+ public var MappingInformationType:String;
+ /**
+ * Reference.
+ * "Direct" This indicates that the mapping information for the n'th element
+ * is found in the n'th place of KFbxLayerElementTemplate::mDirectArray.
+ * "Index" This symbol is kept for backward compatibility with FBX v5.0 files.
+ * In FBX v6.0 and higher, this symbol is replaced with "IndexToDirect".
+ * "IndexToDirect" This indicates that the KFbxLayerElementTemplate::mIndexArray
+ * contains, for the n'th element, an index in the KFbxLayerElementTemplate::mDirectArray
+ * array of mapping elements.
+ */
+ public var ReferenceInformationType:String;
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxLayerElementMaterial.as b/src/alternativa/engine3d/loaders/filmbox/KFbxLayerElementMaterial.as
new file mode 100644
index 0000000..b8b8ed3
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxLayerElementMaterial.as
@@ -0,0 +1,16 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ /**
+ * @private SDK: Layer element for mapping materials (KFbxSurfaceMaterial) to a geometry.
+ */
+ public class KFbxLayerElementMaterial extends KFbxLayerElement {
+ public var Materials:Vector. = new Vector.();
+
+ public function KFbxLayerElementMaterial(values:Vector. = null, mapping:String = "ByPolygon",
+ reference:String = "IndexToDirect") {
+ if (values != null) Materials = values;
+ if (mapping != null) MappingInformationType = mapping;
+ if (reference != null) ReferenceInformationType = reference;
+ }
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxLayerElementNormal.as b/src/alternativa/engine3d/loaders/filmbox/KFbxLayerElementNormal.as
new file mode 100644
index 0000000..1f61bf9
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxLayerElementNormal.as
@@ -0,0 +1,71 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ import flash.geom.Vector3D;
+
+ /**
+ * @private SDK: Layer to map Normals on a geometry.
+ */
+ public class KFbxLayerElementNormal extends KFbxLayerElement {
+ public var Normals:Vector. = new Vector.();
+
+ public function KFbxLayerElementNormal(values:Vector. = null, mapping:String = "ByVertice"
+ /* TODO "ByPolygonVertex" ?? */, reference:String = "Direct") {
+ if (values != null) Normals = values;
+ if (mapping != null) MappingInformationType = mapping;
+ if (reference != null) ReferenceInformationType = reference;
+ }
+
+ static public function generateDefaultNormals(mesh:KFbxMesh):KFbxLayerElementNormal {
+ var vertices:Vector. = mesh.Vertices, polyIndex:Vector. = mesh.PolygonVertexIndex;
+
+ var vertexNormals:Vector. = new Vector.(vertices.length);
+
+ var i:int = 0;
+ var va:Vector3D = new Vector3D;
+ var vb:Vector3D = new Vector3D;
+ var vc:Vector3D = new Vector3D;
+ var vn:Vector3D;
+ while (i < polyIndex.length) {
+ var a:int = polyIndex [i], a3:int = a*3;
+ var b:int = polyIndex [i + 1], b3:int = b*3;
+ var c:int = polyIndex [i + 2], c3:int;
+ if (c < 0) c = -c - 1;
+ c3 = c*3;
+ va.x = vertices [a3];
+ va.y = vertices [a3 + 1];
+ va.z = vertices [a3 + 2];
+ vb.x = vertices [b3];
+ vb.y = vertices [b3 + 1];
+ vb.z = vertices [b3 + 2];
+ vc.x = vertices [c3];
+ vc.y = vertices [c3 + 1];
+ vc.z = vertices [c3 + 2];
+ va.decrementBy(vc);
+ vb.decrementBy(vc);
+ vn = va.crossProduct(vb); //vn.normalize ();
+
+ // TODO smoothing groups, "ByPolygonVertex"?
+ do {
+ c = polyIndex [i++];
+ if (c < 0) c = -c - 1;
+ c *= 3;
+ vertexNormals [c] += vn.x;
+ vertexNormals [c + 1] += vn.y;
+ vertexNormals [c + 2] += vn.z;
+ } while (polyIndex [i - 1] >= 0);
+ }
+
+ for (i = 0; i < vertexNormals.length; i += 3) {
+ vn.x = 1e-9 + vertexNormals [i];
+ vn.y = vertexNormals [i + 1];
+ vn.z = vertexNormals [i + 2];
+ vn.normalize();
+ vertexNormals [i] = vn.x;
+ vertexNormals [i + 1] = vn.y;
+ vertexNormals [i + 2] = vn.z;
+ }
+
+ return new KFbxLayerElementNormal(vertexNormals, "ByVertice", "Direct");
+ }
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxLayerElementTexture.as b/src/alternativa/engine3d/loaders/filmbox/KFbxLayerElementTexture.as
new file mode 100644
index 0000000..9729b90
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxLayerElementTexture.as
@@ -0,0 +1,50 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ /**
+ * @private SDK: Layer element for mapping Textures to a geometry.
+ * Deprecated since FBX SDK 2011. Textures (KFbxTexture derived classes)
+ * should be connected to material properties.
+ */
+ public class KFbxLayerElementTexture extends KFbxLayerElement {
+ public var TextureId:Vector. = new Vector.();
+
+ /**
+ * Канал для ParserMaterial. Сам MotionBuilder v6, похоже, умеет только
+ * diffuse текстуры, однако SDK предусматривает кучу типов. К сожалению,
+ * SDK v7 считает KFbxLayerElementTexture устаревшим и не описывает
+ * соответствующие токены (в примере файла найдены токены для specular и bump).
+ *
+ *
+ * В SDK каждый KFbxLayerElement имеет тип, и в зависимости от типа
+ * данные пишутся в конкретный класс-наследник; в KFbxLayerElementTexture
+ * пишутся данные для следующих типов:
+ * eDIFFUSE_TEXTURES Layer Element of type KFbxLayerElementTexture,
+ * eEMISSIVE_TEXTURES Layer Element of type KFbxLayerElementTexture,
+ * eEMISSIVE_FACTOR_TEXTURES Layer Element of type KFbxLayerElementTexture,
+ * eAMBIENT_TEXTURES Layer Element of type KFbxLayerElementTexture,
+ * eAMBIENT_FACTOR_TEXTURES Layer Element of type KFbxLayerElementTexture,
+ * eDIFFUSE_FACTOR_TEXTURES Layer Element of type KFbxLayerElementTexture,
+ * eSPECULAR_TEXTURES Layer Element of type KFbxLayerElementTexture,
+ * eNORMALMAP_TEXTURES Layer Element of type KFbxLayerElementTexture,
+ * eSPECULAR_FACTOR_TEXTURES Layer Element of type KFbxLayerElementTexture,
+ * eSHININESS_TEXTURES Layer Element of type KFbxLayerElementTexture,
+ * eBUMP_TEXTURES Layer Element of type KFbxLayerElementTexture,
+ * eTRANSPARENT_TEXTURES Layer Element of type KFbxLayerElementTexture,
+ * eTRANSPARENCY_FACTOR_TEXTURES Layer Element of type KFbxLayerElementTexture,
+ * eREFLECTION_TEXTURES Layer Element of type KFbxLayerElementTexture,
+ * eREFLECTION_FACTOR_TEXTURES Layer Element of type KFbxLayerElementTexture,
+ * eDISPLACEMENT_TEXTURES Layer Element of type KFbxLayerElementTexture,
+ * eVECTOR_DISPLACEMENT_TEXTURES Layer Element of type KFbxLayerElementTexture.
+ * @see http://download.autodesk.com/global/docs/fbxsdk2012/en_us/cpp_ref/class_k_fbx_layer_element.html#a6478dfad43def5e882aaf6607af3fdae
+ */
+ public var renderChannel:String;
+
+ public function KFbxLayerElementTexture(channel:String = "diffuse", values:Vector. = null,
+ mapping:String = "ByPolygon", reference:String = "IndexToDirect") {
+ if (channel != null) renderChannel = channel;
+ if (values != null) TextureId = values;
+ if (mapping != null) MappingInformationType = mapping;
+ if (reference != null) ReferenceInformationType = reference;
+ }
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxLayerElementUV.as b/src/alternativa/engine3d/loaders/filmbox/KFbxLayerElementUV.as
new file mode 100644
index 0000000..54c6a70
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxLayerElementUV.as
@@ -0,0 +1,37 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ /**
+ * @private SDK: Layer element for mapping UVs to a geometry.
+ */
+ public class KFbxLayerElementUV extends KFbxLayerElement {
+ public var UV:Vector. = new Vector.();
+ public var UVIndex:Vector. = new Vector.();
+
+ public function KFbxLayerElementUV() {
+ // v5 has MappingInformationType set to "ByPolygon" - how the fuck could UVs be by polygon?
+ MappingInformationType = "ByPolygonVertex";
+ ReferenceInformationType = "IndexToDirect";
+ }
+
+ static public function generateDefaultTextureMap(mesh:KFbxMesh):KFbxLayerElementUV {
+ var uv:KFbxLayerElementUV = new KFbxLayerElementUV;
+ var i:int, j:int;
+ for (i = 0; i < mesh.PolygonVertexIndex.length; i++) {
+ j = mesh.PolygonVertexIndex [i];
+ if (j < 0) j = -j - 1;
+ uv.UVIndex [i] = j;
+ }
+ for (i = 0; i < mesh.Vertices.length/3; i++) {
+ j = i*3;
+ var x:Number = mesh.Vertices [j++];
+ var y:Number = mesh.Vertices [j++];
+ var z:Number = mesh.Vertices [j++];
+ var rxy:Number = 1e-5 + Math.sqrt(x*x + y*y);
+ j = i*2;
+ uv.UV [j++] = Math.atan2(rxy, z)*0.159154943 + 0.5;
+ uv.UV [j++] = Math.atan2(y, x)*0.159154943 + 0.5;
+ }
+ return uv;
+ }
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxLight.as b/src/alternativa/engine3d/loaders/filmbox/KFbxLight.as
new file mode 100644
index 0000000..4fb2293
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxLight.as
@@ -0,0 +1,37 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ /**
+ * @private SDK: This node attribute contains methods for accessing the properties of a light..
+ */
+ public class KFbxLight extends KFbxNodeAttribute {
+ public var Color:Vector. = Vector.([1, 1, 1]);
+ /** Percents? Blender uses 0..200 range. */
+ public var Intensity:Number = 100;
+
+ public function getIntensity():Number {
+ return Intensity*1e-2;
+ }
+
+
+ /** Outer cone, degrees. */
+ public var Coneangle:Number = 45;
+ /** Inner cone, degrees. Blender does not write this value (hence -1 default). */
+ public var HotSpot:Number = -1;
+
+ /** 0 point, 1 directional, 2 spot, 3 blender hemi (?.. investigating), -1 ambient */
+ public var LightType:Number = -1;
+
+ /** 0 none, 1 linear, 2 quadratic, 3 cubic */
+ public var DecayType:Number = 0;
+ public var DecayStart:Number = 50;
+
+ /* not supported by alternativa
+ // public var EnableNearAttenuation:Number = 0;
+ public var NearAttenuationStart:Number = 0;
+ public var NearAttenuationEnd:Number = 0; */
+
+ // public var EnableFarAttenuation:Number = 0;
+ public var FarAttenuationStart:Number = 0;
+ public var FarAttenuationEnd:Number = 0;
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxMesh.as b/src/alternativa/engine3d/loaders/filmbox/KFbxMesh.as
new file mode 100644
index 0000000..0ae11a7
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxMesh.as
@@ -0,0 +1,29 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ /**
+ * @private SDK: A mesh is a geometry made of polygons.
+ * The class can define a geometry with as many n-sided polygons as needed. Users can freely mix triangles, quadrilaterals, and other polygons. Since the mesh-related terminology of the FBX SDK differs a little from the known standards, here are our definitions:
+ * A control point is an XYZ coordinate, it is synonym of vertex.
+ * A polygon vertex is an index to a control point (the same control point can be referenced by multiple polygon vertices).
+ * A polygon is a group of polygon vertices. The minimum valid number of polygon vertices to define a polygon is 3.
+ */
+ public class KFbxMesh extends KFbxGeometry {
+ /*
+ 00955 public:
+ 00956 //Please use GetPolygonVertexIndex and GetPolygonVertices to access these arrays.
+ 00957 //DO NOT MODIFY them directly, otherwise unexpected behavior will occur.
+ 00958 //These members are public only for application data copy performance reasons.
+ 00959 struct KFbxPolygon{ int mIndex; int mSize; int mGroup; };
+ 00960 KArrayTemplate mPolygons;
+ 00961 KArrayTemplate mPolygonVertices;
+ */
+
+ /**
+ * mPolygonVertices.
+ * В файле: "PolygonVertexIndex"
+ * Пример: 0,3,2,-2,3,7,6,-3,0,4,7...
+ * отрицательные числа = последняя вершина полика XOR -1 (X XOR -1 = -X -1)
+ */
+ public var PolygonVertexIndex:Vector. = new Vector.();
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxNode.as b/src/alternativa/engine3d/loaders/filmbox/KFbxNode.as
new file mode 100644
index 0000000..0f55348
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxNode.as
@@ -0,0 +1,226 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ import flash.geom.Matrix3D;
+ import flash.geom.Vector3D;
+
+ /**
+ * @private SDK: Represents an element in the scene graph.
+ */
+ public class KFbxNode {
+ public var LclTranslation:Vector. = Vector.([0, 0, 0]);
+ public var LclRotation:Vector. = Vector.([0, 0, 0]);
+ public var LclScaling:Vector. = Vector.([1, 1, 1]);
+ public var RotationOffset:Vector. = Vector.([0, 0, 0]);
+ public var RotationPivot:Vector. = Vector.([0, 0, 0]);
+ public var PreRotation:Vector. = Vector.([0, 0, 0]);
+ public var PostRotation:Vector. = Vector.([0, 0, 0]);
+ public var ScalingOffset:Vector. = Vector.([0, 0, 0]);
+ public var ScalingPivot:Vector. = Vector.([0, 0, 0]);
+ public var GeometricTranslation:Vector. = Vector.([0, 0, 0]);
+ public var GeometricRotation:Vector. = Vector.([0, 0, 0]);
+ public var GeometricScaling:Vector. = Vector.([1, 1, 1]);
+
+ public function transformationClone():KFbxNode {
+ var tmp:KFbxNode = new KFbxNode;
+ tmp.LclTranslation = LclTranslation.slice();
+ tmp.LclRotation = LclRotation.slice();
+ tmp.LclScaling = LclScaling.slice();
+ tmp.RotationOffset = RotationOffset.slice();
+ tmp.RotationPivot = RotationPivot.slice();
+ tmp.PreRotation = PreRotation.slice();
+ tmp.PostRotation = PostRotation.slice();
+ tmp.ScalingOffset = ScalingOffset.slice();
+ tmp.ScalingPivot = ScalingPivot.slice();
+ tmp.GeometricTranslation = GeometricTranslation.slice();
+ tmp.GeometricRotation = GeometricRotation.slice();
+ tmp.GeometricScaling = GeometricScaling.slice();
+ return tmp;
+ }
+
+ /**
+ * Собирает матрицу трансформации. Из доки СДК:
+ *
+ * Each pivot context stores values (as KFbxVector4) for:
+ * - Rotation offset (Roff)
+ * - Rotation pivot (Rp)
+ * - Pre-rotation (Rpre)
+ * - Post-rotation (Rpost)
+ * - Scaling offset (Soff)
+ * - Scaling pivot (Sp)
+ * - Geometric translation (Gt)
+ * - Geometric rotation (Gr)
+ * - Geometric scaling (Gs)
+ *
+ * These values combine in the matrix form to compute the World transform of the node
+ * using the formula:
+ *
+ * World = ParentWorld * T * Roff * Rp * Rpre * R * Rpost * Rp-1 * Soff * Sp * S * Sp-1
+ */
+ public function calculateNodeTransformation():Matrix3D {
+ var extraPostRotation:Number = 0;
+ if (getAttribute(KFbxLight)) {
+ // By default, a KFbxNode uses its positive X axis as the aiming constraint. Recall that
+ // a newly created light points along the node's negative Y axis by default. To make the
+ // light point along the node's positive X axis, a rotation offset of 90 degrees must be
+ // applied to the light's node using KFbxNode::SetPostTargetRotation().
+ extraPostRotation = 90;
+ }
+
+ var T:Matrix3D = new Matrix3D;
+ T.prependTranslation(LclTranslation [0], LclTranslation [1], LclTranslation [2]);
+ // rotation offset
+ T.prependTranslation(RotationOffset [0], RotationOffset [1], RotationOffset [2]);
+ // rotaton pivot
+ var Rp:Matrix3D = new Matrix3D;
+ Rp.prependTranslation(RotationPivot [0], RotationPivot [1], RotationPivot [2]);
+ T.prepend(Rp);
+ // prerotation
+ T.prepend(makeRotationMatrix(PreRotation [0], PreRotation [1], PreRotation [2]));
+ // rotation
+ T.prepend(makeRotationMatrix(LclRotation [0], LclRotation [1], LclRotation [2]));
+ // postrotation
+ T.prepend(makeRotationMatrix(PostRotation [0] - extraPostRotation, PostRotation [1], PostRotation [2]));
+ // inv. rotation pivot
+ Rp.invert();
+ T.prepend(Rp);
+ // scaling offset
+ T.prependTranslation(ScalingOffset [0], ScalingOffset [1], ScalingOffset [2]);
+ // scaling pivot
+ var Sp:Matrix3D = new Matrix3D;
+ Sp.prependTranslation(ScalingPivot [0], ScalingPivot [1], ScalingPivot [2]);
+ T.prepend(Sp);
+ // scaling
+ T.prependScale(LclScaling [0], LclScaling [1], LclScaling [2]);
+ // inv. scaling pivot
+ Sp.invert();
+ T.prepend(Sp);
+
+ return T;
+ }
+
+ /**
+ * Если возвращает ненулевое преобразование, аттрибуты должны быть по-любому добавлены
+ * как дочерние объекты к данной ноде.
+ *
+ * The geometric transformation (Gt * Gr * Gs) is applied only to the node attribute
+ * and after the node transformations. This transformation is not inherited across the
+ * node hierarchy.
+ *
+ * Gt * Gr * Gs вроде как используется только 3DMax-ом в редких случаях:
+ * @see http://download.autodesk.com/global/docs/fbxsdk2012/en_us/files/GUID-10CDD63C-79C1-4F2D-BB28-AD2BE65A02E-50.htm
+ */
+ public function calculateAttributesTransformation():Matrix3D {
+ var hasAttrTransform:Boolean;
+ for (var i:int = 0; i < 3; i++) {
+ hasAttrTransform ||= (GeometricTranslation [i] != 0);
+ hasAttrTransform ||= (GeometricRotation [i] != 0);
+ hasAttrTransform ||= (GeometricScaling [i] != 1);
+ }
+
+ if (hasAttrTransform) {
+ // shit :(
+ var G:Matrix3D = new Matrix3D;
+ G.prependTranslation(GeometricTranslation [0], GeometricTranslation [1], GeometricTranslation [2]);
+ G.prepend(makeRotationMatrix(GeometricRotation [0], GeometricRotation [1], GeometricRotation [2]));
+ G.prependScale(GeometricScaling [0], GeometricScaling [1], GeometricScaling [2]);
+ return G;
+ }
+
+ return null;
+ }
+
+ /**
+ * typedef enum
+ * {
+ * eEULER_XYZ = 0,
+ * eEULER_XZY,
+ * eEULER_YZX,
+ * eEULER_YXZ,
+ * eEULER_ZXY,
+ * eEULER_ZYX,
+ * eSPHERIC_XYZ // WTF??
+ * } ERotationOrder;
+ */
+ public var RotationOrder:int = 0;
+ private const eEULER_XYZ:int = 0;
+ private const eEULER_XZY:int = 1;
+ private const eEULER_YZX:int = 2;
+ private const eEULER_YXZ:int = 3;
+ private const eEULER_ZXY:int = 4;
+ private const eEULER_ZYX:int = 5;
+
+ /**
+ * The R matrix takes into account the rotation order. Because of the mathematical
+ * properties of the matrices, R is the result of one of the possible combinations
+ * of Ry, Ry and Rz (each being matrices also). For example, for the default rotation
+ * order of XYZ, R = Rx * Ry * Rz
+ */
+ private function makeRotationMatrix(rx:Number, ry:Number, rz:Number):Matrix3D {
+ var R:Matrix3D = new Matrix3D;
+ switch (RotationOrder) {
+ case eEULER_XZY:
+ R.appendRotation(rx, Vector3D.X_AXIS);
+ R.appendRotation(rz, Vector3D.Z_AXIS);
+ R.appendRotation(ry, Vector3D.Y_AXIS);
+ break;
+
+ case eEULER_YZX:
+ R.appendRotation(ry, Vector3D.Y_AXIS);
+ R.appendRotation(rz, Vector3D.Z_AXIS);
+ R.appendRotation(rx, Vector3D.X_AXIS);
+ break;
+
+ case eEULER_YXZ:
+ R.appendRotation(ry, Vector3D.Y_AXIS);
+ R.appendRotation(rx, Vector3D.X_AXIS);
+ R.appendRotation(rz, Vector3D.Z_AXIS);
+ break;
+
+ case eEULER_ZXY:
+ R.appendRotation(rz, Vector3D.Z_AXIS);
+ R.appendRotation(rx, Vector3D.X_AXIS);
+ R.appendRotation(ry, Vector3D.Y_AXIS);
+ break;
+
+ case eEULER_ZYX:
+ R.appendRotation(rz, Vector3D.Z_AXIS);
+ R.appendRotation(ry, Vector3D.Y_AXIS);
+ R.appendRotation(rx, Vector3D.X_AXIS);
+ break;
+
+ case eEULER_XYZ:
+ default:
+ R.appendRotation(rx, Vector3D.X_AXIS);
+ R.appendRotation(ry, Vector3D.Y_AXIS);
+ R.appendRotation(rz, Vector3D.Z_AXIS);
+ break;
+ }
+ return R;
+ }
+
+ public var parent:KFbxNode;
+ public var Children:Vector. = new Vector.();//* for V5 only */
+
+ public var attributes:Vector. = new Vector.();
+
+ public var materials:Vector. = new Vector.();
+ public var textures:Vector. = new Vector.();
+
+ public var Visibility:Number = 1;
+
+ public function isVisible():Boolean {
+ return (Visibility == 1);
+ }
+
+ /**
+ * Судя по методам в СДК, аттрибутов может быть много, но по одному одного типа.
+ */
+ public function getAttribute(type:Class):KFbxNodeAttribute {
+ for (var i:int = 0; i < attributes.length; i++) {
+ var a:KFbxNodeAttribute = attributes [i];
+ if (a is type) return a;
+ }
+ return null;
+ }
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxNodeAttribute.as b/src/alternativa/engine3d/loaders/filmbox/KFbxNodeAttribute.as
new file mode 100644
index 0000000..4f92e8e
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxNodeAttribute.as
@@ -0,0 +1,8 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ /**
+ * @private SDK: This class is the base class to all types of node attributes.
+ */
+ public class KFbxNodeAttribute {
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxSkeleton.as b/src/alternativa/engine3d/loaders/filmbox/KFbxSkeleton.as
new file mode 100644
index 0000000..dc751f5
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxSkeleton.as
@@ -0,0 +1,8 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ /**
+ * @private SDK: a node attribute to represent the elements forming "bone" chains.
+ */
+ public class KFbxSkeleton extends KFbxNodeAttribute {
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxSkin.as b/src/alternativa/engine3d/loaders/filmbox/KFbxSkin.as
new file mode 100644
index 0000000..f66571b
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxSkin.as
@@ -0,0 +1,9 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ /**
+ * @private SDK: FBX SDK skin class.
+ */
+ public class KFbxSkin extends KFbxDeformer {
+ public var clusters:Vector. = new Vector.();
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxSurfaceMaterial.as b/src/alternativa/engine3d/loaders/filmbox/KFbxSurfaceMaterial.as
new file mode 100644
index 0000000..79cb2b3
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxSurfaceMaterial.as
@@ -0,0 +1,90 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ /**
+ * @private SDK: This class contains material settings.
+ */
+ public dynamic class KFbxSurfaceMaterial {
+ /*
+ // v6 properties
+ public var ShadingModel:String = "Phong";
+ public var EmissiveColor:Vector. = new [0, 0, 0];
+ public var EmissiveFactor:Number = 1;
+ public var AmbientColor:Vector. = new [0, 0, 0];
+ public var AmbientFactor:Number = 1;
+ public var DiffuseColor:Vector. = new [0, 0, 0];
+ public var DiffuseFactor:Number = 1;
+ public var Bump:Vector. = new [0, 0, 0];
+ public var TransparentColor:Vector. = new [0, 0, 0];
+ public var TransparencyFactor:Number = 1;
+ public var SpecularColor:Vector. = new [0, 0, 0];
+ public var SpecularFactor:Number = 1;
+ public var ShininessExponent:Number = 1;
+ public var ReflectionColor:Vector. = new [0, 0, 0];
+ public var ReflectionFactor:Number = 1;
+ public var Emissive:Vector. = new [0, 0, 0];
+ public var Ambient:Vector. = new [0, 0, 0];
+ public var Diffuse:Vector. = new [0, 0, 0];
+ public var Specular:Vector. = new [0, 0, 0];
+ public var Shininess:Number = 1;
+ public var Opacity:Number = 1;
+ public var Reflectivity:Number = 1;
+ // v7 properties
+ public var NormalMap:Vector. = new [0, 0, 0];
+ public var BumpFactor:Number = 1;
+ public var DisplacementColor:Vector. = new [0, 0, 0];
+ public var DisplacementFactor:Number = 1;
+ public var VectorDisplacementColor:Vector. = new [0, 0, 0];
+ public var VectorDisplacementFactor:Number = 1;
+ */
+ public function KFbxSurfaceMaterial() {
+ // vars dynamic vars so that for (var property:String in this) could work
+ // copy through byte array: ba.readObject() wastes memory
+ // manual prop-after-prop copying: spagetti code, could miss new props if any
+ // describeType: could be an option..
+
+ // v6 properties
+ this.ShadingModel = "Phong";
+ this.EmissiveColor = Vector.([0, 0, 0]);
+ this.EmissiveFactor = 1;
+ this.AmbientColor = Vector.([0, 0, 0]);
+ this.AmbientFactor = 1;
+ this.DiffuseColor = Vector.([0, 0, 0]);
+ this.DiffuseFactor = 1;
+ this.Bump = Vector.([0, 0, 0]);
+ this.TransparentColor = Vector.([0, 0, 0]);
+ this.TransparencyFactor = 1;
+ this.SpecularColor = Vector.([0, 0, 0]);
+ this.SpecularFactor = 1;
+ this.ShininessExponent = 1;
+ this.ReflectionColor = Vector.([0, 0, 0]);
+ this.ReflectionFactor = 1;
+ this.Emissive = Vector.([0, 0, 0]);
+ this.Ambient = Vector.([0, 0, 0]);
+ this.Diffuse = Vector.([0, 0, 0]);
+ this.Specular = Vector.([0, 0, 0]);
+ this.Shininess = 1;
+ this.Opacity = 1;
+ this.Reflectivity = 1;
+ // v7 properties
+ this.NormalMap = Vector.([0, 0, 0]);
+ this.BumpFactor = 1;
+ this.DisplacementColor = Vector.([0, 0, 0]);
+ this.DisplacementFactor = 1;
+ this.VectorDisplacementColor = Vector.([0, 0, 0]);
+ this.VectorDisplacementFactor = 1;
+ }
+
+ // node pointer for v7 to propagate textures up to node
+ public var node:KFbxNode;
+
+ // textures
+ public var textures:Object = {};
+
+ public function copyTo(dest:KFbxSurfaceMaterial):void {
+ // shallow copy dynamic properties
+ for (var property:String in dest) dest [property] = this [property];
+ // and textures
+ for (var channel:String in textures) dest.textures [channel] = this.textures [channel];
+ }
+ }
+}
diff --git a/src/alternativa/engine3d/loaders/filmbox/KFbxTexture.as b/src/alternativa/engine3d/loaders/filmbox/KFbxTexture.as
new file mode 100644
index 0000000..4df0a2e
--- /dev/null
+++ b/src/alternativa/engine3d/loaders/filmbox/KFbxTexture.as
@@ -0,0 +1,66 @@
+package alternativa.engine3d.loaders.filmbox {
+
+ import flash.geom.Matrix;
+ import flash.geom.Matrix3D;
+ import flash.geom.Vector3D;
+
+ /**
+ * @private SDK: This class is the base class for textures,
+ * ie classes KFbxFileTexture, KFbxLayeredTexture and KFbxProceduralTexture.
+ */
+ public class KFbxTexture {
+
+ public var RelativeFilename:String = "";
+ /* these aren't actually set by 3dmax?
+ public var ModelUVTranslation:Vector. = new