use super::*;
fn sphere_body_on() -> bool {
switch!("BREP_OFFSET_SPHERE_BODY", true)
}
pub(super) fn sphere_body(source: &BrepSolid, distance: f64) -> Option<OffsetShellResultRecord> {
if !sphere_body_on() {
return None;
}
match build(source, distance) {
Ok(result) => {
os_debug!("sphere body lane built (thread {})", std::thread::current().name().unwrap_or("main"));
Some(result)
}
Err(reason) => {
os_debug!("sphere body lane declined: {reason}");
None
}
}
}
fn build(source: &BrepSolid, distance: f64) -> Result<OffsetShellResultRecord, String> {
if !(distance.is_finite() && distance != 0.0) {
return Err("not a finite non-zero distance".into());
}
if source.kind != crate::BodyKind::Solid || source.shells.len() != 1 || source.shells[0].faces.is_empty() {
return Err("not one shell of a solid".into());
}
let faces = &source.shells[0].faces;
let Some((centre, radius)) = faces[0].surface.analytic().and_then(|shape| shape.sphere_geometry()) else {
return Err(format!("face {} is not a recognized sphere", faces[0].id));
};
let bar = 1e-12 * (centre.length() + radius).max(1.0);
for face in &faces[1..] {
match face.surface.analytic().and_then(|shape| shape.sphere_geometry()) {
Some((other, other_radius)) if other.sub(centre).length() <= bar && (other_radius - radius).abs() <= bar => {}
_ => return Err(format!("face {} is not on the first face's sphere", face.id)),
}
}
let mut factor = None::<f64>;
for face in faces {
let [u0, u1] = face.surface.domain_u()?;
let [v0, v1] = face.surface.domain_v()?;
let sample = crate::OffsetEvaluator::new("offset_shell", &face.surface, crate::OffsetNormal::FaceStable { same_sense: face.same_sense })
.at((u0 + u1) * 0.5, (v0 + v1) * 0.5, -distance)
.map_err(|error| format!("face {}: {error}", face.id))?;
let (from, to) = (sample.source.sub(centre), sample.point.sub(centre));
let new_radius = to.length();
if (from.length() - radius).abs() > 1e2 * bar
|| to.dot(from) <= 0.0
|| ((new_radius - radius).abs() - distance.abs()).abs() > 1e2 * bar
|| !(new_radius > 1e-6 * radius)
{
return Err(format!("face {}: the offset is not the concentric sphere (radius {new_radius})", face.id));
}
let k = new_radius / radius;
if factor.is_some_and(|previous| (previous - k).abs() > 1e-12) {
return Err("the faces move to different spheres".into());
}
factor = Some(k);
}
let k = factor.ok_or("no face")?;
let scale = |point: Vec3| centre.add(point.sub(centre).scale(k));
let scale_controls = |controls: &mut [crate::Vec4]| -> Result<(), String> {
for control in controls {
*control = crate::Vec4::from_point(scale(control.point()?), control.w);
}
Ok(())
};
let mut solid = source.clone();
for vertex in &mut solid.vertices {
vertex.point = scale(vertex.point);
}
for edge in &mut solid.edges {
let mut controls = edge.curve.control_points.clone();
scale_controls(&mut controls)?;
edge.curve = crate::NurbsCurve::new(edge.curve.degree, edge.curve.knots.clone(), controls)?;
}
let mut face_images = Vec::new();
for face in solid.shells.iter_mut().flat_map(|shell| &mut shell.faces) {
let mut rows = face.surface.control_points.clone();
for row in &mut rows {
scale_controls(row)?;
}
let surface = &face.surface;
face.surface = crate::NurbsSurface::new(surface.degree_u, surface.degree_v, surface.knots_u.clone(), surface.knots_v.clone(), rows)?;
face_images.push(OffsetShellFaceImageRecord { role: OffsetFaceRole::Offset, source_face_id: face.id });
}
let issues = solid.validate();
if !issues.is_empty() {
return Err(format!("the scaled body does not validate: {issues:?}"));
}
Ok(OffsetShellResultRecord { solid, face_images })
}