Files
fs2open.github.com/code/object/collideshipship.cpp
T
asarium 8f06e338a4 Add typesafe handle types and use it in game sound system
This adds a new class which allows to create type safe resource handle
types. At the moment, the engine uses ints pretty much everywhere for
providing handles to resources created by the specific subsystems. Since
all ints are the same to the C/C++ typesystem it is perfectly legal to
pass a sound handle to the graphics API as a buffer handle which will
obviously cause issues.

This adds a new type called "ID" which is a wrapper class for a generic
handle but with added type safety. An instance of this class cannot be
passed to a function expecting another type of handle which provided
compile-time type safety for these APIs.

As an initial test I converted the game sound handles to this new type.
I already found a bug in the mainhall system thanks to this new type.
The mainhall used the game sound handles as sound instance handles which
probably meant that that specific code never really worked right. I
removed the code since there was no better alternative for fixing it.
2018-04-22 16:22:45 +02:00

1392 lines
54 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 "asteroid/asteroid.h"
#include "debris/debris.h"
#include "debugconsole/console.h"
#include "freespace.h"
#include "gamesequence/gamesequence.h"
#include "gamesnd/gamesnd.h"
#include "hud/hudmessage.h"
#include "hud/hudshield.h"
#include "io/joy_ff.h"
#include "io/timer.h"
#include "object/objcollide.h"
#include "object/object.h"
#include "object/objectdock.h"
#include "object/objectshield.h"
#include "scripting/scripting.h"
#include "playerman/player.h"
#include "render/3d.h" // needed for View_position, which is used when playing 3d sound
#include "ship/ship.h"
#include "ship/shiphit.h"
#define COLLIDE_DEBUG
#undef COLLIDE_DEBUG
// GENERAL COLLISIONS FUNCTIONS
// calculates the inverse moment of inertia matrix in world coordinates
static void get_I_inv (matrix* I_inv, matrix* I_inv_body, matrix* orient);
// calculate the physics of extended two body collisions
void calculate_ship_ship_collision_physics(collision_info_struct *ship_ship_hit_info);
int ship_hit_shield(object *obj, mc_info *mc, collision_info_struct *sshs);
void collect_ship_ship_physics_info(object *heavier_obj, object *lighter_obj, mc_info *mc_info_obj, collision_info_struct *ship_ship_hit_info);
#ifndef NDEBUG
static int Collide_friendly = 1;
DCF_BOOL( collide_friendly, Collide_friendly )
#endif
static int Player_collide_sound, AI_collide_sound;
static int Player_collide_shield_sound, AI_collide_shield_sound;
/**
* Return true if two ships are docking.
*/
static int ships_are_docking(object *objp1, object *objp2)
{
ai_info *aip1, *aip2;
ship *shipp1, *shipp2;
shipp1 = &Ships[objp1->instance];
shipp2 = &Ships[objp2->instance];
aip1 = &Ai_info[shipp1->ai_index];
aip2 = &Ai_info[shipp2->ai_index];
if (dock_check_find_direct_docked_object(objp1, objp2)) {
return 1;
}
if (aip1->mode == AIM_DOCK) {
if (aip1->goal_objnum == OBJ_INDEX(objp2)){
return 1;
}
}
if (aip2->mode == AIM_DOCK) {
if (aip2->goal_objnum == OBJ_INDEX(objp1)){
return 1;
}
}
return 0;
}
/**
* If light_obj emerging from or departing to dock bay in heavy_obj, no collision detection.
*/
static int bay_emerge_or_depart(object *heavy_objp, object *light_objp)
{
if (light_objp->type != OBJ_SHIP)
return 0;
ai_info *aip = &Ai_info[Ships[light_objp->instance].ai_index];
// the player shouldn't be allowed to fly through the ship just cause the rest of their wing can
if ((Player_obj == light_objp) && !Player_use_ai){
return 0;
}
if ((aip->mode == AIM_BAY_EMERGE) || (aip->mode == AIM_BAY_DEPART)) {
if (aip->goal_objnum == OBJ_INDEX(heavy_objp))
return 1;
}
return 0;
}
int ship_ship_check_collision(collision_info_struct *ship_ship_hit_info, vec3d *hitpos)
{
object *heavy_obj = ship_ship_hit_info->heavy;
object *light_obj = ship_ship_hit_info->light;
int player_involved; // flag to indicate that A or B is the Player_obj
Assert( heavy_obj->type == OBJ_SHIP );
Assert( light_obj->type == OBJ_SHIP );
ship *heavy_shipp = &Ships[heavy_obj->instance];
ship *light_shipp = &Ships[light_obj->instance];
ship_info *heavy_sip = &Ship_info[heavy_shipp->ship_info_index];
ship_info *light_sip = &Ship_info[light_shipp->ship_info_index];
// AL 12-4-97: we use the player_involved flag to ensure collisions are always
// done with the player, regardless of team.
if ( heavy_obj == Player_obj || light_obj == Player_obj ) {
player_involved = 1;
} else {
player_involved = 0;
}
// Make ships that are warping in not get collision detection done
if ( heavy_shipp->flags[Ship::Ship_Flags::Arriving_stage_1] ) {
return 0;
}
// Don't do collision detection for docking ships, since they will always collide while trying to dock
if ( ships_are_docking(heavy_obj, light_obj) ) {
return 0;
}
// If light_obj emerging from or departing to dock bay in heavy_obj, no collision detection.
if (bay_emerge_or_depart(heavy_obj, light_obj)) {
return 0;
}
// Ships which are dying should not do collision detection.
// Also, this is the only clean way I could figure to get ships to not do damage to each other for one frame
// when they are docked and departing. Due to sequencing, they would not show up as docked, yet they
// would still come through here, so they would harm each other, if on opposing teams. -- MK, 2/2/98
if ((heavy_obj->flags[Object::Object_Flags::Should_be_dead]) || (light_obj->flags[Object::Object_Flags::Should_be_dead])) {
return 0;
}
#ifndef NDEBUG
// Don't do collision detection on a pair of ships on the same team.
// Change this someday, but for now, it's a problem.
if ( !Collide_friendly ) { // Collide_friendly is a global value changed via debug console
if ( (!player_involved) && (heavy_shipp->team == light_shipp->team) ) {
return 0;
}
}
#endif
// Apparently we're doing same team collisions.
// But, if both are offscreen, ignore the collision
if (heavy_shipp->team == light_shipp->team) {
if ( (!(heavy_obj->flags[Object::Object_Flags::Was_rendered]) && !(light_obj->flags[Object::Object_Flags::Was_rendered])) ) {
return 0;
}
}
// Set up model_collide info
mc_info mc;
mc_info_init(&mc);
// Do in heavy object RF
mc.model_num = heavy_sip->model_num; // Fill in the model to check
mc.model_instance_num = heavy_shipp->model_instance_num;
mc.orient = &heavy_obj->orient; // The object's orient
vec3d zero, p0, p1;
vm_vec_zero(&zero); // we need the physical vector and can not set its value to zero
vm_vec_sub(&p0, &light_obj->last_pos, &heavy_obj->last_pos);
vm_vec_sub(&p1, &light_obj->pos, &heavy_obj->pos);
// find the light object's position in the heavy object's reference frame at last frame and also in this frame.
vec3d p0_temp, p0_rotated;
// Collision detection from rotation enabled if at max rotaional velocity and 5fps, rotation is less than PI/2
// This should account for all ships
if ( (vm_vec_mag_squared( &heavy_obj->phys_info.max_rotvel ) * .04) < (PI*PI/4) ) {
// collide_rotate calculate (1) start position and (2) relative velocity
ship_ship_hit_info->collide_rotate = 1;
vm_vec_rotate(&p0_temp, &p0, &heavy_obj->last_orient);
vm_vec_unrotate(&p0_rotated, &p0_temp, &heavy_obj->orient);
mc.p0 = &p0_rotated; // Point 1 of ray to check
vm_vec_sub(&ship_ship_hit_info->light_rel_vel, &p1, &p0_rotated);
vm_vec_scale(&ship_ship_hit_info->light_rel_vel, 1/flFrametime);
}
// should be no ships that can rotate this fast
else {
#ifndef NDEBUG
static bool Warned_about_fast_rotational_collisions = false;
if (!Warned_about_fast_rotational_collisions) {
Warning(LOCATION, "Ship '%s' rotates too quickly! Rotational collision detection has been skipped.", heavy_sip->name);
Warned_about_fast_rotational_collisions = true;
}
#endif
ship_ship_hit_info->collide_rotate = 0;
mc.p0 = &p0; // Point 1 of ray to check
vm_vec_sub(&ship_ship_hit_info->light_rel_vel, &light_obj->phys_info.vel, &heavy_obj->phys_info.vel);
}
// Set up collision info
mc.pos = &zero; // The object's position
mc.p1 = &p1; // Point 2 of ray to check
mc.radius = model_get_core_radius(light_sip->model_num);
mc.flags = (MC_CHECK_MODEL | MC_CHECK_SPHERELINE); // flags
// Only check invisible face polygons for ship:ship of different teams.
if ( !(heavy_shipp->flags[Ship::Ship_Flags::Dont_collide_invis]) ) {
if ((heavy_obj->flags[Object::Object_Flags::Player_ship]) || (light_obj->flags[Object::Object_Flags::Player_ship]) || (heavy_shipp->team != light_shipp->team) ) {
mc.flags |= MC_CHECK_INVISIBLE_FACES;
}
}
// copy important data
int copy_flags = mc.flags; // make a copy of start end positions of sphere in big ship RF
vec3d copy_p0, copy_p1;
copy_p0 = *mc.p0;
copy_p1 = *mc.p1;
// first test against the sphere - if this fails then don't do any submodel tests
mc.flags = MC_ONLY_SPHERE | MC_CHECK_SPHERELINE;
SCP_vector<int> submodel_vector;
int valid_hit_occured = 0;
polymodel *pm_light;
polymodel_instance *pmi;
pm_light = model_get(Ship_info[light_shipp->ship_info_index].model_num);
if(pm_light->submodel[pm_light->detail[0]].no_collisions) {
return 0;
}
if (model_collide(&mc)) {
// Set earliest hit time
ship_ship_hit_info->hit_time = FLT_MAX;
// Do collision the cool new way
if ( ship_ship_hit_info->collide_rotate ) {
SCP_vector<int>::iterator smv;
model_get_rotating_submodel_list(&submodel_vector, heavy_obj);
pmi = model_get_instance(heavy_shipp->model_instance_num);
// turn off all rotating submodels and test for collision
for (smv = submodel_vector.begin(); smv != submodel_vector.end(); ++smv) {
pmi->submodel[*smv].collision_checked = true;
}
// reset flags to check MC_CHECK_MODEL | MC_CHECK_SPHERELINE and maybe MC_CHECK_INVISIBLE_FACES and MC_SUBMODEL_INSTANCE
mc.flags = copy_flags | MC_SUBMODEL_INSTANCE;
if (heavy_sip->collision_lod > -1) {
mc.lod = heavy_sip->collision_lod;
}
// check each submodel in turn
for (smv = submodel_vector.begin(); smv != submodel_vector.end(); ++smv) {
// turn on submodel for collision test
pmi->submodel[*smv].collision_checked = false;
if (pmi->submodel[*smv].blown_off)
{
pmi->submodel[*smv].collision_checked = true;
continue;
}
// set angles for last frame
angles copy_angles = pmi->submodel[*smv].angs;
// find the start and end positions of the sphere in submodel RF
pmi->submodel[*smv].angs = pmi->submodel[*smv].prev_angs;
world_find_model_instance_point(&p0, &light_obj->last_pos, pmi, *smv, &heavy_obj->last_orient, &heavy_obj->last_pos);
pmi->submodel[*smv].angs = copy_angles;
world_find_model_instance_point(&p1, &light_obj->pos, pmi, *smv, &heavy_obj->orient, &heavy_obj->pos);
mc.p0 = &p0;
mc.p1 = &p1;
mc.orient = &vmd_identity_matrix;
mc.submodel_num = *smv;
if ( model_collide(&mc) ) {
if (mc.hit_dist < ship_ship_hit_info->hit_time ) {
valid_hit_occured = 1;
// set up ship_ship_hit_info common
set_hit_struct_info(ship_ship_hit_info, &mc, SUBMODEL_ROT_HIT);
model_instance_find_world_point(&ship_ship_hit_info->hit_pos, &mc.hit_point, mc.model_instance_num, mc.hit_submodel, &heavy_obj->orient, &zero);
// set up ship_ship_hit_info for rotating submodel
if (ship_ship_hit_info->edge_hit == 0) {
model_instance_find_obj_dir(&ship_ship_hit_info->collision_normal, &mc.hit_normal, mc.model_instance_num, mc.hit_submodel, &heavy_obj->orient);
}
// find position in submodel RF of light object at collison
vec3d int_light_pos, diff;
vm_vec_sub(&diff, mc.p1, mc.p0);
vm_vec_scale_add(&int_light_pos, mc.p0, &diff, mc.hit_dist);
model_instance_find_world_point(&ship_ship_hit_info->light_collision_cm_pos, &int_light_pos, mc.model_instance_num, mc.hit_submodel, &heavy_obj->orient, &zero);
}
}
}
}
// Recover and do usual ship_ship collision, but without rotating submodels
mc.flags = copy_flags;
*mc.p0 = copy_p0;
*mc.p1 = copy_p1;
mc.orient = &heavy_obj->orient;
// usual ship_ship collision test
if ( model_collide(&mc) ) {
// check if this is the earliest hit
if (mc.hit_dist < ship_ship_hit_info->hit_time) {
valid_hit_occured = 1;
set_hit_struct_info(ship_ship_hit_info, &mc, SUBMODEL_NO_ROT_HIT);
// get collision normal if not edge hit
if (ship_ship_hit_info->edge_hit == 0) {
model_instance_find_obj_dir(&ship_ship_hit_info->collision_normal, &mc.hit_normal, mc.model_instance_num, mc.hit_submodel, &heavy_obj->orient);
}
// find position in submodel RF of light object at collison
vec3d diff;
vm_vec_sub(&diff, mc.p1, mc.p0);
vm_vec_scale_add(&ship_ship_hit_info->light_collision_cm_pos, mc.p0, &diff, mc.hit_dist);
}
}
}
if (valid_hit_occured) {
// Collision debug stuff
#ifndef NDEBUG
object *collide_obj = NULL;
if (heavy_obj == Player_obj) {
collide_obj = light_obj;
} else if (light_obj == Player_obj) {
collide_obj = heavy_obj;
}
if ((collide_obj != NULL) && (Ship_info[Ships[collide_obj->instance].ship_info_index].is_fighter_bomber())) {
const char *submode_string = "";
ai_info *aip;
extern const char *Mode_text[];
aip = &Ai_info[Ships[collide_obj->instance].ai_index];
if (aip->mode == AIM_CHASE)
submode_string = Submode_text[aip->submode];
nprintf(("AI", "Player collided with ship %s, AI mode = %s, submode = %s\n", Ships[collide_obj->instance].ship_name, Mode_text[aip->mode], submode_string));
}
#endif
// Update ai to deal with collisions
if (heavy_obj-Objects == Ai_info[light_shipp->ai_index].target_objnum) {
Ai_info[light_shipp->ai_index].ai_flags.set(AI::AI_Flags::Target_collision);
}
if (light_obj-Objects == Ai_info[heavy_shipp->ai_index].target_objnum) {
Ai_info[heavy_shipp->ai_index].ai_flags.set(AI::AI_Flags::Target_collision);
}
// SET PHYSICS PARAMETERS
// already have (hitpos - heavy) and light_cm_pos
// get heavy cm pos - already have light_cm_pos
ship_ship_hit_info->heavy_collision_cm_pos = zero;
// get r_heavy and r_light
ship_ship_hit_info->r_heavy = ship_ship_hit_info->hit_pos;
vm_vec_sub(&ship_ship_hit_info->r_light, &ship_ship_hit_info->hit_pos, &ship_ship_hit_info->light_collision_cm_pos);
// set normal for edge hit
if ( ship_ship_hit_info->edge_hit ) {
vm_vec_copy_normalize(&ship_ship_hit_info->collision_normal, &ship_ship_hit_info->r_light);
vm_vec_negate(&ship_ship_hit_info->collision_normal);
}
// get world hitpos
vm_vec_add(hitpos, &ship_ship_hit_info->heavy->pos, &ship_ship_hit_info->r_heavy);
// do physics
calculate_ship_ship_collision_physics(ship_ship_hit_info);
// Provide some separation for the case of same team
if (heavy_shipp->team == light_shipp->team) {
// If a couple of small ships, just move them apart.
if ((heavy_sip->is_small_ship()) && (light_sip->is_small_ship())) {
if ((heavy_obj->flags[Object::Object_Flags::Player_ship]) || (light_obj->flags[Object::Object_Flags::Player_ship])) {
vec3d h_to_l_vec;
vec3d rel_vel_h;
vec3d perp_rel_vel;
vm_vec_sub(&h_to_l_vec, &heavy_obj->pos, &light_obj->pos);
vm_vec_sub(&rel_vel_h, &heavy_obj->phys_info.vel, &light_obj->phys_info.vel);
float mass_sum = light_obj->phys_info.mass + heavy_obj->phys_info.mass;
// get comp of rel_vel perp to h_to_l_vec;
float mag = vm_vec_dot(&h_to_l_vec, &rel_vel_h) / vm_vec_mag_squared(&h_to_l_vec);
vm_vec_scale_add(&perp_rel_vel, &rel_vel_h, &h_to_l_vec, -mag);
vm_vec_normalize(&perp_rel_vel);
vm_vec_scale_add2(&heavy_obj->phys_info.vel, &perp_rel_vel,
heavy_sip->collision_physics.both_small_bounce * light_obj->phys_info.mass / mass_sum);
vm_vec_scale_add2(&light_obj->phys_info.vel, &perp_rel_vel,
-(light_sip->collision_physics.both_small_bounce) * heavy_obj->phys_info.mass / mass_sum);
vm_vec_rotate( &heavy_obj->phys_info.prev_ramp_vel, &heavy_obj->phys_info.vel, &heavy_obj->orient );
vm_vec_rotate( &light_obj->phys_info.prev_ramp_vel, &light_obj->phys_info.vel, &light_obj->orient );
}
} else {
// add extra velocity to separate the two objects, backing up the direction we came in.
// TODO: add effect of velocity from rotating submodel
float rel_vel = vm_vec_mag_quick( &ship_ship_hit_info->light_rel_vel);
if (rel_vel < 1) {
rel_vel = 1.0f;
}
float mass_sum = heavy_obj->phys_info.mass + light_obj->phys_info.mass;
vm_vec_scale_add2( &heavy_obj->phys_info.vel, &ship_ship_hit_info->light_rel_vel,
heavy_sip->collision_physics.bounce*light_obj->phys_info.mass/(mass_sum*rel_vel) );
vm_vec_rotate( &heavy_obj->phys_info.prev_ramp_vel, &heavy_obj->phys_info.vel, &heavy_obj->orient );
vm_vec_scale_add2( &light_obj->phys_info.vel, &ship_ship_hit_info->light_rel_vel,
-(light_sip->collision_physics.bounce)*heavy_obj->phys_info.mass/(mass_sum*rel_vel) );
vm_vec_rotate( &light_obj->phys_info.prev_ramp_vel, &light_obj->phys_info.vel, &light_obj->orient );
}
}
}
return valid_hit_occured;
}
/**
* Gets modified mass of cruiser in cruiser/asteroid collision so cruisers don't get bumped so hard.
* modified mass is 10x, 4x, or 2x larger than asteroid mass
* @return 1 if modified mass is larger than given mass, 0 otherwise
*/
static int check_special_cruiser_asteroid_collision(object *heavy, object *lighter, float *cruiser_mass, int *cruiser_light)
{
int asteroid_type;
if (heavy->type == OBJ_ASTEROID) {
Assert(lighter->type == OBJ_SHIP);
if (Ship_info[Ships[lighter->instance].ship_info_index].is_big_or_huge()) {
asteroid_type = Asteroids[heavy->instance].asteroid_type;
if (asteroid_type == 0) {
*cruiser_mass = 10.0f * heavy->phys_info.mass;
} else if (asteroid_type == 1) {
*cruiser_mass = 4.0f * heavy->phys_info.mass;
} else {
*cruiser_mass = 2.0f * heavy->phys_info.mass;
}
if (*cruiser_mass > lighter->phys_info.mass) {
*cruiser_light = 1;
return 1;
}
}
} else if (lighter->type == OBJ_ASTEROID) {
Assert(heavy->type == OBJ_SHIP);
if (Ship_info[Ships[heavy->instance].ship_info_index].is_big_or_huge()) {
asteroid_type = Asteroids[lighter->instance].asteroid_type;
if (asteroid_type == 0) {
*cruiser_mass = 10.0f * lighter->phys_info.mass;
} else if (asteroid_type == 1) {
*cruiser_mass = 4.0f * lighter->phys_info.mass;
} else {
*cruiser_mass = 2.0f * lighter->phys_info.mass;
}
if (*cruiser_mass > heavy->phys_info.mass) {
*cruiser_light = 0;
return 1;
}
}
}
return 0;
}
/**
* Find the subobject corresponding to the submodel hit
*/
static bool check_subsystem_landing_allowed(ship_info *heavy_sip, collision_info_struct *ship_ship_hit_info) {
if (!(heavy_sip->flags[Ship::Info_Flags::Allow_landings]))
return false;
for (int i = 0; i < heavy_sip->n_subsystems; i++) {
if (heavy_sip->subsystems[i].flags[Model::Subsystem_Flags::Allow_landing] &&
heavy_sip->subsystems[i].subobj_num == ship_ship_hit_info->submodel_num)
{
return true;
}
}
return false;
}
// ------------------------------------------------------------------------------------------------
// input: ship_ship_hit => structure containing ship_ship hit info
// (includes) A, B => objects colliding
// r_A, r_B => position to collision from center of mass
// collision_normal => collision_normal (outward from B)
//
// output: velocity, angular velocity, impulse
//
// ------------------------------------------------------------------------------------------------
//
// calculates correct physics response to collision between two objects given
// masses, moments of inertia, velocities, angular velocities,
// relative collision positions, and the impulse direction
//
void calculate_ship_ship_collision_physics(collision_info_struct *ship_ship_hit_info)
{
// important parameters passed thru ship_ship_or_debris_hit
// calculate the whack applied to each ship from collision
// make local copies of hit_struct parameters
object *heavy = ship_ship_hit_info->heavy;
object *lighter = ship_ship_hit_info->light;
// gurgh... this includes asteroids and debris too
Assert(heavy->type == OBJ_SHIP || heavy->type == OBJ_ASTEROID || heavy->type == OBJ_DEBRIS);
Assert(lighter->type == OBJ_SHIP || lighter->type == OBJ_ASTEROID || lighter->type == OBJ_DEBRIS);
ship_info *light_sip = (lighter->type == OBJ_SHIP) ? &Ship_info[Ships[lighter->instance].ship_info_index] : NULL;
ship_info *heavy_sip = (heavy->type == OBJ_SHIP) ? &Ship_info[Ships[heavy->instance].ship_info_index] : NULL;
// make cruiser/asteroid collision softer on cruisers.
int special_cruiser_asteroid_collision;
int cruiser_light = 0;
float cruiser_mass = 0.0f, copy_mass = 0.0f;
special_cruiser_asteroid_collision = check_special_cruiser_asteroid_collision(heavy, lighter, &cruiser_mass, &cruiser_light);
if (special_cruiser_asteroid_collision) {
if (cruiser_light) {
Assert(lighter->phys_info.mass < cruiser_mass);
copy_mass = lighter->phys_info.mass;
lighter->phys_info.mass = cruiser_mass;
} else {
Assert(heavy->phys_info.mass < cruiser_mass);
copy_mass = heavy->phys_info.mass;
heavy->phys_info.mass = cruiser_mass;
}
}
float coeff_restitution; // parameter controls amount of bounce
float v_rel_normal_m; // relative collision velocity in the direction of the collision normal
vec3d v_rel_parallel_m; // normalized v_rel (Va-Vb) projected onto collision surface
vec3d world_rotvel_heavy_m, world_rotvel_light_m, vel_from_rotvel_heavy_m, vel_from_rotvel_light_m, v_rel_m, vel_heavy_m, vel_light_m;
coeff_restitution = 0.1f; // relative velocity wrt normal is zero after the collision ( range 0-1 )
// find velocity of each obj at collision point
// heavy object is in cm reference frame so we don't get a v_heavy term.
if ( ship_ship_hit_info->collide_rotate ) {
// if we have collisions from rotation, the effective velocity from rotation of the large body is alreay taken account
vm_vec_zero( &vel_heavy_m );
} else {
// take account the effective velocity from rotation
vm_vec_unrotate(&world_rotvel_heavy_m, &heavy->phys_info.rotvel, &heavy->orient); // heavy's world rotvel before collision
vm_vec_cross(&vel_from_rotvel_heavy_m, &world_rotvel_heavy_m, &ship_ship_hit_info->r_heavy); // heavy's velocity from rotvel before collision
vel_heavy_m = vel_from_rotvel_heavy_m;
}
// if collision from rotating submodel of heavy obj, add in vel from rotvel of submodel
vec3d local_vel_from_submodel;
if (ship_ship_hit_info->submodel_rot_hit == 1) {
polymodel *pm;
polymodel_instance *pmi = NULL;
int model_instance_num = -1;
if (heavy->type == OBJ_SHIP) {
pm = model_get(heavy_sip->model_num);
model_instance_num = Ships[heavy->instance].model_instance_num;
pmi = model_get_instance(model_instance_num);
} else if (heavy->type == OBJ_ASTEROID) {
pm = Asteroid_info[Asteroids[heavy->instance].asteroid_type].modelp[Asteroids[heavy->instance].asteroid_subtype];
model_instance_num = Asteroids[heavy->instance].model_instance_num;
pmi = model_get_instance(model_instance_num);
} else if (heavy->type == OBJ_DEBRIS) {
pm = model_get(Debris[heavy->instance].model_num);
} else {
// we should have caught this already
Int3();
pm = NULL;
}
if ( pmi != NULL && pmi->submodel[ship_ship_hit_info->submodel_num].sii != NULL ) {
// set point on axis of rotating submodel if not already set.
if ( !pmi->submodel[ship_ship_hit_info->submodel_num].sii->axis_set ) {
model_init_submodel_axis_pt(pmi->submodel[ship_ship_hit_info->submodel_num].sii, pm->id, ship_ship_hit_info->submodel_num);
}
vec3d omega, axis, r_rot;
if ( pm->submodel[ship_ship_hit_info->submodel_num].movement_axis == MOVEMENT_AXIS_X ) {
axis = vmd_x_vector;
} else if ( pm->submodel[ship_ship_hit_info->submodel_num].movement_axis == MOVEMENT_AXIS_Y ) {
axis = vmd_y_vector;
} else if ( pm->submodel[ship_ship_hit_info->submodel_num].movement_axis == MOVEMENT_AXIS_Z ) {
axis = vmd_z_vector;
} else {
// must be one of these axes or submodel_rot_hit is incorrectly set
Int3();
}
// get world rotational velocity of rotating submodel
model_instance_find_obj_dir(&omega, &axis, model_instance_num, ship_ship_hit_info->submodel_num, &heavy->orient);
vm_vec_scale(&omega, pmi->submodel[ship_ship_hit_info->submodel_num].sii->cur_turn_rate);
// world coords for r_rot
vec3d temp;
vm_vec_unrotate(&temp, &pmi->submodel[ship_ship_hit_info->submodel_num].sii->pt_on_axis, &heavy->orient);
vm_vec_sub(&r_rot, &ship_ship_hit_info->hit_pos, &temp);
vm_vec_cross(&local_vel_from_submodel, &omega, &r_rot);
} else {
vm_vec_zero(&local_vel_from_submodel);
}
} else {
// didn't collide with submodel
vm_vec_zero(&local_vel_from_submodel);
}
vm_vec_unrotate(&world_rotvel_light_m, &lighter->phys_info.rotvel, &lighter->orient); // light's world rotvel before collision
vm_vec_cross(&vel_from_rotvel_light_m, &world_rotvel_light_m, &ship_ship_hit_info->r_light); // light's velocity from rotvel before collision
vm_vec_add(&vel_light_m, &vel_from_rotvel_light_m, &ship_ship_hit_info->light_rel_vel);
vm_vec_sub(&v_rel_m, &vel_light_m, &vel_heavy_m);
// Add in effect of rotating submodel
vm_vec_sub2(&v_rel_m, &local_vel_from_submodel);
v_rel_normal_m = vm_vec_dot(&v_rel_m, &ship_ship_hit_info->collision_normal);// if less than zero, colliding contact taking place
// (v_slow - v_fast) dot (n_fast)
if (v_rel_normal_m > 0) {
// This can happen in 2 situations.
// (1) The rotational velocity is large enough to cause ships to miss. In this case, there would most likely
// have been a collision, but at a later time, so reset v_rel_normal_m
// (2) We could also have just gotten a slightly incorrect hitpos, where r dot v_rel is nearly zero.
// In this case, we know there was a collision, but slight collision and the normal is correct, so reset v_rel_normal_m
// need a normal direction. We can just take the -v_light normalized. v_rel_normal_m = -v_rel_normal_m;
nprintf(("Physics", "Frame %i reset v_rel_normal_m %f Edge %i\n", Framecount, v_rel_normal_m, ship_ship_hit_info->edge_hit));
v_rel_normal_m = -v_rel_normal_m;
}
//Maybe treat the current collision as a landing
//Init values just to be safe
vec3d light_local_vel(ship_ship_hit_info->light_rel_vel);
float light_uvec_dot_norm = 0.0f;
float light_fvec_dot_norm = 0.0f;
float light_rvec_dot_norm = 0.0f;
bool subsys_landing_allowed = lighter->type == OBJ_SHIP && heavy->type == OBJ_SHIP && check_subsystem_landing_allowed(heavy_sip, ship_ship_hit_info);
if (subsys_landing_allowed) {
vm_vec_rotate(&light_local_vel, &ship_ship_hit_info->light_rel_vel, &lighter->orient);
light_uvec_dot_norm = vm_vec_dot(&ship_ship_hit_info->collision_normal, &lighter->orient.vec.uvec);
light_fvec_dot_norm = vm_vec_dot(&ship_ship_hit_info->collision_normal, &lighter->orient.vec.fvec);
light_rvec_dot_norm = vm_vec_dot(&ship_ship_hit_info->collision_normal, &lighter->orient.vec.rvec);
}
if (subsys_landing_allowed &&
light_local_vel.xyz.z < light_sip->collision_physics.landing_max_z &&
light_local_vel.xyz.z > light_sip->collision_physics.landing_min_z &&
light_local_vel.xyz.y > light_sip->collision_physics.landing_min_y &&
fl_abs(light_local_vel.xyz.x) < light_sip->collision_physics.landing_max_x &&
light_uvec_dot_norm > 0 &&
light_fvec_dot_norm < light_sip->collision_physics.landing_max_angle &&
light_fvec_dot_norm > light_sip->collision_physics.landing_min_angle &&
fl_abs(light_rvec_dot_norm) < light_sip->collision_physics.landing_max_rot_angle)
{
ship_ship_hit_info->is_landing = true;
}
vec3d rotational_impulse_heavy, rotational_impulse_light, delta_rotvel_heavy, delta_rotvel_light;
vec3d delta_vel_from_delta_rotvel_heavy, delta_vel_from_delta_rotvel_light, impulse;
float impulse_mag, heavy_denom, light_denom;
matrix heavy_I_inv, light_I_inv;
// include a frictional collision impulse F parallel to the collision plane
// F = I * sin (collision_normal, normalized v_rel_m) [sin is ratio of v_rel_parallel_m to v_rel_m]
// note: (-) sign is needed to account for the direction of the v_rel_parallel_m
if (IS_VEC_NULL(&v_rel_m)) {
// If the relative velocity is zero then the compuatation below would cause NaN errors
vm_vec_zero(&impulse);
} else {
float collision_speed_parallel;
float parallel_mag;
impulse = ship_ship_hit_info->collision_normal;
vm_vec_projection_onto_plane(&v_rel_parallel_m, &v_rel_m, &ship_ship_hit_info->collision_normal);
collision_speed_parallel = vm_vec_normalize_safe(&v_rel_parallel_m);
float friction = (lighter->type == OBJ_SHIP) ? light_sip->collision_physics.friction : COLLISION_FRICTION_FACTOR;
parallel_mag = float(-friction) * collision_speed_parallel / vm_vec_mag(&v_rel_m);
vm_vec_scale_add2(&impulse, &v_rel_parallel_m, parallel_mag);
}
// calculate the effect on the velocity of the collison point per unit impulse
// first find the effect thru change in rotvel
// then find the change in the cm vel
if (heavy == Player_obj) {
vm_vec_zero( &delta_rotvel_heavy );
heavy_denom = 1.0f / heavy->phys_info.mass;
} else {
vm_vec_cross(&rotational_impulse_heavy, &ship_ship_hit_info->r_heavy, &impulse);
get_I_inv(&heavy_I_inv, &heavy->phys_info.I_body_inv, &heavy->orient);
vm_vec_rotate(&delta_rotvel_heavy, &rotational_impulse_heavy, &heavy_I_inv);
float rotation_factor = (heavy->type == OBJ_SHIP) ? heavy_sip->collision_physics.rotation_factor : COLLISION_ROTATION_FACTOR;
vm_vec_scale(&delta_rotvel_heavy, rotation_factor); // hack decrease rotation (delta_rotvel)
vm_vec_cross(&delta_vel_from_delta_rotvel_heavy, &delta_rotvel_heavy , &ship_ship_hit_info->r_heavy);
heavy_denom = vm_vec_dot(&delta_vel_from_delta_rotvel_heavy, &ship_ship_hit_info->collision_normal);
if (heavy_denom < 0) {
// sanity check
heavy_denom = 0.0f;
}
heavy_denom += 1.0f / heavy->phys_info.mass;
}
// calculate the effect on the velocity of the collison point per unit impulse
// first find the effect thru change in rotvel
// then find the change in the cm vel
// SUSHI: If on a landing surface, use the same shortcut the player gets
// This is a bit of a hack, but gets around some nasty unpredictable collision behavior
// when trying to do AI landings for certain ships
if (lighter == Player_obj || subsys_landing_allowed) {
vm_vec_zero( &delta_rotvel_light );
light_denom = 1.0f / lighter->phys_info.mass;
} else {
vm_vec_cross(&rotational_impulse_light, &ship_ship_hit_info->r_light, &impulse);
get_I_inv(&light_I_inv, &lighter->phys_info.I_body_inv, &lighter->orient);
vm_vec_rotate(&delta_rotvel_light, &rotational_impulse_light, &light_I_inv);
float rotation_factor = (lighter->type == OBJ_SHIP) ? light_sip->collision_physics.rotation_factor : COLLISION_ROTATION_FACTOR;
vm_vec_scale(&delta_rotvel_light, rotation_factor); // hack decrease rotation (delta_rotvel)
vm_vec_cross(&delta_vel_from_delta_rotvel_light, &delta_rotvel_light, &ship_ship_hit_info->r_light);
light_denom = vm_vec_dot(&delta_vel_from_delta_rotvel_light, &ship_ship_hit_info->collision_normal);
if (light_denom < 0) {
// sanity check
light_denom = 0.0f;
}
light_denom += 1.0f / lighter->phys_info.mass;
}
// calculate the necessary impulse to achieved the desired relative velocity after the collision
// update damage info in mc
impulse_mag = -(1.0f + coeff_restitution)*v_rel_normal_m / (heavy_denom + light_denom);
ship_ship_hit_info->impulse = impulse_mag;
if (impulse_mag < 0) {
nprintf(("Physics", "negative impulse mag -- Get Dave A if not Descent Physics\n"));
impulse_mag = -impulse_mag;
}
// update the physics info structs for heavy and light objects
// since we have already calculated delta rotvel for heavy and light in world coords
// physics should not have to recalculate this, just change into body coords (done in collide_whack)
vm_vec_scale(&impulse, impulse_mag);
vm_vec_scale(&delta_rotvel_light, impulse_mag);
physics_collide_whack(&impulse, &delta_rotvel_light, &lighter->phys_info, &lighter->orient, ship_ship_hit_info->is_landing);
vm_vec_negate(&impulse);
vm_vec_scale(&delta_rotvel_heavy, -impulse_mag);
physics_collide_whack(&impulse, &delta_rotvel_heavy, &heavy->phys_info, &heavy->orient, true);
// If within certain bounds, we want to add some more rotation towards the "resting orientation" of the ship
// These bounds are defined separately from normal "landing" bounds so that they can be more generous:
// we can have crash landings that still re-orient the ship.
if (subsys_landing_allowed &&
light_local_vel.xyz.z < light_sip->collision_physics.reorient_max_z &&
light_local_vel.xyz.z > light_sip->collision_physics.reorient_min_z &&
light_local_vel.xyz.y > light_sip->collision_physics.reorient_min_y &&
fl_abs(light_local_vel.xyz.x) < light_sip->collision_physics.reorient_max_x &&
light_uvec_dot_norm > 0 &&
light_fvec_dot_norm < light_sip->collision_physics.reorient_max_angle &&
light_fvec_dot_norm > light_sip->collision_physics.reorient_min_angle &&
fl_abs(light_rvec_dot_norm) < light_sip->collision_physics.reorient_max_rot_angle)
{
vec3d landing_delta_rotvel;
landing_delta_rotvel.xyz.x = (light_fvec_dot_norm * light_sip->collision_physics.reorient_mult)
- light_sip->collision_physics.landing_rest_angle;
// For yaw, use the dot product between vel vector (normalized) and orientation on the xz plane
// This reduces to the following math
// We also clamp to reduce huge nose swings at low speeds
float xzVelMag = sqrt(light_local_vel.xyz.x * light_local_vel.xyz.x + light_local_vel.xyz.z * light_local_vel.xyz.z);
float xzVelDotOrient = MIN(MAX((xzVelMag > 0 ? (light_local_vel.xyz.x / xzVelMag) : 0), -0.5f), 0.5f);
landing_delta_rotvel.xyz.y = (xzVelMag > 2) ? (xzVelDotOrient * light_sip->collision_physics.reorient_mult) : 0;
landing_delta_rotvel.xyz.z = light_rvec_dot_norm * light_sip->collision_physics.reorient_mult * -1;
vm_vec_add2( &lighter->phys_info.rotvel, &landing_delta_rotvel );
}
// Find final positions
// We will try not to worry about the left over time in the frame
// heavy's position unchanged by collision
// light's position is heavy's position plus relative position from heavy
vm_vec_add(&lighter->pos, &heavy->pos, &ship_ship_hit_info->light_collision_cm_pos);
// Try to move each body back to its position just before collision occured to prevent interpenetration
// Move away in direction of light and away in direction of normal
vec3d direction_light; // direction light is moving relative to heavy
vm_vec_sub(&direction_light, &ship_ship_hit_info->light_rel_vel, &local_vel_from_submodel);
vm_vec_normalize_safe(&direction_light);
Assert( !vm_is_vec_nan(&direction_light) );
vm_vec_scale_add2(&heavy->pos, &direction_light, 0.2f * lighter->phys_info.mass / (heavy->phys_info.mass + lighter->phys_info.mass));
vm_vec_scale_add2(&heavy->pos, &ship_ship_hit_info->collision_normal, -0.1f * lighter->phys_info.mass / (heavy->phys_info.mass + lighter->phys_info.mass));
//For landings, we want minimal movement on the light ship (just enough to keep the collision detection honest)
if (ship_ship_hit_info->is_landing) {
vm_vec_scale_add2(&lighter->pos, &ship_ship_hit_info->collision_normal, LANDING_POS_OFFSET);
}
else {
vm_vec_scale_add2(&lighter->pos, &direction_light, -0.2f * heavy->phys_info.mass / (heavy->phys_info.mass + lighter->phys_info.mass));
vm_vec_scale_add2(&lighter->pos, &ship_ship_hit_info->collision_normal, 0.1f * heavy->phys_info.mass / (heavy->phys_info.mass + lighter->phys_info.mass));
}
// restore mass in case of special cruiser / asteroid collision
if (special_cruiser_asteroid_collision) {
if (cruiser_light) {
lighter->phys_info.mass = copy_mass;
} else {
heavy->phys_info.mass = copy_mass;
}
}
}
// ------------------------------------------------------------------------------------------------
// get_I_inv()
//
// input: I_inv_body => inverse moment of inertia matrix in body coordinates
// orient => orientation matrix
//
// output: I_inv => inverse moment of inertia matrix in world coordinates
// ------------------------------------------------------------------------------------------------
//
// calculates the inverse moment of inertia matrix from the body matrix and oreint matrix
//
static void get_I_inv (matrix* I_inv, matrix* I_inv_body, matrix* orient)
{
matrix Mtemp1, Mtemp2;
// I_inv = (Rt)(I_inv_body)(R)
// This is opposite to what is commonly seen in books since we are rotating coordianates axes
// which is equivalent to rotating in the opposite direction (or transpose)
vm_matrix_x_matrix(&Mtemp1, I_inv_body, orient);
vm_copy_transpose(&Mtemp2, orient);
vm_matrix_x_matrix(I_inv, &Mtemp2, &Mtemp1);
}
#define PLANET_DAMAGE_SCALE 4.0f
#define PLANET_DAMAGE_RANGE 3 // If within this factor of radius, apply damage.
fix Last_planet_damage_time = 0;
extern void hud_start_text_flash(char *txt, int t, int interval);
/**
* Procss player_ship:planet damage.
* If within range of planet, apply damage to ship.
*/
static void mcp_1(object *player_objp, object *planet_objp)
{
float planet_radius;
float dist;
planet_radius = planet_objp->radius;
dist = vm_vec_dist_quick(&player_objp->pos, &planet_objp->pos);
if (dist > planet_radius*PLANET_DAMAGE_RANGE)
return;
ship_apply_global_damage( player_objp, planet_objp, NULL, PLANET_DAMAGE_SCALE * flFrametime * (float)pow((planet_radius*PLANET_DAMAGE_RANGE)/dist, 3.0f) );
if ((Missiontime - Last_planet_damage_time > F1_0) || (Missiontime < Last_planet_damage_time)) {
HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", XSTR( "Too close to planet. Taking damage!", 465));
Last_planet_damage_time = Missiontime;
snd_play_3d( gamesnd_get_game_sound(ship_get_sound(player_objp, GameSounds::ABURN_ENGAGE)), &player_objp->pos, &View_position );
}
}
/**
* Return true if *objp is a planet, else return false.
* Hack: Just checking first six letters of name.
*/
static int is_planet(object *objp)
{
return (strnicmp(Ships[objp->instance].ship_name, NOX("planet"), 6) == 0);
}
/**
* If exactly one of these is a planet and the other is a player ship, do something special.
* @return true if this was a ship:planet (or planet_ship) collision and we processed it. Else return false.
*/
static int maybe_collide_planet (object *obj1, object *obj2)
{
ship_info *sip1, *sip2;
sip1 = &Ship_info[Ships[obj1->instance].ship_info_index];
sip2 = &Ship_info[Ships[obj2->instance].ship_info_index];
if (sip1->flags[Ship::Info_Flags::Player_ship]) {
if (is_planet(obj2)) {
mcp_1(obj1, obj2);
return 1;
}
} else if (sip2->flags[Ship::Info_Flags::Player_ship]) {
if (is_planet(obj1)) {
mcp_1(obj2, obj1);
return 1;
}
}
return 0;
}
/**
* Given a global point and an object, get the quadrant number the point belongs to.
*/
int get_ship_quadrant_from_global(vec3d *global_pos, object *objp)
{
vec3d tpos;
vec3d rotpos;
vm_vec_sub(&tpos, global_pos, &objp->pos);
vm_vec_rotate(&rotpos, &tpos, &objp->orient);
return get_quadrant(&rotpos, objp);
}
#define MIN_REL_SPEED_FOR_LOUD_COLLISION 50 // relative speed of two colliding objects at which we play the "loud" collide sound
void collide_ship_ship_sounds_init()
{
Player_collide_sound = -1;
AI_collide_sound = -1;
Player_collide_shield_sound = -1;
AI_collide_shield_sound = -1;
}
/**
* Determine what sound to play when two ships collide
*/
void collide_ship_ship_do_sound(vec3d *world_hit_pos, object *A, object *B, int player_involved)
{
vec3d rel_vel;
float rel_speed;
vm_vec_sub(&rel_vel, &A->phys_info.desired_vel, &B->phys_info.desired_vel);
rel_speed = vm_vec_mag_quick(&rel_vel);
if ( rel_speed > MIN_REL_SPEED_FOR_LOUD_COLLISION ) {
snd_play_3d( gamesnd_get_game_sound(GameSounds::SHIP_SHIP_HEAVY), world_hit_pos, &View_position );
} else {
if ( player_involved ) {
if ( !snd_is_playing(Player_collide_sound) ) {
Player_collide_sound = snd_play_3d( gamesnd_get_game_sound(GameSounds::SHIP_SHIP_LIGHT), world_hit_pos, &View_position );
}
} else {
if ( !snd_is_playing(AI_collide_sound) ) {
AI_collide_sound = snd_play_3d( gamesnd_get_game_sound(GameSounds::SHIP_SHIP_LIGHT), world_hit_pos, &View_position );
}
}
}
// maybe play a "shield" collision sound overlay if appropriate
if ( (shield_get_strength(A) > 5) || (shield_get_strength(B) > 5) ) {
if ( player_involved ) {
if ( !snd_is_playing(Player_collide_sound) ) {
Player_collide_shield_sound = snd_play_3d( gamesnd_get_game_sound(GameSounds::SHIP_SHIP_SHIELD), world_hit_pos, &View_position );
}
} else {
if ( !snd_is_playing(Player_collide_sound) ) {
AI_collide_shield_sound = snd_play_3d( gamesnd_get_game_sound(GameSounds::SHIP_SHIP_SHIELD), world_hit_pos, &View_position );
}
}
}
}
/**
* obj1 and obj2 collided.
* If different teams, kamikaze bit set and other ship is large, auto-explode!
*/
static void do_kamikaze_crash(object *obj1, object *obj2)
{
ai_info *aip1, *aip2;
ship *ship1, *ship2;
ship1 = &Ships[obj1->instance];
ship2 = &Ships[obj2->instance];
aip1 = &Ai_info[ship1->ai_index];
aip2 = &Ai_info[ship2->ai_index];
if (ship1->team != ship2->team) {
if (aip1->ai_flags[AI::AI_Flags::Kamikaze]) {
if (Ship_info[ship2->ship_info_index].is_big_or_huge()) {
obj1->hull_strength = KAMIKAZE_HULL_ON_DEATH;
shield_set_strength(obj1, 0.0f);
}
} if (aip2->ai_flags[AI::AI_Flags::Kamikaze]) {
if (Ship_info[ship1->ship_info_index].is_big_or_huge()) {
obj2->hull_strength = KAMIKAZE_HULL_ON_DEATH;
shield_set_strength(obj2, 0.0f);
}
}
}
}
/**
* Response when hit by fast moving cap ship
*/
static void maybe_push_little_ship_from_fast_big_ship(object *big_obj, object *small_obj, float impulse, vec3d *normal)
{
// Move player out of the way of a BIG|HUGE ship warping in or out
int big_class = Ship_info[Ships[big_obj->instance].ship_info_index].class_type;
int small_class = Ship_info[Ships[small_obj->instance].ship_info_index].class_type;
if (big_class > -1 && Ship_types[big_class].flags[Ship::Type_Info_Flags::Warp_pushes]) {
if (small_class > -1 && Ship_types[small_class].flags[Ship::Type_Info_Flags::Warp_pushable]) {
float big_speed = vm_vec_mag_quick(&big_obj->phys_info.vel);
if (big_speed > 3*big_obj->phys_info.max_vel.xyz.z) {
// push player away in direction perp to forward of big ship
// get perp vec
vec3d temp, perp;
vm_vec_sub(&temp, &small_obj->pos, &big_obj->pos);
vm_vec_scale_add(&perp, &temp, &big_obj->orient.vec.fvec, -vm_vec_dot(&temp, &big_obj->orient.vec.fvec));
vm_vec_normalize_quick(&perp);
// don't drive into sfc we just collided with
if (vm_vec_dot(&perp, normal) < 0) {
vm_vec_negate(&perp);
}
// get magnitude of added perp vel
float added_perp_vel_mag = impulse / small_obj->phys_info.mass;
// add to vel and ramp vel
vm_vec_scale_add2(&small_obj->phys_info.vel, &perp, added_perp_vel_mag);
vm_vec_rotate(&small_obj->phys_info.prev_ramp_vel, &small_obj->phys_info.vel, &small_obj->orient);
}
}
}
}
/**
* Checks ship-ship collisions.
* @return 1 if all future collisions between these can be ignored because pair->a or pair->b aren't ships
* @return Otherwise always returns 0, since two ships can always collide unless one (1) dies or (2) warps out.
*/
int collide_ship_ship( obj_pair * pair )
{
int player_involved;
float dist;
object *A = pair->a;
object *B = pair->b;
if ( A->type == OBJ_WAYPOINT ) return 1;
if ( B->type == OBJ_WAYPOINT ) return 1;
Assert( A->type == OBJ_SHIP );
Assert( B->type == OBJ_SHIP );
if (reject_due_collision_groups(A,B))
return 0;
// If the player is one of the two colliding ships, flag this... it is used in
// several places this function.
if ( A == Player_obj || B == Player_obj ) {
player_involved = 1;
} else {
player_involved = 0;
}
// Don't check collisions for warping out player if past stage 1.
if ( player_involved && (Player->control_mode > PCM_WARPOUT_STAGE1) ) {
return 0;
}
dist = vm_vec_dist( &A->pos, &B->pos );
// If one of these is a planet, do special stuff.
if (maybe_collide_planet(A, B))
return 0;
if ( dist < A->radius + B->radius ) {
int hit;
object *HeavyOne, *LightOne;
// if two objects have the same mass, make the one with the larger pointer address the HeavyOne.
if ( fl_abs(A->phys_info.mass - B->phys_info.mass) < 1 ) {
if (A > B) {
HeavyOne = A;
LightOne = B;
} else {
HeavyOne = B;
LightOne = A;
}
} else {
if (A->phys_info.mass > B->phys_info.mass) {
HeavyOne = A;
LightOne = B;
} else {
HeavyOne = B;
LightOne = A;
}
}
ship_info *light_sip = &Ship_info[Ships[LightOne->instance].ship_info_index];
collision_info_struct ship_ship_hit_info;
init_collision_info_struct(&ship_ship_hit_info);
ship_ship_hit_info.heavy = HeavyOne; // heavy object, generally slower moving
ship_ship_hit_info.light = LightOne; // light object, generally faster moving
vec3d world_hit_pos;
hit = ship_ship_check_collision(&ship_ship_hit_info, &world_hit_pos);
pair->next_check_time = timestamp(0);
if ( hit )
{
Script_system.SetHookObjects(4, "Ship", A, "ShipB", B, "Self", A, "Object", B);
bool a_override = Script_system.IsConditionOverride(CHA_COLLIDESHIP, A);
//Yes this should be reversed.
Script_system.SetHookObjects(4, "Ship", B, "ShipB", A, "Self", B, "Object", A);
bool b_override = Script_system.IsConditionOverride(CHA_COLLIDESHIP, B);
if(!a_override && !b_override)
{
float damage;
if ( player_involved && (Player->control_mode == PCM_WARPOUT_STAGE1) ) {
gameseq_post_event( GS_EVENT_PLAYER_WARPOUT_STOP );
HUD_printf("%s", XSTR( "Warpout sequence aborted.", 466));
}
damage = 0.005f * ship_ship_hit_info.impulse; // Cut collision-based damage in half.
// Decrease heavy damage by 2x.
if (damage > 5.0f){
damage = 5.0f + (damage - 5.0f)/2.0f;
}
do_kamikaze_crash(A, B);
if (ship_ship_hit_info.impulse > 0) {
//Only flash the "Collision" text if not landing
if ( player_involved && !ship_ship_hit_info.is_landing) {
hud_start_text_flash(XSTR("Collision", 1431), 2000);
}
}
//If this is a landing, play a different sound
if (ship_ship_hit_info.is_landing) {
if (vm_vec_mag(&ship_ship_hit_info.light_rel_vel) > MIN_LANDING_SOUND_VEL) {
if ( player_involved ) {
if ( !snd_is_playing(Player_collide_sound) ) {
Player_collide_sound = snd_play_3d( gamesnd_get_game_sound(light_sip->collision_physics.landing_sound_idx), &world_hit_pos, &View_position );
}
} else {
if ( !snd_is_playing(AI_collide_sound) ) {
AI_collide_sound = snd_play_3d( gamesnd_get_game_sound(light_sip->collision_physics.landing_sound_idx), &world_hit_pos, &View_position );
}
}
}
}
else {
collide_ship_ship_do_sound(&world_hit_pos, A, B, player_involved);
}
// check if we should do force feedback stuff
if (player_involved && (ship_ship_hit_info.impulse > 0)) {
float scaler;
vec3d v;
scaler = -ship_ship_hit_info.impulse / Player_obj->phys_info.mass * 300;
vm_vec_copy_normalize(&v, &world_hit_pos);
joy_ff_play_vector_effect(&v, scaler);
}
#ifndef NDEBUG
if ( !Collide_friendly ) {
if ( Ships[A->instance].team == Ships[B->instance].team ) {
vec3d collision_vec, right_angle_vec;
vm_vec_normalized_dir(&collision_vec, &ship_ship_hit_info.hit_pos, &A->pos);
if (vm_vec_dot(&collision_vec, &A->orient.vec.fvec) > 0.999f){
right_angle_vec = A->orient.vec.rvec;
} else {
vm_vec_cross(&right_angle_vec, &A->orient.vec.uvec, &collision_vec);
}
vm_vec_scale_add2( &A->phys_info.vel, &right_angle_vec, +2.0f);
vm_vec_scale_add2( &B->phys_info.vel, &right_angle_vec, -2.0f);
return 0;
}
}
#endif
//Only do damage if not a landing
if (!ship_ship_hit_info.is_landing) {
// Scale damage based on skill level for player.
if ((LightOne->flags[Object::Object_Flags::Player_ship]) || (HeavyOne->flags[Object::Object_Flags::Player_ship])) {
damage *= (float) (Game_skill_level*Game_skill_level+1)/(NUM_SKILL_LEVELS+1);
} else if (Ships[LightOne->instance].team == Ships[HeavyOne->instance].team) {
// Decrease damage if non-player ships and not large.
// Looks dumb when fighters are taking damage from bumping into each other.
if ((LightOne->radius < 50.0f) && (HeavyOne->radius <50.0f)) {
damage /= 4.0f;
}
}
float dam2 = (100.0f * damage/LightOne->phys_info.mass);
int quadrant_num = get_ship_quadrant_from_global(&world_hit_pos, ship_ship_hit_info.heavy);
if ((ship_ship_hit_info.heavy->flags[Object::Object_Flags::No_shields]) || !ship_is_shield_up(ship_ship_hit_info.heavy, quadrant_num) ) {
quadrant_num = -1;
}
ship_apply_local_damage(ship_ship_hit_info.heavy, ship_ship_hit_info.light, &world_hit_pos, 100.0f * damage/HeavyOne->phys_info.mass, quadrant_num, CREATE_SPARKS, ship_ship_hit_info.submodel_num, &ship_ship_hit_info.collision_normal);
hud_shield_quadrant_hit(ship_ship_hit_info.heavy, quadrant_num);
// don't draw sparks (using sphere hitpos)
ship_apply_local_damage(ship_ship_hit_info.light, ship_ship_hit_info.heavy, &world_hit_pos, dam2, MISS_SHIELDS, NO_SPARKS, -1, &ship_ship_hit_info.collision_normal);
hud_shield_quadrant_hit(ship_ship_hit_info.light, -1);
maybe_push_little_ship_from_fast_big_ship(ship_ship_hit_info.heavy, ship_ship_hit_info.light, ship_ship_hit_info.impulse, &ship_ship_hit_info.collision_normal);
}
}
if(!(b_override && !a_override))
{
Script_system.SetHookObjects(4, "Ship", A, "ShipB", B, "Self", A, "Object", B);
Script_system.RunCondition(CHA_COLLIDESHIP, '\0', NULL, A);
}
if((b_override && !a_override) || (!b_override && !a_override))
{
//Yes this should be reversed.
Script_system.SetHookObjects(4, "Ship", B, "ShipB", A, "Self", B, "Object", A);
Script_system.RunCondition(CHA_COLLIDESHIP, '\0', NULL, B);
}
Script_system.RemHookVars(4, "Ship", "ShipB", "Self", "Object");
return 0;
}
}
else {
// estimate earliest time at which pair can hit
// cap ships warping in/out can exceed ship's expected velocity
// if ship is warping in, in stage 1, its velocity is 0, so make ship try to collide next frame
// if ship is huge and warping in or out
if (((Ships[A->instance].flags[Ship::Ship_Flags::Arriving_stage_1]) && (Ship_info[Ships[A->instance].ship_info_index].is_huge_ship()))
|| ((Ships[B->instance].flags[Ship::Ship_Flags::Arriving_stage_1]) && (Ship_info[Ships[B->instance].ship_info_index].is_huge_ship())) ) {
pair->next_check_time = timestamp(0); // check next time
return 0;
}
// get max of (1) max_vel.z, (2) 10, (3) afterburner_max_vel.z, (4) vel.z (for warping in ships exceeding expected max vel)
float shipA_max_speed, shipB_max_speed, time;
// get shipA max speed
if (ship_is_beginning_warpout_speedup(A)) {
shipA_max_speed = MAX(ship_get_max_speed(&Ships[A->instance]), ship_get_warpout_speed(A));
} else {
shipA_max_speed = ship_get_max_speed(&Ships[A->instance]);
}
// Maybe warping in or finished warping in with excessive speed
shipA_max_speed = MAX(shipA_max_speed, vm_vec_mag(&A->phys_info.vel));
shipA_max_speed = MAX(shipA_max_speed, 10.0f);
// get shipB max speed
if (ship_is_beginning_warpout_speedup(B)) {
shipB_max_speed = MAX(ship_get_max_speed(&Ships[B->instance]), ship_get_warpout_speed(B));
} else {
shipB_max_speed = ship_get_max_speed(&Ships[B->instance]);
}
// Maybe warping in or finished warping in with excessive speed
shipB_max_speed = MAX(shipB_max_speed, vm_vec_mag(&B->phys_info.vel));
shipB_max_speed = MAX(shipB_max_speed, 10.0f);
time = 1000.0f * (dist - A->radius - B->radius) / (shipA_max_speed + shipB_max_speed);
time -= 200.0f; // allow one frame slow frame at ~5 fps
if (time > 0) {
pair->next_check_time = timestamp( fl2i(time) );
} else {
pair->next_check_time = timestamp(0); // check next time
}
}
return 0;
}
void collect_ship_ship_physics_info(object *heavier_obj, object *lighter_obj, mc_info *mc_info_obj, collision_info_struct *ship_ship_hit_info)
{
// slower moving object [A] is checked at its final position (polygon and position is found on obj)
// faster moving object [B] is reduced to a point and a ray is drawn from its last_pos to pos
// collision code returns hit position and normal on [A]
// estimate location on B that contacts A
// first find orientation of B relative to the normal it collides against.
// then find an approx hit location using the position hit on the bounding box
vec3d *r_heavy = &ship_ship_hit_info->r_heavy;
vec3d *r_light = &ship_ship_hit_info->r_light;
vec3d *heavy_collide_cm_pos = &ship_ship_hit_info->heavy_collision_cm_pos;
vec3d *light_collide_cm_pos = &ship_ship_hit_info->light_collision_cm_pos;
float core_rad = model_get_core_radius(Ship_info[Ships[lighter_obj->instance].ship_info_index].model_num);
// get info needed for ship_ship_collision_physics
Assert(mc_info_obj->hit_dist > 0);
// get light_collide_cm_pos
if ( !ship_ship_hit_info->submodel_rot_hit ) {
vec3d displacement;
vm_vec_sub(&displacement, mc_info_obj->p1, mc_info_obj->p0);
*light_collide_cm_pos = *mc_info_obj->p0;
vm_vec_scale_add2(light_collide_cm_pos, &displacement, ship_ship_hit_info->hit_time);
}
// get r_light
vm_vec_sub(r_light, &ship_ship_hit_info->hit_pos, light_collide_cm_pos);
float mag = float(fabs(vm_vec_mag(r_light) - core_rad));
if (mag > 0.1) {
nprintf(("Physics", "Framecount: %i |r_light - core_rad| > 0.1)\n", Framecount));
}
if (ship_ship_hit_info->edge_hit) {
// For an edge hit, just take the closest valid plane normal as the collision normal
vm_vec_copy_normalize(&ship_ship_hit_info->collision_normal, r_light);
vm_vec_negate(&ship_ship_hit_info->collision_normal);
}
// r dot n may not be negative if hit by moving model parts.
float dot = vm_vec_dot( r_light, &ship_ship_hit_info->collision_normal );
if ( dot > 0 )
{
nprintf(("Physics", "Framecount: %i r dot normal %f > 0\n", Framecount, dot));
}
vm_vec_zero(heavy_collide_cm_pos);
float q = vm_vec_dist(heavy_collide_cm_pos, light_collide_cm_pos) / (heavier_obj->radius + core_rad);
if (q > 1.0f) {
nprintf(("Physics", "Warning: q = %f. Supposed to be <= 1.0.\n", q));
}
*r_heavy = ship_ship_hit_info->hit_pos;
// sphere_sphere_case_handled separately
#ifdef COLLIDE_DEBUG
nprintf(("Physics", "Frame: %i %s info: last_pos: [%4.1f, %4.1f, %4.1f], collide_pos: [%4.1f, %4.1f %4.1f] vel: [%4.1f, %4.1f %4.1f]\n",
Framecount, Ships[heavier_obj->instance].ship_name, heavier_obj->last_pos.x, heavier_obj->last_pos.y, heavier_obj->last_pos.z,
heavy_collide_cm_pos.x, heavy_collide_cm_pos.y, heavy_collide_cm_pos.z,
heavier_obj->phys_info.vel.x, heavier_obj->phys_info.vel.y, heavier_obj->phys_info.vel.z));
nprintf(("Physics", "Frame: %i %s info: last_pos: [%4.1f, %4.1f, %4.1f], collide_pos: [%4.1f, %4.1f, %4.1f] vel: [%4.1f, %4.1f, %4.1f]\n",
Framecount, Ships[lighter_obj->instance].ship_name, lighter_obj->last_pos.x, lighter_obj->last_pos.y, lighter_obj->last_pos.z,
light_collide_cm_pos.x, light_collide_cm_pos.y, light_collide_cm_pos.z,
lighter_obj->phys_info.vel.x, lighter_obj->phys_info.vel.y, lighter_obj->phys_info.vel.z));
#endif
}