From 8b501ddcdead459d5b7d7442cccd3915326ec459 Mon Sep 17 00:00:00 2001 From: dfsek Date: Thu, 21 Jan 2021 18:18:43 -0700 Subject: [PATCH] redo biome blending --- .../api/util/mutable/MutableInteger.java | 51 ++++++++ .../api/util/mutable/MutablePrimitive.java | 7 + .../terra/api/world/biome/Generator.java | 6 +- .../terra/config/base/ConfigPackTemplate.java | 22 +--- .../config/builder/GeneratorBuilder.java | 8 +- .../terra/config/factories/BiomeFactory.java | 1 + .../terra/config/templates/BiomeTemplate.java | 9 ++ .../generation/config/WorldGenerator.java | 18 ++- .../dfsek/terra/generation/math/Sampler.java | 4 +- .../terra/generation/math/SamplerCache.java | 2 +- .../math/interpolation/ChunkInterpolator.java | 120 ++++++++---------- .../BukkitChunkGeneratorWrapper.java | 3 +- 12 files changed, 154 insertions(+), 97 deletions(-) create mode 100644 common/src/main/java/com/dfsek/terra/api/util/mutable/MutableInteger.java create mode 100644 common/src/main/java/com/dfsek/terra/api/util/mutable/MutablePrimitive.java diff --git a/common/src/main/java/com/dfsek/terra/api/util/mutable/MutableInteger.java b/common/src/main/java/com/dfsek/terra/api/util/mutable/MutableInteger.java new file mode 100644 index 000000000..0bb1c4415 --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/util/mutable/MutableInteger.java @@ -0,0 +1,51 @@ +package com.dfsek.terra.api.util.mutable; + +public class MutableInteger extends Number implements MutablePrimitive { + private int value; + + public MutableInteger() { + + } + + public MutableInteger(int init) { + this.value = init; + } + + @Override + public int intValue() { + return value; + } + + @Override + public long longValue() { + return value; + } + + @Override + public float floatValue() { + return value; + } + + @Override + public double doubleValue() { + return value; + } + + @Override + public Integer get() { + return value; + } + + @Override + public void set(Integer value) { + this.value = value; + } + + public void add() { + add(1); + } + + public void add(int add) { + this.value += add; + } +} diff --git a/common/src/main/java/com/dfsek/terra/api/util/mutable/MutablePrimitive.java b/common/src/main/java/com/dfsek/terra/api/util/mutable/MutablePrimitive.java new file mode 100644 index 000000000..f1848007d --- /dev/null +++ b/common/src/main/java/com/dfsek/terra/api/util/mutable/MutablePrimitive.java @@ -0,0 +1,7 @@ +package com.dfsek.terra.api.util.mutable; + +public interface MutablePrimitive { + T get(); + + void set(T value); +} diff --git a/common/src/main/java/com/dfsek/terra/api/world/biome/Generator.java b/common/src/main/java/com/dfsek/terra/api/world/biome/Generator.java index 2b7026d1a..20c0929bf 100644 --- a/common/src/main/java/com/dfsek/terra/api/world/biome/Generator.java +++ b/common/src/main/java/com/dfsek/terra/api/world/biome/Generator.java @@ -13,10 +13,14 @@ public interface Generator { * @param z - The z coordinate. * @return double - Noise value at the specified coordinates. */ - double getNoise(int x, int y, int z); + double getNoise(double x, double y, double z); double getElevation(int x, int z); + int getBlendDistance(); + + double getWeight(); + /** * Gets the BlocPalette to generate the biome with. * diff --git a/common/src/main/java/com/dfsek/terra/config/base/ConfigPackTemplate.java b/common/src/main/java/com/dfsek/terra/config/base/ConfigPackTemplate.java index f71c79d56..207a38b2b 100644 --- a/common/src/main/java/com/dfsek/terra/config/base/ConfigPackTemplate.java +++ b/common/src/main/java/com/dfsek/terra/config/base/ConfigPackTemplate.java @@ -2,18 +2,15 @@ package com.dfsek.terra.config.base; import com.dfsek.tectonic.annotations.Default; import com.dfsek.tectonic.annotations.Value; -import com.dfsek.tectonic.config.ValidatedConfigTemplate; -import com.dfsek.tectonic.exception.ValidationException; -import com.dfsek.terra.api.math.MathUtil; +import com.dfsek.tectonic.config.ConfigTemplate; import com.dfsek.terra.biome.BiomeProvider; import com.dfsek.terra.generation.config.NoiseBuilder; -import net.jafama.FastMath; import java.util.HashMap; import java.util.Map; @SuppressWarnings({"unused", "FieldMayBeFinal"}) -public class ConfigPackTemplate implements ValidatedConfigTemplate { +public class ConfigPackTemplate implements ConfigTemplate { @Value("id") private String id; @@ -36,10 +33,6 @@ public class ConfigPackTemplate implements ValidatedConfigTemplate { @Default private double blendAmp = 4.0D; - @Value("blend.terrain.base") - @Default - private int baseBlend = 4; - @Value("structures.locatable") @Default private Map locatable = new HashMap<>(); @@ -167,9 +160,6 @@ public class ConfigPackTemplate implements ValidatedConfigTemplate { return erodeOctaves; } - public int getBaseBlend() { - return baseBlend; - } public int getElevationBlend() { return elevationBlend; @@ -178,12 +168,4 @@ public class ConfigPackTemplate implements ValidatedConfigTemplate { public Map getLocatable() { return locatable; } - - @Override - public boolean validate() throws ValidationException { - if(!MathUtil.equals(FastMath.log(baseBlend) / FastMath.log(2d), FastMath.round(FastMath.log(baseBlend) / FastMath.log(2d)))) { - throw new ValidationException("Biome base blend value \"" + baseBlend + "\" is not a power of 2."); - } - return true; - } } diff --git a/common/src/main/java/com/dfsek/terra/config/builder/GeneratorBuilder.java b/common/src/main/java/com/dfsek/terra/config/builder/GeneratorBuilder.java index fe0dbaae8..f8b7af143 100644 --- a/common/src/main/java/com/dfsek/terra/config/builder/GeneratorBuilder.java +++ b/common/src/main/java/com/dfsek/terra/config/builder/GeneratorBuilder.java @@ -36,13 +36,19 @@ public class GeneratorBuilder { private double elevationWeight; + private int blendDistance; + public WorldGenerator build(long seed) { synchronized(gens) { - return gens.computeIfAbsent(seed, k -> new WorldGenerator(seed, noiseEquation, elevationEquation, varScope, noiseBuilderMap, palettes, slantPalettes, interpolateElevation, noise2d, base, biomeNoise.build((int) seed), elevationWeight)); + return gens.computeIfAbsent(seed, k -> new WorldGenerator(seed, noiseEquation, elevationEquation, varScope, noiseBuilderMap, palettes, slantPalettes, noise2d, base, biomeNoise.build((int) seed), elevationWeight, blendDistance)); } } + public void setBlendDistance(int blendDistance) { + this.blendDistance = blendDistance; + } + public void setBiomeNoise(NoiseBuilder biomeNoise) { this.biomeNoise = biomeNoise; } diff --git a/common/src/main/java/com/dfsek/terra/config/factories/BiomeFactory.java b/common/src/main/java/com/dfsek/terra/config/factories/BiomeFactory.java index 82ba62bb9..e294d44bf 100644 --- a/common/src/main/java/com/dfsek/terra/config/factories/BiomeFactory.java +++ b/common/src/main/java/com/dfsek/terra/config/factories/BiomeFactory.java @@ -28,6 +28,7 @@ public class BiomeFactory implements TerraFactory { generatorBuilder.setBase(template.getNoise2dBase()); generatorBuilder.setElevationWeight(template.getElevationWeight()); generatorBuilder.setBiomeNoise(template.getBiomeNoise()); + generatorBuilder.setBlendDistance(template.getBlendDistance()); return new UserDefinedBiome(template.getVanilla(), generatorBuilder, template); diff --git a/common/src/main/java/com/dfsek/terra/config/templates/BiomeTemplate.java b/common/src/main/java/com/dfsek/terra/config/templates/BiomeTemplate.java index f4e4b898e..731bdf958 100644 --- a/common/src/main/java/com/dfsek/terra/config/templates/BiomeTemplate.java +++ b/common/src/main/java/com/dfsek/terra/config/templates/BiomeTemplate.java @@ -70,6 +70,11 @@ public class BiomeTemplate extends AbstractableTemplate implements ValidatedConf @Abstractable private NoiseBuilder biomeNoise = new NoiseBuilder(); + @Value("blend.distance") + @Abstractable + @Default + private int blendDistance = 3; + @Value("erode") @Abstractable @Default @@ -165,6 +170,10 @@ public class BiomeTemplate extends AbstractableTemplate implements ValidatedConf return color; } + public int getBlendDistance() { + return blendDistance; + } + public boolean interpolateElevation() { return interpolateElevation; } diff --git a/common/src/main/java/com/dfsek/terra/generation/config/WorldGenerator.java b/common/src/main/java/com/dfsek/terra/generation/config/WorldGenerator.java index 193787a6c..13da7a77b 100644 --- a/common/src/main/java/com/dfsek/terra/generation/config/WorldGenerator.java +++ b/common/src/main/java/com/dfsek/terra/generation/config/WorldGenerator.java @@ -29,21 +29,21 @@ public class WorldGenerator implements Generator { private final Variable zVar; private final Variable elevationXVar; private final Variable elevationZVar; - private final boolean elevationInterpolation; private final boolean noise2d; private final double base; private final NoiseSampler biomeNoise; private final double elevationWeight; + private final int blendDistance; - public WorldGenerator(long seed, String equation, String elevateEquation, Scope vScope, Map noiseBuilders, PaletteHolder palettes, PaletteHolder slantPalettes, boolean elevationInterpolation, boolean noise2d, double base, NoiseSampler biomeNoise, double elevationWeight) { + public WorldGenerator(long seed, String equation, String elevateEquation, Scope vScope, Map noiseBuilders, PaletteHolder palettes, PaletteHolder slantPalettes, boolean noise2d, double base, NoiseSampler biomeNoise, double elevationWeight, int blendDistance) { this.palettes = palettes; this.slantPalettes = slantPalettes; - this.elevationInterpolation = elevationInterpolation; this.noise2d = noise2d; this.base = base; this.biomeNoise = biomeNoise; this.elevationWeight = elevationWeight; + this.blendDistance = blendDistance; Parser p = new Parser(); p.registerFunction("rand", new RandomFunction()); @@ -96,7 +96,17 @@ public class WorldGenerator implements Generator { } @Override - public synchronized double getNoise(int x, int y, int z) { + public int getBlendDistance() { + return blendDistance; + } + + @Override + public double getWeight() { + return 1; + } + + @Override + public synchronized double getNoise(double x, double y, double z) { xVar.setValue(x); if(!noise2d) yVar.setValue(y); zVar.setValue(z); diff --git a/common/src/main/java/com/dfsek/terra/generation/math/Sampler.java b/common/src/main/java/com/dfsek/terra/generation/math/Sampler.java index 581027616..06f93ccfd 100644 --- a/common/src/main/java/com/dfsek/terra/generation/math/Sampler.java +++ b/common/src/main/java/com/dfsek/terra/generation/math/Sampler.java @@ -10,8 +10,8 @@ public class Sampler { private final ChunkInterpolator interpolator; private final ElevationInterpolator elevationInterpolator; - public Sampler(int x, int z, BiomeProvider provider, World world, int elevationSmooth, int generationSmooth) { - this.interpolator = new ChunkInterpolator(world, x, z, provider, generationSmooth); + public Sampler(int x, int z, BiomeProvider provider, World world, int elevationSmooth) { + this.interpolator = new ChunkInterpolator(world, x, z, provider); this.elevationInterpolator = new ElevationInterpolator(world, x, z, provider, elevationSmooth); } diff --git a/common/src/main/java/com/dfsek/terra/generation/math/SamplerCache.java b/common/src/main/java/com/dfsek/terra/generation/math/SamplerCache.java index 06964ca04..e5cd2593f 100644 --- a/common/src/main/java/com/dfsek/terra/generation/math/SamplerCache.java +++ b/common/src/main/java/com/dfsek/terra/generation/math/SamplerCache.java @@ -50,7 +50,7 @@ public class SamplerCache { public Sampler load(@NotNull Long key) { int cx = (int) (key >> 32); int cz = (int) key.longValue(); - return new Sampler(cx, cz, terraWorld.getBiomeProvider(), world, terraWorld.getConfig().getTemplate().getElevationBlend(), terraWorld.getConfig().getTemplate().getBaseBlend()); + return new Sampler(cx, cz, terraWorld.getBiomeProvider(), world, terraWorld.getConfig().getTemplate().getElevationBlend()); } }); terraWorld = main.getWorld(world); diff --git a/common/src/main/java/com/dfsek/terra/generation/math/interpolation/ChunkInterpolator.java b/common/src/main/java/com/dfsek/terra/generation/math/interpolation/ChunkInterpolator.java index f66f9d955..c788ab873 100644 --- a/common/src/main/java/com/dfsek/terra/generation/math/interpolation/ChunkInterpolator.java +++ b/common/src/main/java/com/dfsek/terra/generation/math/interpolation/ChunkInterpolator.java @@ -1,20 +1,20 @@ package com.dfsek.terra.generation.math.interpolation; import com.dfsek.terra.api.platform.world.World; +import com.dfsek.terra.api.util.mutable.MutableInteger; import com.dfsek.terra.api.world.biome.Generator; import com.dfsek.terra.biome.BiomeProvider; import net.jafama.FastMath; +import java.util.HashMap; +import java.util.Map; + /** * Class to abstract away the 16 Interpolators needed to generate a chunk.
* Contains method to get interpolated noise at a coordinate within the chunk. */ public class ChunkInterpolator { private final Interpolator3[][][] interpGrid = new Interpolator3[4][64][4]; - private final Generator[][] gens = new Generator[7][7]; - private final boolean[][] needsBiomeInterp = new boolean[5][5]; - private final double[][][] noiseStorage = new double[7][7][65]; - private final int smooth; /** * Instantiates a 3D ChunkInterpolator at a pair of chunk coordinates. @@ -23,36 +23,34 @@ public class ChunkInterpolator { * @param chunkZ Z coordinate of the chunk. * @param provider BiomeGrid to use for noise fetching. */ - public ChunkInterpolator(World w, int chunkX, int chunkZ, BiomeProvider provider, int smooth) { + public ChunkInterpolator(World w, int chunkX, int chunkZ, BiomeProvider provider) { + Generator[][] gens = new Generator[5][5]; int xOrigin = chunkX << 4; int zOrigin = chunkZ << 4; - this.smooth = smooth; - - for(int x = -1; x < 6; x++) { - for(int z = -1; z < 6; z++) { - gens[x + 1][z + 1] = provider.getBiome(xOrigin + (x * smooth), zOrigin + (z * smooth)).getGenerator(w); - } - } for(int x = 0; x < 5; x++) { for(int z = 0; z < 5; z++) { - needsBiomeInterp[x][z] = compareGens(x + 1, z + 1); + gens[x][z] = provider.getBiome(xOrigin + (x * 4), zOrigin + (z * 4)).getGenerator(w); } } - for(byte x = -1; x < 6; x++) { - for(byte z = -1; z < 6; z++) { - Generator generator = gens[x + 1][z + 1]; - if(generator.is2d()) { - double n = generator.getNoise((x * smooth) + xOrigin, 0, (z * smooth) + zOrigin); - for(int y = 0; y < 65; y++) { - noiseStorage[x + 1][z + 1][y] = n + noise2dExtrude(y << 2, generator.get2dBase()); - } - } else { - for(int y = 0; y < 65; y++) { - noiseStorage[x + 1][z + 1][y] = generator.getNoise((x * smooth) + xOrigin, y << 2, (z * smooth) + zOrigin); + double[][][] noiseStorage = new double[5][5][65]; + + for(int x = 0; x < 5; x++) { + for(int z = 0; z < 5; z++) { + Generator generator = gens[x][z]; + Map genMap = new HashMap<>(); + + int blend = generator.getBlendDistance(); + for(int xi = -blend; xi <= blend; xi++) { + for(int zi = -blend; zi <= blend; zi++) { + genMap.computeIfAbsent(provider.getBiome(xOrigin + ((x + xi) << 2), zOrigin + ((z + zi) << 2)).getGenerator(w), g -> new MutableInteger(0)).add(); // Increment by 1 } } + + for(int y = 0; y < 65; y++) { + noiseStorage[x][z][y] = computeNoise(genMap, (x << 2) + xOrigin, y << 2, (z << 2) + zOrigin); + } } } @@ -60,54 +58,48 @@ public class ChunkInterpolator { for(byte z = 0; z < 4; z++) { for(int y = 0; y < 64; y++) { interpGrid[x][y][z] = new Interpolator3( - biomeAvg(x, y, z), - biomeAvg(x + 1, y, z), - biomeAvg(x, y + 1, z), - biomeAvg(x + 1, y + 1, z), - biomeAvg(x, y, z + 1), - biomeAvg(x + 1, y, z + 1), - biomeAvg(x, y + 1, z + 1), - biomeAvg(x + 1, y + 1, z + 1)); + biomeAvg(x, y, z, noiseStorage), + biomeAvg(x + 1, y, z, noiseStorage), + biomeAvg(x, y + 1, z, noiseStorage), + biomeAvg(x + 1, y + 1, z, noiseStorage), + biomeAvg(x, y, z + 1, noiseStorage), + biomeAvg(x + 1, y, z + 1, noiseStorage), + biomeAvg(x, y + 1, z + 1, noiseStorage), + biomeAvg(x + 1, y + 1, z + 1, noiseStorage)); } } } } + private static double computeNoise(Map gens, double x, double y, double z) { + double n = 0; + double div = 0; + for(Map.Entry entry : gens.entrySet()) { + Generator gen = entry.getKey(); + int weight = entry.getValue().get(); + double noise = computeNoise(gen, x, y, z); + + n += noise * weight; + div += gen.getWeight() * weight; + } + return n / div; + } + + private static double computeNoise(Generator generator, double x, double y, double z) { + if(generator.is2d()) return generator.getNoise(x, 0, z) + noise2dExtrude(y, generator.get2dBase()); + else return generator.getNoise(x, y, z); + } + private static int reRange(int value, int high) { return FastMath.max(FastMath.min(value, high), 0); } - private boolean compareGens(int x, int z) { - Generator comp = gens[x][z]; - if(!comp.equals(gens[x + 1][z])) return true; - - if(!comp.equals(gens[x][z + 1])) return true; - - if(!comp.equals(gens[x - 1][z])) return true; - - if(!comp.equals(gens[x][z - 1])) return true; - - if(!comp.equals(gens[x + 1][z + 1])) return true; - - if(!comp.equals(gens[x - 1][z - 1])) return true; - - if(!comp.equals(gens[x + 1][z - 1])) return true; - - return !comp.equals(gens[x - 1][z + 1]); + private static double noise2dExtrude(double y, double base) { + return ((-FastMath.pow2((y / base))) + 1); } - private double biomeAvg(int x, int y, int z) { - if(needsBiomeInterp[x][z]) { - double t = 0d; - for(int xi = 0; xi <= 2; xi++) { - for(int zi = 0; zi <= 2; zi++) { - t += noiseStorage[x + xi][z + zi][y]; - } - } - return t / 9d; - } else { - return noiseStorage[x + 1][z + 1][y]; - } + private double biomeAvg(int x, int y, int z, double[][][] noise) { + return noise[x][z][y]; } /** @@ -118,10 +110,6 @@ public class ChunkInterpolator { * @return double - The interpolated noise at the coordinates. */ public double getNoise(double x, double y, double z) { - return interpGrid[reRange(((int) x) / smooth, 3)][reRange(((int) y) / 4, 63)][reRange(((int) z) / smooth, 3)].trilerp((x % smooth) / smooth, (y % 4) / 4, (z % smooth) / smooth); - } - - private static double noise2dExtrude(double y, double base) { - return ((-FastMath.pow2((y / base))) + 1); + return interpGrid[reRange(((int) x) / 4, 3)][reRange(((int) y) / 4, 63)][reRange(((int) z) / 4, 3)].trilerp((x % 4) / 4, (y % 4) / 4, (z % 4) / 4); } } diff --git a/platforms/bukkit/src/main/java/com/dfsek/terra/bukkit/generator/BukkitChunkGeneratorWrapper.java b/platforms/bukkit/src/main/java/com/dfsek/terra/bukkit/generator/BukkitChunkGeneratorWrapper.java index 673ad3ed2..909466ac4 100644 --- a/platforms/bukkit/src/main/java/com/dfsek/terra/bukkit/generator/BukkitChunkGeneratorWrapper.java +++ b/platforms/bukkit/src/main/java/com/dfsek/terra/bukkit/generator/BukkitChunkGeneratorWrapper.java @@ -10,7 +10,6 @@ import com.dfsek.terra.bukkit.world.BukkitAdapter; import com.dfsek.terra.bukkit.world.BukkitBiomeGrid; import com.dfsek.terra.config.lang.LangUtil; import com.dfsek.terra.debug.Debug; -import com.dfsek.terra.population.CavePopulator; import com.dfsek.terra.population.FloraPopulator; import com.dfsek.terra.population.OrePopulator; import com.dfsek.terra.population.StructurePopulator; @@ -90,7 +89,7 @@ public class BukkitChunkGeneratorWrapper extends ChunkGenerator implements Gener @Override public @NotNull List getDefaultPopulators(@NotNull World world) { - return Stream.of(new CavePopulator(main), new StructurePopulator(main), popMan).map(BukkitPopulatorWrapper::new).collect(Collectors.toList()); + return Stream.of(new StructurePopulator(main), popMan).map(BukkitPopulatorWrapper::new).collect(Collectors.toList()); } @Override