Line data Source code
1 : /* Copyright (C) 2023 Wildfire Games.
2 : * This file is part of 0 A.D.
3 : *
4 : * 0 A.D. is free software: you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation, either version 2 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * 0 A.D. is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16 : */
17 :
18 : #include "precompiled.h"
19 :
20 : #include "graphics/Terrain.h"
21 : #include "graphics/TextureManager.h"
22 : #include "graphics/ShaderManager.h"
23 : #include "graphics/ShaderProgram.h"
24 : #include "lib/bits.h"
25 : #include "lib/timer.h"
26 : #include "maths/MathUtil.h"
27 : #include "maths/Vector2D.h"
28 : #include "ps/CLogger.h"
29 : #include "ps/CStrInternStatic.h"
30 : #include "ps/Game.h"
31 : #include "ps/VideoMode.h"
32 : #include "ps/World.h"
33 : #include "renderer/backend/IDevice.h"
34 : #include "renderer/Renderer.h"
35 : #include "renderer/RenderingOptions.h"
36 : #include "renderer/SceneRenderer.h"
37 : #include "renderer/WaterManager.h"
38 : #include "simulation2/Simulation2.h"
39 : #include "simulation2/components/ICmpWaterManager.h"
40 : #include "simulation2/components/ICmpRangeManager.h"
41 :
42 : #include <algorithm>
43 :
44 : struct CoastalPoint
45 : {
46 0 : CoastalPoint(int idx, CVector2D pos) : index(idx), position(pos) {};
47 : int index;
48 : CVector2D position;
49 : };
50 :
51 0 : struct SWavesVertex
52 : {
53 : // vertex position
54 : CVector3D m_BasePosition;
55 : CVector3D m_ApexPosition;
56 : CVector3D m_SplashPosition;
57 : CVector3D m_RetreatPosition;
58 :
59 : CVector2D m_PerpVect;
60 : float m_UV[2];
61 : };
62 : cassert(sizeof(SWavesVertex) == 64);
63 :
64 0 : struct WaveObject
65 : {
66 : CVertexBufferManager::Handle m_VBVertices;
67 : CBoundingBoxAligned m_AABB;
68 : size_t m_Width;
69 : float m_TimeDiff;
70 : };
71 :
72 6 : WaterManager::WaterManager()
73 : {
74 : // water
75 6 : m_RenderWater = false; // disabled until textures are successfully loaded
76 6 : m_WaterHeight = 5.0f;
77 :
78 6 : m_RefTextureSize = 0;
79 :
80 6 : m_WaterTexTimer = 0.0;
81 :
82 6 : m_WindAngle = 0.0f;
83 6 : m_Waviness = 8.0f;
84 6 : m_WaterColor = CColor(0.3f, 0.35f, 0.7f, 1.0f);
85 6 : m_WaterTint = CColor(0.28f, 0.3f, 0.59f, 1.0f);
86 6 : m_Murkiness = 0.45f;
87 6 : m_RepeatPeriod = 16.0f;
88 :
89 6 : m_WaterEffects = true;
90 6 : m_WaterFancyEffects = false;
91 6 : m_WaterRealDepth = false;
92 6 : m_WaterRefraction = false;
93 6 : m_WaterReflection = false;
94 6 : m_WaterType = L"ocean";
95 :
96 6 : m_NeedsReloading = false;
97 6 : m_NeedInfoUpdate = true;
98 :
99 6 : m_MapSize = 0;
100 :
101 6 : m_updatei0 = 0;
102 6 : m_updatej0 = 0;
103 6 : m_updatei1 = 0;
104 6 : m_updatej1 = 0;
105 6 : }
106 :
107 12 : WaterManager::~WaterManager()
108 : {
109 : // Cleanup if the caller messed up
110 6 : UnloadWaterTextures();
111 :
112 6 : m_ShoreWaves.clear();
113 6 : m_ShoreWavesVBIndices.Reset();
114 :
115 6 : m_DistanceHeightmap.reset();
116 6 : m_WindStrength.reset();
117 :
118 6 : m_FancyEffectsFramebuffer.reset();
119 6 : m_FancyEffectsOccludersFramebuffer.reset();
120 6 : m_RefractionFramebuffer.reset();
121 6 : m_ReflectionFramebuffer.reset();
122 :
123 6 : m_FancyTexture.reset();
124 6 : m_FancyTextureDepth.reset();
125 6 : m_ReflFboDepthTexture.reset();
126 6 : m_RefrFboDepthTexture.reset();
127 6 : }
128 :
129 6 : void WaterManager::Initialize()
130 : {
131 6 : const uint32_t stride = sizeof(SWavesVertex);
132 :
133 6 : const std::array<Renderer::Backend::SVertexAttributeFormat, 6> attributes{{
134 : {Renderer::Backend::VertexAttributeStream::POSITION,
135 : Renderer::Backend::Format::R32G32B32_SFLOAT,
136 : offsetof(SWavesVertex, m_BasePosition), stride,
137 : Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
138 : {Renderer::Backend::VertexAttributeStream::NORMAL,
139 : Renderer::Backend::Format::R32G32_SFLOAT,
140 : offsetof(SWavesVertex, m_PerpVect), stride,
141 : Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
142 : {Renderer::Backend::VertexAttributeStream::UV0,
143 : Renderer::Backend::Format::R32G32_SFLOAT,
144 : offsetof(SWavesVertex, m_UV), stride,
145 : Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
146 :
147 : {Renderer::Backend::VertexAttributeStream::UV1,
148 : Renderer::Backend::Format::R32G32B32_SFLOAT,
149 : offsetof(SWavesVertex, m_ApexPosition), stride,
150 : Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
151 : {Renderer::Backend::VertexAttributeStream::UV2,
152 : Renderer::Backend::Format::R32G32B32_SFLOAT,
153 : offsetof(SWavesVertex, m_SplashPosition), stride,
154 : Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0},
155 : {Renderer::Backend::VertexAttributeStream::UV3,
156 : Renderer::Backend::Format::R32G32B32_SFLOAT,
157 : offsetof(SWavesVertex, m_RetreatPosition), stride,
158 : Renderer::Backend::VertexAttributeRate::PER_VERTEX, 0}
159 : }};
160 6 : m_ShoreVertexInputLayout = g_Renderer.GetVertexInputLayout(attributes);
161 6 : }
162 :
163 : ///////////////////////////////////////////////////////////////////
164 : // Progressive load of water textures
165 0 : int WaterManager::LoadWaterTextures()
166 : {
167 : // TODO: this doesn't need to be progressive-loading any more
168 : // (since texture loading is async now)
169 :
170 : wchar_t pathname[PATH_MAX];
171 :
172 : // Load diffuse grayscale images (for non-fancy water)
173 0 : for (size_t i = 0; i < ARRAY_SIZE(m_WaterTexture); ++i)
174 : {
175 0 : swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/default/diffuse%02d.dds", (int)i+1);
176 0 : CTextureProperties textureProps(pathname);
177 0 : textureProps.SetAddressMode(
178 : Renderer::Backend::Sampler::AddressMode::REPEAT);
179 :
180 0 : CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
181 0 : texture->Prefetch();
182 0 : m_WaterTexture[i] = texture;
183 : }
184 :
185 0 : m_RenderWater = true;
186 :
187 : // Load normalmaps (for fancy water)
188 0 : ReloadWaterNormalTextures();
189 :
190 : // Load CoastalWaves
191 : {
192 0 : CTextureProperties textureProps(L"art/textures/terrain/types/water/coastalWave.png");
193 0 : textureProps.SetAddressMode(
194 : Renderer::Backend::Sampler::AddressMode::REPEAT);
195 0 : CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
196 0 : texture->Prefetch();
197 0 : m_WaveTex = texture;
198 : }
199 :
200 : // Load Foam
201 : {
202 0 : CTextureProperties textureProps(L"art/textures/terrain/types/water/foam.png");
203 0 : textureProps.SetAddressMode(
204 : Renderer::Backend::Sampler::AddressMode::REPEAT);
205 0 : CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
206 0 : texture->Prefetch();
207 0 : m_FoamTex = texture;
208 : }
209 :
210 0 : RecreateOrLoadTexturesIfNeeded();
211 :
212 0 : return 0;
213 : }
214 :
215 0 : void WaterManager::RecreateOrLoadTexturesIfNeeded()
216 : {
217 0 : Renderer::Backend::IDevice* backendDevice = g_VideoMode.GetBackendDevice();
218 :
219 : // Use screen-sized textures for minimum artifacts.
220 0 : const size_t newRefTextureSize = round_up_to_pow2(g_Renderer.GetHeight());
221 :
222 0 : if (m_RefTextureSize != newRefTextureSize)
223 : {
224 0 : m_ReflectionFramebuffer.reset();
225 0 : m_ReflectionTexture.reset();
226 0 : m_ReflFboDepthTexture.reset();
227 :
228 0 : m_RefractionFramebuffer.reset();
229 0 : m_RefractionTexture.reset();
230 0 : m_RefrFboDepthTexture.reset();
231 :
232 0 : m_RefTextureSize = newRefTextureSize;
233 : }
234 :
235 : const Renderer::Backend::Format depthFormat =
236 : backendDevice->GetPreferredDepthStencilFormat(
237 : Renderer::Backend::ITexture::Usage::SAMPLED |
238 : Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
239 0 : true, false);
240 :
241 : // Create reflection textures.
242 : const bool needsReflectionTextures =
243 0 : g_RenderingOptions.GetWaterEffects() &&
244 0 : g_RenderingOptions.GetWaterReflection();
245 0 : if (needsReflectionTextures && !m_ReflectionTexture)
246 : {
247 0 : m_ReflectionTexture = backendDevice->CreateTexture2D("WaterReflectionTexture",
248 : Renderer::Backend::ITexture::Usage::SAMPLED |
249 : Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT,
250 0 : Renderer::Backend::Format::R8G8B8A8_UNORM, m_RefTextureSize, m_RefTextureSize,
251 0 : Renderer::Backend::Sampler::MakeDefaultSampler(
252 : Renderer::Backend::Sampler::Filter::LINEAR,
253 0 : Renderer::Backend::Sampler::AddressMode::MIRRORED_REPEAT));
254 :
255 0 : m_ReflFboDepthTexture = backendDevice->CreateTexture2D("WaterReflectionDepthTexture",
256 : Renderer::Backend::ITexture::Usage::SAMPLED |
257 : Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
258 0 : depthFormat, m_RefTextureSize, m_RefTextureSize,
259 0 : Renderer::Backend::Sampler::MakeDefaultSampler(
260 : Renderer::Backend::Sampler::Filter::NEAREST,
261 0 : Renderer::Backend::Sampler::AddressMode::REPEAT));
262 :
263 0 : Renderer::Backend::SColorAttachment colorAttachment{};
264 0 : colorAttachment.texture = m_ReflectionTexture.get();
265 0 : colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
266 0 : colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
267 0 : colorAttachment.clearColor = CColor{0.5f, 0.5f, 1.0f, 0.0f};
268 :
269 0 : Renderer::Backend::SDepthStencilAttachment depthStencilAttachment{};
270 0 : depthStencilAttachment.texture = m_ReflFboDepthTexture.get();
271 0 : depthStencilAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
272 0 : depthStencilAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
273 :
274 0 : m_ReflectionFramebuffer = backendDevice->CreateFramebuffer("ReflectionFramebuffer",
275 0 : &colorAttachment, &depthStencilAttachment);
276 0 : if (!m_ReflectionFramebuffer)
277 : {
278 0 : g_RenderingOptions.SetWaterReflection(false);
279 0 : UpdateQuality();
280 : }
281 : }
282 :
283 : // Create refraction textures.
284 : const bool needsRefractionTextures =
285 0 : g_RenderingOptions.GetWaterEffects() &&
286 0 : g_RenderingOptions.GetWaterRefraction();
287 0 : if (needsRefractionTextures && !m_RefractionTexture)
288 : {
289 0 : m_RefractionTexture = backendDevice->CreateTexture2D("WaterRefractionTexture",
290 : Renderer::Backend::ITexture::Usage::SAMPLED |
291 : Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT,
292 0 : Renderer::Backend::Format::R8G8B8A8_UNORM, m_RefTextureSize, m_RefTextureSize,
293 0 : Renderer::Backend::Sampler::MakeDefaultSampler(
294 : Renderer::Backend::Sampler::Filter::LINEAR,
295 0 : Renderer::Backend::Sampler::AddressMode::MIRRORED_REPEAT));
296 :
297 0 : m_RefrFboDepthTexture = backendDevice->CreateTexture2D("WaterRefractionDepthTexture",
298 : Renderer::Backend::ITexture::Usage::SAMPLED |
299 : Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
300 0 : depthFormat, m_RefTextureSize, m_RefTextureSize,
301 0 : Renderer::Backend::Sampler::MakeDefaultSampler(
302 : Renderer::Backend::Sampler::Filter::NEAREST,
303 0 : Renderer::Backend::Sampler::AddressMode::REPEAT));
304 :
305 0 : Renderer::Backend::SColorAttachment colorAttachment{};
306 0 : colorAttachment.texture = m_RefractionTexture.get();
307 0 : colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
308 0 : colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
309 0 : colorAttachment.clearColor = CColor{1.0f, 0.0f, 0.0f, 0.0f};
310 :
311 0 : Renderer::Backend::SDepthStencilAttachment depthStencilAttachment{};
312 0 : depthStencilAttachment.texture = m_RefrFboDepthTexture.get();
313 0 : depthStencilAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
314 0 : depthStencilAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
315 :
316 0 : m_RefractionFramebuffer = backendDevice->CreateFramebuffer("RefractionFramebuffer",
317 0 : &colorAttachment, &depthStencilAttachment);
318 0 : if (!m_RefractionFramebuffer)
319 : {
320 0 : g_RenderingOptions.SetWaterRefraction(false);
321 0 : UpdateQuality();
322 : }
323 : }
324 :
325 0 : const uint32_t newWidth = static_cast<uint32_t>(g_Renderer.GetWidth());
326 0 : const uint32_t newHeight = static_cast<uint32_t>(g_Renderer.GetHeight());
327 0 : if (m_FancyTexture && (m_FancyTexture->GetWidth() != newWidth || m_FancyTexture->GetHeight() != newHeight))
328 : {
329 0 : m_FancyEffectsFramebuffer.reset();
330 0 : m_FancyEffectsOccludersFramebuffer.reset();
331 0 : m_FancyTexture.reset();
332 0 : m_FancyTextureDepth.reset();
333 : }
334 :
335 : // Create the Fancy Effects textures.
336 : const bool needsFancyTextures =
337 0 : g_RenderingOptions.GetWaterEffects() &&
338 0 : g_RenderingOptions.GetWaterFancyEffects();
339 0 : if (needsFancyTextures && !m_FancyTexture)
340 : {
341 0 : m_FancyTexture = backendDevice->CreateTexture2D("WaterFancyTexture",
342 : Renderer::Backend::ITexture::Usage::SAMPLED |
343 : Renderer::Backend::ITexture::Usage::COLOR_ATTACHMENT,
344 0 : Renderer::Backend::Format::R8G8B8A8_UNORM, g_Renderer.GetWidth(), g_Renderer.GetHeight(),
345 0 : Renderer::Backend::Sampler::MakeDefaultSampler(
346 : Renderer::Backend::Sampler::Filter::LINEAR,
347 0 : Renderer::Backend::Sampler::AddressMode::REPEAT));
348 :
349 0 : m_FancyTextureDepth = backendDevice->CreateTexture2D("WaterFancyDepthTexture",
350 : Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
351 0 : depthFormat, g_Renderer.GetWidth(), g_Renderer.GetHeight(),
352 0 : Renderer::Backend::Sampler::MakeDefaultSampler(
353 : Renderer::Backend::Sampler::Filter::LINEAR,
354 0 : Renderer::Backend::Sampler::AddressMode::REPEAT));
355 :
356 0 : Renderer::Backend::SColorAttachment colorAttachment{};
357 0 : colorAttachment.texture = m_FancyTexture.get();
358 0 : colorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
359 0 : colorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
360 0 : colorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
361 :
362 0 : Renderer::Backend::SDepthStencilAttachment depthStencilAttachment{};
363 0 : depthStencilAttachment.texture = m_FancyTextureDepth.get();
364 0 : depthStencilAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::CLEAR;
365 : // We need to store depth for later rendering occluders.
366 0 : depthStencilAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
367 :
368 0 : m_FancyEffectsFramebuffer = backendDevice->CreateFramebuffer("FancyEffectsFramebuffer",
369 0 : &colorAttachment, &depthStencilAttachment);
370 :
371 0 : Renderer::Backend::SColorAttachment occludersColorAttachment{};
372 0 : occludersColorAttachment.texture = m_FancyTexture.get();
373 0 : occludersColorAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::LOAD;
374 0 : occludersColorAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::STORE;
375 0 : occludersColorAttachment.clearColor = CColor{0.0f, 0.0f, 0.0f, 0.0f};
376 :
377 0 : Renderer::Backend::SDepthStencilAttachment occludersDepthStencilAttachment{};
378 0 : occludersDepthStencilAttachment.texture = m_FancyTextureDepth.get();
379 0 : occludersDepthStencilAttachment.loadOp = Renderer::Backend::AttachmentLoadOp::LOAD;
380 0 : occludersDepthStencilAttachment.storeOp = Renderer::Backend::AttachmentStoreOp::DONT_CARE;
381 :
382 0 : m_FancyEffectsOccludersFramebuffer = backendDevice->CreateFramebuffer("FancyEffectsOccludersFramebuffer",
383 0 : &occludersColorAttachment, &occludersDepthStencilAttachment);
384 0 : if (!m_FancyEffectsFramebuffer || !m_FancyEffectsOccludersFramebuffer)
385 : {
386 0 : g_RenderingOptions.SetWaterRefraction(false);
387 0 : UpdateQuality();
388 : }
389 : }
390 0 : }
391 :
392 0 : void WaterManager::ReloadWaterNormalTextures()
393 : {
394 : wchar_t pathname[PATH_MAX];
395 0 : for (size_t i = 0; i < ARRAY_SIZE(m_NormalMap); ++i)
396 : {
397 0 : swprintf_s(pathname, ARRAY_SIZE(pathname), L"art/textures/animated/water/%ls/normal00%02d.png", m_WaterType.c_str(), static_cast<int>(i) + 1);
398 0 : CTextureProperties textureProps(pathname);
399 0 : textureProps.SetAddressMode(
400 : Renderer::Backend::Sampler::AddressMode::REPEAT);
401 0 : textureProps.SetAnisotropicFilter(true);
402 :
403 0 : CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
404 0 : texture->Prefetch();
405 0 : m_NormalMap[i] = texture;
406 : }
407 0 : }
408 :
409 : ///////////////////////////////////////////////////////////////////
410 : // Unload water textures
411 6 : void WaterManager::UnloadWaterTextures()
412 : {
413 366 : for (size_t i = 0; i < ARRAY_SIZE(m_WaterTexture); i++)
414 360 : m_WaterTexture[i].reset();
415 :
416 366 : for (size_t i = 0; i < ARRAY_SIZE(m_NormalMap); i++)
417 360 : m_NormalMap[i].reset();
418 :
419 6 : m_RefractionFramebuffer.reset();
420 6 : m_ReflectionFramebuffer.reset();
421 6 : m_ReflectionTexture.reset();
422 6 : m_RefractionTexture.reset();
423 6 : }
424 :
425 : template<bool Transpose>
426 0 : static inline void ComputeDirection(float* distanceMap, const u16* heightmap, float waterHeight, size_t SideSize, size_t maxLevel)
427 : {
428 : #define ABOVEWATER(x, z) (HEIGHT_SCALE * heightmap[z*SideSize + x] >= waterHeight)
429 : #define UPDATELOOKAHEAD \
430 : for (; lookahead <= id2+maxLevel && lookahead < SideSize && \
431 : ((!Transpose && !ABOVEWATER(lookahead, id1)) || (Transpose && !ABOVEWATER(id1, lookahead))); ++lookahead)
432 : // Algorithm:
433 : // We want to know the distance to the closest shore point. Go through each line/column,
434 : // keep track of when we encountered the last shore point and how far ahead the next one is.
435 0 : for (size_t id1 = 0; id1 < SideSize; ++id1)
436 : {
437 0 : size_t id2 = 0;
438 0 : const size_t& x = Transpose ? id1 : id2;
439 0 : const size_t& z = Transpose ? id2 : id1;
440 :
441 0 : size_t level = ABOVEWATER(x, z) ? 0 : maxLevel;
442 0 : size_t lookahead = (size_t)(level > 0);
443 :
444 0 : UPDATELOOKAHEAD;
445 :
446 : // start moving
447 0 : for (; id2 < SideSize; ++id2)
448 : {
449 : // update current level
450 0 : if (ABOVEWATER(x, z))
451 0 : level = 0;
452 : else
453 0 : level = std::min(level+1, maxLevel);
454 :
455 : // move lookahead
456 0 : if (lookahead == id2)
457 0 : ++lookahead;
458 0 : UPDATELOOKAHEAD;
459 :
460 : // This is the important bit: set the distance to either:
461 : // - the distance to the previous shore point (level)
462 : // - the distance to the next shore point (lookahead-id2)
463 0 : distanceMap[z*SideSize + x] = std::min(distanceMap[z*SideSize + x], (float)std::min(lookahead-id2, level));
464 : }
465 : }
466 : #undef ABOVEWATER
467 : #undef UPDATELOOKAHEAD
468 0 : }
469 :
470 : ///////////////////////////////////////////////////////////////////
471 : // Calculate our binary heightmap from the terrain heightmap.
472 0 : void WaterManager::RecomputeDistanceHeightmap()
473 : {
474 0 : CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
475 0 : if (!terrain || !terrain->GetHeightMap())
476 0 : return;
477 :
478 0 : size_t SideSize = m_MapSize;
479 :
480 : // we want to look ahead some distance, but not too much (less efficient and not interesting). This is our lookahead.
481 0 : const size_t maxLevel = 5;
482 :
483 0 : if (!m_DistanceHeightmap)
484 : {
485 0 : m_DistanceHeightmap = std::make_unique<float[]>(SideSize * SideSize);
486 0 : std::fill(m_DistanceHeightmap.get(), m_DistanceHeightmap.get() + SideSize * SideSize, static_cast<float>(maxLevel));
487 : }
488 :
489 : // Create a manhattan-distance heightmap.
490 : // This could be refined to only be done near the coast itself, but it's probably not necessary.
491 :
492 0 : u16* heightmap = terrain->GetHeightMap();
493 :
494 0 : ComputeDirection<false>(m_DistanceHeightmap.get(), heightmap, m_WaterHeight, SideSize, maxLevel);
495 0 : ComputeDirection<true>(m_DistanceHeightmap.get(), heightmap, m_WaterHeight, SideSize, maxLevel);
496 : }
497 :
498 : // This requires m_DistanceHeightmap to be defined properly.
499 0 : void WaterManager::CreateWaveMeshes()
500 : {
501 0 : if (m_MapSize == 0)
502 0 : return;
503 :
504 0 : CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
505 0 : if (!terrain || !terrain->GetHeightMap())
506 0 : return;
507 :
508 0 : m_ShoreWaves.clear();
509 0 : m_ShoreWavesVBIndices.Reset();
510 :
511 0 : if (m_Waviness < 5.0f && m_WaterType != L"ocean")
512 0 : return;
513 :
514 0 : size_t SideSize = m_MapSize;
515 :
516 : // First step: get the points near the coast.
517 0 : std::set<int> CoastalPointsSet;
518 0 : for (size_t z = 1; z < SideSize-1; ++z)
519 0 : for (size_t x = 1; x < SideSize-1; ++x)
520 : // get the points not on the shore but near it, ocean-side
521 0 : if (m_DistanceHeightmap[z*m_MapSize + x] > 0.5f && m_DistanceHeightmap[z*m_MapSize + x] < 1.5f)
522 0 : CoastalPointsSet.insert((z)*SideSize + x);
523 :
524 : // Second step: create chains out of those coastal points.
525 : static const int around[8][2] = { { -1,-1 }, { -1,0 }, { -1,1 }, { 0,1 }, { 1,1 }, { 1,0 }, { 1,-1 }, { 0,-1 } };
526 :
527 0 : std::vector<std::deque<CoastalPoint> > CoastalPointsChains;
528 0 : while (!CoastalPointsSet.empty())
529 : {
530 0 : int index = *(CoastalPointsSet.begin());
531 0 : int x = index % SideSize;
532 0 : int y = (index - x ) / SideSize;
533 :
534 0 : std::deque<CoastalPoint> Chain;
535 :
536 0 : Chain.push_front(CoastalPoint(index,CVector2D(x*4,y*4)));
537 :
538 : // Erase us.
539 0 : CoastalPointsSet.erase(CoastalPointsSet.begin());
540 :
541 : // We're our starter points. At most we can have 2 points close to us.
542 : // We'll pick the first one and look for its neighbors (he can only have one new)
543 : // Up until we either reach the end of the chain, or ourselves.
544 : // Then go down the other direction if there is any.
545 0 : int neighbours[2] = { -1, -1 };
546 0 : int nbNeighb = 0;
547 0 : for (int i = 0; i < 8; ++i)
548 : {
549 0 : if (CoastalPointsSet.count(x + around[i][0] + (y + around[i][1])*SideSize))
550 : {
551 0 : if (nbNeighb < 2)
552 0 : neighbours[nbNeighb] = x + around[i][0] + (y + around[i][1])*SideSize;
553 0 : ++nbNeighb;
554 : }
555 : }
556 0 : if (nbNeighb > 2)
557 0 : continue;
558 :
559 0 : for (int i = 0; i < 2; ++i)
560 : {
561 0 : if (neighbours[i] == -1)
562 0 : continue;
563 : // Move to our neighboring point
564 0 : int xx = neighbours[i] % SideSize;
565 0 : int yy = (neighbours[i] - xx ) / SideSize;
566 0 : int indexx = xx + yy*SideSize;
567 0 : int endedChain = false;
568 :
569 0 : if (i == 0)
570 0 : Chain.push_back(CoastalPoint(indexx,CVector2D(xx*4,yy*4)));
571 : else
572 0 : Chain.push_front(CoastalPoint(indexx,CVector2D(xx*4,yy*4)));
573 :
574 : // If there's a loop we'll be the "other" neighboring point already so check for that.
575 : // We'll readd at the end/front the other one to have full squares.
576 0 : if (CoastalPointsSet.count(indexx) == 0)
577 0 : break;
578 :
579 0 : CoastalPointsSet.erase(indexx);
580 :
581 : // Start checking from there.
582 0 : while(!endedChain)
583 : {
584 0 : bool found = false;
585 0 : nbNeighb = 0;
586 0 : for (int p = 0; p < 8; ++p)
587 : {
588 0 : if (CoastalPointsSet.count(xx+around[p][0] + (yy + around[p][1])*SideSize))
589 : {
590 0 : if (nbNeighb >= 2)
591 : {
592 0 : CoastalPointsSet.erase(xx + yy*SideSize);
593 0 : continue;
594 : }
595 0 : ++nbNeighb;
596 : // We've found a new point around us.
597 : // Move there
598 0 : xx = xx + around[p][0];
599 0 : yy = yy + around[p][1];
600 0 : indexx = xx + yy*SideSize;
601 0 : if (i == 0)
602 0 : Chain.push_back(CoastalPoint(indexx,CVector2D(xx*4,yy*4)));
603 : else
604 0 : Chain.push_front(CoastalPoint(indexx,CVector2D(xx*4,yy*4)));
605 0 : CoastalPointsSet.erase(xx + yy*SideSize);
606 0 : found = true;
607 0 : break;
608 : }
609 : }
610 0 : if (!found)
611 0 : endedChain = true;
612 : }
613 : }
614 0 : if (Chain.size() > 10)
615 0 : CoastalPointsChains.push_back(Chain);
616 : }
617 :
618 : // (optional) third step: Smooth chains out.
619 : // This is also really dumb.
620 0 : for (size_t i = 0; i < CoastalPointsChains.size(); ++i)
621 : {
622 : // Bump 1 for smoother.
623 0 : for (int p = 0; p < 3; ++p)
624 : {
625 0 : for (size_t j = 1; j < CoastalPointsChains[i].size()-1; ++j)
626 : {
627 0 : CVector2D realPos = CoastalPointsChains[i][j-1].position + CoastalPointsChains[i][j+1].position;
628 :
629 0 : CoastalPointsChains[i][j].position = (CoastalPointsChains[i][j].position + realPos/2.0f)/2.0f;
630 : }
631 : }
632 : }
633 :
634 : // Fourth step: create waves themselves, using those chains. We basically create subchains.
635 0 : u16 waveSizes = 14; // maximal size in width.
636 :
637 : // Construct indices buffer (we can afford one for all of them)
638 0 : std::vector<u16> water_indices;
639 0 : for (u16 a = 0; a < waveSizes - 1; ++a)
640 : {
641 0 : for (u16 rect = 0; rect < 7; ++rect)
642 : {
643 0 : water_indices.push_back(a * 9 + rect);
644 0 : water_indices.push_back(a * 9 + 9 + rect);
645 0 : water_indices.push_back(a * 9 + 1 + rect);
646 0 : water_indices.push_back(a * 9 + 9 + rect);
647 0 : water_indices.push_back(a * 9 + 10 + rect);
648 0 : water_indices.push_back(a * 9 + 1 + rect);
649 : }
650 : }
651 : // Generic indexes, max-length
652 0 : m_ShoreWavesVBIndices = g_VBMan.AllocateChunk(
653 : sizeof(u16), water_indices.size(),
654 : Renderer::Backend::IBuffer::Type::INDEX, false,
655 0 : nullptr, CVertexBufferManager::Group::WATER);
656 0 : m_ShoreWavesVBIndices->m_Owner->UpdateChunkVertices(m_ShoreWavesVBIndices.Get(), &water_indices[0]);
657 :
658 0 : float diff = (rand() % 50) / 5.0f;
659 :
660 0 : std::vector<SWavesVertex> vertices, reversed;
661 0 : for (size_t i = 0; i < CoastalPointsChains.size(); ++i)
662 : {
663 0 : for (size_t j = 0; j < CoastalPointsChains[i].size()-waveSizes; ++j)
664 : {
665 0 : if (CoastalPointsChains[i].size()- 1 - j < waveSizes)
666 0 : break;
667 :
668 0 : u16 width = waveSizes;
669 :
670 : // First pass to get some parameters out.
671 0 : float outmost = 0.0f; // how far to move on the shore.
672 0 : float avgDepth = 0.0f;
673 0 : int sign = 1;
674 0 : CVector2D firstPerp(0,0), perp(0,0), lastPerp(0,0);
675 0 : for (u16 a = 0; a < waveSizes;++a)
676 : {
677 0 : lastPerp = perp;
678 0 : perp = CVector2D(0,0);
679 0 : int nb = 0;
680 0 : CVector2D pos = CoastalPointsChains[i][j+a].position;
681 0 : CVector2D posPlus;
682 0 : CVector2D posMinus;
683 0 : if (a > 0)
684 : {
685 0 : ++nb;
686 0 : posMinus = CoastalPointsChains[i][j+a-1].position;
687 0 : perp += pos-posMinus;
688 : }
689 0 : if (a < waveSizes-1)
690 : {
691 0 : ++nb;
692 0 : posPlus = CoastalPointsChains[i][j+a+1].position;
693 0 : perp += posPlus-pos;
694 : }
695 0 : perp /= nb;
696 0 : perp = CVector2D(-perp.Y,perp.X).Normalized();
697 :
698 0 : if (a == 0)
699 0 : firstPerp = perp;
700 :
701 0 : if ( a > 1 && perp.Dot(lastPerp) < 0.90f && perp.Dot(firstPerp) < 0.70f)
702 : {
703 0 : width = a+1;
704 0 : break;
705 : }
706 :
707 0 : if (terrain->GetExactGroundLevel(pos.X+perp.X*1.5f, pos.Y+perp.Y*1.5f) > m_WaterHeight)
708 0 : sign = -1;
709 :
710 0 : avgDepth += terrain->GetExactGroundLevel(pos.X+sign*perp.X*20.0f, pos.Y+sign*perp.Y*20.0f) - m_WaterHeight;
711 :
712 0 : float localOutmost = -2.0f;
713 0 : while (localOutmost < 0.0f)
714 : {
715 0 : float depth = terrain->GetExactGroundLevel(pos.X+sign*perp.X*localOutmost, pos.Y+sign*perp.Y*localOutmost) - m_WaterHeight;
716 0 : if (depth < 0.0f || depth > 0.6f)
717 0 : localOutmost += 0.2f;
718 : else
719 : break;
720 : }
721 :
722 0 : outmost += localOutmost;
723 : }
724 0 : if (width < 5)
725 : {
726 0 : j += 6;
727 0 : continue;
728 : }
729 :
730 0 : outmost /= width;
731 :
732 0 : if (outmost > -0.5f)
733 : {
734 0 : j += 3;
735 0 : continue;
736 : }
737 0 : outmost = -2.5f + outmost * m_Waviness/10.0f;
738 :
739 0 : avgDepth /= width;
740 :
741 0 : if (avgDepth > -1.3f)
742 : {
743 0 : j += 3;
744 0 : continue;
745 : }
746 : // we passed the checks, we can create a wave of size "width".
747 :
748 0 : std::unique_ptr<WaveObject> shoreWave = std::make_unique<WaveObject>();
749 0 : vertices.clear();
750 0 : vertices.reserve(9 * width);
751 :
752 0 : shoreWave->m_Width = width;
753 0 : shoreWave->m_TimeDiff = diff;
754 0 : diff += (rand() % 100) / 25.0f + 4.0f;
755 :
756 0 : for (u16 a = 0; a < width;++a)
757 : {
758 0 : perp = CVector2D(0,0);
759 0 : int nb = 0;
760 0 : CVector2D pos = CoastalPointsChains[i][j+a].position;
761 0 : CVector2D posPlus;
762 0 : CVector2D posMinus;
763 0 : if (a > 0)
764 : {
765 0 : ++nb;
766 0 : posMinus = CoastalPointsChains[i][j+a-1].position;
767 0 : perp += pos-posMinus;
768 : }
769 0 : if (a < waveSizes-1)
770 : {
771 0 : ++nb;
772 0 : posPlus = CoastalPointsChains[i][j+a+1].position;
773 0 : perp += posPlus-pos;
774 : }
775 0 : perp /= nb;
776 0 : perp = CVector2D(-perp.Y,perp.X).Normalized();
777 :
778 0 : SWavesVertex point[9];
779 :
780 0 : float baseHeight = 0.04f;
781 :
782 0 : float halfWidth = (width-1.0f)/2.0f;
783 0 : float sideNess = sqrtf(Clamp( (halfWidth - fabsf(a - halfWidth)) / 3.0f, 0.0f, 1.0f));
784 :
785 0 : point[0].m_UV[0] = a; point[0].m_UV[1] = 8;
786 0 : point[1].m_UV[0] = a; point[1].m_UV[1] = 7;
787 0 : point[2].m_UV[0] = a; point[2].m_UV[1] = 6;
788 0 : point[3].m_UV[0] = a; point[3].m_UV[1] = 5;
789 0 : point[4].m_UV[0] = a; point[4].m_UV[1] = 4;
790 0 : point[5].m_UV[0] = a; point[5].m_UV[1] = 3;
791 0 : point[6].m_UV[0] = a; point[6].m_UV[1] = 2;
792 0 : point[7].m_UV[0] = a; point[7].m_UV[1] = 1;
793 0 : point[8].m_UV[0] = a; point[8].m_UV[1] = 0;
794 :
795 0 : point[0].m_PerpVect = perp;
796 0 : point[1].m_PerpVect = perp;
797 0 : point[2].m_PerpVect = perp;
798 0 : point[3].m_PerpVect = perp;
799 0 : point[4].m_PerpVect = perp;
800 0 : point[5].m_PerpVect = perp;
801 0 : point[6].m_PerpVect = perp;
802 0 : point[7].m_PerpVect = perp;
803 0 : point[8].m_PerpVect = perp;
804 :
805 : static const float perpT1[9] = { 6.0f, 6.05f, 6.1f, 6.2f, 6.3f, 6.4f, 6.5f, 6.6f, 9.7f };
806 : static const float perpT2[9] = { 2.0f, 2.1f, 2.2f, 2.3f, 2.4f, 3.0f, 3.3f, 3.6f, 9.5f };
807 : static const float perpT3[9] = { 1.1f, 0.7f, -0.2f, 0.0f, 0.6f, 1.3f, 2.2f, 3.6f, 9.0f };
808 : static const float perpT4[9] = { 2.0f, 2.1f, 1.2f, 1.5f, 1.7f, 1.9f, 2.7f, 3.8f, 9.0f };
809 :
810 : static const float heightT1[9] = { 0.0f, 0.2f, 0.5f, 0.8f, 0.9f, 0.85f, 0.6f, 0.2f, 0.0 };
811 : static const float heightT2[9] = { -0.8f, -0.4f, 0.0f, 0.1f, 0.1f, 0.03f, 0.0f, 0.0f, 0.0 };
812 : static const float heightT3[9] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0 };
813 :
814 0 : for (size_t t = 0; t < 9; ++t)
815 : {
816 0 : float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT1[t]+outmost),
817 0 : pos.Y+sign*perp.Y*(perpT1[t]+outmost));
818 0 : point[t].m_BasePosition = CVector3D(pos.X+sign*perp.X*(perpT1[t]+outmost), baseHeight + heightT1[t]*sideNess + std::max(m_WaterHeight,terrHeight),
819 0 : pos.Y+sign*perp.Y*(perpT1[t]+outmost));
820 : }
821 0 : for (size_t t = 0; t < 9; ++t)
822 : {
823 0 : float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT2[t]+outmost),
824 0 : pos.Y+sign*perp.Y*(perpT2[t]+outmost));
825 0 : point[t].m_ApexPosition = CVector3D(pos.X+sign*perp.X*(perpT2[t]+outmost), baseHeight + heightT1[t]*sideNess + std::max(m_WaterHeight,terrHeight),
826 0 : pos.Y+sign*perp.Y*(perpT2[t]+outmost));
827 : }
828 0 : for (size_t t = 0; t < 9; ++t)
829 : {
830 0 : float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT3[t]+outmost*sideNess),
831 0 : pos.Y+sign*perp.Y*(perpT3[t]+outmost*sideNess));
832 0 : point[t].m_SplashPosition = CVector3D(pos.X+sign*perp.X*(perpT3[t]+outmost*sideNess), baseHeight + heightT2[t]*sideNess + std::max(m_WaterHeight,terrHeight), pos.Y+sign*perp.Y*(perpT3[t]+outmost*sideNess));
833 : }
834 0 : for (size_t t = 0; t < 9; ++t)
835 : {
836 0 : float terrHeight = 0.05f + terrain->GetExactGroundLevel(pos.X+sign*perp.X*(perpT4[t]+outmost),
837 0 : pos.Y+sign*perp.Y*(perpT4[t]+outmost));
838 0 : point[t].m_RetreatPosition = CVector3D(pos.X+sign*perp.X*(perpT4[t]+outmost), baseHeight + heightT3[t]*sideNess + std::max(m_WaterHeight,terrHeight),
839 0 : pos.Y+sign*perp.Y*(perpT4[t]+outmost));
840 : }
841 :
842 0 : vertices.push_back(point[8]);
843 0 : vertices.push_back(point[7]);
844 0 : vertices.push_back(point[6]);
845 0 : vertices.push_back(point[5]);
846 0 : vertices.push_back(point[4]);
847 0 : vertices.push_back(point[3]);
848 0 : vertices.push_back(point[2]);
849 0 : vertices.push_back(point[1]);
850 0 : vertices.push_back(point[0]);
851 :
852 0 : shoreWave->m_AABB += point[8].m_SplashPosition;
853 0 : shoreWave->m_AABB += point[8].m_BasePosition;
854 0 : shoreWave->m_AABB += point[0].m_SplashPosition;
855 0 : shoreWave->m_AABB += point[0].m_BasePosition;
856 0 : shoreWave->m_AABB += point[4].m_ApexPosition;
857 : }
858 :
859 0 : if (sign == 1)
860 : {
861 : // Let's do some fancy reversing.
862 0 : reversed.clear();
863 0 : reversed.reserve(vertices.size());
864 0 : for (int a = width - 1; a >= 0; --a)
865 : {
866 0 : for (size_t t = 0; t < 9; ++t)
867 0 : reversed.push_back(vertices[a * 9 + t]);
868 : }
869 0 : std::swap(vertices, reversed);
870 : }
871 0 : j += width/2-1;
872 :
873 0 : shoreWave->m_VBVertices = g_VBMan.AllocateChunk(
874 : sizeof(SWavesVertex), vertices.size(),
875 : Renderer::Backend::IBuffer::Type::VERTEX, false,
876 0 : nullptr, CVertexBufferManager::Group::WATER);
877 0 : shoreWave->m_VBVertices->m_Owner->UpdateChunkVertices(shoreWave->m_VBVertices.Get(), &vertices[0]);
878 :
879 0 : m_ShoreWaves.emplace_back(std::move(shoreWave));
880 : }
881 : }
882 : }
883 :
884 0 : void WaterManager::RenderWaves(
885 : Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
886 : const CFrustum& frustrum)
887 : {
888 0 : if (!m_WaterFancyEffects)
889 0 : return;
890 :
891 0 : m_WaveTex->UploadBackendTextureIfNeeded(deviceCommandContext);
892 0 : m_FoamTex->UploadBackendTextureIfNeeded(deviceCommandContext);
893 :
894 0 : GPU_SCOPED_LABEL(deviceCommandContext, "Render Waves");
895 :
896 : Renderer::Backend::IFramebuffer* framebuffer =
897 0 : m_FancyEffectsFramebuffer.get();
898 0 : deviceCommandContext->BeginFramebufferPass(framebuffer);
899 0 : Renderer::Backend::IDeviceCommandContext::Rect viewportRect{};
900 0 : viewportRect.width = framebuffer->GetWidth();
901 0 : viewportRect.height = framebuffer->GetHeight();
902 0 : deviceCommandContext->SetViewports(1, &viewportRect);
903 :
904 0 : CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_water_waves);
905 0 : deviceCommandContext->SetGraphicsPipelineState(
906 0 : tech->GetGraphicsPipelineState());
907 0 : deviceCommandContext->BeginPass();
908 0 : Renderer::Backend::IShaderProgram* shader = tech->GetShader();
909 :
910 0 : deviceCommandContext->SetTexture(
911 0 : shader->GetBindingSlot(str_waveTex), m_WaveTex->GetBackendTexture());
912 0 : deviceCommandContext->SetTexture(
913 0 : shader->GetBindingSlot(str_foamTex), m_FoamTex->GetBackendTexture());
914 :
915 0 : deviceCommandContext->SetUniform(
916 0 : shader->GetBindingSlot(str_time), static_cast<float>(m_WaterTexTimer));
917 : const CMatrix3D transform =
918 0 : g_Renderer.GetSceneRenderer().GetViewCamera().GetViewProjection();
919 0 : deviceCommandContext->SetUniform(
920 0 : shader->GetBindingSlot(str_transform), transform.AsFloatArray());
921 :
922 0 : for (size_t a = 0; a < m_ShoreWaves.size(); ++a)
923 : {
924 0 : if (!frustrum.IsBoxVisible(m_ShoreWaves[a]->m_AABB))
925 0 : continue;
926 :
927 0 : CVertexBuffer::VBChunk* VBchunk = m_ShoreWaves[a]->m_VBVertices.Get();
928 0 : ENSURE(!VBchunk->m_Owner->GetBuffer()->IsDynamic());
929 0 : ENSURE(!m_ShoreWavesVBIndices->m_Owner->GetBuffer()->IsDynamic());
930 :
931 0 : const uint32_t stride = sizeof(SWavesVertex);
932 0 : const uint32_t firstVertexOffset = VBchunk->m_Index * stride;
933 :
934 0 : deviceCommandContext->SetVertexInputLayout(m_ShoreVertexInputLayout);
935 :
936 0 : deviceCommandContext->SetUniform(
937 0 : shader->GetBindingSlot(str_translation), m_ShoreWaves[a]->m_TimeDiff);
938 0 : deviceCommandContext->SetUniform(
939 0 : shader->GetBindingSlot(str_width), static_cast<float>(m_ShoreWaves[a]->m_Width));
940 :
941 0 : deviceCommandContext->SetVertexBuffer(
942 0 : 0, VBchunk->m_Owner->GetBuffer(), firstVertexOffset);
943 0 : deviceCommandContext->SetIndexBuffer(m_ShoreWavesVBIndices->m_Owner->GetBuffer());
944 :
945 0 : const uint32_t indexCount = (m_ShoreWaves[a]->m_Width - 1) * (7 * 6);
946 0 : deviceCommandContext->DrawIndexed(m_ShoreWavesVBIndices->m_Index, indexCount, 0);
947 :
948 0 : g_Renderer.GetStats().m_DrawCalls++;
949 0 : g_Renderer.GetStats().m_WaterTris += indexCount / 3;
950 : }
951 0 : deviceCommandContext->EndPass();
952 0 : deviceCommandContext->EndFramebufferPass();
953 : }
954 :
955 0 : void WaterManager::RecomputeWaterData()
956 : {
957 0 : if (!m_MapSize)
958 0 : return;
959 :
960 0 : RecomputeDistanceHeightmap();
961 0 : RecomputeWindStrength();
962 0 : CreateWaveMeshes();
963 : }
964 :
965 : ///////////////////////////////////////////////////////////////////
966 : // Calculate the strength of the wind at a given point on the map.
967 0 : void WaterManager::RecomputeWindStrength()
968 : {
969 0 : if (m_MapSize <= 0)
970 0 : return;
971 :
972 0 : if (!m_WindStrength)
973 0 : m_WindStrength = std::make_unique<float[]>(m_MapSize * m_MapSize);
974 :
975 0 : CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
976 0 : if (!terrain || !terrain->GetHeightMap())
977 0 : return;
978 :
979 0 : CVector2D windDir = CVector2D(cos(m_WindAngle), sin(m_WindAngle));
980 :
981 0 : int stepSize = 10;
982 0 : ssize_t windX = -round(stepSize * windDir.X);
983 0 : ssize_t windY = -round(stepSize * windDir.Y);
984 :
985 : struct SWindPoint {
986 0 : SWindPoint(size_t x, size_t y, float strength) : X(x), Y(y), windStrength(strength) {}
987 : ssize_t X;
988 : ssize_t Y;
989 : float windStrength;
990 : };
991 :
992 0 : std::vector<SWindPoint> startingPoints;
993 0 : std::vector<std::pair<int, int>> movement; // Every increment, move each starting point by all of these.
994 :
995 : // Compute starting points (one or two edges of the map) and how much to move each computation increment.
996 0 : if (fabs(windDir.X) < 0.01f)
997 : {
998 0 : movement.emplace_back(0, windY > 0.f ? 1 : -1);
999 0 : startingPoints.reserve(m_MapSize);
1000 0 : size_t start = windY > 0 ? 0 : m_MapSize - 1;
1001 0 : for (size_t x = 0; x < m_MapSize; ++x)
1002 0 : startingPoints.emplace_back(x, start, 0.f);
1003 : }
1004 0 : else if (fabs(windDir.Y) < 0.01f)
1005 : {
1006 0 : movement.emplace_back(windX > 0.f ? 1 : - 1, 0);
1007 0 : startingPoints.reserve(m_MapSize);
1008 0 : size_t start = windX > 0 ? 0 : m_MapSize - 1;
1009 0 : for (size_t z = 0; z < m_MapSize; ++z)
1010 0 : startingPoints.emplace_back(start, z, 0.f);
1011 : }
1012 : else
1013 : {
1014 0 : startingPoints.reserve(m_MapSize * 2);
1015 : // Points along X.
1016 0 : size_t start = windY > 0 ? 0 : m_MapSize - 1;
1017 0 : for (size_t x = 0; x < m_MapSize; ++x)
1018 0 : startingPoints.emplace_back(x, start, 0.f);
1019 : // Points along Z, avoid repeating the corner point.
1020 0 : start = windX > 0 ? 0 : m_MapSize - 1;
1021 0 : if (windY > 0)
1022 0 : for (size_t z = 1; z < m_MapSize; ++z)
1023 0 : startingPoints.emplace_back(start, z, 0.f);
1024 : else
1025 0 : for (size_t z = 0; z < m_MapSize-1; ++z)
1026 0 : startingPoints.emplace_back(start, z, 0.f);
1027 :
1028 : // Compute movement array.
1029 0 : movement.reserve(std::max(std::abs(windX),std::abs(windY)));
1030 0 : while (windX != 0 || windY != 0)
1031 : {
1032 : std::pair<ssize_t, ssize_t> move = {
1033 0 : windX == 0 ? 0 : windX > 0 ? +1 : -1,
1034 0 : windY == 0 ? 0 : windY > 0 ? +1 : -1
1035 0 : };
1036 0 : windX -= move.first;
1037 0 : windY -= move.second;
1038 0 : movement.push_back(move);
1039 : }
1040 : }
1041 :
1042 : // We have all starting points ready, move them all until the map is covered.
1043 0 : for (SWindPoint& point : startingPoints)
1044 : {
1045 : // Starting velocity is 1.0 unless in shallow water.
1046 0 : m_WindStrength[point.Y * m_MapSize + point.X] = 1.f;
1047 0 : float depth = m_WaterHeight - terrain->GetVertexGroundLevel(point.X, point.Y);
1048 0 : if (depth > 0.f && depth < 2.f)
1049 0 : m_WindStrength[point.Y * m_MapSize + point.X] = depth / 2.f;
1050 0 : point.windStrength = m_WindStrength[point.Y * m_MapSize + point.X];
1051 :
1052 0 : bool onMap = true;
1053 0 : while (onMap)
1054 0 : for (size_t step = 0; step < movement.size(); ++step)
1055 : {
1056 : // Move wind speed towards the mean.
1057 0 : point.windStrength = 0.15f + point.windStrength * 0.85f;
1058 :
1059 : // Adjust speed based on height difference, a positive height difference slowly increases speed (simulate venturi effect)
1060 : // and a lower height reduces speed (wind protection from hills/...)
1061 0 : float heightDiff = std::max(m_WaterHeight, terrain->GetVertexGroundLevel(point.X + movement[step].first, point.Y + movement[step].second)) -
1062 0 : std::max(m_WaterHeight, terrain->GetVertexGroundLevel(point.X, point.Y));
1063 0 : if (heightDiff > 0.f)
1064 0 : point.windStrength = std::min(2.f, point.windStrength + std::min(4.f, heightDiff) / 40.f);
1065 : else
1066 0 : point.windStrength = std::max(0.f, point.windStrength + std::max(-4.f, heightDiff) / 5.f);
1067 :
1068 0 : point.X += movement[step].first;
1069 0 : point.Y += movement[step].second;
1070 :
1071 0 : if (point.X < 0 || point.X >= static_cast<ssize_t>(m_MapSize) || point.Y < 0 || point.Y >= static_cast<ssize_t>(m_MapSize))
1072 : {
1073 0 : onMap = false;
1074 0 : break;
1075 : }
1076 0 : m_WindStrength[point.Y * m_MapSize + point.X] = point.windStrength;
1077 : }
1078 : }
1079 : // TODO: should perhaps blur a little, or change the above code to incorporate neighboring tiles a bit.
1080 : }
1081 :
1082 : ////////////////////////////////////////////////////////////////////////
1083 : // TODO: This will always recalculate for now
1084 0 : void WaterManager::SetMapSize(size_t size)
1085 : {
1086 : // TODO: Im' blindly trusting the user here.
1087 0 : m_MapSize = size;
1088 0 : m_NeedInfoUpdate = true;
1089 0 : m_updatei0 = 0;
1090 0 : m_updatei1 = size;
1091 0 : m_updatej0 = 0;
1092 0 : m_updatej1 = size;
1093 :
1094 0 : m_DistanceHeightmap.reset();
1095 0 : m_WindStrength.reset();
1096 0 : }
1097 :
1098 : ////////////////////////////////////////////////////////////////////////
1099 : // This will set the bools properly
1100 0 : void WaterManager::UpdateQuality()
1101 : {
1102 0 : if (g_RenderingOptions.GetWaterEffects() != m_WaterEffects)
1103 : {
1104 0 : m_WaterEffects = g_RenderingOptions.GetWaterEffects();
1105 0 : m_NeedsReloading = true;
1106 : }
1107 0 : if (g_RenderingOptions.GetWaterFancyEffects() != m_WaterFancyEffects)
1108 : {
1109 0 : m_WaterFancyEffects = g_RenderingOptions.GetWaterFancyEffects();
1110 0 : m_NeedsReloading = true;
1111 : }
1112 0 : if (g_RenderingOptions.GetWaterRealDepth() != m_WaterRealDepth)
1113 : {
1114 0 : m_WaterRealDepth = g_RenderingOptions.GetWaterRealDepth();
1115 0 : m_NeedsReloading = true;
1116 : }
1117 0 : if (g_RenderingOptions.GetWaterRefraction() != m_WaterRefraction)
1118 : {
1119 0 : m_WaterRefraction = g_RenderingOptions.GetWaterRefraction();
1120 0 : m_NeedsReloading = true;
1121 : }
1122 0 : if (g_RenderingOptions.GetWaterReflection() != m_WaterReflection)
1123 : {
1124 0 : m_WaterReflection = g_RenderingOptions.GetWaterReflection();
1125 0 : m_NeedsReloading = true;
1126 : }
1127 0 : }
1128 :
1129 0 : bool WaterManager::WillRenderFancyWater() const
1130 : {
1131 : return
1132 0 : m_RenderWater && g_VideoMode.GetBackendDevice()->GetBackend() != Renderer::Backend::Backend::GL_ARB &&
1133 0 : g_RenderingOptions.GetWaterEffects();
1134 : }
1135 :
1136 0 : size_t WaterManager::GetCurrentTextureIndex(const double& period) const
1137 : {
1138 0 : ENSURE(period > 0.0);
1139 0 : return static_cast<size_t>(m_WaterTexTimer * ARRAY_SIZE(m_WaterTexture) / period) % ARRAY_SIZE(m_WaterTexture);
1140 : }
1141 :
1142 0 : size_t WaterManager::GetNextTextureIndex(const double& period) const
1143 : {
1144 0 : ENSURE(period > 0.0);
1145 0 : return (GetCurrentTextureIndex(period) + 1) % ARRAY_SIZE(m_WaterTexture);
1146 3 : }
|