/*******************************************************************************
*                                                                              *
*                                   Viewmol                                    *
*                                                                              *
*                             F E E D B A C K . C                              *
*                                                                              *
*                 Copyright (c) Joerg-R. Hill, October 2003                    *
*                                                                              *
********************************************************************************
*
* $Id: feedback.c,v 1.5 2003/11/07 11:01:52 jrh Exp $
* $Log: feedback.c,v $
* Revision 1.5  2003/11/07 11:01:52  jrh
* Release 2.4
*
* Revision 1.4  2000/12/10 15:07:06  jrh
* Release 2.3
*
* Revision 1.3  1999/05/24 01:25:26  jrh
* Release 2.2.1
*
* Revision 1.2  1999/02/07 21:49:06  jrh
* Release 2.2
*
* Revision 1.1  1998/01/26 00:34:59  jrh
* Initial revision
*
*/  
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include "viewmol.h"

/* OpenGL's GL_3D_COLOR feedback vertex format. */
typedef struct _Feedback3Dcolor {
  GLfloat x;
  GLfloat y;
  GLfloat z;
  GLfloat red;
  GLfloat green;
  GLfloat blue;
  GLfloat alpha;
} Feedback3Dcolor;

typedef struct _Feedback3DindexedColor {
  GLfloat x;
  GLfloat y;
  GLfloat z;
  GLfloat color;
} Feedback3DindexedColor;

typedef struct _DepthIndex {
  GLfloat *ptr;
  GLfloat depth;
} DepthIndex;

void (*setRGBColor)(FILE *, GLfloat, GLfloat, GLfloat);
void (*moveto)(FILE *, int, GLfloat, GLfloat);
void (*lineto)(FILE *, int, GLfloat, GLfloat);
void (*triangle)(FILE *, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat);
void (*triangleColor)(FILE *, GLfloat, GLfloat, GLfloat);
void (*point)(FILE *, int, GLfloat, GLfloat);
void (*lineStipple)(FILE *, GLushort);
void (*showString)(FILE *, int, GLfloat, GLfloat);

void feedbackString(char *, double, double, double, double, GLuint, char);
void feedbackLineStipple(GLint, GLushort);
void feedbackLineWidth(GLfloat);
void feedbackDisable(GLenum);
double visibility(GLfloat *);

extern void PostscriptSetRGBColor(FILE *, GLfloat, GLfloat, GLfloat);
extern void PostscriptMoveto(FILE *, int, GLfloat, GLfloat);
extern void PostscriptLineto(FILE *, int, GLfloat, GLfloat);
extern void PostscriptTriangle(FILE *, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat);
extern void PostscriptTriangleColor(FILE *, GLfloat, GLfloat, GLfloat);
extern void PostscriptPoint(FILE *, int, GLfloat, GLfloat);
extern void PostscriptLineStipple(FILE *, GLushort);
extern void PostscriptShowString(FILE *, int, GLfloat, GLfloat);

extern void hpglSetRGBColor(FILE *, GLfloat, GLfloat, GLfloat);
extern void hpglMoveto(FILE *, int, GLfloat, GLfloat);
extern void hpglLineto(FILE *, int, GLfloat, GLfloat);
extern void hpglTriangle(FILE *, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat, GLfloat);
extern void hpglTriangleColor(FILE *, GLfloat, GLfloat, GLfloat);
extern void hpglPoint(FILE *, int, GLfloat, GLfloat);
extern void hpglLineStipple(FILE *, GLushort);
extern void hpglShowString(FILE *, int, GLfloat, GLfloat);

extern void *getmem(size_t, size_t);
extern void fremem(void **);
extern void getRGBColor(Widget, Pixel, GLfloat *, GLfloat *, GLfloat *);
extern void vectorProduct(double *, double *, double *);
extern double scalarProduct(double *, double *);

extern struct WINDOW windows[];
extern int rgbMode;
extern char *text, *textPointer;

static GLfloat lastred, lastgreen, lastblue;

