mirror of
https://github.com/dhewm/dhewm3.git
synced 2024-11-30 16:11:11 +00:00
736ec20d4d
Don't include the lazy precompiled.h everywhere, only what's required for the compilation unit. platform.h needs to be included instead to provide all essential defines and types. All includes use the relative path to the neo or the game specific root. Move all idlib related includes from idlib/Lib.h to precompiled.h. precompiled.h still exists for the MFC stuff in tools/. Add some missing header guards.
1673 lines
44 KiB
C++
1673 lines
44 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
Doom 3 GPL Source Code
|
|
Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
|
|
|
|
This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code").
|
|
|
|
Doom 3 Source Code is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
Doom 3 Source Code is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
|
|
|
|
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
#include "sys/platform.h"
|
|
#include "gamesys/SaveGame.h"
|
|
#include "Entity.h"
|
|
#include "Game_local.h"
|
|
|
|
#include "physics/Clip.h"
|
|
|
|
#define MAX_SECTOR_DEPTH 12
|
|
#define MAX_SECTORS ((1<<(MAX_SECTOR_DEPTH+1))-1)
|
|
|
|
typedef struct clipSector_s {
|
|
int axis; // -1 = leaf node
|
|
float dist;
|
|
struct clipSector_s * children[2];
|
|
struct clipLink_s * clipLinks;
|
|
} clipSector_t;
|
|
|
|
typedef struct clipLink_s {
|
|
idClipModel * clipModel;
|
|
struct clipSector_s * sector;
|
|
struct clipLink_s * prevInSector;
|
|
struct clipLink_s * nextInSector;
|
|
struct clipLink_s * nextLink;
|
|
} clipLink_t;
|
|
|
|
typedef struct trmCache_s {
|
|
idTraceModel trm;
|
|
int refCount;
|
|
float volume;
|
|
idVec3 centerOfMass;
|
|
idMat3 inertiaTensor;
|
|
} trmCache_t;
|
|
|
|
idVec3 vec3_boxEpsilon( CM_BOX_EPSILON, CM_BOX_EPSILON, CM_BOX_EPSILON );
|
|
|
|
idBlockAlloc<clipLink_t, 1024> clipLinkAllocator;
|
|
|
|
|
|
/*
|
|
===============================================================
|
|
|
|
idClipModel trace model cache
|
|
|
|
===============================================================
|
|
*/
|
|
|
|
static idList<trmCache_s*> traceModelCache;
|
|
static idHashIndex traceModelHash;
|
|
|
|
/*
|
|
===============
|
|
idClipModel::ClearTraceModelCache
|
|
===============
|
|
*/
|
|
void idClipModel::ClearTraceModelCache( void ) {
|
|
traceModelCache.DeleteContents( true );
|
|
traceModelHash.Free();
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idClipModel::TraceModelCacheSize
|
|
===============
|
|
*/
|
|
int idClipModel::TraceModelCacheSize( void ) {
|
|
return traceModelCache.Num() * sizeof( idTraceModel );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idClipModel::AllocTraceModel
|
|
===============
|
|
*/
|
|
int idClipModel::AllocTraceModel( const idTraceModel &trm ) {
|
|
int i, hashKey, traceModelIndex;
|
|
trmCache_t *entry;
|
|
|
|
hashKey = GetTraceModelHashKey( trm );
|
|
for ( i = traceModelHash.First( hashKey ); i >= 0; i = traceModelHash.Next( i ) ) {
|
|
if ( traceModelCache[i]->trm == trm ) {
|
|
traceModelCache[i]->refCount++;
|
|
return i;
|
|
}
|
|
}
|
|
|
|
entry = new trmCache_t;
|
|
entry->trm = trm;
|
|
entry->trm.GetMassProperties( 1.0f, entry->volume, entry->centerOfMass, entry->inertiaTensor );
|
|
entry->refCount = 1;
|
|
|
|
traceModelIndex = traceModelCache.Append( entry );
|
|
traceModelHash.Add( hashKey, traceModelIndex );
|
|
return traceModelIndex;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idClipModel::FreeTraceModel
|
|
===============
|
|
*/
|
|
void idClipModel::FreeTraceModel( int traceModelIndex ) {
|
|
if ( traceModelIndex < 0 || traceModelIndex >= traceModelCache.Num() || traceModelCache[traceModelIndex]->refCount <= 0 ) {
|
|
gameLocal.Warning( "idClipModel::FreeTraceModel: tried to free uncached trace model" );
|
|
return;
|
|
}
|
|
traceModelCache[traceModelIndex]->refCount--;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idClipModel::GetCachedTraceModel
|
|
===============
|
|
*/
|
|
idTraceModel *idClipModel::GetCachedTraceModel( int traceModelIndex ) {
|
|
return &traceModelCache[traceModelIndex]->trm;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idClipModel::GetTraceModelHashKey
|
|
===============
|
|
*/
|
|
int idClipModel::GetTraceModelHashKey( const idTraceModel &trm ) {
|
|
const idVec3 &v = trm.bounds[0];
|
|
return ( trm.type << 8 ) ^ ( trm.numVerts << 4 ) ^ ( trm.numEdges << 2 ) ^ ( trm.numPolys << 0 ) ^ idMath::FloatHash( v.ToFloatPtr(), v.GetDimension() );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idClipModel::SaveTraceModels
|
|
===============
|
|
*/
|
|
void idClipModel::SaveTraceModels( idSaveGame *savefile ) {
|
|
int i;
|
|
|
|
savefile->WriteInt( traceModelCache.Num() );
|
|
for ( i = 0; i < traceModelCache.Num(); i++ ) {
|
|
trmCache_t *entry = traceModelCache[i];
|
|
|
|
savefile->WriteTraceModel( entry->trm );
|
|
savefile->WriteFloat( entry->volume );
|
|
savefile->WriteVec3( entry->centerOfMass );
|
|
savefile->WriteMat3( entry->inertiaTensor );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idClipModel::RestoreTraceModels
|
|
===============
|
|
*/
|
|
void idClipModel::RestoreTraceModels( idRestoreGame *savefile ) {
|
|
int i, num;
|
|
|
|
ClearTraceModelCache();
|
|
|
|
savefile->ReadInt( num );
|
|
traceModelCache.SetNum( num );
|
|
|
|
for ( i = 0; i < num; i++ ) {
|
|
trmCache_t *entry = new trmCache_t;
|
|
|
|
savefile->ReadTraceModel( entry->trm );
|
|
|
|
savefile->ReadFloat( entry->volume );
|
|
savefile->ReadVec3( entry->centerOfMass );
|
|
savefile->ReadMat3( entry->inertiaTensor );
|
|
entry->refCount = 0;
|
|
|
|
traceModelCache[i] = entry;
|
|
traceModelHash.Add( GetTraceModelHashKey( entry->trm ), i );
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
===============================================================
|
|
|
|
idClipModel
|
|
|
|
===============================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
idClipModel::LoadModel
|
|
================
|
|
*/
|
|
bool idClipModel::LoadModel( const char *name ) {
|
|
renderModelHandle = -1;
|
|
if ( traceModelIndex != -1 ) {
|
|
FreeTraceModel( traceModelIndex );
|
|
traceModelIndex = -1;
|
|
}
|
|
collisionModelHandle = collisionModelManager->LoadModel( name, false );
|
|
if ( collisionModelHandle ) {
|
|
collisionModelManager->GetModelBounds( collisionModelHandle, bounds );
|
|
collisionModelManager->GetModelContents( collisionModelHandle, contents );
|
|
return true;
|
|
} else {
|
|
bounds.Zero();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idClipModel::LoadModel
|
|
================
|
|
*/
|
|
void idClipModel::LoadModel( const idTraceModel &trm ) {
|
|
collisionModelHandle = 0;
|
|
renderModelHandle = -1;
|
|
if ( traceModelIndex != -1 ) {
|
|
FreeTraceModel( traceModelIndex );
|
|
}
|
|
traceModelIndex = AllocTraceModel( trm );
|
|
bounds = trm.bounds;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idClipModel::LoadModel
|
|
================
|
|
*/
|
|
void idClipModel::LoadModel( const int renderModelHandle ) {
|
|
collisionModelHandle = 0;
|
|
this->renderModelHandle = renderModelHandle;
|
|
if ( renderModelHandle != -1 ) {
|
|
const renderEntity_t *renderEntity = gameRenderWorld->GetRenderEntity( renderModelHandle );
|
|
if ( renderEntity ) {
|
|
bounds = renderEntity->bounds;
|
|
}
|
|
}
|
|
if ( traceModelIndex != -1 ) {
|
|
FreeTraceModel( traceModelIndex );
|
|
traceModelIndex = -1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idClipModel::Init
|
|
================
|
|
*/
|
|
void idClipModel::Init( void ) {
|
|
enabled = true;
|
|
entity = NULL;
|
|
id = 0;
|
|
owner = NULL;
|
|
origin.Zero();
|
|
axis.Identity();
|
|
bounds.Zero();
|
|
absBounds.Zero();
|
|
material = NULL;
|
|
contents = CONTENTS_BODY;
|
|
collisionModelHandle = 0;
|
|
renderModelHandle = -1;
|
|
traceModelIndex = -1;
|
|
clipLinks = NULL;
|
|
touchCount = -1;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idClipModel::idClipModel
|
|
================
|
|
*/
|
|
idClipModel::idClipModel( void ) {
|
|
Init();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idClipModel::idClipModel
|
|
================
|
|
*/
|
|
idClipModel::idClipModel( const char *name ) {
|
|
Init();
|
|
LoadModel( name );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idClipModel::idClipModel
|
|
================
|
|
*/
|
|
idClipModel::idClipModel( const idTraceModel &trm ) {
|
|
Init();
|
|
LoadModel( trm );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idClipModel::idClipModel
|
|
================
|
|
*/
|
|
idClipModel::idClipModel( const int renderModelHandle ) {
|
|
Init();
|
|
contents = CONTENTS_RENDERMODEL;
|
|
LoadModel( renderModelHandle );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idClipModel::idClipModel
|
|
================
|
|
*/
|
|
idClipModel::idClipModel( const idClipModel *model ) {
|
|
enabled = model->enabled;
|
|
entity = model->entity;
|
|
id = model->id;
|
|
owner = model->owner;
|
|
origin = model->origin;
|
|
axis = model->axis;
|
|
bounds = model->bounds;
|
|
absBounds = model->absBounds;
|
|
material = model->material;
|
|
contents = model->contents;
|
|
collisionModelHandle = model->collisionModelHandle;
|
|
traceModelIndex = -1;
|
|
if ( model->traceModelIndex != -1 ) {
|
|
LoadModel( *GetCachedTraceModel( model->traceModelIndex ) );
|
|
}
|
|
renderModelHandle = model->renderModelHandle;
|
|
clipLinks = NULL;
|
|
touchCount = -1;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idClipModel::~idClipModel
|
|
================
|
|
*/
|
|
idClipModel::~idClipModel( void ) {
|
|
// make sure the clip model is no longer linked
|
|
Unlink();
|
|
if ( traceModelIndex != -1 ) {
|
|
FreeTraceModel( traceModelIndex );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idClipModel::Save
|
|
================
|
|
*/
|
|
void idClipModel::Save( idSaveGame *savefile ) const {
|
|
savefile->WriteBool( enabled );
|
|
savefile->WriteObject( entity );
|
|
savefile->WriteInt( id );
|
|
savefile->WriteObject( owner );
|
|
savefile->WriteVec3( origin );
|
|
savefile->WriteMat3( axis );
|
|
savefile->WriteBounds( bounds );
|
|
savefile->WriteBounds( absBounds );
|
|
savefile->WriteMaterial( material );
|
|
savefile->WriteInt( contents );
|
|
if ( collisionModelHandle >= 0 ) {
|
|
savefile->WriteString( collisionModelManager->GetModelName( collisionModelHandle ) );
|
|
} else {
|
|
savefile->WriteString( "" );
|
|
}
|
|
savefile->WriteInt( traceModelIndex );
|
|
savefile->WriteInt( renderModelHandle );
|
|
savefile->WriteBool( clipLinks != NULL );
|
|
savefile->WriteInt( touchCount );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idClipModel::Restore
|
|
================
|
|
*/
|
|
void idClipModel::Restore( idRestoreGame *savefile ) {
|
|
idStr collisionModelName;
|
|
bool linked;
|
|
|
|
savefile->ReadBool( enabled );
|
|
savefile->ReadObject( reinterpret_cast<idClass *&>( entity ) );
|
|
savefile->ReadInt( id );
|
|
savefile->ReadObject( reinterpret_cast<idClass *&>( owner ) );
|
|
savefile->ReadVec3( origin );
|
|
savefile->ReadMat3( axis );
|
|
savefile->ReadBounds( bounds );
|
|
savefile->ReadBounds( absBounds );
|
|
savefile->ReadMaterial( material );
|
|
savefile->ReadInt( contents );
|
|
savefile->ReadString( collisionModelName );
|
|
if ( collisionModelName.Length() ) {
|
|
collisionModelHandle = collisionModelManager->LoadModel( collisionModelName, false );
|
|
} else {
|
|
collisionModelHandle = -1;
|
|
}
|
|
savefile->ReadInt( traceModelIndex );
|
|
if ( traceModelIndex >= 0 ) {
|
|
traceModelCache[traceModelIndex]->refCount++;
|
|
}
|
|
savefile->ReadInt( renderModelHandle );
|
|
savefile->ReadBool( linked );
|
|
savefile->ReadInt( touchCount );
|
|
|
|
// the render model will be set when the clip model is linked
|
|
renderModelHandle = -1;
|
|
clipLinks = NULL;
|
|
touchCount = -1;
|
|
|
|
if ( linked ) {
|
|
Link( gameLocal.clip, entity, id, origin, axis, renderModelHandle );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idClipModel::SetPosition
|
|
================
|
|
*/
|
|
void idClipModel::SetPosition( const idVec3 &newOrigin, const idMat3 &newAxis ) {
|
|
if ( clipLinks ) {
|
|
Unlink(); // unlink from old position
|
|
}
|
|
origin = newOrigin;
|
|
axis = newAxis;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idClipModel::Handle
|
|
================
|
|
*/
|
|
cmHandle_t idClipModel::Handle( void ) const {
|
|
assert( renderModelHandle == -1 );
|
|
if ( collisionModelHandle ) {
|
|
return collisionModelHandle;
|
|
} else if ( traceModelIndex != -1 ) {
|
|
return collisionModelManager->SetupTrmModel( *GetCachedTraceModel( traceModelIndex ), material );
|
|
} else {
|
|
// this happens in multiplayer on the combat models
|
|
gameLocal.Warning( "idClipModel::Handle: clip model %d on '%s' (%x) is not a collision or trace model", id, entity->name.c_str(), entity->entityNumber );
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idClipModel::GetMassProperties
|
|
================
|
|
*/
|
|
void idClipModel::GetMassProperties( const float density, float &mass, idVec3 ¢erOfMass, idMat3 &inertiaTensor ) const {
|
|
if ( traceModelIndex == -1 ) {
|
|
gameLocal.Error( "idClipModel::GetMassProperties: clip model %d on '%s' is not a trace model\n", id, entity->name.c_str() );
|
|
}
|
|
|
|
trmCache_t *entry = traceModelCache[traceModelIndex];
|
|
mass = entry->volume * density;
|
|
centerOfMass = entry->centerOfMass;
|
|
inertiaTensor = density * entry->inertiaTensor;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idClipModel::Unlink
|
|
===============
|
|
*/
|
|
void idClipModel::Unlink( void ) {
|
|
clipLink_t *link;
|
|
|
|
for ( link = clipLinks; link; link = clipLinks ) {
|
|
clipLinks = link->nextLink;
|
|
if ( link->prevInSector ) {
|
|
link->prevInSector->nextInSector = link->nextInSector;
|
|
} else {
|
|
link->sector->clipLinks = link->nextInSector;
|
|
}
|
|
if ( link->nextInSector ) {
|
|
link->nextInSector->prevInSector = link->prevInSector;
|
|
}
|
|
clipLinkAllocator.Free( link );
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idClipModel::Link_r
|
|
===============
|
|
*/
|
|
void idClipModel::Link_r( struct clipSector_s *node ) {
|
|
clipLink_t *link;
|
|
|
|
while( node->axis != -1 ) {
|
|
if ( absBounds[0][node->axis] > node->dist ) {
|
|
node = node->children[0];
|
|
} else if ( absBounds[1][node->axis] < node->dist ) {
|
|
node = node->children[1];
|
|
} else {
|
|
Link_r( node->children[0] );
|
|
node = node->children[1];
|
|
}
|
|
}
|
|
|
|
link = clipLinkAllocator.Alloc();
|
|
link->clipModel = this;
|
|
link->sector = node;
|
|
link->nextInSector = node->clipLinks;
|
|
link->prevInSector = NULL;
|
|
if ( node->clipLinks ) {
|
|
node->clipLinks->prevInSector = link;
|
|
}
|
|
node->clipLinks = link;
|
|
link->nextLink = clipLinks;
|
|
clipLinks = link;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idClipModel::Link
|
|
===============
|
|
*/
|
|
void idClipModel::Link( idClip &clp ) {
|
|
|
|
assert( idClipModel::entity );
|
|
if ( !idClipModel::entity ) {
|
|
return;
|
|
}
|
|
|
|
if ( clipLinks ) {
|
|
Unlink(); // unlink from old position
|
|
}
|
|
|
|
if ( bounds.IsCleared() ) {
|
|
return;
|
|
}
|
|
|
|
// set the abs box
|
|
if ( axis.IsRotated() ) {
|
|
// expand for rotation
|
|
absBounds.FromTransformedBounds( bounds, origin, axis );
|
|
} else {
|
|
// normal
|
|
absBounds[0] = bounds[0] + origin;
|
|
absBounds[1] = bounds[1] + origin;
|
|
}
|
|
|
|
// because movement is clipped an epsilon away from an actual edge,
|
|
// we must fully check even when bounding boxes don't quite touch
|
|
absBounds[0] -= vec3_boxEpsilon;
|
|
absBounds[1] += vec3_boxEpsilon;
|
|
|
|
Link_r( clp.clipSectors );
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idClipModel::Link
|
|
===============
|
|
*/
|
|
void idClipModel::Link( idClip &clp, idEntity *ent, int newId, const idVec3 &newOrigin, const idMat3 &newAxis, int renderModelHandle ) {
|
|
|
|
this->entity = ent;
|
|
this->id = newId;
|
|
this->origin = newOrigin;
|
|
this->axis = newAxis;
|
|
if ( renderModelHandle != -1 ) {
|
|
this->renderModelHandle = renderModelHandle;
|
|
const renderEntity_t *renderEntity = gameRenderWorld->GetRenderEntity( renderModelHandle );
|
|
if ( renderEntity ) {
|
|
this->bounds = renderEntity->bounds;
|
|
}
|
|
}
|
|
this->Link( clp );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idClipModel::CheckModel
|
|
============
|
|
*/
|
|
cmHandle_t idClipModel::CheckModel( const char *name ) {
|
|
return collisionModelManager->LoadModel( name, false );
|
|
}
|
|
|
|
|
|
/*
|
|
===============================================================
|
|
|
|
idClip
|
|
|
|
===============================================================
|
|
*/
|
|
|
|
/*
|
|
===============
|
|
idClip::idClip
|
|
===============
|
|
*/
|
|
idClip::idClip( void ) {
|
|
numClipSectors = 0;
|
|
clipSectors = NULL;
|
|
worldBounds.Zero();
|
|
numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idClip::CreateClipSectors_r
|
|
|
|
Builds a uniformly subdivided tree for the given world size
|
|
===============
|
|
*/
|
|
clipSector_t *idClip::CreateClipSectors_r( const int depth, const idBounds &bounds, idVec3 &maxSector ) {
|
|
int i;
|
|
clipSector_t *anode;
|
|
idVec3 size;
|
|
idBounds front, back;
|
|
|
|
anode = &clipSectors[idClip::numClipSectors];
|
|
idClip::numClipSectors++;
|
|
|
|
if ( depth == MAX_SECTOR_DEPTH ) {
|
|
anode->axis = -1;
|
|
anode->children[0] = anode->children[1] = NULL;
|
|
|
|
for ( i = 0; i < 3; i++ ) {
|
|
if ( bounds[1][i] - bounds[0][i] > maxSector[i] ) {
|
|
maxSector[i] = bounds[1][i] - bounds[0][i];
|
|
}
|
|
}
|
|
return anode;
|
|
}
|
|
|
|
size = bounds[1] - bounds[0];
|
|
if ( size[0] >= size[1] && size[0] >= size[2] ) {
|
|
anode->axis = 0;
|
|
} else if ( size[1] >= size[0] && size[1] >= size[2] ) {
|
|
anode->axis = 1;
|
|
} else {
|
|
anode->axis = 2;
|
|
}
|
|
|
|
anode->dist = 0.5f * ( bounds[1][anode->axis] + bounds[0][anode->axis] );
|
|
|
|
front = bounds;
|
|
back = bounds;
|
|
|
|
front[0][anode->axis] = back[1][anode->axis] = anode->dist;
|
|
|
|
anode->children[0] = CreateClipSectors_r( depth+1, front, maxSector );
|
|
anode->children[1] = CreateClipSectors_r( depth+1, back, maxSector );
|
|
|
|
return anode;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idClip::Init
|
|
===============
|
|
*/
|
|
void idClip::Init( void ) {
|
|
cmHandle_t h;
|
|
idVec3 size, maxSector = vec3_origin;
|
|
|
|
// clear clip sectors
|
|
clipSectors = new clipSector_t[MAX_SECTORS];
|
|
memset( clipSectors, 0, MAX_SECTORS * sizeof( clipSector_t ) );
|
|
numClipSectors = 0;
|
|
touchCount = -1;
|
|
// get world map bounds
|
|
h = collisionModelManager->LoadModel( "worldMap", false );
|
|
collisionModelManager->GetModelBounds( h, worldBounds );
|
|
// create world sectors
|
|
CreateClipSectors_r( 0, worldBounds, maxSector );
|
|
|
|
size = worldBounds[1] - worldBounds[0];
|
|
gameLocal.Printf( "map bounds are (%1.1f, %1.1f, %1.1f)\n", size[0], size[1], size[2] );
|
|
gameLocal.Printf( "max clip sector is (%1.1f, %1.1f, %1.1f)\n", maxSector[0], maxSector[1], maxSector[2] );
|
|
|
|
// initialize a default clip model
|
|
defaultClipModel.LoadModel( idTraceModel( idBounds( idVec3( 0, 0, 0 ) ).Expand( 8 ) ) );
|
|
|
|
// set counters to zero
|
|
numRotations = numTranslations = numMotions = numRenderModelTraces = numContents = numContacts = 0;
|
|
}
|
|
|
|
/*
|
|
===============
|
|
idClip::Shutdown
|
|
===============
|
|
*/
|
|
void idClip::Shutdown( void ) {
|
|
delete[] clipSectors;
|
|
clipSectors = NULL;
|
|
|
|
// free the trace model used for the temporaryClipModel
|
|
if ( temporaryClipModel.traceModelIndex != -1 ) {
|
|
idClipModel::FreeTraceModel( temporaryClipModel.traceModelIndex );
|
|
temporaryClipModel.traceModelIndex = -1;
|
|
}
|
|
|
|
// free the trace model used for the defaultClipModel
|
|
if ( defaultClipModel.traceModelIndex != -1 ) {
|
|
idClipModel::FreeTraceModel( defaultClipModel.traceModelIndex );
|
|
defaultClipModel.traceModelIndex = -1;
|
|
}
|
|
|
|
clipLinkAllocator.Shutdown();
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idClip::ClipModelsTouchingBounds_r
|
|
====================
|
|
*/
|
|
typedef struct listParms_s {
|
|
idBounds bounds;
|
|
int contentMask;
|
|
idClipModel ** list;
|
|
int count;
|
|
int maxCount;
|
|
} listParms_t;
|
|
|
|
void idClip::ClipModelsTouchingBounds_r( const struct clipSector_s *node, listParms_t &parms ) const {
|
|
|
|
while( node->axis != -1 ) {
|
|
if ( parms.bounds[0][node->axis] > node->dist ) {
|
|
node = node->children[0];
|
|
} else if ( parms.bounds[1][node->axis] < node->dist ) {
|
|
node = node->children[1];
|
|
} else {
|
|
ClipModelsTouchingBounds_r( node->children[0], parms );
|
|
node = node->children[1];
|
|
}
|
|
}
|
|
|
|
for ( clipLink_t *link = node->clipLinks; link; link = link->nextInSector ) {
|
|
idClipModel *check = link->clipModel;
|
|
|
|
// if the clip model is enabled
|
|
if ( !check->enabled ) {
|
|
continue;
|
|
}
|
|
|
|
// avoid duplicates in the list
|
|
if ( check->touchCount == touchCount ) {
|
|
continue;
|
|
}
|
|
|
|
// if the clip model does not have any contents we are looking for
|
|
if ( !( check->contents & parms.contentMask ) ) {
|
|
continue;
|
|
}
|
|
|
|
// if the bounds really do overlap
|
|
if ( check->absBounds[0][0] > parms.bounds[1][0] ||
|
|
check->absBounds[1][0] < parms.bounds[0][0] ||
|
|
check->absBounds[0][1] > parms.bounds[1][1] ||
|
|
check->absBounds[1][1] < parms.bounds[0][1] ||
|
|
check->absBounds[0][2] > parms.bounds[1][2] ||
|
|
check->absBounds[1][2] < parms.bounds[0][2] ) {
|
|
continue;
|
|
}
|
|
|
|
if ( parms.count >= parms.maxCount ) {
|
|
gameLocal.Warning( "idClip::ClipModelsTouchingBounds_r: max count" );
|
|
return;
|
|
}
|
|
|
|
check->touchCount = touchCount;
|
|
parms.list[parms.count] = check;
|
|
parms.count++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idClip::ClipModelsTouchingBounds
|
|
================
|
|
*/
|
|
int idClip::ClipModelsTouchingBounds( const idBounds &bounds, int contentMask, idClipModel **clipModelList, int maxCount ) const {
|
|
listParms_t parms;
|
|
|
|
if ( bounds[0][0] > bounds[1][0] ||
|
|
bounds[0][1] > bounds[1][1] ||
|
|
bounds[0][2] > bounds[1][2] ) {
|
|
// we should not go through the tree for degenerate or backwards bounds
|
|
assert( false );
|
|
return 0;
|
|
}
|
|
|
|
parms.bounds[0] = bounds[0] - vec3_boxEpsilon;
|
|
parms.bounds[1] = bounds[1] + vec3_boxEpsilon;
|
|
parms.contentMask = contentMask;
|
|
parms.list = clipModelList;
|
|
parms.count = 0;
|
|
parms.maxCount = maxCount;
|
|
|
|
touchCount++;
|
|
ClipModelsTouchingBounds_r( clipSectors, parms );
|
|
|
|
return parms.count;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idClip::EntitiesTouchingBounds
|
|
================
|
|
*/
|
|
int idClip::EntitiesTouchingBounds( const idBounds &bounds, int contentMask, idEntity **entityList, int maxCount ) const {
|
|
idClipModel *clipModelList[MAX_GENTITIES];
|
|
int i, j, count, entCount;
|
|
|
|
count = idClip::ClipModelsTouchingBounds( bounds, contentMask, clipModelList, MAX_GENTITIES );
|
|
entCount = 0;
|
|
for ( i = 0; i < count; i++ ) {
|
|
// entity could already be in the list because an entity can use multiple clip models
|
|
for ( j = 0; j < entCount; j++ ) {
|
|
if ( entityList[j] == clipModelList[i]->entity ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( j >= entCount ) {
|
|
if ( entCount >= maxCount ) {
|
|
gameLocal.Warning( "idClip::EntitiesTouchingBounds: max count" );
|
|
return entCount;
|
|
}
|
|
entityList[entCount] = clipModelList[i]->entity;
|
|
entCount++;
|
|
}
|
|
}
|
|
|
|
return entCount;
|
|
}
|
|
|
|
/*
|
|
====================
|
|
idClip::GetTraceClipModels
|
|
|
|
an ent will be excluded from testing if:
|
|
cm->entity == passEntity ( don't clip against the pass entity )
|
|
cm->entity == passOwner ( missiles don't clip with owner )
|
|
cm->owner == passEntity ( don't interact with your own missiles )
|
|
cm->owner == passOwner ( don't interact with other missiles from same owner )
|
|
====================
|
|
*/
|
|
int idClip::GetTraceClipModels( const idBounds &bounds, int contentMask, const idEntity *passEntity, idClipModel **clipModelList ) const {
|
|
int i, num;
|
|
idClipModel *cm;
|
|
idEntity *passOwner;
|
|
|
|
num = ClipModelsTouchingBounds( bounds, contentMask, clipModelList, MAX_GENTITIES );
|
|
|
|
if ( !passEntity ) {
|
|
return num;
|
|
}
|
|
|
|
if ( passEntity->GetPhysics()->GetNumClipModels() > 0 ) {
|
|
passOwner = passEntity->GetPhysics()->GetClipModel()->GetOwner();
|
|
} else {
|
|
passOwner = NULL;
|
|
}
|
|
|
|
for ( i = 0; i < num; i++ ) {
|
|
|
|
cm = clipModelList[i];
|
|
|
|
// check if we should ignore this entity
|
|
if ( cm->entity == passEntity ) {
|
|
clipModelList[i] = NULL; // don't clip against the pass entity
|
|
} else if ( cm->entity == passOwner ) {
|
|
clipModelList[i] = NULL; // missiles don't clip with their owner
|
|
} else if ( cm->owner ) {
|
|
if ( cm->owner == passEntity ) {
|
|
clipModelList[i] = NULL; // don't clip against own missiles
|
|
} else if ( cm->owner == passOwner ) {
|
|
clipModelList[i] = NULL; // don't clip against other missiles from same owner
|
|
}
|
|
}
|
|
}
|
|
|
|
return num;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idClip::TraceRenderModel
|
|
============
|
|
*/
|
|
void idClip::TraceRenderModel( trace_t &trace, const idVec3 &start, const idVec3 &end, const float radius, const idMat3 &axis, idClipModel *touch ) const {
|
|
trace.fraction = 1.0f;
|
|
|
|
// if the trace is passing through the bounds
|
|
if ( touch->absBounds.Expand( radius ).LineIntersection( start, end ) ) {
|
|
modelTrace_t modelTrace;
|
|
|
|
// test with exact render model and modify trace_t structure accordingly
|
|
if ( gameRenderWorld->ModelTrace( modelTrace, touch->renderModelHandle, start, end, radius ) ) {
|
|
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;
|
|
// NOTE: trace.c.id will be the joint number
|
|
touch->id = JOINT_HANDLE_TO_CLIPMODEL_ID( modelTrace.jointNumber );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idClip::TraceModelForClipModel
|
|
============
|
|
*/
|
|
const idTraceModel *idClip::TraceModelForClipModel( const idClipModel *mdl ) const {
|
|
if ( !mdl ) {
|
|
return NULL;
|
|
} else {
|
|
if ( !mdl->IsTraceModel() ) {
|
|
if ( mdl->GetEntity() ) {
|
|
gameLocal.Error( "TraceModelForClipModel: clip model %d on '%s' is not a trace model\n", mdl->GetId(), mdl->GetEntity()->name.c_str() );
|
|
} else {
|
|
gameLocal.Error( "TraceModelForClipModel: clip model %d is not a trace model\n", mdl->GetId() );
|
|
}
|
|
}
|
|
return idClipModel::GetCachedTraceModel( mdl->traceModelIndex );
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idClip::TestHugeTranslation
|
|
============
|
|
*/
|
|
ID_INLINE bool TestHugeTranslation( trace_t &results, const idClipModel *mdl, const idVec3 &start, const idVec3 &end, const idMat3 &trmAxis ) {
|
|
if ( mdl != NULL && ( end - start ).LengthSqr() > Square( CM_MAX_TRACE_DIST ) ) {
|
|
#ifndef CTF
|
|
// May be important: This occurs in CTF when a player connects and spawns
|
|
// in the PVS of a player that has a flag that is spawning the idMoveableItem
|
|
// "nuggets". The error seems benign and the assert was getting in the way
|
|
// of testing.
|
|
assert( 0 );
|
|
#endif
|
|
|
|
results.fraction = 0.0f;
|
|
results.endpos = start;
|
|
results.endAxis = trmAxis;
|
|
memset( &results.c, 0, sizeof( results.c ) );
|
|
results.c.point = start;
|
|
|
|
if ( mdl->GetEntity() ) {
|
|
gameLocal.Printf( "huge translation for clip model %d on entity %d '%s'\n", mdl->GetId(), mdl->GetEntity()->entityNumber, mdl->GetEntity()->GetName() );
|
|
} else {
|
|
gameLocal.Printf( "huge translation for clip model %d\n", mdl->GetId() );
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idClip::TranslationEntities
|
|
============
|
|
*/
|
|
void idClip::TranslationEntities( trace_t &results, const idVec3 &start, const idVec3 &end,
|
|
const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) {
|
|
int i, num;
|
|
idClipModel *touch, *clipModelList[MAX_GENTITIES];
|
|
idBounds traceBounds;
|
|
float radius;
|
|
trace_t trace;
|
|
const idTraceModel *trm;
|
|
|
|
if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) {
|
|
return;
|
|
}
|
|
|
|
trm = TraceModelForClipModel( mdl );
|
|
|
|
results.fraction = 1.0f;
|
|
results.endpos = end;
|
|
results.endAxis = trmAxis;
|
|
|
|
if ( !trm ) {
|
|
traceBounds.FromPointTranslation( start, end - start );
|
|
radius = 0.0f;
|
|
} else {
|
|
traceBounds.FromBoundsTranslation( trm->bounds, start, trmAxis, end - start );
|
|
radius = trm->bounds.GetRadius();
|
|
}
|
|
|
|
num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList );
|
|
|
|
for ( i = 0; i < num; i++ ) {
|
|
touch = clipModelList[i];
|
|
|
|
if ( !touch ) {
|
|
continue;
|
|
}
|
|
|
|
if ( touch->renderModelHandle != -1 ) {
|
|
idClip::numRenderModelTraces++;
|
|
TraceRenderModel( trace, start, end, radius, trmAxis, touch );
|
|
} else {
|
|
idClip::numTranslations++;
|
|
collisionModelManager->Translation( &trace, start, end, trm, trmAxis, contentMask,
|
|
touch->Handle(), touch->origin, touch->axis );
|
|
}
|
|
|
|
if ( trace.fraction < results.fraction ) {
|
|
results = trace;
|
|
results.c.entityNum = touch->entity->entityNumber;
|
|
results.c.id = touch->id;
|
|
if ( results.fraction == 0.0f ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idClip::Translation
|
|
============
|
|
*/
|
|
bool idClip::Translation( trace_t &results, const idVec3 &start, const idVec3 &end,
|
|
const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) {
|
|
int i, num;
|
|
idClipModel *touch, *clipModelList[MAX_GENTITIES];
|
|
idBounds traceBounds;
|
|
float radius;
|
|
trace_t trace;
|
|
const idTraceModel *trm;
|
|
|
|
if ( TestHugeTranslation( results, mdl, start, end, trmAxis ) ) {
|
|
return true;
|
|
}
|
|
|
|
trm = TraceModelForClipModel( mdl );
|
|
|
|
if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) {
|
|
// test world
|
|
idClip::numTranslations++;
|
|
collisionModelManager->Translation( &results, start, end, trm, trmAxis, contentMask, 0, vec3_origin, mat3_default );
|
|
results.c.entityNum = results.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE;
|
|
if ( results.fraction == 0.0f ) {
|
|
return true; // blocked immediately by the world
|
|
}
|
|
} else {
|
|
memset( &results, 0, sizeof( results ) );
|
|
results.fraction = 1.0f;
|
|
results.endpos = end;
|
|
results.endAxis = trmAxis;
|
|
}
|
|
|
|
if ( !trm ) {
|
|
traceBounds.FromPointTranslation( start, results.endpos - start );
|
|
radius = 0.0f;
|
|
} else {
|
|
traceBounds.FromBoundsTranslation( trm->bounds, start, trmAxis, results.endpos - start );
|
|
radius = trm->bounds.GetRadius();
|
|
}
|
|
|
|
num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList );
|
|
|
|
for ( i = 0; i < num; i++ ) {
|
|
touch = clipModelList[i];
|
|
|
|
if ( !touch ) {
|
|
continue;
|
|
}
|
|
|
|
if ( touch->renderModelHandle != -1 ) {
|
|
idClip::numRenderModelTraces++;
|
|
TraceRenderModel( trace, start, end, radius, trmAxis, touch );
|
|
} else {
|
|
idClip::numTranslations++;
|
|
collisionModelManager->Translation( &trace, start, end, trm, trmAxis, contentMask,
|
|
touch->Handle(), touch->origin, touch->axis );
|
|
}
|
|
|
|
if ( trace.fraction < results.fraction ) {
|
|
results = trace;
|
|
results.c.entityNum = touch->entity->entityNumber;
|
|
results.c.id = touch->id;
|
|
if ( results.fraction == 0.0f ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ( results.fraction < 1.0f );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idClip::Rotation
|
|
============
|
|
*/
|
|
bool idClip::Rotation( trace_t &results, const idVec3 &start, const idRotation &rotation,
|
|
const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) {
|
|
int i, num;
|
|
idClipModel *touch, *clipModelList[MAX_GENTITIES];
|
|
idBounds traceBounds;
|
|
trace_t trace;
|
|
const idTraceModel *trm;
|
|
|
|
trm = TraceModelForClipModel( mdl );
|
|
|
|
if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) {
|
|
// test world
|
|
idClip::numRotations++;
|
|
collisionModelManager->Rotation( &results, start, rotation, trm, trmAxis, contentMask, 0, vec3_origin, mat3_default );
|
|
results.c.entityNum = results.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE;
|
|
if ( results.fraction == 0.0f ) {
|
|
return true; // blocked immediately by the world
|
|
}
|
|
} else {
|
|
memset( &results, 0, sizeof( results ) );
|
|
results.fraction = 1.0f;
|
|
results.endpos = start;
|
|
results.endAxis = trmAxis * rotation.ToMat3();
|
|
}
|
|
|
|
if ( !trm ) {
|
|
traceBounds.FromPointRotation( start, rotation );
|
|
} else {
|
|
traceBounds.FromBoundsRotation( trm->bounds, start, trmAxis, rotation );
|
|
}
|
|
|
|
num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList );
|
|
|
|
for ( i = 0; i < num; i++ ) {
|
|
touch = clipModelList[i];
|
|
|
|
if ( !touch ) {
|
|
continue;
|
|
}
|
|
|
|
// no rotational collision with render models
|
|
if ( touch->renderModelHandle != -1 ) {
|
|
continue;
|
|
}
|
|
|
|
idClip::numRotations++;
|
|
collisionModelManager->Rotation( &trace, start, rotation, trm, trmAxis, contentMask,
|
|
touch->Handle(), touch->origin, touch->axis );
|
|
|
|
if ( trace.fraction < results.fraction ) {
|
|
results = trace;
|
|
results.c.entityNum = touch->entity->entityNumber;
|
|
results.c.id = touch->id;
|
|
if ( results.fraction == 0.0f ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ( results.fraction < 1.0f );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idClip::Motion
|
|
============
|
|
*/
|
|
bool idClip::Motion( trace_t &results, const idVec3 &start, const idVec3 &end, const idRotation &rotation,
|
|
const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) {
|
|
int i, num;
|
|
idClipModel *touch, *clipModelList[MAX_GENTITIES];
|
|
idVec3 dir, endPosition;
|
|
idBounds traceBounds;
|
|
float radius;
|
|
trace_t translationalTrace, rotationalTrace, trace;
|
|
idRotation endRotation;
|
|
const idTraceModel *trm;
|
|
|
|
assert( rotation.GetOrigin() == start );
|
|
|
|
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
|
|
return Rotation( results, start, rotation, mdl, trmAxis, contentMask, passEntity );
|
|
}
|
|
} else if ( start != end ) {
|
|
// pure translation
|
|
return Translation( results, start, end, mdl, trmAxis, contentMask, passEntity );
|
|
} else {
|
|
// no motion
|
|
results.fraction = 1.0f;
|
|
results.endpos = start;
|
|
results.endAxis = trmAxis;
|
|
return false;
|
|
}
|
|
|
|
trm = TraceModelForClipModel( mdl );
|
|
|
|
radius = trm->bounds.GetRadius();
|
|
|
|
if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) {
|
|
// translational collision with world
|
|
idClip::numTranslations++;
|
|
collisionModelManager->Translation( &translationalTrace, start, end, trm, trmAxis, contentMask, 0, vec3_origin, mat3_default );
|
|
translationalTrace.c.entityNum = translationalTrace.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE;
|
|
} else {
|
|
memset( &translationalTrace, 0, sizeof( translationalTrace ) );
|
|
translationalTrace.fraction = 1.0f;
|
|
translationalTrace.endpos = end;
|
|
translationalTrace.endAxis = trmAxis;
|
|
}
|
|
|
|
if ( translationalTrace.fraction != 0.0f ) {
|
|
|
|
traceBounds.FromBoundsRotation( trm->bounds, 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];
|
|
}
|
|
}
|
|
|
|
num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList );
|
|
|
|
for ( i = 0; i < num; i++ ) {
|
|
touch = clipModelList[i];
|
|
|
|
if ( !touch ) {
|
|
continue;
|
|
}
|
|
|
|
if ( touch->renderModelHandle != -1 ) {
|
|
idClip::numRenderModelTraces++;
|
|
TraceRenderModel( trace, start, end, radius, trmAxis, touch );
|
|
} else {
|
|
idClip::numTranslations++;
|
|
collisionModelManager->Translation( &trace, start, end, trm, trmAxis, contentMask,
|
|
touch->Handle(), touch->origin, touch->axis );
|
|
}
|
|
|
|
if ( trace.fraction < translationalTrace.fraction ) {
|
|
translationalTrace = trace;
|
|
translationalTrace.c.entityNum = touch->entity->entityNumber;
|
|
translationalTrace.c.id = touch->id;
|
|
if ( translationalTrace.fraction == 0.0f ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
num = -1;
|
|
}
|
|
|
|
endPosition = translationalTrace.endpos;
|
|
endRotation = rotation;
|
|
endRotation.SetOrigin( endPosition );
|
|
|
|
if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) {
|
|
// rotational collision with world
|
|
idClip::numRotations++;
|
|
collisionModelManager->Rotation( &rotationalTrace, endPosition, endRotation, trm, trmAxis, contentMask, 0, vec3_origin, mat3_default );
|
|
rotationalTrace.c.entityNum = rotationalTrace.fraction != 1.0f ? ENTITYNUM_WORLD : ENTITYNUM_NONE;
|
|
} else {
|
|
memset( &rotationalTrace, 0, sizeof( rotationalTrace ) );
|
|
rotationalTrace.fraction = 1.0f;
|
|
rotationalTrace.endpos = endPosition;
|
|
rotationalTrace.endAxis = trmAxis * rotation.ToMat3();
|
|
}
|
|
|
|
if ( rotationalTrace.fraction != 0.0f ) {
|
|
|
|
if ( num == -1 ) {
|
|
traceBounds.FromBoundsRotation( trm->bounds, endPosition, trmAxis, endRotation );
|
|
num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList );
|
|
}
|
|
|
|
for ( i = 0; i < num; i++ ) {
|
|
touch = clipModelList[i];
|
|
|
|
if ( !touch ) {
|
|
continue;
|
|
}
|
|
|
|
// no rotational collision detection with render models
|
|
if ( touch->renderModelHandle != -1 ) {
|
|
continue;
|
|
}
|
|
|
|
idClip::numRotations++;
|
|
collisionModelManager->Rotation( &trace, endPosition, endRotation, trm, trmAxis, contentMask,
|
|
touch->Handle(), touch->origin, touch->axis );
|
|
|
|
if ( trace.fraction < rotationalTrace.fraction ) {
|
|
rotationalTrace = trace;
|
|
rotationalTrace.c.entityNum = touch->entity->entityNumber;
|
|
rotationalTrace.c.id = touch->id;
|
|
if ( rotationalTrace.fraction == 0.0f ) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( rotationalTrace.fraction < 1.0f ) {
|
|
results = rotationalTrace;
|
|
} else {
|
|
results = translationalTrace;
|
|
results.endAxis = rotationalTrace.endAxis;
|
|
}
|
|
|
|
results.fraction = Max( translationalTrace.fraction, rotationalTrace.fraction );
|
|
|
|
return ( translationalTrace.fraction < 1.0f || rotationalTrace.fraction < 1.0f );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idClip::Contacts
|
|
============
|
|
*/
|
|
int idClip::Contacts( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth,
|
|
const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) {
|
|
int i, j, num, n, numContacts;
|
|
idClipModel *touch, *clipModelList[MAX_GENTITIES];
|
|
idBounds traceBounds;
|
|
const idTraceModel *trm;
|
|
|
|
trm = TraceModelForClipModel( mdl );
|
|
|
|
if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) {
|
|
// test world
|
|
idClip::numContacts++;
|
|
numContacts = collisionModelManager->Contacts( contacts, maxContacts, start, dir, depth, trm, trmAxis, contentMask, 0, vec3_origin, mat3_default );
|
|
} else {
|
|
numContacts = 0;
|
|
}
|
|
|
|
for ( i = 0; i < numContacts; i++ ) {
|
|
contacts[i].entityNum = ENTITYNUM_WORLD;
|
|
contacts[i].id = 0;
|
|
}
|
|
|
|
if ( numContacts >= maxContacts ) {
|
|
return numContacts;
|
|
}
|
|
|
|
if ( !trm ) {
|
|
traceBounds = idBounds( start ).Expand( depth );
|
|
} else {
|
|
traceBounds.FromTransformedBounds( trm->bounds, start, trmAxis );
|
|
traceBounds.ExpandSelf( depth );
|
|
}
|
|
|
|
num = GetTraceClipModels( traceBounds, contentMask, passEntity, clipModelList );
|
|
|
|
for ( i = 0; i < num; i++ ) {
|
|
touch = clipModelList[i];
|
|
|
|
if ( !touch ) {
|
|
continue;
|
|
}
|
|
|
|
// no contacts with render models
|
|
if ( touch->renderModelHandle != -1 ) {
|
|
continue;
|
|
}
|
|
|
|
idClip::numContacts++;
|
|
n = collisionModelManager->Contacts( contacts + numContacts, maxContacts - numContacts,
|
|
start, dir, depth, trm, trmAxis, contentMask,
|
|
touch->Handle(), touch->origin, touch->axis );
|
|
|
|
for ( j = 0; j < n; j++ ) {
|
|
contacts[numContacts].entityNum = touch->entity->entityNumber;
|
|
contacts[numContacts].id = touch->id;
|
|
numContacts++;
|
|
}
|
|
|
|
if ( numContacts >= maxContacts ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return numContacts;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idClip::Contents
|
|
============
|
|
*/
|
|
int idClip::Contents( const idVec3 &start, const idClipModel *mdl, const idMat3 &trmAxis, int contentMask, const idEntity *passEntity ) {
|
|
int i, num, contents;
|
|
idClipModel *touch, *clipModelList[MAX_GENTITIES];
|
|
idBounds traceBounds;
|
|
const idTraceModel *trm;
|
|
|
|
trm = TraceModelForClipModel( mdl );
|
|
|
|
if ( !passEntity || passEntity->entityNumber != ENTITYNUM_WORLD ) {
|
|
// test world
|
|
idClip::numContents++;
|
|
contents = collisionModelManager->Contents( start, trm, trmAxis, contentMask, 0, vec3_origin, mat3_default );
|
|
} else {
|
|
contents = 0;
|
|
}
|
|
|
|
if ( !trm ) {
|
|
traceBounds[0] = start;
|
|
traceBounds[1] = start;
|
|
} else if ( trmAxis.IsRotated() ) {
|
|
traceBounds.FromTransformedBounds( trm->bounds, start, trmAxis );
|
|
} else {
|
|
traceBounds[0] = trm->bounds[0] + start;
|
|
traceBounds[1] = trm->bounds[1] + start;
|
|
}
|
|
|
|
num = GetTraceClipModels( traceBounds, -1, passEntity, clipModelList );
|
|
|
|
for ( i = 0; i < num; i++ ) {
|
|
touch = clipModelList[i];
|
|
|
|
if ( !touch ) {
|
|
continue;
|
|
}
|
|
|
|
// no contents test with render models
|
|
if ( touch->renderModelHandle != -1 ) {
|
|
continue;
|
|
}
|
|
|
|
// if the entity does not have any contents we are looking for
|
|
if ( ( touch->contents & contentMask ) == 0 ) {
|
|
continue;
|
|
}
|
|
|
|
// if the entity has no new contents flags
|
|
if ( ( touch->contents & contents ) == touch->contents ) {
|
|
continue;
|
|
}
|
|
|
|
idClip::numContents++;
|
|
if ( collisionModelManager->Contents( start, trm, trmAxis, contentMask, touch->Handle(), touch->origin, touch->axis ) ) {
|
|
contents |= ( touch->contents & contentMask );
|
|
}
|
|
}
|
|
|
|
return contents;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idClip::TranslationModel
|
|
============
|
|
*/
|
|
void idClip::TranslationModel( trace_t &results, const idVec3 &start, const idVec3 &end,
|
|
const idClipModel *mdl, const idMat3 &trmAxis, int contentMask,
|
|
cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) {
|
|
const idTraceModel *trm = TraceModelForClipModel( mdl );
|
|
idClip::numTranslations++;
|
|
collisionModelManager->Translation( &results, start, end, trm, trmAxis, contentMask, model, modelOrigin, modelAxis );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idClip::RotationModel
|
|
============
|
|
*/
|
|
void idClip::RotationModel( trace_t &results, const idVec3 &start, const idRotation &rotation,
|
|
const idClipModel *mdl, const idMat3 &trmAxis, int contentMask,
|
|
cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) {
|
|
const idTraceModel *trm = TraceModelForClipModel( mdl );
|
|
idClip::numRotations++;
|
|
collisionModelManager->Rotation( &results, start, rotation, trm, trmAxis, contentMask, model, modelOrigin, modelAxis );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idClip::ContactsModel
|
|
============
|
|
*/
|
|
int idClip::ContactsModel( contactInfo_t *contacts, const int maxContacts, const idVec3 &start, const idVec6 &dir, const float depth,
|
|
const idClipModel *mdl, const idMat3 &trmAxis, int contentMask,
|
|
cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) {
|
|
const idTraceModel *trm = TraceModelForClipModel( mdl );
|
|
idClip::numContacts++;
|
|
return collisionModelManager->Contacts( contacts, maxContacts, start, dir, depth, trm, trmAxis, contentMask, model, modelOrigin, modelAxis );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idClip::ContentsModel
|
|
============
|
|
*/
|
|
int idClip::ContentsModel( const idVec3 &start,
|
|
const idClipModel *mdl, const idMat3 &trmAxis, int contentMask,
|
|
cmHandle_t model, const idVec3 &modelOrigin, const idMat3 &modelAxis ) {
|
|
const idTraceModel *trm = TraceModelForClipModel( mdl );
|
|
idClip::numContents++;
|
|
return collisionModelManager->Contents( start, trm, trmAxis, contentMask, model, modelOrigin, modelAxis );
|
|
}
|
|
|
|
/*
|
|
============
|
|
idClip::GetModelContactFeature
|
|
============
|
|
*/
|
|
bool idClip::GetModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, idFixedWinding &winding ) const {
|
|
int i;
|
|
cmHandle_t handle;
|
|
idVec3 start, end;
|
|
|
|
handle = -1;
|
|
winding.Clear();
|
|
|
|
if ( clipModel == NULL ) {
|
|
handle = 0;
|
|
} else {
|
|
if ( clipModel->renderModelHandle != -1 ) {
|
|
winding += contact.point;
|
|
return true;
|
|
} else if ( clipModel->traceModelIndex != -1 ) {
|
|
handle = collisionModelManager->SetupTrmModel( *idClipModel::GetCachedTraceModel( clipModel->traceModelIndex ), clipModel->material );
|
|
} else {
|
|
handle = clipModel->collisionModelHandle;
|
|
}
|
|
}
|
|
|
|
// if contact with a collision model
|
|
if ( handle != -1 ) {
|
|
switch( contact.type ) {
|
|
case CONTACT_EDGE: {
|
|
// the model contact feature is a collision model edge
|
|
collisionModelManager->GetModelEdge( handle, contact.modelFeature, start, end );
|
|
winding += start;
|
|
winding += end;
|
|
break;
|
|
}
|
|
case CONTACT_MODELVERTEX: {
|
|
// the model contact feature is a collision model vertex
|
|
collisionModelManager->GetModelVertex( handle, contact.modelFeature, start );
|
|
winding += start;
|
|
break;
|
|
}
|
|
case CONTACT_TRMVERTEX: {
|
|
// the model contact feature is a collision model polygon
|
|
collisionModelManager->GetModelPolygon( handle, contact.modelFeature, winding );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// transform the winding to world space
|
|
if ( clipModel ) {
|
|
for ( i = 0; i < winding.GetNumPoints(); i++ ) {
|
|
winding[i].ToVec3() *= clipModel->axis;
|
|
winding[i].ToVec3() += clipModel->origin;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idClip::PrintStatistics
|
|
============
|
|
*/
|
|
void idClip::PrintStatistics( void ) {
|
|
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::DrawClipModels
|
|
============
|
|
*/
|
|
void idClip::DrawClipModels( const idVec3 &eye, const float radius, const idEntity *passEntity ) {
|
|
int i, num;
|
|
idBounds bounds;
|
|
idClipModel *clipModelList[MAX_GENTITIES];
|
|
idClipModel *clipModel;
|
|
|
|
bounds = idBounds( eye ).Expand( radius );
|
|
|
|
num = idClip::ClipModelsTouchingBounds( bounds, -1, clipModelList, MAX_GENTITIES );
|
|
|
|
for ( i = 0; i < num; i++ ) {
|
|
clipModel = clipModelList[i];
|
|
if ( clipModel->GetEntity() == passEntity ) {
|
|
continue;
|
|
}
|
|
if ( clipModel->renderModelHandle != -1 ) {
|
|
gameRenderWorld->DebugBounds( colorCyan, clipModel->GetAbsBounds() );
|
|
} else {
|
|
collisionModelManager->DrawModel( clipModel->Handle(), clipModel->GetOrigin(), clipModel->GetAxis(), eye, radius );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idClip::DrawModelContactFeature
|
|
============
|
|
*/
|
|
bool idClip::DrawModelContactFeature( const contactInfo_t &contact, const idClipModel *clipModel, int lifetime ) const {
|
|
int i;
|
|
idMat3 axis;
|
|
idFixedWinding winding;
|
|
|
|
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;
|
|
}
|