1#[derive(Debug, Clone)]
15pub struct GpuVoxelGrid {
16 pub nx: usize,
18 pub ny: usize,
20 pub nz: usize,
22 pub voxel_size: f64,
24 pub occupancy: Vec<bool>,
26 pub sdf: Vec<f64>,
28}
29
30impl GpuVoxelGrid {
31 pub fn new(nx: usize, ny: usize, nz: usize, voxel_size: f64) -> Self {
33 let n = nx * ny * nz;
34 Self {
35 nx,
36 ny,
37 nz,
38 voxel_size,
39 occupancy: vec![false; n],
40 sdf: vec![f64::INFINITY; n],
41 }
42 }
43
44 pub fn len(&self) -> usize {
46 self.nx * self.ny * self.nz
47 }
48
49 pub fn is_empty(&self) -> bool {
51 self.len() == 0
52 }
53
54 pub fn index(&self, x: usize, y: usize, z: usize) -> usize {
59 debug_assert!(x < self.nx && y < self.ny && z < self.nz);
60 x + self.nx * (y + self.ny * z)
61 }
62
63 pub fn coords(&self, idx: usize) -> (usize, usize, usize) {
65 let z = idx / (self.nx * self.ny);
66 let rem = idx % (self.nx * self.ny);
67 let y = rem / self.nx;
68 let x = rem % self.nx;
69 (x, y, z)
70 }
71
72 pub fn occupied_count(&self) -> usize {
74 self.occupancy.iter().filter(|&&v| v).count()
75 }
76}
77
78pub fn gpu_voxelize_mesh(grid: &mut GpuVoxelGrid, triangles: &[[f64; 9]]) {
89 for tri in triangles {
90 let v0 = [tri[0], tri[1], tri[2]];
92 let v1 = [tri[3], tri[4], tri[5]];
93 let v2 = [tri[6], tri[7], tri[8]];
94
95 let min_x = v0[0].min(v1[0]).min(v2[0]);
97 let min_y = v0[1].min(v1[1]).min(v2[1]);
98 let min_z = v0[2].min(v1[2]).min(v2[2]);
99 let max_x = v0[0].max(v1[0]).max(v2[0]);
100 let max_y = v0[1].max(v1[1]).max(v2[1]);
101 let max_z = v0[2].max(v1[2]).max(v2[2]);
102
103 let vs = grid.voxel_size;
104 let ix0 = ((min_x / vs).floor() as isize).max(0) as usize;
105 let iy0 = ((min_y / vs).floor() as isize).max(0) as usize;
106 let iz0 = ((min_z / vs).floor() as isize).max(0) as usize;
107 let ix1 = ((max_x / vs).ceil() as usize).min(grid.nx.saturating_sub(1));
108 let iy1 = ((max_y / vs).ceil() as usize).min(grid.ny.saturating_sub(1));
109 let iz1 = ((max_z / vs).ceil() as usize).min(grid.nz.saturating_sub(1));
110
111 for iz in iz0..=iz1 {
112 for iy in iy0..=iy1 {
113 for ix in ix0..=ix1 {
114 let idx = grid.index(ix, iy, iz);
115 grid.occupancy[idx] = true;
116 }
117 }
118 }
119 }
120}
121
122pub fn gpu_sdf_from_voxels(grid: &mut GpuVoxelGrid) {
132 use std::collections::VecDeque;
133
134 let n = grid.len();
135 grid.sdf = vec![f64::INFINITY; n];
136
137 let mut queue: VecDeque<usize> = VecDeque::new();
138
139 for i in 0..n {
141 if grid.occupancy[i] {
142 grid.sdf[i] = 0.0;
143 queue.push_back(i);
144 }
145 }
146
147 let nx = grid.nx;
148 let ny = grid.ny;
149 let nz = grid.nz;
150 let vs = grid.voxel_size;
151
152 while let Some(idx) = queue.pop_front() {
153 let (x, y, z) = grid.coords(idx);
154 let current_dist = grid.sdf[idx];
155
156 let neighbours: [(isize, isize, isize); 6] = [
158 (1, 0, 0),
159 (-1, 0, 0),
160 (0, 1, 0),
161 (0, -1, 0),
162 (0, 0, 1),
163 (0, 0, -1),
164 ];
165 for (dx, dy, dz) in neighbours {
166 let nx_ = x as isize + dx;
167 let ny_ = y as isize + dy;
168 let nz_ = z as isize + dz;
169 if nx_ < 0 || ny_ < 0 || nz_ < 0 {
170 continue;
171 }
172 let (nx_, ny_, nz_) = (nx_ as usize, ny_ as usize, nz_ as usize);
173 if nx_ >= nx || ny_ >= ny || nz_ >= nz {
174 continue;
175 }
176 let ni = nx_ + nx * (ny_ + ny * nz_);
177 let new_dist = current_dist + vs;
178 if new_dist < grid.sdf[ni] {
179 grid.sdf[ni] = new_dist;
180 queue.push_back(ni);
181 }
182 }
183 }
184}
185
186pub fn gpu_voxel_dilate(grid: &mut GpuVoxelGrid) {
191 let old = grid.occupancy.clone();
192 let nx = grid.nx;
193 let ny = grid.ny;
194 let nz = grid.nz;
195
196 for z in 0..nz {
197 for y in 0..ny {
198 for x in 0..nx {
199 if old[x + nx * (y + ny * z)] {
200 continue; }
202 let nbrs: [(isize, isize, isize); 6] = [
204 (1, 0, 0),
205 (-1, 0, 0),
206 (0, 1, 0),
207 (0, -1, 0),
208 (0, 0, 1),
209 (0, 0, -1),
210 ];
211 for (dx, dy, dz) in nbrs {
212 let nx_ = x as isize + dx;
213 let ny_ = y as isize + dy;
214 let nz_ = z as isize + dz;
215 if nx_ < 0 || ny_ < 0 || nz_ < 0 {
216 continue;
217 }
218 let (nx_, ny_, nz_) = (nx_ as usize, ny_ as usize, nz_ as usize);
219 if nx_ >= nx || ny_ >= ny || nz_ >= nz {
220 continue;
221 }
222 if old[nx_ + nx * (ny_ + ny * nz_)] {
223 grid.occupancy[x + nx * (y + ny * z)] = true;
224 break;
225 }
226 }
227 }
228 }
229 }
230}
231
232pub fn gpu_voxel_erode(grid: &mut GpuVoxelGrid) {
236 let old = grid.occupancy.clone();
237 let nx = grid.nx;
238 let ny = grid.ny;
239 let nz = grid.nz;
240
241 for z in 0..nz {
242 for y in 0..ny {
243 for x in 0..nx {
244 if !old[x + nx * (y + ny * z)] {
245 continue; }
247 let nbrs: [(isize, isize, isize); 6] = [
248 (1, 0, 0),
249 (-1, 0, 0),
250 (0, 1, 0),
251 (0, -1, 0),
252 (0, 0, 1),
253 (0, 0, -1),
254 ];
255 for (dx, dy, dz) in nbrs {
256 let nx_ = x as isize + dx;
257 let ny_ = y as isize + dy;
258 let nz_ = z as isize + dz;
259 let is_empty = if nx_ < 0 || ny_ < 0 || nz_ < 0 {
261 true
262 } else {
263 let (nx_, ny_, nz_) = (nx_ as usize, ny_ as usize, nz_ as usize);
264 if nx_ >= nx || ny_ >= ny || nz_ >= nz {
265 true
266 } else {
267 !old[nx_ + nx * (ny_ + ny * nz_)]
268 }
269 };
270 if is_empty {
271 grid.occupancy[x + nx * (y + ny * z)] = false;
272 break;
273 }
274 }
275 }
276 }
277 }
278}
279
280pub fn gpu_march_cubes_count(grid: &GpuVoxelGrid) -> usize {
289 let nx = grid.nx;
290 let ny = grid.ny;
291 let nz = grid.nz;
292
293 if nx < 2 || ny < 2 || nz < 2 {
294 return 0;
295 }
296
297 let mut count = 0usize;
298 for z in 0..nz - 1 {
299 for y in 0..ny - 1 {
300 for x in 0..nx - 1 {
301 let corners = [
303 grid.occupancy[grid.index(x, y, z)],
304 grid.occupancy[grid.index(x + 1, y, z)],
305 grid.occupancy[grid.index(x, y + 1, z)],
306 grid.occupancy[grid.index(x + 1, y + 1, z)],
307 grid.occupancy[grid.index(x, y, z + 1)],
308 grid.occupancy[grid.index(x + 1, y, z + 1)],
309 grid.occupancy[grid.index(x, y + 1, z + 1)],
310 grid.occupancy[grid.index(x + 1, y + 1, z + 1)],
311 ];
312 let any_filled = corners.iter().any(|&v| v);
313 let any_empty = corners.iter().any(|&v| !v);
314 if any_filled && any_empty {
315 count += 1;
316 }
317 }
318 }
319 }
320 count
321}
322
323#[cfg(test)]
326mod tests {
327 use super::*;
328
329 fn small_grid() -> GpuVoxelGrid {
330 GpuVoxelGrid::new(4, 4, 4, 1.0)
331 }
332
333 #[test]
336 fn test_grid_len() {
337 let g = small_grid();
338 assert_eq!(g.len(), 64);
339 }
340
341 #[test]
342 fn test_grid_is_empty_false() {
343 let g = small_grid();
344 assert!(!g.is_empty());
345 }
346
347 #[test]
348 fn test_grid_zero_dim_is_empty() {
349 let g = GpuVoxelGrid::new(0, 4, 4, 1.0);
350 assert!(g.is_empty());
351 }
352
353 #[test]
354 fn test_grid_starts_unoccupied() {
355 let g = small_grid();
356 assert_eq!(g.occupied_count(), 0);
357 }
358
359 #[test]
360 fn test_grid_sdf_starts_infinity() {
361 let g = small_grid();
362 assert!(g.sdf.iter().all(|&v| v == f64::INFINITY));
363 }
364
365 #[test]
366 fn test_index_roundtrip() {
367 let g = small_grid();
368 for z in 0..4 {
369 for y in 0..4 {
370 for x in 0..4 {
371 let idx = g.index(x, y, z);
372 let (rx, ry, rz) = g.coords(idx);
373 assert_eq!((rx, ry, rz), (x, y, z));
374 }
375 }
376 }
377 }
378
379 #[test]
380 fn test_occupied_count() {
381 let mut g = small_grid();
382 g.occupancy[0] = true;
383 g.occupancy[1] = true;
384 assert_eq!(g.occupied_count(), 2);
385 }
386
387 #[test]
390 fn test_voxelize_single_triangle_marks_voxels() {
391 let mut g = GpuVoxelGrid::new(8, 8, 8, 1.0);
392 let tri = [[0.5, 0.5, 0.5, 2.5, 0.5, 0.5, 0.5, 2.5, 0.5]];
393 gpu_voxelize_mesh(&mut g, &tri);
394 assert!(g.occupied_count() > 0);
395 }
396
397 #[test]
398 fn test_voxelize_empty_triangle_list() {
399 let mut g = small_grid();
400 gpu_voxelize_mesh(&mut g, &[]);
401 assert_eq!(g.occupied_count(), 0);
402 }
403
404 #[test]
405 fn test_voxelize_two_triangles_more_voxels() {
406 let mut g = GpuVoxelGrid::new(10, 10, 10, 1.0);
407 let tri1 = [[0.5, 0.5, 0.5, 2.5, 0.5, 0.5, 0.5, 2.5, 0.5]];
408 let tri2 = [[5.5, 5.5, 5.5, 7.5, 5.5, 5.5, 5.5, 7.5, 5.5]];
409 let mut g1 = g.clone();
410 gpu_voxelize_mesh(&mut g1, &tri1);
411 let c1 = g1.occupied_count();
412 gpu_voxelize_mesh(&mut g, &[tri1[0], tri2[0]]);
413 let c2 = g.occupied_count();
414 assert!(c2 >= c1);
415 }
416
417 #[test]
418 fn test_voxelize_triangle_outside_grid_no_panic() {
419 let mut g = GpuVoxelGrid::new(4, 4, 4, 1.0);
420 let tri = [[
421 100.0, 100.0, 100.0, 200.0, 100.0, 100.0, 100.0, 200.0, 100.0,
422 ]];
423 gpu_voxelize_mesh(&mut g, &tri);
424 assert_eq!(g.occupied_count(), 0);
426 }
427
428 #[test]
431 fn test_sdf_occupied_voxel_is_zero() {
432 let mut g = small_grid();
433 let _vi = g.index(2, 2, 2);
434
435 g.occupancy[_vi] = true;
436 gpu_sdf_from_voxels(&mut g);
437 assert!((g.sdf[g.index(2, 2, 2)]).abs() < 1e-12);
438 }
439
440 #[test]
441 fn test_sdf_neighbour_is_one_voxel_size() {
442 let mut g = GpuVoxelGrid::new(5, 5, 5, 1.0);
443 let _vi = g.index(2, 2, 2);
444
445 g.occupancy[_vi] = true;
446 gpu_sdf_from_voxels(&mut g);
447 let d = g.sdf[g.index(3, 2, 2)];
448 assert!((d - 1.0).abs() < 1e-12, "d = {d}");
449 }
450
451 #[test]
452 fn test_sdf_all_unoccupied_stays_infinity() {
453 let mut g = small_grid();
454 gpu_sdf_from_voxels(&mut g);
455 assert!(g.sdf.iter().all(|&v| v == f64::INFINITY));
456 }
457
458 #[test]
459 fn test_sdf_monotone_with_distance() {
460 let mut g = GpuVoxelGrid::new(10, 1, 1, 1.0);
461 let _vi = g.index(0, 0, 0);
462
463 g.occupancy[_vi] = true;
464 gpu_sdf_from_voxels(&mut g);
465 for x in 1..10 {
466 let prev = g.sdf[g.index(x - 1, 0, 0)];
467 let curr = g.sdf[g.index(x, 0, 0)];
468 assert!(
469 curr >= prev,
470 "SDF not monotone at x={x}: prev={prev} curr={curr}"
471 );
472 }
473 }
474
475 #[test]
478 fn test_dilate_expands_single_voxel() {
479 let mut g = small_grid();
480 let _vi = g.index(2, 2, 2);
481
482 g.occupancy[_vi] = true;
483 let before = g.occupied_count();
484 gpu_voxel_dilate(&mut g);
485 let after = g.occupied_count();
486 assert!(after > before);
487 }
488
489 #[test]
490 fn test_dilate_empty_grid_stays_empty() {
491 let mut g = small_grid();
492 gpu_voxel_dilate(&mut g);
493 assert_eq!(g.occupied_count(), 0);
494 }
495
496 #[test]
497 fn test_dilate_fully_filled_stays_full() {
498 let mut g = small_grid();
499 for v in &mut g.occupancy {
500 *v = true;
501 }
502 gpu_voxel_dilate(&mut g);
503 assert_eq!(g.occupied_count(), 64);
504 }
505
506 #[test]
509 fn test_erode_single_voxel_disappears() {
510 let mut g = small_grid();
511 let _vi = g.index(2, 2, 2);
512
513 g.occupancy[_vi] = true;
514 gpu_voxel_erode(&mut g);
515 assert_eq!(g.occupied_count(), 0);
517 }
518
519 #[test]
520 fn test_erode_empty_stays_empty() {
521 let mut g = small_grid();
522 gpu_voxel_erode(&mut g);
523 assert_eq!(g.occupied_count(), 0);
524 }
525
526 #[test]
527 fn test_dilate_then_erode_subset_of_original() {
528 let mut g = small_grid();
529 for z in 1..3 {
531 for y in 1..3 {
532 for x in 1..3 {
533 let _vi = g.index(x, y, z);
534
535 g.occupancy[_vi] = true;
536 }
537 }
538 }
539 let original_count = g.occupied_count();
540 gpu_voxel_dilate(&mut g);
541 gpu_voxel_erode(&mut g);
542 assert!(g.occupied_count() <= original_count);
544 }
545
546 #[test]
549 fn test_march_cubes_empty_grid_zero() {
550 let g = small_grid();
551 assert_eq!(gpu_march_cubes_count(&g), 0);
552 }
553
554 #[test]
555 fn test_march_cubes_full_grid_zero() {
556 let mut g = small_grid();
557 for v in &mut g.occupancy {
558 *v = true;
559 }
560 assert_eq!(gpu_march_cubes_count(&g), 0);
561 }
562
563 #[test]
564 fn test_march_cubes_single_occupied_voxel_nonzero() {
565 let mut g = small_grid();
566 let _vi = g.index(1, 1, 1);
567
568 g.occupancy[_vi] = true;
569 let count = gpu_march_cubes_count(&g);
570 assert!(count > 0, "expected > 0 active cubes, got {count}");
571 }
572
573 #[test]
574 fn test_march_cubes_tiny_grid_no_panic() {
575 let g = GpuVoxelGrid::new(1, 1, 1, 1.0);
576 assert_eq!(gpu_march_cubes_count(&g), 0);
577 }
578
579 #[test]
580 fn test_march_cubes_increases_with_more_surface() {
581 let mut g1 = GpuVoxelGrid::new(6, 6, 6, 1.0);
582 let _vi = g1.index(2, 2, 2);
583
584 g1.occupancy[_vi] = true;
585 let c1 = gpu_march_cubes_count(&g1);
586
587 let mut g2 = GpuVoxelGrid::new(6, 6, 6, 1.0);
588 for z in 1..4 {
589 for y in 1..4 {
590 for x in 1..4 {
591 let _vi = g2.index(x, y, z);
592
593 g2.occupancy[_vi] = true;
594 }
595 }
596 }
597 let c2 = gpu_march_cubes_count(&g2);
598 assert!(c2 >= c1, "c2={c2} should be >= c1={c1}");
599 }
600}