mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-08-27 12:41:43 +00:00
Remove Hydrology
This commit is contained in:
+2
-85
@@ -62,7 +62,6 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
private final ConcurrentHashMap<Long, Holder<Biome>> noiseBiomeCache = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<Long, Holder<Biome>> structureBiomeCache = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<Long, Holder<Biome>> surfaceStructureBiomeCache = new ConcurrentHashMap<>();
|
||||
private final ConcurrentHashMap<Long, Holder<Biome>> naturalSurfaceStructureBiomeCache = new ConcurrentHashMap<>();
|
||||
private volatile KMap<String, Holder<Biome>> customBiomes;
|
||||
private volatile Map<Biome, Holder<Biome>> vanillaSpawnBiomes;
|
||||
private volatile IrisDimension cacheDimension;
|
||||
@@ -332,18 +331,8 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
if (quartStep == 1) {
|
||||
return super.findBiomeHorizontal(x, y, z, searchRadius, allowed, random, sampler);
|
||||
}
|
||||
GenerationSessionLease lease = tryAcquireGenerationLease("bukkit_structure_ring_biome");
|
||||
if (lease == null) {
|
||||
throw new IllegalStateException("Iris structure ring biome lookup was rejected during an engine transition");
|
||||
}
|
||||
try (lease; IrisContext.Scope ignored = IrisContext.open(engine, lease.sessionId(), null)) {
|
||||
if (!isRuntimeAvailable()) {
|
||||
throw new IllegalStateException("Iris structure ring biome lookup has no active engine runtime");
|
||||
}
|
||||
ensureCachesCurrent();
|
||||
return findNaturalSurfaceBiomeHorizontal(
|
||||
x, y, z, searchRadius, quartStep, allowed, random);
|
||||
}
|
||||
return super.findBiomeHorizontal(
|
||||
x, y, z, searchRadius, quartStep, allowed, random, false, sampler);
|
||||
}
|
||||
|
||||
static int horizontalBiomeSearchQuartStep(int blockY, int searchRadius) {
|
||||
@@ -401,60 +390,6 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
return resolvedSurfaceHolder;
|
||||
}
|
||||
|
||||
private Pair<BlockPos, Holder<Biome>> findNaturalSurfaceBiomeHorizontal(
|
||||
int x,
|
||||
int y,
|
||||
int z,
|
||||
int searchRadius,
|
||||
int quartStep,
|
||||
Predicate<Holder<Biome>> allowed,
|
||||
RandomSource random
|
||||
) {
|
||||
int centerQuartX = QuartPos.fromBlock(x);
|
||||
int centerQuartZ = QuartPos.fromBlock(z);
|
||||
int quartRadius = QuartPos.fromBlock(searchRadius);
|
||||
Pair<BlockPos, Holder<Biome>> selected = null;
|
||||
int matches = 0;
|
||||
for (int radius = 0; radius <= quartRadius; radius += quartStep) {
|
||||
for (int offsetZ = -radius; offsetZ <= radius; offsetZ += quartStep) {
|
||||
for (int offsetX = -radius; offsetX <= radius; offsetX += quartStep) {
|
||||
int quartX = centerQuartX + offsetX;
|
||||
int quartZ = centerQuartZ + offsetZ;
|
||||
Holder<Biome> holder = getNaturalSurfaceStructureBiomeHolder(quartX, quartZ);
|
||||
if (!allowed.test(holder)) {
|
||||
continue;
|
||||
}
|
||||
if (selected == null || random.nextInt(matches + 1) == 0) {
|
||||
selected = Pair.of(new BlockPos(
|
||||
QuartPos.toBlock(quartX),
|
||||
y,
|
||||
QuartPos.toBlock(quartZ)), holder);
|
||||
}
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return selected;
|
||||
}
|
||||
|
||||
private Holder<Biome> getNaturalSurfaceStructureBiomeHolder(int x, int z) {
|
||||
long columnKey = packColumnKey(x, z);
|
||||
Holder<Biome> cachedHolder = naturalSurfaceStructureBiomeCache.get(columnKey);
|
||||
if (cachedHolder != null) {
|
||||
return cachedHolder;
|
||||
}
|
||||
Holder<Biome> resolvedHolder = resolveNaturalSurfaceStructureBiomeHolder(x, z);
|
||||
Holder<Biome> existingHolder = naturalSurfaceStructureBiomeCache.putIfAbsent(
|
||||
columnKey, resolvedHolder);
|
||||
if (existingHolder != null) {
|
||||
return existingHolder;
|
||||
}
|
||||
if (naturalSurfaceStructureBiomeCache.size() > NOISE_BIOME_CACHE_MAX) {
|
||||
naturalSurfaceStructureBiomeCache.clear();
|
||||
}
|
||||
return resolvedHolder;
|
||||
}
|
||||
|
||||
private boolean isGuaranteedSurfaceBiome(int quartY) {
|
||||
if (engine == null || engine.isClosed() || engine.getComplex() == null) {
|
||||
return false;
|
||||
@@ -482,23 +417,6 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
return holder;
|
||||
}
|
||||
|
||||
private Holder<Biome> resolveNaturalSurfaceStructureBiomeHolder(int x, int z) {
|
||||
int blockX = x << 2;
|
||||
int blockZ = z << 2;
|
||||
IrisBiome irisBiome = engine.getComplex().getNaturalTrueBiomeStream().get(blockX, blockZ);
|
||||
if (irisBiome == null) {
|
||||
throw new IllegalStateException("Iris returned no natural structure biome at block "
|
||||
+ blockX + "," + blockZ);
|
||||
}
|
||||
Holder<Biome> holder = resolveBiomeHolder(biomeRegistry, irisBiome.getStructureDerivativeKey());
|
||||
if (holder == null) {
|
||||
throw new IllegalStateException("Iris natural structure biome derivative '"
|
||||
+ irisBiome.getStructureDerivativeKey() + "' is not registered at block "
|
||||
+ blockX + "," + blockZ);
|
||||
}
|
||||
return holder;
|
||||
}
|
||||
|
||||
public Holder<Biome> getVisibleNoiseBiome(int x, int y, int z, Climate.Sampler sampler) {
|
||||
GenerationSessionLease lease = tryAcquireGenerationLease("bukkit_visible_biome");
|
||||
if (lease == null) {
|
||||
@@ -588,7 +506,6 @@ public class CustomBiomeSource extends BiomeSource {
|
||||
noiseBiomeCache.clear();
|
||||
structureBiomeCache.clear();
|
||||
surfaceStructureBiomeCache.clear();
|
||||
naturalSurfaceStructureBiomeCache.clear();
|
||||
customBiomes = refreshedCustomBiomes;
|
||||
vanillaSpawnBiomes = refreshedSpawnBiomes;
|
||||
cacheDimension = dimension;
|
||||
|
||||
+1
-28
@@ -45,36 +45,9 @@ public class CustomBiomeSourceStructureContractTest {
|
||||
assertTrue(source.contains("private static final int STRONGHOLD_RING_SEARCH_Y = 0"));
|
||||
assertTrue(source.contains("private static final int STRONGHOLD_RING_SEARCH_RADIUS = 112"));
|
||||
assertTrue(source.contains("private static final int STRONGHOLD_RING_SEARCH_QUART_STEP = 4"));
|
||||
assertTrue(source.contains("x, y, z, searchRadius, quartStep, allowed, random, false, sampler"));
|
||||
assertTrue(source.contains(
|
||||
"return super.findBiomeHorizontal(x, y, z, searchRadius, allowed, random, sampler)"));
|
||||
assertTrue(source.contains("tryAcquireGenerationLease(\"bukkit_structure_ring_biome\")"));
|
||||
assertTrue(source.contains("findNaturalSurfaceBiomeHorizontal("));
|
||||
assertTrue(source.contains("radius += quartStep"));
|
||||
assertTrue(source.contains("offsetZ += quartStep"));
|
||||
assertTrue(source.contains("offsetX += quartStep"));
|
||||
assertTrue(source.contains("random.nextInt(matches + 1) == 0"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onlyConcentricRingSuitabilityUsesTheNaturalTerrainStream() throws IOException {
|
||||
String source = Files.readString(Path.of(System.getProperty("iris.customBiomeSource")));
|
||||
int realResolutionStart = source.indexOf("private Holder<Biome> resolveSurfaceStructureBiomeHolder(");
|
||||
int naturalResolutionStart = source.indexOf(
|
||||
"private Holder<Biome> resolveNaturalSurfaceStructureBiomeHolder(");
|
||||
int resolutionEnd = source.indexOf("public Holder<Biome> getVisibleNoiseBiome(", naturalResolutionStart);
|
||||
|
||||
assertTrue(realResolutionStart >= 0);
|
||||
assertTrue(naturalResolutionStart > realResolutionStart);
|
||||
assertTrue(resolutionEnd > naturalResolutionStart);
|
||||
String realResolution = source.substring(realResolutionStart, naturalResolutionStart);
|
||||
String naturalResolution = source.substring(naturalResolutionStart, resolutionEnd);
|
||||
|
||||
assertTrue(realResolution.contains("engine.getComplex().getTrueBiomeStream().get(blockX, blockZ)"));
|
||||
assertFalse(realResolution.contains("getNaturalTrueBiomeStream()"));
|
||||
assertTrue(naturalResolution.contains(
|
||||
"engine.getComplex().getNaturalTrueBiomeStream().get(blockX, blockZ)"));
|
||||
assertFalse(naturalResolution.contains("getTrueBiomeStream()"));
|
||||
assertFalse(source.contains("studioBootstrapSurfaceStructureBiomeCache"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+1
-6
@@ -2,11 +2,6 @@ package art.arcane.iris.api.terrain;
|
||||
|
||||
public enum IrisColumnField {
|
||||
SURFACE_HEIGHT,
|
||||
NATURAL_HEIGHT,
|
||||
SURFACE_KIND,
|
||||
BIOME_KEY,
|
||||
RIVER_STATE,
|
||||
RIVER_DISTANCE,
|
||||
RIVER_FLOW,
|
||||
RIVER_WATER_SURFACE_Y
|
||||
BIOME_KEY
|
||||
}
|
||||
|
||||
-64
@@ -1,64 +0,0 @@
|
||||
package art.arcane.iris.api.terrain;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record IrisColumnSample(
|
||||
int blockX,
|
||||
int blockZ,
|
||||
int surfaceHeight,
|
||||
int naturalHeight,
|
||||
IrisSurfaceKind surfaceKind,
|
||||
String biomeKey,
|
||||
IrisRiverState riverState,
|
||||
double riverDistance,
|
||||
int riverFlow,
|
||||
int riverWaterSurfaceY
|
||||
) {
|
||||
public static final int UNAVAILABLE_HEIGHT = Integer.MIN_VALUE;
|
||||
public static final double UNAVAILABLE_RIVER_DISTANCE = Double.NaN;
|
||||
public static final int UNAVAILABLE_RIVER_FLOW = -1;
|
||||
|
||||
public IrisColumnSample {
|
||||
Objects.requireNonNull(surfaceKind, "surfaceKind");
|
||||
Objects.requireNonNull(riverState, "riverState");
|
||||
biomeKey = biomeKey == null || biomeKey.isBlank() ? null : biomeKey;
|
||||
if (!Double.isNaN(riverDistance) && (!Double.isFinite(riverDistance) || riverDistance < 0D)) {
|
||||
throw new IllegalArgumentException("riverDistance must be non-negative, finite, or unavailable");
|
||||
}
|
||||
if (riverFlow < UNAVAILABLE_RIVER_FLOW) {
|
||||
throw new IllegalArgumentException("riverFlow must be non-negative or unavailable");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasSurfaceHeight() {
|
||||
return surfaceHeight != UNAVAILABLE_HEIGHT;
|
||||
}
|
||||
|
||||
public boolean hasNaturalHeight() {
|
||||
return naturalHeight != UNAVAILABLE_HEIGHT;
|
||||
}
|
||||
|
||||
public boolean hasSurfaceKind() {
|
||||
return surfaceKind != IrisSurfaceKind.UNKNOWN;
|
||||
}
|
||||
|
||||
public boolean hasBiomeKey() {
|
||||
return biomeKey != null;
|
||||
}
|
||||
|
||||
public boolean hasRiverState() {
|
||||
return riverState != IrisRiverState.NONE;
|
||||
}
|
||||
|
||||
public boolean hasRiverDistance() {
|
||||
return !Double.isNaN(riverDistance);
|
||||
}
|
||||
|
||||
public boolean hasRiverFlow() {
|
||||
return riverFlow != UNAVAILABLE_RIVER_FLOW;
|
||||
}
|
||||
|
||||
public boolean hasRiverWaterSurfaceY() {
|
||||
return riverWaterSurfaceY != UNAVAILABLE_HEIGHT;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -2,5 +2,5 @@ package art.arcane.iris.api.terrain;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface IrisColumnSink {
|
||||
void accept(IrisColumnSample sample);
|
||||
void accept(int blockX, int blockZ, int surfaceHeight, IrisSurfaceKind kind, String biomeKey);
|
||||
}
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
package art.arcane.iris.api.terrain;
|
||||
|
||||
public enum IrisRiverState {
|
||||
NONE,
|
||||
WET,
|
||||
DRY
|
||||
}
|
||||
@@ -4,9 +4,6 @@ public enum IrisSurfaceKind {
|
||||
UNKNOWN,
|
||||
LAND,
|
||||
SHORE,
|
||||
RIVER,
|
||||
RIVER_SHORE,
|
||||
DRY_CHANNEL,
|
||||
OCEAN,
|
||||
VOID
|
||||
}
|
||||
|
||||
+1
-1
@@ -318,7 +318,7 @@ public final class BukkitVisionOverlay implements GuiOverlay {
|
||||
public String openInEditor(double worldX, double worldZ, RenderType type) {
|
||||
IrisComplex complex = engine.getComplex();
|
||||
File file = switch (type) {
|
||||
case BIOME, LAYER_LOAD, DECORATOR_LOAD, OBJECT_LOAD, HEIGHT, RIVER ->
|
||||
case BIOME, LAYER_LOAD, DECORATOR_LOAD, OBJECT_LOAD, HEIGHT ->
|
||||
complex.getTrueBiomeStream().get(worldX, worldZ).openInVSCode();
|
||||
case BIOME_LAND -> complex.getLandBiomeStream().get(worldX, worldZ).openInVSCode();
|
||||
case BIOME_SEA -> complex.getSeaBiomeStream().get(worldX, worldZ).openInVSCode();
|
||||
|
||||
+5
-67
@@ -2,9 +2,7 @@ package art.arcane.iris.core.service;
|
||||
|
||||
import art.arcane.iris.api.terrain.IrisColumnField;
|
||||
import art.arcane.iris.api.terrain.IrisColumnQuery;
|
||||
import art.arcane.iris.api.terrain.IrisColumnSample;
|
||||
import art.arcane.iris.api.terrain.IrisColumnSink;
|
||||
import art.arcane.iris.api.terrain.IrisRiverState;
|
||||
import art.arcane.iris.api.terrain.IrisSurfaceKind;
|
||||
import art.arcane.iris.api.terrain.IrisTerrainService;
|
||||
import art.arcane.iris.api.terrain.IrisWorldInfo;
|
||||
@@ -19,8 +17,6 @@ import art.arcane.iris.engine.object.InferredType;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||
import art.arcane.iris.engine.river.RiverRouteState;
|
||||
import art.arcane.iris.engine.river.runtime.IrisRiverSurfaceSample;
|
||||
import art.arcane.iris.platform.bukkit.BukkitPlatform;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.spi.IrisServices;
|
||||
@@ -105,14 +101,13 @@ public class IrisTerrainSVC implements IrisService, IrisTerrainService {
|
||||
|
||||
try {
|
||||
int surface = engine.getHeight(blockX, blockZ);
|
||||
IrisRiverSurfaceSample riverSurface = engine.getComplex().getRiverSurfaceStream().get(blockX, blockZ);
|
||||
int fluid = (int) Math.round(riverSurface.waterSurfaceY());
|
||||
int fluid = engine.getDimension().getFluidHeight();
|
||||
InferredType inferredType = null;
|
||||
if (IrisSurfaceClassifier.requiresSurfaceBiome(surface, fluid)) {
|
||||
IrisBiome biome = engine.getSurfaceBiome(blockX, blockZ);
|
||||
inferredType = biome == null ? null : biome.getInferredType();
|
||||
}
|
||||
return IrisSurfaceClassifier.classify(surface, fluid, inferredType, riverSurface);
|
||||
return IrisSurfaceClassifier.classify(surface, fluid, inferredType);
|
||||
} catch (Throwable error) {
|
||||
reportQueryFault("surfaceKind", world, error);
|
||||
return IrisSurfaceKind.UNKNOWN;
|
||||
@@ -229,72 +224,26 @@ public class IrisTerrainSVC implements IrisService, IrisTerrainService {
|
||||
|
||||
EnumSet<IrisColumnField> fields = query.fields();
|
||||
boolean wantHeight = fields.contains(IrisColumnField.SURFACE_HEIGHT);
|
||||
boolean wantNaturalHeight = fields.contains(IrisColumnField.NATURAL_HEIGHT);
|
||||
boolean wantKind = fields.contains(IrisColumnField.SURFACE_KIND);
|
||||
boolean wantBiome = fields.contains(IrisColumnField.BIOME_KEY);
|
||||
boolean wantRiverState = fields.contains(IrisColumnField.RIVER_STATE);
|
||||
boolean wantRiverDistance = fields.contains(IrisColumnField.RIVER_DISTANCE);
|
||||
boolean wantRiverFlow = fields.contains(IrisColumnField.RIVER_FLOW);
|
||||
boolean wantRiverWaterSurface = fields.contains(IrisColumnField.RIVER_WATER_SURFACE_Y);
|
||||
boolean wantRiver = wantKind || wantRiverState || wantRiverDistance || wantRiverFlow
|
||||
|| wantRiverWaterSurface;
|
||||
|
||||
try {
|
||||
int minHeight = engine.getMinHeight();
|
||||
int fluid = engine.getDimension().getFluidHeight();
|
||||
long visited = IrisColumnWalk.walk(query, (int blockX, int blockZ) -> {
|
||||
if (engine.isClosed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
IrisRiverSurfaceSample riverSurface = wantRiver
|
||||
? engine.getComplex().getRiverSurfaceStream().get(blockX, blockZ)
|
||||
: null;
|
||||
int surface = wantHeight || wantKind ? engine.getHeight(blockX, blockZ) : 0;
|
||||
int fluid = riverSurface == null
|
||||
? engine.getDimension().getFluidHeight()
|
||||
: (int) Math.round(riverSurface.waterSurfaceY());
|
||||
boolean needsBiome = wantBiome
|
||||
|| (wantKind && IrisSurfaceClassifier.requiresSurfaceBiome(surface, fluid));
|
||||
IrisBiome biome = needsBiome ? engine.getSurfaceBiome(blockX, blockZ) : null;
|
||||
IrisSurfaceKind kind = wantKind
|
||||
? IrisSurfaceClassifier.classify(
|
||||
surface,
|
||||
fluid,
|
||||
biome == null ? null : biome.getInferredType(),
|
||||
riverSurface
|
||||
)
|
||||
? IrisSurfaceClassifier.classify(surface, fluid, biome == null ? null : biome.getInferredType())
|
||||
: IrisSurfaceKind.UNKNOWN;
|
||||
String biomeKey = wantBiome && biome != null ? biome.getLoadKey() : null;
|
||||
int natural = wantNaturalHeight
|
||||
? (int) Math.round(engine.getComplex().getNaturalHeightStream().get(blockX, blockZ)) + minHeight
|
||||
: IrisColumnSample.UNAVAILABLE_HEIGHT;
|
||||
boolean riverPresent = riverSurface != null && riverSurface.river().present();
|
||||
IrisRiverState riverState = wantRiverState && riverSurface != null
|
||||
? riverState(riverSurface)
|
||||
: IrisRiverState.NONE;
|
||||
double riverDistance = wantRiverDistance && riverPresent
|
||||
? riverSurface.river().distance()
|
||||
: IrisColumnSample.UNAVAILABLE_RIVER_DISTANCE;
|
||||
int riverFlow = wantRiverFlow && riverPresent
|
||||
? riverSurface.river().flow()
|
||||
: IrisColumnSample.UNAVAILABLE_RIVER_FLOW;
|
||||
int riverWaterSurfaceY = wantRiverWaterSurface
|
||||
&& riverPresent
|
||||
&& riverSurface.river().state() == RiverRouteState.WET
|
||||
? fluid + minHeight
|
||||
: IrisColumnSample.UNAVAILABLE_HEIGHT;
|
||||
sink.accept(new IrisColumnSample(
|
||||
blockX,
|
||||
blockZ,
|
||||
wantHeight ? surface + minHeight : IrisColumnSample.UNAVAILABLE_HEIGHT,
|
||||
natural,
|
||||
kind,
|
||||
biomeKey,
|
||||
riverState,
|
||||
riverDistance,
|
||||
riverFlow,
|
||||
riverWaterSurfaceY
|
||||
));
|
||||
sink.accept(blockX, blockZ, wantHeight ? surface + minHeight : -1, kind, biomeKey);
|
||||
return true;
|
||||
});
|
||||
return visited == query.columnCount();
|
||||
@@ -304,17 +253,6 @@ public class IrisTerrainSVC implements IrisService, IrisTerrainService {
|
||||
}
|
||||
}
|
||||
|
||||
static IrisRiverState riverState(IrisRiverSurfaceSample surface) {
|
||||
if (!surface.river().present()) {
|
||||
return IrisRiverState.NONE;
|
||||
}
|
||||
return switch (surface.river().state()) {
|
||||
case WET -> IrisRiverState.WET;
|
||||
case DRY -> IrisRiverState.DRY;
|
||||
case SUPPRESSED -> IrisRiverState.NONE;
|
||||
};
|
||||
}
|
||||
|
||||
private static Optional<String> key(IrisBiome biome) {
|
||||
String loadKey = biome == null ? null : biome.getLoadKey();
|
||||
return loadKey == null || loadKey.isEmpty() ? Optional.empty() : Optional.of(loadKey);
|
||||
|
||||
-31
@@ -2,9 +2,6 @@ package art.arcane.iris.core.service.terrain;
|
||||
|
||||
import art.arcane.iris.api.terrain.IrisSurfaceKind;
|
||||
import art.arcane.iris.engine.object.InferredType;
|
||||
import art.arcane.iris.engine.river.RiverRouteState;
|
||||
import art.arcane.iris.engine.river.RiverSection;
|
||||
import art.arcane.iris.engine.river.runtime.IrisRiverSurfaceSample;
|
||||
|
||||
public final class IrisSurfaceClassifier {
|
||||
private IrisSurfaceClassifier() {
|
||||
@@ -25,32 +22,4 @@ public final class IrisSurfaceClassifier {
|
||||
|
||||
return inferredType == InferredType.SHORE ? IrisSurfaceKind.SHORE : IrisSurfaceKind.LAND;
|
||||
}
|
||||
|
||||
public static IrisSurfaceKind classify(
|
||||
int engineSurfaceHeight,
|
||||
int engineFluidHeight,
|
||||
InferredType inferredType,
|
||||
IrisRiverSurfaceSample riverSurface
|
||||
) {
|
||||
if (engineSurfaceHeight <= 0) {
|
||||
return IrisSurfaceKind.VOID;
|
||||
}
|
||||
if (riverSurface != null && riverSurface.river().present() && !riverSurface.subterranean()) {
|
||||
if (riverSurface.river().state() == RiverRouteState.DRY) {
|
||||
return riverSurface.river().section() == RiverSection.DRY_CHANNEL
|
||||
? IrisSurfaceKind.DRY_CHANNEL
|
||||
: IrisSurfaceKind.LAND;
|
||||
}
|
||||
if (riverSurface.river().state() == RiverRouteState.WET) {
|
||||
RiverSection section = riverSurface.river().section();
|
||||
if (section == RiverSection.BANK) {
|
||||
return IrisSurfaceKind.RIVER_SHORE;
|
||||
}
|
||||
if (section == RiverSection.CHANNEL || section == RiverSection.MOUTH) {
|
||||
return IrisSurfaceKind.RIVER;
|
||||
}
|
||||
}
|
||||
}
|
||||
return classify(engineSurfaceHeight, engineFluidHeight, inferredType);
|
||||
}
|
||||
}
|
||||
|
||||
-145
@@ -1,145 +0,0 @@
|
||||
package art.arcane.iris.api.terrain;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class IrisColumnSampleTest {
|
||||
@Test
|
||||
public void unavailableFieldsHaveUnambiguousSentinels() {
|
||||
IrisColumnSample sample = sample(
|
||||
IrisColumnSample.UNAVAILABLE_HEIGHT,
|
||||
IrisColumnSample.UNAVAILABLE_HEIGHT,
|
||||
IrisSurfaceKind.UNKNOWN,
|
||||
null,
|
||||
IrisRiverState.NONE,
|
||||
IrisColumnSample.UNAVAILABLE_RIVER_DISTANCE,
|
||||
IrisColumnSample.UNAVAILABLE_RIVER_FLOW,
|
||||
IrisColumnSample.UNAVAILABLE_HEIGHT
|
||||
);
|
||||
|
||||
assertFalse(sample.hasSurfaceHeight());
|
||||
assertFalse(sample.hasNaturalHeight());
|
||||
assertFalse(sample.hasSurfaceKind());
|
||||
assertFalse(sample.hasBiomeKey());
|
||||
assertFalse(sample.hasRiverState());
|
||||
assertFalse(sample.hasRiverDistance());
|
||||
assertFalse(sample.hasRiverFlow());
|
||||
assertFalse(sample.hasRiverWaterSurfaceY());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void negativeWorldHeightsRemainAvailableValues() {
|
||||
IrisColumnSample sample = sample(
|
||||
-1,
|
||||
-64,
|
||||
IrisSurfaceKind.DRY_CHANNEL,
|
||||
"test:river",
|
||||
IrisRiverState.DRY,
|
||||
0D,
|
||||
0,
|
||||
-1
|
||||
);
|
||||
|
||||
assertTrue(sample.hasSurfaceHeight());
|
||||
assertTrue(sample.hasNaturalHeight());
|
||||
assertTrue(sample.hasSurfaceKind());
|
||||
assertTrue(sample.hasBiomeKey());
|
||||
assertTrue(sample.hasRiverState());
|
||||
assertTrue(sample.hasRiverDistance());
|
||||
assertTrue(sample.hasRiverFlow());
|
||||
assertTrue(sample.hasRiverWaterSurfaceY());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void blankBiomeKeysNormalizeToUnavailable() {
|
||||
IrisColumnSample sample = sample(
|
||||
64,
|
||||
65,
|
||||
IrisSurfaceKind.LAND,
|
||||
" ",
|
||||
IrisRiverState.NONE,
|
||||
IrisColumnSample.UNAVAILABLE_RIVER_DISTANCE,
|
||||
IrisColumnSample.UNAVAILABLE_RIVER_FLOW,
|
||||
IrisColumnSample.UNAVAILABLE_HEIGHT
|
||||
);
|
||||
|
||||
assertNull(sample.biomeKey());
|
||||
assertFalse(sample.hasBiomeKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void theSinkReceivesTheTypedSample() {
|
||||
IrisColumnSample sample = sample(
|
||||
64,
|
||||
65,
|
||||
IrisSurfaceKind.RIVER,
|
||||
"test:river",
|
||||
IrisRiverState.WET,
|
||||
0.5D,
|
||||
3,
|
||||
67
|
||||
);
|
||||
AtomicReference<IrisColumnSample> received = new AtomicReference<>();
|
||||
IrisColumnSink sink = received::set;
|
||||
|
||||
sink.accept(sample);
|
||||
|
||||
assertSame(sample, received.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidHydrologyValuesAreRejected() {
|
||||
assertInvalid(Double.POSITIVE_INFINITY, 1);
|
||||
assertInvalid(-0.1D, 1);
|
||||
assertInvalid(0D, -2);
|
||||
}
|
||||
|
||||
private static void assertInvalid(double distance, int flow) {
|
||||
try {
|
||||
sample(
|
||||
64,
|
||||
65,
|
||||
IrisSurfaceKind.RIVER,
|
||||
"test:river",
|
||||
IrisRiverState.WET,
|
||||
distance,
|
||||
flow,
|
||||
67
|
||||
);
|
||||
fail("Expected invalid hydrology values to be rejected");
|
||||
} catch (IllegalArgumentException expected) {
|
||||
assertTrue(expected.getMessage().startsWith("river"));
|
||||
}
|
||||
}
|
||||
|
||||
private static IrisColumnSample sample(
|
||||
int surfaceHeight,
|
||||
int naturalHeight,
|
||||
IrisSurfaceKind surfaceKind,
|
||||
String biomeKey,
|
||||
IrisRiverState riverState,
|
||||
double riverDistance,
|
||||
int riverFlow,
|
||||
int riverWaterSurfaceY
|
||||
) {
|
||||
return new IrisColumnSample(
|
||||
12,
|
||||
-7,
|
||||
surfaceHeight,
|
||||
naturalHeight,
|
||||
surfaceKind,
|
||||
biomeKey,
|
||||
riverState,
|
||||
riverDistance,
|
||||
riverFlow,
|
||||
riverWaterSurfaceY
|
||||
);
|
||||
}
|
||||
}
|
||||
+1
-12
@@ -1,10 +1,8 @@
|
||||
package art.arcane.iris.core.commands;
|
||||
|
||||
import art.arcane.iris.engine.IrisComplex;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IObjectPlacer;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
@@ -15,31 +13,22 @@ import java.util.Map;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class CommandObjectFluidHeightTest {
|
||||
@Test
|
||||
public void absoluteObjectPlacementShiftsTheColumnRiverHeadFromNegativeMinY() {
|
||||
public void absoluteObjectPlacementShiftsFluidHeightFromNegativeMinY() {
|
||||
World world = mock(World.class);
|
||||
Engine engine = mock(Engine.class);
|
||||
IrisDimension dimension = mock(IrisDimension.class);
|
||||
IrisComplex complex = mock(IrisComplex.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
ProceduralStream<Double> riverHead = mock(ProceduralStream.class);
|
||||
Map<Block, BlockData> future = new HashMap<>();
|
||||
|
||||
when(engine.getMinHeight()).thenReturn(-64);
|
||||
when(engine.getDimension()).thenReturn(dimension);
|
||||
when(engine.getComplex()).thenReturn(complex);
|
||||
when(dimension.getFluidHeight()).thenReturn(127);
|
||||
when(complex.getRiverWaterSurfaceStream()).thenReturn(riverHead);
|
||||
when(riverHead.get(12, -7)).thenReturn(131D);
|
||||
|
||||
IObjectPlacer placer = CommandObject.createPlacer(world, future, engine);
|
||||
|
||||
assertEquals(63, placer.getFluidHeight());
|
||||
assertEquals(67, placer.getFluidHeight(12, -7));
|
||||
verify(riverHead).get(12, -7);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-42
@@ -2,15 +2,8 @@ package art.arcane.iris.core.service;
|
||||
|
||||
import art.arcane.iris.api.terrain.IrisColumnField;
|
||||
import art.arcane.iris.api.terrain.IrisColumnQuery;
|
||||
import art.arcane.iris.api.terrain.IrisRiverState;
|
||||
import art.arcane.iris.api.terrain.IrisSurfaceKind;
|
||||
import art.arcane.iris.api.terrain.IrisTerrainService;
|
||||
import art.arcane.iris.engine.river.RiverEdgeId;
|
||||
import art.arcane.iris.engine.river.RiverNodeId;
|
||||
import art.arcane.iris.engine.river.RiverRouteState;
|
||||
import art.arcane.iris.engine.river.RiverSample;
|
||||
import art.arcane.iris.engine.river.RiverSection;
|
||||
import art.arcane.iris.engine.river.runtime.IrisRiverSurfaceSample;
|
||||
import art.arcane.iris.util.common.plugin.IrisService;
|
||||
import org.bukkit.World;
|
||||
import org.junit.Test;
|
||||
@@ -89,7 +82,9 @@ public class IrisTerrainSVCTest {
|
||||
IrisTerrainSVC service = new IrisTerrainSVC();
|
||||
AtomicInteger sinkCalls = new AtomicInteger();
|
||||
|
||||
boolean answered = service.sampleColumns(null, SMALL, sample -> sinkCalls.incrementAndGet());
|
||||
boolean answered = service.sampleColumns(null, SMALL,
|
||||
(int blockX, int blockZ, int surfaceHeight, IrisSurfaceKind kind, String biomeKey)
|
||||
-> sinkCalls.incrementAndGet());
|
||||
|
||||
assertFalse(answered);
|
||||
assertEquals(0, sinkCalls.get());
|
||||
@@ -102,38 +97,4 @@ public class IrisTerrainSVCTest {
|
||||
assertFalse(service.sampleColumns(null, null, null));
|
||||
assertFalse(service.sampleColumns(null, SMALL, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void riverRouteStatesMapToThePublicDiagnosticStates() {
|
||||
assertEquals(IrisRiverState.NONE, IrisTerrainSVC.riverState(
|
||||
new IrisRiverSurfaceSample(RiverSample.none(), 70D, 70D, 70D, false, false)
|
||||
));
|
||||
assertEquals(IrisRiverState.WET, IrisTerrainSVC.riverState(river(RiverRouteState.WET)));
|
||||
assertEquals(IrisRiverState.DRY, IrisTerrainSVC.riverState(river(RiverRouteState.DRY)));
|
||||
assertEquals(IrisRiverState.NONE, IrisTerrainSVC.riverState(river(RiverRouteState.SUPPRESSED)));
|
||||
}
|
||||
|
||||
private static IrisRiverSurfaceSample river(RiverRouteState state) {
|
||||
RiverSection section = switch (state) {
|
||||
case WET -> RiverSection.CHANNEL;
|
||||
case DRY -> RiverSection.DRY_CHANNEL;
|
||||
case SUPPRESSED -> RiverSection.NONE;
|
||||
};
|
||||
RiverSample sample = new RiverSample(
|
||||
true,
|
||||
state,
|
||||
section,
|
||||
0D,
|
||||
0.5D,
|
||||
1D,
|
||||
1,
|
||||
1,
|
||||
8D,
|
||||
4D,
|
||||
3D,
|
||||
false,
|
||||
RiverEdgeId.of(new RiverNodeId(0, 0), new RiverNodeId(1, 0))
|
||||
);
|
||||
return new IrisRiverSurfaceSample(sample, 70D, 60D, 63D, false, state == RiverRouteState.WET);
|
||||
}
|
||||
}
|
||||
|
||||
-88
@@ -2,12 +2,6 @@ package art.arcane.iris.core.service.terrain;
|
||||
|
||||
import art.arcane.iris.api.terrain.IrisSurfaceKind;
|
||||
import art.arcane.iris.engine.object.InferredType;
|
||||
import art.arcane.iris.engine.river.RiverEdgeId;
|
||||
import art.arcane.iris.engine.river.RiverNodeId;
|
||||
import art.arcane.iris.engine.river.RiverRouteState;
|
||||
import art.arcane.iris.engine.river.RiverSample;
|
||||
import art.arcane.iris.engine.river.RiverSection;
|
||||
import art.arcane.iris.engine.river.runtime.IrisRiverSurfaceSample;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -64,86 +58,4 @@ public class IrisSurfaceClassifierTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void activeRiverGeometryOverridesGenericOceanAndLandKinds() {
|
||||
assertEquals(IrisSurfaceKind.RIVER, IrisSurfaceClassifier.classify(
|
||||
60,
|
||||
63,
|
||||
InferredType.SEA,
|
||||
river(RiverRouteState.WET, RiverSection.CHANNEL)
|
||||
));
|
||||
assertEquals(IrisSurfaceKind.RIVER, IrisSurfaceClassifier.classify(
|
||||
60,
|
||||
63,
|
||||
InferredType.SEA,
|
||||
river(RiverRouteState.WET, RiverSection.MOUTH)
|
||||
));
|
||||
assertEquals(IrisSurfaceKind.RIVER_SHORE, IrisSurfaceClassifier.classify(
|
||||
64,
|
||||
63,
|
||||
InferredType.SHORE,
|
||||
river(RiverRouteState.WET, RiverSection.BANK)
|
||||
));
|
||||
assertEquals(IrisSurfaceKind.DRY_CHANNEL, IrisSurfaceClassifier.classify(
|
||||
60,
|
||||
60,
|
||||
InferredType.LAND,
|
||||
river(RiverRouteState.DRY, RiverSection.DRY_CHANNEL)
|
||||
));
|
||||
assertEquals(IrisSurfaceKind.LAND, IrisSurfaceClassifier.classify(
|
||||
60,
|
||||
60,
|
||||
InferredType.LAND,
|
||||
river(RiverRouteState.DRY, RiverSection.DRY_BANK)
|
||||
));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void voidClassificationWinsOverRiverGeometry() {
|
||||
for (RiverSection section : RiverSection.values()) {
|
||||
if (section == RiverSection.NONE) {
|
||||
continue;
|
||||
}
|
||||
RiverRouteState state = section == RiverSection.DRY_CHANNEL || section == RiverSection.DRY_BANK
|
||||
? RiverRouteState.DRY
|
||||
: RiverRouteState.WET;
|
||||
assertEquals(IrisSurfaceKind.VOID, IrisSurfaceClassifier.classify(
|
||||
0,
|
||||
63,
|
||||
InferredType.LAND,
|
||||
river(state, section)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void suppressedRoutesDoNotCreatePublicRiverSurfaceKinds() {
|
||||
assertEquals(IrisSurfaceKind.LAND, IrisSurfaceClassifier.classify(
|
||||
64,
|
||||
63,
|
||||
InferredType.LAND,
|
||||
river(RiverRouteState.SUPPRESSED, RiverSection.CHANNEL)
|
||||
));
|
||||
}
|
||||
|
||||
private static IrisRiverSurfaceSample river(RiverRouteState state, RiverSection section) {
|
||||
RiverSample sample = new RiverSample(
|
||||
true,
|
||||
state,
|
||||
section,
|
||||
0D,
|
||||
0.5D,
|
||||
1D,
|
||||
1,
|
||||
1,
|
||||
8D,
|
||||
4D,
|
||||
3D,
|
||||
false,
|
||||
RiverEdgeId.of(new RiverNodeId(0, 0), new RiverNodeId(1, 0))
|
||||
);
|
||||
double waterSurface = state == RiverRouteState.WET ? 63D : 60D;
|
||||
return new IrisRiverSurfaceSample(sample, 70D, 60D, waterSurface, false, state == RiverRouteState.WET);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -126,7 +126,7 @@ public final class ModdedVisionOverlay implements GuiOverlay {
|
||||
}
|
||||
IrisComplex complex = engine.getComplex();
|
||||
File file = switch (type) {
|
||||
case BIOME, LAYER_LOAD, DECORATOR_LOAD, OBJECT_LOAD, HEIGHT, RIVER ->
|
||||
case BIOME, LAYER_LOAD, DECORATOR_LOAD, OBJECT_LOAD, HEIGHT ->
|
||||
complex.getTrueBiomeStream().get(worldX, worldZ).openInVSCode();
|
||||
case BIOME_LAND -> complex.getLandBiomeStream().get(worldX, worldZ).openInVSCode();
|
||||
case BIOME_SEA -> complex.getSeaBiomeStream().get(worldX, worldZ).openInVSCode();
|
||||
|
||||
@@ -26,7 +26,6 @@ import art.arcane.iris.engine.framework.render.RenderType;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.engine.river.RiverSection;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.volmlib.util.format.Form;
|
||||
import art.arcane.volmlib.util.localization.MessageArgument;
|
||||
@@ -70,7 +69,6 @@ import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
@@ -475,33 +473,11 @@ public final class VisionGUI extends JPanel implements MouseWheelListener, KeyLi
|
||||
}
|
||||
|
||||
private void renderLegend(Graphics2D canvas) {
|
||||
if (currentType == RenderType.RIVER) {
|
||||
renderRiverLegend(canvas);
|
||||
} else if (currentType == RenderType.HEIGHT) {
|
||||
if (currentType == RenderType.HEIGHT) {
|
||||
renderHeightLegend(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
private void renderRiverLegend(Graphics2D canvas) {
|
||||
RiverSection[] sections = RiverSection.values();
|
||||
int lineHeight = 18;
|
||||
int width = 148;
|
||||
int height = sections.length * lineHeight + CARD_PADDING * 2;
|
||||
int x = getWidth() - width - CARD_PADDING;
|
||||
int y = getHeight() - STATUS_HEIGHT - PROGRESS_HEIGHT - height - CARD_PADDING;
|
||||
drawCardBackground(canvas, x, y, width, height);
|
||||
canvas.setFont(BODY_FONT);
|
||||
for (int index = 0; index < sections.length; index++) {
|
||||
RiverSection section = sections[index];
|
||||
int rowY = y + CARD_PADDING + index * lineHeight;
|
||||
canvas.setColor(new Color(IrisRenderer.riverColor(section)));
|
||||
canvas.fillRoundRect(x + CARD_PADDING, rowY + 2, 12, 12, 4, 4);
|
||||
canvas.setColor(TEXT_SECONDARY);
|
||||
String label = section.name().toLowerCase(Locale.ROOT).replace('_', ' ');
|
||||
canvas.drawString(label, x + CARD_PADDING + 20, rowY + 13);
|
||||
}
|
||||
}
|
||||
|
||||
private void renderHeightLegend(Graphics2D canvas) {
|
||||
int width = 244;
|
||||
int height = 54;
|
||||
@@ -1027,7 +1003,6 @@ public final class VisionGUI extends JPanel implements MouseWheelListener, KeyLi
|
||||
case BIOME_SEA -> DesktopUiMessages.VISION_MODE_BIOME_SEA;
|
||||
case REGION -> DesktopUiMessages.VISION_MODE_REGION;
|
||||
case CAVE_LAND -> DesktopUiMessages.VISION_MODE_CAVE_LAND;
|
||||
case RIVER -> DesktopUiMessages.VISION_MODE_RIVER;
|
||||
case HEIGHT -> DesktopUiMessages.VISION_MODE_HEIGHT;
|
||||
case OBJECT_LOAD -> DesktopUiMessages.VISION_MODE_OBJECT_LOAD;
|
||||
case DECORATOR_LOAD -> DesktopUiMessages.VISION_MODE_DECORATOR_LOAD;
|
||||
|
||||
@@ -275,8 +275,7 @@ final class VisionRenderController implements AutoCloseable {
|
||||
if (!isCurrent(work)) {
|
||||
return;
|
||||
}
|
||||
int admissionLimit = work.frame().spec().type() == RenderType.RIVER ? 1 : renderWorkerCount;
|
||||
while (work.inFlight() < admissionLimit) {
|
||||
while (work.inFlight() < renderWorkerCount) {
|
||||
VisibleTile tile = work.nextMissing();
|
||||
if (tile == null) {
|
||||
return;
|
||||
|
||||
@@ -49,7 +49,6 @@ public final class DesktopUiMessages {
|
||||
public static final TextKey VISION_MODE_BIOME_SEA = TextKey.of("iris.desktop.vision.mode.biome_sea", "Biome sea");
|
||||
public static final TextKey VISION_MODE_REGION = TextKey.of("iris.desktop.vision.mode.region", "Region");
|
||||
public static final TextKey VISION_MODE_CAVE_LAND = TextKey.of("iris.desktop.vision.mode.cave_land", "Cave land");
|
||||
public static final TextKey VISION_MODE_RIVER = TextKey.of("iris.desktop.vision.mode.river", "River network");
|
||||
public static final TextKey VISION_MODE_HEIGHT = TextKey.of("iris.desktop.vision.mode.height", "Height");
|
||||
public static final TextKey VISION_MODE_OBJECT_LOAD = TextKey.of("iris.desktop.vision.mode.object_load", "Object load");
|
||||
public static final TextKey VISION_MODE_DECORATOR_LOAD = TextKey.of("iris.desktop.vision.mode.decorator_load", "Decorator load");
|
||||
@@ -168,7 +167,7 @@ public final class DesktopUiMessages {
|
||||
VISION_HELP_RESET_ZOOM, VISION_HELP_CYCLE_MODE, VISION_HELP_FPS,
|
||||
VISION_HELP_GRID, VISION_HELP_BIOME, VISION_HELP_TELEPORT, VISION_HELP_EDITOR, VISION_OPENED,
|
||||
VISION_TELEPORTING, VISION_MODE_BIOME, VISION_MODE_BIOME_LAND, VISION_MODE_BIOME_SEA,
|
||||
VISION_MODE_REGION, VISION_MODE_CAVE_LAND, VISION_MODE_RIVER, VISION_MODE_HEIGHT, VISION_MODE_OBJECT_LOAD,
|
||||
VISION_MODE_REGION, VISION_MODE_CAVE_LAND, VISION_MODE_HEIGHT, VISION_MODE_OBJECT_LOAD,
|
||||
VISION_MODE_DECORATOR_LOAD, VISION_MODE_CONTINENT, VISION_MODE_LAYER_LOAD, NOISE_TITLE,
|
||||
NOISE_TITLE_GENERATOR, NOISE_SEARCH, NOISE_STATUS, NOISE_CATEGORY_CUSTOM,
|
||||
NOISE_CATEGORY_PACK_GENERATORS, NOISE_CATEGORY_SIMPLEX, NOISE_CATEGORY_PERLIN,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -84,9 +84,6 @@ public final class PackValidator {
|
||||
packFolder, dimensionFiles, validateLiveRegistries);
|
||||
addDistinct(blockingErrors, imageMaps.errors());
|
||||
addDistinct(warnings, imageMaps.warnings());
|
||||
PackRiverValidator.Validation riverValidation = PackRiverValidator.validate(packFolder, dimensionFiles);
|
||||
addDistinct(blockingErrors, riverValidation.errors());
|
||||
addDistinct(warnings, riverValidation.warnings());
|
||||
blockingErrors.addAll(PackCaveProfileValidator.validateLegacyFields(packFolder));
|
||||
PackLootValidator.LootGraphIssues lootIssues = PackLootValidator.validateLootGraph(packFolder);
|
||||
addDistinct(blockingErrors, lootIssues.errors());
|
||||
|
||||
@@ -29,22 +29,8 @@ import art.arcane.iris.engine.object.IrisDecorationPart;
|
||||
import art.arcane.iris.engine.object.IrisDecorator;
|
||||
import art.arcane.iris.engine.object.IrisGenerator;
|
||||
import art.arcane.iris.engine.object.IrisInterpolator;
|
||||
import art.arcane.iris.engine.object.IrisMaterialPalette;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.engine.object.IrisRiverCaves;
|
||||
import art.arcane.iris.engine.object.IrisRiverDeepPools;
|
||||
import art.arcane.iris.engine.object.IrisRiverNetwork;
|
||||
import art.arcane.iris.engine.object.IrisRiverOverride;
|
||||
import art.arcane.iris.engine.object.IrisRiverRoutingPolicy;
|
||||
import art.arcane.iris.engine.object.IrisRiverWater;
|
||||
import art.arcane.iris.engine.object.IrisShapedGeneratorStyle;
|
||||
import art.arcane.iris.engine.river.runtime.IrisRiverRuntime;
|
||||
import art.arcane.iris.engine.river.runtime.IrisRiverRuntimeContext;
|
||||
import art.arcane.iris.engine.river.runtime.IrisRiverSurfaceSample;
|
||||
import art.arcane.iris.engine.river.RiverRouteState;
|
||||
import art.arcane.iris.engine.river.RiverSample;
|
||||
import art.arcane.iris.engine.river.RiverSection;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveFluidKind;
|
||||
import art.arcane.iris.spi.IrisPlatforms;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.spi.PlatformBiome;
|
||||
@@ -67,6 +53,8 @@ import lombok.ToString;
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumMap;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
@@ -75,28 +63,37 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(exclude = {"data", "gridBoundsCache", "frozenInterpolators", "frozenGenerators", "riverRuntime", "imageMapRuntime"})
|
||||
@ToString(exclude = {"data", "gridBoundsCache", "frozenInterpolators", "frozenGenerators", "riverRuntime", "imageMapRuntime"})
|
||||
@EqualsAndHashCode(exclude = {"data", "gridBoundsCache", "frozenInterpolators", "frozenGenerators", "inferredBiomeStreams", "imageMapRuntime"})
|
||||
@ToString(exclude = {"data", "gridBoundsCache", "frozenInterpolators", "frozenGenerators", "inferredBiomeStreams", "imageMapRuntime"})
|
||||
public class IrisComplex implements DataProvider {
|
||||
private static final NoiseBounds ZERO_NOISE_BOUNDS = new NoiseBounds(0D, 0D);
|
||||
private static final AtomicLong lastBoundsFailureLog = new AtomicLong(0L);
|
||||
private static final int GRID_BOUNDS_CACHE_SIZE = 8192;
|
||||
private static final int HEIGHT_BOUNDS_GRID = 4;
|
||||
private static final Comparator<IrisInterpolator> INTERPOLATOR_ORDER = Comparator
|
||||
.comparing((IrisInterpolator interpolator) -> interpolator.getFunction().name())
|
||||
.thenComparingDouble(IrisInterpolator::getHorizontalScale);
|
||||
private static final Comparator<IrisGenerator> GENERATOR_ORDER = Comparator.comparing(
|
||||
IrisGenerator::getLoadKey,
|
||||
Comparator.nullsFirst(Comparator.naturalOrder())
|
||||
);
|
||||
private static final InferredType[] INFERRED_BIOME_PREPARATION_ORDER = {
|
||||
InferredType.LAND,
|
||||
InferredType.CAVE,
|
||||
InferredType.SEA,
|
||||
InferredType.SHORE
|
||||
};
|
||||
@Getter(AccessLevel.NONE)
|
||||
private final transient ThreadLocal<GridBoundsCache> gridBoundsCache = ThreadLocal.withInitial(GridBoundsCache::new);
|
||||
/**
|
||||
* Immutable snapshot of {@link #generators} taken once at the end of construction, in the exact
|
||||
* iteration order the map produces. The per-column height paths walk these arrays instead of
|
||||
* allocating map/set iterators, and the frozen order keeps the floating point accumulation order
|
||||
* identical to the map iteration it replaces. Mutating {@link #generators} after construction is
|
||||
* not reflected here.
|
||||
*/
|
||||
@Getter(AccessLevel.NONE)
|
||||
private final transient IrisInterpolator[] frozenInterpolators;
|
||||
@Getter(AccessLevel.NONE)
|
||||
private final transient IrisGenerator[][] frozenGenerators;
|
||||
@Getter(AccessLevel.NONE)
|
||||
private final transient Map<IrisRegion, Map<InferredType, ProceduralStream<IrisBiome>>> inferredBiomeStreams;
|
||||
private RNG rng;
|
||||
private double fluidHeight;
|
||||
private IrisData data;
|
||||
@@ -112,22 +109,14 @@ public class IrisComplex implements DataProvider {
|
||||
private ProceduralStream<IrisBiome> shoreBiomeStream;
|
||||
private ProceduralStream<IrisBiome> baseBiomeStream;
|
||||
private ProceduralStream<UUID> baseBiomeIDStream;
|
||||
private ProceduralStream<IrisBiome> naturalTrueBiomeStream;
|
||||
private ProceduralStream<IrisBiome> trueBiomeStream;
|
||||
private ProceduralStream<PlatformBiome> trueBiomeDerivativeStream;
|
||||
private ProceduralStream<Double> naturalHeightStream;
|
||||
private ProceduralStream<Double> heightStream;
|
||||
private ProceduralStream<Integer> roundedHeighteightStream;
|
||||
private ProceduralStream<Double> maxHeightStream;
|
||||
private ProceduralStream<Double> overlayStream;
|
||||
private ProceduralStream<Double> heightFluidStream;
|
||||
private ProceduralStream<Double> naturalSlopeStream;
|
||||
private ProceduralStream<Double> slopeStream;
|
||||
private ProceduralStream<IrisRiverSurfaceSample> riverSurfaceStream;
|
||||
private ProceduralStream<Double> riverDistanceStream;
|
||||
private ProceduralStream<Double> riverFlowStream;
|
||||
private ProceduralStream<Double> riverCarveWeightStream;
|
||||
private ProceduralStream<Double> riverWaterSurfaceStream;
|
||||
private ProceduralStream<Integer> topSurfaceStream;
|
||||
private ProceduralStream<IrisDecorator> terrainSurfaceDecoration;
|
||||
private ProceduralStream<IrisDecorator> terrainCeilingDecoration;
|
||||
@@ -138,13 +127,10 @@ public class IrisComplex implements DataProvider {
|
||||
private ProceduralStream<IrisDecorator> shoreSurfaceDecoration;
|
||||
private ProceduralStream<PlatformBlockState> rockStream;
|
||||
private ProceduralStream<PlatformBlockState> fluidStream;
|
||||
private ProceduralStream<PlatformBlockState> riverFluidStream;
|
||||
private ProceduralStream<PlatformBlockState> riverDeepPoolFluidStream;
|
||||
private IrisBiome focusBiome;
|
||||
private IrisRegion focusRegion;
|
||||
private Map<IrisInterpolator, IdentityHashMap<IrisBiome, GeneratorBounds>> generatorBounds;
|
||||
private Set<IrisBiome> generatorBiomes;
|
||||
private IrisRiverRuntime riverRuntime;
|
||||
private transient IrisImageMapRuntime imageMapRuntime;
|
||||
// Copy-on-write: reads happen per column on every burst thread; the synchronizedMap
|
||||
// monitor was taken on every HIT. Writes are once per biome and bounded, so a fresh map
|
||||
@@ -170,6 +156,7 @@ public class IrisComplex implements DataProvider {
|
||||
focusBiome = engine.getFocus();
|
||||
focusRegion = engine.getFocusRegion();
|
||||
Map<InferredType, ProceduralStream<IrisBiome>> inferredStreams = new HashMap<>();
|
||||
KList<IrisRegion> preparedRegions = new KList<>();
|
||||
|
||||
if (focusBiome != null) {
|
||||
focusBiome = focusBiome.withInferredType(InferredType.LAND);
|
||||
@@ -178,31 +165,34 @@ public class IrisComplex implements DataProvider {
|
||||
|
||||
//@builder
|
||||
if (focusRegion != null) {
|
||||
prepareInferredBiomes(focusRegion);
|
||||
focusRegion.getNaturalBiomes(this).forEach(this::registerGenerators);
|
||||
prepareInferredBiomes(focusRegion, preparedRegions);
|
||||
focusRegion.getAllBiomes(this).forEach(this::registerGenerators);
|
||||
} else {
|
||||
engine.getDimension().getRegions().forEach(regionKey -> {
|
||||
IrisRegion region = data.getRegionLoader().load(regionKey);
|
||||
if (region == null) {
|
||||
return;
|
||||
}
|
||||
prepareInferredBiomes(region);
|
||||
region.getNaturalBiomes(this).forEach(this::registerGenerators);
|
||||
prepareInferredBiomes(region, preparedRegions);
|
||||
region.getAllBiomes(this).forEach(this::registerGenerators);
|
||||
});
|
||||
for (IrisRegion region : imageMapRuntime.getMappedRegions()) {
|
||||
prepareInferredBiomes(region);
|
||||
region.getNaturalBiomes(this).forEach(this::registerGenerators);
|
||||
prepareInferredBiomes(region, preparedRegions);
|
||||
region.getAllBiomes(this).forEach(this::registerGenerators);
|
||||
}
|
||||
imageMapRuntime.getMappedBiomes().forEach(this::registerGenerators);
|
||||
}
|
||||
int interpolatorCount = generators.size();
|
||||
inferredBiomeStreams = compileInferredBiomeStreams(
|
||||
preparedRegions,
|
||||
(region, inferredType) -> compileInferredBiomeStream(engine, region, inferredType, emptyBiome)
|
||||
);
|
||||
GeneratorGroup[] generatorGroups = freezeGeneratorGroups(generators);
|
||||
int interpolatorCount = generatorGroups.length;
|
||||
frozenInterpolators = new IrisInterpolator[interpolatorCount];
|
||||
frozenGenerators = new IrisGenerator[interpolatorCount][];
|
||||
int frozenIndex = 0;
|
||||
for (Map.Entry<IrisInterpolator, Set<IrisGenerator>> entry : generators.entrySet()) {
|
||||
frozenInterpolators[frozenIndex] = entry.getKey();
|
||||
frozenGenerators[frozenIndex] = entry.getValue().toArray(new IrisGenerator[0]);
|
||||
frozenIndex++;
|
||||
for (int frozenIndex = 0; frozenIndex < generatorGroups.length; frozenIndex++) {
|
||||
frozenInterpolators[frozenIndex] = generatorGroups[frozenIndex].interpolator();
|
||||
frozenGenerators[frozenIndex] = generatorGroups[frozenIndex].generators();
|
||||
}
|
||||
generatorBounds = buildGeneratorBounds(engine);
|
||||
KList<IrisShapedGeneratorStyle> overlayNoise = engine.getDimension().getOverlayNoise();
|
||||
@@ -221,29 +211,6 @@ public class IrisComplex implements DataProvider {
|
||||
.select(engine.getDimension().getRockPalette().getBlockData(data));
|
||||
fluidStream = engine.getDimension().getFluidPalette().getLayerGenerator(rng.nextParallelRNG(78), data).stream()
|
||||
.select(engine.getDimension().getFluidPalette().getBlockData(data));
|
||||
IrisRiverNetwork configuredRivers = engine.getDimension().getRivers();
|
||||
IrisRiverWater configuredRiverWater = configuredRivers == null ? null : configuredRivers.getWater();
|
||||
riverFluidStream = configuredRivers != null && configuredRivers.isEnabled()
|
||||
? configuredFluidStream(
|
||||
Objects.requireNonNull(configuredRiverWater).getFluidPalette(),
|
||||
rng.nextParallelRNG(79),
|
||||
"River water"
|
||||
)
|
||||
: fluidStream;
|
||||
IrisRiverCaves configuredRiverCaves = configuredRivers == null ? null : configuredRivers.getCaves();
|
||||
IrisRiverDeepPools configuredDeepPools = configuredRiverCaves == null
|
||||
? null
|
||||
: configuredRiverCaves.getDeepPools();
|
||||
riverDeepPoolFluidStream = configuredRivers != null
|
||||
&& configuredRivers.isEnabled()
|
||||
&& configuredDeepPools != null
|
||||
&& configuredDeepPools.isEnabled()
|
||||
? configuredFluidStream(
|
||||
configuredDeepPools.getFluidPalette(),
|
||||
rng.nextParallelRNG(80),
|
||||
"River deep-pool"
|
||||
)
|
||||
: riverFluidStream;
|
||||
regionStyleStream = engine.getDimension().getRegionStyle().create(rng.nextParallelRNG(883), getData()).stream()
|
||||
.zoom(engine.getDimension().getRegionZoom());
|
||||
regionIdentityStream = regionStyleStream.fit(Integer.MIN_VALUE, Integer.MAX_VALUE);
|
||||
@@ -262,41 +229,22 @@ public class IrisComplex implements DataProvider {
|
||||
regionIDStream = regionIdentityStream.convertCached((i) -> new UUID(Double.doubleToLongBits(i),
|
||||
String.valueOf(i * 38445).hashCode() * 3245556666L));
|
||||
caveBiomeStream = regionStream.contextInjecting(engine, (c, x, z) -> c.getRegion().get(x, z))
|
||||
.convert((r)
|
||||
-> engine.getDimension().getCaveBiomeStyle().create(rng.nextParallelRNG(InferredType.CAVE.ordinal()), getData()).stream()
|
||||
.zoom(engine.getDimension().getBiomeZoom())
|
||||
.zoom(r.getCaveBiomeZoom())
|
||||
.selectRarity(loadInferredBiomes(r.getCaveBiomes(), InferredType.CAVE))
|
||||
.onNull(emptyBiome)
|
||||
).convertAware2D(ProceduralStream::get).cache2D("caveBiomeStream", engine, cacheSize);
|
||||
.convert((r) -> createInferredBiomeStream(r, InferredType.CAVE))
|
||||
.convertAware2D(ProceduralStream::get).cache2D("caveBiomeStream", engine, cacheSize);
|
||||
inferredStreams.put(InferredType.CAVE, caveBiomeStream);
|
||||
landBiomeStream = regionStream.contextInjecting(engine, (c, x, z) -> c.getRegion().get(x, z))
|
||||
.convert((r)
|
||||
-> engine.getDimension().getLandBiomeStyle().create(rng.nextParallelRNG(InferredType.LAND.ordinal()), getData()).stream()
|
||||
.zoom(engine.getDimension().getBiomeZoom())
|
||||
.zoom(engine.getDimension().getLandZoom())
|
||||
.zoom(r.getLandBiomeZoom())
|
||||
.selectRarity(loadInferredBiomes(r.getLandBiomes(), InferredType.LAND))
|
||||
).convertAware2D(ProceduralStream::get)
|
||||
.convert((r) -> createInferredBiomeStream(r, InferredType.LAND))
|
||||
.convertAware2D(ProceduralStream::get)
|
||||
.cache2D("landBiomeStream", engine, cacheSize);
|
||||
inferredStreams.put(InferredType.LAND, landBiomeStream);
|
||||
seaBiomeStream = regionStream.contextInjecting(engine, (c, x, z) -> c.getRegion().get(x, z))
|
||||
.convert((r)
|
||||
-> engine.getDimension().getSeaBiomeStyle().create(rng.nextParallelRNG(InferredType.SEA.ordinal()), getData()).stream()
|
||||
.zoom(engine.getDimension().getBiomeZoom())
|
||||
.zoom(engine.getDimension().getSeaZoom())
|
||||
.zoom(r.getSeaBiomeZoom())
|
||||
.selectRarity(loadInferredBiomes(r.getSeaBiomes(), InferredType.SEA))
|
||||
).convertAware2D(ProceduralStream::get)
|
||||
.convert((r) -> createInferredBiomeStream(r, InferredType.SEA))
|
||||
.convertAware2D(ProceduralStream::get)
|
||||
.cache2D("seaBiomeStream", engine, cacheSize);
|
||||
inferredStreams.put(InferredType.SEA, seaBiomeStream);
|
||||
shoreBiomeStream = regionStream.contextInjecting(engine, (c, x, z) -> c.getRegion().get(x, z))
|
||||
.convert((r)
|
||||
-> engine.getDimension().getShoreBiomeStyle().create(rng.nextParallelRNG(InferredType.SHORE.ordinal()), getData()).stream()
|
||||
.zoom(engine.getDimension().getBiomeZoom())
|
||||
.zoom(r.getShoreBiomeZoom())
|
||||
.selectRarity(loadInferredBiomes(r.getShoreBiomes(), InferredType.SHORE))
|
||||
).convertAware2D(ProceduralStream::get).cache2D("shoreBiomeStream", engine, cacheSize);
|
||||
.convert((r) -> createInferredBiomeStream(r, InferredType.SHORE))
|
||||
.convertAware2D(ProceduralStream::get).cache2D("shoreBiomeStream", engine, cacheSize);
|
||||
inferredStreams.put(InferredType.SHORE, shoreBiomeStream);
|
||||
bridgeStream = focusBiome != null ? ProceduralStream.of((x, z) -> focusBiome.getInferredType(),
|
||||
Interpolated.of(a -> 0D, a -> focusBiome.getInferredType())) :
|
||||
@@ -315,90 +263,28 @@ public class IrisComplex implements DataProvider {
|
||||
return mapped == null ? biome : mapped;
|
||||
})
|
||||
.cache2D("imageMappedBaseBiomeStream", engine, cacheSize);
|
||||
naturalHeightStream = ProceduralStream.of((x, z) -> {
|
||||
IrisBiome b = focusBiome != null ? focusBiome : baseBiomeStream.get(x, z);
|
||||
double proceduralHeight = getHeight(engine, b, x, z, engine.getSeedManager().getHeight());
|
||||
heightStream = ProceduralStream.of((x, z) -> {
|
||||
double proceduralHeight = getHeight(engine, x, z, engine.getSeedManager().getHeight());
|
||||
return imageMapRuntime.sampleTerrainHeight(x, z, proceduralHeight);
|
||||
}, Interpolated.DOUBLE).cache2DDouble("naturalHeightStream", engine, cacheSize);
|
||||
naturalSlopeStream = naturalHeightStream.slope(3)
|
||||
.cache2DDouble("naturalSlopeStream", engine, cacheSize);
|
||||
naturalTrueBiomeStream = focusBiome != null ? ProceduralStream.of((x, y) -> focusBiome, Interpolated.of(a -> 0D,
|
||||
}, Interpolated.DOUBLE).cache2DDouble("heightStream", engine, cacheSize);
|
||||
slopeStream = heightStream.slope(3)
|
||||
.cache2DDouble("slopeStream", engine, cacheSize);
|
||||
trueBiomeStream = focusBiome != null ? ProceduralStream.of((x, y) -> focusBiome, Interpolated.of(a -> 0D,
|
||||
b -> focusBiome))
|
||||
.cache2D("naturalTrueBiomeStream-focus", engine, cacheSize) : naturalHeightStream
|
||||
.cache2D("trueBiomeStream-focus", engine, cacheSize) : heightStream
|
||||
.convertAware2D((h, x, z) -> {
|
||||
IrisBiome mapped = imageMapRuntime.sampleBiome(x, z);
|
||||
return mapped == null
|
||||
? fixBiomeType(h, baseBiomeStream.get(x, z), regionStream.get(x, z), x, z, fluidHeight)
|
||||
: mapped;
|
||||
})
|
||||
.cache2D("naturalTrueBiomeStream", engine, cacheSize);
|
||||
if (engine.getDimension().getRivers() != null && engine.getDimension().getRivers().isEnabled()) {
|
||||
ProceduralStream<Boolean> naturalOceanStream = createNaturalOceanStream(
|
||||
bridgeStream,
|
||||
focusBiome
|
||||
).cache2D("naturalOceanStream", engine, cacheSize);
|
||||
int riverFluidHeight = engine.getDimension().getRivers().getWater().getFluidHeight()
|
||||
- engine.getDimension().getMinHeight();
|
||||
riverRuntime = new IrisRiverRuntime(new IrisRiverRuntimeContext(
|
||||
engine.getSeedManager().getBodies(),
|
||||
engine.getDimension().getRivers(),
|
||||
data,
|
||||
riverFluidHeight,
|
||||
(int) Math.round(fluidHeight),
|
||||
IrisEngineMantle.isRiverHydrologyEnabled(engine.getDimension()),
|
||||
IrisEngineMantle.isRiverCaveHydrologyEnabled(engine.getDimension()),
|
||||
blockingRiverRoutingPossible(engine),
|
||||
variableMaxIncisionPossible(engine),
|
||||
biomeRiverOverridesPossible(focusBiome, generatorBiomes),
|
||||
(blockX, blockZ) -> naturalHeightBounds(engine, overlayNoise, blockX, blockZ),
|
||||
naturalHeightStream,
|
||||
naturalSlopeStream,
|
||||
naturalOceanStream,
|
||||
naturalTrueBiomeStream,
|
||||
regionStream
|
||||
));
|
||||
riverSurfaceStream = ProceduralStream.of(
|
||||
(x, z) -> riverRuntime.sample(x, z),
|
||||
Interpolated.of(
|
||||
IrisRiverSurfaceSample::terrainHeight,
|
||||
value -> IrisRiverSurfaceSample.none(value, fluidHeight)
|
||||
)
|
||||
)
|
||||
.cache2D("riverSurfaceStream", engine, cacheSize);
|
||||
} else {
|
||||
riverSurfaceStream = naturalHeightStream.convert(
|
||||
value -> IrisRiverSurfaceSample.none(value, fluidHeight)
|
||||
)
|
||||
.cache2D("riverSurfaceStream-disabled", engine, cacheSize);
|
||||
}
|
||||
heightStream = riverSurfaceStream.convert(IrisRiverSurfaceSample::terrainHeight)
|
||||
.cache2DDouble("heightStream", engine, cacheSize);
|
||||
.cache2D("trueBiomeStream", engine, cacheSize);
|
||||
roundedHeighteightStream = heightStream.contextInjecting(engine, (c, x, z) -> c.getHeight().getDouble(x, z))
|
||||
.round();
|
||||
slopeStream = heightStream.contextInjecting(engine, (c, x, z) -> c.getHeight().getDouble(x, z))
|
||||
.slope(3).cache2DDouble("slopeStream", engine, cacheSize);
|
||||
trueBiomeStream = focusBiome != null ? ProceduralStream.of((x, y) -> focusBiome, Interpolated.of(a -> 0D,
|
||||
b -> focusBiome))
|
||||
.cache2D("trueBiomeStream-focus", engine, cacheSize) : riverSurfaceStream
|
||||
.convertAware2D((sample, x, z) -> resolveRiverSurfaceBiome(sample, x, z))
|
||||
.cache2D("trueBiomeStream", engine, cacheSize);
|
||||
trueBiomeDerivativeStream = trueBiomeStream.contextInjecting(engine, (c, x, z) -> c.getBiome().get(x, z))
|
||||
.convert((b) -> IrisPlatforms.get().registries().biome(b.getDerivativeKey())).cache2D("trueBiomeDerivativeStream", engine, cacheSize);
|
||||
riverDistanceStream = riverSurfaceStream.convert(sample -> sample.river().present()
|
||||
? sample.river().distance()
|
||||
: Double.MAX_VALUE)
|
||||
.cache2DDouble("riverDistanceStream", engine, cacheSize);
|
||||
riverFlowStream = riverSurfaceStream.convert(sample -> (double) sample.river().flow())
|
||||
.cache2DDouble("riverFlowStream", engine, cacheSize);
|
||||
riverCarveWeightStream = riverSurfaceStream.convert(sample -> sample.river().carveWeight())
|
||||
.cache2DDouble("riverCarveWeightStream", engine, cacheSize);
|
||||
riverWaterSurfaceStream = riverSurfaceStream.convert(IrisRiverSurfaceSample::waterSurfaceY)
|
||||
.cache2DDouble("riverWaterSurfaceStream", engine, cacheSize);
|
||||
heightFluidStream = ProceduralStream.ofDouble((x, z) -> Math.max(
|
||||
heightStream.get(x, z),
|
||||
riverWaterSurfaceStream.get(x, z)
|
||||
))
|
||||
.cache2DDouble("heightFluidStream", engine, cacheSize);
|
||||
heightFluidStream = heightStream.contextInjecting(engine, (c, x, z) -> c.getHeight().getDouble(x, z))
|
||||
.max(fluidHeight).cache2DDouble("heightFluidStream", engine, cacheSize);
|
||||
maxHeightStream = ProceduralStream.ofDouble((x, z) -> height);
|
||||
terrainSurfaceDecoration = trueBiomeStream.contextInjecting(engine, (c, x, z) -> c.getBiome().get(x, z))
|
||||
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.NONE)).cache2D("terrainSurfaceDecoration", engine, cacheSize);
|
||||
@@ -424,116 +310,6 @@ public class IrisComplex implements DataProvider {
|
||||
//@done
|
||||
}
|
||||
|
||||
private boolean blockingRiverRoutingPossible(Engine engine) {
|
||||
if (blocksRiverRouting(focusRegion == null ? null : focusRegion.getRiverOverride())
|
||||
|| blocksRiverRouting(focusBiome == null ? null : focusBiome.getRiverOverride())) {
|
||||
return true;
|
||||
}
|
||||
KList<IrisRegion> regions = engine.getDimension().getAllRegions(engine);
|
||||
for (IrisRegion loadedRegion : regions) {
|
||||
if (loadedRegion != null && blocksRiverRouting(loadedRegion.getRiverOverride())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
KList<IrisBiome> biomes = engine.getDimension().getReachableBiomes(engine);
|
||||
for (IrisBiome biome : biomes) {
|
||||
if (biome != null && blocksRiverRouting(biome.getRiverOverride())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean blocksRiverRouting(IrisRiverOverride override) {
|
||||
return override != null && override.getRoutingPolicy() == IrisRiverRoutingPolicy.BLOCK;
|
||||
}
|
||||
|
||||
private boolean variableMaxIncisionPossible(Engine engine) {
|
||||
if (changesMaxIncision(focusRegion == null ? null : focusRegion.getRiverOverride())
|
||||
|| changesMaxIncision(focusBiome == null ? null : focusBiome.getRiverOverride())) {
|
||||
return true;
|
||||
}
|
||||
KList<IrisRegion> regions = engine.getDimension().getAllRegions(engine);
|
||||
for (IrisRegion loadedRegion : regions) {
|
||||
if (loadedRegion != null && changesMaxIncision(loadedRegion.getRiverOverride())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
KList<IrisBiome> biomes = engine.getDimension().getReachableBiomes(engine);
|
||||
for (IrisBiome biome : biomes) {
|
||||
if (biome != null && changesMaxIncision(biome.getRiverOverride())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static boolean changesMaxIncision(IrisRiverOverride override) {
|
||||
if (override == null || override.getMaxIncisionMultiplier() == null) {
|
||||
return false;
|
||||
}
|
||||
double multiplier = override.getMaxIncisionMultiplier();
|
||||
return Double.isFinite(multiplier) && Double.compare(Math.max(0D, multiplier), 1D) != 0;
|
||||
}
|
||||
|
||||
static boolean biomeRiverOverridesPossible(IrisBiome focusBiome, Iterable<IrisBiome> naturalBiomes) {
|
||||
if (focusBiome != null) {
|
||||
return focusBiome.getRiverOverride() != null;
|
||||
}
|
||||
for (IrisBiome biome : naturalBiomes) {
|
||||
if (biome != null && biome.getRiverOverride() != null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static ProceduralStream<Boolean> createNaturalOceanStream(
|
||||
ProceduralStream<InferredType> bridgeStream,
|
||||
IrisBiome focusBiome
|
||||
) {
|
||||
if (focusBiome != null) {
|
||||
boolean ocean = focusBiome.getInferredType() == InferredType.SEA;
|
||||
return ProceduralStream.of((x, z) -> ocean, Interpolated.BOOLEAN);
|
||||
}
|
||||
return bridgeStream.convert(type -> type == InferredType.SEA);
|
||||
}
|
||||
|
||||
private ProceduralStream<PlatformBlockState> configuredFluidStream(
|
||||
IrisMaterialPalette palette,
|
||||
RNG fluidRng,
|
||||
String configurationName
|
||||
) {
|
||||
Objects.requireNonNull(palette, configurationName + " fluidPalette must be configured");
|
||||
KList<PlatformBlockState> blocks = palette.getBlockData(data);
|
||||
if (blocks.isEmpty()) {
|
||||
throw new IllegalArgumentException(
|
||||
configurationName + " fluidPalette must resolve at least one fluid block");
|
||||
}
|
||||
for (PlatformBlockState block : blocks) {
|
||||
if (block == null || !block.isFluid()) {
|
||||
throw new IllegalArgumentException(
|
||||
configurationName + " fluidPalette may contain only fluid blocks");
|
||||
}
|
||||
}
|
||||
return palette.getLayerGenerator(fluidRng, data).stream().select(blocks);
|
||||
}
|
||||
|
||||
public PlatformBlockState resolveRiverCaveFluid(RiverCaveFluidKind fluidKind, double x, double z) {
|
||||
return switch (Objects.requireNonNull(fluidKind)) {
|
||||
case RIVER -> riverFluidStream.get(x, z);
|
||||
case DEEP_POOL -> riverDeepPoolFluidStream.get(x, z);
|
||||
};
|
||||
}
|
||||
|
||||
public PlatformBlockState resolveSurfaceFluid(double x, double z) {
|
||||
IrisRiverSurfaceSample sample = riverSurfaceStream.get(x, z);
|
||||
if (sample.river().present() && sample.surfaceFluid()) {
|
||||
return riverFluidStream.get(x, z);
|
||||
}
|
||||
return fluidStream.get(x, z);
|
||||
}
|
||||
|
||||
public ProceduralStream<IrisBiome> getBiomeStream(InferredType type) {
|
||||
switch (type) {
|
||||
case CAVE:
|
||||
@@ -555,7 +331,7 @@ public class IrisComplex implements DataProvider {
|
||||
if (engine == null) {
|
||||
throw new IllegalArgumentException("Engine is required to sample procedural terrain height");
|
||||
}
|
||||
return getHeight(engine, null, worldX, worldZ, engine.getSeedManager().getHeight());
|
||||
return getHeight(engine, worldX, worldZ, engine.getSeedManager().getHeight());
|
||||
}
|
||||
|
||||
private IrisRegion findRegion(IrisBiome focus, Engine engine) {
|
||||
@@ -594,67 +370,6 @@ public class IrisComplex implements DataProvider {
|
||||
return null;
|
||||
}
|
||||
|
||||
private IrisBiome resolveRiverSurfaceBiome(IrisRiverSurfaceSample sample, double x, double z) {
|
||||
IrisBiome mapped = imageMapRuntime.sampleBiome(x, z);
|
||||
if (mapped != null && (riverRuntime == null || !sample.river().present())) {
|
||||
return mapped;
|
||||
}
|
||||
if (riverRuntime != null) {
|
||||
if (sample.subterranean()) {
|
||||
return fixBiomeType(
|
||||
sample.naturalHeight(),
|
||||
baseBiomeStream.get(x, z),
|
||||
regionStream.get(x, z),
|
||||
x,
|
||||
z,
|
||||
fluidHeight
|
||||
);
|
||||
}
|
||||
IrisBiome riverBiome = riverRuntime.selectSurfaceBiome(sample, x, z);
|
||||
if (riverBiome != null) {
|
||||
return implode(riverBiome, x, z);
|
||||
}
|
||||
InferredType directFallback = directRiverFallback(sample.river());
|
||||
if (directFallback != null) {
|
||||
IrisBiome baseBiome = baseBiomeStream.get(x, z);
|
||||
return implode(baseBiome.withInferredType(directFallback), x, z);
|
||||
}
|
||||
if (sample.river().present()
|
||||
&& sample.river().state() == RiverRouteState.WET
|
||||
&& sample.river().section() == RiverSection.BANK) {
|
||||
return fixBiomeType(
|
||||
sample.terrainHeight(),
|
||||
baseBiomeStream.get(x, z),
|
||||
regionStream.get(x, z),
|
||||
x,
|
||||
z,
|
||||
sample.waterSurfaceY()
|
||||
);
|
||||
}
|
||||
}
|
||||
return fixBiomeType(
|
||||
sample.terrainHeight(),
|
||||
baseBiomeStream.get(x, z),
|
||||
regionStream.get(x, z),
|
||||
x,
|
||||
z,
|
||||
fluidHeight
|
||||
);
|
||||
}
|
||||
|
||||
static InferredType directRiverFallback(RiverSample river) {
|
||||
if (!river.present()) {
|
||||
return null;
|
||||
}
|
||||
if (river.state() == RiverRouteState.DRY) {
|
||||
return InferredType.LAND;
|
||||
}
|
||||
return switch (river.section()) {
|
||||
case CHANNEL, MOUTH -> InferredType.SEA;
|
||||
default -> null;
|
||||
};
|
||||
}
|
||||
|
||||
private IrisBiome fixBiomeType(Double height, IrisBiome biome, IrisRegion region, Double x, Double z, double fluidHeight) {
|
||||
IrisBiome resolved = resolveSurfaceBiome(
|
||||
height,
|
||||
@@ -713,13 +428,7 @@ public class IrisComplex implements DataProvider {
|
||||
double hi = sampledBounds.max();
|
||||
double lo = sampledBounds.min();
|
||||
|
||||
double d = 0;
|
||||
|
||||
for (IrisGenerator i : generators) {
|
||||
d += M.lerp(lo, hi, i.getHeight(x, z, seed + 239945));
|
||||
}
|
||||
|
||||
return d / generators.length;
|
||||
return averageGeneratorHeights(generators, lo, hi, x, z, seed + 239945);
|
||||
}
|
||||
|
||||
private NoiseBounds gridSampleBounds(Engine engine, IrisInterpolator interpolator, int interpolatorIndex, IrisGenerator[] generators, double x, double z) {
|
||||
@@ -742,6 +451,22 @@ public class IrisComplex implements DataProvider {
|
||||
return new NoiseBounds(boundsLow(b00), boundsHigh(b00));
|
||||
}
|
||||
|
||||
if (fz == 0D) {
|
||||
long b10 = cornerBounds(cache, engine, interpolator, interpolatorIndex, generators, gx + grid, gz);
|
||||
return new NoiseBounds(
|
||||
biLerp(boundsLow(b00), boundsLow(b10), boundsLow(b00), boundsLow(b10), fx, fz),
|
||||
biLerp(boundsHigh(b00), boundsHigh(b10), boundsHigh(b00), boundsHigh(b10), fx, fz)
|
||||
);
|
||||
}
|
||||
|
||||
if (fx == 0D) {
|
||||
long b01 = cornerBounds(cache, engine, interpolator, interpolatorIndex, generators, gx, gz + grid);
|
||||
return new NoiseBounds(
|
||||
biLerp(boundsLow(b00), boundsLow(b00), boundsLow(b01), boundsLow(b01), fx, fz),
|
||||
biLerp(boundsHigh(b00), boundsHigh(b00), boundsHigh(b01), boundsHigh(b01), fx, fz)
|
||||
);
|
||||
}
|
||||
|
||||
long b10 = cornerBounds(cache, engine, interpolator, interpolatorIndex, generators, gx + grid, gz);
|
||||
long b01 = cornerBounds(cache, engine, interpolator, interpolatorIndex, generators, gx, gz + grid);
|
||||
long b11 = cornerBounds(cache, engine, interpolator, interpolatorIndex, generators, gx + grid, gz + grid);
|
||||
@@ -803,47 +528,94 @@ public class IrisComplex implements DataProvider {
|
||||
return h;
|
||||
}
|
||||
|
||||
private NoiseBounds naturalHeightBounds(
|
||||
Engine engine,
|
||||
KList<IrisShapedGeneratorStyle> overlayNoise,
|
||||
double x,
|
||||
double z
|
||||
) {
|
||||
double minimum = fluidHeight;
|
||||
double maximum = fluidHeight;
|
||||
for (int interpolatorIndex = 0; interpolatorIndex < frozenInterpolators.length; interpolatorIndex++) {
|
||||
NoiseBounds bounds = gridSampleBounds(
|
||||
engine,
|
||||
frozenInterpolators[interpolatorIndex],
|
||||
interpolatorIndex,
|
||||
frozenGenerators[interpolatorIndex],
|
||||
x,
|
||||
z
|
||||
);
|
||||
minimum += Math.min(bounds.min(), bounds.max());
|
||||
maximum += Math.max(bounds.min(), bounds.max());
|
||||
}
|
||||
for (IrisShapedGeneratorStyle style : overlayNoise) {
|
||||
minimum += Math.min(style.getMin(), style.getMax());
|
||||
maximum += Math.max(style.getMin(), style.getMax());
|
||||
}
|
||||
minimum = imageMapRuntime.sampleTerrainHeight(x, z, minimum);
|
||||
maximum = imageMapRuntime.sampleTerrainHeight(x, z, maximum);
|
||||
return new NoiseBounds(
|
||||
Math.max(0D, Math.min(engine.getHeight(), Math.min(minimum, maximum))),
|
||||
Math.max(0D, Math.min(engine.getHeight(), Math.max(minimum, maximum)))
|
||||
);
|
||||
}
|
||||
|
||||
private double getHeight(Engine engine, IrisBiome b, double x, double z, long seed) {
|
||||
private double getHeight(Engine engine, double x, double z, long seed) {
|
||||
return Math.max(Math.min(getInterpolatedHeight(engine, x, z, seed) + fluidHeight + overlayStream.get(x, z), engine.getHeight()), 0);
|
||||
}
|
||||
|
||||
private void prepareInferredBiomes(IrisRegion region) {
|
||||
private void prepareInferredBiomes(IrisRegion region, KList<IrisRegion> preparedRegions) {
|
||||
loadInferredBiomes(region.getLandBiomes(), InferredType.LAND);
|
||||
loadInferredBiomes(region.getCaveBiomes(), InferredType.CAVE);
|
||||
loadInferredBiomes(region.getSeaBiomes(), InferredType.SEA);
|
||||
loadInferredBiomes(region.getShoreBiomes(), InferredType.SHORE);
|
||||
preparedRegions.add(region);
|
||||
}
|
||||
|
||||
private ProceduralStream<IrisBiome> createInferredBiomeStream(
|
||||
IrisRegion region,
|
||||
InferredType inferredType
|
||||
) {
|
||||
return preparedInferredBiomeStream(inferredBiomeStreams, region, inferredType);
|
||||
}
|
||||
|
||||
private ProceduralStream<IrisBiome> compileInferredBiomeStream(
|
||||
Engine engine,
|
||||
IrisRegion region,
|
||||
InferredType inferredType,
|
||||
IrisBiome emptyBiome
|
||||
) {
|
||||
return switch (inferredType) {
|
||||
case CAVE -> engine.getDimension().getCaveBiomeStyle()
|
||||
.create(rng.nextParallelRNG(InferredType.CAVE.ordinal()), getData()).stream()
|
||||
.zoom(engine.getDimension().getBiomeZoom())
|
||||
.zoom(region.getCaveBiomeZoom())
|
||||
.selectRarity(loadInferredBiomes(region.getCaveBiomes(), InferredType.CAVE))
|
||||
.onNull(emptyBiome);
|
||||
case LAND -> engine.getDimension().getLandBiomeStyle()
|
||||
.create(rng.nextParallelRNG(InferredType.LAND.ordinal()), getData()).stream()
|
||||
.zoom(engine.getDimension().getBiomeZoom())
|
||||
.zoom(engine.getDimension().getLandZoom())
|
||||
.zoom(region.getLandBiomeZoom())
|
||||
.selectRarity(loadInferredBiomes(region.getLandBiomes(), InferredType.LAND));
|
||||
case SEA -> engine.getDimension().getSeaBiomeStyle()
|
||||
.create(rng.nextParallelRNG(InferredType.SEA.ordinal()), getData()).stream()
|
||||
.zoom(engine.getDimension().getBiomeZoom())
|
||||
.zoom(engine.getDimension().getSeaZoom())
|
||||
.zoom(region.getSeaBiomeZoom())
|
||||
.selectRarity(loadInferredBiomes(region.getSeaBiomes(), InferredType.SEA));
|
||||
case SHORE -> engine.getDimension().getShoreBiomeStyle()
|
||||
.create(rng.nextParallelRNG(InferredType.SHORE.ordinal()), getData()).stream()
|
||||
.zoom(engine.getDimension().getBiomeZoom())
|
||||
.zoom(region.getShoreBiomeZoom())
|
||||
.selectRarity(loadInferredBiomes(region.getShoreBiomes(), InferredType.SHORE));
|
||||
};
|
||||
}
|
||||
|
||||
static Map<IrisRegion, Map<InferredType, ProceduralStream<IrisBiome>>> compileInferredBiomeStreams(
|
||||
Iterable<IrisRegion> regions,
|
||||
BiFunction<IrisRegion, InferredType, ProceduralStream<IrisBiome>> compiler
|
||||
) {
|
||||
IdentityHashMap<IrisRegion, Map<InferredType, ProceduralStream<IrisBiome>>> compiled = new IdentityHashMap<>();
|
||||
for (IrisRegion region : regions) {
|
||||
if (compiled.containsKey(region)) {
|
||||
continue;
|
||||
}
|
||||
EnumMap<InferredType, ProceduralStream<IrisBiome>> regionStreams = new EnumMap<>(InferredType.class);
|
||||
for (InferredType inferredType : INFERRED_BIOME_PREPARATION_ORDER) {
|
||||
regionStreams.put(inferredType, Objects.requireNonNull(compiler.apply(region, inferredType)));
|
||||
}
|
||||
compiled.put(region, Collections.unmodifiableMap(regionStreams));
|
||||
}
|
||||
return Collections.unmodifiableMap(compiled);
|
||||
}
|
||||
|
||||
static ProceduralStream<IrisBiome> preparedInferredBiomeStream(
|
||||
Map<IrisRegion, Map<InferredType, ProceduralStream<IrisBiome>>> streams,
|
||||
IrisRegion region,
|
||||
InferredType inferredType
|
||||
) {
|
||||
Map<InferredType, ProceduralStream<IrisBiome>> regionStreams = streams.get(region);
|
||||
if (regionStreams == null) {
|
||||
String regionKey = region == null || region.getLoadKey() == null || region.getLoadKey().isBlank()
|
||||
? "<unkeyed>"
|
||||
: region.getLoadKey();
|
||||
throw new IllegalStateException("Inferred-biome streams were not prepared for region '"
|
||||
+ regionKey + "'.");
|
||||
}
|
||||
ProceduralStream<IrisBiome> stream = regionStreams.get(inferredType);
|
||||
if (stream == null) {
|
||||
throw new IllegalStateException("Inferred-biome stream was not prepared for type " + inferredType + ".");
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
|
||||
private KList<IrisBiome> loadInferredBiomes(KList<String> keys, InferredType type) {
|
||||
@@ -863,6 +635,37 @@ public class IrisComplex implements DataProvider {
|
||||
generators.computeIfAbsent(cachedGenerator.getInterpolator(), (k) -> new HashSet<>()).add(cachedGenerator);
|
||||
}
|
||||
|
||||
static GeneratorGroup[] freezeGeneratorGroups(Map<IrisInterpolator, Set<IrisGenerator>> generators) {
|
||||
GeneratorGroup[] groups = new GeneratorGroup[generators.size()];
|
||||
int groupIndex = 0;
|
||||
for (Map.Entry<IrisInterpolator, Set<IrisGenerator>> entry : generators.entrySet()) {
|
||||
IrisGenerator[] groupGenerators = entry.getValue().toArray(new IrisGenerator[0]);
|
||||
Arrays.sort(groupGenerators, GENERATOR_ORDER);
|
||||
groups[groupIndex] = new GeneratorGroup(entry.getKey(), groupGenerators);
|
||||
groupIndex++;
|
||||
}
|
||||
Arrays.sort(groups, Comparator.comparing(GeneratorGroup::interpolator, INTERPOLATOR_ORDER));
|
||||
return groups;
|
||||
}
|
||||
|
||||
static double averageGeneratorHeights(
|
||||
IrisGenerator[] generators,
|
||||
double low,
|
||||
double high,
|
||||
double x,
|
||||
double z,
|
||||
long seed
|
||||
) {
|
||||
if (generators.length == 0) {
|
||||
return 0D;
|
||||
}
|
||||
double height = 0D;
|
||||
for (IrisGenerator generator : generators) {
|
||||
height += M.lerp(low, high, generator.getHeight(x, z, seed));
|
||||
}
|
||||
return height / generators.length;
|
||||
}
|
||||
|
||||
private Map<IrisInterpolator, IdentityHashMap<IrisBiome, GeneratorBounds>> buildGeneratorBounds(Engine engine) {
|
||||
Map<IrisInterpolator, IdentityHashMap<IrisBiome, GeneratorBounds>> bounds = new HashMap<>();
|
||||
KList<IrisBiome> allBiomes = new KList<>(generatorBiomes);
|
||||
@@ -1265,9 +1068,10 @@ public class IrisComplex implements DataProvider {
|
||||
}
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (riverRuntime != null) {
|
||||
riverRuntime.close();
|
||||
}
|
||||
record GeneratorGroup(IrisInterpolator interpolator, IrisGenerator[] generators) {
|
||||
}
|
||||
|
||||
public void close() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -30,9 +30,7 @@ import art.arcane.iris.engine.mantle.MantlePass;
|
||||
import art.arcane.iris.engine.mantle.components.MantleCarvingComponent;
|
||||
import art.arcane.iris.engine.mantle.components.MantleFloatingObjectComponent;
|
||||
import art.arcane.iris.engine.mantle.components.MantleObjectComponent;
|
||||
import art.arcane.iris.engine.mantle.components.MantleRiverHydrologyComponent;
|
||||
import art.arcane.iris.engine.mantle.components.IrisStructureComponent;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.util.project.matter.IrisMatterContext;
|
||||
@@ -90,7 +88,6 @@ public class IrisEngineMantle implements EngineMantle {
|
||||
this.mantle = createMantle(engine);
|
||||
components = new KMap<>();
|
||||
registerComponent(new MantleCarvingComponent(this));
|
||||
registerComponent(new MantleRiverHydrologyComponent(this));
|
||||
object = new MantleObjectComponent(this);
|
||||
registerComponent(object);
|
||||
registerComponent(new MantleFloatingObjectComponent(this));
|
||||
@@ -179,22 +176,10 @@ public class IrisEngineMantle implements EngineMantle {
|
||||
if (!getDimension().isCarvingEnabled()) {
|
||||
disabled.addIfMissing(ReservedFlag.CARVED);
|
||||
}
|
||||
if (disabled.contains(ReservedFlag.CARVED)
|
||||
|| !isRiverHydrologyEnabled(getDimension())) {
|
||||
disabled.addIfMissing(ReservedFlag.RIVER_HYDROLOGY);
|
||||
}
|
||||
return Set.copyOf(disabled);
|
||||
});
|
||||
}
|
||||
|
||||
static boolean isRiverHydrologyEnabled(IrisDimension dimension) {
|
||||
return MantleRiverHydrologyComponent.isEnabledFor(dimension);
|
||||
}
|
||||
|
||||
static boolean isRiverCaveHydrologyEnabled(IrisDimension dimension) {
|
||||
return MantleRiverHydrologyComponent.isCaveConnectionsEnabledFor(dimension);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MantleObjectComponent getObjectComponent() {
|
||||
return object;
|
||||
|
||||
@@ -14,7 +14,6 @@ import art.arcane.iris.engine.object.IrisShapedGeneratorStyle;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.iris.util.common.data.DataProvider;
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.iris.util.project.interpolation.NoiseBounds;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
@@ -67,8 +66,8 @@ public class UpperDimensionContext implements DataProvider {
|
||||
engine.getDimension(),
|
||||
engine.getData(),
|
||||
chunkHeight,
|
||||
complex.getNaturalHeightStream(),
|
||||
complex.getNaturalTrueBiomeStream(),
|
||||
complex.getHeightStream(),
|
||||
complex.getTrueBiomeStream(),
|
||||
complex.getRegionStream(),
|
||||
complex.getRockStream(),
|
||||
complex.getImageMapRuntime(),
|
||||
@@ -100,25 +99,26 @@ public class UpperDimensionContext implements DataProvider {
|
||||
upperDim.getRegions().forEach(regionKey -> {
|
||||
IrisRegion region = upperData.getRegionLoader().load(regionKey);
|
||||
if (region != null) {
|
||||
region.getNaturalBiomes(dataProvider).forEach(biome -> registerBiomeGenerators(
|
||||
region.getAllBiomes(dataProvider).forEach(biome -> registerBiomeGenerators(
|
||||
biome, dataProvider, allBiomes, generators));
|
||||
}
|
||||
});
|
||||
for (IrisRegion mappedRegion : imageMapRuntime.getMappedRegions()) {
|
||||
mappedRegion.getNaturalBiomes(dataProvider).forEach(biome -> registerBiomeGenerators(
|
||||
mappedRegion.getAllBiomes(dataProvider).forEach(biome -> registerBiomeGenerators(
|
||||
biome, dataProvider, allBiomes, generators));
|
||||
}
|
||||
for (IrisBiome mappedBiome : imageMapRuntime.getMappedBiomes()) {
|
||||
registerBiomeGenerators(mappedBiome, dataProvider, allBiomes, generators);
|
||||
}
|
||||
|
||||
IrisComplex.GeneratorGroup[] generatorGroups = IrisComplex.freezeGeneratorGroups(generators);
|
||||
Map<IrisInterpolator, IdentityHashMap<IrisBiome, NoiseBounds>> generatorBounds = new HashMap<>();
|
||||
for (Map.Entry<IrisInterpolator, Set<IrisGenerator>> entry : generators.entrySet()) {
|
||||
for (IrisComplex.GeneratorGroup group : generatorGroups) {
|
||||
IdentityHashMap<IrisBiome, NoiseBounds> interpolatorBounds = new IdentityHashMap<>(Math.max(allBiomes.size(), 16));
|
||||
for (IrisBiome biome : allBiomes) {
|
||||
double min = 0D;
|
||||
double max = 0D;
|
||||
for (IrisGenerator gen : entry.getValue()) {
|
||||
for (IrisGenerator gen : group.generators()) {
|
||||
String key = gen.getLoadKey();
|
||||
if (key == null || key.isBlank()) {
|
||||
continue;
|
||||
@@ -128,7 +128,7 @@ public class UpperDimensionContext implements DataProvider {
|
||||
}
|
||||
interpolatorBounds.put(biome, new NoiseBounds(min, max));
|
||||
}
|
||||
generatorBounds.put(entry.getKey(), interpolatorBounds);
|
||||
generatorBounds.put(group.interpolator(), interpolatorBounds);
|
||||
}
|
||||
|
||||
ProceduralStream<Double> regionStyleStream = upperDim.getRegionStyle()
|
||||
@@ -204,10 +204,10 @@ public class UpperDimensionContext implements DataProvider {
|
||||
return mappedTerrainHeight(imageMapRuntime, fluidHeight, x, z);
|
||||
}
|
||||
double interpolatedHeight = 0;
|
||||
for (Map.Entry<IrisInterpolator, Set<IrisGenerator>> entry : generators.entrySet()) {
|
||||
IrisInterpolator interpolator = entry.getKey();
|
||||
Set<IrisGenerator> gens = entry.getValue();
|
||||
if (gens.isEmpty()) {
|
||||
for (IrisComplex.GeneratorGroup group : generatorGroups) {
|
||||
IrisInterpolator interpolator = group.interpolator();
|
||||
IrisGenerator[] groupGenerators = group.generators();
|
||||
if (groupGenerators.length == 0) {
|
||||
continue;
|
||||
}
|
||||
IdentityHashMap<IrisBiome, NoiseBounds> cachedBounds = generatorBounds.get(interpolator);
|
||||
@@ -223,7 +223,7 @@ public class UpperDimensionContext implements DataProvider {
|
||||
}
|
||||
double bMin = 0D;
|
||||
double bMax = 0D;
|
||||
for (IrisGenerator gen : gens) {
|
||||
for (IrisGenerator gen : groupGenerators) {
|
||||
String key = gen.getLoadKey();
|
||||
if (key == null || key.isBlank()) {
|
||||
continue;
|
||||
@@ -239,11 +239,14 @@ public class UpperDimensionContext implements DataProvider {
|
||||
});
|
||||
double hi = sampledBounds.max();
|
||||
double lo = sampledBounds.min();
|
||||
double d = 0;
|
||||
for (IrisGenerator gen : gens) {
|
||||
d += M.lerp(lo, hi, gen.getHeight(x, z, heightSeed + 239945));
|
||||
}
|
||||
interpolatedHeight += d / gens.size();
|
||||
interpolatedHeight += IrisComplex.averageGeneratorHeights(
|
||||
groupGenerators,
|
||||
lo,
|
||||
hi,
|
||||
x,
|
||||
z,
|
||||
heightSeed + 239945
|
||||
);
|
||||
}
|
||||
double proceduralHeight = Math.max(
|
||||
Math.min(interpolatedHeight + fluidHeight + overlayStream.get(x, z), chunkHeight),
|
||||
|
||||
@@ -27,8 +27,6 @@ import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.EngineAssignedActuator;
|
||||
import art.arcane.iris.engine.framework.EngineDecorator;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.river.RiverRouteState;
|
||||
import art.arcane.iris.engine.river.runtime.IrisRiverSurfaceSample;
|
||||
import art.arcane.iris.util.common.data.B;
|
||||
import art.arcane.iris.util.project.context.ChunkContext;
|
||||
import art.arcane.volmlib.util.documentation.BlockCoordinates;
|
||||
@@ -66,12 +64,6 @@ public class IrisDecorantActuator extends EngineAssignedActuator<PlatformBlockSt
|
||||
seaFloorDecorator = new IrisSeaFloorDecorator(getEngine());
|
||||
}
|
||||
|
||||
static boolean shouldDecorateShoreline(IrisRiverSurfaceSample sample, int height) {
|
||||
return !sample.subterranean()
|
||||
&& height == Math.round(sample.waterSurfaceY())
|
||||
&& (!sample.river().present() || sample.river().state() != RiverRouteState.DRY);
|
||||
}
|
||||
|
||||
@BlockCoordinates
|
||||
@Override
|
||||
public void onActuate(int x, int z, Hunk<PlatformBlockState> output, boolean multicore, ChunkContext context) {
|
||||
@@ -94,25 +86,23 @@ public class IrisDecorantActuator extends EngineAssignedActuator<PlatformBlockSt
|
||||
height = context.getRoundedHeight(i, j);
|
||||
biome = context.getBiome().get(i, j);
|
||||
cave = shouldRay ? context.getCave().get(i, j) : null;
|
||||
IrisRiverSurfaceSample riverSurface = getComplex().getRiverSurfaceStream().get(realX, realZ);
|
||||
int surfaceFluidHeight = (int) Math.round(riverSurface.waterSurfaceY());
|
||||
|
||||
if (biome.getDecorators().isEmpty() && (cave == null || cave.getDecorators().isEmpty())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (height < surfaceFluidHeight && PREDICATE_SOLID.test(output.get(i, height, j))
|
||||
&& height + 1 < output.getHeight() && B.isFluid(output.get(i, height + 1, j))) {
|
||||
if (height < getDimension().getFluidHeight() && PREDICATE_SOLID.test(output.get(i, height, j))
|
||||
&& height + 1 < output.getHeight() && B.isWater(output.get(i, height + 1, j))) {
|
||||
getSeaSurfaceDecorator().decorate(i, j,
|
||||
realX, Math.round(i + 1), Math.round(x + i - 1),
|
||||
realZ, Math.round(z + j + 1), Math.round(z + j - 1),
|
||||
output, biome, surfaceFluidHeight, getEngine().getHeight());
|
||||
output, biome, getDimension().getFluidHeight(), getEngine().getHeight());
|
||||
getSeaFloorDecorator().decorate(i, j,
|
||||
realX, realZ, output, biome, height + 1,
|
||||
surfaceFluidHeight + 1);
|
||||
getDimension().getFluidHeight() + 1);
|
||||
}
|
||||
|
||||
if (shouldDecorateShoreline(riverSurface, height)) {
|
||||
if (height == getDimension().getFluidHeight()) {
|
||||
getShoreLineDecorator().decorate(i, j,
|
||||
realX, Math.round(x + i + 1), Math.round(x + i - 1),
|
||||
realZ, Math.round(z + j + 1), Math.round(z + j - 1),
|
||||
|
||||
@@ -88,10 +88,13 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<PlatformBl
|
||||
IrisData data = getData();
|
||||
IrisComplex complex = getComplex();
|
||||
RNG localRng = rng;
|
||||
int fluidHeight = dimension.getFluidHeight();
|
||||
int clampedFluidHeight = Math.min(chunkHeight, fluidHeight);
|
||||
boolean bedrockEnabled = dimension.isBedrock();
|
||||
boolean hideOres = dimension.isHideOresForHiddenOre();
|
||||
ChunkedDataCache<IrisBiome> biomeCache = context.getBiome();
|
||||
ChunkedDataCache<IrisRegion> regionCache = context.getRegion();
|
||||
ChunkedDataCache<PlatformBlockState> fluidCache = context.getFluid();
|
||||
ChunkedDataCache<PlatformBlockState> rockCache = context.getRock();
|
||||
int realX = xf + x;
|
||||
UpperDimensionContext upperContext = getEngine().getUpperContext();
|
||||
@@ -107,17 +110,13 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<PlatformBl
|
||||
IrisBiome biome = biomeCache.get(xf, zf);
|
||||
IrisRegion region = regionCache.get(xf, zf);
|
||||
int he = Math.min(chunkHeight, context.getRoundedHeight(xf, zf));
|
||||
int surfaceFluidHeight = Math.min(
|
||||
chunkHeight,
|
||||
(int) Math.round(complex.getRiverWaterSurfaceStream().get(realX, realZ))
|
||||
);
|
||||
int hf = Math.max(surfaceFluidHeight, he);
|
||||
int hf = Math.max(clampedFluidHeight, he);
|
||||
if (hf < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int topY = Math.min(hf, chunkHeight - 1);
|
||||
PlatformBlockState fluid = complex.resolveSurfaceFluid(realX, realZ);
|
||||
PlatformBlockState fluid = fluidCache.get(xf, zf);
|
||||
PlatformBlockState rock = rockCache.get(xf, zf);
|
||||
PlatformBlockState mappedSurfaceBlock = complex.getImageMapRuntime().sampleSurfaceBlock(realX, realZ);
|
||||
KList<IrisOreGenerator> biomeSurfaceOres = hideOres ? null : biome.getSurfaceOreGenerators();
|
||||
|
||||
@@ -40,17 +40,16 @@ public class IrisShoreLineDecorator extends IrisEngineDecorator {
|
||||
@Override
|
||||
public void decorate(int x, int z, int realX, int realX1, int realX_1, int realZ, int realZ1, int realZ_1,
|
||||
Hunk<PlatformBlockState> data, IrisBiome biome, int height, int max) {
|
||||
double localFluidHeight = getComplex().getRiverWaterSurfaceStream().get(realX, realZ);
|
||||
if (height != Math.round(localFluidHeight)) {
|
||||
if (height != getDimension().getFluidHeight()) {
|
||||
return;
|
||||
}
|
||||
|
||||
double complexFluidHeight = getComplex().getFluidHeight();
|
||||
ProceduralStream<Double> heightStream = getComplex().getHeightStream();
|
||||
ProceduralStream<Double> fluidStream = getComplex().getRiverWaterSurfaceStream();
|
||||
if (Math.round(heightStream.get(realX1, realZ)) >= Math.round(fluidStream.get(realX1, realZ))
|
||||
&& Math.round(heightStream.get(realX_1, realZ)) >= Math.round(fluidStream.get(realX_1, realZ))
|
||||
&& Math.round(heightStream.get(realX, realZ1)) >= Math.round(fluidStream.get(realX, realZ1))
|
||||
&& Math.round(heightStream.get(realX, realZ_1)) >= Math.round(fluidStream.get(realX, realZ_1))) {
|
||||
if (Math.round(heightStream.get(realX1, realZ)) >= complexFluidHeight
|
||||
&& Math.round(heightStream.get(realX_1, realZ)) >= complexFluidHeight
|
||||
&& Math.round(heightStream.get(realX, realZ1)) >= complexFluidHeight
|
||||
&& Math.round(heightStream.get(realX, realZ_1)) >= complexFluidHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ public class IrisSurfaceDecorator extends IrisEngineDecorator {
|
||||
@BlockCoordinates
|
||||
public void decorate(int x, int z, int realX, int realX1, int realX_1, int realZ, int realZ1, int realZ_1,
|
||||
Hunk<PlatformBlockState> data, IrisBiome biome, InferredType inferredType, int height, int max) {
|
||||
int fluidHeight = (int) Math.round(getComplex().getRiverWaterSurfaceStream().get(realX, realZ));
|
||||
int fluidHeight = getDimension().getFluidHeight();
|
||||
if (inferredType == InferredType.SHORE && height < fluidHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -42,8 +42,6 @@ import art.arcane.iris.engine.object.IrisPosition;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.engine.object.IrisStructure;
|
||||
import art.arcane.iris.engine.object.IrisWorld;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrologyStorage;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.iris.spi.PlatformBiome;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
@@ -246,14 +244,6 @@ public interface Engine extends DataProvider, Fallible, BlockUpdater, Renderer,
|
||||
|
||||
@BlockCoordinates
|
||||
default IrisBiome getCaveOrMantleBiome(int x, int y, int z) {
|
||||
RiverCaveHydrology hydrology = RiverCaveHydrologyStorage.getIfPresent(
|
||||
getMantle().getMantle(), x, y, z);
|
||||
if (hydrology != null && !hydrology.floodedBiomeKey().isEmpty()) {
|
||||
IrisBiome biome = getData().getBiomeLoader().load(hydrology.floodedBiomeKey());
|
||||
if (biome != null) {
|
||||
return biome;
|
||||
}
|
||||
}
|
||||
MatterCavern m = getMantle().getMantle().get(x, y, z, MatterCavern.class);
|
||||
|
||||
if (m != null && m.getCustomBiome() != null && !m.getCustomBiome().isEmpty()) {
|
||||
|
||||
+1
-4
@@ -157,10 +157,7 @@ public final class NativeStructurePlacementPlanner {
|
||||
}
|
||||
|
||||
static boolean isSubmerged(Engine engine, int blockX, int blockZ) {
|
||||
int localFluidHeight = engine.getComplex() == null
|
||||
? engine.getDimension().getFluidHeight()
|
||||
: (int) Math.round(engine.getComplex().getRiverWaterSurfaceStream().get(blockX, blockZ));
|
||||
return engine.getHeight(blockX, blockZ, true) < localFluidHeight;
|
||||
return engine.getHeight(blockX, blockZ, true) < engine.getDimension().getFluidHeight();
|
||||
}
|
||||
|
||||
private static int comparePlacementPriority(IrisStructurePlacement left, IrisStructurePlacement right) {
|
||||
|
||||
+1
-26
@@ -3,8 +3,6 @@ package art.arcane.iris.engine.framework;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisStructureAnchorMode;
|
||||
import art.arcane.iris.engine.object.IrisStructurePlacement;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrologyStorage;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
|
||||
@@ -288,12 +286,10 @@ public final class StructureCaveAnchorResolver {
|
||||
int mantleY,
|
||||
int blockZ
|
||||
) {
|
||||
RiverCaveHydrology hydrology = hydrologyAt(engine, blockX, mantleY, blockZ);
|
||||
MatterCavern cavern = cavernAt(engine, blockX, mantleY, blockZ);
|
||||
return acceptsAnchorFluid(
|
||||
placement.isUnderwater(),
|
||||
cavern,
|
||||
hydrology,
|
||||
mantleY,
|
||||
engine.getDimension().getCaveLavaHeight());
|
||||
}
|
||||
@@ -304,19 +300,6 @@ public final class StructureCaveAnchorResolver {
|
||||
int mantleY,
|
||||
int defaultLavaHeight
|
||||
) {
|
||||
return acceptsAnchorFluid(underwater, cavern, null, mantleY, defaultLavaHeight);
|
||||
}
|
||||
|
||||
static boolean acceptsAnchorFluid(
|
||||
boolean underwater,
|
||||
MatterCavern cavern,
|
||||
RiverCaveHydrology hydrology,
|
||||
int mantleY,
|
||||
int defaultLavaHeight
|
||||
) {
|
||||
if (hydrology != null && hydrology.protectsPlacement()) {
|
||||
return false;
|
||||
}
|
||||
if (cavern == null || !cavern.isCavern()) {
|
||||
return false;
|
||||
}
|
||||
@@ -330,15 +313,7 @@ public final class StructureCaveAnchorResolver {
|
||||
}
|
||||
|
||||
private static MatterCavern cavernAt(Engine engine, int blockX, int mantleY, int blockZ) {
|
||||
MatterCavern baseline = engine.getMantle().getMantle()
|
||||
.get(blockX, mantleY, blockZ, MatterCavern.class);
|
||||
RiverCaveHydrology hydrology = hydrologyAt(engine, blockX, mantleY, blockZ);
|
||||
return hydrology == null ? baseline : hydrology.asCavern();
|
||||
}
|
||||
|
||||
private static RiverCaveHydrology hydrologyAt(Engine engine, int blockX, int mantleY, int blockZ) {
|
||||
return RiverCaveHydrologyStorage.getIfPresent(
|
||||
engine.getMantle().getMantle(), blockX, mantleY, blockZ);
|
||||
return engine.getMantle().getMantle().get(blockX, mantleY, blockZ, MatterCavern.class);
|
||||
}
|
||||
|
||||
static int toMantleY(int worldY, int worldMinHeight) {
|
||||
|
||||
@@ -21,6 +21,7 @@ import art.arcane.iris.util.common.scheduling.J;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.iris.util.common.data.IrisCustomData;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -111,7 +112,7 @@ public class WorldObjectPlacer implements IObjectPlacer {
|
||||
|
||||
@Override
|
||||
public boolean isCarved(int x, int y, int z) {
|
||||
return mantle.isCarved(x, y, z);
|
||||
return mantle.getMantle().get(x, y, z, MatterCavern.class) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -24,9 +24,6 @@ import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisBiomeGeneratorLink;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.engine.river.RiverSample;
|
||||
import art.arcane.iris.engine.river.RiverSection;
|
||||
import art.arcane.iris.engine.river.runtime.IrisRiverRuntime;
|
||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
|
||||
import java.awt.Color;
|
||||
@@ -43,12 +40,6 @@ public final class IrisRenderer {
|
||||
private static final int BLUE = new Color(45, 91, 156).getRGB();
|
||||
private static final int YELLOW = new Color(211, 164, 67).getRGB();
|
||||
private static final int GREEN = new Color(78, 137, 83).getRGB();
|
||||
private static final int RIVER_CHANNEL = new Color(48, 112, 190).getRGB();
|
||||
private static final int RIVER_MOUTH = new Color(54, 164, 205).getRGB();
|
||||
private static final int RIVER_BANK = new Color(92, 146, 78).getRGB();
|
||||
private static final int DRY_CHANNEL = new Color(171, 128, 68).getRGB();
|
||||
private static final int DRY_BANK = new Color(132, 105, 62).getRGB();
|
||||
private static final int NO_RIVER = new Color(28, 31, 38).getRGB();
|
||||
private static final int DEEP_WATER = new Color(20, 48, 92).getRGB();
|
||||
private static final int SHALLOW_WATER = new Color(50, 112, 154).getRGB();
|
||||
private static final int LOWLAND = new Color(78, 128, 76).getRGB();
|
||||
@@ -114,17 +105,9 @@ public final class IrisRenderer {
|
||||
renderHeightAtlas(pixels, resolution, sx, sz, step, renderer, cancelled);
|
||||
return image;
|
||||
}
|
||||
PixelShader shader = shader(currentType, step, studio);
|
||||
if (studio && currentType == RenderType.RIVER) {
|
||||
Arrays.fill(pixels, NO_RIVER);
|
||||
renderRiverAtlas(pixels, resolution, sx, sz, step, renderer.getComplex(), cancelled, false);
|
||||
return image;
|
||||
}
|
||||
PixelShader shader = shader(currentType, studio);
|
||||
if (studio && adaptiveStudioType(currentType)) {
|
||||
renderAdaptiveAtlas(pixels, resolution, sx, sz, step, shader, cancelled);
|
||||
if (currentType == RenderType.BIOME) {
|
||||
renderRiverAtlas(pixels, resolution, sx, sz, step, renderer.getComplex(), cancelled, true);
|
||||
}
|
||||
return image;
|
||||
}
|
||||
int groupSize = sampleGroup(step, resolution);
|
||||
@@ -150,18 +133,6 @@ public final class IrisRenderer {
|
||||
return image;
|
||||
}
|
||||
|
||||
public static int riverColor(RiverSection section) {
|
||||
Objects.requireNonNull(section, "section");
|
||||
return switch (section) {
|
||||
case CHANNEL -> RIVER_CHANNEL;
|
||||
case MOUTH -> RIVER_MOUTH;
|
||||
case BANK -> RIVER_BANK;
|
||||
case DRY_CHANNEL -> DRY_CHANNEL;
|
||||
case DRY_BANK -> DRY_BANK;
|
||||
case NONE -> NO_RIVER;
|
||||
};
|
||||
}
|
||||
|
||||
public static int heightColor(double height, double maximumHeight, double fluidHeight) {
|
||||
double boundedMaximum = Math.max(1D, maximumHeight);
|
||||
double boundedHeight = clamp(height, 0D, boundedMaximum);
|
||||
@@ -189,7 +160,7 @@ public final class IrisRenderer {
|
||||
return Math.max(1, Math.min(resolution, (int) Math.floor(16D / absoluteStep)));
|
||||
}
|
||||
|
||||
private PixelShader shader(RenderType currentType, double step, boolean studio) {
|
||||
private PixelShader shader(RenderType currentType, boolean studio) {
|
||||
IrisComplex complex = renderer.getComplex();
|
||||
return switch (currentType) {
|
||||
case BIOME, DECORATOR_LOAD, OBJECT_LOAD, LAYER_LOAD -> biomeShader(
|
||||
@@ -198,8 +169,7 @@ public final class IrisRenderer {
|
||||
case BIOME_SEA -> biomeShader(complex.getSeaBiomeStream(), currentType);
|
||||
case REGION -> regionShader(complex, currentType);
|
||||
case CAVE_LAND -> biomeShader(complex.getCaveBiomeStream(), currentType);
|
||||
case HEIGHT -> heightShader(studio ? complex.getNaturalHeightStream() : complex.getHeightStream());
|
||||
case RIVER -> (double x, double z) -> riverColor(complex, x, z, step);
|
||||
case HEIGHT -> heightShader(complex.getHeightStream());
|
||||
case CONTINENT -> studio
|
||||
? continentShader(complex.getBaseBiomeStream())
|
||||
: this::continentColor;
|
||||
@@ -210,7 +180,7 @@ public final class IrisRenderer {
|
||||
return switch (type) {
|
||||
case BIOME, DECORATOR_LOAD, OBJECT_LOAD, LAYER_LOAD, BIOME_LAND, BIOME_SEA, REGION, CAVE_LAND,
|
||||
CONTINENT -> true;
|
||||
case HEIGHT, RIVER -> false;
|
||||
case HEIGHT -> false;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -254,7 +224,7 @@ public final class IrisRenderer {
|
||||
startX,
|
||||
startZ,
|
||||
step,
|
||||
complex.getNaturalHeightStream(),
|
||||
complex.getHeightStream(),
|
||||
engine.getHeight(),
|
||||
fluidHeight,
|
||||
cancelled
|
||||
@@ -267,103 +237,6 @@ public final class IrisRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
private static void renderRiverAtlas(
|
||||
int[] pixels,
|
||||
int resolution,
|
||||
double startX,
|
||||
double startZ,
|
||||
double step,
|
||||
IrisComplex complex,
|
||||
BooleanSupplier cancelled,
|
||||
boolean composite
|
||||
) {
|
||||
IrisRiverRuntime runtime = complex.getRiverRuntime();
|
||||
if (runtime == null) {
|
||||
return;
|
||||
}
|
||||
int maximumPixels = Math.max(1, Math.min(16, (int) Math.floor(64D / step)));
|
||||
int blockPixels = Integer.highestOneBit(maximumPixels);
|
||||
for (int pixelZ = 0; pixelZ < resolution; pixelZ += blockPixels) {
|
||||
int height = Math.min(blockPixels, resolution - pixelZ);
|
||||
for (int pixelX = 0; pixelX < resolution; pixelX += blockPixels) {
|
||||
renderRiverBlock(
|
||||
pixels,
|
||||
resolution,
|
||||
startX,
|
||||
startZ,
|
||||
step,
|
||||
runtime,
|
||||
cancelled,
|
||||
composite,
|
||||
pixelX,
|
||||
pixelZ,
|
||||
Math.min(blockPixels, resolution - pixelX),
|
||||
height
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void renderRiverBlock(
|
||||
int[] pixels,
|
||||
int resolution,
|
||||
double startX,
|
||||
double startZ,
|
||||
double step,
|
||||
IrisRiverRuntime runtime,
|
||||
BooleanSupplier cancelled,
|
||||
boolean composite,
|
||||
int pixelX,
|
||||
int pixelZ,
|
||||
int width,
|
||||
int height
|
||||
) {
|
||||
checkCancelled(cancelled);
|
||||
RiverSample sample = runtime.sampleFootprint(
|
||||
startX + pixelX * step,
|
||||
startZ + pixelZ * step,
|
||||
startX + (pixelX + width) * step,
|
||||
startZ + (pixelZ + height) * step
|
||||
);
|
||||
if (!sample.present()) {
|
||||
return;
|
||||
}
|
||||
if (width == 1 && height == 1) {
|
||||
int index = pixelZ * resolution + pixelX;
|
||||
int color = riverColor(sample.section());
|
||||
pixels[index] = composite ? riverCompositeColor(pixels[index], sample.section(), color) : color;
|
||||
return;
|
||||
}
|
||||
int leftWidth = Math.max(1, width / 2);
|
||||
int rightWidth = width - leftWidth;
|
||||
int topHeight = Math.max(1, height / 2);
|
||||
int bottomHeight = height - topHeight;
|
||||
renderRiverBlock(pixels, resolution, startX, startZ, step, runtime, cancelled, composite,
|
||||
pixelX, pixelZ, leftWidth, topHeight);
|
||||
if (rightWidth > 0) {
|
||||
renderRiverBlock(pixels, resolution, startX, startZ, step, runtime, cancelled, composite,
|
||||
pixelX + leftWidth, pixelZ, rightWidth, topHeight);
|
||||
}
|
||||
if (bottomHeight > 0) {
|
||||
renderRiverBlock(pixels, resolution, startX, startZ, step, runtime, cancelled, composite,
|
||||
pixelX, pixelZ + topHeight, leftWidth, bottomHeight);
|
||||
if (rightWidth > 0) {
|
||||
renderRiverBlock(pixels, resolution, startX, startZ, step, runtime, cancelled, composite,
|
||||
pixelX + leftWidth, pixelZ + topHeight, rightWidth, bottomHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int riverCompositeColor(int base, RiverSection section, int river) {
|
||||
return switch (section) {
|
||||
case CHANNEL, MOUTH -> river;
|
||||
case BANK -> blend(base, river, 0.68D);
|
||||
case DRY_CHANNEL -> blend(base, river, 0.88D);
|
||||
case DRY_BANK -> blend(base, river, 0.58D);
|
||||
case NONE -> base;
|
||||
};
|
||||
}
|
||||
|
||||
private PixelShader biomeShader(ProceduralStream<IrisBiome> stream, RenderType currentType) {
|
||||
IdentityHashMap<IrisBiome, Integer> colors = new IdentityHashMap<>();
|
||||
return (double x, double z) -> {
|
||||
@@ -398,22 +271,6 @@ public final class IrisRenderer {
|
||||
return (double x, double z) -> heightColor(stream.getDouble(x, z), maximumHeight, fluidHeight);
|
||||
}
|
||||
|
||||
private int riverColor(IrisComplex complex, double x, double z, double step) {
|
||||
IrisRiverRuntime runtime = complex.getRiverRuntime();
|
||||
if (runtime == null) {
|
||||
return riverColor(RiverSection.NONE);
|
||||
}
|
||||
double endX = x + step;
|
||||
double endZ = z + step;
|
||||
RiverSample sample = runtime.sampleFootprint(
|
||||
StrictMath.min(x, endX),
|
||||
StrictMath.min(z, endZ),
|
||||
StrictMath.max(x, endX),
|
||||
StrictMath.max(z, endZ)
|
||||
);
|
||||
return riverColor(sample.section());
|
||||
}
|
||||
|
||||
private int continentColor(double x, double z) {
|
||||
IrisBiome biome = renderer.getBiome(
|
||||
(int) Math.round(x),
|
||||
|
||||
@@ -19,5 +19,5 @@
|
||||
package art.arcane.iris.engine.framework.render;
|
||||
|
||||
public enum RenderType {
|
||||
BIOME, BIOME_LAND, BIOME_SEA, REGION, CAVE_LAND, RIVER, HEIGHT, OBJECT_LOAD, DECORATOR_LOAD, CONTINENT, LAYER_LOAD
|
||||
BIOME, BIOME_LAND, BIOME_SEA, REGION, CAVE_LAND, HEIGHT, OBJECT_LOAD, DECORATOR_LOAD, CONTINENT, LAYER_LOAD
|
||||
}
|
||||
|
||||
@@ -27,8 +27,6 @@ import art.arcane.iris.engine.framework.EngineTarget;
|
||||
import art.arcane.iris.engine.framework.TreeBlockMaterial;
|
||||
import art.arcane.iris.engine.mantle.components.MantleObjectComponent;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrologyStorage;
|
||||
import art.arcane.iris.engine.object.IrisPosition;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.iris.util.common.data.B;
|
||||
@@ -104,7 +102,7 @@ public interface EngineMantle extends MatterGenerator {
|
||||
}
|
||||
|
||||
default int getHighest(int x, int z, IrisData data, boolean ignoreFluid) {
|
||||
return ignoreFluid ? trueHeight(x, z) : Math.max(trueHeight(x, z), getFluidHeight(x, z));
|
||||
return ignoreFluid ? trueHeight(x, z) : Math.max(trueHeight(x, z), getEngine().getDimension().getFluidHeight());
|
||||
}
|
||||
|
||||
default int trueHeight(int x, int z) {
|
||||
@@ -112,10 +110,6 @@ public interface EngineMantle extends MatterGenerator {
|
||||
}
|
||||
|
||||
default boolean isCarved(int x, int h, int z) {
|
||||
RiverCaveHydrology hydrology = RiverCaveHydrologyStorage.getIfPresent(getMantle(), x, h, z);
|
||||
if (hydrology != null) {
|
||||
return hydrology.carves();
|
||||
}
|
||||
return getMantle().get(x, h, z, MatterCavern.class) != null;
|
||||
}
|
||||
|
||||
@@ -131,17 +125,13 @@ public interface EngineMantle extends MatterGenerator {
|
||||
}
|
||||
|
||||
default boolean isUnderwater(int x, int z) {
|
||||
return getHighest(x, z, true) < getFluidHeight(x, z);
|
||||
return getHighest(x, z, true) <= getFluidHeight();
|
||||
}
|
||||
|
||||
default int getFluidHeight() {
|
||||
return getEngine().getDimension().getFluidHeight();
|
||||
}
|
||||
|
||||
default int getFluidHeight(int x, int z) {
|
||||
return (int) Math.round(getComplex().getRiverWaterSurfaceStream().get(x, z));
|
||||
}
|
||||
|
||||
default boolean isDebugSmartBore() {
|
||||
return getEngine().getDimension().isDebugSmartBore();
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ import art.arcane.iris.engine.object.IObjectPlacer;
|
||||
import art.arcane.iris.engine.object.IrisGeneratorStyle;
|
||||
import art.arcane.iris.engine.object.IrisPosition;
|
||||
import art.arcane.iris.engine.object.TileData;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
|
||||
import art.arcane.volmlib.util.collection.KSet;
|
||||
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
||||
import art.arcane.volmlib.util.function.Function3;
|
||||
@@ -207,10 +206,6 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable {
|
||||
if (chunk == null) return;
|
||||
|
||||
Matter matter = chunk.getOrCreate(y >> 4);
|
||||
if ((t instanceof PlatformBlockState || t instanceof MatterCavern)
|
||||
&& hasProtectedHydrology(matter, x, y, z)) {
|
||||
return;
|
||||
}
|
||||
if (t instanceof PlatformBlockState) {
|
||||
clearDeferredPlacement(matter, x, y, z);
|
||||
}
|
||||
@@ -236,9 +231,6 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable {
|
||||
}
|
||||
|
||||
Matter matter = chunk.getOrCreate(y >> 4);
|
||||
if (hasProtectedHydrology(matter, x, y, z)) {
|
||||
return false;
|
||||
}
|
||||
MatterCavern existing = matter.<MatterCavern>slice(MatterCavern.class).get(x & 15, y & 15, z & 15);
|
||||
if (existing != null) {
|
||||
return false;
|
||||
@@ -259,9 +251,6 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable {
|
||||
}
|
||||
|
||||
Matter matter = chunk.getOrCreate(y >> 4);
|
||||
if (hasProtectedHydrology(matter, x, y, z)) {
|
||||
return false;
|
||||
}
|
||||
if (matter.hasSlice(PlatformBlockState.class)) {
|
||||
matter.<PlatformBlockState>getSlice(PlatformBlockState.class).set(x & 15, y & 15, z & 15, null);
|
||||
}
|
||||
@@ -284,9 +273,6 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable {
|
||||
+ x + "," + y + "," + z);
|
||||
}
|
||||
Matter matter = chunk.getOrCreate(y >> 4);
|
||||
if (hasProtectedHydrology(matter, x, y, z)) {
|
||||
return;
|
||||
}
|
||||
if (matter.hasSlice(PlatformBlockState.class)) {
|
||||
matter.<PlatformBlockState>getSlice(PlatformBlockState.class).set(x & 15, y & 15, z & 15, null);
|
||||
}
|
||||
@@ -310,9 +296,6 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable {
|
||||
if (matter == null) {
|
||||
return;
|
||||
}
|
||||
if (hasProtectedHydrology(matter, x, y, z)) {
|
||||
return;
|
||||
}
|
||||
if (matter.hasSlice(PlatformBlockState.class)) {
|
||||
matter.<PlatformBlockState>getSlice(PlatformBlockState.class).set(x & 15, y & 15, z & 15, null);
|
||||
}
|
||||
@@ -370,22 +353,9 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable {
|
||||
if (matter == null || !matter.hasSlice(type)) {
|
||||
return;
|
||||
}
|
||||
if ((type == PlatformBlockState.class || type == MatterCavern.class)
|
||||
&& hasProtectedHydrology(matter, x, y, z)) {
|
||||
return;
|
||||
}
|
||||
matter.getSlice(type).set(x & 15, y & 15, z & 15, null);
|
||||
}
|
||||
|
||||
private static boolean hasProtectedHydrology(Matter matter, int x, int y, int z) {
|
||||
if (!matter.hasSlice(RiverCaveHydrology.class)) {
|
||||
return false;
|
||||
}
|
||||
RiverCaveHydrology hydrology = matter.<RiverCaveHydrology>getSlice(RiverCaveHydrology.class)
|
||||
.get(x & 15, y & 15, z & 15);
|
||||
return hydrology != null && hydrology.protectsPlacement();
|
||||
}
|
||||
|
||||
private static void clearDeferredPlacement(Matter matter, int x, int y, int z) {
|
||||
if (matter.hasSlice(Identifier.class)) {
|
||||
matter.<Identifier>getSlice(Identifier.class).set(x & 15, y & 15, z & 15, null);
|
||||
@@ -480,10 +450,6 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable {
|
||||
|
||||
@Override
|
||||
public boolean isCarved(int x, int y, int z) {
|
||||
RiverCaveHydrology hydrology = getDataIfPresent(x, y, z, RiverCaveHydrology.class);
|
||||
if (hydrology != null) {
|
||||
return hydrology.carves();
|
||||
}
|
||||
return getDataIfPresent(x, y, z, MatterCavern.class) != null;
|
||||
}
|
||||
|
||||
@@ -508,28 +474,14 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable {
|
||||
}
|
||||
|
||||
Matter matter = chunk.get(section);
|
||||
if (matter == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
MatterSlice<MatterCavern> cavernSlice = matter.hasSlice(MatterCavern.class)
|
||||
? matter.getSlice(MatterCavern.class)
|
||||
: null;
|
||||
MatterSlice<RiverCaveHydrology> hydrologySlice = matter.hasSlice(RiverCaveHydrology.class)
|
||||
? matter.getSlice(RiverCaveHydrology.class)
|
||||
: null;
|
||||
if (cavernSlice == null && hydrologySlice == null) {
|
||||
if (matter == null || !matter.hasSlice(MatterCavern.class)) {
|
||||
continue;
|
||||
}
|
||||
MatterSlice<MatterCavern> slice = matter.getSlice(MatterCavern.class);
|
||||
int sectionBaseY = section << 4;
|
||||
int sectionMaxY = Math.min(cappedHeight, sectionBaseY + 16);
|
||||
for (int y = sectionBaseY; y < sectionMaxY; y++) {
|
||||
RiverCaveHydrology hydrology = hydrologySlice == null
|
||||
? null
|
||||
: hydrologySlice.get(localX, y & 15, localZ);
|
||||
if (hydrology != null) {
|
||||
carvedColumn[y] = hydrology.carves() ? (byte) 1 : 0;
|
||||
} else if (cavernSlice != null && cavernSlice.get(localX, y & 15, localZ) != null) {
|
||||
if (slice.get(localX, y & 15, localZ) != null) {
|
||||
carvedColumn[y] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,14 +61,15 @@ public final class CarveOrphanSweep {
|
||||
int[] surfaceHeights,
|
||||
int maxSurfaceBreakDepth,
|
||||
int worldCeilingY,
|
||||
long[] surfaceFluidBoundaries
|
||||
int[] surfaceFluidBoundaryStartY,
|
||||
int fluidHeight
|
||||
) {
|
||||
if (chunk == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return sweep(surfaceHeights, maxSurfaceBreakDepth, 0, worldCeilingY,
|
||||
new MantleCarveAccess(chunk, surfaceFluidBoundaries));
|
||||
new MantleCarveAccess(chunk, surfaceFluidBoundaryStartY, fluidHeight));
|
||||
}
|
||||
|
||||
public static int sweep(int[] surfaceHeights, int maxSurfaceBreakDepth, int worldFloorY, int worldCeilingY, CarveAccess access) {
|
||||
@@ -228,13 +229,15 @@ public final class CarveOrphanSweep {
|
||||
|
||||
private static final class MantleCarveAccess implements CarveAccess {
|
||||
private final MantleChunk<Matter> chunk;
|
||||
private final long[] surfaceFluidBoundaries;
|
||||
private final int[] surfaceFluidBoundaryStartY;
|
||||
private final int fluidHeight;
|
||||
private MatterSlice<MatterCavern> cachedSlice;
|
||||
private int cachedSectionIndex = -1;
|
||||
|
||||
private MantleCarveAccess(MantleChunk<Matter> chunk, long[] surfaceFluidBoundaries) {
|
||||
private MantleCarveAccess(MantleChunk<Matter> chunk, int[] surfaceFluidBoundaryStartY, int fluidHeight) {
|
||||
this.chunk = chunk;
|
||||
this.surfaceFluidBoundaries = surfaceFluidBoundaries;
|
||||
this.surfaceFluidBoundaryStartY = surfaceFluidBoundaryStartY;
|
||||
this.fluidHeight = fluidHeight;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -258,7 +261,7 @@ public final class CarveOrphanSweep {
|
||||
@Override
|
||||
public boolean isProtected(int localX, int y, int localZ) {
|
||||
int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ);
|
||||
return SurfaceFluidBoundaryPlan.protects(surfaceFluidBoundaries, columnIndex, y);
|
||||
return SurfaceFluidBoundaryPlan.protects(surfaceFluidBoundaryStartY, columnIndex, y, fluidHeight);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-9
@@ -23,7 +23,6 @@ import art.arcane.iris.engine.data.cache.Cache;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.IObjectPlacer;
|
||||
import art.arcane.iris.engine.object.TileData;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.util.common.data.B;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
@@ -72,12 +71,6 @@ final class CaveObjectPlacementTransaction implements IObjectPlacer {
|
||||
discard();
|
||||
return CommitResult.REJECTED_BOUNDS;
|
||||
}
|
||||
RiverCaveHydrology hydrology = delegate.getData(
|
||||
mutation.x(), mutation.y(), mutation.z(), RiverCaveHydrology.class);
|
||||
if (hydrology != null && hydrology.protectsPlacement()) {
|
||||
discard();
|
||||
return CommitResult.REJECTED_HYDROLOGY;
|
||||
}
|
||||
}
|
||||
|
||||
for (BufferedMutation mutation : mutations) {
|
||||
@@ -252,8 +245,7 @@ final class CaveObjectPlacementTransaction implements IObjectPlacer {
|
||||
enum CommitResult {
|
||||
COMMITTED,
|
||||
EMPTY,
|
||||
REJECTED_BOUNDS,
|
||||
REJECTED_HYDROLOGY
|
||||
REJECTED_BOUNDS
|
||||
}
|
||||
|
||||
private interface BufferedMutation {
|
||||
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
package art.arcane.iris.engine.mantle.components;
|
||||
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.engine.object.IrisGeneratorStyle;
|
||||
import art.arcane.iris.engine.object.NoiseStyle;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveGrottoShape;
|
||||
import art.arcane.iris.engine.river.cave.RiverCavePlannerSettings;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveSource;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
|
||||
final class ConfiguredRiverGrottoShape implements RiverCaveGrottoShape {
|
||||
private static final long SHAPE_SALT = 0x3C6EF372FE94F82BL;
|
||||
private static final long WARP_X_SALT = 0xA54FF53A5F1D36F1L;
|
||||
private static final long WARP_Y_SALT = 0x510E527FADE682D1L;
|
||||
private static final long WARP_Z_SALT = 0x9B05688C2B3E6C1FL;
|
||||
|
||||
private final CNG shape;
|
||||
private final CNG warpX;
|
||||
private final CNG warpY;
|
||||
private final CNG warpZ;
|
||||
private final double warpStrength;
|
||||
private final double boundaryVariation;
|
||||
|
||||
ConfiguredRiverGrottoShape(
|
||||
long seed,
|
||||
IrisData data,
|
||||
IrisGeneratorStyle shapeStyle,
|
||||
IrisGeneratorStyle warpStyle,
|
||||
double warpStrength,
|
||||
double boundaryVariation
|
||||
) {
|
||||
IrisGeneratorStyle resolvedShape = shapeStyle == null
|
||||
? new IrisGeneratorStyle(NoiseStyle.FLAT)
|
||||
: shapeStyle;
|
||||
IrisGeneratorStyle resolvedWarp = warpStyle == null
|
||||
? new IrisGeneratorStyle(NoiseStyle.FLAT)
|
||||
: warpStyle;
|
||||
shape = resolvedShape.createNoCache(new RNG(seed ^ SHAPE_SALT), data);
|
||||
warpX = resolvedWarp.createNoCache(new RNG(seed ^ WARP_X_SALT), data);
|
||||
warpY = resolvedWarp.createNoCache(new RNG(seed ^ WARP_Y_SALT), data);
|
||||
warpZ = resolvedWarp.createNoCache(new RNG(seed ^ WARP_Z_SALT), data);
|
||||
this.warpStrength = Math.max(0D, warpStrength);
|
||||
this.boundaryVariation = Math.max(0D, Math.min(0.75D, boundaryVariation));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(
|
||||
RiverCaveSource source,
|
||||
RiverCavePlannerSettings settings,
|
||||
int offsetX,
|
||||
int offsetY,
|
||||
int offsetZ
|
||||
) {
|
||||
double worldX = source.target().x() + offsetX;
|
||||
double worldY = source.target().y() + offsetY;
|
||||
double worldZ = source.target().z() + offsetZ;
|
||||
double warpedX = offsetX + warpX.fitDouble(-warpStrength, warpStrength, worldX, worldY, worldZ);
|
||||
double warpedY = offsetY + warpY.fitDouble(-warpStrength, warpStrength, worldY, worldZ, worldX);
|
||||
double warpedZ = offsetZ + warpZ.fitDouble(-warpStrength, warpStrength, worldZ, worldX, worldY);
|
||||
double horizontalRadius = settings.grottoHorizontalRadius();
|
||||
double verticalRadius = settings.grottoVerticalRadius();
|
||||
double normalized = (warpedX * warpedX / (horizontalRadius * horizontalRadius))
|
||||
+ (warpedY * warpedY / (verticalRadius * verticalRadius))
|
||||
+ (warpedZ * warpedZ / (horizontalRadius * horizontalRadius));
|
||||
double boundary = shape.fitDouble(-boundaryVariation, boundaryVariation, worldX, worldY, worldZ);
|
||||
return normalized <= 1D + boundary;
|
||||
}
|
||||
}
|
||||
@@ -199,7 +199,7 @@ public class IrisCaveCarver3D {
|
||||
double thresholdPenalty,
|
||||
IrisRange worldYRange,
|
||||
int[] precomputedSurfaceHeights,
|
||||
long[] surfaceFluidBoundaries,
|
||||
int[] surfaceFluidBoundaryStartY,
|
||||
IrisRange overrideVerticalRange,
|
||||
CaveFluidSupportPlan fluidSupportPlan
|
||||
) {
|
||||
@@ -324,7 +324,7 @@ public class IrisCaveCarver3D {
|
||||
surfaceBreakThresholdBoost,
|
||||
columnMaxY,
|
||||
fluidMaxY,
|
||||
surfaceFluidBoundaries,
|
||||
surfaceFluidBoundaryStartY,
|
||||
surfaceBreakFloorY,
|
||||
surfaceBreakColumn,
|
||||
columnThreshold,
|
||||
@@ -347,7 +347,7 @@ public class IrisCaveCarver3D {
|
||||
surfaceBreakThresholdBoost,
|
||||
columnMaxY,
|
||||
fluidMaxY,
|
||||
surfaceFluidBoundaries,
|
||||
surfaceFluidBoundaryStartY,
|
||||
surfaceBreakFloorY,
|
||||
surfaceBreakColumn,
|
||||
columnThreshold,
|
||||
@@ -373,7 +373,7 @@ public class IrisCaveCarver3D {
|
||||
surfaceBreakThresholdBoost,
|
||||
columnMaxY,
|
||||
fluidMaxY,
|
||||
surfaceFluidBoundaries,
|
||||
surfaceFluidBoundaryStartY,
|
||||
surfaceBreakFloorY,
|
||||
surfaceBreakColumn,
|
||||
columnThreshold,
|
||||
@@ -397,7 +397,7 @@ public class IrisCaveCarver3D {
|
||||
surfaceBreakThresholdBoost,
|
||||
columnMaxY,
|
||||
fluidMaxY,
|
||||
surfaceFluidBoundaries,
|
||||
surfaceFluidBoundaryStartY,
|
||||
surfaceBreakFloorY,
|
||||
surfaceBreakColumn,
|
||||
columnThreshold,
|
||||
@@ -428,7 +428,7 @@ public class IrisCaveCarver3D {
|
||||
double surfaceBreakThresholdBoost,
|
||||
int[] columnMaxY,
|
||||
int[] fluidMaxY,
|
||||
long[] surfaceFluidBoundaries,
|
||||
int[] surfaceFluidBoundaryStartY,
|
||||
int[] surfaceBreakFloorY,
|
||||
boolean[] surfaceBreakColumn,
|
||||
double[] columnThreshold,
|
||||
@@ -484,7 +484,7 @@ public class IrisCaveCarver3D {
|
||||
}
|
||||
|
||||
int columnIndex = activeColumnIndices[activeIndex];
|
||||
if (SurfaceFluidBoundaryPlan.protects(surfaceFluidBoundaries, columnIndex, y)) {
|
||||
if (SurfaceFluidBoundaryPlan.protects(surfaceFluidBoundaryStartY, columnIndex, y, fluidHeight)) {
|
||||
continue;
|
||||
}
|
||||
planeColumnIndices[planeCount] = columnIndex;
|
||||
@@ -560,7 +560,7 @@ public class IrisCaveCarver3D {
|
||||
double surfaceBreakThresholdBoost,
|
||||
int[] columnMaxY,
|
||||
int[] fluidMaxY,
|
||||
long[] surfaceFluidBoundaries,
|
||||
int[] surfaceFluidBoundaryStartY,
|
||||
int[] surfaceBreakFloorY,
|
||||
boolean[] surfaceBreakColumn,
|
||||
double[] columnThreshold,
|
||||
@@ -622,7 +622,7 @@ public class IrisCaveCarver3D {
|
||||
}
|
||||
|
||||
int columnIndex = activeColumnIndices[activeIndex];
|
||||
if (SurfaceFluidBoundaryPlan.protects(surfaceFluidBoundaries, columnIndex, y)) {
|
||||
if (SurfaceFluidBoundaryPlan.protects(surfaceFluidBoundaryStartY, columnIndex, y, fluidHeight)) {
|
||||
continue;
|
||||
}
|
||||
planeColumnIndices[planeCount] = columnIndex;
|
||||
@@ -720,7 +720,7 @@ public class IrisCaveCarver3D {
|
||||
double surfaceBreakThresholdBoost,
|
||||
int[] columnMaxY,
|
||||
int[] fluidMaxY,
|
||||
long[] surfaceFluidBoundaries,
|
||||
int[] surfaceFluidBoundaryStartY,
|
||||
int[] surfaceBreakFloorY,
|
||||
boolean[] surfaceBreakColumn,
|
||||
double[] columnThreshold,
|
||||
@@ -822,7 +822,7 @@ public class IrisCaveCarver3D {
|
||||
}
|
||||
|
||||
int index = tileIndices[columnIndex];
|
||||
if (SurfaceFluidBoundaryPlan.protects(surfaceFluidBoundaries, index, yy)) {
|
||||
if (SurfaceFluidBoundaryPlan.protects(surfaceFluidBoundaryStartY, index, yy, fluidHeight)) {
|
||||
continue;
|
||||
}
|
||||
double localThreshold = passThreshold[index];
|
||||
@@ -870,7 +870,7 @@ public class IrisCaveCarver3D {
|
||||
double surfaceBreakThresholdBoost,
|
||||
int[] columnMaxY,
|
||||
int[] fluidMaxY,
|
||||
long[] surfaceFluidBoundaries,
|
||||
int[] surfaceFluidBoundaryStartY,
|
||||
int[] surfaceBreakFloorY,
|
||||
boolean[] surfaceBreakColumn,
|
||||
double[] columnThreshold,
|
||||
@@ -909,7 +909,7 @@ public class IrisCaveCarver3D {
|
||||
double density = sampleDensityOptimized(scratch, x, y, z);
|
||||
int carveMaxY = Math.min(columnTopY, y + sampleStep - 1);
|
||||
for (int yy = y; yy <= carveMaxY; yy++) {
|
||||
if (SurfaceFluidBoundaryPlan.protects(surfaceFluidBoundaries, index, yy)) {
|
||||
if (SurfaceFluidBoundaryPlan.protects(surfaceFluidBoundaryStartY, index, yy, fluidHeight)) {
|
||||
continue;
|
||||
}
|
||||
double localThreshold = threshold;
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ public class IrisStructureComponent extends IrisMantleComponent {
|
||||
private static final MatterCavern CARVE_CAVERN = new MatterCavern(true, "", (byte) 3);
|
||||
|
||||
public IrisStructureComponent(EngineMantle engineMantle) {
|
||||
super(engineMantle, ReservedFlag.JIGSAW, 4);
|
||||
super(engineMantle, ReservedFlag.JIGSAW, 3);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+13
-23
@@ -104,19 +104,20 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
||||
PrecisionStopwatch resolveStopwatch = PrecisionStopwatch.start();
|
||||
List<WeightedProfile> weightedProfiles = resolveWeightedProfiles(x, z, complex, resolverState);
|
||||
getEngineMantle().getEngine().getMetrics().getCarveResolve().put(resolveStopwatch.getMilliseconds());
|
||||
long[] surfaceFluidBoundaries = blendScratch.surfaceFluidBoundaries;
|
||||
int fluidHeight = getDimension().getFluidHeight();
|
||||
int[] surfaceFluidBoundaryStartY = blendScratch.surfaceFluidBoundaryStartY;
|
||||
SurfaceFluidBoundaryPlan.fill(
|
||||
chunkSurfaceHeights,
|
||||
blendScratch.fieldSurfaceHeights,
|
||||
blendScratch.fieldHasFluid,
|
||||
blendScratch.fieldFluidHeights,
|
||||
FIELD_SIZE,
|
||||
BLEND_RADIUS,
|
||||
surfaceFluidBoundaries
|
||||
fluidHeight,
|
||||
surfaceFluidBoundaryStartY
|
||||
);
|
||||
CaveFluidSupportPlan fluidSupportPlan = new CaveFluidSupportPlan();
|
||||
for (WeightedProfile weightedProfile : weightedProfiles) {
|
||||
carveProfile(weightedProfile, writer, x, z, chunkSurfaceHeights, surfaceFluidBoundaries, fluidSupportPlan);
|
||||
carveProfile(weightedProfile, writer, x, z, chunkSurfaceHeights, surfaceFluidBoundaryStartY, fluidSupportPlan);
|
||||
}
|
||||
|
||||
UpperDimensionContext upperCtx = getEngineMantle().getEngine().getUpperContext();
|
||||
@@ -131,7 +132,8 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
||||
chunkSurfaceHeights,
|
||||
maxSurfaceBreakDepth(weightedProfiles),
|
||||
writer.getMantle().getWorldHeight() - 1,
|
||||
surfaceFluidBoundaries
|
||||
surfaceFluidBoundaryStartY,
|
||||
fluidHeight
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -146,11 +148,11 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
||||
|
||||
@ChunkCoordinates
|
||||
private void carveProfile(WeightedProfile weightedProfile, MantleWriter writer, int cx, int cz,
|
||||
int[] chunkSurfaceHeights, long[] surfaceFluidBoundaries,
|
||||
int[] chunkSurfaceHeights, int[] surfaceFluidBoundaryStartY,
|
||||
CaveFluidSupportPlan fluidSupportPlan) {
|
||||
IrisCaveCarver3D carver = getCarver(weightedProfile.profile);
|
||||
carver.carve(writer, cx, cz, weightedProfile.columnWeights, MIN_WEIGHT, THRESHOLD_PENALTY,
|
||||
weightedProfile.worldYRange, chunkSurfaceHeights, surfaceFluidBoundaries, null, fluidSupportPlan);
|
||||
weightedProfile.worldYRange, chunkSurfaceHeights, surfaceFluidBoundaryStartY, null, fluidSupportPlan);
|
||||
}
|
||||
|
||||
private void carveUpperTerrain(UpperDimensionContext upperCtx, List<WeightedProfile> normalProfiles,
|
||||
@@ -473,9 +475,7 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
||||
|
||||
private void prefillProfileFieldSamples(int startX, int startZ, IrisComplex complex, BlendScratch blendScratch) {
|
||||
fillFieldHeights(complex.getHeightStream(), startX, startZ, blendScratch.fieldSurfaceHeights);
|
||||
fillFieldHeights(complex.getRiverWaterSurfaceStream(), startX, startZ, blendScratch.fieldFluidHeights);
|
||||
fillFieldFluidPresence(complex, startX, startZ, blendScratch.fieldSurfaceHeights,
|
||||
blendScratch.fieldFluidHeights, blendScratch.fieldHasFluid);
|
||||
fillFieldFluidPresence(complex.getFluidStream(), startX, startZ, blendScratch.fieldHasFluid);
|
||||
fillFieldObjects(complex.getRegionStream(), startX, startZ, blendScratch.fieldRegions);
|
||||
fillFieldObjects(complex.getTrueBiomeStream(), startX, startZ, blendScratch.fieldSurfaceBiomes);
|
||||
fillFieldObjects(complex.getCaveBiomeStream(), startX, startZ, blendScratch.fieldCaveBiomes);
|
||||
@@ -499,20 +499,11 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
||||
}
|
||||
}
|
||||
|
||||
private void fillFieldFluidPresence(
|
||||
IrisComplex complex,
|
||||
int startX,
|
||||
int startZ,
|
||||
double[] surfaceHeights,
|
||||
double[] fluidHeights,
|
||||
boolean[] target
|
||||
) {
|
||||
private void fillFieldFluidPresence(ProceduralStream<PlatformBlockState> stream, int startX, int startZ, boolean[] target) {
|
||||
for (int fieldX = 0; fieldX < FIELD_SIZE; fieldX++) {
|
||||
int worldX = startX + fieldX;
|
||||
for (int fieldZ = 0; fieldZ < FIELD_SIZE; fieldZ++) {
|
||||
int fieldIndex = (fieldX * FIELD_SIZE) + fieldZ;
|
||||
target[fieldIndex] = B.isFluid(complex.resolveSurfaceFluid(worldX, startZ + fieldZ))
|
||||
&& Math.round(surfaceHeights[fieldIndex]) < Math.round(fluidHeights[fieldIndex]);
|
||||
target[(fieldX * FIELD_SIZE) + fieldZ] = B.isFluid(stream.get(worldX, startZ + fieldZ));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -706,13 +697,12 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
||||
private final IdentityHashMap<IrisCaveProfile, Boolean> activeProfiles = new IdentityHashMap<>();
|
||||
private final List<IrisCaveProfile> profileOrder = new ArrayList<>();
|
||||
private final double[] fieldSurfaceHeights = new double[FIELD_SIZE * FIELD_SIZE];
|
||||
private final double[] fieldFluidHeights = new double[FIELD_SIZE * FIELD_SIZE];
|
||||
private final boolean[] fieldHasFluid = new boolean[FIELD_SIZE * FIELD_SIZE];
|
||||
private final long[] surfaceFluidBoundaries = new long[CHUNK_AREA];
|
||||
private final IrisRegion[] fieldRegions = new IrisRegion[FIELD_SIZE * FIELD_SIZE];
|
||||
private final IrisBiome[] fieldSurfaceBiomes = new IrisBiome[FIELD_SIZE * FIELD_SIZE];
|
||||
private final IrisBiome[] fieldCaveBiomes = new IrisBiome[FIELD_SIZE * FIELD_SIZE];
|
||||
private final int[] chunkSurfaceHeights = new int[CHUNK_AREA];
|
||||
private final int[] surfaceFluidBoundaryStartY = new int[CHUNK_AREA];
|
||||
private final double[] chunkSurfaceHeightSamples = new double[CHUNK_AREA];
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ public class MantleFloatingObjectComponent extends IrisMantleComponent {
|
||||
private static final IrisObjectRotation ROTATION_NONE = IrisObjectRotation.of(0, 0, 0);
|
||||
|
||||
public MantleFloatingObjectComponent(EngineMantle engineMantle) {
|
||||
super(engineMantle, ReservedFlag.FLOATING_OBJECT, 3);
|
||||
super(engineMantle, ReservedFlag.FLOATING_OBJECT, 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
-23
@@ -48,7 +48,6 @@ import art.arcane.iris.engine.object.IrisProceduralPlacement;
|
||||
import art.arcane.iris.engine.object.IrisProceduralTree;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.engine.object.ObjectPlaceMode;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
|
||||
import art.arcane.iris.spi.IrisLogging;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
@@ -84,7 +83,7 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
||||
private static final Set<String> MISSING_LOAD_KEY_WARNED = ConcurrentHashMap.newKeySet();
|
||||
|
||||
public MantleObjectComponent(EngineMantle engineMantle) {
|
||||
super(engineMantle, ReservedFlag.OBJECT, 2);
|
||||
super(engineMantle, ReservedFlag.OBJECT, 1);
|
||||
}
|
||||
|
||||
private static String placementMarker(IrisObject object, int id, String context) {
|
||||
@@ -577,18 +576,11 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
||||
minDepthBelowSurface,
|
||||
anchorCache
|
||||
);
|
||||
RiverCaveHydrology hydrology = candidateY < 0
|
||||
? null
|
||||
: writer.getDataIfPresent(candidateX, candidateY, candidateZ, RiverCaveHydrology.class);
|
||||
MatterCavern cavern = candidateY < 0
|
||||
? null
|
||||
: writer.getDataIfPresent(candidateX, candidateY, candidateZ, MatterCavern.class);
|
||||
if (candidateY < 0
|
||||
|| caveAnchorBiomeConflicts(candidateX, candidateY, candidateZ, expectedCaveBiomeKey)
|
||||
|| !acceptsCaveAnchorFluid(
|
||||
underwater,
|
||||
hydrology == null ? cavern : hydrology.asCavern(),
|
||||
hydrology,
|
||||
writer.getDataIfPresent(candidateX, candidateY, candidateZ, MatterCavern.class),
|
||||
candidateY,
|
||||
getDimension().getCaveLavaHeight())) {
|
||||
continue;
|
||||
@@ -599,19 +591,6 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
||||
}
|
||||
|
||||
static boolean acceptsCaveAnchorFluid(boolean underwater, MatterCavern cavern, int y, int lavaHeight) {
|
||||
return acceptsCaveAnchorFluid(underwater, cavern, null, y, lavaHeight);
|
||||
}
|
||||
|
||||
static boolean acceptsCaveAnchorFluid(
|
||||
boolean underwater,
|
||||
MatterCavern cavern,
|
||||
RiverCaveHydrology hydrology,
|
||||
int y,
|
||||
int lavaHeight
|
||||
) {
|
||||
if (hydrology != null && hydrology.protectsPlacement()) {
|
||||
return false;
|
||||
}
|
||||
if (cavern == null || !cavern.isCavern()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
-174
@@ -1,174 +0,0 @@
|
||||
package art.arcane.iris.engine.mantle.components;
|
||||
|
||||
import art.arcane.iris.engine.river.cave.CavePosition;
|
||||
import art.arcane.iris.engine.river.cave.CaveVoxel;
|
||||
import art.arcane.iris.engine.river.cave.CaveVoxelView;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveFluidKind;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
|
||||
import art.arcane.iris.engine.data.cache.Cache;
|
||||
import art.arcane.iris.engine.object.IrisProceduralBlocks;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.volmlib.util.function.Function2;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.mantle.runtime.TectonicPlate;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
final class MantleRiverCaveVoxelView implements MantleRiverHydrologyComponent.TunnelVoxelView {
|
||||
private static final int CLOSED_COLUMN = Integer.MAX_VALUE;
|
||||
private static final int CACHE_MISS = Integer.MIN_VALUE;
|
||||
|
||||
private final Mantle<Matter> mantle;
|
||||
private final int worldHeight;
|
||||
private final Function2<Integer, Integer, Integer> surfaceHeight;
|
||||
private final Function2<Integer, Integer, PlatformBlockState> compatibleFluid;
|
||||
private final RiverCaveFluidKind planningFluidKind;
|
||||
private final BiConsumer<Integer, Integer> chunkLoader;
|
||||
private final LongOpenHashSet loadedChunks;
|
||||
private final Long2IntOpenHashMap openFloorCache;
|
||||
private final Long2IntOpenHashMap surfaceHeightCache;
|
||||
|
||||
MantleRiverCaveVoxelView(
|
||||
Mantle<Matter> mantle,
|
||||
int worldHeight,
|
||||
Function2<Integer, Integer, Integer> surfaceHeight,
|
||||
Function2<Integer, Integer, PlatformBlockState> compatibleFluid,
|
||||
RiverCaveFluidKind planningFluidKind,
|
||||
BiConsumer<Integer, Integer> chunkLoader
|
||||
) {
|
||||
this.mantle = Objects.requireNonNull(mantle);
|
||||
this.worldHeight = worldHeight;
|
||||
this.surfaceHeight = Objects.requireNonNull(surfaceHeight);
|
||||
this.compatibleFluid = Objects.requireNonNull(compatibleFluid);
|
||||
this.planningFluidKind = Objects.requireNonNull(planningFluidKind);
|
||||
this.chunkLoader = Objects.requireNonNull(chunkLoader);
|
||||
loadedChunks = new LongOpenHashSet();
|
||||
openFloorCache = new Long2IntOpenHashMap();
|
||||
openFloorCache.defaultReturnValue(CACHE_MISS);
|
||||
surfaceHeightCache = new Long2IntOpenHashMap();
|
||||
surfaceHeightCache.defaultReturnValue(CACHE_MISS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInWorld(CavePosition position) {
|
||||
return position.y() > 0 && position.y() < worldHeight - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CaveVoxel voxelAt(CavePosition position) {
|
||||
RiverCaveHydrology hydrology = dataIfPresent(position, RiverCaveHydrology.class);
|
||||
if (hydrology != null && hydrology.fluidKind() != planningFluidKind) {
|
||||
return CaveVoxel.INCOMPATIBLE_FLUID;
|
||||
}
|
||||
MatterCavern cavern = dataIfPresent(position, MatterCavern.class);
|
||||
if (cavern != null) {
|
||||
if (cavern.isLava()) {
|
||||
PlatformBlockState expected = compatibleFluid.apply(position.x(), position.z());
|
||||
return expected != null
|
||||
&& IrisProceduralBlocks.materialKey(expected).endsWith(":lava")
|
||||
? CaveVoxel.COMPATIBLE_FLUID
|
||||
: CaveVoxel.LAVA;
|
||||
}
|
||||
if (cavern.getLiquid() == 1) {
|
||||
return CaveVoxel.COMPATIBLE_FLUID;
|
||||
}
|
||||
return CaveVoxel.CAVE_AIR;
|
||||
}
|
||||
PlatformBlockState block = dataIfPresent(position, PlatformBlockState.class);
|
||||
if (block == null) {
|
||||
return position.y() > surfaceY(position.x(), position.z())
|
||||
? CaveVoxel.CAVE_AIR
|
||||
: CaveVoxel.SOLID;
|
||||
}
|
||||
if (!block.isFluid()) {
|
||||
return CaveVoxel.SOLID;
|
||||
}
|
||||
PlatformBlockState expected = compatibleFluid.apply(position.x(), position.z());
|
||||
if (expected != null
|
||||
&& IrisProceduralBlocks.materialKey(expected).equals(IrisProceduralBlocks.materialKey(block))) {
|
||||
return CaveVoxel.COMPATIBLE_FLUID;
|
||||
}
|
||||
return IrisProceduralBlocks.materialKey(block).endsWith(":lava")
|
||||
? CaveVoxel.LAVA
|
||||
: CaveVoxel.INCOMPATIBLE_FLUID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpenToSurface(CavePosition position) {
|
||||
if (!isInWorld(position) || voxelAt(position) == CaveVoxel.SOLID) {
|
||||
return false;
|
||||
}
|
||||
if (position.y() > surfaceY(position.x(), position.z())) {
|
||||
return true;
|
||||
}
|
||||
long key = Cache.key(position.x(), position.z());
|
||||
int openFloor = openFloorCache.get(key);
|
||||
if (openFloor == CACHE_MISS) {
|
||||
openFloor = resolveOpenFloor(position.x(), position.z());
|
||||
openFloorCache.put(key, openFloor);
|
||||
}
|
||||
return openFloor != CLOSED_COLUMN && position.y() >= openFloor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RiverCaveHydrology riverHydrologyAt(CavePosition position) {
|
||||
return dataIfPresent(position, RiverCaveHydrology.class);
|
||||
}
|
||||
|
||||
private int resolveOpenFloor(int x, int z) {
|
||||
int top = surfaceY(x, z);
|
||||
CavePosition surface = new CavePosition(x, top, z);
|
||||
if (voxelAt(surface) == CaveVoxel.SOLID) {
|
||||
return CLOSED_COLUMN;
|
||||
}
|
||||
int y = top;
|
||||
while (y > 0 && voxelAt(new CavePosition(x, y - 1, z)) != CaveVoxel.SOLID) {
|
||||
y--;
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
private int surfaceY(int x, int z) {
|
||||
long key = Cache.key(x, z);
|
||||
int cached = surfaceHeightCache.get(key);
|
||||
if (cached != CACHE_MISS) {
|
||||
return cached;
|
||||
}
|
||||
int resolved = Math.max(1, Math.min(worldHeight - 2, surfaceHeight.apply(x, z)));
|
||||
surfaceHeightCache.put(key, resolved);
|
||||
return resolved;
|
||||
}
|
||||
|
||||
private <T> T dataIfPresent(CavePosition position, Class<T> type) {
|
||||
int chunkX = position.x() >> 4;
|
||||
int chunkZ = position.z() >> 4;
|
||||
long chunkKey = Mantle.key(chunkX, chunkZ);
|
||||
if (loadedChunks.add(chunkKey)) {
|
||||
chunkLoader.accept(chunkX, chunkZ);
|
||||
}
|
||||
TectonicPlate<Matter> plate = mantle.getLoadedRegions().get(Mantle.key(chunkX >> 5, chunkZ >> 5));
|
||||
if (plate == null || plate.isClosed()) {
|
||||
return null;
|
||||
}
|
||||
MantleChunk<Matter> chunk = plate.get(chunkX & 31, chunkZ & 31);
|
||||
int section = position.y() >> 4;
|
||||
if (chunk == null || !chunk.exists(section)) {
|
||||
return null;
|
||||
}
|
||||
Matter matter = chunk.get(section);
|
||||
if (matter == null || !matter.hasSlice(type)) {
|
||||
return null;
|
||||
}
|
||||
return matter.<T>getSlice(type).get(
|
||||
position.x() & 15,
|
||||
position.y() & 15,
|
||||
position.z() & 15
|
||||
);
|
||||
}
|
||||
}
|
||||
-1206
File diff suppressed because it is too large
Load Diff
+24
-48
@@ -32,17 +32,16 @@ final class SurfaceFluidBoundaryPlan {
|
||||
int[] chunkSurfaceHeights,
|
||||
double[] fieldSurfaceHeights,
|
||||
boolean[] fieldHasFluid,
|
||||
double[] fieldFluidHeights,
|
||||
int fieldSize,
|
||||
int padding,
|
||||
long[] boundaries
|
||||
int fluidHeight,
|
||||
int[] boundaryStartY
|
||||
) {
|
||||
if (chunkSurfaceHeights == null || chunkSurfaceHeights.length < CHUNK_AREA
|
||||
|| boundaries == null || boundaries.length < CHUNK_AREA
|
||||
|| boundaryStartY == null || boundaryStartY.length < CHUNK_AREA
|
||||
|| padding < 1 || fieldSize < CHUNK_SIZE + (padding * 2)
|
||||
|| fieldSurfaceHeights == null || fieldSurfaceHeights.length < fieldSize * fieldSize
|
||||
|| fieldHasFluid == null || fieldHasFluid.length < fieldSize * fieldSize
|
||||
|| fieldFluidHeights == null || fieldFluidHeights.length < fieldSize * fieldSize) {
|
||||
|| fieldHasFluid == null || fieldHasFluid.length < fieldSize * fieldSize) {
|
||||
throw new IllegalArgumentException("Surface fluid boundary fields do not cover a padded chunk");
|
||||
}
|
||||
|
||||
@@ -52,67 +51,44 @@ final class SurfaceFluidBoundaryPlan {
|
||||
int fieldZ = localZ + padding;
|
||||
int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ);
|
||||
int boundaryY = NO_BOUNDARY;
|
||||
int boundaryEndY = Integer.MIN_VALUE;
|
||||
int surfaceY = chunkSurfaceHeights[columnIndex];
|
||||
int fieldIndex = (fieldX * fieldSize) + fieldZ;
|
||||
int fluidHeight = roundedHeight(fieldFluidHeights[fieldIndex]);
|
||||
if (fieldHasFluid[fieldIndex] && surfaceY < fluidHeight) {
|
||||
boundaryY = surfaceY;
|
||||
boundaryEndY = fluidHeight;
|
||||
}
|
||||
|
||||
long boundary = expandBoundary(boundaryY, boundaryEndY, fieldSurfaceHeights,
|
||||
fieldHasFluid, fieldFluidHeights, ((fieldX - 1) * fieldSize) + fieldZ);
|
||||
boundary = expandBoundary(startY(boundary), endY(boundary), fieldSurfaceHeights,
|
||||
fieldHasFluid, fieldFluidHeights, ((fieldX + 1) * fieldSize) + fieldZ);
|
||||
boundary = expandBoundary(startY(boundary), endY(boundary), fieldSurfaceHeights,
|
||||
fieldHasFluid, fieldFluidHeights, (fieldX * fieldSize) + fieldZ - 1);
|
||||
boundaries[columnIndex] = expandBoundary(startY(boundary), endY(boundary), fieldSurfaceHeights,
|
||||
fieldHasFluid, fieldFluidHeights, (fieldX * fieldSize) + fieldZ + 1);
|
||||
boundaryY = lowerBoundary(boundaryY, fieldSurfaceHeights, fieldHasFluid,
|
||||
((fieldX - 1) * fieldSize) + fieldZ, fluidHeight);
|
||||
boundaryY = lowerBoundary(boundaryY, fieldSurfaceHeights, fieldHasFluid,
|
||||
((fieldX + 1) * fieldSize) + fieldZ, fluidHeight);
|
||||
boundaryY = lowerBoundary(boundaryY, fieldSurfaceHeights, fieldHasFluid,
|
||||
(fieldX * fieldSize) + fieldZ - 1, fluidHeight);
|
||||
boundaryY = lowerBoundary(boundaryY, fieldSurfaceHeights, fieldHasFluid,
|
||||
(fieldX * fieldSize) + fieldZ + 1, fluidHeight);
|
||||
boundaryStartY[columnIndex] = boundaryY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static boolean protects(long[] boundaries, int columnIndex, int y) {
|
||||
if (boundaries == null || columnIndex < 0 || columnIndex >= boundaries.length) {
|
||||
return false;
|
||||
}
|
||||
long boundary = boundaries[columnIndex];
|
||||
return y >= startY(boundary) && y <= endY(boundary);
|
||||
static boolean protects(int[] boundaryStartY, int columnIndex, int y, int fluidHeight) {
|
||||
return boundaryStartY != null
|
||||
&& columnIndex >= 0
|
||||
&& columnIndex < boundaryStartY.length
|
||||
&& y >= boundaryStartY[columnIndex]
|
||||
&& y <= fluidHeight;
|
||||
}
|
||||
|
||||
static int startY(long boundary) {
|
||||
return (int) (boundary >> 32);
|
||||
}
|
||||
|
||||
static int endY(long boundary) {
|
||||
return (int) boundary;
|
||||
}
|
||||
|
||||
private static long expandBoundary(
|
||||
private static int lowerBoundary(
|
||||
int currentBoundaryY,
|
||||
int currentBoundaryEndY,
|
||||
double[] fieldSurfaceHeights,
|
||||
boolean[] fieldHasFluid,
|
||||
double[] fieldFluidHeights,
|
||||
int fieldIndex
|
||||
int fieldIndex,
|
||||
int fluidHeight
|
||||
) {
|
||||
int fluidHeight = roundedHeight(fieldFluidHeights[fieldIndex]);
|
||||
int neighborSurfaceY = (int) Math.round(fieldSurfaceHeights[fieldIndex]);
|
||||
if (!fieldHasFluid[fieldIndex] || neighborSurfaceY >= fluidHeight) {
|
||||
return boundary(currentBoundaryY, currentBoundaryEndY);
|
||||
return currentBoundaryY;
|
||||
}
|
||||
return boundary(
|
||||
Math.min(currentBoundaryY, neighborSurfaceY + 1),
|
||||
Math.max(currentBoundaryEndY, fluidHeight)
|
||||
);
|
||||
}
|
||||
|
||||
private static int roundedHeight(double height) {
|
||||
return Double.isFinite(height) ? (int) Math.round(height) : Integer.MIN_VALUE;
|
||||
}
|
||||
|
||||
static long boundary(int startY, int endY) {
|
||||
return ((long) startY << 32) | (endY & 0xffffffffL);
|
||||
return Math.min(currentBoundaryY, neighborSurfaceY + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,6 @@ import art.arcane.iris.engine.object.IrisDecorationPart;
|
||||
import art.arcane.iris.engine.object.IrisDecorator;
|
||||
import art.arcane.iris.engine.object.IrisDimensionCarvingResolver;
|
||||
import art.arcane.iris.engine.object.IrisProceduralBlocks;
|
||||
import art.arcane.iris.engine.object.IrisRiverCaves;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveAction;
|
||||
import art.arcane.iris.engine.river.cave.RiverCaveHydrology;
|
||||
import art.arcane.iris.util.project.context.ChunkContext;
|
||||
import art.arcane.iris.util.common.data.B;
|
||||
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
||||
@@ -59,8 +56,6 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
private static final int CAVE_BIOME_BLEND_RADIUS = 3;
|
||||
private static final int CAVE_BIOME_BLEND_CENTER_WEIGHT = 4;
|
||||
private static final int CAVE_BIOME_BLEND_TOTAL_WEIGHT = 8;
|
||||
private static final int RIVER_BIOME_INHERITANCE_CELL_SIZE = 4;
|
||||
private static final long RIVER_BIOME_INHERITANCE_SALT = 0x4CF5AD432745937FL;
|
||||
private static final MatterCavern BASIC_CAVERN = new MatterCavern(true, "", (byte) 0);
|
||||
private final RNG rng;
|
||||
private final PlatformBlockState AIR = B.getState("CAVE_AIR");
|
||||
@@ -110,28 +105,49 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
PrecisionStopwatch resolveStopwatch = PrecisionStopwatch.start();
|
||||
int worldHeightSpan = getEngine().getWorld().maxHeight() - getEngine().getWorld().minHeight();
|
||||
int caveLavaHeight = getEngine().getDimension().getCaveLavaHeight();
|
||||
CarveResolutionContext resolutionContext = new CarveResolutionContext(
|
||||
output,
|
||||
context,
|
||||
scratch,
|
||||
columnMasks,
|
||||
upperSurfaceHeights,
|
||||
worldHeightSpan,
|
||||
caveLavaHeight,
|
||||
chunkBlockX,
|
||||
chunkBlockZ
|
||||
);
|
||||
CarveResolver carveResolver = new CarveResolver(resolutionContext);
|
||||
mantleChunk.iterate(MatterCavern.class, (xx, yy, zz, cavern) -> carveResolver.apply(
|
||||
xx,
|
||||
yy,
|
||||
zz,
|
||||
cavern,
|
||||
dataIfPresent(mantleChunk, xx, yy, zz, RiverCaveHydrology.class)
|
||||
));
|
||||
mantleChunk.iterate(RiverCaveHydrology.class, (xx, yy, zz, hydrology) -> {
|
||||
if (dataIfPresent(mantleChunk, xx, yy, zz, MatterCavern.class) == null) {
|
||||
carveResolver.apply(xx, yy, zz, null, hydrology);
|
||||
mantleChunk.iterate(MatterCavern.class, (xx, yy, zz, cavern) -> {
|
||||
if (cavern == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (yy >= worldHeightSpan || yy <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int rx = xx & 15;
|
||||
int rz = zz & 15;
|
||||
int columnIndex = PowerOfTwoCoordinates.packLocal16(rx, rz);
|
||||
|
||||
if (upperSurfaceHeights != null && yy >= upperSurfaceHeights[columnIndex]) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlatformBlockState current = output.getRaw(rx, yy, rz);
|
||||
boolean explicitCarveIntent = hasExplicitCarveIntent(cavern);
|
||||
|
||||
if (shouldPreserveExistingFluid(cavern, current)) {
|
||||
return;
|
||||
}
|
||||
|
||||
columnMasks[columnIndex].add(yy);
|
||||
|
||||
if (!cavern.getCustomBiome().isEmpty()) {
|
||||
scratch.customCaveBiomePresent = true;
|
||||
}
|
||||
|
||||
if (current.isAir() && !explicitCarveIntent) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (explicitCarveIntent) {
|
||||
// Only a fluid cavern consumes the fluid sample, and on the maintenance path that
|
||||
// sample is a full procedural stream evaluation, so never take it per voxel.
|
||||
PlatformBlockState fluid = isFluidIntent(cavern) ? context.getFluid().get(rx, rz) : null;
|
||||
output.setRaw(rx, yy, rz, resolveExplicitCarveState(cavern, fluid, LAVA, AIR));
|
||||
} else if (usesDefaultLava(caveLavaHeight, yy)) {
|
||||
output.setRaw(rx, yy, rz, LAVA);
|
||||
} else {
|
||||
output.setRaw(rx, yy, rz, AIR);
|
||||
}
|
||||
});
|
||||
if (scratch.customCaveBiomePresent) {
|
||||
@@ -144,12 +160,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
|
||||
PrecisionStopwatch applyStopwatch = PrecisionStopwatch.start();
|
||||
try {
|
||||
walls.forEach((rx, yy, rz, cavern, riverBoundary) -> {
|
||||
RiverCaveHydrology hydrology = dataIfPresent(
|
||||
mantleChunk, rx, yy, rz, RiverCaveHydrology.class);
|
||||
if (hydrology != null && hydrology.protectsPlacement()) {
|
||||
return;
|
||||
}
|
||||
walls.forEach((rx, yy, rz, cavern) -> {
|
||||
int worldX = rx + chunkBlockX;
|
||||
int worldZ = rz + chunkBlockZ;
|
||||
String customBiome = cavern.getCustomBiome();
|
||||
@@ -168,36 +179,14 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
});
|
||||
|
||||
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
||||
processColumnFromMask(
|
||||
output,
|
||||
mantleChunk,
|
||||
mantle,
|
||||
columnMasks[columnIndex],
|
||||
columnIndex,
|
||||
x,
|
||||
z,
|
||||
resolverState,
|
||||
caveBiomeCache,
|
||||
customBiomeCache
|
||||
);
|
||||
processColumnFromMask(output, mantleChunk, mantle, columnMasks[columnIndex], columnIndex, x, z, resolverState, caveBiomeCache, customBiomeCache);
|
||||
}
|
||||
|
||||
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
||||
if (boundaryMasks[columnIndex].isEmpty() || !columnMasks[columnIndex].isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
processBoundaryColumnFromMask(
|
||||
output,
|
||||
mantleChunk,
|
||||
boundaryMasks[columnIndex],
|
||||
walls,
|
||||
columnIndex,
|
||||
x,
|
||||
z,
|
||||
resolverState,
|
||||
caveBiomeCache,
|
||||
customBiomeCache
|
||||
);
|
||||
processBoundaryColumnFromMask(output, boundaryMasks[columnIndex], walls, columnIndex, x, z, resolverState, caveBiomeCache, customBiomeCache);
|
||||
}
|
||||
|
||||
// Surface-break carving must not leave an ore cap suspended across the opening.
|
||||
@@ -263,150 +252,6 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
return cavern.getLiquid() == 3 ? air : null;
|
||||
}
|
||||
|
||||
static MatterCavern composeCavern(MatterCavern baseline, RiverCaveHydrology hydrology) {
|
||||
return hydrology == null ? baseline : hydrology.asCavern();
|
||||
}
|
||||
|
||||
static PlatformBlockState resolveHydrologyState(
|
||||
RiverCaveHydrology hydrology,
|
||||
PlatformBlockState current,
|
||||
PlatformBlockState fluid,
|
||||
PlatformBlockState air
|
||||
) {
|
||||
if (hydrology == null) {
|
||||
return null;
|
||||
}
|
||||
return switch (hydrology.action()) {
|
||||
case WET_SOURCE -> fluid;
|
||||
case FALLING_FLUID -> fallingFluidState(fluid);
|
||||
case DRY_AIR -> air;
|
||||
case SEAL_GUARD -> normalizeWaterlogging(current, null);
|
||||
};
|
||||
}
|
||||
|
||||
static PlatformBlockState normalizeWaterlogging(PlatformBlockState state, PlatformBlockState resultingFluid) {
|
||||
if (state == null || B.isFluid(state) || !IrisProceduralBlocks.hasProperty(state, "waterlogged")) {
|
||||
return state;
|
||||
}
|
||||
String target = resultingFluid != null && resultingFluid.isWater() ? "true" : "false";
|
||||
if (target.equals(IrisProceduralBlocks.propertyValue(state, "waterlogged"))) {
|
||||
return state;
|
||||
}
|
||||
return state.withProperty("waterlogged", target);
|
||||
}
|
||||
|
||||
static PlatformBlockState normalizeHydrologyWaterlogging(
|
||||
PlatformBlockState state,
|
||||
MatterCavern baseline,
|
||||
RiverCaveHydrology hydrology,
|
||||
PlatformBlockState columnFluid
|
||||
) {
|
||||
if (hydrology == null) {
|
||||
return state;
|
||||
}
|
||||
MatterCavern composed = composeCavern(baseline, hydrology);
|
||||
PlatformBlockState resultingFluid = isFluidIntent(composed) ? columnFluid : null;
|
||||
return normalizeWaterlogging(state, resultingFluid);
|
||||
}
|
||||
|
||||
private static PlatformBlockState fallingFluidState(PlatformBlockState fluid) {
|
||||
if (fluid == null || !IrisProceduralBlocks.hasProperty(fluid, "level")) {
|
||||
return fluid;
|
||||
}
|
||||
if ("8".equals(IrisProceduralBlocks.propertyValue(fluid, "level"))) {
|
||||
return fluid;
|
||||
}
|
||||
return fluid.withProperty("level", "8");
|
||||
}
|
||||
|
||||
private final class CarveResolver {
|
||||
private final CarveResolutionContext context;
|
||||
|
||||
private CarveResolver(CarveResolutionContext context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
private void apply(
|
||||
int x,
|
||||
int y,
|
||||
int z,
|
||||
MatterCavern baseline,
|
||||
RiverCaveHydrology hydrology
|
||||
) {
|
||||
if (y >= context.worldHeightSpan() || y <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int localX = x & 15;
|
||||
int localZ = z & 15;
|
||||
int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ);
|
||||
if (context.upperSurfaceHeights() != null && y >= context.upperSurfaceHeights()[columnIndex]) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlatformBlockState current = context.output().getRaw(localX, y, localZ);
|
||||
if (hydrology != null && hydrology.action() == RiverCaveAction.SEAL_GUARD) {
|
||||
PlatformBlockState normalized = resolveHydrologyState(hydrology, current, null, AIR);
|
||||
if (normalized != current) {
|
||||
context.output().setRaw(localX, y, localZ, normalized);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
MatterCavern cavern = composeCavern(baseline, hydrology);
|
||||
if (cavern == null || shouldPreserveExistingFluid(cavern, current)) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.columnMasks()[columnIndex].add(y);
|
||||
if (!cavern.getCustomBiome().isEmpty()) {
|
||||
context.scratch().customCaveBiomePresent = true;
|
||||
}
|
||||
|
||||
boolean explicitCarveIntent = hasExplicitCarveIntent(cavern);
|
||||
if (current.isAir() && !explicitCarveIntent) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlatformBlockState fluid = null;
|
||||
if (isFluidIntent(cavern)) {
|
||||
fluid = hydrology == null
|
||||
? context.chunkContext().getFluid().get(localX, localZ)
|
||||
: getComplex().resolveRiverCaveFluid(
|
||||
hydrology.fluidKind(),
|
||||
context.chunkBlockX() + localX,
|
||||
context.chunkBlockZ() + localZ
|
||||
);
|
||||
}
|
||||
if (hydrology != null) {
|
||||
context.output().setRaw(localX, y, localZ,
|
||||
resolveHydrologyState(hydrology, current, fluid, AIR));
|
||||
return;
|
||||
}
|
||||
if (explicitCarveIntent) {
|
||||
context.output().setRaw(localX, y, localZ,
|
||||
resolveExplicitCarveState(cavern, fluid, LAVA, AIR));
|
||||
} else if (usesDefaultLava(context.caveLavaHeight(), y)) {
|
||||
context.output().setRaw(localX, y, localZ, LAVA);
|
||||
} else {
|
||||
context.output().setRaw(localX, y, localZ, AIR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private record CarveResolutionContext(
|
||||
Hunk<PlatformBlockState> output,
|
||||
ChunkContext chunkContext,
|
||||
IrisCarveScratch scratch,
|
||||
CarveColumnMask[] columnMasks,
|
||||
int[] upperSurfaceHeights,
|
||||
int worldHeightSpan,
|
||||
int caveLavaHeight,
|
||||
int chunkBlockX,
|
||||
int chunkBlockZ
|
||||
) {
|
||||
}
|
||||
|
||||
private void addInternalWallsFromMasks(CarveWallBuffer walls, CarveColumnMask[] columnMasks) {
|
||||
for (int columnIndex = 0; columnIndex < 256; columnIndex++) {
|
||||
CarveColumnMask columnMask = columnMasks[columnIndex];
|
||||
@@ -419,16 +264,16 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
int yy = columnMask.nextSetBit(0);
|
||||
while (yy >= 0) {
|
||||
if (rz < 15 && !columnMasks[columnIndex + 1].contains(yy)) {
|
||||
walls.put(rx, yy, rz + 1, BASIC_CAVERN, false);
|
||||
walls.put(rx, yy, rz + 1, BASIC_CAVERN);
|
||||
}
|
||||
if (rx < 15 && !columnMasks[columnIndex + 16].contains(yy)) {
|
||||
walls.put(rx + 1, yy, rz, BASIC_CAVERN, false);
|
||||
walls.put(rx + 1, yy, rz, BASIC_CAVERN);
|
||||
}
|
||||
if (rz > 0 && !columnMasks[columnIndex - 1].contains(yy)) {
|
||||
walls.put(rx, yy, rz - 1, BASIC_CAVERN, false);
|
||||
walls.put(rx, yy, rz - 1, BASIC_CAVERN);
|
||||
}
|
||||
if (rx > 0 && !columnMasks[columnIndex - 16].contains(yy)) {
|
||||
walls.put(rx - 1, yy, rz, BASIC_CAVERN, false);
|
||||
walls.put(rx - 1, yy, rz, BASIC_CAVERN);
|
||||
}
|
||||
yy = columnMask.nextSetBit(yy + 1);
|
||||
}
|
||||
@@ -446,21 +291,19 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
int rz = columnIndex & 15;
|
||||
int yy = columnMask.nextSetBit(0);
|
||||
while (yy >= 0) {
|
||||
MatterCavern cavern = composedCavernAt(mc, rx, yy, rz);
|
||||
MatterCavern cavern = mc.get(rx, yy, rz, MatterCavern.class);
|
||||
if (cavern != null) {
|
||||
RiverCaveHydrology hydrology = dataIfPresent(mc, rx, yy, rz, RiverCaveHydrology.class);
|
||||
boolean riverBoundary = hydrology != null && !hydrology.floodedBiomeKey().isEmpty();
|
||||
if (rz < 15 && composedCavernAt(mc, rx, yy, rz + 1) == null) {
|
||||
walls.put(rx, yy, rz + 1, cavern, riverBoundary);
|
||||
if (rz < 15 && mc.get(rx, yy, rz + 1, MatterCavern.class) == null) {
|
||||
walls.put(rx, yy, rz + 1, cavern);
|
||||
}
|
||||
if (rx < 15 && composedCavernAt(mc, rx + 1, yy, rz) == null) {
|
||||
walls.put(rx + 1, yy, rz, cavern, riverBoundary);
|
||||
if (rx < 15 && mc.get(rx + 1, yy, rz, MatterCavern.class) == null) {
|
||||
walls.put(rx + 1, yy, rz, cavern);
|
||||
}
|
||||
if (rz > 0 && composedCavernAt(mc, rx, yy, rz - 1) == null) {
|
||||
walls.put(rx, yy, rz - 1, cavern, riverBoundary);
|
||||
if (rz > 0 && mc.get(rx, yy, rz - 1, MatterCavern.class) == null) {
|
||||
walls.put(rx, yy, rz - 1, cavern);
|
||||
}
|
||||
if (rx > 0 && composedCavernAt(mc, rx - 1, yy, rz) == null) {
|
||||
walls.put(rx - 1, yy, rz, cavern, riverBoundary);
|
||||
if (rx > 0 && mc.get(rx - 1, yy, rz, MatterCavern.class) == null) {
|
||||
walls.put(rx - 1, yy, rz, cavern);
|
||||
}
|
||||
}
|
||||
yy = columnMask.nextSetBit(yy + 1);
|
||||
@@ -527,19 +370,16 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
int neighborX,
|
||||
int neighborZ
|
||||
) {
|
||||
if (composedCavernAt(mc, localX, yy, localZ) != null) {
|
||||
if (mc.get(localX, yy, localZ, MatterCavern.class) != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
MatterCavern neighbor = composedCavernAt(neighborChunk, neighborX, yy, neighborZ);
|
||||
MatterCavern neighbor = neighborChunk.get(neighborX, yy, neighborZ, MatterCavern.class);
|
||||
if (neighbor == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
RiverCaveHydrology hydrology = dataIfPresent(
|
||||
neighborChunk, neighborX, yy, neighborZ, RiverCaveHydrology.class);
|
||||
boolean riverBoundary = hydrology != null && !hydrology.floodedBiomeKey().isEmpty();
|
||||
walls.put(localX, yy, localZ, neighbor, riverBoundary);
|
||||
walls.put(localX, yy, localZ, neighbor);
|
||||
int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ);
|
||||
boundaryMasks[columnIndex].add(yy);
|
||||
}
|
||||
@@ -552,24 +392,6 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
return plate.get(chunkX & 31, chunkZ & 31);
|
||||
}
|
||||
|
||||
private MatterCavern composedCavernAt(MantleChunk<Matter> mantleChunk, int x, int y, int z) {
|
||||
MatterCavern baseline = dataIfPresent(mantleChunk, x, y, z, MatterCavern.class);
|
||||
RiverCaveHydrology hydrology = dataIfPresent(mantleChunk, x, y, z, RiverCaveHydrology.class);
|
||||
return composeCavern(baseline, hydrology);
|
||||
}
|
||||
|
||||
private static <T> T dataIfPresent(MantleChunk<Matter> mantleChunk, int x, int y, int z, Class<T> type) {
|
||||
int section = y >> 4;
|
||||
if (y < 0 || !mantleChunk.exists(section)) {
|
||||
return null;
|
||||
}
|
||||
Matter matter = mantleChunk.get(section);
|
||||
if (matter == null || !matter.hasSlice(type)) {
|
||||
return null;
|
||||
}
|
||||
return matter.<T>getSlice(type).get(x & 15, y & 15, z & 15);
|
||||
}
|
||||
|
||||
private void processColumnFromMask(
|
||||
Hunk<PlatformBlockState> output,
|
||||
MantleChunk<Matter> mc,
|
||||
@@ -607,8 +429,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
zone.ceiling = buf;
|
||||
} else {
|
||||
if (zone.isValid(getEngine())) {
|
||||
processZone(output, mc, mantle, zone, rx, rz, worldX, worldZ, resolverState,
|
||||
caveBiomeCache, customBiomeCache);
|
||||
processZone(output, mc, mantle, zone, rx, rz, worldX, worldZ, resolverState, caveBiomeCache, customBiomeCache);
|
||||
}
|
||||
zone = new CaveZone();
|
||||
zone.setFloor(y);
|
||||
@@ -620,14 +441,12 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
}
|
||||
|
||||
if (zone.isValid(getEngine())) {
|
||||
processZone(output, mc, mantle, zone, rx, rz, worldX, worldZ, resolverState,
|
||||
caveBiomeCache, customBiomeCache);
|
||||
processZone(output, mc, mantle, zone, rx, rz, worldX, worldZ, resolverState, caveBiomeCache, customBiomeCache);
|
||||
}
|
||||
}
|
||||
|
||||
private void processBoundaryColumnFromMask(
|
||||
Hunk<PlatformBlockState> output,
|
||||
MantleChunk<Matter> mantleChunk,
|
||||
CarveColumnMask boundaryMask,
|
||||
CarveWallBuffer walls,
|
||||
int columnIndex,
|
||||
@@ -654,21 +473,18 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
if (y == zoneCeiling + 1) {
|
||||
zoneCeiling = y;
|
||||
} else {
|
||||
paintBoundaryZone(output, mantleChunk, walls, rx, rz, worldX, worldZ, zoneFloor, zoneCeiling,
|
||||
resolverState, caveBiomeCache, customBiomeCache);
|
||||
paintBoundaryZone(output, walls, rx, rz, worldX, worldZ, zoneFloor, zoneCeiling, resolverState, caveBiomeCache, customBiomeCache);
|
||||
zoneFloor = y;
|
||||
zoneCeiling = y;
|
||||
}
|
||||
y = boundaryMask.nextSetBit(y + 1);
|
||||
}
|
||||
|
||||
paintBoundaryZone(output, mantleChunk, walls, rx, rz, worldX, worldZ, zoneFloor, zoneCeiling,
|
||||
resolverState, caveBiomeCache, customBiomeCache);
|
||||
paintBoundaryZone(output, walls, rx, rz, worldX, worldZ, zoneFloor, zoneCeiling, resolverState, caveBiomeCache, customBiomeCache);
|
||||
}
|
||||
|
||||
private void paintBoundaryZone(
|
||||
Hunk<PlatformBlockState> output,
|
||||
MantleChunk<Matter> mantleChunk,
|
||||
CarveWallBuffer walls,
|
||||
int rx,
|
||||
int rz,
|
||||
@@ -682,12 +498,10 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
) {
|
||||
IrisBiome floorBiome = resolveCaveBoundaryBiome(
|
||||
walls.get(rx, zoneFloor, rz), worldX, zoneFloor, worldZ,
|
||||
resolverState, caveBiomeCache, customBiomeCache,
|
||||
walls.isRiverBoundary(rx, zoneFloor, rz));
|
||||
resolverState, caveBiomeCache, customBiomeCache);
|
||||
IrisBiome ceilingBiome = resolveCaveBoundaryBiome(
|
||||
walls.get(rx, zoneCeiling, rz), worldX, zoneCeiling, worldZ,
|
||||
resolverState, caveBiomeCache, customBiomeCache,
|
||||
walls.isRiverBoundary(rx, zoneCeiling, rz));
|
||||
resolverState, caveBiomeCache, customBiomeCache);
|
||||
if (floorBiome == null && ceilingBiome == null) {
|
||||
return;
|
||||
}
|
||||
@@ -703,18 +517,9 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
if (floorY < 0) {
|
||||
break;
|
||||
}
|
||||
RiverCaveHydrology hydrology = dataIfPresent(
|
||||
mantleChunk, rx, floorY, rz, RiverCaveHydrology.class);
|
||||
if (hydrology != null
|
||||
&& hydrology.protectsPlacement()
|
||||
&& hydrology.action() != RiverCaveAction.SEAL_GUARD) {
|
||||
continue;
|
||||
}
|
||||
PlatformBlockState existing = output.getRaw(rx, floorY, rz);
|
||||
PlatformBlockState layer = floorLayers.get(i);
|
||||
if (!B.isSolid(existing)
|
||||
|| !canReplaceRiverGuard(hydrology, layer, false)
|
||||
|| !canReplaceCaveFloorLayer(output, rx, floorY, rz, layer)) {
|
||||
if (!B.isSolid(existing) || !canReplaceCaveFloorLayer(output, rx, floorY, rz, layer)) {
|
||||
continue;
|
||||
}
|
||||
if (B.isOre(existing)) {
|
||||
@@ -734,21 +539,11 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
if (ceilingY >= worldMaxY) {
|
||||
break;
|
||||
}
|
||||
RiverCaveHydrology hydrology = dataIfPresent(
|
||||
mantleChunk, rx, ceilingY, rz, RiverCaveHydrology.class);
|
||||
if (hydrology != null
|
||||
&& hydrology.protectsPlacement()
|
||||
&& hydrology.action() != RiverCaveAction.SEAL_GUARD) {
|
||||
continue;
|
||||
}
|
||||
PlatformBlockState existing = output.getRaw(rx, ceilingY, rz);
|
||||
if (!B.isSolid(existing)) {
|
||||
continue;
|
||||
}
|
||||
PlatformBlockState layer = ceilingLayers.get(i);
|
||||
if (!canReplaceRiverGuard(hydrology, layer, true)) {
|
||||
continue;
|
||||
}
|
||||
if (B.isOre(existing)) {
|
||||
output.setRaw(rx, ceilingY, rz, B.toDeepSlateOre(existing, layer));
|
||||
continue;
|
||||
@@ -770,11 +565,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
return (h & 15L) == 0L;
|
||||
}
|
||||
|
||||
private void processZone(Hunk<PlatformBlockState> output, MantleChunk<Matter> mc, Mantle<Matter> mantle,
|
||||
CaveZone zone, int rx, int rz, int xx, int zz,
|
||||
IrisDimensionCarvingResolver.State resolverState,
|
||||
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache,
|
||||
Map<String, IrisBiome> customBiomeCache) {
|
||||
private void processZone(Hunk<PlatformBlockState> output, MantleChunk<Matter> mc, Mantle<Matter> mantle, CaveZone zone, int rx, int rz, int xx, int zz, IrisDimensionCarvingResolver.State resolverState, Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache, Map<String, IrisBiome> customBiomeCache) {
|
||||
int maxY = output.getHeight();
|
||||
|
||||
if (zone.ceiling + 1 < maxY && B.isDecorant(output.getRaw(rx, zone.ceiling + 1, rz))) {
|
||||
@@ -799,7 +590,6 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
IrisBiome floorBiome = resolveCaveBoundaryBiome(mc, rx, zone.floor, rz, xx, zz, resolverState, caveBiomeCache, customBiomeCache);
|
||||
IrisBiome ceilingBiome = resolveCaveBoundaryBiome(mc, rx, zone.ceiling, rz, xx, zz, resolverState, caveBiomeCache, customBiomeCache);
|
||||
if (floorBiome == null && ceilingBiome == null) {
|
||||
normalizeCaveZoneWaterlogging(output, mc, zone, rx, rz, xx, zz);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -810,17 +600,9 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
break;
|
||||
}
|
||||
int y = zone.floor - i - 1;
|
||||
RiverCaveHydrology hydrology = dataIfPresent(mc, rx, y, rz, RiverCaveHydrology.class);
|
||||
if (hydrology != null
|
||||
&& hydrology.protectsPlacement()
|
||||
&& hydrology.action() != RiverCaveAction.SEAL_GUARD) {
|
||||
continue;
|
||||
}
|
||||
PlatformBlockState block = floorBlocks.get(i);
|
||||
PlatformBlockState existing = output.getRaw(rx, y, rz);
|
||||
if (!B.isSolid(existing)
|
||||
|| !canReplaceRiverGuard(hydrology, block, false)
|
||||
|| !canReplaceCaveFloorLayer(output, rx, y, rz, block)) {
|
||||
if (!B.isSolid(existing) || !canReplaceCaveFloorLayer(output, rx, y, rz, block)) {
|
||||
continue;
|
||||
}
|
||||
if (B.isOre(existing)) {
|
||||
@@ -838,15 +620,9 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
if (cy >= maxY) {
|
||||
break;
|
||||
}
|
||||
RiverCaveHydrology hydrology = dataIfPresent(mc, rx, cy, rz, RiverCaveHydrology.class);
|
||||
if (hydrology != null
|
||||
&& hydrology.protectsPlacement()
|
||||
&& hydrology.action() != RiverCaveAction.SEAL_GUARD) {
|
||||
continue;
|
||||
}
|
||||
PlatformBlockState block = ceilingBlocks.get(i);
|
||||
PlatformBlockState existing = output.getRaw(rx, cy, rz);
|
||||
if (!B.isSolid(existing) || !canReplaceRiverGuard(hydrology, block, true)) {
|
||||
if (!B.isSolid(existing)) {
|
||||
continue;
|
||||
}
|
||||
if (B.isOre(existing)) {
|
||||
@@ -870,133 +646,33 @@ public class IrisCarveModifier extends EngineAssignedModifier<PlatformBlockState
|
||||
if (ceilingDecorators.length > 0 && zone.getCeiling() + 1 < maxY && B.isSolid(output.getRaw(rx, zone.getCeiling() + 1, rz))) {
|
||||
decorant.getCeilingDecorator().decorate(rx, rz, xx, xx, xx, zz, zz, zz, output, ceilingBiome, InferredType.CAVE, zone.getCeiling(), zone.airThickness());
|
||||
}
|
||||
|
||||
normalizeCaveZoneWaterlogging(output, mc, zone, rx, rz, xx, zz);
|
||||
}
|
||||
|
||||
private void normalizeCaveZoneWaterlogging(
|
||||
Hunk<PlatformBlockState> output,
|
||||
MantleChunk<Matter> mantleChunk,
|
||||
CaveZone zone,
|
||||
int localX,
|
||||
int localZ,
|
||||
int worldX,
|
||||
int worldZ
|
||||
) {
|
||||
int minimumY = Math.max(0, zone.floor - 1);
|
||||
int maximumY = Math.min(output.getHeight() - 1, zone.ceiling + 1);
|
||||
for (int y = minimumY; y <= maximumY; y++) {
|
||||
RiverCaveHydrology hydrology = dataIfPresent(
|
||||
mantleChunk, localX, y, localZ, RiverCaveHydrology.class);
|
||||
if (hydrology == null) {
|
||||
continue;
|
||||
}
|
||||
MatterCavern baseline = dataIfPresent(
|
||||
mantleChunk, localX, y, localZ, MatterCavern.class);
|
||||
PlatformBlockState current = output.getRaw(localX, y, localZ);
|
||||
PlatformBlockState columnFluid = getComplex().resolveRiverCaveFluid(
|
||||
hydrology.fluidKind(),
|
||||
worldX,
|
||||
worldZ
|
||||
);
|
||||
PlatformBlockState normalized = normalizeHydrologyWaterlogging(
|
||||
current,
|
||||
baseline,
|
||||
hydrology,
|
||||
columnFluid
|
||||
);
|
||||
if (normalized != current) {
|
||||
output.setRaw(localX, y, localZ, normalized);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IrisBiome resolveCaveBoundaryBiome(MantleChunk<Matter> mantleChunk, int x, int y, int z, int worldX, int worldZ, IrisDimensionCarvingResolver.State resolverState, Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache, Map<String, IrisBiome> customBiomeCache) {
|
||||
MatterCavern cavern = composedCavernAt(mantleChunk, x, y, z);
|
||||
RiverCaveHydrology hydrology = dataIfPresent(
|
||||
mantleChunk, x, y, z, RiverCaveHydrology.class);
|
||||
MatterCavern cavern = dataIfPresent(mantleChunk, x, y, z, MatterCavern.class);
|
||||
return resolveCaveBoundaryBiome(
|
||||
cavern, worldX, y, worldZ, resolverState, caveBiomeCache, customBiomeCache,
|
||||
hydrology != null && !hydrology.floodedBiomeKey().isEmpty());
|
||||
cavern, worldX, y, worldZ, resolverState, caveBiomeCache, customBiomeCache);
|
||||
}
|
||||
|
||||
private static <T> T dataIfPresent(MantleChunk<Matter> mantleChunk, int x, int y, int z, Class<T> type) {
|
||||
int section = y >> 4;
|
||||
if (y < 0 || !mantleChunk.exists(section)) {
|
||||
return null;
|
||||
}
|
||||
Matter matter = mantleChunk.get(section);
|
||||
if (matter == null || !matter.hasSlice(type)) {
|
||||
return null;
|
||||
}
|
||||
return matter.<T>getSlice(type).get(x & 15, y & 15, z & 15);
|
||||
}
|
||||
|
||||
IrisBiome resolveCaveBoundaryBiome(MatterCavern cavern, int worldX, int y, int worldZ, IrisDimensionCarvingResolver.State resolverState, Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache, Map<String, IrisBiome> customBiomeCache) {
|
||||
return resolveCaveBoundaryBiome(
|
||||
cavern, worldX, y, worldZ, resolverState, caveBiomeCache, customBiomeCache, false);
|
||||
}
|
||||
|
||||
private IrisBiome resolveCaveBoundaryBiome(MatterCavern cavern, int worldX, int y, int worldZ, IrisDimensionCarvingResolver.State resolverState, Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache, Map<String, IrisBiome> customBiomeCache, boolean riverBoundary) {
|
||||
if (cavern != null && !cavern.getCustomBiome().isEmpty()) {
|
||||
if (riverBoundary && selectsParentRiverBiome(
|
||||
getEngine().getSeedManager().getCarve(),
|
||||
worldX,
|
||||
worldZ,
|
||||
riverParentBiomeInheritance())) {
|
||||
IrisBiome parent = resolveRiverParentBiome(
|
||||
caveBiomeCache, worldX, y, worldZ, resolverState);
|
||||
if (parent != null) {
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
return resolveCustomBiome(customBiomeCache, cavern.getCustomBiome());
|
||||
}
|
||||
return resolveCaveBiome(caveBiomeCache, worldX, y, worldZ, resolverState);
|
||||
}
|
||||
|
||||
static boolean selectsParentRiverBiome(long seed, int worldX, int worldZ, double inheritance) {
|
||||
if (inheritance <= 0D) {
|
||||
return false;
|
||||
}
|
||||
if (inheritance >= 1D) {
|
||||
return true;
|
||||
}
|
||||
int cellX = Math.floorDiv(worldX, RIVER_BIOME_INHERITANCE_CELL_SIZE);
|
||||
int cellZ = Math.floorDiv(worldZ, RIVER_BIOME_INHERITANCE_CELL_SIZE);
|
||||
long hash = (seed + RIVER_BIOME_INHERITANCE_SALT) ^ BlockPosition.toLong(cellX, 0, cellZ);
|
||||
hash = (hash ^ (hash >>> 30)) * 0xBF58476D1CE4E5B9L;
|
||||
hash = (hash ^ (hash >>> 27)) * 0x94D049BB133111EBL;
|
||||
hash ^= hash >>> 31;
|
||||
double roll = (hash >>> 11) * 0x1.0p-53;
|
||||
return roll < inheritance;
|
||||
}
|
||||
|
||||
static boolean canReplaceRiverGuard(
|
||||
RiverCaveHydrology hydrology,
|
||||
PlatformBlockState layer,
|
||||
boolean ceiling
|
||||
) {
|
||||
if (hydrology == null || hydrology.action() != RiverCaveAction.SEAL_GUARD) {
|
||||
return true;
|
||||
}
|
||||
return layer != null
|
||||
&& B.isSolid(layer)
|
||||
&& !B.isFluid(layer)
|
||||
&& (!ceiling || !isGravityAffected(layer));
|
||||
}
|
||||
|
||||
private double riverParentBiomeInheritance() {
|
||||
if (getDimension().getRivers() == null || getDimension().getRivers().getCaves() == null) {
|
||||
return 0D;
|
||||
}
|
||||
IrisRiverCaves caves = getDimension().getRivers().getCaves();
|
||||
return Math.max(0D, Math.min(1D, caves.getParentBiomeInheritance()));
|
||||
}
|
||||
|
||||
private IrisBiome resolveRiverParentBiome(
|
||||
Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache,
|
||||
int worldX,
|
||||
int y,
|
||||
int worldZ,
|
||||
IrisDimensionCarvingResolver.State resolverState
|
||||
) {
|
||||
IrisBiome parent = resolveCaveBiome(caveBiomeCache, worldX, y, worldZ, resolverState);
|
||||
if (parent != null && parent == getEngine().getSurfaceBiome(worldX, worldZ)) {
|
||||
IrisBiome natural = getComplex().getNaturalTrueBiomeStream().get(worldX, worldZ);
|
||||
return natural == null ? parent : natural;
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
|
||||
static boolean canReplaceCaveFloorLayer(Hunk<PlatformBlockState> output, int x, int y, int z, PlatformBlockState layer) {
|
||||
return !isGravityAffected(layer) || y > 0 && B.isSolid(output.getRaw(x, y - 1, z));
|
||||
}
|
||||
|
||||
@@ -140,7 +140,6 @@ final class CarveWallBuffer {
|
||||
|
||||
private int[] keys;
|
||||
private MatterCavern[] values;
|
||||
private boolean[] riverBoundaries;
|
||||
private int mask;
|
||||
private int resizeAt;
|
||||
private int size;
|
||||
@@ -155,12 +154,11 @@ final class CarveWallBuffer {
|
||||
keys = new int[capacity];
|
||||
Arrays.fill(keys, EMPTY_KEY);
|
||||
values = new MatterCavern[capacity];
|
||||
riverBoundaries = new boolean[capacity];
|
||||
mask = capacity - 1;
|
||||
resizeAt = Math.max(1, (int) (capacity * LOAD_FACTOR));
|
||||
}
|
||||
|
||||
void put(int x, int y, int z, MatterCavern value, boolean riverBoundary) {
|
||||
void put(int x, int y, int z, MatterCavern value) {
|
||||
int key = pack(x, y, z);
|
||||
int index = mix(key) & mask;
|
||||
|
||||
@@ -169,7 +167,6 @@ final class CarveWallBuffer {
|
||||
if (existingKey == EMPTY_KEY) {
|
||||
keys[index] = key;
|
||||
values[index] = value;
|
||||
riverBoundaries[index] = riverBoundary;
|
||||
size++;
|
||||
if (size >= resizeAt) {
|
||||
resize();
|
||||
@@ -179,7 +176,6 @@ final class CarveWallBuffer {
|
||||
|
||||
if (existingKey == key) {
|
||||
values[index] = value;
|
||||
riverBoundaries[index] = riverBoundaries[index] || riverBoundary;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -202,21 +198,6 @@ final class CarveWallBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
boolean isRiverBoundary(int x, int y, int z) {
|
||||
int key = pack(x, y, z);
|
||||
int index = mix(key) & mask;
|
||||
while (true) {
|
||||
int existingKey = keys[index];
|
||||
if (existingKey == EMPTY_KEY) {
|
||||
return false;
|
||||
}
|
||||
if (existingKey == key) {
|
||||
return riverBoundaries[index];
|
||||
}
|
||||
index = (index + 1) & mask;
|
||||
}
|
||||
}
|
||||
|
||||
void forEach(Consumer consumer) {
|
||||
for (int index = 0; index < keys.length; index++) {
|
||||
int key = keys[index];
|
||||
@@ -226,7 +207,7 @@ final class CarveWallBuffer {
|
||||
|
||||
MatterCavern cavern = values[index];
|
||||
if (cavern != null) {
|
||||
consumer.accept(unpackX(key), unpackY(key), unpackZ(key), cavern, riverBoundaries[index]);
|
||||
consumer.accept(unpackX(key), unpackY(key), unpackZ(key), cavern);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,19 +215,16 @@ final class CarveWallBuffer {
|
||||
void clear() {
|
||||
Arrays.fill(keys, EMPTY_KEY);
|
||||
Arrays.fill(values, null);
|
||||
Arrays.fill(riverBoundaries, false);
|
||||
size = 0;
|
||||
}
|
||||
|
||||
private void resize() {
|
||||
int[] oldKeys = keys;
|
||||
MatterCavern[] oldValues = values;
|
||||
boolean[] oldRiverBoundaries = riverBoundaries;
|
||||
int nextCapacity = oldKeys.length << 1;
|
||||
keys = new int[nextCapacity];
|
||||
Arrays.fill(keys, EMPTY_KEY);
|
||||
values = new MatterCavern[nextCapacity];
|
||||
riverBoundaries = new boolean[nextCapacity];
|
||||
mask = nextCapacity - 1;
|
||||
resizeAt = Math.max(1, (int) (nextCapacity * LOAD_FACTOR));
|
||||
size = 0;
|
||||
@@ -255,12 +233,12 @@ final class CarveWallBuffer {
|
||||
int key = oldKeys[index];
|
||||
MatterCavern value = oldValues[index];
|
||||
if (key != EMPTY_KEY && value != null) {
|
||||
reinsert(key, value, oldRiverBoundaries[index]);
|
||||
reinsert(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void reinsert(int key, MatterCavern value, boolean riverBoundary) {
|
||||
private void reinsert(int key, MatterCavern value) {
|
||||
int index = mix(key) & mask;
|
||||
while (keys[index] != EMPTY_KEY) {
|
||||
index = (index + 1) & mask;
|
||||
@@ -268,7 +246,6 @@ final class CarveWallBuffer {
|
||||
|
||||
keys[index] = key;
|
||||
values[index] = value;
|
||||
riverBoundaries[index] = riverBoundary;
|
||||
size++;
|
||||
}
|
||||
|
||||
@@ -295,6 +272,6 @@ final class CarveWallBuffer {
|
||||
|
||||
@FunctionalInterface
|
||||
interface Consumer {
|
||||
void accept(int x, int y, int z, MatterCavern cavern, boolean riverBoundary);
|
||||
void accept(int x, int y, int z, MatterCavern cavern);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,9 +57,10 @@ public class IrisPostModifier extends EngineAssignedModifier<PlatformBlockState>
|
||||
IrisDimension dimension = getDimension();
|
||||
boolean walls = dimension.isPostProcessingWalls();
|
||||
boolean slabs = dimension.isPostProcessingSlabs();
|
||||
int fluidHeight = dimension.getFluidHeight();
|
||||
for (int i = 0; i < width; i++) {
|
||||
for (int j = 0; j < depth; j++) {
|
||||
post(i, j, sync, i + x, j + z, context, heights, planeWidth, walls, slabs);
|
||||
post(i, j, sync, i + x, j + z, context, heights, planeWidth, walls, slabs, fluidHeight);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +90,7 @@ public class IrisPostModifier extends EngineAssignedModifier<PlatformBlockState>
|
||||
return heights;
|
||||
}
|
||||
|
||||
private void post(int currentPostX, int currentPostZ, Hunk<PlatformBlockState> currentData, int x, int z, ChunkContext context, int[] heights, int planeWidth, boolean walls, boolean slabs) {
|
||||
private void post(int currentPostX, int currentPostZ, Hunk<PlatformBlockState> currentData, int x, int z, ChunkContext context, int[] heights, int planeWidth, boolean walls, boolean slabs, int fluidHeight) {
|
||||
// x/z are world coordinates, the hunk is indexed relative to this chunk origin.
|
||||
int originX = x - currentPostX;
|
||||
int originZ = z - currentPostZ;
|
||||
@@ -99,7 +100,6 @@ public class IrisPostModifier extends EngineAssignedModifier<PlatformBlockState>
|
||||
int hb = heights[center + planeWidth];
|
||||
int hc = heights[center - 1];
|
||||
int hd = heights[center - planeWidth];
|
||||
int fluidHeight = (int) Math.round(getComplex().getRiverWaterSurfaceStream().get(x, z));
|
||||
|
||||
// Floating Nibs
|
||||
int g = 0;
|
||||
|
||||
@@ -50,17 +50,6 @@ public interface IObjectPlacer {
|
||||
|
||||
int getFluidHeight();
|
||||
|
||||
default int getFluidHeight(int x, int z) {
|
||||
Engine engine = getEngine();
|
||||
if (engine == null || engine.getComplex() == null) {
|
||||
return getFluidHeight();
|
||||
}
|
||||
int coordinateShift = getFluidHeight() - engine.getDimension().getFluidHeight();
|
||||
return coordinateShift + (int) Math.round(
|
||||
engine.getComplex().getRiverWaterSurfaceStream().get(x, z)
|
||||
);
|
||||
}
|
||||
|
||||
boolean isDebugSmartBore();
|
||||
|
||||
void setTile(int xx, int yy, int zz, TileData tile);
|
||||
|
||||
@@ -130,8 +130,6 @@ public class IrisBiome extends IrisRegistrant implements IRare {
|
||||
private int lockLayersMax = 7;
|
||||
@Desc("Profile-driven 3D cave configuration")
|
||||
private IrisCaveProfile caveProfile = new IrisCaveProfile();
|
||||
@Desc("Biome-level river routing, shape, cave-entry, and biome-pool overrides. Omit to inherit region and dimension settings.")
|
||||
private IrisRiverOverride riverOverride = null;
|
||||
@MinNumber(1)
|
||||
@MaxNumber(512)
|
||||
@Desc("The rarity of this biome (integer)")
|
||||
|
||||
@@ -36,7 +36,7 @@ final class IrisBiomeColorRenderer {
|
||||
|
||||
static Color getColor(IrisBiome biome, Engine engine, RenderType type) {
|
||||
switch (type) {
|
||||
case BIOME, HEIGHT, CAVE_LAND, REGION, BIOME_SEA, BIOME_LAND, RIVER -> {
|
||||
case BIOME, HEIGHT, CAVE_LAND, REGION, BIOME_SEA, BIOME_LAND -> {
|
||||
return biome.getCacheColor().aquire(() -> {
|
||||
if (biome.getColor() == null) {
|
||||
RandomColor randomColor = new RandomColor(biome.getName().hashCode());
|
||||
|
||||
@@ -49,6 +49,9 @@ import lombok.experimental.Accessors;
|
||||
@Desc("Creates ore & other block deposits underground")
|
||||
@Data
|
||||
public class IrisDepositGenerator {
|
||||
private static final long FNV_OFFSET_BASIS = 0xcbf29ce484222325L;
|
||||
private static final long FNV_PRIME = 0x100000001b3L;
|
||||
|
||||
private final transient ConcurrentMap<ClumpCacheKey, KList<IrisObject>> objects = new ConcurrentHashMap<>();
|
||||
private final transient AtomicCache<KList<PlatformBlockState>> blockData = new AtomicCache<>();
|
||||
private final transient AtomicCache<Boolean> ore = new AtomicCache<>();
|
||||
@@ -135,7 +138,7 @@ public class IrisDepositGenerator {
|
||||
|
||||
ClumpCacheKey cacheKey = new ClumpCacheKey(engine.getSeedManager().getDeposit(), minSize, maxSize);
|
||||
KList<IrisObject> objects = this.objects.computeIfAbsent(cacheKey, key -> {
|
||||
RNG rngv = new RNG(key.depositSeed() + hashCode());
|
||||
RNG rngv = new RNG(key.depositSeed() + stableClumpSalt(rdata));
|
||||
KList<IrisObject> objectsf = new KList<>();
|
||||
|
||||
for (int i = 0; i < varience; i++) {
|
||||
@@ -163,7 +166,7 @@ public class IrisDepositGenerator {
|
||||
engine.getSeedManager().getDeposit(), scaledMinSize, scaledMaxSize);
|
||||
KList<IrisObject> objects = scaledObjects.computeIfAbsent(cacheKey, key -> {
|
||||
long sizeSeed = ((long) key.minSize() << 32) ^ (key.maxSize() & 0xffffffffL);
|
||||
RNG rngv = new RNG(key.depositSeed() + hashCode() + sizeSeed);
|
||||
RNG rngv = new RNG(key.depositSeed() + stableClumpSalt(rdata) + sizeSeed);
|
||||
KList<IrisObject> generated = new KList<>();
|
||||
|
||||
for (int i = 0; i < varience; i++) {
|
||||
@@ -184,6 +187,64 @@ public class IrisDepositGenerator {
|
||||
return Math.max(0, Math.min(8192, (int) Math.round(size * multiplier)));
|
||||
}
|
||||
|
||||
long stableClumpSalt(IrisData rdata) {
|
||||
long hash = FNV_OFFSET_BASIS;
|
||||
hash = mix(hash, minHeight);
|
||||
hash = mix(hash, maxHeight);
|
||||
hash = mixEnum(hash, heightDistribution);
|
||||
hash = mixEnum(hash, placementScope);
|
||||
hash = mix(hash, surfaceClearance);
|
||||
hash = mix(hash, minSize);
|
||||
hash = mix(hash, maxSize);
|
||||
hash = mixEnum(hash, shape);
|
||||
hash = mix(hash, maxPerChunk);
|
||||
hash = mix(hash, minPerChunk);
|
||||
hash = mix(hash, Double.doubleToLongBits(spawnChance));
|
||||
hash = mix(hash, Double.doubleToLongBits(perClumpSpawnChance));
|
||||
hash = mix(hash, Double.doubleToLongBits(discardChanceOnAirExposure));
|
||||
KList<PlatformBlockState> resolvedPalette = getBlockData(rdata);
|
||||
hash = mix(hash, resolvedPalette.size());
|
||||
for (PlatformBlockState block : resolvedPalette) {
|
||||
hash = mixString(hash, block == null ? null : block.key());
|
||||
}
|
||||
hash = mix(hash, varience);
|
||||
hash = mixStrings(hash, replaceableBlocks);
|
||||
hash = mixEnum(hash, biomeScope);
|
||||
hash = mixStrings(hash, includedBiomes);
|
||||
hash = mixStrings(hash, excludedBiomes);
|
||||
return mix(hash, replaceBedrock ? 1L : 0L);
|
||||
}
|
||||
|
||||
private static long mix(long hash, long value) {
|
||||
return (hash ^ value) * FNV_PRIME;
|
||||
}
|
||||
|
||||
private static long mixEnum(long hash, Enum<?> value) {
|
||||
return mixString(hash, value == null ? null : value.name());
|
||||
}
|
||||
|
||||
private static long mixString(long hash, String value) {
|
||||
if (value == null) {
|
||||
return mix(hash, -1L);
|
||||
}
|
||||
long mixed = mix(hash, value.length());
|
||||
for (int i = 0; i < value.length(); i++) {
|
||||
mixed = mix(mixed, value.charAt(i));
|
||||
}
|
||||
return mixed;
|
||||
}
|
||||
|
||||
private static long mixStrings(long hash, KList<String> values) {
|
||||
if (values == null) {
|
||||
return mix(hash, -1L);
|
||||
}
|
||||
long mixed = mix(hash, values.size());
|
||||
for (String value : values) {
|
||||
mixed = mixString(mixed, value);
|
||||
}
|
||||
return mixed;
|
||||
}
|
||||
|
||||
private IrisObject generateConfiguredClumpObject(RNG rng, IrisData rdata, int clumpMinSize, int clumpMaxSize) {
|
||||
int size = rng.i(clumpMinSize, clumpMaxSize + 1);
|
||||
return switch (shape) {
|
||||
|
||||
@@ -165,8 +165,6 @@ public class IrisDimension extends IrisRegistrant {
|
||||
private KList<IrisDimensionCarvingEntry> carving = new KList<>();
|
||||
@Desc("Profile-driven 3D cave configuration")
|
||||
private IrisCaveProfile caveProfile = new IrisCaveProfile();
|
||||
@Desc("Connected surface rivers and contained river cave-water generation.")
|
||||
private IrisRiverNetwork rivers = new IrisRiverNetwork();
|
||||
@Desc("Refuse to place surface objects and trees over carved surface openings.")
|
||||
private boolean requireObjectSurfaceSupport = true;
|
||||
@MinNumber(0)
|
||||
@@ -515,17 +513,11 @@ public class IrisDimension extends IrisRegistrant {
|
||||
}
|
||||
|
||||
Deque<String> pending = new ArrayDeque<>();
|
||||
IrisRiverNetwork riverNetwork = getRivers();
|
||||
boolean riversEnabled = riverNetwork != null && riverNetwork.isEnabled();
|
||||
if (riversEnabled && riverNetwork.getBiomes() != null) {
|
||||
addReachableBiomeKeys(pending, riverNetwork.getBiomes().getAllBiomeIds());
|
||||
}
|
||||
for (IrisRegion region : getAllRegions(g)) {
|
||||
if (region == null) {
|
||||
continue;
|
||||
}
|
||||
addReachableBiomeKeys(pending,
|
||||
riversEnabled ? region.getAllBiomeIds() : region.getNaturalBiomeIds());
|
||||
addReachableBiomeKeys(pending, region.getAllBiomeIds());
|
||||
}
|
||||
for (IrisImageMapBinding binding : getImageMaps()) {
|
||||
if (binding == null || binding.getApplication() != IrisImageMapApplication.BIOME) {
|
||||
@@ -571,10 +563,6 @@ public class IrisDimension extends IrisRegistrant {
|
||||
biomes.put(loadKey, biome);
|
||||
addReachableBiomeKeys(pending, biome.getChildren());
|
||||
addReachableBiomeKey(pending, biome.getCarvingBiome());
|
||||
if (riversEnabled && biome.getRiverOverride() != null) {
|
||||
addReachableBiomeKeys(pending, biome.getRiverOverride().getAllBiomeIds());
|
||||
}
|
||||
|
||||
KList<IrisFloatingChildBiomes> floatingChildren = biome.getFloatingChildBiomes();
|
||||
if (floatingChildren == null) {
|
||||
continue;
|
||||
|
||||
@@ -28,9 +28,6 @@ public enum IrisEngineStreamType {
|
||||
@Desc("Represents the given slope at the x, z coordinates")
|
||||
SLOPE((f) -> f.getComplex().getSlopeStream()),
|
||||
|
||||
@Desc("Represents terrain height before river incision and river biome replacement.")
|
||||
NATURAL_HEIGHT((f) -> f.getComplex().getNaturalHeightStream()),
|
||||
|
||||
@Desc("Represents the base generator height at the given position. This includes only the biome generators / interpolation and noise features but does not include carving, caves.")
|
||||
HEIGHT((f) -> f.getComplex().getHeightStream()),
|
||||
|
||||
@@ -44,19 +41,7 @@ public enum IrisEngineStreamType {
|
||||
REGION_STYLE((f) -> f.getComplex().getRegionStyleStream()),
|
||||
|
||||
@Desc("Represents the identity of regions. Each region has a unique number (very large numbers)")
|
||||
REGION_IDENTITY((f) -> f.getComplex().getRegionIdentityStream()),
|
||||
|
||||
@Desc("Represents block distance from the nearest active river centerline.")
|
||||
RIVER_DISTANCE((f) -> f.getComplex().getRiverDistanceStream()),
|
||||
|
||||
@Desc("Represents the merged upstream flow carried by the active river reach.")
|
||||
RIVER_FLOW((f) -> f.getComplex().getRiverFlowStream()),
|
||||
|
||||
@Desc("Represents the normalized river terrain-incision weight.")
|
||||
RIVER_CARVE_WEIGHT((f) -> f.getComplex().getRiverCarveWeightStream()),
|
||||
|
||||
@Desc("Represents the solved river water-surface height.")
|
||||
RIVER_WATER_SURFACE((f) -> f.getComplex().getRiverWaterSurfaceStream());
|
||||
REGION_IDENTITY((f) -> f.getComplex().getRegionIdentityStream());
|
||||
|
||||
private final Function<Engine, ProceduralStream<Double>> getter;
|
||||
|
||||
|
||||
@@ -52,9 +52,7 @@ public class IrisInterpolator {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
// Bit-identical to Objects.hash(horizontalScale, function) without the Object[] + Double boxing.
|
||||
// The exact value is load bearing: it decides HashMap bucket order for the generator maps in
|
||||
// IrisComplex, and that order fixes the floating point summation order of interpolated heights.
|
||||
// Bit-identical to Objects.hash(horizontalScale, function) without the Object[] or Double boxing.
|
||||
int result = 31 + Double.hashCode(horizontalScale);
|
||||
return (31 * result) + (function == null ? 0 : function.hashCode());
|
||||
}
|
||||
|
||||
@@ -20,21 +20,27 @@ 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.data.cache.LazyBoundedCache;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.annotations.ArrayType;
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
import art.arcane.iris.engine.object.annotations.MinNumber;
|
||||
import art.arcane.iris.engine.object.annotations.Required;
|
||||
import art.arcane.iris.engine.object.annotations.Snippet;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import art.arcane.iris.spi.PlatformBlockState;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@Snippet("palette")
|
||||
@Accessors(chain = true)
|
||||
@@ -43,8 +49,17 @@ import java.util.Optional;
|
||||
@Desc("A palette of materials")
|
||||
@Data
|
||||
public class IrisMaterialPalette {
|
||||
private static final int LAYER_GENERATOR_CACHE_SIZE = 32;
|
||||
private static final int LAYER_GENERATOR_SALT = -23_498_896;
|
||||
|
||||
private final transient AtomicCache<KList<PlatformBlockState>> blockData = new AtomicCache<>();
|
||||
private final transient AtomicCache<CNG> layerGenerator = new AtomicCache<>();
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private final transient LazyBoundedCache<LayerGeneratorKey, CNG> layerGenerators =
|
||||
new LazyBoundedCache<>(LAYER_GENERATOR_CACHE_SIZE);
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private final transient AtomicReference<CachedLayerGenerator> recentLayerGenerator = new AtomicReference<>();
|
||||
private final transient AtomicCache<CNG> heightGenerator = new AtomicCache<>();
|
||||
@Desc("The style of noise")
|
||||
private IrisGeneratorStyle style = NoiseStyle.STATIC.style();
|
||||
@@ -82,11 +97,21 @@ public class IrisMaterialPalette {
|
||||
}
|
||||
|
||||
public CNG getLayerGenerator(RNG rng, IrisData rdata) {
|
||||
return layerGenerator.aquire(() ->
|
||||
{
|
||||
RNG rngx = rng.nextParallelRNG(-23498896 + getBlockData(rdata).size());
|
||||
return style.create(rngx, rdata);
|
||||
});
|
||||
Engine engine = rdata == null ? null : rdata.getEngine();
|
||||
int generatorSignature = LAYER_GENERATOR_SALT + getBlockData(rdata).size();
|
||||
long generatorSeed = rng.getSeed() + generatorSignature;
|
||||
CachedLayerGenerator recent = recentLayerGenerator.get();
|
||||
if (recent != null && recent.key.matches(rdata, engine, generatorSeed)) {
|
||||
return recent.generator;
|
||||
}
|
||||
|
||||
LayerGeneratorKey key = new LayerGeneratorKey(rdata, engine, generatorSeed);
|
||||
CNG generator = layerGenerators.computeIfAbsent(key,
|
||||
ignored -> style.create(new RNG(generatorSeed), rdata, engine));
|
||||
if (generator != null) {
|
||||
recentLayerGenerator.set(new CachedLayerGenerator(key, generator));
|
||||
}
|
||||
return generator;
|
||||
}
|
||||
|
||||
public IrisMaterialPalette qclear() {
|
||||
@@ -127,4 +152,49 @@ public class IrisMaterialPalette {
|
||||
palette.clear();
|
||||
return this;
|
||||
}
|
||||
|
||||
private static final class LayerGeneratorKey {
|
||||
private final IrisData data;
|
||||
private final Engine engine;
|
||||
private final long seed;
|
||||
|
||||
private LayerGeneratorKey(IrisData data, Engine engine, long seed) {
|
||||
this.data = data;
|
||||
this.engine = engine;
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
private boolean matches(IrisData data, Engine engine, long seed) {
|
||||
return this.data == data && this.engine == engine && this.seed == seed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
}
|
||||
if (!(object instanceof LayerGeneratorKey other)) {
|
||||
return false;
|
||||
}
|
||||
return data == other.data && engine == other.engine && seed == other.seed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = System.identityHashCode(data);
|
||||
result = 31 * result + System.identityHashCode(engine);
|
||||
result = 31 * result + Long.hashCode(seed);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CachedLayerGenerator {
|
||||
private final LayerGeneratorKey key;
|
||||
private final CNG generator;
|
||||
|
||||
private CachedLayerGenerator(LayerGeneratorKey key, CNG generator) {
|
||||
this.key = key;
|
||||
this.generator = generator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
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.data.cache.LazyBoundedCache;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.object.annotations.ArrayType;
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
import art.arcane.iris.engine.object.annotations.MaxNumber;
|
||||
@@ -30,11 +31,16 @@ import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.iris.util.project.interpolation.IrisInterpolation;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
@Snippet("generator")
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@@ -42,7 +48,16 @@ import lombok.experimental.Accessors;
|
||||
@Desc("A noise generator")
|
||||
@Data
|
||||
public class IrisNoiseGenerator {
|
||||
private final transient AtomicCache<CNG> generator = new AtomicCache<>();
|
||||
private static final int GENERATOR_CACHE_SIZE = 32;
|
||||
private static final long GENERATOR_SEED_SALT = 33_955_677L;
|
||||
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private final transient LazyBoundedCache<GeneratorKey, CNG> generators =
|
||||
new LazyBoundedCache<>(GENERATOR_CACHE_SIZE);
|
||||
@Getter(AccessLevel.NONE)
|
||||
@Setter(AccessLevel.NONE)
|
||||
private final transient AtomicReference<CachedGenerator> recentGenerator = new AtomicReference<>();
|
||||
@MinNumber(0.0001)
|
||||
@Desc("The coordinate input zoom")
|
||||
private double zoom = 1;
|
||||
@@ -87,7 +102,20 @@ public class IrisNoiseGenerator {
|
||||
}
|
||||
|
||||
protected CNG getGenerator(long superSeed, IrisData data) {
|
||||
return generator.aquire(() -> style.create(new RNG(superSeed + 33955677 - seed), data).oct(octaves));
|
||||
Engine engine = data == null ? null : data.getEngine();
|
||||
long generatorSeed = superSeed + GENERATOR_SEED_SALT - seed;
|
||||
CachedGenerator recent = recentGenerator.get();
|
||||
if (recent != null && recent.key.matches(data, engine, generatorSeed)) {
|
||||
return recent.generator;
|
||||
}
|
||||
|
||||
GeneratorKey key = new GeneratorKey(data, engine, generatorSeed);
|
||||
CNG generator = generators.computeIfAbsent(key,
|
||||
ignored -> style.createNoCache(new RNG(generatorSeed), data).oct(octaves));
|
||||
if (generator != null) {
|
||||
recentGenerator.set(new CachedGenerator(key, generator));
|
||||
}
|
||||
return generator;
|
||||
}
|
||||
|
||||
public double getMax() {
|
||||
@@ -136,4 +164,49 @@ public class IrisNoiseGenerator {
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
private static final class GeneratorKey {
|
||||
private final IrisData data;
|
||||
private final Engine engine;
|
||||
private final long seed;
|
||||
|
||||
private GeneratorKey(IrisData data, Engine engine, long seed) {
|
||||
this.data = data;
|
||||
this.engine = engine;
|
||||
this.seed = seed;
|
||||
}
|
||||
|
||||
private boolean matches(IrisData data, Engine engine, long seed) {
|
||||
return this.data == data && this.engine == engine && this.seed == seed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
}
|
||||
if (!(object instanceof GeneratorKey other)) {
|
||||
return false;
|
||||
}
|
||||
return data == other.data && engine == other.engine && seed == other.seed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = System.identityHashCode(data);
|
||||
result = 31 * result + System.identityHashCode(engine);
|
||||
result = 31 * result + Long.hashCode(seed);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CachedGenerator {
|
||||
private final GeneratorKey key;
|
||||
private final CNG generator;
|
||||
|
||||
private CachedGenerator(GeneratorKey key, CNG generator) {
|
||||
this.key = key;
|
||||
this.generator = generator;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,8 +332,7 @@ final class IrisObjectPlacementRunner {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!config.isForcePlace() && !rawStructurePiece && config.isUnderwater()
|
||||
&& y + rty + ty >= placer.getFluidHeight(x, z)) {
|
||||
if (!config.isForcePlace() && !rawStructurePiece && config.isUnderwater() && y + rty + ty >= placer.getFluidHeight()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -118,8 +118,6 @@ public class IrisRegion extends IrisRegistrant implements IRare {
|
||||
private double caveBiomeZoom = 1;
|
||||
@Desc("Profile-driven 3D cave configuration")
|
||||
private IrisCaveProfile caveProfile = new IrisCaveProfile();
|
||||
@Desc("Region-level river routing, shape, cave-entry, and biome-pool overrides. Omit to inherit dimension settings.")
|
||||
private IrisRiverOverride riverOverride = null;
|
||||
@RegistryListResource(IrisBiome.class)
|
||||
@Required
|
||||
@ArrayType(min = 1, type = String.class)
|
||||
@@ -279,33 +277,18 @@ public class IrisRegion extends IrisRegistrant implements IRare {
|
||||
}
|
||||
|
||||
public KSet<String> getAllBiomeIds() {
|
||||
KSet<String> names = getNaturalBiomeIds();
|
||||
if (riverOverride != null) {
|
||||
names.addAll(riverOverride.getAllBiomeIds());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
public KSet<String> getNaturalBiomeIds() {
|
||||
KSet<String> names = new KSet<>();
|
||||
names.addAll(landBiomes);
|
||||
names.addAll(caveBiomes);
|
||||
names.addAll(seaBiomes);
|
||||
names.addAll(shoreBiomes);
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
public KList<IrisBiome> getAllBiomes(DataProvider g) {
|
||||
return resolveBiomes(g, getAllBiomeIds());
|
||||
}
|
||||
|
||||
public KList<IrisBiome> getNaturalBiomes(DataProvider g) {
|
||||
return resolveBiomes(g, getNaturalBiomeIds());
|
||||
}
|
||||
|
||||
private KList<IrisBiome> resolveBiomes(DataProvider g, KSet<String> biomeIds) {
|
||||
KMap<String, IrisBiome> b = new KMap<>();
|
||||
KSet<String> names = biomeIds.copy();
|
||||
KSet<String> names = getAllBiomeIds();
|
||||
|
||||
while (!names.isEmpty()) {
|
||||
for (String i : new KList<>(names)) {
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.object.annotations.ArrayType;
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
import art.arcane.iris.engine.object.annotations.RegistryListResource;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KSet;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@Desc("Biome pools used by dimension-level river sections and contained river caves.")
|
||||
@Data
|
||||
public class IrisRiverBiomes {
|
||||
@Desc("Noise used to select a biome inside the active river-section pool.")
|
||||
private IrisGeneratorStyle selectionStyle = new IrisGeneratorStyle(NoiseStyle.CELLULAR_IRIS_DOUBLE)
|
||||
.zoomed(512D);
|
||||
|
||||
@RegistryListResource(IrisBiome.class)
|
||||
@ArrayType(type = String.class)
|
||||
@Desc("Biome pool for wet river channels.")
|
||||
private KList<String> channel = new KList<>();
|
||||
|
||||
@RegistryListResource(IrisBiome.class)
|
||||
@ArrayType(type = String.class)
|
||||
@Desc("Biome pool for river banks outside the wet channel.")
|
||||
private KList<String> bank = new KList<>();
|
||||
|
||||
@RegistryListResource(IrisBiome.class)
|
||||
@ArrayType(type = String.class)
|
||||
@Desc("Biome pool for river reaches meeting natural sea.")
|
||||
private KList<String> mouth = new KList<>();
|
||||
|
||||
@RegistryListResource(IrisBiome.class)
|
||||
@ArrayType(type = String.class)
|
||||
@Desc("Biome pool for dry river channels and terminal tapers.")
|
||||
private KList<String> dry = new KList<>();
|
||||
|
||||
@RegistryListResource(IrisBiome.class)
|
||||
@ArrayType(type = String.class)
|
||||
@Desc("Cave biome pool for accepted contained river cave bodies.")
|
||||
private KList<String> floodedCave = new KList<>();
|
||||
|
||||
public KSet<String> getAllBiomeIds() {
|
||||
KSet<String> biomeIds = new KSet<>();
|
||||
addAll(biomeIds, channel);
|
||||
addAll(biomeIds, bank);
|
||||
addAll(biomeIds, mouth);
|
||||
addAll(biomeIds, dry);
|
||||
addAll(biomeIds, floodedCave);
|
||||
return biomeIds;
|
||||
}
|
||||
|
||||
private static void addAll(KSet<String> destination, KList<String> source) {
|
||||
if (source != null) {
|
||||
destination.addAll(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
|
||||
@Desc("Selects the fallback when a requested river cave connection cannot be proven safe.")
|
||||
public enum IrisRiverCaveFallback {
|
||||
@Desc("Keep the rejected connection sealed.")
|
||||
SEALED,
|
||||
|
||||
@Desc("Try a bounded generated grotto instead of the rejected existing cave.")
|
||||
GENERATE_GROTTO
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
|
||||
@Desc("Selects the contained cave-water behavior available to river entry events.")
|
||||
public enum IrisRiverCaveMode {
|
||||
@Desc("Keep the surface reservoir sealed from caves.")
|
||||
SEALED,
|
||||
|
||||
@Desc("Flood only an existing cave component whose complete fluid-reachable boundary is proven closed.")
|
||||
FLOOD_CLOSED_COMPONENT,
|
||||
|
||||
@Desc("Generate a bounded grotto with a guaranteed solid shell.")
|
||||
GENERATE_GROTTO,
|
||||
|
||||
@Desc("Use a proven closed cave component when available, otherwise generate a bounded grotto.")
|
||||
GROTTO_OR_CLOSED_COMPONENT,
|
||||
|
||||
@Desc("Generate a controlled falling column into a proven contained pool.")
|
||||
WATERFALL_POOL
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
import art.arcane.iris.engine.object.annotations.MaxNumber;
|
||||
import art.arcane.iris.engine.object.annotations.MinNumber;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@Desc("Controls bounded and transactionally validated river-to-cave connections.")
|
||||
@Data
|
||||
public class IrisRiverCaves {
|
||||
@Desc("The contained cave-water behavior available to river entry events.")
|
||||
private IrisRiverCaveMode mode = IrisRiverCaveMode.SEALED;
|
||||
|
||||
@Desc("Selects cave-entry stations at stable river-reach anchors.")
|
||||
private IrisRiverNoiseChance entry = new IrisRiverNoiseChance()
|
||||
.setChance(0.12D)
|
||||
.setStyle(new IrisGeneratorStyle(NoiseStyle.IRIS).zoomed(1024D))
|
||||
.setInfluence(0.4D);
|
||||
|
||||
@MinNumber(16)
|
||||
@MaxNumber(4096)
|
||||
@Desc("The minimum distance in blocks between cave-entry candidates.")
|
||||
private int minimumSpacing = 128;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(16)
|
||||
@Desc("The maximum entry-noise-eligible cave anchors accepted on one ordinary reach. A forced sinkhole terminal uses its reach exclusively and requires this value above zero.")
|
||||
private int maximumPerReach = 1;
|
||||
|
||||
@MinNumber(1)
|
||||
@MaxNumber(256)
|
||||
@Desc("The maximum vertical distance searched while boring from river bed to cave.")
|
||||
private int maxBoreDepth = 48;
|
||||
|
||||
@MinNumber(1)
|
||||
@MaxNumber(16)
|
||||
@Desc("The radius in blocks of a generated river-to-cave throat.")
|
||||
private int throatRadius = 2;
|
||||
|
||||
@MinNumber(-64)
|
||||
@MaxNumber(64)
|
||||
@Desc("The offset applied to river water height when filling an accepted cave body.")
|
||||
private int waterLevelOffset = 0;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(64)
|
||||
@Desc("The minimum dry headroom retained above water in generated grottos.")
|
||||
private int dryHeadroom = 4;
|
||||
|
||||
@MinNumber(2)
|
||||
@MaxNumber(128)
|
||||
@Desc("The horizontal radius in blocks of a generated sealed grotto.")
|
||||
private int grottoHorizontalRadius = 12;
|
||||
|
||||
@MinNumber(2)
|
||||
@MaxNumber(128)
|
||||
@Desc("The vertical radius in blocks of a generated sealed grotto.")
|
||||
private int grottoVerticalRadius = 7;
|
||||
|
||||
@Desc("Noise shaping the boundary of generated sealed grottos.")
|
||||
private IrisGeneratorStyle grottoShapeStyle = new IrisGeneratorStyle(NoiseStyle.IRIS).zoomed(24D);
|
||||
|
||||
@Desc("Noise warping the coordinate field used for generated sealed grottos.")
|
||||
private IrisGeneratorStyle grottoWarpStyle = new IrisGeneratorStyle(NoiseStyle.IRIS).zoomed(48D);
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(32)
|
||||
@Desc("The maximum coordinate warp applied to generated sealed grottos in blocks.")
|
||||
private double grottoWarpStrength = 2D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1)
|
||||
@Desc("The fraction of river-cave boundary columns that inherit the naturally resolved parent cave or surface biome instead of a flooded-cave override.")
|
||||
private double parentBiomeInheritance = 0.5D;
|
||||
|
||||
@MinNumber(4)
|
||||
@MaxNumber(256)
|
||||
@Desc("The horizontal proof radius for an existing closed cave component.")
|
||||
private int maxFloodRadius = 48;
|
||||
|
||||
@MinNumber(4)
|
||||
@MaxNumber(256)
|
||||
@Desc("The vertical proof depth for an existing closed cave component.")
|
||||
private int maxFloodDepth = 32;
|
||||
|
||||
@MinNumber(64)
|
||||
@MaxNumber(1048576)
|
||||
@Desc("The greatest cave-component volume that may be fully proven and flooded.")
|
||||
private int maxFloodVolume = 8192;
|
||||
|
||||
@Desc("The behavior used when a requested cave connection cannot be proven safe.")
|
||||
private IrisRiverCaveFallback fallback = IrisRiverCaveFallback.SEALED;
|
||||
|
||||
@Desc("The policy for fluid already present in a candidate contained cave body.")
|
||||
private IrisRiverExistingFluidPolicy existingFluidPolicy = IrisRiverExistingFluidPolicy.REJECT;
|
||||
|
||||
@Desc("Sparse, independently filled cave-floor pools generated at deep river-network anchors.")
|
||||
private IrisRiverDeepPools deepPools = new IrisRiverDeepPools();
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
import art.arcane.iris.engine.object.annotations.MaxNumber;
|
||||
import art.arcane.iris.engine.object.annotations.MinNumber;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@Desc("Controls sparse, river-anchored fluid pools attached to deep cave floors.")
|
||||
@Data
|
||||
public class IrisRiverDeepPools {
|
||||
@Desc("Enables independently configured deep cave pools along eligible wet river reaches.")
|
||||
private boolean enabled = false;
|
||||
|
||||
@Desc("Selects complete wet river reaches that may host deep pools.")
|
||||
private IrisRiverNoiseChance reach = new IrisRiverNoiseChance()
|
||||
.setChance(1D / 3D)
|
||||
.setStyle(new IrisGeneratorStyle(NoiseStyle.IRIS).zoomed(4096D))
|
||||
.setInfluence(0.08D);
|
||||
|
||||
@MinNumber(16)
|
||||
@MaxNumber(4096)
|
||||
@Desc("The minimum distance in blocks between deep-pool candidates.")
|
||||
private int minimumSpacing = 768;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(16)
|
||||
@Desc("The maximum accepted deep pools on one river reach.")
|
||||
private int maximumPerReach = 1;
|
||||
|
||||
@MinNumber(-2048)
|
||||
@MaxNumber(2048)
|
||||
@Desc("The lowest absolute world Y considered for the pool fluid surface.")
|
||||
private int minimumFluidY = -224;
|
||||
|
||||
@MinNumber(-2048)
|
||||
@MaxNumber(2048)
|
||||
@Desc("The highest absolute world Y considered for the pool fluid surface.")
|
||||
private int maximumFluidY = -104;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(256)
|
||||
@Desc("The horizontal distance searched from a river anchor for a contained cave floor.")
|
||||
private int searchRadius = 16;
|
||||
|
||||
@MinNumber(1)
|
||||
@MaxNumber(64)
|
||||
@Desc("The number of deterministic nearby columns tested for a contained cave floor.")
|
||||
private int searchAttempts = 12;
|
||||
|
||||
@MinNumber(2)
|
||||
@MaxNumber(128)
|
||||
@Desc("The horizontal radius of the generated deep-pool chamber.")
|
||||
private int horizontalRadius = 18;
|
||||
|
||||
@MinNumber(2)
|
||||
@MaxNumber(64)
|
||||
@Desc("The vertical radius of the generated deep-pool chamber.")
|
||||
private int verticalRadius = 8;
|
||||
|
||||
@MinNumber(1)
|
||||
@MaxNumber(63)
|
||||
@Desc("The dry chamber height retained above the deep-pool fluid surface.")
|
||||
private int dryHeadroom = 4;
|
||||
|
||||
@Desc("Noise shaping the deep-pool chamber boundary.")
|
||||
private IrisGeneratorStyle shapeStyle = new IrisGeneratorStyle(NoiseStyle.IRIS).zoomed(12D);
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(0.75)
|
||||
@Desc("The proportional noise displacement applied to the deep-pool chamber boundary.")
|
||||
private double shapeVariation = 0.5D;
|
||||
|
||||
@Desc("Noise warping the deep-pool chamber coordinate field.")
|
||||
private IrisGeneratorStyle warpStyle = new IrisGeneratorStyle(NoiseStyle.IRIS).zoomed(24D);
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(64)
|
||||
@Desc("The maximum coordinate warp applied to the deep-pool chamber in blocks.")
|
||||
private double warpStrength = 6D;
|
||||
|
||||
@MinNumber(64)
|
||||
@MaxNumber(1048576)
|
||||
@Desc("The greatest generated deep-pool chamber volume that may be transactionally published.")
|
||||
private int maximumVolume = 32768;
|
||||
|
||||
@Desc("The fluid palette used only by accepted deep pools.")
|
||||
private IrisMaterialPalette fluidPalette = new IrisMaterialPalette().qclear().qadd("lava");
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
|
||||
@Desc("Controls how river cave hydrology treats fluid already present in a candidate cave body.")
|
||||
public enum IrisRiverExistingFluidPolicy {
|
||||
@Desc("Reject a candidate containing any existing fluid.")
|
||||
REJECT,
|
||||
|
||||
@Desc("Accept only fluid compatible with the river fluid palette.")
|
||||
ALLOW_SAME,
|
||||
|
||||
@Desc("Replace contained existing fluid with the river fluid palette.")
|
||||
REPLACE
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@Desc("Dimension-owned configuration for connected surface rivers and contained river cave water.")
|
||||
@Data
|
||||
public class IrisRiverNetwork {
|
||||
@Desc("Enable the river network for this dimension.")
|
||||
private boolean enabled = false;
|
||||
|
||||
@Desc("Dimension-owned connected graph and source-selection settings.")
|
||||
private IrisRiverTopology topology = new IrisRiverTopology();
|
||||
|
||||
@Desc("Channel geometry, terrain incision, meanders, and terminal behavior.")
|
||||
private IrisRiverTerrain terrain = new IrisRiverTerrain();
|
||||
|
||||
@Desc("River water-surface settings.")
|
||||
private IrisRiverWater water = new IrisRiverWater();
|
||||
|
||||
@Desc("Dimension-level biome pools for river sections and contained river caves.")
|
||||
private IrisRiverBiomes biomes = new IrisRiverBiomes();
|
||||
|
||||
@Desc("Bounded river-to-cave connection settings.")
|
||||
private IrisRiverCaves caves = new IrisRiverCaves();
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
import art.arcane.iris.engine.object.annotations.MaxNumber;
|
||||
import art.arcane.iris.engine.object.annotations.MinNumber;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@Desc("A deterministic graph-event chance modulated by configurable noise.")
|
||||
@Data
|
||||
public class IrisRiverNoiseChance {
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1)
|
||||
@Desc("The base probability before noise modulation.")
|
||||
private double chance = 1D;
|
||||
|
||||
@Desc("The noise sampled once at the stable graph-event anchor.")
|
||||
private IrisGeneratorStyle style = new IrisGeneratorStyle(NoiseStyle.FLAT);
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1)
|
||||
@Desc("The maximum centered noise contribution added to the base probability.")
|
||||
private double influence = 0D;
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.object.annotations.ArrayType;
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
import art.arcane.iris.engine.object.annotations.MaxNumber;
|
||||
import art.arcane.iris.engine.object.annotations.MinNumber;
|
||||
import art.arcane.iris.engine.object.annotations.RegistryListResource;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KSet;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@Desc("Nullable river settings overridden by a region or natural biome without changing graph identity.")
|
||||
@Data
|
||||
public class IrisRiverOverride {
|
||||
@Desc("Whether new river sources may begin in this area. Existing trunks are unaffected.")
|
||||
private Boolean allowSources = null;
|
||||
|
||||
@Desc("How downstream routing treats this area.")
|
||||
private IrisRiverRoutingPolicy routingPolicy = null;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(64)
|
||||
@Desc("Multiplier applied to downstream routing cost.")
|
||||
private Double routingCostMultiplier = null;
|
||||
|
||||
@MinNumber(0.0001)
|
||||
@MaxNumber(16)
|
||||
@Desc("Multiplier applied to wet channel width.")
|
||||
private Double widthMultiplier = null;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(16)
|
||||
@Desc("Multiplier applied to river bank width.")
|
||||
private Double bankWidthMultiplier = null;
|
||||
|
||||
@MinNumber(0.0001)
|
||||
@MaxNumber(16)
|
||||
@Desc("Multiplier applied to river-bed depth.")
|
||||
private Double depthMultiplier = null;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(16)
|
||||
@Desc("Multiplier applied to maximum terrain incision.")
|
||||
private Double maxIncisionMultiplier = null;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(16)
|
||||
@Desc("Multiplier applied to reach continuation probability.")
|
||||
private Double continuationChanceMultiplier = null;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(16)
|
||||
@Desc("Multiplier applied to cave-entry probability.")
|
||||
private Double caveEntryMultiplier = null;
|
||||
|
||||
@Desc("Optional terminal behavior override for failed routes in this area.")
|
||||
private IrisRiverTerminalMode terminalMode = null;
|
||||
|
||||
@RegistryListResource(IrisBiome.class)
|
||||
@ArrayType(type = String.class)
|
||||
@Desc("Optional replacement biome pool for wet river channels. Empty explicitly disables this pool.")
|
||||
private KList<String> channelBiomes = null;
|
||||
|
||||
@RegistryListResource(IrisBiome.class)
|
||||
@ArrayType(type = String.class)
|
||||
@Desc("Optional replacement biome pool for river banks. Empty explicitly disables this pool.")
|
||||
private KList<String> bankBiomes = null;
|
||||
|
||||
@RegistryListResource(IrisBiome.class)
|
||||
@ArrayType(type = String.class)
|
||||
@Desc("Optional replacement biome pool for river mouths. Empty explicitly disables this pool.")
|
||||
private KList<String> mouthBiomes = null;
|
||||
|
||||
@RegistryListResource(IrisBiome.class)
|
||||
@ArrayType(type = String.class)
|
||||
@Desc("Optional replacement biome pool for dry river channels. Empty explicitly disables this pool.")
|
||||
private KList<String> dryBiomes = null;
|
||||
|
||||
@RegistryListResource(IrisBiome.class)
|
||||
@ArrayType(type = String.class)
|
||||
@Desc("Optional replacement cave biome pool for accepted contained river cave bodies. Empty explicitly disables this pool.")
|
||||
private KList<String> floodedCaveBiomes = null;
|
||||
|
||||
public KSet<String> getAllBiomeIds() {
|
||||
KSet<String> biomeIds = new KSet<>();
|
||||
addAll(biomeIds, channelBiomes);
|
||||
addAll(biomeIds, bankBiomes);
|
||||
addAll(biomeIds, mouthBiomes);
|
||||
addAll(biomeIds, dryBiomes);
|
||||
addAll(biomeIds, floodedCaveBiomes);
|
||||
return biomeIds;
|
||||
}
|
||||
|
||||
private static void addAll(KSet<String> destination, KList<String> source) {
|
||||
if (source != null) {
|
||||
destination.addAll(source);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
|
||||
@Desc("Controls how river routing treats a region or biome.")
|
||||
public enum IrisRiverRoutingPolicy {
|
||||
@Desc("Allow normal river routing through this area.")
|
||||
ALLOW,
|
||||
|
||||
@Desc("Increase the routing cost while still permitting established river trunks.")
|
||||
AVOID,
|
||||
|
||||
@Desc("Forbid river reaches from crossing this area.")
|
||||
BLOCK
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
|
||||
@Desc("Selects how a river route ends when it cannot continue to an outlet.")
|
||||
public enum IrisRiverTerminalMode {
|
||||
@Desc("Suppress the failed route instead of generating it.")
|
||||
SUPPRESS,
|
||||
|
||||
@Desc("Continue as a dry channel that tapers back into natural terrain.")
|
||||
DRY_CHANNEL,
|
||||
|
||||
@Desc("End in a contained underground grotto when cave hydrology accepts the connection.")
|
||||
SINKHOLE_GROTTO
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.object.annotations.ArrayType;
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
import art.arcane.iris.engine.object.annotations.MaxNumber;
|
||||
import art.arcane.iris.engine.object.annotations.MinNumber;
|
||||
import art.arcane.iris.engine.object.annotations.Required;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@Desc("Controls river channel geometry, banks, incision, meanders, and terminal tapering.")
|
||||
@Data
|
||||
public class IrisRiverTerrain {
|
||||
@Desc("The wet channel width in blocks before stream-order scaling.")
|
||||
private IrisStyledRange channelWidth = range(8D, 20D, NoiseStyle.IRIS, 1024D);
|
||||
|
||||
@Desc("The bank width outside the wet channel in blocks.")
|
||||
private IrisStyledRange bankWidth = range(5D, 18D, NoiseStyle.IRIS, 1024D);
|
||||
|
||||
@Desc("The wet-bed depth below the local water surface, or dry-channel depth below natural terrain, in blocks.")
|
||||
private IrisStyledRange depth = range(2D, 7D, NoiseStyle.IRIS, 768D);
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(64)
|
||||
@Desc("The radius added to every channel after worm, regional, local, and stream-order shaping.")
|
||||
private double channelRadiusBonus = 0D;
|
||||
|
||||
@MinNumber(1)
|
||||
@MaxNumber(2048)
|
||||
@Desc("The final wet-channel width cap after region, biome, and stream-order scaling.")
|
||||
private double maxChannelWidth = 10D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(2048)
|
||||
@Desc("The final bank-width cap on each side after region and biome scaling.")
|
||||
private double maxBankWidth = 4D;
|
||||
|
||||
@MinNumber(1)
|
||||
@MaxNumber(512)
|
||||
@Desc("The final river-depth cap after region, biome, and stream-order scaling.")
|
||||
private double maxDepth = 10D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(8)
|
||||
@Desc("Additional channel-width fraction applied for each merged upstream flow order.")
|
||||
private double orderWidthFactor = 0.35D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(8)
|
||||
@Desc("Additional river-bed depth fraction applied for each merged upstream flow order.")
|
||||
private double orderDepthFactor = 0.2D;
|
||||
|
||||
@Desc("Selects whether a complete graph reach may incise terrain. A rejected reach follows terminal behavior.")
|
||||
private IrisRiverNoiseChance incision = new IrisRiverNoiseChance();
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(512)
|
||||
@Desc("The greatest permitted vertical incision below natural terrain.")
|
||||
private int maxIncision = 48;
|
||||
|
||||
@MinNumber(0.125)
|
||||
@MaxNumber(16)
|
||||
@Desc("The exponent shaping the channel-to-bank cross-section transition.")
|
||||
private double bankExponent = 2D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(16)
|
||||
@Desc("The longitudinal transition length and maximum lateral and roof flare where a surface river enters or exits solid terrain.")
|
||||
private double tunnelMouthBlend = 2D;
|
||||
|
||||
@Desc("Noise modulating the submerged floor of river tunnels.")
|
||||
private IrisGeneratorStyle tunnelFloorStyle = new IrisGeneratorStyle(NoiseStyle.IRIS).zoomed(48D);
|
||||
|
||||
@Desc("The subterranean tunnel width multiplier relative to the surface river width.")
|
||||
private IrisStyledRange tunnelWidthMultiplier = range(1D, 1D, NoiseStyle.FLAT, 1D);
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(8)
|
||||
@Desc("The maximum vertical floor variation in river tunnels.")
|
||||
private double tunnelFloorVariation = 2D;
|
||||
|
||||
@Desc("Noise modulating the dry roof of river tunnels.")
|
||||
private IrisGeneratorStyle tunnelRoofStyle = new IrisGeneratorStyle(NoiseStyle.IRIS).zoomed(64D);
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(16)
|
||||
@Desc("The maximum vertical roof variation in river tunnels.")
|
||||
private double tunnelRoofVariation = 3D;
|
||||
|
||||
@Required
|
||||
@ArrayType(min = 1, type = IrisRiverWorm.class)
|
||||
@Desc("Weighted root Perlin-worm families with inherited child styles for trunks and tributaries.")
|
||||
private KList<IrisRiverWorm> worms = new KList<IrisRiverWorm>();
|
||||
|
||||
@Desc("Modulates small river-bed height variation after the connected channel shape is solved.")
|
||||
private IrisGeneratorStyle bedRoughnessStyle = new IrisGeneratorStyle(NoiseStyle.IRIS).zoomed(96D);
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(8)
|
||||
@Desc("The maximum river-bed roughness in blocks.")
|
||||
private double bedRoughness = 0.75D;
|
||||
|
||||
@Desc("The behavior used when a graph route cannot continue as a wet channel.")
|
||||
private IrisRiverTerminalMode terminalMode = IrisRiverTerminalMode.DRY_CHANNEL;
|
||||
|
||||
@MinNumber(8)
|
||||
@MaxNumber(1024)
|
||||
@Desc("The distance in blocks over which a terminal channel returns to natural terrain.")
|
||||
private int terminalTaper = 64;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1)
|
||||
@Desc("The probability that a failed wet route continues as a tapered dry channel.")
|
||||
private double dryContinuationChance = 1D;
|
||||
|
||||
private static IrisStyledRange range(double min, double max, NoiseStyle style, double zoom) {
|
||||
return new IrisStyledRange(min, max, new IrisGeneratorStyle(style).zoomed(zoom));
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
import art.arcane.iris.engine.object.annotations.MaxNumber;
|
||||
import art.arcane.iris.engine.object.annotations.MinNumber;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@Desc("Dimension-owned settings for the deterministic connected river graph.")
|
||||
@Data
|
||||
public class IrisRiverTopology {
|
||||
@MinNumber(64)
|
||||
@MaxNumber(4096)
|
||||
@Desc("The routing-cell width in blocks. This controls graph identity and cannot be overridden by regions or biomes.")
|
||||
private int cellSize = 512;
|
||||
|
||||
@MinNumber(1)
|
||||
@MaxNumber(64)
|
||||
@Desc("The number of routing cells grouped into one immutable river cache tile.")
|
||||
private int tileCells = 4;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(0.49)
|
||||
@Desc("The fraction of a routing cell used to jitter its graph node away from the center.")
|
||||
private double siteJitter = 0.35D;
|
||||
|
||||
@MinNumber(1)
|
||||
@MaxNumber(256)
|
||||
@Desc("The maximum number of directed graph reaches followed by one source route.")
|
||||
private int maxRouteReaches = 16;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(64)
|
||||
@Desc("The minimum number of noise-weighted source nodes selected in each routing tile while source chance is above zero.")
|
||||
private int minimumSourcesPerTile = 0;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(7)
|
||||
@Desc("The number of alternate downstream reaches inspected before declaring a sink.")
|
||||
private int sinkSearchReaches = 4;
|
||||
|
||||
@MinNumber(8)
|
||||
@MaxNumber(256)
|
||||
@Desc("The spacing of deterministic drainage-basin sinks in routing cells. Larger values produce longer trunks and wider tributary trees.")
|
||||
private int routingBasinCells = 64;
|
||||
|
||||
@MinNumber(8)
|
||||
@MaxNumber(256)
|
||||
@Desc("The wavelength in routing cells of the smooth domain warp applied to drainage distance.")
|
||||
private int routingDeviationScaleCells = 24;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(32)
|
||||
@Desc("The maximum drainage-domain displacement in routing cells. Zero keeps straight radial basin gradients.")
|
||||
private double routingDeviationStrengthCells = 0D;
|
||||
|
||||
@MinNumber(1)
|
||||
@MaxNumber(64)
|
||||
@Desc("The horizontal basin-distance span in routing cells per one block of terraced water rise.")
|
||||
private double routingPlateauHeight = 8D;
|
||||
|
||||
@Desc("Selects complete river source routes at stable graph nodes.")
|
||||
private IrisRiverNoiseChance source = new IrisRiverNoiseChance()
|
||||
.setChance(0.05D)
|
||||
.setStyle(new IrisGeneratorStyle(NoiseStyle.IRIS).zoomed(8192D))
|
||||
.setInfluence(0.035D);
|
||||
|
||||
@Desc("Selects complete continuation reaches. A rejected reach terminates or suppresses its route rather than creating a gap.")
|
||||
private IrisRiverNoiseChance continuation = new IrisRiverNoiseChance()
|
||||
.setChance(0.99D)
|
||||
.setStyle(new IrisGeneratorStyle(NoiseStyle.VASCULAR).zoomed(4096D))
|
||||
.setInfluence(0.01D);
|
||||
|
||||
@Desc("Adds deterministic cost variation while choosing downstream graph neighbors.")
|
||||
private IrisGeneratorStyle routingStyle = new IrisGeneratorStyle(NoiseStyle.VASCULAR).zoomed(8192D);
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1024)
|
||||
@Desc("The maximum routing-cost contribution from routingStyle.")
|
||||
private double routingNoiseWeight = 24D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1024)
|
||||
@Desc("The penalty for choosing a downstream edge that does not follow the local routingStyle tangent.")
|
||||
private double flowAlignmentWeight = 24D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1024)
|
||||
@Desc("The deterministic attraction toward shared downstream nodes. Larger values form stronger tributary trees and confluences.")
|
||||
private double confluenceWeight = 0D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(16)
|
||||
@Desc("The contribution of natural terrain height to downstream routing cost.")
|
||||
private double terrainHeightWeight = 0.7D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(16)
|
||||
@Desc("The contribution of natural terrain slope to downstream routing cost.")
|
||||
private double terrainSlopeWeight = 0.35D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(16)
|
||||
@Desc("The routing preference toward natural sea outlets.")
|
||||
private double oceanAttraction = 1D;
|
||||
|
||||
@Desc("Require every wet source route to reach natural sea or a proven sea-reaching trunk.")
|
||||
private boolean requireOcean = false;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
import art.arcane.iris.engine.object.annotations.MaxNumber;
|
||||
import art.arcane.iris.engine.object.annotations.MinNumber;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@Desc("Controls the river water-surface solver.")
|
||||
@Data
|
||||
public class IrisRiverWater {
|
||||
@Desc("The strategy used to determine river water-surface height.")
|
||||
private IrisRiverWaterMode mode = IrisRiverWaterMode.FIXED;
|
||||
|
||||
@MinNumber(-2048)
|
||||
@MaxNumber(2048)
|
||||
@Desc("The base river fluid surface in absolute world Y, independent of the dimension ocean height.")
|
||||
private int fluidHeight = 63;
|
||||
|
||||
@Desc("The river fluid palette used by surface channels, contained tunnels, grottos, and waterfall throats.")
|
||||
private IrisMaterialPalette fluidPalette = new IrisMaterialPalette().qclear().qadd("water");
|
||||
|
||||
@MinNumber(8)
|
||||
@MaxNumber(4096)
|
||||
@Desc("The target length of each flat terraced pool in blocks.")
|
||||
private int poolLength = 96;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(64)
|
||||
@Desc("The greatest terraced river height permitted above fluidHeight.")
|
||||
private int maximumPoolRise = 4;
|
||||
|
||||
@MinNumber(1)
|
||||
@MaxNumber(32)
|
||||
@Desc("The vertical height of controlled drops between terraced pools.")
|
||||
private int dropHeight = 1;
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
|
||||
@Desc("Selects how a river determines its surface fluid height.")
|
||||
public enum IrisRiverWaterMode {
|
||||
@Desc("Use the river water configuration's fixed fluid height for every wet reach.")
|
||||
FIXED,
|
||||
|
||||
@Desc("Use flat pools connected by controlled vertical drops.")
|
||||
TERRACED
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.object.annotations.ArrayType;
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
import art.arcane.iris.engine.object.annotations.MaxNumber;
|
||||
import art.arcane.iris.engine.object.annotations.MinNumber;
|
||||
import art.arcane.iris.engine.object.annotations.Required;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@Desc("One weighted Perlin-worm river shape and its channel proportions.")
|
||||
@Data
|
||||
public class IrisRiverWorm {
|
||||
@Required
|
||||
@Desc("Unique lowercase identifier for this root or child style.")
|
||||
private String id = "river";
|
||||
|
||||
@Desc("Stable salt for this Perlin field pair.")
|
||||
private long seed = 1L;
|
||||
|
||||
@MinNumber(0.000001)
|
||||
@MaxNumber(1000000)
|
||||
@Desc("Relative probability when selecting this root family or one child transition.")
|
||||
private double weight = 1D;
|
||||
|
||||
@MinNumber(8)
|
||||
@MaxNumber(16384)
|
||||
@Desc("Primary gradient-Perlin wavelength in blocks.")
|
||||
private double wavelength = 1024D;
|
||||
|
||||
@MinNumber(8)
|
||||
@MaxNumber(16384)
|
||||
@Desc("Secondary gradient-Perlin wavelength in blocks.")
|
||||
private double detailWavelength = 256D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1)
|
||||
@Desc("Primary heading deviation as a fraction of 180 degrees.")
|
||||
private double tortuosity = 0.5D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1)
|
||||
@Desc("Secondary heading deviation as a fraction of 180 degrees.")
|
||||
private double detailTortuosity = 0.15D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1024)
|
||||
@Desc("Maximum endpoint-bridged displacement from the reach chord in blocks.")
|
||||
private double maxOffset = 320D;
|
||||
|
||||
@MinNumber(1)
|
||||
@MaxNumber(64)
|
||||
@Desc("Number of deterministic Perlin-worm steps used to resolve the reach.")
|
||||
private int segments = 48;
|
||||
|
||||
@MinNumber(0.125)
|
||||
@MaxNumber(8)
|
||||
@Desc("Channel-width multiplier for reaches selecting this worm.")
|
||||
private double widthMultiplier = 1D;
|
||||
|
||||
@MinNumber(0.125)
|
||||
@MaxNumber(8)
|
||||
@Desc("Bank-width multiplier for reaches selecting this worm.")
|
||||
private double bankMultiplier = 1D;
|
||||
|
||||
@MinNumber(0.125)
|
||||
@MaxNumber(8)
|
||||
@Desc("Depth multiplier for reaches selecting this worm.")
|
||||
private double depthMultiplier = 1D;
|
||||
|
||||
@MinNumber(8)
|
||||
@MaxNumber(16384)
|
||||
@Desc("Primary world-space wavelength controlling longitudinal body swelling and pinching.")
|
||||
private double bodyWavelength = 512D;
|
||||
|
||||
@MinNumber(8)
|
||||
@MaxNumber(16384)
|
||||
@Desc("Detail wavelength adding smaller changes to the longitudinal body profile.")
|
||||
private double bodyDetailWavelength = 128D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1)
|
||||
@Desc("Share of the longitudinal body field supplied by bodyDetailWavelength; the remainder uses bodyWavelength.")
|
||||
private double bodyDetailInfluence = 0.3D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(0.875)
|
||||
@Desc("Maximum proportional channel-width variation along this style's body.")
|
||||
private double widthVariation = 0D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(0.875)
|
||||
@Desc("Maximum proportional bank or basin-width variation along this style's body.")
|
||||
private double bankVariation = 0D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(0.875)
|
||||
@Desc("Maximum proportional bed-depth variation along this style's body.")
|
||||
private double depthVariation = 0D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(0.875)
|
||||
@Desc("Maximum downward variation of tunnel roof clearance without exceeding the authored cave headroom.")
|
||||
private double roofVariation = 0D;
|
||||
|
||||
@MinNumber(1)
|
||||
@MaxNumber(8)
|
||||
@Desc("Number of upstream children admitted before additional siblings decay probabilistically.")
|
||||
private int branchCap = 4;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1)
|
||||
@Desc("Multiplicative survival probability for every sibling beyond branchCap.")
|
||||
private double branchDecay = 0.35D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(8)
|
||||
@Desc("Multiplier applied to the dimension confluence attraction for this style.")
|
||||
private double confluenceMultiplier = 1D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1)
|
||||
@Desc("Chance that an upstream continuation mutates from this style into one weighted child.")
|
||||
private double childChance = 0D;
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(1)
|
||||
@Desc("Additional child-mutation chance for each sibling slot beyond the primary branch.")
|
||||
private double branchChildChance = 0D;
|
||||
|
||||
@ArrayType(type = IrisRiverWorm.class)
|
||||
@Desc("Weighted descendant styles inherited by the complete upstream lineage after mutation.")
|
||||
private KList<IrisRiverWorm> children = new KList<IrisRiverWorm>();
|
||||
}
|
||||
@@ -311,14 +311,6 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
}
|
||||
|
||||
private int resolveInitialSpawnY(World world, Location initialSpawn, int minY, int maxY) {
|
||||
Engine activeEngine = engine;
|
||||
if (activeEngine != null && activeEngine.getComplex() != null && activeEngine.getComplex().getHeightStream() != null) {
|
||||
int generatedY = activeEngine.getMinHeight()
|
||||
+ activeEngine.getComplex().getHeightStream().get(initialSpawn.getX(), initialSpawn.getZ()).intValue()
|
||||
+ 1;
|
||||
return Math.max(minY, Math.min(maxY, generatedY));
|
||||
}
|
||||
|
||||
return Math.max(minY, Math.min(maxY, world.getHighestBlockYAt(initialSpawn) + 1));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record RiverAnchor(
|
||||
RiverEdgeId reachId,
|
||||
int index,
|
||||
long stableId,
|
||||
double samplingSpacing,
|
||||
long samplingSalt,
|
||||
double x,
|
||||
double z,
|
||||
double alongReach,
|
||||
RiverRouteState state,
|
||||
int flow,
|
||||
int order
|
||||
) {
|
||||
public RiverAnchor {
|
||||
Objects.requireNonNull(reachId);
|
||||
Objects.requireNonNull(state);
|
||||
if (index < 0 || !Double.isFinite(samplingSpacing) || samplingSpacing <= 0D
|
||||
|| !Double.isFinite(x) || !Double.isFinite(z)
|
||||
|| !Double.isFinite(alongReach) || alongReach < 0.0 || alongReach > 1.0) {
|
||||
throw new IllegalArgumentException("River anchor index and coordinates must be valid");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class RiverBodyProfile {
|
||||
private final double[] positions;
|
||||
private final double[] widths;
|
||||
private final double[] bankWidths;
|
||||
private final double[] depths;
|
||||
private final double[] roofScales;
|
||||
private final double maximumWidth;
|
||||
private final double maximumBankWidth;
|
||||
private final double maximumDepth;
|
||||
|
||||
public RiverBodyProfile(
|
||||
double[] positions,
|
||||
double[] widths,
|
||||
double[] bankWidths,
|
||||
double[] depths,
|
||||
double[] roofScales
|
||||
) {
|
||||
if (positions == null || widths == null || bankWidths == null || depths == null || roofScales == null
|
||||
|| positions.length < 2
|
||||
|| positions.length != widths.length
|
||||
|| positions.length != bankWidths.length
|
||||
|| positions.length != depths.length
|
||||
|| positions.length != roofScales.length) {
|
||||
throw new IllegalArgumentException("River body profiles require matching dimension samples");
|
||||
}
|
||||
this.positions = positions.clone();
|
||||
this.widths = widths.clone();
|
||||
this.bankWidths = bankWidths.clone();
|
||||
this.depths = depths.clone();
|
||||
this.roofScales = roofScales.clone();
|
||||
double resolvedMaximumWidth = 0D;
|
||||
double resolvedMaximumBankWidth = 0D;
|
||||
double resolvedMaximumDepth = 0D;
|
||||
for (int index = 0; index < this.positions.length; index++) {
|
||||
double position = this.positions[index];
|
||||
if (!Double.isFinite(position) || position < 0D || position > 1D
|
||||
|| index > 0 && position <= this.positions[index - 1]) {
|
||||
throw new IllegalArgumentException("River body profile positions must increase from zero to one");
|
||||
}
|
||||
requirePositive(this.widths[index], "width");
|
||||
requireNonNegative(this.bankWidths[index], "bank width");
|
||||
requirePositive(this.depths[index], "depth");
|
||||
requireUnitScale(this.roofScales[index], "roof scale");
|
||||
resolvedMaximumWidth = StrictMath.max(resolvedMaximumWidth, this.widths[index]);
|
||||
resolvedMaximumBankWidth = StrictMath.max(resolvedMaximumBankWidth, this.bankWidths[index]);
|
||||
resolvedMaximumDepth = StrictMath.max(resolvedMaximumDepth, this.depths[index]);
|
||||
}
|
||||
if (this.positions[0] != 0D || this.positions[this.positions.length - 1] != 1D) {
|
||||
throw new IllegalArgumentException("River body profile positions must include zero and one");
|
||||
}
|
||||
maximumWidth = resolvedMaximumWidth;
|
||||
maximumBankWidth = resolvedMaximumBankWidth;
|
||||
maximumDepth = resolvedMaximumDepth;
|
||||
}
|
||||
|
||||
public static RiverBodyProfile constant(double width, double bankWidth, double depth) {
|
||||
return new RiverBodyProfile(
|
||||
new double[]{0D, 1D},
|
||||
new double[]{width, width},
|
||||
new double[]{bankWidth, bankWidth},
|
||||
new double[]{depth, depth},
|
||||
new double[]{1D, 1D}
|
||||
);
|
||||
}
|
||||
|
||||
public double width(double alongReach) {
|
||||
return sample(widths, alongReach);
|
||||
}
|
||||
|
||||
public double bankWidth(double alongReach) {
|
||||
return sample(bankWidths, alongReach);
|
||||
}
|
||||
|
||||
public double depth(double alongReach) {
|
||||
return sample(depths, alongReach);
|
||||
}
|
||||
|
||||
public double roofScale(double alongReach) {
|
||||
return sample(roofScales, alongReach);
|
||||
}
|
||||
|
||||
public double maximumWidth() {
|
||||
return maximumWidth;
|
||||
}
|
||||
|
||||
public double maximumBankWidth() {
|
||||
return maximumBankWidth;
|
||||
}
|
||||
|
||||
public double maximumDepth() {
|
||||
return maximumDepth;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return positions.length;
|
||||
}
|
||||
|
||||
public double position(int index) {
|
||||
return positions[index];
|
||||
}
|
||||
|
||||
public double widthAtIndex(int index) {
|
||||
return widths[index];
|
||||
}
|
||||
|
||||
public double bankWidthAtIndex(int index) {
|
||||
return bankWidths[index];
|
||||
}
|
||||
|
||||
public double depthAtIndex(int index) {
|
||||
return depths[index];
|
||||
}
|
||||
|
||||
public double roofScaleAtIndex(int index) {
|
||||
return roofScales[index];
|
||||
}
|
||||
|
||||
public int intervalIndex(double alongReach) {
|
||||
double position = StrictMath.max(0D, StrictMath.min(1D, alongReach));
|
||||
int index = Arrays.binarySearch(positions, position);
|
||||
if (index >= 0) {
|
||||
return StrictMath.min(index, positions.length - 2);
|
||||
}
|
||||
return StrictMath.max(0, StrictMath.min(-index - 2, positions.length - 2));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (this == object) {
|
||||
return true;
|
||||
}
|
||||
if (!(object instanceof RiverBodyProfile profile)) {
|
||||
return false;
|
||||
}
|
||||
return Arrays.equals(positions, profile.positions)
|
||||
&& Arrays.equals(widths, profile.widths)
|
||||
&& Arrays.equals(bankWidths, profile.bankWidths)
|
||||
&& Arrays.equals(depths, profile.depths)
|
||||
&& Arrays.equals(roofScales, profile.roofScales);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int hash = Arrays.hashCode(positions);
|
||||
hash = 31 * hash + Arrays.hashCode(widths);
|
||||
hash = 31 * hash + Arrays.hashCode(bankWidths);
|
||||
hash = 31 * hash + Arrays.hashCode(depths);
|
||||
return 31 * hash + Arrays.hashCode(roofScales);
|
||||
}
|
||||
|
||||
private double sample(double[] values, double alongReach) {
|
||||
double position = StrictMath.max(0D, StrictMath.min(1D, alongReach));
|
||||
int index = Arrays.binarySearch(positions, position);
|
||||
if (index >= 0) {
|
||||
return values[index];
|
||||
}
|
||||
int upper = -index - 1;
|
||||
if (upper <= 0) {
|
||||
return values[0];
|
||||
}
|
||||
if (upper >= positions.length) {
|
||||
return values[values.length - 1];
|
||||
}
|
||||
int lower = upper - 1;
|
||||
double range = positions[upper] - positions[lower];
|
||||
double interpolation = range <= 0D ? 0D : (position - positions[lower]) / range;
|
||||
return values[lower] + (values[upper] - values[lower]) * interpolation;
|
||||
}
|
||||
|
||||
private static void requirePositive(double value, String name) {
|
||||
if (!Double.isFinite(value) || value <= 0D) {
|
||||
throw new IllegalArgumentException("River body profile " + name + " must be finite and positive");
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireNonNegative(double value, String name) {
|
||||
if (!Double.isFinite(value) || value < 0D) {
|
||||
throw new IllegalArgumentException("River body profile " + name + " must be finite and non-negative");
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireUnitScale(double value, String name) {
|
||||
if (!Double.isFinite(value) || value <= 0D || value > 1D) {
|
||||
throw new IllegalArgumentException("River body profile " + name + " must be greater than zero and at most one");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record RiverEdgeId(RiverNodeId first, RiverNodeId second) implements Comparable<RiverEdgeId> {
|
||||
public RiverEdgeId {
|
||||
Objects.requireNonNull(first);
|
||||
Objects.requireNonNull(second);
|
||||
if (first.compareTo(second) >= 0) {
|
||||
throw new IllegalArgumentException("River edge endpoints must be distinct and canonical");
|
||||
}
|
||||
}
|
||||
|
||||
public static RiverEdgeId of(RiverNodeId first, RiverNodeId second) {
|
||||
Objects.requireNonNull(first);
|
||||
Objects.requireNonNull(second);
|
||||
if (first.equals(second)) {
|
||||
throw new IllegalArgumentException("River edge endpoints must be distinct");
|
||||
}
|
||||
return first.compareTo(second) < 0
|
||||
? new RiverEdgeId(first, second)
|
||||
: new RiverEdgeId(second, first);
|
||||
}
|
||||
|
||||
public long stableId() {
|
||||
return RiverNetwork.mix(first.stableId() ^ Long.rotateLeft(second.stableId(), 29));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(RiverEdgeId other) {
|
||||
int firstComparison = first.compareTo(other.first);
|
||||
return firstComparison != 0 ? firstComparison : second.compareTo(other.second);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,488 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public record RiverNetworkOptions(
|
||||
long seed,
|
||||
int cellSize,
|
||||
int tileCells,
|
||||
double siteJitter,
|
||||
int maxRouteReaches,
|
||||
int minimumSourcesPerTile,
|
||||
int downstreamCandidateLimit,
|
||||
int routingBasinCells,
|
||||
int routingDeviationScaleCells,
|
||||
double routingDeviationStrengthCells,
|
||||
double routingPlateauHeight,
|
||||
double hydraulicBaseHeight,
|
||||
boolean requireOcean,
|
||||
double sourceChance,
|
||||
double reachChance,
|
||||
double dryChannelChance,
|
||||
double terrainHeightWeight,
|
||||
double routingNoiseWeight,
|
||||
double flowAlignmentWeight,
|
||||
double confluenceWeight,
|
||||
double oceanAttraction,
|
||||
double channelWidth,
|
||||
double bankWidth,
|
||||
double depth,
|
||||
double channelRadiusBonus,
|
||||
double maxChannelWidth,
|
||||
double maxBankWidth,
|
||||
double maxDepth,
|
||||
double orderWidthFactor,
|
||||
double orderDepthFactor,
|
||||
double maximumReachRadius,
|
||||
List<RiverWorm> worms
|
||||
) {
|
||||
public RiverNetworkOptions {
|
||||
requireRange(cellSize, 8, 4096, "cellSize");
|
||||
requireRange(tileCells, 1, 64, "tileCells");
|
||||
requireRange(maxRouteReaches, 1, 256, "maxRouteReaches");
|
||||
requireRange(minimumSourcesPerTile, 0, tileCells * tileCells, "minimumSourcesPerTile");
|
||||
requireRange(downstreamCandidateLimit, 1, 8, "downstreamCandidateLimit");
|
||||
requireRange(routingBasinCells, 8, 256, "routingBasinCells");
|
||||
requireRange(routingDeviationScaleCells, 8, 256, "routingDeviationScaleCells");
|
||||
requireRange(routingDeviationStrengthCells, 0D, 32D, "routingDeviationStrengthCells");
|
||||
requirePositive(routingPlateauHeight, "routingPlateauHeight");
|
||||
requireFinite(hydraulicBaseHeight, "hydraulicBaseHeight");
|
||||
requireProbability(siteJitter, "siteJitter");
|
||||
requireProbability(sourceChance, "sourceChance");
|
||||
requireProbability(reachChance, "reachChance");
|
||||
requireProbability(dryChannelChance, "dryChannelChance");
|
||||
requireFiniteNonNegative(terrainHeightWeight, "terrainHeightWeight");
|
||||
requireFiniteNonNegative(routingNoiseWeight, "routingNoiseWeight");
|
||||
requireFiniteNonNegative(flowAlignmentWeight, "flowAlignmentWeight");
|
||||
requireFiniteNonNegative(confluenceWeight, "confluenceWeight");
|
||||
requireFiniteNonNegative(oceanAttraction, "oceanAttraction");
|
||||
requirePositive(channelWidth, "channelWidth");
|
||||
requireFiniteNonNegative(bankWidth, "bankWidth");
|
||||
requirePositive(depth, "depth");
|
||||
requireFiniteNonNegative(channelRadiusBonus, "channelRadiusBonus");
|
||||
requirePositive(maxChannelWidth, "maxChannelWidth");
|
||||
requireFiniteNonNegative(maxBankWidth, "maxBankWidth");
|
||||
requirePositive(maxDepth, "maxDepth");
|
||||
requireFiniteNonNegative(orderWidthFactor, "orderWidthFactor");
|
||||
requireFiniteNonNegative(orderDepthFactor, "orderDepthFactor");
|
||||
requireFiniteNonNegative(maximumReachRadius, "maximumReachRadius");
|
||||
if (worms == null || worms.isEmpty()) {
|
||||
throw new IllegalArgumentException("worms must contain at least one profile");
|
||||
}
|
||||
if (worms.size() > 16) {
|
||||
throw new IllegalArgumentException("worms must contain at most 16 root profiles");
|
||||
}
|
||||
Set<String> ids = new HashSet<String>();
|
||||
Set<Long> seeds = new HashSet<Long>();
|
||||
int wormCount = validateWormTree(worms, 1, ids, seeds);
|
||||
if (wormCount > 128) {
|
||||
throw new IllegalArgumentException("worm hierarchy must contain at most 128 profiles");
|
||||
}
|
||||
worms = List.copyOf(worms);
|
||||
RiverTopologyComplexity.requireSafe(
|
||||
cellSize,
|
||||
tileCells,
|
||||
siteJitter,
|
||||
maxRouteReaches,
|
||||
maximumReachRadius,
|
||||
maximumWormOffset(worms),
|
||||
maximumWormSegments(worms)
|
||||
);
|
||||
}
|
||||
|
||||
public static Builder builder(long seed) {
|
||||
return new Builder(seed);
|
||||
}
|
||||
|
||||
public double maximumWormOffset() {
|
||||
return maximumWormOffset(worms);
|
||||
}
|
||||
|
||||
public int maximumWormSegments() {
|
||||
return maximumWormSegments(worms);
|
||||
}
|
||||
|
||||
private static double maximumWormOffset(List<RiverWorm> worms) {
|
||||
double maximum = 0D;
|
||||
for (RiverWorm worm : worms) {
|
||||
if (worm == null) {
|
||||
throw new IllegalArgumentException("worms must not contain null profiles");
|
||||
}
|
||||
maximum = StrictMath.max(maximum, worm.maxOffset());
|
||||
maximum = StrictMath.max(maximum, maximumWormOffset(worm.children()));
|
||||
}
|
||||
return maximum;
|
||||
}
|
||||
|
||||
private static int maximumWormSegments(List<RiverWorm> worms) {
|
||||
int maximum = 1;
|
||||
for (RiverWorm worm : worms) {
|
||||
if (worm == null) {
|
||||
throw new IllegalArgumentException("worms must not contain null profiles");
|
||||
}
|
||||
maximum = StrictMath.max(maximum, worm.segments());
|
||||
maximum = StrictMath.max(maximum, maximumWormSegments(worm.children()));
|
||||
}
|
||||
return maximum;
|
||||
}
|
||||
|
||||
private static int validateWormTree(
|
||||
List<RiverWorm> worms,
|
||||
int depth,
|
||||
Set<String> ids,
|
||||
Set<Long> seeds
|
||||
) {
|
||||
if (depth > 4) {
|
||||
throw new IllegalArgumentException("worm hierarchy must be at most 4 profiles deep");
|
||||
}
|
||||
int count = 0;
|
||||
for (RiverWorm worm : worms) {
|
||||
if (worm == null) {
|
||||
throw new IllegalArgumentException("worm hierarchy must not contain null profiles");
|
||||
}
|
||||
if (!ids.add(worm.id())) {
|
||||
throw new IllegalArgumentException("worm ids must be unique: " + worm.id());
|
||||
}
|
||||
if (!seeds.add(worm.seed())) {
|
||||
throw new IllegalArgumentException("worm seeds must be unique: " + worm.seed());
|
||||
}
|
||||
count++;
|
||||
if (!worm.children().isEmpty()) {
|
||||
count += validateWormTree(worm.children(), depth + 1, ids, seeds);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
private static void requireRange(int value, int minimum, int maximum, String name) {
|
||||
if (value < minimum || value > maximum) {
|
||||
throw new IllegalArgumentException(name + " must be between " + minimum + " and " + maximum);
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireRange(double value, double minimum, double maximum, String name) {
|
||||
if (!Double.isFinite(value) || value < minimum || value > maximum) {
|
||||
throw new IllegalArgumentException(name + " must be between " + minimum + " and " + maximum);
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireProbability(double value, String name) {
|
||||
if (!Double.isFinite(value) || value < 0.0 || value > 1.0) {
|
||||
throw new IllegalArgumentException(name + " must be finite and between 0 and 1");
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireFiniteNonNegative(double value, String name) {
|
||||
if (!Double.isFinite(value) || value < 0.0) {
|
||||
throw new IllegalArgumentException(name + " must be finite and non-negative");
|
||||
}
|
||||
}
|
||||
|
||||
private static void requirePositive(double value, String name) {
|
||||
if (!Double.isFinite(value) || value <= 0.0) {
|
||||
throw new IllegalArgumentException(name + " must be finite and positive");
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireFinite(double value, String name) {
|
||||
if (!Double.isFinite(value)) {
|
||||
throw new IllegalArgumentException(name + " must be finite");
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final long seed;
|
||||
private int cellSize;
|
||||
private int tileCells;
|
||||
private double siteJitter;
|
||||
private int maxRouteReaches;
|
||||
private int minimumSourcesPerTile;
|
||||
private int downstreamCandidateLimit;
|
||||
private int routingBasinCells;
|
||||
private int routingDeviationScaleCells;
|
||||
private double routingDeviationStrengthCells;
|
||||
private double routingPlateauHeight;
|
||||
private double hydraulicBaseHeight;
|
||||
private boolean requireOcean;
|
||||
private double sourceChance;
|
||||
private double reachChance;
|
||||
private double dryChannelChance;
|
||||
private double terrainHeightWeight;
|
||||
private double routingNoiseWeight;
|
||||
private double flowAlignmentWeight;
|
||||
private double confluenceWeight;
|
||||
private double oceanAttraction;
|
||||
private double channelWidth;
|
||||
private double bankWidth;
|
||||
private double depth;
|
||||
private double channelRadiusBonus;
|
||||
private double maxChannelWidth;
|
||||
private double maxBankWidth;
|
||||
private double maxDepth;
|
||||
private double orderWidthFactor;
|
||||
private double orderDepthFactor;
|
||||
private double maximumReachRadius;
|
||||
private List<RiverWorm> worms;
|
||||
|
||||
private Builder(long seed) {
|
||||
this.seed = seed;
|
||||
cellSize = 512;
|
||||
tileCells = 4;
|
||||
siteJitter = 0.35;
|
||||
maxRouteReaches = 16;
|
||||
minimumSourcesPerTile = 0;
|
||||
downstreamCandidateLimit = 4;
|
||||
routingBasinCells = 64;
|
||||
routingDeviationScaleCells = 24;
|
||||
routingDeviationStrengthCells = 0D;
|
||||
routingPlateauHeight = 8.0;
|
||||
hydraulicBaseHeight = 64D;
|
||||
requireOcean = false;
|
||||
sourceChance = 0.12;
|
||||
reachChance = 0.98;
|
||||
dryChannelChance = 0.35;
|
||||
terrainHeightWeight = 1.0;
|
||||
routingNoiseWeight = 24.0;
|
||||
flowAlignmentWeight = 0D;
|
||||
confluenceWeight = 0D;
|
||||
oceanAttraction = 64.0;
|
||||
channelWidth = 10.0;
|
||||
bankWidth = 8.0;
|
||||
depth = 4.0;
|
||||
maxChannelWidth = 10D;
|
||||
maxBankWidth = 8D;
|
||||
maxDepth = 10D;
|
||||
orderWidthFactor = 0.35;
|
||||
orderDepthFactor = 0.2;
|
||||
maximumReachRadius = Double.NaN;
|
||||
worms = List.of(new RiverWorm(
|
||||
"default",
|
||||
1L,
|
||||
1D,
|
||||
1024D,
|
||||
256D,
|
||||
0.5D,
|
||||
0.15D,
|
||||
40D,
|
||||
8,
|
||||
1D,
|
||||
1D,
|
||||
1D,
|
||||
512D,
|
||||
128D,
|
||||
0.3D,
|
||||
0D,
|
||||
0D,
|
||||
0D,
|
||||
0D,
|
||||
4,
|
||||
0.35D,
|
||||
1D,
|
||||
0D,
|
||||
0D,
|
||||
List.of()
|
||||
));
|
||||
}
|
||||
|
||||
public Builder cellSize(int value) {
|
||||
cellSize = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder tileCells(int value) {
|
||||
tileCells = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder siteJitter(double value) {
|
||||
siteJitter = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder maxRouteReaches(int value) {
|
||||
maxRouteReaches = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder minimumSourcesPerTile(int value) {
|
||||
minimumSourcesPerTile = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder downstreamCandidateLimit(int value) {
|
||||
downstreamCandidateLimit = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder routingBasinCells(int value) {
|
||||
routingBasinCells = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder routingDeviationScaleCells(int value) {
|
||||
routingDeviationScaleCells = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder routingDeviationStrengthCells(double value) {
|
||||
routingDeviationStrengthCells = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder routingPlateauHeight(double value) {
|
||||
routingPlateauHeight = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder hydraulicBaseHeight(double value) {
|
||||
hydraulicBaseHeight = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder requireOcean(boolean value) {
|
||||
requireOcean = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder sourceChance(double value) {
|
||||
sourceChance = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder reachChance(double value) {
|
||||
reachChance = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder dryChannelChance(double value) {
|
||||
dryChannelChance = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder terrainHeightWeight(double value) {
|
||||
terrainHeightWeight = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder routingNoiseWeight(double value) {
|
||||
routingNoiseWeight = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder flowAlignmentWeight(double value) {
|
||||
flowAlignmentWeight = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder confluenceWeight(double value) {
|
||||
confluenceWeight = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder oceanAttraction(double value) {
|
||||
oceanAttraction = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder channelWidth(double value) {
|
||||
channelWidth = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder bankWidth(double value) {
|
||||
bankWidth = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder depth(double value) {
|
||||
depth = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder channelRadiusBonus(double value) {
|
||||
channelRadiusBonus = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder maxChannelWidth(double value) {
|
||||
maxChannelWidth = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder maxBankWidth(double value) {
|
||||
maxBankWidth = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder maxDepth(double value) {
|
||||
maxDepth = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder orderWidthFactor(double value) {
|
||||
orderWidthFactor = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder orderDepthFactor(double value) {
|
||||
orderDepthFactor = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder maximumReachRadius(double value) {
|
||||
maximumReachRadius = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder worms(List<RiverWorm> value) {
|
||||
worms = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public RiverNetworkOptions build() {
|
||||
double resolvedMaximumReachRadius = Double.isFinite(maximumReachRadius)
|
||||
? maximumReachRadius
|
||||
: defaultMaximumReachRadius();
|
||||
return new RiverNetworkOptions(
|
||||
seed,
|
||||
cellSize,
|
||||
tileCells,
|
||||
siteJitter,
|
||||
maxRouteReaches,
|
||||
minimumSourcesPerTile,
|
||||
downstreamCandidateLimit,
|
||||
routingBasinCells,
|
||||
routingDeviationScaleCells,
|
||||
routingDeviationStrengthCells,
|
||||
routingPlateauHeight,
|
||||
hydraulicBaseHeight,
|
||||
requireOcean,
|
||||
sourceChance,
|
||||
reachChance,
|
||||
dryChannelChance,
|
||||
terrainHeightWeight,
|
||||
routingNoiseWeight,
|
||||
flowAlignmentWeight,
|
||||
confluenceWeight,
|
||||
oceanAttraction,
|
||||
channelWidth,
|
||||
bankWidth,
|
||||
depth,
|
||||
channelRadiusBonus,
|
||||
maxChannelWidth,
|
||||
maxBankWidth,
|
||||
maxDepth,
|
||||
orderWidthFactor,
|
||||
orderDepthFactor,
|
||||
resolvedMaximumReachRadius,
|
||||
worms
|
||||
);
|
||||
}
|
||||
|
||||
private double defaultMaximumReachRadius() {
|
||||
return maxChannelWidth * 0.5D + maxBankWidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record RiverNode(
|
||||
RiverNodeId id,
|
||||
double x,
|
||||
double z,
|
||||
double naturalHeight,
|
||||
double hydraulicHeight,
|
||||
double rank,
|
||||
double routingScore,
|
||||
boolean ocean,
|
||||
boolean riverAllowed
|
||||
) {
|
||||
public RiverNode {
|
||||
Objects.requireNonNull(id);
|
||||
if (!Double.isFinite(x) || !Double.isFinite(z) || !Double.isFinite(naturalHeight)
|
||||
|| !Double.isFinite(hydraulicHeight)
|
||||
|| !Double.isFinite(rank) || !Double.isFinite(routingScore)) {
|
||||
throw new IllegalArgumentException("River node coordinates, height, and rank must be finite");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
public record RiverNodeId(long cellX, long cellZ) implements Comparable<RiverNodeId> {
|
||||
public long stableId() {
|
||||
return RiverNetwork.mix(cellX * 0x9E3779B97F4A7C15L ^ Long.rotateLeft(cellZ * 0xC2B2AE3D27D4EB4FL, 31));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(RiverNodeId other) {
|
||||
int xComparison = Long.compare(cellX, other.cellX);
|
||||
return xComparison != 0 ? xComparison : Long.compare(cellZ, other.cellZ);
|
||||
}
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
public final class RiverPolyline {
|
||||
private final double[] x;
|
||||
private final double[] z;
|
||||
private final double[] cumulativeLength;
|
||||
private final double length;
|
||||
|
||||
public RiverPolyline(double[] x, double[] z) {
|
||||
if (x.length != z.length || x.length < 2) {
|
||||
throw new IllegalArgumentException("River polyline requires matching coordinate arrays and at least two points");
|
||||
}
|
||||
this.x = x.clone();
|
||||
this.z = z.clone();
|
||||
cumulativeLength = new double[x.length];
|
||||
double measuredLength = 0.0;
|
||||
for (int i = 0; i < x.length; i++) {
|
||||
if (!Double.isFinite(x[i]) || !Double.isFinite(z[i])) {
|
||||
throw new IllegalArgumentException("River polyline coordinates must be finite");
|
||||
}
|
||||
if (i > 0) {
|
||||
measuredLength += StrictMath.hypot(x[i] - x[i - 1], z[i] - z[i - 1]);
|
||||
cumulativeLength[i] = measuredLength;
|
||||
}
|
||||
}
|
||||
length = measuredLength;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return x.length;
|
||||
}
|
||||
|
||||
public double x(int index) {
|
||||
return x[index];
|
||||
}
|
||||
|
||||
public double z(int index) {
|
||||
return z[index];
|
||||
}
|
||||
|
||||
public double cumulativeLength(int index) {
|
||||
return cumulativeLength[index];
|
||||
}
|
||||
|
||||
public double length() {
|
||||
return length;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public record RiverReach(
|
||||
RiverEdgeId id,
|
||||
RiverNode from,
|
||||
RiverNode to,
|
||||
RiverRouteState state,
|
||||
int flow,
|
||||
int order,
|
||||
double width,
|
||||
double bankWidth,
|
||||
double depth,
|
||||
RiverBodyProfile bodyProfile,
|
||||
boolean mouth,
|
||||
boolean terminal,
|
||||
RiverPolyline polyline
|
||||
) {
|
||||
public RiverReach {
|
||||
Objects.requireNonNull(id);
|
||||
Objects.requireNonNull(from);
|
||||
Objects.requireNonNull(to);
|
||||
Objects.requireNonNull(state);
|
||||
Objects.requireNonNull(bodyProfile);
|
||||
Objects.requireNonNull(polyline);
|
||||
if (state == RiverRouteState.SUPPRESSED) {
|
||||
throw new IllegalArgumentException("Suppressed routes cannot produce reaches");
|
||||
}
|
||||
if (flow < 1 || order < 1) {
|
||||
throw new IllegalArgumentException("River reach flow and order must be positive");
|
||||
}
|
||||
if (!Double.isFinite(width) || width <= 0.0 || !Double.isFinite(bankWidth) || bankWidth < 0.0
|
||||
|| !Double.isFinite(depth) || depth <= 0.0) {
|
||||
throw new IllegalArgumentException("River reach dimensions must be finite and valid");
|
||||
}
|
||||
if (Double.compare(width, bodyProfile.maximumWidth()) != 0
|
||||
|| Double.compare(bankWidth, bodyProfile.maximumBankWidth()) != 0
|
||||
|| Double.compare(depth, bodyProfile.maximumDepth()) != 0) {
|
||||
throw new IllegalArgumentException("River reach dimensions must equal their body-profile maxima");
|
||||
}
|
||||
}
|
||||
|
||||
public double widthAt(double alongReach) {
|
||||
return bodyProfile.width(alongReach);
|
||||
}
|
||||
|
||||
public double bankWidthAt(double alongReach) {
|
||||
return bodyProfile.bankWidth(alongReach);
|
||||
}
|
||||
|
||||
public double depthAt(double alongReach) {
|
||||
return bodyProfile.depth(alongReach);
|
||||
}
|
||||
|
||||
public double roofScaleAt(double alongReach) {
|
||||
return bodyProfile.roofScale(alongReach);
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public record RiverRoute(
|
||||
RiverNodeId source,
|
||||
RiverRouteState state,
|
||||
List<RiverEdgeId> edges,
|
||||
boolean oceanConnected,
|
||||
boolean terminal
|
||||
) {
|
||||
public RiverRoute {
|
||||
Objects.requireNonNull(source);
|
||||
Objects.requireNonNull(state);
|
||||
edges = List.copyOf(edges);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
public enum RiverRouteState {
|
||||
WET,
|
||||
DRY,
|
||||
SUPPRESSED
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public final class RiverRoutingContext {
|
||||
private final RiverEdgeId edgeId;
|
||||
private final RiverNode from;
|
||||
private final RiverNode to;
|
||||
private final Supplier<RiverPolyline> polylineSupplier;
|
||||
private volatile RiverPolyline polyline;
|
||||
|
||||
public RiverRoutingContext(RiverEdgeId edgeId, RiverNode from, RiverNode to, RiverPolyline polyline) {
|
||||
this(edgeId, from, to, () -> polyline, Objects.requireNonNull(polyline));
|
||||
}
|
||||
|
||||
static RiverRoutingContext lazy(
|
||||
RiverEdgeId edgeId,
|
||||
RiverNode from,
|
||||
RiverNode to,
|
||||
Supplier<RiverPolyline> polylineSupplier
|
||||
) {
|
||||
return new RiverRoutingContext(edgeId, from, to, polylineSupplier, null);
|
||||
}
|
||||
|
||||
private RiverRoutingContext(
|
||||
RiverEdgeId edgeId,
|
||||
RiverNode from,
|
||||
RiverNode to,
|
||||
Supplier<RiverPolyline> polylineSupplier,
|
||||
RiverPolyline polyline
|
||||
) {
|
||||
this.edgeId = Objects.requireNonNull(edgeId);
|
||||
this.from = Objects.requireNonNull(from);
|
||||
this.to = Objects.requireNonNull(to);
|
||||
this.polylineSupplier = Objects.requireNonNull(polylineSupplier);
|
||||
this.polyline = polyline;
|
||||
}
|
||||
|
||||
public RiverEdgeId edgeId() {
|
||||
return edgeId;
|
||||
}
|
||||
|
||||
public RiverNode from() {
|
||||
return from;
|
||||
}
|
||||
|
||||
public RiverNode to() {
|
||||
return to;
|
||||
}
|
||||
|
||||
public RiverPolyline polyline() {
|
||||
RiverPolyline resolved = polyline;
|
||||
if (resolved != null) {
|
||||
return resolved;
|
||||
}
|
||||
synchronized (this) {
|
||||
if (polyline == null) {
|
||||
polyline = Objects.requireNonNull(polylineSupplier.get());
|
||||
}
|
||||
return polyline;
|
||||
}
|
||||
}
|
||||
|
||||
public int midpointX() {
|
||||
return (int) StrictMath.max(
|
||||
Integer.MIN_VALUE,
|
||||
StrictMath.min(Integer.MAX_VALUE, StrictMath.round((from.x() + to.x()) * 0.5))
|
||||
);
|
||||
}
|
||||
|
||||
public int midpointZ() {
|
||||
return (int) StrictMath.max(
|
||||
Integer.MIN_VALUE,
|
||||
StrictMath.min(Integer.MAX_VALUE, StrictMath.round((from.z() + to.z()) * 0.5))
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object candidate) {
|
||||
if (this == candidate) {
|
||||
return true;
|
||||
}
|
||||
if (!(candidate instanceof RiverRoutingContext context)) {
|
||||
return false;
|
||||
}
|
||||
return edgeId.equals(context.edgeId)
|
||||
&& from.equals(context.from)
|
||||
&& to.equals(context.to)
|
||||
&& polyline().equals(context.polyline());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(edgeId, from, to, polyline());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "RiverRoutingContext[edgeId=" + edgeId
|
||||
+ ", from=" + from
|
||||
+ ", to=" + to
|
||||
+ ", polyline=" + polyline() + "]";
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
public record RiverSample(
|
||||
boolean present,
|
||||
RiverRouteState state,
|
||||
RiverSection section,
|
||||
double distance,
|
||||
double alongReach,
|
||||
double carveWeight,
|
||||
int flow,
|
||||
int order,
|
||||
double width,
|
||||
double bankWidth,
|
||||
double depth,
|
||||
boolean terminal,
|
||||
RiverEdgeId reachId
|
||||
) {
|
||||
private static final RiverSample NONE = new RiverSample(
|
||||
false,
|
||||
RiverRouteState.SUPPRESSED,
|
||||
RiverSection.NONE,
|
||||
Double.POSITIVE_INFINITY,
|
||||
0.0,
|
||||
0.0,
|
||||
0,
|
||||
0,
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
false,
|
||||
null
|
||||
);
|
||||
|
||||
public static RiverSample none() {
|
||||
return NONE;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
public enum RiverSection {
|
||||
NONE,
|
||||
CHANNEL,
|
||||
MOUTH,
|
||||
BANK,
|
||||
DRY_CHANNEL,
|
||||
DRY_BANK
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
public enum RiverTerminalPolicy {
|
||||
INHERIT,
|
||||
WET,
|
||||
DRY,
|
||||
SUPPRESS
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
public record RiverTerrainNodeSample(
|
||||
double naturalHeight,
|
||||
boolean ocean,
|
||||
boolean riverAllowed,
|
||||
double routingCost
|
||||
) {
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
public interface RiverTerrainSampler {
|
||||
double naturalHeight(int blockX, int blockZ);
|
||||
|
||||
boolean isOcean(int blockX, int blockZ);
|
||||
|
||||
default RiverTerrainNodeSample sampleNode(int blockX, int blockZ) {
|
||||
return new RiverTerrainNodeSample(
|
||||
naturalHeight(blockX, blockZ),
|
||||
isOcean(blockX, blockZ),
|
||||
allowsRiver(blockX, blockZ),
|
||||
routingCost(blockX, blockZ)
|
||||
);
|
||||
}
|
||||
|
||||
default RiverTerrainSourceSample sampleSource(int blockX, int blockZ) {
|
||||
return new RiverTerrainSourceSample(
|
||||
sourceChanceMultiplier(blockX, blockZ),
|
||||
allowsRiver(blockX, blockZ),
|
||||
isOcean(blockX, blockZ)
|
||||
);
|
||||
}
|
||||
|
||||
default double routingCost(int blockX, int blockZ) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
default double sourceChanceMultiplier(int blockX, int blockZ) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
default double maximumSourceChanceMultiplier() {
|
||||
return Double.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
default double reachChanceMultiplier(int blockX, int blockZ) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
default boolean allowsRiver(int blockX, int blockZ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
default boolean allowsReach(RiverRoutingContext context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
default double reachRoutingCost(RiverRoutingContext context) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
default double flowNoise(double x, double z) {
|
||||
return Double.NaN;
|
||||
}
|
||||
|
||||
default double channelWidth(RiverRoutingContext context, double fallback) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
default double channelWidth(
|
||||
RiverRoutingContext context,
|
||||
double x,
|
||||
double z,
|
||||
double fallback
|
||||
) {
|
||||
return channelWidth(context, fallback);
|
||||
}
|
||||
|
||||
default double bankWidth(RiverRoutingContext context, double fallback) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
default double bankWidth(
|
||||
RiverRoutingContext context,
|
||||
double x,
|
||||
double z,
|
||||
double fallback
|
||||
) {
|
||||
return bankWidth(context, fallback);
|
||||
}
|
||||
|
||||
default double depth(RiverRoutingContext context, double fallback) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
default double depth(
|
||||
RiverRoutingContext context,
|
||||
double x,
|
||||
double z,
|
||||
double fallback
|
||||
) {
|
||||
return depth(context, fallback);
|
||||
}
|
||||
|
||||
default RiverTerminalPolicy terminalPolicy(int blockX, int blockZ) {
|
||||
return RiverTerminalPolicy.INHERIT;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
public record RiverTerrainSourceSample(
|
||||
double chanceMultiplier,
|
||||
boolean riverAllowed,
|
||||
boolean ocean
|
||||
) {
|
||||
}
|
||||
@@ -1,717 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public final class RiverTile {
|
||||
private static final int BUCKET_SIZE = 64;
|
||||
|
||||
private final int tileX;
|
||||
private final int tileZ;
|
||||
private final int minimumX;
|
||||
private final int minimumZ;
|
||||
private final int maximumX;
|
||||
private final int maximumZ;
|
||||
private final List<RiverReach> reaches;
|
||||
private final Map<RiverEdgeId, RiverReach> reachesById;
|
||||
private final Map<Long, List<RiverReach>> spatialIndex;
|
||||
|
||||
public RiverTile(
|
||||
int tileX,
|
||||
int tileZ,
|
||||
int minimumX,
|
||||
int minimumZ,
|
||||
int maximumX,
|
||||
int maximumZ,
|
||||
List<RiverReach> reaches
|
||||
) {
|
||||
if (minimumX >= maximumX || minimumZ >= maximumZ) {
|
||||
throw new IllegalArgumentException("River tile bounds must have positive area");
|
||||
}
|
||||
this.tileX = tileX;
|
||||
this.tileZ = tileZ;
|
||||
this.minimumX = minimumX;
|
||||
this.minimumZ = minimumZ;
|
||||
this.maximumX = maximumX;
|
||||
this.maximumZ = maximumZ;
|
||||
this.reaches = List.copyOf(reaches);
|
||||
reachesById = indexById(this.reaches);
|
||||
spatialIndex = createSpatialIndex(this.reaches);
|
||||
}
|
||||
|
||||
public int tileX() {
|
||||
return tileX;
|
||||
}
|
||||
|
||||
public int tileZ() {
|
||||
return tileZ;
|
||||
}
|
||||
|
||||
public int minimumX() {
|
||||
return minimumX;
|
||||
}
|
||||
|
||||
public int minimumZ() {
|
||||
return minimumZ;
|
||||
}
|
||||
|
||||
public int maximumX() {
|
||||
return maximumX;
|
||||
}
|
||||
|
||||
public int maximumZ() {
|
||||
return maximumZ;
|
||||
}
|
||||
|
||||
public List<RiverReach> reaches() {
|
||||
return reaches;
|
||||
}
|
||||
|
||||
public RiverReach reach(RiverEdgeId id) {
|
||||
return reachesById.get(Objects.requireNonNull(id));
|
||||
}
|
||||
|
||||
public List<RiverAnchor> candidateAnchors(double spacing, long salt) {
|
||||
return candidateAnchors(minimumX, minimumZ, maximumX, maximumZ, spacing, salt);
|
||||
}
|
||||
|
||||
public List<RiverAnchor> candidateAnchors(
|
||||
double queryMinimumX,
|
||||
double queryMinimumZ,
|
||||
double queryMaximumX,
|
||||
double queryMaximumZ,
|
||||
double spacing,
|
||||
long salt
|
||||
) {
|
||||
if (!Double.isFinite(spacing) || spacing <= 0.0) {
|
||||
throw new IllegalArgumentException("River anchor spacing must be finite and positive");
|
||||
}
|
||||
if (!Double.isFinite(queryMinimumX) || !Double.isFinite(queryMinimumZ)
|
||||
|| !Double.isFinite(queryMaximumX) || !Double.isFinite(queryMaximumZ)
|
||||
|| queryMinimumX >= queryMaximumX || queryMinimumZ >= queryMaximumZ) {
|
||||
throw new IllegalArgumentException("River anchor query bounds must be finite and have positive area");
|
||||
}
|
||||
ArrayList<RiverAnchor> anchors = new ArrayList<>();
|
||||
for (RiverReach reach : indexedReaches(queryMinimumX, queryMinimumZ, queryMaximumX, queryMaximumZ)) {
|
||||
addAnchors(
|
||||
reach,
|
||||
spacing,
|
||||
salt,
|
||||
queryMinimumX,
|
||||
queryMinimumZ,
|
||||
queryMaximumX,
|
||||
queryMaximumZ,
|
||||
anchors
|
||||
);
|
||||
}
|
||||
return List.copyOf(anchors);
|
||||
}
|
||||
|
||||
public int sampleCandidateCount(double x, double z) {
|
||||
return indexedReaches(x, z).size();
|
||||
}
|
||||
|
||||
public RiverSample sample(double x, double z) {
|
||||
return sampleExpanded(x, z, 0D);
|
||||
}
|
||||
|
||||
public RiverSample sampleExpanded(double x, double z, double additionalRadius) {
|
||||
if (!Double.isFinite(additionalRadius) || additionalRadius < 0D) {
|
||||
throw new IllegalArgumentException("Additional river sample radius must be finite and non-negative");
|
||||
}
|
||||
RiverReach nearestReach = null;
|
||||
double nearestDistanceSquared = Double.POSITIVE_INFINITY;
|
||||
double nearestAlongReach = 0.0;
|
||||
List<RiverReach> candidates = additionalRadius == 0D
|
||||
? indexedReaches(x, z)
|
||||
: indexedReaches(
|
||||
x - additionalRadius,
|
||||
z - additionalRadius,
|
||||
x + additionalRadius,
|
||||
z + additionalRadius
|
||||
);
|
||||
for (RiverReach reach : candidates) {
|
||||
ClosestPoint closest = closestCoveringPoint(reach, x, z, additionalRadius);
|
||||
if (closest == null) {
|
||||
continue;
|
||||
}
|
||||
if (closest.distanceSquared() < nearestDistanceSquared
|
||||
|| (closest.distanceSquared() == nearestDistanceSquared
|
||||
&& nearestReach != null
|
||||
&& reach.id().compareTo(nearestReach.id()) < 0)) {
|
||||
nearestReach = reach;
|
||||
nearestDistanceSquared = closest.distanceSquared();
|
||||
nearestAlongReach = closest.alongReach();
|
||||
}
|
||||
}
|
||||
if (nearestReach == null) {
|
||||
return RiverSample.none();
|
||||
}
|
||||
|
||||
return createSample(nearestReach, nearestDistanceSquared, nearestAlongReach);
|
||||
}
|
||||
|
||||
public RiverSample sampleFootprint(
|
||||
double queryMinimumX,
|
||||
double queryMinimumZ,
|
||||
double queryMaximumX,
|
||||
double queryMaximumZ
|
||||
) {
|
||||
if (!Double.isFinite(queryMinimumX) || !Double.isFinite(queryMinimumZ)
|
||||
|| !Double.isFinite(queryMaximumX) || !Double.isFinite(queryMaximumZ)
|
||||
|| queryMinimumX > queryMaximumX || queryMinimumZ > queryMaximumZ) {
|
||||
throw new IllegalArgumentException("River footprint bounds must be finite and ordered");
|
||||
}
|
||||
RiverReach nearestReach = null;
|
||||
double nearestDistanceSquared = Double.POSITIVE_INFINITY;
|
||||
double nearestAlongReach = 0.0;
|
||||
for (RiverReach reach : indexedReachesInclusive(
|
||||
queryMinimumX,
|
||||
queryMinimumZ,
|
||||
queryMaximumX,
|
||||
queryMaximumZ
|
||||
)) {
|
||||
ClosestPoint closest = closestCoveringPoint(
|
||||
reach,
|
||||
queryMinimumX,
|
||||
queryMinimumZ,
|
||||
queryMaximumX,
|
||||
queryMaximumZ
|
||||
);
|
||||
if (closest == null) {
|
||||
continue;
|
||||
}
|
||||
if (closest.distanceSquared() < nearestDistanceSquared
|
||||
|| (closest.distanceSquared() == nearestDistanceSquared
|
||||
&& nearestReach != null
|
||||
&& reach.id().compareTo(nearestReach.id()) < 0)) {
|
||||
nearestReach = reach;
|
||||
nearestDistanceSquared = closest.distanceSquared();
|
||||
nearestAlongReach = closest.alongReach();
|
||||
}
|
||||
}
|
||||
if (nearestReach == null) {
|
||||
return RiverSample.none();
|
||||
}
|
||||
|
||||
return createSample(nearestReach, nearestDistanceSquared, nearestAlongReach);
|
||||
}
|
||||
|
||||
private static RiverSample createSample(
|
||||
RiverReach nearestReach,
|
||||
double nearestDistanceSquared,
|
||||
double nearestAlongReach
|
||||
) {
|
||||
|
||||
double distance = StrictMath.sqrt(nearestDistanceSquared);
|
||||
double localWidth = nearestReach.widthAt(nearestAlongReach);
|
||||
double localBankWidth = nearestReach.bankWidthAt(nearestAlongReach);
|
||||
double localDepth = nearestReach.depthAt(nearestAlongReach);
|
||||
double channelRadius = localWidth * 0.5;
|
||||
RiverSection section = section(nearestReach, distance, channelRadius);
|
||||
double carveWeight = carveWeight(distance, channelRadius, localBankWidth);
|
||||
return new RiverSample(
|
||||
true,
|
||||
nearestReach.state(),
|
||||
section,
|
||||
distance,
|
||||
nearestAlongReach,
|
||||
carveWeight,
|
||||
nearestReach.flow(),
|
||||
nearestReach.order(),
|
||||
localWidth,
|
||||
localBankWidth,
|
||||
localDepth,
|
||||
nearestReach.terminal(),
|
||||
nearestReach.id()
|
||||
);
|
||||
}
|
||||
|
||||
private static RiverSection section(RiverReach reach, double distance, double channelRadius) {
|
||||
if (distance <= channelRadius) {
|
||||
if (reach.state() == RiverRouteState.DRY) {
|
||||
return RiverSection.DRY_CHANNEL;
|
||||
}
|
||||
return reach.mouth() ? RiverSection.MOUTH : RiverSection.CHANNEL;
|
||||
}
|
||||
return reach.state() == RiverRouteState.DRY ? RiverSection.DRY_BANK : RiverSection.BANK;
|
||||
}
|
||||
|
||||
private void addAnchors(
|
||||
RiverReach reach,
|
||||
double spacing,
|
||||
long salt,
|
||||
double queryMinimumX,
|
||||
double queryMinimumZ,
|
||||
double queryMaximumX,
|
||||
double queryMaximumZ,
|
||||
List<RiverAnchor> anchors
|
||||
) {
|
||||
double length = reach.polyline().length();
|
||||
double firstDistance = unit(RiverNetwork.mix(reach.id().stableId() ^ salt)) * spacing;
|
||||
int index = 0;
|
||||
for (double distance = firstDistance; distance < length; distance += spacing) {
|
||||
Position position = positionAt(reach.polyline(), distance);
|
||||
if (position.x() >= minimumX && position.x() < maximumX
|
||||
&& position.z() >= minimumZ && position.z() < maximumZ
|
||||
&& position.x() >= queryMinimumX && position.x() < queryMaximumX
|
||||
&& position.z() >= queryMinimumZ && position.z() < queryMaximumZ) {
|
||||
long stableId = RiverNetwork.mix(
|
||||
reach.id().stableId() ^ salt ^ (long) index * 0x9E3779B97F4A7C15L
|
||||
);
|
||||
anchors.add(new RiverAnchor(
|
||||
reach.id(),
|
||||
index,
|
||||
stableId,
|
||||
spacing,
|
||||
salt,
|
||||
position.x(),
|
||||
position.z(),
|
||||
position.alongReach(),
|
||||
reach.state(),
|
||||
reach.flow(),
|
||||
reach.order()
|
||||
));
|
||||
}
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
private static Position positionAt(RiverPolyline polyline, double targetDistance) {
|
||||
double traversed = 0.0;
|
||||
for (int point = 0; point < polyline.size() - 1; point++) {
|
||||
double startX = polyline.x(point);
|
||||
double startZ = polyline.z(point);
|
||||
double deltaX = polyline.x(point + 1) - startX;
|
||||
double deltaZ = polyline.z(point + 1) - startZ;
|
||||
double segmentLength = StrictMath.hypot(deltaX, deltaZ);
|
||||
if (targetDistance <= traversed + segmentLength || point == polyline.size() - 2) {
|
||||
double t = segmentLength == 0.0 ? 0.0 : (targetDistance - traversed) / segmentLength;
|
||||
t = StrictMath.max(0.0, StrictMath.min(1.0, t));
|
||||
double alongReach = polyline.length() == 0.0 ? 0.0 : targetDistance / polyline.length();
|
||||
return new Position(startX + deltaX * t, startZ + deltaZ * t, alongReach);
|
||||
}
|
||||
traversed += segmentLength;
|
||||
}
|
||||
return new Position(
|
||||
polyline.x(polyline.size() - 1),
|
||||
polyline.z(polyline.size() - 1),
|
||||
1.0
|
||||
);
|
||||
}
|
||||
|
||||
private static double unit(long hash) {
|
||||
return (hash >>> 11) * 0x1.0p-53;
|
||||
}
|
||||
|
||||
private static double carveWeight(double distance, double channelRadius, double bankWidth) {
|
||||
if (distance <= channelRadius || bankWidth == 0.0) {
|
||||
return 1.0;
|
||||
}
|
||||
double t = StrictMath.min(1.0, (distance - channelRadius) / bankWidth);
|
||||
double smooth = t * t * (3.0 - 2.0 * t);
|
||||
return 1.0 - smooth;
|
||||
}
|
||||
|
||||
private static ClosestPoint closestCoveringPoint(
|
||||
RiverReach reach,
|
||||
double x,
|
||||
double z,
|
||||
double additionalRadius
|
||||
) {
|
||||
RiverPolyline polyline = reach.polyline();
|
||||
RiverBodyProfile bodyProfile = reach.bodyProfile();
|
||||
double polylineLength = polyline.length();
|
||||
if (polylineLength == 0D) {
|
||||
double distanceSquared = squared(x - polyline.x(0)) + squared(z - polyline.z(0));
|
||||
double radius = reach.widthAt(0D) * 0.5D + reach.bankWidthAt(0D) + additionalRadius;
|
||||
return distanceSquared <= radius * radius ? new ClosestPoint(distanceSquared, 0D) : null;
|
||||
}
|
||||
double nearest = Double.POSITIVE_INFINITY;
|
||||
double nearestAlong = 0.0;
|
||||
int pointLimit = polyline.size() - 1;
|
||||
int profileLimit = bodyProfile.size() - 1;
|
||||
for (int point = 0; point < pointLimit; point++) {
|
||||
double segmentStartAlong = polyline.cumulativeLength(point) / polylineLength;
|
||||
double segmentEndAlong = polyline.cumulativeLength(point + 1) / polylineLength;
|
||||
double segmentAlongSpan = segmentEndAlong - segmentStartAlong;
|
||||
if (segmentAlongSpan == 0D) {
|
||||
continue;
|
||||
}
|
||||
double startX = polyline.x(point);
|
||||
double startZ = polyline.z(point);
|
||||
double deltaX = polyline.x(point + 1) - startX;
|
||||
double deltaZ = polyline.z(point + 1) - startZ;
|
||||
int firstProfileIndex = bodyProfile.intervalIndex(segmentStartAlong);
|
||||
for (int profileIndex = firstProfileIndex;
|
||||
profileIndex < profileLimit
|
||||
&& bodyProfile.position(profileIndex) <= segmentEndAlong;
|
||||
profileIndex++) {
|
||||
double profileStart = bodyProfile.position(profileIndex);
|
||||
double profileEnd = bodyProfile.position(profileIndex + 1);
|
||||
double overlapStart = StrictMath.max(segmentStartAlong, profileStart);
|
||||
double overlapEnd = StrictMath.min(segmentEndAlong, profileEnd);
|
||||
if (overlapStart > overlapEnd) {
|
||||
continue;
|
||||
}
|
||||
double intervalStart = (overlapStart - segmentStartAlong) / segmentAlongSpan;
|
||||
double intervalEnd = (overlapEnd - segmentStartAlong) / segmentAlongSpan;
|
||||
double profileSpan = profileEnd - profileStart;
|
||||
double profileWidth = bodyProfile.widthAtIndex(profileIndex);
|
||||
double profileBankWidth = bodyProfile.bankWidthAtIndex(profileIndex);
|
||||
double widthSlope = (bodyProfile.widthAtIndex(profileIndex + 1)
|
||||
- profileWidth) / profileSpan;
|
||||
double bankSlope = (bodyProfile.bankWidthAtIndex(profileIndex + 1)
|
||||
- profileBankWidth) / profileSpan;
|
||||
double radiusBase = (profileWidth
|
||||
+ widthSlope * (segmentStartAlong - profileStart)) * 0.5D
|
||||
+ profileBankWidth
|
||||
+ bankSlope * (segmentStartAlong - profileStart)
|
||||
+ additionalRadius;
|
||||
double radiusSlope = (widthSlope * 0.5D + bankSlope) * segmentAlongSpan;
|
||||
ClosestPoint candidate = coveringPoint(
|
||||
intervalStart,
|
||||
intervalEnd,
|
||||
deltaX,
|
||||
startX - x,
|
||||
deltaZ,
|
||||
startZ - z,
|
||||
radiusSlope,
|
||||
radiusBase,
|
||||
segmentStartAlong,
|
||||
segmentAlongSpan
|
||||
);
|
||||
if (candidate != null && candidate.distanceSquared() < nearest) {
|
||||
nearest = candidate.distanceSquared();
|
||||
nearestAlong = candidate.alongReach();
|
||||
}
|
||||
}
|
||||
}
|
||||
return Double.isFinite(nearest) ? new ClosestPoint(nearest, nearestAlong) : null;
|
||||
}
|
||||
|
||||
private static ClosestPoint closestCoveringPoint(
|
||||
RiverReach reach,
|
||||
double minimumX,
|
||||
double minimumZ,
|
||||
double maximumX,
|
||||
double maximumZ
|
||||
) {
|
||||
RiverPolyline polyline = reach.polyline();
|
||||
RiverBodyProfile bodyProfile = reach.bodyProfile();
|
||||
double polylineLength = polyline.length();
|
||||
if (polylineLength == 0D) {
|
||||
double distanceSquared = pointRectangleDistanceSquared(
|
||||
polyline.x(0),
|
||||
polyline.z(0),
|
||||
minimumX,
|
||||
minimumZ,
|
||||
maximumX,
|
||||
maximumZ
|
||||
);
|
||||
double radius = reach.widthAt(0D) * 0.5D + reach.bankWidthAt(0D);
|
||||
return distanceSquared <= radius * radius ? new ClosestPoint(distanceSquared, 0D) : null;
|
||||
}
|
||||
double nearest = Double.POSITIVE_INFINITY;
|
||||
double nearestAlong = 0.0;
|
||||
int pointLimit = polyline.size() - 1;
|
||||
int profileLimit = bodyProfile.size() - 1;
|
||||
for (int point = 0; point < pointLimit; point++) {
|
||||
double segmentStartAlong = polyline.cumulativeLength(point) / polylineLength;
|
||||
double segmentEndAlong = polyline.cumulativeLength(point + 1) / polylineLength;
|
||||
double segmentAlongSpan = segmentEndAlong - segmentStartAlong;
|
||||
if (segmentAlongSpan == 0D) {
|
||||
continue;
|
||||
}
|
||||
double startX = polyline.x(point);
|
||||
double startZ = polyline.z(point);
|
||||
double deltaX = polyline.x(point + 1) - startX;
|
||||
double deltaZ = polyline.z(point + 1) - startZ;
|
||||
int firstProfileIndex = bodyProfile.intervalIndex(segmentStartAlong);
|
||||
for (int profileIndex = firstProfileIndex;
|
||||
profileIndex < profileLimit
|
||||
&& bodyProfile.position(profileIndex) <= segmentEndAlong;
|
||||
profileIndex++) {
|
||||
double profileStart = bodyProfile.position(profileIndex);
|
||||
double profileEnd = bodyProfile.position(profileIndex + 1);
|
||||
double overlapStart = StrictMath.max(segmentStartAlong, profileStart);
|
||||
double overlapEnd = StrictMath.min(segmentEndAlong, profileEnd);
|
||||
if (overlapStart > overlapEnd) {
|
||||
continue;
|
||||
}
|
||||
double intervalStart = (overlapStart - segmentStartAlong) / segmentAlongSpan;
|
||||
double intervalEnd = (overlapEnd - segmentStartAlong) / segmentAlongSpan;
|
||||
double profileSpan = profileEnd - profileStart;
|
||||
double profileWidth = bodyProfile.widthAtIndex(profileIndex);
|
||||
double profileBankWidth = bodyProfile.bankWidthAtIndex(profileIndex);
|
||||
double widthSlope = (bodyProfile.widthAtIndex(profileIndex + 1)
|
||||
- profileWidth) / profileSpan;
|
||||
double bankSlope = (bodyProfile.bankWidthAtIndex(profileIndex + 1)
|
||||
- profileBankWidth) / profileSpan;
|
||||
double radiusBase = (profileWidth
|
||||
+ widthSlope * (segmentStartAlong - profileStart)) * 0.5D
|
||||
+ profileBankWidth
|
||||
+ bankSlope * (segmentStartAlong - profileStart);
|
||||
double radiusSlope = (widthSlope * 0.5D + bankSlope) * segmentAlongSpan;
|
||||
double cursor = intervalStart;
|
||||
do {
|
||||
double next = intervalEnd;
|
||||
next = nextCrossing(startX, deltaX, minimumX, cursor, next);
|
||||
next = nextCrossing(startX, deltaX, maximumX, cursor, next);
|
||||
next = nextCrossing(startZ, deltaZ, minimumZ, cursor, next);
|
||||
next = nextCrossing(startZ, deltaZ, maximumZ, cursor, next);
|
||||
double middle = (cursor + next) * 0.5D;
|
||||
double middleX = startX + deltaX * middle;
|
||||
double middleZ = startZ + deltaZ * middle;
|
||||
double distanceSlopeX = middleX < minimumX || middleX > maximumX ? deltaX : 0D;
|
||||
double distanceBaseX = middleX < minimumX
|
||||
? startX - minimumX
|
||||
: middleX > maximumX ? startX - maximumX : 0D;
|
||||
double distanceSlopeZ = middleZ < minimumZ || middleZ > maximumZ ? deltaZ : 0D;
|
||||
double distanceBaseZ = middleZ < minimumZ
|
||||
? startZ - minimumZ
|
||||
: middleZ > maximumZ ? startZ - maximumZ : 0D;
|
||||
ClosestPoint candidate = coveringPoint(
|
||||
cursor,
|
||||
next,
|
||||
distanceSlopeX,
|
||||
distanceBaseX,
|
||||
distanceSlopeZ,
|
||||
distanceBaseZ,
|
||||
radiusSlope,
|
||||
radiusBase,
|
||||
segmentStartAlong,
|
||||
segmentAlongSpan
|
||||
);
|
||||
if (candidate != null && candidate.distanceSquared() < nearest) {
|
||||
nearest = candidate.distanceSquared();
|
||||
nearestAlong = candidate.alongReach();
|
||||
}
|
||||
cursor = next;
|
||||
} while (cursor < intervalEnd);
|
||||
}
|
||||
}
|
||||
return Double.isFinite(nearest) ? new ClosestPoint(nearest, nearestAlong) : null;
|
||||
}
|
||||
|
||||
private static ClosestPoint coveringPoint(
|
||||
double intervalStart,
|
||||
double intervalEnd,
|
||||
double distanceSlopeX,
|
||||
double distanceBaseX,
|
||||
double distanceSlopeZ,
|
||||
double distanceBaseZ,
|
||||
double radiusSlope,
|
||||
double radiusBase,
|
||||
double segmentStartAlong,
|
||||
double segmentAlongSpan
|
||||
) {
|
||||
double distanceQuadratic = squared(distanceSlopeX) + squared(distanceSlopeZ);
|
||||
double distanceLinear = 2D * (distanceSlopeX * distanceBaseX + distanceSlopeZ * distanceBaseZ);
|
||||
double distanceConstant = squared(distanceBaseX) + squared(distanceBaseZ);
|
||||
double coverageQuadratic = distanceQuadratic - squared(radiusSlope);
|
||||
double coverageLinear = distanceLinear - 2D * radiusSlope * radiusBase;
|
||||
double coverageConstant = distanceConstant - squared(radiusBase);
|
||||
double distancePosition = distanceQuadratic == 0D
|
||||
? intervalStart
|
||||
: clamp(-distanceLinear / (2D * distanceQuadratic), intervalStart, intervalEnd);
|
||||
if (quadraticValue(coverageQuadratic, coverageLinear, coverageConstant, distancePosition) <= 0D) {
|
||||
return new ClosestPoint(
|
||||
StrictMath.max(0D, quadraticValue(
|
||||
distanceQuadratic,
|
||||
distanceLinear,
|
||||
distanceConstant,
|
||||
distancePosition
|
||||
)),
|
||||
segmentStartAlong + segmentAlongSpan * distancePosition
|
||||
);
|
||||
}
|
||||
double coveragePosition = intervalStart;
|
||||
double minimumCoverage = quadraticValue(
|
||||
coverageQuadratic,
|
||||
coverageLinear,
|
||||
coverageConstant,
|
||||
coveragePosition
|
||||
);
|
||||
double endCoverage = quadraticValue(coverageQuadratic, coverageLinear, coverageConstant, intervalEnd);
|
||||
if (endCoverage < minimumCoverage) {
|
||||
minimumCoverage = endCoverage;
|
||||
coveragePosition = intervalEnd;
|
||||
}
|
||||
if (coverageQuadratic > 0D) {
|
||||
double vertex = clamp(-coverageLinear / (2D * coverageQuadratic), intervalStart, intervalEnd);
|
||||
double vertexCoverage = quadraticValue(coverageQuadratic, coverageLinear, coverageConstant, vertex);
|
||||
if (vertexCoverage < minimumCoverage) {
|
||||
minimumCoverage = vertexCoverage;
|
||||
coveragePosition = vertex;
|
||||
}
|
||||
}
|
||||
if (minimumCoverage > 0D) {
|
||||
return null;
|
||||
}
|
||||
double uncovered = distancePosition;
|
||||
double covered = coveragePosition;
|
||||
for (int iteration = 0; iteration < 40; iteration++) {
|
||||
double middle = (uncovered + covered) * 0.5D;
|
||||
if (quadraticValue(coverageQuadratic, coverageLinear, coverageConstant, middle) <= 0D) {
|
||||
covered = middle;
|
||||
} else {
|
||||
uncovered = middle;
|
||||
}
|
||||
}
|
||||
return new ClosestPoint(
|
||||
StrictMath.max(0D, quadraticValue(
|
||||
distanceQuadratic,
|
||||
distanceLinear,
|
||||
distanceConstant,
|
||||
covered
|
||||
)),
|
||||
segmentStartAlong + segmentAlongSpan * covered
|
||||
);
|
||||
}
|
||||
|
||||
private static double nextCrossing(
|
||||
double start,
|
||||
double delta,
|
||||
double boundary,
|
||||
double cursor,
|
||||
double currentNext
|
||||
) {
|
||||
if (delta == 0D) {
|
||||
return currentNext;
|
||||
}
|
||||
double crossing = (boundary - start) / delta;
|
||||
return crossing > cursor && crossing < currentNext ? crossing : currentNext;
|
||||
}
|
||||
|
||||
private static double quadraticValue(double quadratic, double linear, double constant, double value) {
|
||||
return (quadratic * value + linear) * value + constant;
|
||||
}
|
||||
|
||||
private static double clamp(double value, double minimum, double maximum) {
|
||||
return StrictMath.max(minimum, StrictMath.min(maximum, value));
|
||||
}
|
||||
|
||||
private static double pointRectangleDistanceSquared(
|
||||
double x,
|
||||
double z,
|
||||
double minimumX,
|
||||
double minimumZ,
|
||||
double maximumX,
|
||||
double maximumZ
|
||||
) {
|
||||
double deltaX = x < minimumX ? minimumX - x : StrictMath.max(0.0, x - maximumX);
|
||||
double deltaZ = z < minimumZ ? minimumZ - z : StrictMath.max(0.0, z - maximumZ);
|
||||
return squared(deltaX) + squared(deltaZ);
|
||||
}
|
||||
|
||||
private static double squared(double value) {
|
||||
return value * value;
|
||||
}
|
||||
|
||||
private static Map<Long, List<RiverReach>> createSpatialIndex(List<RiverReach> reaches) {
|
||||
HashMap<Long, Set<RiverReach>> mutable = new HashMap<>();
|
||||
for (RiverReach reach : reaches) {
|
||||
double radius = reach.width() * 0.5 + reach.bankWidth();
|
||||
RiverPolyline polyline = reach.polyline();
|
||||
for (int point = 0; point < polyline.size() - 1; point++) {
|
||||
int minimumBucketX = bucket(StrictMath.min(polyline.x(point), polyline.x(point + 1)) - radius);
|
||||
int maximumBucketX = bucket(StrictMath.max(polyline.x(point), polyline.x(point + 1)) + radius);
|
||||
int minimumBucketZ = bucket(StrictMath.min(polyline.z(point), polyline.z(point + 1)) - radius);
|
||||
int maximumBucketZ = bucket(StrictMath.max(polyline.z(point), polyline.z(point + 1)) + radius);
|
||||
for (int bucketX = minimumBucketX; bucketX <= maximumBucketX; bucketX++) {
|
||||
for (int bucketZ = minimumBucketZ; bucketZ <= maximumBucketZ; bucketZ++) {
|
||||
mutable.computeIfAbsent(bucketKey(bucketX, bucketZ), ignored -> new LinkedHashSet<>()).add(reach);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
HashMap<Long, List<RiverReach>> immutable = new HashMap<>(mutable.size());
|
||||
for (Map.Entry<Long, Set<RiverReach>> entry : mutable.entrySet()) {
|
||||
immutable.put(entry.getKey(), List.copyOf(entry.getValue()));
|
||||
}
|
||||
return Map.copyOf(immutable);
|
||||
}
|
||||
|
||||
private static Map<RiverEdgeId, RiverReach> indexById(List<RiverReach> reaches) {
|
||||
HashMap<RiverEdgeId, RiverReach> indexed = new HashMap<>(reaches.size());
|
||||
for (RiverReach reach : reaches) {
|
||||
RiverReach previous = indexed.put(reach.id(), reach);
|
||||
if (previous != null) {
|
||||
throw new IllegalArgumentException("River tile cannot contain duplicate reach IDs");
|
||||
}
|
||||
}
|
||||
return Map.copyOf(indexed);
|
||||
}
|
||||
|
||||
private List<RiverReach> indexedReaches(double x, double z) {
|
||||
return spatialIndex.getOrDefault(bucketKey(bucket(x), bucket(z)), List.of());
|
||||
}
|
||||
|
||||
private List<RiverReach> indexedReaches(
|
||||
double queryMinimumX,
|
||||
double queryMinimumZ,
|
||||
double queryMaximumX,
|
||||
double queryMaximumZ
|
||||
) {
|
||||
LinkedHashSet<RiverReach> indexed = new LinkedHashSet<>();
|
||||
int minimumBucketX = bucket(queryMinimumX);
|
||||
int maximumBucketX = bucket(StrictMath.nextDown(queryMaximumX));
|
||||
int minimumBucketZ = bucket(queryMinimumZ);
|
||||
int maximumBucketZ = bucket(StrictMath.nextDown(queryMaximumZ));
|
||||
if (minimumBucketX == maximumBucketX && minimumBucketZ == maximumBucketZ) {
|
||||
return spatialIndex.getOrDefault(
|
||||
bucketKey(minimumBucketX, minimumBucketZ),
|
||||
List.of()
|
||||
);
|
||||
}
|
||||
for (int bucketX = minimumBucketX; bucketX <= maximumBucketX; bucketX++) {
|
||||
for (int bucketZ = minimumBucketZ; bucketZ <= maximumBucketZ; bucketZ++) {
|
||||
indexed.addAll(spatialIndex.getOrDefault(bucketKey(bucketX, bucketZ), List.of()));
|
||||
}
|
||||
}
|
||||
return List.copyOf(indexed);
|
||||
}
|
||||
|
||||
private List<RiverReach> indexedReachesInclusive(
|
||||
double queryMinimumX,
|
||||
double queryMinimumZ,
|
||||
double queryMaximumX,
|
||||
double queryMaximumZ
|
||||
) {
|
||||
int minimumBucketX = bucket(queryMinimumX);
|
||||
int maximumBucketX = bucket(queryMaximumX);
|
||||
int minimumBucketZ = bucket(queryMinimumZ);
|
||||
int maximumBucketZ = bucket(queryMaximumZ);
|
||||
if (minimumBucketX == maximumBucketX && minimumBucketZ == maximumBucketZ) {
|
||||
return indexedReaches(queryMinimumX, queryMinimumZ);
|
||||
}
|
||||
LinkedHashSet<RiverReach> indexed = new LinkedHashSet<>();
|
||||
for (int bucketX = minimumBucketX; bucketX <= maximumBucketX; bucketX++) {
|
||||
for (int bucketZ = minimumBucketZ; bucketZ <= maximumBucketZ; bucketZ++) {
|
||||
indexed.addAll(spatialIndex.getOrDefault(bucketKey(bucketX, bucketZ), List.of()));
|
||||
}
|
||||
}
|
||||
return List.copyOf(indexed);
|
||||
}
|
||||
|
||||
private static int bucket(double coordinate) {
|
||||
return (int) StrictMath.floor(coordinate / BUCKET_SIZE);
|
||||
}
|
||||
|
||||
private static long bucketKey(int bucketX, int bucketZ) {
|
||||
return ((long) bucketX << 32) ^ (bucketZ & 0xFFFFFFFFL);
|
||||
}
|
||||
|
||||
private record Position(double x, double z, double alongReach) {
|
||||
}
|
||||
|
||||
private record ClosestPoint(double distanceSquared, double alongReach) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,200 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CompletionException;
|
||||
|
||||
public final class RiverTileCache implements AutoCloseable {
|
||||
private final Object lock;
|
||||
private final int maxCompletedEntries;
|
||||
private final Map<TileKey, Entry> entries;
|
||||
private final LinkedHashMap<TileKey, Entry> completedEntries;
|
||||
private TileBuilder builder;
|
||||
private boolean closed;
|
||||
|
||||
public RiverTileCache(int maxCompletedEntries, TileBuilder builder) {
|
||||
if (maxCompletedEntries < 1) {
|
||||
throw new IllegalArgumentException("River tile cache capacity must be positive");
|
||||
}
|
||||
this.maxCompletedEntries = maxCompletedEntries;
|
||||
this.builder = Objects.requireNonNull(builder);
|
||||
lock = new Object();
|
||||
entries = new HashMap<>(maxCompletedEntries);
|
||||
completedEntries = new LinkedHashMap<>(maxCompletedEntries, 0.75f, true);
|
||||
}
|
||||
|
||||
public RiverTile get(int tileX, int tileZ) {
|
||||
TileKey key = new TileKey(tileX, tileZ);
|
||||
Entry entry;
|
||||
TileBuilder activeBuilder;
|
||||
boolean build;
|
||||
synchronized (lock) {
|
||||
requireOpen();
|
||||
entry = entries.get(key);
|
||||
if (entry == null) {
|
||||
entry = new Entry();
|
||||
entries.put(key, entry);
|
||||
activeBuilder = builder;
|
||||
build = true;
|
||||
} else {
|
||||
if (entry.completed) {
|
||||
completedEntries.get(key);
|
||||
}
|
||||
activeBuilder = null;
|
||||
build = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (build) {
|
||||
build(key, entry, activeBuilder);
|
||||
}
|
||||
return await(entry.future, key);
|
||||
}
|
||||
|
||||
public int completedSize() {
|
||||
synchronized (lock) {
|
||||
return completedEntries.size();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
synchronized (lock) {
|
||||
return closed;
|
||||
}
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
List<CompletableFuture<RiverTile>> invalidated;
|
||||
synchronized (lock) {
|
||||
requireOpen();
|
||||
invalidated = clearLocked();
|
||||
}
|
||||
invalidate(invalidated, "River tile cache was cleared");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
List<CompletableFuture<RiverTile>> invalidated;
|
||||
synchronized (lock) {
|
||||
if (closed) {
|
||||
return;
|
||||
}
|
||||
closed = true;
|
||||
builder = null;
|
||||
invalidated = clearLocked();
|
||||
}
|
||||
invalidate(invalidated, "River tile cache was closed");
|
||||
}
|
||||
|
||||
private void build(TileKey key, Entry entry, TileBuilder activeBuilder) {
|
||||
try {
|
||||
RiverTile tile = Objects.requireNonNull(
|
||||
activeBuilder.build(key.tileX(), key.tileZ()),
|
||||
"River tile builder returned null"
|
||||
);
|
||||
if (tile.tileX() != key.tileX() || tile.tileZ() != key.tileZ()) {
|
||||
throw new IllegalStateException(
|
||||
"River tile builder returned " + tile.tileX() + "," + tile.tileZ()
|
||||
+ " for " + key.tileX() + "," + key.tileZ()
|
||||
);
|
||||
}
|
||||
publishCompleted(key, entry, tile);
|
||||
} catch (Throwable failure) {
|
||||
removeFailed(key, entry);
|
||||
entry.future.completeExceptionally(failure);
|
||||
}
|
||||
}
|
||||
|
||||
private void publishCompleted(TileKey key, Entry entry, RiverTile tile) {
|
||||
synchronized (lock) {
|
||||
if (closed || entries.get(key) != entry) {
|
||||
entry.future.completeExceptionally(new IllegalStateException(
|
||||
closed ? "River tile cache was closed" : "River tile cache entry was cleared"
|
||||
));
|
||||
return;
|
||||
}
|
||||
entry.completed = true;
|
||||
completedEntries.put(key, entry);
|
||||
while (completedEntries.size() > maxCompletedEntries) {
|
||||
Map.Entry<TileKey, Entry> eldest = completedEntries.entrySet().iterator().next();
|
||||
completedEntries.remove(eldest.getKey());
|
||||
entries.remove(eldest.getKey(), eldest.getValue());
|
||||
}
|
||||
entry.future.complete(tile);
|
||||
}
|
||||
}
|
||||
|
||||
private void removeFailed(TileKey key, Entry entry) {
|
||||
synchronized (lock) {
|
||||
entries.remove(key, entry);
|
||||
completedEntries.remove(key, entry);
|
||||
}
|
||||
}
|
||||
|
||||
private List<CompletableFuture<RiverTile>> clearLocked() {
|
||||
ArrayList<CompletableFuture<RiverTile>> invalidated = new ArrayList<>(entries.size());
|
||||
for (Entry entry : entries.values()) {
|
||||
if (!entry.future.isDone()) {
|
||||
invalidated.add(entry.future);
|
||||
}
|
||||
}
|
||||
entries.clear();
|
||||
completedEntries.clear();
|
||||
return invalidated;
|
||||
}
|
||||
|
||||
private void requireOpen() {
|
||||
if (closed) {
|
||||
throw new IllegalStateException("River tile cache is closed");
|
||||
}
|
||||
}
|
||||
|
||||
private static RiverTile await(CompletableFuture<RiverTile> future, TileKey key) {
|
||||
try {
|
||||
return future.join();
|
||||
} catch (CompletionException failure) {
|
||||
Throwable cause = failure.getCause();
|
||||
if (cause instanceof InterruptedException) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
if (cause instanceof RuntimeException runtimeException) {
|
||||
throw runtimeException;
|
||||
}
|
||||
if (cause instanceof Error error) {
|
||||
throw error;
|
||||
}
|
||||
throw new IllegalStateException(
|
||||
"Failed to build river tile " + key.tileX() + "," + key.tileZ(),
|
||||
cause
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static void invalidate(List<CompletableFuture<RiverTile>> futures, String message) {
|
||||
for (CompletableFuture<RiverTile> future : futures) {
|
||||
future.completeExceptionally(new IllegalStateException(message));
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface TileBuilder {
|
||||
RiverTile build(int tileX, int tileZ) throws Exception;
|
||||
}
|
||||
|
||||
private record TileKey(int tileX, int tileZ) {
|
||||
}
|
||||
|
||||
private static final class Entry {
|
||||
private final CompletableFuture<RiverTile> future;
|
||||
private boolean completed;
|
||||
|
||||
private Entry() {
|
||||
future = new CompletableFuture<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,211 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class RiverTopologyComplexity {
|
||||
public static final long MAXIMUM_SOURCE_WINDOW_CELLS = 65_536L;
|
||||
public static final long MAXIMUM_ROUTE_SCAN_STEPS = 65_536L;
|
||||
public static final long MAXIMUM_BUCKET_WRITES_PER_REACH = 1_048_576L;
|
||||
public static final long MAXIMUM_TUNNEL_SAMPLE_COLUMNS = 65_536L;
|
||||
private static final int SPATIAL_BUCKET_SIZE = 64;
|
||||
|
||||
private RiverTopologyComplexity() {
|
||||
}
|
||||
|
||||
public static Estimate estimate(
|
||||
int cellSize,
|
||||
int tileCells,
|
||||
double siteJitter,
|
||||
int maxRouteReaches,
|
||||
double maximumReachRadius,
|
||||
double maximumWormOffset,
|
||||
int maximumWormSegments
|
||||
) {
|
||||
double maximumEdgeAxisDelta = cellSize * (1D + siteJitter);
|
||||
long geometryPaddingCells = 1L + ceilToLong(
|
||||
(maximumReachRadius + maximumWormOffset) / cellSize
|
||||
);
|
||||
long targetWindowAxis = saturatedAdd(tileCells, saturatedMultiply(2L, geometryPaddingCells));
|
||||
long sourceWindowAxis = saturatedAdd(
|
||||
targetWindowAxis,
|
||||
saturatedMultiply(2L, maxRouteReaches)
|
||||
);
|
||||
long sourceWindowCells = saturatedMultiply(sourceWindowAxis, sourceWindowAxis);
|
||||
long maximumRouteScanSteps = saturatedMultiply(sourceWindowCells, maxRouteReaches);
|
||||
double maximumSegmentSpan = maximumEdgeAxisDelta
|
||||
+ maximumWormOffset * 2D
|
||||
+ maximumReachRadius * 2D;
|
||||
long maximumSegmentBucketAxis = saturatedAdd(
|
||||
ceilToLong(maximumSegmentSpan / SPATIAL_BUCKET_SIZE),
|
||||
1L
|
||||
);
|
||||
long maximumSegmentBucketCount = saturatedMultiply(
|
||||
maximumSegmentBucketAxis,
|
||||
maximumSegmentBucketAxis
|
||||
);
|
||||
long maximumBucketWritesPerReach = saturatedMultiply(
|
||||
maximumSegmentBucketCount,
|
||||
maximumWormSegments
|
||||
);
|
||||
return new Estimate(
|
||||
geometryPaddingCells,
|
||||
sourceWindowAxis,
|
||||
sourceWindowCells,
|
||||
maximumRouteScanSteps,
|
||||
maximumSegmentBucketAxis,
|
||||
maximumBucketWritesPerReach
|
||||
);
|
||||
}
|
||||
|
||||
public static void requireSafe(
|
||||
int cellSize,
|
||||
int tileCells,
|
||||
double siteJitter,
|
||||
int maxRouteReaches,
|
||||
double maximumReachRadius,
|
||||
double maximumWormOffset,
|
||||
int maximumWormSegments
|
||||
) {
|
||||
Estimate estimate = estimate(
|
||||
cellSize,
|
||||
tileCells,
|
||||
siteJitter,
|
||||
maxRouteReaches,
|
||||
maximumReachRadius,
|
||||
maximumWormOffset,
|
||||
maximumWormSegments
|
||||
);
|
||||
List<String> violations = estimate.violations();
|
||||
if (!violations.isEmpty()) {
|
||||
throw new IllegalArgumentException(String.join(" ", violations));
|
||||
}
|
||||
}
|
||||
|
||||
public static int tunnelHalo(
|
||||
double maximumChannelWidth,
|
||||
double maximumTunnelWidthMultiplier,
|
||||
double tunnelMouthBlend
|
||||
) {
|
||||
if (!Double.isFinite(maximumChannelWidth) || maximumChannelWidth <= 0D
|
||||
|| !Double.isFinite(maximumTunnelWidthMultiplier) || maximumTunnelWidthMultiplier < 1D
|
||||
|| !Double.isFinite(tunnelMouthBlend) || tunnelMouthBlend < 0D) {
|
||||
throw new IllegalArgumentException("River tunnel dimensions must be finite and valid");
|
||||
}
|
||||
return Math.max(
|
||||
1,
|
||||
(int) StrictMath.ceil(
|
||||
maximumChannelWidth * 0.5D * maximumTunnelWidthMultiplier + tunnelMouthBlend
|
||||
) + 1
|
||||
);
|
||||
}
|
||||
|
||||
public static long tunnelSampleColumns(
|
||||
double maximumChannelWidth,
|
||||
double maximumTunnelWidthMultiplier,
|
||||
double tunnelMouthBlend
|
||||
) {
|
||||
long axis = 16L + 2L * tunnelHalo(
|
||||
maximumChannelWidth,
|
||||
maximumTunnelWidthMultiplier,
|
||||
tunnelMouthBlend
|
||||
);
|
||||
return saturatedMultiply(axis, axis);
|
||||
}
|
||||
|
||||
public static String tunnelPlanViolation(
|
||||
double maximumChannelWidth,
|
||||
double maximumTunnelWidthMultiplier,
|
||||
double tunnelMouthBlend
|
||||
) {
|
||||
long columns = tunnelSampleColumns(
|
||||
maximumChannelWidth,
|
||||
maximumTunnelWidthMultiplier,
|
||||
tunnelMouthBlend
|
||||
);
|
||||
if (columns <= MAXIMUM_TUNNEL_SAMPLE_COLUMNS) {
|
||||
return null;
|
||||
}
|
||||
return "River tunnel planning may sample " + columns
|
||||
+ " columns per generated chunk, above the safe limit of " + MAXIMUM_TUNNEL_SAMPLE_COLUMNS
|
||||
+ "; reduce maxChannelWidth, tunnelWidthMultiplier.max, or tunnelMouthBlend.";
|
||||
}
|
||||
|
||||
public static void requireSafeTunnelPlan(
|
||||
double maximumChannelWidth,
|
||||
double maximumTunnelWidthMultiplier,
|
||||
double tunnelMouthBlend
|
||||
) {
|
||||
String violation = tunnelPlanViolation(
|
||||
maximumChannelWidth,
|
||||
maximumTunnelWidthMultiplier,
|
||||
tunnelMouthBlend
|
||||
);
|
||||
if (violation != null) {
|
||||
throw new IllegalArgumentException(violation);
|
||||
}
|
||||
}
|
||||
|
||||
private static long ceilToLong(double value) {
|
||||
if (!Double.isFinite(value) || value >= Long.MAX_VALUE) {
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
if (value <= 0D) {
|
||||
return 0L;
|
||||
}
|
||||
return (long) StrictMath.ceil(value);
|
||||
}
|
||||
|
||||
private static long saturatedAdd(long first, long second) {
|
||||
if (first > Long.MAX_VALUE - second) {
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
return first + second;
|
||||
}
|
||||
|
||||
private static long saturatedMultiply(long first, long second) {
|
||||
if (first == 0L || second == 0L) {
|
||||
return 0L;
|
||||
}
|
||||
if (first > Long.MAX_VALUE / second) {
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
return first * second;
|
||||
}
|
||||
|
||||
public record Estimate(
|
||||
long geometryPaddingCells,
|
||||
long sourceWindowAxis,
|
||||
long sourceWindowCells,
|
||||
long maximumRouteScanSteps,
|
||||
long maximumSegmentBucketAxis,
|
||||
long maximumBucketWritesPerReach
|
||||
) {
|
||||
public boolean safe() {
|
||||
return violations().isEmpty();
|
||||
}
|
||||
|
||||
public List<String> violations() {
|
||||
ArrayList<String> violations = new ArrayList<>(3);
|
||||
if (sourceWindowCells > MAXIMUM_SOURCE_WINDOW_CELLS) {
|
||||
violations.add("River topology source window requires " + sourceWindowCells
|
||||
+ " cells (" + sourceWindowAxis + " per axis), above the safe limit of "
|
||||
+ MAXIMUM_SOURCE_WINDOW_CELLS
|
||||
+ "; increase cellSize or reduce tileCells, maxRouteReaches, channel width, or bank width.");
|
||||
}
|
||||
if (maximumRouteScanSteps > MAXIMUM_ROUTE_SCAN_STEPS) {
|
||||
violations.add("River topology route scan permits " + maximumRouteScanSteps
|
||||
+ " source-to-reach steps, above the safe limit of " + MAXIMUM_ROUTE_SCAN_STEPS
|
||||
+ "; reduce maxRouteReaches, tileCells, channel width, or bank width.");
|
||||
}
|
||||
if (maximumBucketWritesPerReach > MAXIMUM_BUCKET_WRITES_PER_REACH) {
|
||||
violations.add("River topology spatial index may require " + maximumBucketWritesPerReach
|
||||
+ " bucket writes for one reach (" + maximumSegmentBucketAxis
|
||||
+ " buckets per segment axis), above the safe limit of "
|
||||
+ MAXIMUM_BUCKET_WRITES_PER_REACH
|
||||
+ "; reduce channel width, bank width, orderWidthFactor, worm maxOffset, or worm segments.");
|
||||
}
|
||||
return List.copyOf(violations);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package art.arcane.iris.engine.river;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record RiverWorm(
|
||||
String id,
|
||||
long seed,
|
||||
double weight,
|
||||
double wavelength,
|
||||
double detailWavelength,
|
||||
double tortuosity,
|
||||
double detailTortuosity,
|
||||
double maxOffset,
|
||||
int segments,
|
||||
double widthMultiplier,
|
||||
double bankMultiplier,
|
||||
double depthMultiplier,
|
||||
double bodyWavelength,
|
||||
double bodyDetailWavelength,
|
||||
double bodyDetailInfluence,
|
||||
double widthVariation,
|
||||
double bankVariation,
|
||||
double depthVariation,
|
||||
double roofVariation,
|
||||
int branchCap,
|
||||
double branchDecay,
|
||||
double confluenceMultiplier,
|
||||
double childChance,
|
||||
double branchChildChance,
|
||||
List<RiverWorm> children
|
||||
) {
|
||||
public RiverWorm {
|
||||
if (id == null || !id.matches("[a-z0-9][a-z0-9_-]{0,63}")) {
|
||||
throw new IllegalArgumentException("id must use 1 to 64 lowercase letters, digits, underscores, or hyphens");
|
||||
}
|
||||
requireRange(weight, 0.000001D, 1000000D, "weight");
|
||||
requireRange(wavelength, 8D, 16384D, "wavelength");
|
||||
requireRange(detailWavelength, 8D, 16384D, "detailWavelength");
|
||||
requireRange(tortuosity, 0D, 1D, "tortuosity");
|
||||
requireRange(detailTortuosity, 0D, 1D, "detailTortuosity");
|
||||
requireRange(maxOffset, 0D, 1024D, "maxOffset");
|
||||
if (segments < 1 || segments > 64) {
|
||||
throw new IllegalArgumentException("segments must be between 1 and 64");
|
||||
}
|
||||
requireRange(widthMultiplier, 0.125D, 8D, "widthMultiplier");
|
||||
requireRange(bankMultiplier, 0.125D, 8D, "bankMultiplier");
|
||||
requireRange(depthMultiplier, 0.125D, 8D, "depthMultiplier");
|
||||
requireRange(bodyWavelength, 8D, 16384D, "bodyWavelength");
|
||||
requireRange(bodyDetailWavelength, 8D, 16384D, "bodyDetailWavelength");
|
||||
requireRange(bodyDetailInfluence, 0D, 1D, "bodyDetailInfluence");
|
||||
requireRange(widthVariation, 0D, 0.875D, "widthVariation");
|
||||
requireRange(bankVariation, 0D, 0.875D, "bankVariation");
|
||||
requireRange(depthVariation, 0D, 0.875D, "depthVariation");
|
||||
requireRange(roofVariation, 0D, 0.875D, "roofVariation");
|
||||
if (branchCap < 1 || branchCap > 8) {
|
||||
throw new IllegalArgumentException("branchCap must be between 1 and 8");
|
||||
}
|
||||
requireRange(branchDecay, 0D, 1D, "branchDecay");
|
||||
requireRange(confluenceMultiplier, 0D, 8D, "confluenceMultiplier");
|
||||
requireRange(childChance, 0D, 1D, "childChance");
|
||||
requireRange(branchChildChance, 0D, 1D, "branchChildChance");
|
||||
if (children == null) {
|
||||
throw new IllegalArgumentException("children must not be null");
|
||||
}
|
||||
if (children.size() > 16) {
|
||||
throw new IllegalArgumentException("children must contain at most 16 profiles");
|
||||
}
|
||||
for (RiverWorm child : children) {
|
||||
if (child == null) {
|
||||
throw new IllegalArgumentException("children must not contain null profiles");
|
||||
}
|
||||
}
|
||||
children = List.copyOf(children);
|
||||
}
|
||||
|
||||
private static void requireRange(double value, double minimum, double maximum, String name) {
|
||||
if (!Double.isFinite(value) || value < minimum || value > maximum) {
|
||||
throw new IllegalArgumentException(name + " must be between " + minimum + " and " + maximum);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
package art.arcane.iris.engine.river.cave;
|
||||
|
||||
public record CavePosition(int x, int y, int z) {
|
||||
public CavePosition offset(int dx, int dy, int dz) {
|
||||
return new CavePosition(x + dx, y + dy, z + dz);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user