Skip to content

Commit e2c2989

Browse files
authored
Merge pull request #386 from Unity-Technologies/UT-110-add-animated-rotation-offset-export
Ut 110 add animated rotation offset export
2 parents c1adaf3 + c6e5c2b commit e2c2989

File tree

6 files changed

+1156
-145
lines changed

6 files changed

+1156
-145
lines changed

Assets/com.unity.formats.fbx/Editor/Scripts/FbxExporter.cs

Lines changed: 424 additions & 141 deletions
Large diffs are not rendered by default.
Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
using Unity.FbxSdk;
2+
using System.Collections.Generic;
3+
4+
namespace FbxExporters
5+
{
6+
namespace Editor
7+
{
8+
/// <summary>
9+
/// Store FBX property name and channel name
10+
/// Default constructor added because it needs to be called before autoimplemented properties can be assigned. Otherwise we get build errors
11+
/// </summary>
12+
struct FbxPropertyChannelPair
13+
{
14+
public string Property { get; private set; }
15+
public string Channel { get; private set; }
16+
17+
public FbxPropertyChannelPair(string p, string c) : this()
18+
{
19+
Property = p;
20+
Channel = c;
21+
}
22+
23+
struct UnityPropertyChannelPair
24+
{
25+
public string property;
26+
public string channel;
27+
28+
public UnityPropertyChannelPair(string p, string c)
29+
{
30+
property = p;
31+
channel = c;
32+
}
33+
}
34+
35+
/// <summary>
36+
/// Contains the two dictionaries that map Unity property to FBX property and Unity channel to Fbx channel
37+
/// for a set of properties.
38+
/// </summary>
39+
struct PropertyChannelMap
40+
{
41+
public Dictionary<string, string> MapUnityPropToFbxProp;
42+
public Dictionary<string, string> MapUnityChannelToFbxChannel;
43+
44+
public PropertyChannelMap(Dictionary<string,string> propertyMap, Dictionary<string, string> channelMap)
45+
{
46+
MapUnityPropToFbxProp = propertyMap;
47+
MapUnityChannelToFbxChannel = channelMap;
48+
}
49+
}
50+
51+
// =========== Property Maps ================
52+
// These are dictionaries that map a Unity property name to it's corresponding Fbx property name.
53+
// Split up into multiple dictionaries as some are channel and object dependant.
54+
55+
/// <summary>
56+
/// Map of Unity transform properties to their FBX equivalent.
57+
/// </summary>
58+
private static Dictionary<string, string> MapTransformPropToFbxProp = new Dictionary<string, string>()
59+
{
60+
{ "m_LocalScale", "Lcl Scaling" },
61+
{ "Motion S", "Lcl Scaling" },
62+
{ "m_LocalPosition", "Lcl Translation" },
63+
{ "Motion T", "Lcl Translation" },
64+
{ "m_TranslationOffset", "Translation" },
65+
{ "m_ScaleOffset", "Scaling" },
66+
{ "m_RotationOffset", "Rotation" }
67+
};
68+
69+
/// <summary>
70+
/// Map of Unity Aim constraint properties to their FBX equivalent.
71+
/// </summary>
72+
private static Dictionary<string, string> MapAimConstraintPropToFbxProp = new Dictionary<string, string>()
73+
{
74+
{ "m_AimVector", "AimVector" },
75+
{ "m_UpVector", "UpVector" },
76+
{ "m_WorldUpVector", "WorldUpVector" },
77+
{ "m_RotationOffset", "RotationOffset" }
78+
};
79+
80+
/// <summary>
81+
/// Map of Unity color properties to their FBX equivalent.
82+
/// </summary>
83+
private static Dictionary<string, string> MapColorPropToFbxProp = new Dictionary<string, string>()
84+
{
85+
{ "m_Color", "Color" }
86+
};
87+
88+
/// <summary>
89+
/// Map of Unity properties to their FBX equivalent.
90+
/// </summary>
91+
private static Dictionary<string, string> MapPropToFbxProp = new Dictionary<string, string>()
92+
{
93+
{ "m_Intensity", "Intensity" },
94+
{ "field of view", "FieldOfView" },
95+
{ "m_Weight", "Weight" }
96+
};
97+
98+
/// <summary>
99+
/// Map of Unity constraint source property name as a regular expression to the FBX property as a string format.
100+
/// This is necessary because the Unity property contains an index in to an array, and the FBX property contains
101+
/// the name of the source object.
102+
/// </summary>
103+
private static Dictionary<string, string> MapConstraintSourcePropToFbxProp = new Dictionary<string, string>()
104+
{
105+
{ @"m_Sources\.Array\.data\[(\d+)\]\.weight", "{0}.Weight" }
106+
};
107+
108+
/// <summary>
109+
/// Map of Unity constraint source transform property name as a regular expression to the FBX property as a string format.
110+
/// This is necessary because the Unity property contains an index in to an array, and the FBX property contains
111+
/// the name of the source object.
112+
/// </summary>
113+
private static Dictionary<string, string> MapConstraintSourceTransformPropToFbxProp = new Dictionary<string, string>()
114+
{
115+
{ @"m_TranslationOffsets\.Array\.data\[(\d+)\]", "{0}.Offset T" },
116+
{ @"m_RotationOffsets\.Array\.data\[(\d+)\]", "{0}.Offset R" }
117+
};
118+
119+
// ================== Channel Maps ======================
120+
121+
/// <summary>
122+
/// Map of Unity transform channels to their FBX equivalent.
123+
/// </summary>
124+
private static Dictionary<string, string> MapTransformChannelToFbxChannel = new Dictionary<string, string>()
125+
{
126+
{ "x", Globals.FBXSDK_CURVENODE_COMPONENT_X },
127+
{ "y", Globals.FBXSDK_CURVENODE_COMPONENT_Y },
128+
{ "z", Globals.FBXSDK_CURVENODE_COMPONENT_Z }
129+
};
130+
131+
/// <summary>
132+
/// Map of Unity color channels to their FBX equivalent.
133+
/// </summary>
134+
private static Dictionary<string, string> MapColorChannelToFbxChannel = new Dictionary<string, string>()
135+
{
136+
{ "b", Globals.FBXSDK_CURVENODE_COLOR_BLUE },
137+
{ "g", Globals.FBXSDK_CURVENODE_COLOR_GREEN },
138+
{ "r", Globals.FBXSDK_CURVENODE_COLOR_RED }
139+
};
140+
141+
// =======================================================
142+
143+
private static PropertyChannelMap TransformPropertyMap = new PropertyChannelMap(MapTransformPropToFbxProp, MapTransformChannelToFbxChannel);
144+
private static PropertyChannelMap AimConstraintPropertyMap = new PropertyChannelMap(MapAimConstraintPropToFbxProp, MapTransformChannelToFbxChannel);
145+
private static PropertyChannelMap ColorPropertyMap = new PropertyChannelMap(MapColorPropToFbxProp, MapColorChannelToFbxChannel);
146+
private static PropertyChannelMap ConstraintSourcePropertyMap = new PropertyChannelMap(MapConstraintSourcePropToFbxProp, null);
147+
private static PropertyChannelMap ConstraintSourceTransformPropertyMap = new PropertyChannelMap(MapConstraintSourceTransformPropToFbxProp, MapTransformChannelToFbxChannel);
148+
private static PropertyChannelMap OtherPropertyMap = new PropertyChannelMap(MapPropToFbxProp, null);
149+
150+
/// <summary>
151+
/// Separates and returns the property and channel from the full Unity property name.
152+
///
153+
/// Takes what is after the last period as the channel.
154+
/// In order to use this have to be certain that there are channels, as there are cases where what is after
155+
/// the last period is still the property name. E.g. m_Sources.Array.data[0].weight has no channel.
156+
/// </summary>
157+
/// <param name="fullPropertyName"></param>
158+
/// <returns></returns>
159+
private static UnityPropertyChannelPair GetUnityPropertyChannelPair(string fullPropertyName)
160+
{
161+
int index = fullPropertyName.LastIndexOf('.');
162+
if (index < 0)
163+
{
164+
return new UnityPropertyChannelPair(fullPropertyName, null);
165+
}
166+
167+
var property = fullPropertyName.Substring(0, index);
168+
var channel = fullPropertyName.Substring(index + 1);
169+
return new UnityPropertyChannelPair(property, channel);
170+
}
171+
172+
/// <summary>
173+
/// Get the Fbx property name for the given Unity property name from the given dictionary.
174+
/// </summary>
175+
/// <param name="uniProperty"></param>
176+
/// <param name="propertyMap"></param>
177+
/// <returns>The Fbx property name or null if there was no match in the dictionary</returns>
178+
private static string GetFbxProperty(string uniProperty, Dictionary<string, string> propertyMap)
179+
{
180+
string fbxProperty;
181+
if(!propertyMap.TryGetValue(uniProperty, out fbxProperty)){
182+
return null;
183+
}
184+
return fbxProperty;
185+
}
186+
187+
/// <summary>
188+
/// Get the Fbx property name for the given Unity constraint source property name from the given dictionary.
189+
///
190+
/// This is different from GetFbxProperty() because the Unity constraint source properties contain indices, and
191+
/// the Fbx constraint source property contains the name of the source object.
192+
/// </summary>
193+
/// <param name="uniProperty"></param>
194+
/// <param name="constraint"></param>
195+
/// <param name="propertyMap"></param>
196+
/// <returns>The Fbx property name or null if there was no match in the dictionary</returns>
197+
private static string GetFbxConstraintSourceProperty(string uniProperty, FbxConstraint constraint, Dictionary<string, string> propertyMap)
198+
{
199+
foreach (var prop in propertyMap)
200+
{
201+
var match = System.Text.RegularExpressions.Regex.Match(uniProperty, prop.Key);
202+
if (match.Success && match.Groups.Count > 0)
203+
{
204+
var matchedStr = match.Groups[1].Value;
205+
int index;
206+
if (!int.TryParse(matchedStr, out index))
207+
{
208+
continue;
209+
}
210+
var source = constraint.GetConstraintSource(index);
211+
return string.Format(prop.Value, source.GetName());
212+
}
213+
}
214+
return null;
215+
}
216+
217+
/// <summary>
218+
/// Get the Fbx channel name for the given Unity channel from the given dictionary.
219+
/// </summary>
220+
/// <param name="uniChannel"></param>
221+
/// <param name="channelMap"></param>
222+
/// <returns>The Fbx channel name or null if there was no match in the dictionary</returns>
223+
private static string GetFbxChannel(string uniChannel, Dictionary<string, string> channelMap)
224+
{
225+
string fbxChannel;
226+
if(!channelMap.TryGetValue(uniChannel, out fbxChannel))
227+
{
228+
return null;
229+
}
230+
return fbxChannel;
231+
}
232+
233+
/// <summary>
234+
/// Try to get the property channel pairs for the given Unity property from the given property channel mapping.
235+
/// </summary>
236+
/// <param name="uniPropertyName"></param>
237+
/// <param name="propertyChannelMap"></param>
238+
/// <param name="constraint"></param>
239+
/// <returns>The property channel pairs or null if there was no match</returns>
240+
private static FbxPropertyChannelPair[] GetChannelPairs(string uniPropertyName, PropertyChannelMap propertyChannelMap, FbxConstraint constraint = null)
241+
{
242+
// Unity property name is of the format "property.channel" or "property". Handle both cases.
243+
var possibleUniPropChannelPairs = new List<UnityPropertyChannelPair>();
244+
245+
// could give same result as already in the list, avoid checking this case twice
246+
var propChannelPair = GetUnityPropertyChannelPair(uniPropertyName);
247+
possibleUniPropChannelPairs.Add(propChannelPair);
248+
if (propChannelPair.property != uniPropertyName)
249+
{
250+
possibleUniPropChannelPairs.Add(new UnityPropertyChannelPair(uniPropertyName, null));
251+
}
252+
253+
foreach (var uniPropChannelPair in possibleUniPropChannelPairs)
254+
{
255+
// try to match property
256+
var fbxProperty = GetFbxProperty(uniPropChannelPair.property, propertyChannelMap.MapUnityPropToFbxProp);
257+
if (string.IsNullOrEmpty(fbxProperty) && constraint != null)
258+
{
259+
// check if it's a constraint source property
260+
fbxProperty = GetFbxConstraintSourceProperty(uniPropChannelPair.property, constraint, propertyChannelMap.MapUnityPropToFbxProp);
261+
}
262+
if (string.IsNullOrEmpty(fbxProperty))
263+
{
264+
continue;
265+
}
266+
267+
// matched property, now try to match channel
268+
string fbxChannel = null;
269+
if(!string.IsNullOrEmpty(uniPropChannelPair.channel) && propertyChannelMap.MapUnityChannelToFbxChannel != null)
270+
{
271+
fbxChannel = GetFbxChannel(uniPropChannelPair.channel, propertyChannelMap.MapUnityChannelToFbxChannel);
272+
if (string.IsNullOrEmpty(fbxChannel))
273+
{
274+
// couldn't match the Unity channel to the fbx channel
275+
continue;
276+
}
277+
}
278+
return new FbxPropertyChannelPair[] { new FbxPropertyChannelPair(fbxProperty, fbxChannel) };
279+
}
280+
return null;
281+
}
282+
283+
/// <summary>
284+
/// Map a Unity property name to the corresponding FBX property and
285+
/// channel names.
286+
/// </summary>
287+
public static bool TryGetValue(string uniPropertyName, out FbxPropertyChannelPair[] prop, FbxConstraint constraint = null)
288+
{
289+
prop = new FbxPropertyChannelPair[] { };
290+
291+
// spot angle is a special case as it returns two channel pairs instead of one
292+
System.StringComparison ct = System.StringComparison.CurrentCulture;
293+
if (uniPropertyName.StartsWith("m_SpotAngle", ct))
294+
{
295+
prop = new FbxPropertyChannelPair[]{
296+
new FbxPropertyChannelPair ("OuterAngle", null),
297+
new FbxPropertyChannelPair ("InnerAngle", null)
298+
};
299+
return true;
300+
}
301+
302+
var propertyMaps = new List<PropertyChannelMap>();
303+
304+
// Try get constraint specific channel pairs first as we know this is a constraint
305+
if (constraint != null)
306+
{
307+
// Aim constraint shares the RotationOffset property with RotationConstraint, so make sure that the correct FBX property is returned
308+
if (constraint.GetConstraintType() == FbxConstraint.EType.eAim)
309+
{
310+
propertyMaps.Add(AimConstraintPropertyMap);
311+
}
312+
313+
propertyMaps.Add(ConstraintSourcePropertyMap);
314+
propertyMaps.Add(ConstraintSourceTransformPropertyMap);
315+
}
316+
317+
// Check if this is a transform, color, or other property and return the channel pairs if they match.
318+
propertyMaps.Add(TransformPropertyMap);
319+
propertyMaps.Add(ColorPropertyMap);
320+
propertyMaps.Add(OtherPropertyMap);
321+
322+
foreach (var propMap in propertyMaps)
323+
{
324+
prop = GetChannelPairs(uniPropertyName, propMap, constraint);
325+
if (prop != null)
326+
{
327+
return true;
328+
}
329+
}
330+
return false;
331+
}
332+
}
333+
}
334+
}