GLfloat *writePrimitives(FILE *file, GLfloat *loc, int window, int format, int portrait)
{
  int token;
  int nvertices, i;
  GLfloat red, green, blue, alpha=0.0, dr, dg, db;
  int smooth, triOffset;
  Feedback3Dcolor *vertexC=(Feedback3Dcolor *)0;
  Feedback3DindexedColor *vertexI=(Feedback3DindexedColor *)0;

  token=*loc;
  loc++;
  switch (token)
  {
    case GL_LINE_RESET_TOKEN:
    case GL_LINE_TOKEN:         if (rgbMode)
                                {
                                  vertexC=(Feedback3Dcolor *)loc;
                                  if (vertexC[1].alpha != 1.0)
                                    (*setRGBColor)(file, vertexC[1].alpha, vertexC[1].alpha, vertexC[1].alpha);
                                  else
                                    (*setRGBColor)(file, vertexC[1].red, vertexC[1].green, vertexC[1].blue);
                                  (*moveto)(file, portrait, vertexC[0].x, vertexC[0].y);
                                  (*lineto)(file, portrait, vertexC[1].x, vertexC[1].y);
                                  loc+=14; /* Each vertex element in the feedback buffer is 7 GLfloats. */
                                }
                                else
                                {
                                  vertexI=(Feedback3DindexedColor *)loc;
                                  getRGBColor(windows[window].widget, (Pixel)vertexI[1].color, &red, &green, &blue);
                                  (*setRGBColor)(file, red, green, blue);
                                  (*moveto)(file, portrait, vertexI[0].x, vertexI[0].y);
                                  (*lineto)(file, portrait, vertexI[1].x, vertexI[1].y);
                                  loc+=8;  /* Each vertex element in the feedback buffer is 4 GLfloats. */
                                }
                                if (format == POSTSCRIPT) fprintf(file, "stroke\n");
                                break;
    case GL_POLYGON_TOKEN:      nvertices=*loc;
                                loc++;
                                if (nvertices > 0)
                                {
                                  if (rgbMode)
                                  {
                                    vertexC=(Feedback3Dcolor *)loc;
                                    red=vertexC[0].red;
                                    green=vertexC[0].green;
                                    blue=vertexC[0].blue;
                                    alpha=vertexC[0].alpha;
                                  }
                                  else
                                  {
                                    vertexI=(Feedback3DindexedColor *)loc;
                                    getRGBColor(windows[window].widget, (Pixel)vertexI[0].color, &red, &green, &blue);
                                  }
                                  smooth=FALSE;
                                  if (alpha == 1.0)
                                  {
                                    for (i=1; i<nvertices; i++)
                                    {
                                      if (rgbMode)
                                      {
                                        dr=vertexC[i].red;
                                        dg=vertexC[i].green;
                                        db=vertexC[i].blue;
                                      }
                                      else
                                        getRGBColor(windows[window].widget, (Pixel)vertexI[i].color, &dr, &dg, &db);
                                      if (red != dr || green != dg || blue != db)
                                      {
                                        smooth=TRUE;
                                        break;
                                      }
                                    }
                                  }
                                  else
                                    red=green=blue=alpha;
                                  if (smooth)
                                  {
                                    /* Smooth shaded polygon; varying colors at vertices. */
                                    /* Break polygon into "nvertices-2" triangle fans. */
                                    for (i=0; i<nvertices-2; i++)
                                    {
                                      if (rgbMode)
                                      {
                                        triOffset=i*7;
                                        (*triangle)(file, vertexC[0].x, vertexC[i+1].x, vertexC[i+2].x,
                                                    vertexC[0].y, vertexC[i+1].y, vertexC[i+2].y);
                                        (*triangleColor)(file, vertexC[0].red, vertexC[0].green, vertexC[0].blue);
                                        (*triangleColor)(file, vertexC[i+1].red, vertexC[i+1].green, vertexC[i+1].blue);
                                        (*triangleColor)(file, vertexC[i+2].red, vertexC[i+2].green, vertexC[i+2].blue);
                                      }
                                      else
                                      {
                                        triOffset=i*4;
                                        (*triangle)(file, vertexI[0].x, vertexI[i+1].x, vertexI[i+2].x,
                                                    vertexI[0].y, vertexI[i+1].y, vertexI[i+2].y);
                                        getRGBColor(windows[window].widget, (Pixel)vertexI[0].color, &red, &green, &blue);
                                        (*triangleColor)(file, red, green, blue);
                                        getRGBColor(windows[window].widget, (Pixel)vertexI[i+1].color, &red, &green, &blue);
                                        (*triangleColor)(file, red, green, blue);
                                        getRGBColor(windows[window].widget, (Pixel)vertexI[i+2].color, &red, &green, &blue);
                                        (*triangleColor)(file, red, green, blue);
                                      }
                                    }
                                    lastred=lastgreen=lastblue=(-1.);
                                  }
                                  else
                                  {
                                    /* Flat shaded polygon; all vertex colors the same. */
                                    if (format == POSTSCRIPT)
                                      fprintf(file, "newpath\n");
                                    (*setRGBColor)(file, red, green, blue);

                                    /* Draw a filled polygon. */
                                    if (rgbMode)
                                      (*moveto)(file, portrait, vertexC[0].x, vertexC[0].y);
                                    else
                                      (*moveto)(file, portrait, vertexI[0].x, vertexI[0].y);
                                    for (i=1; i<nvertices; i++)
                                    {
                                      if (rgbMode)
                                        (*lineto)(file, portrait, vertexC[i].x, vertexC[i].y);
                                      else
                                        (*lineto)(file, portrait, vertexI[i].x, vertexI[i].y);
                                    }
                                    if (format == POSTSCRIPT)
                                      fprintf(file, "closepath fill\n");
                                  }
                                }
                                if (rgbMode)
                                  loc+=nvertices*7;  /* Each vertex element in the
                                                        feedback buffer is 7 GLfloats. */
                                else
                                  loc+=nvertices*4;  /* Each vertex element in the
                                                          feedback buffer is 4 GLfloats. */
                                break;
    case GL_POINT_TOKEN:        if (rgbMode)
                                {
                                  vertexC=(Feedback3Dcolor *)loc;
                                  if (vertexC[0].alpha != 1.0)
                                    (*setRGBColor)(file, vertexC[0].alpha, vertexC[0].alpha, vertexC[0].alpha);
                                  else
                                    (*setRGBColor)(file, vertexC[0].red, vertexC[0].green, vertexC[0].blue);
                                  (*point)(file, portrait, vertexC[0].x, vertexC[0].y);
                                  loc+=7;           /* Each vertex element in the feedback
                                                       buffer is 7 GLfloats. */
                                }
                                else
                                {
                                  vertexI=(Feedback3DindexedColor *)loc;
                                  getRGBColor(windows[window].widget, (Pixel)vertexI[0].color, &red, &green, &blue);
                                  (*setRGBColor)(file, red, green, blue);
                                    (*point)(file, portrait, vertexI[0].x, vertexI[0].y);
                                  loc+=4;           /* Each vertex element in the feedback
                                                       buffer is 4 GLfloats. */
                                }
                                break;
    case GL_BITMAP_TOKEN:
    case GL_COPY_PIXEL_TOKEN:
    case GL_DRAW_PIXEL_TOKEN:   if (rgbMode)
                                {
                                  vertexC=(Feedback3Dcolor *)loc;
                                  if (vertexC[0].alpha != 1.0)
                                    (*setRGBColor)(file, vertexC[0].alpha, vertexC[0].alpha, vertexC[0].alpha);
                                  else
                                    (*setRGBColor)(file, vertexC[0].red, vertexC[0].green, vertexC[0].blue);
                                  (*showString)(file, portrait, vertexC[0].x, vertexC[0].y);
                                  loc+=7;
                                }
                                else
                                {
                                  vertexI=(Feedback3DindexedColor *)loc;
                                  getRGBColor(windows[window].widget, (Pixel)vertexI[0].color, &red, &green, &blue);
                                  (*setRGBColor)(file, red, green, blue);
                                  (*showString)(file, portrait, vertexI[0].x, vertexI[0].y);
                                  loc+=4;
                                }
                                break;
    case GL_PASS_THROUGH_TOKEN: i=((int)(*loc) & 0xffff0000);
                                switch (i)
                                {
                                  case LINE_STIPPLE: (*lineStipple)(file, (GLushort)(*loc) & 0xffff);
                                                     break;
                                  case LINE_WIDTH:   if (format == POSTSCRIPT)
                                                       fprintf(file, "%d setlinewidth\n", (int)(*loc) & 0xffff);
                                                     break;
                                }
                                loc+=1;
                                break;
    default:                    /* If we arrive here OpenGL has been changed. */
                                break;
  }
  return(loc);
}

