313 lines
11 KiB
C#
313 lines
11 KiB
C#
// Animancer // https://kybernetik.com.au/animancer // Copyright 2018-2024 Kybernetik //
|
|
|
|
#if UNITY_EDITOR
|
|
|
|
using System;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
namespace Animancer.Editor.Previews
|
|
{
|
|
/// <summary>[Editor-Only] Utility for playing through transition previews.</summary>
|
|
/// https://kybernetik.com.au/animancer/api/Animancer.Editor.Previews/TransitionPreviewPlayer
|
|
[Serializable]
|
|
public class TransitionPreviewPlayer : IDisposable
|
|
{
|
|
/************************************************************************************************************************/
|
|
|
|
[SerializeReference] private ITransition _FromTransition;
|
|
[SerializeReference] private ITransition _ToTransition;
|
|
|
|
/// <summary>The animation to play first.</summary>
|
|
public ref ITransition FromTransition
|
|
=> ref _FromTransition;
|
|
|
|
/// <summary>The animation to transition into.</summary>
|
|
public ref ITransition ToTransition
|
|
=> ref _ToTransition;
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
private AnimancerGraph _Graph;
|
|
|
|
/// <summary>The graph used to play the animations.</summary>
|
|
public AnimancerGraph Graph
|
|
{
|
|
get => _Graph;
|
|
set
|
|
{
|
|
_Graph = value;
|
|
Evaluate();
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>
|
|
/// The minimum amount of time to play the <see cref="FromTransition"/>
|
|
/// before the <see cref="ToTransition"/> starts (in seconds).
|
|
/// </summary>
|
|
public float FromDuration { get; set; }
|
|
|
|
/// <summary>
|
|
/// The minimum amount of time to continue playing
|
|
/// after the <see cref="ToTransition"/> started (in seconds).
|
|
/// </summary>
|
|
public float ToDuration { get; set; }
|
|
|
|
/// <summary>The speed at which the preview plays.</summary>
|
|
public float Speed { get; set; } = 1;
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
private float _FadeDuration = float.NaN;
|
|
|
|
/// <summary>The <see cref="ITransition.FadeDuration"/>.</summary>
|
|
/// <remarks><see cref="float.NaN"/> uses the value from the <see cref="ToTransition"/>.</remarks>
|
|
public float FadeDuration
|
|
{
|
|
get => float.IsNaN(_FadeDuration) && ToTransition.IsValid()
|
|
? ToTransition.FadeDuration
|
|
: _FadeDuration;
|
|
set => _FadeDuration = value;
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>The lowest allowed <see cref="CurrentTime"/>.</summary>
|
|
/// <remarks>
|
|
/// This is the lower of the negative <see cref="FromDuration"/>
|
|
/// or the negative duration of the <see cref="FromTransition"/>.
|
|
/// </remarks>
|
|
public float MinTime { get; private set; }
|
|
|
|
/// <summary>The highest allowed <see cref="CurrentTime"/>.</summary>
|
|
/// <remarks>
|
|
/// This is the higher of the <see cref="ToDuration"/> or <see cref="FadeDuration"/>
|
|
/// or the duration of the <see cref="ToTransition"/>.
|
|
/// </remarks>
|
|
public float MaxTime { get; private set; }
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Recalculated the <see cref="MinTime"/> and <see cref="MaxTime"/>.</summary>
|
|
public void RecalculateTimeBounds()
|
|
{
|
|
MinTime = -FromDuration;
|
|
|
|
if (_FromTransition.IsValid() &&
|
|
AnimancerUtilities.TryCalculateDuration(_FromTransition, out var duration))
|
|
MinTime = Math.Min(MinTime, -duration);
|
|
|
|
MaxTime = ToDuration;
|
|
|
|
var fadeDuration = FadeDuration;
|
|
if (!float.IsNaN(fadeDuration))
|
|
MaxTime = Math.Max(ToDuration, fadeDuration);
|
|
|
|
if (_ToTransition.IsValid() &&
|
|
AnimancerUtilities.TryCalculateDuration(_ToTransition, out duration))
|
|
MaxTime = Math.Max(MaxTime, duration);
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Converts normalized time to seconds.</summary>
|
|
public float LerpTimeUnclamped(float normalizedTime)
|
|
=> Mathf.LerpUnclamped(MinTime, MaxTime, normalizedTime);
|
|
|
|
/// <summary>Converts seconds to normalized time.</summary>
|
|
public float InverseLerpTimeUnclamped(float time)
|
|
=> AnimancerUtilities.InverseLerpUnclamped(MinTime, MaxTime, time);
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
private float _CurrentTime = float.NaN;
|
|
|
|
/// <summary>The amount of time that has passed since the <see cref="ToTransition"/> started (in seconds).</summary>
|
|
/// <remarks>This value goes from the <see cref="MinTime"/> to the <see cref="MaxTime"/>.</remarks>
|
|
public float CurrentTime
|
|
{
|
|
get => float.IsNaN(_CurrentTime)
|
|
? MinTime
|
|
: _CurrentTime;
|
|
set
|
|
{
|
|
_CurrentTime = value;
|
|
Evaluate();
|
|
}
|
|
}
|
|
|
|
/// <summary>The amount of time that has passed since the <see cref="ToTransition"/> started (normalized).</summary>
|
|
/// <remarks>0 is at the <see cref="MinTime"/> and 1 is at the <see cref="MaxTime"/>.</remarks>
|
|
public float NormalizedTime
|
|
{
|
|
get => InverseLerpTimeUnclamped(CurrentTime);
|
|
set => CurrentTime = LerpTimeUnclamped(value);
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
private bool _IsPlaying;
|
|
|
|
/// <summary>Is the preview currently playing?</summary>
|
|
public bool IsPlaying
|
|
{
|
|
get => _IsPlaying;
|
|
set
|
|
{
|
|
if (_IsPlaying == value)
|
|
return;
|
|
|
|
_IsPlaying = value;
|
|
|
|
if (_IsPlaying)
|
|
{
|
|
_LastUpdateTime = TimeSinceStartup;
|
|
|
|
EditorApplication.update += Update;
|
|
}
|
|
else
|
|
{
|
|
EditorApplication.update -= Update;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>Cleans up this player.</summary>
|
|
public void Dispose()
|
|
=> IsPlaying = false;
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary><see cref="EditorApplication.timeSinceStartup"/></summary>
|
|
private static double TimeSinceStartup
|
|
=> EditorApplication.timeSinceStartup;
|
|
|
|
private double _LastUpdateTime;
|
|
|
|
/// <summary>Updates the preview time while playing.</summary>
|
|
private void Update()
|
|
{
|
|
if (Graph == null || !Graph.IsValidOrDispose())
|
|
{
|
|
EditorApplication.update -= Update;
|
|
return;
|
|
}
|
|
|
|
var time = TimeSinceStartup;
|
|
var deltaTime = time - _LastUpdateTime;
|
|
_LastUpdateTime = time;
|
|
|
|
CurrentTime += ((float)deltaTime) * Speed;
|
|
|
|
AnimancerGUI.RepaintEverything();
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Applies the animations at the <see cref="CurrentTime"/>.</summary>
|
|
private void Evaluate()
|
|
{
|
|
if (Graph == null)
|
|
return;
|
|
|
|
Graph.PauseGraph();
|
|
Graph.Stop();
|
|
|
|
if (_FromTransition.IsValid())
|
|
{
|
|
if (_ToTransition.IsValid())
|
|
{
|
|
Apply(CurrentTime, _FromTransition, _ToTransition);
|
|
}
|
|
else
|
|
{
|
|
var minTime = MinTime;
|
|
Apply(CurrentTime - minTime, -minTime, _FromTransition);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (_ToTransition.IsValid())
|
|
Apply(CurrentTime, MaxTime, _ToTransition);
|
|
else
|
|
return;
|
|
}
|
|
|
|
Graph.Evaluate();
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Applies the animations at the `currentTime`.</summary>
|
|
private void Apply(float currentTime, ITransition from, ITransition to)
|
|
{
|
|
var layer = Graph.Layers[0];
|
|
|
|
// Playing From.
|
|
if (currentTime < 0)
|
|
{
|
|
var state = layer.Play(from);
|
|
state.Time += state.NormalizedEndTime * state.Length + currentTime;
|
|
return;
|
|
}
|
|
|
|
var maxTime = MaxTime;
|
|
|
|
// Fading.
|
|
if (currentTime < maxTime)
|
|
{
|
|
var state = layer.Play(from);
|
|
state.Time += state.NormalizedEndTime * state.Length + currentTime;
|
|
|
|
state = layer.Play(to, FadeDuration, to.FadeMode);
|
|
state.Time += currentTime;
|
|
|
|
var fade = state.FadeGroup;
|
|
if (fade != null)
|
|
{
|
|
fade.NormalizedTime += currentTime * fade.FadeSpeed;
|
|
fade.ApplyWeights();
|
|
}
|
|
}
|
|
// Playing To.
|
|
else
|
|
{
|
|
var state = layer.Play(to);
|
|
state.Time += currentTime;
|
|
|
|
// Finished.
|
|
if (currentTime >= maxTime)
|
|
{
|
|
_CurrentTime = MinTime;
|
|
state.Time = _CurrentTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
|
|
/// <summary>Applies the animation at the `currentTime`.</summary>
|
|
private void Apply(float currentTime, float endTime, ITransition transition)
|
|
{
|
|
var state = Graph.Layers[0].Play(transition);
|
|
|
|
if (currentTime < endTime)// Playing.
|
|
{
|
|
state.Time = currentTime;
|
|
}
|
|
else// Finished.
|
|
{
|
|
_CurrentTime = MinTime;
|
|
state.Time = _CurrentTime;
|
|
}
|
|
}
|
|
|
|
/************************************************************************************************************************/
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|