#include "pxl8_vxl_render.h" #include #include "pxl8_mem.h" #include "pxl8_noise.h" #include "pxl8_vxl.h" typedef struct pxl8_vxl_greedy_mask { u8 block[PXL8_VXL_CHUNK_SIZE][PXL8_VXL_CHUNK_SIZE]; bool done[PXL8_VXL_CHUNK_SIZE][PXL8_VXL_CHUNK_SIZE]; } pxl8_vxl_greedy_mask; static const pxl8_vec3 face_normals[6] = { {-1, 0, 0}, { 1, 0, 0}, { 0, -1, 0}, { 0, 1, 0}, { 0, 0, -1}, { 0, 0, 1} }; static const i32 face_dirs[6][3] = { {-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1} }; static inline bool vxl_in_bounds(i32 x, i32 y, i32 z) { return x >= 0 && x < PXL8_VXL_CHUNK_SIZE && y >= 0 && y < PXL8_VXL_CHUNK_SIZE && z >= 0 && z < PXL8_VXL_CHUNK_SIZE; } static bool block_is_opaque(u8 block) { return block != PXL8_VXL_BLOCK_AIR && block != PXL8_VXL_BLOCK_UNKNOWN; } static bool block_is_full_cube(u8 block, const pxl8_vxl_block_registry* registry) { if (block == PXL8_VXL_BLOCK_AIR) return false; pxl8_vxl_geometry geo = pxl8_vxl_block_registry_geometry(registry, block); return geo == PXL8_VXL_GEOMETRY_CUBE; } static u8 get_block_or_neighbor(const pxl8_vxl_chunk* chunk, const pxl8_vxl_chunk** neighbors, i32 x, i32 y, i32 z) { if (vxl_in_bounds(x, y, z)) { return pxl8_vxl_block_get(chunk, x, y, z); } i32 nx = x, ny = y, nz = z; i32 neighbor_idx = -1; if (x < 0) { neighbor_idx = 0; nx = x + PXL8_VXL_CHUNK_SIZE; } else if (x >= PXL8_VXL_CHUNK_SIZE) { neighbor_idx = 1; nx = x - PXL8_VXL_CHUNK_SIZE; } else if (y < 0) { neighbor_idx = 2; ny = y + PXL8_VXL_CHUNK_SIZE; } else if (y >= PXL8_VXL_CHUNK_SIZE) { neighbor_idx = 3; ny = y - PXL8_VXL_CHUNK_SIZE; } else if (z < 0) { neighbor_idx = 4; nz = z + PXL8_VXL_CHUNK_SIZE; } else if (z >= PXL8_VXL_CHUNK_SIZE) { neighbor_idx = 5; nz = z - PXL8_VXL_CHUNK_SIZE; } if (neighbor_idx >= 0 && neighbors && neighbors[neighbor_idx]) { return pxl8_vxl_block_get(neighbors[neighbor_idx], nx, ny, nz); } return PXL8_VXL_BLOCK_UNKNOWN; } static f32 compute_ao(const pxl8_vxl_chunk* chunk, const pxl8_vxl_chunk** neighbors, i32 x, i32 y, i32 z, i32 dx1, i32 dy1, i32 dz1, i32 dx2, i32 dy2, i32 dz2) { u8 s1 = get_block_or_neighbor(chunk, neighbors, x + dx1, y + dy1, z + dz1); u8 s2 = get_block_or_neighbor(chunk, neighbors, x + dx2, y + dy2, z + dz2); u8 corner = get_block_or_neighbor(chunk, neighbors, x + dx1 + dx2, y + dy1 + dy2, z + dz1 + dz2); bool side1 = block_is_opaque(s1); bool side2 = block_is_opaque(s2); bool has_corner = block_is_opaque(corner); if (side1 && side2) return 0.0f; return (3.0f - (f32)side1 - (f32)side2 - (f32)has_corner) / 3.0f; } static f32 sample_corner_displacement(const pxl8_vxl_chunk* chunk, const pxl8_vxl_chunk** neighbors, i32 cx, i32 cz, i32 base_y) { f32 sum = 0.0f; i32 count = 0; for (i32 dz = -1; dz <= 0; dz++) { for (i32 dx = -1; dx <= 0; dx++) { i32 x = cx + dx; i32 z = cz + dz; u8 block = get_block_or_neighbor(chunk, neighbors, x, base_y, z); u8 above = get_block_or_neighbor(chunk, neighbors, x, base_y + 1, z); if (block_is_opaque(block) && !block_is_opaque(above)) { sum += 1.0f; } else if (!block_is_opaque(block)) { sum -= 1.0f; } count++; } } return count > 0 ? (sum / (f32)count) * 0.15f : 0.0f; } static void slice_to_world(i32 face, i32 slice, i32 u, i32 v, i32* x, i32* y, i32* z) { switch (face) { case 0: *x = slice; *y = v; *z = u; break; case 1: *x = slice; *y = v; *z = u; break; case 2: *x = u; *y = slice; *z = v; break; case 3: *x = u; *y = slice; *z = v; break; case 4: *x = u; *y = v; *z = slice; break; case 5: *x = u; *y = v; *z = slice; break; } } static f32 terrain_noise(f32 wx, f32 wz, u64 seed) { f32 noise = pxl8_fbm(wx * 0.08f, wz * 0.08f, seed, 3); return (noise - 0.5f) * 0.4f; } static void emit_greedy_quad(pxl8_mesh* mesh, const pxl8_vxl_chunk* chunk, const pxl8_vxl_chunk** neighbors, const pxl8_vxl_mesh_config* config, i32 face, i32 slice, i32 u, i32 v, i32 width, i32 height, u8 texture_id, f32 ao_avg, f32 ao_strength) { pxl8_vec3 normal = face_normals[face]; u8 light = (u8)(255.0f * (1.0f - ao_strength * (1.0f - ao_avg))); f32 p[4][3]; switch (face) { case 0: p[0][0] = (f32)slice; p[0][1] = (f32)v; p[0][2] = (f32)(u + width); p[1][0] = (f32)slice; p[1][1] = (f32)v; p[1][2] = (f32)u; p[2][0] = (f32)slice; p[2][1] = (f32)(v + height); p[2][2] = (f32)u; p[3][0] = (f32)slice; p[3][1] = (f32)(v + height); p[3][2] = (f32)(u + width); break; case 1: p[0][0] = (f32)(slice + 1); p[0][1] = (f32)v; p[0][2] = (f32)u; p[1][0] = (f32)(slice + 1); p[1][1] = (f32)v; p[1][2] = (f32)(u + width); p[2][0] = (f32)(slice + 1); p[2][1] = (f32)(v + height); p[2][2] = (f32)(u + width); p[3][0] = (f32)(slice + 1); p[3][1] = (f32)(v + height); p[3][2] = (f32)u; break; case 2: p[0][0] = (f32)u; p[0][1] = (f32)slice; p[0][2] = (f32)v; p[1][0] = (f32)(u + width); p[1][1] = (f32)slice; p[1][2] = (f32)v; p[2][0] = (f32)(u + width); p[2][1] = (f32)slice; p[2][2] = (f32)(v + height); p[3][0] = (f32)u; p[3][1] = (f32)slice; p[3][2] = (f32)(v + height); break; case 3: { f32 base_y = (f32)(slice + 1); f32 wx0 = (f32)(config->chunk_x * PXL8_VXL_CHUNK_SIZE + u); f32 wz0 = (f32)(config->chunk_z * PXL8_VXL_CHUNK_SIZE + v); f32 d0 = sample_corner_displacement(chunk, neighbors, u, v + height, slice); f32 d1 = sample_corner_displacement(chunk, neighbors, u + width, v + height, slice); f32 d2 = sample_corner_displacement(chunk, neighbors, u + width, v, slice); f32 d3 = sample_corner_displacement(chunk, neighbors, u, v, slice); f32 n0 = terrain_noise(wx0, wz0 + (f32)height, config->seed); f32 n1 = terrain_noise(wx0 + (f32)width, wz0 + (f32)height, config->seed); f32 n2 = terrain_noise(wx0 + (f32)width, wz0, config->seed); f32 n3 = terrain_noise(wx0, wz0, config->seed); p[0][0] = (f32)u; p[0][1] = base_y + d0 + n0; p[0][2] = (f32)(v + height); p[1][0] = (f32)(u + width); p[1][1] = base_y + d1 + n1; p[1][2] = (f32)(v + height); p[2][0] = (f32)(u + width); p[2][1] = base_y + d2 + n2; p[2][2] = (f32)v; p[3][0] = (f32)u; p[3][1] = base_y + d3 + n3; p[3][2] = (f32)v; pxl8_vec3 e1 = {p[1][0] - p[0][0], p[1][1] - p[0][1], p[1][2] - p[0][2]}; pxl8_vec3 e2 = {p[3][0] - p[0][0], p[3][1] - p[0][1], p[3][2] - p[0][2]}; normal = (pxl8_vec3){ e1.y * e2.z - e1.z * e2.y, e1.z * e2.x - e1.x * e2.z, e1.x * e2.y - e1.y * e2.x }; f32 len_sq = normal.x * normal.x + normal.y * normal.y + normal.z * normal.z; if (len_sq > 0.0001f) { f32 inv_len = pxl8_fast_inv_sqrt(len_sq); normal.x *= inv_len; normal.y *= inv_len; normal.z *= inv_len; } else { normal = (pxl8_vec3){0, 1, 0}; } break; } case 4: p[0][0] = (f32)u; p[0][1] = (f32)v; p[0][2] = (f32)slice; p[1][0] = (f32)u; p[1][1] = (f32)(v + height); p[1][2] = (f32)slice; p[2][0] = (f32)(u + width); p[2][1] = (f32)(v + height); p[2][2] = (f32)slice; p[3][0] = (f32)(u + width); p[3][1] = (f32)v; p[3][2] = (f32)slice; break; case 5: p[0][0] = (f32)(u + width); p[0][1] = (f32)v; p[0][2] = (f32)(slice + 1); p[1][0] = (f32)(u + width); p[1][1] = (f32)(v + height); p[1][2] = (f32)(slice + 1); p[2][0] = (f32)u; p[2][1] = (f32)(v + height); p[2][2] = (f32)(slice + 1); p[3][0] = (f32)u; p[3][1] = (f32)v; p[3][2] = (f32)(slice + 1); break; } f32 tex_u = (f32)width; f32 tex_v = (f32)height; pxl8_vertex verts[4]; for (i32 i = 0; i < 4; i++) { verts[i].position.x = p[i][0]; verts[i].position.y = p[i][1]; verts[i].position.z = p[i][2]; verts[i].normal = normal; verts[i].color = texture_id; verts[i].light = light; } verts[0].u = 0; verts[0].v = tex_v; verts[1].u = tex_u; verts[1].v = tex_v; verts[2].u = tex_u; verts[2].v = 0; verts[3].u = 0; verts[3].v = 0; u16 i0 = pxl8_mesh_push_vertex(mesh, verts[0]); u16 i1 = pxl8_mesh_push_vertex(mesh, verts[1]); u16 i2 = pxl8_mesh_push_vertex(mesh, verts[2]); u16 i3 = pxl8_mesh_push_vertex(mesh, verts[3]); pxl8_mesh_push_quad(mesh, i0, i1, i2, i3); } static void build_greedy_slice(const pxl8_vxl_chunk* chunk, const pxl8_vxl_chunk** neighbors, const pxl8_vxl_block_registry* registry, const pxl8_vxl_mesh_config* config, i32 face, i32 slice, pxl8_mesh* mesh) { pxl8_vxl_greedy_mask mask; memset(&mask, 0, sizeof(mask)); i32 dx = face_dirs[face][0]; i32 dy = face_dirs[face][1]; i32 dz = face_dirs[face][2]; for (i32 v = 0; v < PXL8_VXL_CHUNK_SIZE; v++) { for (i32 u = 0; u < PXL8_VXL_CHUNK_SIZE; u++) { i32 x, y, z; slice_to_world(face, slice, u, v, &x, &y, &z); u8 block = pxl8_vxl_block_get(chunk, x, y, z); if (block == PXL8_VXL_BLOCK_AIR) continue; pxl8_vxl_geometry geo = pxl8_vxl_block_registry_geometry(registry, block); if (geo != PXL8_VXL_GEOMETRY_CUBE) continue; i32 nx = x + dx; i32 ny = y + dy; i32 nz = z + dz; u8 neighbor = get_block_or_neighbor(chunk, neighbors, nx, ny, nz); if (neighbor == PXL8_VXL_BLOCK_UNKNOWN) continue; if (block_is_full_cube(neighbor, registry)) continue; mask.block[u][v] = block; } } f32 ao_strength = config->ambient_occlusion ? config->ao_strength : 0.0f; for (i32 v = 0; v < PXL8_VXL_CHUNK_SIZE; v++) { for (i32 u = 0; u < PXL8_VXL_CHUNK_SIZE; u++) { if (mask.done[u][v] || mask.block[u][v] == 0) continue; u8 block = mask.block[u][v]; u8 texture_id = pxl8_vxl_block_registry_texture_for_face(registry, block, face); i32 width = 1; while (u + width < PXL8_VXL_CHUNK_SIZE && !mask.done[u + width][v] && mask.block[u + width][v] == block) { width++; } i32 height = 1; bool can_expand = true; while (can_expand && v + height < PXL8_VXL_CHUNK_SIZE) { for (i32 wu = 0; wu < width; wu++) { if (mask.done[u + wu][v + height] || mask.block[u + wu][v + height] != block) { can_expand = false; break; } } if (can_expand) height++; } for (i32 dv = 0; dv < height; dv++) { for (i32 du = 0; du < width; du++) { mask.done[u + du][v + dv] = true; } } f32 ao_sum = 0.0f; i32 ao_count = 0; if (config->ambient_occlusion) { i32 cx, cy, cz; slice_to_world(face, slice, u, v, &cx, &cy, &cz); i32 fx = cx + dx; i32 fy = cy + dy; i32 fz = cz + dz; static const i32 ao_offsets[6][4][2][3] = { [0] = {{{0,-1,0}, {0,0,1}}, {{0,-1,0}, {0,0,-1}}, {{0,1,0}, {0,0,-1}}, {{0,1,0}, {0,0,1}}}, [1] = {{{0,-1,0}, {0,0,-1}}, {{0,-1,0}, {0,0,1}}, {{0,1,0}, {0,0,1}}, {{0,1,0}, {0,0,-1}}}, [2] = {{{-1,0,0}, {0,0,-1}}, {{1,0,0}, {0,0,-1}}, {{1,0,0}, {0,0,1}}, {{-1,0,0}, {0,0,1}}}, [3] = {{{-1,0,0}, {0,0,1}}, {{1,0,0}, {0,0,1}}, {{1,0,0}, {0,0,-1}}, {{-1,0,0}, {0,0,-1}}}, [4] = {{{-1,0,0}, {0,1,0}}, {{-1,0,0}, {0,-1,0}}, {{1,0,0}, {0,-1,0}}, {{1,0,0}, {0,1,0}}}, [5] = {{{1,0,0}, {0,-1,0}}, {{1,0,0}, {0,1,0}}, {{-1,0,0}, {0,1,0}}, {{-1,0,0}, {0,-1,0}}} }; for (i32 corner = 0; corner < 4; corner++) { f32 ao = compute_ao(chunk, neighbors, fx, fy, fz, ao_offsets[face][corner][0][0], ao_offsets[face][corner][0][1], ao_offsets[face][corner][0][2], ao_offsets[face][corner][1][0], ao_offsets[face][corner][1][1], ao_offsets[face][corner][1][2]); ao_sum += ao; ao_count++; } } f32 ao_avg = ao_count > 0 ? ao_sum / (f32)ao_count : 1.0f; emit_greedy_quad(mesh, chunk, neighbors, config, face, slice, u, v, width, height, texture_id, ao_avg, ao_strength); } } } static void add_face_vertices(pxl8_mesh* mesh, const pxl8_vxl_mesh_config* config, pxl8_vec3 pos, i32 face, u8 texture_id, f32 ao0, f32 ao1, f32 ao2, f32 ao3) { static const i32 face_vertices[6][4][3] = { {{0,0,1}, {0,0,0}, {0,1,0}, {0,1,1}}, {{1,0,0}, {1,0,1}, {1,1,1}, {1,1,0}}, {{0,0,0}, {1,0,0}, {1,0,1}, {0,0,1}}, {{0,1,1}, {1,1,1}, {1,1,0}, {0,1,0}}, {{0,0,0}, {0,1,0}, {1,1,0}, {1,0,0}}, {{1,0,1}, {1,1,1}, {0,1,1}, {0,0,1}} }; static const f32 face_uvs[4][2] = { {0, 1}, {1, 1}, {1, 0}, {0, 0} }; f32 ao_values[4] = {ao0, ao1, ao2, ao3}; f32 ao_strength = config->ambient_occlusion ? config->ao_strength : 0.0f; f32 tex_scale = config->texture_scale; pxl8_vec3 normal = face_normals[face]; u16 indices[4]; for (i32 i = 0; i < 4; i++) { pxl8_vertex v = {0}; v.position.x = pos.x + (f32)face_vertices[face][i][0]; v.position.y = pos.y + (f32)face_vertices[face][i][1]; v.position.z = pos.z + (f32)face_vertices[face][i][2]; v.normal = normal; v.u = face_uvs[i][0] * tex_scale; v.v = face_uvs[i][1] * tex_scale; v.color = texture_id; v.light = (u8)(255.0f * (1.0f - ao_strength * (1.0f - ao_values[i]))); indices[i] = pxl8_mesh_push_vertex(mesh, v); } if (ao0 + ao2 > ao1 + ao3) { pxl8_mesh_push_quad(mesh, indices[0], indices[1], indices[2], indices[3]); } else { pxl8_mesh_push_triangle(mesh, indices[0], indices[1], indices[2]); pxl8_mesh_push_triangle(mesh, indices[0], indices[2], indices[3]); } } static void add_slab_faces(pxl8_mesh* mesh, const pxl8_vxl_chunk* chunk, const pxl8_vxl_chunk** neighbors, const pxl8_vxl_block_registry* registry, const pxl8_vxl_mesh_config* config, i32 x, i32 y, i32 z, u8 block, bool top) { u8 texture_id = pxl8_vxl_block_registry_texture(registry, block); f32 y_offset = top ? 0.5f : 0.0f; pxl8_vec3 pos = {(f32)x, (f32)y + y_offset, (f32)z}; static const i32 horiz_faces[4] = {0, 1, 4, 5}; for (i32 i = 0; i < 4; i++) { i32 face = horiz_faces[i]; i32 nx = x + face_dirs[face][0]; i32 nz = z + face_dirs[face][2]; u8 neighbor = get_block_or_neighbor(chunk, neighbors, nx, y, nz); if (neighbor == PXL8_VXL_BLOCK_UNKNOWN) continue; if (block_is_full_cube(neighbor, registry)) continue; add_face_vertices(mesh, config, pos, face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f); } i32 top_face = 3; i32 bot_face = 2; if (top) { u8 above = get_block_or_neighbor(chunk, neighbors, x, y + 1, z); if (above != PXL8_VXL_BLOCK_UNKNOWN && !block_is_full_cube(above, registry)) { add_face_vertices(mesh, config, pos, top_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f); } add_face_vertices(mesh, config, (pxl8_vec3){(f32)x, (f32)y + 0.5f, (f32)z}, bot_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f); } else { add_face_vertices(mesh, config, pos, top_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f); u8 below = get_block_or_neighbor(chunk, neighbors, x, y - 1, z); if (below != PXL8_VXL_BLOCK_UNKNOWN && !block_is_full_cube(below, registry)) { add_face_vertices(mesh, config, pos, bot_face, texture_id, 1.0f, 1.0f, 1.0f, 1.0f); } } } static void add_slope_faces(pxl8_mesh* mesh, const pxl8_vxl_chunk* chunk, const pxl8_vxl_chunk** neighbors, const pxl8_vxl_block_registry* registry, const pxl8_vxl_mesh_config* config, i32 x, i32 y, i32 z, u8 block, i32 direction) { u8 texture_top = pxl8_vxl_block_registry_texture_for_face(registry, block, 3); u8 texture_side = pxl8_vxl_block_registry_texture_for_face(registry, block, 0); pxl8_vec3 pos = {(f32)x, (f32)y, (f32)z}; u8 below = get_block_or_neighbor(chunk, neighbors, x, y - 1, z); if (below != PXL8_VXL_BLOCK_UNKNOWN && !block_is_full_cube(below, registry)) { add_face_vertices(mesh, config, pos, 2, texture_side, 1.0f, 1.0f, 1.0f, 1.0f); } static const i32 dir_to_back_face[4] = {5, 4, 1, 0}; i32 back_face = dir_to_back_face[direction]; i32 bx = x + face_dirs[back_face][0]; i32 bz = z + face_dirs[back_face][2]; u8 back_neighbor = get_block_or_neighbor(chunk, neighbors, bx, y, bz); if (back_neighbor != PXL8_VXL_BLOCK_UNKNOWN && !block_is_full_cube(back_neighbor, registry)) { add_face_vertices(mesh, config, pos, back_face, texture_side, 1.0f, 1.0f, 1.0f, 1.0f); } static const f32 slope_verts[4][4][3] = { {{0,0,0}, {1,0,0}, {1,1,1}, {0,1,1}}, {{0,0,1}, {1,0,1}, {1,1,0}, {0,1,0}}, {{1,0,0}, {1,0,1}, {0,1,1}, {0,1,0}}, {{0,0,0}, {0,0,1}, {1,1,1}, {1,1,0}} }; static const pxl8_vec3 slope_normals[4] = { {0, 0.707f, -0.707f}, {0, 0.707f, 0.707f}, {-0.707f, 0.707f, 0}, {0.707f, 0.707f, 0} }; pxl8_vertex verts[4]; memset(verts, 0, sizeof(verts)); for (i32 i = 0; i < 4; i++) { verts[i].position.x = pos.x + slope_verts[direction][i][0]; verts[i].position.y = pos.y + slope_verts[direction][i][1]; verts[i].position.z = pos.z + slope_verts[direction][i][2]; verts[i].normal = slope_normals[direction]; verts[i].color = texture_top; verts[i].light = 255; } u16 i0 = pxl8_mesh_push_vertex(mesh, verts[0]); u16 i1 = pxl8_mesh_push_vertex(mesh, verts[1]); u16 i2 = pxl8_mesh_push_vertex(mesh, verts[2]); u16 i3 = pxl8_mesh_push_vertex(mesh, verts[3]); pxl8_mesh_push_quad(mesh, i0, i1, i2, i3); static const i32 side_faces[4][2] = {{0, 1}, {0, 1}, {4, 5}, {4, 5}}; static const f32 side_tris[4][2][3][3] = { {{{0,0,0}, {0,1,1}, {0,0,1}}, {{1,0,0}, {1,0,1}, {1,1,1}}}, {{{0,0,0}, {0,0,1}, {0,1,0}}, {{1,0,0}, {1,1,0}, {1,0,1}}}, {{{0,0,0}, {0,0,1}, {0,1,1}}, {{1,0,0}, {1,1,0}, {1,0,1}}}, {{{0,0,0}, {0,1,0}, {0,0,1}}, {{1,0,0}, {1,0,1}, {1,1,1}}} }; static const pxl8_vec3 side_normals[6] = { {-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1} }; for (i32 s = 0; s < 2; s++) { i32 side_face = side_faces[direction][s]; i32 snx = x + face_dirs[side_face][0]; i32 snz = z + face_dirs[side_face][2]; u8 side_neighbor = get_block_or_neighbor(chunk, neighbors, snx, y, snz); if (side_neighbor != PXL8_VXL_BLOCK_UNKNOWN && !block_is_full_cube(side_neighbor, registry)) { pxl8_vertex tv[3]; memset(tv, 0, sizeof(tv)); for (i32 vi = 0; vi < 3; vi++) { tv[vi].position.x = pos.x + side_tris[direction][s][vi][0]; tv[vi].position.y = pos.y + side_tris[direction][s][vi][1]; tv[vi].position.z = pos.z + side_tris[direction][s][vi][2]; tv[vi].normal = side_normals[side_face]; tv[vi].color = texture_side; tv[vi].light = 255; } u16 ti0 = pxl8_mesh_push_vertex(mesh, tv[0]); u16 ti1 = pxl8_mesh_push_vertex(mesh, tv[1]); u16 ti2 = pxl8_mesh_push_vertex(mesh, tv[2]); pxl8_mesh_push_triangle(mesh, ti0, ti1, ti2); } } } pxl8_mesh* pxl8_vxl_build_mesh(const pxl8_vxl_chunk* chunk, const pxl8_vxl_chunk** neighbors, const pxl8_vxl_block_registry* registry, const pxl8_vxl_mesh_config* config) { if (!chunk || !registry) return NULL; pxl8_vxl_mesh_config cfg = config ? *config : PXL8_VXL_MESH_CONFIG_DEFAULT; u32 max_faces = PXL8_VXL_CHUNK_VOLUME * 6; pxl8_mesh* mesh = pxl8_mesh_create(max_faces * 4, max_faces * 6); if (!mesh) return NULL; for (i32 face = 0; face < 6; face++) { for (i32 slice = 0; slice < PXL8_VXL_CHUNK_SIZE; slice++) { build_greedy_slice(chunk, neighbors, registry, &cfg, face, slice, mesh); } } for (i32 z = 0; z < PXL8_VXL_CHUNK_SIZE; z++) { for (i32 y = 0; y < PXL8_VXL_CHUNK_SIZE; y++) { for (i32 x = 0; x < PXL8_VXL_CHUNK_SIZE; x++) { u8 block = pxl8_vxl_block_get(chunk, x, y, z); if (block == PXL8_VXL_BLOCK_AIR) continue; pxl8_vxl_geometry geo = pxl8_vxl_block_registry_geometry(registry, block); switch (geo) { case PXL8_VXL_GEOMETRY_CUBE: break; case PXL8_VXL_GEOMETRY_SLAB_BOTTOM: add_slab_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, false); break; case PXL8_VXL_GEOMETRY_SLAB_TOP: add_slab_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, true); break; case PXL8_VXL_GEOMETRY_SLOPE_NORTH: add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 0); break; case PXL8_VXL_GEOMETRY_SLOPE_SOUTH: add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 1); break; case PXL8_VXL_GEOMETRY_SLOPE_EAST: add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 2); break; case PXL8_VXL_GEOMETRY_SLOPE_WEST: add_slope_faces(mesh, chunk, neighbors, registry, &cfg, x, y, z, block, 3); break; case PXL8_VXL_GEOMETRY_STAIRS_NORTH: case PXL8_VXL_GEOMETRY_STAIRS_SOUTH: case PXL8_VXL_GEOMETRY_STAIRS_EAST: case PXL8_VXL_GEOMETRY_STAIRS_WEST: break; } } } } return mesh; } pxl8_vxl_render_state* pxl8_vxl_render_state_create(void) { return pxl8_calloc(1, sizeof(pxl8_vxl_render_state)); } void pxl8_vxl_render_state_destroy(pxl8_vxl_render_state* state) { pxl8_free(state); }