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.
435 lines
17 KiB
435 lines
17 KiB
using System; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using UnityEngine; |
|
using UnityEditor; |
|
using UnityEngineInternal; |
|
using UnityEngine.Timeline; |
|
using UnityEngine.Playables; |
|
using Object = UnityEngine.Object; |
|
|
|
namespace UnityEditor.Timeline |
|
{ |
|
class TimelineAnimationUtilities |
|
{ |
|
public enum OffsetEditMode |
|
{ |
|
None = -1, |
|
Translation = 0, |
|
Rotation = 1 |
|
} |
|
|
|
public static bool ValidateOffsetAvailabitity(PlayableDirector director, Animator animator) |
|
{ |
|
if (director == null || animator == null) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
public static TimelineClip GetPreviousClip(TimelineClip clip) |
|
{ |
|
TimelineClip previousClip = null; |
|
foreach (var c in clip.GetParentTrack().clips) |
|
{ |
|
if (c.start < clip.start && (previousClip == null || c.start >= previousClip.start)) |
|
previousClip = c; |
|
} |
|
return previousClip; |
|
} |
|
|
|
public static TimelineClip GetNextClip(TimelineClip clip) |
|
{ |
|
return clip.GetParentTrack().clips.Where(c => c.start > clip.start).OrderBy(c => c.start).FirstOrDefault(); |
|
} |
|
|
|
public struct RigidTransform |
|
{ |
|
public Vector3 position; |
|
public Quaternion rotation; |
|
|
|
public static RigidTransform Compose(Vector3 pos, Quaternion rot) |
|
{ |
|
RigidTransform ret; |
|
ret.position = pos; |
|
ret.rotation = rot; |
|
return ret; |
|
} |
|
|
|
public static RigidTransform Mul(RigidTransform a, RigidTransform b) |
|
{ |
|
RigidTransform ret; |
|
ret.rotation = a.rotation * b.rotation; |
|
ret.position = a.position + a.rotation * b.position; |
|
return ret; |
|
} |
|
|
|
public static RigidTransform Inverse(RigidTransform a) |
|
{ |
|
RigidTransform ret; |
|
ret.rotation = Quaternion.Inverse(a.rotation); |
|
ret.position = ret.rotation * (-a.position); |
|
return ret; |
|
} |
|
|
|
public static RigidTransform identity |
|
{ |
|
get { return Compose(Vector3.zero, Quaternion.identity); } |
|
} |
|
} |
|
|
|
|
|
private static Matrix4x4 GetTrackMatrix(Transform transform, AnimationTrack track) |
|
{ |
|
Matrix4x4 trackMatrix = Matrix4x4.TRS(track.position, track.rotation, Vector3.one); |
|
|
|
// in scene off mode, the track offsets are set to the preview position which is stored in the track |
|
if (track.trackOffset == TrackOffset.ApplySceneOffsets) |
|
{ |
|
trackMatrix = Matrix4x4.TRS(track.sceneOffsetPosition, Quaternion.Euler(track.sceneOffsetRotation), Vector3.one); |
|
} |
|
|
|
// put the parent transform on to the track matrix |
|
if (transform.parent != null) |
|
{ |
|
trackMatrix = transform.parent.localToWorldMatrix * trackMatrix; |
|
} |
|
|
|
return trackMatrix; |
|
} |
|
|
|
// Given a world space position and rotation, updates the clip offsets to match that |
|
public static RigidTransform UpdateClipOffsets(AnimationPlayableAsset asset, AnimationTrack track, Transform transform, Vector3 globalPosition, Quaternion globalRotation) |
|
{ |
|
Matrix4x4 worldToLocal = transform.worldToLocalMatrix; |
|
Matrix4x4 clipMatrix = Matrix4x4.TRS(asset.position, asset.rotation, Vector3.one); |
|
Matrix4x4 trackMatrix = GetTrackMatrix(transform, track); |
|
|
|
|
|
// Use the transform to find the proper goal matrix with scale taken into account |
|
var oldPos = transform.position; |
|
var oldRot = transform.rotation; |
|
transform.position = globalPosition; |
|
transform.rotation = globalRotation; |
|
Matrix4x4 goal = transform.localToWorldMatrix; |
|
transform.position = oldPos; |
|
transform.rotation = oldRot; |
|
|
|
// compute the new clip matrix. |
|
Matrix4x4 newClip = trackMatrix.inverse * goal * worldToLocal * trackMatrix * clipMatrix; |
|
return RigidTransform.Compose(newClip.GetColumn(3), MathUtils.QuaternionFromMatrix(newClip)); |
|
} |
|
|
|
public static RigidTransform GetTrackOffsets(AnimationTrack track, Transform transform) |
|
{ |
|
Vector3 position = track.position; |
|
Quaternion rotation = track.rotation; |
|
if (transform != null && transform.parent != null) |
|
{ |
|
position = transform.parent.TransformPoint(position); |
|
rotation = transform.parent.rotation * rotation; |
|
MathUtils.QuaternionNormalize(ref rotation); |
|
} |
|
|
|
return RigidTransform.Compose(position, rotation); |
|
} |
|
|
|
public static void UpdateTrackOffset(AnimationTrack track, Transform transform, RigidTransform offsets) |
|
{ |
|
if (transform != null && transform.parent != null) |
|
{ |
|
offsets.position = transform.parent.InverseTransformPoint(offsets.position); |
|
offsets.rotation = Quaternion.Inverse(transform.parent.rotation) * offsets.rotation; |
|
MathUtils.QuaternionNormalize(ref offsets.rotation); |
|
} |
|
|
|
track.position = offsets.position; |
|
track.eulerAngles = AnimationUtility.GetClosestEuler(offsets.rotation, track.eulerAngles, RotationOrder.OrderZXY); |
|
track.UpdateClipOffsets(); |
|
} |
|
|
|
static MatchTargetFields GetMatchFields(TimelineClip clip) |
|
{ |
|
var track = clip.GetParentTrack() as AnimationTrack; |
|
if (track == null) |
|
return MatchTargetFieldConstants.None; |
|
|
|
var asset = clip.asset as AnimationPlayableAsset; |
|
var fields = track.matchTargetFields; |
|
if (asset != null && !asset.useTrackMatchFields) |
|
fields = asset.matchTargetFields; |
|
return fields; |
|
} |
|
|
|
static void WriteMatchFields(AnimationPlayableAsset asset, RigidTransform result, MatchTargetFields fields) |
|
{ |
|
Vector3 position = asset.position; |
|
|
|
position.x = fields.HasAny(MatchTargetFields.PositionX) ? result.position.x : position.x; |
|
position.y = fields.HasAny(MatchTargetFields.PositionY) ? result.position.y : position.y; |
|
position.z = fields.HasAny(MatchTargetFields.PositionZ) ? result.position.z : position.z; |
|
|
|
asset.position = position; |
|
|
|
// check first to avoid unnecessary conversion errors |
|
if (fields.HasAny(MatchTargetFieldConstants.Rotation)) |
|
{ |
|
Vector3 eulers = asset.eulerAngles; |
|
Vector3 resultEulers = result.rotation.eulerAngles; |
|
|
|
eulers.x = fields.HasAny(MatchTargetFields.RotationX) ? resultEulers.x : eulers.x; |
|
eulers.y = fields.HasAny(MatchTargetFields.RotationY) ? resultEulers.y : eulers.y; |
|
eulers.z = fields.HasAny(MatchTargetFields.RotationZ) ? resultEulers.z : eulers.z; |
|
|
|
asset.eulerAngles = AnimationUtility.GetClosestEuler(Quaternion.Euler(eulers), asset.eulerAngles, RotationOrder.OrderZXY); |
|
} |
|
} |
|
|
|
public static void MatchPrevious(TimelineClip currentClip, Transform matchPoint, PlayableDirector director) |
|
{ |
|
const double timeEpsilon = 0.00001; |
|
MatchTargetFields matchFields = GetMatchFields(currentClip); |
|
if (matchFields == MatchTargetFieldConstants.None || matchPoint == null) |
|
return; |
|
|
|
double cachedTime = director.time; |
|
|
|
// finds previous clip |
|
TimelineClip previousClip = GetPreviousClip(currentClip); |
|
if (previousClip == null || currentClip == previousClip) |
|
return; |
|
|
|
// make sure the transform is properly updated before modifying the graph |
|
director.Evaluate(); |
|
|
|
var parentTrack = currentClip.GetParentTrack() as AnimationTrack; |
|
|
|
var blendIn = currentClip.blendInDuration; |
|
currentClip.blendInDuration = 0; |
|
var blendOut = previousClip.blendOutDuration; |
|
previousClip.blendOutDuration = 0; |
|
|
|
//evaluate previous without current |
|
parentTrack.RemoveClip(currentClip); |
|
director.RebuildGraph(); |
|
double previousEndTime = currentClip.start > previousClip.end ? previousClip.end : currentClip.start; |
|
director.time = previousEndTime - timeEpsilon; |
|
director.Evaluate(); // add port to evaluate only track |
|
|
|
var targetPosition = matchPoint.position; |
|
var targetRotation = matchPoint.rotation; |
|
|
|
// evaluate current without previous |
|
parentTrack.AddClip(currentClip); |
|
parentTrack.RemoveClip(previousClip); |
|
director.RebuildGraph(); |
|
director.time = currentClip.start + timeEpsilon; |
|
director.Evaluate(); |
|
|
|
////////////////////////////////////////////////////////////////////// |
|
//compute offsets |
|
|
|
var animationPlayable = currentClip.asset as AnimationPlayableAsset; |
|
var match = UpdateClipOffsets(animationPlayable, parentTrack, matchPoint, targetPosition, targetRotation); |
|
WriteMatchFields(animationPlayable, match, matchFields); |
|
|
|
////////////////////////////////////////////////////////////////////// |
|
|
|
currentClip.blendInDuration = blendIn; |
|
previousClip.blendOutDuration = blendOut; |
|
|
|
parentTrack.AddClip(previousClip); |
|
director.RebuildGraph(); |
|
director.time = cachedTime; |
|
director.Evaluate(); |
|
} |
|
|
|
public static void MatchNext(TimelineClip currentClip, Transform matchPoint, PlayableDirector director) |
|
{ |
|
const double timeEpsilon = 0.00001; |
|
MatchTargetFields matchFields = GetMatchFields(currentClip); |
|
if (matchFields == MatchTargetFieldConstants.None || matchPoint == null) |
|
return; |
|
|
|
double cachedTime = director.time; |
|
|
|
// finds next clip |
|
TimelineClip nextClip = GetNextClip(currentClip); |
|
if (nextClip == null || currentClip == nextClip) |
|
return; |
|
|
|
// make sure the transform is properly updated before modifying the graph |
|
director.Evaluate(); |
|
|
|
var parentTrack = currentClip.GetParentTrack() as AnimationTrack; |
|
|
|
var blendOut = currentClip.blendOutDuration; |
|
var blendIn = nextClip.blendInDuration; |
|
currentClip.blendOutDuration = 0; |
|
nextClip.blendInDuration = 0; |
|
|
|
//evaluate previous without current |
|
parentTrack.RemoveClip(currentClip); |
|
director.RebuildGraph(); |
|
director.time = nextClip.start + timeEpsilon; |
|
director.Evaluate(); // add port to evaluate only track |
|
|
|
var targetPosition = matchPoint.position; |
|
var targetRotation = matchPoint.rotation; |
|
|
|
// evaluate current without next |
|
parentTrack.AddClip(currentClip); |
|
parentTrack.RemoveClip(nextClip); |
|
director.RebuildGraph(); |
|
director.time = Math.Min(nextClip.start, currentClip.end - timeEpsilon); |
|
director.Evaluate(); |
|
|
|
////////////////////////////////////////////////////////////////////// |
|
//compute offsets |
|
|
|
var animationPlayable = currentClip.asset as AnimationPlayableAsset; |
|
var match = UpdateClipOffsets(animationPlayable, parentTrack, matchPoint, targetPosition, targetRotation); |
|
WriteMatchFields(animationPlayable, match, matchFields); |
|
|
|
////////////////////////////////////////////////////////////////////// |
|
|
|
currentClip.blendOutDuration = blendOut; |
|
nextClip.blendInDuration = blendIn; |
|
|
|
parentTrack.AddClip(nextClip); |
|
director.RebuildGraph(); |
|
director.time = cachedTime; |
|
director.Evaluate(); |
|
} |
|
|
|
public static TimelineWindowTimeControl CreateTimeController(TimelineClip clip) |
|
{ |
|
var animationWindow = EditorWindow.GetWindow<AnimationWindow>(); |
|
var timeController = ScriptableObject.CreateInstance<TimelineWindowTimeControl>(); |
|
timeController.Init(animationWindow.state, clip); |
|
return timeController; |
|
} |
|
|
|
public static TimelineWindowTimeControl CreateTimeController(TimelineWindowTimeControl.ClipData clipData) |
|
{ |
|
var animationWindow = EditorWindow.GetWindow<AnimationWindow>(); |
|
var timeController = ScriptableObject.CreateInstance<TimelineWindowTimeControl>(); |
|
timeController.Init(animationWindow.state, clipData); |
|
return timeController; |
|
} |
|
|
|
public static void EditAnimationClipWithTimeController(AnimationClip animationClip, TimelineWindowTimeControl timeController, Object sourceObject) |
|
{ |
|
var animationWindow = EditorWindow.GetWindow<AnimationWindow>(); |
|
animationWindow.EditSequencerClip(animationClip, sourceObject, timeController); |
|
} |
|
|
|
public static void UnlinkAnimationWindowFromTracks(IEnumerable<TrackAsset> tracks) |
|
{ |
|
var clips = new List<AnimationClip>(); |
|
foreach (var track in tracks) |
|
{ |
|
var animationTrack = track as AnimationTrack; |
|
if (animationTrack != null && animationTrack.infiniteClip != null) |
|
clips.Add(animationTrack.infiniteClip); |
|
|
|
GetAnimationClips(track.GetClips(), clips); |
|
} |
|
UnlinkAnimationWindowFromAnimationClips(clips); |
|
} |
|
|
|
public static void UnlinkAnimationWindowFromClips(IEnumerable<TimelineClip> timelineClips) |
|
{ |
|
var clips = new List<AnimationClip>(); |
|
GetAnimationClips(timelineClips, clips); |
|
UnlinkAnimationWindowFromAnimationClips(clips); |
|
} |
|
|
|
public static void UnlinkAnimationWindowFromAnimationClips(ICollection<AnimationClip> clips) |
|
{ |
|
if (clips.Count == 0) |
|
return; |
|
|
|
UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(AnimationWindow)); |
|
foreach (var animWindow in windows.OfType<AnimationWindow>()) |
|
{ |
|
if (animWindow != null && animWindow.state != null && animWindow.state.linkedWithSequencer && clips.Contains(animWindow.state.activeAnimationClip)) |
|
animWindow.UnlinkSequencer(); |
|
} |
|
} |
|
|
|
public static void UnlinkAnimationWindow() |
|
{ |
|
UnityEngine.Object[] windows = Resources.FindObjectsOfTypeAll(typeof(AnimationWindow)); |
|
foreach (var animWindow in windows.OfType<AnimationWindow>()) |
|
{ |
|
if (animWindow != null && animWindow.state != null && animWindow.state.linkedWithSequencer) |
|
animWindow.UnlinkSequencer(); |
|
} |
|
} |
|
|
|
private static void GetAnimationClips(IEnumerable<TimelineClip> timelineClips, List<AnimationClip> clips) |
|
{ |
|
foreach (var timelineClip in timelineClips) |
|
{ |
|
if (timelineClip.curves != null) |
|
clips.Add(timelineClip.curves); |
|
AnimationPlayableAsset apa = timelineClip.asset as AnimationPlayableAsset; |
|
if (apa != null && apa.clip != null) |
|
clips.Add(apa.clip); |
|
} |
|
} |
|
|
|
public static int GetAnimationWindowCurrentFrame() |
|
{ |
|
var animationWindow = EditorWindow.GetWindow<AnimationWindow>(); |
|
if (animationWindow) |
|
return animationWindow.state.currentFrame; |
|
return -1; |
|
} |
|
|
|
public static void SetAnimationWindowCurrentFrame(int frame) |
|
{ |
|
var animationWindow = EditorWindow.GetWindow<AnimationWindow>(); |
|
if (animationWindow) |
|
animationWindow.state.currentFrame = frame; |
|
} |
|
|
|
public static void ConstrainCurveToBooleanValues(AnimationCurve curve) |
|
{ |
|
// Clamp the values first |
|
var keys = curve.keys; |
|
for (var i = 0; i < keys.Length; i++) |
|
{ |
|
var key = keys[i]; |
|
key.value = key.value < 0.5f ? 0.0f : 1.0f; |
|
keys[i] = key; |
|
} |
|
curve.keys = keys; |
|
|
|
// Update the tangents once all the values are clamped |
|
for (var i = 0; i < curve.length; i++) |
|
{ |
|
AnimationUtility.SetKeyLeftTangentMode(curve, i, AnimationUtility.TangentMode.Constant); |
|
AnimationUtility.SetKeyRightTangentMode(curve, i, AnimationUtility.TangentMode.Constant); |
|
} |
|
} |
|
|
|
public static void ConstrainCurveToRange(AnimationCurve curve, float minValue, float maxValue) |
|
{ |
|
var keys = curve.keys; |
|
for (var i = 0; i < keys.Length; i++) |
|
{ |
|
var key = keys[i]; |
|
key.value = Mathf.Clamp(key.value, minValue, maxValue); |
|
keys[i] = key; |
|
} |
|
curve.keys = keys; |
|
} |
|
|
|
public static bool IsAnimationClip(TimelineClip clip) |
|
{ |
|
return clip != null && (clip.asset as AnimationPlayableAsset) != null; |
|
} |
|
} |
|
}
|
|
|