Skip to content

Field Of View

Chris3606 edited this page Jan 15, 2019 · 8 revisions

GoRogue offers a high-performance class for FOV that can support a variety of different shapes.

Table of Contents

Code Examples

Code examples in this section show only code in the Main function. The code provided assumes that the following "using" statements are at the top of the code file:

using GoRogue;
using GoRogue.MapGeneration;
using GoRogue.MapViews;

FOV vs SenseMap

The FOV class is designed to implement a single-source, more or less roguelike-standard FOV algorithm in a simplistic way. GoRogue does offer support for light/multiple sensory-based sources, however -- see the Sense Mapping system for these use cases.

Constructing an FOV Instance

To create an FOV instance, you must simply give it an IMapView<bool> instance (see Map View documentation for details). The IMapView should produce a value of true for locations that are transparent (do not block FOV), and false for locations that do block FOV. Alternatively, for convenience you may pass it an IMapView<double> like the views that Sense Mapping takes as input.

ArrayMap<bool> map = new ArrayMap<bool>(10, 10);
// Sets edges to false, and all other locations to true.  See Map Generation documentation for details
QuickGenerators.GenerateRectangleMap(map);

FOV fov = new FOV(map);

// IMapView that converts values of true in map to 0.0, and values of false in map to 1.0.  This is a
// mock-up for values that SenseMap would take as input, see SenseMap documentation for details.
IMapView<double> senseMapView = new LambdaTranslationMap<bool, double>(map, val => val ? 0.0 : 1.0);

FOV doubleFov = new FOV(senseMapView);

Calculating FOV

Once an FOV instance has been created, you can call one of the various overloads of Calculate to complete the FOV calculation.

Source Location

In its most basic form, you simply pass to Calculate a source location. The radius of the calculated FOV is assumed to be infinite.

ArrayMap<bool> map = new ArrayMap<bool>(10, 10);
// Sets edges to false, and all other locations to true.  See Map Generation documentation for details
QuickGenerators.GenerateRectangleMap(map);
// Create an obstacle for FOV demonstration right next to the source
map[4, 5] = false;

FOV fov = new FOV(map);
Coord start = Coord.Get(5, 5);

fov.Calculate(start);
// We can also specify the source as x/y value, rather than a Coord:
// fov.Calculate(start.X, start.Y);

// See documentation of Stringifying FOV for details.  In this output, '+' means a value is in FOV,
// and '-' means it is outside.  Notice how the walls on the visible edges are considered in FOV -- as
// per standard, walls in FOV are considered "lit"
System.Console.WriteLine(fov.ToString()); 

Specifying a Radius

You may also pass to Calculate a radius for the resulting FOV. Any location outside this radius is guaranteed to be outside the resulting FOV. Since we do not specify a Distance/Radius shape, it defaults to using a circle (Euclidean distance):

ArrayMap<bool> map = new ArrayMap<bool>(10, 10);
// Sets edges to false, and all other locations to true.  See Map Generation documentation for details
QuickGenerators.GenerateRectangleMap(map);
// Create an obstacle for FOV demonstration right next to the source
map[4, 5] = false;

FOV fov = new FOV(map);
Coord start = Coord.Get(5, 5);

fov.Calculate(start, 3);
// We can also specify the source as x/y value, rather than a Coord:
// fov.Calculate(start.X, start.Y, 3);

// See documentation of Stringifying FOV for details.  In this output, '+' means a value is in FOV,
// and '-' means it is outside.  Notice how the walls on the visible edges are considered in FOV -- as
// per standard, walls in FOV are considered "lit"
System.Console.WriteLine(fov.ToString()); 

Specifying a Shape

Finally, we may also specify a Radius instance to Calculate to specify the shape of the resulting FOV (provided it is unobstructed). Since Radius and Distance are implicitly convertable to each other, we can also specify a Distance instance that determines the radius shape:

ArrayMap<bool> map = new ArrayMap<bool>(10, 10);
// Sets edges to false, and all other locations to true.  See Map Generation documentation for details
QuickGenerators.GenerateRectangleMap(map);
// Create an obstacle for FOV demonstration right next to the source
map[4, 5] = false;

FOV fov = new FOV(map);
Coord start = Coord.Get(5, 5);

fov.Calculate(start, 3, Radius.SQUARE);
// Note that we can also specify an x and y value for the source.
// As per the Distance/Radius documentation linked above, CHEBYSHEV distance
// defines distance in such a way that the radius is a square
// fov.Calculate(start.X, start.Y, Distance.CHEBYSHEV);

// See documentation of Stringifying FOV for details.  In this output, '+' means a valueis in FOV,
// and '-' means it is outside.  Notice how the walls on the visible edges are considered in FOV -- as
// per standard, walls in FOV are considered "lit"
System.Console.WriteLine(fov.ToString()); 

Directional FOV