static int compare(const void *a, const void *b)
{
  DepthIndex *p1=(DepthIndex *)a;
  DepthIndex *p2=(DepthIndex *)b;
  GLfloat diff=p2->depth-p1->depth;

  if (diff > 0.0)
    return(1);
  else if (diff < 0.0)
    return(-1);
  else
    return(0);
}

void writeFeedback(FILE * file, GLint size, GLfloat *buffer, int window,
                   int format, int smooth, int portrait)
{
  GLfloat *loc, *end;
  Feedback3Dcolor *vertexC;
  Feedback3DindexedColor *vertexI;
  GLfloat depthSum, visible, addDepth=0.0, textDepth=0.0;
  DepthIndex *prims;
  int token, nprimitives, item, checkVisibility=TRUE;
  int nvertices;
  register int i;

  switch (format)
  {
    case HPGL:       setRGBColor=hpglSetRGBColor;
                     moveto=hpglMoveto;
                     lineto=hpglLineto;
                     triangle=hpglTriangle;
                     triangleColor=hpglTriangleColor;
                     point=hpglPoint;
                     lineStipple=hpglLineStipple;
                     showString=hpglShowString;
                     break;
    case POSTSCRIPT: setRGBColor=PostscriptSetRGBColor;
                     moveto=PostscriptMoveto;
                     lineto=PostscriptLineto;
                     triangle=PostscriptTriangle;
                     triangleColor=PostscriptTriangleColor;
                     point=PostscriptPoint;
                     lineStipple=PostscriptLineStipple;
                     showString=PostscriptShowString;
                     break;
  }

  end=buffer+size;
  textPointer=text;
  lastred=lastgreen=lastblue=(-1.);

  /* Count how many primitives there are. */
  nprimitives=0;
  loc=buffer;
  if (smooth)
  {
    while (loc < end)
    {
      token = *loc;
      loc++;
      switch (token)
      {
        case GL_LINE_TOKEN:
        case GL_LINE_RESET_TOKEN:   if (rgbMode) loc+=14;
                                    else         loc+=8;
                                    nprimitives++;
                                    break;
        case GL_POLYGON_TOKEN:      nvertices=*loc;
                                    loc++;
                                    if (rgbMode) loc+=(7*nvertices);
                                    else         loc+=(4*nvertices);
                                    nprimitives++;
                                    break;
        case GL_POINT_TOKEN:        if (rgbMode) loc+=7;
                                    else         loc+=4;
                                    nprimitives++;
                                    break;
        case GL_BITMAP_TOKEN:
        case GL_COPY_PIXEL_TOKEN:
        case GL_DRAW_PIXEL_TOKEN:   if (rgbMode)
                                      loc+=7;
                                    else
                                      loc+=4;
                                    nprimitives++;
                                    break;
        case GL_PASS_THROUGH_TOKEN: loc+=1;
                                    nprimitives++;
                                    break;
        default:                    /* If we arrive here OpenGL has been changed. */
                                      break;
      }
    }

    /* Allocate an array of pointers that will point back at
       primitives in the feedback buffer.  There will be one
       entry per primitive.  This array is also where we keep the
       primitive's average depth.  There is one entry per
       primitive  in the feedback buffer. */
    prims=(DepthIndex *)getmem(nprimitives, sizeof(DepthIndex));

    item=0;
    loc=buffer;
    while (loc < end)
    {
      prims[item].ptr=loc;  /* Save this primitive's location. */
      token=*loc;
      loc++;
      visible=(-1.0);
      switch (token)
      {
        case GL_LINE_TOKEN:
        case GL_LINE_RESET_TOKEN:   if (rgbMode)
                                    {
                                      vertexC=(Feedback3Dcolor *)loc;
                                      depthSum=vertexC[0].z+vertexC[1].z;
                                      loc+=14;
                                    }
                                    else
                                    {
                                      vertexI=(Feedback3DindexedColor *)loc;
                                      depthSum=vertexI[0].z+vertexI[1].z;
                                      loc+=8;
                                    }
                                    prims[item].depth=depthSum*0.5+addDepth;
                                    break;
        case GL_POLYGON_TOKEN:      nvertices=*loc;
                                    loc++;
                                    if (checkVisibility) visible=visibility(loc);
                                    if (rgbMode)
                                    {
                                      vertexC=(Feedback3Dcolor *)loc;
                                      depthSum=vertexC[0].z;
                                      for (i=1; i<nvertices; i++)
                                        depthSum+=vertexC[i].z;
                                      loc+=(7*nvertices);
                                    }
                                    else
                                    {
                                      vertexI=(Feedback3DindexedColor *)loc;
                                      depthSum=vertexI[0].z;
                                      for (i=1; i<nvertices; i++)
                                        depthSum+=vertexI[i].z;
                                      loc+=(4*nvertices);
                                    }
                                    prims[item].depth=depthSum/nvertices+addDepth;
                                    break;
        case GL_POINT_TOKEN:        if (rgbMode)
                                    {
                                      vertexC=(Feedback3Dcolor *)loc;
                                      prims[item].depth=vertexC[0].z+addDepth;
                                      loc+=7;
                                    }
                                    else
                                    {
                                      vertexI=(Feedback3DindexedColor *)loc;
                                      prims[item].depth=vertexI[0].z+addDepth;
                                      loc+=4;
                                    }
                                    break;
        case GL_BITMAP_TOKEN:
        case GL_COPY_PIXEL_TOKEN:
        case GL_DRAW_PIXEL_TOKEN:   if (rgbMode)
                                    {
                                      vertexC=(Feedback3Dcolor *)loc;
                                      prims[item].depth=textDepth+addDepth;
                                      textDepth-=1.0;
                                      loc+=7;
                                    }
                                    else
                                    {
                                      vertexI=(Feedback3DindexedColor *)loc;
                                      prims[item].depth=textDepth+addDepth;
                                      textDepth-=1.0;
                                      loc+=4;
                                    }
                                    break;
        case GL_PASS_THROUGH_TOKEN: i=(int)(*loc) & 0xffff0000; 
                                    switch (i)
                                    {
                                      case ADD_DEPTH:  addDepth=(GLfloat)((int)(*loc) & 0xffff);
                                                        break;
                                      case VISIBILITY: checkVisibility=!checkVisibility;
                                                       break;
                                    }
                                    loc+=1;
                                    break;
        default:                    /* If we arrive here OpenGL has been changed. */
                                    break;
      }
      if (token == GL_POLYGON_TOKEN)
      {
        if (visible < 0.0) item++;
      }
      else
        item++;
    }
    nprimitives=item;

    /* Sort the primitives back to front. */
    qsort(prims, nprimitives, sizeof(DepthIndex), compare);

    /* Understand that sorting by a primitives average depth
       doesn't allow us to disambiguate some cases like self
       intersecting polygons.  Handling these cases would require
       breaking up the primitives.  That's too involved for this
       example.  Sorting by depth is good enough for lots of
       applications. */

    /* Emit the Encapsulated PostScript for the primitives in
       back to front order. */
    for (item=0; item<nprimitives; item++) {
      (void)writePrimitives(file, prims[item].ptr, window, format, portrait);
    }

    fremem((void *)&prims);
  } /* end of if (smooth) */
  else
  {
    while (loc < end)
      loc=writePrimitives(file, loc, window, format, portrait);
  }
}

