/*
 * lib.c - a library of vector operations, a random number generator, and
 *     object output routines.
 *
 * Version:  2.2 (11/17/87)
 * Author:  Eric Haines, 3D/Eye, Inc.
 *
 * Modified: 1 October 1992
 *           Alexander R. Enzmann
 *
 *           I made quite a few changes in order to support multiple raytracers,
 *           with the hopes that this library would become even more useful than
 *           it already is.
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include "lib.h"
#include "disp.h"

/* Here are some local variables that are used to control things like
   the current output file, current texture, ... */
static FILE *outfile      = stdout;
static char *texture_name = NULL;
static int  texture_count = 0;
static double texture_ior = 1.0;
static int  format        = OUTPUT_POVRAY;
static int  u_resolution  = OUTPUT_RESOLUTION;
static int  v_resolution  = OUTPUT_RESOLUTION;
static COORD4 bk_color = {0.0, 0.0, 0.0, 0.0};
static COORD4 fg_color = {0.0, 0.0, 0.0, 0.0};
static double view_bounds[2][3];
static int view_init_flag = 0;

surface_ptr lib_surfaces = NULL;
object_ptr lib_objects = NULL;
light_ptr lib_lights = NULL;
viewpoint view = {{0, 0, -10, 1}, {0, 0, 0, 1}, {0, 1, 0, 1}, 45,
		  1, 1.0e-3, 10, 128, 128,
		  {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}}};

/* Polygon stack for making PLG files */
static object_ptr polygon_stack = NULL;

/*
 * Routines to set/reset the various output parameters
 */
void
lib_set_output_file(new_outfile)
   FILE *new_outfile;
{
   if (new_outfile == NULL)
      outfile = stdout;
   else
      outfile = new_outfile;
}

void
lib_set_default_texture(default_texture)
   char *default_texture;
{
   texture_name = default_texture;
}

void
lib_set_raytracer(default_tracer)
   int default_tracer;
{
   if (default_tracer < OUTPUT_VIDEO ||
       default_tracer > OUTPUT_DELAYED) {
      fprintf(stderr, "Unknown renderer index: %d\n", default_tracer);
      exit(1);
      }
   format = default_tracer;
}

void
lib_set_polygonalization(u_steps, v_steps)
   int u_steps, v_steps;
{
   if (u_steps > 0 && v_steps > 0) {
      u_resolution = u_steps;
      v_resolution = v_steps;
      }
}

static void
lookup_surface_stats(index, tcount, tior)
   int index, *tcount;
   double *tior;
{
   surface_ptr temp_ptr = lib_surfaces;

   while (temp_ptr != NULL && temp_ptr->surf_index != index)
      temp_ptr = temp_ptr->next;
   *tior = temp_ptr->ior;
   if (*tior < 1.0) *tior = 1.0;
   *tcount = temp_ptr->surf_index;
}

/* OUTPUT ROUTINES */

/*
 * Output viewpoint location.  The parameters are:
 *   From:  the eye location.
 *   At:  a position to be at the center of the image.  A.k.a. "lookat"
 *   Up:  a vector defining which direction is up.
 *
 * Note that no assumptions are made about normalizing the data (e.g. the
 * from-at distance does not have to be 1).  Also, vectors are not
 * required to be perpendicular to each other.
 *
 * For all databases some viewing parameters are always the same:
 *
 *   Viewing angle is defined as from the center of top pixel row to bottom
 *     pixel row and left column to right column.
 *   Yon is "at infinity."
 *   Resolution is always 512 x 512.
 */
void
lib_output_viewpoint(from, at, up,
                     fov_angle, aspect_ratio, hither,
                     resx, resy)
   COORD4 *from, *at, *up;
   double fov_angle, aspect_ratio, hither;
   int    resx, resy;
{
   COORD4 viewvec, rightvec, dir;
   double frustrumheight, frustrumwidth;

   switch (format) {
      case OUTPUT_DELAYED:
      case OUTPUT_VIDEO:
      case OUTPUT_PLG:
         /* Save the various view parameters */
         COPY_COORD4(view.from, *from);
         COPY_COORD4(view.at, *at);
         COPY_COORD4(view.up, *up);
         view.angle  = fov_angle;
         view.hither = hither;
         view.resx   = resx;
         view.resy   = resy;
         view.aspect = aspect_ratio;

         /* Make the 3D clipping box for this view */
         view_bounds[0][0] = -view.resx/2;
         view_bounds[1][0] =  view.resx/2;
         view_bounds[0][1] = -view.resy/2;
         view_bounds[1][1] =  view.resy/2;
         view_bounds[0][2] = view.hither;
         view_bounds[1][2] = 1.0e10;

         /* Generate the perspective view matrix */
         lib_create_view_matrix(view.tx, &view.from, &view.at,
                                &view.up, view.resx, view.resy,
                                view.angle, view.aspect);

	 /* Turn on graphics using system dependent video routines */
	 if (format == OUTPUT_VIDEO) {
	    display_init(view.resx, view.resy);
	    view_init_flag = 1;
	    }
         break;
      case OUTPUT_NFF:
         fprintf(outfile, "v\n");
         fprintf(outfile, "from %g %g %g\n", from->x, from->y, from->z);
         fprintf(outfile, "at %g %g %g\n", at->x, at->y, at->z);
         fprintf(outfile, "up %g %g %g\n", up->x, up->y, up->z);
         fprintf(outfile, "angle %g\n", fov_angle);
         fprintf(outfile, "hither %g\n", hither);
         fprintf(outfile, "resolution %d %d\n", resx, resy);
         break;
      case OUTPUT_POVRAY:
      case OUTPUT_POVRAY_15:
         /* Lets get a set of vectors that are all at right angles to each
            other that describe the view given. */
         lib_normalize_coord3(up);
         SUB3_COORD(viewvec, *at, *from);
         lib_normalize_coord3(&viewvec);
         CROSS(rightvec, *up, viewvec);
         lib_normalize_coord3(&rightvec);
         CROSS(*up, viewvec, rightvec);
         lib_normalize_coord3(up);

         /* Calculate the height of the view frustrum in world coordinates.
            and then scale the right and up vectors appropriately. */
         frustrumheight = 2.0 * tan(PI * fov_angle / 360.0);
         frustrumwidth = aspect_ratio * frustrumheight;
         up->x *= frustrumheight;
         up->y *= frustrumheight;
         up->z *= frustrumheight;
         rightvec.x *= frustrumwidth;
         rightvec.y *= frustrumwidth;
         rightvec.z *= frustrumwidth;

         fprintf(outfile, "camera {\n");
         if (format == OUTPUT_POVRAY) {
            fprintf(outfile, "  location <%g %g %g>\n",
                    from->x, from->y, from->z);
            fprintf(outfile, "  direction <%g %g %g>\n",
                    viewvec.x, viewvec.y, viewvec.z);
            fprintf(outfile, "  right <%g %g %g>\n",
                    rightvec.x, rightvec.y, rightvec.z);
            fprintf(outfile, "  up <%g %g %g>\n",
                    up->x, up->y, up->z);
            }
         else {
            fprintf(outfile, "  location <%g, %g, %g>\n",
                    from->x, from->y, from->z);
            fprintf(outfile, "  direction <%g, %g, %g>\n",
                    viewvec.x, viewvec.y, viewvec.z);
            fprintf(outfile, "  right <%g, %g, %g>\n",
                    rightvec.x, rightvec.y, rightvec.z);
            fprintf(outfile, "  up <%g, %g, %g>\n",
                    up->x, up->y, up->z);
            }
         fprintf(outfile, "  }\n");
         break;
      case OUTPUT_POLYRAY:
         fprintf(outfile, "viewpoint {\n");
         fprintf(outfile, "   from <%g, %g, %g>\n", from->x, from->y, from->z);
         fprintf(outfile, "   at <%g, %g, %g>\n", at->x, at->y, at->z);
         fprintf(outfile, "   up <%g, %g, %g>\n", up->x, up->y, up->z);
         fprintf(outfile, "   angle %g\n", fov_angle);
         fprintf(outfile, "   aspect %g\n", aspect_ratio);
         fprintf(outfile, "   hither %g\n", hither);
         fprintf(outfile, "   resolution %d, %d\n", resx, resy);
         fprintf(outfile, "   }\n");
         break;
      case OUTPUT_VIVID:
         fprintf(outfile, "studio = {\n");
         fprintf(outfile, "   from = %g %g %g;\n", from->x, from->y, from->z);
         fprintf(outfile, "   at = %g %g %g;\n", at->x, at->y, at->z);
         fprintf(outfile, "   up = %g %g %g;\n", up->x, up->y, up->z);
         fprintf(outfile, "   angle = %g;\n", fov_angle);
         fprintf(outfile, "   aspect = %g;\n", aspect_ratio);
         fprintf(outfile, "   resolution = %d %d;\n", resx, resy);
         fprintf(outfile, "   }\n");
         break;
      case OUTPUT_QRT:
         fprintf(outfile, "OBSERVER = (\n");
         fprintf(outfile, "   loc = (%g,%g,%g),\n", from->x, from->y, from->z);
         fprintf(outfile, "   lookat = (%g,%g,%g),\n", at->x, at->y, at->z);
         fprintf(outfile, "   up = (%g,%g,%g) )\n", up->x, up->y, up->z);
         fprintf(outfile, "FOC_LENGTH = %g\n",
                 35.0 / tan(PI * fov_angle / 360.0));
         fprintf(outfile, "DEFAULT (\n");
         fprintf(outfile, "   aspect = %g,\n", 6.0 * aspect_ratio / 7.0);
         fprintf(outfile, "   x_res = %d, y_res = %d )\n", resx, resy);

         /* QRT insists on having the output file as part of the data text */
         fprintf(outfile, "FILE_NAME = qrt.tga\n");
         break;
      case OUTPUT_RAYSHADE:
         fprintf(outfile, "eyep %g %g %g\n", from->x, from->y, from->z);
         fprintf(outfile, "lookp %g %g %g\n", at->x, at->y, at->z);
         fprintf(outfile, "up %g %g %g\n", up->x, up->y, up->z);
         fprintf(outfile, "fov %g %g\n", aspect_ratio * fov_angle, fov_angle);
         fprintf(outfile, "screen %d %d\n", resx, resy);
         fprintf(outfile, "sample 1 nojitter\n");
         break;
      case OUTPUT_RTRACE:
	 fprintf(outfile, "View\n");
	 fprintf(outfile, "%g %g %g\n", from->x, from->y, from->z);
	 fprintf(outfile, "%g %g %g\n", at->x, at->y, at->z);
	 fprintf(outfile, "%g %g %g\n", up->x, up->y, up->z);
	 fprintf(outfile, "%g %g\n", aspect_ratio * fov_angle/2, fov_angle/2);
	 break;
      }
}

/*
 * Output light.  A light is defined by position.  All lights have the same
 * intensity.
 *
 */