Assets/com.unity.formats.fbx/Editor/Scripts/FbxPropertyChannelPair.cs.meta

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/com.unity.formats.fbx/EditorTests/FbxAnimationTest.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -347,9 +347,10 @@ public static void ConfigureImportSettings(string filename, object customSetting
347347
if (customSettings==null)
348348
{
349349
customSettings = new {
350-
resampleCurves = false,
351-
animationType= ModelImporterAnimationType.Legacy,
352-
animationCompression = ModelImporterAnimationCompression.Off
350+
resampleCurves = false,
351+
animationType = ModelImporterAnimationType.Legacy,
352+
animationCompression = ModelImporterAnimationCompression.Off,
353+
importConstraints = true
353354
};
354355
}
355356

@@ -358,6 +359,7 @@ public static void ConfigureImportSettings(string filename, object customSetting
358359
AssetDatabase.ImportAsset (filename);
359360
modelImporter.animationType = (ModelImporterAnimationType)customSettings.GetType().GetProperty("animationType").GetValue(customSettings,null);
360361
modelImporter.animationCompression = (ModelImporterAnimationCompression)customSettings.GetType().GetProperty("animationCompression").GetValue(customSettings,null);
362+
modelImporter.importConstraints = (bool)customSettings.GetType().GetProperty("importConstraints").GetValue(customSettings, null);
361363
AssetDatabase.ImportAsset (filename);
362364
}
363365

@@ -745,7 +747,7 @@ public int ContinuousRotationAnimTest (RotationCurveType rotCurveType, float []
745747
}
746748

747749
KeyData keyData = new TransformKeyData {
748-
importSettings = new {resampleCurves = false, animationType= ModelImporterAnimationType.Legacy, animationCompression = ModelImporterAnimationCompression.Off},
750+
importSettings = new {resampleCurves = false, animationType= ModelImporterAnimationType.Legacy, animationCompression = ModelImporterAnimationCompression.Off, importConstraints = true},
749751
compareOriginalKeys=compareOriginalKeys, RotationType = rotCurveType, propertyNames = propertyNames, componentType = componentType, keyTimes = keyTimesInSeconds, keyEulerValues = keyEulerValues };
750752

751753
var tester = new AnimTester {keyData=keyData, testName=testName, path=GetRandomFbxFilePath ()};

0 commit comments

Comments
 (0)