mirror of
https://github.com/XevianLight/Aphelion.git
synced 2026-05-11 01:50:56 +01:00
Debug rendering for oxygen (VERY LAGGY BUT WORKS)
This commit is contained in:
189
src/main/java/net/xevianlight/aphelion/util/TechnoFloodFill.java
Normal file
189
src/main/java/net/xevianlight/aphelion/util/TechnoFloodFill.java
Normal file
@@ -0,0 +1,189 @@
|
||||
package net.xevianlight.aphelion.util;
|
||||
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Standalone flood-fill utility.
|
||||
* - Traverses blocks starting from origin
|
||||
* - Visits only positions allowed by Passable predicate
|
||||
* - Bounded by maxRange (Manhattan distance)
|
||||
* - Returns all visited positions (excluding origin by default)
|
||||
*/
|
||||
public final class TechnoFloodFill {
|
||||
|
||||
private TechnoFloodFill() {}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Passable {
|
||||
boolean test(Level level, BlockPos pos, BlockState state);
|
||||
}
|
||||
|
||||
/** Convenience predicate: treat air as passable. */
|
||||
public static final Passable AIR_ONLY = (level, pos, state) -> state.isAir();
|
||||
|
||||
/**
|
||||
* Runs a bounded flood fill.
|
||||
*
|
||||
* @param level the world
|
||||
* @param origin starting position (typically BE position)
|
||||
* @param maxRange Manhattan range limit (|dx|+|dy|+|dz|)
|
||||
* @param passable which blocks can be entered/added
|
||||
* @param includeOrigin whether to include origin in the returned list
|
||||
*/
|
||||
public static List<BlockPos> run(Level level, BlockPos origin, int maxRange, Passable passable, boolean includeOrigin) {
|
||||
if (level == null) return List.of();
|
||||
|
||||
// Choose a grid size big enough to cover maxRange in all axes.
|
||||
// We need coordinates within [-maxRange, +maxRange] around origin.
|
||||
// So size must be >= (2*maxRange + 1). Next power-of-two for cheap indexing.
|
||||
int needed = 2 * maxRange + 1;
|
||||
int sizePow2 = nextPow2(needed);
|
||||
int bits = Integer.numberOfTrailingZeros(sizePow2); // since pow2
|
||||
BigVisitedGrid seen = new BigVisitedGrid(bits, origin.getX(), origin.getY(), origin.getZ());
|
||||
|
||||
// Chunk-cached blockstate fetch (no BE state needed)
|
||||
ChunkCache chunkCache = new ChunkCache(level);
|
||||
|
||||
List<BlockPos> out = new ArrayList<>();
|
||||
Deque<BlockPos> stack = new ArrayDeque<>();
|
||||
|
||||
if (includeOrigin) {
|
||||
out.add(origin);
|
||||
}
|
||||
|
||||
// Mark origin visited so we don't bounce back into it.
|
||||
seen.add(origin.getX(), origin.getY(), origin.getZ());
|
||||
stack.push(origin);
|
||||
|
||||
while (!stack.isEmpty()) {
|
||||
BlockPos from = stack.pop();
|
||||
|
||||
for (Direction d : Direction.values()) {
|
||||
BlockPos next = from.relative(d);
|
||||
|
||||
if (!inRangeManhattan(origin, next, maxRange)) continue;
|
||||
|
||||
// visited check first: cheapest early-out
|
||||
if (!seen.add(next.getX(), next.getY(), next.getZ())) continue;
|
||||
|
||||
BlockState st = chunkCache.getBlockState(next);
|
||||
if (!passable.test(level, next, st)) continue;
|
||||
|
||||
out.add(next);
|
||||
stack.push(next);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private static boolean inRangeManhattan(BlockPos a, BlockPos b, int max) {
|
||||
int dx = Math.abs(a.getX() - b.getX());
|
||||
int dy = Math.abs(a.getY() - b.getY());
|
||||
int dz = Math.abs(a.getZ() - b.getZ());
|
||||
return dx + dy + dz <= max;
|
||||
}
|
||||
|
||||
private static int nextPow2(int x) {
|
||||
// smallest power of 2 >= x, with a minimum of 8
|
||||
int v = 1;
|
||||
while (v < x) v <<= 1;
|
||||
return Math.max(v, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple chunk cache for repeated reads in flood fill.
|
||||
*/
|
||||
private static final class ChunkCache {
|
||||
private final Level level;
|
||||
private LevelChunk lastChunk;
|
||||
private int lastCx = Integer.MIN_VALUE;
|
||||
private int lastCz = Integer.MIN_VALUE;
|
||||
|
||||
private ChunkCache(Level level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
BlockState getBlockState(BlockPos pos) {
|
||||
int cx = pos.getX() >> 4;
|
||||
int cz = pos.getZ() >> 4;
|
||||
|
||||
if (cx != lastCx || cz != lastCz || lastChunk == null) {
|
||||
lastChunk = level.getChunk(cx, cz); // may load/generate; okay for your current usage
|
||||
lastCx = cx;
|
||||
lastCz = cz;
|
||||
}
|
||||
return lastChunk.getBlockState(pos);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Packed visited grid centered on an origin.
|
||||
*
|
||||
* Uses an int[] with 32 bits per word:
|
||||
* - size = 2^bits (must be power-of-two)
|
||||
* - Word index layout: ((y * size) + z) * wordsPerRow + (x / 32)
|
||||
* - Bit inside the word: (x % 32)
|
||||
*/
|
||||
private static final class BigVisitedGrid {
|
||||
private final int bits;
|
||||
private final int size;
|
||||
private final int wordsPerRow;
|
||||
private final int[] words;
|
||||
private final int xOff, yOff, zOff;
|
||||
|
||||
BigVisitedGrid(int bits, int xOrigin, int yOrigin, int zOrigin) {
|
||||
if (bits < 3) throw new IllegalArgumentException("Grid too small (bits=" + bits + ")");
|
||||
if (bits > 12) throw new IllegalArgumentException("Grid too large (bits=" + bits + ")");
|
||||
|
||||
this.bits = bits;
|
||||
this.size = 1 << bits;
|
||||
|
||||
// Center origin at middle of grid
|
||||
this.xOff = -xOrigin + (size / 2);
|
||||
this.yOff = -yOrigin + (size / 2);
|
||||
this.zOff = -zOrigin + (size / 2);
|
||||
|
||||
if ((size & 31) != 0) {
|
||||
// to keep wordsPerRow integer
|
||||
throw new IllegalArgumentException("Grid size must be divisible by 32, got " + size);
|
||||
}
|
||||
|
||||
this.wordsPerRow = size >>> 5; // size / 32
|
||||
int totalWords = wordsPerRow * size * size; // (y,z) rows * x-words
|
||||
this.words = new int[totalWords];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if it was NOT previously visited and is now marked visited
|
||||
*/
|
||||
boolean add(int x, int y, int z) {
|
||||
int inX = x + xOff;
|
||||
int inY = y + yOff;
|
||||
int inZ = z + zOff;
|
||||
|
||||
// Bounds check (fast and safe)
|
||||
if ((inX | inY | inZ) < 0 || inX >= size || inY >= size || inZ >= size) {
|
||||
return false; // out of grid => treat as "already seen" to prevent expansion
|
||||
}
|
||||
|
||||
int wordIndex = ((inY * size) + inZ) * wordsPerRow + (inX >>> 5);
|
||||
int bit = 1 << (inX & 31);
|
||||
|
||||
int prev = words[wordIndex];
|
||||
if ((prev & bit) != 0) return false;
|
||||
|
||||
words[wordIndex] = prev | bit;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user