void feedbackString(char *s, double x, double y, double z, double width,
                    GLuint dummy, char align)
{
  GLubyte bitmap[] = {0};
  char alignment[2];

  glRasterPos3d(x, y, z);
  glBitmap(1, 1, 1, 1, 1, 1, bitmap);
  alignment[0]=align;
  alignment[1]='\0';
  strcpy(textPointer, alignment);
  textPointer++;
  while (*s == ' ') s++;
  strcat(textPointer, s);
  textPointer+=strlen(s)+1;
}

void feedbackLineStipple(GLint dummy, GLushort pattern)
{
  glPassThrough((GLfloat)(LINE_STIPPLE + pattern));
}

void feedbackLineWidth(GLfloat width)
{
  glPassThrough((GLfloat)(LINE_WIDTH + width));
}

void feedbackDisable(GLenum what)
{
  if (what == GL_LINE_STIPPLE)
  {
    glPassThrough((GLfloat)(LINE_STIPPLE + 0xffff));
  }
}

double visibility(GLfloat *loc)
{
  Feedback3Dcolor *polygonC;
  Feedback3DindexedColor *polygonI;
  double v1[3], v2[3], n[3];

  if (rgbMode)
  {
    polygonC=(Feedback3Dcolor *)loc;
    v1[0]=polygonC[1].x-polygonC[0].x;
    v1[1]=polygonC[1].y-polygonC[0].y;
    v1[2]=polygonC[1].z-polygonC[0].z;
    v2[0]=polygonC[2].x-polygonC[1].x;
    v2[1]=polygonC[2].y-polygonC[1].y;
    v2[2]=polygonC[2].z-polygonC[1].z;
  }
  else
  {
    polygonI=(Feedback3DindexedColor *)loc;
    v1[0]=polygonI[1].x-polygonI[0].x;
    v1[1]=polygonI[1].y-polygonI[0].y;
    v1[2]=polygonI[1].z-polygonI[0].z;
    v2[0]=polygonI[2].x-polygonI[1].x;
    v2[1]=polygonI[2].y-polygonI[1].y;
    v2[2]=polygonI[2].z-polygonI[1].z;
  }
  vectorProduct(v1, v2, n);
  return(-n[2]/sqrt(scalarProduct(n, n)));
}
