Extract an isosurface from a scalar field using marching cubes.
76{
77 res_x = std::max(res_x, 1U);
78 res_y = std::max(res_y, 1U);
79 res_z = std::max(res_z, 1U);
80
81 const glm::vec3 extent = bounds_max - bounds_min;
82 const glm::vec3 step {
83 extent.x / static_cast<float>(res_x),
84 extent.y / static_cast<float>(res_y),
85 extent.z / static_cast<float>(res_z),
86 };
87
88 const uint32_t nx = res_x + 1;
89 const uint32_t ny = res_y + 1;
90 const uint32_t nz = res_z + 1;
91
92 const uint32_t px = nx + 2;
93 const uint32_t py = ny + 2;
94 const uint32_t pz = nz + 2;
95 const size_t num_voxels = static_cast<size_t>(px) * py * pz;
96
97 const size_t sx = 1;
98 const size_t sy = px;
99 const size_t sz = static_cast<size_t>(py) * px;
100
101 std::vector<float> vals(num_voxels);
102 for (uint32_t iz = 0; iz < pz; ++iz) {
103 float z = bounds_min.z + static_cast<float>(static_cast<int32_t>(iz) - 1) * step.z;
104 for (uint32_t iy = 0; iy < py; ++iy) {
105 float y = bounds_min.y + static_cast<float>(static_cast<int32_t>(iy) - 1) * step.y;
106 size_t base_idx = (static_cast<size_t>(iz) * py + iy) * px;
107 for (uint32_t ix = 0; ix < px; ++ix) {
108 float x = bounds_min.x + static_cast<float>(static_cast<int32_t>(ix) - 1) * step.x;
109 vals[base_idx + ix] = field(glm::vec3(x, y, z));
110 }
111 }
112 }
113
114 std::vector<Kakshya::MeshVertex> verts;
115 std::vector<uint32_t> indices;
116 verts.reserve(static_cast<size_t>(res_x * res_y * res_z) * 3);
117 indices.reserve(static_cast<size_t>(res_x * res_y * res_z) * 3);
118
119 auto grad_at = [&](
size_t ptr) -> glm::vec3 {
120 glm::vec3 n {
121 (vals[
ptr + sx] - vals[
ptr - sx]) / step.x,
122 (vals[
ptr + sy] - vals[
ptr - sy]) / step.y,
123 (vals[
ptr + sz] - vals[
ptr - sz]) / step.z
124 };
125 float len = glm::length(n);
126 return len > 1e-7F ? (n / len) : glm::vec3(0.0F, 1.0F, 0.0F);
127 };
128
129 for (uint32_t iz = 0; iz < res_z; ++iz) {
130 float z0 = bounds_min.z + static_cast<float>(iz) * step.z;
131 float z1 = z0 + step.z;
132
133 for (uint32_t iy = 0; iy < res_y; ++iy) {
134 float y0 = bounds_min.y + static_cast<float>(iy) * step.y;
135 float y1 = y0 + step.y;
136
137 size_t voxel_ptr = (static_cast<size_t>(iz + 1) * py + (iy + 1)) * px + 1;
138
139 for (uint32_t ix = 0; ix < res_x; ++ix, ++voxel_ptr) {
140 const float v[8] = {
141 vals[voxel_ptr], vals[voxel_ptr + sx], vals[voxel_ptr + sy + sx], vals[voxel_ptr + sy],
142 vals[voxel_ptr + sz], vals[voxel_ptr + sz + sx], vals[voxel_ptr + sz + sy + sx], vals[voxel_ptr + sz + sy]
143 };
144
147 if (edges == 0)
148 continue;
149
150 float x0 = bounds_min.x + static_cast<float>(ix) * step.x;
151 float x1 = x0 + step.x;
152 const glm::vec3 c[8] = {
153 { x0, y0, z0 }, { x1, y0, z0 }, { x1, y1, z0 }, { x0, y1, z0 },
154 { x0, y0, z1 }, { x1, y0, z1 }, { x1, y1, z1 }, { x0, y1, z1 }
155 };
156
157 const glm::vec3 nrm[8] = {
158 grad_at(voxel_ptr), grad_at(voxel_ptr + sx), grad_at(voxel_ptr + sy + sx), grad_at(voxel_ptr + sy),
159 grad_at(voxel_ptr + sz), grad_at(voxel_ptr + sz + sx), grad_at(voxel_ptr + sz + sy + sx), grad_at(voxel_ptr + sz + sy)
160 };
161
162 InterpolatedEdge ep[12];
163
164 auto interp_edge = [&](int i1, int i2) -> InterpolatedEdge {
165 float va = v[i1], vb = v[i2];
166 float d = vb - va;
167 float t = (std::abs(d) < 1e-7F) ? 0.5F : ((iso_level - va) / d);
168
169 return {
170 .position = c[i1] + t * (c[i2] - c[i1]),
171 .normal = glm::normalize(nrm[i1] + t * (nrm[i2] - nrm[i1]))
172 };
173 };
174
175 if (edges & 0x001)
176 ep[0] = interp_edge(0, 1);
177 if (edges & 0x002)
178 ep[1] = interp_edge(1, 2);
179 if (edges & 0x004)
180 ep[2] = interp_edge(2, 3);
181 if (edges & 0x008)
182 ep[3] = interp_edge(3, 0);
183 if (edges & 0x010)
184 ep[4] = interp_edge(4, 5);
185 if (edges & 0x020)
186 ep[5] = interp_edge(5, 6);
187 if (edges & 0x040)
188 ep[6] = interp_edge(6, 7);
189 if (edges & 0x080)
190 ep[7] = interp_edge(7, 4);
191 if (edges & 0x100)
192 ep[8] = interp_edge(0, 4);
193 if (edges & 0x200)
194 ep[9] = interp_edge(1, 5);
195 if (edges & 0x400)
196 ep[10] = interp_edge(2, 6);
197 if (edges & 0x800)
198 ep[11] = interp_edge(3, 7);
199
201
202 for (int i = 0; tri[i] != -1; i += 3) {
203 const auto base = static_cast<uint32_t>(verts.size());
204
205 for (int j = 0; j < 3; ++j) {
206 const auto& edge_data = ep[tri[i + j]];
207 glm::vec2 uv((edge_data.position.x - bounds_min.x) / extent.x,
208 (edge_data.position.y - bounds_min.y) / extent.y);
209
210 verts.push_back({ .position = edge_data.position,
211 .uv = uv,
212 .normal = edge_data.normal });
213 }
214 indices.insert(indices.end(), { base, base + 1, base + 2 });
215 }
216 }
217 }
218 }
219
220 if (verts.empty())
221 return Kakshya::MeshData::empty();
222
223 auto data = Kakshya::MeshData::empty();
224 Kakshya::MeshInsertion ins(data.vertex_variant, data.index_variant);
225 ins.insert_flat(
226 std::span<const uint8_t>(reinterpret_cast<const uint8_t*>(verts.data()), verts.size() * sizeof(Kakshya::MeshVertex)),
227 std::span<const uint32_t>(indices),
228 Kakshya::VertexLayout::for_meshes(sizeof(Kakshya::MeshVertex)));
229
230 data.layout = Kakshya::VertexLayout::for_meshes(sizeof(Kakshya::MeshVertex));
231 data.layout.vertex_count = static_cast<uint32_t>(verts.size());
232 return data;
233}
uint32_t compute_cube_index(const float *v, float iso) noexcept
constexpr std::array< uint16_t, 256 > k_edge_table
constexpr std::array< std::array< int8_t, 16 >, 256 > k_tri_table