
/*
 * MODULE: extrude.c
 *
 * FUNCTION:
 * Provides  code for the cylinder, cone and extrusion routines. 
 * The cylinders/cones/etc. are built on top of general purpose
 * extrusions. The code that handles the general purpose extrusions 
 * is in other modules.
 * 
 * AUTHOR:
 * written by Linas Vepstas August/September 1991
 * added polycone, February 1993
 */

#include <stdlib.h>
#include <math.h>
#include <string.h>	/* for the memcpy() subroutine */
#include <GL/tube.h>
#include "gutil.h"
#include "vvector.h"
#include "tube_gc.h"
#include "extrude.h"
#include "intersect.h"

/* ============================================================ */
/* The routine below  determines the type of join style that will be
 * used for tubing. */

void gleSetJoinStyle (int style) 
{
   INIT_GC();
   extrusion_join_style = style;
}

int gleGetJoinStyle (void)
{
   INIT_GC();
   return (extrusion_join_style);
}

/* ============================================================ */
/*
 * draw a general purpose extrusion 
 */

void gleSuperExtrusion (int ncp,               /* number of contour points */
                gleDouble contour[][2],    /* 2D contour */
                gleDouble cont_normal[][2], /* 2D contour normals */
                gleDouble up[3],           /* up vector for contour */
                int npoints,           /* numpoints in poly-line */
                gleDouble point_array[][3],        /* polyline */
                float color_array[][3],        /* color of polyline */
                gleDouble xform_array[][2][3])   /* 2D contour xforms */
{   
   INIT_GC();
   _gle_gc -> ncp = ncp;
   _gle_gc -> contour = contour;
   _gle_gc -> cont_normal = cont_normal;
   _gle_gc -> up = up;
   _gle_gc -> npoints = npoints;
   _gle_gc -> point_array = point_array;
   _gle_gc -> color_array = color_array;
   _gle_gc -> xform_array = xform_array;

   switch (__TUBE_STYLE) {
      case TUBE_JN_RAW:
         (void) extrusion_raw_join (ncp, contour, cont_normal, up,
                                    npoints,
                                    point_array, color_array,
                                    xform_array);
         break;

      case TUBE_JN_ANGLE:
         (void) extrusion_angle_join (ncp, contour, cont_normal, up,
                                    npoints,
                                    point_array, color_array,
                                    xform_array);
         break;

      case TUBE_JN_CUT:
      case TUBE_JN_ROUND:
         /* This routine used for both cut and round styles */
         (void) extrusion_round_or_cut_join (ncp, contour, cont_normal, up,
                                    npoints,
                                    point_array, color_array,
                                    xform_array);
         break;

      default:
         break;
   }
}

/* ============================================================ */

void gleExtrusion (int ncp,               /* number of contour points */
                gleDouble contour[][2],    /* 2D contour */
                gleDouble cont_normal[][2], /* 2D contour normals */
                gleDouble up[3],           /* up vector for contour */
                int npoints,           /* numpoints in poly-line */
                gleDouble point_array[][3],        /* polyline */
                float color_array[][3])        /* color of polyline */
{   
   gleSuperExtrusion (ncp, contour, cont_normal, up,
                    npoints,
                    point_array, color_array,
                    NULL);
}

/* ============================================================ */

/* should really make this an adaptive algorithm ... */
static int __gleSlices = 20;

int
gleGetNumSlices(void)
{
  return __gleSlices;
}

void
gleSetNumSlices(int slices)
{
  __gleSlices = slices;
}

