mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-05-18 23:50:35 +00:00
Opti pass 1
This commit is contained in:
Vendored
+1
-1
@@ -1 +1 @@
|
||||
149256635
|
||||
-1511497018
|
||||
@@ -195,7 +195,6 @@ public class IrisPregenerator {
|
||||
|
||||
private void init() {
|
||||
generator.init();
|
||||
generator.save();
|
||||
}
|
||||
|
||||
private void shutdown() {
|
||||
|
||||
@@ -20,6 +20,7 @@ package art.arcane.iris.core.pregenerator;
|
||||
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.volmlib.util.math.PowerOfTwoCoordinates;
|
||||
import art.arcane.volmlib.util.math.Position2;
|
||||
import art.arcane.volmlib.util.math.Spiraled;
|
||||
import art.arcane.volmlib.util.math.Spiraler;
|
||||
@@ -31,8 +32,7 @@ import java.util.Comparator;
|
||||
@Builder
|
||||
@Data
|
||||
public class PregenTask {
|
||||
private static final Position2 ZERO = new Position2(0, 0);
|
||||
private static final KMap<Position2, KList<Position2>> ORDERS = new KMap<>();
|
||||
private static final KMap<Long, int[]> ORDERS = new KMap<>();
|
||||
|
||||
@Builder.Default
|
||||
private final boolean gui = false;
|
||||
@@ -54,16 +54,28 @@ public class PregenTask {
|
||||
}
|
||||
|
||||
public static void iterateRegion(int xr, int zr, Spiraled s, Position2 pull) {
|
||||
for (Position2 i : ORDERS.computeIfAbsent(pull, PregenTask::computeOrder)) {
|
||||
s.on(i.getX() + (xr << 5), i.getZ() + (zr << 5));
|
||||
iterateRegion(xr, zr, s, pull.getX(), pull.getZ());
|
||||
}
|
||||
|
||||
public static void iterateRegion(int xr, int zr, Spiraled s, int pullX, int pullZ) {
|
||||
for (int packed : orderForPull(pullX, pullZ)) {
|
||||
s.on(PowerOfTwoCoordinates.unpackLocal32X(packed) + PowerOfTwoCoordinates.regionToChunk(xr), PowerOfTwoCoordinates.unpackLocal32Z(packed) + PowerOfTwoCoordinates.regionToChunk(zr));
|
||||
}
|
||||
}
|
||||
|
||||
public static void iterateRegion(int xr, int zr, Spiraled s) {
|
||||
iterateRegion(xr, zr, s, new Position2(-(xr << 5), -(zr << 5)));
|
||||
iterateRegion(xr, zr, s, -PowerOfTwoCoordinates.regionToChunk(xr), -PowerOfTwoCoordinates.regionToChunk(zr));
|
||||
}
|
||||
|
||||
private static KList<Position2> computeOrder(Position2 pull) {
|
||||
private static int[] orderForPull(int pullX, int pullZ) {
|
||||
long key = orderKey(pullX, pullZ);
|
||||
return ORDERS.computeIfAbsent(key, PregenTask::computeOrder);
|
||||
}
|
||||
|
||||
private static int[] computeOrder(long key) {
|
||||
int pullX = (int) (key >> 32);
|
||||
int pullZ = (int) key;
|
||||
Position2 pull = new Position2(pullX, pullZ);
|
||||
KList<Position2> p = new KList<>();
|
||||
new Spiraler(33, 33, (x, z) -> {
|
||||
int xx = (x + 15);
|
||||
@@ -76,18 +88,30 @@ public class PregenTask {
|
||||
}).drain();
|
||||
p.sort(Comparator.comparing((i) -> i.distance(pull)));
|
||||
|
||||
return p;
|
||||
int[] packed = new int[p.size()];
|
||||
for (int index = 0; index < p.size(); index++) {
|
||||
Position2 position = p.get(index);
|
||||
packed[index] = PowerOfTwoCoordinates.packLocal32(position.getX(), position.getZ());
|
||||
}
|
||||
|
||||
return packed;
|
||||
}
|
||||
|
||||
private static long orderKey(int pullX, int pullZ) {
|
||||
long high = (long) pullX << 32;
|
||||
long low = pullZ & 0xFFFFFFFFL;
|
||||
return high | low;
|
||||
}
|
||||
|
||||
public void iterateRegions(Spiraled s) {
|
||||
var bound = bounds.region();
|
||||
Bound bound = bounds.region();
|
||||
new Spiraler(bound.sizeX, bound.sizeZ, ((x, z) -> {
|
||||
if (bound.check(x, z)) s.on(x, z);
|
||||
})).setOffset(center.getX() >> 9, center.getZ() >> 9).drain();
|
||||
})).setOffset(PowerOfTwoCoordinates.blockToRegionFloor(center.getX()), PowerOfTwoCoordinates.blockToRegionFloor(center.getZ())).drain();
|
||||
}
|
||||
|
||||
public void iterateChunks(int rX, int rZ, Spiraled s) {
|
||||
var bound = bounds.chunk();
|
||||
Bound bound = bounds.chunk();
|
||||
iterateRegion(rX, rZ, ((x, z) -> {
|
||||
if (bound.check(x, z)) s.on(x, z);
|
||||
}));
|
||||
@@ -103,11 +127,11 @@ public class PregenTask {
|
||||
}
|
||||
|
||||
KList<RegionChunkCursor> cursors = new KList<>();
|
||||
Bound bound = bounds.chunk();
|
||||
iterateRegions((regionX, regionZ) -> {
|
||||
KList<Position2> chunks = new KList<>();
|
||||
iterateChunks(regionX, regionZ, (chunkX, chunkZ) -> chunks.add(new Position2(chunkX, chunkZ)));
|
||||
if (!chunks.isEmpty()) {
|
||||
cursors.add(new RegionChunkCursor(regionX, regionZ, chunks));
|
||||
RegionChunkCursor cursor = new RegionChunkCursor(regionX, regionZ, bound);
|
||||
if (cursor.hasNext()) {
|
||||
cursors.add(cursor);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -120,16 +144,18 @@ public class PregenTask {
|
||||
}
|
||||
|
||||
hasProgress = true;
|
||||
Position2 chunk = cursor.next();
|
||||
if (chunk == null) {
|
||||
long chunk = cursor.next();
|
||||
if (chunk == Long.MIN_VALUE) {
|
||||
continue;
|
||||
}
|
||||
int chunkX = (int) (chunk >> 32);
|
||||
int chunkZ = (int) chunk;
|
||||
|
||||
boolean shouldContinue = spiraled.on(
|
||||
cursor.getRegionX(),
|
||||
cursor.getRegionZ(),
|
||||
chunk.getX(),
|
||||
chunk.getZ(),
|
||||
chunkX,
|
||||
chunkZ,
|
||||
cursor.getIndex() == 1,
|
||||
!cursor.hasNext()
|
||||
);
|
||||
@@ -155,8 +181,18 @@ public class PregenTask {
|
||||
int minX = center.getX() - radiusX;
|
||||
int minZ = center.getZ() - radiusZ;
|
||||
|
||||
chunk = new Bound(minX >> 4, minZ >> 4, Math.ceilDiv(maxX, 16), Math.ceilDiv(maxZ, 16));
|
||||
region = new Bound(minX >> 9, minZ >> 9, Math.ceilDiv(maxX, 512), Math.ceilDiv(maxZ, 512));
|
||||
chunk = new Bound(
|
||||
PowerOfTwoCoordinates.blockToChunkFloor(minX),
|
||||
PowerOfTwoCoordinates.blockToChunkFloor(minZ),
|
||||
PowerOfTwoCoordinates.ceilDivPow2(maxX, PowerOfTwoCoordinates.CHUNK_BITS),
|
||||
PowerOfTwoCoordinates.ceilDivPow2(maxZ, PowerOfTwoCoordinates.CHUNK_BITS)
|
||||
);
|
||||
region = new Bound(
|
||||
PowerOfTwoCoordinates.blockToRegionFloor(minX),
|
||||
PowerOfTwoCoordinates.blockToRegionFloor(minZ),
|
||||
PowerOfTwoCoordinates.ceilDivPow2(maxX, PowerOfTwoCoordinates.REGION_BITS),
|
||||
PowerOfTwoCoordinates.ceilDivPow2(maxZ, PowerOfTwoCoordinates.REGION_BITS)
|
||||
);
|
||||
}
|
||||
|
||||
public Bound chunk() {
|
||||
@@ -199,28 +235,60 @@ public class PregenTask {
|
||||
private static final class RegionChunkCursor {
|
||||
private final int regionX;
|
||||
private final int regionZ;
|
||||
private final KList<Position2> chunks;
|
||||
private int index;
|
||||
private final Bound bound;
|
||||
private final int[] order;
|
||||
private final int chunkOffsetX;
|
||||
private final int chunkOffsetZ;
|
||||
private int scanIndex;
|
||||
private int emittedIndex;
|
||||
private int currentChunkX;
|
||||
private int currentChunkZ;
|
||||
private boolean hasCurrent;
|
||||
|
||||
private RegionChunkCursor(int regionX, int regionZ, KList<Position2> chunks) {
|
||||
private RegionChunkCursor(int regionX, int regionZ, Bound bound) {
|
||||
this.regionX = regionX;
|
||||
this.regionZ = regionZ;
|
||||
this.chunks = chunks;
|
||||
this.index = 0;
|
||||
this.bound = bound;
|
||||
this.chunkOffsetX = PowerOfTwoCoordinates.regionToChunk(regionX);
|
||||
this.chunkOffsetZ = PowerOfTwoCoordinates.regionToChunk(regionZ);
|
||||
this.order = orderForPull(-chunkOffsetX, -chunkOffsetZ);
|
||||
this.scanIndex = 0;
|
||||
this.emittedIndex = 0;
|
||||
advance();
|
||||
}
|
||||
|
||||
private boolean hasNext() {
|
||||
return index < chunks.size();
|
||||
return hasCurrent;
|
||||
}
|
||||
|
||||
private Position2 next() {
|
||||
private long next() {
|
||||
if (!hasNext()) {
|
||||
return null;
|
||||
return Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
Position2 value = chunks.get(index);
|
||||
index++;
|
||||
return value;
|
||||
long high = (long) currentChunkX << 32;
|
||||
long low = currentChunkZ & 0xFFFFFFFFL;
|
||||
emittedIndex++;
|
||||
advance();
|
||||
return high | low;
|
||||
}
|
||||
|
||||
private void advance() {
|
||||
hasCurrent = false;
|
||||
while (scanIndex < order.length) {
|
||||
int local = order[scanIndex];
|
||||
scanIndex++;
|
||||
int chunkX = chunkOffsetX + PowerOfTwoCoordinates.unpackLocal32X(local);
|
||||
int chunkZ = chunkOffsetZ + PowerOfTwoCoordinates.unpackLocal32Z(local);
|
||||
if (!bound.check(chunkX, chunkZ)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
currentChunkX = chunkX;
|
||||
currentChunkZ = chunkZ;
|
||||
hasCurrent = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private int getRegionX() {
|
||||
@@ -232,7 +300,7 @@ public class PregenTask {
|
||||
}
|
||||
|
||||
private int getIndex() {
|
||||
return index;
|
||||
return emittedIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+25
-18
@@ -5,6 +5,7 @@ import art.arcane.volmlib.util.data.Varint;
|
||||
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
||||
import art.arcane.volmlib.util.documentation.RegionCoordinates;
|
||||
import art.arcane.volmlib.util.io.IO;
|
||||
import art.arcane.volmlib.util.math.PowerOfTwoCoordinates;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap;
|
||||
import net.jpountz.lz4.LZ4BlockInputStream;
|
||||
import net.jpountz.lz4.LZ4BlockOutputStream;
|
||||
@@ -39,19 +40,22 @@ public class PregenCacheImpl implements PregenCache {
|
||||
@Override
|
||||
@ChunkCoordinates
|
||||
public boolean isChunkCached(int x, int z) {
|
||||
return getPlate(x >> 10, z >> 10).isCached(
|
||||
(x >> 5) & 31,
|
||||
(z >> 5) & 31,
|
||||
region -> region.isCached(x & 31, z & 31)
|
||||
return getPlate(PowerOfTwoCoordinates.floorDivPow2(x, 10), PowerOfTwoCoordinates.floorDivPow2(z, 10)).isCached(
|
||||
PowerOfTwoCoordinates.localMaskPow2(PowerOfTwoCoordinates.floorDivPow2(x, 5), PowerOfTwoCoordinates.REGION_CHUNK_BITS),
|
||||
PowerOfTwoCoordinates.localMaskPow2(PowerOfTwoCoordinates.floorDivPow2(z, 5), PowerOfTwoCoordinates.REGION_CHUNK_BITS),
|
||||
region -> region.isCached(
|
||||
PowerOfTwoCoordinates.localMaskPow2(x, PowerOfTwoCoordinates.REGION_CHUNK_BITS),
|
||||
PowerOfTwoCoordinates.localMaskPow2(z, PowerOfTwoCoordinates.REGION_CHUNK_BITS)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@RegionCoordinates
|
||||
public boolean isRegionCached(int x, int z) {
|
||||
return getPlate(x >> 5, z >> 5).isCached(
|
||||
x & 31,
|
||||
z & 31,
|
||||
return getPlate(PowerOfTwoCoordinates.floorDivPow2(x, 5), PowerOfTwoCoordinates.floorDivPow2(z, 5)).isCached(
|
||||
PowerOfTwoCoordinates.localMaskPow2(x, PowerOfTwoCoordinates.REGION_CHUNK_BITS),
|
||||
PowerOfTwoCoordinates.localMaskPow2(z, PowerOfTwoCoordinates.REGION_CHUNK_BITS),
|
||||
Region::isCached
|
||||
);
|
||||
}
|
||||
@@ -59,19 +63,22 @@ public class PregenCacheImpl implements PregenCache {
|
||||
@Override
|
||||
@ChunkCoordinates
|
||||
public void cacheChunk(int x, int z) {
|
||||
getPlate(x >> 10, z >> 10).cache(
|
||||
(x >> 5) & 31,
|
||||
(z >> 5) & 31,
|
||||
region -> region.cache(x & 31, z & 31)
|
||||
getPlate(PowerOfTwoCoordinates.floorDivPow2(x, 10), PowerOfTwoCoordinates.floorDivPow2(z, 10)).cache(
|
||||
PowerOfTwoCoordinates.localMaskPow2(PowerOfTwoCoordinates.floorDivPow2(x, 5), PowerOfTwoCoordinates.REGION_CHUNK_BITS),
|
||||
PowerOfTwoCoordinates.localMaskPow2(PowerOfTwoCoordinates.floorDivPow2(z, 5), PowerOfTwoCoordinates.REGION_CHUNK_BITS),
|
||||
region -> region.cache(
|
||||
PowerOfTwoCoordinates.localMaskPow2(x, PowerOfTwoCoordinates.REGION_CHUNK_BITS),
|
||||
PowerOfTwoCoordinates.localMaskPow2(z, PowerOfTwoCoordinates.REGION_CHUNK_BITS)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@RegionCoordinates
|
||||
public void cacheRegion(int x, int z) {
|
||||
getPlate(x >> 5, z >> 5).cache(
|
||||
x & 31,
|
||||
z & 31,
|
||||
getPlate(PowerOfTwoCoordinates.floorDivPow2(x, 5), PowerOfTwoCoordinates.floorDivPow2(z, 5)).cache(
|
||||
PowerOfTwoCoordinates.localMaskPow2(x, PowerOfTwoCoordinates.REGION_CHUNK_BITS),
|
||||
PowerOfTwoCoordinates.localMaskPow2(z, PowerOfTwoCoordinates.REGION_CHUNK_BITS),
|
||||
Region::cache
|
||||
);
|
||||
}
|
||||
@@ -215,7 +222,7 @@ public class PregenCacheImpl implements PregenCache {
|
||||
return false;
|
||||
}
|
||||
|
||||
int index = x * 32 + z;
|
||||
int index = PowerOfTwoCoordinates.packLocal32(x, z);
|
||||
Region region = regions[index];
|
||||
if (region == null) {
|
||||
region = new Region();
|
||||
@@ -240,7 +247,7 @@ public class PregenCacheImpl implements PregenCache {
|
||||
return true;
|
||||
}
|
||||
|
||||
Region region = regions[x * 32 + z];
|
||||
Region region = regions[PowerOfTwoCoordinates.packLocal32(x, z)];
|
||||
if (region == null) {
|
||||
return false;
|
||||
}
|
||||
@@ -294,7 +301,7 @@ public class PregenCacheImpl implements PregenCache {
|
||||
return false;
|
||||
}
|
||||
|
||||
int index = x * 32 + z;
|
||||
int index = PowerOfTwoCoordinates.packLocal32(x, z);
|
||||
int wordIndex = index >> 6;
|
||||
long bit = 1L << (index & 63);
|
||||
boolean current = (value[wordIndex] & bit) != 0L;
|
||||
@@ -317,7 +324,7 @@ public class PregenCacheImpl implements PregenCache {
|
||||
}
|
||||
|
||||
private boolean isCached(int x, int z) {
|
||||
int index = x * 32 + z;
|
||||
int index = PowerOfTwoCoordinates.packLocal32(x, z);
|
||||
if (count == SIZE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
+132
-7
@@ -35,14 +35,17 @@ import io.papermc.lib.PaperLib;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.World;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
@@ -59,6 +62,9 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
private final int effectiveWorkerThreads;
|
||||
private final int recommendedRuntimeConcurrencyCap;
|
||||
private final int configuredMaxConcurrency;
|
||||
private final Method directChunkAtAsyncUrgentMethod;
|
||||
private final Method directChunkAtAsyncMethod;
|
||||
private final String chunkAccessMode;
|
||||
private final Executor executor;
|
||||
private final Semaphore semaphore;
|
||||
private final int threads;
|
||||
@@ -90,10 +96,16 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
IrisSettings.IrisSettingsPregen pregen = IrisSettings.get().getPregen();
|
||||
this.runtimeSchedulerMode = IrisRuntimeSchedulerMode.resolve(pregen);
|
||||
this.foliaRuntime = runtimeSchedulerMode == IrisRuntimeSchedulerMode.FOLIA;
|
||||
ChunkAsyncMethodSelection chunkAsyncMethodSelection = resolveChunkAsyncMethodSelection(world);
|
||||
this.directChunkAtAsyncUrgentMethod = chunkAsyncMethodSelection.urgentMethod();
|
||||
this.directChunkAtAsyncMethod = chunkAsyncMethodSelection.standardMethod();
|
||||
this.chunkAccessMode = chunkAsyncMethodSelection.mode();
|
||||
int detectedWorkerPoolThreads = resolveWorkerPoolThreads();
|
||||
int detectedCpuThreads = Math.max(1, Runtime.getRuntime().availableProcessors());
|
||||
int configuredWorldGenThreads = Math.max(1, IrisSettings.get().getConcurrency().getWorldGenThreads());
|
||||
int workerThreadsForCap = Math.max(detectedCpuThreads, Math.max(configuredWorldGenThreads, Math.max(1, detectedWorkerPoolThreads)));
|
||||
int workerThreadsForCap = foliaRuntime
|
||||
? resolveFoliaConcurrencyWorkerThreads(detectedWorkerPoolThreads, detectedCpuThreads, configuredWorldGenThreads)
|
||||
: resolvePaperLikeConcurrencyWorkerThreads(detectedWorkerPoolThreads, detectedCpuThreads, configuredWorldGenThreads);
|
||||
if (foliaRuntime) {
|
||||
this.paperLikeBackendMode = IrisPaperLikeBackendMode.AUTO;
|
||||
this.backendMode = "folia-region";
|
||||
@@ -156,11 +168,14 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
|
||||
private void unloadAndSaveAllChunks() {
|
||||
if (foliaRuntime) {
|
||||
// Folia requires world/chunk mutations to be region-owned; periodic global unload/save is unsafe.
|
||||
lastUse.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastUse.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
J.sfut(() -> {
|
||||
if (world == null) {
|
||||
@@ -169,6 +184,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
}
|
||||
|
||||
long minTime = M.ms() - 10_000;
|
||||
AtomicBoolean unloaded = new AtomicBoolean(false);
|
||||
lastUse.entrySet().removeIf(i -> {
|
||||
final Chunk chunk = i.getKey();
|
||||
final Long lastUseTime = i.getValue();
|
||||
@@ -176,11 +192,14 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
return true;
|
||||
if (lastUseTime < minTime) {
|
||||
chunk.unload();
|
||||
unloaded.set(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
world.save();
|
||||
if (unloaded.get()) {
|
||||
world.save();
|
||||
}
|
||||
}).get();
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
@@ -294,6 +313,14 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
return recommendedCap;
|
||||
}
|
||||
|
||||
static int resolvePaperLikeConcurrencyWorkerThreads(int detectedWorkerPoolThreads, int detectedCpuThreads, int configuredWorldGenThreads) {
|
||||
if (detectedWorkerPoolThreads > 0) {
|
||||
return detectedWorkerPoolThreads;
|
||||
}
|
||||
|
||||
return Math.max(1, Math.max(detectedCpuThreads, configuredWorldGenThreads));
|
||||
}
|
||||
|
||||
static int computeFoliaRecommendedCap(int workerThreads) {
|
||||
int normalizedWorkers = Math.max(1, workerThreads);
|
||||
int recommendedCap = normalizedWorkers * 4;
|
||||
@@ -308,6 +335,10 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
return recommendedCap;
|
||||
}
|
||||
|
||||
static int resolveFoliaConcurrencyWorkerThreads(int detectedWorkerPoolThreads, int detectedCpuThreads, int configuredWorldGenThreads) {
|
||||
return Math.max(detectedCpuThreads, Math.max(configuredWorldGenThreads, Math.max(1, detectedWorkerPoolThreads)));
|
||||
}
|
||||
|
||||
static int applyRuntimeConcurrencyCap(int maxConcurrency, boolean foliaRuntime, int workerThreads) {
|
||||
int normalizedMaxConcurrency = Math.max(1, maxConcurrency);
|
||||
int recommendedCap = foliaRuntime
|
||||
@@ -407,6 +438,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
Iris.info("Async pregen init: world=" + world.getName()
|
||||
+ ", mode=" + runtimeSchedulerMode.name().toLowerCase(Locale.ROOT)
|
||||
+ ", backend=" + backendMode
|
||||
+ ", chunkAccess=" + chunkAccessMode
|
||||
+ ", threads=" + threads
|
||||
+ ", adaptiveLimit=" + adaptiveInFlightLimit.get()
|
||||
+ ", workerPoolThreads=" + workerPoolThreads
|
||||
@@ -487,6 +519,96 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
executor.generate(x, z, listener);
|
||||
}
|
||||
|
||||
private CompletableFuture<Chunk> requestChunkAsync(int x, int z) {
|
||||
Throwable failure = null;
|
||||
|
||||
if (directChunkAtAsyncUrgentMethod != null) {
|
||||
try {
|
||||
return invokeChunkFuture(directChunkAtAsyncUrgentMethod, x, z, true, urgent);
|
||||
} catch (Throwable e) {
|
||||
failure = e;
|
||||
}
|
||||
}
|
||||
|
||||
if (directChunkAtAsyncMethod != null) {
|
||||
try {
|
||||
return invokeChunkFuture(directChunkAtAsyncMethod, x, z, true, urgent);
|
||||
} catch (Throwable e) {
|
||||
if (failure == null) {
|
||||
failure = e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
CompletableFuture<Chunk> future = PaperLib.getChunkAtAsync(world, x, z, true, urgent);
|
||||
if (future != null) {
|
||||
return future;
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
if (failure == null) {
|
||||
failure = e;
|
||||
}
|
||||
}
|
||||
|
||||
if (failure == null) {
|
||||
failure = new IllegalStateException("Chunk async access returned no future.");
|
||||
}
|
||||
|
||||
return CompletableFuture.failedFuture(new IllegalStateException("Failed to request async chunk " + x + "," + z + " in world " + world.getName(), failure));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private CompletableFuture<Chunk> invokeChunkFuture(Method method, int x, int z, boolean generate, boolean urgentRequest) throws Throwable {
|
||||
Object result;
|
||||
try {
|
||||
if (method.getParameterCount() == 4) {
|
||||
result = method.invoke(world, x, z, generate, urgentRequest);
|
||||
} else {
|
||||
result = method.invoke(world, x, z, generate);
|
||||
}
|
||||
} catch (InvocationTargetException e) {
|
||||
throw e.getCause() == null ? e : e.getCause();
|
||||
}
|
||||
|
||||
if (result instanceof CompletableFuture<?>) {
|
||||
return (CompletableFuture<Chunk>) result;
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Chunk async method returned a non-future result.");
|
||||
}
|
||||
|
||||
private static ChunkAsyncMethodSelection resolveChunkAsyncMethodSelection(World world) {
|
||||
if (world == null) {
|
||||
return new ChunkAsyncMethodSelection(null, null, "paperlib");
|
||||
}
|
||||
|
||||
Class<?> worldClass = world.getClass();
|
||||
Method urgentMethod = resolveChunkAsyncMethod(worldClass, int.class, int.class, boolean.class, boolean.class);
|
||||
Method standardMethod = resolveChunkAsyncMethod(worldClass, int.class, int.class, boolean.class);
|
||||
if (urgentMethod != null) {
|
||||
return new ChunkAsyncMethodSelection(urgentMethod, standardMethod, "world#getChunkAtAsync(int,int,boolean,boolean)");
|
||||
}
|
||||
if (standardMethod != null) {
|
||||
return new ChunkAsyncMethodSelection(null, standardMethod, "world#getChunkAtAsync(int,int,boolean)");
|
||||
}
|
||||
return new ChunkAsyncMethodSelection(null, null, "paperlib");
|
||||
}
|
||||
|
||||
private static Method resolveChunkAsyncMethod(Class<?> worldClass, Class<?>... parameterTypes) {
|
||||
try {
|
||||
return worldClass.getMethod("getChunkAtAsync", parameterTypes);
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
}
|
||||
|
||||
try {
|
||||
return World.class.getMethod("getChunkAtAsync", parameterTypes);
|
||||
} catch (NoSuchMethodException ignored) {
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mantle getMantle() {
|
||||
if (IrisToolbelt.isIrisWorld(world)) {
|
||||
@@ -547,14 +669,14 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
@Override
|
||||
public void generate(int x, int z, PregenListener listener) {
|
||||
try {
|
||||
PaperLib.getChunkAtAsync(world, x, z, true, urgent)
|
||||
requestChunkAsync(x, z)
|
||||
.orTimeout(timeoutSeconds, TimeUnit.SECONDS)
|
||||
.whenComplete((chunk, throwable) -> completeFoliaChunk(x, z, listener, chunk, throwable));
|
||||
return;
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
|
||||
if (!J.runRegion(world, x, z, () -> PaperLib.getChunkAtAsync(world, x, z, true, urgent)
|
||||
if (!J.runRegion(world, x, z, () -> requestChunkAsync(x, z)
|
||||
.orTimeout(timeoutSeconds, TimeUnit.SECONDS)
|
||||
.whenComplete((chunk, throwable) -> completeFoliaChunk(x, z, listener, chunk, throwable)))) {
|
||||
markFinished(false);
|
||||
@@ -598,7 +720,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
service.submit(() -> {
|
||||
boolean success = false;
|
||||
try {
|
||||
Chunk i = PaperLib.getChunkAtAsync(world, x, z, true, urgent)
|
||||
Chunk i = requestChunkAsync(x, z)
|
||||
.orTimeout(timeoutSeconds, TimeUnit.SECONDS)
|
||||
.exceptionally(e -> onChunkFutureFailure(x, z, e))
|
||||
.get();
|
||||
@@ -632,7 +754,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
private class TicketExecutor implements Executor {
|
||||
@Override
|
||||
public void generate(int x, int z, PregenListener listener) {
|
||||
PaperLib.getChunkAtAsync(world, x, z, true, urgent)
|
||||
requestChunkAsync(x, z)
|
||||
.orTimeout(timeoutSeconds, TimeUnit.SECONDS)
|
||||
.exceptionally(e -> onChunkFutureFailure(x, z, e))
|
||||
.thenAccept(i -> {
|
||||
@@ -653,4 +775,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private record ChunkAsyncMethodSelection(Method urgentMethod, Method standardMethod, String mode) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,10 @@ import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.iris.util.common.plugin.IrisService;
|
||||
import art.arcane.iris.util.common.plugin.VolmitSender;
|
||||
import art.arcane.volmlib.util.scheduling.Looper;
|
||||
import art.arcane.iris.util.project.stream.utility.CachedDoubleStream2D;
|
||||
import art.arcane.iris.util.project.stream.utility.CachedStream2D;
|
||||
import art.arcane.iris.util.project.stream.utility.CachedStream3D;
|
||||
import art.arcane.iris.core.gui.PregeneratorJob;
|
||||
import lombok.Synchronized;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
@@ -98,7 +100,7 @@ public class IrisEngineSVC implements IrisService {
|
||||
double total = 0D;
|
||||
int count = 0;
|
||||
for (var cache : preservation.getCaches()) {
|
||||
if (!(cache instanceof CachedStream2D<?>)) {
|
||||
if (!(cache instanceof CachedStream2D<?>) && !(cache instanceof CachedDoubleStream2D)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -126,6 +128,7 @@ public class IrisEngineSVC implements IrisService {
|
||||
var type = switch (cache) {
|
||||
case ResourceLoader<?> ignored -> 0;
|
||||
case CachedStream2D<?> ignored -> 1;
|
||||
case CachedDoubleStream2D ignored -> 1;
|
||||
case CachedStream3D<?> ignored -> 2;
|
||||
default -> 3;
|
||||
};
|
||||
@@ -250,6 +253,10 @@ public class IrisEngineSVC implements IrisService {
|
||||
return false;
|
||||
}
|
||||
|
||||
static boolean shouldSkipMantleReductionForMaintenance(boolean maintenanceActive, boolean pregeneratorTargetsWorld) {
|
||||
return maintenanceActive && !pregeneratorTargetsWorld;
|
||||
}
|
||||
|
||||
private final class Registered {
|
||||
private final String name;
|
||||
private final PlatformChunkGenerator access;
|
||||
@@ -286,7 +293,7 @@ public class IrisEngineSVC implements IrisService {
|
||||
|| !shouldReduce(engine))
|
||||
return;
|
||||
World engineWorld = engine.getWorld().realWorld();
|
||||
if (engineWorld != null && IrisToolbelt.isWorldMaintenanceActive(engineWorld)) {
|
||||
if (shouldSkipForMaintenance(engineWorld)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -313,7 +320,7 @@ public class IrisEngineSVC implements IrisService {
|
||||
|| !shouldReduce(engine))
|
||||
return;
|
||||
World engineWorld = engine.getWorld().realWorld();
|
||||
if (engineWorld != null && IrisToolbelt.isWorldMaintenanceActive(engineWorld)) {
|
||||
if (shouldSkipForMaintenance(engineWorld)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -363,7 +370,32 @@ public class IrisEngineSVC implements IrisService {
|
||||
}
|
||||
|
||||
private boolean shouldReduce(Engine engine) {
|
||||
return !engine.isStudio() || IrisSettings.get().getPerformance().isTrimMantleInStudio();
|
||||
if (!engine.isStudio() || IrisSettings.get().getPerformance().isTrimMantleInStudio()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
World world = engine.getWorld().realWorld();
|
||||
if (world == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PregeneratorJob pregeneratorJob = PregeneratorJob.getInstance();
|
||||
return pregeneratorJob != null && pregeneratorJob.targetsWorld(world);
|
||||
}
|
||||
|
||||
private boolean shouldSkipForMaintenance(@Nullable World world) {
|
||||
if (world == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean maintenanceActive = IrisToolbelt.isWorldMaintenanceActive(world);
|
||||
if (!maintenanceActive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PregeneratorJob pregeneratorJob = PregeneratorJob.getInstance();
|
||||
boolean pregeneratorTargetsWorld = pregeneratorJob != null && pregeneratorJob.targetsWorld(world);
|
||||
return shouldSkipMantleReductionForMaintenance(maintenanceActive, pregeneratorTargetsWorld);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,11 +200,11 @@ public class IrisComplex implements DataProvider {
|
||||
heightStream = ProceduralStream.of((x, z) -> {
|
||||
IrisBiome b = focusBiome != null ? focusBiome : baseBiomeStream.get(x, z);
|
||||
return getHeight(engine, b, x, z, engine.getSeedManager().getHeight());
|
||||
}, Interpolated.DOUBLE).cache2D("heightStream", engine, cacheSize).waste("Height Stream");
|
||||
}, Interpolated.DOUBLE).cache2DDouble("heightStream", engine, cacheSize).waste("Height Stream");
|
||||
roundedHeighteightStream = heightStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getHeight().get(x, z))
|
||||
.round().waste("Rounded Height Stream");
|
||||
slopeStream = heightStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getHeight().get(x, z))
|
||||
.slope(3).cache2D("slopeStream", engine, cacheSize).waste("Slope Stream");
|
||||
.slope(3).cache2DDouble("slopeStream", engine, cacheSize).waste("Slope Stream");
|
||||
trueBiomeStream = focusBiome != null ? ProceduralStream.of((x, y) -> focusBiome, Interpolated.of(a -> 0D,
|
||||
b -> focusBiome))
|
||||
.cache2D("trueBiomeStream-focus", engine, cacheSize) : heightStream
|
||||
@@ -215,7 +215,7 @@ public class IrisComplex implements DataProvider {
|
||||
trueBiomeDerivativeStream = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z))
|
||||
.convert(IrisBiome::getDerivative).cache2D("trueBiomeDerivativeStream", engine, cacheSize).waste("True Biome Derivative Stream");
|
||||
heightFluidStream = heightStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getHeight().get(x, z))
|
||||
.max(fluidHeight).cache2D("heightFluidStream", engine, cacheSize).waste("Height Fluid Stream");
|
||||
.max(fluidHeight).cache2DDouble("heightFluidStream", engine, cacheSize).waste("Height Fluid Stream");
|
||||
maxHeightStream = ProceduralStream.ofDouble((x, z) -> height).waste("Max Height Stream");
|
||||
terrainSurfaceDecoration = trueBiomeStream.contextInjecting((c, x, z) -> IrisContext.getOr(engine).getChunkContext().getBiome().get(x, z))
|
||||
.convertAware2D((b, xx, zz) -> decorateFor(b, xx, zz, IrisDecorationPart.NONE)).cache2D("terrainSurfaceDecoration", engine, cacheSize).waste("Surface Decoration Stream");
|
||||
|
||||
@@ -37,6 +37,7 @@ import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.mantle.flag.MantleFlag;
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
import art.arcane.volmlib.util.math.PowerOfTwoCoordinates;
|
||||
import art.arcane.volmlib.util.math.Position2;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
@@ -219,8 +220,8 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
}
|
||||
|
||||
J.runEntity(player, () -> {
|
||||
int centerX = player.getLocation().getBlockX() >> 4;
|
||||
int centerZ = player.getLocation().getBlockZ() >> 4;
|
||||
int centerX = PowerOfTwoCoordinates.blockToChunkFloor(player.getLocation().getBlockX());
|
||||
int centerZ = PowerOfTwoCoordinates.blockToChunkFloor(player.getLocation().getBlockZ());
|
||||
int radius = 1;
|
||||
for (int x = -radius; x <= radius; x++) {
|
||||
for (int z = -radius; z <= radius; z++) {
|
||||
@@ -278,8 +279,8 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
}
|
||||
|
||||
J.runEntity(player, () -> {
|
||||
int centerX = player.getLocation().getBlockX() >> 4;
|
||||
int centerZ = player.getLocation().getBlockZ() >> 4;
|
||||
int centerX = PowerOfTwoCoordinates.blockToChunkFloor(player.getLocation().getBlockX());
|
||||
int centerZ = PowerOfTwoCoordinates.blockToChunkFloor(player.getLocation().getBlockZ());
|
||||
int radius = 1;
|
||||
|
||||
for (int x = -radius; x <= radius; x++) {
|
||||
@@ -632,12 +633,12 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
.filter((i) -> i.isValid(biome)),
|
||||
Stream.concat(getData()
|
||||
.getSpawnerLoader()
|
||||
.loadAll(getEngine().getRegion(c.getX() << 4, c.getZ() << 4).getEntitySpawners())
|
||||
.loadAll(getEngine().getRegion(PowerOfTwoCoordinates.chunkToBlock(c.getX()), PowerOfTwoCoordinates.chunkToBlock(c.getZ())).getEntitySpawners())
|
||||
.shuffleCopy(RNG.r)
|
||||
.stream()
|
||||
.filter(filter),
|
||||
getData().getSpawnerLoader()
|
||||
.loadAll(getEngine().getSurfaceBiome(c.getX() << 4, c.getZ() << 4).getEntitySpawners())
|
||||
.loadAll(getEngine().getSurfaceBiome(PowerOfTwoCoordinates.chunkToBlock(c.getX()), PowerOfTwoCoordinates.chunkToBlock(c.getZ())).getEntitySpawners())
|
||||
.shuffleCopy(RNG.r)
|
||||
.stream()
|
||||
.filter(filter)))
|
||||
@@ -668,13 +669,13 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
|
||||
private void spawn(IrisPosition pos, IrisEntitySpawn i) {
|
||||
IrisSpawner ref = i.getReferenceSpawner();
|
||||
if (!ref.canSpawn(getEngine(), pos.getX() >> 4, pos.getZ() >> 4))
|
||||
if (!ref.canSpawn(getEngine(), PowerOfTwoCoordinates.blockToChunkFloor(pos.getX()), PowerOfTwoCoordinates.blockToChunkFloor(pos.getZ())))
|
||||
return;
|
||||
|
||||
int s = i.spawn(getEngine(), pos, RNG.r);
|
||||
actuallySpawned += s;
|
||||
if (s > 0) {
|
||||
ref.spawn(getEngine(), pos.getX() >> 4, pos.getZ() >> 4);
|
||||
ref.spawn(getEngine(), PowerOfTwoCoordinates.blockToChunkFloor(pos.getX()), PowerOfTwoCoordinates.blockToChunkFloor(pos.getZ()));
|
||||
energy -= s * ((i.getEnergyMultiplier() * ref.getEnergyMultiplier() * 1));
|
||||
}
|
||||
}
|
||||
|
||||
+279
-132
@@ -26,6 +26,7 @@ import art.arcane.iris.engine.object.IrisCaveProfile;
|
||||
import art.arcane.iris.engine.object.IrisRange;
|
||||
import art.arcane.iris.util.project.noise.CNG;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.math.PowerOfTwoCoordinates;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
@@ -55,8 +56,11 @@ public class IrisCaveCarver3D {
|
||||
private final MatterCavern carveAir;
|
||||
private final MatterCavern carveLava;
|
||||
private final MatterCavern carveForcedAir;
|
||||
private final double normalizationFactor;
|
||||
private final double baseWeight;
|
||||
private final double detailWeight;
|
||||
private final double detailMinContribution;
|
||||
private final double detailMaxContribution;
|
||||
private final double warpStrength;
|
||||
private final boolean hasWarp;
|
||||
private final boolean hasModules;
|
||||
@@ -93,8 +97,11 @@ public class IrisCaveCarver3D {
|
||||
|
||||
this.modules = moduleStates.toArray(new ModuleState[0]);
|
||||
double normalization = weight <= 0 ? 1 : weight;
|
||||
normalizationFactor = normalization;
|
||||
inverseNormalization = 1D / normalization;
|
||||
hasModules = modules.length > 0;
|
||||
detailMinContribution = -detailWeight;
|
||||
detailMaxContribution = detailWeight;
|
||||
}
|
||||
|
||||
public int carve(MantleWriter writer, int chunkX, int chunkZ) {
|
||||
@@ -178,8 +185,8 @@ public class IrisCaveCarver3D {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int x0 = chunkX << 4;
|
||||
int z0 = chunkZ << 4;
|
||||
int x0 = PowerOfTwoCoordinates.chunkToBlock(chunkX);
|
||||
int z0 = PowerOfTwoCoordinates.chunkToBlock(chunkZ);
|
||||
int[] columnMaxY = scratch.columnMaxY;
|
||||
int[] surfaceBreakFloorY = scratch.surfaceBreakFloorY;
|
||||
boolean[] surfaceBreakColumn = scratch.surfaceBreakColumn;
|
||||
@@ -193,7 +200,7 @@ public class IrisCaveCarver3D {
|
||||
int x = x0 + lx;
|
||||
for (int lz = 0; lz < 16; lz++) {
|
||||
int z = z0 + lz;
|
||||
int index = (lx << 4) | lz;
|
||||
int index = PowerOfTwoCoordinates.packLocal16(lx, lz);
|
||||
int columnSurfaceY;
|
||||
if (precomputedSurfaceHeights != null && precomputedSurfaceHeights.length > index) {
|
||||
columnSurfaceY = precomputedSurfaceHeights[index];
|
||||
@@ -202,7 +209,7 @@ public class IrisCaveCarver3D {
|
||||
}
|
||||
int clearanceTopY = Math.min(maxY, Math.max(minY, columnSurfaceY - surfaceClearance));
|
||||
boolean breakColumn = allowSurfaceBreak
|
||||
&& signed(surfaceBreakDensity.noiseFast2D(x, z)) >= surfaceBreakNoiseThreshold;
|
||||
&& surfaceBreakDensity.noiseFastSigned2D(x, z) >= surfaceBreakNoiseThreshold;
|
||||
int columnTopY = breakColumn
|
||||
? Math.min(maxY, Math.max(minY, columnSurfaceY))
|
||||
: clearanceTopY;
|
||||
@@ -399,13 +406,14 @@ public class IrisCaveCarver3D {
|
||||
}
|
||||
|
||||
int[] planeColumnIndices = scratch.planeColumnIndices;
|
||||
double[] planeDensity = scratch.planeDensity;
|
||||
int minSection = minY >> 4;
|
||||
int maxSection = maxY >> 4;
|
||||
double[] planeThresholdLimit = scratch.planeThresholdLimit;
|
||||
boolean[] planeCarve = scratch.planeCarve;
|
||||
int minSection = PowerOfTwoCoordinates.floorDivPow2(minY, 4);
|
||||
int maxSection = PowerOfTwoCoordinates.floorDivPow2(maxY, 4);
|
||||
|
||||
for (int sectionIndex = minSection; sectionIndex <= maxSection; sectionIndex++) {
|
||||
int sectionMinY = Math.max(minY, sectionIndex << 4);
|
||||
int sectionMaxY = Math.min(maxY, (sectionIndex << 4) + 15);
|
||||
int sectionMinY = Math.max(minY, PowerOfTwoCoordinates.chunkToBlock(sectionIndex));
|
||||
int sectionMaxY = Math.min(maxY, PowerOfTwoCoordinates.chunkToBlock(sectionIndex) + 15);
|
||||
MatterSlice<MatterCavern> cavernSlice = resolveCavernSlice(scratch, chunk, sectionIndex);
|
||||
|
||||
for (int y = sectionMinY; y <= sectionMaxY; y++) {
|
||||
@@ -415,7 +423,14 @@ public class IrisCaveCarver3D {
|
||||
continue;
|
||||
}
|
||||
|
||||
planeColumnIndices[planeCount] = activeColumnIndices[activeIndex];
|
||||
int columnIndex = activeColumnIndices[activeIndex];
|
||||
planeColumnIndices[planeCount] = columnIndex;
|
||||
double localThreshold = passThreshold[columnIndex];
|
||||
if (surfaceBreakColumn[columnIndex] && y >= surfaceBreakFloorY[columnIndex]) {
|
||||
localThreshold += surfaceBreakThresholdBoost;
|
||||
}
|
||||
localThreshold -= verticalEdgeFade[y - minY];
|
||||
planeThresholdLimit[planeCount] = localThreshold * normalizationFactor;
|
||||
planeCount++;
|
||||
}
|
||||
|
||||
@@ -423,24 +438,19 @@ public class IrisCaveCarver3D {
|
||||
continue;
|
||||
}
|
||||
|
||||
fillDensityPlane(x0, z0, y, planeColumnIndices, planeCount, planeDensity);
|
||||
classifyDensityPlane(x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve);
|
||||
int fadeIndex = y - minY;
|
||||
int localY = y & 15;
|
||||
MatterCavern matter = matterByY[fadeIndex];
|
||||
|
||||
if (skipExistingCarved) {
|
||||
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) {
|
||||
int columnIndex = planeColumnIndices[planeIndex];
|
||||
double localThreshold = passThreshold[columnIndex];
|
||||
if (surfaceBreakColumn[columnIndex] && y >= surfaceBreakFloorY[columnIndex]) {
|
||||
localThreshold += surfaceBreakThresholdBoost;
|
||||
}
|
||||
localThreshold -= verticalEdgeFade[fadeIndex];
|
||||
if (planeDensity[planeIndex] > localThreshold) {
|
||||
if (!planeCarve[planeIndex]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int localX = columnIndex >> 4;
|
||||
int columnIndex = planeColumnIndices[planeIndex];
|
||||
int localX = PowerOfTwoCoordinates.unpackLocal16X(columnIndex);
|
||||
int localZ = columnIndex & 15;
|
||||
if (cavernSlice.get(localX, localY, localZ) != null) {
|
||||
continue;
|
||||
@@ -453,17 +463,12 @@ public class IrisCaveCarver3D {
|
||||
}
|
||||
|
||||
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) {
|
||||
int columnIndex = planeColumnIndices[planeIndex];
|
||||
double localThreshold = passThreshold[columnIndex];
|
||||
if (surfaceBreakColumn[columnIndex] && y >= surfaceBreakFloorY[columnIndex]) {
|
||||
localThreshold += surfaceBreakThresholdBoost;
|
||||
}
|
||||
localThreshold -= verticalEdgeFade[fadeIndex];
|
||||
if (planeDensity[planeIndex] > localThreshold) {
|
||||
if (!planeCarve[planeIndex]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int localX = columnIndex >> 4;
|
||||
int columnIndex = planeColumnIndices[planeIndex];
|
||||
int localX = PowerOfTwoCoordinates.unpackLocal16X(columnIndex);
|
||||
int localZ = columnIndex & 15;
|
||||
cavernSlice.set(localX, localY, localZ, matter);
|
||||
carved++;
|
||||
@@ -519,7 +524,7 @@ public class IrisCaveCarver3D {
|
||||
int lz1 = lz + 1;
|
||||
int activeColumns = 0;
|
||||
|
||||
int index00 = (lx << 4) | lz;
|
||||
int index00 = PowerOfTwoCoordinates.packLocal16(lx, lz);
|
||||
if (!Double.isNaN(passThreshold[index00])) {
|
||||
tileIndices[activeColumns] = index00;
|
||||
tileLocalX[activeColumns] = lx;
|
||||
@@ -528,7 +533,7 @@ public class IrisCaveCarver3D {
|
||||
activeColumns++;
|
||||
}
|
||||
|
||||
int index01 = (lx << 4) | lz1;
|
||||
int index01 = PowerOfTwoCoordinates.packLocal16(lx, lz1);
|
||||
if (!Double.isNaN(passThreshold[index01])) {
|
||||
tileIndices[activeColumns] = index01;
|
||||
tileLocalX[activeColumns] = lx;
|
||||
@@ -537,7 +542,7 @@ public class IrisCaveCarver3D {
|
||||
activeColumns++;
|
||||
}
|
||||
|
||||
int index10 = (lx1 << 4) | lz;
|
||||
int index10 = PowerOfTwoCoordinates.packLocal16(lx1, lz);
|
||||
if (!Double.isNaN(passThreshold[index10])) {
|
||||
tileIndices[activeColumns] = index10;
|
||||
tileLocalX[activeColumns] = lx1;
|
||||
@@ -546,7 +551,7 @@ public class IrisCaveCarver3D {
|
||||
activeColumns++;
|
||||
}
|
||||
|
||||
int index11 = (lx1 << 4) | lz1;
|
||||
int index11 = PowerOfTwoCoordinates.packLocal16(lx1, lz1);
|
||||
if (!Double.isNaN(passThreshold[index11])) {
|
||||
tileIndices[activeColumns] = index11;
|
||||
tileLocalX[activeColumns] = lx1;
|
||||
@@ -574,7 +579,7 @@ public class IrisCaveCarver3D {
|
||||
int stampMaxY = Math.min(maxY, y + 1);
|
||||
for (int yy = y; yy <= stampMaxY; yy++) {
|
||||
MatterCavern matter = matterByY[yy - minY];
|
||||
MatterSlice<MatterCavern> cavernSlice = resolveCavernSlice(scratch, chunk, yy >> 4);
|
||||
MatterSlice<MatterCavern> cavernSlice = resolveCavernSlice(scratch, chunk, PowerOfTwoCoordinates.floorDivPow2(yy, 4));
|
||||
int localY = yy & 15;
|
||||
int fadeIndex = yy - minY;
|
||||
for (int columnIndex = 0; columnIndex < activeColumns; columnIndex++) {
|
||||
@@ -640,7 +645,7 @@ public class IrisCaveCarver3D {
|
||||
int x = x0 + lx;
|
||||
for (int lz = 0; lz < 16; lz++) {
|
||||
int z = z0 + lz;
|
||||
int index = (lx << 4) | lz;
|
||||
int index = PowerOfTwoCoordinates.packLocal16(lx, lz);
|
||||
double columnWeight = clampedWeights[index];
|
||||
if (columnWeight <= minWeight) {
|
||||
continue;
|
||||
@@ -669,7 +674,7 @@ public class IrisCaveCarver3D {
|
||||
int carveMaxY = Math.min(columnTopY, y + sampleStep - 1);
|
||||
for (int yy = y; yy <= carveMaxY; yy++) {
|
||||
MatterCavern matter = matterByY[yy - minY];
|
||||
MatterSlice<MatterCavern> cavernSlice = resolveCavernSlice(scratch, chunk, yy >> 4);
|
||||
MatterSlice<MatterCavern> cavernSlice = resolveCavernSlice(scratch, chunk, PowerOfTwoCoordinates.floorDivPow2(yy, 4));
|
||||
int localY = yy & 15;
|
||||
if (skipExistingCarved) {
|
||||
if (cavernSlice.get(lx, localY, lz) == null) {
|
||||
@@ -719,153 +724,234 @@ public class IrisCaveCarver3D {
|
||||
return sampleDensityWarpModules(x, y, z);
|
||||
}
|
||||
|
||||
private void fillDensityPlane(int x0, int z0, int y, int[] planeColumnIndices, int planeCount, double[] planeDensity) {
|
||||
private void classifyDensityPlane(int x0, int z0, int y, int[] planeColumnIndices, double[] planeThresholdLimit, int planeCount, boolean[] planeCarve) {
|
||||
if (!hasWarp) {
|
||||
if (!hasModules) {
|
||||
fillDensityPlaneNoWarpNoModules(x0, z0, y, planeColumnIndices, planeCount, planeDensity);
|
||||
classifyDensityPlaneNoWarpNoModules(x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve);
|
||||
return;
|
||||
}
|
||||
|
||||
fillDensityPlaneNoWarpModules(x0, z0, y, planeColumnIndices, planeCount, planeDensity);
|
||||
classifyDensityPlaneNoWarpModules(x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hasModules) {
|
||||
fillDensityPlaneWarpOnly(x0, z0, y, planeColumnIndices, planeCount, planeDensity);
|
||||
classifyDensityPlaneWarpOnly(x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve);
|
||||
return;
|
||||
}
|
||||
|
||||
fillDensityPlaneWarpModules(x0, z0, y, planeColumnIndices, planeCount, planeDensity);
|
||||
classifyDensityPlaneWarpModules(x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve);
|
||||
}
|
||||
|
||||
private void fillDensityPlaneNoWarpNoModules(int x0, int z0, int y, int[] planeColumnIndices, int planeCount, double[] planeDensity) {
|
||||
private void classifyDensityPlaneNoWarpNoModules(int x0, int z0, int y, int[] planeColumnIndices, double[] planeThresholdLimit, int planeCount, boolean[] planeCarve) {
|
||||
CNG localBaseDensity = baseDensity;
|
||||
CNG localDetailDensity = detailDensity;
|
||||
double localBaseWeight = baseWeight;
|
||||
double localDetailWeight = detailWeight;
|
||||
double normalization = inverseNormalization;
|
||||
double detailMin = detailMinContribution;
|
||||
double detailMax = detailMaxContribution;
|
||||
|
||||
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) {
|
||||
int columnIndex = planeColumnIndices[planeIndex];
|
||||
int x = x0 + (columnIndex >> 4);
|
||||
int x = x0 + PowerOfTwoCoordinates.unpackLocal16X(columnIndex);
|
||||
int z = z0 + (columnIndex & 15);
|
||||
double density = signed(localBaseDensity.noiseFast3D(x, y, z)) * localBaseWeight;
|
||||
density += signed(localDetailDensity.noiseFast3D(x, y, z)) * localDetailWeight;
|
||||
planeDensity[planeIndex] = density * normalization;
|
||||
}
|
||||
}
|
||||
|
||||
private void fillDensityPlaneNoWarpModules(int x0, int z0, int y, int[] planeColumnIndices, int planeCount, double[] planeDensity) {
|
||||
CNG localBaseDensity = baseDensity;
|
||||
CNG localDetailDensity = detailDensity;
|
||||
ModuleState[] localModules = modules;
|
||||
double localBaseWeight = baseWeight;
|
||||
double localDetailWeight = detailWeight;
|
||||
double normalization = inverseNormalization;
|
||||
|
||||
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) {
|
||||
int columnIndex = planeColumnIndices[planeIndex];
|
||||
int x = x0 + (columnIndex >> 4);
|
||||
int z = z0 + (columnIndex & 15);
|
||||
double density = signed(localBaseDensity.noiseFast3D(x, y, z)) * localBaseWeight;
|
||||
density += signed(localDetailDensity.noiseFast3D(x, y, z)) * localDetailWeight;
|
||||
for (int moduleIndex = 0; moduleIndex < localModules.length; moduleIndex++) {
|
||||
ModuleState module = localModules[moduleIndex];
|
||||
if (y < module.minY || y > module.maxY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double moduleDensity = signed(module.density.noiseFast3D(x, y, z)) - module.threshold;
|
||||
if (module.invert) {
|
||||
moduleDensity = -moduleDensity;
|
||||
}
|
||||
|
||||
density += moduleDensity * module.weight;
|
||||
double thresholdLimit = planeThresholdLimit[planeIndex];
|
||||
double density = localBaseDensity.noiseFastSigned3D(x, y, z) * localBaseWeight;
|
||||
if ((density + detailMin) > thresholdLimit) {
|
||||
planeCarve[planeIndex] = false;
|
||||
continue;
|
||||
}
|
||||
if ((density + detailMax) <= thresholdLimit) {
|
||||
planeCarve[planeIndex] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
planeDensity[planeIndex] = density * normalization;
|
||||
density += localDetailDensity.noiseFastSigned3D(x, y, z) * localDetailWeight;
|
||||
planeCarve[planeIndex] = density <= thresholdLimit;
|
||||
}
|
||||
}
|
||||
|
||||
private void fillDensityPlaneWarpOnly(int x0, int z0, int y, int[] planeColumnIndices, int planeCount, double[] planeDensity) {
|
||||
private void classifyDensityPlaneNoWarpModules(int x0, int z0, int y, int[] planeColumnIndices, double[] planeThresholdLimit, int planeCount, boolean[] planeCarve) {
|
||||
CNG localBaseDensity = baseDensity;
|
||||
CNG localDetailDensity = detailDensity;
|
||||
Scratch scratch = SCRATCH.get();
|
||||
int activeModuleCount = prepareActiveModules(scratch, y);
|
||||
if (activeModuleCount == 0) {
|
||||
classifyDensityPlaneNoWarpNoModules(x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve);
|
||||
return;
|
||||
}
|
||||
|
||||
ModuleState[] localModules = scratch.activeModules;
|
||||
double[] remainingMin = scratch.activeModuleRemainingMin;
|
||||
double[] remainingMax = scratch.activeModuleRemainingMax;
|
||||
double localBaseWeight = baseWeight;
|
||||
double localDetailWeight = detailWeight;
|
||||
double detailMin = detailMinContribution;
|
||||
double detailMax = detailMaxContribution;
|
||||
|
||||
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) {
|
||||
int columnIndex = planeColumnIndices[planeIndex];
|
||||
int x = x0 + PowerOfTwoCoordinates.unpackLocal16X(columnIndex);
|
||||
int z = z0 + (columnIndex & 15);
|
||||
double thresholdLimit = planeThresholdLimit[planeIndex];
|
||||
double density = localBaseDensity.noiseFastSigned3D(x, y, z) * localBaseWeight;
|
||||
if ((density + detailMin + remainingMin[0]) > thresholdLimit) {
|
||||
planeCarve[planeIndex] = false;
|
||||
continue;
|
||||
}
|
||||
if ((density + detailMax + remainingMax[0]) <= thresholdLimit) {
|
||||
planeCarve[planeIndex] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
density += localDetailDensity.noiseFastSigned3D(x, y, z) * localDetailWeight;
|
||||
if ((density + remainingMin[0]) > thresholdLimit) {
|
||||
planeCarve[planeIndex] = false;
|
||||
continue;
|
||||
}
|
||||
if ((density + remainingMax[0]) <= thresholdLimit) {
|
||||
planeCarve[planeIndex] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int moduleIndex = 0; moduleIndex < activeModuleCount; moduleIndex++) {
|
||||
ModuleState module = localModules[moduleIndex];
|
||||
density += module.sample(x, y, z);
|
||||
if ((density + remainingMin[moduleIndex + 1]) > thresholdLimit) {
|
||||
density = Double.POSITIVE_INFINITY;
|
||||
break;
|
||||
}
|
||||
if ((density + remainingMax[moduleIndex + 1]) <= thresholdLimit) {
|
||||
density = Double.NEGATIVE_INFINITY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
planeCarve[planeIndex] = density <= thresholdLimit;
|
||||
}
|
||||
}
|
||||
|
||||
private void classifyDensityPlaneWarpOnly(int x0, int z0, int y, int[] planeColumnIndices, double[] planeThresholdLimit, int planeCount, boolean[] planeCarve) {
|
||||
CNG localBaseDensity = baseDensity;
|
||||
CNG localDetailDensity = detailDensity;
|
||||
CNG localWarpDensity = warpDensity;
|
||||
double localBaseWeight = baseWeight;
|
||||
double localDetailWeight = detailWeight;
|
||||
double localWarpStrength = warpStrength;
|
||||
double normalization = inverseNormalization;
|
||||
double detailMin = detailMinContribution;
|
||||
double detailMax = detailMaxContribution;
|
||||
|
||||
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) {
|
||||
int columnIndex = planeColumnIndices[planeIndex];
|
||||
double x = x0 + (columnIndex >> 4);
|
||||
double x = x0 + PowerOfTwoCoordinates.unpackLocal16X(columnIndex);
|
||||
double z = z0 + (columnIndex & 15);
|
||||
double warpA = signed(localWarpDensity.noiseFast3D(x, y, z));
|
||||
double warpB = signed(localWarpDensity.noiseFast3D(x + 31.37D, y - 17.21D, z + 23.91D));
|
||||
double thresholdLimit = planeThresholdLimit[planeIndex];
|
||||
double warpA = localWarpDensity.noiseFastSigned3D(x, y, z);
|
||||
double warpB = localWarpDensity.noiseFastSigned3D(x + 31.37D, y - 17.21D, z + 23.91D);
|
||||
double warpedX = x + (warpA * localWarpStrength);
|
||||
double warpedY = y + (warpB * localWarpStrength);
|
||||
double warpedZ = z + ((warpA - warpB) * 0.5D * localWarpStrength);
|
||||
double density = signed(localBaseDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * localBaseWeight;
|
||||
density += signed(localDetailDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * localDetailWeight;
|
||||
planeDensity[planeIndex] = density * normalization;
|
||||
double density = localBaseDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * localBaseWeight;
|
||||
if ((density + detailMin) > thresholdLimit) {
|
||||
planeCarve[planeIndex] = false;
|
||||
continue;
|
||||
}
|
||||
if ((density + detailMax) <= thresholdLimit) {
|
||||
planeCarve[planeIndex] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
density += localDetailDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * localDetailWeight;
|
||||
planeCarve[planeIndex] = density <= thresholdLimit;
|
||||
}
|
||||
}
|
||||
|
||||
private void fillDensityPlaneWarpModules(int x0, int z0, int y, int[] planeColumnIndices, int planeCount, double[] planeDensity) {
|
||||
private void classifyDensityPlaneWarpModules(int x0, int z0, int y, int[] planeColumnIndices, double[] planeThresholdLimit, int planeCount, boolean[] planeCarve) {
|
||||
CNG localBaseDensity = baseDensity;
|
||||
CNG localDetailDensity = detailDensity;
|
||||
CNG localWarpDensity = warpDensity;
|
||||
ModuleState[] localModules = modules;
|
||||
Scratch scratch = SCRATCH.get();
|
||||
int activeModuleCount = prepareActiveModules(scratch, y);
|
||||
if (activeModuleCount == 0) {
|
||||
classifyDensityPlaneWarpOnly(x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve);
|
||||
return;
|
||||
}
|
||||
|
||||
ModuleState[] localModules = scratch.activeModules;
|
||||
double[] remainingMin = scratch.activeModuleRemainingMin;
|
||||
double[] remainingMax = scratch.activeModuleRemainingMax;
|
||||
double localBaseWeight = baseWeight;
|
||||
double localDetailWeight = detailWeight;
|
||||
double localWarpStrength = warpStrength;
|
||||
double normalization = inverseNormalization;
|
||||
double detailMin = detailMinContribution;
|
||||
double detailMax = detailMaxContribution;
|
||||
|
||||
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) {
|
||||
int columnIndex = planeColumnIndices[planeIndex];
|
||||
double x = x0 + (columnIndex >> 4);
|
||||
double x = x0 + PowerOfTwoCoordinates.unpackLocal16X(columnIndex);
|
||||
double z = z0 + (columnIndex & 15);
|
||||
double warpA = signed(localWarpDensity.noiseFast3D(x, y, z));
|
||||
double warpB = signed(localWarpDensity.noiseFast3D(x + 31.37D, y - 17.21D, z + 23.91D));
|
||||
double thresholdLimit = planeThresholdLimit[planeIndex];
|
||||
double warpA = localWarpDensity.noiseFastSigned3D(x, y, z);
|
||||
double warpB = localWarpDensity.noiseFastSigned3D(x + 31.37D, y - 17.21D, z + 23.91D);
|
||||
double warpedX = x + (warpA * localWarpStrength);
|
||||
double warpedY = y + (warpB * localWarpStrength);
|
||||
double warpedZ = z + ((warpA - warpB) * 0.5D * localWarpStrength);
|
||||
double density = signed(localBaseDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * localBaseWeight;
|
||||
density += signed(localDetailDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * localDetailWeight;
|
||||
for (int moduleIndex = 0; moduleIndex < localModules.length; moduleIndex++) {
|
||||
ModuleState module = localModules[moduleIndex];
|
||||
if (y < module.minY || y > module.maxY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double moduleDensity = signed(module.density.noiseFast3D(warpedX, warpedY, warpedZ)) - module.threshold;
|
||||
if (module.invert) {
|
||||
moduleDensity = -moduleDensity;
|
||||
}
|
||||
|
||||
density += moduleDensity * module.weight;
|
||||
double density = localBaseDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * localBaseWeight;
|
||||
if ((density + detailMin + remainingMin[0]) > thresholdLimit) {
|
||||
planeCarve[planeIndex] = false;
|
||||
continue;
|
||||
}
|
||||
if ((density + detailMax + remainingMax[0]) <= thresholdLimit) {
|
||||
planeCarve[planeIndex] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
planeDensity[planeIndex] = density * normalization;
|
||||
density += localDetailDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * localDetailWeight;
|
||||
if ((density + remainingMin[0]) > thresholdLimit) {
|
||||
planeCarve[planeIndex] = false;
|
||||
continue;
|
||||
}
|
||||
if ((density + remainingMax[0]) <= thresholdLimit) {
|
||||
planeCarve[planeIndex] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (int moduleIndex = 0; moduleIndex < activeModuleCount; moduleIndex++) {
|
||||
ModuleState module = localModules[moduleIndex];
|
||||
density += module.sample(warpedX, warpedY, warpedZ);
|
||||
if ((density + remainingMin[moduleIndex + 1]) > thresholdLimit) {
|
||||
density = Double.POSITIVE_INFINITY;
|
||||
break;
|
||||
}
|
||||
if ((density + remainingMax[moduleIndex + 1]) <= thresholdLimit) {
|
||||
density = Double.NEGATIVE_INFINITY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
planeCarve[planeIndex] = density <= thresholdLimit;
|
||||
}
|
||||
}
|
||||
|
||||
private double sampleDensityNoWarpNoModules(int x, int y, int z) {
|
||||
double density = signed(baseDensity.noiseFast3D(x, y, z)) * baseWeight;
|
||||
density += signed(detailDensity.noiseFast3D(x, y, z)) * detailWeight;
|
||||
double density = baseDensity.noiseFastSigned3D(x, y, z) * baseWeight;
|
||||
density += detailDensity.noiseFastSigned3D(x, y, z) * detailWeight;
|
||||
return density * inverseNormalization;
|
||||
}
|
||||
|
||||
private double sampleDensityNoWarpModules(int x, int y, int z) {
|
||||
double density = signed(baseDensity.noiseFast3D(x, y, z)) * baseWeight;
|
||||
density += signed(detailDensity.noiseFast3D(x, y, z)) * detailWeight;
|
||||
for (int moduleIndex = 0; moduleIndex < modules.length; moduleIndex++) {
|
||||
ModuleState module = modules[moduleIndex];
|
||||
if (y < module.minY || y > module.maxY) {
|
||||
continue;
|
||||
}
|
||||
Scratch scratch = SCRATCH.get();
|
||||
int activeModuleCount = prepareActiveModules(scratch, y);
|
||||
if (activeModuleCount == 0) {
|
||||
return sampleDensityNoWarpNoModules(x, y, z);
|
||||
}
|
||||
|
||||
double moduleDensity = signed(module.density.noiseFast3D(x, y, z)) - module.threshold;
|
||||
ModuleState[] localModules = scratch.activeModules;
|
||||
double density = baseDensity.noiseFastSigned3D(x, y, z) * baseWeight;
|
||||
density += detailDensity.noiseFastSigned3D(x, y, z) * detailWeight;
|
||||
for (int moduleIndex = 0; moduleIndex < activeModuleCount; moduleIndex++) {
|
||||
ModuleState module = localModules[moduleIndex];
|
||||
double moduleDensity = module.density.noiseFastSigned3D(x, y, z) - module.threshold;
|
||||
if (module.invert) {
|
||||
moduleDensity = -moduleDensity;
|
||||
}
|
||||
@@ -877,31 +963,34 @@ public class IrisCaveCarver3D {
|
||||
}
|
||||
|
||||
private double sampleDensityWarpOnly(int x, int y, int z) {
|
||||
double warpA = signed(warpDensity.noiseFast3D(x, y, z));
|
||||
double warpB = signed(warpDensity.noiseFast3D(x + 31.37D, y - 17.21D, z + 23.91D));
|
||||
double warpA = warpDensity.noiseFastSigned3D(x, y, z);
|
||||
double warpB = warpDensity.noiseFastSigned3D(x + 31.37D, y - 17.21D, z + 23.91D);
|
||||
double warpedX = x + (warpA * warpStrength);
|
||||
double warpedY = y + (warpB * warpStrength);
|
||||
double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength);
|
||||
double density = signed(baseDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * baseWeight;
|
||||
density += signed(detailDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * detailWeight;
|
||||
double density = baseDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * baseWeight;
|
||||
density += detailDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * detailWeight;
|
||||
return density * inverseNormalization;
|
||||
}
|
||||
|
||||
private double sampleDensityWarpModules(int x, int y, int z) {
|
||||
double warpA = signed(warpDensity.noiseFast3D(x, y, z));
|
||||
double warpB = signed(warpDensity.noiseFast3D(x + 31.37D, y - 17.21D, z + 23.91D));
|
||||
Scratch scratch = SCRATCH.get();
|
||||
int activeModuleCount = prepareActiveModules(scratch, y);
|
||||
if (activeModuleCount == 0) {
|
||||
return sampleDensityWarpOnly(x, y, z);
|
||||
}
|
||||
|
||||
ModuleState[] localModules = scratch.activeModules;
|
||||
double warpA = warpDensity.noiseFastSigned3D(x, y, z);
|
||||
double warpB = warpDensity.noiseFastSigned3D(x + 31.37D, y - 17.21D, z + 23.91D);
|
||||
double warpedX = x + (warpA * warpStrength);
|
||||
double warpedY = y + (warpB * warpStrength);
|
||||
double warpedZ = z + ((warpA - warpB) * 0.5D * warpStrength);
|
||||
double density = signed(baseDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * baseWeight;
|
||||
density += signed(detailDensity.noiseFast3D(warpedX, warpedY, warpedZ)) * detailWeight;
|
||||
for (int moduleIndex = 0; moduleIndex < modules.length; moduleIndex++) {
|
||||
ModuleState module = modules[moduleIndex];
|
||||
if (y < module.minY || y > module.maxY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
double moduleDensity = signed(module.density.noiseFast3D(warpedX, warpedY, warpedZ)) - module.threshold;
|
||||
double density = baseDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * baseWeight;
|
||||
density += detailDensity.noiseFastSigned3D(warpedX, warpedY, warpedZ) * detailWeight;
|
||||
for (int moduleIndex = 0; moduleIndex < activeModuleCount; moduleIndex++) {
|
||||
ModuleState module = localModules[moduleIndex];
|
||||
double moduleDensity = module.density.noiseFastSigned3D(warpedX, warpedY, warpedZ) - module.threshold;
|
||||
if (module.invert) {
|
||||
moduleDensity = -moduleDensity;
|
||||
}
|
||||
@@ -912,6 +1001,44 @@ public class IrisCaveCarver3D {
|
||||
return density * inverseNormalization;
|
||||
}
|
||||
|
||||
private int prepareActiveModules(Scratch scratch, int y) {
|
||||
ModuleState[] configuredModules = modules;
|
||||
int configuredCount = configuredModules.length;
|
||||
if (configuredCount == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (scratch.activeModules.length < configuredCount) {
|
||||
scratch.activeModules = new ModuleState[configuredCount];
|
||||
}
|
||||
|
||||
int activeCount = 0;
|
||||
for (int moduleIndex = 0; moduleIndex < configuredCount; moduleIndex++) {
|
||||
ModuleState module = configuredModules[moduleIndex];
|
||||
if (y < module.minY || y > module.maxY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
scratch.activeModules[activeCount] = module;
|
||||
activeCount++;
|
||||
}
|
||||
|
||||
if (scratch.activeModuleRemainingMin.length < activeCount + 1) {
|
||||
scratch.activeModuleRemainingMin = new double[activeCount + 1];
|
||||
scratch.activeModuleRemainingMax = new double[activeCount + 1];
|
||||
}
|
||||
|
||||
scratch.activeModuleRemainingMin[activeCount] = 0D;
|
||||
scratch.activeModuleRemainingMax[activeCount] = 0D;
|
||||
for (int moduleIndex = activeCount - 1; moduleIndex >= 0; moduleIndex--) {
|
||||
ModuleState module = scratch.activeModules[moduleIndex];
|
||||
scratch.activeModuleRemainingMin[moduleIndex] = scratch.activeModuleRemainingMin[moduleIndex + 1] + module.minContribution;
|
||||
scratch.activeModuleRemainingMax[moduleIndex] = scratch.activeModuleRemainingMax[moduleIndex + 1] + module.maxContribution;
|
||||
}
|
||||
|
||||
return activeCount;
|
||||
}
|
||||
|
||||
private MatterSlice<MatterCavern> resolveCavernSlice(Scratch scratch, MantleChunk<Matter> chunk, int sectionIndex) {
|
||||
@SuppressWarnings("unchecked")
|
||||
MatterSlice<MatterCavern> cachedSlice = (MatterSlice<MatterCavern>) scratch.sectionSlices[sectionIndex];
|
||||
@@ -964,8 +1091,8 @@ public class IrisCaveCarver3D {
|
||||
}
|
||||
|
||||
private void prepareSectionCaches(Scratch scratch, int minY, int maxY) {
|
||||
int minSection = Math.max(0, minY >> 4);
|
||||
int maxSection = Math.max(minSection, maxY >> 4);
|
||||
int minSection = Math.max(0, PowerOfTwoCoordinates.floorDivPow2(minY, 4));
|
||||
int maxSection = Math.max(minSection, PowerOfTwoCoordinates.floorDivPow2(maxY, 4));
|
||||
int requiredSections = maxSection + 1;
|
||||
if (scratch.sectionMatter.length < requiredSections) {
|
||||
scratch.sectionMatter = new Matter[requiredSections];
|
||||
@@ -1038,6 +1165,8 @@ public class IrisCaveCarver3D {
|
||||
private final double weight;
|
||||
private final double threshold;
|
||||
private final boolean invert;
|
||||
private final double minContribution;
|
||||
private final double maxContribution;
|
||||
|
||||
private ModuleState(IrisCaveFieldModule module, CNG density) {
|
||||
IrisRange range = module.getVerticalRange();
|
||||
@@ -1047,6 +1176,20 @@ public class IrisCaveCarver3D {
|
||||
this.weight = module.getWeight();
|
||||
this.threshold = module.getThreshold();
|
||||
this.invert = module.isInvert();
|
||||
double rawMin = invert ? threshold - 1D : -1D - threshold;
|
||||
double rawMax = invert ? threshold + 1D : 1D - threshold;
|
||||
this.minContribution = rawMin * weight;
|
||||
this.maxContribution = rawMax * weight;
|
||||
}
|
||||
|
||||
private double sample(double x, int y, double z) {
|
||||
double sampled = density.noiseFastSigned3D(x, y, z);
|
||||
return invert ? (threshold - sampled) * weight : (sampled - threshold) * weight;
|
||||
}
|
||||
|
||||
private double sample(double x, double y, double z) {
|
||||
double sampled = density.noiseFastSigned3D(x, y, z);
|
||||
return invert ? (threshold - sampled) * weight : (sampled - threshold) * weight;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1061,11 +1204,15 @@ public class IrisCaveCarver3D {
|
||||
private final int[] activeColumnIndices = new int[256];
|
||||
private final int[] activeColumnTopY = new int[256];
|
||||
private final int[] planeColumnIndices = new int[256];
|
||||
private final double[] planeDensity = new double[256];
|
||||
private final double[] planeThresholdLimit = new double[256];
|
||||
private final boolean[] planeCarve = new boolean[256];
|
||||
private final int[] tileIndices = new int[4];
|
||||
private final int[] tileLocalX = new int[4];
|
||||
private final int[] tileLocalZ = new int[4];
|
||||
private final int[] tileTopY = new int[4];
|
||||
private ModuleState[] activeModules = new ModuleState[0];
|
||||
private double[] activeModuleRemainingMin = new double[0];
|
||||
private double[] activeModuleRemainingMax = new double[0];
|
||||
private double[] verticalEdgeFade = new double[0];
|
||||
private MatterCavern[] matterByY = new MatterCavern[0];
|
||||
private Matter[] sectionMatter = new Matter[0];
|
||||
|
||||
+10
-9
@@ -33,6 +33,7 @@ import art.arcane.iris.engine.object.IrisRange;
|
||||
import art.arcane.iris.util.project.context.ChunkContext;
|
||||
import art.arcane.volmlib.util.documentation.ChunkCoordinates;
|
||||
import art.arcane.volmlib.util.mantle.flag.ReservedFlag;
|
||||
import art.arcane.volmlib.util.math.PowerOfTwoCoordinates;
|
||||
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
|
||||
@@ -159,7 +160,7 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
||||
continue;
|
||||
}
|
||||
|
||||
int columnIndex = (localX << 4) | localZ;
|
||||
int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ);
|
||||
double dominantWeight = clampWeight(dominantKernelWeight / totalKernelWeight);
|
||||
double[] weights = columnProfileWeights.get(dominantProfile);
|
||||
if (weights == null) {
|
||||
@@ -255,21 +256,21 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
||||
}
|
||||
|
||||
private void buildDimensionColumnPlan(IrisDimensionCarvingEntry[] columnPlan, int chunkX, int chunkZ, IrisDimensionCarvingEntry entry, IrisDimensionCarvingResolver.State resolverState) {
|
||||
int baseX = chunkX << 4;
|
||||
int baseZ = chunkZ << 4;
|
||||
int baseX = PowerOfTwoCoordinates.chunkToBlock(chunkX);
|
||||
int baseZ = PowerOfTwoCoordinates.chunkToBlock(chunkZ);
|
||||
for (int localX = 0; localX < CHUNK_SIZE; localX++) {
|
||||
int worldX = baseX + localX;
|
||||
for (int localZ = 0; localZ < CHUNK_SIZE; localZ++) {
|
||||
int worldZ = baseZ + localZ;
|
||||
int columnIndex = (localX << 4) | localZ;
|
||||
int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ);
|
||||
columnPlan[columnIndex] = IrisDimensionCarvingResolver.resolveFromRoot(getEngineMantle().getEngine(), entry, worldX, worldZ, resolverState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void fillProfileField(IrisCaveProfile[] profileField, int chunkX, int chunkZ, IrisComplex complex, IrisDimensionCarvingResolver.State resolverState, Long2ObjectOpenHashMap<IrisBiome> caveBiomeCache) {
|
||||
int startX = (chunkX << 4) - BLEND_RADIUS;
|
||||
int startZ = (chunkZ << 4) - BLEND_RADIUS;
|
||||
int startX = PowerOfTwoCoordinates.chunkToBlock(chunkX) - BLEND_RADIUS;
|
||||
int startZ = PowerOfTwoCoordinates.chunkToBlock(chunkZ) - BLEND_RADIUS;
|
||||
|
||||
for (int fieldX = 0; fieldX < FIELD_SIZE; fieldX++) {
|
||||
int worldX = startX + fieldX;
|
||||
@@ -356,8 +357,8 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
||||
|
||||
private int[] prepareChunkSurfaceHeights(int chunkX, int chunkZ, ChunkContext context, int[] scratch) {
|
||||
int[] surfaceHeights = scratch;
|
||||
int baseX = chunkX << 4;
|
||||
int baseZ = chunkZ << 4;
|
||||
int baseX = PowerOfTwoCoordinates.chunkToBlock(chunkX);
|
||||
int baseZ = PowerOfTwoCoordinates.chunkToBlock(chunkZ);
|
||||
boolean useContextHeight = context != null
|
||||
&& context.getHeight() != null
|
||||
&& context.getX() == baseX
|
||||
@@ -366,7 +367,7 @@ public class MantleCarvingComponent extends IrisMantleComponent {
|
||||
int worldX = baseX + localX;
|
||||
for (int localZ = 0; localZ < CHUNK_SIZE; localZ++) {
|
||||
int worldZ = baseZ + localZ;
|
||||
int columnIndex = (localX << 4) | localZ;
|
||||
int columnIndex = PowerOfTwoCoordinates.packLocal16(localX, localZ);
|
||||
if (useContextHeight) {
|
||||
Double cachedHeight = context.getHeight().get(localX, localZ);
|
||||
if (cachedHeight != null) {
|
||||
|
||||
@@ -35,6 +35,7 @@ import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
|
||||
import art.arcane.volmlib.util.math.BlockPosition;
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
import art.arcane.volmlib.util.math.PowerOfTwoCoordinates;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.matter.Matter;
|
||||
import art.arcane.volmlib.util.matter.MatterCavern;
|
||||
@@ -89,7 +90,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
|
||||
|
||||
int rx = xx & 15;
|
||||
int rz = zz & 15;
|
||||
int columnIndex = (rx << 4) | rz;
|
||||
int columnIndex = PowerOfTwoCoordinates.packLocal16(rx, rz);
|
||||
BlockData current = output.get(rx, yy, rz);
|
||||
|
||||
if (B.isFluid(current)) {
|
||||
@@ -135,8 +136,8 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
|
||||
PrecisionStopwatch applyStopwatch = PrecisionStopwatch.start();
|
||||
try {
|
||||
walls.forEach((rx, yy, rz, cavern) -> {
|
||||
int worldX = rx + (x << 4);
|
||||
int worldZ = rz + (z << 4);
|
||||
int worldX = rx + PowerOfTwoCoordinates.chunkToBlock(x);
|
||||
int worldZ = rz + PowerOfTwoCoordinates.chunkToBlock(z);
|
||||
String customBiome = cavern.getCustomBiome();
|
||||
IrisBiome biome = customBiome.isEmpty()
|
||||
? resolveCaveBiome(caveBiomeCache, worldX, yy, worldZ, resolverState)
|
||||
@@ -184,10 +185,10 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
|
||||
return;
|
||||
}
|
||||
|
||||
int rx = columnIndex >> 4;
|
||||
int rx = PowerOfTwoCoordinates.unpackLocal16X(columnIndex);
|
||||
int rz = columnIndex & 15;
|
||||
int worldX = rx + (chunkX << 4);
|
||||
int worldZ = rz + (chunkZ << 4);
|
||||
int worldX = rx + PowerOfTwoCoordinates.chunkToBlock(chunkX);
|
||||
int worldZ = rz + PowerOfTwoCoordinates.chunkToBlock(chunkZ);
|
||||
CaveZone zone = new CaveZone();
|
||||
zone.setFloor(firstHeight);
|
||||
int buf = firstHeight - 1;
|
||||
@@ -239,7 +240,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
|
||||
}
|
||||
|
||||
for (int i = zone.floor; i <= zone.ceiling; i++) {
|
||||
MatterCavern cavernData = (MatterCavern) mc.getOrCreate(i >> 4).slice(MatterCavern.class)
|
||||
MatterCavern cavernData = (MatterCavern) mc.getOrCreate(PowerOfTwoCoordinates.floorDivPow2(i, 4)).slice(MatterCavern.class)
|
||||
.get(rx, i & 15, rz);
|
||||
|
||||
if (cavernData != null && !cavernData.getCustomBiome().isEmpty()) {
|
||||
@@ -447,11 +448,11 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
|
||||
}
|
||||
|
||||
private int pack(int x, int y, int z) {
|
||||
return (y << 8) | ((x & 15) << 4) | (z & 15);
|
||||
return (y << 8) | PowerOfTwoCoordinates.packLocal16(x & 15, z & 15);
|
||||
}
|
||||
|
||||
private int unpackX(int key) {
|
||||
return (key >> 4) & 15;
|
||||
return PowerOfTwoCoordinates.unpackLocal16X(key & 255);
|
||||
}
|
||||
|
||||
private int unpackY(int key) {
|
||||
@@ -459,7 +460,7 @@ public class IrisCarveModifier extends EngineAssignedModifier<BlockData> {
|
||||
}
|
||||
|
||||
private int unpackZ(int key) {
|
||||
return key & 15;
|
||||
return PowerOfTwoCoordinates.unpackLocal16Z(key);
|
||||
}
|
||||
|
||||
private int mix(int value) {
|
||||
|
||||
@@ -179,14 +179,17 @@ public class IrisBiome extends IrisRegistrant implements IRare {
|
||||
if (ores.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
BlockData b = null;
|
||||
for (IrisOreGenerator i : ores) {
|
||||
if (i.isGenerateSurface() != surface)
|
||||
KList<IrisOreGenerator> localOres = ores;
|
||||
int oreCount = localOres.size();
|
||||
for (int oreIndex = 0; oreIndex < oreCount; oreIndex++) {
|
||||
IrisOreGenerator oreGenerator = localOres.get(oreIndex);
|
||||
if (oreGenerator.isGenerateSurface() != surface) {
|
||||
continue;
|
||||
}
|
||||
|
||||
b = i.generate(x, y, z, rng, data);
|
||||
if (b != null) {
|
||||
return b;
|
||||
BlockData ore = oreGenerator.generate(x, y, z, rng, data);
|
||||
if (ore != null) {
|
||||
return ore;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -77,7 +77,10 @@ public class IrisBiomePaletteLayer {
|
||||
return getBlockData(data).get(0);
|
||||
}
|
||||
|
||||
return getLayerGenerator(rng, data).fit(getBlockData(data), x / zoom, y / zoom, z / zoom);
|
||||
double scaledX = x / zoom;
|
||||
double scaledY = y / zoom;
|
||||
double scaledZ = z / zoom;
|
||||
return getLayerGenerator(rng, data).fit(getBlockData(data), scaledX, scaledY, scaledZ);
|
||||
}
|
||||
|
||||
public CNG getLayerGenerator(RNG rng, IrisData data) {
|
||||
|
||||
@@ -290,22 +290,25 @@ public class IrisDimension extends IrisRegistrant {
|
||||
carvingEntryIndex.reset();
|
||||
}
|
||||
|
||||
public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data, boolean surface) {
|
||||
if (ores.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
BlockData b = null;
|
||||
for (IrisOreGenerator i : ores) {
|
||||
if (i.isGenerateSurface() != surface)
|
||||
continue;
|
||||
|
||||
b = i.generate(x, y, z, rng, data);
|
||||
if (b != null) {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public BlockData generateOres(int x, int y, int z, RNG rng, IrisData data, boolean surface) {
|
||||
if (ores.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
KList<IrisOreGenerator> localOres = ores;
|
||||
int oreCount = localOres.size();
|
||||
for (int oreIndex = 0; oreIndex < oreCount; oreIndex++) {
|
||||
IrisOreGenerator oreGenerator = localOres.get(oreIndex);
|
||||
if (oreGenerator.isGenerateSurface() != surface) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BlockData ore = oreGenerator.generate(x, y, z, rng, data);
|
||||
if (ore != null) {
|
||||
return ore;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public int getFluidHeight() {
|
||||
return fluidHeight - (int) dimensionHeight.getMin();
|
||||
|
||||
@@ -212,21 +212,23 @@ public class IrisGenerator extends IrisRegistrant {
|
||||
int hc = (int) ((cliffHeightMin * 10) + 10 + cliffHeightMax * getSeed() + offsetX + offsetZ);
|
||||
double h = multiplicitive ? 1 : 0;
|
||||
double tp = 0;
|
||||
double sampleX = (rx + offsetX) / zoom;
|
||||
double sampleZ = (rz + offsetZ) / zoom;
|
||||
|
||||
if (composite.size() == 1) {
|
||||
if (multiplicitive) {
|
||||
h *= composite.get(0).getNoise(getSeed() + superSeed + hc, (rx + offsetX) / zoom, (rz + offsetZ) / zoom, getLoader());
|
||||
h *= composite.get(0).getNoise(getSeed() + superSeed + hc, sampleX, sampleZ, getLoader());
|
||||
} else {
|
||||
tp += composite.get(0).getOpacity();
|
||||
h += composite.get(0).getNoise(getSeed() + superSeed + hc, (rx + offsetX) / zoom, (rz + offsetZ) / zoom, getLoader());
|
||||
h += composite.get(0).getNoise(getSeed() + superSeed + hc, sampleX, sampleZ, getLoader());
|
||||
}
|
||||
} else {
|
||||
for (IrisNoiseGenerator i : composite) {
|
||||
if (multiplicitive) {
|
||||
h *= i.getNoise(getSeed() + superSeed + hc, (rx + offsetX) / zoom, (rz + offsetZ) / zoom, getLoader());
|
||||
h *= i.getNoise(getSeed() + superSeed + hc, sampleX, sampleZ, getLoader());
|
||||
} else {
|
||||
tp += i.getOpacity();
|
||||
h += i.getNoise(getSeed() + superSeed + hc, (rx + offsetX) / zoom, (rz + offsetZ) / zoom, getLoader());
|
||||
h += i.getNoise(getSeed() + superSeed + hc, sampleX, sampleZ, getLoader());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -245,7 +247,9 @@ public class IrisGenerator extends IrisRegistrant {
|
||||
|
||||
public double cell(double rx, double rz, double v, double superSeed) {
|
||||
getCellGenerator(getSeed() + 46222).setShuffle(getCellFractureShuffle());
|
||||
return getCellGenerator(getSeed() + 46222).getDistance(rx / getCellFractureZoom(), rz / getCellFractureZoom()) > getCellPercentSize() ? (v * getCellFractureHeight()) : v;
|
||||
double fractureX = rx / getCellFractureZoom();
|
||||
double fractureZ = rz / getCellFractureZoom();
|
||||
return getCellGenerator(getSeed() + 46222).getDistance(fractureX, fractureZ) > getCellPercentSize() ? (v * getCellFractureHeight()) : v;
|
||||
}
|
||||
|
||||
private boolean hasCellCracks() {
|
||||
@@ -254,7 +258,9 @@ public class IrisGenerator extends IrisRegistrant {
|
||||
|
||||
public double getCliffHeight(double rx, double rz, double superSeed) {
|
||||
int hc = (int) ((cliffHeightMin * 10) + 10 + cliffHeightMax * getSeed() + offsetX + offsetZ);
|
||||
double h = cliffHeightGenerator.getNoise((long) (getSeed() + superSeed + hc), (rx + offsetX) / zoom, (rz + offsetZ) / zoom, getLoader());
|
||||
double sampleX = (rx + offsetX) / zoom;
|
||||
double sampleZ = (rz + offsetZ) / zoom;
|
||||
double h = cliffHeightGenerator.getNoise((long) (getSeed() + superSeed + hc), sampleX, sampleZ, getLoader());
|
||||
return IrisInterpolation.lerp(cliffHeightMin, cliffHeightMax, h);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ import lombok.experimental.Accessors;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
@Snippet("style")
|
||||
@Accessors(chain = true)
|
||||
@@ -41,6 +42,7 @@ import java.util.Objects;
|
||||
@Desc("A gen style")
|
||||
@Data
|
||||
public class IrisGeneratorStyle {
|
||||
private static final ConcurrentHashMap<String, String> ACTIVE_CACHE_KEYS = new ConcurrentHashMap<>();
|
||||
private final transient AtomicCache<CNG> cng = new AtomicCache<>();
|
||||
@Desc("The chance is 1 in CHANCE per interval")
|
||||
private NoiseStyle style = NoiseStyle.FLAT;
|
||||
@@ -127,6 +129,11 @@ public class IrisGeneratorStyle {
|
||||
|
||||
private void clearStaleCacheEntries(IrisData data, String prefix, String key) {
|
||||
File cacheFolder = new File(data.getDataFolder(), ".cache");
|
||||
String cacheFolderKey = cacheFolder.getAbsolutePath() + File.separator + prefix;
|
||||
String previousKey = ACTIVE_CACHE_KEYS.put(cacheFolderKey, key);
|
||||
if (key.equals(previousKey)) {
|
||||
return;
|
||||
}
|
||||
File[] files = cacheFolder.listFiles((dir, name) -> name.endsWith(".cnm") && name.startsWith(prefix));
|
||||
if (files == null) {
|
||||
return;
|
||||
|
||||
@@ -61,7 +61,10 @@ public class IrisMaterialPalette {
|
||||
return getBlockData(rdata).get(0);
|
||||
}
|
||||
|
||||
return getLayerGenerator(rng, rdata).fit(getBlockData(rdata), x / zoom, y / zoom, z / zoom);
|
||||
double scaledX = x / zoom;
|
||||
double scaledY = y / zoom;
|
||||
double scaledZ = z / zoom;
|
||||
return getLayerGenerator(rng, rdata).fit(getBlockData(rdata), scaledX, scaledY, scaledZ);
|
||||
}
|
||||
|
||||
public Optional<TileData> getTile(RNG rng, double x, double y, double z, IrisData rdata) {
|
||||
|
||||
@@ -22,8 +22,8 @@ import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.engine.data.cache.AtomicCache;
|
||||
import art.arcane.iris.engine.object.annotations.*;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.iris.util.project.interpolation.IrisInterpolation;
|
||||
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.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
@@ -100,14 +100,17 @@ public class IrisNoiseGenerator {
|
||||
|
||||
for (IrisNoiseGenerator i : fracture) {
|
||||
if (i.isEnabled()) {
|
||||
x += i.getNoise(superSeed + seed + g, xv, zv, data) - (i.getOpacity() / 2D);
|
||||
z += i.getNoise(superSeed + seed + g, zv, xv, data) - (i.getOpacity() / 2D);
|
||||
double fractureOffset = i.getOpacity() / 2D;
|
||||
x += i.getNoise(superSeed + seed + g, xv, zv, data) - fractureOffset;
|
||||
z += i.getNoise(superSeed + seed + g, zv, xv, data) - fractureOffset;
|
||||
}
|
||||
g += 819;
|
||||
}
|
||||
|
||||
CNG cng = getGenerator(superSeed, data);
|
||||
double n = cng.noiseFast2D((x / zoom) + offsetX, (z / zoom) + offsetZ) * opacity;
|
||||
double sampleX = (x / zoom) + offsetX;
|
||||
double sampleZ = (z / zoom) + offsetZ;
|
||||
double n = cng.noiseFast2D(sampleX, sampleZ) * opacity;
|
||||
n = negative ? (-n + opacity) : n;
|
||||
n = (exponent != 1 ? n < 0 ? -Math.pow(-n, exponent) : Math.pow(n, exponent) : n) + offsetY;
|
||||
n = parametric ? IrisInterpolation.parametric(n, 1) : n;
|
||||
|
||||
@@ -155,14 +155,17 @@ public class IrisRegion extends IrisRegistrant implements IRare {
|
||||
if (ores.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
BlockData b = null;
|
||||
for (IrisOreGenerator i : ores) {
|
||||
if (i.isGenerateSurface() != surface)
|
||||
KList<IrisOreGenerator> localOres = ores;
|
||||
int oreCount = localOres.size();
|
||||
for (int oreIndex = 0; oreIndex < oreCount; oreIndex++) {
|
||||
IrisOreGenerator oreGenerator = localOres.get(oreIndex);
|
||||
if (oreGenerator.isGenerateSurface() != surface) {
|
||||
continue;
|
||||
}
|
||||
|
||||
b = i.generate(x, y, z, rng, data);
|
||||
if (b != null) {
|
||||
return b;
|
||||
BlockData ore = oreGenerator.generate(x, y, z, rng, data);
|
||||
if (ore != null) {
|
||||
return ore;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -21,6 +21,7 @@ package art.arcane.iris.engine.platform;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.IrisWorlds;
|
||||
import art.arcane.iris.core.gui.PregeneratorJob;
|
||||
import art.arcane.iris.core.loader.IrisData;
|
||||
import art.arcane.iris.core.nms.INMS;
|
||||
import art.arcane.iris.core.service.StudioSVC;
|
||||
@@ -81,6 +82,8 @@ import java.util.concurrent.locks.ReentrantLock;
|
||||
@Data
|
||||
public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChunkGenerator, Listener {
|
||||
private static final int LOAD_LOCKS = Runtime.getRuntime().availableProcessors() * 4;
|
||||
private static final long HOTLOAD_LOOP_DELAY_MS = 250L;
|
||||
private static final long HOTLOAD_MAINTENANCE_DELAY_MS = 4000L;
|
||||
private final Semaphore loadLock;
|
||||
private final IrisWorld world;
|
||||
private final File dataLocation;
|
||||
@@ -563,11 +566,15 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
this.hotloader = studio ? new Looper() {
|
||||
@Override
|
||||
protected long loop() {
|
||||
if (shouldThrottleHotload()) {
|
||||
return HOTLOAD_MAINTENANCE_DELAY_MS;
|
||||
}
|
||||
|
||||
if (hotloadChecker.flip()) {
|
||||
folder.check();
|
||||
}
|
||||
|
||||
return 250;
|
||||
return HOTLOAD_LOOP_DELAY_MS;
|
||||
}
|
||||
} : null;
|
||||
|
||||
@@ -735,10 +742,24 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
return true;
|
||||
}
|
||||
|
||||
return isMaintenanceActive();
|
||||
}
|
||||
|
||||
private boolean isMaintenanceActive() {
|
||||
World realWorld = this.world.realWorld();
|
||||
return realWorld != null && IrisToolbelt.isWorldMaintenanceActive(realWorld);
|
||||
}
|
||||
|
||||
private boolean shouldThrottleHotload() {
|
||||
if (isMaintenanceActive()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
World realWorld = this.world.realWorld();
|
||||
PregeneratorJob pregeneratorJob = PregeneratorJob.getInstance();
|
||||
return realWorld != null && pregeneratorJob != null && pregeneratorJob.targetsWorld(realWorld);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBaseHeight(@NotNull WorldInfo worldInfo, @NotNull Random random, int x, int z, @NotNull HeightMap heightMap) {
|
||||
Engine currentEngine = engine;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
package art.arcane.iris.util.common.data;
|
||||
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.volmlib.util.math.PowerOfTwoCoordinates;
|
||||
|
||||
public class BiomeMap {
|
||||
private final IrisBiome[] height;
|
||||
@@ -28,10 +29,10 @@ public class BiomeMap {
|
||||
}
|
||||
|
||||
public void setBiome(int x, int z, IrisBiome h) {
|
||||
height[x * 16 + z] = h;
|
||||
height[PowerOfTwoCoordinates.packLocal16(x, z)] = h;
|
||||
}
|
||||
|
||||
public IrisBiome getBiome(int x, int z) {
|
||||
return height[x * 16 + z];
|
||||
return height[PowerOfTwoCoordinates.packLocal16(x, z)];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package art.arcane.iris.util.project.context;
|
||||
|
||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
import art.arcane.iris.util.project.stream.utility.CachedStream2D;
|
||||
import art.arcane.iris.util.project.stream.utility.ChunkFillableStream2D;
|
||||
import art.arcane.volmlib.util.documentation.BlockCoordinates;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@@ -35,8 +35,8 @@ public class ChunkedDataCache<T> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stream instanceof CachedStream2D<?> cachedStream) {
|
||||
cachedStream.fillChunk(x, z, data);
|
||||
if (stream instanceof ChunkFillableStream2D cachedStream) {
|
||||
cachedStream.fillChunkRaw(x, z, data);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+4
-13
@@ -32,7 +32,6 @@ import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
|
||||
public class IrisInterpolation {
|
||||
public static CNG cng = NoiseStyle.SIMPLEX.create(new RNG());
|
||||
@@ -952,18 +951,10 @@ public class IrisInterpolation {
|
||||
*/
|
||||
public static Hunk<Double> getNoise3D(InterpolationMethod3D method, int xo, int yo, int zo, int w, int h, int d, double radX, double radY, double radZ, NoiseProvider3 n) {
|
||||
Hunk<Double> hunk = Hunk.newAtomicDoubleHunk(w, h, d);
|
||||
HashMap<Integer, Double> cache = new HashMap<>();
|
||||
int i, j, k;
|
||||
|
||||
for (i = 0; i < w; i++) {
|
||||
int fi = i;
|
||||
for (j = 0; j < h; j++) {
|
||||
int fj = j;
|
||||
for (k = 0; k < d; k++) {
|
||||
int fk = k;
|
||||
hunk.set(i, j, k, cache.computeIfAbsent((k * w * h) + (j * w) + i, (p)
|
||||
-> getNoise3D(method, fi + xo, fj + yo, fk + zo,
|
||||
radX, radY, radZ, n)));
|
||||
for (int i = 0; i < w; i++) {
|
||||
for (int j = 0; j < h; j++) {
|
||||
for (int k = 0; k < d; k++) {
|
||||
hunk.set(i, j, k, getNoise3D(method, i + xo, j + yo, k + zo, radX, radY, radZ, n));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +74,10 @@ public class CNG {
|
||||
private NoiseStyle leakStyle;
|
||||
private ProceduralStream<Double> customGenerator;
|
||||
private transient boolean identityPostFastPath;
|
||||
private transient boolean identitySignedFastPath;
|
||||
private transient boolean unityOpacity;
|
||||
private transient double effectiveScale;
|
||||
private transient double signedFractureScale;
|
||||
private transient boolean fastPathStateDirty = true;
|
||||
|
||||
public CNG(RNG random) {
|
||||
@@ -635,7 +639,8 @@ public class CNG {
|
||||
}
|
||||
|
||||
private double getNoise(double... dim) {
|
||||
double scale = noscale ? 1 : this.bakedScale * this.scale;
|
||||
ensureFastPathState();
|
||||
double scale = effectiveScale;
|
||||
|
||||
if (fracture == null || noscale) {
|
||||
return generator.noise(
|
||||
@@ -671,7 +676,8 @@ public class CNG {
|
||||
}
|
||||
|
||||
private double getNoise(double x) {
|
||||
double scl = noscale ? 1 : this.bakedScale * this.scale;
|
||||
ensureFastPathState();
|
||||
double scl = effectiveScale;
|
||||
|
||||
if (fracture == null || noscale) {
|
||||
return generator.noise(x * scl, 0D, 0D) * opacity;
|
||||
@@ -682,7 +688,8 @@ public class CNG {
|
||||
}
|
||||
|
||||
private double getNoise(double x, double z) {
|
||||
double scl = noscale ? 1 : this.bakedScale * this.scale;
|
||||
ensureFastPathState();
|
||||
double scl = effectiveScale;
|
||||
|
||||
if (fracture == null || noscale) {
|
||||
return generator.noise(x * scl, z * scl, 0D) * opacity;
|
||||
@@ -694,7 +701,8 @@ public class CNG {
|
||||
}
|
||||
|
||||
private double getNoise(double x, double y, double z) {
|
||||
double scl = noscale ? 1 : this.bakedScale * this.scale;
|
||||
ensureFastPathState();
|
||||
double scl = effectiveScale;
|
||||
|
||||
if (fracture == null || noscale) {
|
||||
return generator.noise(x * scl, y * scl, z * scl) * opacity;
|
||||
@@ -932,6 +940,18 @@ public class CNG {
|
||||
return applyPost(getNoise(x, z), x, z);
|
||||
}
|
||||
|
||||
public double noiseFastSigned2D(double x, double z) {
|
||||
if (cache != null && isWholeCoordinate(x) && isWholeCoordinate(z)) {
|
||||
return (cache.get((int) x, (int) z) * 2D) - 1D;
|
||||
}
|
||||
|
||||
if (hasIdentitySignedFastPath()) {
|
||||
return getSignedNoise(x, z);
|
||||
}
|
||||
|
||||
return (noiseFast2D(x, z) * 2D) - 1D;
|
||||
}
|
||||
|
||||
public double noise(double x, double y, double z) {
|
||||
return applyPost(getNoise(x, y, z), x, y, z);
|
||||
}
|
||||
@@ -944,6 +964,14 @@ public class CNG {
|
||||
return applyPost(getNoise(x, y, z), x, y, z);
|
||||
}
|
||||
|
||||
public double noiseFastSigned3D(double x, double y, double z) {
|
||||
if (hasIdentitySignedFastPath()) {
|
||||
return getSignedNoise(x, y, z);
|
||||
}
|
||||
|
||||
return (noiseFast3D(x, y, z) * 2D) - 1D;
|
||||
}
|
||||
|
||||
public CNG pow(double power) {
|
||||
this.power = power;
|
||||
markFastPathStateDirty();
|
||||
@@ -964,11 +992,19 @@ public class CNG {
|
||||
}
|
||||
|
||||
private boolean isIdentityPostFastPath() {
|
||||
ensureFastPathState();
|
||||
return identityPostFastPath;
|
||||
}
|
||||
|
||||
private boolean hasIdentitySignedFastPath() {
|
||||
ensureFastPathState();
|
||||
return identitySignedFastPath;
|
||||
}
|
||||
|
||||
private void ensureFastPathState() {
|
||||
if (fastPathStateDirty) {
|
||||
refreshFastPathState();
|
||||
}
|
||||
|
||||
return identityPostFastPath;
|
||||
}
|
||||
|
||||
private void markFastPathStateDirty() {
|
||||
@@ -976,15 +1012,62 @@ public class CNG {
|
||||
}
|
||||
|
||||
private void refreshFastPathState() {
|
||||
effectiveScale = noscale ? 1D : bakedScale * scale;
|
||||
identityPostFastPath = power == 1D
|
||||
&& children == null
|
||||
&& fracture == null
|
||||
&& down == 0D
|
||||
&& up == 0D
|
||||
&& patch == 1D;
|
||||
identitySignedFastPath = power == 1D
|
||||
&& children == null
|
||||
&& down == 0D
|
||||
&& up == 0D
|
||||
&& patch == 1D;
|
||||
unityOpacity = opacity == 1D;
|
||||
signedFractureScale = 0.5D * fscale;
|
||||
fastPathStateDirty = false;
|
||||
}
|
||||
|
||||
private double signedWithOpacity(double signedNoise) {
|
||||
if (unityOpacity) {
|
||||
return signedNoise;
|
||||
}
|
||||
|
||||
return ((signedNoise + 1D) * opacity) - 1D;
|
||||
}
|
||||
|
||||
private double getSignedNoise(double x, double z) {
|
||||
ensureFastPathState();
|
||||
double scl = effectiveScale;
|
||||
NoiseGenerator localGenerator = generator;
|
||||
CNG localFracture = fracture;
|
||||
|
||||
if (localFracture == null || noscale) {
|
||||
return signedWithOpacity(localGenerator.noiseSigned(x * scl, z * scl, 0D));
|
||||
}
|
||||
|
||||
double fx = x + (localFracture.noiseFastSigned2D(x, z) * signedFractureScale);
|
||||
double fz = z + (localFracture.noiseFastSigned2D(z, x) * signedFractureScale);
|
||||
return signedWithOpacity(localGenerator.noiseSigned(fx * scl, fz * scl, 0D));
|
||||
}
|
||||
|
||||
private double getSignedNoise(double x, double y, double z) {
|
||||
ensureFastPathState();
|
||||
double scl = effectiveScale;
|
||||
NoiseGenerator localGenerator = generator;
|
||||
CNG localFracture = fracture;
|
||||
|
||||
if (localFracture == null || noscale) {
|
||||
return signedWithOpacity(localGenerator.noiseSigned(x * scl, y * scl, z * scl));
|
||||
}
|
||||
|
||||
double fx = x + (localFracture.noiseFastSigned3D(x, y, z) * signedFractureScale);
|
||||
double fy = y + (localFracture.noiseFastSigned2D(y, x) * signedFractureScale);
|
||||
double fz = z + (localFracture.noiseFastSigned3D(z, x, y) * signedFractureScale);
|
||||
return signedWithOpacity(localGenerator.noiseSigned(fx * scl, fy * scl, fz * scl));
|
||||
}
|
||||
|
||||
private enum InjectorMode {
|
||||
ADD,
|
||||
SRC_SUBTRACT,
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -38,16 +38,31 @@ public class FractalFBMSimplexNoise implements NoiseGenerator, OctaveNoise {
|
||||
return f(n.GetSimplexFractal(x, 0d));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noiseSigned(double x) {
|
||||
return n.GetSimplexFractal(x, 0D);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double z) {
|
||||
return f(n.GetSimplexFractal(x, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noiseSigned(double x, double z) {
|
||||
return n.GetSimplexFractal(x, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double y, double z) {
|
||||
return f(n.GetSimplexFractal(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noiseSigned(double x, double y, double z) {
|
||||
return n.GetSimplexFractal(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOctaves(int o) {
|
||||
n.setFractalOctaves(o);
|
||||
|
||||
@@ -24,6 +24,7 @@ public class HexJamesNoise implements NoiseGenerator, OctaveNoise {
|
||||
{-1L, 1L},
|
||||
{0L, 1L}
|
||||
};
|
||||
private static final ThreadLocal<HexScratch> SCRATCH = ThreadLocal.withInitial(HexScratch::new);
|
||||
private final long seed;
|
||||
private final SimplexNoise heatSimplex;
|
||||
private int octaves;
|
||||
@@ -221,6 +222,9 @@ public class HexJamesNoise implements NoiseGenerator, OctaveNoise {
|
||||
}
|
||||
|
||||
private double sample(double x, double z) {
|
||||
HexScratch scratch = SCRATCH.get();
|
||||
double[] centersQ = scratch.centersQ;
|
||||
double[] centersR = scratch.centersR;
|
||||
double qWorld = (SQRT_3_OVER_3 * x) - (z / 3.0);
|
||||
double rWorld = TWO_OVER_THREE * z;
|
||||
long[] rounded = roundAxial(qWorld, rWorld);
|
||||
@@ -233,8 +237,6 @@ public class HexJamesNoise implements NoiseGenerator, OctaveNoise {
|
||||
double heat = cellHeat(centerQ, centerR, nodeHash, 0);
|
||||
|
||||
for (int level = 0; level < MAX_DEPTH; level++) {
|
||||
double[] centersQ = new double[7];
|
||||
double[] centersR = new double[7];
|
||||
int childIndex = pickChildIndex(localQ, localR, centersQ, centersR);
|
||||
|
||||
if (childIndex < 0) {
|
||||
@@ -285,4 +287,9 @@ public class HexJamesNoise implements NoiseGenerator, OctaveNoise {
|
||||
public void setOctaves(int o) {
|
||||
this.octaves = Math.max(1, o);
|
||||
}
|
||||
|
||||
private static final class HexScratch {
|
||||
private final double[] centersQ = new double[7];
|
||||
private final double[] centersR = new double[7];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ public class HexRandomSizeNoise implements NoiseGenerator, OctaveNoise {
|
||||
{-1L, 1L},
|
||||
{0L, 1L}
|
||||
};
|
||||
private static final ThreadLocal<HexScratch> SCRATCH = ThreadLocal.withInitial(HexScratch::new);
|
||||
private final long seed;
|
||||
private final SimplexNoise heatSimplex;
|
||||
private final SimplexNoise structureSimplex;
|
||||
@@ -186,6 +187,9 @@ public class HexRandomSizeNoise implements NoiseGenerator, OctaveNoise {
|
||||
}
|
||||
|
||||
private double sample(double x, double z) {
|
||||
HexScratch scratch = SCRATCH.get();
|
||||
double[] centersQ = scratch.centersQ;
|
||||
double[] centersR = scratch.centersR;
|
||||
double qWorld = (SQRT_3_OVER_3 * x) - (z / 3.0);
|
||||
double rWorld = TWO_OVER_THREE * z;
|
||||
long[] rounded = roundAxial(qWorld, rWorld);
|
||||
@@ -202,8 +206,6 @@ public class HexRandomSizeNoise implements NoiseGenerator, OctaveNoise {
|
||||
return heat;
|
||||
}
|
||||
|
||||
double[] centersQ = new double[7];
|
||||
double[] centersR = new double[7];
|
||||
int childIndex = pickChildIndex(localQ, localR, centersQ, centersR);
|
||||
|
||||
if (childIndex < 0) {
|
||||
@@ -250,4 +252,9 @@ public class HexRandomSizeNoise implements NoiseGenerator, OctaveNoise {
|
||||
public void setOctaves(int o) {
|
||||
this.octaves = Math.max(1, o);
|
||||
}
|
||||
|
||||
private static final class HexScratch {
|
||||
private final double[] centersQ = new double[7];
|
||||
private final double[] centersR = new double[7];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,18 @@ public interface NoiseGenerator {
|
||||
|
||||
double noise(double x, double y, double z);
|
||||
|
||||
default double noiseSigned(double x) {
|
||||
return (noise(x) * 2D) - 1D;
|
||||
}
|
||||
|
||||
default double noiseSigned(double x, double z) {
|
||||
return (noise(x, z) * 2D) - 1D;
|
||||
}
|
||||
|
||||
default double noiseSigned(double x, double y, double z) {
|
||||
return (noise(x, y, z) * 2D) - 1D;
|
||||
}
|
||||
|
||||
default boolean isStatic() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -6,11 +6,31 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
||||
public class OffsetNoiseGenerator implements NoiseGenerator {
|
||||
private static final int GENERIC = 0;
|
||||
private static final int SIMPLEX = 1;
|
||||
private static final int PERLIN = 2;
|
||||
private static final int FRACTAL_FBM_SIMPLEX = 3;
|
||||
private static final int VASCULAR = 4;
|
||||
private final NoiseGenerator base;
|
||||
private final double ox, oz;
|
||||
private final SimplexNoise simplexBase;
|
||||
private final PerlinNoise perlinBase;
|
||||
private final FractalFBMSimplexNoise fractalFbmSimplexBase;
|
||||
private final VascularNoise vascularBase;
|
||||
private final int dispatchKind;
|
||||
private final double ox;
|
||||
private final double oz;
|
||||
|
||||
public OffsetNoiseGenerator(NoiseGenerator base, long seed) {
|
||||
this.base = base;
|
||||
this.simplexBase = base instanceof SimplexNoise simplex ? simplex : null;
|
||||
this.perlinBase = base instanceof PerlinNoise perlin ? perlin : null;
|
||||
this.fractalFbmSimplexBase = base instanceof FractalFBMSimplexNoise fractal ? fractal : null;
|
||||
this.vascularBase = base instanceof VascularNoise vascular ? vascular : null;
|
||||
this.dispatchKind = simplexBase != null ? SIMPLEX
|
||||
: perlinBase != null ? PERLIN
|
||||
: fractalFbmSimplexBase != null ? FRACTAL_FBM_SIMPLEX
|
||||
: vascularBase != null ? VASCULAR
|
||||
: GENERIC;
|
||||
RNG rng = new RNG(seed);
|
||||
ox = rng.nextInt(Short.MIN_VALUE, Short.MAX_VALUE);
|
||||
oz = rng.nextInt(Short.MIN_VALUE, Short.MAX_VALUE);
|
||||
@@ -18,17 +38,68 @@ public class OffsetNoiseGenerator implements NoiseGenerator {
|
||||
|
||||
@Override
|
||||
public double noise(double x) {
|
||||
return base.noise(x + ox);
|
||||
return switch (dispatchKind) {
|
||||
case SIMPLEX -> simplexBase.noise(x + ox);
|
||||
case PERLIN -> perlinBase.noise(x + ox);
|
||||
case FRACTAL_FBM_SIMPLEX -> fractalFbmSimplexBase.noise(x + ox);
|
||||
case VASCULAR -> vascularBase.noise(x + ox);
|
||||
default -> base.noise(x + ox);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noiseSigned(double x) {
|
||||
return switch (dispatchKind) {
|
||||
case SIMPLEX -> simplexBase.noiseSigned(x + ox);
|
||||
case PERLIN -> perlinBase.noiseSigned(x + ox);
|
||||
case FRACTAL_FBM_SIMPLEX -> fractalFbmSimplexBase.noiseSigned(x + ox);
|
||||
case VASCULAR -> vascularBase.noiseSigned(x + ox);
|
||||
default -> base.noiseSigned(x + ox);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double z) {
|
||||
return base.noise(x + ox, z + oz);
|
||||
return switch (dispatchKind) {
|
||||
case SIMPLEX -> simplexBase.noise(x + ox, z + oz);
|
||||
case PERLIN -> perlinBase.noise(x + ox, z + oz);
|
||||
case FRACTAL_FBM_SIMPLEX -> fractalFbmSimplexBase.noise(x + ox, z + oz);
|
||||
case VASCULAR -> vascularBase.noise(x + ox, z + oz);
|
||||
default -> base.noise(x + ox, z + oz);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noiseSigned(double x, double z) {
|
||||
return switch (dispatchKind) {
|
||||
case SIMPLEX -> simplexBase.noiseSigned(x + ox, z + oz);
|
||||
case PERLIN -> perlinBase.noiseSigned(x + ox, z + oz);
|
||||
case FRACTAL_FBM_SIMPLEX -> fractalFbmSimplexBase.noiseSigned(x + ox, z + oz);
|
||||
case VASCULAR -> vascularBase.noiseSigned(x + ox, z + oz);
|
||||
default -> base.noiseSigned(x + ox, z + oz);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double y, double z) {
|
||||
return base.noise(x + ox, y, z + oz);
|
||||
return switch (dispatchKind) {
|
||||
case SIMPLEX -> simplexBase.noise(x + ox, y, z + oz);
|
||||
case PERLIN -> perlinBase.noise(x + ox, y, z + oz);
|
||||
case FRACTAL_FBM_SIMPLEX -> fractalFbmSimplexBase.noise(x + ox, y, z + oz);
|
||||
case VASCULAR -> vascularBase.noise(x + ox, y, z + oz);
|
||||
default -> base.noise(x + ox, y, z + oz);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noiseSigned(double x, double y, double z) {
|
||||
return switch (dispatchKind) {
|
||||
case SIMPLEX -> simplexBase.noiseSigned(x + ox, y, z + oz);
|
||||
case PERLIN -> perlinBase.noiseSigned(x + ox, y, z + oz);
|
||||
case FRACTAL_FBM_SIMPLEX -> fractalFbmSimplexBase.noiseSigned(x + ox, y, z + oz);
|
||||
case VASCULAR -> vascularBase.noiseSigned(x + ox, y, z + oz);
|
||||
default -> base.noiseSigned(x + ox, y, z + oz);
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -45,4 +116,4 @@ public class OffsetNoiseGenerator implements NoiseGenerator {
|
||||
public NoiseGenerator getBase() {
|
||||
return base;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +51,33 @@ public class PerlinNoise implements NoiseGenerator, OctaveNoise {
|
||||
return f(v / m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noiseSigned(double x) {
|
||||
if (octaves <= 1) {
|
||||
return n.GetPerlin(x, 0D);
|
||||
}
|
||||
|
||||
double frequency = 1D;
|
||||
double magnitude = 0D;
|
||||
double value = 0D;
|
||||
|
||||
for (int i = 0; i < octaves; i++) {
|
||||
double sampleFrequency;
|
||||
if (frequency == 1D) {
|
||||
sampleFrequency = frequency;
|
||||
frequency = 2D;
|
||||
} else {
|
||||
frequency *= 2D;
|
||||
sampleFrequency = frequency;
|
||||
}
|
||||
|
||||
value += n.GetPerlin(x * sampleFrequency, 0D) * frequency;
|
||||
magnitude += frequency;
|
||||
}
|
||||
|
||||
return value / magnitude;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double z) {
|
||||
if (octaves <= 1) {
|
||||
@@ -69,6 +96,26 @@ public class PerlinNoise implements NoiseGenerator, OctaveNoise {
|
||||
return f(v / m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noiseSigned(double x, double z) {
|
||||
if (octaves <= 1) {
|
||||
return n.GetPerlin(x, z);
|
||||
}
|
||||
|
||||
double frequency = 1D;
|
||||
double magnitude = 0D;
|
||||
double value = 0D;
|
||||
|
||||
for (int i = 0; i < octaves; i++) {
|
||||
double octaveFrequency = frequency == 1D ? frequency + 1D : frequency * 2D;
|
||||
value += n.GetPerlin(x * octaveFrequency, z * octaveFrequency) * octaveFrequency;
|
||||
magnitude += octaveFrequency;
|
||||
frequency = octaveFrequency;
|
||||
}
|
||||
|
||||
return value / magnitude;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double y, double z) {
|
||||
if (octaves <= 1) {
|
||||
@@ -87,6 +134,26 @@ public class PerlinNoise implements NoiseGenerator, OctaveNoise {
|
||||
return f(v / m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noiseSigned(double x, double y, double z) {
|
||||
if (octaves <= 1) {
|
||||
return n.GetPerlin(x, y, z);
|
||||
}
|
||||
|
||||
double frequency = 1D;
|
||||
double magnitude = 0D;
|
||||
double value = 0D;
|
||||
|
||||
for (int i = 0; i < octaves; i++) {
|
||||
double octaveFrequency = frequency == 1D ? frequency + 1D : frequency * 2D;
|
||||
value += n.GetPerlin(x * octaveFrequency, y * octaveFrequency, z * octaveFrequency) * octaveFrequency;
|
||||
magnitude += octaveFrequency;
|
||||
frequency = octaveFrequency;
|
||||
}
|
||||
|
||||
return value / magnitude;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOctaves(int o) {
|
||||
octaves = o;
|
||||
|
||||
@@ -51,6 +51,33 @@ public class SimplexNoise implements NoiseGenerator, OctaveNoise {
|
||||
return f(v / m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noiseSigned(double x) {
|
||||
if (octaves <= 1) {
|
||||
return n.GetSimplex(x, 0D);
|
||||
}
|
||||
|
||||
double frequency = 1D;
|
||||
double magnitude = 0D;
|
||||
double value = 0D;
|
||||
|
||||
for (int i = 0; i < octaves; i++) {
|
||||
double sampleFrequency;
|
||||
if (frequency == 1D) {
|
||||
sampleFrequency = frequency;
|
||||
frequency = 2D;
|
||||
} else {
|
||||
frequency *= 2D;
|
||||
sampleFrequency = frequency;
|
||||
}
|
||||
|
||||
value += n.GetSimplex(x * sampleFrequency, 0D) * frequency;
|
||||
magnitude += frequency;
|
||||
}
|
||||
|
||||
return value / magnitude;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double z) {
|
||||
if (octaves <= 1) {
|
||||
@@ -69,6 +96,26 @@ public class SimplexNoise implements NoiseGenerator, OctaveNoise {
|
||||
return f(v / m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noiseSigned(double x, double z) {
|
||||
if (octaves <= 1) {
|
||||
return n.GetSimplex(x, z);
|
||||
}
|
||||
|
||||
double frequency = 1D;
|
||||
double magnitude = 0D;
|
||||
double value = 0D;
|
||||
|
||||
for (int i = 0; i < octaves; i++) {
|
||||
double octaveFrequency = frequency == 1D ? frequency + 1D : frequency * 2D;
|
||||
value += n.GetSimplex(x * octaveFrequency, z * octaveFrequency) * octaveFrequency;
|
||||
magnitude += octaveFrequency;
|
||||
frequency = octaveFrequency;
|
||||
}
|
||||
|
||||
return value / magnitude;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double y, double z) {
|
||||
if (octaves <= 1) {
|
||||
@@ -87,6 +134,26 @@ public class SimplexNoise implements NoiseGenerator, OctaveNoise {
|
||||
return f(v / m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noiseSigned(double x, double y, double z) {
|
||||
if (octaves <= 1) {
|
||||
return n.GetSimplex(x, y, z);
|
||||
}
|
||||
|
||||
double frequency = 1D;
|
||||
double magnitude = 0D;
|
||||
double value = 0D;
|
||||
|
||||
for (int i = 0; i < octaves; i++) {
|
||||
double octaveFrequency = frequency == 1D ? frequency + 1D : frequency * 2D;
|
||||
value += n.GetSimplex(x * octaveFrequency, y * octaveFrequency, z * octaveFrequency) * octaveFrequency;
|
||||
magnitude += octaveFrequency;
|
||||
frequency = octaveFrequency;
|
||||
}
|
||||
|
||||
return value / magnitude;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOctaves(int o) {
|
||||
octaves = o;
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
package art.arcane.iris.util.project.noise;
|
||||
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
|
||||
public class VascularNoise implements NoiseGenerator {
|
||||
@@ -32,7 +31,20 @@ public class VascularNoise implements NoiseGenerator {
|
||||
}
|
||||
|
||||
private double filter(double noise) {
|
||||
return M.clip((noise / 2D) + 0.5D, 0D, 1D);
|
||||
double normalized = (noise * 0.5D) + 0.5D;
|
||||
if (normalized < 0D) {
|
||||
return 0D;
|
||||
}
|
||||
|
||||
return normalized > 1D ? 1D : normalized;
|
||||
}
|
||||
|
||||
private double filterSigned(double noise) {
|
||||
if (noise < -1D) {
|
||||
return -1D;
|
||||
}
|
||||
|
||||
return noise > 1D ? 1D : noise;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -40,13 +52,28 @@ public class VascularNoise implements NoiseGenerator {
|
||||
return filter(n.GetCellular(x, 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noiseSigned(double x) {
|
||||
return filterSigned(n.GetCellular(x, 0D));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double z) {
|
||||
return filter(n.GetCellular(x, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noiseSigned(double x, double z) {
|
||||
return filterSigned(n.GetCellular(x, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noise(double x, double y, double z) {
|
||||
return filter(n.GetCellular(x, y, z));
|
||||
}
|
||||
|
||||
@Override
|
||||
public double noiseSigned(double x, double y, double z) {
|
||||
return filterSigned(n.GetCellular(x, y, z));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,6 +281,11 @@ public interface ProceduralStream<T> extends ProceduralLayer, Interpolated<T> {
|
||||
return new CachedStream2D<T>(name, engine, this, size);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
default ProceduralStream<Double> cache2DDouble(String name, Engine engine, int size) {
|
||||
return new CachedDoubleStream2D(name, engine, (ProceduralStream<Double>) this, size);
|
||||
}
|
||||
|
||||
default ProceduralStream<T> cache3D(String name, Engine engine, int maxSize) {
|
||||
return new CachedStream3D<T>(name, engine, this, maxSize);
|
||||
}
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package art.arcane.iris.util.project.stream.utility;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.service.PreservationSVC;
|
||||
import art.arcane.iris.engine.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.MeteredCache;
|
||||
import art.arcane.iris.util.project.stream.BasicStream;
|
||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
import art.arcane.volmlib.util.cache.WorldCache2DDouble;
|
||||
import art.arcane.volmlib.util.data.KCache;
|
||||
|
||||
public class CachedDoubleStream2D extends BasicStream<Double> implements ProceduralStream<Double>, MeteredCache, ChunkFillableStream2D {
|
||||
private final ProceduralStream<Double> stream;
|
||||
private final WorldCache2DDouble cache;
|
||||
private final Engine engine;
|
||||
|
||||
public CachedDoubleStream2D(String name, Engine engine, ProceduralStream<Double> stream, int size) {
|
||||
super();
|
||||
this.stream = stream;
|
||||
this.engine = engine;
|
||||
this.cache = new WorldCache2DDouble((x, z) -> stream.getDouble(x, z), size);
|
||||
Iris.service(PreservationSVC.class).registerCache(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double toDouble(Double t) {
|
||||
return t;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double fromDouble(double d) {
|
||||
return d;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double get(double x, double z) {
|
||||
return cache.get((int) x, (int) z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Double get(double x, double y, double z) {
|
||||
return stream.get(x, y, z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getDouble(double x, double z) {
|
||||
return cache.get((int) x, (int) z);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getSize() {
|
||||
return cache.getSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public KCache<?, ?> getRawCache() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getMaxSize() {
|
||||
return cache.getMaxSize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
return engine.isClosed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fillChunkRaw(int worldX, int worldZ, Object[] target) {
|
||||
int chunkX = worldX >> 4;
|
||||
int chunkZ = worldZ >> 4;
|
||||
cache.fillChunk(chunkX, chunkZ, target);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ import art.arcane.volmlib.util.cache.WorldCache2D;
|
||||
import art.arcane.volmlib.util.data.KCache;
|
||||
import art.arcane.iris.util.project.stream.BasicStream;
|
||||
import art.arcane.iris.util.project.stream.ProceduralStream;
|
||||
public class CachedStream2D<T> extends BasicStream<T> implements ProceduralStream<T>, MeteredCache {
|
||||
public class CachedStream2D<T> extends BasicStream<T> implements ProceduralStream<T>, MeteredCache, ChunkFillableStream2D {
|
||||
private final ProceduralStream<T> stream;
|
||||
private final WorldCache2D<T> cache;
|
||||
private final Engine engine;
|
||||
@@ -81,7 +81,7 @@ public class CachedStream2D<T> extends BasicStream<T> implements ProceduralStrea
|
||||
return engine.isClosed();
|
||||
}
|
||||
|
||||
public void fillChunk(int worldX, int worldZ, Object[] target) {
|
||||
public void fillChunkRaw(int worldX, int worldZ, Object[] target) {
|
||||
int chunkX = worldX >> 4;
|
||||
int chunkZ = worldZ >> 4;
|
||||
cache.fillChunk(chunkX, chunkZ, target);
|
||||
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
package art.arcane.iris.util.project.stream.utility;
|
||||
|
||||
public interface ChunkFillableStream2D {
|
||||
void fillChunkRaw(int worldX, int worldZ, Object[] target);
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
package art.arcane.iris.core.pregenerator;
|
||||
|
||||
import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.volmlib.util.mantle.runtime.Mantle;
|
||||
import art.arcane.volmlib.util.math.Position2;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class IrisPregeneratorInitTest {
|
||||
@Test
|
||||
public void initDoesNotSaveBeforeGenerationStarts() throws Exception {
|
||||
IrisSettings previousSettings = IrisSettings.settings;
|
||||
IrisSettings.settings = new IrisSettings();
|
||||
TrackingPregeneratorMethod method = new TrackingPregeneratorMethod();
|
||||
PregenTask task = PregenTask.builder()
|
||||
.center(new Position2(0, 0))
|
||||
.radiusX(16)
|
||||
.radiusZ(16)
|
||||
.build();
|
||||
try {
|
||||
IrisPregenerator pregenerator = new IrisPregenerator(task, method, new NoOpPregenListener());
|
||||
Method initMethod = IrisPregenerator.class.getDeclaredMethod("init");
|
||||
initMethod.setAccessible(true);
|
||||
|
||||
initMethod.invoke(pregenerator);
|
||||
|
||||
assertEquals(1, method.initCalls.get());
|
||||
assertEquals(0, method.saveCalls.get());
|
||||
} finally {
|
||||
IrisSettings.settings = previousSettings;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TrackingPregeneratorMethod implements PregeneratorMethod {
|
||||
private final AtomicInteger initCalls = new AtomicInteger();
|
||||
private final AtomicInteger saveCalls = new AtomicInteger();
|
||||
|
||||
@Override
|
||||
public void init() {
|
||||
initCalls.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
saveCalls.incrementAndGet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsRegions(int x, int z, PregenListener listener) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getMethod(int x, int z) {
|
||||
return "test";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateRegion(int x, int z, PregenListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generateChunk(int x, int z, PregenListener listener) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mantle getMantle() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class NoOpPregenListener implements PregenListener {
|
||||
@Override
|
||||
public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, long generated, long totalChunks, long chunksRemaining, long eta, long elapsed, String method, boolean cached) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkGenerating(int x, int z) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkGenerated(int x, int z, boolean cached) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRegionGenerated(int x, int z) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRegionGenerating(int x, int z) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkCleaned(int x, int z) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRegionSkipped(int x, int z) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkStarted(int x, int z) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkFailed(int x, int z) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkReclaim(int revert) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkGeneratedChunk(int x, int z) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkDownloaded(int x, int z) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaving() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkExistsInRegionGen(int x, int z) {
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -29,4 +29,16 @@ public class AsyncPregenMethodConcurrencyCapTest {
|
||||
assertEquals(16, AsyncPregenMethod.applyRuntimeConcurrencyCap(256, false, 8));
|
||||
assertEquals(20, AsyncPregenMethod.applyRuntimeConcurrencyCap(20, false, 40));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void paperLikeConcurrencyUsesChunkWorkerPoolWhenAvailable() {
|
||||
assertEquals(4, AsyncPregenMethod.resolvePaperLikeConcurrencyWorkerThreads(4, 16, 32));
|
||||
assertEquals(24, AsyncPregenMethod.resolvePaperLikeConcurrencyWorkerThreads(-1, 16, 24));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void foliaConcurrencyStillUsesBroaderRuntimeCapacity() {
|
||||
assertEquals(32, AsyncPregenMethod.resolveFoliaConcurrencyWorkerThreads(4, 16, 32));
|
||||
assertEquals(16, AsyncPregenMethod.resolveFoliaConcurrencyWorkerThreads(-1, 16, 12));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package art.arcane.iris.core.service;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class IrisEngineSVCTest {
|
||||
@Test
|
||||
public void maintenanceSkipsReductionWhenPregenDoesNotTargetWorld() {
|
||||
assertTrue(IrisEngineSVC.shouldSkipMantleReductionForMaintenance(true, false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maintenanceDoesNotSkipReductionForActivePregenWorld() {
|
||||
assertFalse(IrisEngineSVC.shouldSkipMantleReductionForMaintenance(true, true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noMaintenanceNeverSkipsReduction() {
|
||||
assertFalse(IrisEngineSVC.shouldSkipMantleReductionForMaintenance(false, false));
|
||||
assertFalse(IrisEngineSVC.shouldSkipMantleReductionForMaintenance(false, true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.util.project.interpolation.InterpolationMethod3D;
|
||||
import art.arcane.iris.util.project.interpolation.IrisInterpolation;
|
||||
import art.arcane.iris.util.project.noise.HexJamesNoise;
|
||||
import art.arcane.iris.util.project.noise.HexRandomSizeNoise;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.function.NoiseProvider3;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class IrisMathNoiseHotPathParityTest {
|
||||
@Test
|
||||
public void hexNoiseSamplesRemainBitExact() {
|
||||
HexRandomSizeNoise randomSize = new HexRandomSizeNoise(12345L);
|
||||
HexJamesNoise james = new HexJamesNoise(54321L);
|
||||
|
||||
assertEquals(0.4670864519829246D, randomSize.noise(12.5D, -8.25D), 0D);
|
||||
assertEquals(0.4935590260372404D, randomSize.noise(-31.75D, 44.5D, 9.125D), 0D);
|
||||
assertEquals(0.5185375921658786D, james.noise(12.5D, -8.25D), 0D);
|
||||
assertEquals(0.4557738681128938D, james.noise(-31.75D, 44.5D, 9.125D), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generatorNoiseSamplesRemainBitExact() {
|
||||
IrisNoiseGenerator noiseGenerator = new IrisNoiseGenerator()
|
||||
.setZoom(7.25D)
|
||||
.setOffsetX(1.75D)
|
||||
.setOffsetZ(-3.5D)
|
||||
.setOpacity(0.82D)
|
||||
.setStyle(NoiseStyle.PERLIN_IRIS.style())
|
||||
.setExponent(1.15D);
|
||||
|
||||
assertEquals(0.3886291844359823D, noiseGenerator.getNoise(9988L, 31.25D, -17.5D, null), 0D);
|
||||
|
||||
IrisGeneratorStyle style = NoiseStyle.IRIS_DOUBLE.style();
|
||||
assertEquals(0.8640377573263068D, style.createNoCache(new RNG(112233L), null).noiseFast2D(42.5D, -19.75D), 0D);
|
||||
|
||||
IrisGenerator generator = new IrisGenerator()
|
||||
.setZoom(9.5D)
|
||||
.setOpacity(0.91D)
|
||||
.setComposite(new KList<IrisNoiseGenerator>().qadd(noiseGenerator));
|
||||
|
||||
assertEquals(0.451949817597527D, generator.getHeight(63.0D, -27.0D, 445566L), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void interpolationSamplesRemainBitExact() {
|
||||
NoiseProvider3 provider = (x, y, z) -> {
|
||||
double angle = (x * 0.017D) + (y * 0.011D) + (z * 0.023D);
|
||||
return 0.5D + (Math.sin(angle) * 0.25D);
|
||||
};
|
||||
|
||||
assertEquals(
|
||||
0.5231950552025616D,
|
||||
IrisInterpolation.getNoise3D(InterpolationMethod3D.TRILINEAR, 5, 7, -3, 2.5D, 3.5D, 4.5D, provider),
|
||||
0D
|
||||
);
|
||||
assertEquals(
|
||||
0.5259208842929466D,
|
||||
IrisInterpolation.getNoise3D(InterpolationMethod3D.TRICUBIC, 5, 7, -3, 2.5D, 3.5D, 4.5D, provider),
|
||||
0D
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,16 @@ public class CNGFastPathParityTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void signedFastPathMatchesLegacyAcrossRepresentativeGenerators() {
|
||||
for (long seed = 31L; seed <= 35L; seed++) {
|
||||
List<CNG> generators = createSignedGenerators(seed);
|
||||
for (int index = 0; index < generators.size(); index++) {
|
||||
assertSignedFastPathParity("signed-seed-" + seed + "-case-" + index, generators.get(index));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void assertFastPathParity(String label, CNG generator) {
|
||||
for (int x = -320; x <= 320; x += 19) {
|
||||
for (int z = -320; z <= 320; z += 23) {
|
||||
@@ -47,6 +57,26 @@ public class CNGFastPathParityTest {
|
||||
}
|
||||
}
|
||||
|
||||
private void assertSignedFastPathParity(String label, CNG generator) {
|
||||
for (int x = -320; x <= 320; x += 19) {
|
||||
for (int z = -320; z <= 320; z += 23) {
|
||||
double expected = (generator.noiseFast2D(x, z) * 2D) - 1D;
|
||||
double actual = generator.noiseFastSigned2D(x, z);
|
||||
assertEquals(label + " signed-2D x=" + x + " z=" + z, expected, actual, 1.0E-12D);
|
||||
}
|
||||
}
|
||||
|
||||
for (int x = -128; x <= 128; x += 17) {
|
||||
for (int y = -96; y <= 96; y += 13) {
|
||||
for (int z = -128; z <= 128; z += 19) {
|
||||
double expected = (generator.noiseFast3D(x, y, z) * 2D) - 1D;
|
||||
double actual = generator.noiseFastSigned3D(x, y, z);
|
||||
assertEquals(label + " signed-3D x=" + x + " y=" + y + " z=" + z, expected, actual, 1.0E-12D);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private CNG createIdentityGenerator(long seed) {
|
||||
DeterministicNoiseGenerator generator = new DeterministicNoiseGenerator(0.31D + (seed * 0.01D));
|
||||
return new CNG(new RNG(seed), generator, 1D, 1).bake();
|
||||
@@ -72,6 +102,24 @@ public class CNGFastPathParityTest {
|
||||
return generators;
|
||||
}
|
||||
|
||||
private List<CNG> createSignedGenerators(long seed) {
|
||||
List<CNG> generators = new ArrayList<>();
|
||||
|
||||
generators.add(new CNG(new RNG(seed), new SimplexNoise(seed + 100L), 1D, 1).bake());
|
||||
generators.add(new CNG(new RNG(seed + 1L), new FractalFBMSimplexNoise(seed + 101L), 1D, 1).bake());
|
||||
generators.add(new CNG(new RNG(seed + 2L), new VascularNoise(seed + 102L), 1D, 1).bake());
|
||||
generators.add(new CNG(new RNG(seed + 3L), new OffsetNoiseGenerator(new SimplexNoise(seed + 103L), seed + 104L), 1D, 1).bake());
|
||||
generators.add(new CNG(new RNG(seed + 4L), new OffsetNoiseGenerator(new PerlinNoise(seed + 107L), seed + 108L), 1D, 1).bake());
|
||||
generators.add(new CNG(new RNG(seed + 5L), new OffsetNoiseGenerator(new VascularNoise(seed + 109L), seed + 110L), 1D, 1).bake());
|
||||
generators.add(new CNG(new RNG(seed + 6L), new OffsetNoiseGenerator(new FractalFBMSimplexNoise(seed + 111L), seed + 112L), 1D, 1).bake());
|
||||
|
||||
CNG fractured = new CNG(new RNG(seed + 7L), new OffsetNoiseGenerator(new FractalFBMSimplexNoise(seed + 105L), seed + 106L), 1D, 1).bake();
|
||||
fractured.fractureWith(createIdentityGenerator(seed + 500L), 9.5D);
|
||||
generators.add(fractured);
|
||||
|
||||
return generators;
|
||||
}
|
||||
|
||||
private static class DeterministicNoiseGenerator implements NoiseGenerator {
|
||||
private final double offset;
|
||||
|
||||
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
package art.arcane.iris.util.project.noise;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class FastNoiseDoubleCellularParityTest {
|
||||
private static final FastNoiseDouble.CellularDistanceFunction[] DISTANCE_FUNCTIONS = new FastNoiseDouble.CellularDistanceFunction[]{
|
||||
FastNoiseDouble.CellularDistanceFunction.Euclidean,
|
||||
FastNoiseDouble.CellularDistanceFunction.Manhattan,
|
||||
FastNoiseDouble.CellularDistanceFunction.Natural
|
||||
};
|
||||
private static final FastNoiseDouble.CellularReturnType[] TWO_EDGE_RETURN_TYPES = new FastNoiseDouble.CellularReturnType[]{
|
||||
FastNoiseDouble.CellularReturnType.Distance2,
|
||||
FastNoiseDouble.CellularReturnType.Distance2Add,
|
||||
FastNoiseDouble.CellularReturnType.Distance2Sub,
|
||||
FastNoiseDouble.CellularReturnType.Distance2Mul,
|
||||
FastNoiseDouble.CellularReturnType.Distance2Div
|
||||
};
|
||||
private static final Method HASH_2D = getDeclaredMethod("hash2D", long.class, long.class, long.class);
|
||||
private static final Method HASH_3D = getDeclaredMethod("hash3D", long.class, long.class, long.class, long.class);
|
||||
private static final double[] CELL_2D_X = getDeclaredDoubleArray("CELL_2D_X");
|
||||
private static final double[] CELL_2D_Y = getDeclaredDoubleArray("CELL_2D_Y");
|
||||
private static final double[] CELL_3D_X = getDeclaredDoubleArray("CELL_3D_X");
|
||||
private static final double[] CELL_3D_Y = getDeclaredDoubleArray("CELL_3D_Y");
|
||||
private static final double[] CELL_3D_Z = getDeclaredDoubleArray("CELL_3D_Z");
|
||||
|
||||
@Test
|
||||
public void cellular2DTwoEdgeModesMatchLegacyUpdateLogic() throws Exception {
|
||||
for (long seed = 11L; seed <= 17L; seed++) {
|
||||
double frequency = 0.037D + (seed * 0.001D);
|
||||
for (FastNoiseDouble.CellularDistanceFunction distanceFunction : DISTANCE_FUNCTIONS) {
|
||||
for (FastNoiseDouble.CellularReturnType returnType : TWO_EDGE_RETURN_TYPES) {
|
||||
FastNoiseDouble noise = new FastNoiseDouble(seed);
|
||||
noise.setFrequency(frequency);
|
||||
noise.setCellularDistanceFunction(distanceFunction);
|
||||
noise.setCellularReturnType(returnType);
|
||||
for (int x = -96; x <= 96; x += 11) {
|
||||
for (int y = -96; y <= 96; y += 13) {
|
||||
double sampleX = x + 0.37D;
|
||||
double sampleY = y - 0.41D;
|
||||
double expected = legacyGetCellular2D(seed, frequency, distanceFunction, returnType, sampleX, sampleY);
|
||||
double actual = noise.GetCellular(sampleX, sampleY);
|
||||
assertEquals("2D seed=" + seed + " distance=" + distanceFunction + " return=" + returnType + " x=" + sampleX + " y=" + sampleY, expected, actual, 0D);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cellular3DTwoEdgeModesMatchLegacyUpdateLogic() throws Exception {
|
||||
for (long seed = 31L; seed <= 35L; seed++) {
|
||||
double frequency = 0.029D + (seed * 0.001D);
|
||||
for (FastNoiseDouble.CellularDistanceFunction distanceFunction : DISTANCE_FUNCTIONS) {
|
||||
for (FastNoiseDouble.CellularReturnType returnType : TWO_EDGE_RETURN_TYPES) {
|
||||
FastNoiseDouble noise = new FastNoiseDouble(seed);
|
||||
noise.setFrequency(frequency);
|
||||
noise.setCellularDistanceFunction(distanceFunction);
|
||||
noise.setCellularReturnType(returnType);
|
||||
for (int x = -48; x <= 48; x += 9) {
|
||||
for (int y = -32; y <= 32; y += 7) {
|
||||
for (int z = -48; z <= 48; z += 11) {
|
||||
double sampleX = x + 0.19D;
|
||||
double sampleY = y - 0.27D;
|
||||
double sampleZ = z + 0.43D;
|
||||
double expected = legacyGetCellular3D(seed, frequency, distanceFunction, returnType, sampleX, sampleY, sampleZ);
|
||||
double actual = noise.GetCellular(sampleX, sampleY, sampleZ);
|
||||
assertEquals("3D seed=" + seed + " distance=" + distanceFunction + " return=" + returnType + " x=" + sampleX + " y=" + sampleY + " z=" + sampleZ, expected, actual, 0D);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private double legacyGetCellular2D(long seed, double frequency, FastNoiseDouble.CellularDistanceFunction distanceFunction, FastNoiseDouble.CellularReturnType returnType, double x, double y) throws Exception {
|
||||
double scaledX = x * frequency;
|
||||
double scaledY = y * frequency;
|
||||
long xr = fastRound(scaledX);
|
||||
long yr = fastRound(scaledY);
|
||||
double distance = 999999D;
|
||||
double distance2 = 999999D;
|
||||
|
||||
for (long xi = xr - 1; xi <= xr + 1; xi++) {
|
||||
for (long yi = yr - 1; yi <= yr + 1; yi++) {
|
||||
int cellIndex = (int) hash2D(seed, xi, yi) & 255;
|
||||
double vecX = xi - scaledX + CELL_2D_X[cellIndex];
|
||||
double vecY = yi - scaledY + CELL_2D_Y[cellIndex];
|
||||
double newDistance = switch (distanceFunction) {
|
||||
case Euclidean -> vecX * vecX + vecY * vecY;
|
||||
case Manhattan -> Math.abs(vecX) + Math.abs(vecY);
|
||||
case Natural -> (Math.abs(vecX) + Math.abs(vecY)) + (vecX * vecX + vecY * vecY);
|
||||
};
|
||||
distance2 = Math.max(Math.min(distance2, newDistance), distance);
|
||||
distance = Math.min(distance, newDistance);
|
||||
}
|
||||
}
|
||||
|
||||
return applyTwoEdgeReturn(distance, distance2, returnType);
|
||||
}
|
||||
|
||||
private double legacyGetCellular3D(long seed, double frequency, FastNoiseDouble.CellularDistanceFunction distanceFunction, FastNoiseDouble.CellularReturnType returnType, double x, double y, double z) throws Exception {
|
||||
double scaledX = x * frequency;
|
||||
double scaledY = y * frequency;
|
||||
double scaledZ = z * frequency;
|
||||
long xr = fastRound(scaledX);
|
||||
long yr = fastRound(scaledY);
|
||||
long zr = fastRound(scaledZ);
|
||||
double distance = 999999D;
|
||||
double distance2 = 999999D;
|
||||
|
||||
for (long xi = xr - 1; xi <= xr + 1; xi++) {
|
||||
for (long yi = yr - 1; yi <= yr + 1; yi++) {
|
||||
for (long zi = zr - 1; zi <= zr + 1; zi++) {
|
||||
int cellIndex = (int) hash3D(seed, xi, yi, zi) & 255;
|
||||
double vecX = xi - scaledX + CELL_3D_X[cellIndex];
|
||||
double vecY = yi - scaledY + CELL_3D_Y[cellIndex];
|
||||
double vecZ = zi - scaledZ + CELL_3D_Z[cellIndex];
|
||||
double newDistance = switch (distanceFunction) {
|
||||
case Euclidean -> vecX * vecX + vecY * vecY + vecZ * vecZ;
|
||||
case Manhattan -> Math.abs(vecX) + Math.abs(vecY) + Math.abs(vecZ);
|
||||
case Natural -> (Math.abs(vecX) + Math.abs(vecY) + Math.abs(vecZ)) + (vecX * vecX + vecY * vecY + vecZ * vecZ);
|
||||
};
|
||||
distance2 = Math.max(Math.min(distance2, newDistance), distance);
|
||||
distance = Math.min(distance, newDistance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return applyTwoEdgeReturn(distance, distance2, returnType);
|
||||
}
|
||||
|
||||
private double applyTwoEdgeReturn(double distance, double distance2, FastNoiseDouble.CellularReturnType returnType) {
|
||||
return switch (returnType) {
|
||||
case Distance2 -> distance2 - 1D;
|
||||
case Distance2Add -> distance2 + distance - 1D;
|
||||
case Distance2Sub -> distance2 - distance - 1D;
|
||||
case Distance2Mul -> distance2 * distance - 1D;
|
||||
case Distance2Div -> distance / distance2 - 1D;
|
||||
default -> 0D;
|
||||
};
|
||||
}
|
||||
|
||||
private static long fastRound(double value) {
|
||||
return value >= 0D ? (long) (value + 0.5D) : (long) (value - 0.5D);
|
||||
}
|
||||
|
||||
private static long hash2D(long seed, long x, long y) throws Exception {
|
||||
Long value = (Long) HASH_2D.invoke(null, seed, x, y);
|
||||
return value.longValue();
|
||||
}
|
||||
|
||||
private static long hash3D(long seed, long x, long y, long z) throws Exception {
|
||||
Long value = (Long) HASH_3D.invoke(null, seed, x, y, z);
|
||||
return value.longValue();
|
||||
}
|
||||
|
||||
private static Method getDeclaredMethod(String name, Class<?>... parameterTypes) {
|
||||
try {
|
||||
Method method = FastNoiseDouble.class.getDeclaredMethod(name, parameterTypes);
|
||||
method.setAccessible(true);
|
||||
return method;
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static double[] getDeclaredDoubleArray(String name) {
|
||||
try {
|
||||
Field field = FastNoiseDouble.class.getDeclaredField(name);
|
||||
field.setAccessible(true);
|
||||
return (double[]) field.get(null);
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user