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.Stage;
011import com.badlogic.gdx.scenes.scene2d.actions.Actions;
012import com.badlogic.gdx.scenes.scene2d.actions.TemporalAction;
013import com.badlogic.gdx.utils.IntIntMap;
014import com.badlogic.gdx.utils.viewport.Viewport;
015import squidpony.IColorCenter;
016
017import java.util.ArrayList;
018
019/**
020 * A subclass of SparseLayers that acts almost the same, but uses 3x3 subcells of background color for every cell that
021 * may contain a char. Methods that affect the background sometimes specify positions in subcells, which allows
022 * affecting less than one cell with some visual effect. You will usually want to use a triple-size resistance and FOV
023 * map; you can get a resistance map that respects subcells with
024 * {@link squidpony.squidgrid.mapping.DungeonUtility#generateResistances3x3(char[][])} (this looks better if it is given
025 * a "line" dungeon as produced by {@link squidpony.squidgrid.mapping.DungeonUtility#hashesToLines(char[][], boolean)}),
026 * and then you can use that resistance map with normal FOV methods, just typically at triple vision range. Much of the
027 * time, it makes sense to run both that triple-range FOV, and a normal-range FOV with a resistance map calculated in
028 * cells; you can use the normal-range FOV for gameplay and AI calculations and the triple-range for visual display. 
029 * <br>
030 * Created by Tommy Ettinger on 8/28/2018.
031 */
032public class SubcellLayers extends SparseLayers {
033    public SubcellLayers(int gridWidth, int gridHeight)
034    {
035        this(gridWidth, gridHeight, 10, 16, DefaultResources.getStretchableFont());
036    }
037
038    public SubcellLayers(int gridWidth, int gridHeight, float cellWidth, float cellHeight)
039    {
040        this(gridWidth, gridHeight, cellWidth, cellHeight, DefaultResources.getStretchableFont());
041    }
042
043    public SubcellLayers(int gridWidth, int gridHeight, float cellWidth, float cellHeight, TextCellFactory font) {
044        this(gridWidth, gridHeight, cellWidth, cellHeight, font, 0f, 0f);
045    }
046
047    public SubcellLayers(int gridWidth, int gridHeight, float cellWidth, float cellHeight, TextCellFactory font, float xOffset, float yOffset) {
048        this.gridWidth = MathUtils.clamp(gridWidth, 1, 65535);
049        this.gridHeight = MathUtils.clamp(gridHeight, 1, 65535);
050        backgrounds = new float[this.gridWidth*3][this.gridHeight*3];
051        layers = new ArrayList<>(4);
052        if(font.initialized())
053            this.font = font;
054        else
055            this.font = font.width(cellWidth).height(cellHeight).initBySize();
056        layers.add(new SparseTextMap(gridWidth * gridHeight >> 2));
057        mapping = new IntIntMap(4);
058        mapping.put(0, 0);
059        glyphs = new ArrayList<>(16);
060        scc = DefaultResources.getSCC();
061        setBounds(xOffset, yOffset,
062                this.font.actualCellWidth * this.gridWidth, this.font.actualCellHeight * this.gridHeight);
063    }
064
065    /**
066     * Places the given char 2D array, if-non-null, in the default foreground color starting at x=0, y=0, while also
067     * setting the background colors to match the given Color 2D array. The colors 2D array should have a width that is
068     * 3 * {@link #gridWidth} and a height that is 3 * {@link #gridHeight}. If the colors argument is null, does not
069     * affect backgrounds but may still affect chars. If the chars argument is null, only affects the background colors.
070     * This will filter each Color in colors if the color center this uses has a filter.
071     *
072     * @param chars  Can be {@code null}, indicating that only colors must be put.
073     * @param colors the background colors for the given chars; this array should have 3 times the width and height of chars
074     */
075    @Override
076    public void put(char[][] chars, Color[][] colors) {
077        super.putChars(chars);
078        super.put(null, colors);
079    }
080
081    /**
082     * Places the given char 2D array, if-non-null, in the default foreground color starting at x=0, y=0, while also
083     * setting the background colors to match the given 2D array of colors as packed floats. The colors 2D array should
084     * have a width that is 3 * {@link #gridWidth} and a height that is 3 * {@link #gridHeight}. If the colors argument 
085     * is null, does not affect backgrounds but may still affect chars. If the chars argument is null, only affects the
086     * background colors. This will not filter the passed colors at all.
087     *
088     * @param chars  Can be {@code null}, indicating that only colors must be put.
089     * @param colors the background colors for the given chars; this array should have 3 times the width and height of chars
090     */
091    @Override
092    public void put(char[][] chars, float[][] colors) {
093        super.putChars(chars);
094        super.put(null, colors);
095    }
096
097    /**
098     * Places the given char 2D array, if-non-null, with the given foreground colors in the first Color 2D array,
099     * starting at x=0, y=0, while also setting the background colors to match the second Color 2D array. If the
100     * bgColors argument is null, only affects foreground chars and colors. If the chars argument or the fgColors
101     * argument is null, only affects the background colors. Any positions where a Color in fgColors is null will not
102     * have a char placed (this can be used to restrict what is placed). This will filter each Color in the background
103     * and foreground if the color center this uses has a filter.
104     *
105     * @param chars    Can be {@code null}, indicating that only colors must be put.
106     * @param fgColors the foreground Colors for the given chars
107     * @param bgColors the background Colors for the given chars; this array should have 3 times the width and height of chars
108     */
109    @Override
110    public void put(char[][] chars, Color[][] fgColors, Color[][] bgColors) {
111        super.putChars(chars, fgColors);
112        super.put(null, bgColors);
113    }
114
115    /**
116     * Places the given char 2D array, if-non-null, with the given foreground colors in the first float 2D array,
117     * starting at x=0, y=0, while also setting the background colors to match the second float 2D array. If the
118     * bgColors argument is null, only affects foreground chars and colors. If the chars argument or the fgColors
119     * argument is null, only affects the background colors. Any positions where a float in fgColors is 0 will not
120     * have a char placed (this can be used to restrict what is placed). This will not filter any colors.
121     *
122     * @param chars    Can be {@code null}, indicating that only colors must be put.
123     * @param fgColors the foreground colors for the given chars, as packed floats
124     * @param bgColors the background colors for the given chars, as packed floats; this array should have 3 times the width and height of chars
125     */
126    @Override
127    public void put(char[][] chars, float[][] fgColors, float[][] bgColors) {
128        super.putChars(chars, fgColors);
129        super.put(null, bgColors);
130    }
131
132    /**
133     * Puts the char c at the position x,y with the given foreground and background colors. If foreground is null or is
134     * fully transparent (all channels 0), then this does not change the foreground contents.
135     * If background is null or is fully transparent, this does not change the background. Uses the color center to
136     * potentially filter the given colors; this can be changed with {@link #setScc(IColorCenter)}.
137     *
138     * @param x          the x position to place the char at
139     * @param y          the y position to place the char at
140     * @param c          the char to place
141     * @param foreground the color to use for c; if null or fully transparent, nothing will change in the foreground
142     * @param background the color to use for the cell; if null, nothing will change in the background
143     */
144    @Override
145    public void put(int x, int y, char c, Color foreground, Color background) {
146        super.put(x, y, c, foreground, null);
147        if(background != null) 
148            put(x, y, background);
149    }
150
151    /**
152     * Puts the char c at the position x,y with the given foreground and background colors as encoded floats, such as
153     * those produced by {@link Color#toFloatBits()}. If foreground is 0f, then this does not
154     * change the foreground contents. If background is 0f, this does not change the background. Does not filter the
155     * given colors.
156     *
157     * @param x          the x position to place the char at
158     * @param y          the y position to place the char at
159     * @param c          the char to place
160     * @param foreground the color to use for c; if 0f, nothing will change in the foreground
161     * @param background the color to use for the cell; if null or fully transparent, nothing will change in the background
162     */
163    @Override
164    public void put(int x, int y, char c, float foreground, float background) {
165        super.put(x, y, c, foreground, 0f);
166        if(background != 0f)
167            put(x, y, background);
168    }
169
170    /**
171     * Puts the char c at the position x,y in the requested layer with the given foreground and background colors. If
172     * foreground is null or is fully transparent (all channels 0), then this does not change
173     * the foreground contents. If background is null or is fully transparent, this does not change the background. Uses
174     * the color center to potentially filter the given colors; this can be changed with
175     * {@link #setColorCenter(IColorCenter)}. The layer can be greater than the number of layers currently present,
176     * which will add a layer to be rendered over the existing layers, but the number that refers to that layer will not
177     * change. It is recommended that to add a layer, you only add at the value equal to {@link #getLayerCount()}, which
178     * will maintain the order and layer numbers in a sane way.
179     *
180     * @param x          the x position to place the char at
181     * @param y          the y position to place the char at
182     * @param c          the char to place
183     * @param foreground the color to use for c; if null or fully transparent, nothing will change in the foreground
184     * @param background the color to use for the cell; if null or fully transparent, nothing will change in the background
185     * @param layer      the layer to place the colorful char into; should usually be between 0 and {@link #getLayerCount()} inclusive
186     */
187    @Override
188    public void put(int x, int y, char c, Color foreground, Color background, int layer) {
189        super.put(x, y, c, foreground, null, layer);
190        if(background != null)
191            put(x, y, background);
192    }
193
194    /**
195     * Puts the char c at the position x,y in the requested layer with the given foreground and background colors as
196     * encoded floats, such as those produced by {@link Color#toFloatBits()}. If foreground is 0f, then this does not
197     * change the foreground contents. If background is 0f, this does not change the
198     * background. Does not filter the given colors. The layer can be greater than the number of layers currently
199     * present, which will add a layer to be rendered over the existing layers, but the number that refers to that layer
200     * will not change. It is recommended that to add a layer, you only add at the value equal to
201     * {@link #getLayerCount()}, which will maintain the order and layer numbers in a sane way.
202     *
203     * @param x          the x position to place the char at
204     * @param y          the y position to place the char at
205     * @param c          the char to place
206     * @param foreground the color to use for c; if 0f, nothing will change in the foreground
207     * @param background the color to use for the cell; if null or fully transparent, nothing will change in the background
208     * @param layer      the layer to place the colorful char into; should usually be between 0 and {@link #getLayerCount()} inclusive
209     */
210    @Override
211    public void put(int x, int y, char c, float foreground, float background, int layer) {
212        super.put(x, y, c, foreground, 0f, layer);
213        if(background != 0f)
214            put(x, y, background);
215
216    }
217
218    /**
219     * Puts text at the position x,y with the given foreground and background colors. If foreground is null or is
220     * fully transparent (all channels 0), then this does not change the foreground contents.
221     * If background is null or is fully transparent, this does not change the background. Uses the color center to
222     * potentially filter the given colors; this can be changed with {@link #setColorCenter(IColorCenter)}.
223     *
224     * @param x          the x position to place the String at
225     * @param y          the y position to place the String at
226     * @param text       the String to place
227     * @param foreground the color to use for text; if null or fully transparent, nothing will change in the foreground
228     * @param background the color to use for the cells; if null or fully transparent, nothing will change in the background
229     */
230    @Override
231    public void put(int x, int y, String text, Color foreground, Color background) {
232        super.put(x, y, text, foreground, null);
233        if(background != null && text != null)
234        {
235            for (int i = 0; i < text.length(); i++) {
236                put(x+i, y, background);
237            }
238        }
239    }
240
241    /**
242     * Puts text at the position x,y with the given foreground and background colors as encoded floats, such as
243     * those produced by {@link Color#toFloatBits()}. If foreground is 0f, then this does not change the foreground
244     * contents. If background is 0f, this does not change the background. Does not filter the given colors.
245     *
246     * @param x          the x position to place the String at
247     * @param y          the y position to place the String at
248     * @param text       the String to place
249     * @param foreground the color to use for text; if 0f, nothing will change in the foreground
250     * @param background the color to use for the cells; if null or fully transparent, nothing will change in the background
251     */
252    @Override
253    public void put(int x, int y, String text, float foreground, float background) {
254        super.put(x, y, text, foreground, 0f);
255        if(background != 0f && text != null)
256        {
257            for (int i = 0; i < text.length(); i++) {
258                put(x+i, y, background);
259            }
260        }
261    }
262
263    /**
264     * Puts text at the position x,y in the requested layer with the given foreground and background colors. If
265     * foreground is null or is fully transparent (all channels 0), then this does not change the foreground contents.
266     * If background is null or is fully transparent, this does not change the background. Uses the color center to
267     * potentially filter the given colors; this can be changed with {@link #setColorCenter(IColorCenter)}. The layer
268     * can be greater than the number of layers currently present, which will add a layer to be rendered over the
269     * existing layers, but the number that refers to that layer will not change. It is recommended that to add a layer,
270     * you only add at the value equal to {@link #getLayerCount()}, which will maintain the order and layer numbers in a
271     * sane way.
272     *
273     * @param x          the x position to place the String at
274     * @param y          the y position to place the String at
275     * @param text       the String to place
276     * @param foreground the color to use for text; if null or fully transparent, nothing will change in the foreground
277     * @param background the color to use for the cells; if null or fully transparent, nothing will change in the background
278     * @param layer      the layer to place the colorful char into; should usually be between 0 and {@link #getLayerCount()} inclusive
279     */
280    @Override
281    public void put(int x, int y, String text, Color foreground, Color background, int layer) {
282        super.put(x, y, text, foreground, null, layer);
283        if(background != null && text != null)
284        {
285            for (int i = 0; i < text.length(); i++) {
286                put(x+i, y, background);
287            }
288        }
289    }
290
291    /**
292     * Puts text at the position x,y in the requested layer with the given foreground and background colors as encoded
293     * floats, such as those produced by {@link Color#toFloatBits()}. If foreground is 0f, then this does not change the
294     * foreground contents. If background is 0f, this does not change the background. Does not filter the given colors.
295     * The layer can be greater than the number of layers currently present, which will add a layer to be rendered over
296     * the existing layers, but the number that refers to that layer will not change. It is recommended that to add a
297     * layer, you only add at the value equal to {@link #getLayerCount()}, which will maintain the order and layer
298     * numbers in a sane way.
299     *
300     * @param x          the x position to place the String at
301     * @param y          the y position to place the String at
302     * @param text       the String to place
303     * @param foreground the color to use for text; if 0f, nothing will change in the foreground
304     * @param background the color to use for the cells; if null or fully transparent, nothing will change in the background
305     * @param layer      the layer to place the colorful char into; should usually be between 0 and {@link #getLayerCount()} inclusive
306     */
307    @Override
308    public void put(int x, int y, String text, float foreground, float background, int layer) {
309        super.put(x, y, text, foreground, 0f, 0);
310        if(background != 0f && text != null)
311        {
312            for (int i = 0; i < text.length(); i++) {
313                put(x+i, y, background);
314            }
315        }
316    }
317
318    /**
319     * Changes the background at position x,y to the given Color. If the color is null, then this will make the
320     * background fully transparent at the specified position.
321     *
322     * @param x     where to change the background color, x-coordinate
323     * @param y     where to change the background color, y-coordinate
324     * @param color the Color to change to; if null will be considered fully transparent
325     */
326    @Override
327    public void put(int x, int y, Color color) {
328        final float bg = (color == null) ? 0x0.0p0F : scc.filter(color).toFloatBits();
329        x *= 3;
330        y *= 3;
331        backgrounds[x  ][y  ] = bg;
332        backgrounds[x  ][y+1] = bg;
333        backgrounds[x  ][y+2] = bg;
334        backgrounds[x+1][y  ] = bg;
335        backgrounds[x+1][y+1] = bg;
336        backgrounds[x+1][y+2] = bg;
337        backgrounds[x+2][y  ] = bg;
338        backgrounds[x+2][y+1] = bg;
339        backgrounds[x+2][y+2] = bg;
340    }
341
342    /**
343     * Changes the background at position x,y to the given color as an encoded float. The color can be transparent,
344     * which will show through to whatever is behind this SparseLayers, or the color the screen was last cleared with.
345     * Unlike other methods in this class, a float equal to 0f will be used instead of being used to skip over a cell,
346     * and will change the background at the given position to fully transparent.
347     *
348     * @param x     where to change the background color, x-coordinate
349     * @param y     where to change the background color, y-coordinate
350     * @param bg    the color, as an encoded float, to change to; may be transparent, and considers 0f a valid color
351     */
352    @Override
353    public void put(int x, int y, float bg) {
354        x *= 3;
355        y *= 3;
356        backgrounds[x  ][y  ] = bg;
357        backgrounds[x  ][y+1] = bg;
358        backgrounds[x  ][y+2] = bg;
359        backgrounds[x+1][y  ] = bg;
360        backgrounds[x+1][y+1] = bg;
361        backgrounds[x+1][y+2] = bg;
362        backgrounds[x+2][y  ] = bg;
363        backgrounds[x+2][y+1] = bg;
364        backgrounds[x+2][y+2] = bg;
365    }
366
367    /**
368     * A convenience method that handles blending the background color with a specified light color, by a specific
369     * amount, without putting a char on the screen; as a whole this affects one x,y position.
370     *
371     * @param x           the x position, in cells
372     * @param y           the y position, in cells
373     * @param background  the "base" color to use for the background, which will be combined with lightColor, as a libGDX Color
374     * @param lightColor  the color to mix with the background, as a libGDX Color
375     * @param lightAmount a float that determines how much lightColor should affect background by; not strictly limited
376     */
377    @Override
378    public void putWithLight(int x, int y, Color background, Color lightColor, float lightAmount) {
379        putWithLight(x, y, background == null ? 0f :  background.toFloatBits(),
380                lightColor == null ? 0f : lightColor.toFloatBits(), lightAmount);
381    }
382
383    /**
384     * A convenience method that handles blending the background color with a specified light color, by a specific
385     * amount, without putting a char on the screen; as a whole this affects one x,y position.
386     *
387     * @param x           the x position, in subcells (3 subcells in a row have the width of one character cell)
388     * @param y           the y position, in subcells (3 subcells in a column have the height of one character cell)
389     * @param background  the "base" color to use for the background, which will be combined with lightColor, as a packed float
390     * @param lightColor  the color to mix with the background, as a packed float
391     * @param lightAmount a float that determines how much lightColor should affect background by; not strictly limited
392     */
393    @Override
394    public void putWithLight(int x, int y, float background, float lightColor, float lightAmount) {         
395        putSingle(x, y,
396                SColor.lerpFloatColors(background, lightColor,
397                        MathUtils.clamp(0xAAp-9f + (0xC8p-9f * lightAmount), 0f, 1f)) 
398                        );
399
400    }
401
402    /**
403     * A convenience method that handles blending the background color with a specified light color, by a specific
404     * amount, without putting a char on the screen; as a whole this affects one x,y position. This will use the same
405     * brightness for all cells given identical lightAmount values when this is called; this differentiates it from
406     * the putWithLight methods that take a Noise3D argument, which would light "splotches" of map with
407     * brighter or darker color. Instead, if lightAmount is obtained via SquidLib's {@code FOV} class, then all cells
408     * at a short distance from an FOV center will be lit brightly and cells far away will flicker in and out of view.
409     *
410     * @param x            the x position, in cells
411     * @param y            the y position, in cells
412     * @param background   the "base" color to use for the background, which will be combined with lightColor, as a packed float color
413     * @param lightColor   the color to mix with the background, as a packed float color
414     * @param lightAmount  a float that determines how much lightColor should affect background by; not strictly limited
415     *                     to between 0 and 1, and negative values can be given to favor background more
416     * @param flickerSpeed a small float multiplier applied to the time in milliseconds; often between 0.0005f and 0.005f
417     */
418    @Override
419    public void putWithConsistentLight(int x, int y, float background, float lightColor, float lightAmount, float flickerSpeed) {
420        super.putWithConsistentLight(x, y, background, lightColor, lightAmount, flickerSpeed);
421    }
422
423    /**
424     * A convenience method that handles blending the background color with a specified light color, by a specific
425     * amount, without putting a char on the screen; as a whole this affects one x,y position. This will use the same
426     * brightness for all cells given identical lightAmount values when this is called; this differentiates it from
427     * the putWithLight methods that take a Noise3D argument, which would light "splotches" of map with
428     * brighter or darker color. Instead, if lightAmount is obtained via SquidLib's {@code FOV} class, then all cells
429     * at a short distance from an FOV center will be lit brightly and cells far away will flicker in and out of view.
430     *
431     * @param x            the x position, in cells
432     * @param y            the y position, in cells
433     * @param background   the "base" color to use for the background, which will be combined with lightColor, as a libGDX Color
434     * @param lightColor   the color to mix with the background, as a libGDX Color
435     * @param lightAmount  a float that determines how much lightColor should affect background by; not strictly limited
436     *                     to between 0 and 1, and negative values can be given to favor background more
437     * @param flickerSpeed a small float multiplier applied to the time in milliseconds; often between 0.0005f and 0.005f
438     */
439    @Override
440    public void putWithConsistentLight(int x, int y, Color background, Color lightColor, float lightAmount, float flickerSpeed) {
441        super.putWithConsistentLight(x, y, background, lightColor, lightAmount, flickerSpeed);
442    }
443
444    /**
445     * Sets a single subcell of the background to use the specified color as a packed float. The values given for x and
446     * y refer to a triple-width, triple-height grid, so the meaning of x and y are different here from other methods.
447     * If you want to set the center subcell of the cell that would normally be set with
448     * {@link #put(int, int, char, float, float)} at the position x=20,y=30, this would set that subcell at
449     * x=20*3+1,y=30*3+1 , where the *3 gets the x and y to refer to the correct 3x3 subcell grid, and the +1 chooses
450     * the subcell as one to the right and one down.
451     * @param x the x position in the triple-width, triple-height grid of background subcells
452     * @param y the y position in the triple-width, triple-height grid of background subcells
453     * @param color a packed float color to place in the background of one subcell
454     */
455    public void putSingle(int x, int y, float color)
456    {
457        backgrounds[x][y] = color;
458    }
459
460    /**
461     * Sets the background colors to match the given Color 2D array. The colors 2D array should have a width that is 3 *
462     * {@link #gridWidth} and a height that is 3 * {@link #gridHeight}. If the colors argument is null, does nothing.
463     * This will filter each Color in colors if the color center this uses has a filter.
464     *
465     * @param colors the background colors for the given chars
466     */
467    @Override
468    public void put(Color[][] colors) {
469        super.put(colors);
470    }
471
472    /**
473     * Sets the background colors to match the given 2D array of colors as packed floats. The colors 2D array should
474     * have a width that is 3 * {@link #gridWidth} and a height that is 3 * {@link #gridHeight}. If the colors argument
475     * is null, does nothing. This will not filter the passed colors at all.
476     *
477     * @param colors the background colors to use for this SparseLayers
478     */
479    @Override
480    public void put(float[][] colors) {
481        super.put(colors);
482    }
483
484    /**
485     * Removes the foreground chars, where present, in all layers at the given x,y position.
486     * The backgrounds will be unchanged.
487     *
488     * @param x the x-coordinate of the position to remove all chars from
489     * @param y the y-coordinate of the position to remove all chars from
490     */
491    @Override
492    public void clear(int x, int y) {
493        if(x < 0 || x >= gridWidth || y < 0 || y >= gridHeight)
494            return;
495        int code = SparseTextMap.encodePosition(x, y);
496        for (int i = 0; i < layers.size(); i++) {
497            layers.get(i).remove(code);
498        }
499        x *= 3;
500        y *= 3;
501        backgrounds[x  ][y  ] = 0f;
502        backgrounds[x  ][y+1] = 0f;
503        backgrounds[x  ][y+2] = 0f;
504        backgrounds[x+1][y  ] = 0f;
505        backgrounds[x+1][y+1] = 0f;
506        backgrounds[x+1][y+2] = 0f;
507        backgrounds[x+2][y  ] = 0f;
508        backgrounds[x+2][y+1] = 0f;
509        backgrounds[x+2][y+2] = 0f;
510
511    }
512
513    /**
514     * Changes the background color in an area to all have the given color, as a libGDX Color (or SColor, etc.).
515     * This uses subcell measurements for x, y, width, and height, where the grid width in subcells is
516     * 3 * {@link #gridWidth} and the grid height in subcells is 3 * {@link #gridHeight}. To affect a full cell, this
517     * needs a width and height of at least 3 each; anything less will affect only some subcells.
518     *
519     * @param color  a libGDX Color to fill the area with; may be null to make the background transparent
520     * @param x      left edge's x coordinate, in subcells (3 subcells to one cell horizontally)
521     * @param y      top edge's y coordinate, in subcells (3 subcells to one cell vertically)
522     * @param width  the width of the area to change the color on, in subcells (3 subcells to one cell horizontally)
523     * @param height the height of the area to change the color on, in subcells (3 subcells to one cell vertically)
524     */
525    @Override
526    public void fillArea(Color color, int x, int y, int width, int height) {
527        fillArea(color == null ? 0f : scc.filter(color).toFloatBits(), x, y, width, height);
528    }
529    /**
530     * Changes the background color in an area to all have the given color, as a packed float.
531     * This uses subcell measurements for x, y, width, and height, where the grid width in subcells is
532     * 3 * {@link #gridWidth} and the grid height in subcells is 3 * {@link #gridHeight}. To affect a full cell, this
533     * needs a width and height of at least 3 each; anything less will affect only some subcells.
534     * 
535     * @param color a color as a packed float to fill the area with; may be 0f to make the background transparent
536     * @param x      left edge's x coordinate, in subcells (3 subcells to one cell horizontally)
537     * @param y      top edge's y coordinate, in subcells (3 subcells to one cell vertically)
538     * @param width  the width of the area to change the color on, in subcells (3 subcells to one cell horizontally)
539     * @param height the height of the area to change the color on, in subcells (3 subcells to one cell vertically)
540     */
541    public void fillArea(float color, int x, int y, int width, int height) {
542        if (x < 0) {
543            width += x;
544            x = 0;
545        }
546        if (y < 0) {
547            height += y;
548            y = 0;
549        }
550        if (width <= 0 || height <= 0)
551            return;
552        final int gw = gridWidth * 3, gh = gridHeight * 3;
553        for (int i = 0, xx = x; i < width && xx < gw; i++, xx++) {
554            for (int j = 0, yy = y; j < height && yy < gh; j++, yy++) {
555                backgrounds[xx][yy] = color;
556            }
557        }
558    }
559
560    /**
561     * Using the existing background color at the subcell position x,y, this performs color blending from that existing
562     * color to the given color (as a float), using the mixBy parameter to determine how much of the color parameter to
563     * use (1f will set the color in this to the parameter, while 0f for mixBy will ignore the color parameter
564     * entirely). The x and y parameters are in subcells, where 3 subcells have the same width or height as one cell (a
565     * cell holds a char, and 3x3 subcells fit in the same area).
566     * @param x the x component of the position in this panel to draw the starting color from
567     * @param y the y component of the position in this panel to draw the starting color from
568     * @param color the new color to mix with the starting color; a packed float, as made by {@link Color#toFloatBits()}
569     * @param mixBy the amount by which the new color will affect the old one, between 0 (no effect) and 1 (overwrite)
570     */
571    @Override
572    public void blend(int x, int y, float color, float mixBy)
573    {
574        backgrounds[x][y] = SColor.lerpFloatColorsBlended(backgrounds[x][y], color, mixBy);
575    }
576
577    /**
578     * Tints the background at position x,y (in cells) so it becomes the given encodedColor, waiting for {@code delay}
579     * (in seconds) before performing it, then after the tint is complete it returns the cell to its original color,
580     * taking duration seconds. Additionally, enqueue {@code postRunnable} for running after the created action ends.
581     * All subcells in the tinted cell will reach the same color during this animation, but the subcells can start with
582     * different colors, and they will return to those starting colors after this animation finishes.
583     * <br>
584     * This will only behave correctly if you call {@link Stage#act()} before you call {@link Stage#draw()}, but after
585     * any changes to the contents of this SparseLayers. If you change the contents, then draw, and then act, that will
586     * draw the contents without the tint this applies, then apply the tint when you call act(), then quickly overwrite
587     * the tint in the next frame. That visually appears as nothing happening other than a delay.
588     * @param delay how long to wait in seconds before starting the effect
589     * @param x the x-coordinate of the cell to tint
590     * @param y the y-coordinate of the cell to tint
591     * @param encodedColor what to transition the cell's color towards, and then transition back from, as a packed float
592     * @param duration how long the total "round-trip" transition should take in seconds
593     * @param postRunnable a Runnable to execute after the tint completes; may be null to do nothing.
594     */
595    public void tint(final float delay, final int x, final int y, final float encodedColor, float duration,
596            /* @Nullable */ Runnable postRunnable) {
597        if(x < 0 || x >= gridWidth || y < 0 || y >= gridHeight)
598            return;
599        duration = Math.max(0.015f, duration);
600        final int xx = x * 3, yy = y * 3;
601        final float
602                x0y0 = backgrounds[xx][yy], x1y0 = backgrounds[xx+1][yy], x2y0 = backgrounds[xx+2][yy],
603                x0y1 = backgrounds[xx][yy+1], x1y1 = backgrounds[xx+1][yy+1], x2y1 = backgrounds[xx+2][yy+1],
604                x0y2 = backgrounds[xx][yy+2], x1y2 = backgrounds[xx+1][yy+2], x2y2 = backgrounds[xx+2][yy+2];
605        final int nbActions = 3 + (0 < delay ? 1 : 0) + (postRunnable == null ? 0 : 1);
606        final Action[] sequence = new Action[nbActions];
607        int index = 0;
608        if (0 < delay)
609            sequence[index++] = Actions.delay(delay);
610        sequence[index++] = new TemporalAction(duration * 0.3f) {
611            @Override
612            protected void update(float percent) {
613                backgrounds[xx  ][yy  ] = SColor.lerpFloatColors(x0y0, encodedColor, percent);
614                backgrounds[xx  ][yy+1] = SColor.lerpFloatColors(x0y1, encodedColor, percent);
615                backgrounds[xx  ][yy+2] = SColor.lerpFloatColors(x0y2, encodedColor, percent);
616                backgrounds[xx+1][yy  ] = SColor.lerpFloatColors(x1y0, encodedColor, percent);
617                backgrounds[xx+1][yy+1] = SColor.lerpFloatColors(x1y1, encodedColor, percent);
618                backgrounds[xx+1][yy+2] = SColor.lerpFloatColors(x1y2, encodedColor, percent);
619                backgrounds[xx+2][yy  ] = SColor.lerpFloatColors(x2y0, encodedColor, percent);
620                backgrounds[xx+2][yy+1] = SColor.lerpFloatColors(x2y1, encodedColor, percent);
621                backgrounds[xx+2][yy+2] = SColor.lerpFloatColors(x2y2, encodedColor, percent);
622            }
623        };
624        sequence[index++] = new TemporalAction(duration * 0.7f) {
625            @Override
626            protected void update(float percent) {
627                backgrounds[xx  ][yy  ] = SColor.lerpFloatColors(encodedColor, x0y0, percent);
628                backgrounds[xx  ][yy+1] = SColor.lerpFloatColors(encodedColor, x0y1, percent);
629                backgrounds[xx  ][yy+2] = SColor.lerpFloatColors(encodedColor, x0y2, percent);
630                backgrounds[xx+1][yy  ] = SColor.lerpFloatColors(encodedColor, x1y0, percent);
631                backgrounds[xx+1][yy+1] = SColor.lerpFloatColors(encodedColor, x1y1, percent);
632                backgrounds[xx+1][yy+2] = SColor.lerpFloatColors(encodedColor, x1y2, percent);
633                backgrounds[xx+2][yy  ] = SColor.lerpFloatColors(encodedColor, x2y0, percent);
634                backgrounds[xx+2][yy+1] = SColor.lerpFloatColors(encodedColor, x2y1, percent);
635                backgrounds[xx+2][yy+2] = SColor.lerpFloatColors(encodedColor, x2y2, percent);
636            }
637        };
638        if(postRunnable != null)
639        {
640            sequence[index++] = Actions.run(postRunnable);
641        }
642        sequence[index] = Actions.delay(duration, Actions.run(new Runnable() {
643            @Override
644            public void run() {
645                backgrounds[xx  ][yy  ] = x0y0;
646                backgrounds[xx  ][yy+1] = x0y1;
647                backgrounds[xx  ][yy+2] = x0y2;
648                backgrounds[xx+1][yy  ] = x1y0;
649                backgrounds[xx+1][yy+1] = x1y1;
650                backgrounds[xx+1][yy+2] = x1y2;
651                backgrounds[xx+2][yy  ] = x2y0;
652                backgrounds[xx+2][yy+1] = x2y1;
653                backgrounds[xx+2][yy+2] = x2y2;
654            }
655        }));
656
657        addAction(Actions.sequence(sequence));
658    }
659    /**
660     * Draws the SubcellLayers and all glyphs it tracks. {@link Batch#begin()} must have already been called on the
661     * batch, and {@link Batch#end()} should be called after this returns and before the rendering code finishes for the
662     * frame.
663     * <br>
664     * This will set the shader of {@code batch} if using a distance field or MSDF font and the shader is currently not
665     * configured for such a font; it does not reset the shader to the default so that multiple Actors can all use the
666     * same shader and so specific extra glyphs or other items can be rendered after calling draw(). If you need to draw
667     * both a distance field font and full-color art, you should set the shader on the Batch to null when you want to
668     * draw full-color art, and end the Batch between drawing this object and the other art.
669     *
670     * @param batch a Batch such as a {@link FilterBatch} that must be between a begin() and end() call; usually done by Stage
671     * @param parentAlpha currently ignored
672     */
673    @Override
674    public void draw(Batch batch, float parentAlpha) {
675        float xo = getX(), yo = getY(), yOff = yo + 1f + gridHeight * font.actualCellHeight, gxo, gyo;
676        font.draw(batch, backgrounds, xo, yo, 3, 3);
677        int len = layers.size();
678        Frustum frustum = null;
679        Stage stage = getStage();
680        if(stage != null) {
681            Viewport viewport = stage.getViewport();
682            if(viewport != null)
683            {
684                Camera camera = viewport.getCamera();
685                if(camera != null)
686                {
687                    if(
688                            camera.frustum != null &&
689                                    (!camera.frustum.boundsInFrustum(xo, yOff - font.actualCellHeight - 1f, 0f, font.actualCellWidth, font.actualCellHeight, 0f) ||
690                                            !camera.frustum.boundsInFrustum(xo + font.actualCellWidth * (gridWidth-1), yo, 0f, font.actualCellWidth, font.actualCellHeight, 0f))
691                    )
692                        frustum = camera.frustum;
693                }
694            }
695        }
696        font.configureShader(batch);
697        if(frustum == null) {
698            for (int i = 0; i < len; i++) {
699                layers.get(i).draw(batch, font, xo, yOff);
700            }
701        }
702        else
703        {
704            for (int i = 0; i < len; i++) {
705                layers.get(i).draw(batch, font, frustum, xo, yOff);
706            }
707        }
708
709        int x, y;
710        for (int i = 0; i < glyphs.size(); i++) {
711            TextCellFactory.Glyph glyph = glyphs.get(i);
712            if(glyph == null)
713                continue;
714            glyph.act(Gdx.graphics.getDeltaTime());
715            if(!glyph.isVisible() ||
716                            (x = Math.round((gxo = glyph.getX() - xo) / font.actualCellWidth)) < 0 || x >= gridWidth ||
717                            (y = Math.round((gyo = glyph.getY() - yo)  / -font.actualCellHeight + gridHeight)) < 0 || y >= gridHeight ||
718                            backgrounds[x * 3 + 1][y * 3 + 1] == 0f || (frustum != null && !frustum.boundsInFrustum(gxo, gyo, 0f, font.actualCellWidth, font.actualCellHeight, 0f)))
719                continue;
720            glyph.draw(batch, 1f);
721        }
722    }
723}