void gen_polycone (int npoints,
               gleDouble point_array[][3],
               float color_array[][3],
               gleDouble radius,
               gleDouble xform_array[][2][3])
{
   int saved_style;
   glePoint *circle = (glePoint*) malloc(sizeof(glePoint)*2*__gleSlices);
   glePoint *norm = &circle[__gleSlices];
   double c, s;
   int i;
   double v21[3];
   double len;
   gleDouble up[3];

   INIT_GC();

   /* this if statement forces this routine into double-duty for
    * both the polycone and the polycylinder routines */
   if (xform_array != NULL) radius = 1.0;

   s = sin (2.0*M_PI/ ((double) __gleSlices));
   c = cos (2.0*M_PI/ ((double) __gleSlices));

   norm [0][0] = 1.0;
   norm [0][1] = 0.0;
   circle [0][0] = radius;
   circle [0][1] = 0.0;

   /* draw a norm using recursion relations */
   for (i=1; i<__gleSlices; i++) {
      norm [i][0] = norm[i-1][0] * c - norm[i-1][1] * s;
      norm [i][1] = norm[i-1][0] * s + norm[i-1][1] * c;
      circle [i][0] = radius * norm[i][0];
      circle [i][1] = radius * norm[i][1];
   }

   /* avoid degenerate vectors */
   /* first, find a non-zero length segment */
   i=0;
   FIND_NON_DEGENERATE_POINT(i,npoints,len,v21,point_array)
   if (i == npoints) return;

   /* next, check to see if this segment lies along x-axis */
   if ((v21[0] == 0.0) && (v21[2] == 0.0)) {
      up[0] = up[1] = up[2] = 1.0;
   } else {
      up[0] = up[2] = 0.0;
      up[1] = 1.0;
   }

   /* save the current join style */
   saved_style = extrusion_join_style;
   extrusion_join_style |= TUBE_CONTOUR_CLOSED;

   /* if lighting is not turned on, don't send normals.  
    * MMODE is a good indicator of whether lighting is active */
   if (!__IS_LIGHTING_ON) {
       gleSuperExtrusion (__gleSlices, circle, NULL, up,
                     npoints, point_array, color_array,
                     xform_array);
   } else {
       gleSuperExtrusion (__gleSlices, circle, norm, up,
                     npoints, point_array, color_array,
                     xform_array);
   }
   
   /* restore the join style */
   extrusion_join_style = saved_style;

   free(circle);
}

/* ============================================================ */

void glePolyCylinder (int npoints,
                   gleDouble point_array[][3],
                   float color_array[][3],
                   gleDouble radius)
{
   gen_polycone (npoints, point_array, color_array, radius, NULL);
}

/* ============================================================ */

void glePolyCone (int npoints,
               gleDouble point_array[][3],
               float color_array[][3],
               gleDouble radius_array[])
{
   gleAffine * xforms;
   int j;

   /* build 2D affine matrices from radius array */
   xforms = (gleAffine *) malloc (npoints * sizeof(gleAffine));
   for (j=0; j<npoints; j++) {
      AVAL(xforms,j,0,0) = radius_array[j];
      AVAL(xforms,j,0,1) = 0.0;
      AVAL(xforms,j,0,2) = 0.0;
      AVAL(xforms,j,1,0) = 0.0;
      AVAL(xforms,j,1,1) = radius_array[j];
      AVAL(xforms,j,1,2) = 0.0;
   }

   gen_polycone (npoints, point_array, color_array, 1.0, xforms);

   free (xforms);
}

/* ============================================================ */

void gleTwistExtrusion (int ncp,         /* number of contour points */
                gleDouble contour[][2],    /* 2D contour */
                gleDouble cont_normal[][2], /* 2D contour normals */
                gleDouble up[3],           /* up vector for contour */
                int npoints,           /* numpoints in poly-line */
                gleDouble point_array[][3],        /* polyline */
                float color_array[][3],        /* color of polyline */
                gleDouble twist_array[])   /* countour twists (in degrees) */

{
   int j;
   double angle;
   double si, co;

   gleAffine *xforms;

   /* build 2D affine matrices from radius array */
   xforms = (gleAffine *) malloc (npoints * sizeof(gleAffine));

   for (j=0; j<npoints; j++) {
      angle = (M_PI/180.0) * twist_array[j];
      si = sin (angle);
      co = cos (angle);
      AVAL(xforms,j,0,0) = co;
      AVAL(xforms,j,0,1) = -si;
      AVAL(xforms,j,0,2) = 0.0;
      AVAL(xforms,j,1,0) = si;
      AVAL(xforms,j,1,1) = co;
      AVAL(xforms,j,1,2) = 0.0;
   }

   gleSuperExtrusion (ncp,               /* number of contour points */
                contour,    /* 2D contour */
                cont_normal, /* 2D contour normals */
                up,           /* up vector for contour */
                npoints,           /* numpoints in poly-line */
                point_array,        /* polyline */
                color_array,        /* color of polyline */
                xforms);

   free (xforms);
}

