/*
===========================================================================
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 .
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 "../idlib/precompiled.h"
#pragma hdrstop
#include "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<> 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(mapPrim)->GetWidth();
height = static_cast(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(mapPrim)->GetNumSides() - 2) * 2;
*numEdges += (static_cast(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(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(mapPrim), i );
continue;
}
if ( mapPrim->GetType() == idMapPrimitive::TYPE_BRUSH ) {
ConvertBrushSides( model, static_cast(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(&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 );
}