From a38e87cce3dfbbe6fecd4045fa4aea3b355e3bd6 Mon Sep 17 00:00:00 2001 From: Brian Neumann-Fopiano Date: Sat, 21 Feb 2026 03:49:10 -0500 Subject: [PATCH] f --- .../arcane/iris/engine/framework/Engine.java | 31 +--- .../components/MantleCarvingComponent.java | 54 +++--- .../components/MantleObjectComponent.java | 4 +- .../iris/engine/object/IrisDimension.java | 38 +++- .../object/IrisDimensionCarvingEntry.java | 47 +++++ .../object/IrisDimensionCarvingResolver.java | 175 ++++++++++++++++++ 6 files changed, 297 insertions(+), 52 deletions(-) create mode 100644 core/src/main/java/art/arcane/iris/engine/object/IrisDimensionCarvingResolver.java diff --git a/core/src/main/java/art/arcane/iris/engine/framework/Engine.java b/core/src/main/java/art/arcane/iris/engine/framework/Engine.java index c611f1ba2..0f3e74435 100644 --- a/core/src/main/java/art/arcane/iris/engine/framework/Engine.java +++ b/core/src/main/java/art/arcane/iris/engine/framework/Engine.java @@ -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 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) { diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleCarvingComponent.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleCarvingComponent.java index bb3f5d195..b78e79788 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleCarvingComponent.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleCarvingComponent.java @@ -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 resolveDimensionCarvingProfiles() { + private List resolveDimensionCarvingProfiles(int chunkX, int chunkZ) { List weightedProfiles = new ArrayList<>(); List 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 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> profileEntries = new ArrayList<>(rootProfileWeights.entrySet()); + profileEntries.sort((a, b) -> Integer.compare(a.getKey().hashCode(), b.getKey().hashCode())); + for (Map.Entry 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(); diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleObjectComponent.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleObjectComponent.java index 829a1003e..78e1760b8 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleObjectComponent.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleObjectComponent.java @@ -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" diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java b/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java index 35baf7536..62cfefc0d 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java @@ -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 rad = new AtomicCache<>(); private final transient AtomicCache featuresUsed = new AtomicCache<>(); private final transient AtomicCache>> cachedPreProcessors = new AtomicCache<>(); + private final transient AtomicCache> 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 getCarvingEntryIndex() { + return carvingEntryIndex.aquire(() -> { + Map index = new HashMap<>(); + KList 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 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()) { diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisDimensionCarvingEntry.java b/core/src/main/java/art/arcane/iris/engine/object/IrisDimensionCarvingEntry.java index d9d60743e..0dfdb97da 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisDimensionCarvingEntry.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisDimensionCarvingEntry.java @@ -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 realBiome = new AtomicCache<>(true); + private final transient AtomicCache 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 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(); + }); + } } diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisDimensionCarvingResolver.java b/core/src/main/java/art/arcane/iris/engine/object/IrisDimensionCarvingResolver.java new file mode 100644 index 000000000..70ffc2a1f --- /dev/null +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisDimensionCarvingResolver.java @@ -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 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 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 entryIndex + ) { + KList children = parent.getChildren(); + if (children == null || children.isEmpty()) { + return parent; + } + + IrisBiome parentBiome = resolveEntryBiome(engine, parent); + if (parentBiome == null) { + return parent; + } + + KList 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; + } + } +}