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: y‑Komponente 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 0–360 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 0–360 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 viewPoints = new List(); 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); } }