1use crate::{
7 boolean_operation, make_box_brep, make_sphere_brep, transform_brep, AffineTransform,
8 BooleanOperation, BooleanOptions, BrepSolid, MeshSegmentation, RegionCarrier, SegmentOptions,
9 Vec3,
10};
11
12#[derive(Clone)]
13struct Sphere {
14 center: Vec3,
15 radius: f64,
16 sag: f64,
18}
19
20struct BoxCarrier {
21 axes: [Vec3; 3],
22 low: [f64; 3],
23 high: [f64; 3],
24}
25
26impl BoxCarrier {
27 fn distance(&self, p: Vec3) -> f64 {
28 (0..3)
29 .map(|i| {
30 let h = p.dot(self.axes[i]);
31 (self.low[i] - h).max(h - self.high[i])
32 })
33 .fold(f64::NEG_INFINITY, f64::max)
34 }
35
36 fn point(&self, coordinates: [f64; 3]) -> Vec3 {
37 (0..3).fold(Vec3::default(), |p, i| {
38 p.add(self.axes[i].scale(coordinates[i]))
39 })
40 }
41
42 fn solid(&self) -> Result<BrepSolid, String> {
43 let local = make_box_brep(
44 Vec3::default(),
45 self.high[0] - self.low[0],
46 self.high[1] - self.low[1],
47 self.high[2] - self.low[2],
48 )?;
49 let origin = self.point(self.low);
50 let [x, y, z] = self.axes;
51 let transform = AffineTransform::new([
52 x.x, y.x, z.x, origin.x, x.y, y.y, z.y, origin.y, x.z, y.z, z.z, origin.z, 0., 0., 0.,
53 1.,
54 ])?;
55 transform_brep(&local, transform, transform.determinant3() < 0.)
56 }
57}
58
59fn box_carrier(seg: &MeshSegmentation, precision: f64) -> Option<Option<BoxCarrier>> {
60 let planes: Vec<_> = seg
61 .regions
62 .iter()
63 .filter_map(|r| match r.carrier {
64 RegionCarrier::Plane { origin, normal } => Some((origin, normal)),
65 _ => None,
66 })
67 .collect();
68 if planes.is_empty() {
69 return Some(None);
70 }
71 if planes.len() != 6 {
72 return None;
73 }
74 let mut axes = Vec::<Vec3>::new();
75 let mut ranges = Vec::<[f64; 2]>::new();
76 let mut counts = Vec::<[usize; 2]>::new();
77 for (origin, normal) in planes {
78 let index = axes
79 .iter()
80 .position(|axis| axis.cross(normal).length() < 1e-6);
81 let i = match index {
82 Some(i) => i,
83 None => {
84 if axes.len() == 3 || axes.iter().any(|axis| axis.dot(normal).abs() > 1e-6) {
85 return None;
86 }
87 axes.push(normal.normalized().ok()?);
88 ranges.push([f64::NEG_INFINITY, f64::INFINITY]);
89 counts.push([0, 0]);
90 axes.len() - 1
91 }
92 };
93 let side = usize::from(axes[i].dot(normal) > 0.);
94 ranges[i][side] = origin.dot(axes[i]);
95 counts[i][side] += 1;
96 }
97 if axes.len() != 3
98 || counts.iter().any(|c| *c != [1, 1])
99 || ranges.iter().any(|r| r[1] - r[0] <= precision)
100 {
101 return None;
102 }
103 Some(Some(BoxCarrier {
104 axes: [axes[0], axes[1], axes[2]],
105 low: [ranges[0][0], ranges[1][0], ranges[2][0]],
106 high: [ranges[0][1], ranges[1][1], ranges[2][1]],
107 }))
108}
109
110fn sphere_distance(sphere: &Sphere, p: Vec3) -> f64 {
111 p.sub(sphere.center).length() - sphere.radius
112}
113
114fn union_distance(spheres: &[Sphere], cube: Option<&BoxCarrier>, p: Vec3) -> f64 {
115 spheres
116 .iter()
117 .map(|s| sphere_distance(s, p))
118 .fold(cube.map_or(f64::INFINITY, |b| b.distance(p)), f64::min)
119}
120
121pub fn reconstruct_spherical_mesh_union(
131 positions: &[f64],
132 indices: &[u32],
133 seg: &MeshSegmentation,
134 options: &SegmentOptions,
135) -> Option<BrepSolid> {
136 let sequential;
137 let indices = if indices.is_empty() {
138 if positions.len() % 9 != 0 || positions.len() / 9 > 10_000 {
139 return None;
140 }
141 sequential = (0..positions.len() as u32 / 3).collect::<Vec<_>>();
142 &sequential
143 } else {
144 indices
145 };
146 if !options.normal_tolerance_deg.is_finite()
148 || !(0. ..90.).contains(&options.normal_tolerance_deg)
149 || positions.len() % 3 != 0
150 || indices.len() % 3 != 0
151 || indices.len() / 3 > 10_000
152 || seg.regions.len() > 16
153 || seg.triangle_region_ids.len() != indices.len() / 3
154 {
155 return None;
156 }
157 let vertices: Vec<_> = positions
158 .chunks_exact(3)
159 .map(|p| Vec3::new(p[0], p[1], p[2]))
160 .collect();
161 if vertices
162 .iter()
163 .any(|p| !p.x.is_finite() || !p.y.is_finite() || !p.z.is_finite())
164 {
165 return None;
166 }
167 let triangles: Vec<[Vec3; 3]> = indices
168 .chunks_exact(3)
169 .map(|t| {
170 Some([
171 *vertices.get(t[0] as usize)?,
172 *vertices.get(t[1] as usize)?,
173 *vertices.get(t[2] as usize)?,
174 ])
175 })
176 .collect::<Option<_>>()?;
177 let mut lo = vertices.first().copied()?;
180 let mut hi = lo;
181 for p in &vertices {
182 lo = Vec3::new(lo.x.min(p.x), lo.y.min(p.y), lo.z.min(p.z));
183 hi = Vec3::new(hi.x.max(p.x), hi.y.max(p.y), hi.z.max(p.z));
184 }
185 let weld_tolerance = if options.weld_tolerance > 0. && options.weld_tolerance.is_finite() {
186 options.weld_tolerance
187 } else {
188 (hi.sub(lo).length() * 1e-6).max(1e-12)
189 };
190 let mut welded = Vec::new();
191 let mut buckets = rustc_hash::FxHashMap::default();
192 let vertex_ids: Vec<_> = vertices
193 .iter()
194 .map(|&p| {
195 crate::mesh_weld::weld_vertex(p, weld_tolerance, &mut welded, &mut buckets) as u32
196 })
197 .collect();
198 let mut edges = rustc_hash::FxHashMap::<(u32, u32), (usize, i32)>::default();
199 for t in indices.chunks_exact(3) {
200 for i in 0..3 {
201 let (a, b) = (
202 vertex_ids[t[i] as usize],
203 vertex_ids[t[(i + 1) % 3] as usize],
204 );
205 if a == b {
206 return None;
207 }
208 let entry = edges.entry((a.min(b), a.max(b))).or_default();
209 entry.0 += 1;
210 entry.1 += if a < b { 1 } else { -1 };
211 }
212 }
213 if edges
214 .values()
215 .any(|&(count, sense)| count != 2 || sense != 0)
216 {
217 return None;
218 }
219 let mut spheres = Vec::new();
220 for region in &seg.regions {
221 match region.carrier {
222 RegionCarrier::Sphere {
223 center,
224 radius,
225 sense: 1,
226 } => {
227 let precision = radius * 2e-6;
228 if !radius.is_finite()
229 || radius <= 0.
230 || region.triangle_count < 24
231 || !region.max_deviation.is_finite()
232 || region.max_deviation > precision
233 {
234 return None;
235 }
236 let mut sag = 0_f64;
237 for (t, &owner) in triangles.iter().zip(&seg.triangle_region_ids) {
238 if owner != region.id {
239 continue;
240 }
241 let normal = t[1].sub(t[0]).cross(t[2].sub(t[0])).normalized().ok()?;
242 let height = t[0].sub(center).dot(normal);
243 if height <= 0. {
244 return None;
245 }
246 sag = sag.max(radius - height);
247 }
248 if sag <= 0. || sag > 0.02 * radius {
249 return None;
250 }
251 spheres.push(Sphere {
252 center,
253 radius,
254 sag: sag + precision,
255 });
256 }
257 RegionCarrier::Plane { .. } | RegionCarrier::Freeform => {}
258 _ => return None,
259 }
260 }
261 if spheres.is_empty() || spheres.len() > 2 {
262 return None;
263 }
264 let precision = spheres
265 .iter()
266 .map(|s| s.radius * 2e-6)
267 .fold(0_f64, f64::max);
268 let cube = box_carrier(seg, precision)?;
269 if spheres.len() == 1 && cube.is_none() {
270 return None;
271 }
272 let sag = spheres.iter().map(|s| s.sag).fold(0_f64, f64::max);
273 let cosine = options.normal_tolerance_deg.to_radians().cos();
274 for t in &triangles {
275 let normal = t[1].sub(t[0]).cross(t[2].sub(t[0])).normalized().ok()?;
276 let centroid = t[0].add(t[1]).add(t[2]).scale(1. / 3.);
277 let samples = [
278 t[0],
279 t[1],
280 t[2],
281 centroid,
282 t[0].add(t[1]).scale(0.5),
283 t[1].add(t[2]).scale(0.5),
284 t[2].add(t[0]).scale(0.5),
285 ];
286 if samples.iter().any(|&p| {
287 let d = union_distance(&spheres, cube.as_ref(), p);
288 !d.is_finite() || d < -sag || d > precision
289 }) {
290 return None;
291 }
292 let spherical = spheres.iter().any(|s| {
293 samples
294 .iter()
295 .all(|&p| sphere_distance(s, p).abs() <= s.sag)
296 && centroid
297 .sub(s.center)
298 .normalized()
299 .is_ok_and(|n| n.dot(normal) >= cosine)
300 });
301 let planar = cube.as_ref().is_some_and(|b| {
302 (0..3).any(|i| {
303 [(-1., b.low[i]), (1., b.high[i])].iter().any(|&(sign, h)| {
304 normal.dot(b.axes[i].scale(sign)) >= cosine
305 && samples
306 .iter()
307 .all(|p| (p.dot(b.axes[i]) - h).abs() <= precision)
308 })
309 })
310 });
311 if !spherical && !planar {
312 return None;
313 }
314 }
315 let covered = |p: Vec3| -> bool {
318 union_distance(&spheres, cube.as_ref(), p) < -precision
319 || triangles
320 .iter()
321 .any(|t| point_triangle_distance(p, *t) <= sag)
322 };
323 for s in &spheres {
324 for i in 0..2048 {
326 let z = 1. - 2. * (i as f64 + 0.5) / 2048.;
327 let angle = i as f64 * std::f64::consts::PI * (3. - 5_f64.sqrt());
328 let r = (1. - z * z).sqrt();
329 if !covered(
330 s.center
331 .add(Vec3::new(r * angle.cos(), r * angle.sin(), z).scale(s.radius)),
332 ) {
333 return None;
334 }
335 }
336 for axis in [
337 Vec3::new(1., 0., 0.),
338 Vec3::new(0., 1., 0.),
339 Vec3::new(0., 0., 1.),
340 ] {
341 for sign in [-1., 1.] {
342 if !covered(s.center.add(axis.scale(sign * s.radius))) {
343 return None;
344 }
345 }
346 }
347 }
348 if let Some(b) = &cube {
349 for i in 0..3 {
350 for h in [b.low[i], b.high[i]] {
351 for u in 0..=16 {
352 for v in 0..=16 {
353 let mut coordinates = b.low;
354 coordinates[i] = h;
355 for (j, fraction) in [((i + 1) % 3, u), ((i + 2) % 3, v)] {
356 coordinates[j] += (b.high[j] - b.low[j]) * fraction as f64 / 16.;
357 }
358 if !covered(b.point(coordinates)) {
359 return None;
360 }
361 }
362 }
363 }
364 }
365 }
366 let mut primitives = Vec::new();
367 if let Some(b) = cube {
368 primitives.push(b.solid().ok()?);
369 }
370 for s in spheres {
371 primitives.push(make_sphere_brep(s.center, s.radius, Vec3::new(0., 0., 1.)).ok()?);
372 }
373 let mut result = primitives.remove(0);
374 let boolean_options = BooleanOptions {
375 tolerance: (precision * 0.05).max(1e-12),
376 ..BooleanOptions::default()
377 };
378 for primitive in primitives {
379 result = boolean_operation(
380 &result,
381 &primitive,
382 BooleanOperation::Union,
383 &boolean_options,
384 )
385 .ok()?;
386 }
387 result.validate().is_empty().then_some(result)
388}
389
390fn point_triangle_distance(p: Vec3, [a, b, c]: [Vec3; 3]) -> f64 {
391 let ab = b.sub(a);
392 let ac = c.sub(a);
393 let ap = p.sub(a);
394 let n = ab.cross(ac);
395 let nn = n.length_squared();
396 if nn > 0. {
397 let projection = p.sub(n.scale(ap.dot(n) / nn));
398 if [(a, b), (b, c), (c, a)]
399 .iter()
400 .all(|&(x, y)| y.sub(x).cross(projection.sub(x)).dot(n) >= -1e-14 * nn)
401 {
402 return ap.dot(n).abs() / nn.sqrt();
403 }
404 }
405 [(a, b), (b, c), (c, a)]
406 .iter()
407 .map(|&(x, y)| {
408 let edge = y.sub(x);
409 let t = (p.sub(x).dot(edge) / edge.length_squared()).clamp(0., 1.);
410 p.sub(x.add(edge.scale(t))).length()
411 })
412 .fold(f64::INFINITY, f64::min)
413}