source: ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp@ 25241

Last change on this file since 25241 was 25241, checked in by Vladislav Belov, 3 years ago

Crops minimap view bounds to map size.

Differential Revision: https://code.wildfiregames.com/D3837

  • Property svn:eol-style set to native
File size: 24.2 KB
Line 
1/* Copyright (C) 2021 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 "CMiniMap.h"
21
22#include "graphics/GameView.h"
23#include "graphics/LOSTexture.h"
24#include "graphics/MiniPatch.h"
25#include "graphics/Terrain.h"
26#include "graphics/TerrainTextureEntry.h"
27#include "graphics/TerrainTextureManager.h"
28#include "graphics/TerritoryTexture.h"
29#include "gui/CGUI.h"
30#include "gui/GUIManager.h"
31#include "gui/GUIMatrix.h"
32#include "lib/bits.h"
33#include "lib/external_libraries/libsdl.h"
34#include "lib/ogl.h"
35#include "lib/timer.h"
36#include "ps/ConfigDB.h"
37#include "ps/Filesystem.h"
38#include "ps/Game.h"
39#include "ps/GameSetup/Config.h"
40#include "ps/Profile.h"
41#include "ps/World.h"
42#include "ps/XML/Xeromyces.h"
43#include "renderer/Renderer.h"
44#include "renderer/RenderingOptions.h"
45#include "renderer/WaterManager.h"
46#include "scriptinterface/ScriptInterface.h"
47#include "simulation2/Simulation2.h"
48#include "simulation2/components/ICmpMinimap.h"
49#include "simulation2/components/ICmpRangeManager.h"
50#include "simulation2/helpers/Los.h"
51#include "simulation2/system/ParamNode.h"
52
53#include <array>
54#include <cmath>
55#include <vector>
56
57extern bool g_GameRestarted;
58
59namespace
60{
61
62// Set max drawn entities to UINT16_MAX for now, which is more than enough
63// TODO: we should be cleverer about drawing them to reduce clutter
64const u16 MAX_ENTITIES_DRAWN = 65535;
65
66unsigned int ScaleColor(unsigned int color, float x)
67{
68 unsigned int r = unsigned(float(color & 0xff) * x);
69 unsigned int g = unsigned(float((color>>8) & 0xff) * x);
70 unsigned int b = unsigned(float((color>>16) & 0xff) * x);
71 return (0xff000000 | b | g<<8 | r<<16);
72}
73
74// Adds segments pieces lying inside the circle to lines.
75void CropPointsByCircle(const std::array<CVector3D, 4>& points, const CVector3D& center, const float radius, std::vector<CVector3D>* lines)
76{
77 constexpr float EPS = 1e-3f;
78 const float radiusSquared = radius * radius;
79 lines->reserve(points.size() * 2);
80 for (size_t idx = 0; idx < points.size(); ++idx)
81 {
82 const CVector3D& currentPoint = points[idx];
83 const CVector3D& nextPoint = points[(idx + 1) % points.size()];
84 const CVector3D direction = (nextPoint - currentPoint).Normalized();
85 const CVector3D normal(direction.Z, 0.0f, -direction.X);
86 const float offset = normal.Dot(currentPoint) - normal.Dot(center);
87 // We need to have lines only inside the circle.
88 if (std::abs(offset) + EPS >= radius)
89 continue;
90 const CVector3D closestPoint = center + normal * offset;
91 const float halfChordLength = sqrt(radius * radius - offset * offset);
92 const CVector3D intersectionA = closestPoint - direction * halfChordLength;
93 const CVector3D intersectionB = closestPoint + direction * halfChordLength;
94 // We have no intersection if the segment is lying outside of the circle.
95 if (direction.Dot(currentPoint) + EPS > direction.Dot(intersectionB) ||
96 direction.Dot(nextPoint) - EPS < direction.Dot(intersectionA))
97 continue;
98
99 lines->emplace_back(
100 direction.Dot(currentPoint) > direction.Dot(intersectionA) ? currentPoint : intersectionA);
101 lines->emplace_back(
102 direction.Dot(nextPoint) < direction.Dot(intersectionB) ? nextPoint : intersectionB);
103 }
104}
105
106} // anonymous namespace
107
108const CStr CMiniMap::EventNameWorldClick = "WorldClick";
109
110CMiniMap::CMiniMap(CGUI& pGUI) :
111 IGUIObject(pGUI),
112 m_TerrainTexture(0), m_TerrainData(0), m_MapSize(0), m_Terrain(0), m_TerrainDirty(true), m_MapScale(1.f),
113 m_EntitiesDrawn(0), m_IndexArray(GL_STATIC_DRAW), m_VertexArray(GL_DYNAMIC_DRAW), m_Mask(false),
114 m_NextBlinkTime(0.0), m_PingDuration(25.0), m_BlinkState(false), m_WaterHeight(0.0)
115{
116 RegisterSetting("mask", m_Mask);
117
118 m_Clicking = false;
119 m_MouseHovering = false;
120
121 // Register Relax NG validator
122 CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng");
123
124 m_ShallowPassageHeight = GetShallowPassageHeight();
125
126 m_AttributePos.type = GL_FLOAT;
127 m_AttributePos.elems = 2;
128 m_VertexArray.AddAttribute(&m_AttributePos);
129
130 m_AttributeColor.type = GL_UNSIGNED_BYTE;
131 m_AttributeColor.elems = 4;
132 m_VertexArray.AddAttribute(&m_AttributeColor);
133
134 m_VertexArray.SetNumVertices(MAX_ENTITIES_DRAWN);
135 m_VertexArray.Layout();
136
137 m_IndexArray.SetNumVertices(MAX_ENTITIES_DRAWN);
138 m_IndexArray.Layout();
139 VertexArrayIterator<u16> index = m_IndexArray.GetIterator();
140 for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i)
141 *index++ = i;
142 m_IndexArray.Upload();
143 m_IndexArray.FreeBackingStore();
144
145
146 VertexArrayIterator<float[2]> attrPos = m_AttributePos.GetIterator<float[2]>();
147 VertexArrayIterator<u8[4]> attrColor = m_AttributeColor.GetIterator<u8[4]>();
148 for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i)
149 {
150 (*attrColor)[0] = 0;
151 (*attrColor)[1] = 0;
152 (*attrColor)[2] = 0;
153 (*attrColor)[3] = 0;
154 ++attrColor;
155
156 (*attrPos)[0] = -10000.0f;
157 (*attrPos)[1] = -10000.0f;
158
159 ++attrPos;
160
161 }
162 m_VertexArray.Upload();
163
164 double blinkDuration = 1.0;
165
166 // Tests won't have config initialised
167 if (CConfigDB::IsInitialised())
168 {
169 CFG_GET_VAL("gui.session.minimap.pingduration", m_PingDuration);
170 CFG_GET_VAL("gui.session.minimap.blinkduration", blinkDuration);
171 }
172 m_HalfBlinkDuration = blinkDuration/2;
173}
174
175CMiniMap::~CMiniMap()
176{
177 Destroy();
178}
179
180void CMiniMap::HandleMessage(SGUIMessage& Message)
181{
182 IGUIObject::HandleMessage(Message);
183 switch (Message.type)
184 {
185 case GUIM_MOUSE_PRESS_LEFT:
186 if (m_MouseHovering)
187 {
188 if (!CMiniMap::FireWorldClickEvent(SDL_BUTTON_LEFT, 1))
189 {
190 SetCameraPos();
191 m_Clicking = true;
192 }
193 }
194 break;
195 case GUIM_MOUSE_RELEASE_LEFT:
196 if (m_MouseHovering && m_Clicking)
197 SetCameraPos();
198 m_Clicking = false;
199 break;
200 case GUIM_MOUSE_DBLCLICK_LEFT:
201 if (m_MouseHovering && m_Clicking)
202 SetCameraPos();
203 m_Clicking = false;
204 break;
205 case GUIM_MOUSE_ENTER:
206 m_MouseHovering = true;
207 break;
208 case GUIM_MOUSE_LEAVE:
209 m_Clicking = false;
210 m_MouseHovering = false;
211 break;
212 case GUIM_MOUSE_RELEASE_RIGHT:
213 CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 1);
214 break;
215 case GUIM_MOUSE_DBLCLICK_RIGHT:
216 CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 2);
217 break;
218 case GUIM_MOUSE_MOTION:
219 if (m_MouseHovering && m_Clicking)
220 SetCameraPos();
221 break;
222 case GUIM_MOUSE_WHEEL_DOWN:
223 case GUIM_MOUSE_WHEEL_UP:
224 Message.Skip();
225 break;
226
227 default:
228 break;
229 }
230}
231
232bool CMiniMap::IsMouseOver() const
233{
234 // Get the mouse position.
235 const CVector2D& mousePos = m_pGUI.GetMousePos();
236 // Get the position of the center of the minimap.
237 CVector2D minimapCenter = CVector2D(m_CachedActualSize.left + m_CachedActualSize.GetWidth() / 2.0, m_CachedActualSize.bottom - m_CachedActualSize.GetHeight() / 2.0);
238 // Take the magnitude of the difference of the mouse position and minimap center.
239 double distFromCenter = sqrt(pow((mousePos.X - minimapCenter.X), 2) + pow((mousePos.Y - minimapCenter.Y), 2));
240 // If the distance is less then the radius of the minimap (half the width) the mouse is over the minimap.
241 if (distFromCenter < m_CachedActualSize.GetWidth() / 2.0)
242 return true;
243 else
244 return false;
245}
246
247void CMiniMap::GetMouseWorldCoordinates(float& x, float& z) const
248{
249 // Determine X and Z according to proportion of mouse position and minimap
250
251 const CVector2D& mousePos = m_pGUI.GetMousePos();
252
253 float px = (mousePos.X - m_CachedActualSize.left) / m_CachedActualSize.GetWidth();
254 float py = (m_CachedActualSize.bottom - mousePos.Y) / m_CachedActualSize.GetHeight();
255
256 float angle = GetAngle();
257
258 // Scale world coordinates for shrunken square map
259 x = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(px-0.5) - sin(angle)*(py-0.5)) + 0.5);
260 z = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(py-0.5) + sin(angle)*(px-0.5)) + 0.5);
261}
262
263void CMiniMap::SetCameraPos()
264{
265 CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
266
267 CVector3D target;
268 GetMouseWorldCoordinates(target.X, target.Z);
269 target.Y = terrain->GetExactGroundLevel(target.X, target.Z);
270 g_Game->GetView()->MoveCameraTarget(target);
271}
272
273float CMiniMap::GetAngle() const
274{
275 CVector3D cameraIn = m_Camera->GetOrientation().GetIn();
276 return -atan2(cameraIn.X, cameraIn.Z);
277}
278
279bool CMiniMap::FireWorldClickEvent(int button, int UNUSED(clicks))
280{
281 ScriptRequest rq(g_GUI->GetActiveGUI()->GetScriptInterface());
282
283 float x, z;
284 GetMouseWorldCoordinates(x, z);
285
286 JS::RootedValue coords(rq.cx);
287 ScriptInterface::CreateObject(rq, &coords, "x", x, "z", z);
288
289 JS::RootedValue buttonJs(rq.cx);
290 ScriptInterface::ToJSVal(rq, &buttonJs, button);
291
292 JS::RootedValueVector paramData(rq.cx);
293 ignore_result(paramData.append(coords));
294 ignore_result(paramData.append(buttonJs));
295
296 return ScriptEventWithReturn(EventNameWorldClick, paramData);
297}
298
299// This sets up and draws the rectangle on the minimap
300// which represents the view of the camera in the world.
301void CMiniMap::DrawViewRect(const CMatrix3D& transform) const
302{
303 // Compute the camera frustum intersected with a fixed-height plane.
304 // Use the water height as a fixed base height, which should be the lowest we can go
305 float h = g_Renderer.GetWaterManager()->m_WaterHeight;
306 const float width = m_CachedActualSize.GetWidth();
307 const float height = m_CachedActualSize.GetHeight();
308 const float invTileMapSize = 1.0f / float(TERRAIN_TILE_SIZE * m_MapSize);
309
310 const std::array<CVector3D, 4> hitPoints = {
311 m_Camera->GetWorldCoordinates(0, g_Renderer.GetHeight(), h),
312 m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), g_Renderer.GetHeight(), h),
313 m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), 0, h),
314 m_Camera->GetWorldCoordinates(0, 0, h)
315 };
316
317 std::vector<CVector3D> lines;
318 // We need to prevent drawing view bounds out of the map.
319 const float halfMapSize = static_cast<float>((m_MapSize - 1) * TERRAIN_TILE_SIZE) * 0.5f;
320 CropPointsByCircle(hitPoints, CVector3D(halfMapSize, 0.0f, halfMapSize), halfMapSize, &lines);
321 if (lines.empty())
322 return;
323
324 std::vector<float> vertices;
325 vertices.reserve(lines.size() * 2);
326 for (const CVector3D& point : lines)
327 {
328 // Convert to minimap space.
329 vertices.emplace_back(width * point.X * invTileMapSize);
330 vertices.emplace_back(-(height * point.Z * invTileMapSize));
331 }
332
333 // Enable Scissoring to restrict the rectangle to only the minimap.
334 glScissor(
335 m_CachedActualSize.left * g_GuiScale,
336 g_Renderer.GetHeight() - m_CachedActualSize.bottom * g_GuiScale,
337 width * g_GuiScale,
338 height * g_GuiScale);
339 glEnable(GL_SCISSOR_TEST);
340 glLineWidth(2.0f);
341
342 CShaderDefines lineDefines;
343 lineDefines.Add(str_MINIMAP_LINE, str_1);
344 CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), lineDefines);
345 tech->BeginPass();
346 CShaderProgramPtr shader = tech->GetShader();
347 shader->Uniform(str_transform, transform);
348 shader->Uniform(str_color, 1.0f, 0.3f, 0.3f, 1.0f);
349
350 shader->VertexPointer(2, GL_FLOAT, 0, vertices.data());
351 shader->AssertPointersBound();
352
353 if (!g_Renderer.m_SkipSubmit)
354 glDrawArrays(GL_LINES, 0, vertices.size() / 2);
355
356 tech->EndPass();
357
358 glLineWidth(1.0f);
359 glDisable(GL_SCISSOR_TEST);
360}
361
362struct MinimapUnitVertex
363{
364 // This struct is copyable for convenience and because to move is to copy for primitives.
365 u8 r, g, b, a;
366 float x, y;
367};
368
369// Adds a vertex to the passed VertexArray
370static void inline addVertex(const MinimapUnitVertex& v,
371 VertexArrayIterator<u8[4]>& attrColor,
372 VertexArrayIterator<float[2]>& attrPos)
373{
374 (*attrColor)[0] = v.r;
375 (*attrColor)[1] = v.g;
376 (*attrColor)[2] = v.b;
377 (*attrColor)[3] = v.a;
378 ++attrColor;
379
380 (*attrPos)[0] = v.x;
381 (*attrPos)[1] = v.y;
382
383 ++attrPos;
384}
385
386
387void CMiniMap::DrawTexture(CShaderProgramPtr shader, float coordMax, float angle, float x, float y, float x2, float y2, float z) const
388{
389 // Rotate the texture coordinates (0,0)-(coordMax,coordMax) around their center point (m,m)
390 // Scale square maps to fit in circular minimap area
391 const float s = sin(angle) * m_MapScale;
392 const float c = cos(angle) * m_MapScale;
393 const float m = coordMax / 2.f;
394
395 float quadTex[] = {
396 m*(-c + s + 1.f), m*(-c + -s + 1.f),
397 m*(c + s + 1.f), m*(-c + s + 1.f),
398 m*(c + -s + 1.f), m*(c + s + 1.f),
399
400 m*(c + -s + 1.f), m*(c + s + 1.f),
401 m*(-c + -s + 1.f), m*(c + -s + 1.f),
402 m*(-c + s + 1.f), m*(-c + -s + 1.f)
403 };
404 float quadVerts[] = {
405 x, y, z,
406 x2, y, z,
407 x2, y2, z,
408
409 x2, y2, z,
410 x, y2, z,
411 x, y, z
412 };
413
414 shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex);
415 shader->VertexPointer(3, GL_FLOAT, 0, quadVerts);
416 shader->AssertPointersBound();
417
418 if (!g_Renderer.m_SkipSubmit)
419 glDrawArrays(GL_TRIANGLES, 0, 6);
420}
421
422// TODO: render the minimap in a framebuffer and just draw the frambuffer texture
423// most of the time, updating the framebuffer twice a frame.
424// Here it updates as ping-pong either texture or vertex array each sec to lower gpu stalling
425// (those operations cause a gpu sync, which slows down the way gpu works)
426void CMiniMap::Draw()
427{
428 PROFILE3("render minimap");
429
430 // The terrain isn't actually initialized until the map is loaded, which
431 // happens when the game is started, so abort until then.
432 if (!g_Game || !g_Game->IsGameStarted())
433 return;
434
435 CSimulation2* sim = g_Game->GetSimulation2();
436 CmpPtr<ICmpRangeManager> cmpRangeManager(*sim, SYSTEM_ENTITY);
437 ENSURE(cmpRangeManager);
438
439 // Set our globals in case they hadn't been set before
440 m_Camera = g_Game->GetView()->GetCamera();
441 m_Terrain = g_Game->GetWorld()->GetTerrain();
442 m_Width = (u32)(m_CachedActualSize.right - m_CachedActualSize.left);
443 m_Height = (u32)(m_CachedActualSize.bottom - m_CachedActualSize.top);
444 m_MapSize = m_Terrain->GetVerticesPerSide();
445 m_TextureSize = (GLsizei)round_up_to_pow2((size_t)m_MapSize);
446 m_MapScale = (cmpRangeManager->GetLosCircular() ? 1.f : 1.414f);
447
448 if (!m_TerrainTexture || g_GameRestarted)
449 CreateTextures();
450
451
452 // only update 2x / second
453 // (note: since units only move a few pixels per second on the minimap,
454 // we can get away with infrequent updates; this is slow)
455 // TODO: Update all but camera at same speed as simulation
456 static double last_time;
457 const double cur_time = timer_Time();
458 const bool doUpdate = cur_time - last_time > 0.5;
459 if (doUpdate)
460 {
461 last_time = cur_time;
462 if (m_TerrainDirty || m_WaterHeight != g_Renderer.GetWaterManager()->m_WaterHeight)
463 RebuildTerrainTexture();
464 }
465
466 const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom;
467 const float x2 = m_CachedActualSize.right, y2 = m_CachedActualSize.top;
468 const float z = GetBufferedZ();
469 const float texCoordMax = (float)(m_MapSize - 1) / (float)m_TextureSize;
470 const float angle = GetAngle();
471 const float unitScale = (cmpRangeManager->GetLosCircular() ? 1.f : m_MapScale/2.f);
472
473 CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture();
474
475 CShaderProgramPtr shader;
476 CShaderTechniquePtr tech;
477
478 CShaderDefines baseDefines;
479 baseDefines.Add(str_MINIMAP_BASE, str_1);
480 if (m_Mask)
481 baseDefines.Add(str_MINIMAP_MASK, str_1);
482
483 tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), baseDefines);
484 tech->BeginPass();
485 shader = tech->GetShader();
486
487 // Draw the main textured quad
488 shader->BindTexture(str_baseTex, m_TerrainTexture);
489 if (m_Mask)
490 {
491 shader->BindTexture(str_maskTex, losTexture.GetTexture());
492 CMatrix3D maskTextureTransform = *losTexture.GetMinimapTextureMatrix();
493 // We need to have texture coordinates in the same coordinate space.
494 const float scale = 1.0f / texCoordMax;
495 maskTextureTransform.Scale(scale, scale, 1.0f);
496 shader->Uniform(str_maskTextureTransform, maskTextureTransform);
497 }
498 const CMatrix3D baseTransform = GetDefaultGuiMatrix();
499 CMatrix3D baseTextureTransform;
500 baseTextureTransform.SetIdentity();
501 shader->Uniform(str_transform, baseTransform);
502 shader->Uniform(str_textureTransform, baseTextureTransform);
503
504 if (m_Mask)
505 {
506 glEnable(GL_BLEND);
507 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
508 }
509
510 DrawTexture(shader, texCoordMax, angle, x, y, x2, y2, z);
511
512 if (!m_Mask)
513 {
514 glEnable(GL_BLEND);
515 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
516 }
517
518 // Draw territory boundaries
519 CTerritoryTexture& territoryTexture = g_Game->GetView()->GetTerritoryTexture();
520
521 shader->BindTexture(str_baseTex, territoryTexture.GetTexture());
522 if (m_Mask)
523 {
524 shader->BindTexture(str_maskTex, losTexture.GetTexture());
525 shader->Uniform(str_maskTextureTransform, *losTexture.GetMinimapTextureMatrix());
526 }
527 const CMatrix3D* territoryTransform = territoryTexture.GetMinimapTextureMatrix();
528 shader->Uniform(str_transform, baseTransform);
529 shader->Uniform(str_textureTransform, *territoryTransform);
530
531 DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z);
532 tech->EndPass();
533
534 // Draw the LOS quad in black, using alpha values from the LOS texture
535 if (!m_Mask)
536 {
537 CShaderDefines losDefines;
538 losDefines.Add(str_MINIMAP_LOS, str_1);
539 tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), losDefines);
540 tech->BeginPass();
541 shader = tech->GetShader();
542 shader->BindTexture(str_baseTex, losTexture.GetTexture());
543
544 const CMatrix3D* losTransform = losTexture.GetMinimapTextureMatrix();
545 shader->Uniform(str_transform, baseTransform);
546 shader->Uniform(str_textureTransform, *losTransform);
547
548 DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z);
549 tech->EndPass();
550 }
551
552 glDisable(GL_BLEND);
553
554 PROFILE_START("minimap units");
555
556 CShaderDefines pointDefines;
557 pointDefines.Add(str_MINIMAP_POINT, str_1);
558 tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), pointDefines);
559 tech->BeginPass();
560 shader = tech->GetShader();
561 shader->Uniform(str_transform, baseTransform);
562 shader->Uniform(str_pointSize, 3.f);
563
564 CMatrix3D unitMatrix;
565 unitMatrix.SetIdentity();
566 // Center the minimap on the origin of the axis of rotation.
567 unitMatrix.Translate(-(x2 - x) / 2.f, -(y2 - y) / 2.f, 0.f);
568 // Rotate the map.
569 unitMatrix.RotateZ(angle);
570 // Scale square maps to fit.
571 unitMatrix.Scale(unitScale, unitScale, 1.f);
572 // Move the minimap back to it's starting position.
573 unitMatrix.Translate((x2 - x) / 2.f, (y2 - y) / 2.f, 0.f);
574 // Move the minimap to it's final location.
575 unitMatrix.Translate(x, y, z);
576 // Apply the gui matrix.
577 unitMatrix *= GetDefaultGuiMatrix();
578 // Load the transform into the shader.
579 shader->Uniform(str_transform, unitMatrix);
580
581 const float sx = (float)m_Width / ((m_MapSize - 1) * TERRAIN_TILE_SIZE);
582 const float sy = (float)m_Height / ((m_MapSize - 1) * TERRAIN_TILE_SIZE);
583
584 CSimulation2::InterfaceList ents = sim->GetEntitiesWithInterface(IID_Minimap);
585
586 if (doUpdate)
587 {
588 VertexArrayIterator<float[2]> attrPos = m_AttributePos.GetIterator<float[2]>();
589 VertexArrayIterator<u8[4]> attrColor = m_AttributeColor.GetIterator<u8[4]>();
590
591 m_EntitiesDrawn = 0;
592 MinimapUnitVertex v;
593 std::vector<MinimapUnitVertex> pingingVertices;
594 pingingVertices.reserve(MAX_ENTITIES_DRAWN / 2);
595
596 if (cur_time > m_NextBlinkTime)
597 {
598 m_BlinkState = !m_BlinkState;
599 m_NextBlinkTime = cur_time + m_HalfBlinkDuration;
600 }
601
602 entity_pos_t posX, posZ;
603 for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
604 {
605 ICmpMinimap* cmpMinimap = static_cast<ICmpMinimap*>(it->second);
606 if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ))
607 {
608 LosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, g_Game->GetSimulation2()->GetSimContext().GetCurrentDisplayedPlayer());
609 if (vis != LosVisibility::HIDDEN)
610 {
611 v.a = 255;
612 v.x = posX.ToFloat() * sx;
613 v.y = -posZ.ToFloat() * sy;
614
615 // Check minimap pinging to indicate something
616 if (m_BlinkState && cmpMinimap->CheckPing(cur_time, m_PingDuration))
617 {
618 v.r = 255; // ping color is white
619 v.g = 255;
620 v.b = 255;
621 pingingVertices.push_back(v);
622 }
623 else
624 {
625 addVertex(v, attrColor, attrPos);
626 ++m_EntitiesDrawn;
627 }
628 }
629 }
630 }
631
632 // Add the pinged vertices at the end, so they are drawn on top
633 for (const MinimapUnitVertex& vertex : pingingVertices)
634 {
635 addVertex(vertex, attrColor, attrPos);
636 ++m_EntitiesDrawn;
637 }
638
639 ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN);
640 m_VertexArray.Upload();
641 }
642
643 m_VertexArray.PrepareForRendering();
644
645 if (m_EntitiesDrawn > 0)
646 {
647#if !CONFIG2_GLES
648 glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
649#endif
650
651 u8* indexBase = m_IndexArray.Bind();
652 u8* base = m_VertexArray.Bind();
653 const GLsizei stride = (GLsizei)m_VertexArray.GetStride();
654
655 shader->VertexPointer(2, GL_FLOAT, stride, base + m_AttributePos.offset);
656 shader->ColorPointer(4, GL_UNSIGNED_BYTE, stride, base + m_AttributeColor.offset);
657 shader->AssertPointersBound();
658
659 if (!g_Renderer.m_SkipSubmit)
660 glDrawElements(GL_POINTS, (GLsizei)(m_EntitiesDrawn), GL_UNSIGNED_SHORT, indexBase);
661
662 g_Renderer.GetStats().m_DrawCalls++;
663 CVertexBuffer::Unbind();
664
665#if !CONFIG2_GLES
666 glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);
667#endif
668 }
669
670 tech->EndPass();
671
672 DrawViewRect(unitMatrix);
673
674 PROFILE_END("minimap units");
675}
676
677void CMiniMap::CreateTextures()
678{
679 Destroy();
680
681 // Create terrain texture
682 glGenTextures(1, &m_TerrainTexture);
683 g_Renderer.BindTexture(0, m_TerrainTexture);
684
685 // Initialise texture with solid black, for the areas we don't
686 // overwrite with glTexSubImage2D later
687 u32* texData = new u32[m_TextureSize * m_TextureSize];
688 for (ssize_t i = 0; i < m_TextureSize * m_TextureSize; ++i)
689 texData[i] = 0xFF000000;
690 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData);
691 delete[] texData;
692
693 m_TerrainData = new u32[(m_MapSize - 1) * (m_MapSize - 1)];
694 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
695 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
696 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
697 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
698
699 // Rebuild and upload both of them
700 RebuildTerrainTexture();
701}
702
703
704void CMiniMap::RebuildTerrainTexture()
705{
706 u32 x = 0;
707 u32 y = 0;
708 u32 w = m_MapSize - 1;
709 u32 h = m_MapSize - 1;
710 m_WaterHeight = g_Renderer.GetWaterManager()->m_WaterHeight;
711
712 m_TerrainDirty = false;
713
714 for (u32 j = 0; j < h; ++j)
715 {
716 u32* dataPtr = m_TerrainData + ((y + j) * (m_MapSize - 1)) + x;
717 for (u32 i = 0; i < w; ++i)
718 {
719 float avgHeight = ( m_Terrain->GetVertexGroundLevel((int)i, (int)j)
720 + m_Terrain->GetVertexGroundLevel((int)i+1, (int)j)
721 + m_Terrain->GetVertexGroundLevel((int)i, (int)j+1)
722 + m_Terrain->GetVertexGroundLevel((int)i+1, (int)j+1)
723 ) / 4.0f;
724
725 if (avgHeight < m_WaterHeight && avgHeight > m_WaterHeight - m_ShallowPassageHeight)
726 {
727 // shallow water
728 *dataPtr++ = 0xffc09870;
729 }
730 else if (avgHeight < m_WaterHeight)
731 {
732 // Set water as constant color for consistency on different maps
733 *dataPtr++ = 0xffa07850;
734 }
735 else
736 {
737 int hmap = ((int)m_Terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8;
738 int val = (hmap / 3) + 170;
739
740 u32 color = 0xFFFFFFFF;
741
742 CMiniPatch* mp = m_Terrain->GetTile(x + i, y + j);
743 if (mp)
744 {
745 CTerrainTextureEntry* tex = mp->GetTextureEntry();
746 if (tex)
747 {
748 // If the texture can't be loaded yet, set the dirty flags
749 // so we'll try regenerating the terrain texture again soon
750 if(!tex->GetTexture()->TryLoad())
751 m_TerrainDirty = true;
752
753 color = tex->GetBaseColor();
754 }
755 }
756
757 *dataPtr++ = ScaleColor(color, float(val) / 255.0f);
758 }
759 }
760 }
761
762 // Upload the texture
763 g_Renderer.BindTexture(0, m_TerrainTexture);
764 glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_RGBA, GL_UNSIGNED_BYTE, m_TerrainData);
765}
766
767void CMiniMap::Destroy()
768{
769 if (m_TerrainTexture)
770 {
771 glDeleteTextures(1, &m_TerrainTexture);
772 m_TerrainTexture = 0;
773 }
774
775 SAFE_ARRAY_DELETE(m_TerrainData);
776}
777
778// static
779float CMiniMap::GetShallowPassageHeight()
780{
781 float shallowPassageHeight = 0.0f;
782 CParamNode externalParamNode;
783 CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder");
784 const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses");
785 if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk())
786 shallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat();
787 return shallowPassageHeight;
788}
Note: See TracBrowser for help on using the repository browser.