@@ -552,7 +552,7 @@ private void CompareMeshComponentAttributes(Mesh mesh, Mesh fbxMesh)
552
552
Assert . AreEqual ( mesh . tangents , fbxMesh . tangents ) ;
553
553
}
554
554
555
- private void ExportSkinnedMesh ( string fileToExport , out SkinnedMeshRenderer originalSkinnedMesh , out SkinnedMeshRenderer exportedSkinnedMesh ) {
555
+ private string ExportSkinnedMesh ( string fileToExport , out SkinnedMeshRenderer originalSkinnedMesh , out SkinnedMeshRenderer exportedSkinnedMesh ) {
556
556
// add fbx to scene
557
557
GameObject originalFbxObj = AssetDatabase . LoadMainAssetAtPath ( fileToExport ) as GameObject ;
558
558
Assert . IsNotNull ( originalFbxObj ) ;
@@ -566,8 +566,14 @@ private void ExportSkinnedMesh(string fileToExport, out SkinnedMeshRenderer orig
566
566
567
567
var importer = AssetImporter . GetAtPath ( filename ) as ModelImporter ;
568
568
#if UNITY_2019_1_OR_NEWER
569
+ importer . importBlendShapes = true ;
569
570
importer . optimizeMeshPolygons = false ;
570
571
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 ;
571
577
#else
572
578
importer . optimizeMesh = false ;
573
579
#endif // UNITY_2019_1_OR_NEWER
@@ -581,6 +587,8 @@ private void ExportSkinnedMesh(string fileToExport, out SkinnedMeshRenderer orig
581
587
582
588
exportedSkinnedMesh = fbxObj . GetComponentInChildren < SkinnedMeshRenderer > ( ) ;
583
589
Assert . IsNotNull ( exportedSkinnedMesh ) ;
590
+
591
+ return filename ;
584
592
}
585
593
586
594
public class SkinnedMeshTestDataClass
@@ -731,26 +739,123 @@ public void TestSkinnedMeshes (string fbxPath) {
731
739
Debug . LogWarningFormat ( "Compared {0} out of a possible {1} bone weights" , comparisonCount , minVertCount ) ;
732
740
}
733
741
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 )
735
744
{
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 ++ )
737
750
{
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 ) ;
742
846
}
743
847
}
744
848
745
- [ Ignore ( "Blendshapes not working yet" ) ]
746
849
[ Test , TestCaseSource ( typeof ( AnimationTestDataClass ) , "BlendShapeTestCases" ) ]
747
850
public void TestBlendShapeExport ( string fbxPath )
748
851
{
852
+ const float epsilon = 0.001f ;
853
+
749
854
fbxPath = FindPathInUnitTests ( fbxPath ) ;
750
855
Assert . That ( fbxPath , Is . Not . Null ) ;
751
856
752
857
SkinnedMeshRenderer originalSMR , exportedSMR ;
753
- ExportSkinnedMesh ( fbxPath , out originalSMR , out exportedSMR ) ;
858
+ var exportedFbxPath = ExportSkinnedMesh ( fbxPath , out originalSMR , out exportedSMR ) ;
754
859
755
860
var originalMesh = originalSMR . sharedMesh ;
756
861
var exportedMesh = exportedSMR . sharedMesh ;
@@ -768,6 +873,8 @@ public void TestBlendShapeExport(string fbxPath)
768
873
var fbxDeltaNormals = new Vector3 [ exportedMesh . vertexCount ] ;
769
874
var fbxDeltaTangents = new Vector3 [ exportedMesh . vertexCount ] ;
770
875
876
+ Assert . AreEqual ( deltaVertices . Length , fbxDeltaVertices . Length ) ;
877
+
771
878
for ( int bi = 0 ; bi < originalMesh . blendShapeCount ; ++ bi )
772
879
{
773
880
Assert . That ( originalMesh . GetBlendShapeName ( bi ) , Is . EqualTo ( exportedMesh . GetBlendShapeName ( bi ) ) ) ;
@@ -781,14 +888,25 @@ public void TestBlendShapeExport(string fbxPath)
781
888
originalMesh . GetBlendShapeFrameVertices ( bi , fi , deltaVertices , deltaNormals , deltaTangents ) ;
782
889
exportedMesh . GetBlendShapeFrameVertices ( bi , fi , fbxDeltaVertices , fbxDeltaNormals , fbxDeltaTangents ) ;
783
890
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.
789
905
}
790
906
}
791
907
}
908
+
909
+ FbxImportAndTestBlendshapes ( exportedFbxPath ) ;
792
910
}
793
911
794
912
[ Test ]
0 commit comments