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