//===============================================================================
// @ IvGLHelp.cpp
// 
// Helper routines to set up basic GL functionality
// ------------------------------------------------------------------------------
// Copyright (C) 2004 by Elsevier, Inc. All rights reserved.
//
//===============================================================================

//-------------------------------------------------------------------------------
//-- Dependencies ---------------------------------------------------------------
//-------------------------------------------------------------------------------

#include "IvGLHelp.h"
#include <IvVector3.h>
#include <IvVector4.h>
#include <IvMatrix44.h>
#include <IvLineSegment3.h>
#include <IvMath.h>
#include <IvQuat.h>

//-------------------------------------------------------------------------------
//-- Functions ------------------------------------------------------------------
//-------------------------------------------------------------------------------

//-------------------------------------------------------------------------------
// @ IvDrawCube()
//-------------------------------------------------------------------------------
// Draw cube using GL calls
//-------------------------------------------------------------------------------
void 
IvDrawCube()
{
    IvVector3 minima( -1.0f, -1.0f, -1.0f );
    IvVector3 maxima( 1.0f, 1.0f, 1.0f );
    IvDrawBox( minima, maxima );

}   // End of IvDrawCube()


//-------------------------------------------------------------------------------
// @ IvDrawBox()
//-------------------------------------------------------------------------------
// Draw box using GL calls
//-------------------------------------------------------------------------------
void 
IvDrawBox( const IvVector3& minima, const IvVector3& maxima )
{
    // draw everything after this as quads
    glBegin(GL_QUADS);

    // top
    // set normal to face (for lighting)
    glNormal3f(0.0f, 0.0f, 1.0f);
    // set geometry
    glVertex3f(maxima.x, maxima.y, maxima.z);
    glVertex3f(minima.x, maxima.y, maxima.z);
    glVertex3f(minima.x, minima.y, maxima.z);
    glVertex3f(maxima.x, minima.y, maxima.z);

    // bottom
    glNormal3f(0.0f, 0.0f, -1.0f);
    glVertex3f(maxima.x, maxima.y, minima.z);
    glVertex3f(maxima.x, minima.y, minima.z);
    glVertex3f(minima.x, minima.y, minima.z);
    glVertex3f(minima.x, maxima.y, minima.z);

    // left
    glNormal3f(0.0f, 1.0f, 0.0f);
    glVertex3f(maxima.x, maxima.y, maxima.z);
    glVertex3f(maxima.x, maxima.y, minima.z);
    glVertex3f(minima.x, maxima.y, minima.z);
    glVertex3f(minima.x, maxima.y, maxima.z);

    // right
    glNormal3f(0.0f, -1.0f, 0.0f);
    glVertex3f(maxima.x, minima.y, maxima.z);
    glVertex3f(minima.x, minima.y, maxima.z);
    glVertex3f(minima.x, minima.y, minima.z);
    glVertex3f(maxima.x, minima.y, minima.z);

    // front
    glNormal3f(1.0f, 0.0f, 0.0f);
    glVertex3f(maxima.x, maxima.y, maxima.z);
    glVertex3f(maxima.x, minima.y, maxima.z);
    glVertex3f(maxima.x, minima.y, minima.z);
    glVertex3f(maxima.x, maxima.y, minima.z);

    // back
    glNormal3f(-1.0f, 0.0f, 0.0f);
    glVertex3f(minima.x, maxima.y, maxima.z);
    glVertex3f(minima.x, maxima.y, minima.z);
    glVertex3f(minima.x, minima.y, minima.z);
    glVertex3f(minima.x, minima.y, maxima.z);

    // done drawing quads
    glEnd();

}   // End of IvDrawBox()


//-------------------------------------------------------------------------------
// @ IvDrawSphere()
//-------------------------------------------------------------------------------
// Draw sphere
//-------------------------------------------------------------------------------
void 
IvDrawSphere( float radius )
{
    glutSolidSphere( radius, 24, 12 );

}   // End of IvDrawSphere()



//-------------------------------------------------------------------------------
// @ IvDrawTeapot()
//-------------------------------------------------------------------------------
// Draw teapot, oriented with z pointing up
//-------------------------------------------------------------------------------
void 
IvDrawTeapot()
{
    glFrontFace(GL_CW);

    // store current matrix
    glPushMatrix();

    IvMatrix44 matrix;
    matrix(1,1) = 0.0f;
    matrix(1,2) = -1.0f;
    matrix(2,2) = 0.0f;
    matrix(2,1) = 1.0f;

    // multiply by transform
    glMultMatrixf(matrix.mV);

    glutSolidTeapot(1.25f);

    glPopMatrix();

    glFrontFace(GL_CCW);

}   // End of IvDrawTeapot()


