1//////////////////////////////////////////////////////////////////////
  2// LibFile: turtle3d.scad
  3//   Three dimensional turtle graphics to generate 3d paths or sequences
  4//   of 3d transformations. 
  5// Includes:
  6//   include <BOSL2/std.scad>
  7//   include <BOSL2/turtle3d.scad>
  8// FileGroup: Advanced Modeling
  9// FileSummary: 3D turtle graphics for making paths or lists of transformations.
 10//////////////////////////////////////////////////////////////////////
 11include<structs.scad>
 12
 13// Section: Functions
 14
 15// Translation vector from a matrix
 16function _transpart(T) = [for(row=[0:2]) T[row][3]];
 17
 18// The non-translation part of a matrix
 19function _rotpart(T) = [for(i=[0:3]) [for(j=[0:3]) j<3 || i==3 ? T[i][j] : 0]];
 20
 21
 22// Function: turtle3d()
 23// Synopsis: Extends [turtle graphics](https://en.wikipedia.org/wiki/Turtle_graphics) to 3d. Generates a 3D path or returns a list of transforms.
 24// SynTags: MatList, Path
 25// Topics: Shapes (3D), Path Generators (3D), Mini-Language
 26// See Also: turtle()
 27// Usage:
 28//   path = turtle3d(commands, [state=], [repeat=]);
 29//   mats = turtle3d(commands, transforms=true, [state=], [repeat=]);
 30//   state = turtle3d(commands, full_state=true, [state=], [repeat=]);
 31// Description:
 32//   Like the classic two dimensional turtle, the 3d turtle flies through space following a sequence
 33//   of turtle graphics commands to generate either a sequence of transformations (suitable for input
 34//   to sweep) or a 3d path.  The turtle state keeps track of the position and orientation (including twist)
 35//   and scale of the turtle.  By default the turtle begins pointing along the X axis with the "right" direction
 36//   along the -Y axis and the "up" direction aligned with the Z axis.  You can give a direction vector
 37//   for the state input to change the starting direction.  Because of the complexity of object positioning
 38//   in three space, some types of movement require compound commands.  These compound commands are lists that specify several operations
 39//   all applied to one turtle step.  For example:  ["move", 4, "twist", 25] executes a twist while moving, and
 40//   the command ["arc", 4, "grow", 2, "right", 45, "up", 30] turns to the right and up while also growing the object.
 41//   .
 42//   You can turn the turtle using relative commands, "right", "left", "up" and "down", which operate relative
 43//   to the turtle's current orientation.   This is sometimes confusing, so you can also use absolute
 44//   commands which turn the turtle relative to the absolute coordinate system, the "xrot", "yrot" and "zrot"
 45//   commands.  You can use "setdir" to point the turtle along a given vector.
 46//   If you want a valid transformation list for use with sweep you will usually want to avoid abrupt changes
 47//   in the orientation of the turtle.  To do this, use the "arc"
 48//   forms for turns.  This form, with commands like "arcright" and "arcup" creates an arc with a gradual
 49//   change in the turtle orientation, which usually produces a better result for sweep operations.
 50//   .
 51//   Another potential problem for sweep is a command that makes movements not relative to the turtle's current direction such as
 52//   "jump" or "untily".  These commands are not a problem for tracing out a path, but if you want a swept shape to
 53//   maintain a constant cross sectional shape then you need to avoid them.  operations and avoid the movement commands
 54//   which do not move relative to the turtle direction such as the "jump" commands.
 55//   .
 56//   If you use sweep to convert a turtle path into a 3d shape the result depends both on the path the shape traces out but also
 57//   the twist and size of the shape.  The "twist" parameter described below to the compound commands has no effect on
 58//   the turtle orientation for the purpose of defining movement, but it will rotate the swept shape around the origin
 59//   as it traces out the path.  Similarly the "grow" and "shrink" options allow you to change the size of the swept
 60//   polygon without any effect on the turtle.  The "roll" command differs from "twist" in that it both rotates the swept
 61//   polygon but also changes the turtle's orientation, so it will alter subsequent operations of the turtle.  Note that
 62//   when making a path, "twist" will have no effect, but "roll" may have an effect because of how it changes the path.  
 63//   .
 64//   The compound "move" command accepts a "reverse" argument.  If you specify "reverse" it reflects the
 65//   turtle direction to point backwards.  This enables you to back out to create a hollow shape.  But be
 66//   aware that everything is reversed, so turns will be the opposite direction.  So for example if you
 67//   used "arcright" on the outside you might expect arcleft when reversed on the inside, but it will
 68//   be "arcright" again.  (Note that "reverse" is the only command that appears by itself with no argument
 69//   .
 70//   By default you get a simple path (like the 2d turtle) which ignores growing/shrinking or twisting in the
 71//   transformation.  If you select transform=true then you will get a list of transformations returned.  Some of
 72//   of the commands are likely to produce transformation lists that are invalid for sweep.  The "jump" commands
 73//   can move in directions not perpendicular to the current direction of movement, which may produce bad results.
 74//   The turning commands like "left" or "up" can rotate the frame so that a sweep operation is invalid.
 75//   The `T` column in the list below marks commands that operate relative
 76//   to the current frame that should generally produce valid sweep transformations.
 77//   Be aware that it is possible to create a self intersection, and hence an invalid swept shape, if the radii of
 78//   arcs in turtle are smaller than the width of the polygon you use with sweep.  
 79//   .
 80//   The turtle state is a list containing:
 81//     - a list of path transformations, the transformations that move the turtle along the path
 82//     - a list of object transformations, the transformations that twist or scale the cross section as the turtle moves
 83//     - the current movement step size (scalar)
 84//     - the current default angle
 85//     - the current default arcsteps  
 86//   .
 87//   Commands   |T | Arguments          | What it does
 88//   ---------- |--| ------------------ | -------------------------------
 89//   "move"     |x | [dist]             | Move turtle scale*dist units in the turtle direction.  Default dist=1.  
 90//   "xmove"    |  | [dist]             | Move turtle scale*dist units in the x direction. Default dist=1.  Does not change turtle direction.
 91//   "ymove"    |  | [dist]             | Move turtle scale*dist units in the y direction. Default dist=1.  Does not change turtle direction.
 92//   "zmove"    |  | [dist]             | Move turtle scale*dist units in the y direction. Default dist=1.  Does not change turtle direction.
 93//   "xyzmove"  |  | vector             | Move turtle by the specified vector.  Does not change turtle direction. 
 94//   "untilx"   |x | xtarget            | Move turtle in turtle direction until x==xtarget.  Produces an error if xtarget is not reachable.
 95//   "untily"   |x | ytarget            | Move turtle in turtle direction until y==ytarget.  Produces an error if ytarget is not reachable.
 96//   "untilz"   |x | ytarget            | Move turtle in turtle direction until y==ytarget.  Produces an error if ztarget is not reachable.
 97//   "jump"     |  | point              | Move the turtle to the specified point
 98//   "xjump"    |  | x                  | Move the turtle's x position to the specified value
 99//   "yjump     |  | y                  | Move the turtle's y position to the specified value
