```/*
* plato.i -- \$Id\$
*
* Copyright (c) 1998.  See accompanying LEGAL file for details.
*/

local plato ;
/* DOCUMENT plato.i
Contains routines to generate points related to Platonic
solids and other pleasing or simple 3D geometrical objects.

pt_tet       All these return points of the solid imbedded in
pt_cube      a pleasing way inside the cube with corners +-1.
pt_oct       With a non-zero parameter, the points are instead
pt_dodec     normalized to unit length.
pt_ico

bucky        return points or faces of geodesic dome-like
solids and their solid angles
*/

func pt_tet (norm)
{
/* half of corners of a cube, no particular order */
p= [[1,1,1],[-1,-1,1],[1,-1,-1],[-1,1,-1]];
/* use other choice of corners if norm<0 */
if (norm) p*= sqrt(1./3.)*sign(norm);
return p;
}

func pt_cube (norm)
{
/* points at corners of cube, hence 2x2x2 organization
* where indices are x, y, z directions */
p= array(-1,3,2,2,2);
p(1,2,,)= p(2,,2,)= p(3,,,2)= 1;
if (norm) p*= sqrt(1./3.);
return p;
}

func pt_oct (norm)
{
/* one point on each face of a cube, hence 2x3 organization
* where 1st index selects + or - face of cube
*       2nd index selects xyz face of cube */
p= array(0,3,2,3);
p(1,,1)= p(2,,2)= p(3,,3)= [-1,1];
return p;
}

func pt_ico (norm)
{
/* two points on each face of a cube, hence 2x2x3 organization,
* where 1st index selects + or - point on face of cube,
*       2nd index selects + or - face of cube
*       3rd index selects xyz face of cube */
g= 0.5*(sqrt(5.)-1.);  /* reciprocal golden ratio */
p= [[[0,g,1],[0,-g,1]],[[0,g,-1],[0,-g,-1]]];
p= [p, roll(p,[1,0,0]), roll(p,[2,0,0])];
if (norm) p/= abs(p(1,..),p(2,..),p(3,..))(-,..);
return p;
}

func pt_dodec (norm)
{
/* two points on each face of a cube, followed by corners of cube */
g= 0.5*(sqrt(5.)-1.);  /* reciprocal golden ratio */
g2= 1.-g;  /* equals g*g */
p= [[[0,g2,1],[0,-g2,1]],[[0,g2,-1],[0,-g2,-1]]];
p= [p, roll(p,[1,0,0]), roll(p,[2,0,0])];
p= grow(p(,*),g*pt_cube()(,*));
if (norm) p/= abs(p(1,..),p(2,..),p(3,..))(-,..);
return p;
}

func bucky (n,faces,&domega)
{
/* two rings of five plus two points at poles
* rings have cos(theta) = +- g (reciprocal golden ratio),
*            sin(theta) = sqrt(g)
*
* The points are organized into five spiral strips running
* southeast from the north pole to the south pole.  Each strip
* consists of a 3x2 array of points which bound a strip of four
* triangular faces; the diagonals run from (1,1) to (2,2) and
* from (2,1) to (3,2).  The point (1,2) is always the north pole,
* and the point (3,1) is always the south pole.  The points
* (2,2) and (3,2) on the eastern edge of one strip are the same
* as the points (1,1) and (3,2), respectively, on the western
* edge of the strip immediately to the east.
* Hence, the dimensionality of the returned array of points
* is 3 by 3x2x5.  (30 - 4 duplications of north pole
* - 4 duplications of south pole - 5*2 other duplicates = 12)
*
* When you specify n (default 0), bucky will halve each of the
* initial 3x2 strips n times, to produce a (2^(n+1)+1)x(2^n+1)
* array of equally spaced points in place of the 3x2 points of
* the n=0 pattern.
*   n=0 -->   12 points   20 faces
*   n=1 -->   42 points   80 faces
*   n=2 -->  162 points  320 faces
*   n=4 -->  642 points 1280 faces
*   n=5 --> 2562 points 5120 faces
*
* suggesting a worst case area ratio of (2+g)/(2-g)/3 = 0.631476.
* However, by renormalizing the points after each doubling, this
* ratio is considerably improved; the worst case in the limit of
* many doublings is a little under 0.769.
*/
g= 0.5*(sqrt(5.)-1.);  /* reciprocal golden ratio */
p= array(0., 3,3,2,5);
p(,1,2,)= [0,0,1];   /* north pole */
p(,3,1,)= [0,0,-1];  /* south pole */
c36= 0.5*(1.+g);  /* equals 0.5/g */
s36= 0.5*sqrt(2.-g);
c72= 0.5*g;
s72= 0.5*sqrt(3.+g);
g= 1./(1.+2.*g);  /* cos of ring angle */
s= 2.*g;          /* sin is twice cos for this magic angle */
ringn= s*[[1,0],[c72,s72],[-c36,s36],[-c36,-s36],[c72,-s72]];
rings= s*[[c36,s36],[-c72,s72],[-1,0],[-c72,-s72],[c36,-s36]];
p(3,1,1,)= p(3,2,2,)= g;
p(1:2,1,1,)= ringn;
p(1:2,2,2,)= roll(ringn,[0,-1]);
p(3,2,1,)= p(3,3,2,)= -g;
p(1:2,2,1,)= rings;
p(1:2,3,2,)= roll(rings,[0,-1]);
if (!n) n= 0;
while (n--) {
dims= dimsof(p);
dims(3:4)= 2*dims(3:4)-1;
q= array(0., dims);
q(,1:0:2,1:0:2,)= p;
q(,2:-1:2,1:0:2,)= p(,zcen,,);
q(,1:0:2,2:-1:2,)= p(,,zcen,);
q(,2:-1:2,2:-1:2,)= p(,2:0,2:0,)+p(,1:-1,1:-1,); /* *0.5 */
p= q/abs(q(1,..),q(2,..),q(3,..))(-,..);
q= x= [];
}
if (faces) {
dims= dimsof(p);
q= array(0.,3,2,dims(3)-1,dims(4)-1,5);
llur= (pb=p(,1:-1,1:-1,)) + (pc=p(,2:0,2:0,));
q(,1,,,)= llur + (pa=p(,1:-1,2:0,));  /* a-b-c */
q(,2,,,)= llur + (pd=p(,2:0,1:-1,));  /* d-c-b */
domega= q(1,..);
p= q/abs(domega,q(2,..),q(3,..))(-,..);
domega(1,..)= pt_solid2(pa,pb,pc);
domega(2,..)= pt_solid2(pd,pc,pb);
}
return p;
}

func pt_cross (a,b)
{
i = [2,3,1];
j = [3,1,2];
return a(i,..)*b(j,..) - a(j,..)*b(i,..);
}

func pt_solid (a,b,c)
{
vab= pt_cross(a,b);
sab= sqrt((vab*vab)(sum,..));
vbc= pt_cross(b,c);
sbc= sqrt((vbc*vbc)(sum,..));
vca= pt_cross(c,a);
sca= sqrt((vca*vca)(sum,..));
cosa= -(vab*vca)(sum,..)/(sab*sca);
cosb= -(vbc*vab)(sum,..)/(sbc*sab);
cosc= -(vca*vbc)(sum,..)/(sca*sbc);
/* this formula is simple, but inaccurate for small triangles */
return acos(cosa)+acos(cosb)+acos(cosc)-pi;
}

func pt_solid2 (a,b,c)
{
vab= pt_cross(a,b);
sab= sqrt((vab*vab)(sum,..));
vbc= pt_cross(b,c);
sbc= sqrt((vbc*vbc)(sum,..));
vca= pt_cross(c,a);
sca= sqrt((vca*vca)(sum,..));
da= 1./(sab*sca);
db= 1./(sbc*sab);
dc= 1./(sca*sbc);
cosa= -(vab*vca)(sum,..)*da;
cosb= -(vbc*vab)(sum,..)*db;
cosc= -(vca*vbc)(sum,..)*dc;
sina= pt_cross(vab,vca);
sina= sqrt((sina*sina)(sum,..))*da;
sinb= pt_cross(vab,vca);
sinb= sqrt((sinb*sinb)(sum,..))*db;
sinc= pt_cross(vab,vca);
sinc= sqrt((sinc*sinc)(sum,..))*dc;
sabc= abs(sina*(cosb*cosc-sinb*sinc)+cosa*(sinb*cosc+cosb*sinc));
cabc= 1. - cosa*(cosb*cosc-sinb*sinc) + sina*(sinb*cosc+cosb*sinc);
/* this formula is good for small triangles,
* bad for triangles with area near 2*pi */
return 4.*asin(0.5*sabc/sqrt(cabc*(1.+sqrt(0.5*cabc))));
}
```