@@ -426,6 +426,86 @@ private static bool ExportUVs(FbxMesh fbxMesh, MeshInfo mesh, int[] unmergedTria
426
426
return k > 0 ;
427
427
}
428
428
429
+ /// <summary>
430
+ /// Export the mesh's blend shapes.
431
+ /// </summary>
432
+ private bool ExportBlendShapes ( MeshInfo mesh , FbxMesh fbxMesh , FbxScene fbxScene , int [ ] unmergedTriangles )
433
+ {
434
+ var umesh = mesh . mesh ;
435
+ if ( umesh . blendShapeCount == 0 )
436
+ return false ;
437
+
438
+ var fbxBlendShape = FbxBlendShape . Create ( fbxScene , umesh . name + "_BlendShape" ) ;
439
+ fbxMesh . AddDeformer ( fbxBlendShape ) ;
440
+
441
+ var numVertices = umesh . vertexCount ;
442
+ var basePoints = umesh . vertices ;
443
+ var baseNormals = umesh . normals ;
444
+ var baseTangents = umesh . tangents ;
445
+ var deltaPoints = new Vector3 [ numVertices ] ;
446
+ var deltaNormals = new Vector3 [ numVertices ] ;
447
+ var deltaTangents = new Vector3 [ numVertices ] ;
448
+
449
+ for ( int bi = 0 ; bi < umesh . blendShapeCount ; ++ bi )
450
+ {
451
+ var bsName = umesh . GetBlendShapeName ( bi ) ;
452
+ var numFrames = umesh . GetBlendShapeFrameCount ( bi ) ;
453
+ var fbxChannel = FbxBlendShapeChannel . Create ( fbxScene , bsName ) ;
454
+ fbxBlendShape . AddBlendShapeChannel ( fbxChannel ) ;
455
+
456
+ for ( int fi = 0 ; fi < numFrames ; ++ fi )
457
+ {
458
+ var weight = umesh . GetBlendShapeFrameWeight ( bi , fi ) ;
459
+ umesh . GetBlendShapeFrameVertices ( bi , fi , deltaPoints , deltaNormals , deltaTangents ) ;
460
+
461
+ var fbxShape = FbxShape . Create ( fbxScene , "" ) ;
462
+ fbxChannel . AddTargetShape ( fbxShape , weight ) ;
463
+
464
+ // control points
465
+ fbxShape . InitControlPoints ( ControlPointToIndex . Count ( ) ) ;
466
+ for ( int vi = 0 ; vi < numVertices ; ++ vi )
467
+ {
468
+ int ni = ControlPointToIndex [ basePoints [ vi ] ] ;
469
+ var v = basePoints [ vi ] + deltaPoints [ vi ] ;
470
+ fbxShape . SetControlPointAt ( ConvertToRightHanded ( v , UnitScaleFactor ) , ni ) ;
471
+ }
472
+
473
+ // normals
474
+ if ( mesh . HasValidNormals ( ) )
475
+ {
476
+ var elemNormals = fbxShape . CreateElementNormal ( ) ;
477
+ elemNormals . SetMappingMode ( FbxLayerElement . EMappingMode . eByPolygonVertex ) ;
478
+ elemNormals . SetReferenceMode ( FbxLayerElement . EReferenceMode . eDirect ) ;
479
+ var dstNormals = elemNormals . GetDirectArray ( ) ;
480
+ dstNormals . SetCount ( unmergedTriangles . Length ) ;
481
+ for ( int ii = 0 ; ii < unmergedTriangles . Length ; ++ ii )
482
+ {
483
+ int vi = unmergedTriangles [ ii ] ;
484
+ var n = baseNormals [ vi ] + deltaNormals [ vi ] ;
485
+ dstNormals . SetAt ( ii , ConvertToRightHanded ( n ) ) ;
486
+ }
487
+ }
488
+
489
+ // tangents
490
+ if ( mesh . HasValidTangents ( ) )
491
+ {
492
+ var elemTangents = fbxShape . CreateElementTangent ( ) ;
493
+ elemTangents . SetMappingMode ( FbxLayerElement . EMappingMode . eByPolygonVertex ) ;
494
+ elemTangents . SetReferenceMode ( FbxLayerElement . EReferenceMode . eDirect ) ;
495
+ var dstTangents = elemTangents . GetDirectArray ( ) ;
496
+ dstTangents . SetCount ( unmergedTriangles . Length ) ;
497
+ for ( int ii = 0 ; ii < unmergedTriangles . Length ; ++ ii )
498
+ {
499
+ int vi = unmergedTriangles [ ii ] ;
500
+ var t = ( Vector3 ) baseTangents [ vi ] + deltaTangents [ vi ] ;
501
+ dstTangents . SetAt ( ii , ConvertToRightHanded ( t ) ) ;
502
+ }
503
+ }
504
+ }
505
+ }
506
+ return true ;
507
+ }
508
+
429
509
/// <summary>
430
510
/// Takes in a left-handed UnityEngine.Vector3 denoting a normal,
431
511
/// returns a right-handed FbxVector4.
@@ -754,6 +834,9 @@ bool ExportMesh (MeshInfo meshInfo, FbxNode fbxNode)
754
834
// Set up normals, etc.
755
835
ExportComponentAttributes ( meshInfo , fbxMesh , unmergedPolygons . ToArray ( ) ) ;
756
836
837
+ // Set up blend shapes.
838
+ ExportBlendShapes ( meshInfo , fbxMesh , fbxScene , unmergedPolygons . ToArray ( ) ) ;
839
+
757
840
// set the fbxNode containing the mesh
758
841
fbxNode . SetNodeAttribute ( fbxMesh ) ;
759
842
fbxNode . SetShadingMode ( FbxNode . EShadingMode . eWireFrame ) ;
@@ -781,31 +864,28 @@ SkinnedMeshRenderer unitySkin
781
864
Debug . Log ( string . Format ( "exporting {0} {1}" , "Skin" , fbxNode . GetName ( ) ) ) ;
782
865
783
866
784
- Dictionary < SkinnedMeshRenderer , Transform [ ] > skinnedMeshToBonesMap ;
785
- // export skeleton
786
- if ( ! ExportSkeleton ( unitySkin , fbxScene , out skinnedMeshToBonesMap ) ) {
787
- Debug . LogWarning ( "failed to export skeleton" ) ;
788
- return false ;
789
- }
867
+ var meshInfo = new MeshInfo ( unitySkin . sharedMesh , unitySkin . sharedMaterials ) ;
790
868
791
- var meshInfo = new MeshInfo ( unitySkin . sharedMesh , unitySkin . sharedMaterials ) ;
792
-
793
- // export skin mesh
794
869
FbxMesh fbxMesh = null ;
795
- if ( ExportMesh ( meshInfo , fbxNode ) ) {
796
- fbxMesh = fbxNode . GetMesh ( ) ;
870
+ if ( ExportMesh ( meshInfo , fbxNode ) )
871
+ {
872
+ fbxMesh = fbxNode . GetMesh ( ) ;
797
873
}
798
-
799
- if ( fbxMesh == null ) {
800
- Debug . LogError ( "Could not find mesh" ) ;
874
+ if ( fbxMesh == null )
875
+ {
876
+ Debug . LogError ( "Could not find mesh" ) ;
801
877
return false ;
802
878
}
803
879
804
- // bind mesh to skeleton
805
- ExportSkin ( unitySkin , meshInfo , fbxScene , fbxMesh , fbxNode ) ;
880
+ Dictionary < SkinnedMeshRenderer , Transform [ ] > skinnedMeshToBonesMap ;
881
+ // export skeleton
882
+ if ( ExportSkeleton ( unitySkin , fbxScene , out skinnedMeshToBonesMap ) ) {
883
+ // bind mesh to skeleton
884
+ ExportSkin ( unitySkin , meshInfo , fbxScene , fbxMesh , fbxNode ) ;
806
885
807
- // add bind pose
808
- ExportBindPose ( unitySkin , fbxNode , fbxScene , skinnedMeshToBonesMap ) ;
886
+ // add bind pose
887
+ ExportBindPose ( unitySkin , fbxNode , fbxScene , skinnedMeshToBonesMap ) ;
888
+ }
809
889
810
890
return true ;
811
891
}
@@ -1137,6 +1217,30 @@ public static FbxVector4 ConvertQuaternionToXYZEuler (FbxQuaternion quat)
1137
1217
return new FbxVector4 ( vector4 . X , - vector4 . Y , - vector4 . Z , vector4 . W ) ;
1138
1218
}
1139
1219
1220
+ /// <summary>
1221
+ /// Euler to quaternion without axis conversion.
1222
+ /// </summary>
1223
+ /// <returns>a quaternion.</returns>
1224
+ /// <param name="euler">Euler.</param>
1225
+ public static FbxQuaternion EulerToQuaternion ( FbxVector4 euler )
1226
+ {
1227
+ FbxAMatrix m = new FbxAMatrix ( ) ;
1228
+ m . SetR ( euler ) ;
1229
+ return m . GetQ ( ) ;
1230
+ }
1231
+
1232
+ /// <summary>
1233
+ /// Quaternion to euler without axis conversion.
1234
+ /// </summary>
1235
+ /// <returns>a euler.</returns>
1236
+ /// <param name="quat">Quaternion.</param>
1237
+ public static FbxVector4 QuaternionToEuler ( FbxQuaternion quat )
1238
+ {
1239
+ FbxAMatrix m = new FbxAMatrix ( ) ;
1240
+ m . SetQ ( quat ) ;
1241
+ return m . GetR ( ) ;
1242
+ }
1243
+
1140
1244
// get a fbxNode's global default position.
1141
1245
protected bool ExportTransform ( UnityEngine . Transform unityTransform , FbxNode fbxNode , Vector3 newCenter , TransformExportType exportType )
1142
1246
{
@@ -1447,11 +1551,12 @@ public float Convert(float value)
1447
1551
1448
1552
/// <summary>
1449
1553
/// Store FBX property name and channel name
1554
+ /// Default constructor added because it needs to be called before autoimplemented properties can be assigned. Otherwise we get build errors
1450
1555
/// </summary>
1451
1556
struct FbxPropertyChannelPair {
1452
1557
public string Property { get ; private set ; }
1453
1558
public string Channel { get ; private set ; }
1454
- public FbxPropertyChannelPair ( string p , string c ) {
1559
+ public FbxPropertyChannelPair ( string p , string c ) : this ( ) {
1455
1560
Property = p ;
1456
1561
Channel = c ;
1457
1562
}
@@ -1602,8 +1707,9 @@ Key [] ComputeKeys(UnityEngine.Quaternion restRotation, FbxNode node) {
1602
1707
var fbxPreRotationEuler = node . GetRotationActive ( )
1603
1708
? node . GetPreRotation ( FbxNode . EPivotSet . eSourcePivot )
1604
1709
: new FbxVector4 ( ) ;
1605
- var fbxPreRotationInverse = new FbxQuaternion ( ) ;
1606
- fbxPreRotationInverse . ComposeSphericalXYZ ( fbxPreRotationEuler ) ;
1710
+
1711
+ // Get the inverse of the prerotation
1712
+ var fbxPreRotationInverse = ModelExporter . EulerToQuaternion ( fbxPreRotationEuler ) ;
1607
1713
fbxPreRotationInverse . Inverse ( ) ;
1608
1714
1609
1715
// If we're only animating along certain coords for some
@@ -1632,17 +1738,23 @@ Key [] ComputeKeys(UnityEngine.Quaternion restRotation, FbxNode node) {
1632
1738
( z == null ) ? lclQuaternion [ 2 ] : z . Evaluate ( seconds ) ,
1633
1739
( w == null ) ? lclQuaternion [ 3 ] : w . Evaluate ( seconds ) ) ;
1634
1740
1741
+ // convert the final animation to righthanded coords
1742
+ var finalEuler = ModelExporter . ConvertQuaternionToXYZEuler ( fbxFinalAnimation ) ;
1743
+
1744
+ // convert it back to a quaternion for multiplication
1745
+ fbxFinalAnimation = ModelExporter . EulerToQuaternion ( finalEuler ) ;
1746
+
1635
1747
// Cancel out the pre-rotation. Order matters. FBX reads left-to-right.
1636
1748
// When we run animation we will apply:
1637
1749
// pre-rotation
1638
1750
// then pre-rotation inverse
1639
1751
// then animation.
1640
- var fbxQuat = fbxPreRotationInverse * fbxFinalAnimation ;
1752
+ var fbxFinalQuat = fbxPreRotationInverse * fbxFinalAnimation ;
1641
1753
1642
1754
// Store the key so we can sort them later.
1643
1755
Key key ;
1644
1756
key . time = FbxTime . FromSecondDouble ( seconds ) ;
1645
- key . euler = ModelExporter . ConvertQuaternionToXYZEuler ( fbxQuat ) ;
1757
+ key . euler = ModelExporter . QuaternionToEuler ( fbxFinalQuat ) ; ;
1646
1758
keys [ i ++ ] = key ;
1647
1759
}
1648
1760
@@ -1681,6 +1793,12 @@ public void Animate(Transform unityTransform, FbxNode fbxNode, FbxAnimLayer fbxA
1681
1793
fbxAnimCurveY . KeyModifyEnd ( ) ;
1682
1794
fbxAnimCurveX . KeyModifyEnd ( ) ;
1683
1795
1796
+ // Uni-35616 unroll curves to preserve continuous rotations
1797
+ var fbxCurveNode = fbxNode . LclRotation . GetCurveNode ( fbxAnimLayer , false /*should already exist*/ ) ;
1798
+
1799
+ FbxAnimCurveFilterUnroll fbxAnimUnrollFilter = new FbxAnimCurveFilterUnroll ( ) ;
1800
+ fbxAnimUnrollFilter . Apply ( fbxCurveNode ) ;
1801
+
1684
1802
if ( Verbose ) {
1685
1803
Debug . Log ( "Exported rotation animation for " + fbxNode . GetName ( ) ) ;
1686
1804
}
@@ -1717,7 +1835,8 @@ protected void ExportAnimationClip (AnimationClip uniAnimClip, GameObject uniRoo
1717
1835
if ( timeMode == FbxTime . EMode . eCustom ) {
1718
1836
timeMode = FbxTime . EMode . eFrames30 ;
1719
1837
}
1720
- FbxTime . SetGlobalTimeMode ( timeMode ) ;
1838
+
1839
+ fbxScene . GetGlobalSettings ( ) . SetTimeMode ( timeMode ) ;
1721
1840
1722
1841
// set time correctly
1723
1842
var fbxStartTime = FbxTime . FromSecondDouble ( 0 ) ;
@@ -2352,7 +2471,7 @@ static void OnContextItem (MenuCommand command)
2352
2471
}
2353
2472
2354
2473
/// <summary>
2355
- // Validate the menu item defined by the function above.
2474
+ /// Validate the menu item defined by the function above.
2356
2475
/// </summary>
2357
2476
[ MenuItem ( MenuItemName , true , 30 ) ]
2358
2477
public static bool OnValidateMenuItem ( )
@@ -2367,6 +2486,7 @@ public static void DisplayNoSelectionDialog()
2367
2486
"No GameObjects selected for export." ,
2368
2487
"Ok" ) ;
2369
2488
}
2489
+
2370
2490
//
2371
2491
// export mesh info from Unity
2372
2492
//
0 commit comments