openmohaa/code/qcommon/cm_trace.c

2171 lines
54 KiB
C
Raw Normal View History

2016-03-27 11:49:47 +02:00
/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
This file is part of Quake III Arena source code.
Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.
Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
===========================================================================
*/
#include "cm_local.h"
// 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
sphere_t sphere;
/*
===============================================================================
BASIC MATH
===============================================================================
*/
/*
================
RotatePoint
================
*/
void RotatePoint(vec3_t point, /*const*/ vec3_t matrix[3]) { // 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);
}
/*
================
CreateRotationMatrix
================
*/
void CreateRotationMatrix(const vec3_t angles, vec3_t matrix[3]) {
AngleVectorsLeft(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) {
union {
float f;
int i;
} t;
float x, y;
const float f = 1.5F;
x = number * 0.5F;
t.f = number;
t.i = 0x5f3759df - ( t.i >> 1 );
y = t.f;
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, cbrush_t *brush ) {
int i;
cplane_t *plane;
float dist;
float d1;
cbrushside_t *side;
float t;
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 ( 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;
plane = side->plane;
// find the closest point on the capsule to the plane
t = DotProduct( plane->normal, sphere.offset );
if( t < 0 )
{
t = -t;
}
2016-03-27 11:49:47 +02:00
// adjust the plane distance apropriately for radius
dist = t + plane->dist + sphere.radius;
2016-03-27 11:49:47 +02:00
d1 = DotProduct( tw->start, 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;
plane = side->plane;
// 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
tw->trace.startsolid = tw->trace.allsolid = qtrue;
tw->trace.fraction = 0;
tw->trace.contents = brush->contents;
}
/*
================
CM_TestInLeaf
================
*/
void CM_TestInLeaf( traceWork_t *tw, cLeaf_t *leaf ) {
int k;
int brushnum;
cbrush_t *b;
cPatch_t *patch;
cTerrain_t *terrain;
// test box position against all brushes in the leaf
for (k=0 ; k<leaf->numLeafBrushes ; k++) {
brushnum = cm.leafbrushes[leaf->firstLeafBrush+k];
b = &cm.brushes[brushnum];
if (b->checkcount == cm.checkcount) {
continue; // already checked this brush in another leaf
}
b->checkcount = cm.checkcount;
if ( !(b->contents & tw->contents)) {
continue;
}
CM_TestBoxInBrush( tw, b );
if ( tw->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++ ) {
patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ];
if ( !patch ) {
continue;
}
if ( patch->checkcount == cm.checkcount ) {
continue; // already checked this brush in another leaf
}
patch->checkcount = cm.checkcount;
if ( !(patch->contents & tw->contents)) {
continue;
}
if( CM_PositionTestInPatchCollide( tw, patch->pc ) ) {
tw->trace.fraction = 0;
tw->trace.startsolid = tw->trace.allsolid = qtrue;
return;
}
}
}
for( k = 0; k < leaf->numLeafTerrains; k++ ) {
terrain = cm.leafterrains[ leaf->firstLeafTerrain + k ];
if( !terrain ) {
continue;
}
if( terrain->checkcount == cm.checkcount ) {
continue;
}
terrain->checkcount = cm.checkcount;
if( CM_PositionTestInTerrainCollide( tw, &terrain->tc ) ) {
tw->trace.fraction = 0;
tw->trace.startsolid = tw->trace.allsolid = qtrue;
return;
}
}
}
/*
==================
CM_TestCapsuleInCapsule
capsule inside capsule check
==================
*/
void CM_TestCapsuleInCapsule( traceWork_t *tw, 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, sphere.offset, top);
VectorSubtract(tw->start, 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(sphere.radius + radius);
// check if any of the spheres overlap
VectorCopy(offset, p1);
p1[2] += offs;
VectorSubtract(p1, top, tmp);
if ( VectorLengthSquared(tmp) < r ) {
tw->trace.startsolid = tw->trace.allsolid = qtrue;
tw->trace.fraction = 0;
}
VectorSubtract(p1, bottom, tmp);
if ( VectorLengthSquared(tmp) < r ) {
tw->trace.startsolid = tw->trace.allsolid = qtrue;
tw->trace.fraction = 0;
}
VectorCopy(offset, p2);
p2[2] -= offs;
VectorSubtract(p2, top, tmp);
if ( VectorLengthSquared(tmp) < r ) {
tw->trace.startsolid = tw->trace.allsolid = qtrue;
tw->trace.fraction = 0;
}
VectorSubtract(p2, bottom, tmp);
if ( VectorLengthSquared(tmp) < r ) {
tw->trace.startsolid = tw->trace.allsolid = qtrue;
tw->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 ) {
tw->trace.startsolid = tw->trace.allsolid = qtrue;
tw->trace.fraction = 0;
}
}
}
/*
==================
CM_TestBoundingBoxInCapsule
bounding box inside capsule check
==================
*/
void CM_TestBoundingBoxInCapsule( traceWork_t *tw, 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
sphere.use = qtrue;
sphere.radius = ( size[1][0] > size[1][2] ) ? size[1][2]: size[1][0];
VectorSet( sphere.offset, 0, 0, size[1][2] - 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, &cmod->leaf );
}
/*
==================
CM_PositionTest
==================
*/
#define MAX_POSITION_LEAFS 1024
void CM_PositionTest( traceWork_t *tw ) {
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;
cm.checkcount++;
CM_BoxLeafnums_r( &ll, 0 );
cm.checkcount++;
// test the contents of the leafs
for (i=0 ; i < ll.count ; i++) {
CM_TestInLeaf( tw, &cm.leafs[leafs[i]] );
if ( tw->trace.allsolid ) {
break;
}
}
}
/*
===============================================================================
TRACING
===============================================================================
*/
/*
================
CM_TraceThroughPatch
================
*/
void CM_TraceThroughPatch( traceWork_t *tw, cPatch_t *patch ) {
float oldFrac;
c_patch_traces++;
oldFrac = tw->trace.fraction;
CM_TraceThroughPatchCollide( tw, patch->pc );
if ( tw->trace.fraction < oldFrac ) {
tw->trace.surfaceFlags = patch->surfaceFlags;
tw->trace.shaderNum = patch->shaderNum;
tw->trace.contents = patch->contents;
}
}
/*
================
CM_TraceThroughTerrain
================
*/
void CM_TraceThroughTerrain( traceWork_t *tw, cTerrain_t *terrain ) {
float oldFrac;
oldFrac = tw->trace.fraction;
// test against all terrain patches
CM_TraceThroughTerrainCollide( tw, &terrain->tc );
if( tw->trace.fraction < oldFrac ) {
tw->trace.surfaceFlags = terrain->surfaceFlags;
tw->trace.shaderNum = terrain->shaderNum;
tw->trace.contents = terrain->contents;
}
}
/*
================
CM_TraceThroughBrush
================
*/
void CM_TraceThroughBrush( traceWork_t *tw, cbrush_t *brush ) {
int i;
cplane_t *plane, *clipplane, *clipplane2;
float dist;
float enterFrac, leaveFrac, leaveFrac2;
float d1, d2;
qboolean getout, startout;
float f;
cbrushside_t *side, *leadside, *leadside2;
float t;
if( !brush->numsides ) {
return;
}
enterFrac = -1.0;
leaveFrac = 1.0;
clipplane = NULL;
c_brush_traces++;
getout = qfalse;
startout = qfalse;
leadside = NULL;
if( !( brush->contents & CONTENTS_FENCE ) || !tw->isPoint ) {
if( sphere.use ) {
//
// 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;
plane = side->plane;
// find the closest point on the capsule to the plane
t = DotProduct( plane->normal, sphere.offset );
if( t < 0 )
{
t = -t;
}
// adjust the plane distance apropriately for radius
dist = t + plane->dist + sphere.radius;
d1 = DotProduct( tw->start, plane->normal ) - dist;
d2 = DotProduct( tw->end, plane->normal ) - dist;
// if it doesn't cross the plane, the plane isn't relevent
if( d1 <= 0 && d2 <= 0 ) {
continue;
}
if( d2 > 0 ) {
getout = qtrue; // endpoint is not in solid
}
if( d1 > 0 ) {
startout = qtrue;
}
// if completely in front of face, no intersection with the entire brush
if( d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) {
return;
}
// crosses face
if( d1 > d2 ) { // enter
f = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 );
if( f < 0 ) {
f = 0;
}
if( f > enterFrac ) {
enterFrac = f;
clipplane = plane;
leadside = side;
}
}
else { // leave
f = ( d1 + SURFACE_CLIP_EPSILON ) / ( d1 - d2 );
if( f > 1 ) {
f = 1;
}
if( f < leaveFrac ) {
leaveFrac = f;
}
}
}
} else {
//
// 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;
plane = side->plane;
// 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 it doesn't cross the plane, the plane isn't relevent
if( d1 <= 0 && d2 <= 0 ) {
continue;
}
if( d2 > 0 ) {
getout = qtrue; // endpoint is not in solid
}
if( d1 > 0 ) {
startout = qtrue;
// if completely in front of face, no intersection with the entire brush
if( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) {
return;
}
}
// crosses face
if( d1 > d2 ) { // enter
f = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 );
if( f < 0 ) {
f = 0;
}
if( f > enterFrac ) {
enterFrac = f;
clipplane = plane;
leadside = side;
}
} else { // leave
f = ( d1 + SURFACE_CLIP_EPSILON ) / ( d1 - d2 );
if( f > 1 ) {
f = 1;
}
if( f < leaveFrac ) {
leaveFrac = f;
}
}
}
}
//
// all planes have been checked, and the trace was not
// completely outside the brush
//
if( !startout ) { // original point was inside brush
tw->trace.startsolid = qtrue;
if( !getout ) {
tw->trace.fraction = 0;
tw->trace.allsolid = qtrue;
}
return;
}
if( enterFrac <= leaveFrac ) {
if( enterFrac > -1 && enterFrac < tw->trace.fraction ) {
if( enterFrac < 0 ) {
enterFrac = 0;
}
tw->trace.fraction = enterFrac;
tw->trace.plane = *clipplane;
tw->trace.surfaceFlags = leadside->surfaceFlags;
tw->trace.shaderNum = leadside->shaderNum;
tw->trace.contents = brush->contents;
}
}
} else {
leaveFrac2 = 1.0;
clipplane2 = NULL;
leadside2 = NULL;
if (!(tw->contents & CONTENTS_FENCE)) {
return;
}
2016-03-27 11:49:47 +02:00
//
// 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;
plane = side->plane;
// 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 it doesn't cross the plane, the plane isn't relevent
if( d1 <= 0 && d2 <= 0 ) {
continue;
}
if( d1 > 0 ) {
// if completely in front of face, no intersection with the entire brush
if( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) {
return;
}
}
// crosses face
if( d1 > d2 ) { // enter
f = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 );
if( f < 0 ) {
f = 0;
}
if( f > enterFrac ) {
enterFrac = f;
clipplane = plane;
leadside = side;
}
} else { // leave
f = ( d1 + SURFACE_CLIP_EPSILON ) / ( d1 - d2 );
if( f > 1 ) {
f = 1;
}
if( f < leaveFrac ) {
leaveFrac = f;
clipplane2 = plane;
leadside2 = side;
leaveFrac2 = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 );
}
}
}
//
// all planes have been checked, and the trace was not
// completely outside the brush
//
if( enterFrac <= leaveFrac ) {
if( enterFrac > -1 && enterFrac < tw->trace.fraction ) {
if( enterFrac < 0 ) {
enterFrac = 0;
}
if( CM_TraceThroughFence( tw, brush, leadside, enterFrac ) ) {
tw->trace.fraction = enterFrac;
tw->trace.plane = *clipplane;
tw->trace.surfaceFlags = leadside->surfaceFlags;
tw->trace.shaderNum = leadside->shaderNum;
tw->trace.contents = brush->contents;
return;
}
}
}
if( ( leaveFrac2 < 1.0 ) && ( leadside2->surfaceFlags & SURF_BACKSIDE ) ) {
if( leaveFrac2 < tw->trace.fraction ) {
if( CM_TraceThroughFence( tw, brush, leadside2, leaveFrac ) ) {
tw->trace.fraction = leaveFrac2;
tw->trace.plane = *clipplane2;
tw->trace.surfaceFlags = leadside2->surfaceFlags;
tw->trace.shaderNum = leadside2->shaderNum;
tw->trace.contents = brush->contents;
return;
}
}
}
}
}
/*
================
CM_TraceToLeaf
================
*/
void CM_TraceToLeaf( traceWork_t *tw, cLeaf_t *leaf ) {
int k;
cbrush_t *b;
cPatch_t *patch;
cTerrain_t *terrain;
// test box position against all brushes in the leaf
for( k = 0; k<leaf->numLeafBrushes; k++ ) {
b = &cm.brushes[ cm.leafbrushes[ leaf->firstLeafBrush + k ] ];
if( b->checkcount == cm.checkcount ) {
continue; // already checked this brush in another leaf
}
b->checkcount = cm.checkcount;
if( !( b->contents & tw->contents ) ) {
continue;
}
CM_TraceThroughBrush( tw, b );
if( !tw->trace.fraction ) {
return;
}
}
// test against all patches
#ifdef BSPC
if( 1 ) {
#else
if( !cm_noCurves->integer ) {
#endif //BSPC
for( k = 0; k < leaf->numLeafSurfaces; k++ ) {
patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ];
if( !patch ) {
continue;
}
if( patch->checkcount == cm.checkcount ) {
continue; // already checked this brush in another leaf
}
patch->checkcount = cm.checkcount;
if( !( patch->contents & tw->contents ) ) {
continue;
}
CM_TraceThroughPatch( tw, patch );
if( !tw->trace.fraction ) {
return;
}
}
}
// test against all terrains
for( k = 0; k < leaf->numLeafTerrains; k++ ) {
terrain = cm.leafterrains[ leaf->firstLeafTerrain + k ];
if( !terrain ) {
continue;
}
if( terrain->checkcount == cm.checkcount ) {
continue;
}
terrain->checkcount = cm.checkcount;
CM_TraceThroughTerrain( tw, terrain );
if( !tw->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, 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)) {
tw->trace.fraction = 0;
tw->trace.startsolid = qtrue;
// test for allsolid
VectorSubtract(end, origin, dir);
l1 = VectorLengthSquared(dir);
if (l1 < Square(radius)) {
tw->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 < tw->trace.fraction ) {
tw->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, tw->trace.plane.normal);
tw->trace.plane.dist = DotProduct(tw->trace.plane.normal, intersection);
tw->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, 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)) {
tw->trace.fraction = 0;
tw->trace.startsolid = qtrue;
VectorSubtract(end2d, org2d, dir);
l1 = VectorLengthSquared(dir);
if (l1 < Square(radius)) {
tw->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 < tw->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) {
//
tw->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, tw->trace.plane.normal);
tw->trace.plane.dist = DotProduct(tw->trace.plane.normal, intersection);
tw->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, 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, sphere.offset, starttop);
VectorSubtract(tw->start, sphere.offset, startbottom);
VectorAdd(tw->end, sphere.offset, endtop);
VectorSubtract(tw->end, 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 += 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 - radius;
// if the cylinder has a height
if ( h > 0 ) {
// test for collisions between the cylinders
CM_TraceThroughVerticalCylinder(tw, offset, radius, h, tw->start, tw->end);
}
}
// test for collision between the spheres
CM_TraceThroughSphere(tw, top, radius, startbottom, endbottom);
CM_TraceThroughSphere(tw, bottom, radius, starttop, endtop);
}
/*
================
CM_TraceBoundingBoxThroughCapsule
bounding box vs. capsule collision
================
*/
void CM_TraceBoundingBoxThroughCapsule( traceWork_t *tw, 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
sphere.use = qtrue;
sphere.radius = ( size[1][0] > size[1][2] ) ? size[1][2]: size[1][0];
VectorSet( sphere.offset, 0, 0, size[1][2] - 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_TraceToLeaf( tw, &cmod->leaf );
}
//=========================================================================================
/*
==================
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, 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 (tw->trace.fraction <= p1f) {
return; // already hit something nearer
}
// if < 0, we are in a leaf node
if (num < 0) {
CM_TraceToLeaf( tw, &cm.leafs[-1-num] );
return;
}
//
// find the point distances to the seperating plane
// and the offset for the size of the box
//
node = cm.nodes + num;
plane = node->plane;
// 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 {
// this is silly
offset = 2048;
}
}
// see which sides we need to consider
if ( t1 >= offset + 1 && t2 >= offset + 1 ) {
CM_TraceThroughTree( tw, node->children[0], p1f, p2f, p1, p2 );
return;
}
if ( t1 < -offset - 1 && t2 < -offset - 1 ) {
CM_TraceThroughTree( tw, 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, 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, node->children[side^1], midf, p2f, mid, p2 );
}
//======================================================================
/*
==================
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 cylinder ) {
int i;
traceWork_t tw;
vec3_t offset;
cmodel_t *cmod;
cmod = CM_ClipHandleToModel( model );
cm.checkcount++; // for multi-check avoidance
c_traces++; // for statistics, may be zeroed
// fill in a default trace
Com_Memset( &tw, 0, sizeof( tw ) );
tw.trace.fraction = 1; // assume it goes the entire distance until shown otherwise
// set basic parms
tw.trace.location = -1; // clear out unneeded location
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 ];
}
tw.height = tw.size[ 1 ][ 2 ];
tw.radius = tw.size[ 1 ][ 0 ];
if( cylinder && !sphere.use )
{
sphere.use = qtrue;
sphere.radius = ( tw.size[ 1 ][ 0 ] > tw.size[ 1 ][ 2 ] ) ? tw.size[ 1 ][ 2 ] : tw.size[ 1 ][ 0 ];
VectorSet( sphere.offset, 0, 0, tw.size[ 1 ][ 2 ] - 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
//
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 ] ) {
if( model ) {
CM_TestInLeaf( &tw, &cmod->leaf );
} else {
CM_PositionTest( &tw );
}
}
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 ) {
CM_TraceToLeaf( &tw, &cmod->leaf );
} else {
CM_TraceThroughTree( &tw, 0, 0, 1, tw.start, tw.end );
}
}
// generate endpos from the original, unmodified start/end
if( tw.trace.fraction == 1 ) {
VectorCopy( end, tw.trace.endpos );
}
else {
for( i = 0; i<3; i++ ) {
tw.trace.endpos[ i ] = start[ i ] + tw.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( tw.trace.allsolid ||
tw.trace.fraction == 1.0 ||
VectorLengthSquared( tw.trace.plane.normal ) > 0.9999 );
*results = tw.trace;
sphere.use = qfalse;
}
/*
==================
CM_TransformedBoxTrace
Handles offseting and rotation of the end points for moving and
rotating entities
==================
*/
void CM_TransformedBoxTrace( trace_t *results, 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 cylinder ) {
trace_t trace;
vec3_t start_l, end_l;
vec3_t a;
vec3_t forward, left, up;
vec3_t temp;
qboolean rotated;
vec3_t offset;
vec3_t symetricSize[2];
int i;
float halfwidth;
float halfheight;
float t;
// 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 ( angles[0] || angles[1] || angles[2] ) {
rotated = qtrue;
} else {
rotated = qfalse;
}
halfwidth = symetricSize[ 1 ][ 0 ];
halfheight = symetricSize[ 1 ][ 2 ];
sphere.use = cylinder;
sphere.radius = ( halfwidth > halfheight ) ? halfheight : halfwidth;
t = halfheight - sphere.radius;
if( rotated ) {
AngleVectorsLeft( angles, forward, left, up );
VectorCopy( start_l, temp );
start_l[ 0 ] = DotProduct( temp, forward );
start_l[ 1 ] = DotProduct( temp, left );
start_l[ 2 ] = DotProduct( temp, up );
VectorCopy( end_l, temp );
end_l[ 0 ] = DotProduct( temp, forward );
end_l[ 1 ] = DotProduct( temp, left );
end_l[ 2 ] = DotProduct( temp, up );
sphere.offset[ 0 ] = forward[ 2 ] * t;
sphere.offset[ 1 ] = left[ 2 ] * t;
sphere.offset[ 2 ] = up[ 2 ] * t;
}
else {
VectorSet( sphere.offset, 0, 0, t );
}
// sweep the box through the model
CM_BoxTrace( &trace, start_l, end_l, symetricSize[0], symetricSize[1], model, brushmask, cylinder );
sphere.use = qfalse;
// if the bmodel was rotated and there was a collision
if ( rotated && trace.fraction != 1.0 ) {
// rotation of bmodel collision plane
VectorNegate( angles, a );
AngleVectorsLeft( a, forward, left, up );
VectorCopy( trace.plane.normal, temp );
trace.plane.normal[ 0 ] = DotProduct( temp, forward );
trace.plane.normal[ 1 ] = DotProduct( temp, left );
trace.plane.normal[ 2 ] = DotProduct( temp, up );
}
// 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]);
*results = trace;
}
/*
================
CM_SightTraceThroughPatch
================
*/
qboolean CM_SightTraceThroughPatch( traceWork_t *tw, cPatch_t *patch )
{
CM_TraceThroughPatchCollide( tw, patch->pc );
if( tw->trace.allsolid || tw->trace.startsolid || tw->trace.fraction < 1 ) {
return qfalse;
}
return qtrue;
}
/*
================
CM_SightTraceThroughTerrain
================
*/
qboolean CM_SightTraceThroughTerrain( traceWork_t *tw, cTerrain_t *terrain )
{
CM_TraceThroughTerrainCollide( tw, &terrain->tc );
if( tw->trace.allsolid || tw->trace.startsolid || tw->trace.fraction < 1 ) {
return qfalse;
}
return qtrue;
}
/*
================
CM_SightTraceThroughBrush
================
*/
qboolean CM_SightTraceThroughBrush( traceWork_t *tw, cbrush_t *brush )
{
int i;
cplane_t *plane;
float dist;
float enterFrac, leaveFrac, leaveFrac2;
float d1, d2;
qboolean startout;
float f;
cbrushside_t *side, *leadside, *leadside2;
float t;
2016-03-27 11:49:47 +02:00
if( !brush->numsides ) {
return qtrue;
}
enterFrac = -1.0;
leaveFrac = 1.0;
c_brush_traces++;
startout = qfalse;
leadside = NULL;
if( !( brush->contents & CONTENTS_FENCE ) || !tw->isPoint ) {
if( sphere.use ) {
//
// 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;
plane = side->plane;
// find the closest point on the capsule to the plane
t = DotProduct( plane->normal, sphere.offset );
if( t < 0 )
{
t = -t;
}
// adjust the plane distance apropriately for radius
dist = t + plane->dist + sphere.radius;
d1 = DotProduct( tw->start, plane->normal ) - dist;
d2 = DotProduct( tw->end, plane->normal ) - dist;
// if it doesn't cross the plane, the plane isn't relevent
if( d1 <= 0 && d2 <= 0 ) {
continue;
}
if( d1 > 0 ) {
startout = qtrue;
}
// if completely in front of face, no intersection with the entire brush
if( d1 > 0 && ( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) ) {
return qtrue;
}
// crosses face
if( d1 > d2 ) { // enter
f = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 );
if( f < 0 ) {
f = 0;
}
if( f > enterFrac ) {
enterFrac = f;
leadside = side;
}
}
else { // leave
f = ( d1 + SURFACE_CLIP_EPSILON ) / ( d1 - d2 );
if( f > 1 ) {
f = 1;
}
if( f < leaveFrac ) {
leaveFrac = f;
}
}
}
} else {
//
// 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;
plane = side->plane;
// 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 it doesn't cross the plane, the plane isn't relevent
if( d1 <= 0 && d2 <= 0 ) {
continue;
}
if( d1 > 0 ) {
startout = qtrue;
// if completely in front of face, no intersection with the entire brush
if( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) {
return qtrue;
}
}
// crosses face
if( d1 > d2 ) { // enter
f = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 );
if( f < 0 ) {
f = 0;
}
if( f > enterFrac ) {
enterFrac = f;
leadside = side;
}
} else { // leave
f = ( d1 + SURFACE_CLIP_EPSILON ) / ( d1 - d2 );
if( f > 1 ) {
f = 1;
}
if( f < leaveFrac ) {
leaveFrac = f;
}
}
}
}
//
// all planes have been checked, and the trace was not
// completely outside the brush
//
if( !startout ) { // original point was inside brush
return qfalse;
}
if( enterFrac <= leaveFrac ) {
if( enterFrac > -1 ) {
return qfalse;
}
}
} else {
leaveFrac2 = 1.0;
leadside2 = 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;
plane = side->plane;
// 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 it doesn't cross the plane, the plane isn't relevent
if( d1 <= 0 && d2 <= 0 ) {
continue;
}
if( d1 > 0 ) {
// if completely in front of face, no intersection with the entire brush
if( d2 >= SURFACE_CLIP_EPSILON || d2 >= d1 ) {
return qtrue;
}
}
// crosses face
if( d1 > d2 ) { // enter
f = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 );
if( f < 0 ) {
f = 0;
}
if( f > enterFrac ) {
enterFrac = f;
leadside = side;
}
} else { // leave
f = ( d1 + SURFACE_CLIP_EPSILON ) / ( d1 - d2 );
if( f > 1 ) {
f = 1;
}
if( f < leaveFrac ) {
leaveFrac = f;
leadside2 = side;
leaveFrac2 = ( d1 - SURFACE_CLIP_EPSILON ) / ( d1 - d2 );
}
}
}
//
// all planes have been checked, and the trace was not
// completely outside the brush
//
if( enterFrac <= leaveFrac ) {
if( enterFrac > -1 && enterFrac < tw->trace.fraction ) {
if( enterFrac < 0 ) {
enterFrac = 0;
}
if( CM_TraceThroughFence( tw, brush, leadside, enterFrac ) ) {
return qfalse;
}
}
}
if( ( leaveFrac2 < 1.0 ) && ( leadside2->surfaceFlags & SURF_BACKSIDE ) ) {
if( leaveFrac2 < tw->trace.fraction ) {
if( CM_TraceThroughFence( tw, brush, leadside2, leaveFrac ) ) {
return qfalse;
}
}
}
2016-03-27 11:49:47 +02:00
}
2016-03-27 11:49:47 +02:00
return qtrue;
}
/*
================
CM_SightTraceToLeaf
================
*/
qboolean CM_SightTraceToLeaf( traceWork_t *tw, cLeaf_t *leaf ) {
int k;
cbrush_t *b;
cPatch_t *patch;
cTerrain_t *terrain;
// test box position against all brushes in the leaf
for( k = 0; k<leaf->numLeafBrushes; k++ ) {
b = &cm.brushes[ cm.leafbrushes[ leaf->firstLeafBrush + k ] ];
if( b->checkcount == cm.checkcount ) {
continue; // already checked this brush in another leaf
}
b->checkcount = cm.checkcount;
if( !( b->contents & tw->contents ) ) {
continue;
}
if( !CM_SightTraceThroughBrush( tw, b ) ) {
return qfalse;
}
}
// test against all patches
#ifdef BSPC
if( 1 ) {
#else
if( !cm_noCurves->integer ) {
#endif //BSPC
for( k = 0; k < leaf->numLeafSurfaces; k++ ) {
patch = cm.surfaces[ cm.leafsurfaces[ leaf->firstLeafSurface + k ] ];
if( !patch ) {
continue;
}
if( patch->checkcount == cm.checkcount ) {
continue; // already checked this brush in another leaf
}
patch->checkcount = cm.checkcount;
if( !( patch->contents & tw->contents ) ) {
continue;
}
if( !CM_SightTraceThroughPatch( tw, patch ) ) {
return qfalse;
}
}
}
// test against all terrains
for( k = 0; k < leaf->numLeafTerrains; k++ ) {
terrain = cm.leafterrains[ leaf->firstLeafTerrain + k ];
if( !terrain ) {
continue;
}
if( terrain->checkcount == cm.checkcount ) {
continue;
}
terrain->checkcount = cm.checkcount;
if( !CM_SightTraceThroughTerrain( tw, terrain ) ) {
return qfalse;
}
}
return qtrue;
}
/*
==================
CM_SightTraceThroughTree
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.
==================
*/
qboolean CM_SightTraceThroughTree( traceWork_t *tw, 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 < 0, we are in a leaf node
if( num < 0 ) {
return CM_SightTraceToLeaf( tw, &cm.leafs[ -1 - num ] );
}
//
// find the point distances to the seperating plane
// and the offset for the size of the box
//
node = cm.nodes + num;
plane = node->plane;
// 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 {
// this is silly
offset = 2048;
}
}
// see which sides we need to consider
if( t1 >= offset + 1 && t2 >= offset + 1 ) {
return CM_SightTraceThroughTree( tw, node->children[ 0 ], p1f, p2f, p1, p2 );
}
if( t1 < -offset - 1 && t2 < -offset - 1 ) {
return CM_SightTraceThroughTree( tw, node->children[ 1 ], p1f, p2f, p1, p2 );
}
// 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 ] );
if( !CM_SightTraceThroughTree( tw, node->children[ side ], p1f, midf, p1, mid ) ) {
return qfalse;
}
// 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 ] );
return CM_SightTraceThroughTree( tw, node->children[ side ^ 1 ], midf, p2f, mid, p2 );
}
/*
==================
CM_BoxSightTrace
==================
*/
qboolean CM_BoxSightTrace( const vec3_t start, const vec3_t end, const vec3_t mins, const vec3_t maxs, clipHandle_t model, int brushmask, qboolean cylinder )
{
int i;
traceWork_t tw;
vec3_t offset;
cmodel_t *cmod;
qboolean bPassed;
cmod = CM_ClipHandleToModel( model );
cm.checkcount++; // for multi-check avoidance
c_traces++; // for statistics, may be zeroed
if( !cm.numNodes ) {
return qfalse;
}
// fill in a default trace
Com_Memset( &tw, 0, sizeof( tw ) );
tw.trace.fraction = 1; // assume it goes the entire distance until shown otherwise
// 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 ];
}
tw.height = tw.size[ 1 ][ 2 ];
tw.radius = tw.size[ 1 ][ 0 ];
if( cylinder && !sphere.use )
{
sphere.use = qtrue;
sphere.radius = ( tw.size[ 1 ][ 0 ] > tw.size[ 1 ][ 2 ] ) ? tw.size[ 1 ][ 2 ] : tw.size[ 1 ][ 0 ];
VectorSet( sphere.offset, 0, 0, tw.size[ 1 ][ 2 ] - 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
//
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 ] ) {
if( model ) {
CM_TestInLeaf( &tw, &cmod->leaf );
}
else {
CM_PositionTest( &tw );
}
bPassed = !tw.trace.startsolid;
}
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 ) {
bPassed = CM_SightTraceToLeaf( &tw, &cmod->leaf );
}
else {
bPassed = CM_SightTraceThroughTree( &tw, 0, 0, 1, tw.start, tw.end );
}
}
sphere.use = qfalse;
return bPassed;
}
/*
==================
CM_TransformedBoxSightTrace
==================
*/
qboolean CM_TransformedBoxSightTrace( 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, qboolean cylinder )
{
vec3_t start_l, end_l;
vec3_t forward, left, up;
vec3_t temp;
qboolean rotated;
vec3_t offset;
vec3_t symetricSize[ 2 ];
int i;
float halfwidth;
float halfheight;
float t;
// 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( angles[ 0 ] || angles[ 1 ] || angles[ 2 ] ) {
rotated = qtrue;
}
else {
rotated = qfalse;
}
halfwidth = symetricSize[ 1 ][ 0 ];
halfheight = symetricSize[ 1 ][ 2 ];
sphere.use = cylinder;
sphere.radius = ( halfwidth > halfheight ) ? halfheight : halfwidth;
t = halfheight - sphere.radius;
if( rotated ) {
AngleVectorsLeft( angles, forward, left, up );
VectorCopy( start_l, temp );
start_l[ 0 ] = DotProduct( temp, forward );
start_l[ 1 ] = DotProduct( temp, left );
start_l[ 2 ] = DotProduct( temp, up );
VectorCopy( end_l, temp );
end_l[ 0 ] = DotProduct( temp, forward );
end_l[ 1 ] = DotProduct( temp, left );
end_l[ 2 ] = DotProduct( temp, up );
sphere.offset[ 0 ] = forward[ 2 ] * t;
sphere.offset[ 1 ] = left[ 2 ] * t;
sphere.offset[ 2 ] = up[ 2 ] * t;
}
else {
VectorSet( sphere.offset, 0, 0, t );
}
// sweep the box through the model
return CM_BoxSightTrace( start_l, end_l, symetricSize[ 0 ], symetricSize[ 1 ], model, brushmask, cylinder );
}