void
lib_output_light(center_pt)
   COORD4 *center_pt;
{
   double lscale;
   light_ptr new_light;

   if (center_pt->w != 0.0)
      lscale = center_pt->w;
   else
      lscale = 1.0;

   switch (format) {
      case OUTPUT_DELAYED:
	 new_light = (light_ptr)malloc(sizeof(struct light_struct));
	 if (new_light == NULL)
	    /* Quietly fail & return */
	    return;
	 COPY_COORD(new_light->center_pt, *center_pt);
	 new_light->center_pt.w = lscale;
	 new_light->next = lib_lights;
	 lib_lights = new_light;
	 break;
      case OUTPUT_VIDEO:
      case OUTPUT_PLG:
         /* Not currently doing anything with lights */
         break;
      case OUTPUT_NFF:
         fprintf(outfile, "l %g %g %g\n",
                 center_pt->x, center_pt->y, center_pt->z ) ;
         break;
      case OUTPUT_POVRAY:
         fprintf(outfile, "object { ");
         fprintf(outfile,
                 "light_source { <%g %g %g> color red %g green %g blue %g }",
                 center_pt->x, center_pt->y, center_pt->z,
                 lscale, lscale, lscale);
         fprintf(outfile, "}\n");
         break;
      case OUTPUT_POVRAY_15:
         fprintf(outfile, "object { ");
         fprintf(outfile,
                 "light_source { <%g, %g, %g> color red %g green %g blue %g }",
                 center_pt->x, center_pt->y, center_pt->z,
                 lscale, lscale, lscale);
         fprintf(outfile, "}\n");
         break;
      case OUTPUT_POLYRAY:
         fprintf(outfile, "light <%g, %g, %g>, <%g, %g, %g>\n",
                 lscale, lscale, lscale,
                 center_pt->x, center_pt->y, center_pt->z);
         break;
      case OUTPUT_VIVID:
         fprintf(outfile, "light = {type=point; position = %g %g %g;",
                 center_pt->x, center_pt->y, center_pt->z );
         fprintf(outfile, " color = %g %g %g;}\n",
                 lscale, lscale, lscale);
         break;
      case OUTPUT_QRT:
         fprintf(outfile, "LAMP ( loc = (%g,%g,%g), dist = 0, radius = 1,",
                 center_pt->x, center_pt->y, center_pt->z );
         fprintf(outfile, " amb = (%g,%g,%g) )\n",
                 lscale, lscale, lscale);
         break;
      case OUTPUT_RAYSHADE:
         fprintf(outfile, "light %g point %g %g %g\n",
                 center_pt->w, center_pt->x, center_pt->y, center_pt->z);
         break;
      case OUTPUT_RTRACE:
	 fprintf(outfile, "1 %g %g %g %g %g %g\n",
		 center_pt->x, center_pt->y, center_pt->z,
		 center_pt->w, center_pt->w, center_pt->w);
      }
}

/*
 * Output background color.  A color is simply RGB (monitor dependent, but
 * that's life).
 */
void
lib_output_background_color(color)
   COORD4 *color;
{
   switch (format) {
      case OUTPUT_DELAYED:
      case OUTPUT_VIDEO:
      case OUTPUT_PLG:
         COPY_COORD4(bk_color, *color);
         break;
      case OUTPUT_NFF:
         fprintf(outfile, "b %g %g %g\n", color->x, color->y, color->z);
         break;
      case OUTPUT_POVRAY:
      case OUTPUT_POVRAY_15:
         /* POV-Ray 1.0/1.5 do not support a background color */
         break;
      case OUTPUT_POLYRAY:
         fprintf(outfile, "background <%g, %g, %g>\n",
                 color->x, color->y, color->z);
         break;
      case OUTPUT_VIVID:
         /* Vivid insists on putting the background into the studio */
         fprintf(outfile, "studio = { background = %g %g %g; }\n",
                 color->x, color->y, color->z);
         break;
      case OUTPUT_QRT:
         fprintf(outfile, "SKY ( horiz = (%g,%g,%g), zenith = (%g,%g,%g),",
                 color->x, color->y, color->z,
                 color->x, color->y, color->z);
         fprintf(outfile, " dither = 0 )\n");
         break;
      case OUTPUT_RAYSHADE:
         fprintf(outfile, "background %g %g %g\n",
                 color->x, color->y, color->z);
         break;
      case OUTPUT_RTRACE:
	 fprintf(outfile, "Colors\n");
	 fprintf(outfile, "%g %g %g\n", color->x, color->y, color->z);
	 fprintf(outfile, "0 0 0\n");
	 break;
      }
}

static char *
create_surface_name(name, val)
   char *name;
   int val;
{
   char *txname;

   if (name != NULL)
      return name;

   txname = (char *)malloc(7*sizeof(char));
   if (txname == NULL)
      return NULL;
   sprintf(txname, "txt%03d", val);
   txname[6] = '\0';
   return txname;
}

/*
 * Output color and shading parameters for all following objects
 *
 * For POV-Ray and Polyray, a character string will be returned that
 * identified this texture.  The default texture will be updated with
 * the name generated by this function.
 *
 * Meaning of the color and shading parameters:
 *    name   = name that this surface can be referenced by...
 *    color  = surface color
 *    ka     = ambient component
 *    kd     = diffuse component
 *    ks     = amount contributed from the reflected direction
 *    shine  = contribution from specular highlights
 *    ang    = angle at which the specular highlight falls to 50% of maximum
 *    t      = amount from the refracted direction
 *    i_of_r = index of refraction of the surface
 *
 */
char *
lib_output_color(name, color, ka, kd, ks, shine, ang, kt, i_of_r)
   char *name;
   COORD4 *color;
   double ka, kd, ks, shine, ang, kt, i_of_r;
{
   surface_ptr new_surf;
   char *txname = NULL;
   double phong_pow;

   /* Increment the number of surface types we know about */
   ++texture_count;
   texture_ior = i_of_r;

   /* Calculate the Phong coefficient */
   phong_pow = PI * ang / 180.0;
   if (phong_pow <= 0.0)
      phong_pow = 100000.0;
   else if (phong_pow >= (PI/4.0))
      phong_pow = 0.000001;
   else
      phong_pow = -(log(2.0) / log(cos(2.0 * phong_pow)));

   switch (format) {
      case OUTPUT_DELAYED:
	 new_surf = (surface_ptr)malloc(sizeof(struct surface_struct));
	 if (new_surf == NULL)
	    /* Quietly fail */
	    return;
	 new_surf->surf_name = create_surface_name(name, texture_count);
	 new_surf->surf_index = texture_count;
	 COPY_COORD(new_surf->color, *color);
	 new_surf->ka = ka;
	 new_surf->kd = kd;
	 new_surf->ks = ks;
	 new_surf->shine = shine;
	 new_surf->ang = ang;
	 new_surf->kt = kt;
	 new_surf->ior = i_of_r;
	 new_surf->next = lib_surfaces;
	 lib_surfaces = new_surf;
	 break;
      case OUTPUT_PLG:
      case OUTPUT_VIDEO:
         COPY_COORD4(fg_color, *color);
         break;
      case OUTPUT_NFF:
         fprintf(outfile, "f %g %g %g %g %g %g %g %g\n",
                 color->x, color->y, color->z, kd, ks, phong_pow, kt, i_of_r);
         break;
      case OUTPUT_POVRAY:
      case OUTPUT_POVRAY_15:
         txname = create_surface_name(name, texture_count);
         fprintf(outfile, "#declare %s = texture {\n", txname);
         if (format == OUTPUT_POVRAY_15)
            fprintf(outfile, "   pigment {\n");
         if (kt > 0)
            fprintf(outfile, "   color red %g green %g blue %g alpha 1.0\n",
                    color->x, color->y, color->z);
         else
            fprintf(outfile, "   color red %g green %g blue %g\n",
                    color->x, color->y, color->z);
         if (format == OUTPUT_POVRAY_15) {
            fprintf(outfile, "   }\n");
            fprintf(outfile, "   finish {\n");
            }
         fprintf(outfile, "   ambient %g\n", ka);
         fprintf(outfile, "   diffuse %g\n", kd);
         if (shine != 0)
            fprintf(outfile, "   phong %g phong_size %g\n", shine, phong_pow);
         if (ks != 0)
            fprintf(outfile, "   reflection %g\n", ks);
         if (kt != 0)
            fprintf(outfile, "   refraction %g ior %g\n", kt, i_of_r);
         if (format == OUTPUT_POVRAY_15)
            fprintf(outfile, "   }\n");
         fprintf(outfile, "   }\n");
         break;
      case OUTPUT_POLYRAY:
         txname = create_surface_name(name, texture_count);
         fprintf(outfile, "define %s\n", txname);
         fprintf(outfile, "texture {\n");
         fprintf(outfile, "   surface {\n");
         fprintf(outfile, "      ambient <%g, %g, %g>, %g\n",
                color->x, color->y, color->z, ka);
         fprintf(outfile, "      diffuse <%g, %g, %g>, %g\n",
                color->x, color->y, color->z, kd);
         if (shine != 0) {
            fprintf(outfile, "      specular white, %g\n", shine);
            fprintf(outfile, "      microfacet Phong %g\n", ang);
            }
         if (ks != 0)
            fprintf(outfile, "      reflection white, %g\n", ks);
         if (kt != 0)
            fprintf(outfile, "      transmission white, %g, %g\n", kt, i_of_r);
         fprintf(outfile, "      }\n");
         fprintf(outfile, "   }\n");
         break;
      case OUTPUT_VIVID:
         fprintf(outfile, "surface = {\n");
         fprintf(outfile, "   ambient = %g %g %g;\n",
                 ka * color->x, ka * color->y, ka * color->z, ka);
         fprintf(outfile, "   diffuse = %g %g %g;\n",
                 kd * color->x, kd * color->y, kd * color->z, ka);
         if (shine != 0)
            fprintf(outfile, "   shine = %g %g %g %g;\n",
                    phong_pow, shine, shine, shine);
         if (ks != 0)
            fprintf(outfile, "   specular = %g %g %g;\n", ks, ks, ks);
         if (kt != 0) {
            fprintf(outfile, "   transparent = %g %g %g;\n",
                 kt * color->x, kt * color->y, kt * color->z);
            fprintf(outfile, "   ior = %g;\n", i_of_r);
            }
         fprintf(outfile, "   }\n");
         break;
      case OUTPUT_QRT:
         fprintf(outfile, "DEFAULT (\n");
         fprintf(outfile, "   amb = (%g,%g,%g),\n",
                 ka * color->x, ka * color->y, ka * color->z, ka);
         fprintf(outfile, "   diff = (%g,%g,%g),\n",
                 kd * color->x, kd * color->y, kd * color->z, ka);
         fprintf(outfile, "   reflect = %g, sreflect = %g,\n",
                 shine, phong_pow);
         fprintf(outfile, "   mirror = (%g,%g,%g),\n",
                 ks * color->x, ks * color->y, ks * color->z);
         fprintf(outfile, "   trans = (%g,%g,%g), index = %g,\n",
                 kt * color->x, kt * color->y, kt * color->z, i_of_r);
         fprintf(outfile, "   dither = 0 )\n");
         break;
      case OUTPUT_RAYSHADE:
         txname = create_surface_name(name, texture_count);
         fprintf(outfile, "surface %s\n", txname);
         fprintf(outfile, "   ambient %g %g %g\n",
                ka * color->x, ka * color->y, ka * color->z);
         fprintf(outfile, "   diffuse %g %g %g\n",
                kd * color->x, kd * color->y, kd * color->z);
         if (shine != 0) {
            fprintf(outfile, "   specular %g %g %g\n", shine, shine, shine);
            fprintf(outfile, "   specpow %g\n", phong_pow);
            }
         if (ks != 0)
            fprintf(outfile, "   reflect %g\n", ks);
         if (kt != 0)
            fprintf(outfile, "   transp %g index %g\n", kt, i_of_r);
         break;
      case OUTPUT_RTRACE:
	 if (shine > 0 && ks == 0.0) ks = shine;
	 fprintf(outfile, "1 %g %g %g %g %g %g %g %g %g %g 0 %g %g %g\n",
		 color->x, color->y, color->z,
		 kd, kd, kd,
		 ks, ks, ks,
		 (phong_pow > 100.0 ? 100.0 : phong_pow),
		 kt, kt, kt);
	 break;
      }

   /* Stash away the current texture name */
   texture_name = txname;

   return txname;
}

