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:
@@ -1,16 +1,23 @@
|
||||
package net.xevianlight.aphelion;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
import net.neoforged.neoforge.client.event.ClientTickEvent;
|
||||
import net.neoforged.neoforge.client.event.EntityRenderersEvent;
|
||||
import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent;
|
||||
import net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent;
|
||||
import net.neoforged.neoforge.event.AddReloadListenerEvent;
|
||||
import net.neoforged.neoforge.event.tick.ServerTickEvent;
|
||||
import net.neoforged.neoforge.network.PacketDistributor;
|
||||
import net.xevianlight.aphelion.block.dummy.renderer.MultiblockDummyRenderer;
|
||||
import net.xevianlight.aphelion.block.entity.custom.renderer.OxygenTestRenderer;
|
||||
import net.xevianlight.aphelion.client.AphelionConfig;
|
||||
import net.xevianlight.aphelion.core.saveddata.EnvironmentSavedData;
|
||||
import net.xevianlight.aphelion.network.packet.PartitionPayload;
|
||||
import net.xevianlight.aphelion.planet.AphelionPlanetJSONLoader;
|
||||
import net.xevianlight.aphelion.core.init.*;
|
||||
import net.xevianlight.aphelion.fluid.BaseFluidType;
|
||||
@@ -132,7 +139,7 @@ public class Aphelion {
|
||||
@SubscribeEvent
|
||||
public static void registerBER(EntityRenderersEvent.RegisterRenderers event) {
|
||||
event.registerBlockEntityRenderer(ModBlockEntities.VAF_MULTIBLOCK_DUMMY_ENTITY.get(), MultiblockDummyRenderer::new);
|
||||
event.registerBlockEntityRenderer(ModBlockEntities.OXYGEN_TEST_BLOCK_ENTITY.get(), OxygenTestRenderer::new);
|
||||
// event.registerBlockEntityRenderer(ModBlockEntities.OXYGEN_TEST_BLOCK_ENTITY.get(), OxygenTestRenderer::new);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
@@ -146,5 +153,10 @@ public class Aphelion {
|
||||
public static void registerRenderers(EntityRenderersEvent.RegisterRenderers event) {
|
||||
event.registerEntityRenderer(ModEntities.ROCKET.get(), RocketRenderer::new);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onClientTick(ClientTickEvent.Post e) {
|
||||
EnvironmentSavedData.refreshFromIntegratedServerIfNeeded(Minecraft.getInstance(), 64, 10000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,34 @@
|
||||
package net.xevianlight.aphelion.block.custom;
|
||||
|
||||
import com.mojang.serialization.MapCodec;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.BaseEntityBlock;
|
||||
import net.minecraft.world.level.block.Block;
|
||||
import net.minecraft.world.level.block.EntityBlock;
|
||||
import net.minecraft.world.level.block.RenderShape;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityTicker;
|
||||
import net.minecraft.world.level.block.entity.BlockEntityType;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import net.xevianlight.aphelion.block.entity.custom.OxygenTestBlockEntity;
|
||||
import net.xevianlight.aphelion.block.entity.custom.TestBlockEntity;
|
||||
import net.xevianlight.aphelion.core.init.ModBlockEntities;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class OxygenTestBlock extends Block implements EntityBlock {
|
||||
public class OxygenTestBlock extends BaseEntityBlock {
|
||||
|
||||
public OxygenTestBlock(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
public static final MapCodec<OxygenTestBlock> CODEC = simpleCodec(OxygenTestBlock::new);
|
||||
|
||||
@Override
|
||||
protected MapCodec<? extends BaseEntityBlock> codec() {
|
||||
return CODEC;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable BlockEntity newBlockEntity(BlockPos blockPos, BlockState blockState) {
|
||||
return new OxygenTestBlockEntity(blockPos, blockState);
|
||||
@@ -22,4 +37,29 @@ public class OxygenTestBlock extends Block implements EntityBlock {
|
||||
public static Properties getProperties() {
|
||||
return Properties.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level level, BlockState state, BlockEntityType<T> blockEntityType) {
|
||||
if (level.isClientSide) {
|
||||
return null;
|
||||
}
|
||||
return createTickerHelper(blockEntityType, ModBlockEntities.OXYGEN_TEST_BLOCK_ENTITY.get(), (level1, blockPos, blockState, oxygenTestBlockEntity) -> oxygenTestBlockEntity.tick(level1, blockPos, blockState));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public RenderShape getRenderShape(BlockState pState) {
|
||||
return RenderShape.MODEL;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRemove(BlockState state, Level level, BlockPos pos, BlockState newState, boolean movedByPiston) {
|
||||
if (state.getBlock() != newState.getBlock()) {
|
||||
BlockEntity blockEntity= level.getBlockEntity(pos);
|
||||
if (blockEntity instanceof OxygenTestBlockEntity oxygenTestBlockEntity) {
|
||||
oxygenTestBlockEntity.removeEnclosed();
|
||||
}
|
||||
}
|
||||
super.onRemove(state, level, pos, newState, movedByPiston);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
package net.xevianlight.aphelion.block.entity.custom;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectArrayMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.SectionPos;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||
import net.minecraft.world.level.block.state.BlockState;
|
||||
import static net.xevianlight.aphelion.Aphelion.LOGGER;
|
||||
|
||||
import net.minecraft.world.level.chunk.ChunkAccess;
|
||||
import net.minecraft.world.level.chunk.LevelChunk;
|
||||
import net.xevianlight.aphelion.core.init.ModBlockEntities;
|
||||
import net.xevianlight.aphelion.core.saveddata.EnvironmentSavedData;
|
||||
import net.xevianlight.aphelion.util.FloodFill3D;
|
||||
import org.openjdk.nashorn.internal.runtime.regexp.joni.exception.ValueException;
|
||||
|
||||
import java.util.*;
|
||||
@@ -43,11 +42,25 @@ public class OxygenTestBlockEntity extends BlockEntity {
|
||||
return level != null && fastBlockState(pos).isAir();
|
||||
}
|
||||
|
||||
public static final int MAX_RANGE = 100;
|
||||
public static final int MAX_RANGE = 16;
|
||||
public boolean isInRange(BlockPos pos1, BlockPos pos2) {
|
||||
return Math.abs(pos1.getX() - pos2.getX()) + Math.abs(pos1.getY() - pos2.getY()) + Math.abs(pos1.getZ() - pos2.getZ()) <= MAX_RANGE;
|
||||
}
|
||||
|
||||
public void removeEnclosed() {
|
||||
if (enclosedCache != null) {
|
||||
var singleplayerServer = Minecraft.getInstance().getSingleplayerServer();
|
||||
if (singleplayerServer != null) {
|
||||
var serverLevel = singleplayerServer.getLevel(level.dimension());
|
||||
if (serverLevel != null) {
|
||||
var savedData = EnvironmentSavedData.get(serverLevel);
|
||||
savedData.resetOxygen(serverLevel, enclosedCache);
|
||||
enclosedCache = Set.of();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 256*256*256 grid of booleans
|
||||
private class BigBoolGrid {
|
||||
int bitsSize;
|
||||
@@ -94,7 +107,7 @@ public class OxygenTestBlockEntity extends BlockEntity {
|
||||
// wrapping order is X->Z->Y, so we go along the X axis,
|
||||
// wrap to the Z axis to make a square, and once the square is full
|
||||
// we step up once along the Y axis.
|
||||
int bitPos = inX & 7; // bottom 4 bits of X is bit pos
|
||||
int bitPos = inX & 15; // bottom 4 bits of X is bit pos
|
||||
int bit = (1 << bitPos);
|
||||
|
||||
// First (bitsSize-4) bits are for X, next (bitsSize) bits are for Z, next (bitsSize) bits are for Y
|
||||
@@ -106,22 +119,29 @@ public class OxygenTestBlockEntity extends BlockEntity {
|
||||
}
|
||||
}
|
||||
|
||||
private List<BlockPos> enclosedCache;
|
||||
public List<BlockPos> getEnclosedBlocks() {
|
||||
if (level == null) return List.of();
|
||||
if (enclosedCache != null) return enclosedCache;
|
||||
private Set<BlockPos> enclosedCache;
|
||||
|
||||
|
||||
public Set<BlockPos> getEnclosedCache() {
|
||||
return enclosedCache;
|
||||
}
|
||||
|
||||
public Set<BlockPos> getEnclosedBlocks() {
|
||||
if (level == null) return Set.of();
|
||||
// if (enclosedCache != null) return enclosedCache;
|
||||
|
||||
long start = System.nanoTime();
|
||||
List<BlockPos> enclosedBlocks = new ArrayList<>();
|
||||
// make this bitch BIIIIIG
|
||||
BigBoolGrid seen = new BigBoolGrid(8, this.getBlockPos().getX(), this.getBlockPos().getY(), this.getBlockPos().getZ());
|
||||
Set<BlockPos> enclosedBlocks = FloodFill3D.run(level, getBlockPos(), 6000, FloodFill3D.TEST_FULL_SEAL, true);
|
||||
|
||||
// It's... a reasonable assumption that we won't have to include more blocks at once than the area of a sphere?
|
||||
// maybe a bit more, IDK how exactly it scales to blocks.
|
||||
Stack<BlockPos> stack = new Stack<>();
|
||||
Stack<Integer> radiusStack = new Stack<>();
|
||||
|
||||
stack.add(this.getBlockPos());
|
||||
// // make this bitch BIIIIIG
|
||||
// BigBoolGrid seen = new BigBoolGrid(8, this.getBlockPos().getX(), this.getBlockPos().getY(), this.getBlockPos().getZ());
|
||||
//
|
||||
// // It's... a reasonable assumption that we won't have to include more blocks at once than the area of a sphere?
|
||||
// // maybe a bit more, IDK how exactly it scales to blocks.
|
||||
// Stack<BlockPos> stack = new Stack<>();
|
||||
// Stack<Integer> radiusStack = new Stack<>();
|
||||
//
|
||||
// stack.add(this.getBlockPos());
|
||||
|
||||
// Do flood fill out from this block
|
||||
// Push on the top of the stack (newest), pop from the bottom of the stack (oldest).
|
||||
@@ -130,21 +150,48 @@ public class OxygenTestBlockEntity extends BlockEntity {
|
||||
// and you'd see that every pos of layer 1 is together, then layer 2, then layer 3...
|
||||
|
||||
|
||||
BlockPos ourPos = getBlockPos();
|
||||
while (!stack.isEmpty()) {
|
||||
BlockPos spreadFromPos = stack.pop();
|
||||
for (Direction d : Direction.values()) {
|
||||
BlockPos relativePos = spreadFromPos.relative(d);
|
||||
// BlockPos ourPos = getBlockPos();
|
||||
// while (!stack.isEmpty()) {
|
||||
// BlockPos spreadFromPos = stack.pop();
|
||||
// for (Direction d : Direction.values()) {
|
||||
// BlockPos relativePos = spreadFromPos.relative(d);
|
||||
//
|
||||
// if (isInRange(relativePos, ourPos) && canSpreadTo(relativePos)) {
|
||||
// // seen.add runs seen.contains under the hood,
|
||||
// // + this is the most expensive operation.
|
||||
// // should save a lot of time!
|
||||
// if (seen.add(relativePos.getX(), relativePos.getY(), relativePos.getZ())) {
|
||||
// enclosedBlocks.add(relativePos);
|
||||
// stack.add(relativePos);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
if (isInRange(relativePos, ourPos) && canSpreadTo(relativePos)) {
|
||||
// seen.add runs seen.contains under the hood,
|
||||
// + this is the most expensive operation.
|
||||
// should save a lot of time!
|
||||
if (seen.add(relativePos.getX(), relativePos.getY(), relativePos.getZ())) {
|
||||
enclosedBlocks.add(relativePos);
|
||||
stack.add(relativePos);
|
||||
var singleplayerServer = Minecraft.getInstance().getSingleplayerServer();
|
||||
if (singleplayerServer != null) {
|
||||
var serverLevel = singleplayerServer.getLevel(level.dimension());
|
||||
if (serverLevel != null) {
|
||||
|
||||
// Build a set of longs for the newly computed blocks (order-independent)
|
||||
boolean changed = isChanged(enclosedBlocks);
|
||||
// boolean changed = false;
|
||||
if (changed) {
|
||||
var savedData = EnvironmentSavedData.get(serverLevel);
|
||||
|
||||
// Revert old affected area back to defaults
|
||||
if (enclosedCache != null) {
|
||||
savedData.resetOxygen(serverLevel, enclosedCache);
|
||||
}
|
||||
|
||||
// Apply oxygen to new affected area
|
||||
savedData.setOxygen(serverLevel, enclosedBlocks, true);
|
||||
|
||||
LOGGER.info("Saved data for {} blocks to leveldata", enclosedBlocks.size());
|
||||
}
|
||||
|
||||
// Update the cache no matter what (so next compare is correct)
|
||||
enclosedCache = enclosedBlocks;
|
||||
}
|
||||
}
|
||||
long durationNanos = System.nanoTime() - start;
|
||||
@@ -152,12 +199,49 @@ public class OxygenTestBlockEntity extends BlockEntity {
|
||||
LOGGER.info("Flood fill completed in {}µs, {} blocks at {}µs/block", durationMicros, enclosedBlocks.size(), durationMicros / enclosedBlocks.size());
|
||||
|
||||
enclosedCache = enclosedBlocks;
|
||||
return enclosedBlocks;
|
||||
|
||||
return enclosedCache;
|
||||
}
|
||||
|
||||
private boolean isChanged(Set<BlockPos> enclosedBlocks) {
|
||||
LongOpenHashSet newSet = new LongOpenHashSet(enclosedBlocks.size());
|
||||
for (BlockPos p : enclosedBlocks) {
|
||||
newSet.add(p.asLong());
|
||||
}
|
||||
|
||||
// Build a set of longs for the cached blocks (if any)
|
||||
LongOpenHashSet oldSet = null;
|
||||
if (enclosedCache != null) {
|
||||
oldSet = new LongOpenHashSet(enclosedCache.size());
|
||||
for (BlockPos p : enclosedCache) {
|
||||
oldSet.add(p.asLong());
|
||||
}
|
||||
}
|
||||
|
||||
// Only save if the set of affected blocks has changed
|
||||
boolean changed = (oldSet == null) || !oldSet.equals(newSet);
|
||||
return changed;
|
||||
}
|
||||
|
||||
private void helper() {
|
||||
var myVar = new BlockPos(1, 1, 1).hashCode();
|
||||
}
|
||||
|
||||
int ticks = 0;
|
||||
int refreshAfter = 20;
|
||||
|
||||
|
||||
|
||||
public void tick(Level level, BlockPos pos, BlockState blockState) {
|
||||
if (level.isClientSide) return;
|
||||
ticks++;
|
||||
if (ticks >= refreshAfter) {
|
||||
getEnclosedBlocks();
|
||||
ticks = 0;
|
||||
|
||||
// UNCOMMENT FOR DEBUG ONLY!!! EXTREMELY TPS INTENSIVE!!!
|
||||
// EnvironmentSavedData.refreshFromIntegratedServerIfNeeded(Minecraft.getInstance(), 64, 10000);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.minecraft.core.Vec3i;
|
||||
import net.minecraft.world.phys.AABB;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.block.entity.custom.OxygenTestBlockEntity;
|
||||
import org.joml.Matrix4f;
|
||||
import org.joml.Vector3f;
|
||||
@@ -23,7 +24,10 @@ public class OxygenTestRenderer implements BlockEntityRenderer<OxygenTestBlockEn
|
||||
public OxygenTestRenderer (BlockEntityRendererProvider.Context context) {}
|
||||
|
||||
private List<BlockPos> toBlockPositions(OxygenTestBlockEntity be) {
|
||||
return be.getEnclosedBlocks();
|
||||
// cache = be.getEnclosedBlocks();
|
||||
// if (cache != null)
|
||||
// return cache;
|
||||
return List.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -31,6 +35,8 @@ public class OxygenTestRenderer implements BlockEntityRenderer<OxygenTestBlockEn
|
||||
return AABB.ofSize(blockEntity.getBlockPos().getCenter(), OxygenTestBlockEntity.MAX_RANGE*2, OxygenTestBlockEntity.MAX_RANGE*2, OxygenTestBlockEntity.MAX_RANGE*2);
|
||||
}
|
||||
|
||||
List<BlockPos> cache;
|
||||
|
||||
private Set<BlockPos> relativePositionsCache;
|
||||
@Override
|
||||
// If in debug mode, renders a model made from the blocks
|
||||
@@ -42,7 +48,7 @@ public class OxygenTestRenderer implements BlockEntityRenderer<OxygenTestBlockEn
|
||||
// Renderers are relative to our block pos, so transform all points to be relative to block pos as well
|
||||
List<BlockPos> positionsToRender = toBlockPositions(be);
|
||||
BlockPos originPos = be.getBlockPos();
|
||||
if (true) return;
|
||||
// if (true) return;
|
||||
|
||||
Set<BlockPos> relativePositions;
|
||||
if (relativePositionsCache != null) relativePositions = relativePositionsCache;
|
||||
|
||||
@@ -2,6 +2,8 @@ package net.xevianlight.aphelion.client;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.dimension.DimensionType;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
@@ -11,6 +13,7 @@ import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.client.dimension.DimensionRenderer;
|
||||
import net.xevianlight.aphelion.client.dimension.DimensionRendererCache;
|
||||
import net.xevianlight.aphelion.client.dimension.SpaceSkyEffects;
|
||||
import net.xevianlight.aphelion.core.saveddata.EnvironmentSavedData;
|
||||
import net.xevianlight.aphelion.core.saveddata.SpacePartitionSavedData;
|
||||
import net.xevianlight.aphelion.util.SpacePartitionHelper;
|
||||
|
||||
@@ -51,5 +54,12 @@ public class AphelionDebugOverlay {
|
||||
// event.getLeft().add(" Sky: " + rendererSummary);
|
||||
event.getLeft().add(" Station: " + x + " " + z + " ID: " + SpacePartitionSavedData.pack(x,z));
|
||||
event.getLeft().add(" Station Destination:" + PartitionClientState.lastData().getDestination());
|
||||
var server = mc.getSingleplayerServer();
|
||||
ServerLevel singlePlayerLevel;
|
||||
if (server != null) {
|
||||
singlePlayerLevel = server.getLevel(mc.level.dimension());
|
||||
if (singlePlayerLevel != null)
|
||||
event.getLeft().add(" Oxygen: " + EnvironmentSavedData.get(singlePlayerLevel).hasOxygen(singlePlayerLevel, mc.player.blockPosition()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package net.xevianlight.aphelion.client;
|
||||
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import net.minecraft.core.BlockPos;
|
||||
|
||||
public final class ClientOxygenCache {
|
||||
public static final LongOpenHashSet OXYGEN = new LongOpenHashSet();
|
||||
public static BlockPos lastCenter = BlockPos.ZERO;
|
||||
public static long lastUpdateGameTime = -1;
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package net.xevianlight.aphelion.client;
|
||||
|
||||
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import com.mojang.blaze3d.vertex.VertexFormat;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderStateShard;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.Direction;
|
||||
import net.neoforged.api.distmarker.Dist;
|
||||
import net.neoforged.bus.api.SubscribeEvent;
|
||||
import net.neoforged.fml.common.EventBusSubscriber;
|
||||
import net.neoforged.neoforge.client.event.RenderLevelStageEvent;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import org.joml.Matrix4f;
|
||||
|
||||
@EventBusSubscriber(modid = Aphelion.MOD_ID, value = Dist.CLIENT)
|
||||
public final class OxygenDebugRender {
|
||||
|
||||
// Untextured translucent quads (POSITION_COLOR only)
|
||||
private static final RenderType OXYGEN_FILL = RenderType.create(
|
||||
"aphelion_oxygen_fill",
|
||||
DefaultVertexFormat.POSITION_COLOR,
|
||||
VertexFormat.Mode.QUADS,
|
||||
256,
|
||||
false,
|
||||
true,
|
||||
RenderType.CompositeState.builder()
|
||||
.setShaderState(RenderStateShard.POSITION_COLOR_SHADER)
|
||||
.setTransparencyState(RenderStateShard.TRANSLUCENT_TRANSPARENCY)
|
||||
.setCullState(RenderStateShard.NO_CULL)
|
||||
.setDepthTestState(RenderStateShard.LEQUAL_DEPTH_TEST)
|
||||
.setWriteMaskState(RenderStateShard.COLOR_DEPTH_WRITE)
|
||||
.createCompositeState(true)
|
||||
);
|
||||
|
||||
@SubscribeEvent
|
||||
public static void onRenderLevel(RenderLevelStageEvent event) {
|
||||
// One stage only (pick one that exists and looks good)
|
||||
if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_TRANSLUCENT_BLOCKS) return;
|
||||
|
||||
Minecraft mc = Minecraft.getInstance();
|
||||
if (mc.level == null || mc.player == null) return;
|
||||
if (!mc.gui.getDebugOverlay().showDebugScreen()) return;
|
||||
|
||||
PoseStack poseStack = event.getPoseStack();
|
||||
var cam = mc.gameRenderer.getMainCamera();
|
||||
var camPos = cam.getPosition();
|
||||
|
||||
poseStack.pushPose();
|
||||
poseStack.translate(-camPos.x, -camPos.y, -camPos.z);
|
||||
|
||||
MultiBufferSource.BufferSource bufferSource = mc.renderBuffers().bufferSource();
|
||||
VertexConsumer vc = bufferSource.getBuffer(OXYGEN_FILL);
|
||||
|
||||
// Render surface faces only (fast + pretty)
|
||||
for (long l : ClientOxygenCache.OXYGEN) {
|
||||
BlockPos p = BlockPos.of(l);
|
||||
drawSurfaceFaces(poseStack, vc, p);
|
||||
}
|
||||
|
||||
poseStack.popPose();
|
||||
bufferSource.endBatch(OXYGEN_FILL);
|
||||
}
|
||||
|
||||
private static void drawSurfaceFaces(PoseStack poseStack, VertexConsumer vc, BlockPos p) {
|
||||
// Neighbor checks: only render faces exposed to non-oxygen
|
||||
boolean up = ClientOxygenCache.OXYGEN.contains(p.above().asLong());
|
||||
boolean down = ClientOxygenCache.OXYGEN.contains(p.below().asLong());
|
||||
boolean north = ClientOxygenCache.OXYGEN.contains(p.north().asLong());
|
||||
boolean south = ClientOxygenCache.OXYGEN.contains(p.south().asLong());
|
||||
boolean east = ClientOxygenCache.OXYGEN.contains(p.east().asLong());
|
||||
boolean west = ClientOxygenCache.OXYGEN.contains(p.west().asLong());
|
||||
|
||||
if (up && down && north && south && east && west) return;
|
||||
|
||||
final float eps = 0.0025f;
|
||||
float x0 = p.getX() + eps;
|
||||
float y0 = p.getY() + eps;
|
||||
float z0 = p.getZ() + eps;
|
||||
float x1 = p.getX() + 1 - eps;
|
||||
float y1 = p.getY() + 1 - eps;
|
||||
float z1 = p.getZ() + 1 - eps;
|
||||
|
||||
// Color (ARGB-ish but as floats)
|
||||
float r = 0.2f, g = 0.8f, b = 1.0f, a = 0.18f;
|
||||
|
||||
Matrix4f mat = poseStack.last().pose();
|
||||
|
||||
// IMPORTANT: vertex winding should be consistent (counter-clockwise)
|
||||
if (!up) quad(mat, vc, x0,y1,z0, x1,y1,z0, x1,y1,z1, x0,y1,z1, r,g,b,a);
|
||||
if (!down) quad(mat, vc, x0,y0,z1, x1,y0,z1, x1,y0,z0, x0,y0,z0, r,g,b,a);
|
||||
|
||||
if (!north) quad(mat, vc, x1,y0,z0, x0,y0,z0, x0,y1,z0, x1,y1,z0, r,g,b,a);
|
||||
if (!south) quad(mat, vc, x0,y0,z1, x1,y0,z1, x1,y1,z1, x0,y1,z1, r,g,b,a);
|
||||
|
||||
if (!east) quad(mat, vc, x1,y0,z1, x1,y0,z0, x1,y1,z0, x1,y1,z1, r,g,b,a);
|
||||
if (!west) quad(mat, vc, x0,y0,z0, x0,y0,z1, x0,y1,z1, x0,y1,z0, r,g,b,a);
|
||||
}
|
||||
|
||||
private static void quad(
|
||||
Matrix4f mat, VertexConsumer vc,
|
||||
float x0, float y0, float z0,
|
||||
float x1, float y1, float z1,
|
||||
float x2, float y2, float z2,
|
||||
float x3, float y3, float z3,
|
||||
float r, float g, float b, float a
|
||||
) {
|
||||
// POSITION_COLOR format: ONLY position + color.
|
||||
vc.addVertex(mat, x0, y0, z0).setColor(r, g, b, a);
|
||||
vc.addVertex(mat, x1, y1, z1).setColor(r, g, b, a);
|
||||
vc.addVertex(mat, x2, y2, z2).setColor(r, g, b, a);
|
||||
vc.addVertex(mat, x3, y3, z3).setColor(r, g, b, a);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
package net.xevianlight.aphelion.core.saveddata;
|
||||
|
||||
import com.jcraft.jorbis.Block;
|
||||
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
|
||||
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.core.BlockPos;
|
||||
import net.minecraft.core.HolderLookup;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
@@ -9,6 +12,7 @@ import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.saveddata.SavedData;
|
||||
import net.xevianlight.aphelion.Aphelion;
|
||||
import net.xevianlight.aphelion.client.ClientOxygenCache;
|
||||
import net.xevianlight.aphelion.core.saveddata.types.EnvironmentData;
|
||||
import net.xevianlight.aphelion.planet.Planet;
|
||||
import net.xevianlight.aphelion.planet.PlanetCache;
|
||||
@@ -60,7 +64,7 @@ public class EnvironmentSavedData extends SavedData {
|
||||
|
||||
if (!tag.contains("Position", Tag.TAG_LONG_ARRAY) || !tag.contains("Value", Tag.TAG_INT_ARRAY)) { return data; }
|
||||
|
||||
long[] positions = tag.getLongArray("Positions");
|
||||
long[] positions = tag.getLongArray("Position");
|
||||
int[] values = tag.getIntArray("Value");
|
||||
|
||||
int length = Math.min(positions.length, values.length);
|
||||
@@ -89,7 +93,7 @@ public class EnvironmentSavedData extends SavedData {
|
||||
}
|
||||
|
||||
public void setDataForPosition(Level level, BlockPos pos, EnvironmentData data) {
|
||||
putOrRemove(pos.asLong(), data.pack());
|
||||
putOrRemove(level, pos.asLong(), data.pack());
|
||||
}
|
||||
|
||||
public boolean hasOxygen(Level level, BlockPos pos) {
|
||||
@@ -100,8 +104,8 @@ public class EnvironmentSavedData extends SavedData {
|
||||
public void setOxygen(Level level, BlockPos pos, boolean value) {
|
||||
var data = getDataForPosition(level, pos);
|
||||
data.setOxygen(value);
|
||||
Aphelion.LOGGER.info("Set oxygen for {} to {}", pos, value);
|
||||
putOrRemove(pos.asLong(), data.pack());
|
||||
// Aphelion.LOGGER.info("Set oxygen for {} to {}", pos, value);
|
||||
putOrRemove(level, pos.asLong(), data.pack());
|
||||
}
|
||||
|
||||
public void setOxygen(Level level, Collection<BlockPos> positions, boolean value) {
|
||||
@@ -112,8 +116,14 @@ public class EnvironmentSavedData extends SavedData {
|
||||
|
||||
public void resetOxygen(Level level, BlockPos pos) {
|
||||
var data = getDataForPosition(level, pos);
|
||||
data.setOxygen(EnvironmentData.DEFAULT_OXYGEN);
|
||||
putOrRemove(pos.asLong(), data.pack());
|
||||
data.setOxygen(defaultData(level).hasOxygen());
|
||||
putOrRemove(level, pos.asLong(), data.pack());
|
||||
}
|
||||
|
||||
public void resetOxygen(Level level, Collection<BlockPos> positions) {
|
||||
for (BlockPos pos : positions) {
|
||||
resetOxygen(level, pos);
|
||||
}
|
||||
}
|
||||
|
||||
public float getGravity(Level level, BlockPos pos) {
|
||||
@@ -124,7 +134,7 @@ public class EnvironmentSavedData extends SavedData {
|
||||
public void setGravity(Level level, BlockPos pos, float value) {
|
||||
var data = getDataForPosition(level, pos);
|
||||
data.setGravity(value);
|
||||
putOrRemove(pos.asLong(), data.pack());
|
||||
putOrRemove(level, pos.asLong(), data.pack());
|
||||
}
|
||||
|
||||
public void setGravity(Level level, Collection<BlockPos> positions, float value) {
|
||||
@@ -133,6 +143,12 @@ public class EnvironmentSavedData extends SavedData {
|
||||
}
|
||||
}
|
||||
|
||||
public void resetGravity(Level level, BlockPos pos) {
|
||||
var data = getDataForPosition(level, pos);
|
||||
data.setGravity(defaultData(level).getGravity());
|
||||
putOrRemove(level, pos.asLong(), data.pack());
|
||||
}
|
||||
|
||||
public short getTemperature(Level level, BlockPos pos) {
|
||||
var data = getDataForPosition(level, pos);
|
||||
return data.getTemperature();
|
||||
@@ -141,7 +157,7 @@ public class EnvironmentSavedData extends SavedData {
|
||||
public void setTemperature(Level level, BlockPos pos, short value) {
|
||||
var data = getDataForPosition(level, pos);
|
||||
data.setTemperature(value);
|
||||
putOrRemove(pos.asLong(), data.pack());
|
||||
putOrRemove(level, pos.asLong(), data.pack());
|
||||
}
|
||||
|
||||
public void setTemperature(Level level, Collection<BlockPos> positions, short value) {
|
||||
@@ -150,8 +166,14 @@ public class EnvironmentSavedData extends SavedData {
|
||||
}
|
||||
}
|
||||
|
||||
private void putOrRemove(long key, int packed) {
|
||||
if (packed == EnvironmentData.DEFAULT_PACKED) {
|
||||
public void resetTemperature(Level level, BlockPos pos) {
|
||||
var data = getDataForPosition(level, pos);
|
||||
data.setTemperature(defaultData(level).getTemperature());
|
||||
putOrRemove(level, pos.asLong(), data.pack());
|
||||
}
|
||||
|
||||
private void putOrRemove(Level level, long key, int packed) {
|
||||
if (packed == defaultPacked(level)) {
|
||||
envData.remove(key);
|
||||
} else {
|
||||
envData.put(key, packed);
|
||||
@@ -159,10 +181,82 @@ public class EnvironmentSavedData extends SavedData {
|
||||
setDirty();
|
||||
}
|
||||
|
||||
private static int defaultPacked(Level level) {
|
||||
Planet planet = PlanetCache.getByDimensionOrNull(level.dimension());
|
||||
if (planet == null) return EnvironmentData.DEFAULT_PACKED;
|
||||
|
||||
// NOTE: adjust gravity/temperature defaults to whatever your data model intends
|
||||
EnvironmentData planetData = new EnvironmentData(
|
||||
planet.oxygen(),
|
||||
EnvironmentData.DEFAULT_TEMPERATURE,
|
||||
(short) planet.gravity()
|
||||
);
|
||||
return planetData.pack();
|
||||
}
|
||||
|
||||
private static EnvironmentData defaultData(Level level) {
|
||||
return EnvironmentData.unpack(defaultPacked(level));
|
||||
}
|
||||
|
||||
public static EnvironmentSavedData get(ServerLevel level) {
|
||||
return level.getDataStorage().computeIfAbsent(
|
||||
new Factory<>(EnvironmentSavedData::create, EnvironmentSavedData::load),
|
||||
NAME
|
||||
);
|
||||
}
|
||||
|
||||
public static void refreshFromIntegratedServerIfNeeded(Minecraft mc, int radius, int maxBlocks) {
|
||||
if (mc.level == null || mc.player == null) return;
|
||||
|
||||
long gameTime = mc.level.getGameTime();
|
||||
if (ClientOxygenCache.lastUpdateGameTime != -1 && gameTime - ClientOxygenCache.lastUpdateGameTime < 20) return; // every 1s
|
||||
|
||||
BlockPos center = mc.player.blockPosition();
|
||||
if (center.distManhattan(ClientOxygenCache.lastCenter) < 1) return; // don’t refresh if player barely moved
|
||||
|
||||
var server = mc.getSingleplayerServer();
|
||||
if (server == null) return;
|
||||
|
||||
// IMPORTANT: execute on server thread
|
||||
server.execute(() -> {
|
||||
var serverLevel = server.getLevel(mc.level.dimension());
|
||||
if (serverLevel == null) return;
|
||||
|
||||
EnvironmentSavedData env = EnvironmentSavedData.get(serverLevel);
|
||||
|
||||
LongOpenHashSet found = new LongOpenHashSet();
|
||||
|
||||
int r = radius;
|
||||
int scanned = 0;
|
||||
|
||||
// Scan a cube-ish region
|
||||
for (int dy = -r; dy <= r; dy++) {
|
||||
for (int dz = -r; dz <= r; dz++) {
|
||||
for (int dx = -r; dx <= r; dx++) {
|
||||
if (found.size() >= maxBlocks) break;
|
||||
|
||||
BlockPos p = center.offset(dx, dy, dz);
|
||||
|
||||
// optional: skip non-air or skip solid blocks (visual preference)
|
||||
// if (!serverLevel.getBlockState(p).isAir()) continue;
|
||||
|
||||
if (env.hasOxygen(serverLevel, p)) {
|
||||
found.add(p.asLong());
|
||||
}
|
||||
|
||||
scanned++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy results back to client thread safely
|
||||
mc.execute(() -> {
|
||||
ClientOxygenCache.OXYGEN.clear();
|
||||
ClientOxygenCache.OXYGEN.addAll(found);
|
||||
ClientOxygenCache.lastCenter = center;
|
||||
ClientOxygenCache.lastUpdateGameTime = gameTime;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,5 +3,5 @@
|
||||
"orbit_distance": 1,
|
||||
"star_system": "aphelon:sol",
|
||||
"gravity": 1,
|
||||
"oxygen": true
|
||||
"oxygen": false
|
||||
}
|
||||
Reference in New Issue
Block a user