use crate::{coons_bilinear, face_from_surface, face_from_surface_trimmed, make_plane, solid_from_sheets, BrepSolid, KernelRefusal, KernelStage, NurbsCurve, OrRefuse, Vec3};
pub const LOFT_ON_GUIDES_REFUSAL: &str = "loftOnGuides";
fn input(what: &'static str, message: String) -> KernelRefusal {
KernelRefusal::input(KernelStage::Collect, what, format!("{LOFT_ON_GUIDES_REFUSAL}: {message}"))
}
fn ends(curve: &NurbsCurve) -> Result<[Vec3; 2], KernelRefusal> {
let [t0, t1] = curve.domain().or_refuse(KernelStage::Collect, "loftOnGuides.domain")?;
Ok([curve.evaluate(t0).or_refuse(KernelStage::Collect, "loftOnGuides.end")?, curve.evaluate(t1).or_refuse(KernelStage::Collect, "loftOnGuides.end")?])
}
fn 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()
}
fn flat_end(section: &[NurbsCurve], size: f64, which: &str) -> Result<BrepSolid, KernelRefusal> {
let points = poles(section);
let origin = points[0];
let far = |off: Option<Vec3>| {
points.iter().map(|point| point.sub(origin)).map(|arm| off.map_or(arm, |axis| arm.sub(axis.scale(arm.dot(axis))))).fold(Vec3::new(0.0, 0.0, 0.0), |best, arm| if arm.length() > best.length() { arm } else { best })
};
let flat = || input("section_plane", format!("the {which} section does not lie in a plane"));
let e1 = far(None).normalized().map_err(|_| flat())?;
let across = far(Some(e1));
if across.length() <= 1e-9 * size {
return Err(flat());
}
let e2 = across.normalized().map_err(|_| flat())?;
let normal = e1.cross(e2);
if points.iter().any(|point| point.sub(origin).dot(normal).abs() > 1e-9 * size) {
return Err(flat());
}
let reach = |axis: Vec3| points.iter().map(|point| point.sub(origin).dot(axis)).fold((0.0f64, 0.0f64), |(low, high), at| (low.min(at), high.max(at)));
let ((low1, high1), (low2, high2)) = (reach(e1), reach(e2));
let corner = origin.add(e1.scale(low1 - 0.5 * size)).add(e2.scale(low2 - 0.5 * size));
let plane = make_plane(corner, e1, e2, high1 - low1 + size, high2 - low2 + size).or_refuse(KernelStage::Collect, "loftOnGuides.plane")?;
face_from_surface_trimmed(&plane, &[section.to_vec()])
}
fn pointed_wall(side: &NurbsCurve, rail: &NurbsCurve, next: &NurbsCurve, k: usize, tolerance: f64) -> Result<crate::NurbsSurface, KernelRefusal> {
let [from, to] = ends(side)?;
let straight = poles(std::slice::from_ref(side)).iter().all(|point| point.sub(from).cross(to.sub(from)).length() <= tolerance * to.sub(from).length());
if !straight {
return Err(KernelRefusal::unsupported(KernelStage::Fragment, "pointed_wall_side", format!("{LOFT_ON_GUIDES_REFUSAL}: wall {k} closes on a point from a side that is not straight")));
}
if rail.degree != next.degree || rail.knots != next.knots {
return Err(KernelRefusal::unsupported(KernelStage::Fragment, "pointed_wall_guides", format!("{LOFT_ON_GUIDES_REFUSAL}: wall {k} closes on a point between guides of different degree or knots")));
}
let net = vec![rail.control_points.clone(), next.control_points.clone()];
crate::NurbsSurface::new(1, rail.degree, vec![0.0, 0.0, 1.0, 1.0], rail.knots.clone(), net).or_refuse(KernelStage::Fragment, "loftOnGuides.pointed_wall")
}
pub fn loft_profile_brep_on_guides(first: &[NurbsCurve], last: &[NurbsCurve], guides: &[NurbsCurve]) -> Result<BrepSolid, KernelRefusal> {
let count = first.len();
if count < 2 || last.len() != count || guides.len() != count {
return Err(input("counts", format!("the sections and the guides must be equally many, at least 2 (got {count}, {} and {})", last.len(), guides.len())));
}
let all = poles(&[first, last, guides].concat());
let size = all.iter().map(|point| point.sub(all[0]).length()).fold(0.0, f64::max);
let tolerance = 1e-9 * size;
let near = |a: Vec3, b: Vec3| a.sub(b).length() <= tolerance;
let mut rails = Vec::with_capacity(count);
for k in 0..count {
let ([foot, _], [head, _]) = (ends(&first[k])?, ends(&last[k])?);
let [from, to] = ends(&guides[k])?;
let rail = if near(from, foot) && near(to, head) {
guides[k].clone()
} else if near(to, foot) && near(from, head) {
guides[k].reversed().or_refuse(KernelStage::Collect, "loftOnGuides.reverse")?
} else {
return Err(input("guide_ends", format!("guide {k} does not join the start of curve {k} of the first section to that of the last")));
};
rails.push(rail);
}
let mut sheets = Vec::with_capacity(count + 2);
for k in 0..count {
let next = (k + 1) % count;
let far_side = last[k].reversed().or_refuse(KernelStage::Collect, "loftOnGuides.reverse")?;
let back = rails[k].reversed().or_refuse(KernelStage::Collect, "loftOnGuides.reverse")?;
let [head, head_next] = ends(&last[k])?;
let patch = if near(head, head_next) && poles(&last[k..=k]).iter().all(|point| near(*point, head)) {
pointed_wall(&first[k], &rails[k], &rails[next], k, tolerance)?
} else {
coons_bilinear([first[k].clone(), rails[next].clone(), far_side, back])
.map_err(|error| KernelRefusal::unsupported(KernelStage::Fragment, "wall", format!("{LOFT_ON_GUIDES_REFUSAL}: wall {k} has no Coons patch: {error:?}")))?
};
sheets.push(face_from_surface(&patch)?);
}
sheets.push(flat_end(first, size, "first")?);
let apex = ends(&last[0])?[0];
let pointed = poles(last).iter().all(|point| near(*point, apex));
if !pointed {
sheets.push(flat_end(last, size, "last")?);
}
solid_from_sheets(&sheets, (1e-8 * size).max(1e-7))
}