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)),
to = unit(point3d(to))
) approx(from,to)? affine3d_identity() :
from.z==0 && to.z==0 ? affine3d_zrot(v_theta(point2d(to)) - v_theta(point2d(from)))
:
let(
u = vector_axis(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=="xjump" ? list_set(state, path, concat(state[path],[[parm,lastpt.y]])):
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=="right" ? 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])) :
command=="angle" ? list_set(state, angle, parm) :
command=="setdir" ? (
is_vector(parm) ?
@ -1020,7 +1020,7 @@ function _turtle_command(command, parm, parm2, state, index) =
list_set(
state, [path,step], [
concat(state[path], list_tail(arcpath)),
rot(lrsign * myangle,p=state[step],planar=true)
rot(lrsign * myangle,p=state[step])
]
) :
command=="arcleftto" || command=="arcrightto" ?
@ -1046,7 +1046,7 @@ function _turtle_command(command, parm, parm2, state, index) =
list_set(
state, [path,step], [
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))

View file

@ -94,11 +94,11 @@ module echo_matrix(M,description,sig=4,eps=1e-9)
// Topics: Matrices, List Handling
// See Also: select(), slice()
// 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.
// Arguments:
// M = The given list of lists.
// idx = The index, list of indices, or range of indices to fetch.
// i = The index to fetch
// Example:
// 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]
@ -114,7 +114,6 @@ function column(M, i) =
[for(row=M) row[i]];
// Function: submatrix()
// Usage:
// mat = submatrix(M, idx1, idx2);
@ -463,7 +462,7 @@ function matrix_inverse(A) =
// B = rot_inverse(A)
// Description:
// 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) =
assert(is_matrix(T,square=true),"Matrix must be square")
let( n = len(T))

View file

@ -344,12 +344,12 @@ module regular_polyhedron(
$center = -mean(facepts);
cfacepts = move($center, p=facepts);
$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;
$faceindex = i;
translate(-$center)
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);
} else {
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(
inset = opp_ang_to_hyp(rounding, (180-360/n)/2),
mat = !is_undef(_mat) ? _mat :
( realign? rot(-180/n, planar=true) : affine2d_identity() ) * (
!is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip), planar=true) :
!is_undef(align_side)? rot(from=RIGHT, to=point2d(align_side), planar=true) * rot(180/n, planar=true) :
affine2d_identity()
( realign? zrot(-180/n) : ident(4)) * (
!is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip)) :
!is_undef(align_side)? rot(from=RIGHT, to=point2d(align_side)) * zrot(180/n) :
1
),
path4 = rounding==0? ellipse(r=r, $fn=n) : (
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;
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.");
mat = ( realign? rot(-180/n, planar=true) : affine2d_identity() ) * (
!is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip), planar=true) :
!is_undef(align_side)? rot(from=RIGHT, to=point2d(align_side), planar=true) * 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)) :
!is_undef(align_side)? rot(from=RIGHT, to=point2d(align_side)) * zrot(180/n) :
1
);
inset = opp_ang_to_hyp(rounding, (180-360/n)/2);
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"))
let(
mat = !is_undef(_mat) ? _mat :
( realign? rot(-180/n, planar=true) : affine2d_identity() ) * (
!is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip), planar=true) :
!is_undef(align_pit)? rot(from=RIGHT, to=point2d(align_pit), planar=true) * rot(180/n, planar=true) :
affine2d_identity()
( realign? zrot(-180/n) : ident(4) ) * (
!is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip)) :
!is_undef(align_pit)? rot(from=RIGHT, to=point2d(align_pit)) * zrot(180/n) :
1
),
stepr = is_undef(step)? r : r*cos(180*step/n)/cos(180*(step-1)/n),
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);
stepr = is_undef(step)? r : r*cos(180*step/n)/cos(180*(step-1)/n);
ir = get_radius(r=ir, d=id, dflt=stepr);
mat = ( realign? rot(-180/n, planar=true) : affine2d_identity() ) * (
!is_undef(align_tip)? rot(from=RIGHT, to=point2d(align_tip), planar=true) :
!is_undef(align_pit)? rot(from=RIGHT, to=point2d(align_pit), planar=true) * 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)) :
!is_undef(align_pit)? rot(from=RIGHT, to=point2d(align_pit)) * zrot(180/n) :
1
);
anchors = [
for (i = [0:1:n-1]) let(

View file

@ -109,7 +109,7 @@ module test_scale() {
cb = cube(1);
vals = [[-1,-2,-3],[1,1,1],[3,6,2],[1,2,3],[243,75,147]];
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, p=[1,2,3]), v_mul([1,2,3], val));
scale(val) union(){};

View file

@ -20,15 +20,57 @@
// 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];
//////////////////////////////////////////////////////////////////////
// Section: Translations
//////////////////////////////////////////////////////////////////////
// Function&Module: move()
// 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]]
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_vector(v) && (len(v)==3 || len(v)==2), "Invalid value for `v`")
translate(point3d(v)+[x,y,z]) children();
}
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(
m = len(v)==2? affine2d_translate(v+[x,y]) :
affine3d_translate(point3d(v)+[x,y,z])
m = affine3d_translate(point3d(v)+[x,y,z])
)
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]]
module left(x=0, p) {
assert(is_undef(p), "Module form `left()` does not accept p= argument.");
assert(is_finite(x), "Invalid number")
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()
@ -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]]
module right(x=0, p) {
assert(is_undef(p), "Module form `right()` does not accept p= argument.");
assert(is_finite(x), "Invalid number")
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()
@ -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]]
module fwd(y=0, p) {
assert(is_undef(p), "Module form `fwd()` does not accept p= argument.");
assert(is_finite(y), "Invalid number")
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()
@ -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]]
module back(y=0, p) {
assert(is_undef(p), "Module form `back()` does not accept p= argument.");
assert(is_finite(y), "Invalid number")
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()
@ -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]]
module up(z=0, p) {
assert(is_undef(p), "Module form `up()` does not accept p= argument.");
assert(is_finite(z), "Invalid number");
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)
),
m2 = is_undef(cp)? m1 : (move(cp) * m1 * move(-cp)),
m3 = reverse? matrix_inverse(m2) : m2
m3 = reverse? rot_inverse(m2) : m2
) m3 : let(
from = is_undef(from)? undef : point3d(from),
to = is_undef(to)? undef : point3d(to),
cp = is_undef(cp)? undef : point3d(cp),
m1 = !is_undef(from)? (
m1 = !is_undef(from) ?
assert(is_num(a))
affine3d_rot_from_to(from,to) * affine3d_rot_by_axis(from,a)
) :
!is_undef(v)? assert(is_num(a)) affine3d_rot_by_axis(v,a) :
is_num(a)? affine3d_zrot(a) :
affine3d_zrot(a.z) * affine3d_yrot(a.y) * affine3d_xrot(a.x),
: !is_undef(v)?
assert(is_num(a))
affine3d_rot_by_axis(v,a)
: 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)),
m3 = reverse? matrix_inverse(m2) : m2
m3 = reverse? rot_inverse(m2) : m2
) m3
)
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))
let(
v = is_num(v)? [v,v,v] : v,
m = len(v)==2? (
cp==[0,0,0] || cp == [0,0] ? affine2d_scale(v) : (
affine2d_translate(point2d(cp)) *
affine2d_scale(v) *
affine2d_translate(point2d(-cp))
)
) : (
cp==[0,0,0] ? affine3d_scale(v) : (
affine3d_translate(point3d(cp)) *
affine3d_scale(v) *
affine3d_translate(point3d(-cp))
)
)
m = 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) ;
@ -1278,15 +1324,17 @@ function is_2d_transform(t) = // z-parameters are zero, except we allow t[2][
// pts = apply(transform, points);
// Topics: Affine, Matrices, Transforms
// 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.
// Applies the specified transformation matrix `transform` to a point, point list, bezier patch or VNF.
// When `points` contains 2D or 3D points the transform matrix may be a 4x4 affine matrix or a 3x4 matrix---
// 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
// with fewer rows to produce lower dimensional output.
// Any other combination of matrices will produce an error, including acting with a 2D matrix (3x3) on 3D data.
// The output of apply is always the same dimension as the input---projections are not supported.
// 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.
// transform = The 2D (3x3 or 2x3) or 3D (4x4 or 3x4) transformation matrix to apply.
// points = The point, point list, bezier patch, or VNF to apply the transformation to.
// Example(3D):
// path1 = path3d(circle(r=40));
// tmat = xrot(45);
@ -1319,30 +1367,27 @@ function apply(transform,points) =
: _apply(transform,points);
function _apply(transform,points) =
assert(is_matrix(transform),"Invalid transformation matrix")
assert(is_matrix(points),"Invalid points list")
let(
tdim = len(transform[0])-1,
datadim = len(points[0]),
outdim = min(datadim,len(transform)),
matrix = [for(i=[0:1:tdim]) [for(j=[0:1:outdim-1]) transform[j][i]]]
datadim = len(points[0])
)
tdim==datadim && (datadim==3 || datadim==2)
? [for(p=points) concat(p,1)] * matrix
: tdim == 3 && datadim == 2 ?
assert(is_2d_transform(transform), str("Transforms is 3d but points are 2d"))
assert(len(transform)==tdim || len(transform)-1==tdim, "transform matrix height not compatible with width")
assert(datadim==2 || datadim==3,"Data must be 2D or 3D")
let(
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
: tdim == 2 && datadim == 3 ?
let(
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));
: assert(false, str("Unsupported combination: ",len(transform),"x",len(transform[0])," transform (dimension ",tdim,
"), data of dimension ",datadim));
// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap