streamingle-unity-utilities/Tests/Editor/Gaze/GazeCalibrationWindowTests.cs

251 lines
10 KiB
C#

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<BlendshapeGazeProfile>();
var window = ScriptableObject.CreateInstance<GazeCalibrationWindow>();
try
{
head.transform.SetParent(root.transform, false);
var renderer = root.AddComponent<SkinnedMeshRenderer>();
renderer.sharedMesh = mesh;
renderer.bones = new[] { head.transform };
profile.ReplaceChannels(new[]
{
new GazeBlendShapeChannel("eyeLookUpLeft")
});
var driver = root.AddComponent<BlendshapeGazeDriver>();
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<BlendshapeGazeProfile>();
var window = ScriptableObject.CreateInstance<GazeCalibrationWindow>();
try
{
head.transform.SetParent(root.transform, false);
existingOrigin.transform.SetParent(head.transform, false);
var renderer = root.AddComponent<SkinnedMeshRenderer>();
renderer.sharedMesh = mesh;
renderer.bones = new[] { head.transform };
profile.ReplaceChannels(new[]
{
new GazeBlendShapeChannel("eyeLookUpLeft")
});
var driver = root.AddComponent<BlendshapeGazeDriver>();
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<BlendshapeGazeProfile>();
var window = ScriptableObject.CreateInstance<GazeCalibrationWindow>();
try
{
var renderer = root.AddComponent<SkinnedMeshRenderer>();
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<BlendshapeGazeDriver>();
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<GazeCalibrationPreviewSession>(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<BlendshapeGazeProfile>();
var window = ScriptableObject.CreateInstance<GazeCalibrationWindow>();
try
{
var renderer = root.AddComponent<SkinnedMeshRenderer>();
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<BlendshapeGazeDriver>();
driver.TargetRenderer = renderer;
driver.Profile = profile;
driver.GazeOrigin = origin;
Invoke(window, "SetDriver", driver);
Invoke(window, "StartCalibration");
var session = GetPrivateField<GazeCalibrationPreviewSession>(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<T>(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;
}
}
}