From 142d3646821f2c084deaa692dde96dcb2881f675 Mon Sep 17 00:00:00 2001 From: Astrash Date: Wed, 17 Apr 2024 09:54:07 +1000 Subject: [PATCH 1/5] WIP fractal-gavoro-pseudoerosion --- .../dfsek/terra/addons/noise/NoiseAddon.java | 10 +- .../DerivativeNoiseSamplerTemplate.java | 25 +++ .../noise/DerivativeFractalTemplate.java | 32 ++++ .../noise/PseudoErosionTemplate.java | 62 ++++++ .../noise/simplex/DerivativeFractal.java | 104 ++++++++++ .../samplers/noise/simplex/PseudoErosion.java | 179 ++++++++++++++++++ .../api/noise/DerivativeNoiseSampler.java | 22 +++ 7 files changed, 432 insertions(+), 2 deletions(-) create mode 100644 common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/DerivativeNoiseSamplerTemplate.java create mode 100644 common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/DerivativeFractalTemplate.java create mode 100644 common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/PseudoErosionTemplate.java create mode 100644 common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/simplex/DerivativeFractal.java create mode 100644 common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/simplex/PseudoErosion.java create mode 100644 common/api/src/main/java/com/dfsek/terra/api/noise/DerivativeNoiseSampler.java diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/NoiseAddon.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/NoiseAddon.java index ca3aa1a44..53b5ef555 100644 --- a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/NoiseAddon.java +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/NoiseAddon.java @@ -17,6 +17,7 @@ import com.dfsek.terra.addons.manifest.api.AddonInitializer; import com.dfsek.terra.addons.noise.config.CubicSplinePointTemplate; import com.dfsek.terra.addons.noise.config.DimensionApplicableNoiseSampler; import com.dfsek.terra.addons.noise.config.templates.BinaryArithmeticTemplate; +import com.dfsek.terra.addons.noise.config.templates.DerivativeNoiseSamplerTemplate; import com.dfsek.terra.addons.noise.config.templates.DomainWarpTemplate; import com.dfsek.terra.addons.noise.config.templates.FunctionTemplate; import com.dfsek.terra.addons.noise.config.templates.ImageSamplerTemplate; @@ -25,9 +26,11 @@ import com.dfsek.terra.addons.noise.config.templates.LinearHeightmapSamplerTempl import com.dfsek.terra.addons.noise.config.templates.TranslateSamplerTemplate; import com.dfsek.terra.addons.noise.config.templates.noise.CellularNoiseTemplate; import com.dfsek.terra.addons.noise.config.templates.noise.ConstantNoiseTemplate; +import com.dfsek.terra.addons.noise.config.templates.noise.DerivativeFractalTemplate; import com.dfsek.terra.addons.noise.config.templates.noise.DistanceSamplerTemplate; import com.dfsek.terra.addons.noise.config.templates.noise.ExpressionFunctionTemplate; import com.dfsek.terra.addons.noise.config.templates.noise.GaborNoiseTemplate; +import com.dfsek.terra.addons.noise.config.templates.noise.PseudoErosionTemplate; import com.dfsek.terra.addons.noise.config.templates.noise.SimpleNoiseTemplate; import com.dfsek.terra.addons.noise.config.templates.noise.fractal.BrownianMotionTemplate; import com.dfsek.terra.addons.noise.config.templates.noise.fractal.PingPongTemplate; @@ -63,6 +66,7 @@ import com.dfsek.terra.api.addon.BaseAddon; import com.dfsek.terra.api.event.events.config.pack.ConfigPackPreLoadEvent; import com.dfsek.terra.api.event.functional.FunctionalEventHandler; import com.dfsek.terra.api.inject.annotations.Inject; +import com.dfsek.terra.api.noise.DerivativeNoiseSampler; import com.dfsek.terra.api.noise.NoiseSampler; import com.dfsek.terra.api.registry.CheckedRegistry; import com.dfsek.terra.api.util.reflection.TypeKey; @@ -94,7 +98,8 @@ public class NoiseAddon implements AddonInitializer { (type, o, loader, depthTracker) -> DistanceSampler.DistanceFunction.valueOf((String) o)) .applyLoader(DimensionApplicableNoiseSampler.class, DimensionApplicableNoiseSampler::new) .applyLoader(FunctionTemplate.class, FunctionTemplate::new) - .applyLoader(CubicSpline.Point.class, CubicSplinePointTemplate::new); + .applyLoader(CubicSpline.Point.class, CubicSplinePointTemplate::new) + .applyLoader(DerivativeNoiseSampler.class, DerivativeNoiseSamplerTemplate::new); noiseRegistry.register(addon.key("LINEAR"), LinearNormalizerTemplate::new); noiseRegistry.register(addon.key("NORMAL"), NormalNormalizerTemplate::new); @@ -117,7 +122,8 @@ public class NoiseAddon implements AddonInitializer { noiseRegistry.register(addon.key("PERLIN"), () -> new SimpleNoiseTemplate(PerlinSampler::new)); noiseRegistry.register(addon.key("SIMPLEX"), () -> new SimpleNoiseTemplate(SimplexSampler::new)); noiseRegistry.register(addon.key("GABOR"), GaborNoiseTemplate::new); - + noiseRegistry.register(addon.key("PSEUDOEROSION"), PseudoErosionTemplate::new); + noiseRegistry.register(addon.key("DERIVATIVE"), DerivativeFractalTemplate::new); noiseRegistry.register(addon.key("VALUE"), () -> new SimpleNoiseTemplate(ValueSampler::new)); noiseRegistry.register(addon.key("VALUE_CUBIC"), () -> new SimpleNoiseTemplate(ValueCubicSampler::new)); diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/DerivativeNoiseSamplerTemplate.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/DerivativeNoiseSamplerTemplate.java new file mode 100644 index 000000000..4d01cc58b --- /dev/null +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/DerivativeNoiseSamplerTemplate.java @@ -0,0 +1,25 @@ +package com.dfsek.terra.addons.noise.config.templates; + +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.exception.ValidationException; + +import com.dfsek.terra.api.noise.DerivativeNoiseSampler; +import com.dfsek.terra.api.noise.NoiseSampler; + + +public class DerivativeNoiseSamplerTemplate extends SamplerTemplate { + + @Value(".") + private NoiseSampler sampler; + + @Override + public boolean validate() throws ValidationException { + if (!(sampler instanceof DerivativeNoiseSampler)) throw new ValidationException("Provided sampler does not support calculating a derivative"); + return super.validate(); + } + + @Override + public DerivativeNoiseSampler get() { + return (DerivativeNoiseSampler) sampler; + } +} diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/DerivativeFractalTemplate.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/DerivativeFractalTemplate.java new file mode 100644 index 000000000..9387651a2 --- /dev/null +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/DerivativeFractalTemplate.java @@ -0,0 +1,32 @@ +package com.dfsek.terra.addons.noise.config.templates.noise; + +import com.dfsek.tectonic.api.config.template.annotations.Default; +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.noise.config.templates.SamplerTemplate; +import com.dfsek.terra.addons.noise.samplers.noise.simplex.DerivativeFractal; + + +public class DerivativeFractalTemplate extends SamplerTemplate { + + @Value("octaves") + @Default + private int octaves = 3; + + @Value("gain") + @Default + private double gain = 0.5; + + @Value("lacunarity") + @Default + private double lacunarity = 2.0; + + @Value("frequency") + @Default + private double frequency = 0.02; + + @Override + public DerivativeFractal get() { + return new DerivativeFractal(octaves, gain, lacunarity, frequency); + } +} diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/PseudoErosionTemplate.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/PseudoErosionTemplate.java new file mode 100644 index 000000000..b81c053f7 --- /dev/null +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/PseudoErosionTemplate.java @@ -0,0 +1,62 @@ +package com.dfsek.terra.addons.noise.config.templates.noise; + +import com.dfsek.tectonic.api.config.template.annotations.Default; +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.noise.config.templates.SamplerTemplate; +import com.dfsek.terra.addons.noise.samplers.noise.simplex.PseudoErosion; +import com.dfsek.terra.api.noise.DerivativeNoiseSampler; + + +public class PseudoErosionTemplate extends SamplerTemplate { + + @Value("octaves") + @Default + private int octaves = 4; + + @Value("lacunarity") + @Default + private double lacunarity = 2.0; + + @Value("gain") + @Default + private double gain = 0.5; + + @Value("slope-strength") + @Default + private double slopeStrength = 1.0; + + @Value("branch-strength") + @Default + private double branchStrength = 1.0; + + @Value("strength") + @Default + private double strength = 0.04; + + @Value("erosion-frequency") + @Default + private double erosionFrequency = 0.02; + + @Value("sampler") + private DerivativeNoiseSampler heightSampler; + + @Value("slope-mask.enable") + @Default + private boolean slopeMask = true; + + @Value("slope-mask.none") + @Default + private double slopeMaskNone = -0.5; + + @Value("slope-mask.full") + @Default + private double slopeMaskFull = 1; + + @Override + public PseudoErosion get() { + return new PseudoErosion(octaves, gain, lacunarity, + slopeStrength, branchStrength, strength, + erosionFrequency, heightSampler, slopeMask, slopeMaskFull, slopeMaskNone); + } +} diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/simplex/DerivativeFractal.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/simplex/DerivativeFractal.java new file mode 100644 index 000000000..45928a619 --- /dev/null +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/simplex/DerivativeFractal.java @@ -0,0 +1,104 @@ +package com.dfsek.terra.addons.noise.samplers.noise.simplex; + +import com.dfsek.terra.api.noise.DerivativeNoiseSampler; + +import static com.dfsek.terra.addons.noise.samplers.noise.simplex.PseudoErosion.dot; +import static com.dfsek.terra.addons.noise.samplers.noise.simplex.PseudoErosion.hash; +import static com.dfsek.terra.addons.noise.samplers.noise.simplex.PseudoErosion.hashX; +import static com.dfsek.terra.addons.noise.samplers.noise.simplex.PseudoErosion.hashY; + + +/** + * Temporary sampler that provides derivatives to test pseudoerosion, should be replaced with + * derivative versions of existing samplers + */ +public class DerivativeFractal implements DerivativeNoiseSampler { + + private final int heightOctaves; + private final double heightGain; + private final double heightLacunarity; + private final double frequency; + + public DerivativeFractal(int octaves, double gain, double lacunarity, double frequency) { + this.heightOctaves = octaves; + this.heightGain = gain; + this.heightLacunarity = lacunarity; + this.frequency = frequency; + } + + private static float[] baseNoise(float px, float py) { + float ix = (float)Math.floor(px); + float iy = (float)Math.floor(py); + float fx = px - ix; + float fy = py - iy; + + float ux = fx * fx * fx * (fx * (fx * 6.0f - 15.0f) + 10.0f); + float uy = fy * fy * fy * (fy * (fy * 6.0f - 15.0f) + 10.0f); + float dux = fx * fx * 30.0f * (fx * (fx - 2.0f) + 1.0f); + float duy = fy * fy * 30.0f * (fy * (fy - 2.0f) + 1.0f); + + float gan = hash(ix, iy); + float gax = hashX(gan); + float gay = hashY(gan); + + float gbn = hash(ix + 1, iy); + float gbx = hashX(gbn); + float gby = hashY(gbn); + + float gcn = hash(ix, iy + 1); + float gcx = hashX(gcn); + float gcy = hashY(gcn); + + float gdn = hash(ix + 1, iy + 1); + float gdx = hashX(gdn); + float gdy = hashY(gdn); + + float va = dot(gax, gay, fx, fy); + float vb = dot(gbx, gby, fx - 1, fy); + float vc = dot(gcx, gcy, fx, fy - 1); + float vd = dot(gdx, gdy, fx - 1, fy - 1); + + float u2x = gax + (gbx - gax) * ux + (gcx - gax) * uy + (gax - gbx - gcx + gdx) * ux * uy + dux * (uy * (va - vb - vc + vd) + vb - va); + float u2y = gay + (gby - gay) * ux + (gcy - gay) * uy + (gay - gby - gcy + gdy) * ux * uy + duy * (ux * (va - vb - vc + vd) + vc - va); + + return new float[] { va + ux * (vb - va) + uy * (vc - va) + ux * uy * (va - vb - vc + vd), u2x, u2y }; + } + + @Override + public double[] noised(long seed, double x, double y) { + x *= frequency; + y *= frequency; + double[] out = { 0.0f, 0.0f, 0.0f }; + float heightFreq = 1.0f; + float heightAmp = 1f; + float cumAmp = 0.0f; + for (int i = 0; i < heightOctaves; i++) { + float[] noise = baseNoise((float) (x * heightFreq), (float) (y * heightFreq)); + out[0] += noise[0] * heightAmp; + out[1] += noise[1] * heightAmp * heightFreq; + out[2] += noise[2] * heightAmp * heightFreq; + cumAmp += heightAmp; + heightAmp *= heightGain; + heightFreq *= heightLacunarity; + } + out[0] /= cumAmp; + out[1] /= cumAmp; + out[2] /= cumAmp; + return out; + } + + @Override + public double[] noised(long seed, double x, double y, double z) { + return noised(seed, x, z); + } + + @Override + public double noise(long seed, double x, double y) { + return noised(seed, x, y)[0]; + } + + @Override + public double noise(long seed, double x, double y, double z) { + return noised(seed, x, y, z)[0]; + } +} diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/simplex/PseudoErosion.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/simplex/PseudoErosion.java new file mode 100644 index 000000000..ca4ce8212 --- /dev/null +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/simplex/PseudoErosion.java @@ -0,0 +1,179 @@ +package com.dfsek.terra.addons.noise.samplers.noise.simplex; + + +import com.dfsek.terra.api.noise.DerivativeNoiseSampler; +import com.dfsek.terra.api.noise.NoiseSampler; +import com.dfsek.terra.api.util.MathUtil; + + +public class PseudoErosion implements NoiseSampler { + public static final float TAU = (float) (2.0 * Math.PI); + private static final float HASH_X = 0.3183099f; + private static final float HASH_Y = 0.3678794f; + private final int octaves; + public final double gain; + public final double lacunarity; + public final double slopeStrength; + public final double branchStrength; + public final double erosionStrength; + private final double erosionFrequency; + private final DerivativeNoiseSampler sampler; + private final boolean slopeMask; + private final double slopeMaskFullSq; + private final double slopeMaskNoneSq; + + public PseudoErosion(int octaves, double gain, double lacunarity, double slopeStrength, double branchStrength, double erosionStrength, double erosionFrequency, DerivativeNoiseSampler sampler, + boolean slopeMask, double slopeMaskFull, double slopeMaskNone) { + this.octaves = octaves; + this.gain = gain; + this.lacunarity = lacunarity; + this.slopeStrength = slopeStrength; + this.branchStrength = branchStrength; + this.erosionStrength = erosionStrength; + this.erosionFrequency = erosionFrequency; + this.sampler = sampler; + this.slopeMask = slopeMask; + // Square these values and maintain sign since they're compared to a + // squared value, otherwise a sqrt would need to be used + this.slopeMaskFullSq = slopeMaskFull * slopeMaskFull * Math.signum(slopeMaskFull); + this.slopeMaskNoneSq = slopeMaskNone * slopeMaskNone * Math.signum((slopeMaskNone)); + } + + public static float hash(float x, float y) { + float xx = x * HASH_X + HASH_Y; + float yy = y * HASH_Y + HASH_X; + + // Swapped the components here + return 16 * (xx * yy * (xx + yy)); + } + + public static float hashX(float n) { + // Swapped the components here + float nx = HASH_X * n; + return -1.0f + 2.0f * fract(nx); + } + + public static float hashY(float n) { + float ny = HASH_Y * n; + return -1.0f + 2.0f * fract(ny); + } + + public static float fract(float x) { + return (x - (float)Math.floor(x)); + } + + public static float[] erosion(float x, float y, float dirX, float dirY) { + float gridX = (float) Math.floor(x); + float gridY = (float) Math.floor(y); + float localX = x - gridX; + float localY = y - gridY; + float noise = 0.0f; + float dirOutX = 0.0f; + float dirOutY = 0.0f; + float cumAmp = 0.0f; + + for (int cellX = -2; cellX < 2; cellX++) { + for (int cellY = -2; cellY < 2; cellY++) { + // TODO - Make seed affect hashing + float cellHash = hash(gridX - (float) cellX, gridY - (float) cellY); + float cellOffsetX = hashX(cellHash) * 0.5f; + float cellOffsetY = hashY(cellHash) * 0.5f; + float cellOriginDeltaX = cellX - cellOffsetX + localX; + float cellOriginDeltaY = cellY - cellOffsetY + localY; + float cellOriginDistSq = dot(cellOriginDeltaX, cellOriginDeltaY, cellOriginDeltaX, cellOriginDeltaY); + float amp = (float)exp(-cellOriginDistSq * 2.0); // Exponentially decrease amplitude further from cell center + cumAmp += amp; + float directionalStrength = dot(cellOriginDeltaX, cellOriginDeltaY, dirX, dirY) * TAU; + noise += (float) (MathUtil.cos(directionalStrength) * amp); + float sinAngle = (float) MathUtil.sin(directionalStrength) * amp; + dirOutX -= sinAngle * (cellOriginDeltaX + dirX); + dirOutY -= sinAngle * (cellOriginDeltaY + dirY); + } + } + + noise /= cumAmp; + dirOutX /= cumAmp; + dirOutY /= cumAmp; + + return new float[] {noise, dirOutX, dirOutY}; + } + + public static double exp(double val) { + final long tmp = (long) (1512775 * val + 1072632447); + return Double.longBitsToDouble(tmp << 32); + } + + public static float smoothstep(float edge0, float edge1, float x) { + // Scale, bias and saturate x to 0..1 range + x = clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f); + // Evaluate polynomial + return x * x * (3 - 2 * x); + } + + public static float clamp(float x, float minVal, float maxVal) { + return Math.max(minVal, Math.min(maxVal, x)); + } + + public float heightMap(long seed, float x, float y) { + double[] sample = sampler.noised(seed, x, y); + float height = (float) sample[0]; + float heightDirX = (float) sample[1]; + float heightDirY = (float) sample[2]; + + // Take the curl of the normal to get the gradient facing down the slope + float baseDirX = heightDirY * (float) slopeStrength; + float baseDirY = -heightDirX * (float) slopeStrength; + + float erosion = 0.0f; + float dirX = 0.0f; + float dirY = 0.0f; + float amp = 1.0f; + float cumAmp = 0.0f; + float freq = 1.0f; + + // Stack erosion octaves + for (int i = 0; i < octaves; i++) { + float[] erosionResult = erosion( + x * freq * (float) erosionFrequency, + y * freq * (float) erosionFrequency, + baseDirX + dirY * (float) branchStrength, + baseDirY - dirX * (float) branchStrength); + erosion += erosionResult[0] * amp; + dirX += erosionResult[1] * amp * freq; + dirY += erosionResult[2] * amp * freq; + cumAmp += amp; + amp *= gain; + freq *= lacunarity; + } + + // TODO - Test different output ranges, see how they affect visuals + // Normalize erosion noise + erosion /= cumAmp; + // [-1, 1] -> [0, 1] + erosion = erosion * 0.5F + 0.5F; + + // Without masking, erosion noise in areas with small gradients tend to produce mounds, + // this reduces erosion amplitude towards smaller gradients to avoid this + if (slopeMask) { + float dirMagSq = dot(baseDirX, baseDirY, baseDirX, baseDirY); + float flatness = smoothstep((float) slopeMaskNoneSq, (float) slopeMaskFullSq, dirMagSq); + erosion *= flatness; + } + + return (float) (height + erosion * erosionStrength); + } + + public static float dot(float x1, float y1, float x2, float y2) { + return x1 * x2 + y1 * y2; + } + + @Override + public double noise(long seed, double x, double y) { + return heightMap(seed, (float) x, (float) y); + } + + @Override + public double noise(long seed, double x, double y, double z) { + return noise(seed, x, z); + } +} \ No newline at end of file diff --git a/common/api/src/main/java/com/dfsek/terra/api/noise/DerivativeNoiseSampler.java b/common/api/src/main/java/com/dfsek/terra/api/noise/DerivativeNoiseSampler.java new file mode 100644 index 000000000..12bc1e36d --- /dev/null +++ b/common/api/src/main/java/com/dfsek/terra/api/noise/DerivativeNoiseSampler.java @@ -0,0 +1,22 @@ +package com.dfsek.terra.api.noise; + +/** + * A NoiseSampler which additionally provides directional derivatives + */ +public interface DerivativeNoiseSampler extends NoiseSampler { + + static boolean providesDerivative(NoiseSampler sampler) { + if (sampler instanceof DerivativeNoiseSampler dSampler) { + return dSampler.isDerivable(); + } + return false; + } + + default boolean isDerivable() { + return false; + } + + double[] noised(long seed, double x, double y); + + double[] noised(long seed, double x, double y, double z); +} From 41652d29dfa4dc6e95911ab10e4e3919f7504433 Mon Sep 17 00:00:00 2001 From: Astrash Date: Fri, 19 Apr 2024 21:05:52 +1000 Subject: [PATCH 2/5] Use proper check for derivative sampler validation --- .../noise/config/templates/DerivativeNoiseSamplerTemplate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/DerivativeNoiseSamplerTemplate.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/DerivativeNoiseSamplerTemplate.java index 4d01cc58b..de6dbac3a 100644 --- a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/DerivativeNoiseSamplerTemplate.java +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/DerivativeNoiseSamplerTemplate.java @@ -14,7 +14,7 @@ public class DerivativeNoiseSamplerTemplate extends SamplerTemplate Date: Fri, 19 Apr 2024 21:07:53 +1000 Subject: [PATCH 3/5] Require implementing derivable check --- .../noise/samplers/noise/simplex/DerivativeFractal.java | 5 +++++ .../com/dfsek/terra/api/noise/DerivativeNoiseSampler.java | 4 +--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/simplex/DerivativeFractal.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/simplex/DerivativeFractal.java index 45928a619..bbf5d8e5c 100644 --- a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/simplex/DerivativeFractal.java +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/simplex/DerivativeFractal.java @@ -64,6 +64,11 @@ public class DerivativeFractal implements DerivativeNoiseSampler { return new float[] { va + ux * (vb - va) + uy * (vc - va) + ux * uy * (va - vb - vc + vd), u2x, u2y }; } + @Override + public boolean isDerivable() { + return true; + } + @Override public double[] noised(long seed, double x, double y) { x *= frequency; diff --git a/common/api/src/main/java/com/dfsek/terra/api/noise/DerivativeNoiseSampler.java b/common/api/src/main/java/com/dfsek/terra/api/noise/DerivativeNoiseSampler.java index 12bc1e36d..5f341ada2 100644 --- a/common/api/src/main/java/com/dfsek/terra/api/noise/DerivativeNoiseSampler.java +++ b/common/api/src/main/java/com/dfsek/terra/api/noise/DerivativeNoiseSampler.java @@ -12,9 +12,7 @@ public interface DerivativeNoiseSampler extends NoiseSampler { return false; } - default boolean isDerivable() { - return false; - } + boolean isDerivable(); double[] noised(long seed, double x, double y); From ff03b38b814ef4db4e1fcfe8fd559365735ada28 Mon Sep 17 00:00:00 2001 From: Astrash Date: Fri, 19 Apr 2024 21:25:16 +1000 Subject: [PATCH 4/5] Derivative API documentation --- .../DerivativeNoiseSamplerTemplate.java | 2 +- .../noise/simplex/DerivativeFractal.java | 2 +- .../api/noise/DerivativeNoiseSampler.java | 31 ++++++++++++++++--- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/DerivativeNoiseSamplerTemplate.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/DerivativeNoiseSamplerTemplate.java index de6dbac3a..d93054ac9 100644 --- a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/DerivativeNoiseSamplerTemplate.java +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/DerivativeNoiseSamplerTemplate.java @@ -14,7 +14,7 @@ public class DerivativeNoiseSamplerTemplate extends SamplerTemplate Date: Sat, 20 Apr 2024 15:55:59 +1000 Subject: [PATCH 5/5] Reduce number of erosion impulses A reduction of impulse evaluations from 5^2 to 3^2, original algorithm used exponential falloff function which required evaluating impulses from further away to avoid artifacts. I've changed it to a parabolic falloff function which is exactly 0 at the largest possible distance from a cell origin, meaning any cells further than that will not have an effect, preventing said artifacting from occurring. The parabolic function could be replaced with an exponential function (or any other similar easing function) as long as it has an x-intercept at maxCellDistSq. In addition, an option to control whether averaging impulses has been added as it has more of a visual impact with the aforementioned changes. --- .../noise/PseudoErosionTemplate.java | 10 +++- .../samplers/noise/simplex/PseudoErosion.java | 48 +++++++++++-------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/PseudoErosionTemplate.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/PseudoErosionTemplate.java index b81c053f7..f5e1ac46c 100644 --- a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/PseudoErosionTemplate.java +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/config/templates/noise/PseudoErosionTemplate.java @@ -53,10 +53,18 @@ public class PseudoErosionTemplate extends SamplerTemplate { @Default private double slopeMaskFull = 1; + @Value("jitter") + @Default + private double jitterModifier = 1; + + @Value("average-impulses") + @Default + private boolean averageErosionImpulses = true; + @Override public PseudoErosion get() { return new PseudoErosion(octaves, gain, lacunarity, slopeStrength, branchStrength, strength, - erosionFrequency, heightSampler, slopeMask, slopeMaskFull, slopeMaskNone); + erosionFrequency, heightSampler, slopeMask, slopeMaskFull, slopeMaskNone, jitterModifier, averageErosionImpulses); } } diff --git a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/simplex/PseudoErosion.java b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/simplex/PseudoErosion.java index ca4ce8212..41b1e2db8 100644 --- a/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/simplex/PseudoErosion.java +++ b/common/addons/config-noise-function/src/main/java/com/dfsek/terra/addons/noise/samplers/noise/simplex/PseudoErosion.java @@ -21,9 +21,14 @@ public class PseudoErosion implements NoiseSampler { private final boolean slopeMask; private final double slopeMaskFullSq; private final double slopeMaskNoneSq; + private final double jitter; + private final double maxCellDistSq; + private final double maxCellDistSqRecip; + private final boolean averageErosionImpulses; public PseudoErosion(int octaves, double gain, double lacunarity, double slopeStrength, double branchStrength, double erosionStrength, double erosionFrequency, DerivativeNoiseSampler sampler, - boolean slopeMask, double slopeMaskFull, double slopeMaskNone) { + boolean slopeMask, double slopeMaskFull, double slopeMaskNone, double jitterModifier, + boolean averageErosionImpulses) { this.octaves = octaves; this.gain = gain; this.lacunarity = lacunarity; @@ -37,6 +42,10 @@ public class PseudoErosion implements NoiseSampler { // squared value, otherwise a sqrt would need to be used this.slopeMaskFullSq = slopeMaskFull * slopeMaskFull * Math.signum(slopeMaskFull); this.slopeMaskNoneSq = slopeMaskNone * slopeMaskNone * Math.signum((slopeMaskNone)); + this.jitter = 0.43701595 * jitterModifier; + this.averageErosionImpulses = averageErosionImpulses; + this.maxCellDistSq = 1 + jitter * jitter; + this.maxCellDistSqRecip = 1 / maxCellDistSq; } public static float hash(float x, float y) { @@ -62,26 +71,25 @@ public class PseudoErosion implements NoiseSampler { return (x - (float)Math.floor(x)); } - public static float[] erosion(float x, float y, float dirX, float dirY) { - float gridX = (float) Math.floor(x); - float gridY = (float) Math.floor(y); - float localX = x - gridX; - float localY = y - gridY; + public float[] erosion(float x, float y, float dirX, float dirY) { + int gridX = Math.round(x); + int gridY = Math.round(y); float noise = 0.0f; float dirOutX = 0.0f; float dirOutY = 0.0f; float cumAmp = 0.0f; - for (int cellX = -2; cellX < 2; cellX++) { - for (int cellY = -2; cellY < 2; cellY++) { + for (int cellX = gridX - 1; cellX <= gridX + 1; cellX++) { + for (int cellY = gridY - 1; cellY <= gridY + 1; cellY++) { // TODO - Make seed affect hashing - float cellHash = hash(gridX - (float) cellX, gridY - (float) cellY); - float cellOffsetX = hashX(cellHash) * 0.5f; - float cellOffsetY = hashY(cellHash) * 0.5f; - float cellOriginDeltaX = cellX - cellOffsetX + localX; - float cellOriginDeltaY = cellY - cellOffsetY + localY; - float cellOriginDistSq = dot(cellOriginDeltaX, cellOriginDeltaY, cellOriginDeltaX, cellOriginDeltaY); - float amp = (float)exp(-cellOriginDistSq * 2.0); // Exponentially decrease amplitude further from cell center + float cellHash = hash(cellX, cellY); + float cellOffsetX = (float) (hashX(cellHash) * jitter); + float cellOffsetY = (float) (hashY(cellHash) * jitter); + float cellOriginDeltaX = (x - cellX) + cellOffsetX; + float cellOriginDeltaY = (y - cellY) + cellOffsetY; + float cellOriginDistSq = cellOriginDeltaX * cellOriginDeltaX + cellOriginDeltaY * cellOriginDeltaY; + if (cellOriginDistSq > maxCellDistSq) continue; // Skip calculating cells too far away + float ampTmp = (float) ((cellOriginDistSq * maxCellDistSqRecip) - 1); float amp = ampTmp * ampTmp; // Decrease cell amplitude further away cumAmp += amp; float directionalStrength = dot(cellOriginDeltaX, cellOriginDeltaY, dirX, dirY) * TAU; noise += (float) (MathUtil.cos(directionalStrength) * amp); @@ -90,11 +98,11 @@ public class PseudoErosion implements NoiseSampler { dirOutY -= sinAngle * (cellOriginDeltaY + dirY); } } - - noise /= cumAmp; - dirOutX /= cumAmp; - dirOutY /= cumAmp; - + if (averageErosionImpulses && cumAmp != 0) { + noise /= cumAmp; + dirOutX /= cumAmp; + dirOutY /= cumAmp; + } return new float[] {noise, dirOutX, dirOutY}; }