Closed revarbat closed 5 years ago
Polyhedra devel work by @adrianVmariano: Requires Oskar Linde's Hull to work. (#18)
use<BOSL/math.scad>
use<BOSL/transforms.scad>
function is_member(value, list) =
len(list)==0 ? false :
list[0] == value ? true : is_member(value,cdr(list));
function even_permutations(v) = [v, [v[2], v[0], v[1]], [v[1],v[2],v[0]]];
function all_permutations(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]]];
function listcomp(a,b,n=0) = !is_list(a) && !is_list(b) ? scalarcomp(a,b):
len(a)<=n && len(b)<=n ? 0 :
len(a)<=n ? 1 :
len(b)<=n ? -1 :
a[n] < b[n] ? -1 :
a[n] > b[n] ? 1 :
listcomp(a,b,n+1);
function scalarcomp(a,b) = a<b ? -1 : a>b ? 1 : 0;
function quicksort(arr) =
!(len(arr)>0) ? [] :
let( pivot = arr[floor(len(arr)/2)],
lesser = [ for (y = arr) if (listcomp(y,pivot)==-1) y ],
equal = [ for (y = arr) if (listcomp(y,pivot)==0) y ],
greater = [ for (y = arr) if (listcomp(y,pivot)==1) y ]
)
concat( quicksort(lesser), equal, quicksort(greater) );
function unique(points) = let(sorted=quicksort(points))
[for(i=[0:len(sorted)-1]) if (i==0 || sorted[i]!=sorted[i-1]) sorted[i]];
// sign=="even" means an even number of minus signs (odd number of plus signs)
// sign=="odd" means an odd number of minus signs (even number of plus signs)
function point_ref(points, sign="both") =
unique([ for(i=[-1,1],j=[-1,1],k=[-1,1]) if (sign=="both" || sign=="even" && i*j*k>0 || sign=="odd" && i*j*k<0)
each [for(point=points) vmul(point,[i,j,k])]
]);
function cosh(x) = (exp(x)+exp(-x))/2;
function acosh(x) = ln(x+sqrt(x*x-1));
// Polyhedra data from Wikipedia and http://dmccooey.com/polyhedra/
phi = (1+sqrt(5))/2;
tribonacci=(1+4*cosh(acosh(2+3/8)/3))/3;
pname = 0;
class = 1;
facecount = 2;
edgelen = 3;
in_radius = 4;
mid_radius = 5;
out_radius = 6;
volume = 7;
vertices = 8;
library = [
["tetrahedron", "platonic", 4, 2*sqrt(2), sqrt(6)/12, sqrt(2)/4, sqrt(6)/4, 1/6/sqrt(2),
point_ref([[1,1,1]], sign="even")],
["cube", "platonic", 6, 2, 1/2, 1/sqrt(2), sqrt(3)/2, 1,
point_ref([[1,1,1]])],
["octahedron", "platonic", 8, sqrt(2), sqrt(6)/6, 1/2, sqrt(2)/2, sqrt(2)/3,
point_ref(even_permutations([1,0,0]))],
["dodecahedron", "platonic", 12, 2/phi, sqrt(5/2+11*sqrt(5)/10)/2, (3+sqrt(5))/4, sqrt(3)*phi/2, (15+7*sqrt(5))/4,
point_ref(concat([[1,1,1]],even_permutations([0,phi,1/phi])))],
["icosahedron", "platonic", 20, 2, phi*phi/2/sqrt(3), cos(36), sin(72), 5*(3+sqrt(5))/12,
point_ref(even_permutations([0,1,phi]))],
["truncated tetrahedron", "archimedean", 8, sqrt(8), sqrt(6)/4, 3*sqrt(2)/4, sqrt(11/8), 23*sqrt(2)/12,
point_ref(all_permutations([1,1,3]),sign="even")],
["cuboctahedron", "archimedean", 14, sqrt(2), sqrt(2)/2, sqrt(3)/2, 1, 5*sqrt(2)/3,
point_ref(all_permutations([1,1,0]))],
["truncated cube", "archimedean", 14, 2*(sqrt(2)-1), (1+sqrt(2))/2, 1+sqrt(2)/2, sqrt(7+4*sqrt(2))/2, 7+14*sqrt(2)/3,
point_ref(all_permutations([1,1,sqrt(2)-1]))],
["truncated octahedron", "archimedean", 14, sqrt(2), sqrt(6)/2, 1.5, sqrt(10)/2, 8*sqrt(2),
point_ref(all_permutations([0,1,2]))],
["rhombicuboctahedron", "archimedean", 26, 2, (1+sqrt(2))/2, sqrt(2*(2+sqrt(2)))/2, sqrt(5+2*sqrt(2))/2, 4+10*sqrt(2)/3,
point_ref(even_permutations([1,1,1+sqrt(2)]))],
["truncated cuboctahedron", "archimedean", 26, 2, (1+2*sqrt(2))/2, sqrt(6*(2+sqrt(2)))/2, sqrt(13+6*sqrt(2))/2,
(22+14*sqrt(2)),
point_ref(all_permutations([1,1+sqrt(2), 1+2*sqrt(2)]))],
["snub cube", "archimedean", 38, 1.60972,1.14261350892596209,1.24722316799364325, 1.34371337374460170,
sqrt((613*tribonacci+203)/(9*(35*tribonacci-62))),
concat(point_ref(even_permutations([1,1/tribonacci,tribonacci]), sign="odd"),
point_ref(even_permutations([1,tribonacci,1/tribonacci]), sign="even"))], // changed from odd!
["icosidodecahedron", "archimedean", 32, 1, sqrt(5*(5+2*sqrt(5)))/5,sqrt(5+2*sqrt(5))/2, phi, (14+17*phi)/3,
point_ref(concat(even_permutations([0,0,phi]),even_permutations([1/2,phi/2,phi*phi/2])))],
["truncated dodecahedron", "archimedean", 32, 2*phi-2, sqrt(7+11*phi)/2, (3*phi+1)/2,sqrt(11+phi*15)/2, 5*(99+47*sqrt(5))/12,
point_ref(concat(even_permutations([0,1/phi, 2+phi]),
even_permutations([1/phi,phi,2*phi]),
even_permutations([phi,2,phi+1])))],
["truncated icosahedron", "archimedean", 32, 2, (3*sqrt(3)+sqrt(15))/4, 3*phi/2, sqrt(58+18*sqrt(5))/4, (125+43*sqrt(5))/4,
point_ref(concat(even_permutations([0,1,3*phi]),
even_permutations([1,2+phi,2*phi]),
even_permutations([phi,2,phi*phi*phi])))],
["rhombicosidodecahedron", "archimedean", 62, 2, 3/10*sqrt(15+20*phi), sqrt(3/2+2*phi), sqrt(8*phi+7)/2, (31+58*phi)/3,
point_ref(concat(even_permutations([1,1,phi*phi*phi]),
even_permutations([phi*phi,phi,2*phi]),
even_permutations([2+phi,0,phi*phi])))],
["truncated icosidodecahedron", "archimedean", 62, 2*phi - 2, sqrt(15/4+5*phi),sqrt(9/2+6*phi),sqrt(19/4+6*phi),
95+50*sqrt(5),
point_ref(concat(even_permutations([1/phi,1/phi,3+phi]),
even_permutations([2/phi,phi,1+2*phi]),
even_permutations([1/phi,phi*phi,3*phi-1]),
even_permutations([2*phi-1,2,2+phi]),
even_permutations([phi,3,2*phi])))],
["snub dodecahedron", "archimedean", 92, 1, 1.98091594728184,2.097053835252087,2.155837375115, 37.61664996273336,
concat(point_ref(even_permutations([0.374821658114562,0.330921024729844,2.097053835252088]), sign="odd"),
point_ref(even_permutations([0.192893711352359,1.249503788463027,1.746186440985827]), sign="odd"),
point_ref(even_permutations([1.103156835071754,0.847550046789061,1.646917940690374]), sign="odd"),
point_ref(even_permutations([0.567715369466922,0.643029605914072,1.977838965420219]), sign="even"),
point_ref(even_permutations([1.415265416255982,0.728335176957192,1.454024229338015]), sign="even"))],
["rhombic dodecahedron", "catalan", 12, sqrt(3), sqrt(2/3), 2*sqrt(2)/3, 2/sqrt(3), 16*sqrt(3)/9,
point_ref(concat([[1,1,1]], even_permutations([2,0,0])))],
["triakis tetrahedron","catalan", 12, 9/5, 5*sqrt(22)/44, 5*sqrt(2)/12, 5*sqrt(6)/12, 25*sqrt(2)/36,
concat(point_ref([9*sqrt(2)/20*[1,1,1]],sign="even"),
point_ref([3*sqrt(2)/4*[1,1,1]],sign="odd"))],
["rhombic dodecahedron", "catalan", 12, 1, 2/sqrt(6), 4/3/sqrt(2), 2/sqrt(3), 16/3/sqrt(3),
point_ref(concat([[1,1,1]/sqrt(3)],even_permutations([2/sqrt(3),0,0])))],
["tetrakis hexahedron", "catalan", 24, 1, 2/sqrt(5), 2*sqrt(2)/3, 2/sqrt(3), 32/9,
point_ref(concat([[2/3,2/3,2/3]],even_permutations([1,0,0])))],
["triakis octahedron", "catalan", 24, 2, sqrt(17*(23+16*sqrt(2)))/34, 1/2+sqrt(2)/4,(1+sqrt(2))/2,3/2+sqrt(2),
point_ref(concat([[1,1,1]],even_permutations([1+sqrt(2),0,0])))],
["deltoidal icositetrahedron", "catalan", 24, 2*sqrt(10-sqrt(2))/7, 7*sqrt((7+4*sqrt(2))/(34 * (10-sqrt(2)))),
7*sqrt(2*(2+sqrt(2)))/sqrt(10-sqrt(2))/4, 7*sqrt(2)/sqrt(10-sqrt(2))/2,
(14+21*sqrt(2))/sqrt(10-sqrt(2)),
point_ref(concat(even_permutations([0,1,1]), even_permutations([sqrt(2),0,0]),
even_permutations((4+sqrt(2))/7*[1,1,1])))],
["pentagonal icositetrahedron","catalan",24, 0.593465355971, 1.950681331784, 2.1015938932963, 2.29400105368695,
35.6302020120713,
concat(
point_ref(even_permutations([0.21879664300048044,0.740183741369857,1.0236561781126901]),sign="even"),
point_ref(even_permutations([0.21879664300048044,1.0236561781126901,0.740183741369857]),sign="odd"),//changed from odd
point_ref(even_permutations([1.3614101519264425,0,0])),
point_ref([0.7401837413698572*[1,1,1]]))],
["rhombic triacontahedron", "catalan", 30,1, sqrt(1+2/sqrt(5)), 1+1/sqrt(5), (1+sqrt(5))/2, 4*sqrt(5+2*sqrt(5)),
concat(point_ref(even_permutations([0,sqrt(1+2/sqrt(5)), sqrt((5+sqrt(5))/10)])),
point_ref(even_permutations([0,sqrt(2/(5+sqrt(5))), sqrt(1+2/sqrt(5))])),
point_ref([sqrt((5+sqrt(5))/10)*[1,1,1]]))],
["disdyakis dodecahedron", "catalan", 48, 1,sqrt(249/194+285/194/sqrt(2)) ,(2+3*sqrt(2))/4, sqrt(183/98+213/98/sqrt(2)),
sqrt(6582+4539*sqrt(2))/7,
point_ref(concat(even_permutations([sqrt(183/98+213/98/sqrt(2)),0,0]),
even_permutations(sqrt(3+3/sqrt(2))/2 * [1,1,0]),[7/sqrt(6*(10-sqrt(2)))*[1,1,1]]))],
["pentakis dodecahedron", "catalan", 60, 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,
point_ref(concat(even_permutations([0,(5-phi)/6, phi/2+2/3]),
even_permutations([0,(phi+1)/2,phi/2]),[(4*phi-1)/6 * [1,1,1]]))],
["triakis icosahedron", "catalan", 60, 1, sqrt((139+199*phi)/244), (8*phi+1)/10, sqrt(13/8+19/8/sqrt(5)), (13*phi+3)/2,
point_ref(concat(even_permutations([(phi+7)/10, 0, (8*phi+1)/10]),
even_permutations([0, 1/2, (phi+1)/2]),[phi/2*[1,1,1]]))],
["deltoidal hexecontahedron", "catalan", 60, sqrt(5*(85-31*sqrt(5)))/11, sqrt(571/164+1269/164/sqrt(5)), 5/4+13/4/sqrt(5),
sqrt(147+65*sqrt(5))/6, sqrt(29530+13204*sqrt(5))/3,
point_ref(concat(even_permutations([0,0,sqrt(5)]),
even_permutations([0,(15+sqrt(5))/22, (25+9*sqrt(5))/22]),
even_permutations([0,(5+3*sqrt(5))/6, (5+sqrt(5))/6]),
even_permutations([(5-sqrt(5))/4, sqrt(5)/2, (5+sqrt(5))/4]),
[(5+4*sqrt(5))/11*[1,1,1]]))],
["deltoidal hexecontahedron", "catalan", 60,0.58289953474498, 3.499527848905764,3.597624822551189,3.80854772878239,
189.789852066885,
concat(
point_ref(even_permutations([0.192893711352359,0.218483370127321,2.097053835252087]), sign="even"),
point_ref(even_permutations([0,0.7554672605165955,1.9778389654202186])),
point_ref(even_permutations([0,1.888445389283669154,1.1671234364753339])),
point_ref(even_permutations([0.56771536946692131,0.824957552676275846,1.8654013108176956657]),sign="odd"),
point_ref(even_permutations([0.37482165811456229,1.13706613386050418,1.746186440985826345]), sign="even"),
point_ref(even_permutations([0.921228888309550,0.95998770139158,1.6469179406903744]),sign="even"),
point_ref(even_permutations([0.7283351769571914773,1.2720962825758121,1.5277030708585051]),sign="odd"),
point_ref([1.222371704903623092*[1,1,1]]))],
["disdyakis triacontahedron","catalan", 120, sqrt(15*(85-31*sqrt(5)))/11, sqrt(3477/964+7707/964/sqrt(5)), 5/4+13/4/sqrt(5),
sqrt(441+195*sqrt(5))/10,sqrt(17718/5+39612/5/sqrt(5)),
point_ref(concat(even_permutations([0,0,3*(5+4*sqrt(5))/11]),
even_permutations([0,(5-sqrt(5))/2,(5+sqrt(5))/2]),
even_permutations([0,(15+9*sqrt(5))/10,3*(5+sqrt(5))/10]),
even_permutations([3*(15+sqrt(5))/44,3*(5+4*sqrt(5))/22, (75+27*sqrt(5))/44]),
[sqrt(5)*[1,1,1]]))],
];
/* Notes on Kepler-Poinsot Solids
It seems these can be made by adding/subtracting a pyramid from other solids.
It's actually possible to do this generally if we have an attach module, assuming that the normal
to all the faces of a polyhedron is the line from the face center to the origin.
Great dodecahedron: from icosahedron subtract pyramind of height sqrt(5/3-phi)
Great stellated dodecahedron: from icosahedron add pyramids of height sqrt(2/3+phi)
Small stellated dodecahedron: from dodecahedron add pyramids of height sqrt((3+4*phi)/5)
Great icosahedron seems to be complicated, not easily derivable
*/
use<hull.scad>
//regular_polyhedron("pentagonal icositetrahedron");
/*
d=7;
% scale([1/s,1/s,1/s])polyhedron(points=pts, faces=hull(pts));
sphere(r=library[index][in_radius]+.01,$fn=100);
translate([d,0,0]){
%scale([1/s,1/s,1/s])polyhedron(points=pts, faces=hull(pts));
sphere(r=library[index][mid_radius]+.0,$fn=100);
}
translate([2*d,0,0]){
scale([1/s,1/s,1/s])polyhedron(points=pts, faces=hull(pts));
%sphere(r=library[index][out_radius]-.05,$fn=100);
}
translate([d,d,0]){
t = library[index][volume];
%scale([1/s,1/s,1/s])polyhedron(points=pts, faces=hull(pts));
sphere(r=pow(t/PI*3/4,1/3),$fn=100);
}
*/
// specify name
// specify (optional) type and (optional) face count and then index
//
// You can give ir to specify the size of the inscribed sphere, mr to specify the mid scribed sphere radius, or
// or to specify the outer sphere size.
//
// Specify r to round over the polyhedron with the specified radius. Note that especially if $fn is small the dimensions
// might be slightly off.
//
module regular_polyhedron(name=undef,index=undef,type=undef,faces=undef,side=1,ir=undef, mr=undef, or=undef, r=0)
{
libsize = len(library);
argcount = sum([is_def(ir)?1:0, is_def(mr)?1:0, is_def(or)?1:0]);
assert(argcount<=1, "You must specify only one of 'ir', 'mr' and 'or'");
indexlist = [for(i=[0:len(library)-1]) if ((is_undef(name) || library[i][pname]==name)
&& (is_undef(type) || library[i][class]==type)
&& (is_undef(faces) || library[i][facecount]==faces)) i];
echo("indexlist = ",indexlist);
entry = library[is_def(index) ? indexlist[index] : indexlist[0]];
echo("index = ",library[is_def(index) ? indexlist[index] : indexlist[0]]);
scalefactor = (argcount == 0 ? side :
is_def(ir) ? ir/entry[in_radius] :
is_def(mr) ? mr/entry[mid_radius] : or/entry[out_radius]) / entry[edgelen];
if (r==0)
polyhedron(scalefactor*entry[vertices], hull(entry[vertices]));
else {
fn = segs(r);
fixed_r = r/cos(180/fn);
adjusted_scale = scalefactor - fixed_r / entry[in_radius] / entry[edgelen];
hull()
place_copies(adjusted_scale*entry[vertices])
sphere(r=fixed_r, $fn=fn);
}
}
//function is_def(x) = !is_undef(x);
//function is_scalar(x) = !is_list(x);
function all(list,n=0) =
n==len(list) ? true :
!(is_list(list[n])? all(list[n]) : list[n]) ? false :
all(list,n+1);
//regular_polyhedron("pentagonal icositetrahedron", or=5);
//%sphere(r=5-.2, $fn=100);
//regular_polyhedron(index=0, type="catalan");
//regular_polyhedron("disdyakis triacontahedron");
/*
shape = "dodecahedron";
$fn=8;
//top_half(cp=[0,0,.2])
difference(){
regular_polyhedron(shape, r=0);
regular_polyhedron(shape, r=.2);
}
*/
$fn=96;
for(i=[0:len(library)-1]) {
place_copies([[3*i,0,0]]) // Plain polyhedron
regular_polyhedron(index=i, mr=1);
place_copies([[3*i,3.5,0]]){ // Inner radius means sphere touches faces of the polyhedron
sphere(r=1.005); // Sphere is slightly oversized so you can see it poking out from each face
%regular_polyhedron(index=i, ir=1);
}
place_copies([[3*i,7,0]]){ // Mid radius means the sphere touches the center of each edge
sphere(r=1);
%regular_polyhedron(index=i, mr=1);
}
place_copies([[3*i,11,0]]){ // outer radius means points of the polyhedron are on the sphere
%sphere(r=.99); // Slightly undersized sphere means the points poke out a bit
regular_polyhedron(index=i, or=1);
}
}
/* regular_polyhedron(faces=60,index=0); */
// TODO
// Use volume info?
// Are archimedean & catalan solids in the "right" order in the list?
// Add a stellate function
One thing I'm not sure about is whether there are other good mechanisms I should add for selecting which polyhedron you want. Right now you can narrow by "type" and by number of faces and then index into the remaining list, or you can give a name. I thought about allowing alternate names but it wasn't clear there were any alternate names that would be helpful.
Catalan and Archimedean solids are duals of each other and at the very least, the ordering should be such that the nth catalan solid is the dual of the nth archimedean one on the list. I think that might not be true at the moment.
It's obviously pretty easy to add more convex polyhedra if you find any you think are essential---we just need to construct a vertex list and hull() does the rest. But my list is pretty extensive, and I think it includes everything you asked for above.
I'm not absolutely certain that all the volume data is correct, as I don't have a good way to validate it, and sometimes it was given for sizes other than the unit polyhedron and I had to adjust it. The radii are easily checked by examining the rendered sphere and polygon together. Is the volume data useful? I saw a library that let you construct polyhedra with a desired volume---I could add that. It seems not very different from using the mid-radius.
I have to assume the volume data is useful to someone, but it's not really a high priority.
Volume data is there already. I just don't have a way of checking its correctness, whereas I did for the radii data. So the volume data might have errors, whereas I'm pretty sure that the radii are all correct. (You can inspect the demo result with the array of 31 polyhedra and see by inspection that the radii are correct, or at least very close to correct by how the spheres intersect the solids.) I suppose drastic errors in the volume data would also be pretty obvious. The extra code to generate a polyhedron with a desired volume is just a couple lines, easy to add.
I do think that the highest priority is providing good methods for people to actually select which shape they want. Some of the names are so long and complex that the only way most people would get them right would be by cutting and pasting from the list in the manual.
Basically all that happens in this code is you select a polyhedron, the code has a vertex list, it passes it to hull to get the faces. There's a bit of scaling to control the desired size. The core of it all is the "library" data structure which has magical radii values the number of faces, and the actual vertex list. That list is created in most cases by permuting and reflecting a small number of generator points using functions (that should be private or "hidden"). I need unique() to avoid duplicating points, which the hull() function doesn't seem to handle. So programmatically there's really not much going on.
Also requires quicksort()
and unique()
. (#11)
Technically it only requires unique() which can be written without a sort function using a loop with search(). I'm not sure if that's a good idea, but it's possible.
You asked above for all the dice, and I realized that the 10-sided die is not included in my file. It's one member of the infinite family of trapezohedrons. It's not clear to me if families like this should have their own functions, or if we should have a list of special cases for such infinite families and have polyhedra handle it. Other infinite families include the bipyramids and antiprisms. There seem to be canonical lengths to these things that are a mystery to me. I'm inclined to think making separate modules makes more sense.
Hmm. A couple thoughts after playing around with this...
Not sure what is going on for you with regards to speed. I just ran
for(i=[0:60]) up(2*i) regular_polyhedron(faces=60,index=0,or=1);
which generates 60 polyhedra that have 60 faces each and it ran in 1s on my machine. The demo, which shows all solids with their insphere, midsphere and outsphere runs in 4 seconds and makes 124 polyhedra, of which 93 are intersected with a sphere. I don't think you can expect faster.
Now if you want rounded edges, so you pick r>0, and you choose a large $fn I think there's no help for it. All the code is doing is a hull of spheres. How would you make that faster? I did four tetrahedra with $fn=64 and it took 27s, or 6.75s per tetrahedron. I'm told by the experts that union is actually much slower than hull, so it might be possible to make this faster by computing our own point sets for the spheres, unioning the points together, and then doing a hull on the resulting large point set. Is it worth the complexity? Linde's hull() function hits a recursion limit after a few thousand points, so I think it would be necessary to use the hack of invoking polyhedron with bogus faces and passing that to hull(). That hack is actually quite fast and computes the hull of 40000 points in 2s. I guess union is slow because it has to check all the faces for intersection?
Actually I've been waiting for this code: $fn=64;polyhedron(faces=60,index=0,or=1,r=.1); to finish and it's taking an extremely long time---just finished at 8.5 minutes. Maybe it is worth investigating the more complex alternative hack I proposed above. Any ideas on a good way to generate a set of points on a sphere? Do we know what points OpenSCAD uses when you ask for a sphere with a given $fn?
A complication for the orientation problem is that for shapes with multiple types of face, which one do you want down? It might be desirable to supply options for each face type. Doing this automatically would require processing all of the faces and then looking for different ones.
The last question is, do we change the default behavior so that shapes appear by default with a side down? (Is there some advantage in the canonical orientation? There might be...I don't know the answer. It might place shapes in a meaningful relation relative to their duals or relative to each other.)
A final question relates to raising the shapes so that they are sitting on the xy plane. The user may not want this because the user wants to know where the shape center is, so it seems both options are needed (e.g. center=true / center=false) But if center=false, the user may need access to the shape's distance below the xy plane for later operations. See below on the issue of having functions that interrogate the library for the user.
A last observation is that due to the non-sphericality of spheres, it may be very tricky to get the shape exactly lined up onto the xy plane when using r>0. I'm not sure exactly how much of an issue this is. Maybe everything just lines up because spheres behave reasonably in the axes directions.
Hmmm. Actually running minkowski is much faster than hull:
minkowski(){
sphere($fn=256,r=.2);
regular_polyhedron(faces=60,index=2,side=1);
}
It's taking 4s to produce 60 sided solids. Tetrahedron in 2s. The hull hack might be faster, but is this fast enough?
So then do I rewrite the code to use minkowski, or just take out the smoothing option entirely and let users use minkowski? (I'm leaning towards the first option.)
Curiously I got very different run time behavior on a different machine running linux. The minkowski method rounds a 60 sided polyhedron in 0s. I implemented the hack I mentioned and it takes 5s. Not sure what to make of that. (Hard to be sure of apples to apples comparison since the hack has a different point layout on the sphere, and maybe a different number of points.)
In any case, we might want this other hull function since it can handle large data. We could call this one point_hull and Linde's function we could call hull_faces, since that's really what it does.
module pointhull(points){
points = simplify3d_path(points); // colinear points are not on the hull and generate a warning message
extra = len(points)%3;
list = concat(
[[for(i=[0:extra+2])i]],
[for(i=[extra+3:3:len(points)-3])[i,i+1,i+2]]);
hull() polyhedron(points, faces=list);
}
and then here's the tweaked version of regular_polyhedron that uses it:
function spherept(center, r, N) =
[for(i = [0:N])
let (theta = 180*i/N, count=1+ceil(N*sin(theta)))
each [for(j=[0:count]) let(phi=360*j/count)
center+r*[sin(theta)*cos(phi),sin(theta)*sin(phi), cos(theta)]]];
module regular_polyhedron(name=undef,index=undef,type=undef,faces=undef,side=1,ir=undef, mr=undef, or=undef, r=0)
{
libsize = len(library);
argcount = sum([is_def(ir)?1:0, is_def(mr)?1:0, is_def(or)?1:0]);
assert(argcount<=1, "You must specify only one of 'ir', 'mr' and 'or'");
indexlist = [for(i=[0:len(library)-1]) if ((is_undef(name) || library[i][pname]==name)
&& (is_undef(type) || library[i][class]==type)
&& (is_undef(faces) || library[i][facecount]==faces)) i];
echo("indexlist = ",indexlist);
entry = library[is_def(index) ? indexlist[index] : indexlist[0]];
echo("index = ",library[is_def(index) ? indexlist[index] : indexlist[0]]);
scalefactor = (argcount == 0 ? side :
is_def(ir) ? ir/entry[in_radius] :
is_def(mr) ? mr/entry[mid_radius] : or/entry[out_radius]) / entry[edgelen];
if (r==0)
polyhedron(scalefactor*entry[vertices], hull(entry[vertices]));
else {
fn = segs(r);
fixed_r = r/cos(180/fn);
adjusted_scale = scalefactor - fixed_r / entry[in_radius] / entry[edgelen];
pointhull([for(v=adjusted_scale*entry[vertices]) each spherept(v,fixed_r, fn)]);
// place_copies(adjusted_scale*entry[vertices])
// sphere(r=fixed_r, $fn=fn);
}
}
So which one is fastest for you?
So then do I rewrite the code to use minkowski, or just take out the smoothing option entirely and let users use minkowski? (I'm leaning towards the first option.)
I concur. However, I'd suggest naming the r
rounding radius to something else, as r
would be my very first instinct to use to define the outer radius of the polyhedron. (Just like cylinder()
, sphere()
, and circle()
.)
Actually, if we're going with consistency with the rest of the library, I'd recommend providing r
and d
alternate arguments for the outer radius, and a circum
argument that would tell it that r
or d
are the radius to circumscribe, vs. inscribe.
Naming the rounding radius fillet
would be consistent, but I'd be happy with round
or rounding
. In BOSL2, I plan to change most references to fillet
to round
.
Do we know what points OpenSCAD uses when you ask for a sphere with a given $fn?
Yes. I had to figure this out when making staggered_sphere()
in shapes.scad. It generates points on $fn/2 latitudes, and $fn longitudinal angles. Something like:
function spherepts(r, fn) = let(tstep = 360/fn, pstep = 180 / floor(fn/2)) [
for (phi=[pstep/2:pstep:180], theta=[0:tstep:360-tstep/2]) spherical_to_xyz(r,theta,phi)
];
Regarding finding bounds of a polyhedron, you can just pass the vertexes to pointlist_bounds()
.
Regarding spinning a face to be flat, if you have the normal for a face you want to rotate to vertical, you can just use rot(from=normv, to=V_UP) ...
I've been using orient_and_align()
in a lot of the shapes code, with orient
and align
arguments, but that's mostly for cubic alignments and rotations. The face orientation of general polyhedra is a slightly different matter, though. Technically, orient
can be any [X,Y,Z] rotation, but finding those angles is somewhat tricky.
Regarding slowness, the 2 to 3 second generation time was for a plain platonic icosahedron (20 triangular faces) with no rounding at all. Mind you, my laptop is not very fast.
Looking at the polyhedra, a lot of shapes don't actually have an obvious face to orient flat. I'm not sure what to do about this.
for(i=[0:60]) up(2*i) regular_polyhedron(faces=60,index=0,or=1);
This took 4 seconds for me.
So it takes 2-3 s to make an icosahedron and 4 s to make 60 of the 60-face polyhedron? That's weird.
In order to use rot(from, to..) I need to have made geometry. But if I then want to use pointlist_bounds I need to still have coordinate points. You don't seem to have the from and to options on the operators that work on points. But also, for the case of no rounding, the in-radius should give the correct distance. But with rounding, it might be slightly in error---that's where I would end up not exactly on the x-y plane, I think. I suppose if I do rounding using the point_hull method I'd have the point set.
Any thoughts on rounding via minkowski vs rounding via point_hull?
I'm not sure I understand your suggestion for inputs r and d. First I thought just make r equivalent to or. But you were thinking another argument, say rtype="outer", "middle","inner" ?
I don't think orient_and_align() will help with polyhedra not based on the right angle. I think the main challenge really is to decide exactly what behavior makes sense, and then I can find some way to implement the desired behavior, most likely by constructing the faces and then picking a suitable face to supply an alignment vector. One possibility is that in face-down mode, the largest face goes down. But maybe it's desirable to let the user pick, so then we need an input mechanism to specify which face goes down, perhaps by number of sides? facedown=5 puts the pentagon down and facedown=3 puts the triangle down? As you say, there are also some shapes where it really isn't clear that face down makes a lot of sense...but you can also argue that corner down often doesn't make sense, so I'm not sure this should be a concern. The user can choose the facedown option or not.
A question about centering is whether the uncentered shape should be entirely in the positive octant like happens e.g. with cube().
Another question: do we need to supply the user with face coordinates and normals...or with some other mechanism for putting stuff onto faces or polyhedra. (Maybe regular_polyhedron does something with its children?)
Linking in #23 as a probable requirement.
I thought about this more and I think I'm going to try to do something like what you did with grid, where the children get placed on each face of the polyhedron, and I'll put the face corners into a $variable for the user to use if desired. The one thing I'm not sure about is how to order the faces.
Another question is whether it's better to supply a "difference=true" operation that makes it subtract the children from the polyhedron or supply a "draw=false" option that causes it to place the children without drawing the polyhedron (so the user could create the solid and then difference the children). Any thoughts here?
I kind of like the $variable face children idea. Each one will probably need to know number of face sides and size, face normal, and more.
Regarding difference
and draw
, they both seem to have their uses. I see three usages:
Feels like it should be a single argument, with three values.
Yes, orient_and_align() seems inappropriate here.
I think I prefer minkowski()
for rounding. It'll probably be faster than hulling the point cloud.
rotate_points3d()
now accepts from
, to
, and axis
args.
In cyl()
, you can specify radius r
or diameter d
, then the circum
argument specifies whether you want in inscribe or circumscribe the circle of that radius/diameter. For purposes of polyhedra, I can see it having true, false, and "mid" optional values.
I really don't know what to do about polyhedra orientation. Some shapes make sense to have large face down. Some make sense to have point-up, with no particular face aligned.
Centering is also a problem. The center of tetrahedral shapes, for example is not vertically centered.
I've implemented a bunch of new stuff. Hulling the point cloud with the polyhedron hack is really fast. I can hull 40000 points in a few seconds. But it seems to be system dependent which one is faster. One one of my computers it seems like they were about the same. The other one, the hull method was twice as fast. I would guess that the time to compute the points of all the spheres is a substantial fraction of the time. I was feeling like having to compute the points on the sphere makes the point hull method a little bit messier. It does have the advantage that we can get the resulting shape exactly onto the xy plane, which is otherwise impossible.
I've added "r" and "d". I don't like "circum" for this context because it doesn't make sense really to write circum="middle". I made an "rtype" parameter that can be set to "outside", "inside" or "middle". I am not convinced this is worth keeping because of or, ir and mr. (Note also you might want to clarify the purpose of circum for cyl() in the wiki. I had to think for a bit before I understood what you meant by "circumscribe a circle". I would add a note about how circles are polygons and their interiors are undersized.)
I added align which works like for cubes---it shifts the polyhedron so that it's most extreme point is on the specified plane.
I added facedown which puts some random face down. This needs help as sometimes it's really not the right face. More on that later. If you use facedown you can shift the solid up so it's resting on the xy plane.
The code now puts the children onto the faces and it passes $face and $faceindex to the child. I added a "draw=false" option so you can place objects without the polyhedron (or for differencing from a drawn solid). If you have repeat=true then the children are cycled through to cover all faces. The faces are in an arbitrary order.
The obvious problems I see that need help are
use<BOSL/math.scad>
use<BOSL/paths.scad>
use<BOSL/transforms.scad>
use<hull.scad>
function num_defined(v) = sum([for(entry=v) is_def(entry) ? 1 : 0]);
function sortind(data) = let(augdata = array_zip([for(i=[0:len(data)])i], data))
array_subindex(sort(augdata,1),0);
function even_permutations(v) = [v, [v[2], v[0], v[1]], [v[1],v[2],v[0]]];
function all_permutations(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]]];
// sign=="even" means an even number of minus signs (odd number of plus signs)
// sign=="odd" means an odd number of minus signs (even number of plus signs)
function point_ref(points, sign="both") =
unique([ for(i=[-1,1],j=[-1,1],k=[-1,1]) if (sign=="both" || sign=="even" && i*j*k>0 || sign=="odd" && i*j*k<0)
each [for(point=points) vmul(point,[i,j,k])]
]);
// Polyhedra data from Wikipedia and http://dmccooey.com/polyhedra/
phi = (1+sqrt(5))/2;
tribonacci=(1+4*cosh(acosh(2+3/8)/3))/3;
pname = 0;
class = 1;
facecount = 2;
edgelen = 3;
in_radius = 4;
mid_radius = 5;
out_radius = 6;
volume = 7;
vertices = 8;
library = [
["tetrahedron", "platonic", 4, 2*sqrt(2), sqrt(6)/12, sqrt(2)/4, sqrt(6)/4, 1/6/sqrt(2),
point_ref([[1,1,1]], sign="even")],
["cube", "platonic", 6, 2, 1/2, 1/sqrt(2), sqrt(3)/2, 1,
point_ref([[1,1,1]])],
["octahedron", "platonic", 8, sqrt(2), sqrt(6)/6, 1/2, sqrt(2)/2, sqrt(2)/3,
point_ref(even_permutations([1,0,0]))],
["dodecahedron", "platonic", 12, 2/phi, sqrt(5/2+11*sqrt(5)/10)/2, (3+sqrt(5))/4, sqrt(3)*phi/2, (15+7*sqrt(5))/4,
point_ref(concat([[1,1,1]],even_permutations([0,phi,1/phi])))],
["icosahedron", "platonic", 20, 2, phi*phi/2/sqrt(3), cos(36), sin(72), 5*(3+sqrt(5))/12,
point_ref(even_permutations([0,1,phi]))],
["truncated tetrahedron", "archimedean", 8, sqrt(8), sqrt(6)/4, 3*sqrt(2)/4, sqrt(11/8), 23*sqrt(2)/12,
point_ref(all_permutations([1,1,3]),sign="even")],
["cuboctahedron", "archimedean", 14, sqrt(2), sqrt(2)/2, sqrt(3)/2, 1, 5*sqrt(2)/3,
point_ref(all_permutations([1,1,0]))],
["truncated cube", "archimedean", 14, 2*(sqrt(2)-1), (1+sqrt(2))/2, 1+sqrt(2)/2, sqrt(7+4*sqrt(2))/2, 7+14*sqrt(2)/3,
point_ref(all_permutations([1,1,sqrt(2)-1]))],
["truncated octahedron", "archimedean", 14, sqrt(2), sqrt(6)/2, 1.5, sqrt(10)/2, 8*sqrt(2),
point_ref(all_permutations([0,1,2]))],
["rhombicuboctahedron", "archimedean", 26, 2, (1+sqrt(2))/2, sqrt(2*(2+sqrt(2)))/2, sqrt(5+2*sqrt(2))/2, 4+10*sqrt(2)/3,
point_ref(even_permutations([1,1,1+sqrt(2)]))],
["truncated cuboctahedron", "archimedean", 26, 2, (1+2*sqrt(2))/2, sqrt(6*(2+sqrt(2)))/2, sqrt(13+6*sqrt(2))/2,
(22+14*sqrt(2)),
point_ref(all_permutations([1,1+sqrt(2), 1+2*sqrt(2)]))],
["snub cube", "archimedean", 38, 1.60972,1.14261350892596209,1.24722316799364325, 1.34371337374460170,
sqrt((613*tribonacci+203)/(9*(35*tribonacci-62))),
concat(point_ref(even_permutations([1,1/tribonacci,tribonacci]), sign="odd"),
point_ref(even_permutations([1,tribonacci,1/tribonacci]), sign="even"))], // changed from odd!
["icosidodecahedron", "archimedean", 32, 1, sqrt(5*(5+2*sqrt(5)))/5,sqrt(5+2*sqrt(5))/2, phi, (14+17*phi)/3,
point_ref(concat(even_permutations([0,0,phi]),even_permutations([1/2,phi/2,phi*phi/2])))],
["truncated dodecahedron", "archimedean", 32, 2*phi-2, sqrt(7+11*phi)/2, (3*phi+1)/2,sqrt(11+phi*15)/2, 5*(99+47*sqrt(5))/12,
point_ref(concat(even_permutations([0,1/phi, 2+phi]),
even_permutations([1/phi,phi,2*phi]),
even_permutations([phi,2,phi+1])))],
["truncated icosahedron", "archimedean", 32, 2, (3*sqrt(3)+sqrt(15))/4, 3*phi/2, sqrt(58+18*sqrt(5))/4, (125+43*sqrt(5))/4,
point_ref(concat(even_permutations([0,1,3*phi]),
even_permutations([1,2+phi,2*phi]),
even_permutations([phi,2,phi*phi*phi])))],
["rhombicosidodecahedron", "archimedean", 62, 2, 3/10*sqrt(15+20*phi), sqrt(3/2+2*phi), sqrt(8*phi+7)/2, (31+58*phi)/3,
point_ref(concat(even_permutations([1,1,phi*phi*phi]),
even_permutations([phi*phi,phi,2*phi]),
even_permutations([2+phi,0,phi*phi])))],
["truncated icosidodecahedron", "archimedean", 62, 2*phi - 2, sqrt(15/4+5*phi),sqrt(9/2+6*phi),sqrt(19/4+6*phi),
95+50*sqrt(5),
point_ref(concat(even_permutations([1/phi,1/phi,3+phi]),
even_permutations([2/phi,phi,1+2*phi]),
even_permutations([1/phi,phi*phi,3*phi-1]),
even_permutations([2*phi-1,2,2+phi]),
even_permutations([phi,3,2*phi])))],
["snub dodecahedron", "archimedean", 92, 1, 1.98091594728184,2.097053835252087,2.155837375115, 37.61664996273336,
concat(point_ref(even_permutations([0.374821658114562,0.330921024729844,2.097053835252088]), sign="odd"),
point_ref(even_permutations([0.192893711352359,1.249503788463027,1.746186440985827]), sign="odd"),
point_ref(even_permutations([1.103156835071754,0.847550046789061,1.646917940690374]), sign="odd"),
point_ref(even_permutations([0.567715369466922,0.643029605914072,1.977838965420219]), sign="even"),
point_ref(even_permutations([1.415265416255982,0.728335176957192,1.454024229338015]), sign="even"))],
["rhombic dodecahedron", "catalan", 12, sqrt(3), sqrt(2/3), 2*sqrt(2)/3, 2/sqrt(3), 16*sqrt(3)/9,
point_ref(concat([[1,1,1]], even_permutations([2,0,0])))],
["triakis tetrahedron","catalan", 12, 9/5, 5*sqrt(22)/44, 5*sqrt(2)/12, 5*sqrt(6)/12, 25*sqrt(2)/36,
concat(point_ref([9*sqrt(2)/20*[1,1,1]],sign="even"),
point_ref([3*sqrt(2)/4*[1,1,1]],sign="odd"))],
["rhombic dodecahedron", "catalan", 12, 1, 2/sqrt(6), 4/3/sqrt(2), 2/sqrt(3), 16/3/sqrt(3),
point_ref(concat([[1,1,1]/sqrt(3)],even_permutations([2/sqrt(3),0,0])))],
["tetrakis hexahedron", "catalan", 24, 1, 2/sqrt(5), 2*sqrt(2)/3, 2/sqrt(3), 32/9,
point_ref(concat([[2/3,2/3,2/3]],even_permutations([1,0,0])))],
["triakis octahedron", "catalan", 24, 2, sqrt(17*(23+16*sqrt(2)))/34, 1/2+sqrt(2)/4,(1+sqrt(2))/2,3/2+sqrt(2),
point_ref(concat([[1,1,1]],even_permutations([1+sqrt(2),0,0])))],
["deltoidal icositetrahedron", "catalan", 24, 2*sqrt(10-sqrt(2))/7, 7*sqrt((7+4*sqrt(2))/(34 * (10-sqrt(2)))),
7*sqrt(2*(2+sqrt(2)))/sqrt(10-sqrt(2))/4, 7*sqrt(2)/sqrt(10-sqrt(2))/2,
(14+21*sqrt(2))/sqrt(10-sqrt(2)),
point_ref(concat(even_permutations([0,1,1]), even_permutations([sqrt(2),0,0]),
even_permutations((4+sqrt(2))/7*[1,1,1])))],
["pentagonal icositetrahedron","catalan",24, 0.593465355971, 1.950681331784, 2.1015938932963, 2.29400105368695,
35.6302020120713,
concat(
point_ref(even_permutations([0.21879664300048044,0.740183741369857,1.0236561781126901]),sign="even"),
point_ref(even_permutations([0.21879664300048044,1.0236561781126901,0.740183741369857]),sign="odd"),//changed from odd
point_ref(even_permutations([1.3614101519264425,0,0])),
point_ref([0.7401837413698572*[1,1,1]]))],
["rhombic triacontahedron", "catalan", 30,1, sqrt(1+2/sqrt(5)), 1+1/sqrt(5), (1+sqrt(5))/2, 4*sqrt(5+2*sqrt(5)),
concat(point_ref(even_permutations([0,sqrt(1+2/sqrt(5)), sqrt((5+sqrt(5))/10)])),
point_ref(even_permutations([0,sqrt(2/(5+sqrt(5))), sqrt(1+2/sqrt(5))])),
point_ref([sqrt((5+sqrt(5))/10)*[1,1,1]]))],
["disdyakis dodecahedron", "catalan", 48, 1,sqrt(249/194+285/194/sqrt(2)) ,(2+3*sqrt(2))/4, sqrt(183/98+213/98/sqrt(2)),
sqrt(6582+4539*sqrt(2))/7,
point_ref(concat(even_permutations([sqrt(183/98+213/98/sqrt(2)),0,0]),
even_permutations(sqrt(3+3/sqrt(2))/2 * [1,1,0]),[7/sqrt(6*(10-sqrt(2)))*[1,1,1]]))],
["pentakis dodecahedron", "catalan", 60, 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,
point_ref(concat(even_permutations([0,(5-phi)/6, phi/2+2/3]),
even_permutations([0,(phi+1)/2,phi/2]),[(4*phi-1)/6 * [1,1,1]]))],
["triakis icosahedron", "catalan", 60, 1, sqrt((139+199*phi)/244), (8*phi+1)/10, sqrt(13/8+19/8/sqrt(5)), (13*phi+3)/2,
point_ref(concat(even_permutations([(phi+7)/10, 0, (8*phi+1)/10]),
even_permutations([0, 1/2, (phi+1)/2]),[phi/2*[1,1,1]]))],
["deltoidal hexecontahedron", "catalan", 60, sqrt(5*(85-31*sqrt(5)))/11, sqrt(571/164+1269/164/sqrt(5)), 5/4+13/4/sqrt(5),
sqrt(147+65*sqrt(5))/6, sqrt(29530+13204*sqrt(5))/3,
point_ref(concat(even_permutations([0,0,sqrt(5)]),
even_permutations([0,(15+sqrt(5))/22, (25+9*sqrt(5))/22]),
even_permutations([0,(5+3*sqrt(5))/6, (5+sqrt(5))/6]),
even_permutations([(5-sqrt(5))/4, sqrt(5)/2, (5+sqrt(5))/4]),
[(5+4*sqrt(5))/11*[1,1,1]]))],
["deltoidal hexecontahedron", "catalan", 60,0.58289953474498, 3.499527848905764,3.597624822551189,3.80854772878239,
189.789852066885,
concat(
point_ref(even_permutations([0.192893711352359,0.218483370127321,2.097053835252087]), sign="even"),
point_ref(even_permutations([0,0.7554672605165955,1.9778389654202186])),
point_ref(even_permutations([0,1.888445389283669154,1.1671234364753339])),
point_ref(even_permutations([0.56771536946692131,0.824957552676275846,1.8654013108176956657]),sign="odd"),
point_ref(even_permutations([0.37482165811456229,1.13706613386050418,1.746186440985826345]), sign="even"),
point_ref(even_permutations([0.921228888309550,0.95998770139158,1.6469179406903744]),sign="even"),
point_ref(even_permutations([0.7283351769571914773,1.2720962825758121,1.5277030708585051]),sign="odd"),
point_ref([1.222371704903623092*[1,1,1]]))],
["disdyakis triacontahedron","catalan", 120, sqrt(15*(85-31*sqrt(5)))/11, sqrt(3477/964+7707/964/sqrt(5)), 5/4+13/4/sqrt(5),
sqrt(441+195*sqrt(5))/10,sqrt(17718/5+39612/5/sqrt(5)),
point_ref(concat(even_permutations([0,0,3*(5+4*sqrt(5))/11]),
even_permutations([0,(5-sqrt(5))/2,(5+sqrt(5))/2]),
even_permutations([0,(15+9*sqrt(5))/10,3*(5+sqrt(5))/10]),
even_permutations([3*(15+sqrt(5))/44,3*(5+4*sqrt(5))/22, (75+27*sqrt(5))/44]),
[sqrt(5)*[1,1,1]]))],
];
/* Notes on Kepler-Poinsot Solids
It seems these can be made by adding/subtracting a pyramid from other solids.
It's actually possible to do this generally if we have an attach module, assuming that the normal
to all the faces of a polyhedron is the line from the face center to the origin.
Great dodecahedron: from icosahedron subtract pyramind of height sqrt(5/3-phi)
Great stellated dodecahedron: from icosahedron add pyramids of height sqrt(2/3+phi)
Small stellated dodecahedron: from dodecahedron add pyramids of height sqrt((3+4*phi)/5)
Great icosahedron seems to be complicated, not easily derivable
*/
// Selecting the polyhedron:
//
// You constrain the polyhedra list by specifying:
// name: e.g. "dodecahedron" or "pentagonal icositetrahedron"
// type: options are "platonic", "archimedean" and "catalan"
// faces: a required number of faces
//
// The result is a list of selected polyhedra. You then specify index to choose which one of the
// remaining polyhedra you want. If you don't give index the first one on the list is created.
//
// Examples:
// faces=12, index=2 will create the 3rd solid with 12 faces
// type="archimedean", faces=14, will create the first archimedean solid with 14 faces (there are 3)
//
// Choosing the size of your polyhedron:
//
// The default is to create a polyhedron whose smallest edge has length 1.
//
// You can specify the smallest edge length with the size option
//
// Alternatively you can specify the size of the inscribed sphere, midscribed sphere, or circumscribed sphere
// using ir, mr and cr respectively.
//
// If you use the r or d options that specifies the circumscribed sphere radius or diameter.
// The rtype option can be set to "middle" or "inner" to change the function of r and d. (Is the rtype option needed, or is
// is just unnecessary complexity?
//
// Orientation
//
// facedown
//
// Rounding
//
// Specify a rounding radius with the rounding parameter. Note that especially fi $fn is small, the dimensions might be slightly
// off (due to imperfect dimensions of sphere).
//
module regular_polyhedron(name=undef,index=undef,type=undef,faces=undef,
side=1,
ir=undef, mr=undef, or=undef,
r=undef, d=undef, rtype="outer",
rounding=0, align=[0,0,0], center=undef, repeat=true, facedown=false,
draw=true
)
{
align = is_def(center) ? [0,0,0] : align;
libsize = len(library);
assert(rounding>=0, "'rounding' must be nonnegative");
argcount = num_defined([ir,mr,or,r,d]);
assert(argcount<=1, "You must specify only one of 'ir', 'mr', 'or', 'r', and 'd'");
r = is_def(d) ? d/2 : r;
ir = is_def(r) && rtype=="inner" ? r : ir;
or = is_def(r) && rtype=="outer" ? r : or;
mr = is_def(r) && rtype=="middle" ? r : mr;
indexlist = [for(i=[0:len(library)-1]) if ((is_undef(name) || library[i][pname]==name)
&& (is_undef(type) || library[i][class]==type)
&& (is_undef(faces) || library[i][facecount]==faces)) i];
validindex = is_undef(index) || (index>=0 && index<len(indexlist));
assert(validindex,
str(len(indexlist)," polyhedra meet specifications, so 'index' must be in [0,",
len(indexlist)-1,"], but 'index' is ",index));
entry = library[is_def(index) ? indexlist[index] : indexlist[0]];
scalefactor = (argcount == 0 ? side :
is_def(ir) ? ir/entry[in_radius] :
is_def(mr) ? mr/entry[mid_radius] : or/entry[out_radius]) / entry[edgelen];
face_triangles = hull(entry[vertices]);
// facenormal = facedown == true ? facenormal(entry[vertices], face_triangles[0]) : [0,0,-1];
facenormal = facenormal(entry[vertices], face_triangles[0]);
scaled_points = scalefactor * (facedown ? rotate_points3d(entry[vertices], from=facenormal, to=[0,0,-1]) :
entry[vertices]);
//test = full_faces(scaled_points, face_triangles);
bounds = pointlist_bounds(scaled_points);
boundtable = [bounds[0], [0,0,0], bounds[1]];
translation = [for(i=[0:2]) -boundtable[1-align[i]][i]];
if (draw){
if (rounding==0)
polyhedron(translate_points(scaled_points, translation), faces = face_triangles);
else {
fn = segs(rounding);
rounding = rounding/cos(180/fn);
adjusted_scale = 1 - rounding / entry[in_radius] / entry[edgelen] / scalefactor;
minkowski(){
sphere(r=rounding, $fn=fn);
polyhedron(translate_points(adjusted_scale*scaled_points,translation), faces = face_triangles);
}
}
}
if ($children>0) {
faces = full_faces(scaled_points, face_triangles); // What should face order be?
maxrange = repeat ? entry[facecount]-1 : $children-1;
for(i=[0:maxrange]) {
normal = faces[i][1]; // Would like to orient so an edge is parallel to x axis
facepts = translate_points(select(scaled_points, faces[i][0]), translation);
center = mean(facepts);
rotatedface = rotate_points3d(translate_points(facepts,-center), from=normal, to=[0,0,1]);
clockwise = sortind([for(pt=rotatedface) -atan2(pt.y,pt.x)]);
$face = path2d(select(rotatedface,clockwise));
$faceindex = i;
translate(center)
rot(from=[0,0,1], to=normal)
children(i % $children);
}
}
}
function facenormal(pts, face) = cross(pts[face[2]]-pts[face[0]], pts[face[1]]-pts[face[0]]);
// hull() function returns triangulated faces. This function identifies the vertices that belong to each face.
// But the order is arbitrary.
function full_faces(pts,faces) = len(faces)==0 ? [] :
let(
plane = plane(pts, faces[0][0],faces[0][1],faces[0][2]),
same = [for(i=[1:1:len(faces)-1]) if (all([for(v=faces[i]) coplanar(plane, pts[v])])) i],
different = [for(i=[1:1:len(faces)-1]) if (!all([for(v=faces[i]) coplanar(plane, pts[v])])) i],
normal = facenormal(pts,faces[0]),
oneface = unique(flatten(select(faces,concat([0],same))))
)
concat([[oneface,normal]],full_faces(pts, select(faces,different)));
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// Examples start here: not part of library
$fn=96;
//regular_polyhedron("pentagonal icositetrahedron", or=5);
//%sphere(r=5-.2, $fn=100);
//regular_polyhedron(index=0, type="catalan");
//regular_polyhedron("disdyakis triacontahedron");
/*
// Test that rounded shapes are the same size as unrounded
shape = "dodecahedron";
//shape = "cube";
top_half(cp=[0,0,.2])
difference(){
regular_polyhedron(shape);
regular_polyhedron(shape, rounding=0.2,side=1.0000);
}
*/
/*
// Display of all solids with insphere, midsphere and circumsphere
for(i=[0:len(library)-1]) {
place_copies([[3*i,0,0]]) // Plain polyhedron
regular_polyhedron(index=i, mr=1,facedown=true);
place_copies([[3*i,3.5,0]]){ // Inner radius means sphere touches faces of the polyhedron
sphere(r=1.005); // Sphere is slightly oversized so you can see it poking out from each face
%regular_polyhedron(index=i, ir=1,facedown=true);
}
place_copies([[3*i,7,0]]){ // Mid radius means the sphere touches the center of each edge
sphere(r=1);
%regular_polyhedron(index=i, mr=1,facedown=true);
}
place_copies([[3*i,11,0]]){ // outer radius means points of the polyhedron are on the sphere
%sphere(r=.99); // Slightly undersized sphere means the points poke out a bit
regular_polyhedron(index=i, or=1,facedown=true);
}
}
*/
// Children demonstration
h1 = sqrt((5+2*sqrt(5))/5);
xdistribute(spacing=2){
regular_polyhedron(faces=12,index=2,repeat=true) color("red") sphere(r=.1,$fn=32);
scale([1,1,1]/3)
regular_polyhedron("tetrahedron",side=3,repeat=true)
up($faceindex/8/2) cube([$faceindex/8,$faceindex/8,$faceindex/8],center=true);
regular_polyhedron("octahedron",side=1,repeat=true,facedown=true)
linear_extrude(height=.1)offset(-.1)polygon($face);
regular_polyhedron("octahedron",repeat=false){
cube([.3,.3,.3]/2,center=true);
sphere(r=.3);
cylinder(r=.1, h=1,center=false);
}
regular_polyhedron("octahedron",repeat=true){
cube([.3,.3,.3]/2,center=true);
sphere(r=.3);
cylinder(r=.1, h=1,center=false);
}
// Stellated dodecahedron (value of h1 appears to be wrong)
scale([1,1,1]/10)
regular_polyhedron("dodecahedron",side=4,repeat=true)
fastpointhull(concat(path3d($face), [[0,0,3*h1]]));
// What is the rule for $face. When I tried to compute with it I got errors.
// Is it not allowed on the RHS of a variable assignment?
//
// I was thinking about making a stellated_polyhedron function, but it will be
// a lot of copying of the interface.
//
// Maybe instead a stellate parameter?
scale([1,1,1]/20)
difference(){
regular_polyhedron("tetrahedron", side=20,facedown=true);
regular_polyhedron("tetrahedron", side=20,facedown=true,draw=false)
down(.3)linear_extrude(height=1)text(str($faceindex),halign="center",valign="center");
}
scale([1,1,1]/3)union(){
// The canonical coordinates may have useful relationships,
%regular_polyhedron("dodecahedron",ir=1,facedown=false);
regular_polyhedron("icosahedron",or=1,facedown=false);
}
}
module fastpointhull(points){
points = simplify3d_path(points); // colinear points are not on the hull and generate a warning message
extra = len(points)%3;
list = concat(
[[for(i=[0:extra+2])i]],
[for(i=[extra+3:3:len(points)-3])[i,i+1,i+2]]);
hull() polyhedron(points, faces=list);
}
/// tetrahedron speed test
/*
place_copies([for(i=[0:20])[0,0,i]])
regular_polyhedron("tetrahedron", align=[1,1,1]);
*/
// TODO
// Use volume info?
// Are archimedean & catalan solids in the "right" order in the list?
// Add a stellate function
You think it's worth adding a difference flag where the children are subtracted from the solid? Is there a way to do this without having to repeat the whole children loop?
Also I was thinking I should provide a way to have the children oriented with respect to the parent axis instead of the polyhedron surface normal.
Oh, and also, if you're going to make a ton of polyhedra the code is going to be slow with "use". You need to use "include" instead because recomputing library every call wastes time. I can't believe that bug was discovered in 2014, was "easy to fix" and not just fixed then and there.
Are you planning to add something like sortind()? I needed this for putting the face vertices into order and I thought you were going to add it, but it looks like it's not there (yet).
These should do. I'll add them.
fuction enumerate(v) = [for (i=[0:len(v)-1]) [i,v[i]]];
function sortind(v) = array_subindex(sort(enumerate(v),idx=1), 0);
Added enumerate()
and sortidx()
in master branch commit 670c47ca869a2ce520a84810b9951dc0bfbc99af
Huh. I didn't even realize I started with sortind(), and ended up with sortidx().
I managed to redesign the slow function full_faces so it is fast. The extreme slowdown I observed was due to examples in hull.scad. I have fixed some errors in the polyhedra names and ordered them in a well-defined and consistent order. I added face types and allow you to select polyhedra by type of face (e.g. give polyhedra that include a pentagon or give polyhedra that have a square or octagon). I added a "draw=false" option to suppress display of the solid itself. I added rotate_children=false to place the children oriented in the parent coordinate system.
Here are what I see as the remaining questions:
use<lib/BOSL/math.scad>
use<lib/BOSL/paths.scad>
use<lib/BOSL/transforms.scad>
use<hull.scad>
function num_defined(v) = sum([for(entry=v) is_def(entry) ? 1 : 0]);
function quantvect(vec,scale) = [for (v=vec) quant(v,scale)];
/////////////////////////////////////////////////////////////////////////////
//
// Some internal functions
//
// All permutations and even permutations of three items
//
function _even_perms(v) = [v, [v[2], v[0], v[1]], [v[1],v[2],v[0]]];
function _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]]];
//
// Point reflections across all planes. In the unconstrained case, this means one point becomes 8 points.
//
// sign=="even" means an even number of minus signs (odd number of plus signs)
// sign=="odd" means an odd number of minus signs (even number of plus signs)
//
function _point_ref(points, sign="both") =
unique([ for(i=[-1,1],j=[-1,1],k=[-1,1]) if (sign=="both" || sign=="even" && i*j*k>0 || sign=="odd" && i*j*k<0)
each [for(point=points) vmul(point,[i,j,k])]
]);
// Polyhedra data from Wikipedia and http://dmccooey.com/polyhedra/
phi = (1+sqrt(5))/2;
tribonacci=(1+4*cosh(acosh(2+3/8)/3))/3;
pname = 0;
class = 1;
facecount = 2;
facevertices = 3;
edgelen = 4;
in_radius = 5;
mid_radius = 6;
out_radius = 7;
volume = 8;
vertices = 9;
library = [
// Platonic Solids
["tetrahedron", "platonic", 4,[3], 2*sqrt(2), sqrt(6)/12, sqrt(2)/4, sqrt(6)/4, 1/6/sqrt(2),
_point_ref([[1,1,1]], sign="even")],
["cube", "platonic", 6, [4], 2, 1/2, 1/sqrt(2), sqrt(3)/2, 1,
_point_ref([[1,1,1]])],
["octahedron", "platonic", 8, [3], sqrt(2), sqrt(6)/6, 1/2, sqrt(2)/2, sqrt(2)/3,
_point_ref(_even_perms([1,0,0]))],
["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,
_point_ref(concat([[1,1,1]],_even_perms([0,phi,1/phi])))],
["icosahedron", "platonic", 20, [3], 2, phi*phi/2/sqrt(3), cos(36), sin(72), 5*(3+sqrt(5))/12,
_point_ref(_even_perms([0,1,phi]))],
// Archimedian Solids, listed in order by Wenniger number, W6-W18
["truncated tetrahedron", "archimedean", 8,[6,3], sqrt(8), sqrt(6)/4, 3*sqrt(2)/4, sqrt(11/8), 23*sqrt(2)/12,
_point_ref(_all_perms([1,1,3]),sign="even")],
["truncated octahedron", "archimedean", 14, [6,4], sqrt(2), sqrt(6)/2, 1.5, sqrt(10)/2, 8*sqrt(2),
_point_ref(_all_perms([0,1,2]))],
["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,
_point_ref(_all_perms([1,1,sqrt(2)-1]))],
["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,
_point_ref(concat(_even_perms([0,1,3*phi]),
_even_perms([1,2+phi,2*phi]),
_even_perms([phi,2,phi*phi*phi])))],
["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,
_point_ref(concat(_even_perms([0,1/phi, 2+phi]),
_even_perms([1/phi,phi,2*phi]),
_even_perms([phi,2,phi+1])))],
["cuboctahedron", "archimedean", 14, [4,3], sqrt(2), sqrt(2)/2, sqrt(3)/2, 1, 5*sqrt(2)/3,
_point_ref(_all_perms([1,1,0]))],
["icosidodecahedron", "archimedean", 32, [5,3], 1, sqrt(5*(5+2*sqrt(5)))/5,sqrt(5+2*sqrt(5))/2, phi, (14+17*phi)/3,
_point_ref(concat(_even_perms([0,0,phi]),_even_perms([1/2,phi/2,phi*phi/2])))],
["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,
_point_ref(_even_perms([1,1,1+sqrt(2)]))],
["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,
_point_ref(concat(_even_perms([1,1,phi*phi*phi]),
_even_perms([phi*phi,phi,2*phi]),
_even_perms([2+phi,0,phi*phi])))],
["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)),
_point_ref(_all_perms([1,1+sqrt(2), 1+2*sqrt(2)]))],
["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),
_point_ref(concat(_even_perms([1/phi,1/phi,3+phi]),
_even_perms([2/phi,phi,1+2*phi]),
_even_perms([1/phi,phi*phi,3*phi-1]),
_even_perms([2*phi-1,2,2+phi]),
_even_perms([phi,3,2*phi])))],
["snub cube", "archimedean", 38, [4,3], 1.60972,1.14261350892596209,1.24722316799364325, 1.34371337374460170,
sqrt((613*tribonacci+203)/(9*(35*tribonacci-62))),
concat(_point_ref(_even_perms([1,1/tribonacci,tribonacci]), sign="odd"),
_point_ref(_even_perms([1,tribonacci,1/tribonacci]), sign="even"))],
["snub dodecahedron", "archimedean", 92, [5, 3], 1, 1.98091594728184,2.097053835252087,2.155837375115, 37.61664996273336,
concat(_point_ref(_even_perms([0.374821658114562,0.330921024729844,2.097053835252088]), sign="odd"),
_point_ref(_even_perms([0.192893711352359,1.249503788463027,1.746186440985827]), sign="odd"),
_point_ref(_even_perms([1.103156835071754,0.847550046789061,1.646917940690374]), sign="odd"),
_point_ref(_even_perms([0.567715369466922,0.643029605914072,1.977838965420219]), sign="even"),
_point_ref(_even_perms([1.415265416255982,0.728335176957192,1.454024229338015]), sign="even"))],
// Catalan Solids, the duals to the Archimedean solids, listed in the corresponding order
["triakis tetrahedron","catalan", 12, [3], 9/5, 5*sqrt(22)/44, 5*sqrt(2)/12, 5*sqrt(6)/12, 25*sqrt(2)/36,
concat(_point_ref([9*sqrt(2)/20*[1,1,1]],sign="even"),
_point_ref([3*sqrt(2)/4*[1,1,1]],sign="odd"))],
["tetrakis hexahedron", "catalan", 24, [3], 1, 2/sqrt(5), 2*sqrt(2)/3, 2/sqrt(3), 32/9,
_point_ref(concat([[2/3,2/3,2/3]],_even_perms([1,0,0])))],
["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),
_point_ref(concat([[1,1,1]],_even_perms([1+sqrt(2),0,0])))],
["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,
_point_ref(concat(_even_perms([0,(5-phi)/6, phi/2+2/3]),
_even_perms([0,(phi+1)/2,phi/2]),[(4*phi-1)/6 * [1,1,1]]))],
["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,
_point_ref(concat(_even_perms([(phi+7)/10, 0, (8*phi+1)/10]),
_even_perms([0, 1/2, (phi+1)/2]),[phi/2*[1,1,1]]))],
["rhombic dodecahedron", "catalan", 12, [4], sqrt(3), sqrt(2/3), 2*sqrt(2)/3, 2/sqrt(3), 16*sqrt(3)/9,
_point_ref(concat([[1,1,1]], _even_perms([2,0,0])))],
["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)),
concat(_point_ref(_even_perms([0,sqrt(1+2/sqrt(5)), sqrt((5+sqrt(5))/10)])),
_point_ref(_even_perms([0,sqrt(2/(5+sqrt(5))), sqrt(1+2/sqrt(5))])),
_point_ref([sqrt((5+sqrt(5))/10)*[1,1,1]]))],
["deltoidal icositetrahedron", "catalan", 24, [4], 2*sqrt(10-sqrt(2))/7, 7*sqrt((7+4*sqrt(2))/(34 * (10-sqrt(2)))),
7*sqrt(2*(2+sqrt(2)))/sqrt(10-sqrt(2))/4, 7*sqrt(2)/sqrt(10-sqrt(2))/2,
(14+21*sqrt(2))/sqrt(10-sqrt(2)),
_point_ref(concat(_even_perms([0,1,1]), _even_perms([sqrt(2),0,0]),
_even_perms((4+sqrt(2))/7*[1,1,1])))],
["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),
sqrt(147+65*sqrt(5))/6, sqrt(29530+13204*sqrt(5))/3,
_point_ref(concat(_even_perms([0,0,sqrt(5)]),
_even_perms([0,(15+sqrt(5))/22, (25+9*sqrt(5))/22]),
_even_perms([0,(5+3*sqrt(5))/6, (5+sqrt(5))/6]),
_even_perms([(5-sqrt(5))/4, sqrt(5)/2, (5+sqrt(5))/4]),
[(5+4*sqrt(5))/11*[1,1,1]]))],
["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)),
sqrt(6582+4539*sqrt(2))/7,
_point_ref(concat(_even_perms([sqrt(183/98+213/98/sqrt(2)),0,0]),
_even_perms(sqrt(3+3/sqrt(2))/2 * [1,1,0]),[7/sqrt(6*(10-sqrt(2)))*[1,1,1]]))],
["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),
sqrt(441+195*sqrt(5))/10,sqrt(17718/5+39612/5/sqrt(5)),
_point_ref(concat(_even_perms([0,0,3*(5+4*sqrt(5))/11]),
_even_perms([0,(5-sqrt(5))/2,(5+sqrt(5))/2]),
_even_perms([0,(15+9*sqrt(5))/10,3*(5+sqrt(5))/10]),
_even_perms([3*(15+sqrt(5))/44,3*(5+4*sqrt(5))/22, (75+27*sqrt(5))/44]),
[sqrt(5)*[1,1,1]]))],
["pentagonal icositetrahedron","catalan",24, [5], 0.593465355971, 1.950681331784, 2.1015938932963, 2.29400105368695,
35.6302020120713,
concat(
_point_ref(_even_perms([0.21879664300048044,0.740183741369857,1.0236561781126901]),sign="even"),
_point_ref(_even_perms([0.21879664300048044,1.0236561781126901,0.740183741369857]),sign="odd"),
_point_ref(_even_perms([1.3614101519264425,0,0])),
_point_ref([0.7401837413698572*[1,1,1]]))],
["pentagonal hexecontahedron", "catalan", 60,[5], 0.58289953474498, 3.499527848905764,3.597624822551189,3.80854772878239,
189.789852066885,
concat(
_point_ref(_even_perms([0.192893711352359,0.218483370127321,2.097053835252087]), sign="even"),
_point_ref(_even_perms([0,0.7554672605165955,1.9778389654202186])),
_point_ref(_even_perms([0,1.888445389283669154,1.1671234364753339])),
_point_ref(_even_perms([0.56771536946692131,0.824957552676275846,1.8654013108176956657]),sign="odd"),
_point_ref(_even_perms([0.37482165811456229,1.13706613386050418,1.746186440985826345]), sign="even"),
_point_ref(_even_perms([0.921228888309550,0.95998770139158,1.6469179406903744]),sign="even"),
_point_ref(_even_perms([0.7283351769571914773,1.2720962825758121,1.5277030708585051]),sign="odd"),
_point_ref([1.222371704903623092*[1,1,1]]))],
];
// Selecting the polyhedron:
//
// You constrain the polyhedra list by specifying:
// name: e.g. "dodecahedron" or "pentagonal icositetrahedron"
// type: options are "platonic", "archimedean" and "catalan"
// faces: a required number of faces
// facetype: required face type. List of vertex counts for the faces. Exactly the types of
// faces listed must appear.
// facetype = 3 // polyhedron will all triangular faces
// facetype = [5,6] // polyhedron with only pentagons and hexagons (must have both!)
// hasfaces: list of vertex counts for faces; at least one listed type must appear
// hasfaces = 3 // polygon has at least one triangular face
// hasfaces = [5,6] // polygon has a hexagonal or a pentagonal face
//
// The result is a list of selected polyhedra. You then specify index to choose which one of the
// remaining polyhedra you want. If you don't give index the first one on the list is created.
//
// Examples:
// faces=12, index=2 will create the 3rd solid with 12 faces
// type="archimedean", faces=14, will create the first archimedean solid with 14 faces (there are 3)
//
// Choosing the size of your polyhedron:
//
// The default is to create a polyhedron whose smallest edge has length 1.
//
// You can specify the smallest edge length with the size option
//
// Alternatively you can specify the size of the inscribed sphere, midscribed sphere, or circumscribed sphere
// using ir, mr and cr respectively.
//
// If you use the r or d options that specifies the circumscribed sphere radius or diameter.
// The rtype option can be set to "middle" or "inner" to change the function of r and d. (Is the rtype option needed, or is
// is just unnecessary complexity?
//
// Orientation
//
// facedown: set this to true and a face of the largest size will be oriented down
// set it to a number, N, and a face with N vertices will be oriented down
// set it to false and you will get the canonical orientation, which in many cases is point down
//
// Rounding
//
// Specify a rounding radius with the rounding parameter. Note that especially fi $fn is small, the dimensions might be slightly
// off (due to imperfect dimensions of sphere).
//
// Children
//
// Children are placed on the faces of the polyhedron. The child coordinate system is positioned so that the origin is the
// center of the face. If rotate_children is true (the default) then the coordinate system
// is oriented so the z axis is normal to the face, which lies in the xy plane.
//
// If you give repeat=true (default) the children are cycled through to cover all faces. With repeat=false each child is used once.
//
// You can specify draw=false to suppress drawing of the polyhedron
//
// $faceindex - index number of the face
// $face - coordinates of the face (2d if rotate_children==true, 3d if not)
// $center - polyhedron center
module regular_polyhedron(name=undef,index=undef,type=undef,faces=undef,facetype=undef, hasfaces=undef,
side=1,
ir=undef, mr=undef, or=undef,
r=undef, d=undef, rtype="outer",
rounding=0, align=[0,0,0], center=undef, repeat=true, facedown=true,
draw=true, rotate_children=true,
)
{
align = is_def(center) ? [0,0,0] : align;
libsize = len(library);
assert(rounding>=0, "'rounding' must be nonnegative");
argcount = num_defined([ir,mr,or,r,d]);
assert(argcount<=1, "You must specify only one of 'ir', 'mr', 'or', 'r', and 'd'");
r = is_def(d) ? d/2 : r;
ir = is_def(r) && rtype=="inner" ? r : ir;
or = is_def(r) && rtype=="outer" ? r : or;
mr = is_def(r) && rtype=="middle" ? r : mr;
indexlist = [for(i=[0:len(library)-1]) if (
(is_undef(name) || library[i][pname]==name)
&& (is_undef(type) || library[i][class]==type)
&& (is_undef(faces) || library[i][facecount]==faces)
&& (is_undef(facetype) ||
0==compare_lists(is_list(facetype)?reverse(sort(facetype)):[facetype],
library[i][facevertices]))
&& (is_undef(hasfaces) ||
any([for (ft=hasfaces) in_list(ft,library[i][facevertices])]))
) i];
assert(len(indexlist)>0, "No polyhedra meet your specification");
validindex = is_undef(index) || (index>=0 && index<len(indexlist));
assert(validindex,
str(len(indexlist)," polyhedra meet specifications, so 'index' must be in [0,",
len(indexlist)-1,"], but 'index' is ",index));
entry = library[is_def(index) ? indexlist[index] : indexlist[0]];
valid_facedown=is_boolean(facedown) || in_list(facedown, entry[facevertices]);
assert(valid_facedown,str("'facedown' set to ",facedown," but selected polygon only has faces with size(s) ",entry[facevertices]));
scalefactor = (argcount == 0 ? side :
is_def(ir) ? ir/entry[in_radius] :
is_def(mr) ? mr/entry[mid_radius] : or/entry[out_radius]) / entry[edgelen];
face_triangles = hull(entry[vertices]);
faces = full_faces(entry[vertices], face_triangles);
faces_vertex_count = [for(face=faces) len(face[0])];
facedown = facedown == true ? entry[facevertices][0] : facedown;
facenormal = facedown == false ? [0,0,-1] :
faces[search(facedown, faces_vertex_count)[0]][1];
scaled_points = scalefactor * (facedown ? rotate_points3d(entry[vertices], from=facenormal, to=[0,0,-1]) :
entry[vertices]);
bounds = pointlist_bounds(scaled_points);
boundtable = [bounds[0], [0,0,0], bounds[1]];
translation = [for(i=[0:2]) -boundtable[1-align[i]][i]];
if (draw){
if (rounding==0)
polyhedron(translate_points(scaled_points, translation), faces = face_triangles);
else {
fn = segs(rounding);
rounding = rounding/cos(180/fn);
adjusted_scale = 1 - rounding / entry[in_radius] / entry[edgelen] / scalefactor;
minkowski(){
sphere(r=rounding, $fn=fn);
polyhedron(translate_points(adjusted_scale*scaled_points,translation), faces = face_triangles);
}
}
}
if ($children>0) {
maxrange = repeat ? entry[facecount]-1 : $children-1;
for(i=[0:maxrange]) {
// Would like to orient so an edge (longest edge?) is parallel to x axis
normal = rotate_points3d([faces[i][1]], from=facenormal, to=[0,0,-1])[0];
facepts = translate_points(select(scaled_points, faces[i][0]), translation);
center = mean(facepts);
rotatedface = rotate_points3d(translate_points(facepts,-center), from=normal, to=[0,0,1]);
clockwise = sortidx([for(pt=rotatedface) -atan2(pt.y,pt.x)]);
$face = rotate_children ? path2d(select(rotatedface,clockwise)) :
select(translate_points(facepts,-center), clockwise);
$faceindex = i;
$center = -translation-center;
translate(center)
if (rotate_children)
rot(from=[0,0,1], to=normal)
children(i % $children);
else
children(i % $children);
}
}
}
function facenormal(pts, face) = normalize(cross(pts[face[2]]-pts[face[0]], pts[face[1]]-pts[face[0]]));
// hull() function returns triangulated faces. This function identifies the vertices that belong to each face
// by grouping together the face triangles that share normal vectors. The output gives the face polygon
// point indices in arbitrary order (not usable as input to a polygon call) and a normal vector.
function full_faces(pts,faces) =
let(
normals = [for(face=faces) quantvect(facenormal(pts,face),1e-12)],
groups = unique_groups(normals)
)
[for(entry=groups) [unique(flatten(select(faces, entry))), facenormal(pts,faces[entry[0]])]];
function approx(a,b,eps=1e-8) = let(d=a-b) abs(d.x)<eps && abs(d.y)<eps && abs(d.z)<eps;
// Groups entries in "arr" into groups of equal values and returns index lists of those groups
function unique_groups(arr) = let(
sorted_index = sortidx(arr),
changes = [ for (i=[0:len(arr)])
if (i==0 || i==len(arr) || !approx(arr[sorted_index[i]],arr[sorted_index[i-1]])) i ]
)
[for(i=[0:len(changes)-2]) slice(sorted_index,changes[i],changes[i+1])];
function slow_full_faces(pts,faces) = len(faces)==0 ? [] :
let(
plane = plane(pts, faces[0][0],faces[0][1],faces[0][2]),
face_coplanar = [ for(i=[1:1:len(faces)-1] )
all(
[for(v=faces[i]) coplanar(plane, pts[v])]
) ],
same = [for(i=[0:1:len(faces)-2]) if (face_coplanar[i]) i+1],
different = [for(i=[0:1:len(faces)-2]) if (!face_coplanar[i]) i+1],
oneface = unique(flatten(select(faces,concat([0],same))))
)
concat([[oneface,plane[0]]],full_faces(pts, select(faces,different)));
///////////////////////////////////////////////////////////////////////////////////////////////////
//
// Examples start here: not part of library
$fn=96;
//regular_polyhedron("pentagonal icositetrahedron", or=5);
//%sphere(r=5-.2, $fn=100);
//regular_polyhedron(index=0, type="catalan");
//regular_polyhedron("disdyakis triacontahedron");
/*
// Test that rounded shapes are the same size as unrounded
shape = "dodecahedron";
//shape = "cube";
top_half(cp=[0,0,.2])
difference(){
regular_polyhedron(shape);
regular_polyhedron(shape, rounding=0.2,side=1.0000);
}
*/
/*
// Test for selection by face type
// Solids that have 8 or 10 edge faces
for(i=[0:3])
right(3*i)
regular_polyhedron(hasfaces=[8,10], index=i, mr=1);
// Solids that include a 4 edged face
for(i=[0:11])
right(3*i)
back(3)
regular_polyhedron(hasfaces=4, index=i, mr=1);
// Solids with only 4 edged faces
for(i=[0:4])
right(3*i)
back(6)
regular_polyhedron(facetype=[4], index=i, mr=1);
// Solids that have pentagons and hexagons for faces.
// (Note both shapes must appear.)
for(i=[0:0])
right(3*i)
back(9)
regular_polyhedron(facetype=[5,6], index=i, mr=1);
*/
if (false) {
// Display of all solids with insphere, midsphere and circumsphere
for(i=[0:len(library)-1]) {
place_copies([[3*i,0,0]]) // Plain polyhedron
regular_polyhedron(index=i, mr=1,facedown=true);
place_copies([[3*i,3.5,0]]){ // Inner radius means sphere touches faces of the polyhedron
sphere(r=1.005); // Sphere is slightly oversized so you can see it poking out from each face
%regular_polyhedron(index=i, ir=1,facedown=true);
}
place_copies([[3*i,7,0]]){ // Mid radius means the sphere touches the center of each edge
sphere(r=1);
%regular_polyhedron(index=i, mr=1,facedown=true);
}
place_copies([[3*i,11,0]]){ // outer radius means points of the polyhedron are on the sphere
%sphere(r=.99); // Slightly undersized sphere means the points poke out a bit
regular_polyhedron(index=i, or=1,facedown=true);
}
}
}
// Children demonstration
if (true) {
h_dod = sqrt((5+2*sqrt(5))/5);
h_icos = sqrt(2/3+phi);
h_dod2 = sqrt(5/3-phi);
xdistribute(spacing=2){
regular_polyhedron(faces=12,index=2,repeat=true) color("red") sphere(r=.1,$fn=32);
scale([1,1,1]/3)
regular_polyhedron("tetrahedron",side=3,repeat=true)
up($faceindex/8/2) cube([$faceindex/8,$faceindex/8,$faceindex/8],center=true);
regular_polyhedron("octahedron",side=1,repeat=true,facedown=true)
linear_extrude(height=.1)offset(-.1)polygon($face);
regular_polyhedron("octahedron",repeat=false){
cube([.3,.3,.3]/2,center=true);
sphere(r=.3);
cylinder(r=.1, h=1,center=false);
}
regular_polyhedron("octahedron",repeat=true){
cube([.3,.3,.3]/2,center=true);
sphere(r=.3);
cylinder(r=.1, h=1,center=false);
}
regular_polyhedron(facedown=true, name="tetrahedron",
,side=1,align=[0,0,1],rotate_children=false
){cylinder(r=.1, h=.5);}
// Kepler-Poinsot Solids
// Stellated dodecahedron
scale([1,1,1]/3)
regular_polyhedron("dodecahedron",side=1,repeat=true)
fastpointhull(concat(path3d($face), [[0,0,h_dod]]));
// Stellated icosahedron
scale([1,1,1]/3)
regular_polyhedron("icosahedron",side=1,repeat=true)
fastpointhull(concat(path3d($face), [[0,0,h_icos]]));
// Great dodecahedron
difference(){
regular_polyhedron("icosahedron", side=1);
regular_polyhedron("icosahedron", side=1, repeat=true, draw=false)
fastpointhull(concat(
[for(pt=$face) [pt.x,pt.y,.01]],
path3d($face),
[[0,0,-h_dod2]]));
}
// Great icosahedron
scale([1,1,1]/3)
regular_polyhedron("dodecahedron", side=1, repeat=true)
linear_extrude(scale=0, height=h_dod) makestar($face);
// What is the rule for $face. When I tried to compute with it I got errors.
// Is it not allowed on the RHS of a variable assignment?
//
// I was thinking about making a stellated_polyhedron function, but it will be
// a lot of copying of the interface.
//
// Maybe instead a stellate parameter?
scale([1,1,1]/20)
difference(){
regular_polyhedron("tetrahedron", side=20,facedown=true);
regular_polyhedron("tetrahedron", side=20,facedown=true,draw=false)
down(.3)linear_extrude(height=1)text(str($faceindex),halign="center",valign="center");
}
scale([1,1,1]/3)union(){
// The canonical coordinates may have useful relationships,
%regular_polyhedron("dodecahedron",ir=1,facedown=false);
regular_polyhedron("icosahedron",or=1,facedown=false);
}
scale([1,1,1]/3)
union(){
%regular_polyhedron("dodecahedron", facedown=true, rotate_children=false);
regular_polyhedron("dodecahedron", facedown=true, rotate_children=false,draw=false)
cylinder(h=1, r=.1);
}
scale([1,1,1]/3)
regular_polyhedron("dodecahedron", facedown=true, rotate_children=false, repeat=true, draw=false)
if($faceindex%2)
fastpointhull(concat([$center], $face));
}
}
module makestar(pts) {
polygon([for(i=[0:len(pts)-1]) let(p0=select(pts,i),
p1=select(pts,i+1),
factor=sqrt(7-4*phi)/2,
center=(p0+p1)/2,
v=factor*(p1-p0))
each [p0, [v.y+center.x, -v.x+center.y]]]);
}
module fastpointhull(points){
points = simplify3d_path(points); // colinear points are not on the hull and generate a warning message
extra = len(points)%3;
list = concat(
[[for(i=[0:extra+2])i]],
[for(i=[extra+3:3:len(points)-3])[i,i+1,i+2]]);
hull() polyhedron(points, faces=list);
}
/// tetrahedron speed test
//place_copies([for(i=[0:20])[0,0,i]])
//regular_polyhedron(index=30, align=[1,1,1]);
//for(i=[0:40]) up(i*10)
// regular_polyhedron(index=30, side=i/2);
// TODO
//
// Use volume info?
// Add a stellate function
// Support choosing a face number down
// Support multiple inspheres/outspheres when appropriate?
// trapezohedron?
//
// face order for children?
// orient faces so an edge is parallel to the x-axis
I fear I may end up complicating your life some.
What I’m working on now uses orient_and_align()
to allow attach()
ing objects together, similarly to the Relativity library. If you pass a list of connectors [“name” XYZPOS DIRVEC ROTANG] in via the alignments
argument, you can make it possible to attach objects to a face by name. (IE: “face3”)
I’m still debating whether to check my code in now, or if I should start making the BOSL2 cleanup including these.
Since we pretty much have to use include
instead of use
, can you put a prefix before the constants you declare?
I think at this point we should focus on providing a usable polyhedron library file rather than an absolutely complete one. I don’t really have an answer for many of your questions. I’m kinda trusting you to figure out something usable with this. I’ll focus on getting Linde’s Hull added.
I'll prefix my constants with "_" the same as internal functions. Is that good enough?
I suggest naming Linde's hull function as hull_faces() since that's really what it does. Should we combine the hull_faces function and my stuff into one polyhedra.scad file? I also think it is worth supplying the hull_points module that uses polyhedron with bogus faces.
I believe that my polyhedra code is in a usable state at present. I suppose the main question would be whether to keep "rtype", since that's an interface issue and we wouldn't want to delete it later after release. I'm not sure about the implications of your new attachment stuff.
A _ prefix is probably fine, though constants should be all caps for consistency. I can see having hull_faces() in the polyhedra libfile, if you want to take on cleaning it up for using BOSL functions. I have no strong feelings about rtype. Feel free to axe it.
The new attachment code lets you do things like:
cube(100, align=“bottom”) {
attach(“left”, “bottom”) cylinder(d=40, h=100);
attach(“top-front-right”) sphere(d=50);
}
You’re already using children with polyhedra, though, so you may not be able to integrate with attach().
So does "top-front-right" mean the sphere is centered on the corner of the cube? How do you specify rotation of the attached object (for situations where it lacks rotational symmetry)?
The situation where I felt like attachment would really help was when I had made a sub-part and had a vector for maybe the back corner and an up direction, and then I want to stick it on my complex model at an edge, or maybe 2 units in from the edge oriented correctly. Figuring out the right sequence of rotations to do this always seems annoying. It could also be useful for differencing roundover masks. It might be the case that just extending the rot() from and to operations so that they also respect a rotation solves this problem (though I'm not sure about the translation part).
It's a little unclear what sort of "attach" commands would make sense for the polyhedra. I could do something like attach(3) to put something on face 3. I'm not sure it's worth the effort to do something like this.
Do you think any of the functions that are in hull() should be added to math.scad for general use? Those functions are area2d (area of a triangle), colinear (2d, by checking triangle area), and distance to a plane, coplanar, and then max_index and min_index (which return the index of the extreme value in a list).
I realized that the hull() function will also do hull in 2d and 1d, which makes me question hull_faces() as a good name for it.
attach()
and align
can be one of the standard connectors, such as "center", "left", "back-top", or "right-front-bottom". The rotations for each one are consistent with the ORIENT_* orientations, so up is generally towards Z+, except on the top, center, and bottom, where up is towards Y+. For non-standard connectors, the rotation is specified in the connector info passed to orient_and_align(), along with attachment point and directional vector.
If you don't want that orientation rotation on attachment, you can attach with norot=true
and your attached part will be vertically oriented in its original rotation. If you want the vector of the attachment, but not the rotation, you can tweak it with zrot()
, since you'll be starting with a known rotation.
Here's a prismoid with the standard connectors shown:
Just for fun, here's the code to generate a fractal tree.
include <BOSL/constants.scad>
include <BOSL/transforms.scad>
include <BOSL/primitives.scad>
include <BOSL/beziers.scad>
module leaf(s) {
path = [
[0,0], [1.5,-1],
[2,1], [0,3], [-2,1],
[-1.5,-1], [0,0]
];
xrot(90)
linear_extrude_bezier(
scale_points(path, [s,s]/2),
height=0.02
);
}
module branches(minsize){
if($parent_size2.x>minsize) {
attach("top")
zrot(gaussian_rand(90,10))
zring(n=floor(log_rand(2,5,4)))
zrot(gaussian_rand(0,5))
yrot(gaussian_rand(30,5))
let(
sc = gaussian_rand(0.7,0.05),
s1 = $parent_size.z*sc,
s2 = $parent_size2.x
)
cylinder(d1=s2, d2=s2*sc, l=s1)
branches(minsize);
} else {
recolor("springgreen")
attach("top") zrot(90)
leaf(gaussian_rand(100,5));
}
}
recolor("lightgray") cylinder(d1=300, d2=250, l=1500) branches(5);
Oh, and yes, "top-front-left" means the upper left-front corner. "bottom-back" means the midpoint of the rear bottom edge. "right" means the center of the right face.
gaussian_rand()
and log_rand()
are two new math.scad functions to generate single random numbers with gaussian/normal and logarithmic distributions, respectively.
More fun -- differencing and intersection can be handled with tags:
diff("body pole", "hole")
staggered_sphere(d=100, $tags="body") {
zcyl(d=55, h=100, $tags="pole"); // attach() not needed if aligning center to center
tags("hole") {
xcyl(d=55, h=101);
ycyl(d=55, h=101);
}
zcyl(d=15, h=140, $tags="axle");
}
Yeah, there's several functions in Linde's hull that would be good to add to math.scad.
Does it make sense for you to integrate the functions you want to add and then once you're done, I'll adjust Linde's function to call the library functions, sanitize the private functions and merge it into polyhedra.scad?
Do you think that I should name the top level variables in polyhedra.scad with the expectation that users might actually refer to them? Or treat them as private variables that are unfortunately exposed due to the limitations of OpenSCAD? If they are private, I don't need to follow any convention other than prefixing with '_' or otherwise obscuring them. If they are public it's a different matter. I was thinking of writing a regular_polyhedron_info function that would pull data out of the library data structure about polyhedra so that there's no need to (intentionally) expose the library data structure to users.
Does the documentation on the wiki get autogenerated from the comments above each function? I'm also wondering about writing up the documentation for regular_polyhedron, which is going to be involved, with ~35 examples.
I can take a quick run at integrating the library-worthy functions first, sure.
If you make one or more info functions, then you don't need to expose the constants. I do worry that some of the names seem very likely to get collisions as they are, though. Especially phi
, library
, class
, edgelen
, volumes
, vertices
, and facecount
. Your code doesn't look like it actually needs to have most of those defined outside of the regular_polyhedra()
module, currently.
Only phi
and library
seem to need to be outside regular_polyhedra()
. The constant phi
looks like a standard constant, so should probably be moved to constants.scad and renamed PHI
. The library
array should be renamed something like POLYHEDRA_LIBRARY
, and if there's more temporary values than phi
needed to generate it, you can generate the array in a function, and assign it to the constant from there.
Yes, the wiki docs are generated from the comments above the modules/functions by the scripts/docs_gen.py script. Not all comments are added to the wiki. Just those comment blocks starting with certain keywords:
// LibFile: NAME
, or // Section: NAME
, or // Module: NAME
or // Function: NAME
, or // Constant: NAME
.
Module and Function docs blocks have a specific format. Indentation is important, as it denotes the end of sub-block:
// Module: foo()
// Status: DEPRECATED, use BLAH instead.
// Usage: Usage Type
// foo(foo, bar, [qux]);
// foo(bar, baz, [qux]);
// Usage: Usage Type 2
// foo(foo, flee, flie, [qux])
// Description: Short description.
// Description:
// A longer, multi-line description.
// All description blocks are added together.
// You can use markdown notation as well.
// You can end multi-line blocks by un-indenting the
// next line, or by using a blank comment line like this:
//
// Arguments:
// foo = This is the description of the foo argument. All on one line.
// bar = This is the description of the bar argument. All on one line.
// baz = This is the description of the baz argument. All on one line.
// qux = This is the description of the qux argument. All on one line.
// flee = This is the description of the flee argument. All on one line.
// flie = This is the description of the flie argument. All on one line.
// Side Effects:
// `$floo` gets set to the floo value.
// Examples: Each line below gets its own example block and image.
// foo(foo="a", bar="b");
// foo(foo="b", baz="c");
// Example: Multi-line example.
// lst = [
// "multi-line examples",
// "are shown in one block",
// "with a single image.",
// ];
// foo(lst, 23, "blah");
// Example(2D): Example to show as 2D top-down rendering.
// foo(foo="b", baz="c", qux=true);
// Example(Spin): Example using rotating animation to show all faces.
// foo(foo="b", baz="c", qux="full");
// Example(FlatSpin): Example using rotating animation to view top, front, back, left, and right faces.
// foo(foo="b", baz="c", qux="full2");
Functions and constants won't generate images for examples unless you force it with a (2D)
, or (3D)
tag on the Example marker. The full set of example tags are 2D
, 3D
, Spin
, FlatSpin
, FR
, Small
, Med
, Big
. The FR
tag is to force full rendering from OpenScad, if the preview mode is insufficient. The size tags can generate larger images, if you need to show more detail. You can combine tags like this: // Example(FlatSpinBig):
I definitely wasn't thinking it is ok to expose names like "vertices" and so on. I was assuming that we could use "use" and they would all be hidden. But since we need to allow "include" then I can just define them inside the function. If we are going to document the "library" data structure for user use I think I would call it POLYHEDRA. If we're going to have it "private" I was thinking I might name it _polyhedra_lib or something like that. I am inclined to keep it "private" and not document it, but supply access through a function (which I think will be better for a user.) I agree that PHI is a reasonable constant to define globally, though I don't know that I want to include the constants file within my file since it pulls in a bunch of stuff the user didn't ask for. I can call them _phi and _tribonacci. Those are the only two parameters I use. I could define the polyhedra library in a function. You think that's better? It trades off the clutter of _phi and _tribonnacci for the clutter of a function name, and the function name would clutter the name space even if "use" is used.
I'll try to get my documentation into compliance. Can I have multiple paragraphs within a section? Are the block names like "Side effects" arbitrary, or they have to match a list of allowed headings? So I can write something like
Examples(3D):
regular_polyhedron("tetrahedron");
regular_polyhedron("cube");
etc
and run through all of them and it will make an image for each one?
The (3D) is the default for Example/Examples for a module, so you don’t need it. Yes, each line of an Examples block gets its own example image and code block in the docs.
As per suggestion from @adrianVmariano, We should have a regular solids libfile. At the least for the platonic solids, and typical dice polyhedra: 4, 6, 8, 10, 12, 20, 30 sides. Maybe some of the Archimedean solids.