/**
 * AA lines with texture mapped quads
 *
 * Brian Paul
 * 9 Feb 2008
 */


#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <glad/glad.h>
#include "glut_wrap.h"

#ifndef M_PI
#define M_PI 3.1415926535
#endif

static GLint WinWidth = 300, WinHeight = 300;
static GLint win = 0;
static GLfloat Width = 8.;

/*
 * Quad strip for line from v0 to v1:
 *
 1   3                     5   7
 +---+---------------------+---+
 |                             |
 |   *v0                 v1*   |
 |                             |
 +---+---------------------+---+
 0   2                     4   6
 */
static void
QuadLine(const GLfloat *v0, const GLfloat *v1, GLfloat width)
{
   GLfloat dx = v1[0] - v0[0];
   GLfloat dy = v1[1] - v0[1];
   GLfloat len = sqrt(dx*dx + dy*dy);
   float dx0, dx1, dx24, dx35, dx6, dx7;
   float dy0, dy1, dy24, dy35, dy6, dy7;

   dx /= len;
   dy /= len;

   width *= 0.5;  /* half width */
   dx = dx * (width + 0.0);
   dy = dy * (width + 0.0);

   dx0 = -dx+dy;  dy0 = -dy-dx;
   dx1 = -dx-dy;  dy1 = -dy+dx;

   dx24 =  0+dy;  dy24 = -dx+0;
   dx35 =  0-dy;  dy35 = +dx+0;

   dx6 =  dx+dy;  dy6 =  dy-dx;
   dx7 =  dx-dy;  dy7 =  dy+dx;

   /*
   printf("dx, dy = %g, %g\n", dx, dy);
   printf("  dx0, dy0: %g, %g\n", dx0, dy0);
   printf("  dx1, dy1: %g, %g\n", dx1, dy1);
   printf("  dx2, dy2: %g, %g\n", dx2, dy2);
   printf("  dx3, dy3: %g, %g\n", dx3, dy3);
   */

   glBegin(GL_QUAD_STRIP);
   glTexCoord2f(0, 0);
   glVertex2f(v0[0] + dx0, v0[1] + dy0);
   glTexCoord2f(0, 1);
   glVertex2f(v0[0] + dx1, v0[1] + dy1);

   glTexCoord2f(0.5, 0);
   glVertex2f(v0[0] + dx24, v0[1] + dy24);
   glTexCoord2f(0.5, 1);
   glVertex2f(v0[0] + dx35, v0[1] + dy35);

   glTexCoord2f(0.5, 0);
   glVertex2f(v1[0] + dx24, v1[1] + dy24);
   glTexCoord2f(0.5, 1);
   glVertex2f(v1[0] + dx35, v1[1] + dy35);

   glTexCoord2f(1, 0);
   glVertex2f(v1[0] + dx6, v1[1] + dy6);
   glTexCoord2f(1, 1);
   glVertex2f(v1[0] + dx7, v1[1] + dy7);
   glEnd();
}


static float Cos(float a)
{
   return cos(a * M_PI / 180.);
}

static float Sin(float a)
{
   return sin(a * M_PI / 180.);
}

static void
Redisplay(void)
{
   float cx = 0.5 * WinWidth, cy = 0.5 * WinHeight;
   float len = 0.5 * WinWidth - 20.0;
   int i;

   glClear(GL_COLOR_BUFFER_BIT);

   glColor3f(1, 1, 1);

   glEnable(GL_BLEND);
   glEnable(GL_TEXTURE_2D);

   for (i = 0; i < 360; i+=5) {
      float v0[2], v1[2];
      v0[0] = cx + 40 * Cos(i);
      v0[1] = cy + 40 * Sin(i);
      v1[0] = cx + len * Cos(i);
      v1[1] = cy + len * Sin(i);
      QuadLine(v0, v1, Width);
   }

   {
      float v0[2], v1[2], x;
      for (x = 0; x < 1.0; x += 0.2) {
         v0[0] = cx + x;
         v0[1] = cy + x * 40 - 20;
         v1[0] = cx + x + 5.0;
         v1[1] = cy + x * 40 - 20;
         QuadLine(v0, v1, Width);
      }
   }

   glDisable(GL_BLEND);
   glDisable(GL_TEXTURE_2D);

   glutSwapBuffers();
}


