Fixed a bunch of undef math warnings with dev snapshot OpenSCAD builds.

This commit is contained in:
Garth Minette 2020-10-03 19:50:29 -07:00
parent e3ccf482fa
commit 16ee49e8b2
15 changed files with 215 additions and 161 deletions

View File

@ -34,15 +34,19 @@
// is_homogeneous( [[1,["a"]], [2,[true]]], 1 ) // Returns true // is_homogeneous( [[1,["a"]], [2,[true]]], 1 ) // Returns true
// is_homogeneous( [[1,["a"]], [2,[true]]], 2 ) // Returns false // is_homogeneous( [[1,["a"]], [2,[true]]], 2 ) // Returns false
// is_homogeneous( [[1,["a"]], [true,["b"]]] ) // Returns true // is_homogeneous( [[1,["a"]], [true,["b"]]] ) // Returns true
function is_homogeneous(l, depth) = function is_homogeneous(l, depth=10) =
!is_list(l) || l==[] ? false : !is_list(l) || l==[] ? false :
let( l0=l[0] ) let( l0=l[0] )
[] == [for(i=[1:len(l)-1]) if( ! _same_type(l[i],l0, depth+1) ) 0 ]; [] == [for(i=[1:len(l)-1]) if( ! _same_type(l[i],l0, depth+1) ) 0 ];
function _same_type(a,b, depth) = function _same_type(a,b, depth) =
(depth==0) || (a>=b) || (a==b) || (a<=b) (depth==0) ||
|| ( is_list(a) && is_list(b) && len(a)==len(b) (is_undef(a) && is_undef(b)) ||
&& []==[for(i=idx(a)) if( ! _same_type(a[i],b[i],depth-1) )0] ); (is_bool(a) && is_bool(b)) ||
(is_num(a) && is_num(b)) ||
(is_string(a) && is_string(b)) ||
(is_list(a) && is_list(b) && len(a)==len(b)
&& []==[for(i=idx(a)) if( ! _same_type(a[i],b[i],depth-1) ) 0] );
// Function: select() // Function: select()

View File

@ -90,13 +90,13 @@ function is_nan(x) = (x!=x);
// is_finite(x); // is_finite(x);
// Description: // Description:
// Returns true if a given value `x` is a finite number. // Returns true if a given value `x` is a finite number.
function is_finite(v) = is_num(0*v); function is_finite(x) = is_num(x) && !is_nan(0*x);
// Function: is_range() // Function: is_range()
// Description: // Description:
// Returns true if its argument is a range // Returns true if its argument is a range
function is_range(x) = !is_list(x) && is_finite(x[0]+x[1]+x[2]) ; function is_range(x) = !is_list(x) && is_finite(x[0]) && is_finite(x[1]) && is_finite(x[2]) ;
// Function: valid_range() // Function: valid_range()
@ -458,5 +458,10 @@ module shape_compare(eps=1/1024) {
} }
function loop_start() = 0;
function loop_done(x) = x==1;
function looping(x) = x<2;
function loop_next(x,b) = x>=1? 2 : (b? 0 : 1);
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View File

@ -177,6 +177,7 @@ function line_ray_intersection(line,ray,eps=EPSILON) =
let( let(
isect = _general_line_intersection(line,ray,eps=eps) isect = _general_line_intersection(line,ray,eps=eps)
) )
is_undef(isect[0]) ? undef :
(isect[2]<0-eps) ? undef : isect[0]; (isect[2]<0-eps) ? undef : isect[0];
@ -195,7 +196,10 @@ function line_segment_intersection(line,segment,eps=EPSILON) =
assert( _valid_line(line, dim=2,eps=eps) &&_valid_line(segment,dim=2,eps=eps), "Invalid line or segment." ) assert( _valid_line(line, dim=2,eps=eps) &&_valid_line(segment,dim=2,eps=eps), "Invalid line or segment." )
let( let(
isect = _general_line_intersection(line,segment,eps=eps) isect = _general_line_intersection(line,segment,eps=eps)
) isect[2]<0-eps || isect[2]>1+eps ? undef : isect[0]; )
is_undef(isect[0]) ? undef :
isect[2]<0-eps || isect[2]>1+eps ? undef :
isect[0];
// Function: ray_intersection() // Function: ray_intersection()
@ -214,6 +218,7 @@ function ray_intersection(r1,r2,eps=EPSILON) =
let( let(
isect = _general_line_intersection(r1,r2,eps=eps) isect = _general_line_intersection(r1,r2,eps=eps)
) )
is_undef(isect[0]) ? undef :
isect[1]<0-eps || isect[2]<0-eps ? undef : isect[0]; isect[1]<0-eps || isect[2]<0-eps ? undef : isect[0];
@ -233,11 +238,9 @@ function ray_segment_intersection(ray,segment,eps=EPSILON) =
let( let(
isect = _general_line_intersection(ray,segment,eps=eps) isect = _general_line_intersection(ray,segment,eps=eps)
) )
isect[1]<0-eps is_undef(isect[0]) ? undef :
|| isect[2]<0-eps isect[1]<0-eps || isect[2]<0-eps || isect[2]>1+eps ? undef :
|| isect[2]>1+eps isect[0];
? undef
: isect[0];
// Function: segment_intersection() // Function: segment_intersection()
@ -256,12 +259,9 @@ function segment_intersection(s1,s2,eps=EPSILON) =
let( let(
isect = _general_line_intersection(s1,s2,eps=eps) isect = _general_line_intersection(s1,s2,eps=eps)
) )
isect[1]<0-eps is_undef(isect[0]) ? undef :
|| isect[1]>1+eps isect[1]<0-eps || isect[1]>1+eps || isect[2]<0-eps || isect[2]>1+eps ? undef :
|| isect[2]<0-eps isect[0];
|| isect[2]>1+eps
? undef
: isect[0];
// Function: line_closest_point() // Function: line_closest_point()

232
math.scad
View File

