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