/*
 * Copyright (c) 1993-1997, Silicon Graphics, Inc.
 * ALL RIGHTS RESERVED
 * Permission to use, copy, modify, and distribute this software for
 * any purpose and without fee is hereby granted, provided that the above
 * copyright notice appear in all copies and that both the copyright notice
 * and this permission notice appear in supporting documentation, and that
 * the name of Silicon Graphics, Inc. not be used in advertising
 * or publicity pertaining to distribution of the software without specific,
 * written prior permission.
 *
 * THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
 * AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
 * FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL SILICON
 * GRAPHICS, INC.  BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
 * SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY
 * KIND, OR ANY DAMAGES WHATSOEVER, INCLUDING WITHOUT LIMITATION,
 * LOSS OF PROFIT, LOSS OF USE, SAVINGS OR REVENUE, OR THE CLAIMS OF
 * THIRD PARTIES, WHETHER OR NOT SILICON GRAPHICS, INC.  HAS BEEN
 * ADVISED OF THE POSSIBILITY OF SUCH LOSS, HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE
 * POSSESSION, USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * US Government Users Restricted Rights
 * Use, duplication, or disclosure by the Government is subject to
 * restrictions set forth in FAR 52.227.19(c)(2) or subparagraph
 * (c)(1)(ii) of the Rights in Technical Data and Computer Software
 * clause at DFARS 252.227-7013 and/or in similar or successor
 * clauses in the FAR or the DOD or NASA FAR Supplement.
 * Unpublished-- rights reserved under the copyright laws of the
 * United States.  Contractor/manufacturer is Silicon Graphics,
 * Inc., 2011 N.  Shoreline Blvd., Mountain View, CA 94039-7311.
 *
 * OpenGL(R) is a registered trademark of Silicon Graphics, Inc.
 */

/*
 *  tess.c
 *  This program demonstrates polygon tessellation.
 *  Two tesselated objects are drawn.  The first is a
 *  rectangle with a triangular hole.  The second is a
 *  smooth shaded, self-intersecting star.
 *
 *  Note the exterior rectangle is drawn with its vertices
 *  in counter-clockwise order, but its interior clockwise.
 *  Note the combineCallback is needed for the self-intersecting
 *  star.  Also note that removing the TessProperty for the
 *  star will make the interior unshaded (WINDING_ODD).
 */

//#include "glut_wrap.h"
//include <GLUT/glut.h>
#include <GL/glut.h>

#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>                    // va_list va_arg



#define GL_FAC_PLANAR 16


/// 3D-point, Typ_PT
typedef struct {double x, y, z;}                                    Point;
// size = 24


/// triangle, Typ_Tria
typedef struct {Point *pa[3];}                                      Triangle;
// size = 12; 3 pointers !


GLuint startList;


#define COL_RED    0
#define COL_YELLOW 1
#define COL_SUR    2

static GLfloat colTab [][4] = {
  1.0, 0.0, 0.0, 1.0,
  1.0, 1.0, 0.0, 1.0,
  0.6, 0.5, 0.5, 1.0};


static int   GLU_Err=0;

#define GLC_PT_MAX 1000  // point-set (including combine-points)
static Point GLC_pa[GLC_PT_MAX];
static int GLC_pNr;

#define GLP_PT_MAX 1000  // patch-points
static Point GLP_pa[GLP_PT_MAX];
static int GLP_pNr, GLP_typ;

#define GLP_TRI_MAX 300
static Triangle GLP_ta[GLP_TRI_MAX];


#ifdef _MSC_VER
#define GLU_CB static void CALLBACK
#else
#define GLU_CB static void
#endif


GLU_CB GLCB2_error (GLenum errNo);
GLU_CB GLCB2_begin (GLenum type);
GLU_CB GLCB2_vertex (GLdouble*);
GLU_CB GLCB2_combi (GLdouble*, GLdouble**, GLfloat*, GLdouble**);
GLU_CB GLCB2_edge (GLboolean flag);


