geodesic_sphere.scad
1 // from https://www.thingiverse.com/thing:1484333 2 // public domain license 3 // same syntax and semantics as built-in sphere, so should be a drop-in replacement 4 // it's a bit slow for large numbers of facets 5 module geodesic_sphere(r=-1, d=-1) { 6 // if neither parameter specified, radius is taken to be 1 7 rad = r > 0 ? r : d > 0 ? d/2 : 1; 8 9 pentside_pr = 2*sin(36); // side length compared to radius of a pentagon 10 pentheight_pr = sqrt(pentside_pr*pentside_pr - 1); 11 // from center of sphere, icosahedron edge subtends this angle 12 edge_subtend = 2*atan(pentheight_pr); 13 14 // vertical rotation by 72 degrees 15 c72 = cos(72); 16 s72 = sin(72); 17 function zrot(pt) = [ c72*pt[0]-s72*pt[1], s72*pt[0]+c72*pt[1], pt[2] ]; 18 19 // rotation from north to vertex along positive x 20 ces = cos(edge_subtend); 21 ses = sin(edge_subtend); 22 function yrot(pt) = [ ces*pt[0] + ses*pt[2], pt[1], ces*pt[2]-ses*pt[0] ]; 23 24 // 12 icosahedron vertices generated from north, south, yrot and zrot 25 ic1 = [ 0, 0, 1 ]; // north 26 ic2 = yrot(ic1); // north and +x 27 ic3 = zrot(ic2); // north and +x and +y 28 ic4 = zrot(ic3); // north and -x and +y 29 ic5 = zrot(ic4); // north and -x and -y 30 ic6 = zrot(ic5); // north and +x and -y 31 ic12 = [ 0, 0, -1]; // south 32 ic10 = yrot(ic12); // south and -x 33 ic11 = zrot(ic10); // south and -x and -y 34 ic7 = zrot(ic11); // south and +x and -y 35 ic8 = zrot(ic7); // south and +x and +y 36 ic9 = zrot(ic8); // south and -x and +y 37 38 // start with icosahedron, icos[0] is vertices and icos[1] is faces 39 icos = [ [ic1, ic2, ic3, ic4, ic5, ic6, ic7, ic8, ic9, ic10, ic11, ic12 ], 40 [ [0, 2, 1], [0, 3, 2], [0, 4, 3], [0, 5, 4], [0, 1, 5], 41 [1, 2, 7], [2, 3, 8], [3, 4, 9], [4, 5, 10], [5, 1, 6], 42 [7, 6, 1], [8, 7, 2], [9, 8, 3], [10, 9, 4], [6, 10, 5], 43 [6, 7, 11], [7, 8, 11], [8, 9, 11], [9, 10, 11], [10, 6, 11]]]; 44 45 // now for polyhedron subdivision functions 46 47 // given two 3D points on the unit sphere, find the half-way point on the great circle 48 // (euclidean midpoint renormalized to be 1 unit away from origin) 49 function midpt(p1, p2) = 50 let (midx = (p1[0] + p2[0])/2, midy = (p1[1] + p2[1])/2, midz = (p1[2] + p2[2])/2) 51 let (midlen = sqrt(midx*midx + midy*midy + midz*midz)) 52 [ midx/midlen, midy/midlen, midz/midlen ]; 53 54 // given a "struct" where pf[0] is vertices and pf[1] is faces, subdivide all faces into 55 // 4 faces by dividing each edge in half along a great circle (midpt function) 56 // and returns a struct of the same format, i.e. pf[0] is a (larger) list of vertices and 57 // pf[1] is a larger list of faces. 58 function subdivpf(pf) = 59 let (p=pf[0], faces=pf[1]) 60 [ // for each face, barf out six points 61 [ for (f=faces) 62 let (p0 = p[f[0]], p1 = p[f[1]], p2=p[f[2]]) 63 // "identity" for-loop saves having to flatten 64 for (outp=[ p0, p1, p2, midpt(p0, p1), midpt(p1, p2), midpt(p0, p2) ]) outp 65 ], 66 // now, again for each face, spit out four faces that connect those six points 67 [ for (i=[0:len(faces)-1]) 68 let (base = 6*i) // points generated in multiples of 6 69 for (outf = 70 [[ base, base+3, base+5], 71 [base+3, base+1, base+4], 72 [base+5, base+4, base+2], 73 [base+3, base+4, base+5]]) outf // "identity" for-loop saves having to flatten 74 ] 75 ]; 76 77 // recursive wrapper for subdivpf that subdivides "levels" times 78 function multi_subdiv_pf(pf, levels) = 79 levels == 0 ? pf : 80 multi_subdiv_pf(subdivpf(pf), levels-1); 81 82 // subdivision level based on $fa: 83 // level 0 has edge angle of edge_subtend so subdivision factor should be edge_subtend/$fa 84 // must round up to next power of 2. 85 // Take log base 2 of angle ratio and round up to next integer 86 ang_levels = ceil(log(edge_subtend/$fa)/log(2)); 87 88 // subdivision level based on $fs: 89 // icosahedron edge length is rad*2*tan(edge_subtend/2) 90 // actually a chord and not circumference but let's say it's close enough 91 // subdivision factor should be rad*2*tan(edge_subtend/2)/$fs 92 side_levels = ceil(log(rad*2*tan(edge_subtend/2)/$fs)/log(2)); 93 94 // subdivision level based on $fn: (fragments around circumference, not total facets) 95 // icosahedron circumference around equator is about 5 (level 1 is exactly 10) 96 // ratio of requested to equatorial segments is $fn/5 97 // level of subdivison is log base 2 of $fn/5 98 // round up to the next whole level so we get at least $fn 99 facet_levels = ceil(log($fn/5)/log(2)); 100 101 // $fn takes precedence, otherwise facet_levels is NaN (-inf) but it's ok 102 // because it falls back to $fa or $fs, whichever translates to fewer levels 103 levels = $fn ? facet_levels : min(ang_levels, side_levels); 104 105 // subdivide icosahedron by these levels 106 subdiv_icos = multi_subdiv_pf(icos, levels); 107 108 scale(rad) 109 polyhedron(points=subdiv_icos[0], faces=subdiv_icos[1]); 110 }