//===============================================================================
// @ Player.cpp
// ------------------------------------------------------------------------------
// Player
//
// Copyright (C) 2004 by Elsevier, Inc. All rights reserved.
//
//
//
// This example demonstrates the textured rendering of a cylinder using both
// textures based on an image file and on image data created directly in app 
// code.  The application allows the user to switch textures on the fly and to
// change texture coordinates on the fly.
//
// The key commands are:
//
// i, k - translate in x
// j, l - translate in y
// u, o - rotate around z axis
// p, ; - uniformly scale
// Space - reset transforms
// t - change texture image
// c - change texture coordinates
//
//===============================================================================

//-------------------------------------------------------------------------------
//-- 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()
{
    mRotate.Identity();
    mScale = 1.0f;
    mTranslate.Zero();

    mRadius = 3.0f;

    mCylinderVerts = 0;
    mCylinderIndices = 0;

    unsigned int i;
    for (i = 0; i < NUM_UVS; i++)
        mCylinderUVs[i] = 0;

    mCylinderIndexCount = 0;
    
    mCurrentUVIndex = 0;

    for (i = 0; i < NUM_TEX; i++)
        mTextureIDs[i] = 0;

    mCurrentTexIndex = 0;

    glEnable( GL_TEXTURE_2D );

    // create a texture "name" to reference later
    glGenTextures( NUM_TEX, mTextureIDs );

    glBindTexture( GL_TEXTURE_2D, mTextureIDs[0] );
    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 soup can 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;
    }

    // create a texture "name" to reference later
    glBindTexture( GL_TEXTURE_2D, mTextureIDs[1] );
    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 );

    // create checkerboard texture procedurally
    const unsigned int size = 128;
    unsigned char pixels[3 * size * size]; // RGB per texel

    unsigned char* tempPixels = pixels;

    unsigned int j;
    for (i = 0; i < size; i++)
    {
        for (j = 0; j < size; j++)
        {
            // 8x8 texel squares of black and white
            unsigned char color;
            if (((j >> 3) + (i >> 3)) % 2)
                color = 255;
            else
                color = 0;

            // set all three components to the same value
            tempPixels[0] = tempPixels[1] = tempPixels[2] = color;
            
            tempPixels += 3;
        }
    }

    glTexImage2D( GL_TEXTURE_2D, 0, GL_RGB, size, size, 0, GL_RGB, 
        GL_UNSIGNED_BYTE, pixels );

    glDisable( GL_TEXTURE_2D );

    CreateCylinderVertexArrays();
}   // End of Player::Player()


//-------------------------------------------------------------------------------
// @ Player::~Player()
//-------------------------------------------------------------------------------
// Destructor
//-------------------------------------------------------------------------------
Player::~Player()
{
    delete[] mCylinderVerts;
    delete[] mCylinderIndices;

    unsigned int i;
    for (i = 0; i < NUM_UVS; i++)
        delete[] mCylinderUVs[i];

    glDeleteTextures(NUM_TEX, mTextureIDs); 
}   // End of Player::~Player()


//-------------------------------------------------------------------------------
// @ Player::Update()
//-------------------------------------------------------------------------------
// Main update loop
//-------------------------------------------------------------------------------
void
Player::Update( float dt )
{
    // get scale, rotate, translate for this frame
    float s = 1.0f;
    float r = 0.0f;
    
    // set up scaling
    if (IvGame::mGame->mEventHandler->IsKeyDown(';'))
    {
        s -= 0.25f*dt;
    }
    if (IvGame::mGame->mEventHandler->IsKeyDown('p'))
    {
        s += 0.25f*dt;
    }
    mScale *= s;
    
    // set up rotate
    if (IvGame::mGame->mEventHandler->IsKeyDown('o'))
    {
        r -= kPI*0.25f*dt;
    }
    if (IvGame::mGame->mEventHandler->IsKeyDown('u'))
    {
        r += kPI*0.25f*dt;
    }
    IvMatrix33 rotate;
    rotate.RotationZ(r);
    mRotate = rotate*mRotate;
    
    // set up translation
    IvVector3 xlate;
    xlate.Zero();
    if (IvGame::mGame->mEventHandler->IsKeyDown('k'))
    {
        xlate.x -= 3.0f*dt;
    }
    if (IvGame::mGame->mEventHandler->IsKeyDown('i'))
    {
        xlate.x += 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;
    }
    mTranslate += xlate;
    
    // clear transform
    if (IvGame::mGame->mEventHandler->IsKeyDown(' '))
    {
        mRotate.Identity();
        mScale = 1.0f;
        mTranslate.Zero();
    }

    // change texture
    if (IvGame::mGame->mEventHandler->IsKeyDown('t'))
    {
        mCurrentTexIndex = (mCurrentTexIndex + 1) % NUM_TEX;
        IvGame::mGame->mEventHandler->KeyUp('t');
    }

    // change texture UVs
    if (IvGame::mGame->mEventHandler->IsKeyDown('c'))
    {
        mCurrentUVIndex = (mCurrentUVIndex + 1) % NUM_UVS;
        IvGame::mGame->mEventHandler->KeyUp('c');
    }

}   // End of Player::Update()


