1use axiolid_brep::{ExactBRep, ExactBRepBuilder, SurfaceId};
42use axiolid_contracts::{GeomError, GeomResult, Operation};
43use axiolid_core::{Frame2, Frame3, Interval, Point2, Point3, Scalar, Tolerance, Vec2, Vec3};
44use axiolid_curve::{Circle2, Circle3, Curve2, Curve3, Line2, Line3};
45use axiolid_overlay::{ArcRing, ArcVertex};
46use axiolid_surface::{Cone, Cylinder, Plane, Surface, Torus};
47use axiolid_topology::{
48 Edge, EdgeId, EdgeUse, Face, FaceBound, FaceId, Loop, Orientation, Shell, Solid, Vertex,
49 VertexId,
50};
51
52use crate::contour_lower::arc_ring_signed_area;
53use crate::revolve_contour::frame_at;
54use crate::BACKEND_ID;
55
56const TAU: Scalar = core::f64::consts::TAU;
57const PI: Scalar = core::f64::consts::PI;
58
59fn unsupported(input: &'static str) -> GeomError {
60 GeomError::UnsupportedInput {
61 backend: BACKEND_ID,
62 operation: Operation::Sweep,
63 input,
64 }
65}
66
67#[derive(Debug, Clone, Copy)]
70struct Station {
71 radius: Scalar,
72 height: Scalar,
73 bulge: Scalar,
74}
75
76struct RingEdges {
78 start: Vec<(EdgeId, Curve2, Interval)>,
80 end: Vec<(EdgeId, Curve2, Interval)>,
82}
83
84pub fn revolve_section_partial(
101 outer: &ArcRing,
102 holes: &[ArcRing],
103 axis_origin: Point3,
104 angle: Scalar,
105 tolerance: Tolerance,
106) -> GeomResult<ExactBRep> {
107 if !angle.is_finite() || angle == 0.0 {
108 return Err(GeomError::InvalidInput(format!(
109 "partial revolution angle must be finite and non-zero, got {angle}"
110 )));
111 }
112 let epsilon = tolerance.linear();
113 if angle.abs() >= TAU - epsilon {
114 return Err(GeomError::InvalidInput(format!(
115 "a partial revolution must turn less than a full turn, got {angle}"
116 )));
117 }
118 if !axis_origin.is_finite() {
119 return Err(GeomError::InvalidInput(
120 "revolution axis origin must be finite".to_owned(),
121 ));
122 }
123
124 let offsets = || {
128 core::iter::once(outer)
129 .chain(holes)
130 .flat_map(|ring| ring.vertices.iter())
131 .map(|vertex| vertex.point.x - axis_origin.x)
132 };
133 let min = offsets().fold(Scalar::INFINITY, Scalar::min);
134 let max = offsets().fold(Scalar::NEG_INFINITY, Scalar::max);
135 if !min.is_finite() || !max.is_finite() {
136 return Err(GeomError::InvalidInput(
137 "revolved section must have finite vertices".to_owned(),
138 ));
139 }
140 if min < -epsilon && max > epsilon {
141 return Err(unsupported(
142 "exact revolution of a section crossing the axis",
143 ));
144 }
145 let side = if max > epsilon { 1.0 } else { -1.0 };
146 if max.abs().max(min.abs()) <= epsilon {
147 return Err(GeomError::Degenerate(
148 "revolved section lies on the axis".to_owned(),
149 ));
150 }
151 let base = if side > 0.0 { 0.0 } else { PI };
152 let (u0, u1) = (base + angle.min(0.0), base + angle.max(0.0));
153
154 let outer_stations = stations(outer, axis_origin.x, side, true, epsilon)?;
155 let hole_stations = holes
156 .iter()
157 .map(|hole| stations(hole, axis_origin.x, side, false, epsilon))
158 .collect::<GeomResult<Vec<_>>>()?;
159
160 let reach = outer_stations
161 .iter()
162 .map(|station| station.radius)
163 .fold(0.0, Scalar::max);
164 if reach * angle.abs() <= epsilon {
165 return Err(GeomError::Degenerate(format!(
166 "revolution through {angle} rad sweeps less than the tolerance"
167 )));
168 }
169
170 let sweep = Sweep {
171 axis_x: axis_origin.x,
172 u0,
173 u1,
174 epsilon,
175 };
176 let mut builder = ExactBRepBuilder::default();
177 let mut faces = Vec::new();
178 let mut rings = Vec::with_capacity(1 + holes.len());
179 for ring in core::iter::once(&outer_stations).chain(&hole_stations) {
180 rings.push(sweep.ring(&mut builder, ring, &mut faces)?);
181 }
182
183 let start_plane = builder.add_surface(Surface::Plane(Plane {
185 frame: sweep.cap_frame(u0),
186 }));
187 let end_plane = builder.add_surface(Surface::Plane(Plane {
188 frame: sweep.cap_frame(u1),
189 }));
190 faces.push(cap(
191 &mut builder,
192 start_plane,
193 rings.iter().map(|ring| ring.start.as_slice()),
194 Orientation::Forward,
195 ));
196 faces.push(cap(
197 &mut builder,
198 end_plane,
199 rings.iter().map(|ring| ring.end.as_slice()),
200 Orientation::Reversed,
201 ));
202
203 let shell = builder.topology_mut().add_shell(Shell {
204 faces: faces
205 .into_iter()
206 .map(|face| (face, Orientation::Forward))
207 .collect(),
208 closed: true,
209 });
210 builder.topology_mut().add_solid(Solid {
211 outer: shell,
212 voids: Vec::new(),
213 });
214 let exact = builder
215 .finish()
216 .map_err(|error| GeomError::BackendContractViolation {
217 backend: BACKEND_ID,
218 detail: format!("partial revolution assembly failed: {error}"),
219 })?;
220 let health = axiolid_topology::audit_brep(exact.topology());
221 if !health.is_closed_manifold() {
222 return Err(GeomError::BackendContractViolation {
223 backend: BACKEND_ID,
224 detail: format!("partial revolution is not a closed manifold: {health:?}"),
225 });
226 }
227 Ok(exact)
228}
229
230fn stations(
233 ring: &ArcRing,
234 axis_x: Scalar,
235 side: Scalar,
236 outer: bool,
237 epsilon: Scalar,
238) -> GeomResult<Vec<Station>> {
239 let mut mapped: Vec<ArcVertex> = Vec::with_capacity(ring.vertices.len());
242 for vertex in &ring.vertices {
243 let radius = side * (vertex.point.x - axis_x);
244 let radius = if radius <= epsilon { 0.0 } else { radius };
246 let point = Point2::new(radius, vertex.point.y);
247 let bulge = side * vertex.bulge;
248 if let Some(last) = mapped.last() {
250 if last.bulge == 0.0 && (last.point - point).length() <= epsilon {
251 if let Some(last) = mapped.last_mut() {
252 last.bulge = bulge;
253 }
254 continue;
255 }
256 }
257 mapped.push(ArcVertex { point, bulge });
258 }
259 while mapped.len() > 1 {
260 let (first, last) = (mapped[0], mapped[mapped.len() - 1]);
261 if last.bulge == 0.0 && (last.point - first.point).length() <= epsilon {
262 mapped.pop();
263 } else {
264 break;
265 }
266 }
267 let count = mapped.len();
268 if count < 3 && mapped.iter().all(|vertex| vertex.bulge == 0.0) {
269 return Err(GeomError::Degenerate(format!(
270 "a revolved section ring needs at least three vertices, got {count}"
271 )));
272 }
273 if count < 2 {
274 return Err(GeomError::Degenerate(
275 "a revolved section ring needs at least two vertices".to_owned(),
276 ));
277 }
278 let area = arc_ring_signed_area(&ArcRing {
279 vertices: mapped.clone(),
280 });
281 if area == 0.0 || !area.is_finite() {
282 return Err(GeomError::Degenerate(
283 "revolved section ring encloses no area".to_owned(),
284 ));
285 }
286 let keep = (area > 0.0) == outer;
287 Ok((0..count)
288 .map(|index| {
289 let source = if keep { index } else { count - 1 - index };
290 let bulge = if keep {
291 mapped[source].bulge
292 } else {
293 -mapped[(source + count - 1) % count].bulge
296 };
297 Station {
298 radius: mapped[source].point.x,
299 height: mapped[source].point.y,
300 bulge,
301 }
302 })
303 .collect())
304}
305
306struct Sweep {
308 axis_x: Scalar,
309 u0: Scalar,
310 u1: Scalar,
311 epsilon: Scalar,
312}
313
314impl Sweep {
315 fn radial(u: Scalar) -> Vec3 {
318 let (sin, cos) = u.sin_cos();
319 Vec3::new(cos, 0.0, -sin)
320 }
321
322 fn place(&self, u: Scalar, radius: Scalar, height: Scalar) -> Point3 {
324 Point3::new(self.axis_x, height, 0.0) + Self::radial(u) * radius
325 }
326
327 fn cap_frame(&self, u: Scalar) -> Frame3 {
330 let x = Self::radial(u);
331 Frame3 {
332 origin: Point3::new(self.axis_x, 0.0, 0.0),
333 x,
334 y: Vec3::Y,
335 z: x.cross(Vec3::Y),
336 }
337 }
338
339 fn ring(
341 &self,
342 builder: &mut ExactBRepBuilder,
343 stations: &[Station],
344 faces: &mut Vec<FaceId>,
345 ) -> GeomResult<RingEdges> {
346 let count = stations.len();
347 let (u0, u1) = (self.u0, self.u1);
348
349 let mut starts: Vec<VertexId> = Vec::with_capacity(count);
351 let mut ends: Vec<VertexId> = Vec::with_capacity(count);
352 let mut arcs: Vec<Option<EdgeId>> = Vec::with_capacity(count);
353 for station in stations {
354 let start = builder.topology_mut().add_vertex(Vertex {
355 position: self.place(u0, station.radius, station.height),
356 });
357 if station.radius == 0.0 {
358 starts.push(start);
359 ends.push(start);
360 arcs.push(None);
361 continue;
362 }
363 let end = builder.topology_mut().add_vertex(Vertex {
364 position: self.place(u1, station.radius, station.height),
365 });
366 let curve = builder.add_curve3(Curve3::Circle(Circle3 {
367 frame: frame_at(self.axis_x, station.height),
368 radius: station.radius,
369 }));
370 let arc = builder.topology_mut().add_edge(Edge {
371 start,
372 end,
373 curve: Some(curve),
374 });
375 builder.set_edge_interval(arc, Interval::new(u0, u1));
376 starts.push(start);
377 ends.push(end);
378 arcs.push(Some(arc));
379 }
380
381 let mut edges = RingEdges {
382 start: Vec::with_capacity(count),
383 end: Vec::with_capacity(count),
384 };
385 for index in 0..count {
386 let next = (index + 1) % count;
387 let (here, there) = (stations[index], stations[next]);
388 let segment = Segment::of(here, there)?;
389
390 let start = self.profile_edge(builder, u0, &segment, starts[index], starts[next]);
391 edges.start.push(start.clone());
392 if here.bulge == 0.0 && here.radius == 0.0 && there.radius == 0.0 {
393 edges.end.push(start);
395 continue;
396 }
397 let end = self.profile_edge(builder, u1, &segment, ends[index], ends[next]);
398 edges.end.push(end.clone());
399
400 let wall = self.wall(builder, here, there, &segment)?;
401 let face = self.wall_face(
402 builder,
403 &wall,
404 (arcs[index], arcs[next]),
405 (start.0, end.0),
406 (here, there),
407 );
408 faces.push(face);
409 }
410 Ok(edges)
411 }
412
413 fn profile_edge(
415 &self,
416 builder: &mut ExactBRepBuilder,
417 u: Scalar,
418 segment: &Segment,
419 from: VertexId,
420 to: VertexId,
421 ) -> (EdgeId, Curve2, Interval) {
422 let (curve3, curve2, interval) = match *segment {
423 Segment::Line { from: a, to: b } => {
424 let origin = self.place(u, a.x, a.y);
425 let curve3 = Curve3::Line(Line3 {
426 origin,
427 direction: self.place(u, b.x, b.y) - origin,
428 });
429 let curve2 = Curve2::Line(Line2 {
430 origin: a,
431 direction: b - a,
432 });
433 (curve3, curve2, Interval::UNIT)
434 }
435 Segment::Arc {
436 centre,
437 radius,
438 start,
439 end,
440 } => {
441 let frame = self.cap_frame(u);
442 let curve3 = Curve3::Circle(Circle3 {
443 frame: Frame3 {
444 origin: self.place(u, centre.x, centre.y),
445 ..frame
446 },
447 radius,
448 });
449 let curve2 = Curve2::Circle(Circle2 {
450 frame: Frame2 {
451 origin: centre,
452 x: Vec2::X,
453 y: Vec2::Y,
454 },
455 radius,
456 });
457 (curve3, curve2, Interval::new(start, end))
458 }
459 };
460 let curve = builder.add_curve3(curve3);
461 let edge = builder.topology_mut().add_edge(Edge {
462 start: from,
463 end: to,
464 curve: Some(curve),
465 });
466 builder.set_edge_interval(edge, interval);
467 (edge, curve2, interval)
468 }
469
470 fn wall(
472 &self,
473 builder: &mut ExactBRepBuilder,
474 here: Station,
475 there: Station,
476 segment: &Segment,
477 ) -> GeomResult<Wall> {
478 let epsilon = self.epsilon;
479 let wall = match *segment {
480 Segment::Arc {
481 centre,
482 radius,
483 start,
484 end,
485 } => {
486 if radius >= centre.x.abs() - Scalar::EPSILON {
489 return Err(unsupported(
490 "revolved arc whose tube reaches the axis (spindle torus)",
491 ));
492 }
493 let surface = builder.add_surface(Surface::Torus(Torus {
494 frame: frame_at(self.axis_x, centre.y),
495 major_radius: centre.x,
496 minor_radius: radius,
497 }));
498 Wall::Ruled {
499 surface,
500 from: start,
501 to: end,
502 }
503 }
504 Segment::Line { .. } if (here.radius - there.radius).abs() <= epsilon => {
505 let surface = builder.add_surface(Surface::Cylinder(Cylinder {
506 frame: frame_at(self.axis_x, here.height),
507 radius: here.radius,
508 }));
509 Wall::Ruled {
510 surface,
511 from: 0.0,
512 to: there.height - here.height,
513 }
514 }
515 Segment::Line { .. } if (here.height - there.height).abs() <= epsilon => {
516 let surface = builder.add_surface(Surface::Plane(Plane {
517 frame: frame_at(self.axis_x, here.height),
518 }));
519 Wall::Planar { surface }
520 }
521 Segment::Line { .. } => {
522 let basis = if there.radius == 0.0 { there } else { here };
526 let surface = builder.add_surface(Surface::Cone(Cone {
527 frame: frame_at(self.axis_x, basis.height),
528 radius: basis.radius,
529 semi_angle: ((there.radius - here.radius) / (there.height - here.height))
530 .atan(),
531 }));
532 Wall::Ruled {
533 surface,
534 from: here.height - basis.height,
535 to: there.height - basis.height,
536 }
537 }
538 };
539 Ok(wall)
540 }
541
542 fn wall_face(
544 &self,
545 builder: &mut ExactBRepBuilder,
546 wall: &Wall,
547 (lower, upper): (Option<EdgeId>, Option<EdgeId>),
548 (start_edge, end_edge): (EdgeId, EdgeId),
549 (here, there): (Station, Station),
550 ) -> FaceId {
551 let (u0, u1) = (self.u0, self.u1);
552 let mut uses: Vec<(EdgeId, Orientation, Curve2, Interval)> = Vec::with_capacity(4);
554 let surface = match *wall {
555 Wall::Ruled { surface, from, to } => {
556 let along = |v: Scalar| {
557 Curve2::Line(Line2 {
558 origin: Vec2::new(0.0, v),
559 direction: Vec2::X,
560 })
561 };
562 let across = |u: Scalar| {
563 Curve2::Line(Line2 {
564 origin: Vec2::new(u, from),
565 direction: Vec2::new(0.0, to - from),
566 })
567 };
568 if let Some(edge) = lower {
569 uses.push((
570 edge,
571 Orientation::Forward,
572 along(from),
573 Interval::new(u0, u1),
574 ));
575 }
576 uses.push((end_edge, Orientation::Forward, across(u1), Interval::UNIT));
577 if let Some(edge) = upper {
578 uses.push((
579 edge,
580 Orientation::Reversed,
581 along(to),
582 Interval::new(u1, u0),
583 ));
584 }
585 uses.push((
586 start_edge,
587 Orientation::Reversed,
588 across(u0),
589 Interval::new(1.0, 0.0),
590 ));
591 surface
592 }
593 Wall::Planar { surface } => {
594 let circle = |radius: Scalar| {
595 Curve2::Circle(Circle2 {
596 frame: Frame2 {
597 origin: Vec2::ZERO,
598 x: Vec2::X,
599 y: Vec2::Y,
600 },
601 radius,
602 })
603 };
604 let radial = |u: Scalar| {
607 let (sin, cos) = u.sin_cos();
608 let direction = Vec2::new(cos, sin);
609 Curve2::Line(Line2 {
610 origin: direction * here.radius,
611 direction: direction * (there.radius - here.radius),
612 })
613 };
614 if let Some(edge) = lower {
615 uses.push((
616 edge,
617 Orientation::Forward,
618 circle(here.radius),
619 Interval::new(u0, u1),
620 ));
621 }
622 uses.push((end_edge, Orientation::Forward, radial(u1), Interval::UNIT));
623 if let Some(edge) = upper {
624 uses.push((
625 edge,
626 Orientation::Reversed,
627 circle(there.radius),
628 Interval::new(u1, u0),
629 ));
630 }
631 uses.push((
632 start_edge,
633 Orientation::Reversed,
634 radial(u0),
635 Interval::new(1.0, 0.0),
636 ));
637 surface
638 }
639 };
640 let loop_id = add_loop(builder, uses);
641 builder.topology_mut().add_face(Face {
642 surface: Some(surface),
643 bounds: vec![FaceBound {
644 loop_id,
645 orientation: Orientation::Forward,
646 outer: true,
647 }],
648 orientation: Orientation::Forward,
649 })
650 }
651}
652
653enum Segment {
655 Line {
656 from: Point2,
657 to: Point2,
658 },
659 Arc {
662 centre: Point2,
663 radius: Scalar,
664 start: Scalar,
665 end: Scalar,
666 },
667}
668
669impl Segment {
670 fn of(here: Station, there: Station) -> GeomResult<Self> {
671 let from = Point2::new(here.radius, here.height);
672 let to = Point2::new(there.radius, there.height);
673 if here.bulge == 0.0 {
674 return Ok(Self::Line { from, to });
675 }
676 let arc = crate::extrude_arc::arc_geometry(from, to, here.bulge)?;
677 if arc.centre.x <= 0.0 {
678 return Err(unsupported(
679 "revolved arc centred on the far side of the axis",
680 ));
681 }
682 let start = (from.y - arc.centre.y).atan2(from.x - arc.centre.x);
683 Ok(Self::Arc {
684 centre: arc.centre,
685 radius: arc.radius,
686 start,
687 end: start + arc.sweep,
688 })
689 }
690}
691
692enum Wall {
694 Ruled {
697 surface: SurfaceId,
698 from: Scalar,
699 to: Scalar,
700 },
701 Planar { surface: SurfaceId },
703}
704
705fn add_loop(
706 builder: &mut ExactBRepBuilder,
707 uses: Vec<(EdgeId, Orientation, Curve2, Interval)>,
708) -> axiolid_topology::LoopId {
709 let mut edges = Vec::with_capacity(uses.len());
710 let mut intervals = Vec::with_capacity(uses.len());
711 for (edge, orientation, pcurve, interval) in uses {
712 let pcurve = builder.add_curve2(pcurve);
713 edges.push(EdgeUse {
714 edge,
715 orientation,
716 pcurve: Some(pcurve),
717 });
718 intervals.push(interval);
719 }
720 let loop_id = builder.topology_mut().add_loop(Loop { edges });
721 for (index, interval) in intervals.into_iter().enumerate() {
722 builder.set_pcurve_interval(loop_id, index, interval);
723 }
724 loop_id
725}
726
727fn cap<'a>(
729 builder: &mut ExactBRepBuilder,
730 surface: SurfaceId,
731 rings: impl Iterator<Item = &'a [(EdgeId, Curve2, Interval)]>,
732 orientation: Orientation,
733) -> FaceId {
734 let mut bounds = Vec::new();
735 for (index, ring) in rings.enumerate() {
736 let uses = ring
737 .iter()
738 .map(|(edge, pcurve, interval)| {
739 (*edge, Orientation::Forward, pcurve.clone(), *interval)
740 })
741 .collect();
742 let loop_id = add_loop(builder, uses);
743 bounds.push(FaceBound {
744 loop_id,
745 orientation: Orientation::Forward,
746 outer: index == 0,
747 });
748 }
749 builder.topology_mut().add_face(Face {
750 surface: Some(surface),
751 bounds,
752 orientation,
753 })
754}