/* ============================================================ */
/* 
 * The spiral primitive forms the basis for the helicoid primitive.
 *
 * Note that this primitive sweeps a contour along a helical path.
 * The algorithm assumes that the path is embedded in Euclidean space,
 * and uses parallel transport along the path.  Parallel transport
 * provides the simplest mathematical model for moving a coordinate 
 * system along a curved path, but some of the effects of doing so 
 * may prove to be surprising to one uninitiated to the concept.
 *
 * Thus, we provide another, related, algorithm below, called "lathe"
 * which introduces a torsion component along the path, correcting for
 * the rotation induced by the helical path.
 *
 * If the above sounds like gobldy-gook to you, you may want to brush 
 * up on differential geometry. Recommend Spivak, Differential Geometry,
 * Volume 1, pages xx-xx.
 */

void gleSpiral (int ncp,               /* number of contour points */
             gleDouble contour[][2],    /* 2D contour */
             gleDouble cont_normal[][2], /* 2D contour normals */
             gleDouble up[3],           /* up vector for contour */
             gleDouble startRadius,
             gleDouble drdTheta,        /* change in radius per revolution */
             gleDouble startZ,
             gleDouble dzdTheta,        /* change in Z per revolution */
             gleDouble startXform[2][3],
             gleDouble dXformdTheta[2][3], /* tangent change xform per revolution */
             gleDouble startTheta,	      /* start angle, in degrees */
             gleDouble sweepTheta)        /* sweep angle, in degrees */
{
   int npoints;
   gleDouble deltaAngle;
   char * mem_anchor;
   gleDouble *pts;
   gleAffine *xforms;
   double delta;

   int saved_style;
   double ccurr, scurr;
   double cprev, sprev;
   double cdelta, sdelta;
   double mA[2][2], mB[2][2];
   double run[2][2];
   double deltaTrans[2];
   double trans[2];
   int i;

   /* allocate sufficient memory to store path */
   npoints = (int) ((((double) __gleSlices) /360.0) * fabs(sweepTheta)) + 4;

   if (startXform == NULL) {
      mem_anchor = malloc (3*npoints * sizeof (gleDouble));
      pts = (gleDouble *) mem_anchor;
      xforms = NULL;
   } else {
      mem_anchor = malloc ((1+2)* 3*npoints * sizeof (gleDouble));
      pts = (gleDouble *) mem_anchor;
      xforms = (gleAffine *) (pts + 3*npoints);
   }

   /* compute delta angle based on number of points */
   deltaAngle = (M_PI / 180.0) * sweepTheta / ((gleDouble) (npoints-3));
   startTheta *= M_PI / 180.0;
   startTheta -= deltaAngle;

   /* initialize factors */
   cprev = cos ((double) startTheta);
   sprev = sin ((double) startTheta);

   cdelta = cos ((double) deltaAngle);
   sdelta = sin ((double) deltaAngle);

   /* renormalize differential factors */
   delta = deltaAngle / (2.0 * M_PI);
   dzdTheta *= delta;
   drdTheta *= delta;

   /* remember, the first point is hidden, so back-step */
   startZ -=  dzdTheta;
   startRadius -=  drdTheta;

   /* draw spiral path using recursion relations for sine, cosine */
   for (i=0; i<npoints; i++) {
      pts [3*i] =  startRadius * cprev;
      pts [3*i+1] =  startRadius * sprev;
      pts [3*i+2] = (gleDouble) startZ;

      startZ +=  dzdTheta;
      startRadius +=  drdTheta;
      ccurr = cprev * cdelta - sprev * sdelta;
      scurr = cprev * sdelta + sprev * cdelta;
      cprev = ccurr;
      sprev = scurr;
   }

   /* If there is a deformation matrix specified, then a deformation
    * path must be generated also */
   if (startXform != NULL) {
      if (dXformdTheta == NULL) {
         for (i=0; i<npoints; i++) {
            xforms[i][0][0] = startXform[0][0];
            xforms[i][0][1] = startXform[0][1];
            xforms[i][0][2] = startXform[0][2];
            xforms[i][1][0] = startXform[1][0];
            xforms[i][1][1] = startXform[1][1];
            xforms[i][1][2] = startXform[1][2];
         }
      } else {
         /* 
          * if there is a differential matrix specified, treat it a 
          * a tangent (algebraic, infinitessimal) matrix.  We need to
          * project it into the group of real 2x2 matricies.  (Note that
          * the specified matrix is affine.  We treat the translation 
          * components linearly, and only treat the 2x2 submatrix as an 
          * algebraic tangenet).
          *
          * For exponentiaition, we use the well known approx:
          * exp(x) = lim (N->inf) (1+x/N) ** N
          * and take N=32. 
          */

         /* initialize translation and delta translation */
         deltaTrans[0] = delta * dXformdTheta[0][2];
         deltaTrans[1] = delta * dXformdTheta[1][2];
         trans[0] = startXform[0][2];
         trans[1] = startXform[1][2];
   
         /* prepare the tangent matrix */
         delta /= 32.0;
         mA[0][0] = 1.0 + delta * dXformdTheta[0][0];
         mA[0][1] = delta * dXformdTheta[0][1];
         mA[1][0] = delta * dXformdTheta[1][0];
         mA[1][1] = 1.0 + delta * dXformdTheta[1][1];
   
         /* compute exponential of matrix */
         MATRIX_PRODUCT_2X2 (mB, mA, mA);  /* squared */
         MATRIX_PRODUCT_2X2 (mA, mB, mB);  /* 4th power */
         MATRIX_PRODUCT_2X2 (mB, mA, mA);  /* 8th power */
         MATRIX_PRODUCT_2X2 (mA, mB, mB);  /* 16th power */
         MATRIX_PRODUCT_2X2 (mB, mA, mA);  /* 32nd power */
   
         /* initialize running matrix */
         COPY_MATRIX_2X2 (run, startXform);
   
         /* remember, the first point is hidden -- load some, any 
          * xform for the first point */
         xforms[0][0][0] = startXform[0][0];
         xforms[0][0][1] = startXform[0][1];
         xforms[0][0][2] = startXform[0][2];
         xforms[0][1][0] = startXform[1][0];
         xforms[0][1][1] = startXform[1][1];
         xforms[0][1][2] = startXform[1][2];

         for (i=1; i<npoints; i++) {
#ifdef FUNKY_C
            xforms[6*i] = run[0][0];
            xforms[6*i+1] = run[0][1];
            xforms[6*i+3] = run[1][0];
            xforms[6*i+4] = run[1][1];
#endif /* FUNKY_C */
            xforms[i][0][0] = run[0][0];
            xforms[i][0][1] = run[0][1];
            xforms[i][1][0] = run[1][0];
            xforms[i][1][1] = run[1][1];

            /* integrate to get exponential matrix */
            /* (Note that the group action is a left-action --
             * i.e. multiply on the left (not the right)) */
            MATRIX_PRODUCT_2X2 (mA, mB, run);
            COPY_MATRIX_2X2 (run, mA);
         
#ifdef FUNKY_C
            xforms[6*i+2] = trans [0];
            xforms[6*i+5] = trans [1];
#endif /* FUNKY_C */
            xforms[i][0][2] = trans [0];
            xforms[i][1][2] = trans [1];
            trans[0] += deltaTrans[0];
            trans[1] += deltaTrans[1];

         }
      }
   }

   /* save the current join style */
   saved_style = extrusion_join_style;

   /* Allow only angle joins (for performance reasons).
    * The idea is that if the tesselation is fine enough, then an angle
    * join should be sufficient to get the desired visual quality.  A 
    * raw join would look terrible, an cut join would leave garbage 
    * everywhere, and a round join will over-tesselate (and thus 
    * should be avoided for performance reasons). 
    */
   extrusion_join_style  &= ~TUBE_JN_MASK; 
   extrusion_join_style  |= TUBE_JN_ANGLE;

   gleSuperExtrusion (ncp, contour, cont_normal, up,
                  npoints, (gleVector *) pts, NULL, xforms);

   /* restore the join style */
   extrusion_join_style = saved_style;

   free (mem_anchor);

}


