From bced7bfda6bcbcb32fb68a3ca7560f49403ef96e Mon Sep 17 00:00:00 2001 From: XiaoMoMi Date: Fri, 28 Nov 2025 02:57:59 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=9E=E4=BD=93=E5=89=94=E9=99=A4=E5=89=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bukkit/block/BukkitBlockManager.java | 4 +- .../plugin/network/BukkitNetworkManager.java | 97 ++++++++++++++++--- .../plugin/user/BukkitServerPlayer.java | 53 ++++++---- .../craftengine/bukkit/world/BukkitWorld.java | 5 +- common-files/src/main/resources/config.yml | 9 +- .../src/main/resources/translations/en.yml | 1 + .../core/block/AbstractBlockManager.java | 8 +- .../core/block/BlockStateAppearance.java | 5 +- .../core/block/ImmutableBlockState.java | 10 ++ .../render/ConstantBlockEntityRenderer.java | 13 ++- .../core/entity/player/Player.java | 16 ++- .../craftengine/core/plugin/CraftEngine.java | 1 - .../core/plugin/config/Config.java | 9 ++ .../config/template/TemplateManager.java | 7 -- .../argument/ExpressionTemplateArgument.java | 1 - .../core/plugin/network/NetWorkUser.java | 11 +-- .../core/util/ResourceConfigUtils.java | 49 ++++++++++ .../craftengine/core/world/Cullable.java | 13 +++ .../craftengine/core/world/Vec3i.java | 21 +--- .../core/world/chunk/ArrayPalette.java | 13 ++- .../core/world/chunk/BiMapPalette.java | 12 ++- .../craftengine/core/world/chunk/CEChunk.java | 21 ++-- .../core/world/chunk/ChunkStatus.java | 7 -- .../core/world/chunk/IdListPalette.java | 5 + .../craftengine/core/world/chunk/Palette.java | 2 + .../core/world/chunk/SingularPalette.java | 9 ++ .../core/world/chunk/client/ClientChunk.java | 33 +++++++ .../world/chunk/client/ClientSection.java | 34 +++++++ .../client/ClientSectionOcclusionStorage.java | 6 ++ .../chunk/client/PackedOcclusionStorage.java | 37 +++++++ .../client/SingularOcclusionStorage.java | 14 +++ .../chunk/client/VirtualCullableObject.java | 31 ++++++ .../serialization/DefaultChunkSerializer.java | 2 +- .../core/world/collision/AABB.java | 11 +++ 34 files changed, 470 insertions(+), 100 deletions(-) create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/Cullable.java delete mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/chunk/ChunkStatus.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientChunk.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSection.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSectionOcclusionStorage.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/PackedOcclusionStorage.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/SingularOcclusionStorage.java create mode 100644 core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java index 30237a4c2..bb379b71f 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/block/BukkitBlockManager.java @@ -88,7 +88,7 @@ public final class BukkitBlockManager extends AbstractBlockManager { this.markVanillaNoteBlocks(); this.findViewBlockingVanillaBlocks(); Arrays.fill(this.immutableBlockStates, EmptyBlock.INSTANCE.defaultState()); - this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 一定要预先初始化一次,预防id超出上限 + this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings, this::isViewBlockingBlock); // 一定要预先初始化一次,预防id超出上限 } public static BukkitBlockManager instance() { @@ -128,7 +128,7 @@ public final class BukkitBlockManager extends AbstractBlockManager { @Override public void delayedLoad() { - this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings); // 重置方块映射表 + this.plugin.networkManager().registerBlockStatePacketListeners(this.blockStateMappings, this::isViewBlockingBlock); // 重置方块映射表 super.delayedLoad(); this.cachedVisualBlockStatePacket = VisualBlockStatePacket.create(); for (BukkitServerPlayer player : BukkitNetworkManager.instance().onlineUsers()) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index 563a5a6e4..0d962ccb9 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -22,6 +22,7 @@ import net.kyori.adventure.nbt.api.BinaryTagHolder; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.event.DataComponentValue; import net.kyori.adventure.text.event.HoverEvent; +import net.momirealms.craftengine.bukkit.api.BukkitAdaptors; import net.momirealms.craftengine.bukkit.api.CraftEngineBlocks; import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture; import net.momirealms.craftengine.bukkit.api.event.FurnitureAttemptBreakEvent; @@ -90,9 +91,12 @@ import net.momirealms.craftengine.core.plugin.text.component.ComponentProvider; import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.world.*; import net.momirealms.craftengine.core.world.chunk.CEChunk; -import net.momirealms.craftengine.core.world.chunk.ChunkStatus; import net.momirealms.craftengine.core.world.chunk.Palette; import net.momirealms.craftengine.core.world.chunk.PalettedContainer; +import net.momirealms.craftengine.core.world.chunk.client.ClientChunk; +import net.momirealms.craftengine.core.world.chunk.client.ClientSection; +import net.momirealms.craftengine.core.world.chunk.client.PackedOcclusionStorage; +import net.momirealms.craftengine.core.world.chunk.client.SingularOcclusionStorage; import net.momirealms.craftengine.core.world.chunk.packet.BlockEntityData; import net.momirealms.craftengine.core.world.chunk.packet.MCSection; import net.momirealms.sparrow.nbt.CompoundTag; @@ -125,6 +129,7 @@ import java.time.Instant; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiFunction; +import java.util.function.Predicate; public class BukkitNetworkManager implements NetworkManager, Listener, PluginMessageListener { private static BukkitNetworkManager instance; @@ -288,7 +293,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } } - public void registerBlockStatePacketListeners(int[] blockStateMappings) { + public void registerBlockStatePacketListeners(int[] blockStateMappings, Predicate occlusionPredicate) { int stoneId = BlockStateUtils.blockStateToId(MBlocks.STONE$defaultState); int vanillaBlocks = BlockStateUtils.vanillaBlockStateCount(); int[] newMappings = new int[blockStateMappings.length]; @@ -318,7 +323,8 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes newMappings, newMappingsMOD, newMappings.length, - RegistryUtils.currentBiomeRegistrySize() + RegistryUtils.currentBiomeRegistrySize(), + occlusionPredicate ), this.packetIds.clientboundLevelChunkWithLightPacket(), "ClientboundLevelChunkWithLightPacket"); registerS2CGamePacketListener(new SectionBlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundSectionBlocksUpdatePacket(), "ClientboundSectionBlocksUpdatePacket"); registerS2CGamePacketListener(new BlockUpdateListener(newMappings, newMappingsMOD), this.packetIds.clientboundBlockUpdatePacket(), "ClientboundBlockUpdatePacket"); @@ -1433,9 +1439,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey); World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString()))); if (world != null) { - int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16; - player.setClientSideSectionCount(sectionCount); - player.setClientSideDimension(Key.of(location.toString())); + player.setClientSideWorld(BukkitAdaptors.adapt(world)); } else { CraftEngine.instance().logger().warn("Failed to handle ClientboundLoginPacket: World " + location + " does not exist"); } @@ -1463,10 +1467,9 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes Object location = FastNMS.INSTANCE.field$ResourceKey$location(dimensionKey); World world = Bukkit.getWorld(Objects.requireNonNull(NamespacedKey.fromString(location.toString()))); if (world != null) { - int sectionCount = (world.getMaxHeight() - world.getMinHeight()) / 16; - player.setClientSideSectionCount(sectionCount); - player.setClientSideDimension(Key.of(location.toString())); + player.setClientSideWorld(BukkitAdaptors.adapt(world)); player.clearTrackedChunks(); + player.clearTrackedBlockEntities(); } else { CraftEngine.instance().logger().warn("Failed to handle ClientboundRespawnPacket: World " + location + " does not exist"); } @@ -1979,13 +1982,15 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes private final IntIdentityList biomeList; private final IntIdentityList blockList; private final boolean needsDowngrade; + private final Predicate occlusionPredicate; - public LevelChunkWithLightListener(int[] blockStateMapper, int[] modBlockStateMapper, int blockRegistrySize, int biomeRegistrySize) { + public LevelChunkWithLightListener(int[] blockStateMapper, int[] modBlockStateMapper, int blockRegistrySize, int biomeRegistrySize, Predicate occlusionPredicate) { this.blockStateMapper = blockStateMapper; this.modBlockStateMapper = modBlockStateMapper; this.biomeList = new IntIdentityList(biomeRegistrySize); this.blockList = new IntIdentityList(blockRegistrySize); this.needsDowngrade = MiscUtils.ceilLog2(BlockStateUtils.vanillaBlockStateCount()) != MiscUtils.ceilLog2(blockRegistrySize); + this.occlusionPredicate = occlusionPredicate; } public int remapBlockState(int stateId, boolean enableMod) { @@ -2022,36 +2027,92 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes buf.readBytes(chunkDataBytes); // 客户端侧section数量很重要,不能读取此时玩家所在的真实世界,包具有滞后性 - int count = player.clientSideSectionCount(); + net.momirealms.craftengine.core.world.World clientSideWorld = player.clientSideWorld(); + WorldHeight worldHeight = clientSideWorld.worldHeight(); + int count = worldHeight.getSectionsCount(); MCSection[] sections = new MCSection[count]; FriendlyByteBuf chunkDataByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(chunkDataBytes)); boolean hasChangedAnyBlock = false; boolean hasGlobalPalette = false; + // 创建客户端侧世界(只在开启实体情况下创建) + ClientSection[] clientSections = Config.enableEntityCulling() ? new ClientSection[count] : null; + for (int i = 0; i < count; i++) { MCSection mcSection = new MCSection(user.clientBlockList(), this.blockList, this.biomeList); mcSection.readPacket(chunkDataByteBuf); + PalettedContainer container = mcSection.blockStateContainer(); + // 重定向生物群系 if (remapBiomes(user, mcSection.biomeContainer())) { hasChangedAnyBlock = true; } + Palette palette = container.data().palette(); if (palette.canRemap()) { + + // 重定向方块 if (palette.remapAndCheck(s -> remapBlockState(s, user.clientModEnabled()))) { hasChangedAnyBlock = true; } + + // 处理客户端侧哪些方块有阻挡 + if (clientSections != null) { + int size = palette.getSize(); + // 单个元素的情况下,使用优化的存储方案 + if (size == 1) { + clientSections[i] = new ClientSection(new SingularOcclusionStorage(this.occlusionPredicate.test(palette.get(0)))); + } else { + boolean hasOcclusions = false; + boolean hasNoOcclusions = false; + for (int h = 0; h < size; h++) { + int entry = palette.get(h); + if (this.occlusionPredicate.test(entry)) { + hasOcclusions = true; + } else { + hasNoOcclusions = true; + } + } + // 两种情况都有,那么需要一个个遍历处理视线遮挡数据 + if (hasOcclusions && hasNoOcclusions) { + PackedOcclusionStorage storage = new PackedOcclusionStorage(false); + for (int j = 0; j < 4096; j++) { + int state = container.get(j); + storage.set(j, this.occlusionPredicate.test(state)); + } + } + // 全遮蔽或全透视则使用优化存储方案 + else { + clientSections[i] = new ClientSection(new SingularOcclusionStorage(hasOcclusions)); + } + } + } } else { hasGlobalPalette = true; + + PackedOcclusionStorage storage = null; + if (clientSections != null) { + storage = new PackedOcclusionStorage(false); + } + for (int j = 0; j < 4096; j++) { int state = container.get(j); + + // 重定向方块 int newState = remapBlockState(state, user.clientModEnabled()); if (newState != state) { container.set(j, newState); hasChangedAnyBlock = true; } + + // 写入视线遮挡数据 + if (storage != null) { + storage.set(j, this.occlusionPredicate.test(state)); + } } } + sections[i] = mcSection; } @@ -2116,13 +2177,17 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes } // 记录加载的区块 - player.addTrackedChunk(chunkPos.longKey, new ChunkStatus()); + player.addTrackedChunk(chunkPos.longKey, new ClientChunk(clientSections, worldHeight)); // 生成方块实体 - CEWorld ceWorld = BukkitWorldManager.instance().getWorld(player.world().uuid()); - CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(chunkPos.longKey); - if (ceChunk != null) { - ceChunk.spawnBlockEntities(player); + CEWorld ceWorld = clientSideWorld.storageWorld(); + // 世界可能被卸载,因为包滞后 + if (ceWorld != null) { + CEChunk ceChunk = ceWorld.getChunkAtIfLoaded(chunkPos.longKey); + if (ceChunk != null) { + // 生成方块实体 + ceChunk.spawnBlockEntities(player); + } } } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java index d6bb5a6f8..842be8e1d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/user/BukkitServerPlayer.java @@ -23,6 +23,7 @@ import net.momirealms.craftengine.core.advancement.AdvancementType; import net.momirealms.craftengine.core.block.BlockStateWrapper; import net.momirealms.craftengine.core.block.ImmutableBlockState; import net.momirealms.craftengine.core.block.entity.BlockEntity; +import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer; import net.momirealms.craftengine.core.entity.data.EntityData; import net.momirealms.craftengine.core.entity.player.GameMode; import net.momirealms.craftengine.core.entity.player.InteractionHand; @@ -39,7 +40,8 @@ import net.momirealms.craftengine.core.sound.SoundSource; import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.world.*; import net.momirealms.craftengine.core.world.World; -import net.momirealms.craftengine.core.world.chunk.ChunkStatus; +import net.momirealms.craftengine.core.world.chunk.client.ClientChunk; +import net.momirealms.craftengine.core.world.chunk.client.VirtualCullableObject; import net.momirealms.craftengine.core.world.collision.AABB; import org.bukkit.*; import org.bukkit.attribute.Attribute; @@ -85,8 +87,7 @@ public class BukkitServerPlayer extends Player { private Reference playerRef; private Reference serverPlayerRef; // client side dimension info - private int sectionCount; - private Key clientSideDimension; + private World clientSideWorld; // check main hand/offhand interaction private int lastSuccessfulInteraction; // to prevent duplicated events @@ -124,7 +125,7 @@ public class BukkitServerPlayer extends Player { // cooldown data private CooldownData cooldownData; // tracked chunks - private ConcurrentLong2ReferenceChainedHashTable trackedChunks; + private ConcurrentLong2ReferenceChainedHashTable trackedChunks; // entity view private Map entityTypeView; // 通过指令或api设定的语言 @@ -138,6 +139,8 @@ public class BukkitServerPlayer extends Player { private boolean isHackedBreak; // 上一次停止挖掘包发出的时间 private int lastStopMiningTick; + // 跟踪到的方块实体渲染器 + private final Map trackedBlockEntityRenderers = new ConcurrentHashMap<>(); public BukkitServerPlayer(BukkitCraftEngine plugin, @Nullable Channel channel) { this.channel = channel; @@ -477,21 +480,13 @@ public class BukkitServerPlayer extends Player { } @Override - public int clientSideSectionCount() { - return sectionCount; - } - - public void setClientSideSectionCount(int sectionCount) { - this.sectionCount = sectionCount; + public World clientSideWorld() { + return this.clientSideWorld; } @Override - public Key clientSideDimension() { - return clientSideDimension; - } - - public void setClientSideDimension(Key clientSideDimension) { - this.clientSideDimension = clientSideDimension; + public void setClientSideWorld(World world) { + this.clientSideWorld = world; } public void setConnectionState(ConnectionState connectionState) { @@ -1180,12 +1175,12 @@ public class BukkitServerPlayer extends Player { } @Override - public ChunkStatus getTrackedChunk(long chunkPos) { + public ClientChunk getTrackedChunk(long chunkPos) { return this.trackedChunks.get(chunkPos); } @Override - public void addTrackedChunk(long chunkPos, ChunkStatus chunkStatus) { + public void addTrackedChunk(long chunkPos, ClientChunk chunkStatus) { this.trackedChunks.put(chunkPos, chunkStatus); } @@ -1300,4 +1295,26 @@ public class BukkitServerPlayer extends Player { public void sendTotemAnimation(Item totem, @Nullable SoundData sound, boolean silent) { PlayerUtils.sendTotemAnimation(this, totem, sound, silent); } + + @Override + public void addTrackedBlockEntities(Map renders) { + for (Map.Entry entry : renders.entrySet()) { + this.trackedBlockEntityRenderers.put(entry.getKey(), new VirtualCullableObject(entry.getValue())); + } + } + + @Override + public void removeTrackedBlockEntities(Collection renders) { + for (BlockPos render : renders) { + VirtualCullableObject remove = this.trackedBlockEntityRenderers.remove(render); + if (remove != null && remove.isShown()) { + remove.cullable().hide(this); + } + } + } + + @Override + public void clearTrackedBlockEntities() { + this.trackedBlockEntityRenderers.clear(); + } } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java index 91d168666..66df48ee3 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/world/BukkitWorld.java @@ -24,7 +24,10 @@ import org.jetbrains.annotations.NotNull; import javax.annotation.Nullable; import java.lang.ref.WeakReference; import java.nio.file.Path; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; public class BukkitWorld implements World { private final WeakReference world; diff --git a/common-files/src/main/resources/config.yml b/common-files/src/main/resources/config.yml index c5c00829e..0d6efafb4 100644 --- a/common-files/src/main/resources/config.yml +++ b/common-files/src/main/resources/config.yml @@ -552,11 +552,10 @@ chunk-system: remove: [] convert: {} -#client-optimization: -# # Using server-side ray tracing algorithms to hide certain entities and reduce client-side rendering pressure. -# entity-culling: -# enable: false -# whitelist-entities: [] +client-optimization: + # Using server-side ray tracing algorithms to hide certain entities and reduce client-side rendering pressure. + entity-culling: + enable: false # Enables or disables debug mode debug: diff --git a/common-files/src/main/resources/translations/en.yml b/common-files/src/main/resources/translations/en.yml index b3ce311aa..cd0751c7b 100644 --- a/common-files/src/main/resources/translations/en.yml +++ b/common-files/src/main/resources/translations/en.yml @@ -87,6 +87,7 @@ warning.config.type.quaternionf: "Issue found in file - Failed t warning.config.type.vector3f: "Issue found in file - Failed to load '': Cannot cast '' to Vector3f type for option ''." warning.config.type.vec3d: "Issue found in file - Failed to load '': Cannot cast '' to Vec3d type for option ''." warning.config.type.map: "Issue found in file - Failed to load '': Cannot cast '' to Map type for option ''." +warning.config.type.aabb: "Issue found in file - Failed to load '': Cannot cast '' to AABB type for option ''." warning.config.type.snbt.invalid_syntax: "Issue found in file - Failed to load '': Invalid snbt syntax ''." warning.config.number.missing_type: "Issue found in file - The config '' is missing the required 'type' argument for number argument." warning.config.number.invalid_type: "Issue found in file - The config '' is using an invalid number argument type ''." diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java index c0f427de8..1b0b09185 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/AbstractBlockManager.java @@ -600,7 +600,11 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem this.arrangeModelForStateAndVerify(visualBlockState, parseBlockModel(modelConfig)); } } - BlockStateAppearance blockStateAppearance = new BlockStateAppearance(visualBlockState, parseBlockEntityRender(appearanceSection.get("entity-renderer"))); + BlockStateAppearance blockStateAppearance = new BlockStateAppearance( + visualBlockState, + parseBlockEntityRender(appearanceSection.get("entity-renderer")), + ResourceConfigUtils.getAsAABB(appearanceSection.getOrDefault("aabb", 1), "aabb") + ); appearances.put(appearanceName, blockStateAppearance); if (anyAppearance == null) { anyAppearance = blockStateAppearance; @@ -639,6 +643,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem } for (ImmutableBlockState possibleState : possibleStates) { possibleState.setVisualBlockState(appearance.blockState()); + possibleState.setEstimatedBoundingBox(appearance.estimateAABB()); appearance.blockEntityRenderer().ifPresent(possibleState::setConstantRenderers); } } @@ -662,6 +667,7 @@ public abstract class AbstractBlockManager extends AbstractModelGenerator implem if (visualState == null) { visualState = anyAppearance.blockState(); state.setVisualBlockState(visualState); + state.setEstimatedBoundingBox(anyAppearance.estimateAABB()); anyAppearance.blockEntityRenderer().ifPresent(state::setConstantRenderers); } int appearanceId = visualState.registryId(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java index 75e39df37..d2b8d0189 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/BlockStateAppearance.java @@ -2,8 +2,11 @@ package net.momirealms.craftengine.core.block; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; +import net.momirealms.craftengine.core.world.collision.AABB; import java.util.Optional; -public record BlockStateAppearance(BlockStateWrapper blockState, Optional[]> blockEntityRenderer) { +public record BlockStateAppearance(BlockStateWrapper blockState, + Optional[]> blockEntityRenderer, + AABB estimateAABB) { } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java index 8e4430e2e..9a4010ef8 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/ImmutableBlockState.java @@ -17,6 +17,7 @@ import net.momirealms.craftengine.core.registry.Holder; import net.momirealms.craftengine.core.util.MiscUtils; import net.momirealms.craftengine.core.world.CEWorld; import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.world.collision.AABB; import net.momirealms.sparrow.nbt.CompoundTag; import net.momirealms.sparrow.nbt.NBT; import net.momirealms.sparrow.nbt.Tag; @@ -42,6 +43,7 @@ public final class ImmutableBlockState { private BlockEntityType blockEntityType; @Nullable private BlockEntityElementConfig[] renderers; + private AABB estimatedBoundingBox; ImmutableBlockState( Holder.Reference owner, @@ -87,6 +89,14 @@ public final class ImmutableBlockState { this.renderers = renderers; } + public void setEstimatedBoundingBox(AABB aabb) { + this.estimatedBoundingBox = aabb; + } + + public AABB estimatedBoundingBox() { + return estimatedBoundingBox; + } + public boolean hasBlockEntity() { return this.blockEntityType != null; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java index 4b45900a0..48245a05d 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/block/entity/render/ConstantBlockEntityRenderer.java @@ -2,14 +2,18 @@ package net.momirealms.craftengine.core.block.entity.render; import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElement; import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.world.Cullable; +import net.momirealms.craftengine.core.world.collision.AABB; import org.jetbrains.annotations.ApiStatus; @ApiStatus.Experimental -public class ConstantBlockEntityRenderer { +public class ConstantBlockEntityRenderer implements Cullable { private final BlockEntityElement[] elements; + public final AABB aabb; - public ConstantBlockEntityRenderer(BlockEntityElement[] elements) { + public ConstantBlockEntityRenderer(BlockEntityElement[] elements, AABB aabb) { this.elements = elements; + this.aabb = aabb; } public void show(Player player) { @@ -47,4 +51,9 @@ public class ConstantBlockEntityRenderer { public BlockEntityElement[] elements() { return this.elements; } + + @Override + public AABB aabb() { + return this.aabb; + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java index 034502a96..d4dc6cddc 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java +++ b/core/src/main/java/net/momirealms/craftengine/core/entity/player/Player.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.core.entity.player; import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.advancement.AdvancementType; +import net.momirealms.craftengine.core.block.entity.render.ConstantBlockEntityRenderer; import net.momirealms.craftengine.core.entity.AbstractEntity; import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.context.CooldownData; @@ -9,14 +10,13 @@ import net.momirealms.craftengine.core.plugin.network.NetWorkUser; import net.momirealms.craftengine.core.sound.SoundData; import net.momirealms.craftengine.core.sound.SoundSource; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.world.BlockPos; -import net.momirealms.craftengine.core.world.Position; -import net.momirealms.craftengine.core.world.Vec3d; -import net.momirealms.craftengine.core.world.WorldPosition; +import net.momirealms.craftengine.core.world.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.Collection; import java.util.Locale; +import java.util.Map; public abstract class Player extends AbstractEntity implements NetWorkUser { private static final Key TYPE = Key.of("minecraft:player"); @@ -35,6 +35,8 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { @Override public abstract Object serverPlayer(); + public abstract void setClientSideWorld(World world); + public abstract float getDestroyProgress(Object blockState, BlockPos pos); public abstract void setClientSideCanBreakBlock(boolean canBreak); @@ -198,6 +200,12 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract void sendTotemAnimation(Item totem, @Nullable SoundData sound, boolean silent); + public abstract void addTrackedBlockEntities(Map renders); + + public abstract void removeTrackedBlockEntities(Collection renders); + + public abstract void clearTrackedBlockEntities(); + @Override public void remove() { } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index 58c1ac8cd..2c32adc8b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -21,7 +21,6 @@ import net.momirealms.craftengine.core.plugin.compatibility.CompatibilityManager import net.momirealms.craftengine.core.plugin.compatibility.PluginTaskRegistry; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.config.template.TemplateManager; -import net.momirealms.craftengine.core.plugin.config.template.TemplateManagerImpl; import net.momirealms.craftengine.core.plugin.context.GlobalVariableManager; import net.momirealms.craftengine.core.plugin.dependency.Dependencies; import net.momirealms.craftengine.core.plugin.dependency.Dependency; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java index c75eaa370..3720a4268 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/Config.java @@ -203,6 +203,8 @@ public class Config { protected boolean emoji$contexts$sign; protected int emoji$max_emojis_per_parse; + protected boolean client_optimization$entity_culling$enable; + public Config(CraftEngine plugin) { this.plugin = plugin; this.configVersion = PluginProperties.getValue("config"); @@ -562,6 +564,9 @@ public class Config { emoji$contexts$sign = config.getBoolean("emoji.contexts.sign", true); emoji$max_emojis_per_parse = config.getInt("emoji.max-emojis-per-parse", 32); + // client optimization + client_optimization$entity_culling$enable = config.getBoolean("client-optimization.entity-culling.enable", false); + firstTime = false; } @@ -1152,6 +1157,10 @@ public class Config { return instance.resource_pack$optimization$texture$zopfli_iterations; } + public static boolean enableEntityCulling() { + return instance.client_optimization$entity_culling$enable; + } + public YamlDocument loadOrCreateYamlData(String fileName) { Path path = this.plugin.dataFolderPath().resolve(fileName); if (!Files.exists(path)) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java index 79b942023..bed0110ef 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/TemplateManager.java @@ -2,14 +2,7 @@ package net.momirealms.craftengine.core.plugin.config.template; import net.momirealms.craftengine.core.plugin.Manageable; import net.momirealms.craftengine.core.plugin.config.ConfigParser; -import net.momirealms.craftengine.core.plugin.config.template.argument.TemplateArgument; -import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.util.SNBTReader; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; public interface TemplateManager extends Manageable { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ExpressionTemplateArgument.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ExpressionTemplateArgument.java index 667b28db2..cf02eeb46 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ExpressionTemplateArgument.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/config/template/argument/ExpressionTemplateArgument.java @@ -3,7 +3,6 @@ package net.momirealms.craftengine.core.plugin.config.template.argument; import com.ezylang.evalex.Expression; import com.ezylang.evalex.data.EvaluationValue; import net.momirealms.craftengine.core.plugin.config.template.ArgumentString; -import net.momirealms.craftengine.core.plugin.config.template.TemplateManager; import net.momirealms.craftengine.core.util.Key; import java.util.Locale; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java index 501200a8a..ab1d0d520 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/network/NetWorkUser.java @@ -6,7 +6,8 @@ import net.kyori.adventure.text.Component; import net.momirealms.craftengine.core.plugin.Plugin; import net.momirealms.craftengine.core.util.IntIdentityList; import net.momirealms.craftengine.core.util.Key; -import net.momirealms.craftengine.core.world.chunk.ChunkStatus; +import net.momirealms.craftengine.core.world.World; +import net.momirealms.craftengine.core.world.chunk.client.ClientChunk; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; @@ -65,9 +66,7 @@ public interface NetWorkUser { @ApiStatus.Internal ConnectionState encoderState(); - int clientSideSectionCount(); - - Key clientSideDimension(); + World clientSideWorld(); Object serverPlayer(); @@ -89,9 +88,9 @@ public interface NetWorkUser { boolean isChunkTracked(long chunkPos); - ChunkStatus getTrackedChunk(long chunkPos); + ClientChunk getTrackedChunk(long chunkPos); - void addTrackedChunk(long chunkPos, ChunkStatus chunkStatus); + void addTrackedChunk(long chunkPos, ClientChunk chunkStatus); void clearTrackedChunks(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java index 415893a3a..1a98323d1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/ResourceConfigUtils.java @@ -6,6 +6,7 @@ import net.momirealms.craftengine.core.plugin.locale.LocalizedException; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.collision.AABB; import org.jetbrains.annotations.Nullable; import org.joml.Quaternionf; import org.joml.Vector3f; @@ -341,4 +342,52 @@ public final class ResourceConfigUtils { } TranslationManager.instance().log(e.node(), e.arguments()); } + + public static AABB getAsAABB(Object o, String option) { + if (o == null) { + throw new LocalizedResourceConfigException("warning.config.type.aabb", "null", option); + } + if (o instanceof Number number) { + double min = -(number.doubleValue() / 2); + double max = number.doubleValue() / 2; + return new AABB(min, min, min, max, max, max); + } else { + double[] args; + if (o instanceof List list) { + args = new double[list.size()]; + for (int i = 0; i < args.length; i++) { + if (list.get(i) instanceof Number number) { + args[i] = number.doubleValue(); + } else { + try { + args[i] = Double.parseDouble(list.get(i).toString()); + } catch (NumberFormatException e) { + throw new LocalizedResourceConfigException("warning.config.type.aabb", o.toString(), option); + } + } + } + } else { + String[] split = o.toString().split(","); + args = new double[split.length]; + for (int i = 0; i < args.length; i++) { + try { + args[i] = Double.parseDouble(split[i]); + } catch (NumberFormatException e) { + throw new LocalizedResourceConfigException("warning.config.type.aabb", o.toString(), option); + } + } + } + if (args.length == 1) { + return new AABB(-args[0]/2, -args[0]/2, -args[0]/2, args[0]/2, args[0]/2, args[0]/2); + } else if (args.length == 2) { + return new AABB(-args[0]/2, -args[1]/2, -args[0]/2, args[0]/2, args[1]/2, args[0]/2); + } else if (args.length == 3) { + return new AABB(-args[0]/2, -args[1]/2, -args[2]/2, args[0]/2, args[1]/2, args[2]/2); + } else if (args.length == 6) { + return new AABB(args[0], args[1], args[2], args[3], args[4], args[5]); + } else { + throw new LocalizedResourceConfigException("warning.config.type.aabb", o.toString(), option); + } + } + } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/Cullable.java b/core/src/main/java/net/momirealms/craftengine/core/world/Cullable.java new file mode 100644 index 000000000..700442fb3 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/Cullable.java @@ -0,0 +1,13 @@ +package net.momirealms.craftengine.core.world; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.world.collision.AABB; + +public interface Cullable { + + AABB aabb(); + + void show(Player player); + + void hide(Player player); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java index 82b2e4bbb..bd4a707e6 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3i.java @@ -4,9 +4,9 @@ import net.momirealms.craftengine.core.util.Direction; public class Vec3i implements Comparable { public static final Vec3i ZERO = new Vec3i(0, 0, 0); - protected int x; - protected int y; - protected int z; + public final int x; + public final int y; + public final int z; public Vec3i(int x, int y, int z) { this.x = x; @@ -30,21 +30,6 @@ public class Vec3i implements Comparable { return x == 0 && y == 0 && z == 0 ? this : new Vec3i(this.x() + x, this.y() + y, this.z() + z); } - protected Vec3i setX(int x) { - this.x = x; - return this; - } - - protected Vec3i setY(int y) { - this.y = y; - return this; - } - - protected Vec3i setZ(int z) { - this.z = z; - return this; - } - @Override public boolean equals(Object object) { return this == object || object instanceof Vec3i vec3i && this.x == vec3i.x && this.y == vec3i.y && this.z == vec3i.z; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ArrayPalette.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ArrayPalette.java index 0ace21c98..f7926684a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ArrayPalette.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ArrayPalette.java @@ -77,15 +77,24 @@ public class ArrayPalette implements Palette { @Override public boolean hasAny(Predicate predicate) { - for(int i = 0; i < this.size; ++i) { + for (int i = 0; i < this.size; ++i) { if (predicate.test(this.array[i])) { return true; } } - return false; } + @Override + public boolean allMatch(Predicate predicate) { + for (int i = 0; i < this.size; ++i) { + if (!predicate.test(this.array[i])) { + return false; + } + } + return true; + } + @Override public T get(int id) { if (id >= 0 && id < this.size) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/BiMapPalette.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/BiMapPalette.java index 02d0b190d..ae9be1758 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/BiMapPalette.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/BiMapPalette.java @@ -67,7 +67,7 @@ public class BiMapPalette implements Palette { @Override public boolean hasAny(Predicate predicate) { - for(int i = 0; i < this.getSize(); ++i) { + for (int i = 0; i < this.getSize(); ++i) { if (predicate.test(this.map.get(i))) { return true; } @@ -75,6 +75,16 @@ public class BiMapPalette implements Palette { return false; } + @Override + public boolean allMatch(Predicate predicate) { + for (int i = 0; i < this.getSize(); ++i) { + if (!predicate.test(this.map.get(i))) { + return false; + } + } + return true; + } + @Override public T get(int id) { T object = this.map.get(id); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java index a0babe284..f12123ae2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/CEChunk.java @@ -11,6 +11,7 @@ import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityEl import net.momirealms.craftengine.core.block.entity.render.element.BlockEntityElementConfig; import net.momirealms.craftengine.core.block.entity.tick.*; import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.logger.Debugger; import net.momirealms.craftengine.core.world.*; import net.momirealms.craftengine.core.world.chunk.serialization.DefaultBlockEntityRendererSerializer; @@ -91,8 +92,12 @@ public class CEChunk { public void spawnBlockEntities(Player player) { try { this.renderLock.readLock().lock(); - for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) { - renderer.show(player); + if (Config.enableEntityCulling()) { + player.addTrackedBlockEntities(this.constantBlockEntityRenderers); + } else { + for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) { + renderer.show(player); + } } for (DynamicBlockEntityRenderer renderer : this.dynamicBlockEntityRenderers.values()) { renderer.show(player); @@ -105,8 +110,12 @@ public class CEChunk { public void despawnBlockEntities(Player player) { try { this.renderLock.readLock().lock(); - for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) { - renderer.hide(player); + if (Config.enableEntityCulling()) { + player.removeTrackedBlockEntities(this.constantBlockEntityRenderers.keySet()); + } else { + for (ConstantBlockEntityRenderer renderer : this.constantBlockEntityRenderers.values()) { + renderer.hide(player); + } } for (DynamicBlockEntityRenderer renderer : this.dynamicBlockEntityRenderers.values()) { renderer.hide(player); @@ -129,7 +138,7 @@ public class CEChunk { BlockEntityElementConfig[] renderers = state.constantRenderers(); if (renderers != null && renderers.length > 0) { BlockEntityElement[] elements = new BlockEntityElement[renderers.length]; - ConstantBlockEntityRenderer renderer = new ConstantBlockEntityRenderer(elements); + ConstantBlockEntityRenderer renderer = new ConstantBlockEntityRenderer(elements, state.estimatedBoundingBox().move(pos)); World wrappedWorld = this.world.world(); List trackedBy = getTrackedBy(); boolean hasTrackedBy = trackedBy != null && !trackedBy.isEmpty(); @@ -439,7 +448,7 @@ public class CEChunk { return Collections.unmodifiableCollection(this.blockEntities.values()); } - public List constantBlockEntityRenderers() { + public List constantBlockEntityRendererPositions() { try { this.renderLock.readLock().lock(); return new ArrayList<>(this.constantBlockEntityRenderers.keySet()); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ChunkStatus.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ChunkStatus.java deleted file mode 100644 index 744068aa6..000000000 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/ChunkStatus.java +++ /dev/null @@ -1,7 +0,0 @@ -package net.momirealms.craftengine.core.world.chunk; - -public class ChunkStatus { - - public ChunkStatus() { - } -} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/IdListPalette.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/IdListPalette.java index 51ee07710..1fd1f1155 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/IdListPalette.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/IdListPalette.java @@ -29,6 +29,11 @@ public class IdListPalette implements Palette { return true; } + @Override + public boolean allMatch(Predicate predicate) { + return true; + } + @Override public T get(int id) { T object = this.idList.get(id); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/Palette.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/Palette.java index f190afaee..73d6523ab 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/Palette.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/Palette.java @@ -13,6 +13,8 @@ public interface Palette { boolean hasAny(Predicate predicate); + boolean allMatch(Predicate predicate); + T get(int id); int getSize(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/SingularPalette.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/SingularPalette.java index afcfe09be..b52a6308c 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/SingularPalette.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/SingularPalette.java @@ -45,6 +45,15 @@ public class SingularPalette implements Palette { } } + @Override + public boolean allMatch(Predicate predicate) { + if (this.entry == null) { + throw new IllegalStateException("Use of an uninitialized palette"); + } else { + return predicate.test(this.entry); + } + } + @Override public T get(int id) { if (this.entry != null && id == 0) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientChunk.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientChunk.java new file mode 100644 index 000000000..e37e1ac31 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientChunk.java @@ -0,0 +1,33 @@ +package net.momirealms.craftengine.core.world.chunk.client; + +import net.momirealms.craftengine.core.world.SectionPos; +import net.momirealms.craftengine.core.world.WorldHeight; +import org.jetbrains.annotations.Nullable; + +public class ClientChunk { + @Nullable + public final ClientSection[] sections; + private final WorldHeight worldHeight; + + public ClientChunk(ClientSection[] sections, WorldHeight worldHeight) { + this.sections = sections; + this.worldHeight = worldHeight; + } + + @Nullable + public ClientSection[] sections() { + return sections; + } + + public boolean isOccluding(int x, int y, int z) { + if (this.sections == null) return false; + int index = sectionIndex(SectionPos.blockToSectionCoord(y)); + ClientSection section = this.sections[index]; + if (section == null) return false; + return section.isOccluding((y & 15) << 8 | (z & 15) << 4 | x & 15); + } + + public int sectionIndex(int sectionId) { + return sectionId - this.worldHeight.getMinSection(); + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSection.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSection.java new file mode 100644 index 000000000..af3f71749 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSection.java @@ -0,0 +1,34 @@ +package net.momirealms.craftengine.core.world.chunk.client; + +public class ClientSection { + private ClientSectionOcclusionStorage storage; + + public ClientSection(ClientSectionOcclusionStorage storage) { + this.storage = storage; + } + + boolean isOccluding(int x, int y, int z) { + return isOccluding((y << 4 | z) << 4 | x); + } + + boolean isOccluding(int index) { + return this.storage.isOccluding(index); + } + + void setOccluding(int x, int y, int z, boolean value) { + this.setOccluding((y << 4 | z) << 4 | x, value); + } + + void setOccluding(int index, boolean value) { + boolean wasOccluding = this.storage.isOccluding(index); + if (wasOccluding != value) { + if (this.storage instanceof PackedOcclusionStorage arrayStorage) { + arrayStorage.set(index, value); + } else { + PackedOcclusionStorage newStorage = new PackedOcclusionStorage(wasOccluding); + newStorage.set(index, value); + this.storage = newStorage; + } + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSectionOcclusionStorage.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSectionOcclusionStorage.java new file mode 100644 index 000000000..0a53ede3d --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/ClientSectionOcclusionStorage.java @@ -0,0 +1,6 @@ +package net.momirealms.craftengine.core.world.chunk.client; + +public interface ClientSectionOcclusionStorage { + + boolean isOccluding(int index); +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/PackedOcclusionStorage.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/PackedOcclusionStorage.java new file mode 100644 index 000000000..10e3b08dd --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/PackedOcclusionStorage.java @@ -0,0 +1,37 @@ +package net.momirealms.craftengine.core.world.chunk.client; + +import java.util.Arrays; + +public class PackedOcclusionStorage implements ClientSectionOcclusionStorage { + private static final int SIZE = 4096; + private static final int LONGS = SIZE / 64; + private final long[] data; + + public PackedOcclusionStorage() { + this.data = new long[LONGS]; + } + + public PackedOcclusionStorage(boolean defaultValue) { + this.data = new long[LONGS]; + if (defaultValue) { + Arrays.fill(this.data, -1L); // 所有位设为1 + } + } + + @Override + public boolean isOccluding(int index) { + int arrayIndex = index >>> 6; // index / 64 + int bitIndex = index & 0x3F; // index % 64 + return (this.data[arrayIndex] & (1L << bitIndex)) != 0; + } + + public void set(int index, boolean occlusion) { + int arrayIndex = index >>> 6; // index / 64 + int bitIndex = index & 0x3F; // index % 64 + if (occlusion) { + this.data[arrayIndex] |= (1L << bitIndex); + } else { + this.data[arrayIndex] &= ~(1L << bitIndex); + } + } +} \ No newline at end of file diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/SingularOcclusionStorage.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/SingularOcclusionStorage.java new file mode 100644 index 000000000..7da13ba23 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/SingularOcclusionStorage.java @@ -0,0 +1,14 @@ +package net.momirealms.craftengine.core.world.chunk.client; + +public class SingularOcclusionStorage implements ClientSectionOcclusionStorage { + private final boolean isOccluding; + + public SingularOcclusionStorage(boolean isOccluding) { + this.isOccluding = isOccluding; + } + + @Override + public boolean isOccluding(int index) { + return this.isOccluding; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java new file mode 100644 index 000000000..127b8b09e --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/client/VirtualCullableObject.java @@ -0,0 +1,31 @@ +package net.momirealms.craftengine.core.world.chunk.client; + +import net.momirealms.craftengine.core.entity.player.Player; +import net.momirealms.craftengine.core.world.Cullable; + +public class VirtualCullableObject { + private final Cullable cullable; + private boolean isShown; + + public VirtualCullableObject(Cullable cullable) { + this.cullable = cullable; + this.isShown = false; + } + + public Cullable cullable() { + return cullable; + } + + public boolean isShown() { + return isShown; + } + + public void setShown(Player player, boolean shown) { + this.isShown = shown; + if (shown) { + this.cullable.show(player); + } else { + this.cullable.hide(player); + } + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java index ef1357235..2f851fe49 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/chunk/serialization/DefaultChunkSerializer.java @@ -32,7 +32,7 @@ public final class DefaultChunkSerializer { if (!blockEntities.isEmpty()) { chunkNbt.put("block_entities", blockEntities); } - ListTag blockEntityRenders = DefaultBlockEntityRendererSerializer.serialize(chunk.constantBlockEntityRenderers()); + ListTag blockEntityRenders = DefaultBlockEntityRendererSerializer.serialize(chunk.constantBlockEntityRendererPositions()); if (!blockEntityRenders.isEmpty()) { chunkNbt.put("block_entity_renderers", blockEntityRenders); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java b/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java index 9a837a003..c5a725f59 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/collision/AABB.java @@ -35,6 +35,17 @@ public class AABB { this.maxZ = Math.max(pos1.z, pos2.z); } + public AABB move(BlockPos pos) { + return new AABB( + this.minX + pos.x + 0.5, + this.minY + pos.y + 0.5, + this.minZ + pos.z + 0.5, + this.maxX + pos.x + 0.5, + this.maxY + pos.y + 0.5, + this.maxZ + pos.z + 0.5 + ); + } + public AABB(BlockPos pos) { this(pos.x(), pos.y(), pos.z(), pos.x() + 1, pos.y() + 1, pos.z() + 1); }