diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/collection/FastRandomCollection.java b/worldedit-core/src/main/java/com/boydti/fawe/object/collection/FastRandomCollection.java index 41ac563cf..d283cfb57 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/collection/FastRandomCollection.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/collection/FastRandomCollection.java @@ -5,12 +5,29 @@ import com.boydti.fawe.util.MathMan; import java.util.ArrayList; import java.util.Map; +import java.util.Optional; public class FastRandomCollection extends RandomCollection { - private T[] values; + private final T[] values; - public FastRandomCollection(Map weights, SimpleRandom random) { - super(weights, random); + private FastRandomCollection(T[] values, SimpleRandom random) { + super(random); + this.values = values; + } + + /** + * Create a new FastRandomCollection if the given values and weights match the criteria. + * The criteria may change at any point, so this method isn't guaranteed to return a + * non-empty Optional in any case. + * + * @param weights the weight of the values. + * @param random the random generator to use for this collection. + * @param the value type. + * @return an {@link Optional} containing the new collection if it could + * be created, {@link Optional#empty()} otherwise. + * @see RandomCollection for API usage. + */ + public static Optional> create(Map weights, SimpleRandom random) { int max = 0; int[] counts = new int[weights.size()]; Double[] weightDoubles = weights.values().toArray(new Double[0]); @@ -18,7 +35,7 @@ public class FastRandomCollection extends RandomCollection { int weight = (int) (weightDoubles[i] * 100); counts[i] = weight; if (weight != (weightDoubles[i] * 100)) { - throw new IllegalArgumentException("Too small"); + return Optional.empty(); } if (weight > max) { max = weight; @@ -26,7 +43,7 @@ public class FastRandomCollection extends RandomCollection { } int gcd = MathMan.gcd(counts); if (max / gcd > 100000) { - throw new IllegalArgumentException("Too large"); + return Optional.empty(); } ArrayList parsed = new ArrayList<>(); for (Map.Entry entry : weights.entrySet()) { @@ -35,11 +52,14 @@ public class FastRandomCollection extends RandomCollection { parsed.add(entry.getKey()); } } - this.values = (T[]) parsed.toArray(); + @SuppressWarnings("unchecked") + T[] values = (T[]) parsed.toArray(); + FastRandomCollection fastRandomCollection = new FastRandomCollection<>(values, random); + return Optional.of(fastRandomCollection); } @Override public T next(int x, int y, int z) { - return values[random.nextInt(x, y, z, values.length)]; + return values[getRandom().nextInt(x, y, z, values.length)]; } } diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/collection/RandomCollection.java b/worldedit-core/src/main/java/com/boydti/fawe/object/collection/RandomCollection.java index fc12c1ac6..ea83647bf 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/collection/RandomCollection.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/collection/RandomCollection.java @@ -6,19 +6,33 @@ import java.util.Map; import static com.google.common.base.Preconditions.checkNotNull; +/** + * A RandomCollection holds multiple values that can be accessed by using + * {@link RandomCollection#next(int, int, int)}. The returned value is + * determined by a given {@link SimpleRandom} implementation. + * + * @param the type of values the collection holds. + */ public abstract class RandomCollection { - protected SimpleRandom random; + private SimpleRandom random; - public RandomCollection(Map weights, SimpleRandom random) { + protected RandomCollection(SimpleRandom random) { this.random = random; } + /** + * Return a new RandomCollection. The implementation may differ depending on the + * given arguments but there is no need to differ. + * + * @param weights the weighted map. + * @param random the random number generator. + * @param the type the collection holds. + * @return a RandomCollection using the given weights and the RNG. + */ public static RandomCollection of(Map weights, SimpleRandom random) { - try { - return new FastRandomCollection<>(weights, random); - } catch (IllegalArgumentException ignore) { - return new SimpleRandomCollection<>(weights, random); - } + checkNotNull(random); + return FastRandomCollection.create(weights, random) + .orElse(new SimpleRandomCollection<>(weights, random)); } public void setRandom(SimpleRandom random) { diff --git a/worldedit-core/src/main/java/com/boydti/fawe/object/collection/SimpleRandomCollection.java b/worldedit-core/src/main/java/com/boydti/fawe/object/collection/SimpleRandomCollection.java index e99593c0f..b3f7cdd76 100644 --- a/worldedit-core/src/main/java/com/boydti/fawe/object/collection/SimpleRandomCollection.java +++ b/worldedit-core/src/main/java/com/boydti/fawe/object/collection/SimpleRandomCollection.java @@ -11,8 +11,16 @@ public class SimpleRandomCollection extends RandomCollection { private final NavigableMap map = new TreeMap<>(); private double total = 0; + /** + * Create a {@link RandomCollection} from a weighted map and a RNG. + * It is recommended to use {@link RandomCollection#of(Map, SimpleRandom)} + * instead of this constructor. + * + * @param weights the weighted map. + * @param random the random number generator. + */ public SimpleRandomCollection(Map weights, SimpleRandom random) { - super(weights, random); + super(random); for (Map.Entry entry : weights.entrySet()) { add(entry.getValue(), entry.getKey()); } @@ -24,7 +32,8 @@ public class SimpleRandomCollection extends RandomCollection { map.put(total, result); } + @Override public E next(int x, int y, int z) { - return map.ceilingEntry(random.nextDouble(x, y, z)).getValue(); + return map.ceilingEntry(getRandom().nextDouble(x, y, z)).getValue(); } } 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); + } +}