/*
	Wheels Of Steel
	Copyright (c) 2006, Darragh Coy.
	All rights reserved.
	Original work(s), source code & author(s) details available at: http://www.darraghcoy.com

	Redistribution and use in source and binary forms, with or without modification, are permitted 
	provided that the following conditions are met:

		* 	Redistributions of source code must retain the above copyright notice, this list of 
			conditions and the following disclaimer.
		* 	Redistributions in binary form must reproduce the above copyright notice, this list of 
			conditions and the following disclaimer in the documentation.
		* 	Neither the name of the author nor the names of contributors may be used to endorse or 
			promote products derived from this software without specific prior written permission.
		* 	The use of any third party libraries, modules or data files provided with the source 
			form of the program must follow and respect the individual licensing terms of these 
			components. You do not have permission to modify or circumvent the licensing terms for 
			provided third party components unless the third party licenses specifically allow this. 
			If you do not agree to the terms of provided third party components then you do not have 
			permission to use them in your derived work(s).
		* 	The terms of this license do not apply to separate (but related) distributions if supplied 
			by the author(s), only to the contents of this source distribution, derived works, and the 
			binaries produced by either modified or unmodified versions of the source code. The terms 
			of this license do not also apply to any third party libraries or components provided with 
			the source distribution. Such third party elements are licensed under their own separate 
			agreements.

	THIS SOFTWARE IS PROVIDED BY DARRAGH COY ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
	BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
	ARE DISCLAIMED. IN NO EVENT SHALL DARRAGH COY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
	OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
	OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
	IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

//##############################################################################
//##############################################################################
//##############################################################################
//##############################################################################
//
//
// Class Name:   AIPlayer
// Purpose:      AI controller for one car object. Decides on next action using
//               some simple heuristics.
//
//##############################################################################
//##############################################################################
//##############################################################################
//##############################################################################

#include "AIPlayer.h"

//------------------------------------------------------------------------------
// Constants
//------------------------------------------------------------------------------

// This is the maximum amount the AI can randomnly have its target x value varied from the calculated value.
// In essence this determines how much from the 'ideal' racing line the AI can vary.

const float AIPlayer::MAX_TARGET_OFFSET_X = 1250.0f;

// This variable determines the chance (per second) that the AI will change its target x offset
// and consequenlty its racing line on the track

const float AIPlayer::TARGET_OFFSET_X_CHANGE_CHANCE = 0.25f;

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Name:     AIPlayer::AIPlayer
Purpose:  Constructor for the AI player. Saves car to control and track its on.
Params:   Car to control and track the AI car is on.
Returns:  none

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

AIPlayer::AIPlayer( Obj_Car * car, Track * track )
{
    // Save the car and track

    this->car   = car;
    this->track = track;

    // Set the starting offsetX value to 0: i.e the ai should follow the
    // ideal racing line initially

    this->offsetX = 0;
}


/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Name:     AIPlayer::makeMove
Purpose:  Decides on the AIs next actions for the car and carries them out.
Params:   Time the last frame took in seconds
Returns:  none

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

void AIPlayer::makeMove( float frameTime )
{
    //--------------------------------------------------------------------------
    // Take track center into consideration when deciding on target x.
    // This is the AIs 'ideal' racing line.
    //--------------------------------------------------------------------------

    considerTrack();

    //--------------------------------------------------------------------------
    // Now add the offset x value to the calculated target x to make the ai
    // follow its slightly less than ideal racing path
    //--------------------------------------------------------------------------

    targetX += offsetX;

    //--------------------------------------------------------------------------
    // Get a random float and see if the test passes for to change the offsetX
    // value and adopt a different racing line.
    //--------------------------------------------------------------------------

    // Define the random float macro

    #define RANDOM_FLOAT() ( float(rand() & 0xffff) / float(0xffff) )

    // Get a random float from 0 to 1

    float randomFloat = RANDOM_FLOAT();

    // If the random float is under the chance for change per second (adjusted to frame time)
    // then change the random offsetX value to make the AI change its racing line slightly

    if ( randomFloat < TARGET_OFFSET_X_CHANGE_CHANCE * frameTime )
    {
        // Test passed: calculate a new targetX offset value

        offsetX = MAX_TARGET_OFFSET_X * RANDOM_FLOAT();

        // Randomly make it negative

        if ( rand() & 1 ) offsetX *= -1;
    }

    // undefine the random float macro

    #undef RANDOM_FLOAT

    //--------------------------------------------------------------------------
    // Consider every object starting in the objects current section when
    // deciding on the target x. only very close objects are really going to
    // have any effect on it.
    //--------------------------------------------------------------------------

    // Number of sections to look ahead

    #define SECTION_LOOKAHEAD 2

    // Go through the defined number of sections, taking all objects into consideration.

    for ( int s = car->getSection() ; s <= car->getSection() + SECTION_LOOKAHEAD; s++ )
    {
        // Make sure the section isn't off the track: if so then abort.

        if ( unsigned(s) >= track->getSections_count() ) break;

        // Get a pointer to this section

        TrackSection * section = track->getSections_array()[s];

        // Get a null terminated list of objects from this section

        GameObject ** objectList = section->get_objects();

        // Go through every object in the list and take it into consideration

        while ( * objectList )
        {
            // take this object into consideration

            considerObjectAhead(*objectList);

            // move onto next object

            objectList++;
        }
    }

    //--------------------------------------------------------------------------
    // Approximate how much the car will ultimately move given its current
    // velocity and whether it is in the dirt or on the track.
    //--------------------------------------------------------------------------

    // maximum amount the car will move on the x axis

    float maxMoveDist = 0;

    // see if the car is in the dirt or on the track

    if (
         car->getPos_x() < track->getLX( car->getPos_z() )
         ||
         car->getPos_x() > track->getRX( car->getPos_z() )
       )
    {
        //----------------------------------------------------------------------
        // Car is in dirt. approximate the maximum x distance it will move at this
        // velocity before it is slowed down to zero.
        //----------------------------------------------------------------------

        float vel = car->getVel_x();

        // take 16 iterations to approximate

        for ( int i = 0 ; i < 16 ; i++ )
        {
            maxMoveDist += vel;                       // add current velocity onto max move distance
            vel         *= Game::FRICTION_GROUND;     // slow down velocity by ground friction
            vel         *= Game::FRICTION_AIR;        // slow down velocity by air friction
        }

    }
    else
    {
        //----------------------------------------------------------------------
        // Car is on track. approximate the maximum x distance it will move at
        // this velocity before it is slowed down to zero.
        //----------------------------------------------------------------------

        float vel = car->getVel_x();

        // take 16 iterations to approximate

        for ( int i = 0 ; i < 16 ; i++ )
        {
            maxMoveDist += vel;                       // add current velocity onto max move distance
            vel         *= Game::FRICTION_TRACK;      // slow down velocity by track friction
            vel         *= Game::FRICTION_AIR;        // slow down velocity by air friction
        }

    }

    //--------------------------------------------------------------------------
    // Get amount the car has to move (on x axis) to reach the target x value
    //--------------------------------------------------------------------------

    //float distToTarget = targetX - car->getPos_x();

    float distToTarget = targetX - car->getPos_x() - car->getVel_x() / 7;

    //--------------------------------------------------------------------------
    // Decide whether to turn left or right
    //--------------------------------------------------------------------------

    //
    // Hack attack:
    //
    // Although the distance to the target is a real qunatifiable figure, and the
    // maxMoveDist variable attempts to approximate how far across the road the car
    // is going to veer across the road, they never really produced the sort of results
    // i wanted. I tried a few methods but none seemed to work that well. Then I went
    // into a hacking mode and came up with these mysterious magic numbers which
    // affect the AI behaviour. These were not choosen at random however, but by doing a case
    // by case analysis of the the AI turning certain corners, and tweaking the variables to
    // improve the results. They work pretty well anyhow, and the AI seems to be well
    // able to handle corners (up to a point), heck, it certainly does a better job
    // than me at it anyhow.. Just don't attach too much meaning to the numbers though,
    // they are simply tweaks to the inequalities and nothing more. You won't be seeing
    // this stuff anytime soon in an academic paper.. Its a hack, and theres simply no disguising it!
    //

    #define MAGIC_1 0.02
    #define MAGIC_2 3.0

    if ( distToTarget > 0 )
    {
        // Need to go right to get to target.
        // If we don't have enough x velocity to get there then turn.

        if ( maxMoveDist * MAGIC_1 < distToTarget * MAGIC_2 ) car->turnRight();

    }
    else
    {
        // Need to go left to get to target.
        // If we don't have enough x velocity to get there then turn.

        if ( maxMoveDist * MAGIC_1 > distToTarget * MAGIC_2 ) car->turnLeft();
    }


    //--------------------------------------------------------------------------
    // Accelerate the car forward
    //--------------------------------------------------------------------------

    car->accelerate();
}

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Name:     AIPlayer::considerTrack
Purpose:  Takes the lie of the track ahead into consideration when setting the
          AIs target x position. Bends further ahead are also considered somewhat.
          This function should be called first as it wipes clean the target x value.
Params:   none
Returns:  none

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

void AIPlayer::considerTrack()
{
    //--------------------------------------------------------------------------
    // For a fixed number of sections ahead get the center point and interpolate
    // it with the current target x. This will set the overall target x, taking
    // the track into consideration.
    //--------------------------------------------------------------------------

    for ( int section = 1 ; section <= 6 ; section++ )
    {
        //----------------------------------------------------------------------
        // Get the center point of the section
        //----------------------------------------------------------------------

        float center;

        center  = track->getLX( car->getPos_z() + (section - 1) * Track::SECTION_SIZE_Z );
        center += track->getRX( car->getPos_z() + (section - 1) * Track::SECTION_SIZE_Z );
        center *= 0.5f;

        //----------------------------------------------------------------------
        // Interpolate it with the current target x. sections further ahead have
        // diminished importance. first section forms base target x.
        //----------------------------------------------------------------------

        targetX = ( 1.0f - 1.0f / float(section) ) * targetX + ( 1.0f / float(section) ) * center;
    }

    //--------------------------------------------------------------------------
    // Make sure the target x is not past the left or right track x value plus
    // the width of the caar at the cars current z value. if so then clamp.
    //--------------------------------------------------------------------------

    // Make sure not past right of current position on track

    if ( targetX + car->getSize_x() * 0.5 > track->getRX(car->getPos_z()) )
    {
        // Past the right: clamp target x

        targetX = track->getRX(car->getPos_z()) - car->getSize_x() * 0.5f;
    }

    // Make sure not past left of current position on track

    if ( targetX - car->getSize_x() * 0.5 < track->getLX(car->getPos_z()) )
    {
        // Past the right: clamp target x

        targetX = track->getLX(car->getPos_z()) + car->getSize_x() * 0.5f;
    }

}

/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Name:     AIPlayer::considerObjectAhead
Purpose:  considers a single object ahead when deciding where the target x value
          should lie.
Params:   none
Returns:  none

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

void AIPlayer::considerObjectAhead(GameObject * object)
{
    //--------------------------------------------------------------------------
    // Make sure object given is not same as the car controlled by the AI.
    // If so then do not consider this object...
    //--------------------------------------------------------------------------

    if ( object == this->car ) return;

    //--------------------------------------------------------------------------
    // These value controls how significant far away objects are on the x and z
    // axes. higher values will affect the target x value more.
    //--------------------------------------------------------------------------

    #define CONSIDER_DISTANCE 1000000.0f

    //--------------------------------------------------------------------------
    // These value defines the maximum amount of importance or effect this object
    // ahead has on the goal x position
    //--------------------------------------------------------------------------


    #define MAX_IMPORTANCE 1.0f

    //--------------------------------------------------------------------------
    // Get the center of the object in question. this will form the basis for the
    // new considered target position.
    //--------------------------------------------------------------------------

    float considerX = object->getPos_x();

    //--------------------------------------------------------------------------
    // Add or the half the size of the object and add or subtract the size of
    // the AIs car depending on which side of the road the object is on. Then
    // add or subtract the padding value. This gives an x position where the car
    // can be (either to the right or left of this object) without hitting it
    //--------------------------------------------------------------------------

    // Amount of padding there should be between the objects

    #define OBJECT_PADDING 256.0f

    // Get the center of the track at the objects z:

    float trackCenterX;

    trackCenterX  = track->getLX( object->getPos_z() );
    trackCenterX += track->getRX( object->getPos_z() );
    trackCenterX *= 0.5f;

    // Now see if the object is at the left or right:

    if ( object->getPos_x() < trackCenterX )
    {
        //----------------------------------------------------------------------
        // object being considered to the left side of the road
        //----------------------------------------------------------------------

        considerX += object->getSize_x() * 0.5f;  // add half object size to considered track position
        considerX += car->getSize_x()    * 0.5f;  // add half car size to considered track position
        considerX += OBJECT_PADDING;             // add the padding amount
    }
    else
    {
        //----------------------------------------------------------------------
        // object being considered to the right side of the road
        //----------------------------------------------------------------------

        considerX -= object->getSize_x() * 0.5f;  // add half object size to considered track position
        considerX -= car->getSize_x()    * 0.5f;  // add half car size to considered track position
        considerX -= OBJECT_PADDING;              // add the padding amount
    }

    //--------------------------------------------------------------------------
    // Now get the objects position relative to the AIs car
    //--------------------------------------------------------------------------

    float relativeX = object->getPos_x() - car->getPos_x();
    float relativeZ = object->getPos_z() - car->getPos_z();

    //--------------------------------------------------------------------------
    // If the object is behind the car then do not consider it as an obstacle
    //--------------------------------------------------------------------------

    //if ( relativeZ < 0 ) return;

    //--------------------------------------------------------------------------
    // Get the distance to the object from to the axis car. use squared distance
    // not only to save computational time, but to make closer objects far more
    // urgent for the ai car to avoid.
    //--------------------------------------------------------------------------

    float distance;

    distance  = relativeX * relativeX;
    distance += relativeZ * relativeZ;

    //--------------------------------------------------------------------------
    // using this decide on the importance of this object, or how much the position
    // calculated to the left or right of it will affect the ultimate target x value
    // for the ai.
    //--------------------------------------------------------------------------

    float importance;

    importance = MAX_IMPORTANCE / ( ( distance / CONSIDER_DISTANCE ) + 1 );

    //--------------------------------------------------------------------------
    // now using the importance amount, linearly interpolate with the considered
    // target x (an x value to the right or left of the object) with the ais current
    // target x.
    //--------------------------------------------------------------------------

    this->targetX =(1.0f-importance) * targetX + considerX * importance;
}

