
/* Copyright (c) Mark J. Kilgard, 1997.  */

/* This program is freely distributable without licensing fees 
   and is provided without guarantee or warrantee expressed or 
   implied. This program is -not- in the public domain. */

/* This example demonstrates how to render particle effects
   with OpenGL.  A cloud of pinkish/orange particles explodes with the
   particles bouncing off the ground.  When the EXT_point_parameters
   is present , the particle size is attenuated based on eye distance. */


/* Modified by Brian Paul to test GL_ARB_point_sprite */


#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>       /* for cos(), sin(), and sqrt() */
#ifdef _WIN32
#include <windows.h>
#endif
#include <glad/glad.h>
#include "glut_wrap.h"

/* Some <math.h> files do not define M_PI... */
#ifndef M_PI
#define M_PI 3.14159265
#endif

#if 0  /* For debugging. */
#undef GL_EXT_point_parameters
#endif

static GLfloat angle = -150;   /* in degrees */
static int spin = 0;
static int moving, begin;
static float theTime;
static int repeat = 1;
static int blend = 1;
static int useMipmaps = 1;
static int linearFiltering = 1;

static GLuint Tex0, Tex1;

static GLfloat constant[3] = { .2,  0.0,     0.0 };
static GLfloat linear[3]   = { .0,   .1,     0.0 };
static GLfloat theQuad[3]  = { .005, 0.05, 1/600.0 };

#define MAX_POINTS 2000

static int numPoints = 200;

static GLfloat pointList[MAX_POINTS][3];
static GLfloat pointTime[MAX_POINTS];
static GLfloat pointVelocity[MAX_POINTS][2];
static GLfloat pointDirection[MAX_POINTS][2];
static int colorList[MAX_POINTS];
static int animate = 1, motion = 0, org = 0, sprite = 1, smooth = 1;

static GLfloat colorSet[][4] = {
  /* Shades of red. */
  { 0.7, 0.2, 0.4, 0.5 },
  { 0.8, 0.0, 0.7, 0.5 },
  { 1.0, 0.0, 0.0, 0.5 },
  { 0.9, 0.3, 0.6, 0.5 },
  { 1.0, 0.4, 0.0, 0.5 },
  { 1.0, 0.0, 0.5, 0.5 },
};

#define NUM_COLORS (sizeof(colorSet)/sizeof(colorSet[0]))

#define DEAD (NUM_COLORS+1)


