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 lists that map Unity property to FBX property and Unity channel to Fbx channel /// for a set of properties. /// struct PropertyChannelMap { public List<(string, string)> MapUnityPropToFbxProp; public List<(string, string)> MapUnityChannelToFbxChannel; public PropertyChannelMap(List<(string, string)> propertyMap, List<(string, string)> channelMap) { MapUnityPropToFbxProp = propertyMap; MapUnityChannelToFbxChannel = channelMap; } private string GetFbxValue(string uniValue, List<(string, string)> list) { return list.Find(x => x.Item1 == uniValue).Item2; } /// /// Get the Fbx property name for the given Unity property name from the given list. /// /// /// /// The Fbx property name or null if there was no match in the list public string GetFbxProperty(string uniProperty) { return GetFbxValue(uniProperty, MapUnityPropToFbxProp); } /// /// Get the Fbx channel name for the given Unity channel from the given list. /// /// /// /// The Fbx channel name or null if there was no match in the list public string GetFbxChannel(string uniChannel) { return GetFbxValue(uniChannel, MapUnityChannelToFbxChannel); } } // =========== Property Maps ================ // These are lists that map a Unity property name to it's corresponding Fbx property name. // Split up into multiple lists as some are channel and object dependant. /// /// Map of Unity transform properties to their FBX equivalent. /// private static List<(string, string)> MapTransformPropToFbxProp = new List<(string, string)>() { ("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"), ("localEulerAnglesRaw", "Lcl Rotation") }; /// /// Map of Unity Aim constraint properties to their FBX equivalent. /// private static List<(string, string)> MapAimConstraintPropToFbxProp = new List<(string, string)>() { ("m_AimVector", "AimVector"), ("m_UpVector", "UpVector"), ("m_WorldUpVector", "WorldUpVector"), ("m_RotationOffset", "RotationOffset") }; /// /// Map of Unity color properties to their FBX equivalent. /// private static List<(string, string)> MapColorPropToFbxProp = new List<(string, string)>() { ("m_Color", "Color") }; /// /// Map of Unity properties to their FBX equivalent. /// private static List<(string, string)> MapPropToFbxProp = new List<(string, string)>() { ("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 List<(string, string)> MapConstraintSourcePropToFbxProp = new List<(string, string)>() { (@"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 List<(string, string)> MapConstraintSourceTransformPropToFbxProp = new List<(string, string)>() { (@"m_TranslationOffsets\.Array\.data\[(\d+)\]", "{0}.Offset T"), (@"m_RotationOffsets\.Array\.data\[(\d+)\]", "{0}.Offset R") }; /// /// Map of Unity blendshape property name as a regular expression to the FBX property. /// This is necessary because the Unity property contains the name of the target object. /// private static List<(string, string)> MapBlendshapesPropToFbxProp = new List<(string, string)>() { (@"blendShape\.(\S+)", "DeformPercent") }; // ================== Channel Maps ====================== /// /// Map of Unity transform channels to their FBX equivalent. /// private static List<(string, string)> MapTransformChannelToFbxChannel = new List<(string, string)>() { ("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 List<(string, string)> MapColorChannelToFbxChannel = new List<(string, string)>() { ("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 BlendshapeMap = new PropertyChannelMap(MapBlendshapesPropToFbxProp, null); 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 constraint source property name from the given list. /// /// 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 list private static string GetFbxConstraintSourceProperty(string uniProperty, FbxConstraint constraint, List<(string, string)> propertyMap) { foreach (var prop in propertyMap) { var match = System.Text.RegularExpressions.Regex.Match(uniProperty, prop.Item1); 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.Item2, source.GetName()); } } return null; } /// /// Get the Fbx property name for the given Unity blendshape property name from the given list. /// /// This is different from GetFbxProperty() because the Unity blendshape properties contain the name /// of the target object. /// /// /// /// The Fbx property name or null if there was no match in the list private static string GetFbxBlendshapeProperty(string uniProperty, List<(string, string)> propertyMap) { foreach (var prop in propertyMap) { var match = System.Text.RegularExpressions.Regex.Match(uniProperty, prop.Item1); if (match.Success) { return prop.Item2; } } return null; } /// /// 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 = propertyChannelMap.GetFbxProperty(uniPropChannelPair.property); if (string.IsNullOrEmpty(fbxProperty)) { if (constraint != null) { // check if it's a constraint source property fbxProperty = GetFbxConstraintSourceProperty(uniPropChannelPair.property, constraint, propertyChannelMap.MapUnityPropToFbxProp); } else if (propertyChannelMap.MapUnityPropToFbxProp == MapBlendshapesPropToFbxProp) { // check if it's a blendshape property fbxProperty = GetFbxBlendshapeProperty(uniPropChannelPair.property, 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 = propertyChannelMap.GetFbxChannel(uniPropChannelPair.channel); 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); propertyMaps.Add(BlendshapeMap); foreach (var propMap in propertyMaps) { prop = GetChannelPairs(uniPropertyName, propMap, constraint); if (prop != null) { return true; } } return false; } } }