public void TestBasics() { Assert.That(!string.IsNullOrEmpty(ModelExporter.GetVersionFromReadme())); // Test GetOrCreateLayer using (var fbxManager = FbxManager.Create()) { var fbxMesh = FbxMesh.Create(fbxManager, "name"); var layer0 = ModelExporter.GetOrCreateLayer(fbxMesh); Assert.That(layer0, Is.Not.Null); Assert.That(ModelExporter.GetOrCreateLayer(fbxMesh), Is.EqualTo(layer0)); var layer5 = ModelExporter.GetOrCreateLayer(fbxMesh, layer: 5); Assert.That(layer5, Is.Not.Null); Assert.That(layer5, Is.Not.EqualTo(layer0)); } // Test axis conversion: a x b in left-handed is the same as b x a // in right-handed (that's why we need to flip the winding order). var a = new Vector3(1, 0, 0); var b = new Vector3(0, 0, 1); var crossLeft = Vector3.Cross(a, b); Assert.That(ModelExporter.DefaultMaterial); // Test non-static functions. using (var fbxManager = FbxManager.Create()) { var fbxScene = FbxScene.Create(fbxManager, "scene"); var fbxNode = FbxNode.Create(fbxScene, "node"); var exporter = new ModelExporter(); // Test ExportMaterial: it exports and it re-exports bool result = exporter.ExportMaterial(ModelExporter.DefaultMaterial, fbxScene, fbxNode); Assert.IsTrue(result); var fbxMaterial = fbxNode.GetMaterial(0); Assert.That(fbxMaterial, Is.Not.Null); result = exporter.ExportMaterial(ModelExporter.DefaultMaterial, fbxScene, fbxNode); var fbxMaterial2 = fbxNode.GetMaterial(1); Assert.AreEqual(fbxMaterial, fbxMaterial2); // Test ExportTexture: it finds the same texture for the default-material (it doesn't create a new one) var fbxMaterialNew = FbxSurfaceLambert.Create(fbxScene, "lambert"); exporter.ExportTexture(ModelExporter.DefaultMaterial, "_MainTex", fbxMaterialNew, FbxSurfaceLambert.sBump); Assert.AreEqual( fbxMaterial.FindProperty(FbxSurfaceLambert.sDiffuse).GetSrcObject(), fbxMaterialNew.FindProperty(FbxSurfaceLambert.sBump).GetSrcObject() ); // Test ExportMesh: make sure we exported a mesh with welded vertices. var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); var cubeNode = FbxNode.Create(fbxScene, "cube"); exporter.ExportMesh(cube.GetComponent <MeshFilter>().sharedMesh, cubeNode); Assert.That(cubeNode.GetMesh(), Is.Not.Null); Assert.That(cubeNode.GetMesh().GetControlPointsCount(), Is.EqualTo(8)); } // Test exporting a skinned-mesh. Make sure it doesn't leak (it did at one point) { var cube = GameObject.CreatePrimitive(PrimitiveType.Cube); var character = new GameObject(); var smr = character.AddComponent <SkinnedMeshRenderer>(); smr.sharedMesh = cube.GetComponent <MeshFilter>().sharedMesh; var meshCount = Object.FindObjectsOfType <Mesh>().Length; ModelExporter.ExportObject(GetRandomFbxFilePath(), character); Assert.AreEqual(meshCount, Object.FindObjectsOfType <Mesh>().Length); } // Test euler to quaternion conversion { // EulerToQuaternionZXY var v = new Vector3(50, 45, 190); var quat = ModelExporter.EulerToQuaternionZXY(v); var unityQuat = Quaternion.Euler(v); Assert.That((float)quat.X, Is.EqualTo(unityQuat.x)); Assert.That((float)quat.Y, Is.EqualTo(unityQuat.y)); Assert.That((float)quat.Z, Is.EqualTo(unityQuat.z)); Assert.That((float)quat.W, Is.EqualTo(unityQuat.w)); // EulerToQuaternionXYZ var fbxV = new FbxVector4(v.x, v.y, v.z); var xyzQuat = ModelExporter.EulerToQuaternionXYZ(fbxV); // get the vector from the quaternion FbxAMatrix m = new FbxAMatrix(); m.SetR(fbxV); var actualQuat = m.GetQ(); // since this quaternion is XYZ instead of ZXY, it should not match the quaternion // created with EulerToQuaternionZXY Assert.That(xyzQuat, Is.Not.EqualTo(quat)); Assert.That(xyzQuat, Is.EqualTo(actualQuat)); } }
//Calculates global transform of the node. //This function was taken from FBX SDK/Smaples/Transformations. Originaly it did not take into account AxisSystem, so the corrections to use AxisSystem was added. //Has the fbx sdk equivalent pNode.EvaluateGlobalTransform(); // //Note: FBX Documentation for ConvertScene states - "The adjustment will affect the translation animation curves and the objects pivots values //(the rotation transformation is applied as a pre-rotation transform therefore the rotation animation curves do not need to be transformed)" //But ConvertScene, does not realy change node.PreRotation, these changes are in the matrix : node.GetScene().GetGlobalSettings().GetAxisSystem().GetMatrix // /* * Terminology: * Suffix "M" means this is a matrix, suffix "V" means it is a vector. * T is translation. * R is rotation. * S is scaling. * SH is shear. * GlobalRM(x) means the Global Rotation Matrix of node "x". * GlobalRM(P(x)) means the Global Rotation Matrix of the parent node of node "x". * All other transforms are described in the similar way. * * The algorithm description: * To calculate global transform of a node x according to different InheritType, * we need to calculate GlobalTM(x) and [GlobalRM(x) * (GlobalSHM(x) * GlobalSM(x))] separately. * GlobalM(x) = GlobalTM(x) * [GlobalRM(x) * (GlobalSHM(x) * GlobalSM(x))]; * * InhereitType = RrSs: * GlobalRM(x) * (GlobalSHM(x) * GlobalSM(x)) = GlobalRM(P(x)) * LocalRM(x) * [GlobalSHM(P(x)) * GlobalSM(P(x))] * LocalSM(x); * * InhereitType = RSrs: * GlobalRM(x) * (GlobalSHM(x) * GlobalSM(x)) = GlobalRM(P(x)) * [GlobalSHM(P(x)) * GlobalSM(P(x))] * LocalRM(x) * LocalSM(x); * * InhereitType = Rrs: * GlobalRM(x) * (GlobalSHM(x) * GlobalSM(x)) = GlobalRM(P(x)) * LocalRM(x) * LocalSM(x); * * LocalM(x)= TM(x) * RoffsetM(x) * RpivotM(x) * RpreM(x) * RM(x) * RpostM(x) * RpivotM(x)^-1 * SoffsetM(x) *SpivotM(x) * SM(x) * SpivotM(x)^-1 * LocalTWithAllPivotAndOffsetInformationV(x) = Local(x).GetT(); * GlobalTV(x) = GlobalM(P(x)) * LocalTWithAllPivotAndOffsetInformationV(x); * * Notice: FBX SDK does not support shear yet, so all local transform won't have shear. * However, global transform might bring in shear by combine the global transform of node in higher hierarchy. * For example, if you scale the parent by a non-uniform scale and then rotate the child node, then a shear will * be generated on the child node's global transform. * In this case, we always compensates shear and store it in the scale matrix too according to following formula: * Shear*Scaling = RotationMatrix.Inverse * TranslationMatrix.Inverse * WholeTranformMatrix */ // ReSharper disable InconsistentNaming static FbxAMatrix CalculateGlobalTransform(FbxNode node) { if (node == null) { var ret = new FbxAMatrix(); ret.SetIdentity(); return(ret); } var lTranlationM = new FbxAMatrix(); var lScalingM = new FbxAMatrix(); var lScalingPivotM = new FbxAMatrix(); var lScalingOffsetM = new FbxAMatrix(); var lRotationOffsetM = new FbxAMatrix(); var lRotationPivotM = new FbxAMatrix(); var lPreRotationM = new FbxAMatrix(); var lRotationM = new FbxAMatrix(); var lPostRotationM = new FbxAMatrix(); FbxAMatrix lParentGX = new FbxAMatrix(); FbxAMatrix lGlobalT = new FbxAMatrix(); FbxAMatrix lGlobalRS = new FbxAMatrix(); // Construct translation matrix FbxVector4 lTranslation = new FbxVector4(node.LclTranslation.Get()); //The fourth component of this object is assigned 1. lTranlationM.SetT(lTranslation); // Construct rotation matrices FbxVector4 lRotation = new FbxVector4(node.LclRotation.Get()); FbxVector4 lPreRotation = new FbxVector4(node.PreRotation.Get()); FbxVector4 lPostRotation = new FbxVector4(node.PostRotation.Get()); lRotationM.SetR(lRotation); lPreRotationM.SetR(lPreRotation); lPostRotationM.SetR(lPostRotation); // Construct scaling matrix FbxVector4 lScaling = new FbxVector4(node.LclScaling.Get()); lScalingM.SetS(lScaling); // Construct offset and pivot matrices FbxVector4 lScalingOffset = new FbxVector4(node.ScalingOffset.Get()); FbxVector4 lScalingPivot = new FbxVector4(node.ScalingPivot.Get()); FbxVector4 lRotationOffset = new FbxVector4(node.RotationOffset.Get()); FbxVector4 lRotationPivot = new FbxVector4(node.RotationPivot.Get()); lScalingOffsetM.SetT(lScalingOffset); lScalingPivotM.SetT(lScalingPivot); lRotationOffsetM.SetT(lRotationOffset); lRotationPivotM.SetT(lRotationPivot); // Calculate the global transform matrix of the parent node FbxNode lParentNode = node.GetParent(); if (lParentNode != null) { //Children of the root node must take into account the axis matrix. //Warning: this function CalculateGlobalTransform was taken from FBX SDK/Smaples/Transformations. Originaly it did not take into account AxisSystem if (lParentNode.GetParent() == null) { FbxAMatrix axisMarix = new FbxAMatrix(); node.GetScene().GetGlobalSettings().GetAxisSystem().GetMatrix(axisMarix); lPreRotationM = axisMarix.mul(lPreRotationM); } lParentGX = CalculateGlobalTransform(lParentNode); } else { lParentGX.SetIdentity(); } //Construct Global Rotation FbxAMatrix lParentGRM = new FbxAMatrix(); FbxVector4 lParentGR = lParentGX.GetR(); lParentGRM.SetR(lParentGR); var lLRM = lPreRotationM.mul(lRotationM).mul(lPostRotationM); //Construct Global Shear*Scaling //FBX SDK does not support shear, to patch this, we use: //Shear*Scaling = RotationMatrix.Inverse * TranslationMatrix.Inverse * WholeTranformMatrix FbxAMatrix lParentTM = new FbxAMatrix(); FbxVector4 lParentGT = lParentGX.GetT(); lParentTM.SetT(lParentGT); var lParentGRSM = lParentTM.Inverse().mul(lParentGX); var lParentGSM = lParentGRM.Inverse().mul(lParentGRSM); var lLSM = lScalingM; //Do not consider translation now FbxTransform.EInheritType lInheritType = node.InheritType.Get(); if (lInheritType == FbxTransform.EInheritType.eInheritRrSs) { lGlobalRS = lParentGRM.mul(lLRM).mul(lParentGSM).mul(lLSM); } else if (lInheritType == FbxTransform.EInheritType.eInheritRSrs) { lGlobalRS = lParentGRM.mul(lParentGSM).mul(lLRM).mul(lLSM); } else if (lInheritType == FbxTransform.EInheritType.eInheritRrs) { if (lParentNode != null) { FbxAMatrix lParentLSM = new FbxAMatrix(); FbxVector4 lParentLS = new FbxVector4(lParentNode.LclScaling.Get()); lParentLSM.SetS(lParentLS); FbxAMatrix lParentGSM_noLocal = lParentGSM.mul(lParentLSM.Inverse()); lGlobalRS = lParentGRM.mul(lLRM).mul(lParentGSM_noLocal).mul(lLSM); } else { lGlobalRS = lParentGRM.mul(lLRM).mul(lLSM); } } else { FbxImportLog.LogError(node, "error, unknown inherit type!"); } // Construct translation matrix // Calculate the local transform matrix var lTransform = lTranlationM.mul(lRotationOffsetM).mul(lRotationPivotM).mul(lPreRotationM).mul(lRotationM).mul(lPostRotationM) .mul(lRotationPivotM.Inverse()).mul(lScalingOffsetM).mul(lScalingPivotM).mul(lScalingM).mul(lScalingPivotM.Inverse()); FbxVector4 lLocalTWithAllPivotAndOffsetInfo = lTransform.GetT(); // Calculate global translation vector according to: // GlobalTranslation = ParentGlobalTransform * LocalTranslationWithPivotAndOffsetInfo FbxVector4 lGlobalTranslation = lParentGX.MultT(lLocalTWithAllPivotAndOffsetInfo); lGlobalT.SetT(lGlobalTranslation); //Construct the whole global transform lTransform = lGlobalT.mul(lGlobalRS); return(lTransform); }
/// <summary> /// Process transformation data and setup Transform component /// </summary> private void ProcessTransform(FbxNode fbxNode, GameObject unityGo) { FbxVector4 lclTrs = new FbxVector4(); FbxQuaternion lclRot = new FbxQuaternion(); FbxVector4 lclScl = new FbxVector4(1.0f, 1.0f, 1.0f); #if UNI_18844 // Construct rotation matrices FbxVector4 fbxRotation = new FbxVector4(fbxNode.LclRotation.Get()); FbxAMatrix fbxRotationM = new FbxAMatrix(); fbxRotationM.SetR(fbxRotation); FbxVector4 fbxPreRotation = new FbxVector4(fbxNode.PreRotation.Get()); FbxAMatrix fbxPreRotationM = new FbxAMatrix(); fbxPreRotationM.SetR(fbxPreRotation); FbxVector4 fbxPostRotation = new FbxVector4(fbxNode.PostRotation.Get()); FbxAMatrix fbxPostRotationM = new FbxAMatrix(); fbxPostRotationM.SetR(fbxPostRotation); // Construct translation matrix FbxAMatrix fbxTranslationM = new FbxAMatrix(); FbxVector4 fbxTranslation = new FbxVector4(fbxNode.LclTranslation.Get()); fbxTranslationM.SetT(fbxTranslation); // Construct scaling matrix FbxAMatrix fbxScalingM = new FbxAMatrix(); FbxVector4 fbxScaling = new FbxVector4(fbxNode.LclScaling.Get()); fbxScalingM.SetS(fbxScaling); // Construct offset and pivot matrices FbxAMatrix fbxRotationOffsetM = new FbxAMatrix(); FbxVector4 fbxRotationOffset = fbxNode.RotationOffset.Get(); fbxRotationOffsetM.SetT(fbxRotationOffset); FbxAMatrix fbxRotationPivotM = new FbxAMatrix(); FbxVector4 fbxRotationPivot = fbxNode.RotationPivot.Get(); fbxRotationPivotM.SetT(fbxRotationPivot); FbxAMatrix fbxScalingOffsetM = new FbxAMatrix(); FbxVector4 fbxScalingOffset = fbxNode.ScalingOffset.Get(); fbxScalingOffsetM.SetT(fbxScalingOffset); FbxAMatrix fbxScalingPivotM = new FbxAMatrix(); FbxVector4 fbxScalingPivot = fbxNode.ScalingPivot.Get(); fbxScalingPivotM.SetT(fbxScalingPivot); FbxAMatrix fbxTransform = fbxTranslationM * fbxRotationOffsetM * fbxRotationPivotM * fbxPreRotationM * fbxRotationM * fbxPostRotationM * fbxRotationPivotM.Inverse() * fbxScalingOffsetM * fbxScalingPivotM * fbxScalingM * fbxScalingPivotM.Inverse(); FbxVector4 lclTrs = fbxTransform.GetT(); FbxQuaternion lclRot = fbxTransform.GetQ(); FbxVector4 lclScl = fbxTransform.GetS(); #endif Debug.Log(string.Format("processing {3} Lcl : T({0}) R({1}) S({2})", lclTrs.ToString(), lclRot.ToString(), lclScl.ToString(), fbxNode.GetName())); unityGo.transform.localPosition = new Vector3((float)lclTrs[0], (float)lclTrs[1], (float)lclTrs[2]); unityGo.transform.localRotation = new Quaternion((float)lclRot[0], (float)lclRot[1], (float)lclRot[2], (float)lclRot[3]); unityGo.transform.localScale = new Vector3((float)lclScl[0], (float)lclScl[1], (float)lclScl[2]); }
/// <summary> /// Process fbx scene by doing nothing /// </summary> public void ProcessNode(FbxNode fbxNode, GameObject unityParentObj = null, bool constructTransform = false) { string name = fbxNode.GetName(); GameObject unityGo = new GameObject(name); NumNodes++; if (unityParentObj != null) { unityGo.transform.parent = unityParentObj.transform; } FbxAMatrix fbxTransform = null; if (constructTransform) { #if UNI_18844 // Construct rotation matrices FbxVector4 fbxRotation = new FbxVector4(fbxNode.LclRotation.Get()); FbxAMatrix fbxRotationM = new FbxAMatrix(); fbxRotationM.SetR(fbxRotation); FbxVector4 fbxPreRotation = new FbxVector4(fbxNode.PreRotation.Get()); FbxAMatrix fbxPreRotationM = new FbxAMatrix(); fbxPreRotationM.SetR(fbxPreRotation); FbxVector4 fbxPostRotation = new FbxVector4(fbxNode.PostRotation.Get()); FbxAMatrix fbxPostRotationM = new FbxAMatrix(); fbxPostRotationM.SetR(fbxPostRotation); // Construct translation matrix FbxAMatrix fbxTranslationM = new FbxAMatrix(); FbxVector4 fbxTranslation = new FbxVector4(fbxNode.LclTranslation.Get()); fbxTranslationM.SetT(fbxTranslation); // Construct scaling matrix FbxAMatrix fbxScalingM = new FbxAMatrix(); FbxVector4 fbxScaling = new FbxVector4(fbxNode.LclScaling.Get()); fbxScalingM.SetS(fbxScaling); // Construct offset and pivot matrices FbxAMatrix fbxRotationOffsetM = new FbxAMatrix(); FbxVector4 fbxRotationOffset = fbxNode.RotationOffset.Get(); fbxRotationOffsetM.SetT(fbxRotationOffset); FbxAMatrix fbxRotationPivotM = new FbxAMatrix(); FbxVector4 fbxRotationPivot = fbxNode.RotationPivot.Get(); fbxRotationPivotM.SetT(fbxRotationPivot); FbxAMatrix fbxScalingOffsetM = new FbxAMatrix(); FbxVector4 fbxScalingOffset = fbxNode.ScalingOffset.Get(); fbxScalingOffsetM.SetT(fbxScalingOffset); FbxAMatrix fbxScalingPivotM = new FbxAMatrix(); FbxVector4 fbxScalingPivot = fbxNode.ScalingPivot.Get(); fbxScalingPivotM.SetT(fbxScalingPivot); fbxTransform = fbxTranslationM * fbxRotationOffsetM * fbxRotationPivotM * fbxPreRotationM * fbxRotationM * fbxPostRotationM * fbxRotationPivotM.Inverse() * fbxScalingOffsetM * fbxScalingPivotM * fbxScalingM * fbxScalingPivotM.Inverse(); lclTrs = fbxTransform.GetT(); lclRot = fbxTransform.GetQ(); lclScl = fbxTransform.GetS(); #endif } else { fbxTransform = fbxNode.EvaluateLocalTransform(); } if (fbxTransform == null) { Debug.LogError(string.Format("failed to retrieve transform for node : {0}", fbxNode.GetName())); return; } FbxVector4 lclTrs = fbxTransform.GetT(); FbxQuaternion lclRot = fbxTransform.GetQ(); FbxVector4 lclScl = fbxTransform.GetS(); Debug.Log(string.Format("processing {3} Lcl : T({0}) R({1}) S({2})", lclTrs.ToString(), lclRot.ToString(), lclScl.ToString(), fbxNode.GetName())); // check we can handle translation value Debug.Assert(lclTrs.X <= float.MaxValue && lclTrs.X >= float.MinValue); Debug.Assert(lclTrs.Y <= float.MaxValue && lclTrs.Y >= float.MinValue); Debug.Assert(lclTrs.Z <= float.MaxValue && lclTrs.Z >= float.MinValue); unityGo.transform.localPosition = new Vector3((float)lclTrs.X, (float)lclTrs.Y, (float)lclTrs.Z); unityGo.transform.localRotation = new Quaternion((float)lclRot[0], (float)lclRot[1], (float)lclRot[2], (float)lclRot[3]); unityGo.transform.localScale = new Vector3((float)lclScl.X, (float)lclScl.Y, (float)lclScl.Z); for (int i = 0; i < fbxNode.GetChildCount(); ++i) { ProcessNode(fbxNode.GetChild(i), unityGo); } }