Compare commits
5 Commits
ef4c454a57
...
d3ffecaa09
Author | SHA1 | Date |
---|---|---|
aurailus | d3ffecaa09 | |
aurailus | 558fa4902a | |
aurailus | ebfe98d55e | |
aurailus | a14fb4a743 | |
aurailus | a55bff673d |
1375
.idea/workspace.xml
1375
.idea/workspace.xml
File diff suppressed because it is too large
Load Diff
|
@ -41,4 +41,13 @@ public class Utils {
|
|||
public static int sign(float in) {
|
||||
return (in > 0 ? 1 : in < 0 ? -1 : 0);
|
||||
}
|
||||
|
||||
public static String pad(double number, int pad) {
|
||||
String num = Double.toString(Math.round(number*100D)/100D);
|
||||
StringBuilder s = new StringBuilder(pad);
|
||||
s.append(num);
|
||||
for (var i = num.length(); i < pad; i++) s.insert(0, " ");
|
||||
return s.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ public class BlockAtlas {
|
|||
|
||||
public BlockAtlas() {
|
||||
blockDefs = new ArrayList<>();
|
||||
create("_:unloaded", null, true, false, false);
|
||||
create("_:air", null, false, false, false);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,20 +9,9 @@ import static helpers.ArrayTrans3D.CHUNK_SIZE;
|
|||
|
||||
public class BlockChunk {
|
||||
short[] blocks;
|
||||
private boolean[] visible;
|
||||
ArrayList<boolean[]> sidesOpaque;
|
||||
|
||||
public BlockChunk(short[] blocks, ArrayList<boolean[]> sidesOpaque) {
|
||||
public BlockChunk(short[] blocks) {
|
||||
this.blocks = blocks;
|
||||
this.sidesOpaque = sidesOpaque;
|
||||
}
|
||||
|
||||
public BlockChunk(byte[] blocks, ArrayList<byte[]> sidesOpaque) {
|
||||
this.blocks = RLE.decodeShorts(blocks);
|
||||
this.sidesOpaque = new ArrayList<>();
|
||||
for (var i = 0; i < 6; i++) {
|
||||
this.sidesOpaque.add(RLE.decodeBools(sidesOpaque.get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
public short getBlock(Vector3i pos) {
|
||||
|
@ -33,22 +22,8 @@ public class BlockChunk {
|
|||
return ArrayTrans3D.get(this.blocks, x, y, z);
|
||||
}
|
||||
|
||||
public boolean getVisible(Vector3i pos) {
|
||||
return getVisible(pos.x, pos.y, pos.z);
|
||||
}
|
||||
|
||||
public boolean getVisible(int x, int y, int z) {
|
||||
if (visible == null) calcVisible();
|
||||
return ArrayTrans3D.get(this.visible, x, y, z);
|
||||
}
|
||||
|
||||
public boolean[] getVisibleArray() {
|
||||
if (visible == null) calcVisible();
|
||||
return visible;
|
||||
}
|
||||
|
||||
private void calcVisible() {
|
||||
visible = new boolean[blocks.length];
|
||||
public boolean[] calcVisible(BlockChunk[] adjacentChunks) {
|
||||
boolean[] visible = new boolean[blocks.length];
|
||||
Vector3i pos = new Vector3i();
|
||||
|
||||
for (var i = 0; i < CHUNK_SIZE; i++) {
|
||||
|
@ -59,9 +34,9 @@ public class BlockChunk {
|
|||
ArrayTrans3D.set(visible, false, pos);
|
||||
|
||||
if (Game.definitions.getDef(getBlock(pos)).getVisible()) {
|
||||
var adjacent = getAdjacent(pos);
|
||||
for (short anAdjacent : adjacent) {
|
||||
if (!Game.definitions.getDef(anAdjacent).getCulls()) {
|
||||
var adjacent = getAdjacentOpaque(pos, adjacentChunks);
|
||||
for (boolean opaque : adjacent) {
|
||||
if (!opaque) {
|
||||
ArrayTrans3D.set(visible, true, pos);
|
||||
break;
|
||||
}
|
||||
|
@ -70,62 +45,81 @@ public class BlockChunk {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return visible;
|
||||
}
|
||||
|
||||
public short[] getAdjacent(Vector3i pos) {
|
||||
short[] adjacent = new short[6];
|
||||
Vector3i checkPos = new Vector3i(pos);
|
||||
public boolean[] getAdjacentOpaque(Vector3i pos, BlockChunk[] adjacentChunks) {
|
||||
boolean[] adjacent = new boolean[6];
|
||||
|
||||
checkPos.set(pos).add(1, 0, 0);
|
||||
Vector3i checkPos = new Vector3i(pos).add(1, 0, 0);
|
||||
|
||||
adjacent[0] = getBlockIncludeEdges(checkPos);
|
||||
adjacent[0] = getOpaqueIncludeEdges(checkPos, adjacentChunks);
|
||||
|
||||
checkPos.set(pos).add(-1, 0, 0);
|
||||
checkPos = new Vector3i(pos).add(-1, 0, 0);
|
||||
|
||||
adjacent[1] = getBlockIncludeEdges(checkPos);
|
||||
adjacent[1] = getOpaqueIncludeEdges(checkPos, adjacentChunks);
|
||||
|
||||
checkPos.set(pos).add(0, 1, 0);
|
||||
checkPos = new Vector3i(pos).add(0, 1, 0);
|
||||
|
||||
adjacent[2] = getBlockIncludeEdges(checkPos);
|
||||
adjacent[2] = getOpaqueIncludeEdges(checkPos, adjacentChunks);
|
||||
|
||||
checkPos.set(pos).add(0, -1, 0);
|
||||
checkPos = new Vector3i(pos).add(0, -1, 0);
|
||||
|
||||
adjacent[3] = getBlockIncludeEdges(checkPos);
|
||||
adjacent[3] = getOpaqueIncludeEdges(checkPos, adjacentChunks);
|
||||
|
||||
checkPos.set(pos).add(0, 0, 1);
|
||||
checkPos = new Vector3i(pos).add(0, 0, 1);
|
||||
|
||||
adjacent[4] = getBlockIncludeEdges(checkPos);
|
||||
adjacent[4] = getOpaqueIncludeEdges(checkPos, adjacentChunks);
|
||||
|
||||
checkPos.set(pos).add(0, 0, -1);
|
||||
checkPos = new Vector3i(pos).add(0, 0, -1);
|
||||
|
||||
adjacent[5] = getBlockIncludeEdges(checkPos);
|
||||
adjacent[5] = getOpaqueIncludeEdges(checkPos, adjacentChunks);
|
||||
|
||||
return adjacent;
|
||||
}
|
||||
|
||||
private short getBlockIncludeEdges(Vector3i pos) {
|
||||
private boolean getOpaqueIncludeEdges(Vector3i pos, BlockChunk[] adjacentChunks) {
|
||||
if (pos.x >= 16 || pos.x < 0 || pos.y >= 16 || pos.y < 0 || pos.z >= 16 || pos.z < 0) {
|
||||
try {
|
||||
return (short)(getEdgeOpaque(pos) ? 1 : 0);
|
||||
return Game.definitions.getDef(getEdgeBlock(pos, adjacentChunks)).getCulls();
|
||||
}
|
||||
catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
return -1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return getBlock(pos);
|
||||
return Game.definitions.getDef(getBlock(pos)).getCulls();
|
||||
}
|
||||
|
||||
private boolean getEdgeOpaque(Vector3i pos) throws Exception {
|
||||
if (pos.x == 16) return sidesOpaque.get(0)[pos.y * 16 + pos.z];
|
||||
if (pos.x == -1) return sidesOpaque.get(1)[pos.y * 16 + pos.z];
|
||||
private short getEdgeBlock(Vector3i pos, BlockChunk[] adjacentChunks) throws Exception {
|
||||
if (pos.x == 16) {
|
||||
if (adjacentChunks[0] == null) return 0;
|
||||
return adjacentChunks[0].getBlock(0, pos.y, pos.z);
|
||||
}
|
||||
if (pos.x == -1) {
|
||||
if (adjacentChunks[1] == null) return 0;
|
||||
return adjacentChunks[1].getBlock(15, pos.y, pos.z);
|
||||
}
|
||||
|
||||
if (pos.y == 16) return sidesOpaque.get(2)[pos.x * 16 + pos.z];
|
||||
if (pos.y == -1) return sidesOpaque.get(3)[pos.x * 16 + pos.z];
|
||||
if (pos.y == 16) {
|
||||
if (adjacentChunks[2] == null) return 0;
|
||||
return adjacentChunks[2].getBlock(pos.x, 0, pos.z);
|
||||
}
|
||||
if (pos.y == -1) {
|
||||
if (adjacentChunks[3] == null) return 0;
|
||||
return adjacentChunks[3].getBlock(pos.x, 15, pos.z);
|
||||
}
|
||||
|
||||
if (pos.z == 16) return sidesOpaque.get(4)[pos.y * 16 + pos.x];
|
||||
if (pos.z == -1) return sidesOpaque.get(5)[pos.y * 16 + pos.x];
|
||||
if (pos.z == 16) {
|
||||
if (adjacentChunks[4] == null) return 0;
|
||||
return adjacentChunks[4].getBlock(pos.x, pos.y, 0);
|
||||
}
|
||||
if (pos.z == -1) {
|
||||
if (adjacentChunks[5] == null) return 0;
|
||||
return adjacentChunks[5].getBlock(pos.x, pos.y, 15);
|
||||
}
|
||||
|
||||
throw new Exception("BAD VALUE");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -37,7 +37,6 @@ public class BlockDef {
|
|||
|
||||
public void setVisible(boolean visible) {
|
||||
this.visible = visible;
|
||||
if (!visible) this.culls = false;
|
||||
}
|
||||
|
||||
public void setSolid(boolean solid) {
|
||||
|
|
|
@ -1,179 +0,0 @@
|
|||
package client.game;
|
||||
|
||||
import client.engine.graphics.Material;
|
||||
import client.engine.graphics.Texture;
|
||||
import helpers.ChunkSerializer;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector3i;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import static helpers.ArrayTrans3D.CHUNK_SIZE;
|
||||
|
||||
public class ChunkAtlas {
|
||||
|
||||
private ArrayList<MeshChunk> meshChunks;
|
||||
private HashSet<Vector3i> loadingChunks;
|
||||
|
||||
private ThreadPoolExecutor meshGenPool;
|
||||
private ArrayList<Future> meshGenFutures;
|
||||
|
||||
|
||||
private ArrayList<BlockChunk> activeChunks;
|
||||
private HashMap<Vector3i, BlockChunk> activeChunkMap;
|
||||
|
||||
|
||||
private ArrayList<EncodedBlockChunk> cachedChunks;
|
||||
private HashMap<Vector3i, EncodedBlockChunk> cachedChunkMap;
|
||||
|
||||
public ChunkAtlas() {
|
||||
|
||||
try {
|
||||
var atlas = new FileInputStream(new File("atlas_0.png"));
|
||||
MeshChunk.meshMaterial = new Material(new Texture(atlas));
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
meshChunks = new ArrayList<>();
|
||||
loadingChunks = new HashSet<>();
|
||||
|
||||
meshGenPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(16);
|
||||
meshGenPool.setMaximumPoolSize(48);
|
||||
meshGenPool.setKeepAliveTime(32, TimeUnit.SECONDS);
|
||||
|
||||
meshGenFutures = new ArrayList<>();
|
||||
|
||||
activeChunks = new ArrayList<>();
|
||||
activeChunkMap = new HashMap<>();
|
||||
|
||||
cachedChunks = new ArrayList<>();
|
||||
cachedChunkMap = new HashMap<>();
|
||||
}
|
||||
|
||||
public ArrayList<MeshChunk> getVisibleChunks() {
|
||||
return meshChunks;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
try {
|
||||
loadMeshes();
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void loadMeshes() throws ExecutionException, InterruptedException {
|
||||
int maxTime = 4;
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
Iterator<Future> it = meshGenFutures.iterator();
|
||||
|
||||
while (it.hasNext() && System.currentTimeMillis() - start < maxTime) {
|
||||
Future f = it.next();
|
||||
if (f.isDone()) {
|
||||
GenChunkTask.ThreadRet ret = (GenChunkTask.ThreadRet) f.get();
|
||||
if (ret != null) {
|
||||
it.remove();
|
||||
|
||||
activeChunkMap.put(ret.position, ret.blockChunk);
|
||||
loadingChunks.remove(ret.position);
|
||||
|
||||
if (ret.meshChunk != null && ret.meshChunk.getMesh() != null) {
|
||||
ret.meshChunk.getMesh().init();
|
||||
|
||||
if (ret.meshChunk.getMesh().getVertexCount() != 0) {
|
||||
meshChunks.add(ret.meshChunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void loadChunksAroundPos(Vector3f pos, int range) {
|
||||
var cOffset = coordsToChunk(pos);
|
||||
|
||||
for (var i = -range; i < range; i++) {
|
||||
for (var j = -range; j < range; j++) {
|
||||
for (var k = -range; k < range; k++) {
|
||||
loadChunk(i + cOffset.x, j + cOffset.y, k + cOffset.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void loadChunk(int x, int y, int z) {
|
||||
Vector3i reqPos = new Vector3i(x, y, z);
|
||||
|
||||
if (!loadingChunks.contains(reqPos) && !activeChunkMap.containsKey(reqPos)) {
|
||||
loadingChunks.add(reqPos);
|
||||
Game.connection.requestChunk(reqPos, (pos, chunk) -> {
|
||||
var task = new GenChunkTask(pos, chunk);
|
||||
meshGenFutures.add(meshGenPool.submit(task));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized BlockChunk getChunk(Vector3i pos) {
|
||||
return activeChunkMap.get(pos);
|
||||
}
|
||||
|
||||
public int getBlock(int x, int y, int z) {
|
||||
return getBlock(new Vector3i(x, y, z));
|
||||
}
|
||||
|
||||
public int getBlock(Vector3i pos) {
|
||||
var chunkPos = coordsToChunk(new Vector3f(pos));
|
||||
var chunk = getChunk(chunkPos);
|
||||
if (chunk == null) return -1;
|
||||
var blockPos = new Vector3i(coordToLocal(pos.x), coordToLocal(pos.y), coordToLocal(pos.z));
|
||||
return chunk.getBlock(blockPos);
|
||||
}
|
||||
|
||||
private Vector3i coordsToChunk(Vector3f pos) {
|
||||
return new Vector3i((int)Math.floor(pos.x / CHUNK_SIZE), (int)Math.floor(pos.y / CHUNK_SIZE), (int)Math.floor(pos.z / CHUNK_SIZE));
|
||||
}
|
||||
|
||||
private int coordToLocal(int num) {
|
||||
return (num >= 0) ? (num % CHUNK_SIZE) : ((CHUNK_SIZE - 1) - Math.abs(num + 1) % CHUNK_SIZE);
|
||||
}
|
||||
|
||||
private class GenChunkTask implements Callable<GenChunkTask.ThreadRet> {
|
||||
Vector3i pos;
|
||||
byte[] chunk;
|
||||
|
||||
public GenChunkTask(Vector3i pos, byte[] chunk) {
|
||||
this.pos = pos;
|
||||
this.chunk = chunk;
|
||||
}
|
||||
|
||||
private class ThreadRet {
|
||||
public BlockChunk blockChunk;
|
||||
public MeshChunk meshChunk;
|
||||
public Vector3i position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreadRet call() {
|
||||
BlockChunk blockChunk = ChunkSerializer.decodeChunk(chunk);
|
||||
MeshChunk meshChunk = new MeshChunk(pos);
|
||||
meshChunk.createMesh(blockChunk);
|
||||
|
||||
ThreadRet r = new ThreadRet();
|
||||
r.blockChunk = blockChunk;
|
||||
r.meshChunk = meshChunk;
|
||||
r.position = pos;
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
meshGenPool.shutdown();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package client.game;
|
||||
|
||||
import client.game.network.ConnMan;
|
||||
import org.joml.Vector3i;
|
||||
|
||||
public class ChunkLoader {
|
||||
private World world;
|
||||
private Player player;
|
||||
private ConnMan connection;
|
||||
|
||||
private int tick = 0;
|
||||
|
||||
public ChunkLoader(World world, Player player) {
|
||||
this.world = world;
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
public void setConnection(ConnMan connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
tick++;
|
||||
|
||||
var floatPos = player.getPosition();
|
||||
if (tick % 30 == 0) connection.sendPosition(new Vector3i(Math.round(floatPos.x), Math.round(floatPos.y), Math.round(floatPos.z)));
|
||||
}
|
||||
|
||||
public void newChunk(Vector3i pos, byte[] data) {
|
||||
// System.out.println("Got chunk for " + pos);
|
||||
|
||||
world.addChunk(pos, data);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package client.game;
|
||||
|
||||
import client.game.blockmodels.MeshInfo;
|
||||
import client.game.blockmodels.MeshPart;
|
||||
import helpers.ArrayTrans3D;
|
||||
import org.joml.Vector3i;
|
||||
|
@ -13,6 +14,7 @@ public class ChunkMeshBuilder {
|
|||
public float[] normals;
|
||||
public int[] indices;
|
||||
|
||||
MeshInfo m;
|
||||
|
||||
private List<Float> vertsList;
|
||||
private List<Float> texCoordsList;
|
||||
|
@ -27,28 +29,31 @@ public class ChunkMeshBuilder {
|
|||
}
|
||||
|
||||
private void addFace(MeshPart face, Vector3i offset) {
|
||||
for (var i = 0; i < face.positions.length/3; i++) {
|
||||
vertsList.add(face.positions[i*3] + offset.x);
|
||||
vertsList.add(face.positions[i*3+1] + offset.y);
|
||||
vertsList.add(face.positions[i*3+2] + offset.z);
|
||||
face.getMeshData(m);
|
||||
|
||||
for (var i = 0; i < m.positions.length/3; i++) {
|
||||
vertsList.add(m.positions[i*3] + offset.x);
|
||||
vertsList.add(m.positions[i*3+1] + offset.y);
|
||||
vertsList.add(m.positions[i*3+2] + offset.z);
|
||||
}
|
||||
|
||||
for (var i = 0; i < face.normals.length; i++) {
|
||||
normalsList.add(face.normals[i]);
|
||||
for (var i = 0; i < m.normals.length; i++) {
|
||||
normalsList.add(m.normals[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < face.texData.length; i++) {
|
||||
texCoordsList.add(face.texData[i]);
|
||||
for (var i = 0; i < m.texData.length; i++) {
|
||||
texCoordsList.add(m.texData[i]);
|
||||
}
|
||||
|
||||
for (var i = 0; i < face.indices.length; i++) {
|
||||
var indiceVal = currIndiceVal + face.indices[i];
|
||||
for (var i = 0; i < m.indices.length; i++) {
|
||||
var indiceVal = currIndiceVal + m.indices[i];
|
||||
indicesList.add(indiceVal);
|
||||
}
|
||||
currIndiceVal += face.positions.length/3;
|
||||
currIndiceVal += m.positions.length/3;
|
||||
}
|
||||
|
||||
public ChunkMeshBuilder(BlockChunk blockChunk) {
|
||||
public ChunkMeshBuilder(BlockChunk blockChunk, BlockChunk[] adjacent) {
|
||||
m = new MeshInfo();
|
||||
|
||||
vertsList = new ArrayList<>();
|
||||
texCoordsList = new ArrayList<>();
|
||||
|
@ -57,31 +62,33 @@ public class ChunkMeshBuilder {
|
|||
|
||||
Vector3i offset = new Vector3i(0, 0, 0);
|
||||
|
||||
for (var i = 0; i < blockChunk.getVisibleArray().length; i++) {
|
||||
boolean[] visible = blockChunk.calcVisible(adjacent);
|
||||
|
||||
for (var i = 0; i < visible.length; i++) {
|
||||
ArrayTrans3D.indToVec(i, offset); //Set offset
|
||||
|
||||
if (blockChunk.getVisible(offset)) {
|
||||
var adj = blockChunk.getAdjacent(offset);
|
||||
if (visible[i]) {
|
||||
var adj = blockChunk.getAdjacentOpaque(offset, adjacent);
|
||||
var bm = Game.definitions.getDef(blockChunk.getBlock(offset)).getModel();
|
||||
|
||||
addFaces(bm.noCulledMP, offset);
|
||||
|
||||
if (!Game.definitions.getDef(adj[0]).getCulls()) { //X Pos
|
||||
if (!adj[0]) { //X Pos
|
||||
addFaces(bm.xPosMP, offset);
|
||||
}
|
||||
if (!Game.definitions.getDef(adj[1]).getCulls()) { //X Neg
|
||||
if (!adj[1]) { //X Neg
|
||||
addFaces(bm.xNegMP, offset);
|
||||
}
|
||||
if (!Game.definitions.getDef(adj[2]).getCulls()) { //Y Pos
|
||||
if (!adj[2]) { //Y Pos
|
||||
addFaces(bm.yPosMP, offset);
|
||||
}
|
||||
if (!Game.definitions.getDef(adj[3]).getCulls()) { //Y Neg
|
||||
if (!adj[3]) { //Y Neg
|
||||
addFaces(bm.yNegMP, offset);
|
||||
}
|
||||
if (!Game.definitions.getDef(adj[4]).getCulls()) { //Z Pos
|
||||
if (!adj[4]) { //Z Pos
|
||||
addFaces(bm.zPosMP, offset);
|
||||
}
|
||||
if (!Game.definitions.getDef(adj[5]).getCulls()) { //Z Neg
|
||||
if (!adj[5]) { //Z Neg
|
||||
addFaces(bm.zNegMP, offset);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,54 +2,27 @@ package client.game;
|
|||
|
||||
import helpers.RLE;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class EncodedBlockChunk {
|
||||
private byte[] blocks;
|
||||
private ArrayList<byte[]> sidesOpaque;
|
||||
|
||||
public EncodedBlockChunk(BlockChunk blockChunk) {
|
||||
this.blocks = RLE.encode(blockChunk.blocks);
|
||||
this.sidesOpaque = new ArrayList<>();
|
||||
this.sidesOpaque.add(RLE.encode(blockChunk.sidesOpaque.get(0)));
|
||||
this.sidesOpaque.add(RLE.encode(blockChunk.sidesOpaque.get(1)));
|
||||
this.sidesOpaque.add(RLE.encode(blockChunk.sidesOpaque.get(2)));
|
||||
this.sidesOpaque.add(RLE.encode(blockChunk.sidesOpaque.get(3)));
|
||||
this.sidesOpaque.add(RLE.encode(blockChunk.sidesOpaque.get(4)));
|
||||
this.sidesOpaque.add(RLE.encode(blockChunk.sidesOpaque.get(5)));
|
||||
}
|
||||
|
||||
public EncodedBlockChunk(byte[] blocks, ArrayList<byte[]> sidesOpaque) {
|
||||
public EncodedBlockChunk(byte[] blocks) {
|
||||
this.blocks = blocks;
|
||||
this.sidesOpaque = sidesOpaque;
|
||||
}
|
||||
|
||||
public EncodedBlockChunk(short[] blocks,
|
||||
boolean[] xPosOpaque, boolean[] xNegOpaque,
|
||||
boolean[] yPosOpaque, boolean[] yNegOpaque,
|
||||
boolean[] zPosOpaque, boolean[] zNegOpaque) {
|
||||
public EncodedBlockChunk(short[] blocks) {
|
||||
|
||||
this.blocks = RLE.encode(blocks);
|
||||
this.sidesOpaque = new ArrayList<>();
|
||||
this.sidesOpaque.add(RLE.encode(xPosOpaque));
|
||||
this.sidesOpaque.add(RLE.encode(xNegOpaque));
|
||||
this.sidesOpaque.add(RLE.encode(yPosOpaque));
|
||||
this.sidesOpaque.add(RLE.encode(yNegOpaque));
|
||||
this.sidesOpaque.add(RLE.encode(zPosOpaque));
|
||||
this.sidesOpaque.add(RLE.encode(zNegOpaque));
|
||||
}
|
||||
|
||||
public BlockChunk decode() {
|
||||
return new BlockChunk(blocks, sidesOpaque);
|
||||
return new BlockChunk(RLE.decodeShorts(blocks));
|
||||
}
|
||||
|
||||
public void encode(BlockChunk blockChunk) {
|
||||
this.blocks = RLE.encode(blockChunk.blocks);
|
||||
this.sidesOpaque.set(0, RLE.encode(blockChunk.sidesOpaque.get(0)));
|
||||
this.sidesOpaque.set(1, RLE.encode(blockChunk.sidesOpaque.get(1)));
|
||||
this.sidesOpaque.set(2, RLE.encode(blockChunk.sidesOpaque.get(2)));
|
||||
this.sidesOpaque.set(3, RLE.encode(blockChunk.sidesOpaque.get(3)));
|
||||
this.sidesOpaque.set(4, RLE.encode(blockChunk.sidesOpaque.get(4)));
|
||||
this.sidesOpaque.set(6, RLE.encode(blockChunk.sidesOpaque.get(5)));
|
||||
}
|
||||
}
|
|
@ -20,7 +20,8 @@ public class Game implements GameLogic {
|
|||
private Player player;
|
||||
|
||||
public static ConnMan connection;
|
||||
public static ChunkAtlas world;
|
||||
public static ChunkLoader worldbridge;
|
||||
public static World world;
|
||||
public static BlockAtlas definitions;
|
||||
public static TextureAtlas assets;
|
||||
|
||||
|
@ -36,7 +37,7 @@ public class Game implements GameLogic {
|
|||
renderer.init(window);
|
||||
|
||||
assets = new TextureAtlas(2048);
|
||||
world = new ChunkAtlas();
|
||||
world = new World();
|
||||
definitions = new BlockAtlas();
|
||||
|
||||
assets.loadTexturesFolder();
|
||||
|
@ -46,13 +47,6 @@ public class Game implements GameLogic {
|
|||
e.printStackTrace();
|
||||
}
|
||||
|
||||
connection = new ConnMan("localhost", 30005);
|
||||
|
||||
scene = new Scene();
|
||||
hud = new Hud("DEMO");
|
||||
|
||||
player = new Player(-5, 8, 6);
|
||||
|
||||
definitions.create("default:grass", new BM_CubeTexLifted("default_grass_top", "default_grass_side", "default_grass_float", "default_dirt"), true, true, true);
|
||||
definitions.create("default:dirt", new BM_CubeTexOne("default_dirt"), true, true, true);
|
||||
definitions.create("default:stone", new BM_CubeTexOne("default_stone"), true, true, true);
|
||||
|
@ -66,6 +60,15 @@ public class Game implements GameLogic {
|
|||
definitions.create("default:log", new BM_CubeTexFour("default_log_top", "default_log_side", "default_log_top"), true, true, true);
|
||||
definitions.create("default:leaves", new BM_CubeTexPoof("default_leaves", "default_leaves_puff"), false, true, true);
|
||||
|
||||
player = new Player(-5, 8, 6);
|
||||
worldbridge = new ChunkLoader(world, player);
|
||||
connection = new ConnMan("localhost", 30005, worldbridge);
|
||||
worldbridge.setConnection(connection);
|
||||
|
||||
scene = new Scene();
|
||||
hud = new Hud("DEMO");
|
||||
|
||||
|
||||
// Mesh mesh = OBJLoader.loadMesh("/models/bunny.obj");
|
||||
// mesh.setMaterial(new Material(new Vector4f(0.7f, 0.7f, 0.7f, 1.0f), 1f));
|
||||
// var COUNT = 360;
|
||||
|
@ -77,7 +80,6 @@ public class Game implements GameLogic {
|
|||
// }
|
||||
// scene.setRenderObjects(obs);
|
||||
|
||||
|
||||
scene.setVisibleChunks(world.getVisibleChunks());
|
||||
|
||||
// world.loadChunksAroundPos(player.getPosition(), 3);
|
||||
|
@ -112,7 +114,8 @@ public class Game implements GameLogic {
|
|||
|
||||
player.update(interval, mouseInput);
|
||||
|
||||
if (tick % 30 == 0) world.loadChunksAroundPos(player.getPosition(), 4);
|
||||
worldbridge.update();
|
||||
|
||||
world.update();
|
||||
|
||||
hud.rotateCompass(player.getCamera().getRotation().y);
|
||||
|
|
|
@ -9,20 +9,31 @@ import org.joml.Vector3i;
|
|||
import static helpers.ArrayTrans3D.CHUNK_SIZE;
|
||||
|
||||
public class MeshChunk {
|
||||
private World world;
|
||||
private ChunkMesh mesh;
|
||||
private RenderObj obj;
|
||||
private Vector3i pos;
|
||||
|
||||
public static Material meshMaterial;
|
||||
|
||||
public MeshChunk(Vector3i pos) {
|
||||
public MeshChunk(Vector3i pos, World world) {
|
||||
this.world = world;
|
||||
this.pos = pos;
|
||||
this.obj = new RenderObj(null);
|
||||
this.obj.setPosition(new Vector3f(pos.x * CHUNK_SIZE, pos.y * CHUNK_SIZE, pos.z * CHUNK_SIZE));
|
||||
}
|
||||
|
||||
//This method is usually called from a GenChunkTask, don't do any OGL methods here.
|
||||
public void createMesh(BlockChunk chunk) {
|
||||
var meshData = new ChunkMeshBuilder(chunk);
|
||||
BlockChunk[] adjacent = new BlockChunk[6];
|
||||
adjacent[0] = world.getChunk(new Vector3i(pos.x + 1, pos.y, pos.z));
|
||||
adjacent[1] = world.getChunk(new Vector3i(pos.x - 1, pos.y, pos.z));
|
||||
adjacent[2] = world.getChunk(new Vector3i(pos.x, pos.y + 1, pos.z));
|
||||
adjacent[3] = world.getChunk(new Vector3i(pos.x, pos.y - 1, pos.z));
|
||||
adjacent[4] = world.getChunk(new Vector3i(pos.x, pos.y, pos.z + 1));
|
||||
adjacent[5] = world.getChunk(new Vector3i(pos.x, pos.y, pos.z - 1));
|
||||
|
||||
var meshData = new ChunkMeshBuilder(chunk, adjacent);
|
||||
|
||||
if (meshData.verts.length > 0) {
|
||||
mesh = new ChunkMesh(meshData.verts, meshData.texCoords, meshData.normals, meshData.indices);
|
||||
|
|
|
@ -0,0 +1,243 @@
|
|||
package client.game;
|
||||
|
||||
import client.engine.Utils;
|
||||
import client.engine.graphics.Material;
|
||||
import client.engine.graphics.Texture;
|
||||
import helpers.ChunkSerializer;
|
||||
import org.joml.Vector3f;
|
||||
import org.joml.Vector3i;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import static helpers.ArrayTrans3D.CHUNK_SIZE;
|
||||
|
||||
public class World {
|
||||
|
||||
private ArrayList<MeshChunk> meshChunks;
|
||||
private HashMap<Vector3i, MeshChunk> meshChunkMap;
|
||||
|
||||
private ThreadPoolExecutor meshGenPool;
|
||||
private ArrayList<Future> meshGenFutures;
|
||||
private ArrayList<Vector3i> adjacentChunkUpdates;
|
||||
|
||||
private ArrayList<BlockChunk> activeChunks;
|
||||
private HashMap<Vector3i, BlockChunk> activeChunkMap;
|
||||
|
||||
|
||||
private ArrayList<EncodedBlockChunk> cachedChunks;
|
||||
private HashMap<Vector3i, EncodedBlockChunk> cachedChunkMap;
|
||||
|
||||
private int mapGenTick = 0;
|
||||
|
||||
public World() {
|
||||
|
||||
try {
|
||||
var atlas = new FileInputStream(new File("atlas_0.png"));
|
||||
MeshChunk.meshMaterial = new Material(new Texture(atlas));
|
||||
}
|
||||
catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
meshChunks = new ArrayList<>();
|
||||
meshChunkMap = new HashMap<>();
|
||||
|
||||
meshGenPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(32);
|
||||
meshGenPool.setMaximumPoolSize(64);
|
||||
meshGenPool.setKeepAliveTime(32, TimeUnit.SECONDS);
|
||||
|
||||
meshGenFutures = new ArrayList<>();
|
||||
adjacentChunkUpdates = new ArrayList<>();
|
||||
|
||||
activeChunks = new ArrayList<>();
|
||||
activeChunkMap = new HashMap<>();
|
||||
|
||||
cachedChunks = new ArrayList<>();
|
||||
cachedChunkMap = new HashMap<>();
|
||||
}
|
||||
|
||||
ArrayList<MeshChunk> getVisibleChunks() {
|
||||
return meshChunks;
|
||||
}
|
||||
|
||||
public void update() {
|
||||
try {
|
||||
loadMeshes();
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void loadMeshes() throws ExecutionException, InterruptedException {
|
||||
int maxNanos = 6_000_000;
|
||||
long start = System.nanoTime();
|
||||
|
||||
int count = meshGenFutures.size();
|
||||
int processed = 0;
|
||||
boolean hadToStop = true;
|
||||
|
||||
Iterator<Future> it = meshGenFutures.iterator();
|
||||
|
||||
while (it.hasNext() && System.nanoTime() - start < maxNanos) {
|
||||
Future f = it.next();
|
||||
if (f.isDone()) {
|
||||
processed ++;
|
||||
|
||||
GenChunkTask.ThreadRet ret = (GenChunkTask.ThreadRet) f.get();
|
||||
if (ret != null) {
|
||||
it.remove();
|
||||
|
||||
if (activeChunkMap.get(ret.position) == null) adjacentChunkUpdates.add(ret.position);
|
||||
activeChunkMap.put(ret.position, ret.blockChunk);
|
||||
|
||||
var eChunk = meshChunkMap.get(ret.position);
|
||||
if (eChunk != null) {
|
||||
meshChunkMap.remove(ret.position);
|
||||
meshChunks.remove(eChunk);
|
||||
}
|
||||
|
||||
if (ret.meshChunk != null && ret.meshChunk.getMesh() != null) {
|
||||
ret.meshChunk.getMesh().init();
|
||||
|
||||
if (ret.meshChunk.getMesh().getVertexCount() != 0) {
|
||||
meshChunkMap.put(ret.position, ret.meshChunk);
|
||||
meshChunks.add(ret.meshChunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!it.hasNext()) hadToStop = false;
|
||||
|
||||
if (count > 0) System.out.println(
|
||||
"Built " + Utils.pad(((float)processed/(float)count*100f), 8) +
|
||||
"% of " + Utils.pad(count, 6) + " chunks. " +
|
||||
"Tick: " + Utils.pad(mapGenTick++, 8) +
|
||||
(hadToStop ? ". Had to stop." : "."));
|
||||
|
||||
adjacentChunkUpdates.forEach(this::updateChunksAround);
|
||||
adjacentChunkUpdates.clear();
|
||||
}
|
||||
|
||||
private synchronized void updateChunksAround(Vector3i pos) {
|
||||
Vector3i modPos;
|
||||
|
||||
modPos = new Vector3i(pos).add(1, 0, 0);
|
||||
BlockChunk chunk = activeChunkMap.get(modPos);
|
||||
if (chunk != null) {
|
||||
updateChunk(modPos, chunk);
|
||||
}
|
||||
|
||||
modPos = new Vector3i(pos).add(-1, 0, 0);
|
||||
chunk = activeChunkMap.get(modPos);
|
||||
if (chunk != null) {
|
||||
updateChunk(modPos, chunk);
|
||||
}
|
||||
|
||||
modPos = new Vector3i(pos).add(0, 0, 1);
|
||||
chunk = activeChunkMap.get(modPos);
|
||||
if (chunk != null) {
|
||||
updateChunk(modPos, chunk);
|
||||
}
|
||||
|
||||
modPos = new Vector3i(pos).add(0, 0, -1);
|
||||
chunk = activeChunkMap.get(modPos);
|
||||
if (chunk != null) {
|
||||
updateChunk(modPos, chunk);
|
||||
}
|
||||
|
||||
modPos = new Vector3i(pos).add(0, 1, 0);
|
||||
chunk = activeChunkMap.get(modPos);
|
||||
if (chunk != null) {
|
||||
updateChunk(modPos, chunk);
|
||||
}
|
||||
|
||||
modPos = new Vector3i(pos).add(0, -1, 0);
|
||||
chunk = activeChunkMap.get(modPos);
|
||||
if (chunk != null) {
|
||||
updateChunk(modPos, chunk);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized boolean hasChunk(Vector3i pos) {
|
||||
return activeChunkMap.containsKey(pos) || cachedChunkMap.containsKey(pos);
|
||||
}
|
||||
|
||||
synchronized void addChunk(Vector3i pos, byte[] data) {
|
||||
var task = new GenChunkTask(pos, data);
|
||||
meshGenFutures.add(meshGenPool.submit(task));
|
||||
}
|
||||
|
||||
synchronized void updateChunk(Vector3i pos, BlockChunk chunk) {
|
||||
var task = new GenChunkTask(pos, chunk);
|
||||
meshGenFutures.add(meshGenPool.submit(task));
|
||||
}
|
||||
|
||||
synchronized BlockChunk getChunk(Vector3i pos) {
|
||||
return activeChunkMap.get(pos);
|
||||
}
|
||||
|
||||
public int getBlock(int x, int y, int z) {
|
||||
return getBlock(new Vector3i(x, y, z));
|
||||
}
|
||||
|
||||
public int getBlock(Vector3i pos) {
|
||||
var chunkPos = coordsToChunk(new Vector3f(pos));
|
||||
var chunk = getChunk(chunkPos);
|
||||
if (chunk == null) return -1;
|
||||
var blockPos = new Vector3i(coordToLocal(pos.x), coordToLocal(pos.y), coordToLocal(pos.z));
|
||||
return chunk.getBlock(blockPos);
|
||||
}
|
||||
|
||||
private Vector3i coordsToChunk(Vector3f pos) {
|
||||
return new Vector3i((int)Math.floor(pos.x / CHUNK_SIZE), (int)Math.floor(pos.y / CHUNK_SIZE), (int)Math.floor(pos.z / CHUNK_SIZE));
|
||||
}
|
||||
|
||||
private int coordToLocal(int num) {
|
||||
return (num >= 0) ? (num % CHUNK_SIZE) : ((CHUNK_SIZE - 1) - Math.abs(num + 1) % CHUNK_SIZE);
|
||||
}
|
||||
|
||||
private class GenChunkTask implements Callable<GenChunkTask.ThreadRet> {
|
||||
Vector3i pos;
|
||||
byte[] chunkData;
|
||||
BlockChunk chunk;
|
||||
|
||||
GenChunkTask(Vector3i pos, byte[] chunkData) {
|
||||
this.pos = pos;
|
||||
this.chunkData = chunkData;
|
||||
}
|
||||
|
||||
GenChunkTask(Vector3i pos, BlockChunk chunk) {
|
||||
this.pos = pos;
|
||||
this.chunk = chunk;
|
||||
}
|
||||
|
||||
private class ThreadRet {
|
||||
BlockChunk blockChunk;
|
||||
MeshChunk meshChunk;
|
||||
Vector3i position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreadRet call() {
|
||||
if (chunk == null) chunk = ChunkSerializer.decodeChunk(chunkData);
|
||||
|
||||
MeshChunk meshChunk = new MeshChunk(pos, Game.world);
|
||||
meshChunk.createMesh(chunk);
|
||||
|
||||
ThreadRet r = new ThreadRet();
|
||||
r.blockChunk = chunk;
|
||||
r.meshChunk = meshChunk;
|
||||
r.position = pos;
|
||||
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
public void cleanup() {
|
||||
meshGenPool.shutdown();
|
||||
}
|
||||
}
|
|
@ -54,6 +54,6 @@ public class BM_PlantLike extends BlockModel {
|
|||
1.0f, 1.0f,
|
||||
1.0f, 0.0f,
|
||||
},
|
||||
texPos)};
|
||||
texPos, MeshMod.SHIFT, 0.15f)};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
package client.game.blockmodels;
|
||||
|
||||
public class MeshInfo {
|
||||
public float[] positions;
|
||||
public float[] texData;
|
||||
public float[] normals;
|
||||
public int[] indices;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package client.game.blockmodels;
|
||||
|
||||
public enum MeshMod {
|
||||
NONE,
|
||||
SHIFT
|
||||
}
|
|
@ -5,12 +5,22 @@ import org.joml.Vector3f;
|
|||
import org.joml.Vector4f;
|
||||
|
||||
public class MeshPart {
|
||||
public float[] positions;
|
||||
public float[] texData;
|
||||
public float[] normals;
|
||||
public int[] indices;
|
||||
private float[] positions;
|
||||
private float[] texData;
|
||||
private float[] normals;
|
||||
private int[] indices;
|
||||
|
||||
private MeshMod meshMod;
|
||||
private float modVal;
|
||||
|
||||
public MeshPart(float[] positions, int[] indices, float[] texCoords, String texName) {
|
||||
this(positions, indices, texCoords, texName, MeshMod.NONE, 0);
|
||||
}
|
||||
|
||||
public MeshPart(float[] positions, int[] indices, float[] texCoords, String texName, MeshMod meshMod, float modVal) {
|
||||
this.meshMod = meshMod;
|
||||
this.modVal = modVal;
|
||||
|
||||
this.positions = positions;
|
||||
this.indices = indices;
|
||||
|
||||
|
@ -56,13 +66,6 @@ public class MeshPart {
|
|||
|
||||
texData = new float[texCoords.length];
|
||||
|
||||
// for (var i = 0; i < texCoords.length/2; i++) {
|
||||
// texData[i*4 ] = (float)(tex.x) / 128;
|
||||
// texData[i*4+1] = (float)(tex.y) / 128;
|
||||
// texData[i*4+2] = texCoords[i*2];
|
||||
// texData[i*4+3] = texCoords[i*2+1];
|
||||
// }
|
||||
|
||||
Vector4f texUVs = Game.assets.getTexUV(texName);
|
||||
|
||||
for (var i = 0; i < texCoords.length/2; i++) {
|
||||
|
@ -70,4 +73,25 @@ public class MeshPart {
|
|||
texData[i*2+1] = texUVs.y + (texUVs.w - texUVs.y) * texCoords[i*2+1];
|
||||
}
|
||||
}
|
||||
|
||||
public MeshInfo getMeshData(MeshInfo m) {
|
||||
m.indices = indices;
|
||||
m.normals = normals;
|
||||
m.texData = texData;
|
||||
|
||||
if (meshMod == MeshMod.NONE) m.positions = positions;
|
||||
else if (meshMod == MeshMod.SHIFT) {
|
||||
float shift_x = -modVal + (float)Math.random() * modVal * 2f;
|
||||
float shift_z = -modVal + (float)Math.random() * modVal * 2f;
|
||||
|
||||
m.positions = new float[positions.length];
|
||||
for (int i = 0; i < positions.length/3; i++) {
|
||||
m.positions[i*3] = positions[i*3] + shift_x;
|
||||
m.positions[i*3+1] = positions[i*3+1];
|
||||
m.positions[i*3+2] = positions[i*3+2] + shift_z;
|
||||
}
|
||||
}
|
||||
|
||||
return m;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
package client.game.network;
|
||||
|
||||
import client.game.ChunkLoader;
|
||||
import helpers.VecUtils;
|
||||
import org.joml.Vector3i;
|
||||
import helpers.PacketType;
|
||||
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class ConnMan {
|
||||
private Pacman pacman;
|
||||
private HashMap<Vector3i, BiConsumer<Vector3i, byte[]>> pendingChunkCallbacks;
|
||||
private ChunkLoader worldbridge;
|
||||
|
||||
public ConnMan(String host, int port) throws Exception {
|
||||
pendingChunkCallbacks = new HashMap<>();
|
||||
public ConnMan(String host, int port, ChunkLoader worldbridge) throws Exception {
|
||||
this.worldbridge = worldbridge;
|
||||
pacman = new Pacman(new Socket(host, port));
|
||||
pacman.start();
|
||||
}
|
||||
|
@ -28,37 +29,35 @@ public class ConnMan {
|
|||
handleReceivedChunk(p);
|
||||
break;
|
||||
default:
|
||||
System.out.println("Recieved packet of type " + p.type + "and we're not prepared to deal with it!");
|
||||
System.out.println("Recieved packet of type " + p.type + " and we're not prepared to deal with it!");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleReceivedChunk(PacketData p) {
|
||||
|
||||
var str = new String(p.data, StandardCharsets.ISO_8859_1);
|
||||
var index = str.indexOf("|");
|
||||
var pos = VecUtils.stringToVector(str.substring(0, index));
|
||||
if (pos == null) return;
|
||||
var cons = pendingChunkCallbacks.get(pos);
|
||||
if (cons == null) return;
|
||||
|
||||
cons.accept(pos, str.substring(index + 1).getBytes(StandardCharsets.ISO_8859_1));
|
||||
worldbridge.newChunk(pos, str.substring(index + 1).getBytes(StandardCharsets.ISO_8859_1));
|
||||
}
|
||||
|
||||
// public void requestChunk(Vector3i pos, BiConsumer<Vector3i, byte[]> consumer) {
|
||||
// requestChunk(pos.x, pos.y, pos.z, consumer);
|
||||
// }
|
||||
|
||||
// public void requestChunk(int x, int y, int z, BiConsumer<Vector3i, byte[]> consumer) {
|
||||
// var pos = new Vector3i(x, y, z);
|
||||
// pacman.sendPacket(PacketType.REQUEST_CHUNK, VecUtils.vectorToString(pos));
|
||||
// }
|
||||
|
||||
public void requestChunk(Vector3i pos, BiConsumer<Vector3i, byte[]> consumer) {
|
||||
requestChunk(pos.x, pos.y, pos.z, consumer);
|
||||
}
|
||||
|
||||
public void requestChunk(int x, int y, int z, BiConsumer<Vector3i, byte[]> consumer) {
|
||||
var pos = new Vector3i(x, y, z);
|
||||
if (pendingChunkCallbacks.containsKey(pos)) return;
|
||||
pendingChunkCallbacks.put(pos, consumer);
|
||||
pacman.sendPacket(PacketType.REQUEST_CHUNK, VecUtils.vectorToString(pos));
|
||||
public void sendPosition(Vector3i pos) {
|
||||
pacman.sendPacket(PacketType.PLAYER_POSITION, VecUtils.vectorToString(pos));
|
||||
}
|
||||
|
||||
public void kill() {
|
||||
pacman.kill();
|
||||
pendingChunkCallbacks.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package client.game.network;
|
||||
|
||||
import helpers.PacketType;
|
||||
|
||||
public class PacketData {
|
||||
public long time;
|
||||
public PacketType type;
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
package client.game.network;
|
||||
|
||||
public enum PacketType {
|
||||
DEBUG,
|
||||
REQUEST_CHUNK,
|
||||
BLOCK_CHUNK,
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package client.game.network;
|
||||
|
||||
import helpers.PacketType;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.Socket;
|
||||
import java.util.ArrayList;
|
||||
|
|
|
@ -14,7 +14,7 @@ import static helpers.Bytes.*;
|
|||
|
||||
public class ChunkSerializer {
|
||||
|
||||
public static byte[] encodeChunk(short[] chunk, ArrayList<short[]> sides) {
|
||||
public static byte[] encodeChunk(short[] chunk) {
|
||||
ArrayList<Byte> list = new ArrayList<>();
|
||||
byte[] rleArray = RLE.encode(chunk);
|
||||
byte[] size = intToBytes(rleArray.length);
|
||||
|
@ -26,9 +26,9 @@ public class ChunkSerializer {
|
|||
ByteArrayInputStream rleStream = new ByteArrayInputStream(rleArray);
|
||||
while (rleStream.available() > 0) list.add((byte)rleStream.read());
|
||||
|
||||
for (short[] shorts : sides) {
|
||||
addAdjacent(list, shorts);
|
||||
}
|
||||
// for (short[] shorts : sides) {
|
||||
// addAdjacent(list, shorts);
|
||||
// }
|
||||
|
||||
byte[] array = new byte[list.size()];
|
||||
for (var i = 0; i < list.size(); i++) {
|
||||
|
@ -60,17 +60,17 @@ public class ChunkSerializer {
|
|||
return compressed;
|
||||
}
|
||||
|
||||
private static void addAdjacent(ArrayList<Byte> list, short[] adj) {
|
||||
byte[] rleArray = RLE.encode(adj);
|
||||
byte[] size = intToBytes(rleArray.length);
|
||||
list.add(size[0]);
|
||||
list.add(size[1]);
|
||||
list.add(size[2]);
|
||||
list.add(size[3]);
|
||||
|
||||
ByteArrayInputStream rleStream = new ByteArrayInputStream(rleArray);
|
||||
while (rleStream.available() > 0) list.add((byte)rleStream.read());
|
||||
}
|
||||
// private static void addAdjacent(ArrayList<Byte> list, short[] adj) {
|
||||
// byte[] rleArray = RLE.encode(adj);
|
||||
// byte[] size = intToBytes(rleArray.length);
|
||||
// list.add(size[0]);
|
||||
// list.add(size[1]);
|
||||
// list.add(size[2]);
|
||||
// list.add(size[3]);
|
||||
//
|
||||
// ByteArrayInputStream rleStream = new ByteArrayInputStream(rleArray);
|
||||
// while (rleStream.available() > 0) list.add((byte)rleStream.read());
|
||||
// }
|
||||
|
||||
public static BlockChunk decodeChunk(byte[] encoded) {
|
||||
byte[] decoded;
|
||||
|
@ -97,23 +97,19 @@ public class ChunkSerializer {
|
|||
bis.read(mainChunkRLE, 0, size);
|
||||
short[] chunk = RLE.decodeShorts(mainChunkRLE);
|
||||
|
||||
ArrayList<boolean[]> sides = new ArrayList<>();
|
||||
// ArrayList<short[]> sides = new ArrayList<>();
|
||||
//
|
||||
// for (var i = 0; i < 6; i++) {
|
||||
// buff = new byte[4];
|
||||
// bis.read(buff, 0, 4);
|
||||
// var size2 = bytesToInt(buff);
|
||||
//
|
||||
// byte[] adjRLE = new byte[size2];
|
||||
// bis.read(adjRLE, 0, size2);
|
||||
// var shorts = RLE.decodeShorts(adjRLE);
|
||||
// sides.add(shorts);
|
||||
// }
|
||||
|
||||
for (var i = 0; i < 6; i++) {
|
||||
buff = new byte[4];
|
||||
bis.read(buff, 0, 4);
|
||||
var size2 = bytesToInt(buff);
|
||||
|
||||
byte[] adjRLE = new byte[size2];
|
||||
bis.read(adjRLE, 0, size2);
|
||||
var shorts = RLE.decodeShorts(adjRLE);
|
||||
var bools = new boolean[shorts.length];
|
||||
for (var j = 0; j < shorts.length; j++) {
|
||||
bools[j] = Game.definitions.getDef(shorts[j]).getCulls();
|
||||
}
|
||||
sides.add(bools);
|
||||
}
|
||||
|
||||
return new BlockChunk(chunk, sides);
|
||||
return new BlockChunk(chunk);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,6 @@ package helpers;
|
|||
|
||||
public enum PacketType {
|
||||
DEBUG,
|
||||
REQUEST_CHUNK,
|
||||
BLOCK_CHUNK,
|
||||
PLAYER_POSITION,
|
||||
}
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
package server.api;
|
||||
|
||||
import org.joml.Vector3i;
|
||||
import server.server.world.Chunk;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public interface IBlockDecors {
|
||||
int getDecorBlock(int x, int z, int depth);
|
||||
void genStructs(Vector3i globalPos, Vector3i chunkPos, HashMap<Vector3i, Chunk> chunks, int depth);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
package server.baseApi;
|
||||
|
||||
import helpers.ArrayTrans3D;
|
||||
import helpers.OpenSimplexNoise;
|
||||
import org.joml.Vector3i;
|
||||
import server.api.IBlockDecors;
|
||||
import server.server.world.Chunk;
|
||||
import server.server.world.World;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import static helpers.ArrayTrans3D.CHUNK_SIZE;
|
||||
|
||||
public class BaseDecorations implements IBlockDecors {
|
||||
// private long seed;
|
||||
// private World world;
|
||||
|
||||
private static final float GRASS_PRECISION = 10f;
|
||||
private static final float TREE_PRECISION = 0.5f;
|
||||
|
||||
private OpenSimplexNoise decorNoise;
|
||||
private OpenSimplexNoise treeNoise;
|
||||
|
||||
public BaseDecorations(long seed, World world) {
|
||||
// this.seed = seed;
|
||||
// this.world = world;
|
||||
|
||||
decorNoise = new OpenSimplexNoise(seed/2);
|
||||
treeNoise = new OpenSimplexNoise(seed/2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDecorBlock(int x, int z, int depth) {
|
||||
if (depth != -1) return -1;
|
||||
|
||||
int grass = (int)Math.round(Math.min(4, Math.max(-1, decorNoise.eval(x / GRASS_PRECISION, z / GRASS_PRECISION) * 5 + Math.random() * 2)));
|
||||
if (grass >= 0) {
|
||||
return (short)(5 + grass);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void genStructs(Vector3i globalPos, Vector3i chunkPos, HashMap<Vector3i, Chunk> chunks, int depth) {
|
||||
if (depth == -1) genTree(globalPos, chunks);
|
||||
}
|
||||
|
||||
private void genTree(Vector3i pos, HashMap<Vector3i, Chunk> chunks) {
|
||||
if (treeAtPosition(pos.x, pos.z)) {
|
||||
|
||||
for (var i = 0; i < 10; i++) {
|
||||
placeBlock(new Vector3i(pos.x, pos.y + i, pos.z), chunks, 12);
|
||||
}
|
||||
placeBlock(new Vector3i(pos.x + 1, pos.y, pos.z), chunks, 12);
|
||||
placeBlock(new Vector3i(pos.x - 1, pos.y, pos.z), chunks, 12);
|
||||
placeBlock(new Vector3i(pos.x, pos.y, pos.z + 1), chunks, 12);
|
||||
placeBlock(new Vector3i(pos.x, pos.y, pos.z - 1), chunks, 12);
|
||||
placeBlock(new Vector3i(pos.x - 1, pos.y + 1, pos.z), chunks, 12);
|
||||
placeBlock(new Vector3i(pos.x, pos.y + 1, pos.z + 1), chunks, 12);
|
||||
|
||||
for (var i = -3; i < 3; i++) {
|
||||
for (var j = -3; j < 3; j++) {
|
||||
placeBlock(new Vector3i(pos.x + i, pos.y + 10, pos.z + j), chunks, 13);
|
||||
placeBlock(new Vector3i(pos.x + i, pos.y + 12, pos.z + j), chunks, 13);
|
||||
}
|
||||
}
|
||||
for (var i = -4; i < 4; i++) {
|
||||
for (var j = -4; j < 4; j++) {
|
||||
placeBlock(new Vector3i(pos.x + i, pos.y + 11, pos.z + j), chunks, 13);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void placeBlock(Vector3i pos, HashMap<Vector3i, Chunk> chunks, int block) {
|
||||
placeBlock(pos, chunks, (short)block); //Just to make things easier
|
||||
}
|
||||
|
||||
private void placeBlock(Vector3i pos, HashMap<Vector3i, Chunk> chunks, short block) {
|
||||
Vector3i chunkPos = globalPosToChunk(pos);
|
||||
Chunk chunk = chunks.get(chunkPos);
|
||||
if (chunk == null) {
|
||||
chunk = new Chunk(new short[4096], false, chunkPos);
|
||||
chunks.put(chunkPos, chunk);
|
||||
}
|
||||
|
||||
ArrayTrans3D.set(chunk.blocks, block, globalPosToLocal(pos));
|
||||
}
|
||||
|
||||
private boolean treeAtPosition(int x, int z) {
|
||||
return treeNoise.eval(x / TREE_PRECISION, z / TREE_PRECISION) > 0.75f;
|
||||
}
|
||||
|
||||
private Vector3i globalPosToChunk(Vector3i pos) {
|
||||
return new Vector3i((int)Math.floor((float)pos.x / CHUNK_SIZE), (int)Math.floor((float)pos.y / CHUNK_SIZE), (int)Math.floor((float)pos.z / CHUNK_SIZE));
|
||||
}
|
||||
|
||||
private Vector3i globalPosToLocal(Vector3i pos) {
|
||||
Vector3i returnPos = new Vector3i();
|
||||
returnPos.x = (pos.x >= 0) ? (pos.x % CHUNK_SIZE) : ((CHUNK_SIZE - 1) - Math.abs(pos.x + 1) % CHUNK_SIZE);
|
||||
returnPos.y = (pos.y >= 0) ? (pos.y % CHUNK_SIZE) : ((CHUNK_SIZE - 1) - Math.abs(pos.y + 1) % CHUNK_SIZE);
|
||||
returnPos.z = (pos.z >= 0) ? (pos.z % CHUNK_SIZE) : ((CHUNK_SIZE - 1) - Math.abs(pos.z + 1) % CHUNK_SIZE);
|
||||
return returnPos;
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ package server.baseApi;
|
|||
import server.api.IMapHeightmap;
|
||||
import helpers.OpenSimplexNoise;
|
||||
|
||||
import static server.server.MapGen.CHUNK_SIZE;
|
||||
import static helpers.ArrayTrans3D.CHUNK_SIZE;
|
||||
|
||||
public class BaseHeightmap implements IMapHeightmap {
|
||||
long seed;
|
||||
|
|
|
@ -4,42 +4,40 @@ import helpers.ChunkSerializer;
|
|||
import helpers.PacketData;
|
||||
import helpers.PacketType;
|
||||
import helpers.VecUtils;
|
||||
import server.baseApi.BaseHeightmap;
|
||||
import server.server.MapGen;
|
||||
import org.joml.Vector3i;
|
||||
import server.server.world.Chunk;
|
||||
import server.server.world.World;
|
||||
|
||||
import java.net.Socket;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
public class ClientThread extends Thread implements Runnable {
|
||||
private Socket socket;
|
||||
private Pacman pacman;
|
||||
private boolean alive;
|
||||
private MapGen mapGen;
|
||||
private World world;
|
||||
|
||||
private ThreadPoolExecutor mapGenPool;
|
||||
private Vector3i position;
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
private final int RANGE = 12;
|
||||
|
||||
public ClientThread(Socket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
// long seed = Math.round(Math.random()*1000000);
|
||||
long seed = 0;
|
||||
pacman = new Pacman(socket);
|
||||
world = new World();
|
||||
|
||||
this.pacman = new Pacman(socket);
|
||||
mapGen = new MapGen(seed, new BaseHeightmap(seed));
|
||||
position = null;
|
||||
|
||||
pacman.start();
|
||||
alive = true;
|
||||
|
||||
mapGenPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(32);
|
||||
mapGenPool.setMaximumPoolSize(96);
|
||||
mapGenPool.setKeepAliveTime(32, TimeUnit.SECONDS);
|
||||
updatePlayer(position, new Vector3i(0,0,0));
|
||||
}
|
||||
|
||||
private void update() {
|
||||
|
@ -48,83 +46,78 @@ public class ClientThread extends Thread implements Runnable {
|
|||
case DEBUG:
|
||||
System.out.println(new String(in.data, StandardCharsets.ISO_8859_1));
|
||||
break;
|
||||
case REQUEST_CHUNK:
|
||||
deferredRenderChunk(in);
|
||||
case PLAYER_POSITION:
|
||||
playerPositionPacket(in);
|
||||
break;
|
||||
default:
|
||||
System.out.println("Recieved packet of type " + in.type + "and we can't to deal with it!");
|
||||
System.out.println("Recieved packet of type " + in.type + " and we're not prepared to deal with it!");
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
world.update();
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
// pacman.sendPacket(PacketType.DEBUG, "server to client: Hi! Time is " + System.currentTimeMillis());
|
||||
}
|
||||
|
||||
private void deferredRenderChunk(PacketData in) {
|
||||
mapGenPool.submit(() -> {
|
||||
Vector3i position = VecUtils.stringToVector(new String(in.data, StandardCharsets.ISO_8859_1));
|
||||
if (position == null) return;
|
||||
private void playerPositionPacket(PacketData in) {
|
||||
Vector3i pos = VecUtils.stringToVector(new String(in.data, StandardCharsets.ISO_8859_1));
|
||||
if (pos == null) return;
|
||||
|
||||
Vector3i chunkPos = new Vector3i(Math.round((float)pos.x / 16), Math.round((float)pos.y / 16), Math.round((float)pos.z / 16));
|
||||
|
||||
if (chunkPos != position) {
|
||||
updatePlayer(position, chunkPos);
|
||||
position = chunkPos;
|
||||
}
|
||||
}
|
||||
|
||||
private void updatePlayer(Vector3i oldPosition, Vector3i newPosition) {
|
||||
ArrayList<Vector3i> positions = getChunksInRange(newPosition, RANGE);
|
||||
|
||||
if (oldPosition != null) {
|
||||
ArrayList<Vector3i> oldChunks = getChunksInRange(oldPosition, RANGE);
|
||||
positions.removeAll(oldChunks);
|
||||
}
|
||||
|
||||
if (positions.size() > 0) {
|
||||
world.getChunks(positions, this::sendChunkArray);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendChunkArray(Chunk[] chunks) {
|
||||
System.out.println(System.currentTimeMillis() + " | Job done!");
|
||||
|
||||
for (Chunk c : chunks) {
|
||||
StringBuilder s = new StringBuilder();
|
||||
s.append(VecUtils.vectorToString(position));
|
||||
s.append(VecUtils.vectorToString(c.getPos()));
|
||||
s.append("|");
|
||||
|
||||
System.out.println("Generating chunk at position " + position);
|
||||
|
||||
var bytes = ChunkSerializer.encodeChunk(mapGen.generateChunk(position), generateSides(position));
|
||||
var bytes = ChunkSerializer.encodeChunk(c.getBlockArray());
|
||||
if (bytes == null) return;
|
||||
|
||||
s.append(new String(bytes, StandardCharsets.ISO_8859_1));
|
||||
|
||||
pacman.sendPacket(PacketType.BLOCK_CHUNK, s.toString());
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private ArrayList<short[]> generateSides(Vector3i pos) {
|
||||
ArrayList<short[]> sides = new ArrayList<>();
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private ArrayList<Vector3i> getChunksInRange(Vector3i chunkPos, int range) {
|
||||
ArrayList<Vector3i> chunks = new ArrayList<>();
|
||||
|
||||
var array = new short[256];
|
||||
sides.add(array);
|
||||
sides.add(array);
|
||||
sides.add(array);
|
||||
sides.add(array);
|
||||
sides.add(array);
|
||||
sides.add(array);
|
||||
for (var i = -range; i < range; i++) {
|
||||
for (var j = -range; j < range; j++) {
|
||||
for (var k = -range; k < range; k++) {
|
||||
chunks.add(new Vector3i(chunkPos.x + i, chunkPos.y + j, chunkPos.z + k));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// var array = new short[256];
|
||||
// for (var i = 0; i < 256; i++) {
|
||||
// array[i] = mapGen.getBlock(pos.x*CHUNK_SIZE + 16, pos.y*CHUNK_SIZE + i/16, pos.z*CHUNK_SIZE + i%16);
|
||||
// }
|
||||
// sides.add(array);
|
||||
//
|
||||
// array = new short[256];
|
||||
// for (var i = 0; i < 256; i++) {
|
||||
// array[i] = mapGen.getBlock(pos.x*CHUNK_SIZE - 1, pos.y*CHUNK_SIZE + i/16, pos.z*CHUNK_SIZE + i%16);
|
||||
// }
|
||||
// sides.add(array);
|
||||
//
|
||||
// array = new short[256];
|
||||
// for (var i = 0; i < 256; i++) {
|
||||
// array[i] = mapGen.getBlock(pos.x*CHUNK_SIZE + i/16, pos.y*CHUNK_SIZE + 16, pos.z*CHUNK_SIZE + i%16);
|
||||
// }
|
||||
// sides.add(array);
|
||||
//
|
||||
// array = new short[256];
|
||||
// for (var i = 0; i < 256; i++) {
|
||||
// array[i] = mapGen.getBlock(pos.x*CHUNK_SIZE + i/16, pos.y*CHUNK_SIZE - 1, pos.z*CHUNK_SIZE + i%16);
|
||||
// }
|
||||
// sides.add(array);
|
||||
//
|
||||
// array = new short[256];
|
||||
// for (var i = 0; i < 256; i++) {
|
||||
// array[i] = mapGen.getBlock(pos.x*CHUNK_SIZE + i%16, pos.y*CHUNK_SIZE + i/16, pos.z*CHUNK_SIZE + 16);
|
||||
// }
|
||||
// sides.add(array);
|
||||
//
|
||||
// array = new short[256];
|
||||
// for (var i = 0; i < 256; i++) {
|
||||
// array[i] = mapGen.getBlock(pos.x*CHUNK_SIZE + i%16, pos.y*CHUNK_SIZE + i/16, pos.z*CHUNK_SIZE - 1);
|
||||
// }
|
||||
// sides.add(array);
|
||||
|
||||
return sides;
|
||||
return chunks;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
package server.server;
|
||||
|
||||
import server.api.IBlockDecors;
|
||||
import server.api.IMapHeightmap;
|
||||
import helpers.ArrayTrans3D;
|
||||
import helpers.OpenSimplexNoise;
|
||||
import org.joml.Vector3i;
|
||||
|
||||
public class MapGen {
|
||||
public static final int CHUNK_SIZE = 16;
|
||||
private long seed;
|
||||
|
||||
private IMapHeightmap heightmap;
|
||||
private IBlockDecors blockdecors;
|
||||
|
||||
private OpenSimplexNoise grassNoise;
|
||||
private OpenSimplexNoise treeNoise;
|
||||
|
||||
public MapGen(long seed, IMapHeightmap heightmap) {
|
||||
|
||||
this.heightmap = heightmap;
|
||||
// this.blockdecors = blockdecors;
|
||||
|
||||
grassNoise = new OpenSimplexNoise(seed);
|
||||
treeNoise = new OpenSimplexNoise(seed/2);
|
||||
}
|
||||
|
||||
public short[] generateChunk(Vector3i pos) {
|
||||
var chunk = new short[4096];
|
||||
|
||||
int[][] heightMap = heightmap.getChunkHeightmap(pos.x, pos.z);
|
||||
|
||||
for (int i = 0; i < 4096; i++) {
|
||||
Vector3i iPos = ArrayTrans3D.indToVec(i);
|
||||
int depth = heightMap[iPos.x][iPos.z] - pos.y * CHUNK_SIZE - iPos.y;
|
||||
|
||||
chunk[i] = getBlockFromDepth(depth);
|
||||
|
||||
if (depth < 0) {
|
||||
chunk[i] = getBlockDecoration(depth, pos.x * CHUNK_SIZE + iPos.x, pos.z * CHUNK_SIZE + iPos.z);
|
||||
}
|
||||
|
||||
if (treeAtPosition(pos.x * CHUNK_SIZE + iPos.x, pos.z * CHUNK_SIZE + iPos.z)) {
|
||||
generateTree(chunk, iPos.x, iPos.y, iPos.z);
|
||||
}
|
||||
}
|
||||
|
||||
return chunk;
|
||||
}
|
||||
|
||||
private void tryPlaceBlock(short[] blocks, short block, int x, int y, int z) {
|
||||
if (x < 0 || x > 15 || y < 0 || y > 15 || z < 0 || z > 15) return;
|
||||
|
||||
ArrayTrans3D.set(blocks, block, x, y, z);
|
||||
}
|
||||
|
||||
private void generateTree(short[] blocks, int x, int y, int z) {
|
||||
for (var i = 0; i < 10; i++) {
|
||||
tryPlaceBlock(blocks, (short)11, x, y + i, z);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean treeAtPosition(int x, int z) {
|
||||
float TREE_PRECISION = 0.5f;
|
||||
return treeNoise.eval(x / TREE_PRECISION, z / TREE_PRECISION) > 0.75f;
|
||||
}
|
||||
|
||||
private short getBlockDecoration(int depth, int x, int z) {
|
||||
float GRASS_PRECISION = 10f;
|
||||
|
||||
if (depth == -1) {
|
||||
int grass = (int)Math.round(Math.min(4, Math.max(-1, grassNoise.eval(x / GRASS_PRECISION, z / GRASS_PRECISION) * 5 + Math.random() * 2)));
|
||||
if (grass >= 0) {
|
||||
return (short)(4 + grass);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private short getBlockFromDepth(int depth) {
|
||||
if (depth > 2) return 3;
|
||||
if (depth > 0) return 2;
|
||||
if (depth == 0) return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package server.server.world;
|
||||
|
||||
import helpers.ArrayTrans3D;
|
||||
import helpers.RLE;
|
||||
import org.joml.Vector3i;
|
||||
|
||||
public class Chunk {
|
||||
public short[] blocks;
|
||||
boolean generated;
|
||||
Vector3i pos;
|
||||
|
||||
public Chunk(byte[] blocks, Vector3i pos) {
|
||||
this(blocks, false, pos);
|
||||
}
|
||||
|
||||
public Chunk(short[] blocks, Vector3i pos) {
|
||||
this(blocks, false, pos);
|
||||
}
|
||||
|
||||
public Chunk(byte[] blocks, boolean generated, Vector3i pos) {
|
||||
this(RLE.decodeShorts(blocks), generated, pos);
|
||||
}
|
||||
|
||||
public Chunk(short[] blocks, boolean generated, Vector3i pos) {
|
||||
this.pos = pos;
|
||||
this.blocks = blocks;
|
||||
this.generated = generated;
|
||||
}
|
||||
|
||||
public short getBlock(Vector3i pos) {
|
||||
return getBlock(pos.x, pos.y, pos.z);
|
||||
}
|
||||
|
||||
public short getBlock(int x, int y, int z) {
|
||||
return ArrayTrans3D.get(this.blocks, x, y, z);
|
||||
}
|
||||
|
||||
public short[] getBlockArray() {
|
||||
return blocks;
|
||||
}
|
||||
|
||||
public void setBlockArray(short[] blocks) {
|
||||
this.blocks = blocks;
|
||||
}
|
||||
|
||||
public void setGenerated(boolean generated) {
|
||||
this.generated = generated;
|
||||
}
|
||||
|
||||
public Vector3i getPos() {
|
||||
return pos;
|
||||
}
|
||||
|
||||
public Chunk mergeChunk(Chunk newChunk) throws Exception {
|
||||
if (generated) {
|
||||
if (newChunk.generated) {
|
||||
throw new Exception("Two Existing Chunks should never be merged!");
|
||||
}
|
||||
else {
|
||||
for (var i = 0; i < newChunk.blocks.length; i++) {
|
||||
if (newChunk.blocks[i] != 0) blocks[i] = newChunk.blocks[i];
|
||||
generated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (newChunk.generated) {
|
||||
for (var i = 0; i < newChunk.blocks.length; i++) {
|
||||
if (blocks[i] == 0) blocks[i] = newChunk.blocks[i];
|
||||
generated = true;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (var i = 0; i < newChunk.blocks.length; i++) {
|
||||
if (newChunk.blocks[i] != 0) blocks[i] = newChunk.blocks[i];
|
||||
generated = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package server.server.world;
|
||||
|
||||
import helpers.RLE;
|
||||
import org.joml.Vector3i;
|
||||
|
||||
public class EncodedChunk {
|
||||
private byte[] blocks;
|
||||
private boolean generated = false;
|
||||
private Vector3i pos;
|
||||
|
||||
public EncodedChunk(Chunk blockChunk) {
|
||||
this.blocks = RLE.encode(blockChunk.blocks);
|
||||
}
|
||||
|
||||
public EncodedChunk(byte[] blocks) {
|
||||
this.blocks = blocks;
|
||||
}
|
||||
|
||||
public EncodedChunk(short[] blocks) {
|
||||
this.blocks = RLE.encode(blocks);
|
||||
}
|
||||
|
||||
public Chunk decode() {
|
||||
return new Chunk(blocks, generated, pos);
|
||||
}
|
||||
|
||||
public void encode(Chunk chunk) {
|
||||
this.blocks = RLE.encode(chunk.blocks);
|
||||
this.generated = chunk.generated;
|
||||
this.pos = chunk.getPos();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package server.server.world;
|
||||
|
||||
import org.joml.Vector3i;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
public class GenChunkTask implements Callable<HashMap<Vector3i, Chunk>> {
|
||||
private Vector3i position;
|
||||
private MapGen gen;
|
||||
|
||||
public GenChunkTask(Vector3i position, MapGen gen) {
|
||||
this.position = position;
|
||||
this.gen = gen;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public HashMap<Vector3i, Chunk> call() throws Exception {
|
||||
|
||||
HashMap<Vector3i, Chunk> chunksMade = new HashMap<>();
|
||||
|
||||
gen.generate(position, chunksMade);
|
||||
|
||||
return chunksMade;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package server.server.world;
|
||||
|
||||
import org.joml.Vector3i;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class GenJob {
|
||||
private ArrayList<Vector3i> pending;
|
||||
public ArrayList<Vector3i> positions;
|
||||
public Consumer<Chunk[]> callback;
|
||||
|
||||
public GenJob(ArrayList<Vector3i> positions, Consumer<Chunk[]> callback) {
|
||||
this.positions = positions;
|
||||
this.pending = new ArrayList<>();
|
||||
pending.addAll(positions);
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
//Boolean == ready
|
||||
public boolean acceptChunk(Vector3i pos) {
|
||||
pending.remove(pos);
|
||||
return pending.size() == 0;
|
||||
}
|
||||
|
||||
public boolean isReady() {
|
||||
return pending.size() == 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package server.server.world;
|
||||
|
||||
import server.api.IBlockDecors;
|
||||
import server.api.IMapHeightmap;
|
||||
import helpers.ArrayTrans3D;
|
||||
import org.joml.Vector3i;
|
||||
import server.baseApi.BaseDecorations;
|
||||
import server.baseApi.BaseHeightmap;
|
||||
import server.server.world.Chunk;
|
||||
import server.server.world.World;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import static helpers.ArrayTrans3D.CHUNK_SIZE;
|
||||
|
||||
public class MapGen {
|
||||
private long seed;
|
||||
|
||||
private IMapHeightmap heightmap;
|
||||
private IBlockDecors blockdecors;
|
||||
|
||||
public MapGen(World world) {
|
||||
seed = 0;
|
||||
this.heightmap = new BaseHeightmap(seed);
|
||||
this.blockdecors = new BaseDecorations(seed, world);
|
||||
}
|
||||
|
||||
//Modifies Chunks so doesn't need to return a value
|
||||
public void generate(Vector3i chunkPos, HashMap<Vector3i, Chunk> chunks) {
|
||||
Chunk c = new Chunk(new short[4096], true, chunkPos);
|
||||
chunks.put(chunkPos, c);
|
||||
|
||||
Vector3i globalPos = new Vector3i(chunkPos);
|
||||
|
||||
int[][] heightMap = heightmap.getChunkHeightmap(chunkPos.x, chunkPos.z);
|
||||
|
||||
for (int i = 0; i < 4096; i++) {
|
||||
if (c.blocks[i] == 0) {
|
||||
Vector3i localPos = ArrayTrans3D.indToVec(i);
|
||||
|
||||
int depth = heightMap[localPos.x][localPos.z] - chunkPos.y * CHUNK_SIZE - localPos.y;
|
||||
|
||||
c.blocks[i] = getBlockFromDepth(depth);
|
||||
|
||||
globalPos.set(chunkPos.x * CHUNK_SIZE + localPos.x, chunkPos.y * CHUNK_SIZE + localPos.y, chunkPos.z * CHUNK_SIZE + localPos.z);
|
||||
|
||||
int decor = blockdecors.getDecorBlock(globalPos.x, globalPos.z, depth);
|
||||
if (decor != -1) c.blocks[i] = (short) decor;
|
||||
|
||||
blockdecors.genStructs(globalPos, chunkPos, chunks, depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private short getBlockFromDepth(int depth) {
|
||||
if (depth > 2) return 4;
|
||||
if (depth > 0) return 3;
|
||||
if (depth == 0) return 2;
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
package server.server.world;
|
||||
|
||||
import org.joml.Vector3i;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
public class World {
|
||||
private HashMap<Vector3i, Chunk> chunks;
|
||||
private MapGen mapGen;
|
||||
|
||||
private ThreadPoolExecutor mapGenPool;
|
||||
|
||||
private ArrayList<Future<HashMap<Vector3i, Chunk>>> genFutures;
|
||||
private ArrayList<GenJob> genJobs;
|
||||
|
||||
public World() {
|
||||
chunks = new HashMap<>();
|
||||
mapGen = new MapGen(this);
|
||||
|
||||
genFutures = new ArrayList<>();
|
||||
genJobs = new ArrayList<>();
|
||||
|
||||
mapGenPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(64);
|
||||
mapGenPool.setMaximumPoolSize(128);
|
||||
mapGenPool.setKeepAliveTime(32, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
//
|
||||
// Main Update methods
|
||||
// Update GenChunkTask futures, if done merge chunks
|
||||
//
|
||||
public void update() throws ExecutionException, InterruptedException {
|
||||
|
||||
Iterator<Future<HashMap<Vector3i, Chunk>>> i = genFutures.iterator();
|
||||
while (i.hasNext()) {
|
||||
|
||||
Future<HashMap<Vector3i, Chunk>> future = i.next();
|
||||
|
||||
if (future.isDone()) {
|
||||
mergeChunks(future.get());
|
||||
i.remove();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Merge Chunks from GenChunkTask into main chunk HashMap, overriding properly,
|
||||
// Then remove pending chunks from Jobs and call callbacks if applicable
|
||||
//
|
||||
private void mergeChunks(HashMap<Vector3i, Chunk> newChunks) {
|
||||
|
||||
for (Chunk newChunk : newChunks.values()) {
|
||||
Chunk existingChunk = getChunk(newChunk.getPos());
|
||||
if (existingChunk == null) chunks.put(newChunk.pos, newChunk);
|
||||
else {
|
||||
try {
|
||||
existingChunk.mergeChunk(newChunk);
|
||||
//TODO: When this happens, some chunks already in the client could be changed, need to push updates!
|
||||
}
|
||||
catch (Exception e) {
|
||||
System.err.println(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Vector3i p : newChunks.keySet()) {
|
||||
|
||||
Iterator<GenJob> i = genJobs.iterator();
|
||||
while (i.hasNext()) {
|
||||
GenJob j = i.next();
|
||||
boolean ready = j.acceptChunk(p);
|
||||
if (ready) {
|
||||
jobCallback(j);
|
||||
i.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Call Job Callback (Return on ClientThread)
|
||||
//
|
||||
private void jobCallback(GenJob j) {
|
||||
Chunk[] vals = new Chunk[j.positions.size()];
|
||||
for (var i = 0; i < j.positions.size(); i++) {
|
||||
vals[i] = getChunk(j.positions.get(i));
|
||||
}
|
||||
j.callback.accept(vals);
|
||||
}
|
||||
|
||||
private Chunk getChunk(Vector3i pos) {
|
||||
return chunks.get(pos);
|
||||
}
|
||||
|
||||
//
|
||||
// Get chunks requested by positions array. The resulting chunks will either be returned immediately
|
||||
// by the consumer, or later when they are done generating and the genJob completion task fires in
|
||||
// the update function.
|
||||
//
|
||||
public void getChunks(ArrayList<Vector3i> positions, Consumer<Chunk[]> chunks) {
|
||||
if (positions.size() > 0) {
|
||||
GenJob j = new GenJob(positions, chunks);
|
||||
for (Vector3i p : positions) {
|
||||
if (getChunk(p) != null && getChunk(p).generated) j.acceptChunk(p);
|
||||
|
||||
else {
|
||||
GenChunkTask t = new GenChunkTask(p, mapGen);
|
||||
Future<HashMap<Vector3i, Chunk>> f = mapGenPool.submit(t);
|
||||
genFutures.add(f);
|
||||
}
|
||||
}
|
||||
if (j.isReady()) {
|
||||
jobCallback(j);
|
||||
} else {
|
||||
genJobs.add(j);
|
||||
}
|
||||
}
|
||||
else System.err.println("Empty job requested.");
|
||||
}
|
||||
}
|
|
@ -1,12 +1,11 @@
|
|||
#version 330
|
||||
|
||||
//in vec2 texBase;
|
||||
//in vec2 texCoord;
|
||||
//in vec3 mvVertexNormal;
|
||||
in vec3 mvVertexPos;
|
||||
in vec3 fragNormal;
|
||||
in vec2 outTexCoord;
|
||||
|
||||
in float fragDist;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
struct Material
|
||||
|
@ -22,15 +21,11 @@ uniform sampler2D texture_sampler;
|
|||
uniform float repeat_scale;
|
||||
uniform float atlas_scale;
|
||||
uniform float specularPower;
|
||||
//uniform Material material;
|
||||
|
||||
vec4 color;
|
||||
|
||||
void main()
|
||||
{
|
||||
//vec2 mult = vec2(mod(texCoord.x * repeat_scale - 0.00001, 1), mod(texCoord.y * repeat_scale - 0.00001, 1));
|
||||
//color = texture(texture_sampler, vec2(texBase.x + (atlas_scale * mult.x), texBase.y + (atlas_scale * mult.y)));
|
||||
|
||||
color = texture(texture_sampler, outTexCoord);
|
||||
|
||||
float light = min(0.80f + 0.10f * abs(fragNormal.z) + 0.25f * abs(fragNormal.y), 1.1);
|
||||
|
@ -38,6 +33,13 @@ void main()
|
|||
color = color * vec4(vec3(light), 1);
|
||||
|
||||
if (color.a < 0.5) discard;
|
||||
|
||||
float maxRenderDist = (16*12) - 8;
|
||||
|
||||
float dist_mult = pow(min(1, max(0, fragDist/maxRenderDist)), 2);
|
||||
|
||||
vec4 sky_color = vec4(0.4235, 0.7058, 0.9686, 1);
|
||||
color = color * (1 - dist_mult) + sky_color * (dist_mult);
|
||||
|
||||
fragColor = color;
|
||||
}
|
|
@ -4,11 +4,10 @@ layout (location=0) in vec3 position;
|
|||
layout (location=1) in vec3 vertexNormal;
|
||||
layout (location=2) in vec2 texCoord;
|
||||
|
||||
//out vec2 texBase;
|
||||
out vec2 outTexCoord;
|
||||
//out vec3 mvVertexNormal;
|
||||
out vec3 mvVertexPos;
|
||||
out vec3 fragNormal;
|
||||
out float fragDist;
|
||||
|
||||
uniform mat4 modelViewMatrix;
|
||||
uniform mat4 projectionMatrix;
|
||||
|
@ -18,11 +17,10 @@ void main()
|
|||
{
|
||||
vec4 mvPos = modelViewMatrix * vec4(position, 1.0);
|
||||
gl_Position = projectionMatrix * mvPos;
|
||||
// texBase = vec2(texData.x, texData.y);
|
||||
// texCoord = vec2(texData.z, texData.w);
|
||||
outTexCoord = texCoord;
|
||||
|
||||
//mvVertexNormal = normalize(modelViewMatrix * vec4(vertexNormal, 0.0)).xyz;
|
||||
mvVertexPos = mvPos.xyz;
|
||||
fragNormal = vertexNormal;
|
||||
}
|
||||
|
||||
fragDist = distance(vec3(0,0,0), vec3(mvPos));
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue