diff --git a/assets/cubyz/shaders/chunks/chunk_fragment.fs b/assets/cubyz/shaders/chunks/chunk_fragment.fs index 173f62f6..60278cfd 100644 --- a/assets/cubyz/shaders/chunks/chunk_fragment.fs +++ b/assets/cubyz/shaders/chunks/chunk_fragment.fs @@ -30,8 +30,7 @@ vec4 calcFog(vec3 pos, vec4 color, Fog fog) { return vec4(resultColor.xyz, color.w + 1 - fogFactor); } -void main() -{ +void main() { fragColor = texture(texture_sampler, vec3(outTexCoord, textureIndex))*vec4((1 - dot(directionalLight, outNormal))*ambientLight, 1); if (fragColor.a <= 0.1f) discard; if (fog.activ) { diff --git a/assets/cubyz/shaders/chunks/chunk_vertex.vs b/assets/cubyz/shaders/chunks/chunk_vertex.vs index 7f1821ee..afda27bf 100644 --- a/assets/cubyz/shaders/chunks/chunk_vertex.vs +++ b/assets/cubyz/shaders/chunks/chunk_vertex.vs @@ -57,8 +57,7 @@ const ivec3[6] textureY = ivec3[6]( ivec3(0, 0, 1) ); -void main() -{ +void main() { int faceID = gl_VertexID/4; int vertexID = gl_VertexID%4; int encodedPosition = faceData[faceID].encodedPosition; diff --git a/src/assets.zig b/src/assets.zig index c6740620..4feb2e96 100644 --- a/src/assets.zig +++ b/src/assets.zig @@ -129,7 +129,61 @@ pub fn registerBlock(assetFolder: []const u8, id: []const u8, info: JsonElement) // } } -pub fn loadWorldAssets(assetFolder: []const u8) !void { +pub const BlockPalette = struct { + palette: std.ArrayList([]const u8), + pub fn init(allocator: Allocator, jsonObject: JsonElement) !*BlockPalette { + var self = try allocator.create(BlockPalette); + self.* = BlockPalette { + .palette = std.ArrayList([]const u8).init(allocator), + }; + errdefer self.deinit(); + if(jsonObject != .JsonObject or jsonObject.JsonObject.count() == 0) { + try self.palette.append(try allocator.dupe(u8, "cubyz:air")); + } else { + var palette = try main.threadAllocator.alloc(?[]const u8, jsonObject.JsonObject.count()); + for(palette) |*val| { + val.* = null; + } + defer main.threadAllocator.free(palette); + var iterator = jsonObject.JsonObject.iterator(); + while(iterator.next()) |entry| { + palette[entry.value_ptr.as(usize, std.math.maxInt(usize))] = entry.key_ptr.*; + } + std.debug.assert(std.mem.eql(u8, palette[0].?, "cubyz:air")); + for(palette) |val| { + std.log.info("palette[{}]: {s}", .{self.palette.items.len, val.?}); + try self.palette.append(try allocator.dupe(u8, val orelse return error.MissingKeyInPalette)); + } + } + return self; + } + + pub fn deinit(self: *BlockPalette) void { + for(self.palette.items) |item| { + self.palette.allocator.free(item); + } + var allocator = self.palette.allocator; + self.palette.deinit(); + allocator.destroy(self); + } + + pub fn add(self: *BlockPalette, id: []const u8) !void { + try self.palette.append(id); + } + + pub fn save(self: *BlockPalette, allocator: Allocator) !JsonElement { + var jsonObject = JsonElement{ + .JsonObject = std.StringHashMap(JsonElement).init(allocator), + }; + errdefer jsonObject.free(allocator); + for(self.palette.items) |item, i| { + jsonObject.JsonObject.put(try allocator.dupe(u8, item), JsonElement{.JsonInt = @intCast(i64, i)}); + } + return jsonObject; + } +}; + +pub fn loadWorldAssets(assetFolder: []const u8, palette: *BlockPalette) !void { var blocks = try commonBlocks.cloneWithAllocator(main.threadAllocator); defer blocks.clearAndFree(); var biomes = try commonBiomes.cloneWithAllocator(main.threadAllocator); @@ -138,24 +192,28 @@ pub fn loadWorldAssets(assetFolder: []const u8) !void { try readAssets(arenaAllocator, assetFolder, &blocks, &biomes); var block: u32 = 0; - // TODO: -// for(; block < palette.size(); block++) { -// Resource id = palette.getResource(block); -// JsonObject json = perWorldBlocks.remove(id); -// if(json == null) { -// Logger.error("Missing block: " + id + ". Replacing it with default block."); -// json = new JsonObject(); -// } -// registerBlock(block, id, json, registries, oreRegistry); -// } - var iterator = commonBlocks.iterator(); + for(palette.palette.items) |id| { + var nullKeyValue = blocks.fetchRemove(id); + var jsonObject: JsonElement = undefined; + if(nullKeyValue) |keyValue| { + jsonObject = keyValue.value; + } else { + std.log.err("Missing block: {s}. Replacing it with default block.", .{id}); + var map: *std.StringHashMap(JsonElement) = try main.threadAllocator.create(std.StringHashMap(JsonElement)); + map.* = std.StringHashMap(JsonElement).init(main.threadAllocator); + jsonObject = JsonElement{.JsonObject=map}; + } + defer if(nullKeyValue == null) jsonObject.free(main.threadAllocator); + try registerBlock(assetFolder, id, jsonObject); + block += 1; + } + var iterator = blocks.iterator(); while(iterator.next()) |entry| { try registerBlock(assetFolder, entry.key_ptr.*, entry.value_ptr.*); + try palette.palette.append(entry.key_ptr.*); block += 1; -// TODO: -// palette.addResource(entry.getKey()); } -// + // public void registerBlocks(Registry registries, NoIDRegistry oreRegistry, BlockPalette palette) { // HashMap perWorldBlocks = new HashMap<>(commonBlocks); // readAllJsonObjects("blocks", (json, id) -> { diff --git a/src/blocks.zig b/src/blocks.zig index 83d7867c..e9486979 100644 --- a/src/blocks.zig +++ b/src/blocks.zig @@ -4,6 +4,7 @@ const JsonElement = @import("json.zig").JsonElement; const Neighbors = @import("chunk.zig").Neighbors; const SSBO = @import("graphics.zig").SSBO; const Image = @import("graphics.zig").Image; +const Color = @import("graphics.zig").Color; const TextureArray = @import("graphics.zig").TextureArray; pub const BlockClass = enum(u8) { @@ -219,15 +220,18 @@ pub const meshes = struct { pub var blockTextureArray: TextureArray = undefined; pub var emissionTextureArray: TextureArray = undefined; - var undefinedTexture = [_]u32 {0xffff00ff, 0xff000000, 0xff000000, 0xffff00ff}; + const black: Color = Color{.r=0, .g=0, .b=0, .a=255}; + const magenta: Color = Color{.r=255, .g=0, .b=255, .a=255}; + var undefinedTexture = [_]Color {magenta, black, black, magenta}; const undefinedImage = Image{.width = 2, .height = 2, .imageData = undefinedTexture[0..]}; - var emptyTexture = [_]u32 {0}; + var emptyTexture = [_]Color {black}; const emptyImage = Image{.width = 1, .height = 1, .imageData = emptyTexture[0..]}; pub fn init() void { animationTimesSSBO = SSBO.init(); animationTimesSSBO.bind(0); animationFramesSSBO = SSBO.init(); + animationFramesSSBO.bind(1); blockTextureArray = TextureArray.init(); emissionTextureArray = TextureArray.init(); arenaForArrayLists = std.heap.ArenaAllocator.init(std.heap.page_allocator); @@ -276,7 +280,7 @@ pub const meshes = struct { const mod = splitter.first(); const id = splitter.rest(); var buffer: [1024]u8 = undefined; - var path = try std.fmt.bufPrint(&buffer, "{s}{s}/blocks/textures/{s}.png", .{assetFolder, mod, id}); + var path = try std.fmt.bufPrint(&buffer, "{s}/{s}/blocks/textures/{s}.png", .{assetFolder, mod, id}); // Test if it's already in the list: for(textureIDs.items) |other, j| { if(std.mem.eql(u8, other, path)) { @@ -287,7 +291,10 @@ pub const meshes = struct { var file = std.fs.cwd().openFile(path, .{}) catch |err| blk: { if(err == error.FileNotFound) { path = try std.fmt.bufPrint(&buffer, "assets/{s}/blocks/textures/{s}.png", .{mod, id}); // Default to global assets. - break :blk try std.fs.cwd().openFile(path, .{}); + break :blk std.fs.cwd().openFile(path, .{}) catch |err2| { + std.log.err("File not found. Searched in \"{s}\" and also in the assetFolder \"{s}\"", .{path, assetFolder}); + return err2; + }; } else { return err; } @@ -324,11 +331,14 @@ pub const meshes = struct { const mod = splitter.first(); const id = splitter.rest(); var buffer: [1024]u8 = undefined; - var path = try std.fmt.bufPrint(&buffer, "{s}{s}/blocks/textures/{s}.png", .{assetFolder, mod, id}); + var path = try std.fmt.bufPrint(&buffer, "{s}/{s}/blocks/textures/{s}.png", .{assetFolder, mod, id}); var file = std.fs.cwd().openFile(path, .{}) catch |err| blk: { if(err == error.FileNotFound) { path = try std.fmt.bufPrint(&buffer, "assets/{s}/blocks/textures/{s}.png", .{mod, id}); // Default to global assets. - break :blk try std.fs.cwd().openFile(path, .{}); + break :blk std.fs.cwd().openFile(path, .{}) catch |err2| { + std.log.err("File not found. Searched in \"{s}\" and also in the assetFolder \"{s}\"", .{path, assetFolder}); + return err2; + }; } else { return err; } @@ -402,12 +412,12 @@ pub const meshes = struct { // } // } - pub fn generateTextureArray() void { - blockTextureArray.generate(blockTextures.items); - emissionTextureArray.generate(emissionTextures.items); + pub fn generateTextureArray() !void { + try blockTextureArray.generate(blockTextures.items); + try emissionTextureArray.generate(emissionTextures.items); // Also generate additional buffers: - animationTimesSSBO.bufferID(animationTimes.items); - animationFramesSSBO.bufferID(animationFrames.items); + animationTimesSSBO.bufferData(u32, animationTimes.items); + animationFramesSSBO.bufferData(u32, animationFrames.items); } }; diff --git a/src/chunk.zig b/src/chunk.zig index 63a449e4..5183bfba 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -12,7 +12,8 @@ const Vec3f = @import("vec.zig").Vec3f; const Vec3d = @import("vec.zig").Vec3d; const Mat4f = @import("vec.zig").Mat4f; -pub const ChunkCoordinate = u32; +pub const ChunkCoordinate = i32; +pub const UChunkCoordinate = u31; pub const chunkShift: u5 = 5; pub const chunkShift2: u5 = chunkShift*2; pub const chunkSize: ChunkCoordinate = 1 << chunkShift; @@ -35,11 +36,11 @@ pub const Neighbors = struct { /// Directions → Index pub const dirNegZ: u32 = 5; /// Index to relative position - pub const relX = [_]u32 {0, 0, 1, -1, 0, 0}; + pub const relX = [_]i32 {0, 0, 1, -1, 0, 0}; /// Index to relative position - pub const relY = [_]u32 {1, -1, 0, 0, 0, 0}; + pub const relY = [_]i32 {1, -1, 0, 0, 0, 0}; /// Index to relative position - pub const relZ = [_]u32 {0, 0, 0, 0, 1, -1}; + pub const relZ = [_]i32 {0, 0, 0, 0, 1, -1}; /// Index to bitMask for bitmap direction data pub const bitMask = [_]u6 {0x01, 0x02, 0x04, 0x08, 0x10, 0x20}; /// To iterate over all neighbors easily @@ -56,7 +57,7 @@ pub const ChunkPosition = struct { wx: ChunkCoordinate, wy: ChunkCoordinate, wz: ChunkCoordinate, - voxelSize: ChunkCoordinate, + voxelSize: UChunkCoordinate, // TODO(mabye?): // public int hashCode() { @@ -68,16 +69,17 @@ pub const ChunkPosition = struct { // int halfWidth = voxelSize * Chunk.chunkSize / 2; // return -(float) source.getPosition().distance(wx + halfWidth, wy + halfWidth, wz + halfWidth) / voxelSize; // } -// public double getMinDistanceSquared(double px, double py, double pz) { -// int halfWidth = voxelSize * Chunk.chunkSize / 2; -// double dx = Math.abs(wx + halfWidth - px); -// double dy = Math.abs(wy + halfWidth - py); -// double dz = Math.abs(wz + halfWidth - pz); -// dx = Math.max(0, dx - halfWidth); -// dy = Math.max(0, dy - halfWidth); -// dz = Math.max(0, dz - halfWidth); -// return dx*dx + dy*dy + dz*dz; -// } + + pub fn getMinDistanceSquared(self: ChunkPosition, playerPosition: Vec3d) f64 { + var halfWidth = @intToFloat(f64, self.voxelSize*@divExact(chunkSize, 2)); + var dx = @fabs(@intToFloat(f64, self.wx) + halfWidth - playerPosition.x); + var dy = @fabs(@intToFloat(f64, self.wy) + halfWidth - playerPosition.y); + var dz = @fabs(@intToFloat(f64, self.wz) + halfWidth - playerPosition.z); + dx = @maximum(0, dx - halfWidth); + dy = @maximum(0, dy - halfWidth); + dz = @maximum(0, dz - halfWidth); + return dx*dx + dy*dy + dz*dz; + } }; pub const Chunk = struct { @@ -95,17 +97,18 @@ pub const Chunk = struct { widthShift: u5, mutex: std.Thread.Mutex, - pub fn init(wx: ChunkCoordinate, wy: ChunkCoordinate, wz: ChunkCoordinate, voxelSize: ChunkCoordinate) Chunk { - std.debug.assert((voxelSize - 1 & voxelSize) == 0, "the voxel size must be a power of 2."); - std.debug.assert(wx % voxelSize == 0 and wy % voxelSize == 0 and wz % voxelSize == 0); + pub fn init(wx: ChunkCoordinate, wy: ChunkCoordinate, wz: ChunkCoordinate, voxelSize: UChunkCoordinate) Chunk { + std.debug.assert((voxelSize - 1 & voxelSize) == 0); + std.debug.assert(@mod(wx, voxelSize) == 0 and @mod(wy, voxelSize) == 0 and @mod(wz, voxelSize) == 0); + const voxelSizeShift = @intCast(u5, std.math.log2_int(UChunkCoordinate, voxelSize)); return Chunk { .pos = ChunkPosition { .wx = wx, .wy = wy, .wz = wz, .voxelSize = voxelSize }, .width = voxelSize*chunkSize, - .voxelSizeShift = @intCast(u5, std.math.log2_int(u32, voxelSize)), + .voxelSizeShift = voxelSizeShift, .voxelSizeMask = voxelSize - 1, - .widthShift = .voxelSizeShift + chunkShift, + .widthShift = voxelSizeShift + chunkShift, .mutex = std.Thread.Mutex{}, }; } @@ -373,22 +376,30 @@ pub const ChunkVisibilityData = struct { voxelSizeShift: u5, /// Finds a block in the surrounding 8 chunks using relative corrdinates. - fn getBlock(self: ChunkVisibilityData, chunks: *const [8]Chunk, x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate) Block { - var relX = x + (self.pos.wx - chunks[0].pos.wx) >> self.voxelSizeShift; - var relY = y + (self.pos.wy - chunks[0].pos.wy) >> self.voxelSizeShift; - var relZ = z + (self.pos.wz - chunks[0].pos.wz) >> self.voxelSizeShift; - var chunk = &chunks[(x >> chunkShift)*4 + (y >> chunkShift)*2 + (z >> chunkShift)]; - relX &= chunkMask; - relY &= chunkMask; - relZ &= chunkMask; + fn getBlock(self: ChunkVisibilityData, chunks: *const [8]Chunk, _x: ChunkCoordinate, _y: ChunkCoordinate, _z: ChunkCoordinate) Block { + var x = _x + (self.pos.wx - chunks[0].pos.wx) >> self.voxelSizeShift; + var y = _y + (self.pos.wy - chunks[0].pos.wy) >> self.voxelSizeShift; + var z = _z + (self.pos.wz - chunks[0].pos.wz) >> self.voxelSizeShift; + var chunk = &chunks[@intCast(usize, (x >> chunkShift)*4 + (y >> chunkShift)*2 + (z >> chunkShift))]; + x &= chunkMask; + y &= chunkMask; + z &= chunkMask; return chunk.blocks[getIndex(x, y, z)]; } - pub fn init(allocator: Allocator, pos: ChunkPosition) !ChunkVisibilityData { + pub fn initEmpty(allocator: Allocator, pos: ChunkPosition, initialCapacity: usize) !ChunkVisibilityData { + return ChunkVisibilityData { + .pos = pos, + .visibles = try std.ArrayList(VisibleBlock).initCapacity(allocator, initialCapacity), + .voxelSizeShift = std.math.log2_int(UChunkCoordinate, pos.voxelSize), + }; + } + + pub fn init(allocator: Allocator, pos: ChunkPosition, initialCapacity: usize) !ChunkVisibilityData { var self = ChunkVisibilityData { .pos = pos, - .visibles = std.ArrayList(VisibleBlock).init(allocator), - .voxelSizeShift = std.math.log2_int(pos.voxelSize), + .visibles = try std.ArrayList(VisibleBlock).initCapacity(allocator, initialCapacity), + .voxelSizeShift = std.math.log2_int(UChunkCoordinate, pos.voxelSize), }; const width = pos.voxelSize*chunkSize; @@ -415,7 +426,7 @@ pub const ChunkVisibilityData = struct { while(y < chunkSize): (y += 1) { var z: u8 = 0; while(z < chunkSize): (z += 1) { - const block = self.getBlock(chunks, x, y, z); + const block = self.getBlock(&chunks, x, y, z); if(block.typ == 0) continue; // Check all neighbors: var neighborVisibility: u8 = 0; @@ -423,7 +434,7 @@ pub const ChunkVisibilityData = struct { const x2 = x + Neighbors.relX[i]; const y2 = y + Neighbors.relY[i]; const z2 = z + Neighbors.relZ[i]; - const neighborBlock = self.getBlock(chunks, x2, y2, z2); + const neighborBlock = self.getBlock(&chunks, x2, y2, z2); var isVisible = neighborBlock.typ == 0; if(!isVisible) { // If the chunk is at a border, more neighbors need to be checked to prevent cracks at LOD changes: @@ -433,7 +444,7 @@ pub const ChunkVisibilityData = struct { const x3 = x2 + Neighbors.relX[j]; const y3 = y2 + Neighbors.relY[j]; const z3 = z2 + Neighbors.relZ[j]; - if(self.getBlock(chunks, x3, y3, z3) == 0) { + if(self.getBlock(&chunks, x3, y3, z3).typ == 0) { isVisible = true; break; } @@ -559,7 +570,7 @@ pub const meshing = struct { size: ChunkCoordinate, replacement: ?*ChunkMesh, faceData: SSBO, - vertexCount: u32, + vertexCount: c_int = 0, generated: bool = false, pub fn init(pos: ChunkPosition, replacement: ?*ChunkMesh) ChunkMesh { @@ -575,11 +586,11 @@ pub const meshing = struct { self.faceData.deinit(); } - pub fn regenerateMesh(self: *ChunkMesh, visDat: ChunkVisibilityData) void { + pub fn regenerateMesh(self: *ChunkMesh, visDat: *ChunkVisibilityData) !void { self.generated = true; faces.clearRetainingCapacity(); - faces.add(visDat.voxelSizeShift); + try faces.append(visDat.pos.voxelSize); for(visDat.visibles.items) |visible| { const block = visible.block; @@ -588,49 +599,49 @@ pub const meshing = struct { const z = visible.z; if(visible.neighbors & Neighbors.bitMask[Neighbors.dirNegX] != 0) { const normal: u32 = 0; - const position: u32 = x | y << 6 | z << 12; + const position: u32 = @as(u32, x) | @as(u32, y) << 6 | @as(u32, z) << 12; const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirNegX] | (normal << 24); try faces.append(position); try faces.append(textureNormal); } if(visible.neighbors & Neighbors.bitMask[Neighbors.dirPosX] != 0) { const normal: u32 = 1; - const position: u32 = x+1 | y << 6 | z << 12; + const position: u32 = @as(u32, x+1) | @as(u32, y) << 6 | @as(u32, z) << 12; const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirPosX] | (normal << 24); try faces.append(position); try faces.append(textureNormal); } if(visible.neighbors & Neighbors.bitMask[Neighbors.dirDown] != 0) { const normal: u32 = 4; - const position: u32 = x | y << 6 | z << 12; + const position: u32 = @as(u32, x) | @as(u32, y) << 6 | @as(u32, z) << 12; const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirDown] | (normal << 24); try faces.append(position); try faces.append(textureNormal); } if(visible.neighbors & Neighbors.bitMask[Neighbors.dirUp] != 0) { const normal: u32 = 5; - const position: u32 = x | (y+1) << 6 | z << 12; + const position: u32 = @as(u32, x) | @as(u32, y+1) << 6 | @as(u32, z) << 12; const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirUp] | (normal << 24); try faces.append(position); try faces.append(textureNormal); } if(visible.neighbors & Neighbors.bitMask[Neighbors.dirNegZ] != 0) { const normal: u32 = 2; - const position: u32 = x | y << 6 | z << 12; + const position: u32 = @as(u32, x) | @as(u32, y) << 6 | @as(u32, z) << 12; const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirNegZ] | (normal << 24); try faces.append(position); try faces.append(textureNormal); } if(visible.neighbors & Neighbors.bitMask[Neighbors.dirPosZ] != 0) { const normal: u32 = 3; - const position: u32 = x | y << 6 | (z + 1) << 12; + const position: u32 = @as(u32, x) | @as(u32, y) << 6 | @as(u32, z+1) << 12; const textureNormal = blocks.meshes.textureIndices(block)[Neighbors.dirPosZ] | (normal << 24); try faces.append(position); try faces.append(textureNormal); } } - self.vertexCount = 6*(faces.size-1)/2; + self.vertexCount = @intCast(c_int, 6*(faces.items.len-1)/2); self.faceData.bufferData(u32, faces.items); } @@ -639,15 +650,15 @@ pub const meshing = struct { if(self.replacement == null) return; c.glUniform3f( uniforms.lowerBounds, - @floatCast(f32, self.pos.wx - playerPosition.x - 0.001), - @floatCast(f32, self.pos.wy - playerPosition.y - 0.001), - @floatCast(f32, self.pos.wz - playerPosition.z - 0.001) + @floatCast(f32, @intToFloat(f64, self.pos.wx) - playerPosition.x - 0.001), + @floatCast(f32, @intToFloat(f64, self.pos.wy) - playerPosition.y - 0.001), + @floatCast(f32, @intToFloat(f64, self.pos.wz) - playerPosition.z - 0.001) ); c.glUniform3f( uniforms.upperBounds, - @floatCast(f32, self.pos.wx + self.size - playerPosition.x + 0.001), - @floatCast(f32, self.pos.wy + self.size - playerPosition.y + 0.001), - @floatCast(f32, self.pos.wz + self.size - playerPosition.z + 0.001) + @floatCast(f32, @intToFloat(f64, self.pos.wx + self.size) - playerPosition.x + 0.001), + @floatCast(f32, @intToFloat(f64, self.pos.wy + self.size) - playerPosition.y + 0.001), + @floatCast(f32, @intToFloat(f64, self.pos.wz + self.size) - playerPosition.z + 0.001) ); self.replacement.?.render(playerPosition); @@ -656,11 +667,12 @@ pub const meshing = struct { c.glUniform3f(uniforms.upperBounds, std.math.inf_f32, std.math.inf_f32, std.math.inf_f32); return; } + if(self.vertexCount == 0) return; c.glUniform3f( uniforms.modelPosition, - @floatCast(f32, self.pos.wx - playerPosition.x), - @floatCast(f32, self.pos.wy - playerPosition.y), - @floatCast(f32, self.pos.wz - playerPosition.z) + @floatCast(f32, @intToFloat(f64, self.pos.wx) - playerPosition.x), + @floatCast(f32, @intToFloat(f64, self.pos.wy) - playerPosition.y), + @floatCast(f32, @intToFloat(f64, self.pos.wz) - playerPosition.z) ); self.faceData.bind(3); c.glDrawElements(c.GL_TRIANGLES, self.vertexCount, c.GL_UNSIGNED_INT, null); diff --git a/src/game.zig b/src/game.zig index 4839265e..baaa0bc6 100644 --- a/src/game.zig +++ b/src/game.zig @@ -1,5 +1,6 @@ const std = @import("std"); +const assets = @import("assets.zig"); const vec = @import("vec.zig"); const Vec3f = vec.Vec3f; const Mat4f = vec.Mat4f; @@ -29,6 +30,7 @@ pub const camera = struct { } }; +pub var blockPalette: *assets.BlockPalette = undefined; pub const World = u1; // TODO pub var testWorld: World = 0; pub var world: ?*World = &testWorld; diff --git a/src/graphics.zig b/src/graphics.zig index 9ae678fa..924097cd 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -418,9 +418,9 @@ pub const SSBO = struct { c.glBindBufferBase(c.GL_SHADER_STORAGE_BUFFER, binding, self.bufferID); } - pub fn bufferData(self: SSBO, T: type, data: []T) void { + pub fn bufferData(self: SSBO, comptime T: type, data: []T) void { c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, self.bufferID); - c.glBufferData(c.GL_SHADER_STORAGE_BUFFER, data.len*@sizeOf(T), data.ptr, c.GL_STATIC_DRAW); + c.glBufferData(c.GL_SHADER_STORAGE_BUFFER, @intCast(c_long, data.len*@sizeOf(T)), data.ptr, c.GL_STATIC_DRAW); c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, 0); } }; @@ -442,29 +442,29 @@ pub const TextureArray = struct { c.glBindTexture(c.GL_TEXTURE_2D_ARRAY, self.textureID); } - fn lodColorInterpolation(colors: [4]u32, isTransparent: bool) u32 { + fn lodColorInterpolation(colors: [4]Color, isTransparent: bool) Color { var r: [4]u32 = undefined; var g: [4]u32 = undefined; var b: [4]u32 = undefined; var a: [4]u32 = undefined; for(colors) |_, i| { - r[i] = colors[i]>>24; - g[i] = colors[i]>>16 & 0xFF; - b[i] = colors[i]>>8 & 0xFF; - a[i] = colors[i] & 0xFF; + r[i] = colors[i].r; + g[i] = colors[i].g; + b[i] = colors[i].b; + a[i] = colors[i].a; } // Use gamma corrected average(https://stackoverflow.com/a/832314/13082649): - var aSum = 0; - var rSum = 0; - var gSum = 0; - var bSum = 0; + var aSum: u32 = 0; + var rSum: u32 = 0; + var gSum: u32 = 0; + var bSum: u32 = 0; for(colors) |_, i| { aSum += a[i]*a[i]; rSum += r[i]*r[i]; gSum += g[i]*g[i]; bSum += b[i]*b[i]; } - aSum = @floatToInt(u32, @round(@sqrt(@intToFloat(f32, aSum)))); + aSum = @floatToInt(u32, @round(@sqrt(@intToFloat(f32, aSum)))/2); if(!isTransparent) { if(aSum < 128) { aSum = 0; @@ -472,47 +472,47 @@ pub const TextureArray = struct { aSum = 255; } } - rSum = @floatToInt(u32, @round(@sqrt(@intToFloat(f32, rSum)))); - gSum = @floatToInt(u32, @round(@sqrt(@intToFloat(f32, gSum)))); - bSum = @floatToInt(u32, @round(@sqrt(@intToFloat(f32, bSum)))); - return rSum<<24 | gSum<<16 | bSum<<8 | aSum; + rSum = @floatToInt(u32, @round(@sqrt(@intToFloat(f32, rSum)))/2); + gSum = @floatToInt(u32, @round(@sqrt(@intToFloat(f32, gSum)))/2); + bSum = @floatToInt(u32, @round(@sqrt(@intToFloat(f32, bSum)))/2); + return Color{.r=@intCast(u8, rSum), .g=@intCast(u8, gSum), .b=@intCast(u8, bSum), .a=@intCast(u8, aSum)}; } /// (Re-)Generates the GPU buffer. - pub fn generate(self: TextureArray, images: []Image) void { - var maxWidth: u32 = 0; - var maxHeight: u32 = 0; + pub fn generate(self: TextureArray, images: []Image) !void { + var maxWidth: u31 = 0; + var maxHeight: u31 = 0; for(images) |image| { maxWidth = @maximum(maxWidth, image.width); maxHeight = @maximum(maxHeight, image.height); } // Make sure the width and height use a power of 2: if(maxWidth-1 & maxWidth != 0) { - maxWidth = 2 << std.math.log2_int(u32, maxWidth); + maxWidth = @as(u31, 2) << std.math.log2_int(u31, maxWidth); } if(maxHeight-1 & maxHeight != 0) { - maxHeight = 2 << std.math.log2_int(u32, maxHeight); + maxHeight = @as(u31, 2) << std.math.log2_int(u31, maxHeight); } std.log.debug("Creating Texture Array of size {}×{} with {} layers.", .{maxWidth, maxHeight, images.len}); self.bind(); - const maxLOD = 1 + std.mat.log2_int(@minimum(maxWidth, maxHeight)); - c.glTexStorage3D(c.GL_TEXTURE_2D_ARRAY, maxLOD, c.GL_RGBA8, maxWidth, maxHeight, images.len); + const maxLOD = 1 + std.math.log2_int(u31, @minimum(maxWidth, maxHeight)); + c.glTexStorage3D(c.GL_TEXTURE_2D_ARRAY, maxLOD, c.GL_RGBA8, maxWidth, maxHeight, @intCast(c_int, images.len)); var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); - var lodBuffer: [][]u32 = try arena.allocator().alloc([]u32, maxLOD); + var lodBuffer: [][]Color = try arena.allocator().alloc([]Color, maxLOD); for(lodBuffer) |*buffer, i| { - buffer.* = try arena.allocator().alloc(u32, (maxWidth >> i)*(maxHeight >> i)); + buffer.* = try arena.allocator().alloc(Color, (maxWidth >> @intCast(u5, i))*(maxHeight >> @intCast(u5, i))); } for(images) |image, i| { // Check if the image contains non-binary alpha values, which makes it transparent. var isTransparent = false; for(image.imageData) |color| { - if(color >> 24 != 0 or color >> 24 != 255) { - isTransparent[i] = true; + if(color.a != 0 or color.a != 255) { + isTransparent = true; break; } } @@ -524,14 +524,13 @@ pub const TextureArray = struct { while(y < maxHeight): (y += 1) { const index = x + y*maxWidth; const imageIndex = (x*image.width)/maxWidth + image.width*(y*image.height)/maxHeight; - const argb = image.imageData[imageIndex]; - const rgba = argb<<8 | argb>>24; - lodBuffer[0][index] = rgba; + lodBuffer[0][index] = image.imageData[imageIndex]; } } // Calculate the mipmap levels: - for(lodBuffer) |_, lod| { + for(lodBuffer) |_, _lod| { + const lod = @intCast(u5, _lod); const curWidth = maxWidth >> lod; const curHeight = maxHeight >> lod; if(lod != 0) { @@ -541,7 +540,7 @@ pub const TextureArray = struct { while(y < curHeight): (y += 1) { const index = x + y*curWidth; const index2 = 2*x + 2*y*2*curWidth; - const colors = [4]u32 { + const colors = [4]Color { lodBuffer[lod-1][index2], lodBuffer[lod-1][index2 + 1], lodBuffer[lod-1][index2 + curWidth*2], @@ -551,7 +550,7 @@ pub const TextureArray = struct { } } } - c.glTexSubImage3D(c.GL_TEXTURE_2D_ARRAY, lod, 0, 0, i, curWidth, curHeight, 1, c.GL_RGBA, c.GL_UNSIGNED_BYTE, &lodBuffer[lod]); + c.glTexSubImage3D(c.GL_TEXTURE_2D_ARRAY, lod, 0, 0, @intCast(c_int, i), curWidth, curHeight, 1, c.GL_RGBA, c.GL_UNSIGNED_BYTE, lodBuffer[lod].ptr); } } c.glTexParameteri(c.GL_TEXTURE_2D, c.GL_TEXTURE_MAX_LOD, maxLOD); @@ -564,10 +563,17 @@ pub const TextureArray = struct { } }; +pub const Color = packed struct(u32) { + r: u8, + g: u8, + b: u8, + a: u8 +}; + pub const Image = struct { - width: u32, - height: u32, - imageData: []u32, + width: u31, + height: u31, + imageData: []Color, pub fn readFromFile(allocator: Allocator, path: []const u8) !Image { var result: Image = undefined; var channel: c_int = undefined; @@ -576,7 +582,9 @@ pub const Image = struct { const data = stb_image.stbi_load(nullTerminatedPath.ptr, @ptrCast([*c]c_int, &result.width), @ptrCast([*c]c_int, &result.height), &channel, 4) orelse { return error.FileNotFound; }; - result.imageData = try allocator.dupe(u32, @ptrCast([*]u32, data)[0..result.width*result.height]); + std.log.info("Image sample direct: {} {} {} {}", .{data[0], data[1], data[2], data[3]}); + result.imageData = try allocator.dupe(Color, @ptrCast([*]Color, data)[0..result.width*result.height]); + std.log.info("Image sample: {} {} {} {}", .{result.imageData[0].r, result.imageData[0].g, result.imageData[0].b, result.imageData[0].a}); stb_image.stbi_image_free(data); return result; } diff --git a/src/main.zig b/src/main.zig index 1a7425ee..0ad7b605 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3,6 +3,7 @@ const std = @import("std"); const assets = @import("assets.zig"); const blocks = @import("blocks.zig"); const chunk = @import("chunk.zig"); +const game = @import("game.zig"); const graphics = @import("graphics.zig"); const renderer = @import("renderer.zig"); const network = @import("network.zig"); @@ -36,13 +37,9 @@ pub fn log( std.debug.getStderrMutex().lock(); defer std.debug.getStderrMutex().unlock(); - const fileMessage = std.fmt.allocPrint(threadAllocator, "[" ++ level.asText() ++ "]" ++ ": " ++ format ++ "\n", args) catch return; - defer threadAllocator.free(fileMessage); - logFile.writeAll(fileMessage) catch return; + logFile.writer().print("[" ++ level.asText() ++ "]" ++ ": " ++ format ++ "\n", args) catch {}; - const terminalMessage = std.fmt.allocPrint(threadAllocator, color ++ format ++ "\x1b[0m\n", args) catch return; - defer threadAllocator.free(terminalMessage); - nosuspend std.io.getStdErr().writeAll(terminalMessage) catch return; + nosuspend std.io.getStdErr().writer().print(color ++ format ++ "\x1b[0m\n", args) catch {}; } pub const Window = struct { @@ -136,7 +133,7 @@ pub const Window = struct { }; pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + var gpa = std.heap.GeneralPurposeAllocator(.{.thread_safe=false}){}; threadAllocator = gpa.allocator(); defer if(gpa.deinit()) { @panic("Memory leak"); @@ -147,7 +144,6 @@ pub fn main() !void { defer logFile.close(); var poolgpa = std.heap.GeneralPurposeAllocator(.{}){}; - threadAllocator = gpa.allocator(); defer if(poolgpa.deinit()) { @panic("Memory leak"); }; @@ -172,8 +168,6 @@ pub fn main() !void { try renderer.init(); defer renderer.deinit(); - try assets.loadWorldAssets("saves"); - network.init(); var conn = try network.ConnectionManager.init(12347, true); @@ -182,9 +176,15 @@ pub fn main() !void { var conn2 = try network.Connection.init(conn, "127.0.0.1"); defer conn2.deinit(); + try renderer.RenderOctree.init(); + defer renderer.RenderOctree.deinit(); + try network.Protocols.handShake.clientSide(conn2, "quanturmdoelvloper"); - c.glEnable(c.GL_CULL_FACE); + try assets.loadWorldAssets("serverAssets", game.blockPalette); + + try blocks.meshes.generateTextureArray(); + c.glCullFace(c.GL_BACK); c.glEnable(c.GL_BLEND); c.glBlendFunc(c.GL_SRC_ALPHA, c.GL_ONE_MINUS_SRC_ALPHA); @@ -198,19 +198,21 @@ pub fn main() !void { } c.glfwSwapBuffers(Window.window); c.glfwPollEvents(); + try renderer.RenderOctree.update(conn2, .{.x = 25, .y = 11, .z = -703}, 4, 2.0); { // Render the game + c.glEnable(c.GL_CULL_FACE); c.glEnable(c.GL_DEPTH_TEST); - renderer.render(.{.x = 0, .y = 0, .z = 0}); + try renderer.render(.{.x = 25, .y = 11, .z = -703}); } { // Render the GUI c.glDisable(c.GL_DEPTH_TEST); - graphics.Draw.setColor(0xff0000ff); - graphics.Draw.rect(Vec2f{.x = 100, .y = 100}, Vec2f{.x = 200, .y = 100}); - graphics.Draw.circle(Vec2f{.x = 200, .y = 200}, 59); - graphics.Draw.setColor(0xffff00ff); - graphics.Draw.line(Vec2f{.x = 0, .y = 0}, Vec2f{.x = 1920, .y = 1080}); + //graphics.Draw.setColor(0xff0000ff); + //graphics.Draw.rect(Vec2f{.x = 100, .y = 100}, Vec2f{.x = 200, .y = 100}); + //graphics.Draw.circle(Vec2f{.x = 200, .y = 200}, 59); + //graphics.Draw.setColor(0xffff00ff); + //graphics.Draw.line(Vec2f{.x = 0, .y = 0}, Vec2f{.x = 1920, .y = 1080}); } } } diff --git a/src/network.zig b/src/network.zig index 55a6b188..e047e904 100644 --- a/src/network.zig +++ b/src/network.zig @@ -1,12 +1,14 @@ const std = @import("std"); const Allocator = std.mem.Allocator; +const assets = @import("assets.zig"); const chunk = @import("chunk.zig"); const main = @import("main.zig"); const game = @import("game.zig"); const settings = @import("settings.zig"); const json = @import("json.zig"); const JsonElement = json.JsonElement; +const renderer = @import("renderer.zig"); const utils = @import("utils.zig"); //TODO: Might want to use SSL or something similar to encode the message @@ -321,6 +323,7 @@ pub const ConnectionManager = struct { allocator: std.mem.Allocator = undefined, mutex: std.Thread.Mutex = std.Thread.Mutex{}, + waitingToFinishReceive: std.Thread.Condition = std.Thread.Condition{}, receiveBuffer: [Connection.maxPacketSize]u8 = undefined, @@ -343,13 +346,13 @@ pub const ConnectionManager = struct { } pub fn deinit(self: *ConnectionManager) void { - self.running.store(false, .Monotonic); - self.thread.join(); - Socket.deinit(self.socket); - for(self.connections.items) |conn| { conn.disconnect() catch |err| {std.log.warn("Error while disconnecting: {s}", .{@errorName(err)});}; } + + self.running.store(false, .Monotonic); + self.thread.join(); + Socket.deinit(self.socket); self.connections.deinit(); for(self.requests.items) |request| { request.requestNotifier.signal(); @@ -413,6 +416,12 @@ pub const ConnectionManager = struct { try self.connections.append(conn); } + pub fn finishCurrentReceive(self: *ConnectionManager) void { + self.mutex.lock(); + defer self.mutex.unlock(); + self.waitingToFinishReceive.wait(&self.mutex); + } + pub fn removeConnection(self: *ConnectionManager, conn: *Connection) void { self.mutex.lock(); defer self.mutex.unlock(); @@ -459,7 +468,7 @@ pub const ConnectionManager = struct { pub fn run(self: *ConnectionManager) !void { self.threadId = std.Thread.getCurrentId(); - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + var gpa = std.heap.GeneralPurposeAllocator(.{.thread_safe=false}){}; main.threadAllocator = gpa.allocator(); defer if(gpa.deinit()) { @panic("Memory leak"); @@ -467,6 +476,7 @@ pub const ConnectionManager = struct { var lastTime = std.time.milliTimestamp(); while(self.running.load(.Monotonic)) { + self.waitingToFinishReceive.broadcast(); var source: Address = undefined; if(self.socket.receive(&self.receiveBuffer, 100, &source)) |data| { try self.onReceive(data, source); @@ -598,6 +608,9 @@ pub const Protocols = blk: { var jsonObject = json.parseFromString(main.threadAllocator, data[1..]); defer jsonObject.free(main.threadAllocator); // TODO: ((ServerConnection)conn).world.finishHandshake(json); + {// TODO: Move this into world loading finishHandshake(). + game.blockPalette = try assets.BlockPalette.init(renderer.RenderOctree.allocator, jsonObject.getChild("blockPalette")); // TODO: Use the world allocator and free this. + } conn.handShakeState = stepComplete; conn.handShakeWaiting.broadcast(); // Notify the waiting client thread. }, @@ -641,10 +654,10 @@ pub const Protocols = blk: { var remaining = data[0..]; while(remaining.len >= 16) { const request = chunk.ChunkPosition{ - .wx = std.mem.readIntBig(chunk.ChunkCoordinate, data[0..4]), - .wy = std.mem.readIntBig(chunk.ChunkCoordinate, data[4..8]), - .wz = std.mem.readIntBig(chunk.ChunkCoordinate, data[8..12]), - .voxelSize = std.mem.readIntBig(chunk.ChunkCoordinate, data[12..16]), + .wx = std.mem.readIntBig(chunk.ChunkCoordinate, remaining[0..4]), + .wy = std.mem.readIntBig(chunk.ChunkCoordinate, remaining[4..8]), + .wz = std.mem.readIntBig(chunk.ChunkCoordinate, remaining[8..12]), + .voxelSize = @intCast(chunk.UChunkCoordinate, std.mem.readIntBig(chunk.ChunkCoordinate, remaining[12..16])), }; _ = request; _ = conn; @@ -654,19 +667,128 @@ pub const Protocols = blk: { } pub fn sendRequest(conn: *Connection, requests: []chunk.ChunkPosition) !void { if(requests.len == 0) return; - var data = main.threadAllocator.alloc(16*requests.len); + var data = try main.threadAllocator.alloc(u8, 16*requests.len); defer main.threadAllocator.free(data); var remaining = data; for(requests) |req| { - std.mem.writeIntBig(chunk.ChunkCoordinate, data[0..4], req.wx); - std.mem.writeIntBig(chunk.ChunkCoordinate, data[4..8], req.wy); - std.mem.writeIntBig(chunk.ChunkCoordinate, data[8..12], req.wz); - std.mem.writeIntBig(chunk.ChunkCoordinate, data[12..16], req.voxelSize); + std.mem.writeIntBig(chunk.ChunkCoordinate, remaining[0..4], req.wx); + std.mem.writeIntBig(chunk.ChunkCoordinate, remaining[4..8], req.wy); + std.mem.writeIntBig(chunk.ChunkCoordinate, remaining[8..12], req.wz); + std.mem.writeIntBig(chunk.ChunkCoordinate, remaining[12..16], req.voxelSize); + std.log.info("Requested some chunk: {}", .{req.voxelSize}); remaining = remaining[16..]; } - conn.sendImportant(id, data); + try conn.sendImportant(id, data); } }), + chunkTransmission: type = addProtocol(&comptimeList, struct { + const id: u8 = 3; + fn receive(_: *Connection, _data: []const u8) !void { + var data = _data; + var pos = chunk.ChunkPosition{ + .wx = std.mem.readIntBig(chunk.ChunkCoordinate, data[0..4]), + .wy = std.mem.readIntBig(chunk.ChunkCoordinate, data[4..8]), + .wz = std.mem.readIntBig(chunk.ChunkCoordinate, data[8..12]), + .voxelSize = @intCast(chunk.UChunkCoordinate, std.mem.readIntBig(chunk.ChunkCoordinate, data[12..16])), + }; + data = data[16..]; + if(pos.voxelSize == 1) { + // TODO: +// byte[] chunkData = ChunkIO.decompressChunk(data, offset, length); +// if(chunkData == null) +// return; +// VisibleChunk ch = new VisibleChunk(Cubyz.world, wx, wy, wz); +// ch.loadFromByteArray(chunkData, chunkData.length); +// ThreadPool.addTask(new ChunkLoadTask(ch)); + } else { + data = try utils.Compression.inflate(main.threadAllocator, data); + defer main.threadAllocator.free(data); + var size = @divExact(data.len, 8); + var x = data[0..size]; + var y = data[size..2*size]; + var z = data[2*size..3*size]; + var neighbors = data[3*size..4*size]; + var visibleBlocks = data[4*size..]; + var result = try renderer.RenderOctree.allocator.create(chunk.ChunkVisibilityData); + result.* = try chunk.ChunkVisibilityData.initEmpty(renderer.RenderOctree.allocator, pos, size); + for(x) |_, i| { + var block = result.visibles.addOneAssumeCapacity(); + block.x = x[i]; + block.y = y[i]; + block.z = z[i]; + block.neighbors = neighbors[i]; + var blockTypeAndData = std.mem.readIntBig(u32, visibleBlocks[4*i..][0..4]); + block.block.typ = @intCast(u16, blockTypeAndData & 0xffff); + block.block.data = @intCast(u16, blockTypeAndData >> 16); + } + try renderer.RenderOctree.updateChunkMesh(result); + } + } + pub fn sendChunk(conn: *Connection, visData: chunk.ChunkVisibilityData) !void { + var data = try main.threadAllocator.alloc(u8, 16 + 8*visData.visibles.items.len); + defer main.threadAllocator.free(data); + std.mem.writeIntBig(chunk.ChunkCoordinate, data[0..4], visData.pos.wx); + std.mem.writeIntBig(chunk.ChunkCoordinate, data[4..8], visData.pos.wy); + std.mem.writeIntBig(chunk.ChunkCoordinate, data[8..12], visData.pos.wz); + std.mem.writeIntBig(chunk.ChunkCoordinate, data[12..16], visData.pos.voxelSize); + var size = visData.visibles.items.len; + var x = data[16..][0..size]; + var y = data[16..][size..2*size]; + var z = data[16..][2*size..3*size]; + var neighbors = data[16..][3*size..4*size]; + var visibleBlocks = data[16..][4*size..]; + for(visData.visibles.items) |block, i| { + x[i] = block.x; + y[i] = block.y; + z[i] = block.z; + neighbors[i] = block.neighbors; + var blockTypeAndData = @as(u32, block.block.data) << 16 | block.block.typ; + std.mem.writeIntBig(u32, visibleBlocks[4*i..][0..4], blockTypeAndData); + } + + var compressed = try utils.Compression.deflate(main.threadAllocator, data); + defer main.threadAllocator.free(compressed); + try conn.sendImportant(id, compressed); + } + // TODO: +// public void sendChunk(UDPConnection conn, ChunkData ch) { +// byte[] data; +// if(ch instanceof NormalChunk) { +// byte[] compressedChunk = ChunkIO.compressChunk((NormalChunk)ch); +// data = new byte[compressedChunk.length + 16]; +// System.arraycopy(compressedChunk, 0, data, 16, compressedChunk.length); +// } else { +// assert false: "Invalid chunk class to send over the network " + ch.getClass() + "."; +// return; +// } +// Bits.putInt(data, 0, ch.wx); +// Bits.putInt(data, 4, ch.wy); +// Bits.putInt(data, 8, ch.wz); +// Bits.putInt(data, 12, ch.voxelSize); +// conn.sendImportant(this, data); +// } +// +// private static class ChunkLoadTask extends ThreadPool.Task { +// private final VisibleChunk ch; +// public ChunkLoadTask(VisibleChunk ch) { +// this.ch = ch; +// } +// @Override +// public float getPriority() { +// return ch.getPriority(Cubyz.player); +// } +// +// @Override +// public boolean isStillNeeded() { +// return Cubyz.chunkTree.findNode(ch) != null; +// } +// +// @Override +// public void run() { +// ch.load(); +// } +// } + }), disconnect: type = addProtocol(&comptimeList, struct { const id: u8 = 5; fn receive(conn: *Connection, _: []const u8) !void { @@ -779,6 +901,10 @@ pub const Connection = struct { pub fn deinit(self: *Connection) void { self.disconnect() catch |err| {std.log.warn("Error while disconnecting: {s}", .{@errorName(err)});}; + self.manager.finishCurrentReceive(); // Wait until all currently received packets are done. + for(self.unconfirmedPackets.items) |packet| { + self.allocator.free(packet.data); + } self.unconfirmedPackets.deinit(); self.receivedPackets[0].deinit(); self.receivedPackets[1].deinit(); diff --git a/src/renderer.zig b/src/renderer.zig index a9955428..5e31682f 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -13,7 +13,10 @@ const Mat4f = vec.Mat4f; const game = @import("game.zig"); const World = game.World; const chunk = @import("chunk.zig"); -const Window = @import("main.zig").Window; +const main = @import("main.zig"); +const network = @import("network.zig"); +const settings = @import("settings.zig"); +const Window = main.Window; /// The number of milliseconds after which no more chunk meshes are created. This allows the game to run smoother on movement. const maximumMeshTime = 12; @@ -139,7 +142,7 @@ pub fn updateViewport(width: u31, height: u31, fov: f32) void { buffers.updateBufferSize(width, height); } -pub fn render(playerPosition: Vec3d) void { +pub fn render(playerPosition: Vec3d) !void { var startTime = std.time.milliTimestamp(); // TODO: BlockMeshes.loadMeshes(); // Loads all meshes that weren't loaded yet // if (Cubyz.player != null) { @@ -188,18 +191,11 @@ pub fn render(playerPosition: Vec3d) void { // // Set intensity: // light.setDirection(light.getDirection().mul(0.1f*Cubyz.world.getGlobalLighting()/light.getDirection().length())); // Window.setClearColor(clearColor); - renderWorld(world, Vec3f{.x=0, .y=0, .z=0}, Vec3f{.x=0, .y=1, .z=0.5}, playerPosition); -// TODO: - _ = startTime; -// // Update meshes: -// do { // A do while loop is used so even when the framerate is low at least one mesh gets updated per frame. -// ChunkMesh mesh = Meshes.getNextQueuedMesh(); -// if (mesh == null) break; -// mesh.regenerateMesh(); -// } while (System.currentTimeMillis() - startTime <= maximumMeshTime); + try renderWorld(world, Vec3f{.x=1, .y=1, .z=1}, Vec3f{.x=0.3, .y=-1, .z=0.5}, playerPosition); + try RenderOctree.updateMeshes(startTime + maximumMeshTime); } else { // clearColor.y = clearColor.z = 0.7f; -// clearColor.x = 0.1f; +// clearColor.x = 0.1f;@import("main.zig") // // Window.setClearColor(clearColor); // @@ -209,9 +205,8 @@ pub fn render(playerPosition: Vec3d) void { // Keyboard.release(); // TODO: Why is this called in the render thread??? } -pub fn renderWorld(world: *World, ambientLight: Vec3f, directionalLight: Vec3f, playerPosition: Vec3d) void { +pub fn renderWorld(world: *World, ambientLight: Vec3f, directionalLight: Vec3f, playerPos: Vec3d) !void { _ = world; - _ = playerPosition; // TODO buffers.bind(); buffers.clearAndBind(Vec4f{.x=0, .y=1, .z=0.5, .w=1}); // TODO:// Clean up old chunk meshes: @@ -228,7 +223,7 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, directionalLight: Vec3f, const waterFog = Fog{.active=true, .color=.{.x=0.0, .y=0.1, .z=0.2}, .density=0.1}; // Update the uniforms. The uniforms are needed to render the replacement meshes. - chunk.meshing.bindShaderAndUniforms(game.projectionMatrix, ambientLight, directionalLight, time); + chunk.meshing.bindShaderAndUniforms(game.lodProjectionMatrix, ambientLight, directionalLight, time); //TODO: NormalChunkMesh.bindShader(ambientLight, directionalLight.getDirection(), time); @@ -246,6 +241,9 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, directionalLight: Vec3f, // SimpleList visibleChunks = new SimpleList(new NormalChunkMesh[64]); // SimpleList visibleReduced = new SimpleList(new ReducedChunkMesh[64]); + var meshes = std.ArrayList(*chunk.meshing.ChunkMesh).init(main.threadAllocator); + defer meshes.deinit(); + try RenderOctree.getRenderChunks(playerPos, &meshes); // for (ChunkMesh mesh : Cubyz.chunkTree.getRenderChunks(frustumInt, x0, y0, z0)) { // if (mesh instanceof NormalChunkMesh) { // visibleChunks.add((NormalChunkMesh)mesh); @@ -270,6 +268,10 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, directionalLight: Vec3f, c.glUniform3fv(chunk.meshing.uniforms.waterFog_color, 1, @ptrCast([*c]f32, &waterFog.color)); c.glUniform1f(chunk.meshing.uniforms.waterFog_density, waterFog.density); + for(meshes.items) |mesh| { + mesh.render(playerPos); + } + // for(int i = 0; i < visibleReduced.size; i++) { // ReducedChunkMesh mesh = visibleReduced.array[i]; // mesh.render(playerPosition); @@ -391,4 +393,319 @@ pub fn renderWorld(world: *World, ambientLight: Vec3f, directionalLight: Vec3f, // } // return output; // } -//} \ No newline at end of file +//} + +pub const RenderOctree = struct { + pub var allocator: std.mem.Allocator = undefined; + var gpa: std.heap.GeneralPurposeAllocator(.{}) = undefined; + pub const Node = struct { + shouldBeRemoved: bool = false, + children: ?[]*Node = null, + size: chunk.ChunkCoordinate, + mesh: chunk.meshing.ChunkMesh, + mutex: std.Thread.Mutex = std.Thread.Mutex{}, + + fn init(replacement: ?*chunk.meshing.ChunkMesh, pos: chunk.ChunkPosition, size: chunk.ChunkCoordinate, meshRequests: *std.ArrayList(chunk.ChunkPosition)) !*Node { + var self = try allocator.create(Node); + self.* = Node { + .size = size, + .mesh = chunk.meshing.ChunkMesh.init(pos, replacement), + }; + try meshRequests.append(pos); + std.debug.assert(pos.voxelSize & pos.voxelSize-1 == 0); + return self; + } + + fn deinit(self: *Node) void { + if(self.children) |children| { + for(children) |child| { + child.deinit(); + } + allocator.free(children); + } + self.mesh.deinit(); + allocator.destroy(self); + } + + fn update(self: *Node, playerPos: Vec3d, renderDistance: i32, maxRD: i32, minHeight: i32, maxHeight: i32, nearRenderDistance: i32, meshRequests: *std.ArrayList(chunk.ChunkPosition)) !void { + self.mutex.lock(); + defer self.mutex.unlock(); + // Calculate the minimum distance between this chunk and the player: + var minDist = self.mesh.pos.getMinDistanceSquared(playerPos); + // Check if this chunk is outside the nearRenderDistance or outside the height limits: + // if (wy + size <= Cubyz.world.chunkManager.getOrGenerateMapFragment(x, z, 32).getMinHeight() || wy > Cubyz.world.chunkManager.getOrGenerateMapFragment(x, z, 32).getMaxHeight()) { + if(self.mesh.pos.wy + self.size <= 0 or self.mesh.pos.wy > 1024) { + if(minDist > @intToFloat(f64, nearRenderDistance*nearRenderDistance)) { + if(self.children) |children| { + for(children) |child| { + child.deinit(); + } + allocator.free(children); + self.children = null; + } + return; + } + } + // Check if parts of this OctTree require using normal chunks: + if(self.size == chunk.chunkSize*2 and minDist < @intToFloat(f64, renderDistance*renderDistance)) { + if(self.children == null) { + self.children = try allocator.alloc(*Node, 8); + for(self.children.?) |*child, i| { + child.* = try Node.init(&self.mesh, chunk.ChunkPosition{ + .wx = self.mesh.pos.wx + (if(i & 1 == 0) 0 else @divFloor(self.size, 2)), + .wy = self.mesh.pos.wy + (if(i & 2 == 0) 0 else @divFloor(self.size, 2)), + .wz = self.mesh.pos.wz + (if(i & 4 == 0) 0 else @divFloor(self.size, 2)), + .voxelSize = @divFloor(self.mesh.pos.voxelSize, 2), + }, @divFloor(self.size, 2), meshRequests); + } + } + for(self.children.?) |child| { + try child.update(playerPos, renderDistance, @divFloor(maxRD, 2), minHeight, maxHeight, nearRenderDistance, meshRequests); + } + // Check if parts of this OctTree require a higher resolution: + } else if(minDist < @intToFloat(f64, maxRD*maxRD)/4 and self.size > chunk.chunkSize*2) { + if(self.children == null) { + self.children = try allocator.alloc(*Node, 8); + for(self.children.?) |*child, i| { + child.* = try Node.init(&self.mesh, chunk.ChunkPosition{ + .wx = self.mesh.pos.wx + (if(i & 1 == 0) 0 else @divFloor(self.size, 2)), + .wy = self.mesh.pos.wy + (if(i & 2 == 0) 0 else @divFloor(self.size, 2)), + .wz = self.mesh.pos.wz + (if(i & 4 == 0) 0 else @divFloor(self.size, 2)), + .voxelSize = @divFloor(self.mesh.pos.voxelSize, 2), + }, @divFloor(self.size, 2), meshRequests); + } + } + for(self.children.?) |child| { + try child.update(playerPos, renderDistance, @divFloor(maxRD, 2), minHeight, maxHeight, nearRenderDistance, meshRequests); + } + // This OctTree doesn't require higher resolution: + } else { + if(self.children) |children| { + for(children) |child| { + child.deinit(); + } + allocator.free(children); + self.children = null; + } + } + } + + fn getChunks(self: *Node, playerPos: Vec3d, meshes: *std.ArrayList(*chunk.meshing.ChunkMesh)) !void { + self.mutex.lock(); + defer self.mutex.unlock(); + if(self.children) |children| { + for(children) |child| { + try child.getChunks(playerPos, meshes); + } + } else { + // TODO: if(testFrustum(frustumInt, x0, y0, z0)) { + try meshes.append(&self.mesh); + //} + } + } + // TODO: +// public boolean testFrustum(FrustumIntersection frustumInt, double x0, double y0, double z0) { +// return frustumInt.testAab((float)(wx - x0), (float)(wy - y0), (float)(wz - z0), (float)(wx + size - x0), (float)(wy + size - y0), (float)(wz + size - z0)); +// } + }; + + const HashMapKey3D = struct { + x: chunk.ChunkCoordinate, + y: chunk.ChunkCoordinate, + z: chunk.ChunkCoordinate, + + pub fn hash(_: anytype, key: HashMapKey3D) u64 { + return @bitCast(u32, ((key.x << 13) | (key.x >> 19)) ^ ((key.y << 7) | (key.y >> 25)) ^ ((key.z << 23) | (key.z >> 9))); // This should be a good hash for now. TODO: Test how good it really is and find a better one. + } + + pub fn eql(_: anytype, key: HashMapKey3D, other: HashMapKey3D) bool { + return key.x == other.x and key.y == other.y and key.z == other.z; + } + }; + + var roots: std.HashMap(HashMapKey3D, *Node, HashMapKey3D, 80) = undefined; + var updatableList: std.ArrayList(*chunk.ChunkVisibilityData) = undefined; + var lastRD: i32 = 0; + var lastFactor: f32 = 0; + var mutex = std.Thread.Mutex{}; + + pub fn init() !void { + lastRD = 0; + lastFactor = 0; + gpa = std.heap.GeneralPurposeAllocator(.{}){}; + allocator = gpa.allocator(); + roots = std.HashMap(HashMapKey3D, *Node, HashMapKey3D, 80).initContext(allocator, undefined); + updatableList = std.ArrayList(*chunk.ChunkVisibilityData).init(allocator); + } + + pub fn deinit() void { + var iterator = roots.valueIterator(); + while(iterator.next()) |value| { + value.*.deinit(); + } + roots.deinit(); + for(updatableList.items) |updatable| { + updatable.visibles.deinit(); + allocator.destroy(updatable); + } + updatableList.deinit(); + game.blockPalette.deinit(); + if(gpa.deinit()) { + @panic("Memory leak"); + } + } + + pub fn update(conn: *network.Connection, playerPos: Vec3d, renderDistance: i32, LODFactor: f32) !void { + if(lastRD != renderDistance and lastFactor != LODFactor) { + // TODO: +// Protocols.GENERIC_UPDATE.sendRenderDistance(Cubyz.world.serverConnection, renderDistance, LODFactor); + } + var px = @floatToInt(chunk.ChunkCoordinate, playerPos.x); + var py = @floatToInt(chunk.ChunkCoordinate, playerPos.y); + var pz = @floatToInt(chunk.ChunkCoordinate, playerPos.z); + var maxRenderDistance = @floatToInt(i32, @ceil(@intToFloat(f32, renderDistance*chunk.chunkSize << settings.highestLOD)*LODFactor)); + var nearRenderDistance = renderDistance*chunk.chunkSize; + var LODShift = settings.highestLOD + chunk.chunkShift; + var LODSize = chunk.chunkSize << settings.highestLOD; + var LODMask = LODSize - 1; + var minX = (px - maxRenderDistance - LODMask) & ~LODMask; + var maxX = (px + maxRenderDistance + LODMask) & ~LODMask; + // The LOD chunks are offset from grid to make generation easier. + minX += @divExact(LODSize, 2) - chunk.chunkSize; + maxX += @divExact(LODSize, 2) - chunk.chunkSize; + var newMap = std.HashMap(HashMapKey3D, *Node, HashMapKey3D, 80).initContext(allocator, undefined); + var meshRequests = std.ArrayList(chunk.ChunkPosition).init(main.threadAllocator); + defer meshRequests.deinit(); + var x = minX; + std.log.info("In the thing to request:{} {}", .{minX, maxX}); + while(x <= maxX): (x += LODSize) { + var maxYRenderDistanceSquare = @intToFloat(f32, maxRenderDistance)*@intToFloat(f32, maxRenderDistance) - @intToFloat(f32, (x - px))*@intToFloat(f32, (x - px)); + if(maxYRenderDistanceSquare < 0) continue; + var maxYRenderDistance = @floatToInt(i32, @ceil(@sqrt(maxYRenderDistanceSquare))); + var minY = (py - maxYRenderDistance - LODMask) & ~LODMask; + var maxY = (py + maxYRenderDistance + LODMask) & ~LODMask; + // The LOD chunks are offset from grid to make generation easier. + minY += @divFloor(LODSize, 2) - chunk.chunkSize; + maxY += @divFloor(LODSize, 2) - chunk.chunkSize; + var y = minY; + while(y <= maxY): (y += LODSize) { + var maxZRenderDistanceSquare = @intToFloat(f32, maxYRenderDistance)*@intToFloat(f32, maxYRenderDistance) - @intToFloat(f32, (y - py))*@intToFloat(f32, (y - py)); + if(maxZRenderDistanceSquare < 0) continue; + var maxZRenderDistance = @floatToInt(i32, @ceil(@sqrt(maxZRenderDistanceSquare))); + var minZ = (pz - maxZRenderDistance - LODMask) & ~LODMask; + var maxZ = (pz + maxZRenderDistance + LODMask) & ~LODMask; + // The LOD chunks are offset from grid to make generation easier. + minZ += @divFloor(LODSize, 2) - chunk.chunkSize; + maxZ += @divFloor(LODSize, 2) - chunk.chunkSize; + var z = minZ; + while(z <= maxZ): (z += LODSize) { + if(y + LODSize <= 0 or y > 1024) { + var dx = @maximum(0, try std.math.absInt(x + @divFloor(LODSize, 2) - px) - @divFloor(LODSize, 2)); + var dy = @maximum(0, try std.math.absInt(y + @divFloor(LODSize, 2) - py) - @divFloor(LODSize, 2)); + var dz = @maximum(0, try std.math.absInt(z + @divFloor(LODSize, 2) - pz) - @divFloor(LODSize, 2)); + if(dx*dx + dy*dy + dz*dz > nearRenderDistance*nearRenderDistance) continue; + } + var rootX = x >> LODShift; + var rootY = y >> LODShift; + var rootZ = z >> LODShift; + + var key = HashMapKey3D{.x = rootX, .y = rootY, .z = rootZ}; + var node = roots.get(key); + if(node) |_node| { + // Mark that this node should not be removed. + _node.shouldBeRemoved = false; + } else { + node = try Node.init(null, .{.wx=x, .wy=y, .wz=z, .voxelSize=@intCast(chunk.UChunkCoordinate, LODSize>>chunk.chunkShift)}, LODSize, &meshRequests); + // Mark this node to be potentially removed in the next update: + node.?.shouldBeRemoved = true; + } + try newMap.put(key, node.?); + try node.?.update(playerPos, renderDistance*chunk.chunkSize, maxRenderDistance, 0, 1024, nearRenderDistance, &meshRequests); + } + } + } + // Clean memory for unused nodes: + mutex.lock(); + defer mutex.unlock(); + var iterator = roots.valueIterator(); + while(iterator.next()) |node| { + if(node.*.shouldBeRemoved) { + node.*.deinit(); + } else { + node.*.shouldBeRemoved = true; + } + } + roots.deinit(); + roots = newMap; + lastRD = renderDistance; + lastFactor = LODFactor; + // Make requests after updating the, to avoid concurrency issues and reduce the number of requests: + try network.Protocols.chunkRequest.sendRequest(conn, meshRequests.items); + } + + fn findNode(pos: chunk.ChunkPosition) ?*Node { + var LODShift = settings.highestLOD + chunk.chunkShift; + var LODSize = @as(chunk.UChunkCoordinate, 1) << LODShift; + var key = HashMapKey3D{ + .x = (pos.wx - LODSize/2 + chunk.chunkSize) >> LODShift, + .y = (pos.wy - LODSize/2 + chunk.chunkSize) >> LODShift, + .z = (pos.wz - LODSize/2 + chunk.chunkSize) >> LODShift, + }; + const rootNode: *Node = roots.get(key) orelse return null; + rootNode.mutex.lock(); + defer rootNode.mutex.unlock(); + + var node = rootNode; + + while(node.mesh.pos.voxelSize != pos.voxelSize) { + var children = node.children orelse return null; + var i: u3 = 0; + if(pos.wx >= node.mesh.pos.wx + @divFloor(node.size, 2)) i += 1; + if(pos.wy >= node.mesh.pos.wy + @divFloor(node.size, 2)) i += 2; + if(pos.wz >= node.mesh.pos.wz + @divFloor(node.size, 2)) i += 4; + node = children[i]; + } + + return node; + } + + pub fn updateMeshes(targetTime: i64) !void { + mutex.lock(); + defer mutex.unlock(); + while(updatableList.items.len != 0) { + std.log.info("Generating mesh.", .{}); + const mesh = updatableList.pop(); + const nullNode = findNode(mesh.pos); + if(nullNode) |node| { + node.mutex.lock(); + defer node.mutex.unlock(); + try node.mesh.regenerateMesh(mesh); + } + mesh.visibles.deinit(); + allocator.destroy(mesh); + if(std.time.milliTimestamp() >= targetTime) break; // Update at least one mesh. + } + } + + pub fn updateChunkMesh(mesh: *chunk.ChunkVisibilityData) !void { + mutex.lock(); + defer mutex.unlock(); + try updatableList.append(mesh); + } + + pub fn getRenderChunks(playerPos: Vec3d, meshes: *std.ArrayList(*chunk.meshing.ChunkMesh)) !void { + mutex.lock(); + defer mutex.unlock(); + var iterator = roots.valueIterator(); + while(iterator.next()) |node| { + try node.*.getChunks(playerPos, meshes); + } + } + // TODO: +// public void updateChunkMesh(VisibleChunk mesh) { +// OctTreeNode node = findNode(mesh); +// if (node != null) { +// ((NormalChunkMesh)node.mesh).updateChunk(mesh); +// } +// } +}; diff --git a/src/settings.zig b/src/settings.zig index 2032763d..2731426d 100644 --- a/src/settings.zig +++ b/src/settings.zig @@ -3,4 +3,6 @@ pub const defaultPort: u16 = 47649; pub const connectionTimeout = 60000; -pub const version = "0.12.0"; \ No newline at end of file +pub const version = "0.12.0"; + +pub const highestLOD: u5 = 5; \ No newline at end of file diff --git a/src/utils.zig b/src/utils.zig index 32a2d566..f26f3c21 100644 --- a/src/utils.zig +++ b/src/utils.zig @@ -4,8 +4,24 @@ const Allocator = std.mem.Allocator; const main = @import("main.zig"); pub const Compression = struct { + pub fn deflate(allocator: Allocator, data: []const u8) ![]u8 { + var result = std.ArrayList(u8).init(allocator); + var comp = try std.compress.deflate.compressor(main.threadAllocator, result.writer(), .{.level = .default_compression}); + try comp.write(data); + try comp.close(); + comp.deinit(); + return result.toOwnedSlice(); + } + + pub fn inflate(allocator: Allocator, data: []const u8) ![]u8 { + var stream = std.io.fixedBufferStream(data); + var decomp = try std.compress.deflate.decompressor(main.threadAllocator, stream.reader(), null); + defer decomp.deinit(); + return try decomp.reader().readAllAlloc(allocator, std.math.maxInt(usize)); + } + pub fn pack(sourceDir: std.fs.IterableDir, writer: anytype) !void { - var comp = try std.compress.deflate.compressor(main.threadAllocator, writer, .{.level = .no_compression}); + var comp = try std.compress.deflate.compressor(main.threadAllocator, writer, .{.level = .default_compression}); defer comp.deinit(); var walker = try sourceDir.walk(main.threadAllocator); defer walker.deinit(); @@ -246,7 +262,7 @@ pub const ThreadPool = struct { fn run(self: ThreadPool) void { // In case any of the tasks wants to allocate memory: - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + var gpa = std.heap.GeneralPurposeAllocator(.{.thread_safe=false}){}; main.threadAllocator = gpa.allocator(); defer if(gpa.deinit()) { @panic("Memory leak");