//===============================================================================
// @ Player.cpp
// ------------------------------------------------------------------------------
// Player
//
// Copyright (C) 2004 by Elsevier, Inc. All rights reserved.
//
//
//
// This example shows the subtle and complex influences of lighting component
// colors and intensities to the rendered effect.  A grid of spheres is drawn,
// with the material colors changing across the grid.  From left to right, the
// diffuse material color changes from black (non-reflective) to white (fully-
// reflective).  From bottom to top, the specular material color changes from
// black to white.  Thus, in a single image, the effect of changing material
// diffuse and ambient colors can be seen.  The scene is lit by a single point
// light.  For instructional reasons, the ambient, diffuse and specular 
// components are each a different color.  The light (represented by a plus
// sign) may be moved in three dimensions, to explore the geometric effects of
// the lighting.  Also, the ambient, diffuse and specular intensities of the
// light may be adjusted (or even completely cleared) to see a single 
// component's effect.  Finally, the specular surface material exponent may be
// changed, so that the changes to the size of the highlight may be explored
//
// The key commands are:
//
// i, k - translate light in x
// j, l - translate light in y
// y, h - translate light in depth
// q, a - increase, decrease light ambient intensity (red)
// w, s - increase, decrease light diffuse intensity (green)
// e, d - increase, decrease light specular intensity (white)
// r, f - increase, decrease specular shininess (exponent)
// Space - reset transforms and lighting values
//
//===============================================================================

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

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

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

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

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

//-------------------------------------------------------------------------------
// @ Player::Player()
//-------------------------------------------------------------------------------
// Constructor
//-------------------------------------------------------------------------------
Player::Player()
{
    mRadius = 1.0f;

    mLightPos.Set(10.0f, 0.0f, 0.0f, 1.0f);

    mSphereVerts = 0;
    mSphereNorms = 0;
    mSphereIndices = 0;

    mAmbient = 0.5f;
    mDiffuse = 0.5f;
    mSpecular = 0.5f;
    mShine = 10.0f;

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


//-------------------------------------------------------------------------------
// @ Player::~Player()
//-------------------------------------------------------------------------------
// Destructor
//-------------------------------------------------------------------------------
Player::~Player()
{
    delete[] mSphereVerts;
    delete[] mSphereNorms;
    delete[] mSphereIndices;
}   // End of Player::~Player()


//-------------------------------------------------------------------------------
// @ Player::Update()
//-------------------------------------------------------------------------------
// Main update loop
//-------------------------------------------------------------------------------
void
Player::Update( float dt )
{
    // adjust ambient
    if (IvGame::mGame->mEventHandler->IsKeyDown('q'))
    {
        mAmbient += dt * 0.5f;
    }
    if (IvGame::mGame->mEventHandler->IsKeyDown('a'))
    {
        mAmbient -= dt * 0.5f;
        mAmbient = (mAmbient < 0.0f) ? 0.0f : mAmbient;
    }

    // adjust diffuse
    if (IvGame::mGame->mEventHandler->IsKeyDown('w'))
    {
        mDiffuse += dt * 0.5f;
    }
    if (IvGame::mGame->mEventHandler->IsKeyDown('s'))
    {
        mDiffuse -= dt * 0.5f;
        mDiffuse = (mDiffuse < 0.0f) ? 0.0f : mDiffuse;
    }
    
    // adjust specular
    if (IvGame::mGame->mEventHandler->IsKeyDown('e'))
    {
        mSpecular += dt * 0.5f;
    }
    if (IvGame::mGame->mEventHandler->IsKeyDown('d'))
    {
        mSpecular -= dt * 5.0f;
        mSpecular = (mSpecular < 0.0f) ? 0.0f : mSpecular;
    }

    // Adjust shineiness
    if (IvGame::mGame->mEventHandler->IsKeyDown('r'))
    {
        mShine += dt * 5.0f;
    }
    if (IvGame::mGame->mEventHandler->IsKeyDown('f'))
    {
        mShine -= dt * 5.0f;
        mShine = (mShine < 1.0f) ? 1.0f : mShine;
    }
    
    // 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;
    }
    if (IvGame::mGame->mEventHandler->IsKeyDown('y'))
    {
        xlate.x -= 3.0f*dt;
    }
    if (IvGame::mGame->mEventHandler->IsKeyDown('h'))
    {
        xlate.x += 3.0f*dt;
    }
    mLightPos += xlate;
    
    // clear transform
    if (IvGame::mGame->mEventHandler->IsKeyDown(' '))
    {
        mLightPos.Set(10.0f, 0.0f, 0.0f, 1.0f);

        mAmbient = 0.5f;
        mDiffuse = 0.5f;
        mSpecular = 0.5f;
        mShine = 10.0f;
    }

}   // End of Player::Update()


