001package squidpony.squidgrid;
002
003import squidpony.ArrayTools;
004import squidpony.squidgrid.mapping.DungeonUtility;
005import squidpony.squidmath.Coord;
006import squidpony.squidmath.GreasedRegion;
007import squidpony.squidmath.MathExtras;
008import squidpony.squidmath.NumberTools;
009
010import java.io.Serializable;
011import java.util.*;
012
013/**
014 * This class provides methods for calculating Field of View in grids. Field of
015 * View (FOV) algorithms determine how much area surrounding a point can be
016 * seen. They return a 2D array of doubles, representing the amount of view
017 * (typically sight, but perhaps sound, smell, etc.) which the origin has of
018 * each cell. In the returned 2D array, 1.0 is always "fully seen," while 0.0
019 * is always "unseen." Values in between are much more common, and they enable
020 * this class to be used for lighting effects.
021 * <br>
022 * The input resistanceMap is considered the percent of opacity. This resistance
023 * is on top of the resistance applied from the light spreading out. You can
024 * obtain a resistance map easily with the {@link #generateResistances(char[][])}
025 * method, which uses defaults for common chars used in SquidLib, but you may
026 * also want to create a resistance map manually if a given char means something
027 * very different in your game. This is easy enough to do by looping over all the
028 * x,y positions in your char[][] map and running a switch statement on each char,
029 * assigning a double to the same x,y position in a double[][]. The value should
030 * be between 0.0 (unblocked) for things light passes through, 1.0 (blocked) for
031 * things light can't pass at all, and possibly other values if you have
032 * translucent obstacles. There's {@link #generateSimpleResistances(char[][])} as
033 * well, which only returns 1.0 (fully blocked) or 0.0 (passable), and 3x3 subcell
034 * variants, which produce a resistance map that is 3 times wider and 3 times
035 * taller than the input map. The subcell variants have especially useful behavior
036 * when using {@link DungeonUtility#hashesToLines(char[][])} to draw a map with
037 * box-drawing characters, since these 3x3 resistance maps will line up blocking
038 * cells to where a box-drawing line is.
039 * <br>
040 * The returned light map is considered the percent of light in the cells.
041 * <br>
042 * All implementations for FOV here (that is, Ripple FOV and Shadow FOV) provide
043 * percentage levels for partially-lit or partially-seen cells. This leads to a
044 * straightforward implementation of soft lighting using an FOV result -- just
045 * mix the background or floor color of a cell, however you represent it, with
046 * a very light color (like pastel yellow), with the percentage of the light
047 * color to mix in equal to the percent of light in the FOV map.
048 * <br>
049 * All solvers perform bounds checking so solid borders in the map are not
050 * required.
051 * <br>
052 * For calculating FOV maps, this class provides both instance methods, which
053 * attempt to reuse the same 2D array for light stored in the object, and static
054 * methods, which take a light 2D array as an argument and edit it in-place.
055 * In older versions of SquidLib, constantly allocating and returning 2D double
056 * arrays on each call dragged performance down, but both of the new methods
057 * should perform well.
058 * <br>
059 * Static methods are provided to add together FOV maps in the simple way
060 * (disregarding visibility of distant FOV from a given cell), or the more
061 * practical way for roguelikes (where a cell needs to be within line-of-sight
062 * in the first place for a distant light to illuminate it). The second method
063 * relies on an LOS map, which is essentially the same as a very-high-radius
064 * FOV map and can be easily obtained with calculateLOSMap().
065 * <br>
066 * If you want to iterate through cells that are visible in a double[][] returned
067 * by FOV, you can pass that double[][] to the constructor for GreasedRegion, and
068 * you can use the GreasedRegion as a reliably-ordered Collection of Coord (among
069 * other things). The order GreasedRegion iterates in is somewhat strange, and
070 * doesn't, for example, start at the center of an FOV map, but it will be the
071 * same every time you create a GreasedRegion with the same FOV map (or the same
072 * visible Coords).
073 * <br>
074 * This class is not thread-safe. This is generally true for most of SquidLib.
075 *
076 * @author Eben Howard - http://squidpony.com - howard@squidpony.com
077 */
078public class FOV implements Serializable {
079    private static final long serialVersionUID = 3258723684733275798L;
080    /**
081     * Performs FOV by pushing values outwards from the source location.
082     * It will go around corners a bit. This corresponds to a
083     * {@code rippleLooseness} of 2 in {@link #reuseRippleFOV(double[][], double[][], int, int, int, double, Radius)}.
084     */
085    public static final int RIPPLE = 1;
086    /**
087     * Performs FOV by pushing values outwards from the source location.
088     * It will spread around edges like smoke or water, but maintain a
089     * tendency to curl towards the start position when going around
090     * edges. This corresponds to a {@code rippleLooseness} of 3
091     * in {@link #reuseRippleFOV(double[][], double[][], int, int, int, double, Radius)}.
092     */
093    public static final int RIPPLE_LOOSE = 2;
094    /**
095     * Performs FOV by pushing values outwards from the source location.
096     * It will only go around corners slightly. This corresponds to a
097     * {@code rippleLooseness} of 1 in {@link #reuseRippleFOV(double[][], double[][], int, int, int, double, Radius)}.
098     */
099    public static final int RIPPLE_TIGHT = 3;
100    /**
101     * Performs FOV by pushing values outwards from the source location.
102     * It will go around corners massively. This corresponds to a
103     * {@code rippleLooseness} of 6 in {@link #reuseRippleFOV(double[][], double[][], int, int, int, double, Radius)}.
104     */
105    public static final int RIPPLE_VERY_LOOSE = 4;
106    /**
107     * Uses Shadow Casting FOV algorithm. Returns a percentage from 1.0 (center of
108     * FOV) to 0.0 (outside of FOV).
109     */
110    public static final int SHADOW = 5;
111    private int type = SHADOW;
112
113        /**
114         * Data allocated in the previous calls to the public API, if any. Used to
115         * save allocations when multiple calls are done on the same instance.
116         */
117    protected double[][] light;
118        /**
119         * Data allocated in the previous calls to the public API, if any. Used to
120         * save allocations when multiple calls are done on the same instance.
121         */
122    protected GreasedRegion nearLight;
123
124    protected static final Direction[] ccw = new Direction[]
125            {Direction.UP_RIGHT, Direction.UP_LEFT, Direction.DOWN_LEFT, Direction.DOWN_RIGHT, Direction.UP_RIGHT},
126            ccw_full = new Direction[]{Direction.RIGHT, Direction.UP_RIGHT, Direction.UP, Direction.UP_LEFT,
127            Direction.LEFT, Direction.DOWN_LEFT, Direction.DOWN, Direction.DOWN_RIGHT};
128    private static final ArrayDeque<Coord> dq = new ArrayDeque<>();
129    private static final GreasedRegion lightWorkspace = new GreasedRegion(64, 64);
130
131    /**
132     * Creates a solver which will use the default SHADOW solver.
133     */
134    public FOV() {
135    }
136
137    /**
138     * Creates a solver which will use the provided FOV solver type, typically one of {@link #SHADOW} (the default),
139     * {@link #RIPPLE}, {@link #RIPPLE_TIGHT}, {@link #RIPPLE_LOOSE}, or {@link #RIPPLE_VERY_LOOSE}.
140     * @param type
141     */
142    public FOV(int type) {
143        this.type = type;
144    }
145
146    /**
147     * Calculates the Field Of View for the provided map from the given x, y
148     * coordinates. Returns a light map where the values represent a percentage
149     * of fully lit.
150     *
151     * The starting point for the calculation is considered to be at the center
152     * of the origin cell. Radius determinations based on Euclidean
153     * calculations. The light will be treated as having infinite possible
154     * radius.
155     *
156     * @param resistanceMap the grid of cells to calculate on; the kind made by {@link #generateResistances(char[][])}
157     * @param startx the horizontal component of the starting location
158     * @param starty the vertical component of the starting location
159     * @return the computed light grid
160     */
161    public double[][] calculateFOV(double[][] resistanceMap, int startx, int starty) {
162        return calculateFOV(resistanceMap, startx, starty, Integer.MAX_VALUE);
163    }
164
165    /**
166     * Calculates the Field Of View for the provided map from the given x, y
167     * coordinates. Returns a light map where the values represent a percentage
168     * of fully lit.
169     *
170     * The starting point for the calculation is considered to be at the center
171     * of the origin cell. Radius determinations based on Euclidean
172     * calculations.
173     *
174     * @param resistanceMap the grid of cells to calculate on; the kind made by {@link #generateResistances(char[][])}
175     * @param startx the horizontal component of the starting location
176     * @param starty the vertical component of the starting location
177     * @param radius the distance the light will extend to
178     * @return the computed light grid
179     */
180    public double[][] calculateFOV(double[][] resistanceMap, int startx, int starty, double radius) {
181        return calculateFOV(resistanceMap, startx, starty, radius, Radius.CIRCLE);
182    }
183
184    /**
185     * Calculates the Field Of View for the provided map from the given x, y
186     * coordinates. Returns a light map where the values represent a percentage
187     * of fully lit.
188     *
189     * The starting point for the calculation is considered to be at the center
190     * of the origin cell. Radius determinations are determined by the provided
191     * RadiusStrategy.
192     *
193     * @param resistanceMap the grid of cells to calculate on; the kind made by {@link #generateResistances(char[][])}
194     * @param startX the horizontal component of the starting location
195     * @param startY the vertical component of the starting location
196     * @param radius the distance the light will extend to
197     * @param radiusTechnique provides a means to calculate the radius as desired
198     * @return the computed light grid
199     */
200    public double[][] calculateFOV(double[][] resistanceMap, int startX, int startY, double radius, Radius radiusTechnique) {
201        double decay = 1.0 / radius;
202
203        int width = resistanceMap.length;
204        int height = resistanceMap[0].length;
205
206        initializeLightMap(width, height);
207        light[startX][startY] = Math.min(1.0, radius);//make the starting space full power unless radius is tiny
208
209        switch (type) {
210            case RIPPLE:
211            case RIPPLE_LOOSE:
212            case RIPPLE_TIGHT:
213            case RIPPLE_VERY_LOOSE:
214                initializeNearLight(width, height);
215                doRippleFOV(light, rippleValue(type), startX, startY, decay, radius, resistanceMap, nearLight, radiusTechnique);
216                break;
217            default:
218                for (Direction d : Direction.DIAGONALS) {
219                    shadowCast(0, d.deltaX, d.deltaY, 0, radius, startX, startY, decay, light, resistanceMap, radiusTechnique);
220                    shadowCast(d.deltaX, 0, 0, d.deltaY, radius, startX, startY, decay, light, resistanceMap, radiusTechnique);
221                }
222                break;
223        }
224        return light;
225    }
226
227        /**
228     * Calculates the Field Of View for the provided map from the given x, y
229     * coordinates. Returns a light map where the values represent a percentage
230     * of fully lit.
231     *
232     * The starting point for the calculation is considered to be at the center
233     * of the origin cell. Radius determinations are determined by the provided
234     * RadiusStrategy. A conical section of FOV is lit by this method if
235     * span is greater than 0.
236     *
237     * @param resistanceMap the grid of cells to calculate on; the kind made by {@link #generateResistances(char[][])}
238     * @param startX the horizontal component of the starting location
239     * @param startY the vertical component of the starting location
240     * @param radius the distance the light will extend to
241     * @param radiusTechnique provides a means to calculate the radius as desired
242     * @param angle the angle in degrees that will be the center of the FOV cone, 0 points right
243     * @param span the angle in degrees that measures the full arc contained in the FOV cone
244     * @return the computed light grid
245     */
246    public double[][] calculateFOV(double[][] resistanceMap, int startX, int startY, double radius,
247                                   Radius radiusTechnique, double angle, double span) {
248        double decay = 1.0 / radius;
249        angle = ((angle >= 360.0 || angle < 0.0)
250                ? MathExtras.remainder(angle, 360.0) : angle) * 0.002777777777777778;
251        span = span * 0.002777777777777778;
252        int width = resistanceMap.length;
253        int height = resistanceMap[0].length;
254
255        initializeLightMap(width, height);
256        light[startX][startY] = Math.min(1.0, radius);//make the starting space full power unless radius is tiny
257
258        switch (type) {
259            case RIPPLE:
260            case RIPPLE_LOOSE:
261            case RIPPLE_TIGHT:
262            case RIPPLE_VERY_LOOSE:
263                initializeNearLight(width, height);
264                doRippleFOV(light, rippleValue(type), startX, startY, decay, radius, resistanceMap, nearLight, radiusTechnique, angle, span);
265                break;
266            default:
267                light = shadowCastLimited(1, 1.0, 0.0, 0, 1, 1, 0, radius, startX, startY, decay, light, resistanceMap, radiusTechnique, angle, span);
268                light = shadowCastLimited(1, 1.0, 0.0, 1, 0, 0, 1, radius, startX, startY, decay, light, resistanceMap, radiusTechnique, angle, span);
269
270                light = shadowCastLimited(1, 1.0, 0.0, 0, -1, 1, 0, radius, startX, startY, decay, light, resistanceMap, radiusTechnique, angle, span);
271                light = shadowCastLimited(1, 1.0, 0.0, -1, 0, 0, 1, radius, startX, startY, decay, light, resistanceMap, radiusTechnique, angle, span);
272
273                light = shadowCastLimited(1, 1.0, 0.0, 0, -1, -1, 0, radius, startX, startY, decay, light, resistanceMap, radiusTechnique, angle, span);
274                light = shadowCastLimited(1, 1.0, 0.0, -1, 0, 0, -1, radius, startX, startY, decay, light, resistanceMap, radiusTechnique, angle, span);
275
276                light = shadowCastLimited(1, 1.0, 0.0, 0, 1, -1, 0, radius, startX, startY, decay, light, resistanceMap, radiusTechnique, angle, span);
277                light = shadowCastLimited(1, 1.0, 0.0, 1, 0, 0, -1, radius, startX, startY, decay, light, resistanceMap, radiusTechnique, angle, span);
278                break;
279        }
280
281        return light;
282    }
283
284
285    /**
286     * Calculates the Field Of View for the provided map from the given x, y
287     * coordinates. Assigns to, and returns, a light map where the values
288     * represent a percentage of fully lit. Always uses shadowcasting FOV,
289     * which allows this method to be static since it doesn't need to keep any
290     * state around, and can reuse the state the user gives it via the
291     * {@code light} parameter.  The values in light are always cleared before
292     * this is run, because prior state can make this give incorrect results.
293     * <br>
294     * The starting point for the calculation is considered to be at the center
295     * of the origin cell. Radius determinations based on Euclidean
296     * calculations. The light will be treated as having infinite possible
297     * radius.
298     * <br>
299     * This static method is equivalent to the static method {@link #reuseFOV(double[][], double[][], int, int)},
300     * but all of the overloads are called reuseFOV(), so this method name is discouraged for new code. Both delegate to
301     * {@link #reuseFOV(double[][], double[][], int, int, double, Radius)} anyway.
302     * 
303     * @param resistanceMap the grid of cells to calculate on; the kind made by {@link #generateResistances(char[][])}
304     * @param light a non-null 2D double array that will have its contents overwritten, modified, and returned
305     * @param startx the horizontal component of the starting location
306     * @param starty the vertical component of the starting location
307     * @return the computed light grid (the same as {@code light})
308     */
309    public static double[][] calculateFOV(double[][] resistanceMap, double[][] light, int startx, int starty) {
310        return reuseFOV(resistanceMap, light, startx, starty, Integer.MAX_VALUE, Radius.CIRCLE);
311    }
312
313    /**
314     * Calculates the Field Of View for the provided map from the given x, y
315     * coordinates. Assigns to, and returns, a light map where the values
316     * represent a percentage of fully lit. Always uses shadowcasting FOV,
317     * which allows this method to be static since it doesn't need to keep any
318     * state around, and can reuse the state the user gives it via the
319     * {@code light} parameter.  The values in light are always cleared before
320     * this is run, because prior state can make this give incorrect results.
321     * <br>
322     * The starting point for the calculation is considered to be at the center
323     * of the origin cell. Radius determinations based on Euclidean
324     * calculations. The light will be treated as having infinite possible
325     * radius.
326     * <br>
327     * This static method is equivalent to the static method {@link #calculateFOV(double[][], double[][], int, int)},
328     * but all of the overloads are called reuseFOV(), so this method name is preferred in new code. Both delegate to
329     * {@link #reuseFOV(double[][], double[][], int, int, double, Radius)} anyway.
330     *
331     * @param resistanceMap the grid of cells to calculate on; the kind made by {@link #generateResistances(char[][])}
332     * @param light a non-null 2D double array that will have its contents overwritten, modified, and returned
333     * @param startx the horizontal component of the starting location
334     * @param starty the vertical component of the starting location
335     * @return the computed light grid (the same as {@code light})
336     */
337    public static double[][] reuseFOV(double[][] resistanceMap, double[][] light, int startx, int starty) {
338        return reuseFOV(resistanceMap, light, startx, starty, Integer.MAX_VALUE, Radius.CIRCLE);
339    }
340
341    /**
342     * Calculates the Field Of View for the provided map from the given x, y
343     * coordinates. Assigns to, and returns, a light map where the values
344     * represent a percentage of fully lit. Always uses shadowcasting FOV,
345     * which allows this method to be static since it doesn't need to keep any
346     * state around, and can reuse the state the user gives it via the
347     * {@code light} parameter. The values in light are always cleared before
348     * this is run, because prior state can make this give incorrect results.
349     * <br>
350     * The starting point for the calculation is considered to be at the center
351     * of the origin cell. Radius determinations based on Euclidean
352     * calculations.
353     *
354     * @param resistanceMap the grid of cells to calculate on; the kind made by {@link #generateResistances(char[][])}
355     * @param startx the horizontal component of the starting location
356     * @param starty the vertical component of the starting location
357     * @param radius the distance the light will extend to
358     * @return the computed light grid
359     */
360    public static double[][] reuseFOV(double[][] resistanceMap, double[][] light, int startx, int starty, double radius) {
361        return reuseFOV(resistanceMap, light, startx, starty, radius, Radius.CIRCLE);
362    }
363
364
365    /**
366     * Calculates the Field Of View for the provided map from the given x, y
367     * coordinates. Assigns to, and returns, a light map where the values
368     * represent a percentage of fully lit. Always uses shadowcasting FOV,
369     * which allows this method to be static since it doesn't need to keep any
370     * state around, and can reuse the state the user gives it via the
371     * {@code light} parameter. The values in light are always cleared before
372     * this is run, because prior state can make this give incorrect results.
373     * <br>
374     * The starting point for the calculation is considered to be at the center
375     * of the origin cell. Radius determinations are determined by the provided
376     * RadiusStrategy.
377     * @param resistanceMap the grid of cells to calculate on; the kind made by {@link #generateResistances(char[][])}
378     * @param light the grid of cells to assign to; may have existing values, and 0.0 is used to mean "unlit"
379     * @param startX the horizontal component of the starting location
380     * @param startY the vertical component of the starting location
381     * @param radius the distance the light will extend to
382     * @param radiusTechnique provides a means to calculate the radius as desired
383     * @return the computed light grid, which is the same 2D array as the value assigned to {@code light}
384     */
385    public static double[][] reuseFOV(double[][] resistanceMap, double[][] light, int startX, int startY, double radius, Radius radiusTechnique)
386    {
387        double decay = 1.0 / radius;
388        ArrayTools.fill(light, 0);
389        light[startX][startY] = Math.min(1.0, radius);//make the starting space full power unless radius is tiny
390
391
392        shadowCast(0, 1, 1, 0, radius, startX, startY, decay, light, resistanceMap, radiusTechnique);
393        shadowCast(1, 0, 0, 1, radius, startX, startY, decay, light, resistanceMap, radiusTechnique);
394
395        shadowCast(0, 1, -1, 0, radius, startX, startY, decay, light, resistanceMap, radiusTechnique);
396        shadowCast(1, 0, 0, -1, radius, startX, startY, decay, light, resistanceMap, radiusTechnique);
397
398        shadowCast(0, -1, -1, 0, radius, startX, startY, decay, light, resistanceMap, radiusTechnique);
399        shadowCast(-1, 0, 0, -1, radius, startX, startY, decay, light, resistanceMap, radiusTechnique);
400
401        shadowCast(0, -1, 1, 0, radius, startX, startY, decay, light, resistanceMap, radiusTechnique);
402        shadowCast(-1, 0, 0, 1, radius, startX, startY, decay, light, resistanceMap, radiusTechnique);
403        return light;
404    }
405    /**
406     * Calculates the Field Of View for the provided map from the given x, y
407     * coordinates. Assigns to, and returns, a light map where the values
408     * represent a percentage of fully lit. Always uses shadowcasting FOV,
409     * which allows this method to be static since it doesn't need to keep any
410     * state around, and can reuse the state the user gives it via the
411     * {@code light} parameter. The values in light are always cleared before
412     * this is run, because prior state can make this give incorrect results.
413     * <br>
414     * The starting point for the calculation is considered to be at the center
415     * of the origin cell. Radius determinations are determined by the provided
416     * RadiusStrategy.
417     * @param resistanceMap the grid of cells to calculate on; the kind made by {@link #generateResistances(char[][])}
418     * @param light the grid of cells to assign to; may have existing values, and 0.0 is used to mean "unlit"
419     * @param startX the horizontal component of the starting location
420     * @param startY the vertical component of the starting location
421     * @param radius the distance the light will extend to
422     * @param radiusTechnique provides a means to calculate the radius as desired
423     * @return the computed light grid, which is the same 2D array as the value assigned to {@code light}
424     */
425    public static double[][] reuseFOVSymmetrical(double[][] resistanceMap, double[][] light, int startX, int startY, double radius, Radius radiusTechnique)
426    {
427        double decay = 1.0 / radius;
428        ArrayTools.fill(light, 0);
429        light[startX][startY] = Math.min(1.0, radius);//make the starting space full power unless radius is tiny
430
431
432        shadowCast(0, 1, 1, 0, radius, startX, startY, decay, light, resistanceMap, radiusTechnique);
433        for (int row = 0; row <= radius + 1.0; row++) {
434            for (int col = Math.max(1,row); col <= radius + 1.0; col++) {
435                if(startX - col >= 0 && startY - row >= 0 && resistanceMap[startX - col][startY - row] < 1.0 &&
436                        !shadowCastCheck(1, 1.0, 0.0, 0, -1, -1, 0, radius, startX - col, startY - row, decay, light, resistanceMap, radiusTechnique, 0, 0, light.length, light[0].length, startX, startY))
437                    light[startX - col][startY - row] = 0.0;
438            }
439        }
440        shadowCast(1, 0, 0, 1, radius, startX, startY, decay, light, resistanceMap, radiusTechnique);
441        for (int col = 0; col <= radius + 1.0; col++) {
442            for (int row = Math.max(1,col); row <= radius + 1.0; row++) {
443                if(startX - col >= 0 && startY - row >= 0 && resistanceMap[startX - col][startY - row] < 1.0 &&
444                        !shadowCastCheck(1, 1.0, 0.0, -1, 0, 0, -1, radius, startX - col, startY - row, decay, light, resistanceMap, radiusTechnique, 0, 0, light.length, light[0].length, startX, startY))
445                    light[startX - col][startY - row] = 0.0;
446            }
447        }
448
449        shadowCast(0, 1, -1, 0, radius, startX, startY, decay, light, resistanceMap, radiusTechnique);
450        for (int row = 0; row <= radius + 1.0; row++) {
451            for (int col = Math.max(1,row); col <= radius + 1.0; col++) {
452                if(startX - col >= 0 && startY + row < light[0].length &&  resistanceMap[startX - col][startY + row] < 1.0 &&
453                        !shadowCastCheck(1, 1.0, 0.0, 0, -1, 1, 0, radius, startX - col, startY + row, decay, light, resistanceMap, radiusTechnique, 0, 0, light.length, light[0].length, startX, startY))
454                    light[startX - col][startY + row] = 0.0;
455            }
456        }
457        shadowCast(1, 0, 0, -1, radius, startX, startY, decay, light, resistanceMap, radiusTechnique);
458        for (int col = 0; col <= radius + 1.0; col++) {
459            for (int row = Math.max(1,col); row <= radius + 1.0; row++) {
460                if(startX - col >= 0 && startY + row < light[0].length && resistanceMap[startX - col][startY + row] < 1.0 &&
461                        !shadowCastCheck(1, 1.0, 0.0, -1, 0, 0, 1, radius, startX - col, startY + row, decay, light, resistanceMap, radiusTechnique, 0, 0, light.length, light[0].length, startX, startY))
462                    light[startX - col][startY + row] = 0.0;
463            }
464        }
465
466        shadowCast(0, -1, -1, 0, radius, startX, startY, decay, light, resistanceMap, radiusTechnique);
467        for (int row = 0; row <= radius + 1.0; row++) {
468            for (int col = Math.max(1,row); col <= radius + 1.0; col++) {
469                if(startX + col < light.length && startY + row < light[0].length && resistanceMap[startX + col][startY + row] < 1.0 &&
470                        !shadowCastCheck(1, 1.0, 0.0, 0, 1, 1, 0, radius, startX + col, startY + row, decay, light, resistanceMap, radiusTechnique, 0, 0, light.length, light[0].length, startX, startY))
471                    light[startX + col][startY + row] = 0.0;
472            }
473        }
474        shadowCast(-1, 0, 0, -1, radius, startX, startY, decay, light, resistanceMap, radiusTechnique);
475        for (int col = 0; col <= radius + 1.0; col++) {
476            for (int row = Math.max(1,col); row <= radius + 1.0; row++) {
477                if(startX + col < light.length && startY + row < light[0].length && resistanceMap[startX + col][startY + row] < 1.0 &&
478                        !shadowCastCheck(1, 1.0, 0.0, 1, 0, 0, 1, radius, startX + col, startY + row, decay, light, resistanceMap, radiusTechnique, 0, 0, light.length, light[0].length, startX, startY))
479                    light[startX + col][startY + row] = 0.0;
480            }
481        }
482
483        shadowCast(0, -1, 1, 0, radius, startX, startY, decay, light, resistanceMap, radiusTechnique);
484        for (int row = 0; row <= radius + 1.0 && startY + row < light[0].length; row++) {
485            for (int col = Math.max(1,row); col <= radius + 1.0; col++) {
486                if(startX + col < light.length && startY - row >= 0 && resistanceMap[startX + col][startY - row] < 1.0 &&
487                        !shadowCastCheck(1, 1.0, 0.0, 0, 1, -1, 0, radius, startX + col, startY - row, decay, light, resistanceMap, radiusTechnique, 0, 0, light.length, light[0].length, startX, startY))
488                    light[startX + col][startY - row] = 0.0;
489            }
490        }
491        shadowCast(-1, 0, 0, 1, radius, startX, startY, decay, light, resistanceMap, radiusTechnique);
492        for (int col = 0; col <= radius + 1.0; col++) {
493            for (int row = Math.max(1,col); row <= radius + 1.0; row++) {
494                if(startX + col < light.length && startY - row >= 0 && resistanceMap[startX + col][startY - row] < 1.0 &&
495                        !shadowCastCheck(1, 1.0, 0.0, 1, 0, 0, -1, radius, startX + col, startY - row, decay, light, resistanceMap, radiusTechnique, 0, 0, light.length, light[0].length, startX, startY))
496                    light[startX + col][startY - row] = 0.0;
497            }
498        }
499        return light;
500    }
501    /**
502     * Calculates which cells have line of sight from the given x, y coordinates.
503     * Assigns to, and returns, a light map where the values
504     * are always either 0.0 for "not in line of sight" or 1.0 for "in line of
505     * sight," which doesn't mean a cell is actually visible if there's no light
506     * in that cell. Always uses shadowcasting FOV, which allows this method to
507     * be static since it doesn't need to keep any state around, and can reuse the
508     * state the user gives it via the {@code light} parameter. The values in light
509     * are always cleared before this is run, because prior state can make this give
510     * incorrect results.
511     * <br>
512     * The starting point for the calculation is considered to be at the center
513     * of the origin cell. Radius determinations are pretty much irrelevant because
514     * the distance doesn't matter, only the presence of a clear line, but this uses
515     * {@link Radius#SQUARE} if it matters.
516     * @param resistanceMap the grid of cells to calculate on; the kind made by {@link #generateResistances(char[][])}
517     * @param light the grid of cells to assign to; may have existing values, and 0.0 is used to mean "no line"
518     * @param startX the horizontal component of the starting location
519     * @param startY the vertical component of the starting location
520     * @return the computed light grid, which is the same 2D array as the value assigned to {@code light}
521     */
522    public static double[][] reuseLOS(double[][] resistanceMap, double[][] light, int startX, int startY)
523    {
524        return reuseLOS(resistanceMap, light, startX, startY, 0, 0, light.length, light[0].length);
525    }
526    /**
527     * Calculates which cells have line of sight from the given x, y coordinates.
528     * Assigns to, and returns, a light map where the values
529     * are always either 0.0 for "not in line of sight" or 1.0 for "in line of
530     * sight," which doesn't mean a cell is actually visible if there's no light
531     * in that cell. Always uses shadowcasting FOV, which allows this method to
532     * be static since it doesn't need to keep any state around, and can reuse the
533     * state the user gives it via the {@code light} parameter. The values in light
534     * are always cleared before this is run, because prior state can make this give
535     * incorrect results.
536     * <br>
537     * The starting point for the calculation is considered to be at the center
538     * of the origin cell. Radius determinations are pretty much irrelevant because
539     * the distance doesn't matter, only the presence of a clear line, but this uses
540     * {@link Radius#SQUARE} if it matters.
541     * @param resistanceMap the grid of cells to calculate on; the kind made by {@link #generateResistances(char[][])}
542     * @param light the grid of cells to assign to; may have existing values, and 0.0 is used to mean "no line"
543     * @param startX the horizontal component of the starting location
544     * @param startY the vertical component of the starting location
545     * @return the computed light grid, which is the same 2D array as the value assigned to {@code light}
546     */
547    public static double[][] reuseLOS(double[][] resistanceMap, double[][] light, int startX, int startY,
548                                      int minX, int minY, int maxX, int maxY)
549    {
550        double radius = light.length + light[0].length;
551        double decay = 1.0 / radius;
552        ArrayTools.fill(light, 0);
553        light[startX][startY] = 1;//make the starting space full power
554        
555        shadowCastBinary(1, 1.0, 0.0, 0, 1, 1, 0, radius, startX, startY, decay, light, resistanceMap, Radius.SQUARE, minX, minY, maxX, maxY);
556        shadowCastBinary(1, 1.0, 0.0, 1, 0, 0, 1, radius, startX, startY, decay, light, resistanceMap, Radius.SQUARE, minX, minY, maxX, maxY);
557        shadowCastBinary(1, 1.0, 0.0, 0, 1, -1, 0, radius, startX, startY, decay, light, resistanceMap, Radius.SQUARE, minX, minY, maxX, maxY);
558        shadowCastBinary(1, 1.0, 0.0, 1, 0, 0, -1, radius, startX, startY, decay, light, resistanceMap, Radius.SQUARE, minX, minY, maxX, maxY);
559        shadowCastBinary(1, 1.0, 0.0, 0, -1, -1, 0, radius, startX, startY, decay, light, resistanceMap, Radius.SQUARE, minX, minY, maxX, maxY);
560        shadowCastBinary(1, 1.0, 0.0, -1, 0, 0, -1, radius, startX, startY, decay, light, resistanceMap, Radius.SQUARE, minX, minY, maxX, maxY);
561        shadowCastBinary(1, 1.0, 0.0, 0, -1, 1, 0, radius, startX, startY, decay, light, resistanceMap, Radius.SQUARE, minX, minY, maxX, maxY);
562        shadowCastBinary(1, 1.0, 0.0, -1, 0, 0, 1, radius, startX, startY, decay, light, resistanceMap, Radius.SQUARE, minX, minY, maxX, maxY);
563        
564        return light;
565    }
566    /**
567     * Calculates the Field Of View for the provided map from the given x, y
568     * coordinates, lighting at the given angle in  degrees and covering a span
569     * centered on that angle, also in degrees. Assigns to, and returns, a light
570     * map where the values represent a percentage of fully lit. Always uses
571     * shadowcasting FOV, which allows this method to be static since it doesn't
572     * need to keep any state around, and can reuse the state the user gives it
573     * via the {@code light} parameter. The values in light are cleared before
574     * this is run, because prior state can make this give incorrect results.
575     * <br>
576     * The starting point for the calculation is considered to be at the center
577     * of the origin cell. Radius determinations are determined by the provided
578     * RadiusStrategy.  A conical section of FOV is lit by this method if
579     * span is greater than 0.
580     *
581     * @param resistanceMap the grid of cells to calculate on; the kind made by {@link #generateResistances(char[][])}
582     * @param light the grid of cells to assign to; may have existing values, and 0.0 is used to mean "unlit"
583     * @param startX the horizontal component of the starting location
584     * @param startY the vertical component of the starting location
585     * @param radius the distance the light will extend to
586     * @param radiusTechnique provides a means to shape the FOV by changing distance calculation (circle, square, etc.)
587     * @param angle the angle in degrees that will be the center of the FOV cone, 0 points right
588     * @param span the angle in degrees that measures the full arc contained in the FOV cone
589     * @return the computed light grid
590     */
591    public static double[][] reuseFOV(double[][] resistanceMap, double[][] light, int startX, int startY,
592                                          double radius, Radius radiusTechnique, double angle, double span) {
593        double decay = 1.0 / radius;
594        ArrayTools.fill(light, 0);
595        light[startX][startY] = Math.min(1.0, radius);//make the starting space full power unless radius is tiny
596        angle = ((angle >= 360.0 || angle < 0.0)
597                ? MathExtras.remainder(angle, 360.0) : angle) * 0.002777777777777778;
598        span = span * 0.002777777777777778;
599
600
601        light = shadowCastLimited(1, 1.0, 0.0, 0, 1, 1, 0, radius, startX, startY, decay, light, resistanceMap, radiusTechnique, angle, span);
602        light = shadowCastLimited(1, 1.0, 0.0, 1, 0, 0, 1, radius, startX, startY, decay, light, resistanceMap, radiusTechnique, angle, span);
603
604        light = shadowCastLimited(1, 1.0, 0.0, 0, -1, 1, 0, radius, startX, startY, decay, light, resistanceMap, radiusTechnique, angle, span);
605        light = shadowCastLimited(1, 1.0, 0.0, -1, 0, 0, 1, radius, startX, startY, decay, light, resistanceMap, radiusTechnique, angle, span);
606
607        light = shadowCastLimited(1, 1.0, 0.0, 0, -1, -1, 0, radius, startX, startY, decay, light, resistanceMap, radiusTechnique, angle, span);
608        light = shadowCastLimited(1, 1.0, 0.0, -1, 0, 0, -1, radius, startX, startY, decay, light, resistanceMap, radiusTechnique, angle, span);
609
610        light = shadowCastLimited(1, 1.0, 0.0, 0, 1, -1, 0, radius, startX, startY, decay, light, resistanceMap, radiusTechnique, angle, span);
611        light = shadowCastLimited(1, 1.0, 0.0, 1, 0, 0, -1, radius, startX, startY, decay, light, resistanceMap, radiusTechnique, angle, span);
612        return light;
613    }
614
615    /**
616     * Like the {@link #reuseFOV(double[][], double[][], int, int, double, Radius)} method, but this uses Ripple FOV
617     * with a configurable tightness/looseness (between 1, tightest, and 6, loosest). Other parameters are similar; you
618     * can get a resistance map from {@link #generateResistances(char[][])}, {@code light} will be modified and returned
619     * (it will be overwritten, but its size should be the same as the resistance map), there's a starting x,y position,
620     * a radius in cells, and a {@link Radius} enum constant to choose the distance measurement.
621     * @param resistanceMap probably calculated with {@link #generateResistances(char[][])}; 1.0 blocks light, 0.0 allows it
622     * @param light will be overwritten! Should be initialized with the same size as {@code resistanceMap}
623     * @param rippleLooseness affects spread; between 1 and 6, inclusive; 1 is tightest, 2 is normal, and 6 is loosest
624     * @param x starting x position to look from
625     * @param y starting y position to look from
626     * @param radius the distance to extend from the starting x,y position
627     * @param radiusTechnique how to measure distance; typically {@link Radius#CIRCLE}.
628     * @return {@code light}, after writing the FOV map into it; 1.0 is fully lit and 0.0 is unseen
629     */
630    public static double[][] reuseRippleFOV(double[][] resistanceMap, double[][] light, int rippleLooseness, int x, int y, double radius, Radius radiusTechnique) {
631        ArrayTools.fill(light, 0);
632        light[x][y] = Math.min(1.0, radius);//make the starting space full power unless radius is tiny
633        lightWorkspace.resizeAndEmpty(light.length, light[0].length);
634        doRippleFOV(light, MathExtras.clamp(rippleLooseness, 1, 6), x, y, 1.0 / radius, radius, resistanceMap, lightWorkspace, radiusTechnique);
635        return light;
636    }
637
638    /**
639     * Like the {@link #reuseFOV(double[][], double[][], int, int, double, Radius, double, double)} method, but this
640     * uses Ripple FOV with a configurable tightness/looseness (between 1, tightest, and 6, loosest). Other parameters
641     * are similar; you can get a resistance map from {@link #generateResistances(char[][])}, {@code light} will be
642     * modified and returned (it will be overwritten, but its size should be the same as the resistance map), there's 
643     * starting x,y position, a radius in cells, a {@link Radius} enum constant to choose the distance measurement, and
644     * the angle/span combination to specify a conical section of FOV (span is the total in degrees, centered on angle).
645     * @param resistanceMap probably calculated with {@link #generateResistances(char[][])}; 1.0 blocks light, 0.0 allows it
646     * @param light will be overwritten! Should be initialized with the same size as {@code resistanceMap}
647     * @param rippleLooseness affects spread; between 1 and 6, inclusive; 1 is tightest, 2 is normal, and 6 is loosest
648     * @param x starting x position to look from
649     * @param y starting y position to look from
650     * @param radius the distance to extend from the starting x,y position
651     * @param radiusTechnique how to measure distance; typically {@link Radius#CIRCLE}.
652     * @param angle the angle to center the conical FOV on
653     * @param span the total span in degrees for the conical FOV to cover
654     * @return {@code light}, after writing the FOV map into it; 1.0 is fully lit and 0.0 is unseen
655     */
656    public static double[][] reuseRippleFOV(double[][] resistanceMap, double[][] light, int rippleLooseness, int x, int y, double radius, Radius radiusTechnique, double angle, double span) {
657        ArrayTools.fill(light, 0);
658        light[x][y] = Math.min(1.0, radius);//make the starting space full power unless radius is tiny
659        lightWorkspace.resizeAndEmpty(light.length, light[0].length);
660        angle = ((angle >= 360.0 || angle < 0.0)
661                ? MathExtras.remainder(angle, 360.0) : angle) * 0.002777777777777778;
662        span = span * 0.002777777777777778;
663        doRippleFOV(light, MathExtras.clamp(rippleLooseness, 1, 6), x, y, 1.0 / radius, radius, resistanceMap, lightWorkspace, radiusTechnique, angle, span);
664        return light;
665    }
666
667        /**
668         * Reuses the existing light 2D array and fills it with a straight-line bouncing path of light that reflects its way
669         * through the given resistanceMap from startX, startY until it uses up the given distance. The angle the path
670         * takes is given in degrees, and the angle used can change as obstacles are hit (reflecting backwards if it hits a
671         * corner pointing directly into or away from its path). This can be used something like an LOS method, but because
672         * the path can be traveled back over, an array or Queue becomes somewhat more complex, and the decreasing numbers
673         * for a straight line that stack may make more sense for how this could be used (especially with visual effects).
674         * This currently allows the path to pass through single-cell wall-like obstacles without changing direction, e.g.
675         * it passes through pillars, but will bounce if it hits a bigger wall.
676         * @param resistanceMap the grid of cells to calculate on; the kind made by {@link #generateResistances(char[][])}
677         * @param light the grid of cells to assign to; may have existing values, and 0.0 is used to mean "unlit"
678         * @param startX the horizontal component of the starting location
679         * @param startY the vertical component of the starting location
680         * @param distance the distance the light will extend to
681         * @param angle in degrees, the angle to start the path traveling in
682         * @return the given light parameter, after modifications
683         */
684    public static double[][] bouncingLine(double[][] resistanceMap, double[][] light, int startX, int startY, double distance, double angle)
685    {
686        double rad = Math.max(1, distance);
687        double decay = 1.0 / rad;
688        ArrayTools.fill(light, 0);
689        light[startX][startY] = 1;//make the starting space full power
690        angle = Math.toRadians((angle > 360.0 || angle < 0.0)
691                ? MathExtras.remainder(angle, 360.0) : angle);
692        float s = (float) NumberTools.sin(angle),
693                c = (float) NumberTools.cos(angle);
694        double deteriorate = 1.0;
695        int dx, dy, width = resistanceMap.length, height = resistanceMap[0].length;
696        for (int d = 1; d <= rad; ) {
697            dx = startX + Math.round(c * d);
698            if(dx < 0 || dx > width)
699                break;
700            dy = startY + Math.round(s * d);
701            if(dy < 0 || dy > height)
702                break;
703            deteriorate -= decay;
704            //check if it's within the lightable area and light if needed
705            if (deteriorate > 0.0) {
706                light[dx][dy] = Math.min(light[dx][dy] + deteriorate, 1.0);
707                if (resistanceMap[dx][dy] >= 1 && deteriorate > decay)
708                {
709                    startX = dx;
710                    startY = dy;
711                    d = 1;
712                    double flipX = resistanceMap[startX + Math.round(-c * d)][dy],
713                            flipY = resistanceMap[dx][startY + Math.round(-s * d)];
714                    if(flipX >= 1.0)
715                        s = -s;
716                    if(flipY >= 1.0)
717                        c = -c;
718                }
719                else ++d;
720
721            }
722            else break;
723        }
724
725        return light;
726    }
727    /**
728         * @param width
729         *            The width that {@link #light} should have.
730         * @param height
731         *            The height that {@link #light} should have.
732         */
733        private void initializeLightMap(int width, int height) {
734                if (light == null)
735                        light = new double[width][height];
736                else {
737                        if (light.length != width || light[0].length != height)
738                                /* Size changed */
739                                light = new double[width][height];
740                        else {
741                                /*
742                                 * Size did not change, we simply need to erase the previous
743                                 * result
744                                 */
745                                ArrayTools.fill(light, 0.0);
746                        }
747                }
748        }
749
750        /**
751         * @param width
752         *            The width that {@link #nearLight} should have.
753         * @param height
754         *            The height that {@link #nearLight} should have.
755         */
756        private void initializeNearLight(int width, int height) {
757                if (nearLight == null) {
758                    /* Can't do much with null */
759            nearLight = new GreasedRegion(width, height);
760        }
761                else {
762                        if (nearLight.width != width || nearLight.height != height) {
763                /* Size changed */
764                nearLight.resizeAndEmpty(width, height);
765            }
766                        else {
767                                /*
768                                 * Size did not change, we simply need to erase the previous
769                                 * result
770                                 */
771                                nearLight.clear();
772                        }
773                }
774        }
775
776        private static int rippleValue(int type) {
777                switch (type) {
778                case RIPPLE:
779                        return 2;
780                case RIPPLE_LOOSE:
781                        return 3;
782                case RIPPLE_TIGHT:
783                        return 1;
784                case RIPPLE_VERY_LOOSE:
785                        return 6;
786                default:
787                        System.err.println("Unrecognized ripple type: " + type + ". Defaulting to RIPPLE");
788                        return 2;
789                }
790        }
791
792    private static void doRippleFOV(double[][] lightMap, int ripple, int x, int y, double decay, double radius, double[][] map, GreasedRegion indirect, Radius radiusStrategy) {
793        dq.clear();
794        int width = lightMap.length;
795        int height = lightMap[0].length;
796        dq.offer(Coord.get(x, y));
797        while (!dq.isEmpty()) {
798            Coord p = dq.removeFirst();
799            if (lightMap[p.x][p.y] <= 0 || indirect.contains(p)) {
800                continue;//no light to spread
801            }
802
803            for (Direction dir : Direction.OUTWARDS) {
804                int x2 = p.x + dir.deltaX;
805                int y2 = p.y + dir.deltaY;
806                if (x2 < 0 || x2 >= width || y2 < 0 || y2 >= height //out of bounds
807                        || radiusStrategy.radius(x, y, x2, y2) >= radius + 1) {//+1 to cover starting tile
808                    continue;
809                }
810
811                double surroundingLight = nearRippleLight(x2, y2, ripple, x, y, decay, lightMap, map, indirect, radiusStrategy);
812                if (lightMap[x2][y2] < surroundingLight) {
813                    lightMap[x2][y2] = surroundingLight;
814                    if (map[x2][y2] < 1) {//make sure it's not a wall
815                        dq.offer(Coord.get(x2, y2));//redo neighbors since this one's light changed
816                    }
817                }
818            }
819        }
820    }
821
822
823
824    private static void doRippleFOV(double[][] lightMap, int ripple, int x, int y, double decay, double radius, double[][] map, GreasedRegion indirect, Radius radiusStrategy, double angle, double span) {
825            dq.clear();
826        int width = lightMap.length;
827        int height = lightMap[0].length;
828        dq.offer(Coord.get(x, y));
829        while (!dq.isEmpty()) {
830            Coord p = dq.removeFirst();
831            if (lightMap[p.x][p.y] <= 0 || indirect.contains(p)) {
832                continue;//no light to spread
833            }
834
835            for (Direction dir : ccw_full) {
836                int x2 = p.x + dir.deltaX;
837                int y2 = p.y + dir.deltaY;
838                if (x2 < 0 || x2 >= width || y2 < 0 || y2 >= height //out of bounds
839                        || radiusStrategy.radius(x, y, x2, y2) >= radius + 1) {//+1 to cover starting tile
840                    continue;
841                }
842                double newAngle = NumberTools.atan2_(y2 - y, x2 - x);
843                if (newAngle > span * 0.5 && newAngle < 1.0 - span * 0.5) 
844                    continue;
845//if (Math.abs(MathExtras.remainder(angle - newAngle, Math.PI * 2)) > span * 0.5)
846
847                double surroundingLight = nearRippleLight(x2, y2, ripple, x, y, decay, lightMap, map, indirect, radiusStrategy );
848                if (lightMap[x2][y2] < surroundingLight) {
849                    lightMap[x2][y2] = surroundingLight;
850                    if (map[x2][y2] < 1) {//make sure it's not a wall
851                        dq.offer(Coord.get(x2, y2));//redo neighbors since this one's light changed
852                    }
853                }
854            }
855        }
856    }
857
858    private static double nearRippleLight(int x, int y, int rippleNeighbors, int startx, int starty, double decay, double[][] lightMap, double[][] map, GreasedRegion indirect, Radius radiusStrategy) {
859        if (x == startx && y == starty) {
860            return 1;
861        }
862        int width = lightMap.length;
863        int height = lightMap[0].length;
864        List<Coord> neighbors = new ArrayList<>();
865        double tmpDistance = 0, testDistance;
866        Coord c;
867        for (Direction di : Direction.OUTWARDS) {
868            int x2 = x + di.deltaX;
869            int y2 = y + di.deltaY;
870            if (x2 >= 0 && x2 < width && y2 >= 0 && y2 < height) {
871                tmpDistance = radiusStrategy.radius(startx, starty, x2, y2);
872                int idx = 0;
873                for(int i = 0; i < neighbors.size() && i <= rippleNeighbors; i++)
874                {
875                    c = neighbors.get(i);
876                    testDistance = radiusStrategy.radius(startx, starty, c.x, c.y);
877                    if(tmpDistance < testDistance) {
878                        break;
879                    }
880                    idx++;
881                }
882                neighbors.add(idx, Coord.get(x2, y2));
883            }
884        }
885
886        if (neighbors.isEmpty()) {
887            return 0;
888        }
889        neighbors = neighbors.subList(0, Math.min(neighbors.size(), rippleNeighbors));
890/*
891        while (neighbors.size() > rippleNeighbors) {
892            Coord p = neighbors.remove(0);
893            double dist = radiusStrategy.radius(startx, starty, p.x, p.y);
894            double dist2 = 0;
895            for (Coord p2 : neighbors) {
896                dist2 = Math.max(dist2, radiusStrategy.radius(startx, starty, p2.x, p2.y));
897            }
898            if (dist < dist2) {//not the largest, put it back
899                neighbors.add(p);
900            }
901        }
902*/
903        double light = 0;
904        int lit = 0, indirects = 0;
905        for (Coord p : neighbors) {
906            if (lightMap[p.x][p.y] > 0) {
907                lit++;
908                if (indirect.contains(p)) {
909                    indirects++;
910                }
911                double dist = radiusStrategy.radius(x, y, p.x, p.y);
912                light = Math.max(light, lightMap[p.x][p.y] - dist * decay - map[p.x][p.y]);
913            }
914        }
915
916        if (map[x][y] >= 1 || indirects >= lit) {
917            indirect.insert(x, y);
918        }
919        return light;
920    }
921
922    private static void shadowCast(int xx, int xy, int yx, int yy,
923                                   double radius, int startx, int starty, double decay, double[][] lightMap,
924                                   double[][] map, Radius radiusStrategy) {
925            shadowCast(1, 1.0, 0.0, xx, xy, yx, yy, radius, startx, starty, decay, lightMap, map, radiusStrategy,
926                0, 0, lightMap.length, lightMap[0].length);
927    }
928
929    private static void shadowCastBinary(int row, double start, double end, int xx, int xy, int yx, int yy,
930                                         double radius, int startx, int starty, double decay, double[][] lightMap,
931                                         double[][] map, Radius radiusStrategy,
932                                         int minX, int minY, int maxX, int maxY) {
933        double newStart = 0;
934        if (start < end) {
935            return;
936        }
937
938        boolean blocked = false;
939        for (int distance = row; distance <= radius && distance < maxX - minX + maxY - minY && !blocked; distance++) {
940            int deltaY = -distance;
941            for (int deltaX = -distance; deltaX <= 0; deltaX++) {
942                int currentX = startx + deltaX * xx + deltaY * xy;
943                int currentY = starty + deltaX * yx + deltaY * yy;
944                double leftSlope = (deltaX - 0.5f) / (deltaY + 0.5f);
945                double rightSlope = (deltaX + 0.5f) / (deltaY - 0.5f);
946
947                if (!(currentX >= minX && currentY >= minY && currentX < maxX && currentY < maxY) || start < rightSlope) {
948                    continue;
949                } else if (end > leftSlope) {
950                    break;
951                }
952
953                lightMap[currentX][currentY] = 1.0;
954
955                if (blocked) { //previous cell was a blocking one
956                    if (map[currentX][currentY] >= 1) {//hit a wall
957                        newStart = rightSlope;
958                    } else {
959                        blocked = false;
960                        start = newStart;
961                    }
962                } else {
963                    if (map[currentX][currentY] >= 1 && distance < radius) {//hit a wall within sight line
964                        blocked = true;
965                        shadowCastBinary(distance + 1, start, leftSlope, xx, xy, yx, yy, radius, startx, starty, decay,
966                                lightMap, map, radiusStrategy, minX, minY, maxX, maxY);
967                        newStart = rightSlope;
968                    }
969                }
970            }
971        }
972    }
973
974    private static boolean shadowCastCheck(int row, double start, double end, int xx, int xy, int yx, int yy,
975                                         double radius, int startx, int starty, double decay, double[][] lightMap,
976                                         double[][] map, Radius radiusStrategy,
977                                         int minX, int minY, int maxX, int maxY, int targetX, int targetY) {
978        double newStart = 0;
979        if (start < end) {
980            return false;
981        }
982
983        boolean blocked = false;
984        for (int distance = row; distance <= radius && distance < maxX - minX + maxY - minY && !blocked; distance++) {
985            int deltaY = -distance;
986            for (int deltaX = -distance; deltaX <= 0; deltaX++) {
987                int currentX = startx + deltaX * xx + deltaY * xy;
988                int currentY = starty + deltaX * yx + deltaY * yy;
989                double leftSlope = (deltaX - 0.5f) / (deltaY + 0.5f);
990                double rightSlope = (deltaX + 0.5f) / (deltaY - 0.5f);
991
992                if (!(currentX >= minX && currentY >= minY && currentX < maxX && currentY < maxY) || start < rightSlope) {
993                    continue;
994                } else if (end > leftSlope) {
995                    break;
996                }
997
998                if(currentX == targetX && currentY == targetY) return true;
999
1000                if (blocked) { //previous cell was a blocking one
1001                    if (map[currentX][currentY] >= 1.0) {//hit a wall
1002                        newStart = rightSlope;
1003                    } else {
1004                        blocked = false;
1005                        start = newStart;
1006                    }
1007                } else {
1008                    if (map[currentX][currentY] >= 1.0 && distance < radius) {//hit a wall within sight line
1009                        blocked = true;
1010                        if(shadowCastCheck(distance + 1, start, leftSlope, xx, xy, yx, yy, radius, startx, starty, decay,
1011                                lightMap, map, radiusStrategy, minX, minY, maxX, maxY, targetX, targetY))
1012                            return true;
1013                        newStart = rightSlope;
1014                    }
1015                }
1016            }
1017        }
1018        return false;
1019    }
1020
1021    private static void shadowCast(int row, double start, double end, int xx, int xy, int yx, int yy,
1022                                   double radius, int startx, int starty, double decay, double[][] lightMap,
1023                                   double[][] map, Radius radiusStrategy,
1024                                   int minX, int minY, int maxX, int maxY) {
1025        double newStart = 0;
1026        if (start < end) {
1027            return;
1028        }
1029
1030        boolean blocked = false;
1031        for (int distance = row; distance <= radius && distance < maxX - minX + maxY - minY && !blocked; distance++) {
1032            int deltaY = -distance;
1033            for (int deltaX = -distance; deltaX <= 0; deltaX++) {
1034                int currentX = startx + deltaX * xx + deltaY * xy;
1035                int currentY = starty + deltaX * yx + deltaY * yy;
1036                double leftSlope = (deltaX - 0.5f) / (deltaY + 0.5f);
1037                double rightSlope = (deltaX + 0.5f) / (deltaY - 0.5f);
1038
1039                if (!(currentX >= minX && currentY >= minY && currentX < maxX && currentY < maxY) || start < rightSlope) {
1040                    continue;
1041                } else if (end > leftSlope) {
1042                    break;
1043                }
1044                double deltaRadius = radiusStrategy.radius(deltaX, deltaY);
1045                //check if it's within the lightable area and light if needed
1046                if (deltaRadius <= radius) {
1047                    lightMap[currentX][currentY] = 1.0 - decay * deltaRadius; 
1048                }
1049
1050                if (blocked) { //previous cell was a blocking one
1051                    if (map[currentX][currentY] >= 1) {//hit a wall
1052                        newStart = rightSlope;
1053                    } else {
1054                        blocked = false;
1055                        start = newStart;
1056                    }
1057                } else {
1058                    if (map[currentX][currentY] >= 1 && distance < radius) {//hit a wall within sight line
1059                        blocked = true;
1060                        shadowCast(distance + 1, start, leftSlope, xx, xy, yx, yy, radius, startx, starty, decay,
1061                                lightMap, map, radiusStrategy, minX, minY, maxX, maxY);
1062                        newStart = rightSlope;
1063                    }
1064                }
1065            }
1066        }
1067    }
1068    private static double[][] shadowCastLimited(int row, double start, double end, int xx, int xy, int yx, int yy,
1069                                                double radius, int startx, int starty, double decay, double[][] lightMap,
1070                                                double[][] map, Radius radiusStrategy, double angle, double span) {
1071        double newStart = 0;
1072        if (start < end) {
1073            return lightMap;
1074        }
1075        int width = lightMap.length;
1076        int height = lightMap[0].length;
1077
1078        boolean blocked = false;
1079        for (int distance = row; distance <= radius && distance < width + height && !blocked; distance++) {
1080            int deltaY = -distance;
1081            for (int deltaX = -distance; deltaX <= 0; deltaX++) {
1082                int currentX = startx + deltaX * xx + deltaY * xy;
1083                int currentY = starty + deltaX * yx + deltaY * yy;
1084                double leftSlope = (deltaX - 0.5f) / (deltaY + 0.5f);
1085                double rightSlope = (deltaX + 0.5f) / (deltaY - 0.5f);
1086
1087                if (!(currentX >= 0 && currentY >= 0 && currentX < width && currentY < height) || start < rightSlope) {
1088                    continue;
1089                } else if (end > leftSlope) {
1090                    break;
1091                }
1092                double deltaRadius = radiusStrategy.radius(deltaX, deltaY),
1093                        at2 = Math.abs(angle - NumberTools.atan2_(currentY - starty, currentX - startx));// + 1.0) % 1.0;
1094                //check if it's within the lightable area and light if needed
1095                if (deltaRadius <= radius
1096                        && (at2 <= span * 0.5
1097                        || at2 >= 1.0 - span * 0.5)) {
1098                    double bright = 1 - decay * deltaRadius;
1099                    lightMap[currentX][currentY] = bright;
1100                }
1101
1102                if (blocked) { //previous cell was a blocking one
1103                    if (map[currentX][currentY] >= 1) {//hit a wall
1104                        newStart = rightSlope;
1105                    } else {
1106                        blocked = false;
1107                        start = newStart;
1108                    }
1109                } else {
1110                    if (map[currentX][currentY] >= 1 && distance < radius) {//hit a wall within sight line
1111                        blocked = true;
1112                        lightMap = shadowCastLimited(distance + 1, start, leftSlope, xx, xy, yx, yy, radius, startx, starty, decay, lightMap, map, radiusStrategy, angle, span);
1113                        newStart = rightSlope;
1114                    }
1115                }
1116            }
1117        }
1118        return lightMap;
1119    }
1120
1121    private static final double[] directionRanges = new double[8];
1122    /**
1123     * Calculates the Field Of View for the provided map from the given x, y
1124     * coordinates, lighting with the view "pointed at" the given {@code angle} in degrees,
1125     * extending to different ranges based on the direction the light is traveling.
1126     * The direction ranges are {@code forward}, {@code sideForward}, {@code side},
1127     * {@code sideBack}, and {@code back}; all are multiplied by {@code radius}.
1128     * Assigns to, and returns, a light map where the values represent a percentage of fully
1129     * lit. The values in light are cleared before this is run, because prior state can make
1130     * this give incorrect results. You can use {@link #addFOVsInto(double[][], double[][]...)}
1131     * if you want to mix FOV results, which works as an alternative to using the prior light state.
1132     * <br>
1133     * The starting point for the calculation is considered to be at the center
1134     * of the origin cell. Radius determinations are determined by the provided
1135     * RadiusStrategy. If all direction ranges are the same, this acts like
1136     * {@link #reuseFOV(double[][], double[][], int, int, double, Radius)}; otherwise
1137     * may produce conical shapes (potentially more than one, or overlapping ones).
1138     *
1139     * @param resistanceMap the grid of cells to calculate on; the kind made by {@link #generateResistances(char[][])}
1140     * @param light the grid of cells to assign to; may have existing values, and 0.0 is used to mean "unlit"
1141     * @param startX the horizontal component of the starting location
1142     * @param startY the vertical component of the starting location
1143     * @param radius the distance the light will extend to (roughly); direction ranges will be multiplied by this
1144     * @param radiusTechnique provides a means to shape the FOV by changing distance calculation (circle, square, etc.)
1145     * @param angle the angle in degrees that will be the center of the FOV cone, 0 points right
1146     * @param forward the range to extend when the light is within 22.5 degrees of angle; will be interpolated with sideForward
1147     * @param sideForward the range to extend when the light is between 22.5 and 67.5 degrees of angle; will be interpolated with forward or side
1148     * @param side the range to extend when the light is between 67.5 and 112.5 degrees of angle; will be interpolated with sideForward or sideBack
1149     * @param sideBack the range to extend when the light is between 112.5 and 157.5 degrees of angle; will be interpolated with side or back
1150     * @param back the range to extend when the light is more than 157.5 degrees away from angle; will be interpolated with sideBack
1151     * @return the computed light grid (the same as {@code light})
1152     */
1153    public static double[][] reuseFOV(double[][] resistanceMap, double[][] light, int startX, int startY,
1154                                      double radius, Radius radiusTechnique, double angle,
1155                                      double forward, double sideForward, double side, double sideBack, double back) {
1156        directionRanges[0] = forward * radius;
1157        directionRanges[7] = directionRanges[1] = sideForward * radius;
1158        directionRanges[6] = directionRanges[2] = side * radius;
1159        directionRanges[5] = directionRanges[3] = sideBack * radius;
1160        directionRanges[4] = back * radius;
1161
1162        radius = Math.max(1, radius);
1163        ArrayTools.fill(light, 0);
1164        light[startX][startY] = 1;//make the starting space full power
1165        angle = ((angle >= 360.0 || angle < 0.0)
1166                ? MathExtras.remainder(angle, 360.0) : angle) * 0.002777777777777778;
1167
1168
1169        light = shadowCastPersonalized(1, 1.0, 0.0, 0, 1, 1, 0,   radius, startX, startY, light, resistanceMap, radiusTechnique, angle, directionRanges);
1170        light = shadowCastPersonalized(1, 1.0, 0.0, 1, 0, 0, 1,   radius, startX, startY, light, resistanceMap, radiusTechnique, angle, directionRanges);
1171        light = shadowCastPersonalized(1, 1.0, 0.0, 0, -1, 1, 0,  radius, startX, startY, light, resistanceMap, radiusTechnique, angle, directionRanges);
1172        light = shadowCastPersonalized(1, 1.0, 0.0, -1, 0, 0, 1,  radius, startX, startY, light, resistanceMap, radiusTechnique, angle, directionRanges);
1173        light = shadowCastPersonalized(1, 1.0, 0.0, 0, -1, -1, 0, radius, startX, startY, light, resistanceMap, radiusTechnique, angle, directionRanges);
1174        light = shadowCastPersonalized(1, 1.0, 0.0, -1, 0, 0, -1, radius, startX, startY, light, resistanceMap, radiusTechnique, angle, directionRanges);
1175        light = shadowCastPersonalized(1, 1.0, 0.0, 0, 1, -1, 0,  radius, startX, startY, light, resistanceMap, radiusTechnique, angle, directionRanges);
1176        light = shadowCastPersonalized(1, 1.0, 0.0, 1, 0, 0, -1,  radius, startX, startY, light, resistanceMap, radiusTechnique, angle, directionRanges);
1177        return light;
1178    }
1179
1180    private static double[][] shadowCastPersonalized(int row, double start, double end, int xx, int xy, int yx, int yy,
1181                                                     double radius, int startx, int starty, double[][] lightMap,
1182                                                     double[][] map, Radius radiusStrategy, double angle, final double[] directionRanges) {
1183        double newStart = 0;
1184        if (start < end) {
1185            return lightMap;
1186        }
1187        int width = lightMap.length;
1188        int height = lightMap[0].length;
1189
1190        boolean blocked = false;
1191        for (int distance = row; distance <= radius && distance < width + height && !blocked; distance++) {
1192            int deltaY = -distance;
1193            for (int deltaX = -distance; deltaX <= 0; deltaX++) {
1194                int currentX = startx + deltaX * xx + deltaY * xy;
1195                int currentY = starty + deltaX * yx + deltaY * yy;
1196                double leftSlope = (deltaX - 0.5f) / (deltaY + 0.5f);
1197                double rightSlope = (deltaX + 0.5f) / (deltaY - 0.5f);
1198
1199                if (!(currentX >= 0 && currentY >= 0 && currentX < width && currentY < height) || start < rightSlope) {
1200                    continue;
1201                } else if (end > leftSlope) {
1202                    break;
1203                }
1204                double at2 = Math.abs(angle - NumberTools.atan2_(currentY - starty, currentX - startx)) * 8.0,
1205                        deltaRadius = radiusStrategy.radius(deltaX, deltaY);
1206                int ia = (int)(at2), low = ia & 7, high = ia + 1 & 7;
1207                double a = at2 - ia, adjRadius = (1.0 - a) * directionRanges[low] + a * directionRanges[high];
1208                //check if it's within the lightable area and light if needed
1209                if (deltaRadius <= adjRadius) {
1210                    lightMap[currentX][currentY] = 1.0 - (deltaRadius / (adjRadius + 1.0)); // how bright the tile is
1211                }
1212
1213                if (blocked) { //previous cell was a blocking one
1214                    if (map[currentX][currentY] >= 1) {//hit a wall
1215                        newStart = rightSlope;
1216                    } else {
1217                        blocked = false;
1218                        start = newStart;
1219                    }
1220                } else {
1221                    if (map[currentX][currentY] >= 1 && distance < adjRadius) {//hit a wall within sight line
1222                        blocked = true;
1223                        lightMap = shadowCastPersonalized(distance + 1, start, leftSlope, xx, xy, yx, yy, radius, startx, starty, lightMap, map, radiusStrategy, angle, directionRanges);
1224                        newStart = rightSlope;
1225                    }
1226                }
1227            }
1228        }
1229        return lightMap;
1230    }
1231
1232    /**
1233     * Adds an FOV map to another in the simplest way possible; does not check line-of-sight between FOV maps.
1234     * Clamps the highest value for any single position at 1.0. Modifies the basis parameter in-place and makes no
1235     * allocations; this is different from {@link #addFOVs(double[][][])}, which creates a new 2D array.
1236     * @param basis a 2D double array, which can be empty or returned by calculateFOV() or reuseFOV(); modified!
1237     * @param addend another 2D double array that will be added into basis; this one will not be modified
1238     * @return the sum of the 2D double arrays passed, using the dimensions of basis if they don't match
1239     */
1240    public static double[][] addFOVsInto(double[][] basis, double[][] addend)
1241    {
1242        for (int x = 0; x < basis.length && x < addend.length; x++) {
1243                for (int y = 0; y < basis[x].length && y < addend[x].length; y++) {
1244                    basis[x][y] = Math.min(1.0, basis[x][y] + addend[x][y]);
1245                }
1246            }
1247        return basis;
1248    }
1249    /**
1250     * Adds multiple FOV maps together in the simplest way possible; does not check line-of-sight between FOV maps.
1251     * Clamps the highest value for any single position at 1.0. Allocates a new 2D double array and returns it.
1252     * @param maps an array or vararg of 2D double arrays, each usually returned by calculateFOV()
1253     * @return the sum of all the 2D double arrays passed, using the dimensions of the first if they don't all match
1254     */
1255    public static double[][] addFOVs(double[][]... maps)
1256    {
1257        if(maps == null || maps.length == 0)
1258            return new double[0][0];
1259        double[][] map = ArrayTools.copy(maps[0]);
1260        for(int i = 1; i < maps.length; i++)
1261        {
1262            for (int x = 0; x < map.length && x < maps[i].length; x++) {
1263                for (int y = 0; y < map[x].length && y < maps[i][x].length; y++) {
1264                    map[x][y] += maps[i][x][y];
1265                }
1266            }
1267        }
1268        for (int x = 0; x < map.length; x++) {
1269            for (int y = 0; y < map[x].length; y++) {
1270                if(map[x][y] > 1.0) map[x][y] = 1.0;
1271            }
1272        }
1273        return map;
1274    }
1275    /**
1276     * Adds multiple FOV maps to basis cell-by-cell, modifying basis; does not check line-of-sight between FOV maps.
1277     * Clamps the highest value for any single position at 1.0. Returns basis without allocating new objects.
1278     * @param basis a 2D double array that will be modified by adding values in maps to it and clamping to 1.0 or less 
1279     * @param maps an array or vararg of 2D double arrays, each usually returned by calculateFOV()
1280     * @return basis, with all elements in all of maps added to the corresponding cells and clamped
1281     */
1282    public static double[][] addFOVsInto(double[][] basis, double[][]... maps) {
1283        if (maps == null || maps.length == 0)
1284            return basis;
1285        for (int i = 1; i < maps.length; i++) {
1286            for (int x = 0; x < basis.length && x < maps[i].length; x++) {
1287                for (int y = 0; y < basis[x].length && y < maps[i][x].length; y++) {
1288                    basis[x][y] += maps[i][x][y];
1289                }
1290            }
1291        }
1292        for (int x = 0; x < basis.length; x++) {
1293            for (int y = 0; y < basis[x].length; y++) {
1294                if (basis[x][y] > 1.0) basis[x][y] = 1.0;
1295            }
1296        }
1297        return basis;
1298    }
1299
1300    /**
1301     * Adds multiple FOV maps together in the simplest way possible; does not check line-of-sight between FOV maps.
1302     * Clamps the highest value for any single position at 1.0. Allocates a new 2D double array and returns it.
1303     * @param maps an Iterable of 2D double arrays (most collections implement Iterable),
1304     *             each usually returned by calculateFOV()
1305     * @return the sum of all the 2D double arrays passed, using the dimensions of the first if they don't all match
1306     */
1307    public static double[][] addFOVs(Iterable<double[][]> maps)
1308    {
1309        if(maps == null)
1310            return new double[0][0];
1311        Iterator<double[][]> it = maps.iterator();
1312        if(!it.hasNext())
1313            return new double[0][0];
1314        double[][] map = ArrayTools.copy(it.next()), t;
1315        while (it.hasNext())
1316        {
1317            t = it.next();
1318            for (int x = 0; x < map.length && x < t.length; x++) {
1319                for (int y = 0; y < map[x].length && y < t[x].length; y++) {
1320                    map[x][y] += t[x][y];
1321                }
1322            }
1323        }
1324        for (int x = 0; x < map.length; x++) {
1325            for (int y = 0; y < map[x].length; y++) {
1326                if(map[x][y] > 1.0) map[x][y] = 1.0;
1327            }
1328        }
1329        return map;
1330    }
1331
1332    /**
1333     * Adds together multiple FOV maps, but only adds to a position if it is visible in the given LOS map. Useful if
1334     * you want distant lighting to be visible only if the player has line-of-sight to a lit cell. Typically the LOS map
1335     * is calculated by {@link #reuseLOS(double[][], double[][], int, int)}, using the same resistance map used to
1336     * calculate the FOV maps. Clamps the highest value for any single position at 1.0.
1337     * @param losMap an LOS map such as one generated by {@link #reuseLOS(double[][], double[][], int, int)}
1338     * @param maps an array or vararg of 2D double arrays, each usually returned by calculateFOV()
1339     * @return the sum of all the 2D double arrays in maps where a cell was visible in losMap
1340     */
1341    public static double[][] mixVisibleFOVs(double[][] losMap, double[][]... maps)
1342    {
1343        if(losMap == null || losMap.length == 0)
1344            return addFOVs(maps);
1345        final int width = losMap.length, height = losMap[0].length;
1346        double[][] map = new double[width][height];
1347        if(maps == null || maps.length == 0)
1348            return map;
1349        for(int i = 0; i < maps.length; i++)
1350        {
1351            for (int x = 0; x < width && x < maps[i].length; x++) {
1352                for (int y = 0; y < height && y < maps[i][x].length; y++) {
1353                    if(losMap[x][y] > 0.0001) {
1354                        map[x][y] += maps[i][x][y];
1355                    }
1356                }
1357            }
1358        }
1359        for (int x = 0; x < width; x++) {
1360            for (int y = 0; y < height; y++) {
1361                if(map[x][y] > 1.0) map[x][y] = 1.0;
1362            }
1363        }
1364
1365        return map;
1366    }
1367    /**
1368     * Adds together multiple FOV maps, but only adds to a position if it is visible in the given LOS map. Useful if
1369     * you want distant lighting to be visible only if the player has line-of-sight to a lit cell. Typically the LOS map
1370     * is calculated by {@link #reuseLOS(double[][], double[][], int, int)}, using the same resistance map used to
1371     * calculate the FOV maps. Clamps the highest value for any single position at 1.0.
1372     * @param losMap an LOS map such as one generated by {@link #reuseLOS(double[][], double[][], int, int)}
1373     * @param basis an existing 2D double array that should have matching width and height to losMap; will be modified
1374     * @param maps an array or vararg of 2D double arrays, each usually returned by calculateFOV()
1375     * @return the sum of all the 2D double arrays in maps where a cell was visible in losMap
1376     */
1377    public static double[][] mixVisibleFOVsInto(double[][] losMap, double[][] basis, double[][]... maps)
1378
1379    {
1380        if(losMap == null || losMap.length <= 0 || losMap[0].length <= 0)
1381            return addFOVsInto(basis, maps);
1382        final int width = losMap.length, height = losMap[0].length;
1383        double[][] map = new double[width][height];
1384        if(maps == null || maps.length == 0)
1385            return map;
1386        for(int i = 0; i < maps.length; i++)
1387        {
1388            for (int x = 0; x < width && x < maps[i].length; x++) {
1389                for (int y = 0; y < height && y < maps[i][x].length; y++) {
1390                    if(losMap[x][y] > 0.0001) {
1391                        map[x][y] += maps[i][x][y];
1392                    }
1393                }
1394            }
1395        }
1396        for (int x = 0; x < width; x++) {
1397            for (int y = 0; y < height; y++) {
1398                if(map[x][y] > 1.0) map[x][y] = 1.0;
1399            }
1400        }
1401        return map;
1402    }
1403
1404    /**
1405     * Adds together multiple FOV maps, but only adds to a position if it is visible in the given LOS map. Useful if
1406     * you want distant lighting to be visible only if the player has line-of-sight to a lit cell. Typically the LOS map
1407     * is calculated by {@link #reuseLOS(double[][], double[][], int, int)}, using the same resistance map used to
1408     * calculate the FOV maps. Clamps the highest value for any single position at 1.0.
1409     * @param losMap an LOS map such as one generated by {@link #reuseLOS(double[][], double[][], int, int)}
1410     * @param maps an Iterable of 2D double arrays, each usually returned by calculateFOV()
1411     * @return the sum of all the 2D double arrays in maps where a cell was visible in losMap
1412     */
1413    public static double[][] mixVisibleFOVs(double[][] losMap, Iterable<double[][]> maps)
1414    {
1415        if(losMap == null || losMap.length == 0)
1416            return addFOVs(maps);
1417        final int width = losMap.length, height = losMap[0].length;
1418        double[][] map = new double[width][height];
1419        if(maps == null)
1420            return map;
1421        for (double[][] map1 : maps) {
1422            for (int x = 0; x < width && x < map1.length; x++) {
1423                for (int y = 0; y < height && y < map1[x].length; y++) {
1424                    if (losMap[x][y] > 0.0001) {
1425                        map[x][y] += map1[x][y];
1426                        if (map[x][y] > 1.0) map[x][y] = 1.0;
1427                    }
1428                }
1429            }
1430        }
1431        return map;
1432    }
1433
1434    /**
1435     * Calculates what cells are visible from (startX,startY) using the given resistanceMap; this can be given to
1436     * mixVisibleFOVs() to limit extra light sources to those visible from the starting point. Just like calling
1437     * calculateFOV(), this creates a new double[][]; there doesn't appear to be a way to work with Ripple FOV and avoid
1438     * needing an empty double[][] every time, since it uses previously-placed light to determine how it should spread.
1439     * @param resistanceMap the grid of cells to calculate on; the kind made by {@link #generateResistances(char[][])}
1440     * @param startX the center of the LOS map; typically the player's x-position
1441     * @param startY the center of the LOS map; typically the player's y-position
1442     * @return an LOS map with the given starting point
1443     */
1444    public double[][] calculateLOSMap(double[][] resistanceMap, int startX, int startY)
1445    {
1446        if(resistanceMap == null || resistanceMap.length <= 0  || resistanceMap[0].length <= 0)
1447            return new double[0][0];
1448
1449        int width = resistanceMap.length;
1450        int height = resistanceMap[0].length;
1451        double rad = width + height;
1452        double decay = 1.0 / rad;
1453
1454        initializeLightMap(width, height);
1455        light[startX][startY] = 1;//make the starting space full power
1456
1457        switch (type) {
1458            case RIPPLE:
1459            case RIPPLE_LOOSE:
1460            case RIPPLE_TIGHT:
1461            case RIPPLE_VERY_LOOSE:
1462                initializeNearLight(width, height);
1463                doRippleFOV(light, rippleValue(type), startX, startY, decay, rad, resistanceMap, nearLight, Radius.SQUARE);
1464                for (int x = 0; x < width; x++) {
1465                    for (int y = 0; y < height; y++) {
1466                        if(light[x][y] > 0.0001)
1467                            light[x][y] = 1.0;
1468                    }
1469                }
1470                break;
1471            default:
1472                for (Direction d : Direction.DIAGONALS) {
1473                    shadowCastBinary(1, 1.0, 0.0, 0, d.deltaX, d.deltaY, 0, rad, startX, startY, decay, light, resistanceMap, Radius.SQUARE, 0, 0, width, height);
1474                    shadowCastBinary(1, 1.0, 0.0, d.deltaX, 0, 0, d.deltaY, rad, startX, startY, decay, light, resistanceMap, Radius.SQUARE, 0, 0, width, height);
1475                }
1476                break;
1477        }
1478        return light;
1479    }
1480
1481
1482
1483    /**
1484     * Given a char[][] for the map, produces a double[][] that can be used with most of the methods in FOV, like
1485     * {@link #reuseFOV(double[][], double[][], int, int, double)}. It expects any doors to be represented by '+' if
1486     * closed or '/' if open (which can be caused by calling {@link DungeonUtility#closeDoors(char[][])}), any walls to
1487     * be '#' or box drawing characters, and it doesn't care what other chars are used (only doors, including open ones,
1488     * and walls obscure light and thus have a resistance by default).
1489     *
1490     * @param map a dungeon, width by height, with any closed doors as '+' and open doors as '/' as per closeDoors()
1491     * @return a resistance map suitable for use with the FOV class, with clear cells assigned 0.0 and blocked ones 1.0
1492     */
1493    public static double[][] generateResistances(char[][] map) {
1494        int width = map.length;
1495        int height = map[0].length;
1496        double[][] portion = new double[width][height];
1497        for (int i = 0; i < width; i++) {
1498            for (int j = 0; j < height; j++) {
1499                switch (map[i][j]) {
1500                    case '\1':
1501                    case '├':
1502                    case '┤':
1503                    case '┴':
1504                    case '┬':
1505                    case '┌':
1506                    case '┐':
1507                    case '└':
1508                    case '┘':
1509                    case '│':
1510                    case '─':
1511                    case '┼':
1512                    case '#':
1513                        portion[i][j] = 1.0;
1514                        break;
1515                    case '/':
1516                    case '"':
1517                        portion[i][j] = 0.15;
1518                        break;
1519                    case '+':
1520                        portion[i][j] = 0.95;
1521                        break;
1522                    case '.':
1523                    case ',':
1524                    case '~':
1525                    case '^':
1526                    default:
1527                        portion[i][j] = 0.0;
1528                }
1529            }
1530        }
1531        return portion;
1532    }
1533
1534    /**
1535     * Given a char[][] for the map that should use box drawing characters (as produced by
1536     * {@link DungeonUtility#hashesToLines(char[][], boolean)}), produces a double[][] with triple width and triple
1537     * height that can be used with FOV methods like {@link #reuseFOV(double[][], double[][], int, int, double)} in
1538     * classes that use subcell lighting. Importantly, this only considers a "thin line" of wall to be blocking
1539     * (matching the box drawing character), instead of the whole 3x3 area. This expects any doors to be represented by
1540     * '+' if closed or '/' if open (which can be caused by calling {@link DungeonUtility#closeDoors(char[][])}), thick
1541     * vegetation or other concealing obstructions to be '"', any normal walls to be box drawing characters, any cells
1542     * that block all subcells to be '#', and it doesn't care what other chars are used (only doors, including open
1543     * ones, vegetation, and walls obscure light and thus have a resistance normally).
1544     *
1545     * @param map a dungeon, width by height, with any closed doors as '+' and open doors as '/' as per closeDoors()
1546     * @return a resistance map suitable for use with the FOV class and subcell lighting, with triple width/height
1547     */
1548    public static double[][] generateResistances3x3(char[][] map) {
1549        int width = map.length;
1550        int height = map[0].length;
1551        double[][] portion = new double[width * 3][height * 3];
1552        for (int i = 0, x = 0; i < width; i++, x+=3) {
1553            for (int j = 0, y = 0; j < height; j++, y+=3) {
1554                switch (map[i][j]) {
1555                    case '\1':
1556                    case '#':
1557                        portion[x][y] = portion[x+1][y] = portion[x+2][y] =
1558                                portion[x][y+1] = portion[x+1][y+1] = portion[x+2][y+1] =
1559                                        portion[x][y+2] = portion[x+1][y+2] = portion[x+2][y+2] = 1.0;
1560                        break;
1561                    case '├':
1562                        /*portion[x][y] =*/ portion[x+1][y] = /*portion[x+2][y] =*/
1563                            /*portion[x][y+1] =*/ portion[x+1][y+1] = portion[x+2][y+1] =
1564                            /*portion[x][y+2] =*/ portion[x+1][y+2] = /*portion[x+2][y+2] =*/ 1.0;
1565                        break;
1566                    case '┤':
1567                        /*portion[x][y] =*/ portion[x+1][y] = /*portion[x+2][y] =*/
1568                            portion[x][y+1] = portion[x+1][y+1] = /*portion[x+2][y+1] =*/
1569                                    /*portion[x][y+2] =*/ portion[x+1][y+2] = /*portion[x+2][y+2] =*/ 1.0;
1570                        break;
1571                    case '┴':
1572                        /*portion[x][y] =*/ portion[x+1][y] = /*portion[x+2][y] =*/
1573                            portion[x][y+1] = portion[x+1][y+1] = portion[x+2][y+1] =
1574                                    /*portion[x][y+2] = portion[x+1][y+2] = portion[x+2][y+2] =*/ 1.0;
1575                        break;
1576                    case '┬':
1577                        /*portion[x][y] = portion[x+1][y] = portion[x+2][y] =*/
1578                        portion[x][y+1] = portion[x+1][y+1] = portion[x+2][y+1] =
1579                                /*portion[x][y+2] =*/ portion[x+1][y+2] = /*portion[x+2][y+2] =*/ 1.0;
1580                        break;
1581                    case '┌':
1582                        /*portion[x][y] = portion[x+1][y] = portion[x+2][y] =*/
1583                        /*portion[x][y+1] =*/ portion[x+1][y+1] = portion[x+2][y+1] =
1584                            /*portion[x][y+2] =*/ portion[x+1][y+2] = /*portion[x+2][y+2] =*/ 1.0;
1585                        break;
1586                    case '┐':
1587                        /*portion[x][y] = portion[x+1][y] = portion[x+2][y] =*/
1588                        portion[x][y+1] = portion[x+1][y+1] = /*portion[x+2][y+1] =*/
1589                                /*portion[x][y+2] =*/ portion[x+1][y+2] = /*portion[x+2][y+2] =*/ 1.0;
1590                        break;
1591                    case '└':
1592                        /*portion[x][y] =*/ portion[x+1][y] = /*portion[x+2][y] =*/
1593                            /*portion[x][y+1] =*/ portion[x+1][y+1] = portion[x+2][y+1] =
1594                            /*portion[x][y+2] = portion[x+1][y+2] = portion[x+2][y+2] =*/ 1.0;
1595                        break;
1596                    case '┘':
1597                        /*portion[x][y] =*/ portion[x+1][y] = /*portion[x+2][y] =*/
1598                            portion[x][y+1] = portion[x+1][y+1] = /*portion[x+2][y+1] =*/
1599                                    /*portion[x][y+2] = portion[x+1][y+2] = portion[x+2][y+2] =*/ 1.0;
1600                        break;
1601                    case '│':
1602                        /*portion[x][y] =*/ portion[x+1][y] = /*portion[x+2][y] =*/
1603                            /*portion[x][y+1] =*/ portion[x+1][y+1] = /*portion[x+2][y+1] =*/
1604                            /*portion[x][y+2] =*/ portion[x+1][y+2] = /*portion[x+2][y+2] =*/ 1.0;
1605                        break;
1606                    case '─':
1607                        portion[x][y+1] = portion[x+1][y+1] = portion[x+2][y+1] = 1.0;
1608                        break;
1609                    case '╴':
1610                        portion[x][y+1] = portion[x+1][y+1] = 1.0;
1611                        break;
1612                    case '╵':
1613                        /*portion[x][y] =*/ portion[x+1][y] = /*portion[x+2][y] =*/
1614                            /*portion[x][y+1] =*/ portion[x+1][y+1] = /*portion[x+2][y+1] =*/ 1.0;
1615                        break;
1616                    case '╶':
1617                        portion[x+1][y+1] = portion[x+2][y+1] = 1.0;
1618                        break;
1619                    case '╷':
1620                        /*portion[x][y+1] =*/ portion[x+1][y+1] = /*portion[x+2][y+1] =*/
1621                            /*portion[x][y+2] =*/ portion[x+1][y+2] = /*portion[x+2][y+2] =*/ 1.0;
1622                        break;
1623                    case '┼':
1624                        /*portion[x][y] =*/ portion[x+1][y] = /*portion[x+2][y] =*/
1625                            portion[x][y+1] = portion[x+1][y+1] = portion[x+2][y+1] =
1626                                    /*portion[x][y+2] =*/ portion[x+1][y+2] = /*portion[x+2][y+2] =*/ 1.0;
1627                        break;
1628                    case '/':
1629                    case '"':
1630                        portion[x][y] = portion[x+1][y] = portion[x+2][y] =
1631                                portion[x][y+1] = portion[x+1][y+1] = portion[x+2][y+1] =
1632                                        portion[x][y+2] = portion[x+1][y+2] = portion[x+2][y+2] = 0.15;
1633                        break;
1634                    case '+':
1635                        portion[x][y] = portion[x+1][y] = portion[x+2][y] =
1636                                portion[x][y+1] = portion[x+1][y+1] = portion[x+2][y+1] =
1637                                        portion[x][y+2] = portion[x+1][y+2] = portion[x+2][y+2] = 0.95;
1638                        break;
1639                }
1640            }
1641        }
1642        return portion;
1643    }
1644
1645    /**
1646     * Given a char[][] for the map, produces a double[][] that can be used with any FOV methods that expect a
1647     * resistance map (like {@link #reuseFOV(double[][], double[][], int, int, double)}), but does not treat
1648     * any cells as partly transparent, only fully-blocking or fully-permitting light. This is mainly useful if you
1649     * expect the FOV radius to be very high or (effectively) infinite, since anything less than complete blockage would
1650     * be passed through by infinite-radius FOV. This expects any doors to be represented by '+' if closed or '/' if
1651     * open (most door placement defaults to a mix of '+' and '/', so by calling
1652     * {@link DungeonUtility#closeDoors(char[][])} you can close all doors at the start), and any walls to be '#' or
1653     * box drawing characters. This will assign 1.0 resistance to walls and closed doors or 0.0 for any other cell.
1654     *
1655     * @param map a dungeon, width by height, with any closed doors as '+' and open doors as '/' as per closeDoors()
1656     * @return a resistance map suitable for use with the FOV class, but with no partially transparent cells
1657     */
1658    public static double[][] generateSimpleResistances(char[][] map) {
1659        int width = map.length;
1660        int height = map[0].length;
1661        double[][] portion = new double[width][height];
1662        for (int i = 0; i < width; i++) {
1663            for (int j = 0; j < height; j++) {
1664                switch (map[i][j]) {
1665                    case '\1':
1666                    case '├':
1667                    case '┤':
1668                    case '┴':
1669                    case '┬':
1670                    case '┌':
1671                    case '┐':
1672                    case '└':
1673                    case '┘':
1674                    case '│':
1675                    case '─':
1676                    case '┼':
1677                    case '#':
1678                    case '+':
1679                        portion[i][j] = 1.0;
1680                        break;
1681                    default:
1682                        portion[i][j] = 0.0;
1683                }
1684            }
1685        }
1686        return portion;
1687    }
1688
1689    /**
1690     * Given a char[][] for the map that should use box drawing characters (as produced by
1691     * {@link DungeonUtility#hashesToLines(char[][], boolean)}), produces a double[][] with triple width and triple
1692     * height that can be used with FOV's methods that expect a resistance map (like
1693     * {@link #reuseFOV(double[][], double[][], int, int, double)}) in classes that use subcell lighting. This expects
1694     * any doors to be represented by '+' if closed or '/' if open (most door placement defaults to a mix of '+' and
1695     * '/', so by calling {@link DungeonUtility#closeDoors(char[][])} you can close all doors at the start), any walls
1696     * to be box drawing characters, and any cells that block all subcells within their area to be '#'. This will assig
1697     * 1.0 resistance to walls and closed doors where a line of the box drawing char would block light, or 0.0 for an
1698     * other subcell.
1699     *
1700     * @param map a dungeon, width by height, with any closed doors as '+' and open doors as '/' as per closeDoors()
1701     * @return a resistance map suitable for use with the FOV class and subcell lighting, with triple width/height
1702     */
1703    public static double[][] generateSimpleResistances3x3(char[][] map) {
1704        int width = map.length;
1705        int height = map[0].length;
1706        double[][] portion = new double[width * 3][height * 3];
1707        for (int i = 0, x = 0; i < width; i++, x+=3) {
1708            for (int j = 0, y = 0; j < height; j++, y+=3) {
1709                switch (map[i][j]) {
1710                    case '\1':
1711                    case '#':
1712                    case '+':
1713                        portion[x][y] = portion[x+1][y] = portion[x+2][y] =
1714                                portion[x][y+1] = portion[x+1][y+1] = portion[x+2][y+1] =
1715                                        portion[x][y+2] = portion[x+1][y+2] = portion[x+2][y+2] = 1.0;
1716                        break;
1717                    case '├':
1718                        /*portion[x][y] =*/ portion[x+1][y] = /*portion[x+2][y] =*/
1719                            /*portion[x][y+1] =*/ portion[x+1][y+1] = portion[x+2][y+1] =
1720                            /*portion[x][y+2] =*/ portion[x+1][y+2] = /*portion[x+2][y+2] =*/ 1.0;
1721                        break;
1722                    case '┤':
1723                        /*portion[x][y] =*/ portion[x+1][y] = /*portion[x+2][y] =*/
1724                            portion[x][y+1] = portion[x+1][y+1] = /*portion[x+2][y+1] =*/
1725                                    /*portion[x][y+2] =*/ portion[x+1][y+2] = /*portion[x+2][y+2] =*/ 1.0;
1726                        break;
1727                    case '┴':
1728                        /*portion[x][y] =*/ portion[x+1][y] = /*portion[x+2][y] =*/
1729                            portion[x][y+1] = portion[x+1][y+1] = portion[x+2][y+1] =
1730                                    /*portion[x][y+2] = portion[x+1][y+2] = portion[x+2][y+2] =*/ 1.0;
1731                        break;
1732                    case '┬':
1733                        /*portion[x][y] = portion[x+1][y] = portion[x+2][y] =*/
1734                        portion[x][y+1] = portion[x+1][y+1] = portion[x+2][y+1] =
1735                                /*portion[x][y+2] =*/ portion[x+1][y+2] = /*portion[x+2][y+2] =*/ 1.0;
1736                        break;
1737                    case '┌':
1738                        /*portion[x][y] = portion[x+1][y] = portion[x+2][y] =*/
1739                        /*portion[x][y+1] =*/ portion[x+1][y+1] = portion[x+2][y+1] =
1740                            /*portion[x][y+2] =*/ portion[x+1][y+2] = /*portion[x+2][y+2] =*/ 1.0;
1741                        break;
1742                    case '┐':
1743                        /*portion[x][y] = portion[x+1][y] = portion[x+2][y] =*/
1744                        portion[x][y+1] = portion[x+1][y+1] = /*portion[x+2][y+1] =*/
1745                                /*portion[x][y+2] =*/ portion[x+1][y+2] = /*portion[x+2][y+2] =*/ 1.0;
1746                        break;
1747                    case '└':
1748                        /*portion[x][y] =*/ portion[x+1][y] = /*portion[x+2][y] =*/
1749                            /*portion[x][y+1] =*/ portion[x+1][y+1] = portion[x+2][y+1] =
1750                            /*portion[x][y+2] = portion[x+1][y+2] = portion[x+2][y+2] =*/ 1.0;
1751                        break;
1752                    case '┘':
1753                        /*portion[x][y] =*/ portion[x+1][y] = /*portion[x+2][y] =*/
1754                            portion[x][y+1] = portion[x+1][y+1] = /*portion[x+2][y+1] =*/
1755                                    /*portion[x][y+2] = portion[x+1][y+2] = portion[x+2][y+2] =*/ 1.0;
1756                        break;
1757                    case '│':
1758                        /*portion[x][y] =*/ portion[x+1][y] = /*portion[x+2][y] =*/
1759                            /*portion[x][y+1] =*/ portion[x+1][y+1] = /*portion[x+2][y+1] =*/
1760                            /*portion[x][y+2] =*/ portion[x+1][y+2] = /*portion[x+2][y+2] =*/ 1.0;
1761                        break;
1762                    case '─':
1763                        portion[x][y+1] = portion[x+1][y+1] = portion[x+2][y+1] = 1.0;
1764                        break;
1765                    case '╴':
1766                        portion[x][y+1] = portion[x+1][y+1] = 1.0;
1767                        break;
1768                    case '╵':
1769                        /*portion[x][y] =*/ portion[x+1][y] = /*portion[x+2][y] =*/
1770                            /*portion[x][y+1] =*/ portion[x+1][y+1] = /*portion[x+2][y+1] =*/ 1.0;
1771                        break;
1772                    case '╶':
1773                        portion[x+1][y+1] = portion[x+2][y+1] = 1.0;
1774                        break;
1775                    case '╷':
1776                        /*portion[x][y+1] =*/ portion[x+1][y+1] = /*portion[x+2][y+1] =*/
1777                            /*portion[x][y+2] =*/ portion[x+1][y+2] = /*portion[x+2][y+2] =*/ 1.0;
1778                        break;
1779                    case '┼':
1780                        /*portion[x][y] =*/ portion[x+1][y] = /*portion[x+2][y] =*/
1781                            portion[x][y+1] = portion[x+1][y+1] = portion[x+2][y+1] =
1782                                    /*portion[x][y+2] =*/ portion[x+1][y+2] = /*portion[x+2][y+2] =*/ 1.0;
1783                        break;
1784                }
1785            }
1786        }
1787        return portion;
1788    }
1789
1790}