use std::f64::consts::PI;
use crate::MathError;
use crate::frame::Frame3;
use crate::vec::{Point3, Vec3};
#[derive(Debug, Clone)]
pub struct Line3D {
origin: Point3,
direction: Vec3,
}
impl Line3D {
pub fn new(origin: Point3, direction: Vec3) -> Result<Self, MathError> {
let len = direction.length();
if len < 1e-15 {
return Err(MathError::ZeroVector);
}
Ok(Self {
origin,
direction: Vec3::new(
direction.x() / len,
direction.y() / len,
direction.z() / len,
),
})
}
#[must_use]
pub fn evaluate(&self, t: f64) -> Point3 {
self.origin + self.direction * t
}
#[must_use]
pub const fn tangent(&self) -> Vec3 {
self.direction
}
#[must_use]
pub fn project(&self, point: Point3) -> f64 {
let v = point - self.origin;
self.direction.dot(v)
}
#[must_use]
pub fn distance_to_point(&self, point: Point3) -> f64 {
let v = point - self.origin;
let proj = self.direction * self.direction.dot(v);
(v - proj).length()
}
#[must_use]
pub const fn origin(&self) -> Point3 {
self.origin
}
#[must_use]
pub const fn direction(&self) -> Vec3 {
self.direction
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Circle3D {
center: Point3,
normal: Vec3,
radius: f64,
u_axis: Vec3,
v_axis: Vec3,
}
impl Circle3D {
pub fn new(center: Point3, normal: Vec3, radius: f64) -> Result<Self, MathError> {
if radius <= 0.0 {
return Err(MathError::ParameterOutOfRange {
value: radius,
min: 0.0,
max: f64::INFINITY,
});
}
let f = Frame3::from_normal(center, normal)?;
Ok(Self {
center,
normal: f.z,
radius,
u_axis: f.x,
v_axis: f.y,
})
}
pub fn new_with_ref(
center: Point3,
normal: Vec3,
radius: f64,
ref_dir: Vec3,
) -> Result<Self, MathError> {
if radius <= 0.0 {
return Err(MathError::ParameterOutOfRange {
value: radius,
min: 0.0,
max: f64::INFINITY,
});
}
let f = Frame3::from_normal_and_ref(center, normal, ref_dir)?;
Ok(Self {
center,
normal: f.z,
radius,
u_axis: f.x,
v_axis: f.y,
})
}
#[must_use]
pub fn evaluate(&self, t: f64) -> Point3 {
let cos_t = t.cos();
let sin_t = t.sin();
self.center + self.u_axis * (self.radius * cos_t) + self.v_axis * (self.radius * sin_t)
}
#[must_use]
pub fn tangent(&self, t: f64) -> Vec3 {
let cos_t = t.cos();
let sin_t = t.sin();
self.u_axis * (-sin_t) + self.v_axis * cos_t
}
#[must_use]
pub fn circumference(&self) -> f64 {
2.0 * PI * self.radius
}
#[must_use]
pub const fn center(&self) -> Point3 {
self.center
}
#[must_use]
pub const fn radius(&self) -> f64 {
self.radius
}
#[must_use]
pub const fn normal(&self) -> Vec3 {
self.normal
}
#[must_use]
pub fn project(&self, point: Point3) -> f64 {
let v = point - self.center;
let u_comp = self.u_axis.dot(v);
let v_comp = self.v_axis.dot(v);
v_comp.atan2(u_comp)
}
#[must_use]
pub const fn u_axis(&self) -> Vec3 {
self.u_axis
}
#[must_use]
pub const fn v_axis(&self) -> Vec3 {
self.v_axis
}
pub fn with_axes(
center: Point3,
normal: Vec3,
radius: f64,
u_axis: Vec3,
v_axis: Vec3,
) -> Result<Self, MathError> {
if radius <= 0.0 {
return Err(MathError::ParameterOutOfRange {
value: radius,
min: 0.0,
max: f64::INFINITY,
});
}
Ok(Self {
center,
normal,
radius,
u_axis,
v_axis,
})
}
#[must_use]
pub fn intersect_segment(
&self,
seg_start: Point3,
seg_end: Point3,
tol: f64,
) -> Vec<(Point3, f64)> {
let mut out = Vec::new();
let d = seg_end - seg_start;
let seg_len_sq = d.length_squared();
if seg_len_sq < tol * tol {
return out;
}
let h0 = (seg_start - self.center).dot(self.normal);
let h1 = (seg_end - self.center).dot(self.normal);
let on_plane = |p: Point3| -> bool {
let v = p - self.center;
let in_plane = v.dot(self.normal).abs() < tol;
let r = v.length();
in_plane && (r - self.radius).abs() < tol
};
let mut push_if_unique = |p: Point3| {
let v = p - self.center;
let mut t = v.dot(self.v_axis).atan2(v.dot(self.u_axis));
if t < 0.0 {
t += std::f64::consts::TAU;
}
if out
.iter()
.any(|(q, _): &(Point3, f64)| (*q - p).length() < tol)
{
return;
}
out.push((p, t));
};
if h0.abs() < tol && h1.abs() < tol {
let p0_u = (seg_start - self.center).dot(self.u_axis);
let p0_v = (seg_start - self.center).dot(self.v_axis);
let p1_u = (seg_end - self.center).dot(self.u_axis);
let p1_v = (seg_end - self.center).dot(self.v_axis);
let du = p1_u - p0_u;
let dv = p1_v - p0_v;
let a = du * du + dv * dv;
let b = p0_u * du + p0_v * dv;
let c = p0_u * p0_u + p0_v * p0_v - self.radius * self.radius;
let disc = b * b - a * c;
if a < tol * tol || disc < -tol * tol * a {
return out;
}
let disc = disc.max(0.0);
let s_slack = tol / seg_len_sq.sqrt();
let sqrt_disc = disc.sqrt();
let roots: &[f64] = if disc <= 2.0 * self.radius * tol * a {
&[-b / a]
} else {
&[(-b - sqrt_disc) / a, (-b + sqrt_disc) / a]
};
for &s in roots {
if s >= -s_slack && s <= 1.0 + s_slack {
let s = s.clamp(0.0, 1.0);
let p = Point3::new(
seg_start.x() + s * d.x(),
seg_start.y() + s * d.y(),
seg_start.z() + s * d.z(),
);
push_if_unique(p);
}
}
} else if h0 * h1 <= tol * tol {
let denom = h0 - h1;
if denom.abs() < tol {
return out;
}
let s = h0 / denom;
let s_slack = tol / seg_len_sq.sqrt();
if s < -s_slack || s > 1.0 + s_slack {
return out;
}
let s = s.clamp(0.0, 1.0);
let p = Point3::new(
seg_start.x() + s * d.x(),
seg_start.y() + s * d.y(),
seg_start.z() + s * d.z(),
);
if on_plane(p) {
push_if_unique(p);
}
}
out
}
#[must_use]
pub fn intersect_circle(&self, other: &Self, tol: f64) -> Vec<(Point3, f64)> {
let mut out = Vec::new();
if self.normal.cross(other.normal).length() > 1e-9 {
return out; }
let dvec = other.center - self.center;
if dvec.dot(self.normal).abs() > tol {
return out; }
let du = dvec.dot(self.u_axis);
let dv = dvec.dot(self.v_axis);
let d2 = du * du + dv * dv;
let d = d2.sqrt();
if d < tol {
return out; }
let (r1, r2) = (self.radius, other.radius);
let a = (d2 + r1 * r1 - r2 * r2) / (2.0 * d);
let h2 = r1 * r1 - a * a;
let r_eff = r1.min(r2);
if h2 < -2.0 * r_eff * tol {
return out; }
let ux = Vec3::new(
(self.u_axis.x() * du + self.v_axis.x() * dv) / d,
(self.u_axis.y() * du + self.v_axis.y() * dv) / d,
(self.u_axis.z() * du + self.v_axis.z() * dv) / d,
);
let vx = self.normal.cross(ux);
let foot = self.center + ux * a;
let mut push = |p: Point3| {
let v = p - self.center;
let mut t = v.dot(self.v_axis).atan2(v.dot(self.u_axis));
if t < 0.0 {
t += std::f64::consts::TAU;
}
if !out
.iter()
.any(|(q, _): &(Point3, f64)| (*q - p).length() < tol)
{
out.push((p, t));
}
};
if h2 <= 2.0 * r_eff * tol {
push(foot);
} else {
let h = h2.sqrt();
push(foot + vx * h);
push(foot - vx * h);
}
out
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Ellipse3D {
center: Point3,
normal: Vec3,
semi_major: f64,
semi_minor: f64,
u_axis: Vec3,
v_axis: Vec3,
}
impl Ellipse3D {
pub fn new(
center: Point3,
normal: Vec3,
semi_major: f64,
semi_minor: f64,
) -> Result<Self, MathError> {
if semi_major <= 0.0 || semi_minor <= 0.0 {
return Err(MathError::ParameterOutOfRange {
value: semi_major.min(semi_minor),
min: 0.0,
max: f64::INFINITY,
});
}
if semi_minor > semi_major {
return Err(MathError::ParameterOutOfRange {
value: semi_minor,
min: 0.0,
max: semi_major,
});
}
let f = Frame3::from_normal(center, normal)?;
Ok(Self {
center,
normal: f.z,
semi_major,
semi_minor,
u_axis: f.x,
v_axis: f.y,
})
}
pub fn new_with_ref(
center: Point3,
normal: Vec3,
semi_major: f64,
semi_minor: f64,
ref_dir: Vec3,
) -> Result<Self, MathError> {
if semi_major <= 0.0 || semi_minor <= 0.0 {
return Err(MathError::ParameterOutOfRange {
value: semi_major.min(semi_minor),
min: 0.0,
max: f64::INFINITY,
});
}
if semi_minor > semi_major {
return Err(MathError::ParameterOutOfRange {
value: semi_minor,
min: 0.0,
max: semi_major,
});
}
let f = Frame3::from_normal_and_ref(center, normal, ref_dir)?;
Ok(Self {
center,
normal: f.z,
semi_major,
semi_minor,
u_axis: f.x,
v_axis: f.y,
})
}
#[must_use]
pub fn evaluate(&self, t: f64) -> Point3 {
let cos_t = t.cos();
let sin_t = t.sin();
self.center
+ self.u_axis * (self.semi_major * cos_t)
+ self.v_axis * (self.semi_minor * sin_t)
}
#[must_use]
pub fn tangent(&self, t: f64) -> Vec3 {
let cos_t = t.cos();
let sin_t = t.sin();
self.u_axis * (-self.semi_major * sin_t) + self.v_axis * (self.semi_minor * cos_t)
}
#[must_use]
pub const fn center(&self) -> Point3 {
self.center
}
#[must_use]
pub const fn semi_major(&self) -> f64 {
self.semi_major
}
#[must_use]
pub const fn semi_minor(&self) -> f64 {
self.semi_minor
}
#[must_use]
pub const fn normal(&self) -> Vec3 {
self.normal
}
#[must_use]
pub fn approximate_circumference(&self) -> f64 {
let a = self.semi_major;
let b = self.semi_minor;
let h = (a - b) * (a - b) / ((a + b) * (a + b));
PI * (a + b) * (1.0 + 3.0 * h / (10.0 + (3.0f64.mul_add(-h, 4.0)).sqrt()))
}
#[must_use]
pub fn project(&self, point: Point3) -> f64 {
let v = point - self.center;
let u_comp = self.u_axis.dot(v) / self.semi_major;
let v_comp = self.v_axis.dot(v) / self.semi_minor;
v_comp.atan2(u_comp)
}
#[must_use]
pub const fn u_axis(&self) -> Vec3 {
self.u_axis
}
#[must_use]
pub const fn v_axis(&self) -> Vec3 {
self.v_axis
}
pub fn with_axes(
center: Point3,
normal: Vec3,
semi_major: f64,
semi_minor: f64,
u_axis: Vec3,
v_axis: Vec3,
) -> Result<Self, MathError> {
if semi_major <= 0.0 || semi_minor <= 0.0 {
return Err(MathError::ParameterOutOfRange {
value: semi_major.min(semi_minor),
min: 0.0,
max: f64::INFINITY,
});
}
Ok(Self {
center,
normal,
semi_major,
semi_minor,
u_axis,
v_axis,
})
}
}
#[derive(Debug, Clone)]
pub struct Parabola3D {
vertex: Point3,
axis_dir: Vec3,
focal_length: f64,
u_axis: Vec3,
}
impl Parabola3D {
pub fn new(vertex: Point3, axis_dir: Vec3, focal_length: f64) -> Result<Self, MathError> {
if focal_length <= 0.0 {
return Err(MathError::ParameterOutOfRange {
value: focal_length,
min: f64::EPSILON,
max: f64::MAX,
});
}
let f = Frame3::from_normal(vertex, axis_dir)?;
Ok(Self {
vertex,
axis_dir: f.z,
focal_length,
u_axis: f.x,
})
}
#[must_use]
pub fn evaluate(&self, t: f64) -> Point3 {
let along_axis = (t * t) / (4.0 * self.focal_length);
self.vertex + self.axis_dir * along_axis + self.u_axis * t
}
#[must_use]
pub fn tangent(&self, t: f64) -> Vec3 {
let d_axis = t / (2.0 * self.focal_length);
self.axis_dir * d_axis + self.u_axis
}
#[must_use]
pub fn curvature(&self, t: f64) -> f64 {
let two_f = 2.0 * self.focal_length;
let ratio = t / two_f;
let denom = ratio.mul_add(ratio, 1.0);
1.0 / (two_f * denom.powf(1.5))
}
#[must_use]
pub const fn vertex(&self) -> Point3 {
self.vertex
}
#[must_use]
pub const fn focal_length(&self) -> f64 {
self.focal_length
}
#[must_use]
pub const fn axis_dir(&self) -> Vec3 {
self.axis_dir
}
#[must_use]
pub const fn u_axis(&self) -> Vec3 {
self.u_axis
}
#[must_use]
pub fn focus(&self) -> Point3 {
self.vertex + self.axis_dir * self.focal_length
}
}
#[derive(Debug, Clone)]
pub struct Hyperbola3D {
center: Point3,
normal: Vec3,
semi_major: f64,
semi_minor: f64,
u_axis: Vec3,
v_axis: Vec3,
}
impl Hyperbola3D {
pub fn new(
center: Point3,
normal: Vec3,
semi_major: f64,
semi_minor: f64,
) -> Result<Self, MathError> {
if semi_major <= 0.0 || semi_minor <= 0.0 {
return Err(MathError::ParameterOutOfRange {
value: semi_major.min(semi_minor),
min: f64::EPSILON,
max: f64::MAX,
});
}
let f = Frame3::from_normal(center, normal)?;
Ok(Self {
center,
normal: f.z,
semi_major,
semi_minor,
u_axis: f.x,
v_axis: f.y,
})
}
#[must_use]
pub fn evaluate(&self, t: f64) -> Point3 {
self.center
+ self.u_axis * (self.semi_major * t.cosh())
+ self.v_axis * (self.semi_minor * t.sinh())
}
#[must_use]
pub fn tangent(&self, t: f64) -> Vec3 {
self.u_axis * (self.semi_major * t.sinh()) + self.v_axis * (self.semi_minor * t.cosh())
}
#[must_use]
pub const fn center(&self) -> Point3 {
self.center
}
#[must_use]
pub const fn semi_major(&self) -> f64 {
self.semi_major
}
#[must_use]
pub const fn semi_minor(&self) -> f64 {
self.semi_minor
}
#[must_use]
pub const fn normal(&self) -> Vec3 {
self.normal
}
#[must_use]
pub const fn u_axis(&self) -> Vec3 {
self.u_axis
}
#[must_use]
pub const fn v_axis(&self) -> Vec3 {
self.v_axis
}
#[must_use]
pub fn eccentricity(&self) -> f64 {
let ratio = self.semi_minor / self.semi_major;
ratio.mul_add(ratio, 1.0).sqrt()
}
#[must_use]
pub fn foci(&self) -> (Point3, Point3) {
let c = self.semi_major.hypot(self.semi_minor);
(
self.center + self.u_axis * c,
self.center + self.u_axis * (-c),
)
}
}
#[cfg(test)]
mod tests;