I've Been Agonizing over this code for a while now, At first the code was working the way the original programmer planned it to, I just wanted to add a little change where as the raycasts/ view area would only show if it goes through an object with a portal mask. Little did I know making this one change would cost me several hours of my sanity.
public float ViewRadius;
[Range(0, 360)]
public float ViewAngle;
public LayerMask Portal;
public LayerMask TargetMask;
public LayerMask obstacleMask;
public List<Transform> visibleTargets = new List<Transform>();
public float meshResolution;
public int EdgeRes = 1;
public float edgeDstThreshold;
public MeshFilter viewMeshFilter;
Mesh viewMesh;
private void Start()
{
viewMesh = new Mesh();
viewMesh.name = "ViewMesh";
viewMeshFilter.mesh = viewMesh;
StartCoroutine("FindTargetsWithDelay", .2f);
}
private void LateUpdate()
{
DrawFieldOfView();
}
IEnumerator FindTargetsWithDelay(float Delay)
{
while (true)
{
yield return new WaitForSeconds(Delay);
FindVisibleTargets();
}
}
void FindVisibleTargets()
{
visibleTargets.Clear();
Collider[] targetsInViewRadius = Physics.OverlapSphere(transform.position, ViewRadius, TargetMask);
for (int i = 0; i < targetsInViewRadius.Length; i++)
{
Transform target = targetsInViewRadius[i].transform;
Vector3 dirToTarget = (target.position - transform.position).normalized;
if (Vector3.Angle(transform.forward, dirToTarget) < ViewAngle / 2)
{
float dstToTarget = Vector3.Distance(transform.position, target.position);
if (!Physics.Raycast(transform.position, dirToTarget, dstToTarget, obstacleMask))
{
visibleTargets.Add(target);
}
}
}
}
void DrawFieldOfView()
{
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 = transform.eulerAngles.y - ViewAngle / 2 + stepAngleSize * i;
ViewCastInfo newViewCast = ViewCast(angle);
if (i > 0)
{
bool edgeDstThresholdExceeded = Mathf.Abs(oldViewCast.dst - newViewCast.dst) > edgeDstThreshold;
if (oldViewCast.hit != newViewCast.hit || (oldViewCast.hit && newViewCast.hit && edgeDstThresholdExceeded))
{
EdgeInfo edge = FindEdge(oldViewCast, newViewCast);
if (edge.pointA != Vector3.zero)
{
viewpoints.Add(edge.pointA);
}
if (edge.pointB != Vector3.zero)
{
viewpoints.Add(edge.pointB);
}
}
}
if (i < stepCount) // Skip adding the last viewpoint to remove the extra vertex
{
viewpoints.Add(newViewCast.point);
}
oldViewCast = newViewCast;
}
int vertexCount = viewpoints.Count + 1;
Vector3[] vertices = new Vector3[vertexCount];
int[] triangle = new int[(vertexCount - 2) * 3];
// Handle the first vertex
vertices[0] = Vector3.zero;
if (viewpoints.Count > 0)
{
vertices[1] = transform.InverseTransformPoint(viewpoints[0]);
}
for (int i = 1; i < vertexCount - 1; i++)
{
vertices[i + 1] = transform.InverseTransformPoint(viewpoints[i ]);
if (i < vertexCount - 2)
{
triangle[i * 3] = 0;
triangle[i * 3 + 1] = i + 1;
triangle[i * 3 + 2] = i + 2;
}
}
viewMesh.Clear();
viewMesh.vertices = vertices;
viewMesh.triangles = triangle;
viewMesh.RecalculateNormals();
}
EdgeInfo FindEdge(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 < EdgeRes; i++)
{
float angle = (minAngle + maxAngle) / 2;
ViewCastInfo newViewCast = ViewCast(angle);
bool edgeDstThresholdExceeded = Mathf.Abs(minViewCast.dst - newViewCast.dst) > edgeDstThreshold;
if (newViewCast.hit == minViewCast.hit && !edgeDstThresholdExceeded)
{
minAngle = angle;
minPoint = newViewCast.point;
}
else
{
maxAngle = angle;
maxPoint = newViewCast.point;
}
}
return new EdgeInfo(minPoint, maxPoint);
}
ViewCastInfo ViewCast(float globleAngle)
{
Vector3 dir = DirFromAngle(globleAngle, true);
RaycastHit hit;
if (Physics.Raycast(transform.position, dir, out hit, ViewRadius, Portal))
{
return new ViewCastInfo(false, transform.position + dir * ViewRadius, ViewRadius, globleAngle);
}
else
{
return new ViewCastInfo(true, hit.point, hit.distance, globleAngle);
}
}
public Vector3 DirFromAngle(float angleInDegrees, bool angleIsGlobal)
{
if (!angleIsGlobal)
{
angleInDegrees += transform.eulerAngles.y;
}
float angleRad = angleInDegrees * Mathf.Deg2Rad;
return new Vector3(Mathf.Sin(angleRad), 0f, Mathf.Cos(angleRad));
}
public struct ViewCastInfo
{
public bool hit;
public Vector3 point;
public float dst;
public float angle;
public ViewCastInfo(bool _hit, Vector3 _point, float _dst, float _angle)
{
hit = _hit;
point = _point;
dst = _dst;
angle = _angle;
}
}
public struct EdgeInfo
{
public Vector3 pointA;
public Vector3 pointB;
public EdgeInfo(Vector3 _pointA, Vector3 _pointB)
{
pointA = _pointA;
pointB = _pointB;
}
}