Dropped affine2d_chain(), affine3d_chain(). Improved affine.scad docs.

This commit is contained in:
Garth Minette 2021-01-20 18:06:46 -08:00
parent 6e42a1189d
commit c2129972c3
3 changed files with 526 additions and 437 deletions

View file

@ -11,417 +11,112 @@
// Function: ident()
// Usage:
// mat = ident(n);
// Description: Create an `n` by `n` identity matrix.
// Description:
// Create an `n` by `n` square 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]];
// Example:
// mat = ident(3);
// // Returns:
// // [
// // [1, 0, 0],
// // [0, 1, 0],
// // [0, 0, 1]
// // ]
// Example:
// mat = ident(4);
// // Returns:
// // [
// // [1, 0, 0, 0],
// // [0, 1, 0, 0],
// // [0, 0, 1, 0],
// // [0, 0, 0, 1]
// // ]
function ident(n) = [
for (i = [0:1:n-1]) [
for (j = [0:1:n-1]) (i==j)? 1 : 0
]
];
// Function: is_affine()
// Usage:
// bool = is_affine(x,<dim>);
// Description:
// Tests if the given value is an affine matrix, possibly also checking it's dimenstion.
// Arguments:
// x = The value to test for being an affine matrix.
// dim = The number of dimensions the given affine is required to be for. Generally 2 for 2D or 3 for 3D. If given as a list of integers, allows any of the given dimensions. Default: `[2,3]`
// Examples:
// bool = is_affine(affine2d_scale([2,3])); // Returns true
// bool = is_affine(affine3d_scale([2,3,4])); // Returns true
// bool = is_affine(affine3d_scale([2,3,4]),2); // Returns false
// bool = is_affine(affine3d_scale([2,3]),2); // Returns true
// bool = is_affine(affine3d_scale([2,3,4]),3); // Returns true
// bool = is_affine(affine3d_scale([2,3]),3); // Returns false
function is_affine(x,dim=[2,3]) =
is_finite(dim)? is_affine(x,[dim]) :
let( ll = len(x) )
is_list(x) && in_list(ll-1,dim) &&
[for (r=x) if(!is_list(r) || len(r)!=ll) 1] == [];
// Function: is_2d_transform()
// Usage:
// x = 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.
// Arguments:
// t = The transformation matrix to check.
// Examples:
// b = is_2d_transform(zrot(45)); // Returns: true
// b = is_2d_transform(yrot(45)); // Returns: false
// b = is_2d_transform(xrot(45)); // Returns: false
// b = is_2d_transform(move([10,20,0])); // Returns: true
// b = is_2d_transform(move([10,20,30])); // Returns: false
// b = is_2d_transform(scale([2,3,4])); // Returns: true
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()
// Function: affine2d_to_3d()
// Usage:
// mat = affine2d_to_3d(m);
// 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()
// Usage:
// mat = affine2d_identify();
// Description: Create a 3x3 affine2d identity matrix.
function affine2d_identity() = ident(3);
// Function: affine2d_translate()
// Usage:
// mat = affine2d_translate(v);
// 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]
// Takes a 3x3 affine2d matrix and returns its 4x4 affine3d equivalent.
// Example:
// mat = affine2d_to_3d(affine2d_translate([10,20]));
// // Returns:
// // [
// // [1, 0, 0, 10],
// // [0, 1, 0, 20],
// // [0, 0, 1, 0],
// // [0, 0, 0, 1],
// // ]
function affine2d_to_3d(m) = [
[ m[0][0], m[0][1], 0, m[0][2] ],
[ m[1][0], m[1][1], 0, m[1][2] ],
[ 0, 0, 1, 0 ],
[ m[2][0], m[2][1], 0, m[2][2] ]
];
// Function: affine2d_scale()
// Function: affine3d_to_2d()
// Usage:
// mat = affine2d_scale(v);
// mat = affine3d_to_2d(m);
// 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()
// Usage:
// mat = affine2d_zrot(ang);
// 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=unit(point2d(v)), a=v.x, b=v.y)
// Takes a 4x4 affine3d matrix and returns its 3x3 affine2d equivalent. 3D transforms that would alter the Z coordinate are disallowed.
function affine3d_to_2d(m) =
assert(is_2d_transform(m))
[
[1-2*a*a, 0-2*a*b, 0],
[0-2*a*b, 1-2*b*b, 0],
[ 0, 0, 1]
for (r=[0:3]) if (r!=2) [
for (c=[0:3]) if (c!=2) m[r][c]
]
];
// Function: affine2d_skew()
// Usage:
// mat = 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:
// mat = 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);
// Section: Affine3d 4x4 Transformation Matrices
// Function: affine3d_identity()
// Usage:
// mat = affine3d_identity();
// Description: Create a 4x4 affine3d identity matrix.
function affine3d_identity() = ident(4);
// Function: affine3d_translate()
// Usage:
// mat = affine3d_translate(v);
// 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()
// Usage:
// mat = affine3d_scale(v);
// 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()
// Usage:
// mat = affine3d_xrot(ang);
// 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()
// Usage:
// mat = affine3d_yrot(ang);
// 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:
// mat = 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:
// mat = 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) =
approx(ang,0)? affine3d_identity() :
let(
u = unit(u),
c = cos(ang),
c2 = 1-c,
s = sin(ang)
) [
[u.x*u.x*c2+c , u.x*u.y*c2-u.z*s, u.x*u.z*c2+u.y*s, 0],
[u.y*u.x*c2+u.z*s, u.y*u.y*c2+c , u.y*u.z*c2-u.x*s, 0],
[u.z*u.x*c2-u.y*s, u.z*u.y*c2+u.x*s, u.z*u.z*c2+c , 0],
[ 0, 0, 0, 1]
];
// Function: affine3d_rot_from_to()
// Usage:
// mat = 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(
from = unit(point3d(from)),
to = unit(point3d(to))
) approx(from,to)? affine3d_identity() :
let(
u = vector_axis(from,to),
ang = vector_angle(from,to),
c = cos(ang),
c2 = 1-c,
s = sin(ang)
) [
[u.x*u.x*c2+c , u.x*u.y*c2-u.z*s, u.x*u.z*c2+u.y*s, 0],
[u.y*u.x*c2+u.z*s, u.y*u.y*c2+c , u.y*u.z*c2-u.x*s, 0],
[u.z*u.x*c2-u.y*s, u.z*u.y*c2+u.x*s, u.z*u.z*c2+c , 0],
[ 0, 0, 0, 1]
];
// Function: affine3d_frame_map()
// Usage:
// map = affine3d_frame_map(v1, v2, v3);
// map = affine3d_frame_map(x=VECTOR1, y=VECTOR2, <reverse>);
// map = affine3d_frame_map(x=VECTOR1, z=VECTOR2, <reverse>);
// map = affine3d_frame_map(y=VECTOR1, y=VECTOR2, <reverse>);
// 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 and the
// `reverse` parameter will supply the inverse map, which enables you to map two arbitrary
// coordinate systems to each other by using the canonical coordinate system as an intermediary.
// You cannot use the `reverse` option with non-orthogonal inputs.
// Arguments:
// x = Destination 3D vector for x axis.
// y = Destination 3D vector for y axis.
// z = Destination 3D vector for z axis.
// reverse = reverse direction of the map for orthogonal inputs. Default: false
// Example:
// T = affine3d_frame_map(x=[1,1,0], y=[-1,1,0]); // This map is just a rotation around the z axis
// Example:
// T = affine3d_frame_map(x=[1,0,0], y=[1,1,0]); // This map is not a rotation because x and y aren't orthogonal
// Example:
// // The next map sends [1,1,0] to [0,1,1] and [-1,1,0] to [0,-1,1]
// T = affine3d_frame_map(x=[0,1,1], y=[0,-1,1]) * affine3d_frame_map(x=[1,1,0], y=[-1,1,0],reverse=true);
function affine3d_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_undef(x)? undef : unit(x,RIGHT),
y = is_undef(y)? undef : unit(y,BACK),
z = is_undef(z)? undef : unit(z,UP),
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? (
let(
ocheck = (
approx(map[0]*map[1],0) &&
approx(map[0]*map[2],0) &&
approx(map[1]*map[2],0)
)
)
assert(ocheck, "Inputs must be orthogonal when reverse==true")
affine2d_to_3d(map)
) : affine2d_to_3d(transpose(map));
// 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=unit(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>, <syz>, <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:
// mat = 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:
// mat = 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:
// mat = 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:
// mat = 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: apply()
// Usage:
// pts = apply(transform, points);
@ -470,18 +165,6 @@ function apply(transform,points) =
assert(false, str("Unsupported combination: transform with dimension ",tdim,", data of dimension ",datadim));
// Function: is_2d_transform()
// Usage:
// x = 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()
// Function: rot_decode()
// Usage:
// info = rot_decode(rotation); // Returns: [angle,axis,cp,translation]
@ -505,25 +188,436 @@ function rot_decode(M) =
let(R = submatrix(M,[0:2],[0:2]))
assert(approx(det3(R),1) && approx(norm_fro(R * transpose(R)-ident(3)),0),"Input matrix is not a rotation")
let(
translation = [for(row=[0:2]) M[row][3]], // translation vector
largest = max_index([R[0][0], R[1][1], R[2][2]]),
axis_matrix = R + transpose(R) - (matrix_trace(R)-1)*ident(3), // Each row is on the rotational axis
// Construct quaternion q = c * [x sin(theta/2), y sin(theta/2), z sin(theta/2), cos(theta/2)]
q_im = axis_matrix[largest],
q_re = R[(largest+2)%3][(largest+1)%3] - R[(largest+1)%3][(largest+2)%3],
c_sin = norm(q_im), // c * sin(theta/2) for some c
c_cos = abs(q_re) // c * cos(theta/2)
translation = [for(row=[0:2]) M[row][3]], // translation vector
largest = max_index([R[0][0], R[1][1], R[2][2]]),
axis_matrix = R + transpose(R) - (matrix_trace(R)-1)*ident(3), // Each row is on the rotational axis
// Construct quaternion q = c * [x sin(theta/2), y sin(theta/2), z sin(theta/2), cos(theta/2)]
q_im = axis_matrix[largest],
q_re = R[(largest+2)%3][(largest+1)%3] - R[(largest+1)%3][(largest+2)%3],
c_sin = norm(q_im), // c * sin(theta/2) for some c
c_cos = abs(q_re) // c * cos(theta/2)
)
approx(c_sin,0) ? [0,[0,0,1],[0,0,0],translation] :
let(
angle = 2*atan2(c_sin, c_cos), // This is supposed to be more accurate than acos or asin
axis = (q_re>=0 ? 1:-1)*q_im/c_sin,
tproj = translation - (translation*axis)*axis, // Translation perpendicular to axis determines centerpoint
cp = (tproj + cross(axis,tproj)*c_cos/c_sin)/2
angle = 2*atan2(c_sin, c_cos), // This is supposed to be more accurate than acos or asin
axis = (q_re>=0 ? 1:-1)*q_im/c_sin,
tproj = translation - (translation*axis)*axis, // Translation perpendicular to axis determines centerpoint
cp = (tproj + cross(axis,tproj)*c_cos/c_sin)/2
)
[angle, axis, cp, (translation*axis)*axis];
// Section: Affine2d 3x3 Transformation Matrices
// Function: affine2d_identity()
// Usage:
// mat = affine2d_identify();
// Description: Create a 3x3 affine2d identity matrix.
function affine2d_identity() = ident(3);
// Function: affine2d_translate()
// Usage:
// mat = affine2d_translate(v);
// Description:
// Returns the 3x3 affine2d matrix to perform a 2D translation.
// Arguments:
// v = 2D Offset to translate by. [X,Y]
function affine2d_translate(v=[0,0]) =
assert(is_vector(v),2)
[
[1, 0, v.x],
[0, 1, v.y],
[0 ,0, 1]
];
// Function: affine2d_scale()
// Usage:
// mat = affine2d_scale(v);
// 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=[1,1]) =
assert(is_vector(v,2))
[
[v.x, 0, 0],
[ 0, v.y, 0],
[ 0, 0, 1]
];
// Function: affine2d_zrot()
// Usage:
// mat = affine2d_zrot(ang);
// 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=0) =
assert(is_finite(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) =
assert(is_vector(v,2))
let(v=unit(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:
// mat = affine2d_skew(xa);
// mat = affine2d_skew(ya=);
// mat = 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. Default: 0
// ya = Skew angle, in degrees, in the direction of the Y axis. Default: 0
function affine2d_skew(xa=0, ya=0) =
assert(is_finite(xa))
assert(is_finite(ya))
[
[1, tan(xa), 0],
[tan(ya), 1, 0],
[0, 0, 1]
];
// Section: Affine3d 4x4 Transformation Matrices
// Function: affine3d_identity()
// Usage:
// mat = affine3d_identity();
// Description: Create a 4x4 affine3d identity matrix.
function affine3d_identity() = ident(4);
// Function: affine3d_translate()
// Usage:
// mat = affine3d_translate(v);
// 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=[0,0,0]) =
assert(is_list(v))
let( v = [for (i=[0:2]) default(v[i],0)] )
[
[1, 0, 0, v.x],
[0, 1, 0, v.y],
[0, 0, 1, v.z],
[0 ,0, 0, 1]
];
// Function: affine3d_scale()
// Usage:
// mat = affine3d_scale(v);
// 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=[1,1,1]) =
assert(is_list(v))
let( v = [for (i=[0:2]) default(v[i],1)] )
[
[v.x, 0, 0, 0],
[ 0, v.y, 0, 0],
[ 0, 0, v.z, 0],
[ 0, 0, 0, 1]
];
// Function: affine3d_xrot()
// Usage:
// mat = affine3d_xrot(ang);
// 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=0) =
assert(is_finite(ang))
[
[1, 0, 0, 0],
[0, cos(ang), -sin(ang), 0],
[0, sin(ang), cos(ang), 0],
[0, 0, 0, 1]
];
// Function: affine3d_yrot()
// Usage:
// mat = affine3d_yrot(ang);
// 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=0) =
assert(is_finite(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:
// mat = 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=0) =
assert(is_finite(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:
// mat = 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=UP, ang=0) =
assert(is_finite(ang))
assert(is_vector(u,3))
approx(ang,0)? affine3d_identity() :
let(
u = unit(u),
c = cos(ang),
c2 = 1-c,
s = sin(ang)
) [
[u.x*u.x*c2+c , u.x*u.y*c2-u.z*s, u.x*u.z*c2+u.y*s, 0],
[u.y*u.x*c2+u.z*s, u.y*u.y*c2+c , u.y*u.z*c2-u.x*s, 0],
[u.z*u.x*c2-u.y*s, u.z*u.y*c2+u.x*s, u.z*u.z*c2+c , 0],
[ 0, 0, 0, 1]
];
// Function: affine3d_rot_from_to()
// Usage:
// mat = 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) =
assert(is_vector(from))
assert(is_vector(to))
assert(len(from)==len(to))
let(
from = unit(point3d(from)),
to = unit(point3d(to))
) approx(from,to)? affine3d_identity() :
let(
u = vector_axis(from,to),
ang = vector_angle(from,to),
c = cos(ang),
c2 = 1-c,
s = sin(ang)
) [
[u.x*u.x*c2+c , u.x*u.y*c2-u.z*s, u.x*u.z*c2+u.y*s, 0],
[u.y*u.x*c2+u.z*s, u.y*u.y*c2+c , u.y*u.z*c2-u.x*s, 0],
[u.z*u.x*c2-u.y*s, u.z*u.y*c2+u.x*s, u.z*u.z*c2+c , 0],
[ 0, 0, 0, 1]
];
// Function: affine3d_frame_map()
// Usage:
// map = affine3d_frame_map(v1, v2, v3);
// map = affine3d_frame_map(x=VECTOR1, y=VECTOR2, <reverse>);
// map = affine3d_frame_map(x=VECTOR1, z=VECTOR2, <reverse>);
// map = affine3d_frame_map(y=VECTOR1, z=VECTOR2, <reverse>);
// 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 and the
// `reverse` parameter will supply the inverse map, which enables you to map two arbitrary
// coordinate systems to each other by using the canonical coordinate system as an intermediary.
// You cannot use the `reverse` option with non-orthogonal inputs.
// Arguments:
// x = Destination 3D vector for x axis.
// y = Destination 3D vector for y axis.
// z = Destination 3D vector for z axis.
// reverse = reverse direction of the map for orthogonal inputs. Default: false
// Example:
// T = affine3d_frame_map(x=[1,1,0], y=[-1,1,0]); // This map is just a rotation around the z axis
// Example:
// T = affine3d_frame_map(x=[1,0,0], y=[1,1,0]); // This map is not a rotation because x and y aren't orthogonal
// Example:
// // The next map sends [1,1,0] to [0,1,1] and [-1,1,0] to [0,-1,1]
// T = affine3d_frame_map(x=[0,1,1], y=[0,-1,1]) * affine3d_frame_map(x=[1,1,0], y=[-1,1,0],reverse=true);
function affine3d_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_undef(x)? undef : unit(x,RIGHT),
y = is_undef(y)? undef : unit(y,BACK),
z = is_undef(z)? undef : unit(z,UP),
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? (
let(
ocheck = (
approx(map[0]*map[1],0) &&
approx(map[0]*map[2],0) &&
approx(map[1]*map[2],0)
)
)
assert(ocheck, "Inputs must be orthogonal when reverse==true")
[for (r=map) [for (c=r) c, 0], [0,0,0,1]]
) : [for (r=transpose(map)) [for (c=r) 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) =
assert(is_vector(v))
let(
v=unit(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>, <syz>, <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:
// mat = affine3d_skew_xy(xa);
// mat = affine3d_skew_xy(ya=);
// mat = 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. Default: 0
// ya = Skew angle, in degrees, in the direction of the Y axis. Default: 0
function affine3d_skew_xy(xa=0, ya=0) =
assert(is_finite(xa))
assert(is_finite(ya))
[
[1, 0, tan(xa), 0],
[0, 1, tan(ya), 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
];
// Function: affine3d_skew_xz()
// Usage:
// mat = affine3d_skew_xz(xa);
// mat = affine3d_skew_xz(za=);
// mat = 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. Default: 0
// za = Skew angle, in degrees, in the direction of the Z axis. Default: 0
function affine3d_skew_xz(xa=0, za=0) =
assert(is_finite(xa))
assert(is_finite(za))
[
[1, tan(xa), 0, 0],
[0, 1, 0, 0],
[0, tan(za), 1, 0],
[0, 0, 0, 1]
];
// Function: affine3d_skew_yz()
// Usage:
// mat = affine3d_skew_yz(ya);
// mat = affine3d_skew_yz(za=);
// mat = 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. Default: 0
// za = Skew angle, in degrees, in the direction of the Z axis. Default: 0
function affine3d_skew_yz(ya=0, za=0) =
assert(is_finite(ya))
assert(is_finite(za))
[
[ 1, 0, 0, 0],
[tan(ya), 1, 0, 0],
[tan(za), 0, 1, 0],
[ 0, 0, 0, 1]
];
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap

View file

@ -31,8 +31,21 @@ module test_is_2d_transform() {
test_is_2d_transform();
module test_is_affine() {
assert(is_affine(affine2d_scale([2,3])));
assert(is_affine(affine3d_scale([2,3,4])));
assert(!is_affine(affine3d_scale([2,3,4]),2));
assert(is_affine(affine2d_scale([2,3]),2));
assert(is_affine(affine3d_scale([2,3,4]),3));
assert(!is_affine(affine2d_scale([2,3]),3));
}
test_is_affine();
module test_affine2d_to_3d() {
assert(affine2d_to_3d(affine2d_identity()) == affine3d_identity());
assert(affine2d_to_3d(affine2d_translate([30,40])) == affine3d_translate([30,40,0]));
assert(affine2d_to_3d(affine2d_scale([3,4])) == affine3d_scale([3,4,1]));
assert(affine2d_to_3d(affine2d_zrot(30)) == affine3d_zrot(30));
}
test_affine2d_to_3d();
@ -88,15 +101,6 @@ module test_affine2d_skew() {
test_affine2d_skew();
module test_affine2d_chain() {
t = affine2d_translate([15,30]);
s = affine2d_scale([1.5,2]);
r = affine2d_zrot(30);
assert(affine2d_chain([t,s,r]) == r * s * t);
}
test_affine2d_chain();
// 3D
module test_affine3d_identity() {
@ -210,15 +214,6 @@ module test_affine3d_skew_yz() {
test_affine3d_skew_yz();
module test_affine3d_chain() {
t = affine3d_translate([15,30,23]);
s = affine3d_scale([1.5,2,1.8]);
r = affine3d_zrot(30);
assert(affine3d_chain([t,s,r]) == r * s * t);
}
test_affine3d_chain();
////////////////////////////
module test_affine3d_frame_map() {

View file

@ -6,7 +6,7 @@
//////////////////////////////////////////////////////////////////////
BOSL_VERSION = [2,0,534];
BOSL_VERSION = [2,0,535];
// Section: BOSL Library Version Functions