//-------------------------------------------------------------------------------
// @ Player::Render()
//-------------------------------------------------------------------------------
// Render stuff
//-------------------------------------------------------------------------------
void 
Player::Render()                                    
{   
    // turn on the lights
    glEnable( GL_LIGHTING );
    glEnable( GL_NORMALIZE );
    glDisable(GL_COLOR_MATERIAL);

    // turn off the global ambient term
    IvVector4 black(0.0f, 0.0f, 0.0f, 1.0f);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, (float*) &black); 

    // increase the material ambient color to full white
    IvVector4 white(1.0f, 1.0f, 1.0f, 1.0f);
    glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, (float*) &white);

    // Set the user-supplied specular exponent
    glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, mShine);

    // Create a grid of spheres to be lit
    const int hCount = 8;
    const int vCount = 6;
    const float spacing = mRadius * 2.25f;
    IvVector4 origin(0.0f, -spacing * (hCount - 1.0f) * 0.5f, 
        -spacing * (vCount - 1.0f) * 0.5f, 1.0f); 

    // set the light components according to the user settings
    glEnable(GL_LIGHT0);
    
    IvVector4 ambient(1.0f, 0.0f, 0.0f, 1.0f);
    ambient *= mAmbient;
    glLightfv(GL_LIGHT0, GL_AMBIENT, (float*) &ambient);

    IvVector4 diffuse(0.0f, 1.0f, 0.0f, 1.0f);
    diffuse *= mDiffuse;
    glLightfv(GL_LIGHT0, GL_DIFFUSE, (float*) &diffuse);

    IvVector4 specular(1.0f, 1.0f, 1.0f, 1.0f);
    specular *= mSpecular;
    glLightfv(GL_LIGHT0, GL_SPECULAR, (float*) &specular);

    glLightfv(GL_LIGHT0, GL_POSITION, (float*) &mLightPos);

    // build 4x4 matrix
    IvMatrix44 transform;

    // create the spheres
    int j;
    for (j = 0; j < vCount; j++)
    {
        IvVector4 rowOrigin = origin;
        origin.z += spacing;

        IvVector4 specular = white * (float)j / (vCount - 1);
        glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, (float*) &specular);

        int i;
        for (i = 0; i < hCount; i++)
        {
            IvVector4 diffuse = white * (float)i / (hCount - 1);
            glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, (float*) &diffuse);

            /// set the position of the sphere
            transform(0,3) = rowOrigin.x;
            transform(1,3) = rowOrigin.y;
            transform(2,3) = rowOrigin.z;

            // store current matrix
            glPushMatrix();

            // multiply by transform
            glMultMatrixf(transform);

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

            // restore original matrix          
            glPopMatrix();

            rowOrigin.y += spacing;
        }
    }

    glDisable( GL_LIGHTING );

    // build 4x4 matrix for the light
    transform(0,3) = mLightPos.x;
    transform(1,3) = mLightPos.y;
    transform(2,3) = mLightPos.z;
    
    // store current matrix
    glPushMatrix();

    // multiply by transform
    glMultMatrixf(transform);

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

    // restore original matrix          
    glPopMatrix();

}   // End of Player::Render()


//-------------------------------------------------------------------------------
// @ Player::DrawSphereVertexArrays()
//-------------------------------------------------------------------------------
// Draw vertex arrays for a sphere centered around the origin
//-------------------------------------------------------------------------------
void
Player::DrawSphereVertexArrays()
{
    // 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_NORMAL_ARRAY);

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

    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_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 * steps];
    mSphereNorms = new IvVector3[steps * steps];

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

    // 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 sinTheta, cosTheta;
        IvSinCos(theta, sinTheta, cosTheta);

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

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

            tempVerts++;
            tempNorms++;
        }
    }

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

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



