mirror of
https://github.com/BelfrySCAD/BOSL2.git
synced 2024-12-29 16:29:40 +00:00
Merge pull request #392 from revarbat/revarbat_dev
Affine docs improvements. Dropped affine[23]d_chain().
This commit is contained in:
commit
288a6419ca
4 changed files with 330 additions and 255 deletions
530
affine.scad
530
affine.scad
|
@ -11,25 +11,201 @@
|
|||
// 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]]
|
||||
);
|
||||
// Description:
|
||||
// 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: affine3d_to_2d()
|
||||
// Usage:
|
||||
// mat = affine3d_to_2d(m);
|
||||
// Description:
|
||||
// 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))
|
||||
[
|
||||
for (r=[0:3]) if (r!=2) [
|
||||
for (c=[0:3]) if (c!=2) m[r][c]
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
// Function: apply()
|
||||
// Usage:
|
||||
// pts = apply(transform, points);
|
||||
// Description:
|
||||
// Applies the specified transformation matrix to a point, pointlist, bezier patch or VNF.
|
||||
// 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.
|
||||
// Arguments:
|
||||
// transform = The 2D or 3D transformation matrix to apply to the point/points.
|
||||
// points = The point, pointlist, bezier patch, or VNF to apply the transformation to.
|
||||
// Example(3D):
|
||||
// path1 = path3d(circle(r=40));
|
||||
// tmat = xrot(45);
|
||||
// path2 = apply(tmat, path1);
|
||||
// #stroke(path1,closed=true);
|
||||
// stroke(path2,closed=true);
|
||||
// Example(2D):
|
||||
// path1 = circle(r=40);
|
||||
// tmat = translate([10,5]);
|
||||
// path2 = apply(tmat, path1);
|
||||
// #stroke(path1,closed=true);
|
||||
// stroke(path2,closed=true);
|
||||
// Example(2D):
|
||||
// path1 = circle(r=40);
|
||||
// tmat = rot(30) * back(15) * scale([1.5,0.5,1]);
|
||||
// path2 = apply(tmat, path1);
|
||||
// #stroke(path1,closed=true);
|
||||
// stroke(path2,closed=true);
|
||||
function apply(transform,points) =
|
||||
points==[] ? [] :
|
||||
is_vector(points)
|
||||
? /* Point */ apply(transform, [points])[0] :
|
||||
is_list(points) && len(points)==2 && is_path(points[0],3) && is_list(points[1]) && is_vector(points[1][0])
|
||||
? /* VNF */ [apply(transform, points[0]), points[1]] :
|
||||
is_list(points) && is_list(points[0]) && is_vector(points[0][0])
|
||||
? /* BezPatch */ [for (x=points) apply(transform,x)] :
|
||||
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: rot_decode()
|
||||
// Usage:
|
||||
// info = rot_decode(rotation); // Returns: [angle,axis,cp,translation]
|
||||
// Description:
|
||||
// Given an input 3D rigid transformation operator (one composed of just rotations and translations) represented
|
||||
// as a 4x4 matrix, compute the rotation and translation parameters of the operator. Returns a list of the
|
||||
// four parameters, the angle, in the interval [0,180], the rotation axis as a unit vector, a centerpoint for
|
||||
// the rotation, and a translation. If you set `parms=rot_decode(rotation)` then the transformation can be
|
||||
// reconstructed from parms as `move(parms[3])*rot(a=parms[0],v=parms[1],cp=parms[2])`. This decomposition
|
||||
// makes it possible to perform interpolation. If you construct a transformation using `rot` the decoding
|
||||
// may flip the axis (if you gave an angle outside of [0,180]). The returned axis will be a unit vector, and
|
||||
// the centerpoint lies on the plane through the origin that is perpendicular to the axis. It may be different
|
||||
// than the centerpoint you used to construct the transformation.
|
||||
// Example:
|
||||
// rot_decode(rot(45)); // Returns [45,[0,0,1], [0,0,0], [0,0,0]]
|
||||
// rot_decode(rot(a=37, v=[1,2,3], cp=[4,3,-7]))); // Returns [37, [0.26, 0.53, 0.80], [4.8, 4.6, -4.6], [0,0,0]]
|
||||
// rot_decode(left(12)*xrot(-33)); // Returns [33, [-1,0,0], [0,0,0], [-12,0,0]]
|
||||
// rot_decode(translate([3,4,5])); // Returns [0, [0,0,1], [0,0,0], [3,4,5]]
|
||||
function rot_decode(M) =
|
||||
assert(is_matrix(M,4,4) && approx(M[3],[0,0,0,1]), "Input matrix must be a 4x4 matrix representing a 3d transformation")
|
||||
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)
|
||||
)
|
||||
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, axis, cp, (translation*axis)*axis];
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -50,11 +226,13 @@ function affine2d_identity() = ident(3);
|
|||
// 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_translate(v=[0,0]) =
|
||||
assert(is_vector(v),2)
|
||||
[
|
||||
[1, 0, v.x],
|
||||
[0, 1, v.y],
|
||||
[0 ,0, 1]
|
||||
];
|
||||
|
||||
|
||||
// Function: affine2d_scale()
|
||||
|
@ -64,11 +242,13 @@ function affine2d_translate(v) = [
|
|||
// 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_scale(v=[1,1]) =
|
||||
assert(is_vector(v,2))
|
||||
[
|
||||
[v.x, 0, 0],
|
||||
[ 0, v.y, 0],
|
||||
[ 0, 0, 1]
|
||||
];
|
||||
|
||||
|
||||
// Function: affine2d_zrot()
|
||||
|
@ -78,11 +258,13 @@ function affine2d_scale(v) = [
|
|||
// 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_zrot(ang=0) =
|
||||
assert(is_finite(ang))
|
||||
[
|
||||
[cos(ang), -sin(ang), 0],
|
||||
[sin(ang), cos(ang), 0],
|
||||
[ 0, 0, 1]
|
||||
];
|
||||
|
||||
|
||||
// Function: affine2d_mirror()
|
||||
|
@ -93,6 +275,7 @@ function affine2d_zrot(ang) = [
|
|||
// 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],
|
||||
|
@ -103,29 +286,22 @@ function affine2d_mirror(v) =
|
|||
|
||||
// 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.
|
||||
// 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);
|
||||
// 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]
|
||||
];
|
||||
|
||||
|
||||
|
||||
|
@ -146,12 +322,15 @@ function affine3d_identity() = ident(4);
|
|||
// 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_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()
|
||||
|
@ -161,12 +340,15 @@ function affine3d_translate(v) = [
|
|||
// 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_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()
|
||||
|
@ -176,12 +358,14 @@ function affine3d_scale(v) = [
|
|||
// 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_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()
|
||||
|
@ -191,12 +375,14 @@ function affine3d_xrot(ang) = [
|
|||
// 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_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()
|
||||
|
@ -206,12 +392,14 @@ function affine3d_yrot(ang) = [
|
|||
// 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_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()
|
||||
|
@ -222,7 +410,9 @@ function affine3d_zrot(ang) = [
|
|||
// Arguments:
|
||||
// u = 3D axis vector to rotate around.
|
||||
// ang = number of degrees to rotate.
|
||||
function affine3d_rot_by_axis(u, ang) =
|
||||
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),
|
||||
|
@ -246,6 +436,9 @@ function affine3d_rot_by_axis(u, ang) =
|
|||
// 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))
|
||||
|
@ -318,8 +511,8 @@ function affine3d_frame_map(x,y,z, reverse=false) =
|
|||
)
|
||||
)
|
||||
assert(ocheck, "Inputs must be orthogonal when reverse==true")
|
||||
affine2d_to_3d(map)
|
||||
) : affine2d_to_3d(transpose(map));
|
||||
[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]];
|
||||
|
||||
|
||||
|
||||
|
@ -331,6 +524,7 @@ function affine3d_frame_map(x,y,z, reverse=false) =
|
|||
// 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
|
||||
|
@ -364,165 +558,65 @@ function affine3d_skew(sxy=0, sxz=0, syx=0, syz=0, szx=0, szy=0) = [
|
|||
|
||||
// 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.
|
||||
// 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]
|
||||
];
|
||||
// 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.
|
||||
// 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]
|
||||
];
|
||||
// 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.
|
||||
// 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);
|
||||
// Description:
|
||||
// Applies the specified transformation matrix to a point, pointlist, bezier patch or VNF.
|
||||
// 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.
|
||||
// Arguments:
|
||||
// transform = The 2D or 3D transformation matrix to apply to the point/points.
|
||||
// points = The point, pointlist, bezier patch, or VNF to apply the transformation to.
|
||||
// Example(3D):
|
||||
// path1 = path3d(circle(r=40));
|
||||
// tmat = xrot(45);
|
||||
// path2 = apply(tmat, path1);
|
||||
// #stroke(path1,closed=true);
|
||||
// stroke(path2,closed=true);
|
||||
// Example(2D):
|
||||
// path1 = circle(r=40);
|
||||
// tmat = translate([10,5]);
|
||||
// path2 = apply(tmat, path1);
|
||||
// #stroke(path1,closed=true);
|
||||
// stroke(path2,closed=true);
|
||||
// Example(2D):
|
||||
// path1 = circle(r=40);
|
||||
// tmat = rot(30) * back(15) * scale([1.5,0.5,1]);
|
||||
// path2 = apply(tmat, path1);
|
||||
// #stroke(path1,closed=true);
|
||||
// stroke(path2,closed=true);
|
||||
function apply(transform,points) =
|
||||
points==[] ? [] :
|
||||
is_vector(points)
|
||||
? /* Point */ apply(transform, [points])[0] :
|
||||
is_list(points) && len(points)==2 && is_path(points[0],3) && is_list(points[1]) && is_vector(points[1][0])
|
||||
? /* VNF */ [apply(transform, points[0]), points[1]] :
|
||||
is_list(points) && is_list(points[0]) && is_vector(points[0][0])
|
||||
? /* BezPatch */ [for (x=points) apply(transform,x)] :
|
||||
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: 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]
|
||||
// Description:
|
||||
// Given an input 3D rigid transformation operator (one composed of just rotations and translations) represented
|
||||
// as a 4x4 matrix, compute the rotation and translation parameters of the operator. Returns a list of the
|
||||
// four parameters, the angle, in the interval [0,180], the rotation axis as a unit vector, a centerpoint for
|
||||
// the rotation, and a translation. If you set `parms=rot_decode(rotation)` then the transformation can be
|
||||
// reconstructed from parms as `move(parms[3])*rot(a=parms[0],v=parms[1],cp=parms[2])`. This decomposition
|
||||
// makes it possible to perform interpolation. If you construct a transformation using `rot` the decoding
|
||||
// may flip the axis (if you gave an angle outside of [0,180]). The returned axis will be a unit vector, and
|
||||
// the centerpoint lies on the plane through the origin that is perpendicular to the axis. It may be different
|
||||
// than the centerpoint you used to construct the transformation.
|
||||
// Example:
|
||||
// rot_decode(rot(45)); // Returns [45,[0,0,1], [0,0,0], [0,0,0]]
|
||||
// rot_decode(rot(a=37, v=[1,2,3], cp=[4,3,-7]))); // Returns [37, [0.26, 0.53, 0.80], [4.8, 4.6, -4.6], [0,0,0]]
|
||||
// rot_decode(left(12)*xrot(-33)); // Returns [33, [-1,0,0], [0,0,0], [-12,0,0]]
|
||||
// rot_decode(translate([3,4,5])); // Returns [0, [0,0,1], [0,0,0], [3,4,5]]
|
||||
function rot_decode(M) =
|
||||
assert(is_matrix(M,4,4) && approx(M[3],[0,0,0,1]), "Input matrix must be a 4x4 matrix representing a 3d transformation")
|
||||
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)
|
||||
)
|
||||
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, axis, cp, (translation*axis)*axis];
|
||||
|
||||
// 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]
|
||||
];
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -259,21 +259,13 @@ module test_rot() {
|
|||
for (xa=angs, ya=angs, za=angs) {
|
||||
assert_equal(
|
||||
rot([xa,ya,za]),
|
||||
affine3d_chain([
|
||||
affine3d_xrot(xa),
|
||||
affine3d_yrot(ya),
|
||||
affine3d_zrot(za)
|
||||
]),
|
||||
affine3d_zrot(za) * affine3d_yrot(ya) * affine3d_xrot(xa),
|
||||
info=str("[X,Y,Z] = ",[xa,ya,za])
|
||||
);
|
||||
assert_equal(
|
||||
rot([xa,ya,za],p=pts3d),
|
||||
apply(
|
||||
affine3d_chain([
|
||||
affine3d_xrot(xa),
|
||||
affine3d_yrot(ya),
|
||||
affine3d_zrot(za)
|
||||
]),
|
||||
affine3d_zrot(za) * affine3d_yrot(ya) * affine3d_xrot(xa),
|
||||
pts3d
|
||||
),
|
||||
info=str("[X,Y,Z] = ",[xa,ya,za], ", p=...")
|
||||
|
@ -312,10 +304,7 @@ module test_rot() {
|
|||
for (a = angs) {
|
||||
assert_equal(
|
||||
rot(from=vec1, to=vec2, a=a),
|
||||
affine3d_chain([
|
||||
affine3d_zrot(a),
|
||||
affine3d_rot_from_to(vec1,vec2)
|
||||
]),
|
||||
affine3d_rot_from_to(vec1,vec2) * affine3d_zrot(a),
|
||||
info=str(
|
||||
"from = ", vec1, ", ",
|
||||
"to = ", vec2, ", ",
|
||||
|
@ -325,10 +314,7 @@ module test_rot() {
|
|||
assert_equal(
|
||||
rot(from=vec1, to=vec2, a=a, p=pts3d),
|
||||
apply(
|
||||
affine3d_chain([
|
||||
affine3d_zrot(a),
|
||||
affine3d_rot_from_to(vec1,vec2)
|
||||
]),
|
||||
affine3d_rot_from_to(vec1,vec2) * affine3d_zrot(a),
|
||||
pts3d
|
||||
),
|
||||
info=str(
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
BOSL_VERSION = [2,0,534];
|
||||
BOSL_VERSION = [2,0,536];
|
||||
|
||||
|
||||
// Section: BOSL Library Version Functions
|
||||
|
|
Loading…
Reference in a new issue