This commit is contained in:
Brian Neumann-Fopiano
2026-02-21 03:49:10 -05:00
parent 1d09cd5f0f
commit a38e87cce3
6 changed files with 297 additions and 52 deletions

View File

@@ -239,34 +239,15 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
@BlockCoordinates
default IrisBiome getCaveBiome(int x, int y, int z) {
IrisBiome surfaceBiome = getSurfaceBiome(x, z);
IrisBiome entryBiome = null;
int worldY = y + getWorld().minHeight();
KList<IrisDimensionCarvingEntry> carvingEntries = getDimension().getCarving();
if (carvingEntries != null && !carvingEntries.isEmpty()) {
for (IrisDimensionCarvingEntry entry : carvingEntries) {
if (entry == null || !entry.isEnabled()) {
continue;
}
String key = entry.getBiome();
if (key == null || key.isBlank()) {
continue;
}
IrisRange worldYRange = entry.getWorldYRange();
if (worldYRange != null && !worldYRange.contains(worldY)) {
continue;
}
IrisBiome loadedBiome = getData().getBiomeLoader().load(key.trim());
if (loadedBiome != null) {
entryBiome = loadedBiome;
}
IrisDimensionCarvingEntry rootCarvingEntry = IrisDimensionCarvingResolver.resolveRootEntry(this, worldY);
if (rootCarvingEntry != null) {
IrisDimensionCarvingEntry resolvedCarvingEntry = IrisDimensionCarvingResolver.resolveFromRoot(this, rootCarvingEntry, x, z);
IrisBiome resolvedCarvingBiome = IrisDimensionCarvingResolver.resolveEntryBiome(this, resolvedCarvingEntry);
if (resolvedCarvingBiome != null) {
return resolvedCarvingBiome;
}
}
if (entryBiome != null) {
return entryBiome;
}
IrisBiome caveBiome = getCaveBiome(x, z);
if (caveBiome == null) {

View File

@@ -25,6 +25,7 @@ import art.arcane.iris.engine.mantle.MantleWriter;
import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisCaveProfile;
import art.arcane.iris.engine.object.IrisDimensionCarvingEntry;
import art.arcane.iris.engine.object.IrisDimensionCarvingResolver;
import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.engine.object.IrisRange;
import art.arcane.iris.util.project.context.ChunkContext;
@@ -32,7 +33,6 @@ import art.arcane.volmlib.util.documentation.ChunkCoordinates;
import art.arcane.volmlib.util.mantle.flag.ReservedFlag;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.IdentityHashMap;
@@ -106,11 +106,11 @@ public class MantleCarvingComponent extends IrisMantleComponent {
}
weightedProfiles.sort(Comparator.comparingDouble(WeightedProfile::averageWeight));
weightedProfiles.addAll(0, resolveDimensionCarvingProfiles());
weightedProfiles.addAll(0, resolveDimensionCarvingProfiles(chunkX, chunkZ));
return weightedProfiles;
}
private List<WeightedProfile> resolveDimensionCarvingProfiles() {
private List<WeightedProfile> resolveDimensionCarvingProfiles(int chunkX, int chunkZ) {
List<WeightedProfile> weightedProfiles = new ArrayList<>();
List<IrisDimensionCarvingEntry> entries = getDimension().getCarving();
if (entries == null || entries.isEmpty()) {
@@ -122,23 +122,39 @@ public class MantleCarvingComponent extends IrisMantleComponent {
continue;
}
String biomeKey = entry.getBiome();
if (biomeKey == null || biomeKey.isBlank()) {
continue;
}
IrisBiome biome = getData().getBiomeLoader().load(biomeKey.trim());
if (biome == null) {
continue;
}
IrisCaveProfile profile = biome.getCaveProfile();
if (!isProfileEnabled(profile)) {
IrisBiome rootBiome = IrisDimensionCarvingResolver.resolveEntryBiome(getEngineMantle().getEngine(), entry);
if (rootBiome == null) {
continue;
}
Map<IrisCaveProfile, double[]> rootProfileWeights = new IdentityHashMap<>();
IrisRange worldYRange = entry.getWorldYRange();
weightedProfiles.add(new WeightedProfile(profile, fullWeights(), -1D, worldYRange));
for (int localX = 0; localX < CHUNK_SIZE; localX++) {
for (int localZ = 0; localZ < CHUNK_SIZE; localZ++) {
int worldX = (chunkX << 4) + localX;
int worldZ = (chunkZ << 4) + localZ;
int columnIndex = (localX << 4) | localZ;
IrisDimensionCarvingEntry resolvedEntry = IrisDimensionCarvingResolver.resolveFromRoot(getEngineMantle().getEngine(), entry, worldX, worldZ);
IrisBiome resolvedBiome = IrisDimensionCarvingResolver.resolveEntryBiome(getEngineMantle().getEngine(), resolvedEntry);
if (resolvedBiome == null) {
continue;
}
IrisCaveProfile profile = resolvedBiome.getCaveProfile();
if (!isProfileEnabled(profile)) {
continue;
}
double[] weights = rootProfileWeights.computeIfAbsent(profile, key -> new double[CHUNK_AREA]);
weights[columnIndex] = 1D;
}
}
List<Map.Entry<IrisCaveProfile, double[]>> profileEntries = new ArrayList<>(rootProfileWeights.entrySet());
profileEntries.sort((a, b) -> Integer.compare(a.getKey().hashCode(), b.getKey().hashCode()));
for (Map.Entry<IrisCaveProfile, double[]> profileEntry : profileEntries) {
weightedProfiles.add(new WeightedProfile(profileEntry.getKey(), profileEntry.getValue(), -1D, worldYRange));
}
}
return weightedProfiles;
@@ -198,12 +214,6 @@ public class MantleCarvingComponent extends IrisMantleComponent {
return (BLEND_RADIUS + 1D) - edgeDistance;
}
private double[] fullWeights() {
double[] weights = new double[CHUNK_AREA];
Arrays.fill(weights, 1D);
return weights;
}
private IrisCaveProfile resolveColumnProfile(int worldX, int worldZ) {
IrisCaveProfile resolved = null;
IrisCaveProfile dimensionProfile = getDimension().getCaveProfile();

View File

@@ -161,7 +161,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
continue;
}
biomeCaveChecked++;
boolean chance = rng.chance(i.getChance() + rng.d(-0.005, 0.005));
boolean chance = rng.chance(i.getChance());
if (traceRegen) {
Iris.info("Regen object placer chance: chunk=" + x + "," + z
+ " scope=biome-cave"
@@ -226,7 +226,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
continue;
}
regionCaveChecked++;
boolean chance = rng.chance(i.getChance() + rng.d(-0.005, 0.005));
boolean chance = rng.chance(i.getChance());
if (traceRegen) {
Iris.info("Regen object placer chance: chunk=" + x + "," + z
+ " scope=region-cave"

View File

@@ -57,6 +57,7 @@ import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
@@ -78,6 +79,7 @@ public class IrisDimension extends IrisRegistrant {
private final transient AtomicCache<Double> rad = new AtomicCache<>();
private final transient AtomicCache<Boolean> featuresUsed = new AtomicCache<>();
private final transient AtomicCache<KMap<String, KList<String>>> cachedPreProcessors = new AtomicCache<>();
private final transient AtomicCache<Map<String, IrisDimensionCarvingEntry>> carvingEntryIndex = new AtomicCache<>();
@MinNumber(2)
@Required
@Desc("The human readable name of this dimension")
@@ -270,9 +272,39 @@ public class IrisDimension extends IrisRegistrant {
return (int) getDimensionHeight().getMax();
}
public int getMinHeight() {
return (int) getDimensionHeight().getMin();
}
public int getMinHeight() {
return (int) getDimensionHeight().getMin();
}
public Map<String, IrisDimensionCarvingEntry> getCarvingEntryIndex() {
return carvingEntryIndex.aquire(() -> {
Map<String, IrisDimensionCarvingEntry> index = new HashMap<>();
KList<IrisDimensionCarvingEntry> entries = getCarving();
if (entries == null || entries.isEmpty()) {
return index;
}
for (IrisDimensionCarvingEntry entry : entries) {
if (entry == null) {
continue;
}
String entryId = entry.getId();
if (entryId == null || entryId.isBlank()) {
continue;
}
index.put(entryId.trim(), entry);
}
return index;
});
}
public void setCarving(KList<IrisDimensionCarvingEntry> carving) {
this.carving = carving == null ? new KList<>() : carving;
carvingEntryIndex.reset();
}
public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data, boolean surface) {
if (ores.isEmpty()) {

View File

@@ -1,8 +1,15 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.data.cache.AtomicCache;
import art.arcane.iris.engine.object.annotations.ArrayType;
import art.arcane.iris.engine.object.annotations.Desc;
import art.arcane.iris.engine.object.annotations.DependsOn;
import art.arcane.iris.engine.object.annotations.RegistryListResource;
import art.arcane.iris.engine.object.annotations.Snippet;
import art.arcane.iris.util.project.noise.CNG;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.math.RNG;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -15,6 +22,9 @@ import lombok.experimental.Accessors;
@Desc("Dimension-level cave biome override with absolute world Y bounds.")
@Data
public class IrisDimensionCarvingEntry {
private final transient AtomicCache<IrisBiome> realBiome = new AtomicCache<>(true);
private final transient AtomicCache<CNG> childGenerator = new AtomicCache<>();
@Desc("Stable id for this carving entry")
private String id = "";
@@ -27,4 +37,41 @@ public class IrisDimensionCarvingEntry {
@Desc("Absolute world Y bounds where this carving entry applies")
private IrisRange worldYRange = new IrisRange(-64, 320);
@DependsOn({"children"})
@Desc("If this carving entry has child carving entries, this controls how small those child carving patches are.")
private double childShrinkFactor = 1.5;
@DependsOn({"children"})
@Desc("If this carving entry has child carving entries, this controls the shape pattern used to pick them.")
private IrisGeneratorStyle childStyle = NoiseStyle.CELLULAR_IRIS_DOUBLE.style();
@ArrayType(min = 1, type = String.class)
@Desc("Child carving entry ids. Child ids can point back to parent ids to create cycles; recursion is bounded by childRecursionDepth.")
private KList<String> children = new KList<>();
@Desc("Maximum recursion depth when resolving child carving entries from this entry.")
private int childRecursionDepth = 3;
public IrisBiome getRealBiome(IrisData data) {
return realBiome.aquire(() -> {
String biomeKey = getBiome();
if (biomeKey == null || biomeKey.isBlank()) {
return null;
}
return data.getBiomeLoader().load(biomeKey.trim());
});
}
public CNG getChildrenGenerator(long seed, IrisData data) {
return childGenerator.aquire(() -> {
String entryId = getId();
long idHash = entryId == null ? 0L : entryId.trim().hashCode();
long generatorSeed = seed ^ (idHash << 32) ^ 2137L;
double scale = Math.max(0.0001D, getChildShrinkFactor());
RNG random = new RNG(generatorSeed);
return getChildStyle().create(random.nextParallelRNG(2137), data).bake().scale(scale).bake();
});
}
}

View File

@@ -0,0 +1,175 @@
package art.arcane.iris.engine.object;
import art.arcane.iris.engine.framework.Engine;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.iris.util.project.noise.CNG;
import java.util.List;
import java.util.Map;
public final class IrisDimensionCarvingResolver {
private static final int MAX_CHILD_DEPTH = 32;
private static final long CHILD_SEED_SALT = 0x9E3779B97F4A7C15L;
private IrisDimensionCarvingResolver() {
}
public static IrisDimensionCarvingEntry resolveRootEntry(Engine engine, int worldY) {
IrisDimension dimension = engine.getDimension();
List<IrisDimensionCarvingEntry> entries = dimension.getCarving();
if (entries == null || entries.isEmpty()) {
return null;
}
IrisDimensionCarvingEntry resolved = null;
for (IrisDimensionCarvingEntry entry : entries) {
if (!isRootCandidate(engine, entry, worldY)) {
continue;
}
resolved = entry;
}
return resolved;
}
public static IrisDimensionCarvingEntry resolveFromRoot(Engine engine, IrisDimensionCarvingEntry rootEntry, int worldX, int worldZ) {
if (rootEntry == null) {
return null;
}
IrisBiome rootBiome = resolveEntryBiome(engine, rootEntry);
if (rootBiome == null) {
return null;
}
int remainingDepth = clampDepth(rootEntry.getChildRecursionDepth());
if (remainingDepth <= 0) {
return rootEntry;
}
Map<String, IrisDimensionCarvingEntry> entryIndex = engine.getDimension().getCarvingEntryIndex();
IrisDimensionCarvingEntry current = rootEntry;
int depth = remainingDepth;
while (depth > 0) {
IrisDimensionCarvingEntry selected = selectChild(engine, current, worldX, worldZ, entryIndex);
if (selected == null || selected == current) {
break;
}
depth--;
int childDepthLimit = clampDepth(selected.getChildRecursionDepth());
if (childDepthLimit < depth) {
depth = childDepthLimit;
}
current = selected;
}
return current;
}
public static IrisBiome resolveEntryBiome(Engine engine, IrisDimensionCarvingEntry entry) {
if (entry == null) {
return null;
}
return entry.getRealBiome(engine.getData());
}
private static boolean isRootCandidate(Engine engine, IrisDimensionCarvingEntry entry, int worldY) {
if (entry == null || !entry.isEnabled()) {
return false;
}
IrisRange worldYRange = entry.getWorldYRange();
if (worldYRange != null && !worldYRange.contains(worldY)) {
return false;
}
return resolveEntryBiome(engine, entry) != null;
}
private static IrisDimensionCarvingEntry selectChild(
Engine engine,
IrisDimensionCarvingEntry parent,
int worldX,
int worldZ,
Map<String, IrisDimensionCarvingEntry> entryIndex
) {
KList<String> children = parent.getChildren();
if (children == null || children.isEmpty()) {
return parent;
}
IrisBiome parentBiome = resolveEntryBiome(engine, parent);
if (parentBiome == null) {
return parent;
}
KList<CarvingChoice> options = new KList<>();
for (String childId : children) {
if (childId == null || childId.isBlank()) {
continue;
}
IrisDimensionCarvingEntry child = entryIndex.get(childId.trim());
if (child == null || !child.isEnabled()) {
continue;
}
IrisBiome childBiome = resolveEntryBiome(engine, child);
if (childBiome == null) {
continue;
}
options.add(new CarvingChoice(child, rarity(childBiome)));
}
options.add(new CarvingChoice(parent, rarity(parentBiome)));
if (options.size() <= 1) {
return parent;
}
long seed = engine.getSeedManager().getCarve() ^ CHILD_SEED_SALT;
CNG childGenerator = parent.getChildrenGenerator(seed, engine.getData());
CarvingChoice selected = childGenerator.fitRarity(options, worldX, worldZ);
if (selected == null || selected.entry == null) {
return parent;
}
return selected.entry;
}
private static int rarity(IrisBiome biome) {
if (biome == null) {
return 1;
}
int rarity = biome.getRarity();
return Math.max(rarity, 1);
}
private static int clampDepth(int depth) {
if (depth <= 0) {
return 0;
}
return Math.min(depth, MAX_CHILD_DEPTH);
}
private static final class CarvingChoice implements IRare {
private final IrisDimensionCarvingEntry entry;
private final int rarity;
private CarvingChoice(IrisDimensionCarvingEntry entry, int rarity) {
this.entry = entry;
this.rarity = rarity;
}
@Override
public int getRarity() {
return rarity;
}
}
}