Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2324 lines
69 KiB

  1. //===== Copyright � 1996-2005, Valve Corporation, All rights reserved. ======//
  2. //
  3. // Purpose: Places "detail" objects which are client-only renderable things
  4. //
  5. // $Revision: $
  6. // $NoKeywords: $
  7. //===========================================================================//
  8. #include "vbsp.h"
  9. #include "bsplib.h"
  10. #include "UtlVector.h"
  11. #include "bspfile.h"
  12. #include "gamebspfile.h"
  13. #include "VPhysics_Interface.h"
  14. #include "Studio.h"
  15. #include "byteswap.h"
  16. #include "UtlBuffer.h"
  17. #include "CollisionUtils.h"
  18. #include <float.h>
  19. #include "CModel.h"
  20. #include "PhysDll.h"
  21. #include "UtlSymbol.h"
  22. #include "tier1/strtools.h"
  23. #include "keyvalues.h"
  24. #include "map.h"
  25. #include "tier3/tier3.h"
  26. #include "phyfile.h"
  27. #include "characterset.h"
  28. #include "utlstring.h"
  29. #ifdef IS_WINDOWS_PC
  30. #include "winlite.h"
  31. #endif
  32. #define STATIC_PROP_COMBINE_ENABLED
  33. static void SetCurrentModel( studiohdr_t *pStudioHdr );
  34. static void FreeCurrentModelVertexes();
  35. IPhysicsCollision *s_pPhysCollision = NULL;
  36. //-----------------------------------------------------------------------------
  37. // These puppies are used to construct the game lumps
  38. //-----------------------------------------------------------------------------
  39. static CUtlVector<StaticPropDictLump_t> s_StaticPropDictLump;
  40. static CUtlVector<StaticPropLump_t> s_StaticPropLump;
  41. static CUtlVector<StaticPropLeafLump_t> s_StaticPropLeafLump;
  42. //-----------------------------------------------------------------------------
  43. // Used to build the static prop
  44. //-----------------------------------------------------------------------------
  45. struct StaticPropBuild_t
  46. {
  47. char const* m_pModelName;
  48. char const* m_pLightingOrigin;
  49. Vector m_Origin;
  50. QAngle m_Angles;
  51. int m_Solid;
  52. int m_Skin;
  53. int m_Flags;
  54. int m_FlagsEx;
  55. float m_FadeMinDist;
  56. float m_FadeMaxDist;
  57. bool m_FadesOut;
  58. float m_flForcedFadeScale;
  59. unsigned char m_nMinCPULevel;
  60. unsigned char m_nMaxCPULevel;
  61. unsigned char m_nMinGPULevel;
  62. unsigned char m_nMaxGPULevel;
  63. color32 m_DiffuseModulation;
  64. bool m_bCombineDataWritten;
  65. int m_nPhysicsHullCount;
  66. CUtlString m_szRefName;
  67. CUtlString m_szPhyName;
  68. int m_nHulls;
  69. bool m_bConcave;
  70. float m_flScale;
  71. int m_nCombineRuleGroup;
  72. bool m_bUpaxisY;
  73. };
  74. //-----------------------------------------------------------------------------
  75. // Used to cache collision model generation
  76. //-----------------------------------------------------------------------------
  77. struct ModelCollisionLookup_t
  78. {
  79. CUtlSymbol m_Name;
  80. CPhysCollide* m_pCollide;
  81. };
  82. static bool ModelLess( ModelCollisionLookup_t const& src1, ModelCollisionLookup_t const& src2 )
  83. {
  84. return src1.m_Name < src2.m_Name;
  85. }
  86. static CUtlRBTree<ModelCollisionLookup_t, unsigned short> s_ModelCollisionCache( 0, 32, ModelLess );
  87. static CUtlVector<int> s_LightingInfo;
  88. //-----------------------------------------------------------------------------
  89. // Gets the keyvalues from a studiohdr
  90. //-----------------------------------------------------------------------------
  91. bool StudioKeyValues( studiohdr_t* pStudioHdr, KeyValues *pValue )
  92. {
  93. if ( !pStudioHdr )
  94. return false;
  95. return pValue->LoadFromBuffer( pStudioHdr->pszName(), pStudioHdr->KeyValueText() );
  96. }
  97. //-----------------------------------------------------------------------------
  98. // Makes sure the studio model is a static prop
  99. //-----------------------------------------------------------------------------
  100. enum isstaticprop_ret
  101. {
  102. RET_VALID,
  103. RET_FAIL_NOT_MARKED_STATIC_PROP,
  104. RET_FAIL_DYNAMIC,
  105. };
  106. isstaticprop_ret IsStaticProp( studiohdr_t* pHdr )
  107. {
  108. if (!(pHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP))
  109. return RET_FAIL_NOT_MARKED_STATIC_PROP;
  110. // If it's got a propdata section in the model's keyvalues, it's not allowed to be a prop_static
  111. KeyValues *modelKeyValues = new KeyValues(pHdr->pszName());
  112. if ( StudioKeyValues( pHdr, modelKeyValues ) )
  113. {
  114. KeyValues *sub = modelKeyValues->FindKey("prop_data");
  115. if ( sub )
  116. {
  117. if ( !(sub->GetInt( "allowstatic", 0 )) )
  118. {
  119. modelKeyValues->deleteThis();
  120. return RET_FAIL_DYNAMIC;
  121. }
  122. }
  123. }
  124. modelKeyValues->deleteThis();
  125. return RET_VALID;
  126. }
  127. //-----------------------------------------------------------------------------
  128. // Add static prop model to the list of models
  129. //-----------------------------------------------------------------------------
  130. static int AddStaticPropDictLump( char const* pModelName )
  131. {
  132. StaticPropDictLump_t dictLump;
  133. strncpy( dictLump.m_Name, pModelName, DETAIL_NAME_LENGTH );
  134. for (int i = s_StaticPropDictLump.Count(); --i >= 0; )
  135. {
  136. if (!memcmp(&s_StaticPropDictLump[i], &dictLump, sizeof(dictLump) ))
  137. return i;
  138. }
  139. return s_StaticPropDictLump.AddToTail( dictLump );
  140. }
  141. //-----------------------------------------------------------------------------
  142. // Load studio model vertex data from a file...
  143. //-----------------------------------------------------------------------------
  144. bool LoadStudioModel( char const* pModelName, char const* pEntityType, CUtlBuffer& buf )
  145. {
  146. if ( !GetMapDataFilesMgr()->ReadRegisteredFile( pModelName, buf ) &&
  147. !g_pFullFileSystem->ReadFile( pModelName, NULL, buf ) )
  148. {
  149. if ( V_stristr( pModelName, "_autocombine_" ) && FileExistsInPak( GetPakFile(), pModelName ) )
  150. {
  151. if ( !ReadFileFromPak( GetPakFile(), pModelName, false, buf ) )
  152. return false;
  153. }
  154. else
  155. {
  156. return false;
  157. }
  158. }
  159. // Check that it's valid
  160. if (strncmp ((const char *) buf.PeekGet(), "IDST", 4) &&
  161. strncmp ((const char *) buf.PeekGet(), "IDAG", 4))
  162. {
  163. return false;
  164. }
  165. studiohdr_t* pHdr = (studiohdr_t*)buf.PeekGet();
  166. Studio_ConvertStudioHdrToNewVersion( pHdr );
  167. if (pHdr->version != STUDIO_VERSION)
  168. {
  169. return false;
  170. }
  171. isstaticprop_ret isStaticProp = IsStaticProp(pHdr);
  172. if ( isStaticProp != RET_VALID )
  173. {
  174. if ( isStaticProp == RET_FAIL_NOT_MARKED_STATIC_PROP )
  175. {
  176. Warning("Error! To use model \"%s\"\n"
  177. " with %s, it must be compiled with $staticprop!\n", pModelName, pEntityType );
  178. }
  179. else if ( isStaticProp == RET_FAIL_DYNAMIC )
  180. {
  181. Warning("Error! %s using model \"%s\", which must be used on a dynamic entity (i.e. prop_physics). Deleted.\n", pEntityType, pModelName );
  182. }
  183. return false;
  184. }
  185. // ensure reset
  186. pHdr->SetVertexBase( NULL );
  187. pHdr->SetIndexBase( NULL );
  188. return true;
  189. }
  190. //-----------------------------------------------------------------------------
  191. // Computes a convex hull from a studio mesh
  192. //-----------------------------------------------------------------------------
  193. static CPhysConvex* ComputeConvexHull( studiohdr_t* pStudioHdr, mstudiomesh_t* pMesh )
  194. {
  195. // Generate a list of all verts in the mesh
  196. CUtlVector<Vector> vertCopy;
  197. CUtlVector<Vector *> ppVerts;
  198. vertCopy.EnsureCount(pMesh->numvertices);
  199. ppVerts.EnsureCount(pMesh->numvertices);
  200. const mstudio_meshvertexdata_t *vertData = pMesh->GetVertexData( (void *)pStudioHdr );
  201. Assert( vertData ); // This can only return NULL on X360 for now
  202. for (int i = 0; i < pMesh->numvertices; ++i)
  203. {
  204. vertCopy[i] = *vertData->Position(i);
  205. // quantize these so that really curved/detailed models don't take forever
  206. vertCopy[i].x = float( RoundFloatToInt(vertCopy[i].x) );
  207. vertCopy[i].y = float( RoundFloatToInt(vertCopy[i].y) );
  208. vertCopy[i].z = float( RoundFloatToInt(vertCopy[i].z) );
  209. ppVerts[i] = &vertCopy[i];
  210. }
  211. // Generate a convex hull from the verts
  212. return s_pPhysCollision->ConvexFromVerts( ppVerts.Base(), pMesh->numvertices );
  213. }
  214. //-----------------------------------------------------------------------------
  215. // Computes a convex hull from the studio model
  216. //-----------------------------------------------------------------------------
  217. CPhysCollide* ComputeConvexHull( studiohdr_t* pStudioHdr )
  218. {
  219. CUtlVector<CPhysConvex*> convexHulls;
  220. for (int body = 0; body < pStudioHdr->numbodyparts; ++body )
  221. {
  222. mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( body );
  223. for( int model = 0; model < pBodyPart->nummodels; ++model )
  224. {
  225. mstudiomodel_t *pStudioModel = pBodyPart->pModel( model );
  226. for( int mesh = 0; mesh < pStudioModel->nummeshes; ++mesh )
  227. {
  228. // Make a convex hull for each mesh
  229. // NOTE: This won't work unless the model has been compiled
  230. // with $staticprop
  231. mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( mesh );
  232. CPhysConvex *pConvex = ComputeConvexHull( pStudioHdr, pStudioMesh );
  233. if ( !pConvex )
  234. {
  235. Warning("Can't create hull for mesh %d/%d of model %s\n", mesh, model, pStudioHdr->name );
  236. }
  237. else
  238. {
  239. convexHulls.AddToTail( pConvex );
  240. }
  241. }
  242. }
  243. }
  244. // Convert an array of convex elements to a compiled collision model
  245. // (this deletes the convex elements)
  246. return s_pPhysCollision->ConvertConvexToCollide( convexHulls.Base(), convexHulls.Count() );
  247. }
  248. //-----------------------------------------------------------------------------
  249. // Add, find collision model in cache
  250. //-----------------------------------------------------------------------------
  251. static CPhysCollide* GetCollisionModel( char const* pModelName )
  252. {
  253. // Convert to a common string
  254. char* pTemp = (char*)_alloca(strlen(pModelName) + 1);
  255. strcpy( pTemp, pModelName );
  256. _strlwr( pTemp );
  257. char* pSlash = strchr( pTemp, '\\' );
  258. while( pSlash )
  259. {
  260. *pSlash = '/';
  261. pSlash = strchr( pTemp, '\\' );
  262. }
  263. // Find it in the cache
  264. ModelCollisionLookup_t lookup;
  265. lookup.m_Name = pTemp;
  266. int i = s_ModelCollisionCache.Find( lookup );
  267. if (i != s_ModelCollisionCache.InvalidIndex())
  268. return s_ModelCollisionCache[i].m_pCollide;
  269. // Load the studio model file
  270. CUtlBuffer buf;
  271. if (!LoadStudioModel(pModelName, "prop_static", buf))
  272. {
  273. Warning("Error loading studio model \"%s\"!\n", pModelName );
  274. // This way we don't try to load it multiple times
  275. lookup.m_pCollide = 0;
  276. s_ModelCollisionCache.Insert( lookup );
  277. return 0;
  278. }
  279. // Compute the convex hull of the model...
  280. studiohdr_t* pStudioHdr = (studiohdr_t*)buf.PeekGet();
  281. // necessary for vertex access
  282. SetCurrentModel( pStudioHdr );
  283. lookup.m_pCollide = ComputeConvexHull( pStudioHdr );
  284. s_ModelCollisionCache.Insert( lookup );
  285. if ( !lookup.m_pCollide )
  286. {
  287. Warning("Bad geometry on \"%s\"!\n", pModelName );
  288. }
  289. // Debugging
  290. if (g_DumpStaticProps)
  291. {
  292. static int propNum = 0;
  293. char tmp[128];
  294. sprintf( tmp, "staticprop%03d.txt", propNum );
  295. DumpCollideToGlView( lookup.m_pCollide, tmp );
  296. ++propNum;
  297. }
  298. FreeCurrentModelVertexes();
  299. // Insert into cache...
  300. return lookup.m_pCollide;
  301. }
  302. //-----------------------------------------------------------------------------
  303. // Tests a single leaf against the static prop
  304. //-----------------------------------------------------------------------------
  305. static bool TestLeafAgainstCollide( int depth, int* pNodeList,
  306. Vector const& origin, QAngle const& angles, CPhysCollide* pCollide )
  307. {
  308. // Copy the planes in the node list into a list of planes
  309. float* pPlanes = (float*)_alloca(depth * 4 * sizeof(float) );
  310. int idx = 0;
  311. for (int i = depth; --i >= 0; ++idx )
  312. {
  313. int sign = (pNodeList[i] < 0) ? -1 : 1;
  314. int node = (sign < 0) ? - pNodeList[i] - 1 : pNodeList[i];
  315. dnode_t* pNode = &dnodes[node];
  316. dplane_t* pPlane = &dplanes[pNode->planenum];
  317. pPlanes[idx*4] = sign * pPlane->normal[0];
  318. pPlanes[idx*4+1] = sign * pPlane->normal[1];
  319. pPlanes[idx*4+2] = sign * pPlane->normal[2];
  320. pPlanes[idx*4+3] = sign * pPlane->dist;
  321. }
  322. // Make a convex solid out of the planes
  323. CPhysConvex* pPhysConvex = s_pPhysCollision->ConvexFromPlanes( pPlanes, depth, 0.0f );
  324. // This should never happen, but if it does, return no collision
  325. Assert( pPhysConvex );
  326. if (!pPhysConvex)
  327. return false;
  328. CPhysCollide* pLeafCollide = s_pPhysCollision->ConvertConvexToCollide( &pPhysConvex, 1 );
  329. // Collide the leaf solid with the static prop solid
  330. trace_t tr;
  331. s_pPhysCollision->TraceCollide( vec3_origin, vec3_origin, pLeafCollide, vec3_angle,
  332. pCollide, origin, angles, &tr );
  333. s_pPhysCollision->DestroyCollide( pLeafCollide );
  334. return (tr.startsolid != 0);
  335. }
  336. //-----------------------------------------------------------------------------
  337. // Find all leaves that intersect with this bbox + test against the static prop..
  338. //-----------------------------------------------------------------------------
  339. static void ComputeConvexHullLeaves_R( int node, int depth, int* pNodeList,
  340. Vector const& mins, Vector const& maxs,
  341. Vector const& origin, QAngle const& angles, CPhysCollide* pCollide, bool bSkipTrace,
  342. CUtlVector<unsigned short>& leafList )
  343. {
  344. Assert( pNodeList && pCollide );
  345. Vector cornermin, cornermax;
  346. while( node >= 0 )
  347. {
  348. dnode_t* pNode = &dnodes[node];
  349. dplane_t* pPlane = &dplanes[pNode->planenum];
  350. // Arbitrary split plane here
  351. for (int i = 0; i < 3; ++i)
  352. {
  353. if (pPlane->normal[i] >= 0)
  354. {
  355. cornermin[i] = mins[i];
  356. cornermax[i] = maxs[i];
  357. }
  358. else
  359. {
  360. cornermin[i] = maxs[i];
  361. cornermax[i] = mins[i];
  362. }
  363. }
  364. if (DotProduct( pPlane->normal, cornermax ) <= pPlane->dist)
  365. {
  366. // Add the node to the list of nodes
  367. pNodeList[depth] = node;
  368. ++depth;
  369. node = pNode->children[1];
  370. }
  371. else if (DotProduct( pPlane->normal, cornermin ) >= pPlane->dist)
  372. {
  373. // In this case, we are going in front of the plane. That means that
  374. // this plane must have an outward normal facing in the oppisite direction
  375. // We indicate this be storing a negative node index in the node list
  376. pNodeList[depth] = - node - 1;
  377. ++depth;
  378. node = pNode->children[0];
  379. }
  380. else
  381. {
  382. // Here the box is split by the node. First, we'll add the plane as if its
  383. // outward facing normal is in the direction of the node plane, then
  384. // we'll have to reverse it for the other child...
  385. pNodeList[depth] = node;
  386. ++depth;
  387. ComputeConvexHullLeaves_R( pNode->children[1], depth, pNodeList, mins, maxs, origin, angles, pCollide, bSkipTrace, leafList );
  388. pNodeList[depth - 1] = - node - 1;
  389. ComputeConvexHullLeaves_R( pNode->children[0], depth, pNodeList, mins, maxs, origin, angles, pCollide, bSkipTrace, leafList );
  390. return;
  391. }
  392. }
  393. Assert( pNodeList && pCollide );
  394. // Never add static props to solid leaves
  395. if ( (dleafs[-node-1].contents & CONTENTS_SOLID) == 0 )
  396. {
  397. if ( bSkipTrace || TestLeafAgainstCollide( depth, pNodeList, origin, angles, pCollide ) )
  398. {
  399. leafList.AddToTail( -node - 1 );
  400. }
  401. }
  402. }
  403. //-----------------------------------------------------------------------------
  404. // Places Static Props in the level
  405. //-----------------------------------------------------------------------------
  406. static void ComputeStaticPropLeaves( CPhysCollide* pCollide, Vector const& origin, QAngle const& angles, CUtlVector<unsigned short>& leafList )
  407. {
  408. // Compute an axis-aligned bounding box for the collide
  409. Vector mins, maxs;
  410. s_pPhysCollision->CollideGetAABB( &mins, &maxs, pCollide, origin, angles );
  411. Vector vSize = maxs - mins;
  412. bool bSkipTrace = false;
  413. if ( vSize.x < 1e-2f || vSize.y < 1e-2f || vSize.z < 1e-2f )
  414. {
  415. // 2d, enlarge and skip the accurate test
  416. bSkipTrace = true;
  417. for ( int i = 0; i < 3; i++ )
  418. {
  419. if ( vSize[i] < 1e-2f )
  420. {
  421. mins[i] -= 1.0f;
  422. maxs[i] += 1.0f;
  423. }
  424. }
  425. }
  426. // Find all leaves that intersect with the bounds
  427. int tempNodeList[1024];
  428. ComputeConvexHullLeaves_R( 0, 0, tempNodeList, mins, maxs, origin, angles, pCollide, bSkipTrace, leafList );
  429. }
  430. //-----------------------------------------------------------------------------
  431. // Computes the lighting origin
  432. //-----------------------------------------------------------------------------
  433. static bool ComputeLightingOrigin( StaticPropBuild_t const& build, Vector& lightingOrigin )
  434. {
  435. for (int i = s_LightingInfo.Count(); --i >= 0; )
  436. {
  437. int entIndex = s_LightingInfo[i];
  438. // Check against all lighting info entities
  439. char const* pTargetName = ValueForKey( &entities[entIndex], "targetname" );
  440. if (!Q_strcmp(pTargetName, build.m_pLightingOrigin))
  441. {
  442. GetVectorForKey( &entities[entIndex], "origin", lightingOrigin );
  443. return true;
  444. }
  445. }
  446. return false;
  447. }
  448. //-----------------------------------------------------------------------------
  449. // Places Static Props in the level
  450. //-----------------------------------------------------------------------------
  451. static void AddStaticPropToLump( StaticPropBuild_t const& build )
  452. {
  453. // Get the collision model
  454. CPhysCollide* pConvexHull = GetCollisionModel( build.m_pModelName );
  455. if (!pConvexHull)
  456. return;
  457. // Compute the leaves the static prop's convex hull hits
  458. CUtlVector< unsigned short > leafList;
  459. ComputeStaticPropLeaves( pConvexHull, build.m_Origin, build.m_Angles, leafList );
  460. if ( !leafList.Count() )
  461. {
  462. Warning( "Static prop %s outside the map (%.2f, %.2f, %.2f)\n", build.m_pModelName, build.m_Origin.x, build.m_Origin.y, build.m_Origin.z );
  463. return;
  464. }
  465. // Insert an element into the lump data...
  466. int i = s_StaticPropLump.AddToTail( );
  467. StaticPropLump_t& propLump = s_StaticPropLump[i];
  468. propLump.m_PropType = AddStaticPropDictLump( build.m_pModelName );
  469. VectorCopy( build.m_Origin, propLump.m_Origin );
  470. VectorCopy( build.m_Angles, propLump.m_Angles );
  471. propLump.m_FirstLeaf = s_StaticPropLeafLump.Count();
  472. propLump.m_LeafCount = leafList.Count();
  473. propLump.m_Solid = build.m_Solid;
  474. propLump.m_Skin = build.m_Skin;
  475. propLump.m_Flags = build.m_Flags;
  476. propLump.m_FlagsEx = build.m_FlagsEx;
  477. if (build.m_FadesOut)
  478. {
  479. propLump.m_Flags |= STATIC_PROP_FLAG_FADES;
  480. }
  481. propLump.m_FadeMinDist = build.m_FadeMinDist;
  482. propLump.m_FadeMaxDist = build.m_FadeMaxDist;
  483. propLump.m_flForcedFadeScale = build.m_flForcedFadeScale;
  484. propLump.m_nMinCPULevel = build.m_nMinCPULevel;
  485. propLump.m_nMaxCPULevel = build.m_nMaxCPULevel;
  486. propLump.m_nMinGPULevel = build.m_nMinGPULevel;
  487. propLump.m_nMaxGPULevel = build.m_nMaxGPULevel;
  488. propLump.m_DiffuseModulation = build.m_DiffuseModulation;
  489. propLump.m_bDisableX360 = false;
  490. if (build.m_pLightingOrigin && *build.m_pLightingOrigin)
  491. {
  492. if (ComputeLightingOrigin( build, propLump.m_LightingOrigin ))
  493. {
  494. propLump.m_Flags |= STATIC_PROP_USE_LIGHTING_ORIGIN;
  495. }
  496. }
  497. // Add the leaves to the leaf lump
  498. for (int j = 0; j < leafList.Count(); ++j)
  499. {
  500. StaticPropLeafLump_t insert;
  501. insert.m_Leaf = leafList[j];
  502. s_StaticPropLeafLump.AddToTail( insert );
  503. }
  504. }
  505. //-----------------------------------------------------------------------------
  506. // Places static props in the lump
  507. //-----------------------------------------------------------------------------
  508. static void SetLumpData( )
  509. {
  510. GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle(GAMELUMP_STATIC_PROPS);
  511. if (handle != g_GameLumps.InvalidGameLump())
  512. g_GameLumps.DestroyGameLump(handle);
  513. int dictsize = s_StaticPropDictLump.Count() * sizeof(StaticPropDictLump_t);
  514. int objsize = s_StaticPropLump.Count() * sizeof(StaticPropLump_t);
  515. int leafsize = s_StaticPropLeafLump.Count() * sizeof(StaticPropLeafLump_t);
  516. int size = dictsize + objsize + leafsize + 3 * sizeof(int);
  517. handle = g_GameLumps.CreateGameLump( GAMELUMP_STATIC_PROPS, size, 0, GAMELUMP_STATIC_PROPS_VERSION );
  518. // Serialize the data
  519. CUtlBuffer buf( g_GameLumps.GetGameLump(handle), size );
  520. buf.PutInt( s_StaticPropDictLump.Count() );
  521. if (dictsize)
  522. buf.Put( s_StaticPropDictLump.Base(), dictsize );
  523. buf.PutInt( s_StaticPropLeafLump.Count() );
  524. if (leafsize)
  525. buf.Put( s_StaticPropLeafLump.Base(), leafsize );
  526. buf.PutInt( s_StaticPropLump.Count() );
  527. if (objsize)
  528. buf.Put( s_StaticPropLump.Base(), objsize );
  529. }
  530. //-----------------------------------------------------------------------------
  531. // Places Static Props in the level
  532. //-----------------------------------------------------------------------------
  533. typedef CUtlVector<StaticPropBuild_t> propBuildVector;
  534. KeyValues *kvSPCombineRules = new KeyValues( "SPCombineRules" );
  535. struct staticpropcombinepeer_t
  536. {
  537. CUtlString m_szMdlName;
  538. CUtlString m_szRefName;
  539. CUtlString m_szPhyName;
  540. CUtlString m_surfaceProp;
  541. int m_nHulls;
  542. bool m_bConcave;
  543. float m_flScale;
  544. bool m_bUpaxisY;
  545. //CUtlString m_szConcatTextureNames;
  546. };
  547. struct staticpropcombinerule_t
  548. {
  549. CUtlString m_szGroupName;
  550. CUtlString m_szQcTemplatePath;
  551. CUtlBuffer m_bufAutoGeneratedQCTemplate;
  552. CCopyableUtlVector<staticpropcombinepeer_t> m_vecCombinePeers;
  553. int m_nClusterLimit;
  554. float m_flDistanceLimit;
  555. CCopyableUtlVector<int> m_vecStatsMemberCounts;
  556. staticpropcombinerule_t() : m_bufAutoGeneratedQCTemplate(0, 0, CUtlBuffer::TEXT_BUFFER) {}
  557. };
  558. CUtlVector<staticpropcombinerule_t> g_vecCombineRules;
  559. CUtlVector<CUtlString> g_vecGeneratedModelNames;
  560. #define DEFAULT_COMBINE_STATIC_PROP_DISTANCE 129.0f
  561. #define DEFAULT_COMBINE_STATIC_PROP_COUNT 16
  562. #define MAX_HULLS 32
  563. #define MAX_EXTRA_COLLISION_MODELS 24
  564. struct convextriangle_t
  565. {
  566. int m_nPad;
  567. short m_nEdges[6];
  568. int GetVert( int nIndex ) const
  569. {
  570. return m_nEdges[ nIndex * 2 ];
  571. }
  572. };
  573. struct convexleaf_t
  574. {
  575. int m_nOffsetVerts;
  576. int m_nPad[2];
  577. short m_nTriangleCount;
  578. short m_nUnused;
  579. const convextriangle_t *GetFirstTriangle() const { return reinterpret_cast<const convextriangle_t *>(this+1); }
  580. const float *GetVertArray() const { return reinterpret_cast<float *>( ((char *)this) + m_nOffsetVerts); }
  581. };
  582. struct treenode_t
  583. {
  584. int m_nRightNode; // if not zero, there are children
  585. int m_nConvexOffset; // offset to the convex if this is a leaf
  586. float m_nFloats[5];
  587. bool IsLeaf() const { return m_nRightNode == 0 ? true : false; }
  588. const convexleaf_t *GetConvex() const { return reinterpret_cast<convexleaf_t *>( ((char *)this) + m_nConvexOffset); }
  589. const treenode_t *GetLeftChild() const { return this + 1; }
  590. const treenode_t *GetRightChild() const { return reinterpret_cast<const treenode_t *>( ((char *)this) + m_nRightNode ); }
  591. };
  592. struct collisionmodel_t
  593. {
  594. float m_flVals[7];
  595. int m_nSurface;
  596. int m_nOffsetTree;
  597. int m_nPad[3];
  598. const treenode_t *GetRoot() const { return reinterpret_cast<const treenode_t *>( ((const char *)this) + m_nOffsetTree); }
  599. };
  600. struct solidheader_t
  601. {
  602. int m_nSolidSize;
  603. int m_nID;
  604. short m_nVersion;
  605. short m_nType;
  606. int m_nSize;
  607. float m_flAreas[3];
  608. int m_nAxisMapSize;
  609. // have to skip 4 more bytes because m_nSolidSize itself is not included in the total
  610. const solidheader_t *GetNextSolid() const { return reinterpret_cast<const solidheader_t *>( ((const char *)this) + m_nSolidSize + 4); }
  611. const collisionmodel_t *GetCollisionModel() const { return reinterpret_cast<const collisionmodel_t *>(this+1); }
  612. };
  613. struct phyfileheader_t
  614. {
  615. int m_nHeaderSize;
  616. int m_nZero;
  617. int m_nSolidCount;
  618. int m_nCheckSum;
  619. const solidheader_t *GetFirstSolid() const { return reinterpret_cast<const solidheader_t *>(this+1); }
  620. int GetCollisionTextOffset() const
  621. {
  622. const solidheader_t *pSolid = GetFirstSolid();
  623. for ( int i = 0; i < m_nSolidCount; i++ )
  624. {
  625. pSolid = pSolid->GetNextSolid();
  626. }
  627. return reinterpret_cast<const char *>(pSolid) - reinterpret_cast<const char *>(this);
  628. }
  629. };
  630. int GetLeaves_r( CUtlVector<const convexleaf_t *> &list, const treenode_t *pNode )
  631. {
  632. while ( pNode )
  633. {
  634. if ( pNode->IsLeaf() )
  635. {
  636. list.AddToTail( pNode->GetConvex() );
  637. return list.Count();
  638. }
  639. GetLeaves_r( list, pNode->GetLeftChild() );
  640. pNode = pNode->GetRightChild();
  641. }
  642. return list.Count();
  643. }
  644. bool InitStaticPropCombinePeer( staticpropcombinepeer_t *newPeer )
  645. {
  646. newPeer->m_nHulls = 0;
  647. newPeer->m_bConcave = false;
  648. newPeer->m_bUpaxisY = false;
  649. newPeer->m_flScale = 1.0f;
  650. // old method was to manually specify the src ref and phy
  651. //newPeer->m_szRefName.Set( pPeer->GetString( "ref", "error" ) );
  652. //newPeer->m_szPhyName.Set( pPeer->GetString( "phy", newPeer->m_szRefName.Get() ) );
  653. // but if we can get the model qc path out of the mdl, then crudely parse out the sources, we can verify them automatically:
  654. CUtlBuffer buf;
  655. if ( !LoadStudioModel( newPeer->m_szMdlName.Get(), "prop_static", buf ) )
  656. {
  657. Error( "Error loading studio model \"%s\"!\n", newPeer->m_szMdlName.Get() );
  658. return false;
  659. }
  660. studiohdr_t* pStudioHdr = (studiohdr_t*)buf.PeekGet();
  661. bool bExtractedSources = false;
  662. if ( pStudioHdr )
  663. {
  664. newPeer->m_surfaceProp = pStudioHdr->pszSurfaceProp();
  665. //char szConcatTextureNames[256];
  666. //szConcatTextureNames[0] = 0;
  667. //if( pStudioHdr->textureindex != 0 )
  668. //{
  669. // //Msg( "%i textures in %s\n", pStudioHdr->numtextures, newPeer->m_szMdlName.Get() );
  670. // for ( int iTex=0; iTex<pStudioHdr->numtextures; iTex++ )
  671. // {
  672. // V_strcat_safe( szConcatTextureNames, pStudioHdr->pTexture( iTex )->pszName() );
  673. // //Msg( " %s\n", pStudioHdr->pTexture( iTex )->pszName() );
  674. // }
  675. //}
  676. //newPeer->m_szConcatTextureNames.Set( szConcatTextureNames );
  677. char szPhyPath[ MAX_PATH ];
  678. V_strcpy_safe( szPhyPath, newPeer->m_szMdlName.Get() );
  679. V_SetExtension( szPhyPath, ".phy", sizeof( szPhyPath ) );
  680. CUtlBuffer bufphy;
  681. if ( g_pFullFileSystem->ReadFile( szPhyPath, NULL, bufphy ) )
  682. {
  683. const phyfileheader_t *pHeader = reinterpret_cast<const phyfileheader_t *>( bufphy.Base() );
  684. if ( pHeader && pHeader->m_nHeaderSize == 16 && pHeader->m_nZero == 0 )
  685. {
  686. const solidheader_t *pSolid = pHeader->GetFirstSolid();
  687. CUtlVector<const convexleaf_t *> leafList;
  688. const treenode_t *pNode = pSolid->GetCollisionModel()->GetRoot();
  689. int nLeafCount = GetLeaves_r( leafList, pNode );
  690. newPeer->m_nHulls = nLeafCount;
  691. //Msg( "Found %i hulls in %s\n", newPeer->m_nHulls, szPhyPath );
  692. }
  693. }
  694. KeyValues *tempKeyValues = new KeyValues( "qc_path" );
  695. if ( tempKeyValues->LoadFromBuffer( NULL, pStudioHdr->KeyValueText() ) )
  696. {
  697. KeyValues *qc_path = tempKeyValues->FindKey( "qc_path", false );
  698. if ( qc_path )
  699. {
  700. const char* szQCPathRelative = qc_path->GetFirstValue()->GetString();
  701. char szTempAbsQCPath[ MAX_PATH ];
  702. if ( !g_pFullFileSystem->RelativePathToFullPath( szQCPathRelative, "CONTENT", szTempAbsQCPath, sizeof( szTempAbsQCPath ) ) )
  703. {
  704. Warning( "QC path read failure: %s\n", szQCPathRelative );
  705. return false;
  706. }
  707. CUtlBuffer bufQC( 0, 0, CUtlBuffer::TEXT_BUFFER );
  708. if ( g_pFullFileSystem->ReadFile( szTempAbsQCPath, NULL, bufQC ) )
  709. {
  710. CUtlVector<CUtlString> vecTokens;
  711. characterset_t breakSet;
  712. CharacterSetBuild( &breakSet, "{}()':" );
  713. char szToken[ 512 ];
  714. while ( bufQC.ParseToken( &breakSet, szToken, sizeof( szToken ), true ) != -1 )
  715. {
  716. if ( szToken[ 0 ] != '{' && szToken[ 0 ] != '}' ) // so much for break chars :(
  717. vecTokens[ vecTokens.AddToTail() ].Set( szToken );
  718. }
  719. int nTok = -1;
  720. nTok = vecTokens.Find( "$modelname" );
  721. if ( nTok != -1 && vecTokens.IsValidIndex( nTok + 1 ) )
  722. {
  723. char szMdlNameA[ 128 ];
  724. V_FileBase( szQCPathRelative, szMdlNameA, sizeof( szMdlNameA ) );
  725. const char* szModelTarget = vecTokens[ nTok + 1 ].Get();
  726. char szMdlNameB[ 128 ];
  727. V_FileBase( szModelTarget, szMdlNameB, sizeof( szMdlNameB ) );
  728. if ( V_strcmp( szMdlNameA, szMdlNameB ) )
  729. {
  730. Warning( "Model %s claims to come from %s, but that qc actually produces %s\n", newPeer->m_szMdlName.Get(), szQCPathRelative, szModelTarget );
  731. }
  732. }
  733. nTok = vecTokens.Find( "$scale" );
  734. if ( nTok != -1 && vecTokens.IsValidIndex( nTok + 1 ) )
  735. {
  736. const char* szScale = vecTokens[ nTok + 1 ].Get();
  737. newPeer->m_flScale = V_atof( szScale );
  738. if ( newPeer->m_flScale == 0 )
  739. {
  740. Warning( "Prop has zero scale! %s\n", szQCPathRelative );
  741. return false;
  742. }
  743. //else if ( newPeer->m_flScale != 1.0f )
  744. //{
  745. // Msg( "Non-unit scale of %.2f on %s\n", newPeer->m_flScale, szQCPathRelative );
  746. //}
  747. }
  748. nTok = vecTokens.Find( "$body" );
  749. if ( nTok != -1 && vecTokens.IsValidIndex( nTok + 2 ) )
  750. {
  751. char szTokenClean[ MAX_PATH ];
  752. V_StripExtension( vecTokens[ nTok + 2 ].Get(), szTokenClean, sizeof( szTokenClean ) );
  753. char szQCFolder[ MAX_PATH ];
  754. V_ExtractFilePath( szQCPathRelative, szQCFolder, sizeof( szQCFolder ) );
  755. char szSrcPath[ MAX_PATH ];
  756. V_sprintf_safe( szSrcPath, "%s%s", szQCFolder, szTokenClean );
  757. newPeer->m_szRefName.Set( szSrcPath );
  758. nTok = vecTokens.Find( "$collisionmodel" );
  759. if ( nTok != -1 && vecTokens.IsValidIndex( nTok + 1 ) )
  760. {
  761. //V_strcpy_safe( szTokenClean, vecTokens[ nTok + 1 ].Get() );
  762. V_StripExtension( vecTokens[ nTok + 1 ].Get(), szTokenClean, sizeof( szTokenClean ) );
  763. V_sprintf_safe( szSrcPath, "%s%s", szQCFolder, szTokenClean );
  764. if ( vecTokens.Find( "$concave" ) != -1 )
  765. {
  766. newPeer->m_bConcave = true;
  767. }
  768. }
  769. newPeer->m_szPhyName.Set( szSrcPath );
  770. bExtractedSources = true;
  771. }
  772. nTok = vecTokens.Find( "$upaxis" );
  773. if ( nTok != -1 && vecTokens.IsValidIndex( nTok + 1 ) )
  774. {
  775. if ( V_stristr( vecTokens[ nTok + 1 ].Get(), "y" ) )
  776. {
  777. newPeer->m_bUpaxisY = true;
  778. }
  779. }
  780. }
  781. }
  782. }
  783. }
  784. if ( !bExtractedSources )
  785. {
  786. Warning( "Error loading studio model \"%s\"!\n", newPeer->m_szMdlName.Get() );
  787. return false;
  788. }
  789. char szTempPath[ MAX_PATH ];
  790. bool bExists = false;
  791. V_strcpy_safe( szTempPath, newPeer->m_szRefName.Get() );
  792. V_SetExtension( szTempPath, ".smd", sizeof( szTempPath ) );
  793. if ( g_pFullFileSystem->FileExists( szTempPath, "CONTENT" ) )
  794. bExists = true;
  795. V_SetExtension( szTempPath, ".fbx", sizeof( szTempPath ) );
  796. if ( g_pFullFileSystem->FileExists( szTempPath, "CONTENT" ) )
  797. bExists = true;
  798. V_SetExtension( szTempPath, ".dmx", sizeof( szTempPath ) );
  799. if ( g_pFullFileSystem->FileExists( szTempPath, "CONTENT" ) )
  800. bExists = true;
  801. if ( !bExists )
  802. {
  803. Warning( "Static prop combine src doesn't exist: %s\n", szTempPath );
  804. return false;
  805. }
  806. return true;
  807. }
  808. bool LoadSPCombineRules( void )
  809. {
  810. #ifndef STATIC_PROP_COMBINE_ENABLED
  811. return false;
  812. #endif
  813. #ifndef IS_WINDOWS_PC
  814. return false;
  815. #endif
  816. if ( !staticpropcombine ) // feature must be enabled via commandline
  817. return false;
  818. if ( kvSPCombineRules->LoadFromFile( g_pFullFileSystem, "scripts/hammer/spcombinerules/spcombinerules.txt", "GAME" ) )
  819. {
  820. CUtlVector<CUtlString> vecUniqueModelNames;
  821. for ( KeyValues *kGroup = kvSPCombineRules->GetFirstSubKey(); kGroup != NULL; kGroup = kGroup->GetNextKey() )
  822. {
  823. //if ( !V_stristr( kGroup->GetName(), "keep" ) )
  824. // continue;
  825. staticpropcombinerule_t *pNewGroup = &g_vecCombineRules[ g_vecCombineRules.AddToTail() ];
  826. pNewGroup->m_szGroupName.Set( kGroup->GetName() );
  827. pNewGroup->m_szQcTemplatePath.Set( kGroup->GetString( "qc_template_path", NULL ) );
  828. pNewGroup->m_nClusterLimit = kGroup->GetInt( "cluster_limit", DEFAULT_COMBINE_STATIC_PROP_COUNT );
  829. pNewGroup->m_flDistanceLimit = kGroup->GetInt( "distance_limit", DEFAULT_COMBINE_STATIC_PROP_DISTANCE );
  830. KeyValues *pPeers = kGroup->FindKey( "peers" );
  831. for ( KeyValues *pPeer = pPeers->GetFirstSubKey(); pPeer != NULL; pPeer = pPeer->GetNextKey() )
  832. {
  833. staticpropcombinepeer_t *newPeer = &pNewGroup->m_vecCombinePeers[ pNewGroup->m_vecCombinePeers.AddToTail() ];
  834. newPeer->m_szMdlName.Set( pPeer->GetName() );
  835. if ( vecUniqueModelNames.Count() && vecUniqueModelNames.Find( newPeer->m_szMdlName ) != -1 )
  836. {
  837. Error( "Model %s in group %s cannot be defined twice!\n", newPeer->m_szMdlName, pNewGroup->m_szGroupName.Get() );
  838. }
  839. else
  840. {
  841. vecUniqueModelNames.AddToTail( newPeer->m_szMdlName );
  842. }
  843. if ( !InitStaticPropCombinePeer( newPeer ) )
  844. {
  845. return false;
  846. }
  847. }
  848. if ( !pNewGroup->m_szQcTemplatePath || pNewGroup->m_vecCombinePeers.Count() == 0 )
  849. {
  850. Error( "Failed to parse static prop combine rule group: \"%s\"\n", pNewGroup->m_szGroupName.Get() );
  851. }
  852. }
  853. //FOR_EACH_VEC( g_vecCombineRules, i )
  854. //{
  855. // staticpropcombinepeer_t *pPeer1 = &g_vecCombineRules[i].m_vecCombinePeers[0];
  856. //
  857. // FOR_EACH_VEC( g_vecCombineRules[i].m_vecCombinePeers, j )
  858. // {
  859. // staticpropcombinepeer_t *pPeer2 = &g_vecCombineRules[i].m_vecCombinePeers[j];
  860. //
  861. // if ( V_strcmp( pPeer1->m_szConcatTextureNames.Get(), pPeer2->m_szConcatTextureNames.Get() ) )
  862. // {
  863. // Msg( "Mismatch materials: %s to %s\n", pPeer1->m_szConcatTextureNames.Get(), pPeer2->m_szConcatTextureNames.Get() );
  864. // Msg( " %s\n", pPeer2->m_szMdlName.Get() );
  865. // }
  866. //
  867. // }
  868. //}
  869. return true;
  870. }
  871. else
  872. {
  873. delete kvSPCombineRules;
  874. kvSPCombineRules = NULL;
  875. }
  876. return false;
  877. }
  878. int GetCombineRuleForProp( const char *pszPropName1 )
  879. {
  880. if ( !g_vecCombineRules.Count() )
  881. return -1;
  882. FOR_EACH_VEC( g_vecCombineRules, i )
  883. {
  884. FOR_EACH_VEC( g_vecCombineRules[i].m_vecCombinePeers, j )
  885. {
  886. staticpropcombinepeer_t *pPeer = &g_vecCombineRules[i].m_vecCombinePeers[j];
  887. if ( !V_strcmp( pszPropName1, pPeer->m_szMdlName.Access() ) )
  888. return i;
  889. }
  890. }
  891. return -1;
  892. }
  893. bool StaticPropsMatchSkin( StaticPropBuild_t* prop1, StaticPropBuild_t* prop2 )
  894. {
  895. int nSkin1 = prop1->m_Skin;
  896. int nSkin2 = prop2->m_Skin;
  897. return ( nSkin1 == nSkin2 );
  898. }
  899. bool StaticPropsMatchFlags( StaticPropBuild_t* prop1, StaticPropBuild_t* prop2 )
  900. {
  901. #define HelperCompareFlags( _flag ) if ( staticpropcombine_doflagcompare_##_flag && ((prop1->m_Flags & _flag) != 0) != ((prop2->m_Flags & _flag) != 0) ) { return false; }
  902. HelperCompareFlags( STATIC_PROP_IGNORE_NORMALS )
  903. HelperCompareFlags( STATIC_PROP_NO_SHADOW )
  904. HelperCompareFlags( STATIC_PROP_NO_FLASHLIGHT )
  905. HelperCompareFlags( STATIC_PROP_MARKED_FOR_FAST_REFLECTION )
  906. HelperCompareFlags( STATIC_PROP_NO_PER_VERTEX_LIGHTING )
  907. HelperCompareFlags( STATIC_PROP_NO_SELF_SHADOWING )
  908. HelperCompareFlags( STATIC_PROP_FLAGS_EX_DISABLE_SHADOW_DEPTH )
  909. return true;
  910. }
  911. bool StaticPropsAreGroupPeers( StaticPropBuild_t* prop1, StaticPropBuild_t* prop2, int nCombineRule )
  912. {
  913. if ( !g_vecCombineRules.Count() || nCombineRule >= g_vecCombineRules.Count() )
  914. return false;
  915. bool bProp1Present = false;
  916. bool bProp2Present = false;
  917. FOR_EACH_VEC( g_vecCombineRules[nCombineRule].m_vecCombinePeers, j )
  918. {
  919. staticpropcombinepeer_t *pPeer = &g_vecCombineRules[nCombineRule].m_vecCombinePeers[j];
  920. if ( !bProp1Present && !V_strcmp( prop1->m_pModelName, pPeer->m_szMdlName.Access() ) )
  921. {
  922. bProp1Present = true;
  923. if ( !prop1->m_bCombineDataWritten )
  924. {
  925. prop1->m_bCombineDataWritten = true;
  926. prop1->m_szRefName = pPeer->m_szRefName;
  927. prop1->m_szPhyName = pPeer->m_szPhyName;
  928. prop1->m_nHulls = pPeer->m_nHulls;
  929. prop1->m_bConcave = pPeer->m_bConcave;
  930. prop1->m_bUpaxisY = pPeer->m_bUpaxisY;
  931. prop1->m_flScale = pPeer->m_flScale;
  932. prop1->m_nCombineRuleGroup = nCombineRule;
  933. }
  934. }
  935. if ( !bProp2Present && !V_strcmp( prop2->m_pModelName, pPeer->m_szMdlName.Access() ) )
  936. {
  937. bProp2Present = true;
  938. if ( !prop2->m_bCombineDataWritten )
  939. {
  940. prop2->m_bCombineDataWritten = true;
  941. prop2->m_szRefName = pPeer->m_szRefName;
  942. prop2->m_szPhyName = pPeer->m_szPhyName;
  943. prop2->m_nHulls = pPeer->m_nHulls;
  944. prop2->m_bConcave = pPeer->m_bConcave;
  945. prop2->m_bUpaxisY = pPeer->m_bUpaxisY;
  946. prop2->m_flScale = pPeer->m_flScale;
  947. prop2->m_nCombineRuleGroup = nCombineRule;
  948. }
  949. }
  950. if ( bProp1Present && bProp2Present )
  951. return true;
  952. }
  953. return false;
  954. }
  955. bool CompileQC( const char *pFileName )
  956. {
  957. // Spawn studiomdl.exe process to generate .mdl and associated files
  958. char cmdline[ 2 * MAX_PATH + 256 ];
  959. V_snprintf( cmdline, sizeof( cmdline ), "studiomdl.exe -nop4 -quiet %s", pFileName );
  960. #ifdef IS_WINDOWS_PC
  961. STARTUPINFO si;
  962. PROCESS_INFORMATION pi;
  963. ZeroMemory( &si, sizeof(si) );
  964. si.cb = sizeof(si);
  965. ZeroMemory( &pi, sizeof(pi) );
  966. // Start the child process.
  967. if ( CreateProcess( NULL, // No module name (use command line).
  968. cmdline, // Command line.
  969. NULL, // Process handle not inheritable.
  970. NULL, // Thread handle not inheritable.
  971. FALSE, // Set handle inheritance to FALSE.
  972. 0, // No creation flags.
  973. NULL, // Use parent's environment block.
  974. NULL, // Use parent's starting directory.
  975. &si, // Pointer to STARTUPINFO structure.
  976. &pi ) ) // Pointer to PROCESS_INFORMATION structure.
  977. {
  978. // Successfully created the process. Wait for it to finish.
  979. WaitForSingleObject( pi.hProcess, INFINITE );
  980. // Get the exit code.
  981. DWORD exitCode;
  982. BOOL result = GetExitCodeProcess( pi.hProcess, &exitCode );
  983. // Close the handles.
  984. CloseHandle( pi.hProcess );
  985. CloseHandle( pi.hThread );
  986. if (!result)
  987. {
  988. return false;
  989. }
  990. return true;
  991. }
  992. #endif
  993. return false;
  994. }
  995. void CombineStaticProps( propBuildVector &vecGroup, int &nCombineIndex )
  996. {
  997. if ( vecGroup.Count() == 0 )
  998. Error( "Combine group has zero members!\n" );
  999. if ( !vecGroup[0].m_bCombineDataWritten )
  1000. Error( "Don't know how to combine model: %s!\n", vecGroup[0].m_pModelName );
  1001. // record into stats
  1002. g_vecCombineRules[ vecGroup[0].m_nCombineRuleGroup ].m_vecStatsMemberCounts.AddToTail( vecGroup.Count() );
  1003. const char* szCombineRuleGroupName = g_vecCombineRules[ vecGroup[0].m_nCombineRuleGroup ].m_szGroupName.Get();
  1004. // let's build the temp qc
  1005. bool bLoadQcTemplate = true;
  1006. const char* szQCPath = g_vecCombineRules[ vecGroup[ 0 ].m_nCombineRuleGroup ].m_szQcTemplatePath.Get();
  1007. if ( strlen( szQCPath ) <= 0 )
  1008. {
  1009. bLoadQcTemplate = false;
  1010. if ( g_vecCombineRules[ vecGroup[ 0 ].m_nCombineRuleGroup ].m_bufAutoGeneratedQCTemplate.Size() == 0 )
  1011. {
  1012. Error( "Invalid qc template path for group: %s!\n", szCombineRuleGroupName );
  1013. }
  1014. }
  1015. CUtlBuffer bufQCTemplate( 0, 0, CUtlBuffer::TEXT_BUFFER );
  1016. if ( bLoadQcTemplate )
  1017. {
  1018. if ( !g_pFullFileSystem->ReadFile( szQCPath, NULL, bufQCTemplate ) )
  1019. {
  1020. Error( "Couldn't load the temp qc from: %s!\n", szQCPath );
  1021. }
  1022. }
  1023. else
  1024. {
  1025. bufQCTemplate.PutString( (const char*)g_vecCombineRules[ vecGroup[ 0 ].m_nCombineRuleGroup ].m_bufAutoGeneratedQCTemplate.Base() );
  1026. }
  1027. nCombineIndex++;
  1028. CUtlBuffer bufQC( 0, 0, CUtlBuffer::TEXT_BUFFER );
  1029. char szModelName[ 512 ];
  1030. V_sprintf_safe( szModelName, "props/autocombine/%s/_autocombine_%s_%i.mdl", mapbase, szCombineRuleGroupName, nCombineIndex );
  1031. #ifdef DEBUG
  1032. Msg( "Building cluster model: %i members of \"%s\" into \"_autocombine_%s_%i.mdl\"\n", vecGroup.Count(), szCombineRuleGroupName, szCombineRuleGroupName, nCombineIndex );
  1033. #else
  1034. Msg( "." );
  1035. #endif
  1036. bufQC.Printf( "$modelname %s\n\n$contentrootrelative\n\n", szModelName );
  1037. if ( vecGroup[ 0 ].m_bUpaxisY )
  1038. {
  1039. // if the source basemodel is upaxis y, we need to build the combine model base in the same coordsys
  1040. bufQC.Printf( "$upaxis y\n\n" );
  1041. }
  1042. float flRootScale = vecGroup[ 0 ].m_flScale;
  1043. if ( flRootScale != 1.0f )
  1044. {
  1045. if ( flRootScale == 0 )
  1046. Error( "Zero scale error!\n" );
  1047. bufQC.Printf( "$scale %.3f\n\n", flRootScale );
  1048. }
  1049. bufQC.Printf( "$body body \"%s\"\n\n", vecGroup[ 0 ].m_szRefName.Get() );
  1050. Vector vecRootPos = vecGroup[ 0 ].m_Origin;
  1051. QAngle angRootAng = vecGroup[ 0 ].m_Angles;
  1052. matrix3x4_t matRoot;
  1053. AngleMatrix( angRootAng, vecRootPos, matRoot );
  1054. CUtlVector<matrix3x4_t> vecOffsets;
  1055. vecOffsets.EnsureCapacity( vecGroup.Count() );
  1056. FOR_EACH_VEC( vecGroup, v )
  1057. {
  1058. vecOffsets[ vecOffsets.AddToTail() ].SetToIdentity();
  1059. }
  1060. CUtlVector<matrix3x4_t> vecOffsetsPhy;
  1061. vecOffsetsPhy.EnsureCapacity( vecGroup.Count() );
  1062. FOR_EACH_VEC( vecGroup, v )
  1063. {
  1064. vecOffsetsPhy[ vecOffsetsPhy.AddToTail() ].SetToIdentity();
  1065. }
  1066. // add in the extra props to combine at their relative offset to the root prop
  1067. FOR_EACH_VEC( vecGroup, i )
  1068. {
  1069. if ( i == 0 )
  1070. continue;
  1071. if ( !vecGroup[ i ].m_bCombineDataWritten )
  1072. Error( "Don't know how to combine model: %s!\n", vecGroup[ i ].m_pModelName );
  1073. bufQC.Printf( "$appendsource \"%s\" ", vecGroup[ i ].m_szRefName.Get() );
  1074. Vector vecAddonPos = vecGroup[ i ].m_Origin;
  1075. QAngle angAddonAng = vecGroup[ i ].m_Angles;
  1076. // get xform relative to root (in hammer space)
  1077. matrix3x4_t matLocal;
  1078. AngleMatrix( angAddonAng, vecAddonPos, matLocal );
  1079. ConcatTransforms( matRoot.InverseTR(), matLocal, matLocal );
  1080. // see: https://intranet.valvesoftware.com/wiki/3D_Coordinate_Systems
  1081. matrix3x4_t matRotate; matRotate.InitFromQAngles( QAngle( 0, -90, 0 ) );
  1082. ConcatTransforms( matRotate, matLocal, matLocal );
  1083. matrix3x4_t matConvert; matConvert.InitXYZ( Vector( 0, -1, 0 ), Vector( 1, 0, 0 ), Vector( 0, 0, 1 ), Vector( 0, 0, 0 ) );
  1084. ConcatTransforms( matLocal, matConvert.InverseTR(), matLocal );
  1085. MatrixCopy( matLocal, vecOffsetsPhy[ i ] );
  1086. if ( vecGroup[ i ].m_bUpaxisY )
  1087. {
  1088. // child combine models with upaxis y need to get flipped around too
  1089. matRotate.InitFromQAngles( QAngle( 0, 0, -90 ) );
  1090. ConcatTransforms( matRotate, matLocal, matLocal );
  1091. matConvert.InitXYZ( Vector( 1, 0, 0 ), Vector( 0, 0, -1 ), Vector( 0, 1, 0 ), Vector( 0, 0, 0 ) );
  1092. ConcatTransforms( matLocal, matConvert.InverseTR(), matLocal );
  1093. }
  1094. MatrixCopy( matLocal, vecOffsets[ i ] );
  1095. // export position + qangles
  1096. MatrixAngles( vecOffsets[ i ], angAddonAng, vecAddonPos );
  1097. bufQC.Printf( "\"offset pos[" );
  1098. bufQC.Printf( " %.3f", vecAddonPos.x );
  1099. bufQC.Printf( " %.3f", vecAddonPos.y );
  1100. bufQC.Printf( " %.3f", vecAddonPos.z );
  1101. bufQC.Printf( " ] angle[" );
  1102. bufQC.Printf( " %.3f", angAddonAng.x );
  1103. bufQC.Printf( " %.3f", angAddonAng.y );
  1104. bufQC.Printf( " %.3f", angAddonAng.z );
  1105. bufQC.Printf( " ] scale[" );
  1106. bufQC.Printf( " %.3f ]\"\n", vecGroup[ i ].m_flScale );
  1107. }
  1108. bufQC.Printf( CUtlString( bufQCTemplate.Base(), bufQCTemplate.Size() ) );
  1109. bufQC.Printf( "\n$sequence \"idle\" \"%s\" act_idle 1\n\n", vecGroup[ 0 ].m_szRefName.Get() );
  1110. // add the physics hulls
  1111. bool bDoPhysics = false;
  1112. FOR_EACH_VEC( vecGroup, i )
  1113. {
  1114. if ( vecGroup[ i ].m_Solid != 0 )
  1115. {
  1116. bDoPhysics = true;
  1117. break;
  1118. }
  1119. }
  1120. if ( bDoPhysics )
  1121. {
  1122. bufQC.Printf( "$collisionprecision 0.01\n" );
  1123. bufQC.Printf( "$collisionmodel \"blank\" {\n" );
  1124. bufQC.Printf( "\t$maxconvexpieces 64\n" ); // should never even approach this limit due to smaller hull limit
  1125. bufQC.Printf( "\t$automass\n" );
  1126. bufQC.Printf( "\t$remove2d\n" );
  1127. bufQC.Printf( "\t$concave\n" );
  1128. FOR_EACH_VEC( vecGroup, i )
  1129. {
  1130. if ( !vecGroup[ i ].m_Solid )
  1131. continue;
  1132. bufQC.Printf( "\t$addconvexsrc \"%s\" ", vecGroup[ i ].m_szPhyName.Get() );
  1133. // export position + qangles
  1134. Vector vecAddonPos;
  1135. QAngle angAddonAng;
  1136. MatrixAngles( vecOffsetsPhy[ i ], angAddonAng, vecAddonPos );
  1137. bufQC.Printf( "\"offset pos[" );
  1138. bufQC.Printf( " %.3f", vecAddonPos.x );
  1139. bufQC.Printf( " %.3f", vecAddonPos.y );
  1140. bufQC.Printf( " %.3f", vecAddonPos.z );
  1141. bufQC.Printf( " ] angle[" );
  1142. bufQC.Printf( " %.3f", angAddonAng.x );
  1143. bufQC.Printf( " %.3f", angAddonAng.y );
  1144. bufQC.Printf( " %.3f", angAddonAng.z );
  1145. bufQC.Printf( " ] scale[" );
  1146. bufQC.Printf( " %.3f ]\"", vecGroup[ i ].m_flScale );
  1147. if ( vecGroup[ i ].m_bConcave )
  1148. {
  1149. bufQC.Printf( " concave " );
  1150. }
  1151. bufQC.Printf( "\n" );
  1152. }
  1153. bufQC.Printf( "}\n" );
  1154. // important! if any members have physics hulls, the root model needs to be flagged as such.
  1155. vecGroup[ 0 ].m_Solid = 6; // 6 is "Use VPhysics"
  1156. }
  1157. char szMapSpecificAutocombineQcDir[ MAX_PATH ];
  1158. V_sprintf_safe( szMapSpecificAutocombineQcDir, "models/props/autocombine/%s/", mapbase );
  1159. g_pFullFileSystem->CreateDirHierarchy( szMapSpecificAutocombineQcDir, "CONTENT" );
  1160. char szGeneratedQCpath[ MAX_PATH ];
  1161. V_sprintf_safe( szGeneratedQCpath, "models/props/autocombine/%s/autocombine_%s_%i.qc", mapbase, szCombineRuleGroupName, nCombineIndex );
  1162. if ( g_pFullFileSystem->WriteFile( szGeneratedQCpath, "CONTENT", bufQC ) )
  1163. {
  1164. char szTempAbsPath[ MAX_PATH ];
  1165. g_pFullFileSystem->RelativePathToFullPath( szGeneratedQCpath, "CONTENT", szTempAbsPath, sizeof( szTempAbsPath ) );
  1166. if ( CompileQC( szTempAbsPath ) )
  1167. {
  1168. char szModelNameModelPrefix[ 512 ];
  1169. V_sprintf_safe( szModelNameModelPrefix, "models/%s", szModelName );
  1170. int nStrIdx = g_vecGeneratedModelNames.AddToTail( CUtlString( szModelNameModelPrefix ) );
  1171. // now bspzip the autogenerated models into the bsp
  1172. char szTempAbsMdlPath[ MAX_PATH ];
  1173. g_pFullFileSystem->RelativePathToFullPath( szModelNameModelPrefix, "GAME", szTempAbsMdlPath, sizeof( szTempAbsMdlPath ) );
  1174. bool bBSPZipSuccess = false;
  1175. // find the .mdl
  1176. if ( g_pFullFileSystem->FileExists( szTempAbsMdlPath ) )
  1177. {
  1178. //add it
  1179. AddFileToPak( GetPakFile(), szModelNameModelPrefix, szTempAbsMdlPath );
  1180. if ( staticpropcombine_delsources )
  1181. g_pFullFileSystem->RemoveFile( szModelNameModelPrefix, "GAME" );
  1182. // find the .phy
  1183. V_SetExtension( szTempAbsMdlPath, ".phy", sizeof( szTempAbsMdlPath ) );
  1184. V_SetExtension( szModelNameModelPrefix, ".phy", sizeof( szModelNameModelPrefix ) );
  1185. if ( g_pFullFileSystem->FileExists( szTempAbsMdlPath ) )
  1186. {
  1187. // add it
  1188. AddFileToPak( GetPakFile(), szModelNameModelPrefix, szTempAbsMdlPath );
  1189. if ( staticpropcombine_delsources )
  1190. g_pFullFileSystem->RemoveFile( szModelNameModelPrefix, "GAME" );
  1191. }
  1192. else
  1193. {
  1194. // not a failure if it doesn't exist
  1195. //Msg( "Couldn't find phy model: %s\n", szModelNameModelPrefix );
  1196. }
  1197. // find the .vvd
  1198. V_SetExtension( szTempAbsMdlPath, ".vvd", sizeof( szTempAbsMdlPath ) );
  1199. V_SetExtension( szModelNameModelPrefix, ".vvd", sizeof( szModelNameModelPrefix ) );
  1200. if ( g_pFullFileSystem->FileExists( szTempAbsMdlPath ) )
  1201. {
  1202. // add it
  1203. AddFileToPak( GetPakFile(), szModelNameModelPrefix, szTempAbsMdlPath );
  1204. if ( staticpropcombine_delsources )
  1205. g_pFullFileSystem->RemoveFile( szModelNameModelPrefix, "GAME" );
  1206. // find the .dx90.vtx
  1207. V_SetExtension( szTempAbsMdlPath, ".dx90.vtx", sizeof( szTempAbsMdlPath ) );
  1208. V_SetExtension( szModelNameModelPrefix, ".dx90.vtx", sizeof( szModelNameModelPrefix ) );
  1209. if ( g_pFullFileSystem->FileExists( szTempAbsMdlPath ) )
  1210. {
  1211. // add it
  1212. AddFileToPak( GetPakFile(), szModelNameModelPrefix, szTempAbsMdlPath );
  1213. if ( staticpropcombine_delsources )
  1214. g_pFullFileSystem->RemoveFile( szModelNameModelPrefix, "GAME" );
  1215. {
  1216. // remove the qc
  1217. if ( staticpropcombine_delsources )
  1218. g_pFullFileSystem->RemoveFile( szTempAbsPath, "CONTENT" );
  1219. vecGroup[ 0 ].m_pModelName = g_vecGeneratedModelNames[ nStrIdx ].Access();
  1220. bBSPZipSuccess = true;
  1221. }
  1222. }
  1223. }
  1224. }
  1225. if ( !bBSPZipSuccess )
  1226. {
  1227. Error( "Failed to bspzip autogenerated model into the map: %s!\n", szModelNameModelPrefix );
  1228. }
  1229. return;
  1230. }
  1231. else
  1232. {
  1233. Error( "Failed while compiling: %s!\n", szGeneratedQCpath );
  1234. }
  1235. }
  1236. else
  1237. {
  1238. Error( "Couldn't write a temp qc to: %s!\n", szGeneratedQCpath );
  1239. }
  1240. }
  1241. Vector vecTempSortOrigin;
  1242. int PropDistanceSortFunctionFarToNear( const StaticPropBuild_t* entry1, const StaticPropBuild_t* entry2 )
  1243. {
  1244. if ( entry1->m_Origin.DistToSqr( vecTempSortOrigin ) > entry2->m_Origin.DistToSqr( vecTempSortOrigin ) )
  1245. {
  1246. return -1;
  1247. }
  1248. else
  1249. {
  1250. return 1;
  1251. }
  1252. // eh, don't care about the equal distance case
  1253. }
  1254. bool PropDiffuseModulationsMatch( const StaticPropBuild_t* entry1, const StaticPropBuild_t* entry2 )
  1255. {
  1256. if ( entry1->m_DiffuseModulation.r == entry2->m_DiffuseModulation.r &&
  1257. entry1->m_DiffuseModulation.g == entry2->m_DiffuseModulation.g &&
  1258. entry1->m_DiffuseModulation.b == entry2->m_DiffuseModulation.b )
  1259. {
  1260. return true;
  1261. }
  1262. return false;
  1263. }
  1264. //-----------------------------------------------------------------------------
  1265. // figure out which leaf a point is in
  1266. //-----------------------------------------------------------------------------
  1267. static int PointLeafnum( const Vector& p, int num = 0 )
  1268. {
  1269. float d;
  1270. while ( num >= 0 )
  1271. {
  1272. dnode_t* node = dnodes + num;
  1273. dplane_t* plane = dplanes + node->planenum;
  1274. if ( plane->type < 3 )
  1275. d = p[ plane->type ] - plane->dist;
  1276. else
  1277. d = DotProduct( plane->normal, p ) - plane->dist;
  1278. if ( d < 0 )
  1279. num = node->children[ 1 ];
  1280. else
  1281. num = node->children[ 0 ];
  1282. }
  1283. return -1 - num;
  1284. }
  1285. bool IsCompletelyInSolid( const StaticPropBuild_t* entry1 )
  1286. {
  1287. // Get the collision model
  1288. CPhysCollide* pConvexHull = GetCollisionModel( entry1->m_pModelName );
  1289. if ( !pConvexHull )
  1290. return false;
  1291. // Compute the leaves the static prop's convex hull hits
  1292. CUtlVector< unsigned short > leafList;
  1293. ComputeStaticPropLeaves( pConvexHull, entry1->m_Origin, entry1->m_Angles, leafList );
  1294. for ( int i = 0; i < leafList.Count(); i++ )
  1295. {
  1296. int cluster = dleafs[ leafList[ i ] ].cluster;
  1297. if ( cluster != -1 )
  1298. {
  1299. return false;
  1300. }
  1301. }
  1302. return true;
  1303. }
  1304. bool StaticPropLocationsMatch( const StaticPropBuild_t* entry1, const StaticPropBuild_t* entry2, int nCombineRule )
  1305. {
  1306. if ( staticpropcombine_considervis )
  1307. {
  1308. // Get the collision model
  1309. CPhysCollide* pConvexHull = GetCollisionModel( entry1->m_pModelName );
  1310. if ( !pConvexHull )
  1311. return false;
  1312. // Compute the leaves the static prop's convex hull hits
  1313. CUtlVector< unsigned short > leafList;
  1314. ComputeStaticPropLeaves( pConvexHull, entry1->m_Origin, entry1->m_Angles, leafList );
  1315. CUtlVector<int> clusterList1;
  1316. for ( int i = 0; i < leafList.Count(); i++ )
  1317. {
  1318. int cluster = dleafs[ leafList[ i ] ].cluster;
  1319. if ( cluster != -1 )
  1320. {
  1321. clusterList1.FindAndFastRemove( cluster );
  1322. clusterList1.AddToTail( cluster );
  1323. }
  1324. }
  1325. pConvexHull = GetCollisionModel( entry2->m_pModelName );
  1326. if ( !pConvexHull )
  1327. return false;
  1328. // Compute the leaves the static prop's convex hull hits
  1329. leafList.RemoveAll();
  1330. ComputeStaticPropLeaves( pConvexHull, entry2->m_Origin, entry2->m_Angles, leafList );
  1331. CUtlVector<int> clusterList2;
  1332. for ( int i = 0; i < leafList.Count(); i++ )
  1333. {
  1334. int cluster = dleafs[ leafList[ i ] ].cluster;
  1335. if ( cluster != -1 )
  1336. {
  1337. clusterList2.FindAndFastRemove( cluster );
  1338. clusterList2.AddToTail( cluster );
  1339. }
  1340. }
  1341. clusterList1.Sort();
  1342. clusterList2.Sort();
  1343. for ( int i = 0; i < clusterList1.Count(); i++ )
  1344. {
  1345. if ( clusterList2.Find( clusterList1[ i ] ) >= 0 )
  1346. {
  1347. return true;
  1348. }
  1349. }
  1350. return false;
  1351. }
  1352. else
  1353. {
  1354. return entry1->m_Origin.DistTo( entry2->m_Origin ) < g_vecCombineRules[ nCombineRule ].m_flDistanceLimit; // candidate must be close enough to the cluster root
  1355. }
  1356. }
  1357. bool GroupProp( propBuildVector &vecSearch, propBuildVector &vecMoveTo )
  1358. {
  1359. if ( !vecSearch.Count() )
  1360. {
  1361. //Msg( "Closing group. There are no more props to group!\n" );
  1362. return false;
  1363. }
  1364. if ( !vecMoveTo.Count() )
  1365. {
  1366. //Msg( "\nStarting NEW cluster with: %s\n", vecSearch[0].m_pModelName );
  1367. vecMoveTo.AddToTail( vecSearch[0] );
  1368. vecSearch.Remove(0);
  1369. return true;
  1370. }
  1371. int nCombineRule = GetCombineRuleForProp( vecMoveTo[0].m_pModelName );
  1372. if ( nCombineRule < 0 )
  1373. {
  1374. //Msg( "Closing group. (No combine rules include %s)\n", vecMoveTo[0].m_pModelName );
  1375. return false;
  1376. }
  1377. if ( vecMoveTo.Count() > g_vecCombineRules[nCombineRule].m_nClusterLimit )
  1378. {
  1379. //Msg( "Closing group. (Combine limit reached: %i)\n", vecMoveTo.Count() );
  1380. return false;
  1381. }
  1382. // there's a limit of MAX_HULLS to prevent creating super complex concave hulls
  1383. int nHulls = 0;
  1384. int nParts = 0;
  1385. FOR_EACH_VEC( vecMoveTo, i )
  1386. {
  1387. if ( vecMoveTo[i].m_Solid != 0 )
  1388. {
  1389. nParts++;
  1390. nHulls += vecMoveTo[i].m_nHulls;
  1391. }
  1392. }
  1393. if ( nHulls >= MAX_HULLS || nParts >= MAX_EXTRA_COLLISION_MODELS )
  1394. {
  1395. //Msg( "Closing group. (Convex hull limit reached: %i)\n", nHulls );
  1396. return false;
  1397. }
  1398. // sort vecSearch from far to near, so the closest entries are at the end of the list.
  1399. vecTempSortOrigin = vecMoveTo[0].m_Origin;
  1400. vecSearch.Sort( PropDistanceSortFunctionFarToNear );
  1401. // look through the as-yet ungrouped props and find the best one to be the next part of the group we're currently building
  1402. FOR_EACH_VEC_BACK( vecSearch, i )
  1403. {
  1404. if ( PropDiffuseModulationsMatch( &vecSearch[i], &vecMoveTo[0] ) && // candidate must have the same diffuse modulation
  1405. StaticPropLocationsMatch(&vecSearch[i], &vecMoveTo[0], nCombineRule ) && // candidate must be close enough to the cluster root
  1406. StaticPropsAreGroupPeers( &vecSearch[i], &vecMoveTo[0], nCombineRule ) && // must be allowed to combine by script rules
  1407. StaticPropsMatchSkin( &vecSearch[i], &vecMoveTo[0] ) && // must have the same skin group
  1408. StaticPropsMatchFlags( &vecSearch[i], &vecMoveTo[0] ) ) // must have matching flags
  1409. {
  1410. //Msg( " Add to cluster: %s\n", vecSearch[i].m_pModelName );
  1411. vecMoveTo.AddToTail( vecSearch[i] );
  1412. vecSearch.Remove(i);
  1413. return true;
  1414. }
  1415. }
  1416. //Msg( "Closing group. (No peers found for %s)\n", vecMoveTo[0].m_pModelName );
  1417. return false;
  1418. }
  1419. inline bool UtlStringLessFunc( const CUtlString &lhs, const CUtlString &rhs )
  1420. {
  1421. return V_strcmp( lhs.Get(), rhs.Get() ) < 0;
  1422. }
  1423. struct MaterialUsageInfo_t
  1424. {
  1425. CUtlString matName;
  1426. int nMaterialCount; // I make a fake MaterialUsageInfo with all material names concatenated for models with multiple materials
  1427. int nInstanceCount;
  1428. CUtlString surfaceProp;
  1429. CCopyableUtlVector<CUtlString> modelsUsingThisMaterial;
  1430. };
  1431. void CreatePropCombineRule( const MaterialUsageInfo_t &materialInstances )
  1432. {
  1433. staticpropcombinerule_t rule;
  1434. rule.m_szGroupName = materialInstances.matName;
  1435. rule.m_szGroupName = rule.m_szGroupName.GetBaseFilename();
  1436. rule.m_flDistanceLimit = 500.0f;
  1437. rule.m_nClusterLimit = 32; //FIXME: Determine based on # of verts in meshes?
  1438. for ( int i = 0; i < materialInstances.modelsUsingThisMaterial.Count(); i++ )
  1439. {
  1440. staticpropcombinepeer_t peer;
  1441. peer.m_szMdlName = materialInstances.modelsUsingThisMaterial[ i ];
  1442. if ( !InitStaticPropCombinePeer( &peer ) )
  1443. {
  1444. return;
  1445. }
  1446. rule.m_vecCombinePeers.AddToTail( peer );
  1447. }
  1448. CUtlString materialPath = materialInstances.matName;
  1449. materialPath = materialPath.DirName();
  1450. rule.m_bufAutoGeneratedQCTemplate.Printf(
  1451. "$cdmaterials %s\n"
  1452. "\n"
  1453. "$staticprop\n"
  1454. "$surfaceprop \"%s\"\n", materialPath.Get(), rule.m_vecCombinePeers[ 0 ].m_surfaceProp.Get() );
  1455. g_vecCombineRules.AddToTail( rule );
  1456. }
  1457. void AutoCreatePropCombineRules( propBuildVector& vecBuilds )
  1458. {
  1459. CUtlMap<CUtlString, int, int> uniqueModels( UtlStringLessFunc );
  1460. struct MaterialInfo_t
  1461. {
  1462. int nMaterialCount;
  1463. int nStaticPropInstancesUsingMaterial;
  1464. CCopyableUtlVector<CUtlString> modelsUsingThisMaterial;
  1465. };
  1466. CUtlMap<CUtlString, MaterialInfo_t, int> uniqueMaterials( UtlStringLessFunc );
  1467. // Count unique props and count # of instances
  1468. for ( int i = 0; i < vecBuilds.Count(); i++ )
  1469. {
  1470. const StaticPropBuild_t &build = vecBuilds[ i ];
  1471. int nIdx = uniqueModels.Find( build.m_pModelName );
  1472. if ( uniqueModels.IsValidIndex( nIdx ) )
  1473. {
  1474. uniqueModels[ nIdx ]++;
  1475. }
  1476. else
  1477. {
  1478. uniqueModels.Insert( build.m_pModelName, 1 );
  1479. }
  1480. }
  1481. struct ModelInstanceInfo_t
  1482. {
  1483. CUtlString modelName;
  1484. int nInstanceCount;
  1485. };
  1486. CUtlVector<ModelInstanceInfo_t> sortedModels;
  1487. for ( int i = uniqueModels.FirstInorder(); uniqueModels.IsValidIndex( i ); i = uniqueModels.NextInorder( i ) )
  1488. {
  1489. ModelInstanceInfo_t e;
  1490. e.modelName = uniqueModels.Key( i );
  1491. e.nInstanceCount = uniqueModels[ i ];
  1492. sortedModels.AddToTail( e );
  1493. }
  1494. std::sort( sortedModels.begin(), sortedModels.end(), []( const ModelInstanceInfo_t& a, const ModelInstanceInfo_t& b ){ return a.nInstanceCount > b.nInstanceCount; } );
  1495. Msg( "%d unique static props used in map\n", sortedModels.Count() );
  1496. for ( const ModelInstanceInfo_t &e : sortedModels )
  1497. {
  1498. CUtlBuffer buf;
  1499. if ( !LoadStudioModel( e.modelName.Get(), "prop_static", buf ) )
  1500. {
  1501. Error( "Error loading studio model \"%s\"!\n", e.modelName.Get() );
  1502. continue;
  1503. }
  1504. studiohdr_t* pStudioHdr = (studiohdr_t*)buf.PeekGet();
  1505. if ( !pStudioHdr )
  1506. {
  1507. Error( "Error loading studio model \"%s\"!\n", e.modelName.Get() );
  1508. continue;
  1509. }
  1510. CUtlString combinedMatName;
  1511. for ( int i = 0; i < pStudioHdr->numtextures; i++ )
  1512. {
  1513. combinedMatName += pStudioHdr->pTexture( i )->pszName();
  1514. if ( i < pStudioHdr->numtextures - 1 )
  1515. {
  1516. combinedMatName += "___";
  1517. }
  1518. }
  1519. int nIdx = uniqueMaterials.Find( combinedMatName );
  1520. if ( uniqueMaterials.IsValidIndex( nIdx ) )
  1521. {
  1522. uniqueMaterials[ nIdx ].nStaticPropInstancesUsingMaterial += e.nInstanceCount;
  1523. uniqueMaterials[ nIdx ].modelsUsingThisMaterial.AddToTail( e.modelName );
  1524. }
  1525. else
  1526. {
  1527. MaterialInfo_t mi;
  1528. mi.nStaticPropInstancesUsingMaterial = e.nInstanceCount;
  1529. mi.nMaterialCount = pStudioHdr->numtextures;
  1530. mi.modelsUsingThisMaterial.AddToTail( e.modelName );
  1531. uniqueMaterials.Insert( combinedMatName, mi );
  1532. }
  1533. }
  1534. CUtlVector<MaterialUsageInfo_t> sortedMaterials;
  1535. for ( int i = uniqueMaterials.FirstInorder(); uniqueMaterials.IsValidIndex( i ); i = uniqueMaterials.NextInorder( i ) )
  1536. {
  1537. MaterialUsageInfo_t e;
  1538. e.matName = uniqueMaterials.Key( i );
  1539. e.nMaterialCount = uniqueMaterials[ i ].nMaterialCount;
  1540. e.nInstanceCount = uniqueMaterials[ i ].nStaticPropInstancesUsingMaterial;
  1541. e.modelsUsingThisMaterial.AddVectorToTail( uniqueMaterials[ i ].modelsUsingThisMaterial );
  1542. sortedMaterials.AddToTail( e );
  1543. }
  1544. int nCreatedRuleCount = 0;
  1545. std::sort( sortedMaterials.begin(), sortedMaterials.end(), []( const MaterialUsageInfo_t& a, const MaterialUsageInfo_t& b ){ return a.nInstanceCount > b.nInstanceCount; } );
  1546. if ( staticpropcombine_autocombine )
  1547. {
  1548. int nInstancesCovered = 0;
  1549. // Create combine rules for highly-instanced models with a single material
  1550. for ( const MaterialUsageInfo_t &e : sortedMaterials )
  1551. {
  1552. if ( e.nMaterialCount > 1 )
  1553. {
  1554. continue;
  1555. }
  1556. if ( e.nInstanceCount >= 30 )
  1557. {
  1558. CreatePropCombineRule( e );
  1559. nCreatedRuleCount++;
  1560. nInstancesCovered += e.nInstanceCount;
  1561. }
  1562. }
  1563. Msg( "Auto-created %d static prop combine rules (covering %d static prop instances) based on static prop and material usage.\n", nCreatedRuleCount, nInstancesCovered );
  1564. }
  1565. if ( staticpropcombine_suggestcombinerules )
  1566. {
  1567. // Print suggestions for what combine rules to create manually for models that use multiple materials
  1568. Msg( "\nSuggested models for manually creating rules in spcombinerules.txt:\n" );
  1569. for ( const MaterialUsageInfo_t &e : sortedMaterials )
  1570. {
  1571. if ( e.nMaterialCount == 1 || e.nInstanceCount < 30 )
  1572. {
  1573. continue;
  1574. }
  1575. Msg( "%4d %s\n", e.nInstanceCount, e.matName.Get() );
  1576. for ( const CUtlString &modelName : e.modelsUsingThisMaterial )
  1577. {
  1578. Msg( "\t%s\n", modelName.Get() );
  1579. }
  1580. }
  1581. }
  1582. }
  1583. void EmitStaticProps()
  1584. {
  1585. CreateInterfaceFn physicsFactory = GetPhysicsFactory();
  1586. if ( physicsFactory )
  1587. {
  1588. s_pPhysCollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL );
  1589. if( !s_pPhysCollision )
  1590. return;
  1591. }
  1592. // Generate a list of lighting origins, and strip them out
  1593. int i;
  1594. for ( i = 0; i < num_entities; ++i)
  1595. {
  1596. char* pEntity = ValueForKey(&entities[i], "classname");
  1597. if (!Q_strcmp(pEntity, "info_lighting"))
  1598. {
  1599. s_LightingInfo.AddToTail(i);
  1600. }
  1601. }
  1602. propBuildVector vecBuilds;
  1603. bool bDoStaticPropCombine = LoadSPCombineRules();
  1604. // Emit specifically specified static props
  1605. for ( i = 0; i < num_entities; ++i)
  1606. {
  1607. char* pEntity = ValueForKey(&entities[i], "classname");
  1608. if (!strcmp(pEntity, "static_prop") || !strcmp(pEntity, "prop_static"))
  1609. {
  1610. StaticPropBuild_t build;
  1611. build.m_bCombineDataWritten = false;
  1612. build.m_szRefName = NULL;
  1613. build.m_szPhyName = NULL;
  1614. build.m_nHulls = 0;
  1615. build.m_bConcave = false;
  1616. build.m_bUpaxisY = false;
  1617. build.m_flScale = 1.0f;
  1618. build.m_nCombineRuleGroup = -1;
  1619. GetVectorForKey( &entities[i], "origin", build.m_Origin );
  1620. GetAnglesForKey( &entities[i], "angles", build.m_Angles );
  1621. build.m_pModelName = ValueForKey( &entities[i], "model" );
  1622. build.m_Solid = IntForKey( &entities[i], "solid" );
  1623. build.m_Skin = IntForKey( &entities[i], "skin" );
  1624. build.m_FadeMaxDist = FloatForKey( &entities[i], "fademaxdist" );
  1625. build.m_Flags = 0;//IntForKey( &entities[i], "spawnflags" ) & STATIC_PROP_WC_MASK;
  1626. build.m_FlagsEx = 0;//IntForKey( &entities[i], "spawnflags" ) & STATIC_PROP_WC_MASK;
  1627. if (IntForKey( &entities[i], "ignorenormals" ) == 1)
  1628. {
  1629. build.m_Flags |= STATIC_PROP_IGNORE_NORMALS;
  1630. }
  1631. if (IntForKey( &entities[i], "disableshadows" ) == 1)
  1632. {
  1633. build.m_Flags |= STATIC_PROP_NO_SHADOW;
  1634. }
  1635. if (IntForKey( &entities[i], "disableflashlight" ) == 1)
  1636. {
  1637. build.m_Flags |= STATIC_PROP_NO_FLASHLIGHT;
  1638. }
  1639. if (IntForKey( &entities[i], "drawinfastreflection" ) == 1)
  1640. {
  1641. build.m_Flags |= STATIC_PROP_MARKED_FOR_FAST_REFLECTION;
  1642. }
  1643. if (IntForKey( &entities[i], "disablevertexlighting" ) == 1)
  1644. {
  1645. build.m_Flags |= STATIC_PROP_NO_PER_VERTEX_LIGHTING;
  1646. }
  1647. if (IntForKey( &entities[i], "disableselfshadowing" ) == 1)
  1648. {
  1649. build.m_Flags |= STATIC_PROP_NO_SELF_SHADOWING;
  1650. }
  1651. if (IntForKey( &entities[i], "disableshadowdepth" ) == 1)
  1652. {
  1653. build.m_FlagsEx |= STATIC_PROP_FLAGS_EX_DISABLE_SHADOW_DEPTH;
  1654. }
  1655. if ( IntForKey( &entities[ i ], "enablelightbounce" ) == 1 )
  1656. {
  1657. build.m_FlagsEx |= STATIC_PROP_FLAGS_EX_ENABLE_LIGHT_BOUNCE;
  1658. }
  1659. if (IntForKey( &entities[i], "screenspacefade" ) == 1)
  1660. {
  1661. Warning( "Encountered obsolete static prop option to do its fade in screen space @ %.2f %.2f %.2f\n",
  1662. build.m_Origin.x, build.m_Origin.y, build.m_Origin.z );
  1663. }
  1664. const char *pKey = ValueForKey( &entities[i], "fadescale" );
  1665. if ( pKey && pKey[0] )
  1666. {
  1667. build.m_flForcedFadeScale = FloatForKey( &entities[i], "fadescale" );
  1668. }
  1669. else
  1670. {
  1671. build.m_flForcedFadeScale = 1;
  1672. }
  1673. build.m_FadesOut = (build.m_FadeMaxDist > 0);
  1674. build.m_pLightingOrigin = ValueForKey( &entities[i], "lightingorigin" );
  1675. if (build.m_FadesOut)
  1676. {
  1677. build.m_FadeMinDist = FloatForKey( &entities[i], "fademindist" );
  1678. if (build.m_FadeMinDist < 0)
  1679. {
  1680. build.m_FadeMinDist = build.m_FadeMaxDist;
  1681. }
  1682. }
  1683. else
  1684. {
  1685. build.m_FadeMinDist = 0;
  1686. }
  1687. build.m_nMinCPULevel = (unsigned char)IntForKey( &entities[i], "mincpulevel" );
  1688. build.m_nMaxCPULevel = (unsigned char)IntForKey( &entities[i], "maxcpulevel" );
  1689. build.m_nMinGPULevel = (unsigned char)IntForKey( &entities[i], "mingpulevel" );
  1690. build.m_nMaxGPULevel = (unsigned char)IntForKey( &entities[i], "maxgpulevel" );
  1691. if ( build.m_nMaxCPULevel && build.m_nMaxCPULevel < build.m_nMinCPULevel )
  1692. {
  1693. build.m_nMaxCPULevel = build.m_nMinCPULevel;
  1694. }
  1695. if ( build.m_nMaxGPULevel && build.m_nMaxGPULevel < build.m_nMinGPULevel )
  1696. {
  1697. build.m_nMaxGPULevel = build.m_nMinGPULevel;
  1698. }
  1699. // FIXME: look for ComputeFXBlend and make sure that you don't
  1700. // need a particlar rendermode for this stuff to happen
  1701. // Get the per-instance render-color for this static prop
  1702. const char *pColorKey = ValueForKey( &entities[i], "rendercolor" );
  1703. if ( *pColorKey != '\0' )
  1704. {
  1705. color32 tmp;
  1706. V_StringToColor32( &tmp, pColorKey );
  1707. build.m_DiffuseModulation.r = tmp.r;
  1708. build.m_DiffuseModulation.g = tmp.g;
  1709. build.m_DiffuseModulation.b = tmp.b;
  1710. // don't copy alpha, legacy support uses renderamt
  1711. }
  1712. else
  1713. {
  1714. build.m_DiffuseModulation.r = build.m_DiffuseModulation.g = build.m_DiffuseModulation.b = 255;
  1715. }
  1716. // Get the per-instance render-alpha for this static prop
  1717. const char *pAlphaKey = ValueForKey( &entities[i], "renderamt" );
  1718. if ( *pAlphaKey != '\0' )
  1719. {
  1720. build.m_DiffuseModulation.a = Q_atoi( pAlphaKey );
  1721. }
  1722. else
  1723. {
  1724. build.m_DiffuseModulation.a = 255;
  1725. }
  1726. if ( bDoStaticPropCombine )
  1727. {
  1728. vecBuilds.AddToTail( build );
  1729. }
  1730. else
  1731. {
  1732. AddStaticPropToLump( build );
  1733. }
  1734. // strip this ent from the .bsp file
  1735. entities[i].epairs = 0;
  1736. }
  1737. }
  1738. if ( bDoStaticPropCombine )
  1739. {
  1740. int nCombinePropCount = 0;
  1741. Msg( "\nCombining static props to reduce drawcalls...\n\n" );
  1742. int nOriginalPropCount = vecBuilds.Count();
  1743. if ( staticpropcombine_autocombine || staticpropcombine_suggestcombinerules )
  1744. {
  1745. AutoCreatePropCombineRules( vecBuilds );
  1746. }
  1747. propBuildVector vecCombinedStaticProps;
  1748. while( vecBuilds.Count() )
  1749. {
  1750. //color32 tempColorize = { 0, 255, 0, 255 };
  1751. //tempColorize.r = (byte)(RandomInt(1,10)*25);
  1752. //tempColorize.g = (byte)(RandomInt(1,10)*25);
  1753. //tempColorize.b = (byte)(RandomInt(1,10)*25);
  1754. propBuildVector vecGroup;
  1755. while ( GroupProp( vecBuilds, vecGroup ) ) {}
  1756. if ( vecGroup.Count() > 0 )
  1757. {
  1758. if ( vecGroup.Count() >= g_nAutoCombineMinInstances )
  1759. {
  1760. // set the fade min/max to that of the furthest group member
  1761. float flFadeMinDist = 0;
  1762. float flFadeMaxDist = 0;
  1763. int nCount_STATIC_PROP_IGNORE_NORMALS = 0;
  1764. int nCount_STATIC_PROP_NO_SHADOW = 0;
  1765. int nCount_STATIC_PROP_NO_FLASHLIGHT = 0;
  1766. int nCount_STATIC_PROP_MARKED_FOR_FAST_REFLECTION = 0;
  1767. int nCount_STATIC_PROP_NO_PER_VERTEX_LIGHTING = 0;
  1768. int nCount_STATIC_PROP_NO_SELF_SHADOWING = 0;
  1769. int nCount_STATIC_PROP_FLAGS_EX_DISABLE_SHADOW_DEPTH = 0;
  1770. FOR_EACH_VEC( vecGroup, n )
  1771. {
  1772. flFadeMinDist = MAX( flFadeMinDist, vecGroup[n].m_FadeMinDist );
  1773. flFadeMaxDist = MAX( flFadeMaxDist, vecGroup[n].m_FadeMaxDist );
  1774. #define HelperCheckFlags( _flagname, _flagbits ) if ( vecGroup[n]._flagbits & _flagname ) { nCount_##_flagname++; }
  1775. HelperCheckFlags( STATIC_PROP_IGNORE_NORMALS, m_Flags );
  1776. HelperCheckFlags( STATIC_PROP_NO_SHADOW, m_Flags );
  1777. HelperCheckFlags( STATIC_PROP_NO_FLASHLIGHT, m_Flags );
  1778. HelperCheckFlags( STATIC_PROP_MARKED_FOR_FAST_REFLECTION, m_Flags );
  1779. HelperCheckFlags( STATIC_PROP_NO_PER_VERTEX_LIGHTING, m_Flags );
  1780. HelperCheckFlags( STATIC_PROP_NO_SELF_SHADOWING, m_Flags );
  1781. HelperCheckFlags( STATIC_PROP_FLAGS_EX_DISABLE_SHADOW_DEPTH, m_FlagsEx );
  1782. #undef HelperCheckFlags
  1783. }
  1784. FOR_EACH_VEC( vecGroup, n )
  1785. {
  1786. vecGroup[n].m_FadeMinDist = flFadeMinDist;
  1787. vecGroup[n].m_FadeMaxDist = flFadeMaxDist;
  1788. #define HelperSetFlags_IF_ALL( _flagname, _flagbits ) if ( nCount_##_flagname == vecGroup.Count() ) { vecGroup[n]._flagbits |= _flagname; } else { vecGroup[n]._flagbits &= ~_flagname; }
  1789. #define HelperSetFlags_IF_ANY( _flagname, _flagbits ) if ( nCount_##_flagname > 0 ) { vecGroup[n]._flagbits |= _flagname; } else { vecGroup[n]._flagbits &= ~_flagname; }
  1790. HelperSetFlags_IF_ALL( STATIC_PROP_IGNORE_NORMALS, m_Flags );
  1791. HelperSetFlags_IF_ALL( STATIC_PROP_NO_SHADOW, m_Flags );
  1792. HelperSetFlags_IF_ALL( STATIC_PROP_NO_FLASHLIGHT, m_Flags );
  1793. HelperSetFlags_IF_ANY( STATIC_PROP_MARKED_FOR_FAST_REFLECTION, m_Flags );
  1794. HelperSetFlags_IF_ALL( STATIC_PROP_NO_PER_VERTEX_LIGHTING, m_Flags );
  1795. HelperSetFlags_IF_ALL( STATIC_PROP_NO_SELF_SHADOWING, m_Flags );
  1796. HelperSetFlags_IF_ANY( STATIC_PROP_FLAGS_EX_DISABLE_SHADOW_DEPTH, m_FlagsEx );
  1797. #undef HelperSetFlags_IF_ALL
  1798. #undef HelperSetFlags_IF_ANY
  1799. }
  1800. CombineStaticProps( vecGroup, nCombinePropCount );
  1801. //vecGroup[0].m_DiffuseModulation = tempColorize;
  1802. vecCombinedStaticProps.AddToTail( vecGroup[ 0 ] );
  1803. }
  1804. else
  1805. {
  1806. for ( int i = 0; i < vecGroup.Count(); i++ )
  1807. {
  1808. vecCombinedStaticProps.AddToTail( vecGroup[ i ] );
  1809. }
  1810. }
  1811. }
  1812. else
  1813. {
  1814. vecCombinedStaticProps.AddToTail( vecBuilds[0] );
  1815. vecBuilds.Remove(0);
  1816. }
  1817. }
  1818. Msg( "\nCompleted static prop combine.\n" );
  1819. FOR_EACH_VEC( g_vecCombineRules, i )
  1820. {
  1821. if ( g_vecCombineRules[i].m_vecStatsMemberCounts.Count() )
  1822. {
  1823. int nSum = 0;
  1824. FOR_EACH_VEC( g_vecCombineRules[i].m_vecStatsMemberCounts, j )
  1825. {
  1826. nSum += g_vecCombineRules[i].m_vecStatsMemberCounts[j];
  1827. }
  1828. float flAvg = ((float)(nSum)) / ((float)(g_vecCombineRules[i].m_vecStatsMemberCounts.Count()));
  1829. Msg( "%i clusters of group \"%s\",\taverage %.1f models\n",
  1830. g_vecCombineRules[i].m_vecStatsMemberCounts.Count(),
  1831. g_vecCombineRules[i].m_szGroupName.Get(),
  1832. flAvg );
  1833. }
  1834. }
  1835. int nPropRemovedCount = nOriginalPropCount - vecCombinedStaticProps.Count();
  1836. Msg( "Props combined away: %i\n", nPropRemovedCount );
  1837. Msg( "Cluster models built: %i\n\n", nCombinePropCount );
  1838. FOR_EACH_VEC( vecCombinedStaticProps, i )
  1839. {
  1840. AddStaticPropToLump( vecCombinedStaticProps[i] );
  1841. }
  1842. }
  1843. // Strip out lighting origins; has to be done here because they are used when
  1844. // static props are made
  1845. for ( i = s_LightingInfo.Count(); --i >= 0; )
  1846. {
  1847. // strip this ent from the .bsp file
  1848. entities[s_LightingInfo[i]].epairs = 0;
  1849. }
  1850. SetLumpData( );
  1851. }
  1852. static studiohdr_t *g_pActiveStudioHdr;
  1853. static void SetCurrentModel( studiohdr_t *pStudioHdr )
  1854. {
  1855. // track the correct model
  1856. g_pActiveStudioHdr = pStudioHdr;
  1857. }
  1858. static void FreeCurrentModelVertexes()
  1859. {
  1860. Assert( g_pActiveStudioHdr );
  1861. if ( g_pActiveStudioHdr->VertexBase() )
  1862. {
  1863. free( g_pActiveStudioHdr->VertexBase() );
  1864. g_pActiveStudioHdr->SetVertexBase( NULL );
  1865. }
  1866. }
  1867. const vertexFileHeader_t * mstudiomodel_t::CacheVertexData( void * pModelData )
  1868. {
  1869. studiohdr_t *pActiveStudioHdr = static_cast<studiohdr_t *>(pModelData);
  1870. if ( pActiveStudioHdr->VertexBase() )
  1871. {
  1872. return (vertexFileHeader_t *)pActiveStudioHdr->VertexBase();
  1873. }
  1874. // mandatory callback to make requested data resident
  1875. // load and persist the vertex file
  1876. char fileName[260];
  1877. strcpy( fileName, "models/" );
  1878. strcat( fileName, pActiveStudioHdr->pszName() );
  1879. Q_StripExtension( fileName, fileName, sizeof( fileName ) );
  1880. strcat( fileName, ".vvd" );
  1881. // load the model
  1882. CUtlBuffer bufFileData;
  1883. if ( !GetMapDataFilesMgr()->ReadRegisteredFile( fileName, bufFileData ) &&
  1884. !g_pFileSystem->ReadFile( fileName, NULL, bufFileData ) )
  1885. {
  1886. if ( V_stristr( fileName, "_autocombine_" ) && FileExistsInPak( GetPakFile(), fileName ) )
  1887. {
  1888. if ( !ReadFileFromPak( GetPakFile(), fileName, false, bufFileData ) )
  1889. Error( "Unable to load vertex data \"%s\"\n", fileName );
  1890. }
  1891. else
  1892. {
  1893. Error( "Unable to load vertex data \"%s\"\n", fileName );
  1894. }
  1895. }
  1896. // Get the file size
  1897. int vvdSize = bufFileData.TellPut();
  1898. if (vvdSize == 0)
  1899. {
  1900. Error( "Bad size for vertex data \"%s\"\n", fileName );
  1901. }
  1902. vertexFileHeader_t *pVvdHdr = (vertexFileHeader_t *) bufFileData.Base();
  1903. // check header
  1904. if ( pVvdHdr->id != MODEL_VERTEX_FILE_ID )
  1905. {
  1906. Error("Error Vertex File %s id %d should be %d\n", fileName, pVvdHdr->id, MODEL_VERTEX_FILE_ID);
  1907. }
  1908. if ( pVvdHdr->version != MODEL_VERTEX_FILE_VERSION )
  1909. {
  1910. Error("Error Vertex File %s version %d should be %d\n", fileName, pVvdHdr->version, MODEL_VERTEX_FILE_VERSION);
  1911. }
  1912. if ( pVvdHdr->checksum != pActiveStudioHdr->checksum )
  1913. {
  1914. Error("Error Vertex File %s checksum %d should be %d\n", fileName, pVvdHdr->checksum, pActiveStudioHdr->checksum);
  1915. }
  1916. // need to perform mesh relocation fixups
  1917. // allocate a new copy
  1918. vertexFileHeader_t *pNewVvdHdr = (vertexFileHeader_t *)malloc( vvdSize );
  1919. if ( !pNewVvdHdr )
  1920. {
  1921. Error( "Error allocating %d bytes for Vertex File '%s'\n", vvdSize, fileName );
  1922. }
  1923. // load vertexes and run fixups
  1924. bool bExtraData = (pActiveStudioHdr->flags & STUDIOHDR_FLAGS_EXTRA_VERTEX_DATA) != 0;
  1925. Studio_LoadVertexes(pVvdHdr, pNewVvdHdr, 0, true, bExtraData);
  1926. // discard original
  1927. pVvdHdr = pNewVvdHdr;
  1928. pActiveStudioHdr->SetVertexBase( (void*)pVvdHdr );
  1929. return pVvdHdr;
  1930. }