Files
fs2open.github.com/code/object/objcollide.cpp
T
2002-06-03 04:10:40 +00:00

1151 lines
34 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.
*
*/
/*
* $Logfile: /Freespace2/code/Object/ObjCollide.cpp $
* $Revision: 2.0 $
* $Date: 2002-06-03 04:02:27 $
* $Author: penguin $
*
* Helper routines for all the collision detection functions
* Also keeps track of all the object pairs.
*
* $Log: not supported by cvs2svn $
* Revision 1.2 2002/05/04 04:52:22 mharris
* 1st draft at porting
*
* Revision 1.1 2002/05/02 18:03:11 mharris
* Initial checkin - converted filenames and includes to lower case
*
*
* 18 7/15/99 9:20a Andsager
* FS2_DEMO initial checkin
*
* 17 6/14/99 3:21p Andsager
* Allow collisions between ship and its debris. Fix up collision pairs
* when large ship is warping out.
*
* 16 5/08/99 8:25p Dave
* Upped object pairs. First run of nebula lightning.
*
* 15 4/23/99 12:01p Johnson
* Added SIF_HUGE_SHIP
*
* 14 4/21/99 6:15p Dave
* Did some serious housecleaning in the beam code. Made it ready to go
* for anti-fighter "pulse" weapons. Fixed collision pair creation. Added
* a handy macro for recalculating collision pairs for a given object.
*
* 13 4/20/99 3:41p Andsager
* Modify objects_will_collide to use endpoints
*
* 12 3/10/99 6:50p Dave
* Changed the way we buffer packets for all clients. Optimized turret
* fired packets. Did some weapon firing optimizations.
*
* 11 3/05/99 3:32p Dan
* Make out of collision pars message npirntf with "collision"
*
* 10 1/21/99 2:06p Dave
* Final checkin for multiplayer testing.
*
* 9 1/21/99 10:44a Dave
* More beam weapon stuff. Put in warmdown time.
*
* 8 1/15/99 5:01p Dave
* Fix for object collision problem.
*
* 7 1/12/99 5:45p Dave
* Moved weapon pipeline in multiplayer to almost exclusively client side.
* Very good results. Bandwidth goes down, playability goes up for crappy
* connections. Fixed object update problem for ship subsystems.
*
* 6 1/12/99 12:53a Dave
* More work on beam weapons - made collision detection very efficient -
* collide against all object types properly - made 3 movement types
* smooth. Put in test code to check for possible non-darkening pixels on
* object textures.
*
* 5 11/19/98 11:08p Andsager
* Check in of physics and collision detection of rotating submodels
*
* 4 11/05/98 5:55p Dave
* Big pass at reducing #includes
*
* 3 10/26/98 9:42a Dave
* Early flak gun support.
*
* 2 10/07/98 10:53a Dave
* Initial checkin.
*
* 1 10/07/98 10:50a Dave
*
* 38 7/29/98 3:24p Allender
* don't call collision code when either of the object pairs is NULL.
*
* 37 4/08/98 8:38a Mike
* Make ships avoid player when attacking or guarding a faraway object.
*
* 36 4/02/98 11:40a Lawrance
* check for #ifdef DEMO instead of #ifdef DEMO_RELEASE
*
* 35 4/01/98 1:48p Allender
* major changes to ship collision in multiplayer. Clients now do own
* ship/ship collisions (with their own ship only) Modifed the hull
* update packet to be sent quicker when object is target of player.
*
* 34 4/01/98 10:57a Mike
* Reduce array sizes to save memory.
*
* 33 3/31/98 5:18p John
* Removed demo/save/restore. Made NDEBUG defined compile. Removed a
* bunch of debug stuff out of player file. Made model code be able to
* unload models and malloc out only however many models are needed.
*
*
* 32 3/06/98 12:45a Lawrance
* Rmove a bogus assert in the object pair save/restore
*
* 31 3/05/98 3:12p Mike
* Fix bugs in asteroid collisions. Throw asteroids at Galataea (needs to
* be made general). Add skill level support for asteroid throwing.
*
* 30 3/05/98 12:12a Mike
* Make an asteroid in the way prevent player warpout.
*
* 29 2/27/98 4:48p John
* Made objects keep track of number of pairs they have associated with
* them. Then, I can early out of the obj_remove_all which was 2.5% of
* frametime at beginning of sm2-2 which then is 0% after this.
*
* 28 2/26/98 10:07p Hoffoss
* Rewrote state saving and restoring to fix bugs and simplify the code.
*
* 27 2/23/98 9:00a Andsager
* improved back face culling of object:laser pairs to check relative
* velocity dot laser forward < 0 vs. v_other dot v_laser < 0
*
* 26 2/19/98 4:34p Lawrance
* Allow weapon-asteroid collisions
*
* 25 2/19/98 10:52a Sandeep
* AL: Have asteroids collide with all ships
*
* 24 2/12/98 11:45a Andsager
* Try to cull debris:weapon collisions before they collisoin pair is
* created. Look at movement in lifetime and whether debris is behind
* laser.
*
* 23 2/07/98 3:54p John
* Added FVI to show_game_resources
*
* 22 2/06/98 7:29p John
* Made only players ships and player ship weapons do collisions with
* asteroids.
*
* 21 2/05/98 11:49p Andsager
* Don't check laser:ship collisions on same team for small ships if laser
* source not player
*
* 20 2/05/98 9:21p John
* Some new Direct3D code. Added code to monitor a ton of stuff in the
* game.
*
* 19 2/05/98 12:51a Mike
* Early asteroid stuff.
*
* 18 2/03/98 9:40a Andsager
* Modify collision object pair creation code so that pairs between debris
* and weapons (other than player) are not created.
*
* 17 1/28/98 11:06a Andsager
* Remove some collision pairs. Add debug code for displaying collision
* pair info.
*
* 16 1/24/98 11:16a Andsager
* Remove debug info.
*
* 15 1/24/98 11:09a Andsager
* Fixed bug that was removing collision pairs when weapon within the
* radius of object.
*
* 14 1/23/98 5:07p Andsager
* Added tests for object pairs. (1) for weapons that do not turn, find
* the soonest hit time. (2) Check if soonest hit time if after end of
* weapon lifetime.
*
* 13 1/22/98 6:43p John
* Took out debug code.
*
* 12 1/22/98 6:42p John
* Move game_flush out of debug console into freespace. Made object
* pair code throw out some weapons that turn. Added stats for how many
* object pair are actually checked.
*
* 11 1/17/98 10:06p Lawrance
* For missiles, only collide if WIF_BOMB flag set.
*
* 10 1/14/98 5:21p Allender
* system to delete object when buffer is nearly full. System in place to
* delete weapons when nearly out of weapons slots
*
* 9 1/13/98 8:09p John
* Removed the old collision system that checked all pairs. Added code
* to disable collisions and particles.
*
* 8 1/13/98 3:11p Allender
* new code to remove old weapons when no more weapon slots available.
* Currently not called anywhere pending further testing
*
* 7 1/10/98 3:22p Allender
* temporary check in to get new code
*
* 6 1/10/98 1:14p John
* Added explanation to debug console commands
*
* 5 1/09/98 9:29a Mike
* Enable docked ships to warp out. Make damage done to a ship not
* proportional to its own mass.
*
* 4 1/08/98 12:12a Mike
* Make ships turn before warping out, if necessary, to avoid a collision.
* Warn player if his warpout will collide. Abort if in stage1.
*
* 3 12/21/97 4:33p John
* Made debug console functions a class that registers itself
* automatically, so you don't need to add the function to
* debugfunctions.cpp.
*
* 2 12/16/97 5:23p Allender
* don't deal with object collision paris on multiplayer clients
*
* 1 9/17/97 2:14p John
* Initial revision
*
* $NoKeywords: $
*/
#include "objcollide.h"
#include "linklist.h"
#include "systemvars.h"
#include "timer.h"
#include "multi.h"
#include "beam.h"
#ifdef FS2_DEMO
#define MAX_PAIRS 3000
#else
#define MAX_PAIRS 8000 // Reduced from 10,000 to 6,000 by MK on 4/1/98.
// Most I saw was 3400 in sm1-06a, the asteriod mission. No other mission came close.
#endif
// the next 3 variables are used for pair statistics
// also in weapon.cpp there is Weapons_created.
int Pairs_created = 0;
int Num_pairs = 0;
int Num_pairs_checked = 0;
int pairs_not_created = 0;
int Num_pairs_hwm = 0;
obj_pair Obj_pairs[MAX_PAIRS];
obj_pair pair_used_list;
obj_pair pair_free_list;
void obj_reset_pairs()
{
int i;
// mprintf(( "Resetting object pairs...\n" ));
pair_used_list.a = pair_used_list.b = NULL;
pair_used_list.next = NULL;
pair_free_list.a = pair_free_list.b = NULL;
pair_free_list.next = &Obj_pairs[0];
Num_pairs = 0;
for (i=0; i<MAX_PAIRS; i++ ) {
Obj_pairs[i].a = Obj_pairs[i].b = NULL;
Obj_pairs[i].next = &Obj_pairs[i+1];
}
Obj_pairs[MAX_PAIRS-1].next = NULL;
}
// returns true if we should reject object pair if one is child of other.
int reject_obj_pair_on_parent(object *A, object *B)
{
if (A->type == OBJ_SHIP) {
if (B->type == OBJ_DEBRIS) {
if (B->parent_sig == A->signature) {
return 0;
}
}
}
if (B->type == OBJ_SHIP) {
if (A->type == OBJ_DEBRIS) {
if (A->parent_sig == B->signature) {
return 0;
}
}
}
if (A->parent_sig == B->signature) {
return 1;
}
if (B->parent_sig == A->signature) {
return 1;
}
return 0;
}
// Adds the pair to the pair list
void obj_add_pair( object *A, object *B, int check_time, int add_to_end )
{
uint ctype;
int (*check_collision)( obj_pair *pair );
int swapped = 0;
check_collision = NULL;
if ( A==B ) return; // Don't check collisions with yourself
if ( !(A->flags&OF_COLLIDES) ) return; // This object doesn't collide with anything
if ( !(B->flags&OF_COLLIDES) ) return; // This object doesn't collide with anything
// Make sure you're not checking a parent with it's kid or vicy-versy
// if ( A->parent_sig == B->signature && !(A->type == OBJ_SHIP && B->type == OBJ_DEBRIS) ) return;
// if ( B->parent_sig == A->signature && !(A->type == OBJ_DEBRIS && B->type == OBJ_SHIP) ) return;
if ( reject_obj_pair_on_parent(A,B) ) {
return;
}
Assert( A->type < 127 );
Assert( B->type < 127 );
ctype = COLLISION_OF(A->type,B->type);
switch( ctype ) {
case COLLISION_OF(OBJ_WEAPON,OBJ_SHIP):
swapped = 1;
check_collision = collide_ship_weapon;
break;
case COLLISION_OF(OBJ_SHIP, OBJ_WEAPON):
check_collision = collide_ship_weapon;
break;
case COLLISION_OF(OBJ_DEBRIS, OBJ_WEAPON):
check_collision = collide_debris_weapon;
break;
case COLLISION_OF(OBJ_WEAPON, OBJ_DEBRIS):
swapped = 1;
check_collision = collide_debris_weapon;
break;
case COLLISION_OF(OBJ_DEBRIS, OBJ_SHIP):
check_collision = collide_debris_ship;
break;
case COLLISION_OF(OBJ_SHIP, OBJ_DEBRIS):
check_collision = collide_debris_ship;
swapped = 1;
break;
case COLLISION_OF(OBJ_ASTEROID, OBJ_WEAPON):
// Only check collision's with player weapons
// if ( Objects[B->parent].flags & OF_PLAYER_SHIP ) {
check_collision = collide_asteroid_weapon;
// }
break;
case COLLISION_OF(OBJ_WEAPON, OBJ_ASTEROID):
swapped = 1;
// Only check collision's with player weapons
// if ( Objects[A->parent].flags & OF_PLAYER_SHIP ) {
check_collision = collide_asteroid_weapon;
// }
break;
case COLLISION_OF(OBJ_ASTEROID, OBJ_SHIP):
// Only check collisions with player ships
// if ( B->flags & OF_PLAYER_SHIP ) {
check_collision = collide_asteroid_ship;
// }
break;
case COLLISION_OF(OBJ_SHIP, OBJ_ASTEROID):
// Only check collisions with player ships
// if ( A->flags & OF_PLAYER_SHIP ) {
check_collision = collide_asteroid_ship;
// }
swapped = 1;
break;
case COLLISION_OF(OBJ_SHIP,OBJ_SHIP):
check_collision = collide_ship_ship;
break;
case COLLISION_OF(OBJ_BEAM, OBJ_SHIP):
if(beam_collide_early_out(A, B)){
return;
}
check_collision = beam_collide_ship;
break;
case COLLISION_OF(OBJ_BEAM, OBJ_ASTEROID):
if(beam_collide_early_out(A, B)){
return;
}
check_collision = beam_collide_asteroid;
break;
case COLLISION_OF(OBJ_BEAM, OBJ_DEBRIS):
if(beam_collide_early_out(A, B)){
return;
}
check_collision = beam_collide_debris;
break;
case COLLISION_OF(OBJ_BEAM, OBJ_WEAPON):
if(beam_collide_early_out(A, B)){
return;
}
check_collision = beam_collide_missile;
break;
case COLLISION_OF(OBJ_WEAPON, OBJ_WEAPON): {
weapon_info *awip, *bwip;
awip = &Weapon_info[Weapons[A->instance].weapon_info_index];
bwip = &Weapon_info[Weapons[B->instance].weapon_info_index];
if (awip->subtype != WP_LASER || bwip->subtype != WP_LASER) {
if (awip->subtype == WP_LASER) {
if ( bwip->wi_flags & WIF_BOMB ) {
check_collision = collide_weapon_weapon;
}
} else if (bwip->subtype == WP_LASER) {
if ( awip->wi_flags & WIF_BOMB ) {
check_collision = collide_weapon_weapon;
swapped=1;
}
} else {
if ( (awip->wi_flags&WIF_BOMB) || (bwip->wi_flags&WIF_BOMB) ) {
check_collision = collide_weapon_weapon;
}
}
}
/*
int atype, btype;
atype = Weapon_info[Weapons[A->instance].weapon_info_index].subtype;
btype = Weapon_info[Weapons[B->instance].weapon_info_index].subtype;
if ((atype == WP_LASER) && (btype == WP_MISSILE))
check_collision = collide_weapon_weapon;
else if ((atype == WP_MISSILE) && (btype == WP_LASER)) {
check_collision = collide_weapon_weapon;
swapped = 1;
} else if ((atype == WP_MISSILE) && (btype == WP_MISSILE))
check_collision = collide_weapon_weapon;
*/
break;
}
default:
return;
}
// Swap them if needed
if ( swapped ) {
object *tmp = A;
A = B;
B = tmp;
}
// if there are any more obj_pair checks
// we should then add function int maybe_not_add_obj_pair()
// MWA -- 4/1/98 -- I'd do it, but I don't want to bust anything, so I'm doing my stuff here instead :-)
//if ( MULTIPLAYER_CLIENT && !(Netgame.debug_flags & NETD_FLAG_CLIENT_NODAMAGE)){
// multiplayer clients will only do ship/ship collisions, and their own ship to boot
// if ( check_collision != collide_ship_ship ){
// return;
// }
// if ( (A != Player_obj) && (B != Player_obj) ){
// return;
// }
//}
// only check debris:weapon collisions for player
if (check_collision == collide_debris_weapon) {
// weapon is B
if ( !(Weapon_info[Weapons[B->instance].weapon_info_index].wi_flags & WIF_TURNS) ) {
// check for dumbfire weapon
// check if debris is behind laser
float vdot;
if (Weapon_info[Weapons[B->instance].weapon_info_index].subtype == WP_LASER) {
vector velocity_rel_weapon;
vm_vec_sub(&velocity_rel_weapon, &B->phys_info.vel, &A->phys_info.vel);
vdot = -vm_vec_dot(&velocity_rel_weapon, &B->orient.vec.fvec);
} else {
vdot = vm_vec_dot( &A->phys_info.vel, &B->phys_info.vel);
}
if ( vdot <= 0.0f ) {
// They're heading in opposite directions...
// check their positions
vector weapon2other;
vm_vec_sub( &weapon2other, &A->pos, &B->pos );
float pdot = vm_vec_dot( &B->orient.vec.fvec, &weapon2other );
if ( pdot <= -A->radius ) {
// The other object is behind the weapon by more than
// its radius, so it will never hit...
return;
}
}
// check dist vs. dist moved during weapon lifetime
vector delta_v;
vm_vec_sub(&delta_v, &B->phys_info.vel, &A->phys_info.vel);
if (vm_vec_dist_squared(&A->pos, &B->pos) > (vm_vec_mag_squared(&delta_v)*Weapons[B->instance].lifeleft*Weapons[B->instance].lifeleft)) {
return;
}
// for nonplayer ships, only create collision pair if close enough
if ( !(Objects[B->parent].flags & OF_PLAYER_SHIP) && (vm_vec_dist(&B->pos, &A->pos) < (4.0f*A->radius + 200.0f)) )
return;
}
}
// don't check same team laser:ship collisions on small ships if not player
if (check_collision == collide_ship_weapon) {
// weapon is B
if ( !(Objects[B->parent].flags & OF_PLAYER_SHIP)
&& (Ships[Objects[B->parent].instance].team == Ships[A->instance].team)
&& (Ship_info[Ships[A->instance].ship_info_index].flags & SIF_SMALL_SHIP)
&& (Weapon_info[Weapons[B->instance].weapon_info_index].subtype == WP_LASER) ) {
pairs_not_created++;
return;
}
}
if ( !check_collision ) return;
Pairs_created++;
// At this point, we have determined that collisions between
// these two should be checked, so add the pair to the
// collision pair list.
if ( pair_free_list.next == NULL ) {
nprintf(( "collision", "Out of object pairs!! Not all collisions will work!\n" ));
return;
}
// get a new obj_pair from the free list
obj_pair * new_pair = pair_free_list.next;
pair_free_list.next = new_pair->next;
if ( add_to_end ) {
obj_pair *last, *tmp;
last = tmp = pair_used_list.next;
while( tmp != NULL ) {
if ( tmp->next == NULL )
last = tmp;
tmp = tmp->next;
}
if ( last == NULL )
last = &pair_used_list;
last->next = new_pair;
Assert(new_pair != NULL);
new_pair->next = NULL;
}
else {
new_pair->next = pair_used_list.next;
pair_used_list.next = new_pair;
}
Num_pairs++;
/* if (Num_pairs > Num_pairs_hwm) {
Num_pairs_hwm = Num_pairs;
//nprintf(("AI", "Num_pairs high water mark = %i\n", Num_pairs_hwm));
}
*/
A->num_pairs++;
B->num_pairs++;
new_pair->a = A;
new_pair->b = B;
new_pair->check_collision = check_collision;
if ( check_time == -1 ){
new_pair->next_check_time = timestamp(0); // 0 means instantly time out
} else {
new_pair->next_check_time = check_time;
}
}
MONITOR(NumPairs);
MONITOR(NumPairsChecked);
void obj_check_all_collisions()
{
if ( !(Game_detail_flags & DETAIL_FLAG_COLLISION) ) return;
obj_pair *parent, *tmp;
// debug info
float avg_time_to_next_check = 0.0f;
parent = &pair_used_list;
tmp = parent->next;
Num_pairs_checked = 0;
while( tmp != NULL ) {
int removed = 0;
if ( (tmp->a != NULL) && (tmp->b != NULL) && timestamp_elapsed(tmp->next_check_time) ) {
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
}
}
if (!removed) {
parent = tmp;
tmp = tmp->next;
// 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;
}
}
}
MONITOR_INC(NumPairs,Num_pairs);
MONITOR_INC(NumPairsChecked,Num_pairs_checked);
// #define PAIR_STATS
#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;
// }
}
// See if two lines intersect by doing recursive subdivision.
// Bails out if larger distance traveled is less than sum of radii + 1.0f.
int collide_subdivide(vector *p0, vector *p1, float prad, vector *q0, vector *q1, float qrad)
{
float a_dist, b_dist, ab_dist;
a_dist = vm_vec_dist(p0, p1);
b_dist = vm_vec_dist(q0, q1);
ab_dist = vm_vec_dist(p1, q1);
// See if their spheres intersect
if (ab_dist < a_dist + b_dist + prad + qrad) {
if (ab_dist < prad + qrad)
return 1;
else if (vm_vec_dist(p0, q0) < prad + qrad)
return 1;
else if (max(a_dist, b_dist) < prad + qrad + 1.0f)
return 0;
else {
int r1, r2 = 0;
vector pa, qa;
vm_vec_avg(&pa, p0, p1);
vm_vec_avg(&qa, q0, q1);
r1 = collide_subdivide(p0, &pa, prad, q0, &qa, qrad);
if (!r1)
r2 = collide_subdivide(&pa, p1, prad, &qa, q1, qrad);
return r1 | r2;
}
} else
return 0;
}
// Return true if object A is expected to collide with object B within time duration
// For purposes of this check, the first object moves from current location to predicted
// location. The second object is assumed to be where it will be at time duration, NOT
// where it currently is.
// radius_scale is used to control the precision of the check.
// If 0.0, then use polygon models to perform check, slow and accurate
// If !0.0, then use as a scale on the radius of the objects. 1.0 is Descent style
// collisions. Larger values can be used to be sloppy about the collisions which
// is useful if a moving object wants to prevent a collision.
int objects_will_collide(object *A, object *B, float duration, float radius_scale)
{
object A_future;
vector hitpos;
A_future = *A;
vm_vec_scale_add2(&A_future.pos, &A->phys_info.vel, duration);
if (radius_scale == 0.0f) {
return ship_check_collision_fast(B, &A_future, &hitpos );
} else {
float size_A, size_B, dist, r;
vector nearest_point;
size_A = A->radius * radius_scale;
size_B = B->radius * radius_scale;
// If A is moving, check along vector.
if (A->phys_info.speed != 0.0f) {
r = find_nearest_point_on_line(&nearest_point, &A->pos, &A_future.pos, &B->pos);
if (r < 0) {
nearest_point = A->pos;
} else if (r > 1) {
nearest_point = A_future.pos;
}
dist = vm_vec_dist_quick(&B->pos, &nearest_point);
return (dist < size_A + size_B);
} else {
return vm_vec_dist_quick(&B->pos, &A->pos) < size_A + size_B;
}
}
}
// Return true if the vector from *start_pos to *end_pos is within objp->radius*radius_scale of *objp
int vector_object_collision(vector *start_pos, vector *end_pos, object *objp, float radius_scale)
{
float dist, r;
vector nearest_point;
r = find_nearest_point_on_line(&nearest_point, start_pos, end_pos, &objp->pos);
if ((r >= 0.0f) && (r <= 1.0f)) {
dist = vm_vec_dist_quick(&objp->pos, &nearest_point);
return (dist < objp->radius * radius_scale);
} else
return 0;
}
// Returns TRUE if the weapon will never hit the other object.
// If it can it predicts how long until these two objects need
// to be checked and fills the time in in current_pair.
int weapon_will_never_hit( object *weapon, object *other, obj_pair * current_pair )
{
Assert( weapon->type == OBJ_WEAPON );
// mprintf(( "Frame: %d, Weapon=%d, Other=%d, pair=$%08x\n", G3_frame_count, OBJ_INDEX(weapon), OBJ_INDEX(other), current_pair ));
// Do some checks for weapons that don't turn
if ( !(Weapon_info[Weapons[weapon->instance].weapon_info_index].wi_flags & WIF_TURNS) ) {
// This first check is to see if a weapon is behind an object, and they
// are heading in opposite directions. If so, we don't need to ever check
// them again. This is only valid for weapons that don't turn.
float vdot;
if (Weapon_info[Weapons[weapon->instance].weapon_info_index].subtype == WP_LASER) {
vector velocity_rel_weapon;
vm_vec_sub(&velocity_rel_weapon, &weapon->phys_info.vel, &other->phys_info.vel);
vdot = -vm_vec_dot(&velocity_rel_weapon, &weapon->orient.vec.fvec);
} else {
vdot = vm_vec_dot( &other->phys_info.vel, &weapon->phys_info.vel);
}
if ( vdot <= 0.0f ) {
// They're heading in opposite directions...
// check their positions
vector weapon2other;
vm_vec_sub( &weapon2other, &other->pos, &weapon->pos );
float pdot = vm_vec_dot( &weapon->orient.vec.fvec, &weapon2other );
if ( pdot <= -other->radius ) {
// The other object is behind the weapon by more than
// its radius, so it will never hit...
return 1;
}
}
// FUTURE ENHANCEMENT IDEAS
// Given a laser does it hit a slow or not moving object
// in its life or the next n seconds? We'd actually need to check the
// model for this.
}
// This check doesn't care about orient, only looks at the maximum speed
// of the two objects, so it knows that in the next n seconds, they can't
// go further than some distance, so don't bother checking collisions for
// that time. This is very rough, but is so general that it works for
// everything and immidiately gets rid of a lot of cases.
if ( current_pair ) {
// Find the time it will take before these get within each others distances.
// tmp->next_check_time = timestamp(500);
//vector max_vel; //maximum foward velocity in x,y,z
float max_vel_weapon, max_vel_other;
max_vel_weapon = weapon->phys_info.max_vel.xyz.z;
max_vel_other = other->phys_info.max_vel.xyz.z;
if (max_vel_other < 10.0f) {
if ( vm_vec_mag_squared( &other->phys_info.vel ) > 100 ) {
// bump up velocity from collision
max_vel_other = vm_vec_mag( &other->phys_info.vel ) + 10.0f;
} else {
max_vel_other = 10.0f; // object may move from collision
}
}
// check weapon that does not turn against sphere expanding at ship maxvel
// compare (weeapon) ray with expanding sphere (ship) to find earliest possible collision time
// look for two time solutions to Xw = Xs, where Xw = Xw0 + Vwt*t Xs = Xs + Vs*(t+dt), where Vs*dt = radius of ship
// Since direction of Vs is unknown, solve for (Vs*t) and find norm of both sides
if ( !(Weapon_info[Weapons[weapon->instance].weapon_info_index].wi_flags & WIF_TURNS) ) {
vector delta_x, laser_vel;
float a,b,c, delta_x_dot_vl, delta_t;
float root1, root2, root, earliest_time;
vm_vec_sub( &delta_x, &weapon->pos, &other->pos );
vm_vec_copy_scale( &laser_vel, &weapon->orient.vec.fvec, max_vel_weapon );
delta_t = (other->radius + 10.0f) / max_vel_other; // time to get from center to radius of other obj
delta_x_dot_vl = vm_vec_dotprod( &delta_x, &laser_vel );
a = max_vel_weapon*max_vel_weapon - max_vel_other*max_vel_other;
b = 2.0f * (delta_x_dot_vl - max_vel_other*max_vel_other*delta_t);
c = vm_vec_mag_squared( &delta_x ) - max_vel_other*max_vel_other*delta_t*delta_t;
float discriminant = b*b - 4.0f*a*c;
if ( discriminant < 0) {
// never hit
return 1;
} else {
root = fl_sqrt( discriminant );
root1 = (-b + root) / (2.0f * a) * 1000.0f; // get time in ms
root2 = (-b - root) / (2.0f * a) * 1000.0f; // get time in ms
}
// find earliest positive time
if ( root1 > root2 ) {
float temp = root1;
root1 = root2;
root2 = temp;
}
if (root1 > 0) {
earliest_time = root1;
} else if (root2 > 0) {
// root1 < 0 and root2 > 0, so we're inside sphere and next check should be next frame
current_pair->next_check_time = timestamp(0); // check next time
return 0;
} else {
// both times negative, so never collides
return 1;
}
// check if possible collision occurs after weapon expires
if ( earliest_time > 1000*Weapons[weapon->instance].lifeleft )
return 1;
// Allow one worst case frametime to elapse (~5 fps)
earliest_time -= 200.0f;
if (earliest_time > 100) {
current_pair->next_check_time = timestamp( fl2i(earliest_time) );
return 0;
} else {
current_pair->next_check_time = timestamp(0); // check next time
return 0;
}
} else {
float dist, max_vel, time;
max_vel = max_vel_weapon + max_vel_other;
// suggest that fudge factor for other radius be changed to other_radius + const (~10)
dist = vm_vec_dist( &other->pos, &weapon->pos ) - (other->radius + 10.0f);
if ( dist > 0.0f ) {
time = (dist*1000.0f) / max_vel;
int time_ms = fl2i(time);
// check if possible collision occurs after weapon expires
if ( time_ms > 1000*Weapons[weapon->instance].lifeleft )
return 1;
time_ms -= 200; // Allow at least one worst case frametime to elapse (~5 fps)
if ( time_ms > 100 ) { // If it takes longer than 1/10th of a second, then delay it
current_pair->next_check_time = timestamp(time_ms);
//mprintf(( "Delaying %d ms\n", time_ms ));
return 0;
}
}
current_pair->next_check_time = timestamp(0); // check next time
}
}
return 0;
}
// Return true if vector from *curpos to *goalpos intersects with object *goalobjp
// Else, return false.
// radius is radius of object moving from curpos to goalpos.
int pp_collide(vector *curpos, vector *goalpos, object *goalobjp, float radius)
{
mc_info mc;
Assert(goalobjp->type == OBJ_SHIP);
ship_model_start(goalobjp);
mc.model_num = Ships[goalobjp->instance].modelnum; // Fill in the model to check
mc.orient = &goalobjp->orient; // The object's orient
mc.pos = &goalobjp->pos; // The object's position
mc.p0 = curpos; // Point 1 of ray to check
mc.p1 = goalpos; // Point 2 of ray to check
mc.flags = MC_CHECK_MODEL | MC_CHECK_SPHERELINE;
mc.radius = radius;
model_collide(&mc);
ship_model_stop(goalobjp);
return mc.num_hits;
}
// Setup and call pp_collide for collide_predict_large_ship
// Returns true if objp will collide with objp2 before it reaches goal_pos.
int cpls_aux(vector *goal_pos, object *objp2, object *objp)
{
float radius;
radius = objp->radius;
if (1.5f * radius < 70.0f)
radius *= 1.5f;
else
radius = 70.0f;
if (pp_collide(&objp->pos, goal_pos, objp2, radius))
return 1;
else
return 0;
}
// Return true if objp will collide with some large object.
// Don't check for an object this ship is docked to.
int collide_predict_large_ship(object *objp, float distance)
{
object *objp2;
vector cur_pos, goal_pos;
object *dock_objp;
ship_info *sip;
sip = &Ship_info[Ships[objp->instance].ship_info_index];
dock_objp = NULL;
if (objp->type == OBJ_SHIP) {
ai_info *aip = &Ai_info[Ships[objp->instance].ai_index];
if (aip->dock_objnum != -1)
dock_objp = &Objects[aip->dock_objnum];
}
cur_pos = objp->pos;
vm_vec_scale_add(&goal_pos, &cur_pos, &objp->orient.vec.fvec, distance);
for ( objp2 = GET_FIRST(&obj_used_list); objp2 != END_OF_LIST(&obj_used_list); objp2 = GET_NEXT(objp2) ) {
if ((objp != objp2) && (objp2->type == OBJ_SHIP)) {
if (Ship_info[Ships[objp2->instance].ship_info_index].flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP)) {
if (objp2 == dock_objp)
continue;
if (cpls_aux(&goal_pos, objp2, objp))
return 1;
}
} else if (!(sip->flags & (SIF_BIG_SHIP | SIF_HUGE_SHIP)) && (objp2->type == OBJ_ASTEROID)) {
if (vm_vec_dist_quick(&objp2->pos, &objp->pos) < (distance + objp2->radius)*2.5f) {
vector pos, delvec;
int count;
float d1;
d1 = 2.5f * distance + objp2->radius;
count = (int) (d1/(objp2->radius + objp->radius)); // Scale up distance, else looks like there would be a collision.
pos = cur_pos;
vm_vec_normalized_dir(&delvec, &goal_pos, &cur_pos);
vm_vec_scale(&delvec, d1/count);
for (; count>0; count--) {
if (vm_vec_dist_quick(&pos, &objp2->pos) < objp->radius + objp2->radius)
return 1;
vm_vec_add2(&pos, &delvec);
}
}
}
}
return 0;
}
// function to iterate through all object collision pairs looking for weapons
// which could be deleted since they are not going to hit anything. Passed into this
// function is a 'time' parameter used as watermark for which weapons to check.
#define CRW_NO_OBJECT -1
#define CRW_NO_PAIR 0
#define CRW_IN_PAIR 1
#define CRW_CAN_DELETE 2
#define CRW_MAX_TO_DELETE 4
char crw_status[MAX_WEAPONS];
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;
}
int collide_remove_weapons( )
{
obj_pair *opp;
int i, num_deleted, oldest_index, j, loop_count;
float oldest_time;
// setup remove_weapon array. assume we can remove it.
for (i = 0; i < MAX_WEAPONS; i++ ) {
if ( Weapons[i].objnum == -1 )
crw_status[i] = CRW_NO_OBJECT;
else
crw_status[i] = CRW_NO_PAIR;
}
// first pass is to see if any of the weapons don't have collision pairs.
opp = &pair_used_list;
opp = opp->next;
while( opp != NULL ) {
// for each collide pair, if the two objects can still collide, then set the remove_weapon
// parameter for the weapon to 0. need to check both parameters
if ( opp->a->type == OBJ_WEAPON )
crw_check_weapon( opp->a->instance, opp->next_check_time );
if ( opp->b->type == OBJ_WEAPON )
crw_check_weapon( opp->b->instance, opp->next_check_time );
opp = opp->next;
}
// for each weapon which could be removed, delete the object
num_deleted = 0;
for ( i = 0; i < MAX_WEAPONS; i++ ) {
if ( crw_status[i] == CRW_CAN_DELETE ) {
Assert( Weapons[i].objnum != -1 );
obj_delete( Weapons[i].objnum );
num_deleted++;
}
}
if ( num_deleted )
return num_deleted;
// if we didn't remove any weapons, try to the N oldest weapons. first checking for pairs, then
// checking for oldest weapons in general. We will go through the loop a max of 2 times. first time
// through, we check oldest weapons with pairs, next time through, for oldest weapons.
loop_count = 0;
do {
for ( j = 0; j < CRW_MAX_TO_DELETE; j++ ) {
oldest_time = 1000.0f;
oldest_index = -1;
for (i = 0; i < MAX_WEAPONS; i++ ) {
if ( Weapons[i].objnum == -1 ) // shouldn't happen, but this is the safe thing to do.
continue;
if ( ((loop_count || crw_status[i] == CRW_NO_PAIR)) && (Weapons[i].lifeleft < oldest_time) ) {
oldest_time = Weapons[i].lifeleft;
oldest_index = i;
}
}
if ( oldest_index != -1 ) {
obj_delete(Weapons[oldest_index].objnum);
num_deleted++;
}
}
// if we deleted some weapons, then we can break
if ( num_deleted )
break;
loop_count++;
} while ( loop_count < 2);
return num_deleted;
}
void set_hit_struct_info(collision_info_struct *hit, mc_info *mc, int submodel_rot_hit)
{
hit->edge_hit = mc->edge_hit;
hit->hit_pos = mc->hit_point_world;
hit->hit_time = mc->hit_dist;
hit->submodel_num = mc->hit_submodel;
hit->submodel_rot_hit = submodel_rot_hit;
}