W3DSnow.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. /*
  2. ** Command & Conquer Generals Zero Hour(tm)
  3. ** Copyright 2025 Electronic Arts Inc.
  4. **
  5. ** This program is free software: you can redistribute it and/or modify
  6. ** it under the terms of the GNU General Public License as published by
  7. ** the Free Software Foundation, either version 3 of the License, or
  8. ** (at your option) any later version.
  9. **
  10. ** This program is distributed in the hope that it will be useful,
  11. ** but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ** GNU General Public License for more details.
  14. **
  15. ** You should have received a copy of the GNU General Public License
  16. ** along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. // FILE: W3DSnow.h /////////////////////////////////////////////////////////
  19. #include "W3DDevice/GameClient/W3DSnow.h"
  20. #include "W3DDevice/GameClient/heightmap.h"
  21. #include "GameClient/View.h"
  22. #include "WW3D2/dx8wrapper.h"
  23. #include "WW3D2/rinfo.h"
  24. #include "WW3D2/camera.h"
  25. #include "WW3D2/assetmgr.h"
  26. #ifdef _INTERNAL
  27. // for occasional debugging...
  28. //#pragma optimize("", off)
  29. //#pragma MESSAGE("************************************** WARNING, optimization disabled for debugging purposes")
  30. #endif
  31. #define D3DFVF_POINTVERTEX (D3DFVF_XYZ)
  32. #define SNOW_BUFFER_SIZE 4096 //size of vertex buffer holding particles.
  33. #define SNOW_BATCH_SIZE 2048 //we render at most this many particles per drawprimitive call. This number * 6 must be less than 65536 to fit into index buffer.
  34. struct POINTVERTEX
  35. {
  36. Vector3 v; //center of particle.
  37. };
  38. W3DSnowManager::W3DSnowManager(void)
  39. {
  40. m_indexBuffer=NULL;
  41. m_snowTexture=NULL;
  42. m_VertexBufferD3D=NULL;
  43. }
  44. W3DSnowManager::~W3DSnowManager()
  45. {
  46. ReleaseResources();
  47. }
  48. void W3DSnowManager::init( void )
  49. {
  50. SnowManager::init();
  51. ReAcquireResources();
  52. }
  53. /** Releases all W3D/D3D assets before a reset.. */
  54. void W3DSnowManager::ReleaseResources(void)
  55. {
  56. REF_PTR_RELEASE(m_snowTexture);
  57. if (m_VertexBufferD3D)
  58. m_VertexBufferD3D->Release();
  59. m_VertexBufferD3D=NULL;
  60. REF_PTR_RELEASE(m_indexBuffer);
  61. }
  62. /** (Re)allocates all W3D/D3D assets after a reset.. */
  63. Bool W3DSnowManager::ReAcquireResources(void)
  64. {
  65. ReleaseResources();
  66. if (!TheWeatherSetting->m_snowEnabled)
  67. return TRUE; //no need for resources if snow is disabled.
  68. if (TheWeatherSetting->m_usePointSprites && DX8Wrapper::Get_Current_Caps()->Support_PointSprites())
  69. {
  70. LPDIRECT3DDEVICE8 m_pDev=DX8Wrapper::_Get_D3D_Device8();
  71. DEBUG_ASSERTCRASH(m_pDev, ("Trying to ReAquireResources on W3DSnowManager without device"));
  72. if (m_VertexBufferD3D == NULL)
  73. { // Create vertex buffer
  74. if (FAILED(m_pDev->CreateVertexBuffer
  75. (
  76. SNOW_BUFFER_SIZE*sizeof(POINTVERTEX),
  77. D3DUSAGE_WRITEONLY|D3DUSAGE_DYNAMIC|D3DUSAGE_POINTS,
  78. D3DFVF_POINTVERTEX,
  79. D3DPOOL_DEFAULT,
  80. &m_VertexBufferD3D
  81. )))
  82. return FALSE;
  83. }
  84. }
  85. else
  86. {
  87. m_indexBuffer=NEW_REF(DX8IndexBufferClass,(SNOW_BATCH_SIZE *6)); //allocate 2 triangles per flake, each with 3 indices.
  88. // Fill up the IB with static vertex indices that will be used for all smudges.
  89. {
  90. DX8IndexBufferClass::WriteLockClass lockIdxBuffer(m_indexBuffer);
  91. UnsignedShort *ib=lockIdxBuffer.Get_Index_Array();
  92. //quad of 4 triangles:
  93. // 0-----3
  94. // |\ /|
  95. // | X |
  96. // |/ \|
  97. // 1-----2
  98. Int vbCount=0;
  99. for (Int i=0; i<SNOW_BATCH_SIZE; i++)
  100. {
  101. //Top
  102. ib[0]=vbCount+3;
  103. ib[1]=vbCount;
  104. ib[2]=vbCount+2;
  105. //Bottom
  106. ib[3]=vbCount+2;
  107. ib[4]=vbCount;
  108. ib[5]=vbCount+1;
  109. vbCount += 4;
  110. ib+=6;
  111. }
  112. }
  113. }
  114. m_snowTexture = WW3DAssetManager::Get_Instance()->Get_Texture(TheWeatherSetting->m_snowTexture.str());
  115. m_dwBase = SNOW_BUFFER_SIZE;
  116. m_dwDiscard = SNOW_BUFFER_SIZE;
  117. m_dwFlush = SNOW_BATCH_SIZE;
  118. return TRUE;
  119. }
  120. void W3DSnowManager::updateIniSettings(void)
  121. {
  122. //Call base class
  123. SnowManager::updateIniSettings();
  124. if (m_snowTexture && stricmp(m_snowTexture->Get_Texture_Name(),TheWeatherSetting->m_snowTexture.str()) != 0)
  125. {
  126. REF_PTR_RELEASE(m_snowTexture);
  127. m_snowTexture = WW3DAssetManager::Get_Instance()->Get_Texture(TheWeatherSetting->m_snowTexture.str());
  128. }
  129. }
  130. void W3DSnowManager::reset( void )
  131. {
  132. SnowManager::reset();
  133. }
  134. void W3DSnowManager::update(void)
  135. {
  136. m_time += WW3D::Get_Frame_Time() / 1000.0f;
  137. //find current time offset, adjusting for overflow
  138. m_time=fmod(m_time,m_fullTimePeriod);
  139. }
  140. #define MAXIMUM_CAMERA_DISTANCE 100000 //maximum distance of camera position from world origin.
  141. #define ISPOW2(x) (x && (x & (x-1)) == 0) //is a number a power of 2?
  142. #define MODPOW2(x,y) ((x) & (y-1)) //mod '%' operator for powers of 2.
  143. // Helper function to stuff a FLOAT into a DWORD argument
  144. inline DWORD FtoDW( FLOAT f ) { return *((DWORD*)&f); }
  145. /*Recursively subdivide the large snow box enclosing the camera until we reach some predefined leaf size. This
  146. method is used so that very few off-screen particles end up getting rendered. Culling them individually would
  147. be too expensive since we're dealing with 1000's for this effect.*/
  148. void W3DSnowManager::renderSubBox(RenderInfoClass &rinfo, Int originX, Int originY, Int cubeDimX, Int cubeDimY )
  149. {
  150. //check if this box is too large and needs subdivision
  151. Int boxDimX=cubeDimX - originX;
  152. Int boxDimY=cubeDimY - originY;
  153. Int halfX=REAL_TO_INT_CEIL(boxDimX*0.5f);
  154. Int halfY=REAL_TO_INT_CEIL(boxDimY*0.5f);
  155. CameraClass &camera=rinfo.Camera;
  156. MinMaxAABoxClass mmbox;
  157. if (boxDimX > m_leafDim)
  158. { //subdivide the box
  159. if (boxDimY > m_leafDim)
  160. { //subdivide in both directions
  161. //Upper left
  162. mmbox.MinCorner.Set(originX*m_emitterSpacing-m_cullOverscan, (originY + halfY)*m_emitterSpacing-m_cullOverscan, m_snowCeiling-m_boxDimensions);
  163. mmbox.MaxCorner.Set((originX + halfX)*m_emitterSpacing+m_cullOverscan, cubeDimY*m_emitterSpacing+m_cullOverscan, m_snowCeiling);
  164. if (CollisionMath::Overlap_Test(camera.Get_Frustum(),mmbox) != CollisionMath::OUTSIDE)
  165. renderSubBox(rinfo, originX, originY + halfY, originX + halfX, cubeDimY);
  166. //Upper right
  167. mmbox.MinCorner.Set((originX + halfX)*m_emitterSpacing-m_cullOverscan, (originY + halfY)*m_emitterSpacing-m_cullOverscan, m_snowCeiling-m_boxDimensions);
  168. mmbox.MaxCorner.Set(cubeDimX*m_emitterSpacing+m_cullOverscan, cubeDimY*m_emitterSpacing+m_cullOverscan, m_snowCeiling);
  169. if (CollisionMath::Overlap_Test(camera.Get_Frustum(),mmbox) != CollisionMath::OUTSIDE)
  170. renderSubBox(rinfo, originX + halfX, originY + halfY,cubeDimX, cubeDimY);
  171. //Lower left
  172. mmbox.MinCorner.Set(originX*m_emitterSpacing-m_cullOverscan, originY*m_emitterSpacing-m_cullOverscan, m_snowCeiling-m_boxDimensions);
  173. mmbox.MaxCorner.Set((originX + halfX)*m_emitterSpacing+m_cullOverscan, (originY + halfY)*m_emitterSpacing+m_cullOverscan, m_snowCeiling);
  174. if (CollisionMath::Overlap_Test(camera.Get_Frustum(),mmbox) != CollisionMath::OUTSIDE)
  175. renderSubBox(rinfo, originX,originY,originX + halfX, originY + halfY);
  176. //Lower right
  177. mmbox.MinCorner.Set((originX + halfX)*m_emitterSpacing-m_cullOverscan, originY*m_emitterSpacing-m_cullOverscan, m_snowCeiling-m_boxDimensions);
  178. mmbox.MaxCorner.Set(cubeDimX*m_emitterSpacing+m_cullOverscan, (originY + halfY)*m_emitterSpacing+m_cullOverscan, m_snowCeiling);
  179. if (CollisionMath::Overlap_Test(camera.Get_Frustum(),mmbox) != CollisionMath::OUTSIDE)
  180. renderSubBox(rinfo, originX + halfX, originY, cubeDimX, originY + halfY);
  181. return;
  182. }
  183. else
  184. { //only subdivide in x direction.
  185. //Left
  186. mmbox.MinCorner.Set(originX*m_emitterSpacing-m_cullOverscan, originY*m_emitterSpacing-m_cullOverscan, m_snowCeiling-m_boxDimensions);
  187. mmbox.MaxCorner.Set((originX + halfX)*m_emitterSpacing+m_cullOverscan, cubeDimY*m_emitterSpacing+m_cullOverscan, m_snowCeiling);
  188. if (CollisionMath::Overlap_Test(camera.Get_Frustum(),mmbox) != CollisionMath::OUTSIDE)
  189. renderSubBox(rinfo, originX, originY, originX + halfX, cubeDimY);
  190. //Right
  191. mmbox.MinCorner.Set((originX + halfX)*m_emitterSpacing-m_cullOverscan, originY*m_emitterSpacing-m_cullOverscan, m_snowCeiling-m_boxDimensions);
  192. mmbox.MaxCorner.Set(cubeDimX*m_emitterSpacing+m_cullOverscan, cubeDimY*m_emitterSpacing+m_cullOverscan, m_snowCeiling);
  193. if (CollisionMath::Overlap_Test(camera.Get_Frustum(),mmbox) != CollisionMath::OUTSIDE)
  194. renderSubBox(rinfo, originX + halfX, originY, cubeDimX, cubeDimY);
  195. return;
  196. }
  197. }
  198. else
  199. if (boxDimY > m_leafDim)
  200. { //only subdivide in y direction
  201. //Top
  202. mmbox.MinCorner.Set(originX*m_emitterSpacing-m_cullOverscan, (originY+halfY)*m_emitterSpacing-m_cullOverscan, m_snowCeiling-m_boxDimensions);
  203. mmbox.MaxCorner.Set(cubeDimX*m_emitterSpacing+m_cullOverscan, cubeDimY*m_emitterSpacing+m_cullOverscan, m_snowCeiling);
  204. if (CollisionMath::Overlap_Test(camera.Get_Frustum(),mmbox) != CollisionMath::OUTSIDE)
  205. renderSubBox(rinfo, originX, originY+halfY,cubeDimX, cubeDimY);
  206. //Bottom
  207. mmbox.MinCorner.Set(originX*m_emitterSpacing-m_cullOverscan, originY*m_emitterSpacing-m_cullOverscan, m_snowCeiling-m_boxDimensions);
  208. mmbox.MaxCorner.Set(cubeDimX*m_emitterSpacing+m_cullOverscan, (originY + halfY)*m_emitterSpacing+m_cullOverscan, m_snowCeiling);
  209. if (CollisionMath::Overlap_Test(camera.Get_Frustum(),mmbox) != CollisionMath::OUTSIDE)
  210. renderSubBox(rinfo, originX, originY, cubeDimX, originY + halfY);
  211. return;
  212. }
  213. //Box too small to subdivide so render it.
  214. //Find total number of particles that need rendering.
  215. Int totalPart=(cubeDimY-originY)*(cubeDimX-originX);
  216. if (!totalPart)
  217. return; //nothing to render.
  218. Int y=originY; //loop counter.
  219. Int cubeOriginXRemainder = originX; //loop counter - adjusted when not all particles fit into render buffer.
  220. Vector3 snowCenter;
  221. m_totalRendered += totalPart;
  222. while (totalPart)
  223. {
  224. Int batchSize=totalPart;
  225. if (batchSize > m_dwFlush)
  226. batchSize = m_dwFlush;
  227. if((m_dwBase + batchSize) > m_dwDiscard)
  228. m_dwBase = 0;
  229. POINTVERTEX* verts;
  230. if(m_VertexBufferD3D->Lock(m_dwBase * sizeof(POINTVERTEX), batchSize * sizeof(POINTVERTEX),
  231. (unsigned char **) &verts, m_dwBase ? D3DLOCK_NOOVERWRITE : D3DLOCK_DISCARD) != D3D_OK )
  232. return; //couldn't lock buffer.
  233. Int numberInBatch=0;
  234. for (;y<cubeDimY; y++)
  235. {
  236. for (Int x=cubeOriginXRemainder; x<cubeDimX; x++)
  237. {
  238. if (numberInBatch >= batchSize)
  239. { cubeOriginXRemainder = x;
  240. goto flush_particles;
  241. }
  242. //Get initial height from noise table. We add a large value to make sure it's positive. Then
  243. //modulate by table dimensions to find a value.
  244. Int noiseOffset=MODPOW2(x+MAXIMUM_CAMERA_DISTANCE,SNOW_NOISE_X)+MODPOW2(y+MAXIMUM_CAMERA_DISTANCE,SNOW_NOISE_Y)*SNOW_NOISE_X;
  245. if (noiseOffset > (SNOW_NOISE_X * SNOW_NOISE_Y))
  246. noiseOffset = 0; //this should never happen but check to prevent buffer over/under flow.
  247. //find current height
  248. Real h0=m_snowCeiling-fmod(m_heightTraveled+m_startingHeights[noiseOffset],m_boxDimensions);
  249. //find world-space position of snow flake
  250. snowCenter.Set(x*m_emitterSpacing,y*m_emitterSpacing,h0);
  251. //Adjust position so snow flakes don't fall straight down.
  252. snowCenter.X += m_amplitude * WWMath::Fast_Sin( h0 * m_frequencyScaleX + (Real)x);
  253. snowCenter.Y += m_amplitude * WWMath::Fast_Sin( h0 * m_frequencyScaleY + (Real)y);
  254. *(Vector3 *)verts=snowCenter;
  255. verts++;
  256. numberInBatch++;
  257. }
  258. //getting here means we did not overflow the render buffer, so reset x origin to normal.
  259. cubeOriginXRemainder = originX; //reset to normal amount
  260. }
  261. flush_particles:
  262. m_VertexBufferD3D->Unlock();
  263. //Render any particles that may be queued up.
  264. if (numberInBatch)
  265. {
  266. Debug_Statistics::Record_DX8_Polys_And_Vertices(numberInBatch*2,numberInBatch*4,ShaderClass::_PresetOpaqueShader);
  267. DX8Wrapper::_Get_D3D_Device8()->DrawPrimitive( D3DPT_POINTLIST, m_dwBase, numberInBatch);
  268. totalPart -= numberInBatch;
  269. m_dwBase += numberInBatch;
  270. }
  271. }
  272. }
  273. void W3DSnowManager::render(RenderInfoClass &rinfo)
  274. {
  275. if (!TheWeatherSetting->m_snowEnabled || !m_isVisible)
  276. return;
  277. Int usePointSprites = DX8Wrapper::Get_Current_Caps()->Support_PointSprites() && TheWeatherSetting->m_usePointSprites;
  278. //make sure the noise table is powers of 2 in dimensions.
  279. WWASSERT(ISPOW2(SNOW_NOISE_X) && ISPOW2(SNOW_NOISE_Y));
  280. //CameraClass &camera=rinfo.Camera;
  281. const Coord3D &cPos=TheTacticalView->get3DCameraPosition();
  282. Vector3 camPos(cPos.x,cPos.y,cPos.z);
  283. //Number of emitters from cube center to edge of visible extent.
  284. Int mumEmittersInHalf=(Int)floor(m_boxDimensions / m_emitterSpacing * 0.5f);
  285. //Find origin of visible cube surrounding camera.
  286. Int cubeCenterX=(Int)floor(camPos.X/m_emitterSpacing);
  287. Int cubeCenterY=(Int)floor(camPos.Y/m_emitterSpacing);
  288. //Find extents of visible cube surrounding camera.
  289. Int cubeOriginX=cubeCenterX - mumEmittersInHalf; //top/left extents.
  290. Int cubeOriginY=cubeCenterY - mumEmittersInHalf;
  291. Int cubeDimX=cubeCenterX + mumEmittersInHalf; //bottom/right extents.
  292. Int cubeDimY=cubeCenterY + mumEmittersInHalf;
  293. const FrustumClass & frustum = rinfo.Camera.Get_Frustum();
  294. AABoxClass bbox;
  295. //Get a bounding box around our visible universe. Bounded by terrain and the sky
  296. //so much tighter fitting volume than what's actually visible. This will cull
  297. //particles falling under the ground.
  298. TheTerrainRenderObject->getMaximumVisibleBox(frustum, &bbox, TRUE);
  299. //Particles move outside the visible box as a result of local sine movement
  300. //so adjust bounding box to include them.
  301. bbox.Extent.X += m_amplitude+m_quadSize;
  302. bbox.Extent.Y += m_amplitude+m_quadSize;
  303. //Clip our visible snow rendering box
  304. if ((cubeOriginX * m_emitterSpacing ) < (bbox.Center.X - bbox.Extent.X))
  305. cubeOriginX = (Int)floor ((bbox.Center.X - bbox.Extent.X)/m_emitterSpacing);
  306. if ((cubeOriginY * m_emitterSpacing ) < (bbox.Center.Y - bbox.Extent.Y))
  307. cubeOriginY = (Int)floor ((bbox.Center.Y - bbox.Extent.Y)/m_emitterSpacing);
  308. if ((cubeDimX * m_emitterSpacing ) > (bbox.Center.X + bbox.Extent.X))
  309. cubeDimX = (Int)floor ((bbox.Center.X + bbox.Extent.X)/m_emitterSpacing);
  310. if ((cubeDimY * m_emitterSpacing ) > (bbox.Center.Y + bbox.Extent.Y))
  311. cubeDimY = (Int)floor ((bbox.Center.Y + bbox.Extent.Y)/m_emitterSpacing);
  312. if ((cubeDimY - cubeOriginY) < 0 || (cubeDimX-cubeOriginX) < 0)
  313. return; //entire snow box is culled by either x or y screen boundary.
  314. //Find total number of particles that need rendering.
  315. Int totalPart=(cubeDimY-cubeOriginY)*(cubeDimX-cubeOriginX);
  316. if (totalPart <= 0)
  317. return; //nothing to render.
  318. //Height at the top of the cube with camera at center.
  319. m_snowCeiling = camPos.Z + m_boxDimensions/2.0f;
  320. //Offset to allow cube extents to move with camera.
  321. Real cameraOffset = fmod (camPos.Z,m_boxDimensions);
  322. m_heightTraveled=m_time*m_velocity+cameraOffset; //height that snow flake traveled this frame.
  323. Matrix4x4 identity(true);
  324. DX8Wrapper::Set_Transform(D3DTS_WORLD,identity);
  325. DX8Wrapper::Set_Shader(ShaderClass::_PresetAlphaShader);
  326. VertexMaterialClass *vmat=VertexMaterialClass::Get_Preset(VertexMaterialClass::PRELIT_DIFFUSE);
  327. DX8Wrapper::Set_Material(vmat);
  328. REF_PTR_RELEASE(vmat);
  329. //make sure we have all the resources we need
  330. if (usePointSprites && !m_VertexBufferD3D)
  331. ReAcquireResources();
  332. if (!usePointSprites && !m_indexBuffer)
  333. ReAcquireResources();
  334. DX8Wrapper::Set_Texture(0,m_snowTexture);
  335. if (!usePointSprites)
  336. {
  337. renderAsQuads(rinfo,cubeOriginX,cubeOriginY,cubeDimX,cubeDimY);
  338. return;
  339. }
  340. Vector3 snowCenter;
  341. DX8Wrapper::Apply_Render_State_Changes();
  342. // Set the render states for using point sprites
  343. DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSPRITEENABLE, TRUE );
  344. DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSCALEENABLE, TRUE );
  345. DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSIZE, FtoDW(m_pointSize) );
  346. DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSIZE_MIN, FtoDW(m_minPointSize) );
  347. DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSIZE_MAX, FtoDW(m_maxPointSize) );
  348. DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSCALE_A, FtoDW(0.00f) );
  349. DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSCALE_B, FtoDW(0.00f) );
  350. DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSCALE_C, FtoDW(1.00f) );
  351. DX8Wrapper::_Get_D3D_Device8()->SetStreamSource( 0, m_VertexBufferD3D, sizeof(POINTVERTEX) );
  352. DX8Wrapper::_Get_D3D_Device8()->SetVertexShader( D3DFVF_POINTVERTEX );
  353. m_dwBase = SNOW_BUFFER_SIZE; //start with a new vertex buffer each frame.
  354. m_leafDim = 45; //cull boxes that are 20x20 emitters in size. Making them much smaller will result in too many draw calls.
  355. m_totalRendered = 0; //keep track of how many particles were rendered.
  356. //Particle centers can deviate from center by by amplitude of sine offset. They also have radius m_quadSize.
  357. //Enlarge culling bounds to compensate.
  358. m_cullOverscan = m_amplitude+m_quadSize;
  359. renderSubBox(rinfo,cubeOriginX,cubeOriginY,cubeDimX,cubeDimY);
  360. // Reset render states
  361. DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSPRITEENABLE, FALSE );
  362. DX8Wrapper::Set_DX8_Render_State( D3DRS_POINTSCALEENABLE, FALSE );
  363. }
  364. /**For hardware that doesn't support point sprites*/
  365. void W3DSnowManager::renderAsQuads(RenderInfoClass &rinfo, Int cubeOriginX, Int cubeOriginY, Int cubeDimX, Int cubeDimY)
  366. {
  367. Matrix4x4 proj;
  368. Matrix3D view;
  369. Vector3 snowCenter;
  370. Vector3 snowCenterVS;
  371. CameraClass &camera=rinfo.Camera;
  372. camera.Get_View_Matrix(&view);
  373. camera.Get_Projection_Matrix(&proj);
  374. Vector3 vertex_offsets[4] = {
  375. Vector3(-0.5f, 0.5f, 0.0f),
  376. Vector3(-0.5f, -0.5f, 0.0f),
  377. Vector3(0.5f, -0.5f, 0.0f),
  378. Vector3(0.5f, 0.5f, 0.0f)
  379. };
  380. Vector2 quad_uvs[4] = {
  381. Vector2(0.0f, 0.0f),
  382. Vector2(0.0f, 1.0f),
  383. Vector2(1.0f, 1.0f),
  384. Vector2(1.0f, 0.0f)
  385. };
  386. //pre-multiple the offsets by particle size
  387. for (Int i=0; i<4; i++)
  388. {
  389. vertex_offsets[i] *= m_quadSize;
  390. }
  391. Matrix4x4 identity(true);
  392. DX8Wrapper::Set_Transform(D3DTS_VIEW,identity);
  393. DX8Wrapper::Set_Index_Buffer(m_indexBuffer,0);
  394. Int y=cubeOriginY; //loop counter.
  395. Int cubeOriginXRemainder = cubeOriginX; //loop counter - adjusted when not all particles fit into render buffer.
  396. //Find total number of particles that need rendering.
  397. Int totalPart=(cubeDimY-cubeOriginY)*(cubeDimX-cubeOriginX);
  398. m_totalRendered += totalPart;
  399. while (totalPart)
  400. {
  401. Int batchSize=totalPart;
  402. if (batchSize > SNOW_BATCH_SIZE)
  403. batchSize = SNOW_BATCH_SIZE;
  404. Int numberInBatch=0;
  405. DynamicVBAccessClass vb_access(BUFFER_TYPE_DYNAMIC_DX8,dynamic_fvf_type,batchSize*4); //allocate 4 verts per flake
  406. {
  407. DynamicVBAccessClass::WriteLockClass lock(&vb_access);
  408. VertexFormatXYZNDUV2* verts=lock.Get_Formatted_Vertex_Array();
  409. for (;y<cubeDimY; y++)
  410. {
  411. for (Int x=cubeOriginXRemainder; x<cubeDimX; x++)
  412. {
  413. if (numberInBatch >= batchSize)
  414. { cubeOriginXRemainder = x;
  415. goto flush_particles;
  416. }
  417. //Get initial height from noise table. We add a large value to make sure it's positive. Then
  418. //modulate by table dimensions to find a value.
  419. Int noiseOffset=MODPOW2(x+MAXIMUM_CAMERA_DISTANCE,SNOW_NOISE_X)+MODPOW2(y+MAXIMUM_CAMERA_DISTANCE,SNOW_NOISE_Y)*SNOW_NOISE_X;
  420. if (noiseOffset > (SNOW_NOISE_X * SNOW_NOISE_Y))
  421. noiseOffset = 0; //this should never happen but check to prevent buffer over/under flow.
  422. //find current height
  423. Real h0=m_snowCeiling-fmod(m_heightTraveled+m_startingHeights[noiseOffset],m_boxDimensions);
  424. //find world-space position of snow flake
  425. snowCenter.Set(x*m_emitterSpacing,y*m_emitterSpacing,h0);
  426. //Get view-space position
  427. Matrix3D::Transform_Vector(view,snowCenter,&snowCenterVS);
  428. //Adjust position so snow flakes don't fall straight down.
  429. snowCenterVS.X += m_amplitude * WWMath::Fast_Sin( h0 * m_frequencyScaleX + (Real)x);
  430. snowCenterVS.Y += m_amplitude * WWMath::Fast_Sin( h0 * m_frequencyScaleY + (Real)y);
  431. for (Int i=0; i<4; i++)
  432. {
  433. *(Vector3 *)verts=snowCenterVS + vertex_offsets[i];
  434. verts->nx=0; //keep AGP write-combining active
  435. verts->ny=0;
  436. verts->nz=0;
  437. verts->diffuse=0xffffffff; //set to opaque
  438. verts->u1=quad_uvs[i].X;
  439. verts->v1=quad_uvs[i].Y;
  440. verts->u2=0; //keep AGP write-combining active
  441. verts->v2=0;
  442. verts++;
  443. }
  444. numberInBatch++;
  445. }
  446. //getting here means we did not overflow the render buffer, so reset x origin to normal.
  447. cubeOriginXRemainder = cubeOriginX; //reset to normal amount
  448. }
  449. flush_particles:
  450. numberInBatch; //need something at goto destination - stupid c compiler.
  451. }
  452. //Render any particles that may be queued up.
  453. if (numberInBatch)
  454. {
  455. DX8Wrapper::Set_Vertex_Buffer(vb_access);
  456. DX8Wrapper::Draw_Triangles( 0,numberInBatch*2, 0, numberInBatch*4);
  457. totalPart -= numberInBatch;
  458. }
  459. }
  460. }