Skip to content

Commit a9ce452

Browse files
authored
Merge pull request #507 from Unity-Technologies/UT-3300-add-blendshape-regression-test
Ut 3300 add blendshape regression test
2 parents 5c4b434 + d49c6b2 commit a9ce452

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
@@ -552,7 +552,7 @@ private void CompareMeshComponentAttributes(Mesh mesh, Mesh fbxMesh)
552552
Assert.AreEqual (mesh.tangents, fbxMesh.tangents);
553553
}
554554

555-
private void ExportSkinnedMesh(string fileToExport, out SkinnedMeshRenderer originalSkinnedMesh, out SkinnedMeshRenderer exportedSkinnedMesh){
555+
private string ExportSkinnedMesh(string fileToExport, out SkinnedMeshRenderer originalSkinnedMesh, out SkinnedMeshRenderer exportedSkinnedMesh){
556556
// add fbx to scene
557557
GameObject originalFbxObj = AssetDatabase.LoadMainAssetAtPath(fileToExport) as GameObject;
558558
Assert.IsNotNull (originalFbxObj);
@@ -566,8 +566,14 @@ private void ExportSkinnedMesh(string fileToExport, out SkinnedMeshRenderer orig
566566

567567
var importer = AssetImporter.GetAtPath(filename) as ModelImporter;
568568
#if UNITY_2019_1_OR_NEWER
569+
importer.importBlendShapes = true;
569570
importer.optimizeMeshPolygons = false;
570571
importer.optimizeMeshVertices = false;
572+
importer.meshCompression = ModelImporterMeshCompression.Off;
573+
// If either blendshape normals are imported or weldVertices is turned off (or both),
574+
// the vertex count between the original and exported meshes does not match.
575+
importer.importBlendShapeNormals = ModelImporterNormals.None;
576+
importer.weldVertices = true;
571577
#else
572578
importer.optimizeMesh = false;
573579
#endif // UNITY_2019_1_OR_NEWER
@@ -581,6 +587,8 @@ private void ExportSkinnedMesh(string fileToExport, out SkinnedMeshRenderer orig
581587

582588
exportedSkinnedMesh = fbxObj.GetComponentInChildren<SkinnedMeshRenderer> ();
583589
Assert.IsNotNull (exportedSkinnedMesh);
590+
591+
return filename;
584592
}
585593

586594
public class SkinnedMeshTestDataClass
@@ -731,26 +739,123 @@ public void TestSkinnedMeshes (string fbxPath) {
731739
Debug.LogWarningFormat ("Compared {0} out of a possible {1} bone weights", comparisonCount, minVertCount);
732740
}
733741

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

745-
[Ignore("Blendshapes not working yet")]
746849
[Test, TestCaseSource(typeof(AnimationTestDataClass), "BlendShapeTestCases")]
747850
public void TestBlendShapeExport(string fbxPath)
748851
{
852+
const float epsilon = 0.001f;
853+
749854
fbxPath = FindPathInUnitTests (fbxPath);
750855
Assert.That (fbxPath, Is.Not.Null);
751856

752857
SkinnedMeshRenderer originalSMR, exportedSMR;
753-
ExportSkinnedMesh (fbxPath, out originalSMR, out exportedSMR);
858+
var exportedFbxPath = ExportSkinnedMesh (fbxPath, out originalSMR, out exportedSMR);
754859

755860
var originalMesh = originalSMR.sharedMesh;
756861
var exportedMesh = exportedSMR.sharedMesh;
@@ -768,6 +873,8 @@ public void TestBlendShapeExport(string fbxPath)
768873
var fbxDeltaNormals = new Vector3[exportedMesh.vertexCount];
769874
var fbxDeltaTangents = new Vector3[exportedMesh.vertexCount];
770875

876+
Assert.AreEqual(deltaVertices.Length, fbxDeltaVertices.Length);
877+
771878
for (int bi = 0; bi < originalMesh.blendShapeCount; ++bi)
772879
{
773880
Assert.That(originalMesh.GetBlendShapeName(bi), Is.EqualTo(exportedMesh.GetBlendShapeName(bi)));
@@ -781,14 +888,25 @@ public void TestBlendShapeExport(string fbxPath)
781888
originalMesh.GetBlendShapeFrameVertices(bi, fi, deltaVertices, deltaNormals, deltaTangents);
782889
exportedMesh.GetBlendShapeFrameVertices(bi, fi, fbxDeltaVertices, fbxDeltaNormals, fbxDeltaTangents);
783890

784-
var v3comparer = new Vector3Comparer();
785-
Assert.That(deltaVertices, Is.EqualTo(fbxDeltaVertices).Using<Vector3>(v3comparer), string.Format("delta vertices don't match"));
786-
Assert.That(deltaNormals, Is.EqualTo(fbxDeltaNormals).Using<Vector3>(v3comparer), string.Format("delta normals don't match"));
787-
Assert.That(deltaTangents, Is.EqualTo(fbxDeltaTangents).Using<Vector3>(v3comparer), string.Format("delta tangents don't match"));
788-
891+
var worldVertices = new Vector3[originalSMR.sharedMesh.vertices.Length];
892+
var exportedWorldVertices = new Vector3[exportedSMR.sharedMesh.vertices.Length];
893+
for (int k = 0; k < worldVertices.Length; k++)
894+
{
895+
worldVertices[k] = originalSMR.transform.TransformPoint(originalMesh.vertices[k] + deltaVertices[k]);
896+
exportedWorldVertices[k] = exportedSMR.transform.TransformPoint(exportedMesh.vertices[k] + fbxDeltaVertices[k]);
897+
}
898+
// Compute the Hausdorff distance to determine if two meshes have the same vertices as
899+
// we can't rely on the vertex order matching.
900+
var hausdorffDistance = ComputeHausdorffDistance<Vector3>(worldVertices, exportedWorldVertices, Vector3.Distance);
901+
Assert.That(hausdorffDistance, Is.LessThan(epsilon), "Maximum distance between two vertices greater than epsilon");
902+
903+
// TODO: Investigate importing blendshape normals without discrepancy in vertex count between the original/exported meshes
904+
// and add test to compare blendshape normals and tangents.
789905
}
790906
}
791907
}
908+
909+
FbxImportAndTestBlendshapes(exportedFbxPath);
792910
}
793911

794912
[Test]

0 commit comments

Comments
 (0)