1//////////////////////////////////////////////////////////////////////
  2// LibFile: polyhedra.scad
  3//   Generate Platonic solids, Archimedian solids, Catalan polyhedra, the trapezohedron, and some stellated polyhedra.
  4//   You can also stellate any of the polyhedra, select polyhedra by their characterics and position objects on polyhedra faces. 
  5// Includes:
  6//   include <BOSL2/std.scad>
  7//   include <BOSL2/polyhedra.scad>
  8// FileGroup: Parts
  9// FileSummary: Platonic, Archimidean, Catalan, and stellated polyhedra
 10//////////////////////////////////////////////////////////////////////
 11
 12
 13// CommonCode:
 14//   $fn=96;
 15
 16
 17// Section: Polyhedra
 18
 19
 20// Groups entries in "arr" into groups of equal values and returns index lists of those groups
 21
 22function _unique_groups(m) = [
 23    for (i=[0:1:len(m)-1]) let(
 24        s = search([m[i]], m, 0)[0]
 25    ) if (s[0]==i) s
 26];
 27
 28
 29// TODO
 30//
 31// Use volume info?
 32// Support choosing a face number down
 33// Support multiple inspheres/outspheres when appropriate?
 34// face order for children?
 35// orient faces so an edge is parallel to the x-axis
 36//
 37
 38
 39// Module: regular_polyhedron()
 40// Usage: Selecting a polyhedron
 41//   regular_polyhedron([name],[index=],[type=],[faces=],[facetype=],[hasfaces=],...) [CHILDREN];
 42// Usage: Controlling the size and position of the polyhedron
 43//   regular_polyhedron(..., [or=|r=|d=],[ir=],[mr=],[side=],[facedown=],[anchor=], ...) [CHILDREN];]
 44// Usage: Other options that change the polyhedron or handling of children
 45//   regular_polyhedron(..., [draw=], [rounding=], [stellate=], [repeat=], [rotate_children=]) [CHILDREN];
 46// Usage: options only for the trapezohedron
 47//   regular_polyhedron("trapezohedron", [longside=],[h=], ...) [CHILDREN];
 48// Description:
 49//   Creates a regular polyhedron with optional rounding.  Children are placed on the polyhedron's faces.  (Note that this is not attachable.)
 50//   .
 51//   **Selecting the polyhedron:**
 52//   You constrain the polyhedra list by specifying different characteristics, that must all be met
 53//   * `name`: e.g. `"dodecahedron"` or `"pentagonal icositetrahedron"`
 54//   * `type`: Options are `"platonic"`, `"archimedean"` and `"catalan"`
 55//   * `faces`: The required number of faces
 56//   * `facetype`: The required face type(s).  List of vertex counts for the faces.  Exactly the listed types of faces must appear:
 57//     * `facetype = 3`: polyhedron with all triangular faces.
 58//     * `facetype = [5,6]`: polyhedron with only pentagons and hexagons. (Must have both!)
 59//   * hasfaces: The list of vertex counts for faces; at least one listed type must appear:
 60//     * `hasfaces = 3`: polygon has at least one triangular face
 61//     * `hasfaces = [5,6]`: polygon has a hexagonal or a pentagonal face
 62//   .
 63//   The result is a list of selected polyhedra.  You then specify `index` to choose which one of the
 64//   remaining polyhedra you want.  If you don't give `index` the first one on the list is created.
 65//   Two examples:
 66//   * `faces=12, index=2`:  Creates the 3rd solid with 12 faces
 67//   * `type="archimedean", faces=14`: Creates the first archimedean solid with 14 faces (there are 3)
 68//   .
 69//   **Choosing the size of your polyhedron:**
 70//   The default is to create a polyhedron whose smallest edge has length 1.  You can specify the
 71//   smallest edge length with the size option.  Alternatively you can specify the size of the
 72//   inscribed sphere, midscribed sphere, or circumscribed sphere using `ir`, `mr` and `cr` respectively.
 73//   If you specify `cr=3` then the outermost points of the polyhedron will be 3 units from the center.
 74//   If you specify `ir=3` then the innermost faces of the polyhedron will be 3 units from the center.
 75//   For the platonic solids every face meets the inscribed sphere and every corner touches the
 76//   circumscribed sphere.  For the Archimedean solids the inscribed sphere will touch only some of
 77//   the faces and for the Catalan solids the circumscribed sphere meets only some of the corners.
 78//   .
 79//   **Orientation:**
 80//   Orientation is controled by the facedown parameter.  Set this to false to get the canonical orientation.
 81//   Set it to true to get the largest face oriented down.  If you set it to a number the module searches for
 82//   a face with the specified number of vertices and orients that face down.
 83//   .
 84//   **Rounding:**
 85//   If you specify the rounding parameter the module makes a rounded polyhedron by first creating an
 86//   undersized model and then expanding it with `minkowski()`.  This only produces the correct result
 87//   if the in-sphere contacts all of the faces of the polyhedron, which is true for the platonic, the
 88//   catalan solids and the trapezohedra but false for the archimedean solids.
 89//   .
 90//   **Children:**
 91//   The module places children on the faces of the polyhedron.  The child coordinate system is
 92//   positioned so that the origin is the center of the face.  If `rotate_children` is true (default)
 93//   then the coordinate system is oriented so the z axis is normal to the face, which lies in the xy
 94//   plane.  If you give `repeat=true` (default) the children are cycled through to cover all faces.
 95//   With `repeat=false` each child is used once.  You can specify `draw=false` to suppress drawing of
 96//   the polyhedron, e.g. to use for `difference()` operations.  The module sets various parameters
 97//   you can use in your children (see the side effects list below).
 98//   .
 99//   **Stellation:**
