diff --git a/shiroha-server/minecraft-patches/features/0074-Frustum-filtering-tracker.patch b/shiroha-server/minecraft-patches/features/0074-Frustum-filtering-tracker.patch new file mode 100644 index 0000000..4381098 --- /dev/null +++ b/shiroha-server/minecraft-patches/features/0074-Frustum-filtering-tracker.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: NanaChiyo0721 +Date: Thu, 16 Jul 2026 23:04:23 +0800 +Subject: [PATCH] Frustum filtering tracker + + +diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java +index 849ad46b2941ebba91b428846b3b20d11ee5e1c9..3291d17bc176b306a792a1ca59254615b188ee9e 100644 +--- a/net/minecraft/server/level/ChunkMap.java ++++ b/net/minecraft/server/level/ChunkMap.java +@@ -932,6 +932,7 @@ public class ChunkMap extends SimpleRegionStorage implements ChunkHolder.PlayerP + public void move(final ServerPlayer player) { + // Paper - optimise entity tracker + ++ if (player.culling != null) player.culling.updateFrustum(); // Shiroha - Frustum filtering tracker + SectionPos oldSection = player.getLastSectionPos(); + SectionPos newSection = SectionPos.of(player); + //boolean wasIgnored = this.playerMap.ignored(player); // Folia - region threading +@@ -1054,6 +1055,11 @@ public class ChunkMap extends SimpleRegionStorage implements ChunkHolder.PlayerP + final ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup entityLookup = (ca.spottedleaf.moonrise.patches.chunk_system.level.entity.server.ServerEntityLookup)((ca.spottedleaf.moonrise.patches.chunk_system.level.ChunkSystemServerLevel)this.level).moonrise$getEntityLookup();; + final ca.spottedleaf.moonrise.common.misc.NearbyPlayers nearbyPlayers = this.level.moonrise$getNearbyPlayers(); // Folia - region threading + ++ // Shiroha start - Frustum filtering tracker ++ for (var player : worldData.getLocalPlayers()) { ++ if (player.culling != null) player.culling.updateFrustum(); ++ } ++ // Shiroha end - Frustum filtering tracker + final ca.spottedleaf.moonrise.common.list.ReferenceList trackerEntities = worldData.trackerEntities; // Folia - region threading + final Entity[] trackerEntitiesRaw = trackerEntities.getRawDataUnchecked(); + for (int i = 0, len = totalEntities = trackerEntities.size(); i < len; ++i) { // Folia - region threading +@@ -1371,6 +1377,15 @@ public class ChunkMap extends SimpleRegionStorage implements ChunkHolder.PlayerP + if (visibleToPlayer && (!ca.spottedleaf.moonrise.common.util.TickThread.isTickThreadFor(player) || !player.getBukkitEntity().canSee(this.entity.getBukkitEntity()))) { // Paper - only consider hits // Folia - region threading + visibleToPlayer = false; + } ++ // Shiroha start - Frustum filtering tracker ++ if (player.culling != null) { ++ player.culling.far((float) Math.sqrt(distanceSquared)); ++ ++ if (visibleToPlayer && !player.isSpectator() && distanceSquared > ((int)io.nanachiyo0721.shiroha.config.modules.optimizations.FrustumFilteringTrackerConfig.minForceVisibleDistance ^ 2)) { ++ visibleToPlayer = player.culling.isVisibleFor(this.entity.getBoundingBox()); ++ } ++ } ++ // Shiroha end - Frustum filtering tracker + // CraftBukkit end + if (visibleToPlayer) { + if (this.seenBy.add(player.connection)) { +diff --git a/net/minecraft/world/entity/player/Player.java b/net/minecraft/world/entity/player/Player.java +index e5e5e1da691dc71dc806d3b039ff02b1fa50eacf..81c5a042b288fb1208bd71239b16f65dd63e69d6 100644 +--- a/net/minecraft/world/entity/player/Player.java ++++ b/net/minecraft/world/entity/player/Player.java +@@ -186,6 +186,8 @@ public abstract class Player extends Avatar implements ContainerUser { + } + // CraftBukkit end + ++ public io.nanachiyo0721.shiroha.utils.Culling culling = io.nanachiyo0721.shiroha.utils.Culling.newDefault(this); // Shiroha - Frustum filtering tracker ++ + public Player(final Level level, final GameProfile gameProfile) { + super(EntityTypes.PLAYER, level); + this.setUUID(gameProfile.id()); diff --git a/shiroha-server/src/main/java/io/nanachiyo0721/shiroha/config/modules/optimizations/FrustumFilteringTrackerConfig.java b/shiroha-server/src/main/java/io/nanachiyo0721/shiroha/config/modules/optimizations/FrustumFilteringTrackerConfig.java new file mode 100644 index 0000000..4093407 --- /dev/null +++ b/shiroha-server/src/main/java/io/nanachiyo0721/shiroha/config/modules/optimizations/FrustumFilteringTrackerConfig.java @@ -0,0 +1,14 @@ +package io.nanachiyo0721.shiroha.config.modules.optimizations; + +import io.nanachiyo0721.shiroha.config.IConfigModule; +import io.nanachiyo0721.shiroha.config.flags.ConfigClassInfo; +import io.nanachiyo0721.shiroha.config.flags.ConfigInfo; +import io.nanachiyo0721.shiroha.enums.EnumConfigCategory; + +@ConfigClassInfo(name = "frustum_filtering_tracker", category = EnumConfigCategory.OPTIMIZATIONS) +public class FrustumFilteringTrackerConfig implements IConfigModule { + @ConfigInfo(name = "enabled") + public static boolean enabled = false; + @ConfigInfo(name = "min_force_visible_distance") + public static double minForceVisibleDistance = 5.0D; +} diff --git a/shiroha-server/src/main/java/io/nanachiyo0721/shiroha/utils/Culling.java b/shiroha-server/src/main/java/io/nanachiyo0721/shiroha/utils/Culling.java new file mode 100644 index 0000000..ee79e13 --- /dev/null +++ b/shiroha-server/src/main/java/io/nanachiyo0721/shiroha/utils/Culling.java @@ -0,0 +1,286 @@ +package io.nanachiyo0721.shiroha.utils; + +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.NonNull; + +public class Culling { + private static final float VERTICAL_FOV_RAD = (float) Math.toRadians(120); + private static final float ASPECT = 1.0f; + private static final float NEAR = 0.1f; + private static final float FAR = 32.0f; + + private float fov; + private float aspect; + private float near; + private float far; + + private final Player player; + private float[] planes; + + @Contract(value = "_ -> new", pure = true) + public static @NonNull Culling newDefault(Player player) { + return fromBuilder(new Builder(), player); + } + + @Contract(value = "_, _ -> new", pure = true) + public static @NonNull Culling fromBuilder(@NonNull Builder builder, Player player) { + return new Culling(builder.fov, builder.aspect, builder.near, builder.far, player); + } + + public static class Builder { + private float fov; + private float aspect; + private float near; + private float far; + + + public Builder() { + this.fov = VERTICAL_FOV_RAD; + this.aspect = ASPECT; + this.near = NEAR; + this.far = FAR; + } + + public Builder fov(float fov) { + this.fov = fov; + return this; + } + + public Builder aspect(float aspect) { + this.aspect = aspect; + return this; + } + + public Builder near(float near) { + this.near = near; + return this; + } + + public Builder far(float far) { + this.far = far; + return this; + } + + public Culling build(Player player) { + return fromBuilder(this, player); + } + } + + public Culling(float fov, float aspect, float near, float far, Player player) { + this.fov = fov; + this.aspect = aspect; + this.near = near; + this.far = far; + this.player = player; + } + + public static float @NonNull [] buildFrustumPlanes( + float eyeX, float eyeY, float eyeZ, + float forwardX, float forwardY, float forwardZ, + float upX, float upY, float upZ, + float verticalFovRad, float aspect, float nearDist, float farDist) { + float lenF = (float) Math.sqrt(forwardX * forwardX + forwardY * forwardY + forwardZ * forwardZ); + float fx = forwardX / lenF; + float fy = forwardY / lenF; + float fz = forwardZ / lenF; + + float rx = fy * upZ - fz * upY; + float ry = fz * upX - fx * upZ; + float rz = fx * upY - fy * upX; + float lenR = (float) Math.sqrt(rx * rx + ry * ry + rz * rz); + + if (lenR < 1.0e-4f) { + float fallbackUpX = 0.0f, fallbackUpY = 0.0f, fallbackUpZ = 1.0f; + if (Math.abs(fz) > 0.999f) { + fallbackUpX = 1.0f; + fallbackUpY = 0.0f; + fallbackUpZ = 0.0f; + } + rx = fy * fallbackUpZ - fz * fallbackUpY; + ry = fz * fallbackUpX - fx * fallbackUpZ; + rz = fx * fallbackUpY - fy * fallbackUpX; + lenR = (float) Math.sqrt(rx * rx + ry * ry + rz * rz); + } + + rx /= lenR; + ry /= lenR; + rz /= lenR; + + float ux = ry * fz - rz * fy; + float uy = rz * fx - rx * fz; + float uz = rx * fy - ry * fx; + float lenU = (float) Math.sqrt(ux * ux + uy * uy + uz * uz); + ux /= lenU; + uy /= lenU; + uz /= lenU; + + float tanHalf = (float) Math.tan(verticalFovRad * 0.5); + float halfVNear = tanHalf * nearDist; + float halfHNear = halfVNear * aspect; + float halfVFar = tanHalf * farDist; + float halfHFar = halfVFar * aspect; + + float nearCX = eyeX + fx * nearDist; + float nearCY = eyeY + fy * nearDist; + float nearCZ = eyeZ + fz * nearDist; + + float farCX = eyeX + fx * farDist; + float farCY = eyeY + fy * farDist; + float farCZ = eyeZ + fz * farDist; + + float ntlX = nearCX + ux * halfVNear - rx * halfHNear; + float ntlY = nearCY + uy * halfVNear - ry * halfHNear; + float ntlZ = nearCZ + uz * halfVNear - rz * halfHNear; + + float ntrX = nearCX + ux * halfVNear + rx * halfHNear; + float ntrY = nearCY + uy * halfVNear + ry * halfHNear; + float ntrZ = nearCZ + uz * halfVNear + rz * halfHNear; + + float nblX = nearCX - ux * halfVNear - rx * halfHNear; + float nblY = nearCY - uy * halfVNear - ry * halfHNear; + float nblZ = nearCZ - uz * halfVNear - rz * halfHNear; + + float nbrX = nearCX - ux * halfVNear + rx * halfHNear; + float nbrY = nearCY - uy * halfVNear + ry * halfHNear; + float nbrZ = nearCZ - uz * halfVNear + rz * halfHNear; + + float ftlX = farCX + ux * halfVFar - rx * halfHFar; + float ftlY = farCY + uy * halfVFar - ry * halfHFar; + float ftlZ = farCZ + uz * halfVFar - rz * halfHFar; + + float ftrX = farCX + ux * halfVFar + rx * halfHFar; + float ftrY = farCY + uy * halfVFar + ry * halfHFar; + float ftrZ = farCZ + uz * halfVFar + rz * halfHFar; + + float fbrX = farCX - ux * halfVFar + rx * halfHFar; + float fbrY = farCY - uy * halfVFar + ry * halfHFar; + float fbrZ = farCZ - uz * halfVFar + rz * halfHFar; + + float midDist = (nearDist + farDist) * 0.5f; + float refX = eyeX + fx * midDist; + float refY = eyeY + fy * midDist; + float refZ = eyeZ + fz * midDist; + + float[] planes = new float[24]; + + int idx = 0; + + idx = computePlane(planes, idx, + eyeX, eyeY, eyeZ, + ntlX, ntlY, ntlZ, + nblX, nblY, nblZ, + refX, refY, refZ); + + idx = computePlane(planes, idx, + eyeX, eyeY, eyeZ, + nbrX, nbrY, nbrZ, + ntrX, ntrY, ntrZ, + refX, refY, refZ); + + idx = computePlane(planes, idx, + eyeX, eyeY, eyeZ, + nblX, nblY, nblZ, + nbrX, nbrY, nbrZ, + refX, refY, refZ); + + idx = computePlane(planes, idx, + eyeX, eyeY, eyeZ, + ntrX, ntrY, ntrZ, + ntlX, ntlY, ntlZ, + refX, refY, refZ); + + idx = computePlane(planes, idx, + ntlX, ntlY, ntlZ, + ntrX, ntrY, ntrZ, + nblX, nblY, nblZ, + refX, refY, refZ); + + idx = computePlane(planes, idx, + ftrX, ftrY, ftrZ, + ftlX, ftlY, ftlZ, + fbrX, fbrY, fbrZ, + refX, refY, refZ); + + return planes; + } + + private static int computePlane(float[] out, int start, + float p0x, float p0y, float p0z, + float p1x, float p1y, float p1z, + float p2x, float p2y, float p2z, + float refX, float refY, float refZ) { + float e1x = p1x - p0x, e1y = p1y - p0y, e1z = p1z - p0z; + float e2x = p2x - p0x, e2y = p2y - p0y, e2z = p2z - p0z; + + float nx = e1y * e2z - e1z * e2y; + float ny = e1z * e2x - e1x * e2z; + float nz = e1x * e2y - e1y * e2x; + + float d = -(nx * p0x + ny * p0y + nz * p0z); + float val = nx * refX + ny * refY + nz * refZ + d; + + if (val < 0.0f) { + nx = -nx; + ny = -ny; + nz = -nz; + d = -d; + } + + float len = (float) Math.sqrt(nx * nx + ny * ny + nz * nz); + nx /= len; + ny /= len; + nz /= len; + + d = -(nx * p0x + ny * p0y + nz * p0z); + + out[start] = nx; + out[start + 1] = ny; + out[start + 2] = nz; + out[start + 3] = d; + + return start + 4; + } + + public void apply(@NonNull Builder builder) { + this.fov = builder.fov; + this.aspect = builder.aspect; + this.near = builder.near; + this.far = builder.far; + } + + public void updateFrustum() { + Vec3 eye = this.player.getEyePosition(); + Vec3 look = this.player.getLookAngle(); + this.planes = buildFrustumPlanes( + (float) eye.x, (float) eye.y, (float) eye.z, + (float) look.x, (float) look.y, (float) look.z, + 0.0f, 1.0f, 0.0f, + this.fov, this.aspect, this.near, this.far + ); + } + + public void far(float far) { + this.far = far; + } + + public boolean isVisibleFor(@NonNull AABB box) { + final float[] planes = this.planes; + + for (int i = 0; i < 6; i++) { + int base = i << 2; + float nx = planes[base], ny = planes[base+1], nz = planes[base+2], d = planes[base+3]; + float px = (nx >= 0) ? (float) box.maxX : (float) box.minX; + float py = (ny >= 0) ? (float) box.maxY : (float) box.minY; + float pz = (nz >= 0) ? (float) box.maxZ : (float) box.minZ; + + if (nx * px + ny * py + nz * pz + d < 0) { + return false; + } + } + + return true; + } +} \ No newline at end of file