|
| 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 | +} |
0 commit comments