pub struct NurbCurve<const D: usize> {
pub degree: usize,
pub control_points: Vec<Vector<D>>,
pub knot_vector: Vec<Scalar>,
/* private fields */
}Expand description
A NURBS curve whose control points live in D-dimensional homogeneous space.
D = 4: 3-D curve — control points are(wx, wy, wz, w).D = 3: 2-D curve (pcurve) — control points are(wu, wv, w).
Fields§
§degree: usize§control_points: Vec<Vector<D>>§knot_vector: Vec<Scalar>Implementations§
Source§impl NurbCurve<3>
impl NurbCurve<3>
Sourcepub fn area_term(&self, origin: &Vector2) -> GeopResult<Scalar>
pub fn area_term(&self, origin: &Vector2) -> GeopResult<Scalar>
½ ∫ ((u − o_u) v' − (v − o_v) u') dt over the curve, o the
origin: its part of the signed area of a closed loop it belongs to
(see the module docs). The parts of one loop must be measured from
the same origin; their sum then does not depend on it.
The curve is moved to origin once, so that every point of the
integrand comes with its tangent from one pass over the spline.
Source§impl NurbCurve<4>
impl NurbCurve<4>
Sourcepub fn closest_point(&self, target: &Vector3) -> GeopResult<(Scalar, Vector3)>
pub fn closest_point(&self, target: &Vector3) -> GeopResult<(Scalar, Vector3)>
The point of the curve nearest target, with its parameter.
Subdivision isolates, Newton refines: least_distance bounds the
distance over every span of the curve, so no dip of it is missed,
and Newton on (C(t) - target) · C'(t) = 0 refines each span left
at the handoff, kept inside the domain — which also reaches an end
of the curve, where the nearest point is one. The parameter is a
free choice of which point of the curve is returned, so every
iterate is sharpened: the point returned lies on the curve, and its
distance to target is attained.
Sourcepub fn closest_parameter_from(
&self,
target: &Vector3,
t: Scalar,
) -> GeopResult<Scalar>
pub fn closest_parameter_from( &self, target: &Vector3, t: Scalar, ) -> GeopResult<Scalar>
The parameter of the point of the curve nearest target around the
seed t, sharp: the local search NurbCurve::closest_point
refines each of its seeds with, for a caller that already knows
where to look.
Damped Newton on f(t) = (C(t) - target) · C'(t) from t: the full
derivative f' where it is definitely positive — near the nearest
point — and |C'|² where not (Gauss–Newton, a descent direction
wherever the curve is regular). A step that does not bring the point
nearer is halved, and the search stops once halving does not help.
Every iterate is sharpened — which point is returned is a free
choice — and clamped into the domain.
Source§impl<const D: usize> NurbCurve<D>
impl<const D: usize> NurbCurve<D>
Sourcepub fn with_unit_domain(&self) -> GeopResult<Self>
pub fn with_unit_domain(&self) -> GeopResult<Self>
The same curve reparametrized from its domain onto [0, 1]: its
knots mapped linearly, its control points unchanged. A curve already
on exactly [0, 1] is returned as it is: mapping its knots would
only round them outwards.
Sourcepub fn elevate_degree(&self) -> GeopResult<Self>
pub fn elevate_degree(&self) -> GeopResult<Self>
The same curve one degree higher, for a curve clamped at both ends.
Cut into its Bézier pieces first — every interior knot inserted up to
multiplicity degree — each piece is raised on its own:
Q_i = i/(p+1) P_{i-1} + (1 - i/(p+1)) P_i on the homogeneous control
points, exact for rational curves too. The pieces stay joined as they
were, every interior knot now of multiplicity degree + 1.
Sourcepub fn with_unit_end_weights(&self) -> GeopResult<Self>
pub fn with_unit_end_weights(&self) -> GeopResult<Self>
The same single-span (Bézier) curve with its end weights one, as a clamped curve’s ends are in a profile — what a split of a rational curve, an arc, leaves otherwise.
Scaling the i-th weight by c^i — every homogeneous control point
with it — reparametrizes a rational Bézier curve without changing it,
for any c > 0: c^p = w_0 / w_p brings both ends to one weight,
and dividing by w_0 makes that one. c is a free choice; the ends
are divided by their own weights, so they come out at one whatever
c rounded to. A curve whose ends already weigh exactly one is
returned as it is.
Sourcepub fn compatible(curves: &[Self]) -> GeopResult<Vec<Self>>
pub fn compatible(curves: &[Self]) -> GeopResult<Vec<Self>>
curves, on one domain and clamped, made compatible: each raised to
the highest degree among them, and given every knot any of them has,
at the highest multiplicity any of them has it — so that they all end
up on one knot vector. Every curve stays the curve it was.
Knots of different curves that could be equal are taken as one, the union of their enclosures, which every curve then carries: an honest enclosure of each curve’s own knot.
Sourcepub fn join(curves: &[Self]) -> GeopResult<Self>
pub fn join(curves: &[Self]) -> GeopResult<Self>
The chain curves, each starting where the one before ends, as one
curve on [0, 1], the i-th of n curves on [i / n, (i + 1) / n]
(see Self::join_at).
Sourcepub fn join_at(curves: &[Self], breaks: &[Scalar]) -> GeopResult<Self>
pub fn join_at(curves: &[Self], breaks: &[Scalar]) -> GeopResult<Self>
The chain curves, each starting where the one before ends, as one
curve, the i-th on [breaks[i], breaks[i + 1]]: each brought onto
its own unit domain with its ends weighted one (see
Self::with_unit_end_weights) and raised to the highest degree
among them, their knot vectors laid end to end, every joint a knot
of full multiplicity. A joint’s control point is the union of the end
of the curve before it and the start of the next — two enclosures of
the one point — and an error if they could not be equal. Every curve
stays the curve it was, on its new piece of the domain.
Sourcepub fn reparametrized(&self, from: &[Scalar], to: &[Scalar]) -> GeopResult<Self>
pub fn reparametrized(&self, from: &[Scalar], to: &[Scalar]) -> GeopResult<Self>
The same curve between the parameters from[0] and from[n - 1],
reparametrized piece by piece: the stretch between from[i] and
from[i + 1] onto [to[i], to[i + 1]], linearly, so that the curve
is at to[i] where it was at from[i]. What lies outside from is
cut off. Both are strictly increasing; a from that could be an end
of the domain is that end.
Every from[i] becomes a joint of full multiplicity (see
Self::join_at). A rational curve is cut at its knots as well, so
that each piece can be weighted one at its ends: its weight is then
one at every joint, and so at every to[i], as a polynomial curve’s
is everywhere. Where a piece’s own knots land between to[i] and
to[i + 1] is a free choice, so they are sharpened.
The from are used as given: like Self::split’s, they must be
narrow, refined rather than sharpened.
Source§impl NurbCurve<4>
impl NurbCurve<4>
Sourcepub fn line(a: Vector3, b: Vector3) -> GeopResult<Self>
pub fn line(a: Vector3, b: Vector3) -> GeopResult<Self>
The straight line from a to b, over [0, 1].
Sourcepub fn arc(
p0: Vector3,
mid: Vector3,
p2: Vector3,
w: Scalar,
) -> GeopResult<Self>
pub fn arc( p0: Vector3, mid: Vector3, p2: Vector3, w: Scalar, ) -> GeopResult<Self>
The rational quadratic from p0 to p2 over [0, 1], its middle
control point mid weighted w. With mid where the tangents of a
circle at p0 and p2 meet and w the cosine of half the angle
between them, it is that circle’s arc exactly: for a quarter,
mid = p0 + p2 - centre and w = super::sqrt2_over_2.
Sourcepub fn start(&self) -> GeopResult<Vector3>
pub fn start(&self) -> GeopResult<Vector3>
The point the curve starts at, for the clamped knot vectors every curve here has: its first control point, divided by its weight.
Sourcepub fn end(&self) -> GeopResult<Vector3>
pub fn end(&self) -> GeopResult<Vector3>
The point the curve ends at: its last control point (see
Self::start).
Source§impl NurbCurve<3>
impl NurbCurve<3>
Sourcepub fn line(a: Vector2, b: Vector2) -> GeopResult<Self>
pub fn line(a: Vector2, b: Vector2) -> GeopResult<Self>
The straight line from a to b, over [0, 1].
Sourcepub fn arc(
p0: Vector2,
mid: Vector2,
p2: Vector2,
w: Scalar,
) -> GeopResult<Self>
pub fn arc( p0: Vector2, mid: Vector2, p2: Vector2, w: Scalar, ) -> GeopResult<Self>
The rational quadratic from p0 to p2 through the weighted
control point mid (see NurbCurve3D::arc).
Sourcepub fn start(&self) -> GeopResult<Vector2>
pub fn start(&self) -> GeopResult<Vector2>
The point the curve starts at (see NurbCurve3D::start).
Sourcepub fn end(&self) -> GeopResult<Vector2>
pub fn end(&self) -> GeopResult<Vector2>
The point the curve ends at (see NurbCurve3D::start).
Source§impl<const D: usize> NurbCurve<D>
impl<const D: usize> NurbCurve<D>
Sourcepub fn weight(&self, t: Scalar) -> GeopResult<Scalar>
pub fn weight(&self, t: Scalar) -> GeopResult<Scalar>
The weight at t: the homogeneous coordinate the curve’s point there
is divided by — the union of its value on every knot span t
reaches. A polynomial curve’s is one everywhere; a rational one’s says
how its parameter runs: its points, multiplied by it, are a
polynomial in t on each span, which is what makes a rational
quadratic an exact circle.
Source§impl NurbCurve<4>
impl NurbCurve<4>
Sourcepub fn evaluate(&self, t: Scalar) -> GeopResult<Vector3>
pub fn evaluate(&self, t: Scalar) -> GeopResult<Vector3>
Evaluate the 3-D NURBS curve at t, returning a Cartesian Vector3.
Sourcepub fn ends(&self) -> GeopResult<(Vector3, Vector3)>
pub fn ends(&self) -> GeopResult<(Vector3, Vector3)>
Where it starts and where it ends.
Source§impl NurbCurve<3>
impl NurbCurve<3>
Sourcepub fn evaluate(&self, t: Scalar) -> GeopResult<Vector2>
pub fn evaluate(&self, t: Scalar) -> GeopResult<Vector2>
Evaluate the 2-D pcurve at t, returning a Cartesian Vector2.
Sourcepub fn ends(&self) -> GeopResult<(Vector2, Vector2)>
pub fn ends(&self) -> GeopResult<(Vector2, Vector2)>
Where it starts and where it ends.
Source§impl<const D: usize> NurbCurve<D>
impl<const D: usize> NurbCurve<D>
Sourcepub fn extended(&self, at_end: bool, by: Scalar) -> GeopResult<Self>
pub fn extended(&self, at_end: bool, by: Scalar) -> GeopResult<Self>
The same curve carried on by by of its parameter past its end
(at_end) or before its start: its end piece continued as the
polynomial it is, so the curve stays as smooth as it was where it
used to end. The domain grows by by on that side; the rest of the
curve keeps its parametrization.
The end piece is isolated as a Bézier piece by knot insertion, then subdivided at a parameter past its end — de Casteljau extrapolates as exactly as it interpolates, on the homogeneous control points, so a rational curve continues as itself: an arc continues round its circle. The curve as it was is kept as it was, control point for control point, and the continuation joined on as a piece of its own at a knot of full multiplicity: where it used to run, the carried-on curve is exactly as narrow as before. Fails for a knot vector not clamped at that end, and where the continued weights stop being positive — an arc carried round too far.
Source§impl<const D: usize> NurbCurve<D>
impl<const D: usize> NurbCurve<D>
Sourcepub fn stationary_parameters<const C: usize>(
&self,
direction: &Vector<C>,
max_nodes: usize,
min_subdivision_size: Scalar,
) -> GeopResult<Vec<Scalar>>
pub fn stationary_parameters<const C: usize>( &self, direction: &Vector<C>, max_nodes: usize, min_subdivision_size: Scalar, ) -> GeopResult<Vec<Scalar>>
Every parameter at which the curve’s coordinate along direction
is stationary — its tangent perpendicular to direction — at an
isolated point, as a parameter box: the curve’s extremes along
direction, and where its projection onto a plane containing
direction turns back on itself. Sorted, and each box at most
min_subdivision_size wide unless several merged.
A stretch along which the coordinate is constant (a straight piece
perpendicular to direction) has no isolated stationary point and
contributes none. A box is a candidate, not a proof: it is where the
search stopped being able to exclude one. Exhausting max_nodes is an
error, since the result would be incomplete. C must be D - 1.
Source§impl NurbCurve<4>
impl NurbCurve<4>
Sourcepub fn bounds(
&self,
max_nodes: usize,
min_subdivision_size: Scalar,
) -> GeopResult<[Scalar; 3]>
pub fn bounds( &self, max_nodes: usize, min_subdivision_size: Scalar, ) -> GeopResult<[Scalar; 3]>
The box the curve itself spans, per axis: the union of its
coordinate at both ends and at every stationary parameter along that
axis (NurbCurve::stationary_parameters) — its extremes, each
enclosed, where the control points’ box is only a bound.
Source§impl NurbCurve<4>
impl NurbCurve<4>
Sourcepub fn fit_enclosing<T: Clone>(
stations: &[(Vector3, T)],
locate: impl Fn(&(Vector3, T), &(Vector3, T), Scalar) -> GeopResult<(Vector3, T)>,
widest_between: &mut WidestBetween,
ctx: &dyn Fn(GeopError) -> GeopError,
) -> GeopResult<NurbCurve<4>>
pub fn fit_enclosing<T: Clone>( stations: &[(Vector3, T)], locate: impl Fn(&(Vector3, T), &(Vector3, T), Scalar) -> GeopResult<(Vector3, T)>, widest_between: &mut WidestBetween, ctx: &dyn Fn(GeopError) -> GeopError, ) -> GeopResult<NurbCurve<4>>
A cubic through stations of a curve — each a point of it and
whatever locate needs to find more — that encloses the curve
between them too. locate(from, to, frac) is the point of the curve
a fraction frac of the way along the leg from station from to
to, with what it needs to go on from there.
Every leg is first split into pieces: a curve given in only a few
stations would leave too few points for a cubic to follow it, so
every fit has at least [MIN_FITTED_LEGS] legs. The cubic drifts
from the curve between its points, which is enclosed as width,
honestly (see NurbCurve3D::interpolate_enclosing); while that
width is more than [FIT_DRIFT] and refining the legs still halves
it, the legs that drift are halved: those a halving of all, which
shrinks a cubic’s drift sixteen times, would leave the widest. Where
the curve bends evenly that is every leg. Where it turns sharply in
one place — a blend’s arc, tangent to a plane it is cut by at three
degrees, meets it in an ellipse nineteen times as long as it is wide,
its tip of radius 5e-3 — it is those at the tip, which legs of an
even length reach only when there are hundreds of them. Once
refining no longer halves the width, the width is the points’ own,
and the coarser fit is kept.
widest_between is told the widest point of the curve located
between stations of the last attempt: the curve is widened to hold
it, so one that is wide makes the whole curve wide, and this says
which.
Source§impl NurbCurve<4>
impl NurbCurve<4>
Sourcepub fn helix(
frame: &CoordinateSystem,
radius: Scalar,
helix: &Helix,
) -> GeopResult<Self>
pub fn helix( frame: &CoordinateSystem, radius: Scalar, helix: &Helix, ) -> GeopResult<Self>
The helix of radius around frame.w() through frame.origin():
starting at origin + radius u, moving as helix says along w —
right-handed turning from u towards v, left-handed the other
way. One rational quadratic per span of at most a quarter turn,
exactly on the cylinder, its height within 0.53 % of the pitch of
the true helix (see the module docs); tapered, on the cone as
closely. The parameter runs over [0, 1], a span’s end at every
k / n. Refused where the radius could come to nothing.
Source§impl<const D: usize> NurbCurve<D>
impl<const D: usize> NurbCurve<D>
Sourcepub fn interpolate_homogeneous(
values: &[Vector<D>],
params: &[Scalar],
degree: usize,
) -> GeopResult<Self>
pub fn interpolate_homogeneous( values: &[Vector<D>], params: &[Scalar], degree: usize, ) -> GeopResult<Self>
The rational curve of degree whose homogeneous control points
interpolate the homogeneous points values — (w p, w), the point
p of weight w — at the strictly increasing params, from 0 to 1,
on the knots their averages give: it passes through each p at its
parameter. Curves interpolated at the same parameters share one knot
vector, so they are the rows of a surface skinned through curves
whose control points are the values (see
crate::nurb_surface::NurbSurface::try_new), its iso-curve at each
parameter exactly the curve there.
The values are solved for as they are: interval values carry their width into the control points (sharpen a value that is a free choice first). An error if a control point’s weight comes out not positive.
Source§impl NurbCurve<4>
impl NurbCurve<4>
Sourcepub fn cubic_spline(
points: &[Vector3],
start: Option<Vector3>,
end: Option<Vector3>,
) -> GeopResult<Self>
pub fn cubic_spline( points: &[Vector3], start: Option<Vector3>, end: Option<Vector3>, ) -> GeopResult<Self>
The cubic spline through points, in order: twice continuously
differentiable, parameterized by chord length (see
[chord_length_params]) with a knot at every point, so it bends
only as much as passing through them needs.
Each end leaves along its tangent start / arrives along end if
given — a direction, whose length does not matter — and is free
otherwise: straight there, its second derivative zero (a natural
spline). How fast it leaves along a given tangent is a free choice
of the spline’s shape; it is the speed the parameterization travels
at anyway, the polyline’s length per unit of parameter, worked out
from the points’ midpoints so that the same points always give the
same spline.
Two points and no tangents give the straight line between them.
Sourcepub fn interpolate(points: &[Vector3], degree: usize) -> GeopResult<Self>
pub fn interpolate(points: &[Vector3], degree: usize) -> GeopResult<Self>
Fit a 3-D NURBS curve exactly through points — see interpolate
above. Between the points it is only an approximation of whatever
curve they were sampled from; use Self::interpolate_enclosing
when the result has to enclose that curve.
Sourcepub fn interpolate_enclosing(
points: &[Vector3],
between: &[Vec<Vector3>],
degree: usize,
) -> GeopResult<Self>
pub fn interpolate_enclosing( points: &[Vector3], between: &[Vec<Vector3>], degree: usize, ) -> GeopResult<Self>
Fit a 3-D NURBS curve through samples points of some true curve
(through their centres; their full width is enclosed), widened so it
also encloses that curve between them: between[i] are
points of the true curve strictly between points[i] and
points[i + 1], in order and roughly evenly spaced (see
true_point_fractions for the usual choice of where).
An interpolant drifts from the curve it was sampled from between the samples. That drift is a genuine uncertainty about where the curve is, so it belongs in the curve’s interval width: otherwise a point on the true curve tests as not on its interpolant, and every containment or intersection question asked of it is answered for the wrong curve. A true point per interval measures it where it is largest; the enclosure is exact there and as good as that measurement in between.
Sourcepub fn drift_by_interval(
points: &[Vector3],
between: &[Vec<Vector3>],
degree: usize,
) -> GeopResult<Vec<f64>>
pub fn drift_by_interval( points: &[Vector3], between: &[Vec<Vector3>], degree: usize, ) -> GeopResult<Vec<f64>>
How far the true points between of each interval stick out of the
interpolant Self::interpolate_enclosing fits through points,
before it is widened, in the coordinate they stick out most: what
each interval asks of the one width the whole curve is then given.
Sourcepub fn enclosing_pad(
&self,
checks: &[(Scalar, Vector3)],
) -> GeopResult<[Scalar; 3]>
pub fn enclosing_pad( &self, checks: &[(Scalar, Vector3)], ) -> GeopResult<[Scalar; 3]>
How far, per coordinate, this curve has to be widened (see
Self::widen) to enclose every check’s point: (t, q), the true
point q, near the curve at about t — exactly as
Self::interpolate_enclosing widens an interpolant to enclose the
curve it was sampled from.
Source§impl NurbCurve<3>
impl NurbCurve<3>
Sourcepub fn interpolate(points: &[Vector2], degree: usize) -> GeopResult<Self>
pub fn interpolate(points: &[Vector2], degree: usize) -> GeopResult<Self>
Fit a 2-D NURBS curve exactly through points — see the 3-D
NurbCurve::interpolate.
Sourcepub fn interpolate_enclosing(
points: &[Vector2],
between: &[Vec<Vector2>],
degree: usize,
) -> GeopResult<Self>
pub fn interpolate_enclosing( points: &[Vector2], between: &[Vec<Vector2>], degree: usize, ) -> GeopResult<Self>
The 2-D counterpart of the 3-D NurbCurve::interpolate_enclosing.
Source§impl<const D: usize> NurbCurve<D>
impl<const D: usize> NurbCurve<D>
Sourcepub fn joined(&self, next: &Self) -> GeopResult<Self>
pub fn joined(&self, next: &Self) -> GeopResult<Self>
This curve and next, which starts where this one ends, as one
curve: the lower in degree raised to the other’s, next’s knots
carried on from where this one’s end, its homogeneous control points
scaled so its first weight is this one’s last — the same rational
curve — and the two joined by a knot of full multiplicity, so the
joined curve is exactly the two pieces, however they meet there.
The control point where they meet is the one both pieces had there:
two enclosures of one point, so what they share.
Fails for a knot vector not clamped where they meet, and for pieces that do not meet: their end and start control points apart.
Source§impl<const D: usize> NurbCurve<D>where
Self: Length,
impl<const D: usize> NurbCurve<D>where
Self: Length,
Sourcepub fn length(&self) -> GeopResult<(Scalar, bool)>
pub fn length(&self) -> GeopResult<(Scalar, bool)>
The curve’s length, ∫ |C'(t)| dt over its domain, enclosed, and
whether the quadrature converged (see Length).
Sourcepub fn parameter_at_length_fraction(&self, fraction: f64) -> GeopResult<Scalar>
pub fn parameter_at_length_fraction(&self, fraction: f64) -> GeopResult<Scalar>
The parameter t at which the fraction fraction of the curve’s
length, from its start, has been run, for fraction in (0, 1):
a sharp value, for a caller choosing where to cut the curve or put
something on it, found by bisection on the length up to t, which
grows with t. An error for a fraction outside (0, 1).
Source§impl NurbCurve<3>
impl NurbCurve<3>
Sourcepub fn offset_miss(
&self,
distance: Scalar,
(t0, t1): (Scalar, Scalar),
fit: &NurbCurve2D,
tolerance: Scalar,
) -> GeopResult<Scalar>
pub fn offset_miss( &self, distance: Scalar, (t0, t1): (Scalar, Scalar), fit: &NurbCurve2D, tolerance: Scalar, ) -> GeopResult<Scalar>
A bound on how far fit, over its domain, is from this curve’s
offset by distance to its left — fit at the start of its domain
standing for the offset of this curve at t0, at its end for that
at t1, affinely between — proven over boxes of fit’s domain,
each split in two while its bound exceeds tolerance (at most
[MISS_SPLITS] times; then the bound is only wider) and its middle
is not definitely further off than that. Fails where the curve has
no tangent.
Source§impl<const D: usize> NurbCurve<D>
impl<const D: usize> NurbCurve<D>
Sourcepub fn refine_parameter_at_point<const C: usize>(
&self,
t: Scalar,
point: &Vector<C>,
) -> Scalarwhere
NurbCurve<D>: ParameterRefinable<C>,
pub fn refine_parameter_at_point<const C: usize>(
&self,
t: Scalar,
point: &Vector<C>,
) -> Scalarwhere
NurbCurve<D>: ParameterRefinable<C>,
Refine t — a parameter enclosure produced by a subdivision search —
into the tightest enclosure of the parameter at which this curve
passes through point: Gauss-Newton on |C(t) - P|², by
newton::refine, which says why and how honestly.
A search’s parameter is as wide as the search’s tolerance; the
refined one is as narrow as the curve and the point pin it down, and
still an honest enclosure. Self::split_parameter_at_point cuts
within it.
A point nowhere near the curve, or a vanishing tangent, leaves t
as it is.
Sourcepub fn split_parameter_at_point<const C: usize>(
&self,
t: Scalar,
point: &Vector<C>,
within: &Vector<C>,
) -> GeopResult<Scalar>where
NurbCurve<D>: ParameterRefinable<C>,
pub fn split_parameter_at_point<const C: usize>(
&self,
t: Scalar,
point: &Vector<C>,
within: &Vector<C>,
) -> GeopResult<Scalar>where
NurbCurve<D>: ParameterRefinable<C>,
The parameter to split this curve at so a piece
ends at point, from a parameter t that isolates it: t refined
(Self::refine_parameter_at_point), then the sharp middle of it.
Where in the refined enclosure the curve is cut is a free choice.
point is an enclosure, and every parameter in the refined one
gives a piece that lies on the curve and ends within point, so
any of them serves; the check below asks it of the one chosen.
Cutting at the whole enclosure instead keeps every such piece in
one: the junction’s uncertainty along the curve becomes width in the
pieces’ control points (through α = (t - a) / (b - a) in Boehm
insertion) and in their end knots. The next cut’s parameter is then
found on the wider piece, comes out wider, and widens the next
pieces more: a quarter arc of radius 4 cut forty times went from
3e-15 wide to 0.08 (three times wider per cut), and a disc’s rim
cut by thirty-two slots ended 4e-10 off its cap. A sharp cut adds
the rounding of one subdivision per cut, nothing more.
A knot in the refined enclosure is the good choice there: the curve is cut at it as at the knot it already has, where cutting beside it would leave a piece a sliver of a span, a hair long, its control points one point and its end without a tangent (a chamfer’s contact edge cut where its run-out span begins, as a fillet then found).
Refused if t could be an end of the domain — then point is an
end of the curve, nothing to cut — or if the curve at the chosen
parameter does not reach within, the enclosure point was chosen
in: the vertex. Not point itself: a pcurve running from u = 1
has parameters a hair 1e-16 apart near its end, and a point of
u = 1e-4, sharper than that, no parameter reaches.
Source§impl<const D: usize> NurbCurve<D>
impl<const D: usize> NurbCurve<D>
Sourcepub fn reverse(&self) -> Self
pub fn reverse(&self) -> Self
This curve traversed in the opposite direction: same domain, same
shape, but reverse().evaluate(t) == evaluate(t_min + t_max - t).
Reverses the control points and complements the knot vector
(u -> t_min + t_max - u, then reversed) — the standard B-spline
reversal construction.
Source§impl<const D: usize> NurbCurve<D>
impl<const D: usize> NurbCurve<D>
Sourcepub fn split(&self, t: Scalar) -> GeopResult<(NurbCurve<D>, NurbCurve<D>)>
pub fn split(&self, t: Scalar) -> GeopResult<(NurbCurve<D>, NurbCurve<D>)>
Split at parameter t (must be strictly inside the domain).
Returns (left, right) sharing the junction value.
t is used exactly as given — it is not sharpened here. Whether
t’s width may be discarded belongs to whoever produced it, since
only they know what it means, so this function neither assumes nor
imposes an answer.
A wide t gives every piece the curve could be cut into there, in
one: its width flows into alpha = (t - e) / (s - e), into the
pieces’ control points and into their end knot. A piece cut again
is then found wider at the next cut, and its pieces wider still.
To end a piece at a point, cut at a sharp parameter of the refined
enclosure of where the curve reaches it
(NurbCurve::split_parameter_at_point): which one is a free
choice, and the pieces then carry no more than the rounding of
the cut.
See “Sharpen only where the value is a free choice” in AGENTS.md.
Sourcepub fn sub_curve(&self, t0: Scalar, t1: Scalar) -> GeopResult<NurbCurve<D>>
pub fn sub_curve(&self, t0: Scalar, t1: Scalar) -> GeopResult<NurbCurve<D>>
This curve restricted to [t0, t1], cut in one pass: each bound that
lies strictly inside the domain (definitely_greater the start /
definitely_less the end) is inserted to full multiplicity and the
outside is dropped; a bound that doesn’t is not cut at, so the result
always covers at least [t0, t1] ∩ domain. Cheaper than two
Self::splits: no discarded piece or intermediate curve is built.
Like split, the bounds are used exactly as given and must be narrow.
Sourcepub fn bezier_pieces(&self) -> GeopResult<Vec<Self>>
pub fn bezier_pieces(&self) -> GeopResult<Vec<Self>>
The curve cut at every interior knot: its polynomial (or rational) pieces, in order.
Sourcepub fn split_mid(&self) -> GeopResult<(NurbCurve<D>, NurbCurve<D>)>
pub fn split_mid(&self) -> GeopResult<(NurbCurve<D>, NurbCurve<D>)>
Split at the midpoint of the parameter domain.
Source§impl NurbCurve<4>
impl NurbCurve<4>
Sourcepub fn sweep(&self, offset: Vector3) -> NurbSurface3D
pub fn sweep(&self, offset: Vector3) -> NurbSurface3D
The ruled surface swept out by translating this curve along
offset: Scalar(t, s) = self.evaluate(t) + s * offset. Degree
(self.degree, 1) — same knot vector as self in t, s ∈ [0, 1] — control points [cp_i, cp_i + offset] row-major per original
control point cp_i.
Source§impl NurbCurve<4>
impl NurbCurve<4>
Sourcepub fn tangent(&self, t: Scalar) -> GeopResult<Vector3>
pub fn tangent(&self, t: Scalar) -> GeopResult<Vector3>
Cartesian tangent vector C'(t) (not normalized).
Sourcepub fn second_derivative(&self, t: Scalar) -> GeopResult<Vector3>
pub fn second_derivative(&self, t: Scalar) -> GeopResult<Vector3>
Cartesian second derivative C''(t) (not normalized). Nonzero for a
rational degree-1 curve too: a line with unequal weights is not
traversed at constant speed.
Source§impl NurbCurve<3>
impl NurbCurve<3>
Sourcepub fn tangent(&self, t: Scalar) -> GeopResult<Vector2>
pub fn tangent(&self, t: Scalar) -> GeopResult<Vector2>
Cartesian tangent vector C'(t) (not normalized), for a 2-D pcurve.
Sourcepub fn second_derivative(&self, t: Scalar) -> GeopResult<Vector2>
pub fn second_derivative(&self, t: Scalar) -> GeopResult<Vector2>
Cartesian second derivative C''(t) (not normalized), for a 2-D
curve.
Source§impl NurbCurve<3>
impl NurbCurve<3>
Sourcepub fn translate(&self, offset: Vector2) -> Self
pub fn translate(&self, offset: Vector2) -> Self
This 2-D curve shifted by offset (see NurbCurve::translate for
3-D). Shifted by an exact zero, it is the curve itself, not widened
by adding zeros.
Sourcepub fn lifted(&self) -> GeopResult<NurbCurve<4>>
pub fn lifted(&self) -> GeopResult<NurbCurve<4>>
This 2-D curve as a 3-D one in the plane z = 0.
Sourcepub fn embed(
&self,
origin: &Vector3,
e1: &Vector3,
e2: &Vector3,
) -> GeopResult<NurbCurve<4>>
pub fn embed( &self, origin: &Vector3, e1: &Vector3, e2: &Vector3, ) -> GeopResult<NurbCurve<4>>
This 2-D curve as the planar 3-D curve origin + x e1 + y e2 for
(x, y) along it: a sketch’s curve where its plane puts it. An
affine map of the homogeneous control points (w x, w y, w) to
(w origin + (w x) e1 + (w y) e2, w), so it is exact for any NURBS
and divides by no weight.
Source§impl NurbCurve<3>
impl NurbCurve<3>
Sourcepub fn within(
&self,
region: &Convex,
min_size: Scalar,
max_nodes: usize,
) -> GeopResult<Within>
pub fn within( &self, region: &Convex, min_size: Scalar, max_nodes: usize, ) -> GeopResult<Within>
Where the curve lies with respect to region: split until each
piece lies in it, lies past one of its sides, or has a point on
either side — or is smaller than min_size, along the boundary.
Visits at most max_nodes pieces: exhausting them is an error, the
answer being unknown.
Source§impl<const D: usize> NurbCurve<D>
impl<const D: usize> NurbCurve<D>
pub fn try_new( degree: usize, control_points: Vec<Vector<D>>, knot_vector: Vec<Scalar>, ) -> GeopResult<Self>
Sourcepub fn recompute_aabb(&mut self)
pub fn recompute_aabb(&mut self)
Refresh the cached Self::aabb from the current control_points.
Every constructor in this module keeps aabb in sync automatically,
but control_points is a pub field and at least one caller outside
this crate (Model::reverse_face, mirroring a pcurve’s control
points in place to flip a face) legitimately mutates it directly
rather than building a new curve — call this afterwards or the
cached box silently goes stale and the intersection search’s
aabb_could_overlap prefilter starts pruning real overlaps.
Sourcepub fn aabb(&self) -> &[Scalar; 3]
pub fn aabb(&self) -> &[Scalar; 3]
The box around the control points, and so around the curve (the
convex hull property): one enclosure per axis, holding every control
point’s coordinate there. Only the first D - 1 axes are the
curve’s; a 2-D curve’s third is Scalar::ENTIRE.
Sourcepub fn could_overlap(&self, other: &Self) -> bool
pub fn could_overlap(&self, other: &Self) -> bool
Whether the boxes around two curves’ control points could overlap:
false proves the curves apart.
Sourcepub fn size(&self) -> GeopResult<Scalar>
pub fn size(&self) -> GeopResult<Scalar>
The diagonal of the box around the control points: by the convex
hull property, a length the whole curve fits within (see
NurbSurface::size).
Sourcepub fn mid_parameter(&self) -> GeopResult<Scalar>
pub fn mid_parameter(&self) -> GeopResult<Scalar>
The middle of Self::domain, sharp: a parameter inside the curve
for a sample, a seed or a split point. Any value in the enclosure of
the middle serves as well, so it is a free choice, and sharpening it
loses nothing (see AGENTS.md).
Sourcepub fn breakpoints(&self) -> Vec<Scalar>
pub fn breakpoints(&self) -> Vec<Scalar>
Where the curve stops being one polynomial piece: the ends of its
domain and every distinct knot between them (see
[crate::spline::breakpoints]).
pub fn domain_as_scalar(&self) -> Scalar
Sourcepub fn everything() -> Self
pub fn everything() -> Self
A degenerate, maximally-unsharp curve: a single control point whose
every coordinate is Scalar::ENTIRE, over a domain that accepts
any parameter. evaluate() at any t returns ENTIRE in every
coordinate, so it could_be_equals any point — a placeholder for
geometry that is not yet known.
Source§impl NurbCurve<4>
impl NurbCurve<4>
Sourcepub fn as_line(&self) -> GeopResult<Option<Axis>>
pub fn as_line(&self) -> GeopResult<Option<Axis>>
The line the curve runs along, if it is straight: every control point
on the line from the first to the last, which is then the curve’s
direction. None for a curve that bends, or whose ends coincide.
Sourcepub fn as_arc(&self) -> GeopResult<Option<Arc>>
pub fn as_arc(&self) -> GeopResult<Option<Arc>>
The arc the curve traces, if it is a circular one: rational quadratic
pieces, each an exact arc, all of one circle — how the kernel builds
every arc and circle, and what splitting one leaves. None for any
other curve.
Trait Implementations§
Source§impl ParameterRefinable<2> for NurbCurve<3>
impl ParameterRefinable<2> for NurbCurve<3>
fn evaluate_cartesian(&self, t: Scalar) -> GeopResult<Vector<2>>
Source§fn tangent_cartesian(&self, t: Scalar) -> GeopResult<Vector<2>>
fn tangent_cartesian(&self, t: Scalar) -> GeopResult<Vector<2>>
tangent (the
quotient rule over the homogeneous derivative()).Source§impl ParameterRefinable<3> for NurbCurve<4>
impl ParameterRefinable<3> for NurbCurve<4>
fn evaluate_cartesian(&self, t: Scalar) -> GeopResult<Vector<3>>
Source§fn tangent_cartesian(&self, t: Scalar) -> GeopResult<Vector<3>>
fn tangent_cartesian(&self, t: Scalar) -> GeopResult<Vector<3>>
tangent (the
quotient rule over the homogeneous derivative()).