mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-27 06:20:02 +00:00
Generation and runtime fixes
- Surface detail blends raw noise with cached six-block interpolation - Locked biome layers sample only the requested ranges - Surface layers skip noise when slope requirements fail - Stilt supports exclude configured materials and block entities - Ice and glass remain eligible foundation materials - Block-drop routing filters materials before loading biome history - Vision displays biome previews before river refinement - Interrupted previews retain reusable partial tiles - Hydrology waiters stop without cancelling shared planning tasks - Regression tests cover generation, supports, drop routing, and preview cancellation
This commit is contained in:
1 parent
e3c6a32968
commit
9a1e4b91d1
25 files changed
+1290
-84
No files matched your search
@@ -78,10 +78,6 @@ final class IrisBiomeLayerGenerator {
|
||||
|
||||
for (int i = 0; i < layerCount; i++) {
|
||||
IrisBiomePaletteLayer layer = layers.get(i);
|
||||
double zoom = layer.getZoom();
|
||||
CNG hgen = heightGenerators.get(i);
|
||||
int d = hgen.fit(layer.getMinHeight(), layer.getMaxHeight(), wx / zoom, wz / zoom);
|
||||
|
||||
IrisSlopeClip sc = layer.getSlopeCondition();
|
||||
|
||||
if (!sc.isDefault()) {
|
||||
@@ -90,10 +86,13 @@ final class IrisBiomeLayerGenerator {
|
||||
sampledSlope = true;
|
||||
}
|
||||
if (!sc.isValid(surfaceSlope)) {
|
||||
d = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
double zoom = layer.getZoom();
|
||||
CNG hgen = heightGenerators.get(i);
|
||||
int d = hgen.fit(layer.getMinHeight(), layer.getMaxHeight(), wx / zoom, wz / zoom);
|
||||
if (d <= 0) {
|
||||
continue;
|
||||
}
|
||||
@@ -171,56 +170,78 @@ final class IrisBiomeLayerGenerator {
|
||||
|
||||
private static KList<PlatformBlockState> generateLockedLayers(IrisBiome biome, double wx, double wz, RNG random, int maxDepthf, int height, IrisData rdata, IrisComplex complex, ProceduralStream<Double> slopeStream) {
|
||||
KList<PlatformBlockState> data = new KList<>();
|
||||
KList<PlatformBlockState> real = new KList<>();
|
||||
int maxDepth = Math.min(maxDepthf, biome.getLockLayersMax());
|
||||
if (maxDepth <= 0) {
|
||||
KList<IrisBiomePaletteLayer> layers = biome.getLayers();
|
||||
int layerCount = layers.size();
|
||||
if (maxDepth <= 0 || layerCount == 0) {
|
||||
return data;
|
||||
}
|
||||
|
||||
KList<IrisBiomePaletteLayer> layers = biome.getLayers();
|
||||
int layerCount = layers.size();
|
||||
|
||||
if (layerCount > 0) {
|
||||
KList<CNG> heightGenerators = getLayerHeightGenerators(biome, random, rdata);
|
||||
double surfaceSlope = 0D;
|
||||
boolean sampledSlope = false;
|
||||
|
||||
for (int i = 0; i < layerCount; i++) {
|
||||
IrisBiomePaletteLayer layer = layers.get(i);
|
||||
double zoom = layer.getZoom();
|
||||
CNG hgen = heightGenerators.get(i);
|
||||
int d = hgen.fit(layer.getMinHeight(), layer.getMaxHeight(), wx / zoom, wz / zoom);
|
||||
|
||||
IrisSlopeClip sc = layer.getSlopeCondition();
|
||||
|
||||
if (!sc.isDefault()) {
|
||||
if (!sampledSlope) {
|
||||
surfaceSlope = resolveSurfaceSlope(complex, slopeStream, wx, height, wz);
|
||||
sampledSlope = true;
|
||||
}
|
||||
if (!sc.isValid(surfaceSlope)) {
|
||||
d = 0;
|
||||
}
|
||||
KList<CNG> heightGenerators = getLayerHeightGenerators(biome, random, rdata);
|
||||
int[] boundaries = new int[layerCount + 1];
|
||||
double surfaceSlope = 0D;
|
||||
boolean sampledSlope = false;
|
||||
for (int i = 0; i < layerCount; i++) {
|
||||
IrisBiomePaletteLayer layer = layers.get(i);
|
||||
IrisSlopeClip sc = layer.getSlopeCondition();
|
||||
boundaries[i + 1] = boundaries[i];
|
||||
if (!sc.isDefault()) {
|
||||
if (!sampledSlope) {
|
||||
surfaceSlope = resolveSurfaceSlope(complex, slopeStream, wx, height, wz);
|
||||
sampledSlope = true;
|
||||
}
|
||||
|
||||
if (d <= 0) {
|
||||
if (!sc.isValid(surfaceSlope)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
appendLayer(data, layer, i, d, wx, wz, random, Integer.MAX_VALUE, rdata);
|
||||
double zoom = layer.getZoom();
|
||||
CNG hgen = heightGenerators.get(i);
|
||||
int thickness = hgen.fit(layer.getMinHeight(), layer.getMaxHeight(), wx / zoom, wz / zoom);
|
||||
boundaries[i + 1] += Math.max(0, thickness);
|
||||
}
|
||||
|
||||
int cycleLength = boundaries[layerCount];
|
||||
if (cycleLength == 0) {
|
||||
return data;
|
||||
}
|
||||
|
||||
int sampledDepth = Math.min(maxDepth, cycleLength);
|
||||
for (int depth = 0; depth < sampledDepth; depth++) {
|
||||
int position = Math.floorMod(512L - height - depth, cycleLength);
|
||||
int layerIndex = findLayer(boundaries, position);
|
||||
IrisBiomePaletteLayer layer = layers.get(layerIndex);
|
||||
int offset = position - boundaries[layerIndex];
|
||||
double zoom = layer.getZoom();
|
||||
try {
|
||||
data.add(layer.get(random, layerIndex + offset,
|
||||
(wx + offset) / zoom, offset, (wz - offset) / zoom, rdata));
|
||||
} catch (Throwable error) {
|
||||
IrisLogging.reportError(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.isEmpty()) {
|
||||
return real;
|
||||
if (sampledDepth == cycleLength && !data.isEmpty()) {
|
||||
int resolvedCycleLength = data.size();
|
||||
for (int depth = resolvedCycleLength; depth < maxDepth; depth++) {
|
||||
data.add(data.get(depth % resolvedCycleLength));
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
for (int i = 0; i < maxDepth; i++) {
|
||||
long offset = 512L - height - i;
|
||||
real.add(data.get(Math.floorMod(offset, data.size())));
|
||||
private static int findLayer(int[] boundaries, int position) {
|
||||
int low = 0;
|
||||
int high = boundaries.length - 1;
|
||||
while (low < high) {
|
||||
int middle = (low + high) >>> 1;
|
||||
if (boundaries[middle + 1] <= position) {
|
||||
low = middle + 1;
|
||||
} else {
|
||||
high = middle;
|
||||
}
|
||||
}
|
||||
|
||||
return real;
|
||||
return low;
|
||||
}
|
||||
|
||||
private static double resolveSurfaceSlope(
|
||||
|
||||
@@ -3,14 +3,21 @@ package art.arcane.iris.generation.decoration;
|
||||
import art.arcane.iris.generation.terrain.IrisMaterialPalette;
|
||||
|
||||
import art.arcane.volmlib.util.documentation.Description;
|
||||
import art.arcane.iris.pack.schema.annotation.ArrayType;
|
||||
import art.arcane.iris.pack.schema.annotation.MaxNumber;
|
||||
import art.arcane.iris.pack.schema.annotation.MinNumber;
|
||||
import art.arcane.iris.pack.schema.annotation.RegistryListBlockType;
|
||||
import art.arcane.iris.pack.schema.annotation.Snippet;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
@Snippet("stilt-settings")
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@@ -32,6 +39,10 @@ public class IrisStiltSettings {
|
||||
private int overStilt;
|
||||
@Description("If defined, stilting will be done using this block palette rather than the last layer of the object.")
|
||||
private IrisMaterialPalette palette;
|
||||
@ArrayType(min = 1, type = String.class)
|
||||
@RegistryListBlockType
|
||||
@Description("Block materials that must never extend into stilts. Matches all block states of each material, before and after palette replacement or object edits. Original object blocks remain in place. Containers and blocks with tile entities are always excluded.")
|
||||
private KList<String> exclude = new KList<>();
|
||||
@MinNumber(1)
|
||||
@MaxNumber(256)
|
||||
@Description("For ORGANIC_STILT / CEILING_HANG: the maximum number of blocks to scan toward the cave floor (or ceiling) looking for solid ground before giving up.")
|
||||
@@ -45,4 +56,16 @@ public class IrisStiltSettings {
|
||||
@Description("For ORGANIC_STILT / CEILING_HANG: in the deepest fraction of each stilt column, blocks are randomly skipped to break the tip up. Higher = more broken and scratchy. 0 disables.")
|
||||
private double organicScratch = 0.55;
|
||||
|
||||
public Set<String> excludedMaterials() {
|
||||
if (exclude == null || exclude.isEmpty()) {
|
||||
return Set.of();
|
||||
}
|
||||
Set<String> materials = new HashSet<>(exclude.size());
|
||||
for (String block : exclude) {
|
||||
String material = block.trim().toLowerCase(Locale.ROOT);
|
||||
materials.add(material.indexOf(':') < 0 ? "minecraft:" + material : material);
|
||||
}
|
||||
return materials;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CancellationException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -890,8 +890,11 @@ public final class HydrologyTileCache implements AutoCloseable {
|
||||
|
||||
private static <T> T awaitPlan(CompletableFuture<T> future) {
|
||||
try {
|
||||
return future.join();
|
||||
} catch (CompletionException failure) {
|
||||
return future.get();
|
||||
} catch (InterruptedException interrupted) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new CancellationException("Hydrology plan wait interrupted.");
|
||||
} catch (ExecutionException failure) {
|
||||
Throwable cause = failure.getCause();
|
||||
if (cause instanceof RuntimeException runtimeException) {
|
||||
throw runtimeException;
|
||||
|
||||
@@ -20,6 +20,8 @@ package art.arcane.iris.generation.noise;
|
||||
|
||||
import art.arcane.iris.pack.loading.IrisRegistrant;
|
||||
import art.arcane.iris.generation.cache.AtomicCache;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.pack.schema.annotation.ArrayType;
|
||||
import art.arcane.volmlib.util.documentation.Description;
|
||||
import art.arcane.iris.pack.schema.annotation.MaxNumber;
|
||||
@@ -29,10 +31,14 @@ import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.interpolation.IrisInterpolation;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.noise.CellGenerator;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.List;
|
||||
@@ -44,13 +50,24 @@ import java.util.List;
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
public class IrisGenerator extends IrisRegistrant {
|
||||
private static final int SURFACE_GRID = 6;
|
||||
private static final int SURFACE_CACHE_STRIPES = 16;
|
||||
private static final int SURFACE_CACHE_STRIPE_SIZE = 1024;
|
||||
|
||||
private final transient AtomicCache<CellGenerator> cellGen = new AtomicCache<>();
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private transient volatile SurfaceCache surfaceSamples;
|
||||
@MinNumber(0.001)
|
||||
@Description("The zoom or frequency.")
|
||||
private double zoom = 1;
|
||||
@MinNumber(0)
|
||||
@Description("The opacity, essentially a multiplier on the output.")
|
||||
private double opacity = 1;
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1)
|
||||
@Description("Fine terrain detail retained around bilinear interpolation of the original noise on a 6-block grid. 1 preserves the original noise; 0.5 halves the sub-grid residual. Original grid heights, feature sizes and height ranges stay unchanged.")
|
||||
private double surfaceDetail = 1;
|
||||
@Description("Multiply the compsites instead of adding them")
|
||||
private boolean multiplicitive = false;
|
||||
@MinNumber(0.001)
|
||||
@@ -165,6 +182,37 @@ public class IrisGenerator extends IrisRegistrant {
|
||||
}
|
||||
|
||||
public double getHeight(double rx, double ry, double rz, long superSeed, boolean no3d) {
|
||||
if (!Double.isFinite(surfaceDetail) || surfaceDetail < 0D || surfaceDetail > 1D) {
|
||||
throw new IllegalArgumentException("Generator surfaceDetail must be finite and between 0 and 1");
|
||||
}
|
||||
if (surfaceDetail == 1D || composite.isEmpty()) {
|
||||
return sampleHeight(rx, rz, superSeed);
|
||||
}
|
||||
IrisData data = getLoader();
|
||||
Engine engine = data == null ? null : data.getEngine();
|
||||
SurfaceCacheStripe[] stripes = surfaceCache(data, engine).stripes();
|
||||
double lowerX = Math.floor(rx / SURFACE_GRID) * SURFACE_GRID;
|
||||
double lowerZ = Math.floor(rz / SURFACE_GRID) * SURFACE_GRID;
|
||||
double dx = (rx - lowerX) / SURFACE_GRID;
|
||||
double dz = (rz - lowerZ) / SURFACE_GRID;
|
||||
double northWest = cachedSurfaceHeight(lowerX, lowerZ, superSeed, stripes);
|
||||
if (dx == 0D && dz == 0D) {
|
||||
return northWest;
|
||||
}
|
||||
double north = dx == 0D ? northWest : IrisInterpolation.lerp(northWest,
|
||||
cachedSurfaceHeight(lowerX + SURFACE_GRID, lowerZ, superSeed, stripes), dx);
|
||||
double smooth = north;
|
||||
if (dz != 0D) {
|
||||
double southWest = cachedSurfaceHeight(lowerX, lowerZ + SURFACE_GRID, superSeed, stripes);
|
||||
double south = dx == 0D ? southWest : IrisInterpolation.lerp(southWest,
|
||||
cachedSurfaceHeight(lowerX + SURFACE_GRID, lowerZ + SURFACE_GRID, superSeed, stripes), dx);
|
||||
smooth = IrisInterpolation.lerp(north, south, dz);
|
||||
}
|
||||
return surfaceDetail == 0D ? smooth
|
||||
: smooth + (sampleHeight(rx, rz, superSeed) - smooth) * surfaceDetail;
|
||||
}
|
||||
|
||||
private double sampleHeight(double rx, double rz, long superSeed) {
|
||||
if (composite.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
@@ -205,6 +253,47 @@ public class IrisGenerator extends IrisRegistrant {
|
||||
return v;
|
||||
}
|
||||
|
||||
private double cachedSurfaceHeight(double x, double z, long superSeed, SurfaceCacheStripe[] stripes) {
|
||||
if (x != (int) x || z != (int) z) {
|
||||
return sampleHeight(x, z, superSeed);
|
||||
}
|
||||
long key = ((long) (int) x << 32) | ((int) z & 0xffffffffL);
|
||||
long mixed = key ^ (key >>> 33);
|
||||
mixed *= 0xff51afd7ed558ccdL;
|
||||
mixed ^= mixed >>> 33;
|
||||
SurfaceCacheStripe stripe = stripes[(int) mixed & (SURFACE_CACHE_STRIPES - 1)];
|
||||
SurfaceSample cached = stripe.get(key, superSeed);
|
||||
if (cached != null) {
|
||||
return cached.height();
|
||||
}
|
||||
double height = sampleHeight(x, z, superSeed);
|
||||
stripe.put(key, new SurfaceSample(superSeed, height));
|
||||
return height;
|
||||
}
|
||||
|
||||
private SurfaceCache surfaceCache(IrisData data, Engine engine) {
|
||||
SurfaceCache cached = surfaceSamples;
|
||||
if (cached != null && cached.data() == data && cached.engine() == engine) {
|
||||
return cached;
|
||||
}
|
||||
synchronized (this) {
|
||||
cached = surfaceSamples;
|
||||
if (cached == null || cached.data() != data || cached.engine() != engine) {
|
||||
cached = new SurfaceCache(data, engine, createSurfaceCache());
|
||||
surfaceSamples = cached;
|
||||
}
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
|
||||
private static SurfaceCacheStripe[] createSurfaceCache() {
|
||||
SurfaceCacheStripe[] stripes = new SurfaceCacheStripe[SURFACE_CACHE_STRIPES];
|
||||
for (int i = 0; i < stripes.length; i++) {
|
||||
stripes[i] = new SurfaceCacheStripe();
|
||||
}
|
||||
return stripes;
|
||||
}
|
||||
|
||||
public double cell(double rx, double rz, double v, double superSeed) {
|
||||
getCellGenerator(getSeed() + 46222).setShuffle(getCellFractureShuffle());
|
||||
double fractureX = rx / getCellFractureZoom();
|
||||
@@ -231,6 +320,7 @@ public class IrisGenerator extends IrisRegistrant {
|
||||
|
||||
public IrisGenerator rescale(double scale) {
|
||||
zoom /= scale;
|
||||
surfaceSamples = null;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -253,4 +343,26 @@ public class IrisGenerator extends IrisRegistrant {
|
||||
public String getTypeName() {
|
||||
return "Generator";
|
||||
}
|
||||
|
||||
private record SurfaceCache(IrisData data, Engine engine, SurfaceCacheStripe[] stripes) {
|
||||
}
|
||||
|
||||
private record SurfaceSample(long seed, double height) {
|
||||
}
|
||||
|
||||
private static final class SurfaceCacheStripe {
|
||||
private final Long2ObjectLinkedOpenHashMap<SurfaceSample> entries = new Long2ObjectLinkedOpenHashMap<>(16);
|
||||
|
||||
private synchronized SurfaceSample get(long key, long seed) {
|
||||
SurfaceSample sample = entries.getAndMoveToLast(key);
|
||||
return sample != null && sample.seed() == seed ? sample : null;
|
||||
}
|
||||
|
||||
private synchronized void put(long key, SurfaceSample sample) {
|
||||
entries.putAndMoveToLast(key, sample);
|
||||
if (entries.size() > SURFACE_CACHE_STRIPE_SIZE) {
|
||||
entries.removeFirst();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -173,6 +173,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
getData().prepareBlockDropRules();
|
||||
super.start();
|
||||
if (!looper.isAlive()) {
|
||||
looper.start();
|
||||
|
||||
@@ -21,12 +21,14 @@ package art.arcane.iris.generation.runtime;
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.world.tree.BlockDropRouter;
|
||||
import art.arcane.iris.world.history.SavedBiomeUnavailableException;
|
||||
import art.arcane.iris.world.history.GenerationHistoryRuntimeRouter;
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.world.loot.IrisBlockDrops;
|
||||
import art.arcane.iris.world.entity.IrisMarker;
|
||||
import art.arcane.iris.generation.terrain.IrisRegion;
|
||||
import art.arcane.iris.platform.generation.EngineBukkitOps;
|
||||
import art.arcane.iris.platform.bukkit.BukkitWorldBinding;
|
||||
import art.arcane.iris.platform.bukkit.BukkitBlockState;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.world.task.J;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
@@ -139,6 +141,9 @@ final class WorldBlockDropRouter {
|
||||
}
|
||||
|
||||
private List<IrisBlockDrops> resolveDropProviders(BlockBreakEvent event) {
|
||||
if (!mayHaveBlockDropRules(BukkitBlockState.of(event.getBlock().getBlockData()).materialKey())) {
|
||||
return List.of();
|
||||
}
|
||||
IrisBiome biome = EngineBukkitOps.getBiome(manager.getEngine(), event.getBlock().getLocation());
|
||||
List<IrisBlockDrops> providers = filterDrops(biome.getBlockDrops(), event, manager.getData());
|
||||
if (providers.stream().noneMatch(IrisBlockDrops::isSkipParents)) {
|
||||
@@ -148,4 +153,15 @@ final class WorldBlockDropRouter {
|
||||
}
|
||||
return providers;
|
||||
}
|
||||
|
||||
private boolean mayHaveBlockDropRules(String material) {
|
||||
if (manager.getData().hasBlockDropRules(material)) {
|
||||
return true;
|
||||
}
|
||||
if (manager.getEngine() instanceof IrisEngine engine) {
|
||||
GenerationHistoryRuntimeRouter router = engine.getGenerationHistoryRuntimeRouter().orElse(null);
|
||||
return router != null && router.biomes().mayHaveBlockDropRules(material);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,9 @@ import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.pack.PackDirectoryResolver;
|
||||
import art.arcane.iris.generation.cache.AtomicCache;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.generation.decoration.IrisProceduralBlocks;
|
||||
import art.arcane.iris.world.loot.IrisBlockDrops;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.structure.placement.IrisStructureLocator;
|
||||
import art.arcane.iris.structure.graph.StructureGraphCatalog;
|
||||
import art.arcane.iris.world.history.GenerationPackFingerprint;
|
||||
@@ -102,6 +105,7 @@ import java.util.Optional;
|
||||
import java.util.Collections;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Set;
|
||||
import java.util.HashSet;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Data
|
||||
@@ -157,6 +161,9 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
@EqualsAndHashCode.Exclude
|
||||
private transient volatile GenerationRegistryContractFactory.CustomBiomeResourceResolver customBiomeResourceResolver;
|
||||
private transient volatile Engine soleEngine;
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private transient volatile Set<String> blockDropMaterials;
|
||||
|
||||
private IrisData(File dataFolder) {
|
||||
this(dataFolder, false);
|
||||
@@ -651,6 +658,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
}
|
||||
|
||||
public synchronized void hotloaded() {
|
||||
blockDropMaterials = null;
|
||||
StructureGraphCatalog.invalidate(this);
|
||||
IrisObjectScale.invalidate(this);
|
||||
contentGate = null;
|
||||
@@ -730,6 +738,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
}
|
||||
|
||||
public void dump() {
|
||||
blockDropMaterials = null;
|
||||
StructureGraphCatalog.invalidate(this);
|
||||
IrisObjectScale.invalidate(this);
|
||||
for (ResourceLoader<?> i : loaders.values()) {
|
||||
@@ -771,12 +780,43 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
|
||||
}
|
||||
|
||||
public void clearLists() {
|
||||
blockDropMaterials = null;
|
||||
for (ResourceLoader<?> i : loaders.values()) {
|
||||
i.clearList();
|
||||
}
|
||||
possibleSnippets.clear();
|
||||
}
|
||||
|
||||
public boolean hasBlockDropRules(String material) {
|
||||
Set<String> materials = blockDropMaterials;
|
||||
return materials == null || materials.contains(material);
|
||||
}
|
||||
|
||||
public void prepareBlockDropRules() {
|
||||
Set<String> materials = new HashSet<>();
|
||||
for (IrisDimension dimension : dimensionLoader.loadAll(dimensionLoader.getPossibleKeys())) {
|
||||
collectBlockDropMaterials(materials, dimension.getBlockDrops());
|
||||
}
|
||||
for (IrisRegion region : regionLoader.loadAll(regionLoader.getPossibleKeys())) {
|
||||
collectBlockDropMaterials(materials, region.getBlockDrops());
|
||||
}
|
||||
for (IrisBiome biome : biomeLoader.loadAll(biomeLoader.getPossibleKeys())) {
|
||||
collectBlockDropMaterials(materials, biome.getBlockDrops());
|
||||
}
|
||||
blockDropMaterials = Set.copyOf(materials);
|
||||
}
|
||||
|
||||
private void collectBlockDropMaterials(Set<String> materials, KList<IrisBlockDrops> rules) {
|
||||
for (IrisBlockDrops rule : rules) {
|
||||
for (IrisBlockData block : rule.getBlocks()) {
|
||||
PlatformBlockState state = block.getBlockData(this);
|
||||
if (state != null) {
|
||||
materials.add(IrisProceduralBlocks.materialKey(state.placementBaseState()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void invalidateLoader(ResourceLoader<?> loader) {
|
||||
if (loader == null) {
|
||||
return;
|
||||
|
||||
@@ -50,6 +50,7 @@ import art.arcane.volmlib.util.matter.MatterMarker;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.stream.StreamSupport;
|
||||
@@ -143,6 +144,9 @@ final class IrisObjectPlacementRunner {
|
||||
config.getMode() == ObjectPlaceMode.MIN_STILT || config.getMode() == ObjectPlaceMode.FAST_MIN_STILT ||
|
||||
config.getMode() == ObjectPlaceMode.CENTER_STILT || config.getMode() == ObjectPlaceMode.ERODE_STILT || organic);
|
||||
boolean eroding = config.getMode() == ObjectPlaceMode.ERODE_STILT;
|
||||
IrisStiltSettings stiltSettings = config.getStiltSettings();
|
||||
Set<String> excludedStiltMaterials = stilting && stiltSettings != null
|
||||
? stiltSettings.excludedMaterials() : Set.of();
|
||||
KMap<Position2, Integer> heightmap = config.getSnow() > 0 ? new KMap<>() : null;
|
||||
int spinx = rng.imax() / 1000;
|
||||
int spiny = rng.imax() / 1000;
|
||||
@@ -539,7 +543,7 @@ final class IrisObjectPlacementRunner {
|
||||
i.add(translateOffset);
|
||||
}
|
||||
|
||||
if (stilting && IrisObjectShaping.shouldStilt(data)) {
|
||||
if (stilting && IrisObjectShaping.isStiltLayerBlock(data)) {
|
||||
if (i.getBlockY() < lowest) {
|
||||
lowest = i.getBlockY();
|
||||
}
|
||||
@@ -654,7 +658,7 @@ final class IrisObjectPlacementRunner {
|
||||
self.readLock.lock();
|
||||
try {
|
||||
VectorMap<PlatformBlockState> blocks = self.blocks;
|
||||
IrisStiltSettings settings = config.getStiltSettings();
|
||||
IrisStiltSettings settings = stiltSettings;
|
||||
|
||||
double erodeCentroidX = 0;
|
||||
double erodeCentroidZ = 0;
|
||||
@@ -670,7 +674,7 @@ final class IrisObjectPlacementRunner {
|
||||
}
|
||||
if (rot.getBlockY() == lowest) {
|
||||
PlatformBlockState bd = centroidCursor.value();
|
||||
if (bd != null && IrisObjectShaping.shouldStilt(bd)) {
|
||||
if (bd != null && IrisObjectShaping.shouldStilt(bd, excludedStiltMaterials)) {
|
||||
erodeCentroidX += rot.getX();
|
||||
erodeCentroidZ += rot.getZ();
|
||||
centroidCount++;
|
||||
@@ -690,7 +694,7 @@ final class IrisObjectPlacementRunner {
|
||||
}
|
||||
if (rot.getBlockY() == lowest) {
|
||||
PlatformBlockState bd = spreadCursor.value();
|
||||
if (bd != null && IrisObjectShaping.shouldStilt(bd)) {
|
||||
if (bd != null && IrisObjectShaping.shouldStilt(bd, excludedStiltMaterials)) {
|
||||
double dx = rot.getX() - erodeCentroidX;
|
||||
double dz = rot.getZ() - erodeCentroidZ;
|
||||
double dist = Math.sqrt(dx * dx + dz * dz);
|
||||
@@ -719,7 +723,7 @@ final class IrisObjectPlacementRunner {
|
||||
sourceData = IrisObject.States.air();
|
||||
}
|
||||
|
||||
if (!IrisObjectShaping.shouldStilt(sourceData)) {
|
||||
if (!IrisObjectShaping.shouldStilt(sourceData, excludedStiltMaterials)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -768,7 +772,7 @@ final class IrisObjectPlacementRunner {
|
||||
}
|
||||
}
|
||||
|
||||
if (d == null || !d.isOccluding())
|
||||
if (d == null || !IrisObjectShaping.shouldStilt(d, excludedStiltMaterials))
|
||||
continue;
|
||||
|
||||
xx = x + (int) Math.round(i.getX());
|
||||
|
||||
@@ -29,6 +29,8 @@ import art.arcane.volmlib.util.math.Vector3i;
|
||||
import art.arcane.volmlib.util.format.Form;
|
||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Volume shaping for {@link IrisObject}: smart boring, shrinkwrapping, compaction and the shared block
|
||||
* classification helpers used by placement.
|
||||
@@ -244,17 +246,23 @@ final class IrisObjectShaping {
|
||||
self.floatingFootprint.reset();
|
||||
}
|
||||
|
||||
static boolean shouldStilt(PlatformBlockState state) {
|
||||
if (!state.isOccluding()) {
|
||||
static boolean isStiltLayerBlock(PlatformBlockState state) {
|
||||
String material = materialKey(state);
|
||||
if (!state.isOccluding() && !material.equals("minecraft:ice") && !material.equals("minecraft:glass")
|
||||
&& !material.equals("minecraft:tinted_glass") && !material.endsWith("_stained_glass")) {
|
||||
return false;
|
||||
}
|
||||
String material = materialKey(state);
|
||||
if (material.endsWith("_stairs") || material.endsWith("_slab")) {
|
||||
return false;
|
||||
}
|
||||
return !material.equals("minecraft:dirt_path");
|
||||
}
|
||||
|
||||
static boolean shouldStilt(PlatformBlockState state, Set<String> excludedMaterials) {
|
||||
return isStiltLayerBlock(state) && !state.isStorage() && !state.hasTileEntity()
|
||||
&& !excludedMaterials.contains(materialKey(state));
|
||||
}
|
||||
|
||||
static String materialKey(PlatformBlockState state) {
|
||||
return IrisProceduralBlocks.materialKey(state);
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ public final class IrisRenderer {
|
||||
RenderType currentType,
|
||||
BooleanSupplier cancelled
|
||||
) {
|
||||
return render(sx, sz, size, resolution, currentType, cancelled, false);
|
||||
return render(sx, sz, size, resolution, currentType, cancelled, false, false);
|
||||
}
|
||||
|
||||
public BufferedImage renderStudio(
|
||||
@@ -105,7 +105,41 @@ public final class IrisRenderer {
|
||||
RenderType currentType,
|
||||
BooleanSupplier cancelled
|
||||
) {
|
||||
return render(sx, sz, size, resolution, currentType, cancelled, true);
|
||||
return render(sx, sz, size, resolution, currentType, cancelled, true, true);
|
||||
}
|
||||
|
||||
public BufferedImage renderStudioBase(
|
||||
double sx,
|
||||
double sz,
|
||||
double size,
|
||||
int resolution,
|
||||
RenderType currentType,
|
||||
BooleanSupplier cancelled
|
||||
) {
|
||||
return render(sx, sz, size, resolution, currentType, cancelled, true, false);
|
||||
}
|
||||
|
||||
public BufferedImage refineStudioBiome(
|
||||
double sx,
|
||||
double sz,
|
||||
double size,
|
||||
BufferedImage base,
|
||||
BooleanSupplier cancelled
|
||||
) {
|
||||
Objects.requireNonNull(base, "base");
|
||||
Objects.requireNonNull(cancelled, "cancelled");
|
||||
checkCancelled(cancelled);
|
||||
if (!Double.isFinite(sx) || !Double.isFinite(sz) || !Double.isFinite(size) || size <= 0D
|
||||
|| base.getWidth() != base.getHeight()) {
|
||||
throw new IllegalArgumentException("Vision biome refinement requires finite coordinates and a square base image");
|
||||
}
|
||||
int resolution = base.getWidth();
|
||||
BufferedImage image = new BufferedImage(resolution, resolution, BufferedImage.TYPE_INT_RGB);
|
||||
base.copyData(image.getRaster());
|
||||
int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
|
||||
renderRiverAtlas(pixels, resolution, sx, sz, size / resolution, renderer.getComplex(), cancelled, true);
|
||||
checkCancelled(cancelled);
|
||||
return image;
|
||||
}
|
||||
|
||||
private BufferedImage render(
|
||||
@@ -115,7 +149,8 @@ public final class IrisRenderer {
|
||||
int resolution,
|
||||
RenderType currentType,
|
||||
BooleanSupplier cancelled,
|
||||
boolean studio
|
||||
boolean studio,
|
||||
boolean riverOverlay
|
||||
) {
|
||||
if (!Double.isFinite(sx) || !Double.isFinite(sz) || !Double.isFinite(size) || size <= 0D) {
|
||||
throw new IllegalArgumentException("Vision render coordinates and size must be finite");
|
||||
@@ -142,7 +177,7 @@ public final class IrisRenderer {
|
||||
}
|
||||
if (studio && adaptiveStudioType(currentType)) {
|
||||
renderAdaptiveAtlas(pixels, resolution, sx, sz, step, shader, cancelled);
|
||||
if (currentType == RenderType.BIOME) {
|
||||
if (currentType == RenderType.BIOME && riverOverlay) {
|
||||
renderRiverAtlas(pixels, resolution, sx, sz, step, renderer.getComplex(), cancelled, true);
|
||||
}
|
||||
return image;
|
||||
|
||||
@@ -136,7 +136,7 @@ final class VisionRenderController implements AutoCloseable {
|
||||
List<VisibleTile> tiles = visibleTiles(spec);
|
||||
Frame frame = new Frame(viewSequence.incrementAndGet(), spec, tiles);
|
||||
for (VisibleTile tile : tiles) {
|
||||
tile.setImage(cache.get(frame.key(tile)));
|
||||
tile.setResult(cache.get(frame.key(tile)));
|
||||
}
|
||||
CancellationToken token = new CancellationToken();
|
||||
WorkState work = new WorkState(frame, token);
|
||||
@@ -165,7 +165,7 @@ final class VisionRenderController implements AutoCloseable {
|
||||
}
|
||||
int ready = 0;
|
||||
for (VisibleTile tile : frame.tiles()) {
|
||||
if (tile.image() != null) {
|
||||
if (tile.complete()) {
|
||||
ready++;
|
||||
}
|
||||
}
|
||||
@@ -275,14 +275,19 @@ final class VisionRenderController implements AutoCloseable {
|
||||
if (!isCurrent(work)) {
|
||||
return;
|
||||
}
|
||||
int admissionLimit = work.frame().spec().type() == RenderType.RIVER ? 1 : renderWorkerCount;
|
||||
int admissionLimit = work.refining() || work.frame().spec().type() == RenderType.RIVER ? 1 : renderWorkerCount;
|
||||
while (work.inFlight() < admissionLimit) {
|
||||
VisibleTile tile = work.nextMissing();
|
||||
if (tile == null) {
|
||||
if (work.inFlight() == 0 && work.beginRefinement()) {
|
||||
admissionLimit = 1;
|
||||
continue;
|
||||
}
|
||||
return;
|
||||
}
|
||||
boolean refining = work.refining();
|
||||
work.incrementInFlight();
|
||||
TrackedRenderTask task = new TrackedRenderTask(() -> render(work, tile), activeRenderTasks);
|
||||
TrackedRenderTask task = new TrackedRenderTask(() -> render(work, tile, refining), activeRenderTasks);
|
||||
activeRenderTasks.add(task);
|
||||
try {
|
||||
renderExecutor.execute(task);
|
||||
@@ -295,7 +300,7 @@ final class VisionRenderController implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
private void render(WorkState work, VisibleTile tile) {
|
||||
private void render(WorkState work, VisibleTile tile, boolean refining) {
|
||||
Frame frame = work.frame();
|
||||
CancellationToken token = work.token();
|
||||
try {
|
||||
@@ -303,19 +308,26 @@ final class VisionRenderController implements AutoCloseable {
|
||||
return;
|
||||
}
|
||||
double tileSpan = TILE_PIXELS * frame.spec().blocksPerPixel();
|
||||
BufferedImage image = frame.spec().renderer().renderStudio(
|
||||
tile.tileX() * tileSpan,
|
||||
tile.tileZ() * tileSpan,
|
||||
tileSpan,
|
||||
TILE_PIXELS,
|
||||
frame.spec().type(),
|
||||
() -> !isCurrent(frame, token)
|
||||
);
|
||||
double startX = tile.tileX() * tileSpan;
|
||||
double startZ = tile.tileZ() * tileSpan;
|
||||
IrisRenderer renderer = frame.spec().renderer();
|
||||
BufferedImage image;
|
||||
if (refining) {
|
||||
image = renderer.refineStudioBiome(startX, startZ, tileSpan, tile.image(),
|
||||
() -> !isCurrent(frame, token));
|
||||
} else if (frame.spec().type() == RenderType.BIOME) {
|
||||
image = renderer.renderStudioBase(startX, startZ, tileSpan, TILE_PIXELS, frame.spec().type(),
|
||||
() -> !isCurrent(frame, token));
|
||||
} else {
|
||||
image = renderer.renderStudio(startX, startZ, tileSpan, TILE_PIXELS, frame.spec().type(),
|
||||
() -> !isCurrent(frame, token));
|
||||
}
|
||||
if (!isCurrent(frame, token)) {
|
||||
return;
|
||||
}
|
||||
cache.put(frame.key(tile), image);
|
||||
tile.setImage(image);
|
||||
TileResult result = new TileResult(image, refining || frame.spec().type() != RenderType.BIOME);
|
||||
cache.put(frame.key(tile), result);
|
||||
tile.setResult(result);
|
||||
publish(frame, token);
|
||||
} catch (CancellationException ignored) {
|
||||
} catch (Throwable error) {
|
||||
@@ -446,7 +458,7 @@ final class VisionRenderController implements AutoCloseable {
|
||||
private final int screenX;
|
||||
private final int screenY;
|
||||
private final double distanceSquared;
|
||||
private volatile BufferedImage image;
|
||||
private volatile TileResult result;
|
||||
|
||||
private VisibleTile(long tileX, long tileZ, int screenX, int screenY, double distanceSquared) {
|
||||
this.tileX = tileX;
|
||||
@@ -477,11 +489,17 @@ final class VisionRenderController implements AutoCloseable {
|
||||
}
|
||||
|
||||
BufferedImage image() {
|
||||
return image;
|
||||
TileResult current = result;
|
||||
return current == null ? null : current.image();
|
||||
}
|
||||
|
||||
void setImage(BufferedImage image) {
|
||||
this.image = image;
|
||||
boolean complete() {
|
||||
TileResult current = result;
|
||||
return current != null && current.complete();
|
||||
}
|
||||
|
||||
void setResult(TileResult result) {
|
||||
this.result = result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -548,6 +566,7 @@ final class VisionRenderController implements AutoCloseable {
|
||||
private final CancellationToken token;
|
||||
private int index;
|
||||
private int inFlight;
|
||||
private boolean refining;
|
||||
|
||||
private WorkState(Frame frame, CancellationToken token) {
|
||||
this.frame = frame;
|
||||
@@ -565,13 +584,26 @@ final class VisionRenderController implements AutoCloseable {
|
||||
VisibleTile nextMissing() {
|
||||
while (index < frame.tiles().size()) {
|
||||
VisibleTile tile = frame.tiles().get(index++);
|
||||
if (tile.image() == null) {
|
||||
if (refining ? tile.image() != null && !tile.complete() : tile.image() == null) {
|
||||
return tile;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean refining() {
|
||||
return refining;
|
||||
}
|
||||
|
||||
boolean beginRefinement() {
|
||||
if (refining || frame.spec().type() != RenderType.BIOME) {
|
||||
return false;
|
||||
}
|
||||
refining = true;
|
||||
index = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
int inFlight() {
|
||||
return inFlight;
|
||||
}
|
||||
@@ -595,14 +627,15 @@ final class VisionRenderController implements AutoCloseable {
|
||||
this.entries = new LinkedHashMap<>(128, 0.75F, true);
|
||||
}
|
||||
|
||||
synchronized BufferedImage get(TileKey key) {
|
||||
synchronized TileResult get(TileKey key) {
|
||||
CacheEntry entry = entries.get(key);
|
||||
return entry == null ? null : entry.image();
|
||||
return entry == null ? null : entry.result();
|
||||
}
|
||||
|
||||
synchronized void put(TileKey key, BufferedImage image) {
|
||||
synchronized void put(TileKey key, TileResult result) {
|
||||
BufferedImage image = result.image();
|
||||
long imageBytes = (long) image.getWidth() * image.getHeight() * Integer.BYTES;
|
||||
CacheEntry previous = entries.put(key, new CacheEntry(image, imageBytes));
|
||||
CacheEntry previous = entries.put(key, new CacheEntry(result, imageBytes));
|
||||
if (previous != null) {
|
||||
bytes -= previous.bytes();
|
||||
}
|
||||
@@ -621,6 +654,12 @@ final class VisionRenderController implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
private record CacheEntry(BufferedImage image, long bytes) {
|
||||
private record TileResult(BufferedImage image, boolean complete) {
|
||||
private TileResult {
|
||||
Objects.requireNonNull(image, "image");
|
||||
}
|
||||
}
|
||||
|
||||
private record CacheEntry(TileResult result, long bytes) {
|
||||
}
|
||||
}
|
||||
@@ -112,6 +112,24 @@ public final class SavedBiomeRuntime implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean mayHaveBlockDropRules(String material) {
|
||||
if (engine.getData().hasBlockDropRules(material)) {
|
||||
return true;
|
||||
}
|
||||
GenerationManifest manifest = history.manifest();
|
||||
String activeEpoch = manifest.activeEpoch().epochId();
|
||||
for (GenerationEpoch epoch : manifest.epochs()) {
|
||||
if (epoch.epochId().equals(activeEpoch)) {
|
||||
continue;
|
||||
}
|
||||
Definitions source = definitions.get(epoch.epochId());
|
||||
if (source == null || source.data().hasBlockDropRules(material)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Optional<BiomeEnvironment> resolveCaveBase(int blockX, int blockZ) {
|
||||
return resolve(blockX, 0, blockZ, QueryKind.CAVE_BASE);
|
||||
}
|
||||
@@ -449,6 +467,7 @@ public final class SavedBiomeRuntime implements AutoCloseable {
|
||||
}
|
||||
Definitions loaded = new Definitions(data, dimension, NativeBiomeSpawnSelection.retainedDerivatives(data),
|
||||
resolveFocusRegions(data, dimension));
|
||||
data.prepareBlockDropRules();
|
||||
definitions.put(epochId, loaded);
|
||||
return loaded;
|
||||
} catch (Throwable failure) {
|
||||
|
||||
Reference in new issue
Block a user