Skip to content

Commit 108a936

Browse files
committed
Merge pull request #507 from Unity-Technologies/UT-3300-add-blendshape-regression-test
Ut 3300 add blendshape regression test
1 parent cc0ceca commit 108a936

File tree

1 file changed

+132
-14
lines changed

1 file changed

+132
-14
lines changed

com.unity.formats.fbx/Tests/FbxTests/ModelExporterTest.cs

Lines changed: 132 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ private void CompareMeshComponentAttributes(Mesh mesh, Mesh fbxMesh)
541541
Assert.AreEqual (mesh.tangents, fbxMesh.tangents);
542542
}
543543

544-
private void ExportSkinnedMesh(string fileToExport, out SkinnedMeshRenderer originalSkinnedMesh, out SkinnedMeshRenderer exportedSkinnedMesh){
544+
private string ExportSkinnedMesh(string fileToExport, out SkinnedMeshRenderer originalSkinnedMesh, out SkinnedMeshRenderer exportedSkinnedMesh){
545545
// add fbx to scene
546546
GameObject originalFbxObj = AssetDatabase.LoadMainAssetAtPath(fileToExport) as GameObject;
547547
Assert.IsNotNull (originalFbxObj);
@@ -555,8 +555,14 @@ private void ExportSkinnedMesh(string fileToExport, out SkinnedMeshRenderer orig
555555

556556
var importer = AssetImporter.GetAtPath(filename) as ModelImporter;
557557
#if UNITY_2019_1_OR_NEWER
558+
importer.importBlendShapes = true;
558559
importer.optimizeMeshPolygons = false;
559560
importer.optimizeMeshVertices = false;
561+
importer.meshCompression = ModelImporterMeshCompression.Off;
562+
// If either blendshape normals are imported or weldVertices is turned off (or both),
563+
// the vertex count between the original and exported meshes does not match.
564+
importer.importBlendShapeNormals = ModelImporterNormals.None;
565+
importer.weldVertices = true;
560566
#else
561567
importer.optimizeMesh = false;
562568
#endif // UNITY_2019_1_OR_NEWER
@@ -570,6 +576,8 @@ private void ExportSkinnedMesh(string fileToExport, out SkinnedMeshRenderer orig
570576

571577
exportedSkinnedMesh = fbxObj.GetComponentInChildren<SkinnedMeshRenderer> ();
572578
Assert.IsNotNull (exportedSkinnedMesh);
579+
580+
return filename;
573581
}
574582

575583
public class SkinnedMeshTestDataClass
@@ -720,26 +728,123 @@ public void TestSkinnedMeshes (string fbxPath) {
720728
Debug.LogWarningFormat ("Compared {0} out of a possible {1} bone weights", comparisonCount, minVertCount);
721729
}
722730

723-
public class Vector3Comparer : IComparer<Vector3>
731+
private delegate float GetDistance<T>(T x, T y);
732+
private static float ComputeHausdorffDistance<T>(T[] orig, T[] converted, GetDistance<T> getDistance)
733+
{
734+
Assert.AreEqual(orig.Length, converted.Length);
735+
// Compute the Hausdorff distance to determine if two meshes have the same vertices as
736+
// we can't rely on the vertex order matching.
737+
float maxDistance = 0;
738+
for (int j = 0; j < orig.Length; j++)
739+
{
740+
float minDistance = float.PositiveInfinity;
741+
var pos = orig[j];
742+
for (int k = 0; k < orig.Length; k++)
743+
{
744+
// find closest vertex in convertedMeshes
745+
var convertedPos = converted[k];
746+
747+
var distance = getDistance(pos, convertedPos);
748+
if (distance < minDistance)
749+
{
750+
minDistance = distance;
751+
}
752+
}
753+
if (minDistance > maxDistance)
754+
{
755+
maxDistance = minDistance;
756+
}
757+
}
758+
return maxDistance;
759+
}
760+
761+
// Test for bug where exporting FbxShapes with empty names would fail to import all
762+
// blendshapes except the first in Maya (fixed by UT-3216)
763+
private void TestFbxShapeNamesNotEmpty(FbxNode node)
764+
{
765+
var mesh = node.GetMesh();
766+
if (mesh != null)
767+
{
768+
for (int i = 0; i < mesh.GetDeformerCount(); i++)
769+
{
770+
var blendshape = mesh.GetBlendShapeDeformer(i);
771+
if (blendshape == null)
772+
{
773+
continue;
774+
}
775+
776+
for(int j = 0; j < blendshape.GetBlendShapeChannelCount(); j++)
777+
{
778+
var blendShapeChannel = blendshape.GetBlendShapeChannel(j);
779+
for (int k = 0; k < blendShapeChannel.GetTargetShapeCount(); k++)
780+
{
781+
var shape = blendShapeChannel.GetTargetShape(k);
782+
Assert.That(string.IsNullOrEmpty(shape.GetName()), Is.False, string.Format("FbxShape missing name on blendshape {0}", blendshape.GetName()));
783+
}
784+
}
785+
}
786+
}
787+
788+
for (int i = 0; i < node.GetChildCount(); i++)
789+
{
790+
TestFbxShapeNamesNotEmpty(node.GetChild(i));
791+
}
792+
}
793+
794+
private void FbxImportAndTestBlendshapes(string fbxPath)
724795
{
725-
public int Compare(Vector3 a, Vector3 b)
796+
// Create the FBX manager
797+
using (var fbxManager = FbxManager.Create())
726798
{
727-
Assert.That(a.x, Is.EqualTo(b.x).Within(0.00001f));
728-
Assert.That(a.y, Is.EqualTo(b.y).Within(0.00001f));
729-
Assert.That(a.z, Is.EqualTo(b.z).Within(0.00001f));
730-
return 0; // we're almost equal
799+
FbxIOSettings fbxIOSettings = FbxIOSettings.Create(fbxManager, Globals.IOSROOT);
800+
801+
// Configure the IO settings.
802+
fbxManager.SetIOSettings(fbxIOSettings);
803+
804+
// Create the importer
805+
var fbxImporter = FbxImporter.Create(fbxManager, "Importer");
806+
807+
// Initialize the importer.
808+
int fileFormat = -1;
809+
810+
bool status = fbxImporter.Initialize(fbxPath, fileFormat, fbxIOSettings);
811+
FbxStatus fbxStatus = fbxImporter.GetStatus();
812+
813+
Assert.That(status, Is.True, fbxStatus.GetErrorString());
814+
Assert.That(fbxImporter.IsFBX(), "file does not contain FBX data");
815+
816+
// Import options. Determine what kind of data is to be imported.
817+
// The default is true, but here we set the options explictly.
818+
fbxIOSettings.SetBoolProp(Globals.IMP_FBX_MATERIAL, false);
819+
fbxIOSettings.SetBoolProp(Globals.IMP_FBX_TEXTURE, false);
820+
fbxIOSettings.SetBoolProp(Globals.IMP_FBX_ANIMATION, false);
821+
fbxIOSettings.SetBoolProp(Globals.IMP_FBX_EXTRACT_EMBEDDED_DATA, false);
822+
fbxIOSettings.SetBoolProp(Globals.IMP_FBX_GLOBAL_SETTINGS, true);
823+
824+
// Create a scene
825+
var fbxScene = FbxScene.Create(fbxManager, "Scene");
826+
827+
// Import the scene to the file.
828+
status = fbxImporter.Import(fbxScene);
829+
fbxStatus = fbxImporter.GetStatus();
830+
Assert.That(status, Is.True, fbxStatus.GetErrorString());
831+
832+
// Get blendshapes and check that the FbxShapes all have names
833+
var rootNode = fbxScene.GetRootNode();
834+
TestFbxShapeNamesNotEmpty(rootNode);
731835
}
732836
}
733837

734-
[Ignore("Blendshapes not working yet")]
735838
[Test, TestCaseSource(typeof(AnimationTestDataClass), "BlendShapeTestCases")]
736839
public void TestBlendShapeExport(string fbxPath)
737840
{
841+
const float epsilon = 0.001f;
842+
738843
fbxPath = FindPathInUnitTests (fbxPath);
739844
Assert.That (fbxPath, Is.Not.Null);
740845

741846
SkinnedMeshRenderer originalSMR, exportedSMR;
742-
ExportSkinnedMesh (fbxPath, out originalSMR, out exportedSMR);
847+
var exportedFbxPath = ExportSkinnedMesh (fbxPath, out originalSMR, out exportedSMR);
743848

744849
var originalMesh = originalSMR.sharedMesh;
745850
var exportedMesh = exportedSMR.sharedMesh;
@@ -757,6 +862,8 @@ public void TestBlendShapeExport(string fbxPath)
757862
var fbxDeltaNormals = new Vector3[exportedMesh.vertexCount];
758863
var fbxDeltaTangents = new Vector3[exportedMesh.vertexCount];
759864

865+
Assert.AreEqual(deltaVertices.Length, fbxDeltaVertices.Length);
866+
760867
for (int bi = 0; bi < originalMesh.blendShapeCount; ++bi)
761868
{
762869
Assert.That(originalMesh.GetBlendShapeName(bi), Is.EqualTo(exportedMesh.GetBlendShapeName(bi)));
@@ -770,14 +877,25 @@ public void TestBlendShapeExport(string fbxPath)
770877
originalMesh.GetBlendShapeFrameVertices(bi, fi, deltaVertices, deltaNormals, deltaTangents);
771878
exportedMesh.GetBlendShapeFrameVertices(bi, fi, fbxDeltaVertices, fbxDeltaNormals, fbxDeltaTangents);
772879

773-
var v3comparer = new Vector3Comparer();
774-
Assert.That(deltaVertices, Is.EqualTo(fbxDeltaVertices).Using<Vector3>(v3comparer), string.Format("delta vertices don't match"));
775-
Assert.That(deltaNormals, Is.EqualTo(fbxDeltaNormals).Using<Vector3>(v3comparer), string.Format("delta normals don't match"));
776-
Assert.That(deltaTangents, Is.EqualTo(fbxDeltaTangents).Using<Vector3>(v3comparer), string.Format("delta tangents don't match"));
777-
880+
var worldVertices = new Vector3[originalSMR.sharedMesh.vertices.Length];
881+
var exportedWorldVertices = new Vector3[exportedSMR.sharedMesh.vertices.Length];
882+
for (int k = 0; k < worldVertices.Length; k++)
883+
{
884+
worldVertices[k] = originalSMR.transform.TransformPoint(originalMesh.vertices[k] + deltaVertices[k]);
885+
exportedWorldVertices[k] = exportedSMR.transform.TransformPoint(exportedMesh.vertices[k] + fbxDeltaVertices[k]);
886+
}
887+
// Compute the Hausdorff distance to determine if two meshes have the same vertices as
888+
// we can't rely on the vertex order matching.
889+
var hausdorffDistance = ComputeHausdorffDistance<Vector3>(worldVertices, exportedWorldVertices, Vector3.Distance);
890+
Assert.That(hausdorffDistance, Is.LessThan(epsilon), "Maximum distance between two vertices greater than epsilon");
891+
892+
// TODO: Investigate importing blendshape normals without discrepancy in vertex count between the original/exported meshes
893+
// and add test to compare blendshape normals and tangents.
778894
}
779895
}
780896
}
897+
898+
FbxImportAndTestBlendshapes(exportedFbxPath);
781899
}
782900

783901
[Test]

0 commit comments

Comments
 (0)