Merge branch 'master' of github.com:revarbat/BOSL2 into revarbat_dev

This commit is contained in:
Garth Minette 2021-12-29 18:43:46 -08:00
commit 2e9c303abb
7 changed files with 127 additions and 81 deletions

View file

@ -404,6 +404,8 @@ function affine3d_rot_from_to(from, to) =
from = unit(point3d(from)), from = unit(point3d(from)),
to = unit(point3d(to)) to = unit(point3d(to))
) approx(from,to)? affine3d_identity() : ) approx(from,to)? affine3d_identity() :
from.z==0 && to.z==0 ? affine3d_zrot(v_theta(point2d(to)) - v_theta(point2d(from)))
:
let( let(
u = vector_axis(from,to), u = vector_axis(from,to),
ang = vector_angle(from,to), ang = vector_angle(from,to),

View file

@ -988,8 +988,8 @@ function _turtle_command(command, parm, parm2, state, index) =
command=="jump" ? list_set(state, path, concat(state[path],[parm])): command=="jump" ? list_set(state, path, concat(state[path],[parm])):
command=="xjump" ? list_set(state, path, concat(state[path],[[parm,lastpt.y]])): command=="xjump" ? list_set(state, path, concat(state[path],[[parm,lastpt.y]])):
command=="yjump" ? list_set(state, path, concat(state[path],[[lastpt.x,parm]])): command=="yjump" ? list_set(state, path, concat(state[path],[[lastpt.x,parm]])):
command=="turn" || command=="left" ? list_set(state, step, rot(default(parm,state[angle]),p=state[step],planar=true)) : command=="turn" || command=="left" ? list_set(state, step, rot(default(parm,state[angle]),p=state[step])) :
command=="right" ? list_set(state, step, rot(-default(parm,state[angle]),p=state[step],planar=true)) : command=="right" ? list_set(state, step, rot(-default(parm,state[angle]),p=state[step])) :
command=="angle" ? list_set(state, angle, parm) : command=="angle" ? list_set(state, angle, parm) :
command=="setdir" ? ( command=="setdir" ? (
is_vector(parm) ? is_vector(parm) ?
@ -1020,7 +1020,7 @@ function _turtle_command(command, parm, parm2, state, index) =
list_set( list_set(
state, [path,step], [ state, [path,step], [
concat(state[path], list_tail(arcpath)), concat(state[path], list_tail(arcpath)),
rot(lrsign * myangle,p=state[step],planar=true) rot(lrsign * myangle,p=state[step])
] ]
) : ) :
command=="arcleftto" || command=="arcrightto" ? command=="arcleftto" || command=="arcrightto" ?
@ -1046,7 +1046,7 @@ function _turtle_command(command, parm, parm2, state, index) =
list_set( list_set(
state, [path,step], [ state, [path,step], [
concat(state[path], list_tail(arcpath)), concat(state[path], list_tail(arcpath)),
rot(delta_angle,p=state[step],planar=true) rot(delta_angle,p=state[step])
] ]
) : ) :
assert(false,str("Unknown turtle command \"",command,"\" at index",index)) assert(false,str("Unknown turtle command \"",command,"\" at index",index))

View file

@ -94,11 +94,11 @@ module echo_matrix(M,description,sig=4,eps=1e-9)
// Topics: Matrices, List Handling // Topics: Matrices, List Handling
// See Also: select(), slice() // See Also: select(), slice()
// Description: // Description:
// Extracts entry i from each list in M, or equivalently column i from the matrix M, and returns it as a vector. // Extracts entry `i` from each list in M, or equivalently column i from the matrix M, and returns it as a vector.
// This function will return `undef` at all entry positions indexed by i not found in M. // This function will return `undef` at all entry positions indexed by i not found in M.
// Arguments: // Arguments:
// M = The given list of lists. // M = The given list of lists.
// idx = The index, list of indices, or range of indices to fetch. // i = The index to fetch
// Example: // Example:
// M = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]]; // M = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]];
// a = column(M,2); // Returns [3, 7, 11, 15] // a = column(M,2); // Returns [3, 7, 11, 15]
@ -114,7 +114,6 @@ function column(M, i) =
[for(row=M) row[i]]; [for(row=M) row[i]];
// Function: submatrix() // Function: submatrix()
// Usage: // Usage:
// mat = submatrix(M, idx1, idx2); // mat = submatrix(M, idx1, idx2);
@ -463,7 +462,7 @@ function matrix_inverse(A) =
// B = rot_inverse(A) // B = rot_inverse(A)
// Description: // Description:
// Inverts a 2d (3x3) or 3d (4x4) rotation matrix. The matrix can be a rotation around any center, // Inverts a 2d (3x3) or 3d (4x4) rotation matrix. The matrix can be a rotation around any center,
// so it may include a translation. // so it may include a translation. This is faster and likely to be more accurate than using `matrix_inverse()`.
function rot_inverse(T) = function rot_inverse(T) =
assert(is_matrix(T,square=true),"Matrix must be square") assert(is_matrix(T,square=true),"Matrix must be square")
let( n = len(T)) let( n = len(T))

View file

@ -344,12 +344,12 @@ module regular_polyhedron(
$center = -mean(facepts); $center = -mean(facepts);
cfacepts = move($center, p=facepts); cfacepts = move($center, p=facepts);
$face = rotate_children $face = rotate_children
? path2d(rot(from=face_normals[i], to=[0,0,1], p=cfacepts)) ? path2d(frame_map(z=face_normals[i], x=facepts[0]-facepts[1], reverse=true, p=cfacepts))
: cfacepts; : cfacepts;
$faceindex = i; $faceindex = i;
translate(-$center) translate(-$center)
if (rotate_children) { if (rotate_children) {
rot(from=[0,0,1], to=face_normals[i]) frame_map(z=face_normals[i], x=facepts[0]-facepts[1])
children(i % $children); children(i % $children);
} else { } else {
children(i % $children); children(i % $children);

View file

@ -459,10 +459,10 @@ function regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false
let( let(
inset = opp_ang_to_hyp(rounding, (180-360/n)/2), inset = opp_ang_to_hyp(rounding, (180-360/n)/2),
mat = !is_undef(_mat) ? _mat : mat = !is_undef(_mat) ? _mat :
( realign? rot(-180/n, planar=true) : affine2d_identity() ) * ( ( realign? zrot(-180/n) : ident(4)) * (
!is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip), planar=true) : !is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip)) :
!is_undef(align_side)? rot(from=RIGHT, to=point2d(align_side), planar=true) * rot(180/n, planar=true) : !is_undef(align_side)? rot(from=RIGHT, to=point2d(align_side)) * zrot(180/n) :
affine2d_identity() 1
), ),
path4 = rounding==0? ellipse(r=r, $fn=n) : ( path4 = rounding==0? ellipse(r=r, $fn=n) : (
let( let(
@ -504,10 +504,10 @@ module regular_ngon(n=6, r, d, or, od, ir, id, side, rounding=0, realign=false,
side = is_finite(side)? side/2/sin(180/n) : undef; side = is_finite(side)? side/2/sin(180/n) : undef;
r = get_radius(r1=ir, r2=or, r=r, d1=id, d2=od, d=d, dflt=side); r = get_radius(r1=ir, r2=or, r=r, d1=id, d2=od, d=d, dflt=side);
assert(!is_undef(r), "regular_ngon(): need to specify one of r, d, or, od, ir, id, side."); assert(!is_undef(r), "regular_ngon(): need to specify one of r, d, or, od, ir, id, side.");
mat = ( realign? rot(-180/n, planar=true) : affine2d_identity() ) * ( mat = ( realign? zrot(-180/n) : ident(4) ) * (
!is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip), planar=true) : !is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip)) :
!is_undef(align_side)? rot(from=RIGHT, to=point2d(align_side), planar=true) * rot(180/n, planar=true) : !is_undef(align_side)? rot(from=RIGHT, to=point2d(align_side)) * zrot(180/n) :
affine2d_identity() 1
); );
inset = opp_ang_to_hyp(rounding, (180-360/n)/2); inset = opp_ang_to_hyp(rounding, (180-360/n)/2);
anchors = [ anchors = [
@ -930,10 +930,10 @@ function star(n, r, ir, d, or, od, id, step, realign=false, align_tip, align_pit
: str("Parameter 'step' must be between 2 and ",floor(n/2-1/2)," for ",n," point stars")) : str("Parameter 'step' must be between 2 and ",floor(n/2-1/2)," for ",n," point stars"))
let( let(
mat = !is_undef(_mat) ? _mat : mat = !is_undef(_mat) ? _mat :
( realign? rot(-180/n, planar=true) : affine2d_identity() ) * ( ( realign? zrot(-180/n) : ident(4) ) * (
!is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip), planar=true) : !is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip)) :
!is_undef(align_pit)? rot(from=RIGHT, to=point2d(align_pit), planar=true) * rot(180/n, planar=true) : !is_undef(align_pit)? rot(from=RIGHT, to=point2d(align_pit)) * zrot(180/n) :
affine2d_identity() 1
), ),
stepr = is_undef(step)? r : r*cos(180*step/n)/cos(180*(step-1)/n), stepr = is_undef(step)? r : r*cos(180*step/n)/cos(180*(step-1)/n),
ir = get_radius(r=ir, d=id, dflt=stepr), ir = get_radius(r=ir, d=id, dflt=stepr),
@ -967,10 +967,10 @@ module star(n, r, ir, d, or, od, id, step, realign=false, align_tip, align_pit,
r = get_radius(r1=or, d1=od, r=r, d=d, dflt=undef); r = get_radius(r1=or, d1=od, r=r, d=d, dflt=undef);
stepr = is_undef(step)? r : r*cos(180*step/n)/cos(180*(step-1)/n); stepr = is_undef(step)? r : r*cos(180*step/n)/cos(180*(step-1)/n);
ir = get_radius(r=ir, d=id, dflt=stepr); ir = get_radius(r=ir, d=id, dflt=stepr);
mat = ( realign? rot(-180/n, planar=true) : affine2d_identity() ) * ( mat = ( realign? zrot(-180/n) : ident(4) ) * (
!is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip), planar=true) : !is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip)) :
!is_undef(align_pit)? rot(from=RIGHT, to=point2d(align_pit), planar=true) * rot(180/n, planar=true) : !is_undef(align_pit)? rot(from=RIGHT, to=point2d(align_pit)) * zrot(180/n) :
affine2d_identity() 1
); );
anchors = [ anchors = [
for (i = [0:1:n-1]) let( for (i = [0:1:n-1]) let(

View file

@ -109,7 +109,7 @@ module test_scale() {
cb = cube(1); cb = cube(1);
vals = [[-1,-2,-3],[1,1,1],[3,6,2],[1,2,3],[243,75,147]]; vals = [[-1,-2,-3],[1,1,1],[3,6,2],[1,2,3],[243,75,147]];
for (val=vals) { for (val=vals) {
assert_equal(scale(point2d(val)), [[val.x,0,0],[0,val.y,0],[0,0,1]]); assert_equal(scale(point2d(val)), [[val.x,0,0,0],[0,val.y,0,0],[0,0,1,0],[0,0,0,1]]);
assert_equal(scale(val), [[val.x,0,0,0],[0,val.y,0,0],[0,0,val.z,0],[0,0,0,1]]); assert_equal(scale(val), [[val.x,0,0,0],[0,val.y,0,0],[0,0,val.z,0],[0,0,0,1]]);
assert_equal(scale(val, p=[1,2,3]), v_mul([1,2,3], val)); assert_equal(scale(val, p=[1,2,3]), v_mul([1,2,3], val));
scale(val) union(){}; scale(val) union(){};

View file

@ -20,15 +20,57 @@
// FileFootnotes: STD=Included in std.scad // FileFootnotes: STD=Included in std.scad
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
// Section: Affine Transformations
// OpenSCAD provides various built-in modules to transform geometry by
// translation, scaling, rotation, and mirroring. All of these operations
// are affine transformations. A three-dimensional affine transformation
// can be represented by a 4x4 matrix. The transformation shortcuts in this
// file generally have three modes of operation. They can operate
// directly on geometry like their OpenSCAD built-in equivalents. For example,
// `left(10) cube()`. They can operate on a list of points (or various other
// types of geometric data). For example, operating on a list of points: `points = left(10, [[1,2,3],[4,5,6]])`.
// The third option is that the shortcut can return the transformation matrix
// corresponding to its action. For example, `M=left(10)`.
// .
// This capability allows you to store and manipulate transformations, and can
// be useful in more advanced modeling. You can multiply these matrices
// together, analogously to applying a sequence of operations with the
// built-in transformations. So you can write `zrot(37)left(5)cube()`
// to perform two operations on a cube. You can also store
// that same transformation by multiplying the matrices together: `M = zrot(37) * left(5)`.
// Note that the order is exactly the same as the order used to apply the transformation.
// .
// Suppose you have constructed `M` as above. What now? You can use
// the OpensCAD built-in `multmatrix` to apply it to some geometry: `multmatrix(M) cube()`.
// Alternative you can use the BOSL2 function `apply` to apply `M` to a point, a list
// of points, a bezier patch, or a VNF. For example, `points = apply(M, [[3,4,5],[5,6,7]])`.
// Note that the `apply` function can work on both 2D and 3D data, but if you want to
// operate on 2D data, you must choose transformations that don't modify z
// .
// You can use matrices as described above without understanding the details, just
// treating a matrix as a box that stores a transformation. The OpenSCAD manual section for multmatrix
// gives some details of how this works. We'll elaborate a bit more below. An affine transformation
// matrix for three dimensional data is a 4x4 matrix. The top left 3x3 portion gives the linear
// transformation to apply to the data. For example, it could be a rotation or scaling, or combination of both.
// The 3x1 column at the top right gives the translation to apply. The bottom row should be `[0,0,0,1]`. That
// bottom row is only present to enable
// the matrices to be multiplied together. OpenSCAD ignores it and in fact `multmatrix` will
// accept a 3x4 matrix, where that row is missing. In order for a matrix to act on a point you have to
// augment the point with an extra 1, making it a length 4 vector. In OpenSCAD you can then compute the
// the affine transformed point as `tran_point = M * point`. However, this syntax hides a complication that
// arises if you have a list of points. A list of points like `[[1,2,3,1],[4,5,6,1],[7,8,9,1]]` has the augmented points
// as row vectors on the list. In order to transform such a list, it needs to be muliplied on the right
// side, not the left side.
//////////////////////////////////////////////////////////////////////
// Section: Translations
//////////////////////////////////////////////////////////////////////
_NO_ARG = [true,[123232345],false]; _NO_ARG = [true,[123232345],false];
//////////////////////////////////////////////////////////////////////
// Section: Translations
//////////////////////////////////////////////////////////////////////
// Function&Module: move() // Function&Module: move()
// Aliases: translate() // Aliases: translate()
// //
@ -98,13 +140,14 @@ _NO_ARG = [true,[123232345],false];
// mat3d = move([2,3,4]); // Returns: [[1,0,0,2],[0,1,0,3],[0,0,1,4],[0,0,0,1]] // mat3d = move([2,3,4]); // Returns: [[1,0,0,2],[0,1,0,3],[0,0,1,4],[0,0,0,1]]
module move(v=[0,0,0], p, x=0, y=0, z=0) { module move(v=[0,0,0], p, x=0, y=0, z=0) {
assert(is_undef(p), "Module form `move()` does not accept p= argument."); assert(is_undef(p), "Module form `move()` does not accept p= argument.");
assert(is_vector(v) && (len(v)==3 || len(v)==2), "Invalid value for `v`")
translate(point3d(v)+[x,y,z]) children(); translate(point3d(v)+[x,y,z]) children();
} }
function move(v=[0,0,0], p=_NO_ARG, x=0, y=0, z=0) = function move(v=[0,0,0], p=_NO_ARG, x=0, y=0, z=0) =
assert(is_vector(v) && (len(v)==3 || len(v)==2), "Invalid value for `v`")
let( let(
m = len(v)==2? affine2d_translate(v+[x,y]) : m = affine3d_translate(point3d(v)+[x,y,z])
affine3d_translate(point3d(v)+[x,y,z])
) )
p==_NO_ARG ? m : apply(m, p); p==_NO_ARG ? m : apply(m, p);
@ -143,10 +186,12 @@ function translate(v=[0,0,0], p=_NO_ARG) = move(v=v, p=p);
// mat3d = left(4); // Returns: [[1,0,0,-4],[0,1,0,0],[0,0,1,0],[0,0,0,1]] // mat3d = left(4); // Returns: [[1,0,0,-4],[0,1,0,0],[0,0,1,0],[0,0,0,1]]
module left(x=0, p) { module left(x=0, p) {
assert(is_undef(p), "Module form `left()` does not accept p= argument."); assert(is_undef(p), "Module form `left()` does not accept p= argument.");
assert(is_finite(x), "Invalid number")
translate([-x,0,0]) children(); translate([-x,0,0]) children();
} }
function left(x=0, p=_NO_ARG) = move([-x,0,0],p=p); function left(x=0, p=_NO_ARG) = assert(is_finite(x), "Invalid number")
move([-x,0,0],p=p);
// Function&Module: right() // Function&Module: right()
@ -181,10 +226,12 @@ function left(x=0, p=_NO_ARG) = move([-x,0,0],p=p);
// mat3d = right(4); // Returns: [[1,0,0,4],[0,1,0,0],[0,0,1,0],[0,0,0,1]] // mat3d = right(4); // Returns: [[1,0,0,4],[0,1,0,0],[0,0,1,0],[0,0,0,1]]
module right(x=0, p) { module right(x=0, p) {
assert(is_undef(p), "Module form `right()` does not accept p= argument."); assert(is_undef(p), "Module form `right()` does not accept p= argument.");
assert(is_finite(x), "Invalid number")
translate([x,0,0]) children(); translate([x,0,0]) children();
} }
function right(x=0, p=_NO_ARG) = move([x,0,0],p=p); function right(x=0, p=_NO_ARG) = assert(is_finite(x), "Invalid number")
move([x,0,0],p=p);
// Function&Module: fwd() // Function&Module: fwd()
@ -219,10 +266,12 @@ function right(x=0, p=_NO_ARG) = move([x,0,0],p=p);
// mat3d = fwd(4); // Returns: [[1,0,0,0],[0,1,0,-4],[0,0,1,0],[0,0,0,1]] // mat3d = fwd(4); // Returns: [[1,0,0,0],[0,1,0,-4],[0,0,1,0],[0,0,0,1]]
module fwd(y=0, p) { module fwd(y=0, p) {
assert(is_undef(p), "Module form `fwd()` does not accept p= argument."); assert(is_undef(p), "Module form `fwd()` does not accept p= argument.");
assert(is_finite(y), "Invalid number")
translate([0,-y,0]) children(); translate([0,-y,0]) children();
} }
function fwd(y=0, p=_NO_ARG) = move([0,-y,0],p=p); function fwd(y=0, p=_NO_ARG) = assert(is_finite(y), "Invalid number")
move([0,-y,0],p=p);
// Function&Module: back() // Function&Module: back()
@ -257,10 +306,12 @@ function fwd(y=0, p=_NO_ARG) = move([0,-y,0],p=p);
// mat3d = back(4); // Returns: [[1,0,0,0],[0,1,0,4],[0,0,1,0],[0,0,0,1]] // mat3d = back(4); // Returns: [[1,0,0,0],[0,1,0,4],[0,0,1,0],[0,0,0,1]]
module back(y=0, p) { module back(y=0, p) {
assert(is_undef(p), "Module form `back()` does not accept p= argument."); assert(is_undef(p), "Module form `back()` does not accept p= argument.");
assert(is_finite(y), "Invalid number")
translate([0,y,0]) children(); translate([0,y,0]) children();
} }
function back(y=0,p=_NO_ARG) = move([0,y,0],p=p); function back(y=0,p=_NO_ARG) = assert(is_finite(y), "Invalid number")
move([0,y,0],p=p);
// Function&Module: down() // Function&Module: down()
@ -331,10 +382,12 @@ function down(z=0, p=_NO_ARG) = move([0,0,-z],p=p);
// mat3d = up(4); // Returns: [[1,0,0,0],[0,1,0,0],[0,0,1,4],[0,0,0,1]] // mat3d = up(4); // Returns: [[1,0,0,0],[0,1,0,0],[0,0,1,4],[0,0,0,1]]
module up(z=0, p) { module up(z=0, p) {
assert(is_undef(p), "Module form `up()` does not accept p= argument."); assert(is_undef(p), "Module form `up()` does not accept p= argument.");
assert(is_finite(z), "Invalid number");
translate([0,0,z]) children(); translate([0,0,z]) children();
} }
function up(z=0, p=_NO_ARG) = move([0,0,z],p=p); function up(z=0, p=_NO_ARG) = assert(is_finite(z), "Invalid number")
move([0,0,z],p=p);
@ -440,20 +493,21 @@ function rot(a=0, v, cp, from, to, reverse=false, planar=false, p=_NO_ARG, _m) =
v_theta(from) v_theta(from)
), ),
m2 = is_undef(cp)? m1 : (move(cp) * m1 * move(-cp)), m2 = is_undef(cp)? m1 : (move(cp) * m1 * move(-cp)),
m3 = reverse? matrix_inverse(m2) : m2 m3 = reverse? rot_inverse(m2) : m2
) m3 : let( ) m3 : let(
from = is_undef(from)? undef : point3d(from), from = is_undef(from)? undef : point3d(from),
to = is_undef(to)? undef : point3d(to), to = is_undef(to)? undef : point3d(to),
cp = is_undef(cp)? undef : point3d(cp), cp = is_undef(cp)? undef : point3d(cp),
m1 = !is_undef(from)? ( m1 = !is_undef(from) ?
assert(is_num(a)) assert(is_num(a))
affine3d_rot_from_to(from,to) * affine3d_rot_by_axis(from,a) affine3d_rot_from_to(from,to) * affine3d_rot_by_axis(from,a)
) : : !is_undef(v)?
!is_undef(v)? assert(is_num(a)) affine3d_rot_by_axis(v,a) : assert(is_num(a))
is_num(a)? affine3d_zrot(a) : affine3d_rot_by_axis(v,a)
affine3d_zrot(a.z) * affine3d_yrot(a.y) * affine3d_xrot(a.x), : is_num(a) ? affine3d_zrot(a)
: affine3d_zrot(a.z) * affine3d_yrot(a.y) * affine3d_xrot(a.x),
m2 = is_undef(cp)? m1 : (move(cp) * m1 * move(-cp)), m2 = is_undef(cp)? m1 : (move(cp) * m1 * move(-cp)),
m3 = reverse? matrix_inverse(m2) : m2 m3 = reverse? rot_inverse(m2) : m2
) m3 ) m3
) )
p==_NO_ARG ? m : apply(m, p); p==_NO_ARG ? m : apply(m, p);
@ -645,19 +699,11 @@ function scale(v=1, p=_NO_ARG, cp=[0,0,0]) =
assert(is_vector(cp)) assert(is_vector(cp))
let( let(
v = is_num(v)? [v,v,v] : v, v = is_num(v)? [v,v,v] : v,
m = len(v)==2? ( m = cp==[0,0,0]
cp==[0,0,0] || cp == [0,0] ? affine2d_scale(v) : ( ? affine3d_scale(v)
affine2d_translate(point2d(cp)) * : affine3d_translate(point3d(cp))
affine2d_scale(v) * * affine3d_scale(v)
affine2d_translate(point2d(-cp)) * affine3d_translate(point3d(-cp))
)
) : (
cp==[0,0,0] ? affine3d_scale(v) : (
affine3d_translate(point3d(cp)) *
affine3d_scale(v) *
affine3d_translate(point3d(-cp))
)
)
) )
p==_NO_ARG? m : apply(m, p) ; p==_NO_ARG? m : apply(m, p) ;
@ -1278,15 +1324,17 @@ function is_2d_transform(t) = // z-parameters are zero, except we allow t[2][
// pts = apply(transform, points); // pts = apply(transform, points);
// Topics: Affine, Matrices, Transforms // Topics: Affine, Matrices, Transforms
// Description: // Description:
// Applies the specified transformation matrix to a point, pointlist, bezier patch or VNF. // Applies the specified transformation matrix `transform` to a point, point list, bezier patch or VNF.
// Both inputs can be 2D or 3D, and it is also allowed to supply 3D transformations with 2D // When `points` contains 2D or 3D points the transform matrix may be a 4x4 affine matrix or a 3x4 matrix---
// data as long as the the only action on the z coordinate is a simple scaling. // the 4x4 matrix with its final row removed. When the data is 2D the matrix must not operate on the Z axis,
// except possibly by scaling it. When points contains 2D data you can also supply the transform as
// a 3x3 affine transformation matrix or the corresponding 2x3 matrix with the last row deleted.
// . // .
// If you construct your own matrices you can also use a transform that acts like a projection // Any other combination of matrices will produce an error, including acting with a 2D matrix (3x3) on 3D data.
// with fewer rows to produce lower dimensional output. // The output of apply is always the same dimension as the input---projections are not supported.
// Arguments: // Arguments:
// transform = The 2D or 3D transformation matrix to apply to the point/points. // transform = The 2D (3x3 or 2x3) or 3D (4x4 or 3x4) transformation matrix to apply.
// points = The point, pointlist, bezier patch, or VNF to apply the transformation to. // points = The point, point list, bezier patch, or VNF to apply the transformation to.
// Example(3D): // Example(3D):
// path1 = path3d(circle(r=40)); // path1 = path3d(circle(r=40));
// tmat = xrot(45); // tmat = xrot(45);
@ -1319,30 +1367,27 @@ function apply(transform,points) =
: _apply(transform,points); : _apply(transform,points);
function _apply(transform,points) = function _apply(transform,points) =
assert(is_matrix(transform),"Invalid transformation matrix") assert(is_matrix(transform),"Invalid transformation matrix")
assert(is_matrix(points),"Invalid points list") assert(is_matrix(points),"Invalid points list")
let( let(
tdim = len(transform[0])-1, tdim = len(transform[0])-1,
datadim = len(points[0]), datadim = len(points[0])
outdim = min(datadim,len(transform)),
matrix = [for(i=[0:1:tdim]) [for(j=[0:1:outdim-1]) transform[j][i]]]
) )
tdim==datadim && (datadim==3 || datadim==2) assert(len(transform)==tdim || len(transform)-1==tdim, "transform matrix height not compatible with width")
? [for(p=points) concat(p,1)] * matrix assert(datadim==2 || datadim==3,"Data must be 2D or 3D")
: tdim == 3 && datadim == 2 ? let(
assert(is_2d_transform(transform), str("Transforms is 3d but points are 2d")) scale = len(transform)==tdim ? 1 : transform[tdim][tdim],
matrix = [for(i=[0:1:tdim]) [for(j=[0:1:datadim-1]) transform[j][i]]] / scale
)
tdim==datadim ? [for(p=points) concat(p,1)] * matrix
: tdim == 3 && datadim == 2 ?
assert(is_2d_transform(transform), str("Transforms is 3D and acts on Z, but points are 2D"))
[for(p=points) concat(p,[0,1])]*matrix [for(p=points) concat(p,[0,1])]*matrix
: tdim == 2 && datadim == 3 ? : assert(false, str("Unsupported combination: ",len(transform),"x",len(transform[0])," transform (dimension ",tdim,
let( "), data of dimension ",datadim));
matrix3d =[[ matrix[0][0], matrix[0][1], 0],
[ matrix[1][0], matrix[1][1], 0],
[ 0, 0, 1],
[ matrix[2][0], matrix[2][1], 0]]
)
[for(p=points) concat(p,1)] * matrix3d
: assert(false, str("Unsupported combination: transform with dimension ",tdim,", data of dimension ",datadim));
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap // vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap