979 lines
No EOL
25 KiB
C++
979 lines
No EOL
25 KiB
C++
/*
|
|
** 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/vehicledriver.cpp $*
|
|
* *
|
|
* Author:: Patrick Smith *
|
|
* *
|
|
* $Modtime:: 2/22/02 7:13p $*
|
|
* *
|
|
* $Revision:: 43 $*
|
|
* *
|
|
*---------------------------------------------------------------------------------------------*
|
|
* Functions: *
|
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
#include "vehicledriver.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 "combat.h"
|
|
#include "chunkio.h"
|
|
#include <windows.h>
|
|
|
|
#include "vehiclecurve.h"
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
// Save/Load constants
|
|
////////////////////////////////////////////////////////////////
|
|
enum
|
|
{
|
|
CHUNKID_VARIABLES = 0x11040454,
|
|
};
|
|
|
|
enum
|
|
{
|
|
VARID_CURRENT_DEST = 1,
|
|
VARID_FINAL_DEST,
|
|
VARID_MAX_SPEED,
|
|
VARID_SPEED_FACTOR,
|
|
VARID_LAST_POS,
|
|
VARID_BAD_PROGRESS_COUNT,
|
|
VARID_IS_BACKING_UP,
|
|
VARID_IS_BACKUP_LOCKED,
|
|
VARID_TURN_OFF_ENGINE,
|
|
VARID_PATH_PTR,
|
|
VARID_GAME_OBJ_PTR,
|
|
VARID_ARRIVED_DIST
|
|
};
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
//
|
|
// VehicleDriverClass
|
|
//
|
|
////////////////////////////////////////////////////////////////
|
|
VehicleDriverClass::VehicleDriverClass (void)
|
|
: m_CurrentDest (0, 0, 0),
|
|
m_FinalDest (0, 0, 0),
|
|
m_CurrentPath (NULL),
|
|
m_GameObj (NULL),
|
|
m_IsBackingUp (false),
|
|
m_MaxSpeed (20.0F),
|
|
m_SpeedFactor (1.0F),
|
|
m_LastPos (0, 0, 0),
|
|
m_BadProgressCount (0),
|
|
m_IsBackupLocked (false),
|
|
m_TurnOffEngineWhenDone (false),
|
|
m_ArrivedDist (1.0F),
|
|
m_BrakingDist (0),
|
|
m_LastFrameExpectedVelocity (0)
|
|
{
|
|
return ;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
//
|
|
// ~VehicleDriverClass
|
|
//
|
|
////////////////////////////////////////////////////////////////
|
|
VehicleDriverClass::~VehicleDriverClass (void)
|
|
{
|
|
Free();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
//
|
|
// Get_Velocity
|
|
//
|
|
// Returns the object-space velocity vector
|
|
//
|
|
////////////////////////////////////////////////////////////////
|
|
void
|
|
VehicleDriverClass::Get_Velocity (const Matrix3D &tm, Vector3 &vel_vector)
|
|
{
|
|
//
|
|
// Get a pointer to the physics object for this vehicle
|
|
//
|
|
MoveablePhysClass *phys_obj = m_GameObj->Peek_Physical_Object()->As_MoveablePhysClass ();
|
|
if (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 (tm, vel_vector_world, &vel_vector);
|
|
} else {
|
|
vel_vector.Set (0, 0, 0);
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
//
|
|
// Initialize
|
|
//
|
|
////////////////////////////////////////////////////////////////
|
|
void
|
|
VehicleDriverClass::Initialize (SmartGameObj *game_obj, PathClass *path)
|
|
{
|
|
m_GameObj = game_obj;
|
|
|
|
//
|
|
// If the game object is driving 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;
|
|
}
|
|
}
|
|
|
|
m_CurrentPath = path;
|
|
m_FinalDest = path->Get_Dest_Pos ();
|
|
m_CurrentDest = path->Get_Dest_Pos ();
|
|
m_BrakingDist = 0;
|
|
m_LastFrameExpectedVelocity = 0;
|
|
|
|
//
|
|
// 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);
|
|
m_TurnOffEngineWhenDone = true;
|
|
} else {
|
|
m_TurnOffEngineWhenDone = false;
|
|
}
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Find_Tangent_Angle
|
|
//
|
|
///////////////////////////////////////////////////////////////////////////
|
|
static float
|
|
Find_Tangent_Angle
|
|
(
|
|
const Vector2 & center,
|
|
float radius,
|
|
const Vector2 & point,
|
|
bool clockwise
|
|
)
|
|
{
|
|
float angle = 0;
|
|
|
|
//
|
|
// Calculate the distance from the point to the center of the circle
|
|
//
|
|
float delta_x = point.X - center.X;
|
|
float delta_y = point.Y - center.Y;
|
|
float dist = ::sqrt (delta_x * delta_x + delta_y * delta_y);
|
|
if (dist == 0) {
|
|
dist = 0.0001F;
|
|
}
|
|
|
|
//
|
|
// Determine the offset angle (from the line between the point and center)
|
|
// where the 2 tangent points lie.
|
|
//
|
|
float angle_offset = WWMath::Fast_Acos (radius / dist);
|
|
float base_angle = WWMath::Atan2 (delta_x, -delta_y);
|
|
base_angle = WWMath::Wrap (base_angle, 0, WWMATH_PI * 2);
|
|
|
|
//
|
|
// Return which ever tangent point we would come across first, depending
|
|
// on our orientation
|
|
//
|
|
if (clockwise) {
|
|
angle = (WWMATH_PI * 3) - (base_angle + angle_offset);
|
|
} else {
|
|
angle = base_angle - angle_offset;
|
|
}
|
|
|
|
angle = WWMath::Wrap (angle, 0, WWMATH_PI * 2);
|
|
|
|
if (dist < radius) {
|
|
angle = DEG_TO_RADF (360.0F);
|
|
}
|
|
|
|
return angle;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
//
|
|
// Drive
|
|
//
|
|
////////////////////////////////////////////////////////////////
|
|
bool
|
|
VehicleDriverClass::Drive (void)
|
|
{
|
|
bool retval = false;
|
|
|
|
//
|
|
// Get the turn radius of the current vehicle
|
|
//
|
|
PathObjectClass path_obj;
|
|
m_CurrentPath->Get_Path_Object (path_obj);
|
|
float turn_radius = path_obj.Get_Turn_Radius ();
|
|
|
|
//
|
|
// Drive either using wheeled vehicle logic or tracked vehicle logic
|
|
//
|
|
if (turn_radius != 0) {
|
|
retval = Drive_Wheeled ();
|
|
} else {
|
|
retval = Drive_Tracked ();
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
//
|
|
// Drive_Wheeled
|
|
//
|
|
////////////////////////////////////////////////////////////////
|
|
bool
|
|
VehicleDriverClass::Drive_Wheeled (void)
|
|
{
|
|
|
|
//
|
|
// Convert the destinations from world to object space
|
|
//
|
|
Matrix3D tm = m_GameObj->Get_Transform ();
|
|
Vector3 pos = tm.Get_Translation ();
|
|
float facing = tm.Get_Z_Rotation ();
|
|
tm.Make_Identity ();
|
|
tm.Set_Translation (pos);
|
|
tm.Rotate_Z (facing);
|
|
|
|
/*PhysClass *phys_obj = m_GameObj->Peek_Physical_Object ();
|
|
RenderObjClass *model = phys_obj->Peek_Model ();
|
|
|
|
AABoxClass bounding_box;
|
|
bounding_box = model->Get_Bounding_Box ();
|
|
|
|
Vector3 offset;
|
|
Matrix3D::Rotate_Vector (tm, Vector3 (bounding_box.Extent.X, 0, 0), &offset);
|
|
tm.Set_Translation (tm.Get_Translation () - offset);
|
|
|
|
PhysicsSceneClass::Get_Instance ()->Add_Debug_AABox (AABoxClass (tm.Get_Translation (), Vector3 (0.5F,0.5F,0.5F)), Vector3 (1, 0, 1));*/
|
|
|
|
//
|
|
// Make sure we have the final destination point
|
|
//
|
|
if (m_CurrentPath != NULL) {
|
|
m_FinalDest = m_CurrentPath->Get_Dest_Pos ();
|
|
}
|
|
|
|
float max_speed = m_MaxSpeed * m_SpeedFactor;
|
|
float curve_modifier = 1.0F;
|
|
|
|
//
|
|
// Modify some of our parameters if we are force moving backwards
|
|
//
|
|
if (m_IsBackupLocked) {
|
|
tm.Rotate_Z (DEG_TO_RADF (180));
|
|
//curve_modifier = 2.0F;
|
|
}
|
|
|
|
//
|
|
// Convert the current and final destination points into object space
|
|
//
|
|
Vector3 final_dest;
|
|
Vector3 current_dest;
|
|
Matrix3D::Inverse_Transform_Vector (tm, m_FinalDest, &final_dest);
|
|
Matrix3D::Inverse_Transform_Vector (tm, m_CurrentDest, ¤t_dest);
|
|
final_dest.Z = 0;
|
|
current_dest.Z = 0;
|
|
|
|
//
|
|
// Get the vehicle's current position
|
|
//
|
|
Vector3 curr_pos;
|
|
m_GameObj->Get_Position (&curr_pos);
|
|
|
|
//
|
|
// Calculate the distance to the destination
|
|
//
|
|
float dist_on_path = m_CurrentPath->Get_Remaining_Path_Length ();
|
|
float dist_to_goal = (current_dest.Length () + dist_on_path);
|
|
|
|
//
|
|
// Are we there yet?
|
|
//
|
|
bool is_complete = (dist_to_goal <= m_ArrivedDist);
|
|
if (is_complete) {
|
|
dist_to_goal = 0;
|
|
}
|
|
|
|
if (is_complete == false) {
|
|
|
|
//
|
|
// Get the sharpness of the curve from the path object
|
|
//
|
|
Vector3 curve_pos (0, 0, 0);
|
|
float curve_sharpness = m_CurrentPath->Get_Curve_Sharpness (&curve_pos);
|
|
|
|
//
|
|
// Take into account the distance to the curve
|
|
//
|
|
float curve_dist = (curve_pos - tm.Get_Translation ()).Length ();
|
|
curve_sharpness *= 1.0F - WWMath::Clamp (curve_dist / max_speed, 0, 1.0F);
|
|
curve_sharpness *= curve_modifier;
|
|
|
|
//
|
|
// Calculate what the angle is between us and the next destination point
|
|
//
|
|
float turn_angle = WWMath::Atan2 (current_dest.Y, current_dest.X);
|
|
turn_angle = WWMath::Wrap (turn_angle, DEG_TO_RADF(-180), DEG_TO_RADF(180));
|
|
|
|
//
|
|
// Calculate how sharp we need to turn based on the sharpness of the curve
|
|
//
|
|
float max_angle = DEG_TO_RADF (10.0F) + (1.0F - curve_sharpness) * DEG_TO_RADF (30.0F);
|
|
float turn_accel = turn_angle / max_angle;
|
|
|
|
|
|
/*PathObjectClass path_obj;
|
|
m_CurrentPath->Get_Path_Object (path_obj);
|
|
float turn_radius = path_obj.Get_Turn_Radius ();*/
|
|
|
|
/*if ((turn_radius * turn_radius) > current_dest.Length2 () && WWMath::Fabs (turn_angle) > DEG_TO_RADF (60.0F)) {
|
|
m_IsBackingUp = true;
|
|
}*/
|
|
|
|
//
|
|
// Calculate our (current) normalized speed
|
|
//
|
|
Vector3 vel_vector;
|
|
Get_Velocity (tm, vel_vector);
|
|
float speed = vel_vector.X;
|
|
float norm_speed = speed / max_speed;
|
|
|
|
//
|
|
// Calculate how fast we should accelerate
|
|
//
|
|
float expected_velocity = 1.0F - (0.3F * curve_sharpness);
|
|
|
|
//
|
|
// Take into account the braking speed as we approach the goal
|
|
//
|
|
float brake_velocity = Calculate_Brake (dist_to_goal, expected_velocity, speed, max_speed);
|
|
expected_velocity = min (brake_velocity, expected_velocity);
|
|
|
|
//
|
|
// Calculate how far off our last velocity request was
|
|
//
|
|
float vel_factor = 1.0F;
|
|
if (norm_speed != 0) {
|
|
vel_factor = m_LastFrameExpectedVelocity / WWMath::Fabs (norm_speed);
|
|
}
|
|
|
|
//
|
|
// Try to account for our last velocity request error
|
|
//
|
|
m_LastFrameExpectedVelocity = WWMath::Fabs (expected_velocity);
|
|
float forward_accel = (expected_velocity - norm_speed) * vel_factor;
|
|
|
|
/*StringClass message;
|
|
message.Format ("forward accel = %f\n", forward_accel);
|
|
WWDEBUG_SAY (((const char *)message));*/
|
|
|
|
//
|
|
// Invert everything if we are locked in driving backwards mode
|
|
//
|
|
if (m_IsBackupLocked) {
|
|
forward_accel = -forward_accel;
|
|
turn_accel = -turn_accel;
|
|
}
|
|
|
|
//
|
|
// Handle the case where we are backing up
|
|
//
|
|
/*if (m_IsBackingUp) {
|
|
|
|
//
|
|
// Switch out of backup mode if we have a pretty good facing on the
|
|
// destination or we got stuck while backing up.
|
|
//
|
|
if (WWMath::Fabs (turn_angle) < DEG_TO_RADF (25.0F) || Are_We_Stuck (vel_vector)) {
|
|
m_IsBackingUp = false;
|
|
m_BadProgressCount = 0;
|
|
} else {
|
|
|
|
//
|
|
// Backup and turn in the opposite direction
|
|
//
|
|
forward_accel = -forward_accel;
|
|
turn_accel = -turn_accel;
|
|
}
|
|
|
|
} else if (Are_We_Stuck (vel_vector)) {
|
|
|
|
m_IsBackingUp = true;
|
|
m_BadProgressCount = 0;
|
|
}*/
|
|
|
|
//
|
|
// Clamp the analog controls to -1 and 1
|
|
//
|
|
turn_accel = WWMath::Clamp (turn_accel, -1.0F, 1.0F);
|
|
forward_accel = WWMath::Clamp (forward_accel, -1.0F, 1.0F);
|
|
|
|
//
|
|
// Pass the controls onto the vehicle
|
|
//
|
|
Apply_Controls (forward_accel, turn_accel);
|
|
|
|
} else {
|
|
|
|
//
|
|
// Calculate our (current) normalized speed
|
|
//
|
|
Vector3 vel_vector;
|
|
Get_Velocity (tm, vel_vector);
|
|
float speed = vel_vector.X;
|
|
float norm_speed = speed / max_speed;
|
|
|
|
//
|
|
// Try to zero out our forward movement
|
|
//
|
|
float forward_accel = WWMath::Clamp (-norm_speed * 5.0F, -1.0F, 1.0F);
|
|
|
|
if (m_GameObj->Peek_Physical_Object ()->As_VTOLVehicleClass ()) {
|
|
forward_accel = WWMath::Clamp (-norm_speed * 5.0F, -1.0F, 1.0F);
|
|
}
|
|
|
|
//
|
|
// Invert everything if we are locked in driving backwards mode
|
|
//
|
|
if (m_IsBackupLocked) {
|
|
forward_accel = -forward_accel;
|
|
}
|
|
|
|
//
|
|
// Try to hover about the point
|
|
//
|
|
Apply_Controls (forward_accel, 0);
|
|
|
|
//
|
|
// We aren't really done unless we've stopped most motion
|
|
//
|
|
if (m_GameObj->Peek_Physical_Object ()->As_VTOLVehicleClass ()) {
|
|
is_complete = (WWMath::Fabs (norm_speed) < 0.0001F);
|
|
}
|
|
|
|
//
|
|
// Do we need to turn off the engine?
|
|
//
|
|
if (is_complete && m_TurnOffEngineWhenDone) {
|
|
|
|
//
|
|
// Turn off the engine if we are finished moving
|
|
//
|
|
VehicleGameObj *vehicle = m_GameObj->As_VehicleGameObj ();
|
|
if (vehicle != NULL) {
|
|
Apply_Controls (0, 0);
|
|
vehicle->Enable_Engine (false);
|
|
}
|
|
}
|
|
}
|
|
|
|
return is_complete;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
//
|
|
// Calculate_Brake
|
|
//
|
|
////////////////////////////////////////////////////////////////
|
|
float
|
|
VehicleDriverClass::Calculate_Brake
|
|
(
|
|
float dist_to_goal,
|
|
float expected_velocity,
|
|
float curr_speed,
|
|
float max_speed
|
|
)
|
|
{
|
|
float brake_velocity = expected_velocity;
|
|
|
|
//
|
|
// Handle VTOL's differently
|
|
//
|
|
if (m_GameObj->Peek_Physical_Object ()->As_VTOLVehicleClass () == NULL) {
|
|
|
|
//
|
|
// Start braking when the vehicle is 1 second away from its destination
|
|
//
|
|
if (m_BrakingDist == 0 && dist_to_goal < curr_speed) {
|
|
m_BrakingDist = curr_speed;
|
|
}
|
|
|
|
//
|
|
// Calculate what speed we should be given the distance to the destination
|
|
//
|
|
if (m_BrakingDist > 0) {
|
|
float end_velocity = (0.5F / max_speed);
|
|
float percent = WWMath::Clamp (dist_to_goal / (m_BrakingDist - m_ArrivedDist), -1.0F, 1.0F);
|
|
brake_velocity = ((1.0F - percent) * end_velocity) + (percent * expected_velocity);
|
|
}
|
|
|
|
} else {
|
|
|
|
|
|
//
|
|
// Start braking when the vehicle is 3 seconds away from its destination
|
|
//
|
|
if (m_BrakingDist == 0 && dist_to_goal < max_speed * 3) {
|
|
m_BrakingDist = max_speed * 3;
|
|
}
|
|
|
|
//
|
|
// Calculate what speed we should be given the distance to the destination
|
|
//
|
|
if (m_BrakingDist > 0) {
|
|
float percent = WWMath::Clamp (dist_to_goal / (m_BrakingDist - m_ArrivedDist), -1.0F, 1.0F);
|
|
float end_velocity = WWMath::Clamp ((-(curr_speed / max_speed) * 5.0F) + (0.75F / max_speed), -1.0F, 1.0F);
|
|
brake_velocity = ((1.0F - percent) * end_velocity) + (percent * expected_velocity);
|
|
}
|
|
}
|
|
|
|
return brake_velocity;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
//
|
|
// Drive_Tracked
|
|
//
|
|
////////////////////////////////////////////////////////////////
|
|
bool
|
|
VehicleDriverClass::Drive_Tracked (void)
|
|
{
|
|
//
|
|
// Convert the destinations from world to object space
|
|
//
|
|
Matrix3D tm = m_GameObj->Get_Transform ();
|
|
Vector3 pos = tm.Get_Translation ();
|
|
float facing = tm.Get_Z_Rotation ();
|
|
tm.Make_Identity ();
|
|
tm.Set_Translation (pos);
|
|
tm.Rotate_Z (facing);
|
|
float max_speed = m_MaxSpeed * m_SpeedFactor;
|
|
|
|
//
|
|
// Let the path know how fast we are moving
|
|
//
|
|
Vector3 vel_vector;
|
|
Get_Velocity (tm, vel_vector);
|
|
m_CurrentPath->Set_Velocity (WWMath::Fabs (vel_vector.X));
|
|
|
|
//
|
|
// Make sure we have the final destination point
|
|
//
|
|
if (m_CurrentPath != NULL) {
|
|
m_FinalDest = m_CurrentPath->Get_Dest_Pos ();
|
|
m_CurrentDest = m_CurrentPath->Get_Next_Pos ();
|
|
}
|
|
|
|
//
|
|
// Modify some of our parameters if we are force moving backwards
|
|
//
|
|
if (m_IsBackupLocked) {
|
|
tm.Rotate_Z (DEG_TO_RADF (180));
|
|
}
|
|
|
|
//
|
|
// Convert the current and final destination points into object space
|
|
//
|
|
Vector3 final_dest;
|
|
Vector3 current_dest;
|
|
Matrix3D::Inverse_Transform_Vector (tm, m_FinalDest, &final_dest);
|
|
Matrix3D::Inverse_Transform_Vector (tm, m_CurrentDest, ¤t_dest);
|
|
final_dest.Z = 0;
|
|
current_dest.Z = 0;
|
|
|
|
//
|
|
// Get the vehicle's current position
|
|
//
|
|
Vector3 curr_pos;
|
|
m_GameObj->Get_Position (&curr_pos);
|
|
|
|
//
|
|
// Calculate the distance to the destination
|
|
//
|
|
float dist_on_path = m_CurrentPath->Get_Remaining_Path_Length ();
|
|
float dist_to_goal = (current_dest.Length () + dist_on_path);
|
|
|
|
//
|
|
// Are we there yet?
|
|
//
|
|
bool is_complete = (dist_to_goal <= m_ArrivedDist);
|
|
if (is_complete) {
|
|
dist_to_goal = 0;
|
|
}
|
|
|
|
if (is_complete == false) {
|
|
|
|
//
|
|
// Calculate what the angle is between us and the next destination point
|
|
//
|
|
float turn_angle = WWMath::Atan2 (current_dest.Y, current_dest.X);
|
|
turn_angle = WWMath::Wrap (turn_angle, DEG_TO_RADF(-180), DEG_TO_RADF(180));
|
|
|
|
//StringClass message;
|
|
// message.Format ("turn_angle = %f\n", turn_angle * 180 / 3.1415926F);
|
|
//WWDEBUG_SAY (((const char *)message));
|
|
|
|
//
|
|
// Calculate how sharp we need to turn based on the sharpness of the curve
|
|
//
|
|
float turn_accel = turn_angle / DEG_TO_RADF (45.0F);
|
|
|
|
//
|
|
// Calculate our (current) normalized speed
|
|
//
|
|
float speed = vel_vector.X;
|
|
float norm_speed = speed / max_speed;
|
|
|
|
//
|
|
// Calculate how fast we should accelerate
|
|
//
|
|
float expected_velocity = 1.0F;
|
|
|
|
//
|
|
// Start braking when the vehicle is 1 second away from its destination
|
|
//
|
|
if (m_BrakingDist == 0 && dist_to_goal < speed) {
|
|
m_BrakingDist = speed;
|
|
}
|
|
|
|
if (m_BrakingDist > 0) {
|
|
float end_velocity = (0.5F / max_speed);
|
|
float percent = WWMath::Clamp (dist_to_goal / (m_BrakingDist - m_ArrivedDist), -1.0F, 1.0F);
|
|
float braking_vel = ((1.0F - percent) * end_velocity) + (percent * expected_velocity);
|
|
|
|
expected_velocity = min (expected_velocity, braking_vel);
|
|
}
|
|
|
|
float forward_accel = expected_velocity - norm_speed;
|
|
|
|
//
|
|
// Make tracked vehicles turn in place
|
|
//
|
|
if (WWMath::Fabs (turn_angle) > DEG_TO_RADF (10.0F)) {
|
|
forward_accel /= 4.0F;
|
|
}
|
|
|
|
//
|
|
// Invert everything if we are locked in driving backwards mode
|
|
//
|
|
if (m_IsBackupLocked) {
|
|
forward_accel = -forward_accel;
|
|
}
|
|
|
|
//
|
|
// Clamp the analog controls to -1 and 1
|
|
//
|
|
turn_accel = WWMath::Clamp (turn_accel, -1.0F, 1.0F);
|
|
forward_accel = WWMath::Clamp (forward_accel, -1.0F, 1.0F);
|
|
|
|
//
|
|
// Pass the controls onto the vehicle
|
|
//
|
|
Apply_Controls (forward_accel, turn_accel);
|
|
|
|
} else {
|
|
|
|
//
|
|
// Calculate our (current) normalized speed
|
|
//
|
|
Vector3 vel_vector;
|
|
Get_Velocity (tm, vel_vector);
|
|
float speed = vel_vector.X;
|
|
float norm_speed = speed / max_speed;
|
|
|
|
//
|
|
// Try to zero out our forward movement
|
|
//
|
|
float forward_accel = WWMath::Clamp (-norm_speed * 2, -1.0F, 1.0F);
|
|
|
|
//
|
|
// Invert everything if we are locked in driving backwards mode
|
|
//
|
|
if (m_IsBackupLocked) {
|
|
forward_accel = -forward_accel;
|
|
}
|
|
|
|
//
|
|
// Try to hover about the point
|
|
//
|
|
Apply_Controls (forward_accel, 0);
|
|
|
|
//
|
|
// Do we need to turn off the engine?
|
|
//
|
|
if (m_TurnOffEngineWhenDone) {
|
|
|
|
//
|
|
// Turn off the engine if we are finished moving
|
|
//
|
|
VehicleGameObj *vehicle = m_GameObj->As_VehicleGameObj ();
|
|
if (vehicle != NULL) {
|
|
Apply_Controls (0, 0);
|
|
vehicle->Enable_Engine (false);
|
|
}
|
|
}
|
|
}
|
|
|
|
return is_complete;
|
|
}
|
|
|
|
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
//
|
|
// Apply_Controls
|
|
//
|
|
////////////////////////////////////////////////////////////////
|
|
void
|
|
VehicleDriverClass::Apply_Controls
|
|
(
|
|
float forward_accel,
|
|
float left_accel
|
|
)
|
|
{
|
|
if (!WWMath::Is_Valid_Float(forward_accel)) {
|
|
#ifdef WWDEBUG
|
|
const char* name = m_GameObj->Peek_Physical_Object()->Peek_Model()->Get_Name();
|
|
WWDEBUG_SAY(("VehicleDriverClass::Apply_Controls - BAD FLOAT forward_access = %f - Model name = %s\n", forward_accel, name));
|
|
#endif //WWDEBUG
|
|
forward_accel = 0.0f;
|
|
}
|
|
if (!WWMath::Is_Valid_Float(left_accel)) {
|
|
#ifdef WWDEBUG
|
|
const char* name = m_GameObj->Peek_Physical_Object()->Peek_Model()->Get_Name();
|
|
WWDEBUG_SAY(("VehicleDriverClass::Apply_Controls - BAD FLOAT left_access = %f - Model name = %s\n", left_accel, name));
|
|
#endif //WWDEBUG
|
|
left_accel = 0.0f;
|
|
}
|
|
|
|
//
|
|
// Now pass the controls onto the game object
|
|
//
|
|
m_GameObj->Set_Analog_Control (ControlClass::ANALOG_MOVE_FORWARD, forward_accel);
|
|
m_GameObj->Set_Analog_Control (ControlClass::ANALOG_TURN_LEFT, left_accel);
|
|
m_GameObj->Set_Analog_Control (ControlClass::ANALOG_MOVE_LEFT, 0);
|
|
return ;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
//
|
|
// Force_Backup
|
|
//
|
|
////////////////////////////////////////////////////////////////
|
|
void
|
|
VehicleDriverClass::Force_Backup (bool onoff)
|
|
{
|
|
//
|
|
// Simply set these flags, the Drive method will
|
|
// make use of them.
|
|
//
|
|
m_IsBackingUp = onoff;
|
|
m_IsBackupLocked = onoff;
|
|
return ;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
//
|
|
// Are_We_Stuck
|
|
//
|
|
////////////////////////////////////////////////////////////////
|
|
bool
|
|
VehicleDriverClass::Are_We_Stuck (const Vector3 &vel_vector)
|
|
{
|
|
//
|
|
// Determine if we are not making significant progress
|
|
//
|
|
if (WWMath::Fabs (vel_vector.X) < (m_MaxSpeed * m_SpeedFactor) / 10.0F) {
|
|
m_BadProgressCount ++;
|
|
} else {
|
|
m_BadProgressCount = 0;
|
|
}
|
|
|
|
//
|
|
// If we aren't making progress for 30 frames in a row,
|
|
// then assume we are stuck
|
|
//
|
|
return bool(m_BadProgressCount > 30);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Save
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
void
|
|
VehicleDriverClass::Save (ChunkSaveClass &csave)
|
|
{
|
|
csave.Begin_Chunk (CHUNKID_VARIABLES);
|
|
|
|
//
|
|
// Save each variable to its own microchunk
|
|
//
|
|
WRITE_MICRO_CHUNK (csave, VARID_CURRENT_DEST, m_CurrentDest);
|
|
WRITE_MICRO_CHUNK (csave, VARID_FINAL_DEST, m_FinalDest);
|
|
WRITE_MICRO_CHUNK (csave, VARID_MAX_SPEED, m_MaxSpeed);
|
|
WRITE_MICRO_CHUNK (csave, VARID_SPEED_FACTOR, m_SpeedFactor);
|
|
WRITE_MICRO_CHUNK (csave, VARID_LAST_POS, m_LastPos);
|
|
WRITE_MICRO_CHUNK (csave, VARID_BAD_PROGRESS_COUNT, m_BadProgressCount);
|
|
WRITE_MICRO_CHUNK (csave, VARID_IS_BACKING_UP, m_IsBackingUp);
|
|
WRITE_MICRO_CHUNK (csave, VARID_IS_BACKUP_LOCKED, m_IsBackupLocked);
|
|
WRITE_MICRO_CHUNK (csave, VARID_TURN_OFF_ENGINE, m_TurnOffEngineWhenDone);
|
|
WRITE_MICRO_CHUNK (csave, VARID_PATH_PTR, m_CurrentPath);
|
|
WRITE_MICRO_CHUNK (csave, VARID_GAME_OBJ_PTR, m_GameObj);
|
|
WRITE_MICRO_CHUNK (csave, VARID_ARRIVED_DIST, m_ArrivedDist);
|
|
|
|
csave.End_Chunk ();
|
|
return ;
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Load
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////////////////////
|
|
void
|
|
VehicleDriverClass::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
|
|
VehicleDriverClass::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_CURRENT_DEST, m_CurrentDest);
|
|
READ_MICRO_CHUNK (cload, VARID_FINAL_DEST, m_FinalDest);
|
|
READ_MICRO_CHUNK (cload, VARID_MAX_SPEED, m_MaxSpeed);
|
|
READ_MICRO_CHUNK (cload, VARID_SPEED_FACTOR, m_SpeedFactor);
|
|
READ_MICRO_CHUNK (cload, VARID_LAST_POS, m_LastPos);
|
|
READ_MICRO_CHUNK (cload, VARID_BAD_PROGRESS_COUNT, m_BadProgressCount);
|
|
READ_MICRO_CHUNK (cload, VARID_IS_BACKING_UP, m_IsBackingUp);
|
|
READ_MICRO_CHUNK (cload, VARID_IS_BACKUP_LOCKED, m_IsBackupLocked);
|
|
READ_MICRO_CHUNK (cload, VARID_TURN_OFF_ENGINE, m_TurnOffEngineWhenDone);
|
|
READ_MICRO_CHUNK (cload, VARID_PATH_PTR, m_CurrentPath);
|
|
READ_MICRO_CHUNK (cload, VARID_GAME_OBJ_PTR, m_GameObj);
|
|
READ_MICRO_CHUNK (cload, VARID_ARRIVED_DIST, m_ArrivedDist);
|
|
}
|
|
|
|
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 ptr gets remapped
|
|
//
|
|
if (m_CurrentPath != NULL) {
|
|
REQUEST_REF_COUNTED_POINTER_REMAP ((RefCountClass **)&m_CurrentPath);
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Reset
|
|
//
|
|
///////////////////////////////////////////////////////////////////////
|
|
void
|
|
VehicleDriverClass::Reset (void)
|
|
{
|
|
m_CurrentPath = NULL;
|
|
m_GameObj = NULL;
|
|
m_BrakingDist = 0;
|
|
m_LastFrameExpectedVelocity = 0;
|
|
m_TurnOffEngineWhenDone = false;
|
|
m_BadProgressCount = 0;
|
|
return ;
|
|
} |