Skip to content

Commit c0a52ea

Browse files
committed
EntityCulling
1 parent 987294d commit c0a52ea

File tree

5 files changed

+214
-1
lines changed

5 files changed

+214
-1
lines changed

atdeprecated-server/minecraft-patches/features/0012-Lophine-Add-config-to-enable-Raytracing-tracker.patch

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Date: Thu, 24 Jul 2025 23:04:37 +0700
44
Subject: [PATCH] Lophine: Add config to enable Raytracing tracker
55

66
Co-authored by: MrHua269 <[email protected]>
7-
As part of: Purpur (https://github.com/LuminolMC/Lophine/blob/904abaa56499a869259d3e6b3e586c1d33e34d28/lophine-server/minecraft-patches/features/0006-Add-config-to-enable-Raytracing-tracker.patch)
7+
As part of: Lophine (https://github.com/LuminolMC/Lophine/blob/904abaa56499a869259d3e6b3e586c1d33e34d28/lophine-server/minecraft-patches/features/0006-Add-config-to-enable-Raytracing-tracker.patch)
88
Licensed under: MIT (https://github.com/LuminolMC/Lophine/blob/904abaa56499a869259d3e6b3e586c1d33e34d28/LICENSE.md)
99

1010
diff --git a/net/minecraft/server/level/ChunkMap.java b/net/minecraft/server/level/ChunkMap.java
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package dev.tr7zw.entityculling;
2+
3+
import ca.spottedleaf.moonrise.common.util.TickThread;
4+
import com.logisticscraft.occlusionculling.OcclusionCullingInstance;
5+
import com.logisticscraft.occlusionculling.util.Vec3d;
6+
import dev.tr7zw.entityculling.versionless.access.Cullable;
7+
import fun.mntale.atdeprecated.config.AtCoreConfig;
8+
import net.minecraft.world.entity.Entity;
9+
import net.minecraft.world.entity.decoration.ArmorStand;
10+
import net.minecraft.world.entity.player.Player;
11+
import net.minecraft.world.phys.AABB;
12+
import net.minecraft.world.phys.Vec3;
13+
14+
import java.util.concurrent.CompletableFuture;
15+
import java.util.concurrent.Executor;
16+
import java.util.concurrent.Executors;
17+
import java.util.concurrent.TimeUnit;
18+
19+
public class CullTask implements Runnable {
20+
21+
private volatile boolean requestCull = false;
22+
private volatile boolean scheduleNext = true;
23+
private volatile boolean inited = false;
24+
25+
private final OcclusionCullingInstance culling;
26+
private final Player checkTarget;
27+
28+
private final int hitboxLimit;
29+
30+
public long lastCheckedTime = 0;
31+
32+
// reused preallocated vars
33+
private final Vec3d lastPos = new Vec3d(0, 0, 0);
34+
private final Vec3d aabbMin = new Vec3d(0, 0, 0);
35+
private final Vec3d aabbMax = new Vec3d(0, 0, 0);
36+
37+
private static final Executor backgroundWorker = Executors.newCachedThreadPool(task -> {
38+
final TickThread worker = new TickThread("EntityCulling") {
39+
@Override
40+
public void run() {
41+
task.run();
42+
}
43+
};
44+
45+
worker.setDaemon(true);
46+
47+
return worker;
48+
});
49+
50+
private final Executor worker;
51+
52+
public CullTask(
53+
OcclusionCullingInstance culling,
54+
Player checkTarget,
55+
int hitboxLimit,
56+
long checkIntervalMs
57+
) {
58+
this.culling = culling;
59+
this.checkTarget = checkTarget;
60+
this.hitboxLimit = hitboxLimit;
61+
this.worker = CompletableFuture.delayedExecutor(checkIntervalMs, TimeUnit.MILLISECONDS, backgroundWorker);
62+
}
63+
64+
public void requestCullSignal() {
65+
this.requestCull = true;
66+
}
67+
68+
public void signalStop() {
69+
this.scheduleNext = false;
70+
}
71+
72+
public void setup() {
73+
if (!this.inited)
74+
this.inited = true;
75+
else
76+
return;
77+
this.worker.execute(this);
78+
}
79+
80+
@Override
81+
public void run() {
82+
try {
83+
if (this.checkTarget.tickCount > 10) {
84+
// getEyePosition can use a fixed delta as its debug only anyway
85+
Vec3 cameraMC = this.checkTarget.getEyePosition(0);
86+
if (requestCull || !(cameraMC.x == lastPos.x && cameraMC.y == lastPos.y && cameraMC.z == lastPos.z)) {
87+
long start = System.currentTimeMillis();
88+
89+
requestCull = false;
90+
91+
lastPos.set(cameraMC.x, cameraMC.y, cameraMC.z);
92+
culling.resetCache();
93+
94+
cullEntities(cameraMC, lastPos);
95+
96+
lastCheckedTime = (System.currentTimeMillis() - start);
97+
}
98+
}
99+
} finally {
100+
if (this.scheduleNext) {
101+
this.worker.execute(this);
102+
}
103+
}
104+
}
105+
106+
private void cullEntities(Vec3 cameraMC, Vec3d camera) {
107+
for (Entity entity : this.checkTarget.level().getEntities().getAll()) {
108+
if (!(entity instanceof Cullable cullable)) {
109+
continue; // Not sure how this could happen outside from mixin screwing up the inject into
110+
// Entity
111+
}
112+
113+
if (entity.getType().skipRaytracningCheck) {
114+
continue;
115+
}
116+
117+
if (!cullable.isForcedVisible()) {
118+
if (entity.isCurrentlyGlowing() || isSkippableArmorstand(entity)) {
119+
cullable.setCulled(false);
120+
continue;
121+
}
122+
123+
if (!entity.position().closerThan(cameraMC, AtCoreConfig.RAY_TRACKING_ENTITY_TRACKER_CONFIG.tracingDistance)) {
124+
cullable.setCulled(false); // If your entity view distance is larger than tracingDistance just
125+
// render it
126+
continue;
127+
}
128+
129+
AABB boundingBox = entity.getBoundingBox();
130+
if (boundingBox.getXsize() > hitboxLimit || boundingBox.getYsize() > hitboxLimit
131+
|| boundingBox.getZsize() > hitboxLimit) {
132+
cullable.setCulled(false); // Too big to bother to cull
133+
continue;
134+
}
135+
136+
aabbMin.set(boundingBox.minX, boundingBox.minY, boundingBox.minZ);
137+
aabbMax.set(boundingBox.maxX, boundingBox.maxY, boundingBox.maxZ);
138+
139+
boolean visible = culling.isAABBVisible(aabbMin, aabbMax, camera);
140+
141+
cullable.setCulled(!visible);
142+
}
143+
}
144+
}
145+
146+
private boolean isSkippableArmorstand(Entity entity) {
147+
if (!AtCoreConfig.RAY_TRACKING_ENTITY_TRACKER_CONFIG.skipMarkerArmorStands)
148+
return false;
149+
return entity instanceof ArmorStand && entity.isInvisible();
150+
}
151+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package dev.tr7zw.entityculling;
2+
3+
import com.logisticscraft.occlusionculling.DataProvider;
4+
import net.minecraft.core.BlockPos;
5+
import net.minecraft.world.level.Level;
6+
import net.minecraft.world.level.block.Blocks;
7+
import net.minecraft.world.level.chunk.ChunkAccess;
8+
9+
public class DefaultChunkDataProvider implements DataProvider {
10+
private final Level level;
11+
12+
public DefaultChunkDataProvider(Level level) {
13+
this.level = level;
14+
}
15+
16+
@Override
17+
public boolean prepareChunk(int chunkX, int chunkZ) {
18+
return this.level.getChunkIfLoaded(chunkX, chunkZ) != null;
19+
}
20+
21+
@Override
22+
public boolean isOpaqueFullCube(int x, int y, int z) {
23+
BlockPos pos = new BlockPos(x, y, z);
24+
25+
final ChunkAccess access = this.level.getChunkIfLoaded(pos);
26+
if (access == null) {
27+
return false;
28+
}
29+
30+
if (this.level.isOutsideBuildHeight(pos)) {
31+
return Blocks.VOID_AIR.defaultBlockState().isSolidRender();
32+
} else {
33+
return access.getBlockState(pos).isSolidRender();// 好孩子不要学坏叔叔这样绕过异步拦截()
34+
}
35+
}
36+
37+
@Override
38+
public void cleanup() {
39+
DataProvider.super.cleanup();
40+
}
41+
42+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package dev.tr7zw.entityculling.versionless.access;
2+
3+
public interface Cullable {
4+
5+
public void setTimeout();
6+
7+
public boolean isForcedVisible();
8+
9+
public void setCulled(boolean value);
10+
11+
public boolean isCulled();
12+
13+
public void setOutOfCamera(boolean value);
14+
15+
public boolean isOutOfCamera();
16+
17+
}

atdeprecated-server/src/main/java/fun/mntale/atdeprecated/config/modules/experiment/RayTrackingEntityTrackerConfig.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ public class RayTrackingEntityTrackerConfig implements IConfigModule {
1818
@ConfigInfo(name = "check-interval-ms", comments = "The interval in milliseconds to check for culling.")
1919
public int checkIntervalMs = 500;
2020

21+
@ConfigInfo(name = "skip-marker-armor-stands", comments = "Enable this to skip marker armor stands from being culled.")
22+
public boolean skipMarkerArmorStands = true;
23+
2124
@Override
2225
public EnumConfigCategory getCategory() {
2326
return EnumConfigCategory.EXPERIMENT;

0 commit comments

Comments
 (0)