Files
Goober5000andClaude Opus 5 519e3aef21 Fix three memory leaks and make the debug leak check accurate
Report leaks after static destruction via _CRTDBG_LEAK_CHECK_DF instead of calling _CrtDumpMemoryLeaks() at the end of main(), which incorrectly reported every global container as a memory leak.

Also fix the three remaining real leaks which were discovered after all the noise was cleared up:
- Mc_point_list was never freed, because its atexit cleanup was only registered by code that no longer runs.  But the list was only a temporary array of pointers used while building the collision tree, so remove it and have model_collide_parse_bsp_defpoints() copy the vertices directly into the tree's point list.
- model_batch_buffer's Mem_alloc copy was never freed.  But the copy is actually not necessary, so remove it and pass Submodel_matrices directly.
- Goal_target_names strings were only freed when the next mission was parsed, so the last mission's names leaked at exit.  Hold them in SCP_vm_unique_ptr so they are freed with the vector.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-09-13 14:25:01 -04:00

1232 lines
35 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.
*
*/
#define MODEL_LIB
#include "cmdline/cmdline.h"
#include "graphics/tmapper.h"
#include "math/fvi.h"
#include "math/vecmat.h"
#include "model/model.h"
#include "model/modelrender.h"
#include "model/modelsinc.h"
#include "render/3d.h"
#include "tracing/Monitor.h"
#include "tracing/tracing.h"
#define TOL 1E-4
#define DIST_TOL 1.0
// Some global variables that get set by model_collide and are used internally for
// checking a collision rather than passing a bunch of parameters around. These are
// not persistant between calls to model_collide
thread_local static mc_info *Mc; // The mc_info passed into model_collide
thread_local static polymodel *Mc_pm; // The polygon model we're checking
thread_local static int Mc_submodel; // The current submodel we're checking
thread_local static polymodel_instance *Mc_pmi;
thread_local static matrix Mc_orient; // A matrix to rotate a world point into the current
// submodel's frame of reference.
thread_local static vec3d Mc_base; // A point used along with Mc_orient.
thread_local static vec3d Mc_p0; // The ray origin rotated into the current submodel's frame of reference
thread_local static vec3d Mc_p1; // The ray end rotated into the current submodel's frame of reference
thread_local static float Mc_mag; // The length of the ray
thread_local static vec3d Mc_direction; // A vector from the ray's origin to its end, in the current submodel's frame of reference
// Returns non-zero if vector from p0 to pdir
// intersects the bounding box.
// hitpos could be NULL, so don't fill it if it is.
int mc_ray_boundingbox( vec3d *min, vec3d *max, vec3d * p0, vec3d *pdir, vec3d *hitpos )
{
vec3d tmp_hitpos;
if ( hitpos == NULL ) {
hitpos = &tmp_hitpos;
}
if ( Mc->flags & MC_CHECK_SPHERELINE ) {
// In the case of a sphere, just increase the size of the box by the radius
// of the sphere in all directions.
vec3d sphere_mod_min, sphere_mod_max;
sphere_mod_min.xyz.x = min->xyz.x - Mc->radius;
sphere_mod_max.xyz.x = max->xyz.x + Mc->radius;
sphere_mod_min.xyz.y = min->xyz.y - Mc->radius;
sphere_mod_max.xyz.y = max->xyz.y + Mc->radius;
sphere_mod_min.xyz.z = min->xyz.z - Mc->radius;
sphere_mod_max.xyz.z = max->xyz.z + Mc->radius;
return fvi_ray_boundingbox( &sphere_mod_min, &sphere_mod_max, p0, pdir, hitpos );
} else {
return fvi_ray_boundingbox( min, max, p0, pdir, hitpos );
}
}
// -----
// mc_check_face
// nv -- number of vertices
// verts -- actual vertices
// plane_pnt -- A point on the plane. Could probably use the first vertex.
// plane_norm -- normal of the plane
// uvl_list -- list of uv coords for the poly.
// ntmap -- The tmap index into the model's textures array.
//
// detects whether or not a vector has collided with a polygon. vector points stored in global
// Mc_p0 and Mc_p1. Results stored in global mc_info * Mc.
static void mc_check_face(int nv, vec3d **verts, vec3d *plane_pnt, vec3d *plane_norm, uv_pair *uvl_list, int ntmap, ubyte *poly, bsp_collision_leaf* bsp_leaf)
{
vec3d hit_point;
float dist;
float u, v;
// Check to see if poly is facing away from ray. If so, don't bother
// checking it.
if (!(Mc->flags & MC_COLLIDE_ALL) && vm_vec_dot(&Mc_direction,plane_norm) > 0.0f) {
return;
}
// Find the intersection of this ray with the plane that the poly
dist = fvi_ray_plane(NULL, plane_pnt, plane_norm, &Mc_p0, &Mc_direction, 0.0f);
if ( dist < 0.0f ) return; // If the ray is behind the plane there is no collision
if ( !(Mc->flags & MC_CHECK_RAY) && (dist > 1.0f) ) return; // The ray isn't long enough to intersect the plane
// If the ray hits, but a closer intersection has already been found, return
if (!(Mc->flags & MC_COLLIDE_ALL) && Mc->num_hits && (dist >= Mc->hit_dist ) ) return;
// Find the hit point
vm_vec_scale_add( &hit_point, &Mc_p0, &Mc_direction, dist );
// Check to see if the point of intersection is on the plane. If so, this
// also finds the uv's where the ray hit.
if ( fvi_point_face(&hit_point, nv, verts, plane_norm, &u,&v, uvl_list ) ) {
Mc->hit_dist = dist;
Mc->hit_point = hit_point;
Mc->hit_submodel = Mc_submodel;
Mc->hit_normal = *plane_norm;
if (Mc->flags & MC_COLLIDE_ALL) {
Mc->hit_points_all.push_back(hit_point);
Mc->hit_submodels_all.push_back(Mc_submodel);
}
if ( uvl_list ) {
Mc->hit_u = u;
Mc->hit_v = v;
if ( ntmap < 0 ) {
Mc->hit_bitmap = -1;
} else {
Mc->hit_bitmap = Mc_pm->maps[ntmap].textures[TM_BASE_TYPE].GetTexture();
}
}
if(ntmap >= 0){
Mc->t_poly = poly;
Mc->f_poly = NULL;
} else {
Mc->t_poly = NULL;
Mc->f_poly = poly;
}
Mc->bsp_leaf = bsp_leaf;
// mprintf(( "Bing!\n" ));
Mc->num_hits++;
}
}
// ----------------------------------------------------------------------------------------------------------
// check face with spheres
//
// inputs: nv => number of vertices
// verts => array of vertices
// plane_pnt => center point in plane (about which radius is measured)
// face_rad => radius of face
// plane_norm => normal of face
static void mc_check_sphereline_face( int nv, vec3d ** verts, vec3d * plane_pnt, vec3d * plane_norm, uv_pair * uvl_list, int ntmap, ubyte *poly, bsp_collision_leaf *bsp_leaf)
{
vec3d hit_point;
float u, v;
float delta_t; // time sphere takes to cross from one side of plane to the other
float face_t; // time at which face touches plane
// NOTE all times are normalized so that t = 1.0 at the end of the frame
int check_face = 1; // assume we'll check the face.
int check_edges = 1; // assume we'll check the edges.
// Check to see if poly is facing away from ray. If so, don't bother
// checking it.
if (!(Mc->flags & MC_COLLIDE_ALL) && vm_vec_dot(&Mc_direction,plane_norm) > 0.0f) {
return;
}
// Find the intersection of this sphere with the plane of the poly
if ( !fvi_sphere_plane( &hit_point, &Mc_p0, &Mc_direction, Mc->radius, plane_norm, plane_pnt, &face_t, &delta_t ) ) {
return;
}
// If the ray is behind the plane there is no collision
if (face_t > 1.0f) {
check_face = 0;
check_edges = 0;
} else if (face_t < 0.0f) {
check_face = 0;
// check whether sphere can hit edge in allowed time range
if ( (face_t + delta_t) < 0.0f)
check_edges = 0;
}
// If the ray hits, but a closer intersection has already been found, don't check face
if (!(Mc->flags & MC_COLLIDE_ALL) && Mc->num_hits && (face_t >= Mc->hit_dist ) ) {
check_face = 0; // The ray isn't long enough to intersect the plane
}
//vec3d temp_sphere;
//vec3d temp_dir;
//float temp_dist;
// DA 11/5/97 Above is used to test distance between hit_point and sphere_hit_point.
// This can be as large as 0.003 on a unit sphere. I suspect that with larger spheres,
// both the relative and absolute error decrease, but this should still be checked for the
// case of larger spheres (about 5-10 units). The error also depends on the geometry of the
// object we're colliding against, but I think to a lesser degree.
if ( check_face ) {
// Find the time of the sphere surface touches the plane
// If this is within the collision window, check to see if we hit a face
if ( fvi_point_face(&hit_point, nv, verts, plane_norm, &u, &v, uvl_list) ) {
Mc->hit_dist = face_t;
Mc->hit_point = hit_point;
Mc->hit_normal = *plane_norm;
Mc->hit_submodel = Mc_submodel;
Mc->edge_hit = false;
if (Mc->flags & MC_COLLIDE_ALL) {
Mc->hit_points_all.push_back(hit_point);
Mc->hit_submodels_all.push_back(Mc_submodel);
}
if ( uvl_list ) {
Mc->hit_u = u;
Mc->hit_v = v;
if ( ntmap < 0 ) {
Mc->hit_bitmap = -1;
} else {
Mc->hit_bitmap = Mc_pm->maps[ntmap].textures[TM_BASE_TYPE].GetTexture();
}
}
if(ntmap >= 0){
Mc->t_poly = poly;
Mc->f_poly = NULL;
} else {
Mc->t_poly = NULL;
Mc->f_poly = poly;
}
Mc->bsp_leaf = bsp_leaf;
Mc->num_hits++;
check_edges = 0;
/*
vm_vec_scale_add( &temp_sphere, &Mc_p0, &Mc_direction, Mc->hit_dist );
temp_dist = vm_vec_dist( &temp_sphere, &hit_point );
if ( (temp_dist - DIST_TOL > Mc->radius) || (temp_dist + DIST_TOL < Mc->radius) ) {
// get Andsager
//mprintf(("Estimated radius error: Estimate %f, actual %f Mc->radius\n", temp_dist, Mc->radius));
}
vm_vec_sub( &temp_dir, &hit_point, &temp_sphere );
// Assert( vm_vec_dot( &temp_dir, &Mc_direction ) > 0 );
*/
}
}
if ( check_edges ) {
// Either (face_t) is out of range or we miss the face
// Check for sphere hitting edge
// If checking shields, we *still* need to check edges
// this is where we need another test to cull checking for edges
// PUT TEST HERE
// check each edge to see if we hit, find the closest edge
// Mc->hit_dist stores the best edge time of *all* faces
float sphere_time;
if ( fvi_polyedge_sphereline(&hit_point, &Mc_p0, &Mc_direction, Mc->radius, nv, verts, &sphere_time)) {
Assert( sphere_time >= 0.0f );
/*
vm_vec_scale_add( &temp_sphere, &Mc_p0, &Mc_direction, sphere_time );
temp_dist = vm_vec_dist( &temp_sphere, &hit_point );
if ( (temp_dist - DIST_TOL > Mc->radius) || (temp_dist + DIST_TOL < Mc->radius) ) {
// get Andsager
//mprintf(("Estimated radius error: Estimate %f, actual %f Mc->radius\n", temp_dist, Mc->radius));
}
vm_vec_sub( &temp_dir, &hit_point, &temp_sphere );
// Assert( vm_vec_dot( &temp_dir, &Mc_direction ) > 0 );
*/
if ((Mc->flags & MC_COLLIDE_ALL) || (Mc->num_hits==0) || (sphere_time < Mc->hit_dist) ) {
// This is closer than best so far
Mc->hit_dist = sphere_time;
Mc->hit_point = hit_point;
Mc->hit_normal = *plane_norm;
Mc->hit_submodel = Mc_submodel;
Mc->edge_hit = true;
if (Mc->flags & MC_COLLIDE_ALL) {
Mc->hit_points_all.push_back(hit_point);
Mc->hit_submodels_all.push_back(Mc_submodel);
}
if ( ntmap < 0 ) {
Mc->hit_bitmap = -1;
} else {
Mc->hit_bitmap = Mc_pm->maps[ntmap].textures[TM_BASE_TYPE].GetTexture();
}
if(ntmap >= 0){
Mc->t_poly = poly;
Mc->f_poly = NULL;
} else {
Mc->t_poly = NULL;
Mc->f_poly = poly;
}
Mc->num_hits++;
// nprintf(("Physics", "edge sphere time: %f, normal: (%f, %f, %f) hit_point: (%f, %f, %f)\n", sphere_time,
// Mc->hit_normal.xyz.x, Mc->hit_normal.xyz.y, Mc->hit_normal.xyz.z,
// hit_point.xyz.x, hit_point.xyz.y, hit_point.xyz.z));
} else { // Not best so far
Assert(Mc->num_hits>0);
Mc->num_hits++;
}
}
}
}
// copies each vertex in the submodel's BSP data into the tree's point list
void model_collide_parse_bsp_defpoints(bsp_collision_tree *tree, ubyte *p)
{
uint n;
uint nverts = uw(p+8);
uint offset = uw(p+16);
ubyte * normcount = p+20;
vec3d *src = vp(p+offset);
tree->n_verts = (int)nverts;
if (nverts == 0) {
tree->point_list = nullptr;
return;
}
tree->point_list = (vec3d*)vm_malloc(sizeof(vec3d) * nverts);
for (n=0; n<nverts; n++ ) {
tree->point_list[n] = *src;
src += normcount[n]+1;
}
}
void model_collide_bsp_poly(bsp_collision_tree *tree, int leaf_index)
{
int i;
int tested_leaf = leaf_index;
uv_pair uvlist[TMAP_MAX_VERTS];
vec3d *points[TMAP_MAX_VERTS];
while ( tested_leaf >= 0 ) {
bsp_collision_leaf *leaf = &tree->leaf_list[tested_leaf];
bool flat_poly = false;
int vert_start = leaf->vert_start;
int nv = leaf->num_verts;
if ( leaf->tmap_num < MAX_MODEL_TEXTURES ) {
if ( (!(Mc->flags & MC_CHECK_INVISIBLE_FACES)) && (Mc_pm->maps[leaf->tmap_num].textures[TM_BASE_TYPE].GetTexture() < 0) ) {
// Don't check invisible polygons.
//SUSHI: Unless $collide_invisible is set.
if (!(Mc_pm->submodel[Mc_submodel].flags[Model::Submodel_flags::Collide_invisible]))
return;
}
} else {
flat_poly = true;
}
int vert_num;
for ( i = 0; i < nv; ++i ) {
vert_num = tree->vert_list[vert_start+i].vertnum;
points[i] = &tree->point_list[vert_num];
uvlist[i].u = tree->vert_list[vert_start+i].u;
uvlist[i].v = tree->vert_list[vert_start+i].v;
}
if ( flat_poly ) {
if ( Mc->flags & MC_CHECK_SPHERELINE ) {
mc_check_sphereline_face(nv, points, points[0], &leaf->plane_norm, nullptr, -1, nullptr, leaf);
} else {
mc_check_face(nv, points, points[0], &leaf->plane_norm, nullptr, -1, nullptr, leaf);
}
} else {
if ( Mc->flags & MC_CHECK_SPHERELINE ) {
mc_check_sphereline_face(nv, points, points[0], &leaf->plane_norm, uvlist, leaf->tmap_num, nullptr, leaf);
} else {
mc_check_face(nv, points, points[0], &leaf->plane_norm, uvlist, leaf->tmap_num, nullptr, leaf);
}
}
tested_leaf = leaf->next;
}
}
void model_collide_bsp(bsp_collision_tree *tree, int node_index)
{
if ( tree->node_list == NULL || tree->n_verts <= 0) {
return;
}
bsp_collision_node *node = &tree->node_list[node_index];
vec3d hitpos;
// check the bounding box of this node. if it passes, check left and right children
if ( mc_ray_boundingbox( &node->min, &node->max, &Mc_p0, &Mc_direction, &hitpos ) ) {
if ( !(Mc->flags & MC_CHECK_RAY) && (vm_vec_dist(&hitpos, &Mc_p0) > Mc_mag) ) {
// The ray isn't long enough to intersect the bounding box
return;
}
if ( node->leaf >= 0 ) {
model_collide_bsp_poly(tree, node->leaf);
} else {
if ( node->back >= 0 ) model_collide_bsp(tree, node->back);
if ( node->front >= 0 ) model_collide_bsp(tree, node->front);
}
}
}
void model_collide_parse_bsp_tmappoly(bsp_collision_leaf *leaf, SCP_vector<model_tmap_vert> *vert_buffer, void *model_ptr)
{
ubyte *p = (ubyte *)model_ptr;
uint i;
uint nv;
nv = uw(p + TMAP_NVERTS);
if (nv > TMAP_MAX_VERTS) {
Error(LOCATION, "Model contains TMAP chunk with more than %d vertices!", TMAP_MAX_VERTS);
return;
}
int tmap_num = w(p + TMAP_TEXNUM);
if (tmap_num < 0 || tmap_num >= MAX_MODEL_TEXTURES) {
Error(LOCATION, "Model contains TMAP2 chunk with invalid texture id (%d)!", tmap_num);
return;
}
auto verts = reinterpret_cast<model_tmap_vert_old*>(&p[TMAP_VERTS]);
leaf->tmap_num = (ubyte)tmap_num;
leaf->num_verts = (ubyte)nv;
leaf->vert_start = (int)vert_buffer->size();
vec3d *plane_norm = vp(p + TMAP_NORMAL);
leaf->plane_norm = *plane_norm;
for ( i = 0; i < nv; ++i ) {
vert_buffer->push_back(model_tmap_vert(verts[i]));
}
}
/**
* @brief Generates BSP leaf and vertex buffer to check against a bsp collision for a TMAP2 chunk.
*
* @param[out] leaf Generated BSP leaf.
* @param[out] vert_buffer Vertex buffer forming the polygon being checked against.
* @param[in] model_ptr Buffer of BSP data from model buffer.
*/
void model_collide_parse_bsp_tmap2poly(bsp_collision_leaf* leaf, SCP_vector<model_tmap_vert>* vert_buffer, void* model_ptr)
{
auto p = (ubyte*)model_ptr;
uint i;
uint nv;
int tmap_num;
vec3d* plane_norm;
model_tmap_vert* verts;
nv = uw(p + TMAP2_NVERTS);
if (nv > TMAP_MAX_VERTS) {
Error(LOCATION,"Model contains TMAP2 chunk with more than %d vertices!", TMAP_MAX_VERTS);
return;
}
tmap_num = w(p + TMAP2_TEXNUM);
if (tmap_num < 0 || tmap_num >= MAX_MODEL_TEXTURES) {
Error(LOCATION, "Model contains TMAP2 chunk with invalid texture id (%d)!", tmap_num);
return;
}
verts = reinterpret_cast<model_tmap_vert*>(p + TMAP2_VERTS);
leaf->tmap_num = (ubyte)tmap_num;
leaf->num_verts = (ubyte)nv;
leaf->vert_start = (int)vert_buffer->size();
plane_norm = vp(p + TMAP2_NORMAL);
leaf->plane_norm = *plane_norm;
for (i = 0; i < nv; ++i) {
vert_buffer->push_back(verts[i]);
}
}
void model_collide_parse_bsp_flatpoly(bsp_collision_leaf *leaf, SCP_vector<model_tmap_vert> *vert_buffer, void *model_ptr)
{
ubyte *p = (ubyte *)model_ptr;
uint i;
uint nv;
short *verts;
nv = uw(p+36);
if ( nv > TMAP_MAX_VERTS ) {
Int3();
return;
}
verts = (short *)(p+44);
leaf->tmap_num = 255;
leaf->num_verts = (ubyte)nv;
leaf->vert_start = (int)vert_buffer->size();
vec3d *plane_norm = vp(p+8);
leaf->plane_norm = *plane_norm;
model_tmap_vert vert;
for ( i = 0; i < nv; ++i ) {
vert.vertnum = verts[i*2];
vert.normnum = 0;
vert.u = 0.0f;
vert.v = 0.0f;
vert_buffer->push_back(vert);
}
}
void model_collide_parse_bsp(bsp_collision_tree *tree, ubyte *bsp_data, int version)
{
TRACE_SCOPE(tracing::ModelParseBSPTree);
ubyte *p = bsp_data;
ubyte *next_p;
int chunk_type = w(p);
int chunk_size = w(p+4);
int next_chunk_type;
int next_chunk_size;
Assert(chunk_type == OP_DEFPOINTS);
model_collide_parse_bsp_defpoints(tree, p);
if (tree->n_verts == 0) {
tree->n_nodes = 0;
tree->node_list = NULL;
tree->n_leaves = 0;
tree->leaf_list = NULL;
// finally copy the vert list.
tree->vert_list = NULL;
return;
}
p += chunk_size;
bsp_collision_node new_node;
bsp_collision_leaf new_leaf{ vmd_zero_vector, 0, 0, 0, 0 };
SCP_vector<bsp_collision_node> node_buffer;
SCP_vector<bsp_collision_leaf> leaf_buffer;
SCP_vector<model_tmap_vert> vert_buffer;
SCP_map<size_t, ubyte*> bsp_datap;
node_buffer.push_back(new_node);
size_t i = 0;
vec3d *min;
vec3d *max;
bsp_datap[i] = p;
while ( i < node_buffer.size() ) {
p = bsp_datap[i];
chunk_type = w(p);
chunk_size = w(p+4);
int front_offset, back_offset;
switch ( chunk_type ) {
case OP_SORTNORM:
if ( version >= 2000 ) {
min = vp(p+56);
max = vp(p+68);
node_buffer[i].min = *min;
node_buffer[i].max = *max;
}
node_buffer[i].leaf = -1;
node_buffer[i].front = -1;
node_buffer[i].back = -1;
front_offset = w(p + 36);
if (front_offset) {
next_chunk_type = w(p + front_offset);
if ( next_chunk_type != OP_EOF ) {
node_buffer.push_back(new_node);
node_buffer[i].front = (int)(node_buffer.size() - 1);
bsp_datap[node_buffer[i].front] = p + front_offset;
}
}
back_offset = w(p + 40);
if (back_offset) {
next_chunk_type = w(p + back_offset);
if ( next_chunk_type != OP_EOF ) {
node_buffer.push_back(new_node);
node_buffer[i].back = (int)(node_buffer.size() - 1);
bsp_datap[node_buffer[i].back] = p + back_offset;
}
}
next_p = p + chunk_size;
next_chunk_type = w(next_p);
Assert( next_chunk_type == OP_EOF );
++i;
break;
case OP_SORTNORM2:
min = vp(p + 16);
max = vp(p + 28);
node_buffer[i].min = *min;
node_buffer[i].max = *max;
node_buffer[i].leaf = -1;
node_buffer[i].front = -1;
node_buffer[i].back = -1;
front_offset = w(p + 8);
if (front_offset) {
next_chunk_type = w(p + front_offset);
if (next_chunk_type != OP_EOF) {
node_buffer.push_back(new_node);
node_buffer[i].front = (int)(node_buffer.size() - 1);
bsp_datap[node_buffer[i].front] = p + front_offset;
}
}
back_offset = w(p + 12);
if (back_offset) {
next_chunk_type = w(p + back_offset);
if (next_chunk_type != OP_EOF) {
node_buffer.push_back(new_node);
node_buffer[i].back = (int)(node_buffer.size() - 1);
bsp_datap[node_buffer[i].back] = p + back_offset;
}
}
next_p = p + chunk_size;
next_chunk_type = OP_EOF; // should not continue after this chunk
++i;
break;
case OP_BOUNDBOX:
min = vp(p+8);
max = vp(p+20);
node_buffer[i].min = *min;
node_buffer[i].max = *max;
node_buffer[i].front = -1;
node_buffer[i].back = -1;
node_buffer[i].leaf = -1;
next_p = p + chunk_size;
next_chunk_type = w(next_p);
next_chunk_size = w(next_p+4);
if (next_chunk_type != OP_EOF &&
(next_chunk_type == OP_TMAPPOLY || next_chunk_type == OP_FLATPOLY)) {
new_leaf.next = -1;
node_buffer[i].leaf = (int)leaf_buffer.size(); // get index of where our poly list starts in the leaf buffer
while ( next_chunk_type != OP_EOF ) {
if ( next_chunk_type == OP_TMAPPOLY ) {
model_collide_parse_bsp_tmappoly(&new_leaf, &vert_buffer, next_p);
} else if ( next_chunk_type == OP_FLATPOLY ) {
model_collide_parse_bsp_flatpoly(&new_leaf, &vert_buffer, next_p);
} else {
Int3();
}
// add another polygon center
vec3d center = vmd_zero_vector;
for (int j = 0; j < new_leaf.num_verts; j++) {
center += tree->point_list[vert_buffer[new_leaf.vert_start + j].vertnum];
}
tree->poly_centers.push_back(center / (float)new_leaf.num_verts);
leaf_buffer.push_back(new_leaf);
leaf_buffer.back().next = (int)leaf_buffer.size();
next_p += next_chunk_size;
next_chunk_type = w(next_p);
next_chunk_size = w(next_p+4);
}
leaf_buffer.back().next = -1;
}
Assert(next_chunk_type == OP_EOF);
++i;
break;
case OP_TMAP2POLY:
min = vp(p + 8);
max = vp(p + 20);
node_buffer[i].min = *min;
node_buffer[i].max = *max;
node_buffer[i].front = -1;
node_buffer[i].back = -1;
node_buffer[i].leaf = (int)leaf_buffer.size();
model_collide_parse_bsp_tmap2poly(&new_leaf, &vert_buffer, p);
// add another polygon center
vec3d center = vmd_zero_vector;
for (int j = 0; j < new_leaf.num_verts; j++) {
center += tree->point_list[vert_buffer[new_leaf.vert_start + j].vertnum];
}
tree->poly_centers.push_back(center / (float)new_leaf.num_verts);
leaf_buffer.push_back(new_leaf);
leaf_buffer.back().next = -1;
++i;
break;
}
}
// copy node info. this might be a good time to organize the nodes into a cache efficient tree layout.
tree->n_nodes = (int)node_buffer.size();
tree->node_list = (bsp_collision_node*)vm_malloc(sizeof(bsp_collision_node) * node_buffer.size());
memcpy(tree->node_list, &node_buffer[0], sizeof(bsp_collision_node) * node_buffer.size());
node_buffer.clear();
// copy leaves.
tree->n_leaves = (int)leaf_buffer.size();
tree->leaf_list = (bsp_collision_leaf*)vm_malloc(sizeof(bsp_collision_leaf) * leaf_buffer.size());
memcpy(tree->leaf_list, &leaf_buffer[0], sizeof(bsp_collision_leaf) * leaf_buffer.size());
leaf_buffer.clear();
// finally copy the vert list.
tree->vert_list = (model_tmap_vert*)vm_malloc(sizeof(model_tmap_vert) * vert_buffer.size());
memcpy(tree->vert_list, &vert_buffer[0], sizeof(model_tmap_vert) * vert_buffer.size());
vert_buffer.clear();
}
bool mc_shield_check_common(shield_tri *tri)
{
vec3d * points[3];
vec3d hitpoint;
float dist;
float sphere_check_closest_shield_dist = FLT_MAX;
// Check to see if Mc_pmly is facing away from ray. If so, don't bother
// checking it.
if (vm_vec_dot(&Mc_direction,&tri->norm) > 0.0f) {
return false;
}
// get the vertices in the form the next function wants them
for (int j = 0; j < 3; j++ )
points[j] = &Mc_pm->shield.verts[tri->verts[j]].pos;
if (!(Mc->flags & MC_CHECK_SPHERELINE) ) { // Don't do this test for sphere colliding against shields
// Find the intersection of this ray with the plane that the Mc_pmly
// lies in
dist = fvi_ray_plane(NULL, points[0],&tri->norm,&Mc_p0,&Mc_direction,0.0f);
if ( dist < 0.0f ) return false; // If the ray is behind the plane there is no collision
if ( !(Mc->flags & MC_CHECK_RAY) && (dist > 1.0f) ) return false; // The ray isn't long enough to intersect the plane
// Find the hit Mc_pmint
vm_vec_scale_add( &hitpoint, &Mc_p0, &Mc_direction, dist );
// Check to see if the Mc_pmint of intersection is on the plane. If so, this
// also finds the uv's where the ray hit.
if ( fvi_point_face(&hitpoint, 3, points, &tri->norm, NULL,NULL,NULL ) ) {
Mc->hit_dist = dist;
Mc->shield_hit_tri = (int)(tri - Mc_pm->shield.tris.get());
Mc->hit_point = hitpoint;
Mc->hit_normal = tri->norm;
Mc->hit_submodel = -1;
Mc->num_hits++;
return true; // We hit, so we're done
}
} else { // Sphere check against shield
// This needs to look at *all* shield tris and not just return after the first hit
// HACK HACK!! The 10000.0 is the face radius, I didn't know this,
// so I'm assume 10000 would be as big as ever.
mc_check_sphereline_face(3, points, points[0], &tri->norm, NULL, 0, NULL, NULL);
if (Mc->num_hits && Mc->hit_dist < sphere_check_closest_shield_dist) {
// same behavior whether face or edge
// normal, edge_hit, hit_point all updated thru sphereline_face
sphere_check_closest_shield_dist = Mc->hit_dist;
Mc->shield_hit_tri = (int)(tri - Mc_pm->shield.tris.get());
Mc->hit_submodel = -1;
Mc->num_hits++;
return true; // We hit, so we're done
}
} // Mc->flags & MC_CHECK_SPHERELINE else
return false;
}
bool mc_check_sldc(int offset)
{
//ShivanSpS - Changed the type char for a type int (Now SLC2)
if (offset > Mc_pm->sldc_size - 5) //no way is this big enough
return false;
int* type_p = (int*)(Mc_pm->shield_collision_tree.get() + offset);
// not used
//int *size_p = (int *)(Mc_pm->shield_collision_tree+offset+4);
// split and polygons
auto* minbox_p = (vec3d*)(Mc_pm->shield_collision_tree.get() + offset + 8);
auto* maxbox_p = (vec3d*)(Mc_pm->shield_collision_tree.get() + offset + 20);
// split
auto* front_offset_p = (unsigned int*)(Mc_pm->shield_collision_tree.get() + offset + 32);
auto* back_offset_p = (unsigned int*)(Mc_pm->shield_collision_tree.get() + offset + 36);
// polygons
auto* num_polygons_p = (unsigned int*)(Mc_pm->shield_collision_tree.get() + offset + 32);
auto* shld_polys = (unsigned int*)(Mc_pm->shield_collision_tree.get() + offset + 36);
// see if it fits inside our bbox
if (!mc_ray_boundingbox(minbox_p, maxbox_p, &Mc_p0, &Mc_direction, NULL)) {
return false;
}
if (*type_p == 0) // SPLIT
{
return mc_check_sldc(offset + *front_offset_p) || mc_check_sldc(offset + *back_offset_p);
}
else
{
// poly list
shield_tri* tri;
for (unsigned int i = 0; i < *num_polygons_p; i++)
{
tri = &Mc_pm->shield.tris[shld_polys[i]];
mc_shield_check_common(tri);
} // for (unsigned int i = 0; i < leaf->num_polygons; i++)
}
// shouldn't be reached
return false;
}
// checks a vector collision against a ships shield (if it has shield points defined).
void mc_check_shield()
{
int i;
if ( Mc_pm->shield.ntris < 1 )
return;
if (Mc_pm->shield_collision_tree)
{
mc_check_sldc(0); // see if we hit the SLDC
}
else
{
for (i = 0; i < Mc_pm->shield.ntris; i++) {
mc_shield_check_common(&Mc_pm->shield.tris[i]);
}
}//model has shield_collsion_tree
}
// This function recursively checks a submodel and its children
// for a collision with a vector.
void mc_check_subobj( int mn )
{
vec3d tempv;
vec3d hitpt; // used in bounding box check
bsp_info * sm;
int i;
Assert( mn >= 0 );
Assert( mn < Mc_pm->n_models );
if ( (mn < 0) || (mn>=Mc_pm->n_models) ) return;
sm = &Mc_pm->submodel[mn];
if (sm->flags[Model::Submodel_flags::No_collisions]) return; // don't do collisions
if (sm->flags[Model::Submodel_flags::Nocollide_this_only]) goto NoHit; // Don't collide for this model, but keep checking others
if (Mc->flags & MC_RESPECT_DETAIL_BOX_SPHERE) {
vec3d local;
vm_vec_sub(&local, &Eye_position, Mc->pos);
vm_vec_rotate(&local, &local, Mc->orient);
if (!model_render_check_detail_box(&local, Mc_pm, mn, MR_NORMAL))
goto NoHit; //This submodel is a detail box that is not displayed, skip it
}
// Rotate the world check points into the current subobject's
// frame of reference.
// After this block, Mc_p0, Mc_p1, Mc_direction, and Mc_mag are correct
// and relative to this subobjects' frame of reference.
vm_vec_sub(&tempv, Mc->p0, &Mc_base);
vm_vec_rotate(&Mc_p0, &tempv, &Mc_orient);
vm_vec_sub(&tempv, Mc->p1, &Mc_base);
vm_vec_rotate(&Mc_p1, &tempv, &Mc_orient);
vm_vec_sub(&Mc_direction, &Mc_p1, &Mc_p0);
// bail early if no ray exists
if ( IS_VEC_NULL(&Mc_direction) ) {
return;
}
if (Mc_pm->detail[0] == mn) {
// Quickly bail if we aren't inside the full model bbox
if (!mc_ray_boundingbox( &Mc_pm->mins, &Mc_pm->maxs, &Mc_p0, &Mc_direction, NULL)) {
return;
}
// If we are checking the root submodel, then we might want to check
// the shield at this point
if ((Mc->flags & MC_CHECK_SHIELD) && (Mc_pm->shield.ntris > 0 )) {
mc_check_shield();
return;
}
}
if (!(Mc->flags & MC_CHECK_MODEL)) {
return;
}
Mc_submodel = mn;
// Check if the ray intersects this subobject's bounding box
if ( mc_ray_boundingbox(&sm->min, &sm->max, &Mc_p0, &Mc_direction, &hitpt) ) {
if (Mc->flags & MC_ONLY_BOUND_BOX) {
float dist = vm_vec_dist( &Mc_p0, &hitpt );
// If the ray is behind the plane there is no collision
if (dist < 0.0f) {
goto NoHit;
}
// The ray isn't long enough to intersect the plane
if ( !(Mc->flags & MC_CHECK_RAY) && (dist > Mc_mag) ) {
goto NoHit;
}
// If the ray hits, but a closer intersection has already been found, return
if ( Mc->num_hits && (dist >= Mc->hit_dist) ) {
goto NoHit;
}
Mc->hit_dist = dist;
Mc->hit_point = hitpt;
Mc->hit_submodel = Mc_submodel;
Mc->hit_bitmap = -1;
Mc->num_hits++;
} else {
// The ray intersects this bounding box, so we have to check all the
// polygons in this submodel.
if (Mc->lod > 0 && sm->num_details > 0) {
bsp_info* lod_sm = sm;
for (i = Mc->lod - 1; i >= 0; i--) {
if (sm->details[i] != -1) {
lod_sm = &Mc_pm->submodel[sm->details[i]];
// mprintf(("Checking %s collision for %s using %s instead\n", Mc_pm->filename, sm->name,
// lod_sm->name));
break;
}
}
model_collide_bsp(model_get_bsp_collision_tree(lod_sm->collision_tree_index), 0);
} else {
model_collide_bsp(model_get_bsp_collision_tree(sm->collision_tree_index), 0);
}
}
}
NoHit:
// If we're only checking one submodel, return
if (Mc->flags & MC_SUBMODEL) {
return;
}
// If this subobject doesn't have any children, we're done checking it.
if ( sm->num_children < 1 ) return;
// Save instance (Mc_orient, Mc_base, Mc_point_base)
matrix saved_orient = Mc_orient;
vec3d saved_base = Mc_base;
// Check all of this subobject's children
i = sm->first_child;
while ( i >= 0 ) {
auto csm = &Mc_pm->submodel[i];
matrix instance_orient = vmd_identity_matrix;
vec3d instance_offset = csm->offset;
bool blown_off = false;
bool collision_checked = false;
if ( Mc_pmi ) {
auto csmi = &Mc_pmi->submodel[i];
instance_orient = csmi->canonical_orient;
vm_vec_add2(&instance_offset, &csmi->canonical_offset);
blown_off = csmi->blown_off;
collision_checked = !Mc->collision_checked.empty() && Mc->collision_checked[i];
}
// Don't check it or its children if it is destroyed
// or if it's set to no collision
if ( !blown_off && !collision_checked && !csm->flags[Model::Submodel_flags::No_collisions] ) {
vm_vec_unrotate(&Mc_base, &instance_offset, &saved_orient);
vm_vec_add2(&Mc_base, &saved_base);
vm_matrix_x_matrix(&Mc_orient, &saved_orient, &instance_orient);
mc_check_subobj( i );
}
i = csm->next_sibling;
}
}
MONITOR(NumFVI)
// See model.h for usage. I don't want to put the
// usage here because you need to see the #defines and structures
// this uses while reading the help.
int model_collide(mc_info *mc_info_obj)
{
Mc = mc_info_obj;
MONITOR_INC(NumFVI,1);
Mc->num_hits = 0; // How many collisions were found
Mc->shield_hit_tri = -1; // Assume we won't hit any shield polygons
Mc->hit_bitmap = -1;
Mc->edge_hit = false;
if ( (Mc->flags & MC_CHECK_SHIELD) && (Mc->flags & MC_CHECK_MODEL) ) {
Error( LOCATION, "Checking both shield and model!\n" );
return 0;
}
//Fill in some global variables that all the model collide routines need internally.
Mc_pm = model_get(Mc->model_num);
Mc_orient = *Mc->orient;
Mc_base = *Mc->pos;
Mc_mag = vm_vec_dist( Mc->p0, Mc->p1 );
if ( Mc->model_instance_num >= 0 ) {
Mc_pmi = model_get_instance(Mc->model_instance_num);
} else {
Mc_pmi = NULL;
}
if (Mc_pmi && Mc->collision_checked.size() != static_cast<size_t>(Mc_pm->n_models)) {
Assertion(Mc->collision_checked.empty(), "model_collide was called with a dirty mc_info state! Please report to the SCP.");
Mc->collision_checked.resize(Mc_pm->n_models, 0);
}
// DA 11/19/98 - disable this check for rotating submodels
// Don't do check if for very small movement
// if (Mc_mag < 0.01f) {
// return 0;
// }
float model_radius; // How big is the model we're checking against
int first_submodel; // Which submodel gets returned as hit if MC_ONLY_SPHERE specified
if ( (Mc->flags & MC_SUBMODEL) || (Mc->flags & MC_SUBMODEL_INSTANCE) ) {
first_submodel = Mc->submodel_num;
model_radius = Mc_pm->submodel[first_submodel].rad;
} else {
first_submodel = Mc_pm->detail[0];
model_radius = Mc_pm->rad;
}
if ( Mc->flags & MC_CHECK_SPHERELINE ) {
if ( Mc->radius <= 0.0f ) {
Warning(LOCATION, "Attempting to collide with a sphere, but the sphere's radius is <= 0.0f!\n\n(model file is %s; submodel is %d, mc_flags are %d)", Mc_pm->filename, first_submodel, Mc->flags);
return 0;
}
// Do a quick check on the Bounding Sphere
if (fvi_segment_sphere(&Mc->hit_point_world, Mc->p0, Mc->p1, Mc->pos, model_radius+Mc->radius) ) {
if ( Mc->flags & MC_ONLY_SPHERE ) {
Mc->hit_point = Mc->hit_point_world;
Mc->hit_submodel = first_submodel;
Mc->num_hits++;
return (Mc->num_hits > 0);
}
// continue checking polygons.
} else {
return 0;
}
} else {
int r;
// Do a quick check on the Bounding Sphere
if ( Mc->flags & MC_CHECK_RAY ) {
r = fvi_ray_sphere(&Mc->hit_point_world, Mc->p0, Mc->p1, Mc->pos, model_radius);
} else {
r = fvi_segment_sphere(&Mc->hit_point_world, Mc->p0, Mc->p1, Mc->pos, model_radius);
}
if (r) {
if ( Mc->flags & MC_ONLY_SPHERE ) {
Mc->hit_point = Mc->hit_point_world;
Mc->hit_submodel = first_submodel;
Mc->num_hits++;
return (Mc->num_hits > 0);
}
// continue checking polygons.
} else {
return 0;
}
}
// Check only one subobject; or check submodel and any children
if ( (Mc->flags & MC_SUBMODEL) || (Mc->flags & MC_SUBMODEL_INSTANCE) ) {
// note: within this function, MC_SUBMODEL will return after one check; but MC_SUBMODEL_INSTANCE will not
mc_check_subobj(Mc->submodel_num);
}
// Check all the the highest detail model polygons and subobjects for intersections
else {
// Don't check it or its children if it is destroyed
if ( Mc_pmi ) {
if ( !Mc_pmi->submodel[Mc_pm->detail[0]].blown_off ) {
mc_check_subobj(Mc_pm->detail[0]);
}
} else {
mc_check_subobj(Mc_pm->detail[0]);
}
}
//If we found a hit, then rotate it into world coordinates
if ( Mc->num_hits ) {
if ( Mc->flags & MC_SUBMODEL ) {
// If we're just checking one submodel, don't use normal instancing to find world points
vm_vec_unrotate(&Mc->hit_point_world, &Mc->hit_point, Mc->orient);
vm_vec_add2(&Mc->hit_point_world, Mc->pos);
} else {
if ( Mc_pmi ) {
model_instance_local_to_global_point(&Mc->hit_point_world, &Mc->hit_point, Mc_pm, Mc_pmi, Mc->hit_submodel, Mc->orient, Mc->pos);
} else {
model_local_to_global_point(&Mc->hit_point_world, &Mc->hit_point, Mc_pm, Mc->hit_submodel, Mc->orient, Mc->pos);
}
}
// do the same for the list of hitpoints, if necessary
if (Mc->flags & MC_COLLIDE_ALL) {
for (size_t i = 0; i < Mc->hit_points_all.size(); i++) {
if (Mc->flags & MC_SUBMODEL) {
vm_vec_unrotate(&Mc->hit_points_all[i], &Mc->hit_points_all[i], Mc->orient);
vm_vec_add2(&Mc->hit_points_all[i], Mc->pos);
} else {
if (Mc_pmi) {
model_instance_local_to_global_point(&Mc->hit_points_all[i], &Mc->hit_points_all[i], Mc_pm, Mc_pmi, Mc->hit_submodels_all[i], Mc->orient, Mc->pos);
}
else {
model_local_to_global_point(&Mc->hit_points_all[i], &Mc->hit_points_all[i], Mc_pm, Mc->hit_submodels_all[i], Mc->orient, Mc->pos);
}
}
}
}
}
return Mc->num_hits;
}