/*****************************************************************
  marble.cpp
  by Dave Pape
  15 April 2003

  This program demonstrates a 3D marble texture, which is generated
  using a turbulence algorithm.
  
  The marble & turbulence code is borrowed from
   http://cs-people.bu.edu/jisidoro/proj/syntex/texdemo.html

*****************************************************************/
#include <stdlib.h>
#include <math.h>
#include <unistd.h>
#include <stdio.h>
#include <GLUT/glut.h>
#include <OpenGL/gl.h>
#include <dms/dms.h>

using namespace dms;

void createTexture(void);
void drawEverything(void);

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


PerspCamera camera;
Light light;
Material *material;

GLuint textureID, textureID_2D;

bool texture3D=false;


int main(int argc, char *argv[])
    {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowSize(512,512);
    glutCreateWindow(argv[0]);
	material = new Material(Color::White);
    
    glutDisplayFunc(drawEverything);
    glutKeyboardFunc(key);
    glutSpecialFunc(specialkey);
    glutIdleFunc(idle);
    
    camera.setPosition(0, 0, 10);
    light.setInfinitePosition(-5, 4, 1);
    createTexture();
    
    glutMainLoop();
    return 0;
    }



#define TEX_HEIGHT 64
#define TEX_WIDTH 64
#define TEX_DEPTH 64

struct _color { GLubyte r,g,b; };

struct _color textureImage[TEX_DEPTH][TEX_HEIGHT][TEX_WIDTH];

#define MARB_TURB_SCALE 128
#define MARB_TURB_POWER .0500
#define MARB_PERIOD 2

#define NOISE_WIDTH 64
#define NOISE_HEIGHT 64
#define NOISE_DEPTH 64

static float noise[NOISE_DEPTH][NOISE_HEIGHT][NOISE_WIDTH];

void generateNoise(void)
   {
   int i,j,k;
   for (i=0; i<NOISE_DEPTH; i++)
      for (j=0; j<NOISE_HEIGHT; j++)
         for (k=0; k<NOISE_WIDTH; k++)
           {
           noise[i][j][k] = (float)drand48();
           }
   }


float lerpNoise(float x, float y,float z)
   { //linear interpolation of repeating 3d noise array
   int ix,iy,iz,ixd,iyd,izd;     //integer parts of  into texture array
   float rx,ry,rz; 		 //fractional part of xyz  
   float accum;			 //value returned

   ix=((int)x);
   iy=((int)y);
   iz=((int)z);

   rx=x-(float)ix;
   ry=y-(float)iy;
   rz=z-(float)iz;
   ix=ix&(NOISE_WIDTH-1);
   iy=iy&(NOISE_HEIGHT-1);
   iz=iz&(NOISE_DEPTH-1);
   ixd=(ix-1)&(NOISE_WIDTH-1);
   iyd=(iy-1)&(NOISE_HEIGHT-1);
   izd=(iz-1)&(NOISE_DEPTH-1);

   accum=0;
   accum+=rx*ry*rz*noise[iz][iy][ix];
   accum+=(1-rx)*ry*rz*noise[iz][iy][ixd];
   accum+=rx*(1-ry)*rz*noise[iz][iyd][ix];
   accum+=(1-rx)*(1-ry)*rz*noise[iz][iyd][ixd];

   accum+=rx*ry*(1-rz)*noise[izd][iy][ix];
   accum+=(1-rx)*ry*(1-rz)*noise[izd][iy][ixd];
   accum+=rx*(1-ry)*(1-rz)*noise[izd][iyd][ix];
   accum+=(1-rx)*(1-ry)*(1-rz)*noise[izd][iyd][ixd];

   return accum;
   }


float turbulence(float x,float y,float z,float scale)
    {
    float t=0;
    while (scale >= 1)
        {
        t += lerpNoise(x/scale,y/scale,z/scale) * scale;
        scale /= 2;
        }
    return t;
    }

GLubyte generateMarbleColor(float x,float y,float z)
    {
    float yval = (y/MARB_PERIOD/TEX_WIDTH)-3.0 *
                MARB_TURB_POWER * turbulence(x,y,z,MARB_TURB_SCALE);
    yval = sin(yval*M_PI);
    return (GLubyte)(255.0*fabs(yval));
    }

void generateMarble(void)
    {
    int i,j,k;
    float color;
    generateNoise();
    for (i=0; i<TEX_DEPTH; i++)
        for (j=0; j<TEX_HEIGHT; j++)
            for (k=0; k<TEX_WIDTH; k++)
                {
                color=generateMarbleColor(i,j,k);
                textureImage[i][j][k].r = (GLubyte)(20 + .9*color);
                textureImage[i][j][k].g = (GLubyte)(.9*color);
                textureImage[i][j][k].b = (GLubyte)(30 + .8*color);
                }
   }

void createTexture(void)
    {
    generateMarble();
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_3D, textureID);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage3D(GL_TEXTURE_3D, 0, GL_RGB, TEX_WIDTH, TEX_HEIGHT, TEX_DEPTH,
                0, GL_RGB, GL_UNSIGNED_BYTE, textureImage);
    glBindTexture(GL_TEXTURE_3D, 0);

    glGenTextures(1, &textureID_2D);
    glBindTexture(GL_TEXTURE_2D, textureID_2D);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, TEX_WIDTH, TEX_HEIGHT,
                0, GL_RGB, GL_UNSIGNED_BYTE, textureImage);
    glBindTexture(GL_TEXTURE_2D, 0);
    }


void drawEverything(void)
    {
    glClearColor(0.5, 0.7, 1.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    
    camera.apply();
    
    glRotatef(currentTime() * 10, 1, 1, 1);

    light.apply();
    material->apply();

    if (texture3D)
        {
        glEnable(GL_TEXTURE_3D);
        glBindTexture(GL_TEXTURE_3D, textureID);
        glEnable(GL_TEXTURE_GEN_S);
        glEnable(GL_TEXTURE_GEN_T);
        glEnable(GL_TEXTURE_GEN_R);
        GLfloat SplaneCoefficients[4] = { 0.25, 0, 0, 0.5 };
        GLfloat TplaneCoefficients[4] = { 0, 0.25, 0, 0.5 };
        GLfloat RplaneCoefficients[4] = { 0, 0, 0.25, 0.5 };
        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
        glTexGenfv(GL_S, GL_EYE_PLANE, SplaneCoefficients);
        glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
        glTexGenfv(GL_T, GL_EYE_PLANE, TplaneCoefficients);
        glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_EYE_LINEAR);
        glTexGenfv(GL_R, GL_EYE_PLANE, RplaneCoefficients);
        }
    else
        {
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, textureID_2D);
        }

    glutSolidTeapot(2.0);
    
    glDisable(GL_TEXTURE_3D);
    glDisable(GL_TEXTURE_2D);
    glDisable(GL_TEXTURE_GEN_S);
    glDisable(GL_TEXTURE_GEN_T);
    glDisable(GL_TEXTURE_GEN_R);

    glutSwapBuffers();

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


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


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)
    {
    glutPostRedisplay();
    }

