You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

718 lines
34 KiB

using System.Collections.Generic;
using System.Linq;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.Timeline;
using System.Globalization;
namespace UnityEditor.Timeline
{
// Methods and data for handling recording to monobehaviours
static partial class TimelineRecording
{
internal class RecordingState : IAnimationRecordingState
{
public GameObject activeGameObject { get; set; }
public GameObject activeRootGameObject { get; set; }
public AnimationClip activeAnimationClip { get; set; }
public void SaveCurve(AnimationWindowCurve curve)
{
Undo.RegisterCompleteObjectUndo(activeAnimationClip, "Edit Curve");
AnimationWindowUtility.SaveCurve(activeAnimationClip, curve);
}
public void AddPropertyModification(EditorCurveBinding binding, PropertyModification propertyModification, bool keepPrefabOverride)
{
AnimationMode.AddPropertyModification(binding, propertyModification, keepPrefabOverride);
}
public bool addZeroFrame
{
get { return false; }
}
public int currentFrame { get; set; }
public bool DiscardModification(PropertyModification modification)
{
return false;
}
}
static readonly RecordingState s_RecordState = new RecordingState();
static readonly AnimationTrackRecorder s_TrackRecorder = new AnimationTrackRecorder();
static readonly List<UndoPropertyModification> s_UnprocessedMods = new List<UndoPropertyModification>();
static readonly List<UndoPropertyModification> s_ModsToProcess = new List<UndoPropertyModification>();
static AnimationTrack s_LastTrackWarning;
public const string kLocalPosition = "m_LocalPosition";
public const string kLocalRotation = "m_LocalRotation";
public const string kLocalEulerHint = "m_LocalEulerAnglesHint";
const string kRotationWarning = "You are recording with an initial rotation offset. This may result in a misrepresentation of euler angles. When recording transform properties, it is recommended to reset rotation prior to recording";
public static bool IsRecordingAnimationTrack { get; private set; }
internal static UndoPropertyModification[] ProcessMonoBehaviourModification(UndoPropertyModification[] modifications, WindowState state)
{
if (!state.recording)
return modifications;
if (state == null || state.editSequence.director == null)
return modifications;
s_UnprocessedMods.Clear();
s_TrackRecorder.PrepareForRecord(state);
s_ModsToProcess.Clear();
s_ModsToProcess.AddRange(modifications.Reverse());
while (s_ModsToProcess.Count > 0)
{
var modification = s_ModsToProcess[s_ModsToProcess.Count - 1];
s_ModsToProcess.RemoveAt(s_ModsToProcess.Count - 1);
// grab the clip we need to apply to
var modifiedGO = GetGameObjectFromModification(modification);
var track = GetTrackForGameObject(modifiedGO, state);
if (track != null)
{
IsRecordingAnimationTrack = true;
double startTime = 0;
var clip = s_TrackRecorder.PrepareTrack(track, state, modifiedGO, out startTime);
if (clip == null)
{
s_ModsToProcess.Reverse();
return s_ModsToProcess.ToArray();
}
s_RecordState.activeAnimationClip = clip;
s_RecordState.activeRootGameObject = state.GetSceneReference(track);
s_RecordState.activeGameObject = modifiedGO;
s_RecordState.currentFrame = Mathf.RoundToInt((float)startTime);
EditorUtility.SetDirty(clip);
var toProcess = GatherRelatedModifications(modification, s_ModsToProcess);
var animator = s_RecordState.activeRootGameObject.GetComponent<Animator>();
var animTrack = track as AnimationTrack;
// update preview mode before recording so the correct values get placed (in case we modify offsets)
// Case 900624
UpdatePreviewMode(toProcess, modifiedGO);
// if this is the first position/rotation recording, copy the current position / rotation to the track offset
AddTrackOffset(animTrack, toProcess, clip, animator);
// same for clip mod clips being created
AddClipOffset(animTrack, toProcess, s_TrackRecorder.recordClip, animator);
// Check if we need to handle position/rotation offsets
var handleOffsets = animator != null && modification.currentValue != null &&
modification.currentValue.target == s_RecordState.activeRootGameObject.transform &&
HasOffsets(animTrack, s_TrackRecorder.recordClip);
if (handleOffsets)
{
toProcess = HandleEulerModifications(animTrack, s_TrackRecorder.recordClip, clip, s_RecordState.currentFrame * clip.frameRate, toProcess);
RemoveOffsets(modification, animTrack, s_TrackRecorder.recordClip, toProcess);
}
var remaining = AnimationRecording.Process(s_RecordState, toProcess);
if (remaining != null && remaining.Length != 0)
{
s_UnprocessedMods.AddRange(remaining);
}
if (handleOffsets)
{
ReapplyOffsets(modification, animTrack, s_TrackRecorder.recordClip, toProcess);
}
s_TrackRecorder.FinializeTrack(track, state);
IsRecordingAnimationTrack = false;
}
else
{
s_UnprocessedMods.Add(modification);
}
}
s_TrackRecorder.FinalizeRecording(state);
return s_UnprocessedMods.ToArray();
}
internal static bool IsPosition(UndoPropertyModification modification)
{
if (modification.currentValue != null)
return modification.currentValue.propertyPath.StartsWith(kLocalPosition);
else if (modification.previousValue != null)
return modification.previousValue.propertyPath.StartsWith(kLocalPosition);
return false;
}
internal static bool IsRotation(UndoPropertyModification modification)
{
if (modification.currentValue != null)
return modification.currentValue.propertyPath.StartsWith(kLocalRotation) ||
modification.currentValue.propertyPath.StartsWith(kLocalEulerHint);
if (modification.previousValue != null)
return modification.previousValue.propertyPath.StartsWith(kLocalRotation) ||
modification.previousValue.propertyPath.StartsWith(kLocalEulerHint);
return false;
}
// Test if this modification position or rotation
internal static bool IsPositionOrRotation(UndoPropertyModification modification)
{
return IsPosition(modification) || IsRotation(modification);
}
internal static void UpdatePreviewMode(UndoPropertyModification[] mods, GameObject go)
{
if (mods.Any(x => IsPositionOrRotation(x) && IsRootModification(x)))
{
bool hasPosition = false;
bool hasRotation = false;
foreach (var mod in mods)
{
EditorCurveBinding binding = new EditorCurveBinding();
if (AnimationUtility.PropertyModificationToEditorCurveBinding(mod.previousValue, go, out binding) != null)
{
hasPosition |= IsPosition(mod);
hasRotation |= IsRotation(mod);
AnimationMode.AddPropertyModification(binding, mod.previousValue, true);
}
}
// case 931859 - if we are only changing one field, all fields must be registered before
// any recording modifications
var driver = WindowState.previewDriver;
if (driver != null && AnimationMode.InAnimationMode(driver))
{
if (hasPosition)
{
DrivenPropertyManager.RegisterProperty(driver, go.transform, kLocalPosition + ".x");
DrivenPropertyManager.RegisterProperty(driver, go.transform, kLocalPosition + ".y");
DrivenPropertyManager.RegisterProperty(driver, go.transform, kLocalPosition + ".z");
}
else if (hasRotation)
{
DrivenPropertyManager.RegisterProperty(driver, go.transform, kLocalRotation + ".x");
DrivenPropertyManager.RegisterProperty(driver, go.transform, kLocalRotation + ".y");
DrivenPropertyManager.RegisterProperty(driver, go.transform, kLocalRotation + ".z");
DrivenPropertyManager.RegisterProperty(driver, go.transform, kLocalRotation + ".w");
}
}
}
}
internal static bool IsRootModification(UndoPropertyModification modification)
{
string path = string.Empty;
if (modification.currentValue != null)
path = modification.currentValue.propertyPath;
else if (modification.previousValue != null)
path = modification.previousValue.propertyPath;
return !path.Contains('/') && !path.Contains('\\');
}
// test if the clip has any position or rotation bindings
internal static bool ClipHasPositionOrRotation(AnimationClip clip)
{
if (clip == null || clip.empty)
return false;
var info = AnimationClipCurveCache.Instance.GetCurveInfo(clip);
for (var i = 0; i < info.bindings.Length; i++)
{
bool isPositionOrRotation =
info.bindings[i].type != null &&
typeof(Transform).IsAssignableFrom(info.bindings[i].type) &&
(
info.bindings[i].propertyName.StartsWith(kLocalPosition) ||
info.bindings[i].propertyName.StartsWith(kLocalRotation) ||
info.bindings[i].propertyName.StartsWith("localEuler")
);
if (isPositionOrRotation)
return true;
}
return false;
}
internal static TimelineAnimationUtilities.RigidTransform ComputeInitialClipOffsets(AnimationTrack track, UndoPropertyModification[] mods, Animator animator)
{
// take into account the track transform
var target = GetInitialTransform(mods, animator);
var trackToClip = TimelineAnimationUtilities.RigidTransform.identity;
if (track.trackOffset == TrackOffset.ApplyTransformOffsets)
trackToClip = TimelineAnimationUtilities.RigidTransform.Compose(track.position, track.rotation);
else if (track.trackOffset == TrackOffset.ApplySceneOffsets)
trackToClip = TimelineAnimationUtilities.RigidTransform.Compose(track.sceneOffsetPosition, Quaternion.Euler(track.sceneOffsetRotation));
target = TimelineAnimationUtilities.RigidTransform.Mul(TimelineAnimationUtilities.RigidTransform.Inverse(trackToClip), target);
// set the previous position in case the animation system adds a default key
SetPreviousPositionAndRotation(mods, animator, trackToClip.position, trackToClip.rotation);
return target;
}
internal static TimelineAnimationUtilities.RigidTransform GetInitialTransform(UndoPropertyModification[] mods, Animator animator)
{
var pos = Vector3.zero;
var rot = Quaternion.identity;
// if we are operating on the root, grab the transform from the undo
if (mods[0].previousValue.target == animator.transform)
{
GetPreviousPositionAndRotation(mods, ref pos, ref rot);
}
// otherwise we need to grab it from the root object, which is the one with the actual animator
else
{
pos = animator.transform.localPosition;
rot = animator.transform.localRotation;
}
// take into account the track transform
return TimelineAnimationUtilities.RigidTransform.Compose(pos, rot);
}
internal static void SetPreviousPositionAndRotation(UndoPropertyModification[] mods, Animator animator, Vector3 pos, Quaternion rot)
{
if (mods[0].previousValue.target == animator.transform)
{
SetPreviousPositionAndRotation(mods, pos, rot);
}
}
// If we are adding to an infinite clip, strip the objects position and rotation and set it as the clip offset
internal static void AddTrackOffset(AnimationTrack track, UndoPropertyModification[] mods, AnimationClip clip, Animator animator)
{
var copyTrackOffset = !track.inClipMode &&
!ClipHasPositionOrRotation(clip) &&
mods.Any(x => IsPositionOrRotation(x) && IsRootModification(x)) &&
animator != null;
if (copyTrackOffset)
{
// in scene offset mode, makes sure we have the correct initial transform set
if (track.trackOffset == TrackOffset.ApplySceneOffsets)
{
var rigidTransform = GetInitialTransform(mods, animator);
track.sceneOffsetPosition = rigidTransform.position;
track.sceneOffsetRotation = rigidTransform.rotation.eulerAngles;
SetPreviousPositionAndRotation(mods, animator, rigidTransform.position, rigidTransform.rotation);
}
else
{
var rigidTransform = ComputeInitialClipOffsets(track, mods, animator);
track.infiniteClipOffsetPosition = rigidTransform.position;
track.infiniteClipOffsetEulerAngles = rigidTransform.rotation.eulerAngles;
}
}
}
internal static void AddClipOffset(AnimationTrack track, UndoPropertyModification[] mods, TimelineClip clip, Animator animator)
{
if (clip == null || clip.asset == null)
return;
var clipAsset = clip.asset as AnimationPlayableAsset;
var copyClipOffset = track.inClipMode &&
clipAsset != null && !ClipHasPositionOrRotation(clipAsset.clip) &&
mods.Any(x => IsPositionOrRotation(x) && IsRootModification(x)) &&
animator != null;
if (copyClipOffset)
{
var rigidTransform = ComputeInitialClipOffsets(track, mods, animator);
clipAsset.position = rigidTransform.position;
clipAsset.rotation = rigidTransform.rotation;
}
}
internal static TimelineAnimationUtilities.RigidTransform GetLocalToTrack(AnimationTrack track, TimelineClip clip)
{
if (track == null)
return TimelineAnimationUtilities.RigidTransform.Compose(Vector3.zero, Quaternion.identity);
var trackPos = track.position;
var trackRot = track.rotation;
if (track.trackOffset == TrackOffset.ApplySceneOffsets)
{
trackPos = track.sceneOffsetPosition;
trackRot = Quaternion.Euler(track.sceneOffsetRotation);
}
var clipWrapper = clip == null ? null : clip.asset as AnimationPlayableAsset;
var clipTransform = TimelineAnimationUtilities.RigidTransform.Compose(Vector3.zero, Quaternion.identity);
if (clipWrapper != null)
{
clipTransform = TimelineAnimationUtilities.RigidTransform.Compose(clipWrapper.position, clipWrapper.rotation);
}
else
{
clipTransform = TimelineAnimationUtilities.RigidTransform.Compose(track.infiniteClipOffsetPosition, track.infiniteClipOffsetRotation);
}
var trackTransform = TimelineAnimationUtilities.RigidTransform.Compose(trackPos, trackRot);
return TimelineAnimationUtilities.RigidTransform.Mul(trackTransform, clipTransform);
}
// Checks whether there are any offsets applied to a clip
internal static bool HasOffsets(AnimationTrack track, TimelineClip clip)
{
if (track == null)
return false;
bool hasClipOffsets = false;
bool hasTrackOffsets = false;
var clipWrapper = clip == null ? null : clip.asset as AnimationPlayableAsset;
if (clipWrapper != null)
hasClipOffsets |= clipWrapper.position != Vector3.zero || clipWrapper.rotation != Quaternion.identity;
if (track.trackOffset == TrackOffset.ApplySceneOffsets)
{
hasTrackOffsets = track.sceneOffsetPosition != Vector3.zero || track.sceneOffsetRotation != Vector3.zero;
}
else
{
hasTrackOffsets = (track.position != Vector3.zero || track.rotation != Quaternion.identity);
if (!track.inClipMode)
hasClipOffsets |= track.infiniteClipOffsetPosition != Vector3.zero || track.infiniteClipOffsetRotation != Quaternion.identity;
}
return hasTrackOffsets || hasClipOffsets;
}
internal static void RemoveOffsets(UndoPropertyModification modification, AnimationTrack track, TimelineClip clip, UndoPropertyModification[] mods)
{
if (IsPositionOrRotation(modification))
{
var modifiedGO = GetGameObjectFromModification(modification);
var target = TimelineAnimationUtilities.RigidTransform.Compose(modifiedGO.transform.localPosition, modifiedGO.transform.localRotation);
var localToTrack = GetLocalToTrack(track, clip);
var trackToLocal = TimelineAnimationUtilities.RigidTransform.Inverse(localToTrack);
var localSpace = TimelineAnimationUtilities.RigidTransform.Mul(trackToLocal, target);
// Update the undo call values
var prevPos = modifiedGO.transform.localPosition;
var prevRot = modifiedGO.transform.localRotation;
GetPreviousPositionAndRotation(mods, ref prevPos, ref prevRot);
var previousRigidTransform = TimelineAnimationUtilities.RigidTransform.Mul(trackToLocal, TimelineAnimationUtilities.RigidTransform.Compose(prevPos, prevRot));
SetPreviousPositionAndRotation(mods, previousRigidTransform.position, previousRigidTransform.rotation);
var currentPos = modifiedGO.transform.localPosition;
var currentRot = modifiedGO.transform.localRotation;
GetCurrentPositionAndRotation(mods, ref currentPos, ref currentRot);
var currentRigidTransform = TimelineAnimationUtilities.RigidTransform.Mul(trackToLocal, TimelineAnimationUtilities.RigidTransform.Compose(currentPos, currentRot));
SetCurrentPositionAndRotation(mods, currentRigidTransform.position, currentRigidTransform.rotation);
modifiedGO.transform.localPosition = localSpace.position;
modifiedGO.transform.localRotation = localSpace.rotation;
}
}
internal static void ReapplyOffsets(UndoPropertyModification modification, AnimationTrack track, TimelineClip clip, UndoPropertyModification[] mods)
{
if (IsPositionOrRotation(modification))
{
var modifiedGO = GetGameObjectFromModification(modification);
var target = TimelineAnimationUtilities.RigidTransform.Compose(modifiedGO.transform.localPosition, modifiedGO.transform.localRotation);
var localToTrack = GetLocalToTrack(track, clip);
var trackSpace = TimelineAnimationUtilities.RigidTransform.Mul(localToTrack, target);
// Update the undo call values
var prevPos = modifiedGO.transform.localPosition;
var prevRot = modifiedGO.transform.localRotation;
GetPreviousPositionAndRotation(mods, ref prevPos, ref prevRot);
var previousRigidTransform = TimelineAnimationUtilities.RigidTransform.Mul(localToTrack, TimelineAnimationUtilities.RigidTransform.Compose(prevPos, prevRot));
SetPreviousPositionAndRotation(mods, previousRigidTransform.position, previousRigidTransform.rotation);
var currentPos = modifiedGO.transform.localPosition;
var currentRot = modifiedGO.transform.localRotation;
GetCurrentPositionAndRotation(mods, ref currentPos, ref currentRot);
var currentRigidTransform = TimelineAnimationUtilities.RigidTransform.Mul(localToTrack, TimelineAnimationUtilities.RigidTransform.Compose(currentPos, currentRot));
SetCurrentPositionAndRotation(mods, currentRigidTransform.position, currentRigidTransform.rotation);
modifiedGO.transform.localPosition = trackSpace.position;
modifiedGO.transform.localRotation = trackSpace.rotation;
}
}
// This will gather the modifications that modify the same property on the same object (rgba of a color, xyzw of a vector)
// Note: This will modify the list, removing any elements that match
static UndoPropertyModification[] GatherRelatedModifications(UndoPropertyModification toMatch, List<UndoPropertyModification> list)
{
var matching = new List<UndoPropertyModification> {toMatch};
for (var i = list.Count - 1; i >= 0; i--)
{
var undo = list[i];
if (undo.previousValue.target == toMatch.previousValue.target &&
DoesPropertyPathMatch(undo.previousValue.propertyPath, toMatch.previousValue.propertyPath))
{
matching.Add(undo);
list.RemoveAt(i);
}
}
return matching.ToArray();
}
// Grab the game object out of the modification object
static GameObject GetGameObjectFromModification(UndoPropertyModification mod)
{
// grab the GO this is modifying
GameObject modifiedGO = null;
if (mod.previousValue.target is GameObject)
modifiedGO = mod.previousValue.target as GameObject;
else if (mod.previousValue.target is Component)
modifiedGO = (mod.previousValue.target as Component).gameObject;
return modifiedGO;
}
// returns the level of the child in the hierarchy relative to the parent,
// or -1 if the child is not the parent or a descendent of it
static int GetChildLevel(GameObject parent, GameObject child)
{
var level = 0;
while (child != null)
{
if (parent == child)
break;
if (child.transform.parent == null)
return -1;
child = child.transform.parent.gameObject;
level++;
}
if (child != null)
return level;
return -1;
}
static bool IsRotationCurve(string propertyName)
{
string groupName = AnimationWindowUtility.GetPropertyGroupName(propertyName);
return groupName == kLocalRotation || groupName == kLocalEulerHint;
}
public static bool DoesPropertyPathMatch(string a, string b)
{
return AnimationWindowUtility.GetPropertyGroupName(a).Equals(AnimationWindowUtility.GetPropertyGroupName(b)) || IsRotationCurve(a) && IsRotationCurve(b);
}
internal static void GetPreviousPositionAndRotation(UndoPropertyModification[] mods, ref Vector3 position, ref Quaternion rotation)
{
var t = mods[0].previousValue.target as Transform;
if (t == null)
t = (Transform)mods[0].currentValue.target;
position = t.localPosition;
rotation = t.localRotation;
foreach (var mod in mods)
{
switch (mod.previousValue.propertyPath)
{
case kLocalPosition + ".x":
position.x = ParseFloat(mod.previousValue.value, position.x);
break;
case kLocalPosition + ".y":
position.y = ParseFloat(mod.previousValue.value, position.y);
break;
case kLocalPosition + ".z":
position.z = ParseFloat(mod.previousValue.value, position.z);
break;
case kLocalRotation + ".x":
rotation.x = ParseFloat(mod.previousValue.value, rotation.x);
break;
case kLocalRotation + ".y":
rotation.y = ParseFloat(mod.previousValue.value, rotation.y);
break;
case kLocalRotation + ".z":
rotation.z = ParseFloat(mod.previousValue.value, rotation.z);
break;
case kLocalRotation + ".w":
rotation.w = ParseFloat(mod.previousValue.value, rotation.w);
break;
}
}
}
internal static void GetCurrentPositionAndRotation(UndoPropertyModification[] mods, ref Vector3 position, ref Quaternion rotation)
{
var t = (Transform)mods[0].currentValue.target;
position = t.localPosition;
rotation = t.localRotation;
foreach (var mod in mods)
{
switch (mod.currentValue.propertyPath)
{
case kLocalPosition + ".x":
position.x = ParseFloat(mod.currentValue.value, position.x);
break;
case kLocalPosition + ".y":
position.y = ParseFloat(mod.currentValue.value, position.y);
break;
case kLocalPosition + ".z":
position.z = ParseFloat(mod.currentValue.value, position.z);
break;
case kLocalRotation + ".x":
rotation.x = ParseFloat(mod.currentValue.value, rotation.x);
break;
case kLocalRotation + ".y":
rotation.y = ParseFloat(mod.currentValue.value, rotation.y);
break;
case kLocalRotation + ".z":
rotation.z = ParseFloat(mod.currentValue.value, rotation.z);
break;
case kLocalRotation + ".w":
rotation.w = ParseFloat(mod.currentValue.value, rotation.w);
break;
}
}
}
// when making the previous position and rotation
internal static void SetPreviousPositionAndRotation(UndoPropertyModification[] mods, Vector3 pos, Quaternion rot)
{
foreach (var mod in mods)
{
switch (mod.previousValue.propertyPath)
{
case kLocalPosition + ".x":
mod.previousValue.value = pos.x.ToString(EditorGUI.kFloatFieldFormatString);
break;
case kLocalPosition + ".y":
mod.previousValue.value = pos.y.ToString(EditorGUI.kFloatFieldFormatString);
break;
case kLocalPosition + ".z":
mod.previousValue.value = pos.z.ToString(EditorGUI.kFloatFieldFormatString);
break;
case kLocalRotation + ".x":
mod.previousValue.value = rot.x.ToString(EditorGUI.kFloatFieldFormatString);
break;
case kLocalRotation + ".y":
mod.previousValue.value = rot.y.ToString(EditorGUI.kFloatFieldFormatString);
break;
case kLocalRotation + ".z":
mod.previousValue.value = rot.z.ToString(EditorGUI.kFloatFieldFormatString);
break;
case kLocalRotation + ".w":
mod.previousValue.value = rot.w.ToString(EditorGUI.kFloatFieldFormatString);
break;
}
}
}
internal static void SetCurrentPositionAndRotation(UndoPropertyModification[] mods, Vector3 pos, Quaternion rot)
{
foreach (var mod in mods)
{
switch (mod.previousValue.propertyPath)
{
case kLocalPosition + ".x":
mod.currentValue.value = pos.x.ToString(EditorGUI.kFloatFieldFormatString);
break;
case kLocalPosition + ".y":
mod.currentValue.value = pos.y.ToString(EditorGUI.kFloatFieldFormatString);
break;
case kLocalPosition + ".z":
mod.currentValue.value = pos.z.ToString(EditorGUI.kFloatFieldFormatString);
break;
case kLocalRotation + ".x":
mod.currentValue.value = rot.x.ToString(EditorGUI.kFloatFieldFormatString);
break;
case kLocalRotation + ".y":
mod.currentValue.value = rot.y.ToString(EditorGUI.kFloatFieldFormatString);
break;
case kLocalRotation + ".z":
mod.currentValue.value = rot.z.ToString(EditorGUI.kFloatFieldFormatString);
break;
case kLocalRotation + ".w":
mod.currentValue.value = rot.w.ToString(EditorGUI.kFloatFieldFormatString);
break;
}
}
}
internal static float ParseFloat(string str, float defaultVal)
{
float temp = 0.0f;
if (float.TryParse(str, NumberStyles.Float, CultureInfo.InvariantCulture.NumberFormat, out temp))
return temp;
return defaultVal;
}
internal static UndoPropertyModification[] HandleEulerModifications(AnimationTrack track, TimelineClip clip, AnimationClip animClip, float time, UndoPropertyModification[] mods)
{
if (mods.Any(x => x.currentValue.propertyPath.StartsWith(kLocalEulerHint) || x.currentValue.propertyPath.StartsWith(kLocalRotation)))
{
// if there is a rotational offsets, we need to strip the euler hints, since they are used by the animation recording system
// over the quaternion.
var localToTrack = GetLocalToTrack(track, clip);
if (localToTrack.rotation != Quaternion.identity)
{
if (s_LastTrackWarning != track)
{
s_LastTrackWarning = track;
Debug.LogWarning(kRotationWarning);
}
Transform transform = mods[0].currentValue.target as Transform;
if (transform != null)
{
var trackToLocal = TimelineAnimationUtilities.RigidTransform.Inverse(localToTrack);
// since the euler angles are going to be transformed, we do a best guess at a euler that gives the shortest path
var quatMods = mods.Where(x => !x.currentValue.propertyPath.StartsWith(kLocalEulerHint));
var eulerMods = FindBestEulerHint(trackToLocal.rotation * transform.localRotation, animClip, time, transform);
return quatMods.Union(eulerMods).ToArray();
}
return mods.Where(x => !x.currentValue.propertyPath.StartsWith(kLocalEulerHint)).ToArray();
}
}
return mods;
}
internal static IEnumerable<UndoPropertyModification> FindBestEulerHint(Quaternion rotation, AnimationClip clip, float time, Transform transform)
{
Vector3 euler = rotation.eulerAngles;
var xCurve = AnimationUtility.GetEditorCurve(clip, EditorCurveBinding.FloatCurve(string.Empty, typeof(Transform), "localEulerAnglesRaw.x"));
var yCurve = AnimationUtility.GetEditorCurve(clip, EditorCurveBinding.FloatCurve(string.Empty, typeof(Transform), "localEulerAnglesRaw.y"));
var zCurve = AnimationUtility.GetEditorCurve(clip, EditorCurveBinding.FloatCurve(string.Empty, typeof(Transform), "localEulerAnglesRaw.z"));
if (xCurve != null)
euler.x = xCurve.Evaluate(time);
if (yCurve != null)
euler.y = yCurve.Evaluate(time);
if (zCurve != null)
euler.z = zCurve.Evaluate(time);
euler = QuaternionCurveTangentCalculation.GetEulerFromQuaternion(rotation, euler);
return new[]
{
PropertyModificationToUndoPropertyModification(new PropertyModification {target = transform, propertyPath = kLocalEulerHint + ".x", value = euler.x.ToString() }),
PropertyModificationToUndoPropertyModification(new PropertyModification {target = transform, propertyPath = kLocalEulerHint + ".y", value = euler.y.ToString() }),
PropertyModificationToUndoPropertyModification(new PropertyModification {target = transform, propertyPath = kLocalEulerHint + ".z", value = euler.z.ToString() })
};
}
}
}