diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java
index eeed2959..cc275fa7 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;
@@ -2729,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
@@ -2972,6 +2999,63 @@ public class Expression
return (cc, tt) -> retval;
});
+ addLazyFunction("perlin", -1, (c, t, lv) -> {
+ PerlinNoiseSampler sampler;
+ Value x, y, z;
+
+ if (lv.size() >= 4)
+ {
+ 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
+ {
+ 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());
+ Value ret = new NumericValue(result);
+ return (cc, tt) -> ret;
+ });
+
+ 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();
+ Value value = new NumericValue(sampler.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..c57973d9
--- /dev/null
+++ b/src/main/java/carpet/script/utils/PerlinNoiseSampler.java
@@ -0,0 +1,184 @@
+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},
+ {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 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;
+ 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)/2+0.5;
+ }
+
+ 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)/2+0.5;
+ }
+
+ 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)+0.5;
+ }
+ 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..7715d915
--- /dev/null
+++ b/src/main/java/carpet/script/utils/SimplexNoiseSampler.java
@@ -0,0 +1,80 @@
+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);
+ }
+
+ 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);
+ return 35.0D * (aa + ab + ac)+0.5;
+ }
+
+ static {
+ SKEW_FACTOR_2D = 0.5D * (sqrt3 - 1.0D);
+ UNSKEW_FACTOR_2D = (3.0D - sqrt3) / 6.0D;
+ }
+}
\ No newline at end of file