mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2025-01-07 12:49:46 +00:00
Merge pull request #649 from adrianVmariano/master
polygon line intersection & triangulation
This commit is contained in:
commit
4935849ae3
17 changed files with 1934 additions and 1833 deletions
16
affine.scad
16
affine.scad
|
@ -142,6 +142,9 @@ function affine3d_to_2d(m) =
|
||||||
// Applies the specified transformation matrix to a point, pointlist, bezier patch or VNF.
|
// Applies the specified transformation matrix to a point, pointlist, bezier patch or VNF.
|
||||||
// Both inputs can be 2D or 3D, and it is also allowed to supply 3D transformations with 2D
|
// Both inputs can be 2D or 3D, and it is also allowed to supply 3D transformations with 2D
|
||||||
// data as long as the the only action on the z coordinate is a simple scaling.
|
// data as long as the the only action on the z coordinate is a simple scaling.
|
||||||
|
// .
|
||||||
|
// If you construct your own matrices you can also use a transform that acts like a projection
|
||||||
|
// with fewer rows to produce lower dimensional output.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// transform = The 2D or 3D transformation matrix to apply to the point/points.
|
// transform = The 2D or 3D transformation matrix to apply to the point/points.
|
||||||
// points = The point, pointlist, bezier patch, or VNF to apply the transformation to.
|
// points = The point, pointlist, bezier patch, or VNF to apply the transformation to.
|
||||||
|
@ -173,14 +176,15 @@ function apply(transform,points) =
|
||||||
? /* BezPatch */ [for (x=points) apply(transform,x)] :
|
? /* BezPatch */ [for (x=points) apply(transform,x)] :
|
||||||
let(
|
let(
|
||||||
tdim = len(transform[0])-1,
|
tdim = len(transform[0])-1,
|
||||||
datadim = len(points[0])
|
datadim = len(points[0]),
|
||||||
|
outdim = min(datadim,len(transform)),
|
||||||
|
matrix = [for(i=[0:1:tdim]) [for(j=[0:1:outdim-1]) transform[j][i]]]
|
||||||
)
|
)
|
||||||
tdim == 3 && datadim == 3 ? [for(p=points) point3d(transform*concat(p,[1]))] :
|
tdim==datadim && (datadim==3 || datadim==2) ? [for(p=points) concat(p,1)] * matrix
|
||||||
tdim == 2 && datadim == 2 ? [for(p=points) point2d(transform*concat(p,[1]))] :
|
: tdim == 3 && datadim == 2 ?
|
||||||
tdim == 3 && datadim == 2 ?
|
|
||||||
assert(is_2d_transform(transform), str("Transforms is 3d but points are 2d"))
|
assert(is_2d_transform(transform), str("Transforms is 3d but points are 2d"))
|
||||||
[for(p=points) point2d(transform*concat(p,[0,1]))] :
|
[for(p=points) concat(p,[0,1])]*matrix
|
||||||
assert(false, str("Unsupported combination: transform with dimension ",tdim,", data of dimension ",datadim));
|
: assert(false, str("Unsupported combination: transform with dimension ",tdim,", data of dimension ",datadim));
|
||||||
|
|
||||||
|
|
||||||
// Function: rot_decode()
|
// Function: rot_decode()
|
||||||
|
|
|
@ -217,7 +217,7 @@ function xy_to_polar(x,y=undef) = let(
|
||||||
// stroke(xypath,closed=true);
|
// stroke(xypath,closed=true);
|
||||||
function project_plane(plane,p) =
|
function project_plane(plane,p) =
|
||||||
is_matrix(plane,3,3) && is_undef(p) ? // no data, 3 points given
|
is_matrix(plane,3,3) && is_undef(p) ? // no data, 3 points given
|
||||||
assert(!collinear(plane),"Points defining the plane must not be collinear")
|
assert(!is_collinear(plane),"Points defining the plane must not be collinear")
|
||||||
let(
|
let(
|
||||||
v = plane[2]-plane[0],
|
v = plane[2]-plane[0],
|
||||||
y = unit(plane[1]-plane[0]), // y axis goes to point b
|
y = unit(plane[1]-plane[0]), // y axis goes to point b
|
||||||
|
@ -242,7 +242,7 @@ function project_plane(plane,p) =
|
||||||
[for(plist=p) project_plane(plane,plist)]
|
[for(plist=p) project_plane(plane,plist)]
|
||||||
: assert(is_vector(p,3) || is_path(p,3),str("Data must be a 3d point, path, region, vnf or bezier patch",p))
|
: assert(is_vector(p,3) || is_path(p,3),str("Data must be a 3d point, path, region, vnf or bezier patch",p))
|
||||||
is_matrix(plane,3,3) ?
|
is_matrix(plane,3,3) ?
|
||||||
assert(!collinear(plane),"Points defining the plane must not be collinear")
|
assert(!is_collinear(plane),"Points defining the plane must not be collinear")
|
||||||
let(
|
let(
|
||||||
v = plane[2]-plane[0],
|
v = plane[2]-plane[0],
|
||||||
y = unit(plane[1]-plane[0]), // y axis goes to point b
|
y = unit(plane[1]-plane[0]), // y axis goes to point b
|
||||||
|
|
|
@ -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()
|
// 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()
|
// 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();
|
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
|
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
|
||||||
|
|
934
drawing.scad
Normal file
934
drawing.scad
Normal file
|
@ -0,0 +1,934 @@
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
// LibFile: drawing.scad
|
||||||
|
// This file includes stroke(), which converts a path into a
|
||||||
|
// geometric object, like drawing with a pen. It even works on
|
||||||
|
// three-dimensional paths. You can make a dashed line or add arrow
|
||||||
|
// heads. The turtle() function provides a turtle graphics style
|
||||||
|
// approach for producing paths. The arc() function produces arc paths,
|
||||||
|
// and helix() produces helix paths.
|
||||||
|
// Includes:
|
||||||
|
// include <BOSL2/std.scad>
|
||||||
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
// Section: Line Drawing
|
||||||
|
|
||||||
|
// Module: stroke()
|
||||||
|
// Usage:
|
||||||
|
// stroke(path, [width], [closed], [endcaps], [endcap_width], [endcap_length], [endcap_extent], [trim]);
|
||||||
|
// stroke(path, [width], [closed], [endcap1], [endcap2], [endcap_width1], [endcap_width2], [endcap_length1], [endcap_length2], [endcap_extent1], [endcap_extent2], [trim1], [trim2]);
|
||||||
|
// Topics: Paths (2D), Paths (3D), Drawing Tools
|
||||||
|
// Description:
|
||||||
|
// Draws a 2D or 3D path with a given line width. Endcaps can be specified for each end individually.
|
||||||
|
// Figure(Med,NoAxes,2D,VPR=[0,0,0],VPD=250): Endcap Types
|
||||||
|
// cap_pairs = [
|
||||||
|
// ["butt", "chisel" ],
|
||||||
|
// ["round", "square" ],
|
||||||
|
// ["line", "cross" ],
|
||||||
|
// ["x", "diamond"],
|
||||||
|
// ["dot", "block" ],
|
||||||
|
// ["tail", "arrow" ],
|
||||||
|
// ["tail2", "arrow2" ]
|
||||||
|
// ];
|
||||||
|
// for (i = idx(cap_pairs)) {
|
||||||
|
// fwd((i-len(cap_pairs)/2+0.5)*13) {
|
||||||
|
// stroke([[-20,0], [20,0]], width=3, endcap1=cap_pairs[i][0], endcap2=cap_pairs[i][1]);
|
||||||
|
// color("black") {
|
||||||
|
// stroke([[-20,0], [20,0]], width=0.25, endcaps=false);
|
||||||
|
// left(28) text(text=cap_pairs[i][0], size=5, halign="right", valign="center");
|
||||||
|
// right(28) text(text=cap_pairs[i][1], size=5, halign="left", valign="center");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// Arguments:
|
||||||
|
// path = The path to draw along.
|
||||||
|
// width = The width of the line to draw. If given as a list of widths, (one for each path point), draws the line with varying thickness to each point.
|
||||||
|
// closed = If true, draw an additional line from the end of the path to the start.
|
||||||
|
// plots = Specifies the plot point shape for every point of the line. If a 2D path is given, use that to draw custom plot points.
|
||||||
|
// joints = Specifies the joint shape for each joint of the line. If a 2D path is given, use that to draw custom joints.
|
||||||
|
// endcaps = Specifies the endcap type for both ends of the line. If a 2D path is given, use that to draw custom endcaps.
|
||||||
|
// endcap1 = Specifies the endcap type for the start of the line. If a 2D path is given, use that to draw a custom endcap.
|
||||||
|
// endcap2 = Specifies the endcap type for the end of the line. If a 2D path is given, use that to draw a custom endcap.
|
||||||
|
// plot_width = Some plot point shapes are wider than the line. This specifies the width of the shape, in multiples of the line width.
|
||||||
|
// joint_width = Some joint shapes are wider than the line. This specifies the width of the shape, in multiples of the line width.
|
||||||
|
// endcap_width = Some endcap types are wider than the line. This specifies the size of endcaps, in multiples of the line width.
|
||||||
|
// endcap_width1 = This specifies the size of starting endcap, in multiples of the line width.
|
||||||
|
// endcap_width2 = This specifies the size of ending endcap, in multiples of the line width.
|
||||||
|
// plot_length = Length of plot point shape, in multiples of the line width.
|
||||||
|
// joint_length = Length of joint shape, in multiples of the line width.
|
||||||
|
// endcap_length = Length of endcaps, in multiples of the line width.
|
||||||
|
// endcap_length1 = Length of starting endcap, in multiples of the line width.
|
||||||
|
// endcap_length2 = Length of ending endcap, in multiples of the line width.
|
||||||
|
// plot_extent = Extents length of plot point shape, in multiples of the line width.
|
||||||
|
// joint_extent = Extents length of joint shape, in multiples of the line width.
|
||||||
|
// endcap_extent = Extents length of endcaps, in multiples of the line width.
|
||||||
|
// endcap_extent1 = Extents length of starting endcap, in multiples of the line width.
|
||||||
|
// endcap_extent2 = Extents length of ending endcap, in multiples of the line width.
|
||||||
|
// plot_angle = Extra rotation given to plot point shapes, in degrees. If not given, the shapes are fully spun.
|
||||||
|
// joint_angle = Extra rotation given to joint shapes, in degrees. If not given, the shapes are fully spun.
|
||||||
|
// endcap_angle = Extra rotation given to endcaps, in degrees. If not given, the endcaps are fully spun.
|
||||||
|
// endcap_angle1 = Extra rotation given to a starting endcap, in degrees. If not given, the endcap is fully spun.
|
||||||
|
// endcap_angle2 = Extra rotation given to a ending endcap, in degrees. If not given, the endcap is fully spun.
|
||||||
|
// trim = Trim the the start and end line segments by this much, to keep them from interfering with custom endcaps.
|
||||||
|
// trim1 = Trim the the starting line segment by this much, to keep it from interfering with a custom endcap.
|
||||||
|
// trim2 = Trim the the ending line segment by this much, to keep it from interfering with a custom endcap.
|
||||||
|
// convexity = Max number of times a line could intersect a wall of an endcap.
|
||||||
|
// hull = If true, use `hull()` to make higher quality joints between segments, at the cost of being much slower. Default: true
|
||||||
|
// Example(2D): Drawing a Path
|
||||||
|
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
|
||||||
|
// stroke(path, width=20);
|
||||||
|
// Example(2D): Closing a Path
|
||||||
|
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
|
||||||
|
// stroke(path, width=20, endcaps=true, closed=true);
|
||||||
|
// Example(2D): Fancy Arrow Endcaps
|
||||||
|
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
|
||||||
|
// stroke(path, width=10, endcaps="arrow2");
|
||||||
|
// Example(2D): Modified Fancy Arrow Endcaps
|
||||||
|
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
|
||||||
|
// stroke(path, width=10, endcaps="arrow2", endcap_width=6, endcap_length=3, endcap_extent=2);
|
||||||
|
// Example(2D): Mixed Endcaps
|
||||||
|
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
|
||||||
|
// stroke(path, width=10, endcap1="tail2", endcap2="arrow2");
|
||||||
|
// Example(2D): Plotting Points
|
||||||
|
// path = [for (a=[0:30:360]) [a-180, 60*sin(a)]];
|
||||||
|
// stroke(path, width=3, joints="diamond", endcaps="arrow2", plot_angle=0, plot_width=5);
|
||||||
|
// Example(2D): Joints and Endcaps
|
||||||
|
// path = [for (a=[0:30:360]) [a-180, 60*sin(a)]];
|
||||||
|
// stroke(path, width=3, joints="dot", endcaps="arrow2", joint_angle=0);
|
||||||
|
// Example(2D): Custom Endcap Shapes
|
||||||
|
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
|
||||||
|
// arrow = [[0,0], [2,-3], [0.5,-2.3], [2,-4], [0.5,-3.5], [-0.5,-3.5], [-2,-4], [-0.5,-2.3], [-2,-3]];
|
||||||
|
// stroke(path, width=10, trim=3.5, endcaps=arrow);
|
||||||
|
// Example(2D): Variable Line Width
|
||||||
|
// path = circle(d=50,$fn=18);
|
||||||
|
// widths = [for (i=idx(path)) 10*i/len(path)+2];
|
||||||
|
// stroke(path,width=widths,$fa=1,$fs=1);
|
||||||
|
// Example: 3D Path with Endcaps
|
||||||
|
// path = rot([15,30,0], p=path3d(pentagon(d=50)));
|
||||||
|
// stroke(path, width=2, endcaps="arrow2", $fn=18);
|
||||||
|
// Example: 3D Path with Flat Endcaps
|
||||||
|
// path = rot([15,30,0], p=path3d(pentagon(d=50)));
|
||||||
|
// stroke(path, width=2, endcaps="arrow2", endcap_angle=0, $fn=18);
|
||||||
|
// Example: 3D Path with Mixed Endcaps
|
||||||
|
// path = rot([15,30,0], p=path3d(pentagon(d=50)));
|
||||||
|
// stroke(path, width=2, endcap1="arrow2", endcap2="tail", endcap_angle2=0, $fn=18);
|
||||||
|
// Example: 3D Path with Joints and Endcaps
|
||||||
|
// path = [for (i=[0:10:360]) [(i-180)/2,20*cos(3*i),20*sin(3*i)]];
|
||||||
|
// stroke(path, width=2, joints="dot", endcap1="round", endcap2="arrow2", joint_width=2.0, endcap_width2=3, $fn=18);
|
||||||
|
function stroke(
|
||||||
|
path, width=1, closed=false,
|
||||||
|
endcaps, endcap1, endcap2, joints, plots,
|
||||||
|
endcap_width, endcap_width1, endcap_width2, joint_width, plot_width,
|
||||||
|
endcap_length, endcap_length1, endcap_length2, joint_length, plot_length,
|
||||||
|
endcap_extent, endcap_extent1, endcap_extent2, joint_extent, plot_extent,
|
||||||
|
endcap_angle, endcap_angle1, endcap_angle2, joint_angle, plot_angle,
|
||||||
|
trim, trim1, trim2,
|
||||||
|
convexity=10, hull=true
|
||||||
|
) = no_function("stroke");
|
||||||
|
module stroke(
|
||||||
|
path, width=1, closed=false,
|
||||||
|
endcaps, endcap1, endcap2, joints, plots,
|
||||||
|
endcap_width, endcap_width1, endcap_width2, joint_width, plot_width,
|
||||||
|
endcap_length, endcap_length1, endcap_length2, joint_length, plot_length,
|
||||||
|
endcap_extent, endcap_extent1, endcap_extent2, joint_extent, plot_extent,
|
||||||
|
endcap_angle, endcap_angle1, endcap_angle2, joint_angle, plot_angle,
|
||||||
|
trim, trim1, trim2,
|
||||||
|
convexity=10, hull=true
|
||||||
|
) {
|
||||||
|
function _shape_defaults(cap) =
|
||||||
|
cap==undef? [1.00, 0.00, 0.00] :
|
||||||
|
cap==false? [1.00, 0.00, 0.00] :
|
||||||
|
cap==true? [1.00, 1.00, 0.00] :
|
||||||
|
cap=="butt"? [1.00, 0.00, 0.00] :
|
||||||
|
cap=="round"? [1.00, 1.00, 0.00] :
|
||||||
|
cap=="chisel"? [1.00, 1.00, 0.00] :
|
||||||
|
cap=="square"? [1.00, 1.00, 0.00] :
|
||||||
|
cap=="block"? [3.00, 1.00, 0.00] :
|
||||||
|
cap=="diamond"? [3.50, 1.00, 0.00] :
|
||||||
|
cap=="dot"? [3.00, 1.00, 0.00] :
|
||||||
|
cap=="x"? [3.50, 0.40, 0.00] :
|
||||||
|
cap=="cross"? [4.50, 0.22, 0.00] :
|
||||||
|
cap=="line"? [4.50, 0.22, 0.00] :
|
||||||
|
cap=="arrow"? [3.50, 0.40, 0.50] :
|
||||||
|
cap=="arrow2"? [3.50, 1.00, 0.14] :
|
||||||
|
cap=="tail"? [3.50, 0.47, 0.50] :
|
||||||
|
cap=="tail2"? [3.50, 0.28, 0.50] :
|
||||||
|
is_path(cap)? [0.00, 0.00, 0.00] :
|
||||||
|
assert(false, str("Invalid cap or joint: ",cap));
|
||||||
|
|
||||||
|
function _shape_path(cap,linewidth,w,l,l2) = (
|
||||||
|
(cap=="butt" || cap==false || cap==undef)? [] :
|
||||||
|
(cap=="round" || cap==true)? scale([w,l], p=circle(d=1, $fn=max(8, segs(w/2)))) :
|
||||||
|
cap=="chisel"? scale([w,l], p=circle(d=1,$fn=4)) :
|
||||||
|
cap=="diamond"? circle(d=w,$fn=4) :
|
||||||
|
cap=="square"? scale([w,l], p=square(1,center=true)) :
|
||||||
|
cap=="block"? scale([w,l], p=square(1,center=true)) :
|
||||||
|
cap=="dot"? circle(d=w, $fn=max(12, segs(w*3/2))) :
|
||||||
|
cap=="x"? [for (a=[0:90:270]) each rot(a,p=[[w+l/2,w-l/2]/2, [w-l/2,w+l/2]/2, [0,l/2]]) ] :
|
||||||
|
cap=="cross"? [for (a=[0:90:270]) each rot(a,p=[[l,w]/2, [-l,w]/2, [-l,l]/2]) ] :
|
||||||
|
cap=="line"? scale([w,l], p=square(1,center=true)) :
|
||||||
|
cap=="arrow"? [[0,0], [w/2,-l2], [w/2,-l2-l], [0,-l], [-w/2,-l2-l], [-w/2,-l2]] :
|
||||||
|
cap=="arrow2"? [[0,0], [w/2,-l2-l], [0,-l], [-w/2,-l2-l]] :
|
||||||
|
cap=="tail"? [[0,0], [w/2,l2], [w/2,l2-l], [0,-l], [-w/2,l2-l], [-w/2,l2]] :
|
||||||
|
cap=="tail2"? [[w/2,0], [w/2,-l], [0,-l-l2], [-w/2,-l], [-w/2,0]] :
|
||||||
|
is_path(cap)? cap :
|
||||||
|
assert(false, str("Invalid endcap: ",cap))
|
||||||
|
) * linewidth;
|
||||||
|
|
||||||
|
assert(is_bool(closed));
|
||||||
|
assert(is_list(path));
|
||||||
|
if (len(path) > 1) {
|
||||||
|
assert(is_path(path,[2,3]), "The path argument must be a list of 2D or 3D points.");
|
||||||
|
}
|
||||||
|
path = deduplicate( closed? close_path(path) : path );
|
||||||
|
|
||||||
|
assert(is_num(width) || (is_vector(width) && len(width)==len(path)));
|
||||||
|
width = is_num(width)? [for (x=path) width] : width;
|
||||||
|
assert(all([for (w=width) w>0]));
|
||||||
|
|
||||||
|
endcap1 = first_defined([endcap1, endcaps, if(!closed) plots, "round"]);
|
||||||
|
endcap2 = first_defined([endcap2, endcaps, plots, "round"]);
|
||||||
|
joints = first_defined([joints, plots, "round"]);
|
||||||
|
assert(is_bool(endcap1) || is_string(endcap1) || is_path(endcap1));
|
||||||
|
assert(is_bool(endcap2) || is_string(endcap2) || is_path(endcap2));
|
||||||
|
assert(is_bool(joints) || is_string(joints) || is_path(joints));
|
||||||
|
|
||||||
|
endcap1_dflts = _shape_defaults(endcap1);
|
||||||
|
endcap2_dflts = _shape_defaults(endcap2);
|
||||||
|
joint_dflts = _shape_defaults(joints);
|
||||||
|
|
||||||
|
endcap_width1 = first_defined([endcap_width1, endcap_width, plot_width, endcap1_dflts[0]]);
|
||||||
|
endcap_width2 = first_defined([endcap_width2, endcap_width, plot_width, endcap2_dflts[0]]);
|
||||||
|
joint_width = first_defined([joint_width, plot_width, joint_dflts[0]]);
|
||||||
|
assert(is_num(endcap_width1));
|
||||||
|
assert(is_num(endcap_width2));
|
||||||
|
assert(is_num(joint_width));
|
||||||
|
|
||||||
|
endcap_length1 = first_defined([endcap_length1, endcap_length, plot_length, endcap1_dflts[1]*endcap_width1]);
|
||||||
|
endcap_length2 = first_defined([endcap_length2, endcap_length, plot_length, endcap2_dflts[1]*endcap_width2]);
|
||||||
|
joint_length = first_defined([joint_length, plot_length, joint_dflts[1]*joint_width]);
|
||||||
|
assert(is_num(endcap_length1));
|
||||||
|
assert(is_num(endcap_length2));
|
||||||
|
assert(is_num(joint_length));
|
||||||
|
|
||||||
|
endcap_extent1 = first_defined([endcap_extent1, endcap_extent, plot_extent, endcap1_dflts[2]*endcap_width1]);
|
||||||
|
endcap_extent2 = first_defined([endcap_extent2, endcap_extent, plot_extent, endcap2_dflts[2]*endcap_width2]);
|
||||||
|
joint_extent = first_defined([joint_extent, plot_extent, joint_dflts[2]*joint_width]);
|
||||||
|
assert(is_num(endcap_extent1));
|
||||||
|
assert(is_num(endcap_extent2));
|
||||||
|
assert(is_num(joint_extent));
|
||||||
|
|
||||||
|
endcap_angle1 = first_defined([endcap_angle1, endcap_angle, plot_angle]);
|
||||||
|
endcap_angle2 = first_defined([endcap_angle2, endcap_angle, plot_angle]);
|
||||||
|
joint_angle = first_defined([joint_angle, plot_angle]);
|
||||||
|
assert(is_undef(endcap_angle1)||is_num(endcap_angle1));
|
||||||
|
assert(is_undef(endcap_angle2)||is_num(endcap_angle2));
|
||||||
|
assert(is_undef(joint_angle)||is_num(joint_angle));
|
||||||
|
|
||||||
|
endcap_shape1 = _shape_path(endcap1, width[0], endcap_width1, endcap_length1, endcap_extent1);
|
||||||
|
endcap_shape2 = _shape_path(endcap2, last(width), endcap_width2, endcap_length2, endcap_extent2);
|
||||||
|
|
||||||
|
trim1 = width[0] * first_defined([
|
||||||
|
trim1, trim,
|
||||||
|
(endcap1=="arrow")? endcap_length1-0.01 :
|
||||||
|
(endcap1=="arrow2")? endcap_length1*3/4 :
|
||||||
|
0
|
||||||
|
]);
|
||||||
|
assert(is_num(trim1));
|
||||||
|
|
||||||
|
trim2 = last(width) * first_defined([
|
||||||
|
trim2, trim,
|
||||||
|
(endcap2=="arrow")? endcap_length2-0.01 :
|
||||||
|
(endcap2=="arrow2")? endcap_length2*3/4 :
|
||||||
|
0
|
||||||
|
]);
|
||||||
|
assert(is_num(trim2));
|
||||||
|
|
||||||
|
if (len(path) == 1) {
|
||||||
|
if (len(path[0]) == 2) {
|
||||||
|
translate(path[0]) circle(d=width[0]);
|
||||||
|
} else {
|
||||||
|
translate(path[0]) sphere(d=width[0]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
spos = path_pos_from_start(path,trim1,closed=false);
|
||||||
|
epos = path_pos_from_end(path,trim2,closed=false);
|
||||||
|
path2 = path_subselect(path, spos[0], spos[1], epos[0], epos[1]);
|
||||||
|
widths = concat(
|
||||||
|
[lerp(width[spos[0]], width[(spos[0]+1)%len(width)], spos[1])],
|
||||||
|
[for (i = [spos[0]+1:1:epos[0]]) width[i]],
|
||||||
|
[lerp(width[epos[0]], width[(epos[0]+1)%len(width)], epos[1])]
|
||||||
|
);
|
||||||
|
|
||||||
|
start_vec = path[0] - path[1];
|
||||||
|
end_vec = last(path) - select(path,-2);
|
||||||
|
|
||||||
|
if (len(path[0]) == 2) {
|
||||||
|
// Straight segments
|
||||||
|
for (i = idx(path2,e=-2)) {
|
||||||
|
seg = select(path2,i,i+1);
|
||||||
|
delt = seg[1] - seg[0];
|
||||||
|
translate(seg[0]) {
|
||||||
|
rot(from=BACK,to=delt) {
|
||||||
|
trapezoid(w1=widths[i], w2=widths[i+1], h=norm(delt), anchor=FRONT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Joints
|
||||||
|
for (i = [1:1:len(path2)-2]) {
|
||||||
|
$fn = quantup(segs(widths[i]/2),4);
|
||||||
|
translate(path2[i]) {
|
||||||
|
if (joints != undef) {
|
||||||
|
joint_shape = _shape_path(
|
||||||
|
joints, width[i],
|
||||||
|
joint_width,
|
||||||
|
joint_length,
|
||||||
|
joint_extent
|
||||||
|
);
|
||||||
|
v1 = unit(path2[i] - path2[i-1]);
|
||||||
|
v2 = unit(path2[i+1] - path2[i]);
|
||||||
|
vec = unit((v1+v2)/2);
|
||||||
|
mat = is_undef(joint_angle)
|
||||||
|
? rot(from=BACK,to=v1)
|
||||||
|
: zrot(joint_angle);
|
||||||
|
multmatrix(mat) polygon(joint_shape);
|
||||||
|
} else if (hull) {
|
||||||
|
hull() {
|
||||||
|
rot(from=BACK, to=path2[i]-path2[i-1])
|
||||||
|
circle(d=widths[i]);
|
||||||
|
rot(from=BACK, to=path2[i+1]-path2[i])
|
||||||
|
circle(d=widths[i]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rot(from=BACK, to=path2[i]-path2[i-1])
|
||||||
|
circle(d=widths[i]);
|
||||||
|
rot(from=BACK, to=path2[i+1]-path2[i])
|
||||||
|
circle(d=widths[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endcap1
|
||||||
|
translate(path[0]) {
|
||||||
|
mat = is_undef(endcap_angle1)? rot(from=BACK,to=start_vec) :
|
||||||
|
zrot(endcap_angle1);
|
||||||
|
multmatrix(mat) polygon(endcap_shape1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endcap2
|
||||||
|
translate(last(path)) {
|
||||||
|
mat = is_undef(endcap_angle2)? rot(from=BACK,to=end_vec) :
|
||||||
|
zrot(endcap_angle2);
|
||||||
|
multmatrix(mat) polygon(endcap_shape2);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
quatsums = q_cumulative([
|
||||||
|
for (i = idx(path2,e=-2)) let(
|
||||||
|
vec1 = i==0? UP : unit(path2[i]-path2[i-1], UP),
|
||||||
|
vec2 = unit(path2[i+1]-path2[i], UP),
|
||||||
|
axis = vector_axis(vec1,vec2),
|
||||||
|
ang = vector_angle(vec1,vec2)
|
||||||
|
) quat(axis,ang)
|
||||||
|
]);
|
||||||
|
rotmats = [for (q=quatsums) q_matrix4(q)];
|
||||||
|
sides = [
|
||||||
|
for (i = idx(path2,e=-2))
|
||||||
|
quantup(segs(max(widths[i],widths[i+1])/2),4)
|
||||||
|
];
|
||||||
|
|
||||||
|
// Straight segments
|
||||||
|
for (i = idx(path2,e=-2)) {
|
||||||
|
dist = norm(path2[i+1] - path2[i]);
|
||||||
|
w1 = widths[i]/2;
|
||||||
|
w2 = widths[i+1]/2;
|
||||||
|
$fn = sides[i];
|
||||||
|
translate(path2[i]) {
|
||||||
|
multmatrix(rotmats[i]) {
|
||||||
|
cylinder(r1=w1, r2=w2, h=dist, center=false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Joints
|
||||||
|
for (i = [1:1:len(path2)-2]) {
|
||||||
|
$fn = sides[i];
|
||||||
|
translate(path2[i]) {
|
||||||
|
if (joints != undef) {
|
||||||
|
joint_shape = _shape_path(
|
||||||
|
joints, width[i],
|
||||||
|
joint_width,
|
||||||
|
joint_length,
|
||||||
|
joint_extent
|
||||||
|
);
|
||||||
|
multmatrix(rotmats[i] * xrot(180)) {
|
||||||
|
$fn = sides[i];
|
||||||
|
if (is_undef(joint_angle)) {
|
||||||
|
rotate_extrude(convexity=convexity) {
|
||||||
|
right_half(planar=true) {
|
||||||
|
polygon(joint_shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rotate([90,0,joint_angle]) {
|
||||||
|
linear_extrude(height=max(widths[i],0.001), center=true, convexity=convexity) {
|
||||||
|
polygon(joint_shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (hull) {
|
||||||
|
hull(){
|
||||||
|
multmatrix(rotmats[i]) {
|
||||||
|
sphere(d=widths[i],style="aligned");
|
||||||
|
}
|
||||||
|
multmatrix(rotmats[i-1]) {
|
||||||
|
sphere(d=widths[i],style="aligned");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
multmatrix(rotmats[i]) {
|
||||||
|
sphere(d=widths[i],style="aligned");
|
||||||
|
}
|
||||||
|
multmatrix(rotmats[i-1]) {
|
||||||
|
sphere(d=widths[i],style="aligned");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endcap1
|
||||||
|
translate(path[0]) {
|
||||||
|
multmatrix(rotmats[0] * xrot(180)) {
|
||||||
|
$fn = sides[0];
|
||||||
|
if (is_undef(endcap_angle1)) {
|
||||||
|
rotate_extrude(convexity=convexity) {
|
||||||
|
right_half(planar=true) {
|
||||||
|
polygon(endcap_shape1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rotate([90,0,endcap_angle1]) {
|
||||||
|
linear_extrude(height=max(widths[0],0.001), center=true, convexity=convexity) {
|
||||||
|
polygon(endcap_shape1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endcap2
|
||||||
|
translate(last(path)) {
|
||||||
|
multmatrix(last(rotmats)) {
|
||||||
|
$fn = last(sides);
|
||||||
|
if (is_undef(endcap_angle2)) {
|
||||||
|
rotate_extrude(convexity=convexity) {
|
||||||
|
right_half(planar=true) {
|
||||||
|
polygon(endcap_shape2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
rotate([90,0,endcap_angle2]) {
|
||||||
|
linear_extrude(height=max(last(widths),0.001), center=true, convexity=convexity) {
|
||||||
|
polygon(endcap_shape2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Function&Module: dashed_stroke()
|
||||||
|
// Usage: As a Module
|
||||||
|
// dashed_stroke(path, dashpat, [closed=]);
|
||||||
|
// Usage: As a Function
|
||||||
|
// dashes = dashed_stroke(path, dashpat, width=, [closed=]);
|
||||||
|
// Topics: Paths, Drawing Tools
|
||||||
|
// See Also: stroke(), path_cut()
|
||||||
|
// Description:
|
||||||
|
// Given a path and a dash pattern, creates a dashed line that follows that
|
||||||
|
// path with the given dash pattern.
|
||||||
|
// - When called as a function, returns a list of dash sub-paths.
|
||||||
|
// - When called as a module, draws all those subpaths using `stroke()`.
|
||||||
|
// Arguments:
|
||||||
|
// path = The path to subdivide into dashes.
|
||||||
|
// dashpat = A list of alternating dash lengths and space lengths for the dash pattern. This will be scaled by the width of the line.
|
||||||
|
// ---
|
||||||
|
// width = The width of the dashed line to draw. Module only. Default: 1
|
||||||
|
// closed = If true, treat path as a closed polygon. Default: false
|
||||||
|
// Example(2D): Open Path
|
||||||
|
// path = [for (a=[-180:10:180]) [a/3,20*sin(a)]];
|
||||||
|
// dashed_stroke(path, [3,2], width=1);
|
||||||
|
// Example(2D): Closed Polygon
|
||||||
|
// path = circle(d=100,$fn=72);
|
||||||
|
// dashpat = [10,2,3,2,3,2];
|
||||||
|
// dashed_stroke(path, dashpat, width=1, closed=true);
|
||||||
|
// Example(FlatSpin,VPD=250): 3D Dashed Path
|
||||||
|
// path = [for (a=[-180:5:180]) [a/3, 20*cos(3*a), 20*sin(3*a)]];
|
||||||
|
// dashed_stroke(path, [3,2], width=1);
|
||||||
|
function dashed_stroke(path, dashpat=[3,3], closed=false) =
|
||||||
|
let(
|
||||||
|
path = closed? close_path(path) : path,
|
||||||
|
dashpat = len(dashpat)%2==0? dashpat : concat(dashpat,[0]),
|
||||||
|
plen = path_length(path),
|
||||||
|
dlen = sum(dashpat),
|
||||||
|
doff = cumsum(dashpat),
|
||||||
|
reps = floor(plen / dlen),
|
||||||
|
step = plen / reps,
|
||||||
|
cuts = [
|
||||||
|
for (i=[0:1:reps-1], off=doff)
|
||||||
|
let (st=i*step, x=st+off)
|
||||||
|
if (x>0 && x<plen) x
|
||||||
|
],
|
||||||
|
dashes = path_cut(path, cuts, closed=false),
|
||||||
|
evens = [for (i=idx(dashes)) if (i%2==0) dashes[i]]
|
||||||
|
) evens;
|
||||||
|
|
||||||
|
|
||||||
|
module dashed_stroke(path, dashpat=[3,3], width=1, closed=false) {
|
||||||
|
segs = dashed_stroke(path, dashpat=dashpat*width, closed=closed);
|
||||||
|
for (seg = segs)
|
||||||
|
stroke(seg, width=width, endcaps=false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Section: Computing paths
|
||||||
|
|
||||||
|
// Function&Module: arc()
|
||||||
|
// Usage: 2D arc from 0º to `angle` degrees.
|
||||||
|
// arc(N, r|d=, angle);
|
||||||
|
// Usage: 2D arc from START to END degrees.
|
||||||
|
// arc(N, r|d=, angle=[START,END])
|
||||||
|
// Usage: 2D arc from `start` to `start+angle` degrees.
|
||||||
|
// arc(N, r|d=, start=, angle=)
|
||||||
|
// Usage: 2D circle segment by `width` and `thickness`, starting and ending on the X axis.
|
||||||
|
// arc(N, width=, thickness=)
|
||||||
|
// Usage: Shortest 2D or 3D arc around centerpoint `cp`, starting at P0 and ending on the vector pointing from `cp` to `P1`.
|
||||||
|
// arc(N, cp=, points=[P0,P1], [long=], [cw=], [ccw=])
|
||||||
|
// Usage: 2D or 3D arc, starting at `P0`, passing through `P1` and ending at `P2`.
|
||||||
|
// arc(N, points=[P0,P1,P2])
|
||||||
|
// Topics: Paths (2D), Paths (3D), Shapes (2D), Path Generators
|
||||||
|
// Description:
|
||||||
|
// If called as a function, returns a 2D or 3D path forming an arc.
|
||||||
|
// If called as a module, creates a 2D arc polygon or pie slice shape.
|
||||||
|
// Arguments:
|
||||||
|
// N = Number of vertices to form the arc curve from.
|
||||||
|
// r = Radius of the arc.
|
||||||
|
// angle = If a scalar, specifies the end angle in degrees (relative to start parameter). If a vector of two scalars, specifies start and end angles.
|
||||||
|
// ---
|
||||||
|
// d = Diameter of the arc.
|
||||||
|
// cp = Centerpoint of arc.
|
||||||
|
// points = Points on the arc.
|
||||||
|
// long = if given with cp and points takes the long arc instead of the default short arc. Default: false
|
||||||
|
// cw = if given with cp and 2 points takes the arc in the clockwise direction. Default: false
|
||||||
|
// ccw = if given with cp and 2 points takes the arc in the counter-clockwise direction. Default: false
|
||||||
|
// width = If given with `thickness`, arc starts and ends on X axis, to make a circle segment.
|
||||||
|
// thickness = If given with `width`, arc starts and ends on X axis, to make a circle segment.
|
||||||
|
// start = Start angle of arc.
|
||||||
|
// wedge = If true, include centerpoint `cp` in output to form pie slice shape.
|
||||||
|
// endpoint = If false exclude the last point (function only). Default: true
|
||||||
|
// Examples(2D):
|
||||||
|
// arc(N=4, r=30, angle=30, wedge=true);
|
||||||
|
// arc(r=30, angle=30, wedge=true);
|
||||||
|
// arc(d=60, angle=30, wedge=true);
|
||||||
|
// arc(d=60, angle=120);
|
||||||
|
// arc(d=60, angle=120, wedge=true);
|
||||||
|
// arc(r=30, angle=[75,135], wedge=true);
|
||||||
|
// arc(r=30, start=45, angle=75, wedge=true);
|
||||||
|
// arc(width=60, thickness=20);
|
||||||
|
// arc(cp=[-10,5], points=[[20,10],[0,35]], wedge=true);
|
||||||
|
// arc(points=[[30,-5],[20,10],[-10,20]], wedge=true);
|
||||||
|
// arc(points=[[5,30],[-10,-10],[30,5]], wedge=true);
|
||||||
|
// Example(2D):
|
||||||
|
// path = arc(points=[[5,30],[-10,-10],[30,5]], wedge=true);
|
||||||
|
// stroke(closed=true, path);
|
||||||
|
// Example(FlatSpin,VPD=175):
|
||||||
|
// path = arc(points=[[0,30,0],[0,0,30],[30,0,0]]);
|
||||||
|
// trace_path(path, showpts=true, color="cyan");
|
||||||
|
function arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false, long=false, cw=false, ccw=false, endpoint=true) =
|
||||||
|
assert(is_bool(endpoint))
|
||||||
|
!endpoint ? assert(!wedge, "endpoint cannot be false if wedge is true")
|
||||||
|
list_head(arc(N+1,r,angle,d,cp,points,width,thickness,start,wedge,long,cw,ccw,true)) :
|
||||||
|
assert(is_undef(N) || (is_integer(N) && N>=2), "Number of points must be an integer 2 or larger")
|
||||||
|
// First try for 2D arc specified by width and thickness
|
||||||
|
is_def(width) && is_def(thickness)? (
|
||||||
|
assert(!any_defined([r,cp,points]) && !any([cw,ccw,long]),"Conflicting or invalid parameters to arc")
|
||||||
|
assert(width>0, "Width must be postive")
|
||||||
|
assert(thickness>0, "Thickness must be positive")
|
||||||
|
arc(N,points=[[width/2,0], [0,thickness], [-width/2,0]],wedge=wedge)
|
||||||
|
) : is_def(angle)? (
|
||||||
|
let(
|
||||||
|
parmok = !any_defined([points,width,thickness]) &&
|
||||||
|
((is_vector(angle,2) && is_undef(start)) || is_num(angle))
|
||||||
|
)
|
||||||
|
assert(parmok,"Invalid parameters in arc")
|
||||||
|
let(
|
||||||
|
cp = first_defined([cp,[0,0]]),
|
||||||
|
start = is_def(start)? start : is_vector(angle) ? angle[0] : 0,
|
||||||
|
angle = is_vector(angle)? angle[1]-angle[0] : angle,
|
||||||
|
r = get_radius(r=r, d=d)
|
||||||
|
)
|
||||||
|
assert(is_vector(cp,2),"Centerpoint must be a 2d vector")
|
||||||
|
assert(angle!=0, "Arc has zero length")
|
||||||
|
assert(is_def(r) && r>0, "Arc radius invalid")
|
||||||
|
let(
|
||||||
|
N = is_def(N) ? N : max(3, ceil(segs(r)*abs(angle)/360)),
|
||||||
|
arcpoints = [for(i=[0:N-1]) let(theta = start + i*angle/(N-1)) r*[cos(theta),sin(theta)]+cp],
|
||||||
|
extra = wedge? [cp] : []
|
||||||
|
)
|
||||||
|
concat(extra,arcpoints)
|
||||||
|
) :
|
||||||
|
assert(is_path(points,[2,3]),"Point list is invalid")
|
||||||
|
// Arc is 3D, so transform points to 2D and make a recursive call, then remap back to 3D
|
||||||
|
len(points[0])==3? (
|
||||||
|
assert(!(cw || ccw), "(Counter)clockwise isn't meaningful in 3d, so `cw` and `ccw` must be false")
|
||||||
|
assert(is_undef(cp) || is_vector(cp,3),"points are 3d so cp must be 3d")
|
||||||
|
let(
|
||||||
|
plane = [is_def(cp) ? cp : points[2], points[0], points[1]],
|
||||||
|
center2d = is_def(cp) ? project_plane(plane,cp) : undef,
|
||||||
|
points2d = project_plane(plane, points)
|
||||||
|
)
|
||||||
|
lift_plane(plane,arc(N,cp=center2d,points=points2d,wedge=wedge,long=long))
|
||||||
|
) : is_def(cp)? (
|
||||||
|
// Arc defined by center plus two points, will have radius defined by center and points[0]
|
||||||
|
// and extent defined by direction of point[1] from the center
|
||||||
|
assert(is_vector(cp,2), "Centerpoint must be a 2d vector")
|
||||||
|
assert(len(points)==2, "When pointlist has length 3 centerpoint is not allowed")
|
||||||
|
assert(points[0]!=points[1], "Arc endpoints are equal")
|
||||||
|
assert(cp!=points[0]&&cp!=points[1], "Centerpoint equals an arc endpoint")
|
||||||
|
assert(count_true([long,cw,ccw])<=1, str("Only one of `long`, `cw` and `ccw` can be true",cw,ccw,long))
|
||||||
|
let(
|
||||||
|
angle = vector_angle(points[0], cp, points[1]),
|
||||||
|
v1 = points[0]-cp,
|
||||||
|
v2 = points[1]-cp,
|
||||||
|
prelim_dir = sign(det2([v1,v2])), // z component of cross product
|
||||||
|
dir = prelim_dir != 0
|
||||||
|
? prelim_dir
|
||||||
|
: assert(cw || ccw, "Collinear inputs don't define a unique arc")
|
||||||
|
1,
|
||||||
|
r=norm(v1),
|
||||||
|
final_angle = long || (ccw && dir<0) || (cw && dir>0) ? -dir*(360-angle) : dir*angle
|
||||||
|
)
|
||||||
|
arc(N,cp=cp,r=r,start=atan2(v1.y,v1.x),angle=final_angle,wedge=wedge)
|
||||||
|
) : (
|
||||||
|
// Final case is arc passing through three points, starting at point[0] and ending at point[3]
|
||||||
|
let(col = is_collinear(points[0],points[1],points[2]))
|
||||||
|
assert(!col, "Collinear inputs do not define an arc")
|
||||||
|
let(
|
||||||
|
cp = line_intersection(_normal_segment(points[0],points[1]),_normal_segment(points[1],points[2])),
|
||||||
|
// select order to be counterclockwise
|
||||||
|
dir = det2([points[1]-points[0],points[2]-points[1]]) > 0,
|
||||||
|
points = dir? select(points,[0,2]) : select(points,[2,0]),
|
||||||
|
r = norm(points[0]-cp),
|
||||||
|
theta_start = atan2(points[0].y-cp.y, points[0].x-cp.x),
|
||||||
|
theta_end = atan2(points[1].y-cp.y, points[1].x-cp.x),
|
||||||
|
angle = posmod(theta_end-theta_start, 360),
|
||||||
|
arcpts = arc(N,cp=cp,r=r,start=theta_start,angle=angle,wedge=wedge)
|
||||||
|
)
|
||||||
|
dir ? arcpts : reverse(arcpts)
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
module arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false)
|
||||||
|
{
|
||||||
|
path = arc(N=N, r=r, angle=angle, d=d, cp=cp, points=points, width=width, thickness=thickness, start=start, wedge=wedge);
|
||||||
|
polygon(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Function: helix()
|
||||||
|
// Description:
|
||||||
|
// Returns a 3D helical path.
|
||||||
|
// Usage:
|
||||||
|
// helix(turns, h, n, r|d, [cp], [scale]);
|
||||||
|
// Arguments:
|
||||||
|
// h = Height of spiral.
|
||||||
|
// turns = Number of turns in spiral.
|
||||||
|
// n = Number of spiral sides.
|
||||||
|
// r = Radius of spiral.
|
||||||
|
// d = Radius of spiral.
|
||||||
|
// cp = Centerpoint of spiral. Default: `[0,0]`
|
||||||
|
// scale = [X,Y] scaling factors for each axis. Default: `[1,1]`
|
||||||
|
// Example(3D):
|
||||||
|
// trace_path(helix(turns=2.5, h=100, n=24, r=50), N=1, showpts=true);
|
||||||
|
function helix(turns=3, h=100, n=12, r, d, cp=[0,0], scale=[1,1]) = let(
|
||||||
|
rr=get_radius(r=r, d=d, dflt=100),
|
||||||
|
cnt=floor(turns*n),
|
||||||
|
dz=h/cnt
|
||||||
|
) [
|
||||||
|
for (i=[0:1:cnt]) [
|
||||||
|
rr * cos(i*360/n) * scale.x + cp.x,
|
||||||
|
rr * sin(i*360/n) * scale.y + cp.y,
|
||||||
|
i*dz
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function _normal_segment(p1,p2) =
|
||||||
|
let(center = (p1+p2)/2)
|
||||||
|
[center, center + norm(p1-p2)/2 * line_normal(p1,p2)];
|
||||||
|
|
||||||
|
|
||||||
|
// Function: turtle()
|
||||||
|
// Usage:
|
||||||
|
// turtle(commands, [state], [full_state=], [repeat=])
|
||||||
|
// Topics: Shapes (2D), Path Generators (2D), Mini-Language
|
||||||
|
// See Also: turtle3d()
|
||||||
|
// Description:
|
||||||
|
// Use a sequence of turtle graphics commands to generate a path. The parameter `commands` is a list of
|
||||||
|
// turtle commands and optional parameters for each command. The turtle state has a position, movement direction,
|
||||||
|
// movement distance, and default turn angle. If you do not give `state` as input then the turtle starts at the
|
||||||
|
// origin, pointed along the positive x axis with a movement distance of 1. By default, `turtle` returns just
|
||||||
|
// the computed turtle path. If you set `full_state` to true then it instead returns the full turtle state.
|
||||||
|
// You can invoke `turtle` again with this full state to continue the turtle path where you left off.
|
||||||
|
// .
|
||||||
|
// The turtle state is a list with three entries: the path constructed so far, the current step as a 2-vector, the current default angle,
|
||||||
|
// and the current arcsteps setting.
|
||||||
|
// .
|
||||||
|
// Commands | Arguments | What it does
|
||||||
|
// ------------ | ------------------ | -------------------------------
|
||||||
|
// "move" | [dist] | Move turtle scale*dist units in the turtle direction. Default dist=1.
|
||||||
|
// "xmove" | [dist] | Move turtle scale*dist units in the x direction. Default dist=1. Does not change turtle direction.
|
||||||
|
// "ymove" | [dist] | Move turtle scale*dist units in the y direction. Default dist=1. Does not change turtle direction.
|
||||||
|
// "xymove" | vector | Move turtle by the specified vector. Does not change turtle direction.
|
||||||
|
// "untilx" | xtarget | Move turtle in turtle direction until x==xtarget. Produces an error if xtarget is not reachable.
|
||||||
|
// "untily" | ytarget | Move turtle in turtle direction until y==ytarget. Produces an error if xtarget is not reachable.
|
||||||
|
// "jump" | point | Move the turtle to the specified point
|
||||||
|
// "xjump" | x | Move the turtle's x position to the specified value
|
||||||
|
// "yjump | y | Move the turtle's y position to the specified value
|
||||||
|
// "turn" | [angle] | Turn turtle direction by specified angle, or the turtle's default turn angle. The default angle starts at 90.
|
||||||
|
// "left" | [angle] | Same as "turn"
|
||||||
|
// "right" | [angle] | Same as "turn", -angle
|
||||||
|
// "angle" | angle | Set the default turn angle.
|
||||||
|
// "setdir" | dir | Set turtle direction. The parameter `dir` can be an angle or a vector.
|
||||||
|
// "length" | length | Change the turtle move distance to `length`
|
||||||
|
// "scale" | factor | Multiply turtle move distance by `factor`
|
||||||
|
// "addlength" | length | Add `length` to the turtle move distance
|
||||||
|
// "repeat" | count, commands | Repeats a list of commands `count` times.
|
||||||
|
// "arcleft" | radius, [angle] | Draw an arc from the current position toward the left at the specified radius and angle. The turtle turns by `angle`. A negative angle draws the arc to the right instead of the left, and leaves the turtle facing right. A negative radius draws the arc to the right but leaves the turtle facing left.
|
||||||
|
// "arcright" | radius, [angle] | Draw an arc from the current position toward the right at the specified radius and angle
|
||||||
|
// "arcleftto" | radius, angle | Draw an arc at the given radius turning toward the left until reaching the specified absolute angle.
|
||||||
|
// "arcrightto" | radius, angle | Draw an arc at the given radius turning toward the right until reaching the specified absolute angle.
|
||||||
|
// "arcsteps" | count | Specifies the number of segments to use for drawing arcs. If you set it to zero then the standard `$fn`, `$fa` and `$fs` variables define the number of segments.
|
||||||
|
//
|
||||||
|
// Arguments:
|
||||||
|
// commands = List of turtle commands
|
||||||
|
// state = Starting turtle state (from previous call) or starting point. Default: start at the origin, pointing right.
|
||||||
|
// ---
|
||||||
|
// full_state = If true return the full turtle state for continuing the path in subsequent turtle calls. Default: false
|
||||||
|
// repeat = Number of times to repeat the command list. Default: 1
|
||||||
|
//
|
||||||
|
// Example(2D): Simple rectangle
|
||||||
|
// path = turtle(["xmove",3, "ymove", "xmove",-3, "ymove",-1]);
|
||||||
|
// stroke(path,width=.1);
|
||||||
|
// Example(2D): Pentagon
|
||||||
|
// path=turtle(["angle",360/5,"move","turn","move","turn","move","turn","move"]);
|
||||||
|
// stroke(path,width=.1,closed=true);
|
||||||
|
// Example(2D): Pentagon using the repeat argument
|
||||||
|
// path=turtle(["move","turn",360/5],repeat=5);
|
||||||
|
// stroke(path,width=.1,closed=true);
|
||||||
|
// Example(2D): Pentagon using the repeat turtle command, setting the turn angle
|
||||||
|
// path=turtle(["angle",360/5,"repeat",5,["move","turn"]]);
|
||||||
|
// stroke(path,width=.1,closed=true);
|
||||||
|
// Example(2D): Pentagram
|
||||||
|
// path = turtle(["move","left",144], repeat=4);
|
||||||
|
// stroke(path,width=.05,closed=true);
|
||||||
|
// Example(2D): Sawtooth path
|
||||||
|
// path = turtle([
|
||||||
|
// "turn", 55,
|
||||||
|
// "untily", 2,
|
||||||
|
// "turn", -55-90,
|
||||||
|
// "untily", 0,
|
||||||
|
// "turn", 55+90,
|
||||||
|
// "untily", 2.5,
|
||||||
|
// "turn", -55-90,
|
||||||
|
// "untily", 0,
|
||||||
|
// "turn", 55+90,
|
||||||
|
// "untily", 3,
|
||||||
|
// "turn", -55-90,
|
||||||
|
// "untily", 0
|
||||||
|
// ]);
|
||||||
|
// stroke(path, width=.1);
|
||||||
|
// Example(2D): Simpler way to draw the sawtooth. The direction of the turtle is preserved when executing "yjump".
|
||||||
|
// path = turtle([
|
||||||
|
// "turn", 55,
|
||||||
|
// "untily", 2,
|
||||||
|
// "yjump", 0,
|
||||||
|
// "untily", 2.5,
|
||||||
|
// "yjump", 0,
|
||||||
|
// "untily", 3,
|
||||||
|
// "yjump", 0,
|
||||||
|
// ]);
|
||||||
|
// stroke(path, width=.1);
|
||||||
|
// Example(2DMed): square spiral
|
||||||
|
// path = turtle(["move","left","addlength",1],repeat=50);
|
||||||
|
// stroke(path,width=.2);
|
||||||
|
// Example(2DMed): pentagonal spiral
|
||||||
|
// path = turtle(["move","left",360/5,"addlength",1],repeat=50);
|
||||||
|
// stroke(path,width=.2);
|
||||||
|
// Example(2DMed): yet another spiral, without using `repeat`
|
||||||
|
// path = turtle(concat(["angle",71],flatten(repeat(["move","left","addlength",1],50))));
|
||||||
|
// stroke(path,width=.2);
|
||||||
|
// Example(2DMed): The previous spiral grows linearly and eventually intersects itself. This one grows geometrically and does not.
|
||||||
|
// path = turtle(["move","left",71,"scale",1.05],repeat=50);
|
||||||
|
// stroke(path,width=.05);
|
||||||
|
// Example(2D): Koch Snowflake
|
||||||
|
// function koch_unit(depth) =
|
||||||
|
// depth==0 ? ["move"] :
|
||||||
|
// concat(
|
||||||
|
// koch_unit(depth-1),
|
||||||
|
// ["right"],
|
||||||
|
// koch_unit(depth-1),
|
||||||
|
// ["left","left"],
|
||||||
|
// koch_unit(depth-1),
|
||||||
|
// ["right"],
|
||||||
|
// koch_unit(depth-1)
|
||||||
|
// );
|
||||||
|
// koch=concat(["angle",60,"repeat",3],[concat(koch_unit(3),["left","left"])]);
|
||||||
|
// polygon(turtle(koch));
|
||||||
|
module turtle(commands, state=[[[0,0]],[1,0],90,0], full_state=false, repeat=1) {no_module();}
|
||||||
|
function turtle(commands, state=[[[0,0]],[1,0],90,0], full_state=false, repeat=1) =
|
||||||
|
let( state = is_vector(state) ? [[state],[1,0],90,0] : state )
|
||||||
|
repeat == 1?
|
||||||
|
_turtle(commands,state,full_state) :
|
||||||
|
_turtle_repeat(commands, state, full_state, repeat);
|
||||||
|
|
||||||
|
function _turtle_repeat(commands, state, full_state, repeat) =
|
||||||
|
repeat==1?
|
||||||
|
_turtle(commands,state,full_state) :
|
||||||
|
_turtle_repeat(commands, _turtle(commands, state, true), full_state, repeat-1);
|
||||||
|
|
||||||
|
function _turtle_command_len(commands, index) =
|
||||||
|
let( one_or_two_arg = ["arcleft","arcright", "arcleftto", "arcrightto"] )
|
||||||
|
commands[index] == "repeat"? 3 : // Repeat command requires 2 args
|
||||||
|
// For these, the first arg is required, second arg is present if it is not a string
|
||||||
|
in_list(commands[index], one_or_two_arg) && len(commands)>index+2 && !is_string(commands[index+2]) ? 3 :
|
||||||
|
is_string(commands[index+1])? 1 : // If 2nd item is a string it's must be a new command
|
||||||
|
2; // Otherwise we have command and arg
|
||||||
|
|
||||||
|
function _turtle(commands, state, full_state, index=0) =
|
||||||
|
index < len(commands) ?
|
||||||
|
_turtle(commands,
|
||||||
|
_turtle_command(commands[index],commands[index+1],commands[index+2],state,index),
|
||||||
|
full_state,
|
||||||
|
index+_turtle_command_len(commands,index)
|
||||||
|
) :
|
||||||
|
( full_state ? state : state[0] );
|
||||||
|
|
||||||
|
// Turtle state: state = [path, step_vector, default angle, default arcsteps]
|
||||||
|
|
||||||
|
function _turtle_command(command, parm, parm2, state, index) =
|
||||||
|
command == "repeat"?
|
||||||
|
assert(is_num(parm),str("\"repeat\" command requires a numeric repeat count at index ",index))
|
||||||
|
assert(is_list(parm2),str("\"repeat\" command requires a command list parameter at index ",index))
|
||||||
|
_turtle_repeat(parm2, state, true, parm) :
|
||||||
|
let(
|
||||||
|
path = 0,
|
||||||
|
step=1,
|
||||||
|
angle=2,
|
||||||
|
arcsteps=3,
|
||||||
|
parm = !is_string(parm) ? parm : undef,
|
||||||
|
parm2 = !is_string(parm2) ? parm2 : undef,
|
||||||
|
needvec = ["jump", "xymove"],
|
||||||
|
neednum = ["untilx","untily","xjump","yjump","angle","length","scale","addlength"],
|
||||||
|
needeither = ["setdir"],
|
||||||
|
chvec = !in_list(command,needvec) || is_vector(parm,2),
|
||||||
|
chnum = !in_list(command,neednum) || is_num(parm),
|
||||||
|
vec_or_num = !in_list(command,needeither) || (is_num(parm) || is_vector(parm,2)),
|
||||||
|
lastpt = last(state[path])
|
||||||
|
)
|
||||||
|
assert(chvec,str("\"",command,"\" requires a vector parameter at index ",index))
|
||||||
|
assert(chnum,str("\"",command,"\" requires a numeric parameter at index ",index))
|
||||||
|
assert(vec_or_num,str("\"",command,"\" requires a vector or numeric parameter at index ",index))
|
||||||
|
|
||||||
|
command=="move" ? list_set(state, path, concat(state[path],[default(parm,1)*state[step]+lastpt])) :
|
||||||
|
command=="untilx" ? (
|
||||||
|
let(
|
||||||
|
int = line_intersection([lastpt,lastpt+state[step]], [[parm,0],[parm,1]]),
|
||||||
|
xgood = sign(state[step].x) == sign(int.x-lastpt.x)
|
||||||
|
)
|
||||||
|
assert(xgood,str("\"untilx\" never reaches desired goal at index ",index))
|
||||||
|
list_set(state,path,concat(state[path],[int]))
|
||||||
|
) :
|
||||||
|
command=="untily" ? (
|
||||||
|
let(
|
||||||
|
int = line_intersection([lastpt,lastpt+state[step]], [[0,parm],[1,parm]]),
|
||||||
|
ygood = is_def(int) && sign(state[step].y) == sign(int.y-lastpt.y)
|
||||||
|
)
|
||||||
|
assert(ygood,str("\"untily\" never reaches desired goal at index ",index))
|
||||||
|
list_set(state,path,concat(state[path],[int]))
|
||||||
|
) :
|
||||||
|
command=="xmove" ? list_set(state, path, concat(state[path],[default(parm,1)*norm(state[step])*[1,0]+lastpt])):
|
||||||
|
command=="ymove" ? list_set(state, path, concat(state[path],[default(parm,1)*norm(state[step])*[0,1]+lastpt])):
|
||||||
|
command=="xymove" ? list_set(state, path, concat(state[path], [lastpt+parm])):
|
||||||
|
command=="jump" ? list_set(state, path, concat(state[path],[parm])):
|
||||||
|
command=="xjump" ? list_set(state, path, concat(state[path],[[parm,lastpt.y]])):
|
||||||
|
command=="yjump" ? list_set(state, path, concat(state[path],[[lastpt.x,parm]])):
|
||||||
|
command=="turn" || command=="left" ? list_set(state, step, rot(default(parm,state[angle]),p=state[step],planar=true)) :
|
||||||
|
command=="right" ? list_set(state, step, rot(-default(parm,state[angle]),p=state[step],planar=true)) :
|
||||||
|
command=="angle" ? list_set(state, angle, parm) :
|
||||||
|
command=="setdir" ? (
|
||||||
|
is_vector(parm) ?
|
||||||
|
list_set(state, step, norm(state[step]) * unit(parm)) :
|
||||||
|
list_set(state, step, norm(state[step]) * [cos(parm),sin(parm)])
|
||||||
|
) :
|
||||||
|
command=="length" ? list_set(state, step, parm*unit(state[step])) :
|
||||||
|
command=="scale" ? list_set(state, step, parm*state[step]) :
|
||||||
|
command=="addlength" ? list_set(state, step, state[step]+unit(state[step])*parm) :
|
||||||
|
command=="arcsteps" ? list_set(state, arcsteps, parm) :
|
||||||
|
command=="arcleft" || command=="arcright" ?
|
||||||
|
assert(is_num(parm),str("\"",command,"\" command requires a numeric radius value at index ",index))
|
||||||
|
let(
|
||||||
|
myangle = default(parm2,state[angle]),
|
||||||
|
lrsign = command=="arcleft" ? 1 : -1,
|
||||||
|
radius = parm*sign(myangle),
|
||||||
|
center = lastpt + lrsign*radius*line_normal([0,0],state[step]),
|
||||||
|
steps = state[arcsteps]==0 ? segs(abs(radius)) : state[arcsteps],
|
||||||
|
arcpath = myangle == 0 || radius == 0 ? [] : arc(
|
||||||
|
steps,
|
||||||
|
points = [
|
||||||
|
lastpt,
|
||||||
|
rot(cp=center, p=lastpt, a=sign(parm)*lrsign*myangle/2),
|
||||||
|
rot(cp=center, p=lastpt, a=sign(parm)*lrsign*myangle)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
list_set(
|
||||||
|
state, [path,step], [
|
||||||
|
concat(state[path], list_tail(arcpath)),
|
||||||
|
rot(lrsign * myangle,p=state[step],planar=true)
|
||||||
|
]
|
||||||
|
) :
|
||||||
|
command=="arcleftto" || command=="arcrightto" ?
|
||||||
|
assert(is_num(parm),str("\"",command,"\" command requires a numeric radius value at index ",index))
|
||||||
|
assert(is_num(parm2),str("\"",command,"\" command requires a numeric angle value at index ",index))
|
||||||
|
let(
|
||||||
|
radius = parm,
|
||||||
|
lrsign = command=="arcleftto" ? 1 : -1,
|
||||||
|
center = lastpt + lrsign*radius*line_normal([0,0],state[step]),
|
||||||
|
steps = state[arcsteps]==0 ? segs(abs(radius)) : state[arcsteps],
|
||||||
|
start_angle = posmod(atan2(state[step].y, state[step].x),360),
|
||||||
|
end_angle = posmod(parm2,360),
|
||||||
|
delta_angle = -start_angle + (lrsign * end_angle < lrsign*start_angle ? end_angle+lrsign*360 : end_angle),
|
||||||
|
arcpath = delta_angle == 0 || radius==0 ? [] : arc(
|
||||||
|
steps,
|
||||||
|
points = [
|
||||||
|
lastpt,
|
||||||
|
rot(cp=center, p=lastpt, a=sign(radius)*delta_angle/2),
|
||||||
|
rot(cp=center, p=lastpt, a=sign(radius)*delta_angle)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
list_set(
|
||||||
|
state, [path,step], [
|
||||||
|
concat(state[path], list_tail(arcpath)),
|
||||||
|
rot(delta_angle,p=state[step],planar=true)
|
||||||
|
]
|
||||||
|
) :
|
||||||
|
assert(false,str("Unknown turtle command \"",command,"\" at index",index))
|
||||||
|
[];
|
||||||
|
|
480
geometry.scad
480
geometry.scad
|
@ -1,6 +1,9 @@
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
// LibFile: geometry.scad
|
// 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:
|
// Includes:
|
||||||
// include <BOSL2/std.scad>
|
// include <BOSL2/std.scad>
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
@ -8,20 +11,23 @@
|
||||||
|
|
||||||
// Section: Lines, Rays, and Segments
|
// Section: Lines, Rays, and Segments
|
||||||
|
|
||||||
// Function: point_on_segment()
|
// Function: is_point_on_line()
|
||||||
// Usage:
|
// Usage:
|
||||||
// pt = point_on_segment(point, edge);
|
// pt = is_point_on_line(point, line, [bounded], [eps]);
|
||||||
// Topics: Geometry, Points, Segments
|
// Topics: Geometry, Points, Segments
|
||||||
// Description:
|
// Description:
|
||||||
// Determine if the point is on the line segment between two points.
|
// Determine if the point is on the line segment, ray or segment defined by the two between two points.
|
||||||
// Returns true if yes, and false if not.
|
// Returns true if yes, and false if not. If bounded is set to true it specifies a segment, with
|
||||||
|
// both lines bounded at the ends. Set bounded to `[true,false]` to get a ray. You can use
|
||||||
|
// the shorthands RAY and SEGMENT to set bounded.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// point = The point to test.
|
// point = The point to test.
|
||||||
// edge = Array of two points forming the line segment to test against.
|
// line = Array of two points defining the line, ray, or segment to test against.
|
||||||
|
// bounded = boolean or list of two booleans defining endpoint conditions for the line. If false treat the line as an unbounded line. If true treat it as a segment. If [true,false] treat as a ray, based at the first endpoint. Default: false
|
||||||
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
|
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
|
||||||
function point_on_segment(point, edge, eps=EPSILON) =
|
function is_point_on_line(point, line, bounded=false, eps=EPSILON) =
|
||||||
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
|
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
|
||||||
point_line_distance(point, edge, SEGMENT)<eps;
|
point_line_distance(point, line, bounded)<eps;
|
||||||
|
|
||||||
|
|
||||||
//Internal - distance from point `d` to the line passing through the origin with unit direction n
|
//Internal - distance from point `d` to the line passing through the origin with unit direction n
|
||||||
|
@ -60,9 +66,9 @@ function _point_left_of_line2d(point, line) =
|
||||||
cross(line[0]-point, line[1]-line[0]);
|
cross(line[0]-point, line[1]-line[0]);
|
||||||
|
|
||||||
|
|
||||||
// Function: collinear()
|
// Function: is_collinear()
|
||||||
// Usage:
|
// Usage:
|
||||||
// test = collinear(a, [b, c], [eps]);
|
// test = is_collinear(a, [b, c], [eps]);
|
||||||
// Topics: Geometry, Points, Collinearity
|
// Topics: Geometry, Points, Collinearity
|
||||||
// Description:
|
// Description:
|
||||||
// Returns true if the points `a`, `b` and `c` are co-linear or if the list of points `a` is collinear.
|
// Returns true if the points `a`, `b` and `c` are co-linear or if the list of points `a` is collinear.
|
||||||
|
@ -71,7 +77,7 @@ function _point_left_of_line2d(point, line) =
|
||||||
// b = Second point or undef; it should be undef if `c` is undef
|
// b = Second point or undef; it should be undef if `c` is undef
|
||||||
// c = Third point or undef.
|
// c = Third point or undef.
|
||||||
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
|
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
|
||||||
function collinear(a, b, c, eps=EPSILON) =
|
function is_collinear(a, b, c, eps=EPSILON) =
|
||||||
assert( is_path([a,b,c],dim=undef)
|
assert( is_path([a,b,c],dim=undef)
|
||||||
|| ( is_undef(b) && is_undef(c) && is_path(a,dim=undef) ),
|
|| ( is_undef(b) && is_undef(c) && is_path(a,dim=undef) ),
|
||||||
"Input should be 3 points or a list of points with same dimension.")
|
"Input should be 3 points or a list of points with same dimension.")
|
||||||
|
@ -330,7 +336,7 @@ function line_from_points(points, fast=false, eps=EPSILON) =
|
||||||
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
|
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
|
||||||
let( pb = furthest_point(points[0],points) )
|
let( pb = furthest_point(points[0],points) )
|
||||||
norm(points[pb]-points[0])<eps*max(norm(points[pb]),norm(points[0])) ? undef :
|
norm(points[pb]-points[0])<eps*max(norm(points[pb]),norm(points[0])) ? undef :
|
||||||
fast || collinear(points)
|
fast || is_collinear(points)
|
||||||
? [points[pb], points[0]]
|
? [points[pb], points[0]]
|
||||||
: undef;
|
: undef;
|
||||||
|
|
||||||
|
@ -339,16 +345,16 @@ function line_from_points(points, fast=false, eps=EPSILON) =
|
||||||
// Section: Planes
|
// Section: Planes
|
||||||
|
|
||||||
|
|
||||||
// Function: coplanar()
|
// Function: is_coplanar()
|
||||||
// Usage:
|
// Usage:
|
||||||
// test = coplanar(points,[eps]);
|
// test = is_coplanar(points,[eps]);
|
||||||
// Topics: Geometry, Coplanarity
|
// Topics: Geometry, Coplanarity
|
||||||
// Description:
|
// Description:
|
||||||
// Returns true if the given 3D points are non-collinear and are on a plane.
|
// Returns true if the given 3D points are non-collinear and are on a plane.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// points = The points to test.
|
// points = The points to test.
|
||||||
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
|
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
|
||||||
function coplanar(points, eps=EPSILON) =
|
function is_coplanar(points, eps=EPSILON) =
|
||||||
assert( is_path(points,dim=3) , "Input should be a list of 3D points." )
|
assert( is_path(points,dim=3) , "Input should be a list of 3D points." )
|
||||||
assert( is_finite(eps) && eps>=0, "The tolerance should be a non-negative value." )
|
assert( is_finite(eps) && eps>=0, "The tolerance should be a non-negative value." )
|
||||||
len(points)<=2 ? false
|
len(points)<=2 ? false
|
||||||
|
@ -533,7 +539,7 @@ function plane_from_polygon(poly, fast=false, eps=EPSILON) =
|
||||||
let(
|
let(
|
||||||
plane = plane_from_normal(poly_normal, poly[0])
|
plane = plane_from_normal(poly_normal, poly[0])
|
||||||
)
|
)
|
||||||
fast? plane: points_on_plane(poly, plane, eps=eps)? plane: [];
|
fast? plane: are_points_on_plane(poly, plane, eps=eps)? plane: [];
|
||||||
|
|
||||||
|
|
||||||
// Function: plane_normal()
|
// Function: plane_normal()
|
||||||
|
@ -621,59 +627,183 @@ function plane_line_intersection(plane, line, bounded=false, eps=EPSILON) =
|
||||||
|
|
||||||
// Function: polygon_line_intersection()
|
// Function: polygon_line_intersection()
|
||||||
// Usage:
|
// Usage:
|
||||||
// pt = polygon_line_intersection(poly, line, [bounded], [eps]);
|
// pt = polygon_line_intersection(poly, line, [bounded], [nonzero], [eps]);
|
||||||
// Topics: Geometry, Polygons, Lines, Intersection
|
// Topics: Geometry, Polygons, Lines, Intersection
|
||||||
// Description:
|
// Description:
|
||||||
// Takes a possibly bounded line, and a 3D planar polygon, and finds their intersection point.
|
// Takes a possibly bounded line, and a 2D or 3D planar polygon, and finds their intersection.
|
||||||
// If the line and the polygon are on the same plane then returns a list, possibly empty, of 3D line
|
// If the line does not intersect the polygon then `undef` returns `undef`.
|
||||||
// segments, one for each section of the line that is inside the polygon.
|
// In 3D if the line is not on the plane of the polygon but intersects it then you get a single intersection point.
|
||||||
// If the line is not on the plane of the polygon, but intersects it, then returns the 3D intersection
|
// Otherwise the polygon and line are in the same plane, or when your input is 2D, ou will get a list of segments and
|
||||||
// point. If the line does not intersect the polygon, then `undef` is returned.
|
// 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:
|
// Arguments:
|
||||||
// poly = The 3D planar polygon to find the intersection with.
|
// poly = The 3D planar polygon to find the intersection with.
|
||||||
// line = A list of two distinct 3D points on the line.
|
// 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)
|
// 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)
|
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
|
||||||
function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) =
|
// 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_finite(eps) && eps>=0, "The tolerance should be a positive number." )
|
||||||
assert(is_path(poly,dim=3), "Invalid polygon." )
|
assert(is_path(poly,dim=[2,3]), "Invalid polygon." )
|
||||||
assert(is_bool(bounded) || is_bool_list(bounded,2), "Invalid bound condition.")
|
assert(is_bool(bounded) || is_bool_list(bounded,2), "Invalid bound condition.")
|
||||||
assert(_valid_line(line,dim=3,eps=eps), "Invalid 3D line." )
|
assert(_valid_line(line,dim=len(poly[0]),eps=eps), "Line invalid or does not match polygon dimension." )
|
||||||
let(
|
let(
|
||||||
bounded = force_list(bounded,2),
|
bounded = force_list(bounded,2),
|
||||||
poly = deduplicate(poly),
|
poly = deduplicate(poly)
|
||||||
indices = noncollinear_triple(poly)
|
)
|
||||||
) indices==[] ? undef :
|
len(poly[0])==2 ? // planar case
|
||||||
let(
|
let(
|
||||||
p1 = poly[indices[0]],
|
|
||||||
p2 = poly[indices[1]],
|
|
||||||
p3 = poly[indices[2]],
|
|
||||||
plane = plane3pt(p1,p2,p3),
|
|
||||||
res = _general_plane_line_intersection(plane, line, eps=eps)
|
|
||||||
) is_undef(res)? undef :
|
|
||||||
is_undef(res[1]) ? (
|
|
||||||
let(// Line is on polygon plane.
|
|
||||||
linevec = unit(line[1] - line[0]),
|
linevec = unit(line[1] - line[0]),
|
||||||
lp1 = line[0] + (bounded[0]? 0 : -1000000) * linevec,
|
bound = 100*max(flatten(pointlist_bounds(poly))),
|
||||||
lp2 = line[1] + (bounded[1]? 0 : 1000000) * linevec,
|
boundedline = [line[0] + (bounded[0]? 0 : -bound) * linevec,
|
||||||
poly2d = clockwise_polygon(project_plane(plane, poly)),
|
line[1] + (bounded[1]? 0 : bound) * linevec],
|
||||||
line2d = project_plane(plane, [lp1,lp2]),
|
parts = split_path_at_region_crossings(boundedline, [poly], closed=false),
|
||||||
parts = split_path_at_region_crossings(line2d, [poly2d], closed=false),
|
|
||||||
inside = [
|
inside = [
|
||||||
|
if(point_in_polygon(parts[0][0], poly, nonzero=nonzero, eps=eps) == 0)
|
||||||
|
[parts[0][0]], // Add starting point if it is on the polygon
|
||||||
for(part = parts)
|
for(part = parts)
|
||||||
if (point_in_polygon(mean(part), poly2d)>0) part
|
if (point_in_polygon(mean(part), poly, nonzero=nonzero, eps=eps) >=0 )
|
||||||
|
part
|
||||||
|
else if(len(part)==2 && point_in_polygon(part[1], poly, nonzero=nonzero, eps=eps) == 0)
|
||||||
|
[part[1]] // Add segment end if it is on the polygon
|
||||||
]
|
]
|
||||||
) !inside? undef :
|
)
|
||||||
let( isegs = [for (seg = inside) lift_plane(plane, seg) ] )
|
(len(inside)==0 ? undef : _merge_segments(inside, [inside[0]], eps))
|
||||||
isegs
|
: // 3d case
|
||||||
) :
|
let(indices = noncollinear_triple(poly))
|
||||||
bounded[0] && res[1]<0? undef :
|
indices==[] ? undef : // Polygon is collinear
|
||||||
bounded[1] && res[1]>1? undef :
|
|
||||||
let(
|
let(
|
||||||
proj = clockwise_polygon(project_plane([p1, p2, p3], poly)),
|
plane = plane3pt(poly[indices[0]], poly[indices[1]], poly[indices[2]]),
|
||||||
pt = project_plane([p1, p2, p3], res[0])
|
plane_isect = plane_line_intersection(plane, line, bounded, eps)
|
||||||
) point_in_polygon(pt, proj) < 0 ? undef :
|
)
|
||||||
res[0];
|
is_undef(plane_isect) ? undef :
|
||||||
|
is_vector(plane_isect,3) ?
|
||||||
|
let(
|
||||||
|
poly2d = project_plane(plane,poly),
|
||||||
|
pt2d = project_plane(plane, plane_isect)
|
||||||
|
)
|
||||||
|
(point_in_polygon(pt2d, poly2d, nonzero=nonzero, eps=eps) < 0 ? undef : plane_isect)
|
||||||
|
: // Case where line is on the polygon plane
|
||||||
|
let(
|
||||||
|
poly2d = project_plane(plane, poly),
|
||||||
|
line2d = project_plane(plane, line),
|
||||||
|
segments = polygon_line_intersection(poly2d, line2d, bounded=bounded, nonzero=nonzero, eps=eps)
|
||||||
|
)
|
||||||
|
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 :
|
||||||
|
approx(last(last(outsegs)), insegs[i][0], eps)
|
||||||
|
? _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()
|
// Function: plane_intersection()
|
||||||
|
@ -796,9 +926,9 @@ function _pointlist_greatest_distance(points,plane) =
|
||||||
abs(max( max(pt_nrm) - plane[3], -min(pt_nrm) + plane[3])) / norm(normal);
|
abs(max( max(pt_nrm) - plane[3], -min(pt_nrm) + plane[3])) / norm(normal);
|
||||||
|
|
||||||
|
|
||||||
// Function: points_on_plane()
|
// Function: are_points_on_plane()
|
||||||
// Usage:
|
// Usage:
|
||||||
// test = points_on_plane(points, plane, [eps]);
|
// test = are_points_on_plane(points, plane, [eps]);
|
||||||
// Topics: Geometry, Planes, Points
|
// Topics: Geometry, Planes, Points
|
||||||
// Description:
|
// Description:
|
||||||
// Returns true if the given 3D points are on the given plane.
|
// Returns true if the given 3D points are on the given plane.
|
||||||
|
@ -806,14 +936,14 @@ function _pointlist_greatest_distance(points,plane) =
|
||||||
// plane = The plane to test the points on.
|
// plane = The plane to test the points on.
|
||||||
// points = The list of 3D points to test.
|
// points = The list of 3D points to test.
|
||||||
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
|
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
|
||||||
function points_on_plane(points, plane, eps=EPSILON) =
|
function are_points_on_plane(points, plane, eps=EPSILON) =
|
||||||
assert( _valid_plane(plane), "Invalid plane." )
|
assert( _valid_plane(plane), "Invalid plane." )
|
||||||
assert( is_matrix(points,undef,3) && len(points)>0, "Invalid pointlist." ) // using is_matrix it accepts len(points)==1
|
assert( is_matrix(points,undef,3) && len(points)>0, "Invalid pointlist." ) // using is_matrix it accepts len(points)==1
|
||||||
assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
|
assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." )
|
||||||
_pointlist_greatest_distance(points,plane) < eps;
|
_pointlist_greatest_distance(points,plane) < eps;
|
||||||
|
|
||||||
|
|
||||||
// Function: above_plane()
|
// Function: is_above_plane()
|
||||||
// Usage:
|
// Usage:
|
||||||
// test = in_front_of_plane(plane, point);
|
// test = in_front_of_plane(plane, point);
|
||||||
// Topics: Geometry, Planes
|
// Topics: Geometry, Planes
|
||||||
|
@ -825,13 +955,51 @@ function points_on_plane(points, plane, eps=EPSILON) =
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// plane = The [A,B,C,D] coefficients for the first plane equation `Ax+By+Cz=D`.
|
// plane = The [A,B,C,D] coefficients for the first plane equation `Ax+By+Cz=D`.
|
||||||
// point = The 3D point to test.
|
// point = The 3D point to test.
|
||||||
function above_plane(plane, point) =
|
function is_above_plane(plane, point) =
|
||||||
point_plane_distance(plane, point) > EPSILON;
|
point_plane_distance(plane, point) > EPSILON;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Section: Circle Calculations
|
// 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()
|
// Function&Module: circle_2tangents()
|
||||||
// Usage: As Function
|
// Usage: As Function
|
||||||
// circ = circle_2tangents(pt1, pt2, pt3, r|d, [tangents]);
|
// circ = circle_2tangents(pt1, pt2, pt3, r|d, [tangents]);
|
||||||
|
@ -906,7 +1074,7 @@ function circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) =
|
||||||
"Invalid input points." )
|
"Invalid input points." )
|
||||||
is_undef(pt2)
|
is_undef(pt2)
|
||||||
? circle_2tangents(pt1[0], pt1[1], pt1[2], r=r, tangents=tangents)
|
? circle_2tangents(pt1[0], pt1[1], pt1[2], r=r, tangents=tangents)
|
||||||
: collinear(pt1, pt2, pt3)? undef :
|
: is_collinear(pt1, pt2, pt3)? undef :
|
||||||
let(
|
let(
|
||||||
v1 = unit(pt1 - pt2),
|
v1 = unit(pt1 - pt2),
|
||||||
v2 = unit(pt3 - pt2),
|
v2 = unit(pt3 - pt2),
|
||||||
|
@ -991,7 +1159,7 @@ function circle_3points(pt1, pt2, pt3) =
|
||||||
: assert( is_vector(pt1) && is_vector(pt2) && is_vector(pt3)
|
: assert( is_vector(pt1) && is_vector(pt2) && is_vector(pt3)
|
||||||
&& max(len(pt1),len(pt2),len(pt3))<=3 && min(len(pt1),len(pt2),len(pt3))>=2,
|
&& max(len(pt1),len(pt2),len(pt3))<=3 && min(len(pt1),len(pt2),len(pt3))>=2,
|
||||||
"Invalid point(s)." )
|
"Invalid point(s)." )
|
||||||
collinear(pt1,pt2,pt3)? [undef,undef,undef] :
|
is_collinear(pt1,pt2,pt3)? [undef,undef,undef] :
|
||||||
let(
|
let(
|
||||||
v = [ point3d(pt1), point3d(pt2), point3d(pt3) ], // triangle vertices
|
v = [ point3d(pt1), point3d(pt2), point3d(pt3) ], // triangle vertices
|
||||||
ed = [for(i=[0:2]) v[(i+1)%3]-v[i] ], // triangle edge vectors
|
ed = [for(i=[0:2]) v[(i+1)%3]-v[i] ], // triangle edge vectors
|
||||||
|
@ -1142,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
|
// Section: Pointlists
|
||||||
|
|
||||||
|
@ -1302,17 +1432,16 @@ function centroid(poly, eps=EPSILON) =
|
||||||
function polygon_normal(poly) =
|
function polygon_normal(poly) =
|
||||||
assert(is_path(poly,dim=3), "Invalid 3D polygon." )
|
assert(is_path(poly,dim=3), "Invalid 3D polygon." )
|
||||||
let(
|
let(
|
||||||
L=len(poly),
|
area_vec = sum([for(i=[1:len(poly)-2])
|
||||||
area_vec = sum([for(i=idx(poly))
|
cross(poly[i]-poly[0],
|
||||||
cross(poly[(i+1)%L]-poly[0],
|
poly[i+1]-poly[i])])
|
||||||
poly[(i+2)%L]-poly[(i+1)%L])])
|
|
||||||
)
|
)
|
||||||
norm(area_vec)<EPSILON ? undef : -unit(area_vec);
|
unit(-area_vec, error=undef);
|
||||||
|
|
||||||
|
|
||||||
// Function: point_in_polygon()
|
// Function: point_in_polygon()
|
||||||
// Usage:
|
// Usage:
|
||||||
// test = point_in_polygon(point, poly, [eps])
|
// test = point_in_polygon(point, poly, [nonzero], [eps])
|
||||||
// Topics: Geometry, Polygons
|
// Topics: Geometry, Polygons
|
||||||
// Description:
|
// Description:
|
||||||
// This function tests whether the given 2D point is inside, outside or on the boundary of
|
// This function tests whether the given 2D point is inside, outside or on the boundary of
|
||||||
|
@ -1356,7 +1485,7 @@ function polygon_normal(poly) =
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// point = The 2D point to check
|
// point = The 2D point to check
|
||||||
// poly = The list of 2D points forming the perimeter of the polygon.
|
// poly = The list of 2D points forming the perimeter of the polygon.
|
||||||
// nonzero = The rule to use: true for "Nonzero" rule and false for "Even-Odd" (Default: true )
|
// nonzero = The rule to use: true for "Nonzero" rule and false for "Even-Odd". Default: false (Even-Odd)
|
||||||
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
|
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
|
||||||
// Example(2D): With nonzero set to true, we get this result. Green dots are inside the polygon and red are outside:
|
// Example(2D): With nonzero set to true, we get this result. Green dots are inside the polygon and red are outside:
|
||||||
// a=20*2/3;
|
// a=20*2/3;
|
||||||
|
@ -1390,7 +1519,7 @@ function polygon_normal(poly) =
|
||||||
// color(point_in_polygon(p,path,nonzero=false)==1 ? "green" : "red")
|
// color(point_in_polygon(p,path,nonzero=false)==1 ? "green" : "red")
|
||||||
// move(p)circle(r=1, $fn=12);
|
// move(p)circle(r=1, $fn=12);
|
||||||
// }
|
// }
|
||||||
function point_in_polygon(point, poly, nonzero=true, eps=EPSILON) =
|
function point_in_polygon(point, poly, nonzero=false, eps=EPSILON) =
|
||||||
// Original algorithms from http://geomalgorithms.com/a03-_inclusion.html
|
// Original algorithms from http://geomalgorithms.com/a03-_inclusion.html
|
||||||
assert( is_vector(point,2) && is_path(poly,dim=2) && len(poly)>2,
|
assert( is_vector(point,2) && is_path(poly,dim=2) && len(poly)>2,
|
||||||
"The point and polygon should be in 2D. The polygon should have more that 2 points." )
|
"The point and polygon should be in 2D. The polygon should have more that 2 points." )
|
||||||
|
@ -1401,7 +1530,7 @@ function point_in_polygon(point, poly, nonzero=true, eps=EPSILON) =
|
||||||
for (i = [0:1:len(poly)-1])
|
for (i = [0:1:len(poly)-1])
|
||||||
let( seg = select(poly,i,i+1) )
|
let( seg = select(poly,i,i+1) )
|
||||||
if (!approx(seg[0],seg[1],eps) )
|
if (!approx(seg[0],seg[1],eps) )
|
||||||
point_on_segment(point, seg, eps=eps)? 1:0
|
is_point_on_line(point, seg, SEGMENT, eps=eps)? 1:0
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
sum(on_brd) > 0? 0 :
|
sum(on_brd) > 0? 0 :
|
||||||
|
@ -1432,6 +1561,124 @@ function point_in_polygon(point, poly, nonzero=true, eps=EPSILON) =
|
||||||
) 2*(len(cross)%2)-1;
|
) 2*(len(cross)%2)-1;
|
||||||
|
|
||||||
|
|
||||||
|
// Function: polygon_triangulate()
|
||||||
|
// Usage:
|
||||||
|
// triangles = polygon_triangulate(poly, [ind], [eps])
|
||||||
|
// Description:
|
||||||
|
// Given a simple polygon in 2D or 3D, triangulates it and returns a list
|
||||||
|
// of triples indexing into the polygon vertices. When the optional argument `ind` is
|
||||||
|
// given, the it is used as an index list into `poly` to define the polygon. In that case,
|
||||||
|
// `poly` may have a length greater than `ind`. Otherwise, all points in `poly`
|
||||||
|
// are considered as vertices of the polygon.
|
||||||
|
// .
|
||||||
|
// The function may issue an error if it finds that the polygon is not simple
|
||||||
|
// (self-intersecting) or its vertices are collinear. It can work for 3d non-planar polygons
|
||||||
|
// if they are close enough to planar but may otherwise issue an error for this case.
|
||||||
|
// .
|
||||||
|
// For 2d polygons, the output triangles will have the same winding (CW or CCW) of
|
||||||
|
// the input polygon. For 3d polygons, the triangle windings will induce a normal
|
||||||
|
// vector with the same direction of the polygon normal.
|
||||||
|
// Arguments:
|
||||||
|
// poly = Array of vertices for the polygon.
|
||||||
|
// ind = A list indexing the vertices of the polygon in `poly`.
|
||||||
|
// eps = A maximum tolerance in geometrical tests. Default: EPSILON
|
||||||
|
// Example:
|
||||||
|
// poly = star(id=10, od=15,n=11);
|
||||||
|
// tris = polygon_triangulate(poly);
|
||||||
|
// polygon(poly);
|
||||||
|
// up(1)
|
||||||
|
// color("blue");
|
||||||
|
// for(tri=tris) trace_path(select(poly,tri), size=.1, closed=true);
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// include<BOSL2/polyhedra.scad>
|
||||||
|
// vnf = regular_polyhedron_info(name="dodecahedron",side=5,info="vnf");
|
||||||
|
// %vnf_polyhedron(vnf);
|
||||||
|
// vnf_tri = [vnf[0], [for(face=vnf[1]) each polygon_triangulate(vnf[0], face) ] ];
|
||||||
|
// color("blue")
|
||||||
|
// vnf_wireframe(vnf_tri, d=.15);
|
||||||
|
|
||||||
|
function polygon_triangulate(poly, ind, eps=EPSILON) =
|
||||||
|
assert(is_path(poly), "Polygon `poly` should be a list of 2d or 3d points")
|
||||||
|
assert(is_undef(ind)
|
||||||
|
|| (is_vector(ind) && min(ind)>=0 && max(ind)<len(poly) ),
|
||||||
|
"Improper or out of bounds list of indices")
|
||||||
|
let( ind = deduplicate_indexed(poly,is_undef(ind) ? count(len(poly)) : ind) )
|
||||||
|
len(ind) == 3 ? [ind] :
|
||||||
|
len(ind) < 3 ? [] :
|
||||||
|
len(poly[ind[0]]) == 3
|
||||||
|
? // represents the polygon projection on its plane as a 2d polygon
|
||||||
|
let(
|
||||||
|
pts = select(poly,ind),
|
||||||
|
nrm = polygon_normal(pts)
|
||||||
|
)
|
||||||
|
// here, instead of an error, it might return [] or undef
|
||||||
|
assert( nrm!=undef,
|
||||||
|
"The polygon has self-intersections or its vertices are collinear or non coplanar.")
|
||||||
|
let(
|
||||||
|
imax = max_index([for(p=pts) norm(p-pts[0]) ]),
|
||||||
|
v1 = unit( pts[imax] - pts[0] ),
|
||||||
|
v2 = cross(v1,nrm),
|
||||||
|
prpts = pts*transpose([v1,v2])
|
||||||
|
)
|
||||||
|
[for(tri=_triangulate(prpts, count(len(ind)), eps)) select(ind,tri) ]
|
||||||
|
: let( cw = is_polygon_clockwise(select(poly, ind)) )
|
||||||
|
cw
|
||||||
|
? [for(tri=_triangulate( poly, reverse(ind), eps )) reverse(tri) ]
|
||||||
|
: _triangulate( poly, ind, eps );
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// requires ccw 2d polygons
|
||||||
|
// returns ccw triangles
|
||||||
|
function _triangulate(poly, ind, eps=EPSILON, tris=[]) =
|
||||||
|
len(ind)==3 ? concat(tris,[ind]) :
|
||||||
|
let( ear = _get_ear(poly,ind,eps) )
|
||||||
|
assert( ear!=undef,
|
||||||
|
"The polygon has self-intersections or its vertices are collinear or non coplanar.")
|
||||||
|
let(
|
||||||
|
ear_tri = select(ind,ear,ear+2),
|
||||||
|
indr = select(ind,ear+2, ear) // indices of the remaining points
|
||||||
|
)
|
||||||
|
_triangulate(poly, indr, eps, concat(tris,[ear_tri]));
|
||||||
|
|
||||||
|
// search a valid ear from the remaining polygon
|
||||||
|
function _get_ear(poly, ind, eps, _i=0) =
|
||||||
|
_i>=len(ind) ? undef : // poly has no ears
|
||||||
|
let( // the _i-th ear candidate
|
||||||
|
p0 = poly[ind[_i]],
|
||||||
|
p1 = poly[ind[(_i+1)%len(ind)]],
|
||||||
|
p2 = poly[ind[(_i+2)%len(ind)]]
|
||||||
|
)
|
||||||
|
// if it is not a convex vertex, try the next one
|
||||||
|
_is_cw2(p0,p1,p2,eps) ? _get_ear(poly,ind,eps, _i=_i+1) :
|
||||||
|
let( // vertex p1 is convex; check if the triangle contains any other point
|
||||||
|
to_tst = select(ind,_i+3, _i-1),
|
||||||
|
pt2tst = select(poly,to_tst), // points other than p0, p1 and p2
|
||||||
|
q = [(p0-p2).y, (p2-p0).x], // orthogonal to ray [p0,p2] pointing right
|
||||||
|
q0 = q*p0,
|
||||||
|
atleft = [for(p=pt2tst) if(p*q<=q0) p ]
|
||||||
|
)
|
||||||
|
atleft==[] ? _i : // no point inside -> an ear
|
||||||
|
let(
|
||||||
|
q = [(p2-p1).y, (p1-p2).x], // orthogonal to ray [p1,p2] pointing right
|
||||||
|
q0 = q*p2,
|
||||||
|
atleft = [for(p=atleft) if(p*q<=q0) p ]
|
||||||
|
)
|
||||||
|
atleft==[] ? _i : // no point inside -> an ear
|
||||||
|
let(
|
||||||
|
q = [(p1-p0).y, (p0-p1).x], // orthogonal to ray [p1,p0] pointing right
|
||||||
|
q0 = q*p1,
|
||||||
|
atleft = [for(p=atleft) if(p*q<=q0) p ]
|
||||||
|
)
|
||||||
|
atleft==[] ? _i : // no point inside -> an ear
|
||||||
|
// check the next ear candidate
|
||||||
|
_get_ear(poly, ind, eps, _i=_i+1);
|
||||||
|
|
||||||
|
function _is_cw2(a,b,c,eps=EPSILON) = cross(a-c,b-c)<eps*norm(a-c)*norm(b-c);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Function: is_polygon_clockwise()
|
// Function: is_polygon_clockwise()
|
||||||
// Usage:
|
// Usage:
|
||||||
// test = is_polygon_clockwise(poly);
|
// test = is_polygon_clockwise(poly);
|
||||||
|
@ -1507,6 +1754,7 @@ function reverse_polygon(poly) =
|
||||||
// Topics: Geometry, Polygons
|
// Topics: Geometry, Polygons
|
||||||
// Description:
|
// Description:
|
||||||
// Given a polygon `poly`, rotates the point ordering so that the first point in the polygon path is the one at index `i`.
|
// Given a polygon `poly`, rotates the point ordering so that the first point in the polygon path is the one at index `i`.
|
||||||
|
// This is identical to `list_rotate` except that it checks for doubled endpoints and removed them if present.
|
||||||
// Arguments:
|
// Arguments:
|
||||||
// poly = The list of points in the polygon path.
|
// poly = The list of points in the polygon path.
|
||||||
// i = The index of the point to shift to the front of the path.
|
// i = The index of the point to shift to the front of the path.
|
||||||
|
@ -1517,24 +1765,6 @@ function polygon_shift(poly, i) =
|
||||||
list_rotate(cleanup_path(poly), i);
|
list_rotate(cleanup_path(poly), i);
|
||||||
|
|
||||||
|
|
||||||
// Function: polygon_shift_to_closest_point()
|
|
||||||
// Usage:
|
|
||||||
// newpoly = polygon_shift_to_closest_point(path, pt);
|
|
||||||
// Topics: Geometry, Polygons
|
|
||||||
// Description:
|
|
||||||
// Given a polygon `poly`, rotates the point ordering so that the first point in the path is the one closest to the given point `pt`.
|
|
||||||
// Arguments:
|
|
||||||
// poly = The list of points in the polygon path.
|
|
||||||
// pt = The reference point.
|
|
||||||
function polygon_shift_to_closest_point(poly, pt) =
|
|
||||||
assert(is_vector(pt), "Invalid point." )
|
|
||||||
assert(is_path(poly,dim=len(pt)), "Invalid polygon or incompatible dimension with the point." )
|
|
||||||
let(
|
|
||||||
poly = cleanup_path(poly),
|
|
||||||
dists = [for (p=poly) norm(p-pt)],
|
|
||||||
closest = min_index(dists)
|
|
||||||
) select(poly,closest,closest+len(poly)-1);
|
|
||||||
|
|
||||||
|
|
||||||
// Function: reindex_polygon()
|
// Function: reindex_polygon()
|
||||||
// Usage:
|
// Usage:
|
||||||
|
@ -1544,7 +1774,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
|
// 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.
|
// 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
|
// 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
|
// 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
|
// 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
|
// vertices. If the input polygon is 2d and is oriented opposite the reference then its point order is
|
||||||
|
|
|
@ -186,7 +186,7 @@ function hull3d_faces(points) =
|
||||||
remaining = [for (i = [0:1:len(points)-1]) if (i!=a && i!=b && i!=c && i!=d) i],
|
remaining = [for (i = [0:1:len(points)-1]) if (i!=a && i!=b && i!=c && i!=d) i],
|
||||||
// Build an initial tetrahedron.
|
// Build an initial tetrahedron.
|
||||||
// Swap b, c if d is in front of triangle t.
|
// Swap b, c if d is in front of triangle t.
|
||||||
ifop = above_plane(plane, points[d]),
|
ifop = is_above_plane(plane, points[d]),
|
||||||
bc = ifop? [c,b] : [b,c],
|
bc = ifop? [c,b] : [b,c],
|
||||||
b = bc[0],
|
b = bc[0],
|
||||||
c = bc[1],
|
c = bc[1],
|
||||||
|
@ -245,7 +245,7 @@ function _remove_internal_edges(halfedges) = [
|
||||||
];
|
];
|
||||||
|
|
||||||
function _find_first_noncoplanar(plane, points, i=0) =
|
function _find_first_noncoplanar(plane, points, i=0) =
|
||||||
(i >= len(points) || !points_on_plane([points[i]],plane))? i :
|
(i >= len(points) || !are_points_on_plane([points[i]],plane))? i :
|
||||||
_find_first_noncoplanar(plane, points, i+1);
|
_find_first_noncoplanar(plane, points, i+1);
|
||||||
|
|
||||||
|
|
||||||
|
|
38
paths.scad
38
paths.scad
|
@ -6,9 +6,6 @@
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
include <triangulation.scad>
|
|
||||||
|
|
||||||
|
|
||||||
// Section: Functions
|
// Section: Functions
|
||||||
|
|
||||||
|
|
||||||
|
@ -121,7 +118,7 @@ function simplify_path(path, eps=EPSILON) =
|
||||||
indices = [
|
indices = [
|
||||||
0,
|
0,
|
||||||
for (i=[1:1:len(path)-2])
|
for (i=[1:1:len(path)-2])
|
||||||
if (!collinear(path[i-1], path[i], path[i+1], eps=eps)) i,
|
if (!is_collinear(path[i-1], path[i], path[i+1], eps=eps)) i,
|
||||||
len(path)-1
|
len(path)-1
|
||||||
]
|
]
|
||||||
) [for (i=indices) path[i]];
|
) [for (i=indices) path[i]];
|
||||||
|
@ -148,7 +145,7 @@ function simplify_path_indexed(points, indices, eps=EPSILON) =
|
||||||
i1 = indices[i-1],
|
i1 = indices[i-1],
|
||||||
i2 = indices[i],
|
i2 = indices[i],
|
||||||
i3 = indices[i+1]
|
i3 = indices[i+1]
|
||||||
) if (!collinear(points[i1], points[i2], points[i3], eps=eps))
|
) if (!is_collinear(points[i1], points[i2], points[i3], eps=eps))
|
||||||
indices[i]
|
indices[i]
|
||||||
],
|
],
|
||||||
indices[len(indices)-1]
|
indices[len(indices)-1]
|
||||||
|
@ -604,38 +601,11 @@ function path_add_jitter(path, dist=1/512, closed=true) =
|
||||||
path[0],
|
path[0],
|
||||||
for (i=idx(path,s=1,e=closed?-1:-2)) let(
|
for (i=idx(path,s=1,e=closed?-1:-2)) let(
|
||||||
n = line_normal([path[i-1],path[i]])
|
n = line_normal([path[i-1],path[i]])
|
||||||
) path[i] + n * (collinear(select(path,i-1,i+1))? (dist * ((i%2)*2-1)) : 0),
|
) path[i] + n * (is_collinear(select(path,i-1,i+1))? (dist * ((i%2)*2-1)) : 0),
|
||||||
if (!closed) last(path)
|
if (!closed) last(path)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
// Function: path3d_spiral()
|
|
||||||
// Description:
|
|
||||||
// Returns a 3D spiral path.
|
|
||||||
// Usage:
|
|
||||||
// path3d_spiral(turns, h, n, r|d, [cp], [scale]);
|
|
||||||
// Arguments:
|
|
||||||
// h = Height of spiral.
|
|
||||||
// turns = Number of turns in spiral.
|
|
||||||
// n = Number of spiral sides.
|
|
||||||
// r = Radius of spiral.
|
|
||||||
// d = Radius of spiral.
|
|
||||||
// cp = Centerpoint of spiral. Default: `[0,0]`
|
|
||||||
// scale = [X,Y] scaling factors for each axis. Default: `[1,1]`
|
|
||||||
// Example(3D):
|
|
||||||
// trace_path(path3d_spiral(turns=2.5, h=100, n=24, r=50), N=1, showpts=true);
|
|
||||||
function path3d_spiral(turns=3, h=100, n=12, r, d, cp=[0,0], scale=[1,1]) = let(
|
|
||||||
rr=get_radius(r=r, d=d, dflt=100),
|
|
||||||
cnt=floor(turns*n),
|
|
||||||
dz=h/cnt
|
|
||||||
) [
|
|
||||||
for (i=[0:1:cnt]) [
|
|
||||||
rr * cos(i*360/n) * scale.x + cp.x,
|
|
||||||
rr * sin(i*360/n) * scale.y + cp.y,
|
|
||||||
i*dz
|
|
||||||
]
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
// Function: path_self_intersections()
|
// Function: path_self_intersections()
|
||||||
// Usage:
|
// Usage:
|
||||||
|
@ -1004,7 +974,7 @@ function _path_cuts_normals(path, cuts, dirs, closed=false) =
|
||||||
// to define the plane of the path.
|
// to define the plane of the path.
|
||||||
function _path_plane(path, ind, i,closed) =
|
function _path_plane(path, ind, i,closed) =
|
||||||
i<(closed?-1:0) ? undef :
|
i<(closed?-1:0) ? undef :
|
||||||
!collinear(path[ind],path[ind-1], select(path,i))?
|
!is_collinear(path[ind],path[ind-1], select(path,i))?
|
||||||
[select(path,i)-path[ind-1],path[ind]-path[ind-1]] :
|
[select(path,i)-path[ind-1],path[ind]-path[ind-1]] :
|
||||||
_path_plane(path, ind, i-1);
|
_path_plane(path, ind, i-1);
|
||||||
|
|
||||||
|
|
|
@ -1865,16 +1865,16 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b
|
||||||
assert(jsvecok || jssingleok,
|
assert(jsvecok || jssingleok,
|
||||||
str("Argument joint_sides is invalid. All entries must be nonnegative, and it must be a number, 2-vector, or a length ",N," list those."))
|
str("Argument joint_sides is invalid. All entries must be nonnegative, and it must be a number, 2-vector, or a length ",N," list those."))
|
||||||
assert(is_num(k_sides) || is_vector(k_sides,N), str("Curvature parameter k_sides must be a number or length ",N," vector"))
|
assert(is_num(k_sides) || is_vector(k_sides,N), str("Curvature parameter k_sides must be a number or length ",N," vector"))
|
||||||
assert(coplanar(bottom))
|
assert(is_coplanar(bottom))
|
||||||
assert(coplanar(top))
|
assert(is_coplanar(top))
|
||||||
assert(!is_num(k_sides) || (k_sides>=0 && k_sides<=1), "Curvature parameter k_sides must be in interval [0,1]")
|
assert(!is_num(k_sides) || (k_sides>=0 && k_sides<=1), "Curvature parameter k_sides must be in interval [0,1]")
|
||||||
let(
|
let(
|
||||||
non_coplanar=[for(i=[0:N-1]) if (!coplanar(concat(select(top,i,i+1), select(bottom,i,i+1)))) [i,(i+1)%N]],
|
non_coplanar=[for(i=[0:N-1]) if (!is_coplanar(concat(select(top,i,i+1), select(bottom,i,i+1)))) [i,(i+1)%N]],
|
||||||
k_sides_vec = is_num(k_sides) ? repeat(k_sides, N) : k_sides,
|
k_sides_vec = is_num(k_sides) ? repeat(k_sides, N) : k_sides,
|
||||||
kbad = [for(i=[0:N-1]) if (k_sides_vec[i]<0 || k_sides_vec[i]>1) i],
|
kbad = [for(i=[0:N-1]) if (k_sides_vec[i]<0 || k_sides_vec[i]>1) i],
|
||||||
joint_sides_vec = jssingleok ? repeat(joint_sides,N) : joint_sides,
|
joint_sides_vec = jssingleok ? repeat(joint_sides,N) : joint_sides,
|
||||||
top_collinear = [for(i=[0:N-1]) if (collinear(select(top,i-1,i+1))) i],
|
top_collinear = [for(i=[0:N-1]) if (is_collinear(select(top,i-1,i+1))) i],
|
||||||
bot_collinear = [for(i=[0:N-1]) if (collinear(select(bottom,i-1,i+1))) i]
|
bot_collinear = [for(i=[0:N-1]) if (is_collinear(select(bottom,i-1,i+1))) i]
|
||||||
)
|
)
|
||||||
assert(non_coplanar==[], str("Side faces are non-coplanar at edges: ",non_coplanar))
|
assert(non_coplanar==[], str("Side faces are non-coplanar at edges: ",non_coplanar))
|
||||||
assert(top_collinear==[], str("Top has collinear or duplicated points at indices: ",top_collinear))
|
assert(top_collinear==[], str("Top has collinear or duplicated points at indices: ",top_collinear))
|
||||||
|
@ -1940,14 +1940,14 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b
|
||||||
vline = concat(select(subindex(top_patch[i],j),2,4),
|
vline = concat(select(subindex(top_patch[i],j),2,4),
|
||||||
select(subindex(bot_patch[i],j),2,4))
|
select(subindex(bot_patch[i],j),2,4))
|
||||||
)
|
)
|
||||||
if (!collinear(vline)) [i,j]],
|
if (!is_collinear(vline)) [i,j]],
|
||||||
//verify horiz edges
|
//verify horiz edges
|
||||||
verify_horiz=[for(i=[0:N-1], j=[0:4])
|
verify_horiz=[for(i=[0:N-1], j=[0:4])
|
||||||
let(
|
let(
|
||||||
hline_top = concat(select(top_patch[i][j],2,4), select(select(top_patch, i+1)[j],0,2)),
|
hline_top = concat(select(top_patch[i][j],2,4), select(select(top_patch, i+1)[j],0,2)),
|
||||||
hline_bot = concat(select(bot_patch[i][j],2,4), select(select(bot_patch, i+1)[j],0,2))
|
hline_bot = concat(select(bot_patch[i][j],2,4), select(select(bot_patch, i+1)[j],0,2))
|
||||||
)
|
)
|
||||||
if (!collinear(hline_top) || !collinear(hline_bot)) [i,j]]
|
if (!is_collinear(hline_top) || !is_collinear(hline_bot)) [i,j]]
|
||||||
)
|
)
|
||||||
assert(debug || top_intersections==[],
|
assert(debug || top_intersections==[],
|
||||||
"Roundovers interfere with each other on top face: either input is self intersecting or top joint length is too large")
|
"Roundovers interfere with each other on top face: either input is self intersecting or top joint length is too large")
|
||||||
|
@ -1958,7 +1958,8 @@ function rounded_prism(bottom, top, joint_bot=0, joint_top=0, joint_sides=0, k_b
|
||||||
vnf = vnf_merge([ each subindex(top_samples,0),
|
vnf = vnf_merge([ each subindex(top_samples,0),
|
||||||
each subindex(bot_samples,0),
|
each subindex(bot_samples,0),
|
||||||
for(pts=edge_points) vnf_vertex_array(pts),
|
for(pts=edge_points) vnf_vertex_array(pts),
|
||||||
vnf_triangulate(vnf_add_faces(EMPTY_VNF,faces))
|
debug ? vnf_add_faces(EMPTY_VNF,faces)
|
||||||
|
: vnf_triangulate(vnf_add_faces(EMPTY_VNF,faces))
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
debug ? [concat(top_patch, bot_patch), vnf] : vnf;
|
debug ? [concat(top_patch, bot_patch), vnf] : vnf;
|
||||||
|
|
903
shapes2d.scad
903
shapes2d.scad
|
@ -1,10 +1,6 @@
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
// LibFile: shapes2d.scad
|
// LibFile: shapes2d.scad
|
||||||
// This file includes stroke(), which converts a path into a
|
// This file lets you create regular polygons
|
||||||
// geometric object, like drawing with a pen. It even works on
|
|
||||||
// three-dimensional paths. You can make a dashed line or add arrow
|
|
||||||
// heads. The turtle() function provides a turtle graphics style
|
|
||||||
// approach for producing paths. You can create regular polygons
|
|
||||||
// with optional rounded corners and alignment features not
|
// with optional rounded corners and alignment features not
|
||||||
// available with circle(). The file also provides teardrop2d,
|
// available with circle(). The file also provides teardrop2d,
|
||||||
// which is useful for 3d printable holes. Lastly you can use the
|
// which is useful for 3d printable holes. Lastly you can use the
|
||||||
|
@ -16,897 +12,6 @@
|
||||||
// include <BOSL2/std.scad>
|
// include <BOSL2/std.scad>
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// Section: Line Drawing
|
|
||||||
|
|
||||||
// Module: stroke()
|
|
||||||
// Usage:
|
|
||||||
// stroke(path, [width], [closed], [endcaps], [endcap_width], [endcap_length], [endcap_extent], [trim]);
|
|
||||||
// stroke(path, [width], [closed], [endcap1], [endcap2], [endcap_width1], [endcap_width2], [endcap_length1], [endcap_length2], [endcap_extent1], [endcap_extent2], [trim1], [trim2]);
|
|
||||||
// Topics: Paths (2D), Paths (3D), Drawing Tools
|
|
||||||
// Description:
|
|
||||||
// Draws a 2D or 3D path with a given line width. Endcaps can be specified for each end individually.
|
|
||||||
// Figure(Med,NoAxes,2D,VPR=[0,0,0],VPD=250): Endcap Types
|
|
||||||
// cap_pairs = [
|
|
||||||
// ["butt", "chisel" ],
|
|
||||||
// ["round", "square" ],
|
|
||||||
// ["line", "cross" ],
|
|
||||||
// ["x", "diamond"],
|
|
||||||
// ["dot", "block" ],
|
|
||||||
// ["tail", "arrow" ],
|
|
||||||
// ["tail2", "arrow2" ]
|
|
||||||
// ];
|
|
||||||
// for (i = idx(cap_pairs)) {
|
|
||||||
// fwd((i-len(cap_pairs)/2+0.5)*13) {
|
|
||||||
// stroke([[-20,0], [20,0]], width=3, endcap1=cap_pairs[i][0], endcap2=cap_pairs[i][1]);
|
|
||||||
// color("black") {
|
|
||||||
// stroke([[-20,0], [20,0]], width=0.25, endcaps=false);
|
|
||||||
// left(28) text(text=cap_pairs[i][0], size=5, halign="right", valign="center");
|
|
||||||
// right(28) text(text=cap_pairs[i][1], size=5, halign="left", valign="center");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Arguments:
|
|
||||||
// path = The path to draw along.
|
|
||||||
// width = The width of the line to draw. If given as a list of widths, (one for each path point), draws the line with varying thickness to each point.
|
|
||||||
// closed = If true, draw an additional line from the end of the path to the start.
|
|
||||||
// plots = Specifies the plot point shape for every point of the line. If a 2D path is given, use that to draw custom plot points.
|
|
||||||
// joints = Specifies the joint shape for each joint of the line. If a 2D path is given, use that to draw custom joints.
|
|
||||||
// endcaps = Specifies the endcap type for both ends of the line. If a 2D path is given, use that to draw custom endcaps.
|
|
||||||
// endcap1 = Specifies the endcap type for the start of the line. If a 2D path is given, use that to draw a custom endcap.
|
|
||||||
// endcap2 = Specifies the endcap type for the end of the line. If a 2D path is given, use that to draw a custom endcap.
|
|
||||||
// plot_width = Some plot point shapes are wider than the line. This specifies the width of the shape, in multiples of the line width.
|
|
||||||
// joint_width = Some joint shapes are wider than the line. This specifies the width of the shape, in multiples of the line width.
|
|
||||||
// endcap_width = Some endcap types are wider than the line. This specifies the size of endcaps, in multiples of the line width.
|
|
||||||
// endcap_width1 = This specifies the size of starting endcap, in multiples of the line width.
|
|
||||||
// endcap_width2 = This specifies the size of ending endcap, in multiples of the line width.
|
|
||||||
// plot_length = Length of plot point shape, in multiples of the line width.
|
|
||||||
// joint_length = Length of joint shape, in multiples of the line width.
|
|
||||||
// endcap_length = Length of endcaps, in multiples of the line width.
|
|
||||||
// endcap_length1 = Length of starting endcap, in multiples of the line width.
|
|
||||||
// endcap_length2 = Length of ending endcap, in multiples of the line width.
|
|
||||||
// plot_extent = Extents length of plot point shape, in multiples of the line width.
|
|
||||||
// joint_extent = Extents length of joint shape, in multiples of the line width.
|
|
||||||
// endcap_extent = Extents length of endcaps, in multiples of the line width.
|
|
||||||
// endcap_extent1 = Extents length of starting endcap, in multiples of the line width.
|
|
||||||
// endcap_extent2 = Extents length of ending endcap, in multiples of the line width.
|
|
||||||
// plot_angle = Extra rotation given to plot point shapes, in degrees. If not given, the shapes are fully spun.
|
|
||||||
// joint_angle = Extra rotation given to joint shapes, in degrees. If not given, the shapes are fully spun.
|
|
||||||
// endcap_angle = Extra rotation given to endcaps, in degrees. If not given, the endcaps are fully spun.
|
|
||||||
// endcap_angle1 = Extra rotation given to a starting endcap, in degrees. If not given, the endcap is fully spun.
|
|
||||||
// endcap_angle2 = Extra rotation given to a ending endcap, in degrees. If not given, the endcap is fully spun.
|
|
||||||
// trim = Trim the the start and end line segments by this much, to keep them from interfering with custom endcaps.
|
|
||||||
// trim1 = Trim the the starting line segment by this much, to keep it from interfering with a custom endcap.
|
|
||||||
// trim2 = Trim the the ending line segment by this much, to keep it from interfering with a custom endcap.
|
|
||||||
// convexity = Max number of times a line could intersect a wall of an endcap.
|
|
||||||
// hull = If true, use `hull()` to make higher quality joints between segments, at the cost of being much slower. Default: true
|
|
||||||
// Example(2D): Drawing a Path
|
|
||||||
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
|
|
||||||
// stroke(path, width=20);
|
|
||||||
// Example(2D): Closing a Path
|
|
||||||
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
|
|
||||||
// stroke(path, width=20, endcaps=true, closed=true);
|
|
||||||
// Example(2D): Fancy Arrow Endcaps
|
|
||||||
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
|
|
||||||
// stroke(path, width=10, endcaps="arrow2");
|
|
||||||
// Example(2D): Modified Fancy Arrow Endcaps
|
|
||||||
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
|
|
||||||
// stroke(path, width=10, endcaps="arrow2", endcap_width=6, endcap_length=3, endcap_extent=2);
|
|
||||||
// Example(2D): Mixed Endcaps
|
|
||||||
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
|
|
||||||
// stroke(path, width=10, endcap1="tail2", endcap2="arrow2");
|
|
||||||
// Example(2D): Plotting Points
|
|
||||||
// path = [for (a=[0:30:360]) [a-180, 60*sin(a)]];
|
|
||||||
// stroke(path, width=3, joints="diamond", endcaps="arrow2", plot_angle=0, plot_width=5);
|
|
||||||
// Example(2D): Joints and Endcaps
|
|
||||||
// path = [for (a=[0:30:360]) [a-180, 60*sin(a)]];
|
|
||||||
// stroke(path, width=3, joints="dot", endcaps="arrow2", joint_angle=0);
|
|
||||||
// Example(2D): Custom Endcap Shapes
|
|
||||||
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]];
|
|
||||||
// arrow = [[0,0], [2,-3], [0.5,-2.3], [2,-4], [0.5,-3.5], [-0.5,-3.5], [-2,-4], [-0.5,-2.3], [-2,-3]];
|
|
||||||
// stroke(path, width=10, trim=3.5, endcaps=arrow);
|
|
||||||
// Example(2D): Variable Line Width
|
|
||||||
// path = circle(d=50,$fn=18);
|
|
||||||
// widths = [for (i=idx(path)) 10*i/len(path)+2];
|
|
||||||
// stroke(path,width=widths,$fa=1,$fs=1);
|
|
||||||
// Example: 3D Path with Endcaps
|
|
||||||
// path = rot([15,30,0], p=path3d(pentagon(d=50)));
|
|
||||||
// stroke(path, width=2, endcaps="arrow2", $fn=18);
|
|
||||||
// Example: 3D Path with Flat Endcaps
|
|
||||||
// path = rot([15,30,0], p=path3d(pentagon(d=50)));
|
|
||||||
// stroke(path, width=2, endcaps="arrow2", endcap_angle=0, $fn=18);
|
|
||||||
// Example: 3D Path with Mixed Endcaps
|
|
||||||
// path = rot([15,30,0], p=path3d(pentagon(d=50)));
|
|
||||||
// stroke(path, width=2, endcap1="arrow2", endcap2="tail", endcap_angle2=0, $fn=18);
|
|
||||||
// Example: 3D Path with Joints and Endcaps
|
|
||||||
// path = [for (i=[0:10:360]) [(i-180)/2,20*cos(3*i),20*sin(3*i)]];
|
|
||||||
// stroke(path, width=2, joints="dot", endcap1="round", endcap2="arrow2", joint_width=2.0, endcap_width2=3, $fn=18);
|
|
||||||
function stroke(
|
|
||||||
path, width=1, closed=false,
|
|
||||||
endcaps, endcap1, endcap2, joints, plots,
|
|
||||||
endcap_width, endcap_width1, endcap_width2, joint_width, plot_width,
|
|
||||||
endcap_length, endcap_length1, endcap_length2, joint_length, plot_length,
|
|
||||||
endcap_extent, endcap_extent1, endcap_extent2, joint_extent, plot_extent,
|
|
||||||
endcap_angle, endcap_angle1, endcap_angle2, joint_angle, plot_angle,
|
|
||||||
trim, trim1, trim2,
|
|
||||||
convexity=10, hull=true
|
|
||||||
) = no_function("stroke");
|
|
||||||
module stroke(
|
|
||||||
path, width=1, closed=false,
|
|
||||||
endcaps, endcap1, endcap2, joints, plots,
|
|
||||||
endcap_width, endcap_width1, endcap_width2, joint_width, plot_width,
|
|
||||||
endcap_length, endcap_length1, endcap_length2, joint_length, plot_length,
|
|
||||||
endcap_extent, endcap_extent1, endcap_extent2, joint_extent, plot_extent,
|
|
||||||
endcap_angle, endcap_angle1, endcap_angle2, joint_angle, plot_angle,
|
|
||||||
trim, trim1, trim2,
|
|
||||||
convexity=10, hull=true
|
|
||||||
) {
|
|
||||||
function _shape_defaults(cap) =
|
|
||||||
cap==undef? [1.00, 0.00, 0.00] :
|
|
||||||
cap==false? [1.00, 0.00, 0.00] :
|
|
||||||
cap==true? [1.00, 1.00, 0.00] :
|
|
||||||
cap=="butt"? [1.00, 0.00, 0.00] :
|
|
||||||
cap=="round"? [1.00, 1.00, 0.00] :
|
|
||||||
cap=="chisel"? [1.00, 1.00, 0.00] :
|
|
||||||
cap=="square"? [1.00, 1.00, 0.00] :
|
|
||||||
cap=="block"? [3.00, 1.00, 0.00] :
|
|
||||||
cap=="diamond"? [3.50, 1.00, 0.00] :
|
|
||||||
cap=="dot"? [3.00, 1.00, 0.00] :
|
|
||||||
cap=="x"? [3.50, 0.40, 0.00] :
|
|
||||||
cap=="cross"? [4.50, 0.22, 0.00] :
|
|
||||||
cap=="line"? [4.50, 0.22, 0.00] :
|
|
||||||
cap=="arrow"? [3.50, 0.40, 0.50] :
|
|
||||||
cap=="arrow2"? [3.50, 1.00, 0.14] :
|
|
||||||
cap=="tail"? [3.50, 0.47, 0.50] :
|
|
||||||
cap=="tail2"? [3.50, 0.28, 0.50] :
|
|
||||||
is_path(cap)? [0.00, 0.00, 0.00] :
|
|
||||||
assert(false, str("Invalid cap or joint: ",cap));
|
|
||||||
|
|
||||||
function _shape_path(cap,linewidth,w,l,l2) = (
|
|
||||||
(cap=="butt" || cap==false || cap==undef)? [] :
|
|
||||||
(cap=="round" || cap==true)? scale([w,l], p=circle(d=1, $fn=max(8, segs(w/2)))) :
|
|
||||||
cap=="chisel"? scale([w,l], p=circle(d=1,$fn=4)) :
|
|
||||||
cap=="diamond"? circle(d=w,$fn=4) :
|
|
||||||
cap=="square"? scale([w,l], p=square(1,center=true)) :
|
|
||||||
cap=="block"? scale([w,l], p=square(1,center=true)) :
|
|
||||||
cap=="dot"? circle(d=w, $fn=max(12, segs(w*3/2))) :
|
|
||||||
cap=="x"? [for (a=[0:90:270]) each rot(a,p=[[w+l/2,w-l/2]/2, [w-l/2,w+l/2]/2, [0,l/2]]) ] :
|
|
||||||
cap=="cross"? [for (a=[0:90:270]) each rot(a,p=[[l,w]/2, [-l,w]/2, [-l,l]/2]) ] :
|
|
||||||
cap=="line"? scale([w,l], p=square(1,center=true)) :
|
|
||||||
cap=="arrow"? [[0,0], [w/2,-l2], [w/2,-l2-l], [0,-l], [-w/2,-l2-l], [-w/2,-l2]] :
|
|
||||||
cap=="arrow2"? [[0,0], [w/2,-l2-l], [0,-l], [-w/2,-l2-l]] :
|
|
||||||
cap=="tail"? [[0,0], [w/2,l2], [w/2,l2-l], [0,-l], [-w/2,l2-l], [-w/2,l2]] :
|
|
||||||
cap=="tail2"? [[w/2,0], [w/2,-l], [0,-l-l2], [-w/2,-l], [-w/2,0]] :
|
|
||||||
is_path(cap)? cap :
|
|
||||||
assert(false, str("Invalid endcap: ",cap))
|
|
||||||
) * linewidth;
|
|
||||||
|
|
||||||
assert(is_bool(closed));
|
|
||||||
assert(is_list(path));
|
|
||||||
if (len(path) > 1) {
|
|
||||||
assert(is_path(path,[2,3]), "The path argument must be a list of 2D or 3D points.");
|
|
||||||
}
|
|
||||||
path = deduplicate( closed? close_path(path) : path );
|
|
||||||
|
|
||||||
assert(is_num(width) || (is_vector(width) && len(width)==len(path)));
|
|
||||||
width = is_num(width)? [for (x=path) width] : width;
|
|
||||||
assert(all([for (w=width) w>0]));
|
|
||||||
|
|
||||||
endcap1 = first_defined([endcap1, endcaps, if(!closed) plots, "round"]);
|
|
||||||
endcap2 = first_defined([endcap2, endcaps, plots, "round"]);
|
|
||||||
joints = first_defined([joints, plots, "round"]);
|
|
||||||
assert(is_bool(endcap1) || is_string(endcap1) || is_path(endcap1));
|
|
||||||
assert(is_bool(endcap2) || is_string(endcap2) || is_path(endcap2));
|
|
||||||
assert(is_bool(joints) || is_string(joints) || is_path(joints));
|
|
||||||
|
|
||||||
endcap1_dflts = _shape_defaults(endcap1);
|
|
||||||
endcap2_dflts = _shape_defaults(endcap2);
|
|
||||||
joint_dflts = _shape_defaults(joints);
|
|
||||||
|
|
||||||
endcap_width1 = first_defined([endcap_width1, endcap_width, plot_width, endcap1_dflts[0]]);
|
|
||||||
endcap_width2 = first_defined([endcap_width2, endcap_width, plot_width, endcap2_dflts[0]]);
|
|
||||||
joint_width = first_defined([joint_width, plot_width, joint_dflts[0]]);
|
|
||||||
assert(is_num(endcap_width1));
|
|
||||||
assert(is_num(endcap_width2));
|
|
||||||
assert(is_num(joint_width));
|
|
||||||
|
|
||||||
endcap_length1 = first_defined([endcap_length1, endcap_length, plot_length, endcap1_dflts[1]*endcap_width1]);
|
|
||||||
endcap_length2 = first_defined([endcap_length2, endcap_length, plot_length, endcap2_dflts[1]*endcap_width2]);
|
|
||||||
joint_length = first_defined([joint_length, plot_length, joint_dflts[1]*joint_width]);
|
|
||||||
assert(is_num(endcap_length1));
|
|
||||||
assert(is_num(endcap_length2));
|
|
||||||
assert(is_num(joint_length));
|
|
||||||
|
|
||||||
endcap_extent1 = first_defined([endcap_extent1, endcap_extent, plot_extent, endcap1_dflts[2]*endcap_width1]);
|
|
||||||
endcap_extent2 = first_defined([endcap_extent2, endcap_extent, plot_extent, endcap2_dflts[2]*endcap_width2]);
|
|
||||||
joint_extent = first_defined([joint_extent, plot_extent, joint_dflts[2]*joint_width]);
|
|
||||||
assert(is_num(endcap_extent1));
|
|
||||||
assert(is_num(endcap_extent2));
|
|
||||||
assert(is_num(joint_extent));
|
|
||||||
|
|
||||||
endcap_angle1 = first_defined([endcap_angle1, endcap_angle, plot_angle]);
|
|
||||||
endcap_angle2 = first_defined([endcap_angle2, endcap_angle, plot_angle]);
|
|
||||||
joint_angle = first_defined([joint_angle, plot_angle]);
|
|
||||||
assert(is_undef(endcap_angle1)||is_num(endcap_angle1));
|
|
||||||
assert(is_undef(endcap_angle2)||is_num(endcap_angle2));
|
|
||||||
assert(is_undef(joint_angle)||is_num(joint_angle));
|
|
||||||
|
|
||||||
endcap_shape1 = _shape_path(endcap1, width[0], endcap_width1, endcap_length1, endcap_extent1);
|
|
||||||
endcap_shape2 = _shape_path(endcap2, last(width), endcap_width2, endcap_length2, endcap_extent2);
|
|
||||||
|
|
||||||
trim1 = width[0] * first_defined([
|
|
||||||
trim1, trim,
|
|
||||||
(endcap1=="arrow")? endcap_length1-0.01 :
|
|
||||||
(endcap1=="arrow2")? endcap_length1*3/4 :
|
|
||||||
0
|
|
||||||
]);
|
|
||||||
assert(is_num(trim1));
|
|
||||||
|
|
||||||
trim2 = last(width) * first_defined([
|
|
||||||
trim2, trim,
|
|
||||||
(endcap2=="arrow")? endcap_length2-0.01 :
|
|
||||||
(endcap2=="arrow2")? endcap_length2*3/4 :
|
|
||||||
0
|
|
||||||
]);
|
|
||||||
assert(is_num(trim2));
|
|
||||||
|
|
||||||
if (len(path) == 1) {
|
|
||||||
if (len(path[0]) == 2) {
|
|
||||||
translate(path[0]) circle(d=width[0]);
|
|
||||||
} else {
|
|
||||||
translate(path[0]) sphere(d=width[0]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
spos = path_pos_from_start(path,trim1,closed=false);
|
|
||||||
epos = path_pos_from_end(path,trim2,closed=false);
|
|
||||||
path2 = path_subselect(path, spos[0], spos[1], epos[0], epos[1]);
|
|
||||||
widths = concat(
|
|
||||||
[lerp(width[spos[0]], width[(spos[0]+1)%len(width)], spos[1])],
|
|
||||||
[for (i = [spos[0]+1:1:epos[0]]) width[i]],
|
|
||||||
[lerp(width[epos[0]], width[(epos[0]+1)%len(width)], epos[1])]
|
|
||||||
);
|
|
||||||
|
|
||||||
start_vec = path[0] - path[1];
|
|
||||||
end_vec = last(path) - select(path,-2);
|
|
||||||
|
|
||||||
if (len(path[0]) == 2) {
|
|
||||||
// Straight segments
|
|
||||||
for (i = idx(path2,e=-2)) {
|
|
||||||
seg = select(path2,i,i+1);
|
|
||||||
delt = seg[1] - seg[0];
|
|
||||||
translate(seg[0]) {
|
|
||||||
rot(from=BACK,to=delt) {
|
|
||||||
trapezoid(w1=widths[i], w2=widths[i+1], h=norm(delt), anchor=FRONT);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Joints
|
|
||||||
for (i = [1:1:len(path2)-2]) {
|
|
||||||
$fn = quantup(segs(widths[i]/2),4);
|
|
||||||
translate(path2[i]) {
|
|
||||||
if (joints != undef) {
|
|
||||||
joint_shape = _shape_path(
|
|
||||||
joints, width[i],
|
|
||||||
joint_width,
|
|
||||||
joint_length,
|
|
||||||
joint_extent
|
|
||||||
);
|
|
||||||
v1 = unit(path2[i] - path2[i-1]);
|
|
||||||
v2 = unit(path2[i+1] - path2[i]);
|
|
||||||
vec = unit((v1+v2)/2);
|
|
||||||
mat = is_undef(joint_angle)
|
|
||||||
? rot(from=BACK,to=v1)
|
|
||||||
: zrot(joint_angle);
|
|
||||||
multmatrix(mat) polygon(joint_shape);
|
|
||||||
} else if (hull) {
|
|
||||||
hull() {
|
|
||||||
rot(from=BACK, to=path2[i]-path2[i-1])
|
|
||||||
circle(d=widths[i]);
|
|
||||||
rot(from=BACK, to=path2[i+1]-path2[i])
|
|
||||||
circle(d=widths[i]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rot(from=BACK, to=path2[i]-path2[i-1])
|
|
||||||
circle(d=widths[i]);
|
|
||||||
rot(from=BACK, to=path2[i+1]-path2[i])
|
|
||||||
circle(d=widths[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Endcap1
|
|
||||||
translate(path[0]) {
|
|
||||||
mat = is_undef(endcap_angle1)? rot(from=BACK,to=start_vec) :
|
|
||||||
zrot(endcap_angle1);
|
|
||||||
multmatrix(mat) polygon(endcap_shape1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Endcap2
|
|
||||||
translate(last(path)) {
|
|
||||||
mat = is_undef(endcap_angle2)? rot(from=BACK,to=end_vec) :
|
|
||||||
zrot(endcap_angle2);
|
|
||||||
multmatrix(mat) polygon(endcap_shape2);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
quatsums = q_cumulative([
|
|
||||||
for (i = idx(path2,e=-2)) let(
|
|
||||||
vec1 = i==0? UP : unit(path2[i]-path2[i-1], UP),
|
|
||||||
vec2 = unit(path2[i+1]-path2[i], UP),
|
|
||||||
axis = vector_axis(vec1,vec2),
|
|
||||||
ang = vector_angle(vec1,vec2)
|
|
||||||
) quat(axis,ang)
|
|
||||||
]);
|
|
||||||
rotmats = [for (q=quatsums) q_matrix4(q)];
|
|
||||||
sides = [
|
|
||||||
for (i = idx(path2,e=-2))
|
|
||||||
quantup(segs(max(widths[i],widths[i+1])/2),4)
|
|
||||||
];
|
|
||||||
|
|
||||||
// Straight segments
|
|
||||||
for (i = idx(path2,e=-2)) {
|
|
||||||
dist = norm(path2[i+1] - path2[i]);
|
|
||||||
w1 = widths[i]/2;
|
|
||||||
w2 = widths[i+1]/2;
|
|
||||||
$fn = sides[i];
|
|
||||||
translate(path2[i]) {
|
|
||||||
multmatrix(rotmats[i]) {
|
|
||||||
cylinder(r1=w1, r2=w2, h=dist, center=false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Joints
|
|
||||||
for (i = [1:1:len(path2)-2]) {
|
|
||||||
$fn = sides[i];
|
|
||||||
translate(path2[i]) {
|
|
||||||
if (joints != undef) {
|
|
||||||
joint_shape = _shape_path(
|
|
||||||
joints, width[i],
|
|
||||||
joint_width,
|
|
||||||
joint_length,
|
|
||||||
joint_extent
|
|
||||||
);
|
|
||||||
multmatrix(rotmats[i] * xrot(180)) {
|
|
||||||
$fn = sides[i];
|
|
||||||
if (is_undef(joint_angle)) {
|
|
||||||
rotate_extrude(convexity=convexity) {
|
|
||||||
right_half(planar=true) {
|
|
||||||
polygon(joint_shape);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rotate([90,0,joint_angle]) {
|
|
||||||
linear_extrude(height=max(widths[i],0.001), center=true, convexity=convexity) {
|
|
||||||
polygon(joint_shape);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (hull) {
|
|
||||||
hull(){
|
|
||||||
multmatrix(rotmats[i]) {
|
|
||||||
sphere(d=widths[i],style="aligned");
|
|
||||||
}
|
|
||||||
multmatrix(rotmats[i-1]) {
|
|
||||||
sphere(d=widths[i],style="aligned");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
multmatrix(rotmats[i]) {
|
|
||||||
sphere(d=widths[i],style="aligned");
|
|
||||||
}
|
|
||||||
multmatrix(rotmats[i-1]) {
|
|
||||||
sphere(d=widths[i],style="aligned");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Endcap1
|
|
||||||
translate(path[0]) {
|
|
||||||
multmatrix(rotmats[0] * xrot(180)) {
|
|
||||||
$fn = sides[0];
|
|
||||||
if (is_undef(endcap_angle1)) {
|
|
||||||
rotate_extrude(convexity=convexity) {
|
|
||||||
right_half(planar=true) {
|
|
||||||
polygon(endcap_shape1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rotate([90,0,endcap_angle1]) {
|
|
||||||
linear_extrude(height=max(widths[0],0.001), center=true, convexity=convexity) {
|
|
||||||
polygon(endcap_shape1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Endcap2
|
|
||||||
translate(last(path)) {
|
|
||||||
multmatrix(last(rotmats)) {
|
|
||||||
$fn = last(sides);
|
|
||||||
if (is_undef(endcap_angle2)) {
|
|
||||||
rotate_extrude(convexity=convexity) {
|
|
||||||
right_half(planar=true) {
|
|
||||||
polygon(endcap_shape2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rotate([90,0,endcap_angle2]) {
|
|
||||||
linear_extrude(height=max(last(widths),0.001), center=true, convexity=convexity) {
|
|
||||||
polygon(endcap_shape2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Function&Module: dashed_stroke()
|
|
||||||
// Usage: As a Module
|
|
||||||
// dashed_stroke(path, dashpat, [closed=]);
|
|
||||||
// Usage: As a Function
|
|
||||||
// dashes = dashed_stroke(path, dashpat, width=, [closed=]);
|
|
||||||
// Topics: Paths, Drawing Tools
|
|
||||||
// See Also: stroke(), path_cut()
|
|
||||||
// Description:
|
|
||||||
// Given a path and a dash pattern, creates a dashed line that follows that
|
|
||||||
// path with the given dash pattern.
|
|
||||||
// - When called as a function, returns a list of dash sub-paths.
|
|
||||||
// - When called as a module, draws all those subpaths using `stroke()`.
|
|
||||||
// Arguments:
|
|
||||||
// path = The path to subdivide into dashes.
|
|
||||||
// dashpat = A list of alternating dash lengths and space lengths for the dash pattern. This will be scaled by the width of the line.
|
|
||||||
// ---
|
|
||||||
// width = The width of the dashed line to draw. Module only. Default: 1
|
|
||||||
// closed = If true, treat path as a closed polygon. Default: false
|
|
||||||
// Example(2D): Open Path
|
|
||||||
// path = [for (a=[-180:10:180]) [a/3,20*sin(a)]];
|
|
||||||
// dashed_stroke(path, [3,2], width=1);
|
|
||||||
// Example(2D): Closed Polygon
|
|
||||||
// path = circle(d=100,$fn=72);
|
|
||||||
// dashpat = [10,2,3,2,3,2];
|
|
||||||
// dashed_stroke(path, dashpat, width=1, closed=true);
|
|
||||||
// Example(FlatSpin,VPD=250): 3D Dashed Path
|
|
||||||
// path = [for (a=[-180:5:180]) [a/3, 20*cos(3*a), 20*sin(3*a)]];
|
|
||||||
// dashed_stroke(path, [3,2], width=1);
|
|
||||||
function dashed_stroke(path, dashpat=[3,3], closed=false) =
|
|
||||||
let(
|
|
||||||
path = closed? close_path(path) : path,
|
|
||||||
dashpat = len(dashpat)%2==0? dashpat : concat(dashpat,[0]),
|
|
||||||
plen = path_length(path),
|
|
||||||
dlen = sum(dashpat),
|
|
||||||
doff = cumsum(dashpat),
|
|
||||||
reps = floor(plen / dlen),
|
|
||||||
step = plen / reps,
|
|
||||||
cuts = [
|
|
||||||
for (i=[0:1:reps-1], off=doff)
|
|
||||||
let (st=i*step, x=st+off)
|
|
||||||
if (x>0 && x<plen) x
|
|
||||||
],
|
|
||||||
dashes = path_cut(path, cuts, closed=false),
|
|
||||||
evens = [for (i=idx(dashes)) if (i%2==0) dashes[i]]
|
|
||||||
) evens;
|
|
||||||
|
|
||||||
|
|
||||||
module dashed_stroke(path, dashpat=[3,3], width=1, closed=false) {
|
|
||||||
segs = dashed_stroke(path, dashpat=dashpat*width, closed=closed);
|
|
||||||
for (seg = segs)
|
|
||||||
stroke(seg, width=width, endcaps=false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Function&Module: arc()
|
|
||||||
// Usage: 2D arc from 0º to `angle` degrees.
|
|
||||||
// arc(N, r|d=, angle);
|
|
||||||
// Usage: 2D arc from START to END degrees.
|
|
||||||
// arc(N, r|d=, angle=[START,END])
|
|
||||||
// Usage: 2D arc from `start` to `start+angle` degrees.
|
|
||||||
// arc(N, r|d=, start=, angle=)
|
|
||||||
// Usage: 2D circle segment by `width` and `thickness`, starting and ending on the X axis.
|
|
||||||
// arc(N, width=, thickness=)
|
|
||||||
// Usage: Shortest 2D or 3D arc around centerpoint `cp`, starting at P0 and ending on the vector pointing from `cp` to `P1`.
|
|
||||||
// arc(N, cp=, points=[P0,P1], [long=], [cw=], [ccw=])
|
|
||||||
// Usage: 2D or 3D arc, starting at `P0`, passing through `P1` and ending at `P2`.
|
|
||||||
// arc(N, points=[P0,P1,P2])
|
|
||||||
// Topics: Paths (2D), Paths (3D), Shapes (2D), Path Generators
|
|
||||||
// Description:
|
|
||||||
// If called as a function, returns a 2D or 3D path forming an arc.
|
|
||||||
// If called as a module, creates a 2D arc polygon or pie slice shape.
|
|
||||||
// Arguments:
|
|
||||||
// N = Number of vertices to form the arc curve from.
|
|
||||||
// r = Radius of the arc.
|
|
||||||
// angle = If a scalar, specifies the end angle in degrees (relative to start parameter). If a vector of two scalars, specifies start and end angles.
|
|
||||||
// ---
|
|
||||||
// d = Diameter of the arc.
|
|
||||||
// cp = Centerpoint of arc.
|
|
||||||
// points = Points on the arc.
|
|
||||||
// long = if given with cp and points takes the long arc instead of the default short arc. Default: false
|
|
||||||
// cw = if given with cp and 2 points takes the arc in the clockwise direction. Default: false
|
|
||||||
// ccw = if given with cp and 2 points takes the arc in the counter-clockwise direction. Default: false
|
|
||||||
// width = If given with `thickness`, arc starts and ends on X axis, to make a circle segment.
|
|
||||||
// thickness = If given with `width`, arc starts and ends on X axis, to make a circle segment.
|
|
||||||
// start = Start angle of arc.
|
|
||||||
// wedge = If true, include centerpoint `cp` in output to form pie slice shape.
|
|
||||||
// endpoint = If false exclude the last point (function only). Default: true
|
|
||||||
// Examples(2D):
|
|
||||||
// arc(N=4, r=30, angle=30, wedge=true);
|
|
||||||
// arc(r=30, angle=30, wedge=true);
|
|
||||||
// arc(d=60, angle=30, wedge=true);
|
|
||||||
// arc(d=60, angle=120);
|
|
||||||
// arc(d=60, angle=120, wedge=true);
|
|
||||||
// arc(r=30, angle=[75,135], wedge=true);
|
|
||||||
// arc(r=30, start=45, angle=75, wedge=true);
|
|
||||||
// arc(width=60, thickness=20);
|
|
||||||
// arc(cp=[-10,5], points=[[20,10],[0,35]], wedge=true);
|
|
||||||
// arc(points=[[30,-5],[20,10],[-10,20]], wedge=true);
|
|
||||||
// arc(points=[[5,30],[-10,-10],[30,5]], wedge=true);
|
|
||||||
// Example(2D):
|
|
||||||
// path = arc(points=[[5,30],[-10,-10],[30,5]], wedge=true);
|
|
||||||
// stroke(closed=true, path);
|
|
||||||
// Example(FlatSpin,VPD=175):
|
|
||||||
// path = arc(points=[[0,30,0],[0,0,30],[30,0,0]]);
|
|
||||||
// trace_path(path, showpts=true, color="cyan");
|
|
||||||
function arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false, long=false, cw=false, ccw=false, endpoint=true) =
|
|
||||||
assert(is_bool(endpoint))
|
|
||||||
!endpoint ? assert(!wedge, "endpoint cannot be false if wedge is true")
|
|
||||||
list_head(arc(N+1,r,angle,d,cp,points,width,thickness,start,wedge,long,cw,ccw,true)) :
|
|
||||||
assert(is_undef(N) || (is_integer(N) && N>=2), "Number of points must be an integer 2 or larger")
|
|
||||||
// First try for 2D arc specified by width and thickness
|
|
||||||
is_def(width) && is_def(thickness)? (
|
|
||||||
assert(!any_defined([r,cp,points]) && !any([cw,ccw,long]),"Conflicting or invalid parameters to arc")
|
|
||||||
assert(width>0, "Width must be postive")
|
|
||||||
assert(thickness>0, "Thickness must be positive")
|
|
||||||
arc(N,points=[[width/2,0], [0,thickness], [-width/2,0]],wedge=wedge)
|
|
||||||
) : is_def(angle)? (
|
|
||||||
let(
|
|
||||||
parmok = !any_defined([points,width,thickness]) &&
|
|
||||||
((is_vector(angle,2) && is_undef(start)) || is_num(angle))
|
|
||||||
)
|
|
||||||
assert(parmok,"Invalid parameters in arc")
|
|
||||||
let(
|
|
||||||
cp = first_defined([cp,[0,0]]),
|
|
||||||
start = is_def(start)? start : is_vector(angle) ? angle[0] : 0,
|
|
||||||
angle = is_vector(angle)? angle[1]-angle[0] : angle,
|
|
||||||
r = get_radius(r=r, d=d)
|
|
||||||
)
|
|
||||||
assert(is_vector(cp,2),"Centerpoint must be a 2d vector")
|
|
||||||
assert(angle!=0, "Arc has zero length")
|
|
||||||
assert(is_def(r) && r>0, "Arc radius invalid")
|
|
||||||
let(
|
|
||||||
N = is_def(N) ? N : max(3, ceil(segs(r)*abs(angle)/360)),
|
|
||||||
arcpoints = [for(i=[0:N-1]) let(theta = start + i*angle/(N-1)) r*[cos(theta),sin(theta)]+cp],
|
|
||||||
extra = wedge? [cp] : []
|
|
||||||
)
|
|
||||||
concat(extra,arcpoints)
|
|
||||||
) :
|
|
||||||
assert(is_path(points,[2,3]),"Point list is invalid")
|
|
||||||
// Arc is 3D, so transform points to 2D and make a recursive call, then remap back to 3D
|
|
||||||
len(points[0])==3? (
|
|
||||||
assert(!(cw || ccw), "(Counter)clockwise isn't meaningful in 3d, so `cw` and `ccw` must be false")
|
|
||||||
assert(is_undef(cp) || is_vector(cp,3),"points are 3d so cp must be 3d")
|
|
||||||
let(
|
|
||||||
plane = [is_def(cp) ? cp : points[2], points[0], points[1]],
|
|
||||||
center2d = is_def(cp) ? project_plane(plane,cp) : undef,
|
|
||||||
points2d = project_plane(plane, points)
|
|
||||||
)
|
|
||||||
lift_plane(plane,arc(N,cp=center2d,points=points2d,wedge=wedge,long=long))
|
|
||||||
) : is_def(cp)? (
|
|
||||||
// Arc defined by center plus two points, will have radius defined by center and points[0]
|
|
||||||
// and extent defined by direction of point[1] from the center
|
|
||||||
assert(is_vector(cp,2), "Centerpoint must be a 2d vector")
|
|
||||||
assert(len(points)==2, "When pointlist has length 3 centerpoint is not allowed")
|
|
||||||
assert(points[0]!=points[1], "Arc endpoints are equal")
|
|
||||||
assert(cp!=points[0]&&cp!=points[1], "Centerpoint equals an arc endpoint")
|
|
||||||
assert(count_true([long,cw,ccw])<=1, str("Only one of `long`, `cw` and `ccw` can be true",cw,ccw,long))
|
|
||||||
let(
|
|
||||||
angle = vector_angle(points[0], cp, points[1]),
|
|
||||||
v1 = points[0]-cp,
|
|
||||||
v2 = points[1]-cp,
|
|
||||||
prelim_dir = sign(det2([v1,v2])), // z component of cross product
|
|
||||||
dir = prelim_dir != 0
|
|
||||||
? prelim_dir
|
|
||||||
: assert(cw || ccw, "Collinear inputs don't define a unique arc")
|
|
||||||
1,
|
|
||||||
r=norm(v1),
|
|
||||||
final_angle = long || (ccw && dir<0) || (cw && dir>0) ? -dir*(360-angle) : dir*angle
|
|
||||||
)
|
|
||||||
arc(N,cp=cp,r=r,start=atan2(v1.y,v1.x),angle=final_angle,wedge=wedge)
|
|
||||||
) : (
|
|
||||||
// Final case is arc passing through three points, starting at point[0] and ending at point[3]
|
|
||||||
let(col = collinear(points[0],points[1],points[2]))
|
|
||||||
assert(!col, "Collinear inputs do not define an arc")
|
|
||||||
let(
|
|
||||||
cp = line_intersection(_normal_segment(points[0],points[1]),_normal_segment(points[1],points[2])),
|
|
||||||
// select order to be counterclockwise
|
|
||||||
dir = det2([points[1]-points[0],points[2]-points[1]]) > 0,
|
|
||||||
points = dir? select(points,[0,2]) : select(points,[2,0]),
|
|
||||||
r = norm(points[0]-cp),
|
|
||||||
theta_start = atan2(points[0].y-cp.y, points[0].x-cp.x),
|
|
||||||
theta_end = atan2(points[1].y-cp.y, points[1].x-cp.x),
|
|
||||||
angle = posmod(theta_end-theta_start, 360),
|
|
||||||
arcpts = arc(N,cp=cp,r=r,start=theta_start,angle=angle,wedge=wedge)
|
|
||||||
)
|
|
||||||
dir ? arcpts : reverse(arcpts)
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
module arc(N, r, angle, d, cp, points, width, thickness, start, wedge=false)
|
|
||||||
{
|
|
||||||
path = arc(N=N, r=r, angle=angle, d=d, cp=cp, points=points, width=width, thickness=thickness, start=start, wedge=wedge);
|
|
||||||
polygon(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function _normal_segment(p1,p2) =
|
|
||||||
let(center = (p1+p2)/2)
|
|
||||||
[center, center + norm(p1-p2)/2 * line_normal(p1,p2)];
|
|
||||||
|
|
||||||
|
|
||||||
// Function: turtle()
|
|
||||||
// Usage:
|
|
||||||
// turtle(commands, [state], [full_state=], [repeat=])
|
|
||||||
// Topics: Shapes (2D), Path Generators (2D), Mini-Language
|
|
||||||
// See Also: turtle3d()
|
|
||||||
// Description:
|
|
||||||
// Use a sequence of turtle graphics commands to generate a path. The parameter `commands` is a list of
|
|
||||||
// turtle commands and optional parameters for each command. The turtle state has a position, movement direction,
|
|
||||||
// movement distance, and default turn angle. If you do not give `state` as input then the turtle starts at the
|
|
||||||
// origin, pointed along the positive x axis with a movement distance of 1. By default, `turtle` returns just
|
|
||||||
// the computed turtle path. If you set `full_state` to true then it instead returns the full turtle state.
|
|
||||||
// You can invoke `turtle` again with this full state to continue the turtle path where you left off.
|
|
||||||
// .
|
|
||||||
// The turtle state is a list with three entries: the path constructed so far, the current step as a 2-vector, the current default angle,
|
|
||||||
// and the current arcsteps setting.
|
|
||||||
// .
|
|
||||||
// Commands | Arguments | What it does
|
|
||||||
// ------------ | ------------------ | -------------------------------
|
|
||||||
// "move" | [dist] | Move turtle scale*dist units in the turtle direction. Default dist=1.
|
|
||||||
// "xmove" | [dist] | Move turtle scale*dist units in the x direction. Default dist=1. Does not change turtle direction.
|
|
||||||
// "ymove" | [dist] | Move turtle scale*dist units in the y direction. Default dist=1. Does not change turtle direction.
|
|
||||||
// "xymove" | vector | Move turtle by the specified vector. Does not change turtle direction.
|
|
||||||
// "untilx" | xtarget | Move turtle in turtle direction until x==xtarget. Produces an error if xtarget is not reachable.
|
|
||||||
// "untily" | ytarget | Move turtle in turtle direction until y==ytarget. Produces an error if xtarget is not reachable.
|
|
||||||
// "jump" | point | Move the turtle to the specified point
|
|
||||||
// "xjump" | x | Move the turtle's x position to the specified value
|
|
||||||
// "yjump | y | Move the turtle's y position to the specified value
|
|
||||||
// "turn" | [angle] | Turn turtle direction by specified angle, or the turtle's default turn angle. The default angle starts at 90.
|
|
||||||
// "left" | [angle] | Same as "turn"
|
|
||||||
// "right" | [angle] | Same as "turn", -angle
|
|
||||||
// "angle" | angle | Set the default turn angle.
|
|
||||||
// "setdir" | dir | Set turtle direction. The parameter `dir` can be an angle or a vector.
|
|
||||||
// "length" | length | Change the turtle move distance to `length`
|
|
||||||
// "scale" | factor | Multiply turtle move distance by `factor`
|
|
||||||
// "addlength" | length | Add `length` to the turtle move distance
|
|
||||||
// "repeat" | count, commands | Repeats a list of commands `count` times.
|
|
||||||
// "arcleft" | radius, [angle] | Draw an arc from the current position toward the left at the specified radius and angle. The turtle turns by `angle`. A negative angle draws the arc to the right instead of the left, and leaves the turtle facing right. A negative radius draws the arc to the right but leaves the turtle facing left.
|
|
||||||
// "arcright" | radius, [angle] | Draw an arc from the current position toward the right at the specified radius and angle
|
|
||||||
// "arcleftto" | radius, angle | Draw an arc at the given radius turning toward the left until reaching the specified absolute angle.
|
|
||||||
// "arcrightto" | radius, angle | Draw an arc at the given radius turning toward the right until reaching the specified absolute angle.
|
|
||||||
// "arcsteps" | count | Specifies the number of segments to use for drawing arcs. If you set it to zero then the standard `$fn`, `$fa` and `$fs` variables define the number of segments.
|
|
||||||
//
|
|
||||||
// Arguments:
|
|
||||||
// commands = List of turtle commands
|
|
||||||
// state = Starting turtle state (from previous call) or starting point. Default: start at the origin, pointing right.
|
|
||||||
// ---
|
|
||||||
// full_state = If true return the full turtle state for continuing the path in subsequent turtle calls. Default: false
|
|
||||||
// repeat = Number of times to repeat the command list. Default: 1
|
|
||||||
//
|
|
||||||
// Example(2D): Simple rectangle
|
|
||||||
// path = turtle(["xmove",3, "ymove", "xmove",-3, "ymove",-1]);
|
|
||||||
// stroke(path,width=.1);
|
|
||||||
// Example(2D): Pentagon
|
|
||||||
// path=turtle(["angle",360/5,"move","turn","move","turn","move","turn","move"]);
|
|
||||||
// stroke(path,width=.1,closed=true);
|
|
||||||
// Example(2D): Pentagon using the repeat argument
|
|
||||||
// path=turtle(["move","turn",360/5],repeat=5);
|
|
||||||
// stroke(path,width=.1,closed=true);
|
|
||||||
// Example(2D): Pentagon using the repeat turtle command, setting the turn angle
|
|
||||||
// path=turtle(["angle",360/5,"repeat",5,["move","turn"]]);
|
|
||||||
// stroke(path,width=.1,closed=true);
|
|
||||||
// Example(2D): Pentagram
|
|
||||||
// path = turtle(["move","left",144], repeat=4);
|
|
||||||
// stroke(path,width=.05,closed=true);
|
|
||||||
// Example(2D): Sawtooth path
|
|
||||||
// path = turtle([
|
|
||||||
// "turn", 55,
|
|
||||||
// "untily", 2,
|
|
||||||
// "turn", -55-90,
|
|
||||||
// "untily", 0,
|
|
||||||
// "turn", 55+90,
|
|
||||||
// "untily", 2.5,
|
|
||||||
// "turn", -55-90,
|
|
||||||
// "untily", 0,
|
|
||||||
// "turn", 55+90,
|
|
||||||
// "untily", 3,
|
|
||||||
// "turn", -55-90,
|
|
||||||
// "untily", 0
|
|
||||||
// ]);
|
|
||||||
// stroke(path, width=.1);
|
|
||||||
// Example(2D): Simpler way to draw the sawtooth. The direction of the turtle is preserved when executing "yjump".
|
|
||||||
// path = turtle([
|
|
||||||
// "turn", 55,
|
|
||||||
// "untily", 2,
|
|
||||||
// "yjump", 0,
|
|
||||||
// "untily", 2.5,
|
|
||||||
// "yjump", 0,
|
|
||||||
// "untily", 3,
|
|
||||||
// "yjump", 0,
|
|
||||||
// ]);
|
|
||||||
// stroke(path, width=.1);
|
|
||||||
// Example(2DMed): square spiral
|
|
||||||
// path = turtle(["move","left","addlength",1],repeat=50);
|
|
||||||
// stroke(path,width=.2);
|
|
||||||
// Example(2DMed): pentagonal spiral
|
|
||||||
// path = turtle(["move","left",360/5,"addlength",1],repeat=50);
|
|
||||||
// stroke(path,width=.2);
|
|
||||||
// Example(2DMed): yet another spiral, without using `repeat`
|
|
||||||
// path = turtle(concat(["angle",71],flatten(repeat(["move","left","addlength",1],50))));
|
|
||||||
// stroke(path,width=.2);
|
|
||||||
// Example(2DMed): The previous spiral grows linearly and eventually intersects itself. This one grows geometrically and does not.
|
|
||||||
// path = turtle(["move","left",71,"scale",1.05],repeat=50);
|
|
||||||
// stroke(path,width=.05);
|
|
||||||
// Example(2D): Koch Snowflake
|
|
||||||
// function koch_unit(depth) =
|
|
||||||
// depth==0 ? ["move"] :
|
|
||||||
// concat(
|
|
||||||
// koch_unit(depth-1),
|
|
||||||
// ["right"],
|
|
||||||
// koch_unit(depth-1),
|
|
||||||
// ["left","left"],
|
|
||||||
// koch_unit(depth-1),
|
|
||||||
// ["right"],
|
|
||||||
// koch_unit(depth-1)
|
|
||||||
// );
|
|
||||||
// koch=concat(["angle",60,"repeat",3],[concat(koch_unit(3),["left","left"])]);
|
|
||||||
// polygon(turtle(koch));
|
|
||||||
module turtle(commands, state=[[[0,0]],[1,0],90,0], full_state=false, repeat=1) {no_module();}
|
|
||||||
function turtle(commands, state=[[[0,0]],[1,0],90,0], full_state=false, repeat=1) =
|
|
||||||
let( state = is_vector(state) ? [[state],[1,0],90,0] : state )
|
|
||||||
repeat == 1?
|
|
||||||
_turtle(commands,state,full_state) :
|
|
||||||
_turtle_repeat(commands, state, full_state, repeat);
|
|
||||||
|
|
||||||
function _turtle_repeat(commands, state, full_state, repeat) =
|
|
||||||
repeat==1?
|
|
||||||
_turtle(commands,state,full_state) :
|
|
||||||
_turtle_repeat(commands, _turtle(commands, state, true), full_state, repeat-1);
|
|
||||||
|
|
||||||
function _turtle_command_len(commands, index) =
|
|
||||||
let( one_or_two_arg = ["arcleft","arcright", "arcleftto", "arcrightto"] )
|
|
||||||
commands[index] == "repeat"? 3 : // Repeat command requires 2 args
|
|
||||||
// For these, the first arg is required, second arg is present if it is not a string
|
|
||||||
in_list(commands[index], one_or_two_arg) && len(commands)>index+2 && !is_string(commands[index+2]) ? 3 :
|
|
||||||
is_string(commands[index+1])? 1 : // If 2nd item is a string it's must be a new command
|
|
||||||
2; // Otherwise we have command and arg
|
|
||||||
|
|
||||||
function _turtle(commands, state, full_state, index=0) =
|
|
||||||
index < len(commands) ?
|
|
||||||
_turtle(commands,
|
|
||||||
_turtle_command(commands[index],commands[index+1],commands[index+2],state,index),
|
|
||||||
full_state,
|
|
||||||
index+_turtle_command_len(commands,index)
|
|
||||||
) :
|
|
||||||
( full_state ? state : state[0] );
|
|
||||||
|
|
||||||
// Turtle state: state = [path, step_vector, default angle, default arcsteps]
|
|
||||||
|
|
||||||
function _turtle_command(command, parm, parm2, state, index) =
|
|
||||||
command == "repeat"?
|
|
||||||
assert(is_num(parm),str("\"repeat\" command requires a numeric repeat count at index ",index))
|
|
||||||
assert(is_list(parm2),str("\"repeat\" command requires a command list parameter at index ",index))
|
|
||||||
_turtle_repeat(parm2, state, true, parm) :
|
|
||||||
let(
|
|
||||||
path = 0,
|
|
||||||
step=1,
|
|
||||||
angle=2,
|
|
||||||
arcsteps=3,
|
|
||||||
parm = !is_string(parm) ? parm : undef,
|
|
||||||
parm2 = !is_string(parm2) ? parm2 : undef,
|
|
||||||
needvec = ["jump", "xymove"],
|
|
||||||
neednum = ["untilx","untily","xjump","yjump","angle","length","scale","addlength"],
|
|
||||||
needeither = ["setdir"],
|
|
||||||
chvec = !in_list(command,needvec) || is_vector(parm,2),
|
|
||||||
chnum = !in_list(command,neednum) || is_num(parm),
|
|
||||||
vec_or_num = !in_list(command,needeither) || (is_num(parm) || is_vector(parm,2)),
|
|
||||||
lastpt = last(state[path])
|
|
||||||
)
|
|
||||||
assert(chvec,str("\"",command,"\" requires a vector parameter at index ",index))
|
|
||||||
assert(chnum,str("\"",command,"\" requires a numeric parameter at index ",index))
|
|
||||||
assert(vec_or_num,str("\"",command,"\" requires a vector or numeric parameter at index ",index))
|
|
||||||
|
|
||||||
command=="move" ? list_set(state, path, concat(state[path],[default(parm,1)*state[step]+lastpt])) :
|
|
||||||
command=="untilx" ? (
|
|
||||||
let(
|
|
||||||
int = line_intersection([lastpt,lastpt+state[step]], [[parm,0],[parm,1]]),
|
|
||||||
xgood = sign(state[step].x) == sign(int.x-lastpt.x)
|
|
||||||
)
|
|
||||||
assert(xgood,str("\"untilx\" never reaches desired goal at index ",index))
|
|
||||||
list_set(state,path,concat(state[path],[int]))
|
|
||||||
) :
|
|
||||||
command=="untily" ? (
|
|
||||||
let(
|
|
||||||
int = line_intersection([lastpt,lastpt+state[step]], [[0,parm],[1,parm]]),
|
|
||||||
ygood = is_def(int) && sign(state[step].y) == sign(int.y-lastpt.y)
|
|
||||||
)
|
|
||||||
assert(ygood,str("\"untily\" never reaches desired goal at index ",index))
|
|
||||||
list_set(state,path,concat(state[path],[int]))
|
|
||||||
) :
|
|
||||||
command=="xmove" ? list_set(state, path, concat(state[path],[default(parm,1)*norm(state[step])*[1,0]+lastpt])):
|
|
||||||
command=="ymove" ? list_set(state, path, concat(state[path],[default(parm,1)*norm(state[step])*[0,1]+lastpt])):
|
|
||||||
command=="xymove" ? list_set(state, path, concat(state[path], [lastpt+parm])):
|
|
||||||
command=="jump" ? list_set(state, path, concat(state[path],[parm])):
|
|
||||||
command=="xjump" ? list_set(state, path, concat(state[path],[[parm,lastpt.y]])):
|
|
||||||
command=="yjump" ? list_set(state, path, concat(state[path],[[lastpt.x,parm]])):
|
|
||||||
command=="turn" || command=="left" ? list_set(state, step, rot(default(parm,state[angle]),p=state[step],planar=true)) :
|
|
||||||
command=="right" ? list_set(state, step, rot(-default(parm,state[angle]),p=state[step],planar=true)) :
|
|
||||||
command=="angle" ? list_set(state, angle, parm) :
|
|
||||||
command=="setdir" ? (
|
|
||||||
is_vector(parm) ?
|
|
||||||
list_set(state, step, norm(state[step]) * unit(parm)) :
|
|
||||||
list_set(state, step, norm(state[step]) * [cos(parm),sin(parm)])
|
|
||||||
) :
|
|
||||||
command=="length" ? list_set(state, step, parm*unit(state[step])) :
|
|
||||||
command=="scale" ? list_set(state, step, parm*state[step]) :
|
|
||||||
command=="addlength" ? list_set(state, step, state[step]+unit(state[step])*parm) :
|
|
||||||
command=="arcsteps" ? list_set(state, arcsteps, parm) :
|
|
||||||
command=="arcleft" || command=="arcright" ?
|
|
||||||
assert(is_num(parm),str("\"",command,"\" command requires a numeric radius value at index ",index))
|
|
||||||
let(
|
|
||||||
myangle = default(parm2,state[angle]),
|
|
||||||
lrsign = command=="arcleft" ? 1 : -1,
|
|
||||||
radius = parm*sign(myangle),
|
|
||||||
center = lastpt + lrsign*radius*line_normal([0,0],state[step]),
|
|
||||||
steps = state[arcsteps]==0 ? segs(abs(radius)) : state[arcsteps],
|
|
||||||
arcpath = myangle == 0 || radius == 0 ? [] : arc(
|
|
||||||
steps,
|
|
||||||
points = [
|
|
||||||
lastpt,
|
|
||||||
rot(cp=center, p=lastpt, a=sign(parm)*lrsign*myangle/2),
|
|
||||||
rot(cp=center, p=lastpt, a=sign(parm)*lrsign*myangle)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
list_set(
|
|
||||||
state, [path,step], [
|
|
||||||
concat(state[path], list_tail(arcpath)),
|
|
||||||
rot(lrsign * myangle,p=state[step],planar=true)
|
|
||||||
]
|
|
||||||
) :
|
|
||||||
command=="arcleftto" || command=="arcrightto" ?
|
|
||||||
assert(is_num(parm),str("\"",command,"\" command requires a numeric radius value at index ",index))
|
|
||||||
assert(is_num(parm2),str("\"",command,"\" command requires a numeric angle value at index ",index))
|
|
||||||
let(
|
|
||||||
radius = parm,
|
|
||||||
lrsign = command=="arcleftto" ? 1 : -1,
|
|
||||||
center = lastpt + lrsign*radius*line_normal([0,0],state[step]),
|
|
||||||
steps = state[arcsteps]==0 ? segs(abs(radius)) : state[arcsteps],
|
|
||||||
start_angle = posmod(atan2(state[step].y, state[step].x),360),
|
|
||||||
end_angle = posmod(parm2,360),
|
|
||||||
delta_angle = -start_angle + (lrsign * end_angle < lrsign*start_angle ? end_angle+lrsign*360 : end_angle),
|
|
||||||
arcpath = delta_angle == 0 || radius==0 ? [] : arc(
|
|
||||||
steps,
|
|
||||||
points = [
|
|
||||||
lastpt,
|
|
||||||
rot(cp=center, p=lastpt, a=sign(radius)*delta_angle/2),
|
|
||||||
rot(cp=center, p=lastpt, a=sign(radius)*delta_angle)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
list_set(
|
|
||||||
state, [path,step], [
|
|
||||||
concat(state[path], list_tail(arcpath)),
|
|
||||||
rot(delta_angle,p=state[step],planar=true)
|
|
||||||
]
|
|
||||||
) :
|
|
||||||
assert(false,str("Unknown turtle command \"",command,"\" at index",index))
|
|
||||||
[];
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Section: 2D Primitives
|
// Section: 2D Primitives
|
||||||
|
|
||||||
|
@ -1556,14 +661,16 @@ 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_tip) || is_vector(align_tip))
|
||||||
assert(is_undef(align_pit) || is_vector(align_pit))
|
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_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(
|
let(
|
||||||
r = get_radius(r1=or, d1=od, r=r, d=d),
|
r = get_radius(r1=or, d1=od, r=r, d=d),
|
||||||
count = num_defined([ir,id,step]),
|
count = num_defined([ir,id,step]),
|
||||||
stepOK = is_undef(step) || (step>1 && step<n/2)
|
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(count==1, "Must specify exactly one of ir, id, step")
|
||||||
assert(stepOK, str("Parameter 'step' must be between 2 and ",floor(n/2)," for ",n," point star"))
|
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")
|
||||||
|
: str("Parameter 'step' must be between 2 and ",floor(n/2-1/2)," for ",n," point stars"))
|
||||||
let(
|
let(
|
||||||
mat = !is_undef(_mat) ? _mat :
|
mat = !is_undef(_mat) ? _mat :
|
||||||
( realign? rot(-180/n, planar=true) : affine2d_identity() ) * (
|
( realign? rot(-180/n, planar=true) : affine2d_identity() ) * (
|
||||||
|
|
3
std.scad
3
std.scad
|
@ -15,8 +15,9 @@ include <distributors.scad>
|
||||||
include <mutators.scad>
|
include <mutators.scad>
|
||||||
include <attachments.scad>
|
include <attachments.scad>
|
||||||
include <primitives.scad>
|
include <primitives.scad>
|
||||||
include <shapes.scad>
|
include <shapes3d.scad>
|
||||||
include <shapes2d.scad>
|
include <shapes2d.scad>
|
||||||
|
include <drawing.scad>
|
||||||
include <masks.scad>
|
include <masks.scad>
|
||||||
include <paths.scad>
|
include <paths.scad>
|
||||||
include <edges.scad>
|
include <edges.scad>
|
||||||
|
|
|
@ -6,8 +6,8 @@ include <../std.scad>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
test_point_on_segment();
|
test_is_point_on_line();
|
||||||
test_collinear();
|
test_is_collinear();
|
||||||
test_point_line_distance();
|
test_point_line_distance();
|
||||||
test_segment_distance();
|
test_segment_distance();
|
||||||
test_line_normal();
|
test_line_normal();
|
||||||
|
@ -33,9 +33,9 @@ test_plane_line_angle();
|
||||||
test_plane_line_intersection();
|
test_plane_line_intersection();
|
||||||
test_polygon_line_intersection();
|
test_polygon_line_intersection();
|
||||||
test_plane_intersection();
|
test_plane_intersection();
|
||||||
test_coplanar();
|
test_is_coplanar();
|
||||||
test_points_on_plane();
|
test_are_points_on_plane();
|
||||||
test_above_plane();
|
test_is_above_plane();
|
||||||
test_circle_2tangents();
|
test_circle_2tangents();
|
||||||
test_circle_3points();
|
test_circle_3points();
|
||||||
test_circle_point_tangents();
|
test_circle_point_tangents();
|
||||||
|
@ -44,7 +44,6 @@ test_noncollinear_triple();
|
||||||
test_polygon_area();
|
test_polygon_area();
|
||||||
test_is_polygon_convex();
|
test_is_polygon_convex();
|
||||||
test_polygon_shift();
|
test_polygon_shift();
|
||||||
test_polygon_shift_to_closest_point();
|
|
||||||
test_reindex_polygon();
|
test_reindex_polygon();
|
||||||
test_align_polygon();
|
test_align_polygon();
|
||||||
test_centroid();
|
test_centroid();
|
||||||
|
@ -53,10 +52,8 @@ test_is_polygon_clockwise();
|
||||||
test_clockwise_polygon();
|
test_clockwise_polygon();
|
||||||
test_ccw_polygon();
|
test_ccw_polygon();
|
||||||
test_reverse_polygon();
|
test_reverse_polygon();
|
||||||
//test_polygon_normal();
|
|
||||||
//test_split_polygons_at_each_x();
|
test_polygon_normal();
|
||||||
//test_split_polygons_at_each_y();
|
|
||||||
//test_split_polygons_at_each_z();
|
|
||||||
|
|
||||||
//tests to migrate to other files
|
//tests to migrate to other files
|
||||||
test_is_path();
|
test_is_path();
|
||||||
|
@ -225,7 +222,7 @@ module test__general_plane_line_intersection() {
|
||||||
*test__general_plane_line_intersection();
|
*test__general_plane_line_intersection();
|
||||||
|
|
||||||
|
|
||||||
module test_points_on_plane() {
|
module test_are_points_on_plane() {
|
||||||
pts = [for(i=[0:40]) rands(-1,1,3) ];
|
pts = [for(i=[0:40]) rands(-1,1,3) ];
|
||||||
dir = rands(-10,10,3);
|
dir = rands(-10,10,3);
|
||||||
normal0 = [1,2,3];
|
normal0 = [1,2,3];
|
||||||
|
@ -234,10 +231,10 @@ module test_points_on_plane() {
|
||||||
plane = [each normal, normal*dir];
|
plane = [each normal, normal*dir];
|
||||||
prj_pts = plane_closest_point(plane,pts);
|
prj_pts = plane_closest_point(plane,pts);
|
||||||
info = info_str([["pts = ",pts],["dir = ",dir],["ang = ",ang]]);
|
info = info_str([["pts = ",pts],["dir = ",dir],["ang = ",ang]]);
|
||||||
assert(points_on_plane(prj_pts,plane),info);
|
assert(are_points_on_plane(prj_pts,plane),info);
|
||||||
assert(!points_on_plane(concat(pts,[normal-dir]),plane),info);
|
assert(!are_points_on_plane(concat(pts,[normal-dir]),plane),info);
|
||||||
}
|
}
|
||||||
*test_points_on_plane();
|
*test_are_points_on_plane();
|
||||||
|
|
||||||
module test_plane_closest_point(){
|
module test_plane_closest_point(){
|
||||||
ang = rands(0,360,1)[0];
|
ang = rands(0,360,1)[0];
|
||||||
|
@ -268,35 +265,43 @@ module test_line_from_points() {
|
||||||
}
|
}
|
||||||
*test_line_from_points();
|
*test_line_from_points();
|
||||||
|
|
||||||
module test_point_on_segment() {
|
module test_is_point_on_line() {
|
||||||
assert(point_on_segment([-15,0], [[-10,0], [10,0]]) == false);
|
assert(is_point_on_line([-15,0], [[-10,0], [10,0]],SEGMENT) == false);
|
||||||
assert(point_on_segment([-10,0], [[-10,0], [10,0]]) == true);
|
assert(is_point_on_line([-10,0], [[-10,0], [10,0]],SEGMENT) == true);
|
||||||
assert(point_on_segment([-5,0], [[-10,0], [10,0]]) == true);
|
assert(is_point_on_line([-5,0], [[-10,0], [10,0]],SEGMENT) == true);
|
||||||
assert(point_on_segment([0,0], [[-10,0], [10,0]]) == true);
|
assert(is_point_on_line([0,0], [[-10,0], [10,0]],SEGMENT) == true);
|
||||||
assert(point_on_segment([3,3], [[-10,0], [10,0]]) == false);
|
assert(is_point_on_line([3,3], [[-10,0], [10,0]],SEGMENT) == false);
|
||||||
assert(point_on_segment([5,0], [[-10,0], [10,0]]) == true);
|
assert(is_point_on_line([5,0], [[-10,0], [10,0]],SEGMENT) == true);
|
||||||
assert(point_on_segment([10,0], [[-10,0], [10,0]]) == true);
|
assert(is_point_on_line([10,0], [[-10,0], [10,0]],SEGMENT) == true);
|
||||||
assert(point_on_segment([15,0], [[-10,0], [10,0]]) == false);
|
assert(is_point_on_line([15,0], [[-10,0], [10,0]],SEGMENT) == false);
|
||||||
|
|
||||||
assert(point_on_segment([0,-15], [[0,-10], [0,10]]) == false);
|
assert(is_point_on_line([0,-15], [[0,-10], [0,10]],SEGMENT) == false);
|
||||||
assert(point_on_segment([0,-10], [[0,-10], [0,10]]) == true);
|
assert(is_point_on_line([0,-10], [[0,-10], [0,10]],SEGMENT) == true);
|
||||||
assert(point_on_segment([0, -5], [[0,-10], [0,10]]) == true);
|
assert(is_point_on_line([0, -5], [[0,-10], [0,10]],SEGMENT) == true);
|
||||||
assert(point_on_segment([0, 0], [[0,-10], [0,10]]) == true);
|
assert(is_point_on_line([0, 0], [[0,-10], [0,10]],SEGMENT) == true);
|
||||||
assert(point_on_segment([3, 3], [[0,-10], [0,10]]) == false);
|
assert(is_point_on_line([3, 3], [[0,-10], [0,10]],SEGMENT) == false);
|
||||||
assert(point_on_segment([0, 5], [[0,-10], [0,10]]) == true);
|
assert(is_point_on_line([0, 5], [[0,-10], [0,10]],SEGMENT) == true);
|
||||||
assert(point_on_segment([0, 10], [[0,-10], [0,10]]) == true);
|
assert(is_point_on_line([0, 10], [[0,-10], [0,10]],SEGMENT) == true);
|
||||||
assert(point_on_segment([0, 15], [[0,-10], [0,10]]) == false);
|
assert(is_point_on_line([0, 15], [[0,-10], [0,10]],SEGMENT) == false);
|
||||||
|
|
||||||
assert(point_on_segment([-15,-15], [[-10,-10], [10,10]]) == false);
|
assert(is_point_on_line([-15,-15], [[-10,-10], [10,10]],SEGMENT) == false);
|
||||||
assert(point_on_segment([-10,-10], [[-10,-10], [10,10]]) == true);
|
assert(is_point_on_line([-10,-10], [[-10,-10], [10,10]],SEGMENT) == true);
|
||||||
assert(point_on_segment([ -5, -5], [[-10,-10], [10,10]]) == true);
|
assert(is_point_on_line([ -5, -5], [[-10,-10], [10,10]],SEGMENT) == true);
|
||||||
assert(point_on_segment([ 0, 0], [[-10,-10], [10,10]]) == true);
|
assert(is_point_on_line([ 0, 0], [[-10,-10], [10,10]],SEGMENT) == true);
|
||||||
assert(point_on_segment([ 0, 3], [[-10,-10], [10,10]]) == false);
|
assert(is_point_on_line([ 0, 3], [[-10,-10], [10,10]],SEGMENT) == false);
|
||||||
assert(point_on_segment([ 5, 5], [[-10,-10], [10,10]]) == true);
|
assert(is_point_on_line([ 5, 5], [[-10,-10], [10,10]],SEGMENT) == true);
|
||||||
assert(point_on_segment([ 10, 10], [[-10,-10], [10,10]]) == true);
|
assert(is_point_on_line([ 10, 10], [[-10,-10], [10,10]],SEGMENT) == true);
|
||||||
assert(point_on_segment([ 15, 15], [[-10,-10], [10,10]]) == false);
|
assert(is_point_on_line([ 15, 15], [[-10,-10], [10,10]],SEGMENT) == false);
|
||||||
|
|
||||||
|
assert(is_point_on_line([10,10], [[0,0],[5,5]]) == true);
|
||||||
|
assert(is_point_on_line([4,4], [[0,0],[5,5]]) == true);
|
||||||
|
assert(is_point_on_line([-2,-2], [[0,0],[5,5]]) == true);
|
||||||
|
assert(is_point_on_line([5,5], [[0,0],[5,5]]) == true);
|
||||||
|
assert(is_point_on_line([10,10], [[0,0],[5,5]],RAY) == true);
|
||||||
|
assert(is_point_on_line([0,0], [[0,0],[5,5]],RAY) == true);
|
||||||
|
assert(is_point_on_line([3,3], [[0,0],[5,5]],RAY) == true);
|
||||||
}
|
}
|
||||||
*test_point_on_segment();
|
*test_is_point_on_line();
|
||||||
|
|
||||||
|
|
||||||
module test__point_left_of_line2d() {
|
module test__point_left_of_line2d() {
|
||||||
|
@ -306,18 +311,18 @@ module test__point_left_of_line2d() {
|
||||||
}
|
}
|
||||||
test__point_left_of_line2d();
|
test__point_left_of_line2d();
|
||||||
|
|
||||||
module test_collinear() {
|
module test_is_collinear() {
|
||||||
assert(collinear([-10,-10], [-15, -16], [10,10]) == false);
|
assert(is_collinear([-10,-10], [-15, -16], [10,10]) == false);
|
||||||
assert(collinear([[-10,-10], [-15, -16], [10,10]]) == false);
|
assert(is_collinear([[-10,-10], [-15, -16], [10,10]]) == false);
|
||||||
assert(collinear([-10,-10], [-15, -15], [10,10]) == true);
|
assert(is_collinear([-10,-10], [-15, -15], [10,10]) == true);
|
||||||
assert(collinear([[-10,-10], [-15, -15], [10,10]]) == true);
|
assert(is_collinear([[-10,-10], [-15, -15], [10,10]]) == true);
|
||||||
assert(collinear([-10,-10], [ -3, 0], [10,10]) == false);
|
assert(is_collinear([-10,-10], [ -3, 0], [10,10]) == false);
|
||||||
assert(collinear([-10,-10], [ 0, 0], [10,10]) == true);
|
assert(is_collinear([-10,-10], [ 0, 0], [10,10]) == true);
|
||||||
assert(collinear([-10,-10], [ 3, 0], [10,10]) == false);
|
assert(is_collinear([-10,-10], [ 3, 0], [10,10]) == false);
|
||||||
assert(collinear([-10,-10], [ 15, 15], [10,10]) == true);
|
assert(is_collinear([-10,-10], [ 15, 15], [10,10]) == true);
|
||||||
assert(collinear([-10,-10], [ 15, 16], [10,10]) == false);
|
assert(is_collinear([-10,-10], [ 15, 16], [10,10]) == false);
|
||||||
}
|
}
|
||||||
*test_collinear();
|
*test_is_collinear();
|
||||||
|
|
||||||
|
|
||||||
module test_point_line_distance() {
|
module test_point_line_distance() {
|
||||||
|
@ -593,6 +598,16 @@ module test_plane_from_points() {
|
||||||
*test_plane_from_points();
|
*test_plane_from_points();
|
||||||
|
|
||||||
|
|
||||||
|
module test_polygon_normal() {
|
||||||
|
circ = path3d(circle($fn=37, r=3));
|
||||||
|
|
||||||
|
assert_approx(polygon_normal(circ), UP);
|
||||||
|
assert_approx(polygon_normal(rot(from=UP,to=[1,2,3],p=circ)), unit([1,2,3]));
|
||||||
|
assert_approx(polygon_normal(rot(from=UP,to=[4,-2,3],p=reverse(circ))), -unit([4,-2,3]));
|
||||||
|
assert_approx(polygon_normal(path3d([[0,0], [10,10], [11,10], [0,-1], [-1,1]])), UP);
|
||||||
|
}
|
||||||
|
*test_polygon_normal();
|
||||||
|
|
||||||
module test_plane_normal() {
|
module test_plane_normal() {
|
||||||
assert_approx(plane_normal(plane3pt([0,0,20], [0,10,10], [0,0,0])), [1,0,0]);
|
assert_approx(plane_normal(plane3pt([0,0,20], [0,10,10], [0,0,0])), [1,0,0]);
|
||||||
assert_approx(plane_normal(plane3pt([2,0,20], [2,10,10], [2,0,0])), [1,0,0]);
|
assert_approx(plane_normal(plane3pt([2,0,20], [2,10,10], [2,0,0])), [1,0,0]);
|
||||||
|
@ -650,30 +665,90 @@ module test_polygon_line_intersection() {
|
||||||
undef, info);
|
undef, info);
|
||||||
assert_approx(polygon_line_intersection(polygnr,linegnr,bounded=[false,false]),
|
assert_approx(polygon_line_intersection(polygnr,linegnr,bounded=[false,false]),
|
||||||
trnsl, info);
|
trnsl, info);
|
||||||
|
|
||||||
|
sq = path3d(square(10));
|
||||||
|
pentagram = 10*path3d(turtle(["move",10,"left",144], repeat=4));
|
||||||
|
for (tran = [ident(4), skew(sxy=1.2)*scale([.9,1,1.2])*yrot(14)*zrot(37)*xrot(9)])
|
||||||
|
{
|
||||||
|
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[5,5,-1], [5,5,10]])), apply(tran, [5,5,0]));
|
||||||
|
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[5,5,1], [5,5,10]])), apply(tran, [5,5,0]));
|
||||||
|
assert(undef==polygon_line_intersection(apply(tran,sq),apply(tran,[[5,5,1], [5,5,10]]),RAY));
|
||||||
|
assert(undef==polygon_line_intersection(apply(tran,sq),apply(tran,[[11,11,-1],[11,11,1]])));
|
||||||
|
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[5,0,-10], [5,0,10]])), apply(tran, [5,0,0]));
|
||||||
|
assert_equal(polygon_line_intersection(apply(tran,sq),apply(tran,[[5,0,1], [5,0,10]]),RAY), undef);
|
||||||
|
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[10,0,1],[10,0,10]])), apply(tran, [10,0,0]));
|
||||||
|
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[1,5,0],[9,6,0]])), apply(tran, [[[0,4.875,0],[10,6.125,0]]]));
|
||||||
|
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[1,5,0],[9,6,0]]),SEGMENT), apply(tran, [[[1,5,0],[9,6,0]]]));
|
||||||
|
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[-1,-1,0],[8,8,0]])), apply(tran, [[[0,0,0],[10,10,0]]]));
|
||||||
|
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[-1,-1,0],[8,8,0]]),SEGMENT), apply(tran, [[[0,0,0],[8,8,0]]]));
|
||||||
|
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[-1,-1,0],[8,8,0]]),RAY), apply(tran, [[[0,0,0],[10,10,0]]]));
|
||||||
|
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[-2,4,0], [12,11,0]]),RAY), apply(tran, [[[0,5,0],[10,10,0]]]));
|
||||||
|
assert_equal(polygon_line_intersection(apply(tran,sq),apply(tran,[[-20,0,0],[20,40,0]]),RAY), undef);
|
||||||
|
assert_approx(polygon_line_intersection(apply(tran,sq),apply(tran,[[-1,0,0],[11,0,0]])), apply(tran, [[[0,0,0],[10,0,0]]]));
|
||||||
|
}
|
||||||
|
assert_approx(polygon_line_intersection(path2d(sq),[[1,5],[9,6]],SEGMENT), [[[1,5],[9,6]]]);
|
||||||
|
assert_approx(polygon_line_intersection(path2d(sq),[[1,5],[9,6]],LINE), [[[0,4.875],[10,6.125]]]);
|
||||||
|
assert_approx(polygon_line_intersection(pentagram,[[50,10,-4],[54,12,4]], nonzero=true), [52,11,0]);
|
||||||
|
assert_equal(polygon_line_intersection(pentagram,[[50,10,-4],[54,12,4]], nonzero=false), undef);
|
||||||
|
assert_approx(polygon_line_intersection(pentagram,[[50,-10,-4],[54,-12,4]], nonzero=true), [52,-11,0]);
|
||||||
|
assert_approx(polygon_line_intersection(pentagram,[[50,-10,-4],[54,-12,4]], nonzero=false), [52,-11,0]);
|
||||||
|
assert_approx(polygon_line_intersection(star(8,step=3,od=10), [[-5,3], [5,3]]),
|
||||||
|
[[[-3.31370849898, 3], [-2.24264068712, 3]],
|
||||||
|
[[-0.828427124746, 3], [0.828427124746, 3]],
|
||||||
|
[[2.24264068712, 3], [3.31370849898, 3]]]);
|
||||||
|
|
||||||
|
tran = skew(sxy=1.2)*scale([.9,1,1.2])*yrot(14)*zrot(37)*xrot(9);
|
||||||
|
|
||||||
|
// assemble multiple edges into one edge
|
||||||
|
assert_approx(polygon_line_intersection(star(r=15,n=8,step=2), [[20,-5],[-5,20]]), [[[15,0],[0,15]]]);
|
||||||
|
assert_approx(polygon_line_intersection(apply(tran,path3d(star(r=15,n=8,step=2))), apply(tran,[[20,-5,0],[-5,20,0]])), apply(tran,[[[15,0,0],[0,15,0]]]));
|
||||||
|
// line going the other direction
|
||||||
|
assert_approx(polygon_line_intersection(star(r=15,n=8,step=2), [[-5,20],[20,-5]]), [[[0,15],[15,0]]]);
|
||||||
|
assert_approx(polygon_line_intersection(apply(tran,path3d(star(r=15,n=8,step=2))), apply(tran,[[-5,20,0],[20,-5,0]])),apply(tran, [[[0,15,0],[15,0,0]]]));
|
||||||
|
// single point
|
||||||
|
assert_approx(polygon_line_intersection(hexagon(r=15), [[15,-10],[15,13]], RAY), [[[15,0]]]);
|
||||||
|
assert_approx(polygon_line_intersection(apply(tran,path3d(hexagon(r=15))), apply(tran,[[15,-10,0],[15,13,0]]), RAY),
|
||||||
|
[[apply(tran,[15,0,0])]]);
|
||||||
|
// two points
|
||||||
|
assert_approx(polygon_line_intersection(star(r=15,n=8,step=3), rot(22.5,p=[[15,-10],[15,20]],cp=[15,0])),
|
||||||
|
[[[15,0]], [[10.6066017178, 10.6066017178]]]);
|
||||||
|
assert_approx(polygon_line_intersection(apply(tran,path3d(star(r=15,n=8,step=3))), apply(tran,rot(22.5,p=[[15,-10,0],[15,20,0]],cp=[15,0,0]))),
|
||||||
|
[[apply(tran,[15,0,0])], [apply(tran,[10.6066017178, 10.6066017178,0])]]);
|
||||||
|
// two segments and one point
|
||||||
|
star7 = star(r=25,ir=9,n=7);
|
||||||
|
assert_approx(polygon_line_intersection(star7, [left(10,p=star7[8]), right(50,p=star7[8])]),
|
||||||
|
[[[-22.5242216976, 10.8470934779]],
|
||||||
|
[[-5.60077322195, 10.8470934779], [0.997372374838, 10.8470934779]],
|
||||||
|
[[4.61675816681, 10.8470934779], [11.4280421589, 10.8470934779]]]);
|
||||||
|
assert_approx(polygon_line_intersection(apply(tran,path3d(star7)),
|
||||||
|
apply(tran, path3d([left(10,p=star7[8]), right(50,p=star7[8])]))),
|
||||||
|
[[apply(tran,[-22.5242216976, 10.8470934779,0])],
|
||||||
|
apply(tran,[[-5.60077322195, 10.8470934779,0], [0.997372374838, 10.8470934779,0]]),
|
||||||
|
apply(tran,[[4.61675816681, 10.8470934779,0], [11.4280421589, 10.8470934779,0]])]);
|
||||||
}
|
}
|
||||||
*test_polygon_line_intersection();
|
*test_polygon_line_intersection();
|
||||||
|
|
||||||
|
|
||||||
module test_coplanar() {
|
module test_is_coplanar() {
|
||||||
assert(coplanar([ [5,5,1],[0,0,1],[-1,-1,1] ]) == false);
|
assert(is_coplanar([ [5,5,1],[0,0,1],[-1,-1,1] ]) == false);
|
||||||
assert(coplanar([ [5,5,1],[0,0,0],[-1,-1,1] ]) == true);
|
assert(is_coplanar([ [5,5,1],[0,0,0],[-1,-1,1] ]) == true);
|
||||||
assert(coplanar([ [0,0,0],[1,0,1],[1,1,1], [0,1,2] ]) == false);
|
assert(is_coplanar([ [0,0,0],[1,0,1],[1,1,1], [0,1,2] ]) == false);
|
||||||
assert(coplanar([ [0,0,0],[1,0,1],[1,1,2], [0,1,1] ]) == true);
|
assert(is_coplanar([ [0,0,0],[1,0,1],[1,1,2], [0,1,1] ]) == true);
|
||||||
}
|
}
|
||||||
*test_coplanar();
|
*test_is_coplanar();
|
||||||
|
|
||||||
|
|
||||||
module test_above_plane() {
|
module test_is_above_plane() {
|
||||||
plane = plane3pt([0,0,0], [0,10,10], [10,0,10]);
|
plane = plane3pt([0,0,0], [0,10,10], [10,0,10]);
|
||||||
assert(above_plane(plane, [5,5,10]) == false);
|
assert(is_above_plane(plane, [5,5,10]) == false);
|
||||||
assert(above_plane(plane, [-5,0,0]) == true);
|
assert(is_above_plane(plane, [-5,0,0]) == true);
|
||||||
assert(above_plane(plane, [5,0,0]) == false);
|
assert(is_above_plane(plane, [5,0,0]) == false);
|
||||||
assert(above_plane(plane, [0,-5,0]) == true);
|
assert(is_above_plane(plane, [0,-5,0]) == true);
|
||||||
assert(above_plane(plane, [0,5,0]) == false);
|
assert(is_above_plane(plane, [0,5,0]) == false);
|
||||||
assert(above_plane(plane, [0,0,5]) == true);
|
assert(is_above_plane(plane, [0,0,5]) == true);
|
||||||
assert(above_plane(plane, [0,0,-5]) == false);
|
assert(is_above_plane(plane, [0,0,-5]) == false);
|
||||||
}
|
}
|
||||||
*test_above_plane();
|
*test_is_above_plane();
|
||||||
|
|
||||||
|
|
||||||
module test_is_path() {
|
module test_is_path() {
|
||||||
|
@ -744,15 +819,6 @@ module test_polygon_shift() {
|
||||||
*test_polygon_shift();
|
*test_polygon_shift();
|
||||||
|
|
||||||
|
|
||||||
module test_polygon_shift_to_closest_point() {
|
|
||||||
path = [[1,1],[-1,1],[-1,-1],[1,-1]];
|
|
||||||
assert(polygon_shift_to_closest_point(path,[1.1,1.1]) == [[1,1],[-1,1],[-1,-1],[1,-1]]);
|
|
||||||
assert(polygon_shift_to_closest_point(path,[-1.1,1.1]) == [[-1,1],[-1,-1],[1,-1],[1,1]]);
|
|
||||||
assert(polygon_shift_to_closest_point(path,[-1.1,-1.1]) == [[-1,-1],[1,-1],[1,1],[-1,1]]);
|
|
||||||
assert(polygon_shift_to_closest_point(path,[1.1,-1.1]) == [[1,-1],[1,1],[-1,1],[-1,-1]]);
|
|
||||||
}
|
|
||||||
*test_polygon_shift_to_closest_point();
|
|
||||||
|
|
||||||
|
|
||||||
module test_reindex_polygon() {
|
module test_reindex_polygon() {
|
||||||
pent = subdivide_path([for(i=[0:4])[sin(72*i),cos(72*i)]],5);
|
pent = subdivide_path([for(i=[0:4])[sin(72*i),cos(72*i)]],5);
|
||||||
|
|
|
@ -1,55 +1,6 @@
|
||||||
include <../std.scad>
|
include <../std.scad>
|
||||||
|
|
||||||
|
|
||||||
module test_turtle() {
|
|
||||||
assert_approx(
|
|
||||||
turtle([
|
|
||||||
"move", 10,
|
|
||||||
"ymove", 5,
|
|
||||||
"xmove", 5,
|
|
||||||
"xymove", [10,15],
|
|
||||||
"left", 135,
|
|
||||||
"untilx", 0,
|
|
||||||
"turn", 90,
|
|
||||||
"untily", 0,
|
|
||||||
"right", 135,
|
|
||||||
"arcsteps", 5,
|
|
||||||
"arcright", 15, 30,
|
|
||||||
"arcleft", 15, 30,
|
|
||||||
"arcsteps", 0,
|
|
||||||
"arcrightto", 15, 90,
|
|
||||||
"arcleftto", 15, 180,
|
|
||||||
"jump", [10,10],
|
|
||||||
"xjump", 15,
|
|
||||||
"yjump", 15,
|
|
||||||
"angle", 30,
|
|
||||||
"length", 15,
|
|
||||||
"right",
|
|
||||||
"move",
|
|
||||||
"scale", 2,
|
|
||||||
"left",
|
|
||||||
"move",
|
|
||||||
"addlength", 5,
|
|
||||||
"repeat", 3, ["move"],
|
|
||||||
], $fn=24),
|
|
||||||
[[0,0],[10,0],[10,5],[15,5],[25,20],[-3.5527136788e-15,45],[-45,0],[-44.8716729206,1.9578928833],[-44.4888873943,3.88228567654],[-43.8581929877,5.74025148548],[-42.9903810568,7.5],[-42.1225691259,9.25974851452],[-41.4918747192,11.1177143235],[-41.1090891929,13.0421071167],[-40.9807621135,15],[-41.0157305757,16.0236362005],[-41.120472923,17.0424997364],[-41.2945007983,18.0518401958],[-41.5370028033,19.0469515674],[-41.8468482818,20.0231941826],[-42.222592591,20.9760163477],[-42.6624838375,21.900975566],[-43.1644710453,22.7937592505],[-43.7262137184,23.6502048317],[-44.345092753,24.4663191649],[-45.0182226494,25.2382971483],[-45.7424649653,25.9625394642],[-46.5144429486,26.6356693606],[-47.3305572818,27.2545483952],[-48.187002863,27.8162910682],[-49.0797865476,28.318278276],[-50.0047457658,28.7581695226],[-50.957567931,29.1339138318],[-51.9338105462,29.4437593102],[-52.9289219177,29.6862613152],[-53.9382623771,29.8602891905],[-54.9571259131,29.9650315379],[-55.9807621135,30],[10,10],[15,10],[15,15],[2.00961894323,22.5],[-27.9903810568,22.5],[-62.9903810568,22.5],[-97.9903810568,22.5],[-132.990381057,22.5]]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
test_turtle();
|
|
||||||
|
|
||||||
|
|
||||||
module test_arc() {
|
|
||||||
assert_approx(arc(N=8, d=100, angle=135, cp=[10,10]), [[60,10],[57.1941665154,26.5139530978],[49.0915741234,41.1744900929],[36.6016038258,52.3362099614],[21.1260466978,58.7463956091],[4.40177619483,59.6856104947],[-11.6941869559,55.0484433951],[-25.3553390593,45.3553390593]]);
|
|
||||||
assert_approx(arc(N=8, d=100, angle=135, cp=[10,10],endpoint=false), [[60,10],[57.8470167866,24.5142338627],[51.5734806151,37.778511651],[41.7196642082,48.6505226681],[29.1341716183,56.1939766256],[14.9008570165,59.7592363336],[0.245483899194,59.0392640202],[-13.5698368413,54.0960632174]]);
|
|
||||||
assert_approx(arc(N=8, d=100, angle=[45,225], cp=[10,10]), [[45.3553390593,45.3553390593],[26.5139530978,57.1941665154],[4.40177619483,59.6856104947],[-16.6016038258,52.3362099614],[-32.3362099614,36.6016038258],[-39.6856104947,15.5982238052],[-37.1941665154,-6.51395309776],[-25.3553390593,-25.3553390593]]);
|
|
||||||
assert_approx(arc(N=8, d=100, start=45, angle=135, cp=[10,10]), [[45.3553390593,45.3553390593],[31.6941869559,55.0484433951],[15.5982238052,59.6856104947],[-1.12604669782,58.7463956091],[-16.6016038258,52.3362099614],[-29.0915741234,41.1744900929],[-37.1941665154,26.5139530978],[-40,10]]);
|
|
||||||
assert_approx(arc(N=8, d=100, start=45, angle=-90, cp=[10,10]), [[45.3553390593,45.3553390593],[52.3362099614,36.6016038258],[57.1941665154,26.5139530978],[59.6856104947,15.5982238052],[59.6856104947,4.40177619483],[57.1941665154,-6.51395309776],[52.3362099614,-16.6016038258],[45.3553390593,-25.3553390593]]);
|
|
||||||
assert_approx(arc(N=8, width=100, thickness=30), [[50,-3.5527136788e-15],[39.5300788555,13.9348601124],[25.3202618476,24.0284558904],[8.71492362453,29.3258437015],[-8.71492362453,29.3258437015],[-25.3202618476,24.0284558904],[-39.5300788555,13.9348601124],[-50,-1.42108547152e-14]]);
|
|
||||||
assert_approx(arc(N=8, cp=[10,10], points=[[45,45],[-25,45]]), [[45,45],[36.3342442379,51.9107096148],[26.3479795075,56.7198412457],[15.5419588213,59.1862449514],[4.45804117867,59.1862449514],[-6.34797950747,56.7198412457],[-16.3342442379,51.9107096148],[-25,45]]);
|
|
||||||
assert_approx(arc(N=24, cp=[10,10], points=[[45,45],[-25,45]], long=true), [[45,45],[51.3889035257,37.146982612],[56.0464336973,28.1583574081],[58.7777575294,18.4101349813],[59.4686187624,8.31010126292],[58.0901174104,-1.71924090789],[54.6999187001,-11.2583458482],[49.4398408296,-19.9081753929],[42.5299224539,-27.3068913894],[34.2592180667,-33.1449920477],[24.9737063235,-37.1782589647],[15.0618171232,-39.2379732261],[4.93818287676,-39.2379732261],[-4.97370632349,-37.1782589647],[-14.2592180667,-33.1449920477],[-22.5299224539,-27.3068913894],[-29.4398408296,-19.9081753929],[-34.6999187001,-11.2583458482],[-38.0901174104,-1.71924090789],[-39.4686187624,8.31010126292],[-38.7777575294,18.4101349813],[-36.0464336973,28.1583574081],[-31.3889035257,37.146982612],[-25,45]]);
|
|
||||||
assert_approx(arc($fn=24, cp=[10,10], points=[[45,45],[-25,45]], long=true), [[45,45],[53.2421021636,34.0856928585],[58.1827254512,21.3324740498],[59.4446596304,7.71403542491],[56.9315576496,-5.72987274525],[50.8352916125,-17.9728253654],[41.6213035891,-28.0800887515],[29.9930697126,-35.2799863457],[16.8383906815,-39.0228152281],[3.16160931847,-39.0228152281],[-9.9930697126,-35.2799863457],[-21.6213035891,-28.0800887515],[-30.8352916125,-17.9728253654],[-36.9315576496,-5.72987274525],[-39.4446596304,7.71403542491],[-38.1827254512,21.3324740498],[-33.2421021636,34.0856928585],[-25,45]]);
|
|
||||||
}
|
|
||||||
test_arc();
|
|
||||||
|
|
||||||
|
|
||||||
module test_rect() {
|
module test_rect() {
|
||||||
|
|
|
@ -605,7 +605,7 @@ function zrot(a=0, p, cp) = rot(a, cp=cp, p=p);
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
// Section: Scaling and Mirroring
|
// Section: Scaling
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,194 +0,0 @@
|
||||||
//////////////////////////////////////////////////////////////////////
|
|
||||||
// LibFile: triangulation.scad
|
|
||||||
// Functions to triangulate polyhedron faces.
|
|
||||||
// Includes:
|
|
||||||
// include <BOSL2/std.scad>
|
|
||||||
// include <BOSL2/triangulation.scad>
|
|
||||||
//////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
|
|
||||||
// Section: Functions
|
|
||||||
|
|
||||||
|
|
||||||
// Function: face_normal()
|
|
||||||
// Description:
|
|
||||||
// Given an array of vertices (`points`), and a list of indexes into the
|
|
||||||
// vertex array (`face`), returns the normal vector of the face.
|
|
||||||
// Arguments:
|
|
||||||
// points = Array of vertices for the polyhedron.
|
|
||||||
// face = The face, given as a list of indices into the vertex array `points`.
|
|
||||||
function face_normal(points, face) =
|
|
||||||
let(count=len(face))
|
|
||||||
unit(
|
|
||||||
sum(
|
|
||||||
[
|
|
||||||
for(i=[0:1:count-1]) cross(
|
|
||||||
points[face[(i+1)%count]]-points[face[0]],
|
|
||||||
points[face[(i+2)%count]]-points[face[(i+1)%count]]
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
;
|
|
||||||
|
|
||||||
|
|
||||||
// Function: find_convex_vertex()
|
|
||||||
// Description:
|
|
||||||
// Returns the index of a convex point on the given face.
|
|
||||||
// Arguments:
|
|
||||||
// points = Array of vertices for the polyhedron.
|
|
||||||
// face = The face, given as a list of indices into the vertex array `points`.
|
|
||||||
// facenorm = The normal vector of the face.
|
|
||||||
function find_convex_vertex(points, face, facenorm, i=0) =
|
|
||||||
let(count=len(face),
|
|
||||||
p0=points[face[i]],
|
|
||||||
p1=points[face[(i+1)%count]],
|
|
||||||
p2=points[face[(i+2)%count]]
|
|
||||||
)
|
|
||||||
(len(face)>i)? (
|
|
||||||
(cross(p1-p0, p2-p1)*facenorm>0)? (i+1)%count :
|
|
||||||
find_convex_vertex(points, face, facenorm, i+1)
|
|
||||||
) : //This should never happen since there is at least 1 convex vertex.
|
|
||||||
undef
|
|
||||||
;
|
|
||||||
|
|
||||||
|
|
||||||
// Function: point_in_ear()
|
|
||||||
// Description: Determine if a point is in a clipable convex ear.
|
|
||||||
// Arguments:
|
|
||||||
// points = Array of vertices for the polyhedron.
|
|
||||||
// face = The face, given as a list of indices into the vertex array `points`.
|
|
||||||
function point_in_ear(points, face, tests, i=0) =
|
|
||||||
(i<len(face)-1)?
|
|
||||||
let(
|
|
||||||
prev=point_in_ear(points, face, tests, i+1),
|
|
||||||
test=_check_point_in_ear(points[face[i]], tests)
|
|
||||||
)
|
|
||||||
(test>prev[0])? [test, i] : prev
|
|
||||||
:
|
|
||||||
[_check_point_in_ear(points[face[i]], tests), i]
|
|
||||||
;
|
|
||||||
|
|
||||||
|
|
||||||
// Internal non-exposed function.
|
|
||||||
function _check_point_in_ear(point, tests) =
|
|
||||||
let(
|
|
||||||
result=[
|
|
||||||
(point*tests[0][0])-tests[0][1],
|
|
||||||
(point*tests[1][0])-tests[1][1],
|
|
||||||
(point*tests[2][0])-tests[2][1]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
(result[0]>0 && result[1]>0 && result[2]>0)? result[0] : -1
|
|
||||||
;
|
|
||||||
|
|
||||||
|
|
||||||
// Function: normalize_vertex_perimeter()
|
|
||||||
// Description: Removes the last item in an array if it is the same as the first item.
|
|
||||||
// Arguments:
|
|
||||||
// v = The array to normalize.
|
|
||||||
function normalize_vertex_perimeter(v) =
|
|
||||||
let(lv = len(v))
|
|
||||||
(lv < 2)? v :
|
|
||||||
(v[lv-1] != v[0])? v :
|
|
||||||
[for (i=[0:1:lv-2]) v[i]]
|
|
||||||
;
|
|
||||||
|
|
||||||
|
|
||||||
// Function: is_only_noncolinear_vertex()
|
|
||||||
// Description:
|
|
||||||
// Given a face in a polyhedron, and a vertex in that face, returns true
|
|
||||||
// if that vertex is the only non-colinear vertex in the face.
|
|
||||||
// Arguments:
|
|
||||||
// points = Array of vertices for the polyhedron.
|
|
||||||
// facelist = The face, given as a list of indices into the vertex array `points`.
|
|
||||||
// vertex = The index into `facelist`, of the vertex to test.
|
|
||||||
function is_only_noncolinear_vertex(points, facelist, vertex) =
|
|
||||||
let(
|
|
||||||
face=select(facelist, vertex+1, vertex-1),
|
|
||||||
count=len(face)
|
|
||||||
)
|
|
||||||
0==sum(
|
|
||||||
[
|
|
||||||
for(i=[0:1:count-1]) norm(
|
|
||||||
cross(
|
|
||||||
points[face[(i+1)%count]]-points[face[0]],
|
|
||||||
points[face[(i+2)%count]]-points[face[(i+1)%count]]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
;
|
|
||||||
|
|
||||||
|
|
||||||
// Function: triangulate_face()
|
|
||||||
// Description:
|
|
||||||
// Given a face in a polyhedron, subdivides the face into triangular faces.
|
|
||||||
// Returns an array of faces, where each face is a list of three vertex indices.
|
|
||||||
// Arguments:
|
|
||||||
// points = Array of vertices for the polyhedron.
|
|
||||||
// face = The face, given as a list of indices into the vertex array `points`.
|
|
||||||
function triangulate_face(points, face) =
|
|
||||||
let(
|
|
||||||
points = path3d(points),
|
|
||||||
face = deduplicate_indexed(points,face),
|
|
||||||
count = len(face)
|
|
||||||
)
|
|
||||||
(count < 3)? [] :
|
|
||||||
(count == 3)? [face] :
|
|
||||||
let(
|
|
||||||
facenorm=face_normal(points, face),
|
|
||||||
cv=find_convex_vertex(points, face, facenorm)
|
|
||||||
)
|
|
||||||
assert(!is_undef(cv), "Cannot triangulate self-crossing face perimeters.")
|
|
||||||
let(
|
|
||||||
pv=(count+cv-1)%count,
|
|
||||||
nv=(cv+1)%count,
|
|
||||||
p0=points[face[pv]],
|
|
||||||
p1=points[face[cv]],
|
|
||||||
p2=points[face[nv]],
|
|
||||||
tests=[
|
|
||||||
[cross(facenorm, p0-p2), cross(facenorm, p0-p2)*p0],
|
|
||||||
[cross(facenorm, p1-p0), cross(facenorm, p1-p0)*p1],
|
|
||||||
[cross(facenorm, p2-p1), cross(facenorm, p2-p1)*p2]
|
|
||||||
],
|
|
||||||
ear_test=point_in_ear(points, face, tests),
|
|
||||||
clipable_ear=(ear_test[0]<0),
|
|
||||||
diagonal_point=ear_test[1]
|
|
||||||
)
|
|
||||||
(clipable_ear)? // There is no point inside the ear.
|
|
||||||
is_only_noncolinear_vertex(points, face, cv)?
|
|
||||||
// In the point&line degeneracy clip to somewhere in the middle of the line.
|
|
||||||
concat(
|
|
||||||
triangulate_face(points, select(face, cv, (cv+2)%count)),
|
|
||||||
triangulate_face(points, select(face, (cv+2)%count, cv))
|
|
||||||
)
|
|
||||||
:
|
|
||||||
// Otherwise the ear is safe to clip.
|
|
||||||
[
|
|
||||||
select(face, pv, nv),
|
|
||||||
each triangulate_face(points, select(face, nv, pv))
|
|
||||||
]
|
|
||||||
: // If there is a point inside the ear, make a diagonal and clip along that.
|
|
||||||
concat(
|
|
||||||
triangulate_face(points, select(face, cv, diagonal_point)),
|
|
||||||
triangulate_face(points, select(face, diagonal_point, cv))
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
// Function: triangulate_faces()
|
|
||||||
// Description:
|
|
||||||
// Subdivides all faces for the given polyhedron that have more than three vertices.
|
|
||||||
// Returns an array of faces where each face is a list of three vertex array indices.
|
|
||||||
// Arguments:
|
|
||||||
// points = Array of vertices for the polyhedron.
|
|
||||||
// faces = Array of faces for the polyhedron. Each face is a list of 3 or more indices into the `points` array.
|
|
||||||
function triangulate_faces(points, faces) =
|
|
||||||
[
|
|
||||||
for (face=faces) each
|
|
||||||
len(face)==3? [face] :
|
|
||||||
triangulate_face(points, normalize_vertex_perimeter(face))
|
|
||||||
];
|
|
||||||
|
|
||||||
|
|
||||||
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
|
|
529
vnf.scad
529
vnf.scad
|
@ -1,22 +1,23 @@
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
// LibFile: vnf.scad
|
// 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:
|
// Includes:
|
||||||
// include <BOSL2/std.scad>
|
// include <BOSL2/std.scad>
|
||||||
//////////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
include <triangulation.scad>
|
// Creating Polyhedrons with VNF Structures
|
||||||
|
|
||||||
|
// Section: VNF Testing and Access
|
||||||
// Section: Creating Polyhedrons with VNF Structures
|
|
||||||
// VNF stands for "Vertices'N'Faces". VNF structures are 2-item lists, `[VERTICES,FACES]` where the
|
// 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
|
// 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.
|
// 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
|
// 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.
|
// merge the various VNFs to get the completed polyhedron vertex list and faces.
|
||||||
|
|
||||||
|
|
||||||
EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
|
EMPTY_VNF = [[],[]]; // The standard empty VNF with no vertices or faces.
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,18 +50,6 @@ function vnf_vertices(vnf) = vnf[0];
|
||||||
function vnf_faces(vnf) = vnf[1];
|
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()
|
// Function: vnf_get_vertex()
|
||||||
// Usage:
|
// Usage:
|
||||||
// vvnf = vnf_get_vertex(vnf, p);
|
// vvnf = vnf_get_vertex(vnf, p);
|
||||||
|
@ -89,130 +78,7 @@ function vnf_get_vertex(vnf=EMPTY_VNF, p) =
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
// Function: vnf_add_face()
|
// Section: Constructing VNFs
|
||||||
// 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]
|
|
||||||
) [verts, triangulate_faces(verts, vnf[1])];
|
|
||||||
|
|
||||||
|
|
||||||
// Function: vnf_vertex_array()
|
// Function: vnf_vertex_array()
|
||||||
// Usage:
|
// Usage:
|
||||||
|
@ -468,6 +334,152 @@ function vnf_tri_array(points, row_wrap=false, reverse=false, vnf=EMPTY_VNF) =
|
||||||
vnf_merge(cleanup=true, [vnf, [flatten(points), faces]]);
|
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 len(face)==3 ? [face] :
|
||||||
|
polygon_triangulate(verts, face)]
|
||||||
|
) [verts, faces];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Section: Turning a VNF into geometry
|
||||||
|
|
||||||
|
|
||||||
// Module: vnf_polyhedron()
|
// Module: vnf_polyhedron()
|
||||||
// Usage:
|
// Usage:
|
||||||
|
@ -493,7 +505,6 @@ module vnf_polyhedron(vnf, convexity=2, extent=true, cp=[0,0,0], anchor="origin"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Module: vnf_wireframe()
|
// Module: vnf_wireframe()
|
||||||
// Usage:
|
// Usage:
|
||||||
// vnf_wireframe(vnf, <r|d>);
|
// vnf_wireframe(vnf, <r|d>);
|
||||||
|
@ -528,6 +539,8 @@ module vnf_wireframe(vnf, r, d)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Section: Operations on VNFs
|
||||||
|
|
||||||
// Function: vnf_volume()
|
// Function: vnf_volume()
|
||||||
// Usage:
|
// Usage:
|
||||||
// vol = vnf_volume(vnf);
|
// vol = vnf_volume(vnf);
|
||||||
|
@ -545,6 +558,16 @@ function vnf_volume(vnf) =
|
||||||
])/6;
|
])/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))]);
|
||||||
|
|
||||||
|
|
||||||
// Function: vnf_centroid()
|
// Function: vnf_centroid()
|
||||||
// Usage:
|
// Usage:
|
||||||
// vol = vnf_centroid(vnf);
|
// vol = vnf_centroid(vnf);
|
||||||
|
@ -573,6 +596,115 @@ function vnf_centroid(vnf) =
|
||||||
pos[1]/pos[0]/4;
|
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) =
|
function _triangulate_planar_convex_polygons(polys) =
|
||||||
polys==[]? [] :
|
polys==[]? [] :
|
||||||
let(
|
let(
|
||||||
|
@ -817,6 +949,8 @@ function _split_polygons_at_each_y(polys, ys, _i=0) =
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// Section: Debugging VNFs
|
||||||
|
|
||||||
// Function&Module: vnf_validate()
|
// Function&Module: vnf_validate()
|
||||||
// Usage: As Function
|
// Usage: As Function
|
||||||
// fails = vnf_validate(vnf);
|
// fails = vnf_validate(vnf);
|
||||||
|
@ -1071,7 +1205,7 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) =
|
||||||
faceverts = [for (k=face) varr[k]]
|
faceverts = [for (k=face) varr[k]]
|
||||||
)
|
)
|
||||||
if (is_num(area) && abs(area) > EPSILON)
|
if (is_num(area) && abs(area) > EPSILON)
|
||||||
if (!coplanar(faceverts))
|
if (!is_coplanar(faceverts))
|
||||||
_vnf_validate_err("NONPLANAR", faceverts)
|
_vnf_validate_err("NONPLANAR", faceverts)
|
||||||
]),
|
]),
|
||||||
issues = concat(issues, nonplanars)
|
issues = concat(issues, nonplanars)
|
||||||
|
@ -1144,114 +1278,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
|
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap
|
||||||
|
|
Loading…
Reference in a new issue