static void
lib_output_polygon_cylcone(base_pt, apex_pt)
   COORD4 *base_pt, *apex_pt;
{
   double  angle, delta_angle, divisor;
   COORD4  axis, dir, norm_axis, start_norm;
   COORD4  norm[4], vert[4], start_radius[4];
   MATRIX  mx;
   int     i;

   SUB3_COORD(axis, (*apex_pt), (*base_pt));
   COPY_COORD(norm_axis, axis);
   lib_normalize_coord3(&norm_axis);

   SET_COORD(dir, 0, 0, 1);
   CROSS(start_norm, axis, dir);
   start_norm.w = 0.0;

   divisor = lib_normalize_coord3(&start_norm);
   if (ABS(divisor) < EPSILON) {
      SET_COORD(dir, 1, 0, 0);
      CROSS(start_norm, axis, dir);
      lib_normalize_coord3(&start_norm);
      }

   start_radius[0].x = start_norm.x * base_pt->w;
   start_radius[0].y = start_norm.y * base_pt->w;
   start_radius[0].z = start_norm.z * base_pt->w;
   start_radius[0].w = 0.0;
   ADD3_COORD(vert[0], (*base_pt), start_radius[0]);

   start_radius[1].x = start_norm.x * apex_pt->w;
   start_radius[1].y = start_norm.y * apex_pt->w;
   start_radius[1].z = start_norm.z * apex_pt->w;
   start_radius[1].w = 0.0;
   ADD3_COORD(vert[1], (*apex_pt), start_radius[1]);

   COPY_COORD4(norm[0], start_norm);
   COPY_COORD4(norm[1], start_norm);

   delta_angle = 2.0 * PI / (double)(2*u_resolution);
   for (i=1,angle=delta_angle;i<=2*u_resolution;++i,angle+=delta_angle) {
      lib_create_axis_rotate_matrix(mx, &norm_axis, angle);

      lib_transform_coord(&vert[2], &start_radius[1], mx);
      ADD2_COORD(vert[2], *apex_pt);
      lib_transform_coord(&norm[2], &start_norm, mx);
      lib_output_polypatch(3, vert, norm);

      COPY_COORD4(vert[1], vert[2]);
      norm[1] = norm[2];
      lib_transform_coord(&vert[2], &start_radius[0], mx);
      ADD2_COORD(vert[2], *base_pt);
      lib_output_polypatch(3, vert, norm);

      COPY_COORD4(vert[0], vert[2]);
      COPY_COORD4(norm[0], norm[2]);
      }
}

/*
 * Output cylinder or cone.  A cylinder is defined as having a radius and an
 * axis defined by two points, which also define the top and bottom edge of the
 * cylinder.  A cone is defined similarly, the difference being that the apex
 * and base radii are different.  The apex radius is defined as being smaller
 * than the base radius.  Note that the surface exists without endcaps.
 *
 * If format=OUTPUT_CURVES, output the cylinder/cone in format:
 *     "c"
 *     base.x base.y base.z base_radius
 *     apex.x apex.y apex.z apex_radius
 *
 * If the format=OUTPUT_POLYGONS, the surface is polygonalized and output.
 * (4*OUTPUT_RESOLUTION) polygons are output as rectangles by
 * lib_output_polypatch.
 */
void
lib_output_cylcone(base_pt, apex_pt, curve_format)
   COORD4 *base_pt, *apex_pt;
   int curve_format;
{
   object_ptr new_object;
   double  angle;
   COORD4  axis;
   double  len, cottheta, xang, yang;

   if (format == OUTPUT_DELAYED) {
      /* Save all the pertinent information */
      new_object = (object_ptr)malloc(sizeof(struct object_struct));
      if (new_object == NULL)
	 /* Quietly fail */
	 return;
      new_object->object_type  = CONE_OBJ;
      new_object->curve_format = curve_format;
      new_object->surf_index   = texture_count;
      COPY_COORD4(new_object->object_data.cone.apex_pt, *apex_pt);
      COPY_COORD4(new_object->object_data.cone.base_pt, *base_pt);
      new_object->next_object = lib_objects;
      lib_objects = new_object;
      }
   else if (curve_format == OUTPUT_CURVES) {
      switch (format) {
         case OUTPUT_VIDEO:
	 case OUTPUT_PLG:
            lib_output_polygon_cylcone(base_pt, apex_pt);
            break;
         case OUTPUT_NFF:
            fprintf(outfile, "c\n" ) ;
            fprintf(outfile, "%g %g %g %g\n",
                    base_pt->x, base_pt->y, base_pt->z, base_pt->w ) ;
            fprintf(outfile, "%g %g %g %g\n",
                    apex_pt->x, apex_pt->y, apex_pt->z, apex_pt->w ) ;
            break;
         case OUTPUT_POVRAY:
            /* Since POV-Ray uses infinite primitives, we will start
               with a cone aligned with the z-axis (QCone_Z) and figure
               out how to clip and scale it to match what we want */
            if (apex_pt->w < base_pt->w) {
                /* Put the bigger end at the top */
                COPY_COORD4(axis, *base_pt);
                COPY_COORD4(*base_pt, *apex_pt);
                COPY_COORD4(*apex_pt, axis);
                }
            /* Find the axis and axis length */
            SUB3_COORD(axis, *apex_pt, *base_pt);
            len = lib_normalize_coord3(&axis);
            if (len < EPSILON)
               /* Degenerate cone/cylinder */
               break;
            if (ABS(apex_pt->w - base_pt->w) < 0.000001) {
               /* Treat this thing as a cylinder */
               cottheta = 1.0;
               fprintf(outfile, "object {quadric{<1 1 0><0 0 0><0 0 0>-1}\n");
               }
            else {
               /* Determine alignment */
               cottheta = len / (apex_pt->w - base_pt->w);
               fprintf(outfile, "object {quadric{<1 1 -1><0 0 0><0 0 0>0}\n");
               }
            fprintf(outfile, "   clipped_by {\n");
            fprintf(outfile, "      intersection {\n");
            fprintf(outfile, "         plane { <0 0 -1> %g }\n", -base_pt->w);
            fprintf(outfile, "         plane { <0 0  1> %g }\n", apex_pt->w);
            fprintf(outfile, "         } }\n");
            fprintf(outfile, "   translate <0 0 %g>\n", -base_pt->w);
            fprintf(outfile, "   scale <1 1 %g>\n", cottheta);
            len = sqrt(axis.x * axis.x + axis.z * axis.z);
            xang = -180.0 * asin(axis.y) / PI;
            yang = 180.0 * acos(axis.z / len) / PI;
            if (axis.x < 0)
               yang = -yang;
            fprintf(outfile, "   rotate <%g %g 0>\n", xang, yang);
            fprintf(outfile, "   translate <%g %g %g>\n",
                    base_pt->x, base_pt->y, base_pt->z);
            if (texture_name != NULL)
               fprintf(outfile, "   texture { %s }", texture_name);
            fprintf(outfile, "   }\n");
            break;
         case OUTPUT_POVRAY_15:
            fprintf(outfile, "object {\n");
            fprintf(outfile, "   cone { apex <%g, %g, %g> apex_radius %g\n",
                    apex_pt->x, apex_pt->y, apex_pt->z, apex_pt->w);
            fprintf(outfile, "          base <%g, %g, %g> base_radius %g }\n",
                    base_pt->x, base_pt->y, base_pt->z, base_pt->w);
            if (texture_name != NULL)
               fprintf(outfile, "   texture { %s }", texture_name);
            fprintf(outfile, "   }\n");
            break;
         case OUTPUT_POLYRAY:
            fprintf(outfile, "object {");
            if (base_pt->w == apex_pt->w)
               fprintf(outfile, " cylinder <%g, %g, %g>, <%g, %g, %g>, %g",
                      base_pt->x, base_pt->y, base_pt->z,
                      apex_pt->x, apex_pt->y, apex_pt->z, apex_pt->w);
            else
               fprintf(outfile, " cone <%g, %g, %g>, %g, <%g, %g, %g>, %g",
                      base_pt->x, base_pt->y, base_pt->z, base_pt->w,
                      apex_pt->x, apex_pt->y, apex_pt->z, apex_pt->w);
            if (texture_name != NULL)
               fprintf(outfile, " %s", texture_name);
            fprintf(outfile, " }\n");
            break;
         case OUTPUT_VIVID:
            fprintf(outfile, "cone = {");
            fprintf(outfile, " base = %g %g %g; base_radius = %g;\n",
                    base_pt->x, base_pt->y, base_pt->z, base_pt->w);
            fprintf(outfile, " apex = %g %g %g; apex_radius = %g;\n",
                    apex_pt->x, apex_pt->y, apex_pt->z, apex_pt->w);
            fprintf(outfile, "   }\n");
            break;
         case OUTPUT_QRT:
            fprintf(outfile, "BEGIN_BBOX\n");
            lib_output_polygon_cylcone(base_pt, apex_pt);
            fprintf(outfile, "END_BBOX\n");
            break;
         case OUTPUT_RAYSHADE:
            fprintf(outfile, "cone ");
            if (texture_name != NULL)
               fprintf(outfile, "%s ", texture_name);
            fprintf(outfile, " %g %g %g %g %g %g %g %g\n",
                    base_pt->w, base_pt->x, base_pt->y, base_pt->z,
                    apex_pt->w, apex_pt->x, apex_pt->y, apex_pt->z);
            break;
	 case OUTPUT_RTRACE:
	    fprintf(outfile, "4 %d %g %g %g %g %g %g %g %g %g\n",
		    texture_count, texture_ior,
		    base_pt->x, base_pt->y, base_pt->z, base_pt->w,
		    apex_pt->x, apex_pt->y, apex_pt->z, apex_pt->w);
	    break;
         }
      }
   else
      lib_output_polygon_cylcone(base_pt, apex_pt);
}

static void
disc_evaluator(trans, theta, v, r, vert)
   MATRIX trans;
   double theta, v, r;
   COORD4 *vert;
{
   COORD4 tvert;

   /* Compute the position of the point */
   SET_COORD4(tvert, (r + v) * cos(theta), (r + v) * sin(theta), 0, 1.0);
   lib_transform_coord(vert, &tvert, trans);
}

static void
lib_output_polygon_disc(center, normal, iradius, oradius)
   COORD4 *center, *normal;
   double iradius, oradius;
{
   double  divisor, u, v, delta_u, delta_v;
   MATRIX mx, imx;
   int i, j;
   COORD4 norm, vert[4];

   COPY_COORD4(norm, *normal);
   if ((divisor = lib_normalize_coord3(&norm)) < EPSILON) {
      fprintf(stderr, "Bad disc normal\n");
      exit(1);
      }
   lib_create_canonical_matrix(mx, imx, center, &norm);
   delta_u = 2.0 * PI / (double)(4 * u_resolution);
   delta_v = (oradius - iradius) / (double)(v_resolution);

   /* Dump out polygons */
   for (i=0,u=0.0;i<4*u_resolution;i++,u+=delta_u) {
      for (j=0,v=0.0;j<v_resolution;j++,v+=delta_v) {
         disc_evaluator(imx, u, v, iradius, &vert[0]);
         disc_evaluator(imx, u+delta_u, v, iradius, &vert[1]);
         disc_evaluator(imx, u+delta_u, v+delta_v, iradius, &vert[2]);
         lib_output_polygon(3, vert);
         vert[1] = vert[2];
         disc_evaluator(imx, u, v+delta_v, iradius, &vert[2]);
         lib_output_polygon(3, vert);
         }
      }
}

void
lib_output_disc(center, normal, iradius, oradius, curve_format)
   COORD4 *center, *normal;
   double iradius, oradius;
   int curve_format;
{
   object_ptr new_object;
   double  angle;
   COORD4  axis, base, apex;
   double  len, cottheta, xang, yang;

   if (format == OUTPUT_DELAYED) {
      /* Save all the pertinent information */
      new_object = (object_ptr)malloc(sizeof(struct object_struct));
      if (new_object == NULL)
	 /* Quietly fail */
	 return;
      new_object->object_type  = DISC_OBJ;
      new_object->curve_format = curve_format;
      new_object->surf_index   = texture_count;
      COPY_COORD4(new_object->object_data.disc.center, *center);
      COPY_COORD4(new_object->object_data.disc.normal, *normal);
      new_object->object_data.disc.iradius = iradius;
      new_object->object_data.disc.iradius = oradius;
      new_object->next_object = lib_objects;
      lib_objects = new_object;
      }
   else if (curve_format == OUTPUT_CURVES) {
      switch (format) {
         case OUTPUT_VIDEO:
         case OUTPUT_NFF:
	 case OUTPUT_PLG:
            lib_output_polygon_disc(center, normal, iradius, oradius);
            break;
         case OUTPUT_POVRAY:
            /* A disc is a plane intersected with either one or two spheres */
            COPY_COORD(axis, *normal);
            len = lib_normalize_coord3(&axis);
            fprintf(outfile, "object { plane { <0 0 1> 1}\n");
            fprintf(outfile, "   clipped_by {\n");
            if (iradius > 0.0) {
               fprintf(outfile, "      intersection {\n");
               fprintf(outfile, "         sphere { <0 0 0> %g inverse }\n",
		       iradius);
               fprintf(outfile, "         sphere { <0 0  1> %g }\n", oradius);
               fprintf(outfile, "         } }\n");
               }
            else {
               fprintf(outfile, "      object { sphere { <0 0 0> %g } }\n",
		       oradius);
               }
            len = sqrt(axis.x * axis.x + axis.z * axis.z);
            xang = -180.0 * asin(axis.y) / PI;
            yang = 180.0 * acos(axis.z / len) / PI;
            if (axis.x < 0)
               yang = -yang;
            fprintf(outfile, "   rotate <%g %g 0>\n", xang, yang);
            fprintf(outfile, "   translate <%g %g %g>\n",
                    center->x, center->y, center->z);
            if (texture_name != NULL)
               fprintf(outfile, "   texture { %s }", texture_name);
            fprintf(outfile, "   }\n");
            break;
         case OUTPUT_POVRAY_15:
            lib_output_polygon_disc(center, normal, iradius, oradius);
            break;
         case OUTPUT_POLYRAY:
            fprintf(outfile, "object { disc <%g, %g, %g>, ",
                    center->x, center->y, center->z);
            fprintf(outfile, "<%g, %g, %g>, ",
                    normal->x, normal->y, normal->z);
            if (iradius > 0.0)
               fprintf(outfile, "%g, ", iradius);
            fprintf(outfile, "%g ", oradius);
            if (texture_name != NULL)
               fprintf(outfile, " %s", texture_name);
            fprintf(outfile, " }\n");
            break;
         case OUTPUT_VIVID:
            lib_output_polygon_disc(center, normal, iradius, oradius);
            break;
         case OUTPUT_QRT:
            fprintf(outfile, "BEGIN_BBOX\n");
            lib_output_polygon_disc(center, normal, iradius, oradius);
            fprintf(outfile, "END_BBOX\n");
            break;
         case OUTPUT_RAYSHADE:
            lib_output_polygon_disc(center, normal, iradius, oradius);
            break;
	 case OUTPUT_RTRACE:
	    COPY_COORD(base, *center);
	    base.w = iradius;
	    apex.x = center->x + normal->x * EPSILON;
	    apex.y = center->y + normal->y * EPSILON;
	    apex.z = center->z + normal->z * EPSILON;
	    apex.w = oradius;
            lib_output_cylcone(&base, &apex, curve_format);
	    break;
         }
      }
   else
      lib_output_polygon_disc(center, normal, iradius, oradius);
}

static void
sq_sphere_val(a1, a2, a3, n, e, u, v, P)
   double a1, a2, a3, n, e, u, v;
   COORD4 *P;
{
   double cu, su, cv, sv;

   cu = cos(u); su = sin(u);
   cv = cos(v); sv = sin(v);
   P->x = a1 * POW(ABS(cv), n) * POW(ABS(cu), e) * SGN(cv) * SGN(cu);
   P->y = a2 * POW(ABS(cv), n) * POW(ABS(su), e) * SGN(cv) * SGN(su);
   P->z = a3 * POW(ABS(sv), n) * SGN(sv);
}

static void
sq_sphere_norm(a1, a2, a3, n, e, u, v, N)
   double a1, a2, a3, n, e, u, v;
   COORD4 *N;
{
   double cu, su, cv, sv;

   cu = cos(u); su = sin(u);
   cv = cos(v); sv = sin(v);

   /* May be some singularities in the values, lets catch them & put
      a fudged normal into N */
   if (e < 2 || n < 2) {
      if (ABS(cu) < 1.0e-3 || ABS(su) < 1.0e-3 ||
          ABS(cu) < 1.0e-3 || ABS(su) < 1.0e-3) {
         SET_COORD(*N, cu*cv, su*cv, sv);
         lib_normalize_coord3(N);
         return;
         }
      }

   N->x = a1 * POW(ABS(cv), 2-n) * POW(ABS(cu), 2-e) * SGN(cv) * SGN(cu);
   N->y = a2 * POW(ABS(cv), 2-n) * POW(ABS(su), 2-e) * SGN(cv) * SGN(su);
   N->z = a3 * POW(ABS(sv), 2-n) * SGN(sv);
   lib_normalize_coord3(N);
}

void
lib_output_sq_sphere(center_pt, a1, a2, a3, n, e)
   COORD4 *center_pt;
   double a1, a2, a3, n, e;
{
   object_ptr new_object;
   int i, j, u_res, v_res;
   double u, delta_u, v, delta_v;
   COORD4 verts[4], norms[4];

   if (format == OUTPUT_DELAYED) {
      /* Save all the pertinent information */
      new_object = (object_ptr)malloc(sizeof(struct object_struct));
      if (new_object == NULL)
	 /* Quietly fail */
	 return;
      new_object->object_type  = SUPERQ_OBJ;
      new_object->curve_format = OUTPUT_PATCHES;
      new_object->surf_index   = texture_count;
      COPY_COORD4(new_object->object_data.superq.center_pt, *center_pt);
      new_object->object_data.superq.a1 = a1;
      new_object->object_data.superq.a2 = a2;
      new_object->object_data.superq.a3 = a3;
      new_object->object_data.superq.n  = n;
      new_object->object_data.superq.e  = e;
      new_object->next_object = lib_objects;
      lib_objects = new_object;
      return;
      }

   u_res = 4 * u_resolution;
   v_res = 4 * v_resolution;
   delta_u = 2.0 * PI / (double)u_res;
   delta_v = PI / (double)v_res;

   for (i=0,u=0.0;i<u_res;i++,u+=delta_u) {
      for (j=0,v=-PI/2.0;j<v_res;j++,v+=delta_v) {
         if (j == 0) {
            sq_sphere_val(a1, a2, a3, n, e, u, v, &verts[0]);
            sq_sphere_norm(a1, a2, a3, n, e, u, v, &norms[0]);
            sq_sphere_val(a1, a2, a3, n, e, u, v+delta_v, &verts[1]);
            sq_sphere_norm(a1, a2, a3, n, e, u, v+delta_v, &norms[1]);
            sq_sphere_val(a1, a2, a3, n, e, u+delta_u, v+delta_v, &verts[2]);
            sq_sphere_norm(a1, a2, a3, n, e, u+delta_u, v+delta_v, &norms[2]);
            ADD3_COORD(verts[0], verts[0], *center_pt);
            ADD3_COORD(verts[1], verts[1], *center_pt);
            ADD3_COORD(verts[2], verts[2], *center_pt);
            lib_output_polypatch(3, verts, norms);
            }
         else if (j == v_res-1) {
            sq_sphere_val(a1, a2, a3, n, e, u, v, &verts[0]);
            sq_sphere_norm(a1, a2, a3, n, e, u, v, &norms[0]);
            sq_sphere_val(a1, a2, a3, n, e, u, v+delta_v, &verts[1]);
            sq_sphere_norm(a1, a2, a3, n, e, u, v+delta_v, &norms[1]);
            sq_sphere_val(a1, a2, a3, n, e, u+delta_u, v, &verts[2]);
            sq_sphere_norm(a1, a2, a3, n, e, u+delta_u, v, &norms[2]);
            ADD3_COORD(verts[0], verts[0], *center_pt);
            ADD3_COORD(verts[1], verts[1], *center_pt);
            ADD3_COORD(verts[2], verts[2], *center_pt);
            lib_output_polypatch(3, verts, norms);
            }
         else {
            sq_sphere_val(a1, a2, a3, n, e, u, v, &verts[0]);
            sq_sphere_norm(a1, a2, a3, n, e, u, v, &norms[0]);
            sq_sphere_val(a1, a2, a3, n, e, u, v+delta_v, &verts[1]);
            sq_sphere_norm(a1, a2, a3, n, e, u, v+delta_v, &norms[1]);
            sq_sphere_val(a1, a2, a3, n, e, u+delta_u, v+delta_v, &verts[2]);
            sq_sphere_norm(a1, a2, a3, n, e, u+delta_u, v+delta_v, &norms[2]);
            ADD3_COORD(verts[0], verts[0], *center_pt);
            ADD3_COORD(verts[1], verts[1], *center_pt);
            ADD3_COORD(verts[2], verts[2], *center_pt);
            lib_output_polypatch(3, verts, norms);
            COPY_COORD(verts[1], verts[2]);
            COPY_COORD(norms[1], norms[2]);
            sq_sphere_val(a1, a2, a3, n, e, u+delta_u, v, &verts[2]);
            sq_sphere_norm(a1, a2, a3, n, e, u+delta_u, v, &norms[2]);
            ADD3_COORD(verts[2], verts[2], *center_pt);
            lib_output_polypatch(3, verts, norms);
            }
         }
      }
}

static void
lib_output_polygon_sphere(center_pt)
   COORD4 *center_pt;
{
   double  angle;
   COORD4  edge_norm[3], edge_pt[3];
   long    num_face, num_edge, num_tri, num_vert;
   COORD4  *x_axis, *y_axis, **pt;
   COORD4  mid_axis;
   MATRIX  rot_mx;
   long    u_pol, v_pol;

   /* Allocate storage for the polygon vertices */
   x_axis = (COORD4 *)malloc((u_resolution+1) * sizeof(COORD4));
   y_axis = (COORD4 *)malloc((v_resolution+1) * sizeof(COORD4));
   pt     = (COORD4 **)malloc((u_resolution+1) * sizeof(COORD4 *));
   if (x_axis == NULL || y_axis == NULL || pt == NULL) {
      fprintf(stderr, "Failed to allocate polygon data\n");
      exit(1);
      }
   for (num_edge=0;num_edge<u_resolution+1;num_edge++) {
      pt[num_edge] = (COORD4 *)malloc((v_resolution+1) * sizeof(COORD4));
      if (pt[num_edge] == NULL) {
         fprintf(stderr, "Failed to allocate polygon data\n");
         exit(1);
         }
      }

   /* calculate axes used to find grid points */
   for (num_edge=0;num_edge<=u_resolution;++num_edge) {
      angle = (PI/4.0) * (2.0*(double)num_edge/u_resolution - 1.0);
      mid_axis.w = 0.0;

      mid_axis.x = 1.0; mid_axis.y = 0.0; mid_axis.z = 0.0;
      lib_create_rotate_matrix(rot_mx, Y_AXIS, angle);
      lib_transform_coord(&x_axis[num_edge], &mid_axis, rot_mx);
      }

   for (num_edge=0;num_edge<=v_resolution;++num_edge) {
      angle = (PI/4.0) * (2.0*(double)num_edge/v_resolution - 1.0);
      mid_axis.w = 0.0;

      mid_axis.x = 0.0; mid_axis.y = 1.0; mid_axis.z = 0.0;
      lib_create_rotate_matrix(rot_mx, X_AXIS, angle);
      lib_transform_coord(&y_axis[num_edge], &mid_axis, rot_mx);
      }

   /* set up grid of points on +Z sphere surface */
   for (u_pol=0;u_pol<=u_resolution;++u_pol) {
      for (v_pol=0;v_pol<=u_resolution;++v_pol) {
         CROSS(pt[u_pol][v_pol], x_axis[u_pol], y_axis[v_pol]);
         lib_normalize_coord3(&pt[u_pol][v_pol]);
         pt[u_pol][v_pol].w = 1.0;
         }
      }

   for (num_face=0;num_face<6;++num_face) {
      /* transform points to cube face */
      for (u_pol=0;u_pol<=u_resolution;++u_pol) {
         for (v_pol=0;v_pol<=v_resolution;++v_pol) {
            lib_rotate_cube_face(&pt[u_pol][v_pol], Z_AXIS, num_face);
            }
         }

      /* output grid */
      for (u_pol=0;u_pol<u_resolution;++u_pol) {
         for (v_pol=0;v_pol<v_resolution;++v_pol) {
            for (num_tri=0;num_tri<2;++num_tri) {
               for (num_edge=0;num_edge<3;++num_edge) {
                  num_vert = (num_tri*2 + num_edge) % 4;
                  if (num_vert == 0) {
                     COPY_COORD4(edge_pt[num_edge], pt[u_pol][v_pol]);
                     }
                  else if ( num_vert == 1 ) {
                     COPY_COORD4(edge_pt[num_edge], pt[u_pol][v_pol+1]);
                     }
                  else if ( num_vert == 2 ) {
                     COPY_COORD4(edge_pt[num_edge], pt[u_pol+1][v_pol+1]);
                     }
                  else {
                     COPY_COORD4(edge_pt[num_edge], pt[u_pol+1][v_pol]);
                     }
                  COPY_COORD4(edge_norm[num_edge], edge_pt[num_edge]);
                  edge_pt[num_edge].x = edge_pt[num_edge].x * center_pt->w +
                                        center_pt->x ;
                  edge_pt[num_edge].y = edge_pt[num_edge].y * center_pt->w +
                                        center_pt->y ;
                  edge_pt[num_edge].z = edge_pt[num_edge].z * center_pt->w +
                                        center_pt->z ;
   
                  }
               lib_output_polypatch(3, edge_pt, edge_norm);
               }
            }
         }
      }

   /* Release any memory used */
   for (num_edge=0;num_edge<u_resolution+1;num_edge++)
      free(pt[num_edge]);
   free(pt);
   free(y_axis);
   free(x_axis);
}

/*
 * Output sphere.  A sphere is defined by a radius and center position.
 *
 * If format=OUTPUT_CURVES, output the sphere in format:
 *     "s" center.x center.y center.z radius
 *
 * If the format=OUTPUT_POLYGONS, the sphere is polygonalized and output.
 * The sphere is polygonalized by splitting it into 6 faces (of a cube
 * projected onto the sphere) and dividing these faces by equally spaced
 * great circles.  OUTPUT_RESOLUTION affects the number of great circles.
 * (6*2*u_resolution*v_resolution) polygons are output as triangles
 * using lib_output_polypatch.
 */
void
lib_output_sphere(center_pt, curve_format)
   COORD4 *center_pt;
   int curve_format;
{
   object_ptr new_object;

   if (format == OUTPUT_DELAYED) {
      /* Save all the pertinent information */
      new_object = (object_ptr)malloc(sizeof(struct object_struct));
      if (new_object == NULL)
	 /* Quietly fail */
	 return;
      new_object->object_type  = SPHERE_OBJ;
      new_object->curve_format = curve_format;
      new_object->surf_index   = texture_count;
      COPY_COORD4(new_object->object_data.sphere.center_pt, *center_pt);
      new_object->next_object = lib_objects;
      lib_objects = new_object;
      }
   else if (curve_format == OUTPUT_CURVES) {
      switch (format) {
         case OUTPUT_VIDEO:
	 case OUTPUT_PLG:
            lib_output_polygon_sphere(center_pt);
            break;
         case OUTPUT_NFF:
            fprintf(outfile, "s %g %g %g %g\n",
                    center_pt->x, center_pt->y, center_pt->z, center_pt->w ) ;
            break;
         case OUTPUT_POVRAY:
            fprintf(outfile, "object { sphere { <%g %g %g> %g }",
                    center_pt->x, center_pt->y, center_pt->z, center_pt->w);
            if (texture_name != NULL)
               fprintf(outfile, " texture { %s }", texture_name);
            fprintf(outfile, " }\n");
            break;
         case OUTPUT_POVRAY_15:
            fprintf(outfile, "object { sphere { <%g, %g, %g>, %g }",
                    center_pt->x, center_pt->y, center_pt->z, center_pt->w);
            if (texture_name != NULL)
               fprintf(outfile, " texture { %s }", texture_name);
            fprintf(outfile, " }\n");
            break;
         case OUTPUT_POLYRAY:
            fprintf(outfile, "object { sphere <%g, %g, %g>, %g",
                    center_pt->x, center_pt->y, center_pt->z, center_pt->w);
            if (texture_name != NULL)
               fprintf(outfile, " %s", texture_name);
            fprintf(outfile, " }\n");
            break;
         case OUTPUT_VIVID:
            fprintf(outfile, "sphere = { center = %g %g %g; radius = %g; }\n",
                    center_pt->x, center_pt->y, center_pt->z, center_pt->w);
            break;
         case OUTPUT_QRT:
            fprintf(outfile, "sphere ( loc = (%g, %g, %g), radius = %g )\n",
                    center_pt->x, center_pt->y, center_pt->z, center_pt->w);
            break;
         case OUTPUT_RAYSHADE:
              fprintf(outfile, "sphere ");
              if (texture_name != NULL)
                 fprintf(outfile, "%s ", texture_name);
              fprintf(outfile, " %g %g %g %g\n",
                      center_pt->w, center_pt->x, center_pt->y, center_pt->z);
              break;
	 case OUTPUT_RTRACE:
	    fprintf(outfile, "1 %d %g %g %g %g %g\n",
		    texture_count, texture_ior,
		    center_pt->x, center_pt->y, center_pt->z, center_pt->w);
	    break;
         }
       }
    else
      lib_output_polygon_sphere(center_pt);
}

static unsigned hfcount = 0;

static char *
create_height_file(filename, height, width, data, type)
   char *filename;
   unsigned height, width;
   float **data;
   int type;
{
   FILE *file;
   float v;
   unsigned i, j;
   unsigned char r, g, b;
   unsigned char tgaheader[18];

   if (filename == NULL) {
      /* Need to create a new name for the height file */
      filename = malloc(10 * sizeof(char));
      if (filename == NULL) return NULL;
      sprintf(filename, "hf%03d.tga", hfcount++);
      }
   if ((file = fopen(filename, "wb")) == NULL) return NULL;
   if (type == 0) {
      /* Targa style height field for POV-Ray or Polyray */
      memset(tgaheader, 0, 18);
      tgaheader[2] = 2;
      tgaheader[12] = (unsigned char)(width & 0xFF);
      tgaheader[13] = (unsigned char)((width >> 8) & 0xFF);
      tgaheader[14] = (unsigned char)(height & 0xFF);
      tgaheader[15] = (unsigned char)((height >> 8) & 0xFF);
      tgaheader[16] = 24;
      tgaheader[17] = 0x20;
      fwrite(tgaheader, 18, 1, file);
      for (i=0;i<height;i++)
	 for (j=0;j<width;j++) {
	    v = data[i][j];
	    if (v < -128.0) v = -128.0;
	    if (v > 127.0) v = 127.0;
	    v += 128.0;
	    r = v;
	    v -= (float)r;
	    g = (unsigned char)(256.0 * v);
	    b = 0;
	    fputc(b, file);
	    fputc(g, file);
	    fputc(r, file);
	    }
      }
   else {
      /* Only square height fields in RayShade */
      if (height < width) width = height;
      else if (width < height) height = width;

      /* Start by storing the size as an int */
      fwrite(&height, sizeof(int), 1, file);

      /* Now store height values as native floats */
      for (i=0;i<height;i++)
	 for (j=0;j<width;j++)
	    fwrite(&data[i][j], sizeof(float), 1, file);
      }
   fclose(file);

   return filename;
}

static void
find_height_min_max(height, width, data, y0, y1)
   unsigned height, width;
   float **data;
   float *y0, *y1;
{
   unsigned i, j;
   float v;

   *y0 = *y1 = data[0][0];
   for (i=0;i<height;i++)
      for (j=0;j<width;j++) {
	 v = data[i][j];
	 if (v < *y0) *y0 = v;
	 if (v > *y1) *y1 = v;
	 }
}

static void
lib_output_polygon_height(height, width, data, x0, x1, y0, y1, z0, z1)
   unsigned height, width;
   float **data;
   float x0, x1, y0, y1, z0, z1;
{
   unsigned i, j;
   float xdelta, ydelta, zdelta;
   float xbase, ybase, zbase;
   float yscale, yt0, yt1;
   COORD4 verts[3];

   xdelta = (x1 - x0) / (float)(width - 1);
   zdelta = (z1 - z0) / (float)(height - 1);
   for (i=0;i<height-1;i++)
      for (j=0;j<width-1;j++) {
	 SET_COORD(verts[0], x0 + j * xdelta, y0 + data[i][j],
		   z0 + i * zdelta);
	 SET_COORD(verts[1], x0 + (j+1) * xdelta, y0 + data[i][j+1],
		   z0 + i * zdelta);
	 SET_COORD(verts[2], x0 + (j+1) * xdelta, y0 + data[i+1][j+1],
		   z0 + (i + 1) * zdelta);
	 lib_output_polygon(3, verts);
	 COPY_COORD(verts[1], verts[2]);
	 SET_COORD(verts[2], x0 + j * xdelta, y0 + data[i+1][j],
		   z0 + (i + 1) * zdelta);
	 lib_output_polygon(3, verts);
	 }
}

