using System; using System.Collections.Generic; using System.Reflection; using UnityEngine; using UnityEngine.Animations; using UnityEngine.Animations.Rigging; using UnityEngine.Playables; namespace UnityEditor.Animations.Rigging { /// /// Utility class that groups bi-directional baking utilities. /// public static class BakeUtils { private const string kBakeToSkeletonUndoLabel = "Transfer motion to skeleton"; private const string kBakeToConstraintUndoLabel = "Transfer motion to constraint"; interface IEvaluationGraph { void Evaluate(float time); } class EvaluationGraph : IDisposable, IEvaluationGraph { SyncSceneToStreamLayer m_SyncSceneToStreamLayer; List m_RigLayers; PlayableGraph m_Graph; AnimationClip m_Clip; bool m_ClipLoopTime; public EvaluationGraph(RigBuilder rigBuilder, AnimationClip clip, AnimationClip defaultPoseClip, IReadOnlyDictionary overrides, IRigConstraint lastConstraint = null) { m_SyncSceneToStreamLayer = new SyncSceneToStreamLayer(); bool stopBuilding = false; var layers = rigBuilder.layers; m_RigLayers = new List(layers.Count); for (int i = 0; i < layers.Count; ++i) { if (stopBuilding == true) break; if (layers[i].rig == null || !layers[i].active) continue; IRigConstraint[] constraints = RigUtils.GetConstraints(layers[i].rig); if (constraints == null || constraints.Length == 0) continue; var newConstraints = new List(constraints.Length); foreach (IRigConstraint constraint in constraints) { if (overrides.TryGetValue(constraint, out IRigConstraint newConstraint)) { if (newConstraint != null) { newConstraints.Add(newConstraint); } } else { newConstraints.Add(constraint); } if (constraint == lastConstraint) { stopBuilding = true; break; } } m_RigLayers.Add(new OverrideRigLayer(layers[i].rig, newConstraints.ToArray())); } m_Graph = PlayableGraph.Create("Evaluation-Graph"); m_Graph.SetTimeUpdateMode(DirectorUpdateMode.Manual); var animator = rigBuilder.GetComponent(); m_Clip = clip; var settings = AnimationUtility.GetAnimationClipSettings(m_Clip); m_ClipLoopTime = settings.loopTime; // Override loop time in clip asset. settings.loopTime = false; AnimationUtility.SetAnimationClipSettings(m_Clip, settings); var defaultPosePlayable = AnimationClipPlayable.Create(m_Graph, defaultPoseClip); var clipPlayable = AnimationClipPlayable.Create(m_Graph, m_Clip); defaultPosePlayable.SetApplyFootIK(false); clipPlayable.SetApplyFootIK(false); AnimationLayerMixerPlayable mixer = AnimationLayerMixerPlayable.Create(m_Graph, 2); mixer.ConnectInput(0, defaultPosePlayable, 0, 1.0f); mixer.ConnectInput(1, clipPlayable, 0, 1.0f); Playable inputPlayable = mixer; var playableChains = RigBuilderUtils.BuildPlayables(animator, m_Graph, m_RigLayers, m_SyncSceneToStreamLayer); foreach (var chain in playableChains) { if (!chain.IsValid()) continue; chain.playables[0].AddInput(inputPlayable, 0, 1); inputPlayable = chain.playables[chain.playables.Length - 1]; } var output = AnimationPlayableOutput.Create(m_Graph, "bake-output", animator); output.SetSourcePlayable(inputPlayable); } public void Evaluate(float time) { if (!AnimationMode.InAnimationMode()) return; m_SyncSceneToStreamLayer.Update(m_RigLayers); foreach (var layer in m_RigLayers) { if (layer.IsValid() && layer.active) layer.Update(); } AnimationMode.BeginSampling(); AnimationMode.SamplePlayableGraph(m_Graph, 0, time); AnimationMode.EndSampling(); } public void Dispose() { m_Graph.Destroy(); for (int i = 0; i < m_RigLayers.Count; ++i) { m_RigLayers[i].Reset(); } m_RigLayers.Clear(); m_SyncSceneToStreamLayer.Reset(); // Restore loop time in clip asset. var settings = AnimationUtility.GetAnimationClipSettings(m_Clip); settings.loopTime = m_ClipLoopTime; AnimationUtility.SetAnimationClipSettings(m_Clip, settings); } } /// /// Validates if the Editor and the provided RigBuilder are in a correct state to do motion transfer. /// /// The RigBuilder that will be used for motion transfer. /// Returns true if both the editor and the provided RigBuilder are in a valid state for motion transfer. Returns false if the requirements are not met. public static bool TransferMotionValidate(RigBuilder rigBuilder) { if (!AnimationWindowUtils.isPreviewing || AnimationWindowUtils.activeAnimationClip == null) return false; var selected = Selection.instanceIDs; if (selected.Length != 1) return false; var selectedGO = EditorUtility.InstanceIDToObject(selected[0]) as GameObject; if (selectedGO != rigBuilder.gameObject) return false; var animator = rigBuilder.GetComponent(); if (animator.isHuman) return false; return true; } /// /// Validates if the Editor and the provided Rig are in a correct state to do motion transfer. /// /// The Rig that will be used for motion transfer. /// Returns true if both the editor and the provided Rig are in a valid state for motion transfer. Returns false if the requirements are not met. public static bool TransferMotionValidate(Rig rig) { if (!AnimationWindowUtils.isPreviewing || AnimationWindowUtils.activeAnimationClip == null) return false; var selected = Selection.instanceIDs; if (selected.Length != 1) return false; var selectedGO = EditorUtility.InstanceIDToObject(selected[0]) as GameObject; if (selectedGO != rig.gameObject) return false; var rigBuilder = rig.GetComponentInParent(); if (rigBuilder == null) return false; var animator = rigBuilder.GetComponent(); if (animator.isHuman) return false; bool inRigBuilder = false; var layers = rigBuilder.layers; for (int i = 0; i < layers.Count; ++i) { if (layers[i].rig == rig && layers[i].active) inRigBuilder = true; } return inRigBuilder; } /// /// Validates if the Editor and the provided RigConstraint are in a correct state to do motion transfer. /// /// Type of RigConstraint that is to be validated. /// The RigConstraint that will be used for motion transfer. /// Returns true if both the editor and the provided RigConstraint are in a valid state for motion transfer. Returns false if the requirements are not met. public static bool TransferMotionValidate(T constraint) where T : MonoBehaviour, IRigConstraint { if (!AnimationWindowUtils.isPreviewing || AnimationWindowUtils.activeAnimationClip == null) return false; var selected = Selection.instanceIDs; if (selected.Length != 1) return false; var selectedGO = EditorUtility.InstanceIDToObject(selected[0]) as GameObject; if (selectedGO != constraint.gameObject) return false; var rig = constraint.GetComponentInParent(); if (rig == null) return false; var rigBuilder = rig.GetComponentInParent(); if (rigBuilder == null) return false; var animator = rigBuilder.GetComponent(); if (animator.isHuman) return false; bool inRigBuilder = false; var layers = rigBuilder.layers; for (int i = 0; i < layers.Count; ++i) { if (layers[i].rig == rig && layers[i].active) inRigBuilder = true; } if (!inRigBuilder) return false; return constraint.IsValid(); } /// /// Bakes motion from any RigConstraints in the RigBuilder to the skeleton. /// /// The RigBuilder whose RigConstraints are to be baked. public static void TransferMotionToSkeleton(RigBuilder rigBuilder) { List layers = rigBuilder.layers; List rigs = new List(layers.Count); foreach(var layer in layers) { if (layer.rig != null && layer.active) { rigs.Add(layer.rig); } } TransferMotionToSkeleton(rigBuilder, rigs); } /// /// Bakes motion from any RigConstraints in the Rig to the skeleton. /// /// The Rig whose RigConstraints are to be baked. public static void TransferMotionToSkeleton(Rig rig) { var rigBuilder = rig.GetComponentInParent(); if (rigBuilder == null) throw new InvalidOperationException("No rigbuilder was found in the hierarchy."); TransferMotionToSkeleton(rigBuilder, new Rig[]{rig}); } private static void TransferMotionToSkeleton(RigBuilder rigBuilder, IEnumerable rigs) { var constraints = new List(); foreach(var rig in rigs) { constraints.AddRange(RigUtils.GetConstraints(rig)); } var clip = AnimationWindowUtils.activeAnimationClip; // Make sure we have a clip selected if (clip == null) { throw new InvalidOperationException( "There is no clip to work on." + " The animation window must be open with an active clip!"); } AnimationClip editableClip = clip; if (!GetEditableClip(ref editableClip)) return; AnimationClip defaultPoseClip = CreateDefaultPose(rigBuilder); Undo.RegisterCompleteObjectUndo(editableClip, kBakeToSkeletonUndoLabel); var animator = rigBuilder.GetComponent(); if (editableClip != clip) AddClipToAnimatorController(animator, editableClip); var bindingsToRemove = new HashSet(); foreach(IRigConstraint constraint in constraints) { var bakeParameters = FindBakeParameters(constraint); if (bakeParameters == null || !bakeParameters.canBakeToSkeleton) continue; // Flush out animation mode modifications AnimationMode.BeginSampling(); AnimationMode.EndSampling(); var bindings = bakeParameters.GetConstrainedCurveBindings(rigBuilder, constraint); BakeToSkeleton(rigBuilder, constraint, editableClip, defaultPoseClip, bindings, Preferences.bakeToSkeletonCurveFilterOptions); bindingsToRemove.UnionWith(bakeParameters.GetSourceCurveBindings(rigBuilder, constraint)); } // Remove weight curve & force constraint to be active if (Preferences.forceConstraintWeightOnBake) { AnimationCurve zeroWeightCurve = AnimationCurve.Constant(0f, editableClip.length, 0f); foreach(var rig in rigs) { AnimationUtility.SetEditorCurve(editableClip, GetWeightCurveBinding(rigBuilder, rig), zeroWeightCurve); } } if (Preferences.bakeToSkeletonAndRemoveCurves) RemoveCurves(editableClip, bindingsToRemove); } /// /// Bakes motion from the RigConstraint to the skeleton. /// /// Type of RigConstraint that is to be baked. /// The RigConstraint that will be baked to the skeleton. public static void TransferMotionToSkeleton(T constraint) where T : MonoBehaviour, IRigConstraint { var rigBuilder = constraint.GetComponentInParent(); if (rigBuilder == null) throw new InvalidOperationException("No rigbuilder was found in the hierarchy."); var bakeParameters = FindBakeParameters(constraint); if (bakeParameters == null) throw new InvalidOperationException(string.Format("Could not find BakeParameters class for constraint {0}.", constraint != null ? constraint.ToString() : "no-name")); if (!bakeParameters.canBakeToSkeleton) throw new InvalidOperationException("Constraint disallows transfering motion to skeleton."); var bindings = bakeParameters.GetConstrainedCurveBindings(rigBuilder, constraint); var clip = AnimationWindowUtils.activeAnimationClip; // Make sure we have a clip selected if (clip == null) { throw new InvalidOperationException( "There is no clip to work on." + " The animation window must be open with an active clip!"); } AnimationClip editableClip = clip; if (!GetEditableClip(ref editableClip)) return; AnimationClip defaultPoseClip = CreateDefaultPose(rigBuilder); Undo.RegisterCompleteObjectUndo(editableClip, kBakeToSkeletonUndoLabel); var animator = rigBuilder.GetComponent(); if (editableClip != clip) AddClipToAnimatorController(animator, editableClip); BakeToSkeleton(constraint, editableClip, defaultPoseClip, bindings, Preferences.bakeToSkeletonCurveFilterOptions); if (Preferences.forceConstraintWeightOnBake) { AnimationCurve zeroWeightCurve = AnimationCurve.Constant(0f, editableClip.length, 0f); AnimationUtility.SetEditorCurve(editableClip, GetWeightCurveBinding(rigBuilder, constraint), zeroWeightCurve); } if (Preferences.bakeToSkeletonAndRemoveCurves) RemoveCurves(editableClip, bakeParameters.GetSourceCurveBindings(rigBuilder, constraint)); } internal static void BakeToSkeleton(T constraint, AnimationClip clip, AnimationClip defaultPoseClip, IEnumerable bindings, CurveFilterOptions filterOptions) where T : MonoBehaviour, IRigConstraint { // Make sure we have a rigbuilder (which guarantees an animator). var rigBuilder = constraint.GetComponentInParent(); if (rigBuilder == null) { throw new InvalidOperationException( "No rigbuilder was found in the hierarchy. " + "A RigBuilder and Animator are required to construct valid bindings."); } BakeToSkeleton(rigBuilder, constraint, clip, defaultPoseClip, bindings, filterOptions); } private static void BakeToSkeleton(RigBuilder rigBuilder, IRigConstraint constraint, AnimationClip clip, AnimationClip defaultPoseClip, IEnumerable bindings, CurveFilterOptions filterOptions) { // Make sure the base constraint is valid if (constraint == null || !constraint.IsValid()) { throw new InvalidOperationException( string.Format("The rig constraint {0} is not a valid constraint.", constraint != null ? constraint.ToString() : "")); } var overrides = new Dictionary(); using(var graph = new EvaluationGraph(rigBuilder, clip, defaultPoseClip, overrides, constraint)) { BakeCurvesToClip(clip, bindings, rigBuilder, graph, filterOptions); } } /// /// Bakes motion from the skeleton to the constraints in the RigBuilder. /// /// The RigBuilder whose RigConstraints are to be baked. public static void TransferMotionToConstraint(RigBuilder rigBuilder) { List layers = rigBuilder.layers; List rigs = new List(layers.Count); foreach(var layer in layers) { if (layer.rig != null && layer.active) { rigs.Add(layer.rig); } } TransferMotionToConstraint(rigBuilder, rigs); } /// /// Bakes motion from the skeleton to the constraints in the Rig. /// /// The Rig whose RigConstraints are to be baked. public static void TransferMotionToConstraint(Rig rig) { var rigBuilder = rig.GetComponentInParent(); if (rigBuilder == null) throw new InvalidOperationException("No rigbuilder was found in the hierarchy."); TransferMotionToConstraint(rigBuilder, new Rig[]{rig}); } private static void TransferMotionToConstraint(RigBuilder rigBuilder, IEnumerable rigs) { var constraints = new List(); foreach(var rig in rigs) { constraints.AddRange(RigUtils.GetConstraints(rig)); } var clip = AnimationWindowUtils.activeAnimationClip; // Make sure we have a clip selected if (clip == null) { throw new InvalidOperationException( "There is no clip to work on." + " The animation window must be open with an active clip!"); } AnimationClip editableClip = clip; if (!GetEditableClip(ref editableClip)) return; AnimationClip defaultPoseClip = CreateDefaultPose(rigBuilder); Undo.RegisterCompleteObjectUndo(editableClip, kBakeToConstraintUndoLabel); var animator = rigBuilder.GetComponent(); if (editableClip != clip) AddClipToAnimatorController(animator, editableClip); var bindingsToRemove = new HashSet(); // Remove weight curve & force constraint to be active if (Preferences.forceConstraintWeightOnBake) { AnimationCurve oneWeightCurve = AnimationCurve.Constant(0f, editableClip.length, 1f); foreach(var rig in rigs) { AnimationUtility.SetEditorCurve(editableClip, GetWeightCurveBinding(rigBuilder, rig), oneWeightCurve); } } foreach(IRigConstraint constraint in constraints) { var bakeParameters = FindBakeParameters(constraint); if (bakeParameters == null || !bakeParameters.canBakeToConstraint) continue; // Flush out animation mode modifications AnimationMode.BeginSampling(); AnimationMode.EndSampling(); var bindings = bakeParameters.GetSourceCurveBindings(rigBuilder, constraint); BakeToConstraint(rigBuilder, constraint, editableClip, defaultPoseClip, bindings, Preferences.bakeToConstraintCurveFilterOptions); bindingsToRemove.UnionWith(bakeParameters.GetConstrainedCurveBindings(rigBuilder, constraint)); } if (Preferences.bakeToConstraintAndRemoveCurves) RemoveCurves(editableClip, bindingsToRemove); } /// /// Bakes motion from the skeleton to the RigConstraint /// /// Type of RigConstraint that is to be baked. /// The RigConstraint that will be baked to. public static void TransferMotionToConstraint(T constraint) where T : MonoBehaviour, IRigConstraint { var rigBuilder = constraint.GetComponentInParent(); if (rigBuilder == null) throw new InvalidOperationException("No rigbuilder was found in the hierarchy."); var bakeParameters = FindBakeParameters(constraint); if (bakeParameters == null) throw new InvalidOperationException(string.Format("Could not find BakeParameters class for constraint {0}.", constraint != null ? constraint.ToString() : "no-name")); if (!bakeParameters.canBakeToSkeleton) throw new InvalidOperationException("Constraint disallows transfering motion to constraint."); var bindings = bakeParameters.GetSourceCurveBindings(rigBuilder, constraint); var clip = AnimationWindowUtils.activeAnimationClip; // Make sure we have a clip selected if (clip == null) { throw new InvalidOperationException( "There is no clip to work on." + " The animation window must be open with an active clip!"); } AnimationClip editableClip = clip; if (!GetEditableClip(ref editableClip)) return; AnimationClip defaultPoseClip = CreateDefaultPose(rigBuilder); Undo.RegisterCompleteObjectUndo(editableClip, kBakeToConstraintUndoLabel); var animator = rigBuilder.GetComponent(); if (editableClip != clip) AddClipToAnimatorController(animator, editableClip); // Remove weight curve & force constraint to be active if (Preferences.forceConstraintWeightOnBake) { AnimationCurve oneWeightCurve = AnimationCurve.Constant(0f, editableClip.length, 1f); AnimationUtility.SetEditorCurve(editableClip, GetWeightCurveBinding(rigBuilder, constraint), oneWeightCurve); } BakeToConstraint(constraint, editableClip, defaultPoseClip, bindings, Preferences.bakeToConstraintCurveFilterOptions); if (Preferences.bakeToConstraintAndRemoveCurves) RemoveCurves(editableClip, bakeParameters.GetConstrainedCurveBindings(rigBuilder, constraint)); } internal static void BakeToConstraint(T constraint, AnimationClip clip, AnimationClip defaultPoseClip, IEnumerable bindings, CurveFilterOptions filterOptions) where T : MonoBehaviour, IRigConstraint { // Make sure we have a rigbuilder (which guarantees an animator). var rigBuilder = constraint.GetComponentInParent(); if(rigBuilder == null) { throw new InvalidOperationException( "No rigbuilder was found in the hierarchy. " + "A RigBuilder and Animator are required to construct valid bindings."); } BakeToConstraint(rigBuilder, constraint, clip, defaultPoseClip, bindings, filterOptions); } private static void BakeToConstraint(RigBuilder rigBuilder, IRigConstraint constraint, AnimationClip clip, AnimationClip defaultPoseClip, IEnumerable bindings, CurveFilterOptions filterOptions) { // Make sure the base constraint is valid if (constraint == null || !constraint.IsValid()) { throw new InvalidOperationException( string.Format("The rig constraint {0} is not a valid constraint.", constraint != null ? constraint.ToString() : "")); } // Check if the constraint is inverse solvable var inverseConstraint = FindInverseRigConstraint(constraint); if (inverseConstraint == null) { throw new InvalidOperationException( string.Format("No inverse rig constraint could be found for {0}.", constraint.ToString())); } else if (!inverseConstraint.IsValid()) { throw new InvalidOperationException( string.Format("The inverse rig constrain {1} for {0} is not a valid constraint.", constraint.ToString(), inverseConstraint.ToString())); } var overrides = new Dictionary(); overrides.Add(constraint, inverseConstraint); using(var graph = new EvaluationGraph(rigBuilder, clip, defaultPoseClip, overrides, constraint)) { BakeCurvesToClip(clip, bindings, rigBuilder, graph, filterOptions); } } private static AnimationClip CreateDefaultPose(RigBuilder rigBuilder) { if(rigBuilder == null) throw new ArgumentNullException("It is not possible to bake curves without an RigBuilder."); var defaultPoseClip = new AnimationClip() { name = "DefaultPose" }; if (!AnimationMode.InAnimationMode()) return defaultPoseClip; var bindings = new List(); var gameObjects = new Queue(); gameObjects.Enqueue(rigBuilder.gameObject); while (gameObjects.Count > 0) { var gameObject = gameObjects.Dequeue(); EditorCurveBinding[] allBindings = AnimationUtility.GetAnimatableBindings(gameObject, rigBuilder.gameObject); foreach (var binding in allBindings) { if (binding.isPPtrCurve) continue; if (binding.type == typeof(GameObject)) continue; UnityEngine.Object target = gameObject.GetComponent(binding.type); if (!AnimationMode.IsPropertyAnimated(target, binding.propertyName)) continue; bindings.Add(binding); } // Iterate over all child GOs for (int i = 0; i < gameObject.transform.childCount; i++) { Transform childTransform = gameObject.transform.GetChild(i); gameObjects.Enqueue(childTransform.gameObject); } } // Flush out animation mode modifications AnimationMode.BeginSampling(); AnimationMode.EndSampling(); foreach(var binding in bindings) { float floatValue; AnimationUtility.GetFloatValue(rigBuilder.gameObject, binding, out floatValue); var key = new Keyframe(0f, floatValue); var curve = new AnimationCurve(new Keyframe[] {key}); defaultPoseClip.SetCurve(binding.path, binding.type, binding.propertyName, curve); } return defaultPoseClip; } private static void BakeCurvesToClip(AnimationClip clip, IEnumerable bindings, RigBuilder rigBuilder, IEvaluationGraph graph, CurveFilterOptions filterOptions) { if(rigBuilder == null) throw new ArgumentNullException("It is not possible to bake curves without an RigBuilder."); if (clip == null) throw new ArgumentNullException("It is not possible to bake curves to a clip that is null."); if (!AnimationMode.InAnimationMode()) throw new ArgumentException("AnimationMode must be active during bake operation."); var animator = rigBuilder.GetComponent(); var recorder = new GameObjectRecorder(animator.gameObject); foreach (var binding in bindings) recorder.Bind(binding); var frameCount = (int)(clip.length * clip.frameRate); float dt = 1f / clip.frameRate; float time = 0f; graph?.Evaluate(0f); recorder.TakeSnapshot(0f); for (int frame = 1; frame <= frameCount; ++frame) { time = frame / clip.frameRate; graph?.Evaluate(time); recorder.TakeSnapshot(dt); } var tempClip = new AnimationClip(); recorder.SaveToClip(tempClip, clip.frameRate, filterOptions); CopyCurvesToClip(tempClip, clip); } private static void RemoveCurves(AnimationClip clip, IEnumerable bindings) { if (clip == null) throw new ArgumentNullException("The destination animation clip cannot be null."); var rotationBinding = new EditorCurveBinding(); foreach(var binding in bindings) { // Remove the correct editor curve binding for a rotation curves if (EditorCurveBindingUtils.RemapRotationBinding(clip, binding, ref rotationBinding)) AnimationUtility.SetEditorCurve(clip, rotationBinding, null); else AnimationUtility.SetEditorCurve(clip, binding, null); } } private static void CopyCurvesToClip(AnimationClip fromClip, AnimationClip toClip) { var rotationBinding = new EditorCurveBinding(); var bindings = AnimationUtility.GetCurveBindings(fromClip); foreach(var binding in bindings) { var curve = AnimationUtility.GetEditorCurve(fromClip, binding); if (EditorCurveBindingUtils.RemapRotationBinding(toClip, binding, ref rotationBinding)) AnimationUtility.SetEditorCurve(toClip, rotationBinding, curve); else AnimationUtility.SetEditorCurve(toClip, binding, curve); } } private static IRigConstraint FindInverseRigConstraint(IRigConstraint constraint) { if (constraint == null) return null; var inverseConstraintTypes = TypeCache.GetTypesWithAttribute(); foreach (var inverseConstraintType in inverseConstraintTypes) { var attribute = inverseConstraintType.GetCustomAttribute(); if (attribute.baseConstraint == constraint.GetType()) return (IRigConstraint)Activator.CreateInstance(inverseConstraintType, new object[] { constraint }); } return null; } internal static IBakeParameters FindBakeParameters(IRigConstraint constraint) { var constraintType = constraint.GetType(); var bakeParametersTypes = TypeCache.GetTypesWithAttribute(); foreach (var bakeParametersType in bakeParametersTypes) { if (!typeof(IBakeParameters).IsAssignableFrom(bakeParametersType)) continue; var attribute = bakeParametersType.GetCustomAttribute(); if (attribute.constraintType == constraintType) return (IBakeParameters)Activator.CreateInstance(bakeParametersType); } return null; } private static EditorCurveBinding GetWeightCurveBinding(RigBuilder rigBuilder, Rig rig) { var path = AnimationUtility.CalculateTransformPath(rig.transform, rigBuilder.transform); var binding = EditorCurveBinding.FloatCurve(path, typeof(Rig), ConstraintProperties.s_Weight); return binding; } private static EditorCurveBinding GetWeightCurveBinding(RigBuilder rigBuilder, T constraint) where T : MonoBehaviour, IRigConstraint { var path = AnimationUtility.CalculateTransformPath(constraint.transform, rigBuilder.transform); var binding = EditorCurveBinding.FloatCurve(path, typeof(T), ConstraintProperties.s_Weight); return binding; } private static bool GetEditableClip(ref AnimationClip clip) { if (clip == null) return false; if ((clip.hideFlags & HideFlags.NotEditable) != 0) { var path = EditorUtility.SaveFilePanelInProject( "Save new clip", clip.name + "(Clone)", "anim", string.Format("Create an editable clone of the readonly clip {0}.", clip.name)); if (path == "") return false; clip = UnityEngine.Object.Instantiate(clip); AssetDatabase.CreateAsset(clip, path); } return true; } private static void AddClipToAnimatorController(Animator animator, AnimationClip clip) { RuntimeAnimatorController runtimeController = animator.runtimeAnimatorController; AnimatorController effectiveController = runtimeController as AnimatorController; if (effectiveController == null) { AnimatorOverrideController overrideController = runtimeController as AnimatorOverrideController; if (overrideController != null) { effectiveController = overrideController.runtimeAnimatorController as AnimatorController; } } if (effectiveController != null) { string title = "Add clip to controller"; string message = String.Format("Do you want to add clip '{0}' to controller '{1}'?", clip.name, effectiveController.name); if (EditorUtility.DisplayDialog(title, message, "yes", "no", DialogOptOutDecisionType.ForThisSession, "com.unity.animation.rigging-add-clip-to-controller")) { effectiveController.AddMotion(clip); AnimationWindowUtils.activeAnimationClip = clip; AnimationWindowUtils.StartPreview(); } } } } }