001package squidpony.squidmath;
002
003import java.io.Serializable;
004
005import static squidpony.squidmath.Noise.emphasizeSigned;
006import static squidpony.squidmath.NumberTools.sway;
007import static squidpony.squidmath.NumberTools.swayRandomized;
008
009/**
010 * Like a kind of RNG, but fully deterministic in a way that depends on a "connected" double array.
011 * Intended as a way to produce similar values when small changes occur in the connections, while potentially producing
012 * larger changes when the changes are more significant (unlike an RNG or hashing function, which can and should produce
013 * very different output given even slightly different seeds/input). This might be useful to produce procedural story
014 * data that is similar when most of the connected inputs are similar, or for terrain generation/population. This can
015 * produce ints and doubles, and does not produce a different output unless its input is changed (usually by altering a
016 * shared reference to {@code connections}). Also implements the various {@link Noise} interfaces, which this doesn't
017 * do perfectly but is at least different (it may yield large spans of high or low results, which Simplex and Perlin
018 * noise cannot actually do). <a href="https://i.imgur.com/BrTVlj1.gifv">Here's a short clip of this in motion</a>.
019 * <br>
020 * If you're using this as an arbitrary-dimensional noise algorithm, you may want to also consider {@link PhantomNoise},
021 * which has output that looks more like Perlin or Simplex noise but scales to arbitrary dimensions.
022 * <a href="https://i.imgur.com/rmwXwO5.gifv">Here's a short clip of PhantomNoise in motion</a>.
023 * <br>
024 * Created by Tommy Ettinger on 5/18/2017.
025 */
026public class CosmicNumbering implements Serializable, Noise.Noise2D, Noise.Noise3D, Noise.Noise4D, Noise.Noise6D {
027    private static final long serialVersionUID = 0L;
028    public static final CosmicNumbering instance = new CosmicNumbering(0x1337BEEFL, new double[]{1.618, 3.14});
029    protected double[] connections;
030    protected int len;
031//    private int upper;
032    protected long seed;
033//    private transient long[] scratch3;
034//    private transient double[] scratch;
035
036    protected double effect;
037    public CosmicNumbering() {
038        this(1234567890L, new double[1]);
039    }
040    public CosmicNumbering(double[] connections) {
041        this(1234567890L, connections);
042    }
043
044    public CosmicNumbering(long seed, double[] connections) {
045        if(connections == null || connections.length == 0)
046            this.connections = new double[1];
047        else
048            this.connections = connections;
049        len = this.connections.length;
050//        upper = 1 << len;
051//        scratch3 = new long[len * 3];
052//        scratch = new double[upper];
053        this.seed = seed;
054//        effect = 0x1.81p-62 * Math.pow(1.1875, len);
055    }
056
057    public double[] getConnections() {
058        return connections;
059    }
060
061    public void setConnections(double[] connections) {
062        if (connections == null || connections.length == 0)
063            this.connections = new double[1];
064        else
065            this.connections = connections;
066        if (len != this.connections.length) {
067            len = this.connections.length;
068//            upper = 1 << len;
069//            scratch3 = new long[len * 3];
070//            scratch = new double[upper];
071//            effect = 0x1.81p-62 * Math.pow(1.1875, len);
072        }
073    }
074//    /*
075//     * Quintic-interpolates between start and end (valid floats), with a between 0 (yields start) and 1 (yields end).
076//     * Will smoothly transition toward start or end as a approaches 0 or 1, respectively.
077//     * @param start a valid float
078//     * @param end a valid float
079//     * @param a a float between 0 and 1 inclusive
080//     * @return a float between x and y inclusive
081//     */
082//    public static double querp(final double start, final double end, double a){
083//        return (1.0 - (a *= a * a * (a * (a * 6.0 - 15.0) + 10.0))) * start + a * end;
084//    }
085//    /*
086//     * Linearly interpolates between start and end (valid floats), with a between 0 (yields start) and 1 (yields end).
087//     * @param start a valid float
088//     * @param end a valid float
089//     * @param a a float between 0 and 1 inclusive
090//     * @return a float between x and y inclusive
091//     */
092//    public static double interpolate(final double start, final double end, final double a)
093//    {
094//        return (1.0 - a) * start + a * end;
095//    }
096
097//    public double getDoubleBase()
098//    {
099//        double[] connections = this.connections;
100//        final int len = connections.length;
101//        long floor, seed = 1234567;
102//        double diff, conn, result = 0.0;
103//        for (int i = 0; i < len; i++) {
104//            diff = (conn = connections[i]) - (floor = longFloor(conn));
105//            seed += 10000;
106//            result += querp(
107//                    NumberTools.formCurvedFloat(NumberTools.splitMix64(floor * seed + 100 * (i + 1))),
108//                    NumberTools.formCurvedFloat(NumberTools.splitMix64((floor + 1L) * seed + 100 * (i + 1))),
109//                    diff
110//            );
111//        }
112//        return NumberTools.bounce(5.0 + 2.4 * result);
113//    }
114
115    /**
116     * Gets a double determined by the current values in the connections, accessible via {@link #getConnections()}.
117     * Returns a value between -1.0 and 1.0 (exclusive on 1.0). Used as the basis for other methods in this class.
118     * @return a double between -1.0 and 1.0; will be the same value until/unless connections change
119     */
120    public final double getDoubleBase() {
121        //return (getDouble() - 0.5) * 2.0;
122        double sum = swayRandomized(seed, connections[len - 1] + connections[0]);
123        for (int i = 1; i < len; i++) {
124            sum += swayRandomized(seed, sum + connections[i - 1] + connections[i]);
125        }
126        return sum / len;
127    }
128
129//    {
130//        double[] connections = this.connections;
131//        final int len = connections.length;
132//        long floor;
133//        double diff, conn, result = 0.0;//, total = 1.0;
134//        for (int i = 0; i < len; i++) {
135//            diff = (conn = connections[i]) - (floor = longFloor(conn));
136//            //  & 0xfffffffffffffL
137//            result +=
138//                    NumberTools.bounce((NumberTools.longBitsToDouble((floor * 0x9E3779B97F4A7C15L >>> 12) | 0x4000000000000000L) - 3.0)
139//                            * (1.0 - diff)
140//                            + (NumberTools.longBitsToDouble(((floor + 1L) * 0x9E3779B97F4A7C15L >>> 12) | 0x4000000000000000L) - 3.0)
141//                            * diff
142//                            + 5 + ~i * 0.618);
143//        }
144//        return result / len;
145//    }
146
147    /**
148     * Gets a double determined by the current values in the connections, accessible via {@link #getConnections()}.
149     * Returns a value between 0.0 and 1.0 (exclusive on 1.0).
150     * @return a double between 0.0 and 1.0; will be the same value until/unless connections change
151     */
152    public double getDouble()
153    {
154//        for (int i = 0; i < len; i++) {
155//            long seed = seeds[i];
156//            scratch3[i * 3 + 1] = (scratch3[i * 3] = (scratch3[i * 3 + 2] = longFloor(connections[i])) * seed) + seed;
157//        }
158//        long working;
159//        for (int i = 0; i < upper; i++) {
160//            working = 0L;
161//            for (int j = 0; j < len; j++) {
162//                working += scratch3[j * 3 + (i >>> j & 1)];
163//            }
164//            scratch[i] = determine(working) * effect;
165//        }
166//        for (int i = 0; i < len; ++i) {
167//            for (int j = 0, t = upper >> i; j < t; j += 2) {
168//                scratch[j >>> 1] = cerp(scratch[j], scratch[j + 1], connections[i] - scratch3[i * 3 + 2]);
169//            }
170//        }
171//        return scratch[0] - longFloor(scratch[0]);
172//// has a different look than the above line
173////        return NumberTools.sway(scratch[0]);
174        double sum = swayRandomized(seed, connections[len - 1] + connections[0]);
175        for (int i = 1; i < len; i++) {
176            sum += swayRandomized(seed, sum + connections[i - 1] + connections[i]);
177        }
178        return sum / (len << 1) + 0.5;
179
180    }
181
182//    public double getDouble()
183//    {
184//        double v = 0.0, diff;
185//        double[] connections = this.connections;
186//        final int len = connections.length;
187//        long floor;
188//        for (int i = 0; i < len; i++) {
189//            diff = connections[i] - (floor = longFloor(connections[i]));
190//            v += randomDouble(floor) * (1.0 - diff) + randomDouble(floor + 1L) * diff;
191//        }
192//        return v / len;
193//    }
194    /**
195     * Gets an int determined by the current values in the connections, accessible via {@link #getConnections()}.
196     * Returns a value in the full range of ints, but is less likely to produce ints close to {@link Integer#MAX_VALUE}
197     * or {@link Integer#MIN_VALUE} (expect very few values in the bottom and top quarters of the range).
198     * @return an int which can be positive or negative; will be the same value until/unless connections change
199     */
200    public int getInt()
201    {
202        return (int)(0x80000000 * getDoubleBase());
203    }
204    
205    @Override
206    public double getNoise(double x, double y) {
207        return getNoiseWithSeed(x, y, seed);
208    }
209
210    @Override
211    public double getNoiseWithSeed(double x, double y, long seed) {
212        x *= 2.25;
213        y *= 2.25;
214        double sum = (swayRandomized(seed, x) * swayRandomized(~seed, x + y));
215        sum += (swayRandomized(seed, sum + y) * swayRandomized(~seed, x - y));
216//        sum += swayRandomized(seed, sum - x + y);
217        return Noise.extremeSigned(sum * 0.5);
218//        return Noise.emphasizeSigned((sum + swayRandomized(seed, sum + x + y)) * 0.25);
219        //return sum * 0.5;
220    }
221
222    @Override
223    public double getNoise(double x, double y, double z) {
224        return getNoiseWithSeed(x, y, z, seed);
225    }
226
227    @Override
228    public double getNoiseWithSeed(double x, double y, double z, long seed) {
229        x *= 1.5;
230        y *= 1.5;
231        z *= 1.5;
232        x += swayRandomized(seed ^ 0xD1B54A32D192ED03L, y + z) - swayRandomized(0xABC98388FB8FAC03L - seed, x - y);
233        y += swayRandomized(seed ^ 0xDB4F0B9175AE2165L, x + z) - swayRandomized(0xBBE0563303A4615FL - seed, y - z);
234        z += swayRandomized(seed ^ 0xE19B01AA9D42C633L, x + y) - swayRandomized(0xC6D1D6C8ED0C9631L - seed, z - x);
235//        double sum = swayRandomized(seed, x - y) * (swayRandomized(~seed, x - z));
236//        sum += swayRandomized(seed ^ 0xDB4F0B9175AE2165L, sum + y - z) * (swayRandomized(0xBBE0563303A4615FL - seed, x + y - sum));
237//        sum += swayRandomized(seed ^ 0xE19B01AA9D42C633L, sum + z - x) * (swayRandomized(0xC6D1D6C8ED0C9631L - seed, -y - z + sum));
238        return sway((x + y + z) * 0.3333333333333333);
239        //return emphasizeSigned(sum * 0.3333333333333333);
240    }
241
242    @Override
243    public double getNoise(double x, double y, double z, double w) {
244        return getNoiseWithSeed(x, y, z, w, seed);
245    }
246
247    @Override
248    public double getNoiseWithSeed(double x, double y, double z, double w, long seed) {
249        double sum = swayRandomized(seed, w + x - y);
250        sum += swayRandomized(seed, sum + x + y - z);
251        sum += swayRandomized(seed, sum + y + z - w);
252        sum += swayRandomized(seed, sum + z + w - x);
253        return emphasizeSigned(sum * 0.25);
254    }
255
256    @Override
257    public double getNoise(double x, double y, double z, double w, double u, double v) {
258        return getNoiseWithSeed(x, y, z, w, u, v, seed);
259    }
260
261    @Override
262    public double getNoiseWithSeed(double x, double y, double z, double w, double u, double v, long seed) {
263        double sum = swayRandomized(seed, v + x - y);
264        sum += swayRandomized(seed, sum + x + y - z);
265        sum += swayRandomized(seed, sum + y + z - w);
266        sum += swayRandomized(seed, sum + z + w - u);
267        sum += swayRandomized(seed, sum + w + u - v);
268        sum += swayRandomized(seed, sum + u + v - x);
269        return emphasizeSigned(sum * 0.16666666666666666);
270    }
271
272    /*
273     * Linearly interpolates between start and end (valid floats), with a between 0 (yields start) and 1 (yields end).
274     * @param start a valid float
275     * @param end a valid float
276     * @param a a float between 0 and 1 inclusive
277     * @return a float between x and y inclusive
278     * /
279    private static double interpolate(final double start, final double end, final double a)
280    {
281        return (1.0 - a) * start + a * end;
282    }
283    */
284    /*
285    private boolean haveNextNextGaussian = false;
286    private double nextNextGaussian;
287    private double nextGaussian(int state) {
288        if (haveNextNextGaussian) {
289            haveNextNextGaussian = false;
290            return nextNextGaussian;
291        } else {
292            double v1, v2, s;
293            do {
294                v1 = 2 * NumberTools.randomDouble(state += 0xAE3779B9) - 1; // between -1 and 1
295                v2 = 2 * NumberTools.randomDouble(state + 0xBE3779B9) - 1; // between -1 and 1
296                s = v1 * v1 + v2 * v2;
297            } while (s >= 1 || s == 0);
298            double multiplier = Math.sqrt(-2 * Math.log(s) / s);
299            nextNextGaussian = v2 * multiplier;
300            haveNextNextGaussian = true;
301            return v1 * multiplier;
302        }
303    }
304
305    public void randomUnitVector(int seed, final double[] vector)
306    {
307        final int len = vector.length;
308        double mag = 0.0, t;
309        for (int i = 0; i < len; i++) {
310            vector[i] = (t = nextGaussian(seed += 0x8E3779B9));
311            mag += t * t;
312        }
313        if(mag == 0)
314        {
315            vector[0] = 1.0;
316            mag = 1.0;
317        }
318        else
319            mag = Math.sqrt(mag);
320        for (int i = 0; i < len; i++) {
321            vector[i] /= mag;
322        }
323    }
324    public void randomManhattanVector (int seed, final double[] vector)
325    {
326        final int len = vector.length;
327        double mag = 0.0;
328        for (int i = 0; i < len; i++) {
329            mag += Math.abs(vector[i] = NumberTools.randomFloatCurved(seed += 0x8E3779B9));
330        }
331        if(mag == 0)
332        {
333            vector[0] = 1.0;
334            mag = 1.0;
335        }
336        for (int i = 0; i < len; i++) {
337            vector[i] /= mag;
338        }
339    }
340    
341     * The same as {@link DiverRNG#determine(long)}, except this assumes state has already been multiplied by
342     * 0x632BE59BD9B4E019L.
343     * @param state a long that should change in increments of 0x632BE59BD9B4E019L
344     * @return a pseudo-random permutation of state
345    public static long determine(long state)
346    {
347        return (state = ((state = ((state ^ 0x9E3779B97F4A7C15L) * 0xC6BC279692B5CC83L)) ^ state >>> 27) * 0xAEF17502108EF2D9L) ^ state >>> 25;
348    }
349
350    
351    */
352}