mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 16:29:40 +00:00
Resectioned distributors and vnf.
Error msg fix in shapes2d section tweak in transforms fixed polygon_line_intersection
This commit is contained in:
parent
4f8ebb2e80
commit
33ca0d4a69
5 changed files with 618 additions and 488 deletions
|
@ -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
|
||||
|
|
210
geometry.scad
210
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 <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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
522
vnf.scad
|
@ -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+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]])<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+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]])<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
|
||||
|
|
Loading…
Reference in a new issue