using System; using System.Collections.Generic; using NUnit.Framework; using UnityEngine; namespace Streamingle.Gaze.Tests { public sealed class GazeDirectionSolverTests { [Test] public void Calculate_ClampsAndNormalizesAsymmetricRanges() { var result = GazeDirectionSolver.Calculate( Quaternion.identity, GazeDirectionSolver.DirectionFromAngles(60f, 30f), -20f, 40f, -10f, 20f); Assert.That(result.RawYaw, Is.EqualTo(60f).Within(0.01f)); Assert.That(result.RawPitch, Is.EqualTo(30f).Within(0.01f)); Assert.That(result.ClampedYaw, Is.EqualTo(40f).Within(0.01f)); Assert.That(result.ClampedPitch, Is.EqualTo(20f).Within(0.01f)); Assert.That(result.NormalizedYaw, Is.EqualTo(1f).Within(0.001f)); Assert.That(result.NormalizedPitch, Is.EqualTo(1f).Within(0.001f)); Assert.That(result.WasClamped, Is.True); } [Test] public void Calculate_UsesCharacterLocalReference() { var reference = Quaternion.Euler(5f, 73f, 0f); var localDirection = GazeDirectionSolver.DirectionFromAngles(-10f, -5f); var result = GazeDirectionSolver.Calculate( reference, reference * localDirection, -20f, 40f, -10f, 20f); Assert.That(result.RawYaw, Is.EqualTo(-10f).Within(0.01f)); Assert.That(result.RawPitch, Is.EqualTo(-5f).Within(0.01f)); Assert.That(result.NormalizedYaw, Is.EqualTo(-0.5f).Within(0.001f)); Assert.That(result.NormalizedPitch, Is.EqualTo(-0.5f).Within(0.001f)); Assert.That(result.WasClamped, Is.False); } [Test] public void Profile_BilinearlyInterpolatesTheThreeByThreeGrid() { var profile = ScriptableObject.CreateInstance(); try { profile.ReplaceChannels(new[] { new GazeBlendShapeChannel("eyeLookUpLeft") }); for (var index = 0; index < 9; index++) profile.SetSample((GazeCalibrationPoint)index, new[] { (float)index }); var output = new float[1]; var direction = DirectionWithNormalizedCoordinates(-0.5f, -0.5f); Assert.That(profile.TryEvaluate(direction, false, output), Is.True); // Bottom-left/center cell: lerp of sample values 0,1,3,4. Assert.That(output[0], Is.EqualTo(2f).Within(0.001f)); direction = DirectionWithNormalizedCoordinates(0.5f, 0.5f); Assert.That(profile.TryEvaluate(direction, false, output), Is.True); // Center/top-right cell: lerp of sample values 4,5,7,8. Assert.That(output[0], Is.EqualTo(6f).Within(0.001f)); } finally { UnityEngine.Object.DestroyImmediate(profile); } } [Test] public void Profile_ExcludesCorrectiveChannelsUnlessRequested() { var profile = ScriptableObject.CreateInstance(); try { profile.ReplaceChannels(new List { new GazeBlendShapeChannel("eyeLookUpLeft"), new GazeBlendShapeChannel("eyeBlinkLeft", GazeChannelUsage.Corrective) }); for (var index = 0; index < 9; index++) profile.SetSample((GazeCalibrationPoint)index, new[] { 10f, 20f }); var output = new float[2]; Assert.That(profile.TryEvaluate(DirectionWithNormalizedCoordinates(0f, 0f), false, output), Is.True); Assert.That(output[0], Is.EqualTo(10f)); Assert.That(float.IsNaN(output[1]), Is.True); Assert.That(profile.TryEvaluate(DirectionWithNormalizedCoordinates(0f, 0f), true, output), Is.True); Assert.That(output[1], Is.EqualTo(20f)); } finally { UnityEngine.Object.DestroyImmediate(profile); } } [Test] public void Profile_DoesNotEvaluateAnIncompleteCalibration() { var profile = ScriptableObject.CreateInstance(); try { profile.ReplaceChannels(new[] { new GazeBlendShapeChannel("eyeLookUpLeft") }); profile.SetSample(GazeCalibrationPoint.Center, new[] { 50f }); Assert.That( profile.TryEvaluate( DirectionWithNormalizedCoordinates(0f, 0f), false, new float[1]), Is.False); } finally { UnityEngine.Object.DestroyImmediate(profile); } } private static GazeDirectionResult DirectionWithNormalizedCoordinates(float yaw, float pitch) { return new GazeDirectionResult( Vector3.forward, Vector3.forward, 0f, 0f, 0f, 0f, yaw, pitch); } } }