add entity culling
This commit is contained in:
parent
08d01ecc2d
commit
f99a6228d6
84534
javascript/classes.js
84534
javascript/classes.js
File diff suppressed because it is too large
Load Diff
|
@ -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) {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,514 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
57
src/main/java/com/logisticscraft/occlusionculling/cache/ArrayOcclusionCache.java
vendored
Normal file
57
src/main/java/com/logisticscraft/occlusionculling/cache/ArrayOcclusionCache.java
vendored
Normal file
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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 + ")";
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,18 @@
|
|||
package net.hoosiertransfer;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Config {
|
||||
public static int biomeBlendRadius = 2;
|
||||
public static boolean fancyLeaves = false;
|
||||
|
||||
public static boolean enableCulling = true;
|
||||
public static boolean renderNameTagsThroguthWalls = true;
|
||||
public static boolean skipMarkerArmorStands = true;
|
||||
public static int tracingDistance = 128;
|
||||
public static Set<String> blockEntityWhitelist = new HashSet<>(Arrays.asList("minecraft:beacon"));
|
||||
public static int SleepDuration = 10;
|
||||
public static int hitboxLimit = 50;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
package net.hoosiertransfer.Culling;
|
||||
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Set;
|
||||
|
||||
import com.logisticscraft.occlusionculling.OcclusionCullingInstance;
|
||||
import com.logisticscraft.occlusionculling.util.Vec3d;
|
||||
|
||||
import net.hoosiertransfer.Config;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.entity.Entity;
|
||||
import net.minecraft.entity.item.EntityArmorStand;
|
||||
import net.minecraft.tileentity.TileEntity;
|
||||
import net.minecraft.util.AxisAlignedBB;
|
||||
import net.minecraft.util.BlockPos;
|
||||
import net.minecraft.util.Vec3;
|
||||
|
||||
public class CullTask implements Runnable {
|
||||
public boolean requestCull = false;
|
||||
|
||||
private final OcclusionCullingInstance culling;
|
||||
private final Set<String> unCullable;
|
||||
private final Minecraft client = Minecraft.getMinecraft();
|
||||
|
||||
private final int hitboxLimit = Config.hitboxLimit;
|
||||
|
||||
public long lastTime = 0;
|
||||
|
||||
private Vec3d lastPos = new Vec3d(0, 0, 0);
|
||||
private Vec3d aabbMin = new Vec3d(0, 0, 0);
|
||||
private Vec3d aabbMax = new Vec3d(0, 0, 0);
|
||||
|
||||
public CullTask(OcclusionCullingInstance culling, Set<String> unCullable) {
|
||||
this.culling = culling;
|
||||
this.unCullable = unCullable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (client != null) {
|
||||
try {
|
||||
Thread.sleep(Config.SleepDuration);
|
||||
|
||||
if (Config.enableCulling && client.theWorld != null && client.thePlayer != null && client.thePlayer.ticksExisted > 0 && client.getRenderViewEntity() != null) {
|
||||
Vec3 cameraMC = getCameraPos();
|
||||
if (requestCull || !(cameraMC.xCoord == lastPos.x && cameraMC.yCoord == lastPos.y && cameraMC.zCoord == lastPos.z)) {
|
||||
long start = System.currentTimeMillis();
|
||||
requestCull = false;
|
||||
lastPos.set(cameraMC.xCoord, cameraMC.yCoord, cameraMC.zCoord);
|
||||
Vec3d camera = lastPos;
|
||||
culling.resetCache();
|
||||
boolean noCulling = client.thePlayer.isSpectator() || client.gameSettings.thirdPersonView != 0;
|
||||
Iterator<TileEntity> iterator = client.theWorld.loadedTileEntityList.iterator();
|
||||
TileEntity entry;
|
||||
while(iterator.hasNext()) {
|
||||
try {
|
||||
entry = iterator.next();
|
||||
} catch(NullPointerException | ConcurrentModificationException ex) {
|
||||
break; // We are not synced to the main thread, so NPE's/CME are allowed here and way less
|
||||
// overhead probably than trying to sync stuff up for no really good reason
|
||||
}
|
||||
if (unCullable.contains(entry.getBlockType().getUnlocalizedName())) {
|
||||
continue;
|
||||
}
|
||||
if (!entry.isForcedVisible()) {
|
||||
if (noCulling) {
|
||||
entry.setCulled(true);
|
||||
continue;
|
||||
}
|
||||
BlockPos pos = entry.getPos();
|
||||
if(pos.distanceSq(cameraMC.xCoord, cameraMC.yCoord, cameraMC.zCoord) < 64*64) { // 64 is the fixed max tile view distance
|
||||
aabbMin.set(pos.getX(), pos.getY(), pos.getZ());
|
||||
aabbMax.set(pos.getX()+1d, pos.getY()+1d, pos.getZ()+1d);
|
||||
boolean visible = culling.isAABBVisible(aabbMin, aabbMax, camera);
|
||||
entry.setCulled(!visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
Entity entity = null;
|
||||
Iterator<Entity> iterable = client.theWorld.getLoadedEntityList().iterator();
|
||||
while(iterable.hasNext()) {
|
||||
try {
|
||||
entity = iterable.next();
|
||||
} catch(NullPointerException | ConcurrentModificationException ex) {
|
||||
break; // We are not synced to the main thread, so NPE's/CME are allowed here and way less
|
||||
// overhead probably than trying to sync stuff up for no really good reason
|
||||
}
|
||||
if (entity == null) {
|
||||
continue;
|
||||
}
|
||||
if (!entity.isForcedVisible()) {
|
||||
if (noCulling || isSkippableArmorstand(entity)) {
|
||||
entity.setCulled(false);
|
||||
continue;
|
||||
}
|
||||
if(entity.getPositionVector().squareDistanceTo(cameraMC) > Config.tracingDistance * Config.tracingDistance) {
|
||||
entity.setCulled(false); // If your entity view distance is larger than tracingDistance just render it
|
||||
continue;
|
||||
}
|
||||
AxisAlignedBB boundingBox = entity.getEntityBoundingBox();
|
||||
if(boundingBox.maxX - boundingBox.minX > hitboxLimit || boundingBox.maxY - boundingBox.minY > hitboxLimit || boundingBox.maxZ - boundingBox.minZ > hitboxLimit) {
|
||||
entity.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);
|
||||
entity.setCulled(!visible);
|
||||
}
|
||||
}
|
||||
lastTime = (System.currentTimeMillis()-start);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
System.out.println("Culling thread stopped");
|
||||
}
|
||||
|
||||
private Vec3 getCameraPos() {
|
||||
if (client.gameSettings.thirdPersonView == 0) {
|
||||
return client.thePlayer.getPositionEyes(0);
|
||||
}
|
||||
return client.getRenderViewEntity().getPositionEyes(0);
|
||||
}
|
||||
|
||||
private boolean isSkippableArmorstand(Entity entity) {
|
||||
if (!Config.skipMarkerArmorStands) return false;
|
||||
return entity instanceof EntityArmorStand && ((EntityArmorStand) entity).func_181026_s(); // i think this is the marker flag
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package net.hoosiertransfer.Culling;
|
||||
|
||||
import com.logisticscraft.occlusionculling.DataProvider;
|
||||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.multiplayer.WorldClient;
|
||||
import net.minecraft.util.BlockPos;
|
||||
|
||||
public class Provider implements DataProvider {
|
||||
private final Minecraft client = Minecraft.getMinecraft();
|
||||
private WorldClient world = null;
|
||||
|
||||
@Override
|
||||
public boolean prepareChunk(int chunkX, int chunkZ) {
|
||||
world = client.theWorld;
|
||||
return world != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpaqueFullCube(int x, int y, int z) {
|
||||
BlockPos pos = new BlockPos(x, y, z);
|
||||
return world.getBlockState(pos).getBlock().isOpaqueCube();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
world = null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package net.hoosiertransfer;
|
||||
|
||||
import com.logisticscraft.occlusionculling.OcclusionCullingInstance;
|
||||
|
||||
import net.hoosiertransfer.Culling.CullTask;
|
||||
import net.hoosiertransfer.Culling.Provider;
|
||||
|
||||
public class CullingMod {
|
||||
public static OcclusionCullingInstance culling;
|
||||
public static CullTask cullTask;
|
||||
private static Thread cullThread;
|
||||
|
||||
public static int skippedBlockEntities = 0;
|
||||
public static int renderedBlockEntities = 0;
|
||||
public static int skippedEntities = 0;
|
||||
public static int renderedEntities = 0;
|
||||
|
||||
public static void intialize() {
|
||||
culling = new OcclusionCullingInstance(Config.tracingDistance, new Provider());
|
||||
cullTask = new CullTask(culling, Config.blockEntityWhitelist);
|
||||
|
||||
cullThread = new Thread(cullTask, "CullThread");
|
||||
|
||||
cullThread.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||
System.err.println("Error in culling thread");
|
||||
throwable.printStackTrace();
|
||||
});
|
||||
cullThread.start();
|
||||
}
|
||||
|
||||
public static void setRequestCull(boolean cull) {
|
||||
cullTask.requestCull = cull;
|
||||
}
|
||||
}
|
|
@ -19,6 +19,7 @@ import org.apache.commons.lang3.Validate;
|
|||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import net.hoosiertransfer.CullingMod;
|
||||
import net.lax1dude.eaglercraft.v1_8.Display;
|
||||
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
|
||||
import net.lax1dude.eaglercraft.v1_8.EaglerXBungeeVersion;
|
||||
|
@ -328,6 +329,7 @@ public class Minecraft implements IThreadListener {
|
|||
}
|
||||
|
||||
public void run() {
|
||||
CullingMod.intialize();
|
||||
this.running = true;
|
||||
|
||||
try {
|
||||
|
@ -1226,6 +1228,7 @@ public class Minecraft implements IThreadListener {
|
|||
* Runs the current tick.
|
||||
*/
|
||||
public void runTick() throws IOException {
|
||||
CullingMod.setRequestCull(true);
|
||||
if (this.rightClickDelayTimer > 0) {
|
||||
--this.rightClickDelayTimer;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,8 @@ import java.util.TimeZone;
|
|||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import net.hoosiertransfer.Config;
|
||||
import net.hoosiertransfer.CullingMod;
|
||||
import net.lax1dude.eaglercraft.v1_8.Display;
|
||||
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
|
||||
import net.lax1dude.eaglercraft.v1_8.HString;
|
||||
|
@ -413,7 +415,16 @@ public class GuiOverlayDebug extends Gui {
|
|||
arraylist.add(HString.format("Looking at: %d %d %d", new Object[] { Integer.valueOf(blockpos1.getX()),
|
||||
Integer.valueOf(blockpos1.getY()), Integer.valueOf(blockpos1.getZ()) }));
|
||||
}
|
||||
arraylist.add("[Culling] Last pass: " + CullingMod.cullTask.lastTime + "ms");
|
||||
arraylist.add("[Culling] Rendered Block Entities: " + CullingMod.renderedBlockEntities + " Skipped: "
|
||||
+ CullingMod.skippedBlockEntities);
|
||||
arraylist.add("[Culling] Rendered Entities: " + CullingMod.renderedEntities + " Skipped: " + CullingMod.skippedEntities);
|
||||
|
||||
CullingMod.renderedBlockEntities = 0;
|
||||
CullingMod.skippedBlockEntities = 0;
|
||||
CullingMod.renderedEntities = 0;
|
||||
CullingMod.skippedEntities = 0;
|
||||
|
||||
return arraylist;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import java.util.Map;
|
|||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import net.hoosiertransfer.Config;
|
||||
import net.hoosiertransfer.CullingMod;
|
||||
import net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.opengl.OpenGlHelper;
|
||||
import net.lax1dude.eaglercraft.v1_8.opengl.WorldRenderer;
|
||||
|
@ -24,6 +26,7 @@ import net.minecraft.client.model.ModelSlime;
|
|||
import net.minecraft.client.model.ModelSquid;
|
||||
import net.minecraft.client.model.ModelWolf;
|
||||
import net.minecraft.client.model.ModelZombie;
|
||||
import net.minecraft.client.renderer.EntityRenderer;
|
||||
import net.minecraft.client.renderer.RenderGlobal;
|
||||
import net.minecraft.client.renderer.Tessellator;
|
||||
import net.minecraft.client.renderer.culling.ICamera;
|
||||
|
@ -373,6 +376,17 @@ public class RenderManager {
|
|||
|
||||
public boolean doRenderEntity(Entity entity, double x, double y, double z, float entityYaw, float partialTicks,
|
||||
boolean parFlag) {
|
||||
if (!entity.isForcedVisible() && entity.isCulled()) {
|
||||
Render<Entity> render = getEntityRenderObject(entity);
|
||||
if (Config.renderNameTagsThroguthWalls && render.canRenderName(entity)) {
|
||||
render.renderLivingLabel(entity, entity.getDisplayName().getFormattedText(), x, y, z, 64);
|
||||
}
|
||||
CullingMod.skippedEntities++;
|
||||
return false;
|
||||
}
|
||||
CullingMod.renderedEntities++;
|
||||
entity.setOutOfCamera(false);
|
||||
|
||||
Render render = null;
|
||||
|
||||
try {
|
||||
|
|
|
@ -4,6 +4,7 @@ import java.util.Map;
|
|||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import net.hoosiertransfer.CullingMod;
|
||||
import net.lax1dude.eaglercraft.v1_8.opengl.GlStateManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.opengl.OpenGlHelper;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
|
@ -143,6 +144,12 @@ public class TileEntityRendererDispatcher {
|
|||
*/
|
||||
public void renderTileEntityAt(TileEntity tileEntityIn, double x, double y, double z, float partialTicks,
|
||||
int destroyStage) {
|
||||
if (!tileEntityIn.isForcedVisible() && tileEntityIn.isCulled()) {
|
||||
CullingMod.skippedBlockEntities++;
|
||||
return;
|
||||
}
|
||||
CullingMod.renderedBlockEntities++;
|
||||
|
||||
TileEntitySpecialRenderer tileentityspecialrenderer = this.getSpecialRenderer(tileEntityIn);
|
||||
if (tileentityspecialrenderer != null) {
|
||||
try {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package net.minecraft.entity;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import net.hoosiertransfer.Config;
|
||||
import net.lax1dude.eaglercraft.v1_8.EaglercraftRandom;
|
||||
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
|
||||
import net.lax1dude.eaglercraft.v1_8.HString;
|
||||
|
@ -148,6 +150,39 @@ public abstract class Entity implements ICommandSender {
|
|||
protected EaglercraftUUID entityUniqueID;
|
||||
private final CommandResultStats cmdResultStats;
|
||||
|
||||
private long lastTime = 0;
|
||||
private boolean culled = false;
|
||||
private boolean outOfCamera = false;
|
||||
|
||||
public void setTimeout() {
|
||||
lastTime = System.currentTimeMillis() + 1000;
|
||||
}
|
||||
|
||||
public boolean isForcedVisible() {
|
||||
return lastTime > System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void setCulled(boolean value) {
|
||||
this.culled = value;
|
||||
if (!value) {
|
||||
setTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCulled() {
|
||||
if (!Config.enableCulling) return false;
|
||||
return culled;
|
||||
}
|
||||
|
||||
public void setOutOfCamera(boolean value) {
|
||||
this.outOfCamera = value;
|
||||
}
|
||||
|
||||
public boolean isOutOfCamera() {
|
||||
if (!Config.enableCulling) return false;
|
||||
return outOfCamera;
|
||||
}
|
||||
|
||||
public int getEntityId() {
|
||||
return this.entityId;
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import java.util.concurrent.Callable;
|
|||
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import net.hoosiertransfer.Config;
|
||||
import net.lax1dude.eaglercraft.v1_8.HString;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
|
||||
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
|
||||
|
@ -48,6 +49,39 @@ public abstract class TileEntity {
|
|||
private int blockMetadata = -1;
|
||||
protected Block blockType;
|
||||
|
||||
private long lastTime = 0;
|
||||
private boolean culled = false;
|
||||
private boolean outOfCamera = false;
|
||||
|
||||
public void setTimeout() {
|
||||
lastTime = System.currentTimeMillis() + 1000;
|
||||
}
|
||||
|
||||
public boolean isForcedVisible() {
|
||||
return lastTime > System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void setCulled(boolean value) {
|
||||
this.culled = value;
|
||||
if (!value) {
|
||||
setTimeout();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isCulled() {
|
||||
if (!Config.enableCulling) return false;
|
||||
return culled;
|
||||
}
|
||||
|
||||
public void setOutOfCamera(boolean value) {
|
||||
this.outOfCamera = value;
|
||||
}
|
||||
|
||||
public boolean isOutOfCamera() {
|
||||
if (!Config.enableCulling) return false;
|
||||
return outOfCamera;
|
||||
}
|
||||
|
||||
/**+
|
||||
* Adds a new two-way mapping between the class and its string
|
||||
* name in both hashmaps.
|
||||
|
|
Loading…
Reference in New Issue