Update geometry.scad

This commit is contained in:
Richard Milewski 2025-12-14 14:56:48 -08:00
parent 5659fa9ef5
commit daf7ca24dc

View file

@ -11,6 +11,9 @@
// FileFootnotes: STD=Included in std.scad
//////////////////////////////////////////////////////////////////////
_BOSL2_GEOMETRY = is_undef(_BOSL2_STD) && (is_undef(BOSL2_NO_STD_WARNING) || !BOSL2_NO_STD_WARNING) ?
echo("Warning: geometry.scad included without std.scad; dependencies may be missing\nSet BOSL2_NO_STD_WARNING = true to mute this warning.") true : true;
// Section: Lines, Rays, and Segments
@ -29,14 +32,14 @@
// point = The point to test.
// 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)
function is_point_on_line(point, line, bounded=false, eps=EPSILON) =
// eps = Tolerance in geometric comparisons. Default: `_EPSILON` (1e-9)
function is_point_on_line(point, line, bounded=false, eps=_EPSILON) =
assert(is_finite(eps) && (eps>=0), "\nThe tolerance should be a non-negative value." )
assert(is_vector(point), "\nPoint must be a vector.")
assert(_valid_line(line, len(point),eps),"\nGiven line is not valid.")
_is_point_on_line(point, line, bounded,eps);
function _is_point_on_line(point, line, bounded=false, eps=EPSILON) =
function _is_point_on_line(point, line, bounded=false, eps=_EPSILON) =
let(
v1 = (line[1]-line[0]),
v0 = (point-line[0]),
@ -55,12 +58,12 @@ function _dist2line(d,n) = norm(d-(d * n) * n);
///Internal
function _valid_line(line,dim,eps=EPSILON) =
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]));
//Internal
function _valid_plane(p, eps=EPSILON) = is_vector(p,4) && ! approx(norm(p),0,eps);
function _valid_plane(p, eps=_EPSILON) = is_vector(p,4) && ! approx(norm(p),0,eps);
/// Internal Function: _is_at_left()
@ -73,7 +76,7 @@ function _valid_plane(p, eps=EPSILON) = is_vector(p,4) && ! approx(norm(p),0,eps
/// pt = The 2d point to check position of.
/// line = Array of two 2d points forming the line segment to test against.
/// eps = Tolerance in the geometrical tests.
function _is_at_left(pt,line,eps=EPSILON) = _tri_class([pt,line[0],line[1]],eps) <= 0;
function _is_at_left(pt,line,eps=_EPSILON) = _tri_class([pt,line[0],line[1]],eps) <= 0;
/// Internal Function: _degenerate_tri()
@ -100,7 +103,7 @@ function _degenerate_tri(tri,eps) =
/// Arguments:
/// tri = A list of the three 2d vertices of a triangle.
/// eps = Tolerance in the geometrical tests.
function _tri_class(tri, eps=EPSILON) =
function _tri_class(tri, eps=_EPSILON) =
let( crx = cross(tri[1]-tri[2],tri[0]-tri[2]) )
abs( crx ) <= eps*norm(tri[1]-tri[2])*norm(tri[0]-tri[2]) ? 0 : sign( crx );
@ -118,7 +121,7 @@ function _tri_class(tri, eps=EPSILON) =
/// point = The point to check position of.
/// tri = A list of the three 2d vertices of a triangle.
/// eps = Tolerance in the geometrical tests.
function _pt_in_tri(point, tri, eps=EPSILON) =
function _pt_in_tri(point, tri, eps=_EPSILON) =
min( _tri_class([tri[0],tri[1],point],eps),
_tri_class([tri[1],tri[2],point],eps),
_tri_class([tri[2],tri[0],point],eps) );
@ -135,7 +138,7 @@ function _pt_in_tri(point, tri, eps=EPSILON) =
/// 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, eps=EPSILON) =
function _point_left_of_line2d(point, line, eps=_EPSILON) =
assert( is_vector(point,2) && is_vector(line*point, 2), "\nImproper input." )
// cross(line[0]-point, line[1]-line[0]);
_tri_class([point,line[1],line[0]],eps);
@ -153,8 +156,8 @@ function _point_left_of_line2d(point, line, eps=EPSILON) =
// a = First point or list of points.
// b = Second point or undef; it should be undef if `c` is undef
// c = Third point or undef.
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
function is_collinear(a, b, c, eps=EPSILON) =
// eps = Tolerance in geometric comparisons. Default: `_EPSILON` (1e-9)
function is_collinear(a, b, c, eps=_EPSILON) =
assert( is_path([a,b,c],dim=undef)
|| ( is_undef(b) && is_undef(c) && is_path(a,dim=undef) ),
"\nInput should be 3 points or a list of points with same dimension.")
@ -205,7 +208,7 @@ function point_line_distance(pt, line, bounded=false) =
// Example:
// dist = segment_distance([[-14,3], [-15,9]], [[-10,0], [10,0]]); // Returns: 5
// dist2 = segment_distance([[-5,5], [5,-5]], [[-10,3], [10,-3]]); // Returns: 0
function segment_distance(seg1, seg2,eps=EPSILON) =
function segment_distance(seg1, seg2,eps=_EPSILON) =
assert( is_matrix(concat(seg1,seg2),4), "\nInputs should be two valid segments." )
convex_distance(seg1,seg2,eps);
@ -247,7 +250,7 @@ function line_normal(p1,p2) =
// the extension of the segment. If lines are parallel or coincident then
// it returns undef.
function _general_line_intersection(s1,s2,eps=EPSILON) =
function _general_line_intersection(s1,s2,eps=_EPSILON) =
let(
denominator = cross(s1[0]-s1[1],s2[0]-s2[1])
)
@ -281,7 +284,7 @@ function _general_line_intersection(s1,s2,eps=EPSILON) =
// bounded2 = boolean or list of two booleans defining which ends are bounded for line2. Default: [false,false]
// ---
// bounded = boolean or list of two booleans defining which ends are bounded for both lines. The bounded1 and bounded2 parameters override this if both are given.
// eps = tolerance for geometric comparisons. Default: `EPSILON` (1e-9)
// eps = tolerance for geometric comparisons. Default: `_EPSILON` (1e-9)
// Example(2D): The segments do not intersect but the lines do in this example.
// line1 = 10*[[9, 4], [5, 7]];
// line2 = 10*[[2, 3], [6, 5]];
@ -302,7 +305,7 @@ function _general_line_intersection(s1,s2,eps=EPSILON) =
// stroke(line1);
// stroke(line2);
// isect = line_intersection(line1, line2, bounded=true); // Returns undef
function line_intersection(line1, line2, bounded1, bounded2, bounded, eps=EPSILON) =
function line_intersection(line1, line2, bounded1, bounded2, bounded, eps=_EPSILON) =
assert( is_finite(eps) && (eps>=0), "\nThe tolerance should be a non-negative value." )
assert( _valid_line(line1,dim=2,eps=eps), "\nFirst line invalid.")
assert( _valid_line(line2,dim=2,eps=eps), "\nSecond line invalid.")
@ -421,7 +424,7 @@ function line_closest_point(line, pt, bounded=false) =
// Arguments:
// points = The list of points to find the line through.
// check_collinear = If true, don't verify that all points are collinear. Default: false
// eps = How much variance is allowed in testing each point against the line. Default: `EPSILON` (1e-9)
// eps = How much variance is allowed in testing each point against the line. Default: `_EPSILON` (1e-9)
// Example(FlatSpin,VPD=250): A line fitted to a cloud of points.
// points = rot(45, v=[-0.5,1,0],
// p=random_points(100,3,scale=[5,5,50],seed=47));
@ -434,7 +437,7 @@ function _line_greatest_distance(points,line) = // internal function
: let(d = [ for(p=points) point_line_distance(p, line) ])
max(d);
function line_from_points(points, check_collinear=false, eps=EPSILON, fast) =
function line_from_points(points, check_collinear=false, eps=_EPSILON, fast) =
assert( is_path(points), "\nInvalid point list." )
assert( is_finite(eps) && (eps>=0), "\nThe tolerance should be a non-negative value." )
len(points) == 2
@ -468,8 +471,8 @@ function line_from_points(points, check_collinear=false, eps=EPSILON, fast) =
// Returns true if the given 3D points are non-collinear and are on a plane.
// Arguments:
// points = The points to test.
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
function is_coplanar(points, eps=EPSILON) =
// eps = Tolerance in geometric comparisons. Default: `_EPSILON` (1e-9)
function is_coplanar(points, eps=_EPSILON) =
assert( is_path(points,dim=3) , "\nInput should be a list of 3D points." )
assert( is_finite(eps) && eps>=0, "\nThe tolerance should be a non-negative value." )
len(points)<=2 ? false
@ -560,7 +563,7 @@ function plane_from_normal(normal, pt=[0,0,0]) =
// Based on: https://en.wikipedia.org/wiki/Eigenvalue_algorithm
function _eigenvals_symm_3(M) =
let( p1 = pow(M[0][1],2) + pow(M[0][2],2) + pow(M[1][2],2) )
(p1<EPSILON)
(p1<_EPSILON)
? -sort(-[ M[0][0], M[1][1], M[2][2] ]) // diagonal matrix: eigenvals in decreasing order
: let( q = (M[0][0]+M[1][1]+M[2][2])/3,
B = (M - q*ident(3)),
@ -584,7 +587,7 @@ function _eigenvec_symm_3(M,evals,i=0) =
A = (M - evals[(i+1)%3]*I) * (M - evals[(i+2)%3]*I) ,
k = max_index( [for(i=[0:2]) norm(A[i]) ])
)
norm(A[k])<EPSILON ? I[k] : A[k]/norm(A[k]);
norm(A[k])<_EPSILON ? I[k] : A[k]/norm(A[k]);
// finds the eigenvector corresponding to the smallest eigenvalue of the covariance matrix of a pointlist
@ -615,7 +618,7 @@ function _covariance_evec_eval(points, eigenvalue_id) =
// Arguments:
// points = The list of points to find the best-fit plane.
// check_coplanar = If true, verify the point coplanarity within `eps` tolerance. Default: false
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
// eps = Tolerance in geometric comparisons. Default: `_EPSILON` (1e-9)
// Example(FlatSpin,VPD=320,VPT=[-2,5,-2]): 100 non-coplanar random points (yellow spheres) distributed in a volume, showing the best-fit plane (transparent square) with its normal vector.
// points = rot(45, v=[-0.3,1,0],
// p=random_points(100,3,scale=[50,50,15],seed=47));
@ -626,7 +629,7 @@ function _covariance_evec_eval(points, eigenvalue_id) =
// color("#06f") anchor_arrow(50, flag=false);
// %linear_extrude(0.1) square(100, center=true);
// }
function plane_from_points(points, check_coplanar=false, eps=EPSILON, fast) =
function plane_from_points(points, check_coplanar=false, eps=_EPSILON, fast) =
assert( is_path(points,dim=3), "\nImproper 3d point list." )
assert( is_finite(eps) && (eps>=0), "\nThe tolerance should be a non-negative value." )
len(points) == 3
@ -660,14 +663,14 @@ function plane_from_points(points, check_coplanar=false, eps=EPSILON, fast) =
// Arguments:
// poly = The planar 3D polygon to find the plane of.
// check_coplanar = If false, doesn't verify that all points in the polygon are coplanar. Default: true
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
// eps = Tolerance in geometric comparisons. Default: `_EPSILON` (1e-9)
// 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,width=3);
// cp = centroid(xyzpath);
// move(cp) rot(from=UP,to=plane_normal(plane)) anchor_arrow(45);
function plane_from_polygon(poly, check_coplanar=true, eps=EPSILON, fast) =
function plane_from_polygon(poly, check_coplanar=true, eps=_EPSILON, fast) =
assert( is_path(poly,dim=3), "\nInvalid polygon." )
assert( is_finite(eps) && (eps>=0), "\nThe tolerance should be a non-negative value." )
let(
@ -718,7 +721,7 @@ function plane_offset(plane) =
// Returns [POINT, U] if line intersects plane at one point, where U is zero at line[0] and 1 at line[1]
// Returns [LINE, undef] if the line is on the plane.
// Returns undef if line is parallel to, but not on the given plane.
function _general_plane_line_intersection(plane, line, eps=EPSILON) =
function _general_plane_line_intersection(plane, line, eps=_EPSILON) =
let(
a = plane*[each line[0],-1], // evaluation of the plane expression at line[0]
b = plane*[each(line[1]-line[0]),0] // difference between the plane expression evaluation at line[1] and at line[0]
@ -756,8 +759,8 @@ function _normalize_plane(plane) =
// plane = The [A,B,C,D] values for the equation of the plane.
// line = A list of two distinct 3D points that are 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)
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
function plane_line_intersection(plane, line, bounded=false, eps=EPSILON) =
// eps = Tolerance in geometric comparisons. Default: `_EPSILON` (1e-9)
function plane_line_intersection(plane, line, bounded=false, eps=_EPSILON) =
assert( is_finite(eps) && eps>=0, "\nThe tolerance should be a positive number." )
assert(_valid_plane(plane,eps=eps) && _valid_line(line,dim=3,eps=eps), "\nInvalid plane and/or 3d line.")
assert(is_bool(bounded) || is_bool_list(bounded,2), "\nInvalid bound condition.")
@ -912,8 +915,8 @@ function _pointlist_greatest_distance(points,plane) =
// Arguments:
// plane = The plane to test the points on.
// points = The list of 3D points to test.
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
function are_points_on_plane(points, plane, eps=EPSILON) =
// eps = Tolerance in geometric comparisons. Default: `_EPSILON` (1e-9)
function are_points_on_plane(points, plane, eps=_EPSILON) =
assert( _valid_plane(plane), "\nInvalid plane." )
assert( is_matrix(points,undef,3) && len(points)>0, "\nInvalid pointlist." ) // using is_matrix it accepts len(points)==1
assert( is_finite(eps) && eps>=0, "\nThe tolerance should be a positive number." )
@ -933,7 +936,7 @@ function are_points_on_plane(points, plane, eps=EPSILON) =
/// plane = The [A,B,C,D] coefficients for the first plane equation `Ax+By+Cz=D`.
/// point = The 3D point to test.
function _is_point_above_plane(plane, point) =
point_plane_distance(plane, point) > EPSILON;
point_plane_distance(plane, point) > _EPSILON;
// Module: show_plane()
@ -1028,14 +1031,14 @@ module show_plane(plane, size, offset=0)
// color("black") stroke(line, endcaps="arrow2", width=0.5);
// isects = circle_line_intersection(r=r, cp=cp, line=line);
// color("#f44") move_copies(isects) circle(d=1);
function circle_line_intersection(r, cp, line, bounded=false, d, eps=EPSILON) =
function circle_line_intersection(r, cp, line, bounded=false, d, eps=_EPSILON) =
assert(_valid_line(line,2), "\nInvalid 2d line.")
assert(is_vector(cp,2), "\nCircle center must be a 2-vector")
_circle_or_sphere_line_intersection(r, cp, line, bounded, d, eps);
function _circle_or_sphere_line_intersection(r, cp, line, bounded=false, d, eps=EPSILON) =
function _circle_or_sphere_line_intersection(r, cp, line, bounded=false, d, eps=_EPSILON) =
let(r=get_radius(r=r,d=d,dflt=undef))
assert(is_num(r) && r>0, "\nRadius must be positive")
assert(is_bool(bounded) || is_bool_list(bounded,2), "\nInvalid bound condition")
@ -1071,7 +1074,7 @@ function _circle_or_sphere_line_intersection(r, cp, line, bounded=false, d, eps=
// cp1 = Centerpoint of the first circle.
// r2 = Radius of the second circle.
// cp2 = Centerpoint of the second circle.
// eps = Tolerance for detecting tangent circles. Default: EPSILON
// eps = Tolerance for detecting tangent circles. Default: _EPSILON
// ---
// d1 = Diameter of the first circle.
// d2 = Diameter of the second circle.
@ -1107,7 +1110,7 @@ function _circle_or_sphere_line_intersection(r, cp, line, bounded=false, d, eps=
// move(cp1) stroke(circle(r=r1), width=0.2, closed=true);
// move(cp2) stroke(circle(r=r2), width=0.2, closed=true);
// color("red") move_copies(pts) circle(r=.3);
function circle_circle_intersection(r1, cp1, r2, cp2, eps=EPSILON, d1, d2) =
function circle_circle_intersection(r1, cp1, r2, cp2, eps=_EPSILON, d1, d2) =
assert( is_path([cp1,cp2],dim=2), "\nInvalid center point(s)." )
let(
r1 = get_radius(r1=r1,d1=d1),
@ -1418,8 +1421,8 @@ function circle_circle_tangents(r1, cp1, r2, cp2, d1, d2) =
/// Arguments:
/// points = List of input points.
/// error = Defines the behaviour for collinear input points. When `true`, produces an error, otherwise returns []. Default: `true`.
/// eps = Tolerance for collinearity test. Default: EPSILON.
function _noncollinear_triple(points,error=true,eps=EPSILON) =
/// eps = Tolerance for collinearity test. Default: _EPSILON.
function _noncollinear_triple(points,error=true,eps=_EPSILON) =
assert( is_path(points), "\nInvalid input points." )
assert( is_finite(eps) && (eps>=0), "The tolerance should be a non-negative value." )
len(points)<3 ? [] :
@ -1468,7 +1471,7 @@ function _noncollinear_triple(points,error=true,eps=EPSILON) =
// color("cyan") stroke(line);
// move(cp) sphere(r=r, $fn=72);
// color("red") move_copies(isects) sphere(d=3, $fn=12);
function sphere_line_intersection(r, cp, line, bounded=false, d, eps=EPSILON) =
function sphere_line_intersection(r, cp, line, bounded=false, d, eps=_EPSILON) =
assert(_valid_line(line,3), "\nInvalid 3d line.")
assert(is_vector(cp,3), "\nSphere center must be a 3-vector")
_circle_or_sphere_line_intersection(r, cp, line, bounded, d, eps);
@ -1543,7 +1546,7 @@ function polygon_area(poly, signed=false) =
// linear_extrude(height=0.01) polygon(path);
// cp = centroid(path);
// color("red") move(cp) sphere(d=2);
function centroid(object,eps=EPSILON) =
function centroid(object,eps=_EPSILON) =
assert(is_finite(eps) && (eps>=0), "\nThe tolerance should a non-negative value." )
is_vnf(object) ? _vnf_centroid(object,eps)
: is_path(object,[2,3]) ? _polygon_centroid(object,eps)
@ -1553,7 +1556,7 @@ function centroid(object,eps=EPSILON) =
/// Internal Function: _region_centroid()
/// Compute centroid of region
function _region_centroid(region,eps=EPSILON) =
function _region_centroid(region,eps=_EPSILON) =
let(
region=force_region(region),
parts = region_parts(region),
@ -1578,8 +1581,8 @@ function _region_centroid(region,eps=EPSILON) =
/// polygons or an error is produced.
/// Arguments:
/// poly = Points of the polygon from which the centroid is calculated.
/// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
function _polygon_centroid(poly, eps=EPSILON) =
/// eps = Tolerance in geometric comparisons. Default: `_EPSILON` (1e-9)
function _polygon_centroid(poly, eps=_EPSILON) =
assert( is_path(poly,dim=[2,3]), "\nThe input must be a 2D or 3D polygon." )
let(
n = len(poly[0])==2 ? 1 :
@ -1680,7 +1683,7 @@ function polygon_normal(poly) =
// point = The 2D point to check
// 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: 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 false (the default), we get this result. Green dots are inside the polygon and red are outside:
// a=20*2/3;
// b=30*2/3;
@ -1723,7 +1726,7 @@ function _point_above_below_segment(point, edge) =
: (edge[1].y <= 0 && cross(edge[0], edge[1]-edge[0]) < 0) ? -1 : 0;
function point_in_polygon(point, poly, nonzero=false, eps=EPSILON) =
function point_in_polygon(point, poly, nonzero=false, eps=_EPSILON) =
// Original algorithms from http://geomalgorithms.com/a03-_inclusion.html
assert( is_vector(point,2) && is_path(poly,dim=2) && len(poly)>2,
"\nThe point and polygon should be in 2D. The polygon should have more that 2 points." )
@ -1802,7 +1805,7 @@ function point_in_polygon(point, poly, nonzero=false, eps=EPSILON) =
// line = A list of two distinct 3D points on the line.
// bounded = If false, the line is considered unbounded. If true, it is treated as a bounded line segment. If given as `[true, false]` or `[false, true]`, the boundedness of the points are specified individually, allowing the line to be treated as a half-bounded ray. Default: false (unbounded)
// nonzero = set to true to use the nonzero rule for determining it points are in a polygon. See point_in_polygon. Default: false.
// eps = Tolerance in geometric comparisons. Default: `EPSILON` (1e-9)
// eps = Tolerance in geometric comparisons. Default: `_EPSILON` (1e-9)
// Example(3D): 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]];
@ -1906,7 +1909,7 @@ function point_in_polygon(point, poly, nonzero=false, eps=EPSILON) =
// move(part[0]) circle(r=1,$fn=12);
// else
// stroke(part);
function polygon_line_intersection(poly, line, bounded=false, nonzero=false, eps=EPSILON) =
function polygon_line_intersection(poly, line, bounded=false, nonzero=false, eps=_EPSILON) =
assert( is_finite(eps) && eps>=0, "\nThe tolerance should be a positive number." )
assert(is_path(poly,dim=[2,3]), "\nInvalid polygon." )
assert(is_bool(bounded) || is_bool_list(bounded,2), "\nInvalid bound condition.")
@ -2000,7 +2003,7 @@ function _merge_segments(insegs,outsegs, eps, i=1) =
// poly = Array of the polygon vertices.
// ind = If given, a list of indices indexing the vertices of the polygon in `poly`. Default: use all the points of poly
// error = If false, returns `undef` when the polygon cannot be triangulated; otherwise, issues an assert error. Default: true.
// eps = A maximum tolerance in geometrical tests. Default: EPSILON
// eps = A maximum tolerance in geometrical tests. Default: _EPSILON
// Example(2D,NoAxes): a simple polygon; see from above
// poly = star(id=10, od=15,n=11);
// tris = polygon_triangulate(poly);
@ -2037,7 +2040,7 @@ function _merge_segments(insegs,outsegs, eps, i=1) =
// vnf_tri = [vnf[0], [for(face=vnf[1]) each polygon_triangulate(vnf[0], face) ] ];
// color("blue")
// vnf_wireframe(vnf_tri, width=.15);
function polygon_triangulate(poly, ind, error=true, eps=EPSILON) =
function polygon_triangulate(poly, ind, error=true, eps=_EPSILON) =
assert(is_path(poly) && len(poly)>=3, "\nPolygon `poly` should be a list of at least three 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")
@ -2086,7 +2089,7 @@ function polygon_triangulate(poly, ind, error=true, eps=EPSILON) =
// implements a modified version of ear cut method for non-twisted polygons
// the polygons accepted by this function are those decomposable in simple
// CW polygons.
function _triangulate(poly, ind, error, eps=EPSILON, tris=[]) =
function _triangulate(poly, ind, error, eps=_EPSILON, tris=[]) =
len(ind)==3
? _degenerate_tri(select(poly,ind),eps)
? tris // if last 3 pts perform a degenerate triangle, ignore it
@ -2349,7 +2352,7 @@ function align_polygon(reference, poly, angles, cp, trans, return_ind=false) =
],
scores = column(alignments,1),
minscore = min(scores),
minind = [for(i=idx(scores)) if (scores[i]<minscore+EPSILON) i],
minind = [for(i=idx(scores)) if (scores[i]<minscore+_EPSILON) i],
dummy = is_def(angles) ? echo(best_angles = select(list(angles), minind)):0,
best = minind[0]
)
@ -2375,7 +2378,7 @@ function align_polygon(reference, poly, angles, cp, trans, return_ind=false) =
// rot(360/5, p=pentagon(r=4))); // returns true
// are_polygons_equal(pentagon(r=4),
// rot(90, p=pentagon(r=4))); // returns false
function are_polygons_equal(poly1, poly2, eps=EPSILON) =
function are_polygons_equal(poly1, poly2, eps=_EPSILON) =
let(
poly1 = list_unwrap(poly1),
poly2 = list_unwrap(poly2),
@ -2497,8 +2500,8 @@ function _backtracking(i,points,h,t,m,all) =
// clockwise check (2d)
function _is_cw(a,b,c,all) =
all ? cross(a-c,b-c)<=EPSILON*norm(a-c)*norm(b-c) :
cross(a-c,b-c)<-EPSILON*norm(a-c)*norm(b-c);
all ? cross(a-c,b-c)<=_EPSILON*norm(a-c)*norm(b-c) :
cross(a-c,b-c)<-_EPSILON*norm(a-c)*norm(b-c);
// Function: hull2d_path()
@ -2624,7 +2627,7 @@ function hull3d_faces(points) =
// Adds the remaining points one by one to the convex hull
function _hull3d_iterative(points, triangles, planes, remaining, _i=0) = //let( EPSILON=1e-12 )
function _hull3d_iterative(points, triangles, planes, remaining, _i=0) = //let( _EPSILON=1e-12 )
_i >= len(remaining) ? triangles :
let (
// pick a point
@ -2632,7 +2635,7 @@ function _hull3d_iterative(points, triangles, planes, remaining, _i=0) = //let(
// evaluate the triangle plane equations at point i
planeq_val = planes*[each points[i], -1],
// find the triangles that are in conflict with the point (point not inside)
conflicts = [for (i = [0:1:len(planeq_val)-1]) if (planeq_val[i]>EPSILON) i ],
conflicts = [for (i = [0:1:len(planeq_val)-1]) if (planeq_val[i]>_EPSILON) i ],
// collect the halfedges of all triangles that are in conflict
halfedges = [
for(c = conflicts, i = [0:2])
@ -2645,12 +2648,12 @@ function _hull3d_iterative(points, triangles, planes, remaining, _i=0) = //let(
// add tria2add and remove conflict triangles
new_triangles =
concat( tri2add,
[ for (i = [0:1:len(planes)-1]) if (planeq_val[i]<=EPSILON) triangles[i] ]
[ for (i = [0:1:len(planes)-1]) if (planeq_val[i]<=_EPSILON) triangles[i] ]
),
// add the plane equations of new added triangles and remove the plane equations of the conflict ones
new_planes =
[ for (t = tri2add) plane3pt_indexed(points, t[0], t[1], t[2]) ,
for (i = [0:1:len(planes)-1]) if (planeq_val[i]<=EPSILON) planes[i] ]
for (i = [0:1:len(planes)-1]) if (planeq_val[i]<=_EPSILON) planes[i] ]
) _hull3d_iterative(
points,
new_triangles,
@ -2692,13 +2695,13 @@ function _find_first_noncoplanar(plane, points, i=0) =
// If the points are collinear or not coplanar an error may be generated.
// Arguments:
// poly = Polygon to check.
// eps = Tolerance for the collinearity and coplanarity tests. Default: EPSILON.
// eps = Tolerance for the collinearity and coplanarity tests. Default: _EPSILON.
// Example:
// 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
// spiral = [for (i=[0:36]) let(a=-i*10) (10+i)*[cos(a),sin(a)]];
// 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), "\nThe input should be a 2D or 3D polygon." )
let(
lp = len(poly),
@ -2741,7 +2744,7 @@ function is_polygon_convex(poly,eps=EPSILON) =
// Arguments:
// points1 = first list of 2d or 3d points.
// points2 = second list of 2d or 3d points.
// eps = tolerance in distance evaluations. Default: EPSILON.
// eps = tolerance in distance evaluations. Default: _EPSILON.
// Example(2D):
// pts1 = move([-3,0], p=square(3,center=true));
// pts2 = rot(a=45, p=square(2,center=true));
@ -2759,7 +2762,7 @@ function is_polygon_convex(poly,eps=EPSILON) =
// vnf_polyhedron(sphr2);
// echo(convex_distance(sphr1[0], sphr2[0])); // Returns: 0
// echo(convex_distance(sphr1[0], sphr3[0])); // Returns: 0.5
function convex_distance(points1, points2, eps=EPSILON) =
function convex_distance(points1, points2, eps=_EPSILON) =
assert(is_matrix(points1) && is_matrix(points2,undef,len(points1[0])),
"\nThe input lists should be compatible consistent non empty lists of points.")
assert(len(points1[0])==2 || len(points1[0])==3 ,
@ -2773,7 +2776,7 @@ function convex_distance(points1, points2, eps=EPSILON) =
// Finds the vector difference between the hulls of the two pointsets by the GJK algorithm
// Based on:
// http://www.dtecta.com/papers/jgt98convex.pdf
function _GJK_distance(points1, points2, eps=EPSILON, lbd, d, simplex=[]) =
function _GJK_distance(points1, points2, eps=_EPSILON, lbd, d, simplex=[]) =
let( nrd = norm(d) ) // distance upper bound
nrd<eps ? d :
let(
@ -2801,7 +2804,7 @@ function _GJK_distance(points1, points2, eps=EPSILON, lbd, d, simplex=[]) =
// Arguments:
// points1 = first list of 2d or 3d points.
// points2 = second list of 2d or 3d points.
// eps - tolerance for the intersection tests. Default: EPSILON.
// eps - tolerance for the intersection tests. Default: _EPSILON.
// Example(2D):
// pts1 = move([-3,0], p=square(3,center=true));
// pts2 = rot(a=45, p=square(2,center=true));
@ -2820,7 +2823,7 @@ function _GJK_distance(points1, points2, eps=EPSILON, lbd, d, simplex=[]) =
// echo(convex_collision(sphr1[0], sphr2[0])); // Returns: true
// echo(convex_collision(sphr1[0], sphr3[0])); // Returns: false
//
function convex_collision(points1, points2, eps=EPSILON) =
function convex_collision(points1, points2, eps=_EPSILON) =
assert(is_matrix(points1) && is_matrix(points2,undef,len(points1[0])),
"\nThe input lists should be compatible consistent non empty lists of points.")
assert(len(points1[0])==2 || len(points1[0])==3 ,
@ -2835,7 +2838,7 @@ function convex_collision(points1, points2, eps=EPSILON) =
// http://uu.diva-portal.org/smash/get/diva2/FFULLTEXT01.pdf
// or
// http://www.dtecta.com/papers/jgt98convex.pdf
function _GJK_collide(points1, points2, d, simplex, eps=EPSILON) =
function _GJK_collide(points1, points2, d, simplex, eps=_EPSILON) =
norm(d) < eps ? true : // does collide
let( v = _support_diff(points1,points2,-d) )
v*d > eps*eps ? false : // no collision
@ -2847,7 +2850,7 @@ function _GJK_collide(points1, points2, d, simplex, eps=EPSILON) =
// given a simplex s, returns a pair:
// - the point of the s closest to the origin
// - the smallest sub-simplex of s that contains that point
function _closest_simplex(s,eps=EPSILON) =
function _closest_simplex(s,eps=_EPSILON) =
len(s)==2 ? _closest_s1(s,eps) :
len(s)==3 ? _closest_s2(s,eps) :
len(s)==4 ? _closest_s3(s,eps) :
@ -2855,7 +2858,7 @@ function _closest_simplex(s,eps=EPSILON) =
// find the point of a 1-simplex closest to the origin
function _closest_s1(s,eps=EPSILON) =
function _closest_s1(s,eps=_EPSILON) =
norm(s[1]-s[0])<=eps*(norm(s[0])+norm(s[1]))/2 ? [ s[0], [s[0]] ] :
let(
c = s[1]-s[0],
@ -2867,7 +2870,7 @@ function _closest_s1(s,eps=EPSILON) =
// find the point of a 2-simplex closest to the origin
function _closest_s2(s, eps=EPSILON) =
function _closest_s2(s, eps=_EPSILON) =
// considering that s[2] was the last inserted vertex in s by GJK,
// the plane orthogonal to the triangle [ origin, s[0], s[1] ] that
// contains [s[0],s[1]] have the origin and s[2] on the same side;
@ -2898,7 +2901,7 @@ function _closest_s2(s, eps=EPSILON) =
// find the point of a 3-simplex closest to the origin
function _closest_s3(s,eps=EPSILON) =
function _closest_s3(s,eps=_EPSILON) =
let( nr = cross(s[1]-s[0],s[2]-s[0]),
sz = [ norm(s[0]-s[1]), norm(s[1]-s[2]), norm(s[2]-s[0]) ] )
norm(nr)<=eps*pow(max(sz),2)