001package squidpony.squidgrid.gui.gdx;
002
003import com.badlogic.gdx.graphics.Color;
004import squidpony.StringKit;
005import squidpony.squidmath.NumberTools;
006
007import java.io.Serializable;
008
009/**
010 * Grouping of qualities related to glow and light emission. When a Radiance variable in some object is null, it
011 * means that object doesn't emit light; if a Radiance variable is non-null, it will probably emit light unless the
012 * color of light it produces is fully transparent. Light may take up one cell or extend into nearby cells, and the
013 * radius may change over time in up to two patterns (flicker, which randomly increases and decreases lighting radius,
014 * and/or strobe, which increases and decreases lighting radius in an orderly retract-expand-retract-expand pattern).
015 * You can set the {@link #flare} variable to some value between 0.0f and 1.0f to temporarily expand the minimum radius
016 * for strobe and/or flare, useful for gameplay-dependent brightening of a Radiance.
017 * <br>
018 * This object has 6 fields, each a float:
019 * <ul>
020 * <li>range, how far the light extends; 0f is "just this cell"</li>
021 * <li>color, the color of the light as a float; typically opaque and lighter than the glowing object's color</li>
022 * <li>flicker, the rate of random continuous change to radiance range</li>
023 * <li>strobe, the rate of non-random continuous change to radiance range</li>
024 * <li>flare, used to suddenly increase the minimum radius of lighting; expected to be changed after creation</li>
025 * <li>delay, which delays the pattern of effects like strobe so a sequence can be formed with multiple Radiance</li>
026 * </ul>
027 * These all have defaults; if no parameters are specified the light will be white, affect only the current cell, and
028 * won't flicker or strobe.
029 * <br>
030 * Created by Tommy Ettinger on 6/16/2018.
031 */
032public class Radiance implements Serializable {
033    private static final long serialVersionUID = 1L;
034
035    /**
036     * How far the radiated light extends; 0f is "just this cell", anything higher can go into neighboring cells.
037     * This is permitted to be a non-integer value, which will make this extend into further cells partially.
038     */
039    public float range;
040    /**
041     * The color of light as a float; typically opaque and lighter than the glowing object's symbol.
042     */
043    public float color;
044    /**
045     * The rate of random continuous change to radiance range, like the light from a campfire. The random component of
046     * the change is determined by the {@link System#identityHashCode(Object)} of this Radiance, which will probably
047     * make all flicker effects different when flicker is non-0.
048     */
049    public float flicker;
050    /**
051     * The rate of non-random continuous change to radiance range, like a mechanical strobe effect. This looks like a
052     * strobe light when the value is high enough, but at lower values it will smoothly pulse, which can be less
053     * distracting to players.
054     */
055    public float strobe;
056
057    /**
058     * A time delay that applies to when the strobe and flicker effects change; useful with strobe to make a strobe
059     * expand its lit radius at one point, then expand at a slightly later time at another Radiance with a delay. The
060     * range for delay should be considered 0f to 1f, with 0f the default (no delay) and values between 0 and 1f that
061     * fraction of a full strobe delayed from that default.
062     */
063    public float delay;
064    /**
065     * A temporary increase to the minimum radiance range, meant to brighten a glow during an effect.
066     * This should be a float between 0f and 1f, with 0f meaning no change and 1f meaning always max radius.
067     */
068    public float flare;
069
070    /**
071     * All-default constructor; makes a single-cell unchanging white light.
072     */
073    public Radiance()
074    {
075        this(0f, SColor.FLOAT_WHITE, 0f, 0f, 0f, 0f);
076    }
077
078    /**
079     * Makes an unchanging white light with the specified range in cells.
080     * @param range possibly-non-integer radius to light, in cells
081     */
082    public Radiance(float range)
083    {
084        this(range, SColor.FLOAT_WHITE, 0f, 0f, 0f, 0f);
085    }
086
087    /**
088     * Makes an unchanging light with the given color (as a packed float) and the specified range in cells.
089     * @param range possibly-non-integer radius to light, in cells
090     * @param color packed float color, as produced by {@link Color#toFloatBits()}
091     */
092    public Radiance(float range, float color)
093    {
094        this(range, color, 0f, 0f, 0f, 0f);
095    }
096
097    /**
098     * Makes a flickering light with the given color (as a packed float) and the specified range in cells; the flicker
099     * parameter affects the rate at which this will randomly reduce its range and return to normal.
100     * @param range possibly-non-integer radius to light, in cells
101     * @param color packed float color, as produced by {@link Color#toFloatBits()}
102     * @param flicker the rate at which to flicker, as a non-negative float
103     */
104    public Radiance(float range, float color, float flicker)
105    {
106        this(range, color, flicker, 0f, 0f, 0f);
107    }
108    /**
109     * Makes a flickering light with the given color (as a packed float) and the specified range in cells; the flicker
110     * parameter affects the rate at which this will randomly reduce its range and return to normal, and the strobe
111     * parameter affects the rate at which this will steadily reduce its range and return to normal. Usually one of
112     * flicker or strobe is 0; if both are non-0, the radius will be smaller than normal.
113     * @param range possibly-non-integer radius to light, in cells
114     * @param color packed float color, as produced by {@link Color#toFloatBits()}
115     * @param flicker the rate at which to flicker, as a non-negative float
116     * @param strobe the rate at which to strobe or pulse, as a non-negative float
117     */
118    public Radiance(float range, float color, float flicker, float strobe)
119    {
120        this(range, color, flicker, strobe, 0f, 0f);
121    }
122    /**
123     * Makes a flickering light with the given color (as a libGDX Color) and the specified range in cells; the flicker
124     * parameter affects the rate at which this will randomly reduce its range and return to normal, and the strobe
125     * parameter affects the rate at which this will steadily reduce its range and return to normal. Usually one of
126     * flicker or strobe is 0; if both are non-0, the radius will be smaller than normal.
127     * @param range possibly-non-integer radius to light, in cells
128     * @param color a libGDX Color object; will not be modified
129     * @param flicker the rate at which to flicker, as a non-negative float
130     * @param strobe the rate at which to strobe or pulse, as a non-negative float
131     */
132    public Radiance(float range, Color color, float flicker, float strobe)
133    {
134        this(range, color.toFloatBits(), flicker, strobe, 0f, 0f);
135    }
136    
137    /**
138     * Makes a flickering light with the given color (as a packed float) and the specified range in cells; the flicker
139     * parameter affects the rate at which this will randomly reduce its range and return to normal, and the strobe
140     * parameter affects the rate at which this will steadily reduce its range and return to normal. Usually one of
141     * flicker or strobe is 0; if both are non-0, the radius will be smaller than normal. The delay parameter is usually
142     * from 0f to 1f, and is almost always 0f unless this is part of a group of related Radiance objects; it affects
143     * when strobe and flicker hit "high points" and "low points", and should usually be used with strobe.
144     * @param range possibly-non-integer radius to light, in cells
145     * @param color packed float color, as produced by {@link Color#toFloatBits()}
146     * @param flicker the rate at which to flicker, as a non-negative float
147     * @param strobe the rate at which to strobe or pulse, as a non-negative float
148     * @param delay a delay applied to the "high points" and "low points" of strobe and flicker, from 0f to 1f
149     */
150    public Radiance(float range, float color, float flicker, float strobe, float delay)
151    {
152        this(range, color, flicker, strobe, delay, 0f);
153    }
154    /**
155     * Makes a flickering light with the given color (as a packed float) and the specified range in cells; the flicker
156     * parameter affects the rate at which this will randomly reduce its range and return to normal, and the strobe
157     * parameter affects the rate at which this will steadily reduce its range and return to normal. Usually one of
158     * flicker or strobe is 0; if both are non-0, the radius will be smaller than normal. The delay parameter is usually
159     * from 0f to 1f, and is almost always 0f unless this is part of a group of related Radiance objects; it affects
160     * when strobe and flicker hit "high points" and "low points", and should usually be used with strobe. This allows
161     * setting flare, where flare is used to create a sudden increase in the minimum radius for the Radiance, but flare
162     * makes the most sense to set when an event should brighten a Radiance, not in the constructor. Valid values for
163     * flare are usually between 0f and 1f.
164     * @param range possibly-non-integer radius to light, in cells
165     * @param color packed float color, as produced by {@link Color#toFloatBits()}
166     * @param flicker the rate at which to flicker, as a non-negative float
167     * @param strobe the rate at which to strobe or pulse, as a non-negative float
168     * @param delay a delay applied to the "high points" and "low points" of strobe and flicker, from 0f to 1f
169     * @param flare affects the minimum radius for the Radiance, from 0f to 1f with a default of 0f
170     */
171    public Radiance(float range, float color, float flicker, float strobe, float delay, float flare)
172    {
173        this.range = range;
174        this.color = color;
175        this.flicker = flicker;
176        this.strobe = strobe;
177        this.delay = delay;
178        this.flare = flare;
179    }
180
181    /**
182     * Copies another Radiance exactly, except for the pattern its flicker may have, if any.
183     * @param other another Radiance to copy
184     */
185    public Radiance(Radiance other)
186    {
187        this(other.range, other.color, other.flicker, other.strobe, other.delay, other.flare);
188    }
189
190    /**
191     * Provides the calculated current range adjusted for flicker and strobe at the current time in milliseconds, with
192     * flicker seeded with the identity hash code of this Radiance. Higher values of flicker and strobe will increase
193     * the frequency at which the range changes but will not allow it to exceed its starting range, only to diminish
194     * temporarily. If both flicker and strobe are non-0, the range will usually be smaller than if only one was non-0,
195     * and if both are 0, this simply returns range.
196     * @return the current range, adjusting for flicker and strobe using the current time
197     */
198    public float currentRange()
199    {
200        final float time = (System.currentTimeMillis() & 0x3ffffL) * 0x1.9p-9f;
201        float current = range;
202        if(flicker != 0f) 
203            current *= NumberTools.swayRandomized(System.identityHashCode(this), time * flicker + delay) * 0.375f + 0.625f;
204        if(strobe != 0f)
205            current *= NumberTools.swayTight(time * strobe + delay) * 0.5f + 0.5f;
206        return Math.max(current, range * flare);
207    }
208
209    /**
210     * Makes a chain of Radiance objects that will pulse in a sequence, expanding from one to the next.
211     * This chain is an array of Radiance where the order matters.
212     * @param length how many Radiance objects should be in the returned array
213     * @param range in cells, how far each Radiance should expand from its start at its greatest radius
214     * @param color as a packed float color
215     * @param strobe the rate at which the chain will pulse; should be greater than 0
216     * @return an array of Radiance objects that will pulse in sequence.
217     */
218    public static Radiance[] makeChain(int length, float range, float color, float strobe)
219    {
220        if(length <= 1)
221            return new Radiance[]{new Radiance(range, color, 0f, strobe)};
222        Radiance[] chain = new Radiance[length];
223        float d = -2f / (length);
224        for (int i = 0; i < length; i++) {
225            chain[i] = new Radiance(range, color, 0f, strobe, d * i);
226        }
227        return chain;
228    }
229
230    @Override
231    public String toString() {
232        return "Radiance{" +
233                "range=" + range +
234                ", color=" + color +
235                ", flicker=" + flicker +
236                ", strobe=" + strobe +
237                ", delay=" + delay +
238                ", flare=" + flare +
239                '}';
240    }
241
242    @Override
243    public boolean equals(Object o) {
244        if (this == o) return true;
245        if (o == null || getClass() != o.getClass()) return false;
246
247        Radiance radiance = (Radiance) o;
248
249        if (Float.compare(radiance.range, range) != 0) return false;
250        if (Float.compare(radiance.color, color) != 0) return false;
251        if (Float.compare(radiance.flicker, flicker) != 0) return false;
252        if (Float.compare(radiance.strobe, strobe) != 0) return false;
253        if (Float.compare(radiance.delay, delay) != 0) return false;
254        return Float.compare(radiance.flare, flare) == 0;
255    }
256
257    @Override
258    public int hashCode() {
259        int result = NumberTools.floatToIntBits(range);
260        result ^= (result << 11 | result >>> 21) + (result << 19 | result >>> 13) + NumberTools.floatToIntBits(color);
261        result ^= (result << 11 | result >>> 21) + (result << 19 | result >>> 13) + NumberTools.floatToIntBits(flicker);
262        result ^= (result << 11 | result >>> 21) + (result << 19 | result >>> 13) + NumberTools.floatToIntBits(strobe);
263        result ^= (result << 11 | result >>> 21) + (result << 19 | result >>> 13) + NumberTools.floatToIntBits(delay);
264        result ^= (result << 11 | result >>> 21) + (result << 19 | result >>> 13) + NumberTools.floatToIntBits(flare);
265        return result;
266    }
267
268    public String serializeToString()
269    {
270        return  "{" + StringKit.hex(NumberTools.floatToIntBits(range)) +
271                "," + StringKit.hex(NumberTools.floatToIntBits(color)) +
272                "," + StringKit.hex(NumberTools.floatToIntBits(flicker)) +
273                "," + StringKit.hex(NumberTools.floatToIntBits(strobe)) + 
274                "," + StringKit.hex(NumberTools.floatToIntBits(delay)) +
275                "," + StringKit.hex(NumberTools.floatToIntBits(flare)) +
276                "}";
277    }
278    
279    public static Radiance deserializeFromString(String data)
280    {
281        return data != null && data.length() >= 54
282                ? new Radiance(
283                NumberTools.intBitsToFloat(StringKit.intFromHex(data, 1, 9)),
284                NumberTools.intBitsToFloat(StringKit.intFromHex(data, 10, 18)),
285                NumberTools.intBitsToFloat(StringKit.intFromHex(data, 19, 27)),
286                NumberTools.intBitsToFloat(StringKit.intFromHex(data, 28, 36)),
287                NumberTools.intBitsToFloat(StringKit.intFromHex(data, 37, 45)),
288                NumberTools.intBitsToFloat(StringKit.intFromHex(data, 46, 54)))
289                : null;
290    }
291}