From 7d1a9e519b5fafdb1e90b66836e1ef5f65facf20 Mon Sep 17 00:00:00 2001 From: gnembon Date: Fri, 24 Jan 2020 15:05:58 -0400 Subject: [PATCH] added 3d simplex --- src/main/java/carpet/script/Expression.java | 60 ++++++++---- .../script/utils/SimplexNoiseSampler.java | 91 ++++++++++++++++++- 2 files changed, 131 insertions(+), 20 deletions(-) diff --git a/src/main/java/carpet/script/Expression.java b/src/main/java/carpet/script/Expression.java index cc275fa7..d5ad5457 100644 --- a/src/main/java/carpet/script/Expression.java +++ b/src/main/java/carpet/script/Expression.java @@ -2732,25 +2732,26 @@ 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 + *

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 + *

Perlin noise is based on a square grid and generates rougher maps comparing to Simplex, which is creamier. + * 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. + *

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

+ *

returns a noise value from 0.0 to 1.0 (roughly) for 2 or 3 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. + * specified as a 4th argument as well. In case you need 2D noise values with custom seed, use null + * for z argument.

+ *

Simplex noise is based on a triangular grid and generates smoother maps comparing to Perlin. + * 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, @@ -3044,16 +3045,37 @@ public class Expression 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()); + 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 = SimplexNoiseSampler.getSimplex(NumericValue.asNumber(lv.get(3).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; + { + sampler = SimplexNoiseSampler.instance; + z = Value.NULL; + if (lv.size() < 2 ) + throw new InternalExpressionException("'simplex' requires at least two dimensions to sample from"); + x = NumericValue.asNumber(lv.get(0).evalValue(c)); + 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) + 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; }); addUnaryFunction("print", (v) -> diff --git a/src/main/java/carpet/script/utils/SimplexNoiseSampler.java b/src/main/java/carpet/script/utils/SimplexNoiseSampler.java index 7715d915..c2083028 100644 --- a/src/main/java/carpet/script/utils/SimplexNoiseSampler.java +++ b/src/main/java/carpet/script/utils/SimplexNoiseSampler.java @@ -38,7 +38,7 @@ public class SimplexNoiseSampler extends PerlinNoiseSampler { return g; } - public double sample(double x, double y) { + public double sample2d(double x, double y) { double d = (x + y) * SKEW_FACTOR_2D; int i = PerlinNoiseSampler.floor(x + d); int j = PerlinNoiseSampler.floor(y + d); @@ -73,6 +73,95 @@ public class SimplexNoiseSampler extends PerlinNoiseSampler { return 35.0D * (aa + ab + ac)+0.5; } + public double sample3d(double d, double e, double f) { + double g = 0.3333333333333333D; + double h = (d + e + f) * 0.3333333333333333D; + int i = floor(d + h); + int j = floor(e + h); + int k = floor(f + h); + double l = 0.16666666666666666D; + double m = (double)(i + j + k) * 0.16666666666666666D; + double n = (double)i - m; + double o = (double)j - m; + double p = (double)k - m; + double q = d - n; + double r = e - o; + double s = f - p; + byte z; + byte aa; + byte ab; + byte ac; + byte ad; + byte bc; + if (q >= r) { + if (r >= s) { + z = 1; + aa = 0; + ab = 0; + ac = 1; + ad = 1; + bc = 0; + } else if (q >= s) { + z = 1; + aa = 0; + ab = 0; + ac = 1; + ad = 0; + bc = 1; + } else { + z = 0; + aa = 0; + ab = 1; + ac = 1; + ad = 0; + bc = 1; + } + } else if (r < s) { + z = 0; + aa = 0; + ab = 1; + ac = 0; + ad = 1; + bc = 1; + } else if (q < s) { + z = 0; + aa = 1; + ab = 0; + ac = 0; + ad = 1; + bc = 1; + } else { + z = 0; + aa = 1; + ab = 0; + ac = 1; + ad = 1; + bc = 0; + } + + double bd = q - (double)z + 0.16666666666666666D; + double be = r - (double)aa + 0.16666666666666666D; + double bf = s - (double)ab + 0.16666666666666666D; + double bg = q - (double)ac + 0.3333333333333333D; + double bh = r - (double)ad + 0.3333333333333333D; + double bi = s - (double)bc + 0.3333333333333333D; + double bj = q - 1.0D + 0.5D; + double bk = r - 1.0D + 0.5D; + double bl = s - 1.0D + 0.5D; + int bm = i & 255; + int bn = j & 255; + int bo = k & 255; + int bp = this.getGradient(bm + this.getGradient(bn + this.getGradient(bo))) % 12; + int bq = this.getGradient(bm + z + this.getGradient(bn + aa + this.getGradient(bo + ab))) % 12; + int br = this.getGradient(bm + ac + this.getGradient(bn + ad + this.getGradient(bo + bc))) % 12; + int bs = this.getGradient(bm + 1 + this.getGradient(bn + 1 + this.getGradient(bo + 1))) % 12; + double bt = this.grad(bp, q, r, s, 0.6D); + double bu = this.grad(bq, bd, be, bf, 0.6D); + double bv = this.grad(br, bg, bh, bi, 0.6D); + double bw = this.grad(bs, bj, bk, bl, 0.6D); + return 16.0D * (bt + bu + bv + bw) + 0.5; + } + static { SKEW_FACTOR_2D = 0.5D * (sqrt3 - 1.0D); UNSKEW_FACTOR_2D = (3.0D - sqrt3) / 6.0D;