diff --git a/linalg.scad b/linalg.scad index a8a47b0..03c5bea 100644 --- a/linalg.scad +++ b/linalg.scad @@ -416,14 +416,19 @@ function block_matrix(M) = // Function: linear_solve() // Usage: -// solv = linear_solve(A,b) +// solv = linear_solve(A,b,[pivot]) // Description: // Solves the linear system Ax=b. If `A` is square and non-singular the unique solution is returned. If `A` is overdetermined // the least squares solution is returned. If `A` is underdetermined, the minimal norm solution is returned. // If `A` is rank deficient or singular then linear_solve returns `[]`. If `b` is a matrix that is compatible with `A` // then the problem is solved for the matrix valued right hand side and a matrix is returned. Note that if you // want to solve Ax=b1 and Ax=b2 that you need to form the matrix `transpose([b1,b2])` for the right hand side and then -// transpose the returned value. +// transpose the returned value. The solution is computed using QR factorization. If `pivot` is set to true (the default) then +// pivoting is used in the QR factorization, which is slower but expected to be more accurate. +// Usage: +// A = Matrix describing the linear system, which need not be square +// b = right hand side for linear system, which can be a matrix to solve several cases simultaneously. Must be consistent with A. +// pivot = if true use pivoting when computing the QR factorization. Default: true function linear_solve(A,b,pivot=true) = assert(is_matrix(A), "Input should be a matrix.") let( @@ -444,6 +449,31 @@ function linear_solve(A,b,pivot=true) = m=0 && size<=4); Mmin = [0.0696, 0.0900, 0.1110, 0.1315, 0.1895][size]; Mmax = [0.0710, 0.0910, 0.1126, 0.1330, 0.1910][size]; @@ -351,14 +356,14 @@ module robertson_mask(size, extra=1) { Fmin = [0.032, 0.057, 0.065, 0.085, 0.090][size]; Fmax = [0.038, 0.065, 0.075, 0.095, 0.100][size]; F = (Fmin + Fmax) / 2 * INCH; - ang = 4; h = T + extra; + Mslop=M+2*$slop; down(T) { intersection(){ - Mtop = M + 2*adj_ang_to_opp(F+extra,ang); - Mbot = M - 2*adj_ang_to_opp(T-F,ang); + Mtop = Mslop + 2*adj_ang_to_opp(F+extra,ang); + Mbot = Mslop - 2*adj_ang_to_opp(T-F,ang); prismoid([Mbot,Mbot],[Mtop,Mtop],h=h,anchor=BOT); - cyl(d1=0, d2=M/(T-F)*sqrt(2)*h, h=h, anchor=BOT); + cyl(d1=0, d2=Mslop/(T-F)*sqrt(2)*h, h=h, anchor=BOT); } } } diff --git a/shapes3d.scad b/shapes3d.scad index 4c64dde..24cee6e 100644 --- a/shapes3d.scad +++ b/shapes3d.scad @@ -1654,8 +1654,8 @@ function sphere(r, d, circum=false, style="orig", anchor=CENTER, spin=0, orient= // - `style="orig"` constructs a sphere the same way that the OpenSCAD `sphere()` built-in does. // - `style="aligned"` constructs a sphere where, if `$fn` is a multiple of 4, it has vertices at all axis maxima and minima. ie: its bounding box is exactly the sphere diameter in length on all three axes. This is the default. // - `style="stagger"` forms a sphere where all faces are triangular, but the top and bottom poles have thinner triangles. -// - `style="octa"` forms a sphere by subdividing an octahedron (8-sided platonic solid). This makes more uniform faces over the entirety of the sphere, and guarantees the bounding box is the sphere diameter in size on all axes. The effective `$fn` value is quantized to a multiple of 4, though. This is used in constructing rounded corners for various other shapes. -// - `style="icosa"` forms a sphere by subdividing an icosahedron (20-sided platonic solid). This makes even more uniform faces over the entirety of the sphere. The effective `$fn` value is quantized to a multiple of 5, though. +// - `style="octa"` forms a sphere by subdividing an octahedron (8-sided platonic solid). This makes more uniform faces over the entirety of the sphere, and guarantees the bounding box is the sphere diameter in size on all axes. The effective `$fn` value is quantized to a multiple of 4. This is used in constructing rounded corners for various other shapes. +// - `style="icosa"` forms a sphere by subdividing an icosahedron (20-sided platonic solid). This makes even more uniform faces over the entirety of the sphere. The effective `$fn` value is quantized to a multiple of 5. // Arguments: // r = Radius of the spheroid. // style = The style of the spheroid's construction. One of "orig", "aligned", "stagger", "octa", or "icosa". Default: "aligned" @@ -1717,6 +1717,12 @@ module spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, orie } +// p is a list of 3 points defining a triangle in any dimension. N is the number of extra points +// to add, so output triangle has N+2 points on each side. +function _subsample_triangle(p,N) = + [for(i=[0:N+1]) [for (j=[0:N+1-i]) unit(lerp(p[0],p[1],i/(N+1)) + (p[2]-p[0])*j/(N+1))]]; + + function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, orient=UP) = let( r = get_radius(r=r, d=d, dflt=1), @@ -1725,7 +1731,36 @@ function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, or octa_steps = round(max(4,hsides)/4), icosa_steps = round(max(5,hsides)/5), rr = circum? (r / cos(90/vsides) / cos(180/hsides)) : r, - stagger = style=="stagger", + stagger = style=="stagger" + ) + style=="icosa" ? // subdivide faces of an icosahedron and project them onto a sphere + let( + N = icosa_steps-1, + // construct an icosahedron + icovert=[ for(i=[-1,1], j=[-1,1]) each [[0,i,j*PHI], [i,j*PHI,0], [j*PHI,0,i]]], + icoface = hull(icovert), + // Subsample face 0 of the icosahedron + face0 = select(icovert,icoface[0]), + sampled = rr * _subsample_triangle(face0,N), + dir0 = mean(face0), + point0 = face0[0]-dir0, + // Make a rotated copy of the subsampled triangle on each icosahedral face + tri_list = [sampled, + for(i=[1:1:len(icoface)-1]) + let(face = select(icovert,icoface[i])) + apply(frame_map(z=mean(face),x=face[0]-mean(face)) + *frame_map(z=dir0,x=point0,reverse=true), + sampled)], + // faces for the first triangle group + faces = vnf_tri_array(tri_list[0])[1], + size = repeat((N+2)*(N+3)/2,3), + // Expand to full face list + fullfaces = [for(i=idx(tri_list)) each [for(f=faces) f+i*size]], + fullvert = flatten(flatten(tri_list)) // eliminate triangle structure + ) + [reorient(anchor,spin,orient, r=r, p=fullvert), fullfaces] + : + let( verts = style=="orig"? [ for (i=[0:1:vsides-1]) let(phi = (i+0.5)*180/(vsides)) for (j=[0:1:hsides-1]) let(theta = j*360/hsides) @@ -1746,32 +1781,6 @@ function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, or ) [ for (i=idx(meridians), j=[0:1:meridians[i]-1]) spherical_to_xyz(rr, j*360/meridians[i], i*180/(len(meridians)-1)) - ] : style=="icosa"? [ - for (tb=[0,1], j=[0,2], i = [0:1:4]) let( - theta0 = i*360/5, - theta1 = (i-0.5)*360/5, - theta2 = (i+0.5)*360/5, - phi0 = 180/3 * j, - phi1 = 180/3, - v0 = spherical_to_xyz(1,theta0,phi0), - v1 = spherical_to_xyz(1,theta1,phi1), - v2 = spherical_to_xyz(1,theta2,phi1), - ax0 = vector_axis(v0, v1), - ang0 = vector_angle(v0, v1), - ax1 = vector_axis(v0, v2), - ang1 = vector_angle(v0, v2) - ) - for (k = [0:1:icosa_steps]) let( - u = k/icosa_steps, - vv0 = rot(ang0*u, ax0, p=v0), - vv1 = rot(ang1*u, ax1, p=v0), - ax2 = vector_axis(vv0, vv1), - ang2 = vector_angle(vv0, vv1) - ) - for (l = [0:1:k]) let( - v = k? l/k : 0, - pt = rot(ang2*v, v=ax2, p=vv0) * rr * (tb? -1 : 1) - ) pt ] : assert(in_list(style,["orig","aligned","stagger","octa","icosa"])), lv = len(verts), faces = style=="orig"? [ @@ -1832,25 +1841,6 @@ function spheroid(r, style="aligned", d, circum=false, anchor=CENTER, spin=0, or [p5, p7, p8], if (k0) [v1-1,v1,v2], - [v1,v3,v2], - ], - faces2 = (tb+j)%2? [for (f=faces) reverse(f)] : faces - ) each faces2 ] : [] ) [reorient(anchor,spin,orient, r=r, p=verts), faces]; diff --git a/tests/test_shapes3d.scad b/tests/test_shapes3d.scad index f7352c5..a9d8a40 100644 --- a/tests/test_shapes3d.scad +++ b/tests/test_shapes3d.scad @@ -32,7 +32,7 @@ module test_sphere() { assert_approx(sphere(r=40,style="aligned"), [[[0,0,40],[34.6410161514,0,20],[17.3205080757,30,20],[-17.3205080757,30,20],[-34.6410161514,0,20],[-17.3205080757,-30,20],[17.3205080757,-30,20],[34.6410161514,0,-20],[17.3205080757,30,-20],[-17.3205080757,30,-20],[-34.6410161514,0,-20],[-17.3205080757,-30,-20],[17.3205080757,-30,-20],[0,0,-40]],[[1,0,2],[13,7,8],[2,0,3],[13,8,9],[3,0,4],[13,9,10],[4,0,5],[13,10,11],[5,0,6],[13,11,12],[6,0,1],[13,12,7],[1,2,8],[1,8,7],[2,3,9],[2,9,8],[3,4,10],[3,10,9],[4,5,11],[4,11,10],[5,6,12],[5,12,11],[6,1,7],[6,7,12]]]); assert_approx(sphere(r=40,style="stagger"), [[[0,0,40],[30,17.3205080757,20],[0,34.6410161514,20],[-30,17.3205080757,20],[-30,-17.3205080757,20],[0,-34.6410161514,20],[30,-17.3205080757,20],[34.6410161514,0,-20],[17.3205080757,30,-20],[-17.3205080757,30,-20],[-34.6410161514,0,-20],[-17.3205080757,-30,-20],[17.3205080757,-30,-20],[0,0,-40]],[[1,0,2],[13,7,8],[2,0,3],[13,8,9],[3,0,4],[13,9,10],[4,0,5],[13,10,11],[5,0,6],[13,11,12],[6,0,1],[13,12,7],[1,2,8],[1,8,7],[2,3,9],[2,9,8],[3,4,10],[3,10,9],[4,5,11],[4,11,10],[5,6,12],[5,12,11],[6,1,7],[6,7,12]]]); assert_approx(sphere(r=40,style="octa"), [[[0,0,40],[28.2842712475,0,28.2842712475],[0,28.2842712475,28.2842712475],[-28.2842712475,0,28.2842712475],[0,-28.2842712475,28.2842712475],[40,0,0],[28.2842712475,28.2842712475,0],[0,40,0],[-28.2842712475,28.2842712475,0],[-40,0,0],[-28.2842712475,-28.2842712475,0],[0,-40,0],[28.2842712475,-28.2842712475,0],[28.2842712475,0,-28.2842712475],[0,28.2842712475,-28.2842712475],[-28.2842712475,0,-28.2842712475],[0,-28.2842712475,-28.2842712475],[0,0,-40]],[[0,2,1],[0,3,2],[0,4,3],[0,1,4],[17,15,16],[17,14,15],[17,13,14],[17,16,13],[1,6,5],[1,2,6],[13,5,6],[13,6,14],[2,7,6],[14,6,7],[2,8,7],[2,3,8],[14,7,8],[14,8,15],[3,9,8],[15,8,9],[3,10,9],[3,4,10],[15,9,10],[15,10,16],[4,11,10],[16,10,11],[4,12,11],[4,1,12],[16,11,12],[16,12,13],[1,5,12],[13,12,5]]]); - assert_approx(sphere(r=40,style="icosa"), [[[0,0,40],[28.0251707689,-20.3614784182,20],[28.0251707689,20.3614784182,20],[0,0,40],[28.0251707689,20.3614784182,20],[-10.7046626932,32.9455641419,20],[0,0,40],[-10.7046626932,32.9455641419,20],[-34.6410161514,6.66133814775e-15,20],[0,0,40],[-34.6410161514,0,20],[-10.7046626932,-32.9455641419,20],[0,0,40],[-10.7046626932,-32.9455641419,20],[28.0251707689,-20.3614784182,20],[34.6410161514,0,-20],[28.0251707689,-20.3614784182,20],[28.0251707689,20.3614784182,20],[10.7046626932,32.9455641419,-20],[28.0251707689,20.3614784182,20],[-10.7046626932,32.9455641419,20],[-28.0251707689,20.3614784182,-20],[-10.7046626932,32.9455641419,20],[-34.6410161514,-4.4408920985e-15,20],[-28.0251707689,-20.3614784182,-20],[-34.6410161514,1.11022302463e-15,20],[-10.7046626932,-32.9455641419,20],[10.7046626932,-32.9455641419,-20],[-10.7046626932,-32.9455641419,20],[28.0251707689,-20.3614784182,20],[0,0,-40],[-28.0251707689,20.3614784182,-20],[-28.0251707689,-20.3614784182,-20],[0,0,-40],[-28.0251707689,-20.3614784182,-20],[10.7046626932,-32.9455641419,-20],[0,0,-40],[10.7046626932,-32.9455641419,-20],[34.6410161514,-6.66133814775e-15,-20],[0,0,-40],[34.6410161514,0,-20],[10.7046626932,32.9455641419,-20],[0,0,-40],[10.7046626932,32.9455641419,-20],[-28.0251707689,20.3614784182,-20],[-34.6410161514,0,20],[-28.0251707689,20.3614784182,-20],[-28.0251707689,-20.3614784182,-20],[-10.7046626932,-32.9455641419,20],[-28.0251707689,-20.3614784182,-20],[10.7046626932,-32.9455641419,-20],[28.0251707689,-20.3614784182,20],[10.7046626932,-32.9455641419,-20],[34.6410161514,4.4408920985e-15,-20],[28.0251707689,20.3614784182,20],[34.6410161514,-1.11022302463e-15,-20],[10.7046626932,32.9455641419,-20],[-10.7046626932,32.9455641419,20],[10.7046626932,32.9455641419,-20],[-28.0251707689,20.3614784182,-20]],[[0,2,1],[3,5,4],[6,8,7],[9,11,10],[12,14,13],[16,17,15],[19,20,18],[22,23,21],[25,26,24],[28,29,27],[31,32,30],[34,35,33],[37,38,36],[40,41,39],[43,44,42],[45,47,46],[48,50,49],[51,53,52],[54,56,55],[57,59,58]]]); + assert_approx(sphere(r=40,style="icosa"),[[[0,21.0292444848,34.0260323341],[34.0260323341,0,21.0292444848],[21.0292444848,34.0260323341,0],[21.0292444848,34.0260323341,3.5527136788e-15],[34.0260323341,-3.5527136788e-15,21.0292444848],[34.0260323341,-1.7763568394e-15,-21.0292444848],[34.0260323341,-3.5527136788e-15,-21.0292444848],[34.0260323341,-8.881784197e-15,21.0292444848],[21.0292444848,-34.0260323341,0],[21.0292444848,-34.0260323341,5.3290705182e-15],[34.0260323341,-5.3290705182e-15,21.0292444848],[5.3290705182e-15,-21.0292444848,34.0260323341],[3.5527136788e-15,-21.0292444848,34.0260323341],[34.0260323341,3.5527136788e-15,21.0292444848],[3.5527136788e-15,21.0292444848,34.0260323341],[-21.0292444848,34.0260323341,-3.5527136788e-15],[21.0292444848,34.0260323341,-8.881784197e-15],[0,21.0292444848,-34.0260323341],[5.3290705182e-15,21.0292444848,-34.0260323341],[21.0292444848,34.0260323341,-5.3290705182e-15],[34.0260323341,5.3290705182e-15,-21.0292444848],[3.5527136788e-15,21.0292444848,34.0260323341],[21.0292444848,34.0260323341,-3.5527136788e-15],[-21.0292444848,34.0260323341,-1.7763568394e-15],[-34.0260323341,3.5527136788e-15,-21.0292444848],[-34.0260323341,8.881784197e-15,21.0292444848],[-21.0292444848,34.0260323341,0],[-21.0292444848,34.0260323341,5.3290705182e-15],[-34.0260323341,5.3290705182e-15,21.0292444848],[-5.3290705182e-15,21.0292444848,34.0260323341],[-3.5527136788e-15,21.0292444848,34.0260323341],[-34.0260323341,-3.5527136788e-15,21.0292444848],[-3.5527136788e-15,-21.0292444848,34.0260323341],[-5.39089693932e-15,-21.0292444848,34.0260323341],[-34.0260323341,-9.16854539271e-15,21.0292444848],[-21.0292444848,-34.0260323341,6.83383025096e-15],[-21.0292444848,-34.0260323341,3.5527136788e-15],[-34.0260323341,3.5527136788e-15,21.0292444848],[-34.0260323341,1.7763568394e-15,-21.0292444848],[34.0260323341,-5.39089693932e-15,-21.0292444848],[21.0292444848,-34.0260323341,-9.16854539271e-15],[6.83383025096e-15,-21.0292444848,-34.0260323341],[3.5527136788e-15,-21.0292444848,-34.0260323341],[21.0292444848,-34.0260323341,3.5527136788e-15],[-21.0292444848,-34.0260323341,1.7763568394e-15],[-21.0292444848,-34.0260323341,3.5527136788e-15],[21.0292444848,-34.0260323341,8.881784197e-15],[0,-21.0292444848,34.0260323341],[3.5527136788e-15,-21.0292444848,-34.0260323341],[8.881784197e-15,21.0292444848,-34.0260323341],[34.0260323341,0,-21.0292444848],[-34.0260323341,3.5527136788e-15,-21.0292444848],[3.5527136788e-15,21.0292444848,-34.0260323341],[1.7763568394e-15,-21.0292444848,-34.0260323341],[-21.0292444848,34.0260323341,-5.39089693932e-15],[-9.16854539271e-15,21.0292444848,-34.0260323341],[-34.0260323341,6.83383025096e-15,-21.0292444848],[-34.0260323341,-5.3290705182e-15,-21.0292444848],[-5.3290705182e-15,-21.0292444848,-34.0260323341],[-21.0292444848,-34.0260323341,-5.3290705182e-15]],[[0,1,2],[3,4,5],[6,7,8],[9,10,11],[12,13,14],[15,16,17],[18,19,20],[21,22,23],[24,25,26],[27,28,29],[30,31,32],[33,34,35],[36,37,38],[39,40,41],[42,43,44],[45,46,47],[48,49,50],[51,52,53],[54,55,56],[57,58,59]]]); } test_sphere(); @@ -70,7 +70,7 @@ module test_spheroid() { assert_approx(spheroid(r=50,style="aligned"),[[[0,0,50],[43.3012701892,0,25],[21.6506350946,37.5,25],[-21.6506350946,37.5,25],[-43.3012701892,0,25],[-21.6506350946,-37.5,25],[21.6506350946,-37.5,25],[43.3012701892,0,-25],[21.6506350946,37.5,-25],[-21.6506350946,37.5,-25],[-43.3012701892,0,-25],[-21.6506350946,-37.5,-25],[21.6506350946,-37.5,-25],[0,0,-50]],[[1,0,2],[13,7,8],[2,0,3],[13,8,9],[3,0,4],[13,9,10],[4,0,5],[13,10,11],[5,0,6],[13,11,12],[6,0,1],[13,12,7],[1,2,8],[1,8,7],[2,3,9],[2,9,8],[3,4,10],[3,10,9],[4,5,11],[4,11,10],[5,6,12],[5,12,11],[6,1,7],[6,7,12]]]); assert_approx(spheroid(r=50,style="stagger"),[[[0,0,50],[37.5,21.6506350946,25],[0,43.3012701892,25],[-37.5,21.6506350946,25],[-37.5,-21.6506350946,25],[0,-43.3012701892,25],[37.5,-21.6506350946,25],[43.3012701892,0,-25],[21.6506350946,37.5,-25],[-21.6506350946,37.5,-25],[-43.3012701892,0,-25],[-21.6506350946,-37.5,-25],[21.6506350946,-37.5,-25],[0,0,-50]],[[1,0,2],[13,7,8],[2,0,3],[13,8,9],[3,0,4],[13,9,10],[4,0,5],[13,10,11],[5,0,6],[13,11,12],[6,0,1],[13,12,7],[1,2,8],[1,8,7],[2,3,9],[2,9,8],[3,4,10],[3,10,9],[4,5,11],[4,11,10],[5,6,12],[5,12,11],[6,1,7],[6,7,12]]]); assert_approx(spheroid(r=50,style="octa"),[[[0,0,50],[35.3553390593,0,35.3553390593],[0,35.3553390593,35.3553390593],[-35.3553390593,0,35.3553390593],[0,-35.3553390593,35.3553390593],[50,0,0],[35.3553390593,35.3553390593,0],[0,50,0],[-35.3553390593,35.3553390593,0],[-50,0,0],[-35.3553390593,-35.3553390593,0],[0,-50,0],[35.3553390593,-35.3553390593,0],[35.3553390593,0,-35.3553390593],[0,35.3553390593,-35.3553390593],[-35.3553390593,0,-35.3553390593],[0,-35.3553390593,-35.3553390593],[0,0,-50]],[[0,2,1],[0,3,2],[0,4,3],[0,1,4],[17,15,16],[17,14,15],[17,13,14],[17,16,13],[1,6,5],[1,2,6],[13,5,6],[13,6,14],[2,7,6],[14,6,7],[2,8,7],[2,3,8],[14,7,8],[14,8,15],[3,9,8],[15,8,9],[3,10,9],[3,4,10],[15,9,10],[15,10,16],[4,11,10],[16,10,11],[4,12,11],[4,1,12],[16,11,12],[16,12,13],[1,5,12],[13,12,5]]]); - assert_approx(spheroid(r=50,style="icosa"),[[[0,0,50],[35.0314634611,-25.4518480228,25],[35.0314634611,25.4518480228,25],[0,0,50],[35.0314634611,25.4518480228,25],[-13.3808283665,41.1819551773,25],[0,0,50],[-13.3808283665,41.1819551773,25],[-43.3012701892,8.32667268469e-15,25],[0,0,50],[-43.3012701892,0,25],[-13.3808283665,-41.1819551773,25],[0,0,50],[-13.3808283665,-41.1819551773,25],[35.0314634611,-25.4518480228,25],[43.3012701892,0,-25],[35.0314634611,-25.4518480228,25],[35.0314634611,25.4518480228,25],[13.3808283665,41.1819551773,-25],[35.0314634611,25.4518480228,25],[-13.3808283665,41.1819551773,25],[-35.0314634611,25.4518480228,-25],[-13.3808283665,41.1819551773,25],[-43.3012701892,-5.55111512313e-15,25],[-35.0314634611,-25.4518480228,-25],[-43.3012701892,1.38777878078e-15,25],[-13.3808283665,-41.1819551773,25],[13.3808283665,-41.1819551773,-25],[-13.3808283665,-41.1819551773,25],[35.0314634611,-25.4518480228,25],[0,0,-50],[-35.0314634611,25.4518480228,-25],[-35.0314634611,-25.4518480228,-25],[0,0,-50],[-35.0314634611,-25.4518480228,-25],[13.3808283665,-41.1819551773,-25],[0,0,-50],[13.3808283665,-41.1819551773,-25],[43.3012701892,-8.32667268469e-15,-25],[0,0,-50],[43.3012701892,0,-25],[13.3808283665,41.1819551773,-25],[0,0,-50],[13.3808283665,41.1819551773,-25],[-35.0314634611,25.4518480228,-25],[-43.3012701892,0,25],[-35.0314634611,25.4518480228,-25],[-35.0314634611,-25.4518480228,-25],[-13.3808283665,-41.1819551773,25],[-35.0314634611,-25.4518480228,-25],[13.3808283665,-41.1819551773,-25],[35.0314634611,-25.4518480228,25],[13.3808283665,-41.1819551773,-25],[43.3012701892,5.55111512313e-15,-25],[35.0314634611,25.4518480228,25],[43.3012701892,-1.38777878078e-15,-25],[13.3808283665,41.1819551773,-25],[-13.3808283665,41.1819551773,25],[13.3808283665,41.1819551773,-25],[-35.0314634611,25.4518480228,-25]],[[0,2,1],[3,5,4],[6,8,7],[9,11,10],[12,14,13],[16,17,15],[19,20,18],[22,23,21],[25,26,24],[28,29,27],[31,32,30],[34,35,33],[37,38,36],[40,41,39],[43,44,42],[45,47,46],[48,50,49],[51,53,52],[54,56,55],[57,59,58]]]); + assert_approx(spheroid(r=50,style="icosa"),[[[0,26.286555606,42.5325404176],[42.5325404176,0,26.286555606],[26.286555606,42.5325404176,0],[26.286555606,42.5325404176,3.5527136788e-15],[42.5325404176,-7.1054273576e-15,26.286555606],[42.5325404176,-1.7763568394e-15,-26.286555606],[42.5325404176,-3.5527136788e-15,-26.286555606],[42.5325404176,-1.24344978758e-14,26.286555606],[26.286555606,-42.5325404176,0],[26.286555606,-42.5325404176,5.3290705182e-15],[42.5325404176,-7.1054273576e-15,26.286555606],[5.3290705182e-15,-26.286555606,42.5325404176],[5.3290705182e-15,-26.286555606,42.5325404176],[42.5325404176,7.1054273576e-15,26.286555606],[3.5527136788e-15,26.286555606,42.5325404176],[-26.286555606,42.5325404176,-3.5527136788e-15],[26.286555606,42.5325404176,-1.24344978758e-14],[0,26.286555606,-42.5325404176],[5.3290705182e-15,26.286555606,-42.5325404176],[26.286555606,42.5325404176,-7.1054273576e-15],[42.5325404176,5.3290705182e-15,-26.286555606],[3.5527136788e-15,26.286555606,42.5325404176],[26.286555606,42.5325404176,-7.1054273576e-15],[-26.286555606,42.5325404176,-1.7763568394e-15],[-42.5325404176,3.5527136788e-15,-26.286555606],[-42.5325404176,1.24344978758e-14,26.286555606],[-26.286555606,42.5325404176,0],[-26.286555606,42.5325404176,5.3290705182e-15],[-42.5325404176,7.1054273576e-15,26.286555606],[-5.3290705182e-15,26.286555606,42.5325404176],[-5.3290705182e-15,26.286555606,42.5325404176],[-42.5325404176,-7.1054273576e-15,26.286555606],[-3.5527136788e-15,-26.286555606,42.5325404176],[-6.73862117414e-15,-26.286555606,42.5325404176],[-42.5325404176,-1.14606817409e-14,26.286555606],[-26.286555606,-42.5325404176,8.5422878137e-15],[-26.286555606,-42.5325404176,3.5527136788e-15],[-42.5325404176,7.1054273576e-15,26.286555606],[-42.5325404176,1.7763568394e-15,-26.286555606],[42.5325404176,-6.73862117414e-15,-26.286555606],[26.286555606,-42.5325404176,-1.14606817409e-14],[8.5422878137e-15,-26.286555606,-42.5325404176],[3.5527136788e-15,-26.286555606,-42.5325404176],[26.286555606,-42.5325404176,7.1054273576e-15],[-26.286555606,-42.5325404176,1.7763568394e-15],[-26.286555606,-42.5325404176,3.5527136788e-15],[26.286555606,-42.5325404176,1.24344978758e-14],[0,-26.286555606,42.5325404176],[3.5527136788e-15,-26.286555606,-42.5325404176],[1.24344978758e-14,26.286555606,-42.5325404176],[42.5325404176,0,-26.286555606],[-42.5325404176,3.5527136788e-15,-26.286555606],[7.1054273576e-15,26.286555606,-42.5325404176],[1.7763568394e-15,-26.286555606,-42.5325404176],[-26.286555606,42.5325404176,-6.73862117414e-15],[-1.14606817409e-14,26.286555606,-42.5325404176],[-42.5325404176,8.5422878137e-15,-26.286555606],[-42.5325404176,-5.3290705182e-15,-26.286555606],[-7.1054273576e-15,-26.286555606,-42.5325404176],[-26.286555606,-42.5325404176,-5.3290705182e-15]],[[0,1,2],[3,4,5],[6,7,8],[9,10,11],[12,13,14],[15,16,17],[18,19,20],[21,22,23],[24,25,26],[27,28,29],[30,31,32],[33,34,35],[36,37,38],[39,40,41],[42,43,44],[45,46,47],[48,49,50],[51,52,53],[54,55,56],[57,58,59]]]); } test_spheroid();