/* ============================================================ */
/* 
 */

void gleLathe (int ncp,               /* number of contour points */
             gleDouble contour[][2],    /* 2D contour */
             gleDouble cont_normal[][2], /* 2D contour normals */
             gleDouble up[3],           /* up vector for contour */
             gleDouble startRadius,
             gleDouble drdTheta,        /* change in radius per revolution */
             gleDouble startZ,
             gleDouble dzdTheta,        /* change in Z per revolution */
             gleDouble startXform[2][3],
             gleDouble dXformdTheta[2][3], /* tangent change xform per revln */
             gleDouble startTheta,	      /* start angle, in degrees */
             gleDouble sweepTheta)        /* sweep angle, in degrees */
{
   gleDouble localup[3];
   gleDouble len;
   gleDouble trans[2];
   gleDouble start[2][3], delt[2][3];

   /* Because the spiral always starts on the axis, and proceeds in the
    * positive y direction, we can see that valid up-vectors must lie 
    * in the x-z plane. Therefore, we make sure we have a valid up
    * vector by projecting it onto the x-z plane, and normalizing. */
   if (up[1] != 0.0) {
      localup[0] = up[0];
      localup[1] = 0.0;
      localup[2] = up[2];
      VEC_LENGTH (len, localup);
      if (len != 0.0) {
         len = 1.0/len;
         localup[0] *= len;
         localup[2] *= len;
         VEC_SCALE (localup, len, localup);
      } else {
         /* invalid up vector was passed in */
         localup[0] = 0.0;
         localup[2] = 1.0;
      }
   } else {
      VEC_COPY (localup, up);
   }

   /* the dzdtheta derivative and the drdtheta derivative form a vector
    * in the x-z plane.  dzdtheta is the z component, and drdtheta is 
    * the x component.  We need to convert this vector into the local 
    * coordinate system defined by the up vector.  We do this by 
    * applying a 2D rotation matrix. 
    */
   trans[0] = localup[2] * drdTheta - localup[0] * dzdTheta;
   trans[1] = localup[0] * drdTheta + localup[2] * dzdTheta;

   /* now, add this translation vector into the affine xform */
   if (startXform != NULL) {
      if (dXformdTheta != NULL) {
         COPY_MATRIX_2X3 (delt, dXformdTheta);
         delt[0][2] += trans[0];
         delt[1][2] += trans[1];
      } else {
         /*Hmm- the transforms don't exist */

         delt[0][0] = 0.0;
         delt[0][1] = 0.0;
         delt[0][2] = trans[0];
         delt[1][0] = 0.0;
         delt[1][1] = 0.0;
         delt[1][2] = trans[1];
      }
      gleSpiral (ncp, contour, cont_normal, up, 
              startRadius, 0.0, startZ, 0.0,
              startXform, delt,
              startTheta, sweepTheta);

   } else {
      /* Hmm- the transforms don't exist */
      start[0][0] = 1.0;
      start[0][1] = 0.0;
      start[0][2] = 0.0;
      start[1][0] = 0.0;
      start[1][1] = 1.0;
      start[1][2] = 0.0;

      delt[0][0] = 0.0;
      delt[0][1] = 0.0;
      delt[0][2] = trans[0];
      delt[1][0] = 0.0;
      delt[1][1] = 0.0;
      delt[1][2] = trans[1];
      gleSpiral (ncp, contour, cont_normal, up, 
              startRadius, 0.0, startZ, 0.0,
              start, delt,
              startTheta, sweepTheta);
   }
}