int TX_Error(char* txt, ...) {
    va_list va;
    va_start(va,txt);
    printf("*** Fehler: ");
    vprintf(txt, va);
    printf("\n");
  return 0;

}


//================================================================
  int UTRI_triaNr_glpat (int ptNr, int patchTyp) {
//================================================================
/// \code
/// UTRI_triaNr_glpat     get nr of triangles for opengl-patch
/// Input:
///   ptNr     nr of points
///   patchTyp GL_TRIANGLE_STRIP|GL_TRIANGLE_FAN|GL_QUAD_STRIP
///
/// see TSU_facNr_ipatch
/// see tess_siz2
/// \endcode

  int   tNr;


    switch (patchTyp) {

      case GL_TRIANGLES:
        tNr = ptNr / 3;
        break;

      case GL_TRIANGLE_STRIP:
      case GL_FAC_PLANAR:
      case GL_TRIANGLE_FAN:
      case GL_QUAD_STRIP:
        tNr = ptNr - 2;
        break;

      default:
        TX_Error("UTRI_triaNr_glpat NYI typ=%d",patchTyp);
        return -1;
    }


    // printf("ex UTRI_triaNr_glpat %d\n",tNr);


  return tNr;

}

//=======================================================================
  int UTRI_ntria_glpat (Triangle *tTab, int *tSiz,
                        Point *pTab, int pNr, int pTyp) {
//=======================================================================
// get triangles from opengl-patch (pTab, pNr, pTyp)
// Input:
//   tSiz   size of tTab
//   pTyp   patchTyp GL_TRIANGLE_STRIP|GL_TRIANGLE_FAN|GL_QUAD_STRIP
// Output:
//   tSiz   nr of Triangles created
// 
// find min size of tTab: UTRI_triaNr_glpat()
// see TSU_itria_ipatch TSU_itria_ipatch tess_ntri_tfac__

  int      i1, ii, tNr;


  ii = 0;

  switch (pTyp) {

    //================================================================
    case GL_TRIANGLES:  // 4

      tNr = pNr / 3;           // total nr of triangles
      if(tNr > *tSiz) goto L_errEOM;

      L_T0:
        tTab->pa[0] = pTab;  ++pTab;
        tTab->pa[1] = pTab;  ++pTab;
        tTab->pa[2] = pTab;  ++pTab;
        ++tTab;
        ++ii;
        if(ii < tNr) goto L_T0;

      break;


    //================================================================
    case GL_FAC_PLANAR:      // 16:   Achtung: additinal for indexed 
    case GL_TRIANGLE_FAN:    // 6
      //    1------2           ptAnz = 4
      //    |    /  |
      //    |   /   |
      //    |  /    |
      //    | /     |
      //    0.------3
      //      \     /
      //        \  /
      //         4
      //
      // sollte so zerlegt werden:
      // 0 1 2
      // 0 2 3
      // 0 3 4

      tNr = pNr - 2;
      if(tNr > *tSiz) goto L_errEOM;
      i1 = 1;

      L_T6:
        tTab->pa[0] = &pTab[0];
        tTab->pa[1] = &pTab[i1]; ++i1;
        tTab->pa[2] = &pTab[i1];
        ++tTab;
        ++ii;
        if(ii < tNr) goto L_T6;

      break;


    //================================================================
    case GL_TRIANGLE_STRIP:  // 5
    case GL_QUAD_STRIP:      // 8
      //
      //    0--2--4--6
      //    | /| /| /|
      //    |/ |/ |/ |
      //    1--3--5--7
      //
      // sollte so zerlegt werden:
      // 0 1 2
      // 2 1 3
      // 2 3 4
      // 4 3 5 ..

      tNr = pNr - 2;
      if(tNr > *tSiz) goto L_errEOM;
      i1 = 0;

      L_T8:
        tTab->pa[0] = &pTab[i1]; ++i1;
        tTab->pa[1] = &pTab[i1]; ++i1;
        tTab->pa[2] = &pTab[i1];
        ++tTab;
        ++ii;
        if(ii >= tNr) break;

        tTab->pa[0] = &pTab[i1];
        tTab->pa[1] = &pTab[i1-1];
        tTab->pa[2] = &pTab[i1+1];
        ++tTab;
        ++ii;
        if(ii < tNr) goto L_T8;

      break;


    //================================================================
    default:
      TX_Error("UTRI_ntria_glpat E001 %d", pTyp);
      return -1;

  }

  *tSiz = tNr;

  return 0;


  L_errEOM:
    TX_Error("UTRI_ntria_glpat EOM");
    return -1;



  return 0;

}