@ -56,7 +56,7 @@ function log2(x) =
// Function: hypot() // Function: hypot()
// Usage: // Usage:
// l = hypot(x,y,[z]); // l = hypot(x,y,<z>);
// Description: // Description:
// Calculate hypotenuse length of a 2D or 3D triangle. // Calculate hypotenuse length of a 2D or 3D triangle.
// Arguments: // Arguments:
@ -73,7 +73,7 @@ function hypot(x,y,z=0) =
// Function: factorial() // Function: factorial()
// Usage: // Usage:
// x = factorial(n,[d]); // x = factorial(n,<d>);
// Description: // Description:
// Returns the factorial of the given integer value, or n!/d! if d is given. // Returns the factorial of the given integer value, or n!/d! if d is given.
// Arguments: // Arguments:
@ -373,7 +373,7 @@ function modang(x) =
// Function: modrange() // Function: modrange()
// Usage: // Usage:
// modrange(x, y, m, [step]) // modrange(x, y, m, <step>)
// Description: // Description:
// Returns a normalized list of numbers from `x` to `y`, by `step`, modulo `m`. Wraps if `x` > `y`. // Returns a normalized list of numbers from `x` to `y`, by `step`, modulo `m`. Wraps if `x` > `y`.
// Arguments: // Arguments:
@ -401,7 +401,7 @@ function modrange(x, y, m, step=1) =
// Function: rand_int() // Function: rand_int()
// Usage: // Usage:
// rand_int(minval,maxval,N,[seed]); // rand_int(minval,maxval,N,<seed>);
// Description: // Description:
// Return a list of random integers in the range of minval to maxval, inclusive. // Return a list of random integers in the range of minval to maxval, inclusive.
// Arguments: // Arguments:
@ -421,7 +421,7 @@ function rand_int(minval, maxval, N, seed=undef) =
// Function: gaussian_rands() // Function: gaussian_rands()
// Usage: // Usage:
// gaussian_rands(mean, stddev, [N], [seed]) // gaussian_rands(mean, stddev, <N>, <seed>)
// Description: // Description:
// Returns a random number with a gaussian/normal distribution. // Returns a random number with a gaussian/normal distribution.
// Arguments: // Arguments:
@ -437,7 +437,7 @@ function gaussian_rands(mean, stddev, N=1, seed=undef) =
// Function: log_rands() // Function: log_rands()
// Usage: // Usage:
// log_rands(minval, maxval, factor, [N], [seed]); // log_rands(minval, maxval, factor, <N>, <seed>);
// Description: // Description:
// Returns a single random number, with a logarithmic distribution. // Returns a single random number, with a logarithmic distribution.
// Arguments: // Arguments:
@ -506,6 +506,8 @@ function lcm(a,b=[]) =
// Section: Sums, Products, Aggregate Functions. // Section: Sums, Products, Aggregate Functions.
// Function: sum() // Function: sum()
// Usage:
// x = sum(v, <dflt>);
// Description: // Description:
// Returns the sum of all entries in the given consistent list. // Returns the sum of all entries in the given consistent list.
// If passed an array of vectors, returns the sum the vectors. // If passed an array of vectors, returns the sum the vectors.
@ -518,8 +520,7 @@ function lcm(a,b=[]) =
// sum([1,2,3]); // returns 6. // sum([1,2,3]); // returns 6.
// sum([[1,2,3], [3,4,5], [5,6,7]]); // returns [9, 12, 15] // sum([[1,2,3], [3,4,5], [5,6,7]]); // returns [9, 12, 15]
function sum(v, dflt=0) = function sum(v, dflt=0) =
is_list(v) && len(v) == 0 ? dflt : v==[]? dflt :
is_vector(v) || is_matrix(v)? [for(i=v) 1]*v :
assert(is_consistent(v), "Input to sum is non-numeric or inconsistent") assert(is_consistent(v), "Input to sum is non-numeric or inconsistent")
_sum(v,v[0]*0); _sum(v,v[0]*0);
@ -527,6 +528,8 @@ function _sum(v,_total,_i=0) = _i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1);
// Function: cumsum() // Function: cumsum()
// Usage:
// sums = cumsum(v);
// Description: // Description:
// Returns a list where each item is the cumulative sum of all items up to and including the corresponding entry in the input list. // Returns a list where each item is the cumulative sum of all items up to and including the corresponding entry in the input list.
// If passed an array of vectors, returns a list of cumulative vectors sums. // If passed an array of vectors, returns a list of cumulative vectors sums.
@ -573,6 +576,8 @@ function sum_of_sines(a, sines) =
// Function: deltas() // Function: deltas()
// Usage:
// delts = deltas(v);
// Description: // Description:
// Returns a list with the deltas of adjacent entries in the given list. // Returns a list with the deltas of adjacent entries in the given list.
// The list should be a consistent list of numeric components (numbers, vectors, matrix, etc). // The list should be a consistent list of numeric components (numbers, vectors, matrix, etc).
@ -588,6 +593,8 @@ function deltas(v) =
// Function: product() // Function: product()
// Usage:
// x = product(v);
// Description: // Description:
// Returns the product of all entries in the given list. // Returns the product of all entries in the given list.
// If passed a list of vectors of same dimension, returns a vector of products of each part. // If passed a list of vectors of same dimension, returns a vector of products of each part.
@ -611,6 +618,8 @@ function _product(v, i=0, _tot) =
// Function: outer_product() // Function: outer_product()
// Usage:
// x = outer_product(u,v);
// Description: // Description:
// Compute the outer product of two vectors, a matrix. // Compute the outer product of two vectors, a matrix.
// Usage: // Usage:
@ -621,6 +630,8 @@ function outer_product(u,v) =
// Function: mean() // Function: mean()
// Usage:
// x = mean(v);
// Description: // Description:
// Returns the arithmetic mean/average of all entries in the given array. // Returns the arithmetic mean/average of all entries in the given array.
// If passed a list of vectors, returns a vector of the mean of each part. // If passed a list of vectors, returns a vector of the mean of each part.
@ -660,14 +671,15 @@ function convolve(p,q) =
// Section: Matrix math // Section: Matrix math
// Function: linear_solve() // Function: linear_solve()
// Usage: linear_solve(A,b) // Usage:
// solv = linear_solve(A,b)
// Description: // Description:
// Solves the linear system Ax=b. If A is square and non-singular the unique solution is returned. If A is overdetermined // Solves the linear system Ax=b. If `A` is square and non-singular the unique solution is returned. If `A` is overdetermined
// the least squares solution is returned. If A is underdetermined, the minimal norm solution is returned. // the least squares solution is returned. If `A` is underdetermined, the minimal norm solution is returned.
// If A is rank deficient or singular then linear_solve returns []. If b is a matrix that is compatible with A // If `A` is rank deficient or singular then linear_solve returns `[]`. If `b` is a matrix that is compatible with `A`
// then the problem is solved for the matrix valued right hand side and a matrix is returned. Note that if you // then the problem is solved for the matrix valued right hand side and a matrix is returned. Note that if you
// want to solve Ax=b1 and Ax=b2 that you need to form the matrix transpose([b1,b2]) for the right hand side and then // want to solve Ax=b1 and Ax=b2 that you need to form the matrix `transpose([b1,b2])` for the right hand side and then
// transpose the returned value. // transpose the returned value.
function linear_solve(A,b,pivot=true) = function linear_solve(A,b,pivot=true) =
assert(is_matrix(A), "Input should be a matrix.") assert(is_matrix(A), "Input should be a matrix.")
let( let(
@ -690,33 +702,32 @@ function linear_solve(A,b,pivot=true) =
// Function: matrix_inverse() // Function: matrix_inverse()
// Usage: // Usage:
// matrix_inverse(A) // mat = matrix_inverse(A)
// Description: // Description:
// Compute the matrix inverse of the square matrix A. If A is singular, returns undef. // Compute the matrix inverse of the square matrix `A`. If `A` is singular, returns `undef`.
// Note that if you just want to solve a linear system of equations you should NOT // Note that if you just want to solve a linear system of equations you should NOT use this function.
// use this function. Instead use linear_solve, or use qr_factor. The computation // Instead use [[`linear_solve()`|linear_solve]], or use [[`qr_factor()`|qr_factor]]. The computation
// will be faster and more accurate. // will be faster and more accurate.
function matrix_inverse(A) = function matrix_inverse(A) =
assert(is_matrix(A,square=true),"Input to matrix_inverse() must be a square matrix") assert(is_matrix(A) && len(A)==len(A[0]),"Input to matrix_inverse() must be a square matrix")
linear_solve(A,ident(len(A))); linear_solve(A,ident(len(A)));
// Function: null_space() // Function: null_space()
// Usage: // Usage:
// null_space(A) // x = null_space(A)
// Description: // Description:
// Returns an orthonormal basis for the null space of A, namely the vectors {x} such that Ax=0. If the null space // Returns an orthonormal basis for the null space of `A`, namely the vectors {x} such that Ax=0.
// is just the origin then returns an empty list. // If the null space is just the origin then returns an empty list.
function null_space(A,eps=1e-12) = function null_space(A,eps=1e-12) =
assert(is_matrix(A)) assert(is_matrix(A))
let( let(
Q_R=qr_factor(transpose(A),pivot=true), Q_R = qr_factor(transpose(A),pivot=true),
R=Q_R[1], R = Q_R[1],
zrow = [for(i=idx(R)) if (all_zero(R[i],eps)) i] zrow = [for(i=idx(R)) if (all_zero(R[i],eps)) i]
) )
len(zrow)==0 len(zrow)==0 ? [] :
? [] transpose(subindex(Q_R[0],zrow));
: transpose(subindex(Q_R[0],zrow));
// Function: qr_factor() // Function: qr_factor()
@ -730,19 +741,18 @@ function null_space(A,eps=1e-12) =
function qr_factor(A, pivot=false) = function qr_factor(A, pivot=false) =
assert(is_matrix(A), "Input must be a matrix." ) assert(is_matrix(A), "Input must be a matrix." )
let( let(
m = len(A), m = len(A),
n = len(A[0]) n = len(A[0])
) )
let( let(
qr =_qr_factor(A, Q=ident(m),P=ident(n), pivot=pivot, column=0, m = m, n=n), qr = _qr_factor(A, Q=ident(m),P=ident(n), pivot=pivot, column=0, m = m, n=n),
Rzero = Rzero = let( R = qr[1]) [
let( R = qr[1] ) for(i=[0:m-1]) [
[ for(i=[0:m-1]) [ let( ri = R[i] )
let( ri = R[i] ) for(j=[0:n-1]) i>j ? 0 : ri[j]
for(j=[0:n-1]) i>j ? 0 : ri[j]
] ]
] ]
) [qr[0],Rzero,qr[2]]; ) [qr[0], Rzero, qr[2]];
function _qr_factor(A,Q,P, pivot, column, m, n) = function _qr_factor(A,Q,P, pivot, column, m, n) =
column >= min(m-1,n) ? [Q,A,P] : column >= min(m-1,n) ? [Q,A,P] :
@ -770,7 +780,7 @@ function _swap_matrix(n,i,j) =
// Function: back_substitute() // Function: back_substitute()
// Usage: back_substitute(R, b, [transpose]) // Usage: back_substitute(R, b, <transpose>)
// Description: // Description:
// Solves the problem Rx=b where R is an upper triangular square matrix. The lower triangular entries of R are // Solves the problem Rx=b where R is an upper triangular square matrix. The lower triangular entries of R are
// ignored. If transpose==true then instead solve transpose(R)*x=b. // ignored. If transpose==true then instead solve transpose(R)*x=b.
@ -799,6 +809,8 @@ function _back_substitute(R, b, x=[]) =
// Function: det2() // Function: det2()
// Usage:
// d = det2(M);
// Description: // Description:
// Optimized function that returns the determinant for the given 2x2 square matrix. // Optimized function that returns the determinant for the given 2x2 square matrix.
// Arguments: // Arguments:
@ -807,11 +819,13 @@ function _back_substitute(R, b, x=[]) =
// M = [ [6,-2], [1,8] ]; // M = [ [6,-2], [1,8] ];
// det = det2(M); // Returns: 50 // det = det2(M); // Returns: 50
function det2(M) = function det2(M) =
assert( 0*M==[[0,0],[0,0]], "Matrix should be 2x2." ) assert(is_matrix(M,2,2), "Matrix must be 2x2.")
M[0][0] * M[1][1] - M[0][1]*M[1][0]; M[0][0] * M[1][1] - M[0][1]*M[1][0];
// Function: det3() // Function: det3()
// Usage:
// d = det3(M);
// Description: // Description:
// Optimized function that returns the determinant for the given 3x3 square matrix. // Optimized function that returns the determinant for the given 3x3 square matrix.
// Arguments: // Arguments:
@ -820,13 +834,15 @@ function det2(M) =
// M = [ [6,4,-2], [1,-2,8], [1,5,7] ]; // M = [ [6,4,-2], [1,-2,8], [1,5,7] ];
// det = det3(M); // Returns: -334 // det = det3(M); // Returns: -334
function det3(M) = function det3(M) =
assert( 0*M==[[0,0,0],[0,0,0],[0,0,0]], "Matrix should be 3x3." ) assert(is_matrix(M,3,3), "Matrix must be 3x3.")
M[0][0] * (M[1][1]*M[2][2]-M[2][1]*M[1][2]) - M[0][0] * (M[1][1]*M[2][2]-M[2][1]*M[1][2]) -
M[1][0] * (M[0][1]*M[2][2]-M[2][1]*M[0][2]) + M[1][0] * (M[0][1]*M[2][2]-M[2][1]*M[0][2]) +
M[2][0] * (M[0][1]*M[1][2]-M[1][1]*M[0][2]); M[2][0] * (M[0][1]*M[1][2]-M[1][1]*M[0][2]);
// Function: determinant() // Function: determinant()
// Usage:
// d = determinant(M);
// Description: // Description:
// Returns the determinant for the given square matrix. // Returns the determinant for the given square matrix.
// Arguments: // Arguments:
@ -835,7 +851,7 @@ function det3(M) =
// M = [ [6,4,-2,9], [1,-2,8,3], [1,5,7,6], [4,2,5,1] ]; // M = [ [6,4,-2,9], [1,-2,8,3], [1,5,7,6], [4,2,5,1] ];
// det = determinant(M); // Returns: 2267 // det = determinant(M); // Returns: 2267
function determinant(M) = function determinant(M) =
assert(is_matrix(M,square=true), "Input should be a square matrix." ) assert(is_matrix(M, square=true), "Input should be a square matrix." )
len(M)==1? M[0][0] : len(M)==1? M[0][0] :
len(M)==2? det2(M) : len(M)==2? det2(M) :
len(M)==3? det3(M) : len(M)==3? det3(M) :
@ -856,23 +872,22 @@ function determinant(M) =
// Function: is_matrix() // Function: is_matrix()
// Usage: // Usage:
// is_matrix(A,[m],[n],[square]) // is_matrix(A,<m>,<n>,<square>)
// Description: // Description:
// Returns true if A is a numeric matrix of height m and width n. If m or n // Returns true if A is a numeric matrix of height m and width n. If m or n
// are omitted or set to undef then true is returned for any positive dimension. // are omitted or set to undef then true is returned for any positive dimension.
// If `square` is true then the matrix is required to be square.
// specify m != n and require a square matrix then the result will always be false.
// Arguments: // Arguments:
// A = matrix to test // A = The matrix to test.
// m = optional height of matrix // m = Is given, requires the matrix to have the given height.
// n = optional width of matrix // n = Is given, requires the matrix to have the given width.
// square = set to true to require a square matrix. Default: false // square = If true, requires the matrix to have a width equal to its height. Default: false
function is_matrix(A,m,n,square=false) = function is_matrix(A,m,n,square=false) =
is_list(A[0]) is_list(A)
&& ( let(v = A*A[0]) is_num(0*(v*v)) ) // a matrix of finite numbers && (( is_undef(m) && len(A) ) || len(A)==m)
&& (is_undef(n) || len(A[0])==n ) && is_list(A[0])
&& (is_undef(m) || len(A)==m ) && (( is_undef(n) && len(A[0]) ) || len(A[0])==n)
&& ( !square || len(A)==len(A[0])); && (!square || len(A) == len(A[0]))
&& is_consistent(A);
// Function: norm_fro() // Function: norm_fro()
@ -891,7 +906,7 @@ function norm_fro(A) =
// Function: all_zero() // Function: all_zero()
// Usage: // Usage:
// all_zero(x); // x = all_zero(x, <eps>);
// Description: // Description:
// Returns true if the finite 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. // If passed a list, recursively checks if all items in the list are approximately zero.
@ -912,7 +927,7 @@ function all_zero(x, eps=EPSILON) =
// Function: all_nonzero() // Function: all_nonzero()
// Usage: // Usage:
// all_nonzero(x); // x = all_nonzero(x, <eps>);
// Description: // Description:
// Returns true if the finite number passed to it is not almost zero, to within `eps`. // 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. // If passed a list, recursively checks if all items in the list are not almost zero.
@ -1030,7 +1045,7 @@ function all_nonnegative(x) =
// Function: approx() // Function: approx()
// Usage: // Usage:
// approx(a,b,[eps]) // b = approx(a,b,<eps>)
// Description: // Description:
// Compares two numbers or vectors, and returns true if they are closer than `eps` to each other. // Compares two numbers or vectors, and returns true if they are closer than `eps` to each other.
// Arguments: // Arguments:
@ -1044,12 +1059,9 @@ function all_nonnegative(x) =
// approx(0.3333,1/3,eps=1e-3); // Returns: true // approx(0.3333,1/3,eps=1e-3); // Returns: true
// approx(PI,3.1415926536); // Returns: true // approx(PI,3.1415926536); // Returns: true
function approx(a,b,eps=EPSILON) = function approx(a,b,eps=EPSILON) =
a==b? true : (a==b && is_bool(a) == is_bool(b)) ||
a*0!=b*0? false : (is_num(a) && is_num(b) && abs(a-b) <= eps) ||
is_list(a) (is_list(a) && is_list(b) && len(a) == len(b) && [] == [for (i=idx(a)) if (!approx(a[i],b[i],eps=eps)) 1]);
? ([for (i=idx(a)) if( !approx(a[i],b[i],eps=eps)) 1] == [])
: is_num(a) && is_num(b) && (abs(a-b) <= eps);
function _type_num(x) = function _type_num(x) =
@ -1063,7 +1075,7 @@ function _type_num(x) =
// Function: compare_vals() // Function: compare_vals()
// Usage: // Usage:
// compare_vals(a, b); // b = compare_vals(a, b);
// Description: // Description:
// Compares two values. Lists are compared recursively. // Compares two values. Lists are compared recursively.
// Returns <0 if a<b. Returns >0 if a>b. Returns 0 if a==b. // Returns <0 if a<b. Returns >0 if a>b. Returns 0 if a==b.
@ -1081,7 +1093,7 @@ function compare_vals(a, b) =
// Function: compare_lists() // Function: compare_lists()
// Usage: // Usage:
// compare_lists(a, b) // b = compare_lists(a, b)
// Description: // Description:
// Compare contents of two lists using `compare_vals()`. // Compare contents of two lists using `compare_vals()`.
// Returns <0 if `a`<`b`. // Returns <0 if `a`<`b`.
@ -1102,6 +1114,8 @@ function compare_lists(a, b) =
// Function: any() // Function: any()
// Usage:
// b = any(l);
// Description: // Description:
// Returns true if any item in list `l` evaluates as true. // Returns true if any item in list `l` evaluates as true.
// If `l` is a lists of lists, `any()` is applied recursively to each sublist. // If `l` is a lists of lists, `any()` is applied recursively to each sublist.
@ -1115,17 +1129,19 @@ function compare_lists(a, b) =
// any([[0,0], [1,0]]); // Returns true. // any([[0,0], [1,0]]); // Returns true.
function any(l) = function any(l) =
assert(is_list(l), "The input is not a list." ) assert(is_list(l), "The input is not a list." )
_any(l, i=0, succ=false); _any(l);
function _any(l, i=0, succ=false) = function _any(l, i=0, succ=false) =
(i>=len(l) || succ)? succ : (i>=len(l) || succ)? succ :
_any( l, _any(
i+1, l, i+1,
succ = is_list(l[i]) ? _any(l[i]) : !(!l[i]) succ = is_list(l[i]) ? _any(l[i]) : !(!l[i])
); );
// Function: all() // Function: all()
// Usage:
// b = all(l);
// Description: // Description:
// Returns true if all items in list `l` evaluate as true. // Returns true if all items in list `l` evaluate as true.
// If `l` is a lists of lists, `all()` is applied recursively to each sublist. // If `l` is a lists of lists, `all()` is applied recursively to each sublist.
@ -1138,21 +1154,21 @@ function _any(l, i=0, succ=false) =
// all([[0,0], [0,0]]); // Returns false. // all([[0,0], [0,0]]); // Returns false.
// all([[0,0], [1,0]]); // Returns false. // all([[0,0], [1,0]]); // Returns false.
// all([[1,1], [1,1]]); // Returns true. // all([[1,1], [1,1]]); // Returns true.
function all(l, i=0, fail=false) = function all(l) =
assert( is_list(l), "The input is not a list." ) assert( is_list(l), "The input is not a list." )
_all(l, i=0, fail=false); _all(l);
function _all(l, i=0, fail=false) = function _all(l, i=0, fail=false) =
(i>=len(l) || fail)? !fail : (i>=len(l) || fail)? !fail :
_all( l, _all(
i+1, l, i+1,
fail = is_list(l[i]) ? !_all(l[i]) : !l[i] fail = is_list(l[i]) ? !_all(l[i]) : !l[i]
) ; ) ;
// Function: count_true() // Function: count_true()
// Usage: // Usage:
// count_true(l) // n = count_true(l)
// Description: // Description:
// Returns the number of items in `l` that evaluate as true. // Returns the number of items in `l` that evaluate as true.
// If `l` is a lists of lists, this is applied recursively to each // If `l` is a lists of lists, this is applied recursively to each
@ -1170,26 +1186,22 @@ function _all(l, i=0, fail=false) =
// count_true([[0,0], [1,0]]); // Returns 1. // count_true([[0,0], [1,0]]); // Returns 1.
// count_true([[1,1], [1,1]]); // Returns 4. // count_true([[1,1], [1,1]]); // Returns 4.
// count_true([[1,1], [1,1]], nmax=3); // Returns 3. // count_true([[1,1], [1,1]], nmax=3); // Returns 3.
function _count_true_rec(l, nmax, _cnt=0, _i=0) =
_i>=len(l) || (is_num(nmax) && _cnt>=nmax)? _cnt :
_count_true_rec(l, nmax, _cnt=_cnt+(l[_i]?1:0), _i=_i+1);
function count_true(l, nmax) = function count_true(l, nmax) =
!is_list(l) ? !(!l) ? 1: 0 : is_undef(nmax)? len([for (x=l) if(x) 1]) :
let( c = [for( i = 0, !is_list(l) ? ( l? 1: 0) :
n = !is_list(l[i]) ? !(!l[i]) ? 1: 0 : undef, _count_true_rec(l, nmax);
c = !is_undef(n)? n : count_true(l[i], nmax),
s = c;
i<len(l) && (is_undef(nmax) || s<nmax);
i = i+1,
n = !is_list(l[i]) ? !(!l[i]) ? 1: 0 : undef,
c = !is_undef(n) || (i==len(l))? n : count_true(l[i], nmax-s),
s = s+c
) s ] )
len(c)<len(l)? nmax: c[len(c)-1];
// Section: Calculus // Section: Calculus
// Function: deriv() // Function: deriv()
// Usage: deriv(data, [h], [closed]) // Usage:
// x = deriv(data, [h], [closed])
// Description: // Description:
// Computes a numerical derivative estimate of the data, which may be scalar or vector valued. // Computes a numerical derivative estimate of the data, which may be scalar or vector valued.
// The `h` parameter gives the step size of your sampling so the derivative can be scaled correctly. // The `h` parameter gives the step size of your sampling so the derivative can be scaled correctly.
@ -1253,7 +1265,8 @@ function _deriv_nonuniform(data, h, closed) =
// Function: deriv2() // Function: deriv2()
// Usage: deriv2(data, [h], [closed]) // Usage:
// x = deriv2(data, <h>, <closed>)
// Description: // Description:
// Computes a numerical estimate of the second derivative of the data, which may be scalar or vector valued. // Computes a numerical estimate of the second derivative of the data, which may be scalar or vector valued.
// The `h` parameter gives the step size of your sampling so the derivative can be scaled correctly. // The `h` parameter gives the step size of your sampling so the derivative can be scaled correctly.
@ -1296,7 +1309,8 @@ function deriv2(data, h=1, closed=false) =
// Function: deriv3() // Function: deriv3()
// Usage: deriv3(data, [h], [closed]) // Usage:
// x = deriv3(data, <h>, <closed>)
// Description: // Description:
// Computes a numerical third derivative estimate of the data, which may be scalar or vector valued. // Computes a numerical third derivative estimate of the data, which may be scalar or vector valued.
// The `h` parameter gives the step size of your sampling so the derivative can be scaled correctly. // The `h` parameter gives the step size of your sampling so the derivative can be scaled correctly.
@ -1306,6 +1320,10 @@ function deriv2(data, h=1, closed=false) =
// f'''(t) = (-f(t-2*h)+2*f(t-h)-2*f(t+h)+f(t+2*h)) / 2h^3. At the first and second points from the end // f'''(t) = (-f(t-2*h)+2*f(t-h)-2*f(t+h)+f(t+2*h)) / 2h^3. At the first and second points from the end
// the estimates are f'''(t) = (-5*f(t)+18*f(t+h)-24*f(t+2*h)+14*f(t+3*h)-3*f(t+4*h)) / 2h^3 and // the estimates are f'''(t) = (-5*f(t)+18*f(t+h)-24*f(t+2*h)+14*f(t+3*h)-3*f(t+4*h)) / 2h^3 and
// f'''(t) = (-3*f(t-h)+10*f(t)-12*f(t+h)+6*f(t+2*h)-f(t+3*h)) / 2h^3. // f'''(t) = (-3*f(t-h)+10*f(t)-12*f(t+h)+6*f(t+2*h)-f(t+3*h)) / 2h^3.
// Arguments:
// data = the list of the elements to compute the derivative of.
// h = the constant parametric sampling of the data.
// closed = boolean to indicate if the data set should be wrapped around from the end to the start.
function deriv3(data, h=1, closed=false) = function deriv3(data, h=1, closed=false) =
assert( is_consistent(data) , "Input list is not consistent or not numerical.") assert( is_consistent(data) , "Input list is not consistent or not numerical.")
assert( len(data)>=5, "Input list has less than 5 elements.") assert( len(data)>=5, "Input list has less than 5 elements.")
@ -1335,17 +1353,27 @@ function deriv3(data, h=1, closed=false) =
// Section: Complex Numbers // Section: Complex Numbers
// Function: C_times() // Function: C_times()
// Usage: C_times(z1,z2) // Usage:
// c = C_times(z1,z2)
// Description: // Description:
// Multiplies two complex numbers represented by 2D vectors. // Multiplies two complex numbers represented by 2D vectors.
// Returns a complex number as a 2D vector [REAL, IMAGINARY].
// Arguments:
// z1 = First complex number, given as a 2D vector [REAL, IMAGINARY]
// z2 = Second complex number, given as a 2D vector [REAL, IMAGINARY]
function C_times(z1,z2) = function C_times(z1,z2) =
assert( is_matrix([z1,z2],2,2), "Complex numbers should be represented by 2D vectors" ) assert( is_matrix([z1,z2],2,2), "Complex numbers should be represented by 2D vectors" )
[ z1.x*z2.x - z1.y*z2.y, z1.x*z2.y + z1.y*z2.x ]; [ z1.x*z2.x - z1.y*z2.y, z1.x*z2.y + z1.y*z2.x ];
// Function: C_div() // Function: C_div()
// Usage: C_div(z1,z2) // Usage:
// x = C_div(z1,z2)
// Description: // Description:
// Divides two complex numbers represented by 2D vectors. // Divides two complex numbers represented by 2D vectors.
// Returns a complex number as a 2D vector [REAL, IMAGINARY].
// Arguments:
// z1 = First complex number, given as a 2D vector [REAL, IMAGINARY]
// z2 = Second complex number, given as a 2D vector [REAL, IMAGINARY]
function C_div(z1,z2) = function C_div(z1,z2) =
assert( is_vector(z1,2) && is_vector(z2), "Complex numbers should be represented by 2D vectors." ) assert( is_vector(z1,2) && is_vector(z2), "Complex numbers should be represented by 2D vectors." )
assert( !approx(z2,0), "The divisor `z2` cannot be zero." ) assert( !approx(z2,0), "The divisor `z2` cannot be zero." )
@ -1358,16 +1386,14 @@ function C_div(z1,z2) =
// Function: quadratic_roots() // Function: quadratic_roots()
// Usage: // Usage:
// roots = quadratic_roots(a,b,c,[real]) // roots = quadratic_roots(a,b,c,<real>)
// Description: // Description:
// Computes roots of the quadratic equation a*x^2+b*x+c==0, where the // Computes roots of the quadratic equation a*x^2+b*x+c==0, where the
// coefficients are real numbers. If real is true then returns only the // coefficients are real numbers. If real is true then returns only the
// real roots. Otherwise returns a pair of complex values. This method // real roots. Otherwise returns a pair of complex values. This method
// may be more reliable than the general root finder at distinguishing // may be more reliable than the general root finder at distinguishing
// real roots from complex roots. // real roots from complex roots.
// Algorithm from: https://people.csail.mit.edu/bkph/articles/Quadratics.pdf
// https://people.csail.mit.edu/bkph/articles/Quadratics.pdf
function quadratic_roots(a,b,c,real=false) = function quadratic_roots(a,b,c,real=false) =
real ? [for(root = quadratic_roots(a,b,c,real=false)) if (root.y==0) root.x] real ? [for(root = quadratic_roots(a,b,c,real=false)) if (root.y==0) root.x]
: :
@ -1393,7 +1419,7 @@ function quadratic_roots(a,b,c,real=false) =
// Function: polynomial() // Function: polynomial()
// Usage: // Usage:
// polynomial(p, z) // x = polynomial(p, z)
// Description: // Description:
// Evaluates specified real polynomial, p, at the complex or real input value, z. // Evaluates specified real polynomial, p, at the complex or real input value, z.
// The polynomial is specified as p=[a_n, a_{n-1},...,a_1,a_0] // The polynomial is specified as p=[a_n, a_{n-1},...,a_1,a_0]
@ -1409,8 +1435,8 @@ function polynomial(p,z,k,total) =
// Function: poly_mult() // Function: poly_mult()
// Usage: // Usage:
// polymult(p,q) // x = polymult(p,q)
// polymult([p1,p2,p3,...]) // x = polymult([p1,p2,p3,...])
// Description: // Description:
// Given a list of polynomials represented as real coefficient lists, with the highest degree coefficient first, // Given a list of polynomials represented as real coefficient lists, with the highest degree coefficient first,
// computes the coefficient list of the product polynomial. // computes the coefficient list of the product polynomial.
@ -1481,7 +1507,7 @@ function poly_add(p,q) =
// Function: poly_roots() // Function: poly_roots()
// Usage: // Usage:
// poly_roots(p,[tol]) // poly_roots(p,<tol>)
// Description: // Description:
// Returns all complex roots of the specified real polynomial p. // Returns all complex roots of the specified real polynomial p.
// The polynomial is specified as p=[a_n, a_{n-1},...,a_1,a_0] // The polynomial is specified as p=[a_n, a_{n-1},...,a_1,a_0]
@ -1551,7 +1577,7 @@ function _poly_roots(p, pderiv, s, z, tol, i=0) =
// Function: real_roots() // Function: real_roots()
// Usage: // Usage:
// real_roots(p, [eps], [tol]) // real_roots(p, <eps>, <tol>)
// Description: // Description:
// Returns the real roots of the specified real polynomial p. // Returns the real roots of the specified real polynomial p.
// The polynomial is specified as p=[a_n, a_{n-1},...,a_1,a_0] // The polynomial is specified as p=[a_n, a_{n-1},...,a_1,a_0]

View File

@ -1215,6 +1215,7 @@ function _sum_preserving_round(data, index=0) =
// Arguments: // Arguments:
// path = path to subdivide // path = path to subdivide
// N = scalar total number of points desired or with `method="segment"` can be a vector requesting `N[i]-1` points on segment i. // N = scalar total number of points desired or with `method="segment"` can be a vector requesting `N[i]-1` points on segment i.
// refine = number of points to add each segment.
// closed = set to false if the path is open. Default: True // closed = set to false if the path is open. Default: True
// exact = if true return exactly the requested number of points, possibly sacrificing uniformity. If false, return uniform point sample that may not match the number of points requested. Default: True // exact = if true return exactly the requested number of points, possibly sacrificing uniformity. If false, return uniform point sample that may not match the number of points requested. Default: True
// method = One of `"length"` or `"segment"`. If `"length"`, adds vertices evenly along the total path length. If `"segment"`, adds points evenly among the segments. Default: `"length"` // method = One of `"length"` or `"segment"`. If `"length"`, adds vertices evenly along the total path length. If `"segment"`, adds points evenly among the segments. Default: `"length"`
@ -1252,7 +1253,11 @@ function subdivide_path(path, N, refine, closed=true, exact=true, method="length
assert(is_path(path)) assert(is_path(path))
assert(method=="length" || method=="segment") assert(method=="length" || method=="segment")
assert(num_defined([N,refine]),"Must give exactly one of N and refine") assert(num_defined([N,refine]),"Must give exactly one of N and refine")
let(N = first_defined([N,len(path)*refine])) let(
N = !is_undef(N)? N :
!is_undef(refine)? len(path) * refine :
undef
)
assert((is_num(N) && N>0) || is_vector(N),"Parameter N to subdivide_path must be postive number or vector") assert((is_num(N) && N>0) || is_vector(N),"Parameter N to subdivide_path must be postive number or vector")
let( let(
count = len(path) - (closed?0:1), count = len(path) - (closed?0:1),

View File

@ -144,7 +144,7 @@ function region_path_crossings(path, region, closed=true, eps=EPSILON) = sort([
) let ( ) let (
isect = _general_line_intersection(segs[si], s2, eps=eps) isect = _general_line_intersection(segs[si], s2, eps=eps)
) if ( ) if (
!is_undef(isect) && !is_undef(isect[0]) &&
isect[1] >= 0-eps && isect[1] < 1+eps && isect[1] >= 0-eps && isect[1] < 1+eps &&
isect[2] >= 0-eps && isect[2] < 1+eps isect[2] >= 0-eps && isect[2] < 1+eps
) )

View File

@ -932,7 +932,10 @@ function oval(r, d, realign=false, circum=false, anchor=CENTER, spin=0) =
function regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0) = function regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0) =
let( let(
sc = 1/cos(180/n), sc = 1/cos(180/n),
r = get_radius(r1=ir*sc, r2=or, r=r, d1=id*sc, d2=od, d=d, dflt=side/2/sin(180/n)) ir = is_finite(ir)? ir*sc : undef,
id = is_finite(id)? id*sc : undef,
side = is_finite(side)? side/2/sin(180/n) : undef,
r = get_radius(r1=ir, r2=or, r=r, d1=id, d2=od, d=d, dflt=side)
) )
assert(!is_undef(r), "regular_ngon(): need to specify one of r, d, or, od, ir, id, side.") assert(!is_undef(r), "regular_ngon(): need to specify one of r, d, or, od, ir, id, side.")
let( let(
@ -970,7 +973,10 @@ function regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false
module regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0) { module regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false, anchor=CENTER, spin=0) {
sc = 1/cos(180/n); sc = 1/cos(180/n);
r = get_radius(r1=ir*sc, r2=or, r=r, d1=id*sc, d2=od, d=d, dflt=side/2/sin(180/n)); ir = is_finite(ir)? ir*sc : undef;
id = is_finite(id)? id*sc : undef;
side = is_finite(side)? side/2/sin(180/n) : undef;
r = get_radius(r1=ir, r2=or, r=r, d1=id, d2=od, d=d, dflt=side);
assert(!is_undef(r), "regular_ngon(): need to specify one of r, d, or, od, ir, id, side."); assert(!is_undef(r), "regular_ngon(): need to specify one of r, d, or, od, ir, id, side.");
path = regular_ngon(n=n, r=r, rounding=rounding, realign=realign); path = regular_ngon(n=n, r=r, rounding=rounding, realign=realign);
inset = opp_ang_to_hyp(rounding, (180-360/n)/2); inset = opp_ang_to_hyp(rounding, (180-360/n)/2);

View File

@ -196,7 +196,7 @@ function str_frac(str,mixed=true,improper=true,signed=true) =
signed && str[0]=="-" ? -str_frac(substr(str,1),mixed=mixed,improper=improper,signed=false) : signed && str[0]=="-" ? -str_frac(substr(str,1),mixed=mixed,improper=improper,signed=false) :
signed && str[0]=="+" ? str_frac(substr(str,1),mixed=mixed,improper=improper,signed=false) : signed && str[0]=="+" ? str_frac(substr(str,1),mixed=mixed,improper=improper,signed=false) :
mixed ? ( mixed ? (
str_find(str," ")>0 || is_undef(str_find(str,"/"))? ( !in_list(str_find(str," "), [undef,0]) || is_undef(str_find(str,"/"))? (
let(whole = str_split(str,[" "])) let(whole = str_split(str,[" "]))
_str_int_recurse(whole[0],10,len(whole[0])-1) + str_frac(whole[1], mixed=false, improper=improper, signed=false) _str_int_recurse(whole[0],10,len(whole[0])-1) + str_frac(whole[1], mixed=false, improper=improper, signed=false)
) : str_frac(str,mixed=false, improper=improper) ) : str_frac(str,mixed=false, improper=improper)
@ -292,12 +292,12 @@ function _str_cmp_recurse(str,sindex,pattern,plen,pindex=0,) =
// Usage: // Usage:
// str_find(str,pattern,[last],[all],[start]) // str_find(str,pattern,[last],[all],[start])
// Description: // Description:
// Searches input string `str` for the string `pattern` and returns the index or indices of the matches in `str`. // Searches input string `str` for the string `pattern` and returns the index or indices of the matches in `str`.
// By default str_find() returns the index of the first match in `str`. If `last` is true then it returns the index of the last match. // By default `str_find()` returns the index of the first match in `str`. If `last` is true then it returns the index of the last match.
// If the pattern is the empty string the first match is at zero and the last match is the last character of the `str`. // If the pattern is the empty string the first match is at zero and the last match is the last character of the `str`.
// If `start` is set then the search begins at index start, working either forward and backward from that position. If you set `start` // If `start` is set then the search begins at index start, working either forward and backward from that position. If you set `start`
// and `last` is true then the search will find the pattern if it begins at index `start`. If no match exists, returns undef. // and `last` is true then the search will find the pattern if it begins at index `start`. If no match exists, returns `undef`.
// If you set `all` to true then all str_find() returns all of the matches in a list, or an empty list if there are no matches. // If you set `all` to true then `str_find()` returns all of the matches in a list, or an empty list if there are no matches.
// Arguments: // Arguments:
// str = String to search. // str = String to search.
// pattern = string pattern to search for // pattern = string pattern to search for

View File

@ -8,7 +8,7 @@ module test_is_homogeneous(){
assert(is_homogeneous([[1,["a"]], [2,[true]]])==false); assert(is_homogeneous([[1,["a"]], [2,[true]]])==false);
assert(is_homogeneous([[1,["a"]], [2,[true]]],1)==true); assert(is_homogeneous([[1,["a"]], [2,[true]]],1)==true);
assert(is_homogeneous([[1,["a"]], [2,[true]]],2)==false); assert(is_homogeneous([[1,["a"]], [2,[true]]],2)==false);
assert(is_homogeneous([[1,["a"]], [true,["b"]]])==true); assert(is_homogeneous([[1,["a"]], [true,["b"]]])==false);
} }
test_is_homogeneous(); test_is_homogeneous();
@ -134,12 +134,12 @@ test_list_rotate();
module test_deduplicate() { module test_deduplicate() {
assert(deduplicate([8,3,4,4,4,8,2,3,3,8,8]) == [8,3,4,8,2,3,8]); assert_equal(deduplicate([8,3,4,4,4,8,2,3,3,8,8]), [8,3,4,8,2,3,8]);
assert(deduplicate(closed=true, [8,3,4,4,4,8,2,3,3,8,8]) == [8,3,4,8,2,3]); assert_equal(deduplicate(closed=true, [8,3,4,4,4,8,2,3,3,8,8]), [8,3,4,8,2,3]);
assert(deduplicate("Hello") == "Helo"); assert_equal(deduplicate("Hello"), "Helo");
assert(deduplicate([[3,4],[7,1.99],[7,2],[1,4]],eps=0.1) == [[3,4],[7,2],[1,4]]); assert_equal(deduplicate([[3,4],[7,1.99],[7,2],[1,4]],eps=0.1), [[3,4],[7,2],[1,4]]);
assert(deduplicate([], closed=true) == []); assert_equal(deduplicate([], closed=true), []);
assert(deduplicate([[1,[1,[undef]]],[1,[1,[undef]]],[1,[2]],[1,[2,[0]]]])==[[1, [1,[undef]]],[1,[2]],[1,[2,[0]]]]); assert_equal(deduplicate([[1,[1,[undef]]],[1,[1,[undef]]],[1,[2]],[1,[2,[0]]]]), [[1, [1,[undef]]],[1,[2]],[1,[2,[0]]]]);
} }
test_deduplicate(); test_deduplicate();

View File

@ -208,10 +208,12 @@ module test_valid_range() {
assert(valid_range([0:-1:0])); assert(valid_range([0:-1:0]));
assert(valid_range([10:-1:0])); assert(valid_range([10:-1:0]));
assert(valid_range([2.1:-1.1:0.1])); assert(valid_range([2.1:-1.1:0.1]));
assert(!valid_range([10:1:0])); if (version_num() < 20200600) {
assert(!valid_range([2.1:1.1:0.1])); assert(!valid_range([10:1:0]));
assert(!valid_range([0:-1:10])); assert(!valid_range([2.1:1.1:0.1]));
assert(!valid_range([0.1:-1.1:2.1])); assert(!valid_range([0:-1:10]));
assert(!valid_range([0.1:-1.1:2.1]));
}
} }
test_valid_range(); test_valid_range();

View File

@ -72,8 +72,7 @@ test_constrain();
module test_is_matrix() { module test_is_matrix() {
assert(is_matrix([[2,3,4],[5,6,7],[8,9,10]])); assert(is_matrix([[2,3,4],[5,6,7],[8,9,10]]));
assert(is_matrix([[2,3,4],[5,6,7],[8,9,10]],square=true)); assert(is_matrix([[2,3],[5,6],[8,9]],3,2));
assert(is_matrix([[2,3,4],[5,6,7],[8,9,10]],square=false));
assert(is_matrix([[2,3],[5,6],[8,9]],m=3,n=2)); assert(is_matrix([[2,3],[5,6],[8,9]],m=3,n=2));
assert(is_matrix([[2,3,4],[5,6,7]],m=2,n=3)); assert(is_matrix([[2,3,4],[5,6,7]],m=2,n=3));
assert(is_matrix([[2,3,4],[5,6,7]],2,3)); assert(is_matrix([[2,3,4],[5,6,7]],2,3));
@ -82,8 +81,6 @@ module test_is_matrix() {
assert(is_matrix([[2,3,4],[5,6,7]],n=3)); assert(is_matrix([[2,3,4],[5,6,7]],n=3));
assert(!is_matrix([[2,3,4],[5,6,7]],m=4)); assert(!is_matrix([[2,3,4],[5,6,7]],m=4));
assert(!is_matrix([[2,3,4],[5,6,7]],n=5)); assert(!is_matrix([[2,3,4],[5,6,7]],n=5));
assert(!is_matrix([[2,3,4],[5,6,7]],m=2,n=3,square=true));
assert(is_matrix([[2,3,4],[5,6,7],[8,9,10]],square=false));
assert(!is_matrix([[2,3],[5,6],[8,9]],m=2,n=3)); assert(!is_matrix([[2,3],[5,6],[8,9]],m=2,n=3));
assert(!is_matrix([[2,3,4],[5,6,7]],m=3,n=2)); assert(!is_matrix([[2,3,4],[5,6,7]],m=3,n=2));
assert(!is_matrix(undef)); assert(!is_matrix(undef));
@ -688,10 +685,10 @@ module test_count_true() {
assert_equal(count_true([1,false,undef]), 1); assert_equal(count_true([1,false,undef]), 1);
assert_equal(count_true([1,5,false]), 2); assert_equal(count_true([1,5,false]), 2);
assert_equal(count_true([1,5,true]), 3); assert_equal(count_true([1,5,true]), 3);
assert_equal(count_true([[0,0], [0,0]]), 0); assert_equal(count_true([[0,0], [0,0]]), 2);
assert_equal(count_true([[0,0], [1,0]]), 1); assert_equal(count_true([[0,0], [1,0]]), 2);
assert_equal(count_true([[1,1], [1,1]]), 4); assert_equal(count_true([[1,1], [1,1]]), 2);
assert_equal(count_true([[1,1], [1,1]], nmax=3), 3); assert_equal(count_true([1,1,1,1,1], nmax=3), 3);
} }
test_count_true(); test_count_true();

View File

@ -4,6 +4,8 @@ include <../std.scad>
module test_is_vector() { module test_is_vector() {
assert(is_vector([1,2,3]) == true); assert(is_vector([1,2,3]) == true);
assert(is_vector([[1,2,3]]) == false); assert(is_vector([[1,2,3]]) == false);
assert(is_vector([[1,2,3,4],[5,6,7,8]]) == false);
assert(is_vector([[1,2,3,4],[5,6]]) == false);
assert(is_vector(["foo"]) == false); assert(is_vector(["foo"]) == false);
assert(is_vector([]) == false); assert(is_vector([]) == false);
assert(is_vector(1) == false); assert(is_vector(1) == false);

View File

@ -38,7 +38,7 @@
// is_vector([1,1,1],all_nonzero=false); // Returns true // is_vector([1,1,1],all_nonzero=false); // Returns true
// is_vector([],zero=false); // Returns false // is_vector([],zero=false); // Returns false
function is_vector(v, length, zero, all_nonzero=false, eps=EPSILON) = function is_vector(v, length, zero, all_nonzero=false, eps=EPSILON) =
is_list(v) && is_num(0*(v*v)) is_list(v) && is_num(v[0]) && is_num(0*(v*v))
&& (is_undef(length) || len(v)==length) && (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)) ; && (!all_nonzero || all_nonzero(v)) ;

View File

@ -8,7 +8,7 @@
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
BOSL_VERSION = [2,0,436]; BOSL_VERSION = [2,0,437];
// Section: BOSL Library Version Functions // Section: BOSL Library Version Functions

View File

@ -388,17 +388,24 @@ function vnf_volume(vnf) =
function vnf_centroid(vnf) = function vnf_centroid(vnf) =
let( let(
verts = vnf[0], verts = vnf[0],
val = sum([ for(face=vnf[1], j=[1:1:len(face)-2]) vol = sum([
let( for(face=vnf[1], j=[1:1:len(face)-2]) let(
v0 = verts[face[0]], v0 = verts[face[0]],
v1 = verts[face[j]], v1 = verts[face[j]],
v2 = verts[face[j+1]], v2 = verts[face[j+1]]
vol = cross(v2,v1)*v0 ) cross(v2,v1)*v0
) ]),
[ vol, (v0+v1+v2)*vol ] pos = sum([
]) for(face=vnf[1], j=[1:1:len(face)-2]) let(
v0 = verts[face[0]],
v1 = verts[face[j]],
v2 = verts[face[j+1]],
vol = cross(v2,v1)*v0
)
(v0+v1+v2)*vol
])
) )
val[1]/val[0]/4; pos/vol/4;
function _triangulate_planar_convex_polygons(polys) = function _triangulate_planar_convex_polygons(polys) =