1043 lines
38 KiB
Diff
1043 lines
38 KiB
Diff
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
|
From: MrHua269 <wangxyper@163.com>
|
|
Date: Wed, 5 Feb 2025 15:22:21 +0800
|
|
Subject: [PATCH] Raytracing tracker experiment
|
|
|
|
Based on the framework of EntityCulling(((((((
|
|
|
|
diff --git a/src/main/java/com/logisticscraft/occlusionculling/DataProvider.java b/src/main/java/com/logisticscraft/occlusionculling/DataProvider.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..5ab4600554e8dcb2a2ddc22dd1c1015c89ba5b82
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/logisticscraft/occlusionculling/DataProvider.java
|
|
@@ -0,0 +1,34 @@
|
|
+package com.logisticscraft.occlusionculling;
|
|
+
|
|
+import com.logisticscraft.occlusionculling.util.Vec3d;
|
|
+
|
|
+public interface DataProvider {
|
|
+
|
|
+ /**
|
|
+ * Prepares the requested chunk. Returns true if the chunk is ready, false when
|
|
+ * not loaded. Should not reload the chunk when the x and y are the same as the
|
|
+ * last request!
|
|
+ *
|
|
+ * @param chunkX
|
|
+ * @param chunkZ
|
|
+ * @return
|
|
+ */
|
|
+ boolean prepareChunk(int chunkX, int chunkZ);
|
|
+
|
|
+ /**
|
|
+ * Location is inside the chunk.
|
|
+ *
|
|
+ * @param x
|
|
+ * @param y
|
|
+ * @param z
|
|
+ * @return
|
|
+ */
|
|
+ boolean isOpaqueFullCube(int x, int y, int z);
|
|
+
|
|
+ default void cleanup() {
|
|
+ }
|
|
+
|
|
+ default void checkingPosition(Vec3d[] targetPoints, int size, Vec3d viewerPosition) {
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/com/logisticscraft/occlusionculling/OcclusionCullingInstance.java b/src/main/java/com/logisticscraft/occlusionculling/OcclusionCullingInstance.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..30efa77d8ed337e9f044c41a10c6ecfd5ec162e2
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/logisticscraft/occlusionculling/OcclusionCullingInstance.java
|
|
@@ -0,0 +1,515 @@
|
|
+package com.logisticscraft.occlusionculling;
|
|
+
|
|
+import java.util.Arrays;
|
|
+import java.util.BitSet;
|
|
+
|
|
+import com.logisticscraft.occlusionculling.cache.ArrayOcclusionCache;
|
|
+import com.logisticscraft.occlusionculling.cache.OcclusionCache;
|
|
+import com.logisticscraft.occlusionculling.util.MathUtilities;
|
|
+import com.logisticscraft.occlusionculling.util.Vec3d;
|
|
+
|
|
+public class OcclusionCullingInstance {
|
|
+
|
|
+ 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;
|
|
+ private final double aabbExpansion;
|
|
+ private final DataProvider provider;
|
|
+ private final OcclusionCache cache;
|
|
+
|
|
+ // Reused allocated data structures
|
|
+ private final BitSet skipList = new BitSet(); // Grows bigger in case some mod introduces giant hitboxes
|
|
+ private final Vec3d[] targetPoints = new Vec3d[15];
|
|
+ private final Vec3d targetPos = new Vec3d(0, 0, 0);
|
|
+ private final int[] cameraPos = new int[3];
|
|
+ private final boolean[] dotselectors = new boolean[14];
|
|
+ private boolean allowRayChecks = false;
|
|
+ private final int[] lastHitBlock = new int[3];
|
|
+ private boolean allowWallClipping = false;
|
|
+
|
|
+
|
|
+ public OcclusionCullingInstance(int maxDistance, DataProvider provider) {
|
|
+ this(maxDistance, provider, new ArrayOcclusionCache(maxDistance), 0.5);
|
|
+ }
|
|
+
|
|
+ public OcclusionCullingInstance(int maxDistance, DataProvider provider, OcclusionCache cache, double aabbExpansion) {
|
|
+ this.reach = maxDistance;
|
|
+ this.provider = provider;
|
|
+ this.cache = cache;
|
|
+ this.aabbExpansion = aabbExpansion;
|
|
+ for(int i = 0; i < targetPoints.length; i++) {
|
|
+ targetPoints[i] = new Vec3d(0, 0, 0);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ public boolean isAABBVisible(Vec3d aabbMin, Vec3d aabbMax, Vec3d viewerPosition) {
|
|
+ try {
|
|
+ int maxX = MathUtilities.floor(aabbMax.x
|
|
+ + aabbExpansion);
|
|
+ int maxY = MathUtilities.floor(aabbMax.y
|
|
+ + aabbExpansion);
|
|
+ int maxZ = MathUtilities.floor(aabbMax.z
|
|
+ + aabbExpansion);
|
|
+ int minX = MathUtilities.floor(aabbMin.x
|
|
+ - aabbExpansion);
|
|
+ int minY = MathUtilities.floor(aabbMin.y
|
|
+ - aabbExpansion);
|
|
+ int minZ = MathUtilities.floor(aabbMin.z
|
|
+ - aabbExpansion);
|
|
+
|
|
+ cameraPos[0] = MathUtilities.floor(viewerPosition.x);
|
|
+ cameraPos[1] = MathUtilities.floor(viewerPosition.y);
|
|
+ cameraPos[2] = MathUtilities.floor(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; // We are inside of the AABB, don't cull
|
|
+ }
|
|
+
|
|
+ skipList.clear();
|
|
+
|
|
+ // Just check the cache first
|
|
+ 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) {
|
|
+ // non-occluding
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ if (cachedValue != 0) {
|
|
+ // was checked and it wasn't visible
|
|
+ skipList.set(id);
|
|
+ }
|
|
+ id++;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // only after the first hit wall the cache becomes valid.
|
|
+ allowRayChecks = false;
|
|
+
|
|
+ // since the cache wasn't helpfull
|
|
+ id = 0;
|
|
+ for (int x = minX; x <= maxX; x++) {
|
|
+ byte visibleOnFaceX = 0;
|
|
+ byte faceEdgeDataX = 0;
|
|
+ faceEdgeDataX |= (x == minX) ? ON_MIN_X : 0;
|
|
+ faceEdgeDataX |= (x == maxX) ? ON_MAX_X : 0;
|
|
+ visibleOnFaceX |= (x == minX && relX == Relative.POSITIVE) ? ON_MIN_X : 0;
|
|
+ visibleOnFaceX |= (x == maxX && relX == Relative.NEGATIVE) ? ON_MAX_X : 0;
|
|
+ for (int y = minY; y <= maxY; y++) {
|
|
+ byte faceEdgeDataY = faceEdgeDataX;
|
|
+ byte visibleOnFaceY = visibleOnFaceX;
|
|
+ faceEdgeDataY |= (y == minY) ? ON_MIN_Y : 0;
|
|
+ faceEdgeDataY |= (y == maxY) ? ON_MAX_Y : 0;
|
|
+ visibleOnFaceY |= (y == minY && relY == Relative.POSITIVE) ? ON_MIN_Y : 0;
|
|
+ visibleOnFaceY |= (y == maxY && relY == Relative.NEGATIVE) ? ON_MAX_Y : 0;
|
|
+ for (int z = minZ; z <= maxZ; z++) {
|
|
+ byte faceEdgeData = faceEdgeDataY;
|
|
+ byte visibleOnFace = visibleOnFaceY;
|
|
+ faceEdgeData |= (z == minZ) ? ON_MIN_Z : 0;
|
|
+ faceEdgeData |= (z == maxZ) ? ON_MAX_Z : 0;
|
|
+ visibleOnFace |= (z == minZ && relZ == Relative.POSITIVE) ? ON_MIN_Z : 0;
|
|
+ visibleOnFace |= (z == maxZ && relZ == Relative.NEGATIVE) ? ON_MAX_Z : 0;
|
|
+ if(skipList.get(id)) { // was checked and it wasn't visible
|
|
+ id++;
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ if (visibleOnFace != 0) {
|
|
+ targetPos.set(x, y, z);
|
|
+ if (isVoxelVisible(viewerPosition, targetPos, faceEdgeData, visibleOnFace)) {
|
|
+ return true;
|
|
+ }
|
|
+ }
|
|
+ id++;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return false;
|
|
+ } catch (Throwable t) {
|
|
+ // Failsafe
|
|
+ t.printStackTrace();
|
|
+ }
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * @param viewerPosition
|
|
+ * @param position
|
|
+ * @param faceData contains rather this Block is on the outside for a given face
|
|
+ * @param visibleOnFace contains rather a face should be concidered
|
|
+ * @return
|
|
+ */
|
|
+ private boolean isVoxelVisible(Vec3d viewerPosition, Vec3d position, byte faceData, byte visibleOnFace) {
|
|
+ int targetSize = 0;
|
|
+ Arrays.fill(dotselectors, false);
|
|
+ if((visibleOnFace & ON_MIN_X) == ON_MIN_X){
|
|
+ dotselectors[0] = true;
|
|
+ if((faceData & ~ON_MIN_X) != 0) {
|
|
+ dotselectors[1] = true;
|
|
+ dotselectors[4] = true;
|
|
+ dotselectors[5] = true;
|
|
+ }
|
|
+ dotselectors[8] = true;
|
|
+ }
|
|
+ if((visibleOnFace & ON_MIN_Y) == ON_MIN_Y){
|
|
+ dotselectors[0] = true;
|
|
+ if((faceData & ~ON_MIN_Y) != 0) {
|
|
+ dotselectors[3] = true;
|
|
+ dotselectors[4] = true;
|
|
+ dotselectors[7] = true;
|
|
+ }
|
|
+ dotselectors[9] = true;
|
|
+ }
|
|
+ if((visibleOnFace & ON_MIN_Z) == ON_MIN_Z){
|
|
+ dotselectors[0] = true;
|
|
+ if((faceData & ~ON_MIN_Z) != 0) {
|
|
+ dotselectors[1] = true;
|
|
+ dotselectors[4] = true;
|
|
+ dotselectors[5] = true;
|
|
+ }
|
|
+ dotselectors[10] = true;
|
|
+ }
|
|
+ if((visibleOnFace & ON_MAX_X) == ON_MAX_X){
|
|
+ dotselectors[4] = true;
|
|
+ if((faceData & ~ON_MAX_X) != 0) {
|
|
+ dotselectors[5] = true;
|
|
+ dotselectors[6] = true;
|
|
+ dotselectors[7] = true;
|
|
+ }
|
|
+ dotselectors[11] = true;
|
|
+ }
|
|
+ if((visibleOnFace & ON_MAX_Y) == ON_MAX_Y){
|
|
+ dotselectors[1] = true;
|
|
+ if((faceData & ~ON_MAX_Y) != 0) {
|
|
+ dotselectors[2] = true;
|
|
+ dotselectors[5] = true;
|
|
+ dotselectors[6] = true;
|
|
+ }
|
|
+ dotselectors[12] = true;
|
|
+ }
|
|
+ if((visibleOnFace & ON_MAX_Z) == ON_MAX_Z){
|
|
+ dotselectors[2] = true;
|
|
+ if((faceData & ~ON_MAX_Z) != 0) {
|
|
+ dotselectors[3] = true;
|
|
+ dotselectors[6] = true;
|
|
+ dotselectors[7] = true;
|
|
+ }
|
|
+ dotselectors[13] = true;
|
|
+ }
|
|
+
|
|
+ if (dotselectors[0])targetPoints[targetSize++].setAdd(position, 0.05, 0.05, 0.05);
|
|
+ if (dotselectors[1])targetPoints[targetSize++].setAdd(position, 0.05, 0.95, 0.05);
|
|
+ if (dotselectors[2])targetPoints[targetSize++].setAdd(position, 0.05, 0.95, 0.95);
|
|
+ if (dotselectors[3])targetPoints[targetSize++].setAdd(position, 0.05, 0.05, 0.95);
|
|
+ if (dotselectors[4])targetPoints[targetSize++].setAdd(position, 0.95, 0.05, 0.05);
|
|
+ if (dotselectors[5])targetPoints[targetSize++].setAdd(position, 0.95, 0.95, 0.05);
|
|
+ if (dotselectors[6])targetPoints[targetSize++].setAdd(position, 0.95, 0.95, 0.95);
|
|
+ if (dotselectors[7])targetPoints[targetSize++].setAdd(position, 0.95, 0.05, 0.95);
|
|
+ // middle points
|
|
+ if (dotselectors[8])targetPoints[targetSize++].setAdd(position, 0.05, 0.5, 0.5);
|
|
+ if (dotselectors[9])targetPoints[targetSize++].setAdd(position, 0.5, 0.05, 0.5);
|
|
+ if (dotselectors[10])targetPoints[targetSize++].setAdd(position, 0.5, 0.5, 0.05);
|
|
+ if (dotselectors[11])targetPoints[targetSize++].setAdd(position, 0.95, 0.5, 0.5);
|
|
+ if (dotselectors[12])targetPoints[targetSize++].setAdd(position, 0.5, 0.95, 0.5);
|
|
+ if (dotselectors[13])targetPoints[targetSize++].setAdd(position, 0.5, 0.5, 0.95);
|
|
+
|
|
+ return isVisible(viewerPosition, targetPoints, targetSize);
|
|
+ }
|
|
+
|
|
+ private boolean rayIntersection(int[] b, Vec3d rayOrigin, Vec3d rayDir) {
|
|
+ Vec3d rInv = new Vec3d(1, 1, 1).div(rayDir);
|
|
+
|
|
+ double t1 = (b[0] - rayOrigin.x) * rInv.x;
|
|
+ double t2 = (b[0] + 1 - rayOrigin.x) * rInv.x;
|
|
+ double t3 = (b[1] - rayOrigin.y) * rInv.y;
|
|
+ double t4 = (b[1] + 1 - rayOrigin.y) * rInv.y;
|
|
+ double t5 = (b[2] - rayOrigin.z) * rInv.z;
|
|
+ double t6 = (b[2] + 1 - rayOrigin.z) * rInv.z;
|
|
+
|
|
+ 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));
|
|
+
|
|
+ // if tmax > 0, ray (line) is intersecting AABB, but the whole AABB is behind us
|
|
+ if (tmax > 0) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ // if tmin > tmax, ray doesn't intersect AABB
|
|
+ if (tmin > tmax) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * returns the grid cells that intersect with this Vec3d<br>
|
|
+ * <a href=
|
|
+ * "http://playtechs.blogspot.de/2007/03/raytracing-on-grid.html">http://playtechs.blogspot.de/2007/03/raytracing-on-grid.html</a>
|
|
+ * <p>
|
|
+ * Caching assumes that all Vec3d's are inside the same block
|
|
+ */
|
|
+ private boolean isVisible(Vec3d start, Vec3d[] targets, int size) {
|
|
+ // start cell coordinate
|
|
+ int x = cameraPos[0];
|
|
+ int y = cameraPos[1];
|
|
+ int z = cameraPos[2];
|
|
+
|
|
+ for (int v = 0; v < size; v++) {
|
|
+ // ray-casting target
|
|
+ Vec3d target = targets[v];
|
|
+
|
|
+ double relativeX = start.x - target.getX();
|
|
+ double relativeY = start.y - target.getY();
|
|
+ double relativeZ = start.z - target.getZ();
|
|
+
|
|
+ if(allowRayChecks && rayIntersection(lastHitBlock, start, new Vec3d(relativeX, relativeY, relativeZ).normalize())) {
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ // horizontal and vertical cell amount spanned
|
|
+ double dimensionX = Math.abs(relativeX);
|
|
+ double dimensionY = Math.abs(relativeY);
|
|
+ double dimensionZ = Math.abs(relativeZ);
|
|
+
|
|
+ // distance between horizontal intersection points with cell border as a
|
|
+ // fraction of the total Vec3d length
|
|
+ double dimFracX = 1f / dimensionX;
|
|
+ // distance between vertical intersection points with cell border as a fraction
|
|
+ // of the total Vec3d length
|
|
+ double dimFracY = 1f / dimensionY;
|
|
+ double dimFracZ = 1f / dimensionZ;
|
|
+
|
|
+ // total amount of intersected cells
|
|
+ int intersectCount = 1;
|
|
+
|
|
+ // 1, 0 or -1
|
|
+ // determines the direction of the next cell (horizontally / vertically)
|
|
+ int x_inc, y_inc, z_inc;
|
|
+
|
|
+ // the distance to the next horizontal / vertical intersection point with a cell
|
|
+ // border as a fraction of the total Vec3d length
|
|
+ double t_next_y, t_next_x, t_next_z;
|
|
+
|
|
+ if (dimensionX == 0f) {
|
|
+ x_inc = 0;
|
|
+ t_next_x = dimFracX; // don't increment horizontally because the Vec3d is perfectly vertical
|
|
+ } else if (target.x > start.x) {
|
|
+ x_inc = 1; // target point is horizontally greater than starting point so increment every
|
|
+ // step by 1
|
|
+ intersectCount += MathUtilities.floor(target.x) - x; // increment total amount of intersecting cells
|
|
+ t_next_x = (float) ((x + 1 - start.x) * dimFracX); // calculate the next horizontal
|
|
+ // intersection
|
|
+ // point based on the position inside
|
|
+ // the first cell
|
|
+ } else {
|
|
+ x_inc = -1; // target point is horizontally smaller than starting point so reduce every step
|
|
+ // by 1
|
|
+ intersectCount += x - MathUtilities.floor(target.x); // increment total amount of intersecting cells
|
|
+ t_next_x = (float) ((start.x - x)
|
|
+ * dimFracX); // calculate the next horizontal
|
|
+ // intersection point
|
|
+ // based on the position inside
|
|
+ // the first cell
|
|
+ }
|
|
+
|
|
+ if (dimensionY == 0f) {
|
|
+ y_inc = 0;
|
|
+ t_next_y = dimFracY; // don't increment vertically because the Vec3d is perfectly horizontal
|
|
+ } else if (target.y > start.y) {
|
|
+ y_inc = 1; // target point is vertically greater than starting point so increment every
|
|
+ // step by 1
|
|
+ intersectCount += MathUtilities.floor(target.y) - y; // increment total amount of intersecting cells
|
|
+ t_next_y = (float) ((y + 1 - start.y)
|
|
+ * dimFracY); // calculate the next vertical
|
|
+ // intersection
|
|
+ // point based on the position inside
|
|
+ // the first cell
|
|
+ } else {
|
|
+ y_inc = -1; // target point is vertically smaller than starting point so reduce every step
|
|
+ // by 1
|
|
+ intersectCount += y - MathUtilities.floor(target.y); // increment total amount of intersecting cells
|
|
+ t_next_y = (float) ((start.y - y)
|
|
+ * dimFracY); // calculate the next vertical intersection
|
|
+ // point
|
|
+ // based on the position inside
|
|
+ // the first cell
|
|
+ }
|
|
+
|
|
+ if (dimensionZ == 0f) {
|
|
+ z_inc = 0;
|
|
+ t_next_z = dimFracZ; // don't increment vertically because the Vec3d is perfectly horizontal
|
|
+ } else if (target.z > start.z) {
|
|
+ z_inc = 1; // target point is vertically greater than starting point so increment every
|
|
+ // step by 1
|
|
+ intersectCount += MathUtilities.floor(target.z) - z; // increment total amount of intersecting cells
|
|
+ t_next_z = (float) ((z + 1 - start.z)
|
|
+ * dimFracZ); // calculate the next vertical
|
|
+ // intersection
|
|
+ // point based on the position inside
|
|
+ // the first cell
|
|
+ } else {
|
|
+ z_inc = -1; // target point is vertically smaller than starting point so reduce every step
|
|
+ // by 1
|
|
+ intersectCount += z - MathUtilities.floor(target.z); // increment total amount of intersecting cells
|
|
+ t_next_z = (float) ((start.z - z)
|
|
+ * dimFracZ); // calculate the next vertical intersection
|
|
+ // point
|
|
+ // based on the position inside
|
|
+ // the first cell
|
|
+ }
|
|
+
|
|
+ boolean finished = stepRay(start, x, y, z,
|
|
+ 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);
|
|
+ return true;
|
|
+ } else {
|
|
+ allowRayChecks = true;
|
|
+ }
|
|
+ }
|
|
+ cacheResult(targets[0], false);
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ private boolean stepRay(Vec3d start, 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; // initially allow rays to go through walls till they are on the outside
|
|
+ // iterate through all intersecting cells (n times)
|
|
+ for (; n > 1; n--) { // n-1 times because we don't want to check the last block
|
|
+ // towards - where from
|
|
+
|
|
+
|
|
+ // get cached value, 0 means uncached (default)
|
|
+ int cVal = getCacheValue(currentX, currentY, currentZ);
|
|
+
|
|
+ if (cVal == 2 && !allowWallClipping) {
|
|
+ // block cached as occluding, stop ray
|
|
+ lastHitBlock[0] = currentX;
|
|
+ lastHitBlock[1] = currentY;
|
|
+ lastHitBlock[2] = currentZ;
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (cVal == 0) {
|
|
+ // save current cell
|
|
+ int chunkX = currentX >> 4;
|
|
+ int chunkZ = currentZ >> 4;
|
|
+
|
|
+ if (!provider.prepareChunk(chunkX, chunkZ)) { // Chunk not ready
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (provider.isOpaqueFullCube(currentX, currentY, currentZ)) {
|
|
+ if (!allowWallClipping) {
|
|
+ cache.setLastHidden();
|
|
+ lastHitBlock[0] = currentX;
|
|
+ lastHitBlock[1] = currentY;
|
|
+ lastHitBlock[2] = currentZ;
|
|
+ return false;
|
|
+ }
|
|
+ } else {
|
|
+ // outside of wall, now clipping is not allowed
|
|
+ allowWallClipping = false;
|
|
+ cache.setLastVisible();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if(cVal == 1) {
|
|
+ // outside of wall, now clipping is not allowed
|
|
+ allowWallClipping = false;
|
|
+ }
|
|
+
|
|
+
|
|
+ if (t_next_y < t_next_x && t_next_y < t_next_z) { // next cell is upwards/downwards because the distance to
|
|
+ // the next vertical
|
|
+ // intersection point is smaller than to the next horizontal intersection point
|
|
+ currentY += y_inc; // move up/down
|
|
+ t_next_y += distInY; // update next vertical intersection point
|
|
+ } else if (t_next_x < t_next_y && t_next_x < t_next_z) { // next cell is right/left
|
|
+ currentX += x_inc; // move right/left
|
|
+ t_next_x += distInX; // update next horizontal intersection point
|
|
+ } else {
|
|
+ currentZ += z_inc; // move right/left
|
|
+ t_next_z += distInZ; // update next horizontal intersection point
|
|
+ }
|
|
+
|
|
+ }
|
|
+ return true;
|
|
+ }
|
|
+
|
|
+ // -1 = invalid location, 0 = not checked yet, 1 = visible, 2 = occluding
|
|
+ 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;
|
|
+ }
|
|
+
|
|
+ // check if target is already known
|
|
+ return cache.getState(x + reach, y + reach, z + reach);
|
|
+ }
|
|
+
|
|
+
|
|
+ private void cacheResult(int x, int y, int z, boolean result) {
|
|
+ int cx = x - cameraPos[0] + reach;
|
|
+ int cy = y - cameraPos[1] + reach;
|
|
+ int cz = z - cameraPos[2] + reach;
|
|
+ if (result) {
|
|
+ cache.setVisible(cx, cy, cz);
|
|
+ } else {
|
|
+ cache.setHidden(cx, cy, cz);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ private void cacheResult(Vec3d vector, boolean result) {
|
|
+ int cx = MathUtilities.floor(vector.x) - cameraPos[0] + reach;
|
|
+ int cy = MathUtilities.floor(vector.y) - cameraPos[1] + reach;
|
|
+ int cz = MathUtilities.floor(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 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;
|
|
+ }
|
|
+ return INSIDE;
|
|
+ }
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/com/logisticscraft/occlusionculling/cache/ArrayOcclusionCache.java b/src/main/java/com/logisticscraft/occlusionculling/cache/ArrayOcclusionCache.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..08f0ddb343c12ac781ee33f1a7da1462573405b9
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/logisticscraft/occlusionculling/cache/ArrayOcclusionCache.java
|
|
@@ -0,0 +1,57 @@
|
|
+package com.logisticscraft.occlusionculling.cache;
|
|
+
|
|
+import java.util.Arrays;
|
|
+
|
|
+public class ArrayOcclusionCache implements OcclusionCache {
|
|
+
|
|
+ private final int reachX2;
|
|
+ private final byte[] cache;
|
|
+ private int positionKey;
|
|
+ private int entry;
|
|
+ private int offset;
|
|
+
|
|
+ public ArrayOcclusionCache(int reach) {
|
|
+ this.reachX2 = reach * 2;
|
|
+ this.cache = new byte[(reachX2 * reachX2 * reachX2) / 4];
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void resetCache() {
|
|
+ Arrays.fill(cache, (byte) 0);
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setVisible(int x, int y, int z) {
|
|
+ positionKey = x + y * reachX2 + z * reachX2 * reachX2;
|
|
+ entry = positionKey / 4;
|
|
+ offset = (positionKey % 4) * 2;
|
|
+ cache[entry] |= 1 << offset;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setHidden(int x, int y, int z) {
|
|
+ positionKey = x + y * reachX2 + z * reachX2 * reachX2;
|
|
+ entry = positionKey / 4;
|
|
+ offset = (positionKey % 4) * 2;
|
|
+ cache[entry] |= 1 << offset + 1;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int getState(int x, int y, int z) {
|
|
+ positionKey = x + y * reachX2 + z * reachX2 * reachX2;
|
|
+ entry = positionKey / 4;
|
|
+ offset = (positionKey % 4) * 2;
|
|
+ return cache[entry] >> offset & 3;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setLastVisible() {
|
|
+ cache[entry] |= 1 << offset;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void setLastHidden() {
|
|
+ cache[entry] |= 1 << offset + 1;
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/com/logisticscraft/occlusionculling/cache/OcclusionCache.java b/src/main/java/com/logisticscraft/occlusionculling/cache/OcclusionCache.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..2a2e5620fb2b1a71fbca2068fc049bfa89a47737
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/logisticscraft/occlusionculling/cache/OcclusionCache.java
|
|
@@ -0,0 +1,17 @@
|
|
+package com.logisticscraft.occlusionculling.cache;
|
|
+
|
|
+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();
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/com/logisticscraft/occlusionculling/util/MathUtilities.java b/src/main/java/com/logisticscraft/occlusionculling/util/MathUtilities.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..fb28265631b079eb5da8ae3a7ecca6e343f8c0b6
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/logisticscraft/occlusionculling/util/MathUtilities.java
|
|
@@ -0,0 +1,25 @@
|
|
+package com.logisticscraft.occlusionculling.util;
|
|
+
|
|
+/**
|
|
+ * Contains MathHelper methods
|
|
+ */
|
|
+public final class MathUtilities {
|
|
+
|
|
+ private MathUtilities() {
|
|
+ }
|
|
+
|
|
+ public static int floor(double d) {
|
|
+ int i = (int) d;
|
|
+ return d < (double) i ? i - 1 : i;
|
|
+ }
|
|
+
|
|
+ public static int fastFloor(double d) {
|
|
+ return (int) (d + 1024.0) - 1024;
|
|
+ }
|
|
+
|
|
+ public static int ceil(double d) {
|
|
+ int i = (int) d;
|
|
+ return d > (double) i ? i + 1 : i;
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/com/logisticscraft/occlusionculling/util/Vec3d.java b/src/main/java/com/logisticscraft/occlusionculling/util/Vec3d.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..7e3cb9e11f65af0627164c8dbd6aad3e5690d36c
|
|
--- /dev/null
|
|
+++ b/src/main/java/com/logisticscraft/occlusionculling/util/Vec3d.java
|
|
@@ -0,0 +1,87 @@
|
|
+package com.logisticscraft.occlusionculling.util;
|
|
+
|
|
+public class Vec3d {
|
|
+
|
|
+ public double x;
|
|
+ public double y;
|
|
+ public double z;
|
|
+
|
|
+ public Vec3d(double x, double y, double z) {
|
|
+ this.x = x;
|
|
+ this.y = y;
|
|
+ this.z = z;
|
|
+ }
|
|
+
|
|
+ public double getX() {
|
|
+ return x;
|
|
+ }
|
|
+
|
|
+ public double getY() {
|
|
+ return y;
|
|
+ }
|
|
+
|
|
+ public double getZ() {
|
|
+ return z;
|
|
+ }
|
|
+
|
|
+ public void set(double x, double y, double z) {
|
|
+ this.x = x;
|
|
+ this.y = y;
|
|
+ this.z = z;
|
|
+ }
|
|
+
|
|
+ public void setAdd(Vec3d vec, double x, double y, double z) {
|
|
+ this.x = vec.x + x;
|
|
+ this.y = vec.y + y;
|
|
+ this.z = vec.z + z;
|
|
+ }
|
|
+
|
|
+ public Vec3d div(Vec3d rayDir) {
|
|
+ this.x /= rayDir.x;
|
|
+ this.z /= rayDir.z;
|
|
+ this.y /= rayDir.y;
|
|
+ return this;
|
|
+ }
|
|
+
|
|
+ public Vec3d normalize() {
|
|
+ double mag = Math.sqrt(x*x+y*y+z*z);
|
|
+ this.x /= mag;
|
|
+ this.y /= mag;
|
|
+ this.z /= mag;
|
|
+ return this;
|
|
+ }
|
|
+
|
|
+ public boolean equals(Object other) {
|
|
+ if (this == other) {
|
|
+ return true;
|
|
+ }
|
|
+ if (!(other instanceof Vec3d)) {
|
|
+ return false;
|
|
+ }
|
|
+ Vec3d vec3d = (Vec3d) other;
|
|
+ if (Double.compare(vec3d.x, x) != 0) {
|
|
+ return false;
|
|
+ }
|
|
+ if (Double.compare(vec3d.y, y) != 0) {
|
|
+ return false;
|
|
+ }
|
|
+ return Double.compare(vec3d.z, z) == 0;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public int hashCode() {
|
|
+ long l = Double.doubleToLongBits(x);
|
|
+ int i = (int) (l ^ l >>> 32);
|
|
+ l = Double.doubleToLongBits(y);
|
|
+ i = 31 * i + (int) (l ^ l >>> 32);
|
|
+ l = Double.doubleToLongBits(z);
|
|
+ i = 31 * i + (int) (l ^ l >>> 32);
|
|
+ return i;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public String toString() {
|
|
+ return "(" + x + ", " + y + ", " + z + ")";
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/dev/tr7zw/entityculling/CullTask.java b/src/main/java/dev/tr7zw/entityculling/CullTask.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..57e36d4a2c770a3d2ae72a78e69802dd119e6999
|
|
--- /dev/null
|
|
+++ b/src/main/java/dev/tr7zw/entityculling/CullTask.java
|
|
@@ -0,0 +1,152 @@
|
|
+package dev.tr7zw.entityculling;
|
|
+
|
|
+import java.util.concurrent.CompletableFuture;
|
|
+import java.util.concurrent.Executor;
|
|
+import java.util.concurrent.Executors;
|
|
+import java.util.concurrent.TimeUnit;
|
|
+
|
|
+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 me.earthme.luminol.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;
|
|
+
|
|
+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, me.earthme.luminol.config.modules.experiment.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); // To 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();
|
|
+ }
|
|
+}
|
|
diff --git a/src/main/java/dev/tr7zw/entityculling/DefaultChunkDataProvider.java b/src/main/java/dev/tr7zw/entityculling/DefaultChunkDataProvider.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..aadf7cdc4a28ecf92853e147fa115d43c89f6fe8
|
|
--- /dev/null
|
|
+++ b/src/main/java/dev/tr7zw/entityculling/DefaultChunkDataProvider.java
|
|
@@ -0,0 +1,43 @@
|
|
+package dev.tr7zw.entityculling;
|
|
+
|
|
+import com.logisticscraft.occlusionculling.DataProvider;
|
|
+import net.minecraft.core.BlockPos;
|
|
+import net.minecraft.world.level.Level;
|
|
+import net.minecraft.world.level.block.Blocks;
|
|
+import net.minecraft.world.level.chunk.ChunkAccess;
|
|
+import net.minecraft.world.level.chunk.status.ChunkStatus;
|
|
+
|
|
+public class DefaultChunkDataProvider implements DataProvider {
|
|
+ private final Level level;
|
|
+
|
|
+ public DefaultChunkDataProvider(Level level) {
|
|
+ this.level = level;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean prepareChunk(int chunkX, int chunkZ) {
|
|
+ return this.level.getChunkIfLoaded(chunkX, chunkZ) != null;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public boolean isOpaqueFullCube(int x, int y, int z) {
|
|
+ BlockPos pos = new BlockPos(x, y, z);
|
|
+
|
|
+ final ChunkAccess access = this.level.getChunkIfLoaded(pos);
|
|
+ if (access == null) {
|
|
+ return false;
|
|
+ }
|
|
+
|
|
+ if (this.level.isOutsideBuildHeight(pos)) {
|
|
+ return Blocks.VOID_AIR.defaultBlockState().isSolidRender();
|
|
+ } else {
|
|
+ return access.getBlockState(pos).isSolidRender();// 好孩子不要学坏叔叔这样绕过异步拦截()
|
|
+ }
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public void cleanup() {
|
|
+ DataProvider.super.cleanup();
|
|
+ }
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/dev/tr7zw/entityculling/versionless/access/Cullable.java b/src/main/java/dev/tr7zw/entityculling/versionless/access/Cullable.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..948e8e934793532704161e0a129ebf86355ba5a8
|
|
--- /dev/null
|
|
+++ b/src/main/java/dev/tr7zw/entityculling/versionless/access/Cullable.java
|
|
@@ -0,0 +1,17 @@
|
|
+package dev.tr7zw.entityculling.versionless.access;
|
|
+
|
|
+public interface Cullable {
|
|
+
|
|
+ public void setTimeout();
|
|
+
|
|
+ public boolean isForcedVisible();
|
|
+
|
|
+ public void setCulled(boolean value);
|
|
+
|
|
+ public boolean isCulled();
|
|
+
|
|
+ public void setOutOfCamera(boolean value);
|
|
+
|
|
+ public boolean isOutOfCamera();
|
|
+
|
|
+}
|
|
diff --git a/src/main/java/me/earthme/luminol/config/modules/experiment/RayTrackingEntityTrackerConfig.java b/src/main/java/me/earthme/luminol/config/modules/experiment/RayTrackingEntityTrackerConfig.java
|
|
new file mode 100644
|
|
index 0000000000000000000000000000000000000000..1e3e6a500c9eb26a06f5441f9e542dc9b1ad0cf4
|
|
--- /dev/null
|
|
+++ b/src/main/java/me/earthme/luminol/config/modules/experiment/RayTrackingEntityTrackerConfig.java
|
|
@@ -0,0 +1,28 @@
|
|
+package me.earthme.luminol.config.modules.experiment;
|
|
+
|
|
+import me.earthme.luminol.config.ConfigInfo;
|
|
+import me.earthme.luminol.config.EnumConfigCategory;
|
|
+import me.earthme.luminol.config.IConfigModule;
|
|
+
|
|
+public class RayTrackingEntityTrackerConfig implements IConfigModule {
|
|
+ @ConfigInfo(baseName = "enabled")
|
|
+ public static boolean enabled = false;
|
|
+ @ConfigInfo(baseName = "skip_marker_armor_stands")
|
|
+ public static boolean skipMarkerArmorStands = true;
|
|
+ @ConfigInfo(baseName = "check_interval_ms")
|
|
+ public static int checkIntervalMs = 10;
|
|
+ @ConfigInfo(baseName = "tracing_distance")
|
|
+ public static int tracingDistance = 48;
|
|
+ @ConfigInfo(baseName = "hitbox_limit")
|
|
+ public static int hitboxLimit = 50;
|
|
+
|
|
+ @Override
|
|
+ public EnumConfigCategory getCategory() {
|
|
+ return EnumConfigCategory.EXPERIMENT;
|
|
+ }
|
|
+
|
|
+ @Override
|
|
+ public String getBaseName() {
|
|
+ return "ray_tracking_entity_tracker";
|
|
+ }
|
|
+}
|