Files
Lophine/lophine-server/src/main/java/dev/tr7zw/entityculling/CullTask.java
T
2025-06-30 13:51:32 +08:00

151 lines
5.0 KiB
Java

package dev.tr7zw.entityculling;
import ca.spottedleaf.moonrise.common.util.TickThread;
import com.logisticscraft.occlusionculling.OcclusionCullingInstance;
import com.logisticscraft.occlusionculling.util.Vec3d;
import dev.tr7zw.entityculling.versionless.access.Cullable;
import fun.bm.lophine.config.modules.experiment.RayTrackingEntityTrackerConfig;
import net.minecraft.world.entity.Entity;
import net.minecraft.world.entity.decoration.ArmorStand;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.phys.Vec3;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CullTask implements Runnable {
private volatile boolean requestCull = false;
private volatile boolean scheduleNext = true;
private volatile boolean inited = false;
private final OcclusionCullingInstance culling;
private final Player checkTarget;
private final int hitboxLimit;
public long lastCheckedTime = 0;
// reused preallocated vars
private final Vec3d lastPos = new Vec3d(0, 0, 0);
private final Vec3d aabbMin = new Vec3d(0, 0, 0);
private final Vec3d aabbMax = new Vec3d(0, 0, 0);
private static final Executor backgroundWorker = Executors.newCachedThreadPool(task -> {
final TickThread worker = new TickThread("EntityCulling") {
@Override
public void run() {
task.run();
}
};
worker.setDaemon(true);
return worker;
});
private final Executor worker;
public CullTask(
OcclusionCullingInstance culling,
Player checkTarget,
int hitboxLimit,
long checkIntervalMs
) {
this.culling = culling;
this.checkTarget = checkTarget;
this.hitboxLimit = hitboxLimit;
this.worker = CompletableFuture.delayedExecutor(checkIntervalMs, TimeUnit.MILLISECONDS, backgroundWorker);
}
public void requestCullSignal() {
this.requestCull = true;
}
public void signalStop() {
this.scheduleNext = false;
}
public void setup() {
if (!this.inited)
this.inited = true;
else
return;
this.worker.execute(this);
}
@Override
public void run() {
try {
if (this.checkTarget.tickCount > 10) {
// getEyePosition can use a fixed delta as its debug only anyway
Vec3 cameraMC = this.checkTarget.getEyePosition(0);
if (requestCull || !(cameraMC.x == lastPos.x && cameraMC.y == lastPos.y && cameraMC.z == lastPos.z)) {
long start = System.currentTimeMillis();
requestCull = false;
lastPos.set(cameraMC.x, cameraMC.y, cameraMC.z);
culling.resetCache();
cullEntities(cameraMC, lastPos);
lastCheckedTime = (System.currentTimeMillis() - start);
}
}
} finally {
if (this.scheduleNext) {
this.worker.execute(this);
}
}
}
private void cullEntities(Vec3 cameraMC, Vec3d camera) {
for (Entity entity : this.checkTarget.level().getEntities().getAll()) {
if (!(entity instanceof Cullable cullable)) {
continue; // Not sure how this could happen outside from mixin screwing up the inject into
// Entity
}
if (entity.getType().skipRaytracningCheck) {
continue;
}
if (!cullable.isForcedVisible()) {
if (entity.isCurrentlyGlowing() || isSkippableArmorstand(entity)) {
cullable.setCulled(false);
continue;
}
if (!entity.position().closerThan(cameraMC, RayTrackingEntityTrackerConfig.tracingDistance)) {
cullable.setCulled(false); // If your entity view distance is larger than tracingDistance just
// render it
continue;
}
AABB boundingBox = entity.getBoundingBox();
if (boundingBox.getXsize() > hitboxLimit || boundingBox.getYsize() > hitboxLimit
|| boundingBox.getZsize() > hitboxLimit) {
cullable.setCulled(false); // Too big to bother to cull
continue;
}
aabbMin.set(boundingBox.minX, boundingBox.minY, boundingBox.minZ);
aabbMax.set(boundingBox.maxX, boundingBox.maxY, boundingBox.maxZ);
boolean visible = culling.isAABBVisible(aabbMin, aabbMax, camera);
cullable.setCulled(!visible);
}
}
}
private boolean isSkippableArmorstand(Entity entity) {
if (!RayTrackingEntityTrackerConfig.skipMarkerArmorStands)
return false;
return entity instanceof ArmorStand && entity.isInvisible();
}
}