mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-04-03 06:16:19 +00:00
f
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user