using NUnit.Framework; using Streamingle.Gaze.Editor; using UnityEngine; namespace Streamingle.Gaze.Tests { public sealed class GazeCalibrationPreviewSessionTests { [Test] public void Session_UsesRuntimeBlendSemantics_BlocksDriverAndRestoresWeights() { var root = new GameObject("CalibrationPreviewSessionTest"); var mesh = CreateMesh(); var profile = ScriptableObject.CreateInstance(); var session = new GazeCalibrationPreviewSession(); try { var renderer = root.AddComponent(); renderer.sharedMesh = mesh; renderer.SetBlendShapeWeight(0, 25f); renderer.SetBlendShapeWeight(1, 30f); renderer.SetBlendShapeWeight(2, 45f); renderer.SetBlendShapeWeight(3, 55f); profile.ReplaceChannels(new[] { new GazeBlendShapeChannel("eyeLookUpLeft"), new GazeBlendShapeChannel( "eyeCorrectiveAdd", GazeChannelUsage.Corrective, GazeCorrectiveBlendMode.AdditiveFromCenter), new GazeBlendShapeChannel( "eyeCorrectiveOverride", GazeChannelUsage.Corrective, GazeCorrectiveBlendMode.Override) }); for (var index = 0; index < 9; index++) { profile.SetSample( (GazeCalibrationPoint)index, new[] { index * 10f, 10f + index * 2f, 100f - index * 5f }); } var driver = root.AddComponent(); driver.TargetRenderer = renderer; driver.Profile = profile; var gazeOrigin = new GameObject("GazeOrigin").transform; gazeOrigin.SetParent(root.transform, false); driver.GazeOrigin = gazeOrigin; Assert.That(session.Start(driver, out var error), Is.True, error); Assert.That(session.GetOriginalWeightsCopy(), Is.EqualTo(new[] { 25f, 30f, 45f })); session.PreviewSample(GazeCalibrationPoint.UpRight); Assert.That(renderer.GetBlendShapeWeight(0), Is.EqualTo(80f).Within(0.001f)); Assert.That(renderer.GetBlendShapeWeight(1), Is.EqualTo(38f).Within(0.001f)); Assert.That(renderer.GetBlendShapeWeight(2), Is.EqualTo(60f).Within(0.001f)); Assert.That(renderer.GetBlendShapeWeight(3), Is.EqualTo(55f).Within(0.001f)); session.PreviewWeights(new[] { 67f, 23f, 72f }, GazeCalibrationPoint.CenterRight); Assert.That(session.IsFreePreview, Is.False); Assert.That(session.PreviewCoordinates, Is.EqualTo(Vector2.right)); Assert.That(renderer.GetBlendShapeWeight(0), Is.EqualTo(67f).Within(0.001f)); Assert.That(renderer.GetBlendShapeWeight(1), Is.EqualTo(35f).Within(0.001f)); Assert.That(renderer.GetBlendShapeWeight(2), Is.EqualTo(72f).Within(0.001f)); // The four surrounding samples are deliberately distinct. At // (0.5, -0.5), indices 1/2/4/5 contribute equally. session.PreviewNormalized(new Vector2(0.5f, -0.5f)); Assert.That(session.IsFreePreview, Is.True); Assert.That(renderer.GetBlendShapeWeight(0), Is.EqualTo(30f).Within(0.001f)); Assert.That(renderer.GetBlendShapeWeight(1), Is.EqualTo(28f).Within(0.001f)); Assert.That(renderer.GetBlendShapeWeight(2), Is.EqualTo(85f).Within(0.001f)); driver.SetTimelineState(1234, new GazeTimelineState { TargetPosition = new Vector3(1f, 0f, 1f), HasTargetPosition = true, Influence = 1f, EnableCorrectives = true }); driver.EvaluateNow(true); Assert.That(renderer.GetBlendShapeWeight(0), Is.EqualTo(30f).Within(0.001f)); Assert.That(renderer.GetBlendShapeWeight(1), Is.EqualTo(28f).Within(0.001f)); Assert.That(renderer.GetBlendShapeWeight(2), Is.EqualTo(85f).Within(0.001f)); session.Stop(); Assert.That(renderer.GetBlendShapeWeight(0), Is.EqualTo(25f).Within(0.001f)); Assert.That(renderer.GetBlendShapeWeight(1), Is.EqualTo(30f).Within(0.001f)); Assert.That(renderer.GetBlendShapeWeight(2), Is.EqualTo(45f).Within(0.001f)); Assert.That(renderer.GetBlendShapeWeight(3), Is.EqualTo(55f).Within(0.001f)); // Releasing the session also releases the driver override. A yaw // beyond the profile limit resolves to CenterRight (sample 5). driver.EvaluateNow(true); Assert.That(renderer.GetBlendShapeWeight(0), Is.EqualTo(50f).Within(0.001f)); Assert.That(renderer.GetBlendShapeWeight(1), Is.EqualTo(32f).Within(0.001f)); Assert.That(renderer.GetBlendShapeWeight(2), Is.EqualTo(75f).Within(0.001f)); } finally { session.Dispose(); Object.DestroyImmediate(root); Object.DestroyImmediate(mesh); Object.DestroyImmediate(profile); } } [Test] public void Session_DoesNotRestoreIntoAnIndexWhoseResolvedNameChanged() { var root = new GameObject("CalibrationPreviewResolvedNameTest"); var mesh = CreateMesh(); var profile = ScriptableObject.CreateInstance(); var session = new GazeCalibrationPreviewSession(); try { var renderer = root.AddComponent(); renderer.sharedMesh = mesh; renderer.SetBlendShapeWeight(0, 25f); profile.ReplaceChannels(new[] { new GazeBlendShapeChannel("eyeLookUpLeft") }); for (var index = 0; index < 9; index++) profile.SetSample((GazeCalibrationPoint)index, new[] { 80f }); var driver = root.AddComponent(); driver.TargetRenderer = renderer; driver.Profile = profile; Assert.That(session.Start(driver, out var error), Is.True, error); session.PreviewSample(GazeCalibrationPoint.Center); Assert.That(renderer.GetBlendShapeWeight(0), Is.EqualTo(80f).Within(0.001f)); mesh.ClearBlendShapes(); AddBlendShape(mesh, "replacementShape"); renderer.SetBlendShapeWeight(0, 66f); session.Stop(); Assert.That(renderer.GetBlendShapeWeight(0), Is.EqualTo(66f).Within(0.001f)); } finally { session.Dispose(); Object.DestroyImmediate(root); Object.DestroyImmediate(mesh); Object.DestroyImmediate(profile); } } [Test] public void Session_AdditiveDraftUsesOriginalWeightUntilCenterIsCaptured() { var root = new GameObject("CalibrationPreviewDraftCorrectiveTest"); var mesh = CreateMesh(); var profile = ScriptableObject.CreateInstance(); var session = new GazeCalibrationPreviewSession(); try { var renderer = root.AddComponent(); renderer.sharedMesh = mesh; renderer.SetBlendShapeWeight(1, 30f); profile.ReplaceChannels(new[] { new GazeBlendShapeChannel( "eyeCorrectiveAdd", GazeChannelUsage.Corrective, GazeCorrectiveBlendMode.AdditiveFromCenter) }); var driver = root.AddComponent(); driver.TargetRenderer = renderer; driver.Profile = profile; Assert.That(session.Start(driver, out var error), Is.True, error); session.PreviewWeights(new[] { 42f }, GazeCalibrationPoint.UpRight); Assert.That(renderer.GetBlendShapeWeight(1), Is.EqualTo(42f).Within(0.001f)); session.Stop(); Assert.That(renderer.GetBlendShapeWeight(1), Is.EqualTo(30f).Within(0.001f)); } finally { session.Dispose(); Object.DestroyImmediate(root); Object.DestroyImmediate(mesh); Object.DestroyImmediate(profile); } } private static Mesh CreateMesh() { var mesh = new Mesh { name = "CalibrationPreviewSessionMesh" }; 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 }; AddBlendShape(mesh, "Face.eyeLookUpLeft"); AddBlendShape(mesh, "Face.eyeCorrectiveAdd"); AddBlendShape(mesh, "Face.eyeCorrectiveOverride"); AddBlendShape(mesh, "unrelatedFaceShape"); return mesh; } private static void AddBlendShape(Mesh mesh, string name) { mesh.AddBlendShapeFrame( name, 100f, new[] { Vector3.up * 0.01f, Vector3.up * 0.01f, Vector3.up * 0.01f }, new Vector3[3], new Vector3[3]); } } }