//-------------------------------------------------------------------------------
// @ Player::Render()
//-------------------------------------------------------------------------------
// Render stuff
//-------------------------------------------------------------------------------
void 
Player::Render()                                    
{   
    // build 4x4 matrix
    IvMatrix44 transform(mRotate);

    transform(0,0) *= mScale;
    transform(1,0) *= mScale;
    transform(2,0) *= mScale;
    transform(0,1) *= mScale;
    transform(1,1) *= mScale;
    transform(2,1) *= mScale;
    transform(0,2) *= mScale;
    transform(1,2) *= mScale;
    transform(2,2) *= mScale;
    transform(0,3) = mTranslate.x;
    transform(1,3) = mTranslate.y;
    transform(2,3) = mTranslate.z;
    
    // store current matrix
    glPushMatrix();

    // multiply by transform
    glMultMatrixf(transform);

    // turn off the lights
    glDisable( GL_LIGHTING );

    // Apply texturing
    glEnable( GL_TEXTURE_2D );
    glBindTexture( GL_TEXTURE_2D, mTextureIDs[mCurrentTexIndex] );

    // draw geometry
    glColor3f(1.0f, 1.0f, 1.0f);
    DrawCylinderVertexArrays();

    glDisable( GL_TEXTURE_2D );

    // restore original matrix          
    glPopMatrix();

}   // End of Player::Render()


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

    glVertexPointer(3, GL_FLOAT, 0, mCylinderVerts); 
    glTexCoordPointer(2, GL_FLOAT, 0, mCylinderUVs[mCurrentUVIndex]); 
    
    glDrawElements(GL_TRIANGLE_STRIP, mCylinderIndexCount, GL_UNSIGNED_SHORT, 
        mCylinderIndices);

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
} // End of Player::DrawCylinderVertexArrays()
    
//-------------------------------------------------------------------------------
// @ Player::CreateCylinderVertexArrays()
//-------------------------------------------------------------------------------
// Create vertex arrays for a cylinder centered around the origin
//-------------------------------------------------------------------------------
void 
Player::CreateCylinderVertexArrays()                                    
{
    // Creates a grid of points, shaped into a cylinder.  In order to avoid a
    // texturing anomaly, we cannot simply share the vertical seam edge vertices
    // They must be duplicated; one copy must have a U-coord of 0.0, the other a
    // U-coord of 1.0f
    const unsigned int steps = 32;
    mCylinderVerts = new IvVector3[(steps + 1) * steps];

    unsigned int i;
    for (i = 0; i < NUM_UVS; i++)
        mCylinderUVs[i] = new IvVector2[(steps + 1) * steps];

    // temporary pointers that can be stepped along the arrays
    IvVector3* tempVerts = mCylinderVerts;
    IvVector2* tempUVs = mCylinderUVs[0];

    // A double loop, walking around and down the cylinder
    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);

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

            if (i == 0)
            {
                tempVerts->x = 0.0f;
                tempVerts->y = 0.0f;
                tempVerts->z = -mRadius;
            }
            else if (i == (steps - 1))
            {
                tempVerts->x = 0.0f;
                tempVerts->y = 0.0f;
                tempVerts->z = mRadius;
            }
            else
            {
                tempVerts->x = mRadius * cosTheta;
                tempVerts->y = mRadius * sinTheta;
                tempVerts->z = mRadius * IvSin(phi);
            }

            tempUVs->x = u;
            tempUVs->y = v;

            tempVerts++;
            tempUVs++;
        }
    }

    // compute the other texture UVs as multiples of the original
    for (j = 1; j < NUM_UVS; j++)
    {
        const IvVector2* srcUVs = mCylinderUVs[j - 1];
        tempUVs = mCylinderUVs[j];

        for (i = 0; i < steps*steps; i++)
        {
            *tempUVs = 2.0f * (*srcUVs); 

            srcUVs++;
            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 cylinder
    mCylinderIndexCount = steps * 2 + (steps - 1) * (steps * 2 + 2);

    mCylinderIndices = new unsigned short[mCylinderIndexCount];

    unsigned short* tempIndices = mCylinderIndices;

    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::CreateCylinderVertexArrays()



