/* spheregen.c,v 1.1.1.1 1995/02/27 07:38:49 explorer Exp */

/*
 * Routines to define a unit sphere Object.
 * If you have a sphere library (-lsphere on most SGI machines), you can
 * use that by defining SPHERELIB on the cc command line.  Otherwise,
 * we use Jon Leech's code to generate a sphere.
 * C. Kolb 6/91
 */
#ifdef SPHERELIB
#include <gl.h>

/*
 * Define unit sphere Object.  Here, we make use of sgi's spherelib.
 * If not available, undefine SPHERELIB, and the code below will be used.
 */
Object
GLSphereObjectDefine()
{
  int sphere;
  
  sphere = genobj();
  sphobj(sphere);
  return sphere;
}
#else

/*
 * The following code has been slightly modified.  The original sphere
 * code is available via anonymous
 * ftp from weedeater.math.yale.edu (130.132.23.17) in pub/sphere.c
 *
 * Modifications include changing print_triangle to draw a GL object,
 * the inclusion of gl.h,
 * the renaming of main() to GLSphereObjectDefine(), the nuking of anything
 * phigs-related, and setting the ccw flag to be true.
 * 
 * C. Kolb 6/91
 */

/*% cc -g sphere.c -o sphere -lm
 *
 * sphere - generate a triangle mesh approximating a sphere by
 *  recursive subdivision. First approximation is an octahedron;
 *  each level of refinement increases the number of triangles by
 *  a factor of 4.
 * Level 3 (128 triangles) is a good tradeoff if gouraud
 *  shading is used to render the database.
 *
 * Usage: sphere [level] [-p] [-c]
 *	level is an integer >= 1 setting the recursion level (default 1).
 *	-p causes generation of a PPHIGS format ASCII archive
 *	    instead of the default generic output format.
 *	-c causes triangles to be generated with vertices in counterclockwise
 *	    order as viewed from the outside in a RHS coordinate system.
 *	    The default is clockwise order.
 *
 *  The subroutines print_object() and print_triangle() should
 *  be changed to generate whatever the desired database format is.
 *
 * Jon Leech (leech@cs.unc.edu) 3/24/89
 */
#include <stdio.h>
#include <math.h>

#include <gl.h>	

typedef struct {
  double  x, y, z;
} point;

typedef struct {
  point     pt[3];	/* Vertices of triangle */
  double    area;	/* Unused; might be used for adaptive subdivision */
} triangle;

typedef struct {
  int       npoly;	/* # of triangles in object */
  triangle *poly;	/* Triangles */
} object;

/* Six equidistant points lying on the unit sphere */
#define XPLUS {  1,  0,  0 }	/*  X */
#define XMIN  { -1,  0,  0 }	/* -X */
#define YPLUS {  0,  1,  0 }	/*  Y */
#define YMIN  {  0, -1,  0 }	/* -Y */
#define ZPLUS {  0,  0,  1 }	/*  Z */
#define ZMIN  {  0,  0, -1 }	/* -Z */

/* Vertices of a unit octahedron */
triangle octahedron[] = {
  { { XPLUS, ZPLUS, YPLUS }, 0.0 },
  { { YPLUS, ZPLUS, XMIN  }, 0.0 },
  { { XMIN , ZPLUS, YMIN  }, 0.0 },
  { { YMIN , ZPLUS, XPLUS }, 0.0 },
  { { XPLUS, YPLUS, ZMIN  }, 0.0 },
  { { YPLUS, XMIN , ZMIN  }, 0.0 },
  { { XMIN , YMIN , ZMIN  }, 0.0 },
  { { YMIN , XPLUS, ZMIN  }, 0.0 }
};

/* A unit octahedron */
object oct = {
  sizeof(octahedron) / sizeof(octahedron[0]),
  &octahedron[0]
};

/* Forward declarations */
point *normalize(/* point *p */);
point *midpoint(/* point *a, point *b */);
void print_object(/* object *obj, int level */);
void print_triangle(/* triangle *t */);

/*extern char *malloc(/* unsigned );*/