//-------------------------------------------------------------------------------
// @ IvDrawCapsule()
//-------------------------------------------------------------------------------
// Draw capsule
//-------------------------------------------------------------------------------
void 
IvDrawCapsule( const IvLineSegment3& segment, float radius )
{
    // this is kinda of a cheat
    // we are going to draw this in a local space oriented along the z axis
    // so first we need to calculate a transform to convert from our space to 
    // the orientation and position of the actual capsule
    
    // get direction of segment axis
    IvVector3 direction = segment.GetEndpoint1() - segment.GetEndpoint0();
    float length = direction.Length();

    // if spherical capsule, just draw sphere
    if ( ::IsZero( length ) )
    {
        IvMatrix44 transform;
        transform(0,3) = segment.GetEndpoint0().x;
        transform(1,3) = segment.GetEndpoint0().y;
        transform(2,3) = segment.GetEndpoint0().z;

        // concatenate with the current matrix
        glPushMatrix();
        glMultMatrixf(transform.mV);

        ::IvDrawSphere( radius );
        
        // restore current matrix
        glPopMatrix();

        return;
    }

    // rotate z axis to segment axis
    direction.Normalize();
    IvQuat quat( IvVector3::zAxis, direction );

    // create matrix to align with axis, then translate the origin to endpoint0
    IvMatrix44 transform(quat);
    transform(0,3) = segment.GetEndpoint0().x;
    transform(1,3) = segment.GetEndpoint0().y;
    transform(2,3) = segment.GetEndpoint0().z;

    // concatenate with the current matrix
    glPushMatrix();
    glMultMatrixf(transform.mV);

    IvVector3 xyz( 0.0f, 0.0f, 1.0f );
    IvVector3 normal;
    const float increment = kPI*0.03125f;
    const float thetaIncrement = kTwoPI*0.03125f;

    // draw the bottom
    float phi, theta;
    for (phi = -kHalfPI+0.00001f; phi < 0.0f; phi += increment)
    {
        // draw everything after this as a triangle strip
        glBegin(GL_TRIANGLE_STRIP);

        int verts = 0;
        for (theta = kTwoPI+0.5f*thetaIncrement; theta >= 0.0f; theta -= thetaIncrement)
        {
            float sinTheta, cosTheta;
            IvSinCos(theta, sinTheta, cosTheta);

            float sinPhi, cosPhi;
            IvSinCos(phi, sinPhi, cosPhi);

            normal[0] = cosTheta*cosPhi;
            normal[1] = sinTheta*cosPhi;
            normal[2] = sinPhi;
            glNormal3fv(&normal.x);
            xyz[0] = radius*normal[0];
            xyz[1] = radius*normal[1];
            xyz[2] = radius*normal[2];
            glVertex3fv(&xyz.x);

            float sinPhiInc, cosPhiInc;
            IvSinCos(phi + increment, sinPhiInc, cosPhiInc);

            normal[0] = cosTheta*cosPhiInc;
            normal[1] = sinTheta*cosPhiInc;
            normal[2] = sinPhiInc;
            glNormal3fv(&normal.x);
            xyz[0] = radius*normal[0];
            xyz[1] = radius*normal[1];
            xyz[2] = radius*normal[2];
            glVertex3fv(&xyz.x);
        }
        
        glEnd();
    }

    // draw the middle
    // draw everything after this as a triangle strip
    glBegin(GL_TRIANGLE_STRIP);

    int verts = 0;
    for (theta = kTwoPI+0.5f*thetaIncrement; theta >= 0.0f; theta -= thetaIncrement)
    {
        float sinTheta, cosTheta;
        IvSinCos(theta, sinTheta, cosTheta);

        normal[0] = cosTheta;
        normal[1] = sinTheta;
        normal[2] = 0.0f;
        glNormal3fv(&normal.x);
        xyz[0] = radius*normal[0];
        xyz[1] = radius*normal[1];
        xyz[2] = radius*normal[2];
        glVertex3fv(&xyz.x);

        glNormal3fv(&normal.x);
        xyz[2] += length;
        glVertex3fv(&xyz.x);
    }
    
    glEnd();

    // draw the top
    for (phi = 0.0f; phi < kHalfPI; phi += increment)
    {
        // draw everything after this as a triangle strip
        glBegin(GL_TRIANGLE_STRIP);

        int verts = 0;
        for (theta = kTwoPI+0.5f*thetaIncrement; theta >= 0.0f; theta -= thetaIncrement)
        {
            float sinTheta, cosTheta;
            IvSinCos(theta, sinTheta, cosTheta);

            float sinPhi, cosPhi;
            IvSinCos(phi, sinPhi, cosPhi);

            normal[0] = cosTheta*cosPhi;
            normal[1] = sinTheta*cosPhi;
            normal[2] = sinPhi;
            glNormal3fv(&normal.x);
            xyz[0] = radius*normal[0];
            xyz[1] = radius*normal[1];
            xyz[2] = radius*normal[2]+length;
            glVertex3fv(&xyz.x);

            float sinPhiInc, cosPhiInc;
            IvSinCos(phi + increment, sinPhiInc, cosPhiInc);

            normal[0] = cosTheta*cosPhiInc;
            normal[1] = sinTheta*cosPhiInc;
            normal[2] = sinPhiInc;
            glNormal3fv(&normal.x);
            xyz[0] = radius*normal[0];
            xyz[1] = radius*normal[1];
            xyz[2] = radius*normal[2]+length;
            glVertex3fv(&xyz.x);
        }
        
        glEnd();
    } 

    // restore current matrix
    glPopMatrix();

}   // End of IvDrawCapsule()


