#include "voxel_mesher_transvoxel.h" #include "transvoxel_tables.cpp" #include inline float tof(int8_t v) { return static_cast(v) / 256.f; } inline int8_t tos(uint8_t v) { return v - 128; } // Values considered negative have a sign bit of 1 inline uint8_t sign(int8_t v) { return (v >> 7) & 1; } // // 6-------7 // /| /| // / | / | Corners // 4-------5 | // | 2----|--3 // | / | / z y // |/ |/ |/ // 0-------1 o--x // const Vector3i g_corner_dirs[8] = { Vector3i(0, 0, 0), Vector3i(1, 0, 0), Vector3i(0, 1, 0), Vector3i(1, 1, 0), Vector3i(0, 0, 1), Vector3i(1, 0, 1), Vector3i(0, 1, 1), Vector3i(1, 1, 1) }; inline Vector3i dir_to_prev_vec(uint8_t dir) { //return g_corner_dirs[mask] - Vector3(1,1,1); return Vector3i( -(dir & 1), -((dir >> 1) & 1), -((dir >> 2) & 1)); } template void copy_to(PoolVector &to, Vector &from) { to.resize(from.size()); typename PoolVector::Write w = to.write(); for (unsigned int i = 0; i < from.size(); ++i) { w[i] = from[i]; } } VoxelMesherTransvoxel::ReuseCell::ReuseCell() { case_index = 0; for (unsigned int i = 0; i < 4; ++i) { vertices[i] = -1; } } VoxelMesherTransvoxel::VoxelMesherTransvoxel() { } Ref VoxelMesherTransvoxel::build_mesh(Ref voxels_ref, unsigned int channel, Ref mesh) { ERR_FAIL_COND_V(voxels_ref.is_null(), Ref()); VoxelBuffer &buffer = **voxels_ref; Array surfaces = build(buffer, channel); if (mesh.is_null()) mesh.instance(); //int surface = mesh->get_surface_count(); for (int i = 0; i < surfaces.size(); ++i) { Array arrays = surfaces[i]; mesh->add_surface_from_arrays(Mesh::PRIMITIVE_TRIANGLES, arrays); //mesh->surface_set_material(surface, _materials[i]); } return mesh; } Array VoxelMesherTransvoxel::build(const VoxelBuffer &voxels, unsigned int channel) { ERR_FAIL_COND_V(channel >= VoxelBuffer::MAX_CHANNELS, Array()); // Initialize dynamic memory: // These vectors are re-used. // We don't know in advance how much geometry we are going to produce. // Once capacity is big enough, no more memory should be allocated m_output_vertices.clear(); //m_output_vertices_secondary.clear(); m_output_normals.clear(); m_output_indices.clear(); build_internal(voxels, channel); // OS::get_singleton()->print("vertices: %i, normals: %i, indices: %i\n", // m_output_vertices.size(), // m_output_normals.size(), // m_output_indices.size()); if (m_output_vertices.size() == 0) { // The mesh can be empty return Array(); } PoolVector vertices; PoolVector normals; PoolVector indices; copy_to(vertices, m_output_vertices); copy_to(normals, m_output_normals); copy_to(indices, m_output_indices); Array arrays; arrays.resize(Mesh::ARRAY_MAX); arrays[Mesh::ARRAY_VERTEX] = vertices; if (m_output_normals.size() != 0) { arrays[Mesh::ARRAY_NORMAL] = normals; } arrays[Mesh::ARRAY_INDEX] = indices; Array surfaces; surfaces.append(arrays); return surfaces; } void VoxelMesherTransvoxel::build_internal(const VoxelBuffer &voxels, unsigned int channel) { // Each 2x2 voxel group is a "cell" if (voxels.is_uniform(channel)) { // Nothing to extract, because constant isolevels never cross the threshold and describe no surface return; } const Vector3i block_size = voxels.get_size(); // TODO No lod yet, but it's planned const int lod_index = 0; const int lod_scale = 1 << lod_index; // Prepare vertex reuse cache m_block_size = block_size; unsigned int deck_area = block_size.x * block_size.y; for (int i = 0; i < 2; ++i) { if (m_cache[i].size() != deck_area) { m_cache[i].clear(); // Clear any previous data m_cache[i].resize(deck_area); } } // Iterate all cells with padding (expected to be neighbors) Vector3i pos; for (pos.z = PAD.z; pos.z < block_size.z - 2; ++pos.z) { for (pos.y = PAD.y; pos.y < block_size.y - 2; ++pos.y) { for (pos.x = PAD.x; pos.x < block_size.x - 2; ++pos.x) { // Get the value of cells. // Negative values are "solid" and positive are "air". // Due to raw cells being unsigned 8-bit, they get converted to signed. int8_t cell_samples[8] = { tos(voxels.get_voxel(pos.x, pos.y, pos.z, channel)), tos(voxels.get_voxel(pos.x + 1, pos.y, pos.z, channel)), tos(voxels.get_voxel(pos.x, pos.y + 1, pos.z, channel)), tos(voxels.get_voxel(pos.x + 1, pos.y + 1, pos.z, channel)), tos(voxels.get_voxel(pos.x, pos.y, pos.z + 1, channel)), tos(voxels.get_voxel(pos.x + 1, pos.y, pos.z + 1, channel)), tos(voxels.get_voxel(pos.x, pos.y + 1, pos.z + 1, channel)), tos(voxels.get_voxel(pos.x + 1, pos.y + 1, pos.z + 1, channel)) }; // Concatenate the sign of cell values to obtain the case code. // Index 0 is the less significant bit, and index 7 is the most significant bit. uint8_t case_code = sign(cell_samples[0]); case_code |= (sign(cell_samples[1]) << 1); case_code |= (sign(cell_samples[2]) << 2); case_code |= (sign(cell_samples[3]) << 3); case_code |= (sign(cell_samples[4]) << 4); case_code |= (sign(cell_samples[5]) << 5); case_code |= (sign(cell_samples[6]) << 6); case_code |= (sign(cell_samples[7]) << 7); { ReuseCell &rc = get_reuse_cell(pos); rc.case_index = case_code; } if (case_code == 0 || case_code == 255) { // If the case_code is 0 or 255, there is no triangulation to do continue; } // TODO We might not always need all of them // Compute normals Vector3 corner_normals[8]; for (unsigned int i = 0; i < 8; ++i) { Vector3i p = pos + g_corner_dirs[i]; float nx = tof(tos(voxels.get_voxel(p - Vector3i(1, 0, 0), channel))) - tof(tos(voxels.get_voxel(p + Vector3i(1, 0, 0), channel))); float ny = tof(tos(voxels.get_voxel(p - Vector3i(0, 1, 0), channel))) - tof(tos(voxels.get_voxel(p + Vector3i(0, 1, 0), channel))); float nz = tof(tos(voxels.get_voxel(p - Vector3i(0, 0, 1), channel))) - tof(tos(voxels.get_voxel(p + Vector3i(0, 0, 1), channel))); corner_normals[i] = Vector3(nx, ny, nz); corner_normals[i].normalize(); } // For cells occurring along the minimal boundaries of a block, // the preceding cells needed for vertex reuse may not exist. // In these cases, we allow new vertex creation on additional edges of a cell. // While iterating through the cells in a block, a 3-bit mask is maintained whose bits indicate // whether corresponding bits in a direction code are valid uint8_t direction_validity_mask = (pos.x > 1 ? 1 : 0) | ((pos.y > 1 ? 1 : 0) << 1) | ((pos.z > 1 ? 1 : 0) << 2); uint8_t regular_cell_class_index = Transvoxel::regularCellClass[case_code]; Transvoxel::RegularCellData regular_cell_class = Transvoxel::regularCellData[regular_cell_class_index]; uint8_t triangle_count = regular_cell_class.geometryCounts & 0x0f; uint8_t vertex_count = (regular_cell_class.geometryCounts & 0xf0) >> 4; int cell_mesh_indices[12]; // For each vertex in the case for (unsigned int i = 0; i < vertex_count; ++i) { // The case index maps to a list of 16-bit codes providing information about the edges on which the vertices lie. // The low byte of each 16-bit code contains the corner indexes of the edge’s endpoints in one nibble each, // and the high byte contains the mapping code shown in Figure 3.8(b) unsigned short rvd = Transvoxel::regularVertexData[case_code][i]; unsigned short edge_code_low = rvd & 0xff; unsigned short edge_code_high = (rvd >> 8) & 0xff; // Get corner indexes in the low nibble (always ordered so the higher comes last) uint8_t v0 = (edge_code_low >> 4) & 0xf; uint8_t v1 = edge_code_low & 0xf; ERR_FAIL_COND(v1 <= v0); // Get voxel values at the corners int sample0 = cell_samples[v0]; // called d0 in the paper int sample1 = cell_samples[v1]; // called d1 in the paper // TODO Zero-division is not mentionned in the paper?? ERR_FAIL_COND(sample1 == sample0); ERR_FAIL_COND(sample1 == 0 && sample0 == 0); // Get interpolation position // We use an 8-bit fraction, allowing the new vertex to be located at one of 257 possible // positions along the edge when both endpoints are included. int t = (sample1 << 8) / (sample1 - sample0); float t0 = static_cast(t) / 256.f; float t1 = static_cast(0x0100 - t) / 256.f; Vector3i p0 = pos + g_corner_dirs[v0]; Vector3i p1 = pos + g_corner_dirs[v1]; if (t & 0xff) { //OS::get_singleton()->print("A"); // Vertex lies in the interior of the edge. // Each edge of a cell is assigned an 8-bit code, as shown in Figure 3.8(b), // that provides a mapping to a preceding cell and the coincident edge on that preceding cell // for which new vertex creation was allowed. // The high nibble of this code indicates which direction to go in order to reach the correct preceding cell. // The bit values 1, 2, and 4 in this nibble indicate that we must subtract one // from the x, y, and/or z coordinate, respectively. uint8_t reuse_dir = (edge_code_high >> 4) & 0xf; uint8_t reuse_vertex_index = edge_code_high & 0xf; bool can_reuse = (reuse_dir & direction_validity_mask) == reuse_dir; if (can_reuse) { Vector3i cache_pos = pos + dir_to_prev_vec(reuse_dir); ReuseCell &prev_cell = get_reuse_cell(cache_pos); if (prev_cell.case_index == 0 || prev_cell.case_index == 255) { // TODO I don't think this can happen for non-corner vertices. cell_mesh_indices[i] = -1; } else { // Will reuse a previous vertice cell_mesh_indices[i] = prev_cell.vertices[reuse_vertex_index]; } } if (!can_reuse || cell_mesh_indices[i] == -1) { // Going to create a new vertice cell_mesh_indices[i] = m_output_vertices.size(); Vector3 pi = p0.to_vec3() * t0 + p1.to_vec3() * t1; Vector3 primary = pi; //pos.to_vec3() + pi; Vector3 normal = corner_normals[v0] * t0 + corner_normals[v1] * t1; emit_vertex(primary, normal); if (reuse_dir & 8) { // Store the generated vertex so that other cells can reuse it. ReuseCell &rc = get_reuse_cell(pos); rc.vertices[reuse_vertex_index] = cell_mesh_indices[i]; } } } else if (t == 0 && v1 == 7) { //OS::get_singleton()->print("B"); // This cell owns the vertex, so it should be created. cell_mesh_indices[i] = m_output_vertices.size(); Vector3 pi = p0.to_vec3() * t0 + p1.to_vec3() * t1; Vector3 primary = pi; //pos.to_vec3() + pi; Vector3 normal = corner_normals[v0] * t0 + corner_normals[v1] * t1; emit_vertex(primary, normal); ReuseCell &rc = get_reuse_cell(pos); rc.vertices[0] = cell_mesh_indices[i]; } else { // Always try to reuse previous vertices in these cases //OS::get_singleton()->print("C"); // A 3-bit direction code leading to the proper cell can easily be obtained by // inverting the 3-bit corner index (bitwise, by exclusive ORing with the number 7). // The corner index depends on the value of t, t = 0 means that we're at the higher // numbered endpoint. uint8_t reuse_dir = (t == 0 ? v1 ^ 7 : v0 ^ 7); bool can_reuse = (reuse_dir & direction_validity_mask) == reuse_dir; // Note: the only difference with similar code above is that we take vertice 0 in the `else` if (can_reuse) { Vector3i cache_pos = pos + dir_to_prev_vec(reuse_dir); ReuseCell prev_cell = get_reuse_cell(cache_pos); // The previous cell might not have any geometry, and we // might therefore have to create a new vertex anyway. if (prev_cell.case_index == 0 || prev_cell.case_index == 255) { cell_mesh_indices[i] = -1; } else { cell_mesh_indices[i] = prev_cell.vertices[0]; } } if (!can_reuse || cell_mesh_indices[i] < 0) { cell_mesh_indices[i] = m_output_vertices.size(); Vector3 pi = p0.to_vec3() * t0 + p1.to_vec3() * t1; Vector3 primary = pi; //pos.to_vec3() + pi; Vector3 normal = corner_normals[v0] * t0 + corner_normals[v1] * t1; emit_vertex(primary, normal); } } } // for each cell vertice //OS::get_singleton()->print("_"); for (int t = 0; t < triangle_count; ++t) { for (int i = 0; i < 3; ++i) { int index = cell_mesh_indices[regular_cell_class.vertexIndex[t * 3 + i]]; m_output_indices.push_back(index); } } } // x } // y } // z //OS::get_singleton()->print("\n"); } VoxelMesherTransvoxel::ReuseCell &VoxelMesherTransvoxel::get_reuse_cell(Vector3i pos) { int j = pos.z & 1; int i = pos.y * m_block_size.y + pos.x; return m_cache[j].write[i]; } void VoxelMesherTransvoxel::emit_vertex(Vector3 primary, Vector3 normal) { m_output_vertices.push_back(primary - PAD.to_vec3()); m_output_normals.push_back(normal); } void VoxelMesherTransvoxel::_bind_methods() { ClassDB::bind_method(D_METHOD("build", "voxels", "channel", "existing_mesh"), &VoxelMesherTransvoxel::build_mesh, DEFVAL(Variant())); }