rendering: improve face preparation, fix some bugs in world rendering/handling

This commit is contained in:
Bixilon 2020-09-30 16:35:02 +02:00
parent 1d3b09c856
commit b41a7b3694
No known key found for this signature in database
GPG Key ID: 5CAD791931B09AC4
9 changed files with 158 additions and 70 deletions

View File

@ -63,6 +63,7 @@ public class BlockPosition {
public ChunkLocation getChunkLocation() {
int x = getX() / 16;
int z = getZ() / 16;
//ToDo
if (getX() < 0) {
x--;
}

View File

@ -18,14 +18,15 @@ import de.bixilon.minosoft.game.datatypes.objectLoader.blocks.Blocks;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Collection of 16 chunks nibbles
*/
public class Chunk {
final HashMap<Byte, ChunkNibble> nibbles;
final ConcurrentHashMap<Byte, ChunkNibble> nibbles;
public Chunk(HashMap<Byte, ChunkNibble> chunks) {
public Chunk(ConcurrentHashMap<Byte, ChunkNibble> chunks) {
this.nibbles = chunks;
}
@ -46,17 +47,17 @@ public class Chunk {
public void setBlock(int x, int y, int z, Block block) {
byte section = (byte) (y / 16);
createSection(section);
createSectionIfNotExist(section);
nibbles.get(section).setBlock(x, y % 16, z, block);
}
public void setBlock(InChunkLocation location, Block block) {
byte section = (byte) (location.getY() / 16);
createSection(section);
createSectionIfNotExist(section);
nibbles.get(section).setBlock(location.getChunkNibbleLocation(), block);
}
void createSection(byte section) {
void createSectionIfNotExist(byte section) {
if (nibbles.get(section) == null) {
// nibble was empty before, creating it
nibbles.put(section, new ChunkNibble());
@ -69,7 +70,7 @@ public class Chunk {
}
}
public HashMap<Byte, ChunkNibble> getNibbles() {
public ConcurrentHashMap<Byte, ChunkNibble> getNibbles() {
return nibbles;
}
}

View File

@ -14,25 +14,29 @@
package de.bixilon.minosoft.game.datatypes.world;
import de.bixilon.minosoft.game.datatypes.objectLoader.blocks.Block;
import de.bixilon.minosoft.game.datatypes.objectLoader.blocks.Blocks;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
/**
* Collection of 16x16x16 blocks
*/
public class ChunkNibble {
final HashMap<ChunkNibbleLocation, Block> blocks;
final ConcurrentHashMap<ChunkNibbleLocation, Block> blocks;
public ChunkNibble(HashMap<ChunkNibbleLocation, Block> blocks) {
public ChunkNibble(ConcurrentHashMap<ChunkNibbleLocation, Block> blocks) {
this.blocks = blocks;
}
public ChunkNibble() {
// empty
this.blocks = new HashMap<>();
this.blocks = new ConcurrentHashMap<>();
}
public Block getBlock(ChunkNibbleLocation loc) {
if (!blocks.containsKey(loc)) {
return null;
}
return blocks.get(loc);
}
@ -41,14 +45,18 @@ public class ChunkNibble {
}
public void setBlock(int x, int y, int z, Block block) {
blocks.put(new ChunkNibbleLocation(x, y, z), block);
setBlock(new ChunkNibbleLocation(x, y, z), block);
}
public void setBlock(ChunkNibbleLocation location, Block block) {
if (block == null || block.equals(Blocks.nullBlock)) {
blocks.remove(location);
return;
}
blocks.put(location, block);
}
public HashMap<ChunkNibbleLocation, Block> getBlocks() {
public ConcurrentHashMap<ChunkNibbleLocation, Block> getBlocks() {
return blocks;
}
}

View File

@ -17,20 +17,20 @@ import de.bixilon.minosoft.game.datatypes.entities.Entity;
import de.bixilon.minosoft.game.datatypes.objectLoader.blocks.Block;
import de.bixilon.minosoft.game.datatypes.objectLoader.blocks.Blocks;
import de.bixilon.minosoft.game.datatypes.objectLoader.dimensions.Dimension;
import de.bixilon.minosoft.render.GameWindow;
import de.bixilon.minosoft.util.nbt.tag.CompoundTag;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Collection of ChunkColumns
*/
public class World {
final HashMap<ChunkLocation, Chunk> chunks = new HashMap<>();
final HashMap<Integer, Entity> entities = new HashMap<>();
final ConcurrentHashMap<ChunkLocation, Chunk> chunks = new ConcurrentHashMap<>();
final ConcurrentHashMap<Integer, Entity> entities = new ConcurrentHashMap<>();
final String name;
final HashMap<BlockPosition, CompoundTag> blockEntityMeta = new HashMap<>();
final ConcurrentHashMap<BlockPosition, CompoundTag> blockEntityMeta = new ConcurrentHashMap<>();
boolean hardcore;
boolean raining;
Dimension dimension; // used for sky color, etc
@ -47,7 +47,7 @@ public class World {
return chunks.get(loc);
}
public HashMap<ChunkLocation, Chunk> getAllChunks() {
public ConcurrentHashMap<ChunkLocation, Chunk> getAllChunks() {
return chunks;
}
@ -65,7 +65,6 @@ public class World {
public void setBlock(BlockPosition pos, Block block) {
if (getChunk(pos.getChunkLocation()) != null) {
getChunk(pos.getChunkLocation()).setBlock(pos.getInChunkLocation(), block);
GameWindow.getRenderer().prepareChunkNibble(pos.getChunkLocation(), (byte) (pos.getY() / 16), getChunk(pos.getChunkLocation()).getNibbles().get((byte) (pos.getY() / 16)));
}
// do nothing if chunk is unloaded
}
@ -76,14 +75,10 @@ public class World {
public void setChunk(ChunkLocation location, Chunk chunk) {
chunks.put(location, chunk);
GameWindow.getRenderer().queueChunk(location, chunk);
}
public void setChunks(HashMap<ChunkLocation, Chunk> chunkMap) {
for (Map.Entry<ChunkLocation, Chunk> set : chunkMap.entrySet()) {
chunks.put(set.getKey(), set.getValue());
}
GameWindow.getRenderer().queueChunkBulk(chunkMap);
chunkMap.forEach(chunks::put);
}
public boolean isHardcore() {
@ -135,7 +130,7 @@ public class World {
return blockEntityMeta.get(position);
}
public void setBlockEntityData(HashMap<BlockPosition, CompoundTag> blockEntities) {
public void setBlockEntityData(ConcurrentHashMap<BlockPosition, CompoundTag> blockEntities) {
for (Map.Entry<BlockPosition, CompoundTag> entrySet : blockEntities.entrySet()) {
blockEntityMeta.put(entrySet.getKey(), entrySet.getValue());
}

View File

@ -24,10 +24,10 @@ import de.bixilon.minosoft.util.ChunkUtil;
import de.bixilon.minosoft.util.Util;
import de.bixilon.minosoft.util.nbt.tag.CompoundTag;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
public class PacketChunkData implements ClientboundPacket {
final HashMap<BlockPosition, CompoundTag> blockEntities = new HashMap<>();
final ConcurrentHashMap<BlockPosition, CompoundTag> blockEntities = new ConcurrentHashMap<>();
ChunkLocation location;
Chunk chunk;
CompoundTag heightMap;
@ -122,7 +122,7 @@ public class PacketChunkData implements ClientboundPacket {
return chunk;
}
public HashMap<BlockPosition, CompoundTag> getBlockEntities() {
public ConcurrentHashMap<BlockPosition, CompoundTag> getBlockEntities() {
return blockEntities;
}

View File

@ -55,7 +55,7 @@ public class PacketMultiBlockChange implements ClientboundPacket {
byte pos = buffer.readByte();
byte y = buffer.readByte();
int blockId = buffer.readVarInt();
blocks.put(new InChunkLocation((pos & 0xF0 >>> 4) & 0xF, y, pos & 0xF), buffer.getConnection().getMapping().getBlockById(blockId));
blocks.put(new InChunkLocation((pos >>> 4) & 0xF, y, pos & 0xF), buffer.getConnection().getMapping().getBlockById(blockId));
}
return true;
}

View File

@ -29,6 +29,7 @@ import de.bixilon.minosoft.game.datatypes.scoreboard.ScoreboardScore;
import de.bixilon.minosoft.game.datatypes.scoreboard.Team;
import de.bixilon.minosoft.game.datatypes.world.BlockPosition;
import de.bixilon.minosoft.game.datatypes.world.Chunk;
import de.bixilon.minosoft.game.datatypes.world.ChunkLocation;
import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.protocol.network.Connection;
import de.bixilon.minosoft.protocol.packets.clientbound.login.*;
@ -38,10 +39,10 @@ import de.bixilon.minosoft.protocol.packets.clientbound.status.PacketStatusRespo
import de.bixilon.minosoft.protocol.packets.serverbound.login.PacketEncryptionResponse;
import de.bixilon.minosoft.protocol.packets.serverbound.play.PacketKeepAliveResponse;
import de.bixilon.minosoft.protocol.packets.serverbound.play.PacketResourcePackStatus;
import de.bixilon.minosoft.util.nbt.tag.CompoundTag;
import de.bixilon.minosoft.util.nbt.tag.StringTag;
import de.bixilon.minosoft.render.GameWindow;
import de.bixilon.minosoft.render.utility.Vec3;
import de.bixilon.minosoft.util.nbt.tag.CompoundTag;
import de.bixilon.minosoft.util.nbt.tag.StringTag;
import javax.crypto.SecretKey;
import java.math.BigInteger;
@ -67,7 +68,7 @@ public class PacketHandler {
if (version == null) {
Log.fatal(String.format("Server is running on unknown version or a invalid version was forced (version=%d, brand=\"%s\")", versionId, pkg.getResponse().getServerBrand()));
} else {
connection.setVersion(version);
connection.setVersion(version);
}
Log.info(String.format("Status response received: %s/%s online. MotD: '%s'", pkg.getResponse().getPlayerOnline(), pkg.getResponse().getMaxPlayers(), pkg.getResponse().getMotd().getColoredMessage()));
connection.handlePingCallbacks(pkg.getResponse());
@ -172,6 +173,7 @@ public class PacketHandler {
public void handle(PacketChunkBulk pkg) {
connection.getPlayer().getWorld().setChunks(pkg.getChunkMap());
GameWindow.getRenderer().queueChunkBulk(pkg.getChunkMap());
}
public void handle(PacketUpdateHealth pkg) {
@ -288,7 +290,10 @@ public class PacketHandler {
}
public void handle(PacketBlockChange pkg) {
connection.getPlayer().getWorld().setBlock(pkg.getPosition(), pkg.getBlock());
ChunkLocation chunkLocation = pkg.getPosition().getChunkLocation();
Chunk chunk = connection.getPlayer().getWorld().getChunk(chunkLocation);
chunk.setBlock(pkg.getPosition().getInChunkLocation(), pkg.getBlock());
GameWindow.getRenderer().queueChunk(chunkLocation, chunk); // ToDo: only recalculate the changed nibbles
}
public void handle(PacketMultiBlockChange pkg) {
@ -298,6 +303,7 @@ public class PacketHandler {
return;
}
chunk.setBlocks(pkg.getBlocks());
GameWindow.getRenderer().queueChunk(pkg.getLocation(), chunk); // ToDo: only recalculate the changed nibbles
}
public void handle(PacketRespawn pkg) {
@ -328,6 +334,7 @@ public class PacketHandler {
public void handle(PacketChunkData pkg) {
connection.getPlayer().getWorld().setChunk(pkg.getLocation(), pkg.getChunk());
connection.getPlayer().getWorld().setBlockEntityData(pkg.getBlockEntities());
GameWindow.getRenderer().queueChunk(pkg.getLocation(), pkg.getChunk());
}
public void handle(PacketEntityEffect pkg) {

View File

@ -22,8 +22,10 @@ import javafx.util.Pair;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import static org.lwjgl.opengl.GL11.*;
@ -33,6 +35,11 @@ public class WorldRenderer {
private LinkedBlockingQueue<Pair<ChunkLocation, Chunk>> queuedChunks;
public int getCountOfFaces() {
AtomicInteger count = new AtomicInteger();
faces.forEach((chunkLocation, nibbleMap) -> nibbleMap.forEach((height, faceMap) -> faceMap.forEach(((nibbleLocation, faces) -> faces.forEach((face -> count.incrementAndGet()))))));
return count.get();
}
public void init() {
queuedChunks = new LinkedBlockingQueue<>();
@ -44,7 +51,8 @@ public class WorldRenderer {
while (true) {
try {
Pair<ChunkLocation, Chunk> current = queuedChunks.take();
prepareChunk(current.getKey(), current.getValue());
prepareChunk(current.getKey(), current.getValue(), true);
//Log.verbose(String.format("Count of faces: %d", getCountOfFaces()));
} catch (InterruptedException e) {
e.printStackTrace();
}
@ -62,52 +70,113 @@ public class WorldRenderer {
queuedChunks.add(new Pair<>(location, chunk));
}
public void prepareChunk(ChunkLocation location, Chunk chunk) {
public void prepareChunk(ChunkLocation location, Chunk chunk, boolean checkEdges) {
// clear or create current chunk
faces.put(location, new ConcurrentHashMap<>());
chunk.getNibbles().forEach(((height, chunkNibble) -> prepareChunkNibble(location, height, chunkNibble)));
ConcurrentHashMap<Byte, ConcurrentHashMap<ChunkNibbleLocation, HashSet<Face>>> chunkFaces = new ConcurrentHashMap<>();
chunk.getNibbles().forEach(((height, chunkNibble) -> chunkFaces.put(height, getFacesForChunkNibble(location, height, chunkNibble))));
faces.put(location, chunkFaces);
if (!checkEdges) {
return;
}
//ToDo
}
public void prepareChunkNibble(ChunkLocation chunkLocation, byte height, ChunkNibble nibble) {
public ConcurrentHashMap<ChunkNibbleLocation, HashSet<Face>> getFacesForChunkNibble(ChunkLocation chunkLocation, byte sectionHeight, ChunkNibble nibble) {
ConcurrentHashMap<ChunkLocation, Chunk> world = GameWindow.getConnection().getPlayer().getWorld().getAllChunks();
// clear or create current chunk nibble
ConcurrentHashMap<ChunkNibbleLocation, HashSet<Face>> nibbleMap = new ConcurrentHashMap<>();
faces.get(chunkLocation).put(height, nibbleMap);
HashMap<ChunkNibbleLocation, Block> nibbleBlocks = nibble.getBlocks();
nibbleBlocks.forEach((location, block) -> {
ConcurrentHashMap<ChunkNibbleLocation, Block> nibbleBlocks = nibble.getBlocks();
for (Map.Entry<ChunkNibbleLocation, Block> entry : nibbleBlocks.entrySet()) {
ChunkNibbleLocation location = entry.getKey();
Block block = entry.getValue();
HashSet<FaceOrientation> facesToDraw = new HashSet<>();
for (FaceOrientation orientation : FaceOrientation.values()) {
if ((location.getX() == 0 && orientation == FaceOrientation.WEST) || (location.getX() == 15 && orientation == FaceOrientation.EAST)) {
facesToDraw.add(orientation);
continue;
}
if ((location.getY() == 0 && orientation == FaceOrientation.DOWN) || (location.getY() == 15 && orientation == FaceOrientation.UP)) {
facesToDraw.add(orientation);
continue;
}
if ((location.getZ() == 0 && orientation == FaceOrientation.NORTH) || (location.getZ() == 15 && orientation == FaceOrientation.SOUTH)) {
facesToDraw.add(orientation);
continue;
}
//BlockPosition neighbourPos = location.add(faceDir[orientation.ordinal()]);
boolean isNeighbourFull = switch (orientation) {
case DOWN -> assetsLoader.getBlockModelLoader().isFull(nibbleBlocks.get(new ChunkNibbleLocation(location.getX(), location.getY() - 1, location.getZ())));
case UP -> assetsLoader.getBlockModelLoader().isFull(nibbleBlocks.get(new ChunkNibbleLocation(location.getX(), location.getY() + 1, location.getZ())));
case WEST -> assetsLoader.getBlockModelLoader().isFull(nibbleBlocks.get(new ChunkNibbleLocation(location.getX() - 1, location.getY(), location.getZ())));
case EAST -> assetsLoader.getBlockModelLoader().isFull(nibbleBlocks.get(new ChunkNibbleLocation(location.getX() + 1, location.getY(), location.getZ())));
case NORTH -> assetsLoader.getBlockModelLoader().isFull(nibbleBlocks.get(new ChunkNibbleLocation(location.getX(), location.getY(), location.getZ() - 1)));
case SOUTH -> assetsLoader.getBlockModelLoader().isFull(nibbleBlocks.get(new ChunkNibbleLocation(location.getX(), location.getY(), location.getZ() + 1)));
Block dependedBlock = switch (orientation) {
case DOWN -> {
if (location.getY() == 0) {
// need to check upper section (nibble)
if (sectionHeight == 0) {
// y = 0, there can't be any blocks below me
yield null;
}
// check if block over us is a full block
byte bottomSection = (byte) (sectionHeight - 1);
if (!world.get(chunkLocation).getNibbles().containsKey(bottomSection)) {
yield null;
}
yield world.get(chunkLocation).getNibbles().get(bottomSection).getBlock(location.getX(), 15, location.getZ());
}
yield nibbleBlocks.get(new ChunkNibbleLocation(location.getX(), location.getY() - 1, location.getZ()));
}
case UP -> {
if (location.getY() == 15) {
// need to check upper section (nibble)
if (sectionHeight == 15) {
// y = 255, there can't be any blocks above me
yield null;
}
// check if block over us is a full block
byte upperSection = (byte) (sectionHeight + 1);
if (!world.get(chunkLocation).getNibbles().containsKey(upperSection)) {
yield null;
}
yield world.get(chunkLocation).getNibbles().get(upperSection).getBlock(location.getX(), 0, location.getZ());
}
yield nibbleBlocks.get(new ChunkNibbleLocation(location.getX(), location.getY() + 1, location.getZ()));
}
case WEST -> {
if (location.getX() == 0) {
ChunkNibble otherChunkNibble = getChunkNibbleOfWorld(world, new ChunkLocation(chunkLocation.getX() - 1, chunkLocation.getZ()), sectionHeight);
if (otherChunkNibble != null) {
yield otherChunkNibble.getBlock(15, location.getY(), location.getZ());
}
}
yield nibbleBlocks.get(new ChunkNibbleLocation(location.getX() - 1, location.getY(), location.getZ()));
}
case EAST -> {
if (location.getX() == 15) {
ChunkNibble otherChunkNibble = getChunkNibbleOfWorld(world, new ChunkLocation(chunkLocation.getX() + 1, chunkLocation.getZ()), sectionHeight);
if (otherChunkNibble != null) {
yield otherChunkNibble.getBlock(0, location.getY(), location.getZ());
}
}
yield nibbleBlocks.get(new ChunkNibbleLocation(location.getX() + 1, location.getY(), location.getZ()));
}
case NORTH -> {
if (location.getZ() == 0) {
ChunkNibble otherChunkNibble = getChunkNibbleOfWorld(world, new ChunkLocation(chunkLocation.getX(), chunkLocation.getZ() - 1), sectionHeight);
if (otherChunkNibble != null) {
yield otherChunkNibble.getBlock(location.getX(), location.getY(), 15);
}
}
yield nibbleBlocks.get(new ChunkNibbleLocation(location.getX(), location.getY(), location.getZ() - 1));
}
case SOUTH -> {
if (location.getZ() == 15) {
ChunkNibble otherChunkNibble = getChunkNibbleOfWorld(world, new ChunkLocation(chunkLocation.getX(), chunkLocation.getZ() + 1), sectionHeight);
if (otherChunkNibble != null) {
yield otherChunkNibble.getBlock(location.getX(), location.getY(), 0);
}
}
yield nibbleBlocks.get(new ChunkNibbleLocation(location.getX(), location.getY(), location.getZ() + 1));
}
};
if (!isNeighbourFull) {
if (dependedBlock == null || !assetsLoader.getBlockModelLoader().isFull(dependedBlock)) {
facesToDraw.add(orientation);
}
}
if (facesToDraw.size() > 0) {
nibbleMap.put(location, assetsLoader.getBlockModelLoader().prepare(block, facesToDraw));
}
});
}
return nibbleMap;
}
public void prepareChunkNibble(ChunkLocation chunkLocation, byte sectionHeight, ChunkNibble nibble) {
faces.get(chunkLocation).put(sectionHeight, getFacesForChunkNibble(chunkLocation, sectionHeight, nibble));
}
@ -122,4 +191,11 @@ public class WorldRenderer {
public AssetsLoader getAssetsLoader() {
return assetsLoader;
}
private ChunkNibble getChunkNibbleOfWorld(ConcurrentHashMap<ChunkLocation, Chunk> world, ChunkLocation location, byte sectionHeight) {
if (world.containsKey(location) && world.get(location).getNibbles().containsKey(sectionHeight)) {
return world.get(location).getNibbles().get(sectionHeight);
}
return null;
}
}

View File

@ -23,7 +23,7 @@ import de.bixilon.minosoft.logging.Log;
import de.bixilon.minosoft.protocol.protocol.InByteBuffer;
import de.bixilon.minosoft.protocol.protocol.ProtocolDefinition;
import java.util.HashMap;
import java.util.concurrent.ConcurrentHashMap;
public final class ChunkUtil {
public static Chunk readChunkPacket(InByteBuffer buffer, short sectionBitMask, short addBitMask, boolean groundUpContinuous, boolean containsSkyLight) {
@ -51,11 +51,11 @@ public final class ChunkUtil {
//parse data
int arrayPos = 0;
HashMap<Byte, ChunkNibble> nibbleMap = new HashMap<>();
ConcurrentHashMap<Byte, ChunkNibble> nibbleMap = new ConcurrentHashMap<>();
for (byte c = 0; c < 16; c++) { // max sections per chunks in chunk column
if (BitByte.isBitSet(sectionBitMask, c)) {
HashMap<ChunkNibbleLocation, Block> blockMap = new HashMap<>();
ConcurrentHashMap<ChunkNibbleLocation, Block> blockMap = new ConcurrentHashMap<>();
for (int nibbleY = 0; nibbleY < 16; nibbleY++) {
for (int nibbleZ = 0; nibbleZ < 16; nibbleZ++) {
@ -115,12 +115,12 @@ public final class ChunkUtil {
}
int arrayPos = 0;
HashMap<Byte, ChunkNibble> nibbleMap = new HashMap<>();
ConcurrentHashMap<Byte, ChunkNibble> nibbleMap = new ConcurrentHashMap<>();
for (byte c = 0; c < 16; c++) { // max sections per chunks in chunk column
if (!BitByte.isBitSet(sectionBitMask, c)) {
continue;
}
HashMap<ChunkNibbleLocation, Block> blockMap = new HashMap<>();
ConcurrentHashMap<ChunkNibbleLocation, Block> blockMap = new ConcurrentHashMap<>();
for (int nibbleY = 0; nibbleY < 16; nibbleY++) {
for (int nibbleZ = 0; nibbleZ < 16; nibbleZ++) {
@ -141,7 +141,7 @@ public final class ChunkUtil {
return new Chunk(nibbleMap);
}
// really big thanks to: https://wiki.vg/index.php?title=Chunk_Format&oldid=13712
HashMap<Byte, ChunkNibble> nibbleMap = new HashMap<>();
ConcurrentHashMap<Byte, ChunkNibble> nibbleMap = new ConcurrentHashMap<>();
for (byte c = 0; c < 16; c++) { // max sections per chunks in chunk column
if (!BitByte.isBitSet(sectionBitMask, c)) {
continue;
@ -155,7 +155,7 @@ public final class ChunkUtil {
long[] data = buffer.readLongArray(buffer.readVarInt());
HashMap<ChunkNibbleLocation, Block> blockMap = new HashMap<>();
ConcurrentHashMap<ChunkNibbleLocation, Block> blockMap = new ConcurrentHashMap<>();
for (int nibbleY = 0; nibbleY < 16; nibbleY++) {
for (int nibbleZ = 0; nibbleZ < 16; nibbleZ++) {
for (int nibbleX = 0; nibbleX < 16; nibbleX++) {