//========================================================================
  int tst_gl2_point_d (Point *GLC_pab, int pNr, GLfloat *glCol) {
//========================================================================
// draw pNr points into open glList 

  int   ii, ip;

  glDisable(GL_LIGHTING);

  glPointSize (5.0);

  // glColor3f(1.0, 1.0, 0.0);
  glColor3fv (glCol);

  glBegin(GL_POINTS);
  for(ii=0; ii<pNr; ++ii) {
      // printf(" pt[%d]= %f %f %f\n",ip,GLC_pab[ip], GLC_pab[ip+1], GLC_pab[ip+2]);
    glVertex3dv ((double*)&GLC_pab[ii]);
  }
  glEnd();

  glEnable(GL_LIGHTING);

  return 0;

}

//================================================================
  int tst_gl2_patch__ (Point *GLC_pab, int pNr, int pTyp) {
//================================================================
// display triangles of OpenGL-patch

static int ti=0;

  int      irc, i1, tNr;


  printf("tst_gl2_patch__ ti=%d\n",ti);


  // get tNr = nr of triangles for this patch
  tNr = UTRI_triaNr_glpat (pNr, pTyp);
  if(tNr < 1) {printf("***** tst_gl2_patch__ E001\n"); return -1;}


  // get space for <tNr> triangles
  // triTab = (Triangle*) MEM_alloc_tmp (tNr * sizeof(Triangle));


  // get <tNr> triangles from patch (GLC_pab, pNr, pTyp)
  tNr = GLP_TRI_MAX;
  irc = UTRI_ntria_glpat (GLP_ta, &tNr, GLC_pab, pNr, pTyp);
  if(irc < 0) return -1;


  // display triangles
  for(i1=0; i1<tNr; ++i1) {
    printf(" ti=%d p1 %f %f %f\n", ti,
           GLP_ta[i1].pa[0]->x,GLP_ta[i1].pa[0]->y,GLP_ta[i1].pa[0]->z);
    printf(" ti=%d p2 %f %f %f\n", ti,
           GLP_ta[i1].pa[1]->x,GLP_ta[i1].pa[1]->y,GLP_ta[i1].pa[1]->z);
    printf(" ti=%d p3 %f %f %f\n", ti,
           GLP_ta[i1].pa[2]->x,GLP_ta[i1].pa[2]->y,GLP_ta[i1].pa[2]->z);
    // GR_Disp_tria (&GLP_ta[i1], 9);
    // GR_Disp_triv  (&GLP_ta[i1], 3, ti, -1);
    ++ti;
    // GR_Disp_tris (&GLP_ta[i1], 8);
  }

  return 0;

}



//================================================================
  GLU_CB GLCB2_begin (GLenum type) {
//================================================================
   printf ("glBegin(");

   // glBegin (type);  //  resubmit rendering directive

  GLP_pNr = 0;
  GLP_typ = type;


   switch (type) {  /*  print diagnostic message  */
     case GL_LINES:
       printf ("GL_LINES)\n");
       break;
     case GL_LINE_LOOP:
       printf ("GL_LINE_LOOP)\n");
       break;
     case GL_LINE_STRIP:
       printf ("GL_LINE_STRIP)\n");
       break;
     case GL_TRIANGLES:
       printf ("GL_TRIANGLES)\n");
       break;
     case GL_TRIANGLE_STRIP:
       printf ("GL_TRIANGLE_STRIP)\n");
       break;
     case GL_TRIANGLE_FAN:
       printf ("GL_TRIANGLE_FAN)\n");
       break;
     case GL_QUADS:
       printf ("GL_QUADS)\n");
       break;
     case GL_QUAD_STRIP:
       printf ("GL_QUAD_STRIP)\n");
       break;
     case GL_POLYGON:
       printf ("GL_POLYGON)\n");
       break;
     default:
   break;
   }
}


//=========================================================
  GLU_CB GLCB2_error (GLenum errNo) {
//=========================================================

  const GLubyte *estring;

  printf("****** GLCB2_error %d\n",errNo);

  estring = gluErrorString(errNo);
  fprintf (stderr, "Nurbs Error: %s\n", estring);
  GLU_Err = -1;
  // exit (0);


}


//================================================================
  GLU_CB GLCB2_end () {
//================================================================

   printf ("glEnd()\n");
   // printf ("glEnd() ptNr=%d\n",ptNr);

  // display as points
  tst_gl2_point_d (GLP_pa, GLP_pNr, (GLfloat*)&colTab[COL_YELLOW]);


  // display triangles
  tst_gl2_patch__ (GLP_pa, GLP_pNr, GLP_typ);


}


//================================================================
  GLU_CB GLCB2_vertex (GLdouble *vertex) {
//================================================================


  int   ipt;


   printf ("glVertex3f (%5.3f, %5.3f, %5.3f - %d)\n",
     vertex[0], vertex[1], vertex[2], GLP_pNr);

  if(GLU_Err) return;


  // find point-index. For this all points (including combine-points)
  // must be in the same set.
  ipt = ((char*)vertex - (char*)GLC_pa) / sizeof(Point);
    printf(" point-index-ipt=%d\n",ipt);


  GLP_pa[GLP_pNr].x = vertex[0];
  GLP_pa[GLP_pNr].y = vertex[1];
  GLP_pa[GLP_pNr].z = vertex[2];

  if(GLP_pNr < GLP_PT_MAX) ++GLP_pNr;
  else printf("********* ERROR GLCB2_vertex EOF\n");

}


//=========================================================
  GLU_CB GLCB2_combi (GLdouble newPt[3],
                     GLdouble *data[4],
                     GLfloat  weight[4],
                     GLdouble **dataOut) {
//=========================================================
// MUST set dataOut to address of static stored vertex newPt !
// This points must be kept until end of tesselation.


  printf(" GLCB2_combi %lf %lf %lf - %d\n",
    newPt[0], newPt[1], newPt[2], GLC_pNr);

  if(GLU_Err) return;

  GLC_pa[GLC_pNr].x = newPt[0];
  GLC_pa[GLC_pNr].y = newPt[1];
  GLC_pa[GLC_pNr].z = newPt[2];


  // set dataOut to address of static stored vertex newPt
  *dataOut = (GLdouble*)&GLC_pa[GLC_pNr];


  if(GLC_pNr < GLC_PT_MAX) ++GLC_pNr;
  else printf("********* ERROR GLCB2_combi EOF\n");


  return;
}


//================================================================
static void init (void) {
//================================================================

  GLUtesselator *ot1;
  int           i1, i2, ii, ip1, idx, idy;
  Point pta[] =
    {{ 0.0,  0.0, 0.0},  // 0  OB
     { 3.0,  0.0, 0.0},
     { 7.0,  0.0, 0.0},
     {10.0,  0.0, 0.0},
    
     { 0.0,  3.0, 0.0},  // 4
     { 3.0,  3.0, 0.0},     
     { 7.0,  3.0, 0.0},
     {10.0,  3.0, 0.0},

     { 0.0,  7.0, 0.0},  // 8
     { 3.0,  7.0, 0.0},
     { 7.0,  7.0, 0.0},
     {10.0,  7.0, 0.0},

     { 0.0, 10.0, 0.0}, // 12
     { 3.0, 10.0, 0.0},
     { 7.0, 10.0, 0.0},
     {10.0, 10.0, 0.0},


     { 1.0,  3.0, 0.0},  // 16  IB
     { 5.0,  5.0, 0.0},
     { 5.0,  1.0, 0.0}};



  // get all points into pointset GLC_pa
  GLC_pNr = sizeof(pta)/sizeof(Point);
  for(i1=0; i1<GLC_pNr; ++i1) {
    GLC_pa[i1] = pta[i1];
  }


   glClearColor(0.0, 0.0, 0.0, 0.0);

   startList = glGenLists(2);

   ot1 = gluNewTess();

  // define the callback-functions
  gluTessCallback (ot1, GLU_TESS_BEGIN,      (void*)&GLCB2_begin);
  gluTessCallback (ot1, GLU_TESS_VERTEX,   (void*)&GLCB2_vertex);
  // gluTessCallback (ot1, GLU_TESS_VERTEX_DATA,(void*)&GLCB2_iData);
  gluTessCallback (ot1, GLU_TESS_COMBINE,    (void*)&GLCB2_combi);
  // gluTessCallback (ot1, GLU_TESS_EDGE_FLAG,  (void*)&GLCB2_edge);
  gluTessCallback (ot1, GLU_TESS_END,        (void*)&GLCB2_end);
  gluTessCallback (ot1, GLU_TESS_ERROR,      (void*)&GLCB2_error);


   glNewList(startList, GL_COMPILE);
   glShadeModel(GL_FLAT);

  gluTessBeginPolygon (ot1, NULL);

  // outer-boundary horizontal must be CCW;
  for(i1=0; i1<2; ++i1) { // vert loop
    ip1 = i1 * 4;
    for(i2=0; i2<2; ++i2) { // hor loop
        printf(" nxt rect %d\n",ip1);
      gluTessBeginContour (ot1);
        gluTessVertex (ot1, (double*)&GLC_pa[ip1], (double*)&GLC_pa[ip1]);
        gluTessVertex (ot1, (double*)&GLC_pa[ip1+1], (double*)&GLC_pa[ip1+1]);
        gluTessVertex (ot1, (double*)&GLC_pa[ip1+5], (double*)&GLC_pa[ip1+5]);
        gluTessVertex (ot1, (double*)&GLC_pa[ip1+4], (double*)&GLC_pa[ip1+4]);
      gluTessEndContour (ot1);
      ip1 += 1;
    }
  }


  ip1 = 16;
  // inner-boundary must be CW;
    gluTessBeginContour (ot1);
      gluTessVertex (ot1, (double*)&GLC_pa[ip1], (double*)&GLC_pa[ip1]);
      gluTessVertex (ot1, (double*)&GLC_pa[ip1+1], (double*)&GLC_pa[ip1+1]);
      gluTessVertex (ot1, (double*)&GLC_pa[ip1+2], (double*)&GLC_pa[ip1+2]);
    gluTessEndContour (ot1);

   gluTessEndPolygon(ot1);

   glEndList();

   gluDeleteTess(ot1);
}


//================================================================
static void display (void) {
//================================================================

   glClear(GL_COLOR_BUFFER_BIT);
   glColor3f(1.0, 1.0, 1.0);

   glCallList(startList);
   glCallList(startList + 1);
   glFlush();
}


//================================================================
static void reshape (int w, int h) {
//================================================================

  printf("reshape %d %d\n",w,h);


   glViewport(0, 0, (GLsizei) w, (GLsizei) h);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   gluOrtho2D(0.0, (GLdouble) w, 0.0, (GLdouble) h);
}


//================================================================
static void keyboard(unsigned char key, int x, int y) {
//================================================================
   switch (key) {
      case 27:
         exit(0);
         break;
   }
}


//================================================================
int main(int argc, char** argv) {
//================================================================

   glutInit(&argc, argv);
   glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
   glutInitWindowSize(500, 500);
   glutCreateWindow(argv[0]);
   init();
   glutDisplayFunc(display);
   glutReshapeFunc(reshape);
   glutKeyboardFunc(keyboard);
   glutMainLoop();
   return 0;
}


// EOF
