Merge branch '8.29.ParserFBX' of github.com:AlternativaPlatform/Alternativa3D

This commit is contained in:
Leonid Gaev
2012-04-19 12:29:18 +06:00
33 changed files with 3283 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -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.<KFbxAnimCurveNode> = new Vector.<KFbxAnimCurveNode>();
public function collectNodes(nodes:Vector.<KFbxNode>):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.<KFbxAnimCurveNode>, 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.<Number> = new Vector.<Number>();
public var KeyValueFloat:Vector.<Number> = new Vector.<Number>();
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)];
}
}
}

View File

@@ -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);
}
}
}
}
}
}

View File

@@ -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.<KFbxAnimLayer> = new Vector.<KFbxAnimLayer>();
}
}

View File

@@ -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.<Number> = new Vector.<Number>;
public var Weights:Vector.<Number> = new Vector.<Number>;
public var Transform:Vector.<Number> = Vector.<Number>([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
public var TransformLink:Vector.<Number> = Vector.<Number>([1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1]);
// связь с джоинтом
public var jointNode:KFbxNode;
}
}

View File

@@ -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 {
}
}

View File

@@ -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.<KFbxDeformer> = new Vector.<KFbxDeformer>();
}
}

View File

@@ -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.<Number> = new Vector.<Number>();
}
}

View File

@@ -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.<KFbxLayerElement> = new Vector.<KFbxLayerElement>();
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;
}
}
}

View File

@@ -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.<KFbxLayer> = new Vector.<KFbxLayer>();
}
}

View File

@@ -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;
}
}

View File

@@ -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.<Number> = new Vector.<Number>();
public function KFbxLayerElementMaterial(values:Vector.<Number> = null, mapping:String = "ByPolygon",
reference:String = "IndexToDirect") {
if (values != null) Materials = values;
if (mapping != null) MappingInformationType = mapping;
if (reference != null) ReferenceInformationType = reference;
}
}
}

View File

@@ -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.<Number> = new Vector.<Number>();
public function KFbxLayerElementNormal(values:Vector.<Number> = 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.<Number> = mesh.Vertices, polyIndex:Vector.<Number> = mesh.PolygonVertexIndex;
var vertexNormals:Vector.<Number> = new Vector.<Number>(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");
}
}
}

View File

@@ -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.<Number> = new Vector.<Number>();
/**
* Канал для 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.<Number> = 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;
}
}
}

View File

@@ -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.<Number> = new Vector.<Number>();
public var UVIndex:Vector.<Number> = new Vector.<Number>();
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;
}
}
}

View File

@@ -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.<Number> = Vector.<Number>([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;
}
}

View File

@@ -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<KFbxPolygon> mPolygons;
00961 KArrayTemplate<int> 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.<Number> = new Vector.<Number>();
}
}

View File

@@ -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.<Number> = Vector.<Number>([0, 0, 0]);
public var LclRotation:Vector.<Number> = Vector.<Number>([0, 0, 0]);
public var LclScaling:Vector.<Number> = Vector.<Number>([1, 1, 1]);
public var RotationOffset:Vector.<Number> = Vector.<Number>([0, 0, 0]);
public var RotationPivot:Vector.<Number> = Vector.<Number>([0, 0, 0]);
public var PreRotation:Vector.<Number> = Vector.<Number>([0, 0, 0]);
public var PostRotation:Vector.<Number> = Vector.<Number>([0, 0, 0]);
public var ScalingOffset:Vector.<Number> = Vector.<Number>([0, 0, 0]);
public var ScalingPivot:Vector.<Number> = Vector.<Number>([0, 0, 0]);
public var GeometricTranslation:Vector.<Number> = Vector.<Number>([0, 0, 0]);
public var GeometricRotation:Vector.<Number> = Vector.<Number>([0, 0, 0]);
public var GeometricScaling:Vector.<Number> = Vector.<Number>([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.<String> = new Vector.<String>();//* for V5 only */
public var attributes:Vector.<KFbxNodeAttribute> = new Vector.<KFbxNodeAttribute>();
public var materials:Vector.<KFbxSurfaceMaterial> = new Vector.<KFbxSurfaceMaterial>();
public var textures:Vector.<KFbxTexture> = new Vector.<KFbxTexture>();
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;
}
}
}

View File

@@ -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 {
}
}

View File

@@ -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 {
}
}

View File

@@ -0,0 +1,9 @@
package alternativa.engine3d.loaders.filmbox {
/**
* @private SDK: FBX SDK skin class.
*/
public class KFbxSkin extends KFbxDeformer {
public var clusters:Vector.<KFbxCluster> = new Vector.<KFbxCluster>();
}
}

