diff --git a/geometry.scad b/geometry.scad index 62290a5..3e3525d 100644 --- a/geometry.scad +++ b/geometry.scad @@ -26,12 +26,12 @@ function point_on_segment2d(point, edge, eps=EPSILON) = assert( _valid_line(edge,2,eps=eps), "Invalid segment." ) let( dp = point-edge[0], de = edge[1]-edge[0], - ne = norm(de) ) - ( dp*de >= -eps*ne ) + ne = norm(de) ) + ( dp*de >= -eps*ne ) && ( (dp-de)*de <= eps*ne ) // point projects on the segment - && _dist2line(point-edge[0],unit(de)) 0 && cross(edge[0], edge[1]-edge[0]) > 0) ? 1 : 0 : (edge[1].y <= 0 && cross(edge[0], edge[1]-edge[0]) < 0) ? -1 : 0 ; //Internal -function _valid_line(line,dim,eps=EPSILON) = - is_matrix(line,2,dim) - && ! approx(norm(line[1]-line[0]), 0, eps); - +function _valid_line(line,dim,eps=EPSILON) = + is_matrix(line,2,dim) + && ! approx(norm(line[1]-line[0]), 0, eps); + //Internal function _valid_plane(p, eps=EPSILON) = is_vector(p,4) && ! approx(norm(p),0,eps); @@ -79,13 +79,13 @@ function point_left_of_line2d(point, line) = // eps = Acceptable variance. Default: `EPSILON` (1e-9) function 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) ), + || ( is_undef(b) && is_undef(c) && is_path(a,dim=undef) ), "Input should be 3 points or a list of points with same dimension.") assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." ) let( points = is_def(c) ? [a,b,c]: a ) len(points)<3 ? true : noncollinear_triple(points,error=false,eps=eps)==[]; - + // Function: distance_from_line() // Usage: @@ -98,11 +98,11 @@ function collinear(a, b, c, eps=EPSILON) = // Example: // distance_from_line([[-10,0], [10,0]], [3,8]); // Returns: 8 function distance_from_line(line, pt) = - assert( _valid_line(line) && is_vector(pt,len(line[0])), + assert( _valid_line(line) && is_vector(pt,len(line[0])), "Invalid line, invalid point or incompatible dimensions." ) _dist2line(pt-line[0],unit(line[1]-line[0])); - - + + // Function: line_normal() // Usage: // line_normal([P1,P2]) @@ -121,9 +121,9 @@ function distance_from_line(line, pt) = // color("blue") move_copies([p1,p2]) circle(d=2, $fn=12); function line_normal(p1,p2) = is_undef(p2) - ? assert( len(p1)==2 && !is_undef(p1[1]) , "Invalid input." ) - line_normal(p1[0],p1[1]) - : assert( _valid_line([p1,p2],dim=2), "Invalid line." ) + ? assert( len(p1)==2 && !is_undef(p1[1]) , "Invalid input." ) + line_normal(p1[0],p1[1]) + : assert( _valid_line([p1,p2],dim=2), "Invalid line." ) unit([p1.y-p2.y,p2.x-p1.x]); @@ -157,7 +157,7 @@ function _general_line_intersection(s1,s2,eps=EPSILON) = function line_intersection(l1,l2,eps=EPSILON) = assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." ) assert( _valid_line(l1,dim=2,eps=eps) &&_valid_line(l2,dim=2,eps=eps), "Invalid line(s)." ) - let(isect = _general_line_intersection(l1,l2,eps=eps)) + let(isect = _general_line_intersection(l1,l2,eps=eps)) isect[0]; @@ -176,7 +176,7 @@ function line_ray_intersection(line,ray,eps=EPSILON) = assert( _valid_line(line,dim=2,eps=eps) && _valid_line(ray,dim=2,eps=eps), "Invalid line or ray." ) let( isect = _general_line_intersection(line,ray,eps=eps) - ) + ) is_undef(isect[0]) ? undef : (isect[2]<0-eps) ? undef : isect[0]; @@ -217,7 +217,7 @@ function ray_intersection(r1,r2,eps=EPSILON) = assert( _valid_line(r1,dim=2,eps=eps) && _valid_line(r2,dim=2,eps=eps), "Invalid ray(s)." ) let( isect = _general_line_intersection(r1,r2,eps=eps) - ) + ) is_undef(isect[0]) ? undef : isect[1]<0-eps || isect[2]<0-eps ? undef : isect[0]; @@ -237,7 +237,7 @@ function ray_segment_intersection(ray,segment,eps=EPSILON) = assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." ) let( isect = _general_line_intersection(ray,segment,eps=eps) - ) + ) is_undef(isect[0]) ? undef : isect[1]<0-eps || isect[2]<0-eps || isect[2]>1+eps ? undef : isect[0]; @@ -258,7 +258,7 @@ function segment_intersection(s1,s2,eps=EPSILON) = assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." ) let( isect = _general_line_intersection(s1,s2,eps=eps) - ) + ) is_undef(isect[0]) ? undef : isect[1]<0-eps || isect[1]>1+eps || isect[2]<0-eps || isect[2]>1+eps ? undef : isect[0]; @@ -449,7 +449,7 @@ function segment_closest_point(seg,pt) = projection>=seglen ? seg[1] : seg[0] + projection*segvec; - + // Function: line_from_points() // Usage: // line_from_points(points, [fast], [eps]); @@ -586,25 +586,25 @@ function tri_calc(ang,ang2,adj,opp,hyp) = assert(num_defined([ang,ang2,adj,opp,hyp])==2, "Exactly two arguments must be given.") let( ang = ang!=undef - ? assert(ang>0&&ang<90, "The input angles should be acute angles." ) ang - : ang2!=undef ? (90-ang2) - : adj==undef ? asin(constrain(opp/hyp,-1,1)) - : opp==undef ? acos(constrain(adj/hyp,-1,1)) + ? assert(ang>0&&ang<90, "The input angles should be acute angles." ) ang + : ang2!=undef ? (90-ang2) + : adj==undef ? asin(constrain(opp/hyp,-1,1)) + : opp==undef ? acos(constrain(adj/hyp,-1,1)) : atan2(opp,adj), ang2 = ang2!=undef - ? assert(ang2>0&&ang2<90, "The input angles should be acute angles." ) ang2 + ? assert(ang2>0&&ang2<90, "The input angles should be acute angles." ) ang2 : (90-ang), adj = adj!=undef - ? assert(adj>0, "Triangle side lengths should be positive." ) adj + ? assert(adj>0, "Triangle side lengths should be positive." ) adj : (opp!=undef? (opp/tan(ang)) : (hyp*cos(ang))), opp = opp!=undef - ? assert(opp>0, "Triangle side lengths should be positive." ) opp + ? assert(opp>0, "Triangle side lengths should be positive." ) opp : (adj!=undef? (adj*tan(ang)) : (hyp*sin(ang))), hyp = hyp!=undef - ? assert(hyp>0, "Triangle side lengths should be positive." ) + ? assert(hyp>0, "Triangle side lengths should be positive." ) assert(adj=0 && opp>=0, + assert(is_finite(hyp+opp) && hyp>=0 && opp>=0, "Triangle side lengths should be a positive numbers." ) sqrt(hyp*hyp-opp*opp); @@ -672,7 +672,7 @@ function opp_ang_to_adj(opp,ang) = // Example: // opp = hyp_adj_to_opp(5,4); // Returns: 3 function hyp_adj_to_opp(hyp,adj) = - assert(is_finite(hyp) && hyp>=0 && is_finite(adj) && adj>=0, + assert(is_finite(hyp) && hyp>=0 && is_finite(adj) && adj>=0, "Triangle side lengths should be a positive numbers." ) sqrt(hyp*hyp-adj*adj); @@ -720,7 +720,7 @@ function adj_ang_to_opp(adj,ang) = // Example: // hyp = adj_opp_to_hyp(3,4); // Returns: 5 function adj_opp_to_hyp(adj,opp) = - assert(is_finite(opp) && opp>=0 && is_finite(adj) && adj>=0, + assert(is_finite(opp) && opp>=0 && is_finite(adj) && adj>=0, "Triangle side lengths should be a positive numbers." ) norm([opp,adj]); @@ -768,7 +768,7 @@ function opp_ang_to_hyp(opp,ang) = // Example: // ang = hyp_adj_to_ang(8,4); // Returns: 60 degrees function hyp_adj_to_ang(hyp,adj) = - assert(is_finite(hyp) && hyp>0 && is_finite(adj) && adj>=0, + assert(is_finite(hyp) && hyp>0 && is_finite(adj) && adj>=0, "Triangle side lengths should be positive numbers." ) acos(adj/hyp); @@ -784,7 +784,7 @@ function hyp_adj_to_ang(hyp,adj) = // Example: // ang = hyp_opp_to_ang(8,4); // Returns: 30 degrees function hyp_opp_to_ang(hyp,opp) = - assert(is_finite(hyp+opp) && hyp>0 && opp>=0, + assert(is_finite(hyp+opp) && hyp>0 && opp>=0, "Triangle side lengths should be positive numbers." ) asin(opp/hyp); @@ -800,7 +800,7 @@ function hyp_opp_to_ang(hyp,opp) = // Example: // ang = adj_opp_to_ang(sqrt(3)/2,0.5); // Returns: 30 degrees function adj_opp_to_ang(adj,opp) = - assert(is_finite(adj+opp) && adj>0 && opp>=0, + assert(is_finite(adj+opp) && adj>0 && opp>=0, "Triangle side lengths should be positive numbers." ) atan2(opp,adj); @@ -814,10 +814,10 @@ function adj_opp_to_ang(adj,opp) = // Examples: // triangle_area([0,0], [5,10], [10,0]); // Returns -50 // triangle_area([10,0], [5,10], [0,0]); // Returns 50 -function triangle_area(a,b,c) = - assert( is_path([a,b,c]), "Invalid points or incompatible dimensions." ) - len(a)==3 - ? 0.5*norm(cross(c-a,c-b)) +function triangle_area(a,b,c) = + assert( is_path([a,b,c]), "Invalid points or incompatible dimensions." ) + len(a)==3 + ? 0.5*norm(cross(c-a,c-b)) : 0.5*cross(c-a,c-b); @@ -830,7 +830,7 @@ function triangle_area(a,b,c) = // plane3pt(p1, p2, p3); // 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 [A,B,C,D] where Ax + By + Cz = D is the equation of a plane. // Returns [], if the points are collinear. // Arguments: // p1 = The first point on the plane. @@ -838,11 +838,11 @@ function triangle_area(a,b,c) = // p3 = The third point on the plane. function plane3pt(p1, p2, p3) = assert( is_path([p1,p2,p3],dim=3) && len(p1)==3, - "Invalid points or incompatible dimensions." ) + "Invalid points or incompatible dimensions." ) let( crx = cross(p3-p1, p2-p1), nrm = norm(crx) - ) + ) approx(nrm,0) ? [] : concat(crx, crx*p1)/nrm; @@ -869,7 +869,7 @@ function plane3pt_indexed(points, i1, i2, i3) = p1 = points[i1], p2 = points[i2], p3 = points[i3] - ) + ) plane3pt(p1,p2,p3); @@ -881,7 +881,7 @@ function plane3pt_indexed(points, i1, i2, i3) = // Example: // plane_from_normal([0,0,1], [2,2,2]); // Returns the xy plane passing through the point (2,2,2) function plane_from_normal(normal, pt=[0,0,0]) = - assert( is_matrix([normal,pt],2,3) && !approx(norm(normal),0), + assert( is_matrix([normal,pt],2,3) && !approx(norm(normal),0), "Inputs `normal` and `pt` should 3d vectors/points and `normal` cannot be zero." ) concat(normal, normal*pt) / norm(normal); @@ -946,7 +946,7 @@ function plane_from_polygon(poly, fast=false, eps=EPSILON) = poly = deduplicate(poly), n = polygon_normal(poly), plane = [n.x, n.y, n.z, n*poly[0]] - ) + ) fast? plane: coplanar(poly,eps=eps)? plane: []; @@ -955,7 +955,7 @@ function plane_from_polygon(poly, fast=false, eps=EPSILON) = // plane_normal(plane); // Description: // Returns the unit length normal vector for the given plane. -function plane_normal(plane) = +function plane_normal(plane) = assert( _valid_plane(plane), "Invalid input plane." ) unit([plane.x, plane.y, plane.z]); @@ -964,10 +964,10 @@ function plane_normal(plane) = // Usage: // d = plane_offset(plane); // Description: -// Returns coeficient D of the normalized plane equation `Ax+By+Cz=D`, or the scalar offset of the plane from the origin. +// Returns coeficient D of the normalized plane equation `Ax+By+Cz=D`, or the scalar offset of the plane from the origin. // This value may be negative. // The absolute value of this coefficient is the distance of the plane from the origin. -function plane_offset(plane) = +function plane_offset(plane) = assert( _valid_plane(plane), "Invalid input plane." ) plane[3]/norm([plane.x, plane.y, plane.z]); @@ -994,7 +994,7 @@ function plane_transform(plane) = plane = normalize_plane(plane), n = point3d(plane), cp = n * plane[3] - ) + ) rot(from=n, to=UP) * move(-cp); @@ -1002,7 +1002,7 @@ function plane_transform(plane) = // Usage: // projection_on_plane(points); // Description: -// Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or 3d points, return the 3D orthogonal +// Given a plane definition `[A,B,C,D]`, where `Ax+By+Cz=D`, and a list of 2d or 3d points, return the 3D orthogonal // projection of the points on the plane. // Arguments: // plane = The `[A,B,C,D]` plane definition where `Ax+By+Cz=D` is the formula of the plane. @@ -1014,13 +1014,13 @@ function plane_transform(plane) = function projection_on_plane(plane, points) = assert( _valid_plane(plane), "Invalid plane." ) assert( is_path(points), "Invalid list of points or dimension." ) - let( + let( p = len(points[0])==2 ? [for(pi=points) point3d(pi) ] - : points, + : points, plane = normalize_plane(plane), n = point3d(plane) - ) + ) [for(pi=p) pi - (pi*n - plane[3])*n]; @@ -1069,7 +1069,7 @@ function closest_point_on_plane(plane, point) = let( plane = normalize_plane(plane), n = point3d(plane), d = n*point - plane[3] // distance from plane - ) + ) point - n*d; @@ -1078,7 +1078,7 @@ function closest_point_on_plane(plane, point) = // Returns undef if line is parallel to, but not on the given plane. function _general_plane_line_intersection(plane, line, eps=EPSILON) = let( - a = plane*[each line[0],-1], // evaluation of the plane expression at line[0] + 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] ) approx(b,0,eps) // is (line[1]-line[0]) "parallel" to the plane ? @@ -1086,10 +1086,10 @@ function _general_plane_line_intersection(plane, line, eps=EPSILON) = ? [line,undef] // line is on the plane : undef // line is parallel but not on the plane : [ line[0]-a/b*(line[1]-line[0]), -a/b ]; - - + + // Function: normalize_plane() -// Usage: +// Usage: // nplane = normalize_plane(plane); // Description: // Returns a new representation [A,B,C,D] of `plane` where norm([A,B,C]) is equal to one. @@ -1099,12 +1099,12 @@ function normalize_plane(plane) = // Function: plane_line_angle() -// Usage: +// Usage: // angle = plane_line_angle(plane,line); // Description: // Compute the angle between a plane [A, B, C, D] and a line, specified as a pair of points [p1,p2]. -// The resulting angle is signed, with the sign positive if the vector p2-p1 lies on -// the same side of the plane as the plane's normal vector. +// The resulting angle is signed, with the sign positive if the vector p2-p1 lies on +// the same side of the plane as the plane's normal vector. function plane_line_angle(plane, line) = assert( _valid_plane(plane), "Invalid plane." ) assert( _valid_line(line), "Invalid line." ) @@ -1113,7 +1113,7 @@ function plane_line_angle(plane, line) = normal = plane_normal(plane), sin_angle = linedir*normal, cos_angle = norm(cross(linedir,normal)) - ) + ) atan2(sin_angle,cos_angle); @@ -1189,19 +1189,19 @@ function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) = inside = [for (part = parts) if (point_in_polygon(mean(part), poly2d)>0) part ] - ) + ) !inside? undef : let( isegs = [for (seg = inside) lift_plane(seg, plane) ] - ) + ) isegs - ) + ) : bounded[0] && res[1]<0? undef : bounded[1] && res[1]>1? undef : let( proj = clockwise_polygon(project_plane(poly, p1, p2, p3)), pt = project_plane(res[0], p1, p2, p3) - ) + ) point_in_polygon(pt, proj) < 0 ? undef : res[0]; @@ -1212,7 +1212,7 @@ function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) = // Compute the point which is the intersection of the three planes, or the line intersection of two planes. // If you give three planes the intersection is returned as a point. If you give two planes the intersection // is returned as a list of two points on the line of intersection. If any two input planes are parallel -// or coincident then returns undef. +// or coincident then returns undef. function plane_intersection(plane1,plane2,plane3) = assert( _valid_plane(plane1) && _valid_plane(plane2) && (is_undef(plane3) ||_valid_plane(plane3)), "The input must be 2 or 3 planes." ) @@ -1220,15 +1220,15 @@ function plane_intersection(plane1,plane2,plane3) = ? let( matrix = [for(p=[plane1,plane2,plane3]) point3d(p)], rhs = [for(p=[plane1,plane2,plane3]) p[3]] - ) + ) linear_solve(matrix,rhs) - : let( normal = cross(plane_normal(plane1), plane_normal(plane2)) ) + : let( normal = cross(plane_normal(plane1), plane_normal(plane2)) ) approx(norm(normal),0) ? undef : let( matrix = [for(p=[plane1,plane2]) point3d(p)], rhs = [plane1[3], plane2[3]], point = linear_solve(matrix,rhs) - ) + ) point==[]? undef: [point, point+normal]; @@ -1244,13 +1244,13 @@ function coplanar(points, eps=EPSILON) = assert( is_path(points,dim=3) , "Input should be a list of 3D points." ) assert( is_finite(eps) && eps>=0, "The tolerance should be a non-negative number." ) len(points)<=2 ? false - : let( ip = noncollinear_triple(points,error=false,eps=eps) ) - ip == [] ? false : - let( plane = plane3pt(points[ip[0]],points[ip[1]],points[ip[2]]), + : let( ip = noncollinear_triple(points,error=false,eps=eps) ) + ip == [] ? false : + let( plane = plane3pt(points[ip[0]],points[ip[1]],points[ip[2]]), normal = point3d(plane) ) max( points*normal ) - plane[3]< eps*norm(normal); - + // Function: points_on_plane() // Usage: // points_on_plane(points, plane, ); @@ -1355,11 +1355,11 @@ function in_front_of_plane(plane, point) = function circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) = let(r = get_radius(r=r, d=d, dflt=undef)) assert(r!=undef, "Must specify either r or d.") - assert( ( is_path(pt1) && len(pt1)==3 && is_undef(pt2) && is_undef(pt3)) + assert( ( is_path(pt1) && len(pt1)==3 && is_undef(pt2) && is_undef(pt3)) || (is_matrix([pt1,pt2,pt3]) && (len(pt1)==2 || len(pt1)==3) ), "Invalid input points." ) - is_undef(pt2) - ? circle_2tangents(pt1[0], pt1[1], pt1[2], r=r, tangents=tangents) + is_undef(pt2) + ? circle_2tangents(pt1[0], pt1[1], pt1[2], r=r, tangents=tangents) : collinear(pt1, pt2, pt3)? undef : let( v1 = unit(pt1 - pt2), @@ -1369,7 +1369,7 @@ function circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) = a = vector_angle(v1, v2), hyp = r / sin(a/2), cp = pt2 + hyp * vmid - ) + ) !tangents ? [cp, n] : let( x = hyp * cos(a/2), @@ -1377,7 +1377,7 @@ function circle_2tangents(pt1, pt2, pt3, r, d, tangents=false) = tp2 = pt2 + x * v2, dang1 = vector_angle(tp1-cp,pt2-cp), dang2 = vector_angle(tp2-cp,pt2-cp) - ) + ) [cp, n, tp1, tp2, dang1, dang2]; module circle_2tangents(pt1, pt2, pt3, r, d, h, center=false) { @@ -1438,8 +1438,8 @@ module circle_2tangents(pt1, pt2, pt3, r, d, h, center=false) { // move_copies(pts) color("cyan") sphere(d=3, $fn=12); function circle_3points(pt1, pt2, pt3) = (is_undef(pt2) && is_undef(pt3) && is_list(pt1)) - ? circle_3points(pt1[0], pt1[1], pt1[2]) - : assert( is_vector(pt1) && is_vector(pt2) && is_vector(pt3) + ? circle_3points(pt1[0], pt1[1], pt1[2]) + : assert( is_vector(pt1) && is_vector(pt2) && is_vector(pt3) && max(len(pt1),len(pt2),len(pt3))<=3 && min(len(pt1),len(pt2),len(pt3))>=2, "Invalid point(s)." ) collinear(pt1,pt2,pt3)? [undef,undef,undef] : @@ -1447,17 +1447,17 @@ function circle_3points(pt1, pt2, pt3) = v = [ point3d(pt1), point3d(pt2), point3d(pt3) ], // triangle vertices ed = [for(i=[0:2]) v[(i+1)%3]-v[i] ], // triangle edge vectors pm = [for(i=[0:2]) v[(i+1)%3]+v[i] ]/2, // edge mean points - es = sortidx( [for(di=ed) norm(di) ] ), + es = sortidx( [for(di=ed) norm(di) ] ), e1 = ed[es[1]], // take the 2 longest edges e2 = ed[es[2]], - n0 = vector_axis(e1,e2), // normal standardization + n0 = vector_axis(e1,e2), // normal standardization n = n0.z<0? -n0 : n0, - sc = plane_intersection( + sc = plane_intersection( [ each e1, e1*pm[es[1]] ], // planes orthogonal to 2 edges [ each e2, e2*pm[es[2]] ], [ each n, n*v[0] ] ), // triangle plane - cp = len(pt1)+len(pt2)+len(pt3)>6 ? sc : [sc.x, sc.y], + cp = len(pt1)+len(pt2)+len(pt3)>6 ? sc : [sc.x, sc.y], r = norm(sc-v[0]) ) [ cp, r, n ]; @@ -1522,8 +1522,8 @@ function circle_point_tangents(r, d, cp, pt) = // circle 1. If the circles intersect then the interior tangents don't exist and the function // returns only two entries. If one circle is inside the other one then no tangents exist // so the function returns the empty set. When the circles are tangent a degenerate tangent line -// passes through the point of tangency of the two circles: this degenerate line is NOT returned. -// Example(2D): Four tangents, first in green, second in black, third in blue, last in red. +// passes through the point of tangency of the two circles: this degenerate line is NOT returned. +// Example(2D): 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; @@ -1541,7 +1541,7 @@ function circle_point_tangents(r, d, cp, pt) = // move(c2) stroke(circle(r=r2), width=.1, 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. +// Example(2D): 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; @@ -1550,7 +1550,7 @@ function circle_point_tangents(r, d, cp, pt) = // move(c2) stroke(circle(r=r2), width=.1, 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. +// 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. // $fn=32; // c1 = [4,4]; r1 = 4; // c2 = [5,5]; r2 = 2; @@ -1608,12 +1608,12 @@ function noncollinear_triple(points,error=true,eps=EPSILON) = [] : let( n = (pb-pa)/nrm, - distlist = [for(i=[0:len(points)-1]) _dist2line(points[i]-pa, n)] + distlist = [for(i=[0:len(points)-1]) _dist2line(points[i]-pa, n)] ) max(distlist)0 && len(pts[0])>0 , "Invalid pointlist." ) + assert(is_matrix(pts) && len(pts)>0 && len(pts[0])>0 , "Invalid pointlist." ) let(ptsT = transpose(pts)) [ [for(row=ptsT) min(row)], @@ -1669,7 +1669,7 @@ function furthest_point(pt, points) = // Usage: // area = polygon_area(poly); // Description: -// Given a 2D or 3D planar polygon, returns the area of that polygon. +// Given a 2D or 3D planar polygon, returns the area of that polygon. // If the polygon is self-crossing, the results are undefined. For non-planar points the result is undef. // When `signed` is true, a signed area is returned; a positive area indicates a counterclockwise polygon. // Arguments: @@ -1678,14 +1678,23 @@ function furthest_point(pt, points) = function polygon_area(poly, signed=false) = assert(is_path(poly), "Invalid polygon." ) len(poly)<3 ? 0 : + let( cpoly = close_path(simplify_path(poly)) ) len(poly[0])==2 - ? sum([for(i=[1:1:len(poly)-2]) cross(poly[i]-poly[0],poly[i+1]-poly[0]) ])/2 - : let( plane = plane_from_points(poly) ) - plane==undef? undef : - let( n = unit(plane_normal(plane)), - total = sum([for(i=[1:1:len(poly)-1]) cross(poly[i]-poly[0],poly[i+1]-poly[0])*n ])/2 - ) - signed ? total : abs(total); + ? sum([for(i=[1:1:len(poly)-2]) cross(poly[i]-poly[0],poly[i+1]-poly[0]) ])/2 + : let( plane = plane_from_points(poly) ) + plane==undef? undef : + let( + n = unit(plane_normal(plane)), + total = sum([ + for(i=[1:1:len(cpoly)-2]) + let( + v1 = cpoly[i] - cpoly[0], + v2 = cpoly[i+1] - cpoly[0] + ) + cross(v1,v2) * n + ])/2 + ) + signed ? total : abs(total); // Function: is_convex_polygon() @@ -1693,7 +1702,7 @@ function polygon_area(poly, signed=false) = // is_convex_polygon(poly); // Description: // Returns true if the given 2D polygon is convex. The result is meaningless if the polygon is not simple (self-intersecting). -// If the points are collinear the result is true. +// If the points are collinear the result is true. // Example: // is_convex_polygon(circle(d=50)); // Returns: true // Example: @@ -1702,14 +1711,14 @@ function polygon_area(poly, signed=false) = function is_convex_polygon(poly) = assert(is_path(poly,dim=2), "The input should be a 2D polygon." ) let( l = len(poly) ) - len([for( i = l-1, - c = cross(poly[(i+1)%l]-poly[i], poly[(i+2)%l]-poly[(i+1)%l]), + len([for( i = l-1, + c = cross(poly[(i+1)%l]-poly[i], poly[(i+2)%l]-poly[(i+1)%l]), s = sign(c); i>=0 && sign(c)==s; - i = i-1, - c = i<0? 0: cross(poly[(i+1)%l]-poly[i],poly[(i+2)%l]-poly[(i+1)%l]), + i = i-1, + c = i<0? 0: cross(poly[(i+1)%l]-poly[i],poly[(i+2)%l]-poly[(i+1)%l]), s = s==0 ? sign(c) : s - ) i + ) i ])== l; @@ -1748,7 +1757,7 @@ function polygon_shift_to_closest_point(poly, pt) = // newpoly = reindex_polygon(reference, poly); // Description: // Rotates and possibly reverses the point order of a 2d or 3d polygon path to optimize its pairwise point -// association with a reference polygon. The two polygons must have the same number of vertices and be the same dimension. +// association with a reference polygon. The two polygons must have the same number of vertices and be the same dimension. // The optimization is done by computing the distance, norm(reference[i]-poly[i]), between // corresponding pairs of vertices of the two polygons and choosing the polygon point order that // makes the total sum over all pairs as small as possible. Returns the reindexed polygon. Note @@ -1772,7 +1781,7 @@ function polygon_shift_to_closest_point(poly, pt) = // 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 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.") @@ -1784,14 +1793,14 @@ function reindex_polygon(reference, poly, return_error=false) = ? clockwise_polygon(poly) : ccw_polygon(poly), I = [for(i=[0:N-1]) 1], - val = [ for(k=[0:N-1]) -           [for(i=[0:N-1]) + val = [ for(k=[0:N-1]) +           [for(i=[0:N-1])              (reference[i]*poly[(i+k)%N]) ] ]*I, optimal_poly = polygon_shift(fixpoly, max_index(val)) ) return_error? [optimal_poly, min(poly*(I*poly)-2*val)] : optimal_poly; - + // Function: align_polygon() // Usage: @@ -1802,11 +1811,11 @@ function reindex_polygon(reference, poly, return_error=false) = // so if run time is a problem, use a smaller sampling of angles. Returns the rotated and reindexed // polygon. // Arguments: -// reference = reference polygon +// reference = reference polygon // poly = polygon to rotate into alignment with the reference // angles = list or range of angles to test // cp = centerpoint for rotations -// Example(2D): The original hexagon in yellow is not well aligned with the pentagon. Turning it so the faces line up gives an optimal alignment, shown in red. +// Example(2D): The original hexagon in yellow is not well aligned with the pentagon. Turning it so the faces line up gives an optimal alignment, shown in red. // $fn=32; // pentagon = subdivide_path(pentagon(side=2),60); // hexagon = subdivide_path(hexagon(side=2.7),60); @@ -1860,7 +1869,7 @@ function centroid(poly) = ] ) ) val[1]/val[0]/3; - + // Function: point_in_polygon() // Usage: @@ -1888,30 +1897,30 @@ function point_in_polygon(point, poly, eps=EPSILON, nonzero=true) = assert( is_finite(eps) && eps>=0, "Invalid tolerance." ) // Does the point lie on any edges? If so return 0. let( - on_brd = [for(i=[0:1:len(poly)-1]) - let( seg = select(poly,i,i+1) ) - if( !approx(seg[0],seg[1],eps=EPSILON) ) + on_brd = [for(i=[0:1:len(poly)-1]) + let( seg = select(poly,i,i+1) ) + if( !approx(seg[0],seg[1],eps=EPSILON) ) point_on_segment2d(point, seg, eps=eps)? 1:0 ] ) sum(on_brd) > 0 - ? 0 + ? 0 : nonzero ? // Compute winding number and return 1 for interior, -1 for exterior let( - windchk = [for(i=[0:1:len(poly)-1]) - let(seg=select(poly,i,i+1)) - if(!approx(seg[0],seg[1],eps=eps)) + windchk = [for(i=[0:1:len(poly)-1]) + let(seg=select(poly,i,i+1)) + if(!approx(seg[0],seg[1],eps=eps)) _point_above_below_segment(point, seg) ] ) sum(windchk) != 0 ? 1 : -1 : // or compute the crossings with the ray [point, point+[1,0]] - let( + let( n = len(poly), - cross = + cross = [for(i=[0:n-1]) - let( - p0 = poly[i]-point, + let( + p0 = poly[i]-point, p1 = poly[(i+1)%n]-point ) if( ( (p1.y>eps && p0.y<=0) || (p1.y<=0 && p0.y>eps) ) @@ -1970,7 +1979,7 @@ function reverse_polygon(poly) = // n = polygon_normal(poly); // Description: // Given a 3D planar polygon, returns a unit-length normal vector for the -// clockwise orientation of the polygon. +// clockwise orientation of the polygon. function polygon_normal(poly) = assert(is_path(poly,dim=3), "Invalid 3D polygon." ) let( @@ -2082,9 +2091,8 @@ function _split_polygon_at_z(poly, z) = // polys = A list of 3D polygons to split. // xs = A list of scalar X values to split at. function split_polygons_at_each_x(polys, xs, _i=0) = - assert( is_consistent(polys) && is_path(poly[0],dim=3) , - "The input list should contains only 3D polygons." ) - assert( is_finite(xs), "The split value list should contain only numbers." ) + assert( [for (poly=polys) if (!is_path(poly,3)) 1] == [], "Expects list of 3D paths.") + assert( is_vector(xs), "The split value list should contain only numbers." ) _i>=len(xs)? polys : split_polygons_at_each_x( [ @@ -2092,7 +2100,7 @@ function split_polygons_at_each_x(polys, xs, _i=0) = each _split_polygon_at_x(poly, xs[_i]) ], xs, _i=_i+1 ); - + // Function: split_polygons_at_each_y() // Usage: @@ -2103,9 +2111,8 @@ function split_polygons_at_each_x(polys, xs, _i=0) = // polys = A list of 3D polygons to split. // ys = A list of scalar Y values to split at. function split_polygons_at_each_y(polys, ys, _i=0) = -// assert( is_consistent(polys) && is_path(polys[0],dim=3) , // not all polygons should have the same length!!! - // "The input list should contains only 3D polygons." ) - assert( is_finite(ys) || is_vector(ys), "The split value list should contain only numbers." ) //*** + assert( [for (poly=polys) if (!is_path(poly,3)) 1] == [], "Expects list of 3D paths.") + assert( is_vector(ys), "The split value list should contain only numbers." ) _i>=len(ys)? polys : split_polygons_at_each_y( [ @@ -2124,9 +2131,8 @@ function split_polygons_at_each_y(polys, ys, _i=0) = // polys = A list of 3D polygons to split. // zs = A list of scalar Z values to split at. function split_polygons_at_each_z(polys, zs, _i=0) = - assert( is_consistent(polys) && is_path(poly[0],dim=3) , - "The input list should contains only 3D polygons." ) - assert( is_finite(zs), "The split value list should contain only numbers." ) + assert( [for (poly=polys) if (!is_path(poly,3)) 1] == [], "Expects list of 3D paths.") + assert( is_vector(zs), "The split value list should contain only numbers." ) _i>=len(zs)? polys : split_polygons_at_each_z( [ diff --git a/version.scad b/version.scad index 89e02f4..76088b4 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,479]; +BOSL_VERSION = [2,0,480]; // Section: BOSL Library Version Functions diff --git a/vnf.scad b/vnf.scad index a55e204..822328f 100644 --- a/vnf.scad +++ b/vnf.scad @@ -672,8 +672,11 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) = ], nonplanars = unique([ for (face = faces) let( - faceverts = [for (k=face) varr[k]] - ) if (!coplanar(faceverts)) [ + faceverts = [for (k=face) varr[k]], + area = polygon_area(faceverts) + ) + if (is_num(area) && abs(area) > EPSILON) + if (!coplanar(faceverts)) [ "ERROR", "NONPLANAR", "Face vertices are not coplanar", @@ -712,9 +715,12 @@ function vnf_validate(vnf, show_warns=true, check_isects=false) = if (v!=edge[0] && v!=edge[1]) let( a = varr[edge[0]], b = varr[v], - c = varr[edge[1]], + c = varr[edge[1]] + ) + if (a != b && b != c && a != c) let( pt = segment_closest_point([a,c],b) - ) if (pt == b) [ + ) + if (pt == b) [ "ERROR", "T_JUNCTION", "Vertex is mid-edge on another Face",