void
lib_output_height(char *filename, float **data,
		  unsigned height, unsigned width,
		  float x0, float x1, float y0, float y1, float z0, float z1)
   /*
   char *filename;
   float **data;
   unsigned height, width;
   float x0, x1;
   float y0, y1;
   float z0, z1;
   */
{
   object_ptr new_object;
   float yt0, yt1;

   if (format == OUTPUT_DELAYED) {
      filename = create_height_file(filename, height, width, data, 0);
      if (filename == NULL) return;

      /* Save all the pertinent information */
      new_object = (object_ptr)malloc(sizeof(struct object_struct));
      if (new_object == NULL)
	 /* Quietly fail */
	 return;
      new_object->object_type  = HEIGHT_OBJ;
      new_object->curve_format = OUTPUT_CURVES;
      new_object->surf_index   = texture_count;
      new_object->object_data.height.width = width;
      new_object->object_data.height.height = height;
      new_object->object_data.height.data = data;
      new_object->object_data.height.filename = filename;
      new_object->object_data.height.x0 = x0;
      new_object->object_data.height.x1 = x1;
      new_object->object_data.height.y0 = y0;
      new_object->object_data.height.y1 = y1;
      new_object->object_data.height.z0 = z0;
      new_object->object_data.height.z1 = z1;
      new_object->next_object = lib_objects;
      lib_objects = new_object;
      }
   else {
      switch (format) {
         case OUTPUT_VIDEO:
         case OUTPUT_NFF:
	 case OUTPUT_PLG:
         case OUTPUT_QRT:
	 case OUTPUT_RTRACE:
         case OUTPUT_VIVID:
	    lib_output_polygon_height(height, width, data,
				      x0, x1, y0, y1, z0, z1);
            break;
         case OUTPUT_POVRAY:
         case OUTPUT_POVRAY_15:
	    filename = create_height_file(filename, height, width, data, 0);
	    if (filename == NULL) return;
	    fprintf(outfile, "object { height_field { tga \"%s\" }", filename);
	    if (format == OUTPUT_POVRAY) {
	       fprintf(outfile, " scale <%g %g %g>",
		       fabs(x1 - x0), fabs(y1 - y0), fabs(z1 - z0));
	       fprintf(outfile, " translate <%g %g %g>", x0, y0, z0);
	       }
	    else {
	       fprintf(outfile, " scale <%g, %g, %g>",
		       fabs(x1 - x0), fabs(y1 - y0), fabs(z1 - z0));
	       fprintf(outfile, " translate <%g, %g, %g>", x0, y0, z0);
	       }
            if (texture_name != NULL)
               fprintf(outfile, " texture { %s }", texture_name);
            fprintf(outfile, " }\n");
            break;
         case OUTPUT_POLYRAY:
	    filename = create_height_file(filename, height, width, data, 0);
	    if (filename == NULL) return;
            fprintf(outfile, "object { height_field \"%s\" ", filename);
	    fprintf(outfile, "scale <%g, 1, %g> ", fabs(x1-x0), fabs(z1-z0));
	    fprintf(outfile, "translate <%g, %g, %g> ", x0, y0, z0);
            if (texture_name != NULL)
               fprintf(outfile, " %s", texture_name);
            fprintf(outfile, " }\n");
            break;
         case OUTPUT_RAYSHADE:
	    filename = create_height_file(filename, height, width, data, 1);
	    if (filename == NULL) return;
            fprintf(outfile, "heightfield ");
            if (texture_name != NULL)
               fprintf(outfile, " %s", texture_name);
	    fprintf(outfile, "\"%s\" ", filename);
	    fprintf(outfile, "rotate 1 0 0 90 ");
	    fprintf(outfile, "scale  %g 1 %g ", fabs(x1 - x0), fabs(z1 - z0));
	    fprintf(outfile, "translate  %g %g %g ", x0, y0, z0);
            fprintf(outfile, "\n");
	    break;
         }
      }
}

static void
torus_evaluator(trans, theta, phi, r0, r1, vert, norm)
   MATRIX trans;
   double theta, phi, r0, r1;
   COORD4 *vert, *norm;
{
   COORD4 v0, v1, tvert, tnorm;

   /* Compute the position of the point */
   SET_COORD4(tvert, (r0 + r1 * sin(theta)) * cos(phi),
                    (r0 + r1 * sin(theta)) * sin(phi),
                    r1 * cos(theta), 1.0);
   /* Compute the normal at that point */
   SET_COORD(v0, r1*cos(theta)*cos(phi),
                 r1*cos(theta)*sin(phi),
                 -r1*sin(theta));
   SET_COORD(v1, -(r0+r1*sin(theta))*sin(phi),
                 (r0+r1*sin(theta))*cos(phi),
                 0.0);
   CROSS(tnorm, v0, v1);
   tnorm.w = 0.0;
   lib_normalize_coord3(&tnorm);
   lib_transform_coord(vert, &tvert, trans);
   lib_transform_coord(norm, &tnorm, trans);
}

static void
lib_output_polygon_torus(center, normal, iradius, oradius)
   COORD4 *center, *normal;
   double iradius, oradius;
{
   double  divisor, u, v, delta_u, delta_v;
   MATRIX mx, imx;
   int i, j;
   COORD4 vert[4], norm[4];

   if ((divisor = lib_normalize_coord3(normal)) < EPSILON) {
      fprintf(stderr, "Bad torus normal\n");
      exit(1);
      }
   lib_create_canonical_matrix(mx, imx, center, normal);
   delta_u = 2.0 * PI / (double)u_resolution;
   delta_v = 2.0 * PI / (double)v_resolution;

   /* Dump out polygons */
   for (i=0,u=0.0;i<u_resolution;i++,u+=delta_u) {
      for (j=0,v=0.0;j<v_resolution;j++,v+=delta_v) {
         torus_evaluator(imx, u, v,
                         iradius, oradius,
                         &vert[0], &norm[0]);
         torus_evaluator(imx, u, v+delta_v,
                         iradius, oradius,
                         &vert[1], &norm[1]);
         torus_evaluator(imx, u+delta_u, v+delta_v,
                         iradius, oradius,
                         &vert[2], &norm[2]);
         lib_output_polypatch(3, vert, norm);
         vert[1] = vert[2];
         norm[1] = norm[2];
         torus_evaluator(imx, u+delta_u, v,
                         iradius, oradius,
                         &vert[2], &norm[2]);
         lib_output_polypatch(3, vert, norm);
         }
      }
}

void
lib_output_torus(center, normal, iradius, oradius, curve_format)
   COORD4 *center, *normal;
   double iradius, oradius;
   int curve_format;
{
   object_ptr new_object;

   if (format == OUTPUT_DELAYED) {
      /* Save all the pertinent information */
      new_object = (object_ptr)malloc(sizeof(struct object_struct));
      if (new_object == NULL)
	 /* Quietly fail */
	 return;
      new_object->object_type  = TORUS_OBJ;
      new_object->curve_format = curve_format;
      new_object->surf_index   = texture_count;
      COPY_COORD4(new_object->object_data.torus.center, *center);
      COPY_COORD4(new_object->object_data.torus.normal, *normal);
      new_object->object_data.torus.iradius = iradius;
      new_object->object_data.torus.oradius = oradius;
      new_object->next_object = lib_objects;
      lib_objects = new_object;
      }
   else if (curve_format == OUTPUT_CURVES) {
      switch (format) {
         case OUTPUT_VIDEO:
         case OUTPUT_NFF:
         case OUTPUT_VIVID:
         case OUTPUT_QRT:
         case OUTPUT_POVRAY:
         case OUTPUT_POVRAY_15:
	 case OUTPUT_PLG:
	 case OUTPUT_RTRACE:
            lib_output_polygon_torus(center, normal, iradius, oradius);
            break;
         case OUTPUT_POLYRAY:
            fprintf(outfile, "object { torus %g, %g", iradius, oradius);
            fprintf(outfile, ", <%g, %g, %g>, <%g, %g, %g>",
                    center->x, center->y, center->z,
                    normal->x, normal->y, normal->z);
            if (texture_name != NULL)
               fprintf(outfile, " %s", texture_name);
            fprintf(outfile, " }\n");
            break;
         case OUTPUT_RAYSHADE:
              fprintf(outfile, "torus ");
              if (texture_name != NULL)
                 fprintf(outfile, "%s ", texture_name);
              fprintf(outfile, " %g %g %g %g\n",
                      iradius, center->x, center->y, center->z);
              break;
         }
       }
    else
      lib_output_polygon_torus(center, normal, iradius, oradius);
}

/* Generate a box as a set of 4-sided polygons */
static void
lib_output_polygon_box(p1, p2)
   COORD4 *p1, *p2;
{
   COORD4 box_verts[4];

   /* Sides */
   SET_COORD(box_verts[0], p1->x, p1->y, p1->z);
   SET_COORD(box_verts[1], p1->x, p2->y, p1->z);
   SET_COORD(box_verts[2], p1->x, p2->y, p2->z);
   SET_COORD(box_verts[3], p1->x, p1->y, p2->z);
   lib_output_polygon(4, box_verts);
   SET_COORD(box_verts[3], p2->x, p1->y, p1->z);
   SET_COORD(box_verts[2], p2->x, p2->y, p1->z);
   SET_COORD(box_verts[1], p2->x, p2->y, p2->z);
   SET_COORD(box_verts[0], p2->x, p1->y, p2->z);
   lib_output_polygon(4, box_verts);

   /* Front/Back */
   SET_COORD(box_verts[0], p1->x, p1->y, p1->z);
   SET_COORD(box_verts[1], p1->x, p2->y, p1->z);
   SET_COORD(box_verts[2], p2->x, p2->y, p1->z);
   SET_COORD(box_verts[3], p2->x, p1->y, p1->z);
   lib_output_polygon(4, box_verts);
   SET_COORD(box_verts[3], p1->x, p1->y, p2->z);
   SET_COORD(box_verts[2], p1->x, p2->y, p2->z);
   SET_COORD(box_verts[1], p2->x, p2->y, p2->z);
   SET_COORD(box_verts[0], p2->x, p1->y, p2->z);
   lib_output_polygon(4, box_verts);

   /* Top/Bottom */
   SET_COORD(box_verts[0], p1->x, p1->y, p1->z);
   SET_COORD(box_verts[1], p1->x, p1->y, p2->z);
   SET_COORD(box_verts[2], p2->x, p1->y, p2->z);
   SET_COORD(box_verts[3], p2->x, p1->y, p1->z);
   lib_output_polygon(4, box_verts);
   SET_COORD(box_verts[3], p1->x, p2->y, p1->z);
   SET_COORD(box_verts[2], p1->x, p2->y, p2->z);
   SET_COORD(box_verts[1], p2->x, p2->y, p2->z);
   SET_COORD(box_verts[0], p2->x, p2->y, p1->z);
   lib_output_polygon(4, box_verts);
}

/* Output box.  A box is defined by a diagonally opposite corners. */
void
lib_output_box(p1, p2)
   COORD4 *p1, *p2;
{
   object_ptr new_object;

   if (format == OUTPUT_DELAYED) {
      /* Save all the pertinent information */
      new_object = (object_ptr)malloc(sizeof(struct object_struct));
      if (new_object == NULL)
	 /* Quietly fail */
	 return;
      new_object->object_type  = BOX_OBJ;
      new_object->curve_format = OUTPUT_PATCHES;
      new_object->surf_index   = texture_count;
      COPY_COORD4(new_object->object_data.box.point1, *p1);
      COPY_COORD4(new_object->object_data.box.point2, *p2);
      new_object->next_object = lib_objects;
      lib_objects = new_object;
      }
   else {
      switch (format) {
	 case OUTPUT_VIDEO:
	 case OUTPUT_NFF:
	 case OUTPUT_VIVID:
	 case OUTPUT_PLG:
	    lib_output_polygon_box(p1, p2);
	    break;
	 case OUTPUT_POVRAY:
	    fprintf(outfile, "object { box { <%g %g %g> <%g %g %g> }",
		    p1->x, p1->y, p1->z, p2->x, p2->y, p2->z);
	    if (texture_name != NULL)
	       fprintf(outfile, " texture { %s }", texture_name);
	    fprintf(outfile, " }\n");
	    break;
	 case OUTPUT_POVRAY_15:
	    fprintf(outfile, "object { box { <%g, %g, %g>, <%g, %g, %g> }",
		    p1->x, p1->y, p1->z, p2->x, p2->y, p2->z);
	    if (texture_name != NULL)
	       fprintf(outfile, " texture { %s }", texture_name);
	    fprintf(outfile, " }\n");
	    break;
	 case OUTPUT_POLYRAY:
	    fprintf(outfile, "object { box <%g, %g, %g>, <%g, %g, %g>",
		    p1->x, p1->y, p1->z, p2->x, p2->y, p2->z);
	    if (texture_name != NULL)
	       fprintf(outfile, " %s", texture_name);
	    fprintf(outfile, " }\n");
	    break;
	 case OUTPUT_QRT:
	    fprintf(outfile, "BEGIN_BBOX\n");
	    lib_output_polygon_box(p1, p2);
	    fprintf(outfile, "END_BBOX\n");
	    break;
	 case OUTPUT_RAYSHADE:
	    fprintf(outfile, "box ");
	    if (texture_name != NULL)
	       fprintf(outfile, "%s ", texture_name);
	    fprintf(outfile, " %g %g %g %g %g %g\n",
		    p1->x, p1->y, p1->z, p2->x, p2->y, p2->z);
	    break;
	 case OUTPUT_RTRACE:
	    fprintf(outfile, "2 %d %g %g %g %g %g %g %g\n",
		    texture_count, texture_ior,
		    (p1->x + p2->x) / 2.0,
		    (p1->y + p2->y) / 2.0,
		    (p1->z + p2->z) / 2.0,
		    p2->x - p1->x, p2->y - p1->y, p2->z - p1->z);
	    break;
	 }
      }
}


