//===============================================================================
// @ Player.cpp
// ------------------------------------------------------------------------------
// Player
//
// Copyright (C) 2004 by Elsevier, Inc. All rights reserved.
//
//
//
// This example demonstrates a point light and its effect upon a tesselated
// sphere, both with and without texturing.  The controls move the light.  A red
// plus sign icon shows the position of the light.  The draw mode may be changed
// to see the texture colors and lighting colors by themselves, as well as the
// combined result.
//
// The key commands are:
//
// i, k - translate light in x
// j, l - translate light in y
// Space - reset light transforms
// d - change draw mode (lit and textured, lit, textured)
//
//===============================================================================

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

#include <IvEventHandler.h>
#include <IvGLHelp.h>
#include <IvImage.h>
#include <IvMath.h>
#include <IvMatrix44.h>

#include "Player.h"
#include "Game.h"

//-------------------------------------------------------------------------------
//-- Static Members -------------------------------------------------------------
//-------------------------------------------------------------------------------

//-------------------------------------------------------------------------------
//-- Methods --------------------------------------------------------------------
//-------------------------------------------------------------------------------

//-------------------------------------------------------------------------------
// @ Player::Player()
//-------------------------------------------------------------------------------
// Constructor
//-------------------------------------------------------------------------------
Player::Player()
{
    mLightPos.Set(-3.0f, 0.0f, 0.0f, 1.0f);

    mSphereVerts = 0;
    mSphereUVs = 0;
    mSphereIndices = 0;
    
    mRadius = 2.0f;

    mTextureID = 0;

    mDrawMode = DRAW_LIT_TEXTURE;

    glEnable( GL_TEXTURE_2D );

    // create a texture "name" to reference later
    glGenTextures( 1, &mTextureID );

    glBindTexture( GL_TEXTURE_2D, mTextureID );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );

    // Load the Frankfurt Euro image into this texture
    IvImage* image = IvImage::CreateFromFile("image.tga");
    if (image)
    {
        glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, 
            image->GetWidth(), image->GetHeight(), 0, GL_RGB, GL_UNSIGNED_BYTE, 
            image->GetPixels() );
        delete image;
        image = 0;
    }

    glDisable( GL_TEXTURE_2D );

    CreateSphereVertexArrays();

}   // End of Player::Player()


//-------------------------------------------------------------------------------
// @ Player::~Player()
//-------------------------------------------------------------------------------
// Destructor
//-------------------------------------------------------------------------------
Player::~Player()
{
    delete[] mSphereVerts;
    delete[] mSphereUVs;
    delete[] mSphereIndices;

    // Free the allocated texture
    glDeleteTextures(1, &mTextureID); 

}   // End of Player::~Player()


//-------------------------------------------------------------------------------
// @ Player::Update()
//-------------------------------------------------------------------------------
// Main update loop
//-------------------------------------------------------------------------------
void
Player::Update( float dt )
{       
    // set up translation
    IvVector4 xlate;
    xlate.Zero();
    if (IvGame::mGame->mEventHandler->IsKeyDown('k'))
    {
        xlate.z -= 3.0f*dt;
    }
    if (IvGame::mGame->mEventHandler->IsKeyDown('i'))
    {
        xlate.z += 3.0f*dt;
    }
    if (IvGame::mGame->mEventHandler->IsKeyDown('l'))
    {
        xlate.y -= 3.0f*dt;
    }
    if (IvGame::mGame->mEventHandler->IsKeyDown('j'))
    {
        xlate.y += 3.0f*dt;
    }
    mLightPos += xlate;
    
    // clear transform
    if (IvGame::mGame->mEventHandler->IsKeyDown(' '))
    {
        mLightPos.Set(-3.0f, 0.0f, 0.0f, 1.0f);
    }

    // change draw mode
    if (IvGame::mGame->mEventHandler->IsKeyDown('d'))
    {
        switch (mDrawMode)
        {
        case DRAW_LIT_TEXTURE:
            mDrawMode = DRAW_LIT;
            break;
        case DRAW_LIT:
            mDrawMode = DRAW_TEXTURE;
            break;
        case DRAW_TEXTURE:
            mDrawMode = DRAW_LIT_TEXTURE;
            break;
        };

        IvGame::mGame->mEventHandler->KeyUp('d');
    }

}   // End of Player::Update()


