const std = @import("std"); const Allocator = std.mem.Allocator; const blocks = @import("blocks.zig"); const Block = blocks.Block; const game = @import("game.zig"); const graphics = @import("graphics.zig"); const c = graphics.c; const Shader = graphics.Shader; const SSBO = graphics.SSBO; const main = @import("main.zig"); const renderer = @import("renderer.zig"); const vec = @import("vec.zig"); const Vec3f = vec.Vec3f; const Vec3d = vec.Vec3d; const Mat4f = vec.Mat4f; 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; pub const chunkSizeIterator: [chunkSize]u0 = undefined; pub const chunkVolume: UChunkCoordinate = 1 << 3*chunkShift; pub const chunkMask: ChunkCoordinate = chunkSize - 1; /// Contains a bunch of constants used to describe neighboring blocks. pub const Neighbors = struct { /// How many neighbors there are. pub const neighbors: u32 = 6; /// Directions → Index pub const dirUp: u32 = 0; /// Directions → Index pub const dirDown: u32 = 1; /// Directions → Index pub const dirPosX: u32 = 2; /// Directions → Index pub const dirNegX: u32 = 3; /// Directions → Index pub const dirPosZ: u32 = 4; /// Directions → Index pub const dirNegZ: u32 = 5; /// Index to relative position pub const relX = [_]ChunkCoordinate {0, 0, 1, -1, 0, 0}; /// Index to relative position pub const relY = [_]ChunkCoordinate {1, -1, 0, 0, 0, 0}; /// Index to relative position pub const relZ = [_]ChunkCoordinate {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 pub const iterable = [_]u3 {0, 1, 2, 3, 4, 5}; }; /// Gets the index of a given position inside this chunk. fn getIndex(x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate) u32 { std.debug.assert((x & chunkMask) == x and (y & chunkMask) == y and (z & chunkMask) == z); return (@intCast(u32, x) << chunkShift) | (@intCast(u32, y) << chunkShift2) | @intCast(u32, z); } /// Gets the x coordinate from a given index inside this chunk. fn extractXFromIndex(index: usize) ChunkCoordinate { return @intCast(ChunkCoordinate, index >> chunkShift & chunkMask); } /// Gets the y coordinate from a given index inside this chunk. fn extractYFromIndex(index: usize) ChunkCoordinate { return @intCast(ChunkCoordinate, index >> chunkShift2 & chunkMask); } /// Gets the z coordinate from a given index inside this chunk. fn extractZFromIndex(index: usize) ChunkCoordinate { return @intCast(ChunkCoordinate, index & chunkMask); } pub const ChunkPosition = struct { wx: ChunkCoordinate, wy: ChunkCoordinate, wz: ChunkCoordinate, voxelSize: UChunkCoordinate, // TODO(mabye?): // public int hashCode() { // int shift = Math.min(Integer.numberOfTrailingZeros(wx), Math.min(Integer.numberOfTrailingZeros(wy), Integer.numberOfTrailingZeros(wz))); // return (((wx >> shift) * 31 + (wy >> shift)) * 31 + (wz >> shift)) * 31 + voxelSize; // } 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 { pos: ChunkPosition, blocks: [chunkVolume]Block = undefined, wasChanged: bool = false, /// When a chunk is cleaned, it won't be saved by the ChunkManager anymore, so following changes need to be saved directly. wasCleaned: bool = false, generated: bool = false, width: ChunkCoordinate, voxelSizeShift: u5, voxelSizeMask: ChunkCoordinate, widthShift: u5, mutex: std.Thread.Mutex, pub fn init(self: *Chunk, pos: ChunkPosition) void { std.debug.assert((pos.voxelSize - 1 & pos.voxelSize) == 0); std.debug.assert(@mod(pos.wx, pos.voxelSize) == 0 and @mod(pos.wy, pos.voxelSize) == 0 and @mod(pos.wz, pos.voxelSize) == 0); const voxelSizeShift = @intCast(u5, std.math.log2_int(UChunkCoordinate, pos.voxelSize)); self.* = Chunk { .pos = pos, .width = pos.voxelSize*chunkSize, .voxelSizeShift = voxelSizeShift, .voxelSizeMask = pos.voxelSize - 1, .widthShift = voxelSizeShift + chunkShift, .mutex = std.Thread.Mutex{}, }; } pub fn setChanged(self: *Chunk) void { self.wasChanged = true; { self.mutex.lock(); if(self.wasCleaned) { self.save(); } self.mutex.unlock(); } } pub fn clean(self: *Chunk) void { { self.mutex.lock(); self.wasCleaned = true; self.save(); self.mutex.unlock(); } } pub fn unclean(self: *Chunk) void { { self.mutex.lock(); self.wasCleaned = false; self.save(); self.mutex.unlock(); } } /// Checks if the given relative coordinates lie within the bounds of this chunk. pub fn liesInChunk(self: *const Chunk, x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate) bool { return x >= 0 and x < self.width and y >= 0 and y < self.width and z >= 0 and z < self.width; } /// This is useful to convert for loops to work for reduced resolution: /// Instead of using /// for(int x = start; x < end; x++) /// for(int x = chunk.startIndex(start); x < end; x += chunk.getVoxelSize()) /// should be used to only activate those voxels that are used in Cubyz's downscaling technique. pub fn startIndex(self: *const Chunk, start: ChunkCoordinate) ChunkCoordinate { return start+self.voxelSizeMask & ~self.voxelSizeMask; // Rounds up to the nearest valid voxel coordinate. } /// Updates a block if current value is air or the current block is degradable. /// Does not do any bound checks. They are expected to be done with the `liesInChunk` function. pub fn updateBlockIfDegradable(self: *Chunk, x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate, newBlock: Block) void { x >>= self.voxelSizeShift; y >>= self.voxelSizeShift; z >>= self.voxelSizeShift; var index = getIndex(x, y, z); if (self.blocks[index] == 0 || self.blocks[index].degradable()) { self.blocks[index] = newBlock; } } /// Updates a block if it is inside this chunk. /// Does not do any bound checks. They are expected to be done with the `liesInChunk` function. pub fn updateBlock(self: *Chunk, x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate, newBlock: Block) void { x >>= self.voxelSizeShift; y >>= self.voxelSizeShift; z >>= self.voxelSizeShift; var index = getIndex(x, y, z); self.blocks[index] = newBlock; } /// Updates a block if it is inside this chunk. Should be used in generation to prevent accidently storing these as changes. /// Does not do any bound checks. They are expected to be done with the `liesInChunk` function. pub fn updateBlockInGeneration(self: *Chunk, x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate, newBlock: Block) void { x >>= self.voxelSizeShift; y >>= self.voxelSizeShift; z >>= self.voxelSizeShift; var index = getIndex(x, y, z); self.blocks[index] = newBlock; } /// Gets a block if it is inside this chunk. /// Does not do any bound checks. They are expected to be done with the `liesInChunk` function. pub fn getBlock(self: *const Chunk, _x: ChunkCoordinate, _y: ChunkCoordinate, _z: ChunkCoordinate) Block { var x = _x >> self.voxelSizeShift; var y = _y >> self.voxelSizeShift; var z = _z >> self.voxelSizeShift; var index = getIndex(x, y, z); return self.blocks[index]; } pub fn getNeighbors(self: *const Chunk, x: ChunkCoordinate, y: ChunkCoordinate, z: ChunkCoordinate, neighborsArray: *[6]Block) void { std.debug.assert(neighborsArray.length == 6); x &= chunkMask; y &= chunkMask; z &= chunkMask; for(Neighbors.relX) |_, i| { var xi = x + Neighbors.relX[i]; var yi = y + Neighbors.relY[i]; var zi = z + Neighbors.relZ[i]; if (xi == (xi & chunkMask) and yi == (yi & chunkMask) and zi == (zi & chunkMask)) { // Simple double-bound test for coordinates. neighborsArray[i] = self.getBlock(xi, yi, zi); } else { // TODO: What about other chunks? // NormalChunk ch = world.getChunk(xi + wx, yi + wy, zi + wz); // if (ch != null) { // neighborsArray[i] = ch.getBlock(xi & chunkMask, yi & chunkMask, zi & chunkMask); // } else { // neighborsArray[i] = 1; // Some solid replacement, in case the chunk isn't loaded. TODO: Properly choose a solid block. // } } } } pub fn updateFromLowerResolution(self: *Chunk, other: *const Chunk) void { const xOffset = if(other.wx != self.wx) chunkSize/2 else 0; // Offsets of the lower resolution chunk in this chunk. const yOffset = if(other.wy != self.wy) chunkSize/2 else 0; const zOffset = if(other.wz != self.wz) chunkSize/2 else 0; var x: ChunkCoordinate = 0; while(x < chunkSize/2): (x += 1) { var y: ChunkCoordinate = 0; while(y < chunkSize/2): (y += 1) { var z: ChunkCoordinate = 0; while(z < chunkSize/2): (z += 1) { // Count the neighbors for each subblock. An transparent block counts 5. A chunk border(unknown block) only counts 1. var neighborCount: [8]u32 = undefined; var octantBlocks: [8]Block = undefined; var maxCount: u32 = 0; var dx: ChunkCoordinate = 0; while(dx <= 1): (dx += 1) { var dy: ChunkCoordinate = 0; while(dy <= 1): (dy += 1) { var dz: ChunkCoordinate = 0; while(dz <= 1): (dz += 1) { const index = getIndex(x*2 + dx, y*2 + dy, z*2 + dz); const i = dx*4 + dz*2 + dy; octantBlocks[i] = other.blocks[index]; if(octantBlocks[i] == 0) continue; // I don't care about air blocks. var count: u32 = 0; for(Neighbors.iterable) |n| { const nx = x*2 + dx + Neighbors.relX[n]; const ny = y*2 + dy + Neighbors.relY[n]; const nz = z*2 + dz + Neighbors.relZ[n]; if((nx & chunkMask) == nx and (ny & chunkMask) == ny and (nz & chunkMask) == nz) { // If it's inside the chunk. const neighborIndex = getIndex(nx, ny, nz); if(other.blocks[neighborIndex].transparent()) { count += 5; } } else { count += 1; } } maxCount = @maximum(maxCount, count); neighborCount[i] = count; } } } // Uses a specific permutation here that keeps high resolution patterns in lower resolution. const permutationStart = (x & 1)*4 + (z & 1)*2 + (y & 1); const block = Block{.typ = 0, .data = 0}; for(neighborCount) |_, i| { const appliedPermutation = permutationStart ^ i; if(neighborCount[appliedPermutation] >= maxCount - 1) { // Avoid pattern breaks at chunk borders. block = blocks[appliedPermutation]; } } // Update the block: const thisIndex = getIndex(x + xOffset, y + yOffset, z + zOffset); self.blocks[thisIndex] = block; } } } // Create updated meshes and send to client: // TODO: //for(int x = 0; x <= 2*xOffset; x += chunkSize) { // for(int y = 0; y <= 2*yOffset; y += chunkSize) { // for(int z = 0; z <= 2*zOffset; z += chunkSize) { // int wx = this.wx + x*voxelSize - Chunk.chunkSize; // int wy = this.wy + y*voxelSize - Chunk.chunkSize; // int wz = this.wz + z*voxelSize - Chunk.chunkSize; // if(voxelSize == 32) { // wx -= chunkSize*voxelSize/2; // wy -= chunkSize*voxelSize/2; // wz -= chunkSize*voxelSize/2; // } // world.queueChunks(new ChunkData[] {new ChunkData(wx, wy, wz, voxelSize)}); // } // } //} self.setChanged(); } // TODO: Move this outside. // /** // * Generates this chunk. // * If the chunk was already saved it is loaded from file instead. // * @param seed // * @param terrainGenerationProfile // */ // public void generate(World world, long seed, TerrainGenerationProfile terrainGenerationProfile) { // assert !generated : "Seriously, why would you generate this chunk twice???"; // if(!ChunkIO.loadChunkFromFile(world, this)) { // CaveMap caveMap = new CaveMap(this); // CaveBiomeMap biomeMap = new CaveBiomeMap(this); // // for (Generator g : terrainGenerationProfile.generators) { // g.generate(seed ^ g.getGeneratorSeed(), wx, wy, wz, this, caveMap, biomeMap); // } // } // generated = true; // } // TODO: pub fn save(chunk: *const Chunk) void { _ = chunk; // /** // * Saves this chunk. // */ // public void save(World world) { // if(wasChanged) { // ChunkIO.storeChunkToFile(world, this); // wasChanged = false; // // Update the next lod chunk: // if(voxelSize != 1 << Constants.HIGHEST_LOD) { // if(world instanceof ServerWorld) { // ReducedChunk chunk = ((ServerWorld)world).chunkManager.getOrGenerateReducedChunk(wx, wy, wz, voxelSize*2); // chunk.updateFromLowerResolution(this); // } else { // Logger.error("Not implemented: "); // Logger.error(new Exception()); // } // } // } } }; // TODO: Check if/how they are needed: // // public Vector3d getMin() { // return new Vector3d(wx, wy, wz); // } // // public Vector3d getMax() { // return new Vector3d(wx + width, wy + width, wz + width); // } // // @Override // public byte[] saveToByteArray() { // byte[] data = new byte[4*blocks.length]; // for(int i = 0; i < blocks.length; i++) { // Bits.putInt(data, i*4, blocks[i]); // } // return data; // } // // @Override // public boolean loadFromByteArray(byte[] data, int outputLength) { // if(outputLength != 4*blocks.length) { // Logger.error("Chunk is corrupted(invalid data length "+outputLength+") : " + this); // return false; // } // for(int i = 0; i < blocks.length; i++) { // blocks[i] = Bits.getInt(data, i*4); // } // generated = true; // return true; // } pub const VisibleBlock = struct { block: Block, x: u8, y: u8, z: u8, neighbors: u8, }; pub const ChunkVisibilityData = struct { pos: ChunkPosition, visibles: std.ArrayList(VisibleBlock), 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 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 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 = try std.ArrayList(VisibleBlock).initCapacity(allocator, initialCapacity), .voxelSizeShift = std.math.log2_int(UChunkCoordinate, pos.voxelSize), }; const width = pos.voxelSize*chunkSize; const widthMask = width - 1; // Get or generate the 8 surrounding chunks: var chunks: [8]Chunk = undefined; var x: u8 = 0; while(x <= 1): (x += 1) { var y: u8 = 0; while(y <= 1): (y += 1) { var z: u8 = 0; while(z <= 1): (z += 1) { chunks[x*4 + y*2 + z] = Chunk.init((pos.wx & ~widthMask) + x*width, (pos.wy & ~widthMask) + y*width, (pos.wz & ~widthMask) + z*width, pos.voxelSize); // TODO: world.chunkManager.getOrGenerateReducedChunk((wx & ~widthMask) + x*width, (wy & ~widthMask) + y*width, (wz & ~widthMask) + z*width, voxelSize); } } } const halfMask = chunkMask >> 1; x = 0; while(x < chunkSize): (x += 1) { var y: u8 = 0; while(y < chunkSize): (y += 1) { var z: u8 = 0; while(z < chunkSize): (z += 1) { const block = self.getBlock(&chunks, x, y, z); if(block.typ == 0) continue; // Check all neighbors: var neighborVisibility: u8 = 0; for(Neighbors.iterable) |i| { 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); 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: // TODO: Find a better way to do this. This method doesn't always work and adds a lot of additional triangles. if(x & halfMask == (x2 & halfMask) ^ halfMask or y & halfMask == (y2 & halfMask) ^ halfMask or z & halfMask == (z2 & halfMask) ^ halfMask) { for(Neighbors.iterable) |j| { 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).typ == 0) { isVisible = true; break; } } } } if(isVisible) { neighborVisibility |= Neighbors.bitMask[i]; } } if(neighborVisibility != 0) { try self.visibles.append(VisibleBlock{ .block = block, .x = x, .y = y, .z = z, .neighbors = neighborVisibility, }); } } } } return self; } // TODO: Check how this constructor is actually needed. // public ReducedChunkVisibilityData(int wx, int wy, int wz, int voxelSize, byte[] x, byte[] y, byte[] z, byte[] neighbors, int[] visibleBlocks) { // super(wx, wy, wz, voxelSize); // voxelSizeShift = 31 - Integer.numberOfLeadingZeros(voxelSize); // log2 // assert x.length == y.length && y.length == z.length && z.length == neighbors.length && neighbors.length == visibleBlocks.length : "Size of input parameters doesn't match."; // this.x = x; // this.y = y; // this.z = z; // this.neighbors = neighbors; // this.visibleBlocks = visibleBlocks; // capacity = size = x.length; // } //} }; pub const meshing = struct { var shader: Shader = undefined; pub var uniforms: struct { projectionMatrix: c_int, viewMatrix: c_int, modelPosition: c_int, ambientLight: c_int, @"fog.activ": c_int, @"fog.color": c_int, @"fog.density": c_int, texture_sampler: c_int, emissionSampler: c_int, @"waterFog.activ": c_int, @"waterFog.color": c_int, @"waterFog.density": c_int, time: c_int, } = undefined; var vao: c_uint = undefined; var vbo: c_uint = undefined; var faces: std.ArrayList(u32) = undefined; pub fn init() !void { shader = try Shader.create("assets/cubyz/shaders/chunks/chunk_vertex.vs", "assets/cubyz/shaders/chunks/chunk_fragment.fs"); uniforms = shader.bulkGetUniformLocation(@TypeOf(uniforms)); var rawData: [6*3 << (3*chunkShift)]u32 = undefined; // 6 vertices per face, maximum 3 faces/block const lut = [_]u32{0, 1, 2, 2, 1, 3}; for(rawData) |_, i| { rawData[i] = @intCast(u32, i)/6*4 + lut[i%6]; } c.glGenVertexArrays(1, &vao); c.glBindVertexArray(vao); c.glGenBuffers(1, &vbo); c.glBindBuffer(c.GL_ELEMENT_ARRAY_BUFFER, vbo); c.glBufferData(c.GL_ELEMENT_ARRAY_BUFFER, rawData.len*@sizeOf(f32), &rawData, c.GL_STATIC_DRAW); c.glVertexAttribPointer(0, 2, c.GL_FLOAT, c.GL_FALSE, 2*@sizeOf(f32), null); c.glBindVertexArray(0); faces = try std.ArrayList(u32).initCapacity(std.heap.page_allocator, 65536); } pub fn deinit() void { shader.delete(); c.glDeleteVertexArrays(1, &vao); c.glDeleteBuffers(1, &vbo); faces.deinit(); } pub fn bindShaderAndUniforms(projMatrix: Mat4f, ambient: Vec3f, time: u32) void { shader.bind(); c.glUniform1i(uniforms.@"fog.activ", if(game.fog.active) 1 else 0); c.glUniform3fv(uniforms.@"fog.color", 1, @ptrCast([*c]f32, &game.fog.color)); c.glUniform1f(uniforms.@"fog.density", game.fog.density); c.glUniformMatrix4fv(uniforms.projectionMatrix, 1, c.GL_FALSE, @ptrCast([*c]const f32, &projMatrix)); c.glUniform1i(uniforms.texture_sampler, 0); c.glUniform1i(uniforms.emissionSampler, 1); c.glUniformMatrix4fv(uniforms.viewMatrix, 1, c.GL_FALSE, @ptrCast([*c]f32, &game.camera.viewMatrix)); c.glUniform3f(uniforms.ambientLight, ambient.x, ambient.y, ambient.z); c.glUniform1i(uniforms.time, @bitCast(i32, time)); c.glBindVertexArray(vao); } pub const ChunkMesh = struct { pos: ChunkPosition, size: ChunkCoordinate, chunk: ?*Chunk, faces: std.ArrayList(u32), faceData: SSBO, coreCount: u31 = 0, neighborStart: [7]u31 = [_]u31{0} ** 7, vertexCount: u31 = 0, generated: bool = false, mutex: std.Thread.Mutex = std.Thread.Mutex{}, pub fn init(allocator: Allocator, pos: ChunkPosition) ChunkMesh { return ChunkMesh{ .pos = pos, .size = chunkSize*pos.voxelSize, .faces = std.ArrayList(u32).init(allocator), .chunk = null, .faceData = SSBO.init(), }; } pub fn deinit(self: *ChunkMesh) void { self.faceData.deinit(); self.faces.deinit(); if(self.chunk) |ch| { renderer.RenderStructure.allocator.destroy(ch); } } pub fn regenerateMainMesh(self: *ChunkMesh, chunk: *Chunk) !void { std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function. self.faces.clearRetainingCapacity(); try self.faces.append(chunk.pos.voxelSize); var n: u32 = 0; var x: u8 = 0; while(x < chunkSize): (x += 1) { var y: u8 = 0; while(y < chunkSize): (y += 1) { var z: u8 = 0; while(z < chunkSize): (z += 1) { const block = (&chunk.blocks)[getIndex(x, y, z)]; // ← a little hack that increases speed 100×. TODO: check if this is *that* compiler bug. if(block.typ == 0) continue; // Check all neighbors: for(Neighbors.iterable) |i| { n += 1; const x2 = x + Neighbors.relX[i]; const y2 = y + Neighbors.relY[i]; const z2 = z + Neighbors.relZ[i]; if(x2&chunkMask != x2 or y2&chunkMask != y2 or z2&chunkMask != z2) continue; // Neighbor is outside the chunk. const neighborBlock = (&chunk.blocks)[getIndex(x2, y2, z2)]; // ← a little hack that increases speed 100×. TODO: check if this is *that* compiler bug. var isVisible = neighborBlock.typ == 0; // TODO: Transparency if(isVisible) { const normal: u32 = i; const position: u32 = @as(u32, x) | @as(u32, y)<<5 | @as(u32, z)<<10; const textureNormal = blocks.meshes.textureIndices(block)[i] | normal<<24; try self.faces.append(position); try self.faces.append(textureNormal); } } } } } if(self.chunk) |oldChunk| { renderer.RenderStructure.allocator.destroy(oldChunk); } self.chunk = chunk; self.coreCount = @intCast(u31, self.faces.items.len); self.neighborStart = [_]u31{self.coreCount} ** 7; } 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. self.generated = true; self.faces.shrinkRetainingCapacity(self.coreCount); for(Neighbors.iterable) |neighbor| { self.neighborStart[neighbor] = @intCast(u31, self.faces.items.len); var nullNeighborMesh = renderer.RenderStructure.getNeighbor(self.pos, neighbor); if(nullNeighborMesh) |neighborMesh| { std.debug.assert(neighborMesh != self); neighborMesh.mutex.lock(); defer neighborMesh.mutex.unlock(); if(neighborMesh.generated) { var additionalNeighborFaces = std.ArrayList(u32).init(main.threadAllocator); defer additionalNeighborFaces.deinit(); var x3: u8 = if(neighbor & 1 == 0) @intCast(u8, chunkMask) else 0; var x1: u8 = 0; while(x1 < chunkSize): (x1 += 1) { var x2: u8 = 0; while(x2 < chunkSize): (x2 += 1) { var x: u8 = undefined; var y: u8 = undefined; var z: u8 = undefined; if(Neighbors.relX[neighbor] != 0) { x = x3; y = x1; z = x2; } else if(Neighbors.relY[neighbor] != 0) { x = x1; y = x3; z = x2; } else { x = x2; y = x1; z = x3; } var otherX = @intCast(u8, x+%Neighbors.relX[neighbor] & chunkMask); var otherY = @intCast(u8, y+%Neighbors.relY[neighbor] & chunkMask); var otherZ = @intCast(u8, z+%Neighbors.relZ[neighbor] & chunkMask); var block = self.chunk.?.blocks[getIndex(x, y, z)]; var otherBlock = neighborMesh.chunk.?.blocks[getIndex(otherX, otherY, otherZ)]; if(otherBlock.typ == 0 and block.typ != 0) { // TODO: Transparency const normal: u32 = neighbor; const position: u32 = @as(u32, x) | @as(u32, y)<<5 | @as(u32, z)<<10; const textureNormal = blocks.meshes.textureIndices(block)[neighbor] | normal<<24; try self.faces.append(position); try self.faces.append(textureNormal); } if(block.typ == 0 and otherBlock.typ != 0) { // TODO: Transparency const normal: u32 = neighbor ^ 1; const position: u32 = @as(u32, otherX) | @as(u32, otherY)<<5 | @as(u32, otherZ)<<10; const textureNormal = blocks.meshes.textureIndices(otherBlock)[neighbor] | normal<<24; try additionalNeighborFaces.append(position); try additionalNeighborFaces.append(textureNormal); } } } var rangeStart = neighborMesh.neighborStart[neighbor ^ 1]; var rangeEnd = neighborMesh.neighborStart[(neighbor ^ 1)+1]; try neighborMesh.faces.replaceRange(rangeStart, rangeEnd - rangeStart, additionalNeighborFaces.items); for(neighborMesh.neighborStart[1+(neighbor ^ 1)..]) |*neighborStart| { neighborStart.* = neighborStart.* - (rangeEnd - rangeStart) + @intCast(u31, additionalNeighborFaces.items.len); } neighborMesh.vertexCount = @intCast(u31, 6*(neighborMesh.faces.items.len-1)/2); neighborMesh.faceData.bufferData(u32, neighborMesh.faces.items); } else { // TODO: Resolution boundary. } } else { // TODO: Resolution boundary. } } 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); } pub fn render(self: *ChunkMesh, playerPosition: Vec3d) void { if(!self.generated) { return; } if(self.vertexCount == 0) return; c.glUniform3f( uniforms.modelPosition, @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); } }; };