pub struct NurbsSurface {
pub degree_u: usize,
pub degree_v: usize,
pub knots_u: Vec<f64>,
pub knots_v: Vec<f64>,
pub control_points: Vec<Vec<Vec4>>,
/* private fields */
}Expand description
A rational B-spline surface.
Invariant: do not mutate the public fields after the surface has been
evaluated. The surface memoizes derived data (its analytic carrier, seam
closure, projection grids, validation) the first time it is asked, and
Clone copies those caches. Editing control_points, knots_* or a degree
in place afterwards leaves every later query answering for the OLD
geometry. Build a new value with NurbsSurface::new instead (lane K2
audit 2026-10-16: no production path does this today; a test fixture did).
Fields§
§degree_u: usize§degree_v: usize§knots_u: Vec<f64>§knots_v: Vec<f64>§control_points: Vec<Vec<Vec4>>Implementations§
Source§impl NurbsSurface
impl NurbsSurface
Sourcepub fn extend_natural(
&self,
side: SurfaceSide,
delta_param: f64,
) -> Result<NurbsSurface, ExtendRefusal>
pub fn extend_natural( &self, side: SurfaceSide, delta_param: f64, ) -> Result<NurbsSurface, ExtendRefusal>
Continue this surface past one boundary of its parameter square by
delta_param, as the analytic continuation of its own terminal Bézier
span. See the module comment for the construction and for what survives
of the input (everything, over the original domain).
Source§impl NurbsSurface
impl NurbsSurface
pub fn new( degree_u: usize, degree_v: usize, knots_u: Vec<f64>, knots_v: Vec<f64>, control_points: Vec<Vec<Vec4>>, ) -> Result<Self, String>
Sourcepub fn closed_directions(&self) -> Result<(bool, bool), String>
pub fn closed_directions(&self) -> Result<(bool, bool), String>
Whether the surface joins itself along u (first) and v (second):
opposite domain edges coincide at three sampled fractions. The 1e-6
threshold matches the historical LINEAR_TOLERANCE * 10 used by the
projection and curve×surface intersection modules.
pub fn domain_u(&self) -> Result<[f64; 2], String>
pub fn domain_v(&self) -> Result<[f64; 2], String>
Sourcepub fn analytic(&self) -> Option<&AnalyticSurface>
pub fn analytic(&self) -> Option<&AnalyticSurface>
The recognized analytic carrier, if this exact rational patch is a plane or a full revolution quadric/torus. Computed once per instance.
pub fn evaluate_homogeneous(&self, u: f64, v: f64) -> Result<Vec4, String>
pub fn evaluate(&self, u: f64, v: f64) -> Result<Vec3, String>
Sourcepub fn evaluate_extended(&self, u: f64, v: f64) -> Result<Vec3, String>
pub fn evaluate_extended(&self, u: f64, v: f64) -> Result<Vec3, String>
Value beyond the domain (Golovanov §3.15): closed directions wrap cyclically; open directions extend along the boundary tangent plane, with the bilinear corner form when both parameters are outside. Intersection and projection algorithms probe outside the domain, so this is a required capability of every surface.
Sourcepub fn derivatives_extended(
&self,
u: f64,
v: f64,
derivative_count: usize,
) -> Result<Vec<Vec<Vec3>>, String>
pub fn derivatives_extended( &self, u: f64, v: f64, derivative_count: usize, ) -> Result<Vec<Vec<Vec3>>, String>
Derivatives beyond the domain (§3.15). The extension is C¹: first derivatives follow the ruled/bilinear extension, second derivatives are taken at the boundary anchor (zero across an extended direction).
pub fn derivatives( &self, u: f64, v: f64, derivative_count: usize, ) -> Result<Vec<Vec<Vec3>>, String>
pub fn normal(&self, u: f64, v: f64) -> Result<Vec3, String>
Sourcepub fn principal_curvatures(&self, u: f64, v: f64) -> Result<(f64, f64), String>
pub fn principal_curvatures(&self, u: f64, v: f64) -> Result<(f64, f64), String>
Principal curvatures (κ_min, κ_max) at (u, v), signed with respect to the parametrization normal n = Su × Sv / |Su × Sv| via the shape operator I⁻¹·II. Convention check: a cylinder of radius R whose normal points AWAY from the axis has κ = −1/R along the circular direction, so an offset’s regularity factor 1 − d·κ vanishes exactly when an inward offset (d = −R) reaches the axis.
Sourcepub fn is_affine(&self) -> Result<bool, String>
pub fn is_affine(&self) -> Result<bool, String>
Whether this is a genuinely affine degree-1 by degree-1 patch.
A tapered bilinear patch is planar but not affine, so degree alone is insufficient for exact inverse-parameter and integration shortcuts.