View File

@@ -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.<Number> = new <Number> [0, 0, 0];
public var EmissiveFactor:Number = 1;
public var AmbientColor:Vector.<Number> = new <Number> [0, 0, 0];
public var AmbientFactor:Number = 1;
public var DiffuseColor:Vector.<Number> = new <Number> [0, 0, 0];
public var DiffuseFactor:Number = 1;
public var Bump:Vector.<Number> = new <Number> [0, 0, 0];
public var TransparentColor:Vector.<Number> = new <Number> [0, 0, 0];
public var TransparencyFactor:Number = 1;
public var SpecularColor:Vector.<Number> = new <Number> [0, 0, 0];
public var SpecularFactor:Number = 1;
public var ShininessExponent:Number = 1;
public var ReflectionColor:Vector.<Number> = new <Number> [0, 0, 0];
public var ReflectionFactor:Number = 1;
public var Emissive:Vector.<Number> = new <Number> [0, 0, 0];
public var Ambient:Vector.<Number> = new <Number> [0, 0, 0];
public var Diffuse:Vector.<Number> = new <Number> [0, 0, 0];
public var Specular:Vector.<Number> = new <Number> [0, 0, 0];
public var Shininess:Number = 1;
public var Opacity:Number = 1;
public var Reflectivity:Number = 1;
// v7 properties
public var NormalMap:Vector.<Number> = new <Number> [0, 0, 0];
public var BumpFactor:Number = 1;
public var DisplacementColor:Vector.<Number> = new <Number> [0, 0, 0];
public var DisplacementFactor:Number = 1;
public var VectorDisplacementColor:Vector.<Number> = new <Number> [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.<Number>([0, 0, 0]);
this.EmissiveFactor = 1;
this.AmbientColor = Vector.<Number>([0, 0, 0]);
this.AmbientFactor = 1;
this.DiffuseColor = Vector.<Number>([0, 0, 0]);
this.DiffuseFactor = 1;
this.Bump = Vector.<Number>([0, 0, 0]);
this.TransparentColor = Vector.<Number>([0, 0, 0]);
this.TransparencyFactor = 1;
this.SpecularColor = Vector.<Number>([0, 0, 0]);
this.SpecularFactor = 1;
this.ShininessExponent = 1;
this.ReflectionColor = Vector.<Number>([0, 0, 0]);
this.ReflectionFactor = 1;
this.Emissive = Vector.<Number>([0, 0, 0]);
this.Ambient = Vector.<Number>([0, 0, 0]);
this.Diffuse = Vector.<Number>([0, 0, 0]);
this.Specular = Vector.<Number>([0, 0, 0]);
this.Shininess = 1;
this.Opacity = 1;
this.Reflectivity = 1;
// v7 properties
this.NormalMap = Vector.<Number>([0, 0, 0]);
this.BumpFactor = 1;
this.DisplacementColor = Vector.<Number>([0, 0, 0]);
this.DisplacementFactor = 1;
this.VectorDisplacementColor = Vector.<Number>([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];
}
}
}

View File

@@ -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.<Number> = new <Number> [0, 0];
public var ModelUVScaling:Vector.<Number> = new <Number> [1, 1];
*/
public var Translation:Vector.<Number> = Vector.<Number>([0, 0, 0]);
public var Rotation:Vector.<Number> = Vector.<Number>([0, 0, 0]);
public var Scaling:Vector.<Number> = Vector.<Number>([1, 1, 1]);
public var TextureRotationPivot:Vector.<Number> = Vector.<Number>([0, 0, 0]);
public var TextureScalingPivot:Vector.<Number> = Vector.<Number>([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.<Number> = 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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -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 {
;
}
}
}

View File

@@ -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.<String>, 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.<String>(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);
}
}
}

View File

@@ -0,0 +1,8 @@
package alternativa.engine3d.loaders.filmbox.readers {
/** @private Большинство данных в filmbox представимо в этом виде. */
public class RecordData {
public var strings:Vector.<String> = new Vector.<String>();
public var numbers:Vector.<Number> = new Vector.<Number>();
}
}

View File

@@ -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;
}
}

View File

@@ -0,0 +1,118 @@
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;
}
}
}
}
}
}
}

View File

@@ -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;
}
}
}
}
}

View File

@@ -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;
}
}
}
}
}
}

View File

@@ -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.<Number>) {
// 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.<String>) {
// 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.<Number> = 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;
}
}
}

View File

@@ -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;
}
}
}
}