Compare commits

...

5 Commits

Author SHA1 Message Date
aurailus d3ffecaa09 Zeus 0.0.8 - Chunk Merges, GenStruct API call, Trees!
- Chunks merged with other chunks based on Generated flag
- Replace GenTrees with GenStruct, has more control and information about the current chunk and position
- Trees now generate and work across chunk borders
- Stopped empty jobs from being made
- Made sure chunks were *generated* as well as not being null when fulfilling a job
2018-09-30 22:43:05 -07:00
aurailus 558fa4902a Zeus 0.0.7 - Mesh handling changes, Chunk Generation Jobs
- Pad Function in Utils
- MeshInfo POJO for ChunkMeshBuilder
- MeshMod Enum for runtime modifications
- MeshPart now has MeshInfo
- Plantlike now uses MeshMod.SHIFT
- Removed REQUEST_CHUNK packetType
- Commented deprecated requestChunk functions in ConnMan
- Debug text in Client World class
- Rewrote MapGen class, better trees (unfinished)
- Moved map gen to Server World Class
- GenChunkTask class for async generation
- GenJob class for storing returning data
- Server world class is more optimized and can generate faster
2018-09-30 20:28:37 -07:00
aurailus ebfe98d55e Zeus 0.0.6 - Cascading Chunk Rendering!
- Range variable in ClientThread
- Cascading chunk rendering!
- Removed adjacentOpaque info from BlockChunk and EncodedBlockChunk
- Chunks render using adjacent array, can be null
2018-09-28 07:55:17 -07:00
aurailus a14fb4a743 Zeus 0.0.56 - ChunkLoader class, Server sent chunk updates
- Packetdata is a helper class now, shared between projects, added PLAYER_POSITION enum to it
- Renamed chunkatlas to world
- Chunkloader class to seperate logic methods from grunt work with world class
- ClientThread now has chunk position variable
- Client sends position every 30 frames for chunk updates
2018-09-27 23:16:52 -07:00
aurailus a55bff673d Zeus 0.0.55 - Server World Class, better calculate sides function
- Server has a world class, containing chunks and MapGen
- GenerateSides now references world class
- Changed "sidesOpaque" property in client chunks to "sides"
2018-09-26 14:57:32 -07:00
35 changed files with 1689 additions and 1279 deletions

File diff suppressed because it is too large Load Diff

View File

@ -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();
}
}

View File

@ -9,6 +9,7 @@ public class BlockAtlas {
public BlockAtlas() {
blockDefs = new ArrayList<>();
create("_:unloaded", null, true, false, false);
create("_:air", null, false, false, false);
}

View File

@ -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");
}
}
}

View File

@ -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) {

View File

@ -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();
}
}

View File

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

View File

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

View File

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

View File

@ -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);

View File

@ -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);

View File

@ -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();
}
}

View File

@ -54,6 +54,6 @@ public class BM_PlantLike extends BlockModel {
1.0f, 1.0f,
1.0f, 0.0f,
},
texPos)};
texPos, MeshMod.SHIFT, 0.15f)};
}
}

View File

@ -0,0 +1,8 @@
package client.game.blockmodels;
public class MeshInfo {
public float[] positions;
public float[] texData;
public float[] normals;
public int[] indices;
}

View File

@ -0,0 +1,6 @@
package client.game.blockmodels;
public enum MeshMod {
NONE,
SHIFT
}

View File

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

View File

@ -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();
}
}

View File

@ -1,5 +1,7 @@
package client.game.network;
import helpers.PacketType;
public class PacketData {
public long time;
public PacketType type;

View File

@ -1,7 +0,0 @@
package client.game.network;
public enum PacketType {
DEBUG,
REQUEST_CHUNK,
BLOCK_CHUNK,
}

View File

@ -1,5 +1,7 @@
package client.game.network;
import helpers.PacketType;
import java.io.*;
import java.net.Socket;
import java.util.ArrayList;

View File

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

View File

@ -2,6 +2,6 @@ package helpers;
public enum PacketType {
DEBUG,
REQUEST_CHUNK,
BLOCK_CHUNK,
PLAYER_POSITION,
}

View File

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

View File

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

View File

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

View File

@ -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

View File

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

View File

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

View File

@ -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();
}
}

View File

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

View File

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

View File

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

View File

@ -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.");
}
}

View File

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

View File

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