Class VisionFramework

java.lang.Object
com.github.yellowstonegames.grid.VisionFramework
All Implemented Interfaces:
com.github.yellowstonegames.core.ISerializersNeeded
Direct Known Subclasses:
VisionFrameworkRgb

public class VisionFramework extends Object implements com.github.yellowstonegames.core.ISerializersNeeded
A "one-stop shop" for handling field-of-view, line-of-sight, (colorful) light sources, and more. Encapsulates currently-visible and previously-seen cell data, and allows updating and modifying light levels/colors. This controls a LightingManager, which manages LightSource objects. This tracks which cells on a place-map grid have been seen before, which are currently visible, which just became visible and which just became hidden, among other things. This tracks a map as a char[][], typically but not always using box-drawing characters, but these chars can merely be used as abbreviations for what graphic to display in a non-text-based game. This modifies the box-drawing characters it is given based on which walls are actually visible.
After creating a VisionFramework, You can call restart(char[][], CoordFloatOrderedMap, int) when you have a 2D char array to use as the map and one or more viewer positions. After that, add light sources to lighting. Then you can render the map "background" colors by calling update(float) (every frame) and getting the colors/opacities for anything that can move around (monsters, some traps or effects) using getForegroundColor(int, int, float) (also every frame, once per creature/effect). The update() and getForegroundColor() methods take a float, millisSinceLastMove, which is measured in milliseconds since the last action the player took; this allows cells to fade into or out of view over a short period of time. The background colors are available at backgroundColors once update() has been called. When the player character or characters change position (or any special vision the player has moves, like a remote camera), call moveViewer(Coord, Coord). You probably want to move any light source a character carries immediately before calling moveViewer(), so the new position shows the changed lighting right away; moving lights uses LightingManager.moveLight(Coord, Coord). When a small part of the world changes, call editSingle(Coord, char) (such as for a door opening), or editAll(char[][]) if a large part of the world changes at once. After any viewer, light source, or map feature changes (or several if they all changed at once), you should call finishChanges(), which updates prunedPlaceMap with the actually visible map cells, handles lighting changes, and so on.
To recap, the methods here get called in this order:
  1. Call restart(char[][], Coord, float) when an area map is loaded, giving it the viewer(s) and their vision range(s).
  2. Call LightingManager.addLight(Coord, Radiance) on lighting for every light source, or otherwise add LightSource values to that LightingManager.
  3. Every "turn" (when an input is entered), call LightingManager.moveLight(Coord, Coord) if a light source moved.
  4. Every "turn" (when an input is entered), call removeViewer(Coord) if a viewer was removed.
  5. Every "turn" (when an input is entered), call moveViewer(Coord, Coord) if a viewer moved.
  6. Every "turn" (when an input is entered), call editSingle(Coord, char) if a map cell was altered (such as a door opening).
  7. Every "turn" (when an input is entered), call editAll(char[][]) if the whole current map was altered (such as going downstairs to a new area).
  8. Every "turn" (when an input is entered), if any of the previous every-turn methods was called, call finishChanges() to complete the change.
  9. Every frame, call update(float), passing it the number of milliseconds since the last turn was handled (this number can be altered).
  10. Every frame, call getForegroundColor(Coord, float) for every position with a moving creature or object in it, passing it a position to query and the number of milliseconds since the last turn was handled (this number can be altered).
  11. You can get the current colors every frame from backgroundColors, which update() changes.

This class uses the Oklab color space throughout. Use VisionFrameworkRgb if you prefer RGBA colors.
  • Field Summary

    Fields
    Modifier and Type
    Field
    Description
    int[][]
    The background color tints; these are the finished colors produced by update(float).
    A small rim of cells just beyond the player's vision that blocks pathfinding to areas we can't see a path to.
    protected final Region
    Used as temporary storage by methods that can reuse a Region to avoid allocation.
    Contains only the cells that have a value greater than 0 in lighting.fovResult, meaning they are visible right now.
    All cells we were able to see, but just became hidden on this turn or short period of time.
    All cells we just became able to see on this turn or short period of time.
    Handles all light sources, with varying colors, strengths, and flicker/strobe patterns.
    char[][]
    The place map using box-drawing characters or '#' for walls, and any other chars for other terrain.
    All cells that became visible for the first time on this turn or short period of time.
    int
    The y-size of all 2D arrays here (the second index).
    int
    The x-size of all 2D arrays here (the first index).
    int[][]
    The background color tints from just before the latest change; these are used to handle transitions out of view, when a cell has just become hidden.
    float[][]
    The value that lighting.fovResult held in the previous turn or short period of time.
    char[][]
    The same as linePlaceMap, but with any branches of walls that can't be seen trimmed off to only show what is actually visible given the current field of view and the cells seen earlier.
    int
    The int color used to tint cells that could have been seen previously, but aren't currently visible.
    All cells that we have seen in the past, on this place map.
    Maps the positions of "things that can view the map for the player" to how far each of those things can see.
  • Constructor Summary

    Constructors
    Constructor
    Description
    The empty constructor.
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    editAll(char[][] newPlaceMap)
    Fully replaces the contents of linePlaceMap with those of newPlaceMap, and changes the light resistances for the whole place map in lighting.
    void
    editSingle(int newX, int newY, char newCell)
    Changes the char at newX,newY to be newCell and adjusts the light resistance for that cell only in lighting.
    void
    editSingle(Coord position, char newCell)
    Changes the char at position to be newCell and adjusts the light resistance for that cell only in lighting.
    boolean
     
    void
    This completes the changes started by moveViewer(int, int, int, int), editSingle(int, int, char), or editAll(char[][]) and updates the lighting according to those changes.
    int
    getForegroundColor(int x, int y, float millisSinceLastMove)
    For a "foreground" creature or effect that can move between cells, call this every frame to get the color to draw that thing with.
    int
    getForegroundColor(Coord pos, float millisSinceLastMove)
    For a "foreground" creature or effect that can move between cells, call this every frame to get the color to draw that thing with.
     
    int
     
    boolean
    moveViewer(int oldX, int oldY, int newX, int newY)
    If a viewer is present at oldX,oldY in viewers and no view is present at newX,newY, this moves the viewer to newX,newY and returns true.
    boolean
    moveViewer(Coord previousPosition, Coord nextPosition)
    If a viewer is present at previousPosition in viewers and no view is present at nextPosition, this moves the viewer to nextPosition and returns true.
    void
    putViewer(int x, int y, float viewRange)
    Adds a viewer to viewers with the given viewing distance, if there is no viewer already present at x,y.
    void
    putViewer(Coord position, float viewRange)
    Adds a viewer to viewers with the given viewing distance, if there is no viewer already present at position.
    boolean
    removeViewer(int x, int y)
    If a viewer is present at x,y in viewers, this removes that viewer and returns true.
    boolean
    removeViewer(Coord position)
    If a viewer is present at position in viewers, this removes that viewer and returns true.
    void
    restart(char[][] place, CoordFloatOrderedMap viewers)
    Some form of restart() must be called when the map is first created and whenever the whole local map changes.
    void
    restart(char[][] place, CoordFloatOrderedMap viewers, int baseColor)
    Some form of restart() must be called when the map is first created and whenever the whole local map changes.
    void
    restart(char[][] place, Coord playerPosition, float fovRange)
    Some form of restart() must be called when the map is first created and whenever the whole local map changes.
    void
    restart(char[][] place, Coord playerPosition, float fovRange, int baseColor)
    Some form of restart() must be called when the map is first created and whenever the whole local map changes.
     
    void
    update(float millisSinceLastMove)
    Updates the lighting effects and writes to backgroundColors so the current tint color is stored in every cell of backgroundColors, for every cell in the place map.

    Methods inherited from class Object

    clone, finalize, getClass, notify, notifyAll, wait, wait, wait
  • Field Details

    • placeWidth

      public int placeWidth
      The x-size of all 2D arrays here (the first index).
    • placeHeight

      public int placeHeight
      The y-size of all 2D arrays here (the second index).
    • linePlaceMap

      public char[][] linePlaceMap
      The place map using box-drawing characters or '#' for walls, and any other chars for other terrain. In most roguelikes, there would be one of these per dungeon floor.
    • prunedPlaceMap

      public char[][] prunedPlaceMap
      The same as linePlaceMap, but with any branches of walls that can't be seen trimmed off to only show what is actually visible given the current field of view and the cells seen earlier. In most roguelikes, there would be one of these per dungeon floor.
    • lighting

      public LightingManager lighting
      Handles all light sources, with varying colors, strengths, and flicker/strobe patterns.
    • previousLightLevels

      public float[][] previousLightLevels
      The value that lighting.fovResult held in the previous turn or short period of time. In most roguelikes, there would only need to be one of these variables at a time.
    • backgroundColors

      public int[][] backgroundColors
      The background color tints; these are the finished colors produced by update(float). These colors are overwritten every time update() is called.
      This uses packed Oklab int colors, which avoid the overhead of creating new Color objects. Use DescriptiveColor to get, describe, or create Oklab int colors. You can convert to RGBA8888 (which libGDX uses) with DescriptiveColor.oklabIntToFloat(int) if you want a "packed float color" that can be given to libGDX directly, or to a Color using Color.rgba8888ToColor(changingColor, DescriptiveColor.toRGBA8888(oklabColor)).
      In most roguelikes, there would be one of these per dungeon floor.
    • previousBackgroundColors

      public int[][] previousBackgroundColors
      The background color tints from just before the latest change; these are used to handle transitions out of view, when a cell has just become hidden.
    • blockage

      public Region blockage
      A small rim of cells just beyond the player's vision that blocks pathfinding to areas we can't see a path to. In most roguelikes, this would be temporary and only one would need to exist in total.
    • seen

      public Region seen
      All cells that we have seen in the past, on this place map. In most roguelikes, there would be one of these per dungeon floor, and it would persist.
    • justSeen

      public Region justSeen
      All cells we just became able to see on this turn or short period of time. In most roguelikes, this would be temporary and only one would need to exist in total.
    • justHidden

      public Region justHidden
      All cells we were able to see, but just became hidden on this turn or short period of time. In most roguelikes, this would be temporary and only one would need to exist in total.
    • newlyVisible

      public Region newlyVisible
      All cells that became visible for the first time on this turn or short period of time. These cells will be fading in from fully-transparent, rather than from a previously-seen gray color.
    • inView

      public Region inView
      Contains only the cells that have a value greater than 0 in lighting.fovResult, meaning they are visible right now.
    • buffer

      protected final transient Region buffer
      Used as temporary storage by methods that can reuse a Region to avoid allocation.
    • viewers

      public CoordFloatOrderedMap viewers
      Maps the positions of "things that can view the map for the player" to how far each of those things can see. In a traditional roguelike, there is probably just one viewer here unless the game includes remote viewing in some form. In a party-based game, each member of the exploration party is probably a viewer.
    • rememberedColor

      public int rememberedColor
      The int color used to tint cells that could have been seen previously, but aren't currently visible. In VisionFramework, this defaults to the Oklab int color 0xFF7F7F50, which is fully opaque, pure gray, and has about 30% lightness. You can get Oklab int colors using DescriptiveColor. In VisionFrameworkRgb, this defaults to the RGBA8888 color 0x505050FF, which is also fully opaque, pure gray, and has about 30% lightness. You can get RGBA8888 int colors using DescriptiveColorRgb.
  • Constructor Details

  • Method Details

    • restart

      public void restart(char[][] place, Coord playerPosition, float fovRange)
      Some form of restart() must be called when the map is first created and whenever the whole local map changes.
      This overload simplifies the viewers to just the common case of one viewer, the player character. You can specify an fovRange for how much the player can see without a light source, and you can also choose to add a light at the player's position with lighting and its LightingManager.addLight(Coord, Radiance) method. Remember to move the player with moveViewer(Coord, Coord) and any light they carry with LightingManager.moveLight(Coord, Coord). If the player's FOV range changes, you can update it with putViewer(Coord, float) using the player's current position.
      Parameters:
      place - a 2D char array representing a local map; '#' or box drawing characters represent walls
      playerPosition - where the one viewer will be put in the place; must be in-bounds for place
      fovRange - how far, in grid cells, the player can see without needing a light source
    • restart

      public void restart(char[][] place, Coord playerPosition, float fovRange, int baseColor)
      Some form of restart() must be called when the map is first created and whenever the whole local map changes.
      This overload simplifies the viewers to just the common case of one viewer, the player character. You can specify an fovRange for how much the player can see without a light source, and you can also choose to add a light at the player's position with lighting and its LightingManager.addLight(Coord, Radiance) method. Remember to move the player with moveViewer(Coord, Coord) and any light they carry with LightingManager.moveLight(Coord, Coord). If the player's FOV range changes, you can update it with putViewer(Coord, float) using the player's current position.
      This overload allows specifying a baseColor that will be used cells that were seen previously but can't be seen now; typically this is dark gray or very close to that, and it is an Oklab int color as produced by DescriptiveColor. The default, if not specified, is rememberedColor.
      Parameters:
      place - a 2D char array representing a local map; '#' or box drawing characters represent walls
      playerPosition - where the one viewer will be put in the place; must be in-bounds for place
      fovRange - how far, in grid cells, the player can see without needing a light source
      baseColor - the packed Oklab color used for previously-seen, but not in-view, cells
    • restart

      public void restart(char[][] place, CoordFloatOrderedMap viewers)
      Some form of restart() must be called when the map is first created and whenever the whole local map changes.
      This overload allows having one or more viewer positions at the start, with all viewers sharing seen information with each other (enemy characters are not generally viewers here). Each Coord position for a viewer is associated with how much that viewer can see without a light source; you can also choose to add a light at a viewer's position with lighting and its LightingManager.addLight(Coord, Radiance) method. Remember to move any viewer with moveViewer(Coord, Coord) and any light they carry with LightingManager.moveLight(Coord, Coord). If a viewer's FOV range changes, you can update it with putViewer(Coord, float) using that viewer's current position. You can also add new viewers that way.
      Parameters:
      place - a 2D char array representing a local map; '#' or box drawing characters represent walls
      viewers - a CoordFloatOrderedMap of the positions of viewers to their viewing ranges; directly referenced
    • restart

      public void restart(char[][] place, CoordFloatOrderedMap viewers, int baseColor)
      Some form of restart() must be called when the map is first created and whenever the whole local map changes.
      This overload allows having one or more viewer positions at the start, with all viewers sharing seen information with each other (enemy characters are not generally viewers here). Each Coord position for a viewer is associated with how much that viewer can see without a light source; you can also choose to add a light at a viewer's position with lighting and its LightingManager.addLight(Coord, Radiance) method. Remember to move any viewer with moveViewer(Coord, Coord) and any light they carry with LightingManager.moveLight(Coord, Coord). If a viewer's FOV range changes, you can update it with putViewer(Coord, float) using that viewer's current position. You can also add new viewers that way.
      This overload allows specifying a baseColor that will be used cells that were seen previously but can't be seen now; typically this is dark gray or very close to that, and it is an Oklab int color as produced by DescriptiveColor. The default, if not specified, is rememberedColor.
      Parameters:
      place - a 2D char array representing a local map; '#' or box drawing characters represent walls
      viewers - a CoordFloatOrderedMap of the positions of viewers to their viewing ranges; directly referenced
      baseColor - the packed Oklab color used for previously-seen, but not in-view, cells
    • editSingle

      public void editSingle(int newX, int newY, char newCell)
      Changes the char at newX,newY to be newCell and adjusts the light resistance for that cell only in lighting. You must call finishChanges() when you are done changing the place map, in order to update the lighting with the latest changes.
      Parameters:
      newX - the x-position to change, as an int
      newY - the y-position to change, as an int
      newCell - the char value to use at the given position (in linePlaceMap)
    • editSingle

      public void editSingle(Coord position, char newCell)
      Changes the char at position to be newCell and adjusts the light resistance for that cell only in lighting. You must call finishChanges() when you are done changing the place map, in order to update the lighting with the latest changes.
      Parameters:
      position - the position to change, as a non-null Coord
      newCell - the char value to use at the given position (in linePlaceMap)
    • editAll

      public void editAll(char[][] newPlaceMap)
      Fully replaces the contents of linePlaceMap with those of newPlaceMap, and changes the light resistances for the whole place map in lighting. The existing place map should really be the same size as the new place map.You must call finishChanges() when you are done changing the place map, in order to update the lighting with the latest changes.
      Parameters:
      newPlaceMap - the replacement 2D char array to use as linePlaceMap
    • moveViewer

      public boolean moveViewer(int oldX, int oldY, int newX, int newY)
      If a viewer is present at oldX,oldY in viewers and no view is present at newX,newY, this moves the viewer to newX,newY and returns true. Otherwise, this does nothing and returns false. You must call finishChanges() when you are done changing the place map or viewers, in order to update the lighting with the latest changes.
      If a viewer represents a character with a light source, you should probably have the light source known via lighting, and moving the light source should use LightingManager.moveLight(int, int, int, int).
      Parameters:
      oldX - the x-position of the viewer to move, if one is present
      oldY - the y-position of the viewer to move, if one is present
      newX - the x-position to move the viewer to, if possible
      newY - the y-position to move the viewer to, if possible
      Returns:
      true if the viewer moved, or false otherwise
    • moveViewer

      public boolean moveViewer(Coord previousPosition, Coord nextPosition)
      If a viewer is present at previousPosition in viewers and no view is present at nextPosition, this moves the viewer to nextPosition and returns true. Otherwise, this does nothing and returns false. You must call finishChanges() when you are done changing the place map or viewers, in order to update the lighting with the latest changes.
      If a viewer represents a character with a light source, you should probably have the light source known via lighting, and moving the light source should use LightingManager.moveLight(int, int, int, int).
      Parameters:
      previousPosition - the position of the viewer to move, if one is present
      nextPosition - the position to move the viewer to, if possible
      Returns:
      true if the viewer moved, or false otherwise
    • removeViewer

      public boolean removeViewer(int x, int y)
      If a viewer is present at x,y in viewers, this removes that viewer and returns true. Otherwise, this does nothing and returns false. You must call finishChanges() when you are done changing the place map or viewers, in order to update the lighting with the latest changes.
      If a viewer represents a character with a light source, you should probably have the light source known via lighting, and removing the light source should use LightingManager.removeLight(int, int).
      Parameters:
      x - the x-position of the viewer to remove, if one is present
      y - the y-position of the viewer to remove, if one is present
      Returns:
      true if a viewer was removed, or false otherwise
    • removeViewer

      public boolean removeViewer(Coord position)
      If a viewer is present at position in viewers, this removes that viewer and returns true. Otherwise, this does nothing and returns false. You must call finishChanges() when you are done changing the place map or viewers, in order to update the lighting with the latest changes.
      If a viewer represents a character with a light source, you should probably have the light source known via lighting, and removing the light source should use LightingManager.removeLight(int, int).
      Parameters:
      position - the position of the viewer to remove, if one is present
      Returns:
      true if a viewer was removed, or false otherwise
    • putViewer

      public void putViewer(int x, int y, float viewRange)
      Adds a viewer to viewers with the given viewing distance, if there is no viewer already present at x,y. If a viewer is present at x,y in viewers, then this edits its viewing distance. You must call finishChanges() when you are done changing the place map or viewers, in order to update the lighting with the latest changes.
      If a viewer represents a character with a light source, you should probably have the light source known via lighting, and adding a light source should use LightingManager.addLight(int, int, Radiance).
      Parameters:
      x - the x-position of the viewer to place or edit
      y - the y-position of the viewer to place or edit
      viewRange - the viewing distance to use for the placed or edited viewer
    • putViewer

      public void putViewer(Coord position, float viewRange)
      Adds a viewer to viewers with the given viewing distance, if there is no viewer already present at position. If a viewer is present at position in viewers, then this edits its viewing distance. You must call finishChanges() when you are done changing the place map or viewers, in order to update the lighting with the latest changes.
      If a viewer represents a character with a light source, you should probably have the light source known via lighting, and adding a light source should use LightingManager.addLight(Coord, Radiance).
      Parameters:
      position - the position of the viewer to place or edit
      viewRange - the viewing distance to use for the placed or edited viewer
    • finishChanges

      public void finishChanges()
      This completes the changes started by moveViewer(int, int, int, int), editSingle(int, int, char), or editAll(char[][]) and updates the lighting according to those changes. This affects almost all variables present in this object. It should be noted that the methods that change a place map only change linePlaceMap; that is because the end of this method uses linePlaceMap and the currently seen cells to update prunedPlaceMap. Rendering should typically determine what to draw by using prunedPlaceMap.
    • getForegroundColor

      public int getForegroundColor(Coord pos, float millisSinceLastMove)
      For a "foreground" creature or effect that can move between cells, call this every frame to get the color to draw that thing with. The color is a packed Oklab int, as DescriptiveColor produces. This can return 0 if a creature or thing cannot be seen and is not fading in or out of view. Otherwise, the int color this returns will be white with some level of transparency -- if the creature is in view now and was in view previously, then it will be opaque white, otherwise it will have some transparency between 0 and 1. This takes a float argument, millisSinceLastMove, that is the number of milliseconds since the player last entered an input that changed the map or its inhabitants. This is used to handle fading creatures into view when the player's input suddenly revealed those creatures, or fading them out of view if they become hidden. You don't need to give milliseconds precisely; while the input is effectively clamped between 0 and 1000, you can multiply the actual milliseconds that have passed by (for example) 4 to reduce the time a fade effect takes to complete (to a quarter-second). Multiplying by a large number will make fades instantaneous.
      Parameters:
      pos - the position to get the "foreground" color for; does not actually have to have a creature in it
      millisSinceLastMove - how many milliseconds have elapsed since the human player last entered an input, for fading
      Returns:
      the packed Oklab int color to tint any foreground creature or object with at pos
    • getForegroundColor

      public int getForegroundColor(int x, int y, float millisSinceLastMove)
      For a "foreground" creature or effect that can move between cells, call this every frame to get the color to draw that thing with. The color is a packed Oklab int, as DescriptiveColor produces. This can return 0 if a creature or thing cannot be seen and is not fading in or out of view. Otherwise, the int color this returns will be white with some level of transparency -- if the creature is in view now and was in view previously, then it will be opaque white, otherwise it will have some transparency between 0 and 1. Note that because white is the lightest color that can be represented, and it is the "neutral color" for a tint like this, there is no way for this tint to be used to lighten a sprite or other visual object.
      This takes a float argument, millisSinceLastMove, that is the number of milliseconds since the player last entered an input that changed the map or its inhabitants. This is used to handle fading creatures into view when the player's input suddenly revealed those creatures, or fading them out of view if they become hidden. You don't need to give milliseconds precisely; while the input is effectively clamped between 0 and 1000, you can multiply the actual milliseconds that have passed by (for example) 4 to reduce the time a fade effect takes to complete (to a quarter-second). Multiplying by a large number will make fades instantaneous.
      Parameters:
      x - the x-position to get the "foreground" color for; does not actually have to have a creature in it
      y - the y-position to get the "foreground" color for; does not actually have to have a creature in it
      millisSinceLastMove - how many milliseconds have elapsed since the human player last entered an input, for fading
      Returns:
      the packed Oklab int color to tint any foreground creature or object with at x,y
    • update

      public void update(float millisSinceLastMove)
      Updates the lighting effects and writes to backgroundColors so the current tint color is stored in every cell of backgroundColors, for every cell in the place map. Call this every frame while lights are updating (for flicker and strobe effects); this does not need to be called if the game has rendering paused for any reason. This takes a float argument, millisSinceLastMove, that is the number of milliseconds since the player last entered an input that changed the map or its inhabitants. This is used to handle fading creatures into view when the player's input suddenly revealed those creatures, or fading them out of view if they become hidden. You don't need to give milliseconds precisely; while the input is effectively clamped between 0 and 1000, you can multiply the actual milliseconds that have passed by (for example) 4 to reduce the time a fade effect takes to complete (to a quarter-second). Multiplying by a large number will make fades instantaneous.
      This sets backgroundColors to hold visible Oklab int colors where a cell is visible.
      Parameters:
      millisSinceLastMove - how many milliseconds have elapsed since the human player last entered an input, for fading
    • equals

      public boolean equals(Object o)
      Overrides:
      equals in class Object
    • hashCode

      public int hashCode()
      Overrides:
      hashCode in class Object
    • toString

      public String toString()
      Overrides:
      toString in class Object
    • getSerializersNeeded

      public List<Class<?>> getSerializersNeeded()
      Specified by:
      getSerializersNeeded in interface com.github.yellowstonegames.core.ISerializersNeeded