The above examples calculate FOV as 360 degree shapes. You may, in addition to the parameters above, pass an angle and span parameter to Calculate. The angle parameter is an angle in degrees that specifies the "direction" the field of view faces. 0 degrees indicates that the FOV faces right (that is, its center is directly to the right of the source), so 90 faces down, and so forth. The span parameter specifies the number of degrees (total) the arc of the field of view covers. Exactly span/2 degrees will be included in FOV on either side of the center point (defined by the angle given). For example, a 60 degree span will cover 30 degrees on either side of the center:

ArrayMap<bool> map = new ArrayMap<bool>(10, 10);
// Sets edges to false, and all other locations to true.  See Map Generation documentation for details
QuickGenerators.GenerateRectangleMap(map);
// Create an obstacle for FOV demonstration right next to the source
map[4, 5] = false;

FOV fov = new FOV(map);
Coord start = Coord.Get(5, 5);

// FOV that points downward, and covers 30 degrees on either side of the center
fov.Calculate(start, 3, Radius.SQUARE, 90, 60);
// Note that we can also specify an x and y value for the source.
// As per the Distance/Radius documentation linked above, CHEBYSHEV distance
// defines distance in such a way that the radius is a square
// fov.Calculate(start.X, start.Y, Distance.CHEBYSHEV, 90, 60);

// See documentation of Stringifying FOV for details.  In this output, '+' means a valueis in FOV,
// and '-' means it is outside.  Notice how the walls on the visible edges are considered in FOV -- as
// per standard, walls in FOV are considered "lit"
System.Console.WriteLine(fov.ToString()); 

Reading FOV Output

The FOV class exposes the results of the last calculation in a number of ways.

Simple Boolean Values

The simplest way to access the current FOV is via the FOV.BooleanFOV property. This property is an IMapView<bool> instance that, for a given location, returns true if that location is in the current fov (visible), and false if that location is outside fov (not visible):

ArrayMap<bool> map = new ArrayMap<bool>(10, 10);
// Sets edges to false, and all other locations to true.  See Map Generation documentation for details
QuickGenerators.GenerateRectangleMap(map);
// Create an obstacle for FOV demonstration right next to the source
map[4, 5] = false;

FOV fov = new FOV(map);
Coord start = Coord.Get(5, 5);

fov.Calculate(start, 3);

System.Console.WriteLine("(2, 5) in FOV: " + fov.BooleanFOV[2, 5]);
System.Console.WriteLine("Start position in FOV: " + fov.BooleanFOV[start]); // Source is always visible

Results as Doubles

The FOV class itself also implements IMapView<double>, in order to expose the FOV values as doubles. A value of 1.0 is the start location, and 0.0 is not visible. The values in between fall off linearly with respect to the radius given:

ArrayMap<bool> map = new ArrayMap<bool>(10, 10);
// Sets edges to false, and all other locations to true.  See Map Generation documentation for details
QuickGenerators.GenerateRectangleMap(map);
// Create an obstacle for FOV demonstration right next to the source
map[4, 5] = false;

FOV fov = new FOV(map);
Coord start = Coord.Get(5, 5);

fov.Calculate(start, 3);

System.Console.WriteLine("(2, 5) FOV value: " + fov[2, 5]); // (2, 5) is not visible
System.Console.WriteLine("Start position FOV value: " + fov[start]); // Source is always visible at 1.0

// Falls off at a rate of 1/(radius+1) (to ensure we include the starting point), which is 1/4 = 0.25
// per tile from source. This location is 1 tile away from the source so it has a value of 0.75
System.Console.WriteLine("Start position FOV value: " + fov[start.X + 1, start.Y]); 

Getting Locations in Current FOV

The FOV class also has a property CurrentFOV, which is a HashSet<Coord> containing all locations that are in the FOV (as it was calculated the last time Calculate was called):

ArrayMap<bool> map = new ArrayMap<bool>(10, 10);
// Sets edges to false, and all other locations to true.  See Map Generation documentation for details
QuickGenerators.GenerateRectangleMap(map);
// Create an obstacle for FOV demonstration right next to the source
map[4, 5] = false;

FOV fov = new FOV(map);
Coord start = Coord.Get(5, 5);

fov.Calculate(start, 3);

// GoRogue-defined extension method to print IEnumerable<T> like a Python list
System.Console.WriteLine(fov.CurrentFOV.ExtendToString());

Getting Newly Visible Locations in FOV

GoRogue FOV also provides an easy methods to iterate over values that are visible in the currently calculated FOV that were not visible in the previously calculated FOV. The FOV class exposes the NewlySeen property for this:

ArrayMap<bool> map = new ArrayMap<bool>(10, 10);
// Sets edges to false, and all other locations to true.  See Map Generation documentation for details
QuickGenerators.GenerateRectangleMap(map);

FOV fov = new FOV(map);
Coord start = Coord.Get(5, 5);

// Calculate FOV, then calculate it again from a spot 1 x-value to the right
fov.Calculate(start, 3, Radius.SQUARE);
fov.Calculate(start.X + 1, start.Y, 3, Radius.SQUARE);


