using System;
namespace Cthangover.Live2D.Cubism.Framework
{
///
/// Abstract base for all Live2D Cubism motions (animations and expressions).
/// Provides fade-in/fade-out timing, looping, weight blending, and event
/// callbacks shared across and
/// .
///
/// Motions are managed by , which
/// calls on start, then
/// and
/// every frame.
///
public abstract class ACubismMotion
{
///
/// Callback invoked when the motion finishes (non-looping only).
/// The finished motion is passed as the argument.
///
public delegate void FinishedMotionCallback(ACubismMotion motion);
///
/// Callback invoked when the motion begins playback.
/// Called from right after
/// the entry's times are configured.
///
public delegate void BeganMotionCallback(ACubismMotion motion);
protected float _fadeInSeconds = -1f;
protected float _fadeOutSeconds = -1f;
protected float _weight = 1f;
protected float _offsetSeconds;
protected bool _isLoop;
protected bool _isLoopFadeIn = true;
protected bool _previousLoopState;
///
/// Duration of the fade-in transition in seconds.
/// A negative value means the default (1 second) will be used
/// by and .
///
public float FadeInSeconds { get => _fadeInSeconds; set => _fadeInSeconds = value; }
///
/// Duration of the fade-out transition in seconds.
/// A negative value means the default (1 second) will be used.
///
public float FadeOutSeconds { get => _fadeOutSeconds; set => _fadeOutSeconds = value; }
///
/// Overall motion weight (0..1). Multiplied with the fade-in/fade-out
/// factors in to produce the final blend weight.
///
public float Weight { get => _weight; set => _weight = value; }
///
/// Time offset in seconds applied to the motion's start time.
/// Allows starting playback from an arbitrary point.
///
public float OffsetSeconds { get => _offsetSeconds; set => _offsetSeconds = value; }
///
/// Whether the motion loops. When enabled,
/// wraps timeOffset back to the beginning after the duration elapses.
///
public bool IsLoop { get => _isLoop; set => _isLoop = value; }
///
/// When true and looping, the motion performs a fade-in at each loop
/// restart. When false, the fade-in is skipped on subsequent loops.
///
public bool IsLoopFadeIn { get => _isLoopFadeIn; set => _isLoopFadeIn = value; }
///
/// Invoked when the motion finishes (non-looping only).
/// Automatically called by
/// when timeOffset exceeds duration.
///
public FinishedMotionCallback OnFinishedMotion { get; set; }
///
/// Invoked when the motion begins. Fires at the start of
/// after the entry is initialized.
///
public BeganMotionCallback OnBeganMotion { get; set; }
///
/// Returns the total motion duration in seconds.
/// Override in to return the motion3.json
/// duration, or -1 for looping motions.
///
public virtual float GetDuration() => -1f;
///
/// Returns the loop cycle duration in seconds.
/// Override in to return the full duration
/// even when looping is active.
///
public virtual float GetLoopDuration() => -1f;
///
/// Initializes a queue entry with start/end times based on current
/// , ,
/// and . Fires .
///
public void SetupMotionQueueEntry(CubismMotionQueueEntry entry, float userTimeSeconds)
{
var fadeIn = _fadeInSeconds >= 0f ? _fadeInSeconds : 1f;
entry.StartTime = userTimeSeconds - _offsetSeconds;
entry.FadeInStartTime = userTimeSeconds;
var duration = GetDuration();
UpdateEndTime(entry, duration);
OnBeganMotion?.Invoke(this);
}
///
/// Computes the final frame weight = x fadeIn x fadeOut.
/// Fade curves use the sine easing function from .
///
public float UpdateFadeWeight(CubismMotionQueueEntry entry, float userTimeSeconds)
{
var fadeIn = GetFadeIn(entry, userTimeSeconds);
var fadeOut = GetFadeOut(entry, userTimeSeconds);
return _weight * fadeIn * fadeOut;
}
private float GetFadeIn(CubismMotionQueueEntry entry, float userTimeSeconds)
{
var fadeInTime = _fadeInSeconds >= 0f ? _fadeInSeconds : 1f;
if (fadeInTime <= 0f) return 1f;
var t = (userTimeSeconds - entry.FadeInStartTime) / fadeInTime;
return CubismMath.GetEasingSine(t);
}
private float GetFadeOut(CubismMotionQueueEntry entry, float userTimeSeconds)
{
var fadeOutTime = _fadeOutSeconds >= 0f ? _fadeOutSeconds : 1f;
if (fadeOutTime <= 0f) return 1f;
var t = (entry.EndTime - userTimeSeconds) / fadeOutTime;
return CubismMath.GetEasingSine(t);
}
///
/// Sets the queue entry's EndTime based on loop state and duration.
/// Looping motions get EndTime = -1 (infinite).
///
protected void UpdateEndTime(CubismMotionQueueEntry entry, float duration)
{
entry.EndTime = _isLoop ? -1f : entry.StartTime + duration;
}
///
/// Recalculates EndTime when changes mid-playback.
/// Called by every frame
/// to detect loop state transitions.
///
protected void AdjustEndTime(CubismMotionQueueEntry entry, float duration)
{
if (_isLoop != _previousLoopState)
{
_previousLoopState = _isLoop;
UpdateEndTime(entry, duration);
}
}
///
/// Applies motion parameter values to the model for the current frame.
/// Called every frame by .
///
/// The native Cubism model to modify.
/// Elapsed user time in seconds.
/// Blend weight already multiplied by fade factors.
/// The queue entry containing start/end timing.
public abstract void DoUpdateParameters(
CubismNativeModel model,
float userTimeSeconds,
float weight,
CubismMotionQueueEntry entry);
}
}