/*****************************************************************
  multitex-caustics.cpp
  by Dave Pape
  3 April 2003

  This program demonstrates using lightmapping for a caustic
  effect.
  It loads a series of textures that are the animated caustic
  lighting effect, and applies them to an underwater scene,
  using texgen and multitexturing.
  The program also uses reflection mapping on the surface waves.

*****************************************************************/
#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <stdio.h>
#include <GL/glut.h>
#include <GL/gl.h>
#include <GL/glext.h>
#include <dms/dms.h>

using namespace dms;

void createScene(Object& root);
void drawEverything(void);
void drawWaveMesh(int columns,int rows,float timeOffset);

void key(unsigned char k, int x, int y);
void specialkey(int k, int x, int y);
void idle(void);


PerspCamera camera;
Object root;
Light light;

#define NUM_CAUSTICS 32

Texture2D *causticTex[NUM_CAUSTICS];
int causticNum=0;


Texture2D reflectionTex("clouds.tif");

SimpleTransform subXform;


int main(int argc, char *argv[])
    {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowSize(512,512);
    glutCreateWindow(argv[0]);
    
    glutDisplayFunc(drawEverything);
    glutKeyboardFunc(key);
    glutSpecialFunc(specialkey);
    glutIdleFunc(idle);
    
    camera.setPosition(0, 3, 14);
    light.setInfinitePosition(1, 10, 1);
    light.setDiffuse(Color::White * 2.0);
/*************** CODE OF INTEREST ******************************/
/* The caustic textures will be applied to the undersides of   */
/* objects, the same as to the tops.  This isn't visually      */
/* correct.  To cover up this flaw, we can eliminate the       */
/* ambient lighting, so that the undersides of objects will    */
/* be completely black, and the caustics won't show up there.  */
/*                                                             */
//    light.setAmbient(Color::Black);
//    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, Color::Black.vec);
    createScene(root);
    
    glutMainLoop();
    return 0;
    }


void createScene(Object& root)
    {
    Material *whiteMaterial = new Material(Color::White);
    
    QuadricObject *ball = new QuadricObject;
    ball->makeSphere(1.0, 16, 10);
    ball->setMaterial(*whiteMaterial);
    ball->setTexture(*(new Texture2D("rock.tiff")));
    ball->setUseTexture(GL_TRUE);
    SimpleTransform *xform = new SimpleTransform;
    xform->setTranslation(-4, 0, 0);
    ball->setTransform(*xform);
    root.attach(*ball);
    
    ball = new QuadricObject;
    ball->makeSphere(1.0, 32, 16);
    ball->setMaterial(*whiteMaterial);
    ball->setTexture(*(new Texture2D("endmil.tif")));
    ball->setUseTexture(GL_TRUE);
    subXform.setTranslation(3, 2, 0);
    subXform.setRotation(90, 0, 1, 0);
    subXform.setScaling(1, 1, 3);
    ball->setTransform(subXform);
    root.attach(*ball);

    Square *square = new Square(-10, -10, 10, 10, DMS_Y);
    square->setMaterial(*whiteMaterial);
    square->setTexture(*(new Texture2D("bigpble.tiff", GL_REPEAT, GL_LINEAR_MIPMAP_LINEAR)));
    square->setTexCoords(0,0, 4,4);
    root.attach(*square);
        
    for (int i=0; i < NUM_CAUSTICS; i++)
        {
        char filename[256];
        sprintf(filename, "caustics/caustic%02d.tif", i+1);
        causticTex[i] = new Texture2D(filename);
        }
    }


