//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ //=============================================================================// #include #include "mathlib/vector.h" #include "bspfile.h" #include "bsplib.h" #include "cmdlib.h" #include "physdll.h" #include "utlvector.h" #include "vbsp.h" #include "phyfile.h" #include #include "KeyValues.h" #include "UtlBuffer.h" #include "utlsymbol.h" #include "utlrbtree.h" #include "ivp.h" #include "disp_ivp.h" #include "materialpatch.h" #include "bitvec.h" // bit per leaf typedef CBitVec leafbitarray_t; // parameters for conversion to vphysics #define NO_SHRINK 0.0f // NOTE: vphysics maintains a minimum separation radius between objects // This radius is set to 0.25, but it's symmetric. So shrinking potentially moveable // brushes by 0.5 in every direction ensures that these brushes can be constructed // touching the world, and constrained in place without collisions or friction // UNDONE: Add a key to disable this shrinking if necessary #define VPHYSICS_SHRINK (0.5f) // shrink BSP brushes by this much for collision #define VPHYSICS_MERGE 0.01f // merge verts closer than this void EmitPhysCollision(); IPhysicsCollision *physcollision = NULL; extern IPhysicsSurfaceProps *physprops; // a list of all of the materials in the world model static CUtlVector s_WorldPropList; //----------------------------------------------------------------------------- // Purpose: Write key/value pairs out to a memory buffer //----------------------------------------------------------------------------- CTextBuffer::CTextBuffer( void ) { } CTextBuffer::~CTextBuffer( void ) { } void CTextBuffer::WriteText( const char *pText ) { int len = strlen( pText ); CopyData( pText, len ); } void CTextBuffer::WriteIntKey( const char *pKeyName, int outputData ) { char tmp[1024]; // FAIL! if ( strlen(pKeyName) > 1000 ) { Msg("Error writing collision data %s\n", pKeyName ); return; } sprintf( tmp, "\"%s\" \"%d\"\n", pKeyName, outputData ); CopyData( tmp, strlen(tmp) ); } void CTextBuffer::WriteStringKey( const char *pKeyName, const char *outputData ) { CopyStringQuotes( pKeyName ); CopyData( " ", 1 ); CopyStringQuotes( outputData ); CopyData( "\n", 1 ); } void CTextBuffer::WriteFloatKey( const char *pKeyName, float outputData ) { char tmp[1024]; // FAIL! if ( strlen(pKeyName) > 1000 ) { Msg("Error writing collision data %s\n", pKeyName ); return; } sprintf( tmp, "\"%s\" \"%f\"\n", pKeyName, outputData ); CopyData( tmp, strlen(tmp) ); } void CTextBuffer::WriteFloatArrayKey( const char *pKeyName, const float *outputData, int count ) { char tmp[1024]; // FAIL! if ( strlen(pKeyName) > 1000 ) { Msg("Error writing collision data %s\n", pKeyName ); return; } sprintf( tmp, "\"%s\" \"", pKeyName ); for ( int i = 0; i < count; i++ ) { char buf[80]; sprintf( buf, "%f ", outputData[i] ); strcat( tmp, buf ); } strcat( tmp, "\"\n" ); CopyData( tmp, strlen(tmp) ); } void CTextBuffer::CopyStringQuotes( const char *pString ) { CopyData( "\"", 1 ); CopyData( pString, strlen(pString) ); CopyData( "\"", 1 ); } void CTextBuffer::Terminate( void ) { CopyData( "\0", 1 ); } void CTextBuffer::CopyData( const char *pData, int len ) { int offset = m_buffer.AddMultipleToTail( len ); memcpy( m_buffer.Base() + offset, pData, len ); } //----------------------------------------------------------------------------- // Purpose: Writes a glview text file containing the collision surface in question // Input : *pCollide - // *pFilename - //----------------------------------------------------------------------------- void DumpCollideToGlView( CPhysCollide *pCollide, const char *pFilename ) { if ( !pCollide ) return; Msg("Writing %s...\n", pFilename ); Vector *outVerts; int vertCount = physcollision->CreateDebugMesh( pCollide, &outVerts ); FILE *fp = fopen( pFilename, "w" ); int triCount = vertCount / 3; int vert = 0; for ( int i = 0; i < triCount; i++ ) { fprintf( fp, "3\n" ); fprintf( fp, "%6.3f %6.3f %6.3f 1 0 0\n", outVerts[vert].x, outVerts[vert].y, outVerts[vert].z ); vert++; fprintf( fp, "%6.3f %6.3f %6.3f 0 1 0\n", outVerts[vert].x, outVerts[vert].y, outVerts[vert].z ); vert++; fprintf( fp, "%6.3f %6.3f %6.3f 0 0 1\n", outVerts[vert].x, outVerts[vert].y, outVerts[vert].z ); vert++; } fclose( fp ); physcollision->DestroyDebugMesh( vertCount, outVerts ); } void DumpCollideToPHY( CPhysCollide *pCollide, CTextBuffer *text, const char *pFilename ) { Msg("Writing %s...\n", pFilename ); FILE *fp = fopen( pFilename, "wb" ); phyheader_t header; header.size = sizeof(header); header.id = 0; header.checkSum = 0; header.solidCount = 1; fwrite( &header, sizeof(header), 1, fp ); int size = physcollision->CollideSize( pCollide ); fwrite( &size, sizeof(int), 1, fp ); char *buf = (char *)malloc( size ); physcollision->CollideWrite( buf, pCollide ); fwrite( buf, size, 1, fp ); fwrite( text->GetData(), text->GetSize(), 1, fp ); fclose( fp ); free( buf ); } CPhysCollisionEntry::CPhysCollisionEntry( CPhysCollide *pCollide ) { m_pCollide = pCollide; } unsigned int CPhysCollisionEntry::GetCollisionBinarySize() { return physcollision->CollideSize( m_pCollide ); } unsigned int CPhysCollisionEntry::WriteCollisionBinary( char *pDest ) { return physcollision->CollideWrite( pDest, m_pCollide ); } void CPhysCollisionEntry::DumpCollideFileName( const char *pName, int modelIndex, CTextBuffer *pTextBuffer ) { char tmp[128]; sprintf( tmp, "%s%03d.phy", pName, modelIndex ); DumpCollideToPHY( m_pCollide, pTextBuffer, tmp ); sprintf( tmp, "%s%03d.txt", pName, modelIndex ); DumpCollideToGlView( m_pCollide, tmp ); } class CPhysCollisionEntrySolid : public CPhysCollisionEntry { public: CPhysCollisionEntrySolid( CPhysCollide *pCollide, const char *pMaterialName, float mass ); virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); private: float m_volume; float m_mass; const char *m_pMaterial; }; CPhysCollisionEntrySolid::CPhysCollisionEntrySolid( CPhysCollide *pCollide, const char *pMaterialName, float mass ) : CPhysCollisionEntry( pCollide ) { m_volume = physcollision->CollideVolume( m_pCollide ); m_mass = mass; m_pMaterial = pMaterialName; } void CPhysCollisionEntrySolid::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) { DumpCollideFileName( "collide", modelIndex, pTextBuffer ); } void CPhysCollisionEntrySolid::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) { pTextBuffer->WriteText( "solid {\n" ); pTextBuffer->WriteIntKey( "index", collideIndex ); pTextBuffer->WriteFloatKey( "mass", m_mass ); if ( m_pMaterial ) { pTextBuffer->WriteStringKey( "surfaceprop", m_pMaterial ); } if ( m_volume != 0.f ) { pTextBuffer->WriteFloatKey( "volume", m_volume ); } pTextBuffer->WriteText( "}\n" ); } class CPhysCollisionEntryStaticSolid : public CPhysCollisionEntry { public: CPhysCollisionEntryStaticSolid ( CPhysCollide *pCollide, int contentsMask ); virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); private: int m_contentsMask; }; CPhysCollisionEntryStaticSolid ::CPhysCollisionEntryStaticSolid ( CPhysCollide *pCollide, int contentsMask ) : CPhysCollisionEntry( pCollide ), m_contentsMask(contentsMask) { } void CPhysCollisionEntryStaticSolid::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) { char tmp[128]; sprintf( tmp, "static%02d", modelIndex ); DumpCollideFileName( tmp, collideIndex, pTextBuffer ); } void CPhysCollisionEntryStaticSolid::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) { pTextBuffer->WriteText( "staticsolid {\n" ); pTextBuffer->WriteIntKey( "index", collideIndex ); pTextBuffer->WriteIntKey( "contents", m_contentsMask ); pTextBuffer->WriteText( "}\n" ); } CPhysCollisionEntryStaticMesh::CPhysCollisionEntryStaticMesh( CPhysCollide *pCollide, const char *pMaterialName ) : CPhysCollisionEntry( pCollide ) { m_pMaterial = pMaterialName; } void CPhysCollisionEntryStaticMesh::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) { char tmp[128]; sprintf( tmp, "mesh%02d", modelIndex ); DumpCollideFileName( tmp, collideIndex, pTextBuffer ); } void CPhysCollisionEntryStaticMesh::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) { pTextBuffer->WriteText( "staticsolid {\n" ); pTextBuffer->WriteIntKey( "index", collideIndex ); pTextBuffer->WriteText( "}\n" ); } class CPhysCollisionEntryFluid : public CPhysCollisionEntry { public: ~CPhysCollisionEntryFluid(); CPhysCollisionEntryFluid( CPhysCollide *pCollide, const char *pSurfaceProp, float damping, const Vector &normal, float dist, int nContents ); virtual void WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); virtual void DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ); private: char *m_pSurfaceProp; float m_damping; Vector m_surfaceNormal; float m_surfaceDist; int m_contentsMask; }; CPhysCollisionEntryFluid::CPhysCollisionEntryFluid( CPhysCollide *pCollide, const char *pSurfaceProp, float damping, const Vector &normal, float dist, int nContents ) : CPhysCollisionEntry( pCollide ) { m_surfaceNormal = normal; m_surfaceDist = dist; m_pSurfaceProp = new char[strlen(pSurfaceProp)+1]; strcpy( m_pSurfaceProp, pSurfaceProp ); m_damping = damping; m_contentsMask = nContents; } CPhysCollisionEntryFluid::~CPhysCollisionEntryFluid() { delete[] m_pSurfaceProp; } void CPhysCollisionEntryFluid::DumpCollide( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) { char tmp[128]; sprintf( tmp, "water%02d", modelIndex ); DumpCollideFileName( tmp, collideIndex, pTextBuffer ); } void CPhysCollisionEntryFluid::WriteToTextBuffer( CTextBuffer *pTextBuffer, int modelIndex, int collideIndex ) { pTextBuffer->WriteText( "fluid {\n" ); pTextBuffer->WriteIntKey( "index", collideIndex ); pTextBuffer->WriteStringKey( "surfaceprop", m_pSurfaceProp ); // write out water material pTextBuffer->WriteFloatKey( "damping", m_damping ); // write out water damping pTextBuffer->WriteIntKey( "contents", m_contentsMask ); // write out water contents float array[4]; m_surfaceNormal.CopyToArray( array ); array[3] = m_surfaceDist; pTextBuffer->WriteFloatArrayKey( "surfaceplane", array, 4 ); // write out water surface plane pTextBuffer->WriteFloatArrayKey( "currentvelocity", vec3_origin.Base(), 3 ); // write out water velocity pTextBuffer->WriteText( "}\n" ); } // Get an index into the prop list of this prop (add it if necessary) static int PropIndex( CUtlVector &propList, int propIndex ) { for ( int i = 0; i < propList.Count(); i++ ) { if ( propList[i] == propIndex ) return i+1; } if ( propList.Count() < 126 ) { return propList.AddToTail( propIndex )+1; } return 0; } int RemapWorldMaterial( int materialIndexIn ) { return PropIndex( s_WorldPropList, materialIndexIn ); } typedef struct { float normal[3]; float dist; } listplane_t; static void AddListPlane( CUtlVector *list, float x, float y, float z, float d ) { listplane_t plane; plane.normal[0] = x; plane.normal[1] = y; plane.normal[2] = z; plane.dist = d; list->AddToTail( plane ); } class CPlaneList { public: CPlaneList( float shrink, float merge ); ~CPlaneList( void ); void AddConvex( CPhysConvex *pConvex ); // add the brushes to the model int AddBrushes( void ); // Adds a single brush as a convex object void ReferenceBrush( int brushnumber ); bool IsBrushReferenced( int brushnumber ); void ReferenceLeaf( int leafIndex ); bool IsLeafReferenced( int leafIndex ); int GetFirstBrushSide(); private: CPhysConvex *BuildConvexForBrush( int brushnumber, float shrink, CPhysCollide *pCollideTest, float shrinkMinimum ); public: CUtlVector m_convex; CUtlVector m_leafList; int m_contentsMask; float m_shrink; float m_merge; bool *m_brushAdded; float m_totalVolume; }; CPlaneList::CPlaneList( float shrink, float merge ) { m_shrink = shrink; m_merge = merge; m_contentsMask = MASK_SOLID; m_brushAdded = new bool[numbrushes]; memset( m_brushAdded, 0, sizeof(bool) * numbrushes ); m_totalVolume = 0; m_leafList.Purge(); } CPlaneList::~CPlaneList( void ) { delete[] m_brushAdded; } void CPlaneList::AddConvex( CPhysConvex *pConvex ) { if ( pConvex ) { m_totalVolume += physcollision->ConvexVolume( pConvex ); m_convex.AddToTail( pConvex ); } } // Adds a single brush as a convex object void CPlaneList::ReferenceBrush( int brushnumber ) { if ( !(dbrushes[brushnumber].contents & m_contentsMask) ) return; m_brushAdded[brushnumber] = true; } bool CPlaneList::IsBrushReferenced( int brushnumber ) { return m_brushAdded[brushnumber]; } CPhysConvex *CPlaneList::BuildConvexForBrush( int brushnumber, float shrink, CPhysCollide *pCollideTest, float shrinkMinimum ) { CUtlVector temp( 0, 32 ); for ( int i = 0; i < dbrushes[brushnumber].numsides; i++ ) { dbrushside_t *pside = dbrushsides + i + dbrushes[brushnumber].firstside; if ( pside->bevel ) continue; dplane_t *pplane = dplanes + pside->planenum; float shrinkThisPlane = shrink; if ( i < g_MainMap->mapbrushes[brushnumber].numsides ) { if ( !g_MainMap->mapbrushes[brushnumber].original_sides[i].visible ) { // don't shrink brush sides with no visible components. // this produces something closer to the ideal shrink than simply shrinking all planes shrinkThisPlane = 0; } } // Make sure shrinking won't swallow geometry along this axis. if ( pCollideTest && shrinkThisPlane != 0 ) { Vector start = physcollision->CollideGetExtent( pCollideTest, vec3_origin, vec3_angle, pplane->normal ); Vector end = physcollision->CollideGetExtent( pCollideTest, vec3_origin, vec3_angle, -pplane->normal ); float thick = DotProduct( (end-start), pplane->normal ); // NOTE: The object must be at least "shrinkMinimum" inches wide on each axis if ( fabs(thick) < shrinkMinimum ) { #if _DEBUG Warning("Can't shrink brush %d, plane %d (%.2f, %.2f, %.2f)\n", brushnumber, pside->planenum, pplane->normal[0], pplane->normal[1], pplane->normal[2] ); #endif shrinkThisPlane = 0; } } AddListPlane( &temp, pplane->normal[0], pplane->normal[1], pplane->normal[2], pplane->dist - shrinkThisPlane ); } return physcollision->ConvexFromPlanes( (float *)temp.Base(), temp.Count(), m_merge ); } int CPlaneList::AddBrushes( void ) { int count = 0; for ( int brushnumber = 0; brushnumber < numbrushes; brushnumber++ ) { if ( IsBrushReferenced(brushnumber) ) { CPhysConvex *pBrushConvex = NULL; if ( m_shrink != 0 ) { // Make sure shrinking won't swallow this brush. CPhysConvex *pConvex = BuildConvexForBrush( brushnumber, 0, NULL, 0 ); CPhysCollide *pUnshrunkCollide = physcollision->ConvertConvexToCollide( &pConvex, 1 ); pBrushConvex = BuildConvexForBrush( brushnumber, m_shrink, pUnshrunkCollide, m_shrink * 3 ); physcollision->DestroyCollide( pUnshrunkCollide ); } else { pBrushConvex = BuildConvexForBrush( brushnumber, m_shrink, NULL, 1.0 ); } if ( pBrushConvex ) { count++; physcollision->SetConvexGameData( pBrushConvex, brushnumber ); AddConvex( pBrushConvex ); } } } return count; } int CPlaneList::GetFirstBrushSide() { for ( int brushnumber = 0; brushnumber < numbrushes; brushnumber++ ) { if ( IsBrushReferenced(brushnumber) ) { for ( int i = 0; i < dbrushes[brushnumber].numsides; i++ ) { int sideIndex = i + dbrushes[brushnumber].firstside; dbrushside_t *pside = dbrushsides + sideIndex; if ( pside->bevel ) continue; return sideIndex; } } } return 0; } // UNDONE: Try using this kind of algorithm if we run into precision problems. // NOTE: ConvexFromPlanes will be doing a bunch of matrix inversions that can suffer // if plane normals are too close to each other... #if 0 void CPlaneList::AddBrushes( void ) { CUtlVector temp; for ( int brushnumber = 0; brushnumber < numbrushes; brushnumber++ ) { if ( IsBrushReferenced(brushnumber) ) { CUtlVector windings; for ( int i = 0; i < dbrushes[brushnumber].numsides; i++ ) { dbrushside_t *pside = dbrushsides + i + dbrushes[brushnumber].firstside; if (pside->bevel) continue; dplane_t *pplane = dplanes + pside->planenum; winding_t *w = BaseWindingForPlane( pplane->normal, pplane->dist - m_shrink ); for ( int j = 0; j < dbrushes[brushnumber].numsides && w; j++ ) { if (i == j) continue; dbrushside_t *pClipSide = dbrushsides + j + dbrushes[brushnumber].firstside; if (pClipSide->bevel) continue; dplane_t *pClipPlane = dplanes + pClipSide->planenum; ChopWindingInPlace (&w, -pClipPlane->normal, -pClipPlane->dist+m_shrink, 0); //CLIP_EPSILON); } if ( w ) { windings.AddToTail( w ); } } CUtlVector vertList; for ( int p = 0; p < windings.Count(); p++ ) { for ( int v = 0; v < windings[p]->numpoints; v++ ) { vertList.AddToTail( windings[p]->p + v ); } } CPhysConvex *pConvex = physcollision->ConvexFromVerts( vertList.Base(), vertList.Count() ); if ( pConvex ) { physcollision->SetConvexGameData( pConvex, brushnumber ); AddConvex( pConvex ); } temp.RemoveAll(); } } } #endif // If I have a list of leaves, make sure this leaf is in it. // Otherwise, process all leaves bool CPlaneList::IsLeafReferenced( int leafIndex ) { if ( !m_leafList.Count() ) return true; for ( int i = 0; i < m_leafList.Count(); i++ ) { if ( m_leafList[i] == leafIndex ) return true; } return false; } // Add a leaf to my list of interesting leaves void CPlaneList::ReferenceLeaf( int leafIndex ) { m_leafList.AddToTail( leafIndex ); } static void VisitLeaves_r( CPlaneList &planes, int node ) { if ( node < 0 ) { int leafIndex = -1 - node; if ( planes.IsLeafReferenced(leafIndex) ) { int i; // Add the solids in the "empty" leaf for ( i = 0; i < dleafs[leafIndex].numleafbrushes; i++ ) { int brushIndex = dleafbrushes[dleafs[leafIndex].firstleafbrush + i]; planes.ReferenceBrush( brushIndex ); } } } else { dnode_t *pnode = dnodes + node; VisitLeaves_r( planes, pnode->children[0] ); VisitLeaves_r( planes, pnode->children[1] ); } } //----------------------------------------------------------------------------- //----------------------------------------------------------------------------- struct waterleaf_t { Vector surfaceNormal; float surfaceDist; float minZ; bool hasSurface; int waterLeafIndex;// this is the submerged leaf int planenum; //UNDONE: REMOVE int surfaceTexInfo; // if hasSurface == true, this is the texinfo index for the water material int outsideLeafIndex;// this is the leaf on the other side of the water surface node_t *pNode; }; // returns true if newleaf should appear before currentleaf in the list static bool IsLowerLeaf( const waterleaf_t &newleaf, const waterleaf_t ¤tleaf ) { if ( newleaf.hasSurface && currentleaf.hasSurface ) { // the one with the upmost pointing z goes first if ( currentleaf.surfaceNormal.z > newleaf.surfaceNormal.z ) return false; if ( fabs(currentleaf.surfaceNormal.z - newleaf.surfaceNormal.z) < 0.01 ) { if ( newleaf.surfaceDist < currentleaf.surfaceDist ) return true; } return true; } else if ( newleaf.hasSurface ) // the leaf with a surface always goes first return true; return false; } //----------------------------------------------------------------------------- // Purpose: Water surfaces are stored in an RB tree and the tree is used to // create one-off .vmt files embedded in the .bsp for each surface so that the // water depth effect occurs on a per-water surface level. //----------------------------------------------------------------------------- struct WaterTexInfo { // The mangled new .vmt name ( materials/levelename/oldmaterial_depth_xxx ) where xxx is // the water depth (as an integer ) CUtlSymbol m_FullName; // The original .vmt name CUtlSymbol m_MaterialName; // The depth of the water this texinfo refers to int m_nWaterDepth; // The texinfo id int m_nTexInfo; // The subdivision size for the water surface // float m_SubdivSize; }; //----------------------------------------------------------------------------- // Purpose: Helper for RB tree operations ( we compare full mangled names ) // Input : src1 - // src2 - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool WaterLessFunc( WaterTexInfo const& src1, WaterTexInfo const& src2 ) { return src1.m_FullName < src2.m_FullName; } //----------------------------------------------------------------------------- // Purpose: A growable RB tree of water surfaces //----------------------------------------------------------------------------- static CUtlRBTree< WaterTexInfo, int > g_WaterTexInfos( 0, 32, WaterLessFunc ); #if 0 float GetSubdivSizeForFogVolume( int fogVolumeID ) { Assert( fogVolumeID >= 0 && fogVolumeID < g_WaterTexInfos.Count() ); return g_WaterTexInfos[fogVolumeID].m_SubdivSize; } #endif //----------------------------------------------------------------------------- // Purpose: // Input : *mapname - // *materialname - // waterdepth - // *fullname - //----------------------------------------------------------------------------- void GetWaterTextureName( char const *mapname, char const *materialname, int waterdepth, char *fullname ) { char temp[ 512 ]; // Construct the full name (prepend mapname to reduce name collisions) sprintf( temp, "maps/%s/%s_depth_%i", mapname, materialname, (int)waterdepth ); // Make sure it's lower case strlwr( temp ); strcpy( fullname, temp ); } //----------------------------------------------------------------------------- // Purpose: Called to write procedural materials in the rb tree to the embedded // pak file for this .bsp //----------------------------------------------------------------------------- void EmitWaterMaterialFile( WaterTexInfo *wti ) { char waterTextureName[512]; if ( !wti ) { return; } GetWaterTextureName( mapbase, wti->m_MaterialName.String(), ( int )wti->m_nWaterDepth, waterTextureName ); // Convert to string char szDepth[ 32 ]; sprintf( szDepth, "%i", wti->m_nWaterDepth ); CreateMaterialPatch( wti->m_MaterialName.String(), waterTextureName, "$waterdepth", szDepth, PATCH_INSERT ); } //----------------------------------------------------------------------------- // Purpose: Takes the texinfo_t referenced by the .vmt and the computed depth for the // surface and looks up or creates a texdata/texinfo for the mangled one-off water .vmt file // Input : *pBaseInfo - // depth - // Output : int //----------------------------------------------------------------------------- int FindOrCreateWaterTexInfo( texinfo_t *pBaseInfo, float depth ) { char fullname[ 512 ]; char materialname[ 512 ]; // Get the base texture/material name char const *name = TexDataStringTable_GetString( GetTexData( pBaseInfo->texdata )->nameStringTableID ); GetWaterTextureName( mapbase, name, (int)depth, fullname ); // See if we already have an entry for this depth WaterTexInfo lookup; lookup.m_FullName = fullname; int idx = g_WaterTexInfos.Find( lookup ); // If so, return the existing entry texinfo index if ( idx != g_WaterTexInfos.InvalidIndex() ) { return g_WaterTexInfos[ idx ].m_nTexInfo; } // Otherwise, fill in the rest of the data lookup.m_nWaterDepth = (int)depth; // Remember the current material name sprintf( materialname, "%s", name ); strlwr( materialname ); lookup.m_MaterialName = materialname; texinfo_t ti; // Make a copy ti = *pBaseInfo; // Create a texdata that is based on the underlying existing entry ti.texdata = FindAliasedTexData( fullname, GetTexData( pBaseInfo->texdata ) ); // Find or create a new index lookup.m_nTexInfo = FindOrCreateTexInfo( ti ); // Add the new texinfo to the RB tree idx = g_WaterTexInfos.Insert( lookup ); // Msg( "created texinfo for %s\n", lookup.m_FullName.String() ); // Go ahead and create the new vmt file. EmitWaterMaterialFile( &g_WaterTexInfos[idx] ); // Return the new texinfo return g_WaterTexInfos[ idx ].m_nTexInfo; } extern node_t *dfacenodes[MAX_MAP_FACES]; static void WriteFogVolumeIDs( dmodel_t *pModel ) { int i; // write fog volume ID to each face in this model for( i = pModel->firstface; i < pModel->firstface + pModel->numfaces; i++ ) { dface_t *pFace = &dfaces[i]; node_t *pFaceNode = dfacenodes[i]; texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; pFace->surfaceFogVolumeID = -1; if ( pFaceNode ) { if ( (pTexInfo->flags & SURF_WARP ) && pFaceNode->planenum == PLANENUM_LEAF && pFaceNode->diskId >= 0 ) { pFace->surfaceFogVolumeID = dleafs[pFaceNode->diskId].leafWaterDataID; dleafwaterdata_t *pLeafWaterData = &dleafwaterdata[pFace->surfaceFogVolumeID]; // HACKHACK: Should probably mark these faces as water bottom or "bottommaterial" faces. // HACKHACK: Use a heuristic, if it points up, it's the water top. if ( dplanes[pFace->planenum].normal.z > 0 ) { pFace->texinfo = pLeafWaterData->surfaceTexInfoID; } } else { // missed this face somehow? Assert( !(pTexInfo->flags & SURF_WARP ) ); } } } } static bool PortalCrossesWater( waterleaf_t &baseleaf, portal_t *portal ) { if ( baseleaf.hasSurface ) { int side = WindingOnPlaneSide( portal->winding, baseleaf.surfaceNormal, baseleaf.surfaceDist ); if ( side == SIDE_CROSS || side == SIDE_FRONT ) return true; } return false; } static int FindOrCreateLeafWaterData( float surfaceZ, float minZ, int surfaceTexInfoID ) { int i; for( i = 0; i < numleafwaterdata; i++ ) { dleafwaterdata_t *pLeafWaterData = &dleafwaterdata[i]; if( pLeafWaterData->surfaceZ == surfaceZ && pLeafWaterData->minZ == minZ && pLeafWaterData->surfaceTexInfoID == surfaceTexInfoID ) { return i; } } dleafwaterdata_t *pLeafWaterData = &dleafwaterdata[numleafwaterdata]; pLeafWaterData->surfaceZ = surfaceZ; pLeafWaterData->minZ = minZ; pLeafWaterData->surfaceTexInfoID = surfaceTexInfoID; numleafwaterdata++; return numleafwaterdata - 1; } // Enumerate all leaves under node with contents in contentsMask and add them to list void EnumLeaves_r( CUtlVector &list, node_t *node, int contentsMask ) { if ( node->planenum != PLANENUM_LEAF ) { EnumLeaves_r( list, node->children[0], contentsMask ); EnumLeaves_r( list, node->children[1], contentsMask ); return; } if ( !(node->contents & contentsMask) ) return; // has the contents, put it in the list list.AddToTail( node ); } // Builds a waterleaf_t for the given leaf static void BuildWaterLeaf( node_t *pLeafIn, waterleaf_t &waterLeafOut ) { waterLeafOut.pNode = pLeafIn; waterLeafOut.waterLeafIndex = pLeafIn->diskId; waterLeafOut.outsideLeafIndex = -1; waterLeafOut.hasSurface = false; waterLeafOut.surfaceDist = MAX_COORD_INTEGER; waterLeafOut.surfaceNormal.Init( 0.f, 0.f, 1.f ); waterLeafOut.planenum = -1; waterLeafOut.surfaceTexInfo = -1; waterLeafOut.minZ = MAX_COORD_INTEGER; // search the list of portals out of this leaf for one that leaves water // If you find one, this leaf has a surface, so fill out the surface data int oppositeNodeIndex = 0; for (portal_t *p = pLeafIn->portals ; p ; p = p->next[!oppositeNodeIndex]) { oppositeNodeIndex = (p->nodes[0] == pLeafIn) ? 1 : 0; // not visible, can't be the portals we're looking for... if ( !p->side ) continue; // See if this portal crosses into air node_t *pOpposite = p->nodes[oppositeNodeIndex]; if ( !(pOpposite->contents & MASK_WATER) && !(pOpposite->contents & MASK_SOLID) ) { // it does, there must be a surface here plane_t *plane = &g_MainMap->mapplanes[p->side->planenum]; if ( waterLeafOut.hasSurface ) { // Sort to find the most upward facing normal (skips sides) if ( waterLeafOut.surfaceNormal.z > plane->normal.z ) continue; if ( (waterLeafOut.surfaceNormal.z == plane->normal.z) && waterLeafOut.surfaceDist >= plane->dist ) continue; } // water surface needs to point at least somewhat up, this is // probably a map error if ( plane->normal.z <= 0 ) continue; waterLeafOut.surfaceDist = plane->dist; waterLeafOut.surfaceNormal = plane->normal; waterLeafOut.hasSurface = true; waterLeafOut.outsideLeafIndex = p->nodes[oppositeNodeIndex]->diskId; waterLeafOut.surfaceTexInfo = p->side->texinfo; } } } static void InsertSortWaterLeaf( CUtlVector &list, const waterleaf_t &leafInsert ) { // insertion sort the leaf (lowest leaves go first) // leaves that aren't actually on the surface of the water will have leaf.hasSurface == false. for ( int i = 0; i < list.Count(); i++ ) { if ( IsLowerLeaf( leafInsert, list[i] ) ) { list.InsertBefore( i, leafInsert ); return; } } // must the highest one, so stick it at the end. list.AddToTail( leafInsert ); } // Flood fill the tree, finding neighboring water volumes and connecting them to this list // Cut groups that try to cross the surface. // Mark leaves that are in a group as "visited" so they won't be chosen by subsequent fills static void Flood_FindConnectedWaterVolumes_r( CUtlVector &list, node_t *pLeaf, waterleaf_t &baseleaf, leafbitarray_t &visited ) { // already visited, or not the same water contents if ( pLeaf->diskId < 0 || visited.Get(pLeaf->diskId) || !(pLeaf->contents & (baseleaf.pNode->contents & MASK_WATER) ) ) return; int oppositeNodeIndex = 0; for (portal_t *p = pLeaf->portals ; p ; p = p->next[!oppositeNodeIndex]) { oppositeNodeIndex = (p->nodes[0] == pLeaf) ? 1 : 0; // If any portal crosses the water surface, don't flow through this leaf if ( PortalCrossesWater( baseleaf, p ) ) return; } visited.Set( pLeaf->diskId ); list.AddToTail( pLeaf ); baseleaf.minZ = min( pLeaf->mins.z, baseleaf.minZ ); for (portal_t *p = pLeaf->portals ; p ; p = p->next[!oppositeNodeIndex]) { oppositeNodeIndex = (p->nodes[0] == pLeaf) ? 1 : 0; Flood_FindConnectedWaterVolumes_r( list, p->nodes[oppositeNodeIndex], baseleaf, visited ); } } // UNDONE: This is a bit of a hack to avoid crashing when we can't find an // appropriate texinfo for a water model (to get physics properties) int FirstWaterTexinfo( bspbrush_t *brushlist, int contents ) { while (brushlist) { if ( brushlist->original->contents & contents ) { for ( int i = 0; i < brushlist->original->numsides; i++ ) { if ( brushlist->original->original_sides[i].contents & contents ) { return brushlist->original->original_sides[i].texinfo; } } } brushlist = brushlist->next; } Assert(0); return 0; } // This is a list of water data that will be turned into physics models struct watermodel_t { int modelIndex; int contents; waterleaf_t waterLeafData; int depthTexinfo; int firstWaterLeafIndex; int waterLeafCount; int fogVolumeIndex; }; static CUtlVector g_WaterModels; static CUtlVector g_WaterLeafList; // Creates a list of watermodel_t for later processing by EmitPhysCollision void EmitWaterVolumesForBSP( dmodel_t *pModel, node_t *node ) { CUtlVector leafListAnyWater; // build the list of all leaves containing water EnumLeaves_r( leafListAnyWater, node, MASK_WATER ); // make a sorted list to flood fill CUtlVector list; int i; for ( i = 0; i < leafListAnyWater.Count(); i++ ) { waterleaf_t waterLeaf; BuildWaterLeaf( leafListAnyWater[i], waterLeaf ); InsertSortWaterLeaf( list, waterLeaf ); } leafbitarray_t visited; CUtlVector waterAreaList; for ( i = 0; i < list.Count(); i++ ) { Flood_FindConnectedWaterVolumes_r( waterAreaList, list[i].pNode, list[i], visited ); // did we find a list of leaves connected to this one? // remember the list is sorted, so this one may have been attached to a previous // leaf. So it could have nothing hanging off of it. if ( waterAreaList.Count() ) { // yes, emit a watermodel watermodel_t tmp; tmp.modelIndex = nummodels; tmp.contents = list[i].pNode->contents; tmp.waterLeafData = list[i]; tmp.firstWaterLeafIndex = g_WaterLeafList.Count(); tmp.waterLeafCount = waterAreaList.Count(); float waterDepth = tmp.waterLeafData.surfaceDist - tmp.waterLeafData.minZ; if ( tmp.waterLeafData.surfaceTexInfo < 0 ) { // the map has probably leaked in this case, but output something anyway. Assert(list[i].pNode->planenum == PLANENUM_LEAF); tmp.waterLeafData.surfaceTexInfo = FirstWaterTexinfo( list[i].pNode->brushlist, tmp.contents ); } tmp.depthTexinfo = FindOrCreateWaterTexInfo( &texinfo[ tmp.waterLeafData.surfaceTexInfo ], waterDepth ); tmp.fogVolumeIndex = FindOrCreateLeafWaterData( tmp.waterLeafData.surfaceDist, tmp.waterLeafData.minZ, tmp.waterLeafData.surfaceTexInfo ); for ( int j = 0; j < waterAreaList.Count(); j++ ) { g_WaterLeafList.AddToTail( waterAreaList[j]->diskId ); } waterAreaList.RemoveAll(); g_WaterModels.AddToTail( tmp ); } } WriteFogVolumeIDs( pModel ); } static void ConvertWaterModelToPhysCollide( CUtlVector &collisionList, int modelIndex, float shrinkSize, float mergeTolerance ) { dmodel_t *pModel = dmodels + modelIndex; for ( int i = 0; i < g_WaterModels.Count(); i++ ) { watermodel_t &waterModel = g_WaterModels[i]; if ( waterModel.modelIndex != modelIndex ) continue; CPlaneList planes( shrinkSize, mergeTolerance ); int firstLeaf = waterModel.firstWaterLeafIndex; planes.m_contentsMask = waterModel.contents; // push all of the leaves into the collision list for ( int j = 0; j < waterModel.waterLeafCount; j++ ) { int leafIndex = g_WaterLeafList[firstLeaf + j]; dleaf_t *pLeaf = dleafs + leafIndex; // fixup waterdata pLeaf->leafWaterDataID = waterModel.fogVolumeIndex; planes.ReferenceLeaf( leafIndex ); } // visit the referenced leaves that belong to this model VisitLeaves_r( planes, pModel->headnode ); // Now add the brushes from those leaves as convex // BUGBUG: NOTE: If your map has a brush that crosses the surface, it will be added to two water // volumes. This only happens with connected water volumes with multiple surface heights // UNDONE: Right now map makers must cut such brushes. It could be automatically cut by adding the // surface plane to the list for each brush before calling ConvexFromPlanes() planes.AddBrushes(); int count = planes.m_convex.Count(); if ( !count ) continue; // Save off the plane of the surface for this group as well as the collision model // for all convex objects in the group. CPhysCollide *pCollide = physcollision->ConvertConvexToCollide( planes.m_convex.Base(), count ); if ( pCollide ) { int waterSurfaceTexInfoID = -1; // use defaults const char *pSurfaceProp = "water"; float damping = 0.01; if ( waterSurfaceTexInfoID >= 0 ) { // material override int texdata = texinfo[waterSurfaceTexInfoID].texdata; int prop = g_SurfaceProperties[texdata]; pSurfaceProp = physprops->GetPropName( prop ); } if ( !waterModel.waterLeafData.hasSurface ) { waterModel.waterLeafData.surfaceNormal.Init( 0,0,1 ); Vector top = physcollision->CollideGetExtent( pCollide, vec3_origin, vec3_angle, waterModel.waterLeafData.surfaceNormal ); waterModel.waterLeafData.surfaceDist = top.z; } CPhysCollisionEntryFluid *pCollisionEntryFuild = new CPhysCollisionEntryFluid( pCollide, pSurfaceProp, damping, waterModel.waterLeafData.surfaceNormal, waterModel.waterLeafData.surfaceDist, waterModel.contents ); collisionList.AddToTail( pCollisionEntryFuild ); } } } // compute a normal for a triangle of the given three points (points are clockwise, normal points out) static Vector TriangleNormal( const Vector &p0, const Vector &p1, const Vector &p2 ) { Vector e0 = p1 - p0; Vector e1 = p2 - p0; Vector normal = CrossProduct( e1, e0 ); VectorNormalize( normal ); return normal; } // find the side of the brush with the normal closest to the given normal static dbrushside_t *FindBrushSide( int brushIndex, const Vector &normal ) { dbrush_t *pbrush = &dbrushes[brushIndex]; dbrushside_t *out = NULL; float best = -1.f; for ( int i = 0; i < pbrush->numsides; i++ ) { dbrushside_t *pside = dbrushsides + i + pbrush->firstside; dplane_t *pplane = dplanes + pside->planenum; float dot = DotProduct( normal, pplane->normal ); if ( dot > best ) { best = dot; out = pside; } } return out; } static void ConvertWorldBrushesToPhysCollide( CUtlVector &collisionList, float shrinkSize, float mergeTolerance, int contentsMask ) { CPlaneList planes( shrinkSize, mergeTolerance ); planes.m_contentsMask = contentsMask; VisitLeaves_r( planes, dmodels[0].headnode ); planes.AddBrushes(); int count = planes.m_convex.Count(); if ( count ) { CPhysCollide *pCollide = physcollision->ConvertConvexToCollide( planes.m_convex.Base(), count ); ICollisionQuery *pQuery = physcollision->CreateQueryModel( pCollide ); int convex = pQuery->ConvexCount(); for ( int i = 0; i < convex; i++ ) { int triCount = pQuery->TriangleCount( i ); int brushIndex = pQuery->GetGameData( i ); Vector points[3]; for ( int j = 0; j < triCount; j++ ) { pQuery->GetTriangleVerts( i, j, points ); Vector normal = TriangleNormal( points[0], points[1], points[2] ); dbrushside_t *pside = FindBrushSide( brushIndex, normal ); if ( pside->texinfo != TEXINFO_NODE ) { int prop = g_SurfaceProperties[texinfo[pside->texinfo].texdata]; pQuery->SetTriangleMaterialIndex( i, j, RemapWorldMaterial( prop ) ); } } } physcollision->DestroyQueryModel( pQuery ); pQuery = NULL; collisionList.AddToTail( new CPhysCollisionEntryStaticSolid( pCollide, contentsMask ) ); } } // adds any world, terrain, and water collision models to the collision list static void BuildWorldPhysModel( CUtlVector &collisionList, float shrinkSize, float mergeTolerance ) { ConvertWorldBrushesToPhysCollide( collisionList, shrinkSize, mergeTolerance, MASK_SOLID ); ConvertWorldBrushesToPhysCollide( collisionList, shrinkSize, mergeTolerance, CONTENTS_PLAYERCLIP ); ConvertWorldBrushesToPhysCollide( collisionList, shrinkSize, mergeTolerance, CONTENTS_MONSTERCLIP ); if ( !g_bNoVirtualMesh && Disp_HasPower4Displacements() ) { Warning("WARNING: Map using power 4 displacements, terrain physics cannot be compressed, map will need additional memory and CPU.\n"); g_bNoVirtualMesh = true; } // if there's terrain, save it off as a static mesh/polysoup if ( g_bNoVirtualMesh || !physcollision->SupportsVirtualMesh() ) { Disp_AddCollisionModels( collisionList, &dmodels[0], MASK_SOLID ); } else { Disp_BuildVirtualMesh( MASK_SOLID ); } ConvertWaterModelToPhysCollide( collisionList, 0, shrinkSize, mergeTolerance ); } // adds a collision entry for this brush model static void ConvertModelToPhysCollide( CUtlVector &collisionList, int modelIndex, int contents, float shrinkSize, float mergeTolerance ) { int i; CPlaneList planes( shrinkSize, mergeTolerance ); planes.m_contentsMask = contents; dmodel_t *pModel = dmodels + modelIndex; VisitLeaves_r( planes, pModel->headnode ); planes.AddBrushes(); int count = planes.m_convex.Count(); convertconvexparams_t params; params.Defaults(); params.buildOuterConvexHull = count > 1 ? true : false; params.buildDragAxisAreas = true; Vector size = pModel->maxs - pModel->mins; float minSurfaceArea = -1.0f; for ( i = 0; i < 3; i++ ) { int other = (i+1)%3; int cross = (i+2)%3; float surfaceArea = size[other] * size[cross]; if ( minSurfaceArea < 0 || surfaceArea < minSurfaceArea ) { minSurfaceArea = surfaceArea; } } // this can be really slow with super-large models and a low error tolerance // Basically you get a ray cast through each square of epsilon surface area on each OBB side // So compute it for 1% error (on the smallest side, less on larger sides) params.dragAreaEpsilon = clamp( minSurfaceArea * 1e-2f, 1.0f, 1024.0f ); CPhysCollide *pCollide = physcollision->ConvertConvexToCollideParams( planes.m_convex.Base(), count, params ); if ( !pCollide ) return; struct { int prop; float area; } proplist[256]; int numprops = 1; proplist[0].prop = -1; proplist[0].area = 1; // compute the array of props on the surface of this model // NODRAW brushes no longer have any faces if ( !dmodels[modelIndex].numfaces ) { int sideIndex = planes.GetFirstBrushSide(); int texdata = texinfo[dbrushsides[sideIndex].texinfo].texdata; int prop = g_SurfaceProperties[texdata]; proplist[numprops].prop = prop; proplist[numprops].area = 2; numprops++; } for ( i = 0; i < dmodels[modelIndex].numfaces; i++ ) { dface_t *face = dfaces + i + dmodels[modelIndex].firstface; int texdata = texinfo[face->texinfo].texdata; int prop = g_SurfaceProperties[texdata]; int j; for ( j = 0; j < numprops; j++ ) { if ( proplist[j].prop == prop ) { proplist[j].area += face->area; break; } } if ( (!numprops || j >= numprops) && numprops < ARRAYSIZE(proplist) ) { proplist[numprops].prop = prop; proplist[numprops].area = face->area; numprops++; } } // choose the prop with the most surface area int maxIndex = -1; float maxArea = 0; float totalArea = 0; for ( i = 0; i < numprops; i++ ) { if ( proplist[i].area > maxArea ) { maxIndex = i; maxArea = proplist[i].area; } // add up the total surface area totalArea += proplist[i].area; } float mass = 1.0f; const char *pMaterial = "default"; if ( maxIndex >= 0 ) { int prop = proplist[maxIndex].prop; // use default if this material has no prop if ( prop < 0 ) prop = 0; pMaterial = physprops->GetPropName( prop ); float density, thickness; physprops->GetPhysicsProperties( prop, &density, &thickness, NULL, NULL ); // if this is a "shell" material (it is hollow and encloses some empty space) // compute the mass with a constant surface thickness if ( thickness != 0 ) { mass = totalArea * thickness * density * CUBIC_METERS_PER_CUBIC_INCH; } else { // material is completely solid, compute total mass as if constant density throughout. mass = planes.m_totalVolume * density * CUBIC_METERS_PER_CUBIC_INCH; } } // Clamp mass to 100,000 kg if ( mass > VPHYSICS_MAX_MASS ) { mass = VPHYSICS_MAX_MASS; } collisionList.AddToTail( new CPhysCollisionEntrySolid( pCollide, pMaterial, mass ) ); } static void ClearLeafWaterData( void ) { int i; for( i = 0; i < numleafs; i++ ) { dleafs[i].leafWaterDataID = -1; dleafs[i].contents &= ~CONTENTS_TESTFOGVOLUME; } } // This is the only public entry to this file. // The global data touched in the file is: // from bsplib.h: // g_pPhysCollide : This is an output from this file. // g_PhysCollideSize : This is set in this file. // g_numdispinfo : This is an input to this file. // g_dispinfo : This is an input to this file. // numnodewaterdata : This is an output from this file. // dleafwaterdata : This is an output from this file. // from vbsp.h: // g_SurfaceProperties : This is an input to this file. void EmitPhysCollision() { ClearLeafWaterData(); CreateInterfaceFn physicsFactory = GetPhysicsFactory(); if ( physicsFactory ) { physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); } if ( !physcollision ) { Warning("!!! WARNING: Can't build collision data!\n" ); return; } CUtlVector collisionList[MAX_MAP_MODELS]; CTextBuffer *pTextBuffer[MAX_MAP_MODELS]; int physModelCount = 0, totalSize = 0; int start = Plat_FloatTime(); Msg("Building Physics collision data...\n" ); int i, j; for ( i = 0; i < nummodels; i++ ) { // Build a list of collision models for this brush model section if ( i == 0 ) { // world is the only model that processes water separately. // other brushes are assumed to be completely solid or completely liquid BuildWorldPhysModel( collisionList[i], NO_SHRINK, VPHYSICS_MERGE); } else { ConvertModelToPhysCollide( collisionList[i], i, MASK_SOLID|CONTENTS_PLAYERCLIP|CONTENTS_MONSTERCLIP|MASK_WATER, VPHYSICS_SHRINK, VPHYSICS_MERGE ); } pTextBuffer[i] = NULL; if ( !collisionList[i].Count() ) continue; // if we've got collision models, write their script for processing in the game pTextBuffer[i] = new CTextBuffer; for ( j = 0; j < collisionList[i].Count(); j++ ) { // dump a text file for visualization if ( dumpcollide ) { collisionList[i][j]->DumpCollide( pTextBuffer[i], i, j ); } // each model knows how to write its script collisionList[i][j]->WriteToTextBuffer( pTextBuffer[i], i, j ); // total up the binary section's size totalSize += collisionList[i][j]->GetCollisionBinarySize() + sizeof(int); } // These sections only appear in the world's collision text if ( i == 0 ) { if ( !g_bNoVirtualMesh && physcollision->SupportsVirtualMesh() ) { pTextBuffer[i]->WriteText("virtualterrain {}\n"); } if ( s_WorldPropList.Count() ) { pTextBuffer[i]->WriteText( "materialtable {\n" ); for ( j = 0; j < s_WorldPropList.Count(); j++ ) { int propIndex = s_WorldPropList[j]; if ( propIndex < 0 ) { pTextBuffer[i]->WriteIntKey( "default", j+1 ); } else { pTextBuffer[i]->WriteIntKey( physprops->GetPropName( propIndex ), j+1 ); } } pTextBuffer[i]->WriteText( "}\n" ); } } pTextBuffer[i]->Terminate(); // total lump size includes the text buffers (scripts) totalSize += pTextBuffer[i]->GetSize(); physModelCount++; } // add one for tail of list marker physModelCount++; // DWORD align the lump because AddLump assumes that it is DWORD aligned. byte *ptr ; g_PhysCollideSize = totalSize + (physModelCount * sizeof(dphysmodel_t)); g_pPhysCollide = (byte *)malloc(( g_PhysCollideSize + 3 ) & ~3 ); memset( g_pPhysCollide, 0, g_PhysCollideSize ); ptr = g_pPhysCollide; for ( i = 0; i < nummodels; i++ ) { if ( pTextBuffer[i] ) { int j; dphysmodel_t model; model.modelIndex = i; model.solidCount = collisionList[i].Count(); model.dataSize = sizeof(int) * model.solidCount; for ( j = 0; j < model.solidCount; j++ ) { model.dataSize += collisionList[i][j]->GetCollisionBinarySize(); } model.keydataSize = pTextBuffer[i]->GetSize(); // store the header memcpy( ptr, &model, sizeof(model) ); ptr += sizeof(model); for ( j = 0; j < model.solidCount; j++ ) { int collideSize = collisionList[i][j]->GetCollisionBinarySize(); // write size memcpy( ptr, &collideSize, sizeof(int) ); ptr += sizeof(int); // now write the collision model collisionList[i][j]->WriteCollisionBinary( reinterpret_cast(ptr) ); ptr += collideSize; } memcpy( ptr, pTextBuffer[i]->GetData(), pTextBuffer[i]->GetSize() ); ptr += pTextBuffer[i]->GetSize(); } delete pTextBuffer[i]; } dphysmodel_t model; // Mark end of list model.modelIndex = -1; model.dataSize = -1; model.keydataSize = 0; model.solidCount = 0; memcpy( ptr, &model, sizeof(model) ); ptr += sizeof(model); Assert( (ptr-g_pPhysCollide) == g_PhysCollideSize); Msg("done (%d) (%d bytes)\n", (int)(Plat_FloatTime() - start), g_PhysCollideSize ); // UNDONE: Collision models (collisionList) memory leak! }