Files
2025-10-02 00:07:11 +02:00

211 lines
7.6 KiB
C#
Raw Permalink 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;
// Dieses Script ist für die Darstellung des fovealen und peripheren Sichtfelds im Replay zuständig.
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() // zum Start werden zwei neune Meshes generiert, um die Sichtfelder darstellen zu können.
{
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; // In jedem Frame wird zuerst die gemeinsame Blickrichtung der beiden Augen ermittelt.
eyeCenter.rotation = Quaternion.LookRotation(gazeDirection);
float gazeAngle = AngleFromDir(gazeDirection, true);
DrawFOV(transform.eulerAngles.y, periMesh, periAngle); // Das Mesh für das periphere Sichtfeld wird im angegebenen Winkel um die Vorwärts-Achse generiert.
DrawFOV(gazeAngle, foveaMesh, foveaAngle); // Das Mesh für das foveale Sichtfeld wird im angegebenen Winkel um die Sichtlinie herum generiert.
}
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) // Die Funktion, die das eigentliche 3D-Objekt/Mesh generiert.
{
int stepCount = Mathf.RoundToInt(viewAngle * meshResolution); // Es werden mit der angegebenen Auflösung Winkel für die Unterteilungen des Sichtfelds bestimmt.
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); // diese werden zu Debug-Zwecken im Viewport visualisiert.
ViewCastInfo newViewCast = ViewCast(angle); // Nun werden auf diesen Positionen Raycasts ausgesand und geguckt, ob diese ein Hindernis treffen.
if (i > 0) // Trifft ein Strahl ein Objekt und der nächste nicht, wird der Abstand zum nächsten Strakhl so lange halbiert, bis beide das Objekt treffen.
{ // So kann das Mesh des Sichtfelds so gebaut werden, dass die durch hindernisse nicht sichtbaren Bereiche genau ausgeschnitten werden.
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++) // Schließlich werden zwischen allen Punkten Dreiecke aufgespannt, sodass ein Mesh/3D-Objekt entsteht.
{
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);
}
}