Skip to content

Commit

Permalink
make graphed chunks persist and be reusable
Browse files Browse the repository at this point in the history
this is to make outlines on chunk edges generate properly without
excessive re-calculation
  • Loading branch information
Pixaurora committed Jul 16, 2023
1 parent fc45276 commit 954ea9a
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 94 deletions.
Original file line number Diff line number Diff line change
@@ -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) {
Expand All @@ -23,12 +23,16 @@ public static Coordinate fromListIndex(int index) {
return fromListIndex(index, 16);
}

public Stream<Coordinate> getNeighbors() {
return Stream.of(
public boolean isLegal() {
return 0 <= this.x && this.x < this.scale && 0 <= this.z && this.z < this.scale;
}

public List<Coordinate> 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);
);
}
}
34 changes: 34 additions & 0 deletions src/main/java/net/pixaurora/janerator/graph/Graph.java
Original file line number Diff line number Diff line change
@@ -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<ChunkPos, GraphedChunk> chunks = new ConcurrentHashMap<>();

public static CompletableFuture<GraphedChunk> 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
);
}
}
}
124 changes: 124 additions & 0 deletions src/main/java/net/pixaurora/janerator/graph/GraphedChunk.java
Original file line number Diff line number Diff line change
@@ -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<Boolean> shading;
ChunkPos pos;

public GraphedChunk(ChunkPos pos) {
this.pos = pos;

List<CompletableFuture<Boolean>> 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<ChunkGenerator> getGeneratorMap(ChunkGenerator defaultGenerator, ChunkGenerator modifiedGenerator, ChunkGenerator outlineGenerator) {
List<ChunkGenerator> 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<ChunkGenerator> sampleBiomeGeneratorMap(ChunkGenerator defaultGenerator, ChunkGenerator modifiedGenerator) {
List<ChunkGenerator> 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<Boolean, Integer> 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<Coordinate> findOutlinedPortion() {
List<Coordinate> 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<Coordinate> getIndices(boolean shade) {
return IntStream.range(0, 256)
.filter(index -> this.shading.get(index) == shade)
.boxed()
.map(Coordinate::fromListIndex)
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -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<ChunkGenerator> generatorMap;
private List<ChunkGenerator> biomeGeneratorMap;
private List<PlacementSelection> selections;

private ChunkGenerator fallbackGenerator;
private List<PlacementSelection> selections;

public GeneratorFinder(
ChunkGenerator defaultGenerator,
ChunkGenerator modifiedGenerator,
ChunkGenerator outlineGenerator,
ChunkAccess chunk
GraphedChunk graphedArea
) {
this.biomeGeneratorMap = new ArrayList<>();

ChunkPos pos = chunk.getPos();

List<CompletableFuture<Boolean>> 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<ChunkGenerator, Integer> 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<ChunkGenerator> uniqueGenerators = this.generatorMap
.stream()
.distinct()
.toList();
if (uniqueGenerators.size() > 1) {
List<Coordinate> 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()
Expand Down
22 changes: 14 additions & 8 deletions src/main/java/net/pixaurora/janerator/worldgen/MultiGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<GraphedChunk> scheduledGraph;

private GeneratorFinder generators;

Expand All @@ -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
Expand All @@ -56,12 +58,16 @@ public Codec<? extends ChunkGenerator> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down

0 comments on commit 954ea9a

Please sign in to comment.