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—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—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