/* 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 #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; }