From bb05bd24d9df4b79e391728f785f797b4290f93e Mon Sep 17 00:00:00 2001 From: Hannes Greule Date: Sat, 15 Aug 2020 13:17:44 +0200 Subject: [PATCH] Implement more noise patterns --- .../fawe/object/random/NoiseRandom.java | 34 +++++++++ .../fawe/object/random/SimpleRandom.java | 23 +++++- .../fawe/object/random/SimplexRandom.java | 14 ---- .../extension/factory/PatternFactory.java | 6 ++ .../parser/pattern/NoisePatternParser.java | 74 +++++++++++++++++++ .../parser/pattern/PerlinPatternParser.java | 17 +++++ .../RidgedMultiFractalPatternParser.java | 18 +++++ .../parser/pattern/SimplexPatternParser.java | 45 +---------- .../parser/pattern/VoronoiPatternParser.java | 18 +++++ .../math/noise/SimplexNoiseGenerator.java | 23 ++++++ 10 files changed, 215 insertions(+), 57 deletions(-) create mode 100644 worldedit-core/src/main/java/com/boydti/fawe/object/random/NoiseRandom.java delete mode 100644 worldedit-core/src/main/java/com/boydti/fawe/object/random/SimplexRandom.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/NoisePatternParser.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/PerlinPatternParser.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RidgedMultiFractalPatternParser.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/VoronoiPatternParser.java create mode 100644 worldedit-core/src/main/java/com/sk89q/worldedit/math/noise/SimplexNoiseGenerator.java diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/random/NoiseRandom.java b/worldedit-core/src/main/java/com/boydti/fawe/object/random/NoiseRandom.java new file mode 100644 index 000000000..774496c84 --- /dev/null +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/random/NoiseRandom.java @@ -0,0 +1,34 @@ +package com.boydti.fawe.object.random; + +import com.sk89q.worldedit.math.Vector3; +import com.sk89q.worldedit.math.noise.NoiseGenerator; + +public class NoiseRandom implements SimpleRandom { + + private final NoiseGenerator generator; + private final double scale; + + /** + * Create a new NoiseRandom instance using a specific {@link NoiseGenerator} and a scale. + * + * @param generator The generator to use for the noise + * @param scale The scale of the noise + */ + public NoiseRandom(NoiseGenerator generator, double scale) { + this.generator = generator; + this.scale = scale; + } + + @Override + public double nextDouble(int x, int y, int z) { + return cap(this.generator.noise(Vector3.at(x, y, z).multiply(this.scale))); + } + + // workaround for noise generators returning [0, 1] + private double cap(double d) { + if (d >= 1.0) { + return 0x1.fffffffffffffp-1; // very close to 1 but less + } + return d; + } +} diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/random/SimpleRandom.java b/worldedit-core/src/main/java/com/boydti/fawe/object/random/SimpleRandom.java index 1b3848e19..eb49def14 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/random/SimpleRandom.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/random/SimpleRandom.java @@ -2,10 +2,29 @@ package com.boydti.fawe.object.random; public interface SimpleRandom { + /** + * Generate a random double from three integer components. + * The generated value is between 0 (inclusive) and 1 (exclusive). + * + * @param x the first component + * @param y the second component + * @param z the third component + * @return a double between 0 (inclusive) and 1 (exclusive) + */ double nextDouble(int x, int y, int z); - default int nextInt(int x, int y, int z, int len) { + /** + * Generate a random integer from three integer components. + * The generated value is between 0 (inclusive) and 1 (exclusive) + * + * @param x the first component + * @param y the second component + * @param z the third component + * @param bound the upper bound (exclusive) + * @return a random integer between 0 (inclusive) and {@code bound} (exclusive) + */ + default int nextInt(int x, int y, int z, int bound) { double val = nextDouble(x, y, z); - return (int) (val * len); + return (int) (val * bound); } } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/random/SimplexRandom.java b/worldedit-core/src/main/java/com/boydti/fawe/object/random/SimplexRandom.java deleted file mode 100644 index 703737c39..000000000 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/random/SimplexRandom.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.boydti.fawe.object.random; - -public class SimplexRandom implements SimpleRandom { - private final double scale; - - public SimplexRandom(double scale) { - this.scale = scale; - } - - @Override - public double nextDouble(int x, int y, int z) { - return (SimplexNoise.noise(x * scale, y * scale, z * scale) + 1) * 0.5; - } -} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java index 41bece423..06d8ab46a 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/PatternFactory.java @@ -22,11 +22,14 @@ package com.sk89q.worldedit.extension.factory; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.extension.factory.parser.pattern.BlockCategoryPatternParser; import com.sk89q.worldedit.extension.factory.parser.pattern.ClipboardPatternParser; +import com.sk89q.worldedit.extension.factory.parser.pattern.PerlinPatternParser; import com.sk89q.worldedit.extension.factory.parser.pattern.RandomPatternParser; import com.sk89q.worldedit.extension.factory.parser.pattern.RandomStatePatternParser; +import com.sk89q.worldedit.extension.factory.parser.pattern.RidgedMultiFractalPatternParser; import com.sk89q.worldedit.extension.factory.parser.pattern.SimplexPatternParser; import com.sk89q.worldedit.extension.factory.parser.pattern.SingleBlockPatternParser; import com.sk89q.worldedit.extension.factory.parser.pattern.TypeOrStateApplyingPatternParser; +import com.sk89q.worldedit.extension.factory.parser.pattern.VoronoiPatternParser; import com.sk89q.worldedit.function.pattern.Pattern; import com.sk89q.worldedit.internal.registry.AbstractFactory; @@ -58,6 +61,9 @@ public final class PatternFactory extends AbstractFactory { // FAWE register(new SimplexPatternParser(worldEdit)); + register(new VoronoiPatternParser(worldEdit)); + register(new PerlinPatternParser(worldEdit)); + register(new RidgedMultiFractalPatternParser(worldEdit)); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/NoisePatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/NoisePatternParser.java new file mode 100644 index 000000000..e8b2a0e30 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/NoisePatternParser.java @@ -0,0 +1,74 @@ +package com.sk89q.worldedit.extension.factory.parser.pattern; + +import com.boydti.fawe.object.random.NoiseRandom; +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.extension.factory.parser.RichParser; +import com.sk89q.worldedit.extension.input.InputParseException; +import com.sk89q.worldedit.extension.input.ParserContext; +import com.sk89q.worldedit.function.pattern.Pattern; +import com.sk89q.worldedit.function.pattern.RandomPattern; +import com.sk89q.worldedit.math.noise.NoiseGenerator; +import com.sk89q.worldedit.world.block.BlockStateHolder; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Supplier; +import java.util.stream.Stream; + +public abstract class NoisePatternParser extends RichParser { + + private final String name; + private final Supplier generatorSupplier; + + /** + * Create a new noise parser with a defined name, e.g. {@code #simplex}. + * + * @param worldEdit the worldedit instance. + * @param name the name of this noise. + * @param generatorSupplier the supplier to get a {@link NoiseGenerator} instance from. + */ + protected NoisePatternParser(WorldEdit worldEdit, String name, Supplier generatorSupplier) { + super(worldEdit, '#' + name); + this.name = name; + this.generatorSupplier = generatorSupplier; + } + + @Override + protected Stream getSuggestions(String argumentInput, int index) { + if (index == 0) { + return suggestPositiveDoubles(argumentInput); + } + if (index == 1) { + return worldEdit.getPatternFactory().getSuggestions(argumentInput).stream(); + } + return Stream.empty(); + } + + @Override + protected Pattern parseFromInput(@NotNull String[] arguments, ParserContext context) { + if (arguments.length != 2) { + throw new InputParseException(this.name + " requires a scale and a pattern, e.g. #" + + this.name + "[5][dirt,stone]"); + } + double scale = parseScale(arguments[0]); + Pattern inner = worldEdit.getPatternFactory().parseFromInput(arguments[1], context); + if (inner instanceof RandomPattern) { + return new RandomPattern(new NoiseRandom(this.generatorSupplier.get(), scale), (RandomPattern) inner); + } else if (inner instanceof BlockStateHolder) { + return inner; // single blocks won't have any impact on how a noise behaves + } else { + throw new InputParseException("Pattern " + inner.getClass().getSimpleName() + + " cannot be used with #" + this.name); + } + } + + /** + * Modifies the given argument to match the requirements of the noise generator. + * + * @param argument the parsed scale argument. + * @return the modified scale. + */ + protected double parseScale(String argument) { + double scale = Double.parseDouble(argument); + return 1d / Math.max(1, scale); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/PerlinPatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/PerlinPatternParser.java new file mode 100644 index 000000000..1d968c8b7 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/PerlinPatternParser.java @@ -0,0 +1,17 @@ +package com.sk89q.worldedit.extension.factory.parser.pattern; + +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.math.noise.PerlinNoise; + +public class PerlinPatternParser extends NoisePatternParser { + private static final String PERLIN_NAME = "perlin"; + + /** + * Create a new perlin noise parser. + * + * @param worldEdit the worldedit instance. + */ + public PerlinPatternParser(WorldEdit worldEdit) { + super(worldEdit, PERLIN_NAME, PerlinNoise::new); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RidgedMultiFractalPatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RidgedMultiFractalPatternParser.java new file mode 100644 index 000000000..969acc1e6 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/RidgedMultiFractalPatternParser.java @@ -0,0 +1,18 @@ +package com.sk89q.worldedit.extension.factory.parser.pattern; + +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.math.noise.RidgedMultiFractalNoise; + +public class RidgedMultiFractalPatternParser extends NoisePatternParser { + + private static final String RIDGED_MULTI_FRACTAL_NAME = "rmf"; + + /** + * Create a new ridged multi fractal noise parser. + * + * @param worldEdit the worldedit instance. + */ + public RidgedMultiFractalPatternParser(WorldEdit worldEdit) { + super(worldEdit, RIDGED_MULTI_FRACTAL_NAME, RidgedMultiFractalNoise::new); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/SimplexPatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/SimplexPatternParser.java index f8f4a0311..d408c031f 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/SimplexPatternParser.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/SimplexPatternParser.java @@ -1,49 +1,12 @@ package com.sk89q.worldedit.extension.factory.parser.pattern; -import com.boydti.fawe.object.random.SimplexRandom; import com.sk89q.worldedit.WorldEdit; -import com.sk89q.worldedit.extension.factory.parser.RichParser; -import com.sk89q.worldedit.extension.input.InputParseException; -import com.sk89q.worldedit.extension.input.ParserContext; -import com.sk89q.worldedit.function.pattern.Pattern; -import com.sk89q.worldedit.function.pattern.RandomPattern; -import com.sk89q.worldedit.world.block.BlockStateHolder; -import org.jetbrains.annotations.NotNull; +import com.sk89q.worldedit.math.noise.SimplexNoiseGenerator; -import java.util.stream.Stream; - -public class SimplexPatternParser extends RichParser { - private static final String SIMPLEX_PREFIX = "#simplex"; +public class SimplexPatternParser extends NoisePatternParser { + private static final String SIMPLEX_NAME = "simplex"; public SimplexPatternParser(WorldEdit worldEdit) { - super(worldEdit, SIMPLEX_PREFIX); - } - - @Override - protected Stream getSuggestions(String argumentInput, int index) { - if (index == 0) { - return suggestPositiveDoubles(argumentInput); - } - if (index == 1) { - return worldEdit.getPatternFactory().getSuggestions(argumentInput).stream(); - } - return Stream.empty(); - } - - @Override - protected Pattern parseFromInput(@NotNull String[] arguments, ParserContext context) { - if (arguments.length != 2) { - throw new InputParseException("Simplex requires a scale and a pattern, e.g. #simplex[5][dirt,stone]"); - } - double scale = Double.parseDouble(arguments[0]); - scale = 1d / Math.max(1, scale); - Pattern inner = worldEdit.getPatternFactory().parseFromInput(arguments[1], context); - if (inner instanceof RandomPattern) { - return new RandomPattern(new SimplexRandom(scale), (RandomPattern) inner); - } else if (inner instanceof BlockStateHolder) { - return inner; // single blocks won't have any impact on how simplex behaves - } else { - throw new InputParseException("Pattern " + inner.getClass().getSimpleName() + " cannot be used with #simplex"); - } + super(worldEdit, SIMPLEX_NAME, SimplexNoiseGenerator::new); } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/VoronoiPatternParser.java b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/VoronoiPatternParser.java new file mode 100644 index 000000000..5afbf53fd --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/extension/factory/parser/pattern/VoronoiPatternParser.java @@ -0,0 +1,18 @@ +package com.sk89q.worldedit.extension.factory.parser.pattern; + +import com.sk89q.worldedit.WorldEdit; +import com.sk89q.worldedit.math.noise.VoronoiNoise; + +public class VoronoiPatternParser extends NoisePatternParser { + + private static final String VORONOI_NAME = "voronoi"; + + /** + * Create a new voronoi noise parser. + * + * @param worldEdit the worldedit instance. + */ + public VoronoiPatternParser(WorldEdit worldEdit) { + super(worldEdit, VORONOI_NAME, VoronoiNoise::new); + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/math/noise/SimplexNoiseGenerator.java b/worldedit-core/src/main/java/com/sk89q/worldedit/math/noise/SimplexNoiseGenerator.java new file mode 100644 index 000000000..a0b9cea68 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/math/noise/SimplexNoiseGenerator.java @@ -0,0 +1,23 @@ +package com.sk89q.worldedit.math.noise; + +import com.boydti.fawe.object.random.SimplexNoise; +import com.sk89q.worldedit.math.Vector2; +import com.sk89q.worldedit.math.Vector3; + +public class SimplexNoiseGenerator implements NoiseGenerator { + + @Override + public float noise(Vector2 position) { + return convert(SimplexNoise.noise(position.getX(), position.getZ())); + } + + @Override + public float noise(Vector3 position) { + return convert(SimplexNoise.noise(position.getX(), position.getY(), position.getZ())); + } + + private float convert(double d) { + // we need to go from [-1, 1] to [0, 1] and from double to float + return (float) ((d + 1) * 0.5); + } +}