//-------------------------------------------------------------------------------
// @ IvDrawAxes()
//-------------------------------------------------------------------------------
// Draw coordinate axes
//-------------------------------------------------------------------------------
void 
IvDrawAxes()
{
    ::IvSetWorldIdentity();

    GLboolean lighting = false;
    glGetBooleanv(GL_LIGHTING, &lighting);
    if ( lighting )
        glDisable(GL_LIGHTING);
    glBegin(GL_LINES);

    glColor3f(1.0f, 0.0f, 0.0f);
    glVertex3f(0.0f, 0.0f, 0.0f);
    glVertex3f(2.5f, 0.0f, 0.0f);

    glColor3f(0.0f, 1.0f, 0.0f);
    glVertex3f(0.0f, 0.0f, 0.0f);
    glVertex3f(0.0f, 2.5f, 0.0f);

    glColor3f(0.0f, 0.0f, 1.0f);
    glVertex3f(0.0f, 0.0f, 0.0f);
    glVertex3f(0.0f, 0.0f, 2.5f);

    glEnd();
    if (lighting)
        glEnable( GL_LIGHTING );

}   // End of IvDrawAxes()


//-------------------------------------------------------------------------------
// @ IvDrawFloor()
//-------------------------------------------------------------------------------
// Draw tiled floor at the origin
//-------------------------------------------------------------------------------
void 
IvDrawFloor()
{
    ::IvSetWorldIdentity();

    // draw a floor
    for ( int i = -12; i <= 12; i += 3 )
    {
        for ( int j = -12; j <= 12; j += 3 )
        {
            glPushMatrix();
            glTranslatef( (float)i, (float)j, 0.0f );
            glBegin(GL_TRIANGLE_STRIP);
            if ( ( i & 0x1 ) == (j & 0x1 ))
                glColor3f( 0.25f, 0.25f, 0.25f );
            else
                glColor3f( 0.75f, 0.75f, 0.75f );
            glVertex3f(0.0f, 0.0f, 0.0f);
            glVertex3f(3.0f, 0.0f, 0.0f);
            glVertex3f(0.0f, 3.0f, 0.0f);
            glVertex3f(3.0f, 3.0f, 0.0f);
            glEnd();
            glPopMatrix();
        }
    }

}   // End of IvDrawFloor()


//-------------------------------------------------------------------------------
// @ IvDrawLine()
//-------------------------------------------------------------------------------
// Draw line segment from one 3D location to another
//-------------------------------------------------------------------------------
void 
IvDrawLine( const IvVector3& from, const IvVector3& to )
{
    GLboolean lighting = false;
    glGetBooleanv(GL_LIGHTING, &lighting);
    if ( lighting )
        glDisable(GL_LIGHTING);

    glBegin(GL_LINES);
    glVertex3fv( (float*)&from.x );
    glVertex3fv( (float*)&to.x );
    glEnd();
    if (lighting)
        glEnable( GL_LIGHTING );

}   // End of IvDrawLine()


