001package squidpony.squidgrid.gui.gdx; 002 003import com.badlogic.gdx.Gdx; 004import com.badlogic.gdx.graphics.Camera; 005import com.badlogic.gdx.graphics.Color; 006import com.badlogic.gdx.graphics.g2d.Batch; 007import com.badlogic.gdx.math.Frustum; 008import com.badlogic.gdx.math.MathUtils; 009import com.badlogic.gdx.scenes.scene2d.Action; 010import com.badlogic.gdx.scenes.scene2d.Actor; 011import com.badlogic.gdx.scenes.scene2d.Stage; 012import com.badlogic.gdx.scenes.scene2d.actions.Actions; 013import com.badlogic.gdx.scenes.scene2d.actions.TemporalAction; 014import com.badlogic.gdx.utils.Align; 015import com.badlogic.gdx.utils.IntIntMap; 016import com.badlogic.gdx.utils.viewport.Viewport; 017import squidpony.ArrayTools; 018import squidpony.IColorCenter; 019import squidpony.panel.IColoredString; 020import squidpony.squidgrid.Direction; 021import squidpony.squidgrid.Radius; 022import squidpony.squidmath.Noise; 023import squidpony.squidmath.NumberTools; 024import squidpony.squidmath.StatefulRNG; 025import squidpony.squidmath.WhirlingNoise; 026 027import java.util.ArrayList; 028 029/** 030 * A general-purpose char display grid that supports one layer of backgrounds and arbitrarily many layers of foreground, 031 * only rendering foreground chars when something is present at a char's location. This also stores an ArrayList of 032 * {@link TextCellFactory.Glyph} items that can be added to using {@link #glyph(char, Color, int, int)} or one of 033 * several other methods; these Glyphs can have effects performed on them that are analogous to the effects on 034 * {@link SquidPanel}'s {@link AnimatedEntity} effects, such as 035 * {@link #slide(TextCellFactory.Glyph, int, int, int, int, float, Runnable)}. Unlike SquidPanel and SquidLayers, this 036 * class does not typically provide many overloads for each method, and usually limits the overloads to one that takes 037 * a packed float color and one that takes a libGDX {@link Color}, and all the other parameters are expected to be 038 * specified explicitly rather than relying on default behavior. SparseLayers tends to perform better than SquidLayers, 039 * especially when a lot of the map is unassigned/blank. It will cause less GC pressure when packed floats are used, and 040 * because {@link SColor} constants pre-calculate their packed float values, using the overloads that take a Color will 041 * perform a little better when given an SColor than a plain libGDX Color. This class makes heavy use of 042 * {@link SColor#lerpFloatColors(float, float, float)} because it should cause no GC pressure (it doesn't create 043 * temporary Color objects), and if you don't want to mutate Colors in-place (which is a bad idea for constants), then 044 * using lerpFloatColors with the packed float color API here should be very effective. 045 * <br> 046 * The names are a little different for some member fields here; there's a {@link TextCellFactory} called {@link #font}, 047 * a {@code float[][]} called {@link #backgrounds} that stores packed float colors, an ArrayList of 048 * {@link SparseTextMap} that implements the sparse drawing behavior of foreground chars called {@link #layers}, and so 049 * on. 050 * <br> 051 * Created by Tommy Ettinger on 7/28/2017. 052 */ 053public class SparseLayers extends Actor implements IPackedColorPanel { 054 public int gridWidth, gridHeight; 055 /** 056 * A 2D float array of background colors as packed floats. Must have dimensions matching {@link #gridWidth} and 057 * {@link #gridHeight}, and must be non-null with non-null interior arrays, but can otherwise be assigned 2D float 058 * arrays from other sources (that use floats to represent colors, not just any number). 059 */ 060 public float[][] backgrounds; 061 /** 062 * The default foreground color when none is specified, as a Color object. Defaults to white. 063 */ 064 public Color defaultForeground = SColor.WHITE, 065 /** 066 * Currently unused internally, but public so if some background color needs to be stored with this SparseLayers, 067 * then there will be a logical place for it. Defaults to black. 068 */ 069 defaultBackground = SColor.BLACK; 070 /** 071 * The value of {@link #defaultForeground} as a float, for easier usage with the methods that use floats for colors. 072 * Defaults to white. 073 */ 074 public float defaultPackedForeground = SColor.FLOAT_WHITE, 075 /** 076 * The value of {@link #defaultBackground} as a float, for easier usage with the methods that use floats for colors. 077 * Currently unused internally, but public so if some background color needs to be stored with this SparseLayers, 078 * then there will be a logical place for it. Defaults to black. 079 */ 080 defaultPackedBackground = SColor.FLOAT_BLACK; 081 /** 082 * A list of SparseTextMap objects, with each representing a foreground layer. 083 */ 084 public ArrayList<SparseTextMap> layers; 085 protected IntIntMap mapping; 086 /** 087 * The TextCellFactory that is used to determine font size as well as cell size; must be initialized, usually using 088 * {@link TextCellFactory#initBySize()}, if this is changed after construction. 089 */ 090 public TextCellFactory font; 091 /** 092 * Will always be 0 unless user code affects it; use {@link #hasActiveAnimations()} to track animations instead. 093 * The approach of tracking animations via a counter was prone to error when multiple effects might remove a Glyph 094 * or adjust the animationCount in one direction but not the other. This could result in never-ending phases of a 095 * game where input wasn't handled because animationCount was greater than 0, but whatever effect was supposed to 096 * reduce animationCount would never happen. 097 * @deprecated Use {@link #hasActiveAnimations()} instead of adjusting this manually 098 */ 099 public int animationCount; 100 /** 101 * A list of individually-movable Glyph objects. This field is public, and though it shouldn't be assigned null (you 102 * don't really need to be told that), there may be cases where you may need manual control over what Glyph objects 103 * should be added or removed from this SparseLayers. 104 */ 105 public ArrayList<TextCellFactory.Glyph> glyphs; 106 /** 107 * An IColorCenter to affect color caching and filtering; usually a SquidColorCenter, which can be easily obtained 108 * via {@link DefaultResources#getSCC()}. 109 */ 110 public IColorCenter<Color> scc; 111 112 protected SparseLayers() 113 { 114 } 115 public SparseLayers(int gridWidth, int gridHeight) 116 { 117 this(gridWidth, gridHeight, 10, 16, DefaultResources.getStretchableFont()); 118 } 119 120 public SparseLayers(int gridWidth, int gridHeight, float cellWidth, float cellHeight) 121 { 122 this(gridWidth, gridHeight, cellWidth, cellHeight, DefaultResources.getStretchableFont()); 123 } 124 125 public SparseLayers(int gridWidth, int gridHeight, float cellWidth, float cellHeight, TextCellFactory font) { 126 this(gridWidth, gridHeight, cellWidth, cellHeight, font, 0f, 0f); 127 } 128 129 public SparseLayers(int gridWidth, int gridHeight, float cellWidth, float cellHeight, TextCellFactory font, float xOffset, float yOffset) { 130 this.gridWidth = MathUtils.clamp(gridWidth, 1, 65535); 131 this.gridHeight = MathUtils.clamp(gridHeight, 1, 65535); 132 backgrounds = new float[this.gridWidth][this.gridHeight]; 133 layers = new ArrayList<>(4); 134 if(font.initialized()) 135 this.font = font; 136 else 137 this.font = font.width(cellWidth).height(cellHeight).initBySize(); 138 layers.add(new SparseTextMap(gridWidth * gridHeight >> 2)); 139 mapping = new IntIntMap(4); 140 mapping.put(0, 0); 141 glyphs = new ArrayList<>(16); 142 scc = DefaultResources.getSCC(); 143 setBounds(xOffset, yOffset, 144 this.font.actualCellWidth * this.gridWidth, this.font.actualCellHeight * this.gridHeight); 145 } 146 147 /** 148 * Gets the number of layers currently used in this SparseLayers; if a layer is put into that is equal to or greater 149 * than this layer count, then a new layer is created to hold the latest placement. It is recommended that you 150 * assign to the layer equal to the result of this method if you want to add a layer. You can add to layers that are 151 * higher than this result, and can technically do so out of order, but this just gets confusing because their 152 * ordering won't be related to the numbers of the layers. 153 * @return the current number of layers in this SparseLayers 154 */ 155 public int getLayerCount() 156 { 157 return layers.size(); 158 } 159 160 /** 161 * Gets the layer, as a SparseTextMap, that is associated with the given int. If none is associated, this returns 162 * null. The associating number does not necessarily have to be less than the number of layers in this SparseLayers. 163 * Returns a direct reference to the SparseTextMap layer. 164 * @param layer the int that is associated with a layer, usually the int used to add to that layer. 165 * @return the SparseTextMap layer associated with the given int 166 */ 167 public SparseTextMap getLayer(int layer) 168 { 169 layer = mapping.get(layer, -1); 170 if(layer < 0) 171 return null; 172 else 173 return layers.get(layer); 174 } 175 176 /** 177 * Sets the SparseTextMap associated with the given layerNumber to the given contents. If layerNumber is too high 178 * to set an existing layer, this will add contents as a new layer on top of the others. 179 * @param layerNumber must be 0 or greater 180 * @param contents a SparseTextMap, possibly obtained with {@link #getLayer(int)} 181 */ 182 public void setLayer(int layerNumber, SparseTextMap contents) 183 { 184 if(layerNumber < 0) 185 return; 186 layerNumber = mapping.get(layerNumber, layerNumber); 187 if(layerNumber >= layers.size()) 188 { 189 mapping.put(layerNumber, layers.size()); 190 layers.add(contents); 191 } 192 else 193 { 194 layers.set(layerNumber, contents); 195 } 196 197 } 198 199 /** 200 * Finds the layer number associated with layerMap, or -1 if the given SparseTextMap is not in this SparseLayers. 201 * @param layerMap a SparseTextMap that was likely returned by {@link #addLayer()} 202 * @return the int the layer is associated with, or -1 if it is not in this SparseLayers. 203 */ 204 public int findLayer(SparseTextMap layerMap) 205 { 206 int a = layers.indexOf(layerMap); 207 if(a < 0) 208 return -1; 209 return mapping.findKey(a, -1); 210 } 211 212 /** 213 * Adds a layer as a SparseTextMap to this SparseLayers and returns the one just added. The layer will generally be 214 * associated with a layer number equal to the number of layers currently present, and will be rendered over the 215 * existing layers. It might not be associated with the same number if layers were added out-of-order or with skips 216 * between associations. If you want to add a layer at some alternate layer number (which can be confusing but may 217 * be needed for some reason), there is another overload that allows you to specify the association number. 218 * If you may have added some layers out of order or skipped some numbers, you can use 219 * {@link #findLayer(SparseTextMap)} on the returned SparseTextMap to get its association number. 220 * @return a SparseTextMap that was just added; null if something went wrong 221 */ 222 public SparseTextMap addLayer() 223 { 224 int association = layers.size(); 225 while (mapping.containsKey(association)) { 226 ++association; 227 } 228 return addLayer(association); 229 } 230 /** 231 * Adds a layer as a SparseTextMap to this SparseLayers and returns the one just added, or returns an existing layer 232 * if one is already associated with the given number. The layer can be greater than the number of layers currently 233 * present, which will add a layer to be rendered over the existing layers, but the number that refers to that layer 234 * will not change. It is recommended that to add a layer, you only add at the value equal to 235 * {@link #getLayerCount()}, which will maintain the order and layer numbers in a sane way, and this is the behavior 236 * of the addLayer overload that does not take a parameter. 237 * @param association the number to associate the layer with; should usually be between 0 and {@link #getLayerCount()} inclusive 238 * @return a SparseTextMap that either was just added or was already associated with the given layer number; null if something went wrong 239 */ 240 public SparseTextMap addLayer(int association) 241 { 242 if(association < 0) 243 return null; 244 association = mapping.get(association, association); 245 if(association >= layers.size()) 246 { 247 mapping.put(association, layers.size()); 248 SparseTextMap stm = new SparseTextMap(gridWidth * gridHeight >> 4); 249 layers.add(stm); 250 return stm; 251 } 252 else 253 { 254 return layers.get(association); 255 } 256 } 257 258 /** 259 * Puts the character {@code c} at {@code (x, y)} with the default foreground. Does not change the background. 260 * 261 * @param x the x position to place the char at 262 * @param y the y position to place the char at 263 * @param c the char to place, using the default foreground color 264 */ 265 @Override 266 public void put(int x, int y, char c) { 267 put(x, y, c, defaultPackedForeground, 0f); 268 } 269 270 /** 271 * Puts the ICellVisible cell at the position x,y; does not change the background. If {@code cell.getPackedColor()} 272 * is 0f, then this does not change the foreground contents either. Does not filter the given colors. 273 * @param x the x position to place the char at 274 * @param y the y position to place the char at 275 * @param cell an ICellVisible, which is often implemented outside of SquidLib; this uses its char and color. 276 */ 277 public void put(int x, int y, ICellVisible cell) { 278 put(x, y, cell.getSymbol(), cell.getPackedColor(), 0f); 279 } 280 281 /** 282 * Puts the given string horizontally with the first character at the given 283 * offset. Uses the given color for the text and does not change the background 284 * color at all. 285 * <p> 286 * Does not word wrap. Characters that are not renderable (due to being at 287 * negative offsets or offsets greater than the grid size) will not be shown 288 * but will not cause any malfunctions. 289 * 290 * @param xOffset the x coordinate of the first character 291 * @param yOffset the y coordinate of the first character 292 * @param string the characters to be displayed 293 * @param foreground the color to use for the text 294 */ 295 @Override 296 public void put(int xOffset, int yOffset, String string, Color foreground) { 297 put(xOffset, yOffset, string, foreground, null); 298 } 299 300 /** 301 * Puts the given string horizontally with the first character at the given 302 * offset, using the colors that {@code cs} provides. Does not change the 303 * background colors. Does filter the colors from cs if the color center this 304 * uses is set up to filter colors. 305 * <p> 306 * Does not word wrap. Characters that are not renderable (due to being at 307 * negative offsets or offsets greater than the grid size) will not be shown 308 * but will not cause any malfunctions. 309 * 310 * @param xOffset the x coordinate of the first character 311 * @param yOffset the y coordinate of the first character 312 * @param cs an {@link IColoredString} with potentially multiple colors 313 */ 314 @Override 315 public void put(int xOffset, int yOffset, IColoredString<? extends Color> cs) { 316 int x = xOffset; 317 for (IColoredString.Bucket<? extends Color> fragment : cs) { 318 final String s = fragment.getText(); 319 final Color color = fragment.getColor(); 320 put(x, yOffset, s, color == null ? getDefaultForegroundColor() : scc.filter(color), null); 321 x += s.length(); 322 } 323 } 324 325 /** 326 * Puts the character {@code c} at {@code (x, y)} with some {@code color}. 327 * Does not change the background colors. 328 * 329 * @param x the x position to place the char at 330 * @param y the y position to place the char at 331 * @param c the char to place 332 * @param color the color to use for c; if null or fully transparent, nothing will change in the foreground 333 */ 334 @Override 335 public void put(int x, int y, char c, Color color) { 336 put(x, y, c, color, null); 337 } 338 339 /** 340 * Sets the background colors to match the given Color 2D array. If the colors argument is null, does nothing. 341 * This will filter each Color in colors if the color center this uses has a filter. 342 * @param colors the background colors for the given chars 343 */ 344 public void put(Color[][] colors) 345 { 346 put(null, colors); 347 } 348 349 /** 350 * Sets the background colors to match the given 2D array of colors as packed floats. If the colors argument is 351 * null, does nothing. This will not filter the passed colors at all. 352 * @param colors the background colors to use for this SparseLayers 353 */ 354 public void put(float[][] colors) 355 { 356 put(null, colors); 357 } 358 /** 359 * Places the given char 2D array, if-non-null, in the default foreground color starting at x=0, y=0. 360 * @param chars a 2D char array to place without affecting background colors; these chars will use the default foreground color 361 */ 362 public void putChars(char[][] chars) { 363 if(chars == null) 364 return; 365 SparseTextMap layer = layers.get(0); 366 for (int i = 0; i < chars.length; i++) { 367 if (chars[i] != null) { 368 for (int j = 0; j < chars[i].length; j++) { 369 layer.place(i, j, chars[i][j], defaultPackedForeground); 370 } 371 } 372 } 373 } 374 /** 375 * Places the given char 2D array, if-non-null, in the foreground starting at x=0, y=0. Each char will be drawn in 376 * the corresponding foreground color from foregrounds. 377 * @param chars a 2D char array to place without affecting background colors 378 * @param foregrounds a 2D float array of encoded colors as produced by {@link Color#toFloatBits()}; applies to char foregrounds 379 */ 380 public void putChars(char[][] chars, float[][] foregrounds) { 381 if(chars == null) 382 return; 383 if(foregrounds == null) 384 { 385 putChars(chars); 386 return; 387 } 388 SparseTextMap layer = layers.get(0); 389 for (int i = 0; i < chars.length; i++) { 390 if (chars[i] != null) { 391 for (int j = 0; j < chars[i].length; j++) { 392 layer.place(i, j, chars[i][j], foregrounds[i][j]); 393 } 394 } 395 } 396 } 397 /** 398 * Places the given char 2D array, if-non-null, in the foreground starting at x=0, y=0. Each char will be drawn in 399 * the corresponding foreground color from foregrounds. 400 * @param chars a 2D char array to place without affecting background colors 401 * @param foregrounds a 2D array of libGDX colors (any may also be an SColor or other subclass); applies to char foregrounds 402 */ 403 public void putChars(char[][] chars, Color[][] foregrounds) { 404 if(chars == null) 405 return; 406 if(foregrounds == null) 407 { 408 putChars(chars); 409 return; 410 } 411 SparseTextMap layer = layers.get(0); 412 for (int i = 0; i < chars.length; i++) { 413 if (chars[i] != null) { 414 for (int j = 0; j < chars[i].length; j++) { 415 layer.place(i, j, chars[i][j], scc.filter(foregrounds[i][j]).toFloatBits()); 416 } 417 } 418 } 419 } 420 /** 421 * Places the given char 2D array, if-non-null, in the default foreground color starting at x=0, y=0, while also 422 * setting the background colors to match the given Color 2D array. If the colors argument is null, does nothing. If 423 * the chars argument is null, only affects the background colors. This will filter each Color in colors if the 424 * color center this uses has a filter. 425 * @param chars Can be {@code null}, indicating that only colors must be put. 426 * @param colors the background colors for the given chars 427 */ 428 @Override 429 public void put(char[][] chars, Color[][] colors) { 430 if(chars == null) 431 { 432 if(colors != null) 433 { 434 for (int i = 0; i < colors.length; i++) { 435 if (colors[i] == null) 436 continue; 437 for (int j = 0; j < colors[i].length; j++) { 438 backgrounds[i][j] = (colors[i][j] == null) ? 0f : scc.filter(colors[i][j]).toFloatBits(); 439 } 440 } 441 } 442 } 443 else 444 { 445 SparseTextMap layer = layers.get(0); 446 if(colors == null) 447 { 448 for (int i = 0; i < chars.length; i++) { 449 if(chars[i] == null) 450 continue; 451 for (int j = 0; j < chars[i].length; j++) { 452 layer.place(i, j, chars[i][j], defaultPackedForeground); 453 } 454 } 455 } 456 else 457 { 458 for (int i = 0; i < chars.length && i < colors.length; i++) { 459 if(colors[i] == null || chars[i] == null) 460 continue; 461 for (int j = 0; j < chars[i].length && j < colors[i].length; j++) { 462 if(colors[i][j] == null) 463 layer.place(i, j, chars[i][j], defaultPackedForeground); 464 else 465 put(i, j, chars[i][j], defaultPackedForeground, scc.filter(colors[i][j]).toFloatBits()); 466 } 467 } 468 } 469 } 470 } 471 /** 472 * Places the given char 2D array, if-non-null, in the default foreground color starting at x=0, y=0, while also 473 * setting the background colors to match the given 2D array of colors as packed floats. If the colors argument is 474 * null, does nothing. If the chars argument is null, only affects the background colors. This will not filter the 475 * passed colors at all. 476 * @param chars Can be {@code null}, indicating that only colors must be put. 477 * @param colors the background colors for the given chars 478 */ 479 public void put(char[][] chars, float[][] colors) { 480 if(chars == null) 481 { 482 if(colors != null) { 483 for (int i = 0; i < colors.length; i++) { 484 if (colors[i] == null) 485 continue; 486 System.arraycopy(colors[i], 0, backgrounds[i], 0, colors[i].length); 487 } 488 } 489 } 490 else 491 { 492 if(colors == null) 493 { 494 for (int i = 0; i < chars.length; i++) { 495 if(chars[i] == null) 496 continue; 497 for (int j = 0; j < chars[i].length; j++) { 498 put(i, j, chars[i][j], defaultPackedForeground, 0f); 499 } 500 } 501 } 502 else 503 { 504 for (int i = 0; i < chars.length && i < colors.length; i++) { 505 if(colors[i] == null || chars[i] == null) 506 continue; 507 for (int j = 0; j < chars[i].length && j < colors[i].length; j++) { 508 put(i, j, chars[i][j], defaultPackedForeground, colors[i][j]); 509 } 510 } 511 } 512 } 513 } 514 /** 515 * Places the given char 2D array, if-non-null, with the given foreground colors in the first Color 2D array, 516 * starting at x=0, y=0, while also setting the background colors to match the second Color 2D array. If the 517 * bgColors argument is null, only affects foreground chars and colors. If the chars argument or the fgColors 518 * argument is null, only affects the background colors. Any positions where a Color in fgColors is null will not 519 * have a char placed (this can be used to restrict what is placed). This will filter each Color in the background 520 * and foreground if the color center this uses has a filter. 521 * @param chars Can be {@code null}, indicating that only colors must be put. 522 * @param fgColors the foreground Colors for the given chars 523 * @param bgColors the background Colors for the given chars 524 */ 525 public void put(char[][] chars, Color[][] fgColors, Color[][] bgColors) { 526 if(chars == null || fgColors == null) 527 { 528 if(bgColors != null) 529 { 530 for (int i = 0; i < bgColors.length; i++) { 531 if (bgColors[i] == null) 532 continue; 533 for (int j = 0; j < bgColors[i].length; j++) { 534 backgrounds[i][j] = (bgColors[i][j] == null) ? 0f : scc.filter(bgColors[i][j]).toFloatBits(); 535 } 536 } 537 } 538 } 539 else 540 { 541 if(bgColors == null) 542 { 543 for (int i = 0; i < chars.length && i < fgColors.length; i++) { 544 if(chars[i] == null || fgColors[i] == null) 545 continue; 546 for (int j = 0; j < chars[i].length && j < fgColors[i].length; j++) { 547 put(i, j, chars[i][j], fgColors[i][j], null); 548 } 549 } 550 } 551 else 552 { 553 for (int i = 0; i < chars.length && i < bgColors.length && i < fgColors.length; i++) { 554 if(bgColors[i] == null || chars[i] == null || fgColors[i] == null) 555 continue; 556 for (int j = 0; j < chars[i].length && j < bgColors[i].length && j < fgColors[i].length; j++) { 557 put(i, j, chars[i][j], fgColors[i][j], bgColors[i][j]); 558 } 559 } 560 } 561 } 562 } 563 /** 564 * Places the given char 2D array, if-non-null, with the given foreground colors in the first float 2D array, 565 * starting at x=0, y=0, while also setting the background colors to match the second float 2D array. If the 566 * bgColors argument is null, only affects foreground chars and colors. If the chars argument or the fgColors 567 * argument is null, only affects the background colors. Any positions where a float in fgColors is 0 will not 568 * have a char placed (this can be used to restrict what is placed). This will not filter any colors. 569 * @param chars Can be {@code null}, indicating that only colors must be put. 570 * @param fgColors the foreground colors for the given chars, as packed floats 571 * @param bgColors the background colors for the given chars, as packed floats 572 */ 573 public void put(char[][] chars, float[][] fgColors, float[][] bgColors) { 574 if(chars == null || fgColors == null) 575 { 576 if(bgColors != null) 577 { 578 for (int i = 0; i < bgColors.length; i++) { 579 if (bgColors[i] == null) 580 continue; 581 System.arraycopy(bgColors[i], 0, backgrounds[i], 0, bgColors[i].length); 582 } 583 } 584 } 585 else 586 { 587 if(bgColors == null) 588 { 589 for (int i = 0; i < chars.length && i < fgColors.length; i++) { 590 if(chars[i] == null || fgColors[i] == null) 591 continue; 592 for (int j = 0; j < chars[i].length && j < fgColors[i].length; j++) { 593 put(i, j, chars[i][j], fgColors[i][j], 0f); 594 } 595 } 596 } 597 else 598 { 599 for (int i = 0; i < chars.length && i < bgColors.length && i < fgColors.length; i++) { 600 if(bgColors[i] == null || chars[i] == null || fgColors[i] == null) 601 continue; 602 for (int j = 0; j < chars[i].length && j < bgColors[i].length && j < fgColors[i].length; j++) { 603 put(i, j, chars[i][j], fgColors[i][j], bgColors[i][j]); 604 } 605 } 606 } 607 } 608 } 609 610 /** 611 * @return The number of cells that this panel spans, horizontally. 612 */ 613 @Override 614 public int gridWidth() { 615 return gridWidth; 616 } 617 618 /** 619 * @return The number of cells that this panel spans, vertically. 620 */ 621 @Override 622 public int gridHeight() { 623 return gridHeight; 624 } 625 626 /** 627 * The same as {@link #gridWidth()}; here for compatibility with SquidPanel. 628 * @return The number of cells that this panel spans, horizontally. 629 */ 630 public int getGridWidth() { 631 return gridWidth; 632 } 633 634 /** 635 * The same as {@link #gridHeight()}; here for compatibility with SquidPanel. 636 * @return The number of cells that this panel spans, vertically. 637 */ 638 public int getGridHeight() { 639 return gridHeight; 640 } 641 642 /** 643 * @return The width of a cell, in number of pixels. 644 */ 645 @Override 646 public int cellWidth() { 647 return Math.round(font.actualCellWidth); 648 } 649 650 /** 651 * @return The height of a cell, in number of pixels. 652 */ 653 @Override 654 public int cellHeight() { 655 return Math.round(font.actualCellHeight); 656 } 657 658 /** 659 * Changes the width and height of chars the font renders, inside the same cell size. 660 * Primarily useful if the font has issues and doesn't line up visually; some older versions of .fnt files 661 * distributed with SquidLib had this type of problem (and some may still). 662 * Provides compatibility with SquidPanel code. 663 * @param wide width in approximate pixels 664 * @param high height in approximate pixels 665 * @return this for chaining. 666 */ 667 public SparseLayers setTextSize(float wide, float high) 668 { 669 font.tweakWidth(wide).tweakHeight(high).initBySize(); 670 return this; 671 } 672 /** 673 * Sets the default foreground color. 674 * This Color can be null, which will 675 * usually not be rendered unless a different color is specified. 676 * @param color a libGDX Color object or an extension of Color, such as SColor 677 */ 678 @Override 679 public void setDefaultForeground(Color color) { 680 defaultForeground = color; 681 if(color != null) 682 defaultPackedForeground = color.toFloatBits(); 683 else 684 defaultPackedForeground = 0f; 685 } 686 /** 687 * @return The default foreground color (if none was set with 688 * {@link #setDefaultForeground(Color)}), or the last color set with 689 * {@link #setDefaultForeground(Color)}. This Color can be null which 690 * will usually not be rendered unless a different color is specified. 691 */ 692 @Override 693 public Color getDefaultForegroundColor() { 694 return defaultForeground; 695 } 696 697 /** 698 * Sets the default background color. 699 * This Color can be null, which will 700 * usually not be rendered unless a different color is specified. 701 * @param color a libGDX Color object or an extension of Color, such as SColor 702 */ 703 public void setDefaultBackground(Color color) { 704 defaultBackground = color; 705 if(color != null) 706 defaultPackedBackground = color.toFloatBits(); 707 else 708 defaultPackedBackground = 0f; 709 } 710 /** 711 * @return The default background color (if none was set with 712 * {@link #setDefaultBackground(Color)}), or the last color set 713 * with {@link #setDefaultBackground(Color)}. This can be null, 714 * which will usually not be rendered unless a different color 715 * is specified. 716 */ 717 public Color getDefaultBackgroundColor() { 718 return defaultBackground; 719 } 720 721 /** 722 * Method to change the backing {@link IColorCenter}. 723 * Note that the IColorCenter is not used to filter floats that encode colors, so passing the result of 724 * {@link Color#toFloatBits()} can be used to bypass the filtering if you want a color to be used exactly. 725 * @param icc an IColorCenter that can cache and possibly filter {@link Color} objects 726 */ 727 protected void setColorCenter(IColorCenter<Color> icc) { 728 scc = icc; 729 } 730 731 /** 732 * Gets the IColorCenter this can use to filter Color objects; this is usually a {@link SquidColorCenter}. 733 * Note that the IColorCenter is not used to filter floats that encode colors, so passing the result of 734 * {@link Color#toFloatBits()} can be used to bypass the filtering if you want a color to be used exactly. 735 * @return the IColorCenter this uses 736 */ 737 public IColorCenter<Color> getColorCenter() { 738 return scc; 739 } 740 741 /** 742 * Puts the char c at the position x,y with the given foreground and background colors. If foreground is null or is 743 * fully transparent (all channels 0), then this does not change the foreground contents. 744 * If background is null or is fully transparent, this does not change the background. Uses the color center to 745 * potentially filter the given colors; this can be changed with {@link #setColorCenter(IColorCenter)}. 746 * @param x the x position to place the char at 747 * @param y the y position to place the char at 748 * @param c the char to place 749 * @param foreground the color to use for c; if null or fully transparent, nothing will change in the foreground 750 * @param background the color to use for the cell; if null or fully transparent, nothing will change in the background 751 */ 752 public void put(int x, int y, char c, Color foreground, Color background) 753 { 754 put(x, y, c, foreground == null ? 0f : scc.filter(foreground).toFloatBits(), 755 background == null ? 0f : scc.filter(background).toFloatBits()); 756 } 757 /** 758 * Puts the ICellVisible cell at the position x,y with the given background color as an encoded float, such as 759 * those produced by {@link Color#toFloatBits()}. If {@code cell.getPackedColor()} is 0f, then this does not 760 * change the foreground contents. If background is 0f, this does not change the background. Does not filter the 761 * given colors. 762 * @param x the x position to place the char at 763 * @param y the y position to place the char at 764 * @param cell an ICellVisible, which is often implemented outside of SquidLib; this uses its char and color. 765 * @param background the color to use for the cell; if null or fully transparent, nothing will change in the background 766 */ 767 public void put(int x, int y, ICellVisible cell, float background) 768 { 769 put(x, y, cell.getSymbol(), cell.getPackedColor(), background); 770 } 771 /** 772 * Puts the char c at the position x,y with the given foreground and background colors as encoded floats, such as 773 * those produced by {@link Color#toFloatBits()}. If foreground is 0f, then this does not 774 * change the foreground contents. If background is 0f, this does not change the background. Does not filter the 775 * given colors. 776 * @param x the x position to place the char at 777 * @param y the y position to place the char at 778 * @param c the char to place 779 * @param foreground the color to use for c; if 0f, nothing will change in the foreground 780 * @param background the color to use for the cell; if null or fully transparent, nothing will change in the background 781 */ 782 public void put(int x, int y, char c, float foreground, float background) 783 { 784 if(x < 0 || x >= gridWidth || y < 0 || y >= gridHeight) 785 return; 786 if(background != 0f) 787 backgrounds[x][y] = background; 788 if(foreground != 0f) 789 layers.get(0).place(x, y, c, foreground); 790 } 791 792 793 /** 794 * Puts the char c at the position x,y with the given foreground color as an encoded float (the kind produced by 795 * {@link Color#toFloatBits()}). If foreground is 0f, then this does nothing. Does not filter the given color. 796 * @param x the x position to place the char at 797 * @param y the y position to place the char at 798 * @param c the char to place 799 * @param foreground the color to use for c; if 0f, this call does nothing 800 */ 801 public void put(int x, int y, char c, float foreground) 802 { 803 if(x < 0 || x >= gridWidth || y < 0 || y >= gridHeight || foreground == 0f) 804 return; 805 layers.get(0).place(x, y, c, foreground); 806 } 807 /** 808 * Puts the char c at the position x,y in the requested layer with the given foreground and background colors. If 809 * foreground is null or is fully transparent (all channels 0), then this does not change 810 * the foreground contents. If background is null or is fully transparent, this does not change the background. Uses 811 * the color center to potentially filter the given colors; this can be changed with 812 * {@link #setColorCenter(IColorCenter)}. The layer can be greater than the number of layers currently present, 813 * which will add a layer to be rendered over the existing layers, but the number that refers to that layer will not 814 * change. It is recommended that to add a layer, you only add at the value equal to {@link #getLayerCount()}, which 815 * will maintain the order and layer numbers in a sane way. 816 * @param x the x position to place the char at 817 * @param y the y position to place the char at 818 * @param c the char to place 819 * @param foreground the color to use for c; if null or fully transparent, nothing will change in the foreground 820 * @param background the color to use for the cell; if null or fully transparent, nothing will change in the background 821 * @param layer the layer to place the colorful char into; should usually be between 0 and {@link #getLayerCount()} inclusive 822 */ 823 public void put(int x, int y, char c, Color foreground, Color background, int layer) 824 { 825 put(x, y, c, foreground == null ? 0f : scc.filter(foreground).toFloatBits(), 826 background == null ? 0f : scc.filter(background).toFloatBits(), layer); 827 828 } 829 /** 830 * Puts the char c at the position x,y in the requested layer with the given foreground and background colors as 831 * encoded floats, such as those produced by {@link Color#toFloatBits()}. If foreground is 0f, then this does not 832 * change the foreground contents. If background is 0f, this does not change the 833 * background. Does not filter the given colors. The layer can be greater than the number of layers currently 834 * present, which will add a layer to be rendered over the existing layers, but the number that refers to that layer 835 * will not change. It is recommended that to add a layer, you only add at the value equal to 836 * {@link #getLayerCount()}, which will maintain the order and layer numbers in a sane way. 837 * @param x the x position to place the char at 838 * @param y the y position to place the char at 839 * @param c the char to place 840 * @param foreground the color to use for c; if 0f, nothing will change in the foreground 841 * @param background the color to use for the cell; if null or fully transparent, nothing will change in the background 842 * @param layer the layer to place the colorful char into; should usually be between 0 and {@link #getLayerCount()} inclusive 843 */ 844 public void put(int x, int y, char c, float foreground, float background, int layer) 845 { 846 if(x < 0 || x >= gridWidth || y < 0 || y >= gridHeight || layer < 0) 847 return; 848 if(background != 0f) 849 backgrounds[x][y] = background; 850 layer = mapping.get(layer, layer); 851 if(foreground != 0f) 852 { 853 if(layer >= layers.size()) 854 { 855 mapping.put(layer, layers.size()); 856 SparseTextMap stm = new SparseTextMap(gridWidth * gridHeight >> 4); 857 stm.place(x, y, c, foreground); 858 layers.add(stm); 859 } 860 else 861 { 862 layers.get(layer).place(x, y, c, foreground); 863 } 864 } 865 } 866 /** 867 * Puts text at the position x,y with the given foreground and background colors. If foreground is null or is 868 * fully transparent (all channels 0), then this does not change the foreground contents. 869 * If background is null or is fully transparent, this does not change the background. Uses the color center to 870 * potentially filter the given colors; this can be changed with {@link #setColorCenter(IColorCenter)}. 871 * @param x the x position to place the String at 872 * @param y the y position to place the String at 873 * @param text the String to place 874 * @param foreground the color to use for text; if null or fully transparent, nothing will change in the foreground 875 * @param background the color to use for the cells; if null or fully transparent, nothing will change in the background 876 */ 877 public void put(int x, int y, String text, Color foreground, Color background) { 878 put(x, y, text, foreground == null ? 0f : scc.filter(foreground).toFloatBits(), 879 background == null ? 0f : scc.filter(background).toFloatBits()); 880 } 881 /** 882 * Puts text at the position x,y with the given foreground and background colors as encoded floats, such as 883 * those produced by {@link Color#toFloatBits()}. If foreground is 0f, then this does not change the foreground 884 * contents. If background is 0f, this does not change the background. Does not filter the given colors. 885 * @param x the x position to place the String at 886 * @param y the y position to place the String at 887 * @param text the String to place 888 * @param foreground the color to use for text; if 0f, nothing will change in the foreground 889 * @param background the color to use for the cells; if null or fully transparent, nothing will change in the background 890 */ 891 public void put(int x, int y, String text, float foreground, float background) 892 { 893 if(text != null) { 894 int len = Math.min(text.length(), gridWidth - x); 895 for (int i = 0; i < len; i++) { 896 put(x + i, y, text.charAt(i), foreground, background); 897 } 898 } 899 } 900 /** 901 * Puts text at the position x,y in the requested layer with the given foreground and background colors. If 902 * foreground is null or is fully transparent (all channels 0), then this does not change the foreground contents. 903 * If background is null or is fully transparent, this does not change the background. Uses the color center to 904 * potentially filter the given colors; this can be changed with {@link #setColorCenter(IColorCenter)}. The layer 905 * can be greater than the number of layers currently present, which will add a layer to be rendered over the 906 * existing layers, but the number that refers to that layer will not change. It is recommended that to add a layer, 907 * you only add at the value equal to {@link #getLayerCount()}, which will maintain the order and layer numbers in a 908 * sane way. 909 * @param x the x position to place the String at 910 * @param y the y position to place the String at 911 * @param text the String to place 912 * @param foreground the color to use for text; if null or fully transparent, nothing will change in the foreground 913 * @param background the color to use for the cells; if null or fully transparent, nothing will change in the background 914 * @param layer the layer to place the colorful char into; should usually be between 0 and {@link #getLayerCount()} inclusive 915 */ 916 public void put(int x, int y, String text, Color foreground, Color background, int layer) { 917 put(x, y, text, foreground == null ? 0f : scc.filter(foreground).toFloatBits(), 918 background == null ? 0f : scc.filter(background).toFloatBits(), layer); 919 } 920 /** 921 * Puts text at the position x,y in the requested layer with the given foreground and background colors as encoded 922 * floats, such as those produced by {@link Color#toFloatBits()}. If foreground is 0f, then this does not change the 923 * foreground contents. If background is 0f, this does not change the background. Does not filter the given colors. 924 * The layer can be greater than the number of layers currently present, which will add a layer to be rendered over 925 * the existing layers, but the number that refers to that layer will not change. It is recommended that to add a 926 * layer, you only add at the value equal to {@link #getLayerCount()}, which will maintain the order and layer 927 * numbers in a sane way. 928 * @param x the x position to place the String at 929 * @param y the y position to place the String at 930 * @param text the String to place 931 * @param foreground the color to use for text; if 0f, nothing will change in the foreground 932 * @param background the color to use for the cells; if null or fully transparent, nothing will change in the background 933 * @param layer the layer to place the colorful char into; should usually be between 0 and {@link #getLayerCount()} inclusive 934 */ 935 public void put(int x, int y, String text, float foreground, float background, int layer) { 936 if (x < 0 || x >= gridWidth || y < 0 || y >= gridHeight || layer < 0) 937 return; 938 int len = Math.min(text.length(), gridWidth - x); 939 if(background != 0f) { 940 for (int i = 0; i < len; i++) { 941 backgrounds[x + i][y] = background; 942 } 943 } 944 if (foreground != 0f) 945 { 946 layer = mapping.get(layer, layer); 947 if (layer >= layers.size()) { 948 mapping.put(layer, layers.size()); 949 SparseTextMap stm = new SparseTextMap(gridWidth * gridHeight >> 4); 950 for (int i = 0; i < len; i++) { 951 stm.place(x + i, y, text.charAt(i), foreground); 952 } 953 layers.add(stm); 954 } else { 955 SparseTextMap stm = layers.get(layer); 956 for (int i = 0; i < len; i++) { 957 stm.place(x + i, y, text.charAt(i), foreground); 958 } 959 } 960 } 961 } 962 963 /** 964 * Changes the background at position x,y to the given Color. If the color is null, then this will make the 965 * background fully transparent at the specified position. 966 * @param x where to change the background color, x-coordinate 967 * @param y where to change the background color, y-coordinate 968 * @param color the Color to change to; if null will be considered fully transparent 969 */ 970 public void put(int x, int y, Color color) 971 { 972 put(x, y, color == null ? 0f : scc.filter(color).toFloatBits()); 973 } 974 /** 975 * Changes the background at position x,y to the given color as an encoded float. The color can be transparent, 976 * which will show through to whatever is behind this SparseLayers, or the color the screen was last cleared with. 977 * Unlike other methods in this class, a float equal to 0f will be used instead of being used to skip over a cell, 978 * and will change the background at the given position to fully transparent. 979 * @param x where to change the background color, x-coordinate 980 * @param y where to change the background color, y-coordinate 981 * @param color the color, as an encoded float, to change to; may be transparent, and considers 0f a valid color 982 */ 983 public void put(int x, int y, float color) 984 { 985 if(x < 0 || x >= gridWidth || y < 0 || y >= gridHeight) 986 return; 987 backgrounds[x][y] = color; 988 } 989 /** 990 * A convenience method that handles blending the background color with a specified light color, by a specific 991 * amount, without putting a char on the screen; as a whole this affects one x,y position. 992 * @param x the x position, in cells 993 * @param y the y position, in cells 994 * @param background the "base" color to use for the background, which will be combined with lightColor, as a libGDX Color 995 * @param lightColor the color to mix with the background, as a libGDX Color 996 * @param lightAmount a float that determines how much lightColor should affect background by; not strictly limited 997 * to between 0 and 1, and negative values can be given to favor background more 998 */ 999 public void putWithLight(int x, int y, Color background, Color lightColor, float lightAmount) { 1000 putWithLight(x, y, '\0', 0f, background == null ? 0f : background.toFloatBits(), 1001 lightColor == null ? 0f : lightColor.toFloatBits(), lightAmount); 1002 } 1003 /** 1004 * A convenience method that handles blending the background color with a specified light color, by a specific 1005 * amount, without putting a char on the screen; as a whole this affects one x,y position. 1006 * @param x the x position, in cells 1007 * @param y the y position, in cells 1008 * @param background the "base" color to use for the background, which will be combined with lightColor, as a libGDX Color 1009 * @param lightColor the color to mix with the background, as a libGDX Color 1010 * @param lightAmount a double that determines how much lightColor should affect background by; not strictly limited 1011 * to between 0 and 1, and negative values can be given to favor background more 1012 */ 1013 public void putWithLight(int x, int y, Color background, Color lightColor, double lightAmount) { 1014 putWithLight(x, y, '\0', 0f, background == null ? 0f : background.toFloatBits(), 1015 lightColor == null ? 0f : lightColor.toFloatBits(), lightAmount); 1016 } 1017 1018 /** 1019 * A convenience method that handles blending the background color with a specified light color, by a specific 1020 * amount, while also putting a char on the screen; as a whole this affects one x,y position. 1021 * @param x the x position, in cells 1022 * @param y the y position, in cells 1023 * @param c the char to put in the foreground 1024 * @param foreground the color to use for the foreground, as a libGDX Color; if null, this won't place a foreground char 1025 * @param background the "base" color to use for the background, which will be combined with lightColor, as a libGDX Color 1026 * @param lightColor the color to mix with the background, as a libGDX Color 1027 * @param lightAmount a float that determines how much lightColor should affect background by; not strictly limited 1028 * to between 0 and 1, and negative values can be given to favor background more 1029 */ 1030 public void putWithLight(int x, int y, char c, Color foreground, Color background, Color lightColor, float lightAmount) { 1031 putWithLight(x, y, c, foreground == null ? 0f : foreground.toFloatBits(), background == null ? 0f : background.toFloatBits(), 1032 lightColor == null ? 0f : lightColor.toFloatBits(), lightAmount); 1033 } 1034 1035 /** 1036 * A convenience method that handles blending the background color with a specified light color, by a specific 1037 * amount, while also putting a char on the screen; as a whole this affects one x,y position. 1038 * @param x the x position, in cells 1039 * @param y the y position, in cells 1040 * @param c the char to put in the foreground 1041 * @param foreground the color to use for the foreground, as a libGDX Color; if null, this won't place a foreground char 1042 * @param background the "base" color to use for the background, which will be combined with lightColor, as a libGDX Color 1043 * @param lightColor the color to mix with the background, as a libGDX Color 1044 * @param lightAmount a double that determines how much lightColor should affect background by; not strictly limited 1045 * to between 0 and 1, and negative values can be given to favor background more 1046 */ 1047 public void putWithLight(int x, int y, char c, Color foreground, Color background, Color lightColor, double lightAmount) { 1048 putWithLight(x, y, c, foreground == null ? 0f : foreground.toFloatBits(), background == null ? 0f : background.toFloatBits(), 1049 lightColor == null ? 0f : lightColor.toFloatBits(), lightAmount); 1050 } 1051 1052 /** 1053 * A convenience method that handles blending the background color with a specified light color, by a specific 1054 * amount, without putting a char on the screen; as a whole this affects one x,y position. This will use the same 1055 * brightness for all cells given identical lightAmount values when this is called; this differentiates it from 1056 * {@link #putWithLight(int, int, float, float, float, Noise.Noise3D)}, which would light "splotches" of map with 1057 * brighter or darker color. Instead, if lightAmount is obtained via SquidLib's {@code FOV} class, then all cells 1058 * at a short distance from an FOV center will be lit brightly and cells far away will flicker in and out of view. 1059 * @param x the x position, in cells 1060 * @param y the y position, in cells 1061 * @param background the "base" color to use for the background, which will be combined with lightColor, as a packed float color 1062 * @param lightColor the color to mix with the background, as a packed float color 1063 * @param lightAmount a float that determines how much lightColor should affect background by; not strictly limited 1064 * to between 0 and 1, and negative values can be given to favor background more 1065 */ 1066 public void putWithConsistentLight(int x, int y, float background, float lightColor, float lightAmount) { 1067 putWithConsistentLight(x, y, background, lightColor, lightAmount, 0.0015f); 1068 } 1069 /** 1070 * A convenience method that handles blending the background color with a specified light color, by a specific 1071 * amount, without putting a char on the screen; as a whole this affects one x,y position. This will use the same 1072 * brightness for all cells given identical lightAmount values when this is called; this differentiates it from 1073 * {@link #putWithLight(int, int, float, float, float, Noise.Noise3D)}, which would light "splotches" of map with 1074 * brighter or darker color. Instead, if lightAmount is obtained via SquidLib's {@code FOV} class, then all cells 1075 * at a short distance from an FOV center will be lit brightly and cells far away will flicker in and out of view. 1076 * @param x the x position, in cells 1077 * @param y the y position, in cells 1078 * @param background the "base" color to use for the background, which will be combined with lightColor, as a packed float color 1079 * @param lightColor the color to mix with the background, as a packed float color 1080 * @param lightAmount a float that determines how much lightColor should affect background by; not strictly limited 1081 * to between 0 and 1, and negative values can be given to favor background more 1082 * @param flickerSpeed a small float multiplier applied to the time in milliseconds; often between 0.0005f and 0.005f 1083 */ 1084 public void putWithConsistentLight(int x, int y, float background, float lightColor, float lightAmount, float flickerSpeed) { 1085 final float time = (System.currentTimeMillis() & 0xffffffL) * flickerSpeed; 1086 final long time0 = Noise.longFloor(time); 1087 final float noise = Noise.querp(NumberTools.randomFloatCurved(time0), NumberTools.randomFloatCurved(time0 + 1L), time - time0); 1088 lightAmount = Math.max(lightAmount * 0.15f, Math.min(lightAmount - NumberTools.swayTight(noise * 3.141592f) * 0.15f - 0.1f + 0.25f * noise, lightAmount)); 1089 putWithLight(x, y, '\0', 0f, background, lightColor, lightAmount); 1090 } 1091 1092 /** 1093 * Gets a modifier that should be given to {@link #calculateConsistentLightAmount(float, float)}; this only needs to 1094 * be called at most once per frame. It uses a flickerSpeed of 0.0015f, if you want to compare it to 1095 * {@link #calculateConsistentLightModifier(float)}. 1096 * @return a modifier to pass to {@link #calculateConsistentLightAmount(float, float)} 1097 */ 1098 public float calculateConsistentLightModifier() 1099 { 1100 return calculateConsistentLightModifier(0.0015f); 1101 } 1102 /** 1103 * Gets a modifier that should be given to {@link #calculateConsistentLightAmount(float, float)}; this only needs to 1104 * be called at most once per frame. It uses the given flickerSpeed; a common example value is 0.0015f. 1105 * @return a modifier to pass to {@link #calculateConsistentLightAmount(float, float)} 1106 */ 1107 public float calculateConsistentLightModifier(float flickerSpeed) 1108 { 1109 final float time = (System.currentTimeMillis() & 0xffffffL) * flickerSpeed; 1110 final long time0 = Noise.longFloor(time); 1111 return Noise.querp(NumberTools.randomFloatCurved(time0), NumberTools.randomFloatCurved(time0 + 1L), time - time0); 1112 } 1113 1114 /** 1115 * Allows you to reproduce the effects of {@link #putWithConsistentLight(int, int, char, float, float, float, double, float)} 1116 * without recalculating the modifier many times per frame. You should typically calculate the modifier once per 1117 * frame using {@link #calculateConsistentLightModifier()}. This method should usually be called once per cell that 1118 * needs consistent lighting that flickers the same in all directions. 1119 * @param lightAmount typically from FOV or some other way of generating a float with lower values further from a position (such as a light) 1120 * @param modifier calculated previously by {@link #calculateConsistentLightModifier()}, which should be done per-frame 1121 * @return the adjusted light amount to mimic the appearance of {@link #putWithConsistentLight(int, int, char, float, float, float, double, float)} 1122 */ 1123 public float calculateConsistentLightAmount(float lightAmount, float modifier) 1124 { 1125 return lightAmount <= 0f ? -1024f : Math.max( 1126 lightAmount * 0.15f, 1127 Math.min(lightAmount - NumberTools.swayTight(modifier * 3.141592f) * 0.15f - 0.1f + 0.25f * modifier, lightAmount)); 1128 } 1129 1130 /** 1131 * A convenience method that handles blending the background color with a specified light color, by a specific 1132 * amount, without putting a char on the screen; as a whole this affects one x,y position. This will use the same 1133 * brightness for all cells given identical lightAmount values when this is called; this differentiates it from 1134 * {@link #putWithLight(int, int, float, float, float, Noise.Noise3D)}, which would light "splotches" of map with 1135 * brighter or darker color. Instead, if lightAmount is obtained via SquidLib's {@code FOV} class, then all cells 1136 * at a short distance from an FOV center will be lit brightly and cells far away will flicker in and out of view. 1137 * @param x the x position, in cells 1138 * @param y the y position, in cells 1139 * @param background the "base" color to use for the background, which will be combined with lightColor, as a libGDX Color 1140 * @param lightColor the color to mix with the background, as a libGDX Color 1141 * @param lightAmount a float that determines how much lightColor should affect background by; not strictly limited 1142 * to between 0 and 1, and negative values can be given to favor background more 1143 */ 1144 public void putWithConsistentLight(int x, int y, Color background, Color lightColor, float lightAmount) { 1145 putWithConsistentLight(x, y, background, lightColor, lightAmount, 0.0015f); 1146 } 1147 /** 1148 * A convenience method that handles blending the background color with a specified light color, by a specific 1149 * amount, without putting a char on the screen; as a whole this affects one x,y position. This will use the same 1150 * brightness for all cells given identical lightAmount values when this is called; this differentiates it from 1151 * {@link #putWithLight(int, int, float, float, float, Noise.Noise3D)}, which would light "splotches" of map with 1152 * brighter or darker color. Instead, if lightAmount is obtained via SquidLib's {@code FOV} class, then all cells 1153 * at a short distance from an FOV center will be lit brightly and cells far away will flicker in and out of view. 1154 * @param x the x position, in cells 1155 * @param y the y position, in cells 1156 * @param background the "base" color to use for the background, which will be combined with lightColor, as a libGDX Color 1157 * @param lightColor the color to mix with the background, as a libGDX Color 1158 * @param lightAmount a float that determines how much lightColor should affect background by; not strictly limited 1159 * to between 0 and 1, and negative values can be given to favor background more 1160 * @param flickerSpeed a small float multiplier applied to the time in milliseconds; often between 0.0005f and 0.005f 1161 */ 1162 public void putWithConsistentLight(int x, int y, Color background, Color lightColor, float lightAmount, float flickerSpeed) { 1163 final float time = (System.currentTimeMillis() & 0xffffffL) * flickerSpeed; // if you want to adjust the speed of flicker, change the multiplier 1164 final long time0 = Noise.longFloor(time); 1165 final float noise = Noise.querp(NumberTools.randomFloatCurved(time0), NumberTools.randomFloatCurved(time0 + 1L), time - time0); 1166 lightAmount = Math.max(lightAmount * 0.15f, Math.min(lightAmount - NumberTools.swayTight(noise * 3.141592f) * 0.15f - 0.1f + 0.25f * noise, lightAmount)); // 0.1f * noise for light theme, 0.2f * noise for dark theme 1167 putWithLight(x, y, '\0', 0f, background == null ? 0f : background.toFloatBits(), 1168 lightColor == null ? 0f : lightColor.toFloatBits(), lightAmount); 1169 } 1170 /** 1171 * A convenience method that handles blending the background color with a specified light color, by a specific 1172 * amount, without putting a char on the screen; as a whole this affects one x,y position. This will use the same 1173 * brightness for all cells given identical lightAmount values when this is called; this differentiates it from 1174 * {@link #putWithLight(int, int, float, float, float, Noise.Noise3D)}, which would light "splotches" of map with 1175 * brighter or darker color. Instead, if lightAmount is obtained via SquidLib's {@code FOV} class, then all cells 1176 * at a short distance from an FOV center will be lit brightly and cells far away will flicker in and out of view. 1177 * @param x the x position, in cells 1178 * @param y the y position, in cells 1179 * @param c the char to draw; may be {@code '\0'} to draw a solid block 1180 * @param background the "base" color to use for the background, which will be combined with lightColor, as a packed float color 1181 * @param lightColor the color to mix with the background, as a packed float color 1182 * @param lightAmount a float that determines how much lightColor should affect background by; not strictly limited 1183 * to between 0 and 1, and negative values can be given to favor background more 1184 */ 1185 public void putWithConsistentLight(int x, int y, char c, float foreground, float background, float lightColor, float lightAmount) { 1186 putWithConsistentLight(x, y, c, foreground, background, lightColor, lightAmount, 0.0015f); 1187 } 1188 /** 1189 * A convenience method that handles blending the background color with a specified light color, by a specific 1190 * amount, without putting a char on the screen; as a whole this affects one x,y position. This will use the same 1191 * brightness for all cells given identical lightAmount values when this is called; this differentiates it from 1192 * {@link #putWithLight(int, int, float, float, float, Noise.Noise3D)}, which would light "splotches" of map with 1193 * brighter or darker color. Instead, if lightAmount is obtained via SquidLib's {@code FOV} class, then all cells 1194 * at a short distance from an FOV center will be lit brightly and cells far away will flicker in and out of view. 1195 * @param x the x position, in cells 1196 * @param y the y position, in cells 1197 * @param c the char to draw; may be {@code '\0'} to draw a solid block 1198 * @param background the "base" color to use for the background, which will be combined with lightColor, as a packed float color 1199 * @param lightColor the color to mix with the background, as a packed float color 1200 * @param lightAmount a double that determines how much lightColor should affect background by; not strictly limited 1201 * to between 0 and 1, and negative values can be given to favor background more 1202 */ 1203 public void putWithConsistentLight(int x, int y, char c, float foreground, float background, float lightColor, double lightAmount) { 1204 putWithConsistentLight(x, y, c, foreground, background, lightColor, lightAmount, 0.0015f); 1205 } 1206 /** 1207 * A convenience method that handles blending the background color with a specified light color, by a specific 1208 * amount, without putting a char on the screen; as a whole this affects one x,y position. This will use the same 1209 * brightness for all cells given identical lightAmount values when this is called; this differentiates it from 1210 * {@link #putWithLight(int, int, float, float, float, Noise.Noise3D)}, which would light "splotches" of map with 1211 * brighter or darker color. Instead, if lightAmount is obtained via SquidLib's {@code FOV} class, then all cells 1212 * at a short distance from an FOV center will be lit brightly and cells far away will flicker in and out of view. 1213 * @param x the x position, in cells 1214 * @param y the y position, in cells 1215 * @param c the char to draw; may be {@code '\0'} to draw a solid block 1216 * @param foreground the color to use for the foreground, as a packed float; if 0f, this won't place a foreground char 1217 * @param background the "base" color to use for the background, which will be combined with lightColor, as a packed float color 1218 * @param lightColor the color to mix with the background, as a packed float color 1219 * @param lightAmount a float that determines how much lightColor should affect background by; not strictly limited 1220 * to between 0 and 1, and negative values can be given to favor background more 1221 * @param flickerSpeed a small float multiplier applied to the time in milliseconds; often between 0.0005f and 0.005f 1222 */ 1223 public void putWithConsistentLight(int x, int y, char c, float foreground, float background, float lightColor, double lightAmount, float flickerSpeed) { 1224 final float time = (System.currentTimeMillis() & 0xffffffL) * flickerSpeed; // if you want to adjust the speed of flicker, change the multiplier 1225 final long time0 = Noise.longFloor(time); 1226 final float noise = Noise.querp(NumberTools.randomFloatCurved(time0), NumberTools.randomFloatCurved(time0 + 1L), time - time0); 1227 lightAmount = Math.max(lightAmount * 0.15f, Math.min(lightAmount - NumberTools.swayTight(noise * 3.141592f) * 0.15f - 0.1f + 0.25f * noise, lightAmount)); // 0.1f * noise for light theme, 0.2f * noise for dark theme 1228 putWithLight(x, y, c, foreground, background, lightColor, lightAmount); 1229 } 1230 /** 1231 * A convenience method that handles blending the foreground color with a specified light color, by a specific 1232 * amount, without putting a char on the screen; as a whole this affects one x,y position and placed the background 1233 * as-is. This will use the same brightness for all cells given identical lightAmount values when this is called; 1234 * this differentiates it from {@link #putWithLight(int, int, float, float, float, Noise.Noise3D)}, which would 1235 * light "splotches" of map with brighter or darker color. Instead, if lightAmount is obtained via SquidLib's 1236 * {@code FOV} class, then all cells at a short distance from an FOV center will be lit brightly and cells far away 1237 * will flicker in and out of view. 1238 * @param x the x position, in cells 1239 * @param y the y position, in cells 1240 * @param c the char to draw; may be {@code '\0'} to draw a solid block 1241 * @param foreground the "base" color to use for the foreground, which will be combined with lightColor, as a packed float color 1242 * @param background the "base" color to use for the background, which will used without modification, as a packed float color 1243 * @param lightColor the color to mix with the foreground, as a packed float color 1244 * @param lightAmount a float that determines how much lightColor should affect foreground by; not strictly limited 1245 * to between 0 and 1, and negative values can be given to favor foreground more 1246 */ 1247 public void putWithReverseConsistentLight(int x, int y, char c, float foreground, float background, float lightColor, float lightAmount) { 1248 putWithReverseConsistentLight(x, y, c, foreground, background, lightColor, lightAmount, 0.0015f); 1249 } 1250 /** 1251 * A convenience method that handles blending the foreground color with a specified light color, by a specific 1252 * amount, without putting a char on the screen; as a whole this affects one x,y position and placed the background 1253 * as-is. This will use the same brightness for all cells given identical lightAmount values when this is called; 1254 * this differentiates it from {@link #putWithLight(int, int, float, float, float, Noise.Noise3D)}, which would 1255 * light "splotches" of map with brighter or darker color. Instead, if lightAmount is obtained via SquidLib's 1256 * {@code FOV} class, then all cells at a short distance from an FOV center will be lit brightly and cells far away 1257 * will flicker in and out of view. 1258 * @param x the x position, in cells 1259 * @param y the y position, in cells 1260 * @param c the char to draw; may be {@code '\0'} to draw a solid block 1261 * @param foreground the "base" color to use for the foreground, which will be combined with lightColor, as a packed float color 1262 * @param background the "base" color to use for the background, which will used without modification, as a packed float color 1263 * @param lightColor the color to mix with the foreground, as a packed float color 1264 * @param lightAmount a float that determines how much lightColor should affect foreground by; not strictly limited 1265 * to between 0 and 1, and negative values can be given to favor foreground more 1266 * @param flickerSpeed a small float multiplier applied to the time in milliseconds; often between 0.0005f and 0.005f 1267 */ 1268 public void putWithReverseConsistentLight(int x, int y, char c, float foreground, float background, float lightColor, float lightAmount, float flickerSpeed) { 1269 final float time = (System.currentTimeMillis() & 0xffffffL) * flickerSpeed; // if you want to adjust the speed of flicker, change the multiplier 1270 final long time0 = Noise.longFloor(time); 1271 final float noise = Noise.querp(NumberTools.randomFloatCurved(time0), NumberTools.randomFloatCurved(time0 + 1L), time - time0); 1272 lightAmount = Math.max(lightAmount * 0.15f, Math.min(lightAmount - NumberTools.swayTight(noise * 3.141592f) * 0.15f - 0.1f + 0.25f * noise, lightAmount)); // 0.1f * noise for light theme, 0.2f * noise for dark theme 1273 putWithReverseLight(x, y, c, foreground, background, lightColor, lightAmount); 1274 } 1275 1276 /** 1277 * A convenience method that handles blending the background color with a specified light color, by a specific 1278 * amount, without putting a char on the screen; as a whole this affects one x,y position. This will use the same 1279 * brightness for all cells given identical lightAmount values when this is called; this differentiates it from 1280 * {@link #putWithLight(int, int, float, float, float, Noise.Noise3D)}, which would light "splotches" of map with 1281 * brighter or darker color. Instead, if lightAmount is obtained via SquidLib's {@code FOV} class, then all cells 1282 * at a short distance from an FOV center will be lit brightly and cells far away will flicker in and out of view. 1283 * @param x the x position, in cells 1284 * @param y the y position, in cells 1285 * @param c the char to draw; may be {@code '\0'} to draw a solid block 1286 * @param foreground the "base" color to use for the foreground, which will be combined with lightColor, as a libGDX Color 1287 * @param background the "base" color to use for the background, which will used without modification 1288 * @param lightColor the color to mix with the background, as a libGDX Color 1289 * @param lightAmount a float that determines how much lightColor should affect background by; not strictly limited 1290 * to between 0 and 1, and negative values can be given to favor background more 1291 */ 1292 public void putWithConsistentLight(int x, int y, char c, Color foreground, Color background, Color lightColor, float lightAmount) { 1293 putWithConsistentLight(x, y, c, foreground, background, lightColor, lightAmount, 0.0015f); 1294 } 1295 /** 1296 * A convenience method that handles blending the background color with a specified light color, by a specific 1297 * amount, without putting a char on the screen; as a whole this affects one x,y position. This will use the same 1298 * brightness for all cells given identical lightAmount values when this is called; this differentiates it from 1299 * {@link #putWithLight(int, int, float, float, float, Noise.Noise3D)}, which would light "splotches" of map with 1300 * brighter or darker color. Instead, if lightAmount is obtained via SquidLib's {@code FOV} class, then all cells 1301 * at a short distance from an FOV center will be lit brightly and cells far away will flicker in and out of view. 1302 * @param x the x position, in cells 1303 * @param y the y position, in cells 1304 * @param c the char to draw; may be {@code '\0'} to draw a solid block 1305 * @param foreground the "base" color to use for the foreground, which will used without modification 1306 * @param background the "base" color to use for the background, which will be combined with lightColor, as a libGDX Color 1307 * @param lightColor the color to mix with the background, as a libGDX Color 1308 * @param lightAmount a float that determines how much lightColor should affect background by; not strictly limited 1309 * to between 0 and 1, and negative values can be given to favor background more 1310 * @param flickerSpeed a small float multiplier applied to the time in milliseconds; often between 0.0005f and 0.005f 1311 */ 1312 public void putWithConsistentLight(int x, int y, char c, Color foreground, Color background, Color lightColor, float lightAmount, float flickerSpeed) { 1313 final float time = (System.currentTimeMillis() & 0xffffffL) * flickerSpeed; // if you want to adjust the speed of flicker, change the multiplier 1314 final long time0 = Noise.longFloor(time); 1315 final float noise = Noise.querp(NumberTools.randomFloatCurved(time0), NumberTools.randomFloatCurved(time0 + 1L), time - time0); 1316 lightAmount = Math.max(lightAmount * 0.15f, Math.min(lightAmount - NumberTools.swayTight(noise * 3.141592f) * 0.15f - 0.1f + 0.25f * noise, lightAmount)); // 0.1f * noise for light theme, 0.2f * noise for dark theme 1317 putWithLight(x, y, c, foreground == null ? 0f : foreground.toFloatBits(), background == null ? 0f : background.toFloatBits(), 1318 lightColor == null ? 0f : lightColor.toFloatBits(), lightAmount); 1319 } 1320 1321 /** 1322 * A convenience method that handles blending the background color with a specified light color, by a specific 1323 * amount, without putting a char on the screen; as a whole this affects one x,y position. This will use the same 1324 * brightness for all cells given identical lightAmount values when this is called; this differentiates it from 1325 * {@link #putWithLight(int, int, float, float, float, Noise.Noise3D)}, which would light "splotches" of map with 1326 * brighter or darker color. Instead, if lightAmount is obtained via SquidLib's {@code FOV} class, then all cells 1327 * at a short distance from an FOV center will be lit brightly and cells far away will flicker in and out of view. 1328 * <br> 1329 * Identical to calling {@link #putWithReverseConsistentLight(int, int, char, Color, Color, Color, float, float)} 1330 * with 0.0015f as the last parameter. 1331 * @param x the x position, in cells 1332 * @param y the y position, in cells 1333 * @param c the char to draw; may be {@code '\0'} to draw a solid block 1334 * @param foreground the "base" color to use for the foreground, which will be combined with lightColor, as a libGDX Color 1335 * @param background the "base" color to use for the background, which will used without modification 1336 * @param lightColor the color to mix with the foreground, as a libGDX Color 1337 * @param lightAmount a float that determines how much lightColor should affect foreground by; not strictly limited 1338 * to between 0 and 1, and negative values can be given to favor foreground more 1339 */ 1340 public void putWithReverseConsistentLight(int x, int y, char c, Color foreground, Color background, Color lightColor, float lightAmount) { 1341 putWithReverseConsistentLight(x, y, c, foreground, background, lightColor, lightAmount, 0.0015f); 1342 } 1343 1344 /** 1345 * A convenience method that handles blending the background color with a specified light color, by a specific 1346 * amount, without putting a char on the screen; as a whole this affects one x,y position. This will use the same 1347 * brightness for all cells given identical lightAmount values when this is called; this differentiates it from 1348 * {@link #putWithLight(int, int, float, float, float, Noise.Noise3D)}, which would light "splotches" of map with 1349 * brighter or darker color. Instead, if lightAmount is obtained via SquidLib's {@code FOV} class, then all cells 1350 * at a short distance from an FOV center will be lit brightly and cells far away will flicker in and out of view. 1351 * @param x the x position, in cells 1352 * @param y the y position, in cells 1353 * @param c the char to draw; may be {@code '\0'} to draw a solid block 1354 * @param foreground the "base" color to use for the foreground, which will be combined with lightColor, as a libGDX Color 1355 * @param background the "base" color to use for the background, which will used without modification 1356 * @param lightColor the color to mix with the foreground, as a libGDX Color 1357 * @param lightAmount a float that determines how much lightColor should affect foreground by; not strictly limited 1358 * to between 0 and 1, and negative values can be given to favor foreground more 1359 * @param flickerSpeed a small float multiplier applied to the time in milliseconds; often between 0.0005f and 0.005f 1360 */ 1361 public void putWithReverseConsistentLight(int x, int y, char c, Color foreground, Color background, Color lightColor, float lightAmount, float flickerSpeed) { 1362 final float time = (System.currentTimeMillis() & 0xffffffL) * flickerSpeed; // if you want to adjust the speed of flicker, change the multiplier 1363 final long time0 = Noise.longFloor(time); 1364 final float noise = Noise.querp(NumberTools.randomFloatCurved(time0), NumberTools.randomFloatCurved(time0 + 1L), time - time0); 1365 lightAmount = Math.max(lightAmount * 0.15f, Math.min(lightAmount - NumberTools.swayTight(noise * 3.141592f) * 0.15f - 0.1f + 0.25f * noise, lightAmount)); // 0.1f * noise for light theme, 0.2f * noise for dark theme 1366 putWithReverseLight(x, y, c, foreground == null ? 0f : foreground.toFloatBits(), background == null ? 0f : background.toFloatBits(), 1367 lightColor == null ? 0f : lightColor.toFloatBits(), lightAmount); 1368 } 1369 1370 /** 1371 * A convenience method that handles blending the background color with a specified light color, by a specific 1372 * amount, without putting a char on the screen; as a whole this affects one x,y position. This will use the same 1373 * brightness for all cells given identical lightAmount values when this is called; this differentiates it from 1374 * {@link #putWithLight(int, int, float, float, float, Noise.Noise3D)}, which would light "splotches" of map with 1375 * brighter or darker color. Instead, if lightAmount is obtained via SquidLib's {@code FOV} class, then all cells 1376 * at a short distance from an FOV center will be lit brightly and cells far away will flicker in and out of view. 1377 * @param x the x position, in cells 1378 * @param y the y position, in cells 1379 * @param c the char to draw; may be {@code '\0'} to draw a solid block 1380 * @param foreground the "base" color to use for the foreground, which will be combined with lightColor, as a libGDX Color 1381 * @param background the "base" color to use for the background, which will used without modification 1382 * @param lightColor the color to mix with the foreground, as a libGDX Color 1383 * @param lightAmount a double that determines how much lightColor should affect foreground by; not strictly limited 1384 * to between 0 and 1, and negative values can be given to favor foreground more 1385 * @param flickerSpeed a small float multiplier applied to the time in milliseconds; often between 0.0005f and 0.005f 1386 */ 1387 public void putWithReverseConsistentLight(int x, int y, char c, Color foreground, Color background, Color lightColor, double lightAmount, float flickerSpeed) { 1388 final float time = (System.currentTimeMillis() & 0xffffffL) * flickerSpeed; // if you want to adjust the speed of flicker, change the multiplier 1389 final long time0 = Noise.longFloor(time); 1390 final float noise = Noise.querp(NumberTools.randomFloatCurved(time0), NumberTools.randomFloatCurved(time0 + 1L), time - time0); 1391 lightAmount = Math.max(lightAmount * 0.15f, Math.min(lightAmount - NumberTools.swayTight(noise * 3.141592f) * 0.15f - 0.1f + 0.25f * noise, lightAmount)); // 0.1f * noise for light theme, 0.2f * noise for dark theme 1392 putWithReverseLight(x, y, c, foreground == null ? 0f : foreground.toFloatBits(), background == null ? 0f : background.toFloatBits(), 1393 lightColor == null ? 0f : lightColor.toFloatBits(), lightAmount); 1394 } 1395 1396 /** 1397 * A convenience method that handles blending the background color with a specified light color, by a specific 1398 * amount, without putting a char on the screen; as a whole this affects one x,y position. 1399 * @param x the x position, in cells 1400 * @param y the y position, in cells 1401 * @param background the "base" color to use for the background, which will be combined with lightColor, as a packed float 1402 * @param lightColor the color to mix with the background, as a packed float 1403 * @param lightAmount a float that determines how much lightColor should affect background by; not strictly limited 1404 * to between 0 and 1, and negative values can be given to favor background more 1405 */ 1406 public void putWithLight(int x, int y, float background, float lightColor, float lightAmount) { 1407 put(x, y, '\0', 0f, 1408 SColor.lerpFloatColors(background, lightColor, 1409 MathUtils.clamp(0xAAp-9f + (0xC8p-9f * lightAmount), 0f, 1f))); 1410 } 1411 /** 1412 * A convenience method that handles blending the background color with a specified light color, by a specific 1413 * amount, without putting a char on the screen; as a whole this affects one x,y position. 1414 * @param x the x position, in cells 1415 * @param y the y position, in cells 1416 * @param background the "base" color to use for the background, which will be combined with lightColor, as a packed float 1417 * @param lightColor the color to mix with the background, as a packed float 1418 * @param lightAmount a double that determines how much lightColor should affect background by; not strictly limited 1419 * to between 0 and 1, and negative values can be given to favor background more 1420 */ 1421 public void putWithLight(int x, int y, float background, float lightColor, double lightAmount) { 1422 put(x, y, '\0', 0f, 1423 SColor.lerpFloatColors(background, lightColor, 1424 MathUtils.clamp(0xAAp-9f + (0xC8p-9f * (float) lightAmount), 0f, 1f))); 1425 } 1426 1427 /** 1428 * A convenience method that handles blending the background color with a specified light color, by a specific 1429 * amount, while also putting a char on the screen; as a whole this affects one x,y position. 1430 * @param x the x position, in cells 1431 * @param y the y position, in cells 1432 * @param c the char to put in the foreground 1433 * @param foreground the color to use for the foreground, as a packed float; if 0f, this won't place a foreground char 1434 * @param background the "base" color to use for the background, which will be combined with lightColor, as a packed float 1435 * @param lightColor the color to mix with the background, as a packed float 1436 * @param lightAmount a float that determines how much lightColor should affect background by; not strictly limited 1437 * to between 0 and 1, and negative values can be given to favor background more 1438 */ 1439 public void putWithLight(int x, int y, char c, float foreground, float background, float lightColor, float lightAmount) { 1440 put(x, y, c, foreground, 1441 SColor.lerpFloatColors(background, lightColor, 1442 MathUtils.clamp(0xAAp-9f + (0xC8p-9f * lightAmount), 0f, 1f))); 1443 } 1444 1445 /** 1446 * A convenience method that handles blending the background color with a specified light color, by a specific 1447 * amount, while also putting a char on the screen; as a whole this affects one x,y position. 1448 * @param x the x position, in cells 1449 * @param y the y position, in cells 1450 * @param c the char to put in the foreground 1451 * @param foreground the color to use for the foreground, as a packed float; if 0f, this won't place a foreground char 1452 * @param background the "base" color to use for the background, which will be combined with lightColor, as a packed float 1453 * @param lightColor the color to mix with the background, as a packed float 1454 * @param lightAmount a double that determines how much lightColor should affect background by; not strictly limited 1455 * to between 0 and 1, and negative values can be given to favor background more 1456 */ 1457 public void putWithLight(int x, int y, char c, float foreground, float background, float lightColor, double lightAmount) { 1458 put(x, y, c, foreground, 1459 SColor.lerpFloatColors(background, lightColor, 1460 MathUtils.clamp(0xAAp-9f + (0xC8p-9f * (float) lightAmount), 0f, 1f))); 1461 } 1462 1463 /** 1464 * A convenience method that handles blending the background color with a specified light color, by a specific 1465 * amount, while also putting a char on the screen; as a whole this affects one x,y position. 1466 * @param x the x position, in cells 1467 * @param y the y position, in cells 1468 * @param c the char to put in the foreground 1469 * @param foreground the color to use for the foreground, as a packed float; if 0f, this won't place a foreground char 1470 * @param background the "base" color to use for the background, which will be combined with lightColor, as a packed float 1471 * @param lightColor the color to mix with the background, as a packed float 1472 * @param lightAmount a float that determines how much lightColor should affect background by; not strictly limited 1473 * to between 0 and 1, and negative values can be given to favor background more 1474 */ 1475 public void putWithReverseLight(int x, int y, char c, float foreground, float background, float lightColor, float lightAmount) { 1476 put(x, y, c, SColor.lerpFloatColors(foreground, lightColor, 1477 MathUtils.clamp(0x88p-9f + (0xC8p-9f * lightAmount), 0f, 1f)), background); 1478 } 1479 1480 /** 1481 * A convenience method that handles blending the background color with a specified light color, by a specific 1482 * amount, while also putting a char on the screen; as a whole this affects one x,y position. 1483 * @param x the x position, in cells 1484 * @param y the y position, in cells 1485 * @param c the char to put in the foreground 1486 * @param foreground the color to use for the foreground, as a packed float; if 0f, this won't place a foreground char 1487 * @param background the "base" color to use for the background, which will be combined with lightColor, as a packed float 1488 * @param lightColor the color to mix with the background, as a packed float 1489 * @param lightAmount a double that determines how much lightColor should affect background by; not strictly limited 1490 * to between 0 and 1, and negative values can be given to favor background more 1491 */ 1492 public void putWithReverseLight(int x, int y, char c, float foreground, float background, float lightColor, double lightAmount) { 1493 put(x, y, c, SColor.lerpFloatColors(foreground, lightColor, 1494 MathUtils.clamp(0x88p-9f + (0xC8p-9f * (float) lightAmount), 0f, 1f)), background); 1495 } 1496 /** 1497 * A convenience method that handles blending the background color with a specified light color, by a specific 1498 * amount that will be adjusted by the given {@link Noise.Noise3D} object, without putting a char on the screen; 1499 * as a whole this affects one x,y position and will change what it puts as the time (in milliseconds) changes. If 1500 * {@code flicker} is null, this will default to using {@link WhirlingNoise}. You can make the lighting dimmer by 1501 * using a darker color for {@code lightColor} or by giving a lower value for {@code lightAmount}. 1502 * @param x the x position, in cells 1503 * @param y the y position, in cells 1504 * @param background the "base" color to use for the background, which will be combined with lightColor, as a libGDX Color 1505 * @param lightColor the color to mix with the background, as a libGDX Color 1506 * @param lightAmount a float that determines how much lightColor should affect background by; not strictly limited 1507 * to between 0 and 1, and negative values can be given to favor background more 1508 *@param flicker a Noise.Noise3D instance, such as {@link WhirlingNoise#instance}; may be null to use WhirlingNoise 1509 */ 1510 public void putWithLight(int x, int y, Color background, Color lightColor, float lightAmount, Noise.Noise3D flicker) { 1511 putWithLight(x, y, '\0', 0f, background == null ? 0f : background.toFloatBits(), 1512 lightColor == null ? 0f : lightColor.toFloatBits(), lightAmount, flicker); 1513 } 1514 /** 1515 * A convenience method that handles blending the background color with a specified light color, by a specific 1516 * amount that will be adjusted by the given {@link Noise.Noise3D} object, while also putting a char on the screen; 1517 * as a whole this affects one x,y position and will change what it puts as the time (in milliseconds) changes. If 1518 * {@code flicker} is null, this will default to using {@link WhirlingNoise}. You can make the lighting dimmer by 1519 * using a darker color for {@code lightColor} or by giving a lower value for {@code lightAmount}. 1520 * @param x the x position, in cells 1521 * @param y the y position, in cells 1522 * @param c the char to put in the foreground 1523 * @param foreground the color to use for the foreground, as a libGDX Color; if null, this won't place a foreground char 1524 * @param background the "base" color to use for the background, which will be combined with lightColor, as a libGDX Color 1525 * @param lightColor the color to mix with the background, as a libGDX Color 1526 * @param lightAmount a float that determines how much lightColor should affect background by; not strictly limited 1527 * to between 0 and 1, and negative values can be given to favor background more 1528 *@param flicker a Noise.Noise3D instance, such as {@link WhirlingNoise#instance}; may be null to use WhirlingNoise 1529 */ 1530 public void putWithLight(int x, int y, char c, Color foreground, Color background, Color lightColor, float lightAmount, Noise.Noise3D flicker) { 1531 putWithLight(x, y, c, foreground == null ? 0f : foreground.toFloatBits(), background == null ? 0f : background.toFloatBits(), 1532 lightColor == null ? 0f : lightColor.toFloatBits(), lightAmount, flicker); 1533 } 1534 /** 1535 * A convenience method that handles blending the background color with a specified light color, by a specific 1536 * amount that will be adjusted by the given {@link Noise.Noise3D} object, without putting a char on the screen; 1537 * as a whole this affects one x,y position and will change what it puts as the time (in milliseconds) changes. If 1538 * {@code flicker} is null, this will default to using {@link WhirlingNoise}. You can make the lighting dimmer by 1539 * using a darker color for {@code lightColor} or by giving a lower value for {@code lightAmount}. 1540 * @param x the x position, in cells 1541 * @param y the y position, in cells 1542 * @param background the "base" color to use for the background, which will be combined with lightColor, as a packed float color 1543 * @param lightColor the color to mix with the background, as a packed float color 1544 * @param lightAmount a float that determines how much lightColor should affect background by; not strictly limited 1545 * to between 0 and 1, and negative values can be given to favor background more 1546 *@param flicker a Noise.Noise3D instance, such as {@link WhirlingNoise#instance}; may be null to use WhirlingNoise 1547 */ 1548 public void putWithLight(int x, int y, float background, float lightColor, float lightAmount, Noise.Noise3D flicker) { 1549 putWithLight(x, y, '\0', 0f, background, lightColor, lightAmount, flicker); 1550 } 1551 /** 1552 * A convenience method that handles blending the background color with a specified light color, by a specific 1553 * amount that will be adjusted by the given {@link Noise.Noise3D} object, while also putting a char on the screen; 1554 * as a whole this affects one x,y position and will change what it puts as the time (in milliseconds) changes. If 1555 * {@code flicker} is null, this will default to using {@link WhirlingNoise}. You can make the lighting dimmer by 1556 * using a darker color for {@code lightColor} or by giving a lower value for {@code lightAmount}. 1557 * @param x the x position, in cells 1558 * @param y the y position, in cells 1559 * @param c the char to put in the foreground 1560 * @param foreground the color to use for the foreground, as a packed float; if 0f, this won't place a foreground char 1561 * @param background the "base" color to use for the background, which will be combined with lightColor, as a packed float color 1562 * @param lightColor the color to mix with the background, as a packed float color 1563 * @param lightAmount a float that determines how much lightColor should affect background by; not strictly limited 1564 * to between 0 and 1, and negative values can be given to favor background more 1565 *@param flicker a Noise.Noise3D instance, such as {@link WhirlingNoise#instance}; may be null to use WhirlingNoise 1566 */ 1567 public void putWithLight(int x, int y, char c, float foreground, float background, float lightColor, float lightAmount, Noise.Noise3D flicker) { 1568 if(flicker == null) 1569 put(x, y, c, foreground, 1570 SColor.lerpFloatColors(background, lightColor,(0xAAp-9f + (0xC8p-9f * lightAmount * 1571 (1f + 0.35f * (float) WhirlingNoise.noise(x * 0.3, y * 0.3, (System.currentTimeMillis() & 0xffffffL) * 0.00125)))))); 1572 else 1573 put(x, y, c, foreground, 1574 SColor.lerpFloatColors(background, lightColor,(0xAAp-9f + (0xC8p-9f * lightAmount * 1575 (1f + 0.35f * (float) flicker.getNoise(x * 0.3, y * 0.3, (System.currentTimeMillis() & 0xffffffL) * 0.00125)))))); 1576 } 1577 public void putBorders(float color, String caption) 1578 { 1579 put(0, 0,'┌', color); 1580 put(gridWidth - 1, 0,'┐', color); 1581 put(0, gridHeight - 1,'└', color); 1582 put(gridWidth - 1, gridHeight - 1,'┘', color); 1583 for (int i = 1; i < gridWidth - 1; i++) { 1584 put(i, 0, '─', color); 1585 put(i, gridHeight - 1, '─', color); 1586 } 1587 for (int y = 1; y < gridHeight - 1; y++) { 1588 put(0, y, '│', color); 1589 put(gridWidth - 1, y, '│', color); 1590 } 1591 for (int y = 1; y < gridHeight - 1; y++) { 1592 for (int x = 1; x < gridWidth - 1; x++) { 1593 clear(x, y, 0); 1594 } 1595 } 1596 1597 if (caption != null) { 1598 put(1, 0, caption, color, 0f); 1599 } 1600 } 1601 1602 /** 1603 * 1604 * @param color the color to draw the borders with; does not affect caption's color(s) 1605 * @param caption an IColoredString 1606 */ 1607 public void putBordersCaptioned(float color, IColoredString<Color> caption) 1608 { 1609 put(0, 0,'┌', color); 1610 put(gridWidth - 1, 0,'┐', color); 1611 put(0, gridHeight - 1,'└', color); 1612 put(gridWidth - 1, gridHeight - 1,'┘', color); 1613 for (int i = 1; i < gridWidth - 1; i++) { 1614 put(i, 0, '─', color); 1615 put(i, gridHeight - 1, '─', color); 1616 } 1617 for (int y = 1; y < gridHeight - 1; y++) { 1618 put(0, y, '│', color); 1619 put(gridWidth - 1, y, '│', color); 1620 } 1621 for (int y = 1; y < gridHeight - 1; y++) { 1622 for (int x = 1; x < gridWidth - 1; x++) { 1623 clear(x, y, 0); 1624 } 1625 } 1626 1627 if (caption != null) { 1628 put(1, 0, caption); 1629 } 1630 } 1631 1632 /** 1633 * Removes the foreground chars, where present, in all layers at the given x,y position. 1634 * The backgrounds will be unchanged. 1635 * @param x the x-coordinate of the position to remove all chars from 1636 * @param y the y-coordinate of the position to remove all chars from 1637 */ 1638 @Override 1639 public void clear(int x, int y) 1640 { 1641 if(x < 0 || x >= gridWidth || y < 0 || y >= gridHeight) 1642 return; 1643 backgrounds[x][y] = 0f; 1644 int code = SparseTextMap.encodePosition(x, y); 1645 for (int i = 0; i < layers.size(); i++) { 1646 layers.get(i).remove(code); 1647 } 1648 } 1649 1650 /** 1651 * Removes the foreground char, if present, in the given layer at the given x,y position. 1652 * The backgrounds and other layers will be unchanged. 1653 * @param x the x-coordinate of the position to remove all chars from 1654 * @param y the y-coordinate of the position to remove all chars from 1655 * @param layer the layer to remove from 1656 */ 1657 public void clear(int x, int y, int layer) 1658 { 1659 layer = mapping.get(layer, -1); 1660 if(layer >= 0) 1661 { 1662 layers.get(layer).remove(SparseTextMap.encodePosition(x, y)); 1663 } 1664 } 1665 1666 /** 1667 * Removes all background colors, setting them to transparent, and all foreground chars in all layers. 1668 */ 1669 @Override 1670 public void clear() 1671 { 1672 ArrayTools.fill(backgrounds, 0f); 1673 for (int i = 0; i < layers.size(); i++) { 1674 layers.get(i).clear(); 1675 } 1676 } 1677 1678 /** 1679 * Removes all foreground chars in the requested layer; does not affect the background or other layers. 1680 */ 1681 public void clear(int layer) 1682 { 1683 int lay = mapping.get(layer, -1); 1684 if(lay >= 0) 1685 layers.get(lay).clear(); 1686 } 1687 /** 1688 * Fills all of the background with the given color as libGDX Color object. 1689 * @param color the color to use for all of the background, as a libGDX Color or some subclass like SColor 1690 */ 1691 public void fillBackground(Color color) 1692 { 1693 ArrayTools.fill(backgrounds, color == null ? 0f : scc.filter(color).toFloatBits()); 1694 } 1695 1696 /** 1697 * Fills all of the background with the given color as a packed float. 1698 * @param color the color to use for all of the background, as a packed float 1699 */ 1700 public void fillBackground(float color) 1701 { 1702 ArrayTools.fill(backgrounds, color); 1703 } 1704 1705 /** 1706 * Changes the background color in an area to all have the given color, as a libGDX Color (or SColor, etc.). 1707 * @param color a libGDX Color to fill the area with; may be null to make the background transparent 1708 * @param x left edge's x coordinate, in cells 1709 * @param y top edge's y coordinate, in cells 1710 * @param width the width of the area to change the color on 1711 * @param height the height of the area to change the color on 1712 */ 1713 public void fillArea(Color color, int x, int y, int width, int height) { 1714 fillArea(color == null ? 0f : scc.filter(color).toFloatBits(), x, y, width, height); 1715 } 1716 /** 1717 * Changes the background color in an area to all have the given color, as a packed float. 1718 * @param color a color as a packed float to fill the area with; may be 0f to make the background transparent 1719 * @param x left edge's x coordinate, in cells 1720 * @param y top edge's y coordinate, in cells 1721 * @param width the width of the area to change the color on 1722 * @param height the height of the area to change the color on 1723 */ 1724 public void fillArea(float color, int x, int y, int width, int height) { 1725 if (x < 0) { 1726 width += x; 1727 x = 0; 1728 } 1729 if (y < 0) { 1730 height += y; 1731 y = 0; 1732 } 1733 if (width <= 0 || height <= 0) 1734 return; 1735 for (int i = 0, xx = x; i < width && xx < gridWidth; i++, xx++) { 1736 for (int j = 0, yy = y; j < height && yy < gridHeight; j++, yy++) { 1737 backgrounds[xx][yy] = color; 1738 } 1739 } 1740 } 1741 1742 /** 1743 * Gets whether any animations or other scene2d Actions are running on this SparseLayers. 1744 * @return true if any animations or Actions are currently running, false otherwise 1745 */ 1746 @Override 1747 public boolean hasActiveAnimations() 1748 { 1749 if(hasActions()) 1750 return true; 1751 for (int i = 0; i < glyphs.size(); i++) { 1752 if(glyphs.get(i).hasActions()) return true; 1753 } 1754 return false; 1755 } 1756 1757 /** 1758 * Gets a direct reference to the 2D float array this uses for background colors. 1759 * @return a direct reference to the 2D float array this uses for background colors. 1760 */ 1761 public float[][] getBackgrounds() { 1762 return backgrounds; 1763 } 1764 1765 /** 1766 * Changes the reference this uses for the float array for background colors; this will not accept null parameters, 1767 * nor will it accept any 2D array with dimensions that do not match the (unchanging) gridWidth and gridHeight of 1768 * this SparseLayers. 1769 * @param backgrounds a non-null 2D float array of colors; must have width == gridWidth and height == gridHeight 1770 */ 1771 public void setBackgrounds(float[][] backgrounds) { 1772 if(backgrounds == null || backgrounds[0] == null) 1773 throw new IllegalArgumentException("SparseLayers.backgrounds must not be set to null"); 1774 if(backgrounds.length != gridWidth || backgrounds[0].length != gridHeight) 1775 throw new IllegalArgumentException("Must be given a 2D array with equal dimensions to the current gridWidth and gridHeight"); 1776 this.backgrounds = backgrounds; 1777 } 1778 1779 /** 1780 * Gets a direct reference to the TextCellFactory this uses to draw and size its text items and cells. 1781 * @return a direct reference to the TextCellFactory this uses to draw and size its text items and cells. 1782 */ 1783 public TextCellFactory getFont() { 1784 return font; 1785 } 1786 1787 /** 1788 * Sets the TextCellFactory this uses to draw and size its text items and cells. The given TextCellFactory must not 1789 * be null. If font is uninitialized, this will initialize it using cellWidth and cellHeight. If font has been 1790 * initialized with a different height and width, then the sizing of this SparseLayers will change. 1791 * @param font a non-null TextCellFactory; if uninitialized, this will initialize it using cellWidth and cellHeight. 1792 */ 1793 public void setFont(TextCellFactory font) { 1794 if(font == null) 1795 throw new IllegalArgumentException("SparseLayers.font must not be set to null"); 1796 if(!font.initialized()) 1797 font.width(cellWidth()).height(cellHeight()).initBySize(); 1798 this.font = font; 1799 } 1800 1801 /** 1802 * Gets the IColorCenter for Color objects (almost always a SquidColorCenter, but this isn't guaranteed) that this 1803 * uses to cache and possibly alter Colors that given to it as parameters. 1804 * @return the IColorCenter of Color that this uses to cache and modify Colors given to it 1805 */ 1806 public IColorCenter<Color> getScc() { 1807 return scc; 1808 } 1809 1810 /** 1811 * Sets the IColorCenter for Color objects that this uses to cache and modify Colors given to it; does not accept 1812 * null parameters. 1813 * @param scc a non-null IColorCenter of Color that this will use to cache and modify Colors given to it 1814 */ 1815 public void setScc(IColorCenter<Color> scc) { 1816 if(scc == null) 1817 throw new IllegalArgumentException("The IColorCenter<Color> given to setScc() must not be null"); 1818 this.scc = scc; 1819 } 1820 1821 /** 1822 * Used internally to go between grid positions and world positions. 1823 * @param gridX x on the grid 1824 * @return x in the world 1825 */ 1826 public float worldX(int gridX) 1827 { 1828 return getX() + gridX * font.actualCellWidth; 1829 } 1830 /** 1831 * Used internally to go between grid positions and world positions. 1832 * @param gridY y on the grid 1833 * @return y in the world 1834 */ 1835 public float worldY(int gridY) 1836 { 1837 return getY() + (gridHeight - gridY) * font.actualCellHeight; 1838 } 1839 1840 /** 1841 * Used internally to go between world positions and grid positions. 1842 * @param worldX x in the world 1843 * @return x on the grid 1844 */ 1845 public int gridX(float worldX) 1846 { 1847 return Math.round((worldX - getX()) / font.actualCellWidth); 1848 } 1849 1850 /** 1851 * Used internally to go between world positions and grid positions. 1852 * @param worldY y in the world 1853 * @return y on the grid 1854 */ 1855 public int gridY(float worldY) 1856 { 1857 return Math.round((getY() - worldY) / font.actualCellHeight + gridHeight); 1858 } 1859 1860 /** 1861 * Using the existing background color at the position x,y, this performs color blending from that existing color to 1862 * the given color (as a float), using the mixBy parameter to determine how much of the color parameter to use (1f 1863 * will set the color in this to the parameter, while 0f for mixBy will ignore the color parameter entirely). 1864 * @param x the x component of the position in this panel to draw the starting color from 1865 * @param y the y component of the position in this panel to draw the starting color from 1866 * @param color the new color to mix with the starting color; a packed float, as made by {@link Color#toFloatBits()} 1867 * @param mixBy the amount by which the new color will affect the old one, between 0 (no effect) and 1 (overwrite) 1868 */ 1869 @Override 1870 public void blend(int x, int y, float color, float mixBy) 1871 { 1872 backgrounds[x][y] = SColor.lerpFloatColorsBlended(backgrounds[x][y], color, mixBy); 1873 } 1874 1875 /** 1876 * Produces a single char with a color, that can be positioned independently of the contents of this SparseLayers. 1877 * Various effects in this class take a Glyph parameter and can perform visual effects with one. This takes a char 1878 * to show, a color that may be filtered, and an x,y position in grid cells, and returns a Glyph that has those 1879 * qualities set. 1880 * @param shown the char to use in the Glyph 1881 * @param color the color to use for the Glyph, which can be filtered 1882 * @param x the x position, in grid cells 1883 * @param y the y position, in grid cells 1884 * @return a Glyph (an inner class of TextCellFactory) with the given qualities 1885 */ 1886 public TextCellFactory.Glyph glyph(char shown, Color color, int x, int y) 1887 { 1888 return glyph(shown, color == null ? 0f : scc.filter(color).toFloatBits(), x, y); 1889 } 1890 1891 /** 1892 * Produces a single char with a color, that can be positioned independently of the contents of this SparseLayers. 1893 * Various effects in this class take a Glyph parameter and can perform visual effects with one. This takes a char 1894 * to show, a color as an encoded float, and an x,y position in grid cells, and returns a Glyph that has those 1895 * qualities set. 1896 * @param shown the char to use in the Glyph 1897 * @param color the color to use for the Glyph as an encoded float 1898 * @param x the x position, in grid cells 1899 * @param y the y position, in grid cells 1900 * @return a Glyph (an inner class of TextCellFactory) with the given qualities 1901 */ 1902 public TextCellFactory.Glyph glyph(char shown, float color, int x, int y) 1903 { 1904 TextCellFactory.Glyph g = 1905 font.glyph(shown, color, 1906 worldX(x), 1907 worldY(y)); 1908 glyphs.add(g); 1909 return g; 1910 } 1911 /** 1912 * "Promotes" a colorful char in the first layer to a Glyph that can be positioned independently of the contents of 1913 * this SparseLayers. Various effects in this class take a Glyph parameter and can perform visual effects with one. 1914 * This takes only an x,y position in grid cells, removes the char at that position in the first layer from normal 1915 * rendering, and returns a Glyph at that same position with the same char and color, but that can be moved more. 1916 * @param x the x position, in grid cells 1917 * @param y the y position, in grid cells 1918 * @return a Glyph (an inner class of TextCellFactory) that took the qualities of the removed char and its color 1919 */ 1920 public TextCellFactory.Glyph glyphFromGrid(int x, int y) 1921 { 1922 int code = SparseTextMap.encodePosition(x, y); 1923 SparseTextMap stm = layers.get(0); 1924 char shown = stm.getChar(code, ' '); 1925 float color = stm.getFloat(code, 0f); 1926 stm.remove(code); 1927 TextCellFactory.Glyph g = 1928 font.glyph(shown, color, worldX(x), worldY(y)); 1929 glyphs.add(g); 1930 return g; 1931 } 1932 /** 1933 * "Promotes" a colorful char in the given layer to a Glyph that can be positioned independently of the contents of 1934 * this SparseLayers. Various effects in this class take a Glyph parameter and can perform visual effects with one. 1935 * This takes only an x,y position in grid cells, removes the char at that position in the given layer from normal 1936 * rendering, and returns a Glyph at that same position with the same char and color, but that can be moved more. 1937 * @param x the x position, in grid cells 1938 * @param y the y position, in grid cells 1939 * @param layer the layer to take a colorful char from 1940 * @return a Glyph (an inner class of TextCellFactory) that took the qualities of the removed char and its color; may return null if the layer is invalid 1941 */ 1942 public TextCellFactory.Glyph glyphFromGrid(int x, int y, int layer) { 1943 layer = mapping.get(layer, -1); 1944 if (layer >= 0) { 1945 SparseTextMap stm = layers.get(layer); 1946 int code = SparseTextMap.encodePosition(x, y); 1947 char shown = stm.getChar(code, ' '); 1948 float color = stm.getFloat(code, 0f); 1949 stm.remove(code); 1950 TextCellFactory.Glyph g = 1951 font.glyph(shown, color, worldX(x), worldY(y)); 1952 glyphs.add(g); 1953 return g; 1954 } 1955 else 1956 { 1957 return null; 1958 } 1959 } 1960 1961 /** 1962 * Brings a Glyph back into normal rendering, removing it from the Glyphs this class knows about and filling the 1963 * grid's char at the Glyph's position in the first layer with the Glyph's char and color. 1964 * @param glyph the Glyph to remove and fit back into the grid 1965 */ 1966 public void recallToGrid(TextCellFactory.Glyph glyph) 1967 { 1968 layers.get(0).place(gridX(glyph.getY()), gridY(glyph.getY()), glyph.shown, glyph.getPackedColor()); 1969 glyphs.remove(glyph); 1970 } 1971 1972 /** 1973 * Brings a Glyph back into normal rendering, removing it from the Glyphs this class knows about and filling the 1974 * grid's char at the Glyph's position in the given layer with the Glyph's char and color. 1975 * @param glyph the Glyph to remove and fit back into the grid 1976 */ 1977 public void recallToGrid(TextCellFactory.Glyph glyph, int layer) 1978 { 1979 layer = mapping.get(layer, 0); 1980 layers.get(layer).place(gridX(glyph.getY()), gridY(glyph.getY()), glyph.shown, glyph.getPackedColor()); 1981 glyphs.remove(glyph); 1982 } 1983 1984 /** 1985 * A way to remove a Glyph from the group of glyphs this renders, while also ending any animations or other Actions 1986 * that the removed Glyph was scheduled to perform. 1987 * @param glyph a Glyph that should be removed from the {@link #glyphs} List this holds 1988 */ 1989 public void removeGlyph(TextCellFactory.Glyph glyph) 1990 { 1991 glyph.clearActions(); 1992 glyphs.remove(glyph); 1993 } 1994 /** 1995 * Start a bumping animation in the given direction that will last duration seconds. 1996 * @param glyph 1997 * A {@link TextCellFactory.Glyph}, probably produced by 1998 * {@link #glyph(char, float, int, int)} or {@link #glyphFromGrid(int, int)} 1999 * @param direction the direction for the glyph to bump towards 2000 * @param duration a float, measured in seconds, for how long the animation should last; commonly 0.12f 2001 */ 2002 public void bump(final TextCellFactory.Glyph glyph, Direction direction, float duration) { 2003 bump(0f, glyph, direction, duration, null); 2004 } 2005 /** 2006 * Start a bumping animation in the given direction after delay seconds, that will last duration seconds; runs 2007 * postRunnable after the duration completes if postRunnable is non-null. 2008 * 2009 * @param delay how long to wait in seconds before starting the effect 2010 * @param glyph 2011 * A {@link TextCellFactory.Glyph}, which should be produced by a SparseLayers method like 2012 * {@link #glyph(char, float, int, int)} or {@link #glyphFromGrid(int, int)} 2013 * @param direction the direction for the glyph to bump towards 2014 * @param duration a float, measured in seconds, for how long the animation should last; commonly 0.12f 2015 * @param postRunnable a Runnable to execute after the bump completes; may be null to do nothing. 2016 */ 2017 public void bump(final float delay, final TextCellFactory.Glyph glyph, Direction direction, float duration, final /* @Nullable */ Runnable postRunnable) 2018 { 2019 final float x = glyph.getX(), 2020 y = glyph.getY(); 2021 duration = Math.max(0.015f, duration); 2022 final int nbActions = 2 + (0 < delay ? 1 : 0) + (postRunnable == null ? 0 : 1); 2023 int index = 0; 2024 final Action[] sequence = new Action[nbActions]; 2025 if (0 < delay) 2026 sequence[index++] = Actions.delay(delay); 2027 sequence[index++] = Actions.moveToAligned(x + direction.deltaX * 0.35f * font.actualCellWidth, 2028 y - direction.deltaY * 0.35f * font.actualCellHeight, 2029 Align.bottomLeft, duration * 0.35F); 2030 sequence[index++] = Actions.moveToAligned(x, y, Align.bottomLeft, duration * 0.65F); 2031 if(postRunnable != null) 2032 { 2033 sequence[index] = Actions.run(postRunnable); 2034 } 2035 glyph.addAction(Actions.sequence(sequence)); 2036 } 2037 2038 /** 2039 * Slides {@code glyph} from {@code (xstartX,startY)} to {@code (newx, newy)}. 2040 * Takes a number of seconds equal to duration to complete. This also allows 2041 * a Runnable to be given as {@code postRunnable} to be run after the 2042 * slide completes, or null to not run anything after the slide. 2043 * 2044 * @param glyph 2045 * A {@link TextCellFactory.Glyph}, probably produced by 2046 * {@link #glyph(char, float, int, int)} or {@link #glyphFromGrid(int, int)} 2047 * @param startX 2048 * Where to start the slide, horizontally. 2049 * @param startY 2050 * Where to start the slide, vertically. 2051 * @param newX 2052 * Where to end the slide, horizontally. 2053 * @param newY 2054 * Where to end the slide, vertically. 2055 * @param duration 2056 * The animation's duration. 2057 * @param postRunnable a Runnable to execute after the slide completes; may be null to do nothing. 2058 */ 2059 public void slide(TextCellFactory.Glyph glyph, final int startX, final int startY, final int newX, 2060 final int newY, float duration, /* @Nullable */ Runnable postRunnable) { 2061 slide(0f, glyph, startX, startY, newX, newY, duration, postRunnable); 2062 } 2063 2064 /** 2065 * Slides {@code glyph} from {@code (xstartX,startY)} to {@code (newx, newy)} after waiting {@code delay} seconds. 2066 * Takes a number of seconds equal to duration to complete (starting after the delay). This also allows 2067 * a Runnable to be given as {@code postRunnable} to be run after the 2068 * slide completes, or null to not run anything after the slide. 2069 * 2070 * @param delay how long to wait in seconds before starting the effect 2071 * @param glyph 2072 * A {@link TextCellFactory.Glyph}, probably produced by 2073 * {@link #glyph(char, float, int, int)} or {@link #glyphFromGrid(int, int)} 2074 * @param startX 2075 * Where to start the slide, horizontally. 2076 * @param startY 2077 * Where to start the slide, vertically. 2078 * @param newX 2079 * Where to end the slide, horizontally. 2080 * @param newY 2081 * Where to end the slide, vertically. 2082 * @param duration 2083 * The animation's duration. 2084 * @param postRunnable a Runnable to execute after the slide completes; may be null to do nothing. 2085 */ 2086 public void slide(float delay, TextCellFactory.Glyph glyph, final int startX, final int startY, final int newX, 2087 final int newY, float duration, /* @Nullable */ Runnable postRunnable) { 2088 glyph.setPosition(worldX(startX), worldY(startY)); 2089 duration = Math.max(0.015f, duration); 2090 final int nbActions = 1 + (0 < delay ? 1 : 0) + (postRunnable == null ? 0 : 1); 2091 int index = 0; 2092 final Action[] sequence = new Action[nbActions]; 2093 if (0 < delay) 2094 sequence[index++] = Actions.delay(delay); 2095 final float nextX = worldX(newX); 2096 final float nextY = worldY(newY); 2097 sequence[index++] = Actions.moveToAligned(nextX, nextY, Align.bottomLeft, duration); 2098 if(postRunnable != null) 2099 { 2100 sequence[index] = Actions.run(postRunnable); 2101 } 2102 2103 glyph.addAction(Actions.sequence(sequence)); 2104 } 2105 2106 /** 2107 * Starts an wiggling animation for the object at the given location for the given duration in seconds. 2108 * 2109 * @param glyph 2110 * A {@link TextCellFactory.Glyph}, probably produced by 2111 * {@link #glyph(char, float, int, int)} or {@link #glyphFromGrid(int, int)} 2112 * @param duration in seconds, as a float 2113 */ 2114 public void wiggle(final TextCellFactory.Glyph glyph, float duration) { 2115 wiggle(0f, glyph, duration, null); 2116 } 2117 2118 /** 2119 * Starts a wiggling animation for the object at the given location, after waiting delay seconds, for the given 2120 * duration in seconds; runs postRunnable afterwards if it is non-null. 2121 * 2122 * @param delay how long to wait in seconds before starting the effect 2123 * @param glyph 2124 * A {@link TextCellFactory.Glyph}, probably produced by 2125 * {@link #glyph(char, float, int, int)} or {@link #glyphFromGrid(int, int)} 2126 * @param duration in seconds, as a float 2127 * @param postRunnable a Runnable to execute after the wiggle completes; may be null to do nothing. 2128 */ 2129 public void wiggle(final float delay, final TextCellFactory.Glyph glyph, float duration, 2130 /* @Nullable */ Runnable postRunnable) { 2131 final float x = glyph.getX(), y = glyph.getY(), 2132 cellWidth = font.actualCellWidth, cellHeight = font.actualCellHeight; 2133 duration = Math.max(0.015f, duration); 2134 final int nbActions = 5 + (0 < delay ? 1 : 0) + (postRunnable == null ? 0 : 1); 2135 final Action[] sequence = new Action[nbActions]; 2136 int index = 0; 2137 if (0 < delay) 2138 sequence[index++] = Actions.delay(delay); 2139 final StatefulRNG gRandom = DefaultResources.getGuiRandom(); 2140 sequence[index++] = Actions.moveToAligned(x + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f, 2141 y + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f, Align.bottomLeft, duration * 0.2F); 2142 sequence[index++] = Actions.moveToAligned(x + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f, 2143 y + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f, Align.bottomLeft, duration * 0.2F); 2144 sequence[index++] = Actions.moveToAligned(x + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f, 2145 y + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f, Align.bottomLeft, duration * 0.2F); 2146 sequence[index++] = Actions.moveToAligned(x + (gRandom.nextFloat() - 0.5F) * cellWidth * 0.4f, 2147 y + (gRandom.nextFloat() - 0.5F) * cellHeight * 0.4f, Align.bottomLeft, duration * 0.2F); 2148 sequence[index++] = Actions.moveToAligned(x, y, Align.bottomLeft, duration * 0.2F); 2149 if(postRunnable != null) 2150 { 2151 sequence[index] = Actions.run(postRunnable); 2152 } 2153 glyph.addAction(Actions.sequence(sequence)); 2154 } 2155 /** 2156 * Tints the background at position x,y so it becomes the given encodedColor, then after the tint is complete it 2157 * returns the cell to its original color, taking duration seconds. 2158 * <br> 2159 * This will only behave correctly if you call {@link Stage#act()} before you call {@link Stage#draw()}, but after 2160 * any changes to the contents of this SparseLayers. If you change the contents, then draw, and then act, that will 2161 * draw the contents without the tint this applies, then apply the tint when you call act(), then quickly overwrite 2162 * the tint in the next frame. That visually appears as nothing happening other than a delay. 2163 * @param x the x-coordinate of the cell to tint 2164 * @param y the y-coordinate of the cell to tint 2165 * @param color what to transition the cell's color towards, and then transition back from, as a Color object 2166 * @param duration how long the total "round-trip" transition should take in seconds 2167 */ 2168 public void tint(final int x, final int y, final Color color, float duration) { 2169 tint(0f, x, y, color.toFloatBits(), duration, null); 2170 } 2171 /** 2172 * Tints the background at position x,y so it becomes the given encodedColor, then after the tint is complete it 2173 * returns the cell to its original color, taking duration seconds. 2174 * <br> 2175 * This will only behave correctly if you call {@link Stage#act()} before you call {@link Stage#draw()}, but after 2176 * any changes to the contents of this SparseLayers. If you change the contents, then draw, and then act, that will 2177 * draw the contents without the tint this applies, then apply the tint when you call act(), then quickly overwrite 2178 * the tint in the next frame. That visually appears as nothing happening other than a delay. 2179 * @param x the x-coordinate of the cell to tint 2180 * @param y the y-coordinate of the cell to tint 2181 * @param encodedColor what to transition the cell's color towards, and then transition back from, as a packed float 2182 * @param duration how long the total "round-trip" transition should take in seconds 2183 */ 2184 public void tint(final int x, final int y, final float encodedColor, float duration) { 2185 tint(0f, x, y, encodedColor, duration, null); 2186 } 2187 /** 2188 * Tints the background at position x,y so it becomes the given encodedColor, waiting for {@code delay} (in seconds) 2189 * before performing it, then after the tint is complete it returns the cell to its original color, taking duration 2190 * seconds. Additionally, enqueue {@code postRunnable} for running after the created action ends. 2191 * <br> 2192 * This will only behave correctly if you call {@link Stage#act()} before you call {@link Stage#draw()}, but after 2193 * any changes to the contents of this SparseLayers. If you change the contents, then draw, and then act, that will 2194 * draw the contents without the tint this applies, then apply the tint when you call act(), then quickly overwrite 2195 * the tint in the next frame. That visually appears as nothing happening other than a delay. 2196 * @param delay how long to wait in seconds before starting the effect 2197 * @param x the x-coordinate of the cell to tint 2198 * @param y the y-coordinate of the cell to tint 2199 * @param encodedColor what to transition the cell's color towards, and then transition back from, as a packed float 2200 * @param duration how long the total "round-trip" transition should take in seconds 2201 * @param postRunnable a Runnable to execute after the tint completes; may be null to do nothing. 2202 */ 2203 public void tint(final float delay, final int x, final int y, final float encodedColor, float duration, 2204 /* @Nullable */ Runnable postRunnable) { 2205 if(x < 0 || x >= gridWidth || y < 0 || y >= gridHeight) 2206 return; 2207 duration = Math.max(0.015f, duration); 2208 final float ac = backgrounds[x][y]; 2209 final int nbActions = 3 + (0 < delay ? 1 : 0) + (postRunnable == null ? 0 : 1); 2210 final Action[] sequence = new Action[nbActions]; 2211 int index = 0; 2212 if (0 < delay) 2213 sequence[index++] = Actions.delay(delay); 2214 sequence[index++] = new TemporalAction(duration * 0.3f) { 2215 @Override 2216 protected void update(float percent) { 2217 backgrounds[x][y] = SColor.lerpFloatColors(ac, encodedColor, percent); 2218 } 2219 }; 2220 sequence[index++] = new TemporalAction(duration * 0.7f) { 2221 @Override 2222 protected void update(float percent) { 2223 backgrounds[x][y] = SColor.lerpFloatColors(encodedColor, ac, percent); 2224 } 2225 }; 2226 if(postRunnable != null) 2227 { 2228 sequence[index++] = Actions.run(postRunnable); 2229 } 2230 sequence[index] = Actions.delay(duration, Actions.run(new Runnable() { 2231 @Override 2232 public void run() { 2233 backgrounds[x][y] = ac; 2234 } 2235 })); 2236 2237 addAction(Actions.sequence(sequence)); 2238 } 2239 2240 /** 2241 * Tints the foreground in the given layer at position x,y so it becomes the given encodedColor, then after the tint 2242 * is complete it returns the cell to its original color, taking duration seconds. 2243 * <br> 2244 * The {@link SquidPanel#tint(float, int, int, Color, float, Runnable)} method has been reworked to use the same 2245 * technique this class uses for rendering text, and the two classes should have similar appearance (if not the 2246 * same) when rendering the same text. SparseLayers tends to be faster, especially when not all of the map is shown. 2247 * <br> 2248 * This will only behave correctly if you call {@link Stage#act()} before you call {@link Stage#draw()}, but after 2249 * any changes to the contents of this SparseLayers. If you change the contents, then draw, and then act, that will 2250 * draw the contents without the tint this applies, then apply the tint when you call act(), then quickly overwrite 2251 * the tint in the next frame. That visually appears as nothing happening other than a delay. 2252 * @param x the x-coordinate of the cell to tint 2253 * @param y the y-coordinate of the cell to tint 2254 * @param layer which layer to affect; if you haven't specified a layer when placing text, then this should be 0 2255 * @param color what to transition the cell's color towards, and then transition back from, as a Color object 2256 * @param duration how long the total "round-trip" transition should take in seconds 2257 */ 2258 public void tint(final int x, final int y, final int layer, final Color color, float duration) { 2259 tint(0f, x, y, layer, color.toFloatBits(), duration, null); 2260 } 2261 2262 /** 2263 * Tints the foreground in the given layer at position x,y so it becomes the given encodedColor, then after the tint 2264 * is complete it returns the cell to its original color, taking duration seconds. 2265 * <br> 2266 * The {@link SquidPanel#tint(float, int, int, Color, float, Runnable)} method has been reworked to use the same 2267 * technique this class uses for rendering text, and the two classes should have similar appearance (if not the 2268 * same) when rendering the same text. SparseLayers tends to be faster, especially when not all of the map is shown. 2269 * <br> 2270 * This will only behave correctly if you call {@link Stage#act()} before you call {@link Stage#draw()}, but after 2271 * any changes to the contents of this SparseLayers. If you change the contents, then draw, and then act, that will 2272 * draw the contents without the tint this applies, then apply the tint when you call act(), then quickly overwrite 2273 * the tint in the next frame. That visually appears as nothing happening other than a delay. 2274 * @param x the x-coordinate of the cell to tint 2275 * @param y the y-coordinate of the cell to tint 2276 * @param layer which layer to affect; if you haven't specified a layer when placing text, then this should be 0 2277 * @param encodedColor what to transition the cell's color towards, and then transition back from, as a packed float 2278 * @param duration how long the total "round-trip" transition should take in seconds 2279 */ 2280 public void tint(final int x, final int y, final int layer, final float encodedColor, float duration) { 2281 tint(0f, x, y, layer, encodedColor, duration, null); 2282 } 2283 /** 2284 * Tints the foreground in the given layer at position x,y so it becomes the given encodedColor, waiting for 2285 * {@code delay} (in seconds) before performing it, then after the tint is complete it returns the cell to its 2286 * original color, taking duration seconds. Additionally, enqueue {@code postRunnable} for running after the created 2287 * action ends. 2288 * <br> 2289 * The {@link SquidPanel#tint(float, int, int, Color, float, Runnable)} method has been reworked to use the same 2290 * technique this class uses for rendering text, and the two classes should have similar appearance (if not the 2291 * same) when rendering the same text. SparseLayers tends to be faster, especially when not all of the map is shown. 2292 * <br> 2293 * This will only behave correctly if you call {@link Stage#act()} before you call {@link Stage#draw()}, but after 2294 * any changes to the contents of this SparseLayers. If you change the contents, then draw, and then act, that will 2295 * draw the contents without the tint this applies, then apply the tint when you call act(), then quickly overwrite 2296 * the tint in the next frame. That visually appears as nothing happening other than a delay. 2297 * @param delay how long to wait in seconds before starting the effect 2298 * @param x the x-coordinate of the cell to tint 2299 * @param y the y-coordinate of the cell to tint 2300 * @param layer which layer to affect; if you haven't specified a layer when placing text, then this should be 0 2301 * @param encodedColor what to transition the cell's color towards, and then transition back from, as a packed float 2302 * @param duration how long the total "round-trip" transition should take in seconds 2303 * @param postRunnable a Runnable to execute after the tint completes; may be null to do nothing. 2304 */ 2305 public void tint(final float delay, final int x, final int y, final int layer, final float encodedColor, float duration, 2306 /* @Nullable */ Runnable postRunnable) { 2307 if(x < 0 || x >= gridWidth || y < 0 || y >= gridHeight || layer < 0 || layer >= layers.size()) 2308 return; 2309 final SparseTextMap l = layers.get(layer); 2310 duration = Math.max(0.015f, duration); 2311 final int pos = SparseTextMap.encodePosition(x, y); 2312 final float ac = l.getFloat(pos,0f); 2313 final int nbActions = 3 + (0 < delay ? 1 : 0) + (postRunnable == null ? 0 : 1); 2314 final Action[] sequence = new Action[nbActions]; 2315 int index = 0; 2316 if (0 < delay) 2317 sequence[index++] = Actions.delay(delay); 2318 sequence[index++] = new TemporalAction(duration * 0.3f) { 2319 @Override 2320 protected void update(float percent) { 2321 l.updateFloat(pos, SColor.lerpFloatColors(ac, encodedColor, percent)); 2322 } 2323 }; 2324 sequence[index++] = new TemporalAction(duration * 0.7f) { 2325 @Override 2326 protected void update(float percent) { 2327 l.updateFloat(pos, SColor.lerpFloatColors(encodedColor, ac, percent)); 2328 } 2329 }; 2330 if(postRunnable != null) 2331 { 2332 sequence[index++] = Actions.run(postRunnable); 2333 } 2334 /* Do this one last, so that hasActiveAnimations() returns true during 'postRunnables' */ 2335 sequence[index] = Actions.delay(duration, Actions.run(new Runnable() { 2336 @Override 2337 public void run() { 2338 l.updateFloat(pos, ac); 2339 } 2340 })); 2341 addAction(Actions.sequence(sequence)); 2342 } 2343 /** 2344 * Tints the given glyph (which may or may not be part of the {@link #glyphs} list this holds) so it becomes the 2345 * given encodedColor, then after the tint is complete it returns the cell to its original color, taking duration 2346 * seconds. This resets the glyph to its pre-tint color before it ends. 2347 * @param glyph the {@link TextCellFactory.Glyph} to tint 2348 * @param encodedColor what to transition the cell's color towards, and then transition back from, as a packed float 2349 * @param duration how long the total "round-trip" transition should take in seconds 2350 */ 2351 public void tint(final TextCellFactory.Glyph glyph, final float encodedColor, float duration) { 2352 tint(0f, glyph, encodedColor, duration, null); 2353 } 2354 /** 2355 * Tints the given glyph (which may or may not be part of the {@link #glyphs} list this holds) so it becomes the 2356 * given encodedColor, waiting for {@code delay} (in seconds) before performing it, then after the tint is complete 2357 * it returns the cell to its original color, taking duration seconds. Additionally, enqueue {@code postRunnable} 2358 * for running after the created action ends. This resets the glyph to its pre-tint color before it runs any 2359 * {@code postRunnable}. 2360 * @param delay how long to wait in seconds before starting the effect 2361 * @param glyph the {@link TextCellFactory.Glyph} to tint 2362 * @param encodedColor what to transition the cell's color towards, and then transition back from, as a packed float 2363 * @param duration how long the total "round-trip" transition should take in seconds 2364 * @param postRunnable a Runnable to execute after the tint completes; may be null to do nothing. 2365 */ 2366 public void tint(float delay, final TextCellFactory.Glyph glyph, final float encodedColor, float duration, 2367 /* @Nullable */ Runnable postRunnable) { 2368 duration = Math.max(0.015f, duration); 2369 final float ac = glyph.getPackedColor(); 2370 final int nbActions = 3 + (0 < delay ? 1 : 0) + (postRunnable == null ? 0 : 1); 2371 final Action[] sequence = new Action[nbActions]; 2372 int index = 0; 2373 if (0 < delay) 2374 sequence[index++] = Actions.delay(delay); 2375 sequence[index++] = new TemporalAction(duration * 0.3f) { 2376 @Override 2377 protected void update(float percent) { 2378 glyph.setPackedColor(SColor.lerpFloatColors(ac, encodedColor, percent)); 2379 } 2380 }; 2381 sequence[index++] = new TemporalAction(duration * 0.7f) { 2382 @Override 2383 protected void update(float percent) { 2384 glyph.setPackedColor(SColor.lerpFloatColors(encodedColor, ac, percent)); 2385 } 2386 }; 2387 if(postRunnable != null) 2388 { 2389 sequence[index++] = Actions.run(postRunnable); 2390 } 2391 /* Do this one last, so that hasActiveAnimations() returns true during 'postRunnables' */ 2392 sequence[index] = Actions.delay(duration, Actions.run(new Runnable() { 2393 @Override 2394 public void run() { 2395 glyph.setPackedColor(ac); 2396 } 2397 })); 2398 addAction(Actions.sequence(sequence)); 2399 } 2400 /** 2401 * Create a new Glyph at (startX, startY) using the char shown with the given startColor, and immediately starts 2402 * changing color to endColor, changing position so it ends on the cell (endX, endY), taking duration seconds to 2403 * complete before removing the Glyph. 2404 * <br> 2405 * Unlike {@link SquidPanel#summon(float, int, int, int, int, char, Color, Color, boolean, float, float, float)}, 2406 * this does not rotate the Glyph it produces. 2407 * @param startX the starting x position in cells 2408 * @param startY the starting y position in cells 2409 * @param endX the ending x position in cells 2410 * @param endY the ending y position in cells 2411 * @param shown the char to show (the same char throughout the effect) 2412 * @param startColor the starting Color 2413 * @param endColor the Color to transition to 2414 * @param duration the duration in seconds for the effect 2415 */ 2416 public void summon(int startX, int startY, int endX, int endY, char shown, 2417 final float startColor, final float endColor, float duration) 2418 { 2419 summon(0f, startX, startY, endX, endY, shown, startColor, endColor, duration, null); 2420 } 2421 /** 2422 * Create a new Glyph at (startX, startY) using the char shown with the given startColor, and after delay seconds, 2423 * starts changing color to endColor, changing position so it ends on the cell (endX, endY), taking duration seconds 2424 * to complete before running postRunnable (if it is non-null) and finally removing the Glyph. 2425 * <br> 2426 * Unlike {@link SquidPanel#summon(float, int, int, int, int, char, Color, Color, boolean, float, float, float)}, 2427 * this does not rotate the Glyph it produces. 2428 * @param delay how long to wait in seconds before starting the effect 2429 * @param startX the starting x position in cells 2430 * @param startY the starting y position in cells 2431 * @param endX the ending x position in cells 2432 * @param endY the ending y position in cells 2433 * @param shown the char to show (the same char throughout the effect) 2434 * @param startColor the starting Color 2435 * @param endColor the Color to transition to 2436 * @param duration the duration in seconds for the effect 2437 * @param postRunnable a Runnable to execute after the summon completes; may be null to do nothing. 2438 */ 2439 public void summon(float delay, int startX, int startY, int endX, int endY, char shown, 2440 final float startColor, final float endColor, float duration, 2441 /* @Nullable */ Runnable postRunnable) 2442 { 2443 duration = Math.max(0.015f, duration); 2444 final int nbActions = 2 + (0 < delay ? 1 : 0) + (postRunnable == null ? 0 : 1); 2445 int index = 0; 2446 final Action[] sequence = new Action[nbActions]; 2447 if (0 < delay) 2448 sequence[index++] = Actions.delay(delay); 2449 final TextCellFactory.Glyph glyph = glyph(shown, startColor, startX, startY); 2450 sequence[index++] = Actions.parallel( 2451 new TemporalAction(duration) { 2452 @Override 2453 protected void update(float percent) { 2454 glyph.setPackedColor(SColor.lerpFloatColors(startColor, endColor, percent * 0.95f)); 2455 } 2456 }, 2457 Actions.moveTo(worldX(endX), worldY(endY), duration)); 2458 if(postRunnable != null) 2459 { 2460 sequence[index++] = Actions.run(postRunnable); 2461 } 2462 /* Do this one last, so that hasActiveAnimations() returns true during 'postRunnables' */ 2463 sequence[index] = Actions.run(new Runnable() { 2464 @Override 2465 public void run() { 2466 glyphs.remove(glyph); 2467 } 2468 }); 2469 glyph.addAction(Actions.sequence(sequence)); 2470 } 2471 2472 /** 2473 * Create a new Glyph at (startX, startY) in world coordinates (often pixels on the screen) using the char shown 2474 * with the given startColor, and immediately starts changing color to endColor, changing position so it ends at the 2475 * world coordinates (endX, endY), taking duration seconds to complete before removing the Glyph. 2476 * <br> 2477 * Unlike {@link SquidPanel#summon(float, int, int, int, int, char, Color, Color, boolean, float, float, float)}, 2478 * this does not rotate the Glyph it produces. 2479 * @param startX the starting x position in world coordinates 2480 * @param startY the starting y position in world coordinates 2481 * @param endX the ending x position in world coordinates 2482 * @param endY the ending y position in world coordinates 2483 * @param shown the char to show (the same char throughout the effect) 2484 * @param startColor the starting Color 2485 * @param endColor the Color to transition to 2486 * @param duration the duration in seconds for the effect 2487 */ 2488 public void summon(float startX, float startY, float endX, float endY, char shown, 2489 final float startColor, final float endColor, float duration) 2490 { 2491 summon(0f, startX, startY, endX, endY, shown, startColor, endColor, duration, null); 2492 } 2493 /** 2494 * Create a new Glyph at (startX, startY) in world coordinates (often pixels on the screen) using the char shown 2495 * with the given startColor, and after delay seconds, starts changing color to endColor, changing position so it 2496 * ends at the world coordinates (endX, endY), taking duration seconds to complete before running postRunnable (if 2497 * it is non-null) and finally removing the Glyph. 2498 * <br> 2499 * Unlike {@link SquidPanel#summon(float, int, int, int, int, char, Color, Color, boolean, float, float, float)}, 2500 * this does not rotate the Glyph it produces. 2501 * @param delay how long to wait in seconds before starting the effect 2502 * @param startX the starting x position in world coordinates 2503 * @param startY the starting y position in world coordinates 2504 * @param endX the ending x position in world coordinates 2505 * @param endY the ending y position in world coordinates 2506 * @param shown the char to show (the same char throughout the effect) 2507 * @param startColor the starting Color 2508 * @param endColor the Color to transition to 2509 * @param duration the duration in seconds for the effect 2510 * @param postRunnable a Runnable to execute after the summon completes; may be null to do nothing. 2511 */ 2512 public void summon(float delay, float startX, float startY, float endX, float endY, char shown, 2513 final float startColor, final float endColor, float duration, 2514 /* @Nullable */ Runnable postRunnable) 2515 { 2516 duration = Math.max(0.015f, duration); 2517 final int nbActions = 2 + (0 < delay ? 1 : 0) + (postRunnable == null ? 0 : 1); 2518 int index = 0; 2519 final Action[] sequence = new Action[nbActions]; 2520 if (0 < delay) 2521 sequence[index++] = Actions.delay(delay); 2522 final TextCellFactory.Glyph glyph = glyph(shown, startColor, gridX(startX), gridY(startY)); 2523 sequence[index++] = Actions.parallel( 2524 new TemporalAction(duration) { 2525 @Override 2526 protected void update(float percent) { 2527 glyph.setPackedColor(SColor.lerpFloatColors(startColor, endColor, percent * 0.95f)); 2528 } 2529 }, 2530 Actions.moveTo(endX, endY, duration)); 2531 if(postRunnable != null) 2532 { 2533 sequence[index++] = Actions.run(postRunnable); 2534 } 2535 /* Do this one last, so that hasActiveAnimations() returns true during 'postRunnables' */ 2536 sequence[index] = Actions.run(new Runnable() { 2537 @Override 2538 public void run() { 2539 glyphs.remove(glyph); 2540 } 2541 }); 2542 glyph.addAction(Actions.sequence(sequence)); 2543 } 2544 2545 2546 /** 2547 * Convenience method to produce an explosion, splash, or burst effect. Calls 2548 * {@link #summon(int, int, int, int, char, float, float, float)} repeatedly with 2549 * different parameters. As with summon(), this creates temporary Glyphs that change color and position. 2550 * The distance is how many cells away to move the created Actors away from (x,y). The measurement determines 2551 * whether this produces Glyphs in 4 (cardinal) directions for DIAMOND or OCTAHEDRON, or 8 (cardinal and diagonal) 2552 * directions for any other enum value for Radius; CIRCLE and SPHERE will position the 8 glyphs in a circle, while 2553 * SQUARE and CUBE will position their 8 glyphs in a square. 2554 * <br> 2555 * Unlike {@link SquidPanel#burst(float, int, int, int, boolean, char, Color, Color, boolean, float, float)}, this 2556 * does not rotate the individual Glyphs it produces. 2557 * @param x the starting, center, x-position in cells to create all Actors at 2558 * @param y the starting, center, y-position in cells to create all Actors at 2559 * @param distance how far away, in cells, to move each actor from the center (Chebyshev distance, forming a square) 2560 * @param measurement a Radius enum that determines if 4 (DIAMOND, OCTAHEDRON) or 8 (anything else) Glyphs are 2561 * created, and the shape they will take 2562 * @param shown the char to use for Glyphs; should definitely be visible 2563 * @param startColor the encoded color to start the effect with 2564 * @param endColor the encoded color to end the effect on 2565 * @param duration how long, in seconds, the effect should last 2566 */ 2567 public void burst(int x, int y, int distance, Radius measurement, char shown, 2568 float startColor, float endColor, float duration) { 2569 burst(0f, x, y, distance, measurement, shown, startColor, endColor, duration, null); 2570 } 2571 /** 2572 * Convenience method to produce an explosion, splash, or burst effect. Calls 2573 * {@link #summon(float, int, int, int, int, char, float, float, float, Runnable)} repeatedly with 2574 * different parameters. As with summon(), this creates temporary Glyphs that change color and position. 2575 * The distance is how many cells away to move the created Actors away from (x,y). The measurement determines 2576 * whether this produces Glyphs in 4 (cardinal) directions for DIAMOND or OCTAHEDRON, or 8 (cardinal and diagonal) 2577 * directions for any other enum value for Radius; CIRCLE and SPHERE will position the 8 glyphs in a circle, while 2578 * SQUARE and CUBE will position their 8 glyphs in a square. This takes a delay in seconds that can be used to make 2579 * the effect wait to start for some amount of time, and a Runnable that will be run once after the burst's full 2580 * duration completes (not once per summoned Glyph). 2581 * <br> 2582 * Unlike {@link SquidPanel#burst(float, int, int, int, boolean, char, Color, Color, boolean, float, float)}, this 2583 * does not rotate the individual Glyphs it produces. 2584 * @param delay how long to wait in seconds before starting the effect 2585 * @param x the starting, center, x-position in cells to create all Actors at 2586 * @param y the starting, center, y-position in cells to create all Actors at 2587 * @param distance how far away, in cells, to move each actor from the center (Chebyshev distance, forming a square) 2588 * @param measurement a Radius enum that determines if 4 (DIAMOND, OCTAHEDRON) or 8 (anything else) Glyphs are 2589 * created, and the shape they will take 2590 * @param shown the char to use for Glyphs; should definitely be visible 2591 * @param startColor the encoded color to start the effect with 2592 * @param endColor the encoded color to end the effect on 2593 * @param duration how long, in seconds, the effect should last 2594 * @param postRunnable a Runnable to execute after the burst completes; may be null to do nothing, 2595 * and will only be run once for the whole burst effect. 2596 */ 2597 public void burst(float delay, int x, int y, int distance, Radius measurement, char shown, 2598 float startColor, float endColor, float duration, /* @Nullable */ Runnable postRunnable) 2599 { 2600 Direction d; 2601 switch (measurement) 2602 { 2603 case SQUARE: 2604 case CUBE: 2605 for (int i = 0; i < 7; i++) { 2606 d = Direction.CLOCKWISE[i]; 2607 summon(delay, x, y, x - d.deltaX * distance, y + d.deltaY * distance, 2608 shown, startColor, endColor, duration, null); 2609 } 2610 d = Direction.CLOCKWISE[7]; 2611 summon(delay, x, y, x - d.deltaX * distance, y + d.deltaY * distance, 2612 shown, startColor, endColor, duration, postRunnable); 2613 break; 2614 case CIRCLE: 2615 case SPHERE: 2616 float xf = worldX(x), yf = worldY(y); 2617 for (int i = 0; i < 4; i++) { 2618 d = Direction.DIAGONALS[i]; 2619 summon(delay, xf, yf, xf - d.deltaX * font.actualCellWidth * distance * 0.7071067811865475f, // the constant is 1.0 / Math.sqrt(2.0) 2620 yf + d.deltaY * font.actualCellHeight * distance * 0.7071067811865475f, 2621 shown, startColor, endColor, duration, null); 2622 } 2623 // break intentionally absent 2624 default: 2625 for (int i = 0; i < 3; i++) { 2626 d = Direction.CARDINALS_CLOCKWISE[i]; 2627 summon(delay, x, y, x - d.deltaX * distance, y + d.deltaY * distance, 2628 shown, startColor, endColor, duration, null); 2629 } 2630 d = Direction.CARDINALS_CLOCKWISE[3]; 2631 summon(delay, x, y, x - d.deltaX * distance, y + d.deltaY * distance, 2632 shown, startColor, endColor, duration, postRunnable); 2633 break; 2634 } 2635 } 2636 /** 2637 * Convenience method to produce an explosion, splash, or burst effect. Calls 2638 * {@link #summon(int, int, int, int, char, float, float, float)} repeatedly with 2639 * different parameters. As with summon(), this creates temporary Glyphs that change color and position. 2640 * The distance is how many cells away to move the created Actors away from (x,y). The measurement determines 2641 * whether this produces Glyphs in 4 (cardinal) directions for DIAMOND or OCTAHEDRON, or 8 (cardinal and diagonal) 2642 * directions for any other enum value for Radius; CIRCLE and SPHERE will position the 8 glyphs in a circle, while 2643 * SQUARE and CUBE will position their 8 glyphs in a square. 2644 * <br> 2645 * Unlike {@link SquidPanel#burst(float, int, int, int, boolean, char, Color, Color, boolean, float, float)}, this 2646 * does not rotate the individual Glyphs it produces. 2647 * @param x the starting, center, x-position in cells to create all Actors at 2648 * @param y the starting, center, y-position in cells to create all Actors at 2649 * @param distance how far away, in cells, to move each actor from the center (Chebyshev distance, forming a square) 2650 * @param measurement a Radius enum that determines if 4 (DIAMOND, OCTAHEDRON) or 8 (anything else) Glyphs are 2651 * created, and the shape they will take 2652 * @param choices a String or other CharSequence containing the chars this can randomly choose 2653 * @param startColor the encoded color to start the effect with 2654 * @param endColor the encoded color to end the effect on 2655 * @param duration how long, in seconds, the effect should last 2656 */ 2657 public void burst(int x, int y, int distance, Radius measurement, CharSequence choices, 2658 float startColor, float endColor, float duration) { 2659 burst(0f, x, y, distance, measurement, choices, startColor, endColor, duration, null); 2660 } 2661 /** 2662 * Convenience method to produce an explosion, splash, or burst effect. Calls 2663 * {@link #summon(int, int, int, int, char, float, float, float)} repeatedly with 2664 * different parameters. As with summon(), this creates temporary Glyphs that change color and position. 2665 * The distance is how many cells away to move the created Actors away from (x,y). The measurement determines 2666 * whether this produces Glyphs in 4 (cardinal) directions for DIAMOND or OCTAHEDRON, or 8 (cardinal and diagonal) 2667 * directions for any other enum value for Radius; CIRCLE and SPHERE will position the 8 glyphs in a circle, while 2668 * SQUARE and CUBE will position their 8 glyphs in a square. This takes a delay in seconds that can be used to make 2669 * the effect wait to start for some amount of time, and a Runnable that will be run once after the burst's full 2670 * duration completes (not once per summoned Glyph). 2671 * <br> 2672 * Unlike {@link SquidPanel#burst(float, int, int, int, boolean, char, Color, Color, boolean, float, float)}, this 2673 * does not rotate the individual Glyphs it produces. 2674 * @param delay how long to wait in seconds before starting the effect 2675 * @param x the starting, center, x-position in cells to create all Actors at 2676 * @param y the starting, center, y-position in cells to create all Actors at 2677 * @param distance how far away, in cells, to move each actor from the center (Chebyshev distance, forming a square) 2678 * @param measurement a Radius enum that determines if 4 (DIAMOND, OCTAHEDRON) or 8 (anything else) Glyphs are 2679 * created, and the shape they will take 2680 * @param choices a String or other CharSequence containing the chars this can randomly choose 2681 * @param startColor the encoded color to start the effect with 2682 * @param endColor the encoded color to end the effect on 2683 * @param duration how long, in seconds, the effect should last 2684 * @param postRunnable a Runnable to execute after the burst completes; may be null to do nothing, 2685 * and will only be run once for the whole burst effect. 2686 */ 2687 public void burst(float delay, int x, int y, int distance, Radius measurement, CharSequence choices, 2688 float startColor, float endColor, float duration, /* @Nullable */ Runnable postRunnable) 2689 { 2690 Direction d; 2691 final int len = choices.length(); 2692 final StatefulRNG rng = DefaultResources.getGuiRandom(); 2693 switch (measurement) 2694 { 2695 case SQUARE: 2696 case CUBE: 2697 for (int i = 0; i < 7; i++) { 2698 d = Direction.CLOCKWISE[i]; 2699 summon(delay, x, y, x - d.deltaX * distance, y + d.deltaY * distance, 2700 choices.charAt(rng.nextIntHasty(len)), startColor, endColor, duration, null); 2701 } 2702 d = Direction.CLOCKWISE[7]; 2703 summon(delay, x, y, x - d.deltaX * distance, y + d.deltaY * distance, 2704 choices.charAt(rng.nextIntHasty(len)), startColor, endColor, duration, postRunnable); 2705 2706 break; 2707 case CIRCLE: 2708 case SPHERE: 2709 float xf = worldX(x), yf = worldY(y); 2710 for (int i = 0; i < 4; i++) { 2711 d = Direction.DIAGONALS[i]; 2712 summon(delay, xf, yf, xf - d.deltaX * font.actualCellWidth * distance * 0.7071067811865475f, 2713 yf + d.deltaY * font.actualCellHeight * distance * 0.7071067811865475f, 2714 choices.charAt(rng.nextIntHasty(len)), startColor, endColor, duration, null); 2715 } 2716 // break intentionally absent 2717 default: 2718 for (int i = 0; i < 3; i++) { 2719 d = Direction.CARDINALS_CLOCKWISE[i]; 2720 summon(delay, x, y, x - d.deltaX * distance, y + d.deltaY * distance, 2721 choices.charAt(rng.nextIntHasty(len)), startColor, endColor, duration, null); 2722 } 2723 d = Direction.CARDINALS_CLOCKWISE[3]; 2724 summon(delay, x, y, x - d.deltaX * distance, y + d.deltaY * distance, 2725 choices.charAt(rng.nextIntHasty(len)), startColor, endColor, duration, postRunnable); 2726 2727 break; 2728 } 2729 } 2730 2731 /** 2732 * Changes the background at position x,y so it becomes the given color, taking duration seconds. The 2733 * background will keep the changed color after the effect, unless drawn over. 2734 * <br> 2735 * This will only behave correctly if you call {@link Stage#act()} before you call {@link Stage#draw()}, but after 2736 * any changes to the contents of this SparseLayers. If you change the contents, then draw, and then act, that will 2737 * draw the contents without the recolor this applies, then apply the recolor when you call act(), then quickly 2738 * overwrite the recolor in the next frame. That visually appears as nothing happening other than a delay. 2739 * @param x the x-coordinate of the cell to recolor 2740 * @param y the y-coordinate of the cell to recolor 2741 * @param color what to gradually change the cell's color to, as a Color object 2742 * @param duration how long the total transition should take in seconds 2743 */ 2744 public void recolor(final int x, final int y, final Color color, float duration) { 2745 recolor(0f, x, y, color.toFloatBits(), duration, null); 2746 } 2747 2748 /** 2749 * Changes the background at position x,y so it becomes the given encodedColor, taking duration seconds. The 2750 * background will keep the changed color after the effect, unless drawn over. 2751 * <br> 2752 * This will only behave correctly if you call {@link Stage#act()} before you call {@link Stage#draw()}, but after 2753 * any changes to the contents of this SparseLayers. If you change the contents, then draw, and then act, that will 2754 * draw the contents without the recolor this applies, then apply the recolor when you call act(), then quickly 2755 * overwrite the recolor in the next frame. That visually appears as nothing happening other than a delay. 2756 * @param x the x-coordinate of the cell to recolor 2757 * @param y the y-coordinate of the cell to recolor 2758 * @param encodedColor what to gradually change the cell's color to, as a packed float 2759 * @param duration how long the total transition should take in seconds 2760 */ 2761 public void recolor(final int x, final int y, final float encodedColor, float duration) { 2762 recolor(0f, x, y, encodedColor, duration, null); 2763 } 2764 2765 /** 2766 * Changes the background at position x,y so it becomes the given encodedColor, waiting for {@code delay} (in 2767 * seconds) before performing it, taking duration seconds. The background will keep the changed color after 2768 * the effect, unless drawn over. 2769 * <br> 2770 * This will only behave correctly if you call {@link Stage#act()} before you call {@link Stage#draw()}, but after 2771 * any changes to the contents of this SparseLayers. If you change the contents, then draw, and then act, that will 2772 * draw the contents without the recolor this applies, then apply the recolor when you call act(), then quickly 2773 * overwrite the recolor in the next frame. That visually appears as nothing happening other than a delay. 2774 * @param delay how long to wait in seconds before starting the effect 2775 * @param x the x-coordinate of the cell to recolor 2776 * @param y the y-coordinate of the cell to recolor 2777 * @param encodedColor what to gradually change the cell's color to, as a packed float 2778 * @param duration how long the total transition should take in seconds 2779 */ 2780 public void recolor(final float delay, final int x, final int y, final float encodedColor, float duration) { 2781 recolor(delay, x, y, encodedColor, duration, null); 2782 } 2783 /** 2784 * Changes the background at position x,y so it becomes the given encodedColor, waiting for {@code delay} (in 2785 * seconds) before performing it, taking duration seconds. The background will keep the changed color after the 2786 * effect, unless drawn over. Additionally, enqueue {@code postRunnable} for running after the created action ends. 2787 * <br> 2788 * This will only behave correctly if you call {@link Stage#act()} before you call {@link Stage#draw()}, but after 2789 * any changes to the contents of this SparseLayers. If you change the contents, then draw, and then act, that will 2790 * draw the contents without the recolor this applies, then apply the recolor when you call act(), then quickly 2791 * overwrite the recolor in the next frame. That visually appears as nothing happening other than a delay. 2792 * @param delay how long to wait in seconds before starting the effect 2793 * @param x the x-coordinate of the cell to recolor 2794 * @param y the y-coordinate of the cell to recolor 2795 * @param encodedColor what to gradually change the cell's color to, as a packed float 2796 * @param duration how long the total transition should take in seconds 2797 * @param postRunnable a Runnable to execute after the recolor completes; may be null to do nothing. 2798 */ 2799 public void recolor(final float delay, final int x, final int y, final float encodedColor, float duration, 2800 /* @Nullable */ Runnable postRunnable) { 2801 if(x < 0 || x >= gridWidth || y < 0 || y >= gridHeight) 2802 return; 2803 duration = Math.max(0.015f, duration); 2804 final float ac = backgrounds[x][y]; 2805 final int nbActions = 2 + (0 < delay ? 1 : 0) + (postRunnable == null ? 0 : 1); 2806 final Action[] sequence = new Action[nbActions]; 2807 int index = 0; 2808 if (0 < delay) 2809 sequence[index++] = Actions.delay(delay); 2810 sequence[index++] = new TemporalAction(duration) { 2811 @Override 2812 protected void update(float percent) { 2813 backgrounds[x][y] = SColor.lerpFloatColors(ac, encodedColor, percent); 2814 } 2815 }; 2816 if(postRunnable != null) 2817 { 2818 sequence[index++] = Actions.run(postRunnable); 2819 } 2820 /* Do this one last, so that hasActiveAnimations() returns true during 'postRunnables' */ 2821 sequence[index] = Actions.delay(duration, Actions.run(new Runnable() { 2822 @Override 2823 public void run() { 2824 backgrounds[x][y] = encodedColor; 2825 } 2826 })); 2827 2828 addAction(Actions.sequence(sequence)); 2829 } 2830 2831 2832 2833 2834 /** 2835 * Changes the foreground in the given layer at position x,y so it becomes the given color, taking duration 2836 * seconds. The foreground color will keep the changed color after the effect, unless drawn over. 2837 * <br> 2838 * This will only behave correctly if you call {@link Stage#act()} before you call {@link Stage#draw()}, but after 2839 * any changes to the contents of this SparseLayers. If you change the contents, then draw, and then act, that will 2840 * draw the contents without the recolor this applies, then apply the recolor when you call act(), then quickly 2841 * overwrite the recolor in the next frame. That visually appears as nothing happening other than a delay. 2842 * @param x the x-coordinate of the cell to recolor 2843 * @param y the y-coordinate of the cell to recolor 2844 * @param layer which layer to affect; if you haven't specified a layer when placing text, then this should be 0 2845 * @param color what to gradually change the cell's color to, as a Color object 2846 * @param duration how long the total transition should take in seconds 2847 */ 2848 public void recolor(final int x, final int y, final int layer, final Color color, float duration) { 2849 recolor(0f, x, y, layer, color.toFloatBits(), duration, null); 2850 } 2851 /** 2852 * Changes the foreground in the given layer at position x,y so it becomes the given encodedColor, taking duration 2853 * seconds. The foreground color will keep the changed color after the effect, unless drawn over. 2854 * <br> 2855 * This will only behave correctly if you call {@link Stage#act()} before you call {@link Stage#draw()}, but after 2856 * any changes to the contents of this SparseLayers. If you change the contents, then draw, and then act, that will 2857 * draw the contents without the recolor this applies, then apply the recolor when you call act(), then quickly 2858 * overwrite the recolor in the next frame. That visually appears as nothing happening other than a delay. 2859 * @param x the x-coordinate of the cell to recolor 2860 * @param y the y-coordinate of the cell to recolor 2861 * @param layer which layer to affect; if you haven't specified a layer when placing text, then this should be 0 2862 * @param encodedColor what to gradually change the cell's color to, as a packed float 2863 * @param duration how long the total transition should take in seconds 2864 */ 2865 public void recolor(final int x, final int y, final int layer, final float encodedColor, float duration) { 2866 recolor(0f, x, y, layer, encodedColor, duration, null); 2867 } 2868 2869 2870 /** 2871 * Changes the foreground in the given layer at position x,y so it becomes the given encodedColor, waiting for 2872 * {@code delay} (in seconds) before performing it, taking duration seconds. The foreground color will keep the 2873 * changed color after the effect, unless drawn over. 2874 * <br> 2875 * This will only behave correctly if you call {@link Stage#act()} before you call {@link Stage#draw()}, but after 2876 * any changes to the contents of this SparseLayers. If you change the contents, then draw, and then act, that will 2877 * draw the contents without the recolor this applies, then apply the recolor when you call act(), then quickly 2878 * overwrite the recolor in the next frame. That visually appears as nothing happening other than a delay. 2879 * @param delay how long to wait in seconds before starting the effect 2880 * @param x the x-coordinate of the cell to recolor 2881 * @param y the y-coordinate of the cell to recolor 2882 * @param layer which layer to affect; if you haven't specified a layer when placing text, then this should be 0 2883 * @param encodedColor what to gradually change the cell's color to, as a packed float 2884 * @param duration how long the total transition should take in seconds 2885 */ 2886 public void recolor(final float delay, final int x, final int y, final int layer, final float encodedColor, float duration) { 2887 recolor(delay, x, y, layer, encodedColor, duration, null); 2888 } 2889 2890 2891 /** 2892 * Changes the foreground in the given layer at position x,y so it becomes the given encodedColor, waiting for 2893 * {@code delay} (in seconds) before performing it, taking duration seconds. The foreground color will keep the 2894 * changed color after the effect, unless drawn over. Additionally, enqueue {@code postRunnable} for running after 2895 * the created action ends. 2896 * <br> 2897 * This will only behave correctly if you call {@link Stage#act()} before you call {@link Stage#draw()}, but after 2898 * any changes to the contents of this SparseLayers. If you change the contents, then draw, and then act, that will 2899 * draw the contents without the recolor this applies, then apply the recolor when you call act(), then quickly 2900 * overwrite the recolor in the next frame. That visually appears as nothing happening other than a delay. 2901 * @param delay how long to wait in seconds before starting the effect 2902 * @param x the x-coordinate of the cell to recolor 2903 * @param y the y-coordinate of the cell to recolor 2904 * @param layer which layer to affect; if you haven't specified a layer when placing text, then this should be 0 2905 * @param encodedColor what to gradually change the cell's color to, as a packed float 2906 * @param duration how long the total transition should take in seconds 2907 * @param postRunnable a Runnable to execute after the recolor completes; may be null to do nothing. 2908 */ 2909 public void recolor(final float delay, final int x, final int y, final int layer, final float encodedColor, float duration, 2910 /* @Nullable */ Runnable postRunnable) { 2911 if(x < 0 || x >= gridWidth || y < 0 || y >= gridHeight || layer < 0 || layer >= layers.size()) 2912 return; 2913 final SparseTextMap l = layers.get(layer); 2914 duration = Math.max(0.015f, duration); 2915 final int pos = SparseTextMap.encodePosition(x, y); 2916 final float ac = l.getFloat(pos, 0f); 2917 final int nbActions = 2 + (0 < delay ? 1 : 0) + (postRunnable == null ? 0 : 1); 2918 final Action[] sequence = new Action[nbActions]; 2919 int index = 0; 2920 if (0 < delay) 2921 sequence[index++] = Actions.delay(delay); 2922 sequence[index++] = new TemporalAction(duration) { 2923 @Override 2924 protected void update(float percent) { 2925 l.updateFloat(pos, SColor.lerpFloatColors(ac, encodedColor, percent)); 2926 } 2927 }; 2928 if(postRunnable != null) 2929 { 2930 sequence[index++] = Actions.run(postRunnable); 2931 } 2932 /* Do this one last, so that hasActiveAnimations() returns true during 'postRunnables' */ 2933 sequence[index] = Actions.delay(duration, Actions.run(new Runnable() { 2934 @Override 2935 public void run() { 2936 l.updateFloat(pos, encodedColor); 2937 } 2938 })); 2939 addAction(Actions.sequence(sequence)); 2940 } 2941 2942 /** 2943 * Draws the SparseLayers and all glyphs it tracks. {@link Batch#begin()} must have already been called on the 2944 * batch, and {@link Batch#end()} should be called after this returns and before the rendering code finishes for the 2945 * frame. 2946 * <br> 2947 * This will set the shader of {@code batch} if using a distance field or MSDF font and the shader is currently not 2948 * configured for such a font; it does not reset the shader to the default so that multiple Actors can all use the 2949 * same shader and so specific extra glyphs or other items can be rendered after calling draw(). If you need to draw 2950 * both a distance field font and full-color art, you should set the shader on the Batch to null when you want to 2951 * draw full-color art, and end the Batch between drawing this object and the other art. 2952 * 2953 * @param batch a Batch such as a {@link FilterBatch} that must be between a begin() and end() call; usually done by Stage 2954 * @param parentAlpha currently ignored 2955 */ 2956 @Override 2957 public void draw(Batch batch, float parentAlpha) { 2958 super.draw(batch, parentAlpha); 2959 float xo = getX(), yo = getY(), yOff = yo + 1f + gridHeight * font.actualCellHeight, gxo, gyo; 2960 font.draw(batch, backgrounds, xo, yo); 2961 int len = layers.size(); 2962 Frustum frustum = null; 2963 Stage stage = getStage(); 2964 if(stage != null) { 2965 Viewport viewport = stage.getViewport(); 2966 if(viewport != null) 2967 { 2968 Camera camera = viewport.getCamera(); 2969 if(camera != null) 2970 { 2971 if( 2972 camera.frustum != null && 2973 (!camera.frustum.boundsInFrustum(xo, yOff - font.actualCellHeight - 1f, 0f, font.actualCellWidth, font.actualCellHeight, 0f) || 2974 !camera.frustum.boundsInFrustum(xo + font.actualCellWidth * (gridWidth-1), yo, 0f, font.actualCellWidth, font.actualCellHeight, 0f)) 2975 ) 2976 frustum = camera.frustum; 2977 } 2978 } 2979 } 2980 font.configureShader(batch); 2981 if(frustum == null) { 2982 for (int i = 0; i < len; i++) { 2983 layers.get(i).draw(batch, font, xo, yOff); 2984 } 2985 2986 } 2987 else 2988 { 2989 for (int i = 0; i < len; i++) { 2990 layers.get(i).draw(batch, font, frustum, xo, yOff); 2991 } 2992 } 2993 2994 int x, y; 2995 for (int i = 0; i < glyphs.size(); i++) { 2996 TextCellFactory.Glyph glyph = glyphs.get(i); 2997 if(glyph == null) 2998 continue; 2999 glyph.act(Gdx.graphics.getDeltaTime()); 3000 if( 3001 !glyph.isVisible() || 3002 (x = Math.round((gxo = glyph.getX() - xo) / font.actualCellWidth)) < 0 || x >= gridWidth || 3003 (y = Math.round((gyo = glyph.getY() - yo) / -font.actualCellHeight + gridHeight)) < 0 || y >= gridHeight || 3004 backgrounds[x][y] == 0f || (frustum != null && !frustum.boundsInFrustum(gxo, gyo, 0f, font.actualCellWidth, font.actualCellHeight, 0f))) 3005 continue; 3006 glyph.draw(batch, 1f); 3007 } 3008 } 3009}