/*
 * Output polygon.  A polygon is defined by a set of vertices.  With these
 * databases, a polygon is defined to have all points coplanar.  A polygon has
 * only one side, with the order of the vertices being counterclockwise as you
 * face the polygon (right-handed coordinate system).
 *
 * The output format is always:
 *     "p" total_vertices
 *     vert1.x vert1.y vert1.z
 *     [etc. for total_vertices polygons]
 *
 */
void
lib_output_polygon(tot_vert, vert)
   int tot_vert;
   COORD4 *vert;
{
   object_ptr new_object;
   int num_vert, i, j;
   COORD4 x, tvert[3], v0, v1;
   double w;

   /* First lets do a couple of checks to see if this is a valid polygon */
   for (i=0;i<tot_vert;) {
      /* If there are two adjacent coordinates that degenerate then
         collapse them down to one */
      SUB3_COORD(x, vert[i], vert[(i+1)%tot_vert]);
      if (lib_normalize_coord3(&x) < EPSILON) {
         for (j=i;j<tot_vert-1;j++)
            memcpy(&vert[j], &vert[j+1], sizeof(COORD4));
         tot_vert--;
         }
      else
         i++;
      }
   
   if (tot_vert < 3)
      /* No such thing as a poly that only has two sides */
      return;

   if (format == OUTPUT_DELAYED) {
      /* Save all the pertinent information */
      new_object = (object_ptr)malloc(sizeof(struct object_struct));
      new_object->object_data.polygon.vert =
	 (COORD4 *)malloc(tot_vert * sizeof(COORD4));
      if (new_object == NULL || new_object->object_data.polygon.vert == NULL)
	 /* Quietly fail */
	 return;
      new_object->object_type  = POLYGON_OBJ;
      new_object->curve_format = OUTPUT_PATCHES;
      new_object->surf_index   = texture_count;
      new_object->object_data.polygon.tot_vert = tot_vert;
      for (i=0;i<tot_vert;i++)
	 COPY_COORD4(new_object->object_data.polygon.vert[i], vert[i])
      new_object->next_object = lib_objects;
      lib_objects = new_object;
      }
   else {
      switch (format) {
	 case OUTPUT_VIDEO:
	    /* First make sure the display has been opened for drawing */
	    if (!view_init_flag) {
	       lib_create_view_matrix(view.tx, &view.from, &view.at,
				      &view.up, view.resx, view.resy,
				      view.angle, view.aspect);
	       display_init(view.resx, view.resy);
	       view_init_flag = 1;
	       }
	    /* Step through each segment of the polygon, projecting it
	       onto the screen. */
	    for (i=0;i<tot_vert;i++) {
	       COPY_COORD(tvert[0], vert[i]);
	       tvert[0].w = 1.0;
	       lib_transform_coord(&v0, &tvert[0], view.tx);
	       COPY_COORD(tvert[1], vert[(i+1)%tot_vert]);
	       tvert[1].w = 1.0;
	       lib_transform_coord(&v1, &tvert[1], view.tx);
	       /* Do the perspective transform on the points */
	       v0.x /= v0.w; v0.y /= v0.w;
	       v1.x /= v1.w; v1.y /= v1.w;
	       if (lib_clip_to_box(&v0, &v1, view_bounds))
		  display_line((int)v0.x, (int)v0.y, (int)v1.x, (int)v1.y,
			       &fg_color);
	       }
	    break;
	 case OUTPUT_NFF:
	    fprintf(outfile, "p %d\n", tot_vert);
	    for (num_vert=0;num_vert<tot_vert;++num_vert)
	       fprintf(outfile, "%g %g %g\n",
		       vert[num_vert].x, vert[num_vert].y, vert[num_vert].z);
	   break;
	 case OUTPUT_POVRAY:
	 case OUTPUT_POVRAY_15:
	    /* No support in POV-Ray for an arbitrary polygon, we make the
	       assumption that |hs polygon is convex, and split it into
	       triangles */
	    COPY_COORD(tvert[0], vert[0]);
	    for (i=1;i<tot_vert-1;i++) {
	       COPY_COORD(tvert[1], vert[i]);
	       COPY_COORD(tvert[2], vert[i+1]);
	       fprintf(outfile, "object { triangle { ");
	       for (num_vert=0;num_vert<3;++num_vert)
		  if (format == OUTPUT_POVRAY)
		     fprintf(outfile, "<%g %g %g> ",
			    tvert[num_vert].x, tvert[num_vert].y,
			    tvert[num_vert].z);
		  else {
		     fprintf(outfile, "<%g, %g, %g>",
			    tvert[num_vert].x, tvert[num_vert].y,
			    tvert[num_vert].z);
		     if (num_vert < 2)
			fprintf(outfile, ",");
		     fprintf(outfile, " ");
		     }
	       if (texture_name != NULL)
		  fprintf(outfile, "} texture { %s }", texture_name);
	       fprintf(outfile, " }\n");
	       }
	    break;
	 case OUTPUT_POLYRAY:
	    fprintf(outfile, "object { polygon %d,", tot_vert);
	    for (num_vert = 0; num_vert < tot_vert; num_vert++) {
	       fprintf(outfile, " <%g, %g, %g>",
		      vert[num_vert].x, vert[num_vert].y, vert[num_vert].z);
	       if (num_vert < tot_vert-1)
		  fprintf(outfile, ", ");
	       }
	    if (texture_name != NULL)
	       fprintf(outfile, " %s", texture_name);
	    fprintf(outfile, " }\n");
	    break;
	 case OUTPUT_VIVID:
	    fprintf(outfile, "polygon = { points = %d;", tot_vert);
	    for (num_vert = 0; num_vert < tot_vert; num_vert++)
	       /* Vivid has problems with very long input lines, so in order
		  to handle polygons with many vertices, we split the vertices
		  one to a line. */
	       fprintf(outfile, " vertex = %g %g %g;\n",
		      vert[num_vert].x, vert[num_vert].y, vert[num_vert].z);
	    fprintf(outfile, " }\n");
	    break;
	 case OUTPUT_QRT:
	    /* No support in QRT for an arbitrary polygon, we make the
	       assumption that ths polygon is convex, and split it into
	       triangles */
	    COPY_COORD(tvert[0], vert[0]);
	    for (i=1;i<tot_vert-1;i++) {
	       COPY_COORD(tvert[1], vert[i]);
	       COPY_COORD(tvert[2], vert[i+1]);
	       fprintf(outfile, "TRIANGLE ( ");
	       fprintf(outfile, "loc = (%g, %g, %g), ",
			 tvert[0].x, tvert[0].y, tvert[0].z);
	       fprintf(outfile, "vect1 = (%g, %g, %g), ",
			 tvert[1].x - tvert[0].x, tvert[1].y - tvert[0].y,
			 tvert[1].z - tvert[0].z);
	       fprintf(outfile, "vect2 = (%g, %g, %g) ",
			 tvert[2].x - tvert[0].x, tvert[2].y - tvert[0].y,
			 tvert[2].z - tvert[0].z);
	       fprintf(outfile, " );\n");
	       }
	    break;
	 case OUTPUT_RAYSHADE:
	    fprintf(outfile, "polygon ");
	    if (texture_name != NULL)
	       fprintf(outfile, "%s ", texture_name);
	    for (num_vert=0;num_vert<tot_vert;num_vert++)
	       fprintf(outfile, "%g %g %g ",
		       vert[num_vert].x, vert[num_vert].y, vert[num_vert].z);
	    fprintf(outfile, "\n");
	    break;
	 case OUTPUT_RTRACE:
	    fprintf(outfile, "5 %d %g 0 0 0 1 1 1 -\n",
		    texture_count, texture_ior);
	    fprintf(outfile, "%d ", tot_vert);
	    for (num_vert=0;num_vert<tot_vert;num_vert++)
	       fprintf(outfile, "%d ", num_vert+1);
	    fprintf(outfile, "\n\n");
	    for (num_vert=0;num_vert<tot_vert;num_vert++)
	       fprintf(outfile, "%g %g %g\n",
		       vert[num_vert].x, vert[num_vert].y, vert[num_vert].z);
	    fprintf(outfile, "\n");
	    break;
	 case OUTPUT_PLG:
	    /* Simply add this polygon to the stack */
	    new_object = (object_ptr)malloc(sizeof(struct object_struct));
	    new_object->object_data.polygon.vert =
	       (COORD4 *)malloc(tot_vert * sizeof(COORD4));
	    if (new_object == NULL ||
	        new_object->object_data.polygon.vert == NULL)
	       return;
	    new_object->object_type  = POLYGON_OBJ;
	    new_object->curve_format = OUTPUT_PATCHES;
	    new_object->surf_index   = texture_count;
	    new_object->object_data.polygon.tot_vert = tot_vert;
	    for (i=0;i<tot_vert;i++)
	       COPY_COORD4(new_object->object_data.polygon.vert[i], vert[i])
	    new_object->next_object = polygon_stack;
	    polygon_stack = new_object;
	    break;
	 }
      }
}


/*
 * Output polygonal patch.  A patch is defined by a set of vertices and their
 * normals.  With these databases, a patch is defined to have all points
 * coplanar.  A patch has only one side, with the order of the vertices being
 * counterclockwise as you face the patch (right-handed coordinate system).
 *
 * The output format is always:
 *     "pp" total_vertices
 *     vert1.x vert1.y vert1.z norm1.x norm1.y norm1.z
 *     [etc. for total_vertices polygonal patches]
 *
 */
