diff --git a/src/main/java/net/pixaurora/janerator/graphing/Coordinate.java b/src/main/java/net/pixaurora/janerator/graph/Coordinate.java similarity index 76% rename from src/main/java/net/pixaurora/janerator/graphing/Coordinate.java rename to src/main/java/net/pixaurora/janerator/graph/Coordinate.java index 8f8a32b..621f617 100644 --- a/src/main/java/net/pixaurora/janerator/graphing/Coordinate.java +++ b/src/main/java/net/pixaurora/janerator/graph/Coordinate.java @@ -1,6 +1,6 @@ -package net.pixaurora.janerator.graphing; +package net.pixaurora.janerator.graph; -import java.util.stream.Stream; +import java.util.List; public record Coordinate(int x, int z, int scale) { public Coordinate(int x, int z) { @@ -23,12 +23,16 @@ public static Coordinate fromListIndex(int index) { return fromListIndex(index, 16); } - public Stream getNeighbors() { - return Stream.of( + public boolean isLegal() { + return 0 <= this.x && this.x < this.scale && 0 <= this.z && this.z < this.scale; + } + + public List getNeighbors() { + return List.of( new Coordinate(this.x + 1, this.z, this.scale), new Coordinate(this.x - 1, this. z, this.scale), new Coordinate(this.x, this.z + 1, this.scale), new Coordinate(this.x, this.z - 1, this.scale) - ).filter(coord -> 0 <= coord.x && coord.x < this.scale && 0 <= coord.z && coord.z < this.scale); + ); } } diff --git a/src/main/java/net/pixaurora/janerator/graph/Graph.java b/src/main/java/net/pixaurora/janerator/graph/Graph.java new file mode 100644 index 0000000..1749760 --- /dev/null +++ b/src/main/java/net/pixaurora/janerator/graph/Graph.java @@ -0,0 +1,34 @@ +package net.pixaurora.janerator.graph; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; + +import net.minecraft.world.level.ChunkPos; +import net.pixaurora.janerator.graphing.Graphing; + +public class Graph { + public static boolean SHADED = true; + public static boolean UNSHADED = false; + + private static Map chunks = new ConcurrentHashMap<>(); + + public static CompletableFuture scheduleChunkGraphing(ChunkPos pos) { + GraphedChunk existingChunk = Graph.chunks.get(pos); + + if (Objects.nonNull(existingChunk)) { + return CompletableFuture.completedFuture(existingChunk); + } else { + return CompletableFuture.supplyAsync( + () -> { + GraphedChunk chunk = new GraphedChunk(pos); + Graph.chunks.put(pos, chunk); + + return chunk; + }, + Graphing.graphingThreadPool + ); + } + } +} diff --git a/src/main/java/net/pixaurora/janerator/graph/GraphedChunk.java b/src/main/java/net/pixaurora/janerator/graph/GraphedChunk.java new file mode 100644 index 0000000..1df5a1f --- /dev/null +++ b/src/main/java/net/pixaurora/janerator/graph/GraphedChunk.java @@ -0,0 +1,124 @@ +package net.pixaurora.janerator.graph; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.IntStream; + +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.chunk.ChunkGenerator; +import net.pixaurora.janerator.graphing.Graphing; + +public class GraphedChunk { + List shading; + ChunkPos pos; + + public GraphedChunk(ChunkPos pos) { + this.pos = pos; + + List> graphingFutures = new ArrayList<>(); + + int startX = pos.getMinBlockX(); + int startZ = pos.getMinBlockZ(); + + int endX = startX + 16; + int endZ = startZ + 16; + + for (int x = startX; x < endX; x++) { + for (int z = startZ; z < endZ; z++) { + graphingFutures.add(Graphing.scheduleGraphing(x, z)); + } + } + + this.shading = graphingFutures + .stream() + .map(future -> Graphing.completeGraphing(future)) + .toList(); + } + + public List getGeneratorMap(ChunkGenerator defaultGenerator, ChunkGenerator modifiedGenerator, ChunkGenerator outlineGenerator) { + List generatorMap = new ArrayList<>( + IntStream.range(0, 256) + .boxed() + .map(value -> defaultGenerator) + .toList() + ); + + this.getIndices(Graph.SHADED) + .stream() + .forEach(coord -> generatorMap.set(coord.toListIndex(), modifiedGenerator)); + this.findOutlinedPortion() + .stream() + .forEach(coord -> generatorMap.set(coord.toListIndex(), outlineGenerator)); + + return generatorMap; + } + + public List sampleBiomeGeneratorMap(ChunkGenerator defaultGenerator, ChunkGenerator modifiedGenerator) { + List biomeGeneratorMap = new ArrayList<>(); + + // Because biomes are placed per every 4 blocks, we sample + // the most common generator in 4 block sections throughout the chunk + // so that the biome placements line up with the blocks better + for (int section_x = 0; section_x < 16; section_x += 4) { + for (int section_z = 0; section_z < 16; section_z += 4) { + Map generatorSample = new HashMap<>(2); + + for (int x = section_x; x < section_x + 4; x++) { + for (int z = section_z; z < section_z + 4; z++) { + boolean generator = this.shading.get(new Coordinate(x, z).toListIndex()); + + int currentScore = generatorSample.getOrDefault(generator, 0); + generatorSample.put(generator, currentScore + 1); + } + } + + boolean sampledShade = generatorSample.entrySet() + .stream() + .max((entry1, entry2) -> entry1.getValue() - entry2.getValue()) + .get().getKey(); + + biomeGeneratorMap.add( + sampledShade ? modifiedGenerator : defaultGenerator + ); + } + } + + return biomeGeneratorMap; + } + + private List findOutlinedPortion() { + List outlinedPortion = new ArrayList<>(); + + for (Coordinate coordinate : this.getIndices(Graph.SHADED)) { + boolean hasContrastingNeighbor = coordinate.getNeighbors() + .stream() + .anyMatch( + neighbor -> { + if (neighbor.isLegal()) { + return this.shading.get(neighbor.toListIndex()) != Graph.SHADED; + } else { + // TODO: Handle coordinates outside the chunk + return false; + } + } + ); + + if (hasContrastingNeighbor) { + outlinedPortion.add(coordinate); + } + } + + return outlinedPortion; + } + + public List getIndices(boolean shade) { + return IntStream.range(0, 256) + .filter(index -> this.shading.get(index) == shade) + .boxed() + .map(Coordinate::fromListIndex) + .toList(); + } +} diff --git a/src/main/java/net/pixaurora/janerator/worldgen/GeneratorFinder.java b/src/main/java/net/pixaurora/janerator/worldgen/GeneratorFinder.java index 07bf48a..16f3470 100644 --- a/src/main/java/net/pixaurora/janerator/worldgen/GeneratorFinder.java +++ b/src/main/java/net/pixaurora/janerator/worldgen/GeneratorFinder.java @@ -1,104 +1,33 @@ package net.pixaurora.janerator.worldgen; -import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; import java.util.stream.IntStream; -import net.minecraft.world.level.ChunkPos; -import net.minecraft.world.level.chunk.ChunkAccess; import net.minecraft.world.level.chunk.ChunkGenerator; -import net.pixaurora.janerator.graphing.Coordinate; -import net.pixaurora.janerator.graphing.Graphing; +import net.pixaurora.janerator.graph.Coordinate; +import net.pixaurora.janerator.graph.GraphedChunk; public class GeneratorFinder { private List generatorMap; private List biomeGeneratorMap; - private List selections; private ChunkGenerator fallbackGenerator; + private List selections; public GeneratorFinder( ChunkGenerator defaultGenerator, ChunkGenerator modifiedGenerator, ChunkGenerator outlineGenerator, - ChunkAccess chunk + GraphedChunk graphedArea ) { - this.biomeGeneratorMap = new ArrayList<>(); - - ChunkPos pos = chunk.getPos(); - - List> graphingFutures = new ArrayList<>(); - - int startX = pos.getMinBlockX(); - int startZ = pos.getMinBlockZ(); - - int endX = startX + 16; - int endZ = startZ + 16; - - for (int x = startX; x < endX; x++) { - for (int z = startZ; z < endZ; z++) { - graphingFutures.add(Graphing.scheduleGraphing(x, z)); - } - } - - this.generatorMap = graphingFutures - .stream() - .map(future -> Graphing.completeGraphing(future)) - .map(shouldOverride -> shouldOverride ? modifiedGenerator : defaultGenerator) - .toList(); - this.generatorMap = new ArrayList<>(this.generatorMap); - - // Because biomes are placed per every 4 blocks, we sample - // the most common generator in 4 block sections throughout the chunk - // so that the biome placements line up with the blocks better - for (int section_x = 0; section_x < 16; section_x += 4) { - for (int section_z = 0; section_z < 16; section_z += 4) { - Map generatorSample = new HashMap<>(); - - for (int x = section_x; x < section_x + 4; x++) { - for (int z = section_z; z < section_z + 4; z++) { - ChunkGenerator generator = this.getAt(new Coordinate(x, z)); - - int currentScore = generatorSample.getOrDefault(generator, 0); - generatorSample.put(generator, currentScore + 1); - } - } - - biomeGeneratorMap.add( - generatorSample.entrySet() - .stream() - .max((entry1, entry2) -> entry1.getValue() - entry2.getValue()) - .get().getKey() - ); - } - } - - List uniqueGenerators = this.generatorMap - .stream() - .distinct() - .toList(); - if (uniqueGenerators.size() > 1) { - List coordinates = this.getIndices(modifiedGenerator) - .stream() - .map(Coordinate::fromListIndex) - .toList(); - - for (Coordinate coordinate : coordinates) { - if (coordinate.getNeighbors().anyMatch(coord -> this.getAt(coord) == defaultGenerator)) { - this.generatorMap.set(coordinate.toListIndex(), outlineGenerator); - } - } - } + this.generatorMap = graphedArea.getGeneratorMap(defaultGenerator, modifiedGenerator, outlineGenerator); + this.biomeGeneratorMap = graphedArea.sampleBiomeGeneratorMap(defaultGenerator, modifiedGenerator); this.selections = this.generatorMap .stream() .distinct() - .map(generator -> - new PlacementSelection(generator, this.getIndices(generator)) - ).toList(); + .map(generator -> new PlacementSelection(generator, this.getIndices(generator))) + .toList(); this.fallbackGenerator = this.selections .stream() diff --git a/src/main/java/net/pixaurora/janerator/worldgen/MultiGenerator.java b/src/main/java/net/pixaurora/janerator/worldgen/MultiGenerator.java index 83780af..5b9b352 100644 --- a/src/main/java/net/pixaurora/janerator/worldgen/MultiGenerator.java +++ b/src/main/java/net/pixaurora/janerator/worldgen/MultiGenerator.java @@ -23,14 +23,16 @@ import net.minecraft.world.level.levelgen.RandomState; import net.minecraft.world.level.levelgen.blending.Blender; import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager; -import net.pixaurora.janerator.graphing.Coordinate; +import net.pixaurora.janerator.graph.Coordinate; +import net.pixaurora.janerator.graph.Graph; +import net.pixaurora.janerator.graph.GraphedChunk; public class MultiGenerator extends ChunkGenerator { private boolean generatorsInitialized; ChunkGenerator defaultGenerator; ChunkGenerator modifiedGenerator; ChunkGenerator outlineGenerator; - ChunkAccess chunk; + CompletableFuture scheduledGraph; private GeneratorFinder generators; @@ -47,7 +49,7 @@ public MultiGenerator( this.defaultGenerator = defaultGenerator; this.modifiedGenerator = modifiedGenerator; this.outlineGenerator = outlineGenerator; - this.chunk = chunk; + this.scheduledGraph = Graph.scheduleChunkGraphing(chunk.getPos()); } @Override @@ -56,12 +58,16 @@ public Codec codec() { } private GeneratorFinder getGenerators() { - if (! generatorsInitialized) { - this.generators = new GeneratorFinder(this.defaultGenerator, this.modifiedGenerator, this.outlineGenerator, this.chunk); - this.generatorsInitialized = true; + try { + if (! generatorsInitialized) { + this.generators = new GeneratorFinder(this.defaultGenerator, this.modifiedGenerator, this.outlineGenerator, this.scheduledGraph.get()); + this.generatorsInitialized = true; + } + + return this.generators; + } catch (Exception e) { + throw new RuntimeException(e); } - - return this.generators; } @Override diff --git a/src/main/java/net/pixaurora/janerator/worldgen/PlacementSelection.java b/src/main/java/net/pixaurora/janerator/worldgen/PlacementSelection.java index e12d760..ba3d5c5 100644 --- a/src/main/java/net/pixaurora/janerator/worldgen/PlacementSelection.java +++ b/src/main/java/net/pixaurora/janerator/worldgen/PlacementSelection.java @@ -3,7 +3,7 @@ import java.util.List; import net.minecraft.world.level.chunk.ChunkGenerator; -import net.pixaurora.janerator.graphing.Coordinate; +import net.pixaurora.janerator.graph.Coordinate; public class PlacementSelection { private ChunkGenerator generator; diff --git a/src/main/java/net/pixaurora/janerator/worldgen/WrappedBiomeResolver.java b/src/main/java/net/pixaurora/janerator/worldgen/WrappedBiomeResolver.java index 7a93be5..4bf4986 100644 --- a/src/main/java/net/pixaurora/janerator/worldgen/WrappedBiomeResolver.java +++ b/src/main/java/net/pixaurora/janerator/worldgen/WrappedBiomeResolver.java @@ -17,7 +17,7 @@ import net.minecraft.world.level.levelgen.NoiseGeneratorSettings; import net.minecraft.world.level.levelgen.RandomState; import net.minecraft.world.level.levelgen.blending.Blender; -import net.pixaurora.janerator.graphing.Coordinate; +import net.pixaurora.janerator.graph.Coordinate; import net.pixaurora.janerator.mixin.NoiseBasedChunkGeneratorAccessor; import net.pixaurora.janerator.mixin.NoiseChunkAccessor;