diff --git a/distributors.scad b/distributors.scad index 4932463..5e08d37 100644 --- a/distributors.scad +++ b/distributors.scad @@ -7,7 +7,7 @@ ////////////////////////////////////////////////////////////////////// -// Section: Translational Distributors +// Section: Translating copies of all the children ////////////////////////////////////////////////////////////////////// @@ -267,185 +267,6 @@ module zcopies(spacing, n, l, sp) -// Module: distribute() -// -// Description: -// Spreads out each individual child along the direction `dir`. -// Every child is placed at a different position, in order. -// This is useful for laying out groups of disparate objects -// where you only really care about the spacing between them. -// -// Usage: -// distribute(spacing, dir, [sizes]) ... -// distribute(l, dir, [sizes]) ... -// -// Arguments: -// spacing = Spacing to add between each child. (Default: 10.0) -// sizes = Array containing how much space each child will need. -// dir = Vector direction to distribute copies along. -// l = Length to distribute copies along. -// -// Side Effects: -// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. -// `$idx` is set to the index number of each child being copied. -// -// Example: -// distribute(sizes=[100, 30, 50], dir=UP) { -// sphere(r=50); -// cube([10,20,30], center=true); -// cylinder(d=30, h=50, center=true); -// } -module distribute(spacing=undef, sizes=undef, dir=RIGHT, l=undef) -{ - gaps = ($children < 2)? [0] : - !is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] : - [for (i=[0:1:$children-2]) 0]; - spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); - gaps2 = [for (gap = gaps) gap+spc]; - spos = dir * -sum(gaps2)/2; - spacings = cumsum([0, each gaps2]); - for (i=[0:1:$children-1]) { - $pos = spos + spacings[i] * dir; - $idx = i; - translate($pos) children(i); - } -} - - -// Module: xdistribute() -// -// Description: -// Spreads out each individual child along the X axis. -// Every child is placed at a different position, in order. -// This is useful for laying out groups of disparate objects -// where you only really care about the spacing between them. -// -// Usage: -// xdistribute(spacing, [sizes]) ... -// xdistribute(l, [sizes]) ... -// -// Arguments: -// spacing = spacing between each child. (Default: 10.0) -// sizes = Array containing how much space each child will need. -// l = Length to distribute copies along. -// -// Side Effects: -// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. -// `$idx` is set to the index number of each child being copied. -// -// Example: -// xdistribute(sizes=[100, 10, 30], spacing=40) { -// sphere(r=50); -// cube([10,20,30], center=true); -// cylinder(d=30, h=50, center=true); -// } -module xdistribute(spacing=10, sizes=undef, l=undef) -{ - dir = RIGHT; - gaps = ($children < 2)? [0] : - !is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] : - [for (i=[0:1:$children-2]) 0]; - spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); - gaps2 = [for (gap = gaps) gap+spc]; - spos = dir * -sum(gaps2)/2; - spacings = cumsum([0, each gaps2]); - for (i=[0:1:$children-1]) { - $pos = spos + spacings[i] * dir; - $idx = i; - translate($pos) children(i); - } -} - - -// Module: ydistribute() -// -// Description: -// Spreads out each individual child along the Y axis. -// Every child is placed at a different position, in order. -// This is useful for laying out groups of disparate objects -// where you only really care about the spacing between them. -// -// Usage: -// ydistribute(spacing, [sizes]) -// ydistribute(l, [sizes]) -// -// Arguments: -// spacing = spacing between each child. (Default: 10.0) -// sizes = Array containing how much space each child will need. -// l = Length to distribute copies along. -// -// Side Effects: -// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. -// `$idx` is set to the index number of each child being copied. -// -// Example: -// ydistribute(sizes=[30, 20, 100], spacing=40) { -// cylinder(d=30, h=50, center=true); -// cube([10,20,30], center=true); -// sphere(r=50); -// } -module ydistribute(spacing=10, sizes=undef, l=undef) -{ - dir = BACK; - gaps = ($children < 2)? [0] : - !is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] : - [for (i=[0:1:$children-2]) 0]; - spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); - gaps2 = [for (gap = gaps) gap+spc]; - spos = dir * -sum(gaps2)/2; - spacings = cumsum([0, each gaps2]); - for (i=[0:1:$children-1]) { - $pos = spos + spacings[i] * dir; - $idx = i; - translate($pos) children(i); - } -} - - -// Module: zdistribute() -// -// Description: -// Spreads out each individual child along the Z axis. -// Every child is placed at a different position, in order. -// This is useful for laying out groups of disparate objects -// where you only really care about the spacing between them. -// -// Usage: -// zdistribute(spacing, [sizes]) -// zdistribute(l, [sizes]) -// -// Arguments: -// spacing = spacing between each child. (Default: 10.0) -// sizes = Array containing how much space each child will need. -// l = Length to distribute copies along. -// -// Side Effects: -// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. -// `$idx` is set to the index number of each child being copied. -// -// Example: -// zdistribute(sizes=[30, 20, 100], spacing=40) { -// cylinder(d=30, h=50, center=true); -// cube([10,20,30], center=true); -// sphere(r=50); -// } -module zdistribute(spacing=10, sizes=undef, l=undef) -{ - dir = UP; - gaps = ($children < 2)? [0] : - !is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] : - [for (i=[0:1:$children-2]) 0]; - spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); - gaps2 = [for (gap = gaps) gap+spc]; - spos = dir * -sum(gaps2)/2; - spacings = cumsum([0, each gaps2]); - for (i=[0:1:$children-1]) { - $pos = spos + spacings[i] * dir; - $idx = i; - translate($pos) children(i); - } -} - // Module: grid2d() @@ -632,7 +453,7 @@ module grid3d(xa=[0], ya=[0], za=[0], n=undef, spacing=undef) ////////////////////////////////////////////////////////////////////// -// Section: Rotational Distributors +// Section: Rotating copies of all children ////////////////////////////////////////////////////////////////////// @@ -1018,6 +839,7 @@ module ovoid_spread(r=undef, d=undef, n=100, cone_ang=90, scale=[1,1,1], perp=tr } } +// Section: Placing copies of all children on a path // Module: path_spread() @@ -1149,7 +971,7 @@ module path_spread(path, n, spacing, sp=undef, rotate_children=true, closed=fals ////////////////////////////////////////////////////////////////////// -// Section: Reflectional Distributors +// Section: Making a copy of all children with reflection ////////////////////////////////////////////////////////////////////// @@ -1305,6 +1127,190 @@ module zflip_copy(offset=0, z=0) mirror_copy(v=[0,0,1], offset=offset, cp=[0,0,z]) children(); } +//////////////////// +// Section: Distributing children individually along a line +/////////////////// + +// Module: distribute() +// +// Description: +// Spreads out each individual child along the direction `dir`. +// Every child is placed at a different position, in order. +// This is useful for laying out groups of disparate objects +// where you only really care about the spacing between them. +// +// Usage: +// distribute(spacing, dir, [sizes]) ... +// distribute(l, dir, [sizes]) ... +// +// Arguments: +// spacing = Spacing to add between each child. (Default: 10.0) +// sizes = Array containing how much space each child will need. +// dir = Vector direction to distribute copies along. +// l = Length to distribute copies along. +// +// Side Effects: +// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. +// `$idx` is set to the index number of each child being copied. +// +// Example: +// distribute(sizes=[100, 30, 50], dir=UP) { +// sphere(r=50); +// cube([10,20,30], center=true); +// cylinder(d=30, h=50, center=true); +// } +module distribute(spacing=undef, sizes=undef, dir=RIGHT, l=undef) +{ + gaps = ($children < 2)? [0] : + !is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] : + [for (i=[0:1:$children-2]) 0]; + spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); + gaps2 = [for (gap = gaps) gap+spc]; + spos = dir * -sum(gaps2)/2; + spacings = cumsum([0, each gaps2]); + for (i=[0:1:$children-1]) { + $pos = spos + spacings[i] * dir; + $idx = i; + translate($pos) children(i); + } +} + + +// Module: xdistribute() +// +// Description: +// Spreads out each individual child along the X axis. +// Every child is placed at a different position, in order. +// This is useful for laying out groups of disparate objects +// where you only really care about the spacing between them. +// +// Usage: +// xdistribute(spacing, [sizes]) ... +// xdistribute(l, [sizes]) ... +// +// Arguments: +// spacing = spacing between each child. (Default: 10.0) +// sizes = Array containing how much space each child will need. +// l = Length to distribute copies along. +// +// Side Effects: +// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. +// `$idx` is set to the index number of each child being copied. +// +// Example: +// xdistribute(sizes=[100, 10, 30], spacing=40) { +// sphere(r=50); +// cube([10,20,30], center=true); +// cylinder(d=30, h=50, center=true); +// } +module xdistribute(spacing=10, sizes=undef, l=undef) +{ + dir = RIGHT; + gaps = ($children < 2)? [0] : + !is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] : + [for (i=[0:1:$children-2]) 0]; + spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); + gaps2 = [for (gap = gaps) gap+spc]; + spos = dir * -sum(gaps2)/2; + spacings = cumsum([0, each gaps2]); + for (i=[0:1:$children-1]) { + $pos = spos + spacings[i] * dir; + $idx = i; + translate($pos) children(i); + } +} + + +// Module: ydistribute() +// +// Description: +// Spreads out each individual child along the Y axis. +// Every child is placed at a different position, in order. +// This is useful for laying out groups of disparate objects +// where you only really care about the spacing between them. +// +// Usage: +// ydistribute(spacing, [sizes]) +// ydistribute(l, [sizes]) +// +// Arguments: +// spacing = spacing between each child. (Default: 10.0) +// sizes = Array containing how much space each child will need. +// l = Length to distribute copies along. +// +// Side Effects: +// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. +// `$idx` is set to the index number of each child being copied. +// +// Example: +// ydistribute(sizes=[30, 20, 100], spacing=40) { +// cylinder(d=30, h=50, center=true); +// cube([10,20,30], center=true); +// sphere(r=50); +// } +module ydistribute(spacing=10, sizes=undef, l=undef) +{ + dir = BACK; + gaps = ($children < 2)? [0] : + !is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] : + [for (i=[0:1:$children-2]) 0]; + spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); + gaps2 = [for (gap = gaps) gap+spc]; + spos = dir * -sum(gaps2)/2; + spacings = cumsum([0, each gaps2]); + for (i=[0:1:$children-1]) { + $pos = spos + spacings[i] * dir; + $idx = i; + translate($pos) children(i); + } +} + + +// Module: zdistribute() +// +// Description: +// Spreads out each individual child along the Z axis. +// Every child is placed at a different position, in order. +// This is useful for laying out groups of disparate objects +// where you only really care about the spacing between them. +// +// Usage: +// zdistribute(spacing, [sizes]) +// zdistribute(l, [sizes]) +// +// Arguments: +// spacing = spacing between each child. (Default: 10.0) +// sizes = Array containing how much space each child will need. +// l = Length to distribute copies along. +// +// Side Effects: +// `$pos` is set to the relative centerpoint of each child copy, and can be used to modify each child individually. +// `$idx` is set to the index number of each child being copied. +// +// Example: +// zdistribute(sizes=[30, 20, 100], spacing=40) { +// cylinder(d=30, h=50, center=true); +// cube([10,20,30], center=true); +// sphere(r=50); +// } +module zdistribute(spacing=10, sizes=undef, l=undef) +{ + dir = UP; + gaps = ($children < 2)? [0] : + !is_undef(sizes)? [for (i=[0:1:$children-2]) sizes[i]/2 + sizes[i+1]/2] : + [for (i=[0:1:$children-2]) 0]; + spc = !is_undef(l)? ((l - sum(gaps)) / ($children-1)) : default(spacing, 10); + gaps2 = [for (gap = gaps) gap+spc]; + spos = dir * -sum(gaps2)/2; + spacings = cumsum([0, each gaps2]); + for (i=[0:1:$children-1]) { + $pos = spos + spacings[i] * dir; + $idx = i; + translate($pos) children(i); + } +} + + // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/geometry.scad b/geometry.scad index 266a20c..30dc581 100644 --- a/geometry.scad +++ b/geometry.scad @@ -1,6 +1,9 @@ ////////////////////////////////////////////////////////////////////// // LibFile: geometry.scad -// Geometry helpers. +// Perform calculations on lines, polygons, planes and circles, including +// normals, intersections of objects, distance between objects, and tangent lines. +// Throughout this library, lines can be treated as either unbounded lines, as rays with +// a single endpoint or as segments, bounded by endpoints at both ends. // Includes: // include ////////////////////////////////////////////////////////////////////// @@ -630,14 +633,122 @@ function plane_line_intersection(plane, line, bounded=false, eps=EPSILON) = // Takes a possibly bounded line, and a 2D or 3D planar polygon, and finds their intersection. // If the line does not intersect the polygon then `undef` returns `undef`. // In 3D if the line is not on the plane of the polygon but intersects it then you get a single intersection point. -// Otherwise the polygon and line are in the same plane and you will get a list of segments. -// Use `is_vector` to distinguish these two cases. +// Otherwise the polygon and line are in the same plane, or when your input is 2D, ou will get a list of segments and +// single point lists. Use `is_vector` to distinguish these two cases. +// . +// In the 2D case, when single points are in the intersection they appear on the segment list as lists of a single point +// (like single point segments) so a single point intersection in 2D has the form `[[[x,y,z]]]` as compared +// to a single point intersection in 3D which has the form `[x,y,z]`. You can identify whether an entry in the +// segment list is a true segment by checking its length, which will be 2 for a segment and 1 for a point. // Arguments: // poly = The 3D planar polygon to find the intersection with. // line = A list of two distinct 3D points on the line. // bounded = If false, the line is considered unbounded. If true, it is treated as a bounded line segment. If given as `[true, false]` or `[false, true]`, the boundedness of the points are specified individually, allowing the line to be treated as a half-bounded ray. Default: false (unbounded) // nonzero = set to true to use the nonzero rule for determining it points are in a polygon. See point_in_polygon. Default: false. // eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9) +// Example: The line intersects the 3d hexagon in a single point. +// hex = zrot(140,p=rot([-45,40,20],p=path3d(hexagon(r=15)))); +// line = [[5,0,-13],[-3,-5,13]]; +// isect = polygon_line_intersection(hex,line); +// stroke(hex,closed=true); +// stroke(line); +// color("red")move(isect)sphere(r=1); +// Example: In 2D things are more complicated. The output is a list of intersection parts, in the simplest case a single segment. +// hex = hexagon(r=15); +// line = [[-20,10],[25,-7]]; +// isect = polygon_line_intersection(hex,line); +// stroke(hex,closed=true); +// stroke(line,endcaps="arrow2"); +// color("red") +// for(part=isect) +// if(len(part)==1) +// move(part[0]) sphere(r=1); +// else +// stroke(part); +// Example: In 2D things are more complicated. Here the line is treated as a ray. +// hex = hexagon(r=15); +// line = [[0,0],[25,-7]]; +// isect = polygon_line_intersection(hex,line,RAY); +// stroke(hex,closed=true); +// stroke(line,endcap2="arrow2"); +// color("red") +// for(part=isect) +// if(len(part)==1) +// move(part[0]) circle(r=1,$fn=12); +// else +// stroke(part); +// Example: Here the intersection is a single point, which is returned as a single point "path" on the path list. +// hex = hexagon(r=15); +// line = [[15,-10],[15,13]]; +// isect = polygon_line_intersection(hex,line,RAY); +// stroke(hex,closed=true); +// stroke(line,endcap2="arrow2"); +// color("red") +// for(part=isect) +// if(len(part)==1) +// move(part[0]) circle(r=1,$fn=12); +// else +// stroke(part); +// Example: Another way to get a single segment +// hex = hexagon(r=15); +// line = rot(30,p=[[15,-10],[15,25]],cp=[15,0]); +// isect = polygon_line_intersection(hex,line,RAY); +// stroke(hex,closed=true); +// stroke(line,endcap2="arrow2"); +// color("red") +// for(part=isect) +// if(len(part)==1) +// move(part[0]) circle(r=1,$fn=12); +// else +// stroke(part); +// Example: Single segment again +// star = star(r=15,n=8,step=2); +// line = [[20,-5],[-5,20]]; +// isect = polygon_line_intersection(star,line,RAY); +// stroke(star,closed=true); +// stroke(line,endcap2="arrow2"); +// color("red") +// for(part=isect) +// if(len(part)==1) +// move(part[0]) circle(r=1,$fn=12); +// else +// stroke(part); +// Example: Solution is two points +// star = star(r=15,n=8,step=3); +// line = rot(22.5,p=[[15,-10],[15,20]],cp=[15,0]); +// isect = polygon_line_intersection(star,line,SEGMENT); +// stroke(star,closed=true); +// stroke(line); +// color("red") +// for(part=isect) +// if(len(part)==1) +// move(part[0]) circle(r=1,$fn=12); +// else +// stroke(part); +// Example: Solution is list of three segments +// star = star(r=25,ir=9,n=8); +// line = [[-25,12],[25,12]]; +// isect = polygon_line_intersection(star,line); +// stroke(star,closed=true); +// stroke(line,endcaps="arrow2"); +// color("red") +// for(part=isect) +// if(len(part)==1) +// move(part[0]) circle(r=1,$fn=12); +// else +// stroke(part); +// Example: Solution is a mixture of segments and points +// star = star(r=25,ir=9,n=7); +// line = [left(10,p=star[8]), right(50,p=star[8])]; +// isect = polygon_line_intersection(star,line); +// stroke(star,closed=true); +// stroke(line,endcaps="arrow2"); +// color("red") +// for(part=isect) +// if(len(part)==1) +// move(part[0]) circle(r=1,$fn=12); +// else +// stroke(part); function polygon_line_intersection(poly, line, bounded=false, nonzero=false, eps=EPSILON) = assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." ) assert(is_path(poly,dim=[2,3]), "Invalid polygon." ) @@ -648,7 +759,7 @@ function polygon_line_intersection(poly, line, bounded=false, nonzero=false, eps poly = deduplicate(poly) ) len(poly[0])==2 ? // planar case - let( + let( linevec = unit(line[1] - line[0]), bound = 100*max(flatten(pointlist_bounds(poly))), boundedline = [line[0] + (bounded[0]? 0 : -bound) * linevec, @@ -664,14 +775,7 @@ function polygon_line_intersection(poly, line, bounded=false, nonzero=false, eps [part[1]] // Add segment end if it is on the polygon ] ) - ( - len(inside)==0 ? undef : - let( - seglist = [for (entry=_merge_segments(inside, [inside[0]], eps)) - same_shape(entry,[[0,0]]) ? entry[0]:entry] - ) - len(seglist)==1 && is_vector(seglist[0]) ? seglist[0] : seglist - ) + (len(inside)==0 ? undef : _merge_segments(inside, [inside[0]], eps)) : // 3d case let(indices = noncollinear_triple(poly)) indices==[] ? undef : // Polygon is collinear @@ -692,7 +796,8 @@ function polygon_line_intersection(poly, line, bounded=false, nonzero=false, eps line2d = project_plane(plane, line), segments = polygon_line_intersection(poly2d, line2d, bounded=bounded, nonzero=nonzero, eps=eps) ) - segments==undef ? undef : [for(seg=segments) lift_plane(plane,seg)]; + segments==undef ? undef + : [for(seg=segments) len(seg)==2 ? lift_plane(plane,seg) : [lift_plane(plane,seg[0])]]; function _merge_segments(insegs,outsegs, eps, i=1) = i==len(insegs) ? outsegs : @@ -700,7 +805,6 @@ function _merge_segments(insegs,outsegs, eps, i=1) = ? _merge_segments(insegs, [each list_head(outsegs),[last(outsegs)[0],last(insegs[i])]], eps, i+1) : _merge_segments(insegs, [each outsegs, insegs[i]], eps, i+1); - // Function: plane_intersection() // Usage: @@ -858,6 +962,44 @@ function above_plane(plane, point) = // Section: Circle Calculations +// Function: circle_line_intersection() +// Usage: +// isect = circle_line_intersection(c,,[line],[bounded],[eps]); +// Topics: Geometry, Circles, Lines, Intersection +// Description: +// Find intersection points between a 2d circle and a line, ray or segment specified by two points. +// By default the line is unbounded. +// Arguments: +// c = center of circle +// r = radius of circle +// --- +// d = diameter of circle +// line = two points defining the unbounded line +// bounded = false for unbounded line, true for a segment, or a vector [false,true] or [true,false] to specify a ray with the first or second end unbounded. Default: false +// eps = epsilon used for identifying the case with one solution. Default: 1e-9 +function circle_line_intersection(c,r,d,line,bounded=false,eps=EPSILON) = + let(r=get_radius(r=r,d=d,dflt=undef)) + assert(_valid_line(line,2), "Invalid 2d line.") + assert(is_vector(c,2), "Circle center must be a 2-vector") + assert(is_num(r) && r>0, "Radius must be positive") + assert(is_bool(bounded) || is_bool_list(bounded,2), "Invalid bound condition") + let( + bounded = force_list(bounded,2), + closest = line_closest_point(line,c), + d = norm(closest-c) + ) + d > r ? [] : + let( + isect = approx(d,r,eps) ? [closest] : + let( offset = sqrt(r*r-d*d), + uvec=unit(line[1]-line[0]) + ) [closest-offset*uvec, closest+offset*uvec] + ) + [for(p=isect) + if ((!bounded[0] || (p-line[0])*(line[1]-line[0])>=0) + && (!bounded[1] || (p-line[1])*(line[0]-line[1])>=0)) p]; + + // Function&Module: circle_2tangents() // Usage: As Function // circ = circle_2tangents(pt1, pt2, pt3, r|d, [tangents]); @@ -1168,44 +1310,6 @@ function circle_circle_tangents(c1,r1,c2,r2,d1,d2) = ]; -// Function: circle_line_intersection() -// Usage: -// isect = circle_line_intersection(c,,[line],[bounded],[eps]); -// Topics: Geometry, Circles, Lines, Intersection -// Description: -// Find intersection points between a 2d circle and a line, ray or segment specified by two points. -// By default the line is unbounded. -// Arguments: -// c = center of circle -// r = radius of circle -// --- -// d = diameter of circle -// line = two points defining the unbounded line -// bounded = false for unbounded line, true for a segment, or a vector [false,true] or [true,false] to specify a ray with the first or second end unbounded. Default: false -// eps = epsilon used for identifying the case with one solution. Default: 1e-9 -function circle_line_intersection(c,r,d,line,bounded=false,eps=EPSILON) = - let(r=get_radius(r=r,d=d,dflt=undef)) - assert(_valid_line(line,2), "Invalid 2d line.") - assert(is_vector(c,2), "Circle center must be a 2-vector") - assert(is_num(r) && r>0, "Radius must be positive") - assert(is_bool(bounded) || is_bool_list(bounded,2), "Invalid bound condition") - let( - bounded = force_list(bounded,2), - closest = line_closest_point(line,c), - d = norm(closest-c) - ) - d > r ? [] : - let( - isect = approx(d,r,eps) ? [closest] : - let( offset = sqrt(r*r-d*d), - uvec=unit(line[1]-line[0]) - ) [closest-offset*uvec, closest+offset*uvec] - ) - [for(p=isect) - if ((!bounded[0] || (p-line[0])*(line[1]-line[0])>=0) - && (!bounded[1] || (p-line[1])*(line[0]-line[1])>=0)) p]; - - // Section: Pointlists @@ -1686,7 +1790,7 @@ function polygon_shift_to_closest_point(poly, pt) = // Rotates and possibly reverses the point order of a 2d or 3d polygon path to optimize its pairwise point // association with a reference polygon. The two polygons must have the same number of vertices and be the same dimension. // The optimization is done by computing the distance, norm(reference[i]-poly[i]), between -// corresponding pairs of vertices of the two polygons and choosing the polygon point order that +// corresponding pairs of vertices of the two polygons and choosing the polygon point index rotation that // makes the total sum over all pairs as small as possible. Returns the reindexed polygon. Note // that the geometry of the polygon is not changed by this operation, just the labeling of its // vertices. If the input polygon is 2d and is oriented opposite the reference then its point order is diff --git a/shapes2d.scad b/shapes2d.scad index 0cfc5ae..8ea7461 100644 --- a/shapes2d.scad +++ b/shapes2d.scad @@ -1556,12 +1556,12 @@ function star(n, r, ir, d, or, od, id, step, realign=false, align_tip, align_pit assert(is_undef(align_tip) || is_vector(align_tip)) assert(is_undef(align_pit) || is_vector(align_pit)) assert(is_undef(align_tip) || is_undef(align_pit), "Can only specify one of align_tip and align_pit") + assert(is_def(n), "Must specify number of points, n") let( r = get_radius(r1=or, d1=od, r=r, d=d), count = num_defined([ir,id,step]), stepOK = is_undef(step) || (step>1 && step ////////////////////////////////////////////////////////////////////// @@ -8,15 +11,15 @@ include +// Creating Polyhedrons with VNF Structures -// Section: Creating Polyhedrons with VNF Structures +// Section: VNF Testing and Access // VNF stands for "Vertices'N'Faces". VNF structures are 2-item lists, `[VERTICES,FACES]` where the // first item is a list of vertex points, and the second is a list of face indices into the vertex // list. Each VNF is self contained, with face indices referring only to its own vertex list. // You can construct a `polyhedron()` in parts by describing each part in a self-contained VNF, then // merge the various VNFs to get the completed polyhedron vertex list and faces. - EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces. @@ -49,18 +52,6 @@ function vnf_vertices(vnf) = vnf[0]; function vnf_faces(vnf) = vnf[1]; -// Function: vnf_quantize() -// Usage: -// vnf2 = vnf_quantize(vnf,[q]); -// Description: -// Quantizes the vertex coordinates of the VNF to the given quanta `q`. -// Arguments: -// vnf = The VNF to quantize. -// q = The quanta to quantize the VNF coordinates to. -function vnf_quantize(vnf,q=pow(2,-12)) = - [[for (pt = vnf[0]) quant(pt,q)], vnf[1]]; - - // Function: vnf_get_vertex() // Usage: // vvnf = vnf_get_vertex(vnf, p); @@ -89,131 +80,7 @@ function vnf_get_vertex(vnf=EMPTY_VNF, p) = ]; -// Function: vnf_add_face() -// Usage: -// vnf_add_face(vnf, pts); -// Description: -// Given a VNF structure and a list of face vertex points, adds the face to the VNF structure. -// Returns the modified VNF structure `[VERTICES, FACES]`. It is up to the caller to make -// sure that the points are in the correct order to make the face normal point outwards. -// Arguments: -// vnf = The VNF structure to add a face to. -// pts = The vertex points for the face. -function vnf_add_face(vnf=EMPTY_VNF, pts) = - assert(is_vnf(vnf)) - assert(is_path(pts)) - let( - res = set_union(vnf[0], pts, get_indices=true), - face = deduplicate(res[0], closed=true) - ) [ - res[1], - concat(vnf[1], len(face)>2? [face] : []) - ]; - - -// Function: vnf_add_faces() -// Usage: -// vnf_add_faces(vnf, faces); -// Description: -// Given a VNF structure and a list of faces, where each face is given as a list of vertex points, -// adds the faces to the VNF structure. Returns the modified VNF structure `[VERTICES, FACES]`. -// It is up to the caller to make sure that the points are in the correct order to make the face -// normals point outwards. -// Arguments: -// vnf = The VNF structure to add a face to. -// faces = The list of faces, where each face is given as a list of vertex points. -function vnf_add_faces(vnf=EMPTY_VNF, faces) = - assert(is_vnf(vnf)) - assert(is_list(faces)) - let( - res = set_union(vnf[0], flatten(faces), get_indices=true), - idxs = res[0], - nverts = res[1], - offs = cumsum([0, for (face=faces) len(face)]), - ifaces = [ - for (i=idx(faces)) [ - for (j=idx(faces[i])) - idxs[offs[i]+j] - ] - ] - ) [ - nverts, - concat(vnf[1],ifaces) - ]; - - -// Function: vnf_merge() -// Usage: -// vnf = vnf_merge([VNF, VNF, VNF, ...], [cleanup],[eps]); -// Description: -// Given a list of VNF structures, merges them all into a single VNF structure. -// When cleanup=true, it consolidates all duplicate vertices with a tolerance `eps`, -// drops unreferenced vertices and any final face with less than 3 vertices. -// Unreferenced vertices of the input VNFs that doesn't duplicate any other vertex -// are not dropped. -// Arguments: -// vnfs - a list of the VNFs to merge in one VNF. -// cleanup - when true, consolidates the duplicate vertices of the merge. Default: false -// eps - the tolerance in finding duplicates when cleanup=true. Default: EPSILON -function vnf_merge(vnfs, cleanup=false, eps=EPSILON) = - is_vnf(vnfs) ? vnf_merge([vnfs], cleanup, eps) : - assert( is_vnf_list(vnfs) , "Improper vnf or vnf list")   - let ( - offs = cumsum([ 0, for (vnf = vnfs) len(vnf[0]) ]), - verts = [for (vnf=vnfs) each vnf[0]], - faces = - [ for (i = idx(vnfs)) - let( faces = vnfs[i][1] ) - for (face = faces) - if ( len(face) >= 3 ) - [ for (j = face) - assert( j>=0 && j= 3) dface - ] - ) - [nverts, nfaces]; - - -// Function: vnf_reverse_faces() -// Usage: -// rvnf = vnf_reverse_faces(vnf); -// Description: -// Reverses the facing of all the faces in the given VNF. -function vnf_reverse_faces(vnf) = - [vnf[0], [for (face=vnf[1]) reverse(face)]]; - - -// Function: vnf_triangulate() -// Usage: -// vnf2 = vnf_triangulate(vnf); -// Description: -// Forces triangulation of faces in the VNF that have more than 3 vertices. -function vnf_triangulate(vnf) = - let( - vnf = is_vnf_list(vnf)? vnf_merge(vnf) : vnf, - verts = vnf[0], - faces = [for (face=vnf[1]) each polygon_triangulate(verts, face)] - ) [verts, faces]; - +// Section: Constructing VNFs // Function: vnf_vertex_array() // Usage: @@ -469,6 +336,151 @@ function vnf_tri_array(points, row_wrap=false, reverse=false, vnf=EMPTY_VNF) = vnf_merge(cleanup=true, [vnf, [flatten(points), faces]]); +// Function: vnf_add_face() +// Usage: +// vnf_add_face(vnf, pts); +// Description: +// Given a VNF structure and a list of face vertex points, adds the face to the VNF structure. +// Returns the modified VNF structure `[VERTICES, FACES]`. It is up to the caller to make +// sure that the points are in the correct order to make the face normal point outwards. +// Arguments: +// vnf = The VNF structure to add a face to. +// pts = The vertex points for the face. +function vnf_add_face(vnf=EMPTY_VNF, pts) = + assert(is_vnf(vnf)) + assert(is_path(pts)) + let( + res = set_union(vnf[0], pts, get_indices=true), + face = deduplicate(res[0], closed=true) + ) [ + res[1], + concat(vnf[1], len(face)>2? [face] : []) + ]; + + +// Function: vnf_add_faces() +// Usage: +// vnf_add_faces(vnf, faces); +// Description: +// Given a VNF structure and a list of faces, where each face is given as a list of vertex points, +// adds the faces to the VNF structure. Returns the modified VNF structure `[VERTICES, FACES]`. +// It is up to the caller to make sure that the points are in the correct order to make the face +// normals point outwards. +// Arguments: +// vnf = The VNF structure to add a face to. +// faces = The list of faces, where each face is given as a list of vertex points. +function vnf_add_faces(vnf=EMPTY_VNF, faces) = + assert(is_vnf(vnf)) + assert(is_list(faces)) + let( + res = set_union(vnf[0], flatten(faces), get_indices=true), + idxs = res[0], + nverts = res[1], + offs = cumsum([0, for (face=faces) len(face)]), + ifaces = [ + for (i=idx(faces)) [ + for (j=idx(faces[i])) + idxs[offs[i]+j] + ] + ] + ) [ + nverts, + concat(vnf[1],ifaces) + ]; + + +// Function: vnf_merge() +// Usage: +// vnf = vnf_merge([VNF, VNF, VNF, ...], [cleanup],[eps]); +// Description: +// Given a list of VNF structures, merges them all into a single VNF structure. +// When cleanup=true, it consolidates all duplicate vertices with a tolerance `eps`, +// drops unreferenced vertices and any final face with less than 3 vertices. +// Unreferenced vertices of the input VNFs that doesn't duplicate any other vertex +// are not dropped. +// Arguments: +// vnfs - a list of the VNFs to merge in one VNF. +// cleanup - when true, consolidates the duplicate vertices of the merge. Default: false +// eps - the tolerance in finding duplicates when cleanup=true. Default: EPSILON +function vnf_merge(vnfs, cleanup=false, eps=EPSILON) = + is_vnf(vnfs) ? vnf_merge([vnfs], cleanup, eps) : + assert( is_vnf_list(vnfs) , "Improper vnf or vnf list")   + let ( + offs = cumsum([ 0, for (vnf = vnfs) len(vnf[0]) ]), + verts = [for (vnf=vnfs) each vnf[0]], + faces = + [ for (i = idx(vnfs)) + let( faces = vnfs[i][1] ) + for (face = faces) + if ( len(face) >= 3 ) + [ for (j = face) + assert( j>=0 && j= 3) dface + ] + ) + [nverts, nfaces]; + + + +// Section: Altering the VNF Internals + + +// Function: vnf_reverse_faces() +// Usage: +// rvnf = vnf_reverse_faces(vnf); +// Description: +// Reverses the facing of all the faces in the given VNF. +function vnf_reverse_faces(vnf) = + [vnf[0], [for (face=vnf[1]) reverse(face)]]; + + +// Function: vnf_quantize() +// Usage: +// vnf2 = vnf_quantize(vnf,[q]); +// Description: +// Quantizes the vertex coordinates of the VNF to the given quanta `q`. +// Arguments: +// vnf = The VNF to quantize. +// q = The quanta to quantize the VNF coordinates to. +function vnf_quantize(vnf,q=pow(2,-12)) = + [[for (pt = vnf[0]) quant(pt,q)], vnf[1]]; + + +// Function: vnf_triangulate() +// Usage: +// vnf2 = vnf_triangulate(vnf); +// Description: +// Triangulates faces in the VNF that have more than 3 vertices. +function vnf_triangulate(vnf) = + let( + vnf = is_vnf_list(vnf)? vnf_merge(vnf) : vnf, + verts = vnf[0], + faces = [for (face=vnf[1]) each polygon_triangulate(verts, face)] + ) [verts, faces]; + + + +// Section: Turning a VNF into geometry + // Module: vnf_polyhedron() // Usage: @@ -494,7 +506,6 @@ module vnf_polyhedron(vnf, convexity=2, extent=true, cp=[0,0,0], anchor="origin" } - // Module: vnf_wireframe() // Usage: // vnf_wireframe(vnf, ); @@ -529,6 +540,8 @@ module vnf_wireframe(vnf, r, d) } +// Section: Operations on VNFs + // Function: vnf_volume() // Usage: // vol = vnf_volume(vnf); @@ -546,6 +559,11 @@ function vnf_volume(vnf) = ])/6; +// Function: vnf_area() +// Usage: +// area = vnf_area(vnf); +// Description: +// Returns the surface area in any VNF by adding up the area of all its faces. The VNF need not be a manifold. function vnf_area(vnf) = let(verts=vnf[0]) sum([for(face=vnf[1]) polygon_area(select(verts,face))]); @@ -579,6 +597,115 @@ function vnf_centroid(vnf) = pos[1]/pos[0]/4; +// Function: vnf_halfspace() +// Usage: +// newvnf = vnf_halfspace(plane, vnf, [closed]); +// Description: +// Returns the intersection of the vnf with a half space. The half space is defined by +// plane = [A,B,C,D], taking the side where the normal [A,B,C] points: Ax+By+Cz≥D. +// If closed is set to false then the cut face is not included in the vnf. This could +// allow further extension of the vnf by merging with other vnfs. +// Arguments: +// plane = plane defining the boundary of the half space +// vnf = vnf to cut +// closed = if false do not return include cut face(s). Default: true +// Example: +// vnf = cube(10,center=true); +// cutvnf = vnf_halfspace([-1,1,-1,0], vnf); +// vnf_polyhedron(cutvnf); +// Example: Cut face has 2 components +// vnf = path_sweep(circle(r=4, $fn=16), +// circle(r=20, $fn=64),closed=true); +// cutvnf = vnf_halfspace([-1,1,-4,0], vnf); +// vnf_polyhedron(cutvnf); +// Example: Cut face is not simply connected +// vnf = path_sweep(circle(r=4, $fn=16), +// circle(r=20, $fn=64),closed=true); +// cutvnf = vnf_halfspace([0,0.7,-4,0], vnf); +// vnf_polyhedron(cutvnf); +// Example: Cut object has multiple components +// function knot(a,b,t) = // rolling knot +// [ a * cos (3 * t) / (1 - b* sin (2 *t)), +// a * sin( 3 * t) / (1 - b* sin (2 *t)), +// 1.8 * b * cos (2 * t) /(1 - b* sin (2 *t))]; +// a = 0.8; b = sqrt (1 - a * a); +// ksteps = 400; +// knot_path = [for (i=[0:ksteps-1]) 50 * knot(a,b,(i/ksteps)*360)]; +// ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; +// knot=path_sweep(ushape, knot_path, closed=true, method="incremental"); +// cut_knot = vnf_halfspace([1,0,0,0], knot); +// vnf_polyhedron(cut_knot); +function vnf_halfspace(plane, vnf, closed=true) = + let( + inside = [for(x=vnf[0]) plane*[each x,-1] >= 0 ? 1 : 0], + vertexmap = [0,each cumsum(inside)], + faces_edges_vertices = _vnfcut(plane, vnf[0],vertexmap,inside, vnf[1], last(vertexmap)), + newvert = concat(bselect(vnf[0],inside), faces_edges_vertices[2]) + ) + closed==false ? [newvert, faces_edges_vertices[0]] : + let( + allpaths = _assemble_paths(newvert, faces_edges_vertices[1]), + newpaths = [for(p=allpaths) if (len(p)>=3) p + else assert(approx(p[0],p[1]),"Orphan edge found when assembling cut edges.") + ] + ) + len(newpaths)<=1 ? [newvert, concat(faces_edges_vertices[0], newpaths)] + : + let( + faceregion = project_plane(plane, newpaths), + facevnf = region_faces(faceregion,reverse=true) + ) + vnf_merge([[newvert, faces_edges_vertices[0]], lift_plane(plane, facevnf)]); + + +function _assemble_paths(vertices, edges, paths=[],i=0) = + i==len(edges) ? paths : + norm(vertices[edges[i][0]]-vertices[edges[i][1]])3 + ? _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount+1, + concat(newfaces, [list_head(newface)]), newedges,concat(newvertices,[newvert[0]]),i+1) + : + _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount,newfaces, newedges, newvert, i+1); + + + function _triangulate_planar_convex_polygons(polys) = polys==[]? [] : let( @@ -823,6 +950,8 @@ function _split_polygons_at_each_y(polys, ys, _i=0) = +// Section: Debugging VNFs + // Function&Module: vnf_validate() // Usage: As Function // fails = vnf_validate(vnf); @@ -1049,7 +1178,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) = ) if (!is_undef(isects)) for (isect = isects) - if (len(isect) > 1) let( + if (len(isect) > 1) isects2 = polygon_line_intersection(poly2, isect, bounded=true) ) if (!is_undef(isects2)) @@ -1150,114 +1279,5 @@ module vnf_validate(vnf, size=1, show_warns=true, check_isects=false) { } -// Section: VNF Transformations - -// Function: vnf_halfspace() -// Usage: -// newvnf = vnf_halfspace(plane, vnf, [closed]); -// Description: -// Returns the intersection of the vnf with a half space. The half space is defined by -// plane = [A,B,C,D], taking the side where the normal [A,B,C] points: Ax+By+Cz≥D. -// If closed is set to false then the cut face is not included in the vnf. This could -// allow further extension of the vnf by merging with other vnfs. -// Arguments: -// plane = plane defining the boundary of the half space -// vnf = vnf to cut -// closed = if false do not return include cut face(s). Default: true -// Example: -// vnf = cube(10,center=true); -// cutvnf = vnf_halfspace([-1,1,-1,0], vnf); -// vnf_polyhedron(cutvnf); -// Example: Cut face has 2 components -// vnf = path_sweep(circle(r=4, $fn=16), -// circle(r=20, $fn=64),closed=true); -// cutvnf = vnf_halfspace([-1,1,-4,0], vnf); -// vnf_polyhedron(cutvnf); -// Example: Cut face is not simply connected -// vnf = path_sweep(circle(r=4, $fn=16), -// circle(r=20, $fn=64),closed=true); -// cutvnf = vnf_halfspace([0,0.7,-4,0], vnf); -// vnf_polyhedron(cutvnf); -// Example: Cut object has multiple components -// function knot(a,b,t) = // rolling knot -// [ a * cos (3 * t) / (1 - b* sin (2 *t)), -// a * sin( 3 * t) / (1 - b* sin (2 *t)), -// 1.8 * b * cos (2 * t) /(1 - b* sin (2 *t))]; -// a = 0.8; b = sqrt (1 - a * a); -// ksteps = 400; -// knot_path = [for (i=[0:ksteps-1]) 50 * knot(a,b,(i/ksteps)*360)]; -// ushape = [[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[ 7, 2],[ 7, 7],[ 10, 7],[ 10, 0]]; -// knot=path_sweep(ushape, knot_path, closed=true, method="incremental"); -// cut_knot = vnf_halfspace([1,0,0,0], knot); -// vnf_polyhedron(cut_knot); -function vnf_halfspace(plane, vnf, closed=true) = - let( - inside = [for(x=vnf[0]) plane*[each x,-1] >= 0 ? 1 : 0], - vertexmap = [0,each cumsum(inside)], - faces_edges_vertices = _vnfcut(plane, vnf[0],vertexmap,inside, vnf[1], last(vertexmap)), - newvert = concat(bselect(vnf[0],inside), faces_edges_vertices[2]) - ) - closed==false ? [newvert, faces_edges_vertices[0]] : - let( - allpaths = _assemble_paths(newvert, faces_edges_vertices[1]), - newpaths = [for(p=allpaths) if (len(p)>=3) p - else assert(approx(p[0],p[1]),"Orphan edge found when assembling cut edges.") - ] - ) - len(newpaths)<=1 ? [newvert, concat(faces_edges_vertices[0], newpaths)] - : - let( - faceregion = project_plane(plane, newpaths), - facevnf = region_faces(faceregion,reverse=true) - ) - vnf_merge([[newvert, faces_edges_vertices[0]], lift_plane(plane, facevnf)]); - - -function _assemble_paths(vertices, edges, paths=[],i=0) = - i==len(edges) ? paths : - norm(vertices[edges[i][0]]-vertices[edges[i][1]])3 - ? _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount+1, - concat(newfaces, [list_head(newface)]), newedges,concat(newvertices,[newvert[0]]),i+1) - : - _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount,newfaces, newedges, newvert, i+1); - // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap