
/*
 * MODULE NAME: ex_cut_round.c
 *
 * FUNCTION:
 * This module contains code that draws extrusions with cut or round
 * join styles. The cut join style is a beveled edge.
 * The code also inserts colors and normals if appropriate.
 *
 * HISTORY:
 * written by Linas Vepstas August/September 1991
 * split into multiple compile units, Linas, October 1991
 * added normal vectors Linas, October 1991
 * Fixed filleting problem, Linas February 1993
 * Modified to handle round joins as well (based on common code),
 *                           Linas, March 1993
 * work around OpenGL's lack of support for concave polys, June 1994
 *
 * Copyright (C) 1991,1993,1994,2003 Linas Vepstas <linas@linas.org>
 */


#if defined(_WIN32) && !defined(__clang__)
# include <malloc.h>
#endif
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>	/* for the memcpy() subroutine */

#include "gle.h"
#include "port.h" 
#include "vvector.h"
#include "tube_gc.h"
#include "extrude.h"
#include "intersect.h"
#include "segment.h"

typedef void (*gleCapCallback) (int iloop,
                                double cap[][3], 
                                float face_color[3],
                                gleDouble cut_vector[3],
                                gleDouble bisect_vector[3],
                                double norms[][3], 
                                int frontwards);

#define INVALID_BUG_NEEDS_FIXING 0

#ifdef NONCONCAVE_CAPS

/* ============================================================ */
/* 
 * This subroutine draws a flat cap, to close off the cut ends 
 * of the cut-style join.  Because OpenGL doe not natively handle 
 * concave polygons, this will cause some artifacts to appear on the
 * screen.
 */

static void 
draw_cut_style_cap_callback (int iloop,
                                  double cap[][3], 
                                  float face_color[3],
                                  gleDouble cut_vector[3],
                                  gleDouble bisect_vector[3],
                                  double norms[][3], 
                                  int frontwards)
{
   int i;

   if (face_color != NULL) C3F (face_color);

   if (frontwards) {

      /* if lighting is on, specify the endcap normal */
      if (cut_vector != NULL) {
         /* if normal pointing in wrong direction, flip it. */
         if (cut_vector[2] < 0.0) { 
            VEC_SCALE (cut_vector, -1.0, cut_vector); 
         }
         N3F_D (cut_vector);
      }
      BGNPOLYGON();
      for (i=0; i<iloop; i++) {
         V3F_D (cap[i], i, FRONT_CAP);
      }
      ENDPOLYGON();
   } else {

      /* if lighting is on, specify the endcap normal */
      if (cut_vector != NULL) {
         /* if normal pointing in wrong direction, flip it. */
         if (cut_vector[2] > 0.0) 
           { VEC_SCALE (cut_vector, -1.0, cut_vector); }
         N3F_D (cut_vector);
      }
      /* the sense of the loop is reversed for backfacing culling */
      BGNPOLYGON();
      for (i=iloop-1; i>-1; i--) {
         V3F_D (cap[i], i, BACK_CAP);
      }
      ENDPOLYGON();
   }

}

#else /* NONCONCAVE_CAPS */

/* ============================================================ */
/* 
 * This subroutine draws a flat cap, to close off the cut ends 
 * of the cut-style join.  Properly handles concave endcaps.
 */

static void 
draw_cut_style_cap_callback (int iloop,
                                  double cap[][3], 
                                  float face_color[3],
                                  gleDouble cut_vector[3],
                                  gleDouble bisect_vector[3],
                                  double norms[][3], 
                                  int frontwards)
{
#ifdef GL_32
   int i;
#endif /* GL_32 */

#ifdef DELICATE_TESSELATOR
   int i;
   int is_colinear;
   double *previous_vertex = 0x0;
   double *first_vertex = 0x0;
#endif /* DELICATE_TESSELATOR */

#ifdef OPENGL_10
   GLUtriangulatorObj *tobj;
   tobj = gluNewTess ();
   gluTessCallback (tobj, GLU_BEGIN, (_GLUfuncptr) glBegin);
   gluTessCallback (tobj, GLU_VERTEX, (_GLUfuncptr) glVertex3dv);
   gluTessCallback (tobj, GLU_END, (_GLUfuncptr) glEnd);
#endif /* OPENGL_10 */

   if (face_color != NULL) C3F (face_color);

   if (frontwards) {

      /* if lighting is on, specify the endcap normal */
      if (cut_vector != NULL) {
         /* if normal pointing in wrong direction, flip it. */
         if (cut_vector[2] < 0.0) { 
            VEC_SCALE (cut_vector, -1.0, cut_vector); 
         }
         N3F_D (cut_vector);
      }
#ifdef GL_32
      BGNPOLYGON();
      for (i=0; i<iloop; i++) {
         V3F_D (cap[i], i, FRONT_CAP);
      }
      ENDPOLYGON();
#endif /* GL_32 */
#ifdef OPENGL_10

/* If you have a tesselator that is happy with anything,
 * including degenerate points, colinear segments, etc. 
 * then define this. Otherwise, pick one of the others.
 *
 * I beleive that the stock SGI tesselator is "lenient",
 * despite explicit disclaimers in the documentation.
 * (circa 1995).
 *
 * The Mesa tesselator is not at all forgiving of 
 * degenerate points.
 * (circa 1997-1998)
 */
 
#ifdef LENIENT_TESSELATOR
      gluBeginPolygon (tobj);
      for (i=0; i<iloop; i++) {
         gluTessVertex (tobj, cap[i], cap[i]);
      }
      gluEndPolygon (tobj);
#endif /* LENIENT_TESSELATOR */

#ifdef DELICATE_TESSELATOR
      gluBeginPolygon (tobj);

      first_vertex = 0x0;
      previous_vertex = cap[iloop-1];
      for (i=0; i<iloop-1; i++) {
         COLINEAR (is_colinear, previous_vertex, cap[i], cap[i+1]);
         if (!is_colinear) {
            gluTessVertex (tobj, cap[i], cap[i]);
            previous_vertex = cap[i];
            if (!first_vertex) first_vertex = previous_vertex;
         }
      }

      if (!first_vertex) first_vertex = cap[0];
      COLINEAR (is_colinear, previous_vertex, cap[iloop-1], first_vertex);
      if (!is_colinear) gluTessVertex (tobj, cap[iloop-1], cap[iloop-1]);

      gluEndPolygon (tobj);
#endif /* DELICATE_TESSELATOR */

#endif /* OPENGL_10 */
   } else {

      /* if lighting is on, specify the endcap normal */
      if (cut_vector != NULL) {
         /* if normal pointing in wrong direction, flip it. */
         if (cut_vector[2] > 0.0) {
            VEC_SCALE (cut_vector, -1.0, cut_vector); 
         }
         N3F_D (cut_vector);
      }
      /* the sense of the loop is reversed for backfacing culling */
#ifdef GL_32
      BGNPOLYGON();
      for (i=iloop-1; i>-1; i--) {
         V3F_D (cap[i], i, BACK_CAP);
      }
      ENDPOLYGON();
#endif /* GL_32 */
#ifdef OPENGL_10

#ifdef LENIENT_TESSELATOR
      gluBeginPolygon (tobj);
      for (i=iloop-1; i>-1; i--) {
         gluTessVertex (tobj, cap[i], cap[i]);
      }
      gluEndPolygon (tobj);
#endif /* LENIENT_TESSELATOR */

#ifdef DELICATE_TESSELATOR
      gluBeginPolygon (tobj);

      first_vertex = 0x0;
      previous_vertex = cap[0];
      for (i=iloop-1; i>0; i--) {
         COLINEAR (is_colinear, previous_vertex, cap[i], cap[i-1]);
         if (!is_colinear) {
            gluTessVertex (tobj, cap[i], cap[i]);
            previous_vertex = cap[i];
            if (!first_vertex) first_vertex = previous_vertex;
         }
      }

      if (!first_vertex) first_vertex = cap[iloop-1];
      COLINEAR (is_colinear, previous_vertex, cap[0], first_vertex);
      if (!is_colinear) gluTessVertex (tobj, cap[0], cap[0]);

      gluEndPolygon (tobj);
#endif /* DELICATE_TESSELATOR */

#endif /* OPENGL_10 */
   }

#ifdef OPENGL_10
   gluDeleteTess (tobj);
#endif /* OPENGL_10 */

}
#endif /* NONCONCAVE_ENDCAPS */

/* ============================================================ */
/* 
 * This subroutine matchs the cap callback template, but is a no-op
 */

static void 
null_cap_callback (int iloop,
                        double cap[][3], 
                        float face_color[3],
                        gleDouble cut_vector[3],
                        gleDouble bisect_vector[3],
                        double norms[][3], 
                        int frontwards)
{}

/* ============================================================ */
/* 
 * This little routine draws the little idd-biddy fillet triangle with
 * the right  color, normal, etc.
 *
 * HACK ALERT -- there are two aspects to this routine/interface that
 * are "unfinished".
 * 1) the third point of the triangle should get a color thats
 *    interpolated beween the front and back color.  The interpolant
 *    is not currently being computed.  The error introduced by not 
 *    doing this should be tiny and/or non-exitant in almost all 
 *    expected uses of this code.
 *
 * 2) additional normal vectors should be supplied, and these should
 *    be interpolated to fit.  Currently, this is not being done.  As
 *    above, the expected error of not doing this should be tiny and/or
 *    non-existant in almost all expected uses of this code.
 */
static void 
draw_fillet_triangle_plain
                          (gleDouble va[3],
                           gleDouble vb[3],
                           gleDouble vc[3],
                           int face,
                           float front_color[3],
                           float back_color[3])
{

   if (front_color != NULL) C3F (front_color);
   BGNTMESH (-5, 0.0);
   if (face) {
      V3F (va, INVALID_BUG_NEEDS_FIXING, FILLET);
      V3F (vb, INVALID_BUG_NEEDS_FIXING, FILLET);
   } else {
      V3F (vb, INVALID_BUG_NEEDS_FIXING, FILLET);
      V3F (va, INVALID_BUG_NEEDS_FIXING, FILLET);
   }
   V3F (vc, INVALID_BUG_NEEDS_FIXING, FILLET);
   ENDTMESH ();

}

/* ============================================================ */
/* 
 * This little routine draws the little idd-biddy fillet triangle with
 * the right  color, normal, etc.
 *
 * HACK ALERT -- there are two aspects to this routine/interface that
 * are "unfinished".
 * 1) the third point of the triangle should get a color thats
 *    interpolated beween the front and back color.  The interpolant
 *    is not currently being computed.  The error introduced by not 
 *    doing this should be tiny and/or non-exitant in almost all 
 *    expected uses of this code.
 *
 * 2) additional normal vectors should be supplied, and these should
 *    be interpolated to fit.  Currently, this is not being done.  As
 *    above, the expected error of not doing this should be tiny and/or
 *    non-existant in almost all expected uses of this code.
 */
static void draw_fillet_triangle_n_norms
                          (gleDouble va[3],
                           gleDouble vb[3],
                           gleDouble vc[3],
                           int face,
                           float front_color[3],
                           float back_color[3],
                           double na[3],
                           double nb[3])
{

   if (front_color != NULL) C3F (front_color);
   BGNTMESH (-5, 0.0);
   if (__TUBE_DRAW_FACET_NORMALS) {
      N3F_D (na);
      if (face) {
         V3F (va, INVALID_BUG_NEEDS_FIXING, FILLET);
         V3F (vb, INVALID_BUG_NEEDS_FIXING, FILLET);
      } else {
         V3F (vb, INVALID_BUG_NEEDS_FIXING, FILLET);
         V3F (va, INVALID_BUG_NEEDS_FIXING, FILLET);
      }
      V3F (vc, INVALID_BUG_NEEDS_FIXING, FILLET);
   } else {
      if (face) {
         N3F_D (na);
         V3F (va, INVALID_BUG_NEEDS_FIXING, FILLET);
         N3F_D (nb);
         V3F (vb, INVALID_BUG_NEEDS_FIXING, FILLET);
      } else {
         N3F_D (nb);
         V3F (vb, INVALID_BUG_NEEDS_FIXING, FILLET);
         N3F_D (na);
         V3F (va, INVALID_BUG_NEEDS_FIXING, FILLET);
         N3F_D (nb);
      }
      V3F (vc, INVALID_BUG_NEEDS_FIXING, FILLET);
   }
   ENDTMESH ();

}

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

static void draw_fillets_and_join_plain
                    (int ncp, 
                    gleDouble trimmed_loop[][3],
                    gleDouble untrimmed_loop[][3], 
                    int is_trimmed[],
                    gleDouble bis_origin[3], 
                    gleDouble bis_vector[3], 
                    float front_color[3],
                    float back_color[3],
                    gleDouble cut_vector[3], 
                    int face,
                    gleCapCallback cap_callback)
{
   int istop;
   int icnt, icnt_prev, iloop;
   double *cap_loop;
   gleDouble sect[3];
   gleDouble tmp_vec[3];
   int save_style = 0;
   int was_trimmed = FALSE;

   cap_loop = (double *) malloc (((size_t)ncp+3)*3*sizeof (double));
   
   /*
    * If the first point is trimmed, keep going until one
    * is found that is not trimmed, and start join there. 
    */

   save_style = gleGetJoinStyle ();
   icnt = 0;
   iloop = 0;
   if (!is_trimmed[0]) {

      /* if the first point on the contour isn't trimmed, go ahead and
       * drop an edge down to the bisecting plane, (thus starting the 
       * join).  (Only need to do this for cut join, its bad if done for
       * round join). (Also, do this only for open contours; leads to 
       * bugs when done for closed contours).
       */
      if ((__TUBE_CUT_JOIN) && (!(save_style & TUBE_CONTOUR_CLOSED))) {
         VEC_SUM (tmp_vec, trimmed_loop[0], bis_vector);
         INNERSECT (sect, 
                    bis_origin,
                    bis_vector,
                    trimmed_loop[0],
                    tmp_vec);
         VEC_COPY ( (&cap_loop[3*iloop]), sect);
         iloop ++;
      }
      VEC_COPY ( (&cap_loop[3*iloop]), (trimmed_loop[0])); 
      iloop++;
      icnt_prev = icnt;
      icnt ++;
   } else {

      /* else, loop until an untrimmed point is found */
      was_trimmed = TRUE;
      while (is_trimmed[icnt]) {
         icnt_prev = icnt;
         icnt ++;
         if (icnt >= ncp) { 
            free (cap_loop);
            return;    /* oops - everything was trimmed */
         }
      }
   }

   /* Start walking around the end cap.  Every time the end loop is
    * trimmed, we know we'll need to draw a fillet triangle.  In
    * addition, after every pair of visibility changes, we draw a cap. */
   if (__TUBE_CLOSE_CONTOUR) {
      istop = ncp;
   } else {
      istop = ncp-1;
   }

   /* save the join style, and disable a closed contour.
    * Need to do this so partial contours don't close up. */
   gleSetJoinStyle (save_style & ~TUBE_CONTOUR_CLOSED);

   for (; icnt_prev < istop; icnt_prev ++, icnt ++, icnt %= ncp) {

      /* There are four interesting cases for drawing caps and fillets:
       *    1) this & previous point were trimmed.  Don't do anything, 
       *       advance counter.
       *    2) this point trimmed, previous not -- draw fillet, and 
       *       draw cap.
       *    3) this point not trimmed, previous one was -- compute
       *       intersection point, draw fillet with it, and save 
       *       point for cap contour.
       *    4) this & previous point not trimmed -- save for endcap.
       */

      /* Case 1 -- noop, just advance pointers */
      if (is_trimmed[icnt_prev] && is_trimmed[icnt]) {
      }

      /* Case 2 --  Hah! first point! compute intersect & draw fillet! */
      if (is_trimmed[icnt_prev] && !is_trimmed[icnt]) {

         /* important note: the array "untrimmed" contains valid
          * untrimmed data ONLY when is_trim is TRUE.  Otherwise, 
          * only "trim" containes valid data */

         /* compute intersection */
         INNERSECT (sect, 
                    bis_origin,
                    bis_vector,
                    untrimmed_loop[icnt_prev],
                    trimmed_loop[icnt]);

         /* Draw Fillet */
         draw_fillet_triangle_plain (trimmed_loop[icnt_prev],
                               trimmed_loop[icnt],
                               sect,
                               face,
                               front_color,
                               back_color);

         VEC_COPY ( (&cap_loop[3*iloop]), sect);
         iloop ++;
         VEC_COPY ( (&cap_loop[3*iloop]), (trimmed_loop[icnt])); 
         iloop++;
      }

      /* Case 3 -- add to collection of points */
      if (!is_trimmed[icnt_prev] && !is_trimmed[icnt]) {
         VEC_COPY ( (&cap_loop[3*iloop]), (trimmed_loop[icnt])); 
         iloop++; 
      } 

      /* Case 4 -- Hah! last point!  draw fillet & draw cap!  */
      if (!is_trimmed[icnt_prev] && is_trimmed[icnt]) {
         was_trimmed = TRUE;

         /* important note: the array "untrimmed" contains valid
          * untrimmed data ONLY when is_trim is TRUE.  Otherwise, 
          * only "trim" containes valid data */

         /* compute intersection */
         INNERSECT (sect, 
                    bis_origin,
                    bis_vector,
                    trimmed_loop[icnt_prev],
                    untrimmed_loop[icnt]);

         /* Draw Fillet */
         draw_fillet_triangle_plain (trimmed_loop[icnt_prev],
                               trimmed_loop[icnt],
                               sect,
                               face,
                               front_color,
                               back_color);

         VEC_COPY ( (&cap_loop[3*iloop]), sect);
         iloop ++;

         /* draw cap */
         if (iloop >= 3) (*cap_callback) (iloop, 
                                          (gleDouble (*)[3]) cap_loop, 
                                          front_color,
                                          cut_vector,
                                          bis_vector,
                                          NULL,
                                          face);

         /* reset cap counter */
         iloop = 0;
      }
   }

   /* now, finish up in the same way that we started.  If the last
    * point of the contour is visible, drop an edge to the bisecting 
    * plane, thus finishing the join, and then, draw the join! */

   icnt --;  /* decrement to make up for loop exit condititons */
   icnt += ncp;
   icnt %= ncp;
   if ((!is_trimmed[icnt]) && (iloop >= 2))  {
   
      VEC_SUM (tmp_vec, trimmed_loop[icnt], bis_vector);
      INNERSECT (sect, 
                 bis_origin,
                 bis_vector,
                 trimmed_loop[icnt],
                 tmp_vec);
      VEC_COPY ( (&cap_loop[3*iloop]), sect);
      iloop ++;

      /* if nothing was ever trimmed, then we want to draw the 
       * cap the way the user asked for it -- closed or not closed.
       * Therefore, reset the closure flag to its original state.
       */
      if (!was_trimmed) {
         gleSetJoinStyle (save_style);
      }

      /* draw cap */
      (*cap_callback) (iloop, 
                       (gleDouble (*)[3]) cap_loop, 
                       front_color,
                       cut_vector,
                       bis_vector,
                       NULL,
                       face);
   }

   /* rest to the saved style */
   gleSetJoinStyle (save_style);
   free (cap_loop);
}

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

static void 
draw_fillets_and_join_n_norms
                    (int ncp, 
                    gleDouble trimmed_loop[][3],
                    gleDouble untrimmed_loop[][3], 
                    int is_trimmed[],
                    gleDouble bis_origin[3], 
                    gleDouble bis_vector[3], 
                    double normals[][3],
                    float front_color[3],
                    float back_color[3],
                    gleDouble cut_vector[3], 
                    int face,
                    gleCapCallback cap_callback)
{
   int istop;
   int icnt, icnt_prev, iloop;
   double *cap_loop, *norm_loop;
   gleDouble sect[3];
   gleDouble tmp_vec[3]; 
   int save_style = 0;
   int was_trimmed = FALSE;

   save_style = gleGetJoinStyle ();
   cap_loop = (double *) malloc (((size_t)ncp+3)*3*2*sizeof (double));
   norm_loop = cap_loop + (ncp+3)*3;
   
   /* 
    * If the first point is trimmed, keep going until one
    * is found that is not trimmed, and start join there. 
    */

   icnt = 0;
   iloop = 0;
   if (!is_trimmed[0]) {

      /* if the first point on the contour isn't trimmed, go ahead and
       * drop an edge down to the bisecting plane, (thus starting the 
       * join).  (Only need to do this for cut join, its bad if done for
       * round join).  (Also, leads to bugs when done for closed
       * contours ... do this only if contour is open).
       */
      if ((__TUBE_CUT_JOIN) && (!(save_style & TUBE_CONTOUR_CLOSED))) {
         VEC_SUM (tmp_vec, trimmed_loop[0], bis_vector);
         INNERSECT (sect, 
                    bis_origin,
                    bis_vector,
                    trimmed_loop[0],
                    tmp_vec);
         VEC_COPY ( (&cap_loop[3*iloop]), sect);
         VEC_COPY ( (&norm_loop[3*iloop]), normals[0]);
         iloop ++;
      }
      VEC_COPY ( (&cap_loop[3*iloop]), (trimmed_loop[0])); 
      VEC_COPY ( (&norm_loop[3*iloop]), normals[0]);
      iloop++;
      icnt_prev = icnt;
      icnt ++;
   } else {

      /* else, loop until an untrimmed point is found */
      was_trimmed = TRUE;
      while (is_trimmed[icnt]) {
         icnt_prev = icnt;
         icnt ++;
         if (icnt >= ncp) {
            free (cap_loop);
            return;    /* oops - everything was trimmed */
         }
      }
   }

   /* Start walking around the end cap.  Every time the end loop is
    * trimmed, we know we'll need to draw a fillet triangle.  In
    * addition, after every pair of visibility changes, we draw a cap. */
   if (__TUBE_CLOSE_CONTOUR) {
      istop = ncp;
   } else {
      istop = ncp-1;
   }

   /* save the join style, and disable a closed contour.
    * Need to do this so partial contours don't close up. */
   save_style = gleGetJoinStyle ();
   gleSetJoinStyle (save_style & ~TUBE_CONTOUR_CLOSED);

   for (; icnt_prev < istop; icnt_prev ++, icnt ++, icnt %= ncp) {

      /* There are four interesting cases for drawing caps and fillets:
       *    1) this & previous point were trimmed.  Don't do anything, 
       *       advance counter.
       *    2) this point trimmed, previous not -- draw fillet, and 
       *       draw cap.
       *    3) this point not trimmed, previous one was -- compute
       *       intersection point, draw fillet with it, and save 
       *       point for cap contour.
       *    4) this & previous point not trimmed -- save for endcap.
       */

      /* Case 1 -- noop, just advance pointers */
      if (is_trimmed[icnt_prev] && is_trimmed[icnt]) {
      }

      /* Case 2 --  Hah! first point! compute intersect & draw fillet! */
      if (is_trimmed[icnt_prev] && !is_trimmed[icnt]) {

         /* important note: the array "untrimmed" contains valid
          * untrimmed data ONLY when is_trim is TRUE.  Otherwise, 
          * only "trim" containes valid data */

         /* compute intersection */
         INNERSECT (sect, 
                    bis_origin,
                    bis_vector,
                    untrimmed_loop[icnt_prev],
                    trimmed_loop[icnt]);

         /* Draw Fillet */
         draw_fillet_triangle_n_norms (trimmed_loop[icnt_prev],
                               trimmed_loop[icnt],
                               sect,
                               face,
                               front_color,
                               back_color,
                               normals[icnt_prev],
                               normals[icnt]);
         VEC_COPY ( (&cap_loop[3*iloop]), sect);
         VEC_COPY ( (&norm_loop[3*iloop]), normals[icnt_prev]);
         iloop ++;
         VEC_COPY ( (&cap_loop[3*iloop]), (trimmed_loop[icnt])); 
         VEC_COPY ( (&norm_loop[3*iloop]), normals[icnt]);
         iloop++;
      }

      /* Case 3 -- add to collection of points */
      if (!is_trimmed[icnt_prev] && !is_trimmed[icnt]) {
         VEC_COPY ( (&cap_loop[3*iloop]), (trimmed_loop[icnt])); 
         VEC_COPY ( (&norm_loop[3*iloop]), normals[icnt]);
         iloop++; 
      } 

      /* Case 4 -- Hah! last point!  draw fillet & draw cap!  */
      if (!is_trimmed[icnt_prev] && is_trimmed[icnt]) {
         was_trimmed = TRUE;

         /* important note: the array "untrimmed" contains valid
          * untrimmed data ONLY when is_trim is TRUE.  Otherwise, 
          * only "trim" containes valid data */

         /* compute intersection */
         INNERSECT (sect, 
                    bis_origin,
                    bis_vector,
                    trimmed_loop[icnt_prev],
                    untrimmed_loop[icnt]);

         /* Draw Fillet */
         draw_fillet_triangle_n_norms (trimmed_loop[icnt_prev],
                               trimmed_loop[icnt],
                               sect,
                               face,
                               front_color,
                               back_color,
                               normals[icnt_prev],
                               normals[icnt]);

         VEC_COPY ( (&cap_loop[3*iloop]), sect);

         /* OK, maybe phong normals are wrong, but at least facet
          * normals will come out OK. */
         if (__TUBE_DRAW_FACET_NORMALS) {
            VEC_COPY ( (&norm_loop[3*iloop]), normals[icnt_prev]);
         } else {
            VEC_COPY ( (&norm_loop[3*iloop]), normals[icnt]);
         }
         iloop ++;

         /* draw cap */
         if (iloop >= 3) (*cap_callback) (iloop, 
                                          (gleVector *) cap_loop, 
                                          front_color,
                                          cut_vector,
                                          bis_vector,
                                          (gleVector *) norm_loop,
                                          face);

         /* reset cap counter */
         iloop = 0;
      }
   }

   /* now, finish up in the same way that we started. */

   icnt --;  /* decrement to make up for loop exit condititons */
   icnt += ncp;
   icnt %= ncp;
   if ((!is_trimmed[icnt]) && (iloop >= 2))  {
   
      /* If the last point of the contour is visible, drop an edge 
       * to the bisecting plane, thus finishing the join.  
       * Note that doing this leads to bugs if done for closed
       * contours ... do this only if contour is open.
       */
      if ((__TUBE_CUT_JOIN) && (!(save_style & TUBE_CONTOUR_CLOSED))) {
         VEC_SUM (tmp_vec, trimmed_loop[icnt], bis_vector);
         INNERSECT (sect, 
                    bis_origin,
                    bis_vector,
                    trimmed_loop[icnt],
                    tmp_vec);
         VEC_COPY ( (&cap_loop[3*iloop]), sect);
         VEC_COPY ( (&norm_loop[3*iloop]), normals[icnt]);
         iloop ++;
      }

      /* if nothing was ever trimmed, then we want to draw the 
       * cap the way the user asked for it -- closed or not closed.
       * Therefore, reset the closure flag to its original state.
       */
      if (!was_trimmed) {
         gleSetJoinStyle (save_style);
      }
      /* draw cap */
      (*cap_callback) (iloop, 
                       (gleVector *) cap_loop, 
                       front_color,
                       cut_vector,
                       bis_vector,
                       (gleVector *) norm_loop,
                       face);
   }

   /* rest to the saved style */
   gleSetJoinStyle (save_style);
   free (cap_loop);
}

