001package squidpony.squidgrid.gui.gdx;
002
003import com.badlogic.gdx.graphics.Color;
004import com.badlogic.gdx.math.MathUtils;
005import squidpony.squidgrid.FOV;
006import squidpony.squidgrid.Radius;
007import squidpony.squidmath.Coord;
008import squidpony.squidmath.GreasedRegion;
009import squidpony.squidmath.NumberTools;
010import squidpony.squidmath.OrderedMap;
011
012import java.io.Serializable;
013
014import static squidpony.squidgrid.gui.gdx.SColor.FLOAT_WHITE;
015import static squidpony.squidgrid.gui.gdx.SColor.lerpFloatColorsBlended;
016
017/**
018 * A convenience class that makes dealing with multiple colored light sources easier.
019 * All fields are public and documented to encourage their use alongside the API methods. The typical usage case for
020 * this class is when a game has complex lighting needs that should be consolidated into one LightingHandler per level,
021 * where a level corresponds to a {@code char[][]}. After constructing a LightingHandler with the resistances for a
022 * level, you should add all light sources with their positions, either using {@link #addLight(int, int, Radiance)} or
023 * by directly putting keys and values into {@link #lights}. Then you can calculate the visible cells once lighting is
024 * considered (which may include distant lit cells with unseen but unobstructing cells between the viewer and the light)
025 * using {@link #calculateFOV(Coord)}, which should be called every time the viewer moves. You can update the flicker
026 * and strobe effects on all Radiance objects, which is typically done every frame, using {@link #update()} or
027 * {@link #updateAll()} (updateAll() is for when there is no viewer), and once that update() call has been made you can
028 * call {@link #draw(SparseLayers)} to change the background colors of a SparseLayers, {@link #draw(SquidPanel)} to
029 * change the colors of a SquidPanel (typically the background layer of a SquidLayers, as from
030 * {@link SquidLayers#getBackgroundLayer()}), or {@link #draw(float[][])} to change a 2D float array that holds packed
031 * float colors (which may be used in some custom setup). To place user-interface lighting effects that don't affect the
032 * actual FOV of creatures in the game, you can use {@link #updateUI(Coord, Radiance)}, which is called after
033 * {@link #update()} but before {@link #draw(float[][])}.
034 * <br>
035 * Created by Tommy Ettinger on 11/2/2018.
036 */
037public class LightingHandler implements Serializable {
038    private static final long serialVersionUID = 0L;
039
040    /**
041     * How light should spread; usually {@link Radius#CIRCLE} unless gameplay reasons need it to be SQUARE or DIAMOND.
042     */
043    public Radius radiusStrategy;
044    /**
045     * The 2D array of light-resistance values from 0.0 to 1.0 for each cell on the map, as produced by
046     * {@link squidpony.squidgrid.mapping.DungeonUtility#generateResistances(char[][])}.
047     */
048    public double[][] resistances;
049    /**
050     * What the "viewer" (as passed to {@link #calculateFOV(Coord)}) can see either nearby without light or because an
051     * area in line-of-sight has light in it. Edited by {@link #calculateFOV(Coord)} and {@link  #update()}, but
052     * not {@link #updateUI(Coord, Radiance)} (which is meant for effects that are purely user-interface).
053     */
054    public double[][] fovResult;
055    /**
056     * A 2D array of doubles that are either 0.0 if a cell has an obstruction between it and the viewer, or greater than
057     * 0.0 otherwise.
058     */
059    public double[][] losResult;
060    /**
061     * Temporary storage array used for calculations involving {@link #fovResult}; it sometimes may make sense for other
062     * code to use this as temporary storage as well.
063     */
064    public double[][] tempFOV;
065    /**
066     * A pair of 2D float arrays with different usages; {@code colorLighting[0]} is a 2D array that stores the strength
067     * of light in each cell, and {@code colorLighting[1]} is a 2D array that stores the color of light in each cell, as
068     * a packed float color. Both 2D arrays are the size of the map, as defined by {@link #resistances} initially and
069     * later available in {@link #width} and {@link #height}.
070     */
071    public float[][][] colorLighting;
072    /**
073     * Temporary storage array used for calculations involving {@link #colorLighting}; it sometimes may make sense for
074     * other code to use this as temporary storage as well.
075     */
076    public float[][][] tempColorLighting;
077    /**
078     * Width of the 2D arrays used in this, as obtained from {@link #resistances}.
079     */
080    public int width;
081    /**
082     * Height of the 2D arrays used in this, as obtained from {@link #resistances}.
083     */
084    public int height;
085    /**
086     * The packed float color to mix background cells with when a cell has lighting and is within line-of-sight, but has
087     * no background color to start with (its color is 0f as a packed float, or {@link SColor#TRANSPARENT}).
088     */
089    public float backgroundColor;
090    /**
091     * How far the viewer can see without light; defaults to 4.0 cells, and you are encouraged to change this member
092     * field if the vision range changes after construction.
093     */
094    public double viewerRange;
095    /**
096     * A mapping from positions as {@link Coord} objects to {@link Radiance} objects that describe the color, lighting
097     * radius, and changes over time of any in-game lights that should be shown on the map and change FOV. You can edit
098     * this manually or by using {@link #moveLight(int, int, int, int)}, {@link #addLight(int, int, Radiance)}, and
099     * {@link #removeLight(int, int)}.
100     */
101    public OrderedMap<Coord, Radiance> lights;
102
103    /**
104     * A GreasedRegion that stores any cells that are in line-of-sight or are close enough to a cell in line-of-sight to
105     * potentially cast light into such a cell. Depends on the highest {@link Radiance#range} in {@link #lights}.
106     */
107    public GreasedRegion noticeable;
108    
109    /**
110     * Unlikely to be used except during serialization; makes a LightingHandler for a 20x20 fully visible level.
111     * The viewer vision range will be 4.0, and lights will use a circular shape.
112     */
113    public LightingHandler()
114    {
115        this(new double[20][20], SColor.FLOAT_BLACK, Radius.CIRCLE, 4.0);
116    }
117
118    /**
119     * Given a resistance array as produced by {@link squidpony.squidgrid.mapping.DungeonUtility#generateResistances(char[][])}
120     * or {@link squidpony.squidgrid.mapping.DungeonUtility#generateSimpleResistances(char[][])}, makes a
121     * LightingHandler that can have {@link Radiance} objects added to it in various locations. This will use a solid
122     * black background when it casts light on cells without existing lighting. The viewer vision range will be 4.0, and
123     * lights will use a circular shape.
124     * @param resistance a resistance array as produced by DungeonUtility
125     */
126    public LightingHandler(double[][] resistance)
127    {
128        this(resistance, SColor.FLOAT_BLACK, Radius.CIRCLE, 4.0);
129    }
130    /**
131     * Given a resistance array as produced by {@link squidpony.squidgrid.mapping.DungeonUtility#generateResistances(char[][])}
132     * or {@link squidpony.squidgrid.mapping.DungeonUtility#generateSimpleResistances(char[][])}, makes a
133     * LightingHandler that can have {@link Radiance} objects added to it in various locations.
134     * @param resistance a resistance array as produced by DungeonUtility
135     * @param backgroundColor the background color to use, as a libGDX color
136     * @param radiusStrategy the shape lights should take, typically {@link Radius#CIRCLE} for "realistic" lights or one
137     *                       of {@link Radius#DIAMOND} or {@link Radius#SQUARE} to match game rules for distance
138     * @param viewerVisionRange how far the player can see without light, in cells
139     */
140    public LightingHandler(double[][] resistance, Color backgroundColor, Radius radiusStrategy, double viewerVisionRange)
141    {
142        this(resistance, backgroundColor.toFloatBits(), radiusStrategy, viewerVisionRange);
143    }
144    /**
145     * Given a resistance array as produced by {@link squidpony.squidgrid.mapping.DungeonUtility#generateResistances(char[][])}
146     * or {@link squidpony.squidgrid.mapping.DungeonUtility#generateSimpleResistances(char[][])}, makes a
147     * LightingHandler that can have {@link Radiance} objects added to it in various locations.
148     * @param resistance a resistance array as produced by DungeonUtility
149     * @param backgroundColor the background color to use, as a packed float (produced by {@link Color#toFloatBits()})
150     * @param radiusStrategy the shape lights should take, typically {@link Radius#CIRCLE} for "realistic" lights or one
151     *                       of {@link Radius#DIAMOND} or {@link Radius#SQUARE} to match game rules for distance
152     * @param viewerVisionRange how far the player can see without light, in cells
153     */
154    public LightingHandler(double[][] resistance, float backgroundColor, Radius radiusStrategy, double viewerVisionRange)
155    {
156        this.radiusStrategy = radiusStrategy;
157        viewerRange = viewerVisionRange;
158        this.backgroundColor = backgroundColor;
159        resistances = resistance;
160        width = resistances.length;
161        height = resistances[0].length;
162        fovResult = new double[width][height];
163        tempFOV = new double[width][height];
164        losResult = new double[width][height];
165        colorLighting = SColor.blankColoredLighting(width, height);
166        tempColorLighting = new float[2][width][height];
167        Coord.expandPoolTo(width, height);
168        lights = new OrderedMap<>(32);
169        noticeable = new GreasedRegion(width, height);
170    }
171
172    /**
173     * Adds a Radiance as a light source at the given position. Overwrites any existing Radiance at the same position.
174     * @param x the x-position to add the Radiance at
175     * @param y the y-position to add the Radiance at
176     * @param light a Radiance object that can have a changing radius, color, and various other effects on lighting
177     * @return this for chaining
178     */
179    public LightingHandler addLight(int x, int y, Radiance light)
180    {
181        return addLight(Coord.get(x, y), light);
182    }
183    /**
184     * Adds a Radiance as a light source at the given position. Overwrites any existing Radiance at the same position.
185     * @param position the position to add the Radiance at
186     * @param light a Radiance object that can have a changing radius, color, and various other effects on lighting
187     * @return this for chaining
188     */
189    public LightingHandler addLight(Coord position, Radiance light)
190    {
191        lights.put(position, light);
192        return this;
193    }
194
195    /**
196     * Removes a Radiance as a light source from the given position, if any is present.
197     * @param x the x-position to remove the Radiance from
198     * @param y the y-position to remove the Radiance from
199     * @return this for chaining
200     */
201    public LightingHandler removeLight(int x, int y)
202    {
203        return removeLight(Coord.get(x, y));
204    }
205    /**
206     * Removes a Radiance as a light source from the given position, if any is present.
207     * @param position the position to remove the Radiance from
208     * @return this for chaining
209     */
210    public LightingHandler removeLight(Coord position)
211    {
212        lights.remove(position);
213        return this;
214    }
215    /**
216     * If a Radiance is present at oldX,oldY, this will move it to newX,newY and overwrite any existing Radiance at
217     * newX,newY. If no Radiance is present at oldX,oldY, this does nothing.
218     * @param oldX the x-position to move a Radiance from
219     * @param oldY the y-position to move a Radiance from
220     * @param newX the x-position to move a Radiance to
221     * @param newY the y-position to move a Radiance to
222     * @return this for chaining
223     */
224    public LightingHandler moveLight(int oldX, int oldY, int newX, int newY)
225    {
226        return moveLight(Coord.get(oldX, oldY), Coord.get(newX, newY));
227    }
228    /**
229     * If a Radiance is present at oldPosition, this will move it to newPosition and overwrite any existing Radiance at
230     * newPosition. If no Radiance is present at oldPosition, this does nothing.
231     * @param oldPosition the Coord to move a Radiance from
232     * @param newPosition the Coord to move a Radiance to
233     * @return this for chaining
234     */
235    public LightingHandler moveLight(Coord oldPosition, Coord newPosition)
236    {
237        Radiance old = lights.get(oldPosition);
238        if(old == null) return this;
239        lights.alter(oldPosition, newPosition);
240        return this;
241    }
242
243    /**
244     * Gets the Radiance at the given position, if present, or null if there is no light source there.
245     * @param x the x-position to look up
246     * @param y the y-position to look up
247     * @return the Radiance at the given position, or null if none is present there
248     */
249    public Radiance get(int x, int y)
250    {
251        return lights.get(Coord.get(x, y));
252    }
253    /**
254     * Gets the Radiance at the given position, if present, or null if there is no light source there.
255     * @param position the position to look up
256     * @return the Radiance at the given position, or null if none is present there
257     */
258    public Radiance get(Coord position)
259    {
260        return lights.get(position);
261    }
262
263    /**
264     * Edits {@link #colorLighting} by adding in and mixing the colors in {@link #tempColorLighting}, with the strength
265     * of light in tempColorLighting boosted by flare (which can be any finite float greater than -1f, but is usually
266     * from 0f to 1f when increasing strength).
267     * Primarily used internally, but exposed so outside code can do the same things this class can.
268     * @param flare boosts the effective strength of lighting in {@link #tempColorLighting}; usually from 0 to 1
269     */
270    public void mixColoredLighting(float flare)
271    {
272        float[][][] basis = colorLighting, other = tempColorLighting;
273        flare += 1f;
274        float b0, b1, o0, o1;
275        for (int x = 0; x < width; x++) {
276            for (int y = 0; y < height; y++) {
277                if (losResult[x][y] > 0) {
278                    if (resistances[x][y] >= 1) {
279                        o0 = 0f;
280                        if (y > 0) {
281                            if ((losResult[x][y - 1] > 0 && other[0][x][y - 1] > 0 && resistances[x][y - 1] < 1)
282                                    || (x > 0 && losResult[x - 1][y - 1] > 0 && other[0][x - 1][y - 1] > 0 && resistances[x - 1][y - 1] < 1)
283                                    || (x < width - 1 && losResult[x + 1][y - 1] > 0 && other[0][x + 1][y - 1] > 0 && resistances[x + 1][y - 1] < 1)) {
284                                o0 = other[0][x][y];
285                            }
286                        }
287                        if (y < height - 1) {
288                            if ((losResult[x][y + 1] > 0 && other[0][x][y + 1] > 0 && resistances[x][y + 1] < 1)
289                                    || (x > 0 && losResult[x - 1][y + 1] > 0 && other[0][x - 1][y + 1] > 0 && resistances[x - 1][y + 1] < 1)
290                                    || (x < width - 1 && losResult[x + 1][y + 1] > 0 && other[0][x + 1][y + 1] > 0 && resistances[x + 1][y + 1] < 1)) {
291                                o0 = other[0][x][y];
292                            }
293                        }
294                        if (x > 0 && losResult[x - 1][y] > 0 && other[0][x - 1][y] > 0 && resistances[x - 1][y] < 1) {
295                            o0 = other[0][x][y];
296                        }
297                        if (x < width - 1 && losResult[x + 1][y] > 0 && other[0][x + 1][y] > 0 && resistances[x + 1][y] < 1) {
298                            o0 = other[0][x][y];
299                        }
300                        if(o0 > 0f) o1 = other[1][x][y];
301                        else continue;
302                    } else {
303                        o0 = other[0][x][y];
304                        o1 = other[1][x][y];
305                    }
306                    if (o0 <= 0f || o1 == 0f)
307                        continue;
308                    b0 = basis[0][x][y];
309                    b1 = basis[1][x][y];
310                    if (b1 == FLOAT_WHITE) {
311                        basis[1][x][y] = o1;
312                        basis[0][x][y] = Math.min(1.0f, b0 + o0 * flare);
313                    } else {
314                        if (o1 != FLOAT_WHITE) {
315                            float change = (o0 - b0) * 0.5f + 0.5f;
316                            final int s = NumberTools.floatToIntBits(b1), e = NumberTools.floatToIntBits(o1),
317                                    rs = (s & 0xFF), gs = (s >>> 8) & 0xFF, bs = (s >>> 16) & 0xFF, as = s & 0xFE000000,
318                                    re = (e & 0xFF), ge = (e >>> 8) & 0xFF, be = (e >>> 16) & 0xFF, ae = (e >>> 25);
319                            change *= ae * 0.007874016f;
320                            basis[1][x][y] = NumberTools.intBitsToFloat(((int) (rs + change * (re - rs)) & 0xFF)
321                                    | ((int) (gs + change * (ge - gs)) & 0xFF) << 8
322                                    | (((int) (bs + change * (be - bs)) & 0xFF) << 16)
323                                    | as);
324                            basis[0][x][y] = Math.min(1.0f, b0 + o0 * change * flare);
325                        } else {
326                            basis[0][x][y] = Math.min(1.0f, b0 + o0 * flare);
327                        }
328                    }
329                }
330            }
331        }
332    }
333
334    /**
335     * Edits {@link #colorLighting} by adding in and mixing the given color where the light strength in {@link #tempFOV}
336     * is greater than 0, with that strength boosted by flare (which can be any finite float greater than -1f, but is
337     * usually from 0f to 1f when increasing strength).
338     * Primarily used internally, but exposed so outside code can do the same things this class can.
339     * @param flare boosts the effective strength of lighting in {@link #tempColorLighting}; usually from 0 to 1
340     */
341    public void mixColoredLighting(float flare, float color)
342    {
343        final float[][][] basis = colorLighting;
344        final double[][] otherStrength = tempFOV;
345        flare += 1f;
346        float b0, b1, o0, o1;
347        for (int x = 0; x < width; x++) {
348            for (int y = 0; y < height; y++) {
349                if (losResult[x][y] > 0) {
350                    if (resistances[x][y] >= 1) {
351                        o0 = 0f;
352                        if (y > 0) {
353                            if ((losResult[x][y - 1] > 0 && otherStrength[x][y - 1] > 0 && resistances[x][y - 1] < 1)
354                            || (x > 0 && losResult[x - 1][y - 1] > 0 && otherStrength[x - 1][y - 1] > 0 && resistances[x - 1][y - 1] < 1)
355                            || (x < width - 1 && losResult[x + 1][y - 1] > 0 && otherStrength[x + 1][y - 1] > 0 && resistances[x + 1][y - 1] < 1)) {
356                                o0 = (float) otherStrength[x][y];
357                            }
358                        }
359                        if (y < height - 1) {
360                            if ((losResult[x][y + 1] > 0 && otherStrength[x][y + 1] > 0 && resistances[x][y + 1] < 1)
361                                    || (x > 0 && losResult[x - 1][y + 1] > 0 && otherStrength[x - 1][y + 1] > 0 && resistances[x - 1][y + 1] < 1)
362                                    || (x < width - 1 && losResult[x + 1][y + 1] > 0 && otherStrength[x + 1][y + 1] > 0 && resistances[x + 1][y + 1] < 1)) {
363                                o0 = (float) otherStrength[x][y];
364                            }
365                        }
366                        if (x > 0 && losResult[x - 1][y] > 0 && otherStrength[x - 1][y] > 0 && resistances[x - 1][y] < 1) {
367                            o0 = (float) otherStrength[x][y];
368                        }
369                        if (x < width - 1 && losResult[x + 1][y] > 0 && otherStrength[x + 1][y] > 0 && resistances[x + 1][y] < 1) {
370                            o0 = (float) otherStrength[x][y];
371                        }
372                        if(o0 > 0f) o1 = color;
373                        else continue;
374                    } else {
375                        if((o0 = (float) otherStrength[x][y]) != 0) o1 = color;
376                        else continue;
377                    }
378                    b0 = basis[0][x][y];
379                    b1 = basis[1][x][y];
380                    if (b1 == FLOAT_WHITE) {
381                        basis[1][x][y] = o1;
382                        basis[0][x][y] = Math.min(1.0f, b0 + o0 * flare);
383                    } else {
384                        if (o1 != FLOAT_WHITE) {
385                            float change = (o0 - b0) * 0.5f + 0.5f;
386                            final int s = NumberTools.floatToIntBits(b1), e = NumberTools.floatToIntBits(o1),
387                                    rs = (s & 0xFF), gs = (s >>> 8) & 0xFF, bs = (s >>> 16) & 0xFF, as = s & 0xFE000000,
388                                    re = (e & 0xFF), ge = (e >>> 8) & 0xFF, be = (e >>> 16) & 0xFF, ae = (e >>> 25);
389                            change *= ae * 0.007874016f;
390                            basis[1][x][y] = NumberTools.intBitsToFloat(((int) (rs + change * (re - rs)) & 0xFF)
391                                    | ((int) (gs + change * (ge - gs)) & 0xFF) << 8
392                                    | (((int) (bs + change * (be - bs)) & 0xFF) << 16)
393                                    | as);
394                            basis[0][x][y] = Math.min(1.0f, b0 + o0 * change * flare);
395                        } else {
396                            basis[0][x][y] = Math.min(1.0f, b0 + o0 * flare);
397                        }
398                    }
399                }
400            }
401        }
402    }
403
404    /**
405     * Typically called every frame, this updates the flicker and strobe effects of Radiance objects and applies those
406     * changes in lighting color and strength to the various fields of this LightingHandler. This will only have an
407     * effect if {@link #calculateFOV(Coord)} or {@link #calculateFOV(int, int)} was called during the last time the
408     * viewer position changed; typically calculateFOV() only needs to be called once per move, while update() needs to
409     * be called once per frame. This method is usually called before each call to {@link #draw(float[][])}, but other
410     * code may be between the calls and may affect the lighting in customized ways.
411     */
412    public void update()
413    {
414        Radiance radiance;
415        SColor.eraseColoredLighting(colorLighting);
416        final int sz = lights.size();
417        Coord pos;
418        for (int i = 0; i < sz; i++) {
419            pos = lights.keyAt(i);
420            if(!noticeable.contains(pos))
421                continue;
422            radiance = lights.getAt(i);
423            FOV.reuseFOV(resistances, tempFOV, pos.x, pos.y, radiance.currentRange());
424            //SColor.colorLightingInto(tempColorLighting, tempFOV, radiance.color);
425            mixColoredLighting(radiance.flare, radiance.color);
426        }
427    }
428    /**
429     * Typically called every frame when there isn't a single viewer, this updates the flicker and strobe effects of
430     * Radiance objects and applies those changes in lighting color and strength to the various fields of this
431     * LightingHandler. This method is usually called before each call to {@link #draw(float[][])}, but other code may
432     * be between the calls and may affect the lighting in customized ways. This overload has no viewer, so all cells
433     * are considered visible unless they are fully obstructed (solid cells behind walls, for example). Unlike update(),
434     * this method does not need {@link #calculateFOV(Coord)} to be called for it to work properly.
435     */
436    public void updateAll()
437    {
438        Radiance radiance;
439        for (int x = 0; x < width; x++) {
440            PER_CELL:
441            for (int y = 0; y < height; y++) {
442                for (int xx = Math.max(0, x - 1), xi = 0; xi < 3 && xx < width; xi++, xx++) {
443                    for (int yy = Math.max(0, y - 1), yi = 0; yi < 3 && yy < height; yi++, yy++) {
444                        if(resistances[xx][yy] < 1.0){
445                            losResult[x][y] = 1.0;
446                            continue PER_CELL;
447                        }
448                    }
449                }
450            }
451        }
452        SColor.eraseColoredLighting(colorLighting);
453        final int sz = lights.size();
454        Coord pos;
455        for (int i = 0; i < sz; i++) {
456            pos = lights.keyAt(i);
457            radiance = lights.getAt(i);
458            FOV.reuseFOV(resistances, tempFOV, pos.x, pos.y, radiance.currentRange());
459            //SColor.colorLightingInto(tempColorLighting, tempFOV, radiance.color);
460            mixColoredLighting(radiance.flare, radiance.color);
461        }
462        for (int x = 0; x < width; x++) {
463            for (int y = 0; y < height; y++) {
464                if (losResult[x][y] > 0.0) {
465                    fovResult[x][y] = MathUtils.clamp(losResult[x][y] + colorLighting[0][x][y], 0, 1);
466                }
467            }
468        }
469    }
470    /**
471     * Updates the flicker and strobe effects of a Radiance object and applies the lighting from just that Radiance to
472     * just the {@link #colorLighting} field, without changing FOV. This method is meant to be used for GUI effects that
473     * aren't representative of something a character in the game could interact with. It is usually called after
474     * {@link #update()} and before each call to {@link #draw(float[][])}, but other code may be between the calls
475     * and may affect the lighting in customized ways.
476     * @param pos the position of the light effect
477     * @param radiance the Radiance to update standalone, which does not need to be already added to this 
478     */
479    public void updateUI(Coord pos, Radiance radiance)
480    {
481        updateUI(pos.x, pos.y, radiance);
482    }
483
484    /**
485     * Updates the flicker and strobe effects of a Radiance object and applies the lighting from just that Radiance to
486     * just the {@link #colorLighting} field, without changing FOV. This method is meant to be used for GUI effects that
487     * aren't representative of something a character in the game could interact with. It is usually called after
488     * {@link #update()} and before each call to {@link #draw(float[][])}, but other code may be between the calls
489     * and may affect the lighting in customized ways.
490     * @param lightX the x-position of the light effect
491     * @param lightY the y-position of the light effect
492     * @param radiance the Radiance to update standalone, which does not need to be already added to this 
493     */
494    public void updateUI(int lightX, int lightY, Radiance radiance)
495    {
496        FOV.reuseFOV(resistances, tempFOV, lightX, lightY, radiance.currentRange());
497        //SColor.colorLightingInto(tempColorLighting, tempFOV, radiance.color);
498        mixColoredLighting(radiance.flare, radiance.color);
499    }
500
501    /**
502     * Given a SparseLayers, fills the SparseLayers with different colors based on what lights are present in line of
503     * sight of the viewer and the various flicker or strobe effects that Radiance light sources can do. You should
504     * usually call {@link #update()} before each call to draw(), but you may want to make custom changes to the
505     * lighting in between those two calls (that is the only place those changes will be noticed).
506     * @param layers a SparseLayers that may have existing background colors (these will be mixed in)
507     */
508    public void draw(SparseLayers layers)
509    {
510        draw(layers.backgrounds);
511    }
512    /**
513     * Given a SquidPanel that should be only solid blocks (such as the background of a SquidLayers) and a position for
514     * the viewer (typically the player), fills the SquidPanel with different colors based on what lights are present in
515     * line of sight of the viewer and the various flickering or pulsing effects that Radiance light sources can do. 
516     * Given a SquidPanel that should be only solid blocks (such as the background of a SquidLayers), fills the
517     * SquidPanel with different colors based on what lights are present in line of sight of the viewer and the various
518     * flicker or strobe effects that Radiance light sources can do. You should usually call {@link #update()}
519     * before each call to draw(), but you may want to make custom changes to the lighting in between those two calls
520     * (that is the only place those changes will be noticed).
521     * @param background a SquidPanel used as a background, such as the back Panel of a SquidLayers
522     */
523    public void draw(SquidPanel background)
524    {
525        draw(background.colors);
526    }
527
528    /**
529     * Given a 2D array of packed float colors, fills the 2D array with different colors based on what lights are
530     * present in line of sight of the viewer and the various flicker or strobe effects that Radiance light sources can
531     * do. You should usually call {@link #update()} before each call to draw(), but you may want to make custom
532     * changes to the lighting in between those two calls (that is the only place those changes will be noticed).
533     * @param backgrounds a 2D float array, typically obtained from {@link squidpony.squidgrid.gui.gdx.SquidPanel#colors} or {@link squidpony.squidgrid.gui.gdx.SparseLayers#backgrounds}
534     */
535    public void draw(float[][] backgrounds)
536    {
537        float current;
538        for (int x = 0; x < width; x++) {
539            for (int y = 0; y < height; y++) {
540                if (losResult[x][y] > 0.0 && fovResult[x][y] > 0.0) {
541                        current = backgrounds[x][y];
542                        if(current == 0f)
543                            current = backgroundColor;
544                        backgrounds[x][y] = lerpFloatColorsBlended(current,
545                                colorLighting[1][x][y], colorLighting[0][x][y] * 0.4f);
546                }
547            }
548        }
549    }
550    /**
551     * Used to calculate what cells are visible as if any flicker or strobe effects were simply constant light sources.
552     * Runs part of the calculations to draw lighting as if all radii are at their widest, but does no actual drawing.
553     * This should be called any time the viewer moves to a different cell, and it is critical that this is called (at
554     * least) once after a move but before {@link #update()} gets called to change lighting at the new cell. This sets
555     * important information on what lights might need to be calculated during each update(Coord) call; it does not need
556     * to be called before {@link #updateAll()} (with no arguments) because that doesn't need a viewer. Sets
557     * {@link #fovResult}, {@link #losResult}, and {@link #noticeable} based on the given viewer position and any lights
558     * in {@link #lights}.
559     * @param viewer the position of the player or other viewer
560     * @return the calculated FOV 2D array, which is also stored in {@link #fovResult}
561     */
562    public double[][] calculateFOV(Coord viewer)
563    {
564        return calculateFOV(viewer.x, viewer.y);
565    }
566
567    /**
568     * Used to calculate what cells are visible as if any flicker or strobe effects were simply constant light sources.
569     * Runs part of the calculations to draw lighting as if all radii are at their widest, but does no actual drawing.
570     * This should be called any time the viewer moves to a different cell, and it is critical that this is called (at
571     * least) once after a move but before {@link #update()} gets called to change lighting at the new cell. This sets
572     * important information on what lights might need to be calculated during each update(Coord) call; it does not need
573     * to be called before {@link #updateAll()} (with no arguments) because that doesn't need a viewer. Sets
574     * {@link #fovResult}, {@link #losResult}, and {@link #noticeable} based on the given viewer position and any lights
575     * in {@link #lights}.
576     * @param viewerX the x-position of the player or other viewer
577     * @param viewerY the y-position of the player or other viewer
578     * @return the calculated FOV 2D array, which is also stored in {@link #fovResult}
579     */
580    public double[][] calculateFOV(int viewerX, int viewerY)
581    {
582        return calculateFOV(viewerX, viewerY, 0, 0, width, height);
583    }
584
585    /**
586     * Used to calculate what cells are visible as if any flicker or strobe effects were simply constant light sources.
587     * Runs part of the calculations to draw lighting as if all radii are at their widest, but does no actual drawing.
588     * This should be called any time the viewer moves to a different cell, and it is critical that this is called (at
589     * least) once after a move but before {@link #update()} gets called to change lighting at the new cell. This sets
590     * important information on what lights might need to be calculated during each update(Coord) call; it does not need
591     * to be called before {@link #updateAll()} (with no arguments) because that doesn't need a viewer. This overload
592     * allows the area this processes to be restricted to a rectangle between {@code minX} and {@code maxX} and between
593     * {@code minY} and {@code maxY}, ignoring any lights outside that area (typically because they are a long way out
594     * from the map's shown area). Sets {@link #fovResult}, {@link #losResult}, and {@link #noticeable} based on the
595     * given viewer position and any lights in {@link #lights}.
596     * @param viewerX the x-position of the player or other viewer
597     * @param viewerY the y-position of the player or other viewer
598     * @param minX inclusive lower bound on x to calculate
599     * @param minY inclusive lower bound on y to calculate
600     * @param maxX exclusive upper bound on x to calculate
601     * @param maxY exclusive upper bound on y to calculate
602     * @return the calculated FOV 2D array, which is also stored in {@link #fovResult}
603     */
604    public double[][] calculateFOV(int viewerX, int viewerY, int minX, int minY, int maxX, int maxY)
605    {
606        Radiance radiance;
607        minX = MathUtils.clamp(minX, 0, width);
608        maxX = MathUtils.clamp(maxX, 0, width);
609        minY = MathUtils.clamp(minY, 0, height);
610        maxY = MathUtils.clamp(maxY, 0, height);
611        FOV.reuseFOV(resistances, fovResult, viewerX, viewerY, viewerRange, radiusStrategy);
612        SColor.eraseColoredLighting(colorLighting);
613        final int sz = lights.size();
614        float maxRange = 0, range;
615        Coord pos;
616        for (int i = 0; i < sz; i++) {
617            pos = lights.keyAt(i);
618            range = lights.getAt(i).range;
619            if(range > maxRange && 
620                    pos.x + range >= minX && pos.x - range < maxX && pos.y + range >= minY && pos.y - range < maxY) 
621                maxRange = range;
622        }
623        FOV.reuseLOS(resistances, losResult, viewerX, viewerY, minX, minY, maxX, maxY);
624        noticeable.refill(losResult, 0.0001, Double.POSITIVE_INFINITY).expand8way((int) Math.ceil(maxRange));
625        for (int i = 0; i < sz; i++) {
626            pos = lights.keyAt(i);
627            if(!noticeable.contains(pos))
628                continue;
629            radiance = lights.getAt(i);
630            FOV.reuseFOV(resistances, tempFOV, pos.x, pos.y, radiance.range);
631            //SColor.colorLightingInto(tempColorLighting, tempFOV, radiance.color);
632            mixColoredLighting(radiance.flare, radiance.color);
633        }
634        for (int x = Math.max(0, minX); x < maxX && x < width; x++) {
635            for (int y = Math.max(0, minY); y < maxY && y < height; y++) {
636                if (losResult[x][y] > 0.0) {
637                    fovResult[x][y] = MathUtils.clamp(fovResult[x][y] + colorLighting[0][x][y], 0, 1);
638                }
639            }
640        }
641        return fovResult;
642    }
643
644}