Merge remote-tracking branch 'origin' into revarbat_dev

This commit is contained in:
Garth Minette 2022-01-30 20:55:06 -08:00
commit c560b0e236
4 changed files with 82 additions and 57 deletions

View file

@ -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<n ? Q*back_substitute(R,transpose(P)*b,transpose=true) // Too messy to avoid input checks here
: P*_back_substitute(R, transpose(Q)*b); // Calling internal version skips input checks
// Function: linear_solve3()
// Usage:
// x = linear_solve3(A,b)
// Description:
// Fast solution to a 3x3 linear system using Cramer's rule (which appears to be the fastest
// method in OpenSCAD). The input `A` must be a 3x3 matrix. Returns undef if `A` is singular.
// The input `b` must be a 3-vector. Note that Cramer's rule is not a stable algorithm, so for
// the highest accuracy on ill-conditioned problems you may want to use the general solver, which is about ten times slower.
// Arguments:
// A = 3x3 matrix for linear system
// b = length 3 vector, right hand side of linear system
function linear_solve3(A,b) =
// Arg sanity checking adds 7% overhead
assert(b*0==[0,0,0], "Input b must be a 3-vector")
assert(A*0==[[0,0,0],[0,0,0],[0,0,0]],"Input A must be a 3x3 matrix")
let(
Az = [for(i=[0:2])[A[i][0], A[i][1], b[i]]],
Ay = [for(i=[0:2])[A[i][0], b[i], A[i][2]]],
Ax = [for(i=[0:2])[b[i], A[i][1], A[i][2]]],
detA = det3(A)
)
detA==0 ? undef : [det3(Ax), det3(Ay), det3(Az)] / detA;
// Function: matrix_inverse()
// Usage:
// mat = matrix_inverse(A)

View file

@ -330,9 +330,14 @@ module torx_mask(size, l=5, center, anchor, spin=0, orient=UP) {
// robertson_mask(size, [extra]);
// Description:
// Creates a mask for creating a Robertson/Square drive recess given the drive size as an integer.
// The width of the recess will be oversized by `2 * $slop`. Note that this model is based
// on an incomplete spec. https://www.aspenfasteners.com/content/pdf/square_drive_specification.pdf
// We determined the angle by doing print tests on a Prusa MK3S with $slop set to 0.05.
// Arguments:
// size = The size of the square drive, as an integer from 0 to 4.
// extra = Extra length of drive mask to create.
// ang = taper angle of each face. Default: 2.5
// $slop = enlarge recess by this twice amount. Default: 0
// Example:
// robertson_mask(size=2);
// Example:
@ -340,7 +345,7 @@ module torx_mask(size, l=5, center, anchor, spin=0, orient=UP) {
// cyl(d1=2, d2=8, h=4, anchor=TOP);
// robertson_mask(size=2);
// }
module robertson_mask(size, extra=1) {
module robertson_mask(size, extra=1, ang=2.5) {
assert(is_int(size) && size>=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);
}
}
}

View file

@ -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 (k<m-1) [p5, p8, p6],
],
] : style=="icosa"? let(
pyr = [for (x=[0:1:icosa_steps+1]) x],
tri = sum(pyr),
soff = cumsum(pyr)
) [
for (tb=[0,1], j=[0,1], i = [0:1:4]) let(
base = ((((tb*2) + j) * 5) + i) * tri
)
for (k = [0:1:icosa_steps-1])
for (l = [0:1:k]) let(
v1 = base + soff[k] + l,
v2 = base + soff[k+1] + l,
v3 = base + soff[k+1] + (l + 1),
faces = [
if(l>0) [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];

View file

@ -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();