Skip to main content

NurbSurface

Struct NurbSurface 

Source
pub struct NurbSurface<const D: usize> {
    pub degree_u: usize,
    pub degree_v: usize,
    pub num_u: usize,
    pub num_v: usize,
    pub control_points: Vec<Vector<D>>,
    pub knot_vector_u: Vec<Scalar>,
    pub knot_vector_v: Vec<Scalar>,
    /* 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<D>>§knot_vector_u: Vec<Scalar>§knot_vector_v: Vec<Scalar>

Implementations§

Source§

impl NurbSurface<4>

Source

pub fn covering(&self, points: &[Vector3]) -> GeopResult<Self>

This surface carried on past its sides (NurbSurface3D::extended) until the foot point of each of points on it lies inside its domain, not on a side: a surface something is to lie on has to reach it. Carried on past a side each time by as much as the domain is long that way — how much is a free choice — and refused, naming the points, where it cannot be carried on far enough.

Source

pub fn carrying(&self, other: &Self) -> GeopResult<Option<(Self, UvMap)>>

Where the patch other lies on this surface, if it does: this surface carried on until it covers other (Self::covering), and the map from other’s (u, v) to its own — the very same surface, each parameter of other one of its own shifted and scaled, the way patches of one surface cut apart, mirrored or built again are, its normal the same way. None where other is another surface, faces the other way, or is parametrized otherwise.

The map comes from where other‘s corners are on this surface. That it holds everywhere is a polynomial identity: on each cell between the two patches’ knots, both are rational of the same degree p in each parameter, and two such that agree at 2p + 1 parameters in each direction agree everywhere — their difference, cleared of denominators, is a polynomial of degree 2p with more roots than its degree. So they are compared there, and only there; within the enclosures’ widths, as everything is.

Source§

impl NurbSurface<4>

Source

pub fn closest_point( &self, target: &Vector3, ) -> GeopResult<(Scalar, Scalar, Vector3)>

The point of the (untrimmed) surface nearest target, with its parameters.

Subdivision isolates, Newton refines: least_distance bounds the distance over every patch of the surface, so no dip of it is missed, and each patch left at the handoff is refined by damped Newton (see NurbSurface::refine_closest). The parameters are a free choice of which point of the surface is returned, so every iterate is sharp: the point returned lies on the surface, and its distance to target is attained.

Source

pub fn refine_closest( &self, target: &Vector3, u: Scalar, v: Scalar, ) -> GeopResult<(Scalar, Scalar)>

Damped Newton on the gradient of half the squared distance to target, from (u, v). The step solves the full Hessian where it is definitely positive definite — near the nearest point, where it converges quadratically — and the first fundamental form where not (Gauss–Newton, a descent direction wherever the parametrization is regular); at a pole, where that is singular too, it moves along the one parameter that still moves the point. A step that does not bring the point nearer is halved, and the search stops once halving does not help. Every iterate is clamped into the domain.

Source§

impl NurbSurface<4>

Source

pub fn curvature_radius( &self, u: Scalar, v: Scalar, ) -> GeopResult<Option<Scalar>>

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 (see Self::mean_curvature). 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

pub fn curvature_radius_along( &self, u: Scalar, v: Scalar, along: &Vector3, ) -> GeopResult<Option<Scalar>>

A radius of curvature at (u, v) along the tangent direction along — a unit vector in the tangent plane, the way a curve on the surface runs — or None if the surface runs straight that way: the normal curvature κ = (L a² + 2 M a b + N b²), where along = a Su + b Sv and L, M, N are the second fundamental form (Suu·n, Suv·n, Svv·n). Exact for any parametrization.

A curve on the surface turns, as far as the surface makes it, by this — where Self::curvature_radius, the tightest bend in any direction, says how a curve circling there would. At a cone’s apex the tightest bend has the radius of the circle round the axis, which goes to nothing; a generator running into the apex does not bend at all.

An error where Su × Sv vanishes (a pole), and where along leaves the tangent plane’s reach.

Source

pub fn normal_curvature( &self, u: Scalar, v: Scalar, along: &Vector3, ) -> GeopResult<Scalar>

The normal curvature at (u, v) along the unit tangent along, signed: positive where the surface bends towards its normal normalize(Su × Sv) (see Self::normal) that way, the inverse of the radius of the circle it bends round there, on that side (see Self::curvature_radius_along).

Source

pub fn normal_turning( &self, u: Scalar, v: Scalar, along: &Vector3, ) -> GeopResult<(Vector3, Vector3)>

The unit normal normalize(Su × Sv) at (u, v) and how fast it turns as the point moves on the surface at the velocity along, a tangent vector there: dn = (dN − n (n·dN)) / |N| for N = Su × Sv, dN = (Suu a + Suv b) × Sv + Su × (Suv a + Svv b), (a, b) the parameters’ rates that move the point at along (its components in Su, Sv, by the first fundamental form). Over a box of (u, v) an enclosure of both over the box.

Source

pub fn mean_curvature(&self, u: Scalar, v: Scalar) -> GeopResult<Scalar>

The mean curvature at (u, v), signed against the normal normalize(Su × Sv): half the trace of the second fundamental form relative to the first, H = (L G − 2 M F + N E) / (2 (E G − F²)), with E, F, G the first fundamental form and L, M, N the second (Suu·n, Suv·n, Svv·n). Unlike Self::curvature_radius it is exact for any parametrization, orthogonal or not.

An error where Su × Sv vanishes (a pole): the surface may well have a curvature there, but not one these partials can express.

Source§

impl NurbSurface<4>

Source

pub fn evaluate(&self, u: Scalar, v: Scalar) -> GeopResult<Vector3>

Evaluate the surface at (u, v), returning a 3-D Cartesian point.

Relative to one of the control points acting there ([centered]), so an interval (u, v) gives a point as wide as the patch is, not as far from the origin as it lies.

Over a box of (u, v) that is also the mean value form, the point at the box’s middle plus the partials over the box times the half widths, and the two intersected: both enclose the same points. Taken directly, A / w over a box divides two enclosures that cannot be correlated, and the quotient widens with how far the point lies from the span’s control point times how much the weight changes: a cylinder evaluated at a foot point a micron wide came out ten microns wide along its axis, and a shell’s copy of an edge each time eight times as wide as the edge.

Source§

impl NurbSurface<4>

Source

pub fn fill( sides: &[NurbCurve3D], neighbours: &[Option<Neighbour<'_>>], ) -> GeopResult<Fill>

The closed loop sides, three or more curves each starting where the one before ends, filled smoothly (see the module docs): patches that run exactly along every side, meet each other and the flat inner polygon tangentially, and meet the face neighbours[k] names along side k, if any, tangentially too — exactly where that face is flat, leaving it at TILT towards its normal where it is curved, as NurbSurface3D::tangent_to does.

Refused, naming the corner: a loop that turns back on itself, sides at a corner that do not lie in the tangent plane of a face given along either, and faces along two sides meeting there at an angle.

Source§

impl NurbSurface<4>

Source

pub fn fit_pcurve( &self, curve: &NurbCurve<4>, pin_start: Option<Vector2>, pin_end: Option<Vector2>, max_nodes: usize, min_subdivision_size: Scalar, ) -> GeopResult<NurbCurve2D>

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.

The pcurve is fitted piece by piece, the curve broken at each knot where it is less than twice continuously differentiable — the joints of the kernel’s rational arcs and helices, which are only C1 — and the pieces joined. The fit is a cubic spline, C2 at its own knots: across a jump in the trace’s curvature it cannot follow, and its drift there shrinks only as h^2 with the spacing of the samples. A helix of pitch 1 fitted across its joints drifted past the accuracy limit. Within a piece, the samples are doubled while the pcurve is wider than the target (see [DRIFT_PER_RESOLUTION]).

The walk’s first seed has to be found globally, because Newton only polishes a foot point it is already near: seeded from anywhere else on a curved patch it settles on whichever local foot point is closest — including one clamped against a domain bound, where the residual is merely orthogonal to the boundary — and every later sample, seeded from its predecessor, follows it there. So the start is pin_start when there is one (the face’s own authoritative (u, v)), and is otherwise located by Self::locate (max_nodes / min_subdivision_size bound its search). A curve that does not start on this surface is an error: there is no trace of it to fit.

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

pub fn affine_pcurve( &self, curve: &NurbCurve<4>, ) -> GeopResult<Option<NurbCurve2D>>

The pcurve of curve, which lies on this surface, where the surface is affine in (u, v) — degree one both ways, its four control points a parallelogram of one weight: then (u, v) are an affine function of the point, and the pcurve is curve itself with each control point mapped to them, of the same weights and knots. Exact, where a fit only encloses. None for any other surface.

Source§

impl NurbSurface<4>

Source

pub fn joined(&self, next: &Self, along_u: bool) -> GeopResult<Self>

This surface and next, which starts along u — along v, if not along_u — where this one ends, as one surface: next’s knots carried on from where this one’s end, its homogeneous control points scaled so that its first row’s weights are this one’s last row’s — the same rational surface — and the two joined by a knot of full multiplicity, so the joined surface is exactly the two pieces, however they meet. The row where they meet is what both had there: two enclosures of one row of points, so what they share. What the pieces of a wall swept along a profile are, one face per piece of the profile, joined into the wall.

Fails, saying why, for surfaces of other degrees or other knots across the join, knot vectors not clamped where they meet, and rows that do not meet.

Source§

impl NurbSurface<4>

Source

pub fn gordon( u_curves: &[NurbCurve3D], v_curves: &[NurbCurve3D], u_crossings: &[Vec<Scalar>], v_crossings: &[Vec<Scalar>], ) -> GeopResult<Self>

The Gordon surface through a network of curves on [0, 1]²: the curves u_curves along u, in order of increasing v, and v_curves along v, in order of increasing u, each u curve crossing each v curve once. u_crossings[j][i] is the parameter of u_curves[j] where it crosses v_curves[i], v_crossings[i][j] that of v_curves[i] there; both increase along each curve. Two curves at least in each direction.

Each curve is reparametrized (see NurbCurve::reparametrized) so that the curves cross at common parameters, chosen as common_parameters says, and cut to the stretch between its first and last crossing: the surface’s sides are the first and last curves of each family. The crossing points are each the union of where the two curves have them — an error naming both if they could not be one point. The surface is then the sum of the two skinned surfaces less the tensor-product one, each of degree three across the curves, or lower for fewer than four. Its border rows are the outer curves’ control points, as NurbSurface3D::coons’ are, so it interpolates them exactly, and with two curves each way it is their Coons patch.

Across a crossed curve the surface is as smooth as the curves’ reparametrizations: where they cross every curve at the same fraction of its length, smooth; otherwise each curve’s speed changes there, and the surface may bend across it.

Source§

impl NurbSurface<4>

Source

pub fn partials( &self, u: Scalar, v: Scalar, order: usize, ) -> GeopResult<Vec<Vec<Vector3>>>

Every partial derivative up to order at (u, v): S[k][l] is ∂^(k+l) S / ∂u^k ∂v^l for k + l <= order, S[0][0] the point. Each homogeneous partial is the span’s own net differenced k times along u and l times along v ([hodograph]), then evaluated; the Cartesian ones follow by the rational quotient rule (Piegl & Tiller algorithm A4.4). Relative to a control point of the span, as everything here is (see [centered]).

Differenced first, then evaluated, so that an interval (u, v) box gives an enclosure as wide as the box times the next derivative. Evaluated first along v and differenced along u afterwards, as Self::second_derivatives does for its mixed partial, the rows’ values over the box are independent intervals, and their third differences, divided by the span’s width cubed, came out a hundred times wider than the partial itself.

A box reaching over a knot is taken span by span, and the partials over the pieces united (see [find_spans]): one span’s polynomial carried on over the next encloses nothing of the surface there, and a proof over the box (NurbSurface3D::enclose_foot) would prove nothing.

Source

pub fn derivatives( &self, u: Scalar, v: Scalar, ) -> GeopResult<(Vector3, Vector3)>

Partial derivatives (∂Scalar/∂u, ∂Scalar/∂v) at (u, v).

Source

pub fn second_derivatives( &self, u: Scalar, v: Scalar, ) -> GeopResult<[Vector3; 3]>

Second partial derivatives [∂²S/∂u², ∂²S/∂u∂v, ∂²S/∂v²] at (u, v). The pure ones by the rational quotient rule along their own direction ([rational_derivatives]); the mixed one by differentiating A = w Scalar once along each: A_uv = w_uv Scalar + w_u S_v + w_v S_u + w S_uv.

Source

pub fn pole_at(&self, u: Scalar, v: Scalar) -> Option<Vector3>

The pole (u, v) lies on: the point a boundary row it touches collapses to. None if it touches no pole.

Source

pub fn normal(&self, u: Scalar, v: Scalar) -> GeopResult<Vector3>

Unit surface normal at (u, v), normalize(∂Scalar/∂u × ∂Scalar/∂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.

At a pole — (u, v) touching a boundary row that collapses to a point — S_u × S_v vanishes, and the normal is that of the pole itself (Self::pole_normal), the same for every u (v) along the row. At a corner whose boundary curves leave it along one line, it is the corner’s (Self::corner_normal). Anything else where the cross product vanishes has no normal and is an error.

Source

pub fn normal_at( &self, point: &Vector3, max_nodes: usize, min_subdivision_size: Scalar, ) -> GeopResult<Vector3>

The unit normal of this surface at point, which lies on it: at its (u, v) there (Self::locate). An error if the point is not on the surface.

Source§

impl NurbSurface<4>

Source

pub fn offset(&self, distance: Scalar) -> GeopResult<Option<Self>>

The surface distance along its normal — against it for a negative distance — with the same parametrization: the offset of the point at (u, v) is at (u, v) of the result, so a trim loop’s (u, v) stay meaningful on it. An apex is the exception (see the module docs): it goes to the offset’s apex, at the same (u, v), and along the lines from it the points move along those lines too.

None where the offset collapses: every point of the surface passes its centre of curvature, as a cylinder shrunk past its axis, a sphere or a torus’s tube past its centre, so the offset has no area — a blend narrower than the walls a shell builds are thick. Fails where only part of it would, where the offset folds, and at an apex whose offset does not close in a point.

Source§

impl NurbSurface<4>

Source

pub fn has_normal_everywhere(&self) -> GeopResult<bool>

Whether the surface has a normal everywhere: its normal field, a pole’s factor divided out, definitely not zero over every span, each split in four up to [REGULAR_SPLITS] times. false where it vanishes, or could: a patch carried on so far that it folds back on itself, its offset no surface.

Source§

impl NurbSurface<4>

Source

pub fn ruled(a: &NurbCurve3D, b: &NurbCurve3D) -> GeopResult<Self>

The ruled surface between a, at v = 0, and b, at v = 1: the straight lines joining their points of equal parameter, both brought onto [0, 1], which is the surface’s u. Its sides along u are the two curves exactly.

Source

pub fn ruled_compatible(a: &NurbCurve3D, b: &NurbCurve3D) -> GeopResult<Self>

The ruled surface between a, at v = 0, and b, at v = 1, which are already alike — of one degree, as many control points and knots — taken as they are: its u runs over a‘s knots, not brought onto [0, 1]. Exact, since its sides along u are the curves’ own control points.

Source

pub fn bilinear( p00: Vector3, p10: Vector3, p11: Vector3, p01: Vector3, ) -> GeopResult<Self>

The bilinear patch with corners p00 at (u, v) = (0, 0), p10 at (1, 0), p11 at (1, 1) and p01 at (0, 1), over [0, 1]² — a plane’s, where the four lie in one.

Source

pub fn coons(sides: [&NurbCurve3D; 4]) -> GeopResult<Self>

The bilinearly blended Coons patch bounded by sides, which run round it end to end: from the corner at (0, 0) along v = 0 to (1, 0), up u = 1 to (1, 1), back along v = 1 to (0, 1) and down u = 0 to the start — counter-clockwise in (u, v), so the patch’s normal is the one the loop winds around.

The patch is the sum of the ruled surfaces between opposite sides, less the bilinear patch of the corners, each laid on one control net (the Greville abscissae turn a linear blend into control points exactly): opposite sides are made compatible, and each blend is taken of the homogeneous control points, so rational sides are fine. Its border rows are the sides’ control points themselves — that is what the blend gives there exactly; taking them as they are only leaves out the rounding of adding and taking away the same corners — so the patch interpolates its sides exactly, and its corners are the union of the two sides meeting there.

The sides must end where the next one starts, with their end weights one (or a single span, which can be brought to it): two sides meeting at a corner must agree on its weight for the blend to meet them both.

Source

pub fn elevate_degree(&self, along_u: bool) -> GeopResult<Self>

The same surface one degree higher in u (along_u) or in v, for knots clamped at both ends: each row of control points along that direction raised as a curve (see NurbCurve::elevate_degree).

Source

pub fn extended( &self, along_u: bool, at_end: bool, by: Scalar, ) -> GeopResult<Self>

The same surface carried on by by of its u (along_u) or v parameter past the end (at_end) or before the start of that domain: each row of control points along that direction continued as a curve (see NurbCurve::extended), so the surface goes on as the polynomial it is there, as smooth as it was.

Source

pub fn tangent_to(&self, sides: &[Option<Neighbour<'_>>; 4]) -> GeopResult<Self>

The same patch made tangent along each side k (numbered as NurbSurface3D::coons numbers them) that has a sides[k], to the face it names — a face ending at the side, the patch continuing it smoothly.

The patch is first raised in degree until each such side has a row of its own next to it, not next to the opposite side. Each control point of that row is then moved, along the face’s normal, into the face’s tangent plane where the side is at that point’s Greville abscissa: the derivative across a side is a combination of that row less the side, so a row in the tangent plane makes the patch tangent there — along a curved face, that plane leant by TILT (see below). Where two such rows cross, the point goes onto the line the two planes meet in.

Along a flat face the tangent plane is one plane, and the patch is tangent to it exactly — rational or not. Along a curved one it turns, and no NURBS row follows it exactly: the row is set so that the patch leaves the face at TILT towards the face’s normal at each of those abscissae — where an approximate tangency would dip through the face as often as not, a crease of a millionth of a radian keeps the patch on one side of it — and proven over the whole side to stay within twice that, on that side (NurbSurface3D::strays), the patch refined along the side while it does not.

The ends of that row are the second control points of the sides meeting this one: they are curves the patch must keep, so they are not moved, and must lie in the tangent plane at the corner — a side leaving it there contradicts the tangency, and is refused. So is a row that does not head away from the face: the patch would fold back over the face it is to continue.

Source§

impl NurbSurface<4>

Source

pub fn project( &self, target: Vector3, u0: Scalar, v0: Scalar, iterations: usize, ) -> GeopResult<(Scalar, Scalar)>

Fixed-iteration-count Newton foot-point projection of target onto this surface, starting from (u0, v0).

Each step is Newton’s on the squared distance: it solves the 2x2 system H·Δ = r where r = (residual·Su, residual·Sv) and H its Hessian, [[Su·Su − residual·Suu, Su·Sv − residual·Suv], [·, Sv·Sv − residual·Svv]], then clamps (u, v) into domain_u() / domain_v() before the next iteration. Where H is not definitely positive definite — far from the foot point, or by a pole — the step is Gauss-Newton’s, H without the second derivatives (the first fundamental form). Without them always, the step over a curved surface overshoots by the target’s distance over the surface’s radius of curvature: a target twice the radius off a cylinder, a ball rounding a fill of its own radius, swung from side to side of its foot point for ever. 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 repeats one before it exactly — a fixed point, or a cycle in the last bit (iterate).

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). There only the collapsed parameter stops moving the point; the other still does, so that iteration takes the Newton step along it alone — down the meridian, off the pole, after which the full step takes over. A seed on the pole would otherwise never leave it, whatever the target. For a target at the pole that step is zero, and (u, v) stays. Where neither derivative is definitely nonzero, no step is taken, and (u, v) is the seed the previous iteration left.

Source

pub fn locate( &self, point: &Vector3, max_nodes: usize, min_subdivision_size: Scalar, ) -> GeopResult<Option<(Scalar, Scalar)>>

Where point, a point of this surface, lies on it: its (u, v), or None if it is not on the surface.

Subdivide to isolate, then Newton to refine (see AGENTS.md): the containment search (surface_could_contain, with max_nodes and min_subdivision_size bounding its effort) proves the point lies in a box only min_subdivision_size narrow, and Self::project from the box’s midpoint polishes that to what the point’s own width allows. Both enclose the same foot point, so the answer is their intersection. The refinement only tightens: where Newton fails or leaves the box, the box is the answer.

A point that could be a pole lies on the row collapsing to it (see AGENTS.md, “A pole is one vertex at many (u, v)”), whatever Newton settled on beside it — where the Jacobian is singular, it stops where it can step no further, a hair off the row: the row’s parameter is kept as one of the point’s too.

Source

pub fn enclose_foot( &self, target: &Vector3, near: (Scalar, Scalar), ) -> GeopResult<Option<(Scalar, Scalar)>>

The (u, v) of the foot point of every point of the box target near near, proven: a box in which each of those points has one foot point and no other, None where that cannot be shown.

A foot point is where the gradient of half the squared distance, g = (Su·(S − P), Sv·(S − P)), vanishes. Krawczyk’s operator K(X) = c − Y g(c) + (I − Y J(X)) (X − c), with J the Hessian of the distance over the box X (and every P of target), c its middle and Y the inverse of the Hessian at c — a preconditioner, a free choice — holds every zero of g in X; where it lies within X, there is exactly one, and it is in K(X) too. X starts as twice the Newton step from near and grows where K reaches past it, a few times: how hard the search tries. It must keep within the domain, so a target whose foot is on the domain’s edge needs the surface carried on past it (Self::extended).

Over a box of targets along a curve on the surface, the box found holds the curve’s (u, v) over that stretch: what the normal and its turn are enclosed over (see Self::normal_turning).

Source

pub fn foot_moving( &self, target: &Vector3, velocity: &Vector3, near: (Scalar, Scalar), ) -> GeopResult<Option<MovingFoot>>

The foot point of a point moving through the box target at velocity, near near, proven as Self::enclose_foot proves it, and how it moves: None where that cannot be shown.

The foot keeps the gradient g of half the squared distance zero, so H (u', v') = (Su·P', Sv·P') for the Hessian H of that distance: the foot moves at Su u' + Sv v', and the normal there turns as Self::normal_turning says for that motion. Over the box of feet, every one an enclosure over the whole box of targets.

Source

pub fn project_in_domain( &self, target: Vector3, u0: Scalar, v0: Scalar, iterations: usize, ) -> GeopResult<(Scalar, Scalar)>

The foot point of target as Self::project finds it, within the surface’s domain: the foot point lies on the patch, so its (u, v) are in the domain as much as where the projection says, and both enclosures hold at once.

Source§

impl<const D: usize> NurbSurface<D>

Source

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 NurbSurface<4>

Source

pub fn revolve( profile: &NurbCurve3D, axis: &Axis, from: f64, to: f64, ) -> GeopResult<Self>

The surface profile sweeps turning about axis from the angle from to the angle to, in radians, right-handed about axis.direction: the profile as it is at angle 0, turned.

u is the turn, on [0, 1]: rational quadratic pieces of equal angle, each at most a quarter turn, the way crate::shape::Arc builds an arc. v is the profile’s own parameter. So Su x Sv points away from the axis where the profile runs along axis.direction.

A profile control point on the axis stays put at every angle: its row collapses to one point, a pole of the parametrization.

The angles of the joints between pieces are free choices, taken in f64 as crate::shape::Arc::point_at does: any angle serves equally well, and each joint lies honestly on the circle it is built from. Fails unless to is more than from, by at most a full turn.

Source§

impl NurbSurface<4>

Source

pub fn spherical_triangle( center: &Vector3, radius: Scalar, corners: [Vector3; 3], ) -> GeopResult<Self>

The piece of the sphere of radius around center bounded by the great arcs between its points corners, each less than a half turn from the others: exactly, a rational patch of degree 4 along u and 2 along v.

v = 0 is the great arc from the first corner (u = 0) to the second (u = 1), in its usual form — the rational quadratic whose middle control point is where the arc’s end tangents meet, weighted by the cosine of half its angle (degree-raised, which leaves it the same curve). Every u-line is the great arc from that arc’s point to the third corner, which v = 1 collapses to: a pole. So u = 0 and u = 1 are the great arcs from the first and the second corner to the third, again each in its usual form. The normal faces out of the sphere where the corners run counter-clockwise, seen from outside.

Why it is exact: the great arc from a point B to P, with b = B - C and p = P - C, has weights (1, cos(t/2), 1), t the angle between them — or any (a, m, c) with m^2 / (a c) = (1 + cos t) / 2. Along the base arc B(u) = X(u) / beta(u), X and beta quadratic, 1 + cos t = gamma / (beta r^2) with gamma = beta r^2 + (X - beta C) · p, also quadratic. Then the row (X, beta), tau (C delta + r^2 (X + beta p), gamma) (delta = gamma - beta r^2, the middle point where the tangents meet, weighted tau gamma), 2 r^2 tau^2 gamma (P, 1) has that ratio for every u and any tau(u); tau linear, chosen so that both side arcs come out in their usual form, makes the row polynomial of degree at most 4.

The rows multiply the center and the corners by weights and by each other, so it is built about the center’s middle, a free choice of origin, and moved back by one rounding: built where it lies, a corner of a block at (1e4, 1e4, 0) came out with control points 8e-4 wide, and at 1e5 with no normal left at all.

Source§

impl<const D: usize> NurbSurface<D>

Source

pub fn split_u(&self, t: Scalar) -> GeopResult<(NurbSurface<D>, NurbSurface<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.

Source

pub fn split_v(&self, t: Scalar) -> GeopResult<(NurbSurface<D>, NurbSurface<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.

Source

pub fn iso_curve(&self, u_fixed: bool, at: Scalar) -> GeopResult<NurbCurve<D>>

The iso-line of the surface at u = at (u_fixed), or at v = at, as a curve along the other parameter, on its knots: a row of the control net, exactly, where at is a clamped end of the domain or a knot of full multiplicity, and otherwise the row knot insertion makes there. at is used exactly as given, as by Self::split_u. Fails for an at outside the domain, or at an end that is not clamped.

Source

pub fn split_u_mid(&self) -> GeopResult<(NurbSurface<D>, NurbSurface<D>)>

Split at the midpoint of the u domain.

Source

pub fn split_v_mid(&self) -> GeopResult<(NurbSurface<D>, NurbSurface<D>)>

Split at the midpoint of the v domain.

Source

pub fn sub_surface( &self, (u0, u1): (Scalar, Scalar), (v0, v1): (Scalar, Scalar), ) -> 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 NurbSurface<4>

Source

pub fn translate(&self, offset: Vector3) -> Self

This surface shifted by offset — same shape and parametrization, every point moved by offset. Translates each (homogeneous) control point by offset * weight, leaving weights and knot vectors untouched.

Source

pub fn transform(&self, motion: &Motion) -> Self

This surface moved by motion — a rigid motion, a translation or a mirror — with the same parametrization, so every pcurve drawn on it still traces the same patch: each control point moved (see [crate::nurb_curve::transform_control_point]), weights and knots untouched.

A mirror turns Su x Sv against the mirrored surface’s material side: the normal of the result is the mirrored normal, negated. What the surface bounds has to be turned around for that (see Model::transform_body).

Source§

impl<const D: usize> NurbSurface<D>

Source

pub fn try_new( degree_u: usize, degree_v: usize, control_points: Vec<Vector<D>>, knot_vector_u: Vec<Scalar>, knot_vector_v: Vec<Scalar>, ) -> GeopResult<Self>

Source

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).

Source

pub fn widen(&mut self, pad: Scalar)

This surface widened by ±pad in every coordinate, everywhere: every control point by that much (times its weight, in homogeneous form), as crate::nurb_curve::NurbCurve::widen widens a curve — the basis is non-negative and sums to one, so the widened patch holds every point within pad of the patch along each axis.

Source

pub fn aabb(&self) -> &[Scalar; 3]

The box around the control points, and so around the patch (see crate::nurb_curve::NurbCurve::aabb).

Source

pub fn could_overlap(&self, other: &Self) -> bool

Whether the two patches could share a point: false proves they do not, as the boxes around their control points — which hold each whole patch, by the convex hull property — are apart.

Source

pub fn size(&self) -> GeopResult<Scalar>

The diagonal of the box around the control points: by the convex hull property, a length the whole patch fits within — the size of the feature it is part of, for choices that should scale with it.

Source

pub fn domain_u(&self) -> (Scalar, Scalar)

Valid parameter range in the u direction: (u_min, u_max).

Source

pub fn domain_v(&self) -> (Scalar, Scalar)

Valid parameter range in the v direction: (v_min, v_max).

Source

pub fn breakpoints_u(&self) -> Vec<Scalar>

Where the surface stops being one polynomial piece along u: the ends of its domain and every distinct knot between them.

Source

pub fn breakpoints_v(&self) -> Vec<Scalar>

Like Self::breakpoints_u, along v.

Source

pub fn could_be_equal(&self, other: &Self) -> bool

Whether other could be the very same patch: equal degrees, and knots and homogeneous control points that could_be_equal pairwise.

A test of the representation, so it is exact where it answers true — two patches built the same way, e.g. two copies of one solid — and says nothing about the same surface parametrized differently.

Source

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.

Source

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 NurbSurface<4>

Source

pub fn as_plane(&self) -> GeopResult<Option<Plane>>

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.

Source

pub fn axis_of_revolution(&self) -> GeopResult<Option<Axis>>

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.

Source

pub fn as_round_wall(&self) -> GeopResult<Option<RoundWall>>

The round wall the surface lies on, if it is a piece of one: a surface of revolution (see NurbSurface3D::axis_of_revolution) whose rows of control points are arcs whose centres along the axis and radii lie on one straight line — the revolution of a straight profile, with no row collapsed to a pole. Every point of the surface is a blend of the rows with the same weights for its height and its radius, so it lies on that line too. A cylinder has no taper; None for a sphere, a disc, a torus or any other surface.

Trait Implementations§

Source§

impl<const D: usize> Clone for NurbSurface<D>

Source§

fn clone(&self) -> NurbSurface<D>

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<const D: usize> Debug for NurbSurface<D>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<const D: usize> Exact for NurbSurface<D>

Source§

fn write<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>

Source§

fn read<'de, De: Deserializer<'de>>(deserializer: De) -> Result<Self, De::Error>

Source§

impl<const D: usize> PartialEq for NurbSurface<D>

Source§

fn eq(&self, other: &NurbSurface<D>) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl<const D: usize> StructuralPartialEq for NurbSurface<D>

Auto Trait Implementations§

§

impl<const D: usize> Freeze for NurbSurface<D>

§

impl<const D: usize> RefUnwindSafe for NurbSurface<D>

§

impl<const D: usize> Send for NurbSurface<D>

§

impl<const D: usize> Sync for NurbSurface<D>

§

impl<const D: usize> Unpin for NurbSurface<D>

§

impl<const D: usize> UnsafeUnpin for NurbSurface<D>

§

impl<const D: usize> UnwindSafe for NurbSurface<D>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.