void
lib_output_polypatch(tot_vert, vert, norm)
   int tot_vert;
   COORD4 *vert, *norm;
{
   object_ptr new_object;
   int i, num_vert;

   if (format == OUTPUT_DELAYED) {
      /* Save all the pertinent information */
      new_object = (object_ptr)malloc(sizeof(struct object_struct));
      new_object->object_data.polypatch.vert =
	 (COORD4 *)malloc(tot_vert * sizeof(COORD4));
      new_object->object_data.polypatch.norm =
	 (COORD4 *)malloc(tot_vert * sizeof(COORD4));
      if (new_object == NULL ||
	  new_object->object_data.polypatch.vert == NULL ||
	  new_object->object_data.polypatch.norm == NULL)
	 /* Quietly fail */
	 return;
      new_object->object_type  = POLYPATCH_OBJ;
      new_object->curve_format = OUTPUT_PATCHES;
      new_object->surf_index   = texture_count;
      new_object->object_data.polypatch.tot_vert = tot_vert;
      for (i=0;i<tot_vert;i++) {
	 COPY_COORD4(new_object->object_data.polypatch.vert[i], vert[i])
	 COPY_COORD4(new_object->object_data.polypatch.norm[i], norm[i])
	 }
      }
   else {
      switch (format) {
	 case OUTPUT_VIDEO:
	    lib_output_polygon(tot_vert, vert);
	    break;
	 case OUTPUT_NFF:
	    fprintf(outfile, "pp %d\n", tot_vert);
	    for (num_vert=0;num_vert<tot_vert;++num_vert) {
		fprintf(outfile, "%g %g %g %g %g %g\n",
			vert[num_vert].x, vert[num_vert].y, vert[num_vert].z,
			norm[num_vert].x, norm[num_vert].y, norm[num_vert].z);
		}
	    break;
	 case OUTPUT_POVRAY:
	 case OUTPUT_POVRAY_15:
	    if (tot_vert != 3) {
	       fprintf(outfile, "Smooth triangles must have 3 vertices\n");
	       exit(1);
	       }
	    fprintf(outfile, "object { smooth_triangle {");
	    for (num_vert=0;num_vert<tot_vert;++num_vert)
	       if (format == OUTPUT_POVRAY)
		  fprintf(outfile, "<%g %g %g> <%g %g %g> ",
			 vert[num_vert].x, vert[num_vert].y, vert[num_vert].z,
			 norm[num_vert].x, norm[num_vert].y, norm[num_vert].z);
	       else {
		  fprintf(outfile, "<%g, %g, %g>, <%g, %g, %g>",
			 vert[num_vert].x, vert[num_vert].y, vert[num_vert].z,
			 norm[num_vert].x, norm[num_vert].y, norm[num_vert].z);
		  if (num_vert < 2)
		     fprintf(outfile, ",");
		  fprintf(outfile, " ");
		  }
	    if (format == OUTPUT_POVRAY_15)
	       fprintf(outfile, " }");
	    if (texture_name != NULL)
	       fprintf(outfile, " texture { %s }", texture_name);
	    fprintf(outfile, " }\n");
	    break;
	 case OUTPUT_POLYRAY:
	    if (tot_vert != 3) {
	       fprintf(outfile, "Patches must have 3 vertices, input was %d\n",
		       tot_vert);
	       exit(1);
	       }
	    fprintf(outfile, "object { patch ");
	    for (num_vert=0;num_vert<tot_vert;++num_vert) {
	       fprintf(outfile, "<%g, %g, %g>, <%g, %g, %g>",
		      vert[num_vert].x, vert[num_vert].y, vert[num_vert].z,
		      norm[num_vert].x, norm[num_vert].y, norm[num_vert].z);
	       if (num_vert<2)
		  fprintf(outfile, ", ");
	       }
	    if (texture_name != NULL)
	       fprintf(outfile, " %s", texture_name);
	    fprintf(outfile, " }\n");
	    break;
	 case OUTPUT_VIVID:
	    if (tot_vert != 3) {
	       fprintf(outfile, "Patches must have 3 vertices, input was %d\n",
		       tot_vert);
	       exit(1);
	       }
	    fprintf(outfile, "patch = {");
	    for (num_vert=0;num_vert<tot_vert;++num_vert) {
	       fprintf(outfile, " vertex = %g %g %g; normal = %g %g %g;",
		      vert[num_vert].x, vert[num_vert].y, vert[num_vert].z,
		      norm[num_vert].x, norm[num_vert].y, norm[num_vert].z);
	       }
	    fprintf(outfile, " }\n");
	    break;
	 case OUTPUT_QRT:
	 case OUTPUT_PLG:
	    /* No smooth patches in QRT, use flat triangles */
	    lib_output_polygon(tot_vert, vert);
	    break;
	 case OUTPUT_RAYSHADE:
	    if (tot_vert != 3) {
	       fprintf(outfile, "Patches must have 3 vertices, not %d\n",
		       tot_vert);
	       exit(1);
	       }
	    fprintf(outfile, "triangle %d\n", tot_vert);
	    for (num_vert=0;num_vert<tot_vert;++num_vert) {
		fprintf(outfile, "%g %g %g %g %g %g ",
			vert[num_vert].x, vert[num_vert].y, vert[num_vert].z,
			norm[num_vert].x, norm[num_vert].y, norm[num_vert].z);
		}
	    fprintf(outfile, "\n");
	    break;
	 case OUTPUT_RTRACE:
	    fprintf(outfile, "6 %d %g 0 0 0 1 1 1 -\n",
		    texture_count, texture_ior);
	    for (num_vert=0;num_vert<tot_vert;num_vert++)
		fprintf(outfile, "%g %g %g %g %g %g\n",
			vert[num_vert].x, vert[num_vert].y, vert[num_vert].z,
			norm[num_vert].x, norm[num_vert].y, norm[num_vert].z);
	    fprintf(outfile, "\n");
	    break;
	 }
      }
}

static void
dump_plg_file()
{
   object_ptr temp_obj;
   int i;
   unsigned fcnt, vcnt;

   fcnt = 0;
   vcnt = 0;
   for (temp_obj = polygon_stack;
	temp_obj != NULL;
	temp_obj = temp_obj->next_object) {
      fcnt++;
      vcnt += temp_obj->object_data.polygon.tot_vert;
      }

   fprintf(outfile, "objx %d %d\n", vcnt, fcnt);

   /* Dump all vertices */
   for (temp_obj = polygon_stack;
	temp_obj != NULL;
	temp_obj = temp_obj->next_object)
      for (i=0;i<temp_obj->object_data.polygon.tot_vert;i++)
	 fprintf(outfile, "%f %f %f\n",
	        temp_obj->object_data.polygon.vert[i].x,
	        temp_obj->object_data.polygon.vert[i].y,
	        temp_obj->object_data.polygon.vert[i].z);

   /* Dump all faces */
   vcnt = 0;
   for (temp_obj = polygon_stack;
	temp_obj != NULL;
	temp_obj = temp_obj->next_object) {
      fprintf(outfile, "0x11ff %d ", temp_obj->object_data.polygon.tot_vert);
      for (i=0;i<temp_obj->object_data.polygon.tot_vert;i++)
	 fprintf(outfile, "%d ", vcnt + i);
      fprintf(outfile, "\n");
      vcnt += i;
      }
}

static void
dump_all_objects()
{
   object_ptr temp_obj;

   if (format == OUTPUT_RTRACE)
      fprintf(outfile, "Objects\n");

   /* Step through all objects dumping them as we go. */
   for (temp_obj = lib_objects;
	temp_obj != NULL;
	temp_obj = temp_obj->next_object) {
      lookup_surface_stats(temp_obj->surf_index, &texture_count, &texture_ior);
      switch (temp_obj->object_type) {
	    break;
	 case BOX_OBJ:
	    lib_output_box(&temp_obj->object_data.box.point1,
			   &temp_obj->object_data.box.point2);
	    break;
	 case CONE_OBJ:
	    lib_output_cylcone(&temp_obj->object_data.cone.base_pt,
			       &temp_obj->object_data.cone.apex_pt,
			       temp_obj->curve_format);
	    break;
	 case DISC_OBJ:
	    lib_output_disc(&temp_obj->object_data.disc.center,
			    &temp_obj->object_data.disc.normal,
			    temp_obj->object_data.disc.iradius,
			    temp_obj->object_data.disc.oradius,
			    temp_obj->curve_format);
	    break;
	 case HEIGHT_OBJ:
	    lib_output_height(temp_obj->object_data.height.filename,
	    		      temp_obj->object_data.height.data,
			      temp_obj->object_data.height.height,
	    		      temp_obj->object_data.height.width,
	    		      temp_obj->object_data.height.x0,
	    		      temp_obj->object_data.height.x1,
	    		      temp_obj->object_data.height.y0,
	    		      temp_obj->object_data.height.y1,
	    		      temp_obj->object_data.height.z0,
	    		      temp_obj->object_data.height.z1);
	    break;
	 case POLYGON_OBJ:
	    lib_output_polygon(temp_obj->object_data.polygon.tot_vert,
			       temp_obj->object_data.polygon.vert);
	    break;
	 case POLYPATCH_OBJ:
	    lib_output_polypatch(temp_obj->object_data.polypatch.tot_vert,
				 temp_obj->object_data.polypatch.vert,
				 temp_obj->object_data.polypatch.norm);
	    break;
	 case SPHERE_OBJ:
	    lib_output_sphere(&temp_obj->object_data.sphere.center_pt,
			      temp_obj->curve_format);
	    break;
	 case SUPERQ_OBJ:
	    lib_output_sq_sphere(&temp_obj->object_data.superq.center_pt,
				 temp_obj->object_data.superq.a1,
				 temp_obj->object_data.superq.a2,
				 temp_obj->object_data.superq.a3,
				 temp_obj->object_data.superq.n,
				 temp_obj->object_data.superq.e);
	    break;
	 case TORUS_OBJ:
	    lib_output_torus(&temp_obj->object_data.torus.center,
			     &temp_obj->object_data.torus.normal,
			     temp_obj->object_data.torus.iradius,
			     temp_obj->object_data.torus.oradius,
			     temp_obj->curve_format);
	    break;
	 default:
	    fprintf(outfile, "Bad object type: %d\n",
		    temp_obj->object_type);
	    exit(1);
	 }
      }

   if (format == OUTPUT_RTRACE)
      fprintf(outfile, "\n");
}

static void
reorder_surfaces()
{
   surface_ptr temp_ptr, head_ptr = NULL;

   while (lib_surfaces != NULL) {
      temp_ptr = lib_surfaces;
      lib_surfaces = lib_surfaces->next;
      temp_ptr->next = head_ptr;
      head_ptr = temp_ptr;
      }

   lib_surfaces = head_ptr;
}

static void
dump_all_lights()
{
   light_ptr temp_ptr = lib_lights;

   if (format == OUTPUT_RTRACE)
      fprintf(outfile, "Lights\n");

   while (temp_ptr != NULL) {
      lib_output_light(&temp_ptr->center_pt);
      temp_ptr = temp_ptr->next;
      }

   if (format == OUTPUT_RTRACE)
      fprintf(outfile, "\n");
}

static void
dump_all_surfaces()
{
   surface_ptr temp_ptr = lib_surfaces;

   if (format == OUTPUT_RTRACE)
      fprintf(outfile, "Surfaces\n");

   while (temp_ptr != NULL) {
      lib_output_color(temp_ptr->surf_name, &temp_ptr->color, temp_ptr->ka,
		       temp_ptr->kd, temp_ptr->ks, temp_ptr->shine,
		       temp_ptr->ang, temp_ptr->kt, temp_ptr->ior);
      temp_ptr = temp_ptr->next;
      }

   if (format == OUTPUT_RTRACE)
      fprintf(outfile, "\n");
}

void
lib_flush_definitions()
{
   unsigned cnt = 0;
   object_ptr temp_obj;

   switch (format) {
      case OUTPUT_RTRACE:
      case OUTPUT_VIDEO:
      case OUTPUT_NFF:
      case OUTPUT_POVRAY:
      case OUTPUT_POLYRAY:
      case OUTPUT_VIVID:
      case OUTPUT_QRT:
      case OUTPUT_RAYSHADE:
      case OUTPUT_POVRAY_15:
      case OUTPUT_PLG:
	 lib_output_viewpoint(&view.from, &view.at, &view.up, view.angle,
			      view.aspect, view.hither, view.resx, view.resy);

	 lib_output_background_color(&bk_color);

	 dump_all_lights();

	 reorder_surfaces();

	 dump_all_surfaces();

	 dump_all_objects();

	 if (format == OUTPUT_RTRACE)
	    fprintf(outfile, "Textures\n\n");

         break;
      case OUTPUT_DELAYED:
         fprintf(stderr, "Error: Renderer not selected before flushing\n");
         exit(1);
      }

   if (format == OUTPUT_PLG) {
      /* An extra step is needed to build the polygon file. */
      dump_plg_file();
      }
}
