using System.Reflection; using NUnit.Framework; using UnityEngine; namespace Streamingle.Gaze.Tests { public sealed class GazeProfileAuthoringTests { private static readonly string[] StandardChannels = { "eyeLookUpLeft", "eyeLookUpRight", "eyeLookDownLeft", "eyeLookDownRight", "eyeLookInLeft", "eyeLookInRight", "eyeLookOutLeft", "eyeLookOutRight" }; [Test] public void SeedStandardEyeLook_CreatesFivePointsAndFourCorners() { var profile = CreateStandardProfile(); try { Assert.That(profile.SeedStandardEyeLook(80f), Is.True); for (var index = 0; index < 9; index++) Assert.That(profile.IsSampleCaptured((GazeCalibrationPoint)index), Is.True); Assert.That( profile.GetSampleWeightsCopy(GazeCalibrationPoint.Center), Is.EqualTo(new[] { 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f })); Assert.That( profile.GetSampleWeightsCopy(GazeCalibrationPoint.CenterLeft), Is.EqualTo(new[] { 0f, 0f, 0f, 0f, 0f, 80f, 80f, 0f })); Assert.That( profile.GetSampleWeightsCopy(GazeCalibrationPoint.UpRight), Is.EqualTo(new[] { 80f, 80f, 0f, 0f, 80f, 0f, 0f, 80f })); } finally { Object.DestroyImmediate(profile); } } [Test] public void GenerateCorners_CombinesCardinalDeltasAndClampsByUsage() { var profile = ScriptableObject.CreateInstance(); try { profile.ReplaceChannels(new[] { new GazeBlendShapeChannel("eyeLookUpLeft"), new GazeBlendShapeChannel("eyeLidCorrective", GazeChannelUsage.Corrective) }); profile.SetSample(GazeCalibrationPoint.Center, new[] { 80f, 0f }); profile.SetSample(GazeCalibrationPoint.CenterLeft, new[] { 100f, 100f }); profile.SetSample(GazeCalibrationPoint.CenterRight, new[] { 0f, -100f }); profile.SetSample(GazeCalibrationPoint.UpCenter, new[] { 100f, 100f }); profile.SetSample(GazeCalibrationPoint.DownCenter, new[] { 0f, -100f }); Assert.That(profile.CanGenerateCorners, Is.True); Assert.That(profile.GenerateCornersFromCardinals(), Is.True); Assert.That( profile.GetSampleWeightsCopy(GazeCalibrationPoint.UpLeft), Is.EqualTo(new[] { 100f, 100f })); Assert.That( profile.GetSampleWeightsCopy(GazeCalibrationPoint.DownRight), Is.EqualTo(new[] { 0f, -100f })); } finally { Object.DestroyImmediate(profile); } } [Test] public void GenerateCorners_UsesCardinalDeltasRelativeToCenter() { var profile = ScriptableObject.CreateInstance(); try { profile.ReplaceChannels(new[] { new GazeBlendShapeChannel("eyeLookTest") }); profile.SetSample(GazeCalibrationPoint.Center, new[] { 10f }); profile.SetSample(GazeCalibrationPoint.CenterLeft, new[] { 30f }); profile.SetSample(GazeCalibrationPoint.CenterRight, new[] { 10f }); profile.SetSample(GazeCalibrationPoint.UpCenter, new[] { 40f }); profile.SetSample(GazeCalibrationPoint.DownCenter, new[] { 10f }); Assert.That(profile.GenerateCornersFromCardinals(), Is.True); Assert.That( profile.GetSampleWeight(GazeCalibrationPoint.UpLeft, 0), Is.EqualTo(60f)); } finally { Object.DestroyImmediate(profile); } } [Test] public void DirectionLimits_SanitizeNonFiniteValuesAndClampToSupportedRanges() { var profile = ScriptableObject.CreateInstance(); try { profile.SetDirectionLimits( float.NaN, float.PositiveInfinity, float.NegativeInfinity, float.NaN); Assert.That(profile.MinYaw, Is.EqualTo(-35f)); Assert.That(profile.MaxYaw, Is.EqualTo(35f)); Assert.That(profile.MinPitch, Is.EqualTo(-25f)); Assert.That(profile.MaxPitch, Is.EqualTo(20f)); profile.SetDirectionLimits(-180f, -5f, 10f, 180f); Assert.That(profile.MinYaw, Is.EqualTo(-90f)); Assert.That(profile.MaxYaw, Is.EqualTo(0.1f)); Assert.That(profile.MinPitch, Is.EqualTo(-0.1f)); Assert.That(profile.MaxPitch, Is.EqualTo(90f)); SetPrivateField(profile, "minYaw", float.PositiveInfinity); SetPrivateField(profile, "maxYaw", float.NegativeInfinity); SetPrivateField(profile, "minPitch", float.NaN); SetPrivateField(profile, "maxPitch", float.NaN); InvokeOnValidate(profile); Assert.That(profile.MinYaw, Is.EqualTo(-35f)); Assert.That(profile.MaxYaw, Is.EqualTo(35f)); Assert.That(profile.MinPitch, Is.EqualTo(-25f)); Assert.That(profile.MaxPitch, Is.EqualTo(20f)); } finally { Object.DestroyImmediate(profile); } } [Test] public void CopySample_WithMirrorSwapsLeftAndRightChannels() { var profile = CreateStandardProfile(); try { profile.SetSample( GazeCalibrationPoint.CenterLeft, new[] { 1f, 2f, 3f, 4f, 5f, 6f, 7f, 8f }); Assert.That( profile.CopySample( GazeCalibrationPoint.CenterLeft, GazeCalibrationPoint.CenterRight, true), Is.True); Assert.That( profile.GetSampleWeightsCopy(GazeCalibrationPoint.CenterRight), Is.EqualTo(new[] { 2f, 1f, 4f, 3f, 6f, 5f, 8f, 7f })); } finally { Object.DestroyImmediate(profile); } } [Test] public void SetSample_RejectsNonFiniteWeights() { var profile = ScriptableObject.CreateInstance(); try { profile.ReplaceChannels(new[] { new GazeBlendShapeChannel("eyeLookUpLeft") }); Assert.Throws(() => profile.SetSample(GazeCalibrationPoint.Center, new[] { float.NaN })); Assert.Throws(() => profile.SetSample(GazeCalibrationPoint.Center, new[] { float.PositiveInfinity })); Assert.That(profile.IsSampleCaptured(GazeCalibrationPoint.Center), Is.False); } finally { Object.DestroyImmediate(profile); } } private static BlendshapeGazeProfile CreateStandardProfile() { var profile = ScriptableObject.CreateInstance(); var channels = new GazeBlendShapeChannel[StandardChannels.Length]; for (var index = 0; index < channels.Length; index++) channels[index] = new GazeBlendShapeChannel(StandardChannels[index]); profile.ReplaceChannels(channels); return profile; } private static void SetPrivateField( BlendshapeGazeProfile profile, string fieldName, float value) { var field = typeof(BlendshapeGazeProfile).GetField( fieldName, BindingFlags.Instance | BindingFlags.NonPublic); Assert.That(field, Is.Not.Null, $"Expected private field '{fieldName}'."); field.SetValue(profile, value); } private static void InvokeOnValidate(BlendshapeGazeProfile profile) { var method = typeof(BlendshapeGazeProfile).GetMethod( "OnValidate", BindingFlags.Instance | BindingFlags.NonPublic); Assert.That(method, Is.Not.Null, "Expected private OnValidate method."); method.Invoke(profile, null); } } }