1use super::backend_report::OrbitalGridReport;
11use super::context::{
12 bytes_to_f32_vec, ceil_div_u32, f32_slice_to_bytes, pack_uniform_values,
13 ComputeBindingDescriptor, ComputeBindingKind, ComputeDispatchDescriptor, GpuContext,
14 UniformValue,
15};
16use super::orbital_grid::GridParams;
17
18#[derive(Debug, Clone)]
20pub struct McOutput {
21 pub vertices: Vec<f32>,
23 pub normals: Vec<f32>,
25 pub indices: Vec<u32>,
27 pub n_triangles: usize,
29}
30
31pub fn marching_cubes_cpu(values: &[f64], params: &GridParams, isovalue: f64) -> McOutput {
33 let [nx, ny, nz] = params.dimensions;
34 let mut vertices = Vec::new();
35 let mut normals = Vec::new();
36 let mut indices = Vec::new();
37 let mut vertex_count = 0u32;
38
39 for ix in 0..nx.saturating_sub(1) {
40 for iy in 0..ny.saturating_sub(1) {
41 for iz in 0..nz.saturating_sub(1) {
42 let corners = [
43 values[params.flat_index(ix, iy, iz)],
44 values[params.flat_index(ix + 1, iy, iz)],
45 values[params.flat_index(ix + 1, iy + 1, iz)],
46 values[params.flat_index(ix, iy + 1, iz)],
47 values[params.flat_index(ix, iy, iz + 1)],
48 values[params.flat_index(ix + 1, iy, iz + 1)],
49 values[params.flat_index(ix + 1, iy + 1, iz + 1)],
50 values[params.flat_index(ix, iy + 1, iz + 1)],
51 ];
52
53 let mut cube_index = 0u8;
54 for (i, &val) in corners.iter().enumerate() {
55 if val > isovalue {
56 cube_index |= 1 << i;
57 }
58 }
59
60 let edge_bits = EDGE_TABLE[cube_index as usize];
61 if edge_bits == 0 {
62 continue;
63 }
64
65 let corner_positions = [
66 params.point(ix, iy, iz),
67 params.point(ix + 1, iy, iz),
68 params.point(ix + 1, iy + 1, iz),
69 params.point(ix, iy + 1, iz),
70 params.point(ix, iy, iz + 1),
71 params.point(ix + 1, iy, iz + 1),
72 params.point(ix + 1, iy + 1, iz + 1),
73 params.point(ix, iy + 1, iz + 1),
74 ];
75
76 let mut edge_vertices = [[0.0f64; 3]; 12];
77 for edge in 0..12 {
78 if edge_bits & (1 << edge) != 0 {
79 let (v0, v1) = EDGE_VERTICES[edge];
80 let p0 = corner_positions[v0];
81 let p1 = corner_positions[v1];
82 let val0 = corners[v0];
83 let val1 = corners[v1];
84
85 let t = if (val1 - val0).abs() > 1e-10 {
86 ((isovalue - val0) / (val1 - val0)).clamp(0.0, 1.0)
87 } else {
88 0.5
89 };
90
91 edge_vertices[edge] = [
92 p0[0] + t * (p1[0] - p0[0]),
93 p0[1] + t * (p1[1] - p0[1]),
94 p0[2] + t * (p1[2] - p0[2]),
95 ];
96 }
97 }
98
99 let tri_entry = &TRI_TABLE[cube_index as usize];
100 let mut i = 0;
101 while i < tri_entry.len() && tri_entry[i] != -1 {
102 let e0 = tri_entry[i] as usize;
103 let e1 = tri_entry[i + 1] as usize;
104 let e2 = tri_entry[i + 2] as usize;
105
106 let v0 = edge_vertices[e0];
107 let v1 = edge_vertices[e1];
108 let v2 = edge_vertices[e2];
109
110 let u = [v1[0] - v0[0], v1[1] - v0[1], v1[2] - v0[2]];
112 let v = [v2[0] - v0[0], v2[1] - v0[1], v2[2] - v0[2]];
113 let mut n = [
114 u[1] * v[2] - u[2] * v[1],
115 u[2] * v[0] - u[0] * v[2],
116 u[0] * v[1] - u[1] * v[0],
117 ];
118 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
119 if len > 1e-10 {
120 n[0] /= len;
121 n[1] /= len;
122 n[2] /= len;
123 }
124
125 for vert in &[v0, v1, v2] {
126 vertices.push(vert[0] as f32);
127 vertices.push(vert[1] as f32);
128 vertices.push(vert[2] as f32);
129 normals.push(n[0] as f32);
130 normals.push(n[1] as f32);
131 normals.push(n[2] as f32);
132 indices.push(vertex_count);
133 vertex_count += 1;
134 }
135
136 i += 3;
137 }
138 }
139 }
140 }
141
142 McOutput {
143 n_triangles: indices.len() / 3,
144 vertices,
145 normals,
146 indices,
147 }
148}
149
150pub fn smooth_mesh_normals(mesh: &mut McOutput) {
152 if mesh.vertices.is_empty() {
153 return;
154 }
155
156 let n_verts = mesh.vertices.len() / 3;
157 let tolerance = 1e-4f32;
158 let mut smoothed = vec![[0.0f32; 3]; n_verts];
159
160 for i in 0..n_verts {
161 let pi = [
162 mesh.vertices[i * 3],
163 mesh.vertices[i * 3 + 1],
164 mesh.vertices[i * 3 + 2],
165 ];
166 let mut nx = 0.0f32;
167 let mut ny = 0.0f32;
168 let mut nz = 0.0f32;
169 let mut count = 0u32;
170
171 for j in 0..n_verts {
172 let pj = [
173 mesh.vertices[j * 3],
174 mesh.vertices[j * 3 + 1],
175 mesh.vertices[j * 3 + 2],
176 ];
177 let dx = pi[0] - pj[0];
178 let dy = pi[1] - pj[1];
179 let dz = pi[2] - pj[2];
180 if dx * dx + dy * dy + dz * dz < tolerance * tolerance {
181 nx += mesh.normals[j * 3];
182 ny += mesh.normals[j * 3 + 1];
183 nz += mesh.normals[j * 3 + 2];
184 count += 1;
185 }
186 }
187
188 if count > 0 {
189 let len = (nx * nx + ny * ny + nz * nz).sqrt();
190 if len > 1e-10 {
191 smoothed[i] = [nx / len, ny / len, nz / len];
192 } else {
193 smoothed[i] = [
194 mesh.normals[i * 3],
195 mesh.normals[i * 3 + 1],
196 mesh.normals[i * 3 + 2],
197 ];
198 }
199 }
200 }
201
202 for i in 0..n_verts {
203 mesh.normals[i * 3] = smoothed[i][0];
204 mesh.normals[i * 3 + 1] = smoothed[i][1];
205 mesh.normals[i * 3 + 2] = smoothed[i][2];
206 }
207}
208
209pub fn marching_cubes_with_report(
213 values: &[f64],
214 params: &GridParams,
215 isovalue: f64,
216) -> (McOutput, OrbitalGridReport) {
217 let ctx = GpuContext::best_available();
218 if ctx.is_gpu_available() {
219 if let Ok(mesh) = marching_cubes_gpu(&ctx, values, params, isovalue) {
220 return (
221 mesh,
222 OrbitalGridReport {
223 backend: ctx.capabilities.backend.clone(),
224 used_gpu: true,
225 attempted_gpu: true,
226 n_points: params.n_points(),
227 note: format!("GPU marching cubes on {}", ctx.capabilities.backend),
228 },
229 );
230 }
231 }
232 let mesh = marching_cubes_cpu(values, params, isovalue);
233 (
234 mesh,
235 OrbitalGridReport {
236 backend: "CPU".to_string(),
237 used_gpu: false,
238 attempted_gpu: ctx.is_gpu_available(),
239 n_points: params.n_points(),
240 note: "CPU marching cubes".to_string(),
241 },
242 )
243}
244
245pub fn marching_cubes_gpu(
251 ctx: &GpuContext,
252 values: &[f64],
253 params: &GridParams,
254 isovalue: f64,
255) -> Result<McOutput, String> {
256 let [nx, ny, nz] = params.dimensions;
257 let vx = nx.saturating_sub(1).max(1);
258 let vy = ny.saturating_sub(1).max(1);
259 let vz = nz.saturating_sub(1).max(1);
260 let n_voxels = vx * vy * vz;
261
262 let max_floats_per_voxel = 5 * 3 * 6; let max_output = n_voxels * max_floats_per_voxel;
265
266 let values_f32: Vec<f32> = values.iter().map(|&v| v as f32).collect();
267
268 let params_bytes = pack_uniform_values(&[
269 UniformValue::F32(params.origin[0] as f32),
270 UniformValue::F32(params.origin[1] as f32),
271 UniformValue::F32(params.origin[2] as f32),
272 UniformValue::F32(params.spacing as f32),
273 UniformValue::U32(nx as u32),
274 UniformValue::U32(ny as u32),
275 UniformValue::U32(nz as u32),
276 UniformValue::F32(isovalue as f32),
277 ]);
278
279 let edge_table_u32: Vec<u32> = EDGE_TABLE.iter().map(|&v| v as u32).collect();
281 let edge_table_bytes: Vec<u8> = edge_table_u32
282 .iter()
283 .flat_map(|v| v.to_le_bytes())
284 .collect();
285
286 let mut tri_table_i32: Vec<i32> = Vec::with_capacity(256 * 16);
288 for row in &TRI_TABLE {
289 for &val in row.iter() {
290 tri_table_i32.push(val as i32);
291 }
292 }
293 let tri_table_bytes: Vec<u8> = tri_table_i32.iter().flat_map(|v| v.to_le_bytes()).collect();
294
295 let counter_bytes: Vec<u8> = vec![0u8; 4];
297
298 let output_bytes = f32_slice_to_bytes(&vec![0.0f32; max_output]);
300
301 let descriptor = ComputeDispatchDescriptor {
302 label: "marching cubes".to_string(),
303 shader_source: MARCHING_CUBES_SHADER.to_string(),
304 entry_point: "main".to_string(),
305 workgroup_count: [
306 ceil_div_u32(vx, 4),
307 ceil_div_u32(vy, 4),
308 ceil_div_u32(vz, 4),
309 ],
310 bindings: vec![
311 ComputeBindingDescriptor {
312 label: "scalar_field".to_string(),
313 kind: ComputeBindingKind::StorageReadOnly,
314 bytes: f32_slice_to_bytes(&values_f32),
315 },
316 ComputeBindingDescriptor {
317 label: "edge_table".to_string(),
318 kind: ComputeBindingKind::StorageReadOnly,
319 bytes: edge_table_bytes,
320 },
321 ComputeBindingDescriptor {
322 label: "tri_table".to_string(),
323 kind: ComputeBindingKind::StorageReadOnly,
324 bytes: tri_table_bytes,
325 },
326 ComputeBindingDescriptor {
327 label: "params".to_string(),
328 kind: ComputeBindingKind::Uniform,
329 bytes: params_bytes,
330 },
331 ComputeBindingDescriptor {
332 label: "counter".to_string(),
333 kind: ComputeBindingKind::StorageReadWrite,
334 bytes: counter_bytes,
335 },
336 ComputeBindingDescriptor {
337 label: "output".to_string(),
338 kind: ComputeBindingKind::StorageReadWrite,
339 bytes: output_bytes,
340 },
341 ],
342 };
343
344 let result = ctx.run_compute(&descriptor)?;
345 if result.outputs.len() < 2 {
346 return Err("Missing outputs from marching cubes kernel".to_string());
347 }
348
349 let counter_out = &result.outputs[0];
351 let tri_count = if counter_out.len() >= 4 {
352 u32::from_le_bytes([
353 counter_out[0],
354 counter_out[1],
355 counter_out[2],
356 counter_out[3],
357 ])
358 } else {
359 0
360 };
361
362 let vertex_data = bytes_to_f32_vec(&result.outputs[1]);
363 let n_triangles = tri_count as usize;
364 let n_verts = n_triangles * 3;
365 let n_floats = n_verts * 6; let actual_floats = n_floats.min(vertex_data.len());
368 let actual_verts = actual_floats / 6;
369 let actual_tris = actual_verts / 3;
370
371 let mut vertices = Vec::with_capacity(actual_verts * 3);
372 let mut normals = Vec::with_capacity(actual_verts * 3);
373 let mut indices = Vec::with_capacity(actual_verts);
374
375 for v in 0..actual_verts {
376 let base = v * 6;
377 if base + 5 < vertex_data.len() {
378 vertices.push(vertex_data[base]);
379 vertices.push(vertex_data[base + 1]);
380 vertices.push(vertex_data[base + 2]);
381 normals.push(vertex_data[base + 3]);
382 normals.push(vertex_data[base + 4]);
383 normals.push(vertex_data[base + 5]);
384 indices.push(v as u32);
385 }
386 }
387
388 Ok(McOutput {
389 n_triangles: actual_tris,
390 vertices,
391 normals,
392 indices,
393 })
394}
395
396const MARCHING_CUBES_SHADER: &str = r#"
402struct McParams {
403 origin_x: f32, origin_y: f32, origin_z: f32,
404 spacing: f32,
405 dims_x: u32, dims_y: u32, dims_z: u32,
406 isovalue: f32,
407};
408
409@group(0) @binding(0) var<storage, read> scalar_field: array<f32>;
410@group(0) @binding(1) var<storage, read> edge_table: array<u32>;
411@group(0) @binding(2) var<storage, read> tri_table: array<i32>;
412@group(0) @binding(3) var<uniform> params: McParams;
413@group(0) @binding(4) var<storage, read_write> tri_counter: array<atomic<u32>>;
414@group(0) @binding(5) var<storage, read_write> output: array<f32>;
415
416fn field_index(ix: u32, iy: u32, iz: u32) -> u32 {
417 return ix * params.dims_y * params.dims_z + iy * params.dims_z + iz;
418}
419
420fn grid_point(ix: u32, iy: u32, iz: u32) -> vec3<f32> {
421 return vec3<f32>(
422 params.origin_x + f32(ix) * params.spacing,
423 params.origin_y + f32(iy) * params.spacing,
424 params.origin_z + f32(iz) * params.spacing,
425 );
426}
427
428fn interp_vertex(p0: vec3<f32>, p1: vec3<f32>, v0: f32, v1: f32, iso: f32) -> vec3<f32> {
429 let dv = v1 - v0;
430 var t: f32 = 0.5;
431 if (abs(dv) > 1e-10) {
432 t = clamp((iso - v0) / dv, 0.0, 1.0);
433 }
434 return mix(p0, p1, vec3<f32>(t));
435}
436
437@compute @workgroup_size(4, 4, 4)
438fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
439 let ix = gid.x;
440 let iy = gid.y;
441 let iz = gid.z;
442
443 let vx = params.dims_x - 1u;
444 let vy = params.dims_y - 1u;
445 let vz = params.dims_z - 1u;
446
447 if (ix >= vx || iy >= vy || iz >= vz) {
448 return;
449 }
450
451 // Sample 8 corners
452 let c0 = scalar_field[field_index(ix, iy, iz)];
453 let c1 = scalar_field[field_index(ix + 1, iy, iz)];
454 let c2 = scalar_field[field_index(ix + 1, iy + 1, iz)];
455 let c3 = scalar_field[field_index(ix, iy + 1, iz)];
456 let c4 = scalar_field[field_index(ix, iy, iz + 1)];
457 let c5 = scalar_field[field_index(ix + 1, iy, iz + 1)];
458 let c6 = scalar_field[field_index(ix + 1, iy + 1, iz + 1)];
459 let c7 = scalar_field[field_index(ix, iy + 1, iz + 1)];
460
461 let iso = params.isovalue;
462
463 var cube_index: u32 = 0u;
464 if (c0 > iso) { cube_index |= 1u; }
465 if (c1 > iso) { cube_index |= 2u; }
466 if (c2 > iso) { cube_index |= 4u; }
467 if (c3 > iso) { cube_index |= 8u; }
468 if (c4 > iso) { cube_index |= 16u; }
469 if (c5 > iso) { cube_index |= 32u; }
470 if (c6 > iso) { cube_index |= 64u; }
471 if (c7 > iso) { cube_index |= 128u; }
472
473 let edge_bits = edge_table[cube_index];
474 if (edge_bits == 0u) {
475 return;
476 }
477
478 // Corner positions
479 let p0 = grid_point(ix, iy, iz);
480 let p1 = grid_point(ix + 1, iy, iz);
481 let p2 = grid_point(ix + 1, iy + 1, iz);
482 let p3 = grid_point(ix, iy + 1, iz);
483 let p4 = grid_point(ix, iy, iz + 1);
484 let p5 = grid_point(ix + 1, iy, iz + 1);
485 let p6 = grid_point(ix + 1, iy + 1, iz + 1);
486 let p7 = grid_point(ix, iy + 1, iz + 1);
487
488 // Interpolate edge vertices
489 var ev: array<vec3<f32>, 12>;
490 if ((edge_bits & 1u) != 0u) { ev[0] = interp_vertex(p0, p1, c0, c1, iso); }
491 if ((edge_bits & 2u) != 0u) { ev[1] = interp_vertex(p1, p2, c1, c2, iso); }
492 if ((edge_bits & 4u) != 0u) { ev[2] = interp_vertex(p2, p3, c2, c3, iso); }
493 if ((edge_bits & 8u) != 0u) { ev[3] = interp_vertex(p3, p0, c3, c0, iso); }
494 if ((edge_bits & 16u) != 0u) { ev[4] = interp_vertex(p4, p5, c4, c5, iso); }
495 if ((edge_bits & 32u) != 0u) { ev[5] = interp_vertex(p5, p6, c5, c6, iso); }
496 if ((edge_bits & 64u) != 0u) { ev[6] = interp_vertex(p6, p7, c6, c7, iso); }
497 if ((edge_bits & 128u) != 0u) { ev[7] = interp_vertex(p7, p4, c7, c4, iso); }
498 if ((edge_bits & 256u) != 0u) { ev[8] = interp_vertex(p0, p4, c0, c4, iso); }
499 if ((edge_bits & 512u) != 0u) { ev[9] = interp_vertex(p1, p5, c1, c5, iso); }
500 if ((edge_bits & 1024u) != 0u) { ev[10] = interp_vertex(p2, p6, c2, c6, iso); }
501 if ((edge_bits & 2048u) != 0u) { ev[11] = interp_vertex(p3, p7, c3, c7, iso); }
502
503 // Emit triangles from tri_table
504 let tri_base = cube_index * 16u;
505 var i: u32 = 0u;
506 loop {
507 if (i >= 16u) { break; }
508 let e0_i = tri_table[tri_base + i];
509 if (e0_i < 0) { break; }
510 let e1_i = tri_table[tri_base + i + 1u];
511 let e2_i = tri_table[tri_base + i + 2u];
512
513 let v0 = ev[u32(e0_i)];
514 let v1 = ev[u32(e1_i)];
515 let v2 = ev[u32(e2_i)];
516
517 // Face normal
518 let u_vec = v1 - v0;
519 let v_vec = v2 - v0;
520 var n = cross(u_vec, v_vec);
521 let len = length(n);
522 if (len > 1e-10) {
523 n = n / len;
524 }
525
526 // Atomically allocate 1 triangle slot
527 let tri_idx = atomicAdd(&tri_counter[0], 1u);
528 let out_base = tri_idx * 18u; // 3 verts × 6 floats (pos3 + normal3)
529
530 // Vertex 0
531 output[out_base + 0u] = v0.x;
532 output[out_base + 1u] = v0.y;
533 output[out_base + 2u] = v0.z;
534 output[out_base + 3u] = n.x;
535 output[out_base + 4u] = n.y;
536 output[out_base + 5u] = n.z;
537 // Vertex 1
538 output[out_base + 6u] = v1.x;
539 output[out_base + 7u] = v1.y;
540 output[out_base + 8u] = v1.z;
541 output[out_base + 9u] = n.x;
542 output[out_base + 10u] = n.y;
543 output[out_base + 11u] = n.z;
544 // Vertex 2
545 output[out_base + 12u] = v2.x;
546 output[out_base + 13u] = v2.y;
547 output[out_base + 14u] = v2.z;
548 output[out_base + 15u] = n.x;
549 output[out_base + 16u] = n.y;
550 output[out_base + 17u] = n.z;
551
552 i = i + 3u;
553 }
554}
555"#;
556
557const EDGE_VERTICES: [(usize, usize); 12] = [
560 (0, 1),
561 (1, 2),
562 (2, 3),
563 (3, 0),
564 (4, 5),
565 (5, 6),
566 (6, 7),
567 (7, 4),
568 (0, 4),
569 (1, 5),
570 (2, 6),
571 (3, 7),
572];
573
574#[rustfmt::skip]
575const EDGE_TABLE: [u16; 256] = [
576 0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
577 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
578 0x190, 0x099, 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
579 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
580 0x230, 0x339, 0x033, 0x13a, 0x636, 0x73f, 0x435, 0x53c,
581 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
582 0x3a0, 0x2a9, 0x1a3, 0x0aa, 0x7a6, 0x6af, 0x5a5, 0x4ac,
583 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
584 0x460, 0x569, 0x663, 0x76a, 0x066, 0x16f, 0x265, 0x36c,
585 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
586 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0x0ff, 0x3f5, 0x2fc,
587 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
588 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x055, 0x15c,
589 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
590 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0x0cc,
591 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
592 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
593 0x0cc, 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
594 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
595 0x15c, 0x055, 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
596 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
597 0x2fc, 0x3f5, 0x0ff, 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
598 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
599 0x36c, 0x265, 0x16f, 0x066, 0x76a, 0x663, 0x569, 0x460,
600 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
601 0x4ac, 0x5a5, 0x6af, 0x7a6, 0x0aa, 0x1a3, 0x2a9, 0x3a0,
602 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,
603 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x033, 0x339, 0x230,
604 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
605 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x099, 0x190,
606 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
607 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x000,
608];
609
610const TRI_TABLE: [[i8; 16]; 256] = {
612 let mut t = [[-1i8; 16]; 256];
613 t[1] = [0, 8, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
614 t[2] = [0, 1, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
615 t[3] = [1, 8, 3, 9, 8, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
616 t[4] = [1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
617 t[5] = [0, 8, 3, 1, 2, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
618 t[6] = [9, 2, 10, 0, 2, 9, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
619 t[7] = [2, 8, 3, 2, 10, 8, 10, 9, 8, -1, -1, -1, -1, -1, -1, -1];
620 t[8] = [3, 11, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
621 t[9] = [0, 11, 2, 8, 11, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
622 t[10] = [1, 9, 0, 2, 3, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
623 t[11] = [1, 11, 2, 1, 9, 11, 9, 8, 11, -1, -1, -1, -1, -1, -1, -1];
624 t[12] = [3, 10, 1, 11, 10, 3, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
625 t[13] = [0, 10, 1, 0, 8, 10, 8, 11, 10, -1, -1, -1, -1, -1, -1, -1];
626 t[14] = [3, 9, 0, 3, 11, 9, 11, 10, 9, -1, -1, -1, -1, -1, -1, -1];
627 t[15] = [9, 8, 10, 10, 8, 11, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1];
628 t
629};
630
631#[cfg(test)]
632mod tests {
633 use super::*;
634
635 #[test]
636 fn test_marching_cubes_sphere() {
637 let params = GridParams {
638 origin: [-2.0, -2.0, -2.0],
639 spacing: 0.5,
640 dimensions: [9, 9, 9],
641 };
642
643 let mut values = vec![0.0; params.n_points()];
644 let [nx, ny, nz] = params.dimensions;
645
646 for ix in 0..nx {
647 for iy in 0..ny {
648 for iz in 0..nz {
649 let r = params.point(ix, iy, iz);
650 let r2 = r[0] * r[0] + r[1] * r[1] + r[2] * r[2];
651 values[params.flat_index(ix, iy, iz)] = 1.0 - r2;
652 }
653 }
654 }
655
656 let mesh = marching_cubes_cpu(&values, ¶ms, 0.0);
657 assert!(mesh.n_triangles > 0);
658 assert_eq!(mesh.vertices.len(), mesh.n_triangles * 9);
659 assert_eq!(mesh.normals.len(), mesh.n_triangles * 9);
660 }
661
662 #[test]
663 fn test_marching_cubes_empty() {
664 let params = GridParams {
665 origin: [0.0, 0.0, 0.0],
666 spacing: 1.0,
667 dimensions: [3, 3, 3],
668 };
669 let values = vec![-1.0; params.n_points()];
670 let mesh = marching_cubes_cpu(&values, ¶ms, 0.0);
671 assert_eq!(mesh.n_triangles, 0);
672 }
673
674 #[test]
675 fn test_marching_cubes_all_inside() {
676 let params = GridParams {
677 origin: [0.0, 0.0, 0.0],
678 spacing: 1.0,
679 dimensions: [3, 3, 3],
680 };
681 let values = vec![1.0; params.n_points()];
682 let mesh = marching_cubes_cpu(&values, ¶ms, 0.0);
683 assert_eq!(mesh.n_triangles, 0);
684 }
685
686 #[test]
687 fn test_smooth_normals_empty() {
688 let mut mesh = McOutput {
689 vertices: vec![],
690 normals: vec![],
691 indices: vec![],
692 n_triangles: 0,
693 };
694 smooth_mesh_normals(&mut mesh); }
696}