207 lines
6.3 KiB
C#
207 lines
6.3 KiB
C#
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 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;
|
||
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<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);
|
||
}
|
||
}
|