mirror of
https://github.com/dhewm/dhewm3.git
synced 2025-03-02 15:41:57 +00:00
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.
3695 lines
99 KiB
C++
3695 lines
99 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.
|
|
|
|
===========================================================================
|
|
*/
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
Trace model vs. polygonal model collision detection.
|
|
|
|
It is more important to minimize the number of collision polygons
|
|
than it is to minimize the number of edges used for collision
|
|
detection (total edges - internal edges).
|
|
|
|
Stitching the world tends to minimize the number of edges used
|
|
for collision detection (more internal edges). However stitching
|
|
also results in more collision polygons which usually makes a
|
|
stitched world slower.
|
|
|
|
In an average map over 30% of all edges is internal.
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
#include "sys/platform.h"
|
|
#include "idlib/Timer.h"
|
|
#include "renderer/Model.h"
|
|
#include "renderer/ModelManager.h"
|
|
#include "renderer/RenderWorld.h"
|
|
|
|
#include "cm/CollisionModel_local.h"
|
|
|
|
idCollisionModelManagerLocal collisionModelManagerLocal;
|
|
idCollisionModelManager * collisionModelManager = &collisionModelManagerLocal;
|
|
|
|
cm_windingList_t * cm_windingList;
|
|
cm_windingList_t * cm_outList;
|
|
cm_windingList_t * cm_tmpList;
|
|
|
|
idHashIndex * cm_vertexHash;
|
|
idHashIndex * cm_edgeHash;
|
|
|
|
idBounds cm_modelBounds;
|
|
int cm_vertexShift;
|
|
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
Proc BSP tree for data pruning
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::ParseProcNodes
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::ParseProcNodes( idLexer *src ) {
|
|
int i;
|
|
|
|
src->ExpectTokenString( "{" );
|
|
|
|
numProcNodes = src->ParseInt();
|
|
if ( numProcNodes < 0 ) {
|
|
src->Error( "ParseProcNodes: bad numProcNodes" );
|
|
}
|
|
procNodes = (cm_procNode_t *)Mem_ClearedAlloc( numProcNodes * sizeof( cm_procNode_t ) );
|
|
|
|
for ( i = 0; i < numProcNodes; i++ ) {
|
|
cm_procNode_t *node;
|
|
|
|
node = &procNodes[i];
|
|
|
|
src->Parse1DMatrix( 4, node->plane.ToFloatPtr() );
|
|
node->children[0] = src->ParseInt();
|
|
node->children[1] = src->ParseInt();
|
|
}
|
|
|
|
src->ExpectTokenString( "}" );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::LoadProcBSP
|
|
|
|
FIXME: if the nodes would be at the start of the .proc file it would speed things up considerably
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::LoadProcBSP( const char *name ) {
|
|
idStr filename;
|
|
idToken token;
|
|
idLexer *src;
|
|
|
|
// load it
|
|
filename = name;
|
|
filename.SetFileExtension( PROC_FILE_EXT );
|
|
src = new idLexer( filename, LEXFL_NOSTRINGCONCAT | LEXFL_NODOLLARPRECOMPILE );
|
|
if ( !src->IsLoaded() ) {
|
|
common->Warning( "idCollisionModelManagerLocal::LoadProcBSP: couldn't load %s", filename.c_str() );
|
|
delete src;
|
|
return;
|
|
}
|
|
|
|
if ( !src->ReadToken( &token ) || token.Icmp( PROC_FILE_ID ) ) {
|
|
common->Warning( "idCollisionModelManagerLocal::LoadProcBSP: bad id '%s' instead of '%s'", token.c_str(), PROC_FILE_ID );
|
|
delete src;
|
|
return;
|
|
}
|
|
|
|
// parse the file
|
|
while ( 1 ) {
|
|
if ( !src->ReadToken( &token ) ) {
|
|
break;
|
|
}
|
|
|
|
if ( token == "model" ) {
|
|
src->SkipBracedSection();
|
|
continue;
|
|
}
|
|
|
|
if ( token == "shadowModel" ) {
|
|
src->SkipBracedSection();
|
|
continue;
|
|
}
|
|
|
|
if ( token == "interAreaPortals" ) {
|
|
src->SkipBracedSection();
|
|
continue;
|
|
}
|
|
|
|
if ( token == "nodes" ) {
|
|
ParseProcNodes( src );
|
|
break;
|
|
}
|
|
|
|
src->Error( "idCollisionModelManagerLocal::LoadProcBSP: bad token \"%s\"", token.c_str() );
|
|
}
|
|
|
|
delete src;
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
Free map
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::Clear
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::Clear( void ) {
|
|
mapName.Clear();
|
|
mapFileTime = 0;
|
|
loaded = 0;
|
|
checkCount = 0;
|
|
maxModels = 0;
|
|
numModels = 0;
|
|
models = NULL;
|
|
memset( trmPolygons, 0, sizeof( trmPolygons ) );
|
|
trmBrushes[0] = NULL;
|
|
trmMaterial = NULL;
|
|
numProcNodes = 0;
|
|
procNodes = NULL;
|
|
getContacts = false;
|
|
contacts = NULL;
|
|
maxContacts = 0;
|
|
numContacts = 0;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::RemovePolygonReferences_r
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::RemovePolygonReferences_r( cm_node_t *node, cm_polygon_t *p ) {
|
|
cm_polygonRef_t *pref;
|
|
|
|
while( node ) {
|
|
for ( pref = node->polygons; pref; pref = pref->next ) {
|
|
if ( pref->p == p ) {
|
|
pref->p = NULL;
|
|
// cannot return here because we can have links down the tree due to polygon merging
|
|
//return;
|
|
}
|
|
}
|
|
// if leaf node
|
|
if ( node->planeType == -1 ) {
|
|
break;
|
|
}
|
|
if ( p->bounds[0][node->planeType] > node->planeDist ) {
|
|
node = node->children[0];
|
|
}
|
|
else if ( p->bounds[1][node->planeType] < node->planeDist ) {
|
|
node = node->children[1];
|
|
}
|
|
else {
|
|
RemovePolygonReferences_r( node->children[1], p );
|
|
node = node->children[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::RemoveBrushReferences_r
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::RemoveBrushReferences_r( cm_node_t *node, cm_brush_t *b ) {
|
|
cm_brushRef_t *bref;
|
|
|
|
while( node ) {
|
|
for ( bref = node->brushes; bref; bref = bref->next ) {
|
|
if ( bref->b == b ) {
|
|
bref->b = NULL;
|
|
return;
|
|
}
|
|
}
|
|
// if leaf node
|
|
if ( node->planeType == -1 ) {
|
|
break;
|
|
}
|
|
if ( b->bounds[0][node->planeType] > node->planeDist ) {
|
|
node = node->children[0];
|
|
}
|
|
else if ( b->bounds[1][node->planeType] < node->planeDist ) {
|
|
node = node->children[1];
|
|
}
|
|
else {
|
|
RemoveBrushReferences_r( node->children[1], b );
|
|
node = node->children[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::FreeNode
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::FreeNode( cm_node_t *node ) {
|
|
// don't free the node here
|
|
// the nodes are allocated in blocks which are freed when the model is freed
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::FreePolygonReference
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::FreePolygonReference( cm_polygonRef_t *pref ) {
|
|
// don't free the polygon reference here
|
|
// the polygon references are allocated in blocks which are freed when the model is freed
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::FreeBrushReference
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::FreeBrushReference( cm_brushRef_t *bref ) {
|
|
// don't free the brush reference here
|
|
// the brush references are allocated in blocks which are freed when the model is freed
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::FreePolygon
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::FreePolygon( cm_model_t *model, cm_polygon_t *poly ) {
|
|
model->numPolygons--;
|
|
model->polygonMemory -= sizeof( cm_polygon_t ) + ( poly->numEdges - 1 ) * sizeof( poly->edges[0] );
|
|
if ( model->polygonBlock == NULL ) {
|
|
Mem_Free( poly );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::FreeBrush
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::FreeBrush( cm_model_t *model, cm_brush_t *brush ) {
|
|
model->numBrushes--;
|
|
model->brushMemory -= sizeof( cm_brush_t ) + ( brush->numPlanes - 1 ) * sizeof( brush->planes[0] );
|
|
if ( model->brushBlock == NULL ) {
|
|
Mem_Free( brush );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::FreeTree_r
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::FreeTree_r( cm_model_t *model, cm_node_t *headNode, cm_node_t *node ) {
|
|
cm_polygonRef_t *pref;
|
|
cm_polygon_t *p;
|
|
cm_brushRef_t *bref;
|
|
cm_brush_t *b;
|
|
|
|
// free all polygons at this node
|
|
for ( pref = node->polygons; pref; pref = node->polygons ) {
|
|
p = pref->p;
|
|
if ( p ) {
|
|
// remove all other references to this polygon
|
|
RemovePolygonReferences_r( headNode, p );
|
|
FreePolygon( model, p );
|
|
}
|
|
node->polygons = pref->next;
|
|
FreePolygonReference( pref );
|
|
}
|
|
// free all brushes at this node
|
|
for ( bref = node->brushes; bref; bref = node->brushes ) {
|
|
b = bref->b;
|
|
if ( b ) {
|
|
// remove all other references to this brush
|
|
RemoveBrushReferences_r( headNode, b );
|
|
FreeBrush( model, b );
|
|
}
|
|
node->brushes = bref->next;
|
|
FreeBrushReference( bref );
|
|
}
|
|
// recurse down the tree
|
|
if ( node->planeType != -1 ) {
|
|
FreeTree_r( model, headNode, node->children[0] );
|
|
node->children[0] = NULL;
|
|
FreeTree_r( model, headNode, node->children[1] );
|
|
node->children[1] = NULL;
|
|
}
|
|
FreeNode( node );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::FreeModel
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::FreeModel( cm_model_t *model ) {
|
|
cm_polygonRefBlock_t *polygonRefBlock, *nextPolygonRefBlock;
|
|
cm_brushRefBlock_t *brushRefBlock, *nextBrushRefBlock;
|
|
cm_nodeBlock_t *nodeBlock, *nextNodeBlock;
|
|
|
|
// free the tree structure
|
|
if ( model->node ) {
|
|
FreeTree_r( model, model->node, model->node );
|
|
}
|
|
// free blocks with polygon references
|
|
for ( polygonRefBlock = model->polygonRefBlocks; polygonRefBlock; polygonRefBlock = nextPolygonRefBlock ) {
|
|
nextPolygonRefBlock = polygonRefBlock->next;
|
|
Mem_Free( polygonRefBlock );
|
|
}
|
|
// free blocks with brush references
|
|
for ( brushRefBlock = model->brushRefBlocks; brushRefBlock; brushRefBlock = nextBrushRefBlock ) {
|
|
nextBrushRefBlock = brushRefBlock->next;
|
|
Mem_Free( brushRefBlock );
|
|
}
|
|
// free blocks with nodes
|
|
for ( nodeBlock = model->nodeBlocks; nodeBlock; nodeBlock = nextNodeBlock ) {
|
|
nextNodeBlock = nodeBlock->next;
|
|
Mem_Free( nodeBlock );
|
|
}
|
|
// free block allocated polygons
|
|
Mem_Free( model->polygonBlock );
|
|
// free block allocated brushes
|
|
Mem_Free( model->brushBlock );
|
|
// free edges
|
|
Mem_Free( model->edges );
|
|
// free vertices
|
|
Mem_Free( model->vertices );
|
|
// free the model
|
|
delete model;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::FreeMap
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::FreeMap( void ) {
|
|
int i;
|
|
|
|
if ( !loaded ) {
|
|
Clear();
|
|
return;
|
|
}
|
|
|
|
for ( i = 0; i < maxModels; i++ ) {
|
|
if ( !models[i] ) {
|
|
continue;
|
|
}
|
|
FreeModel( models[i] );
|
|
}
|
|
|
|
FreeTrmModelStructure();
|
|
|
|
Mem_Free( models );
|
|
|
|
Clear();
|
|
|
|
ShutdownHash();
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::FreeTrmModelStructure
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::FreeTrmModelStructure( void ) {
|
|
int i;
|
|
|
|
assert( models );
|
|
if ( !models[MAX_SUBMODELS] ) {
|
|
return;
|
|
}
|
|
|
|
for ( i = 0; i < MAX_TRACEMODEL_POLYS; i++ ) {
|
|
FreePolygon( models[MAX_SUBMODELS], trmPolygons[i]->p );
|
|
}
|
|
FreeBrush( models[MAX_SUBMODELS], trmBrushes[0]->b );
|
|
|
|
models[MAX_SUBMODELS]->node->polygons = NULL;
|
|
models[MAX_SUBMODELS]->node->brushes = NULL;
|
|
FreeModel( models[MAX_SUBMODELS] );
|
|
}
|
|
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
Edge normals
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::CalculateEdgeNormals
|
|
================
|
|
*/
|
|
#define SHARP_EDGE_DOT -0.7f
|
|
|
|
void idCollisionModelManagerLocal::CalculateEdgeNormals( cm_model_t *model, cm_node_t *node ) {
|
|
cm_polygonRef_t *pref;
|
|
cm_polygon_t *p;
|
|
cm_edge_t *edge;
|
|
float dot, s;
|
|
int i, edgeNum;
|
|
idVec3 dir;
|
|
|
|
while( 1 ) {
|
|
for ( pref = node->polygons; pref; pref = pref->next ) {
|
|
p = pref->p;
|
|
// if we checked this polygon already
|
|
if ( p->checkcount == checkCount ) {
|
|
continue;
|
|
}
|
|
p->checkcount = checkCount;
|
|
|
|
for ( i = 0; i < p->numEdges; i++ ) {
|
|
edgeNum = p->edges[i];
|
|
edge = model->edges + abs( edgeNum );
|
|
if ( edge->normal[0] == 0.0f && edge->normal[1] == 0.0f && edge->normal[2] == 0.0f ) {
|
|
// if the edge is only used by this polygon
|
|
if ( edge->numUsers == 1 ) {
|
|
dir = model->vertices[ edge->vertexNum[edgeNum < 0]].p - model->vertices[ edge->vertexNum[edgeNum > 0]].p;
|
|
edge->normal = p->plane.Normal().Cross( dir );
|
|
edge->normal.Normalize();
|
|
} else {
|
|
// the edge is used by more than one polygon
|
|
edge->normal = p->plane.Normal();
|
|
}
|
|
} else {
|
|
dot = edge->normal * p->plane.Normal();
|
|
// if the two planes make a very sharp edge
|
|
if ( dot < SHARP_EDGE_DOT ) {
|
|
// max length normal pointing outside both polygons
|
|
dir = model->vertices[ edge->vertexNum[edgeNum > 0]].p - model->vertices[ edge->vertexNum[edgeNum < 0]].p;
|
|
edge->normal = edge->normal.Cross( dir ) + p->plane.Normal().Cross( -dir );
|
|
edge->normal *= ( 0.5f / ( 0.5f + 0.5f * SHARP_EDGE_DOT ) ) / edge->normal.Length();
|
|
model->numSharpEdges++;
|
|
} else {
|
|
s = 0.5f / ( 0.5f + 0.5f * dot );
|
|
edge->normal = s * ( edge->normal + p->plane.Normal() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// if leaf node
|
|
if ( node->planeType == -1 ) {
|
|
break;
|
|
}
|
|
CalculateEdgeNormals( model, node->children[1] );
|
|
node = node->children[0];
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
Trace model to general collision model
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::AllocModel
|
|
================
|
|
*/
|
|
cm_model_t *idCollisionModelManagerLocal::AllocModel( void ) {
|
|
cm_model_t *model;
|
|
|
|
model = new cm_model_t;
|
|
model->contents = 0;
|
|
model->isConvex = false;
|
|
model->maxVertices = 0;
|
|
model->numVertices = 0;
|
|
model->vertices = NULL;
|
|
model->maxEdges = 0;
|
|
model->numEdges = 0;
|
|
model->edges= NULL;
|
|
model->node = NULL;
|
|
model->nodeBlocks = NULL;
|
|
model->polygonRefBlocks = NULL;
|
|
model->brushRefBlocks = NULL;
|
|
model->polygonBlock = NULL;
|
|
model->brushBlock = NULL;
|
|
model->numPolygons = model->polygonMemory =
|
|
model->numBrushes = model->brushMemory =
|
|
model->numNodes = model->numBrushRefs =
|
|
model->numPolygonRefs = model->numInternalEdges =
|
|
model->numSharpEdges = model->numRemovedPolys =
|
|
model->numMergedPolys = model->usedMemory = 0;
|
|
|
|
return model;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::AllocNode
|
|
================
|
|
*/
|
|
cm_node_t *idCollisionModelManagerLocal::AllocNode( cm_model_t *model, int blockSize ) {
|
|
int i;
|
|
cm_node_t *node;
|
|
cm_nodeBlock_t *nodeBlock;
|
|
|
|
if ( !model->nodeBlocks || !model->nodeBlocks->nextNode ) {
|
|
nodeBlock = (cm_nodeBlock_t *) Mem_ClearedAlloc( sizeof( cm_nodeBlock_t ) + blockSize * sizeof(cm_node_t) );
|
|
nodeBlock->nextNode = (cm_node_t *) ( ( (byte *) nodeBlock ) + sizeof( cm_nodeBlock_t ) );
|
|
nodeBlock->next = model->nodeBlocks;
|
|
model->nodeBlocks = nodeBlock;
|
|
node = nodeBlock->nextNode;
|
|
for ( i = 0; i < blockSize - 1; i++ ) {
|
|
node->parent = node + 1;
|
|
node = node->parent;
|
|
}
|
|
node->parent = NULL;
|
|
}
|
|
|
|
node = model->nodeBlocks->nextNode;
|
|
model->nodeBlocks->nextNode = node->parent;
|
|
node->parent = NULL;
|
|
|
|
return node;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::AllocPolygonReference
|
|
================
|
|
*/
|
|
cm_polygonRef_t *idCollisionModelManagerLocal::AllocPolygonReference( cm_model_t *model, int blockSize ) {
|
|
int i;
|
|
cm_polygonRef_t *pref;
|
|
cm_polygonRefBlock_t *prefBlock;
|
|
|
|
if ( !model->polygonRefBlocks || !model->polygonRefBlocks->nextRef ) {
|
|
prefBlock = (cm_polygonRefBlock_t *) Mem_Alloc( sizeof( cm_polygonRefBlock_t ) + blockSize * sizeof(cm_polygonRef_t) );
|
|
prefBlock->nextRef = (cm_polygonRef_t *) ( ( (byte *) prefBlock ) + sizeof( cm_polygonRefBlock_t ) );
|
|
prefBlock->next = model->polygonRefBlocks;
|
|
model->polygonRefBlocks = prefBlock;
|
|
pref = prefBlock->nextRef;
|
|
for ( i = 0; i < blockSize - 1; i++ ) {
|
|
pref->next = pref + 1;
|
|
pref = pref->next;
|
|
}
|
|
pref->next = NULL;
|
|
}
|
|
|
|
pref = model->polygonRefBlocks->nextRef;
|
|
model->polygonRefBlocks->nextRef = pref->next;
|
|
|
|
return pref;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::AllocBrushReference
|
|
================
|
|
*/
|
|
cm_brushRef_t *idCollisionModelManagerLocal::AllocBrushReference( cm_model_t *model, int blockSize ) {
|
|
int i;
|
|
cm_brushRef_t *bref;
|
|
cm_brushRefBlock_t *brefBlock;
|
|
|
|
if ( !model->brushRefBlocks || !model->brushRefBlocks->nextRef ) {
|
|
brefBlock = (cm_brushRefBlock_t *) Mem_Alloc( sizeof(cm_brushRefBlock_t) + blockSize * sizeof(cm_brushRef_t) );
|
|
brefBlock->nextRef = (cm_brushRef_t *) ( ( (byte *) brefBlock ) + sizeof(cm_brushRefBlock_t) );
|
|
brefBlock->next = model->brushRefBlocks;
|
|
model->brushRefBlocks = brefBlock;
|
|
bref = brefBlock->nextRef;
|
|
for ( i = 0; i < blockSize - 1; i++ ) {
|
|
bref->next = bref + 1;
|
|
bref = bref->next;
|
|
}
|
|
bref->next = NULL;
|
|
}
|
|
|
|
bref = model->brushRefBlocks->nextRef;
|
|
model->brushRefBlocks->nextRef = bref->next;
|
|
|
|
return bref;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::AllocPolygon
|
|
================
|
|
*/
|
|
cm_polygon_t *idCollisionModelManagerLocal::AllocPolygon( cm_model_t *model, int numEdges ) {
|
|
cm_polygon_t *poly;
|
|
int size;
|
|
|
|
size = sizeof( cm_polygon_t ) + ( numEdges - 1 ) * sizeof( poly->edges[0] );
|
|
model->numPolygons++;
|
|
model->polygonMemory += size;
|
|
if ( model->polygonBlock && model->polygonBlock->bytesRemaining >= size ) {
|
|
poly = (cm_polygon_t *) model->polygonBlock->next;
|
|
model->polygonBlock->next += size;
|
|
model->polygonBlock->bytesRemaining -= size;
|
|
} else {
|
|
poly = (cm_polygon_t *) Mem_Alloc( size );
|
|
}
|
|
return poly;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::AllocBrush
|
|
================
|
|
*/
|
|
cm_brush_t *idCollisionModelManagerLocal::AllocBrush( cm_model_t *model, int numPlanes ) {
|
|
cm_brush_t *brush;
|
|
int size;
|
|
|
|
size = sizeof( cm_brush_t ) + ( numPlanes - 1 ) * sizeof( brush->planes[0] );
|
|
model->numBrushes++;
|
|
model->brushMemory += size;
|
|
if ( model->brushBlock && model->brushBlock->bytesRemaining >= size ) {
|
|
brush = (cm_brush_t *) model->brushBlock->next;
|
|
model->brushBlock->next += size;
|
|
model->brushBlock->bytesRemaining -= size;
|
|
} else {
|
|
brush = (cm_brush_t *) Mem_Alloc( size );
|
|
}
|
|
return brush;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::AddPolygonToNode
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::AddPolygonToNode( cm_model_t *model, cm_node_t *node, cm_polygon_t *p ) {
|
|
cm_polygonRef_t *pref;
|
|
|
|
pref = AllocPolygonReference( model, model->numPolygonRefs < REFERENCE_BLOCK_SIZE_SMALL ? REFERENCE_BLOCK_SIZE_SMALL : REFERENCE_BLOCK_SIZE_LARGE );
|
|
pref->p = p;
|
|
pref->next = node->polygons;
|
|
node->polygons = pref;
|
|
model->numPolygonRefs++;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::AddBrushToNode
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::AddBrushToNode( cm_model_t *model, cm_node_t *node, cm_brush_t *b ) {
|
|
cm_brushRef_t *bref;
|
|
|
|
bref = AllocBrushReference( model, model->numBrushRefs < REFERENCE_BLOCK_SIZE_SMALL ? REFERENCE_BLOCK_SIZE_SMALL : REFERENCE_BLOCK_SIZE_LARGE );
|
|
bref->b = b;
|
|
bref->next = node->brushes;
|
|
node->brushes = bref;
|
|
model->numBrushRefs++;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::SetupTrmModelStructure
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::SetupTrmModelStructure( void ) {
|
|
int i;
|
|
cm_node_t *node;
|
|
cm_model_t *model;
|
|
|
|
// setup model
|
|
model = AllocModel();
|
|
|
|
assert( models );
|
|
models[MAX_SUBMODELS] = model;
|
|
// create node to hold the collision data
|
|
node = (cm_node_t *) AllocNode( model, 1 );
|
|
node->planeType = -1;
|
|
model->node = node;
|
|
// allocate vertex and edge arrays
|
|
model->numVertices = 0;
|
|
model->maxVertices = MAX_TRACEMODEL_VERTS;
|
|
model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->maxVertices * sizeof(cm_vertex_t) );
|
|
model->numEdges = 0;
|
|
model->maxEdges = MAX_TRACEMODEL_EDGES+1;
|
|
model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->maxEdges * sizeof(cm_edge_t) );
|
|
// create a material for the trace model polygons
|
|
trmMaterial = declManager->FindMaterial( "_tracemodel", false );
|
|
if ( !trmMaterial ) {
|
|
common->FatalError( "_tracemodel material not found" );
|
|
}
|
|
|
|
// allocate polygons
|
|
for ( i = 0; i < MAX_TRACEMODEL_POLYS; i++ ) {
|
|
trmPolygons[i] = AllocPolygonReference( model, MAX_TRACEMODEL_POLYS );
|
|
trmPolygons[i]->p = AllocPolygon( model, MAX_TRACEMODEL_POLYEDGES );
|
|
trmPolygons[i]->p->bounds.Clear();
|
|
trmPolygons[i]->p->plane.Zero();
|
|
trmPolygons[i]->p->checkcount = 0;
|
|
trmPolygons[i]->p->contents = -1; // all contents
|
|
trmPolygons[i]->p->material = trmMaterial;
|
|
trmPolygons[i]->p->numEdges = 0;
|
|
}
|
|
// allocate brush for position test
|
|
trmBrushes[0] = AllocBrushReference( model, 1 );
|
|
trmBrushes[0]->b = AllocBrush( model, MAX_TRACEMODEL_POLYS );
|
|
trmBrushes[0]->b->primitiveNum = 0;
|
|
trmBrushes[0]->b->bounds.Clear();
|
|
trmBrushes[0]->b->checkcount = 0;
|
|
trmBrushes[0]->b->contents = -1; // all contents
|
|
trmBrushes[0]->b->numPlanes = 0;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::SetupTrmModel
|
|
|
|
Trace models (item boxes, etc) are converted to collision models on the fly, using the last model slot
|
|
as a reusable temporary buffer
|
|
================
|
|
*/
|
|
cmHandle_t idCollisionModelManagerLocal::SetupTrmModel( const idTraceModel &trm, const idMaterial *material ) {
|
|
int i, j;
|
|
cm_vertex_t *vertex;
|
|
cm_edge_t *edge;
|
|
cm_polygon_t *poly;
|
|
cm_model_t *model;
|
|
const traceModelVert_t *trmVert;
|
|
const traceModelEdge_t *trmEdge;
|
|
const traceModelPoly_t *trmPoly;
|
|
|
|
assert( models );
|
|
|
|
if ( material == NULL ) {
|
|
material = trmMaterial;
|
|
}
|
|
|
|
model = models[MAX_SUBMODELS];
|
|
model->node->brushes = NULL;
|
|
model->node->polygons = NULL;
|
|
// if not a valid trace model
|
|
if ( trm.type == TRM_INVALID || !trm.numPolys ) {
|
|
return TRACE_MODEL_HANDLE;
|
|
}
|
|
// vertices
|
|
model->numVertices = trm.numVerts;
|
|
vertex = model->vertices;
|
|
trmVert = trm.verts;
|
|
for ( i = 0; i < trm.numVerts; i++, vertex++, trmVert++ ) {
|
|
vertex->p = *trmVert;
|
|
vertex->sideSet = 0;
|
|
}
|
|
// edges
|
|
model->numEdges = trm.numEdges;
|
|
edge = model->edges + 1;
|
|
trmEdge = trm.edges + 1;
|
|
for ( i = 0; i < trm.numEdges; i++, edge++, trmEdge++ ) {
|
|
edge->vertexNum[0] = trmEdge->v[0];
|
|
edge->vertexNum[1] = trmEdge->v[1];
|
|
edge->normal = trmEdge->normal;
|
|
edge->internal = false;
|
|
edge->sideSet = 0;
|
|
}
|
|
// polygons
|
|
model->numPolygons = trm.numPolys;
|
|
trmPoly = trm.polys;
|
|
for ( i = 0; i < trm.numPolys; i++, trmPoly++ ) {
|
|
poly = trmPolygons[i]->p;
|
|
poly->numEdges = trmPoly->numEdges;
|
|
for ( j = 0; j < trmPoly->numEdges; j++ ) {
|
|
poly->edges[j] = trmPoly->edges[j];
|
|
}
|
|
poly->plane.SetNormal( trmPoly->normal );
|
|
poly->plane.SetDist( trmPoly->dist );
|
|
poly->bounds = trmPoly->bounds;
|
|
poly->material = material;
|
|
// link polygon at node
|
|
trmPolygons[i]->next = model->node->polygons;
|
|
model->node->polygons = trmPolygons[i];
|
|
}
|
|
// if the trace model is convex
|
|
if ( trm.isConvex ) {
|
|
// setup brush for position test
|
|
trmBrushes[0]->b->numPlanes = trm.numPolys;
|
|
for ( i = 0; i < trm.numPolys; i++ ) {
|
|
trmBrushes[0]->b->planes[i] = trmPolygons[i]->p->plane;
|
|
}
|
|
trmBrushes[0]->b->bounds = trm.bounds;
|
|
// link brush at node
|
|
trmBrushes[0]->next = model->node->brushes;
|
|
model->node->brushes = trmBrushes[0];
|
|
}
|
|
// model bounds
|
|
model->bounds = trm.bounds;
|
|
// convex
|
|
model->isConvex = trm.isConvex;
|
|
|
|
return TRACE_MODEL_HANDLE;
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
Optimisation, removal of polygons contained within brushes or solid
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
/*
|
|
============
|
|
idCollisionModelManagerLocal::R_ChoppedAwayByProcBSP
|
|
============
|
|
*/
|
|
int idCollisionModelManagerLocal::R_ChoppedAwayByProcBSP( int nodeNum, idFixedWinding *w, const idVec3 &normal, const idVec3 &origin, const float radius ) {
|
|
int res;
|
|
idFixedWinding back;
|
|
cm_procNode_t *node;
|
|
float dist;
|
|
|
|
do {
|
|
node = procNodes + nodeNum;
|
|
dist = node->plane.Normal() * origin + node->plane[3];
|
|
if ( dist > radius ) {
|
|
res = SIDE_FRONT;
|
|
}
|
|
else if ( dist < -radius ) {
|
|
res = SIDE_BACK;
|
|
}
|
|
else {
|
|
res = w->Split( &back, node->plane, CHOP_EPSILON );
|
|
}
|
|
if ( res == SIDE_FRONT ) {
|
|
nodeNum = node->children[0];
|
|
}
|
|
else if ( res == SIDE_BACK ) {
|
|
nodeNum = node->children[1];
|
|
}
|
|
else if ( res == SIDE_ON ) {
|
|
// continue with the side the winding faces
|
|
if ( node->plane.Normal() * normal > 0.0f ) {
|
|
nodeNum = node->children[0];
|
|
}
|
|
else {
|
|
nodeNum = node->children[1];
|
|
}
|
|
}
|
|
else {
|
|
// if either node is not solid
|
|
if ( node->children[0] < 0 || node->children[1] < 0 ) {
|
|
return false;
|
|
}
|
|
// only recurse if the node is not solid
|
|
if ( node->children[1] > 0 ) {
|
|
if ( !R_ChoppedAwayByProcBSP( node->children[1], &back, normal, origin, radius ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
nodeNum = node->children[0];
|
|
}
|
|
} while ( nodeNum > 0 );
|
|
if ( nodeNum < 0 ) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idCollisionModelManagerLocal::ChoppedAwayByProcBSP
|
|
============
|
|
*/
|
|
int idCollisionModelManagerLocal::ChoppedAwayByProcBSP( const idFixedWinding &w, const idPlane &plane, int contents ) {
|
|
idFixedWinding neww;
|
|
idBounds bounds;
|
|
float radius;
|
|
idVec3 origin;
|
|
|
|
// if the .proc file has no BSP tree
|
|
if ( procNodes == NULL ) {
|
|
return false;
|
|
}
|
|
// don't chop if the polygon is not solid
|
|
if ( !(contents & CONTENTS_SOLID) ) {
|
|
return false;
|
|
}
|
|
// make a local copy of the winding
|
|
neww = w;
|
|
neww.GetBounds( bounds );
|
|
origin = (bounds[1] - bounds[0]) * 0.5f;
|
|
radius = origin.Length() + CHOP_EPSILON;
|
|
origin = bounds[0] + origin;
|
|
//
|
|
return R_ChoppedAwayByProcBSP( 0, &neww, plane.Normal(), origin, radius );
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idCollisionModelManagerLocal::ChopWindingWithBrush
|
|
|
|
returns the least number of winding fragments outside the brush
|
|
=============
|
|
*/
|
|
void idCollisionModelManagerLocal::ChopWindingListWithBrush( cm_windingList_t *list, cm_brush_t *b ) {
|
|
int i, k, res, startPlane, planeNum, bestNumWindings;
|
|
idFixedWinding back, front;
|
|
idPlane plane;
|
|
bool chopped;
|
|
int sidedness[MAX_POINTS_ON_WINDING];
|
|
float dist;
|
|
|
|
if ( b->numPlanes > MAX_POINTS_ON_WINDING ) {
|
|
return;
|
|
}
|
|
|
|
// get sidedness for the list of windings
|
|
for ( i = 0; i < b->numPlanes; i++ ) {
|
|
plane = -b->planes[i];
|
|
|
|
dist = plane.Distance( list->origin );
|
|
if ( dist > list->radius ) {
|
|
sidedness[i] = SIDE_FRONT;
|
|
}
|
|
else if ( dist < -list->radius ) {
|
|
sidedness[i] = SIDE_BACK;
|
|
}
|
|
else {
|
|
sidedness[i] = list->bounds.PlaneSide( plane );
|
|
if ( sidedness[i] == PLANESIDE_FRONT ) {
|
|
sidedness[i] = SIDE_FRONT;
|
|
}
|
|
else if ( sidedness[i] == PLANESIDE_BACK ) {
|
|
sidedness[i] = SIDE_BACK;
|
|
}
|
|
else {
|
|
sidedness[i] = SIDE_CROSS;
|
|
}
|
|
}
|
|
}
|
|
|
|
cm_outList->numWindings = 0;
|
|
for ( k = 0; k < list->numWindings; k++ ) {
|
|
//
|
|
startPlane = 0;
|
|
bestNumWindings = 1 + b->numPlanes;
|
|
chopped = false;
|
|
do {
|
|
front = list->w[k];
|
|
cm_tmpList->numWindings = 0;
|
|
for ( planeNum = startPlane, i = 0; i < b->numPlanes; i++, planeNum++ ) {
|
|
|
|
if ( planeNum >= b->numPlanes ) {
|
|
planeNum = 0;
|
|
}
|
|
|
|
res = sidedness[planeNum];
|
|
|
|
if ( res == SIDE_CROSS ) {
|
|
plane = -b->planes[planeNum];
|
|
res = front.Split( &back, plane, CHOP_EPSILON );
|
|
}
|
|
|
|
// NOTE: disabling this can create gaps at places where Z-fighting occurs
|
|
// Z-fighting should not occur but what if there is a decal brush side
|
|
// with exactly the same size as another brush side ?
|
|
// only leave windings on a brush if the winding plane and brush side plane face the same direction
|
|
if ( res == SIDE_ON && list->primitiveNum >= 0 && (list->normal * b->planes[planeNum].Normal()) > 0 ) {
|
|
// return because all windings in the list will be on this brush side plane
|
|
return;
|
|
}
|
|
|
|
if ( res == SIDE_BACK ) {
|
|
if ( cm_outList->numWindings >= MAX_WINDING_LIST ) {
|
|
common->Warning( "idCollisionModelManagerLocal::ChopWindingWithBrush: primitive %d more than %d windings", list->primitiveNum, MAX_WINDING_LIST );
|
|
return;
|
|
}
|
|
// winding and brush didn't intersect, store the original winding
|
|
cm_outList->w[cm_outList->numWindings] = list->w[k];
|
|
cm_outList->numWindings++;
|
|
chopped = false;
|
|
break;
|
|
}
|
|
|
|
if ( res == SIDE_CROSS ) {
|
|
if ( cm_tmpList->numWindings >= MAX_WINDING_LIST ) {
|
|
common->Warning( "idCollisionModelManagerLocal::ChopWindingWithBrush: primitive %d more than %d windings", list->primitiveNum, MAX_WINDING_LIST );
|
|
return;
|
|
}
|
|
// store the front winding in the temporary list
|
|
cm_tmpList->w[cm_tmpList->numWindings] = back;
|
|
cm_tmpList->numWindings++;
|
|
chopped = true;
|
|
}
|
|
|
|
// if already found a start plane which generates less fragments
|
|
if ( cm_tmpList->numWindings >= bestNumWindings ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// find the best start plane to get the least number of fragments outside the brush
|
|
if ( cm_tmpList->numWindings < bestNumWindings ) {
|
|
bestNumWindings = cm_tmpList->numWindings;
|
|
// store windings from temporary list in the out list
|
|
for ( i = 0; i < cm_tmpList->numWindings; i++ ) {
|
|
if ( cm_outList->numWindings + i >= MAX_WINDING_LIST ) {
|
|
common->Warning( "idCollisionModelManagerLocal::ChopWindingWithBrush: primitive %d more than %d windings", list->primitiveNum, MAX_WINDING_LIST );
|
|
return;
|
|
}
|
|
cm_outList->w[cm_outList->numWindings+i] = cm_tmpList->w[i];
|
|
}
|
|
// if only one winding left then we can't do any better
|
|
if ( bestNumWindings == 1 ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// try the next start plane
|
|
startPlane++;
|
|
|
|
} while ( chopped && startPlane < b->numPlanes );
|
|
//
|
|
if ( chopped ) {
|
|
cm_outList->numWindings += bestNumWindings;
|
|
}
|
|
}
|
|
for ( k = 0; k < cm_outList->numWindings; k++ ) {
|
|
list->w[k] = cm_outList->w[k];
|
|
}
|
|
list->numWindings = cm_outList->numWindings;
|
|
}
|
|
|
|
/*
|
|
============
|
|
idCollisionModelManagerLocal::R_ChopWindingListWithTreeBrushes
|
|
============
|
|
*/
|
|
void idCollisionModelManagerLocal::R_ChopWindingListWithTreeBrushes( cm_windingList_t *list, cm_node_t *node ) {
|
|
int i;
|
|
cm_brushRef_t *bref;
|
|
cm_brush_t *b;
|
|
|
|
while( 1 ) {
|
|
for ( bref = node->brushes; bref; bref = bref->next ) {
|
|
b = bref->b;
|
|
// if we checked this brush already
|
|
if ( b->checkcount == checkCount ) {
|
|
continue;
|
|
}
|
|
b->checkcount = checkCount;
|
|
// if the windings in the list originate from this brush
|
|
if ( b->primitiveNum == list->primitiveNum ) {
|
|
continue;
|
|
}
|
|
// if brush has a different contents
|
|
if ( b->contents != list->contents ) {
|
|
continue;
|
|
}
|
|
// brush bounds and winding list bounds should overlap
|
|
for ( i = 0; i < 3; i++ ) {
|
|
if ( list->bounds[0][i] > b->bounds[1][i] ) {
|
|
break;
|
|
}
|
|
if ( list->bounds[1][i] < b->bounds[0][i] ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( i < 3 ) {
|
|
continue;
|
|
}
|
|
// chop windings in the list with brush
|
|
ChopWindingListWithBrush( list, b );
|
|
// if all windings are chopped away we're done
|
|
if ( !list->numWindings ) {
|
|
return;
|
|
}
|
|
}
|
|
// if leaf node
|
|
if ( node->planeType == -1 ) {
|
|
break;
|
|
}
|
|
if ( list->bounds[0][node->planeType] > node->planeDist ) {
|
|
node = node->children[0];
|
|
}
|
|
else if ( list->bounds[1][node->planeType] < node->planeDist ) {
|
|
node = node->children[1];
|
|
}
|
|
else {
|
|
R_ChopWindingListWithTreeBrushes( list, node->children[1] );
|
|
if ( !list->numWindings ) {
|
|
return;
|
|
}
|
|
node = node->children[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
============
|
|
idCollisionModelManagerLocal::WindingOutsideBrushes
|
|
|
|
Returns one winding which is not fully contained in brushes.
|
|
We always favor less polygons over a stitched world.
|
|
If the winding is partly contained and the contained pieces can be chopped off
|
|
without creating multiple winding fragments then the chopped winding is returned.
|
|
============
|
|
*/
|
|
idFixedWinding *idCollisionModelManagerLocal::WindingOutsideBrushes( idFixedWinding *w, const idPlane &plane, int contents, int primitiveNum, cm_node_t *headNode ) {
|
|
int i, windingLeft;
|
|
|
|
cm_windingList->bounds.Clear();
|
|
for ( i = 0; i < w->GetNumPoints(); i++ ) {
|
|
cm_windingList->bounds.AddPoint( (*w)[i].ToVec3() );
|
|
}
|
|
|
|
cm_windingList->origin = (cm_windingList->bounds[1] - cm_windingList->bounds[0]) * 0.5;
|
|
cm_windingList->radius = cm_windingList->origin.Length() + CHOP_EPSILON;
|
|
cm_windingList->origin = cm_windingList->bounds[0] + cm_windingList->origin;
|
|
cm_windingList->bounds[0] -= idVec3( CHOP_EPSILON, CHOP_EPSILON, CHOP_EPSILON );
|
|
cm_windingList->bounds[1] += idVec3( CHOP_EPSILON, CHOP_EPSILON, CHOP_EPSILON );
|
|
|
|
cm_windingList->w[0] = *w;
|
|
cm_windingList->numWindings = 1;
|
|
cm_windingList->normal = plane.Normal();
|
|
cm_windingList->contents = contents;
|
|
cm_windingList->primitiveNum = primitiveNum;
|
|
//
|
|
checkCount++;
|
|
R_ChopWindingListWithTreeBrushes( cm_windingList, headNode );
|
|
//
|
|
if ( !cm_windingList->numWindings ) {
|
|
return NULL;
|
|
}
|
|
if ( cm_windingList->numWindings == 1 ) {
|
|
return &cm_windingList->w[0];
|
|
}
|
|
// if not the world model
|
|
if ( numModels != 0 ) {
|
|
return w;
|
|
}
|
|
// check if winding fragments would be chopped away by the proc BSP tree
|
|
windingLeft = -1;
|
|
for ( i = 0; i < cm_windingList->numWindings; i++ ) {
|
|
if ( !ChoppedAwayByProcBSP( cm_windingList->w[i], plane, contents ) ) {
|
|
if ( windingLeft >= 0 ) {
|
|
return w;
|
|
}
|
|
windingLeft = i;
|
|
}
|
|
}
|
|
if ( windingLeft >= 0 ) {
|
|
return &cm_windingList->w[windingLeft];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
Merging polygons
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
/*
|
|
=============
|
|
idCollisionModelManagerLocal::ReplacePolygons
|
|
|
|
does not allow for a node to have multiple references to the same polygon
|
|
=============
|
|
*/
|
|
void idCollisionModelManagerLocal::ReplacePolygons( cm_model_t *model, cm_node_t *node, cm_polygon_t *p1, cm_polygon_t *p2, cm_polygon_t *newp ) {
|
|
cm_polygonRef_t *pref, *lastpref, *nextpref;
|
|
cm_polygon_t *p;
|
|
bool linked;
|
|
|
|
while( 1 ) {
|
|
linked = false;
|
|
lastpref = NULL;
|
|
for ( pref = node->polygons; pref; pref = nextpref ) {
|
|
nextpref = pref->next;
|
|
//
|
|
p = pref->p;
|
|
// if this polygon reference should change
|
|
if ( p == p1 || p == p2 ) {
|
|
// if the new polygon is already linked at this node
|
|
if ( linked ) {
|
|
if ( lastpref ) {
|
|
lastpref->next = nextpref;
|
|
}
|
|
else {
|
|
node->polygons = nextpref;
|
|
}
|
|
FreePolygonReference( pref );
|
|
model->numPolygonRefs--;
|
|
}
|
|
else {
|
|
pref->p = newp;
|
|
linked = true;
|
|
lastpref = pref;
|
|
}
|
|
}
|
|
else {
|
|
lastpref = pref;
|
|
}
|
|
}
|
|
// if leaf node
|
|
if ( node->planeType == -1 ) {
|
|
break;
|
|
}
|
|
if ( p1->bounds[0][node->planeType] > node->planeDist && p2->bounds[0][node->planeType] > node->planeDist ) {
|
|
node = node->children[0];
|
|
}
|
|
else if ( p1->bounds[1][node->planeType] < node->planeDist && p2->bounds[1][node->planeType] < node->planeDist ) {
|
|
node = node->children[1];
|
|
}
|
|
else {
|
|
ReplacePolygons( model, node->children[1], p1, p2, newp );
|
|
node = node->children[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idCollisionModelManagerLocal::TryMergePolygons
|
|
=============
|
|
*/
|
|
#define CONTINUOUS_EPSILON 0.005f
|
|
#define NORMAL_EPSILON 0.01f
|
|
|
|
cm_polygon_t *idCollisionModelManagerLocal::TryMergePolygons( cm_model_t *model, cm_polygon_t *p1, cm_polygon_t *p2 ) {
|
|
int i, j, nexti, prevj;
|
|
int p1BeforeShare, p1AfterShare, p2BeforeShare, p2AfterShare;
|
|
int newEdges[CM_MAX_POLYGON_EDGES], newNumEdges;
|
|
int edgeNum, edgeNum1, edgeNum2, newEdgeNum1, newEdgeNum2;
|
|
cm_edge_t *edge;
|
|
cm_polygon_t *newp;
|
|
idVec3 delta, normal;
|
|
float dot;
|
|
bool keep1, keep2;
|
|
|
|
if ( p1->material != p2->material ) {
|
|
return NULL;
|
|
}
|
|
if ( idMath::Fabs( p1->plane.Dist() - p2->plane.Dist() ) > NORMAL_EPSILON ) {
|
|
return NULL;
|
|
}
|
|
for ( i = 0; i < 3; i++ ) {
|
|
if ( idMath::Fabs( p1->plane.Normal()[i] - p2->plane.Normal()[i] ) > NORMAL_EPSILON ) {
|
|
return NULL;
|
|
}
|
|
if ( p1->bounds[0][i] > p2->bounds[1][i] ) {
|
|
return NULL;
|
|
}
|
|
if ( p1->bounds[1][i] < p2->bounds[0][i] ) {
|
|
return NULL;
|
|
}
|
|
}
|
|
// this allows for merging polygons with multiple shared edges
|
|
// polygons with multiple shared edges probably never occur tho ;)
|
|
p1BeforeShare = p1AfterShare = p2BeforeShare = p2AfterShare = -1;
|
|
for ( i = 0; i < p1->numEdges; i++ ) {
|
|
nexti = (i+1)%p1->numEdges;
|
|
for ( j = 0; j < p2->numEdges; j++ ) {
|
|
prevj = (j+p2->numEdges-1)%p2->numEdges;
|
|
//
|
|
if ( abs(p1->edges[i]) != abs(p2->edges[j]) ) {
|
|
// if the next edge of p1 and the previous edge of p2 are the same
|
|
if ( abs(p1->edges[nexti]) == abs(p2->edges[prevj]) ) {
|
|
// if both polygons don't use the edge in the same direction
|
|
if ( p1->edges[nexti] != p2->edges[prevj] ) {
|
|
p1BeforeShare = i;
|
|
p2AfterShare = j;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
// if both polygons don't use the edge in the same direction
|
|
else if ( p1->edges[i] != p2->edges[j] ) {
|
|
// if the next edge of p1 and the previous edge of p2 are not the same
|
|
if ( abs(p1->edges[nexti]) != abs(p2->edges[prevj]) ) {
|
|
p1AfterShare = nexti;
|
|
p2BeforeShare = prevj;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( p1BeforeShare < 0 || p1AfterShare < 0 || p2BeforeShare < 0 || p2AfterShare < 0 ) {
|
|
return NULL;
|
|
}
|
|
|
|
// check if the new polygon would still be convex
|
|
edgeNum = p1->edges[p1BeforeShare];
|
|
edge = model->edges + abs(edgeNum);
|
|
delta = model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p -
|
|
model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p;
|
|
normal = p1->plane.Normal().Cross( delta );
|
|
normal.Normalize();
|
|
|
|
edgeNum = p2->edges[p2AfterShare];
|
|
edge = model->edges + abs(edgeNum);
|
|
delta = model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p -
|
|
model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p;
|
|
|
|
dot = delta * normal;
|
|
if (dot < -CONTINUOUS_EPSILON)
|
|
return NULL; // not a convex polygon
|
|
keep1 = (bool)(dot > CONTINUOUS_EPSILON);
|
|
|
|
edgeNum = p2->edges[p2BeforeShare];
|
|
edge = model->edges + abs(edgeNum);
|
|
delta = model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p -
|
|
model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p;
|
|
normal = p1->plane.Normal().Cross( delta );
|
|
normal.Normalize();
|
|
|
|
edgeNum = p1->edges[p1AfterShare];
|
|
edge = model->edges + abs(edgeNum);
|
|
delta = model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p -
|
|
model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p;
|
|
|
|
dot = delta * normal;
|
|
if (dot < -CONTINUOUS_EPSILON)
|
|
return NULL; // not a convex polygon
|
|
keep2 = (bool)(dot > CONTINUOUS_EPSILON);
|
|
|
|
newEdgeNum1 = newEdgeNum2 = 0;
|
|
// get new edges if we need to replace colinear ones
|
|
if ( !keep1 ) {
|
|
edgeNum1 = p1->edges[p1BeforeShare];
|
|
edgeNum2 = p2->edges[p2AfterShare];
|
|
GetEdge( model, model->vertices[model->edges[abs(edgeNum1)].vertexNum[INTSIGNBITSET(edgeNum1)]].p,
|
|
model->vertices[model->edges[abs(edgeNum2)].vertexNum[INTSIGNBITNOTSET(edgeNum2)]].p,
|
|
&newEdgeNum1, -1 );
|
|
if ( newEdgeNum1 == 0 ) {
|
|
keep1 = true;
|
|
}
|
|
}
|
|
if ( !keep2 ) {
|
|
edgeNum1 = p2->edges[p2BeforeShare];
|
|
edgeNum2 = p1->edges[p1AfterShare];
|
|
GetEdge( model, model->vertices[model->edges[abs(edgeNum1)].vertexNum[INTSIGNBITSET(edgeNum1)]].p,
|
|
model->vertices[model->edges[abs(edgeNum2)].vertexNum[INTSIGNBITNOTSET(edgeNum2)]].p,
|
|
&newEdgeNum2, -1 );
|
|
if ( newEdgeNum2 == 0 ) {
|
|
keep2 = true;
|
|
}
|
|
}
|
|
// set edges for new polygon
|
|
newNumEdges = 0;
|
|
if ( !keep2 ) {
|
|
newEdges[newNumEdges++] = newEdgeNum2;
|
|
}
|
|
if ( p1AfterShare < p1BeforeShare ) {
|
|
for ( i = p1AfterShare + (!keep2); i <= p1BeforeShare - (!keep1); i++ ) {
|
|
newEdges[newNumEdges++] = p1->edges[i];
|
|
}
|
|
}
|
|
else {
|
|
for ( i = p1AfterShare + (!keep2); i < p1->numEdges; i++ ) {
|
|
newEdges[newNumEdges++] = p1->edges[i];
|
|
}
|
|
for ( i = 0; i <= p1BeforeShare - (!keep1); i++ ) {
|
|
newEdges[newNumEdges++] = p1->edges[i];
|
|
}
|
|
}
|
|
if ( !keep1 ) {
|
|
newEdges[newNumEdges++] = newEdgeNum1;
|
|
}
|
|
if ( p2AfterShare < p2BeforeShare ) {
|
|
for ( i = p2AfterShare + (!keep1); i <= p2BeforeShare - (!keep2); i++ ) {
|
|
newEdges[newNumEdges++] = p2->edges[i];
|
|
}
|
|
}
|
|
else {
|
|
for ( i = p2AfterShare + (!keep1); i < p2->numEdges; i++ ) {
|
|
newEdges[newNumEdges++] = p2->edges[i];
|
|
}
|
|
for ( i = 0; i <= p2BeforeShare - (!keep2); i++ ) {
|
|
newEdges[newNumEdges++] = p2->edges[i];
|
|
}
|
|
}
|
|
|
|
newp = AllocPolygon( model, newNumEdges );
|
|
memcpy( newp, p1, sizeof(cm_polygon_t) );
|
|
memcpy( newp->edges, newEdges, newNumEdges * sizeof(int) );
|
|
newp->numEdges = newNumEdges;
|
|
newp->checkcount = 0;
|
|
// increase usage count for the edges of this polygon
|
|
for ( i = 0; i < newp->numEdges; i++ ) {
|
|
if ( !keep1 && newp->edges[i] == newEdgeNum1 ) {
|
|
continue;
|
|
}
|
|
if ( !keep2 && newp->edges[i] == newEdgeNum2 ) {
|
|
continue;
|
|
}
|
|
model->edges[abs(newp->edges[i])].numUsers++;
|
|
}
|
|
// create new bounds from the merged polygons
|
|
newp->bounds = p1->bounds + p2->bounds;
|
|
|
|
return newp;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idCollisionModelManagerLocal::MergePolygonWithTreePolygons
|
|
=============
|
|
*/
|
|
bool idCollisionModelManagerLocal::MergePolygonWithTreePolygons( cm_model_t *model, cm_node_t *node, cm_polygon_t *polygon ) {
|
|
int i;
|
|
cm_polygonRef_t *pref;
|
|
cm_polygon_t *p, *newp;
|
|
|
|
while( 1 ) {
|
|
for ( pref = node->polygons; pref; pref = pref->next ) {
|
|
p = pref->p;
|
|
//
|
|
if ( p == polygon ) {
|
|
continue;
|
|
}
|
|
//
|
|
newp = TryMergePolygons( model, polygon, p );
|
|
// if polygons were merged
|
|
if ( newp ) {
|
|
model->numMergedPolys++;
|
|
// replace links to the merged polygons with links to the new polygon
|
|
ReplacePolygons( model, model->node, polygon, p, newp );
|
|
// decrease usage count for edges of both merged polygons
|
|
for ( i = 0; i < polygon->numEdges; i++ ) {
|
|
model->edges[abs(polygon->edges[i])].numUsers--;
|
|
}
|
|
for ( i = 0; i < p->numEdges; i++ ) {
|
|
model->edges[abs(p->edges[i])].numUsers--;
|
|
}
|
|
// free merged polygons
|
|
FreePolygon( model, polygon );
|
|
FreePolygon( model, p );
|
|
|
|
return true;
|
|
}
|
|
}
|
|
// if leaf node
|
|
if ( node->planeType == -1 ) {
|
|
break;
|
|
}
|
|
if ( polygon->bounds[0][node->planeType] > node->planeDist ) {
|
|
node = node->children[0];
|
|
}
|
|
else if ( polygon->bounds[1][node->planeType] < node->planeDist ) {
|
|
node = node->children[1];
|
|
}
|
|
else {
|
|
if ( MergePolygonWithTreePolygons( model, node->children[1], polygon ) ) {
|
|
return true;
|
|
}
|
|
node = node->children[0];
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idCollisionModelManagerLocal::MergeTreePolygons
|
|
|
|
try to merge any two polygons with the same surface flags and the same contents
|
|
=============
|
|
*/
|
|
void idCollisionModelManagerLocal::MergeTreePolygons( cm_model_t *model, cm_node_t *node ) {
|
|
cm_polygonRef_t *pref;
|
|
cm_polygon_t *p;
|
|
bool merge;
|
|
|
|
while( 1 ) {
|
|
do {
|
|
merge = false;
|
|
for ( pref = node->polygons; pref; pref = pref->next ) {
|
|
p = pref->p;
|
|
// if we checked this polygon already
|
|
if ( p->checkcount == checkCount ) {
|
|
continue;
|
|
}
|
|
p->checkcount = checkCount;
|
|
// try to merge this polygon with other polygons in the tree
|
|
if ( MergePolygonWithTreePolygons( model, model->node, p ) ) {
|
|
merge = true;
|
|
break;
|
|
}
|
|
}
|
|
} while (merge);
|
|
// if leaf node
|
|
if ( node->planeType == -1 ) {
|
|
break;
|
|
}
|
|
MergeTreePolygons( model, node->children[1] );
|
|
node = node->children[0];
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
Find internal edges
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
/*
|
|
|
|
if (two polygons have the same contents)
|
|
if (the normals of the two polygon planes face towards each other)
|
|
if (an edge is shared between the polygons)
|
|
if (the edge is not shared in the same direction)
|
|
then this is an internal edge
|
|
else
|
|
if (this edge is on the plane of the other polygon)
|
|
if (this edge if fully inside the winding of the other polygon)
|
|
then this edge is an internal edge
|
|
|
|
*/
|
|
|
|
/*
|
|
=============
|
|
idCollisionModelManagerLocal::PointInsidePolygon
|
|
=============
|
|
*/
|
|
bool idCollisionModelManagerLocal::PointInsidePolygon( cm_model_t *model, cm_polygon_t *p, idVec3 &v ) {
|
|
int i, edgeNum;
|
|
idVec3 *v1, *v2, dir1, dir2, vec;
|
|
cm_edge_t *edge;
|
|
|
|
for ( i = 0; i < p->numEdges; i++ ) {
|
|
edgeNum = p->edges[i];
|
|
edge = model->edges + abs(edgeNum);
|
|
//
|
|
v1 = &model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p;
|
|
v2 = &model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p;
|
|
dir1 = (*v2) - (*v1);
|
|
vec = v - (*v1);
|
|
dir2 = dir1.Cross( p->plane.Normal() );
|
|
if ( vec * dir2 > VERTEX_EPSILON ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idCollisionModelManagerLocal::FindInternalEdgesOnPolygon
|
|
=============
|
|
*/
|
|
void idCollisionModelManagerLocal::FindInternalEdgesOnPolygon( cm_model_t *model, cm_polygon_t *p1, cm_polygon_t *p2 ) {
|
|
int i, j, k, edgeNum;
|
|
cm_edge_t *edge;
|
|
idVec3 *v1, *v2, dir1, dir2;
|
|
float d;
|
|
|
|
// bounds of polygons should overlap or touch
|
|
for ( i = 0; i < 3; i++ ) {
|
|
if ( p1->bounds[0][i] > p2->bounds[1][i] ) {
|
|
return;
|
|
}
|
|
if ( p1->bounds[1][i] < p2->bounds[0][i] ) {
|
|
return;
|
|
}
|
|
}
|
|
//
|
|
// FIXME: doubled geometry causes problems
|
|
//
|
|
for ( i = 0; i < p1->numEdges; i++ ) {
|
|
edgeNum = p1->edges[i];
|
|
edge = model->edges + abs(edgeNum);
|
|
// if already an internal edge
|
|
if ( edge->internal ) {
|
|
continue;
|
|
}
|
|
//
|
|
v1 = &model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p;
|
|
v2 = &model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p;
|
|
// if either of the two vertices is outside the bounds of the other polygon
|
|
for ( k = 0; k < 3; k++ ) {
|
|
d = p2->bounds[1][k] + VERTEX_EPSILON;
|
|
if ( (*v1)[k] > d || (*v2)[k] > d ) {
|
|
break;
|
|
}
|
|
d = p2->bounds[0][k] - VERTEX_EPSILON;
|
|
if ( (*v1)[k] < d || (*v2)[k] < d ) {
|
|
break;
|
|
}
|
|
}
|
|
if ( k < 3 ) {
|
|
continue;
|
|
}
|
|
//
|
|
k = abs(edgeNum);
|
|
for ( j = 0; j < p2->numEdges; j++ ) {
|
|
if ( k == abs(p2->edges[j]) ) {
|
|
break;
|
|
}
|
|
}
|
|
// if the edge is shared between the two polygons
|
|
if ( j < p2->numEdges ) {
|
|
// if the edge is used by more than 2 polygons
|
|
if ( edge->numUsers > 2 ) {
|
|
// could still be internal but we'd have to test all polygons using the edge
|
|
continue;
|
|
}
|
|
// if the edge goes in the same direction for both polygons
|
|
if ( edgeNum == p2->edges[j] ) {
|
|
// the polygons can lay ontop of each other or one can obscure the other
|
|
continue;
|
|
}
|
|
}
|
|
// the edge was not shared
|
|
else {
|
|
// both vertices should be on the plane of the other polygon
|
|
d = p2->plane.Distance( *v1 );
|
|
if ( idMath::Fabs(d) > VERTEX_EPSILON ) {
|
|
continue;
|
|
}
|
|
d = p2->plane.Distance( *v2 );
|
|
if ( idMath::Fabs(d) > VERTEX_EPSILON ) {
|
|
continue;
|
|
}
|
|
}
|
|
// the two polygon plane normals should face towards each other
|
|
dir1 = (*v2) - (*v1);
|
|
dir2 = p1->plane.Normal().Cross( dir1 );
|
|
if ( p2->plane.Normal() * dir2 < 0 ) {
|
|
//continue;
|
|
break;
|
|
}
|
|
// if the edge was not shared
|
|
if ( j >= p2->numEdges ) {
|
|
// both vertices of the edge should be inside the winding of the other polygon
|
|
if ( !PointInsidePolygon( model, p2, *v1 ) ) {
|
|
continue;
|
|
}
|
|
if ( !PointInsidePolygon( model, p2, *v2 ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
// we got another internal edge
|
|
edge->internal = true;
|
|
model->numInternalEdges++;
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idCollisionModelManagerLocal::FindInternalPolygonEdges
|
|
=============
|
|
*/
|
|
void idCollisionModelManagerLocal::FindInternalPolygonEdges( cm_model_t *model, cm_node_t *node, cm_polygon_t *polygon ) {
|
|
cm_polygonRef_t *pref;
|
|
cm_polygon_t *p;
|
|
|
|
if ( polygon->material->GetCullType() == CT_TWO_SIDED || polygon->material->ShouldCreateBackSides() ) {
|
|
return;
|
|
}
|
|
|
|
while( 1 ) {
|
|
for ( pref = node->polygons; pref; pref = pref->next ) {
|
|
p = pref->p;
|
|
//
|
|
// FIXME: use some sort of additional checkcount because currently
|
|
// polygons can be checked multiple times
|
|
//
|
|
// if the polygons don't have the same contents
|
|
if ( p->contents != polygon->contents ) {
|
|
continue;
|
|
}
|
|
if ( p == polygon ) {
|
|
continue;
|
|
}
|
|
FindInternalEdgesOnPolygon( model, polygon, p );
|
|
}
|
|
// if leaf node
|
|
if ( node->planeType == -1 ) {
|
|
break;
|
|
}
|
|
if ( polygon->bounds[0][node->planeType] > node->planeDist ) {
|
|
node = node->children[0];
|
|
}
|
|
else if ( polygon->bounds[1][node->planeType] < node->planeDist ) {
|
|
node = node->children[1];
|
|
}
|
|
else {
|
|
FindInternalPolygonEdges( model, node->children[1], polygon );
|
|
node = node->children[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idCollisionModelManagerLocal::FindContainedEdges
|
|
=============
|
|
*/
|
|
void idCollisionModelManagerLocal::FindContainedEdges( cm_model_t *model, cm_polygon_t *p ) {
|
|
int i, edgeNum;
|
|
cm_edge_t *edge;
|
|
idFixedWinding w;
|
|
|
|
for ( i = 0; i < p->numEdges; i++ ) {
|
|
edgeNum = p->edges[i];
|
|
edge = model->edges + abs(edgeNum);
|
|
if ( edge->internal ) {
|
|
continue;
|
|
}
|
|
w.Clear();
|
|
w += model->vertices[edge->vertexNum[INTSIGNBITSET(edgeNum)]].p;
|
|
w += model->vertices[edge->vertexNum[INTSIGNBITNOTSET(edgeNum)]].p;
|
|
if ( ChoppedAwayByProcBSP( w, p->plane, p->contents ) ) {
|
|
edge->internal = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=============
|
|
idCollisionModelManagerLocal::FindInternalEdges
|
|
=============
|
|
*/
|
|
void idCollisionModelManagerLocal::FindInternalEdges( cm_model_t *model, cm_node_t *node ) {
|
|
cm_polygonRef_t *pref;
|
|
cm_polygon_t *p;
|
|
|
|
while( 1 ) {
|
|
for ( pref = node->polygons; pref; pref = pref->next ) {
|
|
p = pref->p;
|
|
// if we checked this polygon already
|
|
if ( p->checkcount == checkCount ) {
|
|
continue;
|
|
}
|
|
p->checkcount = checkCount;
|
|
|
|
FindInternalPolygonEdges( model, model->node, p );
|
|
|
|
//FindContainedEdges( model, p );
|
|
}
|
|
// if leaf node
|
|
if ( node->planeType == -1 ) {
|
|
break;
|
|
}
|
|
FindInternalEdges( model, node->children[1] );
|
|
node = node->children[0];
|
|
}
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
Spatial subdivision
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
CM_FindSplitter
|
|
================
|
|
*/
|
|
static int CM_FindSplitter( const cm_node_t *node, const idBounds &bounds, int *planeType, float *planeDist ) {
|
|
int i, j, type, axis[3], polyCount;
|
|
float dist, t, bestt, size[3];
|
|
cm_brushRef_t *bref;
|
|
cm_polygonRef_t *pref;
|
|
const cm_node_t *n;
|
|
bool forceSplit = false;
|
|
|
|
for ( i = 0; i < 3; i++ ) {
|
|
size[i] = bounds[1][i] - bounds[0][i];
|
|
axis[i] = i;
|
|
}
|
|
// sort on largest axis
|
|
for ( i = 0; i < 2; i++ ) {
|
|
if ( size[i] < size[i+1] ) {
|
|
t = size[i];
|
|
size[i] = size[i+1];
|
|
size[i+1] = t;
|
|
j = axis[i];
|
|
axis[i] = axis[i+1];
|
|
axis[i+1] = j;
|
|
i = -1;
|
|
}
|
|
}
|
|
// if the node is too small for further splits
|
|
if ( size[0] < MIN_NODE_SIZE ) {
|
|
polyCount = 0;
|
|
for ( pref = node->polygons; pref; pref = pref->next) {
|
|
polyCount++;
|
|
}
|
|
if ( polyCount > MAX_NODE_POLYGONS ) {
|
|
forceSplit = true;
|
|
}
|
|
}
|
|
// find an axial aligned splitter
|
|
for ( i = 0; i < 3; i++ ) {
|
|
// start with the largest axis first
|
|
type = axis[i];
|
|
bestt = size[i];
|
|
// if the node is small anough in this axis direction
|
|
if ( !forceSplit && bestt < MIN_NODE_SIZE ) {
|
|
break;
|
|
}
|
|
// find an axial splitter from the brush bounding boxes
|
|
// also try brushes from parent nodes
|
|
for ( n = node; n; n = n->parent ) {
|
|
for ( bref = n->brushes; bref; bref = bref->next) {
|
|
for ( j = 0; j < 2; j++ ) {
|
|
dist = bref->b->bounds[j][type];
|
|
// if the splitter is already used or outside node bounds
|
|
if ( dist >= bounds[1][type] || dist <= bounds[0][type] ) {
|
|
continue;
|
|
}
|
|
// find the most centered splitter
|
|
t = abs((bounds[1][type] - dist) - (dist - bounds[0][type]));
|
|
if ( t < bestt ) {
|
|
bestt = t;
|
|
*planeType = type;
|
|
*planeDist = dist;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// find an axial splitter from the polygon bounding boxes
|
|
// also try brushes from parent nodes
|
|
for ( n = node; n; n = n->parent ) {
|
|
for ( pref = n->polygons; pref; pref = pref->next) {
|
|
for ( j = 0; j < 2; j++ ) {
|
|
dist = pref->p->bounds[j][type];
|
|
// if the splitter is already used or outside node bounds
|
|
if ( dist >= bounds[1][type] || dist <= bounds[0][type] ) {
|
|
continue;
|
|
}
|
|
// find the most centered splitter
|
|
t = abs((bounds[1][type] - dist) - (dist - bounds[0][type]));
|
|
if ( t < bestt ) {
|
|
bestt = t;
|
|
*planeType = type;
|
|
*planeDist = dist;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// if we found a splitter on the largest axis
|
|
if ( bestt < size[i] ) {
|
|
// if forced split due to lots of polygons
|
|
if ( forceSplit ) {
|
|
return true;
|
|
}
|
|
// don't create splitters real close to the bounds
|
|
if ( bounds[1][type] - *planeDist > (MIN_NODE_SIZE*0.5f) &&
|
|
*planeDist - bounds[0][type] > (MIN_NODE_SIZE*0.5f) ) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
CM_R_InsideAllChildren
|
|
================
|
|
*/
|
|
static int CM_R_InsideAllChildren( cm_node_t *node, const idBounds &bounds ) {
|
|
assert(node != NULL);
|
|
if ( node->planeType != -1 ) {
|
|
if ( bounds[0][node->planeType] >= node->planeDist ) {
|
|
return false;
|
|
}
|
|
if ( bounds[1][node->planeType] <= node->planeDist ) {
|
|
return false;
|
|
}
|
|
if ( !CM_R_InsideAllChildren( node->children[0], bounds ) ) {
|
|
return false;
|
|
}
|
|
if ( !CM_R_InsideAllChildren( node->children[1], bounds ) ) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::R_FilterPolygonIntoTree
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::R_FilterPolygonIntoTree( cm_model_t *model, cm_node_t *node, cm_polygonRef_t *pref, cm_polygon_t *p ) {
|
|
assert(node != NULL);
|
|
while ( node->planeType != -1 ) {
|
|
if ( CM_R_InsideAllChildren( node, p->bounds ) ) {
|
|
break;
|
|
}
|
|
if ( p->bounds[0][node->planeType] >= node->planeDist ) {
|
|
node = node->children[0];
|
|
}
|
|
else if ( p->bounds[1][node->planeType] <= node->planeDist ) {
|
|
node = node->children[1];
|
|
}
|
|
else {
|
|
R_FilterPolygonIntoTree( model, node->children[1], NULL, p );
|
|
node = node->children[0];
|
|
}
|
|
}
|
|
if ( pref ) {
|
|
pref->next = node->polygons;
|
|
node->polygons = pref;
|
|
}
|
|
else {
|
|
AddPolygonToNode( model, node, p );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::R_FilterBrushIntoTree
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::R_FilterBrushIntoTree( cm_model_t *model, cm_node_t *node, cm_brushRef_t *pref, cm_brush_t *b ) {
|
|
assert(node != NULL);
|
|
while ( node->planeType != -1 ) {
|
|
if ( CM_R_InsideAllChildren( node, b->bounds ) ) {
|
|
break;
|
|
}
|
|
if ( b->bounds[0][node->planeType] >= node->planeDist ) {
|
|
node = node->children[0];
|
|
}
|
|
else if ( b->bounds[1][node->planeType] <= node->planeDist ) {
|
|
node = node->children[1];
|
|
}
|
|
else {
|
|
R_FilterBrushIntoTree( model, node->children[1], NULL, b );
|
|
node = node->children[0];
|
|
}
|
|
}
|
|
if ( pref ) {
|
|
pref->next = node->brushes;
|
|
node->brushes = pref;
|
|
}
|
|
else {
|
|
AddBrushToNode( model, node, b );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::R_CreateAxialBSPTree
|
|
|
|
a brush or polygon is linked in the node closest to the root where
|
|
the brush or polygon is inside all children
|
|
================
|
|
*/
|
|
cm_node_t *idCollisionModelManagerLocal::R_CreateAxialBSPTree( cm_model_t *model, cm_node_t *node, const idBounds &bounds ) {
|
|
int planeType = 0;
|
|
float planeDist = 0.0f;
|
|
cm_polygonRef_t *pref, *nextpref, *prevpref;
|
|
cm_brushRef_t *bref, *nextbref, *prevbref;
|
|
cm_node_t *frontNode, *backNode, *n;
|
|
idBounds frontBounds, backBounds;
|
|
|
|
if ( !CM_FindSplitter( node, bounds, &planeType, &planeDist ) ) {
|
|
node->planeType = -1;
|
|
return node;
|
|
}
|
|
// create two child nodes
|
|
frontNode = AllocNode( model, NODE_BLOCK_SIZE_LARGE );
|
|
memset( frontNode, 0, sizeof(cm_node_t) );
|
|
frontNode->parent = node;
|
|
frontNode->planeType = -1;
|
|
//
|
|
backNode = AllocNode( model, NODE_BLOCK_SIZE_LARGE );
|
|
memset( backNode, 0, sizeof(cm_node_t) );
|
|
backNode->parent = node;
|
|
backNode->planeType = -1;
|
|
//
|
|
model->numNodes += 2;
|
|
// set front node bounds
|
|
frontBounds = bounds;
|
|
frontBounds[0][planeType] = planeDist;
|
|
// set back node bounds
|
|
backBounds = bounds;
|
|
backBounds[1][planeType] = planeDist;
|
|
//
|
|
node->planeType = planeType;
|
|
node->planeDist = planeDist;
|
|
node->children[0] = frontNode;
|
|
node->children[1] = backNode;
|
|
// filter polygons and brushes down the tree if necesary
|
|
for ( n = node; n; n = n->parent ) {
|
|
prevpref = NULL;
|
|
for ( pref = n->polygons; pref; pref = nextpref) {
|
|
nextpref = pref->next;
|
|
// if polygon is not inside all children
|
|
if ( !CM_R_InsideAllChildren( n, pref->p->bounds ) ) {
|
|
// filter polygon down the tree
|
|
R_FilterPolygonIntoTree( model, n, pref, pref->p );
|
|
if ( prevpref ) {
|
|
prevpref->next = nextpref;
|
|
}
|
|
else {
|
|
n->polygons = nextpref;
|
|
}
|
|
}
|
|
else {
|
|
prevpref = pref;
|
|
}
|
|
}
|
|
prevbref = NULL;
|
|
for ( bref = n->brushes; bref; bref = nextbref) {
|
|
nextbref = bref->next;
|
|
// if brush is not inside all children
|
|
if ( !CM_R_InsideAllChildren( n, bref->b->bounds ) ) {
|
|
// filter brush down the tree
|
|
R_FilterBrushIntoTree( model, n, bref, bref->b );
|
|
if ( prevbref ) {
|
|
prevbref->next = nextbref;
|
|
}
|
|
else {
|
|
n->brushes = nextbref;
|
|
}
|
|
}
|
|
else {
|
|
prevbref = bref;
|
|
}
|
|
}
|
|
}
|
|
R_CreateAxialBSPTree( model, frontNode, frontBounds );
|
|
R_CreateAxialBSPTree( model, backNode, backBounds );
|
|
return node;
|
|
}
|
|
|
|
/*
|
|
int cm_numSavedPolygonLinks;
|
|
int cm_numSavedBrushLinks;
|
|
|
|
int CM_R_CountChildren( cm_node_t *node ) {
|
|
if ( node->planeType == -1 ) {
|
|
return 0;
|
|
}
|
|
return 2 + CM_R_CountChildren(node->children[0]) + CM_R_CountChildren(node->children[1]);
|
|
}
|
|
|
|
void CM_R_TestOptimisation( cm_node_t *node ) {
|
|
int polyCount, brushCount, numChildren;
|
|
cm_polygonRef_t *pref;
|
|
cm_brushRef_t *bref;
|
|
|
|
if ( node->planeType == -1 ) {
|
|
return;
|
|
}
|
|
polyCount = 0;
|
|
for ( pref = node->polygons; pref; pref = pref->next) {
|
|
polyCount++;
|
|
}
|
|
brushCount = 0;
|
|
for ( bref = node->brushes; bref; bref = bref->next) {
|
|
brushCount++;
|
|
}
|
|
if ( polyCount || brushCount ) {
|
|
numChildren = CM_R_CountChildren( node );
|
|
cm_numSavedPolygonLinks += (numChildren - 1) * polyCount;
|
|
cm_numSavedBrushLinks += (numChildren - 1) * brushCount;
|
|
}
|
|
CM_R_TestOptimisation( node->children[0] );
|
|
CM_R_TestOptimisation( node->children[1] );
|
|
}
|
|
*/
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::CreateAxialBSPTree
|
|
================
|
|
*/
|
|
cm_node_t *idCollisionModelManagerLocal::CreateAxialBSPTree( cm_model_t *model, cm_node_t *node ) {
|
|
cm_polygonRef_t *pref;
|
|
cm_brushRef_t *bref;
|
|
idBounds bounds;
|
|
|
|
// get head node bounds
|
|
bounds.Clear();
|
|
for ( pref = node->polygons; pref; pref = pref->next) {
|
|
bounds += pref->p->bounds;
|
|
}
|
|
for ( bref = node->brushes; bref; bref = bref->next) {
|
|
bounds += bref->b->bounds;
|
|
}
|
|
|
|
// create axial BSP tree from head node
|
|
node = R_CreateAxialBSPTree( model, node, bounds );
|
|
|
|
return node;
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
Raw polygon and brush data
|
|
|
|
===============================================================================
|
|
*/
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::SetupHash
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::SetupHash( void ) {
|
|
if ( !cm_vertexHash ) {
|
|
cm_vertexHash = new idHashIndex( VERTEX_HASH_SIZE, 1024 );
|
|
}
|
|
if ( !cm_edgeHash ) {
|
|
cm_edgeHash = new idHashIndex( EDGE_HASH_SIZE, 1024 );
|
|
}
|
|
// init variables used during loading and optimization
|
|
if ( !cm_windingList ) {
|
|
cm_windingList = new cm_windingList_t;
|
|
}
|
|
if ( !cm_outList ) {
|
|
cm_outList = new cm_windingList_t;
|
|
}
|
|
if ( !cm_tmpList ) {
|
|
cm_tmpList = new cm_windingList_t;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::ShutdownHash
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::ShutdownHash( void ) {
|
|
delete cm_vertexHash;
|
|
cm_vertexHash = NULL;
|
|
delete cm_edgeHash;
|
|
cm_edgeHash = NULL;
|
|
delete cm_tmpList;
|
|
cm_tmpList = NULL;
|
|
delete cm_outList;
|
|
cm_outList = NULL;
|
|
delete cm_windingList;
|
|
cm_windingList = NULL;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::ClearHash
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::ClearHash( idBounds &bounds ) {
|
|
int i;
|
|
float f, max;
|
|
|
|
cm_vertexHash->Clear();
|
|
cm_edgeHash->Clear();
|
|
|
|
cm_modelBounds = bounds;
|
|
max = bounds[1].x - bounds[0].x;
|
|
f = bounds[1].y - bounds[0].y;
|
|
if ( f > max ) {
|
|
max = f;
|
|
}
|
|
cm_vertexShift = (float) max / VERTEX_HASH_BOXSIZE;
|
|
for ( i = 0; (1<<i) < cm_vertexShift; i++ ) {
|
|
}
|
|
if ( i == 0 ) {
|
|
cm_vertexShift = 1;
|
|
}
|
|
else {
|
|
cm_vertexShift = i;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::HashVec
|
|
================
|
|
*/
|
|
ID_INLINE int idCollisionModelManagerLocal::HashVec(const idVec3 &vec) {
|
|
/*
|
|
int x, y;
|
|
|
|
x = (((int)(vec[0] - cm_modelBounds[0].x + 0.5 )) >> cm_vertexShift) & (VERTEX_HASH_BOXSIZE-1);
|
|
y = (((int)(vec[1] - cm_modelBounds[0].y + 0.5 )) >> cm_vertexShift) & (VERTEX_HASH_BOXSIZE-1);
|
|
|
|
assert (x >= 0 && x < VERTEX_HASH_BOXSIZE && y >= 0 && y < VERTEX_HASH_BOXSIZE);
|
|
|
|
return y * VERTEX_HASH_BOXSIZE + x;
|
|
*/
|
|
int x, y, z;
|
|
|
|
x = (((int) (vec[0] - cm_modelBounds[0].x + 0.5)) + 2) >> 2;
|
|
y = (((int) (vec[1] - cm_modelBounds[0].y + 0.5)) + 2) >> 2;
|
|
z = (((int) (vec[2] - cm_modelBounds[0].z + 0.5)) + 2) >> 2;
|
|
return (x + y * VERTEX_HASH_BOXSIZE + z) & (VERTEX_HASH_SIZE-1);
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::GetVertex
|
|
================
|
|
*/
|
|
int idCollisionModelManagerLocal::GetVertex( cm_model_t *model, const idVec3 &v, int *vertexNum ) {
|
|
int i, hashKey, vn;
|
|
idVec3 vert, *p;
|
|
|
|
for (i = 0; i < 3; i++) {
|
|
if ( idMath::Fabs(v[i] - idMath::Rint(v[i])) < INTEGRAL_EPSILON )
|
|
vert[i] = idMath::Rint(v[i]);
|
|
else
|
|
vert[i] = v[i];
|
|
}
|
|
|
|
hashKey = HashVec( vert );
|
|
|
|
for (vn = cm_vertexHash->First( hashKey ); vn >= 0; vn = cm_vertexHash->Next( vn ) ) {
|
|
p = &model->vertices[vn].p;
|
|
// first compare z-axis because hash is based on x-y plane
|
|
if (idMath::Fabs(vert[2] - (*p)[2]) < VERTEX_EPSILON &&
|
|
idMath::Fabs(vert[0] - (*p)[0]) < VERTEX_EPSILON &&
|
|
idMath::Fabs(vert[1] - (*p)[1]) < VERTEX_EPSILON )
|
|
{
|
|
*vertexNum = vn;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if ( model->numVertices >= model->maxVertices ) {
|
|
cm_vertex_t *oldVertices;
|
|
|
|
// resize vertex array
|
|
model->maxVertices = (float) model->maxVertices * 1.5f + 1;
|
|
oldVertices = model->vertices;
|
|
model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->maxVertices * sizeof(cm_vertex_t) );
|
|
memcpy( model->vertices, oldVertices, model->numVertices * sizeof(cm_vertex_t) );
|
|
Mem_Free( oldVertices );
|
|
|
|
cm_vertexHash->ResizeIndex( model->maxVertices );
|
|
}
|
|
model->vertices[model->numVertices].p = vert;
|
|
model->vertices[model->numVertices].checkcount = 0;
|
|
*vertexNum = model->numVertices;
|
|
// add vertice to hash
|
|
cm_vertexHash->Add( hashKey, model->numVertices );
|
|
//
|
|
model->numVertices++;
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::GetEdge
|
|
================
|
|
*/
|
|
int idCollisionModelManagerLocal::GetEdge( cm_model_t *model, const idVec3 &v1, const idVec3 &v2, int *edgeNum, int v1num ) {
|
|
int v2num, hashKey, e;
|
|
int found, *vertexNum;
|
|
|
|
// the first edge is a dummy
|
|
if ( model->numEdges == 0 ) {
|
|
model->numEdges = 1;
|
|
}
|
|
|
|
if ( v1num != -1 ) {
|
|
found = 1;
|
|
}
|
|
else {
|
|
found = GetVertex( model, v1, &v1num );
|
|
}
|
|
found &= GetVertex( model, v2, &v2num );
|
|
// if both vertices are the same or snapped onto each other
|
|
if ( v1num == v2num ) {
|
|
*edgeNum = 0;
|
|
return true;
|
|
}
|
|
hashKey = cm_edgeHash->GenerateKey( v1num, v2num );
|
|
// if both vertices where already stored
|
|
if (found) {
|
|
for (e = cm_edgeHash->First( hashKey ); e >= 0; e = cm_edgeHash->Next( e ) )
|
|
{
|
|
// NOTE: only allow at most two users that use the edge in opposite direction
|
|
if ( model->edges[e].numUsers != 1 ) {
|
|
continue;
|
|
}
|
|
|
|
vertexNum = model->edges[e].vertexNum;
|
|
if ( vertexNum[0] == v2num ) {
|
|
if ( vertexNum[1] == v1num ) {
|
|
// negative for a reversed edge
|
|
*edgeNum = -e;
|
|
break;
|
|
}
|
|
}
|
|
/*
|
|
else if ( vertexNum[0] == v1num ) {
|
|
if ( vertexNum[1] == v2num ) {
|
|
*edgeNum = e;
|
|
break;
|
|
}
|
|
}
|
|
*/
|
|
}
|
|
// if edge found in hash
|
|
if ( e >= 0 ) {
|
|
model->edges[e].numUsers++;
|
|
return true;
|
|
}
|
|
}
|
|
if ( model->numEdges >= model->maxEdges ) {
|
|
cm_edge_t *oldEdges;
|
|
|
|
// resize edge array
|
|
model->maxEdges = (float) model->maxEdges * 1.5f + 1;
|
|
oldEdges = model->edges;
|
|
model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->maxEdges * sizeof(cm_edge_t) );
|
|
memcpy( model->edges, oldEdges, model->numEdges * sizeof(cm_edge_t) );
|
|
Mem_Free( oldEdges );
|
|
|
|
cm_edgeHash->ResizeIndex( model->maxEdges );
|
|
}
|
|
// setup edge
|
|
model->edges[model->numEdges].vertexNum[0] = v1num;
|
|
model->edges[model->numEdges].vertexNum[1] = v2num;
|
|
model->edges[model->numEdges].internal = false;
|
|
model->edges[model->numEdges].checkcount = 0;
|
|
model->edges[model->numEdges].numUsers = 1; // used by one polygon atm
|
|
model->edges[model->numEdges].normal.Zero();
|
|
//
|
|
*edgeNum = model->numEdges;
|
|
// add edge to hash
|
|
cm_edgeHash->Add( hashKey, model->numEdges );
|
|
|
|
model->numEdges++;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::CreatePolygon
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::CreatePolygon( cm_model_t *model, idFixedWinding *w, const idPlane &plane, const idMaterial *material, int primitiveNum ) {
|
|
int i, j, edgeNum, v1num;
|
|
int numPolyEdges, polyEdges[MAX_POINTS_ON_WINDING];
|
|
idBounds bounds;
|
|
cm_polygon_t *p;
|
|
|
|
// turn the winding into a sequence of edges
|
|
numPolyEdges = 0;
|
|
v1num = -1; // first vertex unknown
|
|
for ( i = 0, j = 1; i < w->GetNumPoints(); i++, j++ ) {
|
|
if ( j >= w->GetNumPoints() ) {
|
|
j = 0;
|
|
}
|
|
GetEdge( model, (*w)[i].ToVec3(), (*w)[j].ToVec3(), &polyEdges[numPolyEdges], v1num );
|
|
if ( polyEdges[numPolyEdges] ) {
|
|
// last vertex of this edge is the first vertex of the next edge
|
|
v1num = model->edges[ abs(polyEdges[numPolyEdges]) ].vertexNum[ INTSIGNBITNOTSET(polyEdges[numPolyEdges]) ];
|
|
// this edge is valid so keep it
|
|
numPolyEdges++;
|
|
}
|
|
}
|
|
// should have at least 3 edges
|
|
if ( numPolyEdges < 3 ) {
|
|
return;
|
|
}
|
|
// the polygon is invalid if some edge is found twice
|
|
for ( i = 0; i < numPolyEdges; i++ ) {
|
|
for ( j = i+1; j < numPolyEdges; j++ ) {
|
|
if ( abs(polyEdges[i]) == abs(polyEdges[j]) ) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
// don't overflow max edges
|
|
if ( numPolyEdges > CM_MAX_POLYGON_EDGES ) {
|
|
common->Warning( "idCollisionModelManagerLocal::CreatePolygon: polygon has more than %d edges", numPolyEdges );
|
|
numPolyEdges = CM_MAX_POLYGON_EDGES;
|
|
}
|
|
|
|
w->GetBounds( bounds );
|
|
|
|
p = AllocPolygon( model, numPolyEdges );
|
|
p->numEdges = numPolyEdges;
|
|
p->contents = material->GetContentFlags();
|
|
p->material = material;
|
|
p->checkcount = 0;
|
|
p->plane = plane;
|
|
p->bounds = bounds;
|
|
for ( i = 0; i < numPolyEdges; i++ ) {
|
|
edgeNum = polyEdges[i];
|
|
p->edges[i] = edgeNum;
|
|
}
|
|
R_FilterPolygonIntoTree( model, model->node, NULL, p );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::PolygonFromWinding
|
|
|
|
NOTE: for patches primitiveNum < 0 and abs(primitiveNum) is the real number
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::PolygonFromWinding( cm_model_t *model, idFixedWinding *w, const idPlane &plane, const idMaterial *material, int primitiveNum ) {
|
|
int contents;
|
|
|
|
contents = material->GetContentFlags();
|
|
|
|
// if this polygon is part of the world model
|
|
if ( numModels == 0 ) {
|
|
// if the polygon is fully chopped away by the proc bsp tree
|
|
if ( ChoppedAwayByProcBSP( *w, plane, contents ) ) {
|
|
model->numRemovedPolys++;
|
|
return;
|
|
}
|
|
}
|
|
|
|
// get one winding that is not or only partly contained in brushes
|
|
w = WindingOutsideBrushes( w, plane, contents, primitiveNum, model->node );
|
|
|
|
// if the polygon is fully contained within a brush
|
|
if ( !w ) {
|
|
model->numRemovedPolys++;
|
|
return;
|
|
}
|
|
|
|
if ( w->IsHuge() ) {
|
|
common->Warning( "idCollisionModelManagerLocal::PolygonFromWinding: model %s primitive %d is degenerate", model->name.c_str(), abs(primitiveNum) );
|
|
return;
|
|
}
|
|
|
|
CreatePolygon( model, w, plane, material, primitiveNum );
|
|
|
|
if ( material->GetCullType() == CT_TWO_SIDED || material->ShouldCreateBackSides() ) {
|
|
w->ReverseSelf();
|
|
CreatePolygon( model, w, -plane, material, primitiveNum );
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idCollisionModelManagerLocal::CreatePatchPolygons
|
|
=================
|
|
*/
|
|
void idCollisionModelManagerLocal::CreatePatchPolygons( cm_model_t *model, idSurface_Patch &mesh, const idMaterial *material, int primitiveNum ) {
|
|
int i, j;
|
|
float dot;
|
|
int v1, v2, v3, v4;
|
|
idFixedWinding w;
|
|
idPlane plane;
|
|
idVec3 d1, d2;
|
|
|
|
for ( i = 0; i < mesh.GetWidth() - 1; i++ ) {
|
|
for ( j = 0; j < mesh.GetHeight() - 1; j++ ) {
|
|
|
|
v1 = j * mesh.GetWidth() + i;
|
|
v2 = v1 + 1;
|
|
v3 = v1 + mesh.GetWidth() + 1;
|
|
v4 = v1 + mesh.GetWidth();
|
|
|
|
d1 = mesh[v2].xyz - mesh[v1].xyz;
|
|
d2 = mesh[v3].xyz - mesh[v1].xyz;
|
|
plane.SetNormal( d1.Cross(d2) );
|
|
if ( plane.Normalize() != 0.0f ) {
|
|
plane.FitThroughPoint( mesh[v1].xyz );
|
|
dot = plane.Distance( mesh[v4].xyz );
|
|
// if we can turn it into a quad
|
|
if ( idMath::Fabs(dot) < 0.1f ) {
|
|
w.Clear();
|
|
w += mesh[v1].xyz;
|
|
w += mesh[v2].xyz;
|
|
w += mesh[v3].xyz;
|
|
w += mesh[v4].xyz;
|
|
|
|
PolygonFromWinding( model, &w, plane, material, -primitiveNum );
|
|
continue;
|
|
}
|
|
else {
|
|
// create one of the triangles
|
|
w.Clear();
|
|
w += mesh[v1].xyz;
|
|
w += mesh[v2].xyz;
|
|
w += mesh[v3].xyz;
|
|
|
|
PolygonFromWinding( model, &w, plane, material, -primitiveNum );
|
|
}
|
|
}
|
|
// create the other triangle
|
|
d1 = mesh[v3].xyz - mesh[v1].xyz;
|
|
d2 = mesh[v4].xyz - mesh[v1].xyz;
|
|
plane.SetNormal( d1.Cross(d2) );
|
|
if ( plane.Normalize() != 0.0f ) {
|
|
plane.FitThroughPoint( mesh[v1].xyz );
|
|
|
|
w.Clear();
|
|
w += mesh[v1].xyz;
|
|
w += mesh[v3].xyz;
|
|
w += mesh[v4].xyz;
|
|
|
|
PolygonFromWinding( model, &w, plane, material, -primitiveNum );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
CM_EstimateVertsAndEdges
|
|
=================
|
|
*/
|
|
static void CM_EstimateVertsAndEdges( const idMapEntity *mapEnt, int *numVerts, int *numEdges ) {
|
|
int j, width, height;
|
|
|
|
*numVerts = *numEdges = 0;
|
|
for ( j = 0; j < mapEnt->GetNumPrimitives(); j++ ) {
|
|
const idMapPrimitive *mapPrim;
|
|
mapPrim = mapEnt->GetPrimitive(j);
|
|
if ( mapPrim->GetType() == idMapPrimitive::TYPE_PATCH ) {
|
|
// assume maximum tesselation without adding verts
|
|
width = static_cast<const idMapPatch*>(mapPrim)->GetWidth();
|
|
height = static_cast<const idMapPatch*>(mapPrim)->GetHeight();
|
|
*numVerts += width * height;
|
|
*numEdges += (width-1) * height + width * (height-1) + (width-1) * (height-1);
|
|
continue;
|
|
}
|
|
if ( mapPrim->GetType() == idMapPrimitive::TYPE_BRUSH ) {
|
|
// assume cylinder with a polygon with (numSides - 2) edges ontop and on the bottom
|
|
*numVerts += (static_cast<const idMapBrush*>(mapPrim)->GetNumSides() - 2) * 2;
|
|
*numEdges += (static_cast<const idMapBrush*>(mapPrim)->GetNumSides() - 2) * 3;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
=================
|
|
idCollisionModelManagerLocal::ConverPatch
|
|
=================
|
|
*/
|
|
void idCollisionModelManagerLocal::ConvertPatch( cm_model_t *model, const idMapPatch *patch, int primitiveNum ) {
|
|
const idMaterial *material;
|
|
idSurface_Patch *cp;
|
|
|
|
material = declManager->FindMaterial( patch->GetMaterial() );
|
|
if ( !( material->GetContentFlags() & CONTENTS_REMOVE_UTIL ) ) {
|
|
return;
|
|
}
|
|
|
|
// copy the patch
|
|
cp = new idSurface_Patch( *patch );
|
|
|
|
// if the patch has an explicit number of subdivisions use it to avoid cracks
|
|
if ( patch->GetExplicitlySubdivided() ) {
|
|
cp->SubdivideExplicit( patch->GetHorzSubdivisions(), patch->GetVertSubdivisions(), false, true );
|
|
} else {
|
|
cp->Subdivide( DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_ERROR_CD, DEFAULT_CURVE_MAX_LENGTH_CD, false );
|
|
}
|
|
|
|
// create collision polygons for the patch
|
|
CreatePatchPolygons( model, *cp, material, primitiveNum );
|
|
|
|
delete cp;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::ConvertBrushSides
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::ConvertBrushSides( cm_model_t *model, const idMapBrush *mapBrush, int primitiveNum ) {
|
|
int i, j;
|
|
idMapBrushSide *mapSide;
|
|
idFixedWinding w;
|
|
idPlane *planes;
|
|
const idMaterial *material;
|
|
|
|
// fix degenerate planes
|
|
planes = (idPlane *) _alloca16( mapBrush->GetNumSides() * sizeof( planes[0] ) );
|
|
for ( i = 0; i < mapBrush->GetNumSides(); i++ ) {
|
|
planes[i] = mapBrush->GetSide(i)->GetPlane();
|
|
planes[i].FixDegeneracies( DEGENERATE_DIST_EPSILON );
|
|
}
|
|
|
|
// create a collision polygon for each brush side
|
|
for ( i = 0; i < mapBrush->GetNumSides(); i++ ) {
|
|
mapSide = mapBrush->GetSide(i);
|
|
material = declManager->FindMaterial( mapSide->GetMaterial() );
|
|
if ( !( material->GetContentFlags() & CONTENTS_REMOVE_UTIL ) ) {
|
|
continue;
|
|
}
|
|
w.BaseForPlane( -planes[i] );
|
|
for ( j = 0; j < mapBrush->GetNumSides() && w.GetNumPoints(); j++ ) {
|
|
if ( i == j ) {
|
|
continue;
|
|
}
|
|
w.ClipInPlace( -planes[j], 0 );
|
|
}
|
|
|
|
if ( w.GetNumPoints() ) {
|
|
PolygonFromWinding( model, &w, planes[i], material, primitiveNum );
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::ConvertBrush
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::ConvertBrush( cm_model_t *model, const idMapBrush *mapBrush, int primitiveNum ) {
|
|
int i, j, contents;
|
|
idBounds bounds;
|
|
idMapBrushSide *mapSide;
|
|
cm_brush_t *brush;
|
|
idPlane *planes;
|
|
idFixedWinding w;
|
|
const idMaterial *material = NULL;
|
|
|
|
contents = 0;
|
|
bounds.Clear();
|
|
|
|
// fix degenerate planes
|
|
planes = (idPlane *) _alloca16( mapBrush->GetNumSides() * sizeof( planes[0] ) );
|
|
for ( i = 0; i < mapBrush->GetNumSides(); i++ ) {
|
|
planes[i] = mapBrush->GetSide(i)->GetPlane();
|
|
planes[i].FixDegeneracies( DEGENERATE_DIST_EPSILON );
|
|
}
|
|
|
|
// we are only getting the bounds for the brush so there's no need
|
|
// to create a winding for the last brush side
|
|
for ( i = 0; i < mapBrush->GetNumSides() - 1; i++ ) {
|
|
mapSide = mapBrush->GetSide(i);
|
|
material = declManager->FindMaterial( mapSide->GetMaterial() );
|
|
contents |= ( material->GetContentFlags() & CONTENTS_REMOVE_UTIL );
|
|
w.BaseForPlane( -planes[i] );
|
|
for ( j = 0; j < mapBrush->GetNumSides() && w.GetNumPoints(); j++ ) {
|
|
if ( i == j ) {
|
|
continue;
|
|
}
|
|
w.ClipInPlace( -planes[j], 0 );
|
|
}
|
|
|
|
for ( j = 0; j < w.GetNumPoints(); j++ ) {
|
|
bounds.AddPoint( w[j].ToVec3() );
|
|
}
|
|
}
|
|
if ( !contents ) {
|
|
return;
|
|
}
|
|
// create brush for position test
|
|
brush = AllocBrush( model, mapBrush->GetNumSides() );
|
|
brush->checkcount = 0;
|
|
brush->contents = contents;
|
|
brush->material = material;
|
|
brush->primitiveNum = primitiveNum;
|
|
brush->bounds = bounds;
|
|
brush->numPlanes = mapBrush->GetNumSides();
|
|
for (i = 0; i < mapBrush->GetNumSides(); i++) {
|
|
brush->planes[i] = planes[i];
|
|
}
|
|
AddBrushToNode( model, model->node, brush );
|
|
}
|
|
|
|
/*
|
|
================
|
|
CM_CountNodeBrushes
|
|
================
|
|
*/
|
|
static int CM_CountNodeBrushes( const cm_node_t *node ) {
|
|
int count;
|
|
cm_brushRef_t *bref;
|
|
|
|
count = 0;
|
|
for ( bref = node->brushes; bref; bref = bref->next ) {
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/*
|
|
================
|
|
CM_R_GetModelBounds
|
|
================
|
|
*/
|
|
static void CM_R_GetNodeBounds( idBounds *bounds, cm_node_t *node ) {
|
|
cm_polygonRef_t *pref;
|
|
cm_brushRef_t *bref;
|
|
|
|
while ( 1 ) {
|
|
for ( pref = node->polygons; pref; pref = pref->next ) {
|
|
bounds->AddPoint( pref->p->bounds[0] );
|
|
bounds->AddPoint( pref->p->bounds[1] );
|
|
}
|
|
for ( bref = node->brushes; bref; bref = bref->next ) {
|
|
bounds->AddPoint( bref->b->bounds[0] );
|
|
bounds->AddPoint( bref->b->bounds[1] );
|
|
}
|
|
if ( node->planeType == -1 ) {
|
|
break;
|
|
}
|
|
CM_R_GetNodeBounds( bounds, node->children[1] );
|
|
node = node->children[0];
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
CM_GetNodeBounds
|
|
================
|
|
*/
|
|
void CM_GetNodeBounds( idBounds *bounds, cm_node_t *node ) {
|
|
bounds->Clear();
|
|
CM_R_GetNodeBounds( bounds, node );
|
|
if ( bounds->IsCleared() ) {
|
|
bounds->Zero();
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
CM_GetNodeContents
|
|
================
|
|
*/
|
|
int CM_GetNodeContents( cm_node_t *node ) {
|
|
int contents;
|
|
cm_polygonRef_t *pref;
|
|
cm_brushRef_t *bref;
|
|
|
|
contents = 0;
|
|
while ( 1 ) {
|
|
for ( pref = node->polygons; pref; pref = pref->next ) {
|
|
contents |= pref->p->contents;
|
|
}
|
|
for ( bref = node->brushes; bref; bref = bref->next ) {
|
|
contents |= bref->b->contents;
|
|
}
|
|
if ( node->planeType == -1 ) {
|
|
break;
|
|
}
|
|
contents |= CM_GetNodeContents( node->children[1] );
|
|
node = node->children[0];
|
|
}
|
|
return contents;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idCollisionModelManagerLocal::RemapEdges
|
|
==================
|
|
*/
|
|
void idCollisionModelManagerLocal::RemapEdges( cm_node_t *node, int *edgeRemap ) {
|
|
cm_polygonRef_t *pref;
|
|
cm_polygon_t *p;
|
|
int i;
|
|
|
|
while ( 1 ) {
|
|
for ( pref = node->polygons; pref; pref = pref->next ) {
|
|
p = pref->p;
|
|
// if we checked this polygon already
|
|
if ( p->checkcount == checkCount ) {
|
|
continue;
|
|
}
|
|
p->checkcount = checkCount;
|
|
for ( i = 0; i < p->numEdges; i++ ) {
|
|
if ( p->edges[i] < 0 ) {
|
|
p->edges[i] = -edgeRemap[ abs(p->edges[i]) ];
|
|
}
|
|
else {
|
|
p->edges[i] = edgeRemap[ p->edges[i] ];
|
|
}
|
|
}
|
|
}
|
|
if ( node->planeType == -1 ) {
|
|
break;
|
|
}
|
|
|
|
RemapEdges( node->children[1], edgeRemap );
|
|
node = node->children[0];
|
|
}
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idCollisionModelManagerLocal::OptimizeArrays
|
|
|
|
due to polygon merging and polygon removal the vertex and edge array
|
|
can have a lot of unused entries.
|
|
==================
|
|
*/
|
|
void idCollisionModelManagerLocal::OptimizeArrays( cm_model_t *model ) {
|
|
int i, newNumVertices, newNumEdges, *v;
|
|
int *remap;
|
|
cm_edge_t *oldEdges;
|
|
cm_vertex_t *oldVertices;
|
|
|
|
remap = (int *) Mem_ClearedAlloc( Max( model->numVertices, model->numEdges ) * sizeof( int ) );
|
|
// get all used vertices
|
|
for ( i = 0; i < model->numEdges; i++ ) {
|
|
remap[ model->edges[i].vertexNum[0] ] = true;
|
|
remap[ model->edges[i].vertexNum[1] ] = true;
|
|
}
|
|
// create remap index and move vertices
|
|
newNumVertices = 0;
|
|
for ( i = 0; i < model->numVertices; i++ ) {
|
|
if ( remap[ i ] ) {
|
|
remap[ i ] = newNumVertices;
|
|
model->vertices[ newNumVertices ] = model->vertices[ i ];
|
|
newNumVertices++;
|
|
}
|
|
}
|
|
model->numVertices = newNumVertices;
|
|
// change edge vertex indexes
|
|
for ( i = 1; i < model->numEdges; i++ ) {
|
|
v = model->edges[i].vertexNum;
|
|
v[0] = remap[ v[0] ];
|
|
v[1] = remap[ v[1] ];
|
|
}
|
|
|
|
// create remap index and move edges
|
|
newNumEdges = 1;
|
|
for ( i = 1; i < model->numEdges; i++ ) {
|
|
// if the edge is used
|
|
if ( model->edges[ i ].numUsers ) {
|
|
remap[ i ] = newNumEdges;
|
|
model->edges[ newNumEdges ] = model->edges[ i ];
|
|
newNumEdges++;
|
|
}
|
|
}
|
|
// change polygon edge indexes
|
|
checkCount++;
|
|
RemapEdges( model->node, remap );
|
|
model->numEdges = newNumEdges;
|
|
|
|
Mem_Free( remap );
|
|
|
|
// realloc vertices
|
|
oldVertices = model->vertices;
|
|
if ( oldVertices ) {
|
|
model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->numVertices * sizeof(cm_vertex_t) );
|
|
memcpy( model->vertices, oldVertices, model->numVertices * sizeof(cm_vertex_t) );
|
|
Mem_Free( oldVertices );
|
|
}
|
|
|
|
// realloc edges
|
|
oldEdges = model->edges;
|
|
if ( oldEdges ) {
|
|
model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->numEdges * sizeof(cm_edge_t) );
|
|
memcpy( model->edges, oldEdges, model->numEdges * sizeof(cm_edge_t) );
|
|
Mem_Free( oldEdges );
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::FinishModel
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::FinishModel( cm_model_t *model ) {
|
|
// try to merge polygons
|
|
checkCount++;
|
|
MergeTreePolygons( model, model->node );
|
|
// find internal edges (no mesh can ever collide with internal edges)
|
|
checkCount++;
|
|
FindInternalEdges( model, model->node );
|
|
// calculate edge normals
|
|
checkCount++;
|
|
CalculateEdgeNormals( model, model->node );
|
|
|
|
//common->Printf( "%s vertex hash spread is %d\n", model->name.c_str(), cm_vertexHash->GetSpread() );
|
|
//common->Printf( "%s edge hash spread is %d\n", model->name.c_str(), cm_edgeHash->GetSpread() );
|
|
|
|
// remove all unused vertices and edges
|
|
OptimizeArrays( model );
|
|
// get model bounds from brush and polygon bounds
|
|
CM_GetNodeBounds( &model->bounds, model->node );
|
|
// get model contents
|
|
model->contents = CM_GetNodeContents( model->node );
|
|
// total memory used by this model
|
|
model->usedMemory = model->numVertices * sizeof(cm_vertex_t) +
|
|
model->numEdges * sizeof(cm_edge_t) +
|
|
model->polygonMemory +
|
|
model->brushMemory +
|
|
model->numNodes * sizeof(cm_node_t) +
|
|
model->numPolygonRefs * sizeof(cm_polygonRef_t) +
|
|
model->numBrushRefs * sizeof(cm_brushRef_t);
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::LoadRenderModel
|
|
================
|
|
*/
|
|
cm_model_t *idCollisionModelManagerLocal::LoadRenderModel( const char *fileName ) {
|
|
int i, j;
|
|
idRenderModel *renderModel;
|
|
const modelSurface_t *surf;
|
|
idFixedWinding w;
|
|
cm_node_t *node;
|
|
cm_model_t *model;
|
|
idPlane plane;
|
|
idBounds bounds;
|
|
bool collisionSurface;
|
|
idStr extension;
|
|
|
|
// only load ASE and LWO models
|
|
idStr( fileName ).ExtractFileExtension( extension );
|
|
if ( ( extension.Icmp( "ase" ) != 0 ) && ( extension.Icmp( "lwo" ) != 0 ) && ( extension.Icmp( "ma" ) != 0 ) ) {
|
|
return NULL;
|
|
}
|
|
|
|
if ( !renderModelManager->CheckModel( fileName ) ) {
|
|
return NULL;
|
|
}
|
|
|
|
renderModel = renderModelManager->FindModel( fileName );
|
|
|
|
model = AllocModel();
|
|
model->name = fileName;
|
|
node = AllocNode( model, NODE_BLOCK_SIZE_SMALL );
|
|
node->planeType = -1;
|
|
model->node = node;
|
|
|
|
model->maxVertices = 0;
|
|
model->numVertices = 0;
|
|
model->maxEdges = 0;
|
|
model->numEdges = 0;
|
|
|
|
bounds = renderModel->Bounds( NULL );
|
|
|
|
collisionSurface = false;
|
|
for ( i = 0; i < renderModel->NumSurfaces(); i++ ) {
|
|
surf = renderModel->Surface( i );
|
|
if ( surf->shader->GetSurfaceFlags() & SURF_COLLISION ) {
|
|
collisionSurface = true;
|
|
}
|
|
}
|
|
|
|
for ( i = 0; i < renderModel->NumSurfaces(); i++ ) {
|
|
surf = renderModel->Surface( i );
|
|
// if this surface has no contents
|
|
if ( ! ( surf->shader->GetContentFlags() & CONTENTS_REMOVE_UTIL ) ) {
|
|
continue;
|
|
}
|
|
// if the model has a collision surface and this surface is not a collision surface
|
|
if ( collisionSurface && !( surf->shader->GetSurfaceFlags() & SURF_COLLISION ) ) {
|
|
continue;
|
|
}
|
|
// get max verts and edges
|
|
model->maxVertices += surf->geometry->numVerts;
|
|
model->maxEdges += surf->geometry->numIndexes;
|
|
}
|
|
|
|
model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->maxVertices * sizeof(cm_vertex_t) );
|
|
model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->maxEdges * sizeof(cm_edge_t) );
|
|
|
|
// setup hash to speed up finding shared vertices and edges
|
|
SetupHash();
|
|
|
|
cm_vertexHash->ResizeIndex( model->maxVertices );
|
|
cm_edgeHash->ResizeIndex( model->maxEdges );
|
|
|
|
ClearHash( bounds );
|
|
|
|
for ( i = 0; i < renderModel->NumSurfaces(); i++ ) {
|
|
surf = renderModel->Surface( i );
|
|
// if this surface has no contents
|
|
if ( ! ( surf->shader->GetContentFlags() & CONTENTS_REMOVE_UTIL ) ) {
|
|
continue;
|
|
}
|
|
// if the model has a collision surface and this surface is not a collision surface
|
|
if ( collisionSurface && !( surf->shader->GetSurfaceFlags() & SURF_COLLISION ) ) {
|
|
continue;
|
|
}
|
|
|
|
for ( j = 0; j < surf->geometry->numIndexes; j += 3 ) {
|
|
w.Clear();
|
|
w += surf->geometry->verts[ surf->geometry->indexes[ j + 2 ] ].xyz;
|
|
w += surf->geometry->verts[ surf->geometry->indexes[ j + 1 ] ].xyz;
|
|
w += surf->geometry->verts[ surf->geometry->indexes[ j + 0 ] ].xyz;
|
|
w.GetPlane( plane );
|
|
plane = -plane;
|
|
PolygonFromWinding( model, &w, plane, surf->shader, 1 );
|
|
}
|
|
}
|
|
|
|
// create a BSP tree for the model
|
|
model->node = CreateAxialBSPTree( model, model->node );
|
|
|
|
model->isConvex = false;
|
|
|
|
FinishModel( model );
|
|
|
|
// shutdown the hash
|
|
ShutdownHash();
|
|
|
|
common->Printf( "loaded collision model %s\n", model->name.c_str() );
|
|
|
|
return model;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::CollisionModelForMapEntity
|
|
================
|
|
*/
|
|
cm_model_t *idCollisionModelManagerLocal::CollisionModelForMapEntity( const idMapEntity *mapEnt ) {
|
|
cm_model_t *model;
|
|
idBounds bounds;
|
|
const char *name;
|
|
int i, brushCount;
|
|
|
|
// if the entity has no primitives
|
|
if ( mapEnt->GetNumPrimitives() < 1 ) {
|
|
return NULL;
|
|
}
|
|
|
|
// get a name for the collision model
|
|
mapEnt->epairs.GetString( "model", "", &name );
|
|
if ( !name[0] ) {
|
|
mapEnt->epairs.GetString( "name", "", &name );
|
|
if ( !name[0] ) {
|
|
if ( !numModels ) {
|
|
// first model is always the world
|
|
name = "worldMap";
|
|
}
|
|
else {
|
|
name = "unnamed inline model";
|
|
}
|
|
}
|
|
}
|
|
|
|
model = AllocModel();
|
|
model->node = AllocNode( model, NODE_BLOCK_SIZE_SMALL );
|
|
|
|
CM_EstimateVertsAndEdges( mapEnt, &model->maxVertices, &model->maxEdges );
|
|
model->numVertices = 0;
|
|
model->numEdges = 0;
|
|
model->vertices = (cm_vertex_t *) Mem_ClearedAlloc( model->maxVertices * sizeof(cm_vertex_t) );
|
|
model->edges = (cm_edge_t *) Mem_ClearedAlloc( model->maxEdges * sizeof(cm_edge_t) );
|
|
|
|
cm_vertexHash->ResizeIndex( model->maxVertices );
|
|
cm_edgeHash->ResizeIndex( model->maxEdges );
|
|
|
|
model->name = name;
|
|
model->isConvex = false;
|
|
|
|
// convert brushes
|
|
for ( i = 0; i < mapEnt->GetNumPrimitives(); i++ ) {
|
|
idMapPrimitive *mapPrim;
|
|
|
|
mapPrim = mapEnt->GetPrimitive(i);
|
|
if ( mapPrim->GetType() == idMapPrimitive::TYPE_BRUSH ) {
|
|
ConvertBrush( model, static_cast<idMapBrush*>(mapPrim), i );
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// create an axial bsp tree for the model if it has more than just a bunch brushes
|
|
brushCount = CM_CountNodeBrushes( model->node );
|
|
if ( brushCount > 4 ) {
|
|
model->node = CreateAxialBSPTree( model, model->node );
|
|
} else {
|
|
model->node->planeType = -1;
|
|
}
|
|
|
|
// get bounds for hash
|
|
if ( brushCount ) {
|
|
CM_GetNodeBounds( &bounds, model->node );
|
|
} else {
|
|
bounds[0].Set( -256, -256, -256 );
|
|
bounds[1].Set( 256, 256, 256 );
|
|
}
|
|
|
|
// different models do not share edges and vertices with each other, so clear the hash
|
|
ClearHash( bounds );
|
|
|
|
// create polygons from patches and brushes
|
|
for ( i = 0; i < mapEnt->GetNumPrimitives(); i++ ) {
|
|
idMapPrimitive *mapPrim;
|
|
|
|
mapPrim = mapEnt->GetPrimitive(i);
|
|
if ( mapPrim->GetType() == idMapPrimitive::TYPE_PATCH ) {
|
|
ConvertPatch( model, static_cast<idMapPatch*>(mapPrim), i );
|
|
continue;
|
|
}
|
|
if ( mapPrim->GetType() == idMapPrimitive::TYPE_BRUSH ) {
|
|
ConvertBrushSides( model, static_cast<idMapBrush*>(mapPrim), i );
|
|
continue;
|
|
}
|
|
}
|
|
|
|
FinishModel( model );
|
|
|
|
return model;
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::FindModel
|
|
================
|
|
*/
|
|
cmHandle_t idCollisionModelManagerLocal::FindModel( const char *name ) {
|
|
int i;
|
|
|
|
// check if this model is already loaded
|
|
for ( i = 0; i < numModels; i++ ) {
|
|
if ( !models[i]->name.Icmp( name ) ) {
|
|
break;
|
|
}
|
|
}
|
|
// if the model is already loaded
|
|
if ( i < numModels ) {
|
|
return i;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idCollisionModelManagerLocal::PrintModelInfo
|
|
==================
|
|
*/
|
|
void idCollisionModelManagerLocal::PrintModelInfo( const cm_model_t *model ) {
|
|
common->Printf( "%6i vertices (%zu KB)\n", model->numVertices, (model->numVertices * sizeof(cm_vertex_t))>>10 );
|
|
common->Printf( "%6i edges (%zu KB)\n", model->numEdges, (model->numEdges * sizeof(cm_edge_t))>>10 );
|
|
common->Printf( "%6i polygons (%i KB)\n", model->numPolygons, model->polygonMemory>>10 );
|
|
common->Printf( "%6i brushes (%i KB)\n", model->numBrushes, model->brushMemory>>10 );
|
|
common->Printf( "%6i nodes (%zu KB)\n", model->numNodes, (model->numNodes * sizeof(cm_node_t))>>10 );
|
|
common->Printf( "%6i polygon refs (%zu KB)\n", model->numPolygonRefs, (model->numPolygonRefs * sizeof(cm_polygonRef_t))>>10 );
|
|
common->Printf( "%6i brush refs (%zu KB)\n", model->numBrushRefs, (model->numBrushRefs * sizeof(cm_brushRef_t))>>10 );
|
|
common->Printf( "%6i internal edges\n", model->numInternalEdges );
|
|
common->Printf( "%6i sharp edges\n", model->numSharpEdges );
|
|
common->Printf( "%6i contained polygons removed\n", model->numRemovedPolys );
|
|
common->Printf( "%6i polygons merged\n", model->numMergedPolys );
|
|
common->Printf( "%6i KB total memory used\n", model->usedMemory>>10 );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::AccumulateModelInfo
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::AccumulateModelInfo( cm_model_t *model ) {
|
|
int i;
|
|
|
|
memset( model, 0, sizeof( *model ) );
|
|
// accumulate statistics of all loaded models
|
|
for ( i = 0; i < numModels; i++ ) {
|
|
model->numVertices += models[i]->numVertices;
|
|
model->numEdges += models[i]->numEdges;
|
|
model->numPolygons += models[i]->numPolygons;
|
|
model->polygonMemory += models[i]->polygonMemory;
|
|
model->numBrushes += models[i]->numBrushes;
|
|
model->brushMemory += models[i]->brushMemory;
|
|
model->numNodes += models[i]->numNodes;
|
|
model->numBrushRefs += models[i]->numBrushRefs;
|
|
model->numPolygonRefs += models[i]->numPolygonRefs;
|
|
model->numInternalEdges += models[i]->numInternalEdges;
|
|
model->numSharpEdges += models[i]->numSharpEdges;
|
|
model->numRemovedPolys += models[i]->numRemovedPolys;
|
|
model->numMergedPolys += models[i]->numMergedPolys;
|
|
model->usedMemory += models[i]->usedMemory;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::ModelInfo
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::ModelInfo( cmHandle_t model ) {
|
|
cm_model_t modelInfo;
|
|
|
|
if ( model == -1 ) {
|
|
AccumulateModelInfo( &modelInfo );
|
|
PrintModelInfo( &modelInfo );
|
|
return;
|
|
}
|
|
if ( model < 0 || model > MAX_SUBMODELS || model > maxModels ) {
|
|
common->Printf( "idCollisionModelManagerLocal::ModelInfo: invalid model handle\n" );
|
|
return;
|
|
}
|
|
if ( !models[model] ) {
|
|
common->Printf( "idCollisionModelManagerLocal::ModelInfo: invalid model\n" );
|
|
return;
|
|
}
|
|
|
|
PrintModelInfo( models[model] );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::ListModels
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::ListModels( void ) {
|
|
int i, totalMemory;
|
|
|
|
totalMemory = 0;
|
|
for ( i = 0; i < numModels; i++ ) {
|
|
common->Printf( "%4d: %5d KB %s\n", i, (models[i]->usedMemory>>10), models[i]->name.c_str() );
|
|
totalMemory += models[i]->usedMemory;
|
|
}
|
|
common->Printf( "%4d KB in %d models\n", (totalMemory>>10), numModels );
|
|
}
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::BuildModels
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::BuildModels( const idMapFile *mapFile ) {
|
|
int i;
|
|
const idMapEntity *mapEnt;
|
|
|
|
idTimer timer;
|
|
timer.Start();
|
|
|
|
if ( !LoadCollisionModelFile( mapFile->GetName(), mapFile->GetGeometryCRC() ) ) {
|
|
|
|
if ( !mapFile->GetNumEntities() ) {
|
|
return;
|
|
}
|
|
|
|
// load the .proc file bsp for data optimisation
|
|
LoadProcBSP( mapFile->GetName() );
|
|
|
|
// convert brushes and patches to collision data
|
|
for ( i = 0; i < mapFile->GetNumEntities(); i++ ) {
|
|
mapEnt = mapFile->GetEntity(i);
|
|
|
|
if ( numModels >= MAX_SUBMODELS ) {
|
|
common->Error( "idCollisionModelManagerLocal::BuildModels: more than %d collision models", MAX_SUBMODELS );
|
|
break;
|
|
}
|
|
models[numModels] = CollisionModelForMapEntity( mapEnt );
|
|
if ( models[ numModels] ) {
|
|
numModels++;
|
|
}
|
|
}
|
|
|
|
// free the proc bsp which is only used for data optimization
|
|
Mem_Free( procNodes );
|
|
procNodes = NULL;
|
|
|
|
// write the collision models to a file
|
|
WriteCollisionModelsToFile( mapFile->GetName(), 0, numModels, mapFile->GetGeometryCRC() );
|
|
}
|
|
|
|
timer.Stop();
|
|
|
|
// print statistics on collision data
|
|
cm_model_t model;
|
|
AccumulateModelInfo( &model );
|
|
common->Printf( "collision data:\n" );
|
|
common->Printf( "%6i models\n", numModels );
|
|
PrintModelInfo( &model );
|
|
common->Printf( "%.0f msec to load collision data.\n", timer.Milliseconds() );
|
|
}
|
|
|
|
|
|
/*
|
|
================
|
|
idCollisionModelManagerLocal::LoadMap
|
|
================
|
|
*/
|
|
void idCollisionModelManagerLocal::LoadMap( const idMapFile *mapFile ) {
|
|
|
|
if ( mapFile == NULL ) {
|
|
common->Error( "idCollisionModelManagerLocal::LoadMap: NULL mapFile" );
|
|
}
|
|
|
|
// check whether we can keep the current collision map based on the mapName and mapFileTime
|
|
if ( loaded ) {
|
|
if ( mapName.Icmp( mapFile->GetName() ) == 0 ) {
|
|
if ( mapFile->GetFileTime() == mapFileTime ) {
|
|
common->DPrintf( "Using loaded version\n" );
|
|
return;
|
|
}
|
|
common->DPrintf( "Reloading modified map\n" );
|
|
}
|
|
FreeMap();
|
|
}
|
|
|
|
// clear the collision map
|
|
Clear();
|
|
|
|
// models
|
|
maxModels = MAX_SUBMODELS;
|
|
numModels = 0;
|
|
models = (cm_model_t **) Mem_ClearedAlloc( (maxModels+1) * sizeof(cm_model_t *) );
|
|
|
|
// setup hash to speed up finding shared vertices and edges
|
|
SetupHash();
|
|
|
|
// setup trace model structure
|
|
SetupTrmModelStructure();
|
|
|
|
// build collision models
|
|
BuildModels( mapFile );
|
|
|
|
// save name and time stamp
|
|
mapName = mapFile->GetName();
|
|
mapFileTime = mapFile->GetFileTime();
|
|
loaded = true;
|
|
|
|
// shutdown the hash
|
|
ShutdownHash();
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idCollisionModelManagerLocal::GetModelName
|
|
===================
|
|
*/
|
|
const char *idCollisionModelManagerLocal::GetModelName( cmHandle_t model ) const {
|
|
if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) {
|
|
common->Printf( "idCollisionModelManagerLocal::GetModelBounds: invalid model handle\n" );
|
|
return "";
|
|
}
|
|
return models[model]->name.c_str();
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idCollisionModelManagerLocal::GetModelBounds
|
|
===================
|
|
*/
|
|
bool idCollisionModelManagerLocal::GetModelBounds( cmHandle_t model, idBounds &bounds ) const {
|
|
|
|
if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) {
|
|
common->Printf( "idCollisionModelManagerLocal::GetModelBounds: invalid model handle\n" );
|
|
return false;
|
|
}
|
|
|
|
bounds = models[model]->bounds;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idCollisionModelManagerLocal::GetModelContents
|
|
===================
|
|
*/
|
|
bool idCollisionModelManagerLocal::GetModelContents( cmHandle_t model, int &contents ) const {
|
|
if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) {
|
|
common->Printf( "idCollisionModelManagerLocal::GetModelContents: invalid model handle\n" );
|
|
return false;
|
|
}
|
|
|
|
contents = models[model]->contents;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idCollisionModelManagerLocal::GetModelVertex
|
|
===================
|
|
*/
|
|
bool idCollisionModelManagerLocal::GetModelVertex( cmHandle_t model, int vertexNum, idVec3 &vertex ) const {
|
|
if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) {
|
|
common->Printf( "idCollisionModelManagerLocal::GetModelVertex: invalid model handle\n" );
|
|
return false;
|
|
}
|
|
|
|
if ( vertexNum < 0 || vertexNum >= models[model]->numVertices ) {
|
|
common->Printf( "idCollisionModelManagerLocal::GetModelVertex: invalid vertex number\n" );
|
|
return false;
|
|
}
|
|
|
|
vertex = models[model]->vertices[vertexNum].p;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idCollisionModelManagerLocal::GetModelEdge
|
|
===================
|
|
*/
|
|
bool idCollisionModelManagerLocal::GetModelEdge( cmHandle_t model, int edgeNum, idVec3 &start, idVec3 &end ) const {
|
|
if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) {
|
|
common->Printf( "idCollisionModelManagerLocal::GetModelEdge: invalid model handle\n" );
|
|
return false;
|
|
}
|
|
|
|
edgeNum = abs( edgeNum );
|
|
if ( edgeNum >= models[model]->numEdges ) {
|
|
common->Printf( "idCollisionModelManagerLocal::GetModelEdge: invalid edge number\n" );
|
|
return false;
|
|
}
|
|
|
|
start = models[model]->vertices[models[model]->edges[edgeNum].vertexNum[0]].p;
|
|
end = models[model]->vertices[models[model]->edges[edgeNum].vertexNum[1]].p;
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
===================
|
|
idCollisionModelManagerLocal::GetModelPolygon
|
|
===================
|
|
*/
|
|
bool idCollisionModelManagerLocal::GetModelPolygon( cmHandle_t model, int polygonNum, idFixedWinding &winding ) const {
|
|
int i, edgeNum;
|
|
cm_polygon_t *poly;
|
|
|
|
if ( model < 0 || model > MAX_SUBMODELS || model >= numModels || !models[model] ) {
|
|
common->Printf( "idCollisionModelManagerLocal::GetModelPolygon: invalid model handle\n" );
|
|
return false;
|
|
}
|
|
|
|
poly = *reinterpret_cast<cm_polygon_t **>(&polygonNum);
|
|
winding.Clear();
|
|
for ( i = 0; i < poly->numEdges; i++ ) {
|
|
edgeNum = poly->edges[i];
|
|
winding += models[model]->vertices[ models[model]->edges[abs(edgeNum)].vertexNum[INTSIGNBITSET(edgeNum)] ].p;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idCollisionModelManagerLocal::LoadModel
|
|
==================
|
|
*/
|
|
cmHandle_t idCollisionModelManagerLocal::LoadModel( const char *modelName, const bool precache ) {
|
|
int handle;
|
|
|
|
handle = FindModel( modelName );
|
|
if ( handle >= 0 ) {
|
|
return handle;
|
|
}
|
|
|
|
if ( numModels >= MAX_SUBMODELS ) {
|
|
common->Error( "idCollisionModelManagerLocal::LoadModel: no free slots\n" );
|
|
return 0;
|
|
}
|
|
|
|
// try to load a .cm file
|
|
if ( LoadCollisionModelFile( modelName, 0 ) ) {
|
|
handle = FindModel( modelName );
|
|
if ( handle >= 0 ) {
|
|
return handle;
|
|
} else {
|
|
common->Warning( "idCollisionModelManagerLocal::LoadModel: collision file for '%s' contains different model", modelName );
|
|
}
|
|
}
|
|
|
|
// if only precaching .cm files do not waste memory converting render models
|
|
if ( precache ) {
|
|
return 0;
|
|
}
|
|
|
|
// try to load a .ASE or .LWO model and convert it to a collision model
|
|
models[numModels] = LoadRenderModel( modelName );
|
|
if ( models[numModels] != NULL ) {
|
|
numModels++;
|
|
return ( numModels - 1 );
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idCollisionModelManagerLocal::TrmFromModel_r
|
|
==================
|
|
*/
|
|
bool idCollisionModelManagerLocal::TrmFromModel_r( idTraceModel &trm, cm_node_t *node ) {
|
|
cm_polygonRef_t *pref;
|
|
cm_polygon_t *p;
|
|
int i;
|
|
|
|
while ( 1 ) {
|
|
for ( pref = node->polygons; pref; pref = pref->next ) {
|
|
p = pref->p;
|
|
|
|
if ( p->checkcount == checkCount ) {
|
|
continue;
|
|
}
|
|
|
|
p->checkcount = checkCount;
|
|
|
|
if ( trm.numPolys >= MAX_TRACEMODEL_POLYS ) {
|
|
return false;
|
|
}
|
|
// copy polygon properties
|
|
trm.polys[ trm.numPolys ].bounds = p->bounds;
|
|
trm.polys[ trm.numPolys ].normal = p->plane.Normal();
|
|
trm.polys[ trm.numPolys ].dist = p->plane.Dist();
|
|
trm.polys[ trm.numPolys ].numEdges = p->numEdges;
|
|
// copy edge index
|
|
for ( i = 0; i < p->numEdges; i++ ) {
|
|
trm.polys[ trm.numPolys ].edges[ i ] = p->edges[ i ];
|
|
}
|
|
trm.numPolys++;
|
|
}
|
|
if ( node->planeType == -1 ) {
|
|
break;
|
|
}
|
|
if ( !TrmFromModel_r( trm, node->children[1] ) ) {
|
|
return false;
|
|
}
|
|
node = node->children[0];
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idCollisionModelManagerLocal::TrmFromModel
|
|
|
|
NOTE: polygon merging can merge colinear edges and as such might cause dangling edges.
|
|
==================
|
|
*/
|
|
bool idCollisionModelManagerLocal::TrmFromModel( const cm_model_t *model, idTraceModel &trm ) {
|
|
int i, j, numEdgeUsers[MAX_TRACEMODEL_EDGES+1];
|
|
|
|
// if the model has too many vertices to fit in a trace model
|
|
if ( model->numVertices > MAX_TRACEMODEL_VERTS ) {
|
|
common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s has too many vertices.\n", model->name.c_str() );
|
|
PrintModelInfo( model );
|
|
return false;
|
|
}
|
|
|
|
// plus one because the collision model accounts for the first unused edge
|
|
if ( model->numEdges > MAX_TRACEMODEL_EDGES+1 ) {
|
|
common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s has too many edges.\n", model->name.c_str() );
|
|
PrintModelInfo( model );
|
|
return false;
|
|
}
|
|
|
|
trm.type = TRM_CUSTOM;
|
|
trm.numVerts = 0;
|
|
trm.numEdges = 1;
|
|
trm.numPolys = 0;
|
|
trm.bounds.Clear();
|
|
|
|
// copy polygons
|
|
checkCount++;
|
|
if ( !TrmFromModel_r( trm, model->node ) ) {
|
|
common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s has too many polygons.\n", model->name.c_str() );
|
|
PrintModelInfo( model );
|
|
return false;
|
|
}
|
|
|
|
// copy vertices
|
|
for ( i = 0; i < model->numVertices; i++ ) {
|
|
trm.verts[ i ] = model->vertices[ i ].p;
|
|
trm.bounds.AddPoint( trm.verts[ i ] );
|
|
}
|
|
trm.numVerts = model->numVertices;
|
|
|
|
// copy edges
|
|
for ( i = 0; i < model->numEdges; i++ ) {
|
|
trm.edges[ i ].v[0] = model->edges[ i ].vertexNum[0];
|
|
trm.edges[ i ].v[1] = model->edges[ i ].vertexNum[1];
|
|
}
|
|
// minus one because the collision model accounts for the first unused edge
|
|
trm.numEdges = model->numEdges - 1;
|
|
|
|
// each edge should be used exactly twice
|
|
memset( numEdgeUsers, 0, sizeof(numEdgeUsers) );
|
|
for ( i = 0; i < trm.numPolys; i++ ) {
|
|
for ( j = 0; j < trm.polys[i].numEdges; j++ ) {
|
|
numEdgeUsers[ abs( trm.polys[i].edges[j] ) ]++;
|
|
}
|
|
}
|
|
for ( i = 1; i <= trm.numEdges; i++ ) {
|
|
if ( numEdgeUsers[i] != 2 ) {
|
|
common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s has dangling edges, the model has to be an enclosed hull.\n", model->name.c_str() );
|
|
PrintModelInfo( model );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// assume convex
|
|
trm.isConvex = true;
|
|
// check if really convex
|
|
for ( i = 0; i < trm.numPolys; i++ ) {
|
|
// to be convex no vertices should be in front of any polygon plane
|
|
for ( j = 0; j < trm.numVerts; j++ ) {
|
|
if ( trm.polys[ i ].normal * trm.verts[ j ] - trm.polys[ i ].dist > 0.01f ) {
|
|
trm.isConvex = false;
|
|
break;
|
|
}
|
|
}
|
|
if ( j < trm.numVerts ) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// offset to center of model
|
|
trm.offset = trm.bounds.GetCenter();
|
|
|
|
trm.GenerateEdgeNormals();
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
==================
|
|
idCollisionModelManagerLocal::TrmFromModel
|
|
==================
|
|
*/
|
|
bool idCollisionModelManagerLocal::TrmFromModel( const char *modelName, idTraceModel &trm ) {
|
|
cmHandle_t handle;
|
|
|
|
handle = LoadModel( modelName, false );
|
|
if ( !handle ) {
|
|
common->Printf( "idCollisionModelManagerLocal::TrmFromModel: model %s not found.\n", modelName );
|
|
return false;
|
|
}
|
|
|
|
return TrmFromModel( models[ handle ], trm );
|
|
}
|