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