/* ============================================================ */
/*
 * Super-Helicoid primitive 
 */

typedef void (*HelixCallback) (
             int ncp,               
             gleDouble contour[][2],
             gleDouble cont_normal[][2],
             gleDouble up[3],
             gleDouble startRadius,
             gleDouble drdTheta,
             gleDouble startZ,
             gleDouble dzdTheta,
             gleDouble startXform[2][3],
             gleDouble dXformdTheta[2][3],
             gleDouble startTheta,
             gleDouble sweepTheta);

void super_helix (gleDouble rToroid,
             gleDouble startRadius,
             gleDouble drdTheta,        /* change in radius per revolution */
             gleDouble startZ,
             gleDouble dzdTheta,        /* change in Z per revolution */
             gleDouble startXform[2][3],
             gleDouble dXformdTheta[2][3], /* tangent change xform per revol */
             gleDouble startTheta,	      /* start angle, in degrees */
             gleDouble sweepTheta,        /* sweep angle, in degrees */
	     HelixCallback helix_callback)
{

   int saved_style;
   glePoint *circle = (glePoint*) malloc(sizeof(glePoint)*2*__gleSlices);
   glePoint *norm = &circle[__gleSlices];
   double c, s;
   int i;
   gleDouble up[3];

   /* initialize sine and cosine for circle recusrion equations */
   s = sin (2.0*M_PI/ ((double) __gleSlices));
   c = cos (2.0*M_PI/ ((double) __gleSlices));

   norm [0][0] = 1.0;
   norm [0][1] = 0.0;
   circle [0][0] = rToroid;
   circle [0][1] = 0.0;

   /* draw a norm using recursion relations */
   for (i=1; i<__gleSlices; i++) {
      norm [i][0] = norm[i-1][0] * c - norm[i-1][1] * s;
      norm [i][1] = norm[i-1][0] * s + norm[i-1][1] * c;
      circle [i][0] = rToroid * norm[i][0];
      circle [i][1] = rToroid * norm[i][1];
   }

   /* make up vector point along x axis */
   up[1] = up[2] = 0.0;
   up[0] = 1.0;

   /* save the current join style */
   saved_style = extrusion_join_style;
   extrusion_join_style |= TUBE_CONTOUR_CLOSED;
   extrusion_join_style |= TUBE_NORM_PATH_EDGE;

   /* if lighting is not turned on, don't send normals.  
    * MMODE is a good indicator of whether lighting is active */
   if (!__IS_LIGHTING_ON) {
      (*helix_callback) (__gleSlices, circle, NULL, up,
             startRadius,
             drdTheta,
             startZ,
             dzdTheta,
             startXform,
             dXformdTheta,
             startTheta,
             sweepTheta);
   } else {
      (*helix_callback) (__gleSlices, circle, norm, up,
             startRadius,
             drdTheta,
             startZ,
             dzdTheta,
             startXform,
             dXformdTheta,
             startTheta,
             sweepTheta);
   }
   
   /* restore the join style */
   extrusion_join_style = saved_style;

   free(circle);
}

