125 lines
4.7 KiB
C#
125 lines
4.7 KiB
C#
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
|
|
namespace Streamingle.Gaze
|
|
{
|
|
/// <summary>
|
|
/// Pure direction and 3x3 interpolation helpers. Kept independent of the
|
|
/// component so they can also be reused by a future real-time input source.
|
|
/// </summary>
|
|
public static class GazeDirectionSolver
|
|
{
|
|
private const float MinimumAngleRange = 0.001f;
|
|
|
|
public static GazeDirectionResult Calculate(
|
|
Quaternion referenceRotation,
|
|
Vector3 worldDirection,
|
|
float minYaw,
|
|
float maxYaw,
|
|
float minPitch,
|
|
float maxPitch)
|
|
{
|
|
if (worldDirection.sqrMagnitude <= Mathf.Epsilon)
|
|
worldDirection = referenceRotation * Vector3.forward;
|
|
else
|
|
worldDirection.Normalize();
|
|
|
|
var localDirection = Quaternion.Inverse(referenceRotation) * worldDirection;
|
|
if (localDirection.sqrMagnitude <= Mathf.Epsilon)
|
|
localDirection = Vector3.forward;
|
|
else
|
|
localDirection.Normalize();
|
|
|
|
var planarLength = Mathf.Sqrt(
|
|
localDirection.x * localDirection.x + localDirection.z * localDirection.z);
|
|
var rawYaw = Mathf.Atan2(localDirection.x, localDirection.z) * Mathf.Rad2Deg;
|
|
var rawPitch = Mathf.Atan2(localDirection.y, planarLength) * Mathf.Rad2Deg;
|
|
|
|
NormalizeRanges(ref minYaw, ref maxYaw, ref minPitch, ref maxPitch);
|
|
var clampedYaw = Mathf.Clamp(rawYaw, minYaw, maxYaw);
|
|
var clampedPitch = Mathf.Clamp(rawPitch, minPitch, maxPitch);
|
|
|
|
return new GazeDirectionResult(
|
|
worldDirection,
|
|
localDirection,
|
|
rawYaw,
|
|
rawPitch,
|
|
clampedYaw,
|
|
clampedPitch,
|
|
NormalizeAsymmetric(clampedYaw, minYaw, maxYaw),
|
|
NormalizeAsymmetric(clampedPitch, minPitch, maxPitch));
|
|
}
|
|
|
|
public static Vector3 DirectionFromAngles(float yaw, float pitch)
|
|
{
|
|
return Quaternion.Euler(-pitch, yaw, 0f) * Vector3.forward;
|
|
}
|
|
|
|
public static float NormalizeAsymmetric(float value, float negativeLimit, float positiveLimit)
|
|
{
|
|
if (value < 0f)
|
|
return Mathf.Clamp(value / Mathf.Max(MinimumAngleRange, -negativeLimit), -1f, 0f);
|
|
|
|
return Mathf.Clamp(value / Mathf.Max(MinimumAngleRange, positiveLimit), 0f, 1f);
|
|
}
|
|
|
|
public static void Evaluate3x3(
|
|
IReadOnlyList<GazeCalibrationSample> samples,
|
|
int channelCount,
|
|
float normalizedYaw,
|
|
float normalizedPitch,
|
|
float[] output)
|
|
{
|
|
if (output == null || output.Length < channelCount)
|
|
return;
|
|
|
|
normalizedYaw = Mathf.Clamp(normalizedYaw, -1f, 1f);
|
|
normalizedPitch = Mathf.Clamp(normalizedPitch, -1f, 1f);
|
|
|
|
var x0 = normalizedYaw < 0f ? 0 : 1;
|
|
var x1 = x0 + 1;
|
|
var y0 = normalizedPitch < 0f ? 0 : 1;
|
|
var y1 = y0 + 1;
|
|
var tx = normalizedYaw < 0f ? normalizedYaw + 1f : normalizedYaw;
|
|
var ty = normalizedPitch < 0f ? normalizedPitch + 1f : normalizedPitch;
|
|
|
|
var bottomLeft = GetSample(samples, y0 * 3 + x0);
|
|
var bottomRight = GetSample(samples, y0 * 3 + x1);
|
|
var topLeft = GetSample(samples, y1 * 3 + x0);
|
|
var topRight = GetSample(samples, y1 * 3 + x1);
|
|
|
|
for (var channel = 0; channel < channelCount; channel++)
|
|
{
|
|
var bottom = Mathf.Lerp(
|
|
bottomLeft != null ? bottomLeft.GetWeight(channel) : 0f,
|
|
bottomRight != null ? bottomRight.GetWeight(channel) : 0f,
|
|
tx);
|
|
var top = Mathf.Lerp(
|
|
topLeft != null ? topLeft.GetWeight(channel) : 0f,
|
|
topRight != null ? topRight.GetWeight(channel) : 0f,
|
|
tx);
|
|
output[channel] = Mathf.Lerp(bottom, top, ty);
|
|
}
|
|
}
|
|
|
|
private static GazeCalibrationSample GetSample(
|
|
IReadOnlyList<GazeCalibrationSample> samples,
|
|
int index)
|
|
{
|
|
return samples != null && index >= 0 && index < samples.Count ? samples[index] : null;
|
|
}
|
|
|
|
private static void NormalizeRanges(
|
|
ref float minYaw,
|
|
ref float maxYaw,
|
|
ref float minPitch,
|
|
ref float maxPitch)
|
|
{
|
|
minYaw = Mathf.Min(-MinimumAngleRange, minYaw);
|
|
maxYaw = Mathf.Max(MinimumAngleRange, maxYaw);
|
|
minPitch = Mathf.Min(-MinimumAngleRange, minPitch);
|
|
maxPitch = Mathf.Max(MinimumAngleRange, maxPitch);
|
|
}
|
|
}
|
|
}
|