pub struct NurbSurface<S: Scalar, const D: usize> {
pub degree_u: usize,
pub degree_v: usize,
pub num_u: usize,
pub num_v: usize,
pub control_points: Vec<Vector<S, D>>,
pub knot_vector_u: Vec<S>,
pub knot_vector_v: Vec<S>,
/* private fields */
}Expand description
A NURBS surface patch whose control points live in D-dimensional homogeneous space.
In practice D = 4 for all 3-D surfaces ((wx, wy, wz, w) control points).
Control points are stored row-major: control_points[i * num_v + j] is the
point at u-index i (0 ≤ i < num_u) and v-index j (0 ≤ j < num_v).
Fields§
§degree_u: usize§degree_v: usize§num_u: usize§num_v: usize§control_points: Vec<Vector<S, D>>§knot_vector_u: Vec<S>§knot_vector_v: Vec<S>Implementations§
Source§impl<S: Scalar> NurbSurface<S, 4>
impl<S: Scalar> NurbSurface<S, 4>
Sourcepub fn convex_hull(&self) -> GeopResult<ConvexHull<S, 3>>
pub fn convex_hull(&self) -> GeopResult<ConvexHull<S, 3>>
Convex hull of the surface patch’s Cartesian (dehomogenized) control
points, stored row-major (matching Self::control_points).
By the convex-hull property of the NURBS basis, every point on the patch lies within this hull.
Sourcepub fn size(&self) -> GeopResult<S>
pub fn size(&self) -> GeopResult<S>
max(u_size, v_size), measured via the convex hull’s control-net
edges (see Self::extents), used as a convergence measure for
subdivision algorithms.
Sourcepub fn split_mid(&self) -> GeopResult<(NurbSurface<S, 4>, NurbSurface<S, 4>)>
pub fn split_mid(&self) -> GeopResult<(NurbSurface<S, 4>, NurbSurface<S, 4>)>
Split along the longer of the u/v dimensions (measured via
Self::extents) at that dimension’s domain midpoint.
Source§impl<S: Scalar> NurbSurface<S, 4>
impl<S: Scalar> NurbSurface<S, 4>
Sourcepub fn curvature_radius(&self, u: S, v: S) -> GeopResult<Option<S>>
pub fn curvature_radius(&self, u: S, v: S) -> GeopResult<Option<S>>
A conservative radius of curvature at (u, v), or None if the
surface is (locally) flat in both parametric directions.
Computed straight from the surface’s own second partial derivatives
— no history, no finite differences — via the normal curvature along
each parametric direction, κ_a = (S_aa · n) / |S_a|² for a ∈ {u, v} (the diagonal terms of the second fundamental form divided by the
diagonal terms of the first). This is the exact normal curvature in
direction a only when Su ⊥ Sv; the general formula also needs the
mixed partial Suv and the off-diagonal metric term F = Su·Sv to
handle an arbitrary direction. Every surface this crate actually
constructs has orthogonal parametric directions by construction —
flat bilinear box/cap faces (Su, Sv are the patch’s two edge
directions) and revolve’s ruled patches (axial u is always
perpendicular to the circular v) — so the approximation is exact
for our surfaces, not just a rough heuristic.
The returned radius is 1 / max(|κ_u|, |κ_v|): the tighter of the
two bends, so a caller sizing steps off of it stays conservative.
Source§impl<S: Scalar> NurbSurface<S, 4>
impl<S: Scalar> NurbSurface<S, 4>
Source§impl<S: Scalar> NurbSurface<S, 4>
impl<S: Scalar> NurbSurface<S, 4>
Sourcepub fn fit_pcurve(
&self,
curve: &NurbCurve<S, 4>,
pin_start: Option<Vector2<S>>,
pin_end: Option<Vector2<S>>,
) -> GeopResult<NurbCurve2D<S>>
pub fn fit_pcurve( &self, curve: &NurbCurve<S, 4>, pin_start: Option<Vector2<S>>, pin_end: Option<Vector2<S>>, ) -> GeopResult<NurbCurve2D<S>>
The (u, v) trace of curve across this surface: sample the curve,
Newton-project each sample onto the surface (each projection seeded
from the previous one’s result, so the walk stays continuous), and
fit a pcurve through the results.
pin_start / pin_end override the projected (u, v) of the first
and last sample. Pass them whenever the curve’s endpoint is a place
this surface’s face already has a coedge for: that coedge’s own
pcurve endpoint is the authoritative (u, v) there, and an
independently re-projected one lands a hair away from it — enough to
break the exact could_be_equal continuity a face’s boundary loop
requires between one coedge’s pcurve end and the next one’s start.
The endpoints are free to pin without disturbing the rest of the
curve because interpolate produces a clamped B-spline, which
passes exactly through each sample.
Source§impl<S: Scalar> NurbSurface<S, 4>
impl<S: Scalar> NurbSurface<S, 4>
Sourcepub fn derivatives(&self, u: S, v: S) -> GeopResult<(Vector3<S>, Vector3<S>)>
pub fn derivatives(&self, u: S, v: S) -> GeopResult<(Vector3<S>, Vector3<S>)>
Partial derivatives (∂S/∂u, ∂S/∂v) at (u, v).
Sourcepub fn normal(&self, u: S, v: S) -> GeopResult<Vector3<S>>
pub fn normal(&self, u: S, v: S) -> GeopResult<Vector3<S>>
Unit surface normal at (u, v), normalize(∂S/∂u × ∂S/∂v).
Whether this points outward or inward for a given face depends on
that surface’s own u/v parametrization convention — it is each
surface constructor’s responsibility to pick the matching
Face::sense (Forward if this normal is already outward,
Reversed if it needs negating) so that callers can treat
Face::sense-corrected normal() as reliably outward. See
box_solid’s FACE_DEFS ((P10 − P00) × (P01 − P00), Forward)
and revolve/sphere’s patch constructors (natural normal is
inward, hence Reversed) for both cases.
Source§impl<S: Scalar> NurbSurface<S, 4>
impl<S: Scalar> NurbSurface<S, 4>
Sourcepub fn project(
&self,
target: Vector3<S>,
u0: S,
v0: S,
iterations: usize,
) -> GeopResult<(S, S)>
pub fn project( &self, target: Vector3<S>, u0: S, v0: S, iterations: usize, ) -> GeopResult<(S, S)>
Fixed-iteration-count Newton foot-point projection of target onto
this surface, starting from (u0, v0).
Each step solves the exact 2x2 system J·Δ = r where
r = (residual·Su, residual·Sv) and J = [[Su·Su, Su·Sv], [Su·Sv, Sv·Sv]] (the first fundamental form — exact for the degree-1
bilinear/low-degree patches used in this crate’s tests, since second
derivatives are dropped), then clamps (u, v) into domain_u() /
domain_v() before the next iteration. Runs iterations times with no
convergence tolerance — interval scalars can’t judge “close enough” —
but stops early, with the identical result, once the sharpened
iterate reaches an exact fixed point (see the loop).
J is singular exactly where the surface’s own parametrization is —
a coordinate-singular pole (e.g. the apex of a revolved disk cap,
where every v collapses to one point and Sv = 0). Rather than
erroring there (div by an exactly- or interval-possibly-zero
determinant), that iteration’s update is simply skipped, leaving
(u, v) exactly where the previous, non-singular iteration left
it. For a target genuinely at or very near such a pole, earlier
iterations still pull (u, v) right up to the singular edge_loop
before this kicks in, so the frozen result is still a meaningful
(if imprecise right at the pole) answer — good enough for a caller
to then recognize “this converged onto a known singular vertex” and
handle it explicitly, instead of the whole projection just failing.
Source§impl<S: Scalar, const D: usize> NurbSurface<S, D>
impl<S: Scalar, const D: usize> NurbSurface<S, D>
Sourcepub fn reverse_u(&self) -> Self
pub fn reverse_u(&self) -> Self
Mirror the u parametrization: the point at u moves to
u_lo + u_hi - u, leaving v alone.
The surface traces exactly the same set of points, but Su reverses,
so Su x Sv — the normal — flips. That is the only way to turn a
face’s material side around in this kernel, since orientation lives in
the parametrization rather than in a flag on the face.
Mirroring rather than merely reordering matters: the domain is
unchanged, so every pcurve drawn on this surface stays in range and
only needs the same mirror applied to its own u coordinate (see
Model::reverse_face).
Source§impl<S: Scalar, const D: usize> NurbSurface<S, D>
impl<S: Scalar, const D: usize> NurbSurface<S, D>
Sourcepub fn split_u(
&self,
t: S,
) -> GeopResult<(NurbSurface<S, D>, NurbSurface<S, D>)>
pub fn split_u( &self, t: S, ) -> GeopResult<(NurbSurface<S, D>, NurbSurface<S, D>)>
Split at parameter t in the u direction.
t must lie strictly inside the u domain. Returns (left, right).
t is used exactly as given — not sharpened here, see NurbCurve::split’s own
doc comment for why (this is the same Boehm-insertion construction,
one dimension up: an unsharpened t carried into the new knot
vector lets alpha = (t - e) / (s - e) blow up over repeated splits
as s - e shrinks while t’s own width doesn’t). Every current caller
therefore sharpens its own midpoint before calling; a caller splitting
at a located parameter must validate the sharpened value it is about
to use, not the wide one it started from.
Sourcepub fn split_v(
&self,
t: S,
) -> GeopResult<(NurbSurface<S, D>, NurbSurface<S, D>)>
pub fn split_v( &self, t: S, ) -> GeopResult<(NurbSurface<S, D>, NurbSurface<S, D>)>
Split at parameter t in the v direction.
t must lie strictly inside the v domain. Returns (left, right).
t is used exactly as given — not sharpened here, same as Self::split_u.
Sourcepub fn split_u_mid(&self) -> GeopResult<(NurbSurface<S, D>, NurbSurface<S, D>)>
pub fn split_u_mid(&self) -> GeopResult<(NurbSurface<S, D>, NurbSurface<S, D>)>
Split at the midpoint of the u domain.
Sourcepub fn split_v_mid(&self) -> GeopResult<(NurbSurface<S, D>, NurbSurface<S, D>)>
pub fn split_v_mid(&self) -> GeopResult<(NurbSurface<S, D>, NurbSurface<S, D>)>
Split at the midpoint of the v domain.
Sourcepub fn sub_surface(
&self,
(u0, u1): (S, S),
(v0, v1): (S, S),
) -> GeopResult<Self>
pub fn sub_surface( &self, (u0, u1): (S, S), (v0, v1): (S, S), ) -> GeopResult<Self>
This surface restricted to u ∈ [u0, u1], v ∈ [v0, v1], with the
same cut rule as NurbCurve::sub_curve: only bounds strictly inside
the domain are cut at, so the result covers at least the requested
box ∩ domain. Bounds are used exactly as given and must be narrow.
Source§impl<S: Scalar> NurbSurface<S, 4>
impl<S: Scalar> NurbSurface<S, 4>
Source§impl<S: Scalar, const D: usize> NurbSurface<S, D>
impl<S: Scalar, const D: usize> NurbSurface<S, D>
pub fn try_new( degree_u: usize, degree_v: usize, control_points: Vec<Vector<S, D>>, knot_vector_u: Vec<S>, knot_vector_v: Vec<S>, ) -> GeopResult<Self>
Sourcepub fn recompute_aabb(&mut self)
pub fn recompute_aabb(&mut self)
Refresh the cached Self::aabb from the current control_points
— see crate::nurb_curve::NurbCurve::recompute_aabb’s identical
doc comment for why this is needed at all (control_points is
pub, and code outside this crate does mutate it in place).
Sourcepub fn is_everything(&self) -> bool
pub fn is_everything(&self) -> bool
Whether this is the NurbSurface::everything placeholder — the
unsharp stand-in a face carries before it is given real geometry.
A finished solid must have none: a placeholder face has no position,
so nothing can be classified against it, and it will silently swallow
any containment or intersection query it is handed (every comparison
against ENTIRE succeeds). Construction code that splits faces off a
starting placeholder has to consume the last one rather than leave it
behind, and this is how a test says so.
Sourcepub fn everything() -> Self
pub fn everything() -> Self
A degenerate, maximally-unsharp surface: a single 1×1 control point
whose every coordinate is [Scalar::ENTIRE], over a domain that
accepts any (u, v). evaluate() anywhere returns ENTIRE in every
coordinate, so it could_be_equals any point — a placeholder for
geometry that is not yet known.
Source§impl<S: Scalar> NurbSurface<S, 4>
impl<S: Scalar> NurbSurface<S, 4>
Sourcepub fn as_plane(&self) -> GeopResult<Option<Plane<S>>>
pub fn as_plane(&self) -> GeopResult<Option<Plane<S>>>
The plane the surface lies in, if it is flat: every control point on
the plane through its middle, normal to it there — the normal pointing
the way the surface’s own does. None for a surface that bends.
Sourcepub fn axis_of_revolution(&self) -> GeopResult<Option<Axis<S>>>
pub fn axis_of_revolution(&self) -> GeopResult<Option<Axis<S>>>
The axis the surface turns around, if it is a surface of revolution —
a cylinder, a cone, a sphere, a torus, a disc: one of its parameter
directions sweeps circular arcs around a common axis. None for any
other surface.
Checked on the control net: every row of control points along that
direction is an arc (see NurbCurve3D::as_arc) around the one
axis, starting at the same angle, with weights proportional to every
other row’s — or a single point on the axis, as at a sphere’s pole.
Then every row turns through the same angles at the same parameters,
and a blend of them across the other direction is the blended
profile, turned: a surface of revolution.
Trait Implementations§
Source§impl<S: Clone + Scalar, const D: usize> Clone for NurbSurface<S, D>
impl<S: Clone + Scalar, const D: usize> Clone for NurbSurface<S, D>
Source§fn clone(&self) -> NurbSurface<S, D>
fn clone(&self) -> NurbSurface<S, D>
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more