Team Fortress 2 Source Code as on 22/4/2020
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.

766 lines
18 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. // $NoKeywords: $
  6. //
  7. //=============================================================================//
  8. #include "incremental.h"
  9. #include "lightmap.h"
  10. static bool g_bFileError = false;
  11. // -------------------------------------------------------------------------------- //
  12. // Static helpers.
  13. // -------------------------------------------------------------------------------- //
  14. static bool CompareLights( dworldlight_t *a, dworldlight_t *b )
  15. {
  16. static float flEpsilon = 1e-7;
  17. bool a1 = VectorsAreEqual( a->origin, b->origin, flEpsilon );
  18. bool a2 = VectorsAreEqual( a->intensity, b->intensity, 1.1f ); // intensities are huge numbers
  19. bool a3 = VectorsAreEqual( a->normal, b->normal, flEpsilon );
  20. bool a4 = fabs( a->constant_attn - b->constant_attn ) < flEpsilon;
  21. bool a5 = fabs( a->linear_attn - b->linear_attn ) < flEpsilon;
  22. bool a6 = fabs( a->quadratic_attn - b->quadratic_attn ) < flEpsilon;
  23. bool a7 = fabs( float( a->flags - b->flags ) ) < flEpsilon;
  24. bool a8 = fabs( a->stopdot - b->stopdot ) < flEpsilon;
  25. bool a9 = fabs( a->stopdot2 - b->stopdot2 ) < flEpsilon;
  26. bool a10 = fabs( a->exponent - b->exponent ) < flEpsilon;
  27. bool a11 = fabs( a->radius - b->radius ) < flEpsilon;
  28. return a1 && a2 && a3 && a4 && a5 && a6 && a7 && a8 && a9 && a10 && a11;
  29. }
  30. long FileOpen( char const *pFilename, bool bRead )
  31. {
  32. g_bFileError = false;
  33. return (long)g_pFileSystem->Open( pFilename, bRead ? "rb" : "wb" );
  34. }
  35. void FileClose( long fp )
  36. {
  37. if( fp )
  38. g_pFileSystem->Close( (FILE*)fp );
  39. }
  40. // Returns true if there was an error reading from the file.
  41. bool FileError()
  42. {
  43. return g_bFileError;
  44. }
  45. static inline void FileRead( long fp, void *pOut, int size )
  46. {
  47. if( g_bFileError || g_pFileSystem->Read( pOut, size, (FileHandle_t)fp ) != size )
  48. {
  49. g_bFileError = true;
  50. memset( pOut, 0, size );
  51. }
  52. }
  53. template<class T>
  54. static inline void FileRead( long fp, T &out )
  55. {
  56. FileRead( fp, &out, sizeof(out) );
  57. }
  58. static inline void FileWrite( long fp, void const *pData, int size )
  59. {
  60. if( g_bFileError || g_pFileSystem->Write( pData, size, (FileHandle_t)fp ) != size )
  61. {
  62. g_bFileError = true;
  63. }
  64. }
  65. template<class T>
  66. static inline void FileWrite( long fp, T out )
  67. {
  68. FileWrite( fp, &out, sizeof(out) );
  69. }
  70. IIncremental* GetIncremental()
  71. {
  72. static CIncremental inc;
  73. return &inc;
  74. }
  75. // -------------------------------------------------------------------------------- //
  76. // CIncremental.
  77. // -------------------------------------------------------------------------------- //
  78. CIncremental::CIncremental()
  79. {
  80. m_TotalMemory = 0;
  81. m_pIncrementalFilename = NULL;
  82. m_pBSPFilename = NULL;
  83. m_bSuccessfulRun = false;
  84. }
  85. CIncremental::~CIncremental()
  86. {
  87. }
  88. bool CIncremental::Init( char const *pBSPFilename, char const *pIncrementalFilename )
  89. {
  90. m_pBSPFilename = pBSPFilename;
  91. m_pIncrementalFilename = pIncrementalFilename;
  92. return true;
  93. }
  94. bool CIncremental::PrepareForLighting()
  95. {
  96. if( !m_pBSPFilename )
  97. return false;
  98. // Clear the touched faces list.
  99. m_FacesTouched.SetSize( numfaces );
  100. memset( m_FacesTouched.Base(), 0, numfaces );
  101. // If we haven't done a complete successful run yet, then we either haven't
  102. // loaded the lights, or a run was aborted and our lights are half-done so we
  103. // should reload them.
  104. if( !m_bSuccessfulRun )
  105. LoadIncrementalFile();
  106. // unmatched = a list of the lights we have
  107. CUtlLinkedList<int,int> unmatched;
  108. for( int i=m_Lights.Head(); i != m_Lights.InvalidIndex(); i = m_Lights.Next(i) )
  109. unmatched.AddToTail( i );
  110. // Match the light lists and get rid of lights that we already have all the data for.
  111. directlight_t *pNext;
  112. directlight_t **pPrev = &activelights;
  113. for( directlight_t *dl=activelights; dl != NULL; dl = pNext )
  114. {
  115. pNext = dl->next;
  116. //float flClosest = 3000000000;
  117. //CIncLight *pClosest = 0;
  118. // Look for this light in our light list.
  119. int iNextUnmatched, iUnmatched;
  120. for( iUnmatched=unmatched.Head(); iUnmatched != unmatched.InvalidIndex(); iUnmatched = iNextUnmatched )
  121. {
  122. iNextUnmatched = unmatched.Next( iUnmatched );
  123. CIncLight *pLight = m_Lights[ unmatched[iUnmatched] ];
  124. //float flTest = (pLight->m_Light.origin - dl->light.origin).Length();
  125. //if( flTest < flClosest )
  126. //{
  127. // flClosest = flTest;
  128. // pClosest = pLight;
  129. //}
  130. if( CompareLights( &dl->light, &pLight->m_Light ) )
  131. {
  132. unmatched.Remove( iUnmatched );
  133. // Ok, we have this light's data already, yay!
  134. // Get rid of it from the active light list.
  135. *pPrev = dl->next;
  136. free( dl );
  137. dl = 0;
  138. break;
  139. }
  140. }
  141. //bool bTest=false;
  142. //if(bTest)
  143. // CompareLights( &dl->light, &pClosest->m_Light );
  144. if( iUnmatched == unmatched.InvalidIndex() )
  145. pPrev = &dl->next;
  146. }
  147. // Remove any of our lights that were unmatched.
  148. for( int iUnmatched=unmatched.Head(); iUnmatched != unmatched.InvalidIndex(); iUnmatched = unmatched.Next( iUnmatched ) )
  149. {
  150. CIncLight *pLight = m_Lights[ unmatched[iUnmatched] ];
  151. // First tag faces that it touched so they get recomposited.
  152. for( unsigned short iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) )
  153. {
  154. m_FacesTouched[ pLight->m_LightFaces[iFace]->m_FaceIndex ] = 1;
  155. }
  156. delete pLight;
  157. m_Lights.Remove( unmatched[iUnmatched] );
  158. }
  159. // Now add a light structure for each new light.
  160. AddLightsForActiveLights();
  161. return true;
  162. }
  163. bool CIncremental::ReadIncrementalHeader( long fp, CIncrementalHeader *pHeader )
  164. {
  165. int version;
  166. FileRead( fp, version );
  167. if( version != INCREMENTALFILE_VERSION )
  168. return false;
  169. int nFaces;
  170. FileRead( fp, nFaces );
  171. pHeader->m_FaceLightmapSizes.SetSize( nFaces );
  172. FileRead( fp, pHeader->m_FaceLightmapSizes.Base(), sizeof(CIncrementalHeader::CLMSize) * nFaces );
  173. return !FileError();
  174. }
  175. bool CIncremental::WriteIncrementalHeader( long fp )
  176. {
  177. int version = INCREMENTALFILE_VERSION;
  178. FileWrite( fp, version );
  179. int nFaces = numfaces;
  180. FileWrite( fp, nFaces );
  181. CIncrementalHeader hdr;
  182. hdr.m_FaceLightmapSizes.SetSize( nFaces );
  183. for( int i=0; i < nFaces; i++ )
  184. {
  185. hdr.m_FaceLightmapSizes[i].m_Width = g_pFaces[i].m_LightmapTextureSizeInLuxels[0];
  186. hdr.m_FaceLightmapSizes[i].m_Height = g_pFaces[i].m_LightmapTextureSizeInLuxels[1];
  187. }
  188. FileWrite( fp, hdr.m_FaceLightmapSizes.Base(), sizeof(CIncrementalHeader::CLMSize) * nFaces );
  189. return !FileError();
  190. }
  191. bool CIncremental::IsIncrementalFileValid()
  192. {
  193. long fp = FileOpen( m_pIncrementalFilename, true );
  194. if( !fp )
  195. return false;
  196. bool bValid = false;
  197. CIncrementalHeader hdr;
  198. if( ReadIncrementalHeader( fp, &hdr ) )
  199. {
  200. // If the number of faces is the same and their lightmap sizes are the same,
  201. // then this file is considered a legitimate incremental file.
  202. if( hdr.m_FaceLightmapSizes.Count() == numfaces )
  203. {
  204. int i;
  205. for( i=0; i < numfaces; i++ )
  206. {
  207. if( hdr.m_FaceLightmapSizes[i].m_Width != g_pFaces[i].m_LightmapTextureSizeInLuxels[0] ||
  208. hdr.m_FaceLightmapSizes[i].m_Height != g_pFaces[i].m_LightmapTextureSizeInLuxels[1] )
  209. {
  210. break;
  211. }
  212. }
  213. // Were all faces valid?
  214. if( i == numfaces )
  215. bValid = true;
  216. }
  217. }
  218. FileClose( fp );
  219. return bValid && !FileError();
  220. }
  221. void CIncremental::AddLightToFace(
  222. IncrementalLightID lightID,
  223. int iFace,
  224. int iSample,
  225. int lmSize,
  226. float dot,
  227. int iThread )
  228. {
  229. // If we're not being used, don't do anything.
  230. if( !m_pIncrementalFilename )
  231. return;
  232. CIncLight *pLight = m_Lights[lightID];
  233. // Check for the 99.99% case in which the face already exists.
  234. CLightFace *pFace;
  235. if( pLight->m_pCachedFaces[iThread] &&
  236. pLight->m_pCachedFaces[iThread]->m_FaceIndex == iFace )
  237. {
  238. pFace = pLight->m_pCachedFaces[iThread];
  239. }
  240. else
  241. {
  242. bool bNew;
  243. EnterCriticalSection( &pLight->m_CS );
  244. pFace = pLight->FindOrCreateLightFace( iFace, lmSize, &bNew );
  245. LeaveCriticalSection( &pLight->m_CS );
  246. pLight->m_pCachedFaces[iThread] = pFace;
  247. if( bNew )
  248. m_TotalMemory += pFace->m_LightValues.Count() * sizeof( pFace->m_LightValues[0] );
  249. }
  250. // Add this into the light's data.
  251. pFace->m_LightValues[iSample].m_Dot = dot;
  252. }
  253. unsigned short DecodeCharOrShort( CUtlBuffer *pIn )
  254. {
  255. unsigned short val = pIn->GetUnsignedChar();
  256. if( val & 0x80 )
  257. {
  258. val = ((val & 0x7F) << 8) | pIn->GetUnsignedChar();
  259. }
  260. return val;
  261. }
  262. void EncodeCharOrShort( CUtlBuffer *pBuf, unsigned short val )
  263. {
  264. if( (val & 0xFF80) == 0 )
  265. {
  266. pBuf->PutUnsignedChar( (unsigned char)val );
  267. }
  268. else
  269. {
  270. if( val > 32767 )
  271. val = 32767;
  272. pBuf->PutUnsignedChar( (val >> 8) | 0x80 );
  273. pBuf->PutUnsignedChar( val & 0xFF );
  274. }
  275. }
  276. void DecompressLightData( CUtlBuffer *pIn, CUtlVector<CLightValue> *pOut )
  277. {
  278. int iOut = 0;
  279. while( pIn->TellGet() < pIn->TellPut() )
  280. {
  281. unsigned char runLength = pIn->GetUnsignedChar();
  282. unsigned short usVal = DecodeCharOrShort( pIn );
  283. while( runLength > 0 )
  284. {
  285. --runLength;
  286. pOut->Element(iOut).m_Dot = usVal;
  287. ++iOut;
  288. }
  289. }
  290. }
  291. #ifdef _WIN32
  292. #pragma warning (disable:4701)
  293. #endif
  294. void CompressLightData(
  295. CLightValue const *pValues,
  296. int nValues,
  297. CUtlBuffer *pBuf )
  298. {
  299. unsigned char runLength=0;
  300. unsigned short flLastValue;
  301. for( int i=0; i < nValues; i++ )
  302. {
  303. unsigned short flCurValue = (unsigned short)pValues[i].m_Dot;
  304. if( i == 0 )
  305. {
  306. flLastValue = flCurValue;
  307. runLength = 1;
  308. }
  309. else if( flCurValue == flLastValue && runLength < 255 )
  310. {
  311. ++runLength;
  312. }
  313. else
  314. {
  315. pBuf->PutUnsignedChar( runLength );
  316. EncodeCharOrShort( pBuf, flLastValue );
  317. flLastValue = flCurValue;
  318. runLength = 1;
  319. }
  320. }
  321. // Write the end..
  322. if( runLength )
  323. {
  324. pBuf->PutUnsignedChar( runLength );
  325. EncodeCharOrShort( pBuf, flLastValue );
  326. }
  327. }
  328. #ifdef _WIN32
  329. #pragma warning (default:4701)
  330. #endif
  331. void MultiplyValues( CUtlVector<CLightValue> &values, float scale )
  332. {
  333. for( int i=0; i < values.Count(); i++ )
  334. values[i].m_Dot *= scale;
  335. }
  336. void CIncremental::FinishFace(
  337. IncrementalLightID lightID,
  338. int iFace,
  339. int iThread )
  340. {
  341. CIncLight *pLight = m_Lights[lightID];
  342. // Check for the 99.99% case in which the face already exists.
  343. CLightFace *pFace;
  344. if( pLight->m_pCachedFaces[iThread] && pLight->m_pCachedFaces[iThread]->m_FaceIndex == iFace )
  345. {
  346. pFace = pLight->m_pCachedFaces[iThread];
  347. // Compress the data.
  348. MultiplyValues( pFace->m_LightValues, pLight->m_flMaxIntensity );
  349. pFace->m_CompressedData.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
  350. CompressLightData(
  351. pFace->m_LightValues.Base(),
  352. pFace->m_LightValues.Count(),
  353. &pFace->m_CompressedData );
  354. #if 0
  355. // test decompression
  356. CUtlVector<CLightValue> test;
  357. test.SetSize( 2048 );
  358. pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
  359. DecompressLightData( &pFace->m_CompressedData, &test );
  360. #endif
  361. if( pFace->m_CompressedData.TellPut() == 0 )
  362. {
  363. // No contribution.. delete this face from the light.
  364. EnterCriticalSection( &pLight->m_CS );
  365. pLight->m_LightFaces.Remove( pFace->m_LightFacesIndex );
  366. delete pFace;
  367. LeaveCriticalSection( &pLight->m_CS );
  368. }
  369. else
  370. {
  371. // Discard the uncompressed data.
  372. pFace->m_LightValues.Purge();
  373. m_FacesTouched[ pFace->m_FaceIndex ] = 1;
  374. }
  375. }
  376. }
  377. bool CIncremental::Finalize()
  378. {
  379. // If we're not being used, don't do anything.
  380. if( !m_pIncrementalFilename || !m_pBSPFilename )
  381. return false;
  382. CUtlVector<CFaceLightList> faceLights;
  383. LinkLightsToFaces( faceLights );
  384. Vector faceLight[(MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) * (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2)];
  385. CUtlVector<CLightValue> faceLightValues;
  386. faceLightValues.SetSize( (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) * (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) );
  387. // Only update the faces we've touched.
  388. for( int facenum = 0; facenum < numfaces; facenum++ )
  389. {
  390. if( !m_FacesTouched[facenum] || !faceLights[facenum].Count() )
  391. continue;
  392. int w = g_pFaces[facenum].m_LightmapTextureSizeInLuxels[0]+1;
  393. int h = g_pFaces[facenum].m_LightmapTextureSizeInLuxels[1]+1;
  394. int nLuxels = w * h;
  395. assert( nLuxels <= sizeof(faceLight) / sizeof(faceLight[0]) );
  396. // Clear the lighting for this face.
  397. memset( faceLight, 0, nLuxels * sizeof(Vector) );
  398. // Composite all the light contributions.
  399. for( int iFace=0; iFace < faceLights[facenum].Count(); iFace++ )
  400. {
  401. CLightFace *pFace = faceLights[facenum][iFace];
  402. pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
  403. DecompressLightData( &pFace->m_CompressedData, &faceLightValues );
  404. for( int iSample=0; iSample < nLuxels; iSample++ )
  405. {
  406. float flDot = faceLightValues[iSample].m_Dot;
  407. if( flDot )
  408. {
  409. VectorMA(
  410. faceLight[iSample],
  411. flDot / pFace->m_pLight->m_flMaxIntensity,
  412. pFace->m_pLight->m_Light.intensity,
  413. faceLight[iSample] );
  414. }
  415. }
  416. }
  417. // Convert to the floating-point representation in the BSP file.
  418. Vector *pSrc = faceLight;
  419. unsigned char *pDest = &(*pdlightdata)[ g_pFaces[facenum].lightofs ];
  420. for( int iSample=0; iSample < nLuxels; iSample++ )
  421. {
  422. VectorToColorRGBExp32( *pSrc, *( ColorRGBExp32 *)pDest );
  423. pDest += 4;
  424. pSrc++;
  425. }
  426. }
  427. m_bSuccessfulRun = true;
  428. return true;
  429. }
  430. void CIncremental::GetFacesTouched( CUtlVector<unsigned char> &touched )
  431. {
  432. touched.CopyArray( m_FacesTouched.Base(), m_FacesTouched.Count() );
  433. }
  434. bool CIncremental::Serialize()
  435. {
  436. if( !SaveIncrementalFile() )
  437. return false;
  438. WriteBSPFile( (char*)m_pBSPFilename );
  439. return true;
  440. }
  441. void CIncremental::Term()
  442. {
  443. m_Lights.PurgeAndDeleteElements();
  444. m_TotalMemory = 0;
  445. }
  446. void CIncremental::AddLightsForActiveLights()
  447. {
  448. // Create our lights.
  449. for( directlight_t *dl=activelights; dl != NULL; dl = dl->next )
  450. {
  451. CIncLight *pLight = new CIncLight;
  452. dl->m_IncrementalID = m_Lights.AddToTail( pLight );
  453. // Copy the light information.
  454. pLight->m_Light = dl->light;
  455. pLight->m_flMaxIntensity = max( dl->light.intensity[0], max( dl->light.intensity[1], dl->light.intensity[2] ) );
  456. }
  457. }
  458. bool CIncremental::LoadIncrementalFile()
  459. {
  460. Term();
  461. if( !IsIncrementalFileValid() )
  462. return false;
  463. long fp = FileOpen( m_pIncrementalFilename, true );
  464. if( !fp )
  465. return false;
  466. // Read the header.
  467. CIncrementalHeader hdr;
  468. if( !ReadIncrementalHeader( fp, &hdr ) )
  469. {
  470. FileClose( fp );
  471. return false;
  472. }
  473. // Read the lights.
  474. int nLights;
  475. FileRead( fp, nLights );
  476. for( int iLight=0; iLight < nLights; iLight++ )
  477. {
  478. CIncLight *pLight = new CIncLight;
  479. m_Lights.AddToTail( pLight );
  480. FileRead( fp, pLight->m_Light );
  481. pLight->m_flMaxIntensity =
  482. max( pLight->m_Light.intensity.x,
  483. max( pLight->m_Light.intensity.y, pLight->m_Light.intensity.z ) );
  484. int nFaces;
  485. FileRead( fp, nFaces );
  486. assert( nFaces < 70000 );
  487. for( int iFace=0; iFace < nFaces; iFace++ )
  488. {
  489. CLightFace *pFace = new CLightFace;
  490. pLight->m_LightFaces.AddToTail( pFace );
  491. pFace->m_pLight = pLight;
  492. FileRead( fp, pFace->m_FaceIndex );
  493. int dataSize;
  494. FileRead( fp, dataSize );
  495. pFace->m_CompressedData.SeekPut( CUtlBuffer::SEEK_HEAD, 0 );
  496. while( dataSize )
  497. {
  498. --dataSize;
  499. unsigned char ucData;
  500. FileRead( fp, ucData );
  501. pFace->m_CompressedData.PutUnsignedChar( ucData );
  502. }
  503. }
  504. }
  505. FileClose( fp );
  506. return !FileError();
  507. }
  508. bool CIncremental::SaveIncrementalFile()
  509. {
  510. long fp = FileOpen( m_pIncrementalFilename, false );
  511. if( !fp )
  512. return false;
  513. if( !WriteIncrementalHeader( fp ) )
  514. {
  515. FileClose( fp );
  516. return false;
  517. }
  518. // Write the lights.
  519. int nLights = m_Lights.Count();
  520. FileWrite( fp, nLights );
  521. for( int iLight=m_Lights.Head(); iLight != m_Lights.InvalidIndex(); iLight = m_Lights.Next( iLight ) )
  522. {
  523. CIncLight *pLight = m_Lights[iLight];
  524. FileWrite( fp, pLight->m_Light );
  525. int nFaces = pLight->m_LightFaces.Count();
  526. FileWrite( fp, nFaces );
  527. for( int iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) )
  528. {
  529. CLightFace *pFace = pLight->m_LightFaces[iFace];
  530. FileWrite( fp, pFace->m_FaceIndex );
  531. int dataSize = pFace->m_CompressedData.TellPut();
  532. FileWrite( fp, dataSize );
  533. pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 );
  534. while( dataSize )
  535. {
  536. --dataSize;
  537. FileWrite( fp, pFace->m_CompressedData.GetUnsignedChar() );
  538. }
  539. }
  540. }
  541. FileClose( fp );
  542. return !FileError();
  543. }
  544. void CIncremental::LinkLightsToFaces( CUtlVector<CFaceLightList> &faceLights )
  545. {
  546. faceLights.SetSize( numfaces );
  547. for( int iLight=m_Lights.Head(); iLight != m_Lights.InvalidIndex(); iLight = m_Lights.Next( iLight ) )
  548. {
  549. CIncLight *pLight = m_Lights[iLight];
  550. for( int iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) )
  551. {
  552. CLightFace *pFace = pLight->m_LightFaces[iFace];
  553. if( m_FacesTouched[pFace->m_FaceIndex] )
  554. faceLights[ pFace->m_FaceIndex ].AddToTail( pFace );
  555. }
  556. }
  557. }
  558. // ------------------------------------------------------------------ //
  559. // CIncLight
  560. // ------------------------------------------------------------------ //
  561. CIncLight::CIncLight()
  562. {
  563. memset( m_pCachedFaces, 0, sizeof(m_pCachedFaces) );
  564. InitializeCriticalSection( &m_CS );
  565. }
  566. CIncLight::~CIncLight()
  567. {
  568. m_LightFaces.PurgeAndDeleteElements();
  569. DeleteCriticalSection( &m_CS );
  570. }
  571. CLightFace* CIncLight::FindOrCreateLightFace( int iFace, int lmSize, bool *bNew )
  572. {
  573. if( bNew )
  574. *bNew = false;
  575. // Look for it.
  576. for( int i=m_LightFaces.Head(); i != m_LightFaces.InvalidIndex(); i=m_LightFaces.Next(i) )
  577. {
  578. CLightFace *pFace = m_LightFaces[i];
  579. if( pFace->m_FaceIndex == iFace )
  580. {
  581. assert( pFace->m_LightValues.Count() == lmSize );
  582. return pFace;
  583. }
  584. }
  585. // Ok, create one.
  586. CLightFace *pFace = new CLightFace;
  587. pFace->m_LightFacesIndex = m_LightFaces.AddToTail( pFace );
  588. pFace->m_pLight = this;
  589. pFace->m_FaceIndex = iFace;
  590. pFace->m_LightValues.SetSize( lmSize );
  591. memset( pFace->m_LightValues.Base(), 0, sizeof( CLightValue ) * lmSize );
  592. if( bNew )
  593. *bNew = true;
  594. return pFace;
  595. }