only generate and mesh one chunk per loop cycle
add setup for voxel size modeling/meshing needs optimization switch to using a bitfield instead of queuestreemaps-chunkstates-iteration
parent
a96c09df4b
commit
dd4732c1c1
|
@ -1,4 +1,7 @@
|
|||
.idea
|
||||
.vscode
|
||||
.gradle
|
||||
bin/**
|
||||
bin
|
||||
bin
|
||||
build/
|
||||
hs_*
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package com.emamaker.voxeltest.intervaltrees;
|
||||
|
||||
import com.emamaker.voxeltest.intervaltrees.data.IntervalMap;
|
||||
import com.emamaker.voxeltest.intervaltrees.utils.Config;
|
||||
import com.emamaker.voxeltest.intervaltrees.world.WorldManager;
|
||||
import com.emamaker.voxeltest.intervaltrees.world.blocks.Blocks;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
|
@ -15,36 +17,34 @@ import com.jme3.util.BufferUtils;
|
|||
public class Voxel extends SimpleApplication {
|
||||
|
||||
WorldManager worldManager = new WorldManager(this);
|
||||
Vector3f oldCamPos = new Vector3f(), pos = new Vector3f();
|
||||
public Vector3f oldCamPos = new Vector3f(), pos = new Vector3f();
|
||||
|
||||
public static void main(String[] args) {
|
||||
Voxel app = new Voxel();
|
||||
app.setShowSettings(false); // Settings dialog not supported on mac
|
||||
app.setShowSettings(true); // Settings dialog not supported on mac
|
||||
app.start();
|
||||
|
||||
BufferUtils.setTrackDirectMemoryEnabled(true);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simpleInitApp() {
|
||||
getFlyByCamera().setMoveSpeed(20f);
|
||||
getCamera().setLocation(new Vector3f(32f, 32f, 32f));
|
||||
getFlyByCamera().setMoveSpeed(40f);
|
||||
getCamera().setLocation(new Vector3f(Config.RENDER_DISTANCE, Config.RENDER_DISTANCE, Config.RENDER_DISTANCE).mult(Config.CHUNK_SIZE));
|
||||
getCamera().lookAt(new Vector3f(Config.CHUNK_SIZE,Config.CHUNK_SIZE, Config.CHUNK_SIZE), Vector3f.UNIT_Y);
|
||||
|
||||
pos.set(this.getCamera().getLocation());
|
||||
worldManager.initWorld();
|
||||
worldManager.render();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simpleUpdate(float tpf) {
|
||||
pos.set(this.getCamera().getLocation());
|
||||
// if (!(pos.equals(oldCamPos))) System.out.println(pos);
|
||||
oldCamPos.set(pos);
|
||||
}
|
||||
|
||||
worldManager.render();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simpleRender(RenderManager rm) {
|
||||
// add render code here (if any)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,99 +1,25 @@
|
|||
package com.emamaker.voxeltest.intervaltrees.renderer;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
|
||||
import com.emamaker.voxeltest.intervaltrees.data.IntervalTree;
|
||||
import com.emamaker.voxeltest.intervaltrees.utils.Config;
|
||||
import com.emamaker.voxeltest.intervaltrees.world.Chunk;
|
||||
import com.emamaker.voxeltest.intervaltrees.world.WorldManager;
|
||||
import com.emamaker.voxeltest.intervaltrees.world.blocks.Blocks;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState.FaceCullMode;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Mesh;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.VertexBuffer.Type;
|
||||
import com.jme3.scene.shape.Box;
|
||||
import com.jme3.util.BufferUtils;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ChunkRenderer {
|
||||
|
||||
// Just render a full cube for every block
|
||||
public void stupidMeshing(WorldManager mgr, Chunk chunk) {
|
||||
// Breadth-first visit each node of the tree
|
||||
chunk.blocks.print();
|
||||
|
||||
Queue<IntervalTree<Blocks>.TreeNode> queue = new ArrayDeque<>();
|
||||
queue.add(chunk.blocks.getRoot());
|
||||
|
||||
IntervalTree<Blocks>.TreeNode t;
|
||||
Box b;
|
||||
Geometry geom;
|
||||
Material mat;
|
||||
int[] coords1, coords2;
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
t = queue.poll();
|
||||
if (t.getLeft() != null)
|
||||
queue.add(t.getLeft());
|
||||
if (t.getRight() != null)
|
||||
queue.add(t.getRight());
|
||||
|
||||
if (!t.getValue().equals(Blocks.AIR)) {
|
||||
|
||||
int start = t.getInterval().getLow();
|
||||
for (int i = t.getInterval().getLow(); i <= t.getInterval().getHigh(); i++) {
|
||||
|
||||
if ((i % Config.CHUNK_SIZE == 0 && i != t.getInterval().getLow())
|
||||
|| i == t.getInterval().getHigh()) {
|
||||
if (i == t.getInterval().getHigh())
|
||||
i++;
|
||||
b = new Box((i - start) * 0.5f, 0.5f, 0.5f);
|
||||
|
||||
geom = new Geometry("Box", b);
|
||||
mat = new Material(mgr.game.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.getAdditionalRenderState().setWireframe(true);
|
||||
geom.setMaterial(mat);
|
||||
|
||||
coords1 = Chunk.coord1DTo3D(start);
|
||||
coords2 = Chunk.coord1DTo3D(i);
|
||||
|
||||
geom.getLocalTransform().setTranslation(coords1[0] + b.xExtent, coords1[1] + b.yExtent,
|
||||
coords1[2] + b.zExtent);
|
||||
System.out.println(Arrays.toString(coords1) + "-" + Arrays.toString(coords2)
|
||||
+ "\t\tBox with extext " + b.xExtent + " (both directions) with center"
|
||||
+ geom.getLocalTransform().getTranslation());
|
||||
|
||||
chunk.chunkNode.attachChild(geom);
|
||||
|
||||
if (t.getValue() == Blocks.DIRT) {
|
||||
mat.setColor("Color", ColorRGBA.Brown);
|
||||
} else if (t.getValue() == Blocks.STONE) {
|
||||
mat.setColor("Color", ColorRGBA.Gray);
|
||||
} else if (t.getValue() == Blocks.GRASS) {
|
||||
mat.setColor("Color", ColorRGBA.Green);
|
||||
}
|
||||
|
||||
start = i;
|
||||
}
|
||||
}
|
||||
|
||||
// System.out.println("Placing a box long " + b.xExtent + " at "
|
||||
// + Arrays.toString(coords));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Blocks[] blocks;
|
||||
public void stupidArrayMeshing(WorldManager mgr, Chunk chunk) {
|
||||
Blocks[] blocks = chunk.treeTo1DArray();
|
||||
blocks = chunk.treeTo1DArray();
|
||||
|
||||
int idx;
|
||||
for (int i = 0; i < Config.CHUNK_SIZE; i++) {
|
||||
|
@ -149,21 +75,22 @@ public class ChunkRenderer {
|
|||
* containing all the quads. In the future, maybe traslucent blocks and liquids
|
||||
* will need a separate mesh, but still on a per-chunk basis
|
||||
*/
|
||||
Vector3f[] vertices = new Vector3f[Config.CHUNK_3DCOORD_MAX_INDEX + 1];
|
||||
float[] colors = new float[(Config.CHUNK_3DCOORD_MAX_INDEX + 1) * 4];
|
||||
short[] indices = new short[(Config.CHUNK_3DCOORD_MAX_INDEX + 1) * 4];
|
||||
Vector3f[] vertices = new Vector3f[(Config.CHUNK_3DCOORD_MAX_INDEX + 1) * 8];
|
||||
float[] colors = new float[(Config.CHUNK_3DCOORD_MAX_INDEX + 1) * 8];
|
||||
short[] indices = new short[(Config.CHUNK_3DCOORD_MAX_INDEX + 1) * 8];
|
||||
|
||||
short vIndex = 0;
|
||||
short iIndex = 0;
|
||||
short cIndex = 0;
|
||||
|
||||
public void greedyMeshing(WorldManager mgr, Chunk chunk) {
|
||||
|
||||
vIndex = 0;
|
||||
iIndex = 0;
|
||||
cIndex = 0;
|
||||
|
||||
// convert tree to array since it is easier to work with it
|
||||
Blocks[] blocks = chunk.treeTo1DArray();
|
||||
blocks = chunk.treeTo1DArray();
|
||||
|
||||
// System.out.println(Arrays.toString(blocks));
|
||||
|
||||
|
@ -328,10 +255,10 @@ public class ChunkRenderer {
|
|||
|
||||
// System.out.println(vIndex + ", " + iIndex);
|
||||
|
||||
vertices[vIndex] = bottomLeft;
|
||||
vertices[vIndex + 1] = bottomRight;
|
||||
vertices[vIndex + 2] = topLeft;
|
||||
vertices[vIndex + 3] = topRight;
|
||||
vertices[vIndex] = bottomLeft.mult(Config.VOXEL_SIZE);
|
||||
vertices[vIndex + 1] = bottomRight.mult(Config.VOXEL_SIZE);
|
||||
vertices[vIndex + 2] = topLeft.mult(Config.VOXEL_SIZE);
|
||||
vertices[vIndex + 3] = topRight.mult(Config.VOXEL_SIZE);
|
||||
|
||||
if (backFace) {
|
||||
indices[iIndex] = (short) (vIndex + 2);
|
||||
|
|
|
@ -8,5 +8,8 @@ public class Config {
|
|||
public static int CHUNK_SIZE = 16;
|
||||
// return x + maxX * (y + z * maxY);
|
||||
public static int CHUNK_3DCOORD_MAX_INDEX = (CHUNK_SIZE-1) + CHUNK_SIZE * ( (CHUNK_SIZE - 1) + (CHUNK_SIZE - 1) * CHUNK_SIZE);
|
||||
public static int RENDER_DISTANCE = 8;
|
||||
public static int VOXEL_SIZE = 1 ;
|
||||
public static int CHUNK_LENGTH = CHUNK_SIZE * VOXEL_SIZE;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
package com.emamaker.voxeltest.intervaltrees.utils;
|
||||
|
||||
public class Utils {
|
||||
|
||||
|
||||
public static boolean withinDistance(int startx, int starty, int startz, int x, int y, int z, int dist) {
|
||||
return (x-startx)*(x-startx) + (y - starty)*(y-starty) + (z-startz)*(z-startz) <= dist*dist;
|
||||
}
|
||||
// https://stackoverflow.com/questions/20266201/3d-array-1d-flat-indexing
|
||||
//flatten 3d coords to 1d array cords
|
||||
public static int coord3DTo1D (int x, int y, int z, int maxX, int maxY, int maxZ){
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
package com.emamaker.voxeltest.intervaltrees.world;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.Queue;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import com.emamaker.voxeltest.intervaltrees.data.Interval;
|
||||
import com.emamaker.voxeltest.intervaltrees.data.IntervalMap;
|
||||
import com.emamaker.voxeltest.intervaltrees.data.IntervalTree;
|
||||
import com.emamaker.voxeltest.intervaltrees.utils.Config;
|
||||
import com.emamaker.voxeltest.intervaltrees.utils.Utils;
|
||||
import com.emamaker.voxeltest.intervaltrees.world.blocks.Blocks;
|
||||
|
@ -18,15 +12,19 @@ public class Chunk {
|
|||
public Vector3f pos;
|
||||
public IntervalMap<Blocks> blocks = new IntervalMap<>();
|
||||
public Node chunkNode = new Node();
|
||||
public boolean loaded = false;
|
||||
|
||||
// A bit field representing the state of the chunk, where each bit is an operation done on the chunk
|
||||
private byte state = 0;
|
||||
// Convenient access to the bit field, each state is the position in the byte (0 is LSB)
|
||||
public static byte CHUNK_STATE_GENERATED = 1;
|
||||
public static byte CHUNK_STATE_MESHED = 2;
|
||||
public static byte CHUNK_STATE_LOADED = 4;
|
||||
public Chunk() {
|
||||
this(Vector3f.ZERO);
|
||||
}
|
||||
|
||||
public Chunk(Vector3f pos_) {
|
||||
this.pos = pos_;
|
||||
chunkNode.setLocalTranslation(pos.mult(Config.CHUNK_SIZE));
|
||||
chunkNode.setLocalTranslation(pos.mult(Config.CHUNK_SIZE).mult(Config.VOXEL_SIZE));
|
||||
|
||||
// I still have to decided if this is necessary. With an empty tree this
|
||||
// takes O(1)
|
||||
|
@ -34,6 +32,17 @@ public class Chunk {
|
|||
blocks.insert(0, Config.CHUNK_3DCOORD_MAX_INDEX, Blocks.AIR);
|
||||
}
|
||||
|
||||
public void setState(byte nstate, boolean value){
|
||||
if(value) state |= nstate;
|
||||
else state &= ~nstate;
|
||||
}
|
||||
|
||||
public boolean bgetState(byte nstate){
|
||||
return (state & nstate) != 0;
|
||||
}
|
||||
public int getState(byte nstate){
|
||||
return state & nstate;
|
||||
}
|
||||
public static int coord3DTo1D(int x, int y, int z) {
|
||||
return Utils.coord3DTo1D(x, y, z, Config.CHUNK_SIZE, Config.CHUNK_SIZE, Config.CHUNK_SIZE);
|
||||
}
|
||||
|
@ -42,6 +51,14 @@ public class Chunk {
|
|||
return Utils.coord1DTo3D(idx, Config.CHUNK_SIZE, Config.CHUNK_SIZE, Config.CHUNK_SIZE);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set blocks. Interval to be intended in the same way as in interval tree
|
||||
* (flatten 1d index of 3d coords)
|
||||
*/
|
||||
public void setBlocks(Blocks block, int intLow, int intHigh) {
|
||||
this.blocks.insert( Math.max(0,intLow), Math.min(Config.CHUNK_3DCOORD_MAX_INDEX+1, intHigh),block);
|
||||
}
|
||||
|
||||
public Blocks getBlock(int x, int y, int z) {
|
||||
return blocks.valueAtKey(Chunk.coord3DTo1D(x, y, z));
|
||||
}
|
||||
|
@ -108,14 +125,6 @@ public class Chunk {
|
|||
this.treeFrom1DArray(array);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set blocks. Interval to be intended in the same way as in interval tree
|
||||
* (flatten 1d index of 3d coords)
|
||||
*/
|
||||
public void setBlocks(Blocks block, int intLow, int intHigh) {
|
||||
this.blocks.insert( Math.max(0,intLow), Math.min(Config.CHUNK_3DCOORD_MAX_INDEX+1, intHigh),block);
|
||||
}
|
||||
|
||||
public Blocks[] treeTo1DArray() {
|
||||
// System.out.println(Config.CHUNK_3DCOORD_MAX_INDEX);
|
||||
Blocks[] result = new Blocks[Config.CHUNK_3DCOORD_MAX_INDEX + 1];
|
||||
|
|
|
@ -1,42 +1,126 @@
|
|||
package com.emamaker.voxeltest.intervaltrees.world;
|
||||
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
|
||||
import com.emamaker.voxeltest.intervaltrees.Voxel;
|
||||
import com.emamaker.voxeltest.intervaltrees.renderer.ChunkRenderer;
|
||||
import com.emamaker.voxeltest.intervaltrees.utils.Config;
|
||||
import com.emamaker.voxeltest.intervaltrees.utils.Utils;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.scene.Node;
|
||||
|
||||
public class WorldManager {
|
||||
|
||||
public final Voxel game;
|
||||
public final Voxel game;
|
||||
|
||||
public int camx, camy, camz;
|
||||
|
||||
ChunkRenderer chunkRenderer = new ChunkRenderer();
|
||||
|
||||
HashMap<Vector3f, Chunk> chunks = new HashMap<>();
|
||||
Queue<Chunk> chunkToModel = new ArrayDeque();
|
||||
ArrayList<Chunk> chunksToRender = new ArrayList<>();
|
||||
Node worldNode = new Node();
|
||||
|
||||
ConcurrentHashMap<Vector3f, Chunk> chunks = new ConcurrentHashMap<>();
|
||||
|
||||
public WorldManager(final Voxel game_) {
|
||||
this.game = game_;
|
||||
}
|
||||
|
||||
public void initWorld() {
|
||||
chunks.put(Vector3f.ZERO, new Chunk());
|
||||
chunks.get(Vector3f.ZERO).arrayGenerateCorner();
|
||||
// game.getRootNode().attachChild(worldNode);
|
||||
// System.out.println(Arrays.toString(game.getRootNode().getChildren().toArray()));
|
||||
|
||||
chunkToModel.add(chunks.get(Vector3f.ZERO));
|
||||
|
||||
// Chunk c = new Chunk(new Vector3f(0,0,0));
|
||||
// c.arrayGenerateCorner();
|
||||
// chunkRenderer.greedyMeshing(this, c);
|
||||
// game.getRootNode().attachChild(c.chunkNode);
|
||||
// chunks.put(new Vector3f(0,0,0), new Chunk(new Vector3f(0,0,0)));
|
||||
// chunks.get(new Vector3f(0,0,0)).generatePlane();
|
||||
// chunkRenderer.stupidArrayMeshing(this, chunks.get(new Vector3f(0,0,0)));
|
||||
// game.getRootNode().attachChild(chunks.get(new Vector3f(0,0,0)).chunkNode);
|
||||
|
||||
// chunks.put(new Vector3f(0,0,1), new Chunk(new Vector3f(0,0,1)));
|
||||
// chunks.get(new Vector3f(0,0,1)).generatePlane();
|
||||
// chunkRenderer.stupidArrayMeshing(this, chunks.get(new Vector3f(0,0,1)));
|
||||
// game.getRootNode().attachChild(chunks.get(new Vector3f(0,0,1)).chunkNode);
|
||||
// chunks.put(new Vector3f(0,0,2), new Chunk(new Vector3f(0,0,2)));
|
||||
// chunks.get(new Vector3f(0,0,2)).generatePlane();
|
||||
// chunkRenderer.stupidArrayMeshing(this, chunks.get(new Vector3f(0,0,2)));
|
||||
// game.getRootNode().attachChild(chunks.get(new Vector3f(0,0,2)).chunkNode);
|
||||
}
|
||||
|
||||
Chunk c;
|
||||
Vector3f v = new Vector3f();
|
||||
int chunkX, chunkY, chunkZ;
|
||||
public boolean generated = false, meshed = false;
|
||||
|
||||
public void render() {
|
||||
for (Chunk c : chunkToModel) {
|
||||
// chunkRenderer.stupidArrayMeshing(this, c);
|
||||
chunkRenderer.greedyMeshing(this, c);
|
||||
|
||||
game.getRootNode().attachChild(c.chunkNode);
|
||||
camx = (int) (game.pos.getX());
|
||||
camy = (int) (game.pos.getY());
|
||||
camz = (int) (game.pos.getZ());
|
||||
|
||||
// // clamp to "chunk" coordinates
|
||||
// // The chunk with origin at x,y,z in chunk coords actually has the origin at
|
||||
// x*chunksize, y*chunksize, z*chunksize in world coords
|
||||
chunkX = (int) (camx / Config.CHUNK_LENGTH);
|
||||
chunkY = (int) (camy / Config.CHUNK_LENGTH);
|
||||
chunkZ = (int) (camz / Config.CHUNK_LENGTH);
|
||||
|
||||
// System.out.println("camera at" + game.pos + new Vector3f(chunkX, chunkY,
|
||||
// chunkZ));
|
||||
|
||||
for (int i = Math.max(0, chunkX - Config.RENDER_DISTANCE); i < chunkX + Config.RENDER_DISTANCE; i++) {
|
||||
for (int j = Math.max(0, chunkY - Config.RENDER_DISTANCE); j < chunkY + Config.RENDER_DISTANCE; j++) {
|
||||
// for (int j = Math.max(0,chunkY - Config.RENDER_DISTANCE); j < chunkY +
|
||||
// Config.RENDER_DISTANCE; j++) {
|
||||
for (int k = Math.max(0, chunkZ - Config.RENDER_DISTANCE); k < chunkZ + Config.RENDER_DISTANCE; k++) {
|
||||
if(!Utils.withinDistance(chunkX, chunkY, chunkZ, i,j,k,Config.RENDER_DISTANCE)) continue;
|
||||
|
||||
v = new Vector3f(i, j, k); // this part has to be revised, i can see spikes in memory usage
|
||||
|
||||
if (chunks.get(v) == null) {
|
||||
c = new Chunk(v);
|
||||
c.arrayGenerateCorner();
|
||||
chunkRenderer.greedyMeshing(this, c);
|
||||
game.getRootNode().attachChild(c.chunkNode);
|
||||
chunks.put(v, c);
|
||||
}
|
||||
v = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
for(Chunk c : chunks.values()){
|
||||
if(!generated && !c.bgetState(Chunk.CHUNK_STATE_GENERATED)) {
|
||||
c.arrayGenerateCorner();
|
||||
c.setState(Chunk.CHUNK_STATE_GENERATED, true);
|
||||
generated = true;
|
||||
}
|
||||
if(!meshed && c.bgetState(Chunk.CHUNK_STATE_GENERATED)&& !c.bgetState(Chunk.CHUNK_STATE_MESHED)) {
|
||||
chunkRenderer.greedyMeshing(this, c);
|
||||
c.setState(Chunk.CHUNK_STATE_MESHED, true);
|
||||
meshed = true;
|
||||
}
|
||||
if(!c.bgetState(Chunk.CHUNK_STATE_LOADED) && c.bgetState(Chunk.CHUNK_STATE_GENERATED) && c.bgetState(Chunk.CHUNK_STATE_MESHED)){
|
||||
game.getRootNode().attachChild(c.chunkNode);
|
||||
c.setState(Chunk.CHUNK_STATE_LOADED, true);
|
||||
}
|
||||
if(!Utils.withinDistance(chunkX, chunkY, chunkZ, (int)c.pos.x, (int)c.pos.y, (int)c.pos.z, Config.RENDER_DISTANCE)){
|
||||
c.chunkNode.removeFromParent();
|
||||
chunks.remove(c.pos);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public boolean chunkInCameraLimits(Chunk c) {
|
||||
int vx = (int) c.pos.x;
|
||||
int vy = (int) c.pos.y;
|
||||
int vz = (int) c.pos.z;
|
||||
return vx >= Math.min(0, chunkX - Config.RENDER_DISTANCE ) && vx < Math.min(0, chunkX + Config.RENDER_DISTANCE) && vy >= chunkY - Config.RENDER_DISTANCE && vy < Math.min(0, chunkY + Config.RENDER_DISTANCE) && vz >= chunkZ - Config.RENDER_DISTANCE && vz < Math.min(0, chunkZ + Config.RENDER_DISTANCE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue