diff --git a/affine.scad b/affine.scad index f5ecdd9..422ee1f 100644 --- a/affine.scad +++ b/affine.scad @@ -259,6 +259,49 @@ function affine3d_rot_from_to(from, to) = let( ]; +// Function: affine_frame_map() +// Usage: map = affine_frame_map(x=v1,y=v2); +// map = affine_frame_map(x=v1,z=v2); +// map = affine_frame_map(y=v1,y=v2); +// map = affine_frame_map(v1,v2,v3); +// Description: +// Returns a transformation that maps one coordinate frame to another. You must specify two or three of `x`, `y`, and `z`. The specified +// axes are mapped to the vectors you supplied. If you give two inputs, the third vector is mapped to the appropriate normal to maintain a right hand coordinate system. +// If the vectors you give are orthogonal the result will be a rotation. The `reverse` parameter will supply the inverse map, which enables you +// to map two arbitrary coordinate systems two each other by using the canonical coordinate system as an intermediary. +// Arguments: +// x = Destination vector for x axis +// y = Destination vector for y axis +// z = Destination vector for z axis +// reverse = reverse direction of the map. Default: false +// Examples: +// T = affine_frame_map(x=[1,1,0], y=[-1,1]); // This map is just a rotation around the z axis +// T = affine_frame_map(x=[1,0,0], y=[1,1]); // This map is not a rotation because x and y aren't orthogonal +// // The next map sends [1,1,0] to [0,1,1] and [-1,1,0] to [0,-1,1] +// T = affine_frame_map(x=[0,1,1], y=[0,-1,1]) * affine_frame_map(x=[1,1,0], y=[-1,1,0],reverse=true); +function affine_frame_map(x,y,z, reverse=false) = + assert(num_defined([x,y,z])>=2, "Must define at least two inputs") + let( + xvalid = is_undef(x) || (is_vector(x) && len(x)==3), + yvalid = is_undef(y) || (is_vector(y) && len(y)==3), + zvalid = is_undef(z) || (is_vector(z) && len(z)==3) + ) + assert(xvalid,"Input x must be a length 3 vector") + assert(yvalid,"Input y must be a length 3 vector") + assert(zvalid,"Input z must be a length 3 vector") + let( + x = is_def(x) ? normalize(x) : undef, + y = is_def(y) ? normalize(y) : undef, + z = is_def(z) ? normalize(z) : undef, + map = is_undef(x) ? [cross(y,z), y, z] : + is_undef(y) ? [x, cross(z,x), z] : + is_undef(z) ? [x, y, cross(x,y)] : + [x, y, z] + ) + reverse ? affine2d_to_3d(map) : affine2d_to_3d(transpose(map)); + + + // Function: affine3d_mirror() // Usage: // mat = affine3d_mirror(v); @@ -441,4 +484,7 @@ function is_2d_transform(t) = // z-parameters are zero, except we allow t[2][ (t[2][2]==1 || !(t[0][0]==1 && t[0][1]==0 && t[1][0]==0 && t[1][1]==1)); // But rule out zscale() + + + // vim: noexpandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap diff --git a/geometry.scad b/geometry.scad index 5670134..3d52ffe 100644 --- a/geometry.scad +++ b/geometry.scad @@ -1030,13 +1030,13 @@ function polygon_shift_to_closest_point(path, pt) = // Usage: // newpoly = reindex_polygon(reference, poly); // Description: -// Rotates and possibly reverses the point order of a polygon path to optimize its pairwise point -// association with a reference polygon. The two polygons must have the same number of vertices. +// Rotates and possibly reverses the point order of a 2d or 3d polygon path to optimize its pairwise point +// association with a reference polygon. The two polygons must have the same number of vertices and be the same dimension. // The optimization is done by computing the distance, norm(reference[i]-poly[i]), between // corresponding pairs of vertices of the two polygons and choosing the polygon point order that // makes the total sum over all pairs as small as possible. Returns the reindexed polygon. Note // that the geometry of the polygon is not changed by this operation, just the labeling of its -// vertices. If the input polygon is oriented opposite the reference then its point order is +// vertices. If the input polygon is 2d and is oriented opposite the reference then its point order is // flipped. // Arguments: // reference = reference polygon path diff --git a/joiners.scad b/joiners.scad index 30cef00..a6303ee 100644 --- a/joiners.scad +++ b/joiners.scad @@ -556,7 +556,7 @@ module dovetail(gender, length, l, width, w, height, h, angle, slope, taper, bac reverse(concat(smallend_points, xflip(p=reverse(smallend_points)))), reverse(concat(bigend_points, xflip(p=reverse(bigend_points)))) ], - convexity=4, slices=0 + slices=0, convexity=4 ); } children(); diff --git a/math.scad b/math.scad index ac99804..010c307 100644 --- a/math.scad +++ b/math.scad @@ -532,6 +532,43 @@ function mean(v) = sum(v)/len(v); // Section: Matrix math +// Function: linear_solve() +// Usage: linear_solve(A,b) +// Description: +// 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. +// If A is rank deficient or singular then linear_solve returns `undef`. +function linear_solve(A,b) = + let( + dim = array_dim(A), + m=dim[0], n=dim[1] + ) + assert(len(b)==m,str("Incompatible matrix and vector",dim,len(b))) + let ( + qr = m=2, "Must define at least two inputs") - let( - xvalid = is_undef(x) || (is_vector(x) && len(x)==3), - yvalid = is_undef(y) || (is_vector(y) && len(y)==3), - zvalid = is_undef(z) || (is_vector(z) && len(z)==3) - ) - assert(xvalid,"Input x must be a length 3 vector") - assert(yvalid,"Input y must be a length 3 vector") - assert(zvalid,"Input z must be a length 3 vector") - let( - x = is_def(x) ? normalize(x) : undef, - y = is_def(y) ? normalize(y) : undef, - z = is_def(z) ? normalize(z) : undef, - map = is_undef(x) ? [cross(y,z), y, z] : - is_undef(y) ? [x, cross(z,x), z] : - is_undef(z) ? [x, y, cross(x,y)] : - [x, y, z] - ) - reverse ? affine2d_to_3d(map) : affine2d_to_3d(transpose(map)); // Function&Module: path_sweep() // Usage: path_sweep(shape, path, [method], [normal], [closed], [twist], [twist_by_length], [symmetry], [last_normal], [tangent], [relaxed], [caps], [convexity], [transforms]) @@ -1091,7 +1051,7 @@ function affine_frame_map(x,y,z, reverse=false) = // path_sweep(ushape,knot_path,normal=normals, method="manual", closed=true); // Example: You can request the transformations and manipulate them before passing them on to sweep. Here we construct a tube that changes scale by first generating the transforms and then applying the scale factor and connecting the inside and outside. Note that the wall thickness varies because it is produced by scaling. // shape = star(n=5, r=10, ir=5); -// rpath = arc(25, points=[[-30,0,0], [-3,1,7], [0,3,6]]); +// rpath = arc(25, points=[[29,6,-4], [3,4,6], [1,1,7]]); // trans = path_sweep(shape, rpath, transforms=true); // outside = [for(i=[0:len(trans)-1]) trans[i]*scale(lerp(1,1.5,i/(len(trans)-1)))]; // inside = [for(i=[len(trans)-1:-1:0]) trans[i]*scale(lerp(1.1,1.4,i/(len(trans)-1)))];