diff --git a/build.gradle.kts b/build.gradle.kts index 1d057add0..20122663d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -42,6 +42,7 @@ afterEvaluate { forImmediateSubProjects(":platforms") { configureDistribution() } + project(":platforms:bukkit:common").configureDistribution() forSubProjects(":common:addons") { apply(plugin = "com.github.johnrengelman.shadow") diff --git a/common/addons/biome-provider-pipeline-v2/LICENSE b/common/addons/biome-provider-pipeline-v2/LICENSE new file mode 100644 index 000000000..64c1cd516 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-2021 Polyhedral Development + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/common/addons/biome-provider-pipeline-v2/README.md b/common/addons/biome-provider-pipeline-v2/README.md new file mode 100644 index 000000000..d1d216dfd --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/README.md @@ -0,0 +1,12 @@ +# biome-provider-pipeline-2 + +The second version of the Biome Pipeline, a procedural biome provider that uses a series +of "stages" to apply "mutations" to a 2D grid of biomes. + +Version 2 is a re-implementation of the original addon with the primary goal of providing +consistent scaling for noise relative to the world +(See https://github.com/PolyhedralDev/Terra/issues/264 for more details), and has been +included as a separate addon to maintain parity with packs utilizing the first version. + +This addon registers the `PIPELINE` biome provider type, and all associated +configurations. \ No newline at end of file diff --git a/common/addons/biome-provider-pipeline-v2/build.gradle.kts b/common/addons/biome-provider-pipeline-v2/build.gradle.kts new file mode 100644 index 000000000..8ca2e3b81 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/build.gradle.kts @@ -0,0 +1,12 @@ +version = version("1.0.0") + +dependencies { + compileOnlyApi(project(":common:addons:manifest-addon-loader")) + + implementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama) + testImplementation("net.jafama", "jafama", Versions.Libraries.Internal.jafama) +} + +tasks.named("shadowJar") { + relocate("net.jafama", "com.dfsek.terra.addons.biome.pipeline.lib.jafama") +} \ No newline at end of file diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/BiomePipelineAddon.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/BiomePipelineAddon.java new file mode 100644 index 000000000..66c1a7908 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/BiomePipelineAddon.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2; + +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import java.util.function.Supplier; + +import com.dfsek.terra.addons.biome.pipeline.v2.config.BiomePipelineTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.config.PipelineBiomeLoader; +import com.dfsek.terra.addons.biome.pipeline.v2.config.source.SamplerSourceTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.expander.ExpanderStageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator.BorderListStageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator.BorderStageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator.ReplaceListStageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator.ReplaceStageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator.SmoothStageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Source; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.manifest.api.AddonInitializer; +import com.dfsek.terra.api.Platform; +import com.dfsek.terra.api.addon.BaseAddon; +import com.dfsek.terra.api.event.events.config.pack.ConfigPackPostLoadEvent; +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.registry.CheckedRegistry; +import com.dfsek.terra.api.registry.Registry; +import com.dfsek.terra.api.util.reflection.TypeKey; +import com.dfsek.terra.api.world.biome.Biome; +import com.dfsek.terra.api.world.biome.generation.BiomeProvider; + + +public class BiomePipelineAddon implements AddonInitializer { + + public static final TypeKey>> SOURCE_REGISTRY_KEY = new TypeKey<>() { + }; + + public static final TypeKey>> STAGE_REGISTRY_KEY = new TypeKey<>() { + }; + public static final TypeKey>> PROVIDER_REGISTRY_KEY = new TypeKey<>() { + }; + @Inject + private Platform platform; + + @Inject + private BaseAddon addon; + + @Override + public void initialize() { + platform.getEventManager() + .getHandler(FunctionalEventHandler.class) + .register(addon, ConfigPackPreLoadEvent.class) + .then(event -> { + CheckedRegistry>> providerRegistry = event.getPack().getOrCreateRegistry( + PROVIDER_REGISTRY_KEY); + providerRegistry.register(addon.key("PIPELINE"), BiomePipelineTemplate::new); + }) + .then(event -> { + CheckedRegistry>> sourceRegistry = event.getPack().getOrCreateRegistry( + SOURCE_REGISTRY_KEY); + sourceRegistry.register(addon.key("SAMPLER"), SamplerSourceTemplate::new); + }) + .then(event -> { + CheckedRegistry>> stageRegistry = event.getPack().getOrCreateRegistry( + STAGE_REGISTRY_KEY); + stageRegistry.register(addon.key("FRACTAL_EXPAND"), ExpanderStageTemplate::new); + stageRegistry.register(addon.key("SMOOTH"), SmoothStageTemplate::new); + stageRegistry.register(addon.key("REPLACE"), ReplaceStageTemplate::new); + stageRegistry.register(addon.key("REPLACE_LIST"), ReplaceListStageTemplate::new); + stageRegistry.register(addon.key("BORDER"), BorderStageTemplate::new); + stageRegistry.register(addon.key("BORDER_LIST"), BorderListStageTemplate::new); + }) + .failThrough(); + platform.getEventManager() + .getHandler(FunctionalEventHandler.class) + .register(addon, ConfigPackPostLoadEvent.class) + .then(event -> { + Registry biomeRegistry = event.getPack().getRegistry(Biome.class); + event.getPack().applyLoader(PipelineBiome.class, new PipelineBiomeLoader(biomeRegistry)); + }); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/BiomePipelineColumn.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/BiomePipelineColumn.java new file mode 100644 index 000000000..9b3e1391a --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/BiomePipelineColumn.java @@ -0,0 +1,71 @@ +package com.dfsek.terra.addons.biome.pipeline.v2; + +import java.util.function.Consumer; + +import com.dfsek.terra.api.util.Column; +import com.dfsek.terra.api.util.function.IntIntObjConsumer; +import com.dfsek.terra.api.util.function.IntObjConsumer; +import com.dfsek.terra.api.world.biome.Biome; +import com.dfsek.terra.api.world.biome.generation.BiomeProvider; + + +public class BiomePipelineColumn implements Column { + private final int min; + private final int max; + + private final int x; + private final int z; + private final Biome biome; + + protected BiomePipelineColumn(BiomeProvider biomeProvider, int min, int max, int x, int z, long seed) { + this.min = min; + this.max = max; + this.x = x; + this.z = z; + this.biome = biomeProvider.getBiome(x, 0, z, seed); + } + + @Override + public int getMinY() { + return min; + } + + @Override + public int getMaxY() { + return max; + } + + @Override + public int getX() { + return x; + } + + @Override + public int getZ() { + return z; + } + + @Override + public Biome get(int y) { + return biome; + } + + @Override + public void forRanges(int resolution, IntIntObjConsumer consumer) { + consumer.accept(min, max, biome); + } + + @Override + public void forEach(Consumer consumer) { + for(int y = min; y < max; y++) { + consumer.accept(biome); + } + } + + @Override + public void forEach(IntObjConsumer consumer) { + for(int y = min; y < max; y++) { + consumer.accept(y, biome); + } + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/PipelineBiomeProvider.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/PipelineBiomeProvider.java new file mode 100644 index 000000000..9748741a8 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/PipelineBiomeProvider.java @@ -0,0 +1,109 @@ +package com.dfsek.terra.addons.biome.pipeline.v2; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.BiomeChunk; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Pipeline; +import com.dfsek.terra.addons.biome.pipeline.v2.api.SeededVector; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; +import net.jafama.FastMath; + +import java.util.Comparator; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.StreamSupport; + +import com.dfsek.terra.api.noise.NoiseSampler; +import com.dfsek.terra.api.registry.key.StringIdentifiable; +import com.dfsek.terra.api.util.Column; +import com.dfsek.terra.api.world.biome.Biome; +import com.dfsek.terra.api.world.biome.generation.BiomeProvider; + + +public class PipelineBiomeProvider implements BiomeProvider { + + private final LoadingCache biomeChunkCache; + private final int chunkSize; + private final int resolution; + private final NoiseSampler mutator; + private final double noiseAmp; + private final Set biomes; + + public PipelineBiomeProvider(Pipeline pipeline, int resolution, NoiseSampler mutator, double noiseAmp) { + this.resolution = resolution; + this.mutator = mutator; + this.noiseAmp = noiseAmp; + this.chunkSize = pipeline.getChunkSize(); + this.biomeChunkCache = Caffeine.newBuilder() + .maximumSize(1024) + .build(pipeline::generateChunk); + + Set biomeSet = new HashSet<>(); + pipeline.getSource().getBiomes().forEach(biomeSet::add); + Iterable result = biomeSet; + for(Stage stage : pipeline.getStages()) { + result = stage.getBiomes(result); + } + this.biomes = new HashSet<>(); + Iterable finalResult = result; + result.forEach(pipelineBiome -> { + if(pipelineBiome.isPlaceholder()) { + + StringBuilder biomeList = new StringBuilder("\n"); + StreamSupport.stream(finalResult.spliterator(), false) + .sorted(Comparator.comparing(StringIdentifiable::getID)) + .forEach(delegate -> biomeList + .append(" - ") + .append(delegate.getID()) + .append(':') + .append(delegate.getClass().getCanonicalName()) + .append('\n')); + throw new IllegalArgumentException("Biome Pipeline leaks placeholder biome \"" + pipelineBiome.getID() + + "\". Ensure there is a stage to guarantee replacement of the placeholder biome. Biomes: " + + biomeList); + } + this.biomes.add(pipelineBiome.getBiome()); + }); + } + + @Override + public Biome getBiome(int x, int y, int z, long seed) { + return getBiome(x, z, seed); + } + + public Biome getBiome(int x, int z, long seed) { + + x += mutator.noise(seed + 1, x, z) * noiseAmp; + z += mutator.noise(seed + 2, x, z) * noiseAmp; + + x /= resolution; + z /= resolution; + + int chunkX = FastMath.floorDiv(x, chunkSize); + int chunkZ = FastMath.floorDiv(z, chunkSize); + + int chunkWorldX = chunkX * chunkSize; + int chunkWorldZ = chunkZ * chunkSize; + + int xInChunk = x - chunkWorldX; + int zInChunk = z - chunkWorldZ; + + return biomeChunkCache.get(new SeededVector(seed, chunkWorldX, chunkWorldZ)).get(xInChunk, zInChunk).getBiome(); + } + + @Override + public Iterable getBiomes() { + return biomes; + } + + @Override + public Column getColumn(int x, int z, long seed, int min, int max) { + return new BiomePipelineColumn(this, min, max, x, z, seed); + } + + @Override + public int resolution() { + return resolution; + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/BiomeChunk.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/BiomeChunk.java new file mode 100644 index 000000000..818652f59 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/BiomeChunk.java @@ -0,0 +1,10 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api; + + +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; + + +public interface BiomeChunk { + + PipelineBiome get(int xInChunk, int zInChunk); +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Expander.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Expander.java new file mode 100644 index 000000000..177e74694 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Expander.java @@ -0,0 +1,29 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api; + +import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl.ViewPoint; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; + + +/** + * Resizes the internal grid of a BiomeChunk when applied, serves the purpose of + * filling in null biomes as a result of this resizing. + */ +public interface Expander extends Stage { + + PipelineBiome fillBiome(ViewPoint viewPoint); + + @Override + default int maxRelativeReadDistance() { + return 0; + } + + @Override + default PipelineBiome apply(ViewPoint viewPoint) { + PipelineBiome currentBiome = viewPoint.getBiome(); + if(currentBiome == null) { + return fillBiome(viewPoint); + } else { + return currentBiome; + } + } +} \ No newline at end of file diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Pipeline.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Pipeline.java new file mode 100644 index 000000000..cfb9ee9a8 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Pipeline.java @@ -0,0 +1,14 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api; + +import java.util.List; + + +public interface Pipeline { + BiomeChunk generateChunk(SeededVector worldCoordinates); + + int getChunkSize(); + + Source getSource(); + + List getStages(); +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/SeededVector.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/SeededVector.java new file mode 100644 index 000000000..6581ca50c --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/SeededVector.java @@ -0,0 +1,19 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api; + +public record SeededVector(long seed, int x, int z) { + + @Override + public boolean equals(Object obj) { + if(obj instanceof SeededVector that) { + return this.z == that.z && this.x == that.x && this.seed == that.seed; + } + return false; + } + + @Override + public int hashCode() { + int code = x; + code = 31 * code + z; + return 31 * code + ((int) (seed ^ (seed >>> 32))); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Source.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Source.java new file mode 100644 index 000000000..bfb135653 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Source.java @@ -0,0 +1,11 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api; + + +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; + + +public interface Source { + PipelineBiome get(long seed, int x, int z); + + Iterable getBiomes(); +} \ No newline at end of file diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Stage.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Stage.java new file mode 100644 index 000000000..ee2c75e89 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/Stage.java @@ -0,0 +1,15 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api; + +import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl.ViewPoint; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; + + +public interface Stage { + PipelineBiome apply(ViewPoint viewPoint); + + int maxRelativeReadDistance(); + + default Iterable getBiomes(Iterable biomes) { + return biomes; + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/DelegatedPipelineBiome.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/DelegatedPipelineBiome.java new file mode 100644 index 000000000..978e68b06 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/DelegatedPipelineBiome.java @@ -0,0 +1,40 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api.biome; + +import java.util.Set; + +import com.dfsek.terra.api.world.biome.Biome; + + +public final class DelegatedPipelineBiome implements PipelineBiome { + private final Biome biome; + + public DelegatedPipelineBiome(Biome biome) { + this.biome = biome; + } + + @Override + public Biome getBiome() { + return biome; + } + + @Override + public int hashCode() { + return biome.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(!(obj instanceof DelegatedPipelineBiome that)) return false; + return that.biome.equals(this.biome); + } + + @Override + public Set getTags() { + return biome.getTags(); + } + + @Override + public String getID() { + return biome.getID(); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/PipelineBiome.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/PipelineBiome.java new file mode 100644 index 000000000..ccde84022 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/PipelineBiome.java @@ -0,0 +1,35 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api.biome; + +import java.util.Set; + +import com.dfsek.terra.api.registry.key.StringIdentifiable; +import com.dfsek.terra.api.world.biome.Biome; + + +public interface PipelineBiome extends StringIdentifiable { + Biome getBiome(); + + static PipelineBiome placeholder(String id) { + return new PlaceholderPipelineBiome(id); + } + + static PipelineBiome from(Biome biome) { + return new DelegatedPipelineBiome(biome); + } + + static PipelineBiome self() { + return SelfPipelineBiome.INSTANCE; + } + + Set getTags(); + + default boolean isPlaceholder() { + return false; + } + + default boolean isSelf() { + return false; + } + + +} \ No newline at end of file diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/PlaceholderPipelineBiome.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/PlaceholderPipelineBiome.java new file mode 100644 index 000000000..87bd67120 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/PlaceholderPipelineBiome.java @@ -0,0 +1,51 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api.biome; + +import java.util.HashSet; +import java.util.Set; + +import com.dfsek.terra.api.world.biome.Biome; + + +final class PlaceholderPipelineBiome implements PipelineBiome { + private final Set tags; + private final String id; + + public PlaceholderPipelineBiome(String id) { + this.id = id; + tags = new HashSet<>(); + tags.add(id); + tags.add("ALL"); + } + + @Override + public Biome getBiome() { + throw new UnsupportedOperationException("Cannot get raw biome from placeholder pipeline biome"); + } + + @Override + public Set getTags() { + return tags; + } + + @Override + public String getID() { + return id; + } + + @Override + public boolean isPlaceholder() { + return true; + } + + @Override + public int hashCode() { + return id.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if(!(obj instanceof PlaceholderPipelineBiome that)) return false; + + return this.id.equals(that.id); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/SelfPipelineBiome.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/SelfPipelineBiome.java new file mode 100644 index 000000000..80583c1cb --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/api/biome/SelfPipelineBiome.java @@ -0,0 +1,40 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.api.biome; + +import java.util.Collections; +import java.util.Set; + +import com.dfsek.terra.api.world.biome.Biome; + + +final class SelfPipelineBiome implements PipelineBiome { + public static final SelfPipelineBiome INSTANCE = new SelfPipelineBiome(); + + private SelfPipelineBiome() { + + } + + @Override + public Biome getBiome() { + throw new UnsupportedOperationException("Cannot get biome from self delegate"); + } + + @Override + public boolean isSelf() { + return true; + } + + @Override + public boolean isPlaceholder() { + return true; + } + + @Override + public Set getTags() { + return Collections.emptySet(); + } + + @Override + public String getID() { + return "SELF"; + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/BiomePipelineTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/BiomePipelineTemplate.java new file mode 100644 index 000000000..52ee4292d --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/BiomePipelineTemplate.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config; + +import com.dfsek.tectonic.api.config.template.annotations.Default; +import com.dfsek.tectonic.api.config.template.annotations.Description; +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import java.util.List; + +import com.dfsek.terra.addons.biome.pipeline.v2.PipelineBiomeProvider; +import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.PipelineImpl; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Source; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.noise.NoiseSampler; +import com.dfsek.terra.api.world.biome.generation.BiomeProvider; + + +@SuppressWarnings({ "FieldMayBeFinal", "unused" }) +public class BiomePipelineTemplate implements ObjectTemplate { + @Value("resolution") + @Default + @Description(""" + The resolution at which to sample biomes. + + Larger values are quadratically faster, but produce lower quality results. + For example, a value of 3 would sample every 3 blocks.""") + protected @Meta int resolution = 1; + + @Value("pipeline.source") + @Description("The Biome Source to use for initial population of biomes.") + private @Meta Source source; + + @Value("pipeline.stages") + @Description("A list of pipeline stages to apply to the result of #source") + private @Meta List<@Meta Stage> stages; + + @Value("blend.sampler") + @Default + @Description("A sampler to use for blending the edges of biomes via domain warping.") + protected @Meta NoiseSampler blendSampler = NoiseSampler.zero(); + + @Value("blend.amplitude") + @Default + @Description("The amplitude at which to perform blending.") + protected @Meta double blendAmplitude = 0d; + + @Override + public BiomeProvider get() { + return new PipelineBiomeProvider(new PipelineImpl(source, stages, resolution, 500), resolution, blendSampler, blendAmplitude); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/PipelineBiomeLoader.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/PipelineBiomeLoader.java new file mode 100644 index 000000000..5e7a08578 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/PipelineBiomeLoader.java @@ -0,0 +1,32 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.config; + +import com.dfsek.tectonic.api.depth.DepthTracker; +import com.dfsek.tectonic.api.exception.LoadException; +import com.dfsek.tectonic.api.loader.ConfigLoader; +import com.dfsek.tectonic.api.loader.type.TypeLoader; +import org.jetbrains.annotations.NotNull; + +import java.lang.reflect.AnnotatedType; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.api.registry.Registry; +import com.dfsek.terra.api.world.biome.Biome; + + +public class PipelineBiomeLoader implements TypeLoader { + private final Registry biomeRegistry; + + public PipelineBiomeLoader(Registry biomeRegistry) { + this.biomeRegistry = biomeRegistry; + } + + @Override + public PipelineBiome load(@NotNull AnnotatedType t, @NotNull Object c, @NotNull ConfigLoader loader, DepthTracker depthTracker) + throws LoadException { + if(c.equals("SELF")) return PipelineBiome.self(); + return biomeRegistry + .getByID((String) c) + .map(PipelineBiome::from) + .orElseGet(() -> PipelineBiome.placeholder((String) c)); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/source/SamplerSourceTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/source/SamplerSourceTemplate.java new file mode 100644 index 000000000..dbfb1ef31 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/source/SamplerSourceTemplate.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config.source; + +import com.dfsek.tectonic.api.config.template.annotations.Description; +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Source; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.source.SamplerSource; +import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.noise.NoiseSampler; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; + + +public class SamplerSourceTemplate extends SourceTemplate { + @Value("sampler") + @Description("The sampler used to distribute biomes.") + private @Meta NoiseSampler noise; + + @Value("biomes") + @Description("The biomes to be distributed.") + private @Meta ProbabilityCollection<@Meta PipelineBiome> biomes; + + @Override + public Source get() { + return new SamplerSource(biomes, noise); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/source/SourceTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/source/SourceTemplate.java new file mode 100644 index 000000000..ba61f8940 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/source/SourceTemplate.java @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config.source; + +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Source; + + +public abstract class SourceTemplate implements ObjectTemplate { + +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/StageTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/StageTemplate.java new file mode 100644 index 000000000..c1cea771e --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/StageTemplate.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config.stage; + +import com.dfsek.tectonic.api.config.template.annotations.Description; +import com.dfsek.tectonic.api.config.template.annotations.Value; +import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.noise.NoiseSampler; + + +public abstract class StageTemplate implements ObjectTemplate { + @Value("sampler") + @Description("Sampler to use for stage distribution.") + protected @Meta NoiseSampler noise; +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/expander/ExpanderStageTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/expander/ExpanderStageTemplate.java new file mode 100644 index 000000000..312880d3b --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/expander/ExpanderStageTemplate.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config.stage.expander; + +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Expander; +import com.dfsek.terra.addons.biome.pipeline.v2.stage.expander.FractalExpander; + + +public class ExpanderStageTemplate extends StageTemplate { + @Override + public Expander get() { + return new FractalExpander(noise); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/BorderListStageTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/BorderListStageTemplate.java new file mode 100644 index 000000000..57d5c7deb --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/BorderListStageTemplate.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator; + +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import java.util.Map; + +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators.BorderListStage; +import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; + + +@SuppressWarnings("unused") +public class BorderListStageTemplate extends StageTemplate { + @Value("from") + private @Meta String from; + + @Value("default-replace") + private @Meta String defaultReplace; + + @Value("default-to") + private @Meta ProbabilityCollection<@Meta PipelineBiome> defaultTo; + + @Value("replace") + private @Meta Map<@Meta PipelineBiome, @Meta ProbabilityCollection<@Meta PipelineBiome>> replace; + + + @Override + public Stage get() { + return new BorderListStage(replace, from, defaultReplace, noise, defaultTo); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/BorderStageTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/BorderStageTemplate.java new file mode 100644 index 000000000..605eeb9f4 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/BorderStageTemplate.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator; + +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators.BorderStage; +import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; + + +@SuppressWarnings("unused") +public class BorderStageTemplate extends StageTemplate { + @Value("from") + private @Meta String from; + + @Value("replace") + private @Meta String replace; + + @Value("to") + private @Meta ProbabilityCollection<@Meta PipelineBiome> to; + + @Override + public Stage get() { + return new BorderStage(from, replace, noise, to); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/ReplaceListStageTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/ReplaceListStageTemplate.java new file mode 100644 index 000000000..be9ade61c --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/ReplaceListStageTemplate.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator; + +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import java.util.Map; + +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators.ReplaceListStage; +import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; + + +@SuppressWarnings("unused") +public class ReplaceListStageTemplate extends StageTemplate { + @Value("default-from") + private @Meta String defaultFrom; + + @Value("default-to") + private @Meta ProbabilityCollection<@Meta PipelineBiome> defaultTo; + + @Value("to") + private @Meta Map<@Meta PipelineBiome, @Meta ProbabilityCollection<@Meta PipelineBiome>> replace; + + @Override + public Stage get() { + return new ReplaceListStage(replace, defaultFrom, defaultTo, noise); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/ReplaceStageTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/ReplaceStageTemplate.java new file mode 100644 index 000000000..6f273a294 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/ReplaceStageTemplate.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator; + +import com.dfsek.tectonic.api.config.template.annotations.Value; + +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators.ReplaceStage; +import com.dfsek.terra.api.config.meta.Meta; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; + + +@SuppressWarnings("unused") +public class ReplaceStageTemplate extends StageTemplate { + @Value("from") + private @Meta String from; + + @Value("to") + private @Meta ProbabilityCollection<@Meta PipelineBiome> to; + + @Override + public Stage get() { + return new ReplaceStage(from, to, noise); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/SmoothStageTemplate.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/SmoothStageTemplate.java new file mode 100644 index 000000000..a13e441af --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/config/stage/mutator/SmoothStageTemplate.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.config.stage.mutator; + +import com.dfsek.terra.addons.biome.pipeline.v2.config.stage.StageTemplate; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators.SmoothStage; + + +public class SmoothStageTemplate extends StageTemplate { + @Override + public Stage get() { + return new SmoothStage(noise); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/pipeline/BiomeChunkImpl.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/pipeline/BiomeChunkImpl.java new file mode 100644 index 000000000..cf739fa77 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/pipeline/BiomeChunkImpl.java @@ -0,0 +1,217 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.pipeline; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.BiomeChunk; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Expander; +import com.dfsek.terra.addons.biome.pipeline.v2.api.SeededVector; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import net.jafama.FastMath; + +import java.util.List; + + +public class BiomeChunkImpl implements BiomeChunk { + + private PipelineBiome[][] biomes; + private final SeededVector worldOrigin; + private final int chunkOriginArrayIndex; + private final int worldCoordinateScale; + + public BiomeChunkImpl(SeededVector worldOrigin, PipelineImpl pipeline) { + + this.worldOrigin = worldOrigin; + this.chunkOriginArrayIndex = pipeline.getChunkOriginArrayIndex(); + this.worldCoordinateScale = pipeline.getResolution(); + + int size = pipeline.getArraySize(); + + int expanderCount = pipeline.getExpanderCount(); + int expansionsApplied = 0; + + // Allocate working arrays + this.biomes = new PipelineBiome[size][size]; + PipelineBiome[][] lookupArray = new PipelineBiome[size][size]; + // A second lookup array is required such that stage application doesn't affect lookups, otherwise application may cascade + + // Construct working grid + int gridOrigin = 0; + int gridInterval = calculateGridInterval(expanderCount, expansionsApplied); + int gridSize = (size / gridInterval); + gridSize += expanderCount > 0 ? 1 : 0; // Add an extra border if expansion occurs + + // Fill working grid with initial cells + for(int gridX = 0; gridX < gridSize; gridX++) { + for(int gridZ = 0; gridZ < gridSize; gridZ++) { + int xIndex = gridOrigin + gridX * gridInterval; + int zIndex = gridOrigin + gridZ * gridInterval; + biomes[xIndex][zIndex] = pipeline.getSource().get(worldOrigin.seed(), xIndexToWorldCoordinate(xIndex), zIndexToWorldCoordinate(zIndex)); + } + } + + for(Stage stage : pipeline.getStages()) { + if(stage instanceof Expander) { + // Shrink working grid size, the expander will fill in null cells (as a result of shrinking the grid) during mutation + expansionsApplied++; + gridInterval = calculateGridInterval(expanderCount, expansionsApplied); + gridSize = expandSize(gridSize); + } + + int stageReadDistance = stage.maxRelativeReadDistance(); + if(stageReadDistance > 0) { + // Discard edges such that adjacent lookups are only ran on valid cells + gridSize = contractBordersFromSize(gridSize, stageReadDistance); + gridOrigin += stageReadDistance * gridInterval; + } + + // Cycle arrays, the previously populated array is swapped to be used for lookups, and the result of the stage application + // overwrites the previous lookup array. This saves having to allocate a new array copy each time + PipelineBiome[][] tempArray = biomes; + biomes = lookupArray; + lookupArray = tempArray; + + // Apply stage to working grid + for(int gridZ = 0; gridZ < gridSize; gridZ = gridZ + 1) { + for(int gridX = 0; gridX < gridSize; gridX = gridX + 1) { + int xIndex = gridOrigin + gridX * gridInterval; + int zIndex = gridOrigin + gridZ * gridInterval; + biomes[xIndex][zIndex] = stage.apply(new ViewPoint(this, gridInterval, gridX, gridZ, xIndex, zIndex, lookupArray)); + } + } + } + } + + @Override + public PipelineBiome get(int xInChunk, int zInChunk) { + int xIndex = xInChunk + chunkOriginArrayIndex; + int zIndex = zInChunk + chunkOriginArrayIndex; + return biomes[xIndex][zIndex]; + } + + private int xIndexToWorldCoordinate(int xIndex) { + return (worldOrigin.x() + xIndex - chunkOriginArrayIndex) * worldCoordinateScale; + } + + private int zIndexToWorldCoordinate(int zIndex) { + return (worldOrigin.z() + zIndex - chunkOriginArrayIndex) * worldCoordinateScale; + } + + protected static int initialSizeToArraySize(int expanderCount, int initialSize) { + int size = initialSize; + for(int i = 0; i < expanderCount; i++) { + size = expandSize(size); + } + return size; + } + + protected static int calculateChunkOriginArrayIndex(int totalExpanderCount, List stages) { + int finalGridOrigin = calculateFinalGridOrigin(totalExpanderCount, stages); + int initialGridInterval = calculateGridInterval(totalExpanderCount, 0); + + // Round the final grid origin up to the nearest multiple of initialGridInterval, such that each + // chunk samples points on the same overall grid. + // Without this, shared chunk borders (required because of adjacent cell reads) will not be identical + // because points would be sampled on grids at different offsets, resulting in artifacts at borders. + return FastMath.ceilToInt((double) finalGridOrigin / initialGridInterval) * initialGridInterval; + } + + private static int calculateFinalGridOrigin(int totalExpanderCount, List stages) { + int gridOrigin = 0; + int expansionsApplied = 0; + int gridInterval = calculateGridInterval(totalExpanderCount, expansionsApplied); + for (Stage stage : stages) { + if (stage instanceof Expander) { + expansionsApplied++; + gridInterval = calculateGridInterval(totalExpanderCount, expansionsApplied); + } + gridOrigin += stage.maxRelativeReadDistance() * gridInterval; + } + return gridOrigin; + } + + protected static int calculateChunkSize(int arraySize, int chunkOriginArrayIndex, int totalExpanderCount) { + return contractBordersFromSize(arraySize, chunkOriginArrayIndex) - (totalExpanderCount > 0 ? 1 : 0); + } + + private static int expandSize(int size) { + return size * 2 - 1; + } + + private static int contractBordersFromSize(int size, int border) { + return size - border * 2; + } + + private static int calculateGridInterval(int totalExpansions, int expansionsApplied) { + return 1 << (totalExpansions - expansionsApplied); + } + + private SeededVector getOrigin() { + return worldOrigin; + } + + /** + * Represents a point on the operating grid within the biomes array + */ + public static class ViewPoint { + private final BiomeChunkImpl chunk; + private final PipelineBiome biome; + private final int gridInterval; + private final int gridX; + private final int gridZ; + private final int xIndex; + private final int zIndex; + private final PipelineBiome[][] lookupArray; + + private ViewPoint(BiomeChunkImpl chunk, int gridInterval, int gridX, int gridZ, int xIndex, int zIndex, PipelineBiome[][] lookupArray) { + this.chunk = chunk; + this.gridInterval = gridInterval; + this.gridX = gridX; + this.gridZ = gridZ; + this.xIndex = xIndex; + this.zIndex = zIndex; + this.lookupArray = lookupArray; + this.biome = lookupArray[xIndex][zIndex]; + } + + public PipelineBiome getRelativeBiome(int x, int z) { + int lookupXIndex = this.xIndex + x * gridInterval; + int lookupZIndex = this.zIndex + z * gridInterval; + return lookupArray[lookupXIndex][lookupZIndex]; + } + + public PipelineBiome getBiome() { + return biome; + } + + /** + * @return X position of the point relative to the operating grid + */ + public int gridX() { + return gridX; + } + + /** + * @return Z position of the point relative to the operating grid + */ + public int gridZ() { + return gridZ; + } + + /** + * @return X position of the point in the world + */ + public int worldX() { + return chunk.xIndexToWorldCoordinate(xIndex); + } + + /** + * @return Z position of the point in the world + */ + public int worldZ() { + return chunk.zIndexToWorldCoordinate(zIndex); + } + + public long worldSeed() { + return chunk.getOrigin().seed(); + } + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/pipeline/PipelineImpl.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/pipeline/PipelineImpl.java new file mode 100644 index 000000000..a01091d2e --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/pipeline/PipelineImpl.java @@ -0,0 +1,92 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.pipeline; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.BiomeChunk; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Expander; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Pipeline; +import com.dfsek.terra.addons.biome.pipeline.v2.api.SeededVector; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Source; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; + + +public class PipelineImpl implements Pipeline { + + private static final Logger logger = LoggerFactory.getLogger(PipelineImpl.class); + + private final Source source; + private final List stages; + private final int chunkSize; + private final int expanderCount; + private final int arraySize; + private final int chunkOriginArrayIndex; + private final int resolution; + + public PipelineImpl(Source source, List stages, int resolution, int idealChunkArraySize) { + this.source = source; + this.stages = stages; + this.resolution = resolution; + this.expanderCount = (int) stages.stream().filter(s -> s instanceof Expander).count(); + + // Optimize for the ideal array size + int arraySize; + int chunkOriginArrayIndex; + int chunkSize; + int initialSize = 1; + while (true) { + arraySize = BiomeChunkImpl.initialSizeToArraySize(expanderCount, initialSize); + chunkOriginArrayIndex = BiomeChunkImpl.calculateChunkOriginArrayIndex(expanderCount, stages); + chunkSize = BiomeChunkImpl.calculateChunkSize(arraySize, chunkOriginArrayIndex, expanderCount); + if (chunkSize > 1 && arraySize >= idealChunkArraySize) break; + initialSize++; + } + + this.arraySize = arraySize; + this.chunkOriginArrayIndex = chunkOriginArrayIndex; + this.chunkSize = chunkSize; + + logger.debug("Initialized a new biome pipeline:"); + logger.debug("Array size: {} (Target: {})", arraySize, idealChunkArraySize); + logger.debug("Internal array origin: {}", chunkOriginArrayIndex); + logger.debug("Chunk size: {}", chunkSize); + } + + @Override + public BiomeChunk generateChunk(SeededVector worldCoordinates) { + return new BiomeChunkImpl(worldCoordinates, this); + } + + @Override + public int getChunkSize() { + return chunkSize; + } + + @Override + public Source getSource() { + return source; + } + + @Override + public List getStages() { + return stages; + } + + protected int getExpanderCount() { + return expanderCount; + } + + protected int getArraySize() { + return arraySize; + } + + protected int getChunkOriginArrayIndex() { + return chunkOriginArrayIndex; + } + + protected int getResolution() { + return resolution; + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/source/SamplerSource.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/source/SamplerSource.java new file mode 100644 index 000000000..f6d66b9e2 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/source/SamplerSource.java @@ -0,0 +1,27 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.source; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Source; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.api.noise.NoiseSampler; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; + + +public class SamplerSource implements Source { + private final ProbabilityCollection biomes; + private final NoiseSampler sampler; + + public SamplerSource(ProbabilityCollection biomes, NoiseSampler sampler) { + this.biomes = biomes; + this.sampler = sampler; + } + + @Override + public PipelineBiome get(long seed, int x, int z) { + return biomes.get(sampler, x, z, seed); + } + + @Override + public Iterable getBiomes() { + return biomes.getContents(); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/source/SingleSource.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/source/SingleSource.java new file mode 100644 index 000000000..b2c0ee8db --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/source/SingleSource.java @@ -0,0 +1,27 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.source; + +import java.util.Collections; +import java.util.Set; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.api.Source; + + +public class SingleSource implements Source { + + private final PipelineBiome biome; + + public SingleSource(PipelineBiome biome) { + this.biome = biome; + } + + @Override + public PipelineBiome get(long seed, int x, int z) { + return biome; + } + + @Override + public Set getBiomes() { + return Collections.singleton(biome); + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/expander/FractalExpander.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/expander/FractalExpander.java new file mode 100644 index 000000000..c82743ba4 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/expander/FractalExpander.java @@ -0,0 +1,37 @@ +package com.dfsek.terra.addons.biome.pipeline.v2.stage.expander; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Expander; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl; +import com.dfsek.terra.api.noise.NoiseSampler; + + +public class FractalExpander implements Expander { + + private final NoiseSampler sampler; + + public FractalExpander(NoiseSampler sampler) { + this.sampler = sampler; + } + + @Override + public PipelineBiome fillBiome(BiomeChunkImpl.ViewPoint viewPoint) { + + int xMod2 = viewPoint.gridX() % 2; + int zMod2 = viewPoint.gridZ() % 2; + + double roll = sampler.noise(viewPoint.worldSeed(), viewPoint.worldX(), viewPoint.worldZ()); + + if (xMod2 == 1 && zMod2 == 0) { // Pick one of 2 neighbors on X axis randomly + return roll > 0 ? viewPoint.getRelativeBiome(-1, 0) : viewPoint.getRelativeBiome(1, 0); + + } else if (xMod2 == 0 && zMod2 == 1) { // Pick one of 2 neighbors on Z axis randomly + return roll > 0 ? viewPoint.getRelativeBiome(0, -1) : viewPoint.getRelativeBiome(0, 1); + + } else { // Pick one of 4 corners randomly + return roll > 0 ? + roll > 0.25 ? viewPoint.getRelativeBiome(-1, 1) : viewPoint.getRelativeBiome(1, 1) : + roll > -0.25 ? viewPoint.getRelativeBiome(-1, -1) : viewPoint.getRelativeBiome(1, -1); + } + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/BorderListStage.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/BorderListStage.java new file mode 100644 index 000000000..2587bc792 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/BorderListStage.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Predicate; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl; +import com.dfsek.terra.api.noise.NoiseSampler; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; +import com.dfsek.terra.api.util.vector.Vector2Int; + + +public class BorderListStage implements Stage { + private final String border; + private final NoiseSampler noiseSampler; + private final ProbabilityCollection replaceDefault; + private final String defaultReplace; + private final Map> replace; + + private final Vector2Int[] borderPoints; + + public BorderListStage(Map> replace, String border, String defaultReplace, + NoiseSampler noiseSampler, ProbabilityCollection replaceDefault) { + this.border = border; + this.noiseSampler = noiseSampler; + this.replaceDefault = replaceDefault; + this.defaultReplace = defaultReplace; + this.replace = replace; + + List points = new ArrayList<>(); + for(int x = -1; x <= 1; x++) { + for(int z = -1; z <= 1; z++) { + if(x == 0 && z == 0) continue; + points.add(Vector2Int.of(x, z)); + } + } + this.borderPoints = points.toArray(new Vector2Int[0]); + + } + + @Override + public Iterable getBiomes(Iterable biomes) { + Set biomeSet = new HashSet<>(); + biomes.forEach(biomeSet::add); + biomeSet.addAll(replaceDefault.getContents().stream().filter(Predicate.not(PipelineBiome::isSelf)).toList()); + replace.forEach((biome, collection) -> biomeSet.addAll(collection.getContents())); + return biomeSet; + } + + @Override + public PipelineBiome apply(BiomeChunkImpl.ViewPoint viewPoint) { + PipelineBiome center = viewPoint.getBiome(); + if(center.getTags().contains(defaultReplace)) { + for(Vector2Int point : borderPoints) { + PipelineBiome current = viewPoint.getRelativeBiome(point.getX(), point.getZ()); + if(current != null && current.getTags().contains(border)) { + if(replace.containsKey(center)) { + PipelineBiome replacement = replace.get(center).get(noiseSampler, viewPoint.worldX(), viewPoint.worldZ(), + viewPoint.worldSeed()); + return replacement.isSelf() ? center : replacement; + } + PipelineBiome replacement = replaceDefault.get(noiseSampler, viewPoint.worldX(), viewPoint.worldZ(), viewPoint.worldSeed()); + return replacement.isSelf() ? center : replacement; + } + } + } + return center; + } + + @Override + public int maxRelativeReadDistance() { + return 1; + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/BorderStage.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/BorderStage.java new file mode 100644 index 000000000..274ef1ff9 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/BorderStage.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl; +import com.dfsek.terra.api.noise.NoiseSampler; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; +import com.dfsek.terra.api.util.vector.Vector2Int; + + +public class BorderStage implements Stage { + private final String border; + private final NoiseSampler noiseSampler; + private final ProbabilityCollection replace; + private final String replaceTag; + private final Vector2Int[] borderPoints; + + public BorderStage(String border, String replaceTag, NoiseSampler noiseSampler, ProbabilityCollection replace) { + this.border = border; + this.noiseSampler = noiseSampler; + this.replace = replace; + this.replaceTag = replaceTag; + List points = new ArrayList<>(); + for(int x = -1; x <= 1; x++) { + for(int z = -1; z <= 1; z++) { + if(x == 0 && z == 0) continue; + points.add(Vector2Int.of(x, z)); + } + } + this.borderPoints = points.toArray(new Vector2Int[0]); + } + + @Override + public PipelineBiome apply(BiomeChunkImpl.ViewPoint viewPoint) { + PipelineBiome center = viewPoint.getBiome(); + if(center.getTags().contains(replaceTag)) { + for(Vector2Int point : borderPoints) { + PipelineBiome current = viewPoint.getRelativeBiome(point.getX(), point.getZ()); + if(current != null && current.getTags().contains(border)) { + PipelineBiome replacement = replace.get(noiseSampler, viewPoint.worldX(), viewPoint.worldZ(), viewPoint.worldSeed()); + return replacement.isSelf() ? center : replacement; + } + } + } + return center; + } + + @Override + public Iterable getBiomes(Iterable biomes) { + Set biomeSet = new HashSet<>(); + biomes.forEach(biomeSet::add); + biomeSet.addAll( + replace + .getContents() + .stream() + .filter( + Predicate.not(PipelineBiome::isSelf) + ) + .toList() + ); + return biomeSet; + } + + @Override + public int maxRelativeReadDistance() { + return 1; + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/ReplaceListStage.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/ReplaceListStage.java new file mode 100644 index 000000000..372127546 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/ReplaceListStage.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Stream; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl; +import com.dfsek.terra.api.noise.NoiseSampler; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; + + +public class ReplaceListStage implements Stage { + private final Map> replace; + private final NoiseSampler sampler; + private final ProbabilityCollection replaceDefault; + private final String defaultTag; + + public ReplaceListStage(Map> replace, String defaultTag, + ProbabilityCollection replaceDefault, NoiseSampler sampler) { + this.replace = replace; + this.sampler = sampler; + this.defaultTag = defaultTag; + this.replaceDefault = replaceDefault; + } + + @Override + public PipelineBiome apply(BiomeChunkImpl.ViewPoint viewPoint) { + PipelineBiome center = viewPoint.getBiome(); + if(replace.containsKey(center)) { + PipelineBiome biome = replace.get(center).get(sampler, viewPoint.worldX(), viewPoint.worldZ(), viewPoint.worldSeed()); + return biome.isSelf() ? viewPoint.getBiome() : biome; + } + if(viewPoint.getBiome().getTags().contains(defaultTag)) { + PipelineBiome biome = replaceDefault.get(sampler, viewPoint.worldX(), viewPoint.worldZ(), viewPoint.worldSeed()); + return biome.isSelf() ? viewPoint.getBiome() : biome; + } + return center; + } + + @Override + public int maxRelativeReadDistance() { + return 0; + } + + @Override + public Iterable getBiomes(Iterable biomes) { + Set biomeSet = new HashSet<>(); + + Set reject = new HashSet<>(); + + biomes.forEach(biome -> { + if(!biome.getTags().contains(defaultTag) && !replace.containsKey(biome)) { + biomeSet.add(biome); + } else { + reject.add(biome); + } + }); + biomeSet.addAll(replaceDefault.getContents().stream().flatMap(terraBiome -> { + if(terraBiome.isSelf()) return reject.stream(); + return Stream.of(terraBiome); + }).toList()); + replace.forEach((biome, collection) -> biomeSet.addAll(collection.getContents().stream().map(terraBiome -> { + if(terraBiome.isSelf()) return biome; + return terraBiome; + }).toList())); + return biomeSet; + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/ReplaceStage.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/ReplaceStage.java new file mode 100644 index 000000000..b7a2fd684 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/ReplaceStage.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Stream; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl; +import com.dfsek.terra.api.noise.NoiseSampler; +import com.dfsek.terra.api.util.collection.ProbabilityCollection; + + +public class ReplaceStage implements Stage { + private final String replaceableTag; + private final ProbabilityCollection replace; + private final NoiseSampler sampler; + + public ReplaceStage(String replaceable, ProbabilityCollection replace, NoiseSampler sampler) { + this.replaceableTag = replaceable; + this.replace = replace; + this.sampler = sampler; + } + + @Override + public PipelineBiome apply(BiomeChunkImpl.ViewPoint viewPoint) { + if(viewPoint.getBiome().getTags().contains(replaceableTag)) { + PipelineBiome biome = replace.get(sampler, viewPoint.worldX(), viewPoint.worldZ(), viewPoint.worldSeed()); + return biome.isSelf() ? viewPoint.getBiome() : biome; + } + return viewPoint.getBiome(); + } + + @Override + public int maxRelativeReadDistance() { + return 0; + } + + @Override + public Iterable getBiomes(Iterable biomes) { + Set biomeSet = new HashSet<>(); + Set reject = new HashSet<>(); + biomes.forEach(biome -> { + if(!biome.getTags().contains(replaceableTag)) { + biomeSet.add(biome); + } else { + reject.add(biome); + } + }); + biomeSet.addAll(replace.getContents().stream().flatMap(terraBiome -> { + if(terraBiome.isSelf()) return reject.stream(); + return Stream.of(terraBiome); + }).toList()); + return biomeSet; + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/SmoothStage.java b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/SmoothStage.java new file mode 100644 index 000000000..92bbee433 --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/java/com/dfsek/terra/addons/biome/pipeline/v2/stage/mutators/SmoothStage.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2020-2021 Polyhedral Development + * + * The Terra Core Addons are licensed under the terms of the MIT License. For more details, + * reference the LICENSE file in this module's root directory. + */ + +package com.dfsek.terra.addons.biome.pipeline.v2.stage.mutators; + +import java.util.Objects; + +import com.dfsek.terra.addons.biome.pipeline.v2.api.Stage; +import com.dfsek.terra.addons.biome.pipeline.v2.api.biome.PipelineBiome; +import com.dfsek.terra.addons.biome.pipeline.v2.pipeline.BiomeChunkImpl; +import com.dfsek.terra.api.noise.NoiseSampler; + + +public class SmoothStage implements Stage { + + private final NoiseSampler sampler; + + public SmoothStage(NoiseSampler sampler) { + this.sampler = sampler; + } + + @Override + public PipelineBiome apply(BiomeChunkImpl.ViewPoint viewPoint) { + PipelineBiome top = viewPoint.getRelativeBiome(1, 0); + PipelineBiome bottom = viewPoint.getRelativeBiome(-1, 0); + PipelineBiome left = viewPoint.getRelativeBiome(0, 1); + PipelineBiome right = viewPoint.getRelativeBiome(0, -1); + + double roll = sampler.noise(viewPoint.worldSeed(), viewPoint.worldX(), viewPoint.worldZ()); + + boolean vert = Objects.equals(top, bottom); + boolean horiz = Objects.equals(left, right); + + if(vert && horiz) { + return roll > 0 ? + roll > 0.25 ? left : right : + roll > -0.25 ? top : bottom; + } + if(vert) { + return roll > 0 ? top : bottom; + } + if(horiz) { + return roll > 0 ? left : right; + } + return viewPoint.getBiome(); + } + + @Override + public int maxRelativeReadDistance() { + return 1; + } +} diff --git a/common/addons/biome-provider-pipeline-v2/src/main/resources/terra.addon.yml b/common/addons/biome-provider-pipeline-v2/src/main/resources/terra.addon.yml new file mode 100644 index 000000000..f800574ca --- /dev/null +++ b/common/addons/biome-provider-pipeline-v2/src/main/resources/terra.addon.yml @@ -0,0 +1,12 @@ +schema-version: 1 +contributors: + - Terra contributors +id: biome-provider-pipeline-v2 +version: @VERSION@ +entrypoints: + - "com.dfsek.terra.addons.biome.pipeline.v2.BiomePipelineAddon" +website: + issues: https://github.com/PolyhedralDev/Terra/issues + source: https://github.com/PolyhedralDev/Terra + docs: https://terra.polydev.org +license: MIT License \ No newline at end of file diff --git a/common/addons/biome-provider-pipeline/src/main/java/com/dfsek/terra/addons/biome/pipeline/BiomePipelineAddon.java b/common/addons/biome-provider-pipeline/src/main/java/com/dfsek/terra/addons/biome/pipeline/BiomePipelineAddon.java index 42aa95b0e..6765ab3c3 100644 --- a/common/addons/biome-provider-pipeline/src/main/java/com/dfsek/terra/addons/biome/pipeline/BiomePipelineAddon.java +++ b/common/addons/biome-provider-pipeline/src/main/java/com/dfsek/terra/addons/biome/pipeline/BiomePipelineAddon.java @@ -8,6 +8,8 @@ package com.dfsek.terra.addons.biome.pipeline; import com.dfsek.tectonic.api.config.template.object.ObjectTemplate; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.function.Supplier; @@ -39,6 +41,8 @@ import com.dfsek.terra.api.world.biome.generation.BiomeProvider; public class BiomePipelineAddon implements AddonInitializer { + private static final Logger logger = LoggerFactory.getLogger(BiomePipelineAddon.class); + public static final TypeKey>> SOURCE_REGISTRY_KEY = new TypeKey<>() { }; @@ -85,5 +89,7 @@ public class BiomePipelineAddon implements AddonInitializer { Registry biomeRegistry = event.getPack().getRegistry(Biome.class); event.getPack().applyLoader(BiomeDelegate.class, new BiomeDelegateLoader(biomeRegistry)); }); + + logger.warn("The biome-provider-pipeline addon is deprecated and scheduled for removal in Terra 7.0. It is recommended to use the biome-provider-pipeline-v2 addon for future pack development instead."); } }