use super::*;
use super::recognition::{full_circle_knots, nearly_equal_points, surface_scale};
pub struct RevolutionStructure {
pub frame: RevolutionFrame,
pub generatrix: NurbsCurve,
pub sweep: f64,
}
pub(crate) fn circumcenter(a: Vec3, b: Vec3, c: Vec3) -> Option<Vec3> {
let ab = b.sub(a);
let ac = c.sub(a);
let normal = ab.cross(ac);
let normal_squared = normal.dot(normal);
if normal_squared <= 1e-30 {
return None;
}
let offset = normal
.cross(ab)
.scale(ac.dot(ac))
.add(ac.cross(normal).scale(ab.dot(ab)))
.scale(1.0 / (2.0 * normal_squared));
Some(a.add(offset))
}
pub fn revolution_structure(surface: &NurbsSurface) -> Option<RevolutionStructure> {
if surface.degree_u != 2 {
return None;
}
let rows = &surface.control_points;
if rows.len() < 3 || rows.len() % 2 == 0 {
return None;
}
let spans = (rows.len() - 1) / 2;
if spans > 4 {
return None;
}
let expected = full_circle_knots(spans);
if surface.knots_u.len() != expected.len()
|| surface
.knots_u
.iter()
.zip(&expected)
.any(|(a, b)| (a - b).abs() > 1e-12)
{
return None;
}
let scale = surface_scale(surface);
let mut sweep = None;
for column in 0..rows[0].len() {
let corner = rows[0][column].w;
let middle = rows[1][column].w;
if corner.abs() <= 1e-12 {
continue;
}
let ratio = middle / corner;
if !(ratio > 0.0 && ratio <= 1.0 + 1e-12) {
return None;
}
let arc_angle = 2.0 * ratio.min(1.0).acos();
if arc_angle <= 1e-9 {
return None;
}
sweep = Some(arc_angle * spans as f64);
break;
}
let sweep = sweep?;
if sweep > std::f64::consts::TAU + 1e-9 {
return None;
}
let mut frame = None;
for column in 0..rows[0].len() {
let arc_points: Vec<crate::Vec4> = rows.iter().map(|row| row[column]).collect();
let Ok(arc) = NurbsCurve::new(2, surface.knots_u.clone(), arc_points) else {
return None;
};
let p0 = arc.evaluate(0.0).ok()?;
let p1 = arc.evaluate(0.4).ok()?;
let p2 = arc.evaluate(0.8).ok()?;
let Some(center) = circumcenter(p0, p1, p2) else {
continue;
};
let radial = p0.sub(center);
let radius = radial.length();
if radius <= RECOGNITION_TOLERANCE * scale.max(1.0) {
continue;
}
let axis = radial.cross(p1.sub(center)).normalized().ok()?;
let x_axis = radial.scale(1.0 / radius);
let y_axis = axis.cross(x_axis);
frame = Some(RevolutionFrame {
origin: center,
axis,
x_axis,
y_axis,
});
break;
}
let frame = frame?;
let generatrix =
NurbsCurve::new(surface.degree_v, surface.knots_v.clone(), rows[0].clone()).ok()?;
let rebuilt = make_revolution(frame.origin, frame.axis, &generatrix, sweep).ok()?;
if !nearly_equal_points(surface, &rebuilt, scale) {
return None;
}
Some(RevolutionStructure {
frame,
generatrix,
sweep,
})
}
pub(super) fn rotate_curve_about_axis(
curve: &NurbsCurve,
origin: Vec3,
axis: Vec3,
angle: f64,
) -> Option<NurbsCurve> {
let (sin, cos) = angle.sin_cos();
let controls = curve
.control_points
.iter()
.map(|control| {
let point = control.point().ok()?;
let d = point.sub(origin);
let axial = axis.scale(d.dot(axis));
let radial = d.sub(axial);
let ortho = axis.cross(radial);
let rotated = origin
.add(axial)
.add(radial.scale(cos))
.add(ortho.scale(sin));
Some(crate::Vec4::from_point(rotated, control.w))
})
.collect::<Option<Vec<_>>>()?;
NurbsCurve::new(curve.degree, curve.knots.clone(), controls).ok()
}
pub(super) fn intersect_coaxial_revolutions(
first: &NurbsSurface,
second: &NurbsSurface,
tolerance: f64,
) -> Option<Vec<NurbsCurve>> {
let a = revolution_structure(first)?;
let b = revolution_structure(second)?;
let scale = surface_scale(first).max(surface_scale(second)).max(1.0);
let alignment = a.frame.axis.dot(b.frame.axis);
if alignment.abs() < 1.0 - 1e-9 {
return None;
}
let offset = b.frame.origin.sub(a.frame.origin);
let perpendicular = offset.sub(a.frame.axis.scale(offset.dot(a.frame.axis)));
if perpendicular.length() > 1e-9 * scale {
return None;
}
let b_start = b
.frame
.x_axis
.dot(a.frame.y_axis)
.atan2(b.frame.x_axis.dot(a.frame.x_axis));
let (b_lo, b_hi) = if alignment > 0.0 {
(b_start, b_start + b.sweep)
} else {
(b_start - b.sweep, b_start)
};
let tau = std::f64::consts::TAU;
let mut intervals = Vec::new();
for shift in [-tau, 0.0, tau] {
let lo = (b_lo + shift).max(0.0);
let hi = (b_hi + shift).min(a.sweep);
if hi - lo > 1e-9 {
intervals.push((lo, hi));
}
}
if intervals.is_empty() {
return Some(Vec::new());
}
let middle = (intervals[0].0 + intervals[0].1) / 2.0;
let generatrix = rotate_curve_about_axis(&a.generatrix, a.frame.origin, a.frame.axis, middle)?;
let hits = crate::intersect_curve_surface(&generatrix, second, tolerance).ok()?;
if hits.len() > 8 {
return None;
}
let radius_floor = (tolerance * 10.0).max(1e-9 * scale);
let mut seen: Vec<(f64, f64)> = Vec::new();
let mut curves = Vec::new();
for hit in hits {
if hit.tangential
&& std::env::var("BREP_COAXIAL_TANGENT_SKIP").as_deref() != Ok("0")
{
continue;
}
let (_, radius, axial) = a.frame.cylindrical(hit.point);
if radius <= radius_floor {
continue;
}
if seen
.iter()
.any(|(r, z)| (r - radius).abs() + (z - axial).abs() <= radius_floor)
{
continue;
}
seen.push((radius, axial));
for (lo, hi) in &intervals {
let center = a.frame.origin.add(a.frame.axis.scale(axial));
curves.push(
make_arc(center, a.frame.x_axis, a.frame.y_axis, radius, *lo, *hi).ok()?,
);
}
}
Some(curves)
}