Object
GLSphereObjectDefine(maxlevel)
     int maxlevel;
{
  object *old;
  object *new;
  int     ccwflag = 1;	/* Reverse vertex order if true */
  int     i, level;
  Object sph;

  makeobj(sph = genobj());
  
  if (ccwflag) {
    /* Reverse order of points in each triangle */
    for (i = 0; i < oct.npoly; i++) {
      point tmp;
      tmp = oct.poly[i].pt[0];
      oct.poly[i].pt[0] = oct.poly[i].pt[2];
      oct.poly[i].pt[2] = tmp;
    }
  }
  
  old = &oct;
  
  /* Subdivide each starting triangle (maxlevel - 1) times */
  for (level = 1; level < maxlevel; level++) {
    /* Allocate a new object */
    new = (object *)malloc(sizeof(object));
    if (new == NULL) {
      fprintf(stderr, "GLSphereObjectDefine: out of memory, level %d\n",
	      level);
      exit(1);
    }
    new->npoly = old->npoly * 4;
    
    /* Allocate 4* the number of points in the current approximation */
    new->poly  = (triangle *)malloc(new->npoly * sizeof(triangle));
    if (new->poly == NULL) {
      fprintf(stderr, "GLSphereObjectDefine: out of memory, level %d\n",
	      level);
      exit(1);
    }
    
    /* Subdivide each triangle in the old approximation and normalize
     *  the new points thus generated to lie on the surface of the unit
     *  sphere.
     * Each input triangle with vertices labelled [0,1,2] as shown
     *  below will be turned into four new triangles:
     *
     *			Make new points
     *			    a = (0+2)/2
     *			    b = (0+1)/2
     *			    c = (1+2)/2
     *	        1
     *	        /\	        Normalize a, b, c
     *	       /  \
     *       b/____\ c	        Construct new triangles
     *       /\    /\               [0,b,a]
     *      /  \  /  \              [b,1,c]
     *     /____\/____\	            [a,b,c]
     *    0      a     2            [a,c,2]
     */
    for (i = 0; i < old->npoly; i++) {
      triangle
	*oldt = &old->poly[i],
	*newt = &new->poly[i*4];
      point a, b, c;
      
      a = *normalize(midpoint(&oldt->pt[0], &oldt->pt[2]));
      b = *normalize(midpoint(&oldt->pt[0], &oldt->pt[1]));
      c = *normalize(midpoint(&oldt->pt[1], &oldt->pt[2]));
      
      newt->pt[0] = oldt->pt[0];
      newt->pt[1] = b;
      newt->pt[2] = a;
      newt++;
      
      newt->pt[0] = b;
      newt->pt[1] = oldt->pt[1];
      newt->pt[2] = c;
      newt++;
      
      newt->pt[0] = a;
      newt->pt[1] = b;
      newt->pt[2] = c;
      newt++;
      
      newt->pt[0] = a;
      newt->pt[1] = c;
      newt->pt[2] = oldt->pt[2];
    }
    
    if (level > 1) {
      free(old->poly);
      free(old);
    }
    
    /* Continue subdividing new triangles */
    old = new;
  }
  
  /* Print out resulting approximation */
  
  print_object(old, maxlevel);
  closeobj();
  return sph;
}

/* Normalize a point p */
point *normalize(p)
     point *p;
{
  static point r;
  double mag;
  
  r = *p;
  mag = r.x * r.x + r.y * r.y + r.z * r.z;
  if (mag != 0.0) {
    mag = 1.0 / sqrt(mag);
    r.x *= mag;
    r.y *= mag;
    r.z *= mag;
  }
  
  return &r;
}

/* Return the midpoint on the line between two points */
point *midpoint(a, b)
     point *a, *b;
{
  static point r;
  
  r.x = (a->x + b->x) * 0.5;
  r.y = (a->y + b->y) * 0.5;
  r.z = (a->z + b->z) * 0.5;
  
  return &r;
}

/* Write out all triangles in an object */
void print_object(obj, level)
     object *obj;
     int level;
{
  int i;
  
  /* Spit out coordinates for each triangle */
  for (i = 0; i < obj->npoly; i++)
    print_triangle(&obj->poly[i]);
}

/* Output a triangle */
void print_triangle(t)
     triangle *t;
{
  int i;
  float p[3];
  
#ifdef sgi
  bgnpolygon();
#endif
  
  p[0] = t->pt[0].x; p[1] = t->pt[0].y; p[2] = t->pt[0].z;
  n3f(p);
#ifdef sgi
  v3f(p);
#else
  pmv(p[0], p[1], p[2]);
#endif
  p[0] = t->pt[1].x; p[1] = t->pt[1].y; p[2] = t->pt[1].z;
  n3f(p);
#ifdef sgi
  v3f(p);
#else
  pdr(p[0], p[1], p[2]);
#endif
  p[0] = t->pt[2].x; p[1] = t->pt[2].y; p[2] = t->pt[2].z;
  n3f(p);
#ifdef sgi
  v3f(p);
  endpolygon();
#else
  pdr(p[0], p[1], p[2]);
  pclos();
#endif
}

#endif
