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