From 72b9e964239f4105b6ab19a6cfc3fa14919c5180 Mon Sep 17 00:00:00 2001 From: Andrey Kopysov Date: Mon, 16 Apr 2012 17:45:07 +0600 Subject: [PATCH] Added parser classes --- src/alternativa/engine3d/loaders/ParserFBX.as | 1214 +++++++++++++++++ .../loaders/filmbox/KFbxAnimCurveNode.as | 54 + .../engine3d/loaders/filmbox/KFbxAnimLayer.as | 28 + .../engine3d/loaders/filmbox/KFbxAnimStack.as | 12 + .../engine3d/loaders/filmbox/KFbxCluster.as | 16 + .../engine3d/loaders/filmbox/KFbxDeformer.as | 8 + .../engine3d/loaders/filmbox/KFbxGeometry.as | 9 + .../loaders/filmbox/KFbxGeometryBase.as | 12 + .../engine3d/loaders/filmbox/KFbxLayer.as | 41 + .../loaders/filmbox/KFbxLayerContainer.as | 9 + .../loaders/filmbox/KFbxLayerElement.as | 40 + .../filmbox/KFbxLayerElementMaterial.as | 16 + .../loaders/filmbox/KFbxLayerElementNormal.as | 71 + .../filmbox/KFbxLayerElementTexture.as | 50 + .../loaders/filmbox/KFbxLayerElementUV.as | 37 + .../engine3d/loaders/filmbox/KFbxLight.as | 37 + .../engine3d/loaders/filmbox/KFbxMesh.as | 29 + .../engine3d/loaders/filmbox/KFbxNode.as | 226 +++ .../loaders/filmbox/KFbxNodeAttribute.as | 8 + .../engine3d/loaders/filmbox/KFbxSkeleton.as | 8 + .../engine3d/loaders/filmbox/KFbxSkin.as | 9 + .../loaders/filmbox/KFbxSurfaceMaterial.as | 90 ++ .../engine3d/loaders/filmbox/KFbxTexture.as | 66 + .../loaders/filmbox/readers/IReader.as | 17 + .../loaders/filmbox/readers/ReaderBinary.as | 32 + .../loaders/filmbox/readers/ReaderText.as | 167 +++ .../loaders/filmbox/readers/RecordData.as | 8 + .../loaders/filmbox/versions/IVersion.as | 9 + .../engine3d/loaders/filmbox/versions/V5.as | 128 ++ .../engine3d/loaders/filmbox/versions/V6.as | 359 +++++ .../engine3d/loaders/filmbox/versions/V7.as | 332 +++++ .../loaders/filmbox/versions/VCommon.as | 131 ++ .../loaders/filmbox/versions/VUnknown.as | 22 + 33 files changed, 3295 insertions(+) create mode 100644 src/alternativa/engine3d/loaders/ParserFBX.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxAnimCurveNode.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxAnimLayer.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxAnimStack.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxCluster.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxDeformer.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxGeometry.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxGeometryBase.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxLayer.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxLayerContainer.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxLayerElement.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxLayerElementMaterial.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxLayerElementNormal.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxLayerElementTexture.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxLayerElementUV.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxLight.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxMesh.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxNode.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxNodeAttribute.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxSkeleton.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxSkin.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxSurfaceMaterial.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/KFbxTexture.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/readers/IReader.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/readers/ReaderBinary.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/readers/ReaderText.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/readers/RecordData.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/versions/IVersion.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/versions/V5.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/versions/V6.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/versions/V7.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/versions/VCommon.as create mode 100644 src/alternativa/engine3d/loaders/filmbox/versions/VUnknown.as 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 [0, 0]; + public var ModelUVScaling:Vector. = new [1, 1]; + */ + public var Translation:Vector. = Vector.([0, 0, 0]); + public var Rotation:Vector. = Vector.([0, 0, 0]); + public var Scaling:Vector. = Vector.([1, 1, 1]); + public var TextureRotationPivot:Vector. = Vector.([0, 0, 0]); + public var TextureScalingPivot:Vector. = Vector.([0, 0, 0]); + + public var transformation:Matrix; + + public function calculateTextureTransformation():Matrix { + // guesswork by analogy to KFbxNode formula + var T:Matrix3D = new Matrix3D; + T.prependTranslation(Translation [0], Translation [1], Translation [2]); + // rotaton pivot + var Rp:Matrix3D = new Matrix3D; + Rp.prependTranslation(TextureRotationPivot [0], TextureRotationPivot [1], TextureRotationPivot [2]); + T.prepend(Rp); + // rotation + T.prepend(makeRotationMatrix(Rotation [0], Rotation [1], Rotation [2])); + // inv. rotation pivot + Rp.invert(); + T.prepend(Rp); + // scaling pivot + var Sp:Matrix3D = new Matrix3D; + Sp.prependTranslation(TextureScalingPivot [0], TextureScalingPivot [1], TextureScalingPivot [2]); + T.prepend(Sp); + // scaling + T.prependScale(Scaling [0], Scaling [1], Scaling [2]); + // inv. scaling pivot + Sp.invert(); + T.prepend(Sp); + + // sample transform at W = 0 + var raw:Vector. = T.rawData; + transformation = new Matrix(raw [0], raw [1], raw [4], raw [5], raw [3], raw [7]); + + return transformation; + } + + private function makeRotationMatrix(rx:Number, ry:Number, rz:Number):Matrix3D { + var R:Matrix3D = new Matrix3D; + R.prependRotation(rx, Vector3D.X_AXIS); + R.prependRotation(ry, Vector3D.Y_AXIS); + R.prependRotation(rz, Vector3D.Z_AXIS); + return R; + } + + } + +} diff --git a/src/alternativa/engine3d/loaders/filmbox/readers/IReader.as b/src/alternativa/engine3d/loaders/filmbox/readers/IReader.as new file mode 100644 index 0000000..51a894a --- /dev/null +++ b/src/alternativa/engine3d/loaders/filmbox/readers/IReader.as @@ -0,0 +1,17 @@ +package alternativa.engine3d.loaders.filmbox.readers { + + /** @private Прокладка для чтения файла. */ + public interface IReader { + function hasDataLeft():Boolean; + + function getDepth():uint; + + function getRecordName():String; + + function getRecordData(parseNumbers:Boolean = true):RecordData; + + function stepIn():void; + + function stepOver():void; + } +} diff --git a/src/alternativa/engine3d/loaders/filmbox/readers/ReaderBinary.as b/src/alternativa/engine3d/loaders/filmbox/readers/ReaderBinary.as new file mode 100644 index 0000000..370c021 --- /dev/null +++ b/src/alternativa/engine3d/loaders/filmbox/readers/ReaderBinary.as @@ -0,0 +1,32 @@ +package alternativa.engine3d.loaders.filmbox.readers { + + + /** @private */ + public class ReaderBinary implements IReader { + + public function hasDataLeft():Boolean { + trace("binary reader not implemented"); + return false; + } + + public function getDepth():uint { + return 0; + } + + public function getRecordName():String { + return ""; + } + + public function getRecordData(parseNumbers:Boolean = true):RecordData { + return new RecordData; + } + + public function stepIn():void { + ; + } + + public function stepOver():void { + ; + } + } +} diff --git a/src/alternativa/engine3d/loaders/filmbox/readers/ReaderText.as b/src/alternativa/engine3d/loaders/filmbox/readers/ReaderText.as new file mode 100644 index 0000000..e2664ee --- /dev/null +++ b/src/alternativa/engine3d/loaders/filmbox/readers/ReaderText.as @@ -0,0 +1,167 @@ +package alternativa.engine3d.loaders.filmbox.readers { + + import flash.utils.ByteArray; + + // import flash.utils.getTimer; + + /** @private */ + public class ReaderText implements IReader { + + private var lines:Vector., currentLine:int = 0, nextLineHint:int = -1; + + public function ReaderText(ba:ByteArray):void { + var text:String = ba.toString(); // ba.readUTFBytes (ba.bytesAvailable); + + var crat:int = text.indexOf("\n"); + var eol:String = ((crat > 0) && (text.charAt(crat - 1) == "\r")) ? "\r\n" : "\n"; + + lines = Vector.(text.split(eol)); + } + + public function hasDataLeft():Boolean { + if (currentLine < nextLineHint) { + currentLine = nextLineHint; + } + + while (currentLine < lines.length) { + var line:String = lines [currentLine]; + if (line.indexOf(": ") > -1) return true; + // can't have "{" here + if (line.indexOf("}") > -1) depth--; + currentLine++; + } + return (currentLine < lines.length); + } + + private var depth:uint = 0; + + public function getDepth():uint { + return depth; + } + + public function getRecordName():String { + var line:String = lines [currentLine]; + var name:String = line.substr(0, line.indexOf(":")); + var last:int = name.lastIndexOf(" ") + 1; + if (last > 0) { + name = name.substr(last); + } else { + last = name.lastIndexOf("\t") + 1; + if (last > 0) { + name = name.substr(last); + } + } + return name; + } + + public function getRecordData(parseNumbers:Boolean = true):RecordData { + //var t:int = getTimer (); + var data:RecordData = new RecordData; + + var text:String = ""; + + // 1st bit of data starts at currentLine after ": " + var line:String = lines [currentLine]; + line = line.substr(line.indexOf(": ") + 1); + + var c:int = currentLine + 1; + while ((line.indexOf(": ") < 0) && (line.indexOf("}") < 0) && (line.indexOf("{") < 0)) { + // additional check for comments + var s:int = line.indexOf(";"); + if (s >= 0) { + for (var i:int = 0; i < s; i++) { + if (line.charCodeAt(i) > 32) { + s = -1; + } + } + } + if (s < 0) { + text += line; + } + line = lines [c]; + c++; + } + + // now line is either last one, or irrelevant one + nextLineHint = c - 2; + if ((line.indexOf(": ") < 0) && (line.indexOf("}") < 0)) { + line = line.substr(0, line.indexOf("{")); + text += line; + } + + // split text + var items:Array = text.split(","); + + //var profile:Boolean = (items.length > 10); + //if (profile) { + // trace ("----->", items.length, "data items over", (c - currentLine), "lines"); + // trace ("@", lines [currentLine].substr (0, 50)); + //} + + var isNumber:Boolean, n:int = items.length; + for (i = 0; i < n; i++) { + var item:String = items [i] as String; + + // trim heading whitespace + s = 0; + while (item.charCodeAt(s) < 33) s++; + if (s > 0) item = item.substr(s); + + // number ? + if (!isNumber) { + s = item.charCodeAt(0); + if (parseNumbers) isNumber = (s == 0x2D) || ((0x2F < s) && (s < 0x3A)); + } + + if (isNumber) { + data.numbers.push(parseFloat(item)); + } else { + var quotes:Boolean = (s == 0x22); + + // trim trailing whitespace + s = item.length; + while (item.charCodeAt(s - 1) < 33) s--; + if (s < item.length) item = item.substr(0, s); + + if (quotes) { + // strip quotes + data.strings.push(item.substr(1, item.length - 2)); + } else if (item.length > 0) { + data.strings.push(item); + } + } + } + + //if (profile) trace (1e-3 * (getTimer () - t), "wasted in getRecordData()"); + + if ((data.numbers.length == 0) && (data.strings.length == 1) && (data.strings [0].charCodeAt(0) == 0x2A)) { + // v7 arrays shortcut + c = currentLine; + i = nextLineHint; + s = depth; + stepIn(); + hasDataLeft(); + data = getRecordData(); + currentLine = c; + nextLineHint = i; + depth = s; + } + + return data; + } + + public function stepIn():void { + currentLine++; + depth++; + } + + public function stepOver():void { + var par:int = 0; + do { + var line:String = lines [currentLine]; + if (line.indexOf("{") >= 0) par++; else if (line.indexOf("}") >= 0) par--; + currentLine++; + } while (par > 0); + } + } +} diff --git a/src/alternativa/engine3d/loaders/filmbox/readers/RecordData.as b/src/alternativa/engine3d/loaders/filmbox/readers/RecordData.as new file mode 100644 index 0000000..9c01b05 --- /dev/null +++ b/src/alternativa/engine3d/loaders/filmbox/readers/RecordData.as @@ -0,0 +1,8 @@ +package alternativa.engine3d.loaders.filmbox.readers { + + /** @private Большинство данных в filmbox представимо в этом виде. */ + public class RecordData { + public var strings:Vector. = new Vector.(); + public var numbers:Vector. = new Vector.(); + } +} diff --git a/src/alternativa/engine3d/loaders/filmbox/versions/IVersion.as b/src/alternativa/engine3d/loaders/filmbox/versions/IVersion.as new file mode 100644 index 0000000..fc64571 --- /dev/null +++ b/src/alternativa/engine3d/loaders/filmbox/versions/IVersion.as @@ -0,0 +1,9 @@ +package alternativa.engine3d.loaders.filmbox.versions { + + import alternativa.engine3d.loaders.filmbox.readers.IReader; + + /** @private Прокладка для интерпретации содержимого файла. TODO better separation! */ + public interface IVersion { + function parseCurrentRecord(reader:IReader, stack:Array, heap:Object):void; + } +} diff --git a/src/alternativa/engine3d/loaders/filmbox/versions/V5.as b/src/alternativa/engine3d/loaders/filmbox/versions/V5.as new file mode 100644 index 0000000..89e45d5 --- /dev/null +++ b/src/alternativa/engine3d/loaders/filmbox/versions/V5.as @@ -0,0 +1,128 @@ +package alternativa.engine3d.loaders.filmbox.versions { + + import alternativa.engine3d.loaders.filmbox.*; + import alternativa.engine3d.loaders.filmbox.readers.*; + + /** @private */ + public class V5 extends VCommon implements IVersion { + public function parseCurrentRecord(reader:IReader, stack:Array, heap:Object):void { + + var data:RecordData, node:KFbxNode, object:Object; + + var recordName:String = reader.getRecordName(); + switch (recordName) { + case "AmbientRenderSettings": + stack.push(null); + reader.stepIn(); + break; + + case "AmbientLightColor": + parseAmbientLight(reader.getRecordData(), heap); + reader.stepOver(); + break; + + + // 3D объекты + case "Model": + data = reader.getRecordData(); + parseModelRecord(data, stack, heap); + reader.stepIn(); + break; + + + // разная хрень из Mesh + case "Vertices": + case "PolygonVertexIndex": + setMeshNumericProperty(reader, stack, recordName); + reader.stepOver(); + break; + case "Normals": + addMeshLayerElement(null, stack, new KFbxLayerElementNormal(reader.getRecordData().numbers), 0, + false); + reader.stepOver(); + break; + case "Meterials": + addMeshLayerElement(null, stack, new KFbxLayerElementMaterial(reader.getRecordData().numbers), 0, + false); + reader.stepOver(); + break; + case "TextureId": + addMeshLayerElement(null, stack, + new KFbxLayerElementTexture("diffuse", reader.getRecordData().numbers), 0, false); + reader.stepOver(); + break; + case "GeometryUVInfo": + addMeshLayerElement(null, stack, new KFbxLayerElementUV, 0); + reader.stepIn(); + break; + + + // поля слоёв + case "TextureUV": + setPredefinedProperty(reader, stack, "UV"); + reader.stepOver(); + break; + case "TextureUVVerticeIndex": + setPredefinedProperty(reader, stack, "UVIndex"); + reader.stepOver(); + break; + // поля текстур + case "Media": + setPredefinedProperty(reader, stack, "RelativeFilename"); + reader.stepOver(); + break; + case "ModelUVTranslation": + case "ModelUVScaling": + // иерархия по версии 5 + case "Children": + setPredefinedProperty(reader, stack, recordName); + reader.stepOver(); + break; + + case "Material": + node = stack [stack.length - 1] as KFbxNode; + node.materials.push(object = new KFbxSurfaceMaterial); + stack.push(object); + reader.stepIn(); + break; + + case "Texture": + node = stack [stack.length - 1] as KFbxNode; + node.textures.push(object = new KFbxTexture); + stack.push(object); + reader.stepIn(); + break; + + case "Takes": + // all nodes were parsed by now + buildHierarchy(heap); + reader.stepOver(); + break; + + default: + reader.stepOver(); + break; + } + } + + private function parseModelRecord(data:RecordData, stack:Array, heap:Object):void { + var node:KFbxNode = new KFbxNode; + // can't determine attribute yet :( + stack.push(heap [data.strings [0]] = node); + } + + private function buildHierarchy(heap:Object):void { + for (var key:String in heap) { + var node:KFbxNode = heap [key] as KFbxNode; + if (node) { + for (var i:int = 0; i < node.Children.length; i++) { + var child:KFbxNode = heap [node.Children [i]] as KFbxNode; + if (child) { + child.parent = node; + } + } + } + } + } + } +} diff --git a/src/alternativa/engine3d/loaders/filmbox/versions/V6.as b/src/alternativa/engine3d/loaders/filmbox/versions/V6.as new file mode 100644 index 0000000..2e1f1b6 --- /dev/null +++ b/src/alternativa/engine3d/loaders/filmbox/versions/V6.as @@ -0,0 +1,359 @@ +package alternativa.engine3d.loaders.filmbox.versions { + + import alternativa.engine3d.loaders.filmbox.*; + import alternativa.engine3d.loaders.filmbox.readers.*; + + /** + * @private + * @see http://paulbourke.net/dataformats/fbx/fbx.pdf ? + */ + public class V6 extends VCommon implements IVersion { + public function parseCurrentRecord(reader:IReader, stack:Array, heap:Object):void { + + var data:RecordData; + + var recordName:String = reader.getRecordName(); + switch (recordName) { + // верхний уровень, Prop60 + case "Objects": + case "Connections": + case "Takes": + case "Properties60": + case "Version5": + case "AmbientRenderSettings": + stack.push(null); + reader.stepIn(); + break; + + case "AmbientLightColor": + parseAmbientLight(reader.getRecordData(), heap); + reader.stepOver(); + break; + + // 3D объекты + case "Model": + data = reader.getRecordData(); + switch (data.strings.length) { + case 2: + // свойства объектов + parseModelRecord(data, stack, heap); + reader.stepIn(); + break; + case 1: + // анимация объектов + parseAnimationLayer(data, stack, heap); + reader.stepIn(); + break; + default: + // не должно бы, на всякий случай + reader.stepOver(); + break; + } + break; + + case "Deformer": + data = reader.getRecordData(); + switch (data.strings [1]) { + case "Cluster": + stack.push(heap [data.strings [0]] = new KFbxCluster); + reader.stepIn(); + break; + case "Skin": + heap [data.strings [0]] = new KFbxSkin; + reader.stepOver(); + break; + default: + reader.stepOver(); + break; + } + break; + + // разная хрень из Mesh + case "Vertices": + case "PolygonVertexIndex": + setMeshNumericProperty(reader, stack, recordName); + reader.stepOver(); + break; + case "LayerElementMaterial": + addMeshLayerElement(reader, stack, new KFbxLayerElementMaterial); + reader.stepIn(); + break; + case "LayerElementTexture": + addMeshLayerElement(reader, stack, new KFbxLayerElementTexture); + reader.stepIn(); + break; + case "LayerElementSpecularTextures": + addMeshLayerElement(reader, stack, new KFbxLayerElementTexture("specular")); + reader.stepIn(); + break; + case "LayerElementBumpTextures": + addMeshLayerElement(reader, stack, new KFbxLayerElementTexture("bump")); + reader.stepIn(); + break; + case "LayerElementUV": + addMeshLayerElement(reader, stack, new KFbxLayerElementUV); + reader.stepIn(); + break; + /* + case "LayerElementSpecularUV": + case "LayerElementBumpUV": + multiple UVs per vertex aren't supported + */ + case "LayerElementNormal": + addMeshLayerElement(reader, stack, new KFbxLayerElementNormal); + reader.stepIn(); + break; + + + // поля слоёв + case "MappingInformationType": + case "ReferenceInformationType": + case "Normals": + case "Materials": + case "TextureId": + case "UV": + case "UVIndex": + // поля текстур + case "RelativeFilename": + case "ModelUVTranslation": + case "ModelUVScaling": + // поля кластеров + case "Indexes": + case "Weights": + case "Transform": + case "TransformLink": + setPredefinedProperty(reader, stack, recordName); + reader.stepOver(); + break; + + case "Material": + data = reader.getRecordData(); + stack.push(heap [data.strings [0]] = new KFbxSurfaceMaterial); + reader.stepIn(); + break; + + case "Texture": + data = reader.getRecordData(); + stack.push(heap [data.strings [0]] = new KFbxTexture); + reader.stepIn(); + break; + + + // свойства + case "Property": + // someSpecialCase () || + setProperty(reader, stack, 3); + reader.stepOver(); + break; + + + case "Connect": + parseConnection(reader.getRecordData(), heap); + reader.stepOver(); + break; + + + case "Take": + data = reader.getRecordData(); + stack.push(heap [data.strings [0]] = new KFbxAnimStack); + reader.stepIn(); + break; + + + case "Channel": + parseAnimationChannel(reader.getRecordData(), stack); + reader.stepIn(); + break; + + + case "Key": + parseAnimationKey(reader.getRecordData(false), stack); + reader.stepOver(); + break; + + + default: + reader.stepOver(); + break; + } + } + + private function parseModelRecord(data:RecordData, stack:Array, heap:Object):void { + var attr:KFbxNodeAttribute; + switch (data.strings [1]) { + case "Light": + attr = new KFbxLight; + break; + case "Limb": + case "LimbNode": + attr = new KFbxSkeleton; + break; + case "Mesh": + attr = new KFbxMesh; + break; + } + var node:KFbxNode = new KFbxNode; + if (attr) { + node.attributes.push(attr); + } + stack.push(heap [data.strings [0]] = node); + } + + private function parseConnection(data:RecordData, heap:Object):void { + if (data.strings [0] == "OO") { + var owned:Object = heap [data.strings [1]]; + var owner:Object = heap [data.strings [2]]; + + if (owned is KFbxNode) { + if (owner is KFbxNode) { + // иерархия + (owned as KFbxNode).parent = owner as KFbxNode; + return; + } + if (owner is KFbxCluster) { + // bind joints + (owner as KFbxCluster).jointNode = owned as KFbxNode; + return; + } + } + // материалы нод + if (owned is KFbxSurfaceMaterial) { + (owner as KFbxNode).materials.push(owned as KFbxSurfaceMaterial); + return; + } + // текстуры + if (owned is KFbxTexture) { + (owner as KFbxNode).textures.push(owned as KFbxTexture); + return; + } + // кластера + if (owned is KFbxCluster) { + (owner as KFbxSkin).clusters.push(owned as KFbxCluster); + return; + } + // скины + if (owned is KFbxSkin) { + var geom:KFbxGeometry = (owner as KFbxNode).getAttribute(KFbxGeometry) as KFbxGeometry; + if (geom) { + geom.deformers.push(owned as KFbxSkin); + } + return; + } + + } else { + // ??? + } + } + + private function parseAnimationChannel(data:RecordData, stack:Array):void { + var aniChannel:KFbxAnimCurveNode = new KFbxAnimCurveNode; + aniChannel.channel = data.strings [0]; + var aniChannelParent:KFbxAnimCurveNode = stack [stack.length - 1] as KFbxAnimCurveNode; + aniChannelParent.curveNodes.push(aniChannel); + stack.push(aniChannel); + } + + private function parseAnimationLayer(data:RecordData, stack:Array, heap:Object):void { + var aniLayer:KFbxAnimLayer = new KFbxAnimLayer; + aniLayer.node = heap [data.strings [0]] as KFbxNode; + var aniStack:KFbxAnimStack = stack [stack.length - 1] as KFbxAnimStack; + aniStack.layers.push(aniLayer); + stack.push(aniLayer); + } + + /** + * @see http://code.google.com/p/blender-to-unity/issues/detail?id=2 + short: + 0,-14.7206611633301,U,a,n,... + + 0,1.619677305221558,C,n, + 14779570560,1.619677901268005,C,n, + 25864248480,1.619676709175110,C,n, + 27711694800,1.619677901268005,C,n,... + + 0,0,U,s,0,0,n, + 48110581250,0,U,s,0,0,n + + variable length: + 0, 90, U, s, 0, 0, a, 0.333233326673508, 0.333233326673508, + 7697693000, 90, U, s, 0, -0, a, 0.333233326673508, 0.333233326673508, + 15395386000, 90, U, s, 0, 0, r, 0.989998996257782 + + neither [s]mooth nor [b]roken tangent: + 38488465000, 31.6565208435059, U, p, 100, -48.1283378601074, n, n, + + + The keys are represented like this: + 1. The first value of a key is a number that represents the time of the key + 2. The second value is the amplitude of the key, this can be meters for + translation or degrees for a rotation... + 3. The third value is a character that represents the interpolation between the + keys, this can be 'C' for constant, 'L' for linear and 'U' for user defined. This + last one can be used to represent Bezier curves. + 4. The fourth value is only needed when using user defined interpolation. This + value is a character 's', for unified tangents and 'b' for broken tangents. + 5. The fifth value is a number that represents the direction of the right tangent + of the current key, this is the amplitude the tangent would have, at the current time + + 1 second. The screenshot attached explains a lot. + 6. The sixth value is a number that represents the direction of the left tangent + of the next key. This notation is exactly the same as the notation for the fifth value. + 7. The seventh value is a character 'a'. + 8. The eight' value is a number representing the horizontal amplitude of the right + tangent of the current key. This is a number between 0 and 1, where 1 is the distance + between the current key and the next key. This can also be seen on the screen attached. + 9. The ninth value is also a number representing the horizontal amplitude, but + this time of the left tangent of the next key. + + */ + private function parseAnimationKey(data:RecordData, stack:Array):void { + var aniCurve:KFbxAnimCurveNode = stack [stack.length - 1] as KFbxAnimCurveNode; + for (var i:int = 0, n:int = data.strings.length; i < n;) { + + aniCurve.KeyTime.push(parseFloat(data.strings [i])); + aniCurve.KeyValueFloat.push(parseFloat(data.strings [i + 1])); + + // находим начало следующего ключа + switch (data.strings [i + 2]) { + case "L": + i += 3; + break; + case "C": + i += 4; + break; + case "U": + switch (data.strings [i + 3]) { + case "a": + i += 5; + break; + case "p": + i += 8; + break; + case "b": + case "s": + switch (data.strings [i + 6]) { + case "n": + i += 7; + break; + case "r": + i += 8; + break; + case "a": + i += 9; + break; + } + break; + default: + trace("unexpected key format (V6)"); + i = n; + break; + } + break; + default: + trace("unexpected key format (V6)"); + i = n; + break; + } + } + } + } +} diff --git a/src/alternativa/engine3d/loaders/filmbox/versions/V7.as b/src/alternativa/engine3d/loaders/filmbox/versions/V7.as new file mode 100644 index 0000000..7f120f6 --- /dev/null +++ b/src/alternativa/engine3d/loaders/filmbox/versions/V7.as @@ -0,0 +1,332 @@ +package alternativa.engine3d.loaders.filmbox.versions { + + import alternativa.engine3d.loaders.filmbox.*; + import alternativa.engine3d.loaders.filmbox.readers.*; + + /** + * @private + * @see http://download.autodesk.com/global/docs/fbxsdk2012/en_us/index.html + */ + public class V7 extends VCommon implements IVersion { + private var namesMap:Object = new Object; + + public function parseCurrentRecord(reader:IReader, stack:Array, heap:Object):void { + + var data:RecordData; + + var recordName:String = reader.getRecordName(); + switch (recordName) { + // верхний уровень, Prop70 + case "Objects": + case "Connections": + case "Takes": + case "Properties70": + stack.push(null); + reader.stepIn(); + break; + + case "GlobalSettings": + stack.push(recordName); + reader.stepIn(); + break; + + + // 3D объекты + case "Geometry": + data = reader.getRecordData(false); + // Geometry: 809442864, "Geometry::", "Mesh" + if (data.strings [2] == "Mesh") { + stack.push(heap [data.strings [0]] = new KFbxMesh); + reader.stepIn(); + } else { + reader.stepOver(); + } + break; + + case "Model": + data = reader.getRecordData(false); + // Model: 1360186080, "Model::Plane001", "Mesh" + namesMap [data.strings [0]] = data.strings [1]; + stack.push(heap [data.strings [1]] = new KFbxNode); + reader.stepIn(); + break; + + case "NodeAttribute": + data = reader.getRecordData(false); + // NodeAttribute: 699168640, "NodeAttribute::", "Light" + // NodeAttribute: 1396263680, "NodeAttribute::", "LimbNode" + parseNodeAttribute(data, stack, heap) ? reader.stepIn() : reader.stepOver(); + break; + + case "Deformer": + data = reader.getRecordData(false); + // Deformer: 802630608, "Deformer::", "Skin" + // Deformer: 1365552016, "SubDeformer::", "Cluster" + switch (data.strings [2]) { + case "Cluster": + stack.push(heap [data.strings [0]] = new KFbxCluster); + reader.stepIn(); + break; + case "Skin": + heap [data.strings [0]] = new KFbxSkin; + reader.stepOver(); + break; + default: + reader.stepOver(); + break; + } + break; + + // разная хрень из Mesh + case "Vertices": + case "PolygonVertexIndex": + setMeshNumericProperty(reader, stack, recordName); + reader.stepOver(); + break; + case "LayerElementMaterial": + addMeshLayerElement(reader, stack, new KFbxLayerElementMaterial); + reader.stepIn(); + break; + case "LayerElementUV": + addMeshLayerElement(reader, stack, new KFbxLayerElementUV); + reader.stepIn(); + break; + case "LayerElementNormal": + addMeshLayerElement(reader, stack, new KFbxLayerElementNormal); + reader.stepIn(); + break; + + + // поля слоёв + case "MappingInformationType": + case "ReferenceInformationType": + case "Normals": + case "Materials": + case "UV": + case "UVIndex": + // поля текстур + case "RelativeFilename": + case "ModelUVTranslation": + case "ModelUVScaling": + // поля кластеров + case "Indexes": + case "Weights": + case "Transform": + case "TransformLink": + // поля анимационных кривых + case "KeyTime": + case "KeyValueFloat": + setPredefinedProperty(reader, stack, recordName); + reader.stepOver(); + break; + + + case "Material": + data = reader.getRecordData(false); + // Material: 699162640, "Material::Default", "" + stack.push(heap [data.strings [0]] = new KFbxSurfaceMaterial); + reader.stepIn(); + break; + + case "Texture": + data = reader.getRecordData(false); + // Texture: 816837168, "Texture::character_anim:file2", "" + stack.push(heap [data.strings [0]] = new KFbxTexture); + reader.stepIn(); + break; + + // свойства + case "P": + setAmbientLight(reader, stack, heap) || setProperty(reader, stack, 4); + reader.stepOver(); + break; + + + case "C": + parseConnection(reader.getRecordData(false), heap); + reader.stepOver(); + break; + + + case "AnimationStack": + data = reader.getRecordData(false); + // AnimationStack: 1391183360, "AnimStack::Take 001", "" { + namesMap [data.strings [0]] = data.strings [1]; + heap [data.strings [1]] = new KFbxAnimStack; + reader.stepOver(); + break; + + + case "AnimationLayer": + data = reader.getRecordData(false); + // AnimationLayer: 817238576, "AnimLayer::BaseLayer", "" { + heap [data.strings [0]] = new KFbxAnimLayer; + reader.stepOver(); + break; + + + case "AnimationCurveNode": + parseAnimationCurve(reader.getRecordData(false), heap); + reader.stepOver(); + break; + + + case "AnimationCurve": + parseAnimationCurve(reader.getRecordData(false), heap, stack); + reader.stepIn(); + break; + + + default: + reader.stepOver(); + break; + } + } + + private function parseAnimationCurve(data:RecordData, heap:Object, stack:Array = null):void { + // AnimationCurveNode: 704770192, "AnimCurveNode::S", "" { + // AnimationCurve: 1382726720, "AnimCurve::", "" { + var curve:KFbxAnimCurveNode = new KFbxAnimCurveNode; + var channel:String = data.strings [1]; + var dcat:int = channel.indexOf("::"); + curve.channel = (dcat > -1) ? channel.substr(dcat + 2) : channel; + heap [data.strings [0]] = curve; + if (stack) stack.push(curve); + } + + private function setAmbientLight(reader:IReader, stack:Array, heap:Object):Boolean { + if (stack [stack.length - 2] == "GlobalSettings") { + var data:RecordData = reader.getRecordData(); + if (data.strings [0] == "AmbientColor") { + parseAmbientLight(data, heap); + return true; + } + } + return false; + } + + private function parseNodeAttribute(data:RecordData, stack:Array, heap:Object):Boolean { + // NodeAttribute: 699168640, "NodeAttribute::", "Light" + // NodeAttribute: 1396263680, "NodeAttribute::", "LimbNode" + var attr:KFbxNodeAttribute; + switch (data.strings [2]) { + case "Light": + attr = new KFbxLight; + break; + case "Limb": + case "LimbNode": + attr = new KFbxSkeleton; + break; + } + if (attr) { + stack.push(heap [data.strings [0]] = attr); + return true; + } + return false; + } + + private function parseConnection(data:RecordData, heap:Object):void { + var owned:Object = heap [data.strings [1]]; + if (owned == null) owned = heap [namesMap [data.strings [1]]]; + var node:KFbxNode = heap [namesMap [data.strings [2]]] as KFbxNode; + if (data.strings [0] == "OO") { + // аттрибуты + if (owned is KFbxNodeAttribute) { + node.attributes.push(owned as KFbxNodeAttribute); + return; + } + + if (owned is KFbxNode) { + if (node) { + // иерархия + (owned as KFbxNode).parent = node; + return; + } + + var cluster:KFbxCluster = heap [data.strings [2]] as KFbxCluster; + if (cluster) { + // bind joints + cluster.jointNode = owned as KFbxNode; + return; + } + } + // материалы нод + if (owned is KFbxSurfaceMaterial) { + var material:KFbxSurfaceMaterial = owned as KFbxSurfaceMaterial; + material.node = node; + node.materials.push(material); + return; + } + // кластера + if (owned is KFbxCluster) { + var skin:KFbxSkin = heap [data.strings [2]] as KFbxSkin; + skin.clusters.push(owned as KFbxCluster); + return; + } + // скины + if (owned is KFbxSkin) { + var geom:KFbxGeometry = heap [data.strings [2]] as KFbxGeometry; + geom.deformers.push(owned as KFbxSkin); + return; + } + // слои анимации + if (owned is KFbxAnimLayer) { + var astack:KFbxAnimStack = heap [namesMap [data.strings [2]]] as KFbxAnimStack; + astack.layers.push(owned as KFbxAnimLayer); + return; + } + // анимационные кривые + if (owned is KFbxAnimCurveNode) { + var aparent:KFbxAnimCurveNode = heap [data.strings [2]] as KFbxAnimCurveNode; + aparent.curveNodes.push(owned as KFbxAnimCurveNode); + return; + } + + } else + + if (data.strings [0] == "OP") { + // текстуры + if (owned is KFbxTexture) { + var texture:KFbxTexture = owned as KFbxTexture; + var channel:String; + switch (data.strings [3]) { + case "Bump": + channel = "bump"; + break; + case "SpecularColor": + channel = "specular"; + break; + case "DiffuseColor": + default: + channel = "diffuse"; + break; + // TODO find values for glossiness, emission, transparent + } + material = heap [data.strings [2]] as KFbxSurfaceMaterial; + material.textures [channel] = texture; + material.node.textures.push(texture); + return; + } + // анимационные кривые + if (owned is KFbxAnimCurveNode) { + var curve:KFbxAnimCurveNode = owned as KFbxAnimCurveNode; + if (node) { + // связь с 3д объектами + curve.node = node; + return; + } + + aparent = heap [data.strings [2]] as KFbxAnimCurveNode; + if (aparent) { + aparent.curveNodes.push(owned as KFbxAnimCurveNode); + // curve channel + channel = data.strings [3]; + var barat:int = channel.indexOf("|"); + curve.channel = (barat >= 0) ? channel.substr(barat + 1) : channel; + return; + } + } + } + } + } +} diff --git a/src/alternativa/engine3d/loaders/filmbox/versions/VCommon.as b/src/alternativa/engine3d/loaders/filmbox/versions/VCommon.as new file mode 100644 index 0000000..db2fb7a --- /dev/null +++ b/src/alternativa/engine3d/loaders/filmbox/versions/VCommon.as @@ -0,0 +1,131 @@ +package alternativa.engine3d.loaders.filmbox.versions { + + import alternativa.engine3d.loaders.filmbox.*; + import alternativa.engine3d.loaders.filmbox.readers.*; + + /** @private */ + public class VCommon { + private function setObjectPropertyFromData(object:Object, property:String, sLength:int, + data:RecordData):Boolean { + if (object.hasOwnProperty(property)) { + var defaultValue:Object = object [property]; + if (data.numbers.length > 0) { + // this is numeric property + // if ((defaultValue == null) ? (data.numbers.length > 1) : defaultValue.hasOwnProperty ("length")) { + if ((defaultValue == null) ? (data.numbers.length > 1) : defaultValue is Vector.) { + // array + object [property] = data.numbers; + } else { + // scalar + object [property] = data.numbers [0]; + } + } else if (data.strings.length > sLength) { + // this is string property + if ((defaultValue == null) ? (data.strings.length > 1 + sLength) : defaultValue is Vector.) { + // array TODO are there actually any string arrays, ever? + data.strings.splice(0, sLength); + object [property] = data.strings; + } else { + // scalar + object [property] = data.strings [sLength]; + } + } + return true; + } + return false; + } + + protected function setPredefinedProperty(reader:IReader, stack:Array, recordName:String):Boolean { + if (stack.length > 0) { + var object:Object = stack [stack.length - 1]; + if (object != null) { + return setObjectPropertyFromData(object, recordName, 0, reader.getRecordData()); + } + } + return false; + } + + protected function setProperty(reader:IReader, stack:Array, sLength:int):Boolean { + if (stack.length > 1) { + var object:Object = stack [stack.length - 2]; + if (object != null) { + var data:RecordData = reader.getRecordData(); + var property:String = data.strings [0]; + if (property.indexOf("|") > 0) return false; // ignore "Compound" properties for now + if (property.indexOf(" ") > 0) property = property.replace(" ", ""); + var success:Boolean = setObjectPropertyFromData(object, property, sLength, data); + if (!success && (object is KFbxNode)) { + // also attempt this on every attribute + var node:KFbxNode = object as KFbxNode; + for each (var attr:KFbxNodeAttribute in node.attributes) { + if (setObjectPropertyFromData(attr, property, sLength, data)) { + return true; + } + } + } + return success; + } + } + return false; + } + + protected function getCurrentMesh(stack:Array):KFbxMesh { + var mesh:KFbxMesh = stack [stack.length - 1] as KFbxMesh; + if (mesh) { + return mesh; + } + // for v6-, there is node on the stack + var node:KFbxNode = stack [stack.length - 1] as KFbxNode; + if (node) { + mesh = node.getAttribute(KFbxMesh) as KFbxMesh; + // for v5, there might be no mesh attribute yet + if (mesh == null) { + mesh = new KFbxMesh; + node.attributes.push(mesh); + } + return mesh; + } + return null; + } + + protected function setMeshNumericProperty(reader:IReader, stack:Array, property:String):void { + var mesh:KFbxMesh = getCurrentMesh(stack); + if (mesh) mesh [property] = reader.getRecordData().numbers; + } + + protected function addMeshLayerElement(reader:IReader, stack:Array, element:KFbxLayerElement, + layerIndex:int = -1, saveOnStack:Boolean = true):void { + var mesh:KFbxMesh = getCurrentMesh(stack); + if (mesh) { + + // v5 does not specify layer in data, so we actually pass it as argument + if (layerIndex < 0) { + var numbers:Vector. = reader.getRecordData().numbers; + if (numbers.length > 0) layerIndex = numbers [0]; + } + + var layer:KFbxLayer; + if (layerIndex < mesh.layers.length) { + layer = mesh.layers [layerIndex]; + } else { + mesh.layers.push(layer = new KFbxLayer); + } + + layer.elements.push(element); + } + + if (saveOnStack) { + stack.push(element); + } + } + + protected function parseAmbientLight(data:RecordData, heap:Object):void { + var node:KFbxNode = new KFbxNode; + var attr:KFbxLight = new KFbxLight; + attr.Color = data.numbers; + attr.Intensity = 100*((attr.Color.length > 3) ? attr.Color.pop() : 1); + node.attributes.push(attr); + heap ["AmbientLight"] = node; + } + } +} diff --git a/src/alternativa/engine3d/loaders/filmbox/versions/VUnknown.as b/src/alternativa/engine3d/loaders/filmbox/versions/VUnknown.as new file mode 100644 index 0000000..e6c4cf4 --- /dev/null +++ b/src/alternativa/engine3d/loaders/filmbox/versions/VUnknown.as @@ -0,0 +1,22 @@ +package alternativa.engine3d.loaders.filmbox.versions { + + import alternativa.engine3d.loaders.filmbox.readers.IReader; + + public class VUnknown implements IVersion { + public var majorVersion:uint = 0; + + public function parseCurrentRecord(reader:IReader, stack:Array, heap:Object):void { + switch (reader.getRecordName()) { + case "FBXHeaderExtension": + stack.push(null); + reader.stepIn(); + break; + case "FBXVersion": + majorVersion = reader.getRecordData().numbers [0]/1000; + default: + reader.stepOver(); + break; + } + } + } +}