//-------------------------------------------------------------------------------
// @ Player::Render()
//-------------------------------------------------------------------------------
// Render stuff
//-------------------------------------------------------------------------------
void 
Player::Render()                                    
{   
    // build 4x4 matrix for the light icon
    IvMatrix44 transform;
    transform(0,3) = mLightPos.x;
    transform(1,3) = mLightPos.y;
    transform(2,3) = mLightPos.z;
    
    // store current matrix
    glPushMatrix();

    // multiply by transform
    glMultMatrixf(transform);

    // turn on the lights
    glDisable( GL_LIGHTING );

    // Draw a visual representation of the light
    DrawLightIcon();

    // restore original matrix          
    glPopMatrix();

    if (mDrawMode != DRAW_TEXTURE)
    {
        IvVector4 white(1.0f, 1.0f, 1.0f, 1.0f);

        // turn on the lights
        glEnable( GL_LIGHTING );
        glEnable( GL_NORMALIZE );
        glDisable( GL_COLOR_MATERIAL );

        // set reasonable deafults for the material
        glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, 30.0f);
        glMaterialfv(GL_FRONT, GL_AMBIENT, (float *)&white);
        glMaterialfv(GL_FRONT, GL_DIFFUSE, (float *)&white);
        glMaterialfv(GL_FRONT, GL_SPECULAR, (float *)&white);

        glEnable(GL_LIGHT0);

        // position the light in world space
        glLightfv(GL_LIGHT0, GL_POSITION, (float *)&mLightPos);

        // set reasonable deafults for the light
        IvVector4 lightAmb(0.25f, 0.125f, 0.125f, 1.0f);
        IvVector4 lightDiff(0.125f, 0.5f, 0.125f, 1.0f);
        IvVector4 lightSpec(0.5f, 0.5f, 0.5f, 1.0f);

        glLightfv(GL_LIGHT0, GL_AMBIENT, (float *)&lightAmb);
        glLightfv(GL_LIGHT0, GL_DIFFUSE, (float *)&lightDiff);
        glLightfv(GL_LIGHT0, GL_SPECULAR, (float *)&lightSpec);
    }
    
    if (mDrawMode != DRAW_LIT)
    {
        glEnable( GL_TEXTURE_2D );
        glBindTexture( GL_TEXTURE_2D, mTextureID );
    }

    // draw sphere geometry
    DrawSphereVertexArrays();

    glDisable( GL_LIGHTING );
    glDisable( GL_TEXTURE_2D );

}   // End of Player::Render()


//-------------------------------------------------------------------------------
// @ Player::DrawSphereVertexArrays()
//-------------------------------------------------------------------------------
// Draw vertex arrays for a sphere centered around the origin
//-------------------------------------------------------------------------------
void
Player::DrawSphereVertexArrays()
{
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    glVertexPointer(3, GL_FLOAT, 0, mSphereVerts); 
    glNormalPointer(GL_FLOAT, 0, mSphereNorms); 
    glTexCoordPointer(2, GL_FLOAT, 0, mSphereUVs); 
    
    glDrawElements(GL_TRIANGLE_STRIP, mSphereIndexCount, GL_UNSIGNED_SHORT, 
        mSphereIndices);

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
} // End of Player::DrawSphereVertexArrays()
    
