/*****************************************************************
  example4.cpp
  by Dave Pape
  18 Feb 2003

 This program is the fifth in a series of examples of using the
 libdms library.  It modifies example3.cpp, using Objects and
 QuadricObjects for everything that will be drawn.
 
 In the previous examples, the scene was drawn by the functions
 drawBall, drawPedestal, drawTeapot, and drawFloorMesh.  drawBall
 and drawPedestal have now been removed, being replaced by
 QuadricObjects.  drawTeapot and drawFloorMesh are now callback
 functions assigned to Objects.
 All of the Objects in the scene are attached together, with
 the floor mesh as the highest-level parent, the ball and pedestal
 attached to the floor mesh, and the teapot attached to the
 pedestal.  This required changing the transformation for the
 teapot, as it is now being drawn relative to the pedestal, rather
 than in world coordinates.
 Update callbacks are also used, for the motion of the ball and
 the teapot.

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

void drawEverything(void);
void updateBall(dms::Object&,void*);
void drawFloorMesh(dms::Object&,void*);
void drawTeapot(dms::Object&,void*);
void updateTeapot(dms::Object&,void*);

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


dms::PerspCamera camera;
dms::OrthoCamera orthoCamera;
dms::Light light;

dms::Vector4 green(0, 0.8, 0.1, 1);
dms::Vector4 red(0.8, 0, 0, 1);
dms::Vector4 white(1, 1, 1, 1);
dms::Vector4 grey(0.6, 0.6, 0.6, 1);
dms::Vector4 cyan(0, 1, 1, 1);
    
dms::Material floorMaterial(green);
dms::Material teapotMaterial(red, white, 90);
dms::Material pedestalMaterial(grey, white, 30);
dms::Material ballMaterial(cyan);

dms::Texture2D texture("texture.tif");

dms::SimpleTransform ballTransform;
dms::SimpleTransform pedestalTransform;
dms::SimpleTransform pedestalCapTransform;
dms::SimpleTransform teapotTransform;

/*************** CODE OF INTEREST ******************************/
/*    Here we declare the Objects that are going to be in the  */
/*    scene.  Actually, only floormesh needs to be declared    */
/*    globally; everything else could be declared inside the   */
/*    main() function.  Similarly, the colors, materials, and  */
/*    texture could be moved inside of main().                 */
/*                                                             */
dms::QuadricObject ball;
dms::QuadricObject pedestal;
dms::QuadricObject pedestalCap;
dms::Object teapot;
dms::Object floormesh;


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, 0, 10);
    light.setInfinitePosition(-5, 4, 1);

/*************** CODE OF INTEREST ******************************/
/*    Initialize all the objects.  This involves setting the   */
/*    quadrics drawing parameters, and the callback functions  */
/*    for the floor and teapot.  Also, all the materials,      */
/*    transforms, etc. are attached to the objects here.       */
/*                                                             */
    floormesh.setMaterial(floorMaterial);
    floormesh.setDrawCallback(drawFloorMesh, (void*)32);

    pedestal.makeCylinder(4.0, 4.0, 4.0, 16, 1, DMS_Y);
    pedestal.setMaterial(pedestalMaterial);
    pedestalTransform.setTranslation(2.0, -2.0, -10.0);
    pedestal.setTransform(pedestalTransform);
    
    pedestalCap.makeDisk(0.0, 4.0, 16, 1, DMS_Y);
    pedestalCap.setMaterial(pedestalMaterial);
    pedestalCapTransform.setTranslation(4 * dms::Vector3::Y_Axis);
    pedestalCap.setTransform(pedestalCapTransform);
    
    teapot.setMaterial(teapotMaterial);
    teapot.material().diffuse()[3] = 0.75;
    teapot.setTexture(texture);
    teapot.setTransparency(dms::Transparency::StandardBlend);
    teapotTransform.setTranslation(5.5 * dms::Vector3::Y_Axis);
    teapot.setTransform(teapotTransform);
    teapot.setDrawCallback(drawTeapot);
    teapot.setUpdateCallback(updateTeapot);
    
    ball.makeSphere(2.0, 16, 8);
    ball.setMaterial(ballMaterial);
    ballTransform.setTranslation(-10.0, 0.0, -15.0);
    ball.setTransform(ballTransform);
    ball.setUpdateCallback(updateBall);
    
