diff --git a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/mythicmobs/MythicItemDrop.java b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/mythicmobs/MythicItemDrop.java index 9258e4214..cb3ba791a 100644 --- a/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/mythicmobs/MythicItemDrop.java +++ b/bukkit/compatibility/src/main/java/net/momirealms/craftengine/bukkit/compatibility/mythicmobs/MythicItemDrop.java @@ -42,7 +42,7 @@ public class MythicItemDrop extends ItemDrop implements IItemDrop { context = ItemBuildContext.of(player); } } - int amountInt = MiscUtils.fastFloor(amount + 0.5F); + int amountInt = MiscUtils.floor(amount + 0.5F); ItemStack itemStack = this.customItem.buildItemStack(context, amountInt); return adapt(itemStack).amount(amountInt); } 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 b0c928641..c49b423e6 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 @@ -2075,6 +2075,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes // 两种情况都有,那么需要一个个遍历处理视线遮挡数据 if (hasOcclusions && hasNoOcclusions) { PackedOcclusionStorage storage = new PackedOcclusionStorage(false); + clientSections[i] = new ClientSection(storage); for (int j = 0; j < 4096; j++) { int state = container.get(j); storage.set(j, this.occlusionPredicate.test(state)); @@ -2092,6 +2093,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes PackedOcclusionStorage storage = null; if (clientSections != null) { storage = new PackedOcclusionStorage(false); + clientSections[i] = new ClientSection(storage); } for (int j = 0; j < 4096; j++) { @@ -2258,6 +2260,12 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes FriendlyByteBuf buf = event.getBuffer(); BlockPos pos = buf.readBlockPos(); int before = buf.readVarInt(); + if (Config.enableEntityCulling()) { + ClientChunk trackedChunk = user.getTrackedChunk(ChunkPos.asLong(pos.x >> 4, pos.z >> 4)); + if (trackedChunk != null) { + trackedChunk.setOccluding(pos.x, pos.y, pos.z, this.occlusionPredicate.test(before)); + } + } if (user.clientModEnabled() && !BlockStateUtils.isVanillaBlock(before)) { return; } @@ -2270,12 +2278,6 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes buf.writeVarInt(event.packetID()); buf.writeBlockPos(pos); buf.writeVarInt(state); - if (Config.enableEntityCulling()) { - ClientChunk trackedChunk = user.getTrackedChunk(ChunkPos.asLong(pos.x >> 4, pos.z >> 4)); - if (trackedChunk != null) { - trackedChunk.setOccluding(pos.x, pos.y, pos.z, this.occlusionPredicate.test(state)); - } - } } } @@ -2437,6 +2439,13 @@ public class BukkitNetworkManager implements NetworkManager, Listener, PluginMes if (eventId != WorldEvents.BLOCK_BREAK_EFFECT) return; BlockPos blockPos = buf.readBlockPos(); int state = buf.readInt(); + // 移除不透明设置 + if (Config.enableEntityCulling()) { + ClientChunk trackedChunk = user.getTrackedChunk(ChunkPos.asLong(blockPos.x >> 4, blockPos.z >> 4)); + if (trackedChunk != null) { + trackedChunk.setOccluding(blockPos.x, blockPos.y, blockPos.z, false); + } + } boolean global = buf.readBoolean(); int newState = user.clientModEnabled() ? modBlockStateMapper[state] : blockStateMapper[state]; Object blockState = BlockStateUtils.idToBlockState(state); 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 842be8e1d..a08fea4dc 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 @@ -32,6 +32,7 @@ import net.momirealms.craftengine.core.item.Item; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.Config; import net.momirealms.craftengine.core.plugin.context.CooldownData; +import net.momirealms.craftengine.core.plugin.entityculling.EntityCulling; import net.momirealms.craftengine.core.plugin.locale.TranslationManager; import net.momirealms.craftengine.core.plugin.network.ConnectionState; import net.momirealms.craftengine.core.plugin.network.EntityPacketHandler; @@ -142,6 +143,8 @@ public class BukkitServerPlayer extends Player { // 跟踪到的方块实体渲染器 private final Map trackedBlockEntityRenderers = new ConcurrentHashMap<>(); + private final EntityCulling culling; + public BukkitServerPlayer(BukkitCraftEngine plugin, @Nullable Channel channel) { this.channel = channel; this.plugin = plugin; @@ -154,6 +157,7 @@ public class BukkitServerPlayer extends Player { } } } + this.culling = new EntityCulling(this, 64, 0.5); } public void setPlayer(org.bukkit.entity.Player player) { @@ -559,6 +563,15 @@ public class BukkitServerPlayer extends Player { this.predictNextBlockToMine(); } } + if (Config.enableEntityCulling()) { + long nano1 = System.nanoTime(); + for (VirtualCullableObject cullableObject : this.trackedBlockEntityRenderers.values()) { + boolean visible = this.culling.isVisible(cullableObject.cullable.aabb(), LocationUtils.toVec3d(platformPlayer().getEyeLocation())); + cullableObject.setShown(this, visible); + } + long nano2 = System.nanoTime(); + //CraftEngine.instance().logger().info("EntityCulling took " + (nano2 - nano1) / 1_000_000d + "ms"); + } } private void updateGUI() { @@ -1303,6 +1316,16 @@ public class BukkitServerPlayer extends Player { } } + @Override + public void addTrackedBlockEntity(BlockPos blockPos, ConstantBlockEntityRenderer renderer) { + this.trackedBlockEntityRenderers.put(blockPos, new VirtualCullableObject(renderer)); + } + + @Override + public VirtualCullableObject getTrackedBlockEntity(BlockPos blockPos) { + return this.trackedBlockEntityRenderers.get(blockPos); + } + @Override public void removeTrackedBlockEntities(Collection renders) { for (BlockPos render : renders) { diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java index 05a9600e1..f9cc52975 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/EntityUtils.java @@ -60,7 +60,7 @@ public final class EntityUtils { Object serverLevel = BukkitAdaptors.adapt(player.getWorld()).serverWorld(); Object serverPlayer = FastNMS.INSTANCE.method$CraftPlayer$getHandle(player); for (Object pose : List.of(CoreReflections.instance$Pose$STANDING, CoreReflections.instance$Pose$CROUCHING, CoreReflections.instance$Pose$SWIMMING)) { - BlockPos pos = new BlockPos(MiscUtils.fastFloor(x), MiscUtils.fastFloor(y), MiscUtils.fastFloor(z)); + BlockPos pos = new BlockPos(MiscUtils.floor(x), MiscUtils.floor(y), MiscUtils.floor(z)); try { double floorHeight = (double) CoreReflections.method$BlockGetter$getBlockFloorHeight.invoke(serverLevel, LocationUtils.toBlockPos(pos)); if (pos.y() + floorHeight > y + 0.75 || !isBlockFloorValid(floorHeight)) { diff --git a/common-files/src/main/resources/resources/default/configuration/templates/loot_tables.yml b/common-files/src/main/resources/resources/default/configuration/templates/loot_tables.yml index 99a366d26..24d3c1878 100644 --- a/common-files/src/main/resources/resources/default/configuration/templates/loot_tables.yml +++ b/common-files/src/main/resources/resources/default/configuration/templates/loot_tables.yml @@ -178,7 +178,7 @@ templates: # template: default:loot_table/ore # arguments: # ore_block: the ore block - # ore_drop: the drops of the ore + # ore_drop: the drops of the ore material # ore_drop_count: the amount of the ore materials # exp: the exp to drop default:loot_table/ore: 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 48245a05d..4551ed667 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 @@ -16,6 +16,7 @@ public class ConstantBlockEntityRenderer implements Cullable { this.aabb = aabb; } + @Override public void show(Player player) { for (BlockEntityElement element : this.elements) { if (element != null) { @@ -24,6 +25,7 @@ public class ConstantBlockEntityRenderer implements Cullable { } } + @Override public void hide(Player player) { for (BlockEntityElement element : this.elements) { if (element != null) { 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 d4dc6cddc..36e3fcb76 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 @@ -11,6 +11,7 @@ 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.*; +import net.momirealms.craftengine.core.world.chunk.client.VirtualCullableObject; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -202,6 +203,10 @@ public abstract class Player extends AbstractEntity implements NetWorkUser { public abstract void addTrackedBlockEntities(Map renders); + public abstract void addTrackedBlockEntity(BlockPos blockPos, ConstantBlockEntityRenderer renderer); + + public abstract VirtualCullableObject getTrackedBlockEntity(BlockPos blockPos); + public abstract void removeTrackedBlockEntities(Collection renders); public abstract void clearTrackedBlockEntities(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/loot/LootPool.java b/core/src/main/java/net/momirealms/craftengine/core/loot/LootPool.java index f07bd5d3c..f82d2a7e9 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/loot/LootPool.java +++ b/core/src/main/java/net/momirealms/craftengine/core/loot/LootPool.java @@ -44,7 +44,7 @@ public class LootPool { } if (this.compositeCondition.test(context)) { Consumer> consumer = LootFunction.decorate(this.compositeFunction, lootConsumer, context); - int i = this.rolls.getInt(context) + MiscUtils.fastFloor(this.bonusRolls.getFloat(context) * context.luck()); + int i = this.rolls.getInt(context) + MiscUtils.floor(this.bonusRolls.getFloat(context) * context.luck()); for (int j = 0; j < i; ++j) { this.addRandomItem(createFunctionApplier(consumer, context), context); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java index 758f77042..59d678797 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/ContextHolder.java @@ -1,12 +1,13 @@ package net.momirealms.craftengine.core.plugin.context; import com.google.common.collect.ImmutableMap; -import com.google.common.collect.ImmutableSortedMap; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; import java.util.function.Supplier; public class ContextHolder { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockCondition.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockCondition.java index 9eee31c0c..7f5e977c4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockCondition.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/condition/MatchBlockCondition.java @@ -40,7 +40,7 @@ public class MatchBlockCondition implements Condition Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); if (optionalWorldPosition.isPresent()) { World world = optionalWorldPosition.get().world(); - ExistingBlock blockAt = world.getBlock(MiscUtils.fastFloor(this.x.getDouble(ctx)), MiscUtils.fastFloor(this.y.getDouble(ctx)), MiscUtils.fastFloor(this.z.getDouble(ctx))); + ExistingBlock blockAt = world.getBlock(MiscUtils.floor(this.x.getDouble(ctx)), MiscUtils.floor(this.y.getDouble(ctx)), MiscUtils.floor(this.z.getDouble(ctx))); return MiscUtils.matchRegex(blockAt.id().asString(), this.ids, this.regexMatch); } return false; diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/BreakBlockFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/BreakBlockFunction.java index 268f23984..d0b63f852 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/BreakBlockFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/BreakBlockFunction.java @@ -28,7 +28,7 @@ public class BreakBlockFunction extends AbstractConditional @Override public void runInternal(CTX ctx) { Optional optionalPlayer = ctx.getOptionalParameter(DirectContextParameters.PLAYER); - optionalPlayer.ifPresent(player -> player.breakBlock(MiscUtils.fastFloor(x.getDouble(ctx)), MiscUtils.fastFloor(y.getDouble(ctx)), MiscUtils.fastFloor(z.getDouble(ctx)))); + optionalPlayer.ifPresent(player -> player.breakBlock(MiscUtils.floor(x.getDouble(ctx)), MiscUtils.floor(y.getDouble(ctx)), MiscUtils.floor(z.getDouble(ctx)))); } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CycleBlockPropertyFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CycleBlockPropertyFunction.java index 6e1e58bf4..b99ef0e72 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CycleBlockPropertyFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/CycleBlockPropertyFunction.java @@ -55,9 +55,9 @@ public class CycleBlockPropertyFunction extends AbstractCon Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); if (optionalWorldPosition.isEmpty()) return; World world = optionalWorldPosition.get().world(); - int x = MiscUtils.fastFloor(this.x.getDouble(ctx)); - int y = MiscUtils.fastFloor(this.y.getDouble(ctx)); - int z = MiscUtils.fastFloor(this.z.getDouble(ctx)); + int x = MiscUtils.floor(this.x.getDouble(ctx)); + int y = MiscUtils.floor(this.y.getDouble(ctx)); + int z = MiscUtils.floor(this.z.getDouble(ctx)); BlockStateWrapper wrapper = updateBlockState(world.getBlock(x, y, z).blockState(), ctx); world.setBlockState(x, y, z, wrapper, this.updateFlags.getInt(ctx)); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaceBlockFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaceBlockFunction.java index 1b6e8bf72..fedd7639a 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaceBlockFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/PlaceBlockFunction.java @@ -40,7 +40,7 @@ public class PlaceBlockFunction extends AbstractConditional Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); if (optionalWorldPosition.isPresent()) { World world = optionalWorldPosition.get().world(); - world.setBlockState(MiscUtils.fastFloor(this.x.getDouble(ctx)), MiscUtils.fastFloor(this.y.getDouble(ctx)), MiscUtils.fastFloor(this.z.getDouble(ctx)), this.lazyBlockState.get(), this.updateFlags.getInt(ctx)); + world.setBlockState(MiscUtils.floor(this.x.getDouble(ctx)), MiscUtils.floor(this.y.getDouble(ctx)), MiscUtils.floor(this.z.getDouble(ctx)), this.lazyBlockState.get(), this.updateFlags.getInt(ctx)); } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java index 020c44fe7..15f5b0586 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/RunFunction.java @@ -48,7 +48,7 @@ public class RunFunction extends AbstractConditionalFunctio for (Function function : functions) { function.run(ctx); } - }, delay, pos.world().platformWorld(), MiscUtils.fastFloor(pos.x()) >> 4, MiscUtils.fastFloor(pos.z()) >> 4); + }, delay, pos.world().platformWorld(), MiscUtils.floor(pos.x()) >> 4, MiscUtils.floor(pos.z()) >> 4); } } } diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TransformBlockFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TransformBlockFunction.java index 09da0dc26..b27f438c4 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TransformBlockFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/TransformBlockFunction.java @@ -45,9 +45,9 @@ public class TransformBlockFunction extends AbstractConditi Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); if (optionalWorldPosition.isPresent()) { World world = optionalWorldPosition.get().world(); - int x = MiscUtils.fastFloor(this.x.getDouble(ctx)); - int y = MiscUtils.fastFloor(this.y.getDouble(ctx)); - int z = MiscUtils.fastFloor(this.z.getDouble(ctx)); + int x = MiscUtils.floor(this.x.getDouble(ctx)); + int y = MiscUtils.floor(this.y.getDouble(ctx)); + int z = MiscUtils.floor(this.z.getDouble(ctx)); BlockStateWrapper existingBlockState = world.getBlock(x, y, z).blockState().withProperties(this.properties); CompoundTag newProperties = new CompoundTag(); for (String propertyName : existingBlockState.getPropertyNames()) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/UpdateBlockPropertyFunction.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/UpdateBlockPropertyFunction.java index 693647cf5..b59202282 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/UpdateBlockPropertyFunction.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/function/UpdateBlockPropertyFunction.java @@ -40,9 +40,9 @@ public class UpdateBlockPropertyFunction extends AbstractCo Optional optionalWorldPosition = ctx.getOptionalParameter(DirectContextParameters.POSITION); if (optionalWorldPosition.isPresent()) { World world = optionalWorldPosition.get().world(); - int x = MiscUtils.fastFloor(this.x.getDouble(ctx)); - int y = MiscUtils.fastFloor(this.y.getDouble(ctx)); - int z = MiscUtils.fastFloor(this.z.getDouble(ctx)); + int x = MiscUtils.floor(this.x.getDouble(ctx)); + int y = MiscUtils.floor(this.y.getDouble(ctx)); + int z = MiscUtils.floor(this.z.getDouble(ctx)); ExistingBlock blockAt = world.getBlock(x, y, z); BlockStateWrapper wrapper = blockAt.blockState().withProperties(this.properties); world.setBlockState(x, y, z, wrapper, this.updateFlags.getInt(ctx)); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java index 628b8d54c..0fd065ee2 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/EntityParameterProvider.java @@ -20,9 +20,9 @@ public class EntityParameterProvider implements ChainParameterProvider { CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, Entity::xRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, Entity::yRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.POSITION, Entity::position); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.fastFloor(p.x())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.fastFloor(p.y())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.fastFloor(p.z())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.floor(p.x())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.floor(p.y())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.floor(p.z())); CONTEXT_FUNCTIONS.put(DirectContextParameters.NAME, Entity::name); CONTEXT_FUNCTIONS.put(DirectContextParameters.UUID, Entity::uuid); CONTEXT_FUNCTIONS.put(DirectContextParameters.WORLD, Entity::world); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PlayerParameterProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PlayerParameterProvider.java index ee6033ccd..668a0c6d5 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PlayerParameterProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PlayerParameterProvider.java @@ -21,9 +21,9 @@ public class PlayerParameterProvider implements ChainParameterProvider { CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, Entity::xRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, Entity::yRot); CONTEXT_FUNCTIONS.put(DirectContextParameters.POSITION, Entity::position); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.fastFloor(p.x())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.fastFloor(p.y())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.fastFloor(p.z())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.floor(p.x())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.floor(p.y())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.floor(p.z())); CONTEXT_FUNCTIONS.put(DirectContextParameters.FOOD, Player::foodLevel); CONTEXT_FUNCTIONS.put(DirectContextParameters.SATURATION, Player::saturation); CONTEXT_FUNCTIONS.put(DirectContextParameters.NAME, Player::name); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java index c646658fb..3808e3f00 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/context/parameter/PositionParameterProvider.java @@ -20,9 +20,9 @@ public class PositionParameterProvider implements ChainParameterProvider MiscUtils.fastFloor(p.x())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.fastFloor(p.y())); - CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.fastFloor(p.z())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_X, p -> MiscUtils.floor(p.x())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Y, p -> MiscUtils.floor(p.y())); + CONTEXT_FUNCTIONS.put(DirectContextParameters.BLOCK_Z, p -> MiscUtils.floor(p.z())); } @SuppressWarnings("unchecked") diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java index 72c6ecdc0..fe190845b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/EntityCulling.java @@ -1,453 +1,276 @@ package net.momirealms.craftengine.core.plugin.entityculling; +import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.util.MiscUtils; +import net.momirealms.craftengine.core.world.ChunkPos; import net.momirealms.craftengine.core.world.MutableVec3d; import net.momirealms.craftengine.core.world.Vec3d; +import net.momirealms.craftengine.core.world.chunk.client.ClientChunk; +import net.momirealms.craftengine.core.world.collision.AABB; import java.util.Arrays; -import java.util.BitSet; -public class EntityCulling { - - // 面掩码常量 - private static final int ON_MIN_X = 0x01; - private static final int ON_MAX_X = 0x02; - private static final int ON_MIN_Y = 0x04; - private static final int ON_MAX_Y = 0x08; - private static final int ON_MIN_Z = 0x10; - private static final int ON_MAX_Z = 0x20; - - private final int reach; +public final class EntityCulling { + private final Player player; + private final int maxDistance; private final double aabbExpansion; - private final DataProvider provider; - private final OcclusionCache cache; + private final boolean[] dotSelectors = new boolean[14]; + private final MutableVec3d[] targetPoints = new MutableVec3d[14]; - // 重用数据结构,减少GC压力 - private final BitSet skipList = new BitSet(); - private final MutableVec3d[] targetPoints = new MutableVec3d[15]; - private final MutableVec3d targetPos = new MutableVec3d(0, 0, 0); - private final int[] cameraPos = new int[3]; - private final boolean[] dotselectors = new boolean[14]; - private final int[] lastHitBlock = new int[3]; - - // 状态标志 - private boolean allowRayChecks = false; - private boolean allowWallClipping = false; - - public EntityCulling(int maxDistance, DataProvider provider) { - this(maxDistance, provider, new ArrayOcclusionCache(maxDistance), 0.5); - } - - public EntityCulling(int maxDistance, DataProvider provider, OcclusionCache cache, double aabbExpansion) { - this.reach = maxDistance; - this.provider = provider; - this.cache = cache; + public EntityCulling(Player player, int maxDistance, double aabbExpansion) { + this.player = player; + this.maxDistance = maxDistance; this.aabbExpansion = aabbExpansion; - // 预先初始化点对象 - for(int i = 0; i < targetPoints.length; i++) { - targetPoints[i] = new MutableVec3d(0, 0, 0); + for (int i = 0; i < this.targetPoints.length; i++) { + this.targetPoints[i] = new MutableVec3d(0,0,0); } } - public boolean isAABBVisible(Vec3d aabbMin, MutableVec3d aabbMax, MutableVec3d viewerPosition) { - try { - // 计算包围盒范围 - int maxX = MiscUtils.fastFloor(aabbMax.x + aabbExpansion); - int maxY = MiscUtils.fastFloor(aabbMax.y + aabbExpansion); - int maxZ = MiscUtils.fastFloor(aabbMax.z + aabbExpansion); - int minX = MiscUtils.fastFloor(aabbMin.x - aabbExpansion); - int minY = MiscUtils.fastFloor(aabbMin.y - aabbExpansion); - int minZ = MiscUtils.fastFloor(aabbMin.z - aabbExpansion); + public boolean isVisible(AABB aabb, Vec3d cameraPos) { + // 根据AABB获取能包裹此AABB的最小长方体 + int minX = MiscUtils.floor(aabb.minX - this.aabbExpansion); + int minY = MiscUtils.floor(aabb.minY - this.aabbExpansion); + int minZ = MiscUtils.floor(aabb.minZ - this.aabbExpansion); + int maxX = MiscUtils.ceil(aabb.maxX + this.aabbExpansion); + int maxY = MiscUtils.ceil(aabb.maxY + this.aabbExpansion); + int maxZ = MiscUtils.ceil(aabb.maxZ + this.aabbExpansion); - cameraPos[0] = MiscUtils.fastFloor(viewerPosition.x); - cameraPos[1] = MiscUtils.fastFloor(viewerPosition.y); - cameraPos[2] = MiscUtils.fastFloor(viewerPosition.z); - - // 判断是否在包围盒内部 - Relative relX = Relative.from(minX, maxX, cameraPos[0]); - Relative relY = Relative.from(minY, maxY, cameraPos[1]); - Relative relZ = Relative.from(minZ, maxZ, cameraPos[2]); - - if(relX == Relative.INSIDE && relY == Relative.INSIDE && relZ == Relative.INSIDE) { - return true; + double cameraX = cameraPos.x; + double cameraY = cameraPos.y; + double cameraZ = cameraPos.z; + + Relative relX = Relative.from(minX, maxX, cameraX); + Relative relY = Relative.from(minY, maxY, cameraY); + Relative relZ = Relative.from(minZ, maxZ, cameraZ); + + // 相机位于实体内部 + if (relX == Relative.INSIDE && relY == Relative.INSIDE && relZ == Relative.INSIDE) { + return true; + } + + // 如果设置了最大距离 + if (this.maxDistance > 0) { + // 计算AABB到相机的最小距离 + double distanceSq = 0.0; + // 计算XYZ轴方向的距离 + distanceSq += distanceSq(minX, maxX, cameraX, relX); + distanceSq += distanceSq(minY, maxY, cameraY, relY); + distanceSq += distanceSq(minZ, maxZ, cameraZ, relZ); + // 检查距离是否超过最大值 + double maxDistanceSq = this.maxDistance * this.maxDistance; + // 超过最大距离,剔除 + if (distanceSq > maxDistanceSq) { + return false; } - - skipList.clear(); - - // 1. 快速检查缓存 - int id = 0; - for (int x = minX; x <= maxX; x++) { - for (int y = minY; y <= maxY; y++) { - for (int z = minZ; z <= maxZ; z++) { - int cachedValue = getCacheValue(x, y, z); - if (cachedValue == 1) return true; // 缓存显示可见 - if (cachedValue != 0) skipList.set(id); // 缓存显示不可见或遮挡 - id++; - } - } - } - - allowRayChecks = false; - id = 0; - - // 2. 遍历体素进行光线投射检查 - for (int x = minX; x <= maxX; x++) { - // 预计算X轴面的可见性和边缘数据 - byte visibleOnFaceX = 0; - byte faceEdgeDataX = 0; - if (x == minX) { faceEdgeDataX |= ON_MIN_X; if (relX == Relative.POSITIVE) visibleOnFaceX |= ON_MIN_X; } - if (x == maxX) { faceEdgeDataX |= ON_MAX_X; if (relX == Relative.NEGATIVE) visibleOnFaceX |= ON_MAX_X; } - - for (int y = minY; y <= maxY; y++) { - byte visibleOnFaceY = visibleOnFaceX; - byte faceEdgeDataY = faceEdgeDataX; - if (y == minY) { faceEdgeDataY |= ON_MIN_Y; if (relY == Relative.POSITIVE) visibleOnFaceY |= ON_MIN_Y; } - if (y == maxY) { faceEdgeDataY |= ON_MAX_Y; if (relY == Relative.NEGATIVE) visibleOnFaceY |= ON_MAX_Y; } - - for (int z = minZ; z <= maxZ; z++) { - // 如果缓存已标记为不可见,跳过 - if(skipList.get(id++)) continue; - - byte visibleOnFace = visibleOnFaceY; - byte faceEdgeData = faceEdgeDataY; - if (z == minZ) { faceEdgeData |= ON_MIN_Z; if (relZ == Relative.POSITIVE) visibleOnFace |= ON_MIN_Z; } - if (z == maxZ) { faceEdgeData |= ON_MAX_Z; if (relZ == Relative.NEGATIVE) visibleOnFace |= ON_MAX_Z; } - - if (visibleOnFace != 0) { - targetPos.set(x, y, z); - // 检查单个体素是否可见 - if (isVoxelVisible(viewerPosition, targetPos, faceEdgeData, visibleOnFace)) { - return true; - } - } - } - } - } - return false; - } catch (Throwable t) { - t.printStackTrace(); - return true; // 发生异常默认可见,防止渲染错误 - } - } - - // 接口定义 - public interface DataProvider { - boolean prepareChunk(int chunkX, int chunkZ); - boolean isOpaqueFullCube(int x, int y, int z); - default void cleanup() {} - default void checkingPosition(MutableVec3d[] targetPoints, int size, MutableVec3d viewerPosition) {} - } - - /** - * 检查单个体素是否对观察者可见 - */ - private boolean isVoxelVisible(MutableVec3d viewerPosition, MutableVec3d position, byte faceData, byte visibleOnFace) { - int targetSize = 0; - Arrays.fill(dotselectors, false); - - // 根据相对位置选择需要检测的关键点(角点和面中心点) - if((visibleOnFace & ON_MIN_X) != 0){ - dotselectors[0] = true; - if((faceData & ~ON_MIN_X) != 0) { dotselectors[1] = dotselectors[4] = dotselectors[5] = true; } - dotselectors[8] = true; - } - if((visibleOnFace & ON_MIN_Y) != 0){ - dotselectors[0] = true; - if((faceData & ~ON_MIN_Y) != 0) { dotselectors[3] = dotselectors[4] = dotselectors[7] = true; } - dotselectors[9] = true; - } - if((visibleOnFace & ON_MIN_Z) != 0){ - dotselectors[0] = true; - if((faceData & ~ON_MIN_Z) != 0) { dotselectors[1] = dotselectors[4] = dotselectors[5] = true; } - dotselectors[10] = true; - } - if((visibleOnFace & ON_MAX_X) != 0){ - dotselectors[4] = true; - if((faceData & ~ON_MAX_X) != 0) { dotselectors[5] = dotselectors[6] = dotselectors[7] = true; } - dotselectors[11] = true; - } - if((visibleOnFace & ON_MAX_Y) != 0){ - dotselectors[1] = true; - if((faceData & ~ON_MAX_Y) != 0) { dotselectors[2] = dotselectors[5] = dotselectors[6] = true; } - dotselectors[12] = true; - } - if((visibleOnFace & ON_MAX_Z) != 0){ - dotselectors[2] = true; - if((faceData & ~ON_MAX_Z) != 0) { dotselectors[3] = dotselectors[6] = dotselectors[7] = true; } - dotselectors[13] = true; } - // 填充目标点,使用偏移量防止Z-Fighting或精度问题 - if (dotselectors[0]) targetPoints[targetSize++].add(position, 0.05, 0.05, 0.05); - if (dotselectors[1]) targetPoints[targetSize++].add(position, 0.05, 0.95, 0.05); - if (dotselectors[2]) targetPoints[targetSize++].add(position, 0.05, 0.95, 0.95); - if (dotselectors[3]) targetPoints[targetSize++].add(position, 0.05, 0.05, 0.95); - if (dotselectors[4]) targetPoints[targetSize++].add(position, 0.95, 0.05, 0.05); - if (dotselectors[5]) targetPoints[targetSize++].add(position, 0.95, 0.95, 0.05); - if (dotselectors[6]) targetPoints[targetSize++].add(position, 0.95, 0.95, 0.95); - if (dotselectors[7]) targetPoints[targetSize++].add(position, 0.95, 0.05, 0.95); + // 清空之前的缓存 + Arrays.fill(this.dotSelectors, false); + if (relX == Relative.POSITIVE) { + this.dotSelectors[0] = this.dotSelectors[2] = this.dotSelectors[4] = this.dotSelectors[6] = this.dotSelectors[10] = true; + } else if (relX == Relative.NEGATIVE) { + this.dotSelectors[1] = this.dotSelectors[3] = this.dotSelectors[5] = this.dotSelectors[7] = this.dotSelectors[11] = true; + } + if (relY == Relative.POSITIVE) { + this.dotSelectors[0] = this.dotSelectors[1] = this.dotSelectors[2] = this.dotSelectors[3] = this.dotSelectors[12] = true; + } else if (relY == Relative.NEGATIVE) { + this.dotSelectors[4] = this.dotSelectors[5] = this.dotSelectors[6] = this.dotSelectors[7] = this.dotSelectors[13] = true; + } + if (relZ == Relative.POSITIVE) { + this.dotSelectors[0] = this.dotSelectors[1] = this.dotSelectors[4] = this.dotSelectors[5] = this.dotSelectors[8] = true; + } else if (relZ == Relative.NEGATIVE) { + this.dotSelectors[2] = this.dotSelectors[3] = this.dotSelectors[6] = this.dotSelectors[7] = this.dotSelectors[9] = true; + } + + int size = 0; + if (this.dotSelectors[0]) targetPoints[size++].set(minX, minY, minZ); + if (this.dotSelectors[1]) targetPoints[size++].set(maxX, minY, minZ); + if (this.dotSelectors[2]) targetPoints[size++].set(minX, minY, maxZ); + if (this.dotSelectors[3]) targetPoints[size++].set(maxX, minY, maxZ); + if (this.dotSelectors[4]) targetPoints[size++].set(minX, maxY, minZ); + if (this.dotSelectors[5]) targetPoints[size++].set(maxX, maxY, minZ); + if (this.dotSelectors[6]) targetPoints[size++].set(minX, maxY, maxZ); + if (this.dotSelectors[7]) targetPoints[size++].set(maxX, maxY, maxZ); // 面中心点 - if (dotselectors[8]) targetPoints[targetSize++].add(position, 0.05, 0.5, 0.5); - if (dotselectors[9]) targetPoints[targetSize++].add(position, 0.5, 0.05, 0.5); - if (dotselectors[10]) targetPoints[targetSize++].add(position, 0.5, 0.5, 0.05); - if (dotselectors[11]) targetPoints[targetSize++].add(position, 0.95, 0.5, 0.5); - if (dotselectors[12]) targetPoints[targetSize++].add(position, 0.5, 0.95, 0.5); - if (dotselectors[13]) targetPoints[targetSize++].add(position, 0.5, 0.5, 0.95); + double averageX = (minX + maxX) / 2.0; + double averageY = (minY + maxY) / 2.0; + double averageZ = (minZ + maxZ) / 2.0; + if (this.dotSelectors[8]) targetPoints[size++].set(averageX, averageY, minZ); + if (this.dotSelectors[9]) targetPoints[size++].set(averageX, averageY, maxZ); + if (this.dotSelectors[10]) targetPoints[size++].set(minX, averageY, averageZ); + if (this.dotSelectors[11]) targetPoints[size++].set(maxX, averageY, averageZ); + if (this.dotSelectors[12]) targetPoints[size++].set(averageX, minY, averageZ); + if (this.dotSelectors[13]) targetPoints[size].set(averageX, maxY, averageZ); - return isVisible(viewerPosition, targetPoints, targetSize); - } - - // 优化:使用基本数据类型代替对象分配 - private boolean rayIntersection(int[] b, MutableVec3d rayOrigin, double dirX, double dirY, double dirZ) { - double invX = 1.0 / dirX; - double invY = 1.0 / dirY; - double invZ = 1.0 / dirZ; - - double t1 = (b[0] - rayOrigin.x) * invX; - double t2 = (b[0] + 1 - rayOrigin.x) * invX; - double t3 = (b[1] - rayOrigin.y) * invY; - double t4 = (b[1] + 1 - rayOrigin.y) * invY; - double t5 = (b[2] - rayOrigin.z) * invZ; - double t6 = (b[2] + 1 - rayOrigin.z) * invZ; - - double tmin = Math.max(Math.max(Math.min(t1, t2), Math.min(t3, t4)), Math.min(t5, t6)); - double tmax = Math.min(Math.min(Math.max(t1, t2), Math.max(t3, t4)), Math.max(t5, t6)); - - // tmax > 0: 射线与AABB相交,但AABB在身后 - // tmin > tmax: 射线不相交 - return tmax > 0 && tmin <= tmax; + return isVisible(cameraPos, this.targetPoints, size); } /** - * 基于网格的光线追踪 (DDA算法) + * 使用3D DDA算法检测从起点到多个目标点的视线是否通畅 + * 算法基于数字微分分析,遍历射线路径上的所有方块 */ - private boolean isVisible(MutableVec3d start, MutableVec3d[] targets, int size) { - int startX = cameraPos[0]; - int startY = cameraPos[1]; - int startZ = cameraPos[2]; + private boolean isVisible(Vec3d start, MutableVec3d[] targets, int targetCount) { + // 起点所在方块的整数坐标(世界坐标转换为方块坐标) + int startBlockX = MiscUtils.floor(start.x); + int startBlockY = MiscUtils.floor(start.y); + int startBlockZ = MiscUtils.floor(start.z); - for (int v = 0; v < size; v++) { - MutableVec3d target = targets[v]; + // 遍历所有目标点进行视线检测 + for (int targetIndex = 0; targetIndex < targetCount; targetIndex++) { + MutableVec3d currentTarget = targets[targetIndex]; - double relX = start.x - target.x; - double relY = start.y - target.y; - double relZ = start.z - target.z; + // 计算起点到目标的相对向量(世界坐标差) + double deltaX = start.x - currentTarget.x; + double deltaY = start.y - currentTarget.y; + double deltaZ = start.z - currentTarget.z; - // 优化:避免在此处创建新的Vec3d对象进行归一化 - if(allowRayChecks) { - double len = Math.sqrt(relX * relX + relY * relY + relZ * relZ); - // 传入归一化后的方向分量 - if (rayIntersection(lastHitBlock, start, relX / len, relY / len, relZ / len)) { - continue; - } - } - - double dimAbsX = Math.abs(relX); - double dimAbsY = Math.abs(relY); - double dimAbsZ = Math.abs(relZ); + // 计算相对向量的绝对值,用于确定各方向上的距离 + double absDeltaX = Math.abs(deltaX); + double absDeltaY = Math.abs(deltaY); + double absDeltaZ = Math.abs(deltaZ); - double dimFracX = 1f / dimAbsX; - double dimFracY = 1f / dimAbsY; - double dimFracZ = 1f / dimAbsZ; + // 预计算每单位距离在各方块边界上的步进增量 + // 这些值表示射线穿过一个方块所需的时间分数 + double stepIncrementX = 1.0 / (absDeltaX + 1e-10); // 避免除0 + double stepIncrementY = 1.0 / (absDeltaY + 1e-10); + double stepIncrementZ = 1.0 / (absDeltaZ + 1e-10); - int intersectCount = 1; - int x_inc, y_inc, z_inc; - double t_next_y, t_next_x, t_next_z; + // 射线将穿过的总方块数量(包括起点和终点) + int totalBlocksToCheck = 1; - // 初始化DDA步进参数 - if (dimAbsX == 0f) { - x_inc = 0; t_next_x = dimFracX; - } else if (target.x > start.x) { - x_inc = 1; - intersectCount += MiscUtils.fastFloor(target.x) - startX; - t_next_x = (startX + 1 - start.x) * dimFracX; + // 各方块坐标的步进方向(1: 正向, -1: 反向, 0: 静止) + int stepDirectionX, stepDirectionY, stepDirectionZ; + + // 到下一个方块边界的时间参数(射线参数化表示) + double nextStepTimeX, nextStepTimeY, nextStepTimeZ; + + // X方向步进参数计算 + if (absDeltaX == 0.0) { + // X方向无变化,射线平行于YZ平面 + stepDirectionX = 0; + nextStepTimeX = stepIncrementX; + } else if (currentTarget.x > start.x) { + // 目标在起点右侧,向右步进 + stepDirectionX = 1; + totalBlocksToCheck += MiscUtils.floor(currentTarget.x) - startBlockX; + nextStepTimeX = (startBlockX + 1 - start.x) * stepIncrementX; } else { - x_inc = -1; - intersectCount += startX - MiscUtils.fastFloor(target.x); - t_next_x = (start.x - startX) * dimFracX; + // 目标在起点左侧,向左步进 + stepDirectionX = -1; + totalBlocksToCheck += startBlockX - MiscUtils.floor(currentTarget.x); + nextStepTimeX = (start.x - startBlockX) * stepIncrementX; } - if (dimAbsY == 0f) { - y_inc = 0; t_next_y = dimFracY; - } else if (target.y > start.y) { - y_inc = 1; - intersectCount += MiscUtils.fastFloor(target.y) - startY; - t_next_y = (startY + 1 - start.y) * dimFracY; + // Y方向步进参数计算 + if (absDeltaY == 0.0) { + // Y方向无变化,射线平行于XZ平面 + stepDirectionY = 0; + nextStepTimeY = stepIncrementY; + } else if (currentTarget.y > start.y) { + // 目标在起点上方,向上步进 + stepDirectionY = 1; + totalBlocksToCheck += MiscUtils.floor(currentTarget.y) - startBlockY; + nextStepTimeY = (startBlockY + 1 - start.y) * stepIncrementY; } else { - y_inc = -1; - intersectCount += startY - MiscUtils.fastFloor(target.y); - t_next_y = (start.y - startY) * dimFracY; + // 目标在起点下方,向下步进 + stepDirectionY = -1; + totalBlocksToCheck += startBlockY - MiscUtils.floor(currentTarget.y); + nextStepTimeY = (start.y - startBlockY) * stepIncrementY; } - if (dimAbsZ == 0f) { - z_inc = 0; t_next_z = dimFracZ; - } else if (target.z > start.z) { - z_inc = 1; - intersectCount += MiscUtils.fastFloor(target.z) - startZ; - t_next_z = (startZ + 1 - start.z) * dimFracZ; + // Z方向步进参数计算 + if (absDeltaZ == 0.0) { + // Z方向无变化,射线平行于XY平面 + stepDirectionZ = 0; + nextStepTimeZ = stepIncrementZ; + } else if (currentTarget.z > start.z) { + // 目标在起点前方,向前步进 + stepDirectionZ = 1; + totalBlocksToCheck += MiscUtils.floor(currentTarget.z) - startBlockZ; + nextStepTimeZ = (startBlockZ + 1 - start.z) * stepIncrementZ; } else { - z_inc = -1; - intersectCount += startZ - MiscUtils.fastFloor(target.z); - t_next_z = (start.z - startZ) * dimFracZ; + // 目标在起点后方,向后步进 + stepDirectionZ = -1; + totalBlocksToCheck += startBlockZ - MiscUtils.floor(currentTarget.z); + nextStepTimeZ = (start.z - startBlockZ) * stepIncrementZ; } - boolean finished = stepRay(startX, startY, startZ, - dimFracX, dimFracY, dimFracZ, intersectCount, - x_inc, y_inc, z_inc, - t_next_y, t_next_x, t_next_z); - - provider.cleanup(); - if (finished) { - cacheResult(targets[0], true); + // 执行DDA步进算法,遍历射线路径上的所有方块 + boolean isLineOfSightClear = stepRay( + startBlockX, startBlockY, startBlockZ, + stepIncrementX, stepIncrementY, stepIncrementZ, totalBlocksToCheck, + stepDirectionX, stepDirectionY, stepDirectionZ, + nextStepTimeY, nextStepTimeX, nextStepTimeZ); + + // 如果当前目标点可见立即返回 + if (isLineOfSightClear) { return true; - } else { - allowRayChecks = true; } } - cacheResult(targets[0], false); + return false; } - private boolean stepRay(int currentX, int currentY, int currentZ, - double distInX, double distInY, double distInZ, - int n, int x_inc, int y_inc, int z_inc, - double t_next_y, double t_next_x, double t_next_z) { - - allowWallClipping = true; // 初始允许穿墙直到移出起始方块 + private boolean stepRay(int currentBlockX, int currentBlockY, int currentBlockZ, + double stepSizeX, double stepSizeY, double stepSizeZ, + int remainingSteps, int stepDirectionX, int stepDirectionY, + int stepDirectionZ, double nextStepTimeY, double nextStepTimeX, + double nextStepTimeZ) { - for (; n > 1; n--) { - // 检查缓存状态:2=遮挡 - int cVal = getCacheValue(currentX, currentY, currentZ); - if (cVal == 2 && !allowWallClipping) { - lastHitBlock[0] = currentX; lastHitBlock[1] = currentY; lastHitBlock[2] = currentZ; - return false; + // 遍历射线路径上的所有方块(跳过最后一个目标方块) + for (; remainingSteps > 1; remainingSteps--) { + + // 检查当前方块是否遮挡视线 + if (isOccluding(currentBlockX, currentBlockY, currentBlockZ)) { + return false; // 视线被遮挡,立即返回 } - if (cVal == 0) { - // 未缓存,查询Provider - int chunkX = currentX >> 4; - int chunkZ = currentZ >> 4; - if (!provider.prepareChunk(chunkX, chunkZ)) return false; - - if (provider.isOpaqueFullCube(currentX, currentY, currentZ)) { - if (!allowWallClipping) { - cache.setLastHidden(); - lastHitBlock[0] = currentX; lastHitBlock[1] = currentY; lastHitBlock[2] = currentZ; - return false; - } - } else { - allowWallClipping = false; - cache.setLastVisible(); - } - } else if(cVal == 1) { - allowWallClipping = false; - } - - // DDA算法选择下一个体素 - if (t_next_y < t_next_x && t_next_y < t_next_z) { - currentY += y_inc; - t_next_y += distInY; - } else if (t_next_x < t_next_y && t_next_x < t_next_z) { - currentX += x_inc; - t_next_x += distInX; + // 基于时间参数选择下一个要遍历的方块方向 + // 选择距离最近的方块边界作为下一步 + if (nextStepTimeY < nextStepTimeX && nextStepTimeY < nextStepTimeZ) { + // Y方向边界最近,垂直移动 + currentBlockY += stepDirectionY; + nextStepTimeY += stepSizeY; + } else if (nextStepTimeX < nextStepTimeY && nextStepTimeX < nextStepTimeZ) { + // X方向边界最近,水平移动 + currentBlockX += stepDirectionX; + nextStepTimeX += stepSizeX; } else { - currentZ += z_inc; - t_next_z += distInZ; + // Z方向边界最近,深度移动 + currentBlockZ += stepDirectionZ; + nextStepTimeZ += stepSizeZ; } } + + // 成功遍历所有中间方块,视线通畅 return true; } - // 缓存状态:-1=无效, 0=未检查, 1=可见, 2=遮挡 - private int getCacheValue(int x, int y, int z) { - x -= cameraPos[0]; - y -= cameraPos[1]; - z -= cameraPos[2]; - if (Math.abs(x) > reach - 2 || Math.abs(y) > reach - 2 || Math.abs(z) > reach - 2) { - return -1; + private double distanceSq(int min, int max, double camera, Relative rel) { + if (rel == Relative.NEGATIVE) { + double dx = camera - max; + return dx * dx; + } else if (rel == Relative.POSITIVE) { + double dx = min - camera; + return dx * dx; } - return cache.getState(x + reach, y + reach, z + reach); + return 0d; } - private void cacheResult(MutableVec3d vector, boolean result) { - int cx = MiscUtils.fastFloor(vector.x) - cameraPos[0] + reach; - int cy = MiscUtils.fastFloor(vector.y) - cameraPos[1] + reach; - int cz = MiscUtils.fastFloor(vector.z) - cameraPos[2] + reach; - if (result) cache.setVisible(cx, cy, cz); - else cache.setHidden(cx, cy, cz); - } - - public void resetCache() { - this.cache.resetCache(); + private boolean isOccluding(int x, int y, int z) { + ClientChunk trackedChunk = this.player.getTrackedChunk(ChunkPos.asLong(x >> 4, z >> 4)); + if (trackedChunk == null) { + return false; + } + return trackedChunk.isOccluding(x, y, z); } private enum Relative { INSIDE, POSITIVE, NEGATIVE; - public static Relative from(int min, int max, int pos) { - if (max > pos && min > pos) return POSITIVE; - else if (min < pos && max < pos) return NEGATIVE; + public static Relative from(int min, int max, double pos) { + if (min > pos) return POSITIVE; + else if (max < pos) return NEGATIVE; return INSIDE; } } - - public interface OcclusionCache { - void resetCache(); - void setVisible(int x, int y, int z); - void setHidden(int x, int y, int z); - int getState(int x, int y, int z); - void setLastHidden(); - void setLastVisible(); - } - - // 使用位运算压缩存储状态的缓存实现 - public static class ArrayOcclusionCache implements OcclusionCache { - private final int reachX2; - private final byte[] cache; - private int entry, offset; - - public ArrayOcclusionCache(int reach) { - this.reachX2 = reach * 2; - // 每一个位置占2位 - this.cache = new byte[(reachX2 * reachX2 * reachX2) / 4 + 1]; - } - - @Override - public void resetCache() { - Arrays.fill(cache, (byte) 0); - } - - private void calcIndex(int x, int y, int z) { - int positionKey = x + y * reachX2 + z * reachX2 * reachX2; - entry = positionKey / 4; - offset = (positionKey % 4) * 2; - } - - @Override - public void setVisible(int x, int y, int z) { - calcIndex(x, y, z); - cache[entry] |= 1 << offset; - } - - @Override - public void setHidden(int x, int y, int z) { - calcIndex(x, y, z); - cache[entry] |= 1 << (offset + 1); - } - - @Override - public int getState(int x, int y, int z) { - calcIndex(x, y, z); - return (cache[entry] >> offset) & 3; - } - - @Override - public void setLastVisible() { - cache[entry] |= 1 << offset; - } - - @Override - public void setLastHidden() { - cache[entry] |= 1 << (offset + 1); - } - } -} \ No newline at end of file +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/VoxelIterator.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/VoxelIterator.java new file mode 100644 index 000000000..a77160fa1 --- /dev/null +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/entityculling/VoxelIterator.java @@ -0,0 +1,216 @@ +package net.momirealms.craftengine.core.plugin.entityculling; + +import java.util.Iterator; +import java.util.NoSuchElementException; + +// Amanatides, J., & Woo, A. A Fast Voxel Traversal Algorithm for Ray Tracing. http://www.cse.yorku.ca/~amana/research/grid.pdf. +public final class VoxelIterator implements Iterator { + private int x; + private int y; + private int z; + private int stepX; + private int stepY; + private int stepZ; + private double tMax; + private double tMaxX; + private double tMaxY; + private double tMaxZ; + private double tDeltaX; + private double tDeltaY; + private double tDeltaZ; + private int[] ref = new int[3]; // This implementation always returns ref or refSwap to avoid garbage. Can easily be changed if needed. + private int[] refSwap = new int[3]; + private int[] next; + + public VoxelIterator(double startX, double startY, double startZ, double endX, double endY, double endZ) { + initialize(startX, startY, startZ, endX, endY, endZ); + } + + public VoxelIterator(int x, int y, int z, double startX, double startY, double startZ, double endX, double endY, double endZ) { + initialize(x, y, z, startX, startY, startZ, endX, endY, endZ); + } + + public VoxelIterator(double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { + initialize(startX, startY, startZ, directionX, directionY, directionZ, distance); + } + + public VoxelIterator(int x, int y, int z, double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { + initialize(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, distance); + } + + public VoxelIterator(double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance, boolean normalized) { + if (normalized) { + initializeNormalized(startX, startY, startZ, directionX, directionY, directionZ, distance); + } else { + initialize(startX, startY, startZ, directionX, directionY, directionZ, distance); + } + } + + public VoxelIterator(int x, int y, int z, double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance, boolean normalized) { + if (normalized) { + initializeNormalized(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, distance); + } else { + initialize(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, distance); + } + } + + public VoxelIterator initialize(double startX, double startY, double startZ, double endX, double endY, double endZ) { + return initialize(floor(startX), floor(startY), floor(startZ), startX, startY, startZ, endX, endY, endZ); + } + + public VoxelIterator initialize(int x, int y, int z, double startX, double startY, double startZ, double endX, double endY, double endZ) { + double directionX = endX - startX; + double directionY = endY - startY; + double directionZ = endZ - startZ; + double distance = Math.sqrt(directionX * directionX + directionY * directionY + directionZ * directionZ); + double fixedDistance = distance == 0. ? Double.NaN : distance; + directionX /= fixedDistance; + directionY /= fixedDistance; + directionZ /= fixedDistance; + return initializeNormalized(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, distance); + } + + public VoxelIterator initialize(double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { + return initialize(floor(startX), floor(startY), floor(startZ), startX, startY, startZ, directionX, directionY, directionZ, distance); + } + + public VoxelIterator initialize(int x, int y, int z, double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { + double signum = Math.signum(distance); + directionX *= signum; + directionY *= signum; + directionZ *= signum; + double length = Math.sqrt(directionX * directionX + directionY * directionY + directionZ * directionZ); + + if (length == 0.) { + length = Double.NaN; + } + + directionX /= length; + directionY /= length; + directionZ /= length; + return initializeNormalized(x, y, z, startX, startY, startZ, directionX, directionY, directionZ, Math.abs(distance)); + } + + public VoxelIterator initializeNormalized(double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { + return initializeNormalized(floor(startX), floor(startY), floor(startZ), startX, startY, startZ, directionX, directionY, directionZ, Math.abs(distance)); + } + + public VoxelIterator initializeNormalized(int x, int y, int z, double startX, double startY, double startZ, double directionX, double directionY, double directionZ, double distance) { + this.x = x; + this.y = y; + this.z = z; + tMax = distance; + stepX = directionX < 0. ? -1 : 1; + stepY = directionY < 0. ? -1 : 1; + stepZ = directionZ < 0. ? -1 : 1; + tMaxX = directionX == 0. ? Double.POSITIVE_INFINITY : (x + (stepX + 1) / 2 - startX) / directionX; + tMaxY = directionY == 0. ? Double.POSITIVE_INFINITY : (y + (stepY + 1) / 2 - startY) / directionY; + tMaxZ = directionZ == 0. ? Double.POSITIVE_INFINITY : (z + (stepZ + 1) / 2 - startZ) / directionZ; + tDeltaX = 1. / Math.abs(directionX); + tDeltaY = 1. / Math.abs(directionY); + tDeltaZ = 1. / Math.abs(directionZ); + next = ref; + ref[0] = x; + ref[1] = y; + ref[2] = z; + return this; + } + + public int[] calculateNext() { + if (tMaxX < tMaxY) { + if (tMaxZ < tMaxX) { + if (tMaxZ <= tMax) { + z += stepZ; + // next = new int[] { x, y, z }; + ref[0] = x; + ref[1] = y; + ref[2] = z; + tMaxZ += tDeltaZ; + } else { + next = null; + } + } else { + if (tMaxX <= tMax) { + if (tMaxZ == tMaxX) { + z += stepZ; + tMaxZ += tDeltaZ; + } + + x += stepX; + // next = new int[] { x, y, z }; + ref[0] = x; + ref[1] = y; + ref[2] = z; + tMaxX += tDeltaX; + } else { + next = null; + } + } + } else if (tMaxY < tMaxZ) { + if (tMaxY <= tMax) { + if (tMaxX == tMaxY) { + x += stepX; + tMaxX += tDeltaX; + } + + y += stepY; + // next = new int[] { x, y, z }; + ref[0] = x; + ref[1] = y; + ref[2] = z; + tMaxY += tDeltaY; + } else { + next = null; + } + } else { + if (tMaxZ <= tMax) { + if (tMaxX == tMaxZ) { + x += stepX; + tMaxX += tDeltaX; + } + + if (tMaxY == tMaxZ) { + y += stepY; + tMaxY += tDeltaY; + } + + z += stepZ; + // next = new int[] { x, y, z }; + ref[0] = x; + ref[1] = y; + ref[2] = z; + tMaxZ += tDeltaZ; + } else { + next = null; + } + } + + return next; + } + + @Override + public boolean hasNext() { + return next != null; + } + + @Override + public int[] next() { + int[] next = this.next; + + if (next == null) { + throw new NoSuchElementException(); + } + + int[] temp = ref; + ref = refSwap; + refSwap = temp; + this.next = ref; + calculateNext(); + return next; + } + + private static int floor(double value) { + int i = (int) value; + return value < (double) i ? i - 1 : i; + } +} diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/Color.java b/core/src/main/java/net/momirealms/craftengine/core/util/Color.java index 5f37d42b0..38299e43b 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/Color.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/Color.java @@ -38,7 +38,7 @@ public class Color { } public static Color fromVector3f(Vector3f vec) { - return new Color(0 << 24 /*不可省略*/ | MiscUtils.fastFloor(vec.x) << 16 | MiscUtils.fastFloor(vec.y) << 8 | MiscUtils.fastFloor(vec.z)); + return new Color(0 << 24 /*不可省略*/ | MiscUtils.floor(vec.x) << 16 | MiscUtils.floor(vec.y) << 8 | MiscUtils.floor(vec.z)); } public static int opaque(int color) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java index 2e28001c2..523bbc364 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java +++ b/core/src/main/java/net/momirealms/craftengine/core/util/MiscUtils.java @@ -21,19 +21,19 @@ public class MiscUtils { } }); - public static int fastFloor(double value) { + public static int floor(double value) { int truncated = (int) value; return value < (double) truncated ? truncated - 1 : truncated; } - public static int fastFloor(float value) { + public static int floor(float value) { int truncated = (int) value; return value < (double) truncated ? truncated - 1 : truncated; } public static int lerpDiscrete(float delta, int start, int end) { int i = end - start; - return start + fastFloor(delta * (float) (i - 1)) + (delta > 0.0F ? 1 : 0); + return start + floor(delta * (float) (i - 1)) + (delta > 0.0F ? 1 : 0); } public static int murmurHash3Mixer(int value) { @@ -270,7 +270,7 @@ public class MiscUtils { } public static byte packDegrees(float degrees) { - return (byte) fastFloor(degrees * 256.0F / 360.0F); + return (byte) floor(degrees * 256.0F / 360.0F); } public static float unpackDegrees(byte degrees) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java b/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java index 38a9acf20..71838e8c3 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/BlockPos.java @@ -23,7 +23,7 @@ public class BlockPos extends Vec3i { } public static BlockPos fromVec3d(Vec3d vec) { - return new BlockPos(MiscUtils.fastFloor(vec.x), MiscUtils.fastFloor(vec.y), MiscUtils.fastFloor(vec.z)); + return new BlockPos(MiscUtils.floor(vec.x), MiscUtils.floor(vec.y), MiscUtils.floor(vec.z)); } public static BlockPos of(long packedPos) { diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/EntityHitResult.java b/core/src/main/java/net/momirealms/craftengine/core/world/EntityHitResult.java index 6944337ed..fed33e417 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/EntityHitResult.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/EntityHitResult.java @@ -23,9 +23,9 @@ public class EntityHitResult { } private BlockPos getBlockPos() { - int x = MiscUtils.fastFloor(this.position.x); - int y = MiscUtils.fastFloor(this.position.y); - int z = MiscUtils.fastFloor(this.position.z); + int x = MiscUtils.floor(this.position.x); + int y = MiscUtils.floor(this.position.y); + int z = MiscUtils.floor(this.position.z); if (this.direction == Direction.UP) { if (this.position.y % 1 == 0) { y--; diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/MutableVec3d.java b/core/src/main/java/net/momirealms/craftengine/core/world/MutableVec3d.java index 1f4a989ac..dd469e7ec 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/MutableVec3d.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/MutableVec3d.java @@ -14,9 +14,9 @@ public class MutableVec3d implements Position { } public MutableVec3d toCenter() { - this.x = MiscUtils.fastFloor(x) + 0.5; - this.y = MiscUtils.fastFloor(y) + 0.5; - this.z = MiscUtils.fastFloor(z) + 0.5; + this.x = MiscUtils.floor(x) + 0.5; + this.y = MiscUtils.floor(y) + 0.5; + this.z = MiscUtils.floor(z) + 0.5; return this; } diff --git a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java index 7165596a0..fe3cb6938 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java +++ b/core/src/main/java/net/momirealms/craftengine/core/world/Vec3d.java @@ -15,7 +15,7 @@ public class Vec3d implements Position { } public Vec3d toCenter() { - return new Vec3d(MiscUtils.fastFloor(x) + 0.5, MiscUtils.fastFloor(y) + 0.5, MiscUtils.fastFloor(z) + 0.5); + return new Vec3d(MiscUtils.floor(x) + 0.5, MiscUtils.floor(y) + 0.5, MiscUtils.floor(z) + 0.5); } public Vec3d add(Vec3d vec) { 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 f12123ae2..4ca2fe5df 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 @@ -14,6 +14,7 @@ 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.client.VirtualCullableObject; import net.momirealms.craftengine.core.world.chunk.serialization.DefaultBlockEntityRendererSerializer; import net.momirealms.craftengine.core.world.chunk.serialization.DefaultBlockEntitySerializer; import net.momirealms.sparrow.nbt.ListTag; @@ -155,8 +156,20 @@ public class CEChunk { if (element != null) { elements[0] = element; if (hasTrackedBy) { - for (Player player : trackedBy) { - element.transform(player); + // 如果启用实体剔除,那么只对已经渲染的进行变换 + if (Config.enableEntityCulling()) { + for (Player player : trackedBy) { + VirtualCullableObject trackedBlockEntity = player.getTrackedBlockEntity(pos); + if (trackedBlockEntity == null || trackedBlockEntity.isShown()) { + element.transform(player); + } + } + } + // 否则直接变换 + else { + for (Player player : trackedBy) { + element.transform(player); + } } } break outer; @@ -165,9 +178,18 @@ public class CEChunk { BlockEntityElement element = config.create(wrappedWorld, pos); elements[0] = element; if (hasTrackedBy) { - for (Player player : trackedBy) { - previousElement.hide(player); - element.show(player); + // 如果启用实体剔除,那么只添加记录 + if (Config.enableEntityCulling()) { + for (Player player : trackedBy) { + player.addTrackedBlockEntity(pos, renderer); + } + } + // 否则直接显示 + else { + for (Player player : trackedBy) { + previousElement.hide(player); + element.show(player); + } } } } @@ -182,8 +204,20 @@ public class CEChunk { previousElements[j] = null; elements[i] = newElement; if (hasTrackedBy) { - for (Player player : trackedBy) { - newElement.transform(player); + // 如果启用实体剔除,那么只对已经渲染的进行变换 + if (Config.enableEntityCulling()) { + for (Player player : trackedBy) { + VirtualCullableObject trackedBlockEntity = player.getTrackedBlockEntity(pos); + if (trackedBlockEntity == null || trackedBlockEntity.isShown()) { + newElement.transform(player); + } + } + } + // 否则直接变换 + else { + for (Player player : trackedBy) { + newElement.transform(player); + } } } continue outer; @@ -193,8 +227,14 @@ public class CEChunk { BlockEntityElement newElement = config.create(wrappedWorld, pos); elements[i] = newElement; if (hasTrackedBy) { - for (Player player : trackedBy) { - newElement.show(player); + if (Config.enableEntityCulling()) { + for (Player player : trackedBy) { + player.addTrackedBlockEntity(pos, renderer); + } + } else { + for (Player player : trackedBy) { + newElement.show(player); + } } } } @@ -214,8 +254,14 @@ public class CEChunk { elements[i] = renderers[i].create(wrappedWorld, pos); } if (hasTrackedBy) { - for (Player player : trackedBy) { - renderer.show(player); + if (Config.enableEntityCulling()) { + for (Player player : trackedBy) { + player.addTrackedBlockEntity(pos, renderer); + } + } else { + for (Player player : trackedBy) { + renderer.show(player); + } } } } @@ -242,8 +288,14 @@ public class CEChunk { ConstantBlockEntityRenderer removed = this.constantBlockEntityRenderers.remove(pos); if (removed != null) { if (hide) { - for (Player player : getTrackedBy()) { - removed.hide(player); + if (Config.enableEntityCulling()) { + for (Player player : getTrackedBy()) { + player.removeTrackedBlockEntities(List.of(pos)); + } + } else { + for (Player player : getTrackedBy()) { + removed.hide(player); + } } } } 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 index 29379fd21..03e0ac6ad 100644 --- 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 @@ -32,7 +32,7 @@ public class ClientChunk { int index = sectionIndex(SectionPos.blockToSectionCoord(y)); ClientSection section = this.sections[index]; if (section == null) return; - section.setOccluding((y & 15) << 8 | (z & 15) << 4, occluding); + section.setOccluding((y & 15) << 8 | (z & 15) << 4 | x & 15, occluding); } public int sectionIndex(int sectionId) { 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 index 127b8b09e..80b6f9ef2 100644 --- 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 @@ -4,7 +4,7 @@ import net.momirealms.craftengine.core.entity.player.Player; import net.momirealms.craftengine.core.world.Cullable; public class VirtualCullableObject { - private final Cullable cullable; + public final Cullable cullable; private boolean isShown; public VirtualCullableObject(Cullable cullable) { @@ -21,6 +21,7 @@ public class VirtualCullableObject { } public void setShown(Player player, boolean shown) { + if (this.isShown == shown) return; this.isShown = shown; if (shown) { this.cullable.show(player);