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