/* ============================================================ */
/*
 * Helicoid primitive 
 * Uses Parallel Transport to take a circular contour along a helical
 * path.
 */

void gleHelicoid (gleDouble rToroid,
             gleDouble startRadius,
             gleDouble drdTheta,        /* change in radius per revolution */
             gleDouble startZ,
             gleDouble dzdTheta,        /* change in Z per revolution */
             gleDouble startXform[2][3],
             gleDouble dXformdTheta[2][3], /* tangent change xform per revol */
             gleDouble startTheta,	      /* start angle, in degrees */
             gleDouble sweepTheta)        /* sweep angle, in degrees */
{
   super_helix (rToroid,
             startRadius,
             drdTheta,        /* change in radius per revolution */
             startZ,
             dzdTheta,        /* change in Z per revolution */
             startXform,
             dXformdTheta, /* tangent change xform per revolution */
             startTheta,	      /* start angle, in degrees */
             sweepTheta,       /* sweep angle, in degrees */
             gleSpiral);
}


/* ============================================================ */
/*
 * Toroid primitive 
 * Uses a helical coordinate system dislocation to take a circular 
 * contour along a helical path.
 */

void gleToroid (gleDouble rToroid,
             gleDouble startRadius,
             gleDouble drdTheta,        /* change in radius per revolution */
             gleDouble startZ,
             gleDouble dzdTheta,        /* change in Z per revolution */
             gleDouble startXform[2][3],
             gleDouble dXformdTheta[2][3], /* tangent change xform per revol */
             gleDouble startTheta,	      /* start angle, in degrees */
             gleDouble sweepTheta)        /* sweep angle, in degrees */
{
   super_helix (rToroid,
             startRadius,
             drdTheta,        /* change in radius per revolution */
             startZ,
             dzdTheta,        /* change in Z per revolution */
             startXform,
             dXformdTheta, /* tangent change xform per revolution */
             startTheta,	      /* start angle, in degrees */
             sweepTheta,       /* sweep angle, in degrees */
             gleLathe);
}

/* ============================================================ */

void gleScrew (int ncp, 
               gleDouble contour[][2], 
               gleDouble cont_normal[][2], 
               gleDouble up[3],
               gleDouble startz,
               gleDouble endz,
               gleDouble twist) 
{
   int i, numsegs;
   gleVector * path; 
   gleDouble *twarr;
   gleDouble currz, delta; 
   gleDouble currang, delang; 

   /* no segment should rotate more than 18 degrees */
   numsegs = (int) fabs (twist / 18.0) + 4;

   /* malloc the extrusion array and the twist array */
   path = (gleVector *) malloc (numsegs * sizeof (gleVector));
   twarr = (gleDouble *) malloc (numsegs * sizeof (gleDouble));

   /* fill in the extrusion array and the twist array uniformly */
   delta = (endz-startz) / ((gleDouble) (numsegs-3));
   currz = startz-delta;
   delang = twist / ((gleDouble) (numsegs-3));
   currang = -delang;
   for (i=0; i<numsegs; i++) {
      path [i][0] = 0.0;
      path [i][1] = 0.0;
      path [i][2] = currz;
      currz += delta;
      twarr[i] = currang;
      currang +=delang;
   }

   gleTwistExtrusion (ncp, contour, cont_normal, up, numsegs, path, NULL, twarr);

   free (path);
   free (twarr);
}

/* ============================================================ */