100//   Technically stellation is an operation of shifting the polyhedron's faces to produce a new shape
101//   that may have self-intersecting faces.  OpenSCAD cannot handle self-intersecting faces, so we
102//   instead erect a pyramid on each face, a process technically referred to as augmentation.  The
103//   height of the pyramid is given by the `stellate` argument.  If `stellate` is `false` or `0` then
104//   no stellation is performed.  Otherwise stellate gives the pyramid height as a multiple of the
105//   edge length.  A negative pyramid height can be used to perform excavation, where a pyramid is
106//   removed from each face.
107//   .
108//   **Special Polyhedra:**
109//   These can be selected only by name and may require different parameters, or ignore some standard
110//   parameters.
111//   * Trapezohedron: a family of solids with an even number of kite shaped sides.
112//     One example of a trapezohedron is the d10 die, which is a 10 face trapezohedron.
113//     You must specify exactly two of `side`, `longside`, `h`, and `r` (or `d`).
114//     You cannot create trapezohedron shapes using `mr`, `ir`, or `or`.
115//     * `side`: Length of the short side.
116//     * `longside`: Length of the long side that extends to the apex.
117//     * `h`: Distance from the center to the apex.
118//     * `r`: Radius of the polygon that defines the equatorial vertices.
119//     * `d`: Diameter of the polygon that defines the equatorial vertices.
120//   .
121//   * Named stellations: various polyhedra such as the Kepler-Poinsot solids are stellations with
122//    specific pyramid heights.  To make them easier to generate you can specify them by name.
123//    This is equivalent to giving the name of the appropriate base solid and the magic stellate
124//    parameter needed to produce that shape.  The supported solids are:
125//     * `"great dodecahedron"`
126//     * `"small stellated dodecahedron"`
127//     * `"great stellated dodecahedron"`
128//     * `"small triambic icosahedron"`
129//
130// Arguments:
131//   name = Name of polyhedron to create.
132//   ---
133//   index = Index to select from polyhedron list.  Default: 0.
134//   type = Type of polyhedron: "platonic", "archimedean", "catalan".
135//   faces = Number of faces.
136//   facetype = Scalar or vector listing required type of faces as vertex count.  Polyhedron must have faces of every type listed and no other types.
137//   hasfaces = Scalar of vector list face vertex counts.  Polyhedron must have at least one of the listed types of face.
138//   side = Length of the smallest edge of the polyhedron.  Default: 1 (if no radius or diameter is given).  
139//   ir = inner radius.  Polyhedron is scaled so it has the specified inner radius. 
140//   mr = middle radius.  Polyhedron is scaled so it has the specified middle radius.  
141//   or / r / d = outer radius.   Polyhedron is scaled so it has the specified outer radius. 
142//   anchor = Side of the origin to anchor to.  The bounding box of the polyhedron is aligned as specified.  Default: `CENTER`
143//   facedown = If false display the solid in native orientation.  If true orient it with a largest face down.  If set to a vertex count, orient it so a face with the specified number of vertices is down.  Default: true.
144//   rounding = Specify a rounding radius for the shape.  Note that depending on $fn the dimensions of the shape may have small dimensional errors.
145//   repeat = If true then repeat the children to fill all the faces.  If false use only the available children and stop.  Default: true.
146//   draw = If true then draw the polyhedron.  If false, draw the children but not the polyhedron.  Default: true.
147//   rotate_children = If true then orient children normal to their associated face.  If false orient children to the parent coordinate system.  Default: true.
148//   stellate = Set to a number to erect a pyramid of that height on every face of your polyhedron.  The height is a multiple of the side length.  Default: false.
149//   longside = Specify the long side length for a trapezohedron.  Invalid for other shapes.
150//   h = Specify the height of the apex for a trapezohedron.  Invalid for other shapes.
151//
152// Side Effects:
153//   `$faceindex` - Index number of the face
154//   `$face` - Coordinates of the face (2d if rotate_children==true, 3d if not)
155//   `$center` - Face center in the child coordinate system
156//
157// Examples: All of the available polyhedra by name in their native orientation
158//   regular_polyhedron("tetrahedron", facedown=false);
159//   regular_polyhedron("cube", facedown=false);
160//   regular_polyhedron("octahedron", facedown=false);
161//   regular_polyhedron("dodecahedron", facedown=false);
162//   regular_polyhedron("icosahedron", facedown=false);
163//   regular_polyhedron("truncated tetrahedron", facedown=false);
164//   regular_polyhedron("truncated octahedron", facedown=false);
165//   regular_polyhedron("truncated cube", facedown=false);
166//   regular_polyhedron("truncated icosahedron", facedown=false);
167//   regular_polyhedron("truncated dodecahedron", facedown=false);
168//   regular_polyhedron("cuboctahedron", facedown=false);
169//   regular_polyhedron("icosidodecahedron", facedown=false);
170//   regular_polyhedron("rhombicuboctahedron", facedown=false);
171//   regular_polyhedron("rhombicosidodecahedron", facedown=false);
172//   regular_polyhedron("truncated cuboctahedron", facedown=false);
173//   regular_polyhedron("truncated icosidodecahedron", facedown=false);
174//   regular_polyhedron("snub cube", facedown=false);
175//   regular_polyhedron("snub dodecahedron", facedown=false);
176//   regular_polyhedron("triakis tetrahedron", facedown=false);
177//   regular_polyhedron("tetrakis hexahedron", facedown=false);
178//   regular_polyhedron("triakis octahedron", facedown=false);
179//   regular_polyhedron("pentakis dodecahedron", facedown=false);
180//   regular_polyhedron("triakis icosahedron", facedown=false);
181//   regular_polyhedron("rhombic dodecahedron", facedown=false);
182//   regular_polyhedron("rhombic triacontahedron", facedown=false);
183//   regular_polyhedron("deltoidal icositetrahedron", facedown=false);
184//   regular_polyhedron("deltoidal hexecontahedron", facedown=false);
185//   regular_polyhedron("disdyakis dodecahedron", facedown=false);
186//   regular_polyhedron("disdyakis triacontahedron", facedown=false);
187//   regular_polyhedron("pentagonal icositetrahedron", facedown=false);
188//   regular_polyhedron("pentagonal hexecontahedron", facedown=false);
189//   regular_polyhedron("trapezohedron",faces=10, side=1, longside=2.25, facedown=false);
190//   regular_polyhedron("great dodecahedron");
191//   regular_polyhedron("small stellated dodecahedron");
192//   regular_polyhedron("great stellated dodecahedron");
193//   regular_polyhedron("small triambic icosahedron");
194// Example: Third Archimedean solid
195//   regular_polyhedron(type="archimedean", index=2);
196// Example(Med): Solids that have 8 or 10 vertex faces
197//   N = len(regular_polyhedron_info("index set", hasfaces=[8,10]));
198//   for(i=[0:N-1]) right(3*i)
199//     regular_polyhedron(hasfaces=[8,10], index=i, mr=1);
200// Example(Big): Solids that include a quadrilateral face
201//   N = len(regular_polyhedron_info("index set", hasfaces=4));
202//   for(i=[0:N-1]) right(3*i)
203//     regular_polyhedron(hasfaces=4, index=i, mr=1);
204// Example(Med): Solids with only quadrilateral faces
205//   N = len(regular_polyhedron_info("index set", facetype=4));
206//   for(i=[0:N-1]) right(3*i)
207//     regular_polyhedron(facetype=4, index=i, mr=1);
208// Example: Solids that have both pentagons and hexagons and no other face types
209//   N = len(regular_polyhedron_info("index set", facetype=[5,6]));
210//   for(i=[0:N-1]) right(3*i)
211//     regular_polyhedron(facetype=[5,6], index=i, mr=1);
212// Example: Rounded octahedron
213//   regular_polyhedron("octahedron", side=1, rounding=.2);
214// Example: Rounded catalon solid
215//   regular_polyhedron("rhombic dodecahedron", side=1, rounding=0.2);
216// Example(Med): Rounded Archimedean solid compared to unrounded version.  The small faces are shifted back from their correct position.
217//   %regular_polyhedron(type="archimedean", mr=1, rounding=0);
218//   regular_polyhedron(type="archimedean", mr=1, rounding=0.3);
219// Example: Two children are distributed arbitrarily over the faces
220//   regular_polyhedron(faces=12,index=2,repeat=true) {
221//     color("red") sphere(r=.1);
222//     color("green") sphere(r=.1);
223//   }
224// Example(FlatSpin,VPD=100): Difference the children from the polyhedron; children depend on $faceindex
225//   difference(){
226//     regular_polyhedron("tetrahedron", side=25);
227//     regular_polyhedron("tetrahedron", side=25,draw=false)
228//       down(.3) linear_extrude(height=1)
229//         text(str($faceindex),halign="center",valign="center");
230//   }
231// Example(Big): With `rotate_children` you can control direction of the children.
232//   regular_polyhedron(name="tetrahedron", anchor=UP, rotate_children=true)
233//     cylinder(r=.1, h=.5);
234//   right(2) regular_polyhedron(name="tetrahedron", anchor=UP, rotate_children=false)
235//     cylinder(r=.1, h=.5);
236// Example(FlatSpin,Med,VPD=15): Using `$face` you can have full control of the construction of your children.  This example constructs the Great Icosahedron.
237//   module makestar(pts) {    // Make a star from a point list
238//       polygon(
239//         [
240//           for(i=[0:len(pts)-1]) let(
241//             p0=select(pts,i),
242//             p1=select(pts,i+1),
243//             center=(p0+p1)/2,
244//             v=sqrt(7/4-PHI)*(p1-p0)
245//           ) each [p0, [v.y+center.x, -v.x+center.y]]
246//         ]
247//       );
248//   }
249//   regular_polyhedron("dodecahedron", side=1, repeat=true)
250//   linear_extrude(scale=0, height=sqrt((5+2*sqrt(5))/5)) makestar($face);
251// Example(Med): The spheres are all radius 1 and the octahedra are sized to match the in-sphere, mid-sphere and out-sphere.  The sphere size is slightly adjusted for the in-sphere and out-sphere so you can see the relationship: the sphere is tangent to the faces for the former and the corners poke out for the latter.  Note also the difference in the size of the three octahedra.
252//   sphere(r=1.005);
253//   %regular_polyhedron("octahedron", ir=1, facedown=false);
254//   right(3.5) {
255//     sphere(r=1);
256//     %regular_polyhedron("octahedron", mr=1, facedown=false);
257//   }
258//   right(6.5) {
259//     %sphere(r=.95);  // Slightly undersized sphere means the points poke out a bit
260//     regular_polyhedron("octahedron", or=1,facedown=false);
261//   }
262// Example(Med): For the Archimdean solids the in-sphere does not touch all of the faces, as shown by this example, but the circumscribed sphere meets every vertex.  (This explains the problem for rounding over these solids because the rounding method uses the in-sphere.)
263//   sphere(r=1.005);
264//   %regular_polyhedron("snub dodecahedron", ir=1, facedown=false);
265//   right(3) {
266//     sphere(r=1);
267//     %regular_polyhedron("snub dodecahedron", mr=1, facedown=false);
268//   }
269//   right(6) {
270//     %sphere(r=.99);
271//     regular_polyhedron("snub dodecahedron", or=1,facedown=false);
272//   }
273// Example(Med): For a Catalan solid the in-sphere touches every face but the circumscribed sphere only touches some vertices.
274//   sphere(r=1.002);
275//   %regular_polyhedron("pentagonal hexecontahedron", ir=1, facedown=false);
276//   right(3) {
277//     sphere(r=1);
278//     %regular_polyhedron("pentagonal hexecontahedron", mr=1, facedown=false);
279//   }
280//   right(6) {
281//     %sphere(r=.98);
282//     regular_polyhedron("pentagonal hexecontahedron", or=1,facedown=false);
283//   }
284// Example: Stellate an Archimedian solid, which has mixed faces
285//   regular_polyhedron("truncated icosahedron",stellate=1.5,or=1);
286// Example: Stellate a Catalan solid where faces are not regular
287//   regular_polyhedron("triakis tetrahedron",stellate=0.5,or=1);
288module regular_polyhedron(
289    name=undef,
290    index=undef,
291    type=undef,
292    faces=undef,
293    facetype=undef,
294    hasfaces=undef,
295    side=undef,
296    ir=undef,
297    mr=undef,
298    or=undef,
299    r=undef,
300    d=undef,
301    anchor=CENTER,
302    rounding=0,
303    repeat=true,
304    facedown=true,
305    draw=true,
306    rotate_children=true,
307    stellate = false,
308    longside=undef, // special parameter for trapezohedron
309    h=undef         // special parameter for trapezohedron
310) {
311    dummy=assert(is_num(rounding) && rounding>=0, "'rounding' must be nonnegative");
312    entry = regular_polyhedron_info(
313        "fullentry", name=name, index=index,
314        type=type, faces=faces, facetype=facetype,
315        hasfaces=hasfaces, side=side,
316        ir=ir, mr=mr, or=or,
317        r=r, d=d,
318        anchor=anchor, 
319        facedown=facedown,
320        stellate=stellate,
321        longside=longside, h=h
322    );
323    scaled_points = entry[0];
324    translation = entry[1];
325    face_triangles = entry[2];
326    faces = entry[3];
327    face_normals = entry[4];
328    in_radius = entry[5];
329    if (draw){
330        if (rounding==0)
331            polyhedron(move(translation, p=scaled_points), faces = face_triangles);
332        else {
333            fn = segs(rounding);
334            rounding = rounding/cos(180/fn);
335            adjusted_scale = 1 - rounding / in_radius;
336            minkowski(){
337                sphere(r=rounding, $fn=fn);
338                polyhedron(move(translation,p=adjusted_scale*scaled_points), faces = face_triangles);
339            }
340        }
341    }
342    translate(translation)
343    if ($children>0) {
344        maxrange = repeat ? len(faces)-1 : $children-1;
345        for(i=[0:1:maxrange]) {
346            // Would like to orient so an edge (longest edge?) is parallel to x axis
347            facepts = select(scaled_points, faces[i]);
348            $center = -mean(facepts);
349            cfacepts = move($center, p=facepts);
350            $face = rotate_children
351                      ? path2d(frame_map(z=face_normals[i], x=facepts[0]-facepts[1], reverse=true, p=cfacepts))
352                      : cfacepts;
353            $faceindex = i;
354            translate(-$center)
355            if (rotate_children) {
356                frame_map(z=face_normals[i], x=facepts[0]-facepts[1])
357                children(i % $children);
358            } else {
359                children(i % $children);
360            }
361        }
362    }
363}
364
365/////////////////////////////////////////////////////////////////////////////
366//
367// Some internal functions used to generate polyhedra data
368//
369// All permutations and even permutations of three items
370//
371function _even_perms(v) = [v, [v[2], v[0], v[1]], [v[1],v[2],v[0]]];
372function _all_perms(v) = [v, [v[2], v[0], v[1]], [v[1],v[2],v[0]], [v[1],v[0],v[2]],[v[2],v[1],v[0]],[v[0],v[2],v[1]]];
373//
374// Point reflections across all planes.    In the unconstrained case, this means one point becomes 8 points.
375//
376// sign=="even" means an even number of minus signs (odd number of plus signs)
377// sign=="odd" means an odd number of minus signs (even number of plus signs)
378//
379function _point_ref(points, sign="both") =
380    unique([
381        for(i=[-1,1],j=[-1,1],k=[-1,1])
382            if (sign=="both" || sign=="even" && i*j*k>0 || sign=="odd" && i*j*k<0)
383                each [for(point=points) v_mul(point,[i,j,k])]
384    ]);
385//
386_tribonacci=(1+4*cosh(acosh(2+3/8)/3))/3;
387//
388/////////////////////////////////////////////////////////////////////////////
389//
390// Polyhedra data table.
391// The polyhedra information is from Wikipedia and http://dmccooey.com/polyhedra/
392//
393_polyhedra_ = [
394    // Platonic Solids
395
396    ["tetrahedron", "platonic", 4,[3], 2*sqrt(2), sqrt(6)/12, sqrt(2)/4, sqrt(6)/4, 1/6/sqrt(2),
397            _point_ref([[1,1,1]], sign="even")],
398    ["cube", "platonic", 6, [4], 2, 1/2, 1/sqrt(2), sqrt(3)/2, 1,
399            _point_ref([[1,1,1]])],
400    ["octahedron", "platonic", 8, [3], sqrt(2), sqrt(6)/6, 1/2, sqrt(2)/2, sqrt(2)/3,
401            _point_ref(_even_perms([1,0,0]))],
402    ["dodecahedron", "platonic", 12, [5], 2/PHI, sqrt(5/2+11*sqrt(5)/10)/2, (3+sqrt(5))/4, sqrt(3)*PHI/2, (15+7*sqrt(5))/4,
403            _point_ref(concat([[1,1,1]],_even_perms([0,PHI,1/PHI])))],
404    ["icosahedron", "platonic", 20, [3], 2, PHI*PHI/2/sqrt(3), cos(36), sin(72), 5*(3+sqrt(5))/12,
405            _point_ref(_even_perms([0,1,PHI]))],
406
407    // Archimedian Solids, listed in order by Wenniger number, W6-W18
408
409    ["truncated tetrahedron", "archimedean", 8,[6,3], sqrt(8), sqrt(6)/4, 3*sqrt(2)/4, sqrt(11/8), 23*sqrt(2)/12,
410            _point_ref(_all_perms([1,1,3]),sign="even")],
411    ["truncated octahedron", "archimedean", 14, [6,4], sqrt(2), sqrt(6)/2, 1.5, sqrt(10)/2, 8*sqrt(2),
412            _point_ref(_all_perms([0,1,2]))],
413    ["truncated cube", "archimedean", 14, [8,3], 2*(sqrt(2)-1), (1+sqrt(2))/2, 1+sqrt(2)/2, sqrt(7+4*sqrt(2))/2, 7+14*sqrt(2)/3,
414            _point_ref(_all_perms([1,1,sqrt(2)-1]))],
415    ["truncated icosahedron", "archimedean", 32, [6, 5], 2, (3*sqrt(3)+sqrt(15))/4, 3*PHI/2, sqrt(58+18*sqrt(5))/4, (125+43*sqrt(5))/4,
416            _point_ref(concat(
417                _even_perms([0,1,3*PHI]),
418                _even_perms([1,2+PHI,2*PHI]),
419                _even_perms([PHI,2,PHI*PHI*PHI])
420            ))],
421    ["truncated dodecahedron", "archimedean", 32, [10, 3], 2*PHI-2, sqrt(7+11*PHI)/2, (3*PHI+1)/2,sqrt(11+PHI*15)/2, 5*(99+47*sqrt(5))/12,
422            _point_ref(concat(
423                _even_perms([0,1/PHI, 2+PHI]),
424                _even_perms([1/PHI,PHI,2*PHI]),
425                _even_perms([PHI,2,PHI+1])
426            ))],
427    ["cuboctahedron", "archimedean", 14, [4,3], sqrt(2), sqrt(2)/2, sqrt(3)/2, 1, 5*sqrt(2)/3,
428            _point_ref(_all_perms([1,1,0]))],
429    ["icosidodecahedron", "archimedean", 32, [5,3], 1, sqrt(5*(5+2*sqrt(5)))/5,sqrt(5+2*sqrt(5))/2, PHI, (14+17*PHI)/3,
430            _point_ref(concat(_even_perms([0,0,PHI]),_even_perms([1/2,PHI/2,PHI*PHI/2])))],
431    ["rhombicuboctahedron", "archimedean", 26, [4, 3], 2, (1+sqrt(2))/2, sqrt(2*(2+sqrt(2)))/2, sqrt(5+2*sqrt(2))/2, 4+10*sqrt(2)/3,
432            _point_ref(_even_perms([1,1,1+sqrt(2)]))],
433    ["rhombicosidodecahedron", "archimedean", 62, [5,4,3], 2, 3/10*sqrt(15+20*PHI), sqrt(3/2+2*PHI), sqrt(8*PHI+7)/2, (31+58*PHI)/3,
434            _point_ref(concat(
435                _even_perms([1,1,PHI*PHI*PHI]),
436                _even_perms([PHI*PHI,PHI,2*PHI]),
437                _even_perms([2+PHI,0,PHI*PHI])
438            ))],
439    ["truncated cuboctahedron", "archimedean", 26, [8, 6, 4], 2, (1+2*sqrt(2))/2, sqrt(6*(2+sqrt(2)))/2, sqrt(13+6*sqrt(2))/2, (22+14*sqrt(2)),
440            _point_ref(_all_perms([1,1+sqrt(2), 1+2*sqrt(2)]))],
441    ["truncated icosidodecahedron", "archimedean", 62, [10,6,4], 2*PHI - 2, sqrt(15/4+5*PHI),sqrt(9/2+6*PHI),sqrt(19/4+6*PHI), 95+50*sqrt(5),
442            _point_ref(concat(
443                _even_perms([1/PHI,1/PHI,3+PHI]),
444                _even_perms([2/PHI,PHI,1+2*PHI]),
445                _even_perms([1/PHI,PHI*PHI,3*PHI-1]),
446                _even_perms([2*PHI-1,2,2+PHI]),
447                _even_perms([PHI,3,2*PHI])
448            ))],
449    ["snub cube", "archimedean",    38, [4,3], 1.60972,1.14261350892596209,1.24722316799364325, 1.34371337374460170,
450            sqrt((613*_tribonacci+203)/(9*(35*_tribonacci-62))),
451            concat(
452                _point_ref(_even_perms([1,1/_tribonacci,_tribonacci]), sign="odd"),
453                _point_ref(_even_perms([1,_tribonacci,1/_tribonacci]), sign="even")
454            )],
455    ["snub dodecahedron", "archimedean", 92, [5, 3], 1, 1.98091594728184,2.097053835252087,2.155837375115, 37.61664996273336,
456            concat(
457                _point_ref(_even_perms([0.374821658114562,0.330921024729844,2.097053835252088]), sign="odd"),
458                _point_ref(_even_perms([0.192893711352359,1.249503788463027,1.746186440985827]), sign="odd"),
459                _point_ref(_even_perms([1.103156835071754,0.847550046789061,1.646917940690374]), sign="odd"),
460                _point_ref(_even_perms([0.567715369466922,0.643029605914072,1.977838965420219]), sign="even"),
461                _point_ref(_even_perms([1.415265416255982,0.728335176957192,1.454024229338015]), sign="even")
462            )],
463
464    // Catalan Solids, the duals to the Archimedean solids, listed in the corresponding order
465
466    ["triakis tetrahedron","catalan", 12, [3], 9/5, 5*sqrt(22)/44, 5*sqrt(2)/12, 5*sqrt(6)/12, 25*sqrt(2)/36,
467            concat(
468                _point_ref([9*sqrt(2)/20*[1,1,1]],sign="even"),
469                _point_ref([3*sqrt(2)/4*[1,1,1]],sign="odd")
470            )],
471    ["tetrakis hexahedron", "catalan", 24, [3], 1, 2/sqrt(5), 2*sqrt(2)/3, 2/sqrt(3), 32/9,
472            _point_ref(concat([[2/3,2/3,2/3]],_even_perms([1,0,0])))],
473    ["triakis octahedron", "catalan", 24, [3], 2, sqrt(17*(23+16*sqrt(2)))/34, 1/2+sqrt(2)/4,(1+sqrt(2))/2,3/2+sqrt(2),
474            _point_ref(concat([[1,1,1]],_even_perms([1+sqrt(2),0,0])))],
475    ["pentakis dodecahedron", "catalan", 60, [3], 1,sqrt(477/436+97*sqrt(5)/218), sqrt(5)/4+11/12, sqrt(7/4+sqrt(5)/3), 125*sqrt(5)/36+205/36,
476            _point_ref(concat(
477                _even_perms([0,(5-PHI)/6, PHI/2+2/3]),
478                _even_perms([0,(PHI+1)/2,PHI/2]),[(4*PHI-1)/6 * [1,1,1]]
479            ))],
480    ["triakis icosahedron", "catalan", 60, [3], 1, sqrt((139+199*PHI)/244), (8*PHI+1)/10, sqrt(13/8+19/8/sqrt(5)), (13*PHI+3)/2,
481            _point_ref(concat(
482                _even_perms([(PHI+7)/10, 0, (8*PHI+1)/10]),
483                _even_perms([0, 1/2, (PHI+1)/2]),[PHI/2*[1,1,1]]
484            ))],
485    ["rhombic dodecahedron", "catalan", 12, [4], sqrt(3), sqrt(2/3), 2*sqrt(2)/3, 2/sqrt(3), 16*sqrt(3)/9,
486            _point_ref(concat([[1,1,1]], _even_perms([2,0,0])))],
487    ["rhombic triacontahedron", "catalan", 30,[4], 1, sqrt(1+2/sqrt(5)), 1+1/sqrt(5), (1+sqrt(5))/2, 4*sqrt(5+2*sqrt(5)),
488            concat(
489                _point_ref(_even_perms([0,sqrt(1+2/sqrt(5)), sqrt((5+sqrt(5))/10)])),
490                _point_ref(_even_perms([0,sqrt(2/(5+sqrt(5))), sqrt(1+2/sqrt(5))])),
491                _point_ref([sqrt((5+sqrt(5))/10)*[1,1,1]])
492            )],
493    ["deltoidal icositetrahedron", "catalan", 24, [4], 2*sqrt(10-sqrt(2))/7, 7*sqrt((7+4*sqrt(2))/(34 * (10-sqrt(2)))),
494            7*sqrt(2*(2+sqrt(2)))/sqrt(10-sqrt(2))/4, 7*sqrt(2)/sqrt(10-sqrt(2))/2,
495            (14+21*sqrt(2))/sqrt(10-sqrt(2)),
496            _point_ref(concat(
497                _even_perms([0,1,1]), _even_perms([sqrt(2),0,0]),
498                _even_perms((4+sqrt(2))/7*[1,1,1])
499            ))],
500    ["deltoidal hexecontahedron", "catalan", 60, [4], sqrt(5*(85-31*sqrt(5)))/11, sqrt(571/164+1269/164/sqrt(5)), 5/4+13/4/sqrt(5),
501            sqrt(147+65*sqrt(5))/6, sqrt(29530+13204*sqrt(5))/3,
502            _point_ref(concat(
503                _even_perms([0,0,sqrt(5)]),
504                _even_perms([0,(15+sqrt(5))/22, (25+9*sqrt(5))/22]),
505                _even_perms([0,(5+3*sqrt(5))/6, (5+sqrt(5))/6]),
506                _even_perms([(5-sqrt(5))/4, sqrt(5)/2, (5+sqrt(5))/4]),
507                [(5+4*sqrt(5))/11*[1,1,1]]
508            ))],
509    ["disdyakis dodecahedron", "catalan", 48, [3], 1,sqrt(249/194+285/194/sqrt(2)) ,(2+3*sqrt(2))/4, sqrt(183/98+213/98/sqrt(2)),
510            sqrt(6582+4539*sqrt(2))/7,
511            _point_ref(concat(
512                _even_perms([sqrt(183/98+213/98/sqrt(2)),0,0]),
513                _even_perms(sqrt(3+3/sqrt(2))/2 * [1,1,0]),[7/sqrt(6*(10-sqrt(2)))*[1,1,1]]
514            ))],
515    ["disdyakis triacontahedron","catalan", 120, [3], sqrt(15*(85-31*sqrt(5)))/11, sqrt(3477/964+7707/964/sqrt(5)), 5/4+13/4/sqrt(5),
516            sqrt(441+195*sqrt(5))/10,sqrt(17718/5+39612/5/sqrt(5)),
517            _point_ref(concat(
518                _even_perms([0,0,3*(5+4*sqrt(5))/11]),
519                _even_perms([0,(5-sqrt(5))/2,(5+sqrt(5))/2]),
520                _even_perms([0,(15+9*sqrt(5))/10,3*(5+sqrt(5))/10]),
521                _even_perms([3*(15+sqrt(5))/44,3*(5+4*sqrt(5))/22, (75+27*sqrt(5))/44]), [sqrt(5)*[1,1,1]]
522            ))],
523    ["pentagonal icositetrahedron","catalan",24, [5], 0.593465355971, 1.950681331784, 2.1015938932963, 2.29400105368695, 35.6302020120713,
524            concat(
525                _point_ref(_even_perms([0.21879664300048044,0.740183741369857,1.0236561781126901]),sign="even"),
526                _point_ref(_even_perms([0.21879664300048044,1.0236561781126901,0.740183741369857]),sign="odd"),
527                _point_ref(_even_perms([1.3614101519264425,0,0])),
528                _point_ref([0.7401837413698572*[1,1,1]])
529            )],
530    ["pentagonal hexecontahedron", "catalan", 60,[5], 0.58289953474498, 3.499527848905764,3.597624822551189,3.80854772878239, 189.789852066885,
531            concat(
532                _point_ref(_even_perms([0.192893711352359,0.218483370127321,2.097053835252087]), sign="even"),
533                _point_ref(_even_perms([0,0.7554672605165955,1.9778389654202186])),
534                _point_ref(_even_perms([0,1.888445389283669154,1.1671234364753339])),
535                _point_ref(_even_perms([0.56771536946692131,0.824957552676275846,1.8654013108176956657]),sign="odd"),
536                _point_ref(_even_perms([0.37482165811456229,1.13706613386050418,1.746186440985826345]), sign="even"),
537                _point_ref(_even_perms([0.921228888309550,0.95998770139158,1.6469179406903744]),sign="even"),
538                _point_ref(_even_perms([0.7283351769571914773,1.2720962825758121,1.5277030708585051]),sign="odd"),
539                _point_ref([1.222371704903623092*[1,1,1]])
540            )],
541];
542
543
544_stellated_polyhedra_ = [
545    ["great dodecahedron", "icosahedron", -sqrt(5/3-PHI)],
546    ["small stellated dodecahedron", "dodecahedron", sqrt((5+2*sqrt(5))/5)],
547    ["great stellated dodecahedron", "icosahedron", sqrt(2/3+PHI)],
548    ["small triambic icosahedron", "icosahedron", sqrt(3/5) - 1/sqrt(3)]
549];
550
551
552// Function: regular_polyhedron_info()
553//
554// Usage:
555//   info = regular_polyhedron_info(info, ...);
556//
557// Description:
558//   Calculate characteristics of regular polyhedra or the selection set for regular_polyhedron().
559//   Invoke with the same polyhedron selection and size arguments used by {{regular_polyhedron()}} and use the `info` argument to
560//   request the desired return value. Set `info` to:
561//     * `"vnf"`: vnf for the selected polyhedron
562//     * `"vertices"`: vertex list for the selected polyhedron
563//     * `"faces"`: list of faces for the selected polyhedron, where each entry on the list is a list of point index values to be used with the vertex list
564//     * `"face normals"`: list of normal vectors for each face
565//     * `"in_radius"`: in-sphere radius for the selected polyhedron
566//     * `"mid_radius"`: mid-sphere radius for the selected polyhedron
567//     * `"out_radius"`: circumscribed sphere radius for the selected polyhedron
568//     * `"index set"`: index set selected by your specifications; use its length to determine the valid range for `index`.
569//     * `"face vertices"`: number of vertices on the faces of the selected polyhedron (always a list)
570//     * `"edge length"`: length of the smallest edge of the selected polyhedron
571//     * `"center"`: center for the polyhedron
572//     * `"type"`: polyhedron type, one of "platonic", "archimedean", "catalan", or "trapezohedron"
573//     * `"name"`: name of selected polyhedron
574//
575// Arguments:
576//   info = Desired information to return for the polyhedron
577//   name = Name of polyhedron to create.
578//   ---
579//   index = Index to select from polyhedron list.  Default: 0.
580//   type = Type of polyhedron: "platonic", "archimedean", "catalan".
581//   faces = Number of faces.
582//   facetype = Scalar or vector listing required type of faces as vertex count.  Polyhedron must have faces of every type listed and no other types.
583//   hasfaces = Scalar of vector list face vertex counts.  Polyhedron must have at least one of the listed types of face.
584//   side = Length of the smallest edge of the polyhedron.  Default: 1 (if no radius or diameter is given).
585//   or / r / d = outer radius.   Polyhedron is scaled so it has the specified outer radius or diameter. 
586//   mr = middle radius.  Polyhedron is scaled so it has the specified middle radius.  
587//   ir = inner radius.  Polyhedron is scaled so it has the specified inner radius. 
588//   anchor = Side of the origin to anchor to.  The bounding box of the polyhedron is aligned as specified.  Default: `CENTER`
589//   facedown = If false display the solid in native orientation.  If true orient it with a largest face down.  If set to a vertex count, orient it so a face with the specified number of vertices is down.  Default: true.
590//   stellate = Set to a number to erect a pyramid of that height on every face of your polyhedron.  The height is a multiple of the side length.  Default: false.
591//   longside = Specify the long side length for a trapezohedron.  Invalid for other shapes.
592//   h = Specify the height of the apex for a trapezohedron.  Invalid for other shapes.
593function regular_polyhedron_info(
594    info=undef, name=undef,
595    index=undef, type=undef,
596    faces=undef, facetype=undef,
597    hasfaces=undef, side=undef,
598    ir=undef, mr=undef, or=undef,
599    r=undef, d=undef,
600    anchor=CENTER,
601    facedown=true, stellate=false,
602    longside=undef, h=undef  // special parameters for trapezohedron
603) = let(
604        argcount = num_defined([side,ir,mr,or,r,d])
605    )
606    assert(argcount<=1, "You must specify only one of 'side', 'ir', 'mr', 'or', 'r', and 'd'")
607    let(  
608        //////////////////////
609        //Index values into the _polyhedra_ array
610        //
611        pname = 0,        // name of polyhedron
612        class = 1,        // class name (e.g. platonic, archimedean)
613        facecount = 2,    // number of faces
614        facevertices = 3, // vertices on the faces, e.g. [3] for all triangles, [3,4] for triangles and squares
615        edgelen = 4,      // length of the edge for the vertex list in the database
616        in_radius = 5,    // in radius for unit polyhedron (shortest side 1)
617        mid_radius = 6,   // mid radius for unit polyhedron
618        out_radius = 7,   // out radius for unit polyhedron
619        volume = 8,       // volume of unit polyhedron (data not validated, not used right now)
620        vertices = 9,     // vertex list (in arbitrary order)
621        //////////////////////
622        or = get_radius(r=r,r1=or,d=d),
623        stellate_index = search([name], _stellated_polyhedra_, 1, 0)[0],
624        name = stellate_index==[] ? name : _stellated_polyhedra_[stellate_index][1],
625        stellate = stellate_index==[] ? stellate : _stellated_polyhedra_[stellate_index][2],
626        indexlist = (
627            name=="trapezohedron" ? [0] : [  // dumy list of one item
628                for(i=[0:1:len(_polyhedra_)-1]) (
629                    if (
630                        (is_undef(name) || _polyhedra_[i][pname]==name) &&
631                        (is_undef(type) || _polyhedra_[i][class]==type) &&
632                        (is_undef(faces) || _polyhedra_[i][facecount]==faces) &&
633                        (
634                            is_undef(facetype) || 0==compare_lists(
635                                is_list(facetype)? reverse(sort(facetype)) : [facetype],
636                                _polyhedra_[i][facevertices]
637                            )
638                        ) &&
639                        (is_undef(hasfaces) || any([for (ft=hasfaces) in_list(ft,_polyhedra_[i][facevertices])]))
640                    ) i
641                )
642            ]
643        )
644    )
645    assert(len(indexlist)>0, "No polyhedra meet your specification")
646    let(validindex = is_undef(index) || (index>=0 && index<len(indexlist)))
647    assert(validindex, str(
648        len(indexlist),
649        " polyhedra meet specifications, so 'index' must be in [0,",
650        len(indexlist)-1,
651        "], but 'index' is ",
652        index
653    ))
654    let(
655        entry = (
656            name == "trapezohedron"? (
657                _trapezohedron(faces=faces, side=side, longside=longside, h=h, r=r)
658            ) : (
659                _polyhedra_[!is_undef(index)?
660                    indexlist[index] :
661                    indexlist[0]]
662            )
663        ),
664        valid_facedown = is_bool(facedown) || in_list(facedown, entry[facevertices])
665    )
666    assert(name == "trapezohedron" || num_defined([longside,h])==0, "The 'longside' and 'h' parameters are only allowed with trapezohedrons")
667    assert(valid_facedown,str("'facedown' set to ",facedown," but selected polygon only has faces with size(s) ",entry[facevertices]))
668    let(
669        scalefactor = (
670            name=="trapezohedron" ? 1 : (
671                argcount == 0? 1     // Default side=1 if no size info given
672              : is_def(side) ? side  
673              : is_def(ir) ? ir/entry[in_radius] 
674              : is_def(mr) ? mr/entry[mid_radius] 
675              :              or/entry[out_radius]
676            ) / entry[edgelen]
677        ),
678        face_triangles = hull(entry[vertices]),
679        faces_normals_vertices = _stellate_faces(
680            entry[edgelen], stellate, entry[vertices],
681            entry[facevertices]==[3]?
682                [face_triangles, [for(face=face_triangles) _facenormal(entry[vertices],face)]] :
683                _full_faces(entry[vertices], face_triangles)
684        ),
685        faces = faces_normals_vertices[0],
686        faces_vertex_count = [for(face=faces) len(face)],
687        facedown = facedown == true ? (stellate==false? entry[facevertices][0] : 3) : facedown,
688        down_direction = facedown == false?  [0,0,-1] :
689            faces_normals_vertices[1][search(facedown, faces_vertex_count)[0]],
690        scaled_points = scalefactor * rot(p=faces_normals_vertices[2], from=down_direction, to=[0,0,-1]),
691        bounds = pointlist_bounds(scaled_points),
692        boundtable = [bounds[0], [0,0,0], bounds[1]],
693        translation = [for(i=[0:2]) -boundtable[1+anchor[i]][i]],
694        face_normals = rot(p=faces_normals_vertices[1], from=down_direction, to=[0,0,-1]),
695        radius_scale = name=="trapezohedron" ? 1 : scalefactor * entry[edgelen]
696    )
697    info == "fullentry" ? [
698        scaled_points,
699        translation,
700        stellate ? faces : face_triangles,
701        faces,
702        face_normals,
703        radius_scale*entry[in_radius]
704    ] :
705    info == "vnf" ? [move(translation,p=scaled_points), faces] :
706    info == "vertices" ? move(translation,p=scaled_points) :
707    info == "faces" ? faces :
708    info == "face normals" ? face_normals :
709    info == "in_radius" ? radius_scale * entry[in_radius] :
710    info == "mid_radius" ? radius_scale * entry[mid_radius] :
711    info == "out_radius" ? radius_scale * entry[out_radius] :
712    info == "index set" ? indexlist :
713    info == "face vertices" ? (stellate==false? entry[facevertices] : [3]) :
714    info == "edge length" ? scalefactor * entry[edgelen] :
715    info == "center" ? translation :
716    info == "type" ? entry[class] :
717    info == "name" ? entry[pname] :
718    assert(false, str("Unknown info type '",info,"' requested"));
719
720
721function _stellate_faces(scalefactor,stellate,vertices,faces_normals) =
722    (stellate == false || stellate == 0)? concat(faces_normals,[vertices]) :
723    let(
724        faces = [for(face=faces_normals[0]) select(face,hull(select(vertices,face)))],
725        direction = [for(i=[0:1:len(faces)-1]) _facenormal(vertices, faces[i])*faces_normals[1][i]>0 ? 1 : -1],
726        maxvertex = len(vertices),
727        newpts = [for(i=[0:1:len(faces)-1]) mean(select(vertices,faces[i]))+stellate*scalefactor*faces_normals[1][i]],
728        newfaces = [for(i=[0:1:len(faces)-1], j=[0:len(faces[i])-1]) concat([i+maxvertex],select(faces[i], [j, j+direction[i]]))],
729        allpts = concat(vertices, newpts),
730        normals = [for(face=newfaces) _facenormal(allpts,face)]
731    ) [newfaces, normals, allpts];
732
733
734function _trapezohedron(faces, r, side, longside, h, d) =
735    assert(faces%2==0, "Must set 'faces' to an even number for trapezohedron")
736    let(
737        r = get_radius(r=r, d=d),
738        N = faces/2,
739        parmcount = num_defined([r,side,longside,h])
740    )
741    assert(parmcount==2,"Must define exactly two of 'r', 'side', 'longside', and 'height'")
742    let(       
743        separation = (     // z distance between non-apex vertices that aren't in the same plane
744            !is_undef(h) ? 2*h*sqr(tan(90/N)) :
745            (!is_undef(r) && !is_undef(side))? sqrt(side*side+2*r*r*(cos(180/N)-1)) :
746            (!is_undef(r) && !is_undef(longside))? 2 * sqrt(sqr(longside)-sqr(r)) / (1-sqr(tan(90/N))) * sqr(tan(90/N)) :
747            2*sqr(sin(90/N))*sqrt((sqr(side) + 2*sqr(longside)*(cos(180/N)-1)) / (cos(180/N)-1) / (cos(180/N)+cos(360/N)))
748        )
749    )
750    assert(separation==separation, "Impossible trapezohedron specification")
751    let(
752        h = !is_undef(h) ? h : 0.5*separation / sqr(tan(90/N)),
753        r = (
754            !is_undef(r) ? r :
755            !is_undef(side) ? sqrt((sqr(separation) - sqr(side))/2/(cos(180/N)-1)) :
756            sqrt(sqr(longside) - sqr(h-separation/2))
757        ),
758        top = [for(i=[0:1:N-1]) [r*cos(360/N*i), r*sin(360/N*i),separation/2]],
759        bot = [for(i=[0:1:N-1]) [r*cos(180/N+360/N*i), r*sin(180/N+360/N*i),-separation/2]],
760        vertices = concat([[0,0,h],[0,0,-h]],top,bot)
761    ) [  
762        "trapezohedron", "trapezohedron", faces, [4],
763        !is_undef(side)? side : sqrt(sqr(separation)-2*r*(cos(180/N)-1)),  // actual side length
764        h*r/sqrt(r*r+sqr(h+separation/2)),     // in_radius
765        h*r/sqrt(r*r+sqr(h-separation/2)),     // mid_radius
766        max(h,sqrt(r*r+sqr(separation/2))),  // out_radius
767        undef,                               // volume
768        vertices
769    ];
770
771
772function _facenormal(pts, face) = unit(cross(pts[face[2]]-pts[face[0]], pts[face[1]]-pts[face[0]]));
773
774// hull() function returns triangulated faces.    This function identifies the vertices that belong to each face
775// by grouping together the face triangles that share normal vectors.    The output gives the face polygon
776// point indices in arbitrary order (not usable as input to a polygon call) and a normal vector.  Finally
777// the faces are ordered based on angle with their center (will always give a valid order for convex polygons).
778// Final return is [ordered_faces, facenormals] where the first is a list of indices into the point list
779// and the second is a list of vectors.  
780
781function _full_faces(pts,faces) =
782    let(
783        normals = [for(face=faces) quant(_facenormal(pts,face),1e-12)],
784        groups = _unique_groups(normals),
785        faces = [for(entry=groups) unique(flatten(select(faces, entry)))],
786        facenormals = [for(entry=groups) normals[entry[0]]],
787        ordered_faces = [
788            for(i=idx(faces))
789              let(
790                  facepts = select(pts, faces[i]),
791                  center = mean(facepts),
792                  rotatedface = rot(from=facenormals[i], to=[0,0,1], p=move(-center, p=facepts)),
793                  clockwise = sortidx([for(pt=rotatedface) -atan2(pt.y,pt.x)])
794              )
795            select(faces[i],clockwise)
796        ]
797    ) [ordered_faces, facenormals];
798
799
800// vim: expandtab tabstop=4 shiftwidth=4 softtabstop=4 nowrap