From ee88f153bb2d592a778cea34163c8321765ededc Mon Sep 17 00:00:00 2001 From: gnembon Date: Fri, 24 Jan 2020 02:17:01 -0400 Subject: [PATCH 1/2] noise gen --- src/main/java/carpet/script/Expression.java | 46 +++++ .../script/utils/PerlinNoiseSampler.java | 170 ++++++++++++++++++ .../script/utils/SimplexNoiseSampler.java | 67 +++++++ 3 files changed, 283 insertions(+) create mode 100644 src/main/java/carpet/script/utils/PerlinNoiseSampler.java create mode 100644 src/main/java/carpet/script/utils/SimplexNoiseSampler.java diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index eeed2959..e1008165 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -20,6 +20,8 @@ import carpet.script.exception.InternalExpressionException; import carpet.script.exception.ResolvedException; import carpet.script.exception.ReturnStatement; import carpet.script.exception.ThrowStatement; +import carpet.script.utils.PerlinNoiseSampler; +import carpet.script.utils.SimplexNoiseSampler; import carpet.script.value.AbstractListValue; import carpet.script.value.ContainerValueInterface; import carpet.script.value.FunctionSignatureValue; @@ -29,6 +31,7 @@ import carpet.script.value.LContainerValue; import carpet.script.value.LazyListValue; import carpet.script.value.ListValue; import carpet.script.value.MapValue; +import carpet.script.value.NullValue; import carpet.script.value.NumericValue; import carpet.script.value.StringValue; import carpet.script.value.ThreadValue; @@ -2972,6 +2975,49 @@ public class Expression return (cc, tt) -> retval; }); + addLazyFunction("perlin", 3, (c, t, lv) -> { + // add seed cache + Value ret; + Value x = lv.get(0).evalValue(c); + Value y = lv.get(1).evalValue(c); + Value z = lv.get(2).evalValue(c); + + if (z instanceof NullValue) + { + if (y instanceof NullValue) + { + ret = new NumericValue(PerlinNoiseSampler.instance.sample1d( + NumericValue.asNumber(x).getDouble() + )); + } + else + { + ret = new NumericValue(PerlinNoiseSampler.instance.sample2d( + NumericValue.asNumber(x).getDouble(), + NumericValue.asNumber(y).getDouble() + )); + } + } + else + { + ret = new NumericValue(PerlinNoiseSampler.instance.sample3d( + NumericValue.asNumber(x).getDouble(), + NumericValue.asNumber(y).getDouble(), + NumericValue.asNumber(z).getDouble() + )); + } + return (cc, tt) -> ret; + }); + + addLazyFunction("simplex", 2, (c, t, lv) -> { + // add seed cache + double x = NumericValue.asNumber(lv.get(0).evalValue(c)).getDouble(); + double y = NumericValue.asNumber(lv.get(1).evalValue(c)).getDouble(); + //double z = NumericValue.asNumber(lv.get(2).evalValue(c)).getDouble(); + Value value = new NumericValue(SimplexNoiseSampler.instance.sample(x,y)); + return (cc, tt) -> value; + }); + addUnaryFunction("print", (v) -> { System.out.println(v.getString()); diff --git a/src/main/java/carpet/script/utils/PerlinNoiseSampler.java b/src/main/java/carpet/script/utils/PerlinNoiseSampler.java new file mode 100644 index 00000000..300df75c --- /dev/null +++ b/src/main/java/carpet/script/utils/PerlinNoiseSampler.java @@ -0,0 +1,170 @@ +package carpet.script.utils; + +import java.util.Random; + +public class PerlinNoiseSampler { + protected static final int[][] gradients3d = new int[][]{ + {1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, + {1, 0, 1}, {-1, 0, 1}, {1, 0, -1}, {-1, 0, -1}, + {0, 1, 1}, {0, -1, 1}, {0, 1, -1}, {0, -1, -1}, + {1, 1, 0}, {0, -1, 1}, {-1, 1, 0}, {0, -1, -1} + }; + protected static final int[][] gradients2d = new int[][]{{1, 1}, {-1, 1}, {1, -1}, {-1, -1}}; + + + private final byte[] permutations; + public final double originX; + public final double originY; + public final double originZ; + public static PerlinNoiseSampler instance = new PerlinNoiseSampler(new Random(0)); + + public PerlinNoiseSampler(Random random) { + this.originX = random.nextDouble() * 256.0D; + this.originY = random.nextDouble() * 256.0D; + this.originZ = random.nextDouble() * 256.0D; + this.permutations = new byte[256]; + + int j; + for(j = 0; j < 256; ++j) { + this.permutations[j] = (byte)j; + } + + for(j = 0; j < 256; ++j) { + int k = random.nextInt(256 - j); + byte b = this.permutations[j]; + this.permutations[j] = this.permutations[j + k]; + this.permutations[j + k] = b; + } + + } + + //3D + public double sample3d(double x, double y, double z) {//, double d, double e) { + double f = x + this.originX; + double g = y + this.originY; + double h = z + this.originZ; + int i = floor(f); + int j = floor(g); + int k = floor(h); + double l = f - (double)i; + double m = g - (double)j; + double n = h - (double)k; + double o = perlinFade(l); + double p = perlinFade(m); + double q = perlinFade(n); + //double t; + /* + if (d != 0.0D) { + double r = Math.min(e, m); + t = (double)floor(r / d) * d; + } else { + t = 0.0D; + }*/ + //return this.sample(i, j, k, l, m - t, n, o, p, q); + return this.sample3d(i, j, k, l, m, n, o, p, q); + } + + private double sample3d(int sectionX, int sectionY, int sectionZ, double localX, double localY, double localZ, double fadeLocalX, double fadeLocalY, double fadeLocalZ) { + int i = this.getGradient(sectionX) + sectionY; + int j = this.getGradient(i) + sectionZ; + int k = this.getGradient(i + 1) + sectionZ; + int l = this.getGradient(sectionX + 1) + sectionY; + int m = this.getGradient(l) + sectionZ; + int n = this.getGradient(l + 1) + sectionZ; + double d = grad3d(this.getGradient(j), localX, localY, localZ); + double e = grad3d(this.getGradient(m), localX - 1.0D, localY, localZ); + double f = grad3d(this.getGradient(k), localX, localY - 1.0D, localZ); + double g = grad3d(this.getGradient(n), localX - 1.0D, localY - 1.0D, localZ); + double h = grad3d(this.getGradient(j + 1), localX, localY, localZ - 1.0D); + double o = grad3d(this.getGradient(m + 1), localX - 1.0D, localY, localZ - 1.0D); + double p = grad3d(this.getGradient(k + 1), localX, localY - 1.0D, localZ - 1.0D); + double q = grad3d(this.getGradient(n + 1), localX - 1.0D, localY - 1.0D, localZ - 1.0D); + return lerp3(fadeLocalX, fadeLocalY, fadeLocalZ, d, e, f, g, h, o, p, q); + } + + private static double grad3d(int hash, double x, double y, double z) { + int i = hash & 15; + return dot3d(gradients3d[i], x, y, z); + } + + protected static double dot3d(int[] gArr, double x, double y, double z) { + return (double)gArr[0] * x + (double)gArr[1] * y + (double)gArr[2] * z; + } + + public static double lerp3(double deltaX, double deltaY, double deltaZ, double d, double e, double f, double g, double h, double i, double j, double k) { + return lerp(deltaZ, lerp2(deltaX, deltaY, d, e, f, g), lerp2(deltaX, deltaY, h, i, j, k)); + } + + //2D + public double sample2d(double x, double y) {//, double d, double e) { + double f = x + this.originX; + double g = y + this.originY; + int i = floor(f); + int j = floor(g); + double l = f - (double)i; + double m = g - (double)j; + double o = perlinFade(l); + double p = perlinFade(m); + return this.sample2d(i, j, l, m, o, p); + } + + private double sample2d(int sectionX, int sectionY, double localX, double localY, double fadeLocalX, double fadeLocalY) { + int j = this.getGradient(sectionX) + sectionY; + int m = this.getGradient(sectionX + 1) + sectionY; + double d = grad2d(this.getGradient(j), localX, localY); + double e = grad2d(this.getGradient(m), localX - 1.0D, localY); + double f = grad2d(this.getGradient(j+1), localX, localY - 1.0D); + double g = grad2d(this.getGradient(m+1), localX - 1.0D, localY - 1.0D); + + return lerp2(fadeLocalX, fadeLocalY, d, e, f, g); + } + + private static double grad2d(int hash, double x, double y) { + int i = hash & 3; + return dot2d(gradients2d[i], x, y); + } + + protected static double dot2d(int[] gArr, double x, double y) { + return (double)gArr[0] * x + (double)gArr[1] * y; + } + + public static double lerp2(double deltaX, double deltaY, double d, double e, double f, double g) { + return lerp(deltaY, lerp(deltaX, d, e), lerp(deltaX, f, g)); + } + + // 1D + public double sample1d(double x) {//, double d, double e) { + double f = x + this.originX; + int i = floor(f); + double l = f - (double)i; + double o = perlinFade(l); + return this.sample1d(i, l, o); + } + private double sample1d(int sectionX, double localX, double fadeLocalX) { + double d = grad1d(this.getGradient(sectionX), localX); + double e = grad1d(this.getGradient(sectionX+1), localX - 1.0D); + return lerp(fadeLocalX, d, e); + } + + private static double grad1d(int hash, double x) { + return ((hash & 1) == 0)? x:-x; + } + + public static double lerp(double delta, double first, double second) { + return first + delta * (second - first); + } + + // shared + public int getGradient(int hash) { + return this.permutations[hash & 255] & 255; + } + + public static double perlinFade(double d) { + return d * d * d * (d * (d * 6.0D - 15.0D) + 10.0D); + } + + public static int floor(double d) { + int i = (int)d; + return d < (double)i ? i - 1 : i; + } +} \ No newline at end of file diff --git a/src/main/java/carpet/script/utils/SimplexNoiseSampler.java b/src/main/java/carpet/script/utils/SimplexNoiseSampler.java new file mode 100644 index 00000000..06c5b71b --- /dev/null +++ b/src/main/java/carpet/script/utils/SimplexNoiseSampler.java @@ -0,0 +1,67 @@ +package carpet.script.utils; + +import java.util.Random; + +public class SimplexNoiseSampler extends PerlinNoiseSampler { + private static final double sqrt3 = Math.sqrt(3.0D); + private static final double SKEW_FACTOR_2D; + private static final double UNSKEW_FACTOR_2D; + + public static SimplexNoiseSampler instance = new SimplexNoiseSampler(new Random(0)); + + public SimplexNoiseSampler(Random random) { + super(random); + } + + private double grad(int hash, double x, double y, double z, double d) { + double e = d - x * x - y * y - z * z; + double g; + if (e < 0.0D) { + g = 0.0D; + } else { + e *= e; + g = e * e * PerlinNoiseSampler.dot3d(PerlinNoiseSampler.gradients3d[hash], x, y, z); + } + + return g; + } + + public double sample(double x, double y) { + double d = (x + y) * SKEW_FACTOR_2D; + int i = PerlinNoiseSampler.floor(x + d); + int j = PerlinNoiseSampler.floor(y + d); + double e = (double)(i + j) * UNSKEW_FACTOR_2D; + double f = (double)i - e; + double g = (double)j - e; + double h = x - f; + double k = y - g; + byte n; + byte o; + if (h > k) { + n = 1; + o = 0; + } else { + n = 0; + o = 1; + } + + double p = h - (double)n + UNSKEW_FACTOR_2D; + double q = k - (double)o + UNSKEW_FACTOR_2D; + double r = h - 1.0D + 2.0D * UNSKEW_FACTOR_2D; + double s = k - 1.0D + 2.0D * UNSKEW_FACTOR_2D; + int t = i & 255; + int u = j & 255; + int v = this.getGradient(t + this.getGradient(u)) % 12; + int w = this.getGradient(t + n + this.getGradient(u + o)) % 12; + int z = this.getGradient(t + 1 + this.getGradient(u + 1)) % 12; + double aa = this.grad(v, h, k, 0.0D, 0.5D); + double ab = this.grad(w, p, q, 0.0D, 0.5D); + double ac = this.grad(z, r, s, 0.0D, 0.5D); + return 70.0D * (aa + ab + ac); + } + + static { + SKEW_FACTOR_2D = 0.5D * (sqrt3 - 1.0D); + UNSKEW_FACTOR_2D = (3.0D - sqrt3) / 6.0D; + } +} \ No newline at end of file From 1a873be361fe3bbbb07d2587cfc16ed434707790 Mon Sep 17 00:00:00 2001 From: gnembon Date: Fri, 24 Jan 2020 13:43:57 -0400 Subject: [PATCH 2/2] added cache and docs --- src/main/java/carpet/script/Expression.java | 94 +++++++++++++------ .../script/utils/PerlinNoiseSampler.java | 20 +++- .../script/utils/SimplexNoiseSampler.java | 15 ++- 3 files changed, 97 insertions(+), 32 deletions(-) diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index e1008165..cc275fa7 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -2732,6 +2732,30 @@ public class Expression * map(range(10), str('%.1f',rand(_))) => [0.0, 0.4, 0.6, 1.9, 2.8, 3.8, 5.3, 2.2, 1.6, 5.6] * * + *

perlin(x), perlin(x,y), perlin(x,y,z), perlin(x,y,z,seed)

+ *

returns a noise value from 0.0 to 1.0 (roughly) for 1, 2 or 3 dimensional coordinate. The default seed it samples from is 0, but seed can be + * specified as a 4th argument as well. In case you need 1D or 2D noise values with custom seed, use null + * for z, or y and z arguments respectively.

+ *

Perlin noise is based on a square grid and generates rougher maps comparing to Simplex, which is creamier, + * but the in-game implementation allows to sample up to 3D noise. Querying for lower-dimensional result, rather than + * affixing unused dimensions to constants has a speed benefit, + *

+ *

Thou shall not sample from noise changing seed frequently. Scarpet will keep track of the last 256 perlin seeds used for sampling + * providing similar speed comparing to the default seed of 0. In case the app engine uses more than 256 seeds at the same time, + * switching between them can get much more expensive.

+ * + *

simplex(x,y), simplex(x,y,seed)

+ *

returns a noise value from 0.0 to 1.0 (roughly) for 2 dimensional coordinate. + * The default seed it samples from is 0, but seed can be + * specified as a 3rd argument as well

+ *

Simplex noise is based on a triangular grid and generates smoother maps comparing to Perlin, + * but the in-game implementation only allows to sample 2D noise. To sample 1D simplex noise, affix other coordinate + * to a constant. + *

+ *

Thou shall not sample from noise changing seed frequently. Scarpet will keep track of the last 256 simplex seeds used for sampling + * providing similar speed comparing to the default seed of 0. In case the app engine uses more than 256 seeds at the same time, + * switching between them can get much more expensive.

+ * *

print(expr)

*

prints the value of the expression to chat. * Passes the result of the argument to the output unchanged, so print-statements can @@ -2975,46 +2999,60 @@ public class Expression return (cc, tt) -> retval; }); - addLazyFunction("perlin", 3, (c, t, lv) -> { - // add seed cache - Value ret; - Value x = lv.get(0).evalValue(c); - Value y = lv.get(1).evalValue(c); - Value z = lv.get(2).evalValue(c); + addLazyFunction("perlin", -1, (c, t, lv) -> { + PerlinNoiseSampler sampler; + Value x, y, z; - if (z instanceof NullValue) + if (lv.size() >= 4) { - if (y instanceof NullValue) - { - ret = new NumericValue(PerlinNoiseSampler.instance.sample1d( - NumericValue.asNumber(x).getDouble() - )); - } - else - { - ret = new NumericValue(PerlinNoiseSampler.instance.sample2d( - NumericValue.asNumber(x).getDouble(), - NumericValue.asNumber(y).getDouble() - )); - } + x = lv.get(0).evalValue(c); + y = lv.get(1).evalValue(c); + z = lv.get(2).evalValue(c); + sampler = PerlinNoiseSampler.getPerlin(NumericValue.asNumber(lv.get(3).evalValue(c)).getLong()); } else { - ret = new NumericValue(PerlinNoiseSampler.instance.sample3d( + sampler = PerlinNoiseSampler.instance; + y = Value.NULL; + z = Value.NULL; + if (lv.size() == 0 ) + throw new InternalExpressionException("'perlin' requires at least one dimension to sample from"); + x = NumericValue.asNumber(lv.get(0).evalValue(c)); + if (lv.size() > 1) + { + y = NumericValue.asNumber(lv.get(1).evalValue(c)); + if (lv.size() > 2) + z = NumericValue.asNumber(lv.get(2).evalValue(c)); + } + } + + double result; + + if (z instanceof NullValue) + if (y instanceof NullValue) + result = sampler.sample1d(NumericValue.asNumber(x).getDouble()); + else + result = sampler.sample2d(NumericValue.asNumber(x).getDouble(), NumericValue.asNumber(y).getDouble()); + else + result = sampler.sample3d( NumericValue.asNumber(x).getDouble(), NumericValue.asNumber(y).getDouble(), - NumericValue.asNumber(z).getDouble() - )); - } + NumericValue.asNumber(z).getDouble()); + Value ret = new NumericValue(result); return (cc, tt) -> ret; }); - addLazyFunction("simplex", 2, (c, t, lv) -> { - // add seed cache + addLazyFunction("simplex", -1, (c, t, lv) -> { + SimplexNoiseSampler sampler; + if (lv.size() == 2) + sampler = SimplexNoiseSampler.instance; + else if (lv.size() == 3) + sampler = SimplexNoiseSampler.getSimplex(NumericValue.asNumber(lv.get(2).evalValue(c)).getLong()); + else + throw new InternalExpressionException("'simplex' requires two or three arguments"); double x = NumericValue.asNumber(lv.get(0).evalValue(c)).getDouble(); double y = NumericValue.asNumber(lv.get(1).evalValue(c)).getDouble(); - //double z = NumericValue.asNumber(lv.get(2).evalValue(c)).getDouble(); - Value value = new NumericValue(SimplexNoiseSampler.instance.sample(x,y)); + Value value = new NumericValue(sampler.sample(x,y)); return (cc, tt) -> value; }); diff --git a/src/main/java/carpet/script/utils/PerlinNoiseSampler.java b/src/main/java/carpet/script/utils/PerlinNoiseSampler.java index 300df75c..c57973d9 100644 --- a/src/main/java/carpet/script/utils/PerlinNoiseSampler.java +++ b/src/main/java/carpet/script/utils/PerlinNoiseSampler.java @@ -1,7 +1,11 @@ package carpet.script.utils; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; + +import java.util.Map; import java.util.Random; +// extracted from import net.minecraft.util.math.noise.PerlinNoiseSampler public class PerlinNoiseSampler { protected static final int[][] gradients3d = new int[][]{ {1, 1, 0}, {-1, 1, 0}, {1, -1, 0}, {-1, -1, 0}, @@ -17,6 +21,14 @@ public class PerlinNoiseSampler { public final double originY; public final double originZ; public static PerlinNoiseSampler instance = new PerlinNoiseSampler(new Random(0)); + public static Map samplers = new Long2ObjectOpenHashMap<>(); + + public static PerlinNoiseSampler getPerlin(long aLong) + { + if (samplers.size() > 256) + samplers.clear(); + return samplers.computeIfAbsent(aLong, seed -> new PerlinNoiseSampler(new Random(seed))); + } public PerlinNoiseSampler(Random random) { this.originX = random.nextDouble() * 256.0D; @@ -38,6 +50,8 @@ public class PerlinNoiseSampler { } + + //3D public double sample3d(double x, double y, double z) {//, double d, double e) { double f = x + this.originX; @@ -61,7 +75,7 @@ public class PerlinNoiseSampler { t = 0.0D; }*/ //return this.sample(i, j, k, l, m - t, n, o, p, q); - return this.sample3d(i, j, k, l, m, n, o, p, q); + return this.sample3d(i, j, k, l, m, n, o, p, q)/2+0.5; } private double sample3d(int sectionX, int sectionY, int sectionZ, double localX, double localY, double localZ, double fadeLocalX, double fadeLocalY, double fadeLocalZ) { @@ -105,7 +119,7 @@ public class PerlinNoiseSampler { double m = g - (double)j; double o = perlinFade(l); double p = perlinFade(m); - return this.sample2d(i, j, l, m, o, p); + return this.sample2d(i, j, l, m, o, p)/2+0.5; } private double sample2d(int sectionX, int sectionY, double localX, double localY, double fadeLocalX, double fadeLocalY) { @@ -138,7 +152,7 @@ public class PerlinNoiseSampler { int i = floor(f); double l = f - (double)i; double o = perlinFade(l); - return this.sample1d(i, l, o); + return this.sample1d(i, l, o)+0.5; } private double sample1d(int sectionX, double localX, double fadeLocalX) { double d = grad1d(this.getGradient(sectionX), localX); diff --git a/src/main/java/carpet/script/utils/SimplexNoiseSampler.java b/src/main/java/carpet/script/utils/SimplexNoiseSampler.java index 06c5b71b..7715d915 100644 --- a/src/main/java/carpet/script/utils/SimplexNoiseSampler.java +++ b/src/main/java/carpet/script/utils/SimplexNoiseSampler.java @@ -1,13 +1,25 @@ package carpet.script.utils; +import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; + +import java.util.Map; import java.util.Random; +// extracted from import net.minecraft.util.math.noise.SimplexNoiseSampler public class SimplexNoiseSampler extends PerlinNoiseSampler { private static final double sqrt3 = Math.sqrt(3.0D); private static final double SKEW_FACTOR_2D; private static final double UNSKEW_FACTOR_2D; public static SimplexNoiseSampler instance = new SimplexNoiseSampler(new Random(0)); + public static Map samplers = new Long2ObjectOpenHashMap<>(); + + public static SimplexNoiseSampler getSimplex(long aLong) + { + if (samplers.size() > 256) + samplers.clear(); + return samplers.computeIfAbsent(aLong, seed -> new SimplexNoiseSampler(new Random(seed))); + } public SimplexNoiseSampler(Random random) { super(random); @@ -57,7 +69,8 @@ public class SimplexNoiseSampler extends PerlinNoiseSampler { double aa = this.grad(v, h, k, 0.0D, 0.5D); double ab = this.grad(w, p, q, 0.0D, 0.5D); double ac = this.grad(z, r, s, 0.0D, 0.5D); - return 70.0D * (aa + ab + ac); + //return 70.0D * (aa + ab + ac); + return 35.0D * (aa + ab + ac)+0.5; } static {