//===============================================================================
// @ Player.cpp
// ------------------------------------------------------------------------------
// Player
//
// Copyright (C) 2004 by Elsevier, Inc. All rights reserved.
//
//
//
// This example demonstrates the effect of distance attenuation with point lights
// The three rows of spheres are each lit by lights of the same color and 
// intensity.  However, moving from left to right across the screen, the distance
// from the light to the sphere increases linearly.  The top row uses constant
// attenuation only.  The second row uses pure linear attenuation, and the bottom
// row uses pure quadratic.  The intensity of each of these attenuation 
// components may be adjusted using the keystrokes below.  Note that even in the
// pure constant case, there are some differences in shading from left to right,
// due to the interaction of the light vector with the sphere normals.  This is
// most pronounced on the left edge, where the light is quite close to the sphere
// when compared to the sphere's radius. 
//
// The key commands are:
//
// q, a - increase, decrease constant attenuation in the range [0, inf]
// w, s - increase, decrease linear attenuation in the range [0, inf]
// e, d - increase, decrease quadratic attenuation in the range [0, inf]
// Space - reset attenuation values to 1.0
//
//===============================================================================

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

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

    mConstant = 1.0f;
    mLinear = 1.0f;
    mQuadratic = 1.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 constant attenuation
    if (IvGame::mGame->mEventHandler->IsKeyDown('q'))
    {
        mConstant += dt;
    }
    if (IvGame::mGame->mEventHandler->IsKeyDown('a'))
    {
        mConstant -= dt;
        mConstant = (mConstant < 0.0f) ? 0.0f : mConstant;
    }

    // adjust linear attenuation
    if (IvGame::mGame->mEventHandler->IsKeyDown('w'))
    {
        mLinear += dt;
    }
    if (IvGame::mGame->mEventHandler->IsKeyDown('s'))
    {
        mLinear -= dt;
        mLinear = (mLinear < 0.0f) ? 0.0f : mLinear;
    }
    
    // adjust quadratic attenuation
    if (IvGame::mGame->mEventHandler->IsKeyDown('e'))
    {
        mQuadratic += dt;
    }
    if (IvGame::mGame->mEventHandler->IsKeyDown('d'))
    {
        mQuadratic -= dt;
        mQuadratic = (mQuadratic < 0.0f) ? 0.0f : mQuadratic;
    }
    
    // reset attenuation
    if (IvGame::mGame->mEventHandler->IsKeyDown(' '))
    {
        mConstant = 1.0f;
        mLinear = 1.0f;
        mQuadratic = 1.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);

    const int count = 5;
    const float spacing = mRadius * 1.8f;

    // remove all ambient lighting to make attenuation more obvious
    glEnable(GL_LIGHT0);
    IvVector4 ambient(0.0f, 0.0f, 0.0f, 1.0f);
    glLightfv(GL_LIGHT0, GL_AMBIENT, (float*) &ambient);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, (float*) &ambient); 

    int j;
    for (j = 0; j < 3; j++)
    {
        float zPos;

        // set the attenuation constants for the given row
        if (j == 0)
        {
            // pure constant
            glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, mConstant);
            glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.0f);
            glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.0f);
            zPos = 2.0f * spacing;
        }
        else if (j == 1)
        {
            // pure linear
            glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.0f);
            glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, mLinear);
            glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, 0.0f);
            zPos = 0.0f;
        }
        else
        {
            // pure quadratic
            glLightf(GL_LIGHT0, GL_CONSTANT_ATTENUATION, 0.0f);
            glLightf(GL_LIGHT0, GL_LINEAR_ATTENUATION, 0.0f);
            glLightf(GL_LIGHT0, GL_QUADRATIC_ATTENUATION, mQuadratic);
            zPos = -2.0f * spacing;
        }

        // build 4x4 matrix
        IvMatrix44 transform;

        float fDist = 1.0f;
        int i;
        for (i = count; i >= -count; i--)
        {
            float yPos = i * spacing;

            // set light to be directly in front of sphere, at the desired distance
            IvVector4 pos(-(fDist + 1.0f) * mRadius, yPos, zPos, 1.0f);
            glLightfv(GL_LIGHT0, GL_POSITION, (float*) &pos);

            /// set the position of the sphere
            transform(0,3) = 0.0f;
            transform(1,3) = yPos;
            transform(2,3) = zPos;

            // store current matrix
            glPushMatrix();

            // multiply by transform
            glMultMatrixf(transform);

            // draw geometry
            DrawSphereVertexArrays();

            // restore original matrix          
            glPopMatrix();

            // increase the distance from the light to the next sphere
            fDist += 1.0f;
        }
    }

    glDisable( GL_LIGHTING );

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




