Add fit_to_range() to math.scad, fit_to_box() to vectors.scad

This commit is contained in:
Alex Matulich
2025-04-30 13:37:40 -07:00
parent 4411758c26
commit c04bfd152d
2 changed files with 206 additions and 90 deletions

185
math.scad
View File

@@ -56,7 +56,7 @@ NAN = acos(2);
// Creates a list of `n` numbers, starting at `s`, incrementing by `step` each time.
// You can also pass a list for n and then the length of the input list is used.
// Arguments:
// n = The length of the list of numbers to create, or a list to match the length of
// n = The length of the list of numbers to create, or a list to match the length of.
// s = The starting value of the list of numbers.
// step = The amount to increment successive numbers in the list.
// reverse = Reverse the list. Default: false.
@@ -80,11 +80,12 @@ function count(n,s=0,step=1,reverse=false) = let(n=is_list(n) ? len(n) : n)
// l = lerp(a, b, LIST);
// Description:
// Interpolate between two values or vectors.
// If `u` is given as a number, returns the single interpolated value.
// If `u` is 0.0, then the value of `a` is returned.
// If `u` is 1.0, then the value of `b` is returned.
// If `u` is a range, or list of numbers, returns a list of interpolated values.
// It is valid to use a `u` value outside the range 0 to 1. The result will be an extrapolation
// * If `u` is given as a number, returns the single interpolated value.
// * If `u` is 0.0, then the value of `a` is returned.
// * If `u` is 1.0, then the value of `b` is returned.
// * If `u` is a range, or list of numbers, returns a list of interpolated values.
// .
// It is valid to use a `u` value outside the range 0 to 1 to extrapolate
// along the slope formed by `a` and `b`.
// Arguments:
// a = First value or vector.
@@ -105,9 +106,9 @@ function count(n,s=0,step=1,reverse=false) = let(n=is_list(n) ? len(n) : n)
// // Points colored in ROYGBIV order.
// rainbow(pts) translate($item) circle(d=3,$fn=8);
function lerp(a,b,u) =
assert(same_shape(a,b), "Bad or inconsistent inputs to lerp")
assert(same_shape(a,b), "\nBad or inconsistent inputs to lerp.")
is_finite(u)? (1-u)*a + u*b :
assert(is_finite(u) || is_vector(u) || valid_range(u), "Input u to lerp must be a number, vector, or valid range.")
assert(is_finite(u) || is_vector(u) || valid_range(u), "\nInput u to lerp must be a number, vector, or valid range.")
[for (v = u) (1-v)*a + v*b ];
@@ -133,7 +134,7 @@ function lerp(a,b,u) =
// l = lerpn(0,1,6); // Returns: [0, 0.2, 0.4, 0.6, 0.8, 1]
// l = lerpn(0,1,5,false); // Returns: [0, 0.2, 0.4, 0.6, 0.8]
function lerpn(a,b,n,endpoint=true) =
assert(same_shape(a,b), "Bad or inconsistent inputs to lerpn")
assert(same_shape(a,b), "\nBad or inconsistent inputs to lerpn.")
assert(is_int(n))
assert(is_bool(endpoint))
let( d = n - (endpoint? 1 : 0) )
@@ -148,7 +149,7 @@ function lerpn(a,b,n,endpoint=true) =
// Description:
// Compute bilinear interpolation between four values using two
// coordinates that are meant to lie in [0,1]. (If they are outside
// this range, the function will extrapolate values.) The `pts`
// this range, the function extrapolates values.) The `pts`
// argument is a list of the four values at the for corners, `[A,B,C,D]`.
// These values are arranged on the corners as shown below. The `x` and
// `y` parameters give the fraction of the distance from the left and bottom
@@ -196,7 +197,7 @@ function bilerp(points,x,y) =
// sqr([2,3,4]); // Returns: 29
// sqr([[1,2],[3,4]]); // Returns [[7,10],[15,22]]
function sqr(x) =
assert(is_finite(x) || is_vector(x) || is_matrix(x), "Input is not a number nor a list of numbers.")
assert(is_finite(x) || is_vector(x) || is_matrix(x), "\nInput is not a number nor a list of numbers.")
x*x;
@@ -213,7 +214,7 @@ function sqr(x) =
// log2(16); // Returns: 4
// log2(256); // Returns: 8
function log2(x) =
assert( is_finite(x), "Input is not a number.")
assert( is_finite(x), "\nInput is not a number.")
ln(x)/ln(2);
// this may return NAN or INF; should it check x>0 ?
@@ -234,7 +235,7 @@ function log2(x) =
// l = hypot(3,4); // Returns: 5
// l = hypot(3,4,5); // Returns: ~7.0710678119
function hypot(x,y,z=0) =
assert( is_vector([x,y,z]), "Improper number(s).")
assert( is_vector([x,y,z]), "\nImproper number(s).")
norm([x,y,z]);
@@ -248,14 +249,14 @@ function hypot(x,y,z=0) =
// Returns the factorial of the given integer value, or n!/d! if d is given.
// Arguments:
// n = The integer number to get the factorial of. (n!)
// d = If given, the returned value will be (n! / d!)
// d = If given, the returned value is (n! / d!)
// Example:
// x = factorial(4); // Returns: 24
// y = factorial(6); // Returns: 720
// z = factorial(9); // Returns: 362880
function factorial(n,d=0) =
assert(is_int(n) && is_int(d) && n>=0 && d>=0, "Factorial is defined only for non negative integers")
assert(d<=n, "d cannot be larger than n")
assert(is_int(n) && is_int(d) && n>=0 && d>=0, "\nFactorial is defined only for non negative integers.")
assert(d<=n, "\nd cannot be larger than n.")
product([1,for (i=[n:-1:d+1]) i]);
@@ -274,7 +275,7 @@ function factorial(n,d=0) =
// y = binomial(4); // Returns: [1,4,6,4,1]
// z = binomial(6); // Returns: [1,6,15,20,15,6,1]
function binomial(n) =
assert( is_int(n) && n>0, "Input is not an integer greater than 0.")
assert( is_int(n) && n>0, "\nInput must be an integer greater than 0.")
[for( c = 1, i = 0;
i<=n;
c = c*(n-i)/(i+1), i = i+1
@@ -296,7 +297,7 @@ function binomial(n) =
// x = binomial_coefficient(3,2); // Returns: 3
// y = binomial_coefficient(10,6); // Returns: 210
function binomial_coefficient(n,k) =
assert( is_int(n) && is_int(k), "Some input is not a number.")
assert( is_int(n) && is_int(k), "\nSome input is not a number.")
k < 0 || k > n ? 0 :
k ==0 || k ==n ? 1 :
let( k = min(k, n-k),
@@ -316,14 +317,14 @@ function binomial_coefficient(n,k) =
// Description:
// Computes the Greatest Common Divisor/Factor of `a` and `b`.
function gcd(a,b) =
assert(is_int(a) && is_int(b),"Arguments to gcd must be integers")
assert(is_int(a) && is_int(b),"\nArguments to gcd must be integers.")
b==0 ? abs(a) : gcd(b,a % b);
// Computes lcm for two integers
function _lcm(a,b) =
assert(is_int(a) && is_int(b), "Invalid non-integer parameters to lcm")
assert(a!=0 && b!=0, "Arguments to lcm should not be zero")
assert(is_int(a) && is_int(b), "\nInvalid non-integer parameters to lcm.")
assert(a!=0 && b!=0, "\nArguments to lcm must be non-zero.")
abs(a*b) / gcd(a,b);
@@ -348,7 +349,7 @@ function lcm(a,b=[]) =
!is_list(a) && !is_list(b)
? _lcm(a,b)
: let( arglist = concat(force_list(a),force_list(b)) )
assert(len(arglist)>0, "Invalid call to lcm with empty list(s)")
assert(len(arglist)>0, "\nInvalid call to lcm with empty list(s).")
_lcmlist(arglist);
// Function rational_approx()
@@ -392,7 +393,7 @@ function _cfrac_to_pq(cfrac,p=0,q=1,ind) =
// a = sinh(x);
// Description: Takes a value `x`, and returns the hyperbolic sine of it.
function sinh(x) =
assert(is_finite(x), "The input must be a finite number.")
assert(is_finite(x), "\nThe input must be a finite number.")
(exp(x)-exp(-x))/2;
// Function: cosh()
@@ -403,7 +404,7 @@ function sinh(x) =
// a = cosh(x);
// Description: Takes a value `x`, and returns the hyperbolic cosine of it.
function cosh(x) =
assert(is_finite(x), "The input must be a finite number.")
assert(is_finite(x), "\nThe input must be a finite number.")
(exp(x)+exp(-x))/2;
@@ -416,7 +417,7 @@ function cosh(x) =
// Description: Takes a value `x`, and returns the hyperbolic tangent of it.
function tanh(x) =
assert(is_finite(x), "The input must be a finite number.")
assert(is_finite(x), "\nThe input must be a finite number.")
let (e = exp(2*x) + 1)
e == INF ? 1 : (e-2)/e;
@@ -428,7 +429,7 @@ function tanh(x) =
// a = asinh(x);
// Description: Takes a value `x`, and returns the inverse hyperbolic sine of it.
function asinh(x) =
assert(is_finite(x), "The input must be a finite number.")
assert(is_finite(x), "\nThe input must be a finite number.")
ln(x+sqrt(x*x+1));
@@ -440,7 +441,7 @@ function asinh(x) =
// a = acosh(x);
// Description: Takes a value `x`, and returns the inverse hyperbolic cosine of it.
function acosh(x) =
assert(is_finite(x), "The input must be a finite number.")
assert(is_finite(x), "\nThe input must be a finite number.")
ln(x+sqrt(x*x-1));
@@ -452,7 +453,7 @@ function acosh(x) =
// a = atanh(x);
// Description: Takes a value `x`, and returns the inverse hyperbolic tangent of it.
function atanh(x) =
assert(is_finite(x), "The input must be a finite number.")
assert(is_finite(x), "\nThe input must be a finite number.")
ln((1+x)/(1-x))/2;
@@ -467,7 +468,7 @@ function atanh(x) =
// Description:
// Quantize a value `x` to an integer multiple of `y`, rounding to the nearest multiple.
// The value of `y` does NOT have to be an integer. If `x` is a list, then every item
// in that list will be recursively quantized.
// in that list is recursively quantized.
// Arguments:
// x = The value or list to quantize.
// y = Positive quantum to quantize to
@@ -491,7 +492,7 @@ function atanh(x) =
// q = quant([9,10,10.4,10.5,11,12],3); // Returns: [9,9,9,12,12,12]
// r = quant([[9,10,10.4],[10.5,11,12]],3); // Returns: [[9,9,9],[12,12,12]]
function quant(x,y) =
assert( is_finite(y) && y>0, "The quantum `y` must be a positive value.")
assert( is_finite(y) && y>0, "\nThe quantum `y` must be a positive value.")
is_num(x) ? round(x/y)*y
: _roundall(x/y)*y;
@@ -508,7 +509,7 @@ function _roundall(data) =
// Description:
// Quantize a value `x` to an integer multiple of `y`, rounding down to the previous multiple.
// The value of `y` does NOT have to be an integer. If `x` is a list, then every item in that
// list will be recursively quantized down.
// list is recursively quantized down.
// Arguments:
// x = The value or list to quantize.
// y = Postive quantum to quantize to.
@@ -532,7 +533,7 @@ function _roundall(data) =
// q = quantdn([9,10,10.4,10.5,11,12],3); // Returns: [9,9,9,9,9,12]
// r = quantdn([[9,10,10.4],[10.5,11,12]],3); // Returns: [[9,9,9],[9,9,12]]
function quantdn(x,y) =
assert( is_finite(y) && y>0, "The quantum `y` must be a positive value.")
assert( is_finite(y) && y>0, "\nThe quantum `y` must be a positive value.")
is_num(x) ? floor(x/y)*y
: _floorall(x/y)*y;
@@ -549,7 +550,7 @@ function _floorall(data) =
// Description:
// Quantize a value `x` to an integer multiple of `y`, rounding up to the next multiple.
// The value of `y` does NOT have to be an integer. If `x` is a list, then every item in
// that list will be recursively quantized up.
// that list is recursively quantized up.
// Arguments:
// x = The value or list to quantize.
// y = Positive quantum to quantize to.
@@ -573,7 +574,7 @@ function _floorall(data) =
// q = quantup([9,10,10.4,10.5,11,12],3); // Returns: [9,12,12,12,12,12]
// r = quantup([[9,10,10.4],[10.5,11,12]],3); // Returns: [[9,12,12],[12,12,12]]
function quantup(x,y) =
assert( is_finite(y) && y>0, "The quantum `y` must be a positive value.")
assert( is_finite(y) && y>0, "\nThe quantum `y` must be a positive value.")
is_num(x) ? ceil(x/y)*y
: _ceilall(x/y)*y;
@@ -602,7 +603,7 @@ function _ceilall(data) =
// d = constrain(9.1, 0, 9); // Returns: 9
// e = constrain(-0.1, 0, 9); // Returns: 0
function constrain(v, minval, maxval) =
assert( is_finite(v+minval+maxval), "Input must be finite number(s).")
assert( is_finite(v+minval+maxval), "\nInput must be finite number(s).")
min(maxval, max(minval, v));
@@ -613,7 +614,7 @@ function constrain(v, minval, maxval) =
// Usage:
// mod = posmod(x, m)
// Description:
// Returns the positive modulo `m` of `x`. Value returned will be satisfy `0 <= mod < m`.
// Returns the positive modulo `m` of `x`. The value returned satisfies `0 <= mod < m`.
// Arguments:
// x = The value to constrain.
// m = Modulo value.
@@ -626,7 +627,7 @@ function constrain(v, minval, maxval) =
// f = posmod(700,360); // Returns: 340
// g = posmod(3,2.5); // Returns: 0.5
function posmod(x,m) =
assert( is_finite(x) && is_finite(m) && !approx(m,0) , "Input must be finite numbers. The divisor cannot be zero.")
assert( is_finite(x) && is_finite(m) && !approx(m,0) , "\nInput must be finite numbers. The divisor cannot be zero.")
(x%m+m)%m;
@@ -646,7 +647,7 @@ function posmod(x,m) =
// a5 = modang(270); // Returns: -90
// a6 = modang(700); // Returns: -20
function modang(x) =
assert( is_finite(x), "Input must be a finite number.")
assert( is_finite(x), "\nInput must be a finite number.")
let(xx = posmod(x,360)) xx<180? xx : xx-360;
@@ -660,12 +661,12 @@ function modang(x) =
// Takes two angles (degrees) in any range and finds the angle halfway between
// the given angles, where halfway is interpreted using the shorter direction.
// In the case where the angles are exactly 180 degrees apart,
// it will return `angle1+90`. The returned angle is always in the interval [0,360).
// it returns `angle1+90`. The returned angle is always in the interval [0,360).
// Arguments:
// angle1 = first angle
// angle2 = second angle
function mean_angle(angle1,angle2) =
assert(is_vector([angle1,angle2]), "Inputs must be finite numbers.")
assert(is_vector([angle1,angle2]), "\nInputs must be finite numbers.")
let(
ang1 = posmod(angle1,360),
ang2 = posmod(angle2,360)
@@ -675,6 +676,62 @@ function mean_angle(angle1,angle2) =
: posmod((ang1+ang2-360)/2,360);
// Function: fit_to_range()
// Synopsis: Scale the values in an array to span a range.
// Topics: Math
// See Also: fit_to_box()
// Usage:
// a = fit_to_range(M, minval, maxval);
// Description:
// Given a vector or list of vectors, scale the values so that they span the full range from `minval` to
// `maxval`. If `minval>maxval`, then the output is a rescaled mirror image of the input.
// Arguments:
// M = vector or list of vectors to scale. A list of vectors needn't be a rectangular matrix; the vectors can have different lengths.
// minval = Minimum value of the rescaled data range.
// maxval = Maximum value of the rescaled data range.
// Example:
// a = [0.0066, 0.194, 0.598, 0.194, 0.0066];
// v = fit_to_range(a,5,10);
// // Returns: [5, 6.584, 10, 6.584, 5]
//
// b = [ [20,20,0], [40,80,20], [60,40,20] ];
// m = fit_to_range(b,-10,10);
// // Returns: [[-5,-5,-10], [0,10,-5], [5,0,-5]]
//
// c = [2,3,4,5,6];
// inv = fit_to_range(c, 20, 8); // inverted range!
// // Returns: [20, 17, 14, 11, 8]
// Example(3D): A texture tile that spans the range [-1,1] is rescaled to span [0,1], resulting in the edges of the texture (which were at z=0) to be raised due to raising the minimu from -1 to 0.
// tex = [
// [0,0,0, 0, 0, 0,0,0,0],
// [0,1,1, 1, 1, 1,1,1,0],
// [0,1,0, 0, 0, 0,0,1,0],
// [0,1,0,-1,-1,-1,0,1,0],
// [0,1,0,-1, 0,-1,0,1,0],
// [0,1,0,-1,-1,-1,0,1,0],
// [0,1,0, 0, 0, 0,0,1,0],
// [0,1,1, 1, 1, 1,1,1,0],
// [0,0,0, 0, 0, 0,0,0,0]
// ];
// left(5) textured_tile(tex,
// [9,9,2],tex_reps=1, anchor=BOTTOM);
// right(5) textured_tile(fit_to_range(tex,0,1),
// [9,9,2],tex_reps=1, anchor=BOTTOM);
function fit_to_range(M, minval, maxval) =
let(
is_vec = is_vector(M),
dum = assert(is_vec || (is_list(M) && is_vector(M[0])), "\nParameter M must be a vector or list of vectors."),
rowlen = len(is_vec ? M : M[0]),
v = is_vec ? M : flatten(M),
a = min(v),
b = max(v)
) a==b ? M
: is_vec ? add_scalar(add_scalar(M,-a) * ((maxval-minval)/(b-a)), minval)
: [ for(row=M)
add_scalar(add_scalar(row, -a) * ((maxval-minval)/(b-a)), + minval)
];
// Section: Operations on Lists (Sums, Mean, Products)
@@ -688,7 +745,7 @@ function mean_angle(angle1,angle2) =
// 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 matrices, returns the sum of the matrices.
// If passed an empty list, the value of `dflt` will be returned.
// If passed an empty list, the value of `dflt` is returned.
// Arguments:
// v = The list to get the sum of.
// dflt = The default value to return if `v` is an empty list. Default: 0
@@ -697,7 +754,7 @@ function mean_angle(angle1,angle2) =
// sum([[1,2,3], [3,4,5], [5,6,7]]); // returns [9, 12, 15]
function sum(v, dflt=0) =
v==[]? dflt :
assert(is_consistent(v), "Input to sum is non-numeric or inconsistent")
assert(is_consistent(v), "\nInput to sum is non-numeric or inconsistent.")
is_finite(v[0]) || is_vector(v[0]) ? [for(i=v) 1]*v :
_sum(v,v[0]*0);
@@ -721,7 +778,7 @@ function _sum(v,_total,_i=0) = _i>=len(v) ? _total : _sum(v,_total+v[_i], _i+1);
// mean([2,3,4]); // returns 3.
// mean([[1,2,3], [3,4,5], [5,6,7]]); // returns [3, 4, 5]
function mean(v) =
assert(is_list(v) && len(v)>0, "Invalid list.")
assert(is_list(v) && len(v)>0, "\nInvalid list.")
sum(v)/len(v);
@@ -735,7 +792,7 @@ function mean(v) =
// Description:
// Returns the median of the given vector.
function median(v) =
assert(is_vector(v), "Input to median must be a vector")
assert(is_vector(v), "\nInput to median must be a vector.")
len(v)%2 ? max( list_smallest(v, ceil(len(v)/2)) ) :
let( lowest = list_smallest(v, len(v)/2 + 1),
max = max(lowest),
@@ -762,7 +819,7 @@ function median(v) =
// deltas([2,5,9,17]); // returns [3,4,8].
// deltas([[1,2,3], [3,6,8], [4,8,11]]); // returns [[2,4,5], [1,2,3]]
function deltas(v, wrap=false) =
assert( is_consistent(v) && len(v)>1 , "Inconsistent list or with length<=1.")
assert( is_consistent(v) && len(v)>1 , "\nInconsistent list or with length<=1.")
[for (p=pair(v,wrap)) p[1]-p[0]] ;
@@ -784,7 +841,7 @@ function deltas(v, wrap=false) =
// cumsum([[1,2,3], [3,4,5], [5,6,7]]); // returns [[1,2,3], [4,6,8], [9,12,15]]
function cumsum(v) =
v==[] ? [] :
assert(is_consistent(v), "The input is not consistent." )
assert(is_consistent(v), "\nThe input is not consistent." )
[for (a = v[0],
i = 1
;
@@ -822,7 +879,7 @@ function product(list,right=true) =
if (i==len(list)) a][0]
:
assert(is_vector(list) || (is_matrix(list[0],square=true) && is_consistent(list)),
"Input must be a vector, a list of vectors, or a list of matrices.")
"\nInput must be a vector, a list of vectors, or a list of matrices.")
[for (a = list[0],
i = 1
;
@@ -842,8 +899,8 @@ function product(list,right=true) =
// Description:
// Returns a list where each item is the cumulative product of all items up to and including the corresponding entry in the input list.
// If passed an array of vectors, returns a list of elementwise vector products. If passed a list of square matrices by default returns matrix
// products multiplying on the left, so a list `[A,B,C]` will produce the output `[A,BA,CBA]`. If you set `right=true` then it returns
// the product of multiplying on the right, so a list `[A,B,C]` will produce the output `[A,AB,ABC]` in that case.
// products multiplying on the left, so a list `[A,B,C]` produces the output `[A,BA,CBA]`. If you set `right=true` then it returns
// the product of multiplying on the right, so a list `[A,B,C]` produces the output `[A,AB,ABC]` in that case.
// Arguments:
// list = The list to get the cumulative product of.
// right = if true multiply matrices on the right
@@ -865,7 +922,7 @@ function cumprod(list,right=false) =
a]
:
assert(is_vector(list) || (is_matrix(list[0],square=true) && is_consistent(list)),
"Input must be a listector, a list of listectors, or a list of matrices.")
"\nInput must be a listector, a list of listectors, or a list of matrices.")
[for (a = list[0],
i = 1
;
@@ -903,7 +960,7 @@ function convolve(p,q) =
p==[] || q==[] ? [] :
assert( (is_vector(p) || is_matrix(p))
&& ( is_vector(q) || (is_matrix(q) && ( !is_vector(p[0]) || (len(p[0])==len(q[0])) ) ) ) ,
"The inputs should be vectors or paths all of the same dimension.")
"\nThe inputs should be vectors or paths all of the same dimension.")
let( n = len(p),
m = len(q))
[for(i=[0:n+m-2], k1 = max(0,i-n+1), k2 = min(i,m-1) )
@@ -959,8 +1016,8 @@ function sum_of_sines(a, sines) =
// ints = rand_int(0,100,3);
// int = rand_int(-10,10,1)[0];
function rand_int(minval, maxval, n, seed=undef) =
assert( is_finite(minval+maxval+n) && (is_undef(seed) || is_finite(seed) ), "Input must be finite numbers.")
assert(maxval >= minval, "Max value cannot be smaller than minval")
assert( is_finite(minval+maxval+n) && (is_undef(seed) || is_finite(seed) ), "\nInput must be finite numbers.")
assert(maxval >= minval, "\nMax value cannot be smaller than minval.")
let (rvect = is_def(seed) ? rands(minval,maxval+1,n,seed) : rands(minval,maxval+1,n))
[for(entry = rvect) floor(entry)];
@@ -981,9 +1038,9 @@ function rand_int(minval, maxval, n, seed=undef) =
// scale = the scale of the point coordinates. Default: 1
// seed = an optional seed for the random generation.
function random_points(n, dim, scale=1, seed) =
assert( is_int(n) && n>=0, "The number of points should be a non-negative integer.")
assert( is_int(dim) && dim>=1, "The point dimensions should be an integer greater than 1.")
assert( is_finite(scale) || is_vector(scale,dim), "The scale should be a number or a vector with length equal to d.")
assert( is_int(n) && n>=0, "\nThe number of points should be a non-negative integer.")
assert( is_int(dim) && dim>=1, "\nThe point dimensions should be an integer greater than 1.")
assert( is_finite(scale) || is_vector(scale,dim), "\nThe scale should be a number or a vector with length equal to d.")
let(
rnds = is_undef(seed)
? rands(-1,1,n*dim)
@@ -1011,18 +1068,18 @@ function gaussian_rands(n=1, mean=0, cov=1, seed=undef) =
let(
dim = is_num(mean) ? 1 : len(mean)
)
assert((dim==1 && is_num(cov)) || is_matrix(cov,dim,dim),"mean and covariance matrix not compatible")
assert((dim==1 && is_num(cov)) || is_matrix(cov,dim,dim),"\nmean and covariance matrix not compatible.")
assert(is_undef(seed) || is_finite(seed))
let(
nums = is_undef(seed)? rands(0,1,dim*n*2) : rands(0,1,dim*n*2,seed),
rdata = [for (i = count(dim*n,0,2)) sqrt(-2*ln(nums[i]))*cos(360*nums[i+1])]
)
dim==1 ? add_scalar(sqrt(cov)*rdata,mean) :
assert(is_matrix_symmetric(cov),"Supplied covariance matrix is not symmetric")
assert(is_matrix_symmetric(cov),"\nSupplied covariance matrix is not symmetric.")
let(
L = cholesky(cov)
)
assert(is_def(L), "Supplied covariance matrix is not positive definite")
assert(is_def(L), "\nSupplied covariance matrix is not positive definite.")
move(mean,list_to_matrix(rdata,dim)*transpose(L));
@@ -1117,7 +1174,7 @@ function random_polygon(n=3,size=1, seed) =
// uses a two point method if sufficient points are available: f'(t) = (3*(f(t+h)-f(t)) - (f(t+2*h)-f(t+h)))/2h.
// .
// If `h` is a vector then it is assumed to be nonuniform, with h[i] giving the sampling distance
// between data[i+1] and data[i], and the data values will be linearly resampled at each corner
// between data[i+1] and data[i], and the data values are linearly resampled at each corner
// to produce a uniform spacing for the derivative estimate. At the endpoints a single point method
// is used: f'(t) = (f(t+h)-f(t))/h.
// Arguments:
@@ -1287,7 +1344,7 @@ function complex(list) =
// c = c_mul(z1,z2)
// Description:
// Multiplies two complex numbers, vectors or matrices, where complex numbers
// or entries are represented as vectors: [REAL, IMAGINARY]. Note that all
// or entries are represented as vectors: [REAL, IMAGINARY]. All
// entries in both arguments must be complex.
// Arguments:
// z1 = First complex number, vector or matrix
@@ -1411,7 +1468,7 @@ function c_norm(z) = norm_fro(z);
// roots = quadratic_roots(a, b, c, [real])
// Description:
// 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
// may be more reliable than the general root finder at distinguishing
// real roots from complex roots.
@@ -1491,7 +1548,7 @@ function poly_mult(p,q) =
// Computes division of the numerator polynomial by the denominator polynomial and returns
// a list of two polynomials, [quotient, remainder]. If the division has no remainder then
// the zero polynomial [0] is returned for the remainder. Similarly if the quotient is zero
// the returned quotient will be [0].
// the returned quotient is [0].
function poly_div(n,d) =
assert( is_vector(n) && is_vector(d) , "Invalid polynomials." )
let( d = _poly_trim(d),
@@ -1664,7 +1721,7 @@ function real_roots(p,eps=undef,tol=1e-14) =
// argument. You must have a version of OpenSCAD that supports function literals
// (2021.01 or newer). The tolerance (tol) specifies the accuracy of the solution:
// abs(f(x)) < tol * yrange, where yrange is the range of observed function values.
// This function can only find roots that cross the x axis: it cannot find the
// This function can find only those roots that *cross* the x axis: it cannot find the
// the root of x^2.
// Arguments:
// f = function literal for a scalar-valued single variable function

View File

@@ -380,31 +380,6 @@ function vector_perp(v,w) =
// Section: Vector Searching
// Function: pointlist_bounds()
// Synopsis: Returns the min and max bounding coordinates for the given list of points.
// Topics: Geometry, Bounding Boxes, Bounds
// See Also: closest_point(), furthest_point(), vnf_bounds()
// Usage:
// pt_pair = pointlist_bounds(pts);
// Description:
// Finds the bounds containing all the points in `pts` which can be a list of points in any dimension.
// Returns a list of two items: a list of the minimums and a list of the maximums. For example, with
// 3d points `[[MINX, MINY, MINZ], [MAXX, MAXY, MAXZ]]`
// Arguments:
// pts = List of points.
function pointlist_bounds(pts) =
assert(is_path(pts,dim=undef,fast=true) , "\nInvalid pointlist." )
let(
select = ident(len(pts[0])),
spread = [
for(i=[0:len(pts[0])-1])
let( spreadi = pts*select[i] )
[ min(spreadi), max(spreadi) ]
]
) transpose(spread);
// Function: closest_point()
// Synopsis: Finds the closest point in a list of points.
// Topics: Geometry, Points, Distance
@@ -417,7 +392,7 @@ function pointlist_bounds(pts) =
// pt = The point to find the closest point to.
// points = The list of points to search.
function closest_point(pt, points) =
assert( is_vector(pt), "\nInvalid point." )
assert(is_vector(pt), "\nInvalid point." )
assert(is_path(points,dim=len(pt)), "\nInvalid pointlist or incompatible dimensions." )
min_index([for (p=points) norm(p-pt)]);
@@ -687,4 +662,88 @@ function _insert_many(list, k, newlist,i=0) =
// Section: Bounds
// Function: pointlist_bounds()
// Synopsis: Returns the min and max bounding coordinates for the given list of points.
// Topics: Geometry, Bounding Boxes, Bounds
// See Also: closest_point(), furthest_point(), vnf_bounds()
// Usage:
// pt_pair = pointlist_bounds(pts);
// Description:
// Finds the bounds containing all the points in `pts`, which can be a list of points in any dimension.
// Returns a list of two items: a list of the minimums and a list of the maximums. For example, with
// 3d points `[[MINX, MINY, MINZ], [MAXX, MAXY, MAXZ]]`
// Arguments:
// pts = List of points.
function pointlist_bounds(pts) =
assert(is_path(pts,dim=undef,fast=true) , "\nInvalid pointlist." )
let(
select = ident(len(pts[0])),
spread = [
for(i=[0:len(pts[0])-1])
let( spreadi = pts*select[i] )
[ min(spreadi), max(spreadi) ]
]
) transpose(spread);
// Function: fit_to_box()
// Synopsis: Scale the x, y, and/or z coordinantes of a list of points to span a range.
// Topics: Geometry, Bounding Boxes, Bounds, VNF
// See Also: fit_to_range()
// Usage:
// new_pts = fit_to_box(pts, [x=], [y=], [z=]);
// new_vnf = fit_to_box(vnf, [x=], [y=], [z=]);
// Description:
// Given a list of 2D or 3D points, or a VNF structure, rescale and position one or more of the coordinates
// to fit within specified ranges. At least one range (`x`, `y`, or `z`) must be specified. A normal use case
// for this function is to rescale a VNF texture to fit within `0 <= z <= 1`.
// .
// While a range is typically `[min_value,max_value]`, the minimum and maximum values can be reversed,
// resulting in new coordinates being a rescaled mirror image of the original coordinates.
// Arguments:
// pts = List of points, or a VNF structure.
// x = `[min,max]` of rescaled x coordinates. Default: undef
// y = `[min,max]` of rescaled y coordinates. Default: undef
// z = `[min,max]` of rescaled z coordinates. Default: undef
// Example(3D): A prismoid (left) is rescaled to fit new x and z bounds. The z bounds minimum and maximum values are reversed, resulting in the new object on the right having inverted z coordinates.
// vnf = prismoid(size1=[50,30], size2=[20,20], h=20, shift=[15,5]);
// vnf_boxed = fit_to_box(vnf, x=[30,55], z=[5,-15]);
// vnf_polyhedron(vnf);
// vnf_polyhedron(vnf_boxed);
function fit_to_box(pts, x, y, z) =
assert(is_path(pts) || is_vnf(pts), "\npts must be a valid 2D or 3D path, or a VNF structure.")
assert(any_defined([x,y,z]), "\nAt least one [min,max] range x, y, or z must be defined.")
assert(is_undef(x) || is_vector(x,2), "\nx must be a 2-vector [min,max].")
assert(is_undef(y) || is_vector(y,2), "\nx must be a 2-vector [min,max].")
assert(is_undef(z) || is_vector(z,2), "\nx must be a 2-vector [min,max].")
let(
isvnf = is_vnf(pts),
p = isvnf ? vnf_vertices(pts) : pts,
dim = len(p[0]),
dum = assert(dim<3 || (dim==3 && is_def(z)), "\n2D data detected with z range specified."),
whichdim = [is_def(x), is_def(y), is_def(z)], // extract only the columns needed
xcol = whichdim.x ? column(p,0) : [0],
ycol = whichdim.y ? column(p,1) : [0],
zcol = whichdim.z ? column(p,2) : [0],
xmin = min(xcol),
ymin = min(ycol),
zmin = min(zcol),
// new scales
xscale = whichdim.x ? (x[1]-x[0]) / (max(xcol)-xmin) : 1,
yscale = whichdim.y ? (y[1]-y[0]) / (max(ycol)-ymin) : 1,
zscale = whichdim.z ? (z[1]-z[0]) / (max(zcol)-zmin) : 1,
// new offsets
xo = whichdim.x ? x[0] : 0,
yo = whichdim.y ? y[0] : 0,
zo = whichdim.z ? z[0] : 0,
// shift original min to 0, rescale to new scale, shift back to new min
newpts = move(dim>2 ? [xo,yo,zo] : [xo,yo],
scale(dim>2 ? [xscale,yscale,zscale] : [xscale,yscale],
move(dim>2 ? -[xmin,ymin,zmin] : -[xmin,ymin], pts)))
) isvnf ? [newpts[0], pts[1]] : newpts;
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap