/*
**	Command & Conquer Renegade(tm)
**	Copyright 2025 Electronic Arts Inc.
**
**	This program is free software: you can redistribute it and/or modify
**	it under the terms of the GNU General Public License as published by
**	the Free Software Foundation, either version 3 of the License, or
**	(at your option) any later version.
**
**	This program is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**	GNU General Public License for more details.
**
**	You should have received a copy of the GNU General Public License
**	along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

/***********************************************************************************************
 ***              C O N F I D E N T I A L  ---  W E S T W O O D  S T U D I O S               ***
 ***********************************************************************************************
 *                                                                                             *
 *                 Project Name : LevelEdit                                                    *
 *                                                                                             *
 *                     $Archive:: /Commando/Code/Combat/pilot.cpp         $*
 *                                                                                             *
 *                       Author:: Patrick Smith                                                *
 *                                                                                             *
 *                     $Modtime:: 11/26/01 5:07p                                              $*
 *                                                                                             *
 *                    $Revision:: 18                                                          $*
 *                                                                                             *
 *---------------------------------------------------------------------------------------------*
 * Functions:                                                                                  *
 * - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#include "pilot.h"

#include "smartgameobj.h"
#include "matrix3d.h"
#include "movephys.h"
#include "vehiclephys.h"
#include "path.h"
#include "pscene.h"
#include "vehicle.h"
#include "soldier.h"
#include "heightdb.h"
#include "debug.h"


////////////////////////////////////////////////////////////////
//	Save/Load constants
////////////////////////////////////////////////////////////////
enum
{
	CHUNKID_VARIABLES				= 0x11040504,
};

enum
{
	VARID_FINAL_DEST				= 1,
	VARID_NEXT_POINT,
	VARID_CURRENT_TM,
	VARID_OBJ_SPACE_DEST,
	VARID_OBJ_SPACE_WPOINT,
	VARID_GAMEOBJ_PTR,
	VARID_MAX_SPEED,
	VARID_SPEED_FACTOR,
	VARID_AGGRESSIVENESS,
	VARID_ARRIVED_DIST,
	VARID_HOVERDIST,
	VARID_FACE_TARGET,
	VARID_TARGET_LOCATION,
	VARID_MODE,
	VARID_FORWARD_SPEED,
	VARID_STRAFE_SPEED,
	VARID_LIFT_SPEED,
	VARID_TURN_SHARPNESS,
	VARID_CIRCLE_ANGLE,
	VARID_CIRCLE_DIST,
	VARID_MIN_CIRCLE_ANGLE,
	VARID_MAX_CIRCLE_ANGLE,
	VARID_ISEXACT_Z_IMPORT,
	VARID_PATH_PTR
};


////////////////////////////////////////////////////////////////
//	Constants
////////////////////////////////////////////////////////////////
const float MAX_HEIGHT_DELTA		= 2.5F;
const float MAX_STRAFE_DELTA		= 10.0F;
const float MAX_FORWARD_DELTA		= 10.0F;

////////////////////////////////////////////////////////////////
//	Clamp_Angle
////////////////////////////////////////////////////////////////
inline float
Clamp_Angle (float angle, float min_angle, float max_angle)
{
	//
	//	Make sure all the parameters are in the same range
	//
	angle			= WWMath::Wrap (angle,		0, DEG_TO_RADF (360));
	min_angle	= WWMath::Wrap (min_angle,	0, DEG_TO_RADF (360));
	max_angle	= WWMath::Wrap (max_angle,	0, DEG_TO_RADF (360));

	float result = angle;

	if (min_angle <= max_angle) {
		
		//
		//	Handle the 'typical' case where there is no 360-mark wrap-around
		//
		if (angle < min_angle) {
			result = min_angle;
		} else if (angle > max_angle) {		
			result = max_angle;
		}

	} else {
		
		//
		//	Handle the 360-mark wrap-around case
		//
		if (angle < min_angle && angle > max_angle) {
			float min_delta = min_angle - angle;
			float max_delta = angle - max_angle;

			//
			//	Which edge is the angle closer to?
			//
			if (min_delta < max_delta) {
				result = min_angle;
			} else {
				result = max_angle;
			}
		}
	}

	return result;
}


////////////////////////////////////////////////////////////////
//
//	PilotClass
//
////////////////////////////////////////////////////////////////
PilotClass::PilotClass (void)
	:	m_FinalDest (0, 0, 0),
		m_NextPoint (0, 0, 0),
		m_GameObj (NULL),
		m_MaxSpeed (20.0F),
		m_SpeedFactor (1.0F),
		m_Aggressiveness (0.25F),
		m_ArrivedDist (0),
		m_HoverDist (15.0F),
		m_FaceTarget (false),
		m_TargetLocation (0, 0, 0),
		m_Mode (MODE_HOVER),
		m_ForwardSpeed (0),
		m_StrafeSpeed (0),
		m_LiftSpeed (0),
		m_TurnSharpness (0),
		m_CurrentPath (NULL),
		m_CircleAngle (0),
		m_CircleDist (0),
		m_MinCircleAngle (-DEG_TO_RADF(180)),
		m_MaxCircleAngle (DEG_TO_RADF(180)),
		m_IsExactZImportant (false)
{
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Get_Object_Space_Velocity
//
//	Returns the object-space velocity vector
//
////////////////////////////////////////////////////////////////
void
PilotClass::Get_Object_Space_Velocity (Vector3 &vel_vector) const
{
	//
	//	Get a pointer to the physics object for this vehicle
	//
	MoveablePhysClass *phys_obj = m_GameObj->Peek_Physical_Object()->As_MoveablePhysClass ();
	WWASSERT (phys_obj != NULL);

	//
	//	Get the world-space velocity vector for this vehicle and transform
	// it into object space.
	//
	Vector3 vel_vector_world;
	phys_obj->Get_Velocity (&vel_vector_world);
	Matrix3D::Inverse_Rotate_Vector (m_GameObj->Get_Transform (), vel_vector_world, &vel_vector);			
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Set_Target
//
////////////////////////////////////////////////////////////////
void
PilotClass::Set_Target (const Vector3 *target)
{
	if (target != NULL) {
		m_TargetLocation = *target;
		
		if (m_Mode != MODE_CIRCLE_POINT) {
			m_FaceTarget = true;
		}

	} else {
		m_FaceTarget = false;
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Initialize
//
////////////////////////////////////////////////////////////////
void
PilotClass::Initialize (SmartGameObj *game_obj)
{
	m_GameObj		= game_obj;
	m_CurrentPath	= NULL;

	//
	//	If the game object is flying a vehicle, then get the vehicle instead
	//
	SoldierGameObj	*soldier_game_obj = game_obj->As_SoldierGameObj ();
	if (soldier_game_obj != NULL) {
		VehicleGameObj	*vehicle_game_obj = soldier_game_obj->Get_Profile_Vehicle ();
		if (vehicle_game_obj != NULL) {
			m_GameObj = vehicle_game_obj;
		}
	}

	//
	//	By default assume our final destination is our current location
	//
	game_obj->Get_Position (&m_FinalDest);
	game_obj->Get_Position (&m_NextPoint);	

	//
	//	Determine if this game object is a vehicle or not (it better be...)
	//
	VehicleGameObj *vehicle = m_GameObj->As_VehicleGameObj ();
	if (vehicle != NULL) {
		
		//
		//	Start this vehicle's engine's running
		//
		if (vehicle->Is_Engine_Enabled () == false) {
			vehicle->Enable_Engine (true);
		}
	}
	
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Calculate_Desired_Relative_Facing
//
////////////////////////////////////////////////////////////////
float
PilotClass::Calculate_Desired_Relative_Facing (void)
{
	float base_angle	= 0;
	
	//
	//	Should we be facing the destination or the current target?
	//
	if (m_FaceTarget) {

		//
		//	Convert the target location from world to object space
		//
		Vector3 target_pos;
		Matrix3D::Inverse_Transform_Vector (m_CurrentTM, m_TargetLocation, &target_pos);
		
		//
		//	Calculate the absolute turn angle
		//
		base_angle	= WWMath::Atan2 (target_pos.Y, target_pos.X);
		base_angle	= WWMath::Wrap (base_angle, 0, WWMATH_PI * 2);

	} else if (m_Mode != MODE_HOVER) {

		//
		//	Calculate the absolute turn angle
		//
		base_angle	= WWMath::Atan2 (m_ObjSpaceWaypoint.Y, m_ObjSpaceWaypoint.X);
		base_angle	= WWMath::Wrap (base_angle, 0, WWMATH_PI * 2);
	
	} 
	
	//
	//	Convert the angle from [0, 360] to [-180, 180]
	//
	return WWMath::Wrap (base_angle, -WWMATH_PI, WWMATH_PI);
}


////////////////////////////////////////////////////////////////
//
//	Think
//
////////////////////////////////////////////////////////////////
bool
PilotClass::Think (void)
{
	//
	//	Update the current
	//
	if (m_CurrentPath != NULL) {
		m_FinalDest = m_CurrentPath->Get_Dest_Pos ();
		m_NextPoint	= m_CurrentPath->Get_Next_Pos ();
	}

	//
	//	Let each mode think independently
	//
	switch (m_Mode)
	{
		case MODE_HOVER:
			Process_Hover ();
			break;

		case MODE_FLY_TO_POINT:
			Process_Fly_To_Point ();
			break;

		case MODE_CIRCLE_POINT:
			Process_Circle_Point ();
			break;
	}

	//
	//	Pass the current controls onto the game object
	//
	Apply_Controls ();	

	//
	//	Return true when we've arrived at the destination
	//
	return Has_Arrived ();
}


////////////////////////////////////////////////////////////////
//
//	Calculate_Strafe_Speed
//
////////////////////////////////////////////////////////////////
void
PilotClass::Calculate_Strafe_Speed (void)
{
	m_StrafeSpeed = 0;

	bool should_strafe = m_FaceTarget;
	if (should_strafe) {
		m_StrafeSpeed = m_ObjSpaceWaypoint.Y / MAX_STRAFE_DELTA;
		m_StrafeSpeed = WWMath::Clamp (m_StrafeSpeed, -1.0F, 1.0F);
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Calculate_Forward_Speed
//
////////////////////////////////////////////////////////////////
void
PilotClass::Calculate_Forward_Speed (float distance)
{
	m_ForwardSpeed = 0;

	//
	//	Pick a speed based on our distance from the destination
	//
	m_ForwardSpeed	= distance / MAX_FORWARD_DELTA;		
	m_ForwardSpeed	= WWMath::Clamp (m_ForwardSpeed, -1.0F, 1.0F);
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Calculate_Lift_Speed
//
////////////////////////////////////////////////////////////////
void
PilotClass::Calculate_Lift_Speed (void)
{
	//bool is_exact_height_important = (m_NextPoint != m_FinalDest);

	//
	//	Determine what z position we should aim for
	//
	float preferred_height	= m_NextPoint.Z;
	float flight_floor		= -5000.0F;
	if (m_IsExactZImportant == false) {

		flight_floor			= Determine_Preferred_Height ();
		preferred_height		= flight_floor;
		if (preferred_height < m_NextPoint.Z) {
			preferred_height = m_NextPoint.Z;
		}

	} else {
		preferred_height = m_NextPoint.Z;
	}

	//
	//	Get the vehicle's current position
	//
	Vector3 curr_pos;
	m_GameObj->Get_Position (&curr_pos);

	//
	//	Calculate the lift speed based on the change in altitude
	// Once the height delta is within MAX_HIEGHT_DELTA, we set the desired velocity to zero
	// and try to let the hovering code maintain the desired altitude.
	//
	float delta_z		= preferred_height - curr_pos.Z;
	float abs_delta_z = WWMath::Fabs(delta_z);

	if (m_Mode != MODE_HOVER || abs_delta_z > MAX_HEIGHT_DELTA) {
		m_LiftSpeed = delta_z / MAX_HEIGHT_DELTA;
		m_LiftSpeed = WWMath::Clamp (m_LiftSpeed, -1.0F, 1.0F);
	} else {
		m_LiftSpeed = 0.0f;
	}

	//
	//	Keep the vehicle from 'sinking' sharply
	//
	if (	m_IsExactZImportant == false &&
			m_Mode == MODE_FLY_TO_POINT &&
			delta_z < 0)
	{
		m_LiftSpeed = m_LiftSpeed * 0.25f;
	}

	//
	//	Slow down our forward speed to give us time to change altitude (if necessary)
	//
	if (curr_pos.Z < flight_floor) {
		//float new_speed = WWMath::Clamp (1.0F - m_LiftSpeed, 0.5F, 1.0F);
		//m_ForwardSpeed = min (m_ForwardSpeed, new_speed);
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Calculate_Turn_Sharpness
//
////////////////////////////////////////////////////////////////
void
PilotClass::Calculate_Turn_Sharpness (void)
{
	float facing_angle = Calculate_Desired_Relative_Facing ();

	//
	//	Calculate how sharp we should turn to make this facing
	//
	m_TurnSharpness	= facing_angle / DEG_TO_RADF (45);
	m_TurnSharpness	= WWMath::Clamp (m_TurnSharpness, -1.0F, 1.0F);

	//
	//	If the vehicle needs to face its target, then make it turn faster then normal
	//
	if (m_FaceTarget || m_Mode == MODE_CIRCLE_POINT) {
		m_TurnSharpness	*= 2.0F;
		m_TurnSharpness	= min (m_TurnSharpness, 1.0F);
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Process_Hover
//
////////////////////////////////////////////////////////////////
void
PilotClass::Process_Hover (void)
{
	// 
	// Update the "simplified" transform
	//
	Update_Transform ();

	// 
	// When hovering, we want to maintain a zero horizontal velocity
	// while moving towards our desired altitude
	//
	m_ForwardSpeed	= 0.0f;
	m_StrafeSpeed	= 0.0f;

	// 
	// Update the "simplified" transform
	//
	Update_Transform ();

	// 
	// When hovering, we want to maintain a zero horizontal velocity
	// while moving towards our desired altitude
	//
	m_ForwardSpeed = 0.0f;
	m_StrafeSpeed = 0.0f;

	//
	//	When hovering its valid to face a target, so update
	// the vehicle's facing
	//
	if (m_FaceTarget) {
		Calculate_Turn_Sharpness ();
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Process_Circle_Point
//
////////////////////////////////////////////////////////////////
void
PilotClass::Process_Circle_Point (void)
{
	//
	//	Reset all control inputs
	//
	m_ForwardSpeed	= 0;
	m_StrafeSpeed	= 0;
	m_LiftSpeed		= 0;

	//
	//	When hovering its valid to face a target, so update
	// the vehicle's facing
	//
	Update_Transform ();

	//
	//	Make sure we are facing the destination
	//
	Calculate_Turn_Sharpness ();

	//
	//	If we are facing the destination, then we can
	// start our 'circling' movement
	//
	if (::fabs (m_TurnSharpness) < 0.1F) {

		//
		//	Make sure the vehicle is the appropriate distance from the circle point
		//
		Calculate_Forward_Speed (m_ObjSpaceDest.X - m_CircleDist);
		Calculate_Lift_Speed ();

		//
		//	Calculate what our current circling angle is
		//
		Vector3 curr_pos;
		m_GameObj->Get_Position (&curr_pos);
		Vector3 delta = curr_pos - m_FinalDest;
		float curr_angle	= WWMath::Atan2 (delta.Y, delta.X);
		float angle_delta	= WWMath::Wrap (curr_angle - m_CircleAngle, -WWMATH_PI, WWMATH_PI);

		//
		//	Calculate how fast we should strafe in order to circle the target
		//
		m_StrafeSpeed = angle_delta / DEG_TO_RADF (5);
		m_StrafeSpeed = WWMath::Clamp (m_StrafeSpeed, -1.0F, 1.0F);
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Process_Fly_To_Point
//
////////////////////////////////////////////////////////////////
void
PilotClass::Process_Fly_To_Point (void)
{
	//
	//	Get the vehicle's current transform and check to see if
	// the vehicle has arrived at its destination yet
	//
	Update_Transform ();
	Check_Completion ();

	//
	//	Calculate how sharp the vehicle needs to turn
	//
	Calculate_Turn_Sharpness ();

	//
	//	Calculate how fast the vehicle needs to move in each direction
	//
	Calculate_Strafe_Speed ();
	
	//
	//	If we are on a path then we need to calculate the forward speed
	// as if we are going full speed the whole way there
	//
	bool is_on_path = (m_NextPoint != m_FinalDest);
	if (is_on_path) {

		//
		//	Clamp the speed to -1, 0, or 1.
		//
		m_ForwardSpeed = m_ObjSpaceWaypoint.X;
		if (m_ForwardSpeed > 0) {
			m_ForwardSpeed = 1.0F;
		} else if (m_ForwardSpeed < 0) {
			m_ForwardSpeed = -1.0F;
		} else {
			m_ForwardSpeed = 0.0F;
		}

		// 
		// Reduce forward/back speed if we are mostly strafing.
		//
		if (WWMath::Fabs (m_StrafeSpeed) > 0.25f) {
			m_ForwardSpeed = m_ForwardSpeed * (1.25F - WWMath::Fabs (m_StrafeSpeed));
		}

		//
		//	Let the pilot slow down to handle sharp corners
		//
		if (WWMath::Fabs (m_TurnSharpness) > 0.65F) {
			m_ForwardSpeed = m_ForwardSpeed * (1.25F - WWMath::Fabs (m_TurnSharpness));
		}		

	} else {
		Calculate_Forward_Speed (m_ObjSpaceDest.X);
	}

	Calculate_Lift_Speed ();
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Check_Completion
//
////////////////////////////////////////////////////////////////
void
PilotClass::Check_Completion (void)
{
	//
	//	Get the vehicle's bounding box
	//
	MoveablePhysClass *phys_obj		= m_GameObj->Peek_Physical_Object ()->As_MoveablePhysClass ();
	RenderObjClass *render_obj			= phys_obj->Peek_Model ();
	AABoxClass bounding_box;
	render_obj->Get_Obj_Space_Bounding_Box (bounding_box);

	//
	//	Calculate the distance remaining on the path
	//
	float dist_to_goal = 0;
	if (m_CurrentPath != NULL) {
		float dist_on_path = m_CurrentPath->Get_Remaining_Path_Length ();
		dist_to_goal = (m_ObjSpaceWaypoint.Length () + dist_on_path);		
	} else {
		dist_to_goal = m_ObjSpaceDest.Length ();
	}


	//
	//	Take into account the 'arrived distance' setting.  We basically fly towards the
	// closest point on a sphere around the destination point.  m_ObjSpaceDest will
	// be initialized to that point transformed into the vehicle's coordinate system.
	//
	if (dist_to_goal > m_ArrivedDist * m_ArrivedDist) {
		Vector3 arrived_len = m_ObjSpaceDest;
		arrived_len.Normalize ();
		arrived_len *= m_ArrivedDist;
		m_ObjSpaceDest -= arrived_len;
		m_ObjSpaceDest += bounding_box.Center;
	}

	// 
	// Once we come within m_HoverDist of the desired position we switch into "hover" mode.
	//
	if (dist_to_goal < m_HoverDist && m_NextPoint == m_FinalDest) {
		Vector2 xy_delta(m_ObjSpaceWaypoint.X, m_ObjSpaceWaypoint.Y);
		if (xy_delta.Length2() < m_HoverDist * m_HoverDist) {
			Set_Mode(MODE_HOVER);

			//
			// If we don't have a target, we need to create a fake target so that
			// the orientation of the vehicle doesn't drift.
			//
			if (m_FaceTarget == false) {
				Vector3 fake_target;
				Matrix3D::Transform_Vector(m_CurrentTM,Vector3(1000.0f,0.0f,0.0f),&fake_target);
				Set_Target(&fake_target);
			}
		}
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Update_Transform
//
////////////////////////////////////////////////////////////////
void
PilotClass::Update_Transform (void)
{
	Matrix3D obj_tm = m_GameObj->Get_Transform ();
	
	//
	//	Build a transform that only uses the facing and position
	// of the vehicle.
	//
	m_CurrentTM.Make_Identity ();
	m_CurrentTM.Rotate_Z (obj_tm.Get_Z_Rotation ());
	m_CurrentTM.Set_Translation (obj_tm.Get_Translation ());

	//
	//	Now convert the destination from world space to object space
	//
	Matrix3D::Inverse_Transform_Vector (m_CurrentTM, m_FinalDest, &m_ObjSpaceDest);
	Matrix3D::Inverse_Transform_Vector (m_CurrentTM, m_NextPoint, &m_ObjSpaceWaypoint);

	//
	//	If its not important for the vehicle to tightly follow the Z value
	// of its points, then simply zero out the Z component
	//
	if (m_IsExactZImportant == false) {
		m_ObjSpaceWaypoint.Z = 0;
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Determine_Preferred_Height
//
////////////////////////////////////////////////////////////////
float
PilotClass::Determine_Preferred_Height (void)
{
	float height = m_NextPoint.Z;
	if (m_Mode != MODE_FLY_TO_POINT) {
		return height;
	}

	//
	//	Get a pointer to the physics object for this vehicle
	//
	MoveablePhysClass *phys_obj = m_GameObj->Peek_Physical_Object()->As_MoveablePhysClass ();
	WWASSERT (phys_obj != NULL);

	//
	//	Get the world-space velocity vector for this vehicle and transform
	// it into object space.
	//
	Vector3 vel_vector_world;
	phys_obj->Get_Velocity (&vel_vector_world);

	//
	//	Try to determine where the aircraft will be in 1 and 2 seconds
	// from now.
	//	
	Vector3 curr_pos;
	m_GameObj->Get_Position (&curr_pos);

	RenderObjClass *render_obj			= phys_obj->Peek_Model ();
	const AABoxClass &bounding_box	= render_obj->Get_Bounding_Box ();

	Vector3 curr_pos1 = curr_pos;
	Vector3 curr_pos2 = curr_pos + Vector3 (bounding_box.Extent.X, bounding_box.Extent.Y, 0);
	Vector3 curr_pos3 = curr_pos + Vector3 (-bounding_box.Extent.X, -bounding_box.Extent.Y, 0);
	Vector3 curr_pos4 = curr_pos + Vector3 (bounding_box.Extent.X, -bounding_box.Extent.Y, 0);
	Vector3 curr_pos5 = curr_pos + Vector3 (-bounding_box.Extent.X, bounding_box.Extent.Y, 0);

	Vector3 future_pos1 = curr_pos1 + (vel_vector_world * 1.25F);
	Vector3 future_pos2 = curr_pos2 + (vel_vector_world * 1.25F);
	Vector3 future_pos3 = curr_pos3 + (vel_vector_world * 1.25F);
	Vector3 future_pos4 = curr_pos4 + (vel_vector_world * 1.25F);
	Vector3 future_pos5 = curr_pos5 + (vel_vector_world * 1.25F);

	Vector3 future_pos6 = curr_pos1 + (vel_vector_world * 2.25F);
	Vector3 future_pos7 = curr_pos2 + (vel_vector_world * 2.25F);
	Vector3 future_pos8 = curr_pos3 + (vel_vector_world * 2.25F);
	Vector3 future_pos9 = curr_pos4 + (vel_vector_world * 2.25F);
	Vector3 future_pos10 = curr_pos5 + (vel_vector_world * 2.25F);

	future_pos1.Z = curr_pos.Z;
	future_pos2.Z = curr_pos.Z;
	future_pos3.Z = curr_pos.Z;
	future_pos4.Z = curr_pos.Z;
	future_pos5.Z = curr_pos.Z;
	future_pos6.Z = curr_pos.Z;
	future_pos7.Z = curr_pos.Z;
	future_pos8.Z = curr_pos.Z;
	future_pos9.Z = curr_pos.Z;
	future_pos10.Z = curr_pos.Z;

	
	//
	//	Now, lookup the preferred height for these future positions
	//
	float height1 = HeightDBClass::Get_Height (future_pos1);
	float height2 = HeightDBClass::Get_Height (future_pos2);
	float height3 = HeightDBClass::Get_Height (future_pos3);
	float height4 = HeightDBClass::Get_Height (future_pos4);
	float height5 = HeightDBClass::Get_Height (future_pos5);
	float height6 = HeightDBClass::Get_Height (future_pos6);
	float height7 = HeightDBClass::Get_Height (future_pos7);
	float height8 = HeightDBClass::Get_Height (future_pos8);
	float height9 = HeightDBClass::Get_Height (future_pos9);
	float height10 = HeightDBClass::Get_Height (future_pos10);

	//
	//	Return the largest height to the caller
	//
	height	= max (height1, height2);
	height	= max (height, height3);
	height	= max (height, height4);
	height	= max (height, height5);
	height	= max (height, height6);
	height	= max (height, height7);
	height	= max (height, height8);
	height	= max (height, height9);
	height	= max (height, height10);
	return height;
}


////////////////////////////////////////////////////////////////
//
//	Apply_Controls
//
////////////////////////////////////////////////////////////////
void
PilotClass::Apply_Controls (void)
{
	//
	//	If the vehicle has a driver, then pass the controls onto the driver
	// instead...
	//
	SmartGameObj *game_obj	= m_GameObj;
	VehicleGameObj *vehicle = m_GameObj->As_VehicleGameObj ();
	if (vehicle != NULL) {
		if (vehicle->Get_Driver () != NULL) {
			//game_obj = vehicle->Get_Driver ();
		}
	}

	//
	//	Calculate our (current) normalized speeds
	//
	Vector3 vel_vector;
	Get_Object_Space_Velocity (vel_vector);
	float curr_forward_speed	= WWMath::Clamp ((vel_vector.X / m_MaxSpeed), -1.0F, 1.0F);
	float curr_strafe_speed		= WWMath::Clamp ((vel_vector.Y / m_MaxSpeed), -1.0F, 1.0F);
	float curr_lift_speed		= WWMath::Clamp ((vel_vector.Z / m_MaxSpeed), -1.0F, 1.0F);

	//
	//	Calculate how much we need to accelerate or decelerate in the three directions
	// in order to meet our target speeds
	//
	float forward_accel	= 0.0f;
	float strafe_accel	= 0.0f;
	float lift_accel		= 0.0f;

	if (m_Mode == MODE_HOVER) {
		forward_accel	= 5.5f * (m_ForwardSpeed - curr_forward_speed) + 0.25f * m_ObjSpaceDest.X;
		strafe_accel	= 3.0f * (m_StrafeSpeed - curr_strafe_speed) + 0.5f * m_ObjSpaceDest.Y;
		lift_accel		= 1.0f * (m_LiftSpeed - curr_lift_speed) + 0.5f * m_ObjSpaceDest.Z;

		forward_accel	= WWMath::Clamp(forward_accel,-1.0f,1.0f);
		strafe_accel	= WWMath::Clamp(strafe_accel,-1.0f,1.0f);
		lift_accel		= WWMath::Clamp(lift_accel,-1.0f,1.0f);

	} else {
		forward_accel	= WWMath::Clamp (5.0f * (m_ForwardSpeed - curr_forward_speed), -1.0F, 1.0F);
		strafe_accel	= WWMath::Clamp (1.0f * (m_StrafeSpeed - curr_strafe_speed), -1.0F, 1.0F);
		lift_accel		= WWMath::Clamp (3.0f * (m_LiftSpeed - curr_lift_speed), -1.0F, 1.0F);	
	}

	//
	//	Make the vehicle go forward/backward
	//
	game_obj->Set_Analog_Control (ControlClass::ANALOG_MOVE_FORWARD, forward_accel);

	//
	//	Make the vehicle strafe left/right
	//
	game_obj->Set_Analog_Control (ControlClass::ANALOG_MOVE_LEFT, strafe_accel);

	//
	//	Make the vehicle go up/down
	//
	game_obj->Set_Analog_Control (ControlClass::ANALOG_MOVE_UP, lift_accel);

	//
	//	Make the vehicle turn
	//
	game_obj->Set_Analog_Control (ControlClass::ANALOG_TURN_LEFT, m_TurnSharpness);
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Set_Mode
//
////////////////////////////////////////////////////////////////
void
PilotClass::Set_Mode (PilotClass::MODE mode)
{
	if (m_Mode != mode) {

		switch (mode)
		{
			case MODE_HOVER:
				break;

			case MODE_FLY_TO_POINT:
				break;

			case MODE_CIRCLE_POINT:
			{
				m_FaceTarget = false;
				
				//
				//	Initialize the circling distance
				//
				Vector3 curr_pos;
				m_GameObj->Get_Position (&curr_pos);
				m_CircleDist = (curr_pos - m_FinalDest).Length ();
			}
			break;
		}

		// 
		// When we change modes, we reset the target in case we had
		// a fake target from hover mode
		//
		Set_Target(NULL);
		m_Mode = mode;
	}

	return ;
}


////////////////////////////////////////////////////////////////
//
//	Set_Circle_Pos
//
////////////////////////////////////////////////////////////////
void
PilotClass::Set_Circle_Pos (const Vector3 &pos)
{
	Vector3 delta = pos - m_FinalDest;
	m_CircleAngle = WWMath::Atan2 (delta.Y, delta.X);
	m_CircleAngle = WWMath::Wrap (m_CircleAngle, 0, DEG_TO_RADF (360));
	m_CircleAngle = ::Clamp_Angle (m_CircleAngle, m_MinCircleAngle, m_MaxCircleAngle);
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Set_Circle_Angle
//
////////////////////////////////////////////////////////////////
void
PilotClass::Set_Circle_Angle (float angle)
{
	m_CircleAngle = WWMath::Wrap (angle, 0, DEG_TO_RADF (360));
	m_CircleAngle = ::Clamp_Angle (m_CircleAngle, m_MinCircleAngle, m_MaxCircleAngle);
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Set_Circle_Bounds
//
////////////////////////////////////////////////////////////////
void
PilotClass::Set_Circle_Bounds (float min_angle, float max_angle)
{
	m_MinCircleAngle	= min_angle;
	m_MaxCircleAngle	= max_angle;
	m_CircleAngle		= ::Clamp_Angle (m_CircleAngle, m_MinCircleAngle, m_MaxCircleAngle);
	return ;
}


////////////////////////////////////////////////////////////////
//
//	Has_Arrived
//
////////////////////////////////////////////////////////////////
bool
PilotClass::Has_Arrived (void) const
{
	bool has_arrived = false;

	if (m_Mode == MODE_HOVER) {

		/*bool 
		if (m_CurrentPath != NULL) {
			m_ObjSpaceDest.Length ()
		}*/
		
		//
		//	In hovering mode, we have 'arrived' if we have mostly finished moving
		//
		Vector3 vel_vector;
		Get_Object_Space_Velocity (vel_vector);
		has_arrived = (WWMath::Fabs (vel_vector.X) < 0.5F &&
							WWMath::Fabs (vel_vector.Y) < 0.5F &&
							WWMath::Fabs (vel_vector.Z) < 0.5F);
	}

	return has_arrived;
}


