//===============================================================================
// @ Player.cpp
// ------------------------------------------------------------------------------
// Player
//
// Copyright (C) 2004 by Elsevier, Inc. All rights reserved.
//
//
//
// This example demonstrates the textured rendering of two copies of a cylinder.
// The first, inner cylinder is rendered as opaque with the soup can texture. The
// second, outer cylinder is rendered using one of a set of three different
// textures: 
// 1) a texture with solid, patterned RGB components, and a fence-shaped alpha
// channel, leading to the appearance of a cage when alpha blending is used
// 2) a texture with a white background and dark dots, with an alpha component
// that is opaque where the texture is dark.  This texture is interesting with
// several blending modes, including modes that ignore the alpha channel.
// 3) a texture whose RGB components are the inverse of the above texture.
//
// 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 blending texture image
// b - change blending mode (alpha blending, additive, multiply, none)
//
//===============================================================================

//-------------------------------------------------------------------------------
//-- 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;

    mBlendMode = BLEND_ALPHA;
    mSrcBlend = GL_SRC_ALPHA;
    mDestBlend = GL_ONE_MINUS_SRC_ALPHA;

    unsigned int i;

    mBaseTextureID = 0;

    for (i = 0; i < NUM_BLEND_TEX; i++)
        mBlendTextureIDs[i] = 0;

    mCurrentBlendTexIndex = 0;

    glEnable( GL_TEXTURE_2D );

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

    glBindTexture( GL_TEXTURE_2D, mBaseTextureID );
    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
    glGenTextures( NUM_BLEND_TEX, mBlendTextureIDs );

    glBindTexture( GL_TEXTURE_2D, mBlendTextureIDs[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
    image = IvImage::CreateFromFile("fence.tga");
    if (image)
    {
        glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 
            image->GetWidth(), image->GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, 
            image->GetPixels() );
        delete image;
        image = 0;
    }

    glBindTexture( GL_TEXTURE_2D, mBlendTextureIDs[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 );

    // Load the soup can image into this texture
    image = IvImage::CreateFromFile("darkdots.tga");
    if (image)
    {
        glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 
            image->GetWidth(), image->GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, 
            image->GetPixels() );
        delete image;
        image = 0;
    }

    glBindTexture( GL_TEXTURE_2D, mBlendTextureIDs[2] );
    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
    image = IvImage::CreateFromFile("glowdots.tga");
    if (image)
    {
        glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, 
            image->GetWidth(), image->GetHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, 
            image->GetPixels() );
        delete image;
        image = 0;
    }

    glDisable( GL_TEXTURE_2D );

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


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

    delete[] mCylinderUVs;

    // Free allocated textures
    glDeleteTextures(1, &mBaseTextureID); 
    glDeleteTextures(NUM_BLEND_TEX, mBlendTextureIDs); 

}   // 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'))
    {
        mCurrentBlendTexIndex = (mCurrentBlendTexIndex + 1) % NUM_BLEND_TEX;
        IvGame::mGame->mEventHandler->KeyUp('t');
    }

    // change blending mode
    if (IvGame::mGame->mEventHandler->IsKeyDown('b'))
    {
        switch (mBlendMode)
        {
        case BLEND_ALPHA:
            mBlendMode = BLEND_ADD;
            mSrcBlend = GL_ONE;
            mDestBlend = GL_ONE;
            break;
        case BLEND_ADD:
            mBlendMode = BLEND_MULT;
            mSrcBlend = GL_ZERO;
            mDestBlend = GL_SRC_COLOR;
            break;
        case BLEND_MULT:
            mBlendMode = BLEND_NONE;
            mSrcBlend = GL_ONE;
            mDestBlend = GL_ZERO;
            break;
        case BLEND_NONE:
            mBlendMode = BLEND_ALPHA;
            mSrcBlend = GL_SRC_ALPHA;
            mDestBlend = GL_ONE_MINUS_SRC_ALPHA;
            break;
        };

        IvGame::mGame->mEventHandler->KeyUp('b');
    }
}   // End of Player::Update()


//-------------------------------------------------------------------------------
// @ Player::Render()
//-------------------------------------------------------------------------------
// Render stuff
//-------------------------------------------------------------------------------
void 
Player::Render()                                    
{   
    // turn off the lights
    glDisable( GL_LIGHTING );

    glEnable( GL_TEXTURE_2D );

    // 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);

    // disable blending
    glDisable(GL_BLEND);

    // set base texture
    glBindTexture( GL_TEXTURE_2D, mBaseTextureID );

    // draw inner, opaque cylinder
    glColor3f(1.0f, 1.0f, 1.0f);
    DrawCylinderVertexArrays();

    // restore original matrix          
    glPopMatrix();

    // push transform with larger scale for outside cylinder
    IvMatrix44 transformOutside(mRotate);

    float outerScale = mScale * 1.2f;

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

    // multiply by transform
    glMultMatrixf(transformOutside);

    // set blending on
    glEnable(GL_BLEND);

    // set selected blending mode
    glBlendFunc(mSrcBlend, mDestBlend);

    // set blending texture
    glBindTexture( GL_TEXTURE_2D, mBlendTextureIDs[mCurrentBlendTexIndex] );

    // draw larger, blended cylinder
    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()
{
    // Note that unlike DrawCylinder, this version avoids all of the sin and cos
    // functions each frame, and can share vertices between the strips, unlike
    // the "vertex-at-a-time" method
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);

    glVertexPointer(3, GL_FLOAT, 0, mCylinderVerts); 
    glTexCoordPointer(2, GL_FLOAT, 0, mCylinderUVs); 
    
    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];

    mCylinderUVs = new IvVector2[(steps + 1) * steps];

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

    // 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);

        unsigned int i;
        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++;
        }
    }

    // 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()


