1use axiolid_brep::{ExactBRep, ExactBRepBuilder};
31use axiolid_contracts::{GeomError, GeomResult, Operation};
32use axiolid_core::{Frame2, Frame3, Interval, Point2, Point3, Scalar, Tolerance, Vec2, Vec3};
33use axiolid_curve::{Circle2, Circle3, Curve2, Curve3, Line2, Line3};
34use axiolid_surface::{Cone, Cylinder, Plane, Surface, Torus};
35use axiolid_topology::{
36 Edge, EdgeId, EdgeUse, Face, FaceBound, FaceId, Loop, LoopId, Orientation, Shell, Solid,
37 Vertex, VertexId,
38};
39
40use axiolid_overlay::ArcRing;
41
42use crate::contour_lower::arc_ring_signed_area;
43use crate::BACKEND_ID;
44
45const TAU: Scalar = core::f64::consts::TAU;
46
47fn unsupported(input: &'static str) -> GeomError {
48 GeomError::UnsupportedInput {
49 backend: BACKEND_ID,
50 operation: Operation::Sweep,
51 input,
52 }
53}
54
55#[derive(Debug, Clone, Copy)]
57struct Station {
58 radius: Scalar,
60 height: Scalar,
62 bulge: Scalar,
64}
65pub fn revolve_arc_ring(
71 ring: &ArcRing,
72 axis_origin: Point3,
73 tolerance: Tolerance,
74) -> GeomResult<ExactBRep> {
75 let epsilon = tolerance.linear();
76 let count = ring.vertices.len();
77 if count < 3 {
78 return Err(GeomError::Degenerate(format!(
79 "a revolved section needs at least three vertices, got {count}"
80 )));
81 }
82
83 let area = arc_ring_signed_area(ring);
86 if area == 0.0 {
87 return Err(GeomError::Degenerate(
88 "revolved section encloses no area".to_owned(),
89 ));
90 }
91 let forward = area > 0.0;
92
93 let stations: Vec<Station> = (0..count)
94 .map(|index| {
95 let source = if forward { index } else { count - 1 - index };
96 let vertex = ring.vertices[source];
97 let bulge = if forward {
98 vertex.bulge
99 } else {
100 -ring.vertices[(source + count - 1) % count].bulge
103 };
104 Station {
105 radius: vertex.point.x - axis_origin.x,
106 height: vertex.point.y,
107 bulge,
108 }
109 })
110 .collect();
111
112 let min = stations
113 .iter()
114 .map(|station| station.radius)
115 .fold(Scalar::INFINITY, Scalar::min);
116 let max = stations
117 .iter()
118 .map(|station| station.radius)
119 .fold(Scalar::NEG_INFINITY, Scalar::max);
120 if min * max < 0.0 || min.abs() <= epsilon || max.abs() <= epsilon {
121 return Err(unsupported(
122 "exact revolution of a section touching or crossing the axis",
123 ));
124 }
125 let flip = max < 0.0;
128 let stations: Vec<Station> = stations
129 .iter()
130 .map(|station| Station {
131 radius: if flip {
132 -station.radius
133 } else {
134 station.radius
135 },
136 height: station.height,
137 bulge: if flip { -station.bulge } else { station.bulge },
138 })
139 .collect();
140
141 build(&stations, axis_origin, epsilon)
142}
143fn build(stations: &[Station], axis_origin: Point3, epsilon: Scalar) -> GeomResult<ExactBRep> {
146 let count = stations.len();
147 let mut builder = ExactBRepBuilder::default();
148 builder
149 .topology_mut()
150 .try_reserve(count * 2, count * 2, count * 2, count, 1, 1)
151 .map_err(|_| GeomError::BudgetExceeded {
152 resource: "exact revolution topology",
153 })?;
154
155 let axis_frame = |height: Scalar| frame_at(axis_origin.x, height);
156
157 let mut circles = Vec::with_capacity(count);
159 let mut vertices = Vec::with_capacity(count);
160 let mut points = Vec::with_capacity(count);
161 for station in stations {
162 let position = Point3::new(axis_origin.x + station.radius, station.height, 0.0);
163 let vertex = builder.topology_mut().add_vertex(Vertex { position });
164 let curve = builder.add_curve3(Curve3::Circle(Circle3 {
165 frame: axis_frame(station.height),
166 radius: station.radius,
167 }));
168 let edge = builder.topology_mut().add_edge(Edge {
169 start: vertex,
170 end: vertex,
171 curve: Some(curve),
172 });
173 builder.set_edge_interval(edge, Interval::new(0.0, TAU));
174 circles.push(edge);
175 vertices.push(vertex);
176 points.push(position);
177 }
178
179 let mut faces = Vec::with_capacity(count);
180 for index in 0..count {
181 let next = (index + 1) % count;
182 let here = stations[index];
183 let there = stations[next];
184 let face = if here.bulge != 0.0 {
185 torus_wall(
186 &mut builder,
187 axis_origin.x,
188 &circles,
189 &vertices,
190 stations,
191 index,
192 )?
193 } else if (here.radius - there.radius).abs() <= epsilon {
194 if (here.height - there.height).abs() <= epsilon {
197 continue;
198 }
199 straight_wall(
200 &mut builder,
201 axis_origin.x,
202 &circles,
203 &vertices,
204 &points,
205 stations,
206 index,
207 false,
208 )?
209 } else if (here.height - there.height).abs() <= epsilon {
210 annulus_wall(&mut builder, axis_origin.x, &circles, stations, index)?
212 } else {
213 straight_wall(
215 &mut builder,
216 axis_origin.x,
217 &circles,
218 &vertices,
219 &points,
220 stations,
221 index,
222 true,
223 )?
224 };
225 faces.push(face);
226 }
227
228 if faces.len() < 3 {
229 return Err(GeomError::Degenerate(format!(
230 "a revolved solid needs at least three faces, got {}",
231 faces.len()
232 )));
233 }
234 finish(builder, faces)
235}
236#[allow(clippy::too_many_arguments)]
242fn straight_wall(
243 builder: &mut ExactBRepBuilder,
244 axis_x: Scalar,
245 circles: &[EdgeId],
246 vertices: &[VertexId],
247 points: &[Point3],
248 stations: &[Station],
249 index: usize,
250 cone: bool,
251) -> GeomResult<FaceId> {
252 let count = stations.len();
253 let next = (index + 1) % count;
254 let here = stations[index];
255 let there = stations[next];
256 let rise = there.height - here.height;
257
258 let seam_curve = builder.add_curve3(Curve3::Line(Line3 {
259 origin: points[index],
260 direction: points[next] - points[index],
261 }));
262 let seam = builder.topology_mut().add_edge(Edge {
263 start: vertices[index],
264 end: vertices[next],
265 curve: Some(seam_curve),
266 });
267 builder.set_edge_interval(seam, Interval::UNIT);
268
269 let surface = if cone {
270 let semi_angle = ((there.radius - here.radius) / rise).atan();
273 builder.add_surface(Surface::Cone(Cone {
274 frame: frame_at(axis_x, here.height),
275 radius: here.radius,
276 semi_angle,
277 }))
278 } else {
279 builder.add_surface(Surface::Cylinder(Cylinder {
280 frame: frame_at(axis_x, here.height),
281 radius: here.radius,
282 }))
283 };
284
285 let loop_id = seam_loop(builder, circles[index], circles[next], seam, rise);
286 Ok(add_face(builder, surface, loop_id, Orientation::Forward))
287}
288
289fn annulus_wall(
291 builder: &mut ExactBRepBuilder,
292 axis_x: Scalar,
293 circles: &[EdgeId],
294 stations: &[Station],
295 index: usize,
296) -> GeomResult<FaceId> {
297 let count = stations.len();
298 let next = (index + 1) % count;
299 let here = stations[index];
300 let there = stations[next];
301 let surface = builder.add_surface(Surface::Plane(Plane {
302 frame: frame_at(axis_x, here.height),
303 }));
304
305 let (outer, inner) = if here.radius > there.radius {
308 (index, next)
309 } else {
310 (next, index)
311 };
312 let outer_loop = circle_loop(
313 builder,
314 circles[outer],
315 stations[outer].radius,
316 Orientation::Forward,
317 );
318 let inner_loop = circle_loop(
319 builder,
320 circles[inner],
321 stations[inner].radius,
322 Orientation::Reversed,
323 );
324 let orientation = if here.radius > there.radius {
325 Orientation::Forward
326 } else {
327 Orientation::Reversed
328 };
329 Ok(builder.topology_mut().add_face(Face {
330 surface: Some(surface),
331 bounds: vec![
332 FaceBound {
333 loop_id: outer_loop,
334 orientation: Orientation::Forward,
335 outer: true,
336 },
337 FaceBound {
338 loop_id: inner_loop,
339 orientation: Orientation::Forward,
340 outer: false,
341 },
342 ],
343 orientation,
344 }))
345}
346fn torus_wall(
352 builder: &mut ExactBRepBuilder,
353 axis_x: Scalar,
354 circles: &[EdgeId],
355 vertices: &[VertexId],
356 stations: &[Station],
357 index: usize,
358) -> GeomResult<FaceId> {
359 let count = stations.len();
360 let next = (index + 1) % count;
361 let here = stations[index];
362 let there = stations[next];
363
364 let arc = crate::extrude_arc::arc_geometry(
365 Point2::new(here.radius, here.height),
366 Point2::new(there.radius, there.height),
367 here.bulge,
368 )?;
369 let (centre_radius, centre_height) = (arc.centre.x, arc.centre.y);
370
371 if arc.radius >= centre_radius.abs() - Scalar::EPSILON {
375 return Err(unsupported(
376 "revolved arc whose tube reaches the axis (spindle torus)",
377 ));
378 }
379 if centre_radius <= 0.0 {
380 return Err(unsupported(
381 "revolved arc centred on the far side of the axis",
382 ));
383 }
384
385 let surface = builder.add_surface(Surface::Torus(Torus {
386 frame: frame_at(axis_x, centre_height),
387 major_radius: centre_radius,
388 minor_radius: arc.radius,
389 }));
390
391 let angle_of =
393 |station: Station| (station.height - centre_height).atan2(station.radius - centre_radius);
394 let start = angle_of(here);
395 let end = start + arc.sweep;
398
399 let seam_frame = Frame3 {
405 origin: Point3::new(axis_x + centre_radius, centre_height, 0.0),
406 x: Vec3::X,
407 y: Vec3::Y,
408 z: Vec3::Z,
409 };
410 let seam_curve = builder.add_curve3(Curve3::Circle(Circle3 {
411 frame: seam_frame,
412 radius: arc.radius,
413 }));
414 let seam = builder.topology_mut().add_edge(Edge {
415 start: vertices[index],
416 end: vertices[next],
417 curve: Some(seam_curve),
418 });
419 builder.set_edge_interval(seam, Interval::new(start, end));
420
421 let loop_id = seam_loop_between(builder, circles[index], circles[next], seam, start, end);
422 Ok(add_face(builder, surface, loop_id, Orientation::Forward))
423}
424pub(crate) fn frame_at(axis_x: Scalar, height: Scalar) -> Frame3 {
441 Frame3 {
442 origin: Point3::new(axis_x, height, 0.0),
443 x: Vec3::X,
444 y: Vec3::NEG_Z,
445 z: Vec3::Y,
446 }
447}
448
449fn circle_loop(
451 builder: &mut ExactBRepBuilder,
452 edge: EdgeId,
453 radius: Scalar,
454 orientation: Orientation,
455) -> LoopId {
456 let pcurve = builder.add_curve2(Curve2::Circle(Circle2 {
457 frame: Frame2 {
458 origin: Vec2::ZERO,
459 x: Vec2::X,
460 y: Vec2::Y,
461 },
462 radius,
463 }));
464 let loop_id = builder.topology_mut().add_loop(Loop {
465 edges: vec![EdgeUse {
466 edge,
467 orientation,
468 pcurve: Some(pcurve),
469 }],
470 });
471 let interval = match orientation {
476 Orientation::Forward => Interval::new(0.0, TAU),
477 Orientation::Reversed => Interval::new(TAU, 0.0),
478 };
479 builder.set_pcurve_interval(loop_id, 0, interval);
480 loop_id
481}
482
483fn seam_loop(
485 builder: &mut ExactBRepBuilder,
486 lower: EdgeId,
487 upper: EdgeId,
488 seam: EdgeId,
489 rise: Scalar,
490) -> LoopId {
491 seam_loop_between(builder, lower, upper, seam, 0.0, rise)
492}
493
494fn seam_loop_between(
500 builder: &mut ExactBRepBuilder,
501 lower: EdgeId,
502 upper: EdgeId,
503 seam: EdgeId,
504 from: Scalar,
505 to: Scalar,
506) -> LoopId {
507 let lower_pcurve = builder.add_curve2(Curve2::Line(Line2 {
508 origin: Vec2::new(0.0, from),
509 direction: Vec2::X,
510 }));
511 let upper_pcurve = builder.add_curve2(Curve2::Line(Line2 {
512 origin: Vec2::new(0.0, to),
513 direction: Vec2::X,
514 }));
515 let up_pcurve = builder.add_curve2(Curve2::Line(Line2 {
516 origin: Vec2::new(TAU, from),
517 direction: Vec2::new(0.0, to - from),
518 }));
519 let down_pcurve = builder.add_curve2(Curve2::Line(Line2 {
520 origin: Vec2::new(0.0, from),
521 direction: Vec2::new(0.0, to - from),
522 }));
523 let loop_id = builder.topology_mut().add_loop(Loop {
524 edges: vec![
525 EdgeUse {
526 edge: lower,
527 orientation: Orientation::Forward,
528 pcurve: Some(lower_pcurve),
529 },
530 EdgeUse {
531 edge: seam,
532 orientation: Orientation::Forward,
533 pcurve: Some(up_pcurve),
534 },
535 EdgeUse {
536 edge: upper,
537 orientation: Orientation::Reversed,
538 pcurve: Some(upper_pcurve),
539 },
540 EdgeUse {
541 edge: seam,
542 orientation: Orientation::Reversed,
543 pcurve: Some(down_pcurve),
544 },
545 ],
546 });
547 builder.set_pcurve_interval(loop_id, 0, Interval::new(0.0, TAU));
548 builder.set_pcurve_interval(loop_id, 1, Interval::UNIT);
549 builder.set_pcurve_interval(loop_id, 2, Interval::new(TAU, 0.0));
550 builder.set_pcurve_interval(loop_id, 3, Interval::new(1.0, 0.0));
551 loop_id
552}
553
554fn add_face(
555 builder: &mut ExactBRepBuilder,
556 surface: axiolid_brep::SurfaceId,
557 loop_id: LoopId,
558 orientation: Orientation,
559) -> FaceId {
560 builder.topology_mut().add_face(Face {
561 surface: Some(surface),
562 bounds: vec![FaceBound {
563 loop_id,
564 orientation: Orientation::Forward,
565 outer: true,
566 }],
567 orientation,
568 })
569}
570
571fn finish(mut builder: ExactBRepBuilder, faces: Vec<FaceId>) -> GeomResult<ExactBRep> {
572 let shell = builder.topology_mut().add_shell(Shell {
573 faces: faces
574 .into_iter()
575 .map(|f| (f, Orientation::Forward))
576 .collect(),
577 closed: true,
578 });
579 builder.topology_mut().add_solid(Solid {
580 outer: shell,
581 voids: Vec::new(),
582 });
583 builder.finish().map_err(|error| {
584 GeomError::InvalidInput(format!("revolved solid failed validation: {error:?}"))
585 })
586}