////////////////////////////////////////////////////////////////
//
//	Get_Current_Circle_Angle
//
////////////////////////////////////////////////////////////////
float
PilotClass::Get_Current_Circle_Angle (void) const
{
	Vector3 curr_pos;
	m_GameObj->Get_Position (&curr_pos);
	Vector3 delta = curr_pos - m_FinalDest;

	return WWMath::Atan2 (delta.Y, delta.X);
}


////////////////////////////////////////////////////////////////////////////////////////////
//
//	Save
//
////////////////////////////////////////////////////////////////////////////////////////////
void
PilotClass::Save (ChunkSaveClass &csave)
{
	csave.Begin_Chunk (CHUNKID_VARIABLES);

		//
		//	Save each variable to its own microchunk
		//		
		
		WRITE_MICRO_CHUNK (csave, VARID_FINAL_DEST,			m_FinalDest);
		WRITE_MICRO_CHUNK (csave, VARID_NEXT_POINT,			m_NextPoint);
		WRITE_MICRO_CHUNK (csave, VARID_CURRENT_TM,			m_CurrentTM);
		WRITE_MICRO_CHUNK (csave, VARID_OBJ_SPACE_DEST,		m_ObjSpaceDest);
		WRITE_MICRO_CHUNK (csave, VARID_OBJ_SPACE_WPOINT,	m_ObjSpaceWaypoint);		
		WRITE_MICRO_CHUNK (csave, VARID_PATH_PTR,				m_CurrentPath);	
		WRITE_MICRO_CHUNK (csave, VARID_GAMEOBJ_PTR,			m_GameObj);	
		WRITE_MICRO_CHUNK (csave, VARID_MAX_SPEED,			m_MaxSpeed);
		WRITE_MICRO_CHUNK (csave, VARID_SPEED_FACTOR,		m_SpeedFactor);
		WRITE_MICRO_CHUNK (csave, VARID_AGGRESSIVENESS,		m_Aggressiveness);
		WRITE_MICRO_CHUNK (csave, VARID_ARRIVED_DIST,		m_ArrivedDist);
		WRITE_MICRO_CHUNK (csave, VARID_HOVERDIST,			m_HoverDist);
		WRITE_MICRO_CHUNK (csave, VARID_ISEXACT_Z_IMPORT,	m_IsExactZImportant);
		WRITE_MICRO_CHUNK (csave, VARID_FACE_TARGET,			m_FaceTarget);
		WRITE_MICRO_CHUNK (csave, VARID_TARGET_LOCATION,	m_TargetLocation);
		WRITE_MICRO_CHUNK (csave, VARID_MODE,					m_Mode);
		WRITE_MICRO_CHUNK (csave, VARID_FORWARD_SPEED,		m_ForwardSpeed);
		WRITE_MICRO_CHUNK (csave, VARID_STRAFE_SPEED,		m_StrafeSpeed);
		WRITE_MICRO_CHUNK (csave, VARID_LIFT_SPEED,			m_LiftSpeed);
		WRITE_MICRO_CHUNK (csave, VARID_TURN_SHARPNESS,		m_TurnSharpness);
		WRITE_MICRO_CHUNK (csave, VARID_CIRCLE_ANGLE,		m_CircleAngle);
		WRITE_MICRO_CHUNK (csave, VARID_CIRCLE_DIST,			m_CircleDist);
		WRITE_MICRO_CHUNK (csave, VARID_MIN_CIRCLE_ANGLE,	m_MinCircleAngle);
		WRITE_MICRO_CHUNK (csave, VARID_MAX_CIRCLE_ANGLE,	m_MaxCircleAngle); 

	csave.End_Chunk ();
	return ;
}


