diff --git a/beziers.scad b/beziers.scad index 710f27f..219a5a8 100644 --- a/beziers.scad +++ b/beziers.scad @@ -61,10 +61,14 @@ use // `[endpt1, cp1, cp2, endpt2, cp3, cp4, endpt3]` // **NOTE**: A bezier path is *NOT* a polyline. It is only the points and controls used to define the curve. // +// **Bezier Patch**: A surface defining grid of (N+1) by (N+1) bezier points. If a Bezier Segment defines a curved line, a Bezier Patch defines a curved surface. +// +// **Bezier Surface**: A surface defined by a list of one or more bezier patches. +// // **Spline Steps**: The number of straight-line segments to split a bezier segment into, to approximate the bezier curve. The more spline steps, the closer the approximation will be to the curve, but the slower it will be to generate. Usually defaults to 16. -// Section: Functions +// Section: Segment Functions // Function: bez_point() // Usage: @@ -96,36 +100,67 @@ function bez_point(curve,u)= ); -// Function: bezier_polyline() +// Function: bezier_segment_closest_point() // Usage: -// bezier_polyline(bezier, [splinesteps], [N]) +// bezier_segment_closest_point(bezier,pt) // Description: -// Takes a bezier path and converts it into a polyline. +// Finds the closest part of the given bezier segment to point `pt`. +// The degree of the curve, N, is one less than the number of points in `curve`. +// Returns `u` for the shortest position on the bezier segment to the given point `pt`. // Arguments: -// bezier = A bezier path to approximate. -// splinesteps = Number of straight lines to split each bezier segment into. default=16 -// N = The degree of the bezier curves. Cubic beziers have N=3. Default: 3 +// curve = The list of endpoints and control points for this bezier segment. +// pt = The point to find the closest curve point to. +// max_err = The maximum allowed error when approximating the closest approach. // Example(2D): -// bez = [ -// [0,0], [-5,30], -// [20,60], [50,50], [110,30], -// [60,25], [70,0], [80,-25], -// [80,-50], [50,-50] -// ]; -// trace_polyline(bez, size=1, N=3, showpts=true); -// trace_polyline(bezier_polyline(bez, N=3), size=3); -function bezier_polyline(bezier, splinesteps=16, N=3) = concat( - [ - for ( - b = [0 : N : len(bezier)-N-1], - l = [0 : splinesteps-1] - ) let ( - crv = [for (i=[0 : N]) bezier[b+i]], - u = l / splinesteps - ) bez_point(crv, u) - ], - [bez_point([for (i=[-(N+1) : -1]) bezier[len(bezier)+i]], 1.0)] -); +// pt = [40,15]; +// bez = [[0,0], [20,40], [60,-25], [80,0]]; +// u = bezier_segment_closest_point(bez, pt); +// trace_bezier(bez, N=len(bez)-1); +// color("red") translate(pt) sphere(r=1); +// color("blue") translate(bez_point(bez,u)) sphere(r=1); +function bezier_segment_closest_point(curve, pt, max_err=0.01, u=0, end_u=1, step_u=undef, min_dist=undef, min_u=undef) = + let( + step = step_u == undef? (end_u-u)/(len(curve)*2) : step_u, + t_u = min(u, end_u), + dist = norm(bez_point(curve, t_u)-pt), + md = (min_dist==undef || dist(end_u-step/2))? ( + (step= 0.125 || defl > max_deflect)? ( + bezier_segment_length(curve, start_u, mid_u, max_deflect) + + bezier_segment_length(curve, mid_u, end_u, max_deflect) + ) : norm(ep-sp); + // Function: fillet3pts() @@ -169,6 +204,400 @@ function fillet3pts(p0, p1, p2, r, maxerr=0.1, w=0.5, dw=0.25) = let( fillet3pts(p0, p1, p2, r, maxerr=maxerr, w=w-dw, dw=dw/2); + +// Section: Patch Functions + + +// Function: bezier_patch_point() +// Usage: +// bezier_patch_point(patch, u, v) +// Description: +// Given a square 2-dimensional array of (N+1) by (N+1) points size, +// that represents a Bezier Patch of degree N, returns a point on that +// surface, at positions `u`, and `v`. A cubic bezier patch will be 4x4 +// points in size. If given a non-square array, each direction will have +// its own degree. +// Arguments: +// patch = The 2D array of endpoints and control points for this bezier patch. +// u = The proportion of the way along the first dimension of the patch to find the point of. 0<=`u`<=1 +// v = The proportion of the way along the second dimension of the patch to find the point of. 0<=`v`<=1 +function bezier_patch_point(patch, u, v) = bez_point([for (bez = patch) bez_point(bez, u)], v); + + +// Function: bezier_triangle_point() +// Usage: +// bezier_triangle_point(tri, u, v) +// Description: +// Given a triangular 2-dimensional array of N+1 by (for the first row) N+1 points, +// that represents a Bezier triangular patch of degree N, returns a point on +// that surface, at positions `u`, and `v`. A cubic bezier triangular patch +// will have a list of 4 points in the first row, 3 in the second, 2 in the +// third, and 1 in the last row. +// Arguments: +// tri = Triangular bezier patch to get point on. +// u = The proportion of the way along the first dimension of the triangular patch to find the point of. 0<=`u`<=1 +// v = The proportion of the way along the second dimension of the triangular patch to find the point of. 0<=`v`<=(1-`u`) +function bezier_triangle_point(tri, u, v) = + len(tri) == 1 ? tri[0][0] : + let( + n = len(tri)-1, + Pu = [for(i=[0:n-1]) [for (j=[1:len(tri[i])-1]) tri[i][j]]], + Pv = [for(i=[0:n-1]) [for (j=[0:len(tri[i])-2]) tri[i][j]]], + Pw = [for(i=[1:len(tri)-1]) tri[i]] + ) + bezier_triangle_point(u*Pu + v*Pv + (1-u-v)*Pw, u, v); + + + +// Internal, not exposed. +function _vertex_list_merge(v1, v2) = concat(v1, [for (v=v2) if (!in_list(v,v1)) v]); +function _vertex_list_face(v, face) = [for (pt = face) search([pt], v, num_returns_per_match=1)[0]]; + + +// Function: bezier_patch() +// Usage: +// bezier_patch(patch, [splinesteps], [vertices], [faces]); +// Description: +// Calculate vertices and faces for forming a partial polyhedron +// from the given bezier rectangular patch. Returns a list containing +// two elements. The first is the list of unique vertices. The +// second is the list of faces, where each face is a list of indices +// into the list of vertices. You can chain calls to this, to add +// more vertices and faces for multiple bezier patches, to stitch +// them together into a complete polyhedron. +// Arguments: +// patch = The rectangular array of endpoints and control points for this bezier patch. +// splinesteps = Number of steps to divide each bezier segment into. Default: 16 +// vertices = Vertex list to add new points to. Default: [] +// faces = Face list to add new faces to. Default: [] +function bezier_patch(patch, splinesteps=16, vertices=[], faces=[]) = + let( + base = len(vertices), + pts = [for (v=[0:splinesteps], u=[0:splinesteps]) bezier_patch_point(patch, u/splinesteps, v/splinesteps)], + new_vertices = concat(vertices, pts), + new_faces = [ + for ( + v=[0:splinesteps-1], + u=[0:splinesteps-1], + i=[0,1] + ) let ( + v1 = u+v*(splinesteps+1) + base, + v2 = v1 + 1, + v3 = v1 + splinesteps + 1, + v4 = v3 + 1, + face = i? [v1,v3,v2] : [v2,v3,v4] + ) face + ] + ) [new_vertices, concat(faces, new_faces)]; + + +function _tri_count(n) = (n*(1+n))/2; + + +// Function: bezier_triangle() +// Usage: +// bezier_triangle(tri, [splinesteps], [vertices], [faces]); +// Description: +// Calculate vertices and faces for forming a partial polyhedron +// from the given bezier triangular patch. Returns a list containing +// two elements. The first is the list of unique vertices. The +// second is the list of faces, where each face is a list of indices +// into the list of vertices. You can chain calls to this, to add +// more vertices and faces for multiple bezier patches, to stitch +// them together into a complete polyhedron. +// Arguments: +// tri = The triangular array of endpoints and control points for this bezier patch. +// splinesteps = Number of steps to divide each bezier segment into. Default: 16 +// vertices = Vertex list to add new points to. Default: [] +// faces = Face list to add new faces to. Default: [] +// Example(3D): +// tri = [ +// [[-50,-33,0], [-25,16,-50], [0,66,0]], +// [[0,-33,-50], [25,16,-50]], +// [[50,-33,0]] +// ]; +// vnf = bezier_triangle(tri, splinesteps=16); +// polyhedron(points=vnf[0], faces=vnf[1]); +function bezier_triangle(tri, splinesteps=16, vertices=[], faces=[]) = + let( + base = len(vertices), + pts = [ + for ( + u=[0:splinesteps], + v=[0:splinesteps-u] + ) bezier_triangle_point(tri, u/splinesteps, v/splinesteps) + ], + new_vertices = concat(vertices, pts), + patchlen = len(tri), + tricnt = _tri_count(splinesteps+1), + new_faces = [ + for ( + u=[0:splinesteps-1], + v=[0:splinesteps-u-1] + ) let ( + v1 = v + (tricnt - _tri_count(splinesteps+1-u)) + base, + v2 = v1 + 1, + v3 = v + (tricnt - _tri_count(splinesteps-u)) + base, + v4 = v3 + 1, + allfaces = concat( + [[v1,v2,v3]], + ((u= len(patches))? [vertices, faces] : + bezier_patch(patches[i], splinesteps=splinesteps, vertices=vertices, faces=faces), + vnf2 = (i >= len(tris))? vnf : + bezier_triangle(tris[i], splinesteps=splinesteps, vertices=vnf[0], faces=vnf[1]) + ) (i >= len(patches) && i >= len(tris))? vnf2 : + bezier_surface_vertices_and_faces(patches=patches, tris=tris, splinesteps=splinesteps, i=i+1, vertices=vnf2[0], faces=vnf2[1]); + + + +// Section: Path Functions + + +// Function: bezier_path_point() +// Usage: +// bezier_path_point(path, seg, u, [N]) +// Description: Returns the coordinates of bezier path segment `seg` at position `u`. +// Arguments: +// path = A bezier path to approximate. +// seg = Segment number along the path. Each segment is N points long. +// u = The proportion of the way along the segment to find the point of. 0<=`u`<=1 +// N = The degree of the bezier curves. Cubic beziers have N=3. Default: 3 +function bezier_path_point(path, seg, u, N=3) = bez_point(select(path,seg*N,(seg+1)*N), u); + + + +// Function: bezier_path_closest_point() +// Usage: +// bezier_path_closest_point(bezier,pt) +// Description: +// Finds the closest part of the given bezier path to point `pt`. +// Returns [segnum, u] for the closest position on the bezier path to the given point `pt`. +// Arguments: +// path = A bezier path to approximate. +// pt = The point to find the closest curve point to. +// N = The degree of the bezier curves. Cubic beziers have N=3. Default: 3 +// max_err = The maximum allowed error when approximating the closest approach. +// Example(2D): +// pt = [100,0]; +// bez = [[0,0], [20,40], [60,-25], [80,0], [100,25], [140,25], [160,0]]; +// pos = bezier_path_closest_point(bez, pt); +// xy = bezier_path_point(bez,pos[0],pos[1]); +// trace_bezier(bez, N=3); +// color("red") translate(pt) sphere(r=1); +// color("blue") translate(xy) sphere(r=1); +function bezier_path_closest_point(path, pt, N=3, max_err=0.01, seg=0, min_seg=undef, min_u=undef, min_dist=undef) = + let(curve = select(path,seg*N,(seg+1)*N)) + (seg*N+1 >= len(path))? ( + let(curve = select(path, min_seg*N, (min_seg+1)*N)) + [min_seg, bezier_segment_closest_point(curve, pt, max_err=max_err)] + ) : ( + let( + curve = select(path,seg*N,(seg+1)*N), + u = bezier_segment_closest_point(curve, pt, max_err=0.05), + dist = norm(bez_point(curve, u)-pt), + mseg = (min_dist==undef || dist +use +use +use + + +function CR_corner(size, orient=[0,0,0], trans=[0,0,0]) = + let ( + r = 0.4, + k = r/2, + // I know this patch is not yet correct for continuous + // rounding, but it's a first approximation proof of concept. + // Currently this is a degree 4 triangular patch. + patch = [ + [[0,1,1], [0,r,1], [0,0,1], [r,0,1], [1,0,1]], + [[0,1,r], [0,k,k], [k,0,k], [1,0,r]], + [[0,1,0], [k,k,0], [1,0,0]], + [[r,1,0], [1,r,0]], + [[1,1,0]] + ] + ) [for (row=patch) + translate_points(v=trans, + rotate_points3d(v=orient, + scale_points(v=size, row) + ) + ) + ]; + + +function CR_edge(size, orient=[0,0,0], trans=[0,0,0]) = + let ( + r = 0.4, + a = -1/2, + b = -1/4, + c = 1/4, + d = 1/2, + // I know this patch is not yet correct for continuous + // rounding, but it's a first approximation proof of concept. + // Currently this is a degree 4 rectangular patch. + patch = [ + [[1,0,a], [1,0,b], [1,0,0], [1,0,c], [1,0,d]], + [[r,0,a], [r,0,b], [r,0,0], [r,0,c], [r,0,d]], + [[0,0,a], [0,0,b], [0,0,0], [0,0,c], [0,0,d]], + [[0,r,a], [0,r,b], [0,r,0], [0,r,c], [0,r,d]], + [[0,1,a], [0,1,b], [0,1,0], [0,1,c], [0,1,d]] + ] + ) [for (row=patch) + translate_points(v=trans, + rotate_points3d(v=orient, + scale_points(v=size, row) + ) + ) + ]; + + +module CR_cube(size=[100,100,100], r=10, splinesteps=8, cheat=false) +{ + s = size-2*[r,r,r]; + h = size/2; + corners = [ + CR_corner([r,r,r], orient=ORIENT_Z, trans=[-size.x/2, -size.y/2, -size.z/2]), + CR_corner([r,r,r], orient=ORIENT_Z_90, trans=[ size.x/2, -size.y/2, -size.z/2]), + CR_corner([r,r,r], orient=ORIENT_Z_180, trans=[ size.x/2, size.y/2, -size.z/2]), + CR_corner([r,r,r], orient=ORIENT_Z_270, trans=[-size.x/2, size.y/2, -size.z/2]), + + CR_corner([r,r,r], orient=ORIENT_ZNEG, trans=[ size.x/2, -size.y/2, size.z/2]), + CR_corner([r,r,r], orient=ORIENT_ZNEG_90, trans=[-size.x/2, -size.y/2, size.z/2]), + CR_corner([r,r,r], orient=ORIENT_ZNEG_180, trans=[-size.x/2, size.y/2, size.z/2]), + CR_corner([r,r,r], orient=ORIENT_ZNEG_270, trans=[ size.x/2, size.y/2, size.z/2]) + ]; + edges = [ + CR_edge([r, r, s.x], orient=ORIENT_X, trans=[ 0, -h.y, -h.z]), + CR_edge([r, r, s.x], orient=ORIENT_X_90, trans=[ 0, h.y, -h.z]), + CR_edge([r, r, s.x], orient=ORIENT_X_180, trans=[ 0, h.y, h.z]), + CR_edge([r, r, s.x], orient=ORIENT_X_270, trans=[ 0, -h.y, h.z]), + + CR_edge([r, r, s.y], orient=ORIENT_Y, trans=[ h.x, 0, -h.z]), + CR_edge([r, r, s.y], orient=ORIENT_Y_90, trans=[-h.x, 0, -h.z]), + CR_edge([r, r, s.y], orient=ORIENT_Y_180, trans=[-h.x, 0, h.z]), + CR_edge([r, r, s.y], orient=ORIENT_Y_270, trans=[ h.x, 0, h.z]), + + CR_edge([r, r, s.z], orient=ORIENT_Z, trans=[-h.x, -h.y, 0]), + CR_edge([r, r, s.z], orient=ORIENT_Z_90, trans=[ h.x, -h.y, 0]), + CR_edge([r, r, s.z], orient=ORIENT_Z_180, trans=[ h.x, h.y, 0]), + CR_edge([r, r, s.z], orient=ORIENT_Z_270, trans=[-h.x, h.y, 0]) + ]; + faces = [ + // Yes, these are degree 1 bezier patches. That means just the four corner points. + // Since these are flat, it doesn't matter what degree they are, and this will reduce calculation overhead. + bezier_patch_flat([s.y, s.z], N=1, orient=ORIENT_X, trans=[ h.x, 0, 0]), + bezier_patch_flat([s.y, s.z], N=1, orient=ORIENT_XNEG, trans=[-h.x, 0, 0]), + + bezier_patch_flat([s.x, s.z], N=1, orient=ORIENT_Y, trans=[ 0, h.y, 0]), + bezier_patch_flat([s.x, s.z], N=1, orient=ORIENT_YNEG, trans=[ 0, -h.y, 0]), + + bezier_patch_flat([s.x, s.y], N=1, orient=ORIENT_Z, trans=[ 0, 0, h.z]), + bezier_patch_flat([s.x, s.y], N=1, orient=ORIENT_ZNEG, trans=[ 0, 0, -h.z]) + ]; + // Generating all the patches above took about 0.05 secs. + + if (cheat) { + // Hulling just the corners takes less than a second. + hull() bezier_polyhedron(tris=corners, splinesteps=splinesteps); + } else { + // Generating the polyhedron fully from bezier patches takes 3 seconds on my laptop. + bezier_polyhedron(patches=concat(edges, faces), tris=corners, splinesteps=splinesteps); + } +} + + +CR_cube(size=[100,100,100], r=20, splinesteps=9, cheat=false); +cube(1); + + + +// vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/math.scad b/math.scad index 9cf38ad..8384812 100644 --- a/math.scad +++ b/math.scad @@ -35,8 +35,10 @@ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + include + // Function: Cpi() // Status: DEPRECATED, use `PI` instead. // Description: @@ -94,7 +96,7 @@ function constrain(v, minval, maxval) = min(maxval, max(minval, v)); // Arguments: // x = The value to constrain. // m = Modulo value. -function posmod(x,m) = (x % m + m) % m; +function posmod(x,m) = (x%m) + (x<0)? m : 0; // Function: modrange() @@ -117,7 +119,7 @@ function modrange(x, y, m, step=1) = a = posmod(x, m), b = posmod(y, m), c = step>0? (a>b? b+m : b) : (ab)? 1 : 0)) : + is_str(a)? -1 : + is_str(b)? 1 : + compare_lists(a,b); + + +// Function: compare_lists() +// Usage: +// compare_lists(a, b) +// Description: +// Compare contents of two lists. +// Returns <0 if `a`<`b`. +// Returns >0 if `a`>`b`. +// Returns 0 if `a`==`b`. +// Arguments: +// a = First list to compare. +// b = Second list to compare. +function compare_lists(a, b, n=0) = + let (la = len(a), lb = len(b)) + (la<=n && lb<=n)? 0 : + (la<=n)? -1 : + (lb<=n)? 1 : + let (cmp = compare_vals(a[n], b[n])) + (cmp != 0)? cmp : + compare_lists(a, b, n+1); // Function: any() @@ -265,15 +306,13 @@ function cdr(list) = len(list)<=1? [] : [for (i=[1:len(list)-1]) list[i]]; // any([1,5,true]); // Returns true. // any([[0,0], [0,0]]); // Returns false. // any([[0,0], [1,0]]); // Returns true. -function any(l, s=0, e=-1) = - let( - e = e<0? e+len(l) : e, - m = ceil((s+e)/2) - ) - (e==s)? ( - is_array(l[s])? any(l[s]) : (l[s]? true : false) - ) : ( - any(l,s,m-1)? true : any(l,m,e) +function any(l, i=0, succ=false) = + (i>=len(l) || succ)? succ : + any( + l, i=i+1, succ=( + is_array(l[i])? any(l[i]) : + !(!l[i]) + ) ); @@ -290,18 +329,78 @@ function any(l, s=0, e=-1) = // all([[0,0], [0,0]]); // Returns false. // all([[0,0], [1,0]]); // Returns false. // all([[1,1], [1,1]]); // Returns true. -function all(l, s=0, e=-1) = - let( - e = e<0? e+len(l) : e, - m = ceil((s+e)/2) - ) - (e==s)? ( - is_array(l[s])? all(l[s]) : (l[s]? true : false) - ) : ( - (!all(l,s,m-1))? false : all(l,m,e) +function all(l, i=0, fail=false) = + (i>=len(l) || fail)? (!fail) : + all( + l, i=i+1, fail=( + is_array(l[i])? !all(l[i]) : + !l[i] + ) ); +// Function: count_true() +// Usage: +// count_true(l) +// Description: +// Returns the number of items in `l` that evaluate as true. +// If `l` is a lists of lists, this is applied recursively to each +// sublist. Returns the total count of items that evaluate as true +// in all recursive sublists. +// Arguments: +// l = The list to test for true items. +// nmax = If given, stop counting if `nmax` items evaluate as true. +// Example: +// count_true([0,false,undef]); // Returns 0. +// count_true([1,false,undef]); // Returns 1. +// count_true([1,5,false]); // Returns 2. +// count_true([1,5,true]); // Returns 3. +// count_true([[0,0], [0,0]]); // Returns 0. +// count_true([[0,0], [1,0]]); // Returns 1. +// count_true([[1,1], [1,1]]); // Returns 4. +// count_true([[1,1], [1,1]], nmax=3); // Returns 3. +function count_true(l, nmax=undef, i=0, cnt=0) = + (i>=len(l) || (nmax!=undef && cnt>=nmax))? cnt : + count_true( + l=l, nmax=nmax, i=i+1, cnt=cnt+( + is_array(l[i])? count_true(l[i], nmax=nmax-cnt) : + (l[i]? 1 : 0) + ) + ); + + + +// Section: List/Array Operations + +// Function: cdr() +// Status: DEPRECATED, use `slice(list,1,-1)` instead. +// Description: Returns all but the first item of a given array. +// Arguments: +// list = The list to get the tail of. +function cdr(list) = len(list)<=1? [] : [for (i=[1:len(list)-1]) list[i]]; + + + +// Function: replist() +// Usage: +// replist(val, n) +// Description: +// Generates a list or array of `n` copies of the given `list`. +// If the count `n` is given as a list of counts, then this creates a +// multi-dimensional array, filled with `val`. +// Arguments: +// val = The value to repeat to make the list or array. +// n = The number of copies to make of `val`. +// Example: +// replist(1, 4); // Returns [1,1,1,1] +// replist(8, [2,3]); // Returns [[8,8,8], [8,8,8]] +// replist(0, [2,2,3]); // Returns [[[0,0,0],[0,0,0]], [[0,0,0],[0,0,0]]] +// replist([1,2,3],3); // Returns [[1,2,3], [1,2,3], [1,2,3]] +function replist(val, n, i=0) = + is_scalar(n)? [for(j=[1:n]) val] : + (i>=len(n))? val : + [for (j=[1:n[i]]) replist(val, n, i+1)]; + // Function: in_list() // Description: Returns true if value `x` is in list `l`. @@ -316,6 +415,7 @@ function all(l, s=0, e=-1) = function in_list(x,l,idx=undef) = search([x], l, num_returns_per_match=1, index_col_num=idx) != [[]]; + // Function: slice() // Description: // Returns a slice of a list. The first item is index 0. @@ -336,6 +436,7 @@ function slice(arr,st,end) = let( ) (s==e)? [] : [for (i=[s:e-1]) if (e>s) arr[i]]; + // Function: wrap_range() // Status: DEPRECATED, use `select()` instead. // Description: @@ -378,12 +479,19 @@ function wrap_range(list, start, end=undef) = select(list,start,end); // select(l, [1:3]); // Returns [4,5,6] // select(l, [1,3]); // Returns [4,6] function select(list, start, end=undef) = - let(l = len(list)) - !is_def(end)? ( + let(l=len(list)) + (list==[])? [] : + end==undef? ( is_scalar(start)? - list[posmod(start, l)] : - [for (i=start) list[posmod(i, l)]] - ) : [for (i = modrange(start, end, l)) list[i]]; + let(s=(start%l+l)%l) list[s] : + [for (i=start) list[(i%l+l)%l]] + ) : ( + let(s=(start%l+l)%l, e=(end%l+l)%l) + (s<=e)? + [for (i = [s:e]) list[i]] : + concat([for (i = [s:l-1]) list[i]], [for (i = [0:e]) list[i]]) + ); + // Function: reverse() @@ -415,9 +523,40 @@ function array_subindex(v, idx) = [ ]; +// Function: list_range() +// Usage: +// list_range(n, [s], [e], [step]) +// list_range(e, [step]) +// list_range(s, e, [step]) +// Description: +// Returns a list, counting up from starting value `s`, by `step` increments, +// until either `n` values are in the list, or it reaches the end value `e`. +// Arguments: +// n = Desired number of values in returned list, if given. +// s = Starting value. Default: 0 +// e = Ending value to stop at, if given. +// step = Amount to increment each value. Default: 1 +// Example: +// list_range(4); // Returns [0,1,2,3] +// list_range(n=4, step=2); // Returns [0,2,4,6] +// list_range(n=4, s=3, step=3); // Returns [3,6,9,12] +// list_range(n=4, s=3, e=9, step=3); // Returns [3,6,9] +// list_range(e=3); // Returns [0,1,2,3] +// list_range(e=6, step=2); // Returns [0,2,4,6] +// list_range(s=3, e=5); // Returns [3,4,5] +// list_range(s=3, e=8, step=2); // Returns [3,5,7] +// list_range(s=4, e=8, step=2); // Returns [4,6,8] +// list_range(n=4, s=[3,4], step=[2,3]); // Returns [[3,4], [5,7], [7,10], [9,13]] +function list_range(n=undef, s=0, e=undef, step=1) = + (n!=undef && e!=undef)? [for (i=[0:n-1]) let(v=s+step*i) if (v<=e) v] : + (n!=undef)? [for (i=[0:n-1]) let(v=s+step*i) v] : + (e!=undef)? [for (v=[s:step:e]) v] : + assertion(false, "Must supply one of `n` or `e`."); + + // Function: array_shortest() // Description: -// Returns the length of the shortest list in a list of list. +// Returns the length of the shortest sublist in a list of lists. // Arguments: // vecs = A list of lists. function array_shortest(vecs) = min([for (v = vecs) len(v)]); @@ -425,7 +564,7 @@ function array_shortest(vecs) = min([for (v = vecs) len(v)]); // Function: array_longest() // Description: -// Returns the length of the longest list in a list of list. +// Returns the length of the longest sublist in a list of lists. // Arguments: // vecs = A list of lists. function array_longest(vecs) = max([for (v = vecs) len(v)]); @@ -526,6 +665,98 @@ function array_group(v, cnt=2, dflt=0) = [for (i = [0:cnt:len(v)-1]) [for (j = [ function flatten(l) = [for (a = l) for (b = a) b]; +// Function: sort() +// Usage: +// sort(arr, [idx]) +// Description: +// Sorts the given list using `compare_vals()`. +// Arguments: +// arr = The list to sort. +// idx = If given, the index, range, or list of indices of sublist items to compare. +// Example: +// l = [45,2,16,37,8,3,9,23,89,12,34]; +// sorted = sort(l); // Returns [2,3,8,9,12,16,23,34,37,45,89] +function sort(arr, idx=undef) = + (len(arr)<=1) ? arr : + let( + pivot = arr[floor(len(arr)/2)], + pivotval = idx==undef? pivot : [for (i=idx) pivot[i]], + compare = [ + for (entry = arr) let( + val = idx==undef? entry : [for (i=idx) entry[i]] + ) compare_vals(val, pivotval) + ], + lesser = [ for (i = [0:len(arr)-1]) if (compare[i] < 0) arr[i] ], + equal = [ for (i = [0:len(arr)-1]) if (compare[i] ==0) arr[i] ], + greater = [ for (i = [0:len(arr)-1]) if (compare[i] > 0) arr[i] ] + ) + concat(sort(lesser,idx), equal, sort(greater,idx)); + + + +// Function: unique() +// Usage: +// unique(arr); +// Description: +// Returns a sorted list with all repeated items removed. +// Arguments: +// arr = The list to uniquify. +function unique(arr) = + len(arr)<=1? arr : let( + sorted = sort(arr) + ) [ + for (i=[0:len(sorted)-1]) + if (i==0 || (sorted[i] != sorted[i-1])) + sorted[i] + ]; + + + +// Internal. Not exposed. +function _array_dim_recurse(v) = + !is_list(v[0])? ( + sum( [for(entry=v) is_list(entry) ? 1 : 0]) == 0 ? [] : [undef] + ) : let( + firstlen = len(v[0]), + first = sum( [for(entry = v) len(entry) == firstlen ? 0 : 1] ) == 0 ? firstlen : undef, + leveldown = flatten(v) + ) is_list(leveldown[0])? ( + concat([first],_array_dim_recurse(leveldown)) + ) : [first]; + + +// Function: array_dim() +// Usage: +// array_dim(v, [depth]) +// Description: +// Returns the size of a multi-dimensional array. Returns a list of +// dimension lengths. The length of `v` is the dimension `0`. The +// length of the items in `v` is dimension `1`. The length of the +// items in the items in `v` is dimension `2`, etc. For each dimension, +// if the length of items at that depth is inconsistent, `undef` will +// be returned. If no items of that dimension depth exist, `0` is +// returned. Otherwise, the consistent length of items in that +// dimensional depth is returned. +// Arguments: +// v = Array to get dimensions of. +// depth = Dimension to get size of. If not given, returns a list of dimension lengths. +// Examples: +// array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]]); // Returns [2,2,3] +// array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]], 0); // Returns 2 +// array_dim([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]], 2); // Returns 3 +// array_dim([[[1,2,3],[4,5,6]],[[7,8,9]]]); // Returns [2,undef,3] +function array_dim(v, depth=undef) = + (depth == undef)? ( + concat([len(v)], _array_dim_recurse(v)) + ) : (depth == 0)? ( + len(v) + ) : ( + let(dimlist = _array_dim_recurse(v)) + (depth > len(dimlist))? 0 : dimlist[depth-1] + ); + + + // Section: Vector Manipulation // Function: vmul() @@ -881,8 +1112,8 @@ function ident(n) = [for (i = [0:n-1]) [for (j = [0:n-1]) (i==j)?1:0]]; // Create an identity matrix, for 3 axes. -ident3 = ident(3); -ident4 = ident(4); +//ident3 = ident(3); +//ident4 = ident(4); // Function: mat3_to_mat4() diff --git a/paths.scad b/paths.scad index f665342..e64cb28 100644 --- a/paths.scad +++ b/paths.scad @@ -91,6 +91,21 @@ function simplify3d_path(path, eps=1e-6) = concat( ); +// Function: path_length() +// Usage: +// path3d_length(path) +// Description: +// Returns the length of the path. +// Arguments: +// path = The list of points of the path to measure. +// Example: +// path = [[0,0], [5,35], [60,-25], [80,0]]; +// echo(path_length(path)); +function path_length(path) = + len(path)<2? 0 : + sum([for (i = [0:len(path)-2]) norm(path[i+1]-path[i])]); + + // Function: path2d_regular_ngon() // Description: // Returns a 2D open counter-clockwise path of the vertices of a regular polygon of `n` sides.