9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-19 15:09:15 +00:00

初步剔除

This commit is contained in:
XiaoMoMi
2025-11-28 22:06:06 +08:00
parent f460784460
commit 7eb4dc1ff8
30 changed files with 584 additions and 452 deletions

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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<BlockPos, VirtualCullableObject> 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<BlockPos> renders) {
for (BlockPos render : renders) {

View File

@@ -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)) {

View File

@@ -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:

View File

@@ -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) {

View File

@@ -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<BlockPos, ConstantBlockEntityRenderer> renders);
public abstract void addTrackedBlockEntity(BlockPos blockPos, ConstantBlockEntityRenderer renderer);
public abstract VirtualCullableObject getTrackedBlockEntity(BlockPos blockPos);
public abstract void removeTrackedBlockEntities(Collection<BlockPos> renders);
public abstract void clearTrackedBlockEntities();

View File

@@ -44,7 +44,7 @@ public class LootPool<T> {
}
if (this.compositeCondition.test(context)) {
Consumer<Item<T>> 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);
}

View File

@@ -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 {

View File

@@ -40,7 +40,7 @@ public class MatchBlockCondition<CTX extends Context> implements Condition<CTX>
Optional<WorldPosition> 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;

View File

@@ -28,7 +28,7 @@ public class BreakBlockFunction<CTX extends Context> extends AbstractConditional
@Override
public void runInternal(CTX ctx) {
Optional<Player> 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

View File

@@ -55,9 +55,9 @@ public class CycleBlockPropertyFunction<CTX extends Context> extends AbstractCon
Optional<WorldPosition> 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));
}

View File

@@ -40,7 +40,7 @@ public class PlaceBlockFunction<CTX extends Context> extends AbstractConditional
Optional<WorldPosition> 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));
}
}

View File

@@ -48,7 +48,7 @@ public class RunFunction<CTX extends Context> extends AbstractConditionalFunctio
for (Function<CTX> 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);
}
}
}

View File

@@ -45,9 +45,9 @@ public class TransformBlockFunction<CTX extends Context> extends AbstractConditi
Optional<WorldPosition> 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()) {

View File

@@ -40,9 +40,9 @@ public class UpdateBlockPropertyFunction<CTX extends Context> extends AbstractCo
Optional<WorldPosition> 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));

View File

@@ -20,9 +20,9 @@ public class EntityParameterProvider implements ChainParameterProvider<Entity> {
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);

View File

@@ -21,9 +21,9 @@ public class PlayerParameterProvider implements ChainParameterProvider<Player> {
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);

View File

@@ -20,9 +20,9 @@ public class PositionParameterProvider implements ChainParameterProvider<WorldPo
CONTEXT_FUNCTIONS.put(DirectContextParameters.Z, WorldPosition::z);
CONTEXT_FUNCTIONS.put(DirectContextParameters.YAW, WorldPosition::xRot);
CONTEXT_FUNCTIONS.put(DirectContextParameters.PITCH, WorldPosition::yRot);
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()));
}
@SuppressWarnings("unchecked")

View File

@@ -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);
double cameraX = cameraPos.x;
double cameraY = cameraPos.y;
double cameraZ = cameraPos.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]);
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 (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 absDeltaX = Math.abs(deltaX);
double absDeltaY = Math.abs(deltaY);
double absDeltaZ = Math.abs(deltaZ);
double dimAbsX = Math.abs(relX);
double dimAbsY = Math.abs(relY);
double dimAbsZ = Math.abs(relZ);
// 预计算每单位距离在各方块边界上的步进增量
// 这些值表示射线穿过一个方块所需的时间分数
double stepIncrementX = 1.0 / (absDeltaX + 1e-10); // 避免除0
double stepIncrementY = 1.0 / (absDeltaY + 1e-10);
double stepIncrementZ = 1.0 / (absDeltaZ + 1e-10);
double dimFracX = 1f / dimAbsX;
double dimFracY = 1f / dimAbsY;
double dimFracZ = 1f / dimAbsZ;
// 射线将穿过的总方块数量(包括起点和终点)
int totalBlocksToCheck = 1;
int intersectCount = 1;
int x_inc, y_inc, z_inc;
double t_next_y, t_next_x, t_next_z;
// 各方块坐标的步进方向1: 正向, -1: 反向, 0: 静止)
int stepDirectionX, stepDirectionY, stepDirectionZ;
// 初始化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;
// 到下一个方块边界的时间参数(射线参数化表示)
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);
// 执行DDA步进算法遍历射线路径上的所有方块
boolean isLineOfSightClear = stepRay(
startBlockX, startBlockY, startBlockZ,
stepIncrementX, stepIncrementY, stepIncrementZ, totalBlocksToCheck,
stepDirectionX, stepDirectionY, stepDirectionZ,
nextStepTimeY, nextStepTimeX, nextStepTimeZ);
provider.cleanup();
if (finished) {
cacheResult(targets[0], true);
// 如果当前目标点可见立即返回
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) {
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) {
allowWallClipping = true; // 初始允许穿墙直到移出起始方块
// 遍历射线路径上的所有方块(跳过最后一个目标方块
for (; remainingSteps > 1; remainingSteps--) {
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;
// 检查当前方块是否遮挡视线
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);
}
}
}

View File

@@ -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<int[]> {
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;
}
}

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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--;

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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);
}
}
}
}

View File

@@ -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) {

View File

@@ -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);