////////////////////////////////////////////////////////////////////////////////////////////
//
//	Load
//
////////////////////////////////////////////////////////////////////////////////////////////
void
PilotClass::Load (ChunkLoadClass &cload)
{
	while (cload.Open_Chunk ()) {		
		switch (cload.Cur_Chunk_ID ()) {

			case CHUNKID_VARIABLES:
				Load_Variables (cload);
				break;
		}

		cload.Close_Chunk ();
	}

	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Load_Variables
//
///////////////////////////////////////////////////////////////////////
void
PilotClass::Load_Variables (ChunkLoadClass &cload)
{
	//
	//	Loop through all the microchunks that define the variables
	//
	while (cload.Open_Micro_Chunk ()) {
		switch (cload.Cur_Micro_Chunk_ID ()) {

			READ_MICRO_CHUNK (cload, VARID_FINAL_DEST,			m_FinalDest);
			READ_MICRO_CHUNK (cload, VARID_NEXT_POINT,			m_NextPoint);
			READ_MICRO_CHUNK (cload, VARID_CURRENT_TM,			m_CurrentTM);
			READ_MICRO_CHUNK (cload, VARID_OBJ_SPACE_DEST,		m_ObjSpaceDest);
			READ_MICRO_CHUNK (cload, VARID_OBJ_SPACE_WPOINT,	m_ObjSpaceWaypoint);
			READ_MICRO_CHUNK (cload, VARID_PATH_PTR,				m_CurrentPath);	
			READ_MICRO_CHUNK (cload, VARID_GAMEOBJ_PTR,			m_GameObj);	
			READ_MICRO_CHUNK (cload, VARID_MAX_SPEED,				m_MaxSpeed);
			READ_MICRO_CHUNK (cload, VARID_SPEED_FACTOR,			m_SpeedFactor);
			READ_MICRO_CHUNK (cload, VARID_AGGRESSIVENESS,		m_Aggressiveness);
			READ_MICRO_CHUNK (cload, VARID_ARRIVED_DIST,			m_ArrivedDist);
			READ_MICRO_CHUNK (cload, VARID_HOVERDIST,				m_HoverDist);
			READ_MICRO_CHUNK (cload, VARID_ISEXACT_Z_IMPORT,	m_IsExactZImportant);
			READ_MICRO_CHUNK (cload, VARID_FACE_TARGET,			m_FaceTarget);
			READ_MICRO_CHUNK (cload, VARID_TARGET_LOCATION,		m_TargetLocation);
			READ_MICRO_CHUNK (cload, VARID_MODE,					m_Mode);
			READ_MICRO_CHUNK (cload, VARID_FORWARD_SPEED,		m_ForwardSpeed);
			READ_MICRO_CHUNK (cload, VARID_STRAFE_SPEED,			m_StrafeSpeed);
			READ_MICRO_CHUNK (cload, VARID_LIFT_SPEED,			m_LiftSpeed);
			READ_MICRO_CHUNK (cload, VARID_TURN_SHARPNESS,		m_TurnSharpness);
			READ_MICRO_CHUNK (cload, VARID_CIRCLE_ANGLE,			m_CircleAngle);
			READ_MICRO_CHUNK (cload, VARID_CIRCLE_DIST,			m_CircleDist);
			READ_MICRO_CHUNK (cload, VARID_MIN_CIRCLE_ANGLE,	m_MinCircleAngle);
			READ_MICRO_CHUNK (cload, VARID_MAX_CIRCLE_ANGLE,	m_MaxCircleAngle); 
		}

		cload.Close_Micro_Chunk ();
	}

	//
	//	Request that the game object ptr gets remapped
	//
	if (m_GameObj != NULL) {
		REQUEST_POINTER_REMAP ((void **)&m_GameObj);
	}

	//
	//	Request that the path object ptr gets remapped
	//
	if (m_CurrentPath != NULL) {
		REQUEST_POINTER_REMAP ((void **)&m_CurrentPath);
	}	

	return ;
}


///////////////////////////////////////////////////////////////////////
//
//	Reset
//
///////////////////////////////////////////////////////////////////////
void
PilotClass::Reset (void)
{
	m_CurrentPath						= NULL;
	m_GameObj							= NULL;
	m_Mode								= MODE_HOVER,
	m_IsExactZImportant				= false;
	return ;
}
