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