thug/Code/Gel/Components/vehiclecomponent.cpp
2016-02-14 08:39:12 +11:00

3322 lines
116 KiB
C++

//****************************************************************************
//* MODULE: Gel/Components
//* FILENAME: VehicleComponent.cpp
//* OWNER: Dan Nelson
//* CREATION DATE: 1/31/3
//****************************************************************************
#include <core/defines.h>
#include <core/math.h>
#include <sys/config/config.h>
#include <gel/components/vehiclecomponent.h>
#include <gel/components/inputcomponent.h>
#include <gel/components/animationcomponent.h>
#include <gel/components/soundcomponent.h>
#include <gel/components/modelcomponent.h>
#include <gel/object/compositeobject.h>
#include <gel/object/compositeobjectmanager.h>
#include <gel/collision/collcache.h>
#include <gel/scripting/checksum.h>
#include <gel/scripting/script.h>
#include <gel/scripting/struct.h>
#include <gel/scripting/array.h>
#include <gel/scripting/vecpair.h>
#include <gel/scripting/symboltable.h>
#include <gfx/nxmodel.h>
#include <gfx/nxhierarchy.h>
#include <gfx/skeleton.h>
#include <gfx/nxviewman.h>
#include <gfx/nxmiscfx.h>
#include <sk/engine/feeler.h>
#include <sk/modules/skate/skate.h>
#include <sk/scripting/nodearray.h>
#include <sk/components/skatercorephysicscomponent.h>
#include <sk/objects/restart.h>
#include <sk/parkeditor2/parked.h>
#include <sk/gamenet/gamenet.h>
#include <core/math/slerp.h>
#define MESSAGE(a) { printf("M:%s:%i: %s\n", __FILE__ + 15, __LINE__, a); }
#define DUMPI(a) { printf("D:%s:%i: " #a " = %i\n", __FILE__ + 15, __LINE__, a); }
#define DUMPF(a) { printf("D:%s:%i: " #a " = %g\n", __FILE__ + 15, __LINE__, a); }
#define DUMPE(a) { printf("D:%s:%i: " #a " = %e\n", __FILE__ + 15, __LINE__, a); }
#define DUMPS(a) { printf("D:%s:%i: " #a " = %s\n", __FILE__ + 15, __LINE__, a); }
#define DUMPP(a) { printf("D:%s:%i: " #a " = %p\n", __FILE__ + 15, __LINE__, a); }
#define DUMPV(a) { printf("D:%s:%i: " #a " = %g, %g, %g\n", __FILE__ + 15, __LINE__, (a)[X], (a)[Y], (a)[Z]); }
#define DUMP4(a) { printf("D:%s:%i: " #a " = %g, %g, %g, %g\n", __FILE__ + 15, __LINE__, (a)[X], (a)[Y], (a)[Z], (a)[W]); }
#define DUMPM(a) { DUMP4(a[X]); DUMP4(a[Y]); DUMP4(a[Z]); DUMP4(a[W]); }
#define MARK { printf("K:%s:%i: %s\n", __FILE__ + 15, __LINE__, __PRETTY_FUNCTION__); }
#define PERIODIC(n) for (static int p__ = 0; (p__ = ++p__ % (n)) == 0; )
// THOUGHTS:
//
// - BUG: solve negative collision depth issue for rect collisions; must project collision normal into rect plane before calculating depth (i think)
// - BUG: bad wipeout behavior; IDEAS:
// - use only line feelers
// - turn off graivty with > 2 contacts
// - sleep like a rigidbody
// - compare algorithm with rigidbody
// - Triggers.
// - Lock motion if very slow and there's no input.
// - Vehicle camera needs to reports its old position to get sounds' pitch correctly.
//
// - two center of masses (collision at zero and suspension) is an issue; actual rotation is done around zero; what sort of poor behavior does this cause
// when the car is on two wheels? what way is there to retain stable suspension behavior but use a common center of mass? perhaps simply damp rotational
// impulses due to the suspension; this may help with the "vert, linear-to-angular energy" freak-out issue as well
// - body-body collisions; treat as boxes
// - prehaps reduce wheel friction when very near vertical to prevent wall riding
// - states: ASLEEP, AWAKE (full brake; rigidbody), DRIVEN, DRONE (?)
// - vertical side rectangle feelers
// - wheel camber <sp> extracted from model
// - side slippage when stopped on hills
namespace Obj
{
CVehicleComponent::SCollisionPoint CVehicleComponent::sp_collision_points[4 * (Nx::MAX_NUM_2D_COLLISIONS_REPORTED + 1)];
/******************************************************************/
/* */
/* */
/******************************************************************/
CBaseComponent* CVehicleComponent::s_create()
{
return static_cast< CBaseComponent* >( new CVehicleComponent );
}
/******************************************************************/
/* */
/* */
/******************************************************************/
CVehicleComponent::CVehicleComponent() : CBaseComponent()
{
SetType( CRC_VEHICLE );
mp_input_component = NULL;
m_draw_debug_lines = 0;
m_update_suspension_only = false;
m_steering_display = 0.0f;
m_skater_pos.Set();
mp_wheels = NULL;
m_sound_setup_checksum = CRCD(0x1ca1ff20, "Default");
m_num_collision_points = 0;
m_artificial_collision_timer = 0.0f;
m_state = AWAKE;
m_consider_sleeping_count = 0;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
CVehicleComponent::~CVehicleComponent()
{
delete [] mp_wheels;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::InitFromStructure( Script::CStruct* pParams )
{
// extract constant parameters from struct; parameters not included in the script are left unchanged
// toggles through the debug line drawing states
if (pParams->ContainsFlag(CRCD(0x935ab858, "debug")))
{
m_draw_debug_lines = ++m_draw_debug_lines % 3;
}
else if (pParams->ContainsFlag(CRCD(0xe6b5fcd9, "full_debug")))
{
m_draw_debug_lines = (m_draw_debug_lines + 2) % 3;
}
else if (pParams->ContainsFlag(CRCD(0x751da48b, "no_debug")))
{
m_draw_debug_lines = 0;
}
// center of mass location (in)
// the point around which the car rotates; a lower center of mass reduces roll tendency; the XZ location affects the
// vehicle's behavior during jumps
if (pParams->ContainsComponentNamed(CRCD(0x93346b8c, "suspension_center_of_mass")))
{
float suspension_center_of_mass_offset;
pParams->GetFloat(CRCD(0x93346b8c, "suspension_center_of_mass"), &suspension_center_of_mass_offset);
m_suspension_center_of_mass.Set(0.0f, suspension_center_of_mass_offset, 0.0f);
}
// mass (lbs)
// affects acceleration, suspension behavior, and resistance to drag
if (pParams->ContainsComponentNamed(CRCD(0x93fca499, "mass")))
{
pParams->GetFloat(CRCD(0x93fca499, "mass"), &m_inv_mass);
m_inv_mass = vVP_GRAVITATIONAL_ACCELERATION / m_inv_mass;
}
// moment of inertia (in^2 lbs)
// resistance to changes in rotation along the three axes; natural value is about the weight of the car times the
// square of the average radius of the car along the axes in question
// X: resistance to rolling
// Y: resistance to turning
// Z: resistance to pitcing during acceleration, braking, and jumps
if (pParams->ContainsComponentNamed(CRCD(0x8c6473ec, "moment_of_inertia")))
{
pParams->GetVector(CRCD(0x8c6473ec, "moment_of_inertia"), &m_inv_moment_body);
m_inv_moment_body[X] = vVP_GRAVITATIONAL_ACCELERATION / m_inv_moment_body[X];
m_inv_moment_body[Y] = vVP_GRAVITATIONAL_ACCELERATION / m_inv_moment_body[Y];
m_inv_moment_body[Z] = vVP_GRAVITATIONAL_ACCELERATION / m_inv_moment_body[Z];
}
// vehicle body's coefficient of restitution
pParams->GetFloat(CRCD(0x79c3b862, "body_restitution"), &m_body_restitution);
// vehicle body's coefficient of friction
pParams->GetFloat(CRCD(0xf476273c, "body_friction"), &m_body_friction);
// vehicle body's coefficient of friction
pParams->GetFloat(CRCD(0x4a23a53c, "body_wipeout_friction"), &m_body_wipeout_friction);
// vehicle body's penalty-method interpenetration-prevention spring constant
pParams->GetFloat(CRCD(0x1dd4890, "body_spring"), &m_body_spring);
// factor of normal impulse over which you can control via steering
pParams->GetFloat(CRCD(0xb9f026ed, "collision_control"), &m_collision_control);
// horizontal velocity cutoff below which no in-air slerping to face velocity occurs
pParams->GetFloat(CRCD(0x8c14709e, "in_air_slerp_velocity_cutoff"), &m_in_air_slerp_vel_cutoff);
// time over which in-air slerping lerps to full strength after takeoff
pParams->GetFloat(CRCD(0x2c91e49b, "in_air_slerp_time_delay"), &m_in_air_slerp_time_delay);
// in-air slerping strength
pParams->GetFloat(CRCD(0x280f0e0b, "in_air_slerp_strength"), &m_in_air_slerp_strength);
// special slerping below standard velocity threshold
m_vert_correction = pParams->ContainsFlag(CRCD(0x5341806a, "vert_correction"));
// maximum steering angle (degrees)
if (pParams->ContainsComponentNamed(CRCD(0xc1e5abdd, "max_steering_angle")))
{
pParams->GetFloat(CRCD(0xc1e5abdd, "max_steering_angle"), &m_max_steering_angle);
m_max_steering_angle = DEGREES_TO_RADIANS(m_max_steering_angle);
}
// rotational damping parameters which prevent fishtailing and drift
pParams->GetFloat(CRCD(0x386837db, "constant_rotational_damping"), &m_const_rotvel_damping);
pParams->GetFloat(CRCD(0x469cf79a, "quadratic_rotational_damping"), &m_quad_rotvel_damping);
// if the car can be exited via triangle
m_exitable = pParams->ContainsFlag(CRCD(0xc2a136cc, "exitable"));
// if the car has a handbrke
m_no_handbrake = pParams->ContainsFlag(CRCD(0xad6c0a46, "no_handbrake"));
// the vehicle's body's interaction with the environment is expressed as an array of rectangular colliders
if (pParams->ContainsComponentNamed(CRCD(0xfc8c4ac6, "colliders")))
{
Script::CArray* p_colliders_array;
pParams->GetArray(CRCD(0xfc8c4ac6, "colliders"), &p_colliders_array, Script::ASSERT);
float average_distance = 0.0f;
int num_colliders = p_colliders_array->GetSize();
// Dbg_MsgAssert(num_colliders == vVP_NUM_COLLIDERS, ("Number of colliders for CVehicleComponent is incorrect"));
for (int collider_idx = num_colliders; collider_idx--; )
{
SCollider& collider = mp_colliders[collider_idx];
Script::CArray* p_corner_array = p_colliders_array->GetArray(collider_idx);
Dbg_MsgAssert(p_corner_array->GetSize() == 3, ("Incorrect number of corner vectors in collider array"));
Script::CVector* p_corner_vector;
p_corner_vector = p_corner_array->GetVector(0);
collider.body.m_corner[X] = p_corner_vector->mX;
collider.body.m_corner[Y] = p_corner_vector->mY;
collider.body.m_corner[Z] = p_corner_vector->mZ;
p_corner_vector = p_corner_array->GetVector(1);
collider.body.m_first_edge[X] = p_corner_vector->mX;
collider.body.m_first_edge[Y] = p_corner_vector->mY;
collider.body.m_first_edge[Z] = p_corner_vector->mZ;
collider.body.m_first_edge -= collider.body.m_corner;
p_corner_vector = p_corner_array->GetVector(2);
collider.body.m_second_edge[X] = p_corner_vector->mX;
collider.body.m_second_edge[Y] = p_corner_vector->mY;
collider.body.m_second_edge[Z] = p_corner_vector->mZ;
collider.body.m_second_edge -= collider.body.m_corner;
// update maximum_distance
average_distance += collider.body.m_corner.Length();
average_distance += (collider.body.m_corner + collider.body.m_first_edge).Length();
average_distance += (collider.body.m_corner + collider.body.m_second_edge).Length();
// calculate the precomputable values
collider.first_edge_length = collider.body.m_first_edge.Length();
collider.second_edge_length = collider.body.m_second_edge.Length();
Dbg_MsgAssert(collider.first_edge_length > 0.0f, ("Collider of zero area"));
Dbg_MsgAssert(collider.second_edge_length > 0.0f, ("Collider of zero area"));
}
// set the skater's rigid body collision radius while in the car
Mdl::Skate::Instance()->GetLocalSkater()->SetRigidBodyCollisionRadiusBoost(average_distance / 3.0f / num_colliders - 48.0f);
}
if (pParams->ContainsComponentNamed(CRCD(0x1757e572, "engine")))
{
Script::CStruct* p_engine_struct;
pParams->GetStructure(CRCD(0x1757e572, "engine"), &p_engine_struct, Script::ASSERT);
// base drive torque of the engine; multiplied by the gear and differential ratio (ft-lbs)
if (p_engine_struct->ContainsComponentNamed(CRCD(0x9aa9faee, "drive_torque")))
{
p_engine_struct->GetFloat(CRCD(0x9aa9faee, "drive_torque"), &m_engine.drive_torque);
m_engine.drive_torque *= 12.0f;
}
// base drag torque of the engine; multiplied by the square of the gear ratio (ft-lbs)
if (p_engine_struct->ContainsComponentNamed(CRCD(0xb2268648, "drag_torque")))
{
p_engine_struct->GetFloat(CRCD(0xb2268648, "drag_torque"), &m_engine.drag_torque);
m_engine.drag_torque *= 12.0f;
}
// when rpm reaches this speed, the transmition upshifts (rpm)
if (p_engine_struct->ContainsComponentNamed(CRCD(0x33921583, "upshift_rpm")))
{
p_engine_struct->GetFloat(CRCD(0x33921583, "upshift_rpm"), &m_engine.upshift_rotvel);
m_engine.upshift_rotvel = RPM_TO_RADIANS_PER_SECOND(m_engine.upshift_rotvel);
}
// base gear ratio; torque and rpm are multiplied by this when translated up and down the drive train
p_engine_struct->GetFloat(CRCD(0x94ea6601, "differential_ratio"), &m_engine.differential_ratio);
// instead of having a true reverse gear, all gears are scaled down by this ratio when in reverse
p_engine_struct->GetFloat(CRCD(0x82bcbb10, "reverse_torque_ratio"), &m_engine.reverse_torque_ratio);
// gear ratios; torque and rpm are multiplied by these when translated up and down the drive train
if (p_engine_struct->ContainsComponentNamed(CRCD(0xc023fa75, "gear_ratios")))
{
Script::CArray* p_engine_gear_ratios_array;
p_engine_struct->GetArray(CRCD(0xc023fa75, "gear_ratios"), &p_engine_gear_ratios_array, Script::ASSERT);
m_engine.num_gears = p_engine_gear_ratios_array->GetSize();
Dbg_MsgAssert(m_engine.num_gears <= vVP_MAX_NUM_GEARS, ("Number of gears exceeds the maximum allowed number"));
for (int n = 0; n < m_engine.num_gears; n++)
{
m_engine.p_gear_ratios[n] = p_engine_gear_ratios_array->GetFloat(n);
if (n == 0) continue;
Dbg_MsgAssert(n == 0 || m_engine.p_gear_ratios[n - 1] > m_engine.p_gear_ratios[n],
("Low gear has lower gear ratio than high gear"));
}
}
}
// you can setup all wheels at once or each individually
// an array of wheel structures
if (pParams->ContainsComponentNamed(CRCD(0xb3f8557e, "wheels")))
{
Script::CArray* p_wheels_array = NULL;
pParams->GetArray(CRCD(0xb3f8557e, "wheels"), &p_wheels_array, Script::ASSERT);
Dbg_MsgAssert(!mp_wheels || m_num_wheels == p_wheels_array->GetSize(), ("Changed number of wheels"));
m_num_wheels = p_wheels_array->GetSize();
if (!mp_wheels)
{
mp_wheels = new SWheel[m_num_wheels];
}
for (int n = m_num_wheels; n--; )
{
Script::CStruct* p_wheel_struct = NULL;
p_wheel_struct = p_wheels_array->GetStructure(n);
// see update_wheel_from_structure() for documentation on wheel parameters
update_wheel_from_structure(mp_wheels[n], p_wheel_struct);
} // END loop over wheels
} // END if wheel array specified
Dbg_MsgAssert(mp_wheels, ("Wheels not created"));
// a structure of wheel parameters which acts on all wheels
if (pParams->ContainsComponentNamed(CRCD(0x371badff, "all_wheels")))
{
Script::CStruct* p_wheel_struct;
pParams->GetStructure(CRCD(0x371badff, "all_wheels"), &p_wheel_struct, Script::ASSERT);
for (int n = m_num_wheels; n--; )
{
// see update_wheel_from_structure() for documentation on wheel parameters
update_wheel_from_structure(mp_wheels[n], p_wheel_struct);
} // END loop over wheels
} // END if all_wheel structure specified
// calculate dependent constant characteristics
// count the number of drive wheels
m_num_drive_wheels = 0;
for (int n = m_num_wheels; n--; )
{
if (mp_wheels[n].drive)
{
m_num_drive_wheels++;
}
}
// setup position and orientation based on the object's state
m_orientation = GetObject()->GetMatrix();
m_orientation.Normalize();
if (pParams->ContainsComponentNamed(CRCD(0xaa99c521, "save")))
{
Mth::Vector orientation_vector;
orientation_vector.Set();
pParams->GetVector(CRCD(0xc97f3aa9, "orientation"), &orientation_vector);
m_orientation.SetVector(orientation_vector);
m_orientation.Normalize();
m_orientation.GetMatrix(m_orientation_matrix);
m_orientation_matrix[W].Set();
GetObject()->SetMatrix(m_orientation_matrix);
GetObject()->SetDisplayMatrix(m_orientation_matrix);
}
m_pos = GetObject()->GetPos();
if (pParams->ContainsComponentNamed(CRCD(0x7f261953, "pos")))
{
pParams->GetVector(CRCD(0x7f261953, "pos"), &m_pos);
GetObject()->SetPos(m_pos);
}
m_skater_visible = m_skater_visible || pParams->ContainsFlag(CRCD(0x2ed67657, "make_skater_visible"));
if (pParams->ContainsFlag(CRCD(0x2ed67657, "make_skater_visible")))
{
pParams->GetVector(CRCD(0xec86ef7a, "skater_pos"), &m_skater_pos, Script::ASSERT);
pParams->GetChecksum(CRCD(0xda75a33e, "skater_anim"), &m_skater_anim, Script::ASSERT);
}
pParams->GetChecksum(CRCD(0xedcf90e, "Sounds"), &m_sound_setup_checksum);
// zero velocities and accumulators
m_mom.Set(0.0f, 0.0f, 0.0f);
m_rotmom.Set(0.0f, 0.0f, 0.0f);
m_force.Set(0.0f, 0.0f, 0.0f);
m_torque.Set(0.0f, 0.0f, 0.0f);
for (int n = m_num_wheels; n--; )
{
SWheel& wheel = mp_wheels[n];
wheel.state = SWheel::OUT_OF_CONTACT;
wheel.orientation = 0.0f;
wheel.rotvel = 0.0f;
wheel.y_offset = wheel.y_offset_hang;
wheel.steering_angle = 0.0f;
wheel.steering_angle_display = 0.0f;
wheel.rotacc = 0.0f;
// set normal force history to their default values
for (int i = vVP_NORMAL_FORCE_HISTORY_LENGTH; i--; )
{
wheel.normal_force_history[i] = vVP_GRAVITATIONAL_ACCELERATION / m_inv_mass / m_num_wheels;
}
}
m_next_normal_force_history_idx = 0;
m_gravity_override_timer = 0.0f;
m_gravity_override_fraction = 1.0f;
m_in_flip = false;
// grab a pointer to the vehicle's skeleton
mp_skeleton_component = static_cast< CSkeletonComponent* >(GetObject()->GetComponent(CRC_SKELETON));
Dbg_MsgAssert(mp_skeleton_component, ("Vehicle component has no peer skeleton component."));
Dbg_MsgAssert(mp_skeleton_component->GetSkeleton()->GetNumBones() == static_cast< int >(2 + m_num_wheels), ("Vehicle component's peer skeleton component has an unexpected number of bones"));
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::RefreshFromStructure( Script::CStruct* pParams )
{
Dbg_Assert(false);
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::Finalize ( )
{
mp_input_component = GetInputComponentFromObject(GetObject());
mp_model_component = GetModelComponentFromObject(GetObject());
Dbg_Assert(mp_input_component);
Dbg_Assert(mp_model_component);
// extract information about the car from the model
Dbg_MsgAssert(m_num_wheels == vVP_NUM_WHEELS, ("Number of wheels must equal CVehicleComponent::vVP_NUM_WHEELS"));
CModelComponent* p_model_component = static_cast< CModelComponent* >(GetModelComponentFromObject(GetObject()));
Dbg_Assert(p_model_component);
Nx::CModel* p_model = p_model_component->GetModel();
Dbg_Assert(p_model);
Nx::CHierarchyObject* p_hierarchy_objects = p_model->GetHierarchy();
Dbg_Assert(p_hierarchy_objects);
for (int n = vVP_NUM_WHEELS; n--; )
{
SWheel& wheel = mp_wheels[n];
Mth::Matrix wheel_matrix = (p_hierarchy_objects + 2 + n)->GetSetupMatrix();
// rotate out of max coordinate system
wheel.pos[X] = -wheel_matrix[W][X];
wheel.pos[Y] = wheel_matrix[W][Z];
wheel.pos[Z] = -wheel_matrix[W][Y];
wheel.pos[W] = 1.0f;
}
Mth::Matrix body_matrix = (p_hierarchy_objects + 1)->GetSetupMatrix();
m_body_pos[X] = -body_matrix[W][X];
m_body_pos[Y] = body_matrix[W][Z];
m_body_pos[Z] = -body_matrix[W][Y];
m_body_pos[W] = 1.0f;
// extract axle and wheelbase information
int left_steering_tire = -1;
int right_steering_tire = -1;
int rear_tire = -1;
for (int n = vVP_NUM_WHEELS; n--; )
{
switch (mp_wheels[n].steering)
{
case SWheel::LEFT:
left_steering_tire = n;
break;
case SWheel::RIGHT:
right_steering_tire = n;
break;
case SWheel::FIXED:
rear_tire = n;
break;
}
}
Dbg_Assert(left_steering_tire != -1);
Dbg_Assert(right_steering_tire != -1);
Dbg_Assert(rear_tire != -1);
m_cornering_wheelbase = Mth::Abs(
(p_hierarchy_objects + 2 + left_steering_tire)->GetSetupMatrix()[W][Y] - (p_hierarchy_objects + 2 + rear_tire)->GetSetupMatrix()[W][Y]
);
m_cornering_axle_length = Mth::Abs(
(p_hierarchy_objects + 2 + left_steering_tire)->GetSetupMatrix()[W][X] - (p_hierarchy_objects + 2 + right_steering_tire)->GetSetupMatrix()[W][X]
);
// calculate the true wheel positions based on the desired wheel positions with vehicle weight applied
for (int n = m_num_wheels; n--; )
{
SWheel& wheel = mp_wheels[n];
// place the wheel in the desired location based on the vehicle mass and the suspension spring rate; the suspension feeler starts at two
// radii above the desired position
float desired_y_pos = wheel.pos[Y];
wheel.pos[Y] = desired_y_pos + 2.0f * wheel.radius;
wheel.y_offset_hang = -wheel.pos[Y] + desired_y_pos - vVP_GRAVITATIONAL_ACCELERATION / (m_inv_mass * m_num_wheels * wheel.spring);
}
// determine the lowest collider point
float lowest_collider_height = mp_colliders[0].body.m_corner[Y];
for (int n = vVP_NUM_COLLIDERS; n--; )
{
lowest_collider_height = Mth::Min(mp_colliders[n].body.m_corner[Y], lowest_collider_height);
lowest_collider_height = Mth::Min(mp_colliders[n].body.m_corner[Y] + mp_colliders[n].body.m_first_edge[Y], lowest_collider_height);
lowest_collider_height = Mth::Min(mp_colliders[n].body.m_corner[Y] + mp_colliders[n].body.m_second_edge[Y], lowest_collider_height);
}
// ready the skater for control
mp_skater = Mdl::Skate::Instance()->GetLocalSkater();
mp_skater_core_physics_component = GetSkaterCorePhysicsComponentFromObject(mp_skater);
mp_skater_trigger_component = GetTriggerComponentFromObject(mp_skater);
Dbg_Assert(mp_skater_core_physics_component);
Dbg_Assert(mp_skater_trigger_component);
if (!m_skater_visible)
{
mp_skater->Hide(true);
}
else
{
mp_skater_animation_component = GetAnimationComponentFromObject(mp_skater);
Dbg_Assert(mp_skater_animation_component);
}
// calculate the center of mass we will use based on the wheel locations
Mth::Vector center_of_mass(0.0f, 0.0f, 0.0f, 0.0f);
for (int n = m_num_wheels; n--; )
{
center_of_mass += mp_wheels[n].pos;
}
center_of_mass /= m_num_wheels;
center_of_mass[Y] = lowest_collider_height;
center_of_mass[W] = 0.0f;
// move wheels and colliders so that they are relative to the correct center of mass
for (int n = m_num_wheels; n--; )
{
mp_wheels[n].pos -= center_of_mass;
}
for (int n = vVP_NUM_COLLIDERS; n--; )
{
mp_colliders[n].body.m_corner -= center_of_mass;
}
m_body_pos -= center_of_mass;
m_skater_pos -= center_of_mass;
update_dependent_variables();
GetObject()->SetPos(m_pos);
GetObject()->SetVel(m_vel);
GetObject()->SetMatrix(m_orientation_matrix);
GetObject()->SetDisplayMatrix(GetObject()->GetMatrix());
update_skeleton();
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::Update()
{
m_reset_this_frame = false;
if (!m_update_suspension_only)
{
get_input();
}
else
{
zero_input();
}
if (m_state == ASLEEP)
{
// reduced work set if we're asleep
GetObject()->SetDisplayMatrix(GetObject()->GetMatrix());
update_steering_angles();
if (m_controls.brake)
{
for (int n = m_num_wheels; n--; )
{
mp_wheels[n].rotvel = 0.0f;
}
}
update_wheel_dynamic_state();
update_skeleton();
draw_shadow();
control_skater();
return;
}
int num_time_steps;
float frame_length = Tmr::FrameLength();
if (in_artificial_collision())
{
m_artificial_collision_timer -= frame_length;
}
// count down timers
if (m_gravity_override_timer != 0.0f)
{
m_gravity_override_timer -= frame_length;
if (m_gravity_override_timer <= 0.0f || m_num_wheels_in_contact > 1)
{
MESSAGE("Ending vehicle gravity override");
m_gravity_override_timer = 0.0f;
m_gravity_override_fraction = 1.0f;
}
}
// the physics is unstable at low frame rates, so we take multiple physics steps during long frame; if vehicle physics is a significant fraction
// of CPU time, this could exacerbate whatever frame rate problems are occuring
if (frame_length >= (1.0f / 30.0f))
{
num_time_steps = static_cast< int >(ceilf(frame_length / (1.0f / 60.0f)));
if (num_time_steps > 6)
{
num_time_steps = 6;
}
m_time_step = frame_length / num_time_steps;
Dbg_Message("CVehicleComponent::Update: using %i steps this frame", num_time_steps);
}
else
{
num_time_steps = 1;
m_time_step = frame_length;
}
for (int step = num_time_steps; step--; )
{
update_dynamic_state();
m_force.Set(0.0f, 0.0f, 0.0f);
m_torque.Set(0.0f, 0.0f, 0.0f);
for (int n = m_num_wheels; n--; )
{
mp_wheels[n].rotacc = 0.0f;
}
if (!m_update_suspension_only)
{
apply_environment_collisions();
if (reset_this_frame()) return;
}
// teleport wheels to the ground, if within reach of the suspension
update_wheel_heights();
if (reset_this_frame()) return;
// update the steering wheels' angles
update_steering_angles();
// damp out rotations to create a more drivable car
damp_rotation();
// accumulate all forces and torques on the body and wheels
accumulate_forces();
/////////////
// update state observers
if (m_num_wheels_in_contact > 0)
{
m_air_time = 0.0f;
}
else
{
m_air_time += m_time_step;
}
if (m_num_wheels_in_contact > 0 || m_max_normal_collision_impulse > 0.0f)
{
m_air_time_no_collision = 0.0f;
}
else
{
m_air_time_no_collision += m_time_step;
}
}
if (m_update_suspension_only) return;
// draw debug lines
draw_debug_rendering();
// update object's position and orientation
GetObject()->SetPos(m_pos);
GetObject()->SetVel(m_vel);
GetObject()->SetMatrix(m_orientation_matrix);
GetObject()->SetDisplayMatrix(GetObject()->GetMatrix());
consider_sleeping();
update_skeleton();
// Hack to draw shadow
draw_shadow();
#if 0
Mth::Vector forward(0.0f, 0.0f, 1.0f);
forward = m_orientation_matrix.Rotate(forward);
float vel = IPS_TO_MPH(Mth::Abs(Mth::DotProduct(m_vel, forward)));
PERIODIC(60) DUMPF(vel);
#endif
// HACK: get player proximity checks, triggers, driving animations, and the like working
control_skater();
}
/******************************************************************/
/* */
/* */
/******************************************************************/
CBaseComponent::EMemberFunctionResult CVehicleComponent::CallMemberFunction( uint32 Checksum, Script::CStruct* pParams, Script::CScript* pScript )
{
switch ( Checksum )
{
// @script | Vehicle_Kick | kicks the vehicle with a force and torque
case CRCC(0x93b713a6, "Vehicle_Kick"):
{
Mth::Vector linear;
Mth::Vector angular;
m_state = AWAKE;
m_consider_sleeping_count = 0;
pParams->GetVector("linear", &linear);
pParams->GetVector("angular", &angular);
linear = m_orientation_matrix.Rotate(linear);
angular = m_orientation_matrix.Rotate(angular);
m_mom += linear / m_inv_mass;
m_rotmom += angular / m_inv_moment_body[X];
update_velocities();
break;
}
// @script | Vehicle_Wake | wakes the vehicle if asleep
case CRCC(0xa80a0d36, "Vehicle_Wake"):
m_state = AWAKE;
m_consider_sleeping_count = 0;
break;
// @script | Vehicle_MoveToRestart | teleport the vehicle to the restart node
case CRCC(0x4b0b27dd, "Vehicle_MoveToRestart"):
{
uint32 node_name_checksum;
if (pParams->GetChecksum(NO_NAME, &node_name_checksum))
{
// a node is specifically specified
MoveToNode(SkateScript::GetNode(SkateScript::FindNamedNode(node_name_checksum, Script::ASSERT)));
}
else
{
// find a linked restart node
int node = pScript->mNode;
Dbg_MsgAssert(node != -1,( "Vehicle_MoveToRestart called from non-node script with no target node specified"));
{
int num_links = SkateScript::GetNumLinks(node);
for (int n = 0; n < num_links; n++)
{
int linked_node = SkateScript::GetLink(node, n);
if (IsRestart(linked_node))
{
MoveToNode(SkateScript::GetNode(linked_node));
return CBaseComponent::MF_TRUE;
}
}
if (Ed::CParkEditor::Instance()->UsingCustomPark())
{
MoveToNode(SkateScript::GetNode(Mdl::Skate::Instance()->find_restart_node(0)));
}
else
{
Dbg_MsgAssert(0, ("Vehicle_MoveToRestart called but node %d not linked to restart", node));
}
}
}
break;
}
// @script | Vehicle_PlaceBeforeCamera | moves the object before the active camera
case CRCC(0xc33608e4, "Vehicle_PlaceBeforeCamera"):
{
Gfx::Camera* camera = Nx::CViewportManager::sGetActiveCamera(0);
if (camera)
{
Mth::Vector& cam_pos = camera->GetPos();
Mth::Matrix& cam_mat = camera->GetMatrix();
m_pos = cam_pos;
m_pos += cam_mat[Y] * 12.0f * 12.0f;
m_pos -= cam_mat[Z] * 12.0f * 12.0f;
GetObject()->SetPos(m_pos);
m_orientation_matrix[X] = -cam_mat[X];
m_orientation_matrix[Y] = cam_mat[Y];
m_orientation_matrix[Z] = -cam_mat[Z];
m_orientation = m_orientation_matrix;
m_orientation.Normalize();
m_orientation.GetMatrix(m_orientation_matrix);
m_orientation_matrix[W].Set();
GetObject()->SetMatrix(m_orientation_matrix);
update_dependent_variables();
}
break;
}
// @script | Vehicle_AdjustGravity | adjusts effective gravity for a given duration or until the vehicle has two or more wheels on the ground,
// whichever occurs first
// @parm float | Percent | Percent of standard gravity.
// @parm float | Duration | Duration in seconds over which to override gravity.
case CRCC(0xdb35aad8, "Vehicle_AdjustGravity"):
pParams->GetFloat(CRCD(0x9e497fc6, "Percent"), &m_gravity_override_fraction, Script::ASSERT);
m_gravity_override_fraction *= 1.0f / 100.0f;
pParams->GetFloat(CRCD(0x79a07f3f, "Duration"), &m_gravity_override_timer, Script::ASSERT);
Dbg_MsgAssert(m_gravity_override_timer > 0.0f, ("Vehicle_AdjustGravity must have positive Duration"));
MESSAGE("Initiating vehicle gravity override");
break;
// @script | Vehicle_ForceBrake | forces on the brake
// case CRCC(0x1ad6b6bc, "Vehicle_ForceBrake"):
// m_force_brake = true;
// break;
// @script | Vehicle_HandbrakeActive | returns true if the car has a handbrake
case CRCC(0x5008b253, "Vehicle_HandbrakeActive"):
return m_force_brake ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
// @script | Vehicle_AllWheelsAreInContact | returns true if all of the wheels are in contact with geo
case CRCC(0x279602ed, "Vehicle_AllWheelsAreInContact"):
return m_num_wheels_in_contact == m_num_wheels ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
// @script | Vehicle_LostCollision | lost a net collision; respond appropriately
case CRCC(0x4c73526b, "Vehicle_LostCollision"):
{
Mth::Vector offset;
pParams->GetVector(CRCD(0xa6f5352f, "Offset"), &offset, Script::ASSERT);
ApplyArtificialCollision(offset);
break;
}
// @script | Vehicle_IsSkaterVisible | true if skater should be visible while driving
case CRCC(0x81faac21, "Vehicle_IsSkaterVisible"):
return m_skater_visible ? CBaseComponent::MF_TRUE : CBaseComponent::MF_FALSE;
default:
return CBaseComponent::MF_NOT_EXECUTED;
}
return CBaseComponent::MF_TRUE;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::GetDebugInfo ( Script::CStruct *p_info )
{
#ifdef __DEBUG_CODE__
Dbg_MsgAssert(p_info, ("NULL p_info sent to CVehicleComponent::GetDebugInfo"));
p_info->AddVector("m_pos", m_pos);
p_info->AddVector("m_mom", m_mom);
p_info->AddVector("m_rotmom", m_rotmom);
p_info->AddFloat("m_orientation", m_orientation.GetScalar());
p_info->AddVector("m_orientation", m_orientation.GetVector());
p_info->AddVector("m_force", m_force);
p_info->AddVector("m_torque", m_torque);
p_info->AddVector("m_vel", m_vel);
p_info->AddVector("m_rotvel", m_rotvel);
p_info->AddVector("m_suspension_center_of_mass", m_suspension_center_of_mass);
p_info->AddFloat("mass", vVP_GRAVITATIONAL_ACCELERATION / m_inv_mass);
p_info->AddVector("moment_of_inertia",
vVP_GRAVITATIONAL_ACCELERATION / m_inv_moment_body[X],
vVP_GRAVITATIONAL_ACCELERATION / m_inv_moment_body[Y],
vVP_GRAVITATIONAL_ACCELERATION / m_inv_moment_body[Z]
);
p_info->AddFloat("body_restitution", m_body_restitution);
p_info->AddFloat("body_friction", m_body_friction);
p_info->AddFloat("body_spring", m_body_spring);
p_info->AddFloat("collision_control", m_collision_control);
p_info->AddFloat("in_air_slerp_velocity_cutoff", m_in_air_slerp_vel_cutoff);
p_info->AddFloat("in_air_slerp_time_delay", m_in_air_slerp_time_delay);
p_info->AddFloat("in_air_slerp_strength", m_in_air_slerp_strength);
p_info->AddChecksum("vert_correction", m_vert_correction ? CRCD(0x203b372, "true") : CRCD(0xd43297cf, "false"));
p_info->AddFloat("wipeout_body_friction", m_body_wipeout_friction);
p_info->AddFloat("max_steering_angle", RADIANS_TO_DEGREES(m_max_steering_angle));
p_info->AddFloat("cornering_wheelbase", m_cornering_wheelbase);
p_info->AddFloat("cornering_axle_length", m_cornering_axle_length);
p_info->AddFloat("constant_rotational_damping", m_const_rotvel_damping);
p_info->AddFloat("quadratic_rotational_damping", m_quad_rotvel_damping);
Script::CStruct* p_engine_info = new Script::CStruct;
p_engine_info->AddFloat("drive_torque", m_engine.drive_torque / 12.0f);
p_engine_info->AddFloat("drag_torque", m_engine.drag_torque / 12.0f);
p_engine_info->AddFloat("upshift_rpm", RADIANS_PER_SECOND_TO_RPM(m_engine.upshift_rotvel));
p_engine_info->AddFloat("differential_ratio", m_engine.differential_ratio);
p_engine_info->AddFloat("reverse_torque_ratio", m_engine.reverse_torque_ratio);
Script::CArray* p_engine_gear_ratio_info = new Script::CArray;
p_engine_gear_ratio_info->SetSizeAndType(m_engine.num_gears, ESYMBOLTYPE_FLOAT);
for (int n = m_engine.num_gears; n--; )
{
p_engine_gear_ratio_info->SetFloat(n, m_engine.p_gear_ratios[n]);
}
p_engine_info->AddArrayPointer("gear_ratios", p_engine_gear_ratio_info);
p_info->AddStructurePointer("m_engine", p_engine_info);
Script::CArray* p_wheels_info = new Script::CArray;
p_wheels_info->SetSizeAndType(m_num_wheels, ESYMBOLTYPE_STRUCTURE);
for (int n = m_num_wheels; n--; )
{
SWheel& wheel = mp_wheels[n];
Script::CStruct* p_local_info = new Script::CStruct;
uint32 state_checksums[] =
{
CRCC(0xba2b31d7, "NO_STATE"),
CRCC(0xcd419ee6, "OUT_OF_CONTACT"),
CRCC(0x927c0fed, "UNDER_GRIPPING"),
CRCC(0xe9bc148a, "GRIPPING"),
CRCC(0x26acccc8, "SLIPPING"),
CRCC(0xa91003cc, "SKIDDING"),
CRCC(0x2e7ef449, "HANDBRAKE_THROTTLE"),
CRCC(0xbf6d6529, "HANDBRAKE_LOCKED"),
};
uint32 steering_checksums[] =
{
CRCC(0x613631cd, "FIXED"),
CRCC(0x85981897, "LEFT"),
CRCC(0x4b358aeb, "RIGHT")
};
p_local_info->AddChecksum("state", state_checksums[wheel.state]);
p_local_info->AddFloat("rotvel", wheel.rotvel);
p_local_info->AddFloat("y_offset", wheel.y_offset);
p_local_info->AddFloat("steering_angle", RADIANS_TO_DEGREES(wheel.steering_angle));
p_local_info->AddFloat("steering_angle_display", RADIANS_TO_DEGREES(wheel.steering_angle_display));
p_local_info->AddFloat("rotacc", wheel.rotacc);
p_local_info->AddFloat("orientation", wheel.orientation);
p_local_info->AddVector("pos", wheel.pos);
p_local_info->AddFloat("y_offset_hang", wheel.y_offset_hang);
p_local_info->AddFloat("max_draw_y_offset", wheel.max_draw_y_offset);
p_local_info->AddChecksum("steering", steering_checksums[wheel.steering]);
p_local_info->AddInteger("drive", wheel.drive);
p_local_info->AddFloat("radius", wheel.radius);
p_local_info->AddFloat("moment", vVP_GRAVITATIONAL_ACCELERATION / wheel.inv_moment);
p_local_info->AddFloat("spring_rate", wheel.spring);
p_local_info->AddFloat("damping_rate", wheel.damping);
p_local_info->AddFloat("static_friction", wheel.static_friction);
p_local_info->AddFloat("dynamic_friction", wheel.dynamic_friction);
p_local_info->AddFloat("handbrake_throttle_friction", wheel.handbrake_throttle_friction);
p_local_info->AddFloat("handbrake_locked_friction", wheel.handbrake_locked_friction);
p_local_info->AddFloat("min_static_grip_velocity", IPS_TO_MPH(wheel.min_static_velocity));
p_local_info->AddFloat("max_static_grip_velocity", IPS_TO_MPH(wheel.max_static_velocity));
p_local_info->AddFloat("min_dynamic_grip_velocity", IPS_TO_MPH(wheel.min_dynamic_velocity));
p_local_info->AddFloat("brake_torque", wheel.brake.torque / 12.0f);
p_local_info->AddFloat("handbrake_torque", wheel.brake.handbrake_torque / 12.0f);
p_wheels_info->SetStructure(n, p_local_info);
}
p_info->AddArrayPointer("mp_wheels", p_wheels_info);
Script::CStruct* p_controls_info = new Script::CStruct;
p_controls_info->AddFloat("steering", m_controls.steering);
p_controls_info->AddInteger("throttle", m_controls.throttle);
p_controls_info->AddInteger("brake", m_controls.brake);
p_controls_info->AddInteger("handbrake", m_controls.handbrake);
p_controls_info->AddInteger("reverse", m_controls.reverse);
p_info->AddStructurePointer("m_controls", p_controls_info);
if (m_skater_visible)
{
p_info->AddVector(CRCD(0xec86ef7a, "skater_pos"), m_skater_pos);
p_info->AddChecksum(CRCD(0xda75a33e, "skater_anim"), m_skater_anim);
}
// we call the base component's GetDebugInfo, so we can add info from the common base component
CBaseComponent::GetDebugInfo(p_info);
#endif
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::MoveToNode ( Script::CStruct* p_node )
{
// move to the position relative to the given node that a ped car would be at
Mth::Vector restart_pos;
SkateScript::GetPosition(p_node, &restart_pos);
Mth::Vector restart_angles;
SkateScript::GetAngles(p_node, &restart_angles);
Mth::Matrix restart_matrix;
restart_matrix.SetFromAngles(restart_angles);
// calculate appropriate offset from the ground based on estimated wheel offsets
float avg_ground_offset = 0.0f;
for (int n = vVP_NUM_WHEELS; n--; )
{
avg_ground_offset += mp_wheels[n].y_offset_hang + mp_wheels[n].pos[Y] + vVP_GRAVITATIONAL_ACCELERATION / (m_inv_mass * m_num_wheels * mp_wheels[n].spring);
avg_ground_offset -= mp_wheels[n].radius;
}
avg_ground_offset /= vVP_NUM_WHEELS;
// find ground height
CFeeler feeler(restart_pos + Mth::Vector(0.0f, 24.0f, 0.0f), restart_pos + Mth::Vector(0.0f, -240.0f, 0.0f));
if (feeler.GetCollision(false))
{
restart_pos[Y] = feeler.GetPoint()[Y] - avg_ground_offset;
}
// ped cars have their origin between the rear wheels
int rear_wheel_idx = -1;
for (int n = vVP_NUM_WHEELS; n--; )
{
if (mp_wheels[n].steering == SWheel::FIXED)
{
rear_wheel_idx = n;
break;
}
}
Dbg_Assert(rear_wheel_idx != -1);
restart_pos -= restart_matrix[Z] * mp_wheels[rear_wheel_idx].pos[Z];
// move the car to the restart position and allow it to settle on its suspension
m_pos = restart_pos;
m_orientation = restart_matrix;
m_mom.Set(0.0f, 0.0f, 0.0f);
m_rotmom.Set(0.0f, 0.0f, 0.0f);
// zero wheels
m_update_suspension_only = true;
for (int n = 60; n--; )
{
// lock wheels each frame
for (int n = m_num_wheels; n--; )
{
mp_wheels[n].rotvel = 0.0f;
}
Update();
}
m_update_suspension_only = false;
m_mom.Set(0.0f, 0.0f, 0.0f);
m_rotmom.Set(0.0f, 0.0f, 0.0f);
m_reset_this_frame = true;
m_state = ASLEEP;
// update object's position and orientation
GetObject()->SetPos(m_pos);
GetObject()->SetVel(m_vel);
GetObject()->SetMatrix(m_orientation_matrix);
GetObject()->SetDisplayMatrix(GetObject()->GetMatrix());
update_skeleton();
control_skater();
GetObject()->SetTeleported();
mp_skater->SetTeleported();
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::ApplyArtificialCollision ( const Mth::Vector& offset )
{
Mth::Vector impulse_direction = offset;
impulse_direction[Y] = 0.0f;
impulse_direction.Normalize();
Mth::Vector sideways(-impulse_direction[Z], 0.0f, impulse_direction[X]);
float impulse_forward = (1.0f + Mth::PlusOrMinus(0.5f)) * Script::GetFloat(CRCD(0x3db3fa83, "vehicle_physics_netcoll_forward_impulse"));
float impulse_sideways = Mth::PlusOrMinus(1.5f) * Script::GetFloat(CRCD(0x39e99fec, "vehicle_physics_netcoll_sideways_impulse"));
float impulse_upwards = (1.0f + Mth::PlusOrMinus(0.2f)) * Script::GetFloat(CRCD(0x738500fd, "vehicle_physics_netcoll_upwards_impulse"));
Mth::Vector impulse = impulse_forward * impulse_direction + impulse_sideways * sideways;
impulse[Y] += impulse_upwards;
float rotate_spin = Mth::PlusOrMinus(1.5f) * Script::GetFloat(CRCD(0x14ddb3ef, "vehicle_physics_netcoll_spin_impulse"));
float rotate_flip = (1.0f + Mth::PlusOrMinus(0.8f)) * Script::GetFloat(CRCD(0x4088e6ec, "vehicle_physics_netcoll_flip_impulse"));
Mth::Vector rotate = rotate_flip * sideways;
rotate[Y] += rotate_spin;
m_mom += impulse / m_inv_mass;
m_rotmom += rotate / m_inv_moment_body[X];
update_velocities();
m_artificial_collision_timer = Script::GetFloat(CRCD(0x771922a6, "vehicle_physics_artificial_collision_duration"));
m_state = AWAKE;
m_consider_sleeping_count = 0;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::update_flip ( )
{
m_orientation.GetMatrix(m_orientation_matrix);
m_orientation_matrix[W].Set();
if (m_orientation_matrix[Y][Y] > 0.999f) // || !m_controls.throttle)
{
m_in_flip = false;
return;
}
bool flipping_appropriate_this_frame = m_vel.LengthSqr() < Mth::Sqr(300.0f)
&& m_rotvel.LengthSqr() < Mth::Sqr(Mth::PI / 4.0f)
&& m_max_normal_collision_impulse > 0.0f
&& m_orientation_matrix[Y][Y] < 0.25f
&& (m_num_wheels_in_contact == 0 || m_orientation_matrix[Y][Y] < 0.0f)
&& m_controls.throttle;
if (!m_in_flip && !flipping_appropriate_this_frame) return;
// starting a flip
if (!m_in_flip)
{
m_mom.Set();
m_rotmom.Set();
m_in_flip = true;
m_flip_start_time_stamp = Tmr::GetTime();
}
// check for maximum flip duration
if (Tmr::ElapsedTime(m_flip_start_time_stamp) > vVP_FLIP_DURATION + vVP_FLIP_DELAY)
{
m_in_flip = false;
return;
}
// put a short delay before the flip actually has an effect
if (Tmr::ElapsedTime(m_flip_start_time_stamp) < vVP_FLIP_DELAY)
{
return;
}
// setup this frame's slerp
Mth::Matrix goal_orientation;
goal_orientation[Z] = m_orientation_matrix[Z];
goal_orientation[Z][Y] = 0.0f;
goal_orientation[Z].Normalize();
goal_orientation[Y].Set(0.0f, 1.0f, 0.0f);
goal_orientation[X].Set(goal_orientation[Z][Z], 0.0f, -goal_orientation[Z][X]);
Mth::SlerpInterpolator slerper;
slerper.setMatrices(&m_orientation_matrix, &goal_orientation);
// calculate the new orientation matrix
slerper.getMatrix(
&m_orientation_matrix,
Mth::ClampMax(vVP_FLIP_RATE * Tmr::FrameLength() / slerper.getRadians(), 1.0f)
);
m_orientation = m_orientation_matrix;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::update_wheel_from_structure ( SWheel& wheel, Script::CStruct* p_wheel_struct )
{
uint32 checksum;
// wheel's steering type
if (p_wheel_struct->ContainsComponentNamed(CRCD(0x7560e63, "steering")))
{
p_wheel_struct->GetChecksum(CRCD(0x7560e63, "steering"), &checksum);
switch (checksum)
{
case CRCC(0x85981897, "left"):
wheel.steering = SWheel::LEFT;
break;
case CRCC(0x4b358aeb, "right"):
wheel.steering = SWheel::RIGHT;
break;
case CRCC(0x613631cd, "fixed"):
wheel.steering = SWheel::FIXED;
break;
default:
Dbg_MsgAssert(false, ("Bad wheel steering setting"));
}
}
// if the wheel is a drive wheel
if (p_wheel_struct->ContainsComponentNamed(CRCD(0x97e20a70, "drive")))
{
p_wheel_struct->GetChecksum(CRCD(0x97e20a70, "drive"), &checksum);
switch (checksum)
{
case CRCC(0x8a18ca56, "yes"):
wheel.drive = true;
break;
case CRCC(0x9855d7e0, "no"):
wheel.drive = false;
break;
default:
Dbg_MsgAssert(false, ("Bad wheel drive setting"));
}
}
// suspension spring rate (lbs / in)
// strength of the suspension spring; multiplied by the compresion to get the spring's force; must be scaled
// with the vehicle weight; large spring rates give stiffer suspensions; large values may cause instability in
// physics model
p_wheel_struct->GetFloat(CRCD(0x9e931fdf, "spring_rate"), &wheel.spring);
// suspension damping; bump rate (lbs s / in)
// bounce damping strength of the suspension; smaller values cause bouncier suspensions; very large values may
// cause instability in the physics model
p_wheel_struct->GetFloat(CRCD(0xafa960ff, "damping_rate"), &wheel.damping);
// wheel radius (in)
// larger radius wheels are harder for the engine to rotate and the brake and rolling resistance to stop
p_wheel_struct->GetFloat(CRCD(0xc48391a5, "radius"), &wheel.radius);
if (p_wheel_struct->ContainsComponentNamed(CRCD(0xca73775d, "moment")))
{
p_wheel_struct->GetFloat(CRCD(0xca73775d, "moment"), &wheel.inv_moment);
wheel.inv_moment = vVP_GRAVITATIONAL_ACCELERATION / wheel.inv_moment;
}
p_wheel_struct->GetFloat(CRCD(0xd87d0183, "max_draw_y_offset"), &wheel.max_draw_y_offset);
// the following parameters affect the shape of the tire's friction curve
// the friction curve gives the strength of the frictional force as a function of the tire's slip velocity; our
// simple curve rises linearly from zero at zero slip velocity, reaches static_friction at a slip velocity of
// min_static_velocity, remains at static_friction up to a slip velocity of max_static_velocity, drops linearly
// to dynamic_friction until a slip velocity of min_dynamic_velocity, and then remains constant at dynamic_friction
// normalized friction strength when the wheel is not skidding
// low values will cause the vehicle to be spin-out and skid easily; high values cause the tires to feel sticky;
// real world values are on the order of 1.3; fun values are much higher
p_wheel_struct->GetFloat(CRCD(0xf36b97c8, "static_friction"), &wheel.static_friction);
// normalized friction strength when the wheel is skidding
// should be lower than static_friction; values much lower than static_friction cause the vehicle to be unforgiving;
// once you lose grip, it is harder to regain control of the vehicle; values near static_friction cause the vehicle
// to be very forgiving, with skidding causing little loss of handling
p_wheel_struct->GetFloat(CRCD(0x674852f6, "dynamic_friction"), &wheel.dynamic_friction);
// normalized friction strength when the handbrake is applied
// currently specially tuned in script to force a fishtail when the handbrake and throttle are both applied and spin-outs when only handbrake is applied
p_wheel_struct->GetFloat(CRCD(0x52242919, "handbrake_throttle_friction"), &wheel.handbrake_throttle_friction);
p_wheel_struct->GetFloat(CRCD(0x6d615f67, "handbrake_locked_friction"), &wheel.handbrake_locked_friction);
// slip speed below which tires undergrip (mph)
// should be around 1 to 2 mph
if (p_wheel_struct->ContainsComponentNamed(CRCD(0x60b2c097, "min_static_grip_velocity")))
{
p_wheel_struct->GetFloat(CRCD(0x60b2c097, "min_static_grip_velocity"), &wheel.min_static_velocity);
wheel.min_static_velocity = MPH_TO_IPS(wheel.min_static_velocity);
}
// maximum slip speed before tires begin to lose their grip (mph)
// should be larger than min_static_velocity; with a low value the vehicle will skid more readily; a high value
// causes there to be a large range of slip velocties with maximum tire grip
if (p_wheel_struct->ContainsComponentNamed(CRCD(0xbb0d9370, "max_static_grip_velocity")))
{
p_wheel_struct->GetFloat(CRCD(0xbb0d9370, "max_static_grip_velocity"), &wheel.max_static_velocity);
wheel.max_static_velocity = MPH_TO_IPS(wheel.max_static_velocity);
}
// minimum slip speed before tires begin skidding (imph)
// should be larger than max_static_velocity; with a low values, tires are very unforgiving and begin to skid as soon
// as they begin to slip; with a larger value, the loss of control and onset of skidding will be more gradual
if (p_wheel_struct->ContainsComponentNamed(CRCD(0xb71803ad, "min_dynamic_grip_velocity")))
{
p_wheel_struct->GetFloat(CRCD(0xb71803ad, "min_dynamic_grip_velocity"), &wheel.min_dynamic_velocity);
wheel.min_dynamic_velocity = MPH_TO_IPS(wheel.min_dynamic_velocity);
}
// torque which the brake exerts on the wheel (ft-lbs)
if (p_wheel_struct->ContainsComponentNamed(CRCD(0x3bccbadc, "brake_torque")))
{
p_wheel_struct->GetFloat(CRCD(0x3bccbadc, "brake_torque"), &wheel.brake.torque);
wheel.brake.torque *= 12.0f;
}
// torque which the handbrake exerts on the wheel when steering is straight and the throttle is down (ft-lbs)
if (p_wheel_struct->ContainsComponentNamed(CRCD(0x9439d144, "handbrake_torque")))
{
p_wheel_struct->GetFloat(CRCD(0x9439d144, "handbrake_torque"), &wheel.brake.handbrake_torque);
wheel.brake.handbrake_torque *= 12.0f;
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::update_dynamic_state ( )
{
// update body state
// calculate accelerations
Mth::Vector acc = m_inv_mass * m_force;
Mth::Vector rotacc = m_inv_moment.Rotate(m_torque);
// update state variables
m_mom += m_time_step * m_force;
update_pos_with_uber_frig(m_time_step * (m_vel + 0.5f * m_time_step * acc));
Mth::Vector delta_orientation_vector = -0.5f * m_time_step * (m_rotvel + 0.5f * m_time_step * rotacc);
Mth::Quat delta_orientation = m_orientation * Mth::Quat(delta_orientation_vector[X], delta_orientation_vector[Y], delta_orientation_vector[Z], 0.0f);
m_orientation += delta_orientation;
m_orientation.Normalize();
m_rotmom += m_time_step * m_torque;
update_wheel_dynamic_state();
// if we're in the air and moving fast enough horizontally
if (m_num_wheels_in_contact == 0 && m_max_normal_collision_impulse == 0.0f)
{
slerp_to_face_velocity();
}
update_flip();
update_dependent_variables();
#if 0
PERIODIC(60) {
float K = 0.5f * Mth::DotProduct(m_vel, m_mom) + 0.5f * Mth::DotProduct(m_rotvel, m_rotmom);
float U = vVP_GRAVITATIONAL_ACCELERATION * m_pos[Y] / m_inv_mass;
DUMPF(K + U);
Mth::Vector a, b;
a = m_rotmom;
b = m_rotvel;
DUMPF(Mth::DotProduct(a.Normalize(), b.Normalize()));
// DUMPF(m_rotvel.Length());
// DUMPV(m_rotvel);
}
#endif
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::update_wheel_dynamic_state ( )
{
for (int n = m_num_wheels; n--; )
{
SWheel& wheel = mp_wheels[n];
// wheel rotvel updates as torques are applied; thus, wheel rotvel is currently the frame's final rotvel
wheel.orientation += m_time_step * (wheel.rotvel - 0.5f * m_time_step * wheel.rotacc);
if (wheel.orientation > (200.0f * Mth::PI))
{
wheel.orientation -= (100.0f * Mth::PI);
}
else if (wheel.orientation < 0.0f)
{
wheel.orientation += (100.0f * Mth::PI);
}
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::update_dependent_variables ( )
{
m_orientation.GetMatrix(m_orientation_matrix);
m_orientation_matrix[W].Set();
m_suspension_center_of_mass_world = m_orientation_matrix.Rotate(m_suspension_center_of_mass);
calculate_inverse_moment();
update_velocities();
// update wheel feeler endpoint positions
for (int n = m_num_wheels; n--; )
{
SWheel& wheel = mp_wheels[n];
wheel.feeler_start_world = m_pos + m_orientation_matrix.Rotate(wheel.pos);
wheel.feeler_end_world = wheel.pos;
wheel.feeler_end_world[Y] += wheel.y_offset_hang - wheel.radius;
wheel.feeler_end_world = m_pos + m_orientation_matrix.Rotate(wheel.feeler_end_world);
}
// update collider world positions
for (int collider_idx = vVP_NUM_COLLIDERS; collider_idx--; )
{
SCollider& collider = mp_colliders[collider_idx];
collider.world.m_corner = m_pos + m_orientation_matrix.Rotate(collider.body.m_corner);
collider.world.m_first_edge = m_orientation_matrix.Rotate(collider.body.m_first_edge);
collider.world.m_second_edge = m_orientation_matrix.Rotate(collider.body.m_second_edge);
collider.first_edge_direction_world = collider.world.m_first_edge / collider.first_edge_length;
collider.second_edge_direction_world = collider.world.m_second_edge / collider.second_edge_length;
}
update_collision_cache();
}
/******************************************************************/
/* */
/* */
/******************************************************************/
float CVehicleComponent::calculate_friction_coefficient ( SWheel& wheel, float velocity ) const
{
// friction based on section 2.4 and 2.7 of Race Car Vehicle Dynamics by Milliken
// the tire friction coefficient curve is very simple; just four concatenated lines
if (m_controls.handbrake)
{
// handbrake friction is a total scam
float friction;
if (m_controls.throttle)
{
friction = wheel.handbrake_throttle_friction;
wheel.state = SWheel::HANDBRAKE_THROTTLE;
}
else
{
friction = wheel.handbrake_locked_friction;
wheel.state = SWheel::HANDBRAKE_LOCKED;
}
if (velocity < wheel.min_static_velocity)
{
// under gripped handbrake "skidding"
wheel.state = SWheel::UNDER_GRIPPING;
return friction * velocity / wheel.min_static_velocity;
}
else
{
// handbrake "skidding"
return friction;
}
}
float multiplier = m_controls.brake ? 2.0f : 1.0f;
if (velocity < wheel.min_static_velocity)
{
// under gripped
wheel.state = SWheel::UNDER_GRIPPING;
return wheel.static_friction * velocity / wheel.min_static_velocity;
}
else if (velocity < wheel.max_static_velocity)
{
// maximum grip
wheel.state = SWheel::GRIPPING;
return multiplier * wheel.static_friction;
}
else if (velocity < wheel.min_dynamic_velocity)
{
// on the verge of skidding
wheel.state = SWheel::SLIPPING;
return multiplier * wheel.dynamic_friction + (wheel.static_friction - wheel.dynamic_friction)
* (velocity - wheel.max_static_velocity) / (wheel.min_dynamic_velocity - wheel.max_static_velocity);
}
else
{
// skidding
wheel.state = SWheel::SKIDDING;
return multiplier * wheel.dynamic_friction;
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::calculate_friction_coefficients ( )
{
// calculate the wheels' friction coefficients this frame
for (int n = m_num_wheels; n--; )
{
SWheel& wheel = mp_wheels[n];
if (wheel.state == SWheel::OUT_OF_CONTACT) continue;
// project the wheel's velocity due to the body's motion into the plane of the contact surface
Mth::Vector projected_vel = wheel.vel_world;
projected_vel.ProjectToPlane(wheel.contact_normal);
// calculate the forward direction of the wheel
Mth::Vector wheel_direction(sinf(wheel.steering_angle), 0.0f, cosf(wheel.steering_angle));
wheel_direction = m_orientation_matrix.Rotate(wheel_direction);
// project that forward direction onto the contact surface
wheel_direction.ProjectToPlane(wheel.contact_normal);
wheel_direction.Normalize();
// NOTE: from this point and up, the calculation is repeated exactly in apply_friction_forces(); we cache the rotated and projected
// wheel direction and the wheel velocity due to body velocity; because the wheel velocity will change before apply_friction_forces(),
// the calculation below must be repeated
wheel.cache_projected_direction = wheel_direction;
wheel.cache_projected_vel = projected_vel;
// subtract the contact patch velocity; positive wheel rotational velocity equals negative contact patch velocity
projected_vel -= wheel.rotvel * wheel.radius * wheel_direction;
// calculate the friction coefficient
wheel.friction_coefficient = calculate_friction_coefficient(wheel, projected_vel.Length());
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::calculate_inverse_moment ( )
{
// m_inv_moment = m_orientation_matrix * m_inv_moment_body_diag_matrix * m_orientation_matrix_transpose
m_inv_moment[X][X] = m_inv_moment_body[X] * m_orientation_matrix[X][X];
m_inv_moment[X][Y] = m_inv_moment_body[Y] * m_orientation_matrix[X][Y];
m_inv_moment[X][Z] = m_inv_moment_body[Z] * m_orientation_matrix[X][Z];
m_inv_moment[Y][X] = m_inv_moment_body[X] * m_orientation_matrix[Y][X];
m_inv_moment[Y][Y] = m_inv_moment_body[Y] * m_orientation_matrix[Y][Y];
m_inv_moment[Y][Z] = m_inv_moment_body[Z] * m_orientation_matrix[Y][Z];
m_inv_moment[Z][X] = m_inv_moment_body[X] * m_orientation_matrix[Z][X];
m_inv_moment[Z][Y] = m_inv_moment_body[Y] * m_orientation_matrix[Z][Y];
m_inv_moment[Z][Z] = m_inv_moment_body[Z] * m_orientation_matrix[Z][Z];
Mth::Matrix orientation_matrix_transpose;
orientation_matrix_transpose.Transpose(m_orientation_matrix);
m_inv_moment = m_inv_moment * orientation_matrix_transpose;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
int CVehicleComponent::determine_effective_gear ( float wheel_rotvel )
{
int gear = 0;
do
{
float engine_rotvel = wheel_rotvel * m_engine.differential_ratio * m_engine.p_gear_ratios[gear];
if (engine_rotvel > m_engine.upshift_rotvel)
{
if (gear < m_engine.num_gears - 1)
{
gear++;
continue;
}
}
return gear;
}
while (true);
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::update_collision_cache ( )
{
Mth::CBBox collision_bbox;
// add wheel location feelers
for (int n = m_num_wheels; n--; )
{
SWheel& wheel = mp_wheels[n];
collision_bbox.AddPoint(wheel.feeler_start_world);
collision_bbox.AddPoint(wheel.feeler_end_world);
}
// add body collision feelers
for (int collider_idx = vVP_NUM_COLLIDERS; collider_idx--; )
{
SCollider& collider = mp_colliders[collider_idx];
collision_bbox.AddPoint(collider.world.m_corner);
collision_bbox.AddPoint(collider.world.m_corner + collider.world.m_first_edge);
collision_bbox.AddPoint(collider.world.m_corner + collider.world.m_second_edge);
collision_bbox.AddPoint(collider.world.m_corner + collider.world.m_first_edge + collider.world.m_second_edge);
}
m_collision_cache.Update(collision_bbox);
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::update_wheel_heights ( )
{
// We crap out and don't do real wheel dynamics. Instead, if the ground is within a wheel's hang point, we teleport the wheel to the
// ground. If not, we teleport the wheel to its hang point.
CFeeler feeler;
feeler.SetCache(&m_collision_cache);
m_num_wheels_in_contact = 0;
for (int n = m_num_wheels; n--; )
{
SWheel& wheel = mp_wheels[n];
// y_offset a feeler from the top of the wheel's y_offset to the wheel's hang point plus its radius
feeler.m_start = wheel.feeler_start_world;
feeler.m_end = wheel.feeler_end_world;
feeler.SetIgnore(mFD_NON_COLLIDABLE, 0);
if (feeler.GetCollision(false))
{
// trip any triggers
if (wheel.state != SWheel::OUT_OF_CONTACT)
{
if (feeler.GetSector() != wheel.last_ground_feeler.GetSector())
{
trip_trigger(TRIGGER_SKATE_OFF, wheel.last_ground_feeler);
if (reset_this_frame()) return;
trip_trigger(TRIGGER_SKATE_ONTO, feeler);
if (reset_this_frame()) return;
}
}
else
{
trip_trigger(TRIGGER_SKATE_ONTO, feeler);
if (reset_this_frame()) return;
}
wheel.last_ground_feeler = feeler;
wheel.state = SWheel::NO_STATE;
wheel.contact_normal = feeler.GetNormal();
wheel.y_offset = feeler.GetDist() * (wheel.y_offset_hang - wheel.radius) + wheel.radius;
m_num_wheels_in_contact++;
feeler.m_end = feeler.GetPoint();
}
else
{
// trip any triggers
if (wheel.state != SWheel::OUT_OF_CONTACT)
{
trip_trigger(TRIGGER_SKATE_OFF, wheel.last_ground_feeler);
if (reset_this_frame()) return;
}
wheel.state = SWheel::OUT_OF_CONTACT;
wheel.y_offset = wheel.y_offset_hang;
}
// Allows CAP kill planes to work on the car.
if (Ed::CParkEditor::Instance()->UsingCustomPark())
{
// now check for non-collidable trigger polys
feeler.SetIgnore(0, mFD_NON_COLLIDABLE | mFD_TRIGGER);
if (feeler.GetCollision(false))
{
trip_trigger(TRIGGER_BONK, feeler);
if (reset_this_frame()) return;
}
}
} // END loop over wheels
// update the wheels' dependent variables which depend of the wheel heights
for (int n = m_num_wheels; n--; )
{
SWheel& wheel = mp_wheels[n];
wheel.pos_world = wheel.pos;
wheel.pos_world[Y] += wheel.y_offset - wheel.radius;
wheel.pos_world = m_orientation_matrix.Rotate(wheel.pos_world);
wheel.vel_world = calculate_body_point_velocity(wheel.pos_world);
} // END loop over wheels
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::update_steering_angles ( )
{
// NOTE: could easily use a table here
m_steering_display = Mth::Lerp(m_steering_display, m_controls.steering, 10.0f * m_time_step);
float left_steering_angle;
float right_steering_angle;
float left_steering_angle_display;
float right_steering_angle_display;
if (Mth::Abs(m_controls.steering) > 0.001f)
{
float turning_radius = m_cornering_wheelbase / tanf(m_controls.steering * m_max_steering_angle);
if (turning_radius > 0.0f)
{
left_steering_angle = -atan2f(m_cornering_wheelbase, turning_radius - m_cornering_axle_length / 2.0f);
right_steering_angle = -atan2f(m_cornering_wheelbase, turning_radius + m_cornering_axle_length / 2.0f);
}
else
{
left_steering_angle = atan2f(m_cornering_wheelbase, -turning_radius + m_cornering_axle_length / 2.0f);
right_steering_angle = atan2f(m_cornering_wheelbase, -turning_radius - m_cornering_axle_length / 2.0f);
}
}
else
{
left_steering_angle = right_steering_angle = 0.0f;
}
if (Mth::Abs(m_steering_display) > 0.001f)
{
float turning_radius_display = m_cornering_wheelbase / tanf(m_steering_display * m_max_steering_angle);
if (turning_radius_display > 0.0f)
{
left_steering_angle_display = -atan2f(m_cornering_wheelbase, turning_radius_display - m_cornering_axle_length / 2.0f);
right_steering_angle_display = -atan2f(m_cornering_wheelbase, turning_radius_display + m_cornering_axle_length / 2.0f);
}
else
{
left_steering_angle_display = atan2f(m_cornering_wheelbase, -turning_radius_display + m_cornering_axle_length / 2.0f);
right_steering_angle_display = atan2f(m_cornering_wheelbase, -turning_radius_display - m_cornering_axle_length / 2.0f);
}
}
else
{
left_steering_angle_display = right_steering_angle_display = 0.0f;
}
for (int n = m_num_wheels; n--; )
{
SWheel& wheel = mp_wheels[n];
if (wheel.steering == SWheel::FIXED) continue;
wheel.steering_angle = (wheel.steering == SWheel::LEFT ? left_steering_angle : right_steering_angle);
wheel.steering_angle_display = (wheel.steering == SWheel::LEFT ? left_steering_angle_display : right_steering_angle_display);
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::damp_rotation ( )
{
// no damping when handbraking on the ground
if (m_controls.handbrake && m_num_wheels_in_contact > 2) return;
if (!m_controls.throttle && m_controls.steering != 0.0f && m_num_wheels_in_contact > 2) return;
if (in_artificial_collision()) return;
// apply quadtratic damping
// quadratic damping in the air makes jump recovery much easier; the vehicle lands on its wheels much more often
float quad_damping = m_time_step * m_quad_rotvel_damping * m_rotmom.LengthSqr();
// prevent reversal
if (quad_damping > m_rotmom.Length())
{
m_rotmom.Set(0.0f, 0.0f, 0.0f);
}
else
{
Mth::Vector direction = m_rotmom;
direction.Normalize();
m_rotmom -= quad_damping * direction;
}
// apply constant damping; only applied in the Y direction
// apply only if we're not steering, we're on the ground, and we're relatively upright
if (m_controls.steering == 0.0f && m_num_wheels_in_contact > 2 && m_orientation_matrix[Y][Y] > 0.5f)
{
float const_damping = m_time_step * m_const_rotvel_damping;;
// prevent reversal
if (Mth::Abs(const_damping) > Mth::Abs(m_rotmom[Y]))
{
m_rotmom[Y] = 0.0f;
}
else
{
m_rotmom[Y] += (m_rotmom[Y] > 0.0f ? -const_damping : const_damping);
}
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::accumulate_forces ( )
{
// forces and torques on the body accumulate and are applied at the end of the frame; torques on the wheels are too large to accumulate;
// they are applied to wheel velocity as they occur; thus, the order of application of wheel torques is critical
apply_gravitational_forces();
apply_suspension_forces();
// we calculate the friction coefficients before applying engine torques so that friction will get the chance to counteract rotational
// accelerations using static friction before the wheels spin out due to engine torque
calculate_friction_coefficients();
if (!m_controls.handbrake)
{
apply_engine_forces();
apply_drag_forces();
// give the brakes a chance to cancel the rotation due to engine torque and to lock up the wheels before friction effects
apply_brake_forces();
apply_friction_forces();
// after friction, the brakes are given the opportunity to apply additional torque and relock the wheels
apply_spare_brake_forces();
}
else
{
if (m_controls.throttle)
{
apply_engine_forces();
// scale the handbrake's braking effect with the steering factor; thus, you power through sharp handbrake turns; yet, the handbrake
// still brakes on straight aways
apply_handbrake_forces(1.0f - Mth::Abs(m_controls.steering));
apply_friction_forces();
apply_spare_brake_forces();
}
else
{
apply_friction_forces();
// lock wheels
for (int n = m_num_wheels; n--; )
{
mp_wheels[n].rotvel = 0.0f;
mp_wheels[n].rotacc = 0.0f;
}
}
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::apply_gravitational_forces ( )
{
// use reduced gravity when flipping the car
if (m_in_flip && Tmr::ElapsedTime(m_flip_start_time_stamp) > vVP_FLIP_DELAY)
{
m_force[Y] -= vVP_FLIP_GRAVITY_FACTOR * m_gravity_override_fraction * vVP_GRAVITATIONAL_ACCELERATION / m_inv_mass;
}
else
{
m_force[Y] -= m_gravity_override_fraction * vVP_GRAVITATIONAL_ACCELERATION / m_inv_mass;
}
// we do our own force accumulation for this simple and constant force, having the same effect as the following code
// Mth::Vector force(0.0f, -vVP_GRAVITATIONAL_ACCELERATION / m_inv_mass, 0.0f);
// Mth::Vector location = m_suspension_center_of_mass;
// accumulate_force(force, location, MAKE_RGB(50, 50, 0));
if (m_draw_debug_lines)
{
Gfx::AddDebugLine(m_pos, m_pos + 0.05f * Mth::Vector(0.0f, -vVP_GRAVITATIONAL_ACCELERATION / m_inv_mass, 0.0f), MAKE_RGB(50, 50, 0), MAKE_RGB(50, 50, 0), 1);
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::apply_suspension_forces ( )
{
for (int n = m_num_wheels; n--; )
{
SWheel& wheel = mp_wheels[n];
// if not in contact with the ground, y_offset == y_offset_hang
if (wheel.state == SWheel::OUT_OF_CONTACT)
{
// set this frame's history to the default value to smooth out landings
wheel.normal_force_history[m_next_normal_force_history_idx] = vVP_GRAVITATIONAL_ACCELERATION / m_inv_mass / m_num_wheels;
continue;
}
// direction of the suspension line; commented version is slower but explicit
// Mth::Vector direction = m_orientation_matrix.Rotate(Mth::Vector(0.0f, 1.0f, 0.0f));
Mth::Vector direction = m_orientation_matrix[Y];
// magnitude of the spring force along the contact normal
float magnitude = -wheel.spring * (wheel.y_offset_hang - wheel.y_offset) * Mth::DotProduct(wheel.contact_normal, direction);
// velocity of the wheel projected into the contact normal direction
float projected_vel = Mth::DotProduct(wheel.vel_world, wheel.contact_normal);
// magnitude adjusted for damping
magnitude += -wheel.damping * projected_vel;
// suspension force
Mth::Vector force = magnitude * wheel.contact_normal;
accumulate_force(force, wheel.pos_world, MAKE_RGB(100, 100, 0));
// set the normal force magnitude; used in the friction force code
wheel.normal_force_history[m_next_normal_force_history_idx] = magnitude;
wheel.normal_force = 0.0f;
for (int i = vVP_NORMAL_FORCE_HISTORY_LENGTH; i--; )
{
wheel.normal_force += wheel.normal_force_history[i];
}
wheel.normal_force /= vVP_NORMAL_FORCE_HISTORY_LENGTH;
}
m_next_normal_force_history_idx = ++m_next_normal_force_history_idx % vVP_NORMAL_FORCE_HISTORY_LENGTH;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::apply_friction_forces ( )
{
// NOTE: we've handled longitudinal friction reversal successfully; however, tangential friction reversal is still a significant issue for large
// friction coefficients and high centers of mass
// we use a simple tire model were the friction force is opposite the velocity of the tire contact and proportional to the normal force and a
// friction coefficient; the friction coefficient is a function of the velocity of the contact with the following form:
// 0 < v < min_static_velocity: rises linearly from zero to static_friction
// min_static_velocity < v < max_static_velocity: constant at static_friction
// max_static_velocity < v < min_dynamic_velocity: falls linearly from static_friction to dynamic_friction
// min_dynamic_velocity < v: constant at dynamic_friction
// NOTE: because friction is zero at zero slip velocity, hill slippage is an issue
for (int n = m_num_wheels; n--; )
{
SWheel& wheel = mp_wheels[n];
if (wheel.state == SWheel::OUT_OF_CONTACT) continue;
// grab the cached wheel direction and velocity projected into the contact plane
Mth::Vector projected_wheel_direction = wheel.cache_projected_direction;
Mth::Vector projected_vel = wheel.cache_projected_vel;
// subtract the contact patch velocity; positive wheel rotational velocity equals negative contact patch velocity
projected_vel -= wheel.rotvel * wheel.radius * projected_wheel_direction;
// store the slip vel for later display
wheel.slip_vel = projected_vel.Length();
// if the velocity is too small, its direction is meaningless
if (wheel.slip_vel < 0.000001) continue;
// propose a frictional force
Mth::Vector vel_direction = projected_vel;
vel_direction.Normalize();
Mth::Vector proposed_force = -wheel.friction_coefficient * wheel.normal_force * vel_direction;
// now we must check that this force is not more that the force which would stop the relative longitudinal velocity
// calculate the torque required to stick the wheel contact patch to the ground
float longitudinal_vel = Mth::DotProduct(projected_vel, projected_wheel_direction);
float stopping_rotvel = longitudinal_vel / wheel.radius;
float stopping_torque = -calculate_stopping_torque(wheel, stopping_rotvel);
// calculate the proposed torque
float proposed_torque = -wheel.radius * Mth::DotProduct(proposed_force, projected_wheel_direction);
Mth::Vector force;
float torque;
// if the proposed torque is less than what is required to stop the rotation
if (Mth::Abs(proposed_torque) < Mth::Abs(stopping_torque))
{
// we're ok
force = proposed_force;
torque = proposed_torque;
}
else
{
// otherwise, reduce the proposed longitudinal force to only the stopping force
float proposed_longitudinal_force = Mth::DotProduct(proposed_force, projected_wheel_direction);
float stopping_longitudinal_force = -stopping_torque / wheel.radius;
force = proposed_force + (stopping_longitudinal_force - proposed_longitudinal_force) * projected_wheel_direction;
torque = stopping_torque;
}
// apply the force to the body
accumulate_force(force, wheel.pos_world, MAKE_RGB(0, 0, 255));
// apply the torque to the wheel
apply_wheel_torque(wheel, torque);
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::apply_drag_forces ( )
{
if (m_controls.throttle || m_controls.reverse || m_controls.brake) return;
for (int n = m_num_wheels; n--; )
{
SWheel& wheel = mp_wheels[n];
if (!wheel.drive || wheel.state == SWheel::OUT_OF_CONTACT) continue;
// determine the effective engine gear
int effective_gear = determine_effective_gear(Mth::Abs(wheel.rotvel));
// drag torque scales like the square of the gear ratio
float torque = m_engine.drag_torque * m_engine.p_gear_ratios[effective_gear] * m_engine.p_gear_ratios[effective_gear] / m_num_drive_wheels;
// prevent reversal
float maximum_torque = Mth::Abs(calculate_stopping_torque(wheel, wheel.rotvel));
if (torque > maximum_torque)
{
torque = maximum_torque;
}
apply_wheel_torque(wheel, wheel.rotvel < 0.0f ? torque : -torque);
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::apply_engine_forces ( )
{
// NOTE: test
float wall_climb_factor = Mth::ClampMax(m_orientation_matrix[Y][Y] * m_orientation_matrix[Y][Y] / 0.9f, 1.0f);
if ((!m_controls.throttle && !m_controls.reverse) || m_controls.brake) return;
for (int n = m_num_wheels; n--; )
{
SWheel& wheel = mp_wheels[n];
if (!wheel.drive) continue;
// turn off power to front wheels when power handbraking
if (m_controls.handbrake && wheel.steering != SWheel::FIXED) continue;
// determine effective engine gear
int effective_gear;
if (!m_controls.reverse)
{
effective_gear = determine_effective_gear(wheel.rotvel > 0.0f ? wheel.rotvel : 0.0f);
}
else
{
effective_gear = determine_effective_gear(wheel.rotvel < 0.0f ? -wheel.rotvel : 0.0f);
}
float torque = m_engine.drive_torque * m_engine.differential_ratio * m_engine.p_gear_ratios[effective_gear] / m_num_drive_wheels;
torque *= wall_climb_factor;
// reverse gears are scaled down by a factor
if (m_controls.reverse)
{
torque *= -m_engine.reverse_torque_ratio;
}
// account for low of power in front wheels in four-wheel drive car when power handbraking
if (m_controls.handbrake && m_num_drive_wheels == 4) torque *= 2.0f;
apply_wheel_torque(wheel, torque);
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::apply_brake_forces ( )
{
if (!m_controls.brake) return;
for (int n = m_num_wheels; n--; )
{
SWheel& wheel = mp_wheels[n];
float torque = wheel.brake.torque;
float maximum_torque = Mth::Abs(calculate_stopping_torque(wheel, wheel.rotvel));
if (torque > maximum_torque)
{
wheel.brake.spare_torque = torque - maximum_torque;
torque = maximum_torque;
}
else
{
wheel.brake.spare_torque = 0.0f;
}
apply_wheel_torque(wheel, wheel.rotvel < 0.0f ? torque : -torque);
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::apply_handbrake_forces ( float application_factor )
{
Dbg_Assert(m_controls.handbrake);
for (int n = m_num_wheels; n--; )
{
SWheel& wheel = mp_wheels[n];
float torque = application_factor * wheel.brake.handbrake_torque;
float maximum_torque = Mth::Abs(calculate_stopping_torque(wheel, wheel.rotvel));
if (torque > maximum_torque)
{
wheel.brake.spare_torque = torque - maximum_torque;
torque = maximum_torque;
}
else
{
wheel.brake.spare_torque = 0.0f;
}
apply_wheel_torque(wheel, wheel.rotvel < 0.0f ? torque : -torque);
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::apply_spare_brake_forces ( )
{
if (!m_controls.brake) return;
for (int n = m_num_wheels; n--; )
{
SWheel& wheel = mp_wheels[n];
if (wheel.brake.spare_torque == 0.0f) continue;
float torque = wheel.brake.spare_torque;
float maximum_torque = Mth::Abs(calculate_stopping_torque(wheel, wheel.rotvel));
if (torque > maximum_torque)
{
torque = maximum_torque;
}
apply_wheel_torque(wheel, wheel.rotvel < 0.0f ? torque : -torque);
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
float CVehicleComponent::calculate_collision_depth ( const SCollisionPoint& collision_point, const SCollider& collider ) const
{
enum EDepthDirection
{
WITH_FIRST_EDGE, AGAINST_FIRST_EDGE, WITH_SECOND_EDGE, AGAINST_SECOND_EDGE
};
// calculate displacement from base corner
Mth::Vector displacement = collision_point.pos - collider.world.m_corner;
// determine the minimum depth and correponding depth direction
float min_depth = Mth::DotProduct(displacement, collider.first_edge_direction_world);
EDepthDirection depth_direction = WITH_FIRST_EDGE;
float depth = collider.first_edge_length - min_depth;
if (depth < min_depth)
{
min_depth = depth;
depth_direction = AGAINST_FIRST_EDGE;
}
depth = Mth::DotProduct(displacement, collider.second_edge_direction_world);
if (depth < min_depth)
{
min_depth = depth;
depth_direction = WITH_SECOND_EDGE;
}
depth = collider.second_edge_length - depth;
if (depth < min_depth)
{
min_depth = depth;
depth_direction = AGAINST_SECOND_EDGE;
}
if (min_depth < 0.01f)
{
return 0.0f;
}
switch (depth_direction)
{
case WITH_FIRST_EDGE:
depth = min_depth / Mth::DotProduct(collider.first_edge_direction_world, collision_point.normal);
break;
case AGAINST_FIRST_EDGE:
depth = -min_depth / Mth::DotProduct(collider.first_edge_direction_world, collision_point.normal);
break;
case WITH_SECOND_EDGE:
depth = min_depth / Mth::DotProduct(collider.second_edge_direction_world, collision_point.normal);
break;
case AGAINST_SECOND_EDGE:
depth = -min_depth / Mth::DotProduct(collider.second_edge_direction_world, collision_point.normal);
break;
}
#ifdef __USER_DAN__
if (depth < 0.0f)
{
MESSAGE("negative depth bug");
}
#endif
return depth;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
bool CVehicleComponent::check_for_capping ( SCollisionPoint& collision_point, const CRectFeeler& rect_feeler, const SCollider& collider, int collision_line_idx, int collision_point_end_idx ) const
{
// check to see if this culled collision is the back of a cappable collision with a 2D object
for (int check_line_idx = rect_feeler.GetNumMergedCollisionSurfaces(); check_line_idx--; )
{
if (check_line_idx == collision_line_idx) continue;
for (int end = 2; end--; )
{
if (very_close(collision_point.pos, rect_feeler.GetMergedCollisionSurface(check_line_idx).ends[end].point)
&& Mth::DotProduct(collision_point.normal, rect_feeler.GetMergedCollisionSurface(check_line_idx).normal) < -0.99)
{
// we'll use this point to cap the collision with the 2D object; the tangent cross the normal will point out of the triangle
collision_point.normal = Mth::CrossProduct(
rect_feeler.GetMergedCollisionSurface(collision_line_idx).ends[collision_point_end_idx].tangent,
collision_point.normal
);
collision_point.depth = calculate_collision_depth(collision_point, collider);
if (m_draw_debug_lines)
{
Gfx::AddDebugLine(
collision_point.pos,
collision_point.pos + (72) * collision_point.normal,
MAKE_RGB(255, 100, 255), MAKE_RGB(255, 100, 255), 1
);
}
return true;
}
} // END loop over checked collision line ends
} // END loop over all other collision lines
return false;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
bool CVehicleComponent::consider_culling_point ( const SCollisionPoint& collision_point ) const
{
// cull collision points which have a normal direction along their body offset
Mth::Vector offset = collision_point.pos - m_pos;
bool cull = Mth::DotProduct(offset, collision_point.normal) >= 0.0f;
if (m_draw_debug_lines && cull)
{
uint32 c = MAKE_RGB(0, 0, 0);
Gfx::AddDebugLine(collision_point.pos, collision_point.pos + 48.0f * collision_point.normal, c, c, 1);
}
return cull;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::apply_environment_collisions ( )
{
m_max_normal_collision_impulse = 0.0f;
CRectFeeler rect_feeler;
CLineFeeler line_feeler;
rect_feeler.SetCache(&m_collision_cache);
line_feeler.SetCache(&m_collision_cache);
m_num_collision_points = 0;
for (int collider_idx = vVP_NUM_COLLIDERS; collider_idx--; )
{
SCollider& collider = mp_colliders[collider_idx];
// first we use a rectangular feeler
// setup the feeler
rect_feeler.SetRectangle(collider.world);
// detect collisions
if (rect_feeler.GetCollision())
{
trip_trigger(TRIGGER_BONK, rect_feeler);
if (reset_this_frame()) return;
rect_feeler.MergeCollisionSurfaces();
// copy out the collision points from the feeler output
for (int collision_line_idx = rect_feeler.GetNumMergedCollisionSurfaces(); collision_line_idx--; )
{
for (int end = 2; end--; )
{
// add the collision line's start to the list of collision points
SCollisionPoint& new_collision_point = sp_collision_points[m_num_collision_points];
new_collision_point.line = false;
new_collision_point.pos = rect_feeler.GetMergedCollisionSurface(collision_line_idx).ends[end].point;
new_collision_point.normal = rect_feeler.GetMergedCollisionSurface(collision_line_idx).normal;
if (!consider_culling_point(new_collision_point))
{
// if this is a keeper, calculate the collision depth
if (rect_feeler.GetMergedCollisionSurface(collision_line_idx).ends[end].tangent_exists)
{
new_collision_point.depth = calculate_collision_depth(new_collision_point, collider);
}
else
{
new_collision_point.depth = 0.0f;
}
m_num_collision_points++;
}
else if (rect_feeler.GetMergedCollisionSurface(collision_line_idx).ends[end].tangent_exists)
{
if (check_for_capping(new_collision_point, rect_feeler, collider, collision_line_idx, end))
{
m_num_collision_points++;
}
}
} // END loop over collision line ends
} // END loop over collision surfaces
} // END if we had a collision this collider
if (m_draw_debug_lines)
{
rect_feeler.DebugLines(255, 255, 255, 1);
}
// line feelers
line_feeler.m_start = m_pos;
line_feeler.m_end = rect_feeler.m_corner;
if (line_feeler.GetCollision(false))
{
trip_trigger(TRIGGER_BONK, line_feeler);
if (reset_this_frame()) return;
sp_collision_points[m_num_collision_points].pos = line_feeler.GetPoint();
sp_collision_points[m_num_collision_points].normal = line_feeler.GetNormal();
if (!consider_culling_point(sp_collision_points[m_num_collision_points]))
{
sp_collision_points[m_num_collision_points].depth = calculate_collision_depth(line_feeler);
m_num_collision_points++;
sp_collision_points[m_num_collision_points].line = true;
}
}
if (m_draw_debug_lines)
{
line_feeler.DebugLine(255, 255, 255, 1);
}
line_feeler.m_end += rect_feeler.m_first_edge;
if (line_feeler.GetCollision(false))
{
trip_trigger(TRIGGER_BONK, line_feeler);
if (reset_this_frame()) return;
sp_collision_points[m_num_collision_points].pos = line_feeler.GetPoint();
sp_collision_points[m_num_collision_points].normal = line_feeler.GetNormal();
if (!consider_culling_point(sp_collision_points[m_num_collision_points]))
{
sp_collision_points[m_num_collision_points].depth = calculate_collision_depth(line_feeler);
m_num_collision_points++;
sp_collision_points[m_num_collision_points].line = true;
}
}
if (m_draw_debug_lines)
{
line_feeler.DebugLine(255, 255, 255, 1);
}
line_feeler.m_end += rect_feeler.m_second_edge;
if (line_feeler.GetCollision(false))
{
trip_trigger(TRIGGER_BONK, line_feeler);
if (reset_this_frame()) return;
sp_collision_points[m_num_collision_points].pos = line_feeler.GetPoint();
sp_collision_points[m_num_collision_points].normal = line_feeler.GetNormal();
if (!consider_culling_point(sp_collision_points[m_num_collision_points]))
{
sp_collision_points[m_num_collision_points].depth = calculate_collision_depth(line_feeler);
m_num_collision_points++;
sp_collision_points[m_num_collision_points].line = true;
}
}
if (m_draw_debug_lines)
{
line_feeler.DebugLine(255, 255, 255, 1);
}
line_feeler.m_end -= rect_feeler.m_first_edge;
if (line_feeler.GetCollision(false))
{
trip_trigger(TRIGGER_BONK, line_feeler);
if (reset_this_frame()) return;
sp_collision_points[m_num_collision_points].pos = line_feeler.GetPoint();
sp_collision_points[m_num_collision_points].normal = line_feeler.GetNormal();
if (!consider_culling_point(sp_collision_points[m_num_collision_points]))
{
sp_collision_points[m_num_collision_points].depth = calculate_collision_depth(line_feeler);
m_num_collision_points++;
sp_collision_points[m_num_collision_points].line = true;
}
}
if (m_draw_debug_lines)
{
line_feeler.DebugLine(255, 255, 255, 1);
}
} // END loop over colliders
if (m_num_collision_points == 0) return;
// zero accumulators
for (int collision_idx = m_num_collision_points; collision_idx--; )
{
sp_collision_points[collision_idx].normal_impulse = 0.0f;
}
// cache the momentums and velocities before normal impulses are applied; they are used when determining the frictional forces
Mth::Vector cache_mom = m_mom;
Mth::Vector cache_rotmom = m_rotmom;
Mth::Vector cache_vel = m_vel;
Mth::Vector cache_rotvel = m_rotvel;
// apply normal impulses
// loop over normal impulse points until all collisions are fully resolved
int pass_count = 0;
bool clean_pass;
do
{
clean_pass = true;
for (int collision_idx = m_num_collision_points; collision_idx--; )
{
SCollisionPoint& collision = sp_collision_points[collision_idx];
// calculate the collision position in body space
Mth::Vector pos_local = collision.pos - m_pos;
// calculate the velocity and normal velocity of the collision point
Mth::Vector vel = calculate_body_point_velocity(pos_local);
float normal_vel = Mth::DotProduct(vel, collision.normal);
// ignore if we're not impacting
if (normal_vel > 0.0f) continue;
clean_pass = false;
// calculate the goal velocity of the collision
float goal_normal_velocity = -m_body_restitution * normal_vel;
// calculate the effective mass of the collision point
float effective_mass = calculate_body_point_effective_mass(pos_local, collision.normal);
// calculate the normal impulse required to reach the body velocity
float normal_impulse = (goal_normal_velocity - normal_vel) * effective_mass;
// accumulate the total normal impulse applied to this collision point
collision.normal_impulse += normal_impulse;
// setup the required impulse vector
Mth::Vector impulse = normal_impulse * collision.normal;
// apply the normal impulse immediately
apply_impulse(impulse, pos_local);
}
if (++pass_count > 5)
{
#ifdef __USER_DAN__
MESSAGE("environment collision resolution pass count limit exceeded");
#endif
break;
}
} while (!clean_pass);
if (m_draw_debug_lines)
{
for (int collision_idx = m_num_collision_points; collision_idx--; )
{
// SCollisionPoint& collision = sp_collision_points[collision_idx];
// Gfx::AddDebugLine(
// collision.pos + collision.normal,
// collision.pos + collision.normal + collision.normal_impulse / 2.0f * collision.normal,
// MAKE_RGB(0, 100, 100), MAKE_RGB(0, 100, 100), 1
// );
}
} // END loop over collision points
// apply penalty forces to prevent interpenetration
for (int collision_idx = m_num_collision_points; collision_idx--; )
{
SCollisionPoint& collision = sp_collision_points[collision_idx];
// collision normal is facing opposite the depth
// BUG: too many rect feeler collisions have negative depth!!!
if (collision.depth <= 0.0f) continue;
// extremely deep collisions are mostly collisions with the underbody which are actually shallow in the Y direction; for now we'll cap such
// collisions and perhaps allow the car to sink down into geometry which comes from below but misses the wheels
if (collision.depth > 2.0f)
{
collision.depth = 2.0f;
}
Mth::Vector force = m_body_spring * collision.depth * collision.normal;
accumulate_collision_force(force, collision.pos - m_pos);
// Gfx::AddDebugLine(
// collision.pos,
// collision.pos + (2 * collision.depth) * collision.normal,
// MAKE_RGB(255, 255, 0), MAKE_RGB(255, 255, 0), 1
// );
}
float body_friction = m_orientation_matrix[Y][Y] > 0.707f ? m_body_friction : m_body_wipeout_friction;
// apply a friction impulse at each point
for (int collision_idx = m_num_collision_points; collision_idx--; )
{
SCollisionPoint& collision = sp_collision_points[collision_idx];
if (collision.normal_impulse == 0.0f) continue;
// calculate the local collision position
Mth::Vector pos_local = collision.pos - m_pos;
// calculate the tangential velocity
Mth::Vector vel = cache_vel + Mth::CrossProduct(cache_rotvel, pos_local);
Mth::Vector tanjent = vel - Mth::DotProduct(vel, collision.normal) * collision.normal;
float tanjent_vel = tanjent.Length();
if (tanjent_vel < 0.001f) continue;
tanjent /= tanjent_vel;
// calculate the effective mass of the collision point
float effective_mass = calculate_body_point_effective_mass(pos_local, tanjent);
// calculate the tanjential impulse required to stop the tanjential velocity
float tanjent_impulse = tanjent_vel * effective_mass;
// cap the frictional force at the normal force times the coefficient of friction
float max_tanjent_impulse = body_friction * collision.normal_impulse;
if (tanjent_impulse > max_tanjent_impulse)
{
tanjent_impulse = max_tanjent_impulse;
}
// setup the tanjent impulse vector
Mth::Vector impulse = -tanjent_impulse * tanjent;
// apply the friction impulse to the true state
apply_impulse(impulse, pos_local);
if (m_draw_debug_lines)
{
// Gfx::AddDebugLine(
// collision.pos + collision.normal,
// collision.pos + collision.normal + impulse / 2.0f,
// MAKE_RGB(100, 0, 100), MAKE_RGB(100, 0, 100), 1
// );
}
// apply the friction impulse to the state we are using for friction calculations
cache_mom += impulse;
cache_rotmom += Mth::CrossProduct(pos_local, impulse);
cache_vel = m_inv_mass * cache_mom;
cache_rotvel = m_inv_moment.Rotate(cache_rotmom);
} // END loop over collision points
// determine the maximum normal impulse this frame
for (int collision_idx = m_num_collision_points; collision_idx--; )
{
SCollisionPoint& collision = sp_collision_points[collision_idx];
m_max_normal_collision_impulse = Mth::Max(collision.normal_impulse, m_max_normal_collision_impulse);
}
// no player controlled impulses in the air or with handbrake on
if (m_controls.steering == 0.0f || m_num_wheels_in_contact < 3 || m_controls.handbrake) return;
// extra player controled collision impulse; allows one slight control over the direction on collision impulses
for (int collision_idx = m_num_collision_points; collision_idx--; )
{
SCollisionPoint& collision = sp_collision_points[collision_idx];
Mth::Vector pos_local = collision.pos - m_pos;
float strength = m_controls.steering * m_collision_control * Mth::DotProduct(m_orientation_matrix[Z], collision.normal) * collision.normal_impulse;
Mth::Vector direction(-collision.normal[Z], 0.0f, collision.normal[X]);
apply_impulse(strength * direction, pos_local);
if (m_draw_debug_lines)
{
Gfx::AddDebugLine(
collision.pos + collision.normal,
collision.pos + collision.normal + strength * direction / 5.0f,
MAKE_RGB(255, 0, 100), MAKE_RGB(255, 0, 100), 1
);
}
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::update_pos_with_uber_frig ( const Mth::Vector& movement )
{
// The vehicle has two uber frigs. The first treats the c.o.m. as a point particle with respect to the geo. The seconds is a traditional uber frig.
Mth::Vector frame_start_pos = m_pos;
// look for collisions along the origin's movement; only do a single bounce, assuming the remaining movement is ok
CFeeler feeler(m_pos, m_pos + movement);
if (feeler.GetCollision(false))
{
// bounce off the surface
MESSAGE("less than uber frig");
m_pos = feeler.GetPoint() + feeler.GetNormal();
Mth::Vector remaining_movement = feeler.GetDist() * movement;
remaining_movement -= 2.0f * Mth::DotProduct(remaining_movement, feeler.GetNormal()) * feeler.GetNormal();
m_pos += remaining_movement;
// use coeff of restit of 0.5
m_mom -= 1.5f * Mth::DotProduct(m_mom, feeler.GetNormal()) * feeler.GetNormal();
}
else
{
m_pos += movement;
}
// now, a traditional uber frig
feeler.m_start = m_pos;
feeler.m_end = feeler.m_start;
feeler.m_end[Y] -= FEET(400.0f);
if (!feeler.GetCollision(false))
{
MESSAGE("uber frig");
m_pos = frame_start_pos;
m_mom *= 1.0f;
m_rotmom *= 1.0f;
}
else
{
mp_model_component->ApplyLightingFromCollision(feeler);
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::slerp_to_face_velocity ( )
{
// slerp to face our velocity
if (m_in_flip) return;
if (in_artificial_collision()) return;
// only slerp if horizontal velocity is below a given threshold
bool vel_below_threshold = m_vel[X] * m_vel[X] + m_vel[Z] * m_vel[Z] < m_in_air_slerp_vel_cutoff * m_in_air_slerp_vel_cutoff;
if (vel_below_threshold && !m_vert_correction) return;
// extract the current orientation matrix
m_orientation.GetMatrix(m_orientation_matrix);
m_orientation_matrix[W].Set();
// construct our target matrix
Mth::Matrix target;
if (vel_below_threshold)
{
target[Z] = m_orientation_matrix[Z];
target[Z][Y] = 0.0f;
target[Z].Normalize();
}
else
{
target[Z] = m_vel;
target[Z].Normalize();
}
if (Mth::DotProduct(target[Z], m_orientation_matrix[Z]) < 0.0f)
{
target[Z] *= -1.0f;
}
target[Y].Set(0.0f, 1.0f, 0.0f);
target[X] = Mth::CrossProduct(target[Y], target[Z]);
target[Y] = Mth::CrossProduct(target[Z], target[X]);
target[W].Set();
// setup the slerp
Mth::SlerpInterpolator slerper;
slerper.setMatrices(&m_orientation_matrix, &target);
// lerp up to full strength over time
if (m_vel[X] * m_vel[X] + m_vel[Z] * m_vel[Z] > 350.0f * 350.0f)
{
slerper.getMatrix(&m_orientation_matrix, Mth::LinearMap(
0.0f,
m_in_air_slerp_strength * m_time_step,
Mth::Min(m_air_time_no_collision, m_in_air_slerp_time_delay),
0.0f,
m_in_air_slerp_time_delay
));
}
else
{
slerper.getMatrix(&m_orientation_matrix, Mth::LinearMap(
0.0f,
0.3f * m_in_air_slerp_strength * m_time_step,
Mth::Min(m_air_time_no_collision, m_in_air_slerp_time_delay),
0.0f,
m_in_air_slerp_time_delay
));
}
m_orientation = m_orientation_matrix;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
float CVehicleComponent::calculate_body_point_effective_mass ( const Mth::Vector& pos, const Mth::Vector& direction ) const
{
// calculate the effect on momentums due to a unit impulse applied at pos towards direction
Mth::Vector delta_mom = direction;
Mth::Vector delta_rotmom = Mth::CrossProduct(pos, direction);
// calculate the resulting change in velocities
Mth::Vector delta_vel = m_inv_mass * delta_mom;
Mth::Vector delta_rotvel = m_inv_moment.Rotate(delta_rotmom);
// calculate the corresponding change in the body point's velocity
Mth::Vector delta_vel_body_point = delta_vel + Mth::CrossProduct(delta_rotvel, pos);
// extract the change in velocity along the direction of interest
float delta_vel_direction = Mth::DotProduct(direction, delta_vel_body_point);
// return the effective mass of the body point in the direction of interest
Dbg_Assert(delta_vel_direction != 0.0f);
return 1.0f / delta_vel_direction;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::consider_sleeping ( )
{
int last_consider_sleeping_count = m_consider_sleeping_count;
m_consider_sleeping_count = 0;
if (m_controls.throttle) return;
if (m_controls.reverse && m_num_wheels_in_contact != 0) return;
// only sleep if we're experiencing three or more collisions; hopefully, a surface-to-surface contact
if (m_num_wheels_in_contact + m_num_collision_points < 3) return;
// only sleep if we've got four-on-the-floor or are completely flipped
if (m_num_wheels_in_contact != 0 && m_num_wheels_in_contact != m_num_wheels) return;
// only sleep if we're moving slow
if (m_vel.LengthSqr() > Mth::Sqr(vVP_SLEEP_VEL)) return;
#ifdef __USER_DAN__
DUMPF(m_vel.Length());
#endif
// only sleep if we're moving slow
if (m_rotvel.LengthSqr() > Mth::Sqr(vVP_SLEEP_ROTVEL)) return;
#ifdef __USER_DAN__
DUMPF(m_rotvel.Length());
#endif
// never sleep during an artificial collision period
if (in_artificial_collision()) return;
m_consider_sleeping_count = last_consider_sleeping_count + 1;
if (m_consider_sleeping_count < 3)
{
return;
}
// sleep
m_mom.Set();
m_rotmom.Set();
m_force.Set(0.0f, 0.0f, 0.0f);
m_torque.Set(0.0f, 0.0f, 0.0f);
for (int n = m_num_wheels; n--; )
{
mp_wheels[n].rotacc = 0.0f;
}
m_state = ASLEEP;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::get_input ( )
{
CControlPad& control_pad = mp_input_component->GetControlPad();
// analog control
// m_controls.steering = control_pad.m_scaled_leftX;
m_controls.steering = control_pad.m_scaled_leftX * Mth::Abs(control_pad.m_scaled_leftX);
// dpad control
if (m_controls.steering == 0.0f)
{
if (control_pad.m_left.GetPressed() && !control_pad.m_right.GetPressed())
{
m_controls.steering = -1.0f;
}
else if (control_pad.m_right.GetPressed() && !control_pad.m_left.GetPressed())
{
m_controls.steering = 1.0f;
}
}
m_controls.throttle = control_pad.m_x.GetPressed();
m_controls.handbrake = control_pad.m_R1.GetPressed() && !m_no_handbrake;
bool brake_pressed = control_pad.m_square.GetPressed() || (m_exitable && control_pad.m_triangle.GetPressed());
bool exit_request = brake_pressed && !control_pad.m_square.GetPressed();
// decide between brake and reverse
if (brake_pressed)
{
if ((m_controls.reverse || (Mth::DotProduct(m_vel, m_orientation_matrix[Z]) < 300.0f && m_num_wheels_in_contact != 0))
&& (m_state != ASLEEP || m_num_wheels_in_contact != 0))
{
// reverse
m_controls.reverse = true;
m_controls.brake = false;
}
else
{
// brake
m_controls.brake = true;
m_controls.reverse = false;
}
if (exit_request)
{
if (Mth::DotProduct(m_vel, m_orientation_matrix[Z]) < 0.0f)
{
// brake
m_controls.brake = true;
m_controls.reverse = false;
}
if (Mth::Abs(Mth::DotProduct(m_vel, m_orientation_matrix[Z])) < 30.0f)
{
// signal an exit
GetObject()->BroadcastEvent(CRCD(0xcbaa3476, "ExitVehicleRequest"));
}
}
}
else
{
m_controls.reverse = false;
m_controls.brake = false;
}
if (m_force_brake)
{
m_controls.brake = true;
m_controls.reverse = false;
}
if (m_state == ASLEEP && (m_controls.throttle || m_controls.reverse))
{
m_state = AWAKE;
m_consider_sleeping_count = 0;
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::zero_input ( )
{
m_controls.brake = false;
m_controls.handbrake = false;
m_controls.throttle = false;
m_controls.reverse = false;
m_controls.steering = 0.0f;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::update_skeleton ( )
{
Mth::Matrix* p_matrices = mp_skeleton_component->GetSkeleton()->GetMatrices();
for (int i = mp_skeleton_component->GetSkeleton()->GetNumBones(); i--; )
{
Mth::Matrix& matrix = p_matrices[i];
// setup the matrix for each bone in the skeleton
if (m_draw_debug_lines == 2)
{
matrix.Zero();
continue;
}
// shadow
if (i == 0)
{
matrix.Zero();
}
// body
else if (i == 1)
{
matrix.Zero();
matrix[X][X] = 1.0f;
matrix[Y][Z] = -1.0f;
matrix[Z][Y] = 1.0f;
matrix[W] = m_body_pos;
}
// wheel
else
{
SWheel& wheel = mp_wheels[i - 2];
matrix.Zero();
matrix[X][X] = -1.0f;
matrix[Y][Z] = 1.0f;
matrix[Z][Y] = 1.0f;
matrix.RotateZLocal(wheel.steering_angle_display);
matrix.RotateXLocal(-wheel.orientation);
matrix[W] = wheel.pos;
#ifdef __NOPT_ASSERT__
if (Script::GetInteger("use_max_y_offset"))
{
matrix[W][Y] += wheel.max_draw_y_offset;
}
else
{
matrix[W][Y] += Mth::ClampMax(wheel.y_offset, wheel.max_draw_y_offset);
}
#else
matrix[W][Y] += Mth::ClampMax(wheel.y_offset, wheel.max_draw_y_offset);
#endif
matrix[W][W] = 1.0f;
}
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::draw_shadow ( )
{
//Dbg_Message("Drawing shadow for car");
#if 0 // Experiment to draw a shadow using texture splats
Mth::Vector start_pos = GetObject()->GetPos();
Mth::Vector end_pos = start_pos;
start_pos[Y] += 12.0f;
end_pos[Y] -= 120.0f;
float y_rot = 360.0f - m_orientation.GetVector()[Y];
while (y_rot < 0.0f)
y_rot += 360.0f;
Nx::TextureSplat( start_pos, end_pos, 120.0f, 100.0f, 2.0f / (float) Config::FPS(), "blood_01", y_rot);
#endif
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::control_skater ( )
{
// HACKY; setup the skater's position, orientation, and animation each frame
mp_skater->SetPos(GetObject()->GetPos() + GetObject()->GetMatrix().Rotate(m_skater_pos));
mp_skater->SetMatrix(GetObject()->GetMatrix());
mp_skater_core_physics_component->ResetLerpingMatrix();
mp_skater->SetVel(GetObject()->GetVel());
if (!m_skater_visible) return;
float target_time = Mth::LinearMap(
0.0f,
mp_skater_animation_component->AnimDuration(m_skater_anim),
mp_skater_core_physics_component->GetFlag(FLIPPED) ? m_steering_display : -m_steering_display,
-1.0f,
1.0f
);
if (!GameNet::Manager::Instance()->InNetGame())
{
mp_skater_animation_component->PlayPrimarySequence(m_skater_anim, false, target_time, 1000.0f, Gfx::LOOPING_CYCLE, 0.0f, 0.0f);
}
else
{
// HACK to reduce number of animation events when driving cars
static int anim_send_count = 0;
if (++anim_send_count == 15)
{
anim_send_count = 0;
mp_skater_animation_component->PlayPrimarySequence(m_skater_anim, true, target_time, 1000.0f, Gfx::LOOPING_CYCLE, 0.0f, 0.0f);
}
else
{
mp_skater_animation_component->PlayPrimarySequence(m_skater_anim, false, target_time, 1000.0f, Gfx::LOOPING_CYCLE, 0.0f, 0.0f);
}
}
}
/******************************************************************/
/* */
/* */
/******************************************************************/
const int num_edges = 12;
Mth::Vector CVehicleComponent::wheel_point ( const SWheel& wheel, int i, bool side ) const
{
Mth::Vector point(
(side ? 1.0f : -1.0f) * 3.85f,
wheel.radius * cosf(2.0f * 3.141592f * i / num_edges + wheel.orientation),
wheel.radius * sinf(2.0f * 3.141592f * i / num_edges + wheel.orientation)
);
Mth::Matrix steering_rotation(Mth::Vector(0.0f, 1.0f, 0.0f), wheel.steering_angle);
point = steering_rotation.Rotate(point);
point[Y] += wheel.y_offset;
point += wheel.pos;
point = m_pos + m_orientation_matrix.Rotate(point);
return point;
}
/******************************************************************/
/* */
/* */
/******************************************************************/
void CVehicleComponent::draw_debug_rendering ( ) const
{
if (m_draw_debug_lines)
{
for (int n = m_num_wheels; n--; )
{
SWheel& wheel = mp_wheels[n];
int32 color = 0;
switch (wheel.state)
{
case SWheel::UNDER_GRIPPING:
color = MAKE_RGB(0, 255, 0);
break;
case SWheel::GRIPPING:
color = MAKE_RGB(0, 0, 0);
break;
case SWheel::SLIPPING:
color = MAKE_RGB(255, 200, 0);
break;
case SWheel::SKIDDING:
color = MAKE_RGB(255, 0, 0);
break;
case SWheel::HANDBRAKE_THROTTLE:
color = MAKE_RGB(150, 0, 150);
break;
case SWheel::HANDBRAKE_LOCKED:
color = MAKE_RGB(0, 150, 150);
break;
case SWheel::NO_STATE:
case SWheel::OUT_OF_CONTACT:
default:
color = MAKE_RGB(255, 255, 255);
break;
}
for (int i = num_edges; i--; )
{
Mth::Vector start = wheel_point(wheel, i, true);
Mth::Vector end = wheel_point(wheel, i + 1, true);
Gfx::AddDebugLine(start, end, color, color, 1);
start = wheel_point(wheel, i, false);
end = wheel_point(wheel, i + 1, false);
Gfx::AddDebugLine(start, end, color, color, 1);
start = wheel_point(wheel, i, true);
end = wheel_point(wheel, i, false);
Gfx::AddDebugLine(start, end, color, color, 1);
#if 1
// draw axis of rotation lines
Mth::Matrix steering_rotation(Mth::Vector(0.0f, 1.0f, 0.0f), wheel.steering_angle);
start.Set(-240.0f, 0.0f, 0.0f);
start = steering_rotation.Rotate(start);
start += wheel.pos;
start[Y] += wheel.y_offset;
start = m_pos + m_orientation_matrix.Rotate(start);
end.Set(240.0f, 0.0f, 0.0f);
end = steering_rotation.Rotate(end);
end += wheel.pos;
end[Y] += wheel.y_offset;
end = m_pos + m_orientation_matrix.Rotate(end);
Gfx::AddDebugLine(start, end, MAKE_RGB(0, 0, 0), MAKE_RGB(0, 0, 0), 1);
#endif
}
} // END loop over wheels
}
#if 1
if (m_draw_debug_lines)
{
Gfx::AddDebugStar(m_pos + m_suspension_center_of_mass_world, 12.0f, MAKE_RGB(100, 0, 100), 1);
Gfx::AddDebugStar(m_pos, 12.0f, MAKE_RGB(0, 100, 100), 1);
}
#endif
}
}