static void
Reshape(int width, int height)
{
   WinWidth = width;
   WinHeight = height;
   glViewport(0, 0, width, height);
   glMatrixMode(GL_PROJECTION);
   glLoadIdentity();
   glOrtho(0, width, 0, height, -1, 1);
   glMatrixMode(GL_MODELVIEW);
   glLoadIdentity();
}


static void
CleanUp(void)
{
   glutDestroyWindow(win);
}


static void
Key(unsigned char key, int x, int y)
{
  (void) x;
  (void) y;

   switch(key) {
   case 'w':
      Width -= 0.5;
      break;
   case 'W':
      Width += 0.5;
      break;
   case 27:
      CleanUp();
      exit(0);
      break;
   }
#if 0
   if (Width < 3)
      Width = 3;
#endif
   printf("Width = %g\n", Width);
   glutPostRedisplay();
}


static float
ramp4(GLint i, GLint size)
{
   float d;
   if (i < 4 ) {
      d = i / 4.0;
   }
   else if (i >= size - 5) {
      d = 1.0 - (i - (size - 5)) / 4.0;
   }
   else {
      d = 1.0;
   }
   return d;
}

static float
ramp2(GLint i, GLint size)
{
   float d;
   if (i < 2 ) {
      d = i / 2.0;
   }
   else if (i >= size - 3) {
      d = 1.0 - (i - (size - 3)) / 2.0;
   }
   else {
      d = 1.0;
   }
   return d;
}

static float
ramp1(GLint i, GLint size)
{
   float d;
   if (i == 0 || i == size-1) {
      d = 0.0;
   }
   else {
      d = 1.0;
   }
   return d;
}


/**
 * Make an alpha texture for antialiasing lines.
 * Just a linear fall-off ramp for now.
 * Should have a number of different textures for different line widths.
 * Could try a bell-like-curve....
 */
static void
MakeTexture(void)
{
#define SZ 8
   GLfloat tex[SZ][SZ];  /* alpha tex */
   int i, j;
   for (i = 0; i < SZ; i++) {
      for (j = 0; j < SZ; j++) {
#if 0
         float k = (SZ-1) / 2.0;
         float dx = fabs(i - k) / k;
         float dy = fabs(j - k) / k;
         float d;

         dx = 1.0 - dx;
         dy = 1.0 - dy;
         d = dx * dy;

#else
         float d = ramp1(i, SZ) * ramp1(j, SZ);
         printf("%d, %d: %g\n", i, j, d);
#endif
         tex[i][j] = d;
      }
   }

   glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, SZ, SZ, 0, GL_ALPHA, GL_FLOAT, tex);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
#undef SZ
}


static void
MakeMipmap(void)
{
#define SZ 64
   GLfloat tex[SZ][SZ];  /* alpha tex */
   int level;

   glPixelStorei(GL_UNPACK_ROW_LENGTH, SZ);
   for (level = 0; level < 7; level++) {
      int sz = 1 << (6 - level);
      int i, j;
      for (i = 0; i < sz; i++) {
         for (j = 0; j < sz; j++) {
            if (level == 6)
               tex[i][j] = 1.0;
            else if (level == 5)
               tex[i][j] = 0.5;
            else
               tex[i][j] = ramp1(i, sz) * ramp1(j, sz);
         }
      }

      glTexImage2D(GL_TEXTURE_2D, level, GL_ALPHA,
                   sz, sz, 0, GL_ALPHA, GL_FLOAT, tex);
   }

   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
   glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
   /* glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, 4); */
   /* glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 5); */

   glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
#undef SZ
}


static void
Init(void)
{
   (void) MakeTexture;
   (void) ramp4;
   (void) ramp2;

   if (!GLAD_GL_VERSION_2_0) {
      printf("This program requires OpenGL 2.x\n");
      exit(1);
   }

   glClearColor(0.3f, 0.3f, 0.3f, 0.0f);

   printf("GL_RENDERER = %s\n",(const char *) glGetString(GL_RENDERER));

   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
#if 0
   glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
#elif 0
   MakeTexture();
#else
   MakeMipmap();
#endif
}


static void
ParseOptions(int argc, char *argv[])
{
}


int
main(int argc, char *argv[])
{
   glutInit(&argc, argv);
   glutInitWindowSize(WinWidth, WinHeight);
   glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
   win = glutCreateWindow(argv[0]);
   gladLoadGL();
   glutReshapeFunc(Reshape);
   glutKeyboardFunc(Key);
   glutDisplayFunc(Redisplay);
   ParseOptions(argc, argv);
   Init();
   glutMainLoop();
   return 0;
}