100//   "zjump     |  | y                  | Move the turtle's y position to the specified value
101//   "left"     |  | [angle]            | Turn turtle left by specified angle or default angle
102//   "right"    |  | [angle]            | Turn turtle to the right by specified angle or default angle
103//   "up"       |  | [angle]            | Turn turtle up by specified angle or default angle
104//   "down"     |  | [angle]            | Turn turtle down by specified angle or default angle
105//   "xrot"     |x | [angle]            | Turn turtle around x-axis by specified angle or default angle
106//   "yrot"     |x | [angle]            | Turn turtle around y-axis by specified angle or default angle
107//   "zrot"     |x | [angle]            | Turn turtle around z-axis by specified angle or default angle
108//   "rot"      |x | rotation           | Turn turtle by specified rotation relative to absolute coordinates
109//   "angle"    |x | angle              | Set the default turn angle.
110//   "setdir"   |  | vector             | Rotate the reference frame along the shortest path to specified direction
111//   "length"   |x | length             | Change the turtle move distance to `length`
112//   "scale"    |x | factor             | Multiply turtle move distances by `factor`.  Does not rescale the cross sectional shape in transformation lists.  
113//   "addlength"|x | length             | Add `length` to the turtle move distance
114//   "repeat"   |x | count, commands    | Repeats a list of commands `count` times.  (To repeat a compound command put it in a list: `[["move",10,"grow",2]]`)
115//   "arcleft"  |x | radius, [angle]    | Draw an arc from the current position toward the left at the specified radius and angle.  The turtle turns by `angle`.
116//   "arcright" |x | radius, [angle]    | Draw an arc from the current position toward the right at the specified radius and angle.  The turtle turns by `angle`.
117//   "arcup"    |x | radius, [angle]    | Draw an arc from the current position upward at the specified radius and angle
118//   "arcdown"  |x | radius, [angle]    | Draw an arc from the current position downward at the specified radius and angle
119//   "arcxrot"  |x | radius, [angle]    | Draw an arc turning around x-axis by specified angle or default angle
120//   "arcyrot"  |x | radius, [angle]    | Draw an arc turning around y-axis by specified angle or default angle
121//   "arczrot"  |x | radius, [angle]    | Draw an arc turning around z-axis by specified angle or default angle
122//   "arcrot"   |x | radius, rotation   | Draw an arc turning by the specified absolute rotation with given radius
123//   "arctodir" |x | radius, vector     | Draw an arc turning to point in the (absolute) direction of given vector
124//   "arcsteps" |x | count              | Specifies the number of segments to use for drawing arcs.  If you set it to zero then the standard `$fn`, `$fa` and `$fs` variables define the number of segments.
125//   .
126//   Compound commands are lists that group multiple commands to be applied simultaneously during a
127//   turtle movement.  Example: `["move", 5, "shrink", 2]`.  The subcommands that may appear are
128//   listed below.  Each compound command must begin with either "move" or "arc".  The order of
129//   subcommands is not important.  Left/right turning is applied before up/down.  You cannot combine
130//   "rot" or "todir" with any other turning commands.  
131//   .
132//   Subcommands  | Arguments          | What it does
133//   ------------ | ------------------ | -------------------------------
134//   "move"       | dist               | Compound command is a forward movement operation
135//   "arc"        | radius             | Compound command traces an arc
136//   "grow"       | factor             | Increase size by specified factor (e.g. 2 doubles the size); factor can be a 2-vector
137//   "shrink"     | factor             | Decrease size by specified factor (e.g. 2 halves the size); factor can be a 2-vector
138//   "twist"      | angle              | Twist by the specified angle over the arc or segment (does not change frame orientation)
139//   "roll"       | angle              | Roll by the specified angle over the arc or segment (changes the orientation of the frame)
140//   "steps"      | count              | Divide arc or segment into this many steps.  Default is 1 for segments, arcsteps for arcs
141//   "reverse"    |                    | For "move" only: If given then reverses the turtle after the move
142//   "right"      | angle              | For "arc" only: Turn to the right by specified angle
143//   "left"       | angle              | For "arc" only: Turn to the left by specified angle
144//   "up"         | angle              | For "arc" only: Turn up by specified angle
145//   "down"       | angle              | For "arc" only: Turn down by specified angle
146//   "xrot"       | angle              | For "arc" only: Absolute rotation around x axis. Cannot be combined with any other rotation.
147//   "yrot"       | angle              | For "arc" only: Absolute rotation around y axis. Cannot be combined with any other rotation.
148//   "zrot"       | angle              | For "arc" only: Absolute rotation around z axis. Cannot be combined with any other rotation.
149//   "rot"        | rotation           | For "arc" only: Turn by specified absolute rotation as a matrix, e.g. xrot(33)*zrot(47).  Cannot be combined with any other rotation.
150//   "todir"      | vector             | For "arc" only: Turn to point in the specified direction
151//   .
152//   The "twist", "shrink" and "grow" subcommands will only have an effect if you return a transformation list.  They do not
153//   change the path the turtle traces.  The "roll" subcommand, on the other hand, changes the turtle frame orientation, so it can alter the path.
154//   The "xrot", "yrot" and "zrot" subcommands can make turns larger than 180 degrees, and even larger than 360 degrees.  If you use "up",
155//   "down", "left" or "right" alone then you can give any angle, but if you combine "up"/"down" with "left"/"right" then the specified
156//   angles must be smaller than 180 degrees.  (This is because the algorithm decodes the rotation into an angle smaller than 180, so
157//   the results are very strange if larger angles are permitted.)
158// Arguments:
159//   commands = List of turtle3d commands
160//   state = Starting turtle direction or full turtle state (from a previous call).  Default: RIGHT
161//   transforms = If true teturn list of transformations instead of points.  Default: false
162//   full_state = If true return full turtle state for continuing the path in subsequent turtle calls.  Default: false
163//   repeat = Number of times to repeat the command list.  Default: 1
164// Example(3D): Angled rectangle
165//   path = turtle3d(["up",25,"move","left","move",3,"left","move"]);
166//   stroke(path,closed=true, width=.2);
167// Example(3D): Path with rounded corners.  Note first and last point of the path are duplicates.  
168//   r = 0.25;
169//   path = turtle3d(["up",25,"move","arcleft",r,"move",3,"arcleft",r,"move","arcleft",r,"move",3,"arcleft",r]);
170//   stroke(path,closed=true, width=.2);
171// Example(3D): Non-coplanar figure
172//   path = turtle3d(["up",25,"move","left","move",3,"up","left",0,"move"]);
173//   stroke(path,closed=true, width=.2);
174// Example(3D): Square spiral.  Note that the core twists because the "up" and "left" turns are relative to the previous turns.
175//   include<BOSL2/skin.scad>
176//   path = turtle3d(["move",10,"left","up",15],repeat=50);
177//   path_sweep(circle(d=1, $fn=12), path);
178// Example(3D): Square spiral, second try.  Use roll to create the spiral instead of turning up.  It still twists because the left turns are inclined.
179//   include<BOSL2/skin.scad>
180//   path = turtle3d(["move",10,"left","roll",10],repeat=50);
181//   path_sweep(circle(d=1, $fn=12), path);
182// Example(3D): Square spiral, third try.  One way to avoid the core twisting in the spiral is to use absolute turns.  Note that the vertical rise is controlled by the starting upward angle of the turtle, which is preserved as we rotate around the z axis.  
183//   include<BOSL2/skin.scad>
184//   path = turtle3d(["up", 5, "repeat", 12, ["move",10,"zrot"]]);
185//   path_sweep(circle(d=1, $fn=12), path);
186// Example(3D): Square spiral, rounded corners.  Careful use of rotations can work for sweep, but it may be better to round the corners.  Here we return a list of transforms and use sweep instead of path_sweep:
187//   include<BOSL2/skin.scad>
188//   path = turtle3d(["up", 5, "repeat", 12, ["move",10,"arczrot",4]],transforms=true);
189//   sweep(circle(d=1, $fn=12), path);
190// Example(3D): Mixing relative and absolute commands
191//   include<BOSL2/skin.scad>
192//   path = turtle3d(["repeat", 4, ["move",80,"arczrot",40],
193//                    "arcyrot",40,-90,
194//                    "move",40,
195//                    "arcxrot",40,90,
196//                    ["arc",14,"rot",xrot(90)*zrot(-33)],
197//                    "move",80,
198//                    "arcyrot",40,
199//                    "arcup",40,
200//                    "arcleft",40,
201//                    "arcup",30,
202//                    ["move",100,"twist",90,"steps",20],
203//                   ],
204//                   state=[1,0,.2],transforms=true);
205//   ushape = rot(90,p=[[-10, 0],[-10, 10],[ -7, 10],[ -7, 2],[  7, 2],[  7, 7],[ 10, 7],[ 10, 0]]);
206//   sweep(ushape, path);
207// Example(3D): Generic helix, constructed by a sequence of movements and then rotations
208//   include<BOSL2/skin.scad>
209//   radius=14;       // Helix radius
210//   pitch=20;        // Distance from one turn to the next
211//   turns=3;         // Number of turns
212//   turn_steps=32;   // Number of steps on each turn
213//   axis = [1,4,1];  // Helix axis
214//   up_angle = atan2(pitch,2*PI*radius);
215//   helix = turtle3d([
216//                      "up", up_angle,
217//                      "zrot", 360/turn_steps/2,
218//                      "rot", rot(from=UP,to=axis), // to correct the turtle direction
219//                      "repeat", turn_steps*turns,
220//                      [
221//                       "move", norm([2*PI*radius, pitch])/turn_steps,
222//                       "rot",  rot(360/turn_steps,v=axis)
223//                      ],
224//                     ], transforms=true);
225//   sweep(subdivide_path(square([5,1]),20), helix);
226// Example(3D): Helix generated by a single command.  Note this only works for x, y, or z aligned helixes because the generic rot cannot handle multi-turn angles.  
227//   include<BOSL2/skin.scad>
228//   pitch=20;       // Distance from one turn to the next
229//   radius=14;      // Helix radius
230//   turns=3;        // Number of turns
231//   turn_steps=33;  // Steps on each turn
232//   up_angle = atan2(pitch,2*PI*radius);
233//   helix = turtle3d([
234//                     "up", up_angle,
235//                     [
236//                       "arc", radius,
237//                       "zrot", 360*turns,
238//                       "steps", turn_steps*turns,
239//                     ]
240//                    ], transforms=true);
241//   sweep(subdivide_path(square([5,1]),80), helix);
242// Example(3D): Expanding helix
243//   include<BOSL2/skin.scad>
244//   path = turtle3d(["length",.2,"angle",360/20,"up",5,"repeat",50,["move","zrot","addlength",0.05]]);
245//   path_sweep(circle(d=1, $fn=12), path);
246// Example(3D): Adding some twist to the model
247//   include<BOSL2/skin.scad>
248//   r = 2.5;
249//   trans = turtle3d(["move",10,
250//                     "arcleft",r,
251//                     ["move",30,"twist",180,"steps",40],
252//                     "arcleft",r,
253//                     "move",10,
254//                     "arcleft",r,
255//                     ["move",30,"twist",360,"steps",40],
256//                     "arcleft",r],
257//                    state=yrot(25,p=RIGHT),transforms=true);
258//   sweep(supershape(m1=4,n1=4,n2=16,n3=1.5,a=.9,b=9,step=5),trans);
259// Example(3D): Twist does not change the turtle orientation, but roll does.  The only change from the previous example is twist was changed to roll.
260//   include<BOSL2/skin.scad>
261//   r = 2;
262//   trans = turtle3d(["move",10,
263//                     "arcleft",r,
264//                     ["move",30,"roll",180,"steps",40],
265//                     "arcleft",r,
266//                     "move",10,
267//                     "arcleft",r,
268//                     ["move",30,"roll",360,"steps",40],
269//                     "arcleft",r],
270//                    state=yrot(25,p=RIGHT),transforms=true);
271//   sweep(supershape(m1=4,n1=4,n2=16,n3=1.5,a=.9,b=9,step=5),trans);
272// Example(3D): Use of shrink and grow
273//   include<BOSL2/skin.scad>
274//   $fn=32;
275//   T = turtle3d([
276//                 "move",10,
277//                 ["arc",8,"right", 90, "twist", 90, "grow", 2],
278//                 ["move", 5,"shrink",4,"steps",4],
279//                 ["arc",8, "right", 45, "up", 90],
280//                 "move", 10,
281//                 "arcright", 5, 90,
282//                 "arcleft", 5, 90,
283//                 "arcup", 5, 90,
284//                 "untily", -1,
285//                ],state=RIGHT, transforms=true);   
286//   sweep(square(2,center=true),T);
287// Example(3D): After several moves you may not understand the turtle orientation. An absolute reorientation with "arctodir" is helpful to head in a known direction
288//   include<BOSL2/skin.scad>
289//   trans = turtle3d([
290//                  "move",5,
291//                  "arcup",1,
292//                  "move",8,
293//                  "arcright",1,
294//                  "move",6,
295//                  "arcdown",1,
296//                  "move",4,
297//                  ["arc",2,"right",45,"up",25,"roll",25],
298//                  "untilz",4,
299//                  "move",1,
300//                  "arctodir",1,DOWN,
301//                  "untilz",0
302//                  ],transforms=true);
303//   sweep(square(1,center=true),trans);
304// Example(3D): The "grow" and "shrink" commands can take a vector giving x and y scaling
305//   include<BOSL2/skin.scad>
306//   tr = turtle3d([
307//                   "move", 1.5, 
308//                   ["move", 5, "grow", [1,2],  "steps", 10],
309//                   ["move", 5, "grow", [2,0.5],"steps", 10]
310//                  ], transforms=true);
311//   sweep(circle($fn=32,r=1), tr);
312// Example(3D): With "twist" added the anisotropic "grow" interacts with "twist", producing a complex form
313//   include<BOSL2/skin.scad>
314//   tr = turtle3d([
315//                   "move", 1.5, 
316//                   ["move", 5, "grow", [1,2],  "steps", 20, "twist",90],
317//                   ["move", 5, "grow", [0.5,2],"steps", 20, "twist",90]
318//                  ], transforms=true);
319//   sweep(circle($fn=64,r=1), tr);
320// Example(3D): Making a tube with "reverse".  Note that the move direction is the same even though the direction is reversed.  
321//   include<BOSL2/skin.scad>
322//   tr = turtle3d([ "move", 4,
323//                   ["move",0, "grow", .8, "reverse"],
324//                   "move", 4
325//                 ],  transforms=true);
326//   back_half(s=10)
327//     sweep(circle(r=1,$fn=16), tr, closed=true);
328// Example(3D): To close the tube at one end we set closed to false in sweep.  
329//   include<BOSL2/skin.scad>
330//   tr = turtle3d([ "move", 4,
331//                   ["move",0, "grow", .8, "reverse"],
332//                   "move", 3.75
333//                 ],  transforms=true);
334//   back_half(s=10)
335//     sweep(circle(r=1,$fn=16), tr, closed=false);
336// Example(3D): Cookie cutter using "reverse" 
337//   include<BOSL2/skin.scad>
338//   cutter = turtle3d( [ 
339//                       ["move", 10, "shrink", 1.3, ],
340//                       ["move", 2, "reverse" ],
341//                       ["move", 8, "shrink", 1.3 ],
342//                      ], transforms=true,state=UP);
343//   cookie_shape = star(5, r=10, ir=5);
344//   sweep(cookie_shape, cutter, closed=true);
345// Example(3D): angled shopvac adapter.  Shopvac tubing wedges together because the tubes are slightly tapered.  We can make this part without using any difference() operations by using "reverse" to trace out the interior portion of the part.  Note that it's "arcright" even when reversed.  
346//   include<BOSL2/skin.scad>
347//   inch = 25.4;
348//   insert_ID = 2.3*inch;        // Size of shopvac tube at larger end of taper
349//   wall = 1.7;                  // Desired wall thickness
350//   seg1_bot_ID = insert_ID;     // Bottom section, to have tube inserted, specify ID
351//   seg2_bot_OD = insert_ID+.03; // Top section inserts into a tube, so specify tapered OD
352//   seg2_top_OD = 2.26*inch;     // The slightly oversized value gave me a better fit
353//   seg1_len = 3*inch;           // Length of bottom section
354//   seg2_len = 2*inch;           // Length of top section
355//   bend_angle=45;               // Angle to bend, 45 or less to print without supports!
356//   // Other diameters derived from the wall thickness
357//   seg1_bot_OD = seg1_bot_ID+2*wall;
358//   seg2_bot_ID = seg2_bot_OD-2*wall;
359//   seg2_top_ID = seg2_top_OD-2*wall;
360//   bend_r = 0.5*inch+seg1_bot_OD/2;   // Bend radius to get constant wall thickness
361//   trans = turtle3d([
362//                       ["move", seg1_len, "grow", seg2_bot_OD/seg1_bot_OD],  
363//                       "arcright", bend_r, bend_angle,
364//                       ["move", seg2_len, "grow", seg2_top_OD/seg2_bot_OD],
365//                       ["move", 0, "reverse", "grow", seg2_top_ID/seg2_top_OD],
366//                       ["move", seg2_len, "grow", seg2_bot_ID/seg2_top_ID],
367//                       "arcright", bend_r, bend_angle,
368//                       ["move", seg1_len, "grow", seg1_bot_ID/seg2_bot_ID]
369//                    ],
370//                    state=UP, transforms=true);
371//   back_half()      // Remove this to get a usable part
372//     sweep(circle(d=seg1_bot_OD, $fn=128), trans, closed=true);
373// Example(3D): Closed spiral
374//   include<BOSL2/skin.scad>
375//   steps = 500;
376//   spiral = turtle3d([
377//                      ["arc", 20,
378//                       "twist", 120,
379//                       "zrot", 360*4,
380//                       "steps",steps,
381//                       "shrink",1.5],
382//                      ["arc", 20,
383//                       "twist", 120,
384//                       "zrot", 360*4,
385//                       "steps",steps/5 ],
386//                      ["arc", 20,
387//                       "twist", 120,
388//                       "zrot", 360*4,
389//                       "steps",steps,
390//                       "grow",1.5],
391//                      ], transforms=true);
392//   sweep(fwd(25,p=circle(r=2,$fn=24)), spiral, caps=false);
393// Example(3D): Mobius strip (square)
394//   include<BOSL2/skin.scad>
395//   mobius = turtle3d([["arc", 20, "zrot", 360,"steps",100,"twist",180]], transforms=true);
396//   sweep(subdivide_path(square(8,center=true),16), mobius, closed=false);
397// Example(3D): Torus knot
398//   include<BOSL2/skin.scad>
399//   p = 3;      // (number of turns)*gcd(p,q)
400//   q = 10;     // (number of dives)*gcd(p,q)
401//   steps = 60; // steps per turn
402//   cordR  = 2; // knot cord radius
403//   torusR = 20;// torus major radius
404//   torusr = 4; // torus minor radius
405//   knot_radius = torusr + 0.75*cordR; // inner radius of knot, set to torusr to put knot
406//   wind_angle = atan(p / q *torusR / torusr);            // center on torus surface
407//   m = gcd(p,q);
408//   torus_knot0 =
409//       turtle3d([ "arcsteps", 1,
410//                  "repeat", p*steps/m-1 ,
411//                     [ [ "arc", torusR, "left", 360/steps, "twist", 360*q/p/steps ] ]
412//                ], transforms=true);
413//   torus_knot = [for(tr=torus_knot0) tr*xrot(wind_angle+90)];
414//   torus = turtle3d( ["arcsteps", steps, "arcleft", torusR, 360], transforms=true);
415//   fwd(torusR){ // to center the torus and knot at the origin
416//       color([.8,.7,1])
417//         sweep(right(knot_radius,p=circle(cordR,$fn=16)), torus_knot,closed=true);
418//       color("blue")
419//         sweep(circle(torusr,$fn=24), torus);
420//   }
421
422/*
423turtle state: sequence of transformations ("path") so far
424              sequence of pre-transforms that apply to the polygon (scaling and twist)
425              default move
426              default angle
427              default arc steps
428*/
429
430function _turtle3d_state_valid(state) =
431    is_list(state)
432        && is_consistent(state[0],ident(4))
433        && is_consistent(state[1],ident(4))
434        && is_num(state[2])
435        && is_num(state[3])
436        && is_num(state[4]);
437
438module turtle3d(commands, state=RIGHT, transforms=false, full_state=false, repeat=1) {no_module();}
439function turtle3d(commands, state=RIGHT, transforms=false, full_state=false, repeat=1) =
440  assert(is_bool(transforms))
441  let(
442       state = is_matrix(state,4,4) ? [[state],[yrot(90)],1,90,0] :
443               is_vector(state,3) ?
444                  let( updir = UP - (UP * state) * state / (state*state) )
445                  [[frame_map(x=state, z=approx(norm(updir),0) ? FWD : updir)], [yrot(90)],1, 90, 0]
446                : assert(_turtle3d_state_valid(state), "Supplied state is not valid")
447                  state,
448       finalstate = _turtle3d_repeat(commands, state, repeat)
449  )
450    assert(is_integer(repeat) && repeat>=0, "turtle3d repeat argument must be a nonnegative integer")
451    full_state  ? finalstate 
452  : !transforms ? deduplicate([for(T=finalstate[0]) apply(T,[0,0,0])])
453  : [for(i=idx(finalstate[0])) finalstate[0][i]*finalstate[1][i]];
454
455function _turtle3d_repeat(commands, state, repeat) =
456   repeat<=0 ? state : _turtle3d_repeat(commands, _turtle3d(commands, state), repeat-1);
457
458function _turtle3d_command_len(commands, index) =
459    let( one_or_two_arg = ["arcleft","arcright", "arcup", "arcdown", "arczrot", "arcyrot", "arcxrot"] )
460    in_list(commands[index],["repeat","arctodir","arcrot"]) ? 3 :   // Repeat, arctodir and arcrot commands require 2 args
461    // For these, the first arg is required, second arg is present if it is not a string or list
462    in_list(commands[index], one_or_two_arg) && len(commands)>index+2 && !is_string(commands[index+2]) && !is_list(commands[index+2])  ? 3 :  
463    is_string(commands[index+1]) || is_list(commands[index])? 1 :  // If 2nd item is a string it's must be a new command; 
464                                                                   // If first item is a list it's a compound command
465    2;                                 // Otherwise we have command and arg
466       
467function _turtle3d(commands, state, index=0) =
468    index >= len(commands) ? state :
469    _turtle3d(commands,
470            _turtle3d_command(commands[index],commands[index+1],commands[index+2],state,index),
471            index+_turtle3d_command_len(commands,index)
472        );
473
474function _turtle3d_rotation(command,angle,center) =
475  let(
476      myangle = (ends_with(command,"right") || ends_with(command,"up") ? -1 : 1 ) * angle
477  )
478  ends_with(command,"xrot") ? xrot(myangle,cp=center) :
479  ends_with(command,"yrot") ? yrot(myangle,cp=center) :
480  ends_with(command,"zrot") ? zrot(myangle,cp=center) :
481  ends_with(command,"right") || ends_with(command,"left") ? zrot(myangle,cp=center) :
482                                                            yrot(myangle,cp=center);
483
484// The turtle3d state maintains two lists of transformations that must be updated together. 
485// This function updates the state by appending a list of transforms and list of pre-transforms
486// to the state.
487function _tupdate(state, tran, pretran) =
488    [
489     concat(state[0],tran),
490     concat(state[1],pretran),
491     each list_tail(state,2)
492    ];
493
494function _turtle3d_command(command, parm, parm2, state, index) =
495    command == "repeat"?
496        assert(is_int(parm) && parm>=0,str("\"repeat\" command requires an integer repeat count at index ",index))
497        assert(is_list(parm2),str("\"repeat\" command requires a command list parameter at index ",index))
498        _turtle3d_repeat(parm2, state, parm) :
499    let(
500        trlist = 0,
501        prelist = 1,
502        movestep=2,
503        angle=3,
504        arcsteps=4,
505        parm = !is_string(parm) ? parm : undef,
506        parm2 = command=="arctodir" || command=="arcrot" ? parm2 
507              : !is_string(parm2) && !is_list(parm2) ? parm2 : undef,
508        needvec = ["jump", "xyzmove","setdir"],
509        neednum = ["untilx","untily","untilz","xjump","yjump","zjump","angle","length","scale","addlength"],
510        numornothing = ["right","left","up","down","xrot","yrot","zrot", "roll", "move"],
511        needtran = ["rot"],
512        chvec = !in_list(command,needvec) || is_vector(parm,3),
513        chnum = (!in_list(command,neednum) || is_num(parm))
514                && (!in_list(command,numornothing) || (is_undef(parm) || is_num(parm))),
515        chtran = !in_list(command,needtran) || is_matrix(parm,4,4),
516        lastT = last(state[trlist]),
517        lastPre = last(state[prelist]),
518        lastpt = apply(lastT,[0,0,0])
519    )
520    assert(chvec,str("\"",command,"\" requires a 3d vector parameter at index ",index))
521    assert(chnum,str("\"",command,"\" requires a numeric parameter at index ",index))
522    assert(chtran,str("\"",command,"\" requires a 4x4 transformation matrix at index ",index))
523    command=="move" ? _tupdate(state, [lastT*right(default(parm,1)*state[movestep])], [lastPre]):
524    in_list(command,["untilx","untily","untilz"]) ? (
525        let(
526            dirlist=[RIGHT, BACK, UP],
527            plane = [each dirlist[search([command],["untilx","untily","untilz"])[0]], parm],
528            step = [lastpt,apply(lastT,RIGHT)],
529            int = plane_line_intersection(plane, step, bounded=[true,false])
530        )
531        assert(is_def(int), str("\"",command,"\" never reaches desired goal at index ",index))
532        let(
533            size = is_vector(int,3) ? norm(int-lastpt) / norm(step[1]-step[0]) : 0
534        )
535        _tupdate(state, [lastT*right(size)], [lastPre])
536    ) :
537    command=="xmove" ? _tupdate(state,[right(default(parm,1)*state[movestep])*lastT],[lastPre]):
538    command=="ymove" ? _tupdate(state,[back(default(parm,1)*state[movestep])*lastT],[lastPre]):
539    command=="zmove" ? _tupdate(state,[up(default(parm,1)*state[movestep])*lastT],[lastPre]):
540    command=="xyzmove" ? _tupdate(state,[move(parm)*lastT],[lastPre]):
541    command=="jump" ? _tupdate(state,[move(parm-lastpt)*lastT],[lastPre]):
542    command=="xjump" ? _tupdate(state,[move([parm,lastpt.y,lastpt.z]-lastpt)*lastT],[lastPre]):
543    command=="yjump" ? _tupdate(state,[move([lastpt.x,parm,lastpt.z]-lastpt)*lastT],[lastPre]):
544    command=="yjump" ? _tupdate(state,[move([lastpt.x,lastpt.y,parm]-lastpt)*lastT],[lastPre]):
545    command=="angle" ? assert(parm!=0,str("\"",command,"\" requires nonnegative argument at index ",index))
546                       list_set(state, angle, parm) :
547    command=="length" ? list_set(state, movestep, parm) :
548    command=="scale" ?  list_set(state, movestep, parm*state[movestep]) :
549    command=="addlength" ?  list_set(state, movestep, state[movestep]+parm) :
550    command=="arcsteps" ?  assert(is_int(parm) && parm>0, str("\"",command,"\" requires a postive integer argument at index ",index))
551                           list_set(state, arcsteps, parm) :
552    command=="roll" ? list_set(state, trlist, concat(list_head(state[trlist]), [lastT*xrot(parm)])):
553    in_list(command,["right","left","up","down"]) ? 
554        list_set(state, trlist, concat(list_head(state[trlist]), [lastT*_turtle3d_rotation(command,default(parm,state[angle]))])):
555    in_list(command,["xrot","yrot","zrot"]) ?
556        let(
557             Trot = _rotpart(lastT),      // Extract rotational part of lastT
558             shift = _transpart(lastT)    // Translation part of lastT
559        )
560        list_set(state, trlist, concat(list_head(state[trlist]),
561                                       [move(shift)*_turtle3d_rotation(command,default(parm,state[angle])) * Trot])):
562    command=="rot" ?
563        let(
564             Trot = _rotpart(lastT),      // Extract rotational part of lastT
565             shift = _transpart(lastT)    // Translation part of lastT
566        )
567        list_set(state, trlist, concat(list_head(state[trlist]),[move(shift) * parm * Trot])):
568    command=="setdir" ?
569        let(
570             Trot = _rotpart(lastT),
571             shift = _transpart(lastT)
572        )
573        list_set(state, trlist, concat(list_head(state[trlist]),
574                                       [move(shift)*rot(from=apply(Trot,RIGHT),to=parm) * Trot ])):
575    in_list(command,["arcleft","arcright","arcup","arcdown"]) ?
576        assert(is_num(parm),str("\"",command,"\" command requires a numeric radius value at index ",index))
577        let(
578            radius = state[movestep]*parm,
579            myangle = default(parm2,state[angle])
580        )
581        assert(myangle!=0, str("\"",command,"\" command requires a nonzero angle at index ",index))
582        let(
583            length = 2*PI*radius * abs(myangle)/360, 
584            center = [0,
585                      command=="arcleft"?radius:command=="arcright"?-radius:0,
586                      command=="arcdown"?-radius:command=="arcup"?radius:0],
587            steps = state[arcsteps]==0 ? segs(abs(radius)) : state[arcsteps]
588        )    
589        _tupdate(state,
590                 [for(n=[1:1:steps]) lastT*_turtle3d_rotation(command,myangle*n/steps,center)],
591                 repeat(lastPre,steps)):
592    in_list(command,["arcxrot","arcyrot","arczrot"]) ?
593        assert(is_num(parm),str("\"",command,"\" command requires a numeric radius value at index ",index))  
594        let(
595            radius = state[movestep]*parm,
596            myangle = default(parm2,state[angle])
597        )
598        assert(myangle!=0, str("\"",command,"\" command requires a nonzero angle at index ",index))
599        let(
600            length = 2*PI*radius * abs(myangle)/360, 
601            steps = state[arcsteps]==0 ? segs(abs(radius)) : state[arcsteps],
602            Trot = _rotpart(lastT),
603            shift = _transpart(lastT),
604            v = apply(Trot,RIGHT),
605            dir = command=="arcxrot" ? RIGHT
606                : command=="arcyrot" ? BACK
607                : UP,
608            projv = v - (dir*v)*dir,
609            center = sign(myangle) * radius * cross(dir,projv),
610            slope = dir*v / norm(projv),
611            vshift = dir*slope*length
612        )
613        assert(!all_zero(projv), str("Rotation acts as twist, which does not produce a valid arc, at index ",index))
614        _tupdate(state,
615                 [for(n=[1:1:steps]) move(shift+vshift*n/steps)*_turtle3d_rotation(command,myangle*n/steps,center)*Trot],
616                 repeat(lastPre,steps)):
617    command=="arctodir" || command=="arcrot"?
618        assert(command!="arctodir" || is_vector(parm2,3),str("\"",command,"\" command requires a direction vector at index ",index))
619        assert(command!="arcrot" || is_matrix(parm2,4,4),str("\"",command,"\" command requires a transformation matrix at index ",index))  
620        let(
621            Trot = _rotpart(lastT),
622            shift = _transpart(lastT),
623            v = apply(Trot,RIGHT),
624            rotparms = command=="arctodir"
625                              ? rot_decode(rot(from=v,to=parm2))
626                              : rot_decode(parm2),
627            dir = rotparms[1],
628            myangle = rotparms[0],
629            projv = v - (dir*v)*dir,
630            slope = dir*v / norm(projv),
631            radius = state[movestep]*parm,
632            length = 2*PI*radius * myangle/360, 
633            vshift = dir*slope*length,
634            steps = state[arcsteps]==0 ? segs(abs(radius)) : state[arcsteps],
635            center = radius * cross(dir,projv)
636        )
637        assert(!all_zero(projv), str("Rotation acts as twist, which does not produce a valid arc, at index ",index))
638        _tupdate(state,
639                 [for(n=[1:1:steps]) move(shift+vshift*n/steps)*rot(n/steps*myangle,v=rotparms[1],cp=center)*Trot],
640                 repeat(lastPre,steps)):
641    is_list(command) ?
642        let(list_update = _turtle3d_list_command(command, state[arcsteps], state[movestep], lastT, lastPre, index))
643        _tupdate(state, list_update[0], list_update[1]):
644    assert(false,str("Unknown turtle command \"",command,"\" at index",index))
645    [];
646       
647
648function _turtle3d_list_command(command,arcsteps,movescale, lastT,lastPre,index) =
649   let(
650       reverse_index = search(["reverse"], command, 0)[0],
651       reverse = len(reverse_index)==1,
652       arcind = search(["arc"], command, 0)[0],
653       moveind = search(["move"], command, 0)[0],
654       movearcok = (arcind==[] || max(arcind)==0) && (moveind==[] || max(moveind)==0)
655   )
656   assert(len(reverse_index)<=1, str("Only one \"reverse\" is allowed at index ",index))
657   assert(!reverse || reverse_index[0]%2==0, str("Error processing compound command at index ",index))
658   assert(movearcok, str("\"move\" or \"arc\" must appear at the beginning of the compound command at index ",index))
659   assert(!reverse || len(command)%2==1,str("Odd number of entries in [keyword,value] list (after removing \"reverse\") at index ",index))
660   assert(reverse || len(command)%2==0,str("Odd number of entries in [keyword,value] list at index ",index))
661   let(
662       
663       command = list_remove(command, reverse_index),
664       keys=command[0]=="move" ?
665               struct_set([
666                           ["move", 0],
667                           ["twist",0],
668                           ["grow",1],
669                           ["shrink",1],
670                           ["steps",1],
671                           ["roll",0],
672                          ],
673                          command, grow=false)
674          :command[0]=="arc" ?
675               struct_set([
676                           ["arc", 0],
677                           ["up", 0],
678                           ["down", 0],
679                           ["left", 0],
680                           ["right", 0],
681                           ["twist",0],
682                           ["grow",1],
683                           ["shrink",1],
684                           ["steps",0],
685                           ["roll",0],
686                           ["rot", 0],
687                           ["todir", 0],
688                           ["xrot", 0],
689                           ["yrot", 0],
690                           ["zrot", 0],
691                          ],
692                          command, grow=false)
693          :assert(false,str("Unknown compound turtle3d command \"",command,"\" at index ",index)),
694       move = command[0]=="move" ? movescale*struct_val(keys,"move") : 0,
695       flip = reverse ? xflip() : ident(4),            // If reverse is given we set flip 
696       radius = movescale*first_defined([struct_val(keys,"arc"),0]),  // arc radius if given
697       twist = struct_val(keys,"twist"),
698       grow = force_list(struct_val(keys,"grow"),2),
699       shrink = force_list(struct_val(keys, "shrink"),2)
700   )
701   assert(is_num(radius), str("Radius parameter to \"arc\" must be a number in command at index ",index))
702   assert(is_vector(grow,2), str("Parameter to \"grow\" must be a scalar or 2d vector at index ",index))
703   assert(is_vector(shrink,2), str("Parameter to \"shrink\" must be a scalar or 2d vector at index ",index))
704   let(
705       scaling = point3d(v_div(grow,shrink),1),
706       usersteps = struct_val(keys,"steps"),
707       roll = struct_val(keys,"roll"),
708       ////////////////////////////////////////////////////////////////////////////////////////
709       ////  Next section is computations for relative rotations: "left", "right", "up" or "down"
710       right = default(struct_val(keys,"right"),0),
711       left = default(struct_val(keys,"left"),0),
712       up = default(struct_val(keys,"up"),0),
713       down = default(struct_val(keys,"down"),0),
714       angleok = assert(command[0]=="move" || (is_num(right) && is_num(left) && is_num(up) && is_num(down)),
715                        str("Must give numeric argument to \"left\", \"right\", \"up\" and \"down\" in command at index ",index))
716                 command[0]=="move" || ((up-down==0 || abs(left-right)<180) && (left-right==0 || abs(up-down)<180))
717   )
718   assert(command[0]=="move" || right==0 || left==0, str("Cannot specify both \"left\" and \"right\" in command at index ",index))
719   assert(command[0]=="move" || up==0 || down==0, str("Cannot specify both \"up\" and \"down\" in command at index ",index))
720   assert(angleok, str("Mixed angles must all be below 180 at index ",index))
721   let(
722        newdir = apply(zrot(left-right)*yrot(down-up),RIGHT),     // This is the new direction turtle points relative to RIGHT
723        relaxis = left-right == 0 ? BACK
724                : down-up == 0 ? UP
725                : cross(RIGHT,newdir),         // This is the axis of rotation for "right", "left", "up" or "down"
726        angle = command[0]=="move" ? 0 :
727                  left-right==0 || down-up==0 ? down-up+left-right :
728                  vector_angle(RIGHT,newdir),    // And this is the angle for that case.
729        center = -radius * (                     // Center of rotation for this case
730                      left-right == 0 ? [0,0,sign(down-up)]
731                    : down-up == 0 ? [0,sign(right-left),0]
732                    :       unit(cross(RIGHT,cross(RIGHT,newdir)),[0,0,0])
733                 ),    
734        ///////////////////////////////////////////////
735        // Next we compute values for absolute rotations: "xrot", "xrot", "yrot", "zrot", and "todir"
736        //
737        xrotangle = struct_val(keys,"xrot"),
738        yrotangle = struct_val(keys,"yrot"),
739        zrotangle = struct_val(keys,"zrot"),
740        rot = struct_val(keys,"rot"),
741        todir = struct_val(keys,"todir"),
742        // Compute rotation angle and axis for the absolute rotation (or undef if no absolute rotation is given)
743        abs_angle_axis =
744            command[0]=="move" ? [undef,CENTER] :
745            let(nzcount=len([for(entry=[xrotangle,yrotangle,zrotangle,rot,todir]) if (entry!=0) 1]))
746            assert(nzcount<=1, str("You can only define one of \"xrot\", \"yrot\", \"zrot\", \"rot\", and \"todir\" at index ",index))
747            rot!=0 ?   assert(is_matrix(rot,4,4),str("Argument to \"rot\" is not a 3d transformation matrix at index ",index))
748                       rot_decode(rot)
749          : todir!=0 ? assert(is_vector(todir,3),str("Argument to \"todir\" is not a length 3 vector at index ",index))
750                       rot_decode(rot(from=v, to=todir))
751          : xrotangle!=0 ? [xrotangle, RIGHT]
752          : yrotangle!=0 ? [yrotangle, BACK]
753          : zrotangle!=0 ? [zrotangle, UP]
754          : [undef,CENTER],
755        absangle = abs_angle_axis[0],
756        absaxis = abs_angle_axis[1],
757        // Computes the extra shift and center with absolute rotation
758        Trot = _rotpart(lastT),  
759        shift = _transpart(lastT), 
760        v = apply(Trot,RIGHT),           // Current direction
761        projv = v - (absaxis*v)*absaxis, // Component of rotation axis orthogonal to v
762        abscenter = is_undef(absangle) ? undef : sign(absangle) * radius * cross(absaxis,projv),    // absangle might be undef if command is "move"
763        slope = absaxis*v / norm(projv),       // This computes the shift in the direction along the rotational axis
764        vshift = is_undef(absangle) ? undef : absaxis*slope* 2*PI*radius*absangle/360
765    )
766    // At this point angle is nonzero if and only if a relative angle command (left, right, up down) was given,
767    //               absangle is defined if and only if an absolute angle command was given
768    assert(is_undef(absangle) || absangle!=0, str("Arc rotation with zero angle at index ",index))
769    assert(angle==0 || is_undef(absangle), str("Mixed relative and absolute rotations at index ",index))
770    assert(is_int(usersteps) && usersteps>=0 && (command[0]=="arc" || usersteps>=1),
771           str("Steps value ",usersteps," invalid at index ",index))
772    assert(is_undef(absangle) || !all_zero(projv), str("Rotation acts as twist, which does not produce a valid arc at index ",index))
773    let( 
774        steps = usersteps != 0 ? usersteps
775              : arcsteps != 0 ? arcsteps
776              : ceil(segs(abs(radius)) * abs(first_defined([absangle,angle]))/360),
777        // The next line computes a list of pairs [trans,pretrans] for the segment or arc
778        result =  is_undef(absangle)
779                  ? [for(n=[1:1:steps]) let(frac=n/steps)
780                              [lastT * flip * right(frac*move) * (angle==0?ident(4):rot(frac*angle,v=relaxis,cp=center)) * xrot(frac*roll),
781                               lastPre * zrot(frac*twist) * scale(lerp([1,1,1],scaling,frac))]
782                    ]
783                  : [for(n=[1:1:steps]) let(frac=n/steps) 
784                              [move(shift+vshift*frac) * rot(frac*absangle,v=absaxis,cp=abscenter)*Trot * xrot(frac*roll),
785                               lastPre * zrot(frac*twist) * scale(lerp([1,1,1],scaling,frac))]
786                    ]
787    )                     // Transpose converts the result into a list of the form [[trans1,trans2,...],[pretran1,pretran2,...]],
788    transpose(result);    // which is required by _tupdate
789
790