Initial Commit

This commit is contained in:
Sebastian Cabrera 2021-08-02 05:44:37 -04:00
parent 53eb92e9af
commit 270ab7d11f
15341 changed files with 700234 additions and 0 deletions

View file

@ -0,0 +1,306 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class AnimationTrackRecorder
{
public static readonly string kRecordClipDefaultName = L10n.Tr("Recorded");
AnimationClip m_TargetClip;
int m_CurveCount = 0;
double m_ClipTime;
bool m_needRebuildRects;
bool m_TrackHasPreviewComponents;
public TimelineClip recordClip { get; private set; }
public void PrepareForRecord(WindowState state)
{
m_CurveCount = 0;
m_TargetClip = null;
m_TrackHasPreviewComponents = false;
}
public AnimationClip PrepareTrack(TrackAsset track, WindowState state, GameObject gameObject, out double startTime)
{
AnimationClip animationClip = null;
// if we are not in clip mode, we simply use the track clip
var animationTrack = (AnimationTrack)track;
// ignore recording if we are in Legacy auto mode
startTime = -1;
var parentTrack = TimelineUtility.GetSceneReferenceTrack(track) as AnimationTrack;
if (parentTrack != null && parentTrack.trackOffset == TrackOffset.Auto)
return null;
if (!animationTrack.inClipMode)
{
var trackClip = animationTrack.GetOrCreateClip();
startTime = trackClip.frameRate * state.editSequence.time;
// Make the first key be at time 0 of the clip
if (trackClip.empty)
{
animationTrack.infiniteClipTimeOffset = 0; // state.time;
animationTrack.infiniteClipPreExtrapolation = TimelineClip.ClipExtrapolation.Hold;
animationTrack.infiniteClipPostExtrapolation = TimelineClip.ClipExtrapolation.Hold;
}
animationClip = trackClip;
}
else
{
TimelineClip activeClip = null;
// if it fails, but returns no clip, we can add one.
if (!track.FindRecordingClipAtTime(state.editSequence.time, out activeClip) && activeClip != null)
{
return null;
}
if (activeClip == null)
{
activeClip = AddRecordableClip(track, state, state.editSequence.time);
}
var clip = activeClip.animationClip;
// flags this as the clip being recorded for the track
var clipTime = state.editSequence.time - activeClip.start;
// if we are in the past
if (clipTime < 0)
{
Undo.RegisterCompleteObjectUndo(clip, L10n.Tr("Record Key"));
UndoExtensions.RegisterTrack(track, L10n.Tr("Prepend Key"));
ShiftAnimationClip(clip, (float)-clipTime);
activeClip.start = state.editSequence.time;
activeClip.duration += -clipTime;
clipTime = 0;
}
m_ClipTime = clipTime;
recordClip = activeClip;
startTime = recordClip.ToLocalTimeUnbound(state.editSequence.time) * clip.frameRate;
m_needRebuildRects = clip.empty;
animationClip = clip;
}
m_TargetClip = animationClip;
m_CurveCount = GetCurveCount(animationClip);
m_TrackHasPreviewComponents = animationTrack.hasPreviewComponents;
return animationClip;
}
static int GetCurveCount(AnimationClip animationClip)
{
int count = 0;
if (animationClip != null)
{
var clipCache = AnimationClipCurveCache.Instance.GetCurveInfo(animationClip);
count = clipCache.curves.Length + clipCache.objectCurves.Count;
}
return count;
}
public void FinializeTrack(TrackAsset track, WindowState state)
{
// make sure we dirty the clip if we are in non clip mode
var animTrack = track as AnimationTrack;
if (!animTrack.inClipMode)
{
EditorUtility.SetDirty(animTrack.GetOrCreateClip());
}
// in clip mode we need to do some extra work
if (recordClip != null)
{
// stretch the clip out to meet the new recording time
if (m_ClipTime > recordClip.duration)
{
UndoExtensions.RegisterTrack(track, L10n.Tr("Add Key"));
recordClip.duration = m_ClipTime;
}
track.CalculateExtrapolationTimes();
}
recordClip = null;
m_ClipTime = 0;
if (m_needRebuildRects)
{
state.CalculateRowRects();
m_needRebuildRects = false;
}
}
public void FinalizeRecording(WindowState state)
{
// rebuild the graph if we add/remove a clip. Rebuild the graph with an evaluation immediately
// so previews and scene position is maintained.
if (m_CurveCount != GetCurveCount(m_TargetClip))
{
state.rebuildGraph = true;
state.GetWindow().RebuildGraphIfNecessary(true);
}
else if (m_TrackHasPreviewComponents)
{
// Track with preview components potentially has modifications impacting other properties that need
// to be refreshed before inspector or scene view to not interfere with manipulation.
state.EvaluateImmediate();
}
}
// For a given track asset get a unique clip name
public static string GetUniqueRecordedClipName(Object owner, string name)
{
// first attempt -- uniquely named in file
var path = AssetDatabase.GetAssetPath(owner);
if (!string.IsNullOrEmpty(path))
{
var names = AssetDatabase.LoadAllAssetsAtPath(path).Where(x => x != null).Select(x => x.name);
return ObjectNames.GetUniqueName(names.ToArray(), name);
}
TrackAsset asset = owner as TrackAsset;
if (asset == null || asset.clips.Length == 0)
return name;
// final attempt - uniquely named in track
return ObjectNames.GetUniqueName(asset.clips.Select(x => x.displayName).ToArray(), name);
}
// Given an appropriate parent track, create a recordable clip
public static TimelineClip AddRecordableClip(TrackAsset parentTrack, WindowState state, double atTime)
{
var sequenceAsset = state.editSequence.asset;
if (sequenceAsset == null)
{
Debug.LogError("Parent Track needs to be bound to an asset to add a recordable");
return null;
}
var animTrack = parentTrack as AnimationTrack;
if (animTrack == null)
{
Debug.LogError("Recordable clips are only valid on Animation Tracks");
return null;
}
var newClip = animTrack.CreateRecordableClip(GetUniqueRecordedClipName(parentTrack, kRecordClipDefaultName));
if (newClip == null)
{
Debug.LogError("Could not create a recordable clip");
return null;
}
newClip.mixInCurve = AnimationCurve.EaseInOut(0, 0, 1, 1);
newClip.mixOutCurve = AnimationCurve.EaseInOut(0, 1, 1, 0);
newClip.preExtrapolationMode = TimelineClip.ClipExtrapolation.Hold;
newClip.postExtrapolationMode = TimelineClip.ClipExtrapolation.Hold;
double startTime = 0;
double endTime = 0;
GetAddedRecordingClipRange(animTrack, state, atTime, out startTime, out endTime);
newClip.start = startTime;
newClip.duration = endTime - startTime;
state.Refresh();
return newClip;
}
// get the start and end times of what an added recording clip at a given time would be
internal static void GetAddedRecordingClipRange(TrackAsset track, WindowState state, double atTime, out double start, out double end)
{
// size to make the clip in pixels. Reasonably big so that both handles are easily manipulated,
// and the full title is normally visible
double defaultDuration = state.PixelDeltaToDeltaTime(100);
start = atTime;
end = atTime + defaultDuration;
double gapStart = 0;
double gapEnd = 0;
// no gap, pick are reasonable amount
if (!track.GetGapAtTime(atTime, out gapStart, out gapEnd))
{
start = atTime;
return;
}
if (!double.IsInfinity(gapEnd))
end = gapEnd;
start = TimeReferenceUtility.SnapToFrameIfRequired(start);
end = TimeReferenceUtility.SnapToFrameIfRequired(end);
}
// Given a clip, shifts the keys in that clip by the given amount.
internal static void ShiftAnimationClip(AnimationClip clip, float amount)
{
if (clip == null)
return;
var curveBindings = AnimationUtility.GetCurveBindings(clip);
var objectCurveBindings = AnimationUtility.GetObjectReferenceCurveBindings(clip);
foreach (var binding in curveBindings)
{
AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, binding);
curve.keys = ShiftKeys(curve.keys, amount);
AnimationUtility.SetEditorCurve(clip, binding, curve);
}
foreach (var binding in objectCurveBindings)
{
ObjectReferenceKeyframe[] keyframes = AnimationUtility.GetObjectReferenceCurve(clip, binding);
keyframes = ShiftObjectKeys(keyframes, amount);
AnimationUtility.SetObjectReferenceCurve(clip, binding, keyframes);
}
EditorUtility.SetDirty(clip);
}
// shift all the keys over by the given time, stretching the time 0 key
static Keyframe[] ShiftKeys(Keyframe[] keys, float time)
{
if (keys == null || keys.Length == 0 || time == 0)
return keys;
for (int i = 0; i < keys.Length; i++)
{
keys[i].time += time;
}
return keys;
}
// Shift object keys over by the appropriate amount
static ObjectReferenceKeyframe[] ShiftObjectKeys(ObjectReferenceKeyframe[] keys, float time)
{
if (keys == null || keys.Length == 0 || time == 0)
return keys;
for (int i = 0; i < keys.Length; i++)
{
keys[i].time += time;
}
return keys;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a0a02e768c802b641b6793fa864f1c2c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,603 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using UnityEngine;
using UnityEngine.Timeline;
using UnityEngine.Playables;
namespace UnityEditor.Timeline
{
// Handles Undo animated properties on Monobehaviours to create track clips
static partial class TimelineRecording
{
static readonly List<PropertyModification> s_TempPropertyModifications = new List<PropertyModification>(6);
internal static UndoPropertyModification[] ProcessUndoModification(UndoPropertyModification[] modifications, WindowState state)
{
if (HasAnyPlayableAssetModifications(modifications))
return ProcessPlayableAssetModification(modifications, state, false);
return ProcessMonoBehaviourModification(modifications, state);
}
static UnityEngine.Object GetTarget(UndoPropertyModification undo)
{
if (undo.currentValue != null)
return undo.currentValue.target;
if (undo.previousValue != null)
return undo.previousValue.target;
return null;
}
// Gets the appropriate track for a given game object
static TrackAsset GetTrackForGameObject(GameObject gameObject, WindowState state)
{
if (gameObject == null)
return null;
var director = state.editSequence.director;
if (director == null)
return null;
var level = int.MaxValue;
TrackAsset result = null;
// search the output tracks
var outputTracks = state.editSequence.asset.flattenedTracks;
foreach (var track in outputTracks)
{
if (track.GetType() != typeof(AnimationTrack))
continue;
if (!state.IsTrackRecordable(track))
continue;
var obj = TimelineUtility.GetSceneGameObject(director, track);
if (obj != null)
{
// checks if the effected gameobject is our child
var childLevel = GetChildLevel(obj, gameObject);
if (childLevel != -1 && childLevel < level)
{
result = track;
level = childLevel;
}
}
}
// the resulting track is not armed. checking here avoids accidentally recording objects with their own
// tracks
if (result && !state.IsTrackRecordable(result))
{
result = null;
}
return result;
}
// Gets the track this property would record to.
// Returns null if there is a track, but it's not currently active for recording
public static TrackAsset GetRecordingTrack(SerializedProperty property, WindowState state)
{
var serializedObject = property.serializedObject;
var component = serializedObject.targetObject as Component;
if (component == null)
return null;
var gameObject = component.gameObject;
return GetTrackForGameObject(gameObject, state);
}
// Given a serialized property, gathers all animatable properties
static void GatherModifications(SerializedProperty property, List<PropertyModification> modifications)
{
// handles child properties (Vector3 is 3 recordable properties)
if (property.hasChildren)
{
var iter = property.Copy();
var end = property.GetEndProperty(false);
// recurse over all children properties
while (iter.Next(true) && !SerializedProperty.EqualContents(iter, end))
{
GatherModifications(iter, modifications);
}
}
var isObject = property.propertyType == SerializedPropertyType.ObjectReference;
var isFloat = property.propertyType == SerializedPropertyType.Float ||
property.propertyType == SerializedPropertyType.Boolean ||
property.propertyType == SerializedPropertyType.Integer;
if (isObject || isFloat)
{
var serializedObject = property.serializedObject;
var modification = new PropertyModification();
modification.target = serializedObject.targetObject;
modification.propertyPath = property.propertyPath;
if (isObject)
{
modification.value = string.Empty;
modification.objectReference = property.objectReferenceValue;
}
else
{
modification.value = TimelineUtility.PropertyToString(property);
}
// Path for monobehaviour based - better to grab the component to get the curvebinding to allow validation
if (serializedObject.targetObject is Component)
{
EditorCurveBinding temp;
var go = ((Component)serializedObject.targetObject).gameObject;
if (AnimationUtility.PropertyModificationToEditorCurveBinding(modification, go, out temp) != null)
{
modifications.Add(modification);
}
}
else
{
modifications.Add(modification);
}
}
}
public static void AddKey(SerializedProperty prop, WindowState state)
{
s_TempPropertyModifications.Clear();
GatherModifications(prop, s_TempPropertyModifications);
if (s_TempPropertyModifications.Any())
{
AddKey(s_TempPropertyModifications, state);
}
}
public static void AddKey(IEnumerable<PropertyModification> modifications, WindowState state)
{
var undos = modifications.Select(PropertyModificationToUndoPropertyModification).ToArray();
if (HasAnyPlayableAssetModifications(undos))
ProcessPlayableAssetModification(undos, state, true);
ProcessMonoBehaviourModification(undos, state);
}
static UndoPropertyModification PropertyModificationToUndoPropertyModification(PropertyModification prop)
{
return new UndoPropertyModification
{
previousValue = prop,
currentValue = new PropertyModification
{
objectReference = prop.objectReference,
propertyPath = prop.propertyPath,
target = prop.target,
value = prop.value
},
keepPrefabOverride = true
};
}
// Given an animation track, return the clip that we are currently recording to
static AnimationClip GetRecordingClip(TrackAsset asset, WindowState state, out double startTime, out double timeScale)
{
startTime = 0;
timeScale = 1;
TimelineClip displayBackground = null;
asset.FindRecordingClipAtTime(state.editSequence.time, out displayBackground);
var animClip = asset.FindRecordingAnimationClipAtTime(state.editSequence.time);
if (displayBackground != null)
{
startTime = displayBackground.start;
timeScale = displayBackground.timeScale;
}
return animClip;
}
// Helper that finds the animation clip we are recording and the relative time to that clip
static bool GetClipAndRelativeTime(UnityEngine.Object target, WindowState state,
out AnimationClip outClip, out double keyTime, out bool keyInRange)
{
const float floatToDoubleError = 0.00001f;
outClip = null;
keyTime = 0;
keyInRange = false;
double startTime = 0;
double timeScale = 1;
AnimationClip clip = null;
IPlayableAsset playableAsset = target as IPlayableAsset;
Component component = target as Component;
// Handle recordable playable assets
if (playableAsset != null)
{
var curvesOwner = AnimatedParameterUtility.ToCurvesOwner(playableAsset, state.editSequence.asset);
if (curvesOwner != null)
{
if (curvesOwner.curves == null)
curvesOwner.CreateCurves(curvesOwner.GetUniqueRecordedClipName());
clip = curvesOwner.curves;
var timelineClip = curvesOwner as TimelineClip;
if (timelineClip != null)
{
startTime = timelineClip.start;
timeScale = timelineClip.timeScale;
}
}
}
// Handle recording components, including infinite clip
else if (component != null)
{
var asset = GetTrackForGameObject(component.gameObject, state);
if (asset != null)
{
clip = GetRecordingClip(asset, state, out startTime, out timeScale);
}
}
if (clip == null)
return false;
keyTime = (state.editSequence.time - startTime) * timeScale;
outClip = clip;
keyInRange = keyTime >= 0 && keyTime <= (clip.length * timeScale + floatToDoubleError);
return true;
}
public static bool HasCurve(IList<PropertyModification> modifications, UnityEngine.Object target,
WindowState state)
{
return GetKeyTimes(modifications, state).Any();
}
public static bool HasKey(IList<PropertyModification> modifications,
WindowState state)
{
AnimationClip clip;
double keyTime;
bool inRange;
if (!GetClipAndRelativeTime(modifications[0].target, state, out clip, out keyTime, out inRange))
return false;
return GetKeyTimes(modifications, state).Any(t => (CurveEditUtility.KeyCompare((float)state.editSequence.time, (float)t, clip.frameRate) == 0));
}
// Checks if a key already exists for this property
static bool HasBinding(UnityEngine.Object target, PropertyModification modification, AnimationClip clip, out EditorCurveBinding binding)
{
var component = target as Component;
var playableAsset = target as IPlayableAsset;
if (component != null)
{
var type = AnimationUtility.PropertyModificationToEditorCurveBinding(modification, component.gameObject, out binding);
binding = RotationCurveInterpolation.RemapAnimationBindingForRotationCurves(binding, clip);
return type != null;
}
if (playableAsset != null)
{
binding = EditorCurveBinding.FloatCurve(string.Empty, target.GetType(),
AnimatedParameterUtility.GetAnimatedParameterBindingName(target, modification.propertyPath));
}
else
{
binding = new EditorCurveBinding();
return false;
}
return true;
}
public static void RemoveKey(UnityEngine.Object target, IEnumerable<PropertyModification> modifications,
WindowState state)
{
AnimationClip clip;
double keyTime;
bool inRange;
if (!GetClipAndRelativeTime(target, state, out clip, out keyTime, out inRange) || !inRange)
return;
var refreshPreview = false;
TimelineUndo.PushUndo(clip, L10n.Tr("Remove Key"));
foreach (var mod in modifications)
{
EditorCurveBinding temp;
if (HasBinding(target, mod, clip, out temp))
{
if (temp.isPPtrCurve)
{
CurveEditUtility.RemoveObjectKey(clip, temp, keyTime);
if (CurveEditUtility.GetObjectKeyCount(clip, temp) == 0)
{
refreshPreview = true;
}
}
else
{
AnimationCurve curve = AnimationUtility.GetEditorCurve(clip, temp);
if (curve != null)
{
CurveEditUtility.RemoveKeyFrameFromCurve(curve, (float)keyTime, clip.frameRate);
AnimationUtility.SetEditorCurve(clip, temp, curve);
if (curve.length == 0)
{
AnimationUtility.SetEditorCurve(clip, temp, null);
refreshPreview = true;
}
}
}
}
}
if (refreshPreview)
{
state.ResetPreviewMode();
}
}
static HashSet<double> GetKeyTimes(IList<PropertyModification> modifications, WindowState state)
{
var keyTimes = new HashSet<double>();
AnimationClip animationClip;
double keyTime;
bool inRange;
var component = modifications[0].target as Component;
var target = modifications[0].target;
if (component != null)
{
var track = GetTrackForGameObject(component.gameObject, state);
var go = TimelineUtility.GetSceneGameObject(TimelineEditor.inspectedDirector, track);
if (go != null)
{
target = go.transform;
}
}
GetClipAndRelativeTime(target, state, out animationClip, out keyTime, out inRange);
if (animationClip == null)
return keyTimes;
var playableAsset = target as IPlayableAsset;
var info = AnimationClipCurveCache.Instance.GetCurveInfo(animationClip);
TimelineClip clip = null;
if (component != null)
{
GetTrackForGameObject(component.gameObject, state).FindRecordingClipAtTime(state.editSequence.time, out clip);
}
else if (playableAsset != null)
{
clip = FindClipWithAsset(state.editSequence.asset, playableAsset);
}
foreach (var mod in modifications)
{
EditorCurveBinding temp;
if (HasBinding(target, mod, animationClip, out temp))
{
IEnumerable<double> keys = new HashSet<double>();
if (temp.isPPtrCurve)
{
var curve = info.GetObjectCurveForBinding(temp);
if (curve != null)
{
keys = curve.Select(x => (double)x.time);
}
}
else
{
var curve = info.GetCurveForBinding(temp);
if (curve != null)
{
keys = curve.keys.Select(x => (double)x.time);
}
}
// Transform the times in to 'global' space using the clip
if (clip != null)
{
foreach (var k in keys)
{
var time = clip.FromLocalTimeUnbound(k);
const double eps = 1e-5;
if (time >= clip.start - eps && time <= clip.end + eps)
{
keyTimes.Add(time);
}
}
}
// infinite clip mode, global == local space
else
{
keyTimes.UnionWith(keys);
}
}
}
return keyTimes;
}
public static void NextKey(UnityEngine.Object target, IList<PropertyModification> modifications, WindowState state)
{
const double eps = 1e-5;
var keyTimes = GetKeyTimes(modifications, state);
if (keyTimes.Count == 0)
return;
var nextKeys = keyTimes.Where(x => x > state.editSequence.time + eps);
if (nextKeys.Any())
{
state.editSequence.time = nextKeys.Min();
}
}
public static void PrevKey(UnityEngine.Object target, IList<PropertyModification> modifications, WindowState state)
{
const double eps = 1e-5;
var keyTimes = GetKeyTimes(modifications, state);
if (keyTimes.Count == 0)
return;
var prevKeys = keyTimes.Where(x => x < state.editSequence.time - eps);
if (prevKeys.Any())
{
state.editSequence.time = prevKeys.Max();
}
}
public static void RemoveCurve(UnityEngine.Object target, IEnumerable<PropertyModification> modifications, WindowState state)
{
AnimationClip clip = null;
double keyTime = 0;
var inRange = false; // not used for curves
if (!GetClipAndRelativeTime(target, state, out clip, out keyTime, out inRange))
return;
TimelineUndo.PushUndo(clip, L10n.Tr("Remove Curve"));
foreach (var mod in modifications)
{
EditorCurveBinding temp;
if (HasBinding(target, mod, clip, out temp))
{
if (temp.isPPtrCurve)
AnimationUtility.SetObjectReferenceCurve(clip, temp, null);
else
AnimationUtility.SetEditorCurve(clip, temp, null);
}
}
state.ResetPreviewMode();
}
public static void KeyAllProperties(Component target, WindowState state)
{
var go = target is Component component ? component.gameObject : null;
GetClipAndRelativeTime(target, state, out var animationClip, out _, out _);
var info = AnimationClipCurveCache.Instance.GetCurveInfo(animationClip);
if (animationClip != null && info.curves.Length > 0)
{
KeyProperties(go, state, info.bindings);
}
}
public static void KeyProperties(GameObject go, WindowState state, IList<EditorCurveBinding> bindings)
{
var allKeyedProperties = new List<PropertyModification>();
var rotationPaths = new HashSet<string>();
for (var i = 0; i < bindings.Count; ++i)
{
// Skip the euler and key quaternion+hint
if (CurveEditUtility.IsRotationKey(bindings[i]))
{
rotationPaths.Add(bindings[i].path);
continue;
}
AnimationUtility.GetFloatValue(go, bindings[i], out var val);
var compo = GetTargetFromEditorBinding(go, bindings[i]);
allKeyedProperties.Add(new PropertyModification
{
target = compo, value = val.ToString(EditorGUI.kFloatFieldFormatString),
propertyPath = bindings[i].propertyName
});
}
foreach (var path in rotationPaths)
{
foreach (var binding in GetRotationBindings(path))
{
var compo = GetTargetFromEditorBinding(go, binding);
var readBinding = binding;
switch (binding.propertyName)
{
case kLocalEulerHint + ".x":
readBinding = EditorCurveBinding.FloatCurve(path, typeof(Transform), kLocalRotation + ".x");
break;
case kLocalEulerHint + ".y":
readBinding = EditorCurveBinding.FloatCurve(path, typeof(Transform), kLocalRotation + ".y");
break;
case kLocalEulerHint + ".z":
readBinding = EditorCurveBinding.FloatCurve(path, typeof(Transform), kLocalRotation + ".z");
break;
}
AnimationUtility.GetFloatValue(go, readBinding, out var val);
allKeyedProperties.Add(new PropertyModification
{
target = compo, value = val.ToString(EditorGUI.kFloatFieldFormatString),
propertyPath = binding.propertyName
});
}
}
AddKey(allKeyedProperties, state);
state.Refresh();
}
static IEnumerable<EditorCurveBinding> GetRotationBindings(string path)
{
return new[]
{
EditorCurveBinding.FloatCurve(path, typeof(Transform), kLocalRotation + ".x"),
EditorCurveBinding.FloatCurve(path, typeof(Transform), kLocalRotation + ".y"),
EditorCurveBinding.FloatCurve(path, typeof(Transform), kLocalRotation + ".z"),
EditorCurveBinding.FloatCurve(path, typeof(Transform), kLocalRotation + ".w"),
EditorCurveBinding.FloatCurve(path, typeof(Transform), kLocalEulerHint + ".x"),
EditorCurveBinding.FloatCurve(path, typeof(Transform), kLocalEulerHint + ".y"),
EditorCurveBinding.FloatCurve(path, typeof(Transform), kLocalEulerHint + ".z"),
};
}
static Component GetTargetFromEditorBinding(GameObject root, EditorCurveBinding binding)
{
GameObject go = null;
if (string.IsNullOrEmpty(binding.path))
{
go = root;
}
var childTransform = root.transform.Find(binding.path);
if (childTransform != null)
{
go = childTransform.gameObject;
}
if (go != null)
{
return go.GetComponent(binding.type);
}
return null;
}
public static IEnumerable<GameObject> GetRecordableGameObjects(WindowState state)
{
if (state == null || state.editSequence.asset == null || state.editSequence.director == null)
yield break;
var outputTracks = state.editSequence.asset.GetOutputTracks();
foreach (var track in outputTracks)
{
if (track.GetType() != typeof(AnimationTrack))
continue;
if (!state.IsTrackRecordable(track) && !track.GetChildTracks().Any(state.IsTrackRecordable))
continue;
var obj = TimelineUtility.GetSceneGameObject(state.editSequence.director, track);
if (obj != null)
{
yield return obj;
}
}
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ef4c81c9368d5a340b14c2fec1cad345
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,142 @@
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
class TimelineRecordingContextualResponder : IAnimationContextualResponder
{
public WindowState state { get; internal set; }
public TimelineRecordingContextualResponder(WindowState _state)
{
state = _state;
}
//Unsupported stuff
public bool HasAnyCandidates() { return false; }
public bool HasAnyCurves()
{
return false;
}
public void AddCandidateKeys() {}
public void AddAnimatedKeys()
{
}
public bool IsAnimatable(PropertyModification[] modifications)
{
// search playable assets
for (int i = 0; i < modifications.Length; i++)
{
var iAsset = modifications[i].target as IPlayableAsset;
if (iAsset != null)
{
var curvesOwner = AnimatedParameterUtility.ToCurvesOwner(iAsset, state.editSequence.asset);
if (curvesOwner != null && curvesOwner.HasAnyAnimatableParameters() && curvesOwner.IsParameterAnimatable(modifications[i].propertyPath))
return true;
}
}
// search recordable game objects
foreach (var gameObject in TimelineRecording.GetRecordableGameObjects(state))
{
for (int i = 0; i < modifications.Length; ++i)
{
var modification = modifications[i];
if (AnimationWindowUtility.PropertyIsAnimatable(modification.target, modification.propertyPath, gameObject))
return true;
}
}
return false;
}
public bool IsEditable(Object targetObject)
{
return true; // i.e. all animatable properties are editable
}
public bool KeyExists(PropertyModification[] modifications)
{
if (modifications.Length == 0 || modifications[0].target == null)
return false;
return TimelineRecording.HasKey(modifications, state);
}
public bool CandidateExists(PropertyModification[] modifications)
{
return true;
}
public bool CurveExists(PropertyModification[] modifications)
{
if (modifications.Length == 0 || modifications[0].target == null)
return false;
return TimelineRecording.HasCurve(modifications, modifications[0].target, state);
}
public void AddKey(PropertyModification[] modifications)
{
TimelineRecording.AddKey(modifications, state);
state.Refresh();
}
public void RemoveKey(PropertyModification[] modifications)
{
if (modifications.Length == 0)
return;
var target = modifications[0].target;
if (target == null)
return;
TimelineRecording.RemoveKey(modifications[0].target, modifications, state);
var curvesOwner = target as ICurvesOwner;
if (curvesOwner != null)
curvesOwner.SanitizeCurvesData();
state.Refresh();
}
public void RemoveCurve(PropertyModification[] modifications)
{
if (modifications.Length == 0)
return;
var target = modifications[0].target;
if (target == null)
return;
TimelineRecording.RemoveCurve(target, modifications, state);
var curvesOwner = target as ICurvesOwner;
if (curvesOwner != null)
curvesOwner.SanitizeCurvesData();
state.Refresh();
}
public void GoToNextKeyframe(PropertyModification[] modifications)
{
if (modifications.Length == 0 || modifications[0].target == null)
return;
TimelineRecording.NextKey(modifications[0].target, modifications, state);
state.Refresh();
}
public void GoToPreviousKeyframe(PropertyModification[] modifications)
{
TimelineRecording.PrevKey(modifications[0].target, modifications, state);
state.Refresh();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a59c2e62fbd97f84f92c3b546e3903cb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,718 @@
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, L10n.Tr("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() })
};
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9805855c8e379ed4cad77f639aaddb73
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,119 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using UnityEngine;
using UnityEngine.Timeline;
using UnityEngine.Playables;
namespace UnityEditor.Timeline
{
// Handles Undo animated properties on PlayableAssets from clips to create parameter animation
static partial class TimelineRecording
{
internal static bool HasAnyPlayableAssetModifications(UndoPropertyModification[] modifications)
{
return modifications.Any(x => GetTarget(x) as IPlayableAsset != null);
}
internal static UndoPropertyModification[] ProcessPlayableAssetModification(UndoPropertyModification[] modifications, WindowState state, bool allowAdd)
{
// can't record without a director since the asset being modified might be a scene instance
if (state == null || state.editSequence.director == null)
return modifications;
var remaining = new List<UndoPropertyModification>();
foreach (UndoPropertyModification mod in modifications)
{
if (!ProcessPlayableAssetModification(mod, state, allowAdd))
remaining.Add(mod);
}
if (remaining.Count != modifications.Length)
{
TimelineEditor.Refresh(RefreshReason.ContentsModified);
}
return remaining.ToArray();
}
static bool ProcessPlayableAssetModification(UndoPropertyModification mod, WindowState state, bool allowAdd)
{
var target = GetTarget(mod) as IPlayableAsset;
if (target == null)
return false;
var curvesOwner = AnimatedParameterUtility.ToCurvesOwner(target, state.editSequence.asset);
if (curvesOwner == null || !curvesOwner.HasAnyAnimatableParameters())
return false;
return ProcessPlayableAssetRecording(mod, state, curvesOwner, allowAdd);
}
internal static TimelineClip FindClipWithAsset(TimelineAsset asset, IPlayableAsset target)
{
if (target == null || asset == null)
return null;
var clips = asset.flattenedTracks.SelectMany(x => x.clips);
return clips.FirstOrDefault(x => x != null && x.asset != null && target == x.asset as IPlayableAsset);
}
static bool ProcessPlayableAssetRecording(UndoPropertyModification mod, WindowState state, ICurvesOwner curvesOwner, bool allowAdd)
{
if (mod.currentValue == null)
return false;
if (!curvesOwner.IsParameterAnimatable(mod.currentValue.propertyPath))
return false;
// only animate items with existing curves
if (!allowAdd && !curvesOwner.IsParameterAnimated(mod.currentValue.propertyPath))
return false;
var localTime = state.editSequence.time;
var timelineClip = curvesOwner as TimelineClip;
if (timelineClip != null)
{
// don't use time global to local since it will possibly loop.
localTime = timelineClip.ToLocalTimeUnbound(state.editSequence.time);
}
if (localTime < 0)
return false;
// grab the value from the current modification
float fValue;
if (!ExpressionEvaluator.Evaluate(mod.currentValue.value, out fValue))
{
// case 916913 -- 'Add Key' menu item will passes 'True' or 'False' (instead of 1, 0)
// so we need a special case to parse the boolean string
bool bValue;
if (!bool.TryParse(mod.currentValue.value, out bValue))
{
Debug.Assert(false, "Invalid type in PlayableAsset recording");
return false;
}
fValue = bValue ? 1 : 0;
}
var added = curvesOwner.AddAnimatedParameterValueAt(mod.currentValue.propertyPath, fValue, (float)localTime);
if (added && AnimationMode.InAnimationMode())
{
EditorCurveBinding binding = curvesOwner.GetCurveBinding(mod.previousValue.propertyPath);
AnimationMode.AddPropertyModification(binding, mod.previousValue, true);
curvesOwner.targetTrack.SetShowInlineCurves(true);
if (state.GetWindow() != null && state.GetWindow().treeView != null)
state.GetWindow().treeView.CalculateRowRects();
}
return added;
}
static bool IsPlayableAssetProperty(SerializedProperty property)
{
return property.serializedObject.targetObject is IPlayableAsset;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7341c0cd0aad4994e8fa461cb443aa7d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,191 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
namespace UnityEditor.Timeline
{
static class TrackAssetRecordingExtensions
{
static readonly Dictionary<TrackAsset, AnimationClip> s_ActiveClips = new Dictionary<TrackAsset, AnimationClip>();
internal static void OnRecordingArmed(this TrackAsset track, PlayableDirector director)
{
if (track == null)
return;
var animClip = track.FindRecordingAnimationClipAtTime(director.time);
if (animClip == null)
return;
s_ActiveClips[track] = animClip;
}
internal static void OnRecordingTimeChanged(this TrackAsset track, PlayableDirector director)
{
if (track == null)
return;
var animClip = track.FindRecordingAnimationClipAtTime(director.time);
AnimationClip prevClip = track.GetActiveRecordingAnimationClip();
if (prevClip != animClip)
{
s_ActiveClips[track] = animClip;
}
}
internal static void OnRecordingUnarmed(this TrackAsset track, PlayableDirector director)
{
s_ActiveClips.Remove(track);
}
internal static bool CanRecordAtTime(this TrackAsset track, double time)
{
// Animation Track
var animTrack = track as AnimationTrack;
if (animTrack != null)
{
if (!animTrack.inClipMode)
return true;
TimelineClip clip = null;
return FindRecordingClipAtTime(track, time, out clip);
}
// Custom track
return track.clips.Any(x => x.start < time + TimeUtility.kTimeEpsilon && x.HasAnyAnimatableParameters());
}
internal static AnimationClip GetActiveRecordingAnimationClip(this TrackAsset track)
{
AnimationClip clip = null;
s_ActiveClips.TryGetValue(track, out clip);
return clip;
}
internal static bool IsRecordingToClip(this TrackAsset track, TimelineClip clip)
{
if (track == null || clip == null)
return false;
var animClip = track.GetActiveRecordingAnimationClip();
if (animClip == null)
return false;
if (animClip == clip.curves)
return true;
var animAsset = clip.asset as AnimationPlayableAsset;
return animAsset != null && animClip == animAsset.clip;
}
// Finds the clip at the given time that recording should use
// returns whether recording at this particular point is valid
// The target clip will be returned, even if recording at that time is invalid
// in case of recording in a blend OR recording to a non-timeline clip
internal static bool FindRecordingClipAtTime(this TrackAsset track, double time, out TimelineClip target)
{
target = null;
if (track == null)
{
return false;
}
var discreteTime = new DiscreteTime(time);
// only animation tracks require the recordable flag as they are recording
// to an animation clip
bool requiresRecordable = (track as AnimationTrack) != null;
if (requiresRecordable)
{
track.SortClips();
var sortedByStartTime = track.clips;
int i = 0;
for (i = 0; i < sortedByStartTime.Length; i++)
{
var clip = sortedByStartTime[i];
if (new DiscreteTime(clip.start) <= discreteTime && new DiscreteTime(clip.end) > discreteTime)
{
target = clip;
// not recordable
if (!clip.recordable)
return false;
// in a blend
if (!Mathf.Approximately(1.0f, clip.EvaluateMixIn(time) * clip.EvaluateMixOut(time)))
return false;
return true;
}
if (new DiscreteTime(clip.start) > discreteTime)
{
break;
}
}
return false;
}
// Recordable playable assets -- takes the last clip that matches
track.SortClips();
for (int i = 0; i < track.clips.Length; i++)
{
var clip = track.clips[i];
if (clip.start <= time && clip.end >= time && clip.HasAnyAnimatableParameters())
target = clip;
if (clip.start > time)
break;
}
return target != null;
}
// Given a track, return the animation clip
internal static AnimationClip FindRecordingAnimationClipAtTime(this TrackAsset trackAsset, double time)
{
if (trackAsset == null)
return null;
AnimationTrack animTrack = trackAsset as AnimationTrack;
if (animTrack != null && !animTrack.inClipMode)
{
return animTrack.infiniteClip;
}
TimelineClip displayBackground;
trackAsset.FindRecordingClipAtTime(time, out displayBackground);
if (displayBackground != null)
{
if (displayBackground.recordable)
{
AnimationPlayableAsset asset = displayBackground.asset as AnimationPlayableAsset;
if (asset != null)
return asset.clip;
}
else if (animTrack == null)
{
if (displayBackground.curves == null)
displayBackground.CreateCurves(AnimationTrackRecorder.GetUniqueRecordedClipName(displayBackground.GetParentTrack(), TimelineClip.kDefaultCurvesName));
return displayBackground.curves;
}
}
else if (trackAsset.HasAnyAnimatableParameters())
{
if (trackAsset.curves == null)
trackAsset.CreateCurves(AnimationTrackRecorder.GetUniqueRecordedClipName(trackAsset.timelineAsset, TrackAsset.kDefaultCurvesName));
return trackAsset.curves;
}
return null;
}
internal static void ClearRecordingState()
{
s_ActiveClips.Clear();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 49f1d2c7420db4444b011955726d0046
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: