2013-04-19 02:52:48 +00:00
|
|
|
//Anything above this #include will be ignored by the compiler
|
|
|
|
#include "../qcommon/exe_headers.h"
|
|
|
|
|
|
|
|
#include "cm_local.h"
|
|
|
|
#include "cm_landscape.h"
|
|
|
|
|
|
|
|
#ifdef _XBOX
|
|
|
|
#include "../renderer/tr_local.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// always use bbox vs. bbox collision and never capsule vs. bbox or vice versa
|
|
|
|
//#define ALWAYS_BBOX_VS_BBOX
|
|
|
|
// always use capsule vs. capsule collision and never capsule vs. bbox or vice versa
|
|
|
|
//#define ALWAYS_CAPSULE_VS_CAPSULE
|
|
|
|
|
|
|
|
//#define CAPSULE_DEBUG
|
|
|
|
|
|
|
|
void CM_TraceThroughTerrain( traceWork_t *tw, trace_t &trace, cbrush_t *brush );
|
|
|
|
|
|
|
|
//#define TEST_TERRAIN_PHYSICS
|
|
|
|
|
|
|
|
#ifdef TEST_TERRAIN_PHYSICS
|
|
|
|
|
|
|
|
Be sure to un-link entity in void SP_terrain(gentity_t *ent) (yeah, left this uncommented to cause error / attention )
|
|
|
|
|
|
|
|
|
|
|
|
void CM_TraceThroughTerrain( traceWork_t *tw, trace_t &trace, CCMLandScape *landscape);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============================================================================
|
|
|
|
|
|
|
|
BASIC MATH
|
|
|
|
|
|
|
|
===============================================================================
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
RotatePoint
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void RotatePoint(vec3_t point, /*const*/ vec3_t matrix[3]) { // bk: FIXME
|
|
|
|
vec3_t tvec;
|
|
|
|
|
|
|
|
VectorCopy(point, tvec);
|
|
|
|
point[0] = DotProduct(matrix[0], tvec);
|
|
|
|
point[1] = DotProduct(matrix[1], tvec);
|
|
|
|
point[2] = DotProduct(matrix[2], tvec);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
TransposeMatrix
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void TransposeMatrix(/*const*/ vec3_t matrix[3], vec3_t transpose[3]) { // bk: FIXME
|
|
|
|
int i, j;
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
|
|
for (j = 0; j < 3; j++) {
|
|
|
|
transpose[i][j] = matrix[j][i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CreateRotationMatrix
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void CreateRotationMatrix(const vec3_t angles, vec3_t matrix[3]) {
|
|
|
|
AngleVectors(angles, matrix[0], matrix[1], matrix[2]);
|
|
|
|
VectorInverse(matrix[1]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CM_ProjectPointOntoVector
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void CM_ProjectPointOntoVector( vec3_t point, vec3_t vStart, vec3_t vDir, vec3_t vProj )
|
|
|
|
{
|
|
|
|
vec3_t pVec;
|
|
|
|
|
|
|
|
VectorSubtract( point, vStart, pVec );
|
|
|
|
// project onto the directional vector for this segment
|
|
|
|
VectorMA( vStart, DotProduct( pVec, vDir ), vDir, vProj );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CM_DistanceFromLineSquared
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
float CM_DistanceFromLineSquared(vec3_t p, vec3_t lp1, vec3_t lp2, vec3_t dir) {
|
|
|
|
vec3_t proj, t;
|
|
|
|
int j;
|
|
|
|
|
|
|
|
CM_ProjectPointOntoVector(p, lp1, dir, proj);
|
|
|
|
for (j = 0; j < 3; j++)
|
|
|
|
if ((proj[j] > lp1[j] && proj[j] > lp2[j]) ||
|
|
|
|
(proj[j] < lp1[j] && proj[j] < lp2[j]))
|
|
|
|
break;
|
|
|
|
if (j < 3) {
|
|
|
|
if (fabs(proj[j] - lp1[j]) < fabs(proj[j] - lp2[j]))
|
|
|
|
VectorSubtract(p, lp1, t);
|
|
|
|
else
|
|
|
|
VectorSubtract(p, lp2, t);
|
|
|
|
return VectorLengthSquared(t);
|
|
|
|
}
|
|
|
|
VectorSubtract(p, proj, t);
|
|
|
|
return VectorLengthSquared(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CM_VectorDistanceSquared
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
float CM_VectorDistanceSquared(vec3_t p1, vec3_t p2) {
|
|
|
|
vec3_t dir;
|
|
|
|
|
|
|
|
VectorSubtract(p2, p1, dir);
|
|
|
|
return VectorLengthSquared(dir);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
SquareRootFloat
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
float SquareRootFloat(float number) {
|
|
|
|
long i;
|
|
|
|
float x, y;
|
|
|
|
const float f = 1.5F;
|
|
|
|
|
|
|
|
x = number * 0.5F;
|
|
|
|
y = number;
|
|
|
|
i = * ( long * ) &y;
|
|
|
|
i = 0x5f3759df - ( i >> 1 );
|
|
|
|
y = * ( float * ) &i;
|
|
|
|
y = y * ( f - ( x * y * y ) );
|
|
|
|
y = y * ( f - ( x * y * y ) );
|
|
|
|
return number * y;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============================================================================
|
|
|
|
|
|
|
|
POSITION TESTING
|
|
|
|
|
|
|
|
===============================================================================
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CM_TestBoxInBrush
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void CM_TestBoxInBrush( traceWork_t *tw, trace_t &trace, cbrush_t *brush ) {
|
|
|
|
int i;
|
|
|
|
cplane_t *plane;
|
|
|
|
float dist;
|
|
|
|
float d1;
|
|
|
|
cbrushside_t *side;
|
|
|
|
float t;
|
|
|
|
vec3_t startp;
|
|
|
|
|
|
|
|
if (!brush->numsides) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// special test for axial
|
|
|
|
if ( tw->bounds[0][0] > brush->bounds[1][0]
|
|
|
|
|| tw->bounds[0][1] > brush->bounds[1][1]
|
|
|
|
|| tw->bounds[0][2] > brush->bounds[1][2]
|
|
|
|
|| tw->bounds[1][0] < brush->bounds[0][0]
|
|
|
|
|| tw->bounds[1][1] < brush->bounds[0][1]
|
|
|
|
|| tw->bounds[1][2] < brush->bounds[0][2]
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( tw->sphere.use ) {
|
|
|
|
// the first six planes are the axial planes, so we only
|
|
|
|
// need to test the remainder
|
|
|
|
for ( i = 6 ; i < brush->numsides ; i++ ) {
|
|
|
|
side = brush->sides + i;
|
|
|
|
|
|
|
|
#ifdef _XBOX
|
|
|
|
plane = &cmg.planes[side->planeNum.GetValue()];
|
|
|
|
#else
|
|
|
|
plane = side->plane;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// adjust the plane distance apropriately for radius
|
|
|
|
dist = plane->dist + tw->sphere.radius;
|
|
|
|
// find the closest point on the capsule to the plane
|
|
|
|
t = DotProduct( plane->normal, tw->sphere.offset );
|
|
|
|
if ( t > 0 )
|
|
|
|
{
|
|
|
|
VectorSubtract( tw->start, tw->sphere.offset, startp );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
VectorAdd( tw->start, tw->sphere.offset, startp );
|
|
|
|
}
|
|
|
|
d1 = DotProduct( startp, plane->normal ) - dist;
|
|
|
|
// if completely in front of face, no intersection
|
|
|
|
if ( d1 > 0 ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// the first six planes are the axial planes, so we only
|
|
|
|
// need to test the remainder
|
|
|
|
for ( i = 6 ; i < brush->numsides ; i++ ) {
|
|
|
|
side = brush->sides + i;
|
|
|
|
|
|
|
|
#ifdef _XBOX
|
|
|
|
plane = &cmg.planes[side->planeNum.GetValue()];
|
|
|
|
#else
|
|
|
|
plane = side->plane;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// adjust the plane distance apropriately for mins/maxs
|
|
|
|
dist = plane->dist - DotProduct( tw->offsets[ plane->signbits ], plane->normal );
|
|
|
|
|
|
|
|
d1 = DotProduct( tw->start, plane->normal ) - dist;
|
|
|
|
|
|
|
|
// if completely in front of face, no intersection
|
|
|
|
if ( d1 > 0 ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// inside this brush
|
|
|
|
trace.startsolid = trace.allsolid = qtrue;
|
|
|
|
trace.fraction = 0;
|
|
|
|
trace.contents = brush->contents;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef _XBOX
|
|
|
|
static int CM_GetSurfaceIndex(int firstLeafSurface)
|
|
|
|
{
|
|
|
|
if(firstLeafSurface > tr.world->nummarksurfaces || firstLeafSurface < 0) {
|
|
|
|
return cmg.leafsurfaces[ firstLeafSurface ] ;
|
|
|
|
} else {
|
|
|
|
return tr.world->marksurfaces[firstLeafSurface] - tr.world->surfaces;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CM_TestInLeaf
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void CM_TestInLeaf( traceWork_t *tw, trace_t &trace, cLeaf_t *leaf, clipMap_t *local )
|
|
|
|
{
|
|
|
|
int k;
|
|
|
|
int brushnum;
|
|
|
|
cbrush_t *b;
|
|
|
|
cPatch_t *patch;
|
|
|
|
|
|
|
|
// test box position against all brushes in the leaf
|
|
|
|
for (k=0 ; k<leaf->numLeafBrushes ; k++) {
|
|
|
|
brushnum = local->leafbrushes[leaf->firstLeafBrush+k];
|
|
|
|
b = &local->brushes[brushnum];
|
|
|
|
if (b->checkcount == local->checkcount) {
|
|
|
|
continue; // already checked this brush in another leaf
|
|
|
|
}
|
|
|
|
b->checkcount = local->checkcount;
|
|
|
|
|
|
|
|
if ( !(b->contents & tw->contents)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef BSPC
|
|
|
|
if (com_terrainPhysics->integer && cmg.landScape && (b->contents & CONTENTS_TERRAIN) )
|
|
|
|
{
|
|
|
|
// Invalidate the checkcount for terrain as the terrain brush has to be processed
|
|
|
|
// many times.
|
|
|
|
b->checkcount--;
|
|
|
|
|
|
|
|
CM_TraceThroughTerrain( tw, trace, b );
|
|
|
|
// If inside a terrain brush don't bother with regular brush collision
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
CM_TestBoxInBrush( tw, trace, b );
|
|
|
|
if ( trace.allsolid ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// test against all patches
|
|
|
|
#ifdef BSPC
|
|
|
|
if (1) {
|
|
|
|
#else
|
|
|
|
if ( !cm_noCurves->integer ) {
|
|
|
|
#endif //BSPC
|
|
|
|
for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) {
|
|
|
|
//#ifdef _XBOX
|
|
|
|
// int index = CM_GetSurfaceIndex(leaf->firstLeafSurface + k);
|
|
|
|
// patch = local->surfaces[ index ];
|
|
|
|
//#else
|
|
|
|
patch = local->surfaces[ local->leafsurfaces[ leaf->firstLeafSurface + k ] ];
|
|
|
|
//#endif
|
|
|
|
if ( !patch ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ( patch->checkcount == local->checkcount ) {
|
|
|
|
continue; // already checked this brush in another leaf
|
|
|
|
}
|
|
|
|
patch->checkcount = local->checkcount;
|
|
|
|
|
|
|
|
if ( !(patch->contents & tw->contents)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( CM_PositionTestInPatchCollide( tw, patch->pc ) ) {
|
|
|
|
trace.startsolid = trace.allsolid = qtrue;
|
|
|
|
trace.fraction = 0;
|
|
|
|
trace.contents = patch->contents;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
CM_TestCapsuleInCapsule
|
|
|
|
|
|
|
|
capsule inside capsule check
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void CM_TestCapsuleInCapsule( traceWork_t *tw, trace_t &trace, clipHandle_t model ) {
|
|
|
|
int i;
|
|
|
|
vec3_t mins, maxs;
|
|
|
|
vec3_t top, bottom;
|
|
|
|
vec3_t p1, p2, tmp;
|
|
|
|
vec3_t offset, symetricSize[2];
|
|
|
|
float radius, halfwidth, halfheight, offs, r;
|
|
|
|
|
|
|
|
CM_ModelBounds(model, mins, maxs);
|
|
|
|
|
|
|
|
VectorAdd(tw->start, tw->sphere.offset, top);
|
|
|
|
VectorSubtract(tw->start, tw->sphere.offset, bottom);
|
|
|
|
for ( i = 0 ; i < 3 ; i++ ) {
|
|
|
|
offset[i] = ( mins[i] + maxs[i] ) * 0.5;
|
|
|
|
symetricSize[0][i] = mins[i] - offset[i];
|
|
|
|
symetricSize[1][i] = maxs[i] - offset[i];
|
|
|
|
}
|
|
|
|
halfwidth = symetricSize[ 1 ][ 0 ];
|
|
|
|
halfheight = symetricSize[ 1 ][ 2 ];
|
|
|
|
radius = ( halfwidth > halfheight ) ? halfheight : halfwidth;
|
|
|
|
offs = halfheight - radius;
|
|
|
|
|
|
|
|
r = Square(tw->sphere.radius + radius);
|
|
|
|
// check if any of the spheres overlap
|
|
|
|
VectorCopy(offset, p1);
|
|
|
|
p1[2] += offs;
|
|
|
|
VectorSubtract(p1, top, tmp);
|
|
|
|
if ( VectorLengthSquared(tmp) < r ) {
|
|
|
|
trace.startsolid = trace.allsolid = qtrue;
|
|
|
|
trace.fraction = 0;
|
|
|
|
}
|
|
|
|
VectorSubtract(p1, bottom, tmp);
|
|
|
|
if ( VectorLengthSquared(tmp) < r ) {
|
|
|
|
trace.startsolid = trace.allsolid = qtrue;
|
|
|
|
trace.fraction = 0;
|
|
|
|
}
|
|
|
|
VectorCopy(offset, p2);
|
|
|
|
p2[2] -= offs;
|
|
|
|
VectorSubtract(p2, top, tmp);
|
|
|
|
if ( VectorLengthSquared(tmp) < r ) {
|
|
|
|
trace.startsolid = trace.allsolid = qtrue;
|
|
|
|
trace.fraction = 0;
|
|
|
|
}
|
|
|
|
VectorSubtract(p2, bottom, tmp);
|
|
|
|
if ( VectorLengthSquared(tmp) < r ) {
|
|
|
|
trace.startsolid = trace.allsolid = qtrue;
|
|
|
|
trace.fraction = 0;
|
|
|
|
}
|
|
|
|
// if between cylinder up and lower bounds
|
|
|
|
if ( (top[2] >= p1[2] && top[2] <= p2[2]) ||
|
|
|
|
(bottom[2] >= p1[2] && bottom[2] <= p2[2]) ) {
|
|
|
|
// 2d coordinates
|
|
|
|
top[2] = p1[2] = 0;
|
|
|
|
// if the cylinders overlap
|
|
|
|
VectorSubtract(top, p1, tmp);
|
|
|
|
if ( VectorLengthSquared(tmp) < r ) {
|
|
|
|
trace.startsolid = trace.allsolid = qtrue;
|
|
|
|
trace.fraction = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
CM_TestBoundingBoxInCapsule
|
|
|
|
|
|
|
|
bounding box inside capsule check
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void CM_TestBoundingBoxInCapsule( traceWork_t *tw, trace_t &trace, clipHandle_t model ) {
|
|
|
|
vec3_t mins, maxs, offset, size[2];
|
|
|
|
clipHandle_t h;
|
|
|
|
cmodel_t *cmod;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
// mins maxs of the capsule
|
|
|
|
CM_ModelBounds(model, mins, maxs);
|
|
|
|
|
|
|
|
// offset for capsule center
|
|
|
|
for ( i = 0 ; i < 3 ; i++ ) {
|
|
|
|
offset[i] = ( mins[i] + maxs[i] ) * 0.5;
|
|
|
|
size[0][i] = mins[i] - offset[i];
|
|
|
|
size[1][i] = maxs[i] - offset[i];
|
|
|
|
tw->start[i] -= offset[i];
|
|
|
|
tw->end[i] -= offset[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
// replace the bounding box with the capsule
|
|
|
|
tw->sphere.use = qtrue;
|
|
|
|
tw->sphere.radius = ( size[1][0] > size[1][2] ) ? size[1][2]: size[1][0];
|
|
|
|
tw->sphere.halfheight = size[1][2];
|
|
|
|
VectorSet( tw->sphere.offset, 0, 0, size[1][2] - tw->sphere.radius );
|
|
|
|
|
|
|
|
// replace the capsule with the bounding box
|
|
|
|
h = CM_TempBoxModel(tw->size[0], tw->size[1], qfalse);
|
|
|
|
// calculate collision
|
|
|
|
cmod = CM_ClipHandleToModel( h );
|
|
|
|
CM_TestInLeaf( tw, trace, &cmod->leaf, &cmg );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
CM_PositionTest
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
#define MAX_POSITION_LEAFS 1024
|
|
|
|
void CM_PositionTest( traceWork_t *tw, trace_t &trace ) {
|
|
|
|
int leafs[MAX_POSITION_LEAFS];
|
|
|
|
int i;
|
|
|
|
leafList_t ll;
|
|
|
|
|
|
|
|
// identify the leafs we are touching
|
|
|
|
VectorAdd( tw->start, tw->size[0], ll.bounds[0] );
|
|
|
|
VectorAdd( tw->start, tw->size[1], ll.bounds[1] );
|
|
|
|
|
|
|
|
for (i=0 ; i<3 ; i++) {
|
|
|
|
ll.bounds[0][i] -= 1;
|
|
|
|
ll.bounds[1][i] += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ll.count = 0;
|
|
|
|
ll.maxcount = MAX_POSITION_LEAFS;
|
|
|
|
ll.list = leafs;
|
|
|
|
ll.storeLeafs = CM_StoreLeafs;
|
|
|
|
ll.lastLeaf = 0;
|
|
|
|
ll.overflowed = qfalse;
|
|
|
|
|
|
|
|
cmg.checkcount++;
|
|
|
|
|
|
|
|
CM_BoxLeafnums_r( &ll, 0 );
|
|
|
|
|
|
|
|
|
|
|
|
cmg.checkcount++;
|
|
|
|
|
|
|
|
// test the contents of the leafs
|
|
|
|
for (i=0 ; i < ll.count ; i++) {
|
|
|
|
CM_TestInLeaf( tw, trace, &cmg.leafs[leafs[i]], &cmg );
|
|
|
|
if ( trace.allsolid ) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============================================================================
|
|
|
|
|
|
|
|
TRACING
|
|
|
|
|
|
|
|
===============================================================================
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CM_TraceThroughPatch
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
|
|
|
|
void CM_TraceThroughPatch( traceWork_t *tw, trace_t &trace, cPatch_t *patch ) {
|
|
|
|
float oldFrac;
|
|
|
|
|
|
|
|
c_patch_traces++;
|
|
|
|
|
|
|
|
oldFrac = trace.fraction;
|
|
|
|
|
|
|
|
CM_TraceThroughPatchCollide( tw, trace, patch->pc );
|
|
|
|
|
|
|
|
if ( trace.fraction < oldFrac ) {
|
|
|
|
trace.surfaceFlags = patch->surfaceFlags;
|
|
|
|
trace.contents = patch->contents;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CM_PlaneCollision
|
|
|
|
|
|
|
|
Returns false for a quick getout
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
|
|
|
|
bool CM_PlaneCollision(traceWork_t *tw, cbrushside_t *side)
|
|
|
|
{
|
|
|
|
float dist, f;
|
|
|
|
float d1, d2;
|
|
|
|
|
|
|
|
#ifdef _XBOX
|
|
|
|
cplane_t *plane = &cmg.planes[side->planeNum.GetValue()];
|
|
|
|
#else
|
|
|
|
cplane_t *plane = side->plane;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// adjust the plane distance apropriately for mins/maxs
|
|
|
|
dist = plane->dist - DotProduct( tw->offsets[ plane->signbits ], plane->normal );
|
|
|
|
|
|
|
|
d1 = DotProduct( tw->start, plane->normal ) - dist;
|
|
|
|
d2 = DotProduct( tw->end, plane->normal ) - dist;
|
|
|
|
|
|
|
|
if (d2 > 0.0f)
|
|
|
|
{
|
|
|
|
// endpoint is not in solid
|
|
|
|
tw->getout = true;
|
|
|
|
}
|
|
|
|
if (d1 > 0.0f)
|
|
|
|
{
|
|
|
|
// startpoint is not in solid
|
|
|
|
tw->startout = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// if completely in front of face, no intersection with the entire brush
|
|
|
|
if ((d1 > 0.0f) && ( (d2 >= SURFACE_CLIP_EPSILON) || (d2 >= d1) ) )
|
|
|
|
{
|
|
|
|
return(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
// if it doesn't cross the plane, the plane isn't relevent
|
|
|
|
if ((d1 <= 0.0f) && (d2 <= 0.0f))
|
|
|
|
{
|
|
|
|
return(true);
|
|
|
|
}
|
|
|
|
// crosses face
|
|
|
|
if (d1 > d2)
|
|
|
|
{ // enter
|
|
|
|
f = (d1 - SURFACE_CLIP_EPSILON);
|
|
|
|
if ( f < 0.0f )
|
|
|
|
{
|
|
|
|
f = 0.0f;
|
|
|
|
if (f > tw->enterFrac)
|
|
|
|
{
|
|
|
|
tw->enterFrac = f;
|
|
|
|
tw->clipplane = plane;
|
|
|
|
tw->leadside = side;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (f > tw->enterFrac * (d1 - d2) )
|
|
|
|
{
|
|
|
|
tw->enterFrac = f / (d1 - d2);
|
|
|
|
tw->clipplane = plane;
|
|
|
|
tw->leadside = side;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{ // leave
|
|
|
|
f = (d1 + SURFACE_CLIP_EPSILON);
|
|
|
|
if ( f < (d1 - d2) )
|
|
|
|
{
|
|
|
|
f = 1.0f;
|
|
|
|
if (f < tw->leaveFrac)
|
|
|
|
{
|
|
|
|
tw->leaveFrac = f;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (f > tw->leaveFrac * (d1 - d2) )
|
|
|
|
{
|
|
|
|
tw->leaveFrac = f / (d1 - d2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CM_TraceThroughBrush
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void CM_TraceThroughBrush( traceWork_t *tw, trace_t &trace, cbrush_t *brush, bool infoOnly )
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
cbrushside_t *side;
|
|
|
|
|
|
|
|
tw->enterFrac = -1.0f;
|
|
|
|
tw->leaveFrac = 1.0f;
|
|
|
|
tw->clipplane = NULL;
|
|
|
|
|
|
|
|
if ( !brush->numsides )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// I'm not sure if test is strictly correct. Are all
|
|
|
|
// bboxes axis aligned? Do I care? It seems to work
|
|
|
|
// good enough...
|
|
|
|
if ( tw->bounds[0][0] > brush->bounds[1][0]
|
|
|
|
|| tw->bounds[0][1] > brush->bounds[1][1]
|
|
|
|
|| tw->bounds[0][2] > brush->bounds[1][2]
|
|
|
|
|| tw->bounds[1][0] < brush->bounds[0][0]
|
|
|
|
|| tw->bounds[1][1] < brush->bounds[0][1]
|
|
|
|
|| tw->bounds[1][2] < brush->bounds[0][2]
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
tw->getout = false;
|
|
|
|
tw->startout = false;
|
|
|
|
tw->leadside = NULL;
|
|
|
|
|
|
|
|
//
|
|
|
|
// compare the trace against all planes of the brush
|
|
|
|
// find the latest time the trace crosses a plane towards the interior
|
|
|
|
// and the earliest time the trace crosses a plane towards the exterior
|
|
|
|
//
|
|
|
|
for (i = 0; i < brush->numsides; i++)
|
|
|
|
{
|
|
|
|
side = brush->sides + i;
|
|
|
|
|
|
|
|
if(!CM_PlaneCollision(tw, side))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// all planes have been checked, and the trace was not
|
|
|
|
// completely outside the brush
|
|
|
|
//
|
|
|
|
if (!tw->startout)
|
|
|
|
{
|
|
|
|
if(!infoOnly)
|
|
|
|
{
|
|
|
|
// original point was inside brush
|
|
|
|
trace.startsolid = qtrue;
|
|
|
|
if (!tw->getout)
|
|
|
|
{
|
|
|
|
trace.allsolid = qtrue;
|
|
|
|
trace.fraction = 0.0f;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
tw->enterFrac = 0.0f;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (tw->enterFrac < tw->leaveFrac)
|
|
|
|
{
|
|
|
|
if ((tw->enterFrac > -1.0f) && (tw->enterFrac < trace.fraction))
|
|
|
|
{
|
|
|
|
if (tw->enterFrac < 0.0f)
|
|
|
|
{
|
|
|
|
tw->enterFrac = 0.0f;
|
|
|
|
}
|
|
|
|
if(!infoOnly)
|
|
|
|
{
|
|
|
|
trace.fraction = tw->enterFrac;
|
|
|
|
trace.plane = *tw->clipplane;
|
|
|
|
trace.surfaceFlags = cmg.shaders[tw->leadside->shaderNum].surfaceFlags;
|
|
|
|
trace.contents = brush->contents;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CM_TraceThroughTerrain
|
|
|
|
|
|
|
|
During this routine the fraction is internal to the brush
|
|
|
|
and converted to a global fraction on exit.
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
|
|
|
|
#ifndef BSPC
|
|
|
|
|
|
|
|
void CM_TraceThroughTerrain( traceWork_t *tw, trace_t &trace, cbrush_t *brush )
|
|
|
|
{
|
|
|
|
CCMLandScape *landscape;
|
|
|
|
vec3_t tBegin, tEnd, tDistance, tStep;
|
|
|
|
vec3_t baseStart;
|
|
|
|
vec3_t baseEnd;
|
|
|
|
int count;
|
|
|
|
int i;
|
|
|
|
float fraction;
|
|
|
|
|
|
|
|
// At this point we know we may be colliding with a terrain brush (and we know we have a valid terrain structure)
|
|
|
|
landscape = (CCMLandScape *)cmg.landScape;
|
|
|
|
|
|
|
|
// Check for absolutely no connection
|
|
|
|
if(!CM_GenericBoxCollide(tw->bounds, landscape->GetBounds()))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// Now we know that at least some part of the trace needs to collide with the terrain
|
|
|
|
// The regular brush collision is handled elsewhere, so advance the ray to an edge in the terrain brush
|
|
|
|
CM_TraceThroughBrush( tw, trace, brush, true );
|
|
|
|
|
|
|
|
// Remember the base entering and leaving fractions
|
|
|
|
tw->baseEnterFrac = tw->enterFrac;
|
|
|
|
tw->baseLeaveFrac = tw->leaveFrac;
|
|
|
|
// Reset to full spread within the brush
|
|
|
|
tw->enterFrac = -1.0f;
|
|
|
|
tw->leaveFrac = 1.0f;
|
|
|
|
|
|
|
|
// Work out the corners of the AABB when the trace first hits the terrain brush and when it leaves
|
|
|
|
VectorAdvance(tw->start, tw->baseEnterFrac, tw->end, tBegin);
|
|
|
|
VectorAdvance(tw->start, tw->baseLeaveFrac, tw->end, tEnd);
|
|
|
|
VectorSubtract(tEnd, tBegin, tDistance);
|
|
|
|
|
|
|
|
// Calculate number of iterations to process
|
|
|
|
count = ceilf(VectorLength(tDistance) / (landscape->GetPatchScalarSize() * TERRAIN_STEP_MAGIC));
|
|
|
|
count = 1;
|
|
|
|
fraction = trace.fraction;
|
|
|
|
VectorScale(tDistance, 1.0f / count, tStep);
|
|
|
|
|
|
|
|
// Save the base start and end vectors
|
|
|
|
VectorCopy ( tw->start, baseStart );
|
|
|
|
VectorCopy ( tw->end, baseEnd );
|
|
|
|
|
|
|
|
// Use the terrain vectors. Start both at the beginning since the
|
|
|
|
// step will be added to the end as the first step of the loop
|
|
|
|
VectorCopy ( tBegin, tw->start );
|
|
|
|
VectorCopy ( tBegin, tw->end );
|
|
|
|
|
|
|
|
// Step thru terrain patches moving on about 1 patch at a time
|
|
|
|
for ( i = 0; i < count; i ++ )
|
|
|
|
{
|
|
|
|
// Add the step to the end
|
|
|
|
VectorAdd(tw->end, tStep, tw->end);
|
|
|
|
|
|
|
|
CM_CalcExtents(tBegin, tw->end, tw, tw->localBounds);
|
|
|
|
|
|
|
|
landscape->PatchCollide(tw, trace, tw->start, tw->end, brush->checkcount);
|
|
|
|
|
|
|
|
// If collision with something closer than water then just stop here
|
|
|
|
if ( trace.fraction < fraction )
|
|
|
|
{
|
|
|
|
// Convert the fraction of this sub tract into the full trace's fraction
|
|
|
|
trace.fraction = i * (1.0f / count) + (1.0f / count) * trace.fraction;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Move the end to the start so the next trace starts
|
|
|
|
// where this one left off
|
|
|
|
VectorCopy(tw->end, tw->start);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Put the original start and end back
|
|
|
|
VectorCopy ( baseStart, tw->start );
|
|
|
|
VectorCopy ( baseEnd, tw->end );
|
|
|
|
|
|
|
|
// Convert to global fraction only if something was hit along the way
|
|
|
|
if ( trace.fraction != 1.0 )
|
|
|
|
{
|
|
|
|
trace.fraction = tw->baseEnterFrac + ((tw->baseLeaveFrac - tw->baseEnterFrac) * trace.fraction);
|
|
|
|
trace.contents = brush->contents;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Collide with any water
|
|
|
|
if ( tw->contents & CONTENTS_WATER )
|
|
|
|
{
|
|
|
|
fraction = landscape->WaterCollide(tw->start, tw->end, trace.fraction);
|
|
|
|
if( fraction < trace.fraction )
|
|
|
|
{
|
|
|
|
VectorSet(trace.plane.normal, 0.0f, 0.0f, 1.0f);
|
|
|
|
trace.contents = landscape->GetWaterContents();
|
|
|
|
trace.fraction = fraction;
|
|
|
|
trace.surfaceFlags = landscape->GetWaterSurfaceFlags();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef TEST_TERRAIN_PHYSICS
|
|
|
|
|
|
|
|
void CM_TraceThroughTerrain( traceWork_t *tw, trace_t &trace, CCMLandScape *landscape)
|
|
|
|
{
|
|
|
|
vec3_t tBegin, tEnd, tDistance, tStep;
|
|
|
|
vec3_t baseStart;
|
|
|
|
vec3_t baseEnd;
|
|
|
|
int count;
|
|
|
|
int i;
|
|
|
|
float fraction;
|
|
|
|
|
|
|
|
// Check for absolutely no connection
|
|
|
|
if(!CM_GenericBoxCollide(tw->bounds, landscape->GetBounds()))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
tw->enterFrac = 0.0f;
|
|
|
|
tw->leaveFrac = 1.0f;
|
|
|
|
tw->clipplane = NULL;
|
|
|
|
tw->getout = false;
|
|
|
|
tw->startout = false;
|
|
|
|
tw->leadside = NULL;
|
|
|
|
|
|
|
|
// Remember the base entering and leaving fractions
|
|
|
|
tw->baseEnterFrac = tw->enterFrac;
|
|
|
|
tw->baseLeaveFrac = tw->leaveFrac;
|
|
|
|
// Reset to full spread within the brush
|
|
|
|
tw->enterFrac = -1.0f;
|
|
|
|
tw->leaveFrac = 1.0f;
|
|
|
|
|
|
|
|
// Work out the corners of the AABB when the trace first hits the terrain brush and when it leaves
|
|
|
|
VectorAdvance(tw->start, tw->baseEnterFrac, tw->end, tBegin);
|
|
|
|
VectorAdvance(tw->start, tw->baseLeaveFrac, tw->end, tEnd);
|
|
|
|
VectorSubtract(tEnd, tBegin, tDistance);
|
|
|
|
|
|
|
|
// Calculate number of iterations to process
|
|
|
|
count = ceilf(VectorLength(tDistance) / (landscape->GetPatchScalarSize() * TERRAIN_STEP_MAGIC));
|
|
|
|
count = 1;
|
|
|
|
fraction = trace.fraction;
|
|
|
|
VectorScale(tDistance, 1.0f / count, tStep);
|
|
|
|
|
|
|
|
// Save the base start and end vectors
|
|
|
|
VectorCopy ( tw->start, baseStart );
|
|
|
|
VectorCopy ( tw->end, baseEnd );
|
|
|
|
|
|
|
|
// Use the terrain vectors. Start both at the beginning since the
|
|
|
|
// step will be added to the end as the first step of the loop
|
|
|
|
VectorCopy ( tBegin, tw->start );
|
|
|
|
VectorCopy ( tBegin, tw->end );
|
|
|
|
|
|
|
|
// Step thru terrain patches moving on about 1 patch at a time
|
|
|
|
for ( i = 0; i < count; i ++ )
|
|
|
|
{
|
|
|
|
// Add the step to the end
|
|
|
|
VectorAdd(tw->end, tStep, tw->end);
|
|
|
|
|
|
|
|
CM_CalcExtents(tBegin, tw->end, tw, tw->localBounds);
|
|
|
|
|
|
|
|
landscape->PatchCollide(tw, trace, tw->start, tw->end, cmg.checkcount);
|
|
|
|
|
|
|
|
// If collision with something closer than water then just stop here
|
|
|
|
if ( trace.fraction < fraction )
|
|
|
|
{
|
|
|
|
// Convert the fraction of this sub tract into the full trace's fraction
|
|
|
|
trace.fraction = i * (1.0f / count) + (1.0f / count) * trace.fraction;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Move the end to the start so the next trace starts
|
|
|
|
// where this one left off
|
|
|
|
VectorCopy(tw->end, tw->start);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Put the original start and end back
|
|
|
|
VectorCopy ( baseStart, tw->start );
|
|
|
|
VectorCopy ( baseEnd, tw->end );
|
|
|
|
|
|
|
|
// Convert to global fraction only if something was hit along the way
|
|
|
|
if ( trace.fraction != 1.0 )
|
|
|
|
{
|
|
|
|
// trace.fraction = tw->baseEnterFrac + ((tw->baseLeaveFrac - tw->baseEnterFrac) * trace.fraction);
|
|
|
|
trace.contents = CONTENTS_TERRAIN | CONTENTS_OUTSIDE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Collide with any water
|
|
|
|
if ( tw->contents & CONTENTS_WATER )
|
|
|
|
{
|
|
|
|
fraction = landscape->WaterCollide(tw->start, tw->end, trace.fraction);
|
|
|
|
if( fraction < trace.fraction )
|
|
|
|
{
|
|
|
|
VectorSet(trace.plane.normal, 0.0f, 0.0f, 1.0f);
|
|
|
|
trace.contents = landscape->GetWaterContents();
|
|
|
|
trace.fraction = fraction;
|
|
|
|
trace.surfaceFlags = landscape->GetWaterSurfaceFlags();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // #ifdef TEST_TERRAIN_PHYSICS
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CM_PatchCollide
|
|
|
|
|
|
|
|
By the time we get here we know the AABB is within the patch AABB ie there is a chance of collision
|
|
|
|
The collision data is made up of bounds, 2 triangle planes
|
|
|
|
There is an BB check for the terxel check to see if it is worth checking the planes.
|
|
|
|
Collide with both triangles to find the shortest fraction
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
|
|
|
|
void CM_HandlePatchCollision(struct traceWork_s *tw, trace_t &trace, const vec3_t tStart, const vec3_t tEnd, CCMPatch *patch, int checkcount)
|
|
|
|
{
|
|
|
|
int numBrushes, i;
|
|
|
|
cbrush_t *brush;
|
|
|
|
|
|
|
|
// Get the collision data
|
|
|
|
brush = patch->GetCollisionData();
|
|
|
|
numBrushes = patch->GetNumBrushes();
|
|
|
|
|
|
|
|
for(i = 0; i < numBrushes; i++, brush++)
|
|
|
|
{
|
|
|
|
if(brush->checkcount == checkcount)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generic collision of terxel bounds to line segment bounds
|
|
|
|
if(!CM_GenericBoxCollide(brush->bounds, tw->localBounds))
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
brush->checkcount = checkcount;
|
|
|
|
|
|
|
|
CM_TraceThroughBrush(tw, trace, brush, false );
|
|
|
|
if (trace.fraction <= 0.0)
|
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CM_GenericBoxCollide
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
|
|
|
|
bool CM_GenericBoxCollide(const vec3pair_t abounds, const vec3pair_t bbounds)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
// Check for completely no intersection
|
|
|
|
for(i = 0; i < 3; i++)
|
|
|
|
{
|
|
|
|
if(abounds[1][i] < bbounds[0][i])
|
|
|
|
{
|
|
|
|
return(false);
|
|
|
|
}
|
|
|
|
if(abounds[0][i] > bbounds[1][i])
|
|
|
|
{
|
|
|
|
return(false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CM_TraceThroughLeaf
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void CM_TraceThroughLeaf( traceWork_t *tw, trace_t &trace, clipMap_t *local, cLeaf_t *leaf ) {
|
|
|
|
int k;
|
|
|
|
int brushnum;
|
|
|
|
cbrush_t *b;
|
|
|
|
cPatch_t *patch;
|
|
|
|
|
|
|
|
// trace line against all brushes in the leaf
|
|
|
|
for ( k = 0 ; k < leaf->numLeafBrushes ; k++ ) {
|
|
|
|
brushnum = local->leafbrushes[leaf->firstLeafBrush+k];
|
|
|
|
|
|
|
|
b = &local->brushes[brushnum];
|
|
|
|
if ( b->checkcount == local->checkcount ) {
|
|
|
|
continue; // already checked this brush in another leaf
|
|
|
|
}
|
|
|
|
b->checkcount = local->checkcount;
|
|
|
|
|
|
|
|
if ( !(b->contents & tw->contents) ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef BSPC
|
|
|
|
if (com_terrainPhysics->integer && cmg.landScape && (b->contents & CONTENTS_TERRAIN) )
|
|
|
|
{
|
|
|
|
// Invalidate the checkcount for terrain as the terrain brush has to be processed
|
|
|
|
// many times.
|
|
|
|
b->checkcount--;
|
|
|
|
|
|
|
|
CM_TraceThroughTerrain( tw, trace, b );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
{
|
|
|
|
CM_TraceThroughBrush( tw, trace, b, false );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !trace.fraction ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// trace line against all patches in the leaf
|
|
|
|
#ifdef BSPC
|
|
|
|
if (1) {
|
|
|
|
#else
|
|
|
|
if ( !cm_noCurves->integer ) {
|
|
|
|
#endif
|
|
|
|
for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) {
|
|
|
|
//#ifdef _XBOX
|
|
|
|
// int index = CM_GetSurfaceIndex(leaf->firstLeafSurface + k);
|
|
|
|
// patch = local->surfaces[ index ];
|
|
|
|
//#else
|
|
|
|
patch = local->surfaces[ local->leafsurfaces[ leaf->firstLeafSurface + k ] ];
|
|
|
|
//#endif
|
|
|
|
if ( !patch ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ( patch->checkcount == local->checkcount ) {
|
|
|
|
continue; // already checked this patch in another leaf
|
|
|
|
}
|
|
|
|
patch->checkcount = local->checkcount;
|
|
|
|
|
|
|
|
if ( !(patch->contents & tw->contents) ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
CM_TraceThroughPatch( tw, trace, patch );
|
|
|
|
if ( !trace.fraction ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#define RADIUS_EPSILON 1.0f
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CM_TraceThroughSphere
|
|
|
|
|
|
|
|
get the first intersection of the ray with the sphere
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void CM_TraceThroughSphere( traceWork_t *tw, trace_t &trace, vec3_t origin, float radius, vec3_t start, vec3_t end ) {
|
|
|
|
float l1, l2, length, scale, fraction;
|
|
|
|
float a, b, c, d, sqrtd;
|
|
|
|
vec3_t v1, dir, intersection;
|
|
|
|
|
|
|
|
// if inside the sphere
|
|
|
|
VectorSubtract(start, origin, dir);
|
|
|
|
l1 = VectorLengthSquared(dir);
|
|
|
|
if (l1 < Square(radius)) {
|
|
|
|
trace.fraction = 0;
|
|
|
|
trace.startsolid = qtrue;
|
|
|
|
// test for allsolid
|
|
|
|
VectorSubtract(end, origin, dir);
|
|
|
|
l1 = VectorLengthSquared(dir);
|
|
|
|
if (l1 < Square(radius)) {
|
|
|
|
trace.allsolid = qtrue;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
//
|
|
|
|
VectorSubtract(end, start, dir);
|
|
|
|
length = VectorNormalize(dir);
|
|
|
|
//
|
|
|
|
l1 = CM_DistanceFromLineSquared(origin, start, end, dir);
|
|
|
|
VectorSubtract(end, origin, v1);
|
|
|
|
l2 = VectorLengthSquared(v1);
|
|
|
|
// if no intersection with the sphere and the end point is at least an epsilon away
|
|
|
|
if (l1 >= Square(radius) && l2 > Square(radius+SURFACE_CLIP_EPSILON)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
//
|
|
|
|
// | origin - (start + t * dir) | = radius
|
|
|
|
// a = dir[0]^2 + dir[1]^2 + dir[2]^2;
|
|
|
|
// b = 2 * (dir[0] * (start[0] - origin[0]) + dir[1] * (start[1] - origin[1]) + dir[2] * (start[2] - origin[2]));
|
|
|
|
// c = (start[0] - origin[0])^2 + (start[1] - origin[1])^2 + (start[2] - origin[2])^2 - radius^2;
|
|
|
|
//
|
|
|
|
VectorSubtract(start, origin, v1);
|
|
|
|
// dir is normalized so a = 1
|
|
|
|
a = 1.0f;//dir[0] * dir[0] + dir[1] * dir[1] + dir[2] * dir[2];
|
|
|
|
b = 2.0f * (dir[0] * v1[0] + dir[1] * v1[1] + dir[2] * v1[2]);
|
|
|
|
c = v1[0] * v1[0] + v1[1] * v1[1] + v1[2] * v1[2] - (radius+RADIUS_EPSILON) * (radius+RADIUS_EPSILON);
|
|
|
|
|
|
|
|
d = b * b - 4.0f * c;// * a;
|
|
|
|
if (d > 0) {
|
|
|
|
sqrtd = SquareRootFloat(d);
|
|
|
|
// = (- b + sqrtd) * 0.5f; // / (2.0f * a);
|
|
|
|
fraction = (- b - sqrtd) * 0.5f; // / (2.0f * a);
|
|
|
|
//
|
|
|
|
if (fraction < 0) {
|
|
|
|
fraction = 0;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
fraction /= length;
|
|
|
|
}
|
|
|
|
if ( fraction < trace.fraction ) {
|
|
|
|
trace.fraction = fraction;
|
|
|
|
VectorSubtract(end, start, dir);
|
|
|
|
VectorMA(start, fraction, dir, intersection);
|
|
|
|
VectorSubtract(intersection, origin, dir);
|
|
|
|
#ifdef CAPSULE_DEBUG
|
|
|
|
l2 = VectorLength(dir);
|
|
|
|
if (l2 < radius) {
|
|
|
|
int bah = 1;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
scale = 1 / (radius+RADIUS_EPSILON);
|
|
|
|
VectorScale(dir, scale, dir);
|
|
|
|
VectorCopy(dir, trace.plane.normal);
|
|
|
|
VectorAdd( tw->modelOrigin, intersection, intersection);
|
|
|
|
trace.plane.dist = DotProduct(trace.plane.normal, intersection);
|
|
|
|
trace.contents = CONTENTS_BODY;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (d == 0) {
|
|
|
|
//t1 = (- b ) / 2;
|
|
|
|
// slide along the sphere
|
|
|
|
}
|
|
|
|
// no intersection at all
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CM_TraceThroughVerticalCylinder
|
|
|
|
|
|
|
|
get the first intersection of the ray with the cylinder
|
|
|
|
the cylinder extends halfheight above and below the origin
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void CM_TraceThroughVerticalCylinder( traceWork_t *tw, trace_t &trace, vec3_t origin, float radius, float halfheight, vec3_t start, vec3_t end) {
|
|
|
|
float length, scale, fraction, l1, l2;
|
|
|
|
float a, b, c, d, sqrtd;
|
|
|
|
vec3_t v1, dir, start2d, end2d, org2d, intersection;
|
|
|
|
|
|
|
|
// 2d coordinates
|
|
|
|
VectorSet(start2d, start[0], start[1], 0);
|
|
|
|
VectorSet(end2d, end[0], end[1], 0);
|
|
|
|
VectorSet(org2d, origin[0], origin[1], 0);
|
|
|
|
// if between lower and upper cylinder bounds
|
|
|
|
if (start[2] <= origin[2] + halfheight &&
|
|
|
|
start[2] >= origin[2] - halfheight) {
|
|
|
|
// if inside the cylinder
|
|
|
|
VectorSubtract(start2d, org2d, dir);
|
|
|
|
l1 = VectorLengthSquared(dir);
|
|
|
|
if (l1 < Square(radius)) {
|
|
|
|
trace.fraction = 0;
|
|
|
|
trace.startsolid = qtrue;
|
|
|
|
VectorSubtract(end2d, org2d, dir);
|
|
|
|
l1 = VectorLengthSquared(dir);
|
|
|
|
if (l1 < Square(radius)) {
|
|
|
|
trace.allsolid = qtrue;
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
|
|
VectorSubtract(end2d, start2d, dir);
|
|
|
|
length = VectorNormalize(dir);
|
|
|
|
//
|
|
|
|
l1 = CM_DistanceFromLineSquared(org2d, start2d, end2d, dir);
|
|
|
|
VectorSubtract(end2d, org2d, v1);
|
|
|
|
l2 = VectorLengthSquared(v1);
|
|
|
|
// if no intersection with the cylinder and the end point is at least an epsilon away
|
|
|
|
if (l1 >= Square(radius) && l2 > Square(radius+SURFACE_CLIP_EPSILON)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// (start[0] - origin[0] - t * dir[0]) ^ 2 + (start[1] - origin[1] - t * dir[1]) ^ 2 = radius ^ 2
|
|
|
|
// (v1[0] + t * dir[0]) ^ 2 + (v1[1] + t * dir[1]) ^ 2 = radius ^ 2;
|
|
|
|
// v1[0] ^ 2 + 2 * v1[0] * t * dir[0] + (t * dir[0]) ^ 2 +
|
|
|
|
// v1[1] ^ 2 + 2 * v1[1] * t * dir[1] + (t * dir[1]) ^ 2 = radius ^ 2
|
|
|
|
// t ^ 2 * (dir[0] ^ 2 + dir[1] ^ 2) + t * (2 * v1[0] * dir[0] + 2 * v1[1] * dir[1]) +
|
|
|
|
// v1[0] ^ 2 + v1[1] ^ 2 - radius ^ 2 = 0
|
|
|
|
//
|
|
|
|
VectorSubtract(start, origin, v1);
|
|
|
|
// dir is normalized so we can use a = 1
|
|
|
|
a = 1.0f;// * (dir[0] * dir[0] + dir[1] * dir[1]);
|
|
|
|
b = 2.0f * (v1[0] * dir[0] + v1[1] * dir[1]);
|
|
|
|
c = v1[0] * v1[0] + v1[1] * v1[1] - (radius+RADIUS_EPSILON) * (radius+RADIUS_EPSILON);
|
|
|
|
|
|
|
|
d = b * b - 4.0f * c;// * a;
|
|
|
|
if (d > 0) {
|
|
|
|
sqrtd = SquareRootFloat(d);
|
|
|
|
// = (- b + sqrtd) * 0.5f;// / (2.0f * a);
|
|
|
|
fraction = (- b - sqrtd) * 0.5f;// / (2.0f * a);
|
|
|
|
//
|
|
|
|
if (fraction < 0) {
|
|
|
|
fraction = 0;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
fraction /= length;
|
|
|
|
}
|
|
|
|
if ( fraction < trace.fraction ) {
|
|
|
|
VectorSubtract(end, start, dir);
|
|
|
|
VectorMA(start, fraction, dir, intersection);
|
|
|
|
// if the intersection is between the cylinder lower and upper bound
|
|
|
|
if (intersection[2] <= origin[2] + halfheight &&
|
|
|
|
intersection[2] >= origin[2] - halfheight) {
|
|
|
|
//
|
|
|
|
trace.fraction = fraction;
|
|
|
|
VectorSubtract(intersection, origin, dir);
|
|
|
|
dir[2] = 0;
|
|
|
|
#ifdef CAPSULE_DEBUG
|
|
|
|
l2 = VectorLength(dir);
|
|
|
|
if (l2 <= radius) {
|
|
|
|
int bah = 1;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
scale = 1 / (radius+RADIUS_EPSILON);
|
|
|
|
VectorScale(dir, scale, dir);
|
|
|
|
VectorCopy(dir, trace.plane.normal);
|
|
|
|
VectorAdd( tw->modelOrigin, intersection, intersection);
|
|
|
|
trace.plane.dist = DotProduct(trace.plane.normal, intersection);
|
|
|
|
trace.contents = CONTENTS_BODY;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (d == 0) {
|
|
|
|
//t[0] = (- b ) / 2 * a;
|
|
|
|
// slide along the cylinder
|
|
|
|
}
|
|
|
|
// no intersection at all
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CM_TraceCapsuleThroughCapsule
|
|
|
|
|
|
|
|
capsule vs. capsule collision (not rotated)
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void CM_TraceCapsuleThroughCapsule( traceWork_t *tw, trace_t &trace, clipHandle_t model ) {
|
|
|
|
int i;
|
|
|
|
vec3_t mins, maxs;
|
|
|
|
vec3_t top, bottom, starttop, startbottom, endtop, endbottom;
|
|
|
|
vec3_t offset, symetricSize[2];
|
|
|
|
float radius, halfwidth, halfheight, offs, h;
|
|
|
|
|
|
|
|
CM_ModelBounds(model, mins, maxs);
|
|
|
|
// test trace bounds vs. capsule bounds
|
|
|
|
if ( tw->bounds[0][0] > maxs[0] + RADIUS_EPSILON
|
|
|
|
|| tw->bounds[0][1] > maxs[1] + RADIUS_EPSILON
|
|
|
|
|| tw->bounds[0][2] > maxs[2] + RADIUS_EPSILON
|
|
|
|
|| tw->bounds[1][0] < mins[0] - RADIUS_EPSILON
|
|
|
|
|| tw->bounds[1][1] < mins[1] - RADIUS_EPSILON
|
|
|
|
|| tw->bounds[1][2] < mins[2] - RADIUS_EPSILON
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// top origin and bottom origin of each sphere at start and end of trace
|
|
|
|
VectorAdd(tw->start, tw->sphere.offset, starttop);
|
|
|
|
VectorSubtract(tw->start, tw->sphere.offset, startbottom);
|
|
|
|
VectorAdd(tw->end, tw->sphere.offset, endtop);
|
|
|
|
VectorSubtract(tw->end, tw->sphere.offset, endbottom);
|
|
|
|
|
|
|
|
// calculate top and bottom of the capsule spheres to collide with
|
|
|
|
for ( i = 0 ; i < 3 ; i++ ) {
|
|
|
|
offset[i] = ( mins[i] + maxs[i] ) * 0.5;
|
|
|
|
symetricSize[0][i] = mins[i] - offset[i];
|
|
|
|
symetricSize[1][i] = maxs[i] - offset[i];
|
|
|
|
}
|
|
|
|
halfwidth = symetricSize[ 1 ][ 0 ];
|
|
|
|
halfheight = symetricSize[ 1 ][ 2 ];
|
|
|
|
radius = ( halfwidth > halfheight ) ? halfheight : halfwidth;
|
|
|
|
offs = halfheight - radius;
|
|
|
|
VectorCopy(offset, top);
|
|
|
|
top[2] += offs;
|
|
|
|
VectorCopy(offset, bottom);
|
|
|
|
bottom[2] -= offs;
|
|
|
|
// expand radius of spheres
|
|
|
|
radius += tw->sphere.radius;
|
|
|
|
// if there is horizontal movement
|
|
|
|
if ( tw->start[0] != tw->end[0] || tw->start[1] != tw->end[1] ) {
|
|
|
|
// height of the expanded cylinder is the height of both cylinders minus the radius of both spheres
|
|
|
|
h = halfheight + tw->sphere.halfheight - radius;
|
|
|
|
// if the cylinder has a height
|
|
|
|
if ( h > 0 ) {
|
|
|
|
// test for collisions between the cylinders
|
|
|
|
CM_TraceThroughVerticalCylinder(tw, trace, offset, radius, h, tw->start, tw->end);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// test for collision between the spheres
|
|
|
|
CM_TraceThroughSphere(tw, trace, top, radius, startbottom, endbottom);
|
|
|
|
CM_TraceThroughSphere(tw, trace, bottom, radius, starttop, endtop);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CM_TraceBoundingBoxThroughCapsule
|
|
|
|
|
|
|
|
bounding box vs. capsule collision
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void CM_TraceBoundingBoxThroughCapsule( traceWork_t *tw, trace_t &trace, clipHandle_t model ) {
|
|
|
|
vec3_t mins, maxs, offset, size[2];
|
|
|
|
clipHandle_t h;
|
|
|
|
cmodel_t *cmod;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
// mins maxs of the capsule
|
|
|
|
CM_ModelBounds(model, mins, maxs);
|
|
|
|
|
|
|
|
// offset for capsule center
|
|
|
|
for ( i = 0 ; i < 3 ; i++ ) {
|
|
|
|
offset[i] = ( mins[i] + maxs[i] ) * 0.5;
|
|
|
|
size[0][i] = mins[i] - offset[i];
|
|
|
|
size[1][i] = maxs[i] - offset[i];
|
|
|
|
tw->start[i] -= offset[i];
|
|
|
|
tw->end[i] -= offset[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
// replace the bounding box with the capsule
|
|
|
|
tw->sphere.use = qtrue;
|
|
|
|
tw->sphere.radius = ( size[1][0] > size[1][2] ) ? size[1][2]: size[1][0];
|
|
|
|
tw->sphere.halfheight = size[1][2];
|
|
|
|
VectorSet( tw->sphere.offset, 0, 0, size[1][2] - tw->sphere.radius );
|
|
|
|
|
|
|
|
// replace the capsule with the bounding box
|
|
|
|
h = CM_TempBoxModel(tw->size[0], tw->size[1], qfalse);
|
|
|
|
// calculate collision
|
|
|
|
cmod = CM_ClipHandleToModel( h );
|
|
|
|
CM_TraceThroughLeaf( tw, trace, &cmg, &cmod->leaf );
|
|
|
|
}
|
|
|
|
|
|
|
|
//=========================================================================================
|
|
|
|
|
|
|
|
/*
|
|
|
|
================
|
|
|
|
CM_TraceToLeaf
|
|
|
|
================
|
|
|
|
*/
|
|
|
|
void CM_TraceToLeaf( traceWork_t *tw, trace_t &trace, cLeaf_t *leaf, clipMap_t *local )
|
|
|
|
{
|
|
|
|
int k;
|
|
|
|
int brushnum;
|
|
|
|
cbrush_t *b;
|
|
|
|
cPatch_t *patch;
|
|
|
|
|
|
|
|
// trace line against all brushes in the leaf
|
|
|
|
for ( k = 0 ; k < leaf->numLeafBrushes ; k++ )
|
|
|
|
{
|
|
|
|
brushnum = local->leafbrushes[leaf->firstLeafBrush + k];
|
|
|
|
|
|
|
|
b = &local->brushes[brushnum];
|
|
|
|
if ( b->checkcount == local->checkcount )
|
|
|
|
{
|
|
|
|
continue; // already checked this brush in another leaf
|
|
|
|
}
|
|
|
|
b->checkcount = local->checkcount;
|
|
|
|
|
|
|
|
if ( !(b->contents & tw->contents) )
|
|
|
|
{
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef BSPC
|
|
|
|
if ( com_terrainPhysics->integer && cmg.landScape && (b->contents & CONTENTS_TERRAIN) )
|
|
|
|
{
|
|
|
|
// Invalidate the checkcount for terrain as the terrain brush has to be processed
|
|
|
|
// many times.
|
|
|
|
b->checkcount--;
|
|
|
|
|
|
|
|
CM_TraceThroughTerrain( tw, trace, b );
|
|
|
|
// If inside a terrain brush don't bother with regular brush collision
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
CM_TraceThroughBrush( tw, trace, b, false);
|
|
|
|
if ( !trace.fraction )
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// trace line against all patches in the leaf
|
|
|
|
#ifdef BSPC
|
|
|
|
if (1) {
|
|
|
|
#else
|
|
|
|
if ( !cm_noCurves->integer ) {
|
|
|
|
#endif
|
|
|
|
for ( k = 0 ; k < leaf->numLeafSurfaces ; k++ ) {
|
|
|
|
patch = local->surfaces[ local->leafsurfaces[ leaf->firstLeafSurface + k ] ];
|
|
|
|
if ( !patch ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if ( patch->checkcount == local->checkcount ) {
|
|
|
|
continue; // already checked this patch in another leaf
|
|
|
|
}
|
|
|
|
patch->checkcount = local->checkcount;
|
|
|
|
|
|
|
|
if ( !(patch->contents & tw->contents) ) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
CM_TraceThroughPatch( tw, trace, patch );
|
|
|
|
if ( !trace.fraction ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
CM_TraceThroughTree
|
|
|
|
|
|
|
|
Traverse all the contacted leafs from the start to the end position.
|
|
|
|
If the trace is a point, they will be exactly in order, but for larger
|
|
|
|
trace volumes it is possible to hit something in a later leaf with
|
|
|
|
a smaller intercept fraction.
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void CM_TraceThroughTree( traceWork_t *tw, trace_t &trace, clipMap_t *local, int num, float p1f, float p2f, vec3_t p1, vec3_t p2) {
|
|
|
|
cNode_t *node;
|
|
|
|
cplane_t *plane;
|
|
|
|
float t1, t2, offset;
|
|
|
|
float frac, frac2;
|
|
|
|
float idist;
|
|
|
|
vec3_t mid;
|
|
|
|
int side;
|
|
|
|
float midf;
|
|
|
|
|
|
|
|
if (trace.fraction <= p1f) {
|
|
|
|
return; // already hit something nearer
|
|
|
|
}
|
|
|
|
|
|
|
|
// if < 0, we are in a leaf node
|
|
|
|
if (num < 0) {
|
|
|
|
CM_TraceThroughLeaf( tw, trace, local, &local->leafs[-1-num] );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// find the point distances to the seperating plane
|
|
|
|
// and the offset for the size of the box
|
|
|
|
//
|
|
|
|
node = local->nodes + num;
|
|
|
|
#ifdef _XBOX
|
|
|
|
plane = cmg.planes + node->planeNum;//tr.world->nodes[num].planeNum;
|
|
|
|
#else
|
|
|
|
plane = node->plane;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// adjust the plane distance apropriately for mins/maxs
|
|
|
|
if ( plane->type < 3 ) {
|
|
|
|
t1 = p1[plane->type] - plane->dist;
|
|
|
|
t2 = p2[plane->type] - plane->dist;
|
|
|
|
offset = tw->extents[plane->type];
|
|
|
|
} else {
|
|
|
|
t1 = DotProduct (plane->normal, p1) - plane->dist;
|
|
|
|
t2 = DotProduct (plane->normal, p2) - plane->dist;
|
|
|
|
if ( tw->isPoint ) {
|
|
|
|
offset = 0;
|
|
|
|
} else {
|
|
|
|
#if 0 // bk010201 - DEAD
|
|
|
|
// an axial brush right behind a slanted bsp plane
|
|
|
|
// will poke through when expanded, so adjust
|
|
|
|
// by sqrt(3)
|
|
|
|
offset = fabs(tw->extents[0]*plane->normal[0]) +
|
|
|
|
fabs(tw->extents[1]*plane->normal[1]) +
|
|
|
|
fabs(tw->extents[2]*plane->normal[2]);
|
|
|
|
|
|
|
|
offset *= 2;
|
|
|
|
offset = tw->maxOffset;
|
|
|
|
#endif
|
|
|
|
// this is silly
|
|
|
|
offset = 2048;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// see which sides we need to consider
|
|
|
|
if ( t1 >= offset + 1 && t2 >= offset + 1 ) {
|
|
|
|
CM_TraceThroughTree( tw, trace, local, node->children[0], p1f, p2f, p1, p2 );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if ( t1 < -offset - 1 && t2 < -offset - 1 ) {
|
|
|
|
CM_TraceThroughTree( tw, trace, local, node->children[1], p1f, p2f, p1, p2 );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// put the crosspoint SURFACE_CLIP_EPSILON pixels on the near side
|
|
|
|
if ( t1 < t2 ) {
|
|
|
|
idist = 1.0/(t1-t2);
|
|
|
|
side = 1;
|
|
|
|
frac2 = (t1 + offset + SURFACE_CLIP_EPSILON)*idist;
|
|
|
|
frac = (t1 - offset + SURFACE_CLIP_EPSILON)*idist;
|
|
|
|
} else if (t1 > t2) {
|
|
|
|
idist = 1.0/(t1-t2);
|
|
|
|
side = 0;
|
|
|
|
frac2 = (t1 - offset - SURFACE_CLIP_EPSILON)*idist;
|
|
|
|
frac = (t1 + offset + SURFACE_CLIP_EPSILON)*idist;
|
|
|
|
} else {
|
|
|
|
side = 0;
|
|
|
|
frac = 1;
|
|
|
|
frac2 = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// move up to the node
|
|
|
|
if ( frac < 0 ) {
|
|
|
|
frac = 0;
|
|
|
|
}
|
|
|
|
if ( frac > 1 ) {
|
|
|
|
frac = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
midf = p1f + (p2f - p1f)*frac;
|
|
|
|
|
|
|
|
mid[0] = p1[0] + frac*(p2[0] - p1[0]);
|
|
|
|
mid[1] = p1[1] + frac*(p2[1] - p1[1]);
|
|
|
|
mid[2] = p1[2] + frac*(p2[2] - p1[2]);
|
|
|
|
|
|
|
|
CM_TraceThroughTree( tw, trace, local, node->children[side], p1f, midf, p1, mid );
|
|
|
|
|
|
|
|
|
|
|
|
// go past the node
|
|
|
|
if ( frac2 < 0 ) {
|
|
|
|
frac2 = 0;
|
|
|
|
}
|
|
|
|
if ( frac2 > 1 ) {
|
|
|
|
frac2 = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
midf = p1f + (p2f - p1f)*frac2;
|
|
|
|
|
|
|
|
mid[0] = p1[0] + frac2*(p2[0] - p1[0]);
|
|
|
|
mid[1] = p1[1] + frac2*(p2[1] - p1[1]);
|
|
|
|
mid[2] = p1[2] + frac2*(p2[2] - p1[2]);
|
|
|
|
|
|
|
|
CM_TraceThroughTree( tw, trace, local, node->children[side^1], midf, p2f, mid, p2 );
|
|
|
|
}
|
|
|
|
|
|
|
|
void CM_CalcExtents(const vec3_t start, const vec3_t end, const traceWork_t *tw, vec3pair_t bounds)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for ( i = 0 ; i < 3 ; i++ )
|
|
|
|
{
|
|
|
|
if ( start[i] < end[i] )
|
|
|
|
{
|
|
|
|
bounds[0][i] = start[i] + tw->size[0][i];
|
|
|
|
bounds[1][i] = end[i] + tw->size[1][i];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
bounds[0][i] = end[i] + tw->size[0][i];
|
|
|
|
bounds[1][i] = start[i] + tw->size[1][i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//======================================================================
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
CM_Trace
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void CM_Trace( trace_t *trace, const vec3_t start, const vec3_t end,
|
|
|
|
const vec3_t mins, const vec3_t maxs,
|
|
|
|
clipHandle_t model, const vec3_t origin, int brushmask, int capsule, sphere_t *sphere ) {
|
|
|
|
int i;
|
|
|
|
traceWork_t tw;
|
|
|
|
vec3_t offset;
|
|
|
|
cmodel_t *cmod;
|
|
|
|
clipMap_t *local = 0;
|
|
|
|
|
|
|
|
cmod = CM_ClipHandleToModel( model, &local );
|
|
|
|
|
|
|
|
local->checkcount++; // for multi-check avoidance
|
|
|
|
|
|
|
|
c_traces++; // for statistics, may be zeroed
|
|
|
|
|
|
|
|
// fill in a default trace
|
|
|
|
Com_Memset( &tw, 0, sizeof(tw) );
|
|
|
|
memset(trace, 0, sizeof(*trace));
|
|
|
|
trace->fraction = 1; // assume it goes the entire distance until shown otherwise
|
|
|
|
VectorCopy(origin, tw.modelOrigin);
|
|
|
|
|
|
|
|
if (!local->numNodes) {
|
|
|
|
return; // map not loaded, shouldn't happen
|
|
|
|
}
|
|
|
|
|
|
|
|
// allow NULL to be passed in for 0,0,0
|
|
|
|
if ( !mins ) {
|
|
|
|
mins = vec3_origin;
|
|
|
|
}
|
|
|
|
if ( !maxs ) {
|
|
|
|
maxs = vec3_origin;
|
|
|
|
}
|
|
|
|
|
|
|
|
// set basic parms
|
|
|
|
tw.contents = brushmask;
|
|
|
|
|
|
|
|
// adjust so that mins and maxs are always symetric, which
|
|
|
|
// avoids some complications with plane expanding of rotated
|
|
|
|
// bmodels
|
|
|
|
for ( i = 0 ; i < 3 ; i++ ) {
|
|
|
|
offset[i] = ( mins[i] + maxs[i] ) * 0.5;
|
|
|
|
tw.size[0][i] = mins[i] - offset[i];
|
|
|
|
tw.size[1][i] = maxs[i] - offset[i];
|
|
|
|
tw.start[i] = start[i] + offset[i];
|
|
|
|
tw.end[i] = end[i] + offset[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
// if a sphere is already specified
|
|
|
|
if ( sphere ) {
|
|
|
|
tw.sphere = *sphere;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
tw.sphere.use = (qboolean)capsule;
|
|
|
|
tw.sphere.radius = ( tw.size[1][0] > tw.size[1][2] ) ? tw.size[1][2]: tw.size[1][0];
|
|
|
|
tw.sphere.halfheight = tw.size[1][2];
|
|
|
|
VectorSet( tw.sphere.offset, 0, 0, tw.size[1][2] - tw.sphere.radius );
|
|
|
|
}
|
|
|
|
|
|
|
|
tw.maxOffset = tw.size[1][0] + tw.size[1][1] + tw.size[1][2];
|
|
|
|
|
|
|
|
// tw.offsets[signbits] = vector to apropriate corner from origin
|
|
|
|
tw.offsets[0][0] = tw.size[0][0];
|
|
|
|
tw.offsets[0][1] = tw.size[0][1];
|
|
|
|
tw.offsets[0][2] = tw.size[0][2];
|
|
|
|
|
|
|
|
tw.offsets[1][0] = tw.size[1][0];
|
|
|
|
tw.offsets[1][1] = tw.size[0][1];
|
|
|
|
tw.offsets[1][2] = tw.size[0][2];
|
|
|
|
|
|
|
|
tw.offsets[2][0] = tw.size[0][0];
|
|
|
|
tw.offsets[2][1] = tw.size[1][1];
|
|
|
|
tw.offsets[2][2] = tw.size[0][2];
|
|
|
|
|
|
|
|
tw.offsets[3][0] = tw.size[1][0];
|
|
|
|
tw.offsets[3][1] = tw.size[1][1];
|
|
|
|
tw.offsets[3][2] = tw.size[0][2];
|
|
|
|
|
|
|
|
tw.offsets[4][0] = tw.size[0][0];
|
|
|
|
tw.offsets[4][1] = tw.size[0][1];
|
|
|
|
tw.offsets[4][2] = tw.size[1][2];
|
|
|
|
|
|
|
|
tw.offsets[5][0] = tw.size[1][0];
|
|
|
|
tw.offsets[5][1] = tw.size[0][1];
|
|
|
|
tw.offsets[5][2] = tw.size[1][2];
|
|
|
|
|
|
|
|
tw.offsets[6][0] = tw.size[0][0];
|
|
|
|
tw.offsets[6][1] = tw.size[1][1];
|
|
|
|
tw.offsets[6][2] = tw.size[1][2];
|
|
|
|
|
|
|
|
tw.offsets[7][0] = tw.size[1][0];
|
|
|
|
tw.offsets[7][1] = tw.size[1][1];
|
|
|
|
tw.offsets[7][2] = tw.size[1][2];
|
|
|
|
|
|
|
|
//
|
|
|
|
// calculate bounds
|
|
|
|
//
|
|
|
|
if ( tw.sphere.use ) {
|
|
|
|
for ( i = 0 ; i < 3 ; i++ ) {
|
|
|
|
if ( tw.start[i] < tw.end[i] ) {
|
|
|
|
tw.bounds[0][i] = tw.start[i] - fabs(tw.sphere.offset[i]) - tw.sphere.radius;
|
|
|
|
tw.bounds[1][i] = tw.end[i] + fabs(tw.sphere.offset[i]) + tw.sphere.radius;
|
|
|
|
} else {
|
|
|
|
tw.bounds[0][i] = tw.end[i] - fabs(tw.sphere.offset[i]) - tw.sphere.radius;
|
|
|
|
tw.bounds[1][i] = tw.start[i] + fabs(tw.sphere.offset[i]) + tw.sphere.radius;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
for ( i = 0 ; i < 3 ; i++ ) {
|
|
|
|
if ( tw.start[i] < tw.end[i] ) {
|
|
|
|
tw.bounds[0][i] = tw.start[i] + tw.size[0][i];
|
|
|
|
tw.bounds[1][i] = tw.end[i] + tw.size[1][i];
|
|
|
|
} else {
|
|
|
|
tw.bounds[0][i] = tw.end[i] + tw.size[0][i];
|
|
|
|
tw.bounds[1][i] = tw.start[i] + tw.size[1][i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// check for position test special case
|
|
|
|
//
|
|
|
|
if (start[0] == end[0] && start[1] == end[1] && start[2] == end[2] &&
|
|
|
|
tw.size[0][0] == 0 && tw.size[0][1] == 0 && tw.size[0][2] == 0)
|
|
|
|
{
|
|
|
|
if ( model && cmod->firstNode == -1)
|
|
|
|
{
|
|
|
|
#ifdef ALWAYS_BBOX_VS_BBOX // bk010201 - FIXME - compile time flag?
|
|
|
|
if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE)
|
|
|
|
{
|
|
|
|
tw.sphere.use = qfalse;
|
|
|
|
CM_TestInLeaf( &tw, &cmod->leaf );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
#elif defined(ALWAYS_CAPSULE_VS_CAPSULE)
|
|
|
|
if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE)
|
|
|
|
{
|
|
|
|
CM_TestCapsuleInCapsule( &tw, model );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
if ( model == CAPSULE_MODEL_HANDLE )
|
|
|
|
{
|
|
|
|
if ( tw.sphere.use )
|
|
|
|
{
|
|
|
|
CM_TestCapsuleInCapsule( &tw, *trace, model );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
CM_TestBoundingBoxInCapsule( &tw, *trace, model );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
CM_TestInLeaf( &tw, *trace, &cmod->leaf, local );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#ifdef TEST_TERRAIN_PHYSICS
|
|
|
|
else if (cmg.landScape && !model && !cmod->firstNode)
|
|
|
|
{
|
|
|
|
CM_TraceThroughTerrain( &tw, *trace, cmg.landScape );
|
|
|
|
}
|
|
|
|
#endif // #ifdef TEST_TERRAIN_PHYSICS
|
|
|
|
else if (cmod->firstNode == -1)
|
|
|
|
{
|
|
|
|
CM_PositionTest( &tw, *trace );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
CM_TraceThroughTree( &tw, *trace, local, cmod->firstNode, 0, 1, tw.start, tw.end );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//
|
|
|
|
// check for point special case
|
|
|
|
//
|
|
|
|
if ( tw.size[0][0] == 0 && tw.size[0][1] == 0 && tw.size[0][2] == 0 )
|
|
|
|
{
|
|
|
|
tw.isPoint = qtrue;
|
|
|
|
VectorClear( tw.extents );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
tw.isPoint = qfalse;
|
|
|
|
tw.extents[0] = tw.size[1][0];
|
|
|
|
tw.extents[1] = tw.size[1][1];
|
|
|
|
tw.extents[2] = tw.size[1][2];
|
|
|
|
}
|
|
|
|
|
|
|
|
//
|
|
|
|
// general sweeping through world
|
|
|
|
//
|
|
|
|
if ( model && cmod->firstNode == -1)
|
|
|
|
{
|
|
|
|
#ifdef ALWAYS_BBOX_VS_BBOX
|
|
|
|
if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE)
|
|
|
|
{
|
|
|
|
tw.sphere.use = qfalse;
|
|
|
|
CM_TraceThroughLeaf( &tw, &cmod->leaf );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
#elif defined(ALWAYS_CAPSULE_VS_CAPSULE)
|
|
|
|
if ( model == BOX_MODEL_HANDLE || model == CAPSULE_MODEL_HANDLE)
|
|
|
|
{
|
|
|
|
CM_TraceCapsuleThroughCapsule( &tw, model );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
#endif
|
|
|
|
if ( model == CAPSULE_MODEL_HANDLE )
|
|
|
|
{
|
|
|
|
if ( tw.sphere.use )
|
|
|
|
{
|
|
|
|
CM_TraceCapsuleThroughCapsule( &tw, *trace, model );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
CM_TraceBoundingBoxThroughCapsule( &tw, *trace, model );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
CM_TraceThroughLeaf( &tw, *trace, local, &cmod->leaf );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#ifdef TEST_TERRAIN_PHYSICS
|
|
|
|
else if (cmg.landScape && !model && !cmod->firstNode)
|
|
|
|
{
|
|
|
|
CM_TraceThroughTerrain( &tw, *trace, cmg.landScape );
|
|
|
|
}
|
|
|
|
#endif // #ifdef TEST_TERRAIN_PHYSICS
|
|
|
|
else
|
|
|
|
{
|
|
|
|
CM_TraceThroughTree( &tw, *trace, local, cmod->firstNode, 0, 1, tw.start, tw.end );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// generate endpos from the original, unmodified start/end
|
|
|
|
if ( trace->fraction == 1 ) {
|
|
|
|
VectorCopy (end, trace->endpos);
|
|
|
|
} else {
|
|
|
|
for ( i=0 ; i<3 ; i++ ) {
|
|
|
|
trace->endpos[i] = start[i] + trace->fraction * (end[i] - start[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If allsolid is set (was entirely inside something solid), the plane is not valid.
|
|
|
|
// If fraction == 1.0, we never hit anything, and thus the plane is not valid.
|
|
|
|
// Otherwise, the normal on the plane should have unit length
|
|
|
|
assert(trace->allsolid ||
|
|
|
|
trace->fraction == 1.0 ||
|
|
|
|
VectorLengthSquared(trace->plane.normal) > 0.9999);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
CM_BoxTrace
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void CM_BoxTrace( trace_t *results, const vec3_t start, const vec3_t end,
|
|
|
|
const vec3_t mins, const vec3_t maxs,
|
|
|
|
clipHandle_t model, int brushmask, int capsule ) {
|
|
|
|
CM_Trace( results, start, end, mins, maxs, model, vec3_origin, brushmask, capsule, NULL );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
CM_TransformedBoxTrace
|
|
|
|
|
|
|
|
Handles offseting and rotation of the end points for moving and
|
|
|
|
rotating entities
|
|
|
|
==================
|
|
|
|
*/
|
|
|
|
void CM_TransformedBoxTrace( trace_t *trace, const vec3_t start, const vec3_t end,
|
|
|
|
const vec3_t mins, const vec3_t maxs,
|
|
|
|
clipHandle_t model, int brushmask,
|
|
|
|
const vec3_t origin, const vec3_t angles, int capsule ) {
|
|
|
|
vec3_t start_l, end_l;
|
|
|
|
qboolean rotated;
|
|
|
|
vec3_t offset;
|
|
|
|
vec3_t symetricSize[2];
|
|
|
|
vec3_t matrix[3], transpose[3];
|
|
|
|
int i;
|
|
|
|
float halfwidth;
|
|
|
|
float halfheight;
|
|
|
|
float t;
|
|
|
|
sphere_t sphere;
|
|
|
|
|
|
|
|
if ( !mins ) {
|
|
|
|
mins = vec3_origin;
|
|
|
|
}
|
|
|
|
if ( !maxs ) {
|
|
|
|
maxs = vec3_origin;
|
|
|
|
}
|
|
|
|
|
|
|
|
// adjust so that mins and maxs are always symetric, which
|
|
|
|
// avoids some complications with plane expanding of rotated
|
|
|
|
// bmodels
|
|
|
|
for ( i = 0 ; i < 3 ; i++ ) {
|
|
|
|
offset[i] = ( mins[i] + maxs[i] ) * 0.5;
|
|
|
|
symetricSize[0][i] = mins[i] - offset[i];
|
|
|
|
symetricSize[1][i] = maxs[i] - offset[i];
|
|
|
|
start_l[i] = start[i] + offset[i];
|
|
|
|
end_l[i] = end[i] + offset[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
// subtract origin offset
|
|
|
|
VectorSubtract( start_l, origin, start_l );
|
|
|
|
VectorSubtract( end_l, origin, end_l );
|
|
|
|
|
|
|
|
// rotate start and end into the models frame of reference
|
|
|
|
if ( model != BOX_MODEL_HANDLE &&
|
|
|
|
(angles[0] || angles[1] || angles[2]) ) {
|
|
|
|
rotated = qtrue;
|
|
|
|
} else {
|
|
|
|
rotated = qfalse;
|
|
|
|
}
|
|
|
|
|
|
|
|
halfwidth = symetricSize[ 1 ][ 0 ];
|
|
|
|
halfheight = symetricSize[ 1 ][ 2 ];
|
|
|
|
|
|
|
|
sphere.use = (qboolean)capsule;
|
|
|
|
sphere.radius = ( halfwidth > halfheight ) ? halfheight : halfwidth;
|
|
|
|
sphere.halfheight = halfheight;
|
|
|
|
t = halfheight - sphere.radius;
|
|
|
|
|
|
|
|
if (rotated) {
|
|
|
|
// rotation on trace line (start-end) instead of rotating the bmodel
|
|
|
|
// NOTE: This is still incorrect for bounding boxes because the actual bounding
|
|
|
|
// box that is swept through the model is not rotated. We cannot rotate
|
|
|
|
// the bounding box or the bmodel because that would make all the brush
|
|
|
|
// bevels invalid.
|
|
|
|
// However this is correct for capsules since a capsule itself is rotated too.
|
|
|
|
CreateRotationMatrix(angles, matrix);
|
|
|
|
RotatePoint(start_l, matrix);
|
|
|
|
RotatePoint(end_l, matrix);
|
|
|
|
// rotated sphere offset for capsule
|
|
|
|
sphere.offset[0] = matrix[0][ 2 ] * t;
|
|
|
|
sphere.offset[1] = -matrix[1][ 2 ] * t;
|
|
|
|
sphere.offset[2] = matrix[2][ 2 ] * t;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
VectorSet( sphere.offset, 0, 0, t );
|
|
|
|
}
|
|
|
|
|
|
|
|
// sweep the box through the model
|
|
|
|
CM_Trace( trace, start_l, end_l, symetricSize[0], symetricSize[1], model, origin, brushmask, capsule, &sphere );
|
|
|
|
|
|
|
|
// if the bmodel was rotated and there was a collision
|
|
|
|
if ( rotated && trace->fraction != 1.0 ) {
|
|
|
|
// rotation of bmodel collision plane
|
|
|
|
TransposeMatrix(matrix, transpose);
|
|
|
|
RotatePoint(trace->plane.normal, transpose);
|
|
|
|
}
|
|
|
|
|
|
|
|
// re-calculate the end position of the trace because the trace.endpos
|
|
|
|
// calculated by CM_Trace could be rotated and have an offset
|
|
|
|
trace->endpos[0] = start[0] + trace->fraction * (end[0] - start[0]);
|
|
|
|
trace->endpos[1] = start[1] + trace->fraction * (end[1] - start[1]);
|
|
|
|
trace->endpos[2] = start[2] + trace->fraction * (end[2] - start[2]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
CM_CullBox
|
|
|
|
|
|
|
|
Returns true if culled out
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
|
|
|
|
bool CM_CullBox(const cplane_t *frustum, const vec3_t transformed[8])
|
|
|
|
{
|
|
|
|
int i, j;
|
|
|
|
const cplane_t *frust;
|
|
|
|
|
|
|
|
// check against frustum planes
|
|
|
|
for (i=0, frust=frustum; i<4 ; i++, frust++)
|
|
|
|
{
|
|
|
|
for (j=0 ; j<8 ; j++)
|
|
|
|
{
|
|
|
|
if (DotProduct(transformed[j], frust->normal) > frust->dist)
|
|
|
|
{ // a point is in front
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (j == 8)
|
|
|
|
{ // all points were behind one of the planes
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
CM_CullWorldBox
|
|
|
|
|
|
|
|
Returns true if culled out
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
|
|
|
|
bool CM_CullWorldBox (const cplane_t *frustum, const vec3pair_t bounds)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
vec3_t transformed[8];
|
|
|
|
|
|
|
|
for (i = 0 ; i < 8 ; i++)
|
|
|
|
{
|
|
|
|
transformed[i][0] = bounds[i & 1][0];
|
|
|
|
transformed[i][1] = bounds[(i >> 1) & 1][1];
|
|
|
|
transformed[i][2] = bounds[(i >> 2) & 1][2];
|
|
|
|
}
|
|
|
|
|
|
|
|
return(CM_CullBox(frustum, transformed));
|
|
|
|
}
|