mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 18:31:26 -10:00
* Fix scripting copy-paste bug The iterators for secondary banks for lua access had a copy-paste bug from primaries, so we should fix that. The early == -1 break masks it whenever a ship uses 3 or fewer secondary banks, which is probably why it went unnoticed. Also add some additional low-hanging fruit optimizations. * radar distance and other cleanup * clang
1315 lines
42 KiB
C++
1315 lines
42 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 "cmdline/cmdline.h"
|
|
#include "globalincs/linklist.h"
|
|
#include "io/timer.h"
|
|
#include "object/objcollide.h"
|
|
#include "object/object.h"
|
|
#include "object/objectdock.h"
|
|
#include "ship/ship.h"
|
|
#include "tracing/tracing.h"
|
|
#include "weapon/beam.h"
|
|
#include "weapon/weapon.h"
|
|
#include "tracing/Monitor.h"
|
|
#include "utils/threading.h"
|
|
|
|
#include <limits>
|
|
|
|
|
|
// the next 2 variables are used for pair statistics
|
|
// also in weapon.cpp there is Weapons_created.
|
|
int Num_pairs = 0;
|
|
int Num_pairs_checked = 0;
|
|
|
|
SCP_vector<int> Collision_sort_list;
|
|
|
|
static_assert(1 << collision_cache_bitshift > MAX_OBJECTS, "Collision pair caching currently relies on the highest possible objnum being less than 2^collision_cache_bitshift.");
|
|
|
|
class collider_pair
|
|
{
|
|
public:
|
|
object *a;
|
|
object *b;
|
|
int signature_a;
|
|
int signature_b;
|
|
int next_check_time;
|
|
bool initialized;
|
|
|
|
// we need to define a constructor because the hash map can
|
|
// implicitly insert an object when we use the [] operator
|
|
collider_pair()
|
|
: a(nullptr), b(nullptr), signature_a(-1), signature_b(-1), next_check_time(-1), initialized(false)
|
|
{}
|
|
};
|
|
|
|
static SCP_set<object*> Collision_cache_stale_objects;
|
|
static SCP_unordered_map<uint, collider_pair> Collision_cached_pairs;
|
|
|
|
class checkobject;
|
|
extern checkobject CheckObjects[MAX_OBJECTS];
|
|
|
|
// 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->flags[Object::Object_Flags::Collides_with_parent] || B->flags[Object::Object_Flags::Collides_with_parent])
|
|
return 0;
|
|
|
|
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;
|
|
}
|
|
|
|
bool reject_due_collision_groups(object *A, object *B)
|
|
{
|
|
if (A->collision_group_id == 0 || B->collision_group_id == 0)
|
|
return false;
|
|
|
|
return (A->collision_group_id & B->collision_group_id);
|
|
}
|
|
|
|
MONITOR(NumPairs)
|
|
MONITOR(NumPairsChecked)
|
|
|
|
// 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(vec3d *p0, vec3d *p1, float prad, vec3d *q0, vec3d *q1, float qrad)
|
|
{
|
|
float a_dist = vm_vec_dist(p0, p1);
|
|
float b_dist = vm_vec_dist(q0, q1);
|
|
float 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;
|
|
vec3d 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)
|
|
{
|
|
vec3d hitpos;
|
|
int ret;
|
|
|
|
|
|
vec3d prev_pos = A->pos;
|
|
vm_vec_scale_add2(&A->pos, &A->phys_info.vel, duration);
|
|
|
|
if (radius_scale == 0.0f) {
|
|
ret = ship_check_collision_fast(B, A, &hitpos);
|
|
} else {
|
|
vec3d nearest_point;
|
|
|
|
const float size_A = A->radius * radius_scale;
|
|
const float size_B = B->radius * radius_scale;
|
|
|
|
// If A is moving, check along vector.
|
|
if (A->phys_info.speed != 0.0f) {
|
|
const float r = find_nearest_point_on_line(&nearest_point, &prev_pos, &A->pos, &B->pos);
|
|
if (r < 0) {
|
|
nearest_point = prev_pos;
|
|
} else if (r > 1) {
|
|
nearest_point = A->pos;
|
|
}
|
|
const float dist = vm_vec_dist_quick(&B->pos, &nearest_point);
|
|
ret = (dist < size_A + size_B);
|
|
} else {
|
|
ret = vm_vec_dist_quick(&B->pos, &prev_pos) < size_A + size_B;
|
|
}
|
|
}
|
|
|
|
// Reset the position to the previous value
|
|
A->pos = prev_pos;
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Return true if the vector from *start_pos to *end_pos is within objp->radius*radius_scale of *objp
|
|
int vector_object_collision(vec3d *start_pos, vec3d *end_pos, object *objp, float radius_scale)
|
|
{
|
|
vec3d nearest_point;
|
|
|
|
float r = find_nearest_point_on_line(&nearest_point, start_pos, end_pos, &objp->pos);
|
|
if ((r >= 0.0f) && (r <= 1.0f)) {
|
|
float 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 *obj_weapon, object *other, obj_pair * current_pair )
|
|
{
|
|
|
|
Assert( obj_weapon->type == OBJ_WEAPON );
|
|
weapon *wp = &Weapons[obj_weapon->instance];
|
|
weapon_info *wip = &Weapon_info[wp->weapon_info_index];
|
|
|
|
// Do some checks for weapons that don't turn
|
|
// gotta treat anything being affected by gravity as turning too
|
|
if ( !(wip->wi_flags[Weapon::Info_Flags::Turns] && (IS_VEC_NULL(&The_mission.gravity) || obj_weapon->phys_info.gravity_const == 0.0))) {
|
|
|
|
// 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 (wip->subtype == WP_LASER) {
|
|
vec3d velocity_rel_weapon;
|
|
vm_vec_sub(&velocity_rel_weapon, &obj_weapon->phys_info.vel, &other->phys_info.vel);
|
|
vdot = -vm_vec_dot(&velocity_rel_weapon, &obj_weapon->orient.vec.fvec);
|
|
} else {
|
|
vdot = vm_vec_dot( &other->phys_info.vel, &obj_weapon->phys_info.vel);
|
|
}
|
|
if ( vdot <= 0.0f ) {
|
|
// They're heading in opposite directions...
|
|
// check their positions
|
|
vec3d weapon2other;
|
|
vm_vec_sub( &weapon2other, &other->pos, &obj_weapon->pos );
|
|
float pdot = vm_vec_dot( &obj_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;
|
|
|
|
//SUSHI: Fix bug where additive weapon velocity screws up collisions
|
|
//If the PF_CONST_VEL flag is set, we can safely assume it doesn't change speed.
|
|
if (obj_weapon->phys_info.flags & PF_CONST_VEL)
|
|
max_vel_weapon = obj_weapon->phys_info.speed;
|
|
else if (wp->lssm_stage == 5)
|
|
max_vel_weapon = wip->lssm_stage5_vel;
|
|
else if (IS_VEC_NULL(&The_mission.gravity) || obj_weapon->phys_info.gravity_const == 0.0f)
|
|
max_vel_weapon = wp->weapon_max_vel;
|
|
else
|
|
max_vel_weapon = obj_weapon->phys_info.speed + wp->lifeleft * vm_vec_mag(&The_mission.gravity);
|
|
|
|
float 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 (weapon) 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 ( !(wip->wi_flags[Weapon::Info_Flags::Turns]) && (obj_weapon->phys_info.flags & PF_CONST_VEL) ) {
|
|
vec3d delta_x;
|
|
float a,b,c, delta_x_dot_vl, delta_t;
|
|
float root1, root2, root, earliest_time;
|
|
|
|
vm_vec_sub( &delta_x, &obj_weapon->pos, &other->pos );
|
|
|
|
if (max_vel_weapon == max_vel_other) {
|
|
// this will give us NAN using the below formula, so check every frame
|
|
current_pair->next_check_time = timestamp(0);
|
|
return 0;
|
|
}
|
|
|
|
// 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_dot( &delta_x, &obj_weapon->phys_info.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) {
|
|
// neither entity passes the other
|
|
if (c < 0) {
|
|
// ship outpaces weapon
|
|
current_pair->next_check_time = timestamp(0); // check next time
|
|
return 0;
|
|
} else {
|
|
// weapon outpaces ship; will 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
|
|
}
|
|
|
|
// standard algorithm
|
|
if (max_vel_weapon > max_vel_other) {
|
|
// 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;
|
|
}
|
|
}
|
|
// need to modify it for weapons that are slower than ships
|
|
else {
|
|
if (root2 > 0) {
|
|
earliest_time = root2;
|
|
} else {
|
|
current_pair->next_check_time = timestamp(0);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// check if possible collision occurs after weapon expires
|
|
if ( earliest_time > 1000*wp->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 {
|
|
const float max_vel = max_vel_weapon + max_vel_other;
|
|
|
|
// suggest that fudge factor for other radius be changed to other_radius + const (~10)
|
|
const float dist = vm_vec_dist( &other->pos, &obj_weapon->pos ) - (other->radius + 10.0f);
|
|
if ( dist > 0.0f ) {
|
|
const float time = (dist*1000.0f) / max_vel;
|
|
int time_ms = fl2i(time);
|
|
|
|
// check if possible collision occurs after weapon expires
|
|
if ( time_ms > 1000*wp->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(vec3d *curpos, vec3d *goalpos, object *goalobjp, float radius)
|
|
{
|
|
Assert(goalobjp->type == OBJ_SHIP);
|
|
|
|
mc_info mc;
|
|
mc.model_instance_num = Ships[goalobjp->instance].model_instance_num;
|
|
mc.model_num = Ship_info[Ships[goalobjp->instance].ship_info_index].model_num; // 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);
|
|
|
|
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(vec3d *goal_pos, object *objp2, object *objp)
|
|
{
|
|
float 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;
|
|
vec3d goal_pos;
|
|
ship_info* sip = &Ship_info[Ships[objp->instance].ship_info_index];
|
|
|
|
vec3d 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 (objp2->flags[Object::Object_Flags::Should_be_dead])
|
|
continue;
|
|
|
|
if ((objp != objp2) && (objp2->type == OBJ_SHIP)) {
|
|
if (Ship_info[Ships[objp2->instance].ship_info_index].is_big_or_huge()) {
|
|
if (dock_check_find_docked_object(objp, objp2))
|
|
continue;
|
|
|
|
if (cpls_aux(&goal_pos, objp2, objp))
|
|
return 1;
|
|
}
|
|
} else if (!(sip->is_big_or_huge()) && (objp2->type == OBJ_ASTEROID)) {
|
|
if (vm_vec_dist_quick(&objp2->pos, &objp->pos) < (distance + objp2->radius)*2.5f) {
|
|
vec3d delvec;
|
|
|
|
const float d1 = 2.5f * distance + objp2->radius;
|
|
auto count = (int) (d1/(objp2->radius + objp->radius)); // Scale up distance, else looks like there would be a collision.
|
|
vec3d 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 )
|
|
{
|
|
weapon *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;
|
|
const float 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( )
|
|
{
|
|
// setup remove_weapon array. assume we can remove it.
|
|
for (int 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.
|
|
for (auto& pair : Collision_cached_pairs) {
|
|
collider_pair* pair_obj = &pair.second;
|
|
|
|
if (!pair_obj->initialized) {
|
|
continue;
|
|
}
|
|
|
|
if (pair_obj->a->type == OBJ_WEAPON && pair_obj->signature_a == pair_obj->a->signature) {
|
|
crw_check_weapon(pair_obj->a->instance, pair_obj->next_check_time);
|
|
|
|
if (crw_status[pair_obj->a->instance] == CRW_CAN_DELETE) {
|
|
pair_obj->initialized = false;
|
|
}
|
|
}
|
|
|
|
if (pair_obj->b->type == OBJ_WEAPON && pair_obj->signature_b == pair_obj->b->signature) {
|
|
crw_check_weapon(pair_obj->b->instance, pair_obj->next_check_time);
|
|
|
|
if (crw_status[pair_obj->b->instance] == CRW_CAN_DELETE) {
|
|
pair_obj->initialized = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// for each weapon which could be removed, delete the object
|
|
int num_deleted = 0;
|
|
for (int 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++;
|
|
}
|
|
}
|
|
|
|
// stop here because any other weapon could currently be involved in a collision and can cause crashes
|
|
return num_deleted;
|
|
|
|
}
|
|
|
|
void set_hit_struct_info(collision_info_struct *hit, mc_info *mc, bool submodel_move_hit)
|
|
{
|
|
hit->edge_hit = mc->edge_hit;
|
|
hit->hit_pos = mc->hit_point_world;
|
|
hit->hit_time = mc->hit_dist;
|
|
hit->heavy_model_num = mc->model_num;
|
|
hit->heavy_submodel_num = mc->hit_submodel;
|
|
|
|
hit->submodel_move_hit = submodel_move_hit;
|
|
}
|
|
|
|
//Previously, this was done with
|
|
//memset(&ship_ship_hit_info, -1, sizeof(collision_info_struct));
|
|
//All those -1s are to replicate that logic
|
|
void init_collision_info_struct(collision_info_struct *cis)
|
|
{
|
|
memset(cis, -1, sizeof(collision_info_struct));
|
|
cis->is_landing = false;
|
|
}
|
|
|
|
void obj_add_collider(int obj_index)
|
|
{
|
|
object *objp = &Objects[obj_index];
|
|
|
|
#ifdef OBJECT_CHECK
|
|
CheckObjects[obj_index].type = objp->type;
|
|
CheckObjects[obj_index].signature = objp->signature;
|
|
CheckObjects[obj_index].flags = objp->flags - Object::Object_Flags::Not_in_coll;
|
|
CheckObjects[obj_index].parent_sig = objp->parent_sig;
|
|
#endif
|
|
|
|
if(!(objp->flags[Object::Object_Flags::Not_in_coll])){
|
|
return;
|
|
}
|
|
|
|
Collision_sort_list.push_back(obj_index);
|
|
|
|
objp->flags.remove(Object::Object_Flags::Not_in_coll);
|
|
}
|
|
|
|
void obj_remove_collider(int obj_index)
|
|
{
|
|
#ifdef OBJECT_CHECK
|
|
CheckObjects[obj_index].flags.set(Object::Object_Flags::Not_in_coll);
|
|
#endif
|
|
|
|
for (size_t i = 0; i < Collision_sort_list.size(); ++i ) {
|
|
if ( Collision_sort_list[i] == obj_index ) {
|
|
Collision_sort_list[i] = Collision_sort_list.back();
|
|
Collision_sort_list.pop_back();
|
|
break;
|
|
}
|
|
}
|
|
|
|
Objects[obj_index].flags.set(Object::Object_Flags::Not_in_coll);
|
|
}
|
|
|
|
void obj_reset_colliders()
|
|
{
|
|
Collision_sort_list.clear();
|
|
Collision_cached_pairs.clear();
|
|
}
|
|
|
|
void obj_collide_retime_stale_pairs()
|
|
{
|
|
TRACE_SCOPE(tracing::RetimeCollisionCache);
|
|
|
|
auto it = Collision_cached_pairs.begin();
|
|
while (it != Collision_cached_pairs.end()) {
|
|
auto &pair = it->second;
|
|
if (pair.signature_a != pair.a->signature || pair.signature_b != pair.b->signature) {
|
|
it = Collision_cached_pairs.erase(it);
|
|
} else {
|
|
if (pair.a->flags[Object::Object_Flags::Collision_cache_stale] || pair.b->flags[Object::Object_Flags::Collision_cache_stale])
|
|
pair.next_check_time = timestamp(0);
|
|
it++;
|
|
}
|
|
}
|
|
|
|
for (auto objp : Collision_cache_stale_objects)
|
|
objp->flags.remove(Object::Object_Flags::Collision_cache_stale);
|
|
Collision_cache_stale_objects.clear();
|
|
}
|
|
|
|
void obj_collide_obj_cache_stale(object* objp)
|
|
{
|
|
objp->flags.set(Object::Object_Flags::Collision_cache_stale);
|
|
Collision_cache_stale_objects.insert(objp);
|
|
}
|
|
|
|
//local helper functions only used in objcollide.cpp
|
|
namespace
|
|
{
|
|
|
|
float obj_get_collider_endpoint(int obj_num, int axis, bool min)
|
|
{
|
|
if ( Objects[obj_num].type == OBJ_BEAM ) {
|
|
beam *b = &Beams[Objects[obj_num].instance];
|
|
|
|
// use the last start and last shot as endpoints
|
|
float min_end, max_end;
|
|
if ( b->last_start.a1d[axis] > b->last_shot.a1d[axis] ) {
|
|
min_end = b->last_shot.a1d[axis];
|
|
max_end = b->last_start.a1d[axis];
|
|
} else {
|
|
min_end = b->last_start.a1d[axis];
|
|
max_end = b->last_shot.a1d[axis];
|
|
}
|
|
|
|
if ( min ) {
|
|
return min_end;
|
|
} else {
|
|
return max_end;
|
|
}
|
|
} else if ( Objects[obj_num].type == OBJ_WEAPON ) {
|
|
float min_end, max_end;
|
|
|
|
if ( Objects[obj_num].pos.a1d[axis] > Objects[obj_num].last_pos.a1d[axis] ) {
|
|
min_end = Objects[obj_num].last_pos.a1d[axis];
|
|
max_end = Objects[obj_num].pos.a1d[axis];
|
|
} else {
|
|
min_end = Objects[obj_num].pos.a1d[axis];
|
|
max_end = Objects[obj_num].last_pos.a1d[axis];
|
|
}
|
|
|
|
if ( min ) {
|
|
return min_end - Objects[obj_num].radius;
|
|
} else {
|
|
return max_end + Objects[obj_num].radius;
|
|
}
|
|
} else {
|
|
vec3d *pos = &Objects[obj_num].pos;
|
|
|
|
if ( min ) {
|
|
return pos->a1d[axis] - Objects[obj_num].radius;
|
|
} else {
|
|
return pos->a1d[axis] + Objects[obj_num].radius;
|
|
}
|
|
}
|
|
}
|
|
|
|
void obj_quicksort_colliders(SCP_vector<int> *list, int left, int right, int axis)
|
|
{
|
|
Assert( axis >= 0 );
|
|
Assert( axis <= 2 );
|
|
|
|
if ( right > left ) {
|
|
int pivot_index = left + (right - left) / 2;
|
|
|
|
float pivot_value = obj_get_collider_endpoint((*list)[pivot_index], axis, true);
|
|
|
|
// swap!
|
|
int temp = (*list)[pivot_index];
|
|
(*list)[pivot_index] = (*list)[right];
|
|
(*list)[right] = temp;
|
|
|
|
int store_index = left;
|
|
|
|
for (int i = left; i < right; ++i ) {
|
|
if ( obj_get_collider_endpoint((*list)[i], axis, true) <= pivot_value ) {
|
|
temp = (*list)[i];
|
|
(*list)[i] = (*list)[store_index];
|
|
(*list)[store_index] = temp;
|
|
store_index++;
|
|
}
|
|
}
|
|
|
|
temp = (*list)[right];
|
|
(*list)[right] = (*list)[store_index];
|
|
(*list)[store_index] = temp;
|
|
|
|
obj_quicksort_colliders(list, left, store_index - 1, axis);
|
|
obj_quicksort_colliders(list, store_index + 1, right, axis);
|
|
}
|
|
}
|
|
|
|
struct collision_thread_data {
|
|
struct collision_queue_item {
|
|
obj_pair objs;
|
|
uint ctype;
|
|
};
|
|
struct collision_queue_result {
|
|
obj_pair objs;
|
|
bool never_recheck;
|
|
std::any collision_data;
|
|
void (*process_collision)( obj_pair *pair, const std::any& collision_data );
|
|
};
|
|
|
|
std::atomic_size_t queue_length, result_length;
|
|
std::mutex queue_mutex, result_mutex;
|
|
std::unique_ptr<SCP_vector<collision_queue_item>> queue_load, queue_process;
|
|
std::unique_ptr<SCP_vector<collision_queue_result>> queue_results, queue_send;
|
|
|
|
collision_thread_data() :
|
|
queue_length(0),
|
|
result_length(0),
|
|
queue_load(std::make_unique<SCP_vector<collision_queue_item>>()),
|
|
queue_process(std::make_unique<SCP_vector<collision_queue_item>>()),
|
|
queue_results(std::make_unique<SCP_vector<collision_queue_result>>()),
|
|
queue_send(std::make_unique<SCP_vector<collision_queue_result>>()) {}
|
|
};
|
|
|
|
std::unique_ptr<collision_thread_data[]> collision_thread_data_buffer;
|
|
std::atomic_bool collision_processing_done = false;
|
|
|
|
void spin_up_mp_collision() {
|
|
collision_processing_done.store(false);
|
|
threading::spin_up_threaded_task(threading::WorkerThreadTask::COLLISION);
|
|
}
|
|
|
|
void spin_down_mp_collision() {
|
|
threading::spin_down_threaded_task();
|
|
collision_processing_done.store(true);
|
|
threading::spin_down_wait_complete();
|
|
}
|
|
|
|
void queue_mp_collision(uint ctype, const obj_pair& colliding) {
|
|
size_t min_queue_length = std::numeric_limits<size_t>::max();
|
|
size_t target_thread = 0;
|
|
for (size_t i = 0; i < threading::get_num_workers(); i++) {
|
|
size_t queue_length = collision_thread_data_buffer[i].queue_length.load(std::memory_order_acquire);
|
|
if (queue_length == 0) {
|
|
target_thread = i;
|
|
break;
|
|
}
|
|
else if (queue_length < min_queue_length) {
|
|
target_thread = i;
|
|
min_queue_length = queue_length;
|
|
}
|
|
}
|
|
{
|
|
auto& thread = collision_thread_data_buffer[target_thread];
|
|
std::scoped_lock lock(thread.queue_mutex);
|
|
thread.queue_load->emplace_back( collision_thread_data::collision_queue_item{colliding, ctype} );
|
|
thread.queue_length.fetch_add(1, std::memory_order_release);
|
|
}
|
|
}
|
|
|
|
void post_process_threaded_collisions() {
|
|
SCP_map<size_t, size_t> workerThreads;
|
|
for (size_t i = 0; i < threading::get_num_workers(); i++)
|
|
workerThreads.emplace(i, 0);
|
|
|
|
while (!workerThreads.empty()) {
|
|
for(auto& [i, processed] : workerThreads) {
|
|
auto& thread = collision_thread_data_buffer[i];
|
|
|
|
size_t queue_length = thread.queue_length.load(std::memory_order_acquire);
|
|
size_t result_length = thread.result_length.load(std::memory_order_acquire);
|
|
|
|
if (result_length > processed) {
|
|
{
|
|
std::scoped_lock lock(thread.result_mutex);
|
|
thread.queue_results.swap(thread.queue_send);
|
|
}
|
|
for (auto& collision : *thread.queue_send) {
|
|
uint key = (OBJ_INDEX(collision.objs.a) << collision_cache_bitshift) + OBJ_INDEX(collision.objs.b);
|
|
collider_pair *collision_info = &Collision_cached_pairs[key];
|
|
|
|
if (collision.collision_data.has_value())
|
|
collision.process_collision(&collision.objs, collision.collision_data);
|
|
|
|
if (collision.never_recheck) {
|
|
collision_info->next_check_time = -1;
|
|
} else {
|
|
collision_info->next_check_time = collision.objs.next_check_time;
|
|
}
|
|
}
|
|
processed += thread.queue_send->size();
|
|
thread.queue_send->clear();
|
|
}
|
|
else if (queue_length == 0) {
|
|
thread.queue_results->clear();
|
|
workerThreads.erase(i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
spin_down_mp_collision();
|
|
}
|
|
|
|
void obj_collide_pair(object *A, object *B)
|
|
{
|
|
TRACE_SCOPE(tracing::CollidePair);
|
|
|
|
int (*check_collision)( obj_pair *pair ) = nullptr;
|
|
int swapped = 0;
|
|
bool support_mp = false;
|
|
|
|
if ( A==B ) return; // Don't check collisions with yourself
|
|
|
|
if ( !(A->flags[Object::Object_Flags::Collides]) ) return; // This object doesn't collide with anything
|
|
if ( !(B->flags[Object::Object_Flags::Collides]) ) return; // This object doesn't collide with anything
|
|
|
|
// Two immobile objects will never collide with each other
|
|
if ( (A->flags[Object::Object_Flags::Immobile] || (A->flags[Object::Object_Flags::Dont_change_position] && A->flags[Object::Object_Flags::Dont_change_orientation]))
|
|
&& (B->flags[Object::Object_Flags::Immobile] || (B->flags[Object::Object_Flags::Dont_change_position] && B->flags[Object::Object_Flags::Dont_change_orientation])) )
|
|
return;
|
|
|
|
// Make sure you're not checking a parent with it's kid or vicy-versy
|
|
if ( reject_obj_pair_on_parent(A,B) ) {
|
|
return;
|
|
}
|
|
|
|
Assert( A->type < 127 );
|
|
Assert( B->type < 127 );
|
|
|
|
uint ctype = COLLISION_OF(A->type,B->type);
|
|
switch( ctype ) {
|
|
case COLLISION_OF(OBJ_WEAPON,OBJ_SHIP):
|
|
swapped = 1;
|
|
check_collision = collide_ship_weapon;
|
|
support_mp = true;
|
|
break;
|
|
case COLLISION_OF(OBJ_SHIP, OBJ_WEAPON):
|
|
check_collision = collide_ship_weapon;
|
|
support_mp = true;
|
|
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_DEBRIS, OBJ_PROP):
|
|
check_collision = collide_debris_prop;
|
|
break;
|
|
case COLLISION_OF(OBJ_PROP, OBJ_DEBRIS):
|
|
check_collision = collide_debris_prop;
|
|
swapped = 1;
|
|
break;
|
|
case COLLISION_OF(OBJ_ASTEROID, OBJ_WEAPON):
|
|
check_collision = collide_asteroid_weapon;
|
|
break;
|
|
case COLLISION_OF(OBJ_WEAPON, OBJ_ASTEROID):
|
|
swapped = 1;
|
|
check_collision = collide_asteroid_weapon;
|
|
break;
|
|
case COLLISION_OF(OBJ_ASTEROID, OBJ_SHIP):
|
|
check_collision = collide_asteroid_ship;
|
|
break;
|
|
case COLLISION_OF(OBJ_SHIP, OBJ_ASTEROID):
|
|
check_collision = collide_asteroid_ship;
|
|
swapped = 1;
|
|
break;
|
|
case COLLISION_OF(OBJ_ASTEROID, OBJ_PROP):
|
|
check_collision = collide_asteroid_prop;
|
|
break;
|
|
case COLLISION_OF(OBJ_PROP, OBJ_ASTEROID):
|
|
check_collision = collide_asteroid_prop;
|
|
swapped = 1;
|
|
break;
|
|
case COLLISION_OF(OBJ_SHIP,OBJ_SHIP):
|
|
check_collision = collide_ship_ship;
|
|
#ifdef NDEBUG
|
|
//This is, due to debug prints, unfortunately only safe in release builds...
|
|
support_mp = true;
|
|
#endif
|
|
break;
|
|
case COLLISION_OF(OBJ_PROP, OBJ_SHIP):
|
|
check_collision = collide_prop_ship;
|
|
break;
|
|
case COLLISION_OF(OBJ_SHIP, OBJ_PROP):
|
|
check_collision = collide_prop_ship;
|
|
swapped = 1;
|
|
break;
|
|
case COLLISION_OF(OBJ_PROP, OBJ_WEAPON):
|
|
check_collision = collide_prop_weapon;
|
|
break;
|
|
case COLLISION_OF(OBJ_WEAPON, OBJ_PROP):
|
|
check_collision = collide_prop_weapon;
|
|
swapped = 1;
|
|
break;
|
|
case COLLISION_OF(OBJ_SHIP, OBJ_BEAM):
|
|
if(beam_collide_early_out(B, A)){
|
|
return;
|
|
}
|
|
swapped = 1;
|
|
check_collision = beam_collide_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_ASTEROID, OBJ_BEAM):
|
|
if(beam_collide_early_out(B, A)) {
|
|
return;
|
|
}
|
|
swapped = 1;
|
|
check_collision = beam_collide_asteroid;
|
|
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_DEBRIS, OBJ_BEAM):
|
|
if(beam_collide_early_out(B, A)) {
|
|
return;
|
|
}
|
|
swapped = 1;
|
|
check_collision = beam_collide_debris;
|
|
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_WEAPON, OBJ_BEAM):
|
|
if(beam_collide_early_out(B, A)) {
|
|
return;
|
|
}
|
|
swapped = 1;
|
|
check_collision = beam_collide_missile;
|
|
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_PROP, OBJ_BEAM):
|
|
if (beam_collide_early_out(B, A)) {
|
|
return;
|
|
}
|
|
swapped = 1;
|
|
check_collision = beam_collide_prop;
|
|
break;
|
|
|
|
case COLLISION_OF(OBJ_BEAM, OBJ_PROP):
|
|
if (beam_collide_early_out(A, B)) {
|
|
return;
|
|
}
|
|
check_collision = beam_collide_prop;
|
|
break;
|
|
|
|
case COLLISION_OF(OBJ_WEAPON, OBJ_WEAPON): {
|
|
weapon_info* awip = &Weapon_info[Weapons[A->instance].weapon_info_index];
|
|
weapon_info* bwip = &Weapon_info[Weapons[B->instance].weapon_info_index];
|
|
|
|
if ((awip->weapon_hitpoints > 0) || (bwip->weapon_hitpoints > 0)) {
|
|
if (bwip->weapon_hitpoints == 0) {
|
|
check_collision = collide_weapon_weapon;
|
|
swapped=1;
|
|
} else {
|
|
check_collision = collide_weapon_weapon;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
if ( !check_collision ) return;
|
|
|
|
// Swap them if needed
|
|
if ( swapped ) {
|
|
std::swap(A,B);
|
|
}
|
|
|
|
bool valid = false;
|
|
uint key = (OBJ_INDEX(A) << collision_cache_bitshift) + OBJ_INDEX(B);
|
|
|
|
collider_pair* collision_info = &Collision_cached_pairs[key];
|
|
|
|
if ( collision_info->initialized ) {
|
|
// make sure we're referring to the correct objects in case the original pair was deleted
|
|
if ( collision_info->signature_a == collision_info->a->signature &&
|
|
collision_info->signature_b == collision_info->b->signature ) {
|
|
valid = true;
|
|
} else {
|
|
collision_info->a = A;
|
|
collision_info->b = B;
|
|
collision_info->signature_a = A->signature;
|
|
collision_info->signature_b = B->signature;
|
|
collision_info->next_check_time = timestamp(0);
|
|
}
|
|
} else {
|
|
collision_info->a = A;
|
|
collision_info->b = B;
|
|
collision_info->signature_a = A->signature;
|
|
collision_info->signature_b = B->signature;
|
|
collision_info->initialized = true;
|
|
collision_info->next_check_time = timestamp(0);
|
|
}
|
|
|
|
if ( valid ) {
|
|
// if this signature is valid, make the necessary checks to see if we need to collide check
|
|
if ( collision_info->next_check_time == -1 ) {
|
|
return;
|
|
} else if ( !timestamp_elapsed(collision_info->next_check_time) ) {
|
|
return;
|
|
}
|
|
} else {
|
|
// 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[Weapon::Info_Flags::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) {
|
|
vec3d 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
|
|
vec3d 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...
|
|
collision_info->next_check_time = -1;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// check dist vs. dist moved during weapon lifetime
|
|
vec3d 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)) {
|
|
collision_info->next_check_time = -1;
|
|
return;
|
|
}
|
|
|
|
// for nonplayer ships, only create collision pair if close enough
|
|
if ( (B->parent >= 0) && ((Objects[B->parent].signature != B->parent_sig) || !(Objects[B->parent].flags[Object::Object_Flags::Player_ship])) && (vm_vec_dist_squared(&B->pos, &A->pos) < (4.0f*A->radius + 200.0f) * (4.0f*A->radius + 200.0f)) ) {
|
|
collision_info->next_check_time = -1;
|
|
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 ( (B->parent >= 0)
|
|
&& (Objects[B->parent].signature == B->parent_sig)
|
|
&& !(Objects[B->parent].flags[Object::Object_Flags::Player_ship])
|
|
&& (Ships[Objects[B->parent].instance].team == Ships[A->instance].team)
|
|
&& (Ship_info[Ships[A->instance].ship_info_index].is_small_ship())
|
|
&& (Weapon_info[Weapons[B->instance].weapon_info_index].subtype == WP_LASER) ) {
|
|
collision_info->next_check_time = -1;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
obj_pair new_pair;
|
|
|
|
new_pair.a = A;
|
|
new_pair.b = B;
|
|
new_pair.next_check_time = collision_info->next_check_time;
|
|
|
|
if (threading::is_threading() && support_mp) {
|
|
queue_mp_collision(ctype, new_pair);
|
|
}
|
|
else {
|
|
if (check_collision(&new_pair)) {
|
|
// don't have to check ever again
|
|
collision_info->next_check_time = -1;
|
|
} else {
|
|
collision_info->next_check_time = new_pair.next_check_time;
|
|
}
|
|
}
|
|
}
|
|
|
|
void obj_find_overlap_colliders(SCP_vector<int> &overlap_list_out, SCP_vector<int> &list, int axis, bool collide)
|
|
{
|
|
TRACE_SCOPE(tracing::FindOverlapColliders);
|
|
|
|
bool first_not_added = true;
|
|
SCP_vector<int> overlappers;
|
|
|
|
for (int in_index : list){
|
|
bool overlapped = false;
|
|
|
|
const float min = obj_get_collider_endpoint(in_index, axis, true);
|
|
|
|
for (size_t j = 0; j < overlappers.size(); ) {
|
|
const float overlap_max = obj_get_collider_endpoint(overlappers[j], axis, false);
|
|
if ( min <= overlap_max ) {
|
|
overlapped = true;
|
|
|
|
if ( overlappers.size() == 1 && first_not_added ) {
|
|
first_not_added = false;
|
|
overlap_list_out.push_back(overlappers[j]);
|
|
}
|
|
|
|
if ( collide ) {
|
|
obj_collide_pair(&Objects[in_index], &Objects[overlappers[j]]);
|
|
}
|
|
} else {
|
|
overlappers[j] = overlappers.back();
|
|
overlappers.pop_back();
|
|
continue;
|
|
}
|
|
|
|
++j;
|
|
}
|
|
|
|
if ( overlappers.empty() ) {
|
|
first_not_added = true;
|
|
}
|
|
|
|
if ( overlapped ) {
|
|
overlap_list_out.push_back(in_index);
|
|
}
|
|
|
|
overlappers.push_back(in_index);
|
|
}
|
|
}
|
|
|
|
} //anon namespace
|
|
|
|
void collide_mp_worker_thread(size_t threadIdx) {
|
|
auto& thread = collision_thread_data_buffer[threadIdx];
|
|
thread.result_length.store(0);
|
|
|
|
while (!thread.queue_process->empty() || thread.queue_length.load(std::memory_order_acquire) > 0 || !collision_processing_done.load(std::memory_order_acquire)) {
|
|
if (!thread.queue_process->empty()) {
|
|
|
|
for (auto& collision_check : *thread.queue_process) {
|
|
collision_result (*check_collision)( obj_pair *pair ) = nullptr;
|
|
|
|
switch( collision_check.ctype ) {
|
|
case COLLISION_OF(OBJ_WEAPON, OBJ_SHIP):
|
|
case COLLISION_OF(OBJ_SHIP, OBJ_WEAPON):
|
|
check_collision = collide_ship_weapon_check;
|
|
break;
|
|
case COLLISION_OF(OBJ_SHIP, OBJ_SHIP):
|
|
check_collision = collide_ship_ship_check;
|
|
break;
|
|
default:
|
|
UNREACHABLE("Got non MP-compatible collision type %d!", collision_check.ctype);
|
|
thread.queue_length.fetch_sub(1, std::memory_order_release); // keep the counter balanced
|
|
continue; // skip the bad pair
|
|
}
|
|
|
|
auto&& [never_check_again, collision_data_maybe, collision_fnc] = check_collision(&collision_check.objs);
|
|
|
|
{
|
|
std::scoped_lock lock{thread.result_mutex};
|
|
thread.queue_results->emplace_back(collision_thread_data::collision_queue_result{collision_check.objs, never_check_again, collision_data_maybe, collision_fnc});
|
|
}
|
|
thread.result_length.fetch_add(1, std::memory_order_release);
|
|
thread.queue_length.fetch_sub(1, std::memory_order_release);
|
|
}
|
|
thread.queue_process->clear();
|
|
}
|
|
else if (thread.queue_length.load(std::memory_order_acquire) > 0) {
|
|
//We must have data in the load queue then.
|
|
std::scoped_lock lock(thread.queue_mutex);
|
|
thread.queue_load.swap(thread.queue_process);
|
|
thread.queue_load->clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
void collide_init() {
|
|
if (threading::is_threading())
|
|
collision_thread_data_buffer = std::make_unique<collision_thread_data[]>(threading::get_num_workers());
|
|
}
|
|
|
|
// used only in obj_sort_and_collide()
|
|
static SCP_vector<int> sort_list_y;
|
|
static SCP_vector<int> sort_list_z;
|
|
|
|
void obj_sort_and_collide(SCP_vector<int>* Collision_list)
|
|
{
|
|
if (Cmdline_dis_collisions)
|
|
return;
|
|
|
|
if ( !(Game_detail_flags & DETAIL_FLAG_COLLISION) )
|
|
return;
|
|
|
|
if (threading::is_threading())
|
|
spin_up_mp_collision();
|
|
|
|
if (!Collision_cache_stale_objects.empty()) {
|
|
obj_collide_retime_stale_pairs();
|
|
}
|
|
|
|
// the main use case is to go through the main Collision detection list, so use that if
|
|
// nothing is defined.
|
|
if (Collision_list == nullptr) {
|
|
Collision_list = &Collision_sort_list;
|
|
}
|
|
|
|
sort_list_y.clear();
|
|
{
|
|
TRACE_SCOPE(tracing::SortColliders);
|
|
obj_quicksort_colliders(Collision_list, 0, (int)(Collision_list->size() - 1), 0);
|
|
}
|
|
obj_find_overlap_colliders(sort_list_y, *Collision_list, 0, false);
|
|
|
|
sort_list_z.clear();
|
|
{
|
|
TRACE_SCOPE(tracing::SortColliders);
|
|
obj_quicksort_colliders(&sort_list_y, 0, (int)(sort_list_y.size() - 1), 1);
|
|
}
|
|
obj_find_overlap_colliders(sort_list_z, sort_list_y, 1, false);
|
|
|
|
sort_list_y.clear();
|
|
{
|
|
TRACE_SCOPE(tracing::SortColliders);
|
|
obj_quicksort_colliders(&sort_list_z, 0, (int)(sort_list_z.size() - 1), 2);
|
|
}
|
|
obj_find_overlap_colliders(sort_list_y, sort_list_z, 2, true);
|
|
|
|
if (threading::is_threading())
|
|
post_process_threaded_collisions();
|
|
}
|
|
|
|
void collide_apply_gravity_flags_weapons() {
|
|
for (object* obj = GET_FIRST(&obj_used_list); obj != END_OF_LIST(&obj_used_list); obj = GET_NEXT(obj)) {
|
|
if (obj->type != OBJ_WEAPON || obj->flags[Object::Object_Flags::Should_be_dead])
|
|
continue;
|
|
|
|
weapon* wp = &Weapons[obj->instance];
|
|
weapon_info* wip = &Weapon_info[wp->weapon_info_index];
|
|
|
|
if (!wip->is_homing() || (wp->weapon_flags[Weapon::Weapon_Flags::No_homing])) {
|
|
// homing weapons dont get any gravity stuff
|
|
if (wip->acceleration_time <= 0.0f || Missiontime - wp->creation_time >= fl2f(wip->acceleration_time)) {
|
|
// if the weapon doesn't accelerate, or has finished accelerating...
|
|
if (The_mission.gravity == vmd_zero_vector || obj->phys_info.gravity_const == 0.0f) {
|
|
obj->phys_info.flags |= PF_CONST_VEL;
|
|
obj->phys_info.flags &= ~PF_BALLISTIC;
|
|
}
|
|
else {
|
|
obj->phys_info.flags |= PF_BALLISTIC;
|
|
obj->phys_info.flags &= ~PF_CONST_VEL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |