@@ -541,7 +541,7 @@ private void CompareMeshComponentAttributes(Mesh mesh, Mesh fbxMesh)
541
541
Assert . AreEqual ( mesh . tangents , fbxMesh . tangents ) ;
542
542
}
543
543
544
- private void ExportSkinnedMesh ( string fileToExport , out SkinnedMeshRenderer originalSkinnedMesh , out SkinnedMeshRenderer exportedSkinnedMesh ) {
544
+ private string ExportSkinnedMesh ( string fileToExport , out SkinnedMeshRenderer originalSkinnedMesh , out SkinnedMeshRenderer exportedSkinnedMesh ) {
545
545
// add fbx to scene
546
546
GameObject originalFbxObj = AssetDatabase . LoadMainAssetAtPath ( fileToExport ) as GameObject ;
547
547
Assert . IsNotNull ( originalFbxObj ) ;
@@ -555,8 +555,14 @@ private void ExportSkinnedMesh(string fileToExport, out SkinnedMeshRenderer orig
555
555
556
556
var importer = AssetImporter . GetAtPath ( filename ) as ModelImporter ;
557
557
#if UNITY_2019_1_OR_NEWER
558
+ importer . importBlendShapes = true ;
558
559
importer . optimizeMeshPolygons = false ;
559
560
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 ;
560
566
#else
561
567
importer . optimizeMesh = false ;
562
568
#endif // UNITY_2019_1_OR_NEWER
@@ -570,6 +576,8 @@ private void ExportSkinnedMesh(string fileToExport, out SkinnedMeshRenderer orig
570
576
571
577
exportedSkinnedMesh = fbxObj . GetComponentInChildren < SkinnedMeshRenderer > ( ) ;
572
578
Assert . IsNotNull ( exportedSkinnedMesh ) ;
579
+
580
+ return filename ;
573
581
}
574
582
575
583
public class SkinnedMeshTestDataClass
@@ -720,26 +728,123 @@ public void TestSkinnedMeshes (string fbxPath) {
720
728
Debug . LogWarningFormat ( "Compared {0} out of a possible {1} bone weights" , comparisonCount , minVertCount ) ;
721
729
}
722
730
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 )
724
795
{
725
- public int Compare ( Vector3 a , Vector3 b )
796
+ // Create the FBX manager
797
+ using ( var fbxManager = FbxManager . Create ( ) )
726
798
{
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 ) ;
731
835
}
732
836
}
733
837
734
- [ Ignore ( "Blendshapes not working yet" ) ]
735
838
[ Test , TestCaseSource ( typeof ( AnimationTestDataClass ) , "BlendShapeTestCases" ) ]
736
839
public void TestBlendShapeExport ( string fbxPath )
737
840
{
841
+ const float epsilon = 0.001f ;
842
+
738
843
fbxPath = FindPathInUnitTests ( fbxPath ) ;
739
844
Assert . That ( fbxPath , Is . Not . Null ) ;
740
845
741
846
SkinnedMeshRenderer originalSMR , exportedSMR ;
742
- ExportSkinnedMesh ( fbxPath , out originalSMR , out exportedSMR ) ;
847
+ var exportedFbxPath = ExportSkinnedMesh ( fbxPath , out originalSMR , out exportedSMR ) ;
743
848
744
849
var originalMesh = originalSMR . sharedMesh ;
745
850
var exportedMesh = exportedSMR . sharedMesh ;
@@ -757,6 +862,8 @@ public void TestBlendShapeExport(string fbxPath)
757
862
var fbxDeltaNormals = new Vector3 [ exportedMesh . vertexCount ] ;
758
863
var fbxDeltaTangents = new Vector3 [ exportedMesh . vertexCount ] ;
759
864
865
+ Assert . AreEqual ( deltaVertices . Length , fbxDeltaVertices . Length ) ;
866
+
760
867
for ( int bi = 0 ; bi < originalMesh . blendShapeCount ; ++ bi )
761
868
{
762
869
Assert . That ( originalMesh . GetBlendShapeName ( bi ) , Is . EqualTo ( exportedMesh . GetBlendShapeName ( bi ) ) ) ;
@@ -770,14 +877,25 @@ public void TestBlendShapeExport(string fbxPath)
770
877
originalMesh . GetBlendShapeFrameVertices ( bi , fi , deltaVertices , deltaNormals , deltaTangents ) ;
771
878
exportedMesh . GetBlendShapeFrameVertices ( bi , fi , fbxDeltaVertices , fbxDeltaNormals , fbxDeltaTangents ) ;
772
879
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.
778
894
}
779
895
}
780
896
}
897
+
898
+ FbxImportAndTestBlendshapes ( exportedFbxPath ) ;
781
899
}
782
900
783
901
[ Test ]
0 commit comments