doc tweaks

This commit is contained in:
Adrian Mariano 2021-10-07 21:31:58 -04:00
parent dbec028bd1
commit 9bb22dc7c5
6 changed files with 185 additions and 105 deletions

View file

@ -103,7 +103,7 @@
// stroke(path, width=3, joints="diamond", endcaps="arrow2", endcap_angle=0, endcap_width=5, joint_angle=0, joint_width=5); // stroke(path, width=3, joints="diamond", endcaps="arrow2", endcap_angle=0, endcap_width=5, joint_angle=0, joint_width=5);
// Example(2D): Joints and Endcaps // Example(2D): Joints and Endcaps
// path = [for (a=[0:30:360]) [a-180, 60*sin(a)]]; // path = [for (a=[0:30:360]) [a-180, 60*sin(a)]];
// stroke(path, width=3, joints="dot", endcaps="arrow2", joint_angle=0); // stroke(path, width=8, joints="dot", endcaps="arrow2");
// Example(2D): Custom Endcap Shapes // Example(2D): Custom Endcap Shapes
// path = [[0,100], [100,100], [200,0], [100,-100], [100,0]]; // 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]]; // 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]];
@ -852,13 +852,13 @@ function _normal_segment(p1,p2) =
// stroke(path,width=.2); // stroke(path,width=.2);
// Example(2DMed): pentagonal spiral // Example(2DMed): pentagonal spiral
// path = turtle(["move","left",360/5,"addlength",1],repeat=50); // path = turtle(["move","left",360/5,"addlength",1],repeat=50);
// stroke(path,width=.2); // stroke(path,width=.7);
// Example(2DMed): yet another spiral, without using `repeat` // Example(2DMed): yet another spiral, without using `repeat`
// path = turtle(concat(["angle",71],flatten(repeat(["move","left","addlength",1],50)))); // path = turtle(concat(["angle",71],flatten(repeat(["move","left","addlength",1],50))));
// stroke(path,width=.2); // stroke(path,width=.7);
// Example(2DMed): The previous spiral grows linearly and eventually intersects itself. This one grows geometrically and does not. // 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); // path = turtle(["move","left",71,"scale",1.05],repeat=50);
// stroke(path,width=.05); // stroke(path,width=.15);
// Example(2D): Koch Snowflake // Example(2D): Koch Snowflake
// function koch_unit(depth) = // function koch_unit(depth) =
// depth==0 ? ["move"] : // depth==0 ? ["move"] :

View file

