@@ -45,7 +45,7 @@ _BOSL2_NURBS = is_undef(_BOSL2_STD) && (is_undef(BOSL2_NO_STD_WARNING) || !BOSL2
4545// number of control points. A single NURBS is more like a bezier **path** than like a single bezier spline.
4646// .
4747// A NURBS or B-spline is a curve made from a moving average of several Bezier curves. The knots specify when one Bezier fades
48- // away to be replaced by the next one. The knot list is a non-increasing list of values that you specify using two parameters,
48+ // away to be replaced by the next one. The knot list is a non-decreasing list of values that you specify using two parameters,
4949// `knots` and `mult`. In practice changing the knot values doesn't have a strong effect on the curve, so it usually suffices
5050// to use a uniform knot vector, which is the default. The major exception to this is repeated knot values.
5151// At generic points in the NURBS, the curve is infinitely differentiable, but at a point that
@@ -647,7 +647,7 @@ module debug_nurbs(control,degree,splinesteps=16,width=1, size, mult,weights,typ
647647// data = [[0,0], [30,50], [60,40], [80,10], [50,-20], [20,-10]];
648648// path = nurbs_curve(nurbs_interp(data, 3, closed = true));
649649// stroke(path, closed = true);
650- // color("red") move_copies(data) circle(r=0.25 , $fn=16);
650+ // color("red") move_copies(data) circle(r=1 , $fn=16);
651651//
652652// Example(2D): Closed polygon - All data points lie exactly on the polygon boundary.
653653// data = [[0,0], [30,50], [60,40], [80,10], [50,-20], [20,-10]];
@@ -661,37 +661,13 @@ module debug_nurbs(control,degree,splinesteps=16,width=1, size, mult,weights,typ
661661// stroke(path, width=0.5);
662662// color("red") move_copies(data) circle(r=1, $fn=16);
663663//
664- // Example(2D): Low-level NURBS parameter list - nurbs_interp() returns a BOSL2 NURBS parameter list compatible with nurbs_curve(), debug_nurbs(), etc.
664+ // Example(2D): Low-level NURBS parameter list — nurbs_interp() returns a BOSL2 NURBS parameter list compatible with nurbs_curve(), debug_nurbs(), etc.
665665// data = [[0,0], [10,30], [25,15], [40,35], [60,10], [80,25]];
666666// result = nurbs_interp(data, 3);
667667// curve = nurbs_curve(result, splinesteps=24);
668668// stroke(curve, width=0.5);
669669// color("red") move_copies(data) circle(r=1, $fn=16);
670670//
671- // Example(3D): 3D closed curve
672- // data3d = [[20,0,0],[0,20,10],[-20,0,20],[0,-20,10]];
673- // path = nurbs_curve(nurbs_interp(data3d, 3, closed=true));
674- // stroke(path, width=1, closed=true);
675- // color("red") move_copies(data3d) sphere(r=1, $fn=16);
676- //
677- // Example(2D): Corner added at data point 3
678- // data = [[0,0], [10,30], [25,15], [40,35], [60,10], [80,25]];
679- // path = nurbs_curve(nurbs_interp(data, 3, corners = [3]));
680- // stroke(path);
681- // color("red") move_copies(data) circle(r=1, $fn=16);
682- //
683- // Example(2D): Controlling the curvature at data point 3
684- // data = [[0,0], [10,30], [25,15], [40,35], [60,10], [80,25]];
685- // path = nurbs_curve(nurbs_interp(data, 3));
686- // stroke(path);
687- // color("red") move_copies(data) circle(r=1, $fn=16);
688- //
689- // Example(2D): Clamped curve (default)
690- // data = [[0,0], [10,30], [25,15], [40,35], [60,10], [80,25]];
691- // path = nurbs_curve(nurbs_interp(data, 3));
692- // stroke(path);
693- // color("red") move_copies(data) circle(r=1, $fn=16);
694- //
695671// Example(2D,Med): Endpoint tangent control - Specify start and/or end tangent vectors. Each vector is automatically scaled by the total chord length; a unit vector produces natural arc-length speed. Magnitude > 1 increases pull, < 1 weakens it.
696672// data = [[0,0], [20,30], [50,25], [80,0]];
697673// // No tangent control (natural):
@@ -704,6 +680,109 @@ module debug_nurbs(control,degree,splinesteps=16,width=1, size, mult,weights,typ
704680// nurbs_curve(nurbs_interp(data, 3, start_deriv=[1,0], end_deriv=[1,0])), width=0.3);
705681// color("black") move_copies(data) circle(r=0.5, $fn=16);
706682//
683+ // Example(2D,Huge,NoAxes,VPT=[55,50,0],VPR=[0,0,0],VPD=600): Controlling the start and end of an otherwise unconstrained NURBS curve using derivitives.
684+ // data = [[0,0], [20,30], [30,90], [36,111],[50,25], [80,0]];
685+ // xdistribute(90){
686+ // union(){
687+ // debug_nurbs_interp(data,3, splinesteps=32, data_size=1);
688+ // fwd(15)text("unconstrained",size=6);
689+ // }
690+ // union(){
691+ // debug_nurbs_interp(data,3, splinesteps=32, data_size=1,
692+ // start_deriv=RIGHT);
693+ // fwd(15)text("start=RIGHT",size=6);
694+ // }
695+ // union(){
696+ // debug_nurbs_interp(data,3, splinesteps=32, data_size=1,
697+ // start_deriv=2*RIGHT,end_deriv=RIGHT);
698+ // fwd(15)text("start=[2,0] end=[1,0]",size=6);
699+ // }
700+ // }
701+ //
702+ // Example(2D,Huge,NoAxes,VPT=[45,50,0],VPR=[0,0,0],VPD=600): Controlling the shape with derivatives and corners
703+ // data = [[0,0], [20,30], [30,90], [36,111],[50,25], [80,0]];
704+ // xdistribute(110){
705+ // union(){
706+ // debug_nurbs_interp(data,3, splinesteps=32, data_size=1,
707+ // deriv=[2*RIGHT,[0,1],undef,undef,undef,RIGHT],
708+ // );
709+ // fwd(15)text("derivs at 0, 1, 5",size=6);
710+ // }
711+ // union(){
712+ // debug_nurbs_interp(data,3, splinesteps=32, data_size=1,
713+ // deriv=[undef,[0,1],undef,undef,RIGHT,undef],
714+ // );
715+ // fwd(15)text("derivs at pts 1, 4",size=6);
716+ // }
717+ // union(){
718+ // debug_nurbs_interp(data,3, splinesteps=32, data_size=1,
719+ // deriv=[undef,[0,1],undef,undef,NAN,undef],
720+ // );
721+ // fwd(15)text("corner and pt 1 deriv",size=6);
722+ // }
723+ // }
724+ //
725+ // Example(2D,Huge,NoAxes,VPT=[70,50,0],VPR=[0,0,0],VPD=725):
726+ // data = [[0,0], [20,30], [30,90], [36,111],[50,25], [80,0]];
727+ // xdistribute(110){
728+ // union(){
729+ // debug_nurbs_interp(data,3, splinesteps=32, data_size=1,
730+ // start_deriv=RIGHT,end_deriv=RIGHT, start_curvature=0,end_curvature=0
731+ // );
732+ // fwd(15)text("ends curvature=0",size=6);
733+ // }
734+ // union(){
735+ // debug_nurbs_interp(data,3, splinesteps=32, data_size=1,
736+ // start_deriv=RIGHT,end_deriv=RIGHT, start_curvature=1/10*unit([1000,1]),end_curvature=1/5
737+ // );
738+ // color("lime") {
739+ // stroke(arc(angle=[180,270], cp=[0,10],r=10));
740+ // stroke(arc(angle=[270,360], cp=last(data)+[0,5], r=5,$fn=32));
741+ // }
742+ // fwd(15)text("ends curvature>0",size=6);
743+ // }
744+ // union(){
745+ // debug_nurbs_interp(data,3, splinesteps=32, data_size=1,
746+ // deriv=[undef,[0,1],undef,[1,0],undef,undef],
747+ // curvature=[undef,-1/10,undef,0,undef,undef]
748+ // );
749+ // fwd(15)text("curvature at points 1 and 3",size=6);
750+ // }
751+ // }
752+ //
753+ // Example(2D,NoAxes,Big): Unconstrained NURBS through the same data points vary depending on the paramaterization method chosen
754+ // data = [[0,0], [20,30], [35,120], [50,30], [70,0]];
755+ // method = ["length", "centripetal", "dynamic", "foley", "fang"];
756+ // color = ["blue","lime","yellow","orange","red"];
757+ // for (i = [0:4]) {
758+ // color(color[i]) {
759+ // debug_nurbs_interp(data, 3, closed = true, method = method[i], size = 5, data_size = 3);
760+ // move([80,100-i*15]) text(method[i]);
761+ // }
762+ // }
763+ //
764+ // Example(2D,NoAxes,Big): Adding extra points reduces the differences between the methods.
765+ // data = [[0,0], [20,30], [35,120], [50,30], [70,0]];
766+ // method = ["length", "centripetal", "dynamic", "foley", "fang"];
767+ // color = ["blue","lime","yellow","orange","red"];
768+ // for (i = [0:4]) {
769+ // color(color[i]) {
770+ // debug_nurbs_interp(data, 3, closed = true, method = method[i], extra_pts = 3, size = 5, data_size = 3);
771+ // move([80,100-i*15]) text(method[i]);
772+ // }
773+ // }
774+ //
775+ // Example(2D,NoAxes,Big): Switching from the default to smooth = 1 improves things further.
776+ // data = [[0,0], [20,30], [35,120], [50,30], [70,0]];
777+ // method = ["length", "centripetal", "dynamic", "foley", "fang"];
778+ // color = ["blue","lime","yellow","orange","red"];
779+ // for (i = [0:4]) {
780+ // color(color[i]) {
781+ // debug_nurbs_interp(data, 3, closed = true, method = method[i], extra_pts = 3, smooth = 1, size = 5, data_size = 3);
782+ // move([80,100-i*15]) text(method[i]);
783+ // }
784+ // }
785+ //
707786// Example(2D,NoAxes,Med,VPT=[37.5,0,0],VPD=275): We can generate a heart shape with a clamped NURBS where the first and last data points are co-incident, and we insert a corner at data point 4.
708787// data = [[0,10], [25,20], [30,0], [20,-15], [0,-30], [-20,-15], [-30,0], [-25,20], [0,10]];
709788// debug_nurbs_interp(data, 3, closed = false, method = "centripetal", corners=[4]);
@@ -739,10 +818,10 @@ module debug_nurbs(control,degree,splinesteps=16,width=1, size, mult,weights,typ
739818// Example(2D,Big): Parameterization methods for sharp turns. For data with sudden direction changes or uneven chord spacing, "centripetal" and "dynamic" reduce unwanted oscillations.
740819// // "length" (blue), "centripetal" (red), "dynamic" (orange) compared.
741820// sharp = [[0,0], [5,40],[6,40], [10,0], [50,0], [55,40],[56,42], [60,0]];
742- // color("blue") stroke(nurbs_curve(nurbs_interp(sharp, 3, method = "centripetal"), splinesteps=32), width=0.1 );
743- // color("red") stroke(nurbs_curve(nurbs_interp(sharp, 3, method="foley"), splinesteps=32), width=0.1 );
744- // color("orange") stroke(nurbs_curve(nurbs_interp(sharp, 3, method="dynamic"), splinesteps=32), width=0.1 );
745- // color("green") move_copies(sharp) circle(r=.1 , $fn=16);
821+ // color("blue") stroke(nurbs_curve(nurbs_interp(sharp, 3, method = "centripetal"), splinesteps=32), width=0.25 );
822+ // color("red") stroke(nurbs_curve(nurbs_interp(sharp, 3, method="foley"), splinesteps=32), width=0.25 );
823+ // color("orange") stroke(nurbs_curve(nurbs_interp(sharp, 3, method="dynamic"), splinesteps=32), width=0.25 );
824+ // color("green") move_copies(sharp) circle(r=.5 , $fn=16);
746825
747826
748827function nurbs_interp(points, degree, method= "centripetal" , closed= false ,
@@ -872,40 +951,7 @@ function nurbs_interp(points, degree, method="centripetal", closed=false,
872951// show_deriv = Show derivative-constraint arrows. Default: `true`
873952// show_curvature = Show curvature-constraint circles / disks. Default: `true`
874953//
875- // Example(2D,NoAxes,Med): Unconstrained NURBS through the same data points vary depending on the paramaterization method chosen
876- // data = [[0,0], [20,30], [35,120], [50,30], [70,0]];
877- // method = ["length", "centripetal", "dynamic", "foley", "fang"];
878- // color = ["blue","lime","yellow","orange","red"];
879- // for (i = [0:4]) {
880- // color(color[i]) {
881- // debug_nurbs_interp(data, 3, closed = true, method = method[i], size = 5, data_size = 3);
882- // move([80,100-i*15]) text(method[i]);
883- // }
884- // }
885- //
886- //
887- // Example(2D,NoAxes,Med): Adding extra points reduces the differences between the methods.
888- // data = [[0,0], [20,30], [35,120], [50,30], [70,0]];
889- // method = ["length", "centripetal", "dynamic", "foley", "fang"];
890- // color = ["blue","lime","yellow","orange","red"];
891- // for (i = [0:4]) {
892- // color(color[i]) {
893- // debug_nurbs_interp(data, 3, closed = true, method = method[i], extra_pts = 3, size = 5, data_size = 3);
894- // move([80,100-i*15]) text(method[i]);
895- // }
896- // }
897- //
898- // Example(2D,NoAxes,Med): Switching from the default to smooth = 1 improves things further.
899- // data = [[0,0], [20,30], [35,120], [50,30], [70,0]];
900- // method = ["length", "centripetal", "dynamic", "foley", "fang"];
901- // color = ["blue","lime","yellow","orange","red"];
902- // for (i = [0:4]) {
903- // color(color[i]) {
904- // debug_nurbs_interp(data, 3, closed = true, method = method[i], extra_pts = 3, smooth = 1, size = 5, data_size = 3);
905- // move([80,100-i*15]) text(method[i]);
906- // }
907- // }
908- //
954+
909955// Example(2D,NoAxes): Keyhole Shape: Simply interpolating a NURBS through the data points yields disappointing results.
910956// data = [[0,0],[0,10],[-5,20],[5,30],[15,20],[10,10],[10,0],[0,0]];
911957// debug_nurbs_interp(data,3, method="centripetal");
@@ -1672,18 +1718,8 @@ module nurbs_vnf(patch, degree, splinesteps=16, weights, type="clamped", mult, k
16721718// ];
16731719// nurbs_interp_surface(surface,3, row_edges = 3);
16741720//
1675- // Example(3D,VPD=320,VPT=[8,10,13]): Setting first and last row/column derivitives
1676- // surface = [
1677- // [[-50, 50, 0], [-16, 50, 0], [ 16, 50, 0], [50, 50, 0], [80, 50, 0]],
1678- // [[-50, 25, 0], [-16, 25, 40], [ 16, 25, 30], [50, 25, 20], [80, 25, 0]],
1679- // [[-50, 0, 0], [-16, 0, 40], [ 16, 0, 30], [50, 0, 30], [80, 0, 0]],
1680- // [[-50,-25, 0], [-16,-25, 35], [ 16,-25, 40], [50,-25, 15], [80,-25, 0]],
1681- // [[-50,-50, 0], [-16,-50, 0], [ 16,-50, 0], [50,-50, 0], [80,-50, 0]],
1682- // ];
1683- // nurbs_interp_surface(surface,3, first_row_deriv = UP+FWD, last_row_deriv = DOWN+FWD,
1684- // first_col_deriv = UP+RIGHT/2, last_col_deriv = DOWN+RIGHT/2);
16851721//
1686- // Example(3D): Tube - Surface closed around the column direction (the rings), clamped along rows (the axis). Uses 5 rings: a cubic closed direction needs at least p+2 = 5 data points to have interior knot freedom.
1722+ // Example(3D): Tube - Surface closed around the column direction (the rings), clamped along rows (the axis).
16871723// r = 20;
16881724// data = [for (u = [0:15:60])
16891725// [for (i = [0:1:5])
@@ -1692,22 +1728,38 @@ module nurbs_vnf(patch, degree, splinesteps=16, weights, type="clamped", mult, k
16921728// ];
16931729// nurbs_interp_surface(data, 3, splinesteps=8, col_wrap=true);
16941730//
1695- // Example(3D,VPR=[80,0,45],VPT=[0,0,20],VPD = 320): Rotated star cross section surface closed in one direction. Degenerate end rows close the shape in the other direction.
1731+ // // Example(3D): We can close the ends of mixed surfaces such as our tube, using caps.
1732+ // r = 20;
1733+ // data = [for (u = [0:15:60])
1734+ // [for (i = [0:1:5])
1735+ // let(a = i * 360/6)
1736+ // [r*cos(a), r*sin(a), u]]
1737+ // ];
1738+ // nurbs_interp_surface(data, 3, splinesteps=8, col_wrap=true, caps = true);
1739+ //
1740+ //
1741+ // Example(3D,Med,VPR=[80,0,45],VPT=[0,0,20],VPD = 320): Rotated star cross section surface closed in one direction. Degenerate end rows close the shape in the other direction.
16961742// surface = [ repeat([0,0,-15],14),
16971743// for(i=[0:4]) zrot(i*15,path3d(star(or=15,ir=13, n=7),i*15)),
16981744// repeat([0,0,5*15],14)
16991745// ];
17001746// nurbs_interp_surface(surface, 3, col_wrap = true);
17011747//
1702- // Example(3D,VPR=[80,0,45],VPT=[0,0,20],VPD = 320): Controlling end shape with normals.
1748+ // Example(3D,Med, VPR=[80,0,45],VPT=[0,0,20],VPD = 320): Controlling end shape with normals.
17031749// surface = [ repeat([0,0,-15],14),
17041750// for(i=[0:4]) zrot(i*15,path3d(star(or=15,ir=13, n=7),i*15)),
17051751// repeat([0,0,5*15],14)
17061752// ];
17071753// nurbs_interp_surface(surface, 3, col_wrap = true, normal1 = DOWN*4, normal2 = UP*2);
17081754//
1709- //
1710- // Example(3D): EGG Smooth parametric ovoid. ~103 long, ~82 wide. Blunt at +z, pointed at -z. Profile: r = 40·sin(φ)·(1 − 0.25·cos(φ)), z = −52·cos(φ) The asymmetry term shifts the belly toward the blunt end. Grid: 9 rings × 8 angles
1755+ // Example(3D,Med,VPR=[80,0,45],VPT=[0,0,20],VPD = 320): Controlling end shape with normals.
1756+ // surface = [ repeat([0,0,-15],14),
1757+ // for(i=[0:4]) zrot(i*15,path3d(star(or=15,ir=13, n=7),i*15)),
1758+ // repeat([0,0,5*15],14)
1759+ // ];
1760+ // nurbs_interp_surface(surface, 3, col_wrap = true, normal1 = DOWN*4, normal2 = UP*2+RIGHT*4);
1761+ //
1762+ // Example(3D): EGG Smooth parametric ovoid. ~103 long, ~82 wide. 9 rings × 8 angles
17111763// egg = [for (i = [0:8])
17121764// let(phi = i * 180/8,
17131765// r = 40 * sin(phi) * (1 - 0.25*cos(phi)),
@@ -1718,15 +1770,31 @@ module nurbs_vnf(patch, degree, splinesteps=16, weights, type="clamped", mult, k
17181770// ]
17191771// ];
17201772// nurbs_interp_surface(egg, 3, col_wrap = true);
1773+ // color("red") move_copies(flatten(egg)) circle(r=1, $fn=16);
17211774//
1722- // Example(3D,VPT=[10,-25,60],VPR=[100,0,30],VPD=375 ): A Mushroom
1775+ // Example(3D,VPT=[10,-25,60],VPR=[100,0,30],VPD=425 ): A Mushroom
17231776// shape = [ repeat([0,0,-1],8),
17241777// for(i=[0:5]) path3d(regular_ngon(n = 8, side = 15),i*15),
17251778// path3d(regular_ngon(n = 8, side = 50), 5 * 15),
17261779// path3d(regular_ngon(n = 8, side = 55), 6.5 * 15),
1727- // repeat([0,0,8 *15],8)
1780+ // repeat([0,0,9 *15],8)
17281781// ];
1729- // nurbs_interp_surface(shape, 3, normal1 = DOWN, normal2 = UP, col_wrap = true, row_edges = 7);
1782+ // nurbs_interp_surface(shape, 3, normal1 = DOWN, normal2 = UP*0.8, col_wrap = true, row_edges = 7);
1783+ //
1784+ //
1785+ // Example(3D,Med,NoAxes,VPT=[0,0,75],VPR=[90,0,0],VPD=900): Controlling mushroom crown shape with normal2.
1786+ // shape = [ repeat([0,0,-1],8),
1787+ // for(i=[0:5]) path3d(regular_ngon(n = 8, side = 15),i*15),
1788+ // path3d(regular_ngon(n = 8, side = 50), 5 * 15),
1789+ // path3d(regular_ngon(n = 8, side = 55), 6.5 * 15),
1790+ // repeat([0,0,9*15],8)
1791+ // ];
1792+ // for (i = [0.4:0.2:0.8]) {
1793+ // right( i * 800 -480){
1794+ // nurbs_interp_surface(shape, 3, normal1 = DOWN, normal2 = UP*i, col_wrap = true, row_edges = 7);
1795+ // up(150) xrot(90) color("blue") text(str("UP * ",i), size = 14, anchor = CENTER);
1796+ // }
1797+ // }
17301798//
17311799// Example(3D,VPR[80,0,40]): A 3d Heart Shape - Based on the 2d Shape from nurbs_interp() example 14.
17321800// data = [[0,10], [25,20], [30,0], [20,-15], [0,-30], [-20,-15], [-30,0], [-25,20]];
0 commit comments