Get block updates from the server and update the mesh.

This commit is contained in:
IntegratedQuantum 2022-10-17 20:30:57 +02:00
parent cd5c466bc8
commit a04fc3369d
4 changed files with 225 additions and 9 deletions

View File

@ -102,6 +102,12 @@ pub fn getByID(id: []const u8) u16 {
pub const Block = packed struct {
typ: u16,
data: u16,
pub fn toInt(self: Block) u32 {
return @as(u32, self.typ) | @as(u32, self.data)<<16;
}
pub fn fromInt(self: u32) Block {
return Block{.typ=@truncate(u16, self), .data=@intCast(u16, self>>16)};
}
pub fn lightingTransparent(self: Block) bool {
return _lightingTransparent[self.typ];
}

View File

@ -479,7 +479,7 @@ pub const meshing = struct {
fn canBeSeenThroughOtherBlock(block: Block, other: Block, neighbor: u3) bool {
_ = neighbor; // TODO: Blocks.mode(other).checkTransparency(other, neighbor)
return other.typ == 0 or false or (!std.meta.eql(block, other) and other.viewThrough());
return block.typ != 0 and (other.typ == 0 or false or (!std.meta.eql(block, other) and other.viewThrough()));
}
pub fn regenerateMainMesh(self: *ChunkMesh, chunk: *Chunk) !void {
@ -523,6 +523,164 @@ pub const meshing = struct {
self.neighborStart = [_]u31{self.coreCount} ** 7;
}
fn addFace(self: *ChunkMesh, position: u32, textureNormal: u32, fromNeighborChunk: ?u3) !void {
std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function.
var insertionIndex: u31 = undefined;
if(fromNeighborChunk) |neighbor| {
insertionIndex = self.neighborStart[neighbor];
for(self.neighborStart[neighbor+1..]) |*start| {
start.* += 2;
}
} else {
insertionIndex = self.coreCount;
self.coreCount += 2;
for(self.neighborStart) |*start| {
start.* += 2;
}
}
try self.faces.insert(insertionIndex, position);
try self.faces.insert(insertionIndex+1, textureNormal);
}
fn removeFace(self: *ChunkMesh, position: u32, textureNormal: u32, fromNeighborChunk: ?u3) void {
std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function.
var searchStart: u32 = undefined;
var searchEnd: u32 = undefined;
if(fromNeighborChunk) |neighbor| {
searchStart = self.neighborStart[neighbor];
searchEnd = self.neighborStart[neighbor+1];
for(self.neighborStart[neighbor+1..]) |*start| {
start.* -= 2;
}
} else {
searchStart = 1;
searchEnd = self.coreCount;
self.coreCount -= 2;
for(self.neighborStart) |*start| {
start.* -= 2;
}
}
var i: u32 = searchStart;
while(i < searchEnd): (i += 2) {
if(self.faces.items[i] == position and self.faces.items[i+1] == textureNormal) {
_ = self.faces.orderedRemove(i+1);
_ = self.faces.orderedRemove(i);
return;
}
}
@panic("Couldn't find the face to remove. This case is not handled.");
}
fn changeFace(self: *ChunkMesh, position: u32, oldTextureNormal: u32, newTextureNormal: u32, fromNeighborChunk: ?u3) void {
std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function.
var searchRange: []u32 = undefined;
if(fromNeighborChunk) |neighbor| {
searchRange = self.faces.items[self.neighborStart[neighbor]..self.neighborStart[neighbor+1]];
} else {
searchRange = self.faces.items[1..self.coreCount];
}
var i: u32 = 0;
while(i < searchRange.len): (i += 2) {
if(searchRange[i] == position and searchRange[i+1] == oldTextureNormal) {
searchRange[i+1] = newTextureNormal;
return;
}
}
std.log.err("Couldn't find the face to replace.", .{});
}
pub fn updateBlock(self: *ChunkMesh, _x: ChunkCoordinate, _y: ChunkCoordinate, _z: ChunkCoordinate, newBlock: Block) !void {
const x = _x & chunkMask;
const y = _y & chunkMask;
const z = _z & chunkMask;
self.mutex.lock();
defer self.mutex.unlock();
if(!self.generated) return;
const oldBlock = self.chunk.?.blocks[getIndex(x, y, z)];
for(Neighbors.iterable) |neighbor| {
var neighborMesh = self;
var nx = x + Neighbors.relX[neighbor];
var ny = y + Neighbors.relY[neighbor];
var nz = z + Neighbors.relZ[neighbor];
if(nx & chunkMask != nx or ny & chunkMask != ny or nz & chunkMask != nz) { // Outside this chunk.
neighborMesh = renderer.RenderStructure.getNeighbor(self.pos, self.pos.voxelSize, neighbor) orelse continue;
if(!neighborMesh.generated) continue;
neighborMesh.mutex.lock();
}
defer if(neighborMesh != self) neighborMesh.mutex.unlock();
nx &= chunkMask;
ny &= chunkMask;
nz &= chunkMask;
const neighborBlock = neighborMesh.chunk.?.blocks[getIndex(nx, ny, nz)];
{
{ // The face of the changed block
const newVisibility = canBeSeenThroughOtherBlock(newBlock, neighborBlock, neighbor);
const position: u32 = @intCast(u32, nx) | @intCast(u32, ny)<<5 | @intCast(u32, nz)<<10;
const normal: u32 = neighbor;
const newTextureNormal = blocks.meshes.textureIndices(newBlock)[neighbor] | normal<<24;
const oldTextureNormal = blocks.meshes.textureIndices(oldBlock)[neighbor] | normal<<24;
if(canBeSeenThroughOtherBlock(oldBlock, neighborBlock, neighbor) != newVisibility) {
if(newVisibility) { // Adding the face
if(neighborMesh == self) {
try self.addFace(position, newTextureNormal, null);
} else {
try neighborMesh.addFace(position, newTextureNormal, neighbor);
}
} else { // Removing the face
if(neighborMesh == self) {
self.removeFace(position, oldTextureNormal, null);
} else {
neighborMesh.removeFace(position, oldTextureNormal, neighbor);
}
}
} else if(newVisibility) { // Changing the face
if(neighborMesh == self) {
self.changeFace(position, oldTextureNormal, newTextureNormal, null);
} else {
neighborMesh.changeFace(position, oldTextureNormal, newTextureNormal, neighbor);
}
}
}
{ // The face of the neighbor block
const newVisibility = canBeSeenThroughOtherBlock(neighborBlock, newBlock, neighbor ^ 1);
const position: u32 = @intCast(u32, x) | @intCast(u32, y)<<5 | @intCast(u32, z)<<10;
const normal: u32 = neighbor ^ 1;
const newTextureNormal = blocks.meshes.textureIndices(neighborBlock)[neighbor] | normal<<24;
const oldTextureNormal = blocks.meshes.textureIndices(neighborBlock)[neighbor] | normal<<24;
if(canBeSeenThroughOtherBlock(neighborBlock, oldBlock, neighbor ^ 1) != newVisibility) {
if(newVisibility) { // Adding the face
if(neighborMesh == self) {
try self.addFace(position, newTextureNormal, null);
} else {
try self.addFace(position, newTextureNormal, neighbor);
}
} else { // Removing the face
if(neighborMesh == self) {
self.removeFace(position, oldTextureNormal, null);
} else {
self.removeFace(position, oldTextureNormal, neighbor);
}
}
} else if(newVisibility) { // Changing the face
if(neighborMesh == self) {
self.changeFace(position, oldTextureNormal, newTextureNormal, null);
} else {
self.changeFace(position, oldTextureNormal, newTextureNormal, neighbor);
}
}
}
}
if(neighborMesh != self) neighborMesh.uploadData();
}
self.chunk.?.blocks[getIndex(x, y, z)] = newBlock;
self.uploadData();
}
fn uploadData(self: *ChunkMesh) void {
self.vertexCount = @intCast(u31, 6*(self.faces.items.len-1)/2);
self.faceData.bufferData(u32, self.faces.items);
}
pub fn uploadDataAndFinishNeighbors(self: *ChunkMesh) !void {
std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function.
if(self.chunk == null) return; // In the mean-time the mesh was discarded and recreated and all the data was lost.
@ -563,14 +721,14 @@ pub const meshing = struct {
var otherZ = @intCast(u8, z+%Neighbors.relZ[neighbor] & chunkMask);
var block = (&self.chunk.?.blocks)[getIndex(x, y, z)]; // a temporary fix to a compiler performance bug. TODO: check if this was fixed.
var otherBlock = (&neighborMesh.chunk.?.blocks)[getIndex(otherX, otherY, otherZ)]; // a temporary fix to a compiler performance bug. TODO: check if this was fixed.
if(canBeSeenThroughOtherBlock(block, otherBlock, neighbor) and block.typ != 0) {
if(canBeSeenThroughOtherBlock(block, otherBlock, neighbor)) {
const normal: u32 = neighbor;
const position: u32 = @as(u32, otherX) | @as(u32, otherY)<<5 | @as(u32, otherZ)<<10;
const textureNormal = blocks.meshes.textureIndices(block)[neighbor] | normal<<24;
try additionalNeighborFaces.append(position);
try additionalNeighborFaces.append(textureNormal);
}
if(canBeSeenThroughOtherBlock(otherBlock, block, neighbor ^ 1) and otherBlock.typ != 0) {
if(canBeSeenThroughOtherBlock(otherBlock, block, neighbor ^ 1)) {
const normal: u32 = neighbor ^ 1;
const position: u32 = @as(u32, x) | @as(u32, y)<<5 | @as(u32, z)<<10;
const textureNormal = blocks.meshes.textureIndices(otherBlock)[neighbor ^ 1] | normal<<24;
@ -625,7 +783,7 @@ pub const meshing = struct {
var otherZ = @intCast(u8, (z+%Neighbors.relZ[neighbor]+%offsetZ >> 1) & chunkMask);
var block = (&self.chunk.?.blocks)[getIndex(x, y, z)]; // a temporary fix to a compiler performance bug. TODO: check if this was fixed.
var otherBlock = (&neighborMesh.chunk.?.blocks)[getIndex(otherX, otherY, otherZ)]; // a temporary fix to a compiler performance bug. TODO: check if this was fixed.
if(canBeSeenThroughOtherBlock(otherBlock, block, neighbor ^ 1) and otherBlock.typ != 0) {
if(canBeSeenThroughOtherBlock(otherBlock, block, neighbor ^ 1)) {
const normal: u32 = neighbor ^ 1;
const position: u32 = @as(u32, x) | @as(u32, y)<<5 | @as(u32, z)<<10;
const textureNormal = blocks.meshes.textureIndices(otherBlock)[neighbor ^ 1] | normal<<24;
@ -639,8 +797,7 @@ pub const meshing = struct {
}
}
self.neighborStart[6] = @intCast(u31, self.faces.items.len);
self.vertexCount = @intCast(u31, 6*(self.faces.items.len-1)/2);
self.faceData.bufferData(u32, self.faces.items);
self.uploadData();
self.generated = true;
}

View File

@ -2,6 +2,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const assets = @import("assets.zig");
const Block = @import("blocks.zig").Block;
const chunk = @import("chunk.zig");
const entity = @import("entity.zig");
const main = @import("main.zig");
@ -690,14 +691,15 @@ pub const Protocols: struct {
.voxelSize = @intCast(chunk.UChunkCoordinate, std.mem.readIntBig(chunk.ChunkCoordinate, data[12..16])),
};
const _inflatedData = try utils.Compression.inflate(main.threadAllocator, data[16..]);
if(_inflatedData.len != chunk.chunkVolume*4) {
std.log.err("Transmission of chunk has invalid size: {}. Input data: {any}, After inflate: {any}", .{_inflatedData.len, data, _inflatedData});
}
data = _inflatedData;
defer main.threadAllocator.free(_inflatedData);
var ch = try renderer.RenderStructure.allocator.create(chunk.Chunk);
ch.init(pos);
for(ch.blocks) |*block| {
var blockTypeAndData = std.mem.readIntBig(u32, data[0..4]);
block.typ = @intCast(u16, blockTypeAndData & 0xffff);
block.data = @intCast(u16, blockTypeAndData >> 16);
block.* = Block.fromInt(std.mem.readIntBig(u32, data[0..4]));
data = data[4..];
}
try renderer.RenderStructure.updateChunkMesh(ch);
@ -813,6 +815,30 @@ pub const Protocols: struct {
conn.sendUnimportant(id, fullItemData);
}
},
blockUpdate: type = struct {
const id: u8 = 7;
fn receive(_: *Connection, data: []const u8) !void {
var x = std.mem.readIntBig(chunk.ChunkCoordinate, data[0..4]);
var y = std.mem.readIntBig(chunk.ChunkCoordinate, data[4..8]);
var z = std.mem.readIntBig(chunk.ChunkCoordinate, data[8..12]);
var newBlock = Block.fromInt(std.mem.readIntBig(u32, data[12..16]));
try renderer.RenderStructure.updateBlock(x, y, z, newBlock);
// TODO:
// if(conn instanceof User) {
// Server.world.updateBlock(x, y, z, newBlock);
// } else {
// Cubyz.world.remoteUpdateBlock(x, y, z, newBlock);
// }
}
pub fn send(conn: *Connection, x: chunk.ChunkCoordinate, y: chunk.ChunkCoordinate, z: chunk.ChunkCoordinate, newBlock: Block) !void {
var data: [16]u8 = undefined;
std.mem.writeIntBig(chunk.ChunkCoordinate, data[0..4], x);
std.mem.writeIntBig(chunk.ChunkCoordinate, data[4..8], y);
std.mem.writeIntBig(chunk.ChunkCoordinate, data[8..12], z);
std.mem.writeIntBig(chunk.ChunkCoordinate, data[12..16], newBlock.toInt());
try conn.sendImportant(id, &data);
}
},
entity: type = struct {
const id: u8 = 8;
fn receive(_: *Connection, data: []const u8) !void {

View File

@ -444,6 +444,14 @@ pub const RenderStructure = struct {
var lastSize: [settings.highestLOD + 1]chunk.ChunkCoordinate = [_]chunk.ChunkCoordinate{0} ** (settings.highestLOD + 1);
var lodMutex: [settings.highestLOD + 1]std.Thread.Mutex = [_]std.Thread.Mutex{std.Thread.Mutex{}} ** (settings.highestLOD + 1);
var mutex = std.Thread.Mutex{};
var blockUpdateMutex = std.Thread.Mutex{};
const BlockUpdate = struct {
x: chunk.ChunkCoordinate,
y: chunk.ChunkCoordinate,
z: chunk.ChunkCoordinate,
newBlock: blocks.Block,
};
var blockUpdateList: std.ArrayList(BlockUpdate) = undefined;
pub fn init() !void {
lastRD = 0;
@ -451,6 +459,7 @@ pub const RenderStructure = struct {
gpa = std.heap.GeneralPurposeAllocator(.{}){};
allocator = gpa.allocator();
updatableList = std.ArrayList(chunk.ChunkPosition).init(allocator);
blockUpdateList = std.ArrayList(BlockUpdate).init(allocator);
clearList = std.ArrayList(*ChunkMeshNode).init(allocator);
for(storageLists) |*storageList| {
storageList.* = try allocator.alloc(?*ChunkMeshNode, 0);
@ -472,6 +481,7 @@ pub const RenderStructure = struct {
chunkMesh.mesh.deinit();
allocator.destroy(chunkMesh);
}
blockUpdateList.deinit();
clearList.deinit();
game.world.?.blockPalette.deinit();
if(gpa.deinit()) {
@ -654,6 +664,17 @@ pub const RenderStructure = struct {
}
pub fn updateMeshes(targetTime: i64) !void {
{ // First of all process all the block updates:
blockUpdateMutex.lock();
defer blockUpdateMutex.unlock();
for(blockUpdateList.items) |blockUpdate| {
const pos = chunk.ChunkPosition{.wx=blockUpdate.x, .wy=blockUpdate.y, .wz=blockUpdate.z, .voxelSize=1};
if(_getNode(pos)) |node| {
try node.mesh.updateBlock(blockUpdate.x, blockUpdate.y, blockUpdate.z, blockUpdate.newBlock);
}
}
blockUpdateList.clearRetainingCapacity();
}
mutex.lock();
defer mutex.unlock();
while(updatableList.items.len != 0) {
@ -757,6 +778,12 @@ pub const RenderStructure = struct {
}
};
pub fn updateBlock(x: chunk.ChunkCoordinate, y: chunk.ChunkCoordinate, z: chunk.ChunkCoordinate, newBlock: blocks.Block) !void {
blockUpdateMutex.lock();
try blockUpdateList.append(BlockUpdate{.x=x, .y=y, .z=z, .newBlock=newBlock});
defer blockUpdateMutex.unlock();
}
pub fn updateChunkMesh(mesh: *chunk.Chunk) !void {
try MeshGenerationTask.schedule(mesh);
}