/*************** CODE OF INTEREST ******************************/
/*    Attach the objects together into a single hierarchy.     */
/*                                                             */
    floormesh.attach(ball);
    floormesh.attach(pedestal);
    pedestal.attach(pedestalCap);
    pedestal.attach(teapot);

    glutMainLoop();
    return 0;
    }

   
/*************** CODE OF INTEREST ******************************/
/*    The prototype for this function had to change, to follow */
/*    the standard form for Object callbacks.  The resolution  */
/*    of the mesh (previously rows & columns) is passed as the */
/*    callback data argument.                                  */
/*                                                             */
void drawFloorMesh(dms::Object&,void* data)
    {
    int resolution = (int)data;
    int i,j;
    GLfloat x,z;
    glNormal3f(0.0, 1.0, 0.0);
    for (j = 0; j < resolution; j++)
        {
        glBegin(GL_TRIANGLE_STRIP);
        for (i = 0; i < resolution; i++)
            {
            x = (((float)i) / resolution) * 40.0 - 20.0;
            z = (((float)j) / resolution) * 40.0 - 20.0;
            glVertex3f(x, -2.0, z);
            z = (((float)j+1) / resolution) * 40.0 - 20.0;
            glVertex3f(x, -2.0, z);
            }
        glEnd();
        }
    }


void drawTeapot(dms::Object&,void *)
    {
    glutSolidTeapot(2.0);
    }


/*************** CODE OF INTEREST ******************************/
/*    These are the functions to update the two dynamic objects*/
/*    - the spinning teapot and the bouncing ball.             */
/*                                                             */
void updateTeapot(dms::Object& object,void *)
    {
#if 1

/* Slightly tricky C++ way to get the SimpleTransform from the object */
    dms::SimpleTransform& xform = (dms::SimpleTransform&)object.transform();
    xform.setRotation(dms::currentTime() * 30.0, 0.0, 1.0, 0.0);

#else   

/* Or, we could do this, just using the global variable */
    teapotTransform.setRotation(dms::currentTime() * 30.0, 0.0, 1.0, 0.0);

#endif
    }


void updateBall(dms::Object&,void *)
    {
    ballTransform.setTranslation(-10.0, fabs(sin(dms::currentTime())) * 3.0, -15.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();
    
    glEnable(GL_LIGHTING);
    light.apply();


/*************** CODE OF INTEREST ******************************/
/*    All we need to do now to draw the scene is call drawAll()*/
/*    for the top-level object.  Everything else is attached   */
/*    and is drawn automatically.                              */
/*                                                             */
    floormesh.drawAll();
 
    glDisable(GL_LIGHTING);

    orthoCamera.apply();
        
    glDisable(GL_DEPTH_TEST);
    glColor3f(0.2, 0.1, 0.0);
    glBegin(GL_QUADS);
     glVertex2f(0.0, 0.0);
     glVertex2f(1.0, 0.0);
     glVertex2f(1.0, 0.05);
     glVertex2f(0.0, 0.05);
    glEnd();
    glColor3f(1.0, 1.0, 0.0);
    glRasterPos2f(0.4, 0.01);
    dms::drawString("example4");

    glutSwapBuffers();

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


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)
    {
/*************** CODE OF INTEREST ******************************/
/*    The explicit updates to the ball and teapot transformas  */
/*    have been replaced by an updateAll(), which will call the*/
/*    update functions for all the attached objects.           */
/*                                                             */
    floormesh.updateAll();
    glutPostRedisplay();
    }

