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}