diff --git a/arrays.scad b/arrays.scad index 8f44fe8..37fee58 100644 --- a/arrays.scad +++ b/arrays.scad @@ -1224,6 +1224,7 @@ function block_matrix(M) = // its diagonal. The off diagonal entries are set to offdiag, // which is zero by default. function diagonal_matrix(diag,offdiag=0) = + assert(is_list(diag) && len(diag)>0) [for(i=[0:1:len(diag)-1]) [for(j=[0:len(diag)-1]) i==j?diag[i] : offdiag]]; @@ -1237,6 +1238,8 @@ function diagonal_matrix(diag,offdiag=0) = function submatrix_set(M,A,m=0,n=0) = assert(is_list(M)) assert(is_list(A)) + assert(is_int(m)) + assert(is_int(n)) let( badrows = [for(i=idx(A)) if (!is_list(A[i])) i]) assert(badrows==[], str("Input submatrix malformed rows: ",badrows)) [for(i=[0:1:len(M)-1]) diff --git a/coords.scad b/coords.scad index e45eab1..3d8be11 100644 --- a/coords.scad +++ b/coords.scad @@ -142,24 +142,24 @@ function xy_to_polar(x,y=undef) = let( // Function: project_plane() -// Usage: With 3 Points +// Usage: With the plane defined by 3 Points // xyz = project_plane(point, a, b, c); -// Usage: With Pointlist +// Usage: With the plane defined by Pointlist // xyz = project_plane(point, POINTLIST); -// Usage: With Plane Definition [A,B,C,D] Where Ax+By+Cz=D +// Usage: With the plane defined by Plane Definition [A,B,C,D] Where Ax+By+Cz=D // xyz = project_plane(point, PLANE); // Description: -// Converts the given 3D point from global coordinates to the 2D planar coordinates of the closest -// point on the plane. This coordinate system can be useful in taking a set of nearly coplanar +// Converts the given 3D points from global coordinates to the 2D planar coordinates of the closest +// points on the plane. This coordinate system can be useful in taking a set of nearly coplanar // points, and converting them to a pure XY set of coordinates for manipulation, before converting -// them back to the original 3D plane. -// Can be called one of three ways: -// - Given three points, `a`, `b`, and `c`, the planar coordinate system will have `[0,0]` at point `a`, and the Y+ axis will be towards point `b`. -// - Given a list of points, finds three reasonably spaced non-collinear points in the list and uses them as points `a`, `b`, and `c` as above. -// - Given a plane definition `[A,B,C,D]` where `Ax+By+Cz=D`, the closest point on that plane to the global origin at `[0,0,0]` will be the planar coordinate origin `[0,0]`. +// them back to the original 3D plane. The parameter `point` may be a single point or a list of points +// The plane may be given in one of three ways: +// - by three points, `a`, `b`, and `c`, the planar coordinate system will have `[0,0]` at point `a`, and the Y+ axis will be towards point `b`. +// - by a list of points passed by `a`, finds three reasonably spaced non-collinear points in the list and uses them as points `a`, `b`, and `c` as above. +// - by a plane definition `[A,B,C,D]` passed by `a` where `Ax+By+Cz=D`, the closest point on that plane to the global origin at `[0,0,0]` will be the planar coordinate origin `[0,0]`. // Arguments: // point = The 3D point, or list of 3D points to project into the plane's 2D coordinate system. -// a = A 3D point that the plane passes through. Used to define the plane. +// a = A 3D point that the plane passes through or a list of points or a plane definition vector. // b = A 3D point that the plane passes through. Used to define the plane. // c = A 3D point that the plane passes through. Used to define the plane. // Example: diff --git a/geometry.scad b/geometry.scad index 0ab9f73..97564db 100644 --- a/geometry.scad +++ b/geometry.scad @@ -762,8 +762,8 @@ function triangle_area(a,b,c) = // Usage: // plane3pt(p1, p2, p3); // Description: -// Generates the 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. +// 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. // Arguments: // p1 = The first point on the plane. @@ -777,7 +777,7 @@ function plane3pt(p1, p2, p3) = nrm = norm(crx) ) approx(nrm,0) ? [] : - concat(crx/nrm, [crx*p1]/nrm); + concat(crx, crx*p1)/nrm; // Function: plane3pt_indexed() @@ -785,7 +785,7 @@ function plane3pt(p1, p2, p3) = // plane3pt_indexed(points, i1, i2, i3); // Description: // Given a list of 3d points, and the indices of three of those points, -// generates the cartesian equation of a plane that those points all +// generates the normalized cartesian equation of a plane that those points all // lie on. If the points are not collinear, returns [A,B,C,D] where Ax+By+Cz=D is the equation of a plane. // If they are collinear, returns []. // Arguments: @@ -816,15 +816,15 @@ function plane3pt_indexed(points, i1, i2, i3) = function plane_from_normal(normal, pt=[0,0,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]); + concat(normal, normal*pt)/norm(normal); // Function: plane_from_points() // Usage: // plane_from_points(points, , ); // Description: -// Given a list of 3 or more coplanar 3D points, returns the coefficients of the cartesian equation of a plane, -// that is [A,B,C,D] where Ax+By+Cz=D is the equation of the plane. +// Given a list of 3 or more coplanar 3D points, returns the coefficients of the normalized cartesian equation of a plane, +// that is [A,B,C,D] where Ax+By+Cz=D is the equation of the plane where norm([A,B,C])=1. // 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: @@ -858,8 +858,8 @@ function plane_from_points(points, fast=false, eps=EPSILON) = // Usage: // plane_from_polygon(points, [fast], [eps]); // Description: -// Given a 3D planar polygon, returns the cartesian equation of its plane. -// Returns [A,B,C,D] where Ax+By+Cz=D is the equation of the plane. +// Given a 3D planar polygon, returns the normalized cartesian equation of its plane. +// Returns [A,B,C,D] where Ax+By+Cz=D is the equation of the plane where norm([A,B,C])=1. // If not all the points in the polygon are coplanar, then [] is returned. // If `fast` is true, the polygon coplanarity check is skipped and the plane may not contain all polygon points. // Arguments: @@ -897,8 +897,9 @@ function plane_normal(plane) = // Usage: // d = plane_offset(plane); // Description: -// Returns D, or the scalar offset of the plane from the origin. This can be a negative value. -// The absolute value of this is the distance of the plane from the origin at its closest approach. +// 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) = assert( _valid_plane(plane), "Invalid input plane." ) plane[3]/norm([plane.x, plane.y, plane.z]); @@ -923,7 +924,8 @@ function plane_offset(plane) = // stroke(xypath,closed=true); function plane_transform(plane) = let( - n = plane_normal(plane), + plane = normalize_plane(plane), + n = point3d(plane), cp = n * plane[3] ) rot(from=n, to=UP) * move(-cp); @@ -949,8 +951,8 @@ function projection_on_plane(plane, points) = p = len(points[0])==2 ? [for(pi=points) point3d(pi) ] : points, - plane = plane/norm([plane.x,plane.y,plane.z]), - n = [plane.x,plane.y,plane.z] + plane = normalize_plane(plane), + n = point3d(plane) ) [for(pi=p) pi - (pi*n - plane[3])*n]; @@ -961,7 +963,8 @@ function projection_on_plane(plane, points) = // Description: // Returns the point on the plane that is closest to the origin. function plane_point_nearest_origin(plane) = - plane_normal(plane) * plane[3]; + let( plane = normalize_plane(plane) ) + point3d(plane) * plane[3]; // Function: distance_from_plane() @@ -980,8 +983,8 @@ function plane_point_nearest_origin(plane) = function distance_from_plane(plane, point) = assert( _valid_plane(plane), "Invalid input plane." ) assert( is_vector(point,3), "The point should be a 3D point." ) - let( nrml = [plane.x, plane.y, plane.z] ) - ( nrml* point - plane[3])/norm(nrml); + let( plane = normalize_plane(plane) ) + point3d(plane)* point - plane[3]; // Function: closest_point_on_plane() @@ -996,9 +999,9 @@ function distance_from_plane(plane, point) = function closest_point_on_plane(plane, point) = assert( _valid_plane(plane), "Invalid input plane." ) assert( is_vector(point,3), "Invalid point." ) - let( - n = unit([plane.x, plane.y, plane.z]), - d = distance_from_plane(plane, point) + let( plane = normalize_plane(plane), + n = point3d(plane), + d = n*point - plane[3] // distance from plane ) point - n*d; @@ -1008,23 +1011,29 @@ 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( - l0 = line[0], // Ray start point - u = line[1] - l0, // Ray direction vector - n = plane_normal(plane), - p0 = n * plane[3], // A point on the plane - w = l0 - p0 // Vector from plane point to ray start - ) approx(n*u, 0, eps=eps) ? ( - // Line is parallel to plane. - approx(n*w, 0, eps=eps) - ? [line, undef] // Line is on the plane. - : undef // Line never intersects the plane. - ) : let( - t = (-n * w) / (n * u) // Distance ratio along ray - ) [ l0 + u*t, t ]; + 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 ? + ? approx(a,0,eps) // is line[0] on the plane ? + ? [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: +// nplane = normalize_plane(plane); +// Description: +// Returns a new representation [A,B,C,D] of `plane` where norm([A,B,C]) is equal to one. +function normalize_plane(plane) = + assert( _valid_plane(plane), "Invalid plane." ) + plane/norm(point3d(plane)); // Function: plane_line_angle() -// Usage: plane_line_angle(plane,line) +// 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 @@ -1033,11 +1042,12 @@ function plane_line_angle(plane, line) = assert( _valid_plane(plane), "Invalid plane." ) assert( _valid_line(line), "Invalid line." ) let( - vect = line[1]-line[0], - zplane = plane_normal(plane), - sin_angle = vect*zplane/norm(zplane)/norm(vect) + linedir = unit(line[1]-line[0]), + normal = plane_normal(plane), + sin_angle = linedir*normal, + cos_angle = norm(cross(linedir,normal)) ) - asin(constrain(sin_angle,-1,1)); + atan2(sin_angle,cos_angle); // Function: plane_line_intersection() @@ -1085,7 +1095,7 @@ function plane_line_intersection(plane, line, bounded=false, eps=EPSILON) = function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) = assert( is_finite(eps) && eps>=0, "The tolerance should be a positive number." ) assert(is_path(poly,dim=3), "Invalid polygon." ) - assert(is_bool(bounded) || (is_list(bounded) && len(bounded)==2), "Invalid bound condition(s).") + assert(!is_list(bounded) || len(bounded)==2, "Invalid bound condition(s).") assert(_valid_line(line,dim=3,eps=eps), "Invalid line." ) let( bounded = is_list(bounded)? bounded : [bounded, bounded], @@ -1094,7 +1104,6 @@ function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) = ) indices==[] ? undef : let( - indices = sort(indices), p1 = poly[indices[0]], p2 = poly[indices[1]], p3 = poly[indices[2]], @@ -1120,8 +1129,8 @@ function polygon_line_intersection(poly, line, bounded=false, eps=EPSILON) = ) isegs ) - : bounded[0]&&res[1]<0? [] : - bounded[1]&&res[1]>1? [] : + : 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) @@ -1142,15 +1151,15 @@ function plane_intersection(plane1,plane2,plane3) = "The input must be 2 or 3 planes." ) is_def(plane3) ? let( - matrix = [for(p=[plane1,plane2,plane3]) select(p,0,2)], + 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)) ) approx(norm(normal),0) ? undef : let( - matrix = [for(p=[plane1,plane2]) select(p,0,2)], - rhs = [for(p=[plane1,plane2]) p[3]], + matrix = [for(p=[plane1,plane2]) point3d(p)], + rhs = [plane1[3], plane2[3]], point = linear_solve(matrix,rhs) ) point==[]? undef: [point, point+normal]; diff --git a/joiners.scad b/joiners.scad index 1178d4e..1d76c36 100644 --- a/joiners.scad +++ b/joiners.scad @@ -517,8 +517,10 @@ module dovetail(gender, length, l, width, w, height, h, angle, slope, taper, bac assert(count3<=1 || (radius==0 && chamfer==0), "Do not specify both chamfer and radius"); slope = is_def(slope) ? slope : is_def(angle) ? 1/tan(angle) : 6; - width = gender == "male" ? w : w + 2*$slop; - height = h + (gender == "female" ? 2*$slop : 0); + extra_slop = gender == "female" ? 2*$slop : 0; + width = w + extra_slop; + height = h + extra_slop; + back_width = back_width + extra_slop; front_offset = is_def(taper) ? -extra * tan(taper) : is_def(back_width) ? extra * (back_width-width)/length/2 : 0; diff --git a/math.scad b/math.scad index 599a0c4..935c44a 100644 --- a/math.scad +++ b/math.scad @@ -712,6 +712,7 @@ function matrix_inverse(A) = assert(is_matrix(A,square=true),"Input to matrix_inverse() must be a square matrix") linear_solve(A,ident(len(A))); + // Function: null_space() // Usage: // null_space(A) @@ -723,7 +724,7 @@ function null_space(A,eps=1e-12) = let( Q_R=qr_factor(transpose(A),pivot=true), R=Q_R[1], - zrow = [for(i=idx(R)) if (is_zero(R[i],eps)) i] + zrow = [for(i=idx(R)) if (all_zero(R[i],eps)) i] ) len(zrow)==0 ? [] @@ -894,125 +895,148 @@ function is_matrix(A,m,n,square=false) = // squares of all of the entries of the matrix. On vectors it is the same as the usual 2-norm. // This is an easily computed norm that is convenient for comparing two matrices. function norm_fro(A) = - sqrt(sum([for(entry=A) sum_of_squares(entry)])); + assert(is_matrix(A) || is_vector(A)) + norm(flatten(A)); // Section: Comparisons and Logic -// Function: is_zero() +// Function: all_zero() // Usage: -// is_zero(x); +// all_zero(x); // Description: -// Returns true if the number passed to it is approximately zero, to within `eps`. +// Returns true if the finite number passed to it is approximately zero, to within `eps`. // If passed a list, recursively checks if all items in the list are approximately zero. // Otherwise, returns false. // Arguments: // x = The value to check. // eps = The maximum allowed variance. Default: `EPSILON` (1e-9) // Example: -// is_zero(0); // Returns: true. -// is_zero(1e-3); // Returns: false. -// is_zero([0,0,0]); // Returns: true. -// is_zero([0,0,1e-3]); // Returns: false. -function is_zero(x, eps=EPSILON) = - is_list(x)? (x != [] && [for (xx=x) if(!is_zero(xx,eps=eps)) 1] == []) : - is_num(x)? approx(x,eps) : +// all_zero(0); // Returns: true. +// all_zero(1e-3); // Returns: false. +// all_zero([0,0,0]); // Returns: true. +// all_zero([0,0,1e-3]); // Returns: false. +function all_zero(x, eps=EPSILON) = + is_finite(x)? approx(x,eps) : + is_list(x)? (x != [] && [for (xx=x) if(!all_zero(xx,eps=eps)) 1] == []) : false; -// Function: is_positive() +// Function: all_nonzero() // Usage: -// is_positive(x); +// all_nonzero(x); // Description: -// Returns true if the number passed to it is greater than zero. +// Returns true if the finite number passed to it is not almost zero, to within `eps`. +// If passed a list, recursively checks if all items in the list are not almost zero. +// Otherwise, returns false. +// Arguments: +// x = The value to check. +// eps = The maximum allowed variance. Default: `EPSILON` (1e-9) +// Example: +// all_nonzero(0); // Returns: false. +// all_nonzero(1e-3); // Returns: true. +// all_nonzero([0,0,0]); // Returns: false. +// all_nonzero([0,0,1e-3]); // Returns: false. +// all_nonzero([1e-3,1e-3,1e-3]); // Returns: true. +function all_nonzero(x, eps=EPSILON) = + is_finite(x)? !approx(x,eps) : + is_list(x)? (x != [] && [for (xx=x) if(!all_nonzero(xx,eps=eps)) 1] == []) : + false; + + +// Function: all_positive() +// Usage: +// all_positive(x); +// Description: +// Returns true if the finite number passed to it is greater than zero. // If passed a list, recursively checks if all items in the list are positive. // Otherwise, returns false. // Arguments: // x = The value to check. // Example: -// is_positive(-2); // Returns: false. -// is_positive(0); // Returns: false. -// is_positive(2); // Returns: true. -// is_positive([0,0,0]); // Returns: false. -// is_positive([0,1,2]); // Returns: false. -// is_positive([3,1,2]); // Returns: true. -// is_positive([3,-1,2]); // Returns: false. -function is_positive(x) = - is_list(x)? (x != [] && [for (xx=x) if(!is_positive(xx)) 1] == []) : +// all_positive(-2); // Returns: false. +// all_positive(0); // Returns: false. +// all_positive(2); // Returns: true. +// all_positive([0,0,0]); // Returns: false. +// all_positive([0,1,2]); // Returns: false. +// all_positive([3,1,2]); // Returns: true. +// all_positive([3,-1,2]); // Returns: false. +function all_positive(x) = is_num(x)? x>0 : + is_list(x)? (x != [] && [for (xx=x) if(!all_positive(xx)) 1] == []) : false; -// Function: is_negative() +// Function: all_negative() // Usage: -// is_negative(x); +// all_negative(x); // Description: -// Returns true if the number passed to it is less than zero. +// Returns true if the finite number passed to it is less than zero. // If passed a list, recursively checks if all items in the list are negative. // Otherwise, returns false. // Arguments: // x = The value to check. // Example: -// is_negative(-2); // Returns: true. -// is_negative(0); // Returns: false. -// is_negative(2); // Returns: false. -// is_negative([0,0,0]); // Returns: false. -// is_negative([0,1,2]); // Returns: false. -// is_negative([3,1,2]); // Returns: false. -// is_negative([3,-1,2]); // Returns: false. -// is_negative([-3,-1,-2]); // Returns: true. -function is_negative(x) = - is_list(x)? (x != [] && [for (xx=x) if(!is_negative(xx)) 1] == []) : +// all_negative(-2); // Returns: true. +// all_negative(0); // Returns: false. +// all_negative(2); // Returns: false. +// all_negative([0,0,0]); // Returns: false. +// all_negative([0,1,2]); // Returns: false. +// all_negative([3,1,2]); // Returns: false. +// all_negative([3,-1,2]); // Returns: false. +// all_negative([-3,-1,-2]); // Returns: true. +function all_negative(x) = is_num(x)? x<0 : + is_list(x)? (x != [] && [for (xx=x) if(!all_negative(xx)) 1] == []) : false; -// Function: is_nonpositive() +// Function: all_nonpositive() // Usage: -// is_nonpositive(x); +// all_nonpositive(x); // Description: -// Returns true if the number passed to it is less than or equal to zero. +// Returns true if the finite number passed to it is less than or equal to zero. // If passed a list, recursively checks if all items in the list are nonpositive. // Otherwise, returns false. // Arguments: // x = The value to check. // Example: -// is_nonpositive(-2); // Returns: true. -// is_nonpositive(0); // Returns: true. -// is_nonpositive(2); // Returns: false. -// is_nonpositive([0,0,0]); // Returns: true. -// is_nonpositive([0,1,2]); // Returns: false. -// is_nonpositive([3,1,2]); // Returns: false. -// is_nonpositive([3,-1,2]); // Returns: false. -// is_nonpositive([-3,-1,-2]); // Returns: true. -function is_nonpositive(x) = - is_list(x)? (x != [] && [for (xx=x) if(!is_nonpositive(xx)) 1] == []) : +// all_nonpositive(-2); // Returns: true. +// all_nonpositive(0); // Returns: true. +// all_nonpositive(2); // Returns: false. +// all_nonpositive([0,0,0]); // Returns: true. +// all_nonpositive([0,1,2]); // Returns: false. +// all_nonpositive([3,1,2]); // Returns: false. +// all_nonpositive([3,-1,2]); // Returns: false. +// all_nonpositive([-3,-1,-2]); // Returns: true. +function all_nonpositive(x) = is_num(x)? x<=0 : + is_list(x)? (x != [] && [for (xx=x) if(!all_nonpositive(xx)) 1] == []) : false; -// Function: is_nonnegative() +// Function: all_nonnegative() // Usage: -// is_nonnegative(x); +// all_nonnegative(x); // Description: -// Returns true if the number passed to it is greater than or equal to zero. +// Returns true if the finite number passed to it is greater than or equal to zero. // If passed a list, recursively checks if all items in the list are nonnegative. // Otherwise, returns false. // Arguments: // x = The value to check. // Example: -// is_nonnegative(-2); // Returns: false. -// is_nonnegative(0); // Returns: true. -// is_nonnegative(2); // Returns: true. -// is_nonnegative([0,0,0]); // Returns: true. -// is_nonnegative([0,1,2]); // Returns: true. -// is_nonnegative([0,-1,-2]); // Returns: false. -// is_nonnegative([3,1,2]); // Returns: true. -// is_nonnegative([3,-1,2]); // Returns: false. -// is_nonnegative([-3,-1,-2]); // Returns: false. -function is_nonnegative(x) = - is_list(x)? (x != [] && [for (xx=x) if(!is_nonnegative(xx)) 1] == []) : +// all_nonnegative(-2); // Returns: false. +// all_nonnegative(0); // Returns: true. +// all_nonnegative(2); // Returns: true. +// all_nonnegative([0,0,0]); // Returns: true. +// all_nonnegative([0,1,2]); // Returns: true. +// all_nonnegative([0,-1,-2]); // Returns: false. +// all_nonnegative([3,1,2]); // Returns: true. +// all_nonnegative([3,-1,2]); // Returns: false. +// all_nonnegative([-3,-1,-2]); // Returns: false. +function all_nonnegative(x) = is_num(x)? x>=0 : + is_list(x)? (x != [] && [for (xx=x) if(!all_nonnegative(xx)) 1] == []) : false; diff --git a/mutators.scad b/mutators.scad index 4db899b..a5a4634 100644 --- a/mutators.scad +++ b/mutators.scad @@ -9,9 +9,61 @@ ////////////////////////////////////////////////////////////////////// -// Section: Halving Mutators +// Section: Volume Division Mutators ////////////////////////////////////////////////////////////////////// +// Module: bounding_box() +// Usage: +// bounding_box() ... +// Description: +// Returns an axis-aligned cube shape that exactly contains all the 3D children given. +// Arguments: +// excess = The amount that the bounding box should be larger than needed to bound the children, in each axis. +// Example: +// #bounding_box() { +// translate([10,8,4]) cube(5); +// translate([3,0,12]) cube(2); +// } +// translate([10,8,4]) cube(5); +// translate([3,0,12]) cube(2); +module bounding_box(excess=0) { + xs = excess>0? excess : 1; + // a 3D approx. of the children projection on X axis + module _xProjection() + linear_extrude(xs, center=true) + hull() + projection() + rotate([90,0,0]) + linear_extrude(xs, center=true) + projection() + children(); + + // a bounding box with an offset of 1 in all axis + module _oversize_bbox() { + minkowski() { + _xProjection() children(); // x axis + rotate(-90) _xProjection() rotate(90) children(); // y axis + rotate([0,-90,0]) _xProjection() rotate([0,90,0]) children(); // z axis + } + } + + // offset children() (a cube) by -1 in all axis + module _shrink_cube() { + intersection() { + translate([ 1, 1, 1]) children(); + translate([-1,-1,-1]) children(); + } + } + + render(convexity=2) + if (excess>0) { + _oversize_bbox() children(); + } else { + _shrink_cube() _oversize_bbox() children(); + } +} + + // Module: half_of() // // Usage: @@ -445,7 +497,6 @@ module offset3d(r=1, size=100, convexity=10) { } - // Module: round2d() // Usage: // round2d(r) ... @@ -512,6 +563,39 @@ module shell2d(thickness, or=0, ir=0, fill=0, round=0) } +// Module: minkowski_difference() +// Usage: +// minkowski_difference() { base_shape(); diff_shape(); ... } +// Description: +// Takes a 3D base shape and one or more 3D diff shapes, carves out the diff shapes from the +// surface of the base shape, in a way complementary to how `minkowski()` unions shapes to the +// surface of its base shape. +// Example: +// minkowski_difference() { +// union() { +// cube([120,70,70], center=true); +// cube([70,120,70], center=true); +// cube([70,70,120], center=true); +// } +// sphere(r=10); +// } +module minkowski_difference() { + difference() { + bounding_box(excess=0) children(0); + render(convexity=10) { + minkowski() { + difference() { + bounding_box(excess=1) children(0); + children(0); + } + for (i=[1,1,$children-1]) children(i); + } + } + } +} + + + ////////////////////////////////////////////////////////////////////// // Section: Colors ////////////////////////////////////////////////////////////////////// diff --git a/regions.scad b/regions.scad index fa761f0..fb72723 100644 --- a/regions.scad +++ b/regions.scad @@ -180,8 +180,9 @@ function split_path_at_region_crossings(path, region, closed=true, eps=EPSILON) ), subpaths = [ for (p = pair(crossings)) - deduplicate(eps=eps, - path_subselect(path, p[0][0], p[0][1], p[1][0], p[1][1], closed=closed) + deduplicate( + path_subselect(path, p[0][0], p[0][1], p[1][0], p[1][1], closed=closed), + eps=eps ) ] ) diff --git a/tests/test_geometry.scad b/tests/test_geometry.scad index e69a8ac..3fd0512 100644 --- a/tests/test_geometry.scad +++ b/tests/test_geometry.scad @@ -4,6 +4,8 @@ include <../std.scad> //the commented lines are for tests to be written //the tests are ordered as they appear in geometry.scad + + test_point_on_segment2d(); test_point_left_of_line2d(); test_collinear(); @@ -35,33 +37,30 @@ test_tri_calc(); test_triangle_area(); test_plane3pt(); test_plane3pt_indexed(); -//test_plane_from_normal(); +test_plane_from_normal(); test_plane_from_points(); -//test_plane_from_polygon(); +test_plane_from_polygon(); test_plane_normal(); -//test_plane_offset(); -//test_plane_transform(); +test_plane_offset(); +test_plane_transform(); test_projection_on_plane(); -//test_plane_point_nearest_origin(); +test_plane_point_nearest_origin(); test_distance_from_plane(); -test_find_circle_2tangents(); -test_find_circle_3points(); -test_circle_point_tangents(); -test_tri_functions(); -//test_closest_point_on_plane(); -//test__general_plane_line_intersection(); -//test_plane_line_angle(); -//test_plane_line_intersection(); +test_closest_point_on_plane(); +test__general_plane_line_intersection(); +test_plane_line_angle(); +test_normalize_plane(); +test_plane_line_intersection(); test_polygon_line_intersection(); -//test_plane_intersection(); +test_plane_intersection(); test_coplanar(); test_points_on_plane(); test_in_front_of_plane(); -//test_find_circle_2tangents(); -//test_find_circle_3points(); -//test_circle_point_tangents(); -//test_circle_circle_tangents(); +test_find_circle_2tangents(); +test_find_circle_3points(); +test_circle_point_tangents(); + test_noncollinear_triple(); test_pointlist_bounds(); test_closest_point(); @@ -92,24 +91,204 @@ test_simplify_path(); test_simplify_path_indexed(); test_is_region(); + // to be used when there are two alternative symmetrical outcomes -// from a function like a plane output. +// from a function like a plane output; v must be a vector function standardize(v) = v==[]? [] : - sign([for(vi=v) if( ! approx(vi,0)) vi,0 ][0])*v; + let( i = max_index([for(vi=v) abs(vi) ]), + s = sign(v[i]) ) + v*s; + + +module assert_std(vc,ve,info) { assert_approx(standardize(vc),standardize(ve),info); } + + +function info_str(list,i=0,string=chr(10)) = + assert(i>=len(list) || (is_list(list[i])&&len(list[i])>=2), "Invalid list for info_str." ) + i>=len(list) + ? str(string) + : info_str(list,i+1,str(string,str(list[i][0],_valstr(list[i][1]),chr(10)))); + + +module test_closest_point_on_plane(){ + plane = rands(-5,5,4)+[10,0,0,0]; + point = rands(-1,1,3); + point2 = closest_point_on_plane(plane,point); + assert_approx(norm(point-point2), abs(distance_from_plane(plane,point))); +} +*test_closest_point_on_plane(); + + +module test_normalize_plane(){ + plane = rands(-5,5,4)+[10,0,0,0]; + plane2 = normalize_plane(plane); + assert_approx(norm(point3d(plane2)),1); + assert_approx(plane*plane2[3],plane2*plane[3]); +} +*test_normalize_plane(); + +module test_plane_line_intersection(){ + line = [rands(-1,1,3),rands(-1,1,3)+[2,0,0]]; + plane1 = plane_from_normal(line[1]-line[0],2*line[0]-line[1]); // plane disjoint from segment + plane2 = plane_from_normal(line[1]-line[0],(line[0]+line[1])/2); // through middle point of line + plane3 = plane3pt(line[1],line[0], rands(-1,1,3)+[0,3,0]); // containing line + plane4 = plane3pt(line[1],line[0], rands(-1,1,3)+[0,3,0])+[0,0,0,1]; // parallel to line + info1 = info_str([ ["line = ",line],["plane = ",plane1]]); + assert_approx(plane_line_intersection(plane1, line),2*line[0]-line[1],info1); + assert_approx(plane_line_intersection(plane1, line,[true,false]),undef,info1); + assert_approx(plane_line_intersection(plane1, line,[false,true]),2*line[0]-line[1],info1); + assert_approx(plane_line_intersection(plane1, line,[true, true]),undef,info1); + info2 = info_str([ ["line = ",line],["plane = ",plane2]]); + assert_approx(plane_line_intersection(plane2, line),(line[0]+line[1])/2,info2); + assert_approx(plane_line_intersection(plane2, line,[true,false]),(line[0]+line[1])/2,info2); + assert_approx(plane_line_intersection(plane2, line,[false,true]),(line[0]+line[1])/2,info2); + assert_approx(plane_line_intersection(plane2, line,[true, true]),(line[0]+line[1])/2,info2); + info3 = info_str([ ["line = ",line],["plane = ",plane3]]); + assert_approx(plane_line_intersection(plane3, line),line,info3); + assert_approx(plane_line_intersection(plane3, line,[true,false]),line,info3); + assert_approx(plane_line_intersection(plane3, line,[false,true]),line,info3); + assert_approx(plane_line_intersection(plane3, line,[true, true]),line,info3); + info4 = info_str([ ["line = ",line],["plane = ",plane4]]); + assert_approx(plane_line_intersection(plane4, line),undef,info4); + assert_approx(plane_line_intersection(plane4, line,[true,false]),undef,info4); + assert_approx(plane_line_intersection(plane4, line,[false,true]),undef,info4); + assert_approx(plane_line_intersection(plane4, line,[true, true]),undef,info4); +} +*test_plane_line_intersection(); + + +module test_plane_intersection(){ + line = [ rands(-1,1,3), rands(-1,1,3)+[2,0,0] ]; // a valid line + pt0 = line[0]-[2,0,0]; // 2 points not on the line + pt1 = line[1]-[0,2,0]; + plane01 = plane3pt(line[0],line[1],pt0); + plane02 = plane3pt(line[0],line[1],pt1); + plane03 = plane3pt(line[0],pt0,pt1); + info = info_str([["plane1 = ",plane01],["plane2 = ",plane02],["plane3 = ",plane03]]); + assert_approx(plane_intersection(plane01,plane02,plane03),line[0],info); + assert_approx(plane_intersection(plane01,2*plane01),undef,info); + lineInters = plane_intersection(plane01,plane02); + assert_approx(line_closest_point(lineInters,line[0]), line[0], info); + assert_approx(line_closest_point(lineInters,line[1]), line[1], info); +} +*test_plane_intersection(); + + +module test_plane_point_nearest_origin(){ + point = rands(-1,1,3)+[2,0,0]; // a non zero vector + plane = [ each point, point*point]; // a plane containing `point` + info = info_str([["point = ",point],["plane = ",plane]]); + assert_approx(plane_point_nearest_origin(plane),point,info); + assert_approx(plane_point_nearest_origin([each point,5]),5*unit(point)/norm(point),info); +} +test_plane_point_nearest_origin(); + + +module test_plane_transform(){ + normal = rands(-1,1,3)+[2,0,0]; + offset = rands(-1,1,1)[0]; + info = info_str([["normal = ",normal],["offset = ",offset]]); + assert_approx(plane_transform([0,0,1,offset]),move([0,0,-offset]),info ); + assert_approx(plane_transform([0,1,0,offset]),xrot(90)*move([0,-offset,0]),info ); +} +*test_plane_transform(); + + +module test_plane_offset(){ + plane = rands(-1,1,4)+[2,0,0,0]; // a valid plane + info = info_str([["plane = ",plane]]); + assert_approx(plane_offset(plane), normalize_plane(plane)[3],info); + assert_approx(plane_offset([1,1,1,1]), 1/sqrt(3),info); +} +*test_plane_offset(); + +module test_plane_from_polygon(){ + poly1 = [ rands(-1,1,3), rands(-1,1,3)+[2,0,0], rands(-1,1,3)+[0,2,2] ]; + poly2 = concat(poly1, [sum(poly1)/3] ); + info = info_str([["poly1 = ",poly1],["poly2 = ",poly2]]); + assert_std(plane_from_polygon(poly1),plane3pt(poly1[0],poly1[1],poly1[2]),info); + assert_std(plane_from_polygon(poly2),plane3pt(poly1[0],poly1[1],poly1[2]),info); +} +*test_plane_from_polygon(); + +module test_plane_from_normal(){ + normal = rands(-1,1,3)+[2,0,0]; + point = rands(-1,1,3); + displ = normal*point; + info = info_str([["normal = ",normal],["point = ",point],["displ = ",displ]]); + assert_approx(plane_from_normal(normal,point)*[each point,-1],0,info); + assert_std(plane_from_normal(normal,point),normalize_plane([each normal,displ]),info); + assert_std(plane_from_normal([1,1,1],[1,2,3]),[0.57735026919,0.57735026919,0.57735026919,3.46410161514]); +} +*test_plane_from_normal(); + +module test_plane_line_angle() { + angs = rands(0,360,3); + displ = rands(-1,1,1)[0]; + info = info_str([["angs = ",angs],["displ = ",displ]]); + assert_approx(plane_line_angle([each rot(angs,p=[0,0,1]),displ],[[0,0,0],rot(angs,p=[0,0,1])]),90,info); + assert_approx(plane_line_angle([each rot(angs,p=[0,0,1]),displ],[[0,0,0],rot(angs,p=[0,1,1])]),45,info); + assert_approx(plane_line_angle([each rot(angs,p=[0,0,1]),0],[[0,0,0],rot(angs,p=[1,1,1])]),35.2643896828); +} +*test_plane_line_angle(); + +module test__general_plane_line_intersection() { + CRLF = chr(10); + // general line + plane1 = rands(-1,1,4)+[2,0,0,0]; // a random valid plane (normal!=0) + line1 = [ rands(-1,1,3), rands(-1,1,3)+[2,0,0] ]; // a random valid line (line1[0]!=line1[1]) + inters1 = _general_plane_line_intersection(plane1, line1); + info1 = info_str([["line = ",line1],["plane = ",plane1]]); + if(inters1==undef) { // parallel to the plane ? + assert_approx( point3d(plane1)*(line1[1]-line1[0]), 0, info1); + assert( point3d(plane1)*line1[0]== plane1[3], info1); // not on the plane + } + if( inters1[1]==undef) { // on the plane ? + assert_approx( point3d(plane1)*(line1[1]-line1[0]), 0, info1); + assert_approx(point3d(plane1)*line1[0],plane1[3], info1) ; // on the plane + } + else { + interspoint = line1[0]+inters1[1]*(line1[1]-line1[0]); + assert_approx(inters1[0],interspoint, info1); + assert_approx(point3d(plane1)*inters1[0], plane1[3], info1); // interspoint on the plane + assert_approx(distance_from_plane(plane1, inters1[0]), 0, info1); // inters1[0] on the plane + } + + // line parallel to the plane + line2 = [ rands(-1,1,3)+[0,2,0], rands(-1,1,3)+[2,0,0] ]; // a random valid line2 + // not containing the origin + plane0 = plane_from_points([line2[0], line2[1], [0,0,0]]); // plane cointaining the line + plane2 = plane_from_normal(plane_normal(plane0), [5,5,5]); + inters2 = _general_plane_line_intersection(plane2, line2); + info2 = info_str([["line = ",line2],["plane = ",plane2]]); + assert(inters2==undef, info2); + + // line on the plane + line3 = [ rands(-1,1,3), rands(-1,1,3)+[2,0,0] ]; // a random valid line + imax = max_index(line3[1]-line3[0]); + w = [for(j=[0:2]) imax==j? 0: 3 ]; + p3 = line3[0] + cross(line3[1]-line3[0],w); // a point not on the line + plane3 = plane_from_points([line3[0], line3[1], p3]); // plane containing line + inters3 = _general_plane_line_intersection(plane3, line3); + info3 = info_str([["line = ",line3],["plane = ",plane3]]); + assert(!is_undef(inters3) && inters3[1]==undef, info3); + assert_approx(inters3[0], line3, info3); +} +*test__general_plane_line_intersection(); -module assert_std(vc,ve) { assert(standardize(vc)==standardize(ve)); } module test_points_on_plane() { pts = [for(i=[0:40]) rands(-1,1,3) ]; dir = rands(-10,10,3); - normal0 = unit([1,2,3]); + normal0 = [1,2,3]; ang = rands(0,360,1)[0]; normal = rot(a=ang,p=normal0); plane = [each normal, normal*dir]; prj_pts = projection_on_plane(plane,pts); - assert(points_on_plane(prj_pts,plane)); - assert(!points_on_plane(concat(pts,[normal-dir]),plane)); + info = info_str([["pts = ",pts],["dir = ",dir],["ang = ",ang]]); + assert(points_on_plane(prj_pts,plane),info); + assert(!points_on_plane(concat(pts,[normal-dir]),plane),info); } *test_points_on_plane(); @@ -122,14 +301,15 @@ module test_projection_on_plane(){ plane = [each normal, 0]; planem = [each normal, normal*dir]; pts = [for(i=[1:10]) rands(-1,1,3)]; + info = info_str([["ang = ",ang],["dir = ",dir]]); assert_approx( projection_on_plane(plane,pts), - projection_on_plane(plane,projection_on_plane(plane,pts))); + projection_on_plane(plane,projection_on_plane(plane,pts)),info); assert_approx( projection_on_plane(plane,pts), - rot(a=ang,p=projection_on_plane(plane0,rot(a=-ang,p=pts)))); + rot(a=ang,p=projection_on_plane(plane0,rot(a=-ang,p=pts))),info); assert_approx( move((-normal*dir)*normal,p=projection_on_plane(planem,pts)), - projection_on_plane(plane,pts)); + projection_on_plane(plane,pts),info); assert_approx( move((normal*dir)*normal,p=projection_on_plane(plane,pts)), - projection_on_plane(planem,pts)); + projection_on_plane(planem,pts),info); } *test_projection_on_plane(); @@ -543,12 +723,43 @@ module test_distance_from_plane() { module test_polygon_line_intersection() { - poly1 = [[50,50,50], [50,-50,50], [-50,-50,50]]; - assert_approx(polygon_line_intersection(poly1, [CENTER, UP]), [0,0,50]); - assert_approx(polygon_line_intersection(poly1, [CENTER, UP+RIGHT]), [50,0,50]); - assert_approx(polygon_line_intersection(poly1, [CENTER, UP+BACK+RIGHT]), [50,50,50]); - assert_approx(polygon_line_intersection(poly1, [[0,0,50], [1,0,50]]), [[[0,0,50], [50,0,50]]]); - assert_approx(polygon_line_intersection(poly1, [[0,0,0], [1,0,0]]), undef); + poly0 = [ [-10,-10, 0],[10,-10, 0],[10,10,0],[0,5,0],[-10,10,0] ]; + line0 = [ [-3,7.5,0],[3,7.5,0] ]; // a segment on poly0 plane, out of poly0 + angs = rands(0,360,3); + poly = rot(angs,p=poly0); + lineon = rot(angs,p=line0); + info = info_str([["angs = ",angs],["line = ",lineon],["poly = ",poly]]); + // line on polygon plane + assert_approx(polygon_line_intersection(poly,lineon,bounded=[true,true]), + undef, info); + assert_approx(polygon_line_intersection(poly,lineon,bounded=[true,false]), + [rot(angs,p=[[5,7.5,0],[10,7.5,0]])], info); + assert_approx(polygon_line_intersection(poly,lineon,bounded=[false,true]), + [rot(angs,p=[[-10,7.5,0],[-5,7.5,0]])], info); + assert_approx(polygon_line_intersection(poly,lineon,bounded=[false,false]), + rot(angs,p=[[[-10,7.5,0],[-5,7.5,0]],[[5,7.5,0],[10,7.5,0]]]), info); + // line parallel to polygon plane + linepll = move([0,0,1],lineon); + assert_approx(polygon_line_intersection(poly,linepll,bounded=[true,true]), + undef, info); + assert_approx(polygon_line_intersection(poly,linepll,bounded=[true,false]), + undef, info); + assert_approx(polygon_line_intersection(poly,linepll,bounded=[false,true]), + undef, info); + assert_approx(polygon_line_intersection(poly,linepll,bounded=[false,false]), + undef, info); + // general case + trnsl = [0,0,1]; + linegnr = move(trnsl,rot(angs,p=[[5,5,5],[3,3,3]])); + polygnr = move(trnsl,rot(angs,p=poly0)); + assert_approx(polygon_line_intersection(polygnr,linegnr,bounded=[true,true]), + undef, info); + assert_approx(polygon_line_intersection(polygnr,linegnr,bounded=[true,false]), + trnsl, info); + assert_approx(polygon_line_intersection(polygnr,linegnr,bounded=[false,true]), + undef, info); + assert_approx(polygon_line_intersection(polygnr,linegnr,bounded=[false,false]), + trnsl, info); } *test_polygon_line_intersection(); @@ -576,6 +787,8 @@ module test_in_front_of_plane() { module test_is_path() { + assert(is_path([[1,2,3],[4,5,6]])); + assert(is_path([[1,2,3],[4,5,6],[7,8,9]])); assert(!is_path(123)); assert(!is_path("foo")); assert(!is_path(true)); @@ -584,8 +797,6 @@ module test_is_path() { assert(!is_path([["foo","bar","baz"]])); assert(!is_path([[1,2,3]])); assert(!is_path([["foo","bar","baz"],["qux","quux","quuux"]])); - assert(is_path([[1,2,3],[4,5,6]])); - assert(is_path([[1,2,3],[4,5,6],[7,8,9]])); } *test_is_path(); diff --git a/tests/test_math.scad b/tests/test_math.scad index 9a25cec..5641416 100644 --- a/tests/test_math.scad +++ b/tests/test_math.scad @@ -100,104 +100,128 @@ module test_is_matrix() { test_is_matrix(); -module test_is_zero() { - assert(is_zero(0)); - assert(is_zero([0,0,0])); - assert(is_zero([[0,0,0],[0,0]])); - assert(is_zero([EPSILON/2,EPSILON/2,EPSILON/2])); - assert(!is_zero(1e-3)); - assert(!is_zero([0,0,1e-3])); - assert(!is_zero([EPSILON*10,0,0])); - assert(!is_zero([0,EPSILON*10,0])); - assert(!is_zero([0,0,EPSILON*10])); - assert(!is_zero(true)); - assert(!is_zero(false)); - assert(!is_zero(INF)); - assert(!is_zero(-INF)); - assert(!is_zero(NAN)); - assert(!is_zero("foo")); - assert(!is_zero([])); - assert(!is_zero([0:1:2])); +module test_all_zero() { + assert(all_zero(0)); + assert(all_zero([0,0,0])); + assert(all_zero([[0,0,0],[0,0]])); + assert(all_zero([EPSILON/2,EPSILON/2,EPSILON/2])); + assert(!all_zero(1e-3)); + assert(!all_zero([0,0,1e-3])); + assert(!all_zero([EPSILON*10,0,0])); + assert(!all_zero([0,EPSILON*10,0])); + assert(!all_zero([0,0,EPSILON*10])); + assert(!all_zero(true)); + assert(!all_zero(false)); + assert(!all_zero(INF)); + assert(!all_zero(-INF)); + assert(!all_zero(NAN)); + assert(!all_zero("foo")); + assert(!all_zero([])); + assert(!all_zero([0:1:2])); } -test_is_zero(); +test_all_zero(); -module test_is_positive() { - assert(!is_positive(-2)); - assert(!is_positive(0)); - assert(is_positive(2)); - assert(!is_positive([0,0,0])); - assert(!is_positive([0,1,2])); - assert(is_positive([3,1,2])); - assert(!is_positive([3,-1,2])); - assert(!is_positive([])); - assert(!is_positive(true)); - assert(!is_positive(false)); - assert(!is_positive("foo")); - assert(!is_positive([0:1:2])); +module test_all_nonzero() { + assert(!all_nonzero(0)); + assert(!all_nonzero([0,0,0])); + assert(!all_nonzero([[0,0,0],[0,0]])); + assert(!all_nonzero([EPSILON/2,EPSILON/2,EPSILON/2])); + assert(all_nonzero(1e-3)); + assert(!all_nonzero([0,0,1e-3])); + assert(!all_nonzero([EPSILON*10,0,0])); + assert(!all_nonzero([0,EPSILON*10,0])); + assert(!all_nonzero([0,0,EPSILON*10])); + assert(all_nonzero([1e-3,1e-3,1e-3])); + assert(all_nonzero([EPSILON*10,EPSILON*10,EPSILON*10])); + assert(!all_nonzero(true)); + assert(!all_nonzero(false)); + assert(!all_nonzero(INF)); + assert(!all_nonzero(-INF)); + assert(!all_nonzero(NAN)); + assert(!all_nonzero("foo")); + assert(!all_nonzero([])); + assert(!all_nonzero([0:1:2])); } -test_is_positive(); +test_all_nonzero(); -module test_is_negative() { - assert(is_negative(-2)); - assert(!is_negative(0)); - assert(!is_negative(2)); - assert(!is_negative([0,0,0])); - assert(!is_negative([0,1,2])); - assert(!is_negative([3,1,2])); - assert(!is_negative([3,-1,2])); - assert(is_negative([-3,-1,-2])); - assert(!is_negative([-3,1,-2])); - assert(is_negative([[-5,-7],[-3,-1,-2]])); - assert(!is_negative([[-5,-7],[-3,1,-2]])); - assert(!is_negative([])); - assert(!is_negative(true)); - assert(!is_negative(false)); - assert(!is_negative("foo")); - assert(!is_negative([0:1:2])); +module test_all_positive() { + assert(!all_positive(-2)); + assert(!all_positive(0)); + assert(all_positive(2)); + assert(!all_positive([0,0,0])); + assert(!all_positive([0,1,2])); + assert(all_positive([3,1,2])); + assert(!all_positive([3,-1,2])); + assert(!all_positive([])); + assert(!all_positive(true)); + assert(!all_positive(false)); + assert(!all_positive("foo")); + assert(!all_positive([0:1:2])); } -test_is_negative(); +test_all_positive(); -module test_is_nonpositive() { - assert(is_nonpositive(-2)); - assert(is_nonpositive(0)); - assert(!is_nonpositive(2)); - assert(is_nonpositive([0,0,0])); - assert(!is_nonpositive([0,1,2])); - assert(is_nonpositive([0,-1,-2])); - assert(!is_nonpositive([3,1,2])); - assert(!is_nonpositive([3,-1,2])); - assert(!is_nonpositive([])); - assert(!is_nonpositive(true)); - assert(!is_nonpositive(false)); - assert(!is_nonpositive("foo")); - assert(!is_nonpositive([0:1:2])); +module test_all_negative() { + assert(all_negative(-2)); + assert(!all_negative(0)); + assert(!all_negative(2)); + assert(!all_negative([0,0,0])); + assert(!all_negative([0,1,2])); + assert(!all_negative([3,1,2])); + assert(!all_negative([3,-1,2])); + assert(all_negative([-3,-1,-2])); + assert(!all_negative([-3,1,-2])); + assert(all_negative([[-5,-7],[-3,-1,-2]])); + assert(!all_negative([[-5,-7],[-3,1,-2]])); + assert(!all_negative([])); + assert(!all_negative(true)); + assert(!all_negative(false)); + assert(!all_negative("foo")); + assert(!all_negative([0:1:2])); } -test_is_nonpositive(); +test_all_negative(); -module test_is_nonnegative() { - assert(!is_nonnegative(-2)); - assert(is_nonnegative(0)); - assert(is_nonnegative(2)); - assert(is_nonnegative([0,0,0])); - assert(is_nonnegative([0,1,2])); - assert(is_nonnegative([3,1,2])); - assert(!is_nonnegative([3,-1,2])); - assert(!is_nonnegative([-3,-1,-2])); - assert(!is_nonnegative([[-5,-7],[-3,-1,-2]])); - assert(!is_nonnegative([[-5,-7],[-3,1,-2]])); - assert(!is_nonnegative([[5,7],[3,-1,2]])); - assert(is_nonnegative([[5,7],[3,1,2]])); - assert(!is_nonnegative([])); - assert(!is_nonnegative(true)); - assert(!is_nonnegative(false)); - assert(!is_nonnegative("foo")); - assert(!is_nonnegative([0:1:2])); +module test_all_nonpositive() { + assert(all_nonpositive(-2)); + assert(all_nonpositive(0)); + assert(!all_nonpositive(2)); + assert(all_nonpositive([0,0,0])); + assert(!all_nonpositive([0,1,2])); + assert(all_nonpositive([0,-1,-2])); + assert(!all_nonpositive([3,1,2])); + assert(!all_nonpositive([3,-1,2])); + assert(!all_nonpositive([])); + assert(!all_nonpositive(true)); + assert(!all_nonpositive(false)); + assert(!all_nonpositive("foo")); + assert(!all_nonpositive([0:1:2])); } -test_is_nonnegative(); +test_all_nonpositive(); + + +module test_all_nonnegative() { + assert(!all_nonnegative(-2)); + assert(all_nonnegative(0)); + assert(all_nonnegative(2)); + assert(all_nonnegative([0,0,0])); + assert(all_nonnegative([0,1,2])); + assert(all_nonnegative([3,1,2])); + assert(!all_nonnegative([3,-1,2])); + assert(!all_nonnegative([-3,-1,-2])); + assert(!all_nonnegative([[-5,-7],[-3,-1,-2]])); + assert(!all_nonnegative([[-5,-7],[-3,1,-2]])); + assert(!all_nonnegative([[5,7],[3,-1,2]])); + assert(all_nonnegative([[5,7],[3,1,2]])); + assert(!all_nonnegative([])); + assert(!all_nonnegative(true)); + assert(!all_nonnegative(false)); + assert(!all_nonnegative("foo")); + assert(!all_nonnegative([0:1:2])); +} +test_all_nonnegative(); module test_approx() { @@ -975,7 +999,7 @@ module test_null_space(){ function nullcheck(A,dim) = let(v=null_space(A)) - len(v)==dim && is_zero(A*transpose(v),eps=1e-12); + len(v)==dim && all_zero(A*transpose(v),eps=1e-12); A = [[-1, 2, -5, 2],[-3,-1,3,-3],[5,0,5,0],[3,-4,11,-4]]; assert(nullcheck(A,1)); diff --git a/tests/test_vectors.scad b/tests/test_vectors.scad index 743112f..2e00705 100644 --- a/tests/test_vectors.scad +++ b/tests/test_vectors.scad @@ -14,6 +14,14 @@ module test_is_vector() { assert(is_vector([0,0,0],zero=false) == false); assert(is_vector([0,1,0],zero=true) == false); assert(is_vector([0,0,1],zero=false) == true); + assert(is_vector([1,1,1],zero=false) == true); + + assert(is_vector([0,0,0],all_nonzero=true) == false); + assert(is_vector([0,1,0],all_nonzero=true) == false); + assert(is_vector([0,0,1],all_nonzero=true) == false); + assert(is_vector([1,1,1],all_nonzero=true) == true); + assert(is_vector([-1,1,1],all_nonzero=true) == true); + assert(is_vector([-1,-1,-1],all_nonzero=true) == true); } test_is_vector(); diff --git a/vectors.scad b/vectors.scad index 1749981..3b83eda 100644 --- a/vectors.scad +++ b/vectors.scad @@ -19,7 +19,8 @@ // Arguments: // v = The value to test to see if it is a vector. // length = If given, make sure the vector is `length` items long. -// zero = If false, require that the length of the vector is not approximately zero. If true, require the length of the vector to be approximately zero-length. Default: `undef` (don't check vector length.) +// zero = If false, require that the length/`norm()` of the vector is not approximately zero. If true, require the length/`norm()` of the vector to be approximately zero-length. Default: `undef` (don't check vector length/`norm()`.) +// all_nonzero = If true, requires all elements of the vector to be more than `eps` different from zero. Default: `false` // eps = The minimum vector length that is considered non-zero. Default: `EPSILON` (`1e-9`) // Example: // is_vector(4); // Returns false @@ -30,14 +31,17 @@ // is_vector([3,4,5],3); // Returns true // is_vector([3,4,5],4); // Returns true // is_vector([]); // Returns false -// is_vector([0,4,0],3,zero=false); // Returns true -// is_vector([0,0,0],zero=false); // Returns false -// is_vector([0,0,1e-12],zero=false); // Returns false -// is_vector([],zero=false); // Returns false -function is_vector(v,length,zero,eps=EPSILON) = +// is_vector([0,4,0],3,zero=false); // Returns true +// is_vector([0,0,0],zero=false); // Returns false +// is_vector([0,0,1e-12],zero=false); // Returns false +// is_vector([0,1,0],all_nonzero=false); // Returns false +// is_vector([1,1,1],all_nonzero=false); // Returns true +// is_vector([],zero=false); // Returns false +function is_vector(v, length, zero, all_nonzero=false, eps=EPSILON) = is_list(v) && is_num(0*(v*v)) && (is_undef(length) || len(v)==length) - && (is_undef(zero) || ((norm(v) >= eps) == !zero)); + && (is_undef(zero) || ((norm(v) >= eps) == !zero)) + && (!all_nonzero || all_nonzero(v)) ; // Function: vang() diff --git a/version.scad b/version.scad index 262b891..e4af507 100644 --- a/version.scad +++ b/version.scad @@ -8,7 +8,7 @@ ////////////////////////////////////////////////////////////////////// -BOSL_VERSION = [2,0,419]; +BOSL_VERSION = [2,0,422]; // Section: BOSL Library Version Functions