1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone)]
10pub struct SliceContour {
11 pub z: f32,
12 pub points: Vec<[f32; 2]>,
13}
14
15#[allow(dead_code)]
17pub struct SliceStackResult {
18 pub contours: Vec<SliceContour>,
19}
20
21fn edge_z_intersect(p0: [f32; 3], p1: [f32; 3], z: f32) -> Option<[f32; 2]> {
23 let dz = p1[2] - p0[2];
24 if dz.abs() < 1e-9 {
25 return None;
26 }
27 let t = (z - p0[2]) / dz;
28 if !(0.0..=1.0).contains(&t) {
29 return None;
30 }
31 let x = p0[0] + t * (p1[0] - p0[0]);
32 let y = p0[1] + t * (p1[1] - p0[1]);
33 Some([x, y])
34}
35
36#[allow(dead_code)]
38pub fn slice_at_z(positions: &[[f32; 3]], indices: &[u32], z: f32) -> Vec<[f32; 2]> {
39 let mut pts = Vec::new();
40 let n_tri = indices.len() / 3;
41 for t in 0..n_tri {
42 let p0 = positions[indices[t * 3] as usize];
43 let p1 = positions[indices[t * 3 + 1] as usize];
44 let p2 = positions[indices[t * 3 + 2] as usize];
45 for &(a, b) in &[(p0, p1), (p1, p2), (p2, p0)] {
46 if let Some(pt) = edge_z_intersect(a, b, z) {
47 pts.push(pt);
48 }
49 }
50 }
51 pts
52}
53
54#[allow(dead_code)]
56pub fn slice_stack(positions: &[[f32; 3]], indices: &[u32], z_heights: &[f32]) -> SliceStackResult {
57 let contours = z_heights
58 .iter()
59 .map(|&z| SliceContour {
60 z,
61 points: slice_at_z(positions, indices, z),
62 })
63 .collect();
64 SliceStackResult { contours }
65}
66
67#[allow(dead_code)]
69pub fn uniform_z_heights(z_min: f32, z_max: f32, count: usize) -> Vec<f32> {
70 if count == 0 {
71 return vec![];
72 }
73 if count == 1 {
74 return vec![(z_min + z_max) * 0.5];
75 }
76 let step = (z_max - z_min) / (count - 1) as f32;
77 (0..count).map(|i| z_min + i as f32 * step).collect()
78}
79
80#[allow(dead_code)]
82pub fn total_contour_points(result: &SliceStackResult) -> usize {
83 result.contours.iter().map(|c| c.points.len()).sum()
84}
85
86#[allow(dead_code)]
88pub fn non_empty_contour_count(result: &SliceStackResult) -> usize {
89 result
90 .contours
91 .iter()
92 .filter(|c| !c.points.is_empty())
93 .count()
94}
95
96#[allow(dead_code)]
98pub fn contour_bounds(contour: &SliceContour) -> ([f32; 2], [f32; 2]) {
99 if contour.points.is_empty() {
100 return ([0.0; 2], [0.0; 2]);
101 }
102 let mut mn = contour.points[0];
103 let mut mx = contour.points[0];
104 for p in &contour.points {
105 mn[0] = mn[0].min(p[0]);
106 mn[1] = mn[1].min(p[1]);
107 mx[0] = mx[0].max(p[0]);
108 mx[1] = mx[1].max(p[1]);
109 }
110 (mn, mx)
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 fn unit_cube_mesh() -> (Vec<[f32; 3]>, Vec<u32>) {
118 let positions = vec![
119 [0.0, 0.0, 0.0],
120 [1.0, 0.0, 0.0],
121 [1.0, 1.0, 0.0],
122 [0.0, 1.0, 0.0],
123 [0.0, 0.0, 1.0],
124 [1.0, 0.0, 1.0],
125 [1.0, 1.0, 1.0],
126 [0.0, 1.0, 1.0],
127 ];
128 let indices: Vec<u32> = vec![
129 0, 1, 2, 0, 2, 3, 4, 6, 5, 4, 7, 6, 0, 5, 1, 0, 4, 5, 2, 6, 3, 3, 6, 7, 1, 6, 2, 1, 5,
130 6, 0, 3, 7, 0, 7, 4,
131 ];
132 (positions, indices)
133 }
134
135 #[test]
136 fn slice_middle_has_points() {
137 let (pos, idx) = unit_cube_mesh();
138 let pts = slice_at_z(&pos, &idx, 0.5);
139 assert!(!pts.is_empty());
140 }
141
142 #[test]
143 fn slice_above_has_no_points() {
144 let (pos, idx) = unit_cube_mesh();
145 let pts = slice_at_z(&pos, &idx, 2.0);
146 assert!(pts.is_empty());
147 }
148
149 #[test]
150 fn slice_stack_contour_count() {
151 let (pos, idx) = unit_cube_mesh();
152 let heights = uniform_z_heights(0.1, 0.9, 5);
153 let result = slice_stack(&pos, &idx, &heights);
154 assert_eq!(result.contours.len(), 5);
155 }
156
157 #[test]
158 fn total_contour_points_sum() {
159 let (pos, idx) = unit_cube_mesh();
160 let heights = uniform_z_heights(0.1, 0.9, 3);
161 let result = slice_stack(&pos, &idx, &heights);
162 let total = total_contour_points(&result);
163 assert!(total > 0);
164 }
165
166 #[test]
167 fn non_empty_contour_count_cube() {
168 let (pos, idx) = unit_cube_mesh();
169 let heights = vec![0.5, 1.5];
170 let result = slice_stack(&pos, &idx, &heights);
171 assert_eq!(non_empty_contour_count(&result), 1);
172 }
173
174 #[test]
175 fn uniform_z_heights_count() {
176 let h = uniform_z_heights(0.0, 1.0, 5);
177 assert_eq!(h.len(), 5);
178 assert!((h[0] - 0.0).abs() < 1e-5);
179 assert!((h[4] - 1.0).abs() < 1e-5);
180 }
181
182 #[test]
183 fn uniform_z_heights_single() {
184 let h = uniform_z_heights(0.0, 1.0, 1);
185 assert_eq!(h.len(), 1);
186 assert!((h[0] - 0.5).abs() < 1e-5);
187 }
188
189 #[test]
190 fn contour_bounds_nonempty() {
191 let contour = SliceContour {
192 z: 0.5,
193 points: vec![[0.0, 0.0], [1.0, 0.5], [0.5, 1.0]],
194 };
195 let (mn, mx) = contour_bounds(&contour);
196 assert!((mn[0] - 0.0).abs() < 1e-5);
197 assert!((mx[0] - 1.0).abs() < 1e-5);
198 }
199
200 #[test]
201 fn edge_z_intersect_midpoint() {
202 let r = edge_z_intersect([0.0, 0.0, 0.0], [0.0, 0.0, 1.0], 0.5);
203 assert!(r.is_some());
204 let pt = r.expect("should succeed");
205 assert!((pt[0] - 0.0).abs() < 1e-5);
206 }
207
208 #[test]
209 fn edge_z_intersect_none_outside() {
210 let r = edge_z_intersect([0.0, 0.0, 0.0], [0.0, 0.0, 1.0], 2.0);
211 assert!(r.is_none());
212 }
213}