//-------------------------------------------------------------------------------
// @ IvDrawArrays()
//-------------------------------------------------------------------------------
// Draw object based on vertex arrays
//-------------------------------------------------------------------------------
void 
IvDrawArrays( const IvVector3* vertices, const IvVector3* normals,
              const IvVector3* colors, unsigned int* indices,
              unsigned int numIndices )
{
    glEnableClientState( GL_VERTEX_ARRAY );
    glEnableClientState( GL_NORMAL_ARRAY );
    glEnableClientState( GL_COLOR_ARRAY );

    glVertexPointer( 3, GL_FLOAT, 0, vertices );
    glNormalPointer( GL_FLOAT, 0, normals );
    glColorPointer( 3, GL_FLOAT, 0, colors );

    glDrawElements( GL_TRIANGLES, numIndices, GL_UNSIGNED_INT, indices );

    glDisableClientState( GL_VERTEX_ARRAY );
    glDisableClientState( GL_NORMAL_ARRAY );
    glDisableClientState( GL_COLOR_ARRAY );

}   // End of IvDrawArrays()


//-------------------------------------------------------------------------------
// @ SetDefaultViewer()
//-------------------------------------------------------------------------------
// Set default viewer location, looking at origin
//-------------------------------------------------------------------------------
void 
IvSetDefaultViewer( float xPos, float yPos, float zPos )
{
    // Reset The View
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();   
    
    // look from (xPos, yPos, zPos) at the origin of the world 
    gluLookAt( xPos, yPos, zPos, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0 );

    // save a copy of this
    glPushMatrix();
    
}   // End of IvSetDefaultViewer()


//-------------------------------------------------------------------------------
// @ IvSetDefaultLighting()
//-------------------------------------------------------------------------------
// Set up a default lighting scheme
//-------------------------------------------------------------------------------
void 
IvSetDefaultLighting()
{
    glEnable(GL_LIGHT1);
    IvVector4 direction(-1.0f, 0.5f, 0.25f, 0.0f);
    IvVector4 ambient(0.4f, 0.4f, 0.4f, 1.0f);
    IvVector4 diffuse(0.25f, 0.25f, 0.25f, 1.0f);
    glLightfv(GL_LIGHT1, GL_POSITION, (GLfloat*) &direction);
    glLightfv(GL_LIGHT1, GL_AMBIENT, (GLfloat*) &ambient);
    glLightfv(GL_LIGHT1, GL_DIFFUSE, (GLfloat*) &diffuse);

    glEnable( GL_LIGHTING );
    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);

    // plug in a light
//  glEnable(GL_LIGHT0);
//  IvVector4 direction(-1.0f, 0.5f, 0.25f, 0.0f);
//  IvVector4 ambient(0.1f, 0.1f, 0.1f, 1.0f);
//  glLightfv(GL_LIGHT0, GL_POSITION, (float*) &direction);
//  glLightfv(GL_LIGHT0, GL_AMBIENT, (float*) &ambient);

}   // End of IvSetDefaultLighting()


//-------------------------------------------------------------------------------
// @ IvSetWorldMatrix()
//-------------------------------------------------------------------------------
// Set current world matrix
//-------------------------------------------------------------------------------
void 
IvSetWorldMatrix( const IvMatrix44& world )
{
    glMatrixMode(GL_MODELVIEW);

    // pop old world matrix
    glPopMatrix();

    // push to save view matrix
    glPushMatrix();

    // multiply new world matrix
    glMultMatrixf( world.mV );

}   // End of IvSetWorldMatrix()


//-------------------------------------------------------------------------------
// @ IvSetWorldIdentity()
//-------------------------------------------------------------------------------
// Set current world matrix
//-------------------------------------------------------------------------------
void 
IvSetWorldIdentity()
{
    glMatrixMode(GL_MODELVIEW);

    // pop old world matrix
    glPopMatrix();

    // push to save view matrix
    glPushMatrix();

}   // End of IvSetWorldIdentity()


//-------------------------------------------------------------------------------
// @ IvSetViewMatrix()
//-------------------------------------------------------------------------------
// Set current view matrix
//-------------------------------------------------------------------------------
void 
IvSetViewMatrix( const IvMatrix44& view )
{
    glMatrixMode(GL_MODELVIEW);
    
    // pop old world matrix
    glPopMatrix();

    // overwrite with new view matrix
    glLoadMatrixf( view.mV );

    // push to save view matrix
    glPushMatrix();

}   // End of IvSetViewMatrix()


//-------------------------------------------------------------------------------
// @ IvSetViewMatrix()
//-------------------------------------------------------------------------------
// Set current projection matrix
//-------------------------------------------------------------------------------
void 
IvSetProjectionMatrix( const IvMatrix44& projection )
{
    glMatrixMode(GL_PROJECTION);                        
    glLoadMatrixf( projection.mV );

}   // End of IvSetProjectionMatrix()


//-------------------------------------------------------------------------------
// @ IvSetViewport()
//-------------------------------------------------------------------------------
// Set current viewport window
//-------------------------------------------------------------------------------
void 
IvSetViewport()
{

}   // End of IvSetViewport()

