Files
fs2open.github.com/code/physics/physics.cpp
T

1454 lines
53 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include "ai/ai_profiles.h" // for the damping issue
#include "freespace.h"
#include "io/timer.h"
#include "mission/missionparse.h"
#include "mod_table/mod_table.h"
#include "physics/physics.h"
#include "ship/ship.h"
#include "utils/Random.h"
// defines for physics functions
#define MAX_TURN_LIMIT 0.2618f // about 15 degrees
#define ROTVEL_TOL 0.1 // Amount of rotvel is decreased if over cap
#define ROTVEL_CAP 14.0 // Rotational velocity cap for live objects
#define DEAD_ROTVEL_CAP 16.3 // Rotational velocity cap for dead objects
constexpr float MAX_SHIP_WHACK_DELTA = 500.0f; // Maximum change in speed allowed after whack or shockwave
constexpr float RESET_SHIP_WHACK_DELTA = 440.0f; // Change in speed that a ship is reset to after exceeding MAX_SHIP_WHACK_DELTA
#define REDUCED_DAMP_FACTOR 10 // increase in side_slip and acceleration time constants (scaled according to reduced damp time)
#define REDUCED_DAMP_VEL 30 // change in velocity at which reduced_damp_time is 2000 ms
#define REDUCED_DAMP_TIME 2000 // ms (2.0 sec)
#define WEAPON_SHAKE_TIME 500 // ms (0.5 sec) viewer shake time after hit by weapon (implemented via afterburner shake)
const float SUPERCAP_WARP_T_CONST = 0.651f; // special warp time constant (lose 99 % of excess speed in 3 sec)
const float SUPERCAP_WARP_EXCESS_SPD_THRESHOLD = 5.0f; // more than this many m/s faster than the ship's max speed and we use the above damp constant instead
void update_reduced_damp_timestamp( physics_info *pi, float impulse );
float velocity_ramp (float v_in, float v_goal, float time_const, float t);
float glide_ramp (float v_in, float v_goal, float ramp_time_const, float accel_mult, float t);
void physics_init( physics_info * pi )
{
memset( pi, 0, sizeof(physics_info) );
pi->mass = 10.0f; // This ship weighs 10 units
pi->side_slip_time_const = 0.05f;
pi->rotdamp = 0.1f;
pi->max_vel.xyz.x = 100.0f; //sideways
pi->max_vel.xyz.y = 100.0f; //up/down
pi->max_vel.xyz.z = 100.0f; //forward
pi->max_rear_vel = 100.0f; //backward -- controlled separately
pi->max_rotvel = vm_vec_new(2.0f, 1.0f, 2.0f);
vm_vec_zero(&pi->prev_ramp_vel);
vm_vec_zero(&pi->desired_vel);
pi->slide_accel_time_const=pi->side_slip_time_const; // slide using max_vel.xyz.x & .xyz.y
pi->slide_decel_time_const=pi->side_slip_time_const; // slide using max_vel.xyz.x & .xyz.y
pi->afterburner_decay = 1;
vm_vec_zero(&pi->linear_thrust);
vm_vec_zero(&pi->rotational_thrust);
pi->flags = 0;
// default values for moment of inertia
vm_vec_make( &pi->I_body_inv.vec.rvec, 1e-5f, 0.0f, 0.0f );
vm_vec_make( &pi->I_body_inv.vec.uvec, 0.0f, 1e-5f, 0.0f );
vm_vec_make( &pi->I_body_inv.vec.fvec, 0.0f, 0.0f, 1e-5f );
pi->ai_desired_orient = vmd_zero_matrix; // Asteroth - initialize to the "invalid" orientation, which will be ignored by physics unless set otherwise
vm_vec_zero(&pi->acceleration);
pi->gravity_const = 0.0f;
}
//==========================================================================
// apply_physics - This does correct physics independent of frame rate.
//
// Given:
// damping = damping factor. Setting this to zero make the object instantly
// go to the target velocity. Increasing it makes the object ramp
// up or down to the target velocity.
// desired_vel = the target velocity
// initial_vel = velocity at last call
// t = elapsed time since last call
//
// Returns:
// new_vel = current velocity
// delta_pos = delta position (framevec)
// You can extend this to 3d by calling it 3 times, once for each x,y,z component.
void apply_physics( float damping, float desired_vel, float initial_vel, float t, float * new_vel, float * delta_pos )
{
if ( damping < 0.0001f ) {
if ( delta_pos )
*delta_pos = desired_vel*t;
if ( new_vel )
*new_vel = desired_vel;
} else {
float dv, e;
dv = initial_vel - desired_vel;
e = (float)exp( -t/damping );
if ( delta_pos )
*delta_pos = (1.0f - e)*dv*damping + desired_vel*t;
if ( new_vel )
*new_vel = dv*e + desired_vel;
}
}
float Physics_viewer_bank = 0.0f;
int Physics_viewer_direction = PHYSICS_VIEWER_FRONT;
physics_info * Viewer_physics_info = NULL;
// If you would like Physics_viewer_bank to be tracked (Which is needed
// for rotating 3d bitmaps) call this and pass it a pointer to the
// viewer's physics info.
void physics_set_viewer( physics_info * p, int dir )
{
if ( (Viewer_physics_info != p) || (Physics_viewer_direction != dir) ) {
Viewer_physics_info = p;
Physics_viewer_bank = 0.0f;
Physics_viewer_direction = dir;
}
}
// -----------------------------------------------------------------------------------------------------------
// add rotational velocity & acceleration
void physics_sim_rot(matrix * orient, physics_info * pi, float sim_time )
{
vec3d new_vel;
matrix tmp;
float rotdamp;
float shock_fraction_time_left;
Assert(is_valid_matrix(orient));
Assert(is_valid_vec(&pi->rotvel));
Assert(is_valid_vec(&pi->desired_rotvel));
// Handle special case of shockwave
float shock_amplitude = 0.0f;
if (pi->flags & PF_IN_SHOCKWAVE) {
if (timestamp_elapsed(pi->shockwave_decay)) {
pi->flags &= ~PF_IN_SHOCKWAVE;
rotdamp = pi->rotdamp;
}
else {
shock_fraction_time_left = timestamp_until(pi->shockwave_decay) / (float)SW_BLAST_DURATION;
rotdamp = pi->rotdamp + pi->rotdamp * (SW_ROT_FACTOR - 1) * shock_fraction_time_left;
shock_amplitude = pi->shockwave_shake_amp * shock_fraction_time_left;
}
}
else if (pi->flags & PF_MANEUVER_NO_DAMP) {
rotdamp = 0.0f;
}
else {
rotdamp = pi->rotdamp;
}
angles tangles = vmd_zero_angles;
// "frit" here meaning Framerate_independent_turning
bool frit_ai_wants_to_move = Framerate_independent_turning && !IS_MAT_NULL(&pi->ai_desired_orient);
// In the case an ai entity used angular_move, we should use those calculations instead
if (frit_ai_wants_to_move){
Assert(is_valid_matrix(&pi->ai_desired_orient));
// AI simply get the rotvel they ask for, calculations were already done
pi->rotvel = pi->desired_rotvel;
// Zero it out because the same AI might not call angular_move again next time
// So we'll assume it's going to "go limp" unless it specifies otherwise next frame
vm_vec_zero(&pi->desired_rotvel);
// Pretty much same as above
*orient = pi->ai_desired_orient;
vm_mat_zero(&pi->ai_desired_orient);
} else { // players and no framerate_independent_turning go here
// Do rotational physics with given damping
apply_physics(rotdamp, pi->desired_rotvel.xyz.x, pi->rotvel.xyz.x, sim_time, &new_vel.xyz.x, nullptr);
apply_physics(rotdamp, pi->desired_rotvel.xyz.y, pi->rotvel.xyz.y, sim_time, &new_vel.xyz.y, nullptr);
apply_physics(rotdamp, pi->desired_rotvel.xyz.z, pi->rotvel.xyz.z, sim_time, &new_vel.xyz.z, nullptr);
Assert(is_valid_vec(&new_vel));
pi->rotvel = new_vel;
tangles.p = pi->rotvel.xyz.x * sim_time;
tangles.h = pi->rotvel.xyz.y * sim_time;
tangles.b = pi->rotvel.xyz.z * sim_time;
}
// Make ship shake due to shockwave, decreasing in amplitude at the end of the shockwave
if ( pi->flags & PF_IN_SHOCKWAVE ) {
tangles.p += (float) (Random::next()-Random::HALF_MAX_VALUE) * Random::INV_F_MAX_VALUE * shock_amplitude;
tangles.h += (float) (Random::next()-Random::HALF_MAX_VALUE) * Random::INV_F_MAX_VALUE * shock_amplitude;
}
vm_angles_2_matrix(&pi->last_rotmat, &tangles );
vm_matrix_x_matrix( &tmp, orient, &pi->last_rotmat );
*orient = tmp;
vm_orthogonalize_matrix(orient);
}
// -----------------------------------------------------------------------------------------------------------
// add rotational velocity & acceleration
void physics_sim_rot_editor(matrix * orient, physics_info * pi, float sim_time)
{
angles tangles;
vec3d new_vel;
matrix tmp;
angles t1, t2;
apply_physics( pi->rotdamp, pi->desired_rotvel.xyz.x, pi->rotvel.xyz.x, sim_time,
&new_vel.xyz.x, NULL );
apply_physics( pi->rotdamp, pi->desired_rotvel.xyz.y, pi->rotvel.xyz.y, sim_time,
&new_vel.xyz.y, NULL );
apply_physics( pi->rotdamp, pi->desired_rotvel.xyz.z, pi->rotvel.xyz.z, sim_time,
&new_vel.xyz.z, NULL );
pi->rotvel = new_vel;
tangles.p = pi->rotvel.xyz.x*sim_time;
tangles.h = pi->rotvel.xyz.y*sim_time;
tangles.b = pi->rotvel.xyz.z*sim_time;
t1 = t2 = tangles;
t1.h = 0.0f; t1.b = 0.0f;
t2.p = 0.0f; t2.b = 0.0f;
// put in p & b like normal
vm_angles_2_matrix(&pi->last_rotmat, &t1 );
vm_matrix_x_matrix( &tmp, orient, &pi->last_rotmat );
// Put in heading separately
vm_angles_2_matrix(&pi->last_rotmat, &t2 );
vm_matrix_x_matrix( orient, &pi->last_rotmat, &tmp );
vm_orthogonalize_matrix(orient);
}
// Adds velocity to position
// finds velocity and displacement in local coords
void physics_sim_vel(vec3d * position, physics_info * pi, matrix *orient, vec3d* gravity, float sim_time)
{
vec3d local_disp; // displacement in this frame
vec3d local_v_in; // velocity in local coords at the start of this frame
vec3d local_desired_vel; // desired velocity in local coords
vec3d local_v_out; // velocity in local coords following this frame
vec3d damp;
vec3d old_vel = pi->vel;
// Maybe clear the reduced_damp flag.
// This fixes the problem of the player getting near-instantaneous acceleration under unknown circumstances.
// The larger problem is probably that PF_USE_VEL is getting stuck set.
if ((pi->flags & PF_REDUCED_DAMP) && (timestamp_elapsed(pi->reduced_damp_decay))) {
pi->flags &= ~PF_REDUCED_DAMP;
}
// Set up damping constants based on special conditions
// dead
if (pi->flags & PF_DEAD_DAMP) {
// side_slip_time_const is already quite large and now needs to be applied in all directions
vm_vec_make( &damp, pi->side_slip_time_const, pi->side_slip_time_const, pi->side_slip_time_const );
}
// case of shock, weapon, collide, etc.
else if (pi->flags & PF_REDUCED_DAMP) {
if ( timestamp_elapsed(pi->reduced_damp_decay) ) {
vm_vec_make( &damp, pi->side_slip_time_const, pi->side_slip_time_const, 0.0f );
} else {
// damp is multiplied by fraction and not fraction^2, gives better collision separation
float reduced_damp_fraction_time_left = timestamp_until( pi->reduced_damp_decay ) / (float) REDUCED_DAMP_TIME;
damp.xyz.x = pi->side_slip_time_const * ( 1 + (REDUCED_DAMP_FACTOR-1) * reduced_damp_fraction_time_left );
damp.xyz.y = pi->side_slip_time_const * ( 1 + (REDUCED_DAMP_FACTOR-1) * reduced_damp_fraction_time_left );
damp.xyz.z = pi->side_slip_time_const * reduced_damp_fraction_time_left * REDUCED_DAMP_FACTOR;
}
}
// no damping at all
else if (pi->flags & PF_MANEUVER_NO_DAMP) {
damp = vmd_zero_vector;
}
// newtonian
else if (pi->flags & PF_NEWTONIAN_DAMP) {
vm_vec_make( &damp, pi->side_slip_time_const, pi->side_slip_time_const, pi->side_slip_time_const );
}
// regular damping
else {
vm_vec_make( &damp, pi->side_slip_time_const, pi->side_slip_time_const, 0.0f );
}
// Note: CANNOT maintain a *local velocity* since a rotation can occur in this frame.
// thus the local velocity of in the next frame can be different (this would require rotate to change local vel
// and this is not desired
// get local components of current velocity
vm_vec_rotate (&local_v_in, &pi->vel, orient);
// get local components of desired velocity
vm_vec_rotate (&local_desired_vel, &pi->desired_vel, orient);
// find updated LOCAL velocity and position in the local x direction
apply_physics (damp.xyz.x, local_desired_vel.xyz.x, local_v_in.xyz.x, sim_time, &local_v_out.xyz.x, &local_disp.xyz.x);
// find updated LOCAL velocity and position in the local y direction
apply_physics (damp.xyz.y, local_desired_vel.xyz.y, local_v_in.xyz.y, sim_time, &local_v_out.xyz.y, &local_disp.xyz.y);
// find updated LOCAL velocity and position in the local z direction
// for player ship, damp should normally be zero, but may be altered in a shockwave
// in death, shockwave,etc. we want damping time const large for all 3 axes
// warp in test - make excessive speed drop exponentially from max allowed
// become (0.01x in 3 sec)
vec3d grav_disp = vmd_zero_vector;
vec3d grav_vel = vmd_zero_vector;
int special_warp_in = FALSE;
float excess = local_v_in.xyz.z - pi->max_vel.xyz.z;
if (excess > SUPERCAP_WARP_EXCESS_SPD_THRESHOLD && (pi->flags & PF_SUPERCAP_WARP_IN)) {
special_warp_in = TRUE;
float exp_factor = float(exp(-sim_time / SUPERCAP_WARP_T_CONST));
local_v_out.xyz.z = pi->max_vel.xyz.z + excess * exp_factor;
local_disp.xyz.z = (pi->max_vel.xyz.z * sim_time) + excess * (float(SUPERCAP_WARP_T_CONST) * (1.0f - exp_factor));
} else if (pi->flags & PF_SUPERCAP_WARP_OUT) {
float exp_factor = float(exp(-sim_time / SUPERCAP_WARP_T_CONST));
vec3d temp;
vm_vec_rotate(&temp, &pi->prev_ramp_vel, orient);
float deficeit = temp.xyz.z - local_v_in.xyz.z;
local_v_out.xyz.z = local_v_in.xyz.z + deficeit * (1.0f - exp_factor);
local_disp.xyz.z = (local_v_in.xyz.z * sim_time) + deficeit * (sim_time - (float(SUPERCAP_WARP_T_CONST) * (1.0f - exp_factor)));
} else {
apply_physics (damp.xyz.z, local_desired_vel.xyz.z, local_v_in.xyz.z, sim_time, &local_v_out.xyz.z, &local_disp.xyz.z);
if (pi->gravity_const != 0.0f) {
grav_vel = *gravity * sim_time * pi->gravity_const;
grav_disp = (grav_vel * sim_time) * 0.5; // 1/2 * at^2
}
}
// maybe turn off special warp in flag
if ((pi->flags & PF_SUPERCAP_WARP_IN) && (excess < SUPERCAP_WARP_EXCESS_SPD_THRESHOLD)) {
pi->flags &= ~(PF_SUPERCAP_WARP_IN);
}
if (!(pi->flags & PF_SCRIPTED_VELOCITY)) {
// update world position from local to world coords using orient
vec3d world_disp;
vm_vec_unrotate(&world_disp, &local_disp, orient);
*position += world_disp;
*position += grav_disp;
// update world velocity
vm_vec_unrotate(&pi->vel, &local_v_out, orient);
pi->vel += grav_vel;
} else {
// velocity set by script, just trust whatever that is, at least for this frame
*position += pi->vel * sim_time;
pi->flags &= ~PF_SCRIPTED_VELOCITY;
}
// update acceleration
pi->acceleration = pi->vel - old_vel;
pi->acceleration *= 1 / sim_time;
if (special_warp_in) {
vm_vec_rotate(&pi->prev_ramp_vel, &pi->vel, orient);
}
}
// -----------------------------------------------------------------------------------------------------------
// Simulate a physics object for this frame
void physics_sim(vec3d* position, matrix* orient, physics_info* pi, vec3d* gravity, float sim_time)
{
// check flag which tells us whether or not to do velocity translation
if (pi->flags & PF_CONST_VEL) {
*position += pi->vel * sim_time;
}
else
{
if (pi->flags & PF_BALLISTIC) {
*position += pi->vel * sim_time + *gravity * sim_time * sim_time * pi->gravity_const * 0.5f; // vt + 1/2 * at^2
pi->vel += *gravity * sim_time * pi->gravity_const;
} else {
physics_sim_vel(position, pi, orient, gravity, sim_time);
}
physics_sim_rot(orient, pi, sim_time);
pi->speed = vm_vec_mag(&pi->vel); // Note, cannot use quick version, causes cumulative error, increasing speed.
pi->fspeed = vm_vec_dot(&orient->vec.fvec, &pi->vel); // instead of vector magnitude -- use only forward vector since we are only interested in forward velocity
}
}
// -----------------------------------------------------------------------------------------------------------
// Simulate a physics object for this frame. Used by the editor. The difference between
// this function and physics_sim() is that this one uses a heading change to rotate around
// the universal Y axis, rather than the local orientation's Y axis. Banking is also ignored.
void physics_sim_editor(vec3d *position, matrix * orient, physics_info * pi, float sim_time )
{
physics_sim_vel(position, pi,orient, &vmd_zero_vector, sim_time);
physics_sim_rot_editor(orient, pi, sim_time);
pi->speed = vm_vec_mag_quick(&pi->vel);
pi->fspeed = vm_vec_dot(&orient->vec.fvec, &pi->vel); // instead of vector magnitude -- use only forward vector since we are only interested in forward velocity
}
// physics_read_flying_controls()
//
// parmeters: *orient ==>
// *pi ==>
// *ci ==>
// Adam: Uncomment-out this define to enable banking while turning.
#define BANK_WHEN_TURN
// function looks at the flying controls and the current velocity to determine a goal velocity
// function determines velocity in object's reference frame and goal velocity in object's reference frame
void physics_read_flying_controls( matrix * orient, physics_info * pi, control_info * ci, float sim_time, vec3d *wash_rot)
{
vec3d goal_vel; // goal velocity in local coords, *not* accounting for ramping of velcity
float ramp_time_const; // time constant for velocity ramping
// apply throttle, unless reverse thrusters are held down
if (ci->forward != -1.0f)
ci->forward += (ci->forward_cruise_percent / 100.0f);
// give control input to cause rotation in engine wash
extern int Wash_on;
if ( wash_rot && Wash_on ) {
ci->pitch += wash_rot->xyz.x;
ci->bank += wash_rot->xyz.z;
ci->heading += wash_rot->xyz.y;
}
if ( pi->flags & PF_AFTERBURNER_ON ){
//SparK: modifield to accept reverse burners
if (!(pi->afterburner_max_reverse_vel > 0.0f)){
ci->forward = 1.0f;
}
}
// maybe we don't need to clamp our velocity...
if ( !(ci->control_flags & CIF_DONT_CLAMP_MAX_VELOCITY) )
{
if (ci->pitch > 1.0f ) ci->pitch = 1.0f;
else if (ci->pitch < -1.0f ) ci->pitch = -1.0f;
if (ci->vertical > 1.0f ) ci->vertical = 1.0f;
else if (ci->vertical < -1.0f ) ci->vertical = -1.0f;
if (ci->heading > 1.0f ) ci->heading = 1.0f;
else if (ci->heading < -1.0f ) ci->heading = -1.0f;
if (ci->sideways > 1.0f ) ci->sideways = 1.0f;
else if (ci->sideways < -1.0f ) ci->sideways = -1.0f;
if (ci->bank > 1.0f ) ci->bank = 1.0f;
else if (ci->bank < -1.0f ) ci->bank = -1.0f;
if (ci->forward > 1.0f ) ci->forward = 1.0f;
else if (ci->forward < -1.0f ) ci->forward = -1.0f;
}
// If Framerate_independent_turning is on, AI don't use CI to turn
if (!Framerate_independent_turning || IS_MAT_NULL(&pi->ai_desired_orient)){
if (!Flight_controls_follow_eyepoint_orientation || (Player_obj == nullptr)) {
// Default behavior; eyepoint orientation has no effect on controls
pi->desired_rotvel.xyz.x = ci->pitch * pi->max_rotvel.xyz.x;
pi->desired_rotvel.xyz.y = ci->heading * pi->max_rotvel.xyz.y;
} else {
// Optional behavior; pitch and yaw are always relative to the eyepoint
// orientation (excluding slew)
vec3d tmp_vec, new_rotvel;
matrix tmp_mat, eyemat, rotvelmat;
object_get_eye(&tmp_vec, &eyemat, Player_obj, false);
vm_copy_transpose(&tmp_mat, &Player_obj->orient);
vm_matrix_x_matrix(&rotvelmat, &tmp_mat, &eyemat);
vm_vec_rotate(&new_rotvel, &pi->max_rotvel, &rotvelmat);
vm_vec_unrotate(&tmp_vec, &pi->max_rotvel, &rotvelmat);
new_rotvel.xyz.x = tmp_vec.xyz.x;
new_rotvel.xyz.x = ci->pitch * new_rotvel.xyz.x;
new_rotvel.xyz.y = ci->heading * new_rotvel.xyz.y;
vm_vec_unrotate(&tmp_vec, &new_rotvel, &rotvelmat);
pi->desired_rotvel = tmp_vec;
}
float delta_bank;
#ifdef BANK_WHEN_TURN
if ( ci->control_flags & CIF_DONT_BANK_WHEN_TURNING )
delta_bank = 0.0f;
else
{
// To change direction of bank, negate the whole expression.
// To increase magnitude of banking, decrease denominator.
// Adam: The following statement is all the math for banking while turning.
delta_bank = - (ci->heading * pi->max_rotvel.xyz.y) * pi->delta_bank_const;
}
#else
delta_bank = 0.0f;
#endif
pi->desired_rotvel.xyz.z = ci->bank * pi->max_rotvel.xyz.z + delta_bank;
}
// update 'thrust values' (these are cosmetic and do not affect physical behavior)
if (Thruster_easing > 0.0f) {
pi->linear_thrust.xyz.z = ci->forward + (pi->linear_thrust.xyz.z - ci->forward) * exp2f(-Thruster_easing * sim_time);
pi->linear_thrust.xyz.y = ci->vertical + (pi->linear_thrust.xyz.y - ci->vertical) * exp2f(-Thruster_easing * sim_time);
pi->linear_thrust.xyz.x = ci->sideways + (pi->linear_thrust.xyz.x - ci->sideways) * exp2f(-Thruster_easing * sim_time);
pi->rotational_thrust.xyz.z = ci->bank + (pi->rotational_thrust.xyz.z - ci->bank) * exp2f(-Thruster_easing * sim_time);
pi->rotational_thrust.xyz.y = ci->heading + (pi->rotational_thrust.xyz.y - ci->heading) * exp2f(-Thruster_easing * sim_time);
pi->rotational_thrust.xyz.x = ci->pitch + (pi->rotational_thrust.xyz.x - ci->pitch) * exp2f(-Thruster_easing * sim_time);
} else {
pi->linear_thrust.xyz.z = ci->forward;
pi->linear_thrust.xyz.y = ci->vertical;
pi->linear_thrust.xyz.x = ci->sideways;
pi->rotational_thrust.xyz.z = ci->bank;
pi->rotational_thrust.xyz.y = ci->heading;
pi->rotational_thrust.xyz.x = ci->pitch;
}
if ( pi->flags & PF_AFTERBURNER_ON ) {
goal_vel.xyz.x = ci->sideways*pi->afterburner_max_vel.xyz.x;
goal_vel.xyz.y = ci->vertical*pi->afterburner_max_vel.xyz.y;
if(ci->forward < 0.0f)
goal_vel.xyz.z = ci->forward* pi->afterburner_max_reverse_vel;
else
goal_vel.xyz.z = ci->forward* pi->afterburner_max_vel.xyz.z;
}
else if ( pi->flags & PF_BOOSTER_ON ) {
goal_vel.xyz.x = ci->sideways*pi->booster_max_vel.xyz.x;
goal_vel.xyz.y = ci->vertical*pi->booster_max_vel.xyz.y;
goal_vel.xyz.z = ci->forward* pi->booster_max_vel.xyz.z;
}
else {
goal_vel.xyz.x = ci->sideways*pi->max_vel.xyz.x;
goal_vel.xyz.y = ci->vertical*pi->max_vel.xyz.y;
goal_vel.xyz.z = ci->forward* pi->max_vel.xyz.z;
}
if ( goal_vel.xyz.z < -pi->max_rear_vel && !(pi->flags & PF_AFTERBURNER_ON) && !(ci->control_flags & CIF_DONT_CLAMP_MAX_VELOCITY) )
goal_vel.xyz.z = -pi->max_rear_vel;
// instantaneous acceleration is trickier than it looks
if ( ci->control_flags & CIF_INSTANTANEOUS_ACCELERATION ) {
// ramp up instantaneously
pi->prev_ramp_vel = goal_vel;
// translate local desired velocities to world velocities, as below
vm_vec_zero(&pi->desired_vel);
vm_vec_scale_add2(&pi->desired_vel, &orient->vec.rvec, pi->prev_ramp_vel.xyz.x);
vm_vec_scale_add2(&pi->desired_vel, &orient->vec.uvec, pi->prev_ramp_vel.xyz.y);
vm_vec_scale_add2(&pi->desired_vel, &orient->vec.fvec, pi->prev_ramp_vel.xyz.z);
}
// standard acceleration
else if ( pi->flags & PF_ACCELERATES ) {
//
// Determine *resultant* DESIRED VELOCITY (desired_vel) accounting for RAMPING of velocity
// Use LOCAL coordinates
// if slide_enabled, ramp velocity for x and y, otherwise set goal (0)
// always ramp velocity for z
//
// If reduced damp in effect, then adjust ramp_velocity and desired_velocity can not change as fast.
// Scale according to reduced_damp_time_expansion.
float reduced_damp_ramp_time_expansion;
if ( pi->flags & PF_REDUCED_DAMP && !timestamp_elapsed(pi->reduced_damp_decay) ) {
float reduced_damp_fraction_time_left = timestamp_until( pi->reduced_damp_decay ) / (float) REDUCED_DAMP_TIME;
reduced_damp_ramp_time_expansion = 1.0f + (REDUCED_DAMP_FACTOR-1) * reduced_damp_fraction_time_left;
} else {
reduced_damp_ramp_time_expansion = 1.0f;
}
if (pi->flags & PF_SLIDE_ENABLED) {
// determine the local velocity
// deterimine whether accelerating or deceleration toward goal for x
if ( goal_vel.xyz.x > 0.0f ) {
if ( goal_vel.xyz.x >= pi->prev_ramp_vel.xyz.x )
ramp_time_const = pi->slide_accel_time_const;
else
ramp_time_const = pi->slide_decel_time_const;
} else if ( goal_vel.xyz.x < 0.0f ) {
if ( goal_vel.xyz.x <= pi->prev_ramp_vel.xyz.x )
ramp_time_const = pi->slide_accel_time_const;
else
ramp_time_const = pi->slide_decel_time_const;
} else {
ramp_time_const = pi->slide_decel_time_const;
}
// If reduced damp in effect, then adjust ramp_velocity and desired_velocity can not change as fast
if ( pi->flags & PF_REDUCED_DAMP ) {
ramp_time_const *= reduced_damp_ramp_time_expansion;
}
pi->prev_ramp_vel.xyz.x = velocity_ramp(pi->prev_ramp_vel.xyz.x, goal_vel.xyz.x, ramp_time_const, sim_time);
// deterimine whether accelerating or deceleration toward goal for y
if ( goal_vel.xyz.y > 0.0f ) {
if ( goal_vel.xyz.y >= pi->prev_ramp_vel.xyz.y )
ramp_time_const = pi->slide_accel_time_const;
else
ramp_time_const = pi->slide_decel_time_const;
} else if ( goal_vel.xyz.y < 0.0f ) {
if ( goal_vel.xyz.y <= pi->prev_ramp_vel.xyz.y )
ramp_time_const = pi->slide_accel_time_const;
else
ramp_time_const = pi->slide_decel_time_const;
} else {
ramp_time_const = pi->slide_decel_time_const;
}
// If reduced damp in effect, then adjust ramp_velocity and desired_velocity can not change as fast
if ( pi->flags & PF_REDUCED_DAMP ) {
ramp_time_const *= reduced_damp_ramp_time_expansion;
}
pi->prev_ramp_vel.xyz.y = velocity_ramp( pi->prev_ramp_vel.xyz.y, goal_vel.xyz.y, ramp_time_const, sim_time);
} else {
// slide not enabled
pi->prev_ramp_vel.xyz.x = 0.0f;
pi->prev_ramp_vel.xyz.y = 0.0f;
}
// deterimine whether accelerating or deceleration toward goal for z
if ( goal_vel.xyz.z > 0.0f ) {
if ( goal_vel.xyz.z >= pi->prev_ramp_vel.xyz.z ) {
if ( pi->flags & PF_AFTERBURNER_ON )
ramp_time_const = pi->afterburner_forward_accel_time_const;
else if (pi->flags & PF_BOOSTER_ON)
ramp_time_const = pi->booster_forward_accel_time_const;
else
ramp_time_const = pi->forward_accel_time_const;
} else {
ramp_time_const = pi->forward_decel_time_const;
}
} else if ( goal_vel.xyz.z < 0.0f ) {
if ( pi->flags & PF_AFTERBURNER_ON )
ramp_time_const = pi->afterburner_reverse_accel;
else
ramp_time_const = pi->forward_decel_time_const;
} else {
ramp_time_const = pi->forward_decel_time_const;
}
// If reduced damp in effect, then adjust ramp_velocity and desired_velocity can not change as fast
if ( pi->flags & PF_REDUCED_DAMP ) {
ramp_time_const *= reduced_damp_ramp_time_expansion;
}
pi->prev_ramp_vel.xyz.z = velocity_ramp(pi->prev_ramp_vel.xyz.z, goal_vel.xyz.z, ramp_time_const, sim_time);
//Deternine the current dynamic glide cap, and ramp to it
//This is outside the normal "glide" block since we want the cap to adjust whether or not the ship is in glide mode
float dynamic_glide_cap_goal = 0.0;
if (pi->flags & PF_AFTERBURNER_ON) {
dynamic_glide_cap_goal = ( goal_vel.xyz.z >= 0.0f ) ? pi->afterburner_max_vel.xyz.z : pi->afterburner_max_reverse_vel;
}
else {
//Use the maximum value in X, Y, and Z (including overclocking)
dynamic_glide_cap_goal = std::max({ pi->max_vel.xyz.x,pi->max_vel.xyz.y, pi->max_vel.xyz.z });
}
pi->cur_glide_cap = velocity_ramp(pi->cur_glide_cap, dynamic_glide_cap_goal, ramp_time_const, sim_time);
if ( (pi->flags & PF_GLIDING) || (pi->flags & PF_FORCE_GLIDE ) ) {
pi->desired_vel = pi->vel;
//SUSHI: A (hopefully better) approach to dealing with accelerations in glide mode
//Get *actual* current velocities along each axis and use those instead of ramped velocities
vec3d local_vel;
vm_vec_rotate(&local_vel, &pi->vel, orient);
//Having pi->glide_cap == 0 means we're using a dynamic glide cap
float curGlideCap = 0.0f;
if (pi->glide_cap == 0.0f)
curGlideCap = pi->cur_glide_cap;
else
curGlideCap = pi->glide_cap;
//If we're near the (positive) glide cap, decay velocity where we aren't thrusting
//This is a hack, but makes the flight feel a lot smoother
//Don't do this if we aren't applying any thrust, we have no glide cap, or the accel multiplier is 0 (no thrust while gliding)
float cap_decay_threshold = 0.95f;
float cap_decay_amount = 0.2f;
if (curGlideCap >= 0.0f && vm_vec_mag(&pi->desired_vel) >= cap_decay_threshold * curGlideCap &&
vm_vec_mag(&goal_vel) > 0.0f &&
pi->glide_accel_mult != 0.0f)
{
if (goal_vel.xyz.x == 0.0f)
vm_vec_scale_add2(&pi->desired_vel, &orient->vec.rvec, -cap_decay_amount * local_vel.xyz.x);
if (goal_vel.xyz.y == 0.0f)
vm_vec_scale_add2(&pi->desired_vel, &orient->vec.uvec, -cap_decay_amount * local_vel.xyz.y);
if (goal_vel.xyz.z == 0.0f)
vm_vec_scale_add2(&pi->desired_vel, &orient->vec.fvec, -cap_decay_amount * local_vel.xyz.z);
}
//The glide_ramp function uses (basically) the same math as the velocity ramp so that thruster power is consistent
//Only ramp if the glide cap is positive
float xVal = glide_ramp(local_vel.xyz.x, goal_vel.xyz.x, pi->slide_accel_time_const, pi->glide_accel_mult, sim_time);
float yVal = glide_ramp(local_vel.xyz.y, goal_vel.xyz.y, pi->slide_accel_time_const, pi->glide_accel_mult, sim_time);
float zVal = 0.0;
if (pi->flags & PF_AFTERBURNER_ON)
zVal = glide_ramp(local_vel.xyz.z, goal_vel.xyz.z, pi->afterburner_forward_accel_time_const, pi->glide_accel_mult, sim_time);
else {
if (goal_vel.xyz.z >= 0.0f)
zVal = glide_ramp(local_vel.xyz.z, goal_vel.xyz.z, pi->forward_accel_time_const, pi->glide_accel_mult, sim_time);
else
zVal = glide_ramp(local_vel.xyz.z, goal_vel.xyz.z, pi->forward_decel_time_const, pi->glide_accel_mult, sim_time);
}
//Compensate for effect of dampening: normal flight cheats here, so /we make up for it this way so glide acts the same way
xVal *= pi->side_slip_time_const / sim_time;
yVal *= pi->side_slip_time_const / sim_time;
if (pi->flags & PF_NEWTONIAN_DAMP)
zVal *= pi->side_slip_time_const / sim_time;
vm_vec_scale_add2(&pi->desired_vel, &orient->vec.fvec, zVal);
vm_vec_scale_add2(&pi->desired_vel, &orient->vec.rvec, xVal);
vm_vec_scale_add2(&pi->desired_vel, &orient->vec.uvec, yVal);
// Only do the glide cap if we have one and are actively thrusting in some direction.
// Unless AIPF2_GLIDE_DECAY_REQUIRES_THRUST isn't set. -MageKing17
if ( curGlideCap >= 0.0f && (!(The_mission.ai_profile->flags[AI::Profile_Flags::Glide_decay_requires_thrust]) || ci->forward != 0.0f || ci->sideways != 0.0f || ci->vertical != 0.0f) ) {
float currentmag = vm_vec_mag(&pi->desired_vel);
if ( currentmag > curGlideCap ) {
vm_vec_scale( &pi->desired_vel, curGlideCap / currentmag );
}
}
}
else
{
// this translates local desired velocities to world velocities
vm_vec_zero(&pi->desired_vel);
vm_vec_scale_add2( &pi->desired_vel, &orient->vec.rvec, pi->prev_ramp_vel.xyz.x );
vm_vec_scale_add2( &pi->desired_vel, &orient->vec.uvec, pi->prev_ramp_vel.xyz.y );
vm_vec_scale_add2( &pi->desired_vel, &orient->vec.fvec, pi->prev_ramp_vel.xyz.z );
}
}
// object does not accelerate (PF_ACCELERATES not set)
else {
pi->desired_vel = pi->vel;
}
}
void physics_maybe_reset_speed_after_whack(physics_info *pi, const vec3d *previous_vel, const char *source)
{
// for release builds
SCP_UNUSED(source);
if (pi->flags & PF_USE_VEL || The_mission.ai_profile->flags[AI::Profile_Flags::Dont_limit_change_in_speed_due_to_physics_whack])
return;
vec3d delta_vel = pi->vel - *previous_vel;
float speed_delta = vm_vec_mag(&delta_vel);
// Limit change in speed due to whack
if (speed_delta > MAX_SHIP_WHACK_DELTA)
{
// Get DaveA
nprintf(("Physics", "speed reset in %s [change in speed: %f; current speed: %f]\n", source, speed_delta, vm_vec_mag(&pi->vel)));
vm_vec_normalize(&delta_vel);
vm_vec_scale_add(&pi->vel, previous_vel, &delta_vel, RESET_SHIP_WHACK_DELTA);
}
}
#define WHACK_LIMIT 0.001f
#define ROTVEL_WHACK_CONST 0.12f
// -----------------------------------------------------------------------------------------------------------
// Returns true if this impulse is below the limit and should be ignored.
bool whack_below_limit(const vec3d* impulse)
{
return (fl_abs(impulse->xyz.x) < WHACK_LIMIT) && (fl_abs(impulse->xyz.y) < WHACK_LIMIT) &&
(fl_abs(impulse->xyz.z) < WHACK_LIMIT);
}
bool whack_below_limit(float impulse)
{
return fl_abs(impulse) < WHACK_LIMIT;
}
// ----------------------------------------------------------------------------
// physics_calculate_and_apply_whack changes the rotaional and linear velocites of a ship due to
// an instantaneous whack.
//
// input: impulse => impulse vector (direction and magnitude of the impulse)
// pos => vector from center of mass to location (in world coords) of where the force acts
// pi => pointer to phys_info struct of object getting whacked
// orient => orientation matrix (needed to set rotational impulse in body coords)
//
void physics_calculate_and_apply_whack(const vec3d *impulse, const vec3d *pos, physics_info *pi, const matrix *orient, const matrix *inv_moi)
{
vec3d local_angular_impulse, angular_impulse;
// Detect null vector.
if (whack_below_limit(impulse))
return;
// first do the rotational velocity
// calculate the angular impulse on the body based on the point on the
// object that was hit and the momentum being applied to the object
vm_vec_cross(&angular_impulse, pos, impulse);
vm_vec_rotate ( &local_angular_impulse, &angular_impulse, orient );
vec3d delta_rotvel;
vm_vec_rotate(&delta_rotvel, &local_angular_impulse, inv_moi);
vec3d delta_vel = *impulse * (1.0f / pi->mass);
physics_apply_whack(vm_vec_mag(impulse), pi, &delta_rotvel, &delta_vel, orient);
}
// This function applies the calculated delta rotational and linear velocities calculated by physics_calculate_and_apply_whack
// or dock_calculate_and_apply_whack_docked_object in objectdock.cpp if it was a docked object
void physics_apply_whack(float orig_impulse, physics_info* pi, const vec3d *delta_rotvel, const vec3d* delta_vel, const matrix* orient)
{
Assertion((pi != nullptr) && (delta_rotvel != nullptr) && (delta_vel != nullptr) && (orient != nullptr),
"physics_apply_whack_direct invalid argument(s)");
vm_vec_scale_add2(&pi->rotvel, delta_rotvel, ROTVEL_WHACK_CONST);
//mprintf(("Whack: %7.3f %7.3f %7.3f\n", pi->rotvel.xyz.x, pi->rotvel.xyz.y, pi->rotvel.xyz.z));
// instant whack on the velocity
// reduce damping on all axes
pi->flags |= PF_REDUCED_DAMP;
update_reduced_damp_timestamp( pi, orig_impulse );
// find time for shake from weapon to end
int dtime = timestamp_until(pi->afterburner_decay);
if (dtime < WEAPON_SHAKE_TIME) {
pi->afterburner_decay = timestamp( WEAPON_SHAKE_TIME );
}
vec3d previous_vel = pi->vel;
vm_vec_add2(&pi->vel, delta_vel);
physics_maybe_reset_speed_after_whack(pi, &previous_vel, "physics_apply_whack");
// set so velocity will ramp starting from current speed
// ramped velocity is now affected by collision
vm_vec_rotate( &pi->prev_ramp_vel, &pi->vel, orient );
}
// function generates a velocity ramp with a given time constant independent of frame rate
// uses an exponential approach to desired velocity and a cheat when close to improve closure speed
float velocity_ramp (float v_in, float v_goal, float ramp_time_const, float t)
{
float delta_v;
float decay_factor;
float dist;
// JAS: If no time elapsed, change nothing
if ( t==0.0f )
return v_in;
delta_v = v_goal - v_in;
dist = (float)fl_abs(delta_v);
// hack to speed up closure when close to goal
if (dist < ramp_time_const/3)
ramp_time_const = dist / 3;
// Rather than get a divide by zero, just go to the goal
if ( ramp_time_const < 0.0001f ) {
return v_goal;
}
// determine decay factor (ranges from 0 for short times to 1 for long times)
// when decay factor is near 0, the velocity in nearly unchanged
// when decay factor in near 1, the velocity approaches goal
decay_factor = (float)exp(- t / ramp_time_const);
return (v_in + delta_v * (1.0f - decay_factor) );
}
//Handles thrust values when gliding. Purposefully similar to velocity_ramp in order to yeild a similar "feel" in
//terms of thruster accels (as much as possible)
//This is all in the local frame of reference for a single movement axis
float glide_ramp (float v_in, float v_goal, float ramp_time_const, float accel_mult, float t)
{
if (v_goal == 0.0f) {
return 0.0f;
}
//This approach to delta_v allows us to ramp accelerations even in glide mode
//Get the difference in velocity between current and goal as thrust
//(capped by the goal velocity on one end and 0 on the other)
//If accel_mult is < 0, don't ramp (fixed acceleration)
float delta_v = 0.0f;
if (accel_mult < 0.0f) {
if (v_goal > 0.0f) {
delta_v = MAX(MIN(v_goal - v_in, v_goal), 0.0f);
}
else {
delta_v = MIN(MAX(v_goal - v_in, v_goal), 0.0f);
}
}
else {
delta_v = v_goal * accel_mult;
}
//Calculate the (decayed) thrust
float decay_factor = (ramp_time_const > 0.0f) ? (1.0f - (float)exp(-t / ramp_time_const)) : 1.0f;
return delta_v * decay_factor;
}
// ----------------------------------------------------------------------------
// physics_apply_shock applies applies a shockwave to an object. This causes a velocity impulse and
// and a rotational impulse. This is different than physics_apply_whack since a shock wave is a pressure
// wave which acts over the *surface* of the object, not a point.
//
// inputs: direction_vec => a position vector whose direction is from the center of the shock wave to the object
// pressure => the pressure of the shock wave at the object
// pi => physics_info structure
// orient => matrix orientation of the object
// min => vector of minimum values of the bounding box
// max => vector of maximum values of the bounding box
// radius => bounding box radius of the object, used for scaling rotation
//
// outputs: makes changes to physics_info structure rotvel and vel variables
//
#define STD_PRESSURE 1000 // amplitude of standard shockwave blasts
#define MIN_RADIUS 10 // radius within which full rotvel and shake applied
#define MAX_RADIUS 50 // radius at which no rotvel or shake applied
#define MAX_ROTVEL 0.4 // max rotational velocity
#define MAX_SHAKE 0.1 // max rotational amplitude of shake
#define MAX_VEL 8 // max vel from shockwave
void physics_apply_shock(vec3d *direction_vec, float pressure, physics_info *pi, matrix *orient, vec3d *min, vec3d *max, float radius)
{
vec3d normal;
vec3d local_torque, temp_torque, torque;
vec3d impact_vec;
vec3d area;
vec3d sin;
if (radius > MAX_RADIUS) {
return;
}
vm_vec_normalize_safe ( direction_vec );
area.xyz.x = (max->xyz.y - min->xyz.y) * (max->xyz.z - min->xyz.z);
area.xyz.y = (max->xyz.x - min->xyz.x) * (max->xyz.z - min->xyz.z);
area.xyz.z = (max->xyz.x - min->xyz.x) * (max->xyz.y - min->xyz.y);
normal.xyz.x = vm_vec_dot( direction_vec, &orient->vec.rvec );
normal.xyz.y = vm_vec_dot( direction_vec, &orient->vec.uvec );
normal.xyz.z = vm_vec_dot( direction_vec, &orient->vec.fvec );
sin.xyz.x = fl_sqrt( fl_abs(1.0f - normal.xyz.x*normal.xyz.x) );
sin.xyz.y = fl_sqrt( fl_abs(1.0f - normal.xyz.y*normal.xyz.y) );
sin.xyz.z = fl_sqrt( fl_abs(1.0f - normal.xyz.z*normal.xyz.z) );
vm_vec_make( &torque, 0.0f, 0.0f, 0.0f );
// find the torque exerted due to the shockwave hitting each face
// model the effect of the shockwave as if the shockwave were a plane of projectiles,
// all moving in the direction direction_vec. then find the torque as the cross prod
// of the force (pressure * area * normal * sin * scale * mass)
// normal takes account the fraction of the surface exposed to the shockwave
// the sin term is not technically needed but "feels" better
// scale factors out the increase in area with larger objects
// more massive objects get less rotation
// find torque due to forces on the right/left face
if ( normal.xyz.x < 0.0f ) // normal < 0, hits the right face
vm_vec_copy_scale( &impact_vec, &orient->vec.rvec, max->xyz.x * pressure * area.xyz.x * normal.xyz.x * sin.xyz.x / pi->mass );
else // normal > 0, hits the left face
vm_vec_copy_scale( &impact_vec, &orient->vec.rvec, min->xyz.x * pressure * area.xyz.x * -normal.xyz.x * sin.xyz.x / pi->mass );
vm_vec_cross( &temp_torque, &impact_vec, direction_vec );
vm_vec_add2( &torque, &temp_torque );
// find torque due to forces on the up/down face
if ( normal.xyz.y < 0.0f )
vm_vec_copy_scale( &impact_vec, &orient->vec.uvec, max->xyz.y * pressure * area.xyz.y * normal.xyz.y * sin.xyz.y / pi->mass );
else
vm_vec_copy_scale( &impact_vec, &orient->vec.uvec, min->xyz.y * pressure * area.xyz.y * -normal.xyz.y * sin.xyz.y / pi->mass );
vm_vec_cross( &temp_torque, &impact_vec, direction_vec );
vm_vec_add2( &torque, &temp_torque );
// find torque due to forces on the forward/backward face
if ( normal.xyz.z < 0.0f )
vm_vec_copy_scale( &impact_vec, &orient->vec.fvec, max->xyz.z * pressure * area.xyz.z * normal.xyz.z * sin.xyz.z / pi->mass );
else
vm_vec_copy_scale( &impact_vec, &orient->vec.fvec, min->xyz.z * pressure * area.xyz.z * -normal.xyz.z * sin.xyz.z / pi->mass );
vm_vec_cross( &temp_torque, &impact_vec, direction_vec );
vm_vec_add2( &torque, &temp_torque );
// compute delta rotvel, scale according to blast and radius
float scale;
if (radius < MIN_RADIUS) {
scale = 1.0f;
} else {
scale = (MAX_RADIUS - radius)/(MAX_RADIUS-MIN_RADIUS);
}
// set shockwave shake amplitude, duration, flag
pi->shockwave_shake_amp = (float)(MAX_SHAKE*(pressure/STD_PRESSURE)*scale);
pi->shockwave_decay = timestamp( SW_BLAST_DURATION );
pi->flags |= PF_IN_SHOCKWAVE;
// safety dance
if (!(IS_VEC_NULL_SQ_SAFE(&torque))) {
vec3d delta_rotvel;
vm_vec_rotate( &local_torque, &torque, orient );
vm_vec_copy_normalize(&delta_rotvel, &local_torque);
vm_vec_scale(&delta_rotvel, (float)(MAX_ROTVEL*(pressure/STD_PRESSURE)*scale));
// nprintf(("Physics", "rotvel scale %f\n", (MAX_ROTVEL*(pressure/STD_PRESSURE)*scale)));
vm_vec_add2(&pi->rotvel, &delta_rotvel);
}
// set reduced translational damping, set flags
float velocity_scale = (float)MAX_VEL*scale;
pi->flags |= PF_REDUCED_DAMP;
update_reduced_damp_timestamp( pi, velocity_scale*pi->mass );
vec3d previous_vel = pi->vel;
vm_vec_scale_add2( &pi->vel, direction_vec, velocity_scale );
// check that kick from shockwave is not too large
physics_maybe_reset_speed_after_whack(pi, &previous_vel, "physics_apply_shock");
// set so velocity will ramp starting from current speed
// ramped velocity is now affected by shockwave
vm_vec_rotate( &pi->prev_ramp_vel, &pi->vel, orient );
}
// ----------------------------------------------------------------------------
// physics_collide_whack applies an instaneous whack on an object changing
// both the objects velocity and the rotational velocity based on the impulse
// being applied.
//
// input: impulse => impulse vector ( force*time = impulse = change in momentum (mv) )
// world_delta_rotvel => change in rotational velocity (already calculated)
// pi => pointer to phys_info struct of object getting whacked
// orient => orientation matrix (needed to set rotational impulse in body coords)
//
// Warning: Do not change ROTVEL_COLLIDE_WHACK_CONST. This will mess up collision physics.
// If you need to change the rotation, change COLLISION_ROTATION_FACTOR in collide_ship_ship.
#define ROTVEL_COLLIDE_WHACK_CONST 1.0
void physics_collide_whack( vec3d *impulse, vec3d *world_delta_rotvel, physics_info *pi, matrix *orient, bool is_landing, float max_rotvel )
{
vec3d body_delta_rotvel;
// Detect null vector.
if ((fl_abs(impulse->xyz.x) <= WHACK_LIMIT) && (fl_abs(impulse->xyz.y) <= WHACK_LIMIT) && (fl_abs(impulse->xyz.z) <= WHACK_LIMIT))
return;
vm_vec_rotate( &body_delta_rotvel, world_delta_rotvel, orient );
// vm_vec_scale( &body_delta_rotvel, (float) ROTVEL_COLLIDE_WHACK_CONST );
vm_vec_add2( &pi->rotvel, &body_delta_rotvel );
if (max_rotvel > 0.0f) {
float rotvel_mag = vm_vec_mag(&pi->rotvel);
if (rotvel_mag > max_rotvel) {
vm_vec_scale(&pi->rotvel, max_rotvel / rotvel_mag);
}
}
pi->flags |= PF_REDUCED_DAMP;
update_reduced_damp_timestamp( pi, vm_vec_mag(impulse) );
// find time for shake from weapon to end
if (!is_landing) {
int dtime = timestamp_until(pi->afterburner_decay);
if (dtime < WEAPON_SHAKE_TIME) {
pi->afterburner_decay = timestamp( WEAPON_SHAKE_TIME );
}
}
vec3d previous_vel = pi->vel;
vm_vec_scale_add2( &pi->vel, impulse, 1.0f / pi->mass );
// check that collision does not give ship too much speed
physics_maybe_reset_speed_after_whack(pi, &previous_vel, "physics_collide_whack");
// set so velocity will ramp starting from current speed
// ramped velocity is now affected by collision
vm_vec_rotate( &pi->prev_ramp_vel, &pi->vel, orient );
}
int check_rotvel_limit( physics_info *pi )
{
if ( 0 == pi->flags ) // weapon
return 0;
if ( Fred_running )
return 0;
int change_made = 0;
if ( !(pi->flags & PF_DEAD_DAMP) ) {
// case of normal, live ship
// -- Commented out by MK: Assert( vm_vec_mag_squared(&pi->max_rotvel) > ROTVEL_TOL );
// Assert( (pi->max_rotvel.xyz.x <= ROTVEL_CAP) && (pi->max_rotvel.xyz.y <= ROTVEL_CAP) && (pi->max_rotvel.xyz.z <= ROTVEL_CAP) );
// Warning(LOCATION,"Excessive rotvel (wx: %f, wy: %f, wz:%f)\n", pi->rotvel.xyz.x, pi->rotvel.xyz.y, pi->rotvel.xyz.z);
if ( fl_abs(pi->rotvel.xyz.x) > pi->max_rotvel.xyz.x ) {
pi->rotvel.xyz.x = (pi->rotvel.xyz.x / fl_abs(pi->rotvel.xyz.x)) * (pi->max_rotvel.xyz.x - (float) ROTVEL_TOL);
change_made = 1;
}
if ( fl_abs(pi->rotvel.xyz.y) > pi->max_rotvel.xyz.y ) {
pi->rotvel.xyz.y = (pi->rotvel.xyz.y / fl_abs(pi->rotvel.xyz.y)) * (pi->max_rotvel.xyz.y - (float) ROTVEL_TOL);
change_made = 1;
}
if ( fl_abs(pi->rotvel.xyz.z) > pi->max_rotvel.xyz.z ) {
pi->rotvel.xyz.z = (pi->rotvel.xyz.z / fl_abs(pi->rotvel.xyz.z)) * (pi->max_rotvel.xyz.z - (float) ROTVEL_TOL);
change_made = 1;
}
} else {
// case of dead ship
if ( fl_abs(pi->rotvel.xyz.x) > DEAD_ROTVEL_CAP ) {
pi->rotvel.xyz.x = (pi->rotvel.xyz.x / fl_abs(pi->rotvel.xyz.x)) * (float) (DEAD_ROTVEL_CAP - ROTVEL_TOL);
change_made = 1;
}
if ( fl_abs(pi->rotvel.xyz.y) > DEAD_ROTVEL_CAP ) {
pi->rotvel.xyz.y = (pi->rotvel.xyz.y / fl_abs(pi->rotvel.xyz.y)) * (float) (DEAD_ROTVEL_CAP - ROTVEL_TOL);
change_made = 1;
}
if ( fl_abs(pi->rotvel.xyz.z) > DEAD_ROTVEL_CAP ) {
pi->rotvel.xyz.z = (pi->rotvel.xyz.z / fl_abs(pi->rotvel.xyz.z)) * (float) (DEAD_ROTVEL_CAP - ROTVEL_TOL);
change_made = 1;
}
}
return change_made;
}
// ----------------------------------------------------------------------------
// update_reduced_damp_timestamp()
//
void update_reduced_damp_timestamp( physics_info *pi, float impulse )
{
// Compute duration of reduced damp from present
// Compare with current value and increase if greater, otherwise ignore
int reduced_damp_decay_time;
reduced_damp_decay_time = (int) (REDUCED_DAMP_TIME * impulse / (REDUCED_DAMP_VEL * pi->mass));
if ( reduced_damp_decay_time > REDUCED_DAMP_TIME )
reduced_damp_decay_time = REDUCED_DAMP_TIME;
// Reset timestamp if larger than current (if any)
if ( timestamp_valid( pi->reduced_damp_decay ) ) {
int time_left = timestamp_until( pi->reduced_damp_decay );
if ( time_left > 0 ) {
// increment old time, but apply cap
int new_time = reduced_damp_decay_time + time_left;
if ( new_time < REDUCED_DAMP_TIME ) {
pi->reduced_damp_decay = timestamp( new_time );
}
} else {
pi->reduced_damp_decay = timestamp( reduced_damp_decay_time );
}
} else {
// set if not valid
pi->reduced_damp_decay = timestamp( reduced_damp_decay_time );
}
}
void physics_add_point_mass_moi(matrix *moi, float mass, vec3d *pos)
{
// moment of inertia for a point mass:
// I_xx = m(y^2+z^2) | I_yx = -mxy | I_zx = -mxz
// I_xy = -mxy | I_yy = m(x^2+z^2) | I_zy = -myz
// I_xz = -mxz | I_yz = -myz | I_zz = m(x^2+y^2)
moi->a2d[0][0] += mass * (pos->xyz.y * pos->xyz.y + pos->xyz.z * pos->xyz.z);
moi->a2d[0][1] -= mass * pos->xyz.x * pos->xyz.y;
moi->a2d[0][2] -= mass * pos->xyz.x * pos->xyz.z;
moi->a2d[1][0] -= mass * pos->xyz.x * pos->xyz.y;
moi->a2d[1][1] += mass * (pos->xyz.x * pos->xyz.x + pos->xyz.z * pos->xyz.z);
moi->a2d[1][2] -= mass * pos->xyz.y * pos->xyz.z;
moi->a2d[2][0] -= mass * pos->xyz.x * pos->xyz.z;
moi->a2d[2][1] -= mass * pos->xyz.y * pos->xyz.z;
moi->a2d[2][2] += mass * (pos->xyz.x * pos->xyz.x + pos->xyz.y * pos->xyz.y);
}
// equation from https://en.wikipedia.org/wiki/Projectile_motion#Angle_%CE%B8_required_to_hit_coordinate_(x,_y)
bool physics_lead_ballistic_trajectory(const vec3d* start, const vec3d* end_pos, const vec3d* target_vel, float weapon_speed, const vec3d* gravity, vec3d* out_direction) {
float best_guess_time = 0.0f;
*out_direction = vmd_zero_vector;
float time = 0.0f;
for (int i = 0; i < 6; i++) {
vec3d target = *end_pos - *start;
target += *target_vel * time;
vec3d gravity_dir = *gravity;
float gravity_accel = vm_vec_normalize(&gravity_dir);
float height = -vm_vec_dot(&target, &gravity_dir);
vec3d plane_pos = target + gravity_dir * height;
float range = vm_vec_normalize(&plane_pos);
float vel_2 = weapon_speed * weapon_speed;
float gx = gravity_accel * range;
float gy = gravity_accel * height;
float discriminant = vel_2 * vel_2 - (gx * gx + 2 * gy * vel_2);
if (discriminant < 0)
return false;
float angle = atanf((vel_2 - sqrtf(discriminant)) / gx);
*out_direction = plane_pos * cosf(angle) - (gravity_dir)*sinf(angle);
time = range / (weapon_speed * cosf(angle));
if (std::abs(time - best_guess_time) < 0.01f)
break;
else
best_guess_time = time;
}
return true;
}
//*************************CLASS: avd_movement*************************
avd_movement::avd_movement() : Pc(0.0f), Vc(0.0f), TSi(0), Pi(0.0f), Vi(0.0f), Pf(0.0f), Tf(0.0f), Tai(0.0f), Taf(0.0f), Vf(0.0f), Vm(0.0f), Ai(0.0f), Af(0.0f)
{
}
void avd_movement::clear()
{
// We should really refactor so that a ::clear() method isn't needed.
// For now, be sure this syncs with the class declaration
//Current
Pc = 0; //Current position
Vc = 0; //Current velocity
//Initial
TSi = 0; //Initial timestamp <-- note TIMESTAMP
Pi = 0; //Initial position
Vi = 0; //Initial velocity
//Given
Pf = 0; //Final position
Tf = 0; //Final duration
Tai = 0; //Starting acceleration duration
Taf = 0; //Ending acceleration duration
Vf = 0; //Final velocity
//Calculated
Vm = 0; //Middle velocity
Ai = 0; //Starting acceleration
Af = 0; //Ending acceleration
}
void avd_movement::get(float Time, float *Position, float *Velocity)
{
this->update(Time);
if(Position != NULL)
*Position = Pc;
if(Velocity != NULL)
*Velocity = Vc;
}
void avd_movement::get(float *Position, float *Velocity)
{
float time = (float)(timestamp() - TSi)/1000.0f;
this->get(time, Position, Velocity);
}
void avd_movement::set(float position)
{
this->clear();
Pi = Pf = Pc = position;
}
void avd_movement::setAVD(float final_position, float total_movement_time, float starting_accleration_time, float ending_acceleration_time, float final_velocity)
{
//Make sure Pc et al are up-to-date
this->update();
Pf = final_position;
Tf = total_movement_time;
Tai = starting_accleration_time;
Taf = ending_acceleration_time;
Vf = final_velocity;
if(Tai+Taf >= Tf)
{
Tai = Tf;
Taf = 0.0f;
}
if(Tf <= 0.0f)
{
Pc = Pi = Pf;
Vc = Vi = Vf;
return;
}
Pi = Pc;
Vi = Vc;
TSi = timestamp();
Vm = (Pf-Pi-0.5f*(Vi*Tai)-0.5f*(Vf*Taf)) / (Tf - 0.5f*Tai - 0.5f*Taf);
// Cyborg - Many coverity dividing by zero issues were caused by this piece of low level code. Most places that called this function were also not checking inputs.
// The two if blocks in this function do not guarantee that they will not be float zero (e.g. tai = 2.0 taf = 0.0 Tf = 1.0)
// The reason why this code used to work even when there were divisions by zero is that the NAN results were gated behind Taf > 0.0 and Tai > 0.0
// checks in other functions. Ai, initial Acceleration, and Af, final Acceleration, are completely meaningless and explicitly disabled if their respective
// time amounts are zero or less than zero. So instead of just letting some NAN's chill out in memory, waiting for someone else to unwisely access them,
// we are going to explicitly set them to zero, like a good engine should.
if (Tai <= 0.0f){
Ai = 0.0;
} else {
Ai = (Vm-Vi)/Tai;
}
if (Taf <= 0.0f){
Af = 0.0f;
} else {
Af = (Vf-Vm)/Taf;
}
}
void avd_movement::setVD(float total_movement_time, float ending_acceleration_time, float final_velocity)
{
//Make sure Pc et al are up-to-date
this->update();
Tf = total_movement_time;
Tai = 0.0f;
Taf = ending_acceleration_time;
Vf = final_velocity;
Pi = Pc;
Vm = Vi = Vc;
TSi = timestamp();
Ai = 0.0f;
// Avoid division by float zero. See the note in setAVD for more info.
if (Taf <= 0.0f){
Af = 0.0f;
} else {
Af = (Vf-Vm)/Taf;
}
Pf = Pi + Pf*(Tf - Taf) + Vm*Taf + 0.5f*Af*(Taf*Taf);
}
void avd_movement::update()
{
float time = (float)(timestamp() - TSi)/1000.0f;
this->update(time);
}
void avd_movement::update(float Time)
{
if(Tf <= 0.0f)
{
//This avd_movement is just serving as static storage
Pc = Pi;
Vc = Vi;
}
else if(Time >= Tf)
{
//Movement has ended, but the thing may still have a constant velocity
Pc = Pf + Vf*(Time-Tf);
Vc = Vf;
}
else
{
//Movement is in-progress and we must calculate where the thing is now.
float t = Time;
float Tc = 0.0f;
if(t >= 0.0f)
{
Pc = Pi;
Vc = Vi;
}
if(t >= 0.0f && Tai > 0.0f)
{
if(t < Tai)
Tc = t;
else
Tc = Tai;
Pc = Pc + Vi*Tc + 0.5f*Ai*(Tc*Tc);
Vc = Vc + Ai*Tc;
}
if(t >= Tai && (Tai+Taf) < Tf)
{
if(t < (Tf-Taf))
Tc = (t-Tai);
else
Tc = (Tf-Tai-Taf);
Pc = Pc + Vm*Tc;
// Vc = Vc;
}
if(t >= (Tf-Taf) && Taf > 0.0f)
{
if(t < Tf)
Tc = t-(Tf-Taf);
else
Tc = Taf;
Pc = Pc + Vm*Tc + 0.5f*Af*(Tc*Tc);
Vc = Vc + Af*Tc;
}
}
}