1use crate::{
21 common::{arc3, line2, pt3, sqrt2_over_2},
22 revolve::{close_bottom_pole_gap, close_top_pole_gap},
23};
24use geop_core_geometry::nurb_surface::NurbSurface;
25use geop_core_math::{
26 geop_error::GeopResult,
27 scalars::Scalar,
28 vector::{Vector2, Vector3, Vector4},
29};
30use geop_core_part::{Namer, Part};
31use geop_core_topology::SolidId;
32
33fn meridian_pcurve<S: Scalar>() -> GeopResult<geop_core_geometry::nurb_curve::NurbCurve2D<S>> {
49 line2(
50 Vector2::from_array([S::ONE, S::ZERO]),
51 Vector2::from_array([S::ONE, S::ONE]),
52 )
53}
54fn beam_pcurve_north<S: Scalar>() -> GeopResult<geop_core_geometry::nurb_curve::NurbCurve2D<S>> {
58 line2(
59 Vector2::from_array([S::ONE, S::ONE]),
60 Vector2::from_array([S::ZERO, S::ONE]),
61 )
62}
63fn beam_pcurve_south<S: Scalar>() -> GeopResult<geop_core_geometry::nurb_curve::NurbCurve2D<S>> {
67 line2(
68 Vector2::from_array([S::ZERO, S::ZERO]),
69 Vector2::from_array([S::ONE, S::ZERO]),
70 )
71}
72fn meridian_closing_pcurve<S: Scalar>() -> GeopResult<geop_core_geometry::nurb_curve::NurbCurve2D<S>>
79{
80 line2(
81 Vector2::from_array([S::ZERO, S::ONE]),
82 Vector2::from_array([S::ZERO, S::ZERO]),
83 )
84}
85
86fn sphere_quadrant_surface<S: Scalar>(
98 pole: Vector3<S>,
99 eq0: Vector3<S>,
100 eq1: Vector3<S>,
101 center: Vector3<S>,
102 w: S,
103 pole_at_v0: bool,
104) -> GeopResult<NurbSurface<S, 4>> {
105 let eq_mid = eq0.add(&eq1).sub(¢er);
106 let mer0_mid = pole.add(&eq0).sub(¢er);
107 let mer1_mid = pole.add(&eq1).sub(¢er);
108 let interior = pole.add(&eq0).add(&eq1).sub(¢er).sub(¢er);
113 let m = w.mul(w);
114
115 let wpt =
116 |p: Vector3<S>, wt: S| Vector4::from_array([p[0].mul(wt), p[1].mul(wt), p[2].mul(wt), wt]);
117
118 let mut u0 = [pt3(pole), wpt(mer0_mid, w), pt3(eq0)];
127 let mut u1 = [wpt(pole, w), wpt(interior, m), wpt(eq_mid, w)];
128 let mut u2 = [pt3(pole), wpt(mer1_mid, w), pt3(eq1)];
129 if !pole_at_v0 {
130 u0.reverse();
131 u1.reverse();
132 u2.reverse();
133 }
134
135 let knots = vec![S::ZERO, S::ZERO, S::ZERO, S::ONE, S::ONE, S::ONE];
145 let cps = vec![
146 u2[0], u2[1], u2[2], u1[0], u1[1], u1[2], u0[0], u0[1], u0[2],
147 ];
148 NurbSurface::try_new(2, 2, cps, knots.clone(), knots)
149}
150
151pub fn sphere_solid<S: Scalar>(
171 part: &mut Part<S>,
172 name: &str,
173 center: Vector3<S>,
174 radius: S,
175) -> GeopResult<SolidId> {
176 let namer = Namer::new("sphere", name)?;
177 let n = |args: &[&str]| namer.name(args);
178 let w = sqrt2_over_2::<S>();
179 let north = center.add(&Vector3::from_array([S::ZERO, S::ZERO, radius]));
180 let south = center.add(&Vector3::from_array([
181 S::ZERO,
182 S::ZERO,
183 S::ZERO.sub(radius),
184 ]));
185 let one = S::ONE;
186 let zero = S::ZERO;
187 let cos_t = [one, zero, zero.sub(one), zero];
188 let sin_t = [zero, one, zero, zero.sub(one)];
189 let equator = |k: usize| {
190 center.add(&Vector3::from_array([
191 radius.mul(cos_t[k]),
192 radius.mul(sin_t[k]),
193 zero,
194 ]))
195 };
196 let arc = |p0: Vector3<S>, p1: Vector3<S>| {
198 let mid = p0.add(&p1).sub(¢er);
199 arc3(p0, mid, p1, w)
200 };
201 let arc_beam = |k: usize, k1: usize| arc(equator(k), equator(k1));
202 let north_quadrant = |k: usize, k1: usize| {
203 sphere_quadrant_surface(north, equator(k), equator(k1), center, w, true)
204 };
205 let south_quadrant = |k: usize, k1: usize| {
206 sphere_quadrant_surface(south, equator(k), equator(k1), center, w, false)
207 };
208
209 let (v_north, face0, solid_id) =
211 part.mvfs(north, n(&["north"]), n(&["south", "q3"]), namer.root())?;
212
213 let (_, north_anchor0, mirror_north0, _) = part.mve_from_vertex(
220 face0,
221 v_north,
222 arc(north, equator(0))?,
223 meridian_pcurve()?,
224 meridian_closing_pcurve()?,
225 equator(0),
226 n(&["equator", "a0"]),
227 n(&["north", "a0"]),
228 )?;
229 let (_, south_anchor0, mirror_south0, _) = part.mve(
230 north_anchor0,
231 arc(equator(0), south)?,
232 meridian_pcurve()?,
233 meridian_closing_pcurve()?,
234 south,
235 n(&["south"]),
236 n(&["south", "a0"]),
237 )?;
238
239 let mut north_anchor = north_anchor0;
240 let mut south_anchor = south_anchor0;
241 for k in 0..3 {
242 let k1 = k + 1;
243 let (q, a1) = (format!("q{k}"), format!("a{k1}"));
244 let (_, beam_fwd, beam_rev, _) = part.mve(
245 north_anchor,
246 arc_beam(k, k1)?,
247 beam_pcurve_north()?,
248 beam_pcurve_south()?,
249 equator(k1),
250 n(&["equator", &a1]),
251 n(&["equator", &q]),
252 )?;
253
254 let (_, _, _, next_north_anchor) = part.mef(
255 beam_fwd,
256 north_anchor,
257 arc(equator(k1), north)?,
258 meridian_closing_pcurve()?,
259 meridian_pcurve()?,
260 north_quadrant(k, k1)?,
261 n(&["north", &a1]),
262 n(&["north", &q]),
263 )?;
264 close_top_pole_gap(part, north_anchor)?;
265 let (_, _, _, next_south_anchor) = part.mef(
266 south_anchor,
267 beam_rev,
268 arc(south, equator(k1))?,
269 meridian_closing_pcurve()?,
270 meridian_pcurve()?,
271 south_quadrant(k, k1)?,
272 n(&["south", &a1]),
273 n(&["south", &q]),
274 )?;
275 close_bottom_pole_gap(part, south_anchor)?;
276
277 north_anchor = next_north_anchor;
278 south_anchor = next_south_anchor;
279 }
280
281 part.mef(
288 north_anchor,
289 mirror_north0,
290 arc_beam(3, 0)?,
291 beam_pcurve_north()?,
292 beam_pcurve_south()?,
293 north_quadrant(3, 0)?,
294 n(&["equator", "q3"]),
295 n(&["north", "q3"]),
296 )?;
297 close_top_pole_gap(part, north_anchor)?;
298 let _ = mirror_south0;
299
300 close_bottom_pole_gap(part, south_anchor)?;
303 part.replace_face(face0, south_quadrant(3, 0)?)?;
304
305 Ok(solid_id)
306}
307
308#[cfg(test)]
309mod tests {
310 use super::*;
311 use geop_core_math::for_all_scalars;
312 use geop_core_topology::validation::{ValidationParameters, validate, validate_manifold};
313
314 fn check_offset_sphere_is_valid<S: Scalar>() {
315 let mut part = Part::<S>::new();
316 let center = Vector3::from_array([S::from_f64(1.0), S::from_f64(-0.5), S::from_f64(0.5)]);
317 sphere_solid(&mut part, "t1", center, S::from_f64(2.0)).unwrap();
318 let model = part.topology();
319
320 let params = ValidationParameters::default();
321 if let Err(e) = validate(¶ms, &model) {
322 panic!("{e:?}");
323 }
324 if let Err(e) = validate_manifold(¶ms, &model) {
325 panic!("{e:?}");
326 }
327 }
328 #[test]
329 fn offset_sphere_is_valid() {
330 for_all_scalars!(check_offset_sphere_is_valid);
331 }
332
333 fn check_sphere_solid_normals_point_outward<S: Scalar>() {
334 let mut part = Part::<S>::new();
335 let center = Vector3::from_array([S::ZERO; 3]);
336 sphere_solid(&mut part, "t2", center, S::ONE).unwrap();
337 let model = part.topology();
338
339 for face in model.faces.values() {
340 let (u0, u1) = face.surface.domain_u();
341 let (v0, v1) = face.surface.domain_v();
342 let mid_u = u0.add(u1.sub(u0).mul(S::from_f64(0.5)));
343 let mid_v = v0.add(v1.sub(v0).mul(S::from_f64(0.5)));
344 let p = face.surface.evaluate(mid_u, mid_v).unwrap();
345 let n = face.surface.normal(mid_u, mid_v).unwrap();
346 let outward = p.sub(¢er);
347 let dot = n.prod_dot(&outward).to_f64();
348 assert!(dot > 0.0, "p={p:?}, n={n:?}, dot={dot}");
349 }
350 }
351 #[test]
352 fn sphere_solid_normals_point_outward() {
353 for_all_scalars!(check_sphere_solid_normals_point_outward);
354 }
355
356 fn check_sphere_solid_is_valid<S: Scalar>() {
357 let mut part = Part::<S>::new();
358 sphere_solid(&mut part, "t3", Vector3::from_array([S::ZERO; 3]), S::ONE).unwrap();
359 let model = part.topology();
360 assert_eq!(model.faces.len(), 8);
361
362 let params = ValidationParameters::default();
363 if let Err(e) = validate(¶ms, &model) {
364 panic!("{e:?}");
365 }
366 if let Err(e) = validate_manifold(¶ms, &model) {
367 panic!("{e:?}");
368 }
369 }
370 #[test]
371 fn sphere_solid_is_valid() {
372 for_all_scalars!(check_sphere_solid_is_valid);
373 }
374
375 fn check_sphere_solid_face_midpoints_are_on_the_sphere<S: Scalar>() {
387 let mut part = Part::<S>::new();
388 let center = Vector3::from_array([S::from_f64(1.0), S::from_f64(-0.5), S::from_f64(0.5)]);
389 let radius = S::from_f64(2.0);
390 let solid = sphere_solid(&mut part, "t4", center, radius).unwrap();
391 let model = part.topology();
392
393 let faces = model.solid_faces(solid).unwrap();
394 assert_eq!(faces.len(), 8, "a sphere is exactly 8 octants");
395
396 let radius_sq = radius.mul(radius);
397 for face_id in faces {
398 let face = model.get_face(face_id).unwrap();
399 assert!(
400 !face.surface.is_everything(),
401 "face {face_id} still carries the `everything` placeholder surface"
402 );
403
404 let (u, v) = geop_core_topology::contains::face::face_interior_point(
405 &model,
406 face_id,
407 20000,
408 S::from_f64(1e-4),
409 0x5EED,
410 )
411 .unwrap_or_else(|e| panic!("face {face_id} has no interior point: {e}"));
412
413 let p = face.surface.evaluate(u, v).unwrap();
414 let d_sq = p.sub(¢er).norm_sq();
415 assert!(
416 d_sq.could_be_equal(radius_sq),
417 "face {face_id}'s interior point {p:?} is |p - center|^2={d_sq:?} from the centre, expected {radius_sq:?}"
418 );
419 }
420 }
421 #[test]
422 fn sphere_solid_face_midpoints_are_on_the_sphere() {
423 for_all_scalars!(check_sphere_solid_face_midpoints_are_on_the_sphere);
424 }
425
426 fn check_sphere_quadrant_surface_is_exact<S: Scalar>() {
427 let center = Vector3::from_array([S::from_f64(1.0), S::from_f64(-0.5), S::from_f64(0.5)]);
428 let radius = S::from_f64(2.0);
429 let north = center.add(&Vector3::from_array([S::ZERO, S::ZERO, radius]));
430 let south = center.add(&Vector3::from_array([
431 S::ZERO,
432 S::ZERO,
433 S::ZERO.sub(radius),
434 ]));
435 let one = S::ONE;
436 let zero = S::ZERO;
437 let cos_t = [one, zero, zero.sub(one), zero];
438 let sin_t = [zero, one, zero, zero.sub(one)];
439 let equator = |k: usize| {
440 center.add(&Vector3::from_array([
441 radius.mul(cos_t[k]),
442 radius.mul(sin_t[k]),
443 zero,
444 ]))
445 };
446 let w = crate::common::sqrt2_over_2::<S>();
447 let radius_sq = radius.mul(radius);
448
449 for k in 0..4 {
450 let k1 = (k + 1) % 4;
451 for (pole, pole_at_v0) in [(north, true), (south, false)] {
452 let surface =
453 sphere_quadrant_surface(pole, equator(k), equator(k1), center, w, pole_at_v0)
454 .unwrap();
455 for i in 0..=4 {
456 for j in 0..=4 {
457 let u = S::from_ratio(i as i64, 4).unwrap();
458 let v = S::from_ratio(j as i64, 4).unwrap();
459 let p = surface.evaluate(u, v).unwrap();
460 let d_sq = p.sub(¢er).norm_sq();
461 let err = d_sq.sub(radius_sq).abs();
462 assert!(
463 !err.definitely_greater(S::from_f64(1e-9)),
464 "k={k}, pole_at_v0={pole_at_v0}, u={u}, v={v}, p={p:?}, |p-center|^2={d_sq}, expected={radius_sq}"
465 );
466 }
467 }
468 }
469 }
470 }
471 #[test]
472 fn sphere_quadrant_surface_is_exact() {
473 for_all_scalars!(check_sphere_quadrant_surface_is_exact);
474 }
475
476 fn check_sphere_solid_points_on_sphere<S: Scalar>() {
482 let mut part = Part::<S>::new();
483 let center = Vector3::from_array([S::from_f64(1.0), S::from_f64(-0.5), S::from_f64(0.5)]);
484 let radius = S::from_f64(2.0);
485 sphere_solid(&mut part, "t5", center, radius).unwrap();
486 let model = part.topology();
487
488 let radius_sq = radius.mul(radius);
489 let assert_on_sphere = |p: Vector3<S>, ctx: &str| {
490 let d_sq = p.sub(¢er).norm_sq();
491 let err = d_sq.sub(radius_sq).abs();
492 assert!(
493 !err.definitely_greater(S::from_f64(1e-9)),
494 "{ctx}: p={p:?}, |p - center|^2={d_sq}, expected={radius_sq}"
495 );
496 };
497
498 for edge in model.edges.values() {
499 let (t0, t1) = edge.curve.domain();
500 for i in 0..=8 {
501 let t = t0.add(t1.sub(t0).mul(S::from_ratio(i as i64, 8).unwrap()));
502 let p = edge.curve.evaluate(t).unwrap();
503 assert_on_sphere(p, "edge sample");
504 }
505 }
506
507 for face in model.faces.values() {
508 let (u0, u1) = face.surface.domain_u();
509 let (v0, v1) = face.surface.domain_v();
510 for i in 0..=8 {
511 for j in 0..=8 {
512 let u = u0.add(u1.sub(u0).mul(S::from_ratio(i as i64, 8).unwrap()));
513 let v = v0.add(v1.sub(v0).mul(S::from_ratio(j as i64, 8).unwrap()));
514 let p = face.surface.evaluate(u, v).unwrap();
515 assert_on_sphere(p, "surface sample");
516 }
517 }
518 }
519 }
520 #[test]
521 fn sphere_solid_points_on_sphere() {
522 for_all_scalars!(check_sphere_solid_points_on_sphere);
523 }
524
525 fn check_rasterize_topology_sphere<S: Scalar>() {
526 let mut part = Part::<S>::new();
527 sphere_solid(&mut part, "t6", Vector3::from_array([S::ZERO; 3]), S::ONE).unwrap();
528 let model = part.topology();
529
530 let scene = geop_ops_rasterize::rasterize_topology(&model, 32).unwrap();
531 assert!(!scene.points.is_empty());
532 assert!(!scene.lines.is_empty());
533 assert!(!scene.triangles_transparent.is_empty());
534 assert!(!scene.labels.is_empty());
535
536 std::fs::create_dir_all("outputs").unwrap();
537 scene.save_to_file("outputs/sphere_topology.html").unwrap();
538 }
539 #[test]
540 fn rasterize_topology_sphere() {
541 for_all_scalars!(check_rasterize_topology_sphere);
542 }
543
544 fn check_rasterize_topology_offset_sphere<S: Scalar>() {
545 let mut part = Part::<S>::new();
546 let center = Vector3::from_array([S::from_f64(1.0), S::from_f64(-0.5), S::from_f64(0.5)]);
547 sphere_solid(&mut part, "t7", center, S::from_f64(2.0)).unwrap();
548 let model = part.topology();
549
550 let scene = geop_ops_rasterize::rasterize_topology(&model, 32).unwrap();
551 assert!(!scene.points.is_empty());
552 assert!(!scene.lines.is_empty());
553 assert!(!scene.triangles_transparent.is_empty());
554 assert!(!scene.labels.is_empty());
555
556 std::fs::create_dir_all("outputs").unwrap();
557 scene
558 .save_to_file("outputs/offset_sphere_topology.html")
559 .unwrap();
560 }
561 #[test]
562 fn rasterize_topology_offset_sphere() {
563 for_all_scalars!(check_rasterize_topology_offset_sphere);
564 }
565}