////////////////////////////////////////////////////////////////////// // LibFile: affine.scad // Matrix math and affine transformation matrices. // To use, add the following lines to the beginning of your file: // ``` // use // ``` ////////////////////////////////////////////////////////////////////// // Section: Matrix Manipulation // Function: ident() // Description: Create an `n` by `n` identity matrix. // Arguments: // n = The size of the identity matrix square, `n` by `n`. function ident(n) = [for (i = [0:1:n-1]) [for (j = [0:1:n-1]) (i==j)?1:0]]; // Function: affine2d_to_3d() // Description: Takes a 3x3 affine2d matrix and returns its 4x4 affine3d equivalent. function affine2d_to_3d(m) = concat( [for (r = [0:2]) concat( [for (c = [0:2]) m[r][c]], [0] ) ], [[0, 0, 0, 1]] ); // Section: Affine2d 3x3 Transformation Matrices // Function: affine2d_identity() // Description: Create a 3x3 affine2d identity matrix. function affine2d_identity() = ident(3); // Function: affine2d_translate() // Description: // Returns the 3x3 affine2d matrix to perform a 2D translation. // Arguments: // v = 2D Offset to translate by. [X,Y] function affine2d_translate(v) = [ [1, 0, v.x], [0, 1, v.y], [0 ,0, 1] ]; // Function: affine2d_scale() // Description: // Returns the 3x3 affine2d matrix to perform a 2D scaling transformation. // Arguments: // v = 2D vector of scaling factors. [X,Y] function affine2d_scale(v) = [ [v.x, 0, 0], [ 0, v.y, 0], [ 0, 0, 1] ]; // Function: affine2d_zrot() // Description: // Returns the 3x3 affine2d matrix to perform a rotation of a 2D vector around the Z axis. // Arguments: // ang = Number of degrees to rotate. function affine2d_zrot(ang) = [ [cos(ang), -sin(ang), 0], [sin(ang), cos(ang), 0], [ 0, 0, 1] ]; // Function: affine2d_mirror() // Usage: // mat = affine2d_mirror(v); // Description: // Returns the 3x3 affine2d matrix to perform a reflection of a 2D vector across the line given by its normal vector. // Arguments: // v = The normal vector of the line to reflect across. function affine2d_mirror(v) = let(v=normalize(point2d(v)), a=v.x, b=v.y) [ [1-2*a*a, 0-2*a*b, 0], [0-2*a*b, 1-2*b*b, 0], [ 0, 0, 1] ]; // Function: affine2d_skew() // Usage: // affine2d_skew(xa, ya) // Description: // Returns the 3x3 affine2d matrix to skew a 2D vector along the XY plane. // Arguments: // xa = Skew angle, in degrees, in the direction of the X axis. // ya = Skew angle, in degrees, in the direction of the Y axis. function affine2d_skew(xa, ya) = [ [1, tan(xa), 0], [tan(ya), 1, 0], [0, 0, 1] ]; // Function: affine2d_chain() // Usage: // affine2d_chain(affines) // Description: // Returns a 3x3 affine2d transformation matrix which results from applying each matrix in `affines` in order. // Arguments: // affines = A list of 3x3 affine2d matrices. function affine2d_chain(affines, _m=undef, _i=0) = (_i>=len(affines))? (is_undef(_m)? ident(3) : _m) : affine2d_chain(affines, _m=(is_undef(_m)? affines[_i] : affines[_i] * _m), _i=_i+1); // Function: affine2d_apply() // Usage: // affine2d_apply(pts, affines) // Description: // Given a list of 3x3 affine2d transformation matrices, applies them in order to the points in the point list. // Arguments: // pts = A list of 2D points to transform. // affines = A list of 3x3 affine2d matrices to apply, in order. // Example: // npts = affine2d_apply( // pts = [for (x=[0:3]) [5*x,0]], // affines =[ // affine2d_scale([3,1]), // affine2d_rot(90), // affine2d_translate([5,5]) // ] // ); // Returns [[5,5], [5,20], [5,35], [5,50]] function affine2d_apply(pts, affines) = let(m = affine2d_chain(affines)) [for (p = pts) point2d(m * concat(point2d(p),[1]))]; // Section: Affine3d 4x4 Transformation Matrices // Function: affine3d_identity() // Description: Create a 4x4 affine3d identity matrix. function affine3d_identity() = ident(4); // Function: affine3d_translate() // Description: // Returns the 4x4 affine3d matrix to perform a 3D translation. // Arguments: // v = 3D offset to translate by. [X,Y,Z] function affine3d_translate(v) = [ [1, 0, 0, v.x], [0, 1, 0, v.y], [0, 0, 1, v.z], [0 ,0, 0, 1] ]; // Function: affine3d_scale() // Description: // Returns the 4x4 affine3d matrix to perform a 3D scaling transformation. // Arguments: // v = 3D vector of scaling factors. [X,Y,Z] function affine3d_scale(v) = [ [v.x, 0, 0, 0], [ 0, v.y, 0, 0], [ 0, 0, v.z, 0], [ 0, 0, 0, 1] ]; // Function: affine3d_xrot() // Description: // Returns the 4x4 affine3d matrix to perform a rotation of a 3D vector around the X axis. // Arguments: // ang = number of degrees to rotate. function affine3d_xrot(ang) = [ [1, 0, 0, 0], [0, cos(ang), -sin(ang), 0], [0, sin(ang), cos(ang), 0], [0, 0, 0, 1] ]; // Function: affine3d_yrot() // Description: // Returns the 4x4 affine3d matrix to perform a rotation of a 3D vector around the Y axis. // Arguments: // ang = Number of degrees to rotate. function affine3d_yrot(ang) = [ [ cos(ang), 0, sin(ang), 0], [ 0, 1, 0, 0], [-sin(ang), 0, cos(ang), 0], [ 0, 0, 0, 1] ]; // Function: affine3d_zrot() // Usage: // affine3d_zrot(ang) // Description: // Returns the 4x4 affine3d matrix to perform a rotation of a 3D vector around the Z axis. // Arguments: // ang = number of degrees to rotate. function affine3d_zrot(ang) = [ [cos(ang), -sin(ang), 0, 0], [sin(ang), cos(ang), 0, 0], [ 0, 0, 1, 0], [ 0, 0, 0, 1] ]; // Function: affine3d_rot_by_axis() // Usage: // affine3d_rot_by_axis(u, ang); // Description: // Returns the 4x4 affine3d matrix to perform a rotation of a 3D vector around an axis. // Arguments: // u = 3D axis vector to rotate around. // ang = number of degrees to rotate. function affine3d_rot_by_axis(u, ang) = let( u = normalize(u), c = cos(ang), c2 = 1-c, s = sin(ang) ) [ [u[0]*u[0]*c2+c , u[0]*u[1]*c2-u[2]*s, u[0]*u[2]*c2+u[1]*s, 0], [u[1]*u[0]*c2+u[2]*s, u[1]*u[1]*c2+c , u[1]*u[2]*c2-u[0]*s, 0], [u[2]*u[0]*c2-u[1]*s, u[2]*u[1]*c2+u[0]*s, u[2]*u[2]*c2+c , 0], [ 0, 0, 0, 1] ]; // Function: affine3d_rot_from_to() // Usage: // affine3d_rot_from_to(from, to); // Description: // Returns the 4x4 affine3d matrix to perform a rotation of a 3D vector from one vector direction to another. // Arguments: // from = 3D axis vector to rotate from. // to = 3D axis vector to rotate to. function affine3d_rot_from_to(from, to) = let( u = vector_axis(from,to), ang = vector_angle(from,to), c = cos(ang), c2 = 1-c, s = sin(ang) ) [ [u[0]*u[0]*c2+c , u[0]*u[1]*c2-u[2]*s, u[0]*u[2]*c2+u[1]*s, 0], [u[1]*u[0]*c2+u[2]*s, u[1]*u[1]*c2+c , u[1]*u[2]*c2-u[0]*s, 0], [u[2]*u[0]*c2-u[1]*s, u[2]*u[1]*c2+u[0]*s, u[2]*u[2]*c2+c , 0], [ 0, 0, 0, 1] ]; // Function: affine3d_mirror() // Usage: // mat = affine3d_mirror(v); // Description: // Returns the 4x4 affine3d matrix to perform a reflection of a 3D vector across the plane given by its normal vector. // Arguments: // v = The normal vector of the plane to reflect across. function affine3d_mirror(v) = let(v=normalize(point3d(v)), a=v.x, b=v.y, c=v.z) [ [1-2*a*a, -2*a*b, -2*a*c, 0], [ -2*b*a, 1-2*b*b, -2*b*c, 0], [ -2*c*a, -2*c*b, 1-2*c*c, 0], [ 0, 0, 0, 1] ]; // Function: affine3d_skew() // Usage: // mat = affine3d_skew([sxy], [sxz], [syx], [xyz], [szx], [szy]); // Description: // Returns the 4x4 affine3d matrix to perform a skew transformation. // Arguments: // sxy = Skew factor multiplier for skewing along the X axis as you get farther from the Y axis. Default: 0 // sxz = Skew factor multiplier for skewing along the X axis as you get farther from the Z axis. Default: 0 // syx = Skew factor multiplier for skewing along the Y axis as you get farther from the X axis. Default: 0 // syz = Skew factor multiplier for skewing along the Y axis as you get farther from the Z axis. Default: 0 // szx = Skew factor multiplier for skewing along the Z axis as you get farther from the X axis. Default: 0 // szy = Skew factor multiplier for skewing along the Z axis as you get farther from the Y axis. Default: 0 function affine3d_skew(sxy=0, sxz=0, syx=0, syz=0, szx=0, szy=0) = [ [ 1, sxy, sxz, 0], [syx, 1, syz, 0], [szx, szy, 1, 0], [ 0, 0, 0, 1] ]; // Function: affine3d_skew_xy() // Usage: // affine3d_skew_xy(xa, ya) // Description: // Returns the 4x4 affine3d matrix to perform a skew transformation along the XY plane. // Arguments: // xa = Skew angle, in degrees, in the direction of the X axis. // ya = Skew angle, in degrees, in the direction of the Y axis. function affine3d_skew_xy(xa, ya) = [ [1, 0, tan(xa), 0], [0, 1, tan(ya), 0], [0, 0, 1, 0], [0, 0, 0, 1] ]; // Function: affine3d_skew_xz() // Usage: // affine3d_skew_xz(xa, za) // Description: // Returns the 4x4 affine3d matrix to perform a skew transformation along the XZ plane. // Arguments: // xa = Skew angle, in degrees, in the direction of the X axis. // za = Skew angle, in degrees, in the direction of the Z axis. function affine3d_skew_xz(xa, za) = [ [1, tan(xa), 0, 0], [0, 1, 0, 0], [0, tan(za), 1, 0], [0, 0, 0, 1] ]; // Function: affine3d_skew_yz() // Usage: // affine3d_skew_yz(ya, za) // Description: // Returns the 4x4 affine3d matrix to perform a skew transformation along the YZ plane. // Arguments: // ya = Skew angle, in degrees, in the direction of the Y axis. // za = Skew angle, in degrees, in the direction of the Z axis. function affine3d_skew_yz(ya, za) = [ [ 1, 0, 0, 0], [tan(ya), 1, 0, 0], [tan(za), 0, 1, 0], [ 0, 0, 0, 1] ]; // Function: affine3d_chain() // Usage: // affine3d_chain(affines) // Description: // Returns a 4x4 affine3d transformation matrix which results from applying each matrix in `affines` in order. // Arguments: // affines = A list of 4x4 affine3d matrices. function affine3d_chain(affines, _m=undef, _i=0) = (_i>=len(affines))? (is_undef(_m)? ident(4) : _m) : affine3d_chain(affines, _m=(is_undef(_m)? affines[_i] : affines[_i] * _m), _i=_i+1); // Function: affine3d_apply() // Usage: // affine3d_apply(pts, affines) // Description: // Given a list of affine3d transformation matrices, applies them in order to the points in the point list. // Arguments: // pts = A list of 3D points to transform. // affines = A list of 4x4 matrices to apply, in order. // Example: // npts = affine3d_apply( // pts = [for (x=[0:3]) [5*x,0,0]], // affines =[ // affine3d_scale([2,1,1]), // affine3d_zrot(90), // affine3d_translate([5,5,10]) // ] // ); // Returns [[5,5,10], [5,15,10], [5,25,10], [5,35,10]] function affine3d_apply(pts, affines) = let(m = affine3d_chain(affines)) [for (p = pts) point3d(m * concat(point3d(p),[1]))]; // Function: apply() // Usage: apply(transform, points) // Description: // Applies the specified transformation matrix to a point list (or single point). Both inputs can be 2d or 3d, and it is also allowed // to supply 3d transformations with 2d data as long as the the only action on the z coordinate is a simple scaling. // Examples: // transformed = apply(xrot(45), path3d(circle(r=3))); // Rotates 3d circle data around x axis // transformed = apply(rot(45), circle(r=3)); // Rotates 2d circle data by 45 deg // transformed = apply(rot(45)*right(4)*scale(3), circle(r=3)); // Scales, translates and rotates 2d circle data function apply(transform,points) = is_vector(points) ? apply(transform, [points])[0] : let( tdim = len(transform[0])-1, datadim = len(points[0]) ) tdim == 3 && datadim == 3 ? [for(p=points) point3d(transform*concat(p,[1]))] : tdim == 2 && datadim == 2 ? [for(p=points) point2d(transform*concat(p,[1]))] : tdim == 3 && datadim == 2 ? assert(is_2d_transform(transform),str("Transforms is 3d but points are 2d")) [for(p=points) point2d(transform*concat(p,[0,1]))] : assert(false,str("Unsupported combination: transform with dimension ",tdim,", data of dimension ",datadim)); // Function: apply_list() // Usage: apply_list(points, transform_list) // Description: // Transforms the specified point list (or single point) using a list of transformation matrices. Transformations on // the list are applied in the order they appear in the list (as in right multiplication of matrices). Both inputs can be // 2d or 3d, and it is also allowed to supply 3d transformations with 2d data as long as the the only action on the z coordinate // is a simple scaling. All transformations on `transform_list` must have the same dimension: you cannot mix 2d and 3d transformations // even when acting on 2d data. // Examples: // transformed = apply_list(path3d(circle(r=3)),[xrot(45)]); // Rotates 3d circle data around x axis // transformed = apply_list(circle(r=3), [scale(3), right(4), rot(45)]); // Scales, then translates, and then rotates 2d circle data function apply_list(points,transform_list) = is_vector(points) ? apply_list([points],transform_list)[0] : let( tdims = array_dim(transform_list), datadim = len(points[0]) ) assert(len(tdims)==3 || tdims[1]!=tdims[2], "Invalid transformation list") let( tdim = tdims[1]-1 ) tdim==2 && datadim == 2 ? apply(affine2d_chain(transform_list), points) : tdim==3 && datadim == 3 ? apply(affine3d_chain(transform_list), points) : tdim==3 && datadim == 2 ? let( badlist = [for(i=idx(transform_list)) if (!is_2d_transform(transform_list[i])) i] ) assert(badlist==[],str("Transforms with indices ",badlist," are 3d but points are 2d")) apply(affine3d_chain(transform_list), points) : assert(false,str("Unsupported combination: transform with dimension ",tdim,", data of dimension ",datadim)); // Function: is_2d_transform() // Usage: is_2d_transform(t) // Description: Checks if the input is a 3d transform that does not act on the z coordinate, except // possibly for a simple scaling of z. Note that an input which is only a zscale returns false. function is_2d_transform(t) = // z-parameters are zero, except we allow t[2][2]!=1 so scale() works t[2][0]==0 && t[2][1]==0 && t[2][3]==0 && t[0][2] == 0 && t[1][2]==0 && (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