# Drawing a dynamic indicator for a field of view

My goal is to draw a dynamic indicator for a cube, which represents the field of view. The ideal indicator would look the same as in Metal Gear Solid(the blue ones):

From what I have gathered, I need an angle and a length for the indicator as such for a 50° FoV:

Now my issue is what method should I use to draw the indicator? A procedural mesh or is there a less demanding way of doing this?

I can't give you a Unity-specific answer (sorry!), but I can tell how I would solve this. I would generate a bunch of points on a circle using the blue vector.

First, what is a circle? Well, a circle looks like this:

However, graphics hardware can't really draw perfect circles. You're always going to end up with a polygon approximation:

We do this approximation by using sine and cosine functions. If you remember from trigonometry, a circle is 360 degrees or 2 pi. Sine and cosine functions take a value out of 2 pi and give you a distance either in the x (cosine) or y (sine) axis. A very useful property indeed.

In code:

x = (cos(degrees_to_radians(angle));


Now, what you want is not a circle, but a half-circle. Specifically, you want this:

This can be achieved by using an angle_start and an angle_end. We then loop over this using an angle_delta that is calculated by dividing the difference between angle_start and angle_end over the quality we want.

Another useful property of approximating circles with sine and cosine (also known as the "unit circle") is that it can be shrunk or grown using a radius value. So let's add a radius to our pseudo-code:

x = (cos(degrees_to_radians(angle)) * radius;


How can we use this for our field-of-view cone? Well, we can use the same circle but draw it at different distances:

And when I add a dotted line to connect the two half-circles, it's starting to look like a cone!

Here, we have two radii: dist_min and dist_max. Our code becomes:

x_min = (cos(degrees_to_radians(angle)) * dist_min;



However, we also need two angles: one for the current step and one for the next step. We'll call these angle_curr and angle_next. Now our code becomes:

x_curr_min = (cos(degrees_to_radians(angle_curr)) * dist_min;



Now we have four points on our half-circles. Let's store these points as triangles. Here's a diagram that should help clear up how can make triangles out of these four points:

So, what do we need to do draw a cone?

• Determine the start angle and the end angle. This is done using the enemy's direction and field of view variable.
• Determine the quality of our approximation. 15 steps should be good enough for most purposes.
• Loop over the difference between start and end using the delta ((end - start) / quality).
• Convert our angles to four points.
• Convert the four points to two triangles.

When we combine these steps, here's what we will end up with:

In full pseudo-code:

int quality = 15;

float dist_min = 0.5f;
float dist_max = 15.f;

vec2 pos = GetEnemyPosition();
float angle_lookat = GetEnemyAngle();
float angle_fov = GetEnemyFieldOfView();

float angle_start = angle_lookat - angle_fov;
float angle_end = angle_lookat + angle_fov;
float angle_delta = (angle_end - angle_start) / quality;

float angle_curr = 0.f;
float angle_next = angle_delta;

for (int i = 0; i < quality - 1; i++)
{
vec2 sphere_curr;

vec2 sphere_next;

vec2 pos_curr_min = pos + sphere_curr * dist_min;
vec2 pos_curr_max = pos + sphere_curr * dist_max;

vec2 pos_next_min = pos + sphere_next * dist_min;
vec2 pos_next_max = pos + sphere_next * dist_max;

WriteTriangle(pos_curr_min, pos_curr_max, pos_next_max);
WriteTriangle(pos_next_max, pos_curr_min, pos_next_min);

angle_curr += angle_delta;
angle_next += angle_delta;
}


This should create the cone you want. Try playing with the parameters (dist_min, dist_max and quality) to get the effect you want. It should only be a few triangles per enemy, I don't think it's much of a problem to generate this every frame. Only consider optimizing it when it becomes a problem.

• I tried to figure out what happens in there, hard without any comments, but apparently your WriteTriangle method draws a procedural mesh according to the angles?
– Esa
Jun 27, 2012 at 7:21
• I always try to make code readable without any comments (because comments lie). However, in this case I will have to make an exception. WriteTriangle is a method that takes three vec2's and saves it as a triangle in some kind of mesh structure. Internally, the mesh would store a (dynamic) list of vertices and use that to render a model. Jun 27, 2012 at 7:47
• I have updated my answer (with pictures!) to hopefully clear things up for you. Jun 27, 2012 at 8:28
• +1. Very detailed answer. Just lacks the texturing to get the same look as in the reference image :) Jun 27, 2012 at 8:50
• +1 from me as well. Great answer. @bummzack I think solving gradient like texturing deserves a question on its own. Oct 15, 2017 at 9:21

Good day y'all!

I've implemented knight666's answer from pseudo-code to Unity-code (C#). Some slight changes were needed but it works like charm, just attach the script to an enemy. I don't know if there are more efficient ways to do some of the things.

One thing worth mentioning is that the number of vertices can be reduced from 4*quality to 2*quality+2, since for example triangle 2 and 3 share two vertices. This is true if the circle segment is continuous, but in my application it isn't, which is my I didn't optimize that part :)

Also note that you cannot set mesh.vertices[i] or mesh.triangles[i], which is why I used the auxiliary arrays vertices and triangles (which probably should be declared in global space for speed).

The code is sparsely commented since knight666's awesome answer and pics say it all. The biggest difference is that his answer is in right hand coordinates, whereas Unity works in left hand coordinates, so some sin & cos needed switching places as well as the order of the vertices in the triangles to get the correct normal (y axis in this case)

using UnityEngine;
using System.Collections;

public class drawFieldOfView : MonoBehaviour {

int quality = 15;
Mesh mesh;
public Material material;

float angle_fov = 40;

float dist_min = 5.0f;
float dist_max = 15.0f;

void Start()
{
mesh = new Mesh();
mesh.vertices = new Vector3[4 * quality];   // Could be of size [2 * quality + 2] if circle segment is continuous
mesh.triangles = new int[3 * 2 * quality];

Vector3[] normals = new Vector3[4 * quality];
Vector2[] uv = new Vector2[4 * quality];

for (int i = 0; i < uv.Length; i++)
uv[i] = new Vector2(0, 0);
for (int i = 0; i < normals.Length; i++)
normals[i] = new Vector3(0, 1, 0);

mesh.uv = uv;
mesh.normals = normals;

}

void Update()
{
float angle_lookat = GetEnemyAngle();

float angle_start = angle_lookat - angle_fov;
float angle_end = angle_lookat + angle_fov;
float angle_delta = (angle_end - angle_start) / quality;

float angle_curr = angle_start;
float angle_next = angle_start + angle_delta;

Vector3 pos_curr_min = Vector3.zero;
Vector3 pos_curr_max = Vector3.zero;

Vector3 pos_next_min = Vector3.zero;
Vector3 pos_next_max = Vector3.zero;

Vector3[] vertices = new Vector3[4 * quality];   // Could be of size [2 * quality + 2] if circle segment is continuous
int[] triangles = new int[3 * 2 * quality];

for (int i = 0; i < quality; i++)
{
Vector3 sphere_curr = new Vector3(
Mathf.Sin(Mathf.Deg2Rad * (angle_curr)), 0,   // Left handed CW

Vector3 sphere_next = new Vector3(

pos_curr_min = transform.position + sphere_curr * dist_min;
pos_curr_max = transform.position + sphere_curr * dist_max;

pos_next_min = transform.position + sphere_next * dist_min;
pos_next_max = transform.position + sphere_next * dist_max;

int a = 4 * i;
int b = 4 * i + 1;
int c = 4 * i + 2;
int d = 4 * i + 3;

vertices[a] = pos_curr_min;
vertices[b] = pos_curr_max;
vertices[c] = pos_next_max;
vertices[d] = pos_next_min;

triangles[6 * i] = a;       // Triangle1: abc
triangles[6 * i + 1] = b;
triangles[6 * i + 2] = c;
triangles[6 * i + 3] = c;   // Triangle2: cda
triangles[6 * i + 4] = d;
triangles[6 * i + 5] = a;

angle_curr += angle_delta;
angle_next += angle_delta;

}

mesh.vertices = vertices;
mesh.triangles = triangles;

Graphics.DrawMesh(mesh, Vector3.zero, Quaternion.identity, material, 0);
}

float GetEnemyAngle()
{
return 90 - Mathf.Rad2Deg * Mathf.Atan2(transform.forward.z, transform.forward.x); // Left handed CW. z = angle 0, x = angle 90
}

}


8 years later and this has helped another coder. I have adjusted knight666's and Rick0r's code to work modularly with a tabletop strategy game I am working on. Though I am having trouble getting it to take a shader, it will only display the color of the shader and not the effect.

using UnityEngine;

public class FiringArcMeshGenerator : MonoBehaviour
{
/// <summary>
/// Credit to Knight666 (knight666.com) & Rickz0r
/// https://gamedev.stackexchange.com/questions/31170/drawing-a-dynamic-indicator-for-a-field-of-view
/// Code modified for modular use by ThomFoxx
/// </summary>
[SerializeField] private int _quality = 6;
private Mesh _arcMesh;

[SerializeField] private bool _isPrimary;
[SerializeField] private float _primaryAngle = 90, _auxAngle = 180;

private float _angleOfFire;

private float _minDist;
private float _maxDist;

public void MeshSetup(ShipInfo ship)
{
WeaponInfo weapon;

if (_isPrimary)
{
_angleOfFire = _primaryAngle / 2;
weapon = ship.Primary;
}
else
{
_angleOfFire = _auxAngle / 2;
_quality *= 2;
weapon = ship.Auxiliary;
}

_minDist = weapon.MinRange;
_maxDist = weapon.MaxRange;

_arcMesh = new Mesh();
_arcMesh.vertices = new Vector3[4 * _quality];
_arcMesh.triangles = new int[3 * 2 * _quality];

Vector3[] normals = new Vector3[4 * _quality];
Vector2[] uv = new Vector2[4 * _quality];

for (int i = 0; i < uv.Length; i++)
uv[i] = new Vector2(0, 0);

for (int i = 0; i < normals.Length; i++)
normals[i] = new Vector3(0, 1, 0);

_arcMesh.uv = uv;
_arcMesh.normals = normals;

GenerateMesh();
GetComponent<MeshFilter>().mesh = _arcMesh;
}

void GenerateMesh()
{
float angle_lookat = 0;

float angle_start = angle_lookat - _angleOfFire;
float angle_end = angle_lookat + _angleOfFire;
float angle_delta = (angle_end - angle_start) / _quality;

float angle_curr = angle_start;
float angle_next = angle_start + angle_delta;

Vector3 pos_curr_min = Vector3.zero;
Vector3 pos_curr_max = Vector3.zero;

Vector3 pos_next_min = Vector3.zero;
Vector3 pos_next_max = Vector3.zero;

Vector3[] vertices = new Vector3[4 * _quality];
int[] triangles = new int[3 * 2 * _quality];

for (int i = 0; i < _quality; i++)
{
Vector3 sphere_curr = new Vector3(
0,

Vector3 sphere_next = new Vector3(
0,

pos_curr_min = transform.position + sphere_curr * _minDist;
pos_curr_max = transform.position + sphere_curr * _maxDist;

pos_next_min = transform.position + sphere_next * _minDist;
pos_next_max = transform.position + sphere_next * _maxDist;

int a = 4 * i;
int b = 4 * i + 1;
int c = 4 * i + 2;
int d = 4 * i + 3;

vertices[a] = pos_curr_min;
vertices[b] = pos_curr_max;
vertices[c] = pos_next_max;
vertices[d] = pos_next_min;

triangles[6 * i] = a;       // Triangle1: abc
triangles[6 * i + 1] = b;
triangles[6 * i + 2] = c;
triangles[6 * i + 3] = c;   // Triangle2: cda
triangles[6 * i + 4] = d;
triangles[6 * i + 5] = a;

angle_curr += angle_delta;
angle_next += angle_delta;

}

_arcMesh.vertices = vertices;
_arcMesh.triangles = triangles;
}
}

• "I am having trouble getting it to take a shader, it will only display the color of the shader and not the effect." — my suspicion would be that it's because you're outputting the same uv coordinates and normal vectors for every vertex, so there are no differences in input from one part of the mesh to the other for the shader to use to produce different output. You can post a question asking how to correct that for a particular shader effect. Feb 23, 2022 at 10:20
• @DMGregory Yeah, I've been looking over this code for the better part of the day. And while graphics coding is not normally in my wheelhouse, I have come to the same conclusion. I feel there might be a lot of overlap in this code as well. It has become my sudoku puzzle for the week. :P Feb 24, 2022 at 8:34