etqw-sdk/source/game/physics/Clip.cpp

3037 lines
86 KiB
C++
Raw Normal View History

2008-05-29 00:00:00 +00:00
// Copyright (C) 2007 Id Software, Inc.
//
#include "../precompiled.h"
#pragma hdrstop
#if defined( _DEBUG ) && !defined( ID_REDIRECT_NEWDELETE )
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#include "../../framework/Licensee.h"
#include "Clip.h"
#include "../Entity.h"
#include "../WorldSpawn.h"
#include "../Player.h"
#include "../ContentMask.h"
#include "../Projectile.h"
#include "../Misc.h"
static idVec3 vec3_boxEpsilon( CM_BOX_EPSILON, CM_BOX_EPSILON, CM_BOX_EPSILON );
unsigned int idClip::mailBoxID = 0;
#ifdef CLIP_DEBUG
class idClipTimer {
public:
idClipTimer( const char* _fileName, int _lineNumber, idClip* _world, clipTimerMode_t _mode ) : lineNumber( _lineNumber ), world( _world ), mode( _mode ) {
sprintf( fileName, "%s", _fileName );
timer.Start();
}
~idClipTimer() {
timer.Stop();
double value = timer.Milliseconds();
if ( collisionModelManager->GetThreadId() == MAIN_THREAD_ID ) {
world->LogTrace( fileName, lineNumber, value, mode );
#ifdef CLIP_DEBUG_EXTREME
world->LogTraceExtreme( fileName, lineNumber, value, mode );
#endif
}
}
private:
idClip* world;
clipTimerMode_t mode;
idTimer timer;
char fileName[ 512 ];
int lineNumber;
};
#endif // CLIP_DEBUG
/*
===============================================================
idClip
===============================================================
*/
/*
===============
idClip::idClip
===============
*/
idClip::idClip( void ) {
temporaryClipModel = NULL;
thirdPersonOffsetModel = NULL;
bigThirdPersonOffsetModel = NULL;
leanOffsetModel = NULL;
defaultClipModel = NULL;
clipSectors = NULL;
numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0;
deletedClipModels = NULL;
#ifdef CLIP_DEBUG_EXTREME
isTraceLogging = false;
logFileUpto = 0;
#endif
}
/*
===============
idClip::GetWorldBounds
===============
*/
void idClip::GetWorldBounds( idBounds& bounds, int surfaceMask, bool inclusive ) const {
bounds.Clear();
idBounds temp;
for ( int i = 0; i < worldClips.Num(); i++) {
worldClips[ i ]->GetCollisionModel()->GetBounds( temp, surfaceMask, inclusive );
bounds += temp;
}
}
/*
===============
idClip::CreateClipSectors
===============
*/
clipSector_t *idClip::CreateClipSectors( const int depth, const idBounds &bounds ) {
if ( clipSectors ) {
delete[] clipSectors;
clipSectors = NULL;
}
int i;
for( i = 0; i < 3; i++ ) {
nodeScale[ i ] = depth / ( bounds[ 1 ][ i ] - bounds[ 0 ][ i ] );
inverseNodeScale[ i ] = 1 / nodeScale[ i ];
nodeOffset[ i ] = bounds.GetMins()[ i ];
}
clipSectors = new clipSector_t[ Square( depth ) ];
memset( clipSectors, 0, Square( depth ) * sizeof( clipSector_t ) );
return clipSectors;
}
/*
===============
idClip::Init
===============
*/
void idClip::Init( void ) {
// load world model
bool finished = false;
int idx = -1;
worldBounds.Clear();
while ( !finished ) {
idClipModel *world = NULL;
if ( idx == -1 ) {
idCollisionModel *mdl = collisionModelManager->LoadModel( gameLocal.GetMapName(), WORLD_MODEL_NAME );
if ( mdl != NULL ) {
world = new idClipModel();
world->LoadCollisionModel( mdl );
mdl->SetWorld( true );
}
} else {
idCollisionModel *mdl = collisionModelManager->LoadModel( gameLocal.GetMapName(), va( "%s%d", WORLD_MODEL_NAME, idx ) );
if ( mdl != NULL ) {
world = new idClipModel();
world->LoadCollisionModel( mdl );
mdl->SetWorld( true );
} else {
finished = true;
break;
}
}
if ( world ) {
world->SetEntity( NULL );
world->SetId( 0 );
if ( world->GetContents() & ( CONTENTS_BODY | CONTENTS_SLIDEMOVER | CONTENTS_CORPSE ) ) {
gameLocal.Error( WORLD_MODEL_NAME " collision model may not use CONTENTS_BODY or CONTENTS_SLIDEMOVER or CONTENTS_CORPSE" );
}
worldBounds.AddBounds( world->GetBounds() );
worldClips.Alloc() = world;
}
idx++;
}
if ( worldClips.Num() == 0 ) {
gameLocal.Error( WORLD_MODEL_NAME " collision model not found" );
}
CreateClipSectors( CLIPSECTOR_WIDTH, worldBounds );
GetClipSectorsStaticContents();
// initialize a temporary clip model
temporaryClipModel = new idClipModel();
thirdPersonOffsetModel = new idClipModel( idTraceModel( idBounds( idVec3( -4.f, -4.f, -4.f ), idVec3( 4.f, 4.f, 4.f ) ) ), false );
bigThirdPersonOffsetModel = new idClipModel( idTraceModel( idBounds( idVec3( -32.f, -32.f, -32.f ), idVec3( 32.f, 32.f, 32.f ) ) ), false );
leanOffsetModel = new idClipModel( idTraceModel( idBounds( idVec3( -8, -8, -7 ), idVec3( 8, 8, 4 ) ) ), false );
// initialize a default clip model
defaultClipModel = new idClipModel( idTraceModel( idBounds( idVec3( 0.0f, 0.0f, 0.0f ) ).Expand( 8.0f ) ), false );
// set counters to zero
numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0;
}
/*
===============
idClip::Shutdown
===============
*/
void idClip::Shutdown( void ) {
// free the clip sectors
delete[] clipSectors;
clipSectors = NULL;
// free the world clip model
for ( int i = 0; i < worldClips.Num(); i++ ) {
gameLocal.clip.DeleteClipModel( worldClips[ i ] );
}
worldClips.Clear();
// free the temporary clip model
gameLocal.clip.DeleteClipModel( temporaryClipModel );
temporaryClipModel = NULL;
gameLocal.clip.DeleteClipModel( thirdPersonOffsetModel );
thirdPersonOffsetModel = NULL;
gameLocal.clip.DeleteClipModel( bigThirdPersonOffsetModel );
bigThirdPersonOffsetModel = NULL;
gameLocal.clip.DeleteClipModel( leanOffsetModel );
leanOffsetModel = NULL;
// free the default clip model
gameLocal.clip.DeleteClipModel( defaultClipModel );
defaultClipModel = NULL;
clipLinkAllocator.Shutdown();
ActuallyDeleteClipModels( true );
}
/*
====================
idClip::GetWorldBounds
====================
*/
const idBounds & idClip::GetWorldBounds( void ) const {
return worldBounds;
}
/*
================
idClip::ClipModelsTouchingBounds
================
*/
int idClip::ClipModelsTouchingBounds( CLIP_DEBUG_PARMS_DECLARATION const idBounds &bounds, int contentMask, const idClipModel** clipModelList, int maxCount, const idEntity* passEntity, bool includeWorld ) const {
#ifdef CLIP_DEBUG
idClipTimer timer( CLIP_DEBUG_PARMS_PASSTHRU const_cast< idClip* >( this ), CTM_CLIPMODELSTOUCHINGBOUNDS );
#endif // CLIP_DEBUG
if ( collisionModelManager->GetThreadId() != MAIN_THREAD_ID ) {
common->FatalError( "idClip::ClipModelsTouchingBounds called from a thread other than the main thread" );
return 0;
}
idBounds parms( bounds[ 0 ] - vec3_boxEpsilon, bounds[ 1 ] + vec3_boxEpsilon );
const int MAX_CLIP_MODELS = 256;
int numClipModels = 0;
idClipModel* clipModels[ MAX_CLIP_MODELS ];
int coords[ 4 ];
CoordsForBounds( coords, parms );
int x, y;
for( x = coords[ 0 ]; x < coords[ 2 ]; x++ ) {
for( y = coords[ 1 ]; y < coords[ 3 ]; y++ ) {
clipSector_t* sector = &clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ];
for ( clipLink_t* link = sector->clipLinks; link; link = link->nextInSector ) {
idClipModel* model = link->clipModel;
if ( !( model->GetContents() & contentMask ) || ( model->GetEntity() == passEntity ) ) {
continue;
}
int i;
for ( i = 0; i < numClipModels; i++ ) {
if ( clipModels[ i ] == model ) {
break;
}
}
if ( i < numClipModels ) {
continue;
}
clipModels[ numClipModels ] = model;
numClipModels++;
if ( numClipModels >= MAX_CLIP_MODELS ) {
gameLocal.Warning( "idClip::ClipModelsTouchingBounds MAX_CLIP_MODELS hit" );
goto done;
}
}
}
}
done:
int count = 0;
for ( int i = 0; i < numClipModels; i++ ) {
idClipModel* model = clipModels[ i ];
// if the bounds really do overlap
if ( !model->GetAbsBounds().IntersectsBounds( parms ) ) {
continue;
}
if( count >= maxCount ) {
break;
}
clipModelList[ count++ ] = model;
}
if ( includeWorld && ( passEntity == NULL || passEntity != gameLocal.world ) ) {
for ( int i = 0; i < worldClips.Num(); i++ ) {
idClipModel *world = worldClips[ i ];
if ( ( world->GetContents() & contentMask ) != 0 ) {
if ( !world->GetAbsBounds().IntersectsBounds( parms ) ) {
continue;
}
if( count >= maxCount ) {
break;
}
clipModelList[ count++ ] = world;
}
}
}
return count;
}
/*
================
idClip::EntitiesTouchingRadius
================
*/
int idClip::EntitiesTouchingRadius( const idSphere& sphere, int contentMask, idEntity **entityList, int maxCount, bool nowarning ) const {
const idClipModel* clipModelList[ MAX_GENTITIES ];
int i, entCount;
if ( collisionModelManager->GetThreadId() != MAIN_THREAD_ID ) {
common->FatalError( "idClip::EntitiesTouchingRadius called from a thread other than the main thread" );
return 0;
}
bool checks[ MAX_GENTITIES ];
memset( checks, 0, sizeof( checks ) );
idBounds bounds( sphere );
int count = idClip::ClipModelsTouchingBounds( CLIP_DEBUG_PARMS bounds, contentMask, clipModelList, MAX_GENTITIES, NULL );
entCount = 0;
for ( i = 0; i < count; i++ ) {
idEntity* clipEnt = clipModelList[ i ]->GetEntity();
if ( checks[ clipEnt->entityNumber ] ) {
continue;
}
checks[ clipEnt->entityNumber ] = true;
if ( !sphere.IntersectsBounds( clipModelList[ i ]->GetAbsBounds() ) ) {
continue;
}
if ( entCount >= maxCount ) {
if ( !nowarning ) {
gameLocal.Warning( "idClip::EntitiesTouchingBounds: max count" );
}
return entCount;
}
entityList[ entCount ] = clipEnt;
entCount++;
}
return entCount;
}
/*
================
idClip::EntitiesTouchingBounds
================
*/
int idClip::EntitiesTouchingBounds( const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount, bool nowarning ) const {
const idClipModel *clipModelList[MAX_GENTITIES];
int i, count, entCount;
if ( collisionModelManager->GetThreadId() != MAIN_THREAD_ID ) {
common->FatalError( "idClip::EntitiesTouchingBounds called from a thread other than the main thread" );
return 0;
}
bool checks[ MAX_GENTITIES ];
memset( checks, 0, sizeof( checks ) );
count = idClip::ClipModelsTouchingBounds( CLIP_DEBUG_PARMS bounds, contentMask, clipModelList, MAX_GENTITIES, NULL );
entCount = 0;
for ( i = 0; i < count; i++ ) {
idEntity* clipEnt = clipModelList[ i ]->GetEntity();
if ( checks[ clipEnt->entityNumber ] ) {
continue;
}
checks[ clipEnt->entityNumber ] = true;
if ( entCount >= maxCount ) {
if ( !nowarning ) {
gameLocal.Warning( "idClip::EntitiesTouchingBounds: max count" );
}
return entCount;
}
entityList[ entCount ] = clipEnt;
entCount++;
}
return entCount;
}
/*
================
idClip::EntitiesTouchingBounds
================
*/
int idClip::EntitiesTouchingBounds( const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount, const idTypeInfo& type, bool nowarning ) const {
const idClipModel *clipModelList[MAX_GENTITIES];
int i, count, entCount;
if ( collisionModelManager->GetThreadId() != MAIN_THREAD_ID ) {
common->FatalError( "idClip::EntitiesTouchingBounds called from a thread other than the main thread" );
return 0;
}
bool checks[ MAX_GENTITIES ];
memset( checks, 0, sizeof( checks ) );
count = idClip::ClipModelsTouchingBounds( CLIP_DEBUG_PARMS bounds, contentMask, clipModelList, MAX_GENTITIES, NULL );
entCount = 0;
for ( i = 0; i < count; i++ ) {
idEntity* clipEnt = clipModelList[ i ]->GetEntity();
if ( checks[ clipEnt->entityNumber ] ) {
continue;
}
checks[ clipEnt->entityNumber ] = true;
if ( !clipEnt->IsType( type ) ) {
continue;
}
if ( entCount >= maxCount ) {
if ( !nowarning ) {
gameLocal.Warning( "idClip::EntitiesTouchingBounds: max count" );
}
return entCount;
}
entityList[ entCount ] = clipEnt;
entCount++;
}
return entCount;
}
const int MAX_CLIPMODELSTOUCHINGBOUNDS = 1024;
/*
====================
idClip::DrawAreaClipSectors
====================
*/
void idClip::DrawAreaClipSectors( float range ) const {
const idClipModel* clipModels[ MAX_CLIPMODELSTOUCHINGBOUNDS ];
idPlayer* player = gameLocal.GetLocalPlayer();
if ( !player ) {
return;
}
idBounds bounds;
bounds.GetMins() = player->GetPhysics()->GetOrigin() - idVec3( range, range, range );
bounds.GetMaxs() = player->GetPhysics()->GetOrigin() + idVec3( range, range, range );
int count = ClipModelsTouchingBounds( CLIP_DEBUG_PARMS bounds, MASK_ALL, clipModels, MAX_CLIPMODELSTOUCHINGBOUNDS, NULL );
int i;
for ( i = 0; i < count; i++ ) {
idEntity* owner = clipModels[ i ]->GetEntity();
const idVec3& org = clipModels[ i ]->GetOrigin();
const idBounds& bounds = clipModels[ i ]->GetBounds();
gameRenderWorld->DebugBounds( colorCyan, bounds, org );
gameRenderWorld->DrawText( owner->GetClassname(), org, 0.5f, colorCyan, player->viewAngles.ToMat3(), 1 );
}
}
/*
====================
idClip::DrawClipSectors
====================
*/
void idClip::DrawClipSectors( void ) const {
idBounds bounds;
idPlayer* player = gameLocal.GetLocalPlayer();
if ( !player ) {
return;
}
const char* filter = g_showClipSectorFilter.GetString();
idTypeInfo* type = idClass::GetClass( filter );
int x;
for( x = 0; x < CLIPSECTOR_WIDTH; x++ ) {
int y;
for( y = 0; y < CLIPSECTOR_WIDTH; y++ ) {
bounds[ 0 ].x = ( inverseNodeScale.x * x ) + nodeOffset.x + 1;
bounds[ 0 ].y = ( inverseNodeScale.y * y ) + nodeOffset.y + 1;
bounds[ 0 ].z = player->GetPhysics()->GetBounds().GetMins().z;
bounds[ 1 ].x = ( inverseNodeScale.x * ( x + 1 ) ) + nodeOffset.x - 1;
bounds[ 1 ].y = ( inverseNodeScale.y * ( y + 1 ) ) + nodeOffset.y - 1;
bounds[ 1 ].z = player->GetPhysics()->GetBounds().GetMaxs().z;
idVec3 point;
point.x = ( bounds[ 0 ].x + bounds[ 1 ].x ) * 0.5f;
point.y = ( bounds[ 0 ].y + bounds[ 1 ].y ) * 0.5f;
point.z = 0.f;
clipSector_t* sector = &clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ];
clipLink_t* link = sector->clipLinks;
while ( link ) {
if ( type && !link->clipModel->GetEntity()->IsType( *type ) ) {
link = link->nextInSector;
} else {
break;
}
}
if( link ) {
gameRenderWorld->DrawText( link->clipModel->GetEntity()->GetClassname(), point, 0.5f, colorCyan, player->viewAngles.ToMat3(), 1 );
gameRenderWorld->DebugBounds( colorMagenta, bounds );
gameRenderWorld->DebugBounds( colorYellow, link->clipModel->GetBounds(), link->clipModel->GetOrigin() );
} else {
// gameRenderWorld->DrawText( sector->clipLinks->clipModel->GetEntity()->GetClassname(), point, 0.08f, colorCyan, gameLocal.GetLocalPlayer()->viewAngles.ToMat3(), 1 );
}
}
}
}
/*
====================
idClip::GetClipSectorsStaticContents
====================
*/
void idClip::GetClipSectorsStaticContents( void ) {
idBounds bounds;
bounds[ 0 ].x = 0;
bounds[ 0 ].y = 0;
bounds[ 0 ].z = worldBounds.GetMins().z;
bounds[ 1 ].x = 1 / nodeScale.x;
bounds[ 1 ].y = 1 / nodeScale.y;
bounds[ 1 ].z = worldBounds.GetMaxs().z;
idTraceModel* trm = new idTraceModel( bounds );
idVec3 org;
org.z = 0;
int x;
for( x = 0; x < CLIPSECTOR_WIDTH; x++ ) {
int y;
for( y = 0; y < CLIPSECTOR_WIDTH; y++ ) {
org.x = ( x / nodeScale.x ) + nodeOffset.x;
org.y = ( y / nodeScale.y ) + nodeOffset.y;
int contents = collisionModelManager->Contents( org, trm, mat3_identity, -1, 0, vec3_origin, mat3_default );
clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ].contents = contents;
}
}
delete trm;
}
/*
============
idClip::TraceRenderModel
============
*/
void idClip::TraceRenderModel( trace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, const idMat3 &axis, int contentMask, idClipModel *mdl ) {
// avoid a crash when bot thread uses render model flag
if ( collisionModelManager->GetThreadId() != MAIN_THREAD_ID ) {
return;
}
// if the trace is passing through the bounds
if ( mdl->GetAbsBounds().Expand( radius ).LineIntersection( start, end ) ) {
modelTrace_t modelTrace;
idClip::numRenderModelTraces++;
// test with exact render model and modify trace_t structure accordingly
if ( gameRenderWorld->ModelTrace( modelTrace, mdl->GetRenderEntity(), start, end, radius, (contentMask & CONTENTS_SHADOWCOLLISION) ? SURF_SHADOWCOLLISION : SURF_COLLISION ) ) {
if ( modelTrace.fraction < trace.fraction ) {
trace.fraction = modelTrace.fraction;
trace.endAxis = axis;
trace.endpos = modelTrace.point;
trace.c.normal = modelTrace.normal;
trace.c.dist = modelTrace.point * modelTrace.normal;
trace.c.point = modelTrace.point;
trace.c.type = CONTACT_TRMVERTEX;
trace.c.modelFeature = 0;
trace.c.trmFeature = 0;
trace.c.contents = modelTrace.material->GetContentFlags();
trace.c.material = modelTrace.material;
trace.c.entityNum = mdl->GetEntityNumber();
trace.c.surfaceType = modelTrace.surfaceType;
trace.c.surfaceColor = modelTrace.surfaceColor;
// NOTE: trace.c.id will be the joint number
trace.c.id = JOINT_HANDLE_TO_CLIPMODEL_ID( modelTrace.jointNumber );
}
}
}
}
/*
============
idClip::GetTraceClipModels
============
*/
int idClip::GetTraceClipModels( const idVec3& start, const idVec3& end, int contentMask, const idEntity* passEntity, idClipModel** clipModelList, traceMode_t traceMode ) const {
idBounds parms;
parms.FromPointTranslation( start, end - start );
parms.ExpandSelf( vec3_boxEpsilon );
const int MAX_CLIP_MODELS = MAX_GENTITIES;
int numClipModels = 0;
idClipModel** clipModels = clipModelList;
int coords[ 4 ];
CoordsForBounds( coords, parms );
sdBounds2D bounds2d;
idVec2 dir = end.ToVec2() - start.ToVec2();
dir.Normalize();
idVec2 normal( dir.y, -dir.x );
bool mainThread = ( collisionModelManager->GetThreadId() == MAIN_THREAD_ID ); // only the main thread may use entity pointers
Lock();
// this must be after the Lock
int curmb = mailBoxID++;
for( int x = coords[ 0 ]; x < coords[ 2 ]; x++ ) {
for( int y = coords[ 1 ]; y < coords[ 3 ]; y++ ) {
idVec2 v[ 4 ];
v[ 0 ].x = ( inverseNodeScale.x * x ) + nodeOffset.x;
v[ 0 ].y = ( inverseNodeScale.y * y ) + nodeOffset.y;
v[ 1 ].x = ( inverseNodeScale.x * ( x + 1 ) ) + nodeOffset.x;
v[ 1 ].y = ( inverseNodeScale.y * ( y + 1 ) ) + nodeOffset.y;
v[ 2 ].x = v[ 0 ].x;
v[ 2 ].y = v[ 1 ].y;
v[ 3 ].x = v[ 1 ].x;
v[ 3 ].y = v[ 0 ].y;
int front = 0;
int back = 0;
int i;
for ( i = 0; i < 4; i++ ) {
float d = ( v[ i ] - start.ToVec2() ) * normal;
if ( d > idMath::FLT_EPSILON ) {
front++;
if ( back ) {
break;
}
} else if ( d < -idMath::FLT_EPSILON ) {
back++;
if ( front ) {
break;
}
} else {
break;
}
}
if ( i == 4 ) {
continue;
}
clipSector_t* sector = &clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ];
for ( clipLink_t* link = sector->clipLinks; link; link = link->nextInSector ) {
idClipModel *cm = link->clipModel;
const idBounds &bb = cm->GetAbsBounds();
if ( parms[1][2] < bb[0][2] ) {
continue;
}
if ( parms[0][2] > bb[1][2] ) {
continue;
}
#if 1
if ( cm->lastMailBox == curmb ) {
continue;
}
cm->lastMailBox = curmb;
#else
int i;
for ( i = 0; i < numClipModels; i++ ) {
if ( clipModels[ i ] == link->clipModel ) {
break;
}
}
if ( i < numClipModels ) {
continue;
}
#endif
clipModels[ numClipModels ] = link->clipModel;
numClipModels++;
if ( numClipModels >= MAX_CLIP_MODELS ) {
goto done;
}
}
}
}
done:
Unlock();
if ( numClipModels >= 256 && mainThread ) {
gameLocal.Warning( "Touching a large number of collision models!" );
}
int count = 0;
if ( passEntity != NULL ) {
for ( int i = 0; i < numClipModels; i++ ) {
idClipModel* model = clipModels[ i ];
if ( !( model->GetContents() & contentMask ) ) {
continue;
}
if ( !model->GetAbsBounds().LineIntersection( start, end ) ) {
continue;
}
if ( mainThread ) {
if ( !model->GetEntity()->CanCollide( passEntity, traceMode ) || !passEntity->CanCollide( model->GetEntity(), traceMode ) ) {
continue;
}
} else {
if ( model->GetEntity() == passEntity ) {
continue;
}
}
clipModelList[ count++ ] = model;
}
} else {
for ( int i = 0; i < numClipModels; i++ ) {
idClipModel* model = clipModels[ i ];
if ( !( model->GetContents() & contentMask ) ) {
continue;
}
if ( !model->GetAbsBounds().LineIntersection( start, end ) ) {
continue;
}
clipModelList[ count++ ] = model;
}
}
return count;
}
/*
============
idClip::GetTraceClipModels
============
*/
int idClip::GetTraceClipModels( const idBounds& bounds, int contentMask, const idEntity* passEntity, idClipModel** clipModelList, traceMode_t traceMode ) const {
idBounds parms( bounds[ 0 ] - vec3_boxEpsilon, bounds[ 1 ] + vec3_boxEpsilon );
const int MAX_CLIP_MODELS = MAX_GENTITIES;
int numClipModels = 0;
idClipModel** clipModels = clipModelList;
int coords[ 4 ];
CoordsForBounds( coords, parms );
bool mainThread = ( collisionModelManager->GetThreadId() == MAIN_THREAD_ID ); // only the main thread may use entity pointers
Lock();
// this must be after the Lock
int curmb = mailBoxID++;
int x, y;
for( x = coords[ 0 ]; x < coords[ 2 ]; x++ ) {
for( y = coords[ 1 ]; y < coords[ 3 ]; y++ ) {
clipSector_t* sector = &clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ];
for ( clipLink_t* link = sector->clipLinks; link; link = link->nextInSector ) {
idClipModel *cm = link->clipModel;
const idBounds &bb = cm->GetAbsBounds();
if ( parms[1][2] < bb[0][2] ) {
continue;
}
if ( parms[0][2] > bb[1][2] ) {
continue;
}
#if 1
if ( cm->lastMailBox == curmb ) {
continue;
}
cm->lastMailBox = curmb;
#else
int i;
for ( i = 0; i < numClipModels; i++ ) {
if ( clipModels[ i ] == link->clipModel ) {
break;
}
}
if ( i < numClipModels ) {
continue;
}
#endif
clipModels[ numClipModels ] = link->clipModel;
numClipModels++;
if ( numClipModels >= MAX_CLIP_MODELS ) {
goto done;
}
}
}
}
done:
Unlock();
if ( numClipModels >= 256 && mainThread ) {
gameLocal.Warning( "Touching a large number of collision models!" );
}
int count = 0;
if ( passEntity != NULL ) {
for ( int i = 0; i < numClipModels; i++ ) {
idClipModel* model = clipModels[ i ];
if ( !( model->GetContents() & contentMask ) ) {
continue;
}
// if the bounds really do overlap
if ( !model->GetAbsBounds().IntersectsBounds( parms ) ) {
continue;
}
if ( mainThread ) {
if ( !model->GetEntity()->CanCollide( passEntity, traceMode ) || !passEntity->CanCollide( model->GetEntity(), traceMode ) ) {
continue;
}
} else {
if ( model->GetEntity() == passEntity ) {
continue;
}
}
clipModelList[ count++ ] = model;
}
} else {
for ( int i = 0; i < numClipModels; i++ ) {
idClipModel* model = clipModels[ i ];
if ( !( model->GetContents() & contentMask ) ) {
continue;
}
// if the bounds really do overlap
if ( !model->GetAbsBounds().IntersectsBounds( parms ) ) {
continue;
}
clipModelList[ count++ ] = model;
}
}
return count;
}
/*
============
idClip::GetTraceClipModelsExt
This version doesn't do anything with the entity pointers, so is thread safe.
============
*/
int idClip::GetTraceClipModelsExt( const idVec3& start, const idVec3& end, int contentMask, const idEntity *passEntity1, const idEntity *passEntity2, idClipModel** clipModelList, traceMode_t traceMode ) const {
idBounds parms;
parms.FromPointTranslation( start, end - start );
parms.ExpandSelf( vec3_boxEpsilon );
const int MAX_CLIP_MODELS = MAX_GENTITIES;
int numClipModels = 0;
idClipModel** clipModels = clipModelList;
int coords[ 4 ];
CoordsForBounds( coords, parms );
sdBounds2D bounds2d;
idVec2 dir = end.ToVec2() - start.ToVec2();
dir.Normalize();
idVec2 normal( dir.y, -dir.x );
bool mainThread = ( collisionModelManager->GetThreadId() == MAIN_THREAD_ID ); // only the main thread may use entity pointers
Lock();
// this must be after the Lock
int curmb = mailBoxID++;
for( int x = coords[ 0 ]; x < coords[ 2 ]; x++ ) {
for( int y = coords[ 1 ]; y < coords[ 3 ]; y++ ) {
idVec2 v[ 4 ];
v[ 0 ].x = ( inverseNodeScale.x * x ) + nodeOffset.x;
v[ 0 ].y = ( inverseNodeScale.y * y ) + nodeOffset.y;
v[ 1 ].x = ( inverseNodeScale.x * ( x + 1 ) ) + nodeOffset.x;
v[ 1 ].y = ( inverseNodeScale.y * ( y + 1 ) ) + nodeOffset.y;
v[ 2 ].x = v[ 0 ].x;
v[ 2 ].y = v[ 1 ].y;
v[ 3 ].x = v[ 1 ].x;
v[ 3 ].y = v[ 0 ].y;
int front = 0;
int back = 0;
int i;
for ( i = 0; i < 4; i++ ) {
float d = ( v[ i ] - start.ToVec2() ) * normal;
if ( d > idMath::FLT_EPSILON ) {
front++;
if ( back ) {
break;
}
} else if ( d < -idMath::FLT_EPSILON ) {
back++;
if ( front ) {
break;
}
} else {
break;
}
}
if ( i == 4 ) {
continue;
}
clipSector_t* sector = &clipSectors[ x + ( y << CLIPSECTOR_DEPTH ) ];
for ( clipLink_t* link = sector->clipLinks; link; link = link->nextInSector ) {
idClipModel *cm = link->clipModel;
const idBounds &bb = cm->GetAbsBounds();
if ( parms[1][2] < bb[0][2] ) {
continue;
}
if ( parms[0][2] > bb[1][2] ) {
continue;
}
#if 1
if ( cm->lastMailBox == curmb ) {
continue;
}
cm->lastMailBox = curmb;
#else
int i;
for ( i = 0; i < numClipModels; i++ ) {
if ( clipModels[ i ] == link->clipModel ) {
break;
}
}
if ( i < numClipModels ) {
continue;
}
#endif
clipModels[ numClipModels ] = link->clipModel;
numClipModels++;
if ( numClipModels >= MAX_CLIP_MODELS ) {
goto done;
}
}
}
}
done:
Unlock();
if ( numClipModels >= 256 && mainThread ) {
gameLocal.Warning( "Touching a large number of collision models!" );
}
int count = 0;
if ( passEntity1 != NULL || passEntity2 != NULL ) {
for ( int i = 0; i < numClipModels; i++ ) {
idClipModel* model = clipModels[ i ];
if ( !( model->GetContents() & contentMask ) ) {
continue;
}
if ( !model->GetAbsBounds().LineIntersection( start, end ) ) {
continue;
}
if ( ( passEntity1 != NULL && model->GetEntity() == passEntity1 ) || ( passEntity2 != NULL && model->GetEntity() == passEntity2 ) ) {
continue;
}
clipModelList[ count++ ] = model;
}
} else {
for ( int i = 0; i < numClipModels; i++ ) {
idClipModel* model = clipModels[ i ];
if ( !( model->GetContents() & contentMask ) ) {
continue;
}
if ( !model->GetAbsBounds().LineIntersection( start, end ) ) {
continue;
}
clipModelList[ count++ ] = model;
}
}
return count;
}
/*
============
idClip::TestHugeTranslation
============
*/
ID_INLINE bool idClip::TestHugeTranslation( trace_t &results, const idClipModel *mdl, const idVec3 &start, const idVec3 &end, const idMat3 &trmAxis ) const {
if ( mdl != NULL && ( end - start ).LengthSqr() > Square( CM_MAX_TRACE_DIST ) ) {
results.fraction = 0.0f;
results.endpos = start;
results.endAxis = trmAxis;
memset( &results.c, 0, sizeof( results.c ) );
results.c.entityNum = ENTITYNUM_WORLD;
results.c.normal.Set( 0.f, 0.f, 1.f );
results.c.point = start;
if ( collisionModelManager->GetThreadId() != MAIN_THREAD_ID ) {
common->FatalError( "huge translation from a thread other than the main thread" );
}
if ( mdl->GetEntity() ) {
gameLocal.Printf( "huge translation for clip model %d on entity %d '%s'\n", mdl->GetId(), mdl->GetEntityNumber(), mdl->GetEntityName() );
} else {
gameLocal.Printf( "huge translation for clip model %d\n", mdl->GetId() );
}
return true;
}
return false;
}
/*
============
idClip::TranslationClipModel
============
*/
ID_INLINE bool idClip::TranslationClipModel( CLIP_DEBUG_PARMS_DECLARATION trace_t &results, const idVec3 &start, const idVec3 &end,
const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, const idClipModel *mdl ) {
int i;
trace_t trace;
#ifdef CLIP_DEBUG
idClipTimer timer( CLIP_DEBUG_PARMS_PASSTHRU this, CTM_TRANSLATION );
#endif // CLIP_DEBUG
for ( i = 0; i < mdl->GetNumCollisionModels(); i++ ) {
idClip::numTranslations++;
collisionModelManager->Translation( &trace, start, end, trm, trmAxis, contentMask,
mdl->GetCollisionModel( i ), mdl->GetOrigin(), mdl->GetAxis() );
if ( trace.fraction < results.fraction ) {
results = trace;
results.c.entityNum = mdl->GetEntityNumber();
results.c.id = mdl->GetId();
if ( results.fraction == 0.0f ) {
return true;
}
}
}
return false;
}
/*
============
idClip::RotationClipModel
============
*/
ID_INLINE bool idClip::RotationClipModel( CLIP_DEBUG_PARMS_DECLARATION trace_t &results, const idVec3 &start, const idRotation &rotation,
const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, const idClipModel *mdl ) {
int i;
trace_t trace;
#ifdef CLIP_DEBUG
idClipTimer timer( CLIP_DEBUG_PARMS_PASSTHRU this, CTM_ROTATION );
#endif // CLIP_DEBUG
for ( i = 0; i < mdl->GetNumCollisionModels(); i++ ) {
idClip::numRotations++;
collisionModelManager->Rotation( &trace, start, rotation, trm, trmAxis, contentMask,
mdl->GetCollisionModel( i ), mdl->GetOrigin(), mdl->GetAxis() );
if ( trace.fraction < results.fraction ) {
results = trace;
results.c.entityNum = mdl->GetEntityNumber();
results.c.id = mdl->GetId();
if ( results.fraction == 0.0f ) {
return true;
}
}
}
return false;
}
/*
============
idClip::ContactsClipModel
============
*/
ID_INLINE int idClip::ContactsClipModel( CLIP_DEBUG_PARMS_DECLARATION contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec3 *dir, const float depth,
const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, const idClipModel *mdl ) {
int i, j, n;
int numContacts;
numContacts = 0;
#ifdef CLIP_DEBUG
idClipTimer timer( CLIP_DEBUG_PARMS_PASSTHRU this, CTM_CONTACTS );
#endif // CLIP_DEBUG
for ( i = 0; i < mdl->GetNumCollisionModels(); i++ ) {
idClip::numContacts++;
n = collisionModelManager->Contacts( contacts + numContacts, maxContacts - numContacts,
start, dir, depth, trm, trmAxis, contentMask,
mdl->GetCollisionModel( i ), mdl->GetOrigin(), mdl->GetAxis() );
for ( j = 0; j < n; j++ ) {
contacts[numContacts].entityNum = mdl->GetEntityNumber();
contacts[numContacts].id = mdl->GetId();
numContacts++;
}
if ( numContacts >= maxContacts ) {
return numContacts;
}
}
return numContacts;
}
/*
============
idClip::ContentsClipModel
============
*/
ID_INLINE int idClip::ContentsClipModel( CLIP_DEBUG_PARMS_DECLARATION const idVec3 &start,
const idTraceModel *trm, const idMat3 &trmAxis, int contentMask, const idClipModel *mdl ) {
int i;
int contents = 0;
#ifdef CLIP_DEBUG
idClipTimer timer( CLIP_DEBUG_PARMS_PASSTHRU this, CTM_CONTENTS );
#endif // CLIP_DEBUG
for ( i = 0; i < mdl->GetNumCollisionModels(); i++ ) {
idClip::numContents++;
if ( collisionModelManager->Contents( start, trm, trmAxis, contentMask, mdl->GetCollisionModel( i ), mdl->GetOrigin(), mdl->GetAxis() ) ) {
contents |= ( mdl->GetContents() & contentMask );
}
}
return contents;
}
/*
============
idClip::TranslationEntities
============
*/
bool idClip::TranslationEntities( CLIP_DEBUG_PARMS_DECLARATION trace_t &results, const idVec3 &start, const idVec3 &end,
const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, traceMode_t traceMode ) {
int i, j, num;
idClipModel *touch, *clipModelList[MAX_GENTITIES];
idBounds traceBounds;
float radius;
if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) {
return true;
}
results.fraction = 1.0f;
results.endpos = end;
results.endAxis = trmAxis;
if ( mdl == NULL ) {
radius = 0.0f;
num = GetTraceClipModels( start, end, contentMask, passEntity, clipModelList, traceMode );
} else {
idBounds traceBounds;
traceBounds.FromBoundsTranslation( mdl->GetBounds(), start, trmAxis, end - start );
radius = mdl->GetBounds().GetRadius();
num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList, traceMode );
}
for ( i = 0; i < num; i++ ) {
touch = clipModelList[ i ];
if ( touch->IsRenderModel() ) {
TraceRenderModel( results, start, end, radius, trmAxis, contentMask, touch );
} else if ( mdl == NULL ) {
if ( TranslationClipModel( CLIP_DEBUG_PARMS_PASSTHRU results, start, end, NULL, trmAxis, contentMask, touch ) ) {
results.c.selfId = -1;
return true;
}
} else {
for ( j = 0; j < mdl->GetNumTraceModels(); j++ ) {
if ( TranslationClipModel( CLIP_DEBUG_PARMS_PASSTHRU results, start, end, mdl->GetTraceModel( j ), trmAxis, contentMask, touch ) ) {
results.c.selfId = mdl->GetId();
return true;
}
}
}
}
results.c.selfId = mdl ? mdl->GetId() : -1;
return ( results.fraction < 1.0f );
}
typedef sdPair< idEntity*, float > entFracPair_t;
int SortByEntsByFraction( const entFracPair_t* a, const entFracPair_t* b ) {
return b->second - a->second;
}
/*
============
idClip::EntitiesForTranslation
============
*/
int idClip::EntitiesForTranslation( CLIP_DEBUG_PARMS_DECLARATION const idVec3 &start, const idVec3 &end, int contentMask, const idEntity* passEntity, idEntity** entities, int maxEntities, traceMode_t traceMode ) {
trace_t results;
results.endpos = end;
results.endAxis = mat3_identity;
memset( &results.c, 0, sizeof( results.c ) );
bool checks[ MAX_GENTITIES ];
memset( checks, 0, sizeof( checks ) );
static idStaticList< entFracPair_t, MAX_GENTITIES > localEnts;
localEnts.Clear();
idBounds traceBounds;
traceBounds.FromPointTranslation( start, end - start );
idClipModel* clipModelList[ MAX_GENTITIES ];
int numClipModels = GetTraceClipModels( start, end, contentMask, passEntity, clipModelList, traceMode );
for ( int i = 0; i < numClipModels; i++ ) {
idClipModel* touch = clipModelList[ i ];
results.fraction = 1.0f;
if ( touch->IsRenderModel() ) {
TraceRenderModel( results, start, end, 0.f, mat3_identity, contentMask, touch );
} else {
TranslationClipModel( CLIP_DEBUG_PARMS_PASSTHRU results, start, end, NULL, mat3_identity, contentMask, touch );
}
idEntity* ent = touch->GetEntity();
if ( checks[ ent->entityNumber ] ) {
for ( int j = 0; j < localEnts.Num(); j++ ) {
if ( localEnts[ j ].first == ent ) {
if ( results.fraction < localEnts[ j ].second ) {
localEnts[ j ].second = results.fraction;
}
}
}
} else {
entFracPair_t* newEnt = localEnts.Alloc();
if ( !newEnt ) {
break;
}
newEnt->first = ent;
newEnt->second = results.fraction;
checks[ ent->entityNumber ] = true;
}
}
localEnts.Sort( SortByEntsByFraction );
for ( int i = 0; i < localEnts.Num(); i++ ) {
if ( i < maxEntities ) {
entities[ i ] = localEnts[ i ].first;
}
}
return localEnts.Num();
}
/*
============
idClip::Translation
============
*/
bool idClip::Translation( CLIP_DEBUG_PARMS_DECLARATION trace_t &results, const idVec3 &start, const idVec3 &end,
const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, traceMode_t traceMode, float forceRadius ) {
int i, j, num;
idClipModel *touch, *clipModelList[MAX_GENTITIES];
idBounds traceBounds;
float radius;
if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) {
return true;
}
results.fraction = 1.0f;
results.endpos = end;
results.endAxis = trmAxis;
memset( &results.c, 0, sizeof( results.c ) );
if ( mdl == NULL ) {
traceBounds.FromPointTranslation( start, end - start );
} else {
traceBounds.FromBoundsTranslation( mdl->GetBounds(), start, trmAxis, end - start );
}
// NOTE: only comparing and not actually using the passEntity pointer to keep this code thread safe
if ( passEntity == NULL || passEntity != gameLocal.world ) {
for ( int i = 0; i < worldClips.Num(); i++ ) {
idClipModel *world = worldClips[ i ];
if ( ( world->GetContents() & contentMask ) != 0 ) {
if ( !world->GetAbsBounds().IntersectsBounds( traceBounds ) ) {
continue;
}
// test world
if ( mdl == NULL ) {
if ( TranslationClipModel( CLIP_DEBUG_PARMS_PASSTHRU results, start, end, NULL, trmAxis, contentMask, world ) ) {
results.c.selfId = -1;
return true; // blocked immediately by the world
}
} else {
for ( j = 0; j < mdl->GetNumTraceModels(); j++ ) {
if ( TranslationClipModel( CLIP_DEBUG_PARMS_PASSTHRU results, start, end, mdl->GetTraceModel( j ), trmAxis, contentMask, world ) ) {
results.c.selfId = mdl->GetId();
return true; // blocked immediately by the world
}
}
}
}
}
}
if ( mdl == NULL ) {
radius = 0.0f;
num = GetTraceClipModels( start, results.endpos, contentMask, passEntity, clipModelList, traceMode );
} else {
radius = mdl->GetBounds().GetRadius();
num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList, traceMode );
}
for ( i = 0; i < num; i++ ) {
touch = clipModelList[ i ];
if ( touch->IsRenderModel() ) {
TraceRenderModel( results, start, end, radius, trmAxis, contentMask, touch );
} else if ( mdl == NULL ) {
if ( TranslationClipModel( CLIP_DEBUG_PARMS_PASSTHRU results, start, end, NULL, trmAxis, contentMask, touch ) ) {
results.c.selfId = -1;
return true;
}
} else {
for ( j = 0; j < mdl->GetNumTraceModels(); j++ ) {
if ( TranslationClipModel( CLIP_DEBUG_PARMS_PASSTHRU results, start, end, mdl->GetTraceModel( j ), trmAxis, contentMask, touch ) ) {
results.c.selfId = mdl->GetId();
return true;
}
}
}
}
if ( forceRadius > 0.f ) {
if ( results.c.entityNum == ENTITYNUM_NONE || results.c.entityNum == ENTITYNUM_WORLD ) {
for ( i = 0; i < num; i++ ) {
touch = clipModelList[ i ];
if ( touch->IsRenderModel() ) {
TraceRenderModel( results, start, end, forceRadius, trmAxis, contentMask, touch );
}
}
}
}
results.c.selfId = mdl ? mdl->GetId() : -1;
return ( results.fraction < 1.0f );
}
/*
============
idClip::TracePointExt
============
*/
bool idClip::TracePointExt( CLIP_DEBUG_PARMS_DECLARATION trace_t &results, const idVec3 &start, const idVec3 &end, int contentMask, const idEntity *passEntity1, const idEntity *passEntity2, traceMode_t traceMode ) {
int i, num;
idClipModel *touch, *clipModelList[MAX_GENTITIES];
idBounds traceBounds;
if ( TestHugeTranslation( results, NULL, start, end, mat3_identity ) ) {
return true;
}
results.fraction = 1.0f;
results.endpos = end;
results.endAxis = mat3_identity;
memset( &results.c, 0, sizeof( results.c ) );
results.c.entityNum = -1;
traceBounds.FromPointTranslation( start, end - start );
// NOTE: only comparing and not actually using the passEntity pointer to keep this code thread safe
if ( ( passEntity1 == NULL || passEntity1 != gameLocal.world ) && ( passEntity2 == NULL || passEntity2 != gameLocal.world ) ) {
for ( int i = 0; i < worldClips.Num(); i++ ) {
idClipModel *world = worldClips[ i ];
if ( ( world->GetContents() & contentMask ) != 0 ) {
if ( !world->GetAbsBounds().IntersectsBounds( traceBounds ) ) {
continue;
}
// test world
if ( TranslationClipModel( CLIP_DEBUG_PARMS_PASSTHRU results, start, end, NULL, mat3_identity, contentMask, world ) ) {
results.c.selfId = -1;
return true; // blocked immediately by the world
}
}
}
}
num = GetTraceClipModelsExt( start, results.endpos, contentMask, passEntity1, passEntity2, clipModelList, traceMode );
for ( i = 0; i < num; i++ ) {
touch = clipModelList[ i ];
if ( TranslationClipModel( CLIP_DEBUG_PARMS_PASSTHRU results, start, end, NULL, mat3_identity, contentMask, touch ) ) {
results.c.selfId = -1;
return true;
}
}
results.c.selfId = -1;
return ( results.fraction < 1.0f );
}
/*
============
idClip::RotationInternal
============
*/
bool idClip::RotationInternal( CLIP_DEBUG_PARMS_DECLARATION trace_t &results, const idVec3 &start, const idRotation &rotation,
const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, traceMode_t traceMode ) {
int i, j, num;
idClipModel *touch, *clipModelList[MAX_GENTITIES];
idBounds traceBounds;
results.fraction = 1.0f;
results.endpos = start;
results.endAxis = trmAxis * rotation.ToMat3();
rotation.RotatePoint( results.endpos );
memset( &results.c, 0, sizeof( results.c ) );
results.c.selfId = mdl ? mdl->GetId() : -1;
if ( mdl == NULL ) {
traceBounds.FromPointRotation( start, rotation );
} else {
traceBounds.FromBoundsRotation( mdl->GetBounds(), start, trmAxis, rotation );
}
// NOTE: only comparing and not actually using the passEntity pointer to keep this code thread safe
if ( passEntity == NULL || passEntity != gameLocal.world ) {
for (int i = 0; i < worldClips.Num(); i++ ) {
idClipModel *world = worldClips[ i ];
if ( ( world->GetContents() & contentMask ) != 0 ) {
if ( !world->GetAbsBounds().IntersectsBounds( traceBounds ) ) {
continue;
}
// test world
if ( mdl == NULL ) {
if ( RotationClipModel( CLIP_DEBUG_PARMS_PASSTHRU results, start, rotation, NULL, trmAxis, contentMask, world ) ) {
results.c.selfId = mdl ? mdl->GetId() : -1;
return true; // blocked immediately by the world
}
} else {
for ( j = 0; j < mdl->GetNumTraceModels(); j++ ) {
if ( RotationClipModel( CLIP_DEBUG_PARMS_PASSTHRU results, start, rotation, mdl->GetTraceModel( j ), trmAxis, contentMask, world ) ) {
results.c.selfId = mdl ? mdl->GetId() : -1;
return true; // blocked immediately by the world
}
}
}
}
}
}
num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList, traceMode );
for ( i = 0; i < num; i++ ) {
touch = clipModelList[i];
if ( touch == NULL ) {
continue;
}
if ( touch->IsRenderModel() ) {
continue; // no rotational collision with render models
} else if ( mdl == NULL ) {
if ( RotationClipModel( CLIP_DEBUG_PARMS_PASSTHRU results, start, rotation, NULL, trmAxis, contentMask, touch ) ) {
results.c.selfId = mdl ? mdl->GetId() : -1;
return true;
}
} else {
for ( j = 0; j < mdl->GetNumTraceModels(); j++ ) {
if ( RotationClipModel( CLIP_DEBUG_PARMS_PASSTHRU results, start, rotation, mdl->GetTraceModel( j ), trmAxis, contentMask, touch ) ) {
results.c.selfId = mdl ? mdl->GetId() : -1;
return true;
}
}
}
}
results.c.selfId = mdl ? mdl->GetId() : -1;
return ( results.fraction < 1.0f );
}
/*
============
idClip::MotionInternal
============
*/
bool idClip::MotionInternal( CLIP_DEBUG_PARMS_DECLARATION trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation,
const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, traceMode_t traceMode ) {
int i, j, num;
idClipModel *touch, *clipModelList[MAX_GENTITIES];
idVec3 dir, endPosition;
idBounds traceBounds;
float radius;
trace_t translationalTrace, rotationalTrace, trace;
idRotation endRotation;
assert( rotation.GetOrigin() == start );
assert( mdl != NULL );
if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) {
return true;
}
if ( mdl != NULL && rotation.GetAngle() != 0.0f && rotation.GetVec() != vec3_origin ) {
// if no translation
if ( start == end ) {
// pure rotation
results.c.selfId = mdl ? mdl->GetId() : -1;
return Rotation( CLIP_DEBUG_PARMS_PASSTHRU results, start, rotation, mdl, trmAxis, contentMask, passEntity );
}
} else if ( start != end ) {
// pure translation
results.c.selfId = mdl ? mdl->GetId() : -1;
return Translation( CLIP_DEBUG_PARMS_PASSTHRU results, start, end, mdl, trmAxis, contentMask, passEntity );
} else {
// no motion
results.fraction = 1.0f;
results.endpos = start;
results.endAxis = trmAxis;
memset( &results.c, 0, sizeof( results.c ) );
results.c.selfId = mdl ? mdl->GetId() : -1;
return false;
}
translationalTrace.fraction = 1.0f;
translationalTrace.endpos = end;
translationalTrace.endAxis = trmAxis;
memset( &translationalTrace.c, 0, sizeof( translationalTrace.c ) );
// NOTE: only comparing and not actually using the passEntity pointer to keep this code thread safe
if ( passEntity == NULL || passEntity != gameLocal.world ) {
for ( int i = 0; i < worldClips.Num(); i++ ) {
idClipModel *world = worldClips[ i ];
if ( ( world->GetContents() & contentMask ) != 0 ) {
// translational collision with world
for ( j = 0; j < mdl->GetNumTraceModels(); j++ ) {
if ( TranslationClipModel( CLIP_DEBUG_PARMS_PASSTHRU translationalTrace, start, end, mdl->GetTraceModel( j ), trmAxis, contentMask, world ) ) {
break;
}
}
}
}
}
if ( translationalTrace.fraction != 0.0f ) {
traceBounds.FromBoundsRotation( mdl->GetBounds(), start, trmAxis, rotation );
dir = translationalTrace.endpos - start;
for ( i = 0; i < 3; i++ ) {
if ( dir[i] < 0.0f ) {
traceBounds[0][i] += dir[i];
} else {
traceBounds[1][i] += dir[i];
}
}
radius = mdl->GetBounds().GetRadius();
num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList, traceMode );
for ( i = 0; i < num; i++ ) {
touch = clipModelList[i];
if ( touch == NULL ) {
continue;
}
if ( touch->IsRenderModel() ) {
TraceRenderModel( translationalTrace, start, end, radius, trmAxis, contentMask, touch );
} else {
for ( j = 0; j < mdl->GetNumTraceModels(); j++ ) {
if ( TranslationClipModel( CLIP_DEBUG_PARMS_PASSTHRU translationalTrace, start, end, mdl->GetTraceModel( j ), trmAxis, contentMask, touch ) ) {
break;
}
}
}
if ( translationalTrace.fraction == 0.0f ) {
break;
}
}
} else {
num = -1;
}
endPosition = translationalTrace.endpos;
endRotation = rotation;
endRotation.SetOrigin( endPosition );
rotationalTrace.fraction = 1.0f;
rotationalTrace.endpos = endPosition;
rotationalTrace.endAxis = trmAxis * endRotation.ToMat3();
memset( &rotationalTrace.c, 0, sizeof( rotationalTrace.c ) );
// NOTE: only comparing and not actually using the passEntity pointer to keep this code thread safe
if ( passEntity == NULL || passEntity != gameLocal.world ) {
for ( int i = 0; i < worldClips.Num(); i++ ) {
idClipModel *world = worldClips[ i ];
if ( ( world->GetContents() & contentMask ) != 0 ) {
// rotational collision with world
for ( j = 0; j < mdl->GetNumTraceModels(); j++ ) {
if ( RotationClipModel( CLIP_DEBUG_PARMS_PASSTHRU rotationalTrace, endPosition, endRotation, mdl->GetTraceModel( j ), trmAxis, contentMask, world ) ) {
break;
}
}
}
}
}
if ( rotationalTrace.fraction != 0.0f ) {
if ( num == -1 ) {
traceBounds.FromBoundsRotation( mdl->GetBounds(), endPosition, trmAxis, endRotation );
num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList, traceMode );
}
for ( i = 0; i < num; i++ ) {
touch = clipModelList[i];
if ( touch == NULL ) {
continue;
}
if ( touch->IsRenderModel() ) {
continue; // no rotational collision detection with render models
} else {
for ( j = 0; j < mdl->GetNumTraceModels(); j++ ) {
if ( RotationClipModel( CLIP_DEBUG_PARMS_PASSTHRU rotationalTrace, endPosition, endRotation, mdl->GetTraceModel( j ), trmAxis, contentMask, touch ) ) {
break;
}
}
}
if ( rotationalTrace.fraction == 0.0f ) {
break;
}
}
}
if ( rotationalTrace.fraction < 1.0f ) {
results = rotationalTrace;
} else {
results = translationalTrace;
results.endAxis = rotationalTrace.endAxis;
}
results.fraction = Min( translationalTrace.fraction, rotationalTrace.fraction );
results.c.selfId = mdl ? mdl->GetId() : -1;
return ( translationalTrace.fraction < 1.0f || rotationalTrace.fraction < 1.0f );
}
/*
============
idClip::Contacts
============
*/
int idClip::Contacts( CLIP_DEBUG_PARMS_DECLARATION contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec3 *dir, const float depth,
const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, traceMode_t traceMode ) {
int i, j, num, numContacts;
idClipModel *touch, *clipModelList[MAX_GENTITIES];
idBounds traceBounds;
numContacts = 0;
if ( mdl == NULL ) {
traceBounds = idBounds( start ).Expand( depth );
} else {
traceBounds.FromTransformedBounds( mdl->GetBounds(), start, trmAxis );
traceBounds.ExpandSelf( depth );
}
// NOTE: only comparing and not actually using the passEntity pointer to keep this code thread safe
if ( passEntity == NULL || passEntity != gameLocal.world ) {
for ( int i = 0; i < worldClips.Num(); i++ ) {
idClipModel *world = worldClips[ i ];
if ( ( world->GetContents() & contentMask ) != 0 ) {
if ( !world->GetAbsBounds().IntersectsBounds( traceBounds ) ) {
continue;
}
// test world
if ( mdl == NULL ) {
int count = ContactsClipModel( CLIP_DEBUG_PARMS_PASSTHRU contacts + numContacts, maxContacts - numContacts, start, dir, depth, NULL, trmAxis, contentMask, world );
for ( int i = 0; i < count; i++ ) {
contacts[ numContacts + i ].selfId = -1;
}
numContacts += count;
if ( numContacts >= maxContacts ) {
return numContacts;
}
} else {
for ( j = 0; j < mdl->GetNumTraceModels(); j++ ) {
int count = ContactsClipModel( CLIP_DEBUG_PARMS_PASSTHRU contacts + numContacts, maxContacts - numContacts, start, dir, depth, mdl->GetTraceModel( j ), trmAxis, contentMask, world );
for ( int i = 0; i < count; i++ ) {
contacts[ numContacts + i ].selfId = mdl->GetId();
}
numContacts += count;
if ( numContacts >= maxContacts ) {
return numContacts;
}
}
}
}
}
}
num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList, traceMode );
for ( i = 0; i < num; i++ ) {
touch = clipModelList[i];
if ( touch == NULL ) {
continue;
}
if ( touch->IsRenderModel() ) {
continue; // no contacts with render models
} else if ( mdl == NULL ) {
int count = ContactsClipModel( CLIP_DEBUG_PARMS_PASSTHRU contacts + numContacts, maxContacts - numContacts, start, dir, depth, NULL, trmAxis, contentMask, touch );
for ( int i = 0; i < count; i++ ) {
contacts[ numContacts + i ].selfId = -1;
}
numContacts += count;
if ( numContacts >= maxContacts ) {
return numContacts;
}
} else {
for ( j = 0; j < mdl->GetNumTraceModels(); j++ ) {
int count = ContactsClipModel( CLIP_DEBUG_PARMS_PASSTHRU contacts + numContacts, maxContacts - numContacts, start, dir, depth, mdl->GetTraceModel( j ), trmAxis, contentMask, touch );
for ( int i = 0; i < count; i++ ) {
contacts[ numContacts + i ].selfId = mdl->GetId();
}
numContacts += count;
if ( numContacts >= maxContacts ) {
return numContacts;
}
}
}
}
return numContacts;
}
/*
============
idClip::Contents
============
*/
int idClip::Contents( CLIP_DEBUG_PARMS_DECLARATION const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity, traceMode_t traceMode ) {
int i, j, num, contents;
idClipModel *touch, *clipModelList[MAX_GENTITIES];
idBounds traceBounds;
contents = 0;
if ( mdl == NULL ) {
traceBounds[0] = start;
traceBounds[1] = start;
} else if ( trmAxis.IsRotated() ) {
traceBounds.FromTransformedBounds( mdl->GetBounds(), start, trmAxis );
} else {
traceBounds[0] = mdl->GetBounds()[0] + start;
traceBounds[1] = mdl->GetBounds()[1] + start;
}
// NOTE: only comparing and not actually using the passEntity pointer to keep this code thread safe
if ( passEntity == NULL || passEntity != gameLocal.world ) {
for (int i = 0; i < worldClips.Num(); i++) {
idClipModel *world = worldClips[ i ];
if ( ( world->GetContents() & contentMask ) != 0 ) {
if ( !world->GetAbsBounds().IntersectsBounds( traceBounds ) ) {
continue;
}
// test world
if ( mdl == NULL ) {
contents |= ContentsClipModel( CLIP_DEBUG_PARMS_PASSTHRU start, NULL, trmAxis, contentMask, world );
} else {
for ( j = 0; j < mdl->GetNumTraceModels(); j++ ) {
contents |= ContentsClipModel( CLIP_DEBUG_PARMS_PASSTHRU start, mdl->GetTraceModel( j ), trmAxis, contentMask, world );
}
}
}
}
}
num = GetTraceClipModels( traceBounds, -1, passEntity, clipModelList, traceMode );
for ( i = 0; i < num; i++ ) {
touch = clipModelList[i];
if ( touch == NULL ) {
continue;
}
// no contents test with render models
if ( touch->IsRenderModel() ) {
continue;
}
// if the entity does not have any contents we are looking for
if ( ( touch->GetContents() & contentMask ) == 0 ) {
continue;
}
// if the entity has no new contents flags
if ( ( touch->GetContents() & contents ) == touch->GetContents() ) {
continue;
}
if ( mdl == NULL ) {
contents |= ContentsClipModel( CLIP_DEBUG_PARMS_PASSTHRU start, NULL, trmAxis, contentMask, touch );
} else {
for ( j = 0; j < mdl->GetNumTraceModels(); j++ ) {
contents |= ContentsClipModel( CLIP_DEBUG_PARMS_PASSTHRU start, mdl->GetTraceModel( j ), trmAxis, contentMask, touch );
}
}
}
return contents;
}
/*
============
idClip::GetTemporaryClipModel
============
*/
const idClipModel* idClip::GetTemporaryClipModel( const idBounds& bounds ) {
// FIXME: this creates a new idCollisionModel for each different bounds
temporaryClipModel->LoadTraceModel( idTraceModel( bounds ), false );
return temporaryClipModel;
}
/*
============
idClip::GetThirdPersonOffsetModel
============
*/
const idClipModel* idClip::GetThirdPersonOffsetModel( void ) {
return thirdPersonOffsetModel;
}
/*
============
idClip::GetBigThirdPersonOffsetModel
============
*/
const idClipModel* idClip::GetBigThirdPersonOffsetModel( void ) {
return bigThirdPersonOffsetModel;
}
/*
============
idClip::GetLeanOffsetModel
============
*/
const idClipModel* idClip::GetLeanOffsetModel( void ) {
return leanOffsetModel;
}
/*
============
idClip::TraceBounds
============
*/
bool idClip::TraceBounds( CLIP_DEBUG_PARMS_DECLARATION trace_t &results, const idVec3 &start, const idVec3 &end, const idBounds &bounds, const idMat3& axis,
int contentMask, const idEntity *passEntity, traceMode_t traceMode ) {
if ( collisionModelManager->GetThreadId() != MAIN_THREAD_ID ) {
common->FatalError( "idClip::TraceBounds called from a thread other than the main thread" );
return false;
}
Translation( CLIP_DEBUG_PARMS_PASSTHRU results, start, end, GetTemporaryClipModel( bounds ), axis, contentMask, passEntity, traceMode );
return ( results.fraction < 1.0f );
}
/*
============
idClip::TranslationModel
============
*/
void idClip::TranslationModel( CLIP_DEBUG_PARMS_DECLARATION trace_t &results, const idVec3 &start, const idVec3 &end,
const idClipModel *trm, const idMat3 &trmAxis, int contentMask,
const idClipModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) {
#ifdef CLIP_DEBUG
idClipTimer timer( CLIP_DEBUG_PARMS_PASSTHRU this, CTM_TRANSLATION );
#endif // CLIP_DEBUG
int i, j;
trace_t trace;
results.fraction = 1.0f;
if ( !( model->GetContents() & contentMask ) ) {
return;
}
if ( trm == NULL ) {
for ( j = 0; j < model->GetNumCollisionModels(); j++ ) {
idClip::numTranslations++;
collisionModelManager->Translation( &trace, start, end, NULL, trmAxis, contentMask, model->GetCollisionModel( j ), modelOrigin, modelAxis );
if ( trace.fraction < results.fraction ) {
results = trace;
results.c.entityNum = model->GetEntityNumber();
results.c.id = model->GetId();
if ( results.fraction == 0.0f ) {
return;
}
}
}
} else {
if ( !trm->IsTraceModel() ) {
gameLocal.Error( "idClip::TranslationModel: clip model %d of entity '%s' is not a trace model", trm->GetId(), trm->GetEntityName() );
return;
}
for ( i = 0; i < trm->GetNumTraceModels(); i++ ) {
for ( j = 0; j < model->GetNumCollisionModels(); j++ ) {
idClip::numTranslations++;
collisionModelManager->Translation( &trace, start, end, trm->GetTraceModel( i ), trmAxis, contentMask, model->GetCollisionModel( j ), modelOrigin, modelAxis );
if ( trace.fraction < results.fraction ) {
results = trace;
results.c.entityNum = model->GetEntityNumber();
results.c.id = model->GetId();
if ( results.fraction == 0.0f ) {
return;
}
}
}
}
}
}
/*
============
idClip::RotationModelInternal
============
*/
void idClip::RotationModelInternal( CLIP_DEBUG_PARMS_DECLARATION trace_t &results, const idVec3 &start, const idRotation &rotation,
const idClipModel *trm, const idMat3 &trmAxis, int contentMask,
const idClipModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) {
#ifdef CLIP_DEBUG
idClipTimer timer( CLIP_DEBUG_PARMS_PASSTHRU this, CTM_ROTATION );
#endif // CLIP_DEBUG
int i, j;
trace_t trace;
if ( !trm->IsTraceModel() ) {
gameLocal.Error( "idClip::TranslationModel: clip model %d of entity '%s' is not a trace model", trm->GetId(), trm->GetEntityName() );
return;
}
results.fraction = 1.0f;
if ( !( model->GetContents() & contentMask ) ) {
return;
}
for ( i = 0; i < trm->GetNumTraceModels(); i++ ) {
for ( j = 0; j < model->GetNumCollisionModels(); j++ ) {
idClip::numRotations++;
collisionModelManager->Rotation( &trace, start, rotation, trm->GetTraceModel( i ), trmAxis, contentMask, model->GetCollisionModel( j ), modelOrigin, modelAxis );
if ( trace.fraction < results.fraction ) {
results = trace;
results.c.entityNum = model->GetEntityNumber();
results.c.id = model->GetId();
if ( results.fraction == 0.0f ) {
return;
}
}
}
}
}
/*
============
idClip::ContactsModel
============
*/
int idClip::ContactsModel( CLIP_DEBUG_PARMS_DECLARATION contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec3 *dir, const float depth,
const idClipModel *trm, const idMat3 &trmAxis, int contentMask,
const idClipModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) {
#ifdef CLIP_DEBUG
idClipTimer timer( CLIP_DEBUG_PARMS_PASSTHRU this, CTM_CONTACTS );
#endif // CLIP_DEBUG
int i, j, numContacts;
trace_t trace;
if ( !trm->IsTraceModel() ) {
gameLocal.Error( "idClip::TranslationModel: clip model %d of entity '%s' is not a trace model", trm->GetId(), trm->GetEntityName() );
return 0;
}
if ( !( model->GetContents() & contentMask ) ) {
return 0;
}
numContacts = 0;
for ( i = 0; i < trm->GetNumTraceModels(); i++ ) {
for ( j = 0; j < model->GetNumCollisionModels(); j++ ) {
idClip::numContacts++;
numContacts += collisionModelManager->Contacts( contacts + numContacts, maxContacts - numContacts,
start, dir, depth, trm->GetTraceModel( i ), trmAxis, contentMask, model->GetCollisionModel( j ), modelOrigin, modelAxis );
}
}
return numContacts;
}
/*
============
idClip::ContentsModel
============
*/
int idClip::ContentsModel( CLIP_DEBUG_PARMS_DECLARATION const idVec3 &start,
const idClipModel *trm, const idMat3 &trmAxis, int contentMask,
const idClipModel *model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) {
#ifdef CLIP_DEBUG
idClipTimer timer( CLIP_DEBUG_PARMS_PASSTHRU this, CTM_CONTENTS );
#endif // CLIP_DEBUG
int i, j, contents;
trace_t trace;
if ( trm && !trm->IsTraceModel() ) {
gameLocal.Error( "idClip::TranslationModel: clip model %d of entity '%s' is not a trace model", trm->GetId(), trm->GetEntityName() );
return 0;
}
if ( !( model->GetContents() & contentMask ) ) {
return 0;
}
contents = 0;
if ( trm ) {
for ( i = 0; i < trm->GetNumTraceModels(); i++ ) {
for ( j = 0; j < model->GetNumCollisionModels(); j++ ) {
idClip::numContents++;
contents |= collisionModelManager->Contents( start, trm->GetTraceModel( i ), trmAxis, contentMask, model->GetCollisionModel( j ), modelOrigin, modelAxis );
}
}
} else {
for ( j = 0; j < model->GetNumCollisionModels(); j++ ) {
idClip::numContents++;
contents |= collisionModelManager->Contents( start, NULL, trmAxis, contentMask, model->GetCollisionModel( j ), modelOrigin, modelAxis );
}
}
return contents;
}
/*
============
idClip::GetModelContactFeature
============
*/
bool idClip::GetModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const {
int i;
idCollisionModel *model;
idVec3 start, end;
model = NULL;
winding.Clear();
if ( clipModel == NULL ) {
return false;
}
if ( clipModel->IsRenderModel() ) {
winding += contact.point;
return true;
} else {
model = clipModel->GetCollisionModel();
}
// if contact with a collision model
if ( model != NULL ) {
switch( contact.type ) {
case CONTACT_EDGE: {
// the model contact feature is a collision model edge
model->GetEdge( contact.modelFeature, start, end );
winding += start;
winding += end;
break;
}
case CONTACT_MODELVERTEX: {
// the model contact feature is a collision model vertex
start = model->GetVertex( contact.modelFeature );
winding += start;
break;
}
case CONTACT_TRMVERTEX: {
// the model contact feature is a collision model polygon
model->GetPolygon( contact.modelFeature, winding );
break;
}
}
}
// transform the winding to world space
if ( clipModel ) {
for ( i = 0; i < winding.GetNumPoints(); i++ ) {
winding[i].ToVec3() *= clipModel->GetAxis();
winding[i].ToVec3() += clipModel->GetOrigin();
}
}
return true;
}
/*
============
idClip::PrintStatistics
============
*/
void idClip::PrintStatistics( void ) {
if ( collisionModelManager->GetThreadId() != MAIN_THREAD_ID ) {
common->FatalError( "idClip::PrintStatistics called from a thread other than the main thread" );
return;
}
gameLocal.Printf( "t = %-3d, r = %-3d, m = %-3d, render = %-3d, contents = %-3d, contacts = %-3d\n",
numTranslations, numRotations, numMotions, numRenderModelTraces, numContents, numContacts );
numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0;
}
/*
============
idClip::DrawWorld
============
*/
void idClip::DrawWorld( float radius ) {
if ( g_showCollisionWorld.GetInteger() & 1 ) {
for ( int i = 0; i < worldClips.Num(); i++ ) {
idClipModel *world = worldClips[ i ];
world->Draw( vec3_origin, mat3_identity, radius );
}
}
if ( g_showCollisionWorld.GetInteger() & 2 ) {
for ( int i = 0; i < worldClips.Num(); i++ ) {
idClipModel *world = worldClips[ i ];
gameRenderWorld->DebugBox( colorRed, idBox( world->GetBounds() ) );
}
}
}
/*
============
idClip::DrawClipModels
============
*/
void idClip::DrawClipModels( const idVec3 &viewOrigin, const idMat3 &viewAxis, const float radius, const idEntity *passEntity ) {
int i, j, num;
idBounds bounds;
const idClipModel* clipModelList[MAX_GENTITIES];
const idClipModel* clipModel;
if ( collisionModelManager->GetThreadId() != MAIN_THREAD_ID ) {
common->FatalError( "idClip::DrawClipModels called from a thread other than the main thread" );
return;
}
bounds = idBounds( viewOrigin ).Expand( radius );
num = idClip::ClipModelsTouchingBounds( CLIP_DEBUG_PARMS bounds, -1, clipModelList, MAX_GENTITIES, passEntity );
for ( i = 0; i < num; i++ ) {
clipModel = clipModelList[i];
if ( !( clipModel->GetContents() & g_collisionModelMask.GetInteger() ) ) {
continue;
}
if ( clipModel->IsRenderModel() ) {
gameRenderWorld->DebugBounds( colorCyan, clipModel->GetBounds(), clipModel->GetOrigin(), clipModel->GetAxis() );
} else {
for ( j = 0; j < clipModel->GetNumCollisionModels(); j++ ) {
collisionModelManager->DrawModel( clipModel->GetCollisionModel( j ), clipModel->GetOrigin(), clipModel->GetAxis(), viewOrigin, viewAxis, radius, 0.0f );
}
}
}
}
/*
============
idClip::DrawModelContactFeature
============
*/
bool idClip::DrawModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, int lifetime ) const {
int i;
idMat3 axis;
idFixedWinding winding;
if ( collisionModelManager->GetThreadId() != MAIN_THREAD_ID ) {
common->FatalError( "idClip::DrawModelContactFeature called from a thread other than the main thread" );
return false;
}
if ( !GetModelContactFeature( contact, clipModel, winding ) ) {
return false;
}
axis = contact.normal.ToMat3();
if ( winding.GetNumPoints() == 1 ) {
gameRenderWorld->DebugLine( colorCyan, winding[0].ToVec3(), winding[0].ToVec3() + 2.0f * axis[0], lifetime );
gameRenderWorld->DebugLine( colorWhite, winding[0].ToVec3() - 1.0f * axis[1], winding[0].ToVec3() + 1.0f * axis[1], lifetime );
gameRenderWorld->DebugLine( colorWhite, winding[0].ToVec3() - 1.0f * axis[2], winding[0].ToVec3() + 1.0f * axis[2], lifetime );
} else {
for ( i = 0; i < winding.GetNumPoints(); i++ ) {
gameRenderWorld->DebugLine( colorCyan, winding[i].ToVec3(), winding[(i+1)%winding.GetNumPoints()].ToVec3(), lifetime );
}
}
axis[0] = -axis[0];
axis[2] = -axis[2];
gameRenderWorld->DrawText( contact.material->GetName(), winding.GetCenter() - 4.0f * axis[2], 0.1f, colorWhite, axis, 1, 5000 );
return true;
}
/*
============
idClip::PrecacheModel
============
*/
void idClip::PrecacheModel( const char* clipModelName ) {
if ( !*clipModelName ) {
return;
}
if ( collisionModelManager->GetThreadId() != MAIN_THREAD_ID ) {
common->FatalError( "idClip::PrecacheModel called from a thread other than the main thread" );
return;
}
idCollisionModel* model = collisionModelManager->LoadModel( gameLocal.GetMapName(), clipModelName );
if ( model ) {
idTraceModel trm;
LoadTraceModel( clipModelName, trm );
}
collisionModelManager->FreeModel( model );
}
#if !defined( _XENON ) && !defined( MONOLITHIC )
idCVar cm_writeCompiledCollisionModels( "cm_writeCompiledCollisionModels", "0", CVAR_BOOL, "write out generated collision models to disk" );
#else
extern idCVar cm_writeCompiledCollisionModels;
#endif
/*
============
idClip::LoadTraceModel
============
*/
bool idClip::LoadTraceModel( const char* clipModelName, idTraceModel& trm ) {
idStr fileName;
collisionModelManager->GetFullModelName( fileName, gameLocal.GetMapName(), clipModelName );
if ( collisionModelManager->GetThreadId() != MAIN_THREAD_ID ) {
common->FatalError( "idClip::LoadTraceModel called from a thread other than the main thread" );
return false;
}
fileName = PREGENERATED_BASEDIR "/trm/" + fileName;
fileName.SetFileExtension( ".trm" );
int index = gameLocal.traceModelCache.FindTraceModel( fileName.c_str(), false );
if ( index != -1 ) {
trm = *gameLocal.traceModelCache.GetTraceModel( index );
return true;
}
idFile* fp = fileSystem->OpenFileRead( fileName.c_str() );
if ( !fp ) {
if ( !collisionModelManager->TrmFromModel( gameLocal.GetMapName(), clipModelName, trm ) ) {
return false;
}
index = gameLocal.traceModelCache.PrecacheTraceModel( trm, fileName );
if ( cm_writeCompiledCollisionModels.GetBool() ) {
fp = fileSystem->OpenFileWrite( fileName.c_str() );
if ( fp ) {
gameLocal.traceModelCache.Write( index, fp );
fileSystem->CloseFile( fp );
}
}
return true;
} else {
#if defined( SD_BUFFERED_FILE_LOADS )
fp = fileSystem->OpenBufferedFile( fp );
#endif
gameLocal.traceModelCache.Read( trm, fp );
fileSystem->CloseFile( fp );
return true;
}
return false;
}
/*
============
idClip::AllocThread
============
*/
void idClip::AllocThread( void ) {
Lock();
collisionModelManager->AllocThread();
Unlock();
}
/*
============
idClip::FreeThread
============
*/
void idClip::FreeThread( void ) {
Lock();
collisionModelManager->FreeThread();
Unlock();
}
/*
============
idClip::DeleteClipModel
============
*/
void idClip::DeleteClipModel( idClipModel *clipModel ) {
if ( clipModel != NULL ) {
if ( collisionModelManager->GetThreadId() != MAIN_THREAD_ID ) {
common->FatalError( "idClip::DeleteClipModel called from a thread other than the main thread" );
return;
}
// unlink the clip model
clipModel->Unlink( *this );
// remove the clip model to the list with to be removed clip models
Lock();
clipModel->entity = NULL; // make sure there's no stale entity pointer
// FIXME: the thread count should really be a set of bit flags such that each thread can sign off on the clip model
// setting the thread count high enough such that at least one bot think passed works for now though
clipModel->deleteThreadCount = collisionModelManager->GetThreadCount() + bot_threadMaxFrameDelay.GetInteger();
clipModel->nextDeleted = deletedClipModels;
deletedClipModels = clipModel;
Unlock();
}
}
/*
============
idClip::ThreadDeleteClipModels
============
*/
void idClip::ThreadDeleteClipModels( void ) {
Lock();
idClipModel *clipModel;
for ( clipModel = deletedClipModels; clipModel != NULL; clipModel = clipModel->nextDeleted ) {
clipModel->deleteThreadCount--;
}
Unlock();
}
/*
============
idClip::ActuallyDeleteClipModels
============
*/
void idClip::ActuallyDeleteClipModels( bool force ) {
// jrad - this can be called during static initialization, guard appropriately
if( collisionModelManager == NULL || collisionModelManager->GetThreadId() == 0 ) {
return;
}
if ( collisionModelManager->GetThreadId() != MAIN_THREAD_ID ) {
common->FatalError( "idClip::ActuallyDeleteClipModels called from a thread other than the main thread" );
return;
}
Lock();
for ( idClipModel **clipModel = &deletedClipModels; *clipModel != NULL; ) {
if ( (*clipModel)->deleteThreadCount <= 0 || force ) {
idClipModel *remove = *clipModel;
*clipModel = (*clipModel)->nextDeleted;
delete remove;
} else {
clipModel = &(*clipModel)->nextDeleted;
}
}
Unlock();
}
/*
============
idClip::ContentsWorld
============
*/
int idClip::ContentsWorld( CLIP_DEBUG_PARMS_DECLARATION const idVec3 &start,
const idClipModel *mdl, const idMat3 &trmAxis, int contentMask ) {
int contents = 0;
for ( int i = 0; i < worldClips.Num(); i++ ) {
idClipModel *world = worldClips[ i ];
if ( ( world->GetContents() & contentMask ) != 0 ) {
// test world
if ( mdl == NULL ) {
contents |= ContentsClipModel( CLIP_DEBUG_PARMS_PASSTHRU start, NULL, trmAxis, contentMask, world );
} else {
for ( int j = 0; j < mdl->GetNumTraceModels(); j++ ) {
contents |= ContentsClipModel( CLIP_DEBUG_PARMS_PASSTHRU start, mdl->GetTraceModel( j ), trmAxis, contentMask, world );
}
}
}
}
return contents;
}
/*
============
idClip::TranslationWorld
============
*/
void idClip::TranslationWorld( CLIP_DEBUG_PARMS_DECLARATION trace_t &results, const idVec3 &start, const idVec3 &end, const idClipModel *trm, const idMat3 &trmAxis, int contentMask ) {
results.fraction = 1.f;
for ( int i = 0; i < worldClips.Num(); i++ ) {
trace_t trace;
TranslationModel( CLIP_DEBUG_PARMS_PASSTHRU trace, start, end, trm, trmAxis, contentMask, worldClips[ i ], vec3_origin, mat3_identity );
if ( trace.fraction < results.fraction ) {
results = trace;
}
}
}
/*
============
idClip::FindLadder
============
*/
int idClip::FindLadder( CLIP_DEBUG_PARMS_DECLARATION const idBounds& bounds, sdLadderEntity** ladderList, int maxLadders ) {
#ifdef CLIP_DEBUG
idClipTimer timer( CLIP_DEBUG_PARMS_PASSTHRU const_cast< idClip* >( this ), CTM_FINDLADDER );
#endif // CLIP_DEBUG
idBounds parms( bounds[ 0 ] - vec3_boxEpsilon, bounds[ 1 ] + vec3_boxEpsilon );
assert( maxLadders > 0 );
int coords[ 4 ];
CoordsForBounds( coords, parms );
int numLadders = 0;
sdInstanceCollector< sdLadderEntity > ladders( true );
for ( int i = 0; i < ladders.Num(); i++ ) {
sdLadderEntity* ladder =ladders[ i ];
if ( !ladder->IsActive() ) {
continue;
}
const idClipModel* model = ladder->GetPhysics()->GetClipModel();
if ( !model->IsLinked() ) {
assert( false );
continue;
}
if ( model->lastLinkCoords[ 2 ] < coords[ 0 ] ) {
continue;
}
if ( model->lastLinkCoords[ 0 ] > coords[ 2 ] ) {
continue;
}
if ( model->lastLinkCoords[ 3 ] < coords[ 1 ] ) {
continue;
}
if ( model->lastLinkCoords[ 1 ] > coords[ 3 ] ) {
continue;
}
if ( !model->GetAbsBounds().IntersectsBounds( bounds ) ) {
continue;
}
ladderList[ numLadders++ ] = ladder;
if ( numLadders == maxLadders ) {
break;
}
}
return numLadders;
}
/*
============
idClip::FindWater
============
*/
int idClip::FindWater( CLIP_DEBUG_PARMS_DECLARATION const idBounds& bounds, const idClipModel** clipModelList, int maxCount ) {
#ifdef CLIP_DEBUG
idClipTimer timer( CLIP_DEBUG_PARMS_PASSTHRU const_cast< idClip* >( this ), CTM_FINDWATER );
#endif // CLIP_DEBUG
idBounds parms( bounds[ 0 ] - vec3_boxEpsilon, bounds[ 1 ] + vec3_boxEpsilon );
int coords[ 4 ];
CoordsForBounds( coords, parms );
sdInstanceCollector< sdLiquid > liquids( true );
int count = 0;
for ( int i = 0; i < liquids.Num(); i++ ) {
sdLiquid* liquid = liquids[ i ];
const idClipModel* model = liquid->GetPhysics()->GetClipModel();
if ( !model->IsLinked() ) {
assert( false );
continue;
}
if ( model->lastLinkCoords[ 2 ] < coords[ 0 ] ) {
continue;
}
if ( model->lastLinkCoords[ 0 ] > coords[ 2 ] ) {
continue;
}
if ( model->lastLinkCoords[ 3 ] < coords[ 1 ] ) {
continue;
}
if ( model->lastLinkCoords[ 1 ] > coords[ 3 ] ) {
continue;
}
if ( model->GetAbsBounds().IntersectsBounds( bounds ) ) {
clipModelList[ count++ ] = model;
if ( count == maxCount ) {
break;
}
}
}
return count;
}
#ifdef CLIP_DEBUG
idCVar g_enableTraceLogging( "g_enableTraceLogging", "0", CVAR_BOOL | CVAR_GAME, "" );
const char* traceTimerModeText[] = {
"Translation",
"Rotation",
"Contacts",
"Contents",
"ClipModelsTouchingBounds",
"FindLadder",
"FindWater"
};
void idClip::LogTrace( const char* fileName, int lineNumber, double time, clipTimerMode_t mode ) {
if ( !g_enableTraceLogging.GetBool() ) {
return;
}
static char tempFileName[ 512 ];
if ( !gameLocal.isNewFrame ) {
sprintf( tempFileName, "REP %s", fileName );
} else {
sprintf( tempFileName, "%s", fileName );
}
idList< clipTimerInfo_t >* info;
if ( !clipTimerInfo[ mode ].Get( tempFileName, &info ) ) {
clipTimerInfo[ mode ].Set( tempFileName, idList< clipTimerInfo_t >() );
clipTimerInfo[ mode ].Get( tempFileName, &info );
}
idList< clipTimerInfo_t >& infoList = *info;
for ( int i = 0; i < infoList.Num(); i++ ) {
if ( infoList[ i ].lineNumber == lineNumber ) {
infoList[ i ].count++;
infoList[ i ].time += time;
return;
}
}
clipTimerInfo_t& data = infoList.Alloc();
data.count = 1;
data.time = time;
data.lineNumber = lineNumber;
}
void idClip::PrintTraceTimings( void ) {
for ( int i = 0; i < CTM_NUM; i++ ) {
gameLocal.Printf( "%s:\n", traceTimerModeText[ i ] );
for ( int j = 0; j < clipTimerInfo[ i ].Num(); j++ ) {
idList< clipTimerInfo_t >& infoList = *clipTimerInfo[ i ].GetIndex( j );
for ( int k = 0; k < infoList.Num(); k++ ) {
gameLocal.Printf( "File: \"%s\" Line: %d Count: %d Time: %d\n", clipTimerInfo[ i ].GetKey( j ).c_str(), infoList[ k ].lineNumber, infoList[ k ].count, ( int )infoList[ k ].time );
}
}
gameLocal.Printf( "==========================\n\n", traceTimerModeText[ i ] );
}
}
void idClip::ClearTraceTimings( void ) {
for ( int i = 0; i < CTM_NUM; i++ ) {
clipTimerInfo[ i ].Clear();
}
}
#endif // CLIP_DEBUG
#ifdef CLIP_DEBUG_EXTREME
void idClip::LogTraceExtreme( const char* fileName, int lineNumber, double time, clipTimerMode_t mode ) {
if ( !isTraceLogging ) {
return;
}
char name[ 512 ];
sprintf( name, "%s: %i", fileName, lineNumber );
clipTimerInfoExtreme_t** infoPtr;
clipTimerInfoExtreme_t* info;
if ( !clipTimerInfoExtreme[ mode ].Get( name, &infoPtr ) ) {
info = new clipTimerInfoExtreme_t;
for ( int i = 0; i < CLIP_DEBUG_MAX_FRAMES; i++ ) {
info->frameInfo[ i ].count = 0;
info->frameInfo[ i ].elapsedTime = 0.0;
}
clipTimerInfoExtreme[ mode ].Set( name, info );
clipTimerInfoExtreme[ mode ].Get( name, &infoPtr );
}
info = *infoPtr;
info->frameInfo[ clipSampleUpto ].count++;
info->frameInfo[ clipSampleUpto ].elapsedTime += time;
}
void idClip::StartTraceLogging( void ) {
if ( isTraceLogging ) {
return;
}
// clear the list
for ( int mode = 0; mode < CTM_NUM; mode++ ) {
for ( int i = 0; i < clipTimerInfoExtreme[ mode ].Num(); i++ ) {
delete *clipTimerInfoExtreme[ mode ].GetIndex( i );
}
clipTimerInfoExtreme[ mode ].Clear();
}
isTraceLogging = true;
clipSampleUpto = 0;
logSubFileUpto = 0;
logFileUpto++;
gameLocal.DPrintf( "Started trace logging.\n" );
}
void idClip::StopTraceLogging( void ) {
if ( !isTraceLogging ) {
return;
}
isTraceLogging = false;
DumpTraceLog();
AssembleTraceLogs();
gameLocal.DPrintf( "Stopped trace logging.\n" );
}
void idClip::UpdateTraceLogging( void ) {
if ( !isTraceLogging ) {
return;
}
clipSampleUpto++;
if ( clipSampleUpto == CLIP_DEBUG_MAX_FRAMES ) {
DumpTraceLog();
}
clipTimerSampleTimes[ clipSampleUpto ] = gameLocal.time;
}
void idClip::DumpTraceLog( void ) {
for ( int i = 0; i < CTM_NUM; i++ ) {
DumpTraceLog( i );
}
logSubFileUpto++;
clipSampleUpto = 1;
}
void idClip::DumpTraceLog( int mode ) {
const char* fileName;
// dump a mini-log out
fileName = va( "tr_%s_mini_time_%i_%i.temp", traceTimerModeText[ mode ], logFileUpto, logSubFileUpto );
idFile* miniTimeLog = fileSystem->OpenFileWrite( fileName );
if ( miniTimeLog == NULL ) {
gameLocal.Warning( "idClip::DumpTraceLog - failed to open %s", fileName );
return;
}
fileName = va( "tr_%s_mini_count_%i_%i.temp", traceTimerModeText[ mode ], logFileUpto, logSubFileUpto );
idFile* miniCountLog = fileSystem->OpenFileWrite( fileName );
if ( miniCountLog == NULL ) {
gameLocal.Warning( "idClip::DumpTraceLog - failed to open %s", fileName );
fileSystem->CloseFile( miniTimeLog );
return;
}
// output number of columns
miniTimeLog->WriteInt( clipTimerInfoExtreme[ mode ].Num() + 2 );
miniCountLog->WriteInt( clipTimerInfoExtreme[ mode ].Num() + 2 );
// print line of titles
miniTimeLog->WriteString( "gameLocal.time" );
miniCountLog->WriteString( "gameLocal.time" );
for ( int i = 0; i < clipTimerInfoExtreme[ mode ].Num(); i++ ) {
miniTimeLog->WriteString( va( "%s", clipTimerInfoExtreme[ mode ].GetKey( i ).c_str() ) );
miniCountLog->WriteString( va( "%s", clipTimerInfoExtreme[ mode ].GetKey( i ).c_str() ) );
}
miniTimeLog->WriteString( "TOTAL" );
miniCountLog->WriteString( "TOTAL" );
// print data, row by row
for ( int sample = 1; sample < clipSampleUpto; sample++ ) {
miniTimeLog->WriteInt( clipTimerSampleTimes[ sample ] );
miniCountLog->WriteInt( clipTimerSampleTimes[ sample ] );
double totalTime = 0.0;
int totalCount = 0;
for ( int i = 0; i < clipTimerInfoExtreme[ mode ].Num(); i++ ) {
clipTimerInfoExtreme_t* info = *clipTimerInfoExtreme[ mode ].GetIndex( i );
miniTimeLog->WriteFloat( ( float )info->frameInfo[ sample ].elapsedTime );
miniCountLog->WriteInt( info->frameInfo[ sample ].count );
totalTime += info->frameInfo[ sample ].elapsedTime;
totalCount += info->frameInfo[ sample ].count;
info->frameInfo[ sample ].elapsedTime = 0.0;
info->frameInfo[ sample ].count = 0;
}
miniTimeLog->WriteFloat( ( float )totalTime );
miniCountLog->WriteInt( totalCount );
}
fileSystem->CloseFile( miniCountLog );
fileSystem->CloseFile( miniTimeLog );
}
void idClip::AssembleTraceLogs( void ) {
for ( int i = 0; i < CTM_NUM; i++ ) {
AssembleTraceLogs( i );
}
}
void idClip::AssembleTraceLogs( int mode ) {
// assemble all the logs for this session into one big csv file & delete the mini-logs
// first find all the logs & collate a list of titles
idList< idStr > titles;
int numLogs = 0;
//
// Initialize the titles list
//
while( 1 ) {
idFile* miniLog = fileSystem->OpenFileRead( va( "tr_%s_mini_time_%i_%i.temp", traceTimerModeText[ mode ], logFileUpto, numLogs ) );
idFile* miniCountLog = fileSystem->OpenFileRead( va( "tr_%s_mini_count_%i_%i.temp", traceTimerModeText[ mode ], logFileUpto, numLogs ) );
if ( miniLog != NULL && miniCountLog != NULL ) {
int numColumns;
miniLog->ReadInt( numColumns );
miniCountLog->ReadInt( numColumns );
for ( int i = 0; i < numColumns; i++ ) {
idStr title, title2;
miniLog->ReadString( title );
titles.AddUnique( title );
miniCountLog->ReadString( title2 );
if ( title != title2 ) {
gameLocal.Warning( "i.dClip::AssembleTraceLogs - count & time titles do not match!" );
return;
}
}
numLogs++;
}
if ( miniCountLog ) {
fileSystem->CloseFile( miniCountLog );
}
if ( miniLog ) {
fileSystem->CloseFile( miniLog );
}
if ( miniCountLog == NULL || miniLog == NULL ) {
break;
}
}
if ( numLogs == 0 ) {
gameLocal.Warning( "idClip::AssembleTraceLogs - no mini logs!" );
return;
}
//
// Sort the titles list, keeping gameLocal.time & TOTAL at the start
//
titles.RemoveFast( idStr( "gameLocal.time" ) );
titles.RemoveFast( idStr( "TOTAL" ) );
titles.Sort();
titles.Insert( idStr( "TOTAL" ), 0 );
titles.Insert( idStr( "gameLocal.time" ), 0 );
//
// Write the titles list
//
idFile* log = fileSystem->OpenFileWrite( va( "tr_%s_time_%i.csv", traceTimerModeText[ mode ], logFileUpto ) );
if ( log == NULL ) {
gameLocal.Warning( "idClip::AssembleTraceLogs - couldn't open file %s", va( "tr_%s_time_%i.csv", traceTimerModeText[ mode ], logFileUpto ) );
return;
}
idFile* countLog = fileSystem->OpenFileWrite( va( "tr_%s_count_%i.csv", traceTimerModeText[ mode ], logFileUpto ) );
if ( countLog == NULL ) {
gameLocal.Warning( "idClip::AssembleTraceLogs - couldn't open file %s", va( "tr_%s_count_%i.csv", traceTimerModeText[ mode ], logFileUpto ) );
fileSystem->CloseFile( log );
return;
}
for ( int i = 0; i < titles.Num(); i++ ) {
log->Printf( "\"%s\",", titles[ i ].c_str() );
countLog->Printf( "\"%s\",", titles[ i ].c_str() );
}
log->Printf( "\n" );
countLog->Printf( "\n" );
//
// Loop through the logs & write them all into the main log
//
idList< int > miniLogColumnToLogColumn;
idList< int > logColumnToMiniLogColumn;
idList< float > columnTimeValues;
idList< int > columnCountValues;
logColumnToMiniLogColumn.AssureSize( titles.Num() );
for ( int logNum = 0; logNum < numLogs; logNum++ ) {
idFile* miniLog = fileSystem->OpenFileRead( va( "tr_%s_mini_time_%i_%i.temp", traceTimerModeText[ mode ], logFileUpto, logNum ) );
idFile* miniCountLog = fileSystem->OpenFileRead( va( "tr_%s_mini_count_%i_%i.temp", traceTimerModeText[ mode ], logFileUpto, logNum ) );
if ( miniLog != NULL && miniCountLog != NULL ) {
// create translation tables to & from the mini log & main log table
int numColumns;
miniLog->ReadInt( numColumns );
miniCountLog->ReadInt( numColumns );
miniLogColumnToLogColumn.AssureSize( numColumns );
for ( int i = 0; i < titles.Num(); i++ ) {
logColumnToMiniLogColumn[ i ] = -1;
}
for ( int i = 0; i < numColumns; i++ ) {
idStr title, title2;
miniLog->ReadString( title );
miniCountLog->ReadString( title2 );
miniLogColumnToLogColumn[ i ] = titles.FindIndex( title );
logColumnToMiniLogColumn[ miniLogColumnToLogColumn[ i ] ] = i;
}
// go through all the samples and write them into the main file
columnTimeValues.AssureSize( numColumns );
columnCountValues.AssureSize( numColumns );
while ( miniLog->Tell() < miniLog->Length() - 1 ) {
int time;
miniLog->ReadInt( time );
miniCountLog->ReadInt( time );
// read the samples
for ( int i = 1; i < numColumns; i++ ) {
miniLog->ReadFloat( columnTimeValues[ i ] );
miniCountLog->ReadInt( columnCountValues[ i ] );
}
// write the time samples
log->Printf( "%i,", time );
countLog->Printf( "%i,", time );
for ( int i = 1; i < titles.Num(); i++ ) {
if ( logColumnToMiniLogColumn[ i ] == -1 ) {
log->Printf( "0," );
countLog->Printf( "0," );
} else {
log->Printf( "%.6f,", columnTimeValues[ logColumnToMiniLogColumn[ i ] ] );
countLog->Printf( "%i,", columnCountValues[ logColumnToMiniLogColumn[ i ] ] );
}
}
log->Printf( "\n" );
countLog->Printf( "\n" );
}
}
if ( miniLog ) {
fileSystem->CloseFile( miniLog );
}
if ( miniCountLog ) {
fileSystem->CloseFile( miniCountLog );
}
fileSystem->RemoveFile( va( "tr_%s_mini_count_%i_%i.temp", traceTimerModeText[ mode ], logFileUpto, logNum ) );
fileSystem->RemoveFile( va( "tr_%s_mini_time_%i_%i.temp", traceTimerModeText[ mode ], logFileUpto, logNum ) );
}
fileSystem->CloseFile( log );
fileSystem->CloseFile( countLog );
}
#endif // CLIP_DEBUG_EXTREME