1use crate::resources::MeshData;
21use std::collections::HashMap;
22
23#[derive(Debug, Clone)]
25pub struct VolumeData {
26 pub data: Vec<f32>,
28 pub dims: [u32; 3],
30 pub origin: [f32; 3],
32 pub spacing: [f32; 3],
34}
35
36impl VolumeData {
37 pub fn in_bounds(&self, ix: u32, iy: u32, iz: u32) -> bool {
39 ix < self.dims[0] && iy < self.dims[1] && iz < self.dims[2]
40 }
41
42 pub fn sample(&self, ix: u32, iy: u32, iz: u32) -> f32 {
48 let nx = self.dims[0] as usize;
49 let ny = self.dims[1] as usize;
50 self.data[ix as usize + iy as usize * nx + iz as usize * nx * ny]
51 }
52}
53
54pub fn trilinear_sample(volume: &VolumeData, world_pos: [f32; 3]) -> f32 {
59 let [nx, ny, nz] = volume.dims;
60
61 let gx = (world_pos[0] - volume.origin[0]) / volume.spacing[0];
63 let gy = (world_pos[1] - volume.origin[1]) / volume.spacing[1];
64 let gz = (world_pos[2] - volume.origin[2]) / volume.spacing[2];
65
66 let gx = gx.clamp(0.0, (nx as f32) - 1.001);
68 let gy = gy.clamp(0.0, (ny as f32) - 1.001);
69 let gz = gz.clamp(0.0, (nz as f32) - 1.001);
70
71 let ix = gx.floor() as u32;
72 let iy = gy.floor() as u32;
73 let iz = gz.floor() as u32;
74
75 let fx = gx - ix as f32;
76 let fy = gy - iy as f32;
77 let fz = gz - iz as f32;
78
79 let ix1 = (ix + 1).min(nx - 1);
80 let iy1 = (iy + 1).min(ny - 1);
81 let iz1 = (iz + 1).min(nz - 1);
82
83 let c000 = volume.sample(ix, iy, iz);
85 let c100 = volume.sample(ix1, iy, iz);
86 let c010 = volume.sample(ix, iy1, iz);
87 let c110 = volume.sample(ix1, iy1, iz);
88 let c001 = volume.sample(ix, iy, iz1);
89 let c101 = volume.sample(ix1, iy, iz1);
90 let c011 = volume.sample(ix, iy1, iz1);
91 let c111 = volume.sample(ix1, iy1, iz1);
92
93 let c00 = c000 * (1.0 - fx) + c100 * fx;
94 let c10 = c010 * (1.0 - fx) + c110 * fx;
95 let c01 = c001 * (1.0 - fx) + c101 * fx;
96 let c11 = c011 * (1.0 - fx) + c111 * fx;
97
98 let c0 = c00 * (1.0 - fy) + c10 * fy;
99 let c1 = c01 * (1.0 - fy) + c11 * fy;
100
101 c0 * (1.0 - fz) + c1 * fz
102}
103
104fn gradient_at(volume: &VolumeData, pos: [f32; 3]) -> [f32; 3] {
106 let hx = volume.spacing[0] * 0.5;
107 let hy = volume.spacing[1] * 0.5;
108 let hz = volume.spacing[2] * 0.5;
109
110 let gx = trilinear_sample(volume, [pos[0] + hx, pos[1], pos[2]])
111 - trilinear_sample(volume, [pos[0] - hx, pos[1], pos[2]]);
112 let gy = trilinear_sample(volume, [pos[0], pos[1] + hy, pos[2]])
113 - trilinear_sample(volume, [pos[0], pos[1] - hy, pos[2]]);
114 let gz = trilinear_sample(volume, [pos[0], pos[1], pos[2] + hz])
115 - trilinear_sample(volume, [pos[0], pos[1], pos[2] - hz]);
116
117 let len = (gx * gx + gy * gy + gz * gz).sqrt();
118 if len > 1e-10 {
119 [gx / len, gy / len, gz / len]
120 } else {
121 [0.0, 1.0, 0.0] }
123}
124
125pub fn extract_isosurface(volume: &VolumeData, isovalue: f32) -> MeshData {
133 let [nx, ny, nz] = volume.dims;
134 if nx < 2 || ny < 2 || nz < 2 {
135 return MeshData {
136 positions: Vec::new(),
137 normals: Vec::new(),
138 indices: Vec::new(),
139 uvs: None,
140 tangents: None,
141 attributes: HashMap::new(),
142 };
143 }
144
145 let mut positions: Vec<[f32; 3]> = Vec::new();
146 let mut normals: Vec<[f32; 3]> = Vec::new();
147 let mut indices: Vec<u32> = Vec::new();
148
149 let mut edge_cache: HashMap<(u32, u32, u32, u8), u32> = HashMap::new();
152
153 for iz in 0..(nz - 1) {
154 for iy in 0..(ny - 1) {
155 for ix in 0..(nx - 1) {
156 let corners = [
158 volume.sample(ix, iy, iz), volume.sample(ix + 1, iy, iz), volume.sample(ix + 1, iy + 1, iz), volume.sample(ix, iy + 1, iz), volume.sample(ix, iy, iz + 1), volume.sample(ix + 1, iy, iz + 1), volume.sample(ix + 1, iy + 1, iz + 1), volume.sample(ix, iy + 1, iz + 1), ];
167
168 let mut cube_index = 0u8;
170 for (i, &val) in corners.iter().enumerate() {
171 if val < isovalue {
172 cube_index |= 1 << i;
173 }
174 }
175
176 let edge_bits = EDGE_TABLE[cube_index as usize];
177 if edge_bits == 0 {
178 continue;
179 }
180
181 let corner_pos = corner_positions(volume, ix, iy, iz);
183
184 let tri_row = &TRI_TABLE[cube_index as usize];
186 let mut i = 0;
187 while i < 16 && tri_row[i] >= 0 {
188 let mut tri_verts = [0u32; 3];
189 for v in 0..3 {
190 let edge_id = tri_row[i + v] as u8;
191 let (a, b) = EDGE_VERTICES[edge_id as usize];
192
193 let cache_key = canonical_edge_key(ix, iy, iz, edge_id);
195
196 tri_verts[v] = *edge_cache.entry(cache_key).or_insert_with(|| {
197 let va = corners[a as usize];
198 let vb = corners[b as usize];
199 let t = if (va - vb).abs() > 1e-10 {
200 (isovalue - va) / (vb - va)
201 } else {
202 0.5
203 };
204 let t = t.clamp(0.0, 1.0);
205
206 let pa = corner_pos[a as usize];
207 let pb = corner_pos[b as usize];
208 let pos = [
209 pa[0] + t * (pb[0] - pa[0]),
210 pa[1] + t * (pb[1] - pa[1]),
211 pa[2] + t * (pb[2] - pa[2]),
212 ];
213
214 let normal = gradient_at(volume, pos);
215
216 let idx = positions.len() as u32;
217 positions.push(pos);
218 normals.push(normal);
219 idx
220 });
221 }
222 indices.push(tri_verts[0]);
224 indices.push(tri_verts[1]);
225 indices.push(tri_verts[2]);
226 i += 3;
227 }
228 }
229 }
230 }
231
232 MeshData {
233 positions,
234 normals,
235 indices,
236 uvs: None,
237 tangents: None,
238 attributes: HashMap::new(),
239 }
240}
241
242fn corner_positions(volume: &VolumeData, ix: u32, iy: u32, iz: u32) -> [[f32; 3]; 8] {
244 let ox = volume.origin[0] + ix as f32 * volume.spacing[0];
245 let oy = volume.origin[1] + iy as f32 * volume.spacing[1];
246 let oz = volume.origin[2] + iz as f32 * volume.spacing[2];
247 let dx = volume.spacing[0];
248 let dy = volume.spacing[1];
249 let dz = volume.spacing[2];
250
251 [
252 [ox, oy, oz], [ox + dx, oy, oz], [ox + dx, oy + dy, oz], [ox, oy + dy, oz], [ox, oy, oz + dz], [ox + dx, oy, oz + dz], [ox + dx, oy + dy, oz + dz], [ox, oy + dy, oz + dz], ]
261}
262
263fn canonical_edge_key(cx: u32, cy: u32, cz: u32, edge_id: u8) -> (u32, u32, u32, u8) {
268 match edge_id {
277 0 => (cx, cy, cz, 0), 2 => (cx, cy + 1, cz, 0), 4 => (cx, cy, cz + 1, 0), 6 => (cx, cy + 1, cz + 1, 0), 3 => (cx, cy, cz, 1), 1 => (cx + 1, cy, cz, 1), 7 => (cx, cy, cz + 1, 1), 5 => (cx + 1, cy, cz + 1, 1), 8 => (cx, cy, cz, 2), 9 => (cx + 1, cy, cz, 2), 10 => (cx + 1, cy + 1, cz, 2), 11 => (cx, cy + 1, cz, 2), _ => (cx, cy, cz, edge_id), }
294}
295
296const EDGE_VERTICES: [(u8, u8); 12] = [
298 (0, 1), (1, 2), (2, 3), (3, 0), (4, 5), (5, 6), (6, 7), (7, 4), (0, 4), (1, 5), (2, 6), (3, 7), ];
311
312#[rustfmt::skip]
318const EDGE_TABLE: [u16; 256] = [
319 0x000, 0x109, 0x203, 0x30a, 0x406, 0x50f, 0x605, 0x70c,
320 0x80c, 0x905, 0xa0f, 0xb06, 0xc0a, 0xd03, 0xe09, 0xf00,
321 0x190, 0x099, 0x393, 0x29a, 0x596, 0x49f, 0x795, 0x69c,
322 0x99c, 0x895, 0xb9f, 0xa96, 0xd9a, 0xc93, 0xf99, 0xe90,
323 0x230, 0x339, 0x033, 0x13a, 0x636, 0x73f, 0x435, 0x53c,
324 0xa3c, 0xb35, 0x83f, 0x936, 0xe3a, 0xf33, 0xc39, 0xd30,
325 0x3a0, 0x2a9, 0x1a3, 0x0aa, 0x7a6, 0x6af, 0x5a5, 0x4ac,
326 0xbac, 0xaa5, 0x9af, 0x8a6, 0xfaa, 0xea3, 0xda9, 0xca0,
327 0x460, 0x569, 0x663, 0x76a, 0x066, 0x16f, 0x265, 0x36c,
328 0xc6c, 0xd65, 0xe6f, 0xf66, 0x86a, 0x963, 0xa69, 0xb60,
329 0x5f0, 0x4f9, 0x7f3, 0x6fa, 0x1f6, 0x0ff, 0x3f5, 0x2fc,
330 0xdfc, 0xcf5, 0xfff, 0xef6, 0x9fa, 0x8f3, 0xbf9, 0xaf0,
331 0x650, 0x759, 0x453, 0x55a, 0x256, 0x35f, 0x055, 0x15c,
332 0xe5c, 0xf55, 0xc5f, 0xd56, 0xa5a, 0xb53, 0x859, 0x950,
333 0x7c0, 0x6c9, 0x5c3, 0x4ca, 0x3c6, 0x2cf, 0x1c5, 0x0cc,
334 0xfcc, 0xec5, 0xdcf, 0xcc6, 0xbca, 0xac3, 0x9c9, 0x8c0,
335 0x8c0, 0x9c9, 0xac3, 0xbca, 0xcc6, 0xdcf, 0xec5, 0xfcc,
336 0x0cc, 0x1c5, 0x2cf, 0x3c6, 0x4ca, 0x5c3, 0x6c9, 0x7c0,
337 0x950, 0x859, 0xb53, 0xa5a, 0xd56, 0xc5f, 0xf55, 0xe5c,
338 0x15c, 0x055, 0x35f, 0x256, 0x55a, 0x453, 0x759, 0x650,
339 0xaf0, 0xbf9, 0x8f3, 0x9fa, 0xef6, 0xfff, 0xcf5, 0xdfc,
340 0x2fc, 0x3f5, 0x0ff, 0x1f6, 0x6fa, 0x7f3, 0x4f9, 0x5f0,
341 0xb60, 0xa69, 0x963, 0x86a, 0xf66, 0xe6f, 0xd65, 0xc6c,
342 0x36c, 0x265, 0x16f, 0x066, 0x76a, 0x663, 0x569, 0x460,
343 0xca0, 0xda9, 0xea3, 0xfaa, 0x8a6, 0x9af, 0xaa5, 0xbac,
344 0x4ac, 0x5a5, 0x6af, 0x7a6, 0x0aa, 0x1a3, 0x2a9, 0x3a0,
345 0xd30, 0xc39, 0xf33, 0xe3a, 0x936, 0x83f, 0xb35, 0xa3c,
346 0x53c, 0x435, 0x73f, 0x636, 0x13a, 0x033, 0x339, 0x230,
347 0xe90, 0xf99, 0xc93, 0xd9a, 0xa96, 0xb9f, 0x895, 0x99c,
348 0x69c, 0x795, 0x49f, 0x596, 0x29a, 0x393, 0x099, 0x190,
349 0xf00, 0xe09, 0xd03, 0xc0a, 0xb06, 0xa0f, 0x905, 0x80c,
350 0x70c, 0x605, 0x50f, 0x406, 0x30a, 0x203, 0x109, 0x000,
351];
352
353#[rustfmt::skip]
355const TRI_TABLE: [[i8; 16]; 256] = [
356 [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
357 [ 0, 8, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
358 [ 0, 1, 9,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
359 [ 1, 8, 3, 9, 8, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
360 [ 1, 2,10,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
361 [ 0, 8, 3, 1, 2,10,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
362 [ 9, 2,10, 0, 2, 9,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
363 [ 2, 8, 3, 2,10, 8,10, 9, 8,-1,-1,-1,-1,-1,-1,-1],
364 [ 3,11, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
365 [ 0,11, 2, 8,11, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
366 [ 1, 9, 0, 2, 3,11,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
367 [ 1,11, 2, 1, 9,11, 9, 8,11,-1,-1,-1,-1,-1,-1,-1],
368 [ 3,10, 1,11,10, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
369 [ 0,10, 1, 0, 8,10, 8,11,10,-1,-1,-1,-1,-1,-1,-1],
370 [ 3, 9, 0, 3,11, 9,11,10, 9,-1,-1,-1,-1,-1,-1,-1],
371 [ 9, 8,10,10, 8,11,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
372 [ 4, 7, 8,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
373 [ 4, 3, 0, 7, 3, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
374 [ 0, 1, 9, 8, 4, 7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
375 [ 4, 1, 9, 4, 7, 1, 7, 3, 1,-1,-1,-1,-1,-1,-1,-1],
376 [ 1, 2,10, 8, 4, 7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
377 [ 3, 4, 7, 3, 0, 4, 1, 2,10,-1,-1,-1,-1,-1,-1,-1],
378 [ 9, 2,10, 9, 0, 2, 8, 4, 7,-1,-1,-1,-1,-1,-1,-1],
379 [ 2,10, 9, 2, 9, 7, 2, 7, 3, 7, 9, 4,-1,-1,-1,-1],
380 [ 8, 4, 7, 3,11, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
381 [11, 4, 7,11, 2, 4, 2, 0, 4,-1,-1,-1,-1,-1,-1,-1],
382 [ 9, 0, 1, 8, 4, 7, 2, 3,11,-1,-1,-1,-1,-1,-1,-1],
383 [ 4, 7,11, 9, 4,11, 9,11, 2, 9, 2, 1,-1,-1,-1,-1],
384 [ 3,10, 1, 3,11,10, 7, 8, 4,-1,-1,-1,-1,-1,-1,-1],
385 [ 1,11,10, 1, 4,11, 1, 0, 4, 7,11, 4,-1,-1,-1,-1],
386 [ 4, 7, 8, 9, 0,11, 9,11,10,11, 0, 3,-1,-1,-1,-1],
387 [ 4, 7,11, 4,11, 9, 9,11,10,-1,-1,-1,-1,-1,-1,-1],
388 [ 9, 5, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
389 [ 9, 5, 4, 0, 8, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
390 [ 0, 5, 4, 1, 5, 0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
391 [ 8, 5, 4, 8, 3, 5, 3, 1, 5,-1,-1,-1,-1,-1,-1,-1],
392 [ 1, 2,10, 9, 5, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
393 [ 3, 0, 8, 1, 2,10, 4, 9, 5,-1,-1,-1,-1,-1,-1,-1],
394 [ 5, 2,10, 5, 4, 2, 4, 0, 2,-1,-1,-1,-1,-1,-1,-1],
395 [ 2,10, 5, 3, 2, 5, 3, 5, 4, 3, 4, 8,-1,-1,-1,-1],
396 [ 9, 5, 4, 2, 3,11,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
397 [ 0,11, 2, 0, 8,11, 4, 9, 5,-1,-1,-1,-1,-1,-1,-1],
398 [ 0, 5, 4, 0, 1, 5, 2, 3,11,-1,-1,-1,-1,-1,-1,-1],
399 [ 2, 1, 5, 2, 5, 8, 2, 8,11, 4, 8, 5,-1,-1,-1,-1],
400 [10, 3,11,10, 1, 3, 9, 5, 4,-1,-1,-1,-1,-1,-1,-1],
401 [ 4, 9, 5, 0, 8, 1, 8,10, 1, 8,11,10,-1,-1,-1,-1],
402 [ 5, 4, 0, 5, 0,11, 5,11,10,11, 0, 3,-1,-1,-1,-1],
403 [ 5, 4, 8, 5, 8,10,10, 8,11,-1,-1,-1,-1,-1,-1,-1],
404 [ 9, 7, 8, 5, 7, 9,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
405 [ 9, 3, 0, 9, 5, 3, 5, 7, 3,-1,-1,-1,-1,-1,-1,-1],
406 [ 0, 7, 8, 0, 1, 7, 1, 5, 7,-1,-1,-1,-1,-1,-1,-1],
407 [ 1, 5, 3, 3, 5, 7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
408 [ 9, 7, 8, 9, 5, 7,10, 1, 2,-1,-1,-1,-1,-1,-1,-1],
409 [10, 1, 2, 9, 5, 0, 5, 3, 0, 5, 7, 3,-1,-1,-1,-1],
410 [ 8, 0, 2, 8, 2, 5, 8, 5, 7,10, 5, 2,-1,-1,-1,-1],
411 [ 2,10, 5, 2, 5, 3, 3, 5, 7,-1,-1,-1,-1,-1,-1,-1],
412 [ 7, 9, 5, 7, 8, 9, 3,11, 2,-1,-1,-1,-1,-1,-1,-1],
413 [ 9, 5, 7, 9, 7, 2, 9, 2, 0, 2, 7,11,-1,-1,-1,-1],
414 [ 2, 3,11, 0, 1, 8, 1, 7, 8, 1, 5, 7,-1,-1,-1,-1],
415 [11, 2, 1,11, 1, 7, 7, 1, 5,-1,-1,-1,-1,-1,-1,-1],
416 [ 9, 5, 8, 8, 5, 7,10, 1, 3,10, 3,11,-1,-1,-1,-1],
417 [ 5, 7, 0, 5, 0, 9, 7,11, 0, 1, 0,10,11,10, 0,-1],
418 [11,10, 0,11, 0, 3,10, 5, 0, 8, 0, 7, 5, 7, 0,-1],
419 [11,10, 5, 7,11, 5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
420 [10, 6, 5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
421 [ 0, 8, 3, 5,10, 6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
422 [ 9, 0, 1, 5,10, 6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
423 [ 1, 8, 3, 1, 9, 8, 5,10, 6,-1,-1,-1,-1,-1,-1,-1],
424 [ 1, 6, 5, 2, 6, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
425 [ 1, 6, 5, 1, 2, 6, 3, 0, 8,-1,-1,-1,-1,-1,-1,-1],
426 [ 9, 6, 5, 9, 0, 6, 0, 2, 6,-1,-1,-1,-1,-1,-1,-1],
427 [ 5, 9, 8, 5, 8, 2, 5, 2, 6, 3, 2, 8,-1,-1,-1,-1],
428 [ 2, 3,11,10, 6, 5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
429 [11, 0, 8,11, 2, 0,10, 6, 5,-1,-1,-1,-1,-1,-1,-1],
430 [ 0, 1, 9, 2, 3,11, 5,10, 6,-1,-1,-1,-1,-1,-1,-1],
431 [ 5,10, 6, 1, 9, 2, 9,11, 2, 9, 8,11,-1,-1,-1,-1],
432 [ 6, 3,11, 6, 5, 3, 5, 1, 3,-1,-1,-1,-1,-1,-1,-1],
433 [ 0, 8,11, 0,11, 5, 0, 5, 1, 5,11, 6,-1,-1,-1,-1],
434 [ 3,11, 6, 0, 3, 6, 0, 6, 5, 0, 5, 9,-1,-1,-1,-1],
435 [ 6, 5, 9, 6, 9,11,11, 9, 8,-1,-1,-1,-1,-1,-1,-1],
436 [ 5,10, 6, 4, 7, 8,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
437 [ 4, 3, 0, 4, 7, 3, 6, 5,10,-1,-1,-1,-1,-1,-1,-1],
438 [ 1, 9, 0, 5,10, 6, 8, 4, 7,-1,-1,-1,-1,-1,-1,-1],
439 [10, 6, 5, 1, 9, 7, 1, 7, 3, 7, 9, 4,-1,-1,-1,-1],
440 [ 6, 1, 2, 6, 5, 1, 4, 7, 8,-1,-1,-1,-1,-1,-1,-1],
441 [ 1, 2, 5, 5, 2, 6, 3, 0, 4, 3, 4, 7,-1,-1,-1,-1],
442 [ 8, 4, 7, 9, 0, 5, 0, 6, 5, 0, 2, 6,-1,-1,-1,-1],
443 [ 7, 3, 9, 7, 9, 4, 3, 2, 9, 5, 9, 6, 2, 6, 9,-1],
444 [ 3,11, 2, 7, 8, 4,10, 6, 5,-1,-1,-1,-1,-1,-1,-1],
445 [ 5,10, 6, 4, 7, 2, 4, 2, 0, 2, 7,11,-1,-1,-1,-1],
446 [ 0, 1, 9, 4, 7, 8, 2, 3,11, 5,10, 6,-1,-1,-1,-1],
447 [ 9, 2, 1, 9,11, 2, 9, 4,11, 7,11, 4, 5,10, 6,-1],
448 [ 8, 4, 7, 3,11, 5, 3, 5, 1, 5,11, 6,-1,-1,-1,-1],
449 [ 5, 1,11, 5,11, 6, 1, 0,11, 7,11, 4, 0, 4,11,-1],
450 [ 0, 5, 9, 0, 6, 5, 0, 3, 6,11, 6, 3, 8, 4, 7,-1],
451 [ 6, 5, 9, 6, 9,11, 4, 7, 9, 7,11, 9,-1,-1,-1,-1],
452 [10, 4, 9, 6, 4,10,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
453 [ 4,10, 6, 4, 9,10, 0, 8, 3,-1,-1,-1,-1,-1,-1,-1],
454 [10, 0, 1,10, 6, 0, 6, 4, 0,-1,-1,-1,-1,-1,-1,-1],
455 [ 8, 3, 1, 8, 1, 6, 8, 6, 4, 6, 1,10,-1,-1,-1,-1],
456 [ 1, 4, 9, 1, 2, 4, 2, 6, 4,-1,-1,-1,-1,-1,-1,-1],
457 [ 3, 0, 8, 1, 2, 9, 2, 4, 9, 2, 6, 4,-1,-1,-1,-1],
458 [ 0, 2, 4, 4, 2, 6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
459 [ 8, 3, 2, 8, 2, 4, 4, 2, 6,-1,-1,-1,-1,-1,-1,-1],
460 [10, 4, 9,10, 6, 4,11, 2, 3,-1,-1,-1,-1,-1,-1,-1],
461 [ 0, 8, 2, 2, 8,11, 4, 9,10, 4,10, 6,-1,-1,-1,-1],
462 [ 3,11, 2, 0, 1, 6, 0, 6, 4, 6, 1,10,-1,-1,-1,-1],
463 [ 6, 4, 1, 6, 1,10, 4, 8, 1, 2, 1,11, 8,11, 1,-1],
464 [ 9, 6, 4, 9, 3, 6, 9, 1, 3,11, 6, 3,-1,-1,-1,-1],
465 [ 8,11, 1, 8, 1, 0,11, 6, 1, 9, 1, 4, 6, 4, 1,-1],
466 [ 3,11, 6, 3, 6, 0, 0, 6, 4,-1,-1,-1,-1,-1,-1,-1],
467 [ 6, 4, 8,11, 6, 8,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
468 [ 7,10, 6, 7, 8,10, 8, 9,10,-1,-1,-1,-1,-1,-1,-1],
469 [ 0, 7, 3, 0,10, 7, 0, 9,10, 6, 7,10,-1,-1,-1,-1],
470 [10, 6, 7, 1,10, 7, 1, 7, 8, 1, 8, 0,-1,-1,-1,-1],
471 [10, 6, 7,10, 7, 1, 1, 7, 3,-1,-1,-1,-1,-1,-1,-1],
472 [ 1, 2, 6, 1, 6, 8, 1, 8, 9, 8, 6, 7,-1,-1,-1,-1],
473 [ 2, 6, 9, 2, 9, 1, 6, 7, 9, 0, 9, 3, 7, 3, 9,-1],
474 [ 7, 8, 0, 7, 0, 6, 6, 0, 2,-1,-1,-1,-1,-1,-1,-1],
475 [ 7, 3, 2, 6, 7, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
476 [ 2, 3,11,10, 6, 8,10, 8, 9, 8, 6, 7,-1,-1,-1,-1],
477 [ 2, 0, 7, 2, 7,11, 0, 9, 7, 6, 7,10, 9,10, 7,-1],
478 [ 1, 8, 0, 1, 7, 8, 1,10, 7, 6, 7,10, 2, 3,11,-1],
479 [11, 2, 1,11, 1, 7,10, 6, 1, 6, 7, 1,-1,-1,-1,-1],
480 [ 8, 9, 6, 8, 6, 7, 9, 1, 6,11, 6, 3, 1, 3, 6,-1],
481 [ 0, 9, 1,11, 6, 7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
482 [ 7, 8, 0, 7, 0, 6, 3,11, 0,11, 6, 0,-1,-1,-1,-1],
483 [ 7,11, 6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
484 [ 7, 6,11,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
485 [ 3, 0, 8,11, 7, 6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
486 [ 0, 1, 9,11, 7, 6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
487 [ 8, 1, 9, 8, 3, 1,11, 7, 6,-1,-1,-1,-1,-1,-1,-1],
488 [10, 1, 2, 6,11, 7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
489 [ 1, 2,10, 3, 0, 8, 6,11, 7,-1,-1,-1,-1,-1,-1,-1],
490 [ 2, 9, 0, 2,10, 9, 6,11, 7,-1,-1,-1,-1,-1,-1,-1],
491 [ 6,11, 7, 2,10, 3,10, 8, 3,10, 9, 8,-1,-1,-1,-1],
492 [ 7, 2, 3, 6, 2, 7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
493 [ 7, 0, 8, 7, 6, 0, 6, 2, 0,-1,-1,-1,-1,-1,-1,-1],
494 [ 2, 7, 6, 2, 3, 7, 0, 1, 9,-1,-1,-1,-1,-1,-1,-1],
495 [ 1, 6, 2, 1, 8, 6, 1, 9, 8, 8, 7, 6,-1,-1,-1,-1],
496 [10, 7, 6,10, 1, 7, 1, 3, 7,-1,-1,-1,-1,-1,-1,-1],
497 [10, 7, 6, 1, 7,10, 1, 8, 7, 1, 0, 8,-1,-1,-1,-1],
498 [ 0, 3, 7, 0, 7,10, 0,10, 9, 6,10, 7,-1,-1,-1,-1],
499 [ 7, 6,10, 7,10, 8, 8,10, 9,-1,-1,-1,-1,-1,-1,-1],
500 [ 6, 8, 4,11, 8, 6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
501 [ 3, 6,11, 3, 0, 6, 0, 4, 6,-1,-1,-1,-1,-1,-1,-1],
502 [ 8, 6,11, 8, 4, 6, 9, 0, 1,-1,-1,-1,-1,-1,-1,-1],
503 [ 9, 4, 6, 9, 6, 3, 9, 3, 1,11, 3, 6,-1,-1,-1,-1],
504 [ 6, 8, 4, 6,11, 8, 2,10, 1,-1,-1,-1,-1,-1,-1,-1],
505 [ 1, 2,10, 3, 0,11, 0, 6,11, 0, 4, 6,-1,-1,-1,-1],
506 [ 4,11, 8, 4, 6,11, 0, 2, 9, 2,10, 9,-1,-1,-1,-1],
507 [10, 9, 3,10, 3, 2, 9, 4, 3,11, 3, 6, 4, 6, 3,-1],
508 [ 8, 2, 3, 8, 4, 2, 4, 6, 2,-1,-1,-1,-1,-1,-1,-1],
509 [ 0, 4, 2, 4, 6, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
510 [ 1, 9, 0, 2, 3, 4, 2, 4, 6, 4, 3, 8,-1,-1,-1,-1],
511 [ 1, 9, 4, 1, 4, 2, 2, 4, 6,-1,-1,-1,-1,-1,-1,-1],
512 [ 8, 1, 3, 8, 6, 1, 8, 4, 6, 6,10, 1,-1,-1,-1,-1],
513 [10, 1, 0,10, 0, 6, 6, 0, 4,-1,-1,-1,-1,-1,-1,-1],
514 [ 4, 6, 3, 4, 3, 8, 6,10, 3, 0, 3, 9,10, 9, 3,-1],
515 [10, 9, 4, 6,10, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
516 [ 4, 9, 5, 7, 6,11,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
517 [ 0, 8, 3, 4, 9, 5,11, 7, 6,-1,-1,-1,-1,-1,-1,-1],
518 [ 5, 0, 1, 5, 4, 0, 7, 6,11,-1,-1,-1,-1,-1,-1,-1],
519 [11, 7, 6, 8, 3, 4, 3, 5, 4, 3, 1, 5,-1,-1,-1,-1],
520 [ 9, 5, 4,10, 1, 2, 7, 6,11,-1,-1,-1,-1,-1,-1,-1],
521 [ 6,11, 7, 1, 2,10, 0, 8, 3, 4, 9, 5,-1,-1,-1,-1],
522 [ 7, 6,11, 5, 4,10, 4, 2,10, 4, 0, 2,-1,-1,-1,-1],
523 [ 3, 4, 8, 3, 5, 4, 3, 2, 5,10, 5, 2,11, 7, 6,-1],
524 [ 7, 2, 3, 7, 6, 2, 5, 4, 9,-1,-1,-1,-1,-1,-1,-1],
525 [ 9, 5, 4, 0, 8, 6, 0, 6, 2, 6, 8, 7,-1,-1,-1,-1],
526 [ 3, 6, 2, 3, 7, 6, 1, 5, 0, 5, 4, 0,-1,-1,-1,-1],
527 [ 6, 2, 8, 6, 8, 7, 2, 1, 8, 4, 8, 5, 1, 5, 8,-1],
528 [ 9, 5, 4,10, 1, 6, 1, 7, 6, 1, 3, 7,-1,-1,-1,-1],
529 [ 1, 6,10, 1, 7, 6, 1, 0, 7, 8, 7, 0, 9, 5, 4,-1],
530 [ 4, 0,10, 4,10, 5, 0, 3,10, 6,10, 7, 3, 7,10,-1],
531 [ 7, 6,10, 7,10, 8, 5, 4,10, 4, 8,10,-1,-1,-1,-1],
532 [ 6, 9, 5, 6,11, 9,11, 8, 9,-1,-1,-1,-1,-1,-1,-1],
533 [ 3, 6,11, 0, 6, 3, 0, 5, 6, 0, 9, 5,-1,-1,-1,-1],
534 [ 0,11, 8, 0, 5,11, 0, 1, 5, 5, 6,11,-1,-1,-1,-1],
535 [ 6,11, 3, 6, 3, 5, 5, 3, 1,-1,-1,-1,-1,-1,-1,-1],
536 [ 1, 2,10, 9, 5,11, 9,11, 8,11, 5, 6,-1,-1,-1,-1],
537 [ 0,11, 3, 0, 6,11, 0, 9, 6, 5, 6, 9, 1, 2,10,-1],
538 [11, 8, 5,11, 5, 6, 8, 0, 5,10, 5, 2, 0, 2, 5,-1],
539 [ 6,11, 3, 6, 3, 5, 2,10, 3,10, 5, 3,-1,-1,-1,-1],
540 [ 5, 8, 9, 5, 2, 8, 5, 6, 2, 3, 8, 2,-1,-1,-1,-1],
541 [ 9, 5, 6, 9, 6, 0, 0, 6, 2,-1,-1,-1,-1,-1,-1,-1],
542 [ 1, 5, 8, 1, 8, 0, 5, 6, 8, 3, 8, 2, 6, 2, 8,-1],
543 [ 1, 5, 6, 2, 1, 6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
544 [ 1, 3, 6, 1, 6,10, 3, 8, 6, 5, 6, 9, 8, 9, 6,-1],
545 [10, 1, 0,10, 0, 6, 9, 5, 0, 5, 6, 0,-1,-1,-1,-1],
546 [ 0, 3, 8, 5, 6,10,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
547 [10, 5, 6,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
548 [11, 5,10, 7, 5,11,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
549 [11, 5,10,11, 7, 5, 8, 3, 0,-1,-1,-1,-1,-1,-1,-1],
550 [ 5,11, 7, 5,10,11, 1, 9, 0,-1,-1,-1,-1,-1,-1,-1],
551 [10, 7, 5,10,11, 7, 9, 8, 1, 8, 3, 1,-1,-1,-1,-1],
552 [11, 1, 2,11, 7, 1, 7, 5, 1,-1,-1,-1,-1,-1,-1,-1],
553 [ 0, 8, 3, 1, 2, 7, 1, 7, 5, 7, 2,11,-1,-1,-1,-1],
554 [ 9, 7, 5, 9, 2, 7, 9, 0, 2, 2,11, 7,-1,-1,-1,-1],
555 [ 7, 5, 2, 7, 2,11, 5, 9, 2, 3, 2, 8, 9, 8, 2,-1],
556 [ 2, 5,10, 2, 3, 5, 3, 7, 5,-1,-1,-1,-1,-1,-1,-1],
557 [ 8, 2, 0, 8, 5, 2, 8, 7, 5,10, 2, 5,-1,-1,-1,-1],
558 [ 9, 0, 1, 5,10, 3, 5, 3, 7, 3,10, 2,-1,-1,-1,-1],
559 [ 9, 8, 2, 9, 2, 1, 8, 7, 2,10, 2, 5, 7, 5, 2,-1],
560 [ 1, 3, 5, 3, 7, 5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
561 [ 0, 8, 7, 0, 7, 1, 1, 7, 5,-1,-1,-1,-1,-1,-1,-1],
562 [ 9, 0, 3, 9, 3, 5, 5, 3, 7,-1,-1,-1,-1,-1,-1,-1],
563 [ 9, 8, 7, 5, 9, 7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
564 [ 5, 8, 4, 5,10, 8,10,11, 8,-1,-1,-1,-1,-1,-1,-1],
565 [ 5, 0, 4, 5,11, 0, 5,10,11,11, 3, 0,-1,-1,-1,-1],
566 [ 0, 1, 9, 8, 4,10, 8,10,11,10, 4, 5,-1,-1,-1,-1],
567 [10,11, 4,10, 4, 5,11, 3, 4, 9, 4, 1, 3, 1, 4,-1],
568 [ 2, 5, 1, 2, 8, 5, 2,11, 8, 4, 5, 8,-1,-1,-1,-1],
569 [ 0, 4,11, 0,11, 3, 4, 5,11, 2,11, 1, 5, 1,11,-1],
570 [ 0, 2, 5, 0, 5, 9, 2,11, 5, 4, 5, 8,11, 8, 5,-1],
571 [ 9, 4, 5, 2,11, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
572 [ 2, 5,10, 3, 5, 2, 3, 4, 5, 3, 8, 4,-1,-1,-1,-1],
573 [ 5,10, 2, 5, 2, 4, 4, 2, 0,-1,-1,-1,-1,-1,-1,-1],
574 [ 3,10, 2, 3, 5,10, 3, 8, 5, 4, 5, 8, 0, 1, 9,-1],
575 [ 5,10, 2, 5, 2, 4, 1, 9, 2, 9, 4, 2,-1,-1,-1,-1],
576 [ 8, 4, 5, 8, 5, 3, 3, 5, 1,-1,-1,-1,-1,-1,-1,-1],
577 [ 0, 4, 5, 1, 0, 5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
578 [ 8, 4, 5, 8, 5, 3, 9, 0, 5, 0, 3, 5,-1,-1,-1,-1],
579 [ 9, 4, 5,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
580 [ 4,11, 7, 4, 9,11, 9,10,11,-1,-1,-1,-1,-1,-1,-1],
581 [ 0, 8, 3, 4, 9, 7, 9,11, 7, 9,10,11,-1,-1,-1,-1],
582 [ 1,10,11, 1,11, 4, 1, 4, 0, 7, 4,11,-1,-1,-1,-1],
583 [ 3, 1, 4, 3, 4, 8, 1,10, 4, 7, 4,11,10,11, 4,-1],
584 [ 4,11, 7, 9,11, 4, 9, 2,11, 9, 1, 2,-1,-1,-1,-1],
585 [ 9, 7, 4, 9,11, 7, 9, 1,11, 2,11, 1, 0, 8, 3,-1],
586 [11, 7, 4,11, 4, 2, 2, 4, 0,-1,-1,-1,-1,-1,-1,-1],
587 [11, 7, 4,11, 4, 2, 8, 3, 4, 3, 2, 4,-1,-1,-1,-1],
588 [ 2, 9,10, 2, 7, 9, 2, 3, 7, 7, 4, 9,-1,-1,-1,-1],
589 [ 9,10, 7, 9, 7, 4,10, 2, 7, 8, 7, 0, 2, 0, 7,-1],
590 [ 3, 7,10, 3,10, 2, 7, 4,10, 1,10, 0, 4, 0,10,-1],
591 [ 1,10, 2, 8, 7, 4,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
592 [ 4, 9, 1, 4, 1, 7, 7, 1, 3,-1,-1,-1,-1,-1,-1,-1],
593 [ 4, 9, 1, 4, 1, 7, 0, 8, 1, 8, 7, 1,-1,-1,-1,-1],
594 [ 4, 0, 3, 7, 4, 3,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
595 [ 4, 8, 7,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
596 [ 9,10, 8,10,11, 8,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
597 [ 3, 0, 9, 3, 9,11,11, 9,10,-1,-1,-1,-1,-1,-1,-1],
598 [ 0, 1,10, 0,10, 8, 8,10,11,-1,-1,-1,-1,-1,-1,-1],
599 [ 3, 1,10,11, 3,10,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
600 [ 1, 2,11, 1,11, 9, 9,11, 8,-1,-1,-1,-1,-1,-1,-1],
601 [ 3, 0, 9, 3, 9,11, 1, 2, 9, 2,11, 9,-1,-1,-1,-1],
602 [ 0, 2,11, 8, 0,11,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
603 [ 3, 2,11,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
604 [ 2, 3, 8, 2, 8,10,10, 8, 9,-1,-1,-1,-1,-1,-1,-1],
605 [ 9,10, 2, 0, 9, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
606 [ 2, 3, 8, 2, 8,10, 0, 1, 8, 1,10, 8,-1,-1,-1,-1],
607 [ 1,10, 2,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
608 [ 1, 3, 8, 9, 1, 8,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
609 [ 0, 9, 1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
610 [ 0, 3, 8,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
611 [-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],
612];
613
614#[cfg(test)]
615mod tests {
616 use super::*;
617
618 #[test]
619 fn test_sphere_isosurface() {
620 let n = 32u32;
621 let mut data = vec![0.0f32; (n * n * n) as usize];
622 let center = n as f32 / 2.0;
623
624 for iz in 0..n {
625 for iy in 0..n {
626 for ix in 0..n {
627 let dx = ix as f32 - center;
628 let dy = iy as f32 - center;
629 let dz = iz as f32 - center;
630 let dist = (dx * dx + dy * dy + dz * dz).sqrt();
631 data[(ix + iy * n + iz * n * n) as usize] = dist;
632 }
633 }
634 }
635
636 let volume = VolumeData {
637 data,
638 dims: [n, n, n],
639 origin: [0.0, 0.0, 0.0],
640 spacing: [1.0, 1.0, 1.0],
641 };
642
643 let mesh = extract_isosurface(&volume, 8.0);
644
645 assert!(
647 !mesh.positions.is_empty(),
648 "Sphere isosurface should produce vertices"
649 );
650
651 assert_eq!(mesh.indices.len() % 3, 0, "Indices must be a multiple of 3");
653
654 assert!(
656 mesh.indices.len() > 100,
657 "Expected > 100 indices for sphere, got {}",
658 mesh.indices.len()
659 );
660
661 for pos in &mesh.positions {
663 for c in pos {
664 assert!(
665 *c >= 4.0 && *c <= 28.0,
666 "Position component {} out of expected range [4, 28]",
667 c
668 );
669 }
670 }
671
672 for n in &mesh.normals {
674 let len = (n[0] * n[0] + n[1] * n[1] + n[2] * n[2]).sqrt();
675 assert!(
676 len > 0.95 && len < 1.05,
677 "Normal length {} not approximately 1.0",
678 len
679 );
680 }
681
682 assert_eq!(mesh.positions.len(), mesh.normals.len());
684 }
685
686 #[test]
687 fn test_empty_volume() {
688 let data = vec![10.0f32; 8]; let volume = VolumeData {
691 data,
692 dims: [2, 2, 2],
693 origin: [0.0, 0.0, 0.0],
694 spacing: [1.0, 1.0, 1.0],
695 };
696
697 let mesh = extract_isosurface(&volume, 5.0);
698 assert_eq!(
699 mesh.positions.len(),
700 0,
701 "All-above should produce empty mesh"
702 );
703 assert_eq!(mesh.indices.len(), 0);
704 }
705
706 #[test]
707 fn test_volume_data_sample() {
708 let data = vec![
709 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, ];
714 let vol = VolumeData {
715 data,
716 dims: [2, 2, 2],
717 origin: [0.0, 0.0, 0.0],
718 spacing: [1.0, 1.0, 1.0],
719 };
720
721 assert_eq!(vol.sample(0, 0, 0), 1.0);
722 assert_eq!(vol.sample(1, 0, 0), 2.0);
723 assert_eq!(vol.sample(0, 1, 0), 3.0);
724 assert_eq!(vol.sample(1, 1, 0), 4.0);
725 assert_eq!(vol.sample(0, 0, 1), 5.0);
726 assert_eq!(vol.sample(1, 0, 1), 6.0);
727 assert_eq!(vol.sample(0, 1, 1), 7.0);
728 assert_eq!(vol.sample(1, 1, 1), 8.0);
729 }
730
731 #[test]
732 fn test_trilinear_interpolation() {
733 let data = vec![
734 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, ];
739 let vol = VolumeData {
740 data,
741 dims: [2, 2, 2],
742 origin: [0.0, 0.0, 0.0],
743 spacing: [1.0, 1.0, 1.0],
744 };
745
746 let v00 = trilinear_sample(&vol, [0.0, 0.0, 0.0]);
748 assert!(
749 (v00 - 0.0).abs() < 0.01,
750 "Expected 0.0 at origin, got {}",
751 v00
752 );
753
754 let v10 = trilinear_sample(&vol, [0.999, 0.0, 0.0]);
755 assert!(
756 (v10 - 1.0).abs() < 0.02,
757 "Expected ~1.0 at (1,0,0), got {}",
758 v10
759 );
760
761 let mid = trilinear_sample(&vol, [0.5, 0.0, 0.0]);
763 assert!(
764 (mid - 0.5).abs() < 0.01,
765 "Expected 0.5 at midpoint, got {}",
766 mid
767 );
768
769 let center = trilinear_sample(&vol, [0.5, 0.5, 0.5]);
771 assert!(
772 (center - 0.5).abs() < 0.01,
773 "Expected 0.5 at center, got {}",
774 center
775 );
776 }
777}