diff --git a/src/main/java/com/simibubi/create/infrastructure/worldgen/LayeredOreFeature.java b/src/main/java/com/simibubi/create/infrastructure/worldgen/LayeredOreFeature.java index 897c93fb7..76610b3cf 100644 --- a/src/main/java/com/simibubi/create/infrastructure/worldgen/LayeredOreFeature.java +++ b/src/main/java/com/simibubi/create/infrastructure/worldgen/LayeredOreFeature.java @@ -1,6 +1,7 @@ package com.simibubi.create.infrastructure.worldgen; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.function.Function; @@ -20,17 +21,24 @@ import net.minecraft.world.level.levelgen.feature.Feature; import net.minecraft.world.level.levelgen.feature.FeaturePlaceContext; import net.minecraft.world.level.levelgen.feature.configurations.OreConfiguration; import net.minecraft.world.level.levelgen.feature.configurations.OreConfiguration.TargetBlockState; +import net.minecraft.world.level.levelgen.synth.SimplexNoise; public class LayeredOreFeature extends Feature { public LayeredOreFeature() { super(LayeredOreConfiguration.CODEC); } + private static final float MAX_LAYER_DISPLACEMENT = 1.75f; + private static final float LAYER_NOISE_FREQUENCY = 0.125f; + + private static final float MAX_RADIAL_THRESHOLD_REDUCTION = 0.25f; + private static final float RADIAL_NOISE_FREQUENCY = 0.125f; + @Override public boolean place(FeaturePlaceContext pContext) { RandomSource random = pContext.random(); - BlockPos blockpos = pContext.origin(); - WorldGenLevel worldgenlevel = pContext.level(); + BlockPos origin = pContext.origin(); + WorldGenLevel worldGenLevel = pContext.level(); LayeredOreConfiguration config = pContext.config(); List patternPool = config.layerPatterns; @@ -40,89 +48,120 @@ public class LayeredOreFeature extends Feature { LayerPattern layerPattern = patternPool.get(random.nextInt(patternPool.size())); int placedAmount = 0; - int size = config.size; - int radius = Mth.ceil(config.size / 2f); - int x0 = blockpos.getX() - radius; - int y0 = blockpos.getY() - radius; - int z0 = blockpos.getZ() - radius; - int width = size + 1; - int length = size + 1; - int height = size + 1; + int size = config.size + 1; + float radius = config.size * 0.5f; + int radiusBound = Mth.ceil(radius) - 1; + int x0 = origin.getX(); + int y0 = origin.getY(); + int z0 = origin.getZ(); - if (blockpos.getY() >= worldgenlevel.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, blockpos.getX(), - blockpos.getZ())) + if (origin.getY() >= worldGenLevel.getHeight(Heightmap.Types.OCEAN_FLOOR_WG, origin.getX(), origin.getZ())) return false; - List resolvedLayers = new ArrayList<>(); - List layerDiameterOffsets = new ArrayList<>(); + List tempLayers = new ArrayList<>(); + float layerSizeTotal = 0.0f; + LayerPattern.Layer current = null; + while (layerSizeTotal < size) { + Layer next = layerPattern.rollNext(current, random); + float layerSize = Mth.randomBetween(random, next.minSize, next.maxSize); + tempLayers.add(new TemporaryLayerEntry(next, layerSize)); + layerSizeTotal += layerSize; + current = next; + } + + List resolvedLayers = new ArrayList<>(tempLayers.size()); + float cumulativeLayerSize = -(layerSizeTotal - size) * random.nextFloat(); + for (TemporaryLayerEntry tempLayerEntry : tempLayers) { + float rampStartValue = resolvedLayers.size() == 0 ? + Float.NEGATIVE_INFINITY : + cumulativeLayerSize * (2.0f / size) - 1.0f; + cumulativeLayerSize += tempLayerEntry.size(); + if (cumulativeLayerSize < 0) + continue; + float radialThresholdMultiplier = Mth.randomBetween(random, 0.5f, 1.0f); + resolvedLayers.add(new ResolvedLayerEntry(tempLayerEntry.layer, radialThresholdMultiplier, rampStartValue)); + } + + // Choose stacking direction + float gy = Mth.randomBetween(random, -1.0f, 1.0f); + gy = (float) Math.cbrt(gy); // Make layer alignment tend towards horizontal more than vertical + float xzRescale = Mth.sqrt(1.0f - gy * gy); + float theta = random.nextFloat() * Mth.TWO_PI; + float gx = Mth.cos(theta) * xzRescale; + float gz = Mth.sin(theta) * xzRescale; + + SimplexNoise layerDisplacementNoise = new SimplexNoise(random); + SimplexNoise radiusNoise = new SimplexNoise(random); MutableBlockPos mutablePos = new MutableBlockPos(); - BulkSectionAccess bulksectionaccess = new BulkSectionAccess(worldgenlevel); - int layerCoordinate = random.nextInt(4); - int slantyCoordinate = random.nextInt(3); - float slope = random.nextFloat() * .75f; + BulkSectionAccess bulkSectionAccess = new BulkSectionAccess(worldGenLevel); try { - for (int x = 0; x < width; x++) { - float dx = x * 2f / width - 1; - if (dx * dx > 1) + for (int dzBlock = -radiusBound; dzBlock <= radiusBound; dzBlock++) { + float dz = dzBlock * (1.0f / radius); + if (dz * dz > 1) continue; - for (int y = 0; y < height; y++) { - float dy = y * 2f / height - 1; - if (dx * dx + dy * dy > 1) - continue; - if (worldgenlevel.isOutsideBuildHeight(y0 + y)) + for (int dxBlock = -radiusBound; dxBlock <= radiusBound; dxBlock++) { + float dx = dxBlock * (1.0f / radius); + if (dz * dz + dx * dx > 1) continue; - for (int z = 0; z < length; z++) { - float dz = z * 2f / height - 1; - - int layerIndex = layerCoordinate == 0 ? z : layerCoordinate == 1 ? x : y; - if (slantyCoordinate != layerCoordinate) - layerIndex += Mth.floor(slantyCoordinate == 0 ? z : slantyCoordinate == 1 ? x : y) * slope; - - while (layerIndex >= resolvedLayers.size()) { - Layer next = layerPattern.rollNext( - resolvedLayers.isEmpty() ? null : resolvedLayers.get(resolvedLayers.size() - 1), - random); - float offset = random.nextFloat() * .5f + .5f; - for (int i = 0; i < next.minSize + random.nextInt(1 + next.maxSize - next.minSize); i++) { - resolvedLayers.add(next); - layerDiameterOffsets.add(offset); - } - } - - if (dx * dx + dy * dy + dz * dz > 1 * layerDiameterOffsets.get(layerIndex)) + for (int dyBlock = -radiusBound; dyBlock <= radiusBound; dyBlock++) { + float dy = dyBlock * (1.0f / radius); + float distanceSquared = dz * dz + dx * dx + dy * dy; + if (distanceSquared > 1) + continue; + if (worldGenLevel.isOutsideBuildHeight(y0 + dyBlock)) continue; - LayerPattern.Layer layer = resolvedLayers.get(layerIndex); - List state = layer.rollBlock(random); + int currentX = x0 + dxBlock; + int currentY = y0 + dyBlock; + int currentZ = z0 + dzBlock; - int currentX = x0 + x; - int currentY = y0 + y; - int currentZ = z0 + z; + float rampValue = gx * dx + gy * dy + gz * dz; + rampValue += layerDisplacementNoise.getValue( + currentX * LAYER_NOISE_FREQUENCY, currentY * LAYER_NOISE_FREQUENCY, currentZ * LAYER_NOISE_FREQUENCY + ) * (MAX_LAYER_DISPLACEMENT / size); + + int layerIndex = Collections.binarySearch(resolvedLayers, new ResolvedLayerEntry(null, 0, rampValue)); + if (layerIndex < 0) layerIndex = -2 - layerIndex; // Counter (-insertionIndex - 1) return result, where insertionIndex = layerIndex + 1 + ResolvedLayerEntry layerEntry = resolvedLayers.get(layerIndex); + + if (distanceSquared > layerEntry.radialThresholdMultiplier) + continue; + + float thresholdNoiseValue = Mth.map( + (float) radiusNoise.getValue(currentX * RADIAL_NOISE_FREQUENCY, currentY * RADIAL_NOISE_FREQUENCY, currentZ * RADIAL_NOISE_FREQUENCY), + -1.0f, 1.0f, 1.0f - MAX_RADIAL_THRESHOLD_REDUCTION, 1.0f + ); + + if (distanceSquared > layerEntry.radialThresholdMultiplier * thresholdNoiseValue) + continue; + + LayerPattern.Layer layer = layerEntry.layer; + List targetBlockStates = layer.rollBlock(random); mutablePos.set(currentX, currentY, currentZ); - if (!worldgenlevel.ensureCanWrite(mutablePos)) + if (!worldGenLevel.ensureCanWrite(mutablePos)) continue; - LevelChunkSection levelchunksection = bulksectionaccess.getSection(mutablePos); - if (levelchunksection == null) + LevelChunkSection levelChunkSection = bulkSectionAccess.getSection(mutablePos); + if (levelChunkSection == null) continue; - int i3 = SectionPos.sectionRelative(currentX); - int j3 = SectionPos.sectionRelative(currentY); - int k3 = SectionPos.sectionRelative(currentZ); - BlockState blockstate = levelchunksection.getBlockState(i3, j3, k3); + int localX = SectionPos.sectionRelative(currentX); + int localY = SectionPos.sectionRelative(currentY); + int localZ = SectionPos.sectionRelative(currentZ); + BlockState blockState = levelChunkSection.getBlockState(localX, localY, localZ); - for (OreConfiguration.TargetBlockState oreconfiguration$targetblockstate : state) { - if (!canPlaceOre(blockstate, bulksectionaccess::getBlockState, random, config, - oreconfiguration$targetblockstate, mutablePos)) + for (OreConfiguration.TargetBlockState targetBlockState : targetBlockStates) { + if (!canPlaceOre(blockState, bulkSectionAccess::getBlockState, random, config, + targetBlockState, mutablePos)) continue; - if (oreconfiguration$targetblockstate.state.isAir()) + if (targetBlockState.state.isAir()) continue; - levelchunksection.setBlockState(i3, j3, k3, oreconfiguration$targetblockstate.state, false); + levelChunkSection.setBlockState(localX, localY, localZ, targetBlockState.state, false); ++placedAmount; break; } @@ -133,7 +172,7 @@ public class LayeredOreFeature extends Feature { } catch (Throwable throwable1) { try { - bulksectionaccess.close(); + bulkSectionAccess.close(); } catch (Throwable throwable) { throwable1.addSuppressed(throwable); } @@ -141,7 +180,7 @@ public class LayeredOreFeature extends Feature { throw throwable1; } - bulksectionaccess.close(); + bulkSectionAccess.close(); return placedAmount > 0; } @@ -159,4 +198,13 @@ public class LayeredOreFeature extends Feature { protected boolean shouldSkipAirCheck(RandomSource pRandom, float pChance) { return pChance <= 0 ? true : pChance >= 1 ? false : pRandom.nextFloat() >= pChance; } + + private record TemporaryLayerEntry(Layer layer, float size) { } + + private record ResolvedLayerEntry(Layer layer, float radialThresholdMultiplier, float rampStartValue) implements Comparable { + @Override + public int compareTo(LayeredOreFeature.ResolvedLayerEntry b) { + return Float.compare(rampStartValue, b.rampStartValue); + } + } }