#![allow(dead_code)]
use brepkit_math::curves2d::{Curve2D, Line2D};
use brepkit_math::nurbs::curve::NurbsCurve;
use brepkit_math::surfaces::CylindricalSurface;
use brepkit_math::traits::ParametricSurface;
use brepkit_math::vec::{Point3, Vec3};
use brepkit_topology::Topology;
use brepkit_topology::face::{FaceId, FaceSurface};
use crate::BlendError;
use crate::section::CircSection;
use crate::spine::Spine;
use crate::stripe::{Stripe, StripeResult};
const ANALYTIC_TOL_LIN: f64 = 1e-9;
const ANALYTIC_TOL_ANG: f64 = 1e-9;
pub struct AnalyticResult {
pub surface: FaceSurface,
pub contact1: NurbsCurve,
pub contact2: NurbsCurve,
pub pcurve1: Curve2D,
pub pcurve2: Curve2D,
pub sections: Vec<CircSection>,
}
#[allow(clippy::too_many_arguments)]
pub fn try_analytic_fillet(
surf1: &FaceSurface,
surf2: &FaceSurface,
spine: &Spine,
topo: &Topology,
radius: f64,
face1: FaceId,
face2: FaceId,
) -> Result<Option<StripeResult>, BlendError> {
match (surf1, surf2) {
(FaceSurface::Plane { normal: n1, d: _d1 }, FaceSurface::Plane { normal: n2, d: _d2 }) => {
let result = plane_plane_fillet(spine, topo, *n1, *n2, radius, face1, face2)?;
Ok(Some(result))
}
(FaceSurface::Plane { normal, d }, FaceSurface::Cylinder(cyl)) => {
plane_cylinder_fillet(*normal, *d, cyl, spine, topo, radius, face1, face2)
}
(FaceSurface::Cylinder(cyl), FaceSurface::Plane { normal, d }) => {
let mut result =
plane_cylinder_fillet(*normal, *d, cyl, spine, topo, radius, face2, face1)?;
if let Some(ref mut r) = result {
swap_stripe_sides(r);
}
Ok(result)
}
(FaceSurface::Plane { normal, d }, FaceSurface::Cone(cone)) => {
plane_cone_fillet(*normal, *d, cone, spine, topo, radius, face1, face2)
}
(FaceSurface::Cone(cone), FaceSurface::Plane { normal, d }) => {
let mut result =
plane_cone_fillet(*normal, *d, cone, spine, topo, radius, face2, face1)?;
if let Some(ref mut r) = result {
swap_stripe_sides(r);
}
Ok(result)
}
(FaceSurface::Plane { normal, d }, FaceSurface::Sphere(sph)) => {
plane_sphere_fillet(*normal, *d, sph, spine, topo, radius, face1, face2)
}
(FaceSurface::Sphere(sph), FaceSurface::Plane { normal, d }) => {
let mut result =
plane_sphere_fillet(*normal, *d, sph, spine, topo, radius, face2, face1)?;
if let Some(ref mut r) = result {
swap_stripe_sides(r);
}
Ok(result)
}
(FaceSurface::Sphere(s1), FaceSurface::Sphere(s2)) => {
sphere_sphere_fillet(s1, s2, spine, topo, radius, face1, face2)
}
(FaceSurface::Cylinder(cyl), FaceSurface::Sphere(sph)) => {
let mut result = sphere_cylinder_fillet(sph, cyl, spine, topo, radius, face2, face1)?;
if let Some(ref mut r) = result {
swap_stripe_sides(r);
}
Ok(result)
}
(FaceSurface::Sphere(sph), FaceSurface::Cylinder(cyl)) => {
sphere_cylinder_fillet(sph, cyl, spine, topo, radius, face1, face2)
}
(FaceSurface::Sphere(sph), FaceSurface::Cone(cone)) => {
sphere_cone_fillet(sph, cone, spine, topo, radius, face1, face2)
}
(FaceSurface::Cone(cone), FaceSurface::Sphere(sph)) => {
let mut result = sphere_cone_fillet(sph, cone, spine, topo, radius, face2, face1)?;
if let Some(ref mut r) = result {
swap_stripe_sides(r);
}
Ok(result)
}
(FaceSurface::Cylinder(c1), FaceSurface::Cylinder(c2)) => {
cylinder_cylinder_fillet(c1, c2, spine, topo, radius, face1, face2)
}
(FaceSurface::Cone(co1), FaceSurface::Cone(co2)) => {
cone_cone_coaxial_fillet(co1, co2, spine, topo, radius, face1, face2)
}
(
FaceSurface::Plane { .. }
| FaceSurface::Cylinder(_)
| FaceSurface::Cone(_)
| FaceSurface::Sphere(_)
| FaceSurface::Torus(_)
| FaceSurface::Nurbs(_),
FaceSurface::Torus(_) | FaceSurface::Nurbs(_),
)
| (
FaceSurface::Cylinder(_) | FaceSurface::Cone(_),
FaceSurface::Cylinder(_) | FaceSurface::Cone(_),
)
| (
FaceSurface::Torus(_) | FaceSurface::Nurbs(_),
FaceSurface::Plane { .. }
| FaceSurface::Cylinder(_)
| FaceSurface::Cone(_)
| FaceSurface::Sphere(_),
) => Ok(None),
}
}
fn swap_stripe_sides(r: &mut StripeResult) {
std::mem::swap(&mut r.stripe.face1, &mut r.stripe.face2);
std::mem::swap(&mut r.stripe.pcurve1, &mut r.stripe.pcurve2);
std::mem::swap(&mut r.stripe.contact1, &mut r.stripe.contact2);
for s in &mut r.stripe.sections {
std::mem::swap(&mut s.p1, &mut s.p2);
std::mem::swap(&mut s.uv1, &mut s.uv2);
}
}
#[allow(clippy::too_many_arguments)]
pub fn try_analytic_chamfer(
surf1: &FaceSurface,
surf2: &FaceSurface,
spine: &Spine,
topo: &Topology,
d1: f64,
d2: f64,
face1: FaceId,
face2: FaceId,
) -> Result<Option<StripeResult>, BlendError> {
match (surf1, surf2) {
(
FaceSurface::Plane {
normal: n1,
d: _dd1,
},
FaceSurface::Plane {
normal: n2,
d: _dd2,
},
) => {
let result = plane_plane_chamfer(spine, topo, *n1, *n2, d1, d2, face1, face2)?;
Ok(Some(result))
}
(FaceSurface::Plane { normal, d }, FaceSurface::Cylinder(cyl)) => {
plane_cylinder_chamfer(*normal, *d, cyl, spine, topo, d1, d2, face1, face2)
}
(FaceSurface::Cylinder(cyl), FaceSurface::Plane { normal, d }) => {
let mut result =
plane_cylinder_chamfer(*normal, *d, cyl, spine, topo, d2, d1, face2, face1)?;
if let Some(ref mut r) = result {
swap_stripe_sides(r);
}
Ok(result)
}
(FaceSurface::Plane { normal, d }, FaceSurface::Cone(cone)) => {
plane_cone_chamfer(*normal, *d, cone, spine, topo, d1, d2, face1, face2)
}
(FaceSurface::Cone(cone), FaceSurface::Plane { normal, d }) => {
let mut result =
plane_cone_chamfer(*normal, *d, cone, spine, topo, d2, d1, face2, face1)?;
if let Some(ref mut r) = result {
swap_stripe_sides(r);
}
Ok(result)
}
(FaceSurface::Plane { normal, d }, FaceSurface::Sphere(sph)) => {
plane_sphere_chamfer(*normal, *d, sph, spine, topo, d1, d2, face1, face2)
}
(FaceSurface::Sphere(sph), FaceSurface::Plane { normal, d }) => {
let mut result =
plane_sphere_chamfer(*normal, *d, sph, spine, topo, d2, d1, face2, face1)?;
if let Some(ref mut r) = result {
swap_stripe_sides(r);
}
Ok(result)
}
(FaceSurface::Sphere(s1), FaceSurface::Sphere(s2)) => {
sphere_sphere_chamfer(s1, s2, spine, topo, d1, d2, face1, face2)
}
(FaceSurface::Sphere(sph), FaceSurface::Cylinder(cyl)) => {
sphere_cylinder_chamfer(sph, cyl, spine, topo, d1, d2, face1, face2)
}
(FaceSurface::Cylinder(cyl), FaceSurface::Sphere(sph)) => {
let mut result = sphere_cylinder_chamfer(sph, cyl, spine, topo, d2, d1, face2, face1)?;
if let Some(ref mut r) = result {
swap_stripe_sides(r);
}
Ok(result)
}
(FaceSurface::Sphere(sph), FaceSurface::Cone(cone)) => {
sphere_cone_chamfer(sph, cone, spine, topo, d1, d2, face1, face2)
}
(FaceSurface::Cone(cone), FaceSurface::Sphere(sph)) => {
let mut result = sphere_cone_chamfer(sph, cone, spine, topo, d2, d1, face2, face1)?;
if let Some(ref mut r) = result {
swap_stripe_sides(r);
}
Ok(result)
}
(FaceSurface::Cylinder(c1), FaceSurface::Cylinder(c2)) => {
cylinder_cylinder_chamfer(c1, c2, spine, topo, d1, d2, face1, face2)
}
(FaceSurface::Cone(co1), FaceSurface::Cone(co2)) => {
cone_cone_coaxial_chamfer(co1, co2, spine, topo, d1, d2, face1, face2)
}
(
FaceSurface::Plane { .. }
| FaceSurface::Cylinder(_)
| FaceSurface::Cone(_)
| FaceSurface::Sphere(_)
| FaceSurface::Torus(_)
| FaceSurface::Nurbs(_),
FaceSurface::Torus(_) | FaceSurface::Nurbs(_),
)
| (
FaceSurface::Cylinder(_) | FaceSurface::Cone(_),
FaceSurface::Cylinder(_) | FaceSurface::Cone(_),
)
| (
FaceSurface::Torus(_) | FaceSurface::Nurbs(_),
FaceSurface::Plane { .. }
| FaceSurface::Cylinder(_)
| FaceSurface::Cone(_)
| FaceSurface::Sphere(_),
) => Ok(None),
}
}
fn nurbs_line(p0: Point3, p1: Point3) -> Result<NurbsCurve, BlendError> {
let curve = NurbsCurve::new(1, vec![0.0, 0.0, 1.0, 1.0], vec![p0, p1], vec![1.0, 1.0])?;
Ok(curve)
}
fn dihedral_half_angle(n1: Vec3, n2: Vec3) -> f64 {
let cos_angle = n1.dot(n2).clamp(-1.0, 1.0);
(std::f64::consts::PI - cos_angle.acos()) / 2.0
}
fn section_basis(n1: Vec3, n2: Vec3, spine_tangent: Vec3) -> (Vec3, Vec3) {
let bisector_raw = n1 + n2;
let bisector = bisector_raw.normalize().unwrap_or_else(|_| {
spine_tangent.cross(n1)
});
let cross_dir_raw = spine_tangent.cross(bisector);
let cross_dir = cross_dir_raw
.normalize()
.unwrap_or(Vec3::new(0.0, 0.0, 1.0));
(bisector, cross_dir)
}
fn material_contact_direction(
topo: &Topology,
face: FaceId,
spine: &Spine,
normal: Vec3,
tangent: Vec3,
) -> Option<Vec3> {
let spine_edge = *spine.edges().first()?;
let f = topo.face(face).ok()?;
let n_eff = if f.is_reversed() { -normal } else { normal };
for wid in std::iter::once(f.outer_wire()).chain(f.inner_wires().iter().copied()) {
let w = topo.wire(wid).ok()?;
for oe in w.edges() {
if oe.edge() == spine_edge {
let t = if oe.is_forward() { tangent } else { -tangent };
return n_eff.cross(t).normalize().ok();
}
}
}
None
}
fn compute_contact_direction(normal: Vec3, bisector: Vec3) -> Vec3 {
let proj = bisector - normal * bisector.dot(normal);
proj.normalize().unwrap_or(bisector)
}
fn midpoint_3d(a: Point3, b: Point3) -> Point3 {
Point3::new(
f64::midpoint(a.x(), b.x()),
f64::midpoint(a.y(), b.y()),
f64::midpoint(a.z(), b.z()),
)
}
#[allow(clippy::too_many_lines, clippy::too_many_arguments)]
fn material_side_witness(
topo: &Topology,
face: FaceId,
n_other: Vec3,
p: Point3,
) -> Result<f64, BlendError> {
let f = topo.face(face)?;
let mut wires = vec![f.outer_wire()];
wires.extend(f.inner_wires().iter().copied());
let mut extreme = 0.0_f64;
for wid in wires {
for oe in topo.wire(wid)?.edges() {
let e = topo.edge(oe.edge())?;
for vid in [e.start(), e.end()] {
let s = n_other.dot(topo.vertex(vid)?.point() - p);
if s.abs() > extreme.abs() {
extreme = s;
}
}
}
}
Ok(extreme)
}
fn plane_plane_fillet(
spine: &Spine,
topo: &Topology,
n1: Vec3,
n2: Vec3,
radius: f64,
face1: FaceId,
face2: FaceId,
) -> Result<StripeResult, BlendError> {
let p_start = spine.evaluate(topo, 0.0)?;
let p_end = spine.evaluate(topo, spine.length())?;
let tangent = spine.tangent(topo, 0.0)?;
let half_angle = dihedral_half_angle(n1, n2);
let sin_half = half_angle.sin();
let cos_half = half_angle.cos();
if sin_half.abs() < 1e-10 {
return Err(BlendError::Math(brepkit_math::MathError::ZeroVector));
}
let (bisector, _cross_dir) = section_basis(n1, n2, tangent);
let w1 = material_side_witness(topo, face1, n2, p_start)?;
let w2 = material_side_witness(topo, face2, n1, p_start)?;
let bisector = if w1 < -ANALYTIC_TOL_LIN && w2 < -ANALYTIC_TOL_LIN {
-bisector
} else {
bisector
};
let center_offset = radius / sin_half;
let cyl_origin = p_start + bisector * center_offset;
let cyl_axis = tangent;
let cylinder = CylindricalSurface::new(cyl_origin, cyl_axis, radius)?;
let contact_offset = radius * cos_half / sin_half;
let contact_dir1 = compute_contact_direction(n1, bisector);
let contact_dir2 = compute_contact_direction(n2, bisector);
let c1_start = p_start + contact_dir1 * contact_offset;
let c1_end = p_end + contact_dir1 * contact_offset;
let c2_start = p_start + contact_dir2 * contact_offset;
let c2_end = p_end + contact_dir2 * contact_offset;
let contact1 = nurbs_line(c1_start, c1_end)?;
let contact2 = nurbs_line(c2_start, c2_end)?;
let pcurve1 = {
let adapter = crate::builder_utils::PlaneAdapter::from_normal_and_d(n1, 0.0);
let (u0, v0) = adapter.project_point(c1_start);
let (u1, v1) = adapter.project_point(c1_end);
Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u0, v0),
brepkit_math::vec::Vec2::new(u1 - u0, v1 - v0),
)?)
};
let pcurve2 = {
let adapter = crate::builder_utils::PlaneAdapter::from_normal_and_d(n2, 0.0);
let (u0, v0) = adapter.project_point(c2_start);
let (u1, v1) = adapter.project_point(c2_end);
Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u0, v0),
brepkit_math::vec::Vec2::new(u1 - u0, v1 - v0),
)?)
};
let section_start = CircSection {
p1: c1_start,
p2: c2_start,
center: cyl_origin,
radius,
uv1: (0.0, 0.0),
uv2: (0.0, 0.0),
t: 0.0,
};
let cyl_end = p_end + bisector * center_offset;
let section_end = CircSection {
p1: c1_end,
p2: c2_end,
center: cyl_end,
radius,
uv1: (1.0, 0.0),
uv2: (1.0, 0.0),
t: 1.0,
};
let stripe = Stripe {
spine: spine.clone(),
surface: FaceSurface::Cylinder(cylinder),
pcurve1,
pcurve2,
contact1,
contact2,
face1,
face2,
sections: vec![section_start, section_end],
};
Ok(StripeResult {
stripe,
new_edges: Vec::new(),
})
}
#[allow(clippy::too_many_lines, clippy::too_many_arguments)]
fn plane_plane_chamfer(
spine: &Spine,
topo: &Topology,
n1: Vec3,
n2: Vec3,
d1: f64,
d2: f64,
face1: FaceId,
face2: FaceId,
) -> Result<StripeResult, BlendError> {
let p_start = spine.evaluate(topo, 0.0)?;
let p_end = spine.evaluate(topo, spine.length())?;
let tangent = spine.tangent(topo, 0.0)?;
let (bisector, _cross_dir) = section_basis(n1, n2, tangent);
let contact_dir1 = material_contact_direction(topo, face1, spine, n1, tangent)
.unwrap_or_else(|| compute_contact_direction(n1, bisector));
let contact_dir2 = material_contact_direction(topo, face2, spine, n2, tangent)
.unwrap_or_else(|| compute_contact_direction(n2, bisector));
let c1_start = p_start + contact_dir1 * d1;
let c1_end = p_end + contact_dir1 * d1;
let c2_start = p_start + contact_dir2 * d2;
let c2_end = p_end + contact_dir2 * d2;
let contact1 = nurbs_line(c1_start, c1_end)?;
let contact2 = nurbs_line(c2_start, c2_end)?;
let chamfer_span = c2_start - c1_start;
let chamfer_normal_raw = tangent.cross(chamfer_span);
let chamfer_normal = chamfer_normal_raw
.normalize()
.map_err(|_| BlendError::Math(brepkit_math::MathError::ZeroVector))?;
let chamfer_d = chamfer_normal.dot(Vec3::new(c1_start.x(), c1_start.y(), c1_start.z()));
let pcurve1 = {
let adapter = crate::builder_utils::PlaneAdapter::from_normal_and_d(n1, 0.0);
let (u0, v0) = adapter.project_point(c1_start);
let (u1, v1) = adapter.project_point(c1_end);
Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u0, v0),
brepkit_math::vec::Vec2::new(u1 - u0, v1 - v0),
)?)
};
let pcurve2 = {
let adapter = crate::builder_utils::PlaneAdapter::from_normal_and_d(n2, 0.0);
let (u0, v0) = adapter.project_point(c2_start);
let (u1, v1) = adapter.project_point(c2_end);
Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u0, v0),
brepkit_math::vec::Vec2::new(u1 - u0, v1 - v0),
)?)
};
let midpoint_start = midpoint_3d(c1_start, c2_start);
let midpoint_end = midpoint_3d(c1_end, c2_end);
let chamfer_radius = (c1_start - c2_start).length() / 2.0;
let section_start = CircSection {
p1: c1_start,
p2: c2_start,
center: midpoint_start,
radius: chamfer_radius,
uv1: (0.0, 0.0),
uv2: (0.0, 0.0),
t: 0.0,
};
let section_end = CircSection {
p1: c1_end,
p2: c2_end,
center: midpoint_end,
radius: chamfer_radius,
uv1: (1.0, 0.0),
uv2: (1.0, 0.0),
t: 1.0,
};
let stripe = Stripe {
spine: spine.clone(),
surface: FaceSurface::Plane {
normal: chamfer_normal,
d: chamfer_d,
},
pcurve1,
pcurve2,
contact1,
contact2,
face1,
face2,
sections: vec![section_start, section_end],
};
Ok(StripeResult {
stripe,
new_edges: Vec::new(),
})
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
pub fn plane_cylinder_fillet(
n_p_inward: Vec3,
d_plane: f64,
cyl: &brepkit_math::surfaces::CylindricalSurface,
spine: &Spine,
topo: &Topology,
radius: f64,
face_plane: FaceId,
face_cyl: FaceId,
) -> Result<Option<StripeResult>, BlendError> {
use brepkit_math::surfaces::ToroidalSurface;
let tol_ang = ANALYTIC_TOL_ANG;
let tol_lin = ANALYTIC_TOL_LIN;
let axis_c = cyl.axis();
let n_dot = axis_c.dot(n_p_inward);
if n_dot.abs() < 1.0 - tol_ang {
return Ok(None);
}
let concave = topo.face(face_cyl)?.is_reversed();
let r_c = cyl.radius();
let rim = !concave && plane_is_bounded_disc(topo, face_plane, cyl, r_c)?;
let inward = concave || rim;
let max_radius = if inward { r_c * 0.5 } else { r_c };
if radius <= tol_lin || radius >= max_radius {
return Ok(None);
}
let o_c = cyl.origin();
let step = d_plane - n_p_inward.dot(Vec3::new(o_c.x(), o_c.y(), o_c.z()));
let p_axis_on_plane = o_c + n_p_inward * step;
let z_axis_dir = if rim { n_p_inward } else { -n_p_inward };
let torus_center = p_axis_on_plane + z_axis_dir * radius;
let major_radius = if inward { r_c - radius } else { r_c + radius };
let minor_radius = radius;
let edges = spine.edges();
let is_closed_spine = if edges.len() == 1 {
let e = topo.edge(edges[0])?;
e.start() == e.end()
} else {
false
};
let spine_len = spine.length();
if !is_closed_spine && spine_len < tol_lin {
return Ok(None);
}
let cyl_x = cyl.x_axis();
let torus = ToroidalSurface::with_axis_and_ref_dir(
torus_center,
major_radius,
minor_radius,
axis_c,
cyl_x,
)?;
let p_spine_start = spine.evaluate(topo, 0.0)?;
let u_start = ParametricSurface::project_point(cyl, p_spine_start).0;
let u_end = if is_closed_spine {
u_start + 2.0 * std::f64::consts::PI
} else {
let p_spine_end = spine.evaluate(topo, spine_len)?;
let u_end_raw = ParametricSurface::project_point(cyl, p_spine_end).0;
if u_end_raw > u_start {
u_end_raw
} else {
u_end_raw + 2.0 * std::f64::consts::PI
}
};
let contact_plane_circle = brepkit_math::curves::Circle3D::with_axes(
p_axis_on_plane,
axis_c,
major_radius,
cyl_x,
cyl.y_axis(),
)?;
let contact_cyl_center = p_axis_on_plane + z_axis_dir * radius;
let contact_cyl_circle = brepkit_math::curves::Circle3D::with_axes(
contact_cyl_center,
axis_c,
r_c,
cyl_x,
cyl.y_axis(),
)?;
let contact_plane = circle_arc_to_nurbs(&contact_plane_circle, u_start, u_end)?;
let contact_cyl = circle_arc_to_nurbs(&contact_cyl_circle, u_start, u_end)?;
let v_cyl = cyl_v_at_point(cyl, contact_cyl_center);
let plane_adapter = crate::builder_utils::PlaneAdapter::from_normal_and_d(n_p_inward, d_plane);
let pcurve_plane = {
let (cu, cv) = plane_adapter.project_point(p_axis_on_plane);
Curve2D::Circle(brepkit_math::curves2d::Circle2D::new(
brepkit_math::vec::Point2::new(cu, cv),
major_radius,
)?)
};
let pcurve_cyl = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_start, v_cyl),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let p_plane_at = |u: f64| contact_plane_circle.evaluate(u);
let p_cyl_at = |u: f64| contact_cyl_circle.evaluate(u);
let center_at = |u: f64| {
contact_plane_circle.evaluate(u) + z_axis_dir * radius
};
let plane_uv_at = |u: f64| plane_adapter.project_point(p_plane_at(u));
let section_start = CircSection {
p1: p_plane_at(u_start),
p2: p_cyl_at(u_start),
center: center_at(u_start),
radius,
uv1: plane_uv_at(u_start),
uv2: (u_start, v_cyl),
t: 0.0,
};
let section_end = CircSection {
p1: p_plane_at(u_end),
p2: p_cyl_at(u_end),
center: center_at(u_end),
radius,
uv1: plane_uv_at(u_end),
uv2: (u_end, v_cyl),
t: 1.0,
};
let stripe = Stripe {
spine: spine.clone(),
surface: FaceSurface::Torus(torus),
pcurve1: pcurve_plane,
pcurve2: pcurve_cyl,
contact1: contact_plane,
contact2: contact_cyl,
face1: face_plane,
face2: face_cyl,
sections: vec![section_start, section_end],
};
Ok(Some(StripeResult {
stripe,
new_edges: Vec::new(),
}))
}
fn plane_is_bounded_disc(
topo: &Topology,
face_plane: FaceId,
cyl: &brepkit_math::surfaces::CylindricalSurface,
r_c: f64,
) -> Result<bool, BlendError> {
let face = topo.face(face_plane)?;
if !face.inner_wires().is_empty() {
return Ok(false);
}
let axis = cyl.axis();
let o_c = cyl.origin();
let radial = |p: Point3| -> f64 {
let d = p - o_c;
let along = axis * axis.dot(d);
(d - along).length()
};
let tol = r_c * 1e-6 + ANALYTIC_TOL_LIN;
let wire = topo.wire(face.outer_wire())?;
for oe in wire.edges() {
let edge = topo.edge(oe.edge())?;
let s = topo.vertex(edge.start())?.point();
let e = topo.vertex(edge.end())?.point();
if radial(s) > r_c + tol || radial(e) > r_c + tol {
return Ok(false);
}
}
Ok(true)
}
fn cyl_v_at_point(cyl: &brepkit_math::surfaces::CylindricalSurface, p: Point3) -> f64 {
let axis = cyl.axis();
let to_p = p - cyl.origin();
axis.dot(to_p)
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
pub fn plane_cylinder_chamfer(
n_p_inward: Vec3,
d_plane: f64,
cyl: &brepkit_math::surfaces::CylindricalSurface,
spine: &Spine,
topo: &Topology,
d1: f64,
d2: f64,
face_plane: FaceId,
face_cyl: FaceId,
) -> Result<Option<StripeResult>, BlendError> {
use brepkit_math::surfaces::ConicalSurface;
use std::f64::consts::PI;
let tol_ang = ANALYTIC_TOL_ANG;
let tol_lin = ANALYTIC_TOL_LIN;
let axis_c = cyl.axis();
let n_dot = axis_c.dot(n_p_inward);
if n_dot.abs() < 1.0 - tol_ang {
return Ok(None);
}
let concave = topo.face(face_cyl)?.is_reversed();
let signed_offset: f64 = if concave { -1.0 } else { 1.0 };
let r_c = cyl.radius();
if d1 <= tol_lin || d2 <= tol_lin {
return Ok(None);
}
if !concave && d1 >= r_c {
return Ok(None);
}
let o_c = cyl.origin();
let step = d_plane - n_p_inward.dot(Vec3::new(o_c.x(), o_c.y(), o_c.z()));
let p_axis_on_plane = o_c + n_p_inward * step;
let axis_toward_material = -n_p_inward;
let edges = spine.edges();
let is_closed_spine = if edges.len() == 1 {
let e = topo.edge(edges[0])?;
e.start() == e.end()
} else {
false
};
let spine_len = spine.length();
if !is_closed_spine && spine_len < tol_lin {
return Ok(None);
}
let half_angle = d2.atan2(d1);
let plate_contact_radius = r_c - signed_offset * d1;
let apex_offset = plate_contact_radius * d2 / d1;
let apex_dir = n_p_inward * signed_offset;
let apex_pos = p_axis_on_plane + apex_dir * apex_offset;
let cone_axis = -apex_dir;
let cyl_x = cyl.x_axis();
let cone = ConicalSurface::with_ref_dir(apex_pos, cone_axis, half_angle, cyl_x)?;
let cone_y = cyl.y_axis();
let contact_plane_circle = brepkit_math::curves::Circle3D::with_axes(
p_axis_on_plane,
axis_c,
plate_contact_radius,
cyl_x,
cone_y,
)?;
let cyl_contact_center = p_axis_on_plane + axis_toward_material * d2;
let contact_cyl_circle =
brepkit_math::curves::Circle3D::with_axes(cyl_contact_center, axis_c, r_c, cyl_x, cone_y)?;
let p_spine_start = spine.evaluate(topo, 0.0)?;
let u_start = ParametricSurface::project_point(cyl, p_spine_start).0;
let u_end = if is_closed_spine {
u_start + 2.0 * PI
} else {
let p_spine_end = spine.evaluate(topo, spine_len)?;
let u_end_raw = ParametricSurface::project_point(cyl, p_spine_end).0;
if u_end_raw > u_start {
u_end_raw
} else {
u_end_raw + 2.0 * PI
}
};
let contact_plane = circle_arc_to_nurbs(&contact_plane_circle, u_start, u_end)?;
let contact_cyl = circle_arc_to_nurbs(&contact_cyl_circle, u_start, u_end)?;
let plane_adapter = crate::builder_utils::PlaneAdapter::from_normal_and_d(n_p_inward, d_plane);
let pcurve_plane = {
let (cu, cv) = plane_adapter.project_point(p_axis_on_plane);
Curve2D::Circle(brepkit_math::curves2d::Circle2D::new(
brepkit_math::vec::Point2::new(cu, cv),
r_c - d1,
)?)
};
let v_cyl = cyl_v_at_point(cyl, cyl_contact_center);
let pcurve_cyl = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_start, v_cyl),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let p_plane_at = |u: f64| contact_plane_circle.evaluate(u);
let p_cyl_at = |u: f64| contact_cyl_circle.evaluate(u);
let plane_uv_at = |u: f64| plane_adapter.project_point(p_plane_at(u));
let section_at = |u: f64, t: f64| {
let p1 = p_plane_at(u);
let p2 = p_cyl_at(u);
let mid = midpoint_3d(p1, p2);
CircSection {
p1,
p2,
center: mid,
radius: (p1 - p2).length() * 0.5,
uv1: plane_uv_at(u),
uv2: (u, v_cyl),
t,
}
};
let section_start = section_at(u_start, 0.0);
let section_end = section_at(u_end, 1.0);
let stripe = Stripe {
spine: spine.clone(),
surface: FaceSurface::Cone(cone),
pcurve1: pcurve_plane,
pcurve2: pcurve_cyl,
contact1: contact_plane,
contact2: contact_cyl,
face1: face_plane,
face2: face_cyl,
sections: vec![section_start, section_end],
};
Ok(Some(StripeResult {
stripe,
new_edges: Vec::new(),
}))
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
pub fn plane_cone_fillet(
n_p_inward: Vec3,
d_plane: f64,
cone: &brepkit_math::surfaces::ConicalSurface,
spine: &Spine,
topo: &Topology,
radius: f64,
face_plane: FaceId,
face_cone: FaceId,
) -> Result<Option<StripeResult>, BlendError> {
use brepkit_math::surfaces::ToroidalSurface;
use std::f64::consts::PI;
let tol_ang = ANALYTIC_TOL_ANG;
let tol_lin = ANALYTIC_TOL_LIN;
let axis_c = cone.axis();
let n_dot = axis_c.dot(n_p_inward);
if n_dot.abs() < 1.0 - tol_ang {
return Ok(None);
}
let concave = topo.face(face_cone)?.is_reversed();
let alpha = cone.half_angle();
if alpha <= 1e-3 || alpha >= std::f64::consts::FRAC_PI_2 - 1e-3 {
return Ok(None);
}
let half_alpha = alpha * 0.5;
let cot_half = half_alpha.tan().recip();
let apex = cone.apex();
let step = d_plane - n_p_inward.dot(Vec3::new(apex.x(), apex.y(), apex.z()));
if step.abs() <= tol_lin {
return Ok(None);
}
if (concave && step <= 0.0) || (!concave && step >= 0.0) {
return Ok(None);
}
let apex_height = step.abs();
let p_axis_on_plane = apex + n_p_inward * step;
let r_p = apex_height * (alpha.cos() / alpha.sin());
let signed_offset = if concave { -1.0 } else { 1.0 };
let major_radius = r_p + signed_offset * radius * cot_half;
let minor_radius = radius;
if major_radius <= tol_lin {
return Ok(None);
}
if concave && major_radius - minor_radius < tol_lin {
return Ok(None);
}
let torus_center = p_axis_on_plane - n_p_inward * radius;
let axis_dir = -n_p_inward;
let edges = spine.edges();
let is_closed_spine = if edges.len() == 1 {
let e = topo.edge(edges[0])?;
e.start() == e.end()
} else {
false
};
let spine_len = spine.length();
if !is_closed_spine && spine_len < tol_lin {
return Ok(None);
}
let cone_x = cone.x_axis();
let cone_y = cone.y_axis();
let torus = ToroidalSurface::with_axis_and_ref_dir(
torus_center,
major_radius,
minor_radius,
axis_dir,
cone_x,
)?;
let u_at = |p: Point3| {
let v = p - p_axis_on_plane;
cone_y.dot(v).atan2(cone_x.dot(v))
};
let p_spine_start = spine.evaluate(topo, 0.0)?;
let u_start = u_at(p_spine_start);
let u_end = if is_closed_spine {
u_start + 2.0 * PI
} else {
let p_spine_end = spine.evaluate(topo, spine_len)?;
let u_end_raw = u_at(p_spine_end);
if u_end_raw > u_start {
u_end_raw
} else {
u_end_raw + 2.0 * PI
}
};
let contact_plane_radius = major_radius;
let contact_cone_radius = (major_radius - signed_offset * radius * alpha.sin()).max(tol_lin);
let contact_cone_axial_magnitude = radius * (1.0 + alpha.cos());
let cone_contact_center = p_axis_on_plane + (-n_p_inward) * contact_cone_axial_magnitude;
let contact_plane_circle = brepkit_math::curves::Circle3D::with_axes(
p_axis_on_plane,
axis_dir,
contact_plane_radius,
cone_x,
cone_y,
)?;
let contact_cone_circle = brepkit_math::curves::Circle3D::with_axes(
cone_contact_center,
axis_dir,
contact_cone_radius,
cone_x,
cone_y,
)?;
let contact_plane = circle_arc_to_nurbs(&contact_plane_circle, u_start, u_end)?;
let contact_cone = circle_arc_to_nurbs(&contact_cone_circle, u_start, u_end)?;
let plane_adapter = crate::builder_utils::PlaneAdapter::from_normal_and_d(n_p_inward, d_plane);
let pcurve_plane = {
let (cu, cv) = plane_adapter.project_point(p_axis_on_plane);
Curve2D::Circle(brepkit_math::curves2d::Circle2D::new(
brepkit_math::vec::Point2::new(cu, cv),
major_radius,
)?)
};
let v_cone = ParametricSurface::project_point(cone, cone_contact_center).1;
let pcurve_cone = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_start, v_cone),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let p_plane_at = |u: f64| contact_plane_circle.evaluate(u);
let p_cone_at = |u: f64| contact_cone_circle.evaluate(u);
let center_at = |u: f64| {
contact_plane_circle.evaluate(u) + (-n_p_inward) * radius
};
let plane_uv_at = |u: f64| plane_adapter.project_point(p_plane_at(u));
let section_start = CircSection {
p1: p_plane_at(u_start),
p2: p_cone_at(u_start),
center: center_at(u_start),
radius,
uv1: plane_uv_at(u_start),
uv2: (u_start, v_cone),
t: 0.0,
};
let section_end = CircSection {
p1: p_plane_at(u_end),
p2: p_cone_at(u_end),
center: center_at(u_end),
radius,
uv1: plane_uv_at(u_end),
uv2: (u_end, v_cone),
t: 1.0,
};
let stripe = Stripe {
spine: spine.clone(),
surface: FaceSurface::Torus(torus),
pcurve1: pcurve_plane,
pcurve2: pcurve_cone,
contact1: contact_plane,
contact2: contact_cone,
face1: face_plane,
face2: face_cone,
sections: vec![section_start, section_end],
};
Ok(Some(StripeResult {
stripe,
new_edges: Vec::new(),
}))
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
pub fn plane_cone_chamfer(
n_p_inward: Vec3,
d_plane: f64,
cone: &brepkit_math::surfaces::ConicalSurface,
spine: &Spine,
topo: &Topology,
d1: f64,
d2: f64,
face_plane: FaceId,
face_cone: FaceId,
) -> Result<Option<StripeResult>, BlendError> {
use brepkit_math::surfaces::ConicalSurface;
use std::f64::consts::PI;
let tol_ang = ANALYTIC_TOL_ANG;
let tol_lin = ANALYTIC_TOL_LIN;
let axis_c = cone.axis();
let n_dot = axis_c.dot(n_p_inward);
if n_dot.abs() < 1.0 - tol_ang {
return Ok(None);
}
let signed_offset: f64 = if n_dot > 0.0 { 1.0 } else { -1.0 };
let concave = signed_offset < 0.0;
if concave != topo.face(face_cone)?.is_reversed() {
return Ok(None);
}
let alpha = cone.half_angle();
if alpha <= 1e-3 || alpha >= std::f64::consts::FRAC_PI_2 - 1e-3 {
return Ok(None);
}
if d1 <= tol_lin || d2 <= tol_lin {
return Ok(None);
}
let apex = cone.apex();
let step = d_plane - n_p_inward.dot(Vec3::new(apex.x(), apex.y(), apex.z()));
if step.abs() <= tol_lin {
return Ok(None);
}
if step * signed_offset <= 0.0 {
return Ok(None);
}
let apex_height = step.abs();
let p_axis_on_plane = apex + n_p_inward * step;
let r_p = apex_height * (alpha.cos() / alpha.sin());
if !concave && d1 >= r_p {
return Ok(None);
}
let (sin_a, cos_a) = alpha.sin_cos();
let dr = d1 - d2 * cos_a;
let dz = d2 * sin_a;
if dz <= tol_lin {
return Ok(None);
}
if dr <= tol_lin {
return Ok(None);
}
let chamfer_half_angle = dz.atan2(dr);
if chamfer_half_angle <= 1e-3 || chamfer_half_angle >= std::f64::consts::FRAC_PI_2 - 1e-3 {
return Ok(None);
}
let plate_contact_radius = r_p - signed_offset * d1;
let chamfer_apex_offset = plate_contact_radius * dz / dr;
let axis_toward_apex = n_p_inward * signed_offset;
let chamfer_apex_pos = p_axis_on_plane + axis_toward_apex * chamfer_apex_offset;
let chamfer_axis = -axis_toward_apex;
let axis_into_material = -n_p_inward;
let edges = spine.edges();
let is_closed_spine = if edges.len() == 1 {
let e = topo.edge(edges[0])?;
e.start() == e.end()
} else {
false
};
let spine_len = spine.length();
if !is_closed_spine && spine_len < tol_lin {
return Ok(None);
}
let cone_x = cone.x_axis();
let cone_y = cone.y_axis();
let chamfer_cone =
ConicalSurface::with_ref_dir(chamfer_apex_pos, chamfer_axis, chamfer_half_angle, cone_x)?;
let cone_contact_radius = r_p - signed_offset * d2 * cos_a;
let cone_contact_axial_offset = d2 * sin_a;
let cone_contact_center = p_axis_on_plane + axis_into_material * cone_contact_axial_offset;
let contact_plane_circle = brepkit_math::curves::Circle3D::with_axes(
p_axis_on_plane,
axis_c,
plate_contact_radius,
cone_x,
cone_y,
)?;
let contact_cone_circle = brepkit_math::curves::Circle3D::with_axes(
cone_contact_center,
axis_c,
cone_contact_radius,
cone_x,
cone_y,
)?;
let u_at = |p: Point3| {
let v = p - p_axis_on_plane;
cone_y.dot(v).atan2(cone_x.dot(v))
};
let p_spine_start = spine.evaluate(topo, 0.0)?;
let u_start = u_at(p_spine_start);
let u_end = if is_closed_spine {
u_start + 2.0 * PI
} else {
let p_spine_end = spine.evaluate(topo, spine_len)?;
let u_end_raw = u_at(p_spine_end);
if u_end_raw > u_start {
u_end_raw
} else {
u_end_raw + 2.0 * PI
}
};
let contact_plane = circle_arc_to_nurbs(&contact_plane_circle, u_start, u_end)?;
let contact_cone = circle_arc_to_nurbs(&contact_cone_circle, u_start, u_end)?;
let plane_adapter = crate::builder_utils::PlaneAdapter::from_normal_and_d(n_p_inward, d_plane);
let pcurve_plane = {
let (cu, cv) = plane_adapter.project_point(p_axis_on_plane);
Curve2D::Circle(brepkit_math::curves2d::Circle2D::new(
brepkit_math::vec::Point2::new(cu, cv),
plate_contact_radius,
)?)
};
let v_cone = ParametricSurface::project_point(cone, cone_contact_center).1;
let pcurve_cone = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_start, v_cone),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let p_plane_at = |u: f64| contact_plane_circle.evaluate(u);
let p_cone_at = |u: f64| contact_cone_circle.evaluate(u);
let plane_uv_at = |u: f64| plane_adapter.project_point(p_plane_at(u));
let section_at = |u: f64, t: f64| {
let p1 = p_plane_at(u);
let p2 = p_cone_at(u);
let mid = midpoint_3d(p1, p2);
CircSection {
p1,
p2,
center: mid,
radius: (p1 - p2).length() * 0.5,
uv1: plane_uv_at(u),
uv2: (u, v_cone),
t,
}
};
let section_start = section_at(u_start, 0.0);
let section_end = section_at(u_end, 1.0);
let stripe = Stripe {
spine: spine.clone(),
surface: FaceSurface::Cone(chamfer_cone),
pcurve1: pcurve_plane,
pcurve2: pcurve_cone,
contact1: contact_plane,
contact2: contact_cone,
face1: face_plane,
face2: face_cone,
sections: vec![section_start, section_end],
};
Ok(Some(StripeResult {
stripe,
new_edges: Vec::new(),
}))
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
pub fn plane_sphere_fillet(
n_p_inward: Vec3,
d_plane: f64,
sphere: &brepkit_math::surfaces::SphericalSurface,
spine: &Spine,
topo: &Topology,
radius: f64,
face_plane: FaceId,
face_sphere: FaceId,
) -> Result<Option<StripeResult>, BlendError> {
use brepkit_math::surfaces::ToroidalSurface;
use std::f64::consts::PI;
let tol_lin = ANALYTIC_TOL_LIN;
let tol_ang = ANALYTIC_TOL_ANG;
if radius <= tol_lin {
return Ok(None);
}
let concave = topo.face(face_sphere)?.is_reversed();
let signed_offset: f64 = if concave { -1.0 } else { 1.0 };
if sphere.z_axis().dot(n_p_inward).abs() < 1.0 - tol_ang {
return Ok(None);
}
let big_r = sphere.radius();
let center = sphere.center();
let center_v = Vec3::new(center.x(), center.y(), center.z());
let step = d_plane - n_p_inward.dot(center_v);
let p_axis_on_plane = center + n_p_inward * step;
let h_signed = -step;
let h_abs = h_signed.abs();
if h_abs >= big_r - tol_lin {
return Ok(None);
}
let r_p_sq = big_r * big_r - h_abs * h_abs;
if r_p_sq <= tol_lin * tol_lin {
return Ok(None);
}
let major_radius_sq = r_p_sq + signed_offset * 2.0 * radius * (big_r - h_signed);
if major_radius_sq <= tol_lin * tol_lin {
return Ok(None);
}
let major_radius = major_radius_sq.sqrt();
let minor_radius = radius;
if major_radius < minor_radius - tol_lin {
return Ok(None);
}
let edges = spine.edges();
let is_closed_spine = if edges.len() == 1 {
let e = topo.edge(edges[0])?;
e.start() == e.end()
} else {
false
};
let spine_len = spine.length();
if !is_closed_spine && spine_len < tol_lin {
return Ok(None);
}
let torus_axis = n_p_inward;
let sphere_x = sphere.x_axis();
let sphere_y = sphere.y_axis();
let ref_dir = if sphere_x.cross(torus_axis).length() > tol_ang {
sphere_x
} else {
sphere_y
};
let torus_center = p_axis_on_plane - n_p_inward * (signed_offset * radius);
let torus = ToroidalSurface::with_axis_and_ref_dir(
torus_center,
major_radius,
minor_radius,
torus_axis,
ref_dir,
)?;
let u_at = |p: Point3| {
let v = p - p_axis_on_plane;
sphere_y.dot(v).atan2(sphere_x.dot(v))
};
let p_spine_start = spine.evaluate(topo, 0.0)?;
let u_start = u_at(p_spine_start);
let u_end = if is_closed_spine {
u_start + 2.0 * PI
} else {
let p_spine_end = spine.evaluate(topo, spine_len)?;
let u_end_raw = u_at(p_spine_end);
if u_end_raw > u_start {
u_end_raw
} else {
u_end_raw + 2.0 * PI
}
};
let denom = big_r + signed_offset * radius;
let contact_sphere_radial = major_radius * big_r / denom;
let contact_sphere_axial = signed_offset * radius * (h_signed - big_r) / denom;
let contact_sphere_center = p_axis_on_plane + n_p_inward * contact_sphere_axial;
let contact_plane_circle = brepkit_math::curves::Circle3D::with_axes(
p_axis_on_plane,
torus_axis,
major_radius,
sphere_x,
sphere_y,
)?;
let contact_sphere_circle = brepkit_math::curves::Circle3D::with_axes(
contact_sphere_center,
torus_axis,
contact_sphere_radial,
sphere_x,
sphere_y,
)?;
let contact_plane = circle_arc_to_nurbs(&contact_plane_circle, u_start, u_end)?;
let contact_sphere = circle_arc_to_nurbs(&contact_sphere_circle, u_start, u_end)?;
let plane_adapter = crate::builder_utils::PlaneAdapter::from_normal_and_d(n_p_inward, d_plane);
let pcurve_plane = {
let (cu, cv) = plane_adapter.project_point(p_axis_on_plane);
Curve2D::Circle(brepkit_math::curves2d::Circle2D::new(
brepkit_math::vec::Point2::new(cu, cv),
major_radius,
)?)
};
let sample_p = contact_sphere_circle.evaluate(u_start);
let v_sphere = ParametricSurface::project_point(sphere, sample_p).1;
let pcurve_sphere = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_start, v_sphere),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let p_plane_at = |u: f64| contact_plane_circle.evaluate(u);
let p_sphere_at = |u: f64| contact_sphere_circle.evaluate(u);
let center_at =
|u: f64| contact_plane_circle.evaluate(u) - n_p_inward * (signed_offset * radius);
let plane_uv_at = |u: f64| plane_adapter.project_point(p_plane_at(u));
let section_at = |u: f64, t: f64| CircSection {
p1: p_plane_at(u),
p2: p_sphere_at(u),
center: center_at(u),
radius,
uv1: plane_uv_at(u),
uv2: (u, v_sphere),
t,
};
let section_start = section_at(u_start, 0.0);
let section_end = section_at(u_end, 1.0);
let stripe = Stripe {
spine: spine.clone(),
surface: FaceSurface::Torus(torus),
pcurve1: pcurve_plane,
pcurve2: pcurve_sphere,
contact1: contact_plane,
contact2: contact_sphere,
face1: face_plane,
face2: face_sphere,
sections: vec![section_start, section_end],
};
Ok(Some(StripeResult {
stripe,
new_edges: Vec::new(),
}))
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
pub fn plane_sphere_chamfer(
n_p_inward: Vec3,
d_plane: f64,
sphere: &brepkit_math::surfaces::SphericalSurface,
spine: &Spine,
topo: &Topology,
d1: f64,
d2: f64,
face_plane: FaceId,
face_sphere: FaceId,
) -> Result<Option<StripeResult>, BlendError> {
use brepkit_math::surfaces::ConicalSurface;
use std::f64::consts::PI;
let tol_lin = ANALYTIC_TOL_LIN;
let tol_ang = ANALYTIC_TOL_ANG;
if d1 <= tol_lin || d2 <= tol_lin {
return Ok(None);
}
let concave = topo.face(face_sphere)?.is_reversed();
let signed_offset: f64 = if concave { -1.0 } else { 1.0 };
if sphere.z_axis().dot(n_p_inward).abs() < 1.0 - tol_ang {
return Ok(None);
}
let big_r = sphere.radius();
let center = sphere.center();
let center_v = Vec3::new(center.x(), center.y(), center.z());
let step = d_plane - n_p_inward.dot(center_v);
let p_axis_on_plane = center + n_p_inward * step;
let h_signed = -step;
let h_abs = h_signed.abs();
if h_abs >= big_r - tol_lin {
return Ok(None);
}
let r_p_sq = big_r * big_r - h_abs * h_abs;
if r_p_sq <= tol_lin * tol_lin {
return Ok(None);
}
let r_p = r_p_sq.sqrt();
let delta = d2 / big_r;
let (sin_d, cos_d) = delta.sin_cos();
let sphere_radial = r_p * cos_d + signed_offset * h_signed * sin_d;
let sphere_axial = h_signed * (1.0 - cos_d) + signed_offset * r_p * sin_d;
if sphere_radial <= tol_lin {
return Ok(None);
}
let delta_r = sphere_radial - (r_p + d1);
let delta_z = sphere_axial;
if delta_r >= -tol_lin {
return Ok(None);
}
if delta_z.abs() <= tol_lin {
return Ok(None);
}
let z_apex = -(r_p + d1) * delta_z / delta_r;
let cone_half_angle = (z_apex.abs() / (r_p + d1)).atan();
if cone_half_angle <= 1e-3 || cone_half_angle >= std::f64::consts::FRAC_PI_2 - 1e-3 {
return Ok(None);
}
let chamfer_apex_pos = p_axis_on_plane + n_p_inward * z_apex;
let chamfer_axis = if z_apex > 0.0 {
-n_p_inward
} else {
n_p_inward
};
let edges = spine.edges();
let is_closed_spine = if edges.len() == 1 {
let e = topo.edge(edges[0])?;
e.start() == e.end()
} else {
false
};
let spine_len = spine.length();
if !is_closed_spine && spine_len < tol_lin {
return Ok(None);
}
let sphere_x = sphere.x_axis();
let sphere_y = sphere.y_axis();
let ref_dir = if sphere_x.cross(chamfer_axis).length() > tol_ang {
sphere_x
} else {
sphere_y
};
let chamfer_cone =
ConicalSurface::with_ref_dir(chamfer_apex_pos, chamfer_axis, cone_half_angle, ref_dir)?;
let u_at = |p: Point3| {
let v = p - p_axis_on_plane;
sphere_y.dot(v).atan2(sphere_x.dot(v))
};
let p_spine_start = spine.evaluate(topo, 0.0)?;
let u_start = u_at(p_spine_start);
let u_end = if is_closed_spine {
u_start + 2.0 * PI
} else {
let p_spine_end = spine.evaluate(topo, spine_len)?;
let u_end_raw = u_at(p_spine_end);
if u_end_raw > u_start {
u_end_raw
} else {
u_end_raw + 2.0 * PI
}
};
let plate_axis = n_p_inward;
let contact_plane_circle = brepkit_math::curves::Circle3D::with_axes(
p_axis_on_plane,
plate_axis,
r_p + d1,
sphere_x,
sphere_y,
)?;
let contact_sphere_center = p_axis_on_plane + n_p_inward * sphere_axial;
let contact_sphere_circle = brepkit_math::curves::Circle3D::with_axes(
contact_sphere_center,
plate_axis,
sphere_radial,
sphere_x,
sphere_y,
)?;
let contact_plane = circle_arc_to_nurbs(&contact_plane_circle, u_start, u_end)?;
let contact_sphere = circle_arc_to_nurbs(&contact_sphere_circle, u_start, u_end)?;
let plane_adapter = crate::builder_utils::PlaneAdapter::from_normal_and_d(n_p_inward, d_plane);
let pcurve_plane = {
let (cu, cv) = plane_adapter.project_point(p_axis_on_plane);
Curve2D::Circle(brepkit_math::curves2d::Circle2D::new(
brepkit_math::vec::Point2::new(cu, cv),
r_p + d1,
)?)
};
let sample_p = contact_sphere_circle.evaluate(u_start);
let v_sphere = ParametricSurface::project_point(sphere, sample_p).1;
let pcurve_sphere = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_start, v_sphere),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let p_plane_at = |u: f64| contact_plane_circle.evaluate(u);
let p_sphere_at = |u: f64| contact_sphere_circle.evaluate(u);
let plane_uv_at = |u: f64| plane_adapter.project_point(p_plane_at(u));
let section_at = |u: f64, t: f64| {
let p1 = p_plane_at(u);
let p2 = p_sphere_at(u);
let mid = midpoint_3d(p1, p2);
CircSection {
p1,
p2,
center: mid,
radius: (p1 - p2).length() * 0.5,
uv1: plane_uv_at(u),
uv2: (u, v_sphere),
t,
}
};
let section_start = section_at(u_start, 0.0);
let section_end = section_at(u_end, 1.0);
let stripe = Stripe {
spine: spine.clone(),
surface: FaceSurface::Cone(chamfer_cone),
pcurve1: pcurve_plane,
pcurve2: pcurve_sphere,
contact1: contact_plane,
contact2: contact_sphere,
face1: face_plane,
face2: face_sphere,
sections: vec![section_start, section_end],
};
Ok(Some(StripeResult {
stripe,
new_edges: Vec::new(),
}))
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
pub fn sphere_sphere_fillet(
s1: &brepkit_math::surfaces::SphericalSurface,
s2: &brepkit_math::surfaces::SphericalSurface,
spine: &Spine,
topo: &Topology,
radius: f64,
face1: FaceId,
face2: FaceId,
) -> Result<Option<StripeResult>, BlendError> {
use brepkit_math::surfaces::ToroidalSurface;
use std::f64::consts::PI;
let tol_lin = ANALYTIC_TOL_LIN;
if radius <= tol_lin {
return Ok(None);
}
let s1_signed: f64 = if topo.face(face1)?.is_reversed() {
-1.0
} else {
1.0
};
let s2_signed: f64 = if topo.face(face2)?.is_reversed() {
-1.0
} else {
1.0
};
let big_r1 = s1.radius();
let big_r2 = s2.radius();
let c1 = s1.center();
let c2 = s2.center();
let c1_to_c2 = c2 - c1;
let big_d = c1_to_c2.length();
if big_d <= tol_lin {
return Ok(None);
}
if big_d <= (big_r1 - big_r2).abs() + tol_lin || big_d >= big_r1 + big_r2 - tol_lin {
return Ok(None);
}
let axis = (c1_to_c2 * (1.0 / big_d)).normalize()?;
let a0 = (big_r1 * big_r1 - big_r2 * big_r2 + big_d * big_d) / (2.0 * big_d);
let r_p_sq = big_r1 * big_r1 - a0 * a0;
if r_p_sq <= tol_lin * tol_lin {
return Ok(None);
}
let q1 = big_r1 + s1_signed * radius;
let q2 = big_r2 + s2_signed * radius;
if q1 <= tol_lin || q2 <= tol_lin {
return Ok(None);
}
let a_ball = (q1 * q1 - q2 * q2 + big_d * big_d) / (2.0 * big_d);
let major_radius_sq = q1 * q1 - a_ball * a_ball;
if major_radius_sq <= tol_lin * tol_lin {
return Ok(None);
}
let major_radius = major_radius_sq.sqrt();
let minor_radius = radius;
if major_radius < minor_radius - tol_lin {
return Ok(None);
}
let edges = spine.edges();
let is_closed_spine = if edges.len() == 1 {
let e = topo.edge(edges[0])?;
e.start() == e.end()
} else {
false
};
let spine_len = spine.length();
if !is_closed_spine && spine_len < tol_lin {
return Ok(None);
}
let tol_ang = ANALYTIC_TOL_ANG;
if s1.z_axis().dot(axis).abs() < 1.0 - tol_ang || s2.z_axis().dot(axis).abs() < 1.0 - tol_ang {
return Ok(None);
}
let s1_x = s1.x_axis();
let s1_y = s1.y_axis();
let ref_dir = if s1_x.cross(axis).length() > tol_ang {
s1_x
} else {
s1_y
};
let torus_center = c1 + axis * a_ball;
let torus = ToroidalSurface::with_axis_and_ref_dir(
torus_center,
major_radius,
minor_radius,
axis,
ref_dir,
)?;
let spine_plane_center = c1 + axis * a0;
let perp_y = axis.cross(ref_dir).normalize()?;
let u_at = |p: Point3| {
let v = p - spine_plane_center;
perp_y.dot(v).atan2(ref_dir.dot(v))
};
let p_spine_start = spine.evaluate(topo, 0.0)?;
let u_start = u_at(p_spine_start);
let u_end = if is_closed_spine {
u_start + 2.0 * PI
} else {
let p_spine_end = spine.evaluate(topo, spine_len)?;
let u_end_raw = u_at(p_spine_end);
if u_end_raw > u_start {
u_end_raw
} else {
u_end_raw + 2.0 * PI
}
};
let s1_contact_axial = big_r1 * a_ball / q1;
let s1_contact_radial = big_r1 * major_radius / q1;
let s1_contact_center = c1 + axis * s1_contact_axial;
let contact1_circle = brepkit_math::curves::Circle3D::with_axes(
s1_contact_center,
axis,
s1_contact_radial,
ref_dir,
perp_y,
)?;
let s2_contact_axial_from_c2 = big_r2 * (a_ball - big_d) / q2;
let s2_contact_radial = big_r2 * major_radius / q2;
let s2_contact_center = c2 + axis * s2_contact_axial_from_c2;
let contact2_circle = brepkit_math::curves::Circle3D::with_axes(
s2_contact_center,
axis,
s2_contact_radial,
ref_dir,
perp_y,
)?;
let contact1 = circle_arc_to_nurbs(&contact1_circle, u_start, u_end)?;
let contact2 = circle_arc_to_nurbs(&contact2_circle, u_start, u_end)?;
let sample1 = contact1_circle.evaluate(u_start);
let v1 = ParametricSurface::project_point(s1, sample1).1;
let pcurve1 = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_start, v1),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let sample2 = contact2_circle.evaluate(u_start);
let v2 = ParametricSurface::project_point(s2, sample2).1;
let pcurve2 = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_start, v2),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let p1_at = |u: f64| contact1_circle.evaluate(u);
let p2_at = |u: f64| contact2_circle.evaluate(u);
let center_at = |u: f64| {
let on_torus_eq = spine_plane_center
+ ref_dir * (major_radius * u.cos())
+ perp_y * (major_radius * u.sin());
on_torus_eq + axis * (a_ball - a0)
};
let section_at = |u: f64, t: f64| CircSection {
p1: p1_at(u),
p2: p2_at(u),
center: center_at(u),
radius,
uv1: (u, v1),
uv2: (u, v2),
t,
};
let section_start = section_at(u_start, 0.0);
let section_end = section_at(u_end, 1.0);
let stripe = Stripe {
spine: spine.clone(),
surface: FaceSurface::Torus(torus),
pcurve1,
pcurve2,
contact1,
contact2,
face1,
face2,
sections: vec![section_start, section_end],
};
Ok(Some(StripeResult {
stripe,
new_edges: Vec::new(),
}))
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
pub fn sphere_cylinder_fillet(
sph: &brepkit_math::surfaces::SphericalSurface,
cyl: &brepkit_math::surfaces::CylindricalSurface,
spine: &Spine,
topo: &Topology,
radius: f64,
face_sphere: FaceId,
face_cyl: FaceId,
) -> Result<Option<StripeResult>, BlendError> {
use brepkit_math::surfaces::ToroidalSurface;
use std::f64::consts::PI;
let tol_lin = ANALYTIC_TOL_LIN;
let tol_ang = ANALYTIC_TOL_ANG;
if radius <= tol_lin {
return Ok(None);
}
let s_sphere: f64 = if topo.face(face_sphere)?.is_reversed() {
-1.0
} else {
1.0
};
let s_cyl: f64 = if topo.face(face_cyl)?.is_reversed() {
-1.0
} else {
1.0
};
let big_r_s = sph.radius();
let r_c = cyl.radius();
let c_s = sph.center();
let cyl_origin = cyl.origin();
let cyl_axis = cyl.axis();
let to_sphere = c_s - cyl_origin;
let to_sphere_v = Vec3::new(to_sphere.x(), to_sphere.y(), to_sphere.z());
let along = to_sphere_v.dot(cyl_axis);
let perp = to_sphere_v - cyl_axis * along;
if perp.length() > tol_lin {
return Ok(None);
}
if sph.z_axis().dot(cyl_axis).abs() < 1.0 - tol_ang {
return Ok(None);
}
if r_c >= big_r_s - tol_lin {
return Ok(None);
}
let h_s_sq = big_r_s * big_r_s - r_c * r_c;
if h_s_sq <= tol_lin * tol_lin {
return Ok(None);
}
let h_s = h_s_sq.sqrt();
let edges = spine.edges();
let is_closed_spine = if edges.len() == 1 {
let e = topo.edge(edges[0])?;
e.start() == e.end()
} else {
false
};
let spine_len = spine.length();
if !is_closed_spine && spine_len < tol_lin {
return Ok(None);
}
let p_spine_sample = spine.evaluate(topo, 0.0)?;
let to_sample = p_spine_sample - c_s;
let to_sample_v = Vec3::new(to_sample.x(), to_sample.y(), to_sample.z());
let sample_axial = to_sample_v.dot(cyl_axis);
let sample_radial_v = to_sample_v - cyl_axis * sample_axial;
let sample_radial = sample_radial_v.length();
if (sample_axial.abs() - h_s).abs() > tol_lin || (sample_radial - r_c).abs() > tol_lin {
return Ok(None);
}
let spine_sign = if sample_axial >= 0.0 { 1.0 } else { -1.0 };
let q_s = big_r_s + s_sphere * radius;
let q_c = r_c + s_cyl * radius;
if q_s <= tol_lin || q_c <= tol_lin {
return Ok(None);
}
let a_ball_sq = q_s * q_s - q_c * q_c;
if a_ball_sq <= tol_lin * tol_lin {
return Ok(None);
}
let a_ball = spine_sign * a_ball_sq.sqrt();
let major_radius = q_c;
let minor_radius = radius;
if major_radius < minor_radius - tol_lin {
return Ok(None);
}
let cyl_x = cyl.x_axis();
let cyl_y = cyl.y_axis();
let ref_dir = if cyl_x.cross(cyl_axis).length() > tol_ang {
cyl_x
} else {
cyl_y
};
let torus_center = c_s + cyl_axis * a_ball;
let torus = ToroidalSurface::with_axis_and_ref_dir(
torus_center,
major_radius,
minor_radius,
cyl_axis,
ref_dir,
)?;
let spine_plane_center = c_s + cyl_axis * sample_axial;
let perp_y = cyl_axis.cross(ref_dir).normalize()?;
let u_at = |p: Point3| {
let v = p - spine_plane_center;
perp_y.dot(v).atan2(ref_dir.dot(v))
};
let u_start = u_at(p_spine_sample);
let u_end = if is_closed_spine {
u_start + 2.0 * PI
} else {
let p_spine_end = spine.evaluate(topo, spine_len)?;
let u_end_raw = u_at(p_spine_end);
if u_end_raw > u_start {
u_end_raw
} else {
u_end_raw + 2.0 * PI
}
};
let sph_contact_axial = big_r_s * a_ball / q_s;
let sph_contact_radial = big_r_s * major_radius / q_s;
let sph_contact_center = c_s + cyl_axis * sph_contact_axial;
let contact_sph_circle = brepkit_math::curves::Circle3D::with_axes(
sph_contact_center,
cyl_axis,
sph_contact_radial,
ref_dir,
perp_y,
)?;
let cyl_contact_axial_world = c_s + cyl_axis * a_ball; let contact_cyl_circle = brepkit_math::curves::Circle3D::with_axes(
cyl_contact_axial_world,
cyl_axis,
r_c,
ref_dir,
perp_y,
)?;
let contact_sph = circle_arc_to_nurbs(&contact_sph_circle, u_start, u_end)?;
let contact_cyl = circle_arc_to_nurbs(&contact_cyl_circle, u_start, u_end)?;
let sample_sph = contact_sph_circle.evaluate(u_start);
let (u_sph_start, v_sph) = ParametricSurface::project_point(sph, sample_sph);
let pcurve_sph = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_sph_start, v_sph),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let sample_cyl = contact_cyl_circle.evaluate(u_start);
let u_cyl_start = ParametricSurface::project_point(cyl, sample_cyl).0;
let v_cyl = cyl_v_at_point(cyl, sample_cyl);
let pcurve_cyl = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_cyl_start, v_cyl),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let p_sph_at = |u: f64| contact_sph_circle.evaluate(u);
let p_cyl_at = |u: f64| contact_cyl_circle.evaluate(u);
let section_at = |u: f64, t: f64| CircSection {
p1: p_sph_at(u),
p2: p_cyl_at(u),
center: torus_center
+ ref_dir * (major_radius * u.cos())
+ perp_y * (major_radius * u.sin()),
radius,
uv1: (u_sph_start + (u - u_start), v_sph),
uv2: (u_cyl_start + (u - u_start), v_cyl),
t,
};
let section_start = section_at(u_start, 0.0);
let section_end = section_at(u_end, 1.0);
let stripe = Stripe {
spine: spine.clone(),
surface: FaceSurface::Torus(torus),
pcurve1: pcurve_sph,
pcurve2: pcurve_cyl,
contact1: contact_sph,
contact2: contact_cyl,
face1: face_sphere,
face2: face_cyl,
sections: vec![section_start, section_end],
};
Ok(Some(StripeResult {
stripe,
new_edges: Vec::new(),
}))
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
pub fn sphere_cone_fillet(
sph: &brepkit_math::surfaces::SphericalSurface,
cone: &brepkit_math::surfaces::ConicalSurface,
spine: &Spine,
topo: &Topology,
radius: f64,
face_sphere: FaceId,
face_cone: FaceId,
) -> Result<Option<StripeResult>, BlendError> {
use brepkit_math::surfaces::ToroidalSurface;
use std::f64::consts::PI;
let tol_lin = ANALYTIC_TOL_LIN;
let tol_ang = ANALYTIC_TOL_ANG;
if radius <= tol_lin {
return Ok(None);
}
let s_sph: f64 = if topo.face(face_sphere)?.is_reversed() {
-1.0
} else {
1.0
};
let s_cone: f64 = if topo.face(face_cone)?.is_reversed() {
-1.0
} else {
1.0
};
let big_r_s = sph.radius();
let c_s = sph.center();
let cone_apex = cone.apex();
let cone_axis = cone.axis();
let beta = cone.half_angle();
let to_sphere = c_s - cone_apex;
let to_sphere_v = Vec3::new(to_sphere.x(), to_sphere.y(), to_sphere.z());
let along = to_sphere_v.dot(cone_axis);
let perp = to_sphere_v - cone_axis * along;
if perp.length() > tol_lin {
return Ok(None);
}
if sph.z_axis().dot(cone_axis).abs() < 1.0 - tol_ang {
return Ok(None);
}
let (sin_b, cos_b) = beta.sin_cos();
if sin_b <= tol_lin || cos_b <= tol_lin {
return Ok(None);
}
let h_signed = along;
let q_s = big_r_s + s_sph * radius;
if q_s <= tol_lin {
return Ok(None);
}
let big_a = s_cone * radius + h_signed * cos_b;
let disc = q_s * q_s - big_a * big_a;
if disc <= tol_lin * tol_lin {
return Ok(None);
}
let disc_sqrt = disc.sqrt();
let c_root_a = -big_a * cos_b + sin_b * disc_sqrt;
let c_root_b = -big_a * cos_b - sin_b * disc_sqrt;
let edges = spine.edges();
let is_closed_spine = if edges.len() == 1 {
let e = topo.edge(edges[0])?;
e.start() == e.end()
} else {
false
};
let spine_len = spine.length();
if !is_closed_spine && spine_len < tol_lin {
return Ok(None);
}
let p_spine_sample = spine.evaluate(topo, 0.0)?;
let to_sample = p_spine_sample - c_s;
let to_sample_v = Vec3::new(to_sample.x(), to_sample.y(), to_sample.z());
let sample_axial = to_sample_v.dot(cone_axis);
let sample_radial_v = to_sample_v - cone_axis * sample_axial;
let sample_radial = sample_radial_v.length();
let cot_b = cos_b / sin_b;
let qa = 1.0 / (sin_b * sin_b);
let qb = 2.0 * h_signed * cot_b * cot_b;
let qc = h_signed * h_signed * cot_b * cot_b - big_r_s * big_r_s;
let q_disc = qb * qb - 4.0 * qa * qc;
if q_disc <= tol_lin * tol_lin {
return Ok(None);
}
let q_disc_sqrt = q_disc.sqrt();
let z_spine_root_a = (-qb + q_disc_sqrt) / (2.0 * qa);
let z_spine_root_b = (-qb - q_disc_sqrt) / (2.0 * qa);
let spine_match_tol = tol_lin * 1e3;
let spine_z = if (sample_axial - z_spine_root_a).abs() < spine_match_tol {
z_spine_root_a
} else if (sample_axial - z_spine_root_b).abs() < spine_match_tol {
z_spine_root_b
} else {
return Ok(None);
};
let r_spine = (spine_z + h_signed) * cot_b;
if r_spine <= tol_lin || (sample_radial - r_spine).abs() > spine_match_tol {
return Ok(None);
}
let z_b = if (c_root_a - spine_z).abs() <= (c_root_b - spine_z).abs() {
c_root_a
} else {
c_root_b
};
let r_t = (s_cone * radius + (z_b + h_signed) * cos_b) / sin_b;
if r_t <= tol_lin {
return Ok(None);
}
let major_radius = r_t;
let minor_radius = radius;
if major_radius < minor_radius - tol_lin {
return Ok(None);
}
let cone_x = cone.x_axis();
let ref_dir = cone_x;
let torus_center = c_s + cone_axis * z_b;
let torus = ToroidalSurface::with_axis_and_ref_dir(
torus_center,
major_radius,
minor_radius,
cone_axis,
ref_dir,
)?;
let spine_plane_center = c_s + cone_axis * spine_z;
let perp_y = cone_axis.cross(ref_dir).normalize()?;
let u_at = |p: Point3| {
let v = p - spine_plane_center;
perp_y.dot(v).atan2(ref_dir.dot(v))
};
let u_start = u_at(p_spine_sample);
let u_end = if is_closed_spine {
u_start + 2.0 * PI
} else {
let p_spine_end = spine.evaluate(topo, spine_len)?;
let u_end_raw = u_at(p_spine_end);
if u_end_raw > u_start {
u_end_raw
} else {
u_end_raw + 2.0 * PI
}
};
let sph_contact_axial = big_r_s * z_b / q_s;
let sph_contact_radial = big_r_s * major_radius / q_s;
let sph_contact_center = c_s + cone_axis * sph_contact_axial;
let contact_sph_circle = brepkit_math::curves::Circle3D::with_axes(
sph_contact_center,
cone_axis,
sph_contact_radial,
ref_dir,
perp_y,
)?;
let cone_contact_axial = z_b + s_cone * radius * cos_b;
let cone_contact_radial = major_radius - s_cone * radius * sin_b;
if cone_contact_radial <= tol_lin {
return Ok(None);
}
let cone_contact_center = c_s + cone_axis * cone_contact_axial;
let contact_cone_circle = brepkit_math::curves::Circle3D::with_axes(
cone_contact_center,
cone_axis,
cone_contact_radial,
ref_dir,
perp_y,
)?;
let contact_sph = circle_arc_to_nurbs(&contact_sph_circle, u_start, u_end)?;
let contact_cone = circle_arc_to_nurbs(&contact_cone_circle, u_start, u_end)?;
let sample_sph = contact_sph_circle.evaluate(u_start);
let (u_sph_start, v_sph) = ParametricSurface::project_point(sph, sample_sph);
let pcurve_sph = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_sph_start, v_sph),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let sample_cone = contact_cone_circle.evaluate(u_start);
let (u_cone_start, v_cone) = ParametricSurface::project_point(cone, sample_cone);
let pcurve_cone = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_cone_start, v_cone),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let p_sph_at = |u: f64| contact_sph_circle.evaluate(u);
let p_cone_at = |u: f64| contact_cone_circle.evaluate(u);
let section_at = |u: f64, t: f64| CircSection {
p1: p_sph_at(u),
p2: p_cone_at(u),
center: torus_center
+ ref_dir * (major_radius * u.cos())
+ perp_y * (major_radius * u.sin()),
radius,
uv1: (u_sph_start + (u - u_start), v_sph),
uv2: (u_cone_start + (u - u_start), v_cone),
t,
};
let section_start = section_at(u_start, 0.0);
let section_end = section_at(u_end, 1.0);
let stripe = Stripe {
spine: spine.clone(),
surface: FaceSurface::Torus(torus),
pcurve1: pcurve_sph,
pcurve2: pcurve_cone,
contact1: contact_sph,
contact2: contact_cone,
face1: face_sphere,
face2: face_cone,
sections: vec![section_start, section_end],
};
Ok(Some(StripeResult {
stripe,
new_edges: Vec::new(),
}))
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
pub fn sphere_sphere_chamfer(
s1: &brepkit_math::surfaces::SphericalSurface,
s2: &brepkit_math::surfaces::SphericalSurface,
spine: &Spine,
topo: &Topology,
d1: f64,
d2: f64,
face1: FaceId,
face2: FaceId,
) -> Result<Option<StripeResult>, BlendError> {
use brepkit_math::surfaces::ConicalSurface;
use std::f64::consts::PI;
let tol_lin = ANALYTIC_TOL_LIN;
let tol_ang = ANALYTIC_TOL_ANG;
if d1 <= tol_lin || d2 <= tol_lin {
return Ok(None);
}
let s1_signed: f64 = if topo.face(face1)?.is_reversed() {
-1.0
} else {
1.0
};
let s2_signed: f64 = if topo.face(face2)?.is_reversed() {
-1.0
} else {
1.0
};
let big_r1 = s1.radius();
let big_r2 = s2.radius();
let c1 = s1.center();
let c2 = s2.center();
let c1_to_c2 = c2 - c1;
let big_d = c1_to_c2.length();
if big_d <= tol_lin {
return Ok(None);
}
if big_d <= (big_r1 - big_r2).abs() + tol_lin || big_d >= big_r1 + big_r2 - tol_lin {
return Ok(None);
}
let axis = (c1_to_c2 * (1.0 / big_d)).normalize()?;
if s1.z_axis().dot(axis).abs() < 1.0 - tol_ang || s2.z_axis().dot(axis).abs() < 1.0 - tol_ang {
return Ok(None);
}
let a0 = (big_r1 * big_r1 - big_r2 * big_r2 + big_d * big_d) / (2.0 * big_d);
let r_p_sq = big_r1 * big_r1 - a0 * a0;
if r_p_sq <= tol_lin * tol_lin {
return Ok(None);
}
let r_p = r_p_sq.sqrt();
let delta1 = d1 / big_r1;
let (sin1, cos1) = delta1.sin_cos();
let p1_r = r_p * cos1 + s1_signed * a0 * sin1;
let p1_z_from_c1 = a0 * cos1 - s1_signed * r_p * sin1;
let delta2 = d2 / big_r2;
let (sin2, cos2) = delta2.sin_cos();
let p2_r = r_p * cos2 + s2_signed * (big_d - a0) * sin2;
let p2_z_from_c1 = big_d - (big_d - a0) * cos2 + s2_signed * r_p * sin2;
if p1_r <= tol_lin || p2_r <= tol_lin {
return Ok(None);
}
let dr = p2_r - p1_r;
let dz = p2_z_from_c1 - p1_z_from_c1;
if dr.abs() <= tol_lin || dz.abs() <= tol_lin {
return Ok(None);
}
let z_apex_from_c1 = p1_z_from_c1 - p1_r * dz / dr;
let mid_z_from_c1 = 0.5 * (p1_z_from_c1 + p2_z_from_c1);
let cone_axis = if mid_z_from_c1 > z_apex_from_c1 {
axis
} else {
-axis
};
let dz_from_apex = mid_z_from_c1 - z_apex_from_c1;
let r_avg = 0.5 * (p1_r + p2_r);
let cone_half_angle = (dz_from_apex.abs() / r_avg).atan();
if cone_half_angle <= 1e-3 || cone_half_angle >= std::f64::consts::FRAC_PI_2 - 1e-3 {
return Ok(None);
}
let chamfer_apex_pos = c1 + axis * z_apex_from_c1;
let edges = spine.edges();
let is_closed_spine = if edges.len() == 1 {
let e = topo.edge(edges[0])?;
e.start() == e.end()
} else {
false
};
let spine_len = spine.length();
if !is_closed_spine && spine_len < tol_lin {
return Ok(None);
}
let s1_x = s1.x_axis();
let s1_y = s1.y_axis();
let ref_dir = if s1_x.cross(axis).length() > tol_ang {
s1_x
} else {
s1_y
};
let chamfer_cone =
ConicalSurface::with_ref_dir(chamfer_apex_pos, cone_axis, cone_half_angle, ref_dir)?;
let spine_plane_center = c1 + axis * a0;
let perp_y = axis.cross(ref_dir).normalize()?;
let u_at = |p: Point3| {
let v = p - spine_plane_center;
perp_y.dot(v).atan2(ref_dir.dot(v))
};
let p_spine_start = spine.evaluate(topo, 0.0)?;
let u_start = u_at(p_spine_start);
let u_end = if is_closed_spine {
u_start + 2.0 * PI
} else {
let p_spine_end = spine.evaluate(topo, spine_len)?;
let u_end_raw = u_at(p_spine_end);
if u_end_raw > u_start {
u_end_raw
} else {
u_end_raw + 2.0 * PI
}
};
let contact1_center = c1 + axis * p1_z_from_c1;
let contact1_circle =
brepkit_math::curves::Circle3D::with_axes(contact1_center, axis, p1_r, ref_dir, perp_y)?;
let contact2_center = c1 + axis * p2_z_from_c1;
let contact2_circle =
brepkit_math::curves::Circle3D::with_axes(contact2_center, axis, p2_r, ref_dir, perp_y)?;
let contact1 = circle_arc_to_nurbs(&contact1_circle, u_start, u_end)?;
let contact2 = circle_arc_to_nurbs(&contact2_circle, u_start, u_end)?;
let sample1 = contact1_circle.evaluate(u_start);
let v1 = ParametricSurface::project_point(s1, sample1).1;
let pcurve1 = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_start, v1),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let sample2 = contact2_circle.evaluate(u_start);
let v2 = ParametricSurface::project_point(s2, sample2).1;
let pcurve2 = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_start, v2),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let p1_at = |u: f64| contact1_circle.evaluate(u);
let p2_at = |u: f64| contact2_circle.evaluate(u);
let section_at = |u: f64, t: f64| {
let p1 = p1_at(u);
let p2 = p2_at(u);
let mid = midpoint_3d(p1, p2);
CircSection {
p1,
p2,
center: mid,
radius: (p1 - p2).length() * 0.5,
uv1: (u, v1),
uv2: (u, v2),
t,
}
};
let section_start = section_at(u_start, 0.0);
let section_end = section_at(u_end, 1.0);
let stripe = Stripe {
spine: spine.clone(),
surface: FaceSurface::Cone(chamfer_cone),
pcurve1,
pcurve2,
contact1,
contact2,
face1,
face2,
sections: vec![section_start, section_end],
};
Ok(Some(StripeResult {
stripe,
new_edges: Vec::new(),
}))
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
pub fn sphere_cylinder_chamfer(
sph: &brepkit_math::surfaces::SphericalSurface,
cyl: &brepkit_math::surfaces::CylindricalSurface,
spine: &Spine,
topo: &Topology,
d1: f64,
d2: f64,
face_sphere: FaceId,
face_cyl: FaceId,
) -> Result<Option<StripeResult>, BlendError> {
use brepkit_math::surfaces::ConicalSurface;
use std::f64::consts::PI;
let tol_lin = ANALYTIC_TOL_LIN;
let tol_ang = ANALYTIC_TOL_ANG;
if d1 <= tol_lin || d2 <= tol_lin {
return Ok(None);
}
let s_sph: f64 = if topo.face(face_sphere)?.is_reversed() {
-1.0
} else {
1.0
};
let s_cyl: f64 = if topo.face(face_cyl)?.is_reversed() {
-1.0
} else {
1.0
};
let big_r_s = sph.radius();
let r_c = cyl.radius();
let c_s = sph.center();
let cyl_origin = cyl.origin();
let cyl_axis = cyl.axis();
let to_sphere = c_s - cyl_origin;
let to_sphere_v = Vec3::new(to_sphere.x(), to_sphere.y(), to_sphere.z());
let along = to_sphere_v.dot(cyl_axis);
let perp = to_sphere_v - cyl_axis * along;
if perp.length() > tol_lin {
return Ok(None);
}
if sph.z_axis().dot(cyl_axis).abs() < 1.0 - tol_ang {
return Ok(None);
}
if r_c >= big_r_s - tol_lin {
return Ok(None);
}
let h_s_sq = big_r_s * big_r_s - r_c * r_c;
let h_s = h_s_sq.sqrt();
let edges = spine.edges();
let is_closed_spine = if edges.len() == 1 {
let e = topo.edge(edges[0])?;
e.start() == e.end()
} else {
false
};
let spine_len = spine.length();
if !is_closed_spine && spine_len < tol_lin {
return Ok(None);
}
let p_spine_sample = spine.evaluate(topo, 0.0)?;
let to_sample = p_spine_sample - c_s;
let to_sample_v = Vec3::new(to_sample.x(), to_sample.y(), to_sample.z());
let sample_axial = to_sample_v.dot(cyl_axis);
let sample_radial_v = to_sample_v - cyl_axis * sample_axial;
let sample_radial = sample_radial_v.length();
if (sample_axial.abs() - h_s).abs() > tol_lin || (sample_radial - r_c).abs() > tol_lin {
return Ok(None);
}
let spine_sign = if sample_axial >= 0.0 { 1.0 } else { -1.0 };
let a_spine = spine_sign * h_s;
let delta = d1 / big_r_s;
let (sin_d, cos_d) = delta.sin_cos();
let r_sph = r_c * cos_d - s_sph * h_s * sin_d;
let z_sph = a_spine * cos_d + s_sph * r_c * spine_sign * sin_d;
if r_sph <= tol_lin {
return Ok(None);
}
let r_cyl = r_c;
let z_cyl = a_spine - spine_sign * s_cyl * d2;
let dr = r_cyl - r_sph;
let dz = z_cyl - z_sph;
if dr.abs() <= tol_lin || dz.abs() <= tol_lin {
return Ok(None);
}
let z_apex = z_sph - r_sph * dz / dr;
let mid_z = 0.5 * (z_sph + z_cyl);
let chamfer_axis = if mid_z > z_apex { cyl_axis } else { -cyl_axis };
let r_avg = 0.5 * (r_sph + r_cyl);
let cone_half_angle = ((mid_z - z_apex).abs() / r_avg).atan();
if cone_half_angle <= 1e-3 || cone_half_angle >= std::f64::consts::FRAC_PI_2 - 1e-3 {
return Ok(None);
}
let chamfer_apex_pos = c_s + cyl_axis * z_apex;
let ref_dir = cyl.x_axis();
let chamfer_cone =
ConicalSurface::with_ref_dir(chamfer_apex_pos, chamfer_axis, cone_half_angle, ref_dir)?;
let spine_plane_center = c_s + cyl_axis * a_spine;
let perp_y = cyl_axis.cross(ref_dir).normalize()?;
let u_at = |p: Point3| {
let v = p - spine_plane_center;
perp_y.dot(v).atan2(ref_dir.dot(v))
};
let u_start = u_at(p_spine_sample);
let u_end = if is_closed_spine {
u_start + 2.0 * PI
} else {
let p_spine_end = spine.evaluate(topo, spine_len)?;
let u_end_raw = u_at(p_spine_end);
if u_end_raw > u_start {
u_end_raw
} else {
u_end_raw + 2.0 * PI
}
};
let sph_contact_center = c_s + cyl_axis * z_sph;
let contact_sph_circle = brepkit_math::curves::Circle3D::with_axes(
sph_contact_center,
cyl_axis,
r_sph,
ref_dir,
perp_y,
)?;
let cyl_contact_center = c_s + cyl_axis * z_cyl;
let contact_cyl_circle = brepkit_math::curves::Circle3D::with_axes(
cyl_contact_center,
cyl_axis,
r_cyl,
ref_dir,
perp_y,
)?;
let contact_sph = circle_arc_to_nurbs(&contact_sph_circle, u_start, u_end)?;
let contact_cyl = circle_arc_to_nurbs(&contact_cyl_circle, u_start, u_end)?;
let sample_sph = contact_sph_circle.evaluate(u_start);
let (u_sph_start, v_sph) = ParametricSurface::project_point(sph, sample_sph);
let pcurve_sph = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_sph_start, v_sph),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let sample_cyl = contact_cyl_circle.evaluate(u_start);
let u_cyl_start = ParametricSurface::project_point(cyl, sample_cyl).0;
let v_cyl = cyl_v_at_point(cyl, sample_cyl);
let pcurve_cyl = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_cyl_start, v_cyl),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let p_sph_at = |u: f64| contact_sph_circle.evaluate(u);
let p_cyl_at = |u: f64| contact_cyl_circle.evaluate(u);
let section_at = |u: f64, t: f64| {
let p1 = p_sph_at(u);
let p2 = p_cyl_at(u);
let mid = midpoint_3d(p1, p2);
CircSection {
p1,
p2,
center: mid,
radius: (p1 - p2).length() * 0.5,
uv1: (u_sph_start + (u - u_start), v_sph),
uv2: (u_cyl_start + (u - u_start), v_cyl),
t,
}
};
let section_start = section_at(u_start, 0.0);
let section_end = section_at(u_end, 1.0);
let stripe = Stripe {
spine: spine.clone(),
surface: FaceSurface::Cone(chamfer_cone),
pcurve1: pcurve_sph,
pcurve2: pcurve_cyl,
contact1: contact_sph,
contact2: contact_cyl,
face1: face_sphere,
face2: face_cyl,
sections: vec![section_start, section_end],
};
Ok(Some(StripeResult {
stripe,
new_edges: Vec::new(),
}))
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
pub fn sphere_cone_chamfer(
sph: &brepkit_math::surfaces::SphericalSurface,
cone: &brepkit_math::surfaces::ConicalSurface,
spine: &Spine,
topo: &Topology,
d1: f64,
d2: f64,
face_sphere: FaceId,
face_cone: FaceId,
) -> Result<Option<StripeResult>, BlendError> {
use brepkit_math::surfaces::ConicalSurface;
use std::f64::consts::PI;
let tol_lin = ANALYTIC_TOL_LIN;
let tol_ang = ANALYTIC_TOL_ANG;
if d1 <= tol_lin || d2 <= tol_lin {
return Ok(None);
}
let s_sph: f64 = if topo.face(face_sphere)?.is_reversed() {
-1.0
} else {
1.0
};
let s_cone: f64 = if topo.face(face_cone)?.is_reversed() {
-1.0
} else {
1.0
};
let big_r_s = sph.radius();
let c_s = sph.center();
let cone_apex = cone.apex();
let cone_axis = cone.axis();
let beta = cone.half_angle();
let to_sphere = c_s - cone_apex;
let to_sphere_v = Vec3::new(to_sphere.x(), to_sphere.y(), to_sphere.z());
let along = to_sphere_v.dot(cone_axis);
let perp = to_sphere_v - cone_axis * along;
if perp.length() > tol_lin {
return Ok(None);
}
if sph.z_axis().dot(cone_axis).abs() < 1.0 - tol_ang {
return Ok(None);
}
let (sin_b, cos_b) = beta.sin_cos();
if sin_b <= tol_lin || cos_b <= tol_lin {
return Ok(None);
}
let cot_b = cos_b / sin_b;
let h_signed = along;
let qa = 1.0 / (sin_b * sin_b);
let qb = 2.0 * h_signed * cot_b * cot_b;
let qc = h_signed * h_signed * cot_b * cot_b - big_r_s * big_r_s;
let q_disc = qb * qb - 4.0 * qa * qc;
if q_disc <= tol_lin * tol_lin {
return Ok(None);
}
let q_disc_sqrt = q_disc.sqrt();
let z_spine_root_a = (-qb + q_disc_sqrt) / (2.0 * qa);
let z_spine_root_b = (-qb - q_disc_sqrt) / (2.0 * qa);
let edges = spine.edges();
let is_closed_spine = if edges.len() == 1 {
let e = topo.edge(edges[0])?;
e.start() == e.end()
} else {
false
};
let spine_len = spine.length();
if !is_closed_spine && spine_len < tol_lin {
return Ok(None);
}
let p_spine_sample = spine.evaluate(topo, 0.0)?;
let to_sample = p_spine_sample - c_s;
let to_sample_v = Vec3::new(to_sample.x(), to_sample.y(), to_sample.z());
let sample_axial = to_sample_v.dot(cone_axis);
let sample_radial_v = to_sample_v - cone_axis * sample_axial;
let sample_radial = sample_radial_v.length();
let spine_match_tol = tol_lin * 1e3;
let spine_z = if (sample_axial - z_spine_root_a).abs() < spine_match_tol {
z_spine_root_a
} else if (sample_axial - z_spine_root_b).abs() < spine_match_tol {
z_spine_root_b
} else {
return Ok(None);
};
let r_spine = (spine_z + h_signed) * cot_b;
if r_spine <= tol_lin || (sample_radial - r_spine).abs() > spine_match_tol {
return Ok(None);
}
if spine_z + h_signed <= tol_lin {
return Ok(None);
}
let spine_sign = if spine_z >= 0.0 { 1.0 } else { -1.0 };
let sphere_arm_sign = -spine_sign * s_sph;
let delta1 = d1 / big_r_s;
let (sin_d1, cos_d1) = delta1.sin_cos();
let r_sph = r_spine * cos_d1 + sphere_arm_sign * spine_z * sin_d1;
let z_sph = spine_z * cos_d1 - sphere_arm_sign * r_spine * sin_d1;
if r_sph <= tol_lin {
return Ok(None);
}
let r_cone = r_spine - s_cone * d2 * cos_b;
let z_cone = spine_z - s_cone * d2 * sin_b;
if r_cone <= tol_lin {
return Ok(None);
}
let dr = r_cone - r_sph;
let dz = z_cone - z_sph;
if dr.abs() <= tol_lin || dz.abs() <= tol_lin {
return Ok(None);
}
let z_apex_chamfer = z_sph - r_sph * dz / dr;
let mid_z = 0.5 * (z_sph + z_cone);
let chamfer_axis = if mid_z > z_apex_chamfer {
cone_axis
} else {
-cone_axis
};
let r_avg = 0.5 * (r_sph + r_cone);
let cone_half_angle = ((mid_z - z_apex_chamfer).abs() / r_avg).atan();
if cone_half_angle <= 1e-3 || cone_half_angle >= std::f64::consts::FRAC_PI_2 - 1e-3 {
return Ok(None);
}
let chamfer_apex_pos = c_s + cone_axis * z_apex_chamfer;
let ref_dir = cone.x_axis();
let chamfer_cone =
ConicalSurface::with_ref_dir(chamfer_apex_pos, chamfer_axis, cone_half_angle, ref_dir)?;
let spine_plane_center = c_s + cone_axis * spine_z;
let perp_y = cone_axis.cross(ref_dir).normalize()?;
let u_at = |p: Point3| {
let v = p - spine_plane_center;
perp_y.dot(v).atan2(ref_dir.dot(v))
};
let u_start = u_at(p_spine_sample);
let u_end = if is_closed_spine {
u_start + 2.0 * PI
} else {
let p_spine_end = spine.evaluate(topo, spine_len)?;
let u_end_raw = u_at(p_spine_end);
if u_end_raw > u_start {
u_end_raw
} else {
u_end_raw + 2.0 * PI
}
};
let sph_contact_center = c_s + cone_axis * z_sph;
let contact_sph_circle = brepkit_math::curves::Circle3D::with_axes(
sph_contact_center,
cone_axis,
r_sph,
ref_dir,
perp_y,
)?;
let cone_contact_center = c_s + cone_axis * z_cone;
let contact_cone_circle = brepkit_math::curves::Circle3D::with_axes(
cone_contact_center,
cone_axis,
r_cone,
ref_dir,
perp_y,
)?;
let contact_sph = circle_arc_to_nurbs(&contact_sph_circle, u_start, u_end)?;
let contact_cone = circle_arc_to_nurbs(&contact_cone_circle, u_start, u_end)?;
let sample_sph = contact_sph_circle.evaluate(u_start);
let (u_sph_start, v_sph) = ParametricSurface::project_point(sph, sample_sph);
let pcurve_sph = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_sph_start, v_sph),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let sample_cone = contact_cone_circle.evaluate(u_start);
let (u_cone_start, v_cone) = ParametricSurface::project_point(cone, sample_cone);
let pcurve_cone = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_cone_start, v_cone),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let p_sph_at = |u: f64| contact_sph_circle.evaluate(u);
let p_cone_at = |u: f64| contact_cone_circle.evaluate(u);
let section_at = |u: f64, t: f64| {
let p1 = p_sph_at(u);
let p2 = p_cone_at(u);
let mid = midpoint_3d(p1, p2);
CircSection {
p1,
p2,
center: mid,
radius: (p1 - p2).length() * 0.5,
uv1: (u_sph_start + (u - u_start), v_sph),
uv2: (u_cone_start + (u - u_start), v_cone),
t,
}
};
let section_start = section_at(u_start, 0.0);
let section_end = section_at(u_end, 1.0);
let stripe = Stripe {
spine: spine.clone(),
surface: FaceSurface::Cone(chamfer_cone),
pcurve1: pcurve_sph,
pcurve2: pcurve_cone,
contact1: contact_sph,
contact2: contact_cone,
face1: face_sphere,
face2: face_cone,
sections: vec![section_start, section_end],
};
Ok(Some(StripeResult {
stripe,
new_edges: Vec::new(),
}))
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
pub fn cylinder_cylinder_fillet(
cyl1: &brepkit_math::surfaces::CylindricalSurface,
cyl2: &brepkit_math::surfaces::CylindricalSurface,
spine: &Spine,
topo: &Topology,
radius: f64,
face1: FaceId,
face2: FaceId,
) -> Result<Option<StripeResult>, BlendError> {
use brepkit_math::surfaces::CylindricalSurface;
let tol_lin = ANALYTIC_TOL_LIN;
let tol_ang = ANALYTIC_TOL_ANG;
if radius <= tol_lin {
return Ok(None);
}
let s1: f64 = if topo.face(face1)?.is_reversed() {
-1.0
} else {
1.0
};
let s2: f64 = if topo.face(face2)?.is_reversed() {
-1.0
} else {
1.0
};
let r1 = cyl1.radius();
let r2 = cyl2.radius();
let a1 = cyl1.axis();
let a2 = cyl2.axis();
if a1.dot(a2).abs() < 1.0 - tol_ang {
return Ok(None);
}
let a_cyl = a1;
let o1 = cyl1.origin();
let o2 = cyl2.origin();
let d_axes = o2 - o1;
let d_axes_v = Vec3::new(d_axes.x(), d_axes.y(), d_axes.z());
let along = d_axes_v.dot(a_cyl);
let d_perp = d_axes_v - a_cyl * along;
let big_d = d_perp.length();
if big_d <= tol_lin {
return Ok(None);
}
if big_d <= (r1 - r2).abs() + tol_lin || big_d >= r1 + r2 - tol_lin {
return Ok(None);
}
let x_hat = d_perp * (1.0 / big_d);
let y_hat = a_cyl.cross(x_hat).normalize()?;
let x_spine = (r1 * r1 - r2 * r2 + big_d * big_d) / (2.0 * big_d);
let y_spine_sq = r1 * r1 - x_spine * x_spine;
if y_spine_sq <= tol_lin * tol_lin {
return Ok(None);
}
let y_spine_abs = y_spine_sq.sqrt();
let edges = spine.edges();
if edges.len() == 1 {
let e = topo.edge(edges[0])?;
if e.start() == e.end() {
return Ok(None);
}
}
let spine_len = spine.length();
if spine_len < tol_lin {
return Ok(None);
}
let p_spine_sample = spine.evaluate(topo, 0.0)?;
let to_sample = p_spine_sample - o1;
let to_sample_v = Vec3::new(to_sample.x(), to_sample.y(), to_sample.z());
let sample_x = to_sample_v.dot(x_hat);
let sample_y = to_sample_v.dot(y_hat);
let spine_match_tol = tol_lin * 1e3;
if (sample_x - x_spine).abs() > spine_match_tol {
return Ok(None);
}
let y_spine = if (sample_y - y_spine_abs).abs() < spine_match_tol {
y_spine_abs
} else if (sample_y + y_spine_abs).abs() < spine_match_tol {
-y_spine_abs
} else {
return Ok(None);
};
let y_sign = if y_spine >= 0.0 { 1.0 } else { -1.0 };
let q1 = r1 + s1 * radius;
let q2 = r2 + s2 * radius;
if q1 <= tol_lin || q2 <= tol_lin {
return Ok(None);
}
let x_ball = (q1 * q1 - q2 * q2 + big_d * big_d) / (2.0 * big_d);
let y_ball_sq = q1 * q1 - x_ball * x_ball;
if y_ball_sq <= tol_lin * tol_lin {
return Ok(None);
}
let y_ball = y_sign * y_ball_sq.sqrt();
let p_spine_start = p_spine_sample;
let spine_tangent = spine.tangent(topo, 0.0)?;
if spine_tangent.dot(a_cyl).abs() < 1.0 - tol_ang {
return Ok(None);
}
let p_spine_end = spine.evaluate(topo, spine_len)?;
let to_start = p_spine_start - o1;
let to_start_v = Vec3::new(to_start.x(), to_start.y(), to_start.z());
let z_start = to_start_v.dot(a_cyl);
let to_end = p_spine_end - o1;
let to_end_v = Vec3::new(to_end.x(), to_end.y(), to_end.z());
let z_end = to_end_v.dot(a_cyl);
let ball_line_origin = o1 + x_hat * x_ball + y_hat * y_ball + a_cyl * z_start;
let fillet_cyl = CylindricalSurface::new(ball_line_origin, a_cyl, radius)?;
let c1_x = r1 * x_ball / q1;
let c1_y = r1 * y_ball / q1;
let c1_start = o1 + x_hat * c1_x + y_hat * c1_y + a_cyl * z_start;
let c1_end = o1 + x_hat * c1_x + y_hat * c1_y + a_cyl * z_end;
let c2_x = big_d + r2 * (x_ball - big_d) / q2;
let c2_y = r2 * y_ball / q2;
let c2_start = o1 + x_hat * c2_x + y_hat * c2_y + a_cyl * z_start;
let c2_end = o1 + x_hat * c2_x + y_hat * c2_y + a_cyl * z_end;
let contact1 = nurbs_line(c1_start, c1_end)?;
let contact2 = nurbs_line(c2_start, c2_end)?;
let u1 = ParametricSurface::project_point(cyl1, c1_start).0;
let v1_start = cyl_v_at_point(cyl1, c1_start);
let v1_end = cyl_v_at_point(cyl1, c1_end);
let pcurve1 = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u1, v1_start),
brepkit_math::vec::Vec2::new(0.0, v1_end - v1_start),
)?);
let u2 = ParametricSurface::project_point(cyl2, c2_start).0;
let v2_start = cyl_v_at_point(cyl2, c2_start);
let v2_end = cyl_v_at_point(cyl2, c2_end);
let pcurve2 = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u2, v2_start),
brepkit_math::vec::Vec2::new(0.0, v2_end - v2_start),
)?);
let section_start = CircSection {
p1: c1_start,
p2: c2_start,
center: ball_line_origin,
radius,
uv1: (u1, v1_start),
uv2: (u2, v2_start),
t: 0.0,
};
let ball_end = ball_line_origin + a_cyl * (z_end - z_start);
let section_end = CircSection {
p1: c1_end,
p2: c2_end,
center: ball_end,
radius,
uv1: (u1, v1_end),
uv2: (u2, v2_end),
t: 1.0,
};
let stripe = Stripe {
spine: spine.clone(),
surface: FaceSurface::Cylinder(fillet_cyl),
pcurve1,
pcurve2,
contact1,
contact2,
face1,
face2,
sections: vec![section_start, section_end],
};
Ok(Some(StripeResult {
stripe,
new_edges: Vec::new(),
}))
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
pub fn cone_cone_coaxial_fillet(
cone1: &brepkit_math::surfaces::ConicalSurface,
cone2: &brepkit_math::surfaces::ConicalSurface,
spine: &Spine,
topo: &Topology,
radius: f64,
face1: FaceId,
face2: FaceId,
) -> Result<Option<StripeResult>, BlendError> {
use brepkit_math::surfaces::ToroidalSurface;
use std::f64::consts::PI;
let tol_lin = ANALYTIC_TOL_LIN;
let tol_ang = ANALYTIC_TOL_ANG;
if radius <= tol_lin {
return Ok(None);
}
let s1: f64 = if topo.face(face1)?.is_reversed() {
-1.0
} else {
1.0
};
let s2: f64 = if topo.face(face2)?.is_reversed() {
-1.0
} else {
1.0
};
let beta1 = cone1.half_angle();
let beta2 = cone2.half_angle();
let apex1 = cone1.apex();
let apex2 = cone2.apex();
let axis1 = cone1.axis();
let axis2 = cone2.axis();
if axis1.dot(axis2) < 1.0 - tol_ang {
return Ok(None);
}
let a_cone = axis1;
let to_apex2 = apex2 - apex1;
let to_apex2_v = Vec3::new(to_apex2.x(), to_apex2.y(), to_apex2.z());
let along = to_apex2_v.dot(a_cone);
let perp = to_apex2_v - a_cone * along;
if perp.length() > tol_lin {
return Ok(None);
}
let h_2 = along;
let (sin_b1, cos_b1) = beta1.sin_cos();
let (sin_b2, cos_b2) = beta2.sin_cos();
let sin_diff = sin_b1 - sin_b2;
let sin_minus = (beta1 - beta2).sin();
if sin_minus.abs() <= tol_ang {
return Ok(None);
}
let z_b = (h_2 * cos_b2 * sin_b1 + radius * (s1 * sin_b2 - s2 * sin_b1)) / sin_minus;
let r_t = (z_b * (cos_b1 - cos_b2) + h_2 * cos_b2 + (s1 - s2) * radius) / sin_diff;
if r_t <= tol_lin {
return Ok(None);
}
let major_radius = r_t;
let minor_radius = radius;
if major_radius < minor_radius - tol_lin {
return Ok(None);
}
let z_spine = h_2 * cos_b2 * sin_b1 / sin_minus;
let cot_b1 = cos_b1 / sin_b1;
let r_spine = z_spine * cot_b1;
if r_spine <= tol_lin {
return Ok(None);
}
let edges = spine.edges();
let is_closed_spine = if edges.len() == 1 {
let e = topo.edge(edges[0])?;
e.start() == e.end()
} else {
false
};
let spine_len = spine.length();
if !is_closed_spine && spine_len < tol_lin {
return Ok(None);
}
let p_spine_sample = spine.evaluate(topo, 0.0)?;
let to_sample = p_spine_sample - apex1;
let to_sample_v = Vec3::new(to_sample.x(), to_sample.y(), to_sample.z());
let sample_axial = to_sample_v.dot(a_cone);
let sample_radial_v = to_sample_v - a_cone * sample_axial;
let sample_radial = sample_radial_v.length();
let spine_match_tol = tol_lin * 1e3;
if (sample_axial - z_spine).abs() > spine_match_tol
|| (sample_radial - r_spine).abs() > spine_match_tol
{
return Ok(None);
}
let ref_dir = cone1.x_axis();
let torus_center = apex1 + a_cone * z_b;
let torus = ToroidalSurface::with_axis_and_ref_dir(
torus_center,
major_radius,
minor_radius,
a_cone,
ref_dir,
)?;
let spine_plane_center = apex1 + a_cone * z_spine;
let perp_y = a_cone.cross(ref_dir).normalize()?;
let u_at = |p: Point3| {
let v = p - spine_plane_center;
perp_y.dot(v).atan2(ref_dir.dot(v))
};
let u_start = u_at(p_spine_sample);
let u_end = if is_closed_spine {
u_start + 2.0 * PI
} else {
let p_spine_end = spine.evaluate(topo, spine_len)?;
let u_end_raw = u_at(p_spine_end);
if u_end_raw > u_start {
u_end_raw
} else {
u_end_raw + 2.0 * PI
}
};
let cone1_contact_axial = z_b + s1 * radius * cos_b1;
let cone1_contact_radial = major_radius - s1 * radius * sin_b1;
let cone2_contact_axial = z_b + s2 * radius * cos_b2;
let cone2_contact_radial = major_radius - s2 * radius * sin_b2;
if cone1_contact_radial <= tol_lin || cone2_contact_radial <= tol_lin {
return Ok(None);
}
let cone1_contact_center = apex1 + a_cone * cone1_contact_axial;
let contact1_circle = brepkit_math::curves::Circle3D::with_axes(
cone1_contact_center,
a_cone,
cone1_contact_radial,
ref_dir,
perp_y,
)?;
let cone2_contact_center = apex1 + a_cone * cone2_contact_axial;
let contact2_circle = brepkit_math::curves::Circle3D::with_axes(
cone2_contact_center,
a_cone,
cone2_contact_radial,
ref_dir,
perp_y,
)?;
let contact1 = circle_arc_to_nurbs(&contact1_circle, u_start, u_end)?;
let contact2 = circle_arc_to_nurbs(&contact2_circle, u_start, u_end)?;
let sample_c1 = contact1_circle.evaluate(u_start);
let (u_c1_start, v_c1) = ParametricSurface::project_point(cone1, sample_c1);
let pcurve1 = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_c1_start, v_c1),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let sample_c2 = contact2_circle.evaluate(u_start);
let (u_c2_start, v_c2) = ParametricSurface::project_point(cone2, sample_c2);
let pcurve2 = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_c2_start, v_c2),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let p1_at = |u: f64| contact1_circle.evaluate(u);
let p2_at = |u: f64| contact2_circle.evaluate(u);
let section_at = |u: f64, t: f64| CircSection {
p1: p1_at(u),
p2: p2_at(u),
center: torus_center
+ ref_dir * (major_radius * u.cos())
+ perp_y * (major_radius * u.sin()),
radius,
uv1: (u_c1_start + (u - u_start), v_c1),
uv2: (u_c2_start + (u - u_start), v_c2),
t,
};
let section_start = section_at(u_start, 0.0);
let section_end = section_at(u_end, 1.0);
let stripe = Stripe {
spine: spine.clone(),
surface: FaceSurface::Torus(torus),
pcurve1,
pcurve2,
contact1,
contact2,
face1,
face2,
sections: vec![section_start, section_end],
};
Ok(Some(StripeResult {
stripe,
new_edges: Vec::new(),
}))
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
pub fn cone_cone_coaxial_chamfer(
cone1: &brepkit_math::surfaces::ConicalSurface,
cone2: &brepkit_math::surfaces::ConicalSurface,
spine: &Spine,
topo: &Topology,
d1: f64,
d2: f64,
face1: FaceId,
face2: FaceId,
) -> Result<Option<StripeResult>, BlendError> {
use brepkit_math::surfaces::ConicalSurface;
use std::f64::consts::PI;
let tol_lin = ANALYTIC_TOL_LIN;
let tol_ang = ANALYTIC_TOL_ANG;
if d1 <= tol_lin || d2 <= tol_lin {
return Ok(None);
}
let s1: f64 = if topo.face(face1)?.is_reversed() {
-1.0
} else {
1.0
};
let s2: f64 = if topo.face(face2)?.is_reversed() {
-1.0
} else {
1.0
};
let beta1 = cone1.half_angle();
let beta2 = cone2.half_angle();
let apex1 = cone1.apex();
let apex2 = cone2.apex();
let axis1 = cone1.axis();
let axis2 = cone2.axis();
if axis1.dot(axis2) < 1.0 - tol_ang {
return Ok(None);
}
let a_cone = axis1;
let to_apex2 = apex2 - apex1;
let to_apex2_v = Vec3::new(to_apex2.x(), to_apex2.y(), to_apex2.z());
let along = to_apex2_v.dot(a_cone);
let perp = to_apex2_v - a_cone * along;
if perp.length() > tol_lin {
return Ok(None);
}
let h_2 = along;
let (sin_b1, cos_b1) = beta1.sin_cos();
let (sin_b2, cos_b2) = beta2.sin_cos();
let sin_minus = (beta1 - beta2).sin();
if sin_minus.abs() <= tol_ang {
return Ok(None);
}
let z_spine = h_2 * cos_b2 * sin_b1 / sin_minus;
let cot_b1 = cos_b1 / sin_b1;
let r_spine = z_spine * cot_b1;
if r_spine <= tol_lin {
return Ok(None);
}
let edges = spine.edges();
let is_closed_spine = if edges.len() == 1 {
let e = topo.edge(edges[0])?;
e.start() == e.end()
} else {
false
};
let spine_len = spine.length();
if !is_closed_spine && spine_len < tol_lin {
return Ok(None);
}
let p_spine_sample = spine.evaluate(topo, 0.0)?;
let to_sample = p_spine_sample - apex1;
let to_sample_v = Vec3::new(to_sample.x(), to_sample.y(), to_sample.z());
let sample_axial = to_sample_v.dot(a_cone);
let sample_radial_v = to_sample_v - a_cone * sample_axial;
let sample_radial = sample_radial_v.length();
let spine_match_tol = tol_lin * 1e3;
if (sample_axial - z_spine).abs() > spine_match_tol
|| (sample_radial - r_spine).abs() > spine_match_tol
{
return Ok(None);
}
let r_c1 = r_spine - s1 * d1 * cos_b1;
let z_c1 = z_spine - s1 * d1 * sin_b1;
let r_c2 = r_spine + s2 * d2 * cos_b2;
let z_c2 = z_spine + s2 * d2 * sin_b2;
if r_c1 <= tol_lin || r_c2 <= tol_lin {
return Ok(None);
}
let dr = r_c2 - r_c1;
let dz = z_c2 - z_c1;
if dr.abs() <= tol_lin {
return Ok(None);
}
let z_apex_chamfer = z_c1 - r_c1 * dz / dr;
let mid_z = 0.5 * (z_c1 + z_c2);
let chamfer_axis = if mid_z > z_apex_chamfer {
a_cone
} else {
-a_cone
};
let r_avg = 0.5 * (r_c1 + r_c2);
let cone_half_angle = ((mid_z - z_apex_chamfer).abs() / r_avg).atan();
if cone_half_angle <= 1e-3 || cone_half_angle >= std::f64::consts::FRAC_PI_2 - 1e-3 {
return Ok(None);
}
let chamfer_apex_pos = apex1 + a_cone * z_apex_chamfer;
let ref_dir = cone1.x_axis();
let chamfer_cone =
ConicalSurface::with_ref_dir(chamfer_apex_pos, chamfer_axis, cone_half_angle, ref_dir)?;
let spine_plane_center = apex1 + a_cone * z_spine;
let perp_y = a_cone.cross(ref_dir).normalize()?;
let u_at = |p: Point3| {
let v = p - spine_plane_center;
perp_y.dot(v).atan2(ref_dir.dot(v))
};
let u_start = u_at(p_spine_sample);
let u_end = if is_closed_spine {
u_start + 2.0 * PI
} else {
let p_spine_end = spine.evaluate(topo, spine_len)?;
let u_end_raw = u_at(p_spine_end);
if u_end_raw > u_start {
u_end_raw
} else {
u_end_raw + 2.0 * PI
}
};
let c1_center = apex1 + a_cone * z_c1;
let contact1_circle =
brepkit_math::curves::Circle3D::with_axes(c1_center, a_cone, r_c1, ref_dir, perp_y)?;
let c2_center = apex1 + a_cone * z_c2;
let contact2_circle =
brepkit_math::curves::Circle3D::with_axes(c2_center, a_cone, r_c2, ref_dir, perp_y)?;
let contact1 = circle_arc_to_nurbs(&contact1_circle, u_start, u_end)?;
let contact2 = circle_arc_to_nurbs(&contact2_circle, u_start, u_end)?;
let sample_c1 = contact1_circle.evaluate(u_start);
let (u_c1_start, v_c1) = ParametricSurface::project_point(cone1, sample_c1);
let pcurve1 = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_c1_start, v_c1),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let sample_c2 = contact2_circle.evaluate(u_start);
let (u_c2_start, v_c2) = ParametricSurface::project_point(cone2, sample_c2);
let pcurve2 = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u_c2_start, v_c2),
brepkit_math::vec::Vec2::new(u_end - u_start, 0.0),
)?);
let p1_at = |u: f64| contact1_circle.evaluate(u);
let p2_at = |u: f64| contact2_circle.evaluate(u);
let section_at = |u: f64, t: f64| {
let p1 = p1_at(u);
let p2 = p2_at(u);
let mid = midpoint_3d(p1, p2);
CircSection {
p1,
p2,
center: mid,
radius: (p1 - p2).length() * 0.5,
uv1: (u_c1_start + (u - u_start), v_c1),
uv2: (u_c2_start + (u - u_start), v_c2),
t,
}
};
let section_start = section_at(u_start, 0.0);
let section_end = section_at(u_end, 1.0);
let stripe = Stripe {
spine: spine.clone(),
surface: FaceSurface::Cone(chamfer_cone),
pcurve1,
pcurve2,
contact1,
contact2,
face1,
face2,
sections: vec![section_start, section_end],
};
Ok(Some(StripeResult {
stripe,
new_edges: Vec::new(),
}))
}
#[allow(clippy::too_many_arguments, clippy::too_many_lines)]
pub fn cylinder_cylinder_chamfer(
cyl1: &brepkit_math::surfaces::CylindricalSurface,
cyl2: &brepkit_math::surfaces::CylindricalSurface,
spine: &Spine,
topo: &Topology,
d1: f64,
d2: f64,
face1: FaceId,
face2: FaceId,
) -> Result<Option<StripeResult>, BlendError> {
let tol_lin = ANALYTIC_TOL_LIN;
let tol_ang = ANALYTIC_TOL_ANG;
if d1 <= tol_lin || d2 <= tol_lin {
return Ok(None);
}
let s1: f64 = if topo.face(face1)?.is_reversed() {
-1.0
} else {
1.0
};
let s2: f64 = if topo.face(face2)?.is_reversed() {
-1.0
} else {
1.0
};
let r1 = cyl1.radius();
let r2 = cyl2.radius();
let a1 = cyl1.axis();
let a2 = cyl2.axis();
if a1.dot(a2).abs() < 1.0 - tol_ang {
return Ok(None);
}
let a_cyl = a1;
let o1 = cyl1.origin();
let o2 = cyl2.origin();
let d_axes = o2 - o1;
let d_axes_v = Vec3::new(d_axes.x(), d_axes.y(), d_axes.z());
let perp = d_axes_v - a_cyl * d_axes_v.dot(a_cyl);
let big_d = perp.length();
if big_d <= tol_lin {
return Ok(None);
}
if big_d <= (r1 - r2).abs() + tol_lin || big_d >= r1 + r2 - tol_lin {
return Ok(None);
}
let x_hat = perp * (1.0 / big_d);
let y_hat = a_cyl.cross(x_hat).normalize()?;
let x_spine = (r1 * r1 - r2 * r2 + big_d * big_d) / (2.0 * big_d);
let y_spine_sq = r1 * r1 - x_spine * x_spine;
if y_spine_sq <= tol_lin * tol_lin {
return Ok(None);
}
let y_spine_abs = y_spine_sq.sqrt();
let edges = spine.edges();
if edges.len() == 1 {
let e = topo.edge(edges[0])?;
if e.start() == e.end() {
return Ok(None);
}
}
let spine_len = spine.length();
if spine_len < tol_lin {
return Ok(None);
}
let p_spine_sample = spine.evaluate(topo, 0.0)?;
let to_sample = p_spine_sample - o1;
let to_sample_v = Vec3::new(to_sample.x(), to_sample.y(), to_sample.z());
let sample_x = to_sample_v.dot(x_hat);
let sample_y = to_sample_v.dot(y_hat);
let spine_match_tol = tol_lin * 1e3;
if (sample_x - x_spine).abs() > spine_match_tol {
return Ok(None);
}
let y_spine = if (sample_y - y_spine_abs).abs() < spine_match_tol {
y_spine_abs
} else if (sample_y + y_spine_abs).abs() < spine_match_tol {
-y_spine_abs
} else {
return Ok(None);
};
let y_sign = if y_spine >= 0.0 { 1.0 } else { -1.0 };
let p_spine_start = p_spine_sample;
let p_spine_end = spine.evaluate(topo, spine_len)?;
let spine_tangent = spine.tangent(topo, 0.0)?;
if spine_tangent.dot(a_cyl).abs() < 1.0 - tol_ang {
return Ok(None);
}
let to_start = p_spine_start - o1;
let z_start = Vec3::new(to_start.x(), to_start.y(), to_start.z()).dot(a_cyl);
let to_end = p_spine_end - o1;
let z_end = Vec3::new(to_end.x(), to_end.y(), to_end.z()).dot(a_cyl);
let dtheta1 = y_sign * s1 * d1 / r1;
let dtheta2 = -y_sign * s2 * d2 / r2;
let (sin1, cos1) = dtheta1.sin_cos();
let (sin2, cos2) = dtheta2.sin_cos();
let c1_x = x_spine * cos1 - y_spine * sin1;
let c1_y = y_spine * cos1 + x_spine * sin1;
let c2_local_x = (x_spine - big_d) * cos2 - y_spine * sin2;
let c2_local_y = y_spine * cos2 + (x_spine - big_d) * sin2;
let c2_x = c2_local_x + big_d;
let c2_y = c2_local_y;
let c1_start = o1 + x_hat * c1_x + y_hat * c1_y + a_cyl * z_start;
let c1_end = o1 + x_hat * c1_x + y_hat * c1_y + a_cyl * z_end;
let c2_start = o1 + x_hat * c2_x + y_hat * c2_y + a_cyl * z_start;
let c2_end = o1 + x_hat * c2_x + y_hat * c2_y + a_cyl * z_end;
let chamfer_span_v = c2_start - c1_start;
if chamfer_span_v.length() <= tol_lin {
return Ok(None);
}
let chamfer_normal_raw = a_cyl.cross(chamfer_span_v);
let chamfer_normal = chamfer_normal_raw
.normalize()
.map_err(|_| BlendError::Math(brepkit_math::MathError::ZeroVector))?;
let chamfer_d = chamfer_normal.dot(Vec3::new(c1_start.x(), c1_start.y(), c1_start.z()));
let contact1 = nurbs_line(c1_start, c1_end)?;
let contact2 = nurbs_line(c2_start, c2_end)?;
let u1 = ParametricSurface::project_point(cyl1, c1_start).0;
let v1_start = cyl_v_at_point(cyl1, c1_start);
let v1_end = cyl_v_at_point(cyl1, c1_end);
let pcurve1 = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u1, v1_start),
brepkit_math::vec::Vec2::new(0.0, v1_end - v1_start),
)?);
let u2 = ParametricSurface::project_point(cyl2, c2_start).0;
let v2_start = cyl_v_at_point(cyl2, c2_start);
let v2_end = cyl_v_at_point(cyl2, c2_end);
let pcurve2 = Curve2D::Line(Line2D::new(
brepkit_math::vec::Point2::new(u2, v2_start),
brepkit_math::vec::Vec2::new(0.0, v2_end - v2_start),
)?);
let chamfer_radius = (c1_start - c2_start).length() * 0.5;
let section_start = CircSection {
p1: c1_start,
p2: c2_start,
center: midpoint_3d(c1_start, c2_start),
radius: chamfer_radius,
uv1: (u1, v1_start),
uv2: (u2, v2_start),
t: 0.0,
};
let section_end = CircSection {
p1: c1_end,
p2: c2_end,
center: midpoint_3d(c1_end, c2_end),
radius: chamfer_radius,
uv1: (u1, v1_end),
uv2: (u2, v2_end),
t: 1.0,
};
let stripe = Stripe {
spine: spine.clone(),
surface: FaceSurface::Plane {
normal: chamfer_normal,
d: chamfer_d,
},
pcurve1,
pcurve2,
contact1,
contact2,
face1,
face2,
sections: vec![section_start, section_end],
};
Ok(Some(StripeResult {
stripe,
new_edges: Vec::new(),
}))
}
fn circle_arc_to_nurbs(
circle: &brepkit_math::curves::Circle3D,
t_start: f64,
t_end: f64,
) -> Result<brepkit_math::nurbs::curve::NurbsCurve, BlendError> {
use std::f64::consts::FRAC_PI_2;
let span = t_end - t_start;
if span.abs() < 1e-15 {
return Err(BlendError::Math(brepkit_math::MathError::ZeroVector));
}
let n_arcs = ((span.abs() / FRAC_PI_2).ceil() as usize).max(1);
#[allow(clippy::cast_precision_loss)]
let delta = span / n_arcs as f64;
let n_cps = 2 * n_arcs + 1;
let mut cps: Vec<Point3> = Vec::with_capacity(n_cps);
let mut weights: Vec<f64> = Vec::with_capacity(n_cps);
let mut knots: Vec<f64> = Vec::with_capacity(2 * n_arcs + 5);
knots.push(0.0);
knots.push(0.0);
knots.push(0.0);
for i in 1..n_arcs {
#[allow(clippy::cast_precision_loss)]
let knot = i as f64 / n_arcs as f64;
knots.push(knot);
knots.push(knot);
}
knots.push(1.0);
knots.push(1.0);
knots.push(1.0);
for arc_idx in 0..n_arcs {
#[allow(clippy::cast_precision_loss)]
let t0 = t_start + arc_idx as f64 * delta;
let t1 = t0 + delta;
let half_angle = delta * 0.5;
let r = circle.radius();
let p0 = circle.evaluate(t0);
let p1 = circle.evaluate(t1);
let tan0 = circle.tangent(t0) * r;
let tan1 = circle.tangent(t1) * r;
let p_mid = tangent_intersection(p0, tan0, p1, tan1);
let w_mid = half_angle.abs().cos();
if arc_idx == 0 {
cps.push(p0);
weights.push(1.0);
}
cps.push(p_mid);
weights.push(w_mid);
cps.push(p1);
weights.push(1.0);
}
Ok(brepkit_math::nurbs::curve::NurbsCurve::new(
2, knots, cps, weights,
)?)
}
fn tangent_intersection(p0: Point3, d0: Vec3, p1: Point3, d1: Vec3) -> Point3 {
let rhs = p1 - p0;
let cross = d0.cross(d1);
let cx = cross.x().abs();
let cy = cross.y().abs();
let cz = cross.z().abs();
let (a00, a01, b0, a10, a11, b1) = if cz >= cx && cz >= cy {
(d0.x(), -d1.x(), rhs.x(), d0.y(), -d1.y(), rhs.y())
} else if cy >= cx {
(d0.x(), -d1.x(), rhs.x(), d0.z(), -d1.z(), rhs.z())
} else {
(d0.y(), -d1.y(), rhs.y(), d0.z(), -d1.z(), rhs.z())
};
let det = a00 * a11 - a01 * a10;
if det.abs() < 1e-30 {
return Point3::new(
(p0.x() + p1.x()) * 0.5,
(p0.y() + p1.y()) * 0.5,
(p0.z() + p1.z()) * 0.5,
);
}
let s = (b0 * a11 - b1 * a01) / det;
p0 + d0 * s
}
#[cfg(test)]
mod tests;