1
0
Fork 0
hoosiertransfer-mod-saigevers/src/main/java/com/logisticscraft/occlusionculling/OcclusionCullingInstance.java

515 lines
21 KiB
Java

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