/* GL */
static GLint spritePattern[16][16] = {
   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
   { 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 },
   { 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 0, 0 },
   { 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0 },
   { 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 },
   { 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0 },
   { 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0 },
   { 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0 },
   { 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0 },
   { 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 1, 0 },
   { 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0 },
   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
   { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};


/** Fragment shader which inverts the texture's color */
static const char *FragShaderText =
   "uniform sampler2D tex; \n"
   "void main(void) \n"
   "{ \n"
   "   gl_FragColor = vec4(1.0) - texture2D(tex, gl_TexCoord[0].xy); \n"
   "} \n";

static GLuint FragShader, ShaderProg;
static GLboolean UseFragShader = GL_FALSE;
static GLboolean HaveShaders = GL_FALSE;

static void
makeFragShader(void)
{
   GLint stat;

   HaveShaders = GLAD_GL_VERSION_2_0;
   if (!HaveShaders)
      return;

   FragShader = glCreateShader(GL_FRAGMENT_SHADER);
   glShaderSource(FragShader, 1, (const GLchar **) &FragShaderText, NULL);
   glCompileShader(FragShader);

   ShaderProg = glCreateProgram();
   glAttachShader(ShaderProg, FragShader);
   glLinkProgram(ShaderProg);

   glGetProgramiv(ShaderProg, GL_LINK_STATUS, &stat);
   assert(stat);
}




#if 0  /* drand48 might be better on Unix machines */
#define RANDOM_RANGE(lo, hi) ((lo) + (hi - lo) * drand48())
#else
static float float_rand(void) { return rand() / (float) RAND_MAX; }
#define RANDOM_RANGE(lo, hi) ((lo) + (hi - lo) * float_rand())
#endif

#define MEAN_VELOCITY 3.0
#define GRAVITY 2.0

/* Modeling units of ground extent in each X and Z direction. */
#define EDGE 12

static void
makePointList(void)
{
  float angle, velocity, direction;
  int i;

  motion = 1;
  for (i=0; i<numPoints; i++) {
    pointList[i][0] = 0.0;
    pointList[i][1] = 0.0;
    pointList[i][2] = 0.0;
    pointTime[i] = 0.0;
    angle = (RANDOM_RANGE(60.0, 70.0)) * M_PI/180.0;
    direction = RANDOM_RANGE(0.0, 360.0) * M_PI/180.0;
    pointDirection[i][0] = cos(direction);
    pointDirection[i][1] = sin(direction);
    velocity = MEAN_VELOCITY + RANDOM_RANGE(-0.8, 1.0);
    pointVelocity[i][0] = velocity * cos(angle);
    pointVelocity[i][1] = velocity * sin(angle);
    colorList[i] = rand() % NUM_COLORS;
  }
  theTime = 0.0;
}

static void
updatePointList(void)
{
  float distance;
  int i;

  static double t0 = -1.;
  double dt, t = glutGet(GLUT_ELAPSED_TIME) / 1000.0;
  if (t0 < 0.0)
    t0 = t;
  dt = t - t0;
  t0 = t;

  motion = 0;
  for (i=0; i<numPoints; i++) {
    distance = pointVelocity[i][0] * theTime;

    /* X and Z */
    pointList[i][0] = pointDirection[i][0] * distance;
    pointList[i][2] = pointDirection[i][1] * distance;

    /* Z */
    pointList[i][1] =
      (pointVelocity[i][1] - 0.5 * GRAVITY * pointTime[i])*pointTime[i];

    /* If we hit the ground, bounce the point upward again. */
    if (pointList[i][1] <= 0.0) {
      if (distance > EDGE) {
        /* Particle has hit ground past the distance duration of
          the particles.  Mark particle as dead. */
       colorList[i] = NUM_COLORS;  /* Not moving. */
       continue;
      }

      pointVelocity[i][1] *= 0.8;  /* 80% of previous up velocity. */
      pointTime[i] = 0.0;  /* Reset the particles sense of up time. */
    }
    motion = 1;
    pointTime[i] += dt;
  }
  theTime += dt;
  if (!motion && !spin) {
    if (repeat) {
      makePointList();
    } else {
      glutIdleFunc(NULL);
    }
  }
}

static void
idle(void)
{
  updatePointList();
  if (spin) {
    angle += 0.3;
  }
  glutPostRedisplay();
}

static void
visible(int vis)
{
  if (vis == GLUT_VISIBLE) {
    if (animate && (motion || spin)) {
      glutIdleFunc(idle);
    }
  } else {
    glutIdleFunc(NULL);
  }
}

static void
redraw(void)
{
  int i;

  glDepthMask(GL_TRUE);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  if (HaveShaders)
     glUseProgram(0);

  glPushMatrix();
  glRotatef(15.0, 1.0, 0.0, 0.0);
  glRotatef(angle, 0.0, 1.0, 0.0);


  /* Draw the floor. */
  glColor3f(0.1, 0.5, 1.0);
  glBegin(GL_QUADS);
    glTexCoord2f(0.0, 0.0);
    glVertex3f(-EDGE, -0.05, -EDGE);
    glTexCoord2f(20.0, 0.0);
    glVertex3f(EDGE, -0.05, -EDGE);
    glTexCoord2f(20.0, 20.0);
    glVertex3f(EDGE, -0.05, EDGE);
    glTexCoord2f(0.0, 20.0);
    glVertex3f(-EDGE, -0.05, EDGE);
  glEnd();

  /* Allow particles to blend with each other. */
  glDepthMask(GL_FALSE);

  if (blend)
     glEnable(GL_BLEND);

  if (sprite) {
     glActiveTexture(GL_TEXTURE0);
     glEnable(GL_TEXTURE_2D);
     if (0) {
        /* debug/test code */
        glActiveTexture(GL_TEXTURE1);
        glEnable(GL_TEXTURE_2D);
     }

#ifdef GL_ARB_point_sprite
     glEnable(GL_POINT_SPRITE_ARB);
#endif
     if (UseFragShader) {
        glUseProgram(ShaderProg);
     }
  }

  glColor3f(1,1,1);
  glBegin(GL_POINTS);
    for (i=0; i<numPoints; i++) {
      /* Draw alive particles. */
      if (colorList[i] != DEAD) {
        if (!sprite)
           glColor4fv(colorSet[colorList[i]]);
        glVertex3fv(pointList[i]);
      }
    }
  glEnd();

  glActiveTexture(GL_TEXTURE0);
  glDisable(GL_TEXTURE_2D);
  glActiveTexture(GL_TEXTURE1);
  glDisable(GL_TEXTURE_2D);

#ifdef GL_ARB_point_sprite
  glDisable(GL_POINT_SPRITE_ARB);
#endif
  glDisable(GL_BLEND);

  glPopMatrix();

  glutSwapBuffers();
}

/* ARGSUSED2 */
static void
mouse(int button, int state, int x, int y)
{
  /* Scene can be spun around Y axis using left
     mouse button movement. */
  if (button == GLUT_LEFT_BUTTON && state == GLUT_DOWN) {
    moving = 1;
    begin = x;
  }
  if (button == GLUT_LEFT_BUTTON && state == GLUT_UP) {
    moving = 0;
  }
}

/* ARGSUSED1 */
static void
mouseMotion(int x, int y)
{
  if (moving) {
    angle = angle + (x - begin);
    begin = x;
    glutPostRedisplay();
  }
}

static void
menu(int option)
{
  switch (option) {
  case 0:
    makePointList();
    break;
#ifdef GL_ARB_point_parameters
  case 1:
    glPointParameterfvARB(GL_POINT_DISTANCE_ATTENUATION_ARB, constant);
    break;
  case 2:
    glPointParameterfvARB(GL_POINT_DISTANCE_ATTENUATION_ARB, linear);
    break;
  case 3:
    glPointParameterfvARB(GL_POINT_DISTANCE_ATTENUATION_ARB, theQuad);
    break;
#endif
  case 4:
    blend = 1;
    break;
  case 5:
    blend = 0;
    break;
#ifdef GL_ARB_point_parameters
  case 6:
    glPointParameterfARB(GL_POINT_FADE_THRESHOLD_SIZE_ARB, 1.0);
    break;
  case 7:
    glPointParameterfARB(GL_POINT_FADE_THRESHOLD_SIZE_ARB, 10.0);
    break;
#endif
  case 8:
    glEnable(GL_POINT_SMOOTH);
    smooth = 1;
    break;
  case 9:
    glDisable(GL_POINT_SMOOTH);
    smooth = 0;
    break;
  case 10:
    glPointSize(16.0);
    break;
  case 11:
    glPointSize(32.0);
    break;
  case 12:
    glPointSize(64.0);
    break;
  case 13:
    spin = 1 - spin;
    if (animate && (spin || motion)) {
      glutIdleFunc(idle);
    } else {
      glutIdleFunc(NULL);
    }
    break;
  case 14:
    numPoints = 200;
    break;
  case 15:
    numPoints = 500;
    break;
  case 16:
    numPoints = 1000;
    break;
  case 17:
    numPoints = 2000;
    break;
  case 666:
    exit(0);
  }
  glutPostRedisplay();
}

/* ARGSUSED1 */
static void
key(unsigned char c, int x, int y)
{
  switch (c) {
  case 13:
    animate = 1 - animate;  /* toggle. */
    if (animate && (motion || spin)) {
      glutIdleFunc(idle);
    } else {
      glutIdleFunc(NULL);
    }
    break;
  case ' ':
    animate = 1;
    makePointList();
    glutIdleFunc(idle);
    break;
  case 'o':
  case 'O':
    org ^= 1;
    glPointParameteri(GL_POINT_SPRITE_COORD_ORIGIN,
                      org ? GL_LOWER_LEFT : GL_UPPER_LEFT);
    glutPostRedisplay();
    break;
  case 't':
  case 'T':
    sprite ^= 1;
    glutPostRedisplay();
    break;
  case 's':
  case 'S':
    (smooth ^= 1) ? glEnable(GL_POINT_SMOOTH) : glDisable(GL_POINT_SMOOTH);
    glutPostRedisplay();
    break;
  case 'f':
  case 'F':
    if (HaveShaders) {
       UseFragShader = !UseFragShader;
       glutPostRedisplay();
    }
    break;
  case '0':
    glPointSize(1.0);
    glutPostRedisplay();
    break;
  case '1':
    glPointSize(16.0);
    glutPostRedisplay();
    break;
  case '2':
    glPointSize(32.0);
    glutPostRedisplay();
    break;
  case '3':
    glPointSize(64.0);
    glutPostRedisplay();
    break;
  case '4':
    glPointSize(128.0);
    glutPostRedisplay();
    break;
  case 27:
    exit(0);
  }
}



static void
makeSpriteTextures(void)
{
   GLubyte texture[16][16][4];
   int i, j;

   if (!glutExtensionSupported("GL_ARB_point_sprite")) {
      printf("Sorry, this demo requires GL_ARB_point_sprite.\n");
      exit(0);
   }
   if (!glutExtensionSupported("GL_ARB_point_parameters")) {
      printf("Sorry, this demo requires GL_ARB_point_parameters.\n");
      exit(0);
   }

   /* "GL" */
   for (i = 0; i < 16; i++) {
      for (j = 0; j < 16; j++) {
         if (spritePattern[i][j]) {
            texture[i][j][0] = 255;
            texture[i][j][1] = 255;
            texture[i][j][2] = 255;
            texture[i][j][3] = 255;
         }
         else {
            texture[i][j][0] = 255;
            texture[i][j][1] = 0;
            texture[i][j][2] = 0;
            texture[i][j][3] = 0;
         }
      }
   }

   /* texture 0 */
   glActiveTexture(GL_TEXTURE0);
   glGenTextures(1, &Tex0);
   glBindTexture(GL_TEXTURE_2D, Tex0);
   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                texture);
   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_REPLACE);