void drawEverything(void)
    {
    GLfloat bgcolor[] = { 0.1, 0.5, 0.4, 0.0 };
    glClearColor(bgcolor[0], bgcolor[1], bgcolor[2], bgcolor[3]);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    
    camera.apply();
    light.apply();
    glEnable(GL_FOG);
    glFogi(GL_FOG_MODE, GL_LINEAR);
    glFogfv(GL_FOG_COLOR, bgcolor);
    glFogf(GL_FOG_START, 4.0);
    glFogf(GL_FOG_END, 30.0);
    
/*************** CODE OF INTEREST ******************************/
/* Apply a caustic lightmap texture to the underwater objects. */
/*                                                             */
    glActiveTextureARB(GL_TEXTURE1_ARB);
    glEnable(GL_TEXTURE_GEN_S);
    glEnable(GL_TEXTURE_GEN_T);
    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
    GLfloat SplaneCoefficients[4] = { 0.1, -0.05, 0, 0 };
    GLfloat TplaneCoefficients[4] = { 0, 0, 0.1, 0 };
    glTexGenfv(GL_S, GL_EYE_PLANE, SplaneCoefficients);
    glTexGenfv(GL_T, GL_EYE_PLANE, TplaneCoefficients);
    causticTex[causticNum]->apply();
    glActiveTextureARB(GL_TEXTURE0_ARB);
    
    root.drawAll();

/*************** CODE OF INTEREST ******************************/
/* Apply a reflection map to the waves.                        */
/*                                                             */
    glActiveTextureARB(GL_TEXTURE1_ARB);
    reflectionTex.apply();
    glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
    glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
    glActiveTextureARB(GL_TEXTURE0_ARB);

    drawWaveMesh(32, 32, currentTime());

    glActiveTextureARB(GL_TEXTURE1_ARB);
    reflectionTex.disable();
    glDisable(GL_TEXTURE_GEN_S);
    glDisable(GL_TEXTURE_GEN_T);
    glActiveTextureARB(GL_TEXTURE0_ARB);
    
    glutSwapBuffers();

    checkGLError("end-of-frame");
    }


void drawWaveMesh(int columns,int rows,float timeOffset)
    {
    static Material wavesMaterial(Color::White, Color::White, 60);
    float x,y,z;
    Vector3 norm;
    wavesMaterial.apply();
    for (int j = 0; j < rows; j++)
        {
        glBegin(GL_TRIANGLE_STRIP);
        for (int i = 0; i < columns; i++)
            {
            x = (((float)i) / columns) * 20.0 - 10.0;
            z = (((float)j) / rows) * 20.0 - 10.0;
            y = sin(x*2+timeOffset) * sin(z*2+timeOffset) * 0.2 + 5;
            norm[0] = -cos(x*2+timeOffset) * sin(z*2+timeOffset);
            norm[0] = -sin(x*2+timeOffset) * cos(z*2+timeOffset);
            norm[1] = 1.0;
            norm.normalize();
            glNormal3fv(norm.vec);
            glVertex3f(x,y,z);

            z = (((float)j+1) / rows) * 20.0 - 10.0;
            y = sin(x*2+timeOffset) * sin(z*2+timeOffset) * 0.2 + 5;
            norm[0] = -cos(x*2+timeOffset) * sin(z*2+timeOffset);
            norm[0] = -sin(x*2+timeOffset) * cos(z*2+timeOffset);
            norm[1] = 1.0;
            norm.normalize();
            glNormal3fv(norm.vec);
            glVertex3f(x,y,z);
            }
        glEnd();
        }
    wavesMaterial.disable();
    }
   

void key(unsigned char k, int x, int y)
    {
    if (k == 27)
        exit(0);
    }


void specialkey(int k, int x, int y)
    {
    if (k == GLUT_KEY_LEFT)
        camera.turn(3);
    else if (k == GLUT_KEY_RIGHT)
        camera.turn(-3);
    else if (k == GLUT_KEY_UP)
        camera.pitch(2);
    else if (k == GLUT_KEY_DOWN)
        camera.pitch(-2);
    else if (k == GLUT_KEY_HOME)
        camera.moveForward(0.25);
    else if (k == GLUT_KEY_END)
        camera.moveForward(-0.25);
    else if (k == GLUT_KEY_PAGE_UP)
        camera.zoom(-1);
    else if (k == GLUT_KEY_PAGE_DOWN)
        camera.zoom(1);
    }


void idle(void)
    {
    beginFrame();
    root.updateAll();
    causticNum = ((int)(frameTime() * 60)) % NUM_CAUSTICS;
    subXform.setTranslation(sin(frameTime()/8)*4, 2, cos(frameTime()/8)*4);
    subXform.setRotation(radiansToDegrees(frameTime()/8)+90, 0, 1, 0);
    glutPostRedisplay();
    }