@ -30,12 +30,12 @@ function is_point_on_line(point, line, bounded=false, eps=EPSILON) =
point_line_distance(point, line, bounded)<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
//_dist2line works for any dimension ///_dist2line works for any dimension
function _dist2line(d,n) = norm(d-(d * n) * n); function _dist2line(d,n) = norm(d-(d * n) * n);
//Internal ///Internal
function _valid_line(line,dim,eps=EPSILON) = function _valid_line(line,dim,eps=EPSILON) =
is_matrix(line,2,dim) is_matrix(line,2,dim)
&& norm(line[1]-line[0])>eps*max(norm(line[1]),norm(line[0])); && norm(line[1]-line[0])>eps*max(norm(line[1]),norm(line[0]));
@ -45,16 +45,16 @@ function _valid_plane(p, eps=EPSILON) = is_vector(p,4) && ! approx(norm(p),0,eps
/// Internal Function: point_left_of_line2d() /// Internal Function: point_left_of_line2d()
// Usage: /// Usage:
// pt = point_left_of_line2d(point, line); /// pt = point_left_of_line2d(point, line);
/// Topics: Geometry, Points, Lines /// Topics: Geometry, Points, Lines
// Description: /// Description:
// Return >0 if point is left of the line defined by `line`. /// Return >0 if point is left of the line defined by `line`.
// Return =0 if point is on the line. /// Return =0 if point is on the line.
// Return <0 if point is right of the line. /// Return <0 if point is right of the line.
// Arguments: /// Arguments:
// point = The point to check position of. /// point = The point to check position of.
// line = Array of two points forming the line segment to test against. /// line = Array of two points forming the line segment to test against.
function _point_left_of_line2d(point, line) = function _point_left_of_line2d(point, line) =
assert( is_vector(point,2) && is_vector(line*point, 2), "Improper input." ) assert( is_vector(point,2) && is_vector(line*point, 2), "Improper input." )
cross(line[0]-point, line[1]-line[0]); cross(line[0]-point, line[1]-line[0]);
@ -372,7 +372,7 @@ function is_coplanar(points, eps=EPSILON) =
// Description: // Description:
// Generates the normalized cartesian equation of a plane from three 3d points. // Generates the normalized cartesian equation of a plane from three 3d points.
// Returns [A,B,C,D] where Ax + By + Cz = D is the equation of a plane. // Returns [A,B,C,D] where Ax + By + Cz = D is the equation of a plane.
// Returns [], if the points are collinear. // Returns undef, if the points are collinear.
// Arguments: // Arguments:
// p1 = The first point on the plane. // p1 = The first point on the plane.
// p2 = The second point on the plane. // p2 = The second point on the plane.
@ -383,7 +383,7 @@ function plane3pt(p1, p2, p3) =
let( let(
crx = cross(p3-p1, p2-p1), crx = cross(p3-p1, p2-p1),
nrm = norm(crx) nrm = norm(crx)
) approx(nrm,0) ? [] : ) approx(nrm,0) ? undef :
concat(crx, crx*p1)/nrm; concat(crx, crx*p1)/nrm;
@ -480,24 +480,23 @@ function _covariance_evec_eval(points) =
// Description: // Description:
// Given a list of 3 or more coplanar 3D points, returns the coefficients of the normalized cartesian equation of a plane, // Given a list of 3 or more coplanar 3D points, returns the coefficients of the normalized cartesian equation of a plane,
// that is [A,B,C,D] where Ax+By+Cz=D is the equation of the plane and norm([A,B,C])=1. // that is [A,B,C,D] where Ax+By+Cz=D is the equation of the plane and norm([A,B,C])=1.
// If `fast` is false and the points in the list are collinear or not coplanar, then [] is returned. // If `fast` is false and the points in the list are collinear or not coplanar, then `undef` is returned.
// If `fast` is true, the polygon coplanarity check is skipped and a best fitted plane is returned. // If `fast` is true, the polygon coplanarity check is skipped and a best fitting plane is returned.
// Arguments: // Arguments:
// points = The list of points to find the plane of. // points = The list of points to find the plane of.
// fast = If true, don't verify the point coplanarity. Default: false // fast = If true, don't verify the point coplanarity. Default: false
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9) // eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
// Example(3D): // Example(3D):
// xyzpath = rot(45, v=[-0.3,1,0], p=path3d(star(n=6,id=70,d=100), 70)); // points = rot(45, v=[-0.3,1,0], p=path3d(random_points(25,2,scale=55,seed=47), 70));
// plane = plane_from_points(xyzpath); // plane = plane_from_points(points);
// #stroke(xyzpath,closed=true); // #move_copies(points)sphere(d=3);
// cp = centroid(xyzpath); // cp = mean(points);
// move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow(); // move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow(50);
function plane_from_points(points, fast=false, eps=EPSILON) = function plane_from_points(points, fast=false, eps=EPSILON) =
assert( is_path(points,dim=3), "Improper 3d point list." ) assert( is_path(points,dim=3), "Improper 3d point list." )
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) == 3 len(points) == 3
? let( plane = plane3pt(points[0],points[1],points[2]) ) ? plane3pt(points[0],points[1],points[2])
plane==[] ? [] : plane
: let( : let(
covmix = _covariance_evec_eval(points), covmix = _covariance_evec_eval(points),
pm = covmix[0], pm = covmix[0],
@ -517,7 +516,7 @@ function plane_from_points(points, fast=false, eps=EPSILON) =
// Given a 3D planar polygon, returns the normalized cartesian equation of its plane. // Given a 3D planar polygon, returns the normalized cartesian equation of its plane.
// Returns [A,B,C,D] where Ax+By+Cz=D is the equation of the plane where norm([A,B,C])=1. // Returns [A,B,C,D] where Ax+By+Cz=D is the equation of the plane where norm([A,B,C])=1.
// If not all the points in the polygon are coplanar, then [] is returned. // If not all the points in the polygon are coplanar, then [] is returned.
// If `fast` is false and the points in the list are collinear or not coplanar, then [] is returned. // If `fast` is false and the points in the list are collinear or not coplanar, then `undef` is returned.
// if `fast` is true, then the coplanarity test is skipped and a plane passing through 3 non-collinear arbitrary points is returned. // if `fast` is true, then the coplanarity test is skipped and a plane passing through 3 non-collinear arbitrary points is returned.
// Arguments: // Arguments:
// poly = The planar 3D polygon to find the plane of. // poly = The planar 3D polygon to find the plane of.
@ -526,20 +525,20 @@ function plane_from_points(points, fast=false, eps=EPSILON) =
// Example(3D): // Example(3D):
// xyzpath = rot(45, v=[0,1,0], p=path3d(star(n=5,step=2,d=100), 70)); // xyzpath = rot(45, v=[0,1,0], p=path3d(star(n=5,step=2,d=100), 70));
// plane = plane_from_polygon(xyzpath); // plane = plane_from_polygon(xyzpath);
// #stroke(xyzpath,closed=true); // #stroke(xyzpath,closed=true,width=3);
// cp = centroid(xyzpath); // cp = polygon_centroid(xyzpath);
// move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow(); // move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow(45);
function plane_from_polygon(poly, fast=false, eps=EPSILON) = function plane_from_polygon(poly, fast=false, eps=EPSILON) =
assert( is_path(poly,dim=3), "Invalid polygon." ) assert( is_path(poly,dim=3), "Invalid polygon." )
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( let(
poly_normal = polygon_normal(poly) poly_normal = polygon_normal(poly)
) )
is_undef(poly_normal) ? [] : is_undef(poly_normal) ? undef :
let( let(
plane = plane_from_normal(poly_normal, poly[0]) plane = plane_from_normal(poly_normal, poly[0])
) )
fast? plane: are_points_on_plane(poly, plane, eps=eps)? plane: []; fast? plane: are_points_on_plane(poly, plane, eps=eps)? plane: undef;
// Function: plane_normal() // Function: plane_normal()
@ -876,11 +875,11 @@ function plane_line_angle(plane, line) =
// points = move([10,20,30], p=yrot(25, p=path3d(circle(d=100, $fn=36)))); // points = move([10,20,30], p=yrot(25, p=path3d(circle(d=100, $fn=36))));
// plane = plane_from_normal([1,0,1]); // plane = plane_from_normal([1,0,1]);
// proj = plane_closest_point(plane,points); // proj = plane_closest_point(plane,points);
// color("red") move_copies(points) sphere(d=2,$fn=12); // color("red") move_copies(points) sphere(d=4,$fn=12);
// color("blue") move_copies(proj) sphere(d=2,$fn=12); // color("blue") move_copies(proj) sphere(d=4,$fn=12);
// move(centroid(proj)) { // move(polygon_centroid(proj)) {
// rot(from=UP,to=plane_normal(plane)) { // rot(from=UP,to=plane_normal(plane)) {
// anchor_arrow(30); // anchor_arrow(50);
// %cube([120,150,0.1],center=true); // %cube([120,150,0.1],center=true);
// } // }
// } // }
@ -1249,40 +1248,40 @@ function circle_point_tangents(r, d, cp, pt) =
// r2 = Radius of the second circle. // r2 = Radius of the second circle.
// d1 = Diameter of the first circle. // d1 = Diameter of the first circle.
// d2 = Diameter of the second circle. // d2 = Diameter of the second circle.
// Example(2D): Four tangents, first in green, second in black, third in blue, last in red. // Example(2D,NoAxes): Four tangents, first in green, second in black, third in blue, last in red.
// $fn=32; // $fn=32;
// c1 = [3,4]; r1 = 2; // c1 = [3,4]; r1 = 2;
// c2 = [7,10]; r2 = 3; // c2 = [7,10]; r2 = 3;
// pts = circle_circle_tangents(c1,r1,c2,r2); // pts = circle_circle_tangents(c1,r1,c2,r2);
// move(c1) stroke(circle(r=r1), width=.1, closed=true); // move(c1) stroke(circle(r=r1), width=0.2, closed=true);
// move(c2) stroke(circle(r=r2), width=.1, closed=true); // move(c2) stroke(circle(r=r2), width=0.2, closed=true);
// colors = ["green","black","blue","red"]; // colors = ["green","black","blue","red"];
// for(i=[0:len(pts)-1]) color(colors[i]) stroke(pts[i],width=.1); // for(i=[0:len(pts)-1]) color(colors[i]) stroke(pts[i],width=0.2);
// Example(2D): Circles overlap so only exterior tangents exist. // Example(2D,NoAxes): Circles overlap so only exterior tangents exist.
// $fn=32; // $fn=32;
// c1 = [4,4]; r1 = 3; // c1 = [4,4]; r1 = 3;
// c2 = [7,7]; r2 = 2; // c2 = [7,7]; r2 = 2;
// pts = circle_circle_tangents(c1,r1,c2,r2); // pts = circle_circle_tangents(c1,r1,c2,r2);
// move(c1) stroke(circle(r=r1), width=.1, closed=true); // move(c1) stroke(circle(r=r1), width=0.2, closed=true);
// move(c2) stroke(circle(r=r2), width=.1, closed=true); // move(c2) stroke(circle(r=r2), width=0.2, closed=true);
// colors = ["green","black","blue","red"]; // colors = ["green","black","blue","red"];
// for(i=[0:len(pts)-1]) color(colors[i]) stroke(pts[i],width=.1); // for(i=[0:len(pts)-1]) color(colors[i]) stroke(pts[i],width=0.2);
// Example(2D): Circles are tangent. Only exterior tangents are returned. The degenerate internal tangent is not returned. // Example(2D,NoAxes): Circles are tangent. Only exterior tangents are returned. The degenerate internal tangent is not returned.
// $fn=32; // $fn=32;
// c1 = [4,4]; r1 = 4; // c1 = [4,4]; r1 = 4;
// c2 = [4,10]; r2 = 2; // c2 = [4,10]; r2 = 2;
// pts = circle_circle_tangents(c1,r1,c2,r2); // pts = circle_circle_tangents(c1,r1,c2,r2);
// move(c1) stroke(circle(r=r1), width=.1, closed=true); // move(c1) stroke(circle(r=r1), width=0.2, closed=true);
// move(c2) stroke(circle(r=r2), width=.1, closed=true); // move(c2) stroke(circle(r=r2), width=0.2, closed=true);
// colors = ["green","black","blue","red"]; // colors = ["green","black","blue","red"];
// for(i=[0:1:len(pts)-1]) color(colors[i]) stroke(pts[i],width=.1); // for(i=[0:1:len(pts)-1]) color(colors[i]) stroke(pts[i],width=0.2);
// Example(2D): One circle is inside the other: no tangents exist. If the interior circle is tangent the single degenerate tangent will not be returned. // Example(2D,NoAxes): One circle is inside the other: no tangents exist. If the interior circle is tangent the single degenerate tangent will not be returned.
// $fn=32; // $fn=32;
// c1 = [4,4]; r1 = 4; // c1 = [4,4]; r1 = 4;
// c2 = [5,5]; r2 = 2; // c2 = [5,5]; r2 = 2;
// pts = circle_circle_tangents(c1,r1,c2,r2); // pts = circle_circle_tangents(c1,r1,c2,r2);
// move(c1) stroke(circle(r=r1), width=.1, closed=true); // move(c1) stroke(circle(r=r1), width=0.2, closed=true);
// move(c2) stroke(circle(r=r2), width=.1, closed=true); // move(c2) stroke(circle(r=r2), width=0.2, closed=true);
// echo(pts); // Returns [] // echo(pts); // Returns []
function circle_circle_tangents(c1,r1,c2,r2,d1,d2) = function circle_circle_tangents(c1,r1,c2,r2,d1,d2) =
assert( is_path([c1,c2],dim=2), "Invalid center point(s)." ) assert( is_path([c1,c2],dim=2), "Invalid center point(s)." )
@ -1359,7 +1358,8 @@ function noncollinear_triple(points,error=true,eps=EPSILON) =
// Topics: Geometry, Polygons, Area // Topics: Geometry, Polygons, Area
// Description: // Description:
// Given a 2D or 3D simple planar polygon, returns the area of that polygon. // Given a 2D or 3D simple planar polygon, returns the area of that polygon.
// If the polygon is self-intersecting or non-planar, the result is `undef.` // If the polygon is non-planar the result is `undef.` If the polygon is self-intersecting
// then the return will be a meaningless number.
// When `signed` is true and the polygon is 2d, a signed area is returned: a positive area indicates a counter-clockwise polygon. // When `signed` is true and the polygon is 2d, a signed area is returned: a positive area indicates a counter-clockwise polygon.
// The area of 3d polygons is always nonnegative. // The area of 3d polygons is always nonnegative.
// Arguments: // Arguments:
@ -1372,8 +1372,8 @@ function polygon_area(poly, signed=false) =
? let( total = sum([for(i=[1:1:len(poly)-2]) cross(poly[i]-poly[0],poly[i+1]-poly[0]) ])/2 ) ? let( total = sum([for(i=[1:1:len(poly)-2]) cross(poly[i]-poly[0],poly[i+1]-poly[0]) ])/2 )
signed ? total : abs(total) signed ? total : abs(total)
: let( plane = plane_from_polygon(poly) ) : let( plane = plane_from_polygon(poly) )
plane==[]? undef : is_undef(plane) ? undef :
let( let( f=echo(plane=plane),
n = plane_normal(plane), n = plane_normal(plane),
total = total =
-sum([ for(i=[1:1:len(poly)-2]) -sum([ for(i=[1:1:len(poly)-2])
@ -1383,9 +1383,9 @@ function polygon_area(poly, signed=false) =
signed ? total : abs(total); signed ? total : abs(total);
// Function: centroid() // Function: polygon_centroid()
// Usage: // Usage:
// cpt = centroid(poly); // cpt = polygon_centroid(poly);
// Topics: Geometry, Polygons, Centroid // Topics: Geometry, Polygons, Centroid
// Description: // Description:
// Given a simple 2D polygon, returns the 2D coordinates of the polygon's centroid. // Given a simple 2D polygon, returns the 2D coordinates of the polygon's centroid.
@ -1395,13 +1395,13 @@ function polygon_area(poly, signed=false) =
// Arguments: // Arguments:
// poly = Points of the polygon from which the centroid is calculated. // poly = Points of the polygon from which the centroid is calculated.
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9) // eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
function centroid(poly, eps=EPSILON) = function polygon_centroid(poly, eps=EPSILON) =
assert( is_path(poly,dim=[2,3]), "The input must be a 2D or 3D polygon." ) assert( is_path(poly,dim=[2,3]), "The input must be a 2D or 3D polygon." )
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( let(
n = len(poly[0])==2 ? 1 : n = len(poly[0])==2 ? 1 :
let( plane = plane_from_points(poly, fast=true) ) let( plane = plane_from_points(poly, fast=false) ,dd=echo(p=plane))
assert( !is_undef(plane), "The polygon must be planar." ) assert(!is_undef(plane), "The polygon must be planar." )
plane_normal(plane), plane_normal(plane),
v0 = poly[0] , v0 = poly[0] ,
val = sum([ val = sum([
@ -1591,10 +1591,10 @@ function point_in_polygon(point, poly, nonzero=false, eps=EPSILON) =
// poly = Array of vertices for the polygon. // poly = Array of vertices for the polygon.
// ind = A list indexing the vertices of the polygon in `poly`. // ind = A list indexing the vertices of the polygon in `poly`.
// eps = A maximum tolerance in geometrical tests. Default: EPSILON // eps = A maximum tolerance in geometrical tests. Default: EPSILON
// Example(2D): // Example(2D,NoAxes):
// poly = star(id=10, od=15,n=11); // poly = star(id=10, od=15,n=11);
// tris = polygon_triangulate(poly); // tris = polygon_triangulate(poly);
// for(tri=tris) stroke(select(poly,tri), width=.1, closed=true); // for(tri=tris) stroke(select(poly,tri), width=.2, closed=true);
// Example(3D): // Example(3D):
// include <BOSL2/polyhedra.scad> // include <BOSL2/polyhedra.scad>
// vnf = regular_polyhedron_info(name="dodecahedron",side=5,info="vnf"); // vnf = regular_polyhedron_info(name="dodecahedron",side=5,info="vnf");
@ -1696,6 +1696,7 @@ function _is_cw2(a,b,c,eps=EPSILON) = cross(a-c,b-c)<eps*norm(a-c)*norm(b-c);
// For algorithm see 2.07 here: http://www.faqs.org/faqs/graphics/algorithms-faq/ // For algorithm see 2.07 here: http://www.faqs.org/faqs/graphics/algorithms-faq/
function is_polygon_clockwise(poly) = function is_polygon_clockwise(poly) =
let( poly=deduplicate(poly))
assert(is_path(poly,dim=2), "Input should be a 2d path") assert(is_path(poly,dim=2), "Input should be a 2d path")
let( let(
minx = min(poly*[1,0]), minx = min(poly*[1,0]),
@ -1800,7 +1801,7 @@ function polygon_shift(poly, i) =
// move_copies(concat(circ,pent)) circle(r=.1,$fn=32); // move_copies(concat(circ,pent)) circle(r=.1,$fn=32);
// color("red") move_copies([pent[0],circ[0]]) circle(r=.1,$fn=32); // color("red") move_copies([pent[0],circ[0]]) circle(r=.1,$fn=32);
// color("blue") translate(reindexed[0])circle(r=.1,$fn=32); // color("blue") translate(reindexed[0])circle(r=.1,$fn=32);
function reindex_polygon(reference, poly, return_error=false) = function old_reindex_polygon(reference, poly, return_error=false) =
assert(is_path(reference) && is_path(poly,dim=len(reference[0])), assert(is_path(reference) && is_path(poly,dim=len(reference[0])),
"Invalid polygon(s) or incompatible dimensions. " ) "Invalid polygon(s) or incompatible dimensions. " )
assert(len(reference)==len(poly), "The polygons must have the same length.") assert(len(reference)==len(poly), "The polygons must have the same length.")
@ -1819,6 +1820,27 @@ function reindex_polygon(reference, poly, return_error=false) =
) )
return_error? [optimal_poly, min(poly*(I*poly)-2*val)] : return_error? [optimal_poly, min(poly*(I*poly)-2*val)] :
optimal_poly; optimal_poly;
function reindex_polygon(reference, poly, return_error=false) =
assert(is_path(reference) && is_path(poly,dim=len(reference[0])),
"Invalid polygon(s) or incompatible dimensions. " )
assert(len(reference)==len(poly), "The polygons must have the same length.")
let(
dim = len(reference[0]),
N = len(reference),
fixpoly = dim != 2? poly :
is_polygon_clockwise(reference)
? clockwise_polygon(poly)
: ccw_polygon(poly),
I = [for(i=reference) 1],
val = [ for(k=[0:N-1])
[for(i=[0:N-1])
norm(reference[i]-fixpoly[(i+k)%N]) ] ]*I,
min_ind = min_index(val),
optimal_poly = polygon_shift(fixpoly, min_ind)
)
return_error? [optimal_poly, val[min_ind]] :
optimal_poly;
// Function: align_polygon() // Function: align_polygon()
@ -1841,7 +1863,7 @@ function reindex_polygon(reference, poly, return_error=false) =
// hexagon = subdivide_path(hexagon(side=2.7),60); // hexagon = subdivide_path(hexagon(side=2.7),60);
// color("red") move_copies(scale(1.4,p=align_polygon(pentagon,hexagon,[0:10:359]))) circle(r=.1); // color("red") move_copies(scale(1.4,p=align_polygon(pentagon,hexagon,[0:10:359]))) circle(r=.1);
// move_copies(concat(pentagon,hexagon))circle(r=.1); // move_copies(concat(pentagon,hexagon))circle(r=.1);
function align_polygon(reference, poly, angles, cp) = function old_align_polygon(reference, poly, angles, cp) =
assert(is_path(reference,dim=2) && is_path(poly,dim=2), assert(is_path(reference,dim=2) && is_path(poly,dim=2),
"Invalid polygon(s). " ) "Invalid polygon(s). " )
assert(len(reference)==len(poly), "The polygons must have the same length.") assert(len(reference)==len(poly), "The polygons must have the same length.")
@ -1858,6 +1880,27 @@ function align_polygon(reference, poly, angles, cp) =
], ],
best = min_index(subindex(alignments,1)) best = min_index(subindex(alignments,1))
) alignments[best][0]; ) alignments[best][0];
function align_polygon(reference, poly, angles, cp) =
assert(is_path(reference,dim=2) && is_path(poly,dim=2),
"Invalid polygon(s). " )
assert(len(reference)==len(poly), "The polygons must have the same length.")
assert( (is_vector(angles) && len(angles)>0) || valid_range(angles),
"The `angle` parameter must be a range or a non void list of numbers.")
let( // alignments is a vector of entries of the form: [polygon, error]
alignments = [
for(angle=angles)
reindex_polygon(
reference,
zrot(angle,p=poly,cp=cp),
return_error=true
)
],
scores = subindex(alignments,1),
minscore = min(scores),
minind = [for(i=idx(scores)) if (scores[i]<minscore+EPSILON) i],
f=echo(best_angles = select(list(angles), minind)),
best = minind[0]
) alignments[best][0];
// Function: are_polygons_equal() // Function: are_polygons_equal()
@ -1925,11 +1968,10 @@ function __is_polygon_in_list(poly, polys, i) =
// poly = Polygon to check. // poly = Polygon to check.
// eps = Tolerance for the collinearity test. Default: EPSILON. // eps = Tolerance for the collinearity test. Default: EPSILON.
// Example: // Example:
// test1 = is_polygon_convex(circle(d=50)); // Returns: true // test1 = is_polygon_convex(circle(d=50)); // Returns: true
// test2 = is_polygon_convex(rot([50,120,30], p=path3d(circle(1,$fn=50)))); // Returns: true // test2 = is_polygon_convex(rot([50,120,30], p=path3d(circle(1,$fn=50)))); // Returns: true
// Example:
// spiral = [for (i=[0:36]) let(a=-i*10) (10+i)*[cos(a),sin(a)]]; // spiral = [for (i=[0:36]) let(a=-i*10) (10+i)*[cos(a),sin(a)]];
// test = is_polygon_convex(spiral); // Returns: false // test = is_polygon_convex(spiral); // Returns: false
function is_polygon_convex(poly,eps=EPSILON) = function is_polygon_convex(poly,eps=EPSILON) =
assert(is_path(poly), "The input should be a 2D or 3D polygon." ) assert(is_path(poly), "The input should be a 2D or 3D polygon." )
let( let(
@ -2178,16 +2220,13 @@ function _support_diff(p1,p2,d) =
// long = if true return the "long way" around, with the angle in [180,360]. Default: false // long = if true return the "long way" around, with the angle in [180,360]. Default: false
// Example: // Example:
// info = rot_decode(rot(45)); // info = rot_decode(rot(45));
// // Returns: [45, [0,0,1], [0,0,0], [0,0,0]] // // Returns: [45, [0,0,1], [0,0,0], [0,0,0]]
// Example:
// info = rot_decode(rot(a=37, v=[1,2,3], cp=[4,3,-7]))); // info = rot_decode(rot(a=37, v=[1,2,3], cp=[4,3,-7])));
// // Returns: [37, [0.26, 0.53, 0.80], [4.8, 4.6, -4.6], [0,0,0]] // // Returns: [37, [0.26, 0.53, 0.80], [4.8, 4.6, -4.6], [0,0,0]]
// Example:
// info = rot_decode(left(12)*xrot(-33)); // info = rot_decode(left(12)*xrot(-33));
// // Returns: [33, [-1,0,0], [0,0,0], [-12,0,0]] // // Returns: [33, [-1,0,0], [0,0,0], [-12,0,0]]
// Example:
// info = rot_decode(translate([3,4,5])); // info = rot_decode(translate([3,4,5]));
// // Returns: [0, [0,0,1], [0,0,0], [3,4,5]] // // Returns: [0, [0,0,1], [0,0,0], [3,4,5]]
function rot_decode(M,long=false) = function rot_decode(M,long=false) =
assert(is_matrix(M,4,4) && approx(M[3],[0,0,0,1]), "Input matrix must be a 4x4 matrix representing a 3d transformation") assert(is_matrix(M,4,4) && approx(M[3],[0,0,0,1]), "Input matrix must be a 4x4 matrix representing a 3d transformation")
let(R = submatrix(M,[0:2],[0:2])) let(R = submatrix(M,[0:2],[0:2]))

View file

@ -98,7 +98,7 @@ function _is_cw(a,b,c,all) =
// Example(2D): // Example(2D):
// pts = [[-10,-10], [0,10], [10,10], [12,-10]]; // pts = [[-10,-10], [0,10], [10,10], [12,-10]];
// path = hull2d_path(pts); // path = hull2d_path(pts);
// move_copies(pts) color("red") sphere(1); // move_copies(pts) color("red") circle(1,$fn=12);
// polygon(points=pts, paths=[path]); // polygon(points=pts, paths=[path]);
// //
// Code based on this method: // Code based on this method:

View file

@ -239,7 +239,8 @@ function _path_self_intersections(path, closed=true, eps=EPSILON) =
isect = _general_line_intersection([a1,a2],[b1,b2],eps=eps) isect = _general_line_intersection([a1,a2],[b1,b2],eps=eps)
) )
if (isect if (isect
&& isect[1]> (i==0 && !closed? -eps: 0) // && isect[1]> (i==0 && !closed? -eps: 0) // Apparently too strict
&& isect[1]>=-eps
&& isect[1]<= 1+eps && isect[1]<= 1+eps
&& isect[2]> 0 && isect[2]> 0
&& isect[2]<= 1+eps) && isect[2]<= 1+eps)
@ -380,9 +381,9 @@ function subdivide_path(path, N, refine, closed=true, exact=true, method="length
// Example(2D): // Example(2D):
// path = pentagon(d=100); // path = pentagon(d=100);
// spath = subdivide_long_segments(path, 10, closed=true); // spath = subdivide_long_segments(path, 10, closed=true);
// stroke(path); // stroke(path,width=2,closed=true);
// color("lightgreen") move_copies(path) circle(d=5,$fn=12); // color("red") move_copies(path) circle(d=9,$fn=12);
// color("blue") move_copies(spath) circle(d=3,$fn=12); // color("blue") move_copies(spath) circle(d=5,$fn=12);
function subdivide_long_segments(path, maxlen, closed=false) = function subdivide_long_segments(path, maxlen, closed=false) =
assert(is_path(path)) assert(is_path(path))
assert(is_finite(maxlen)) assert(is_finite(maxlen))
@ -496,20 +497,20 @@ function path_closest_point(path, pt) =
// path = path to find the tagent vectors for // path = path to find the tagent vectors for
// closed = set to true of the path is closed. Default: false // closed = set to true of the path is closed. Default: false
// uniform = set to false to correct for non-uniform sampling. Default: true // uniform = set to false to correct for non-uniform sampling. Default: true
// Example(3D): A shape with non-uniform sampling gives distorted derivatives that may be undesirable. Note that derivatives tilt towards the long edges of the rectangle. // Example(2D): A shape with non-uniform sampling gives distorted derivatives that may be undesirable. Note that derivatives tilt towards the long edges of the rectangle.
// rect = square([10,3]); // rect = square([10,3]);
// tangents = path_tangents(rect,closed=true); // tangents = path_tangents(rect,closed=true);
// stroke(rect,closed=true, width=0.1); // stroke(rect,closed=true, width=0.25);
// color("purple") // color("purple")
// for(i=[0:len(tangents)-1]) // for(i=[0:len(tangents)-1])
// stroke([rect[i]-tangents[i], rect[i]+tangents[i]],width=.1, endcap2="arrow2"); // stroke([rect[i]-tangents[i], rect[i]+tangents[i]],width=.25, endcap2="arrow2");
// Example(3D): Setting uniform to false corrects the distorted derivatives for this example: // Example(2D): Setting uniform to false corrects the distorted derivatives for this example:
// rect = square([10,3]); // rect = square([10,3]);
// tangents = path_tangents(rect,closed=true,uniform=false); // tangents = path_tangents(rect,closed=true,uniform=false);
// stroke(rect,closed=true, width=0.1); // stroke(rect,closed=true, width=0.25);
// color("purple") // color("purple")
// for(i=[0:len(tangents)-1]) // for(i=[0:len(tangents)-1])
// stroke([rect[i]-tangents[i], rect[i]+tangents[i]],width=.1, endcap2="arrow2"); // stroke([rect[i]-tangents[i], rect[i]+tangents[i]],width=.25, endcap2="arrow2");
function path_tangents(path, closed=false, uniform=true) = function path_tangents(path, closed=false, uniform=true) =
assert(is_path(path)) assert(is_path(path))
!uniform ? [for(t=deriv(path,closed=closed, h=path_segment_lengths(path,closed))) unit(t)] !uniform ? [for(t=deriv(path,closed=closed, h=path_segment_lengths(path,closed))) unit(t)]
@ -881,10 +882,10 @@ function _path_cuts_dir(path, cuts, closed=false, eps=1e-2) =
// path = The original path to split. // path = The original path to split.
// cutdist = Distance or list of distances where path is cut // cutdist = Distance or list of distances where path is cut
// closed = If true, treat the path as a closed polygon. // closed = If true, treat the path as a closed polygon.
// Example(2D): // Example(2D,NoAxes):
// path = circle(d=100); // path = circle(d=100);
// segs = path_cut(path, [50, 200], closed=true); // segs = path_cut(path, [50, 200], closed=true);
// rainbow(segs) stroke($item); // rainbow(segs) stroke($item, endcaps="butt", width=3);
function path_cut(path,cutdist,closed) = function path_cut(path,cutdist,closed) =
is_num(cutdist) ? path_cut(path,[cutdist],closed) : is_num(cutdist) ? path_cut(path,[cutdist],closed) :
assert(is_vector(cutdist)) assert(is_vector(cutdist))
@ -947,10 +948,10 @@ function _cut_to_seg_u_form(pathcut, path, closed) =
// path = The path to split up. // path = The path to split up.
// closed = If true, treat path as a closed polygon. Default: true // closed = If true, treat path as a closed polygon. Default: true
// eps = Acceptable variance. Default: `EPSILON` (1e-9) // eps = Acceptable variance. Default: `EPSILON` (1e-9)
// Example(2D): // Example(2D,NoAxes):
// path = [ [-100,100], [0,-50], [100,100], [100,-100], [0,50], [-100,-100] ]; // path = [ [-100,100], [0,-50], [100,100], [100,-100], [0,50], [-100,-100] ];
// paths = split_path_at_self_crossings(path); // paths = split_path_at_self_crossings(path);
// rainbow(paths) stroke($item, closed=false, width=2); // rainbow(paths) stroke($item, closed=false, width=3);
function split_path_at_self_crossings(path, closed=true, eps=EPSILON) = function split_path_at_self_crossings(path, closed=true, eps=EPSILON) =
let( let(
path = cleanup_path(path, eps=eps), path = cleanup_path(path, eps=eps),
@ -1011,25 +1012,25 @@ function _tag_self_crossing_subpaths(path, nonzero, closed=true, eps=EPSILON) =
// path = The path to split up. // path = The path to split up.
// nonzero = If true use the nonzero method for checking if a point is in a polygon. Otherwise use the even-odd method. Default: false // nonzero = If true use the nonzero method for checking if a point is in a polygon. Otherwise use the even-odd method. Default: false
// eps = The epsilon error value to determine whether two points coincide. Default: `EPSILON` (1e-9) // eps = The epsilon error value to determine whether two points coincide. Default: `EPSILON` (1e-9)
// Example(2D): This cross-crossing polygon breaks up into its 3 components (regardless of the value of nonzero). // Example(2D,NoAxes): This cross-crossing polygon breaks up into its 3 components (regardless of the value of nonzero).
// path = [ // path = [
// [-100,100], [0,-50], [100,100], // [-100,100], [0,-50], [100,100],
// [100,-100], [0,50], [-100,-100] // [100,-100], [0,50], [-100,-100]
// ]; // ];
// splitpaths = polygon_parts(path); // splitpaths = polygon_parts(path);
// rainbow(splitpaths) stroke($item, closed=true, width=3); // rainbow(splitpaths) stroke($item, closed=true, width=3);
// Example(2D): With nonzero=false you get even-odd mode which matches OpenSCAD, so the pentagram breaks apart into its five points. // Example(2D,NoAxes): With nonzero=false you get even-odd mode which matches OpenSCAD, so the pentagram breaks apart into its five points.
// pentagram = turtle(["move",100,"left",144], repeat=4); // pentagram = turtle(["move",100,"left",144], repeat=4);
// left(100)polygon(pentagram); // left(100)polygon(pentagram);
// rainbow(polygon_parts(pentagram,nonzero=false)) // rainbow(polygon_parts(pentagram,nonzero=false))
// stroke($item,closed=true); // stroke($item,closed=true,width=2.5);
// Example(2D): With nonzero=true you get only the outer perimeter. You can use this to create the polygon using the nonzero method, which is not supported by OpenSCAD. // Example(2D,NoAxes): With nonzero=true you get only the outer perimeter. You can use this to create the polygon using the nonzero method, which is not supported by OpenSCAD.
// pentagram = turtle(["move",100,"left",144], repeat=4); // pentagram = turtle(["move",100,"left",144], repeat=4);
// outside = polygon_parts(pentagram,nonzero=true); // outside = polygon_parts(pentagram,nonzero=true);
// left(100)region(outside); // left(100)region(outside);
// rainbow(outside) // rainbow(outside)
// stroke($item,closed=true); // stroke($item,closed=true,width=2.5);
// Example(2D): // Example(2D,NoAxes):
// N=12; // N=12;
// ang=360/N; // ang=360/N;
// sr=10; // sr=10;
@ -1042,19 +1043,19 @@ function _tag_self_crossing_subpaths(path, nonzero, closed=true, eps=EPSILON) =
// "move", sr]); // "move", sr]);
// stroke(path, width=.3); // stroke(path, width=.3);
// right(20)rainbow(polygon_parts(path)) polygon($item); // right(20)rainbow(polygon_parts(path)) polygon($item);
// Example(2D): overlapping path segments disappear // Example(2D,NoAxes): overlapping path segments disappear
// path = [[0,0], [10,0], [10,10], [0,10],[0,20], [20,10],[10,10], [0,10],[0,0]]; // path = [[0,0], [10,0], [10,10], [0,10],[0,20], [20,10],[10,10], [0,10],[0,0]];
// stroke(path,width=0.3); // stroke(path,width=0.3);
// right(22)stroke(polygon_parts(path)[0], width=0.3, closed=true); // right(22)stroke(polygon_parts(path)[0], width=0.3, closed=true);
// Example(2D): Path segments disappear outside as well // Example(2D,NoAxes): Path segments disappear outside as well
// path = turtle(["repeat", 3, ["move", 17, "left", "move", 10, "left", "move", 7, "left", "move", 10, "left"]]); // path = turtle(["repeat", 3, ["move", 17, "left", "move", 10, "left", "move", 7, "left", "move", 10, "left"]]);
// back(2)stroke(path,width=.3); // back(2)stroke(path,width=.5);
// fwd(12)rainbow(polygon_parts(path)) polygon($item); // fwd(12)rainbow(polygon_parts(path)) stroke($item, closed=true, width=0.5);
// Example(2D): This shape has six components // Example(2D,NoAxes): This shape has six components
// path = turtle(["repeat", 3, ["move", 15, "left", "move", 7, "left", "move", 10, "left", "move", 17, "left"]]); // path = turtle(["repeat", 3, ["move", 15, "left", "move", 7, "left", "move", 10, "left", "move", 17, "left"]]);
// polygon(path); // polygon(path);
// right(22)rainbow(polygon_parts(path)) polygon($item); // right(22)rainbow(polygon_parts(path)) polygon($item);
// Example(2D): when the loops of the shape overlap then nonzero gives a different result than the even-odd method. // Example(2D,NoAxes): When the loops of the shape overlap then nonzero gives a different result than the even-odd method.
// path = turtle(["repeat", 3, ["move", 15, "left", "move", 7, "left", "move", 10, "left", "move", 10, "left"]]); // path = turtle(["repeat", 3, ["move", 15, "left", "move", 7, "left", "move", 10, "left", "move", 10, "left"]]);
// polygon(path); // polygon(path);
// right(27)rainbow(polygon_parts(path)) polygon($item); // right(27)rainbow(polygon_parts(path)) polygon($item);

View file

@ -183,7 +183,7 @@ function __are_regions_equal(region1, region2, i) =
/// region = Region to test for crossings of. /// region = Region to test for crossings of.
/// closed = If true, treat path as a closed polygon. Default: true /// closed = If true, treat path as a closed polygon. Default: true
/// eps = Acceptable variance. Default: `EPSILON` (1e-9) /// eps = Acceptable variance. Default: `EPSILON` (1e-9)
function _path_region_intersections(path, region, closed=true, eps=EPSILON) = function old_path_region_intersections(path, region, closed=true, eps=EPSILON) =
let( let(
pathclosed = closed && !is_closed_path(path), pathclosed = closed && !is_closed_path(path),
pathlen = len(path), pathlen = len(path),
@ -218,6 +218,46 @@ function _path_region_intersections(path, region, closed=true, eps=EPSILON) =
); );
// find the intersection points of a path and the polygons of a
// region; only crossing intersections are caught, no collinear
// intersection is returned.
function _path_region_intersections(path, region, closed=true, eps=EPSILON) =
let( path = closed ? close_path(path,eps=eps) : path )
_sort_vectors(
[for(si = [0:1:len(path)-2]) let(
a1 = path[si],
a2 = path[si+1],
nrm = norm(a1-a2)
)
if( nrm>eps ) let( // ignore zero-length path edges
seg_normal = [-(a2-a1).y, (a2-a1).x]/nrm,
ref = a1*seg_normal
)
// `signs[j]` is the sign of the signed distance from
// poly vertex j to the line [a1,a2] where near zero
// distances are snapped to zero; poly edges
// with equal signs at its vertices cannot intersect
// the path edge [a1,a2] or they are collinear and
// further tests can be discarded.
for(poly=region) let(
poly = close_path(poly),
signs = [for(v=poly*seg_normal) v-ref> eps ? 1 : v-ref<-eps ? -1 : 0]
)
if(max(signs)>=0 && min(signs)<=0 ) // some edge edge intersects line [a1,a2]
for(j=[0:1:len(poly)-2])
if( signs[j]!=signs[j+1] ) let( // exclude non-crossing and collinear segments
b1 = poly[j],
b2 = poly[j+1],
isect = _general_line_intersection([a1,a2],[b1,b2],eps=eps)
)
if ( isect
&& isect[1]> (si==0 && !closed? -eps: 0)
&& isect[1]<= 1+eps
&& isect[2]> 0
&& isect[2]<= 1+eps )
[si,isect[1]]
]);
// Function: split_path_at_region_crossings() // Function: split_path_at_region_crossings()
// Usage: // Usage:

View file

@ -276,12 +276,12 @@
// Example(FlatSpin,VPD=32,VPT=[1.2,4.3,2]): Another "distance" example: // Example(FlatSpin,VPD=32,VPT=[1.2,4.3,2]): Another "distance" example:
// off = [0,2]; // off = [0,2];
// shape = turtle(["right",45,"move", "left",45,"move", "left",45, "move", "jump", [.5+sqrt(2)/2,8]]); // shape = turtle(["right",45,"move", "left",45,"move", "left",45, "move", "jump", [.5+sqrt(2)/2,8]]);
// rshape = rot(180,cp=centroid(shape)+off, p=shape); // rshape = rot(180,cp=polygon_centroid(shape)+off, p=shape);
// skin([shape,rshape],z=[0,4], method="distance",slices=10,refine=15); // skin([shape,rshape],z=[0,4], method="distance",slices=10,refine=15);
// Example(FlatSpin,VPD=32,VPT=[1.2,4.3,2]): Slightly shifting the profile changes the optimal linkage // Example(FlatSpin,VPD=32,VPT=[1.2,4.3,2]): Slightly shifting the profile changes the optimal linkage
// off = [0,1]; // off = [0,1];
// shape = turtle(["right",45,"move", "left",45,"move", "left",45, "move", "jump", [.5+sqrt(2)/2,8]]); // shape = turtle(["right",45,"move", "left",45,"move", "left",45, "move", "jump", [.5+sqrt(2)/2,8]]);
// rshape = rot(180,cp=centroid(shape)+off, p=shape); // rshape = rot(180,cp=polygon_centroid(shape)+off, p=shape);
// skin([shape,rshape],z=[0,4], method="distance",slices=10,refine=15); // skin([shape,rshape],z=[0,4], method="distance",slices=10,refine=15);
// Example(FlatSpin,VPD=444,VPT=[0,0,50]): This optimal solution doesn't look terrible: // Example(FlatSpin,VPD=444,VPT=[0,0,50]): This optimal solution doesn't look terrible:
// prof1 = path3d([[-50,-50], [-50,50], [50,50], [25,25], [50,0], [25,-25], [50,-50]]); // prof1 = path3d([[-50,-50], [-50,50], [50,50], [25,25], [50,0], [25,-25], [50,-50]]);
@ -1589,7 +1589,7 @@ function _skin_tangent_match(poly1, poly2) =
swap = len(poly1)>len(poly2), swap = len(poly1)>len(poly2),
big = swap ? poly1 : poly2, big = swap ? poly1 : poly2,
small = swap ? poly2 : poly1, small = swap ? poly2 : poly1,
curve_offset = centroid(small)-centroid(big), curve_offset = polygon_centroid(small)-polygon_centroid(big),
cutpts = [for(i=[0:len(small)-1]) _find_one_tangent(big, select(small,i,i+1),curve_offset=curve_offset)], cutpts = [for(i=[0:len(small)-1]) _find_one_tangent(big, select(small,i,i+1),curve_offset=curve_offset)],
shift = last(cutpts)+1, shift = last(cutpts)+1,
newbig = polygon_shift(big, shift), newbig = polygon_shift(big, shift),