const std = @import("std"); const raylib_helper = @import("../lib_helpers/raylib_helper.zig"); const raylib = raylib_helper.raylib; const v3 = raylib_helper.v3; const A7r = std.mem.Allocator; const comptimePrint = std.fmt.comptimePrint; const VERTICES_BLOCK_SIZE = 2 * 3 * 3; const TEXCOORDS_BLOCK_SIZE = 2 * 2 * 3; const X_DIRECTION = 0; const Y_DIRECTION = 1; const Z_DIRECTION = 3; const RawQuad = struct { tile: u32, top_left: raylib.Vector3, top_right: raylib.Vector3, bottom_right: raylib.Vector3, bottom_left: raylib.Vector3, normal: raylib.Vector3, width: f32, height: f32, top_obscuring_pattern: u32, left_obscuring_pattern: u32, right_obscuring_pattern: u32, bottom_obscuring_pattern: u32, top_left_obscured: bool, top_right_obscured: bool, bottom_right_obscured: bool, bottom_left_obscured: bool, }; // Quad shader metadata. Has to be 128 bytes in size. const Metadata1 = packed struct { ambient_occlusion_1: u32, ambient_occlusion_2: u32, ambient_occlusion_corner1: bool, ambient_occlusion_corner2: bool, ambient_occlusion_corner3: bool, quad_height: u6, quad_width: u6, unused: u17 = 0, unused_2: u32 = 0, }; comptime { if (@bitSizeOf(Metadata1) != 128) { @compileError(comptimePrint("Metadata 1 has wrong size. Expected 128 bytes, found {}", .{@bitSizeOf(Metadata1)})); } } pub const Chunk = struct { tiles: []u32, a7r: A7r, pub fn init(a7r: A7r) !Chunk { const self = Chunk{ .a7r = a7r, .tiles = try a7r.alloc(u32, 32 * 32 * 32), }; @memset(self.tiles, 0); return self; } pub fn deinit(self: Chunk) void { self.a7r.free(self.tiles); } // Fetch the tile at (x, y, z), but with potential side effects. If you imagine tiles to be a 3-dimensional array, this would be tiles[x][y][z]. pub fn getTile(self: Chunk, x: u5, y: u5, z: u5) u32 { return self.tiles[@as(u15, x) << 10 | @as(u15, y) << 5 | @as(u15, z)]; } // Set the tile at (x, y, z). If you imagine tiles to be a 3-dimensional array, this would be tiles[x][y][z] = tile. pub fn setTile(self: Chunk, x: u5, y: u5, z: u5, tile: u32) void { self.tiles[@as(u15, x) << 10 | @as(u15, y) << 5 | @as(u15, z)] = tile; } // Fetch the tile at (x, y, z) without changin anything. If you imagine tiles to be a 3-dimensional array, this would be tiles[x][y][z]. fn getTileRaw(self: Chunk, x: u5, y: u5, z: u5) u32 { return self.tiles[@as(u15, x) << 10 | @as(u15, y) << 5 | @as(u15, z)]; } // This cyclically permutes the x, y, z coordinates at compile time. Useful when iterating over x, y, and z axis. inline fn getTileRawShifted(self: Chunk, x: u5, y: u5, z: u5, comptime dimention: comptime_int) u32 { if (dimention % 3 == 0) { return self.getTileRaw(x, y, z); } else if (dimention % 3 == 1) { return self.getTileRaw(y, z, x); } else if (dimention % 3 == 2) { return self.getTileRaw(z, x, y); } } // Create a raw quad with specified parameters and surface, accounting for dimension and sign. Surface is the block ID. fn pack_raw_quad( x: f32, y_start: usize, y_end: usize, z_start: usize, z_end: usize, sign: comptime_int, dimention: comptime_int, surface: u32, y_minus_obscuring_pattern: u32, y_plus_obscuring_pattern: u32, z_minus_obscuring_pattern: u32, z_plus_obscuring_pattern: u32, ) RawQuad { const ymin: f32 = @as(f32, @floatFromInt(y_start)) - 0.5; const ymax: f32 = @as(f32, @floatFromInt(y_end)) - 0.5; const zmin: f32 = @as(f32, @floatFromInt(z_start)) - 0.5; const zmax: f32 = @as(f32, @floatFromInt(z_end)) - 0.5; const yleft: f32 = if (sign == 1) ymin else ymax; const yright: f32 = if (sign == 1) ymax else ymin; const zleft: f32 = if (sign == 1) zmin else zmax; const zright: f32 = if (sign == 1) zmax else zmin; var raw_quad: RawQuad = undefined; switch (dimention) { X_DIRECTION => { raw_quad = .{ .tile = surface, .top_left = v3.new(x + 0.5 * sign, ymax, zright), .top_right = v3.new(x + 0.5 * sign, ymax, zleft), .bottom_left = v3.new(x + 0.5 * sign, ymin, zright), .bottom_right = v3.new(x + 0.5 * sign, ymin, zleft), .normal = v3.new(sign, 0, 0), .width = zmax - zmin, .height = ymax - ymin, .top_obscuring_pattern = z_plus_obscuring_pattern, .left_obscuring_pattern = z_minus_obscuring_pattern, .right_obscuring_pattern = y_plus_obscuring_pattern, .bottom_obscuring_pattern = y_minus_obscuring_pattern, .top_left_obscured = false, .top_right_obscured = false, .bottom_right_obscured = false, .bottom_left_obscured = false, }; }, Y_DIRECTION => { raw_quad = .{ .tile = surface, .bottom_left = v3.new(yleft, zmin, x + 0.5 * sign), .top_left = v3.new(yleft, zmax, x + 0.5 * sign), .bottom_right = v3.new(yright, zmin, x + 0.5 * sign), .top_right = v3.new(yright, zmax, x + 0.5 * sign), .normal = v3.new(0, 0, sign), .height = zmax - zmin, .width = ymax - ymin, .top_obscuring_pattern = 0, .left_obscuring_pattern = 0, .right_obscuring_pattern = 0, .bottom_obscuring_pattern = 0, .top_left_obscured = false, .top_right_obscured = false, .bottom_right_obscured = false, .bottom_left_obscured = false, }; }, Z_DIRECTION => { raw_quad = .{ .tile = surface, .top_left = v3.new(zleft, x + 0.5 * sign, ymin), .top_right = v3.new(zright, x + 0.5 * sign, ymin), .bottom_left = v3.new(zleft, x + 0.5 * sign, ymax), .bottom_right = v3.new(zright, x + 0.5 * sign, ymax), .normal = v3.new(0, sign, 0), .width = zmax - zmin, .height = ymax - ymin, .top_obscuring_pattern = 0, .left_obscuring_pattern = 0, .right_obscuring_pattern = 0, .bottom_obscuring_pattern = 0, .top_left_obscured = false, .top_right_obscured = false, .bottom_right_obscured = false, .bottom_left_obscured = false, }; }, else => unreachable, } return raw_quad; } // Create mesh of a chunk. tile_rows and tile_columns are the dimensions of the tiles.png file, in terms of individual tile textures. pub fn createMesh(chunk: Chunk, tile_rows: u32, tile_columns: u32) !raylib.ChunkMesh { var raw_quads = try std.ArrayList(RawQuad).initCapacity(chunk.a7r, 4096); defer raw_quads.deinit(); // Begin scanning the chunk for tile surfaces to make raw quads. inline for (0..3) |dimention| { // Iterate over the 3 dimensions, X, Y and Z. for (0..32) |raw_x| { const x: u5 = @intCast(raw_x); // Create surface arrays for the +x side of the layer and the -x side. var positive_tile_surfaces: [32][32]u32 = .{.{0} ** 32} ** 32; var negative_tile_surfaces: [32][32]u32 = .{.{0} ** 32} ** 32; for (0..32) |raw_y| for (0..32) |raw_z| { const y: u5 = @intCast(raw_y); const z: u5 = @intCast(raw_z); const tile: u32 = chunk.getTileRawShifted(x, y, z, dimention); if (tile == 0) continue; // If air, there is no surface. // If either at the edge of the chunk or the tile is exposed, create a tile surface. if (x == 31 or chunk.getTileRawShifted(x + 1, y, z, dimention) == 0) positive_tile_surfaces[y][z] = tile; if (x == 0 or chunk.getTileRawShifted(x - 1, y, z, dimention) == 0) negative_tile_surfaces[y][z] = tile; }; inline for (.{ -1, 1 }) |sign| { var tile_surfaces = if (sign == 1) positive_tile_surfaces else negative_tile_surfaces; for (0..32) |y_start| for (0..32) |z_start| { const surface = tile_surfaces[y_start][z_start]; // Starting surface tile type. if (surface == 0) continue; // No surface if air. tile_surfaces[y_start][z_start] = 0; // Replace this surface with air, since the corresponding quad will be created. // The end coordinates of the quad. The quad is therefore covers rectangle from start coordinates (inclusive) to end coordinates (exclusive). var y_end = y_start + 1; var z_end = z_start + 1; // todo: meshing can be optimized with SIMD stuff!! // Greedy meshing: Extend the quad in the +y direction, until we hit a tile of a different type or the end of the chunk. while (y_end <= 31 and tile_surfaces[y_end][z_start] == surface) : (y_end += 1) { tile_surfaces[y_end][z_start] = 0; } // Greedy meshing: Extend the quad in the +z direction, until the next line does not consist of tiles of correct type. zloop: while (z_end <= 31) : (z_end += 1) { for (y_start..y_end) |y| if (tile_surfaces[y][z_end] != surface) break :zloop; // Stop extending if we hit a tile of incorrect type. for (y_start..y_end) |y| tile_surfaces[y][z_end] = 0; } // Scan the line of tiles adjacent to the quad for ambient occlusion var z_minus_obscuring_pattern: u32 = 0; if (x != (if (sign == 1) 31 else 0) and z_start != 0) { for (y_start..y_end) |raw_y| { const y: u5 = @intCast(raw_y); z_minus_obscuring_pattern <<= 1; if (chunk.getTileRawShifted(if (sign == 1) x+1 else x-1, y, @intCast(z_start - 1), dimention) != 0) z_minus_obscuring_pattern |= 1; } } var z_plus_obscuring_pattern: u32 = 0; if (x != (if (sign == 1) 31 else 0) and z_end != 32) { for (y_start..y_end) |raw_y| { const y: u5 = @intCast(raw_y); z_plus_obscuring_pattern <<= 1; if (chunk.getTileRawShifted(if (sign == 1) x+1 else x-1, y, @intCast(z_end), dimention) != 0) z_plus_obscuring_pattern |= 1; } } var y_minus_obscuring_pattern: u32 = 0; if (x != (if (sign == 1) 31 else 0) and y_start != 0) { for (z_start..z_end) |raw_z| { const z: u5 = @intCast(raw_z); y_minus_obscuring_pattern <<= 1; if (chunk.getTileRawShifted(if (sign == 1) x+1 else x-1, @intCast(y_start - 1), z, dimention) != 0) y_minus_obscuring_pattern |= 1; } } var y_plus_obscuring_pattern: u32 = 0; if (x != (if (sign == 1) 31 else 0) and y_end != 32) { for (z_start..z_end) |raw_z| { const z: u5 = @intCast(raw_z); y_plus_obscuring_pattern <<= 1; if (chunk.getTileRawShifted(if (sign == 1) x+1 else x-1, @intCast(y_end), z, dimention) != 0) y_plus_obscuring_pattern |= 1; } } // std.debug.print("{}\n", .{z_minus_obscuring_pattern}); const raw_quad = pack_raw_quad(@floatFromInt(raw_x), y_start, y_end, z_start, z_end, sign, dimention, surface, y_minus_obscuring_pattern, y_plus_obscuring_pattern, z_minus_obscuring_pattern, z_plus_obscuring_pattern); try raw_quads.append(raw_quad); }; } } } // Create OpenGL buffers const triangle_count: i32 = @as(i32, @intCast(raw_quads.items.len)) * 2; const arr_size: u32 = @as(u32, @intCast(triangle_count)) * 3 * @sizeOf(f32); const vertices: [*]f32 = @ptrCast(@alignCast(raylib.MemAlloc(arr_size * 3))); const texcoords: [*]f32 = @ptrCast(@alignCast(raylib.MemAlloc(arr_size * 2))); const tiletexcoords: [*]f32 = @ptrCast(@alignCast(raylib.MemAlloc(arr_size * 2))); const normals: [*]f32 = @ptrCast(@alignCast(raylib.MemAlloc(arr_size * 3))); const metadata1_packed: [*]f32 = @ptrCast(@alignCast(raylib.MemAlloc(arr_size * 4))); for (raw_quads.items, 0..) |raw_quad, i| { if (raw_quad.tile <= 0) continue; // air tile, no texture const tile = raw_quad.tile; // Set normals for the quads (same as the triangles.) for (0..6) |j| { normals[18 * i + 3 * j + 0] = raw_quad.normal.x; normals[18 * i + 3 * j + 1] = raw_quad.normal.y; normals[18 * i + 3 * j + 2] = raw_quad.normal.z; } // Find UV coordinates of corresponding tiles. const left_uv = @as(f32, @floatFromInt(tile % tile_columns)) / @as(f32, @floatFromInt(tile_columns)); const right_uv = @as(f32, @floatFromInt(tile % tile_columns + 1)) / @as(f32, @floatFromInt(tile_columns)); const top_uv = @as(f32, @floatFromInt(tile / tile_columns)) / @as(f32, @floatFromInt(tile_rows)); const bottom_uv = @as(f32, @floatFromInt(tile / tile_columns + 1)) / @as(f32, @floatFromInt(tile_rows)); // Unwrap raw quads vertex coordinates and UV coordinates into OpenGL buffers. const vertex_corners = .{ raw_quad.top_left, raw_quad.bottom_left, raw_quad.top_right, raw_quad.bottom_right, raw_quad.top_right, raw_quad.bottom_left }; const texcoords_x = .{ left_uv, left_uv, right_uv, right_uv, right_uv, left_uv }; const texcoords_y = .{ top_uv, bottom_uv } ** 3; const tiletexcoords_x = .{ 0.0, 0.0, raw_quad.width, raw_quad.width, raw_quad.width, 0.0 }; const tiletexcoords_y = .{ 0.0, raw_quad.height } ** 3; inline for (0..6) |corner_id| { vertices[VERTICES_BLOCK_SIZE * i + corner_id * 3 + 0] = vertex_corners[corner_id].x; vertices[VERTICES_BLOCK_SIZE * i + corner_id * 3 + 1] = vertex_corners[corner_id].y; vertices[VERTICES_BLOCK_SIZE * i + corner_id * 3 + 2] = vertex_corners[corner_id].z; texcoords[TEXCOORDS_BLOCK_SIZE * i + corner_id * 2 + 0] = texcoords_x[corner_id]; texcoords[TEXCOORDS_BLOCK_SIZE * i + corner_id * 2 + 1] = texcoords_y[corner_id]; tiletexcoords[TEXCOORDS_BLOCK_SIZE * i + corner_id * 2 + 0] = tiletexcoords_x[corner_id]; tiletexcoords[TEXCOORDS_BLOCK_SIZE * i + corner_id * 2 + 1] = tiletexcoords_y[corner_id]; } // Store metadata into OpenGL buffers. for (0..3) |j| { const metadata1 = Metadata1{ .ambient_occlusion_1 = raw_quad.left_obscuring_pattern, .ambient_occlusion_2 = raw_quad.right_obscuring_pattern, .ambient_occlusion_corner1 = raw_quad.top_left_obscured, .ambient_occlusion_corner2 = raw_quad.bottom_left_obscured, .ambient_occlusion_corner3 = raw_quad.top_right_obscured, .quad_height = @intFromFloat(raw_quad.height), .quad_width = @intFromFloat(raw_quad.width), }; const metadata1_baked: [4]f32 = @bitCast(metadata1); for (0..4) |k| { metadata1_packed[24 * i + 4 * j + k] = @bitCast(@as(f32, metadata1_baked[k])); } } for (3..6) |j| { const metadata1 = Metadata1{ .ambient_occlusion_1 = raw_quad.right_obscuring_pattern, .ambient_occlusion_2 = raw_quad.bottom_obscuring_pattern, .ambient_occlusion_corner1 = raw_quad.bottom_right_obscured, .ambient_occlusion_corner2 = raw_quad.top_right_obscured, .ambient_occlusion_corner3 = raw_quad.bottom_left_obscured, .quad_height = @intFromFloat(raw_quad.height), .quad_width = @intFromFloat(raw_quad.width), }; const metadata1_baked: [4]f32 = @bitCast(metadata1); for (0..4) |k| { metadata1_packed[24 * i + 4 * j + k] = @bitCast(@as(f32, metadata1_baked[k])); } } } // Create mesh using the buffers. var mesh = raylib.ChunkMesh{ .triangleCount = triangle_count, .vertexCount = triangle_count * 3, .vertices = vertices, .texcoords = texcoords, .tiletexcoords = tiletexcoords, .normals = normals, .metadata1 = metadata1_packed, .vaoId = 0, .vboId = null, }; raylib.UploadChunkMesh(@ptrCast(&mesh), false); return mesh; } };