/* ============================================================ */
/* This routine draws "cut" style extrusions.  
 */

void 
extrusion_round_or_cut_join (int ncp,	/* number of contour points */
                           gleDouble contour[][2],	/* 2D contour */
                           gleDouble cont_normal[][2],/* 2D normal vecs */
                           gleDouble up[3],	/* up vector for contour */
                           int npoints,		/* numpoints in poly-line */
                           gleDouble point_array[][3],	/* polyline */
                           gleColor color_array[],	/* color of polyline */
                           gleDouble xform_array[][2][3])   /* 2D contour xforms */
{
   int i, j;
   int inext, inextnext;
   gleDouble m[4][4];
   gleDouble tube_len, seg_len;
   gleDouble diff[3];
   gleDouble bi_0[3], bi_1[3];		/* bisecting plane */
   gleDouble bisector_0[3], bisector_1[3];		/* bisecting plane */
   gleDouble cut_0[3], cut_1[3];	/* cutting planes */
   gleDouble lcut_0[3], lcut_1[3];	/* cutting planes */
   int valid_cut_0, valid_cut_1;	/* flag -- cut vector is valid */
   gleDouble end_point_0[3], end_point_1[3]; 
   gleDouble torsion_point_0[3], torsion_point_1[3]; 
   gleDouble isect_point[3];
   gleDouble origin[3], neg_z[3];
   gleDouble yup[3];		/* alternate up vector */
   gleDouble *front_cap, *back_cap;	/* arrays containing the end caps */
   gleDouble *front_loop, *back_loop; /* arrays containing the tube ends */
   double *front_norm, *back_norm; /* arrays containing normal vecs */
   double *norm_loop=0x0, *tmp; /* normal vectors, cast into 3d from 2d */
   int *front_is_trimmed, *back_is_trimmed;   /* T or F */
   float *front_color, *back_color;  /* pointers to segment colors */
   gleCapCallback cap_callback = 0x0 ;  /* function callback to draw cap */
   gleCapCallback tmp_cap_callback = 0x0;  /* function callback to draw cap */
   int join_style_is_cut;      /* TRUE if join style is cut */
   double dot;                  /* partial dot product */
   char *mem_anchor;
   int first_time = TRUE;
   gleDouble *cut_vec;

   /* create a local, block scope copy of of the join style.
    * this will alleviate wasted cycles and register write-backs */
   /* choose the right callback, depending on the choosen join style */
   if (__TUBE_CUT_JOIN) {
      join_style_is_cut = TRUE;
      cap_callback =  draw_cut_style_cap_callback;
   } else {
      join_style_is_cut = FALSE;
      cap_callback =  draw_round_style_cap_callback;
   }

   /* By definition, the contour passed in has its up vector pointing in
    * the y direction */
   if (up == NULL) {
      yup[0] = 0.0;
      yup[1] = 1.0;
      yup[2] = 0.0;
   } else {
      VEC_COPY (yup, up);
   }

   /* ========== "up" vector sanity check ========== */
   (void) up_sanity_check (yup, npoints, point_array);

   /* the origin is at the origin */
   origin [0] = 0.0;
   origin [1] = 0.0;
   origin [2] = 0.0;

   /* and neg_z is at neg z */
   neg_z[0] = 0.0;
   neg_z[1] = 0.0;
   neg_z[2] = 1.0;

   /* malloc the data areas that we'll need to store the end-caps */
   mem_anchor = malloc (4 * 3*(size_t)ncp*sizeof(gleDouble)
                      + 2 * 3*(size_t)ncp*sizeof(double)
                      + 2 * 1*(size_t)ncp*sizeof(int));
   front_norm = (double *) mem_anchor;
   back_norm = front_norm + 3*ncp;
   front_loop = (gleDouble *) (back_norm + 3*ncp);
   back_loop = front_loop + 3*ncp;
   front_cap = back_loop + 3*ncp;
   back_cap  = front_cap + 3*ncp;
   front_is_trimmed = (int *) (back_cap + 3*ncp);
   back_is_trimmed = front_is_trimmed + ncp;

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

   /* |-|-|-|-|-|-|-|-| SET UP FOR FIRST SEGMENT |-|-|-|-|-|-|-| */

   /* ignore all segments of zero length */
   i = 1;
   inext = i;
   FIND_NON_DEGENERATE_POINT (inext, npoints, seg_len, diff, point_array);
   tube_len = seg_len;	/* store for later use */

   /* may as well get the normals set up now */
   if (cont_normal != NULL) {
      if (xform_array == NULL) {
         norm_loop = front_norm;
         back_norm = norm_loop;
         for (j=0; j<ncp; j++) {
            norm_loop[3*j] = cont_normal[j][0];
            norm_loop[3*j+1] = cont_normal[j][1];
            norm_loop[3*j+2] = 0.0;
         }
      } else {
         for (j=0; j<ncp; j++) {
            NORM_XFORM_2X2 ( (&front_norm[3*j]),
                              xform_array[inext-1],
                              cont_normal [j]);
            front_norm[3*j+2] = 0.0;
            back_norm[3*j+2] = 0.0;
         }
      }
   } else {
      front_norm = back_norm = norm_loop = NULL;
   }

   /* get the bisecting plane */
   bisecting_plane (bi_0, point_array[i-1], 
                          point_array[i], 
                          point_array[inext]);

   /* compute cutting plane */
   CUTTING_PLANE (valid_cut_0, cut_0, point_array[i-1], 
                         point_array[i], 
                         point_array[inext]);
   
   /* reflect the up vector in the bisecting plane */
   VEC_REFLECT (yup, yup, bi_0);

   /* |-|-|-|-|-|-|-|-| START LOOP OVER SEGMENTS |-|-|-|-|-|-|-| */

   /* draw tubing, not doing the first segment */
   while (inext<npoints-1) {

      inextnext = inext;
      /* ignore all segments of zero length */
      FIND_NON_DEGENERATE_POINT (inextnext, npoints, 
                                 seg_len, diff, point_array);

      /* get the far bisecting plane */
      bisecting_plane (bi_1, point_array[i], 
                             point_array[inext], 
                             point_array[inextnext]);


      /* compute cutting plane */
      CUTTING_PLANE (valid_cut_1, cut_1, point_array[i], 
                            point_array[inext], 
                            point_array[inextnext]);  

      /* rotate so that z-axis points down v2-v1 axis, 
       * and so that origen is at v1 */
      uviewpoint (m, point_array[i], point_array[inext], yup);
      PUSHMATRIX ();
      MULTMATRIX (m);

      /* rotate the cutting planes into the local coordinate system */
      MAT_DOT_VEC_3X3 (lcut_0, m, cut_0);
      MAT_DOT_VEC_3X3 (lcut_1, m, cut_1);

      /* rotate the bisecting planes into the local coordinate system */
      MAT_DOT_VEC_3X3 (bisector_0, m, bi_0);
      MAT_DOT_VEC_3X3 (bisector_1, m, bi_1);


      neg_z[2] = -tube_len;

      /* draw the tube */
      /* --------- START OF TMESH GENERATION -------------- */
      for (j=0; j<ncp; j++) {

         /* set up the endpoints for segment clipping */
         if (xform_array == NULL) {
            VEC_COPY_2 (end_point_0, contour[j]);
            VEC_COPY_2 (end_point_1, contour[j]);
            VEC_COPY_2 (torsion_point_0, contour[j]);
            VEC_COPY_2 (torsion_point_1, contour[j]);
         } else {
            /* transform the contour points with the local xform */
            MAT_DOT_VEC_2X3 (end_point_0,
                             xform_array[inext-1], contour[j]);
            MAT_DOT_VEC_2X3 (torsion_point_0,
                             xform_array[inext], contour[j]);
            MAT_DOT_VEC_2X3 (end_point_1,
                             xform_array[inext], contour[j]);
            MAT_DOT_VEC_2X3 (torsion_point_1,
                             xform_array[inext-1], contour[j]);

            /* if there are normals and there are affine xforms,
             * then compute local coordinate system normals.
             * Set up the back normals. (The front normals we inherit
             * from previous pass through the loop).  */
            if (cont_normal != NULL) {
               /* do up the normal vectors with the inverse transpose */
               NORM_XFORM_2X2 ( (&back_norm[3*j]),
                                xform_array[inext],
                                cont_normal [j]);
            }
         }
         end_point_0 [2] = 0.0;
         torsion_point_0 [2] = 0.0;
         end_point_1 [2] = - tube_len;
         torsion_point_1 [2] = - tube_len;

         /* The two end-points define a line.  Intersect this line
          * against the clipping plane defined by the PREVIOUS
          * tube segment.  */

         /* if this and the last tube are co-linear, don't cut the angle
          * if you do, a divide by zero will result.  This and last tube
          * are co-linear when the cut vector is of zero length */
         if (valid_cut_0 && join_style_is_cut) {
             INNERSECT (isect_point,  /* isect point (returned) */
                       origin,		/* point on intersecting plane */
                       lcut_0,		/* normal vector to plane */
                       end_point_0,	/* point on line */
                       end_point_1);	/* another point on the line */	

            /* determine whether the raw end of the extrusion would have
             * been cut, by checking to see if the raw and is on the 
             * far end of the half-plane defined by the cut vector.
             * If the raw end is not "cut", then it is "trimmed".
             */
            if (lcut_0[2] < 0.0) { VEC_SCALE (lcut_0, -1.0, lcut_0); }
            dot = lcut_0[0] * end_point_0[0];
            dot += lcut_0[1] * end_point_0[1];

            VEC_COPY ((&front_loop[3*j]), isect_point);
         } else {
            /* actual value of dot not interseting; need 
             * only be positive so that if test below failes */
            dot = 1.0;   
            VEC_COPY ((&front_loop[3*j]), end_point_0);
         }

         INNERSECT (isect_point, 	/* intersection point (returned) */
                    origin,		/* point on intersecting plane */
                    bisector_0,		/* normal vector to plane */
                    end_point_0,	/* point on line */
                    torsion_point_1);	/* another point on the line */	

         /* trim out interior of intersecting tube */
         /* ... but save the untrimmed version for drawing the endcaps */
         /* ... note that cap contains valid data ONLY when is_trimmed
          * is TRUE. */
         if ((dot <= 0.0) || (isect_point[2] < front_loop[3*j+2])) {
/*
         if ((dot <= 0.0) || (front_loop[3*j+2] > 0.0)) {
*/
            VEC_COPY ((&front_cap[3*j]), (&front_loop [3*j]));
            VEC_COPY ((&front_loop[3*j]), isect_point);
            front_is_trimmed[j] = TRUE;
         } else {
            front_is_trimmed[j] = FALSE;
         }

         /* if intersection is behind the end of the segment, 
          * truncate to the end of the segment
          * Note that coding front_loop [3*j+2] = -tube_len;
          * doesn't work when twists are involved, */
         if (front_loop[3*j+2] < -tube_len) {
            VEC_COPY( (&front_loop[3*j]), end_point_1);
         } 

         /* --------------------------------------------------- */
         /* The two end-points define a line.  We did one endpoint 
          * above. Now do the other.Intersect this line
          * against the clipping plane defined by the NEXT
          * tube segment.  */

         /* if this and the last tube are co-linear, don't cut the angle
          * if you do, a divide by zero will result.  This and last tube
          * are co-linear when the cut vector is of zero length */
         if (valid_cut_1 && join_style_is_cut) {
            INNERSECT (isect_point,  /* isect point (returned) */
                       neg_z,		/* point on intersecting plane */
                       lcut_1,		/* normal vector to plane */
                       end_point_1,	/* point on line */
                       end_point_0);	/* another point on the line */	

            if (lcut_1[2] > 0.0) { VEC_SCALE (lcut_1, -1.0, lcut_1); }
            dot = lcut_1[0] * end_point_1[0];
            dot += lcut_1[1] * end_point_1[1];

   
            VEC_COPY ((&back_loop[3*j]), isect_point);
         } else {
            /* actual value of dot not interseting; need 
             * only be positive so that if test below failes */
            dot = 1.0;   
            VEC_COPY ((&back_loop[3*j]), end_point_1);
         }

         INNERSECT (isect_point, 	/* intersection point (returned) */
                    neg_z,		/* point on intersecting plane */
                    bisector_1,		/* normal vector to plane */
                    torsion_point_0,	/* point on line */
                    end_point_1);	/* another point on the line */	

         /* cut out interior of intersecting tube */
         /* ... but save the uncut version for drawing the endcaps */
         /* ... note that cap contains valid data ONLY when is
          *_trimmed is TRUE. */
/*
        if ((dot <= 0.0) || (back_loop[3*j+2] < -tube_len)) {
*/
        if ((dot <= 0.0) || (isect_point[2] > back_loop[3*j+2])) {
            VEC_COPY ((&back_cap[3*j]), (&back_loop [3*j]));
            VEC_COPY ((&back_loop[3*j]), isect_point);
            back_is_trimmed[j] = TRUE;
         } else {
            back_is_trimmed[j] = FALSE;
         }

         /* if intersection is behind the end of the segment, 
          * truncate to the end of the segment 
          * Note that coding back_loop [3*j+2] = 0.0;
          * doesn't work when twists are involved, */
         if (back_loop[3*j+2] > 0.0) {
            VEC_COPY( (&back_loop[3*j]), end_point_0);
         } 
      }

      /* --------- END OF TMESH GENERATION -------------- */

      /* |||||||||||||||||| START SEGMENT DRAW |||||||||||||||||||| */
      /* There are six different cases we can have for presence and/or
       * absecnce of colors and normals, and for interpretation of
       * normals. The blechy set of nested if statements below
       * branch to each of the six cases */
      if (xform_array == NULL) {
         if (color_array == NULL) {
            if (cont_normal == NULL) {
               draw_segment_plain (ncp, (gleVector *) front_loop, (gleVector *) back_loop, inext, seg_len);
            } else
            if (__TUBE_DRAW_FACET_NORMALS) {
               draw_segment_facet_n (ncp, (gleVector *) front_loop, (gleVector *) back_loop, (gleVector *) norm_loop, 
                                     inext, seg_len);
            } else {
               draw_segment_edge_n (ncp, (gleVector *) front_loop, (gleVector *) back_loop, (gleVector *) norm_loop,
                                    inext, seg_len);
            }
         } else {
            if (cont_normal == NULL) {
               draw_segment_color (ncp, (gleVector *) front_loop, (gleVector *) back_loop, 
                                   color_array[inext-1],
                                   color_array[inext], inext, seg_len);
            } else
            if (__TUBE_DRAW_FACET_NORMALS) {
               draw_segment_c_and_facet_n (ncp, 
                                   (gleVector *) front_loop, (gleVector *) back_loop, (gleVector *) norm_loop,
                                   color_array[inext-1],
                                   color_array[inext], inext, seg_len);
            } else {
               draw_segment_c_and_edge_n (ncp, 
                                   (gleVector *) front_loop, (gleVector *) back_loop, (gleVector *) norm_loop,
                                   color_array[inext-1],
                                   color_array[inext], inext, seg_len);
             }
          }
      } else {
         if (color_array == NULL) {
            if (cont_normal == NULL) {
               draw_segment_plain (ncp, (gleVector *) front_loop, (gleVector *) back_loop, inext, seg_len);
            } else 
            if (__TUBE_DRAW_FACET_NORMALS) {
               draw_binorm_segment_facet_n (ncp, (gleVector *) front_loop, (gleVector *) back_loop,
                                                 (gleVector *) front_norm, (gleVector *) back_norm, 
                                                 inext, seg_len);
            } else {
               draw_binorm_segment_edge_n (ncp, (gleVector *) front_loop, (gleVector *) back_loop,
                                                (gleVector *) front_norm, (gleVector *) back_norm,
                                                inext, seg_len);
            }
         } else {
            if (cont_normal == NULL) {
               draw_segment_color (ncp, (gleVector *) front_loop, (gleVector *) back_loop, 
                                   color_array[inext-1],
                                   color_array[inext], inext, seg_len);
            } else
            if (__TUBE_DRAW_FACET_NORMALS) {
               draw_binorm_segment_c_and_facet_n (ncp, 
                                   (gleVector *) front_loop, (gleVector *) back_loop, 
                                   (gleVector *) front_norm, (gleVector *) back_norm, 
                                   color_array[inext-1],
                                   color_array[inext], inext, seg_len);
            } else {
               draw_binorm_segment_c_and_edge_n (ncp, 
                                   (gleVector *) front_loop, (gleVector *) back_loop,
                                   (gleVector *) front_norm, (gleVector *) back_norm, 
                                   color_array[inext-1],
                                   color_array[inext], inext, seg_len);
             }
          }
      }
      /* |||||||||||||||||| END SEGMENT DRAW |||||||||||||||||||| */

      /* v^v^v^v^v^v^v^v^v  BEGIN END CAPS v^v^v^v^v^v^v^v^v^v^v^v */

      /* if end caps are required, draw them. But don't draw any
       * but the very first and last caps */
      if (first_time) {
         first_time = FALSE;
         tmp_cap_callback = cap_callback;
         cap_callback = null_cap_callback;
         if (__TUBE_DRAW_CAP) {
            if (color_array != NULL) C3F (color_array[inext-1]);
            draw_angle_style_front_cap (ncp, bisector_0, 
                                       (gleVector *) front_loop);
         }
      }
      /* v^v^v^v^v^v^v^v^v  END END CAPS v^v^v^v^v^v^v^v^v^v^v^v */

      /* $$$$$$$$$$$$$$$$ BEGIN -1, FILLET & JOIN DRAW $$$$$$$$$$$$$$$$$ */
      /* 
       * Now, draw the fillet triangles, and the join-caps.
       */
      if (color_array != NULL) {
         front_color = color_array[inext-1];
         back_color = color_array[inext];
      } else {
         front_color = NULL;
         back_color = NULL;
      }

      if (cont_normal == NULL) {
         /* the flag valid-cut is true if the cut vector has a valid 
          * value (i.e. if a degenerate case has not occured). 
          */
         if (valid_cut_0) {
            cut_vec = lcut_0;
         } else {
            cut_vec = NULL;
         }
         draw_fillets_and_join_plain (ncp, 
                                  (gleVector *) front_loop,
                                  (gleVector *) front_cap, 
                                  front_is_trimmed,
                                  origin,
                                  bisector_0, 
                                  front_color,
                                  back_color,
                                  cut_vec,
                                  TRUE,
                                  cap_callback);

         /* v^v^v^v^v^v^v^v^v  BEGIN END CAPS v^v^v^v^v^v^v^v^v^v^v^v */
         if (inext == npoints-2) {
            if (__TUBE_DRAW_CAP) {
               if (color_array != NULL) C3F (color_array[inext]);
               draw_angle_style_back_cap (ncp, bisector_1, 
                                           (gleVector *) back_loop);
               cap_callback = null_cap_callback;
            }
         } else {
            /* restore ability to draw cap */
            cap_callback = tmp_cap_callback;
         }
         /* v^v^v^v^v^v^v^v^v  END END CAPS v^v^v^v^v^v^v^v^v^v^v^v */
   
         /* the flag valid-cut is true if the cut vector has a valid 
          * value (i.e. if a degenerate case has not occured). 
          */
         if (valid_cut_1) {
            cut_vec = lcut_1;
         } else {
            cut_vec = NULL;
         }
         draw_fillets_and_join_plain (ncp, 
                                  (gleVector *) back_loop,
                                  (gleVector *) back_cap, 
                                  back_is_trimmed,
                                  neg_z,
                                  bisector_1, 
                                  back_color,
                                  front_color,
                                  cut_vec,
                                  FALSE,
                                  cap_callback);
      } else {
   
         /* the flag valid-cut is true if the cut vector has a valid 
          * value (i.e. if a degenerate case has not occured). 
          */
         if (valid_cut_0) {
            cut_vec = lcut_0;
         } else {
            cut_vec = NULL;
         }
         draw_fillets_and_join_n_norms (ncp, 
                                  (gleVector *) front_loop,
                                  (gleVector *) front_cap, 
                                  front_is_trimmed,
                                  origin,
                                  bisector_0, 
                                  (gleVector *) front_norm,
                                  front_color,
                                  back_color,
                                  cut_vec,
                                  TRUE,
                                  cap_callback);
   
         /* v^v^v^v^v^v^v^v^v  BEGIN END CAPS v^v^v^v^v^v^v^v^v^v^v^v */
         if (inext == npoints-2) {
            if (__TUBE_DRAW_CAP) {
               if (color_array != NULL) C3F (color_array[inext]);
               draw_angle_style_back_cap (ncp, bisector_1, 
                                         (gleDouble (*)[3]) back_loop);
               cap_callback = null_cap_callback;
            }
         } else {
            /* restore ability to draw cap */
            cap_callback = tmp_cap_callback;
         }
         /* v^v^v^v^v^v^v^v^v  END END CAPS v^v^v^v^v^v^v^v^v^v^v^v */

         /* the flag valid-cut is true if the cut vector has a valid 
          * value (i.e. if a degenerate case has not occured). 
          */
         if (valid_cut_1) {
            cut_vec = lcut_1;
         } else {
            cut_vec = NULL;
         }
         draw_fillets_and_join_n_norms (ncp, 
                                  (gleVector *) back_loop,
                                  (gleVector *) back_cap, 
                                  back_is_trimmed,
                                  neg_z,
                                  bisector_1, 
                                  (gleVector *) back_norm,
                                  back_color,
                                  front_color,
                                  cut_vec,
                                  FALSE,
                                  cap_callback);
      }

      /* $$$$$$$$$$$$$$$$ END FILLET & JOIN DRAW $$$$$$$$$$$$$$$$$ */

      /* pop this matrix, do the next set */
      POPMATRIX ();

      /* slosh stuff over to next vertex */
      tmp = front_norm;
      front_norm = back_norm;
      back_norm = tmp;

      tube_len = seg_len;
      i = inext;
      inext = inextnext;
      VEC_COPY (bi_0, bi_1);
      VEC_COPY (cut_0, cut_1);
      valid_cut_0 = valid_cut_1;

      /* reflect the up vector in the bisecting plane */
      VEC_REFLECT (yup, yup, bi_0);
   }
   /* |-|-|-|-|-|-|-|-| END LOOP OVER SEGMENTS |-|-|-|-|-|-|-| */

   free (mem_anchor);

}
   
/* =================== END OF FILE =============================== */
