////////////////////////////////////////////////////////////////////// // LibFile: math.scad // Math helper functions. // To use, add the following lines to the beginning of your file: // ``` // use // ``` ////////////////////////////////////////////////////////////////////// /* BSD 2-Clause License Copyright (c) 2017, Revar Desmera All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ include // Function: Cpi() // Status: DEPRECATED, use `PI` instead. // Description: // Returns the value of pi. function Cpi() = PI; // Deprecated! Use the variable PI instead. // Section: Simple Calculations // Function: quant() // Description: // Quantize a value `x` to an integer multiple of `y`, rounding to the nearest multiple. // Arguments: // x = The value to quantize. // y = The multiple to quantize to. function quant(x,y) = floor(x/y+0.5)*y; // Function: quantdn() // Description: // Quantize a value `x` to an integer multiple of `y`, rounding down to the previous multiple. // Arguments: // x = The value to quantize. // y = The multiple to quantize to. function quantdn(x,y) = floor(x/y)*y; // Function: quantup() // Description: // Quantize a value `x` to an integer multiple of `y`, rounding up to the next multiple. // Arguments: // x = The value to quantize. // y = The multiple to quantize to. function quantup(x,y) = ceil(x/y)*y; // Function: constrain() // Usage: // constrain(v, minval, maxval); // Description: // Constrains value to a range of values between minval and maxval, inclusive. // Arguments: // v = value to constrain. // minval = minimum value to return, if out of range. // maxval = maximum value to return, if out of range. function constrain(v, minval, maxval) = min(maxval, max(minval, v)); // Function: posmod() // Usage: // posmod(x,m) // Description: // Returns the positive modulo `m` of `x`. Value returned will be in the range 0 ... `m`-1. // This if useful for normalizing angles to 0 ... 360. // Arguments: // x = The value to constrain. // m = Modulo value. function posmod(x,m) = (x % m + m) % m; // Function: modrange() // Usage: // modrange(x, y, m, [step]) // Description: // Returns a normalized list of values from `x` to `y`, by `step`, modulo `m`. Wraps if `x` > `y`. // Arguments: // x = The start value to constrain. // y = The end value to constrain. // m = Modulo value. // step = Step by this amount. // Examples: // echo(modrange(90,270,360, step=45)); // Outputs [90,135,180,225,270] // echo(modrange(270,90,360, step=45)); // Outputs [270,315,0,45,90] // echo(modrange(90,270,360, step=-45)); // Outputs [90,45,0,315,270] // echo(modrange(270,90,360, step=-45)); // Outputs [270,225,180,135,90] function modrange(x, y, m, step=1) = let( a = posmod(x, m), b = posmod(y, m), c = step>0? (a>b? b+m : b) : (a0?($fn>3?$fn:3):(ceil(max(min(360.0/$fa,abs(r)*2*PI/$fs),5))); // Function: lerp() // Description: Interpolate between two values or vectors. // Arguments: // a = First value. // b = Second value. // u = The proportion from `a` to `b` to calculate. Valid range is 0.0 to 1.0, inclusive. function lerp(a,b,u) = (1-u)*a + u*b; // Function: hypot() // Description: Calculate hypotenuse length of a 2D or 3D triangle. // Arguments: // x = Length on the X axis. // y = Length on the Y axis. // z = Length on the Z axis. function hypot(x,y,z=0) = norm([x,y,z]); // Function: hypot3() // Status: DEPRECATED, use `norm([x,y,z])` instead. // Description: Calculate hypotenuse length of 3D triangle. // Arguments: // x = Length on the X axis. // y = Length on the Y axis. // z = Length on the Z axis. function hypot3(x,y,z) = norm([x,y,z]); // Function: distance() // Status: DEPRECATED, use `norm(p2-p1)` instead. It's shorter. // Description: Returns the distance between a pair of 2D or 3D points. function distance(p1, p2) = norm(point3d(p2)-point3d(p1)); // Function: sinh() // Description: Takes a radians value `x`, and returns the hyperbolic sine of it. function sinh(x) = (exp(x)-exp(-x))/2; // Function: cosh() // Description: Takes a radians value `x`, and returns the hyperbolic cosine of it. function cosh(x) = (exp(x)+exp(-x))/2; // Function: tanh() // Description: Takes a radians value `x`, and returns the hyperbolic tangent of it. function tanh(x) = sinh(x)/cosh(x); // Function: asinh() // Description: Takes a value `x`, and returns the inverse hyperbolic sine of it in radians. function asinh(x) = ln(x+sqrt(x*x+1)); // Function: acosh() // Description: Takes a value `x`, and returns the inverse hyperbolic cosine of it in radians. function acosh(x) = ln(x+sqrt(x*x-1)); // Function: atanh() // Description: Takes a value `x`, and returns the inverse hyperbolic tangent of it in radians. function atanh(x) = ln((1+x)/(1-x))/2; // Function: sum() // Description: // Returns the sum of all entries in the given array. // If passed an array of vectors, returns a vector of sums of each part. // Arguments: // v = The vector to get the sum of. // Example: // sum([1,2,3]); // returns 6. // sum([[1,2,3], [3,4,5], [5,6,7]]); // returns [9, 12, 15] function sum(v, i=0, tot=undef) = i>=len(v)? tot : sum(v, i+1, ((tot==undef)? v[i] : tot+v[i])); // Function: sum_of_squares() // Description: // Returns the sum of the square of each element of a vector. // Arguments: // v = The vector to get the sum of. // Example: // sum_of_squares([1,2,3]); // returns 14. function sum_of_squares(v, i=0, tot=0) = sum(vmul(v,v)); // Function: sum_of_sines() // Usage: // sum_of_sines(a,sines) // Description: // Gives the sum of a series of sines, at a given angle. // Arguments: // a = Angle to get the value for. // sines = List of [amplitude, frequency, offset] items, where the frequency is the number of times the cycle repeats around the circle. function sum_of_sines(a, sines) = sum([ for (s = sines) let( ss=point3d(s), v=ss.x*sin(a*ss.y+ss.z) ) v ]); // Function: mean() // Description: // Returns the mean of all entries in the given array. // If passed an array of vectors, returns a vector of mean of each part. // Arguments: // v = The list of values to get the mean of. // Example: // mean([2,3,4]); // returns 4.5. // mean([[1,2,3], [3,4,5], [5,6,7]]); // returns [4.5, 6, 7.5] function mean(v) = sum(v)/len(v); // Section: List/Array Operations // Function: cdr() // Status: DEPRECATED, use `slice(list,1,-1)` instead. // Description: Returns all but the first item of a given array. // Arguments: // list = The list to get the tail of. function cdr(list) = len(list)<=1? [] : [for (i=[1:len(list)-1]) list[i]]; // Function: any() // Description: Returns true if any item in list `l` evaluates as true. // Arguments: // l = The list to test for true items. // Example: // any([0,false,undef]); // Returns false. // any([1,false,undef]); // Returns true. // any([1,5,true]); // Returns true. function any(l) = sum([for (x=l) x?1:0]) > 0; // Function: all() // Description: Returns true if all items in list `l` evaluate as true. // Arguments: // l = The list to test for true items. // Example: // all([0,false,undef]); // Returns false. // all([1,false,undef]); // Returns false. // all([1,5,true]); // Returns true. function all(l) = sum([for (x=l) x?1:0]) == len(l); // Function: in_list() // Description: Returns true if value `x` is in list `l`. // Arguments: // x = The value to search for. // l = The list to search. // idx = If given, searches the given subindexes for matches for `x`. // Example: // in_list("bar", ["foo", "bar", "baz"]); // Returns true. // in_list("bee", ["foo", "bar", "baz"]); // Returns false. // in_list("bar", [[2,"foo"], [4,"bar"], [3,"baz"]], idx=1); // Returns true. function in_list(x,l,idx=undef) = search([x], l, num_returns_per_match=1, index_col_num=idx) != [[]]; // Function: slice() // Description: // Returns a slice of a list. The first item is index 0. // Negative indexes are counted back from the end. The last item is -1. // Arguments: // arr = The array/list to get the slice of. // st = The index of the first item to return. // end = The index after the last item to return, unless negative, in which case the last item to return. // Example: // slice([3,4,5,6,7,8,9], 3, 5); // Returns [6,7] // slice([3,4,5,6,7,8,9], 2, -1); // Returns [5,6,7,8,9] // slice([3,4,5,6,7,8,9], 1, 1); // Returns [] // slice([3,4,5,6,7,8,9], 6, -1); // Returns [9] // slice([3,4,5,6,7,8,9], 2, -2); // Returns [5,6,7,8] function slice(arr,st,end) = let( s=st<0?(len(arr)+st):st, e=end<0?(len(arr)+end+1):end ) (s==e)? [] : [for (i=[s:e-1]) if (e>s) arr[i]]; // Function: wrap_range() // Status: DEPRECATED, use `select()` instead. // Description: // Returns a portion of a list, wrapping around past the beginning, if endlength)? array_trim(v,length) : array_pad(v,length,fill); // Function: array_zip() // Usage: // array_zip(v1, v2, v3, [fit], [fill]); // array_zip(vecs, [fit], [fill]); // Description: // Zips together corresponding items from two or more lists. // Returns a list of lists, where each sublist contains corresponding // items from each of the imput lists. `[[A1, B1, C1], [A2, B2, C2], ...]` // Arguments: // vecs = A list of two or more lists to zipper together. // fit = If `fit=="short"`, the zips together up to the length of the shortest list in vecs. If `fit=="long"`, then pads all lists to the length of the longest, using the value in `fill`. If `fit==false`, then requires all lists to be the same length. Default: false. // fill = The default value to fill in with if one or more lists if short. Default: undef // Example: // v1 = [1,2,3,4]; // v2 = [5,6,7]; // v3 = [8,9,10,11]; // array_zip(v1,v3); // returns [[1,8], [2,9], [3,10], [4,11]] // array_zip([v1,v3]); // returns [[1,8], [2,9], [3,10], [4,11]] // array_zip([v1,v2], fit="short"); // returns [[1,5], [2,6], [3,7]] // array_zip([v1,v2], fit="long"); // returns [[1,5], [2,6], [3,7], [4,undef]] // array_zip([v1,v2], fit="long, fill=0); // returns [[1,5], [2,6], [3,7], [4,0]] // array_zip([v1,v2,v3], fit="long"); // returns [[1,5,8], [2,6,9], [3,7,10], [4,undef,11]] // Example: // v1 = [[1,2,3], [4,5,6], [7,8,9]]; // v2 = [[20,19,18], [17,16,15], [14,13,12]]; // array_zip(v1,v2); // Returns [[1,2,3,20,19,18], [4,5,6,17,16,15], [7,8,9,14,13,12]] function array_zip(vecs, v2, v3, fit=false, fill=undef) = (v3!=undef)? array_zip([vecs,v2,v3], fit=fit, fill=fill) : (v2!=undef)? array_zip([vecs,v2], fit=fit, fill=fill) : let( dummy1 = assert_in_list("fit", fit, [false, "short", "long"]), minlen = array_shortest(vecs), maxlen = array_longest(vecs), dummy2 = (fit==false)? assertion(minlen==maxlen, "Input vectors must have the same length") : 0 ) (fit == "long")? [for(i=[0:maxlen-1]) [for(v=vecs) for (x=(i=len(matrices))? m : let (newmat = is_def(m)? matrices[i] * m : matrices[i]) matrix3_mult(matrices, m=newmat, i=i+1); // Function: matrix4_mult() // Usage: // matrix4_mult(matrices) // Description: // Returns a 4x4 transformation matrix which results from applying each matrix in `matrices` in order. // Arguments: // matrices = A list of 4x4 matrices. // m = Optional starting matrix to apply everything to. function matrix4_mult(matrices, m=ident(4), i=0) = (i>=len(matrices))? m : let (newmat = is_def(m)? matrices[i] * m : matrices[i]) matrix4_mult(matrices, m=newmat, i=i+1); // Function: matrix3_apply() // Usage: // matrix3_apply(pts, matrices) // Description: // Given a list of transformation matrices, applies them in order to the points in the point list. // Arguments: // pts = A list of 2D points to transform. // matrices = A list of 3x3 matrices to apply, in order. // Example: // npts = matrix3_apply( // pts = [for (x=[0:3]) [5*x,0]], // matrices =[ // matrix3_scale([3,1]), // matrix3_rot(90), // matrix3_translate([5,5]) // ] // ); // Returns [[5,5], [5,20], [5,35], [5,50]] function matrix3_apply(pts, matrices) = let(m = matrix3_mult(matrices)) [for (p = pts) point2d(m * concat(point2d(p),[1]))]; // Function: matrix4_apply() // Usage: // matrix4_apply(pts, matrices) // Description: // Given a list of transformation matrices, applies them in order to the points in the point list. // Arguments: // pts = A list of 3D points to transform. // matrices = A list of 4x4 matrices to apply, in order. // Example: // npts = matrix4_apply( // pts = [for (x=[0:3]) [5*x,0,0]], // matrices =[ // matrix4_scale([2,1,1]), // matrix4_zrot(90), // matrix4_translate([5,5,10]) // ] // ); // Returns [[5,5,10], [5,15,10], [5,25,10], [5,35,10]] function matrix4_apply(pts, matrices) = let(m = matrix4_mult(matrices)) [for (p = pts) point3d(m * concat(point3d(p),[1]))]; // Section: Geometry // Function: point_on_segment() // Usage: // point_on_segment(point, edge); // Description: // Determine if the point is on the line segment between two points. // Returns true if yes, and false if not. // Arguments: // point = The point to check colinearity of. // edge = Array of two points forming the line segment to test against. function point_on_segment(point, edge) = point==edge[0] || point==edge[1] || // The point is an endpoint sign(edge[0].x-point.x)==sign(point.x-edge[1].x) // point is in between the && sign(edge[0].y-point.y)==sign(point.y-edge[1].y) // edge endpoints && point_left_of_segment(point, edge)==0; // and on the line defined by edge // Function: point_left_of_segment() // Usage: // point_left_of_segment(point, edge); // Description: // Return >0 if point is left of the line defined by edge. // Return =0 if point is on the line. // Return <0 if point is right of the line. // Arguments: // point = The point to check position of. // edge = Array of two points forming the line segment to test against. function point_left_of_segment(point, edge) = (edge[1].x-edge[0].x) * (point.y-edge[0].y) - (point.x-edge[0].x) * (edge[1].y-edge[0].y); // Internal non-exposed function. function _point_above_below_segment(point, edge) = edge[0].y <= point.y? ( (edge[1].y > point.y && point_left_of_segment(point, edge) > 0)? 1 : 0 ) : ( (edge[1].y <= point.y && point_left_of_segment(point, edge) < 0)? -1 : 0 ); // Function: point_in_polygon() // Usage: // point_in_polygon(point, path) // Description: // This function tests whether the given point is inside, outside or on the boundary of // the specified polygon using the Winding Number method. (http://geomalgorithms.com/a03-_inclusion.html) // The polygon is given as a list of points, not including the repeated end point. // Returns -1 if the point is outside the polyon. // Returns 0 if the point is on the boundary. // Returns 1 if the point lies in the interior. // The polygon does not need to be simple: it can have self-intersections. // But the polygon cannot have holes (it must be simply connected). // Rounding error may give mixed results for points on or near the boundary. // Arguments: // point = The point to check position of. // path = The list of 2D path points forming the perimeter of the polygon. function point_in_polygon(point, path) = // Does the point lie on any edges? If so return 0. sum([for(i=[0:len(path)-1]) point_on_segment(point, select(path, i, i+1))?1:0])>0 ? 0 : // Otherwise compute winding number and return 1 for interior, -1 for exterior sum([for(i=[0:len(path)-1]) _point_above_below_segment(point, select(path, i, i+1))]) != 0 ? 1 : -1; // Function: pointlist_bounds() // Usage: // pointlist_bounds(pts); // Description: // Finds the bounds containing all the points in pts. // Returns [[minx, miny, minz], [maxx, maxy, maxz]] // Arguments: // pts = List of points. function pointlist_bounds(pts) = [ [for (a=[0:2]) min([ for (x=pts) point3d(x)[a] ]) ], [for (a=[0:2]) max([ for (x=pts) point3d(x)[a] ]) ] ]; // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap