1//////////////////////////////////////////////////////////////////////
   2// LibFile: transforms.scad
   3//   Functions and modules that provide shortcuts for translation,
   4//   rotation and mirror operations.  Also provided are skew and frame_map
   5//   which remaps the coordinate axes.  The shortcuts can act on
   6//   geometry, like the usual OpenSCAD rotate() and translate(). They
   7//   also work as functions that operate on lists of points in various
   8//   forms: paths, VNFS and bezier patches. Lastly, the function form
   9//   of the shortcuts can return a matrix representing the operation
  10//   the shortcut performs. The rotation and scaling shortcuts accept
  11//   an optional centerpoint for the rotation or scaling operation.
  12//   .
  13//   Almost all of the transformation functions take a point, a point
  14//   list, bezier patch, or VNF as a second positional argument to
  15//   operate on.  The exceptions are rot(), frame_map() and skew().
  16// Includes:
  17//   include <BOSL2/std.scad>
  18// FileGroup: Basic Modeling
  19// FileSummary: Shortcuts for translation, rotation, etc.  Can act on geometry, paths, or can return a matrix.
  20// FileFootnotes: STD=Included in std.scad
  21//////////////////////////////////////////////////////////////////////
  22
  23// Section: Affine Transformations
  24//   OpenSCAD provides various built-in modules to transform geometry by
  25//   translation, scaling, rotation, and mirroring.  All of these operations
  26//   are affine transformations.  A three-dimensional affine transformation
  27//   can be represented by a 4x4 matrix.  The transformation shortcuts in this
  28//   file generally have three modes of operation.  They can operate
  29//   directly on geometry like their OpenSCAD built-in equivalents.  For example,
  30//   `left(10) cube()`.  They can operate on a list of points (or various other
  31//   types of geometric data).  For example, operating on a list of points: `points = left(10, [[1,2,3],[4,5,6]])`.
  32//   The third option is that the shortcut can return the transformation matrix
  33//   corresponding to its action.  For example, `M=left(10)`.
  34//   .
  35//   This capability allows you to store and manipulate transformations, and can
  36//   be useful in more advanced modeling.  You can multiply these matrices
  37//   together, analogously to applying a sequence of operations with the
  38//   built-in transformations.  So you can write `zrot(37)left(5)cube()`
  39//   to perform two operations on a cube.  You can also store
  40//   that same transformation by multiplying the matrices together: `M = zrot(37) * left(5)`.
  41//   Note that the order is exactly the same as the order used to apply the transformation.
  42//   .
  43//   Suppose you have constructed `M` as above.  What now?  You can use
  44//   the OpensCAD built-in `multmatrix` to apply it to some geometry:  `multmatrix(M) cube()`.
  45//   Alternative you can use the BOSL2 function `apply` to apply `M` to a point, a list
  46//   of points, a bezier patch, or a VNF.  For example, `points = apply(M, [[3,4,5],[5,6,7]])`.
  47//   Note that the `apply` function can work on both 2D and 3D data, but if you want to
  48//   operate on 2D data, you must choose transformations that don't modify z
  49//   .
  50//   You can use matrices as described above without understanding the details, just
  51//   treating a matrix as a box that stores a transformation.  The OpenSCAD manual section for multmatrix
  52//   gives some details of how this works.  We'll elaborate a bit more below.  An affine transformation
  53//   matrix for three dimensional data is a 4x4 matrix.  The top left 3x3 portion gives the linear
  54//   transformation to apply to the data.  For example, it could be a rotation or scaling, or combination of both.
  55//   The 3x1 column at the top right gives the translation to apply.  The bottom row should be `[0,0,0,1]`.  That
  56//   bottom row is only present to enable
  57//   the matrices to be multiplied together.  OpenSCAD ignores it and in fact `multmatrix` will
  58//   accept a 3x4 matrix, where that row is missing.  In order for a matrix to act on a point you have to
  59//   augment the point with an extra 1, making it a length 4 vector.  In OpenSCAD you can then compute the
  60//   the affine transformed point as `tran_point = M * point`.  However, this syntax hides a complication that
  61//   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
  62//   as row vectors on the list.  In order to transform such a list, it needs to be muliplied on the right
  63//   side, not the left side.
  64
  65
  66
  67_NO_ARG = [true,[123232345],false];
  68
  69
  70//////////////////////////////////////////////////////////////////////
  71// Section: Translations
  72//////////////////////////////////////////////////////////////////////
  73
  74// Function&Module: move()
  75// Aliases: translate()
  76//
  77// Usage: As Module
  78//   move(v) CHILDREN;
  79// Usage: As a function to translate points, VNF, or Bezier patch
  80//   pts = move(v, p);
  81//   pts = move(STRING, p);
  82// Usage: Get Translation Matrix
  83//   mat = move(v);
  84//
  85// Topics: Affine, Matrices, Transforms, Translation
  86// See Also: left(), right(), fwd(), back(), down(), up(), spherical_to_xyz(), altaz_to_xyz(), cylindrical_to_xyz(), polar_to_xy()
  87//
  88// Description:
  89//   Translates position by the given amount.
  90//   * Called as a module, moves/translates all children.
  91//   * Called as a function with a point in the `p` argument, returns the translated point.
  92//   * Called as a function with a list of points in the `p` argument, returns the translated list of points.
  93//   * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the translated patch.
  94//   * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the translated VNF.
  95//   * Called as a function with the `p` argument, returns the translated point or list of points.
  96//   * Called as a function with the `p` argument set to a VNF or a polygon and `v` set to "centroid", "mean" or "box", translates the argument to the centroid, mean, or bounding box center respectively.
  97//   * Called as a function without a `p` argument, returns a 4x4 translation matrix for operating on 3D data.  
  98//
  99// Arguments:
 100//   v = An [X,Y,Z] vector to translate by.  For function form with `p` a point list or VNF, can be "centroid", "mean" or "box".  
 101//   p = Either a point, or a list of points to be translated when used as a function.
 102//
 103// Example:
 104//   #sphere(d=10);
 105//   move([0,20,30]) sphere(d=10);
 106//
 107// Example: You can move a 3D object with a 2D vector.  The Z component is treated at zero.  
 108//   #sphere(d=10);
 109//   move([-10,-5]) sphere(d=10);
 110//
 111// Example(2D): Move to centroid
 112//   polygon(move("centroid", right_triangle([10,4])));
 113//
 114// Example(FlatSpin): Using Altitude-Azimuth Coordinates
 115//   #sphere(d=10);
 116//   move(altaz_to_xyz(30,90,20)) sphere(d=10);
 117//
 118// Example(FlatSpin): Using Spherical Coordinates
 119//   #sphere(d=10);
 120//   move(spherical_to_xyz(20,45,30)) sphere(d=10);
 121//
 122// Example(2D):
 123//   path = square([50,30], center=true);
 124//   #stroke(path, closed=true);
 125//   stroke(move([10,20],p=path), closed=true);
 126//
 127// Example(NORENDER):
 128//   pt1 = move([0,20,30], p=[15,23,42]);       // Returns: [15, 43, 72]
 129//   pt2 = move([0,3,1], p=[[1,2,3],[4,5,6]]);  // Returns: [[1,5,4], [4,8,7]]
 130//   mat2d = move([2,3]);    // Returns: [[1,0,2],[0,1,3],[0,0,1]]
 131//   mat3d = move([2,3,4]);  // Returns: [[1,0,0,2],[0,1,0,3],[0,0,1,4],[0,0,0,1]]
 132module move(v=[0,0,0], p) {
 133    req_children($children);  
 134    assert(!is_string(v),"Module form of `move()` does not accept string `v` arguments");
 135    assert(is_undef(p), "Module form `move()` does not accept p= argument.");
 136    assert(is_vector(v) && (len(v)==3 || len(v)==2), "Invalid value for `v`")
 137    translate(point3d(v)) children();
 138}
 139
 140function move(v=[0,0,0], p=_NO_ARG) =
 141    is_string(v) ? (
 142        assert(is_vnf(p) || is_path(p),"String movements only work with point lists and VNFs")
 143        let(
 144             center = v=="centroid" ? centroid(p)
 145                    : v=="mean" ? mean(p)
 146                    : v=="box" ? mean(pointlist_bounds(p))
 147                    : assert(false,str("Unknown string movement ",v))
 148        )
 149        move(-center,p=p)
 150      )
 151    :
 152    assert(is_vector(v) && (len(v)==3 || len(v)==2), "Invalid value for `v`")
 153    let(
 154        m = affine3d_translate(point3d(v))
 155    )
 156    p==_NO_ARG ? m : apply(m, p);
 157
 158function translate(v=[0,0,0], p=_NO_ARG) = move(v=v, p=p);
 159
 160
 161// Function&Module: left()
 162//
 163// Usage: As Module
 164//   left(x) CHILDREN;
 165// Usage: Translate Points
 166//   pts = left(x, p);
 167// Usage: Get Translation Matrix
 168//   mat = left(x);
 169//
 170// Topics: Affine, Matrices, Transforms, Translation
 171// See Also: move(), right(), fwd(), back(), down(), up()
 172//
 173// Description:
 174//   If called as a module, moves/translates all children left (in the X- direction) by the given amount.
 175//   If called as a function with the `p` argument, returns the translated point or list of points.
 176//   If called as a function without the `p` argument, returns an affine3d translation matrix.
 177//
 178// Arguments:
 179//   x = Scalar amount to move left.
 180//   p = Either a point, or a list of points to be translated when used as a function.
 181//
 182// Example:
 183//   #sphere(d=10);
 184//   left(20) sphere(d=10);
 185//
 186// Example(NORENDER):
 187//   pt1 = left(20, p=[23,42]);           // Returns: [3,42]
 188//   pt2 = left(20, p=[15,23,42]);        // Returns: [-5,23,42]
 189//   pt3 = left(3, p=[[1,2,3],[4,5,6]]);  // Returns: [[-2,2,3], [1,5,6]]
 190//   mat3d = left(4);  // Returns: [[1,0,0,-4],[0,1,0,0],[0,0,1,0],[0,0,0,1]]
 191module left(x=0, p) {
 192    req_children($children);    
 193    assert(is_undef(p), "Module form `left()` does not accept p= argument.");
 194    assert(is_finite(x), "Invalid number")
 195    translate([-x,0,0]) children();
 196}
 197
 198function left(x=0, p=_NO_ARG) =
 199    assert(is_finite(x), "Invalid number")
 200    move([-x,0,0],p=p);
 201
 202
 203// Function&Module: right()
 204// Aliases: xmove()
 205//
 206// Usage: As Module
 207//   right(x) CHILDREN;
 208// Usage: Translate Points
 209//   pts = right(x, p);
 210// Usage: Get Translation Matrix
 211//   mat = right(x);
 212//
 213// Topics: Affine, Matrices, Transforms, Translation
 214// See Also: move(), left(), fwd(), back(), down(), up()
 215//
 216// Description:
 217//   If called as a module, moves/translates all children right (in the X+ direction) by the given amount.
 218//   If called as a function with the `p` argument, returns the translated point or list of points.
 219//   If called as a function without the `p` argument, returns an affine3d translation matrix.
 220//
 221// Arguments:
 222//   x = Scalar amount to move right.
 223//   p = Either a point, or a list of points to be translated when used as a function.
 224//
 225// Example:
 226//   #sphere(d=10);
 227//   right(20) sphere(d=10);
 228//
 229// Example(NORENDER):
 230//   pt1 = right(20, p=[23,42]);           // Returns: [43,42]
 231//   pt2 = right(20, p=[15,23,42]);        // Returns: [35,23,42]
 232//   pt3 = right(3, p=[[1,2,3],[4,5,6]]);  // Returns: [[4,2,3], [7,5,6]]
 233//   mat3d = right(4);  // Returns: [[1,0,0,4],[0,1,0,0],[0,0,1,0],[0,0,0,1]]
 234module right(x=0, p) {
 235    req_children($children);    
 236    assert(is_undef(p), "Module form `right()` does not accept p= argument.");
 237    assert(is_finite(x), "Invalid number")
 238    translate([x,0,0]) children();
 239}
 240
 241function right(x=0, p=_NO_ARG) =
 242    assert(is_finite(x), "Invalid number")
 243    move([x,0,0],p=p);
 244
 245module xmove(x=0, p) {
 246    req_children($children);    
 247    assert(is_undef(p), "Module form `xmove()` does not accept p= argument.");
 248    assert(is_finite(x), "Invalid number")
 249    translate([x,0,0]) children();
 250}
 251
 252function xmove(x=0, p=_NO_ARG) =
 253    assert(is_finite(x), "Invalid number")
 254    move([x,0,0],p=p);
 255
 256
 257// Function&Module: fwd()
 258//
 259// Usage: As Module
 260//   fwd(y) CHILDREN;
 261// Usage: Translate Points
 262//   pts = fwd(y, p);
 263// Usage: Get Translation Matrix
 264//   mat = fwd(y);
 265//
 266// Topics: Affine, Matrices, Transforms, Translation
 267// See Also: move(), left(), right(), back(), down(), up()
 268//
 269// Description:
 270//   If called as a module, moves/translates all children forward (in the Y- direction) by the given amount.
 271//   If called as a function with the `p` argument, returns the translated point or list of points.
 272//   If called as a function without the `p` argument, returns an affine3d translation matrix.
 273//
 274// Arguments:
 275//   y = Scalar amount to move forward.
 276//   p = Either a point, or a list of points to be translated when used as a function.
 277//
 278// Example:
 279//   #sphere(d=10);
 280//   fwd(20) sphere(d=10);
 281//
 282// Example(NORENDER):
 283//   pt1 = fwd(20, p=[23,42]);           // Returns: [23,22]
 284//   pt2 = fwd(20, p=[15,23,42]);        // Returns: [15,3,42]
 285//   pt3 = fwd(3, p=[[1,2,3],[4,5,6]]);  // Returns: [[1,-1,3], [4,2,6]]
 286//   mat3d = fwd(4);  // Returns: [[1,0,0,0],[0,1,0,-4],[0,0,1,0],[0,0,0,1]]
 287module fwd(y=0, p) {
 288    req_children($children);    
 289    assert(is_undef(p), "Module form `fwd()` does not accept p= argument.");
 290    assert(is_finite(y), "Invalid number")
 291    translate([0,-y,0]) children();
 292}
 293
 294function fwd(y=0, p=_NO_ARG) =
 295    assert(is_finite(y), "Invalid number")
 296    move([0,-y,0],p=p);
 297
 298
 299// Function&Module: back()
 300// Aliases: ymove()
 301//
 302// Usage: As Module
 303//   back(y) CHILDREN;
 304// Usage: Translate Points
 305//   pts = back(y, p);
 306// Usage: Get Translation Matrix
 307//   mat = back(y);
 308//
 309// Topics: Affine, Matrices, Transforms, Translation
 310// See Also: move(), left(), right(), fwd(), down(), up()
 311//
 312// Description:
 313//   If called as a module, moves/translates all children back (in the Y+ direction) by the given amount.
 314//   If called as a function with the `p` argument, returns the translated point or list of points.
 315//   If called as a function without the `p` argument, returns an affine3d translation matrix.
 316//
 317// Arguments:
 318//   y = Scalar amount to move back.
 319//   p = Either a point, or a list of points to be translated when used as a function.
 320//
 321// Example:
 322//   #sphere(d=10);
 323//   back(20) sphere(d=10);
 324//
 325// Example(NORENDER):
 326//   pt1 = back(20, p=[23,42]);           // Returns: [23,62]
 327//   pt2 = back(20, p=[15,23,42]);        // Returns: [15,43,42]
 328//   pt3 = back(3, p=[[1,2,3],[4,5,6]]);  // Returns: [[1,5,3], [4,8,6]]
 329//   mat3d = back(4);  // Returns: [[1,0,0,0],[0,1,0,4],[0,0,1,0],[0,0,0,1]]
 330module back(y=0, p) {
 331    req_children($children);    
 332    assert(is_undef(p), "Module form `back()` does not accept p= argument.");
 333    assert(is_finite(y), "Invalid number")
 334    translate([0,y,0]) children();
 335}
 336
 337function back(y=0,p=_NO_ARG) =
 338    assert(is_finite(y), "Invalid number")
 339    move([0,y,0],p=p);
 340
 341module ymove(y=0, p) {
 342    req_children($children);    
 343    assert(is_undef(p), "Module form `ymove()` does not accept p= argument.");
 344    assert(is_finite(y), "Invalid number")
 345    translate([0,y,0]) children();
 346}
 347
 348function ymove(y=0,p=_NO_ARG) =
 349    assert(is_finite(y), "Invalid number")
 350    move([0,y,0],p=p);
 351
 352
 353// Function&Module: down()
 354//
 355// Usage: As Module
 356//   down(z) CHILDREN;
 357// Usage: Translate Points
 358//   pts = down(z, p);
 359// Usage: Get Translation Matrix
 360//   mat = down(z);
 361//
 362// Topics: Affine, Matrices, Transforms, Translation
 363// See Also: move(), left(), right(), fwd(), back(), up()
 364//
 365// Description:
 366//   If called as a module, moves/translates all children down (in the Z- direction) by the given amount.
 367//   If called as a function with the `p` argument, returns the translated point or list of points.
 368//   If called as a function without the `p` argument, returns an affine3d translation matrix.
 369//
 370// Arguments:
 371//   z = Scalar amount to move down.
 372//   p = Either a point, or a list of points to be translated when used as a function.
 373//
 374// Example:
 375//   #sphere(d=10);
 376//   down(20) sphere(d=10);
 377//
 378// Example(NORENDER):
 379//   pt1 = down(20, p=[15,23,42]);        // Returns: [15,23,22]
 380//   pt2 = down(3, p=[[1,2,3],[4,5,6]]);  // Returns: [[1,2,0], [4,5,3]]
 381//   mat3d = down(4);  // Returns: [[1,0,0,0],[0,1,0,0],[0,0,1,-4],[0,0,0,1]]
 382module down(z=0, p) {
 383    req_children($children);    
 384    assert(is_undef(p), "Module form `down()` does not accept p= argument.");
 385    translate([0,0,-z]) children();
 386}
 387
 388function down(z=0, p=_NO_ARG) =
 389    assert(is_finite(z), "Invalid number")
 390    move([0,0,-z],p=p);
 391
 392
 393// Function&Module: up()
 394// Aliases: zmove()
 395//
 396// Usage: As Module
 397//   up(z) CHILDREN;
 398// Usage: Translate Points
 399//   pts = up(z, p);
 400// Usage: Get Translation Matrix
 401//   mat = up(z);
 402//
 403// Topics: Affine, Matrices, Transforms, Translation
 404// See Also: move(), left(), right(), fwd(), back(), down()
 405//
 406// Description:
 407//   If called as a module, moves/translates all children up (in the Z+ direction) by the given amount.
 408//   If called as a function with the `p` argument, returns the translated point or list of points.
 409//   If called as a function without the `p` argument, returns an affine3d translation matrix.
 410//
 411// Arguments:
 412//   z = Scalar amount to move up.
 413//   p = Either a point, or a list of points to be translated when used as a function.
 414//
 415// Example:
 416//   #sphere(d=10);
 417//   up(20) sphere(d=10);
 418//
 419// Example(NORENDER):
 420//   pt1 = up(20, p=[15,23,42]);        // Returns: [15,23,62]
 421//   pt2 = up(3, p=[[1,2,3],[4,5,6]]);  // Returns: [[1,2,6], [4,5,9]]
 422//   mat3d = up(4);  // Returns: [[1,0,0,0],[0,1,0,0],[0,0,1,4],[0,0,0,1]]
 423module up(z=0, p) {
 424    req_children($children);      
 425    assert(is_undef(p), "Module form `up()` does not accept p= argument.");
 426    assert(is_finite(z), "Invalid number");
 427    translate([0,0,z]) children();
 428}
 429
 430function up(z=0, p=_NO_ARG) =
 431    assert(is_finite(z), "Invalid number")
 432    move([0,0,z],p=p);
 433
 434module zmove(z=0, p) {
 435    req_children($children);      
 436    assert(is_undef(p), "Module form `zmove()` does not accept p= argument.");
 437    assert(is_finite(z), "Invalid number");
 438    translate([0,0,z]) children();
 439}
 440
 441function zmove(z=0, p=_NO_ARG) =
 442    assert(is_finite(z), "Invalid number")
 443    move([0,0,z],p=p);
 444
 445
 446
 447//////////////////////////////////////////////////////////////////////
 448// Section: Rotations
 449//////////////////////////////////////////////////////////////////////
 450
 451
 452// Function&Module: rot()
 453//
 454// Usage: As a Module
 455//   rot(a, [cp=], [reverse=]) CHILDREN;
 456//   rot([X,Y,Z], [cp=], [reverse=]) CHILDREN;
 457//   rot(a, v, [cp=], [reverse=]) CHILDREN;
 458//   rot(from=, to=, [a=], [reverse=]) CHILDREN;
 459// Usage: As a Function to transform data in `p`
 460//   pts = rot(a, p=, [cp=], [reverse=]);
 461//   pts = rot([X,Y,Z], p=, [cp=], [reverse=]);
 462//   pts = rot(a, v, p=, [cp=], [reverse=]);
 463//   pts = rot([a], from=, to=, p=, [reverse=]);
 464// Usage: As a Function to return a transform matrix
 465//   M = rot(a, [cp=], [reverse=]);
 466//   M = rot([X,Y,Z], [cp=], [reverse=]);
 467//   M = rot(a, v, [cp=], [reverse=]);
 468//   M = rot(from=, to=, [a=], [reverse=]);
 469//
 470// Topics: Affine, Matrices, Transforms, Rotation
 471// See Also: xrot(), yrot(), zrot()
 472//
 473// Description:
 474//   This is a shorthand version of the built-in `rotate()`, and operates similarly, with a few additional capabilities.
 475//   You can specify the rotation to perform in one of several ways:
 476//   * `rot(30)` or `rot(a=30)` rotates 30 degrees around the Z axis.
 477//   * `rot([20,30,40])` or `rot(a=[20,30,40])` rotates 20 degrees around the X axis, then 30 degrees around the Y axis, then 40 degrees around the Z axis.
 478//   * `rot(30, [1,1,0])` or `rot(a=30, v=[1,1,0])` rotates 30 degrees around the axis vector `[1,1,0]`.
 479//   * `rot(from=[0,0,1], to=[1,0,0])` rotates the `from` vector to line up with the `to` vector, in this case the top to the right and hence equivalent to `rot(a=90,v=[0,1,0]`.
 480//   * `rot(from=[0,1,1], to=[1,1,0], a=45)` rotates 45 degrees around the `from` vector ([0,1,1]) and then rotates the `from` vector to align with the `to` vector.  Equivalent to `rot(from=[0,1,1],to=[1,1,0]) rot(a=45,v=[0,1,1])`.  You can also regard `a` as as post-rotation around the `to` vector.  For this form, `a` must be a scalar.
 481//   * If the `cp` centerpoint argument is given, then rotations are performed around that centerpoint.  So `rot(args...,cp=[1,2,3])` is equivalent to `move(-[1,2,3])rot(args...)move([1,2,3])`.
 482//   * If the `reverse` argument is true, then the rotations performed will be exactly reversed.
 483//   .
 484//   The behavior and return value varies depending on how `rot()` is called:
 485//   * Called as a module, rotates all children.
 486//   * Called as a function with a `p` argument containing a point, returns the rotated point.
 487//   * Called as a function with a `p` argument containing a list of points, returns the list of rotated points.
 488//   * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the rotated patch.
 489//   * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the rotated VNF.
 490//   * Called as a function without a `p` argument, returns the affine3d rotational matrix.
 491//   Note that unlike almost all the other transformations, the `p` argument must be given as a named argument.
 492//
 493// Arguments:
 494//   a = Scalar angle or vector of XYZ rotation angles to rotate by, in degrees.  If you use the `from` and `to` arguments then `a` must be a scalar.  Default: `0`
 495//   v = vector for the axis of rotation.  Default: [0,0,1] or UP
 496//   ---
 497//   cp = centerpoint to rotate around. Default: [0,0,0]
 498//   from = Starting vector for vector-based rotations.
 499//   to = Target vector for vector-based rotations.
 500//   reverse = If true, exactly reverses the rotation, including axis rotation ordering.  Default: false
 501//   p = If called as a function, this contains data to rotate: a point, list of points, bezier patch or VNF.
 502//
 503// Example:
 504//   #cube([2,4,9]);
 505//   rot([30,60,0], cp=[0,0,9]) cube([2,4,9]);
 506//
 507// Example:
 508//   #cube([2,4,9]);
 509//   rot(30, v=[1,1,0], cp=[0,0,9]) cube([2,4,9]);
 510//
 511// Example:
 512//   #cube([2,4,9]);
 513//   rot(from=UP, to=LEFT+BACK) cube([2,4,9]);
 514//
 515// Example(2D):
 516//   path = square([50,30], center=true);
 517//   #stroke(path, closed=true);
 518//   stroke(rot(30,p=path), closed=true);
 519module rot(a=0, v, cp, from, to, reverse=false)
 520{
 521    req_children($children);        
 522    m = rot(a=a, v=v, cp=cp, from=from, to=to, reverse=reverse);
 523    multmatrix(m) children();
 524}
 525
 526function rot(a=0, v, cp, from, to, reverse=false, p=_NO_ARG, _m) =
 527    assert(is_undef(from)==is_undef(to), "from and to must be specified together.")
 528    assert(is_undef(from) || is_vector(from, zero=false), "'from' must be a non-zero vector.")
 529    assert(is_undef(to) || is_vector(to, zero=false), "'to' must be a non-zero vector.")
 530    assert(is_undef(v) || is_vector(v, zero=false), "'v' must be a non-zero vector.")
 531    assert(is_undef(cp) || is_vector(cp), "'cp' must be a vector.")
 532    assert(is_finite(a) || is_vector(a), "'a' must be a finite scalar or a vector.")
 533    assert(is_bool(reverse))
 534    let(
 535        m = let(
 536                from = is_undef(from)? undef : point3d(from),
 537                to = is_undef(to)? undef : point3d(to),
 538                cp = is_undef(cp)? undef : point3d(cp),
 539                m1 = !is_undef(from) ?
 540                        assert(is_num(a))
 541                        affine3d_rot_from_to(from,to) * affine3d_rot_by_axis(from,a)
 542                   : !is_undef(v)?
 543                        assert(is_num(a))
 544                        affine3d_rot_by_axis(v,a)
 545                   : is_num(a) ? affine3d_zrot(a)
 546                   : affine3d_zrot(a.z) * affine3d_yrot(a.y) * affine3d_xrot(a.x),
 547                m2 = is_undef(cp)? m1 : (move(cp) * m1 * move(-cp)),
 548                m3 = reverse? rot_inverse(m2) : m2
 549            ) m3
 550    )
 551    p==_NO_ARG ? m : apply(m, p);
 552
 553
 554
 555
 556// Function&Module: xrot()
 557//
 558// Usage: As Module
 559//   xrot(a, [cp=]) CHILDREN;
 560// Usage: As a function to rotate points
 561//   rotated = xrot(a, p, [cp=]);
 562// Usage: As a function to return rotation matrix
 563//   mat = xrot(a, [cp=]);
 564//
 565// Topics: Affine, Matrices, Transforms, Rotation
 566// See Also: rot(), yrot(), zrot()
 567//
 568// Description:
 569//   Rotates around the X axis by the given number of degrees.  If `cp` is given, rotations are performed around that centerpoint.
 570//   * Called as a module, rotates all children.
 571//   * Called as a function with a `p` argument containing a point, returns the rotated point.
 572//   * Called as a function with a `p` argument containing a list of points, returns the list of rotated points.
 573//   * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the rotated patch.
 574//   * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the rotated VNF.
 575//   * Called as a function without a `p` argument, returns the affine3d rotational matrix.
 576//
 577// Arguments:
 578//   a = angle to rotate by in degrees.
 579//   p = If called as a function, this contains data to rotate: a point, list of points, bezier patch or VNF.
 580//   ---
 581//   cp = centerpoint to rotate around. Default: [0,0,0]
 582//
 583// Example:
 584//   #cylinder(h=50, r=10, center=true);
 585//   xrot(90) cylinder(h=50, r=10, center=true);
 586module xrot(a=0, p, cp)
 587{
 588    req_children($children);          
 589    assert(is_undef(p), "Module form `xrot()` does not accept p= argument.");
 590    if (a==0) {
 591        children();  // May be slightly faster?
 592    } else if (!is_undef(cp)) {
 593        translate(cp) rotate([a, 0, 0]) translate(-cp) children();
 594    } else {
 595        rotate([a, 0, 0]) children();
 596    }
 597}
 598
 599function xrot(a=0, p=_NO_ARG, cp) = rot([a,0,0], cp=cp, p=p);
 600
 601
 602// Function&Module: yrot()
 603//
 604// Usage: As Module
 605//   yrot(a, [cp=]) CHILDREN;
 606// Usage: Rotate Points
 607//   rotated = yrot(a, p, [cp=]);
 608// Usage: Get Rotation Matrix
 609//   mat = yrot(a, [cp=]);
 610//
 611// Topics: Affine, Matrices, Transforms, Rotation
 612// See Also: rot(), xrot(), zrot()
 613//
 614// Description:
 615//   Rotates around the Y axis by the given number of degrees.  If `cp` is given, rotations are performed around that centerpoint.
 616//   * Called as a module, rotates all children.
 617//   * Called as a function with a `p` argument containing a point, returns the rotated point.
 618//   * Called as a function with a `p` argument containing a list of points, returns the list of rotated points.
 619//   * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the rotated patch.
 620//   * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the rotated VNF.
 621//   * Called as a function without a `p` argument, returns the affine3d rotational matrix.
 622//
 623// Arguments:
 624//   a = angle to rotate by in degrees.
 625//   p = If called as a function, this contains data to rotate: a point, list of points, bezier patch or VNF.
 626//   ---
 627//   cp = centerpoint to rotate around. Default: [0,0,0]
 628//
 629// Example:
 630//   #cylinder(h=50, r=10, center=true);
 631//   yrot(90) cylinder(h=50, r=10, center=true);
 632module yrot(a=0, p, cp)
 633{
 634    req_children($children);  
 635    assert(is_undef(p), "Module form `yrot()` does not accept p= argument.");
 636    if (a==0) {
 637        children();  // May be slightly faster?
 638    } else if (!is_undef(cp)) {
 639        translate(cp) rotate([0, a, 0]) translate(-cp) children();
 640    } else {
 641        rotate([0, a, 0]) children();
 642    }
 643}
 644
 645function yrot(a=0, p=_NO_ARG, cp) = rot([0,a,0], cp=cp, p=p);
 646
 647
 648// Function&Module: zrot()
 649//
 650// Usage: As Module
 651//   zrot(a, [cp=]) CHILDREN;
 652// Usage: As Function to rotate points
 653//   rotated = zrot(a, p, [cp=]);
 654// Usage: As Function to return rotation matrix
 655//   mat = zrot(a, [cp=]);
 656//
 657// Topics: Affine, Matrices, Transforms, Rotation
 658// See Also: rot(), xrot(), yrot()
 659//
 660// Description:
 661//   Rotates around the Z axis by the given number of degrees.  If `cp` is given, rotations are performed around that centerpoint.
 662//   * Called as a module, rotates all children.
 663//   * Called as a function with a `p` argument containing a point, returns the rotated point.
 664//   * Called as a function with a `p` argument containing a list of points, returns the list of rotated points.
 665//   * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the rotated patch.
 666//   * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the rotated VNF.
 667//   * Called as a function without a `p` argument, returns the affine3d rotational matrix.
 668//
 669// Arguments:
 670//   a = angle to rotate by in degrees.
 671//   p = If called as a function, this contains data to rotate: a point, list of points, bezier patch or VNF.
 672//   ---
 673//   cp = centerpoint to rotate around. Default: [0,0,0]
 674//
 675// Example:
 676//   #cube(size=[60,20,40], center=true);
 677//   zrot(90) cube(size=[60,20,40], center=true);
 678module zrot(a=0, p, cp)
 679{
 680    req_children($children);    
 681    assert(is_undef(p), "Module form `zrot()` does not accept p= argument.");
 682    if (a==0) {
 683        children();  // May be slightly faster?
 684    } else if (!is_undef(cp)) {
 685        translate(cp) rotate(a) translate(-cp) children();
 686    } else {
 687        rotate(a) children();
 688    }
 689}
 690
 691function zrot(a=0, p=_NO_ARG, cp) = rot(a, cp=cp, p=p);
 692
 693
 694
 695//////////////////////////////////////////////////////////////////////
 696// Section: Scaling
 697//////////////////////////////////////////////////////////////////////
 698
 699
 700// Function&Module: scale()
 701// Usage: As Module
 702//   scale(SCALAR) CHILDREN;
 703//   scale([X,Y,Z]) CHILDREN;
 704// Usage: Scale Points
 705//   pts = scale(v, p, [cp=]);
 706// Usage: Get Scaling Matrix
 707//   mat = scale(v, [cp=]);
 708// Topics: Affine, Matrices, Transforms, Scaling
 709// See Also: xscale(), yscale(), zscale()
 710// Description:
 711//   Scales by the [X,Y,Z] scaling factors given in `v`.  If `v` is given as a scalar number, all axes are scaled uniformly by that amount.
 712//   * Called as the built-in module, scales all children.
 713//   * Called as a function with a point in the `p` argument, returns the scaled point.
 714//   * Called as a function with a list of points in the `p` argument, returns the list of scaled points.
 715//   * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the scaled patch.
 716//   * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the scaled VNF.
 717//   * Called as a function without a `p` argument, and a 2D list of scaling factors in `v`, returns an affine2d scaling matrix.
 718//   * Called as a function without a `p` argument, and a 3D list of scaling factors in `v`, returns an affine3d scaling matrix.
 719// Arguments:
 720//   v = Either a numeric uniform scaling factor, or a list of [X,Y,Z] scaling factors.  Default: 1
 721//   p = If called as a function, the point or list of points to scale.
 722//   ---
 723//   cp = If given, centers the scaling on the point `cp`.
 724// Example(NORENDER):
 725//   pt1 = scale(3, p=[3,1,4]);        // Returns: [9,3,12]
 726//   pt2 = scale([2,3,4], p=[3,1,4]);  // Returns: [6,3,16]
 727//   pt3 = scale([2,3,4], p=[[1,2,3],[4,5,6]]);  // Returns: [[2,6,12], [8,15,24]]
 728//   mat2d = scale([2,3]);    // Returns: [[2,0,0],[0,3,0],[0,0,1]]
 729//   mat3d = scale([2,3,4]);  // Returns: [[2,0,0,0],[0,3,0,0],[0,0,4,0],[0,0,0,1]]
 730// Example(2D):
 731//   path = circle(d=50,$fn=12);
 732//   #stroke(path,closed=true);
 733//   stroke(scale([1.5,3],p=path),closed=true);
 734function scale(v=1, p=_NO_ARG, cp=[0,0,0]) =
 735    assert(is_num(v) || is_vector(v),"Invalid scale")
 736    assert(p==_NO_ARG || is_list(p),"Invalid point list")
 737    assert(is_vector(cp))
 738    let(
 739        v = is_num(v)? [v,v,v] : v,
 740        m = cp==[0,0,0]
 741          ? affine3d_scale(v)
 742          : affine3d_translate(point3d(cp))
 743            * affine3d_scale(v)
 744            * affine3d_translate(point3d(-cp))
 745    )
 746    p==_NO_ARG? m : apply(m, p) ;
 747
 748
 749// Function&Module: xscale()
 750//
 751//
 752// Usage: As Module
 753//   xscale(x, [cp=]) CHILDREN;
 754// Usage: Scale Points
 755//   scaled = xscale(x, p, [cp=]);
 756// Usage: Get Affine Matrix
 757//   mat = xscale(x, [cp=]);
 758//
 759// Topics: Affine, Matrices, Transforms, Scaling
 760// See Also: scale(), yscale(), zscale()
 761//
 762// Description:
 763//   Scales along the X axis by the scaling factor `x`.
 764//   * Called as the built-in module, scales all children.
 765//   * Called as a function with a point in the `p` argument, returns the scaled point.
 766//   * Called as a function with a list of points in the `p` argument, returns the list of scaled points.
 767//   * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the scaled patch.
 768//   * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the scaled VNF.
 769//   * Called as a function without a `p` argument, and a 2D list of scaling factors in `v`, returns an affine2d scaling matrix.
 770//   * Called as a function without a `p` argument, and a 3D list of scaling factors in `v`, returns an affine3d scaling matrix.
 771//
 772// Arguments:
 773//   x = Factor to scale by, along the X axis.
 774//   p = A point, path, bezier patch, or VNF to scale, when called as a function.
 775//   ---
 776//   cp = If given as a point, centers the scaling on the point `cp`.  If given as a scalar, centers scaling on the point `[cp,0,0]`
 777//
 778// Example: As Module
 779//   xscale(3) sphere(r=10);
 780//
 781// Example(2D): Scaling Points
 782//   path = circle(d=50,$fn=12);
 783//   #stroke(path,closed=true);
 784//   stroke(xscale(2,p=path),closed=true);
 785module xscale(x=1, p, cp=0) {
 786    req_children($children);      
 787    assert(is_undef(p), "Module form `xscale()` does not accept p= argument.");
 788    cp = is_num(cp)? [cp,0,0] : cp;
 789    if (cp == [0,0,0]) {
 790        scale([x,1,1]) children();
 791    } else {
 792        translate(cp) scale([x,1,1]) translate(-cp) children();
 793    }
 794}
 795
 796function xscale(x=1, p=_NO_ARG, cp=0) =
 797    assert(is_finite(x))
 798    assert(p==_NO_ARG || is_list(p))
 799    assert(is_finite(cp) || is_vector(cp))
 800    let( cp = is_num(cp)? [cp,0,0] : cp )
 801    scale([x,1,1], cp=cp, p=p);
 802
 803
 804// Function&Module: yscale()
 805//
 806// Usage: As Module
 807//   yscale(y, [cp=]) CHILDREN;
 808// Usage: Scale Points
 809//   scaled = yscale(y, p, [cp=]);
 810// Usage: Get Affine Matrix
 811//   mat = yscale(y, [cp=]);
 812//
 813// Topics: Affine, Matrices, Transforms, Scaling
 814// See Also: scale(), xscale(), zscale()
 815//
 816// Description:
 817//   Scales along the Y axis by the scaling factor `y`.
 818//   * Called as the built-in module, scales all children.
 819//   * Called as a function with a point in the `p` argument, returns the scaled point.
 820//   * Called as a function with a list of points in the `p` argument, returns the list of scaled points.
 821//   * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the scaled patch.
 822//   * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the scaled VNF.
 823//   * Called as a function without a `p` argument, and a 2D list of scaling factors in `v`, returns an affine2d scaling matrix.
 824//   * Called as a function without a `p` argument, and a 3D list of scaling factors in `v`, returns an affine3d scaling matrix.
 825//
 826// Arguments:
 827//   y = Factor to scale by, along the Y axis.
 828//   p = A point, path, bezier patch, or VNF to scale, when called as a function.
 829//   ---
 830//   cp = If given as a point, centers the scaling on the point `cp`.  If given as a scalar, centers scaling on the point `[0,cp,0]`
 831//
 832// Example: As Module
 833//   yscale(3) sphere(r=10);
 834//
 835// Example(2D): Scaling Points
 836//   path = circle(d=50,$fn=12);
 837//   #stroke(path,closed=true);
 838//   stroke(yscale(2,p=path),closed=true);
 839module yscale(y=1, p, cp=0) {
 840    req_children($children);      
 841    assert(is_undef(p), "Module form `yscale()` does not accept p= argument.");
 842    cp = is_num(cp)? [0,cp,0] : cp;
 843    if (cp == [0,0,0]) {
 844        scale([1,y,1]) children();
 845    } else {
 846        translate(cp) scale([1,y,1]) translate(-cp) children();
 847    }
 848}
 849
 850function yscale(y=1, p=_NO_ARG, cp=0) =
 851    assert(is_finite(y))
 852    assert(p==_NO_ARG || is_list(p))
 853    assert(is_finite(cp) || is_vector(cp))
 854    let( cp = is_num(cp)? [0,cp,0] : cp )
 855    scale([1,y,1], cp=cp, p=p);
 856
 857
 858// Function&Module: zscale()
 859//
 860// Usage: As Module
 861//   zscale(z, [cp=]) CHILDREN;
 862// Usage: Scale Points
 863//   scaled = zscale(z, p, [cp=]);
 864// Usage: Get Affine Matrix
 865//   mat = zscale(z, [cp=]);
 866//
 867// Topics: Affine, Matrices, Transforms, Scaling
 868// See Also: scale(), xscale(), yscale()
 869//
 870// Description:
 871//   Scales along the Z axis by the scaling factor `z`.
 872//   * Called as the built-in module, scales all children.
 873//   * Called as a function with a point in the `p` argument, returns the scaled point.
 874//   * Called as a function with a list of points in the `p` argument, returns the list of scaled points.
 875//   * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the scaled patch.
 876//   * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the scaled VNF.
 877//   * Called as a function without a `p` argument, and a 2D list of scaling factors in `v`, returns an affine2d scaling matrix.
 878//   * Called as a function without a `p` argument, and a 3D list of scaling factors in `v`, returns an affine3d scaling matrix.
 879//
 880// Arguments:
 881//   z = Factor to scale by, along the Z axis.
 882//   p = A point, path, bezier patch, or VNF to scale, when called as a function.
 883//   ---
 884//   cp = If given as a point, centers the scaling on the point `cp`.  If given as a scalar, centers scaling on the point `[0,0,cp]`
 885//
 886// Example: As Module
 887//   zscale(3) sphere(r=10);
 888//
 889// Example: Scaling Points
 890//   path = xrot(90,p=path3d(circle(d=50,$fn=12)));
 891//   #stroke(path,closed=true);
 892//   stroke(zscale(2,path),closed=true);
 893module zscale(z=1, p, cp=0) {
 894    req_children($children);      
 895    assert(is_undef(p), "Module form `zscale()` does not accept p= argument.");
 896    cp = is_num(cp)? [0,0,cp] : cp;
 897    if (cp == [0,0,0]) {
 898        scale([1,1,z]) children();
 899    } else {
 900        translate(cp) scale([1,1,z]) translate(-cp) children();
 901    }
 902}
 903
 904function zscale(z=1, p=_NO_ARG, cp=0) =
 905    assert(is_finite(z))
 906    assert(is_undef(p) || is_list(p))
 907    assert(is_finite(cp) || is_vector(cp))
 908    let( cp = is_num(cp)? [0,0,cp] : cp )
 909    scale([1,1,z], cp=cp, p=p);
 910
 911
 912//////////////////////////////////////////////////////////////////////
 913// Section: Reflection (Mirroring)
 914//////////////////////////////////////////////////////////////////////
 915
 916// Function&Module: mirror()
 917// Usage: As Module
 918//   mirror(v) CHILDREN;
 919// Usage: As Function
 920//   pt = mirror(v, p);
 921// Usage: Get Reflection/Mirror Matrix
 922//   mat = mirror(v);
 923// Topics: Affine, Matrices, Transforms, Reflection, Mirroring
 924// See Also: xflip(), yflip(), zflip()
 925// Description:
 926//   Mirrors/reflects across the plane or line whose normal vector is given in `v`.
 927//   * Called as the built-in module, mirrors all children across the line/plane.
 928//   * Called as a function with a point in the `p` argument, returns the point mirrored across the line/plane.
 929//   * Called as a function with a list of points in the `p` argument, returns the list of points, with each one mirrored across the line/plane.
 930//   * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the mirrored patch.
 931//   * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the mirrored VNF.
 932//   * Called as a function without a `p` argument, and with a 2D normal vector `v`, returns the affine2d 3x3 mirror matrix.
 933//   * Called as a function without a `p` argument, and with a 3D normal vector `v`, returns the affine3d 4x4 mirror matrix.
 934// Arguments:
 935//   v = The normal vector of the line or plane to mirror across.
 936//   p = If called as a function, the point or list of points to scale.
 937// Example:
 938//   n = [1,0,0];
 939//   module obj() right(20) rotate([0,15,-15]) cube([40,30,20]);
 940//   obj();
 941//   mirror(n) obj();
 942//   rot(a=atan2(n.y,n.x),from=UP,to=n) {
 943//       color("red") anchor_arrow(s=20, flag=false);
 944//       color("#7777") cube([75,75,0.1], center=true);
 945//   }
 946// Example:
 947//   n = [1,1,0];
 948//   module obj() right(20) rotate([0,15,-15]) cube([40,30,20]);
 949//   obj();
 950//   mirror(n) obj();
 951//   rot(a=atan2(n.y,n.x),from=UP,to=n) {
 952//       color("red") anchor_arrow(s=20, flag=false);
 953//       color("#7777") cube([75,75,0.1], center=true);
 954//   }
 955// Example:
 956//   n = [1,1,1];
 957//   module obj() right(20) rotate([0,15,-15]) cube([40,30,20]);
 958//   obj();
 959//   mirror(n) obj();
 960//   rot(a=atan2(n.y,n.x),from=UP,to=n) {
 961//       color("red") anchor_arrow(s=20, flag=false);
 962//       color("#7777") cube([75,75,0.1], center=true);
 963//   }
 964// Example(2D):
 965//   n = [0,1];
 966//   path = rot(30, p=square([50,30]));
 967//   color("gray") rot(from=[0,1],to=n) stroke([[-60,0],[60,0]]);
 968//   color("red") stroke([[0,0],10*n],endcap2="arrow2");
 969//   #stroke(path,closed=true);
 970//   stroke(mirror(n, p=path),closed=true);
 971// Example(2D):
 972//   n = [1,1];
 973//   path = rot(30, p=square([50,30]));
 974//   color("gray") rot(from=[0,1],to=n) stroke([[-60,0],[60,0]]);
 975//   color("red") stroke([[0,0],10*n],endcap2="arrow2");
 976//   #stroke(path,closed=true);
 977//   stroke(mirror(n, p=path),closed=true);
 978function mirror(v, p=_NO_ARG) =
 979    assert(is_vector(v))
 980    assert(p==_NO_ARG || is_list(p),"Invalid pointlist")
 981    let(m = len(v)==2? affine2d_mirror(v) : affine3d_mirror(v))
 982    p==_NO_ARG? m : apply(m,p);
 983
 984
 985// Function&Module: xflip()
 986//
 987// Usage: As Module
 988//   xflip([x=]) CHILDREN;
 989// Usage: As Function
 990//   pt = xflip(p, [x]);
 991// Usage: Get Affine Matrix
 992//   mat = xflip([x=]);
 993//
 994// Topics: Affine, Matrices, Transforms, Reflection, Mirroring
 995// See Also: mirror(), yflip(), zflip()
 996//
 997// Description:
 998//   Mirrors/reflects across the origin [0,0,0], along the X axis.  If `x` is given, reflects across [x,0,0] instead.
 999//   * Called as the built-in module, mirrors all children across the line/plane.
