105 lines
3.3 KiB
C#

using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;
namespace Streamingle.Gaze
{
/// <summary>
/// Describes a camera/target gaze request on a Timeline track.
/// The track mixer combines requests and submits the result to a
/// <see cref="BlendshapeGazeDriver"/>; clips never write blendshapes directly.
/// </summary>
public sealed class BlendshapeGazeClip : PlayableAsset, ITimelineClipAsset
{
[SerializeField]
[Tooltip("Optional world-space target. When empty, the Driver Target Camera is used before Camera.main.")]
private ExposedReference<Transform> target;
[SerializeField, Range(0f, 1f)]
[Tooltip("Maximum gaze influence contributed by this clip.")]
private float strength = 1f;
[SerializeField]
[Tooltip("Allow optional eyelid/corrective channels from the calibration profile.")]
private bool enableCorrectives;
[SerializeField]
[Tooltip("Override the bound driver's camera-cut response while this clip is dominant.")]
private bool overrideCutResponse;
[SerializeField]
private GazeCutResponse cutResponse;
[SerializeField, Min(0f)]
[Tooltip("Transition duration used by cut-response modes that smooth between cameras.")]
private float cutTransitionDuration = 0.1f;
public ExposedReference<Transform> Target
{
get => target;
set => target = value;
}
public float Strength
{
get => strength;
set => strength = SanitizeStrength(value);
}
public bool EnableCorrectives
{
get => enableCorrectives;
set => enableCorrectives = value;
}
public bool OverrideCutResponse
{
get => overrideCutResponse;
set => overrideCutResponse = value;
}
public GazeCutResponse CutResponse
{
get => cutResponse;
set => cutResponse = value;
}
public float CutTransitionDuration
{
get => cutTransitionDuration;
set => cutTransitionDuration = SanitizeDuration(value);
}
public ClipCaps clipCaps => ClipCaps.Blending;
public override Playable CreatePlayable(PlayableGraph graph, GameObject owner)
{
var playable = ScriptPlayable<BlendshapeGazeBehaviour>.Create(graph);
var behaviour = playable.GetBehaviour();
behaviour.Target = target.Resolve(graph.GetResolver());
behaviour.Strength = SanitizeStrength(strength);
behaviour.EnableCorrectives = enableCorrectives;
behaviour.OverrideCutResponse = overrideCutResponse;
behaviour.CutResponse = cutResponse;
behaviour.CutTransitionDuration = SanitizeDuration(cutTransitionDuration);
return playable;
}
private static float SanitizeStrength(float value)
{
return float.IsNaN(value) || float.IsInfinity(value)
? 0f
: Mathf.Clamp01(value);
}
private static float SanitizeDuration(float value)
{
return float.IsNaN(value) || float.IsInfinity(value)
? 0f
: Mathf.Max(0f, value);
}
}
}