Resectioned distributors and vnf.

Error msg fix in shapes2d
section tweak in transforms
fixed polygon_line_intersection
This commit is contained in:
Adrian Mariano 2021-09-15 16:30:04 -04:00
parent 4f8ebb2e80
commit 33ca0d4a69
5 changed files with 618 additions and 488 deletions

View file

@ -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

View file

@ -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 <BOSL2/std.scad>
//////////////////////////////////////////////////////////////////////
@ -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,<r|d>,[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,<r|d>,[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

View file

@ -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<n/2)
)
assert(is_def(n), "Must specify number of points, n")
assert(count==1, "Must specify exactly one of ir, id, step")
assert(stepOK, n==4 ? "Parameter 'step' not allowed for 4 point stars"
: n==5 || n==6 ? str("Parameter 'step' must be 2 for ",n," point stars")

View file

@ -605,7 +605,7 @@ function zrot(a=0, p, cp) = rot(a, cp=cp, p=p);
//////////////////////////////////////////////////////////////////////
// Section: Scaling and Mirroring
// Section: Scaling
//////////////////////////////////////////////////////////////////////

522
vnf.scad
View file

@ -1,6 +1,9 @@
//////////////////////////////////////////////////////////////////////
// LibFile: vnf.scad
// VNF structures, holding Vertices 'N' Faces for use with `polyhedron().`
// The Vertices'N'Faces structure (VNF) holds the data used by polyhedron() to construct objects: a vertex
// list and a list of faces. This library makes it easier to construct polyhedra by providing
// functions to construct, merge, and modify VNF data, while avoiding common pitfalls such as
// reversed faces.
// Includes:
// include <BOSL2/std.scad>
//////////////////////////////////////////////////////////////////////
@ -8,15 +11,15 @@
include <triangulation.scad>
// 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<len(vnfs[i][0]),
str("VNF number ", i, " has a face indexing an nonexistent vertex") )
offs[i] + j ]
]
)
! cleanup ? [verts, faces] :
let(
dedup = vector_search(verts,eps,verts), // collect vertex duplicates
map = [for(i=idx(verts)) min(dedup[i]) ], // remap duplic vertices
offset = cumsum([for(i=idx(verts)) map[i]==i ? 0 : 1 ]), // remaping face vertex offsets
map2 = list(idx(verts))-offset, // map old vertex indices to new indices
nverts = [for(i=idx(verts)) if(map[i]==i) verts[i] ], // eliminates all unreferenced vertices
nfaces =
[ for(face=faces)
let(
nface = [ for(vi=face) map2[map[vi]] ],
dface = [for (i=idx(nface))
if( nface[i]!=nface[(i+1)%len(nface)])
nface[i] ]
)
if(len(dface) >= 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<len(vnfs[i][0]),
str("VNF number ", i, " has a face indexing an nonexistent vertex") )
offs[i] + j ]
]
)
! cleanup ? [verts, faces] :
let(
dedup = vector_search(verts,eps,verts), // collect vertex duplicates
map = [for(i=idx(verts)) min(dedup[i]) ], // remap duplic vertices
offset = cumsum([for(i=idx(verts)) map[i]==i ? 0 : 1 ]), // remaping face vertex offsets
map2 = list(idx(verts))-offset, // map old vertex indices to new indices
nverts = [for(i=idx(verts)) if(map[i]==i) verts[i] ], // eliminates all unreferenced vertices
nfaces =
[ for(face=faces)
let(
nface = [ for(vi=face) map2[map[vi]] ],
dface = [for (i=idx(nface))
if( nface[i]!=nface[(i+1)%len(nface)])
nface[i] ]
)
if(len(dface) >= 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, <r|d>);
@ -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+CzD.
// 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]])<EPSILON ? echo(degen=i)_assemble_paths(vertices,edges,paths,i+1) :
let( // Find paths that connects on left side and right side of the edges (if one exists)
left = [for(j=idx(paths)) if (approx(vertices[last(paths[j])],vertices[edges[i][0]])) j],
right = [for(j=idx(paths)) if (approx(vertices[edges[i][1]],vertices[paths[j][0]])) j]
)
assert(len(left)<=1 && len(right)<=1)
let(
keep_path = list_remove(paths,concat(left,right)),
update_path = left==[] && right==[] ? edges[i]
: left==[] ? concat([edges[i][0]],paths[right[0]])
: right==[] ? concat(paths[left[0]],[edges[i][1]])
: left != right ? concat(paths[left[0]], paths[right[0]])
: paths[left[0]]
)
_assemble_paths(vertices, edges, concat(keep_path, [update_path]), i+1);
function _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount, newfaces=[], newedges=[], newvertices=[], i=0) =
i==len(faces) ? [newfaces, newedges, newvertices] :
let(
pts_inside = select(inside,faces[i])
)
all(pts_inside) ? _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount,
concat(newfaces, [select(vertexmap,faces[i])]), newedges, newvertices, i+1):
!any(pts_inside) ? _vnfcut(plane, vertices, vertexmap,inside, faces, vertcount, newfaces, newedges, newvertices, i+1):
let(
first = search([[1,0]],pair(pts_inside,wrap=true),0)[0],
second = search([[0,1]],pair(pts_inside,wrap=true),0)[0]
)
assert(len(first)==1 && len(second)==1, "Found concave face in VNF. Run vnf_triangulate first to ensure convex faces.")
let(
newface = [each select(vertexmap,select(faces[i],second[0]+1,first[0])),vertcount, vertcount+1],
newvert = [plane_line_intersection(plane, select(vertices,select(faces[i],first[0],first[0]+1)),eps=0),
plane_line_intersection(plane, select(vertices,select(faces[i],second[0],second[0]+1)),eps=0)]
)
true //!approx(newvert[0],newvert[1])
? _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount+2,
concat(newfaces, [newface]), concat(newedges,[[vertcount+1,vertcount]]),concat(newvertices,newvert),i+1)
:len(newface)>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+CzD.
// 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]])<EPSILON ? echo(degen=i)_assemble_paths(vertices,edges,paths,i+1) :
let( // Find paths that connects on left side and right side of the edges (if one exists)
left = [for(j=idx(paths)) if (approx(vertices[last(paths[j])],vertices[edges[i][0]])) j],
right = [for(j=idx(paths)) if (approx(vertices[edges[i][1]],vertices[paths[j][0]])) j]
)
assert(len(left)<=1 && len(right)<=1)
let(
keep_path = list_remove(paths,concat(left,right)),
update_path = left==[] && right==[] ? edges[i]
: left==[] ? concat([edges[i][0]],paths[right[0]])
: right==[] ? concat(paths[left[0]],[edges[i][1]])
: left != right ? concat(paths[left[0]], paths[right[0]])
: paths[left[0]]
)
_assemble_paths(vertices, edges, concat(keep_path, [update_path]), i+1);
function _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount, newfaces=[], newedges=[], newvertices=[], i=0) =
i==len(faces) ? [newfaces, newedges, newvertices] :
let(
pts_inside = select(inside,faces[i])
)
all(pts_inside) ? _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount,
concat(newfaces, [select(vertexmap,faces[i])]), newedges, newvertices, i+1):
!any(pts_inside) ? _vnfcut(plane, vertices, vertexmap,inside, faces, vertcount, newfaces, newedges, newvertices, i+1):
let(
first = search([[1,0]],pair(pts_inside,wrap=true),0)[0],
second = search([[0,1]],pair(pts_inside,wrap=true),0)[0]
)
assert(len(first)==1 && len(second)==1, "Found concave face in VNF. Run vnf_triangulate first to ensure convex faces.")
let(
newface = [each select(vertexmap,select(faces[i],second[0]+1,first[0])),vertcount, vertcount+1],
newvert = [plane_line_intersection(plane, select(vertices,select(faces[i],first[0],first[0]+1)),eps=0),
plane_line_intersection(plane, select(vertices,select(faces[i],second[0],second[0]+1)),eps=0)]
)
true //!approx(newvert[0],newvert[1])
? _vnfcut(plane, vertices, vertexmap, inside, faces, vertcount+2,
concat(newfaces, [newface]), concat(newedges,[[vertcount+1,vertcount]]),concat(newvertices,newvert),i+1)
:len(newface)>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