Skip to content
Open
21 changes: 21 additions & 0 deletions Basis/Packages/com.basis.examples/Scripts/BasisSDKMirror.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ public Color ClearColor
}

private BasisMeshRendererCheck basisMeshRendererCheck;
private BasisGazeTarget gazeTarget;
private Vector3 thisPosition;
private Vector3 normal;
private readonly Vector3 projectionDirection = -Vector3.forward;
Expand Down Expand Up @@ -195,6 +196,9 @@ private void CleanUp()
PortalTextureRight = null;
LeftCamera = RightCamera = null;

if (gazeTarget != null)
gazeTarget.enabled = false;

IsActive = false;
IsAbleToRender = false;
InsideRendering = false;
Expand All @@ -217,6 +221,13 @@ private void Initialize()
IsAbleToRender = Renderer.isVisible;
IsActive = true;
InsideRendering = false;

// Set up gaze target so the eye driver focuses on the player's reflection
if (gazeTarget == null)
gazeTarget = BasisHelpers.GetOrAddComponent<BasisGazeTarget>(gameObject);
gazeTarget.Priority = 2f;
gazeTarget.UseTransformPosition = false;
gazeTarget.enabled = true;
}
private static Vector3 TransformPoint(Vector3 position, Quaternion rotation, Vector3 pointLocal)
{
Expand Down Expand Up @@ -248,6 +259,16 @@ private void OnBeforeRender()
thisPosition = Renderer.transform.position;
normal = Renderer.transform.TransformDirection(projectionDirection).normalized;

// Update gaze target: reflect the player's eye position across the mirror plane
if (gazeTarget != null)
{
Vector3 eyePos = BasisLocalCameraDriver.Position;
transform.GetPositionAndRotation(out Vector3 planePosWS, out Quaternion planeRotWS);
Vector3 eyeLocal = InverseTransformPoint(planePosWS, planeRotWS, eyePos);
Vector3 reflLocal = Vector3.Reflect(eyeLocal, Vector3.forward);
gazeTarget.FocusPoint = TransformPoint(planePosWS, planeRotWS, reflLocal);
}

RenderBothEyes(cam);

OnCamerasFinished?.Invoke();
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Collections.Generic;
using Unity.Mathematics;
using UnityEngine;

/// <summary>
/// Attach to any object that should attract eye gaze (mirrors, cameras, signage, etc.).
/// The local eye driver scores all active targets + focuses on the best one.
///
/// For dynamic focus points (e.g., mirror reflections), set <see cref="UseTransformPosition"/>
/// to false and update <see cref="FocusPoint"/> from your own script each frame.
/// </summary>
public class BasisGazeTarget : MonoBehaviour
{
[Tooltip("Higher priority targets win when competing at similar scores. Players default to 1.")]
public float Priority = 1f;

[Tooltip("World-space focus point. Ignored when UseTransformPosition is true.")]
public Vector3 FocusPoint;

[Tooltip("If true, focus point tracks transform.position each frame.")]
public bool UseTransformPosition = true;

/// <summary>All currently active gaze targets.</summary>
public static readonly List<BasisGazeTarget> ActiveTargets = new List<BasisGazeTarget>();

void OnEnable() => ActiveTargets.Add(this);
void OnDisable() => ActiveTargets.Remove(this);

public float3 GetWorldFocusPoint()
{
return UseTransformPosition ? (float3)transform.position : (float3)FocusPoint;
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ public void InitialLocalCalibration(BasisLocalPlayer player)
Calibration(player);

player.LocalBoneDriver.RemoveAllListeners();
BasisLocalEyeDriver.Liveliness = player.BasisAvatar.EyeLiveliness;
BasisLocalEyeDriver.Attentiveness = player.BasisAvatar.EyeAttentiveness;
BasisDebug.Log($"Eye Personality - Liveliness: {BasisLocalEyeDriver.Liveliness:F1} | Attentiveness: {BasisLocalEyeDriver.Attentiveness:F1}", BasisDebug.LogTag.Avatar);
BasisLocalEyeDriver.Initalize();
LocalRenderMeshSettings(BasisLayerMapper.LocalAvatarLayer, SkinnedMeshRendererLength, SkinnedMeshRenderer, player.BasisAvatar.FaceVisemeMesh);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,20 @@
public struct BasisEyeJob : IJob
{
public float dt;
public float maxAngleRad;
public float holdMin, holdMax;
public float maxAngleDeg;
public float saccadeMin, saccadeMax;
public float centerBias;
public float perEyeVarRad;
public bool occasionalCenterReturn;
public quaternion calLeftBasis, calLeftInvBasis;
public quaternion calRightBasis, calRightInvBasis;
public float perEyeVarDeg;

public BasisEyePersonality personality;
public BasisEyeCalibration calLeft, calRight;

public float2 headDeltaYP;

public bool hasGazeTarget;
public float2 gazeLeftEye, gazeRightEye, gazeMouth;
public float gazeMouthScale;
public bool gazeTargetChanged;

public NativeArray<BasisEyeState> state;

public void Execute()
Expand All @@ -22,14 +28,16 @@ public void Execute()

s.Update(
dt,
math.radians(maxAngleRad),
holdMin, holdMax,
headDeltaYP,
math.radians(maxAngleDeg),
saccadeMin, saccadeMax,
centerBias,
math.radians(perEyeVarRad),
occasionalCenterReturn,
calLeftBasis, calLeftInvBasis,
calRightBasis, calRightInvBasis
math.radians(perEyeVarDeg),
personality,
calLeft, calRight,
hasGazeTarget,
gazeLeftEye, gazeRightEye, gazeMouth,
gazeMouthScale,
gazeTargetChanged
);

state[0] = s;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Unity.Mathematics;

/// <summary>
/// Cached personality parameters derived from Liveliness and Attentiveness.
/// Liveliness controls saccade frequency and amplitude (low = settled, high = active).
/// Attentiveness controls eye contact commitment (low = avoidant, high = direct sustained gaze).
/// </summary>
public struct BasisEyePersonality
{
// === Liveliness === (saccade frequency + amplitude)
public float holdMin, holdMax;
public float centerBias;
public float centerReturnChance;
public float maxFocusedJitterRad;

// // === Attentiveness === (eye contact commitment)
public float holdScaleAtFullGaze;
public float gazeBlendInSpeed, gazeBlendOutSpeed;
public float socialHoldScale;
// Gaze disengagement break timing (low attentiveness = frequent breaks)
public float gazeBreakMin, gazeBreakMax;
public float avertedMin, avertedMax;
// Reaction delay to new targets
public float reactionMin, reactionMax;

public static BasisEyePersonality Compute(float liveliness, float attentiveness)
{
float L = liveliness;
float A = attentiveness;
return new BasisEyePersonality
{
holdMin = math.lerp(1.2f, 0.25f, L),
holdMax = math.lerp(6.0f, 1.5f, L),
centerBias = math.lerp(4.0f, 1.2f, L),
centerReturnChance = math.lerp(0.30f, 0.05f, L),
maxFocusedJitterRad = math.radians(math.lerp(0.15f, 1.0f, L)),

holdScaleAtFullGaze = math.lerp(0.3f, 2.0f, A),
gazeBlendInSpeed = math.lerp(1.5f, 8.0f, A),
gazeBlendOutSpeed = math.lerp(4.0f, 0.5f, A),
socialHoldScale = math.lerp(0.3f, 1.5f, A),
gazeBreakMin = math.lerp(1.0f, 30.0f, A),
gazeBreakMax = math.lerp(3.0f, 60.0f, A),
avertedMin = math.lerp(0.8f, 0.1f, A),
avertedMax = math.lerp(2.0f, 0.3f, A),
reactionMin = math.lerp(0.30f, 0.08f, A),
reactionMax = math.lerp(0.60f, 0.15f, A),
};
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading