using Autodesk.Fbx; using System.Collections.Generic; namespace UnityEditor.Formats.Fbx.Exporter { /// /// Store FBX property name and channel name /// Default constructor added because it needs to be called before autoimplemented properties can be assigned. Otherwise we get build errors /// struct FbxPropertyChannelPair { public string Property { get; private set; } public string Channel { get; private set; } public FbxPropertyChannelPair(string p, string c) : this() { Property = p; Channel = c; } struct UnityPropertyChannelPair { public string property; public string channel; public UnityPropertyChannelPair(string p, string c) { property = p; channel = c; } } /// /// Contains the two dictionaries that map Unity property to FBX property and Unity channel to Fbx channel /// for a set of properties. /// struct PropertyChannelMap { public Dictionary MapUnityPropToFbxProp; public Dictionary MapUnityChannelToFbxChannel; public PropertyChannelMap(Dictionary propertyMap, Dictionary channelMap) { MapUnityPropToFbxProp = propertyMap; MapUnityChannelToFbxChannel = channelMap; } } // =========== Property Maps ================ // These are dictionaries that map a Unity property name to it's corresponding Fbx property name. // Split up into multiple dictionaries as some are channel and object dependant. /// /// Map of Unity transform properties to their FBX equivalent. /// private static Dictionary MapTransformPropToFbxProp = new Dictionary() { { "m_LocalScale", "Lcl Scaling" }, { "Motion S", "Lcl Scaling" }, { "m_LocalPosition", "Lcl Translation" }, { "Motion T", "Lcl Translation" }, { "m_TranslationOffset", "Translation" }, { "m_ScaleOffset", "Scaling" }, { "m_RotationOffset", "Rotation" } }; /// /// Map of Unity Aim constraint properties to their FBX equivalent. /// private static Dictionary MapAimConstraintPropToFbxProp = new Dictionary() { { "m_AimVector", "AimVector" }, { "m_UpVector", "UpVector" }, { "m_WorldUpVector", "WorldUpVector" }, { "m_RotationOffset", "RotationOffset" } }; /// /// Map of Unity color properties to their FBX equivalent. /// private static Dictionary MapColorPropToFbxProp = new Dictionary() { { "m_Color", "Color" } }; /// /// Map of Unity properties to their FBX equivalent. /// private static Dictionary MapPropToFbxProp = new Dictionary() { { "m_Intensity", "Intensity" }, { "field of view", "FieldOfView" }, { "m_Weight", "Weight" }, { "m_FocalLength", "FocalLength" }, { "m_LensShift.x", "FilmOffsetX" }, { "m_LensShift.y", "FilmOffsetY" } }; /// /// Map of Unity constraint source property name as a regular expression to the FBX property as a string format. /// This is necessary because the Unity property contains an index in to an array, and the FBX property contains /// the name of the source object. /// private static Dictionary MapConstraintSourcePropToFbxProp = new Dictionary() { { @"m_Sources\.Array\.data\[(\d+)\]\.weight", "{0}.Weight" } }; /// /// Map of Unity constraint source transform property name as a regular expression to the FBX property as a string format. /// This is necessary because the Unity property contains an index in to an array, and the FBX property contains /// the name of the source object. /// private static Dictionary MapConstraintSourceTransformPropToFbxProp = new Dictionary() { { @"m_TranslationOffsets\.Array\.data\[(\d+)\]", "{0}.Offset T" }, { @"m_RotationOffsets\.Array\.data\[(\d+)\]", "{0}.Offset R" } }; // ================== Channel Maps ====================== /// /// Map of Unity transform channels to their FBX equivalent. /// private static Dictionary MapTransformChannelToFbxChannel = new Dictionary() { { "x", Globals.FBXSDK_CURVENODE_COMPONENT_X }, { "y", Globals.FBXSDK_CURVENODE_COMPONENT_Y }, { "z", Globals.FBXSDK_CURVENODE_COMPONENT_Z } }; /// /// Map of Unity color channels to their FBX equivalent. /// private static Dictionary MapColorChannelToFbxChannel = new Dictionary() { { "b", Globals.FBXSDK_CURVENODE_COLOR_BLUE }, { "g", Globals.FBXSDK_CURVENODE_COLOR_GREEN }, { "r", Globals.FBXSDK_CURVENODE_COLOR_RED } }; // ======================================================= private static PropertyChannelMap TransformPropertyMap = new PropertyChannelMap(MapTransformPropToFbxProp, MapTransformChannelToFbxChannel); private static PropertyChannelMap AimConstraintPropertyMap = new PropertyChannelMap(MapAimConstraintPropToFbxProp, MapTransformChannelToFbxChannel); private static PropertyChannelMap ColorPropertyMap = new PropertyChannelMap(MapColorPropToFbxProp, MapColorChannelToFbxChannel); private static PropertyChannelMap ConstraintSourcePropertyMap = new PropertyChannelMap(MapConstraintSourcePropToFbxProp, null); private static PropertyChannelMap ConstraintSourceTransformPropertyMap = new PropertyChannelMap(MapConstraintSourceTransformPropToFbxProp, MapTransformChannelToFbxChannel); private static PropertyChannelMap OtherPropertyMap = new PropertyChannelMap(MapPropToFbxProp, null); /// /// Separates and returns the property and channel from the full Unity property name. /// /// Takes what is after the last period as the channel. /// In order to use this have to be certain that there are channels, as there are cases where what is after /// the last period is still the property name. E.g. m_Sources.Array.data[0].weight has no channel. /// /// /// private static UnityPropertyChannelPair GetUnityPropertyChannelPair(string fullPropertyName) { int index = fullPropertyName.LastIndexOf('.'); if (index < 0) { return new UnityPropertyChannelPair(fullPropertyName, null); } var property = fullPropertyName.Substring(0, index); var channel = fullPropertyName.Substring(index + 1); return new UnityPropertyChannelPair(property, channel); } /// /// Get the Fbx property name for the given Unity property name from the given dictionary. /// /// /// /// The Fbx property name or null if there was no match in the dictionary private static string GetFbxProperty(string uniProperty, Dictionary propertyMap) { string fbxProperty; if(!propertyMap.TryGetValue(uniProperty, out fbxProperty)){ return null; } return fbxProperty; } /// /// Get the Fbx property name for the given Unity constraint source property name from the given dictionary. /// /// This is different from GetFbxProperty() because the Unity constraint source properties contain indices, and /// the Fbx constraint source property contains the name of the source object. /// /// /// /// /// The Fbx property name or null if there was no match in the dictionary private static string GetFbxConstraintSourceProperty(string uniProperty, FbxConstraint constraint, Dictionary propertyMap) { foreach (var prop in propertyMap) { var match = System.Text.RegularExpressions.Regex.Match(uniProperty, prop.Key); if (match.Success && match.Groups.Count > 0) { var matchedStr = match.Groups[1].Value; int index; if (!int.TryParse(matchedStr, out index)) { continue; } var source = constraint.GetConstraintSource(index); return string.Format(prop.Value, source.GetName()); } } return null; } /// /// Get the Fbx channel name for the given Unity channel from the given dictionary. /// /// /// /// The Fbx channel name or null if there was no match in the dictionary private static string GetFbxChannel(string uniChannel, Dictionary channelMap) { string fbxChannel; if(!channelMap.TryGetValue(uniChannel, out fbxChannel)) { return null; } return fbxChannel; } /// /// Try to get the property channel pairs for the given Unity property from the given property channel mapping. /// /// /// /// /// The property channel pairs or null if there was no match private static FbxPropertyChannelPair[] GetChannelPairs(string uniPropertyName, PropertyChannelMap propertyChannelMap, FbxConstraint constraint = null) { // Unity property name is of the format "property.channel" or "property". Handle both cases. var possibleUniPropChannelPairs = new List(); // could give same result as already in the list, avoid checking this case twice var propChannelPair = GetUnityPropertyChannelPair(uniPropertyName); possibleUniPropChannelPairs.Add(propChannelPair); if (propChannelPair.property != uniPropertyName) { possibleUniPropChannelPairs.Add(new UnityPropertyChannelPair(uniPropertyName, null)); } foreach (var uniPropChannelPair in possibleUniPropChannelPairs) { // try to match property var fbxProperty = GetFbxProperty(uniPropChannelPair.property, propertyChannelMap.MapUnityPropToFbxProp); if (string.IsNullOrEmpty(fbxProperty) && constraint != null) { // check if it's a constraint source property fbxProperty = GetFbxConstraintSourceProperty(uniPropChannelPair.property, constraint, propertyChannelMap.MapUnityPropToFbxProp); } if (string.IsNullOrEmpty(fbxProperty)) { continue; } // matched property, now try to match channel string fbxChannel = null; if(!string.IsNullOrEmpty(uniPropChannelPair.channel) && propertyChannelMap.MapUnityChannelToFbxChannel != null) { fbxChannel = GetFbxChannel(uniPropChannelPair.channel, propertyChannelMap.MapUnityChannelToFbxChannel); if (string.IsNullOrEmpty(fbxChannel)) { // couldn't match the Unity channel to the fbx channel continue; } } return new FbxPropertyChannelPair[] { new FbxPropertyChannelPair(fbxProperty, fbxChannel) }; } return null; } /// /// Map a Unity property name to the corresponding FBX property and /// channel names. /// public static bool TryGetValue(string uniPropertyName, out FbxPropertyChannelPair[] prop, FbxConstraint constraint = null) { prop = new FbxPropertyChannelPair[] { }; // spot angle is a special case as it returns two channel pairs instead of one System.StringComparison ct = System.StringComparison.CurrentCulture; if (uniPropertyName.StartsWith("m_SpotAngle", ct)) { prop = new FbxPropertyChannelPair[]{ new FbxPropertyChannelPair ("OuterAngle", null), new FbxPropertyChannelPair ("InnerAngle", null) }; return true; } var propertyMaps = new List(); // Try get constraint specific channel pairs first as we know this is a constraint if (constraint != null) { // Aim constraint shares the RotationOffset property with RotationConstraint, so make sure that the correct FBX property is returned if (constraint.GetConstraintType() == FbxConstraint.EType.eAim) { propertyMaps.Add(AimConstraintPropertyMap); } propertyMaps.Add(ConstraintSourcePropertyMap); propertyMaps.Add(ConstraintSourceTransformPropertyMap); } // Check if this is a transform, color, or other property and return the channel pairs if they match. propertyMaps.Add(TransformPropertyMap); propertyMaps.Add(ColorPropertyMap); propertyMaps.Add(OtherPropertyMap); foreach (var propMap in propertyMaps) { prop = GetChannelPairs(uniPropertyName, propMap, constraint); if (prop != null) { return true; } } return false; } } }