using System.Reflection; using NUnit.Framework; using Streamingle.Gaze.Editor; using UnityEngine; namespace Streamingle.Gaze.Tests { public sealed class GazeCalibrationWindowTests { [Test] public void AutoCreateOrigin_ParentsFaceGazeOriginUnderRendererHeadBone() { var root = new GameObject("GazeOriginParentTest"); var head = new GameObject("Bip001 Head"); var mesh = CreateMesh(); var profile = ScriptableObject.CreateInstance(); var window = ScriptableObject.CreateInstance(); try { head.transform.SetParent(root.transform, false); var renderer = root.AddComponent(); renderer.sharedMesh = mesh; renderer.bones = new[] { head.transform }; profile.ReplaceChannels(new[] { new GazeBlendShapeChannel("eyeLookUpLeft") }); var driver = root.AddComponent(); driver.TargetRenderer = renderer; driver.Profile = profile; Invoke(window, "SetDriver", driver); Invoke(window, "AutoCreateOrRecalculateOrigin"); Assert.That(driver.GazeOrigin, Is.Not.Null); Assert.That(driver.GazeOrigin.name, Is.EqualTo("Face_GazeOrigin")); Assert.That(driver.GazeOrigin.parent, Is.EqualTo(head.transform)); } finally { if (window != null) Object.DestroyImmediate(window); Object.DestroyImmediate(root); Object.DestroyImmediate(mesh); Object.DestroyImmediate(profile); } } [Test] public void AutoCreateOrigin_ReusesExistingHeadChildWhenDriverReferenceIsMissing() { var root = new GameObject("GazeOriginReuseTest"); var head = new GameObject("Bip001 Head"); var existingOrigin = new GameObject("Face_GazeOrigin"); var mesh = CreateMesh(); var profile = ScriptableObject.CreateInstance(); var window = ScriptableObject.CreateInstance(); try { head.transform.SetParent(root.transform, false); existingOrigin.transform.SetParent(head.transform, false); var renderer = root.AddComponent(); renderer.sharedMesh = mesh; renderer.bones = new[] { head.transform }; profile.ReplaceChannels(new[] { new GazeBlendShapeChannel("eyeLookUpLeft") }); var driver = root.AddComponent(); driver.TargetRenderer = renderer; driver.Profile = profile; Invoke(window, "SetDriver", driver); Invoke(window, "AutoCreateOrRecalculateOrigin"); Assert.That(driver.GazeOrigin, Is.SameAs(existingOrigin.transform)); Assert.That(head.transform.childCount, Is.EqualTo(1)); } finally { if (window != null) Object.DestroyImmediate(window); Object.DestroyImmediate(root); Object.DestroyImmediate(mesh); Object.DestroyImmediate(profile); } } [Test] public void SelectingMissingPointsDoesNotCaptureAndCancelRevertsProfile() { var root = new GameObject("GazeCalibrationWindowTest"); var mesh = CreateMesh(); var profile = ScriptableObject.CreateInstance(); var window = ScriptableObject.CreateInstance(); try { var renderer = root.AddComponent(); renderer.sharedMesh = mesh; renderer.SetBlendShapeWeight(0, 12f); profile.ReplaceChannels(new[] { new GazeBlendShapeChannel("eyeLookUpLeft") }); var origin = new GameObject("GazeOrigin").transform; origin.SetParent(root.transform, false); var driver = root.AddComponent(); driver.TargetRenderer = renderer; driver.Profile = profile; driver.GazeOrigin = origin; Invoke(window, "SetDriver", driver); Invoke(window, "StartCalibration"); Invoke(window, "SelectPoint", GazeCalibrationPoint.CenterLeft); Assert.That(profile.IsSampleCaptured(GazeCalibrationPoint.Center), Is.False); Assert.That(profile.IsSampleCaptured(GazeCalibrationPoint.CenterLeft), Is.False); // Guided calibration edits an unsaved working copy. Cancel must // discard it without ever mutating the original profile asset. var session = GetPrivateField(window, "previewSession"); Assert.That(session, Is.Not.Null); Assert.That(session.Profile, Is.Not.SameAs(profile)); UnityEditor.Undo.RecordObject(session.Profile, "Test Gaze Calibration Edit"); session.Profile.SetSample(GazeCalibrationPoint.CenterLeft, new[] { 77f }); renderer.SetBlendShapeWeight(0, 77f); Assert.That(profile.IsSampleCaptured(GazeCalibrationPoint.CenterLeft), Is.False); // A mapping/play-mode/reload guard may stop the preview before the // window repaints. The stop callback must cancel immediately. session.Stop(); Invoke(window, "FinishCalibration", false, false); Assert.That(profile.IsSampleCaptured(GazeCalibrationPoint.CenterLeft), Is.False); Assert.That(renderer.GetBlendShapeWeight(0), Is.EqualTo(12f).Within(0.001f)); } finally { if (window != null) { Invoke(window, "FinishCalibration", false, false); Object.DestroyImmediate(window); } Object.DestroyImmediate(root); Object.DestroyImmediate(mesh); Object.DestroyImmediate(profile); } } [Test] public void ApplyCalibration_CommitsWorkingCopyAndRestoresRendererPose() { var root = new GameObject("GazeCalibrationApplyTest"); var mesh = CreateMesh(); var profile = ScriptableObject.CreateInstance(); var window = ScriptableObject.CreateInstance(); try { var renderer = root.AddComponent(); renderer.sharedMesh = mesh; renderer.SetBlendShapeWeight(0, 14f); profile.ReplaceChannels(new[] { new GazeBlendShapeChannel("eyeLookUpLeft") }); var origin = new GameObject("GazeOrigin").transform; origin.SetParent(root.transform, false); var driver = root.AddComponent(); driver.TargetRenderer = renderer; driver.Profile = profile; driver.GazeOrigin = origin; Invoke(window, "SetDriver", driver); Invoke(window, "StartCalibration"); var session = GetPrivateField(window, "previewSession"); Assert.That(session, Is.Not.Null); Assert.That(session.Profile, Is.Not.SameAs(profile)); UnityEditor.Undo.RecordObject(session.Profile, "Test Apply Gaze Calibration"); session.Profile.SetSample(GazeCalibrationPoint.Center, new[] { 66f }); session.PreviewSample(GazeCalibrationPoint.Center); Assert.That(profile.IsSampleCaptured(GazeCalibrationPoint.Center), Is.False); Invoke(window, "FinishCalibration", true, false); Assert.That(profile.IsSampleCaptured(GazeCalibrationPoint.Center), Is.True); Assert.That( profile.GetSampleWeight(GazeCalibrationPoint.Center, 0), Is.EqualTo(66f).Within(0.001f)); Assert.That(renderer.GetBlendShapeWeight(0), Is.EqualTo(14f).Within(0.001f)); } finally { if (window != null) { Invoke(window, "FinishCalibration", false, false); Object.DestroyImmediate(window); } Object.DestroyImmediate(root); Object.DestroyImmediate(mesh); Object.DestroyImmediate(profile); } } private static void Invoke(object target, string methodName, params object[] arguments) { var method = target.GetType().GetMethod( methodName, BindingFlags.Instance | BindingFlags.NonPublic); Assert.That(method, Is.Not.Null, $"Expected private method '{methodName}'."); method.Invoke(target, arguments); } private static T GetPrivateField(object target, string fieldName) where T : class { var field = target.GetType().GetField( fieldName, BindingFlags.Instance | BindingFlags.NonPublic); Assert.That(field, Is.Not.Null, $"Expected private field '{fieldName}'."); return field.GetValue(target) as T; } private static Mesh CreateMesh() { var mesh = new Mesh { name = "GazeCalibrationWindowMesh" }; mesh.vertices = new[] { new Vector3(-0.1f, 0f, 0f), new Vector3(0.1f, 0f, 0f), new Vector3(0f, 0.1f, 0f) }; mesh.triangles = new[] { 0, 1, 2 }; mesh.AddBlendShapeFrame( "eyeLookUpLeft", 100f, new[] { Vector3.up * 0.01f, Vector3.up * 0.01f, Vector3.up * 0.01f }, new Vector3[3], new Vector3[3]); return mesh; } } }