using UnityEngine; using UnityEngine.Playables; using UnityEngine.Timeline; namespace Streamingle.Gaze { /// /// Describes a camera/target gaze request on a Timeline track. /// The track mixer combines requests and submits the result to a /// ; clips never write blendshapes directly. /// 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 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 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.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); } } }