// GoRogue-defined extension method to print IEnumerable<T> like a Python list
// Note how only the 7 (2 * radius + 1) newly visible coordinates print
System.Console.WriteLine(fov.NewlySeen.ExtendToString());

Getting Coordinates Newly Hidden from FOV

Similar to NewlySeen, GoRogue also provides a convenient method to get any coordinates that are hidden in the currently calculated FOV that were visible in the previously calculated FOV. The FOV class exposes the NewlyUnseen property for this:

ArrayMap<bool> map = new ArrayMap<bool>(10, 10);
// Sets edges to false, and all other locations to true.  See Map Generation documentation for details
QuickGenerators.GenerateRectangleMap(map);

FOV fov = new FOV(map);
Coord start = Coord.Get(5, 5);

// Calculate FOV, then calculate it again from a spot 1 x-value to the right
fov.Calculate(start, 3, Radius.SQUARE);
fov.Calculate(start.X + 1, start.Y, 3, Radius.SQUARE);


// GoRogue-defined extension method to print IEnumerable<T> like a Python list
// Note how only the 7 (2 * radius + 1) newly hidden coordinates print
System.Console.WriteLine(fov.NewlyUnseen.ExtendToString());

Printing an FOV Instance

GoRogue's FOV class provides a number of ToString overloads and other stringifying-functions to help print FOV to the screen (which may be useful for debugging).

ToString

The no-parameter version of FOV.ToString simply prints the FOV in a grid to the screen, where '+' represents any location inside FOV, and '-' represents any location outside of FOV. It can also optionally take characters to substitute for the '+' and '-' characters. Finally, to print the double values, we may specify a single integer to ToString indicating the number of decimal places to print for each value:

ArrayMap<bool> map = new ArrayMap<bool>(10, 10);
// Sets edges to false, and all other locations to true.  See Map Generation documentation for details
QuickGenerators.GenerateRectangleMap(map);

FOV fov = new FOV(map);
Coord start = Coord.Get(5, 5);

fov.Calculate(start, 3);

System.Console.WriteLine("FOV default ToString:");
System.Console.WriteLine(fov);

System.Console.WriteLine("FOV with custom chars:");
System.Console.WriteLine(fov.ToString(' ', '.'));

System.Console.WriteLine("FOV values to 2 decimal places:");
System.Console.WriteLine(fov.ToString(2));

IMapView.ExtendToString

The ToString functions of FOV can cover many common cases, however because FOV implements IMapView, the IMapView.ExtendToString function overloads can be used to provide additional customization. The following code example shows that the ExtendToString function can be used on either FOV or the FOV.BooleanFOV property. ExtendToString offers a number of parameters that can provide extremely advanced customization, which are covered in the IMapView ExtendToString documentation.

ArrayMap<bool> map = new ArrayMap<bool>(10, 10);
// Sets edges to false, and all other locations to true.  See Map Generation documentation for details
QuickGenerators.GenerateRectangleMap(map);

FOV fov = new FOV(map);
Coord start = Coord.Get(5, 5);

fov.Calculate(start, 3);

System.Console.WriteLine("FOV ExtendToString default params:");
System.Console.WriteLine(fov.ExtendToString(fieldSize: 4, elementStringifier: d => d.ToString("0.00")));
System.Console.WriteLine("FOV.BooleanFOV ExtendToString default params:");
System.Console.WriteLine(fov.BooleanFOV.ExtendToString(fieldSize: 5));

Exposing FOV as Read-Only

In a complex map structure, it may be useful to expose a "read-only" view of an FOV instance -- that is, an instance that exposes only the FOV properties and methods that do not modify the FOV. GoRogue's FOV implements IReadOnlyFOV, which exposes the CurrentFOV, NewlySeen, NewlyUnseen, and BooleanFOV properties, as well as all functionality from IMapView<double>:

class Map
    {
        private FOV _fov;
        public IReadOnlyFOV FOV { get => _fov; }

        public Map(ArrayMap<bool> generatedTerrain)
        {
            _fov = new FOV(generatedTerrain);
        }

        public void CalculateFOV(Coord start, int radius)
        {
            _fov.Calculate(start, radius);

            // Here you might perform other functions that should happen each time FOV is calculated,
            // such as updating explored locations, etc.
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            ArrayMap<bool> terrainMap = new ArrayMap<bool>(10, 10);
            // Sets edges to false, and all other locations to true.  See Map Generation documentation for details
            QuickGenerators.GenerateRectangleMap(terrainMap);

            Map map = new Map(terrainMap);
            map.CalculateFOV(Coord.Get(5, 5), 3);

            System.Console.WriteLine("Current FOV:");
            System.Console.WriteLine(map.FOV);

            // Uncommenting this would produce a compile-time error, since Calculate modifies the FOV state
            // map.FOV.Calculate(Coord.Get(5, 5), 3);
        }
    }