Paranoia2/pm_shared/meshdesc.cpp

1244 lines
31 KiB
C++

/*
meshdesc.cpp - cached mesh for tracing custom objects
Copyright (C) 2012 Uncle Mike
This program 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.
This program 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.
*/
#define WIN32_LEAN_AND_MEAN
#include "windows.h"
#include <alert.h>
#include "vector.h"
#include "matrix.h"
#include "const.h"
#include "com_model.h"
#include "trace.h"
#include "mathlib.h"
#include "stringlib.h"
#include "virtualfs.h"
#include "clipfile.h"
#ifdef CLIENT_DLL
#include "cl_dll.h"
#include "render_api.h"
#else
#include "edict.h"
#include "eiface.h"
#include "physcallback.h"
#endif
#include "enginecallback.h"
CMeshDesc *UTIL_GetCollisionMesh( int modelindex )
{
model_t *mod = (model_t *)MODEL_HANDLE( modelindex );
if( !mod || mod->type != mod_studio )
return NULL;
// see if already cached
if( mod->bodymesh )
return mod->bodymesh;
// no studiodata ???
if( !mod->cache.data )
return NULL;
CMeshDesc *bodyMesh = (CMeshDesc *)Mem_Alloc( sizeof( CMeshDesc ));
if( !bodyMesh ) return NULL;
bodyMesh->CMeshDesc::CMeshDesc();
bodyMesh->SetDebugName( mod->name );
bodyMesh->SetModel( mod );
if( bodyMesh->StudioConstructMesh( ))
{
// now cached
mod->bodymesh = bodyMesh;
return bodyMesh;
}
// failed to build
delete bodyMesh;
return NULL;
}
CMeshDesc :: CMeshDesc( void )
{
memset( &m_mesh, 0, sizeof( m_mesh ));
m_debugName = NULL;
m_srcPlaneElems = NULL;
m_curPlaneElems = NULL;
m_srcPlaneHash = NULL;
m_srcPlanePool = NULL;
m_srcFacets = NULL;
m_pModel = NULL;
m_iNumTris = 0;
}
CMeshDesc :: ~CMeshDesc( void )
{
FreeMesh ();
}
void CMeshDesc :: InsertLinkBefore( link_t *l, link_t *before )
{
l->next = before;
l->prev = before->prev;
l->prev->next = l;
l->next->prev = l;
}
void CMeshDesc :: RemoveLink( link_t *l )
{
l->next->prev = l->prev;
l->prev->next = l->next;
}
void CMeshDesc :: ClearLink( link_t *l )
{
l->prev = l->next = l;
}
void CMeshDesc :: StartPacifier( void )
{
m_iOldPercent = -1;
UpdatePacifier( 0.001f );
}
void CMeshDesc :: UpdatePacifier( float percent )
{
int f;
f = (int)(percent * (float)PACIFIER_STEP);
f = bound( m_iOldPercent, f, PACIFIER_STEP );
if( f != m_iOldPercent )
{
for( int i = m_iOldPercent + 1; i <= f; i++ )
{
if(( i % PACIFIER_REM ) == 0 )
{
Msg( "%d%%%%", ( i / PACIFIER_REM ) * 10 );
}
else
{
if( i != PACIFIER_STEP )
{
Msg( "." );
}
}
}
m_iOldPercent = f;
}
}
void CMeshDesc :: EndPacifier( float total )
{
UpdatePacifier( 1.0f );
Msg( " (%.2f secs)", total );
Msg( "\n" );
}
/*
===============
CreateAreaNode
builds a uniformly subdivided tree for the given mesh size
===============
*/
areanode_t *CMeshDesc :: CreateAreaNode( int depth, const Vector &mins, const Vector &maxs )
{
areanode_t *anode;
Vector size;
Vector mins1, maxs1;
Vector mins2, maxs2;
anode = &areanodes[numareanodes++];
// use 'solid_edicts' to store facets
ClearLink( &anode->solid_edicts );
if( depth == MAX_AREA_DEPTH )
{
anode->axis = -1;
anode->children[0] = anode->children[1] = NULL;
return anode;
}
size = maxs - mins;
if( size[0] > size[1] )
anode->axis = 0;
else anode->axis = 1;
anode->dist = 0.5f * ( maxs[anode->axis] + mins[anode->axis] );
mins1 = mins;
mins2 = mins;
maxs1 = maxs;
maxs2 = maxs;
maxs1[anode->axis] = mins2[anode->axis] = anode->dist;
anode->children[0] = CreateAreaNode( depth+1, mins2, maxs2 );
anode->children[1] = CreateAreaNode( depth+1, mins1, maxs1 );
return anode;
}
void CMeshDesc :: FreeMesh( void )
{
has_tree = false;
if( m_mesh.numfacets <= 0 )
return;
m_iTotalPlanes = 0;
m_iAllocPlanes = 0;
m_iHashPlanes = 0;
m_iNumTris = 0;
// single memory block
Mem_Free( m_mesh.planes );
FreeMeshBuild();
memset( &m_mesh, 0, sizeof( m_mesh ));
}
/*
================
PlaneEqual
================
*/
bool CMeshDesc :: PlaneEqual( const mplane_t *p, const Vector &normal, float dist )
{
vec_t t;
if( -PLANE_DIST_EPSILON < ( t = p->dist - dist ) && t < PLANE_DIST_EPSILON
&& -PLANE_DIR_EPSILON < ( t = p->normal[0] - normal[0] ) && t < PLANE_DIR_EPSILON
&& -PLANE_DIR_EPSILON < ( t = p->normal[1] - normal[1] ) && t < PLANE_DIR_EPSILON
&& -PLANE_DIR_EPSILON < ( t = p->normal[2] - normal[2] ) && t < PLANE_DIR_EPSILON )
return true;
return false;
}
/*
=================
PlaneTypeForNormal
=================
*/
int CMeshDesc :: PlaneTypeForNormal( const Vector &normal )
{
vec_t ax, ay, az;
ax = fabs( normal[0] );
ay = fabs( normal[1] );
az = fabs( normal[2] );
if(( ax > 1.0 - PLANE_DIR_EPSILON ) && ( ay < PLANE_DIR_EPSILON ) && ( az < PLANE_DIR_EPSILON ))
return PLANE_X;
if(( ay > 1.0 - PLANE_DIR_EPSILON ) && ( az < PLANE_DIR_EPSILON ) && ( ax < PLANE_DIR_EPSILON ))
return PLANE_Y;
if(( az > 1.0 - PLANE_DIR_EPSILON ) && ( ax < PLANE_DIR_EPSILON ) && ( ay < PLANE_DIR_EPSILON ))
return PLANE_Z;
return PLANE_NONAXIAL;
}
/*
================
SnapNormal
================
*/
int CMeshDesc :: SnapNormal( Vector &normal )
{
int type = PlaneTypeForNormal( normal );
bool renormalize = false;
// snap normal to nearest axial if possible
if( type <= PLANE_LAST_AXIAL )
{
for( int i = 0; i < 3; i++ )
{
if( i == type )
normal[i] = normal[i] > 0 ? 1 : -1;
else normal[i] = 0;
}
renormalize = true;
}
else
{
for( int i = 0; i < 3; i++ )
{
if( fabs( fabs( normal[i] ) - 0.707106 ) < PLANE_DIR_EPSILON )
{
normal[i] = normal[i] > 0 ? 0.707106 : -0.707106;
renormalize = true;
}
}
}
if( renormalize )
normal = normal.Normalize();
return type;
}
/*
================
CreateNewFloatPlane
================
*/
int CMeshDesc :: CreateNewFloatPlane( const Vector &srcnormal, float dist, int hash )
{
hashplane_t *p;
if( srcnormal.Length() < 0.5f )
return -1;
// sanity check
if( m_mesh.numplanes >= m_iAllocPlanes )
return -1;
// snap plane normal
Vector normal = srcnormal;
int type = SnapNormal( normal );
// only snap distance if the normal is an axis. Otherwise there
// is nothing "natural" about snapping the distance to an integer.
if( VectorIsOnAxis( normal ) && fabs( dist - Q_rint( dist )) < PLANE_DIST_EPSILON )
dist = Q_rint( dist ); // catch -0.0
// create a new one
p = &m_srcPlanePool[m_mesh.numplanes];
p->hash = m_srcPlaneHash[hash];
m_srcPlaneHash[hash] = p;
// record the new plane
SetPlane( &p->pl, normal, dist, type );
return m_mesh.numplanes++;
}
/*
=============
FindFloatPlane
=============
*/
int CMeshDesc :: FindFloatPlane( const Vector &normal, float dist )
{
int hash;
// trying to find equal plane
hash = (int)fabs( dist );
hash &= (PLANE_HASHES - 1);
// search the border bins as well
for( int i = -1; i <= 1; i++ )
{
int h = (hash + i) & (PLANE_HASHES - 1);
for( hashplane_t *p = m_srcPlaneHash[h]; p; p = p->hash )
{
if( PlaneEqual( &p->pl, normal, dist ))
return (int)(p - m_srcPlanePool); // already exist
}
}
// allocate a new two opposite planes
return CreateNewFloatPlane( normal, dist, hash );
}
/*
================
PlaneFromPoints
================
*/
int CMeshDesc :: PlaneFromPoints( const Vector &p0, const Vector &p1, const Vector &p2 )
{
Vector t1 = p0 - p1;
Vector t2 = p2 - p1;
Vector normal = CrossProduct( t1, t2 );
if( !normal.NormalizeLength())
return -1;
return FindFloatPlane( normal, DotProduct( normal, p0 ));
}
void CMeshDesc :: ExtractAnimValue( int frame, mstudioanim_t *panim, int dof, float scale, float &v1 )
{
if( !panim || panim->offset[dof] == 0 )
{
v1 = 0.0f;
return;
}
const mstudioanimvalue_t *panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[dof]);
int k = frame;
while( panimvalue->num.total <= k )
{
k -= panimvalue->num.total;
panimvalue += panimvalue->num.valid + 1;
if( panimvalue->num.total == 0 )
{
v1 = 0.0f;
return;
}
}
// Bah, missing blend!
if( panimvalue->num.valid > k )
{
v1 = panimvalue[k+1].value * scale;
}
else
{
// get last valid data block
v1 = panimvalue[panimvalue->num.valid].value * scale;
}
}
void CMeshDesc :: StudioCalcBoneTransform( int frame, mstudiobone_t *pbone, mstudioanim_t *panim, Vector &pos, Vector4D &q )
{
vec3_t origin;
Radian angles;
ExtractAnimValue( frame, panim, 0, pbone->scale[0], origin.x );
ExtractAnimValue( frame, panim, 1, pbone->scale[1], origin.y );
ExtractAnimValue( frame, panim, 2, pbone->scale[2], origin.z );
ExtractAnimValue( frame, panim, 3, pbone->scale[3], angles.x );
ExtractAnimValue( frame, panim, 4, pbone->scale[4], angles.y );
ExtractAnimValue( frame, panim, 5, pbone->scale[5], angles.z );
for( int j = 0; j < 3; j++ )
{
origin[j] = pbone->value[j+0] + origin[j];
angles[j] = pbone->value[j+3] + angles[j];
}
AngleQuaternion( angles, q );
VectorCopy( origin, pos );
}
bool CMeshDesc :: AddMeshTrinagle( const mvert_t triangle[3], int skinref )
{
int i, planenum;
if( m_iNumTris <= 0 )
return false; // were not in a build mode!
if( m_mesh.numfacets >= m_iNumTris )
return false; // not possible?
mfacet_t *facet = &m_srcFacets[m_mesh.numfacets];
mplane_t *mainplane;
// calculate plane for this triangle
if(( planenum = PlaneFromPoints( triangle[0].point, triangle[1].point, triangle[2].point )) == -1 )
return false; // bad plane
mplane_t planes[MAX_FACET_PLANES];
Vector normal;
int numplanes;
float dist;
mainplane = &m_srcPlanePool[planenum].pl;
facet->numplanes = numplanes = 0;
planes[numplanes].normal = mainplane->normal;
planes[numplanes].dist = mainplane->dist;
numplanes++;
// calculate mins & maxs
ClearBounds( facet->mins, facet->maxs );
for( i = 0; i < 3; i++ )
{
AddPointToBounds( triangle[i].point, facet->mins, facet->maxs );
facet->triangle[i] = triangle[i];
}
facet->edge1 = facet->triangle[1].point - facet->triangle[0].point;
facet->edge2 = facet->triangle[2].point - facet->triangle[0].point;
// add the axial planes
for( int axis = 0; axis < 3; axis++ )
{
for( int dir = -1; dir <= 1; dir += 2 )
{
for( i = 0; i < numplanes; i++ )
{
if( planes[i].normal[axis] == dir )
break;
}
if( i == numplanes )
{
normal = g_vecZero;
normal[axis] = dir;
if( dir == 1 )
dist = facet->maxs[axis];
else dist = -facet->mins[axis];
planes[numplanes].normal = normal;
planes[numplanes].dist = dist;
numplanes++;
}
if( numplanes >= MAX_FACET_PLANES )
return false;
}
}
// add the edge bevels
for( i = 0; i < 3; i++ )
{
int j = (i + 1) % 3;
int k = (i + 2) % 3;
Vector vec = triangle[i].point - triangle[j].point;
if( vec.Length() < 0.5f ) continue;
vec = vec.Normalize();
// SnapNormal( vec );
for( j = 0; j < 3; j++ )
{
if( vec[j] == 1.0f || vec[j] == -1.0f )
break; // axial
}
if( j != 3 ) continue; // only test non-axial edges
// try the six possible slanted axials from this edge
for( int axis = 0; axis < 3; axis++ )
{
for( int dir = -1; dir <= 1; dir += 2 )
{
// construct a plane
Vector vec2 = g_vecZero;
vec2[axis] = dir;
normal = CrossProduct( vec, vec2 );
if( normal.Length() < 0.5f )
continue;
normal = normal.Normalize();
dist = DotProduct( triangle[i].point, normal );
for( j = 0; j < numplanes; j++ )
{
// if this plane has already been used, skip it
if( PlaneEqual( &planes[j], normal, dist ))
break;
}
if( j != numplanes ) continue;
// if all other points are behind this plane, it is a proper edge bevel
for( j = 0; j < 3; j++ )
{
if( j != i )
{
float d = DotProduct( triangle[j].point, normal ) - dist;
// point in front: this plane isn't part of the outer hull
if( d > 0.1f ) break;
}
}
if( j != 3 ) continue;
// add this plane
planes[numplanes].normal = normal;
planes[numplanes].dist = dist;
numplanes++;
if( numplanes >= MAX_FACET_PLANES )
return false;
}
}
}
// add triangle to bounds
for( i = 0; i < 3; i++ )
AddPointToBounds( triangle[i].point, m_mesh.mins, m_mesh.maxs );
facet->indices = m_curPlaneElems;
m_curPlaneElems += numplanes;
facet->numplanes = numplanes;
facet->skinref = skinref;
for( i = 0; i < facet->numplanes; i++ )
{
// add plane to global pool
facet->indices[i] = FindFloatPlane( planes[i].normal, planes[i].dist );
}
for( i = 0; i < 3; i++ )
{
// spread the mins / maxs by a pixel
facet->mins[i] -= 1.0f;
facet->maxs[i] += 1.0f;
}
// added
m_iTotalPlanes += numplanes;
m_mesh.numfacets++;
return true;
}
void CMeshDesc :: RelinkFacet( mfacet_t *facet )
{
// find the first node that the facet box crosses
areanode_t *node = areanodes;
while( 1 )
{
if( node->axis == -1 ) break;
if( facet->mins[node->axis] > node->dist )
node = node->children[0];
else if( facet->maxs[node->axis] < node->dist )
node = node->children[1];
else break; // crosses the node
}
// link it in
InsertLinkBefore( &facet->area, &node->solid_edicts );
}
bool CMeshDesc :: StudioLoadCache( const char *pszModelName )
{
char szFilename[MAX_PATH];
char szModelname[MAX_PATH];
int i, length, iCompare;
Q_strncpy( szModelname, pszModelName + Q_strlen( "models/" ), sizeof( szModelname ));
COM_StripExtension( szModelname );
Q_snprintf( szFilename, sizeof( szFilename ), "cache/%s.clip", szModelname );
if( COMPARE_FILE_TIME( m_pModel->name, szFilename, &iCompare ))
{
// MDL file is newer.
if( iCompare > 0 )
return false;
}
else
{
return false;
}
byte *aMemFile = LOAD_FILE( szFilename, &length );
if( !aMemFile ) return false;
CVirtualFS file( aMemFile, length );
dcachelump_t *lump;
dcachehdr_t hdr;
dfacet_t facet;
dplane_t plane;
file.Read( &hdr, sizeof( hdr ));
if( hdr.id != IDCLIPHEADER )
{
ALERT( at_warning, "%s has wrong id (%p should be %p)\n", szFilename, hdr.id, IDCLIPHEADER );
goto cleanup;
}
if( hdr.version != CLIP_VERSION )
{
ALERT( at_warning, "%s has wrong version (%i should be %i)\n", szFilename, hdr.version, CLIP_VERSION );
goto cleanup;
}
if( hdr.modelCRC != m_pModel->modelCRC )
{
ALERT( at_console, "%s was changed, CLIP cache will be updated\n", szFilename );
goto cleanup;
}
ClearBounds( m_mesh.mins, m_mesh.maxs );
memset( areanodes, 0, sizeof( areanodes ));
numareanodes = 0;
// read plane representation table
lump = &hdr.lumps[LUMP_CLIP_PLANE_INDEXES];
m_iAllocPlanes = m_iTotalPlanes = lump->filelen / sizeof( uint );
if( lump->filelen <= 0 || lump->filelen % sizeof( uint ))
{
ALERT( at_warning, "%s has funny size of LUMP_CLIP_PLANE_INDEXES\n", szFilename );
goto cleanup;
}
m_srcPlaneElems = (uint *)calloc( sizeof( uint ), m_iAllocPlanes );
m_curPlaneElems = m_srcPlaneElems;
file.Seek( lump->fileofs, SEEK_SET );
// fill in plane indexes
file.Read( m_srcPlaneElems, lump->filelen );
// read unique planes array (hash is unused)
lump = &hdr.lumps[LUMP_CLIP_PLANES];
m_mesh.numplanes = lump->filelen / sizeof( dplane_t );
if( lump->filelen <= 0 || lump->filelen % sizeof( dplane_t ))
{
ALERT( at_warning, "%s has funny size of LUMP_CLIP_PLANES\n", szFilename );
goto cleanup;
}
m_srcPlanePool = (hashplane_t *)calloc( sizeof( hashplane_t ), m_mesh.numplanes );
file.Seek( lump->fileofs, SEEK_SET );
for( i = 0; i < m_mesh.numplanes; i++ )
{
file.Read( &plane, sizeof( plane ));
m_srcPlanePool[i].pl.normal = plane.normal;
m_srcPlanePool[i].pl.dist = plane.dist;
m_srcPlanePool[i].pl.type = plane.type;
// categorize it
CategorizePlane( &m_srcPlanePool[i].pl );
}
lump = &hdr.lumps[LUMP_CLIP_FACETS];
m_mesh.numfacets = m_iNumTris = lump->filelen / sizeof( dfacet_t );
if( lump->filelen <= 0 || lump->filelen % sizeof( dfacet_t ))
{
ALERT( at_warning, "%s has funny size of LUMP_CLIP_FACETS\n", szFilename );
goto cleanup;
}
m_srcFacets = (mfacet_t *)calloc( sizeof( mfacet_t ), m_iNumTris );
file.Seek( lump->fileofs, SEEK_SET );
for( i = 0; i < m_iNumTris; i++ )
{
file.Read( &facet, sizeof( facet ));
m_srcFacets[i].skinref = facet.skinref;
m_srcFacets[i].mins = facet.mins;
m_srcFacets[i].maxs = facet.maxs;
m_srcFacets[i].edge1 = facet.edge1;
m_srcFacets[i].edge2 = facet.edge2;
m_srcFacets[i].numplanes = facet.numplanes;
// bounds checking
if( m_curPlaneElems != &m_srcPlaneElems[facet.firstindex] )
Msg( "bad mem %p != %p\n", m_curPlaneElems, &m_srcPlaneElems[facet.firstindex] );
// just setup pointer to index array
m_srcFacets[i].indices = m_curPlaneElems;
m_curPlaneElems += m_srcFacets[i].numplanes;
for( int k = 0; k < 3; k++ )
{
m_srcFacets[i].triangle[k] = facet.triangle[k];
AddPointToBounds( facet.triangle[k].point, m_mesh.mins, m_mesh.maxs );
}
}
if( m_iNumTris >= 256 )
has_tree = true; // too many triangles invoke to build AABB tree
else has_tree = false;
// all done
FREE_FILE( aMemFile );
return true;
cleanup:
FREE_FILE( aMemFile );
FreeMeshBuild();
return false;
}
bool CMeshDesc :: StudioSaveCache( const char *pszModelName )
{
char szFilename[MAX_PATH];
char szModelname[MAX_PATH];
dcachelump_t *lump;
dcachehdr_t hdr;
CVirtualFS file;
int i, curIndex;
// something went wrong
if( m_mesh.numfacets <= 0 )
return false;
memset( &hdr, 0, sizeof( hdr ));
hdr.id = IDCLIPHEADER;
hdr.version = CLIP_VERSION;
hdr.modelCRC = m_pModel->modelCRC;
file.Write( &hdr, sizeof( hdr ));
dfacet_t *out_facets = (dfacet_t *)Mem_Alloc( sizeof( dfacet_t ) * m_mesh.numfacets );
dplane_t *out_planes = (dplane_t *)Mem_Alloc( sizeof( dplane_t ) * m_mesh.numplanes );
// copy planes into mesh array (probably aligned block)
for( i = 0, curIndex = 0; i < m_mesh.numfacets; i++ )
{
out_facets[i].mins = m_srcFacets[i].mins;
out_facets[i].maxs = m_srcFacets[i].maxs;
out_facets[i].edge1 = m_srcFacets[i].edge1;
out_facets[i].edge2 = m_srcFacets[i].edge2;
out_facets[i].numplanes = m_srcFacets[i].numplanes;
out_facets[i].skinref = m_srcFacets[i].skinref;
out_facets[i].firstindex = curIndex;
curIndex += m_srcFacets[i].numplanes;
for( int k = 0; k < 3; k++ )
out_facets[i].triangle[k] = m_srcFacets[i].triangle[k];
}
if( curIndex != m_iTotalPlanes )
ALERT( at_error, "StudioSaveCache: invalid planecount! %d != %d\n", curIndex, m_iTotalPlanes );
for( i = 0; i < m_mesh.numplanes; i++ )
{
VectorCopy( m_srcPlanePool[i].pl.normal, out_planes[i].normal );
out_planes[i].dist = m_srcPlanePool[i].pl.dist;
out_planes[i].type = m_srcPlanePool[i].pl.type;
}
if( curIndex != m_iTotalPlanes )
ALERT( at_error, "StudioSaveCache: invalid planecount! %d != %d\n", curIndex, m_iTotalPlanes );
lump = &hdr.lumps[LUMP_CLIP_FACETS];
lump->fileofs = file.Tell();
lump->filelen = sizeof( dfacet_t ) * m_mesh.numfacets;
file.Write( out_facets, (lump->filelen + 3) & ~3 );
lump = &hdr.lumps[LUMP_CLIP_PLANES];
lump->fileofs = file.Tell();
lump->filelen = sizeof( dplane_t ) * m_mesh.numplanes;
file.Write( out_planes, (lump->filelen + 3) & ~3 );
lump = &hdr.lumps[LUMP_CLIP_PLANE_INDEXES];
lump->fileofs = file.Tell();
lump->filelen = sizeof( uint ) * m_iTotalPlanes;
file.Write( m_srcPlaneElems, (lump->filelen + 3) & ~3 );
// update header
file.Seek( 0, SEEK_SET );
file.Write( &hdr, sizeof( hdr ));
Mem_Free( out_facets );
Mem_Free( out_planes );
Q_strncpy( szModelname, pszModelName + Q_strlen( "models/" ), sizeof( szModelname ));
COM_StripExtension( szModelname );
Q_snprintf( szFilename, sizeof( szFilename ), "cache/%s.clip", szModelname );
if( SAVE_FILE( szFilename, file.GetBuffer(), file.GetSize( )))
return true;
ALERT( at_error, "StudioSaveCache: couldn't store %s\n", szFilename );
return false;
}
bool CMeshDesc :: InitMeshBuild( int numTriangles )
{
if( numTriangles <= 0 )
return false;
// perfomance warning
if( numTriangles >= MAX_TRIANGLES )
{
ALERT( at_error, "%s have too many triangles (%i). Mesh cannot be build\n", m_debugName, numTriangles );
return false; // failed to build (too many triangles)
}
else if( numTriangles >= (MAX_TRIANGLES >> 1))
ALERT( at_warning, "%s have too many triangles (%i)\n", m_debugName, numTriangles );
// show the pacifier for user is knowelege what engine is not hanging
if( numTriangles >= (MAX_TRIANGLES >> 3))
m_bShowPacifier = true;
else m_bShowPacifier = false;
if( numTriangles >= 256 )
has_tree = true; // too many triangles invoke to build AABB tree
else has_tree = false;
ClearBounds( m_mesh.mins, m_mesh.maxs );
memset( areanodes, 0, sizeof( areanodes ));
numareanodes = 0;
// bevels for each triangle can't exceeds MAX_FACET_PLANES
m_iAllocPlanes = numTriangles * MAX_FACET_PLANES;
m_iHashPlanes = (m_iAllocPlanes>>2);
m_iNumTris = numTriangles;
m_iTotalPlanes = 0;
// create pools for construct mesh
m_srcFacets = (mfacet_t *)calloc( sizeof( mfacet_t ), numTriangles );
m_srcPlaneHash = (hashplane_t **)calloc( sizeof( hashplane_t* ), m_iHashPlanes );
m_srcPlanePool = (hashplane_t *)calloc( sizeof( hashplane_t ), m_iAllocPlanes );
m_srcPlaneElems = (uint *)calloc( sizeof( uint ), m_iAllocPlanes );
m_curPlaneElems = m_srcPlaneElems;
return true;
}
bool CMeshDesc :: StudioConstructMesh( void )
{
float start_time = Sys_DoubleTime();
if( !m_pModel || m_pModel->type != mod_studio )
return false;
studiohdr_t *phdr = (studiohdr_t *)m_pModel->cache.data;
if( !phdr ) return false;
int body = 0, skin = 0; // FIXME: allow to use body
if( StudioLoadCache( m_pModel->name ))
{
if( !FinishMeshBuild( ))
return false;
FreeMeshBuild();
ALERT( at_aiconsole, "%s: load time %g secs, size %s\n", m_debugName, Sys_DoubleTime() - start_time, Q_memprint( mesh_size ));
PrintMeshInfo();
return true;
}
if( phdr->numbones < 1 )
return false;
// compute default pose for building mesh from
mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)phdr + phdr->seqindex);
mstudioseqgroup_t *pseqgroup = (mstudioseqgroup_t *)((byte *)phdr + phdr->seqgroupindex);
mstudioanim_t *panim = (mstudioanim_t *)((byte *)phdr + pseqdesc->animindex);
mstudiobone_t *pbone = (mstudiobone_t *)((byte *)phdr + phdr->boneindex);
static Vector pos[MAXSTUDIOBONES];
static Vector4D q[MAXSTUDIOBONES];
int totalVertSize = 0;
for( int i = 0; i < phdr->numbones; i++, pbone++, panim++ )
{
StudioCalcBoneTransform( 0, pbone, panim, pos[i], q[i] );
}
pbone = (mstudiobone_t *)((byte *)phdr + phdr->boneindex);
matrix3x4 transform, bonematrix, bonetransform[MAXSTUDIOBONES];
transform = matrix3x4( g_vecZero, g_vecZero, Vector( 1.0f, 1.0f, 1.0f ));
// compute bones for default anim
for( i = 0; i < phdr->numbones; i++ )
{
// initialize bonematrix
bonematrix = matrix3x4( pos[i], q[i] );
if( pbone[i].parent == -1 )
bonetransform[i] = transform.ConcatTransforms( bonematrix );
else bonetransform[i] = bonetransform[pbone[i].parent].ConcatTransforms( bonematrix );
}
// through all bodies to determine max vertices count
for( i = 0; i < phdr->numbodyparts; i++ )
{
mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)phdr + phdr->bodypartindex) + i;
int index = body / pbodypart->base;
index = index % pbodypart->nummodels;
mstudiomodel_t *psubmodel = (mstudiomodel_t *)((byte *)phdr + pbodypart->modelindex) + index;
totalVertSize += psubmodel->numverts;
}
Vector *verts = new Vector[totalVertSize * 8]; // allocate temporary vertices array
float *coords = new float[totalVertSize * 16]; // allocate temporary texcoords array
uint *skinrefs = new uint[totalVertSize * 8]; // store material pointers
unsigned int *indices = new unsigned int[totalVertSize * 24];
int numVerts = 0, numElems = 0, numTris = 0;
mvert_t triangle[3];
for( int k = 0; k < phdr->numbodyparts; k++ )
{
mstudiobodyparts_t *pbodypart = (mstudiobodyparts_t *)((byte *)phdr + phdr->bodypartindex) + k;
int index = body / pbodypart->base;
index = index % pbodypart->nummodels;
int m_skinnum = bound( 0, skin, MAXSTUDIOSKINS );
mstudiomodel_t *psubmodel = (mstudiomodel_t *)((byte *)phdr + pbodypart->modelindex) + index;
Vector *pstudioverts = (Vector *)((byte *)phdr + psubmodel->vertindex);
Vector *m_verts = new Vector[psubmodel->numverts];
byte *pvertbone = ((byte *)phdr + psubmodel->vertinfoindex);
// setup all the vertices
for( i = 0; i < psubmodel->numverts; i++ )
m_verts[i] = bonetransform[pvertbone[i]].VectorTransform( pstudioverts[i] );
mstudiomaterial_t *pmaterial = (m_pModel) ? (mstudiomaterial_t *)m_pModel->materials : NULL;
mstudiotexture_t *ptexture = (mstudiotexture_t *)((byte *)phdr + phdr->textureindex);
short *pskinref = (short *)((byte *)phdr + phdr->skinindex);
if( m_skinnum != 0 && m_skinnum < phdr->numskinfamilies )
pskinref += (m_skinnum * phdr->numskinref);
for( int j = 0; j < psubmodel->nummesh; j++ )
{
mstudiomesh_t *pmesh = (mstudiomesh_t *)((byte *)phdr + psubmodel->meshindex) + j;
short *ptricmds = (short *)((byte *)phdr + pmesh->triindex);
float s = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].width;
float t = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].height;
int flags = ptexture[pskinref[pmesh->skinref]].flags;
while( i = *( ptricmds++ ))
{
int vertexState = 0;
bool tri_strip;
if( i < 0 )
{
tri_strip = false;
i = -i;
}
else tri_strip = true;
numTris += (i - 2);
for( ; i > 0; i--, ptricmds += 4 )
{
// build in indices
if( vertexState++ < 3 )
{
indices[numElems++] = numVerts;
}
else if( tri_strip )
{
// flip triangles between clockwise and counter clockwise
if( vertexState & 1 )
{
// draw triangle [n-2 n-1 n]
indices[numElems++] = numVerts - 2;
indices[numElems++] = numVerts - 1;
indices[numElems++] = numVerts;
}
else
{
// draw triangle [n-1 n-2 n]
indices[numElems++] = numVerts - 1;
indices[numElems++] = numVerts - 2;
indices[numElems++] = numVerts;
}
}
else
{
// draw triangle fan [0 n-1 n]
indices[numElems++] = numVerts - ( vertexState - 1 );
indices[numElems++] = numVerts - 1;
indices[numElems++] = numVerts;
}
verts[numVerts] = m_verts[ptricmds[0]];
skinrefs[numVerts] = pmesh->skinref;
if( flags & STUDIO_NF_CHROME )
{
// probably always equal 64 (see studiomdl.c for details)
coords[numVerts*2+0] = s;
coords[numVerts*2+1] = t;
}
else if( flags & STUDIO_NF_UV_COORDS )
{
coords[numVerts*2+0] = HalfToFloat( ptricmds[2] );
coords[numVerts*2+1] = HalfToFloat( ptricmds[3] );
}
else
{
coords[numVerts*2+0] = ptricmds[2] * s;
coords[numVerts*2+1] = ptricmds[3] * t;
}
numVerts++;
}
}
}
delete [] m_verts; // don't keep this because different submodels may have difference count of vertices
}
if( numTris != ( numElems / 3 ))
ALERT( at_error, "StudioConstructMesh: mismatch triangle count (%i should be %i)\n", (numElems / 3), numTris );
InitMeshBuild( numTris );
if( m_bShowPacifier )
{
if( numTris >= 262144 )
Msg( "StudioConstructMesh: ^1%s^7\n", m_debugName );
else if( numTris >= 131072 )
Msg( "StudioConstructMesh: ^3%s^7\n", m_debugName );
else Msg( "StudioConstructMesh: ^2%s^7\n", m_debugName );
StartPacifier();
}
for( i = 0; i < numElems; i += 3 )
{
// fill the triangle
triangle[0].point = verts[indices[i+0]];
triangle[1].point = verts[indices[i+1]];
triangle[2].point = verts[indices[i+2]];
triangle[0].st[0] = coords[indices[i+0]*2+0];
triangle[0].st[1] = coords[indices[i+0]*2+1];
triangle[1].st[0] = coords[indices[i+1]*2+0];
triangle[1].st[1] = coords[indices[i+1]*2+1];
triangle[2].st[0] = coords[indices[i+2]*2+0];
triangle[2].st[1] = coords[indices[i+2]*2+1];
// add it to mesh
AddMeshTrinagle( triangle, skinrefs[indices[i]] );
if( m_bShowPacifier )
{
UpdatePacifier( (float)m_mesh.numfacets / numTris );
}
}
if( m_bShowPacifier )
{
float end_time = Sys_DoubleTime();
EndPacifier( end_time - start_time );
}
delete [] skinrefs;
delete [] coords;
delete [] verts;
delete [] indices;
if( !FinishMeshBuild( ))
return false;
// now dump the collision into cachefile
StudioSaveCache( m_pModel->name );
FreeMeshBuild();
if( !m_bShowPacifier )
{
ALERT( at_console, "%s: CLIP build time %g secs\n", m_debugName, Sys_DoubleTime() - start_time );
PrintMeshInfo();
}
// done
return true;
}
bool CMeshDesc :: FinishMeshBuild( void )
{
if( m_mesh.numfacets <= 0 )
{
ALERT( at_aiconsole, "%s: failed to build triangle mesh\n", m_debugName );
FreeMesh();
return false;
}
for( int i = 0; i < 3; i++ )
{
// spread the mins / maxs by a pixel
m_mesh.mins[i] -= 1.0f;
m_mesh.maxs[i] += 1.0f;
}
size_t memsize = (sizeof( mfacet_t ) * m_mesh.numfacets) + (sizeof( mplane_t ) * m_mesh.numplanes) + (sizeof( uint ) * m_iTotalPlanes);
mesh_size = sizeof( m_mesh ) + memsize;
// create non-fragmented memory piece and move mesh
byte *buffer = (byte *)Mem_Alloc( memsize );
byte *bufend = buffer + memsize;
// setup pointers
m_mesh.planes = (mplane_t *)buffer; // so we free mem with planes
buffer += (sizeof( mplane_t ) * m_mesh.numplanes);
m_mesh.facets = (mfacet_t *)buffer;
buffer += (sizeof( mfacet_t ) * m_mesh.numfacets);
// setup mesh pointers
for( i = 0; i < m_mesh.numfacets; i++ )
{
m_mesh.facets[i].indices = (uint *)buffer;
buffer += (sizeof( uint ) * m_srcFacets[i].numplanes);
}
if( buffer != bufend )
ALERT( at_error, "FinishMeshBuild: memory representation error! %x != %x\n", buffer, bufend );
// re-use pointer
m_curPlaneElems = m_srcPlaneElems;
// copy planes into mesh array (probably aligned block)
for( i = 0; i < m_mesh.numplanes; i++ )
m_mesh.planes[i] = m_srcPlanePool[i].pl;
// copy planes into mesh array (probably aligned block)
for( i = 0; i < m_mesh.numfacets; i++ )
{
m_mesh.facets[i].mins = m_srcFacets[i].mins;
m_mesh.facets[i].maxs = m_srcFacets[i].maxs;
m_mesh.facets[i].edge1 = m_srcFacets[i].edge1;
m_mesh.facets[i].edge2 = m_srcFacets[i].edge2;
m_mesh.facets[i].area.next = m_mesh.facets[i].area.prev = NULL;
m_mesh.facets[i].numplanes = m_srcFacets[i].numplanes;
m_mesh.facets[i].skinref = m_srcFacets[i].skinref;
for( int j = 0; j < m_srcFacets[i].numplanes; j++ )
m_mesh.facets[i].indices[j] = m_curPlaneElems[j];
m_curPlaneElems += m_srcFacets[i].numplanes;
for( int k = 0; k < 3; k++ )
m_mesh.facets[i].triangle[k] = m_srcFacets[i].triangle[k];
}
if( has_tree )
{
// create tree
CreateAreaNode( 0, m_mesh.mins, m_mesh.maxs );
for( i = 0; i < m_mesh.numfacets; i++ )
RelinkFacet( &m_mesh.facets[i] );
}
return true;
}
void CMeshDesc :: PrintMeshInfo( void )
{
#if 0 // g-cont. just not needs
ALERT( at_console, "FinishMesh: %s %s", m_debugName, Q_memprint( mesh_size ));
ALERT( at_console, " (planes reduced from %i to %i)", m_iTotalPlanes, m_mesh.numplanes );
ALERT( at_console, "\n" );
#endif
}
void CMeshDesc :: FreeMeshBuild( void )
{
// no reason to keep these arrays
free( m_srcPlaneElems );
free( m_srcPlaneHash );
free( m_srcPlanePool );
free( m_srcFacets );
m_srcPlaneElems = NULL;
m_curPlaneElems = NULL;
m_srcPlaneHash = NULL;
m_srcPlanePool = NULL;
m_srcFacets = NULL;
}