// 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