use crate::geometry::{CurveGeometry, NurbsSurface, PcurveGeometry, SurfaceGeometry};
use crate::math::{Point2, Point3, Vector3};
fn cross(a: Vector3, b: Vector3) -> Vector3 {
Vector3::new(
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x,
)
}
fn offset(base: Point3, terms: &[(f64, Vector3)]) -> Point3 {
let mut out = base;
for (factor, direction) in terms {
out.x += factor * direction.x;
out.y += factor * direction.y;
out.z += factor * direction.z;
}
out
}
fn bspline_span(knots: &[f64], degree: usize, count: usize, t: f64) -> Option<usize> {
if knots.len() < count + degree + 1 || count <= degree {
return None;
}
if t >= knots[count] {
return Some(count - 1);
}
if t <= knots[degree] {
return Some(degree);
}
let mut lo = degree;
let mut hi = count;
while lo < hi {
let mid = usize::midpoint(lo, hi);
if t < knots[mid] {
hi = mid;
} else if t >= knots[mid + 1] {
lo = mid + 1;
} else {
return Some(mid);
}
}
Some(lo)
}
fn bspline_basis(knots: &[f64], degree: usize, span: usize, t: f64) -> Vec<f64> {
let mut values = vec![1.0];
let mut left = vec![0.0; degree + 1];
let mut right = vec![0.0; degree + 1];
for j in 1..=degree {
left[j] = t - knots[span + 1 - j];
right[j] = knots[span + j] - t;
let mut saved = 0.0;
let mut next = vec![0.0; j + 1];
for (r, &value) in values.iter().enumerate().take(j) {
let denominator = right[r + 1] + left[j - r];
let factor = if denominator == 0.0 {
0.0
} else {
value / denominator
};
next[r] = saved + right[r + 1] * factor;
saved = left[j - r] * factor;
}
next[j] = saved;
values = next;
}
values
}
pub fn nurbs_curve_point(
degree: u32,
knots: &[f64],
control_points: &[Point3],
weights: Option<&[f64]>,
t: f64,
) -> Option<Point3> {
let degree = usize::try_from(degree).ok()?;
let span = bspline_span(knots, degree, control_points.len(), t)?;
let basis = bspline_basis(knots, degree, span, t);
let mut x = 0.0;
let mut y = 0.0;
let mut z = 0.0;
let mut weight_sum = 0.0;
for (i, value) in basis.iter().enumerate() {
let index = span - degree + i;
let weight = weights
.and_then(|weights| weights.get(index).copied())
.unwrap_or(1.0);
let pole = control_points.get(index)?;
x += value * weight * pole.x;
y += value * weight * pole.y;
z += value * weight * pole.z;
weight_sum += value * weight;
}
(weight_sum != 0.0).then(|| Point3::new(x / weight_sum, y / weight_sum, z / weight_sum))
}
pub fn nurbs_pcurve_uv(
degree: u32,
knots: &[f64],
control_points: &[Point2],
weights: Option<&[f64]>,
t: f64,
) -> Option<Point2> {
let degree = usize::try_from(degree).ok()?;
let span = bspline_span(knots, degree, control_points.len(), t)?;
let basis = bspline_basis(knots, degree, span, t);
let mut u = 0.0;
let mut v = 0.0;
let mut weight_sum = 0.0;
for (i, value) in basis.iter().enumerate() {
let index = span - degree + i;
let weight = weights
.and_then(|weights| weights.get(index).copied())
.unwrap_or(1.0);
let pole = control_points.get(index)?;
u += value * weight * pole.u;
v += value * weight * pole.v;
weight_sum += value * weight;
}
(weight_sum != 0.0).then(|| Point2::new(u / weight_sum, v / weight_sum))
}
pub fn nurbs_surface_point(surface: &NurbsSurface, u_at: f64, v_at: f64) -> Option<Point3> {
let u_degree = usize::try_from(surface.u_degree).ok()?;
let v_degree = usize::try_from(surface.v_degree).ok()?;
let u_count = usize::try_from(surface.u_count).ok()?;
let v_count = usize::try_from(surface.v_count).ok()?;
if surface.control_points.len() != u_count.checked_mul(v_count)? {
return None;
}
let u_span = bspline_span(&surface.u_knots, u_degree, u_count, u_at)?;
let v_span = bspline_span(&surface.v_knots, v_degree, v_count, v_at)?;
let u_basis = bspline_basis(&surface.u_knots, u_degree, u_span, u_at);
let v_basis = bspline_basis(&surface.v_knots, v_degree, v_span, v_at);
let mut x = 0.0;
let mut y = 0.0;
let mut z = 0.0;
let mut weight_sum = 0.0;
for (i, u_value) in u_basis.iter().enumerate() {
for (j, v_value) in v_basis.iter().enumerate() {
let index = (u_span - u_degree + i) * v_count + (v_span - v_degree + j);
let weight = surface
.weights
.as_ref()
.and_then(|weights| weights.get(index).copied())
.unwrap_or(1.0);
let factor = u_value * v_value * weight;
let pole = surface.control_points.get(index)?;
x += factor * pole.x;
y += factor * pole.y;
z += factor * pole.z;
weight_sum += factor;
}
}
(weight_sum != 0.0).then(|| Point3::new(x / weight_sum, y / weight_sum, z / weight_sum))
}
pub fn curve_point(geometry: &CurveGeometry, t: f64) -> Option<Point3> {
match geometry {
CurveGeometry::Line { origin, direction } => Some(offset(*origin, &[(t, *direction)])),
CurveGeometry::Circle {
center,
axis,
ref_direction,
radius,
} => Some(offset(
*center,
&[
(radius * t.cos(), *ref_direction),
(radius * t.sin(), cross(*axis, *ref_direction)),
],
)),
CurveGeometry::Ellipse {
center,
axis,
major_direction,
major_radius,
minor_radius,
} => Some(offset(
*center,
&[
(major_radius * t.cos(), *major_direction),
(minor_radius * t.sin(), cross(*axis, *major_direction)),
],
)),
CurveGeometry::Degenerate { point } => Some(*point),
CurveGeometry::Nurbs(nurbs) => nurbs_curve_point(
nurbs.degree,
&nurbs.knots,
&nurbs.control_points,
nurbs.weights.as_deref(),
t,
),
CurveGeometry::Parabola { .. }
| CurveGeometry::Hyperbola { .. }
| CurveGeometry::Unknown { .. } => None,
}
}
pub fn surface_point(geometry: &SurfaceGeometry, u: f64, v: f64) -> Option<Point3> {
match geometry {
SurfaceGeometry::Plane {
origin,
normal,
u_axis,
} => Some(offset(
*origin,
&[(u, *u_axis), (v, cross(*normal, *u_axis))],
)),
SurfaceGeometry::Cylinder {
origin,
axis,
ref_direction,
radius,
} => Some(offset(
*origin,
&[
(radius * u.cos(), *ref_direction),
(radius * u.sin(), cross(*axis, *ref_direction)),
(v, *axis),
],
)),
SurfaceGeometry::Cone {
origin,
axis,
ref_direction,
radius,
half_angle,
} => {
let local_radius = radius + v * half_angle.tan();
Some(offset(
*origin,
&[
(local_radius * u.cos(), *ref_direction),
(local_radius * u.sin(), cross(*axis, *ref_direction)),
(v, *axis),
],
))
}
SurfaceGeometry::Sphere {
center,
axis,
ref_direction,
radius,
} => Some(offset(
*center,
&[
(radius * v.cos() * u.cos(), *ref_direction),
(radius * v.cos() * u.sin(), cross(*axis, *ref_direction)),
(radius * v.sin(), *axis),
],
)),
SurfaceGeometry::Torus {
center,
axis,
ref_direction,
major_radius,
minor_radius,
} => {
let ring = major_radius + minor_radius * v.cos();
Some(offset(
*center,
&[
(ring * u.cos(), *ref_direction),
(ring * u.sin(), cross(*axis, *ref_direction)),
(minor_radius * v.sin(), *axis),
],
))
}
SurfaceGeometry::Nurbs(nurbs) => nurbs_surface_point(nurbs, u, v),
SurfaceGeometry::Unknown { .. } => None,
}
}
pub fn pcurve_uv(geometry: &PcurveGeometry, t: f64) -> Option<Point2> {
match geometry {
PcurveGeometry::Line { origin, direction } => Some(Point2::new(
origin.u + t * direction.u,
origin.v + t * direction.v,
)),
PcurveGeometry::Nurbs {
degree,
knots,
control_points,
weights,
..
} => nurbs_pcurve_uv(*degree, knots, control_points, weights.as_deref(), t),
}
}