#ifdef GL_ARB_point_sprite
   glTexEnvi(GL_POINT_SPRITE_ARB, GL_COORD_REPLACE_ARB, GL_TRUE);
#endif

   /* left=yellow, right=green */
   memset(texture, 0, sizeof(texture));
   for (i = 0; i < 16; i++) {
      for (j = 0; j < 16; j++) {
         if (j < 8) {
            texture[i][j][0] = 255;
            texture[i][j][1] = 255;
            texture[i][j][2] = 0;
            texture[i][j][3] = 255;
         }
         else {
            texture[i][j][0] = 0;
            texture[i][j][1] = 255;
            texture[i][j][2] = 0;
            texture[i][j][3] = 255;
         }
      }
   }

   /* texture 1 */
   glActiveTexture(GL_TEXTURE1);
   glGenTextures(1, &Tex1);
   glBindTexture(GL_TEXTURE_2D, Tex1);
   glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                texture);
   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);
#ifdef GL_ARB_point_sprite
   glTexEnvi(GL_POINT_SPRITE_ARB, GL_COORD_REPLACE_ARB, GL_TRUE);
#endif

   glActiveTexture(GL_TEXTURE0);
}


static void
reshape(int width, int height)
{
  GLfloat h = (GLfloat) height / (GLfloat) width;

  glViewport(0, 0, (GLint) width, (GLint) height);

#if 0 /* debug/test code */
  glMatrixMode(GL_TEXTURE);
  glLoadIdentity();
  glRotatef(45, 0, 0, 1);
#endif

  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  glFrustum(-1.0, 1.0, -h, h, 2.0, 30.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glTranslatef(0.0, 0.0, -10.0);
}

int
main(int argc, char **argv)
{
  int i;

  glutInitWindowSize(600,300);
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH | GLUT_MULTISAMPLE);

  for (i=1; i<argc; i++) {
    if(!strcmp("-noms", argv[i])) {
      glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
      printf("forcing no multisampling\n");
    } else if(!strcmp("-nomipmaps", argv[i])) {
      useMipmaps = 0;
    } else if(!strcmp("-nearest", argv[i])) {
      linearFiltering = 0;
    }
  }
  glutCreateWindow("sprite blast");
  gladLoadGL();
  glutReshapeFunc(reshape);
  glutDisplayFunc(redraw);
  glutMouseFunc(mouse);
  glutMotionFunc(mouseMotion);
  glutVisibilityFunc(visible);
  glutKeyboardFunc(key);
  glutCreateMenu(menu);
  glutAddMenuEntry("Reset time", 0);
  glutAddMenuEntry("Constant", 1);
  glutAddMenuEntry("Linear", 2);
  glutAddMenuEntry("Quadratic", 3);
  glutAddMenuEntry("Blend on", 4);
  glutAddMenuEntry("Blend off", 5);
  glutAddMenuEntry("Threshold 1", 6);
  glutAddMenuEntry("Threshold 10", 7);
  glutAddMenuEntry("Point smooth on", 8);
  glutAddMenuEntry("Point smooth off", 9);
  glutAddMenuEntry("Point size 16", 10);
  glutAddMenuEntry("Point size 32", 11);
  glutAddMenuEntry("Point size 64", 12);
  glutAddMenuEntry("Toggle spin", 13);
  glutAddMenuEntry("200 points ", 14);
  glutAddMenuEntry("500 points ", 15);
  glutAddMenuEntry("1000 points ", 16);
  glutAddMenuEntry("2000 points ", 17);
  glutAddMenuEntry("Quit", 666);
  glutAttachMenu(GLUT_RIGHT_BUTTON);

  makePointList();
  makeSpriteTextures();
  makeFragShader();

  glShadeModel(GL_FLAT);
  glEnable(GL_DEPTH_TEST);
  glEnable(GL_POINT_SMOOTH);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glPointSize(32.0);
  glPointParameterfvARB(GL_POINT_DISTANCE_ATTENUATION_ARB, theQuad);

  glutMainLoop();
  return 0;             /* ANSI C requires main to return int. */
}