//-------------------------------------------------------------------------------
// @ Player::CreateSphereVertexArrays()
//-------------------------------------------------------------------------------
// Create vertex arrays for a sphere centered around the origin
//-------------------------------------------------------------------------------
void 
Player::CreateSphereVertexArrays()                                  
{
    // Creates a grid of points, shaped into a sphere.  This is not an
    // efficient way to create a sphere (the verts are not evenly distributed),
    // but it shows how to set up arrays of vertices and normals
    const unsigned int steps = 32;
    mSphereVerts = new IvVector3[(steps + 1) * steps];
    mSphereNorms = new IvVector3[(steps + 1) * steps];
    mSphereUVs = new IvVector2[(steps + 1) * steps];

    // temporary pointers that can be stepped along the arrays
    IvVector3* tempVerts = mSphereVerts;
    IvVector3* tempNorms = mSphereNorms;
    IvVector2* tempUVs = mSphereUVs;

    // A double loop, walking around and down the sphere
    const float phiIncrement = kPI / (steps - 1);
    const float thetaIncrement = kTwoPI / steps;

    unsigned int j;
    for (j = 0; j <= steps; j++)
    {
        float theta = thetaIncrement * j;
        float u = j / (float)steps;

        float sinTheta, cosTheta;
        IvSinCos(theta, sinTheta, cosTheta);

        unsigned int i;
        for (i = 0; i < steps; i++)
        {
            float phi = phiIncrement * i - kHalfPI;
            float v = i / (float)(steps - 1);

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

            // the normal and position are directly related (simply a scaling)
            tempNorms->x = cosTheta*cosPhi;
            tempNorms->y = sinTheta*cosPhi;
            tempNorms->z = sinPhi;

            *tempVerts = mRadius * (*tempNorms);

            tempUVs->x = u * 1.5f + 0.6f; // slide U to center the Euro
            tempUVs->y = v;

            tempVerts++;
            tempNorms++;
            tempUVs++;
        }
    }

    // Create index arrays - just a 32x31-quad mesh of triangles
    // Each of the 32 strips has 31 * 2 triangles plus two dummy indices
    // This means that there are 31 * 2 + 2 + 2 (two extra to start the
    // strip, and two extra to end the previous strip) indices in each
    // strip, although we can avoid two indices in the first strip, as
    // there is no previous strip to be ended in that case.  Thus,
    // 64 + 66 * 31 indices for the entire sphere
    mSphereIndexCount = steps * 2 + (steps - 1) * (steps * 2 + 2);

    mSphereIndices = new unsigned short[mSphereIndexCount];

    unsigned short* tempIndices = mSphereIndices;

    for (j = 0; j < steps; j++)
    {
        unsigned int baseIndex0 = steps * j;
        unsigned int baseIndex1 = steps * (j + 1);

        // restart the strip by doubling the last and next indices
        if (j != 0)
        {
            *(tempIndices++) = tempIndices[-1];
            *(tempIndices++) = baseIndex0;
        }

        unsigned int i;
        for (i = 0; i < steps; i++)
        {
            *(tempIndices++) = baseIndex0;
            *(tempIndices++) = baseIndex1;

            baseIndex0++;
            baseIndex1++;
        }
    }

}   // End of Player::CreateSphereVertexArrays()


//-------------------------------------------------------------------------------
// @ DrawLightIcon()
//-------------------------------------------------------------------------------
// Draw icon for light source
//-------------------------------------------------------------------------------
void 
Player::DrawLightIcon()
{
    glBegin(GL_LINES);

    glColor3f(1.0f, 0.0f, 0.0f);
    glVertex3f(-0.25f, 0.0f, 0.0f);
    glVertex3f(0.25f, 0.0f, 0.0f);

    glVertex3f(0.0f, -0.25f, 0.0f);
    glVertex3f(0.0f, 0.25f, 0.0f);

    glVertex3f(0.0f, 0.0f, -0.25f);
    glVertex3f(0.0f, 0.0f, 0.25f);

    glEnd();
    
}   // End of DrawLightIcon()