1000//   * Called as a function with a point in the `p` argument, returns the point mirrored across the line/plane.
1001//   * Called as a function with a list of points in the `p` argument, returns the list of points, with each one mirrored across the line/plane.
1002//   * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the mirrored patch.
1003//   * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the mirrored VNF.
1004//   * Called as a function without a `p` argument, returns the affine3d 4x4 mirror matrix.
1005//
1006// Arguments:
1007//   p = If given, the point, path, patch, or VNF to mirror.  Function use only.
1008//   x = The X coordinate of the plane of reflection.  Default: 0
1009//
1010// Example:
1011//   xflip() yrot(90) cylinder(d1=10, d2=0, h=20);
1012//   color("blue", 0.25) cube([0.01,15,15], center=true);
1013//   color("red", 0.333) yrot(90) cylinder(d1=10, d2=0, h=20);
1014//
1015// Example:
1016//   xflip(x=-5) yrot(90) cylinder(d1=10, d2=0, h=20);
1017//   color("blue", 0.25) left(5) cube([0.01,15,15], center=true);
1018//   color("red", 0.333) yrot(90) cylinder(d1=10, d2=0, h=20);
1019module xflip(p, x=0) {
1020    req_children($children);        
1021    assert(is_undef(p), "Module form `zflip()` does not accept p= argument.");
1022    translate([x,0,0])
1023        mirror([1,0,0])
1024            translate([-x,0,0]) children();
1025}
1026
1027function xflip(p=_NO_ARG, x=0) =
1028    assert(is_finite(x))
1029    assert(p==_NO_ARG || is_list(p),"Invalid point list")
1030    let( v = RIGHT )
1031    x == 0 ? mirror(v,p=p) :
1032    let(
1033        cp = x * v,
1034        m = move(cp) * mirror(v) * move(-cp)
1035    )
1036    p==_NO_ARG? m : apply(m, p);
1037
1038
1039// Function&Module: yflip()
1040//
1041// Usage: As Module
1042//   yflip([y=]) CHILDREN;
1043// Usage: As Function
1044//   pt = yflip(p, [y]);
1045// Usage: Get Affine Matrix
1046//   mat = yflip([y=]);
1047//
1048// Topics: Affine, Matrices, Transforms, Reflection, Mirroring
1049// See Also: mirror(), xflip(), zflip()
1050//
1051// Description:
1052//   Mirrors/reflects across the origin [0,0,0], along the Y axis.  If `y` is given, reflects across [0,y,0] instead.
1053//   * Called as the built-in module, mirrors all children across the line/plane.
1054//   * Called as a function with a point in the `p` argument, returns the point mirrored across the line/plane.
1055//   * Called as a function with a list of points in the `p` argument, returns the list of points, with each one mirrored across the line/plane.
1056//   * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the mirrored patch.
1057//   * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the mirrored VNF.
1058//   * Called as a function without a `p` argument, returns the affine3d 4x4 mirror matrix.
1059//
1060// Arguments:
1061//   p = If given, the point, path, patch, or VNF to mirror.  Function use only.
1062//   y = The Y coordinate of the plane of reflection.  Default: 0
1063//
1064// Example:
1065//   yflip() xrot(90) cylinder(d1=10, d2=0, h=20);
1066//   color("blue", 0.25) cube([15,0.01,15], center=true);
1067//   color("red", 0.333) xrot(90) cylinder(d1=10, d2=0, h=20);
1068//
1069// Example:
1070//   yflip(y=5) xrot(90) cylinder(d1=10, d2=0, h=20);
1071//   color("blue", 0.25) back(5) cube([15,0.01,15], center=true);
1072//   color("red", 0.333) xrot(90) cylinder(d1=10, d2=0, h=20);
1073module yflip(p, y=0) {
1074    req_children($children);          
1075    assert(is_undef(p), "Module form `yflip()` does not accept p= argument.");
1076    translate([0,y,0])
1077        mirror([0,1,0])
1078            translate([0,-y,0]) children();
1079}
1080
1081function yflip(p=_NO_ARG, y=0) =
1082    assert(is_finite(y))
1083    assert(p==_NO_ARG || is_list(p),"Invalid point list")
1084    let( v = BACK )
1085    y == 0 ? mirror(v,p=p) :
1086    let(
1087        cp = y * v,
1088        m = move(cp) * mirror(v) * move(-cp)
1089    )
1090    p==_NO_ARG? m : apply(m, p);
1091
1092
1093// Function&Module: zflip()
1094//
1095// Usage: As Module
1096//   zflip([z=]) CHILDREN;
1097// Usage: As Function
1098//   pt = zflip(p, [z]);
1099// Usage: Get Affine Matrix
1100//   mat = zflip([z=]);
1101//
1102// Topics: Affine, Matrices, Transforms, Reflection, Mirroring
1103// See Also: mirror(), xflip(), yflip()
1104//
1105// Description:
1106//   Mirrors/reflects across the origin [0,0,0], along the Z axis.  If `z` is given, reflects across [0,0,z] instead.
1107//   * Called as the built-in module, mirrors all children across the line/plane.
1108//   * Called as a function with a point in the `p` argument, returns the point mirrored across the line/plane.
1109//   * Called as a function with a list of points in the `p` argument, returns the list of points, with each one mirrored across the line/plane.
1110//   * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the mirrored patch.
1111//   * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the mirrored VNF.
1112//   * Called as a function without a `p` argument, returns the affine3d 4x4 mirror matrix.
1113//
1114// Arguments:
1115//   p = If given, the point, path, patch, or VNF to mirror.  Function use only.
1116//   z = The Z coordinate of the plane of reflection.  Default: 0
1117//
1118// Example:
1119//   zflip() cylinder(d1=10, d2=0, h=20);
1120//   color("blue", 0.25) cube([15,15,0.01], center=true);
1121//   color("red", 0.333) cylinder(d1=10, d2=0, h=20);
1122//
1123// Example:
1124//   zflip(z=-5) cylinder(d1=10, d2=0, h=20);
1125//   color("blue", 0.25) down(5) cube([15,15,0.01], center=true);
1126//   color("red", 0.333) cylinder(d1=10, d2=0, h=20);
1127module zflip(p, z=0) {
1128    req_children($children);          
1129    assert(is_undef(p), "Module form `zflip()` does not accept p= argument.");
1130    translate([0,0,z])
1131        mirror([0,0,1])
1132            translate([0,0,-z]) children();
1133}
1134
1135function zflip(p=_NO_ARG, z=0) =
1136    assert(is_finite(z))
1137    assert(p==_NO_ARG || is_list(p),"Invalid point list")
1138    z==0? mirror([0,0,1],p=p) :
1139    let(m = up(z) * mirror(UP) * down(z))
1140    p==_NO_ARG? m : apply(m, p);
1141
1142
1143//////////////////////////////////////////////////////////////////////
1144// Section: Other Transformations
1145//////////////////////////////////////////////////////////////////////
1146
1147// Function&Module: frame_map()
1148// Usage: As module
1149//   frame_map(v1, v2, v3, [reverse=]) CHILDREN;
1150// Usage: As function to remap points
1151//   transformed = frame_map(v1, v2, v3, p=points, [reverse=]);
1152// Usage: As function to return a transformation matrix:
1153//   map = frame_map(v1, v2, v3, [reverse=]);
1154//   map = frame_map(x=VECTOR1, y=VECTOR2, [reverse=]);
1155//   map = frame_map(x=VECTOR1, z=VECTOR2, [reverse=]);
1156//   map = frame_map(y=VECTOR1, z=VECTOR2, [reverse=]);
1157// Topics: Affine, Matrices, Transforms, Rotation
1158// See Also: rot(), xrot(), yrot(), zrot()
1159// Description:
1160//   Maps one coordinate frame to another.  You must specify two or
1161//   three of `x`, `y`, and `z`.  The specified axes are mapped to the vectors you supplied, so if you
1162//   specify x=[1,1] then the x axis will be mapped to the line y=x.  If you
1163//   give two inputs, the third vector is mapped to the appropriate normal to maintain a right hand
1164//   coordinate system.  If the vectors you give are orthogonal the result will be a rotation and the
1165//   `reverse` parameter will supply the inverse map, which enables you to map two arbitrary
1166//   coordinate systems to each other by using the canonical coordinate system as an intermediary.
1167//   You cannot use the `reverse` option with non-orthogonal inputs.  Note that only the direction
1168//   of the specified vectors matters: the transformation will not apply scaling, though it can
1169//   skew if your provide non-orthogonal axes.
1170// Arguments:
1171//   x = Destination 3D vector for x axis.
1172//   y = Destination 3D vector for y axis.
1173//   z = Destination 3D vector for z axis.
1174//   p = If given, the point, path, patch, or VNF to operate on.  Function use only.
1175//   reverse = reverse direction of the map for orthogonal inputs.  Default: false
1176// Example:  Remap axes after linear extrusion
1177//   frame_map(x=[0,1,0], y=[0,0,1]) linear_extrude(height=10) square(3);
1178// Example: This map is just a rotation around the z axis
1179//   mat = frame_map(x=[1,1,0], y=[-1,1,0]);
1180//   multmatrix(mat) frame_ref();
1181// Example:  This map is not a rotation because x and y aren't orthogonal
1182//   frame_map(x=[1,0,0], y=[1,1,0]) cube(10);
1183// Example:  This sends [1,1,0] to [0,1,1] and [-1,1,0] to [0,-1,1].  (Original directions shown in light shade, final directions shown dark.)
1184//   mat = frame_map(x=[0,1,1], y=[0,-1,1]) * frame_map(x=[1,1,0], y=[-1,1,0],reverse=true);
1185//   color("purple",alpha=.2) stroke([[0,0,0],10*[1,1,0]]);
1186//   color("green",alpha=.2)  stroke([[0,0,0],10*[-1,1,0]]);
1187//   multmatrix(mat) {
1188//      color("purple") stroke([[0,0,0],10*[1,1,0]]);
1189//      color("green") stroke([[0,0,0],10*[-1,1,0]]);
1190//   }
1191function frame_map(x,y,z, p=_NO_ARG, reverse=false) =
1192    p != _NO_ARG
1193    ? apply(frame_map(x,y,z,reverse=reverse), p)
1194    :
1195    assert(num_defined([x,y,z])>=2, "Must define at least two inputs")
1196    let(
1197        xvalid = is_undef(x) || (is_vector(x) && len(x)==3),
1198        yvalid = is_undef(y) || (is_vector(y) && len(y)==3),
1199        zvalid = is_undef(z) || (is_vector(z) && len(z)==3)
1200    )
1201    assert(xvalid,"Input x must be a length 3 vector")
1202    assert(yvalid,"Input y must be a length 3 vector")
1203    assert(zvalid,"Input z must be a length 3 vector")
1204    let(
1205        x = is_undef(x)? undef : unit(x,RIGHT),
1206        y = is_undef(y)? undef : unit(y,BACK),
1207        z = is_undef(z)? undef : unit(z,UP),
1208        map = is_undef(x)? [cross(y,z), y, z] :
1209            is_undef(y)? [x, cross(z,x), z] :
1210            is_undef(z)? [x, y, cross(x,y)] :
1211            [x, y, z]
1212    )
1213    reverse? (
1214        let(
1215            ocheck = (
1216                approx(map[0]*map[1],0) &&
1217                approx(map[0]*map[2],0) &&
1218                approx(map[1]*map[2],0)
1219            )
1220        )
1221        assert(ocheck, "Inputs must be orthogonal when reverse==true")
1222        [for (r=map) [for (c=r) c, 0], [0,0,0,1]]
1223    ) : [for (r=transpose(map)) [for (c=r) c, 0], [0,0,0,1]];
1224
1225
1226module frame_map(x,y,z,p,reverse=false)
1227{
1228   req_children($children);        
1229   assert(is_undef(p), "Module form `frame_map()` does not accept p= argument.");
1230   multmatrix(frame_map(x,y,z,reverse=reverse))
1231       children();
1232}
1233
1234
1235// Function&Module: skew()
1236// Usage: As Module
1237//   skew([sxy=]|[axy=], [sxz=]|[axz=], [syx=]|[ayx=], [syz=]|[ayz=], [szx=]|[azx=], [szy=]|[azy=]) CHILDREN;
1238// Usage: As Function
1239//   pts = skew(p, [sxy=]|[axy=], [sxz=]|[axz=], [syx=]|[ayx=], [syz=]|[ayz=], [szx=]|[azx=], [szy=]|[azy=]);
1240// Usage: Get Affine Matrix
1241//   mat = skew([sxy=]|[axy=], [sxz=]|[axz=], [syx=]|[ayx=], [syz=]|[ayz=], [szx=]|[azx=], [szy=]|[azy=]);
1242// Topics: Affine, Matrices, Transforms, Skewing
1243//
1244// Description:
1245//   Skews geometry by the given skew factors.
1246//   * Called as the built-in module, skews all children.
1247//   * Called as a function with a point in the `p` argument, returns the skewed point.
1248//   * Called as a function with a list of points in the `p` argument, returns the list of skewed points.
1249//   * Called as a function with a [bezier patch](beziers.scad) in the `p` argument, returns the skewed patch.
1250//   * Called as a function with a [VNF structure](vnf.scad) in the `p` argument, returns the skewed VNF.
1251//   * Called as a function without a `p` argument, returns the affine3d 4x4 skew matrix.
1252//   Each skew factor is a multiplier.  For example, if `sxy=2`, then it will skew along the X axis by 2x the value of the Y axis.
1253// Arguments:
1254//   p = If given, the point, path, patch, or VNF to skew.  Function use only.
1255//   ---
1256//   sxy = Skew factor multiplier for skewing along the X axis as you get farther from the Y axis.  Default: 0
1257//   sxz = Skew factor multiplier for skewing along the X axis as you get farther from the Z axis.  Default: 0
1258//   syx = Skew factor multiplier for skewing along the Y axis as you get farther from the X axis.  Default: 0
1259//   syz = Skew factor multiplier for skewing along the Y axis as you get farther from the Z axis.  Default: 0
1260//   szx = Skew factor multiplier for skewing along the Z axis as you get farther from the X axis.  Default: 0
1261//   szy = Skew factor multiplier for skewing along the Z axis as you get farther from the Y axis.  Default: 0
1262//   axy = Angle to skew along the X axis as you get farther from the Y axis.
1263//   axz = Angle to skew along the X axis as you get farther from the Z axis.
1264//   ayx = Angle to skew along the Y axis as you get farther from the X axis.
1265//   ayz = Angle to skew along the Y axis as you get farther from the Z axis.
1266//   azx = Angle to skew along the Z axis as you get farther from the X axis.
1267//   azy = Angle to skew along the Z axis as you get farther from the Y axis.
1268// Example(2D): Skew along the X axis in 2D.
1269//   skew(sxy=0.5) square(40, center=true);
1270// Example(2D): Skew along the X axis by 30ยบ in 2D.
1271//   skew(axy=30) square(40, center=true);
1272// Example(2D): Skew along the Y axis in 2D.
1273//   skew(syx=0.5) square(40, center=true);
1274// Example: Skew along the X axis in 3D as a factor of Y coordinate.
1275//   skew(sxy=0.5) cube(40, center=true);
1276// Example: Skew along the X axis in 3D as a factor of Z coordinate.
1277//   skew(sxz=0.5) cube(40, center=true);
1278// Example: Skew along the Y axis in 3D as a factor of X coordinate.
1279//   skew(syx=0.5) cube(40, center=true);
1280// Example: Skew along the Y axis in 3D as a factor of Z coordinate.
1281//   skew(syz=0.5) cube(40, center=true);
1282// Example: Skew along the Z axis in 3D as a factor of X coordinate.
1283//   skew(szx=0.5) cube(40, center=true);
1284// Example: Skew along the Z axis in 3D as a factor of Y coordinate.
1285//   skew(szy=0.75) cube(40, center=true);
1286// Example(FlatSpin,VPD=275): Skew Along Multiple Axes.
1287//   skew(sxy=0.5, syx=0.3, szy=0.75) cube(40, center=true);
1288// Example(2D): Calling as a 2D Function
1289//   pts = skew(p=square(40,center=true), sxy=0.5);
1290//   color("yellow") stroke(pts, closed=true);
1291//   color("blue") move_copies(pts) circle(d=3, $fn=8);
1292// Example(FlatSpin,VPD=175): Calling as a 3D Function
1293//   pts = skew(p=path3d(square(40,center=true)), szx=0.5, szy=0.3);
1294//   stroke(pts,closed=true,dots=true,dots_color="blue");
1295module skew(p, sxy, sxz, syx, syz, szx, szy, axy, axz, ayx, ayz, azx, azy)
1296{
1297    req_children($children);          
1298    assert(is_undef(p), "Module form `skew()` does not accept p= argument.");
1299    mat = skew(
1300        sxy=sxy, sxz=sxz, syx=syx, syz=syz, szx=szx, szy=szy,
1301        axy=axy, axz=axz, ayx=ayx, ayz=ayz, azx=azx, azy=azy
1302    );
1303    multmatrix(mat) children();
1304}
1305
1306function skew(p=_NO_ARG, sxy, sxz, syx, syz, szx, szy, axy, axz, ayx, ayz, azx, azy) =
1307    assert(num_defined([sxy,axy]) < 2)
1308    assert(num_defined([sxz,axz]) < 2)
1309    assert(num_defined([syx,ayx]) < 2)
1310    assert(num_defined([syz,ayz]) < 2)
1311    assert(num_defined([szx,azx]) < 2)
1312    assert(num_defined([szy,azy]) < 2)
1313    assert(sxy==undef || is_finite(sxy))
1314    assert(sxz==undef || is_finite(sxz))
1315    assert(syx==undef || is_finite(syx))
1316    assert(syz==undef || is_finite(syz))
1317    assert(szx==undef || is_finite(szx))
1318    assert(szy==undef || is_finite(szy))
1319    assert(axy==undef || is_finite(axy))
1320    assert(axz==undef || is_finite(axz))
1321    assert(ayx==undef || is_finite(ayx))
1322    assert(ayz==undef || is_finite(ayz))
1323    assert(azx==undef || is_finite(azx))
1324    assert(azy==undef || is_finite(azy))
1325    let(
1326        sxy = is_num(sxy)? sxy : is_num(axy)? tan(axy) : 0,
1327        sxz = is_num(sxz)? sxz : is_num(axz)? tan(axz) : 0,
1328        syx = is_num(syx)? syx : is_num(ayx)? tan(ayx) : 0,
1329        syz = is_num(syz)? syz : is_num(ayz)? tan(ayz) : 0,
1330        szx = is_num(szx)? szx : is_num(azx)? tan(azx) : 0,
1331        szy = is_num(szy)? szy : is_num(azy)? tan(azy) : 0,
1332        m = affine3d_skew(sxy=sxy, sxz=sxz, syx=syx, syz=syz, szx=szx, szy=szy)
1333    )
1334    p==_NO_ARG? m : apply(m, p);
1335
1336
1337// Section: Applying transformation matrices to data
1338
1339/// Internal Function: is_2d_transform()
1340/// Usage:
1341///   bool = is_2d_transform(t);
1342/// Topics: Affine, Matrices, Transforms, Type Checking
1343/// See Also: is_affine(), is_matrix()
1344/// Description:
1345///   Checks if the input is a 3D transform that does not act on the z coordinate, except possibly
1346///   for a simple scaling of z.  Note that an input which is only a zscale returns false.
1347/// Arguments:
1348///   t = The transformation matrix to check.
1349/// Example:
1350///   b = is_2d_transform(zrot(45));  // Returns: true
1351///   b = is_2d_transform(yrot(45));  // Returns: false
1352///   b = is_2d_transform(xrot(45));  // Returns: false
1353///   b = is_2d_transform(move([10,20,0]));  // Returns: true
1354///   b = is_2d_transform(move([10,20,30]));  // Returns: false
1355///   b = is_2d_transform(scale([2,3,4]));  // Returns: true
1356function is_2d_transform(t) =    // z-parameters are zero, except we allow t[2][2]!=1 so scale() works
1357  t[2][0]==0 && t[2][1]==0 && t[2][3]==0 && t[0][2] == 0 && t[1][2]==0 &&
1358  (t[2][2]==1 || !(t[0][0]==1 && t[0][1]==0 && t[1][0]==0 && t[1][1]==1));   // But rule out zscale()
1359
1360
1361
1362// Function: apply()
1363// Usage:
1364//   pts = apply(transform, points);
1365// Topics: Affine, Matrices, Transforms
1366// Description:
1367//   Applies the specified transformation matrix `transform` to a point, point list, bezier patch or VNF.
1368//   When `points` contains 2D or 3D points the transform matrix may be a 4x4 affine matrix or a 3x4 
1369//   matrix&mdash;the 4x4 matrix with its final row removed.  When the data is 2D the matrix must not operate on the Z axis,
1370//   except possibly by scaling it.  When points contains 2D data you can also supply the transform as
1371//   a 3x3 affine transformation matrix or the corresponding 2x3 matrix with the last row deleted.
1372//   .
1373//   Any other combination of matrices will produce an error, including acting with a 2D matrix (3x3) on 3D data.
1374//   The output of apply is always the same dimension as the input&mdash;projections are not supported.
1375//   .
1376//   Note that a matrix with a negative determinant such as any mirror reflection flips the orientation of faces.
1377//   If the transform matrix is square then apply() checks the determinant and if it is negative, apply() reverses the face order so that
1378//   the transformed VNF has faces with the same winding direction as the original VNF.  This adjustment applies
1379//   only to VNFs, not to beziers or point lists.  
1380// Arguments:
1381//   transform = The 2D (3x3 or 2x3) or 3D (4x4 or 3x4) transformation matrix to apply.
1382//   points = The point, point list, bezier patch, or VNF to apply the transformation to.
1383// Example(3D):
1384//   path1 = path3d(circle(r=40));
1385//   tmat = xrot(45);
1386//   path2 = apply(tmat, path1);
1387//   #stroke(path1,closed=true);
1388//   stroke(path2,closed=true);
1389// Example(2D):
1390//   path1 = circle(r=40);
1391//   tmat = translate([10,5]);
1392//   path2 = apply(tmat, path1);
1393//   #stroke(path1,closed=true);
1394//   stroke(path2,closed=true);
1395// Example(2D):
1396//   path1 = circle(r=40);
1397//   tmat = rot(30) * back(15) * scale([1.5,0.5,1]);
1398//   path2 = apply(tmat, path1);
1399//   #stroke(path1,closed=true);
1400//   stroke(path2,closed=true);
1401function apply(transform,points) =
1402    points==[] ? []
1403  : is_vector(points) ? _apply(transform, [points])[0]    // point
1404  : is_vnf(points) ?                                      // vnf
1405        let(
1406            newvnf = [_apply(transform, points[0]), points[1]],
1407            reverse = (len(transform)==len(transform[0])) && determinant(transform)<0
1408        )
1409        reverse ? vnf_reverse_faces(newvnf) : newvnf
1410  : is_list(points) && is_list(points[0]) && is_vector(points[0][0])    // bezier patch
1411        ? [for (x=points) _apply(transform,x)]
1412  : _apply(transform,points);
1413
1414
1415
1416
1417function _apply(transform,points) =
1418    assert(is_matrix(transform),"Invalid transformation matrix")
1419    assert(is_matrix(points),"Invalid points list")
1420    let(
1421        tdim = len(transform[0])-1,
1422        datadim = len(points[0])
1423    )
1424    assert(len(transform)==tdim || len(transform)-1==tdim, "transform matrix height not compatible with width")
1425    assert(datadim==2 || datadim==3,"Data must be 2D or 3D")
1426    let(
1427        scale = len(transform)==tdim ? 1 : transform[tdim][tdim],
1428        matrix = [for(i=[0:1:tdim]) [for(j=[0:1:datadim-1]) transform[j][i]]] / scale
1429    )
1430    tdim==datadim ? [for(p=points) concat(p,1)] * matrix
1431  : tdim == 3 && datadim == 2 ?
1432            assert(is_2d_transform(transform), str("Transforms is 3D and acts on Z, but points are 2D"))
1433            [for(p=points) concat(p,[0,1])]*matrix
1434  : assert(false, str("Unsupported combination: ",len(transform),"x",len(transform[0])," transform (dimension ",tdim,
1435                          "), data of dimension ",datadim));
1436
1437
1438// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap