Files
RoomAwareVR/Assets/_Scripts/Eye-Tracking/FieldOfView.cs
T
2025-07-20 05:35:05 +02:00

209 lines
6.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using UnityEngine;
public class FieldOfView : MonoBehaviour
{
public float viewRadius;
[Range(0,360)]
public float periAngle;
[Range(0,180)]
public float foveaAngle;
public Transform eyeLeft;
public Transform eyeRight;
public Transform eyeCenter;
public LayerMask targetMask;
public LayerMask obstacleMask;
public float meshResolution;
public int edgeResolveIterations;
public float edgeDistanceThreshold;
public MeshFilter periMeshFilter;
public MeshFilter foveaMeshFilter;
private Mesh periMesh;
private Mesh foveaMesh;
private void Start()
{
periMesh = new Mesh();
periMesh.name = "View Mesh";
periMeshFilter.mesh = periMesh;
foveaMesh = new Mesh();
foveaMesh.name = "View Mesh";
foveaMeshFilter.mesh = foveaMesh;
}
private void LateUpdate()
{
Vector3 gazeDirection = ((eyeLeft.forward + eyeRight.forward) * 0.5f).normalized;
eyeCenter.rotation = Quaternion.LookRotation(gazeDirection);
float gazeAngle = AngleFromDir(gazeDirection, true);
DrawFOV(transform.eulerAngles.y, periMesh, periAngle);
DrawFOV(gazeAngle, foveaMesh, foveaAngle);
}
public Vector3 DirFromAngle(float angleDeg, bool angleIsGlobal)
{
if (!angleIsGlobal)
{
angleDeg += transform.eulerAngles.y;
}
return new Vector3(Mathf.Sin(angleDeg * Mathf.Deg2Rad), 0, Mathf.Cos(angleDeg * Mathf.Deg2Rad));
}
public float AngleFromDir(Vector3 dir, bool angleIsGlobal)
{
// Sicherheitsnetz: yKomponente ignorieren und Vektor normalisieren
dir.y = 0;
if (dir.sqrMagnitude < 0.0001f) return 0f;
// Atan2: (x, z) → Winkel in Rad, dann nach ° umrechnen
float angleDeg = Mathf.Atan2(dir.x, dir.z) * Mathf.Rad2Deg;
// Bereich schön sauber auf 0360 bringen
angleDeg = (angleDeg + 360f) % 360f;
// Soll der Winkel relativ zum Objekt sein?
if (!angleIsGlobal)
angleDeg -= transform.eulerAngles.y;
// Und noch einmal normalisieren, damit auch hier 0360 garantiert ist
return (angleDeg + 360f) % 360f;
}
void DrawFOV(float startAngle, Mesh viewMesh, float viewAngle)
{
int stepCount = Mathf.RoundToInt(viewAngle * meshResolution);
float stepAngleSize = viewAngle / stepCount;
List<Vector3> viewPoints = new List<Vector3>();
ViewCastInfo oldViewCast = new ViewCastInfo();
for (int i = 0; i <= stepCount; i++)
{
float angle = startAngle - viewAngle / 2 + stepAngleSize * i;
Debug.DrawLine(transform.position, transform.position + DirFromAngle(angle, true) * viewRadius, Color.red);
ViewCastInfo newViewCast = ViewCast(angle);
if (i > 0)
{
bool edgeDistThresholdExeeded = Mathf.Abs(oldViewCast.distance - newViewCast.distance) > edgeDistanceThreshold;
if (oldViewCast.didHit != newViewCast.didHit || (oldViewCast.didHit && newViewCast.didHit && edgeDistThresholdExeeded))
{
EdgeInfo edge = FingEdge(oldViewCast, newViewCast);
if (edge.pointmin != Vector3.zero)
{
viewPoints.Add(edge.pointmin);
}
if (edge.pointmax != Vector3.zero)
{
viewPoints.Add(edge.pointmax);
}
}
}
viewPoints.Add(newViewCast.hitPoint);
oldViewCast = newViewCast;
}
int vertexcount = viewPoints.Count + 1;
Vector3[] vertices = new Vector3[vertexcount];
int[] triangles = new int[(vertexcount-2) * 3];
vertices[0] = Vector3.zero;
for (int i = 0; i < vertexcount-1; i++)
{
vertices[i+1] = transform.InverseTransformPoint(viewPoints[i]);
if (i < vertexcount - 2)
{
triangles[i * 3] = 0;
triangles[i * 3 + 1] = i + 1;
triangles[i * 3 + 2] = i + 2;
}
}
viewMesh.Clear();
viewMesh.vertices = vertices;
viewMesh.triangles = triangles;
viewMesh.RecalculateNormals();
}
ViewCastInfo ViewCast(float globalAngle)
{
Vector3 direction = DirFromAngle(globalAngle, true);
RaycastHit hit;
if (Physics.Raycast(transform.position, direction, out hit, viewRadius, obstacleMask))
{
return new ViewCastInfo(true, hit.point, hit.distance, globalAngle);
}
else
{
return new ViewCastInfo(false, transform.position + direction * viewRadius, viewRadius, globalAngle);
}
}
public struct ViewCastInfo
{
public bool didHit;
public Vector3 hitPoint;
public float distance;
public float angle;
public ViewCastInfo(bool _hit, Vector3 _hitPoint, float _distance, float _angle)
{
didHit = _hit;
hitPoint = _hitPoint;
distance = _distance;
angle = _angle;
}
}
public struct EdgeInfo
{
public Vector3 pointmin;
public Vector3 pointmax;
public EdgeInfo(Vector3 _pointmin, Vector3 _pointmax)
{
pointmin = _pointmin;
pointmax = _pointmax;
}
}
EdgeInfo FingEdge(ViewCastInfo minViewCast, ViewCastInfo maxViewCast)
{
float minAngle = minViewCast.angle;
float maxAngle = maxViewCast.angle;
Vector3 minPoint = Vector3.zero;
Vector3 maxPoint = Vector3.zero;
for (int i = 0; i < edgeResolveIterations; i++)
{
float angle = (minAngle + maxAngle) / 2;
ViewCastInfo newViewCast = ViewCast(angle);
bool edgeDistThresholdExeeded = Mathf.Abs(minViewCast.distance - newViewCast.distance) > edgeDistanceThreshold;
if (newViewCast.didHit == minViewCast.didHit && !edgeDistThresholdExeeded)
{
minAngle = angle;
minPoint = newViewCast.hitPoint;
}
else
{
maxAngle = angle;
maxPoint = newViewCast.hitPoint;
}
}
return new EdgeInfo(minPoint, maxPoint);
}
}