use crate::{
boolean_operation, boolean_operation_nary, extrude_profile_brep, face_from_surface_trimmed, make_plane, revolve_profile_brep, solid_from_polygons,
BooleanOperation, BooleanOptions, BrepSolid, KernelRefusal, KernelStage, NurbsCurve, OrRefuse, Vec3, Vec4,
};
pub const SWEEP_ROUND_CORNERS_REFUSAL: &str = "sweepRoundCorners";
fn input(what: &'static str, message: String) -> KernelRefusal {
KernelRefusal::input(KernelStage::Collect, what, format!("{SWEEP_ROUND_CORNERS_REFUSAL}: {message}"))
}
fn moved(curve: &NurbsCurve, map: &dyn Fn(Vec3) -> Vec3) -> Result<NurbsCurve, KernelRefusal> {
let poles = curve.control_points.iter().map(|pole| Vec4::from_point(map(Vec3::new(pole.x / pole.w, pole.y / pole.w, pole.z / pole.w)), pole.w)).collect();
NurbsCurve::new(curve.degree, curve.knots.clone(), poles).or_refuse(KernelStage::Collect, "sweepRoundCorners.move")
}
pub fn sweep_profile_round_corners(section: &[NurbsCurve], path: &[Vec3]) -> Result<BrepSolid, KernelRefusal> {
if path.len() < 2 {
return Err(input("path", "the path needs at least two points".into()));
}
if section.is_empty() {
return Err(input("section", "the section is empty".into()));
}
let stage = KernelStage::Collect;
let poles = |curves: &[NurbsCurve]| -> Vec<Vec3> {
curves.iter().flat_map(|curve| &curve.control_points).map(|pole| Vec3::new(pole.x / pole.w, pole.y / pole.w, pole.z / pole.w)).collect()
};
let reach = poles(section).iter().map(|point| point.sub(path[0]).length()).fold(0.0, f64::max);
if reach <= 0.0 {
return Err(input("section", "the section has no extent".into()));
}
let mut section = section.to_vec();
let mut parts = Vec::new();
for index in 0..path.len() - 1 {
let run = path[index + 1].sub(path[index]);
let along = run.normalized().map_err(|_| input("path", format!("run {index} has no length")))?;
parts.push(extrude_profile_brep(§ion, along, run.length())?);
section = section.iter().map(|curve| moved(curve, &|point| point.add(run))).collect::<Result<_, _>>()?;
let Some(after) = path.get(index + 2) else { break };
let next = after.sub(path[index + 1]).normalized().map_err(|_| input("path", format!("run {} has no length", index + 1)))?;
let turn = along.cross(next);
if turn.length() <= 1e-12 {
if along.dot(next) < 0.0 {
return Err(input("corner", format!("the path turns straight back at point {}", index + 1)));
}
continue;
}
let corner = path[index + 1];
let axis = turn.normalized().or_refuse(stage, "sweepRoundCorners.axis")?;
let angle = turn.length().atan2(along.dot(next));
let across = axis.cross(along);
let span = 4.0 * reach;
let plane = make_plane(corner.sub(axis.scale(span)).sub(across.scale(span)), axis, across, 2.0 * span, 2.0 * span).or_refuse(stage, "sweepRoundCorners.plane")?;
let sheet = face_from_surface_trimmed(&plane, &[section.clone()])?;
for side in [1.0, -1.0] {
let at = |a: f64, b: f64, c: f64| corner.add(axis.scale(a * span)).add(across.scale(side * b * span)).add(along.scale(c * span));
let mut faces = Vec::new();
for s in [0.0, 1.0] {
faces.push(vec![at(-1.0, s, -1.0), at(1.0, s, -1.0), at(1.0, s, 1.0), at(-1.0, s, 1.0)]);
faces.push(vec![at(2.0 * s - 1.0, 0.0, -1.0), at(2.0 * s - 1.0, 1.0, -1.0), at(2.0 * s - 1.0, 1.0, 1.0), at(2.0 * s - 1.0, 0.0, 1.0)]);
faces.push(vec![at(-1.0, 0.0, 2.0 * s - 1.0), at(1.0, 0.0, 2.0 * s - 1.0), at(1.0, 1.0, 2.0 * s - 1.0), at(-1.0, 1.0, 2.0 * s - 1.0)]);
}
let block = solid_from_polygons(&faces)?;
let part = boolean_operation(&sheet, &block, BooleanOperation::Intersect, &BooleanOptions::default())?;
for face in part.shells.iter().flat_map(|shell| &shell.faces) {
if face.loops.len() != 1 {
return Err(KernelRefusal::unsupported(KernelStage::Fragment, "section_holes", format!("{SWEEP_ROUND_CORNERS_REFUSAL}: a section with a hole is not turned about a corner")));
}
let mut outline = Vec::new();
for coedge in &face.loops[0].coedges {
let edge = part.edges.iter().find(|edge| edge.id == coedge.edge_id).ok_or_else(|| input("section", "the cut section refers to a missing edge".into()))?;
let [low, high] = edge.curve.domain().or_refuse(stage, "sweepRoundCorners.domain")?;
let margin = 1e-9 * (high - low);
let mut piece = edge.curve.clone();
if edge.t1 < high - margin {
piece = piece.split(edge.t1).or_refuse(stage, "sweepRoundCorners.trim")?.0;
}
if edge.t0 > low + margin {
piece = piece.split(edge.t0).or_refuse(stage, "sweepRoundCorners.trim")?.1;
}
outline.push(if coedge.forward { piece } else { piece.reversed().or_refuse(stage, "sweepRoundCorners.reverse")? });
}
parts.push(revolve_profile_brep(&outline, corner, axis, angle).or_refuse(KernelStage::Fragment, "sweepRoundCorners.turn")?);
}
}
section = section.iter().map(|curve| moved(curve, &|point| corner.add(point.sub(corner).rotate_about(axis, angle)))).collect::<Result<_, _>>()?;
}
if parts.len() == 1 {
return Ok(parts.remove(0));
}
boolean_operation_nary(&parts, BooleanOperation::Union)
}