UnityGame/Library/PackageCache/com.unity.timeline/Editor/Animation/TimelineAnimationUtilities.cs
2024-10-27 10:53:47 +03:00

436 lines
17 KiB
C#

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;
}
}
}