Skip to main content

brep_kernel/construction/
sweep_ellipsoid.rs

1//! The whole sweep of an ellipsoid slid along a line.
2//!
3//! An ellipsoid is the unit ball under an affine map `A` (a stretch onto its
4//! three semi-axes, then a placement), and a slide commutes with `A`:
5//! `A(K + t·w) = A(K) + t·A·w`. So the ellipsoid slid by `v` sweeps `A` of what
6//! the unit ball sweeps slid by `w = A⁻¹·v`, which the whole sweep of a solid
7//! ([`crate::sweep_solid`]) builds from the ball's two caps and the wall its
8//! great circle square to `w` sweeps. `A` carries that body's rational poles
9//! exactly, so the result's caps are pieces of the ellipsoid where it starts
10//! and where it stops, and its wall the elliptic cylinder between them.
11use crate::{sweep_solid, transform_brep, AffineTransform, BrepSolid, KernelRefusal, KernelStage, OrRefuse, OutlinePiece, ScrewStep, SolidMotion, SolidTool, Vec3};
12use std::f64::consts::FRAC_PI_2;
13
14/// Everything the ellipsoid about `center` passes through as it slides without
15/// turning by `slide`. Its semi-axes are `semi_axes[0]` along `x_direction`,
16/// `semi_axes[1]` square to it toward `y_hint`, and `semi_axes[2]` along their
17/// cross product (as [`crate::make_ellipsoid_brep`]).
18pub fn sweep_ellipsoid_along_line(center: Vec3, x_direction: Vec3, y_hint: Vec3, semi_axes: [f64; 3], slide: Vec3) -> Result<BrepSolid, KernelRefusal> {
19    let input = |what: &'static str, message: &str| KernelRefusal::input(KernelStage::Collect, what, format!("sweepEllipsoid: {message}"));
20    if semi_axes.iter().any(|axis| !(axis.is_finite() && *axis > 0.0)) {
21        return Err(input("ellipsoid_axes", "the three semi-axes must be positive"));
22    }
23    let x = x_direction.normalized().map_err(|_| input("ellipsoid_frame", "the first direction must be non-zero"))?;
24    let y = y_hint.sub(x.scale(y_hint.dot(x))).normalized().map_err(|_| input("ellipsoid_frame", "the second direction must not be parallel to the first"))?;
25    let z = x.cross(y);
26    let [a, b, c] = semi_axes;
27    // the slide as the unit ball sees it
28    let seen = Vec3::new(slide.dot(x) / a, slide.dot(y) / b, slide.dot(z) / c);
29    let along = seen.normalized().map_err(|_| input("slide", "the slide has no length"))?;
30    let across = if along.x.abs() < 0.9 { Vec3::new(1.0, 0.0, 0.0) } else { Vec3::new(0.0, 1.0, 0.0) };
31    let radial = across.sub(along.scale(across.dot(along))).normalized().or_refuse(KernelStage::Collect, "sweepEllipsoid.frame")?;
32    let origin = Vec3::new(0.0, 0.0, 0.0);
33    // the ball as a solid of revolution about the slide's own line
34    let ball = SolidTool::Revolved {
35        axis_origin: origin,
36        axis_direction: along,
37        radial,
38        meridian: vec![
39            OutlinePiece::Arc { centre: [0.0, 0.0], radius: 1.0, start: -FRAC_PI_2, end: FRAC_PI_2 },
40            OutlinePiece::Line { from: [0.0, 1.0], to: [0.0, -1.0] },
41        ],
42    };
43    let motion = SolidMotion { steps: vec![ScrewStep { axis_origin: origin, axis_direction: along, turn: 0.0, advance: seen.length() }], from: 0.0, to: 1.0 };
44    let swept = sweep_solid(&ball, &motion)?;
45    let (ex, ey, ez) = (x.scale(a), y.scale(b), z.scale(c));
46    let map = AffineTransform { elements: [ex.x, ey.x, ez.x, center.x, ex.y, ey.y, ez.y, center.y, ex.z, ey.z, ez.z, center.z, 0.0, 0.0, 0.0, 1.0] };
47    transform_brep(&swept, map, false).or_refuse(KernelStage::Collect, "sweepEllipsoid.stretch")
48}
49