mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 16:29:40 +00:00
doc tweaks
This commit is contained in:
parent
dbec028bd1
commit
9bb22dc7c5
6 changed files with 185 additions and 105 deletions
|
@ -103,7 +103,7 @@
|
|||
// 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
|
||||
// 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
|
||||
// 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]];
|
||||
|
@ -852,13 +852,13 @@ function _normal_segment(p1,p2) =
|
|||
// stroke(path,width=.2);
|
||||
// Example(2DMed): pentagonal spiral
|
||||
// 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`
|
||||
// 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.
|
||||
// path = turtle(["move","left",71,"scale",1.05],repeat=50);
|
||||
// stroke(path,width=.05);
|
||||
// stroke(path,width=.15);
|
||||
// Example(2D): Koch Snowflake
|
||||
// function koch_unit(depth) =
|
||||
// depth==0 ? ["move"] :
|
||||
|
|
179
geometry.scad
179
geometry.scad
|
@ -30,12 +30,12 @@ function is_point_on_line(point, line, bounded=false, eps=EPSILON) =
|
|||
point_line_distance(point, line, bounded)<eps;
|
||||
|
||||
|
||||
//Internal - distance from point `d` to the line passing through the origin with unit direction n
|
||||
//_dist2line works for any dimension
|
||||
///Internal - distance from point `d` to the line passing through the origin with unit direction n
|
||||
///_dist2line works for any dimension
|
||||
function _dist2line(d,n) = norm(d-(d * n) * n);
|
||||
|
||||
|
||||
//Internal
|
||||
///Internal
|
||||
function _valid_line(line,dim,eps=EPSILON) =
|
||||
is_matrix(line,2,dim)
|
||||
&& 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()
|
||||
// Usage:
|
||||
// pt = point_left_of_line2d(point, line);
|
||||
/// Usage:
|
||||
/// pt = point_left_of_line2d(point, line);
|
||||
/// Topics: Geometry, Points, Lines
|
||||
// Description:
|
||||
// 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 right of the line.
|
||||
// Arguments:
|
||||
// point = The point to check position of.
|
||||
// line = Array of two points forming the line segment to test against.
|
||||
/// Description:
|
||||
/// 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 right of the line.
|
||||
/// Arguments:
|
||||
/// point = The point to check position of.
|
||||
/// line = Array of two points forming the line segment to test against.
|
||||
function _point_left_of_line2d(point, line) =
|
||||
assert( is_vector(point,2) && is_vector(line*point, 2), "Improper input." )
|
||||
cross(line[0]-point, line[1]-line[0]);
|
||||
|
@ -372,7 +372,7 @@ function is_coplanar(points, eps=EPSILON) =
|
|||
// Description:
|
||||
// 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 [], if the points are collinear.
|
||||
// Returns undef, if the points are collinear.
|
||||
// Arguments:
|
||||
// p1 = The first point on the plane.
|
||||
// p2 = The second point on the plane.
|
||||
|
@ -383,7 +383,7 @@ function plane3pt(p1, p2, p3) =
|
|||
let(
|
||||
crx = cross(p3-p1, p2-p1),
|
||||
nrm = norm(crx)
|
||||
) approx(nrm,0) ? [] :
|
||||
) approx(nrm,0) ? undef :
|
||||
concat(crx, crx*p1)/nrm;
|
||||
|
||||
|
||||
|
@ -480,24 +480,23 @@ function _covariance_evec_eval(points) =
|
|||
// Description:
|
||||
// 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.
|
||||
// If `fast` is false and the points in the list are collinear or not coplanar, then [] is returned.
|
||||
// If `fast` is true, the polygon coplanarity check is skipped and a best fitted plane 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 fitting plane is returned.
|
||||
// Arguments:
|
||||
// points = The list of points to find the plane of.
|
||||
// fast = If true, don't verify the point coplanarity. Default: false
|
||||
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
|
||||
// Example(3D):
|
||||
// xyzpath = rot(45, v=[-0.3,1,0], p=path3d(star(n=6,id=70,d=100), 70));
|
||||
// plane = plane_from_points(xyzpath);
|
||||
// #stroke(xyzpath,closed=true);
|
||||
// cp = centroid(xyzpath);
|
||||
// move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow();
|
||||
// points = rot(45, v=[-0.3,1,0], p=path3d(random_points(25,2,scale=55,seed=47), 70));
|
||||
// plane = plane_from_points(points);
|
||||
// #move_copies(points)sphere(d=3);
|
||||
// cp = mean(points);
|
||||
// move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow(50);
|
||||
function plane_from_points(points, fast=false, eps=EPSILON) =
|
||||
assert( is_path(points,dim=3), "Improper 3d point list." )
|
||||
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
|
||||
len(points) == 3
|
||||
? let( plane = plane3pt(points[0],points[1],points[2]) )
|
||||
plane==[] ? [] : plane
|
||||
? plane3pt(points[0],points[1],points[2])
|
||||
: let(
|
||||
covmix = _covariance_evec_eval(points),
|
||||
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.
|
||||
// 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 `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.
|
||||
// Arguments:
|
||||
// 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):
|
||||
// xyzpath = rot(45, v=[0,1,0], p=path3d(star(n=5,step=2,d=100), 70));
|
||||
// plane = plane_from_polygon(xyzpath);
|
||||
// #stroke(xyzpath,closed=true);
|
||||
// cp = centroid(xyzpath);
|
||||
// move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow();
|
||||
// #stroke(xyzpath,closed=true,width=3);
|
||||
// cp = polygon_centroid(xyzpath);
|
||||
// move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow(45);
|
||||
function plane_from_polygon(poly, fast=false, eps=EPSILON) =
|
||||
assert( is_path(poly,dim=3), "Invalid polygon." )
|
||||
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
|
||||
let(
|
||||
poly_normal = polygon_normal(poly)
|
||||
)
|
||||
is_undef(poly_normal) ? [] :
|
||||
is_undef(poly_normal) ? undef :
|
||||
let(
|
||||
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()
|
||||
|
@ -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))));
|
||||
// plane = plane_from_normal([1,0,1]);
|
||||
// proj = plane_closest_point(plane,points);
|
||||
// color("red") move_copies(points) sphere(d=2,$fn=12);
|
||||
// color("blue") move_copies(proj) sphere(d=2,$fn=12);
|
||||
// move(centroid(proj)) {
|
||||
// color("red") move_copies(points) sphere(d=4,$fn=12);
|
||||
// color("blue") move_copies(proj) sphere(d=4,$fn=12);
|
||||
// move(polygon_centroid(proj)) {
|
||||
// rot(from=UP,to=plane_normal(plane)) {
|
||||
// anchor_arrow(30);
|
||||
// anchor_arrow(50);
|
||||
// %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.
|
||||
// d1 = Diameter of the first 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;
|
||||
// c1 = [3,4]; r1 = 2;
|
||||
// c2 = [7,10]; r2 = 3;
|
||||
// pts = circle_circle_tangents(c1,r1,c2,r2);
|
||||
// move(c1) stroke(circle(r=r1), width=.1, closed=true);
|
||||
// move(c2) stroke(circle(r=r2), width=.1, closed=true);
|
||||
// move(c1) stroke(circle(r=r1), width=0.2, closed=true);
|
||||
// move(c2) stroke(circle(r=r2), width=0.2, closed=true);
|
||||
// colors = ["green","black","blue","red"];
|
||||
// for(i=[0:len(pts)-1]) color(colors[i]) stroke(pts[i],width=.1);
|
||||
// Example(2D): Circles overlap so only exterior tangents exist.
|
||||
// for(i=[0:len(pts)-1]) color(colors[i]) stroke(pts[i],width=0.2);
|
||||
// Example(2D,NoAxes): Circles overlap so only exterior tangents exist.
|
||||
// $fn=32;
|
||||
// c1 = [4,4]; r1 = 3;
|
||||
// c2 = [7,7]; r2 = 2;
|
||||
// pts = circle_circle_tangents(c1,r1,c2,r2);
|
||||
// move(c1) stroke(circle(r=r1), width=.1, closed=true);
|
||||
// move(c2) stroke(circle(r=r2), width=.1, closed=true);
|
||||
// move(c1) stroke(circle(r=r1), width=0.2, closed=true);
|
||||
// move(c2) stroke(circle(r=r2), width=0.2, closed=true);
|
||||
// colors = ["green","black","blue","red"];
|
||||
// for(i=[0:len(pts)-1]) color(colors[i]) stroke(pts[i],width=.1);
|
||||
// Example(2D): Circles are tangent. Only exterior tangents are returned. The degenerate internal tangent is not returned.
|
||||
// for(i=[0:len(pts)-1]) color(colors[i]) stroke(pts[i],width=0.2);
|
||||
// Example(2D,NoAxes): Circles are tangent. Only exterior tangents are returned. The degenerate internal tangent is not returned.
|
||||
// $fn=32;
|
||||
// c1 = [4,4]; r1 = 4;
|
||||
// c2 = [4,10]; r2 = 2;
|
||||
// pts = circle_circle_tangents(c1,r1,c2,r2);
|
||||
// move(c1) stroke(circle(r=r1), width=.1, closed=true);
|
||||
// move(c2) stroke(circle(r=r2), width=.1, closed=true);
|
||||
// move(c1) stroke(circle(r=r1), width=0.2, closed=true);
|
||||
// move(c2) stroke(circle(r=r2), width=0.2, closed=true);
|
||||
// colors = ["green","black","blue","red"];
|
||||
// for(i=[0:1:len(pts)-1]) color(colors[i]) stroke(pts[i],width=.1);
|
||||
// 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.
|
||||
// for(i=[0:1:len(pts)-1]) color(colors[i]) stroke(pts[i],width=0.2);
|
||||
// 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;
|
||||
// c1 = [4,4]; r1 = 4;
|
||||
// c2 = [5,5]; r2 = 2;
|
||||
// pts = circle_circle_tangents(c1,r1,c2,r2);
|
||||
// move(c1) stroke(circle(r=r1), width=.1, closed=true);
|
||||
// move(c2) stroke(circle(r=r2), width=.1, closed=true);
|
||||
// move(c1) stroke(circle(r=r1), width=0.2, closed=true);
|
||||
// move(c2) stroke(circle(r=r2), width=0.2, closed=true);
|
||||
// echo(pts); // Returns []
|
||||
function circle_circle_tangents(c1,r1,c2,r2,d1,d2) =
|
||||
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
|
||||
// Description:
|
||||
// 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.
|
||||
// The area of 3d polygons is always nonnegative.
|
||||
// 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 )
|
||||
signed ? total : abs(total)
|
||||
: let( plane = plane_from_polygon(poly) )
|
||||
plane==[]? undef :
|
||||
let(
|
||||
is_undef(plane) ? undef :
|
||||
let( f=echo(plane=plane),
|
||||
n = plane_normal(plane),
|
||||
total =
|
||||
-sum([ for(i=[1:1:len(poly)-2])
|
||||
|
@ -1383,9 +1383,9 @@ function polygon_area(poly, signed=false) =
|
|||
signed ? total : abs(total);
|
||||
|
||||
|
||||
// Function: centroid()
|
||||
// Function: polygon_centroid()
|
||||
// Usage:
|
||||
// cpt = centroid(poly);
|
||||
// cpt = polygon_centroid(poly);
|
||||
// Topics: Geometry, Polygons, Centroid
|
||||
// Description:
|
||||
// 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:
|
||||
// poly = Points of the polygon from which the centroid is calculated.
|
||||
// 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_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
|
||||
let(
|
||||
n = len(poly[0])==2 ? 1 :
|
||||
let( plane = plane_from_points(poly, fast=true) )
|
||||
assert( !is_undef(plane), "The polygon must be planar." )
|
||||
let( plane = plane_from_points(poly, fast=false) ,dd=echo(p=plane))
|
||||
assert(!is_undef(plane), "The polygon must be planar." )
|
||||
plane_normal(plane),
|
||||
v0 = poly[0] ,
|
||||
val = sum([
|
||||
|
@ -1591,10 +1591,10 @@ function point_in_polygon(point, poly, nonzero=false, eps=EPSILON) =
|
|||
// 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(2D):
|
||||
// Example(2D,NoAxes):
|
||||
// poly = star(id=10, od=15,n=11);
|
||||
// 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):
|
||||
// include <BOSL2/polyhedra.scad>
|
||||
// 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/
|
||||
function is_polygon_clockwise(poly) =
|
||||
let( poly=deduplicate(poly))
|
||||
assert(is_path(poly,dim=2), "Input should be a 2d path")
|
||||
let(
|
||||
minx = min(poly*[1,0]),
|
||||
|
@ -1800,7 +1801,7 @@ function polygon_shift(poly, i) =
|
|||
// move_copies(concat(circ,pent)) 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);
|
||||
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])),
|
||||
"Invalid polygon(s) or incompatible dimensions. " )
|
||||
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)] :
|
||||
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()
|
||||
|
@ -1841,7 +1863,7 @@ function reindex_polygon(reference, poly, return_error=false) =
|
|||
// 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);
|
||||
// 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),
|
||||
"Invalid polygon(s). " )
|
||||
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))
|
||||
) 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()
|
||||
|
@ -1925,11 +1968,10 @@ function __is_polygon_in_list(poly, polys, i) =
|
|||
// poly = Polygon to check.
|
||||
// eps = Tolerance for the collinearity test. Default: EPSILON.
|
||||
// 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
|
||||
// Example:
|
||||
// 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) =
|
||||
assert(is_path(poly), "The input should be a 2D or 3D polygon." )
|
||||
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
|
||||
// Example:
|
||||
// info = rot_decode(rot(45));
|
||||
// // Returns: [45, [0,0,1], [0,0,0], [0,0,0]]
|
||||
// Example:
|
||||
// // Returns: [45, [0,0,1], [0,0,0], [0,0,0]]
|
||||
// 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]]
|
||||
// Example:
|
||||
// // Returns: [37, [0.26, 0.53, 0.80], [4.8, 4.6, -4.6], [0,0,0]]
|
||||
// info = rot_decode(left(12)*xrot(-33));
|
||||
// // Returns: [33, [-1,0,0], [0,0,0], [-12,0,0]]
|
||||
// Example:
|
||||
// // Returns: [33, [-1,0,0], [0,0,0], [-12,0,0]]
|
||||
// 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) =
|
||||
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]))
|
||||
|
|
|
@ -98,7 +98,7 @@ function _is_cw(a,b,c,all) =
|
|||
// Example(2D):
|
||||
// pts = [[-10,-10], [0,10], [10,10], [12,-10]];
|
||||
// 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]);
|
||||
//
|
||||
// Code based on this method:
|
||||
|
|
53
paths.scad
53
paths.scad
|
@ -239,7 +239,8 @@ function _path_self_intersections(path, closed=true, eps=EPSILON) =
|
|||
isect = _general_line_intersection([a1,a2],[b1,b2],eps=eps)
|
||||
)
|
||||
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[2]> 0
|
||||
&& isect[2]<= 1+eps)
|
||||
|
@ -380,9 +381,9 @@ function subdivide_path(path, N, refine, closed=true, exact=true, method="length
|
|||
// Example(2D):
|
||||
// path = pentagon(d=100);
|
||||
// spath = subdivide_long_segments(path, 10, closed=true);
|
||||
// stroke(path);
|
||||
// color("lightgreen") move_copies(path) circle(d=5,$fn=12);
|
||||
// color("blue") move_copies(spath) circle(d=3,$fn=12);
|
||||
// stroke(path,width=2,closed=true);
|
||||
// color("red") move_copies(path) circle(d=9,$fn=12);
|
||||
// color("blue") move_copies(spath) circle(d=5,$fn=12);
|
||||
function subdivide_long_segments(path, maxlen, closed=false) =
|
||||
assert(is_path(path))
|
||||
assert(is_finite(maxlen))
|
||||
|
@ -496,20 +497,20 @@ function path_closest_point(path, pt) =
|
|||
// path = path to find the tagent vectors for
|
||||
// closed = set to true of the path is closed. Default: false
|
||||
// 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]);
|
||||
// tangents = path_tangents(rect,closed=true);
|
||||
// stroke(rect,closed=true, width=0.1);
|
||||
// stroke(rect,closed=true, width=0.25);
|
||||
// color("purple")
|
||||
// for(i=[0:len(tangents)-1])
|
||||
// stroke([rect[i]-tangents[i], rect[i]+tangents[i]],width=.1, endcap2="arrow2");
|
||||
// Example(3D): Setting uniform to false corrects the distorted derivatives for this example:
|
||||
// stroke([rect[i]-tangents[i], rect[i]+tangents[i]],width=.25, endcap2="arrow2");
|
||||
// Example(2D): Setting uniform to false corrects the distorted derivatives for this example:
|
||||
// rect = square([10,3]);
|
||||
// tangents = path_tangents(rect,closed=true,uniform=false);
|
||||
// stroke(rect,closed=true, width=0.1);
|
||||
// stroke(rect,closed=true, width=0.25);
|
||||
// color("purple")
|
||||
// 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) =
|
||||
assert(is_path(path))
|
||||
!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.
|
||||
// cutdist = Distance or list of distances where path is cut
|
||||
// closed = If true, treat the path as a closed polygon.
|
||||
// Example(2D):
|
||||
// Example(2D,NoAxes):
|
||||
// path = circle(d=100);
|
||||
// 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) =
|
||||
is_num(cutdist) ? path_cut(path,[cutdist],closed) :
|
||||
assert(is_vector(cutdist))
|
||||
|
@ -947,10 +948,10 @@ function _cut_to_seg_u_form(pathcut, path, closed) =
|
|||
// path = The path to split up.
|
||||
// closed = If true, treat path as a closed polygon. Default: true
|
||||
// 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] ];
|
||||
// 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) =
|
||||
let(
|
||||
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.
|
||||
// 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)
|
||||
// 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 = [
|
||||
// [-100,100], [0,-50], [100,100],
|
||||
// [100,-100], [0,50], [-100,-100]
|
||||
// ];
|
||||
// splitpaths = polygon_parts(path);
|
||||
// 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);
|
||||
// left(100)polygon(pentagram);
|
||||
// rainbow(polygon_parts(pentagram,nonzero=false))
|
||||
// stroke($item,closed=true);
|
||||
// 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.
|
||||
// stroke($item,closed=true,width=2.5);
|
||||
// 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);
|
||||
// outside = polygon_parts(pentagram,nonzero=true);
|
||||
// left(100)region(outside);
|
||||
// rainbow(outside)
|
||||
// stroke($item,closed=true);
|
||||
// Example(2D):
|
||||
// stroke($item,closed=true,width=2.5);
|
||||
// Example(2D,NoAxes):
|
||||
// N=12;
|
||||
// ang=360/N;
|
||||
// sr=10;
|
||||
|
@ -1042,19 +1043,19 @@ function _tag_self_crossing_subpaths(path, nonzero, closed=true, eps=EPSILON) =
|
|||
// "move", sr]);
|
||||
// stroke(path, width=.3);
|
||||
// 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]];
|
||||
// stroke(path,width=0.3);
|
||||
// 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"]]);
|
||||
// back(2)stroke(path,width=.3);
|
||||
// fwd(12)rainbow(polygon_parts(path)) polygon($item);
|
||||
// Example(2D): This shape has six components
|
||||
// back(2)stroke(path,width=.5);
|
||||
// fwd(12)rainbow(polygon_parts(path)) stroke($item, closed=true, width=0.5);
|
||||
// Example(2D,NoAxes): This shape has six components
|
||||
// path = turtle(["repeat", 3, ["move", 15, "left", "move", 7, "left", "move", 10, "left", "move", 17, "left"]]);
|
||||
// polygon(path);
|
||||
// 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"]]);
|
||||
// polygon(path);
|
||||
// right(27)rainbow(polygon_parts(path)) polygon($item);
|
||||
|
|
42
regions.scad
42
regions.scad
|
@ -183,7 +183,7 @@ function __are_regions_equal(region1, region2, i) =
|
|||
/// region = Region to test for crossings of.
|
||||
/// closed = If true, treat path as a closed polygon. Default: true
|
||||
/// 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(
|
||||
pathclosed = closed && !is_closed_path(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()
|
||||
// Usage:
|
||||
|
|
|
@ -276,12 +276,12 @@
|
|||
// Example(FlatSpin,VPD=32,VPT=[1.2,4.3,2]): Another "distance" example:
|
||||
// off = [0,2];
|
||||
// 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);
|
||||
// Example(FlatSpin,VPD=32,VPT=[1.2,4.3,2]): Slightly shifting the profile changes the optimal linkage
|
||||
// off = [0,1];
|
||||
// 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);
|
||||
// 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]]);
|
||||
|
@ -1589,7 +1589,7 @@ function _skin_tangent_match(poly1, poly2) =
|
|||
swap = len(poly1)>len(poly2),
|
||||
big = swap ? poly1 : poly2,
|
||||
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)],
|
||||
shift = last(cutpts)+1,
|
||||
newbig = polygon_shift(big, shift),
|
||||
|
|
Loading…
Reference in a new issue