use std::f64::consts::TAU;
use axiolid_brep::{ExactBRep, ExactBRepBuilder};
use axiolid_contracts::{GeomError, GeomResult, Operation};
use axiolid_core::{Frame2, Frame3, Interval, Point3, Scalar, Tolerance, Vec2, Vec3};
use axiolid_curve::{Circle2, Circle3, Curve2, Curve3, Line2, Line3};
use axiolid_profile::{Profile, RectangleProfile};
use axiolid_surface::{Cylinder, Plane, Surface};
use axiolid_topology::{
Edge, EdgeUse, Face, FaceBound, FaceId, Loop, Orientation, Shell, Solid, Vertex,
};
use crate::extrude_exact::extrude_profile_exact;
use crate::BACKEND_ID;
fn unsupported(input: &'static str) -> GeomError {
GeomError::UnsupportedInput {
backend: BACKEND_ID,
operation: Operation::Sweep,
input,
}
}
pub fn revolve_profile_exact(
profile: &Profile,
axis_origin: Point3,
axis_direction: Vec3,
angle: Scalar,
tolerance: Tolerance,
) -> GeomResult<ExactBRep> {
if !angle.is_finite() {
return Err(GeomError::InvalidInput(
"revolution angle must be finite".to_owned(),
));
}
if (angle.abs() - TAU).abs() > tolerance.linear() {
return Err(unsupported("partial-turn exact revolution"));
}
match profile {
Profile::Rectangle(rectangle) => {
revolve_rectangle(rectangle, axis_origin, axis_direction, tolerance)
}
Profile::Circle(_) => Err(unsupported("circle-profile exact revolution")),
Profile::Ellipse(_) => Err(unsupported("ellipse exact revolution")),
Profile::Section(_)
| Profile::Contour(_)
| Profile::Derived { .. }
| Profile::Composite(_)
| Profile::CenterLine(_) => {
revolve_via_contour(profile, axis_origin, axis_direction, tolerance)
}
_ => Err(unsupported("unknown profile exact revolution")),
}
}
fn revolve_rectangle(
rectangle: &RectangleProfile,
axis_origin: Point3,
axis_direction: Vec3,
tolerance: Tolerance,
) -> GeomResult<ExactBRep> {
if rectangle.thickness.is_some() {
return Err(unsupported("hollow rectangle exact revolution"));
}
if rectangle.outer_radius.is_some() || rectangle.inner_radius.is_some() {
return revolve_via_contour(
&Profile::Rectangle(*rectangle),
axis_origin,
axis_direction,
tolerance,
);
}
if !rectangle.x.is_finite()
|| !rectangle.y.is_finite()
|| rectangle.x <= 0.0
|| rectangle.y <= 0.0
{
return Err(GeomError::InvalidInput(format!(
"exact rectangle profile must have positive finite extents, got {} x {}",
rectangle.x, rectangle.y
)));
}
let length = axis_direction.length();
if !length.is_finite() || length <= 0.0 {
return Err(GeomError::InvalidInput(
"revolution axis must be a finite non-zero direction".to_owned(),
));
}
let axis = axis_direction / length;
if (axis.dot(Vec3::Y).abs() - 1.0).abs() > tolerance.linear() {
return Err(unsupported(
"exact revolution about an axis that is not the profile's local y",
));
}
if axis_origin.z.abs() > tolerance.linear() {
return Err(unsupported(
"exact revolution about an axis off the profile plane",
));
}
let (half_x, half_y) = (rectangle.x / 2.0, rectangle.y / 2.0);
let left = -half_x - axis_origin.x;
let right = half_x - axis_origin.x;
if left * right < 0.0 || left.abs() <= tolerance.linear() || right.abs() <= tolerance.linear() {
return Err(unsupported(
"exact revolution of a profile touching or crossing the axis",
));
}
let inner = left.abs().min(right.abs());
let outer = left.abs().max(right.abs());
let bottom = axis_origin.y - half_y;
let top = axis_origin.y + half_y;
let frame_at = |y: Scalar| Frame3 {
origin: Point3::new(axis_origin.x, y, 0.0),
x: Vec3::X,
y: Vec3::Z,
z: Vec3::Y,
};
let frame2 = Frame2 {
origin: Vec2::ZERO,
x: Vec2::X,
y: Vec2::Y,
};
let mut builder = ExactBRepBuilder::default();
builder
.topology_mut()
.try_reserve(4, 6, 6, 4, 1, 1)
.map_err(|_| GeomError::BudgetExceeded {
resource: "exact revolution topology",
})?;
let circle_edge = |builder: &mut ExactBRepBuilder, y: Scalar, radius: Scalar| {
let position = Point3::new(axis_origin.x + radius, y, 0.0);
let vertex = builder.topology_mut().add_vertex(Vertex { position });
let curve = builder.add_curve3(Curve3::Circle(Circle3 {
frame: frame_at(y),
radius,
}));
let edge = builder.topology_mut().add_edge(Edge {
start: vertex,
end: vertex,
curve: Some(curve),
});
builder.set_edge_interval(edge, Interval::new(0.0, TAU));
(edge, vertex, position)
};
let (bottom_outer, bottom_outer_vertex, bottom_outer_point) =
circle_edge(&mut builder, bottom, outer);
let (bottom_inner, bottom_inner_vertex, bottom_inner_point) =
circle_edge(&mut builder, bottom, inner);
let (top_outer, top_outer_vertex, _) = circle_edge(&mut builder, top, outer);
let (top_inner, top_inner_vertex, _) = circle_edge(&mut builder, top, inner);
let seam_edge = |builder: &mut ExactBRepBuilder, start, end, from: Point3| {
let curve = builder.add_curve3(Curve3::Line(Line3 {
origin: from,
direction: Vec3::new(0.0, top - bottom, 0.0),
}));
let edge = builder.topology_mut().add_edge(Edge {
start,
end,
curve: Some(curve),
});
builder.set_edge_interval(edge, Interval::UNIT);
edge
};
let outer_seam = seam_edge(
&mut builder,
bottom_outer_vertex,
top_outer_vertex,
bottom_outer_point,
);
let inner_seam = seam_edge(
&mut builder,
bottom_inner_vertex,
top_inner_vertex,
bottom_inner_point,
);
let circle_loop = |builder: &mut ExactBRepBuilder, edge, radius, orientation| {
let pcurve = builder.add_curve2(Curve2::Circle(Circle2 {
frame: frame2,
radius,
}));
let loop_id = builder.topology_mut().add_loop(Loop {
edges: vec![EdgeUse {
edge,
orientation,
pcurve: Some(pcurve),
}],
});
builder.set_pcurve_interval(loop_id, 0, Interval::new(0.0, TAU));
loop_id
};
let bottom_outer_loop = circle_loop(&mut builder, bottom_outer, outer, Orientation::Forward);
let bottom_inner_loop = circle_loop(&mut builder, bottom_inner, inner, Orientation::Reversed);
let top_outer_loop = circle_loop(&mut builder, top_outer, outer, Orientation::Forward);
let top_inner_loop = circle_loop(&mut builder, top_inner, inner, Orientation::Reversed);
let wall_loop = |builder: &mut ExactBRepBuilder, lower, upper, seam, height: Scalar| {
let lower_pcurve = builder.add_curve2(Curve2::Line(Line2 {
origin: Vec2::ZERO,
direction: Vec2::X,
}));
let upper_pcurve = builder.add_curve2(Curve2::Line(Line2 {
origin: Vec2::new(0.0, height),
direction: Vec2::X,
}));
let up_pcurve = builder.add_curve2(Curve2::Line(Line2 {
origin: Vec2::new(TAU, 0.0),
direction: Vec2::new(0.0, height),
}));
let down_pcurve = builder.add_curve2(Curve2::Line(Line2 {
origin: Vec2::ZERO,
direction: Vec2::new(0.0, height),
}));
let loop_id = builder.topology_mut().add_loop(Loop {
edges: vec![
EdgeUse {
edge: lower,
orientation: Orientation::Forward,
pcurve: Some(lower_pcurve),
},
EdgeUse {
edge: seam,
orientation: Orientation::Forward,
pcurve: Some(up_pcurve),
},
EdgeUse {
edge: upper,
orientation: Orientation::Reversed,
pcurve: Some(upper_pcurve),
},
EdgeUse {
edge: seam,
orientation: Orientation::Reversed,
pcurve: Some(down_pcurve),
},
],
});
builder.set_pcurve_interval(loop_id, 0, Interval::new(0.0, TAU));
builder.set_pcurve_interval(loop_id, 1, Interval::UNIT);
builder.set_pcurve_interval(loop_id, 2, Interval::new(TAU, 0.0));
builder.set_pcurve_interval(loop_id, 3, Interval::new(1.0, 0.0));
loop_id
};
let height = top - bottom;
let outer_loop = wall_loop(&mut builder, bottom_outer, top_outer, outer_seam, height);
let inner_loop = wall_loop(&mut builder, bottom_inner, top_inner, inner_seam, height);
let bottom_surface = builder.add_surface(Surface::Plane(Plane {
frame: frame_at(bottom),
}));
let top_surface = builder.add_surface(Surface::Plane(Plane {
frame: frame_at(top),
}));
let outer_surface = builder.add_surface(Surface::Cylinder(Cylinder {
frame: frame_at(bottom),
radius: outer,
}));
let inner_surface = builder.add_surface(Surface::Cylinder(Cylinder {
frame: frame_at(bottom),
radius: inner,
}));
let annulus = |builder: &mut ExactBRepBuilder, surface, outer_loop, inner_loop, orientation| {
builder.topology_mut().add_face(Face {
surface: Some(surface),
bounds: vec![
FaceBound {
loop_id: outer_loop,
orientation: Orientation::Forward,
outer: true,
},
FaceBound {
loop_id: inner_loop,
orientation: Orientation::Forward,
outer: false,
},
],
orientation,
})
};
let bottom_face = annulus(
&mut builder,
bottom_surface,
bottom_outer_loop,
bottom_inner_loop,
Orientation::Reversed,
);
let top_face = annulus(
&mut builder,
top_surface,
top_outer_loop,
top_inner_loop,
Orientation::Forward,
);
let wall_face = |builder: &mut ExactBRepBuilder, surface, loop_id, orientation| {
builder.topology_mut().add_face(Face {
surface: Some(surface),
bounds: vec![FaceBound {
loop_id,
orientation: Orientation::Forward,
outer: true,
}],
orientation,
})
};
let outer_face = wall_face(
&mut builder,
outer_surface,
outer_loop,
Orientation::Forward,
);
let inner_face = wall_face(
&mut builder,
inner_surface,
inner_loop,
Orientation::Reversed,
);
finish_solid(builder, vec![bottom_face, top_face, outer_face, inner_face])
}
fn finish_solid(mut builder: ExactBRepBuilder, faces: Vec<FaceId>) -> GeomResult<ExactBRep> {
let shell = builder.topology_mut().add_shell(Shell {
faces: faces
.into_iter()
.map(|face| (face, Orientation::Forward))
.collect(),
closed: true,
});
builder.topology_mut().add_solid(Solid {
outer: shell,
voids: Vec::new(),
});
let exact = builder
.finish()
.map_err(|error| GeomError::BackendContractViolation {
backend: BACKEND_ID,
detail: format!("exact revolution assembly failed: {error}"),
})?;
let health = axiolid_topology::audit_brep(exact.topology());
if !health.is_closed_manifold() {
return Err(GeomError::BackendContractViolation {
backend: BACKEND_ID,
detail: format!("exact revolution is not a closed manifold: {health:?}"),
});
}
Ok(exact)
}
pub fn fixed_reference_sweep_exact(
profile: &Profile,
path: &[Point3],
reference_direction: Vec3,
tolerance: Tolerance,
) -> GeomResult<ExactBRep> {
if path.len() < 2 {
return Err(GeomError::InvalidInput(
"a sweep path needs at least two points".to_owned(),
));
}
if !path.iter().all(|p| p.is_finite()) {
return Err(GeomError::InvalidInput(
"sweep path points must be finite".to_owned(),
));
}
if !reference_direction.is_finite() || reference_direction.length() <= 0.0 {
return Err(GeomError::InvalidInput(
"sweep reference direction must be finite and non-zero".to_owned(),
));
}
let start = path[0];
let end = path[path.len() - 1];
let span = end - start;
let length = span.length();
if length <= tolerance.linear() {
return Err(GeomError::Degenerate(
"sweep path start and end coincide, so the path has no direction".to_owned(),
));
}
let direction = span / length;
for point in &path[1..path.len() - 1] {
let offset = *point - start;
let perpendicular = offset - direction * direction.dot(offset);
if perpendicular.length() > tolerance.linear() {
return Err(unsupported("exact sweep along a curved directrix"));
}
}
extrude_profile_exact(profile, direction, length, tolerance)
}
fn revolve_via_contour(
profile: &Profile,
axis_origin: Point3,
axis_direction: Vec3,
tolerance: Tolerance,
) -> GeomResult<ExactBRep> {
let axis = axis_direction.normalize_or_zero();
if (axis.dot(Vec3::Y).abs() - 1.0).abs() > tolerance.linear() {
return Err(unsupported(
"exact revolution about an axis that is not the profile's local y",
));
}
if axis_origin.z.abs() > tolerance.linear() {
return Err(unsupported(
"exact revolution about an axis off the profile plane",
));
}
let contour = crate::extrude_exact::profile_to_contour(profile, tolerance)?;
if !contour.holes.is_empty() {
return Err(unsupported("exact revolution of a section with holes"));
}
let ring = crate::contour_lower::contour_to_arc_ring(&contour.outer, tolerance)?;
crate::revolve_contour::revolve_arc_ring(&ring, axis_origin, tolerance)
}