// ----------------------------------------------------------------------------
// 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 crw_check_weapon( int weapon_num, int collide_next_check )
{
	float next_check_time;
	weapon *wp;

	wp = &Weapons[weapon_num];

	// if this weapons life left > time before next collision, then we cannot remove it
	crw_status[WEAPON_INDEX(wp)] = CRW_IN_PAIR;
	next_check_time = ((float)(timestamp_until(collide_next_check)) / 1000.0f);
	if ( wp->lifeleft < next_check_time )
		crw_status[WEAPON_INDEX(wp)] = CRW_CAN_DELETE;
}
void physics_apply_whack(vec3d *impulse, vec3d *pos, physics_info *pi, matrix *orient, float mass)
{
	vec3d	local_torque, torque;
//	vec3d	npos;

	//	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;

	// first do the rotational velocity
	// calculate the torque on the body based on the point on the
	// object that was hit and the momentum being applied to the object

	vm_vec_crossprod(&torque, pos, impulse);
	vm_vec_rotate ( &local_torque, &torque, orient );

	vec3d delta_rotvel;
	vm_vec_rotate( &delta_rotvel, &local_torque, &pi->I_body_inv );
	vm_vec_scale ( &delta_rotvel, (float) ROTVEL_WHACK_CONST );
	vm_vec_add2( &pi->rotvel, &delta_rotvel );

	//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, vm_vec_mag(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 );
	}

	// Goober5000 - pi->mass should probably be just mass, as specified in the header
	vm_vec_scale_add2( &pi->vel, impulse, 1.0f / mass );
	if (!(pi->flags & PF_USE_VEL) && (vm_vec_mag_squared(&pi->vel) > MAX_SHIP_SPEED*MAX_SHIP_SPEED)) {
		// Get DaveA
		nprintf(("Physics", "speed reset in physics_apply_whack [speed: %f]\n", vm_vec_mag(&pi->vel)));
		vm_vec_normalize(&pi->vel);
		vm_vec_scale(&pi->vel, (float)RESET_SHIP_SPEED);
	}
	vm_vec_rotate( &pi->prev_ramp_vel, &pi->vel, orient );		// set so velocity will ramp starting from current speed
																					// ramped velocity is now affected by collision
}
void physics_collide_whack( vec3d *impulse, vec3d *world_delta_rotvel, physics_info *pi, matrix *orient, bool is_landing )
{
	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 );

	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 );
		}
	}

	pi->flags |= PF_REDUCED_DAMP;
	vm_vec_scale_add2( &pi->vel, impulse, 1.0f / pi->mass );
	// check that collision does not give ship too much speed
	// reset if too high
	if (!(pi->flags & PF_USE_VEL) && (vm_vec_mag_squared(&pi->vel) > MAX_SHIP_SPEED*MAX_SHIP_SPEED)) {
		// Get DaveA
		nprintf(("Physics", "speed reset in physics_collide_whack [speed: %f]\n", vm_vec_mag(&pi->vel)));
		vm_vec_normalize(&pi->vel);
		vm_vec_scale(&pi->vel, (float)RESET_SHIP_SPEED);
	}
	vm_vec_rotate( &pi->prev_ramp_vel, &pi->vel, orient );		// set so velocity will ramp starting from current speed
																					// ramped velocity is now affected by collision
	// rotate previous ramp velocity (in model coord) to be same as vel (in world coords)
}
// 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 (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 ( pi->flags & PF_AFTERBURNER_ON ){
		//SparK: modifield to accept reverse burners
		if (!(pi->afterburner_max_reverse_vel > 0.0f)){
			ci->forward = 1.0f;
		}
	}

	if (ci->forward > 1.0f ) ci->forward = 1.0f;
	else if (ci->forward < -1.0f ) ci->forward = -1.0f;

	if (!Flight_controls_follow_eyepoint_orientation || (Player_obj == NULL) || (Player_obj->type != OBJ_SHIP)) {
		// 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;

		ship_get_eye(&tmp_vec, &eyemat, Player_obj, false);

		vm_copy_transpose_matrix(&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
	//	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;
	pi->forward_thrust = ci->forward;
	pi->vert_thrust = ci->vertical;	//added these two in order to get side and forward thrusters
	pi->side_thrust = ci->sideways;	//to glow brighter when the ship is moving in the right direction -Bobboau

	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) )
		goal_vel.xyz.z = -pi->max_rear_vel;


	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 decleration 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 decleration 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 decleration 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 = MAX(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->use_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.
			if ( curGlideCap >= 0.0f && (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 );
		}
	} else  // object does not accelerate  (PF_ACCELERATES not set)
		pi->desired_vel = pi->vel;
}
// Adds velocity to position
// finds velocity and displacement in local coords
void physics_sim_vel(vec3d * position, physics_info * pi, float sim_time, matrix *orient)
{
	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;

	//	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
	// ie. shockwave, collision, weapon, 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 );

	} else if (pi->flags & PF_REDUCED_DAMP) {
		// case of shock, weapon, collide, etc.
		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;
		}
	} else {
		// regular damping
		if (pi->use_newtonian_damp) {
			vm_vec_make( &damp, pi->side_slip_time_const, pi->side_slip_time_const, pi->side_slip_time_const );
		} 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)

	int special_warp_in = FALSE;
	float excess = local_v_in.xyz.z - pi->max_vel.xyz.z;
	if (excess > 5 && (pi->flags & PF_SPECIAL_WARP_IN)) {
		special_warp_in = TRUE;
		float exp_factor = float(exp(-sim_time / SPECIAL_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(SPECIAL_WARP_T_CONST) * (1.0f - exp_factor));
	} else if (pi->flags & PF_SPECIAL_WARP_OUT) {
		float exp_factor = float(exp(-sim_time / SPECIAL_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(SPECIAL_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);
	}

	// maybe turn off special warp in flag
	if ((pi->flags & PF_SPECIAL_WARP_IN) && (excess < 5)) {
		pi->flags &= ~(PF_SPECIAL_WARP_IN);
	}

	// update world position from local to world coords using orient
	vec3d world_disp;
	vm_vec_unrotate (&world_disp, &local_disp, orient);
	vm_vec_add2 (position, &world_disp);

	// update world velocity
	vm_vec_unrotate(&pi->vel, &local_v_out, orient);

	if (special_warp_in) {
		vm_vec_rotate(&pi->prev_ramp_vel, &pi->vel, orient);
	}
}
void physics_sim_rot(matrix * orient, physics_info * pi, float sim_time )
{
	angles	tangles;
	vec3d	new_vel;
	matrix	tmp;
	float		shock_amplitude;
	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
	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 {
		rotdamp = pi->rotdamp;
	}

	// Do rotational physics with given damping
	apply_physics( rotdamp, pi->desired_rotvel.xyz.x, pi->rotvel.xyz.x, sim_time, &new_vel.xyz.x, NULL );
	apply_physics( rotdamp, pi->desired_rotvel.xyz.y, pi->rotvel.xyz.y, sim_time, &new_vel.xyz.y, NULL );
	apply_physics( rotdamp, pi->desired_rotvel.xyz.z, pi->rotvel.xyz.z, sim_time, &new_vel.xyz.z, NULL );

	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 afterburner.
	if (pi->flags & PF_AFTERBURNER_ON || !timestamp_elapsed(pi->afterburner_decay) ) {
		float	max_speed;

		max_speed = vm_vec_mag_quick(&pi->max_vel);
		tangles.p += (float) (rand()-RAND_MAX_2) * RAND_MAX_1f * pi->speed/max_speed/64.0f;
		tangles.h += (float) (rand()-RAND_MAX_2) * RAND_MAX_1f * pi->speed/max_speed/64.0f;
		if ( pi->flags & PF_AFTERBURNER_ON ) {
			pi->afterburner_decay = timestamp(ABURN_DECAY_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) (myrand()-RAND_MAX_2) * RAND_MAX_1f * shock_amplitude;
		tangles.h += (float) (myrand()-RAND_MAX_2) * RAND_MAX_1f * 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);

}
void obj_check_all_collisions()
{
	obj_pair *parent, *tmp;	

#ifdef PAIR_STATS
	// debug info
	float avg_time_to_next_check = 0.0f;
#endif

	if (Cmdline_dis_collisions)
		return;

	if ( !(Game_detail_flags & DETAIL_FLAG_COLLISION) )
		return;


	parent = &pair_used_list;
	tmp = parent->next;

	Num_pairs_checked = 0;

	while (tmp != NULL) {
		int removed = 0;

		if ( !timestamp_elapsed(tmp->next_check_time) )
			goto NextPair;

		if ( (tmp->a) && (tmp->b) ) {
			Num_pairs_checked++;

			if ( (*tmp->check_collision)(tmp) ) {
				// We never need to check this pair again.
				#if 0	//def DONT_REMOVE_PAIRS
					// Never check it again, but keep the pair around
					// (useful for debugging)
					tmp->next_check_time = timestamp(-1);		
				#else
					// Never check it again, so remove the pair
					removed = 1;
					tmp->a->num_pairs--;	
					Assert( tmp->a->num_pairs > -1 );
					tmp->b->num_pairs--;
					Assert( tmp->b->num_pairs > -1 );
					Num_pairs--;
					// Assert(Num_pairs >= 0);
					parent->next = tmp->next;
					tmp->a = tmp->b = NULL;
					tmp->next = pair_free_list.next;
					pair_free_list.next = tmp;
					tmp = parent->next;
				#endif
			}
		} 

NextPair:
		if ( !removed ) {
			parent = tmp;
			tmp = tmp->next;

#ifdef PAIR_STATS
			// debug info
			if (tmp) {
				int add_time = timestamp_until( tmp->next_check_time );
				if (add_time > 0)
					avg_time_to_next_check += (float) add_time;
			}
#endif
		}
	}

	MONITOR_INC(NumPairs, Num_pairs);
	MONITOR_INC(NumPairsChecked, Num_pairs_checked);

#ifdef PAIR_STATS
	avg_time_to_next_check = avg_time_to_next_check / Num_pairs;
	extern int Num_hull_pieces;
	extern int Weapons_created;
	// mprintf(( "[pairs checked: %d, start_pairs: %d, num obj: %d, avg next time: %f]\n", n, org_pairs, Num_objects, avg_time_to_next_check ));
	// mprintf(( "[Num_hull_pieces: %3d, Num_weapons_created: %3d, pairs_not_created: %3d, pairs_created: %3d, percent new saved: %9.5f]\n", Num_hull_pieces, Weapons_created, pairs_not_created, Pairs_created, 100.0f*(float)pairs_not_created/(float)(pairs_not_created + Pairs_created) ));
	 mprintf(( "[pairs_created: %3d, pairs_not_created: %3d, percent saved %6.3f]\n", Pairs_created, pairs_not_created, 100.0f*pairs_not_created/(Pairs_created+pairs_not_created) ));
	pairs_not_created = 0;
	Weapons_created = 0;
	Pairs_created = 0;
#endif

	// What percent of the pairs did we check?
	// FYI: (n*(n-1))/2 is the total number of checks required for comparing n objects.

//	if ( org_pairs > 1 )	{
//		Object_checked_percentage = (i2fl(n)*100.0f) / i2fl(org_pairs);
//	} else {
//		Object_checked_percentage = 0.0f;
//	}

}
示例#9
0
// ----------------------------------------------------------------------------
// afterburners_update()
//
//	Update the state of the afterburner fuel remaining for an object using the
//	afterburner.  
//
// for the player ship, key_up_time() is called for the afterburner key to
// detect when afterburners disengage.
//
// input:		*objp				=> pointer to the object starting afterburners
//					fl_frametime	=> time in seconds of the last frame
//
void afterburners_update(object* objp, float fl_frametime)
{
	Assert(objp != NULL);
	Assert(objp->type == OBJ_SHIP);
	Assert(objp->instance >= 0 && objp->instance < MAX_SHIPS);

	ship_info* sip;
	ship* shipp;
	static int volume_chg_timer = 1;

	shipp = &Ships[objp->instance];

	Assert(shipp->ship_info_index >= 0 && shipp->ship_info_index < Num_ship_classes);
	sip = &Ship_info[shipp->ship_info_index];

	if ((objp->flags & OF_PLAYER_SHIP) && (Game_mode & GM_DEAD))
	{
		return;
	}

	if (!(sip->flags & SIF_AFTERBURNER))
	{
		return;		// nothing to update, afterburners are not even on the ship
	}

	//shut the afterburners off if we're using the booster tertiary
	//shut the afterburners off if we're in glide mode.
	if ((objp->phys_info.flags & PF_AFTERBURNER_ON) &&
		((objp->phys_info.flags & PF_BOOSTER_ON) || (objp->phys_info.flags & PF_GLIDING)))
	{
		if (objp == Player_obj)
			afterburner_stop_sounds();
		afterburners_stop(objp);
		return;
	}

	if (objp == Player_obj)
	{
		if (!timestamp_elapsed(Player_disengage_timer))
		{
			float remaining;
			remaining = timestamp_until(Player_disengage_timer) / i2fl(DISENGAGE_TIME);
			if (remaining <= 0)
			{
				afterburner_stop_sounds();
			}
			else
			{
				if (remaining > 1.0f)
				{
					remaining = 1.0f;
				}
				snd_set_volume(Player_afterburner_loop_id, remaining * Player_afterburner_vol);
			}
		}
		else
		{
			if (Player_disengage_timer != 1)
			{
				afterburner_stop_sounds();
			}
		}
	}

	// single player, multiplayer servers, and clients for their own ships
	if (!(Game_mode & GM_MULTIPLAYER) || MULTIPLAYER_MASTER || (objp == Player_obj))
	{
		if (!(objp->phys_info.flags & PF_AFTERBURNER_ON))
		{
			// Recover afterburner fuel

			if (shipp->afterburner_fuel < sip->afterburner_fuel_capacity)
			{
				float recharge_scale;
				recharge_scale = Energy_levels[shipp->engine_recharge_index] * 2.0f * The_mission.ai_profile->
					afterburner_recharge_scale[Game_skill_level];
				shipp->afterburner_fuel += (sip->afterburner_recover_rate * fl_frametime * recharge_scale);

				if (shipp->afterburner_fuel > sip->afterburner_fuel_capacity)
				{
					shipp->afterburner_fuel = sip->afterburner_fuel_capacity;
				}
			}
			return;
		}
		else
		{
			// Check if there is enough afterburner fuel
			if (shipp->afterburner_fuel <= 0)
			{
				shipp->afterburner_fuel = 0.0f;
				afterburners_stop(objp);
				return;
			}
		}

		// afterburners are firing at this point

		// Reduce the afterburner fuel
		shipp->afterburner_fuel -= (sip->afterburner_burn_rate * fl_frametime);
		if (shipp->afterburner_fuel < 0.0f)
		{
			shipp->afterburner_fuel = 0.0f;
		}
	}

	if (objp == Player_obj)
	{
		if ((Viewer_mode & VM_NOT_COCKPIT))
		{
			// stop afterburner sound if it is playing
			if (Player_afterburner_loop_id != -1)
			{
				snd_stop(Player_afterburner_loop_id);
				Player_afterburner_loop_id = -1;
			}

			return;
		}

		if (timestamp_elapsed(Player_afterburner_loop_delay))
		{
			Player_afterburner_vol = Snds[SND_ABURN_LOOP].default_volume;
			Player_afterburner_loop_delay = timestamp(50);
			if (Player_afterburner_loop_id == -1)
			{
				Player_afterburner_loop_id = snd_play_looping(&Snds[SND_ABURN_LOOP], 0.0f, -1, -1,
					Player_afterburner_vol);
				//snd_set_volume(Player_afterburner_loop_id, Player_afterburner_vol);
				//				nprintf(("Alan","PLAY LOOPING SOUND\n"));
			}
		}

		// Reduce the volume of the afterburner sound if near the end
		if (timestamp_elapsed(volume_chg_timer))
		{
			float percent_afterburner_left;
			percent_afterburner_left = shipp->afterburner_fuel / sip->afterburner_fuel_capacity;
			volume_chg_timer = timestamp(AFTERBURNER_VOLUME_UPDATE);
			if (percent_afterburner_left < AFTERBURNER_PERCENT_VOL_ATTENUATE)
			{
				Player_afterburner_vol = percent_afterburner_left * (1 / AFTERBURNER_PERCENT_VOL_ATTENUATE) *
					Snds[SND_ABURN_LOOP].default_volume;
				snd_set_volume(Player_afterburner_loop_id, Player_afterburner_vol);
			}
		}	// end if (timestamp_elapsed(volume_chg_timer))
	}
}
/**
 * Update the state of the afterburner fuel remaining for an object using the afterburner.  
 *
 * For the player ship, key_up_time() is called for the afterburner key to
 * detect when afterburners disengage.
 *
 * @param *objp			pointer to the object starting afterburners
 * @param fl_frametime	time in seconds of the last frame
 */
void afterburners_update(object *objp, float fl_frametime)
{
	Assert( objp != NULL );
	Assert( objp->type == OBJ_SHIP );
	Assert( objp->instance >= 0 && objp->instance < MAX_SHIPS );
	
	ship_info *sip;
	ship *shipp;
	static int volume_chg_timer = 1;

	shipp = &Ships[objp->instance];

	Assert( shipp->ship_info_index >= 0 && shipp->ship_info_index < static_cast<int>(Ship_info.size()) );
	sip = &Ship_info[shipp->ship_info_index];

	if ( (objp->flags[Object::Object_Flags::Player_ship] ) && (Game_mode & GM_DEAD) ) {
		return;
	}

	if ( !(sip->flags[Ship::Info_Flags::Afterburner]) )	{
		return;		// nothing to update, afterburners are not even on the ship
	}

	//shut the afterburners off if we're using the booster tertiary
	if ( objp->phys_info.flags & PF_BOOSTER_ON)
	{
		if (objp==Player_obj) afterburner_stop_sounds();
		afterburners_stop(objp);
		return;
	}

	if ( objp == Player_obj ) {
		if ( !timestamp_elapsed(Player_disengage_timer) ) {
			float remaining;
			remaining = timestamp_until(Player_disengage_timer) / i2fl(DISENGAGE_TIME);
			if ( remaining <= 0 ) {
				afterburner_stop_sounds();
			}
			else {
				snd_set_volume( Player_afterburner_loop_id, remaining*Player_afterburner_vol);
			}
		}
		else {
			if ( Player_disengage_timer != 1 ) {
				afterburner_stop_sounds();
			}
		}
	}

	// single player, multiplayer servers, and clients for their own ships
	if(!(Game_mode & GM_MULTIPLAYER) || MULTIPLAYER_MASTER || (objp == Player_obj)) {
		if ( !(objp->phys_info.flags & PF_AFTERBURNER_ON) ) {
			// Recover afterburner fuel

			if ( shipp->afterburner_fuel < sip->afterburner_fuel_capacity ) {
				float recharge_scale;
				recharge_scale = Energy_levels[shipp->engine_recharge_index] * 2.0f * The_mission.ai_profile->afterburner_recharge_scale[Game_skill_level];
				shipp->afterburner_fuel += (sip->afterburner_recover_rate * fl_frametime * recharge_scale);

				if ( shipp->afterburner_fuel >  sip->afterburner_fuel_capacity){
					shipp->afterburner_fuel = sip->afterburner_fuel_capacity;
				}
			}
			return;
		} else {
			// Check if there is enough afterburner fuel
			if ( shipp->afterburner_fuel <= 0 ) {
				shipp->afterburner_fuel = 0.0f;
				afterburners_stop(objp);
				return;
			}
		}

		// afterburners are firing at this point

		// Reduce the afterburner fuel
		shipp->afterburner_fuel -= (sip->afterburner_burn_rate * fl_frametime);
		if ( shipp->afterburner_fuel < 0.0f ) {
			shipp->afterburner_fuel = 0.0f;
		}
	}

	if ( objp == Player_obj ) {
		if ( timestamp_elapsed(Player_afterburner_loop_delay) ) {
			Player_afterburner_vol = AFTERBURNER_DEFAULT_VOL;
			Player_afterburner_loop_delay = 0;
			if ( Player_afterburner_loop_id == -1 ) {
				Player_afterburner_loop_id = snd_play_looping( gamesnd_get_game_sound(ship_get_sound(objp, GameSounds::ABURN_LOOP)), 0.0f , -1, -1);
				snd_set_volume(Player_afterburner_loop_id, Player_afterburner_vol);
			}
		}

		// Reduce the volume of the afterburner sound if near the end
		if ( timestamp_elapsed(volume_chg_timer) ) {
			float percent_afterburner_left;
			percent_afterburner_left = shipp->afterburner_fuel / sip->afterburner_fuel_capacity;
			volume_chg_timer = timestamp(AFTERBURNER_VOLUME_UPDATE);
			if ( percent_afterburner_left < AFTERBURNER_PERCENT_VOL_ATTENUATE ) {
				Player_afterburner_vol = percent_afterburner_left*(1/AFTERBURNER_PERCENT_VOL_ATTENUATE)*AFTERBURNER_DEFAULT_VOL;
				snd_set_volume(Player_afterburner_loop_id, Player_afterburner_vol);
			}
		}	// end if (timestamp_elapsed(volume_chg_timer))
	}
}