///////////////////////////////////////////////////////////////////// // LibFile: beziers.scad // B-Splines and Non-uniform Rational B-Splines (NURBS) are a way to represent smooth curves and smoothly curving // surfaces with a set of control points. The curve or surface is defined by // the control points and a set of "knot" points. The NURBS can be "clamped" in which case the curve passes through // the first and last point, or they can be "closed" in which case the first and last point are coincident. Also possible // are "open" curves which do not necessarily pass through any of their control points. Unlike Bezier curves, a NURBS // can have an unlimited number of control points and changes to the control points only affect the curve locally. // // Includes: // include // include // FileGroup: Advanced Modeling // FileSummary: NURBS and B-spline curves and surfaces. ////////////////////////////////////////////////////////////////////// include include // Section: NURBS Curves // Function: nurbs_curve() // Synopsis: Computes one more more points on a NURBS curve. // SynTags: Path // Topics: NURBS Curves // See Also: debug_nurbs() // Usage: // pts = nurbs_curve(control, degree, splinesteps, [mult=], [weights=], [type=], [knots=]); // pts = nurbs_curve(control, degree, u=, [mult=], [weights=], [type=], [knots=]); // Description: // Compute the points specified by a NURBS curve. You specify the NURBS by supplying the control points, knots and weights. and knots. // Only the control points are required. The knots and weights default to uniform, in which case you get a uniform B-spline. // You can specify endpoint behavior using the `type` parameter. The default, "clamped", gives a curve which starts and // ends at the first and last control points and moves in the tangent direction to the first and last control point segments. // If you request an "open" spline you get a curve which starts somewhere in the middle of the control points. // Finally, a "closed" curve is a one that starts where it ends. Note that each of these types of curve require // a different number of knots. // . // The control points are the most important control over the shape // of the curve. You must have at least p+1 control points for clamped and open NURBS. Unlike a bezier, there is no maximum // number of control points. A single NURBS is more like a bezier **path** than like a single bezier spline. // . // A NURBS or B-spline is a curve made from a moving average of several Bezier curves. The knots specify when one Bezier fades // away to be replaced by the next one. At generic points, the curves are differentiable, but by increasing knot multiplicity, you // can decrease smoothness, or even produce a sharp corner. The knots must be an ascending sequence of values, but repeating values // is OK and controls the smoothness at the knots. The easiest way to specify the knots is to take the default of uniform knots, // and simply set the multiplicity to create repeated knots as needed. The total number of knots is then the sum of the multiplicity // vector. Alternatively you can simply list the knots yourself. Note that regardless of knot values, the domain of evaluation // for u is always the interval [0,1], and it will be scaled to give the entire valid portion of the curve you have chosen. // If you give both a knot vector and multiplicity then the multiplicity vector is appled to the provided knots. // For an open spline the number of knots must be `len(control)+p+1`. For a clamped spline the number of knots is `len(control)-p+1`, // and for a closed spline you need `len(control)+1` knots. If you are using the default uniform knots then the way to // ensure that you have the right number is to check that `sum(mult)` is either not set or equal to the correct value. // . // You can use this function to evaluate the NURBS at `u`, which can be a single point or a list of points. You can also // use it to evaluate the NURBS over its entire domain by giving a splinesteps value. This specifies the number of segments // to use between each knot and guarantees a point exactly at each knot. This may be important if you set the knot multiplicity // to the degree somewhere in your curve, which creates a corner at the knot, because it guarantees a sharp corner regardless // of the number of points. // Arguments: // control = list of control points in any dimension // degree = degree of NURBS // splinesteps = evaluate whole spline with this number of segments between each pair of knots // --- // u = list of values or range in the interval [0,1] where the NURBS should be evaluated // mult = list of multiplicities of the knots. Default: all 1 // weights = vector whose length is the same as control giving weights at each control point. Default: all 1 // type = One of "clamped", "closed" or "open" to define end point handling of the spline. Default: "clamped" // knots = List of knot values. Default: uniform // Example(2D,NoAxes): Simple quadratic uniform clamped b-spline with some points computed using splinesteps. // pts = [[13,43],[30,52],[49,22],[24,3]]; // debug_nurbs(pts,2); // npts = nurbs_curve(pts, 2, splinesteps=3); // color("red")move_copies(npts) circle(r=1); // Example(2D,NoAxes): Simple quadratic uniform clamped b-spline with some points computed using the u parameter. Note that a uniform u parameter doesn't necessarily sample the curve uniformly. // pts = [[13,43],[30,52],[49,22],[24,3]]; // debug_nurbs(pts,2); // npts = nurbs_curve(pts, 2, u=[0:.2:1]); // color("red")move_copies(npts) circle(r=1); // Example(2D,NoAxes): Same control points, but cubic // pts = [[13,43],[30,52],[49,22],[24,3]]; // debug_nurbs(pts,3); // Example(2D,NoAxes): Same control points, quadratic and closed // pts = [[13,43],[30,52],[49,22],[24,3]]; // debug_nurbs(pts,2,type="closed"); // Example(2D,NoAxes): Same control points, cubic and closed // pts = [[13,43],[30,52],[49,22],[24,3]]; // debug_nurbs(pts,3,type="closed"); // Example(2D,NoAxes): Ten control points, quadratic, clamped // pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; // debug_nurbs(pts,2); // Example(2D,NoAxes): Same thing, degree 4 // pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; // debug_nurbs(pts,4); // Example(2D,NoAxes): Same control points, degree 2, open. Note it doesn't reach the ends // pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; // debug_nurbs(pts,2, type="open"); // Example(2D,NoAxes): Same control points, degree 4, open. Note it starts farther from the ends // pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; // debug_nurbs(pts,4,type="open"); // Example(2D,NoAxes): Same control points, degree 2, closed // pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; // debug_nurbs(pts,2,type="closed"); // Example(2D,NoAxes): Same control points, degree 4, closed // pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; // debug_nurbs(pts,4,type="closed"); // Example(2D,NoAxes): Adding weights // pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; // weights = [1,1,1,3,1,1,3,1,1,1]; // debug_nurbs(pts,4,type="clamped",weights=weights); // Example(2D,NoAxes): Using knot multiplicity with quadratic clamped case. Knot count is len(control)-degree+1 = 9. The multiplicity 2 knot creates a corner for a quadratic. // pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; // mult = [1,1,1,2,1,1,1,1]; // debug_nurbs(pts,2,mult=mult,show_knots=true); // Example(2D,NoAxes): Using knot multiplicity with quadratic clamped case. Two knots of multiplicity 2 gives two corners // pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; // mult = [1,1,1,2,2,1,1]; // debug_nurbs(pts,2,mult=mult,show_knots=true); // Example(2D,NoAxes): Using knot multiplicity with cubic clamped case. Knot count is now 8. We need multiplicity equal to degree (3) to create a corner. // pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; // mult = [1,3,1,1,1,1]; // debug_nurbs(pts,3,mult=mult,show_knots=true); // Example(2D,NoAxes): Using knot multiplicity with cubic closed case. Knot count is now len(control)+1=11. Here are three corners. // pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; // mult = [1,3,1,3,3]; // debug_nurbs(pts,3,mult=mult,type="closed",show_knots=true); // Example(2D,NoAxes): Explicitly specified knots only change the quadratic clamped curve slightly. Knot count is len(control)-degree+1 = 9. // pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; // knots = [0,1,3,5,9,13,14,19,21]; // debug_nurbs(pts,2); // Example(2D,NoAxes): Combining explicit knots with mult for the quadratic curve to add a corner // pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; // knots = [0,1,3,9,13,14,19,21]; // mult = [1,1,1,2,1,1,1,1]; // debug_nurbs(pts,2,knots=knots,mult=mult); // Example(2D,NoAxes): Directly repeating a knot in the knot list to create a corner for a cubic spline // pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; // knots = [0,1,3,13,13,13,19,21]; // debug_nurbs(pts,3,knots=knots); // Example(2D,NoAxes): Open cubic spline with explicit knots // pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; // knots = [0,1,3,13,13,13,19,21,27,28,29,40,42,44]; // debug_nurbs(pts,3,knots=knots,type="open"); // Example(2D,NoAxes): Closed quintic spline with explicit knots // pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; // knots = [0,1,3,13,13,13,19,21,27,28,33]; // debug_nurbs(pts,5,knots=knots,type="closed"); // Example(2D,NoAxes): Closed quintic spline with explicit knots and weights // pts = [[5,0],[0,20],[33,43],[37,88],[60,62],[44,22],[77,44],[79,22],[44,3],[22,7]]; // weights = [1,2,3,4,5,6,7,6,5,4]; // knots = [0,1,3,13,13,13,19,21,27,28,33]; // debug_nurbs(pts,5,knots=knots,weights=weights,type="closed"); // Example(2D,NoAxes): Circular arcs are possible with NURBS. This example gives a semi-circle // control = [[1,0],[1,2],[-1,2],[-1,0]]; // w = [1,1/3,1/3,1]; // debug_nurbs(control, 3, weights=w, width=0.1, size=.2); // Example(2D,NoAxes): Gluing two semi-circles together gives a whole circle. Note that this is a clamped not closed NURBS. The interface uses a knot of multiplicity 3 where the clamped ends of the semi-circles meet. // control = [[1,0],[1,2],[-1,2],[-1,0],[-1,-2],[1,-2],[1,0]]; // w = [1,1/3,1/3,1,1/3,1/3,1]; // debug_nurbs(control, 3, splinesteps=16,weights=w,mult=[1,3,1],width=.1,size=.2); // Example(2D,NoAxes): Circle constructed with type="closed" // control = [[1,0],[1,2],[-1,2],[-1,0],[-1,-2],[1,-2]]; // w = [1,1/3,1/3,1,1/3,1/3]; // debug_nurbs(control, 3, splinesteps=16,weights=w,mult=[1,3,3],width=.1,size=.2,type="closed",show_knots=true); function nurbs_curve(control,degree,splinesteps,u, mult,weights,type="clamped",knots) = assert(num_defined([splinesteps,u])==1, "Must define exactly one of u and splinesteps") is_finite(u) ? nurbs_curve(control,degree,[u],mult,weights,type=type)[0] : assert(is_undef(splinesteps) || (is_int(splinesteps) || splinesteps>0), "splinesteps must be a positive integer") let(u=is_range(u) ? list(u) : u,f=echo(u=u)) assert(is_undef(u) || (is_vector(u) && min(u)>=0 && max(u)<=1), "u must be a list of points on the interval [0,1] or a range contained in that interval") is_def(weights) ? assert(is_vector(weights, len(control)), "Weights should be a vector whose length is the number of control points") let( dim = len(control[0]), control = [for(i=idx(control)) [each control[i]*weights[i],weights[i]]], curve = nurbs_curve(control,degree,u=u,splinesteps=splinesteps, mult=mult,type=type) ) [for(pt=curve) select(pt,0,-2)/last(pt)] : let( uniform = is_undef(knots), dum=assert(in_list(type, ["closed","open","clamped"]), str("Unknown nurbs spline type", type)) assert(type=="closed" || len(control)>=degree+1, str(type," nurbs requires at least degree+1 control points")) assert(is_undef(mult) || is_vector(mult), "mult must be a vector"), badmult = is_undef(mult) ? [] : [for(i=idx(mult)) if (!( is_int(mult[i]) && mult[i]>0 && (mult[i]<=degree || (type!="closed" && mult[i]==degree+1 && (i==0 || i==len(mult)-1) ) ) )) i], dummy0 = assert(badmult==[], str("mult vector should contain positive integers no larger than the degree, except at ends of open splines, ", "where degree+1 is allowed. The mult vector has bad values at indices: ",badmult)) assert(is_undef(knots) || is_undef(mult) || len(mult)==len(knots), "If both mult and knots are given they must be vectors of the same length") assert(is_undef(mult) || type!="clamped" || sum(mult)==len(control)-degree+1, str("For ",type," spline knot count (sum of multiplicity vector) must be ",len(control)-degree+1," but is instead ",mult?sum(mult):0)) assert(is_undef(mult) || type!="closed" || sum(mult)==len(control)+1, str("For closed spline knot count (sum of multiplicity vector) must be ",len(control)+1," but is instead ",mult?sum(mult):0)) assert(is_undef(mult) || type!="open" || sum(mult)==len(control)+degree+1, str("For closed spline knot count (sum of multiplicity vector) must be ",len(control)+degree+1," but is instead ",mult?sum(mult):0)), control = type=="open" ? control : type=="clamped" ? control //concat(repeat(control[0], degree),control, repeat(last(control),degree)) : /*type=="closed"*/ concat(control, select(control,count(degree))), // n = len(control)-1, // number of control pts -1 // m = n+p+1, // number of knots - 1 mult = !uniform ? mult : type=="clamped" ? assert(is_undef(mult) || mult[0]==1 && last(mult)==1,"For clamped b-splines, first and last multiplicity must be 1") [degree+1,each slice(default(mult, repeat(1,len(control)-degree+1)),1,-2),degree+1] : is_undef(mult) ? repeat(1,len(control)+degree+1) : type=="open" ? mult : /* type=="closed" */ let( // Closed spline requires that we identify first and last knots and then step at same // interval spacing periodically through the knot vector. This means we pick up the first // multiplicity minus 1 and have to add it to the last multiplicity. lastmult = last(mult)+mult[0]-1, dummy=assert(lastmult<=degree, "For closed spline, first and last knot multiplicity cannot total more than the degree+1"), adjlast = [ each select(mult,0,-2), lastmult ] ) _extend_knot_mult(adjlast,1,len(control)+degree+1), knot = uniform && is_undef(mult) ? lerpn(0,1,len(control)+degree+1) : uniform ? [for(i=idx(mult)) each repeat(i/(len(mult)-1),mult[i])] : let( xknots = is_undef(mult)? knots : assert(len(mult) == len(knots), "If knot vector and mult vector must be the same length") [for(i=idx(mult)) each repeat(knots[i], mult[i])] ) type=="open" ? assert(len(xknots)==len(control)+degree+1, str("For open spline, knot vector with multiplicity must have length ", len(control)+degree+1," but has length ", len(xknots))) xknots : type=="clamped" ? assert(len(xknots) == len(control)+1-degree, str("For clamped spline, knot vector with multiplicity must have length ", len(control)+1-degree," but has length ", len(xknots))) assert(xknots[0]!=xknots[1] && last(xknots)!=select(xknots,-2), "For clamped splint, first and last knots cannot repeat (must have multiplicity one") concat(repeat(xknots[0],degree), xknots, repeat(last(xknots),degree)) : /*type=="closed"*/ assert(len(xknots) == len(control)+1-degree, str("For closed spline, knot vector (including multiplicity) must have length ", len(control)+1-degree," but has length ", len(xknots),control)) let(gmult=_calc_mult(xknots)) assert(gmult[0]+last(gmult)<=degree+1, "For closed spline, first and last knot multiplicity together cannot total more than the degree+1") _extend_knot_vector(xknots,0,len(control)+degree+1), bound = type=="clamped" ? undef : [knot[degree], knot[len(control)]], adjusted_u = !is_undef(splinesteps) ? [for(i=[degree:1:len(control)-1]) each if (knot[i]!=knot[i+1]) lerpn(knot[i],knot[i+1],splinesteps, endpoint=false), if (type!="closed") knot[len(control)] ] : is_undef(bound) ? u : add_scalar((bound[1]-bound[0])*u,bound[0]) ) uniform? let( msum = cumsum(mult) ) [for(uval=adjusted_u) let( mind = floor(uval*(len(mult)-1)), knotidxR=msum[mind]-1, knotidx = knotidxR=len(kmult)-1 || kind+kmult[kmultind]>=len(control)))) ? kind-kmult[kmultind-1] : (uind=knot[kind] && adjusted_u[uind]>=knot[kind] && adjusted_u[uind]=knot[kind+kmult[kmultind]], kind = inc_k ? kind+kmult[kmultind] : kind, kmultind = inc_k ? kmultind+1 : kmultind ) if (is_def(output)) output] ) [for(i=idx(adjusted_u)) bspline_pt_recurse(knot,select(control, knotidx[i]-degree,knotidx[i]), adjusted_u[i], 1, degree, 0, degree, knotidx[i]) ]; function bspline_pt_recurse(knot, control, u, r, h,s,p,k) = r>h ? control[0] : let( ctrl_new = [for(i=[k-p+r:1:k-s]) let( alpha = (u-knot[i]) / (knot[i+p-r+1]-knot[i]) ) (1-alpha) * control[i-1-(k-p)-r+1] + alpha*control[i-(k-p)-r+1] ] ) bspline_pt_recurse(knot,ctrl_new,u,r+1,h,s,p,k); function _extend_knot_mult(mult, next, len) = let(total = sum(mult)) total == len ? mult : total>len ? [ each select(mult,0,-2), last(mult)-(total-len) ] : _extend_knot_mult([each mult,mult[next]], next+1, len); function _extend_knot_vector(knots,next,len) = len(knots)==len ? knots : _extend_knot_vector([each knots, last(knots)+knots[next+1]-knots[next]], next+1, len); function _calc_mult(knots) = let( ind=[ 0, for(i=[1:len(knots)-1]) if (knots[i]!=knots[i-1]) i, len(knots) ] ) deltas(ind); // Module: debug_nurbs() // Synopsis: Shows a NURBS curve and its control points, knots and weights // SynTags: Geom // Topics: NURBS, Debugging // See Also: nurbs_curve() // Usage: // debug_nurbs(control, degree, [width], [splinesteps=], [type=], [mult=], [knots=], [size=], [show_weights=], [show_knots=], [show_idx=]); // Description: // Displays a 2D or 3D NURBS and the associated control points to help debug NURBS curves. You can display the // control point indices and weights, and can also display the knot points. // Arguments: // control = control points for NURBS // degree = degree of NURBS // splinesteps = number of segments between each pair of knots. Default: 16 // width = width of the line. Default: 1 // size = size of text annotations. Default: 3 times the width // mult = multiplicity vector for NURBS // weights = weight vector for NURBS // type = NURBS type, one of "clamped", "open" or "closed". Default: "clamped" // show_index = if true then display index of each control point vertex. Default: true // show_weights = if true then display any non-unity weights. Default: true if weights vector is supplied, false otherwise // show_knots = If true then show the knots on the spline curve. Default: false module debug_nurbs(control,degree,splinesteps=16,width=1, size, mult,weights,type="clamped",knots, show_weights, show_knots=false, show_index=true) { $fn=8; size = default(size, 3*width); show_weights = default(show_weights, is_def(weights)); N=len(control); twodim = len(control[0])==2; curve = nurbs_curve(control=control,degree=degree,splinesteps=splinesteps, mult=mult,weights=weights, type=type, knots=knots); stroke(curve, width=width, closed=type=="closed");//, color="green"); stroke(control, width=width/2, color="lightblue", closed=type=="closed"); if (show_knots){ knotpts = nurbs_curve(control=control, degree=degree, splinesteps=1, mult=mult, weights=weights, type=type, knots=knots); color("green") move_copies(knotpts) if (twodim)circle(r=width); else sphere(r=width); } color("blue") if (show_index) move_copies(control){ let(label = str($idx), anch = show_weights && is_def(weights[$idx]) && weights[$idx]!=1 ? FWD : CENTER) if (twodim) text(text=label, size=size, anchor=anch); else rot($vpr) text3d(text=label, size=size, anchor=anch); } color("blue") if ( show_weights) move_copies(control){ if(is_def(weights[$idx]) && weights[$idx]!=1) let(label = str("w=",weights[$idx]), anch = show_index ? BACK : CENTER ) if (twodim) fwd(size/2*0)text(text=label, size=size, anchor=anch); else rot($vpr) text3d(text=label, size=size, anchor=anch); } } // Section: NURBS Surfaces // Function: is_nurbs_patch() // Synopsis: Returns true if the given item looks like a NURBS patch. // Topics: NURBS Patches, Type Checking // Usage: // bool = is_nurbs_patch(x); // Description: // Returns true if the given item looks like a NURBS patch. (a 2D array of 3D points.) // Arguments: // x = The value to check the type of. function is_nurbs_patch(x) = is_list(x) && is_list(x[0]) && is_vector(x[0][0]) && len(x[0]) == len(x[len(x)-1]); // Function: nurbs_patch_points() // Synopsis: Computes specifies point(s) on a NURBS surface patch // Topics: NURBS Patches // See Also: nurbs_vnf(), nurbs_curve() // Usage: // pointgrid = nurbs_patch_points(patch, degree, [splinesteps], [u=], [v=], [weights=], [type=], [mult=], [knots=]); // Description: // Sample a NURBS patch on a point set. If you give splinesteps then it will sampled uniformly in the spline // parameter between the knots, ensuring that a sample appears at every knot. If you instead give u and v then // the values at those points in parameter space will be returned. The various NURBS parameters can all be // single values, if the NURBS has the same parameters in both directions, or pairs listing the value for the // two directions. // Arguments: // patch = rectangular list of control points in any dimension // degree = a scalar or 2-vector giving the degree of the NURBS in the two directions // splinesteps = a scalar or 2-vector giving the number of segments between each knot in the two directions // --- // u = evaluation points in the u direction of the patch // v = evaluation points in the v direction of the patch // mult = a single list or pair of lists giving the knot multiplicity in the two directions. Default: all 1 // knots = a single list of pair of lists giving the knot vector in each of the two directions. Default: uniform // weights = a single list or pair of lists giving the weight at each control point in the patch. Default: all 1 // type = a single string or pair of strings giving the NURBS type, where each entry is one of "clamped", "open" or "closed". Default: "clamped" // Example(NoScale): Computing points on a patch using ranges // patch = [ // [[-50, 50, 0], [-16, 50, 20], [ 16, 50, 20], [50, 50, 0]], // [[-50, 16, 20], [-16, 16, 40], [ 16, 16, 40], [50, 16, 20]], // [[-50,-16, 20], [-16,-16, 40], [ 16,-16, 40], [50,-16, 20]], // [[-50,-50, 0], [-16,-50, 20], [ 16,-50, 20], [50,-50, 0]], // ]; // pts = nurbs_patch_points(patch, 3, u=[0:.1:1], v=[0:.3:1]); // move_copies(flatten(pts)) sphere(r=2,$fn=16); // Example(NoScale): Computing points using splinesteps // patch = [ // [[-50, 50, 0], [-16, 50, 20], [ 16, 50, 20], [50, 50, 0]], // [[-50, 16, 20], [-16, 16, 40], [ 16, 16, 40], [50, 16, 20]], // [[-50,-16, 20], [-16,-16, 40], [ 16,-16, 40], [50,-16, 20]], // [[-50,-50, 0], [-16,-50, 20], [ 16,-50, 20], [50,-50, 0]], // ]; // pts = nurbs_patch_points(patch, 3, splinesteps=5); // move_copies(flatten(pts)) sphere(r=2,$fn=16); function nurbs_patch_points(patch, degree, splinesteps, u, v, weights, type=["clamped","clamped"], mult=[undef,undef], knots=[undef,undef]) = assert(is_undef(splinesteps) || !any_defined([u,v]), "Cannot combine splinesteps with u and v") is_def(weights) ? assert(is_matrix(weights,len(patch),len(patch[0])), "The weights parameter must be a matrix that matches the size of the patch array") let( patch = [for(i=idx(patch)) [for (j=idx(patch[0])) [each patch[i][j]*weights[i][j], weights[i][j]]]], pts = nurbs_patch_points(patch=patch, degree=degree, splinesteps=splinesteps, u=u, v=v, type=type, mult=mult, knots=knots) ) [for(row=pts) [for (pt=row) select(pt,0,-2)/last(pt)]] : assert(is_undef(u) || is_range(u) || is_vector(u) || is_finite(u), "Input u is invalid") assert(is_undef(v) || is_range(v) || is_vector(v) || is_finite(v), "Input v is invalid") assert(num_defined([u,v])!=1, "Must define both u and v (when using)") let( u=is_range(u) ? list(u) : u, v=is_range(v) ? list(v) : v, degree = force_list(degree,2), type = force_list(type,2), splinesteps = is_undef(splinesteps) ? [undef,undef] : force_list(splinesteps,2), mult = is_vector(mult) || is_undef(mult) ? [mult,mult] : assert((is_undef(mult[0]) || is_vector(mult[0])) && (is_undef(mult[1]) || is_vector(mult[1])), "mult must be a vector or list of two vectors") mult, knots = is_vector(knots) || is_undef(knots) ? [knots,knots] : assert((is_undef(knots[0]) || is_vector(knots[0])) && (is_undef(knots[1]) || is_vector(knots[1])), "knots must be a vector or list of two vectors") knots, ) is_num(u) && is_num(v)? nurbs_curve([for (control=patch) nurbs_curve(control, degree[1], u=v, type=type[1], mult=mult[1], knots=knots[1])], degree[0], u=u, type=type[0], mult=mult[0], knots=knots[0]) : is_num(u) ? nurbs_patch_points(patch, degree, u=[u], v=v, knots=knots, mult=mult, type=type)[0] : is_num(v) ? column(nurbs_patch_points(patch, degree, u=u, v=[v], knots=knots, mult=mult, type=type),0) : let( vsplines = [for (i = idx(patch[0])) nurbs_curve(column(patch,i), degree[0], splinesteps=splinesteps[0],u=u, type=type[0],mult=mult[0],knots=knots[0])] ) [for (i = idx(vsplines[0])) nurbs_curve(column(vsplines,i), degree[1], splinesteps=splinesteps[1], u=v, mult=mult[1], knots=knots[1], type=type[1])]; // Function: nurbs_vnf() // Synopsis: Generates a (possibly non-manifold) VNF for a single NURBS surface patch. // SynTags: VNF // Topics: NURBS Patches // See Also: nurbs_patch_points() // Usage: // vnf = nurbs_vnf(patch, degree, [splinesteps], [mult=], [knots=], [weights=], [type=], [style=]); // Description: // Compute a (possibly non-manifold) VNF for a NURBS. The input patch must be an array of control points. If weights is given it // must be an array of weights that matches the size of the control points. The style parameter // gives the {{vnf_vertex_array()}} style to use. The other parameters may specify the NURBS parameters in the two directions // by giving a single value, which applies to both directions, or a list of two values to specify different values in each direction. // You can specify undef for for a direction to keep the default, such as `mult=[undef,v_multiplicity]`. // Arguments: // patch = rectangular list of control points in any dimension // degree = a scalar or 2-vector giving the degree of the NURBS in the two directions // splinesteps = a scalar or 2-vector giving the number of segments between each knot in the two directions // --- // mult = a single list or pair of lists giving the knot multiplicity in the two directions. Default: all 1 // knots = a single list of pair of lists giving the knot vector in each of the two directions. Default: uniform // weights = a single list or pair of lists giving the weight at each control point in the. Default: all 1 // type = a single string or pair of strings giving the NURBS type, where each entry is one of "clamped", "open" or "closed". Default: "clamped" // style = {{vnf_vertex_array ()}} style to use for triangulating the surface. Default: "default" // Example: Quadratic B-spline surface // patch = [ // [[-50, 50, 0], [-16, 50, 20], [ 16, 50, 20], [50, 50, 0]], // [[-50, 16, 20], [-16, 16, 40], [ 16, 16, 40], [50, 16, 20]], // [[-50,-16, 20], [-16,-16, 40], [ 16,-16, 40], [50,-16, 20]], // [[-50,-50, 0], [-16,-50, 20], [ 16,-50, 20], [50,-50, 0]], // ]; // vnf = nurbs_vnf(patch, 2); // vnf_polyhedron(vnf); // Example: Cubic B-spline surface // patch = [ // [[-50, 50, 0], [-16, 50, 20], [ 16, 50, 20], [50, 50, 0]], // [[-50, 16, 20], [-16, 16, 40], [ 16, 16, 40], [50, 16, 20]], // [[-50,-16, 20], [-16,-16, 40], [ 16,-16, 40], [50,-16, 20]], // [[-50,-50, 0], [-16,-50, 20], [ 16,-50, 20], [50,-50, 0]], // ]; // vnf = nurbs_vnf(patch, 3); // vnf_polyhedron(vnf); // Example: Cubic B-spline surface, closed in one direction // patch = [ // [[-50, 50, 0], [-16, 50, 20], [ 16, 50, 20], [50, 50, 0]], // [[-50, 16, 20], [-16, 16, 40], [ 16, 16, 40], [50, 16, 20]], // [[-50,-16, 20], [-16,-16, 40], [ 16,-16, 40], [50,-16, 20]], // [[-50,-50, 0], [-16,-50, 20], [ 16,-50, 20], [50,-50, 0]], // ]; // vnf = nurbs_vnf(patch, 3, type=["closed","clamped"]); // vnf_polyhedron(vnf); // Example: B-spline surface cubic in one direction, quadratic in the other // patch = [ // [[-50, 50, 0], [-16, 50, 20], [ 16, 50, 20], [50, 50, 0]], // [[-50, 16, 20], [-16, 16, 40], [ 16, 16, 40], [50, 16, 20]], // [[-50,-16, 20], [-16,-16, 40], [ 16,-16, 40], [50,-16, 20]], // [[-50,-50, 0], [-16,-50, 20], [ 16,-50, 20], [50,-50, 0]], // ]; // vnf = nurbs_vnf(patch, [3,2],type=["closed","clamped"]); // vnf_polyhedron(vnf); // Example: The sphere can be represented using NURBS // patch = [ // [[0,0,1], [0,0,1], [0,0,1], [0,0,1], [0,0,1], [0,0,1], [0,0,1]], // [[2,0,1], [2,4,1], [-2,4,1], [-2,0,1], [-2,-4,1], [2,-4,1], [2,0,1]], // [[2,0,-1],[2,4,-1],[-2,4,-1],[-2,0,-1],[-2,-4,-1], [2,-4,-1],[2,0,-1]], // [[0,0,-1],[0,0,-1],[0,0,-1], [0,0,-1], [0,0,-1], [0,0,-1], [0,0,-1]] // ]; // weights = [ // [9,3,3,9,3,3,9], // [3,1,1,3,1,1,3], // [3,1,1,3,1,1,3], // [9,3,3,9,3,3,9], // ]/9; // vknots = [0, 1/2, 1/2, 1/2, 1]; // vnf = nurbs_vnf(patch, 3,weights=weights, knots=[undef,vknots]); // vnf_polyhedron(vnf); function nurbs_vnf(patch, degree, splinesteps=16, weights, type="clamped", mult, knots, style="default") = assert(is_nurbs_patch(patch),"Input patch is not a rectangular aray of points") let( pts = nurbs_patch_points(patch=patch, degree=degree, splinesteps=splinesteps, type=type, mult=mult, knots=knots, weights=weights), ) vnf_vertex_array(pts, style=style, row_wrap=type[0]=="closed", col_wrap=type[1]=="closed");