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}