From 8df67333ca1be609b6e0e815579b4bdbccb84f4c Mon Sep 17 00:00:00 2001 From: Alex Matulich Date: Mon, 28 Apr 2025 13:25:58 -0700 Subject: [PATCH] Added v_round() to vectors.scad --- vectors.scad | 139 ++++++++++++++++++++++++++++----------------------- 1 file changed, 76 insertions(+), 63 deletions(-) diff --git a/vectors.scad b/vectors.scad index b5be49c7..9636e52e 100644 --- a/vectors.scad +++ b/vectors.scad @@ -69,8 +69,8 @@ function is_vector(v, length, zero, all_nonzero=false, eps=EPSILON) = // Example: // a = add_scalar([1,2,3],3); // Returns: [4,5,6] function add_scalar(v,s) = - assert(is_vector(v), "Input v must be a vector") - assert(is_finite(s), "Input s must be a finite scalar") + assert(is_vector(v), "\nInput v must be a vector.") + assert(is_finite(s), "\nInput s must be a finite scalar.") [for(entry=v) entry+s]; @@ -82,15 +82,15 @@ function add_scalar(v,s) = // v3 = v_mul(v1, v2); // Description: // Element-wise multiplication. Multiplies each element of `v1` by the corresponding element of `v2`. -// Both `v1` and `v2` must be the same length. Returns a vector of the products. Note that -// the items in `v1` and `v2` can be anything that OpenSCAD will multiply. +// Both `v1` and `v2` must be the same length. Returns a vector of the products. +// The items in `v1` and `v2` can be anything that OpenSCAD can multiply together. // Arguments: // v1 = The first vector. // v2 = The second vector. // Example: // v_mul([3,4,5], [8,7,6]); // Returns [24, 28, 30] function v_mul(v1, v2) = - assert( is_list(v1) && is_list(v2) && len(v1)==len(v2), "Incompatible input") + assert( is_list(v1) && is_list(v2) && len(v1)==len(v2), "\nIncompatible input.") [for (i = [0:1:len(v1)-1]) v1[i]*v2[i]]; @@ -109,7 +109,7 @@ function v_mul(v1, v2) = // Example: // v_div([24,28,30], [8,7,6]); // Returns [3, 4, 5] function v_div(v1, v2) = - assert( is_vector(v1) && is_vector(v2,len(v1)), "Incompatible vectors") + assert( is_vector(v1) && is_vector(v2,len(v1)), "\nIncompatible vectors.") [for (i = [0:1:len(v1)-1]) v1[i]/v2[i]]; @@ -125,36 +125,49 @@ function v_div(v1, v2) = // Example: // v_abs([-1,3,-9]); // Returns: [1,3,9] function v_abs(v) = - assert( is_vector(v), "Invalid vector" ) + assert( is_vector(v), "\nInvalid vector." ) [for (x=v) abs(x)]; -// Function: v_floor() -// Synopsis: Returns the values of the given vector, rounded down. -// Topics: Vectors, Math -// See Also: v_abs(), v_floor(), v_ceil() -// Usage: -// v2 = v_floor(v); -// Description: -// Returns the given vector after performing a `floor()` on all items. -function v_floor(v) = - assert( is_vector(v), "Invalid vector" ) - [for (x=v) floor(x)]; - - // Function: v_ceil() // Synopsis: Returns the values of the given vector, rounded up. // Topics: Vectors, Math -// See Also: v_abs(), v_floor(), v_ceil() +// See Also: v_abs(), v_floor() // Usage: // v2 = v_ceil(v); // Description: // Returns the given vector after performing a `ceil()` on all items. function v_ceil(v) = - assert( is_vector(v), "Invalid vector" ) + assert(is_vector(v), "\nInvalid vector." ) [for (x=v) ceil(x)]; +// Function: v_floor() +// Synopsis: Returns the values of the given vector, rounded down. +// Topics: Vectors, Math +// See Also: v_abs(), v_ceil() +// Usage: +// v2 = v_floor(v); +// Description: +// Returns the given vector after performing a `floor()` on all items. +function v_floor(v) = + assert(is_vector(v), "\nInvalid vector." ) + [for (x=v) floor(x)]; + + +// Function: v_round() +// Synopsis: Returns the values of the given vector, rounded to the nearest whole number. +// Topics: Vectors, Math +// See Also: v_abs(), v_floor(), v_ceil() +// Usage: +// v2 = v_round(v); +// Description: +// Returns the given vector after performing a `floor()` on all items. +function v_round(v) = + assert(is_vector(v), "\nInvalid vector." ) + [for (x=v) round(x)]; + + // Function: v_lookup() // Synopsis: Like `lookup()`, but it can interpolate between vector results. // Topics: Vectors, Math @@ -178,8 +191,8 @@ function v_lookup(x, v) = hi = vhi[1] ) assert(is_vector(lo) && is_vector(hi), - "Result values must all be numbers, or all be vectors.") - assert(len(lo) == len(hi), "Vector result values must be the same length") + "\nResult values must all be numbers, or all be vectors.") + assert(len(lo) == len(hi), "\nVector result values must be the same length.") vlo.x == vhi.x? vlo[1] : let( u = (x - vlo.x) / (vhi.x - vlo.x) ) lerp(lo,hi,u); @@ -208,8 +221,8 @@ function v_lookup(x, v) = // v5 = unit([0,0,0],[1,2,3]); // Returns: [1,2,3] // v6 = unit([0,0,0]); // Asserts an error. function unit(v, error=[[["ASSERT"]]]) = - assert(is_vector(v), "Invalid vector") - norm(v)=EPSILON,"Cannot normalize a zero vector") : error) : + assert(is_vector(v), "\nInvalid vector.") + norm(v)=EPSILON,"\nCannot normalize a zero vector.") : error) : v/norm(v); @@ -222,7 +235,7 @@ function unit(v, error=[[["ASSERT"]]]) = // Description: // Given a vector, returns the angle in degrees counter-clockwise from X+ on the XY plane. function v_theta(v) = - assert( is_vector(v,2) || is_vector(v,3) , "Invalid vector") + assert( is_vector(v,2) || is_vector(v,3) , "\nInvalid vector.") atan2(v.y,v.x); @@ -255,19 +268,19 @@ function v_theta(v) = function vector_angle(v1,v2,v3) = assert( ( is_undef(v3) && ( is_undef(v2) || same_shape(v1,v2) ) ) || is_consistent([v1,v2,v3]) , - "Bad arguments.") - assert( is_vector(v1) || is_consistent(v1), "Bad arguments.") + "\nBad arguments.") + assert( is_vector(v1) || is_consistent(v1), "\nBad arguments.") let( vecs = ! is_undef(v3) ? [v1-v2,v3-v2] : ! is_undef(v2) ? [v1,v2] : len(v1) == 3 ? [v1[0]-v1[1], v1[2]-v1[1]] : v1 ) - assert(is_vector(vecs[0],2) || is_vector(vecs[0],3), "Bad arguments.") + assert(is_vector(vecs[0],2) || is_vector(vecs[0],3), "\nBad arguments.") let( norm0 = norm(vecs[0]), norm1 = norm(vecs[1]) ) - assert(norm0>0 && norm1>0, "Zero length vector.") + assert(norm0>0 && norm1>0, "\nZero length vector.") // NOTE: constrain() corrects crazy FP rounding errors that exceed acos()'s domain. acos(constrain((vecs[0]*vecs[1])/(norm0*norm1), -1, 1)); @@ -299,16 +312,16 @@ function vector_angle(v1,v2,v3) = // axis6 = vector_axis([[10,0,10], [0,0,0], [-10,10,0]]); // Returns: [-0.57735, -0.57735, 0.57735] function vector_axis(v1,v2=undef,v3=undef) = is_vector(v3) - ? assert(is_consistent([v3,v2,v1]), "Bad arguments.") + ? assert(is_consistent([v3,v2,v1]), "\nBad arguments.") vector_axis(v1-v2, v3-v2) - : assert( is_undef(v3), "Bad arguments.") + : assert( is_undef(v3), "\nBad arguments.") is_undef(v2) - ? assert( is_list(v1), "Bad arguments.") + ? assert( is_list(v1), "\nBad arguments.") len(v1) == 2 ? vector_axis(v1[0],v1[1]) : vector_axis(v1[0],v1[1],v1[2]) : assert( is_vector(v1,zero=false) && is_vector(v2,zero=false) && is_consistent([v1,v2]) - , "Bad arguments.") + , "\nBad arguments.") let( eps = 1e-6, w1 = point3d(v1/norm(v1)), @@ -331,9 +344,9 @@ function vector_axis(v1,v2=undef,v3=undef) = function vector_bisect(v1,v2) = assert(is_vector(v1)) assert(is_vector(v2)) - assert(!approx(norm(v1),0), "Zero length vector.") - assert(!approx(norm(v2),0), "Zero length vector.") - assert(len(v1)==len(v2), "Vectors are of different sizes.") + assert(!approx(norm(v1),0), "\nZero length vector.") + assert(!approx(norm(v2),0), "\nZero length vector.") + assert(len(v1)==len(v2), "\nVectors are of different sizes.") let( v1 = unit(v1), v2 = unit(v2) ) approx(v1,-v2)? undef : let( @@ -360,7 +373,7 @@ function vector_bisect(v1,v2) = // stroke([[0,0],w],endcap2="arrow2",color="red"); // stroke([[0,0],vector_perp(v,w)], endcap2="arrow2", color="blue"); function vector_perp(v,w) = - assert(is_vector(v) && is_vector(w) && len(v)==len(w), "Invalid or mismatched inputs") + assert(is_vector(v) && is_vector(w) && len(v)==len(w), "\nInvalid or mismatched inputs") w - w*v*v/(v*v); @@ -380,7 +393,7 @@ function vector_perp(v,w) = // Arguments: // pts = List of points. function pointlist_bounds(pts) = - assert(is_path(pts,dim=undef,fast=true) , "Invalid pointlist." ) + assert(is_path(pts,dim=undef,fast=true) , "\nInvalid pointlist." ) let( select = ident(len(pts[0])), spread = [ @@ -404,8 +417,8 @@ 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), "Invalid point." ) - assert(is_path(points,dim=len(pt)), "Invalid pointlist or incompatible dimensions." ) + 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)]); @@ -421,8 +434,8 @@ function closest_point(pt, points) = // pt = The point to find the farthest point from. // points = The list of points to search. function furthest_point(pt, points) = - assert( is_vector(pt), "Invalid point." ) - assert(is_path(points,dim=len(pt)), "Invalid pointlist or incompatible dimensions." ) + assert( is_vector(pt), "\nInvalid point." ) + assert(is_path(points,dim=len(pt)), "\nInvalid pointlist or incompatible dimensions." ) max_index([for (p=points) norm(p-pt)]); @@ -436,7 +449,7 @@ function furthest_point(pt, points) = // Given a list of query points `query` and a `target` to search, // finds the points in `target` that match each query point. A match holds when the // distance between a point in `target` and a query point is less than or equal to `r`. -// The returned list will have a list for each query point containing, in arbitrary +// The returned list contains a list for each query point containing, in arbitrary // order, the indices of all points that match that query point. // The `target` may be a simple list of points or a search tree. // When `target` is a large list of points, a search tree is constructed to @@ -444,8 +457,8 @@ function furthest_point(pt, points) = // For small point lists, a direct search is done dispensing a tree construction. // Alternatively, `target` may be a search tree built with `vector_search_tree()`. // In that case, that tree is parsed looking for matches. -// An empty list of query points will return a empty output list. -// An empty list of target points will return a output list with an empty list for each query point. +// An empty list of query points returns a empty output list. +// An empty list of target points returns a output list with an empty list for each query point. // Arguments: // query = list of points to find matches for. // r = the search radius. @@ -485,7 +498,7 @@ function vector_search(query, r, target) = query==[] ? [] : is_list(query) && target==[] ? is_vector(query) ? [] : [for(q=query) [] ] : assert( is_finite(r) && r>=0, - "The query radius should be a positive number." ) + "\nThe query radius should be a positive number." ) let( tgpts = is_matrix(target), // target is a point list tgtree = is_list(target) // target is a tree @@ -495,13 +508,13 @@ function vector_search(query, r, target) = && (len(target[1])==4 || (len(target[1])==1 && is_list(target[1][0])) ) ) assert( tgpts || tgtree, - "The target should be a list of points or a search tree compatible with the query." ) + "\nThe target should be a list of points or a search tree compatible with the query." ) let( dim = tgpts ? len(target[0]) : len(target[0][0]), simple = is_vector(query, dim) ) assert( simple || is_matrix(query,undef,dim), - "The query points should be a list of points compatible with the target point list.") + "\nThe query points should be a list of points compatible with the target point list.") tgpts ? len(target)<=400 ? simple ? [for(i=idx(target)) if(norm(target[i]-query)<=r) i ] : @@ -518,9 +531,9 @@ function _bt_search(query, r, points, tree) = assert( is_list(tree) && ( ( len(tree)==1 && is_list(tree[0]) ) || ( len(tree)==4 && is_num(tree[0]) && is_num(tree[1]) ) ), - "The tree is invalid.") + "\nThe tree is invalid.") len(tree)==1 - ? assert( tree[0]==[] || is_vector(tree[0]), "The tree is invalid." ) + ? assert( tree[0]==[] || is_vector(tree[0]), "\nThe tree is invalid." ) [for(i=tree[0]) if(norm(points[i]-query)<=r) i ] : norm(query-points[tree[0]]) > r+tree[1] ? [] : concat( @@ -541,14 +554,14 @@ function _bt_search(query, r, points, tree) = // search process. The tree construction stops branching when // a tree node represents a number of points less or equal to `leafsize`. // Search trees are ball trees. Constructing the -// tree should be O(n log n) and searches should be O(log n), though real life -// performance depends on how the data is distributed, and it will deteriorate -// for high data dimensions. This data structure is useful when you will be +// tree should be O(n log n) and searches should be O(log n), although real life +// performance depends on how the data is distributed, and it deteriorates +// for high data dimensions. This data structure is useful when you are // performing many searches of the same data, so that the cost of constructing // the tree is justified. (See https://en.wikipedia.org/wiki/Ball_tree) // For a small lists of points, the search with a tree may be more expensive // than direct comparisons. The argument `treemin` sets the minimum length of -// point set for which a tree search will be done by `vector_search`. +// the point set for which a tree search will be done by `vector_search`. // For an empty list of points it returns an empty list. // Arguments: // points = list of points to store in the search tree. @@ -568,9 +581,9 @@ function _bt_search(query, r, points, tree) = // } function vector_search_tree(points, leafsize=25, treemin=400) = points==[] ? [] : - assert( is_matrix(points), "The input list entries should be points." ) + assert( is_matrix(points), "\nThe input list entries should be points." ) assert( is_int(leafsize) && leafsize>=1, - "The tree leaf size should be an integer greater than zero.") + "\nThe tree leaf size should be an integer greater than zero.") len(points)0) - assert(is_vector(query), "Query must be a vector.") + assert(is_vector(query), "\nQuery must be a vector.") let( tgpts = is_matrix(target,undef,len(query)), // target is a point list tgtree = is_list(target) // target is a tree @@ -630,9 +643,9 @@ function vector_nearest(query, k, target) = && (len(target[1])==4 || (len(target[1])==1 && is_list(target[1][0])) ) ) assert( tgpts || tgtree, - "The target should be a list of points or a search tree compatible with the query." ) + "\nThe target should be a list of points or a search tree compatible with the query." ) assert((tgpts && (k<=len(target))) || (tgtree && (k<=len(target[0]))), - "More results are requested than the number of points.") + "\nMore results are requested than the number of points.") tgpts ? let( tree = _bt_tree(target, count(len(target))) ) column(_bt_nearest( query, k, target, tree),0) @@ -644,7 +657,7 @@ function _bt_nearest(p, k, points, tree, answers=[]) = assert( is_list(tree) && ( ( len(tree)==1 && is_list(tree[0]) ) || ( len(tree)==4 && is_num(tree[0]) && is_num(tree[1]) ) ), - "The tree is invalid.") + "\nThe tree is invalid.") len(tree)==1 ? _insert_many(answers, k, [for(entry=tree[0]) [entry, norm(points[entry]-p)]]) : let( d = norm(p-points[tree[0]]) ) @@ -669,7 +682,7 @@ function _insert_sorted(list, k, new) = function _insert_many(list, k, newlist,i=0) = i==len(newlist) ? list - : assert(is_vector(newlist[i],2), "The tree is invalid.") + : assert(is_vector(newlist[i],2), "\nThe tree is invalid.") _insert_many(_insert_sorted(list,k,newlist[i]),k,newlist,i+1);