1 /* This file is part of the Spring engine (GPL v2 or later), see LICENSE.html */
2 
3 
4 #include <cstdlib>
5 
6 #include "ReadMap.h"
7 #include "MapDamage.h"
8 #include "MapInfo.h"
9 #include "MetalMap.h"
10 // #include "SM3/SM3Map.h"
11 #include "SMF/SMFReadMap.h"
12 #include "Game/LoadScreen.h"
13 #include "System/bitops.h"
14 #include "System/EventHandler.h"
15 #include "System/Exceptions.h"
16 #include "System/myMath.h"
17 #include "System/TimeProfiler.h"
18 #include "System/ThreadPool.h"
19 #include "System/FileSystem/ArchiveScanner.h"
20 #include "System/FileSystem/FileHandler.h"
21 #include "System/FileSystem/FileSystem.h"
22 #include "System/Misc/RectangleOptimizer.h"
23 
24 #ifdef USE_UNSYNCED_HEIGHTMAP
25 #include "Game/GlobalUnsynced.h"
26 #include "Sim/Misc/LosHandler.h"
27 #endif
28 
29 //////////////////////////////////////////////////////////////////////
30 // Construction/Destruction
31 //////////////////////////////////////////////////////////////////////
32 
33 // assigned to in CGame::CGame ("readMap = CReadMap::LoadMap(mapname)")
34 CReadMap* readMap = NULL;
35 
36 #ifdef USE_UNSYNCED_HEIGHTMAP
37 	#define	HEIGHTMAP_DIGESTS \
38 		CR_MEMBER(syncedHeightMapDigests), \
39 		CR_MEMBER(unsyncedHeightMapDigests),
40 #else
41 	#define HEIGHTMAP_DIGESTS
42 #endif
43 
44 CR_BIND_INTERFACE(CReadMap)
45 CR_REG_METADATA(CReadMap, (
46 	CR_MEMBER(metalMap),
47 	CR_MEMBER(initMinHeight),
48 	CR_MEMBER(initMaxHeight),
49 	CR_IGNORED(currMinHeight),
50 	CR_IGNORED(currMaxHeight),
51 	CR_MEMBER(mapChecksum),
52 	//CR_MEMBER(heightMapSyncedPtr),
53 	//CR_MEMBER(heightMapUnsyncedPtr),
54 	CR_MEMBER(originalHeightMap),
55 	CR_IGNORED(centerHeightMap),
56 	CR_IGNORED(mipCenterHeightMaps),
57 	//CR_MEMBER(mipPointerHeightMaps),
58 	CR_IGNORED(visVertexNormals),
59 	CR_IGNORED(faceNormalsSynced),
60 	CR_IGNORED(faceNormalsUnsynced),
61 	CR_IGNORED(centerNormalsSynced),
62 	CR_IGNORED(centerNormalsUnsynced),
63 	CR_IGNORED(slopeMap),
64 	CR_MEMBER(typeMap),
65 	CR_MEMBER(unsyncedHeightMapUpdates),
66 	CR_MEMBER(unsyncedHeightMapUpdatesTemp),
67 	HEIGHTMAP_DIGESTS
68 	CR_SERIALIZER(Serialize)
69 ))
70 
71 
LoadMap(const std::string & mapname)72 CReadMap* CReadMap::LoadMap(const std::string& mapname)
73 {
74 	CReadMap* rm = NULL;
75 
76 	if (FileSystem::GetExtension(mapname) == "sm3") {
77 		throw content_error("[CReadMap::LoadMap] SM3 maps are no longer supported as of Spring 95.0");
78 		// rm = new CSM3ReadMap(mapname);
79 	} else {
80 		// assume SMF format by default
81 		rm = new CSMFReadMap(mapname);
82 	}
83 
84 	if (rm == NULL)
85 		return NULL;
86 
87 	/* read metal- and type-map */
88 	MapBitmapInfo mbi;
89 	MapBitmapInfo tbi;
90 
91 	unsigned char* metalmapPtr = rm->GetInfoMap("metal", &mbi);
92 	unsigned char* typemapPtr = rm->GetInfoMap("type", &tbi);
93 
94 	assert(mbi.width == (gs->mapx >> 1));
95 	assert(mbi.height == (gs->mapy >> 1));
96 
97 	rm->metalMap = new CMetalMap(metalmapPtr, mbi.width, mbi.height, mapInfo->map.maxMetal);
98 
99 	if (metalmapPtr != NULL)
100 		rm->FreeInfoMap("metal", metalmapPtr);
101 
102 	if (typemapPtr && tbi.width == (gs->mapx >> 1) && tbi.height == (gs->mapy >> 1)) {
103 		assert(gs->hmapx == tbi.width && gs->hmapy == tbi.height);
104 		rm->typeMap.resize(tbi.width * tbi.height);
105 		memcpy(&rm->typeMap[0], typemapPtr, tbi.width * tbi.height);
106 	} else
107 		throw content_error("[CReadMap::LoadMap] bad/no terrain typemap");
108 
109 	if (typemapPtr != NULL)
110 		rm->FreeInfoMap("type", typemapPtr);
111 
112 	return rm;
113 }
114 
115 
Serialize(creg::ISerializer & s)116 void CReadMap::Serialize(creg::ISerializer& s)
117 {
118 	// remove the const
119 	const float* cshm = GetCornerHeightMapSynced();
120 	      float*  shm = const_cast<float*>(cshm);
121 
122 	s.Serialize(shm, 4 * gs->mapxp1 * gs->mapyp1);
123 
124 	if (!s.IsWriting())
125 		mapDamage->RecalcArea(2, gs->mapx - 3, 2, gs->mapy - 3);
126 }
127 
128 
CReadMap()129 CReadMap::CReadMap()
130 	: metalMap(NULL)
131 	, heightMapSyncedPtr(NULL)
132 	, heightMapUnsyncedPtr(NULL)
133 	, mapChecksum(0)
134 	, initMinHeight(0.0f)
135 	, initMaxHeight(0.0f)
136 	, currMinHeight(0.0f)
137 	, currMaxHeight(0.0f)
138 {
139 }
140 
141 
~CReadMap()142 CReadMap::~CReadMap()
143 {
144 	delete metalMap;
145 }
146 
147 
Initialize()148 void CReadMap::Initialize()
149 {
150 	// set global map info (TODO: move these to ReadMap!)
151 	gs->mapxm1 = gs->mapx - 1;
152 	gs->mapxp1 = gs->mapx + 1;
153 	gs->mapym1 = gs->mapy - 1;
154 	gs->mapyp1 = gs->mapy + 1;
155 	gs->mapSquares = gs->mapx * gs->mapy;
156 	gs->hmapx = gs->mapx >> 1;
157 	gs->hmapy = gs->mapy >> 1;
158 	gs->pwr2mapx = next_power_of_2(gs->mapx);
159 	gs->pwr2mapy = next_power_of_2(gs->mapy);
160 
161 	{
162 		char loadMsg[512];
163 		const char* fmtString = "Loading Map (%u MB)";
164 		unsigned int reqMemFootPrintKB =
165 			((( gs->mapxp1)   * gs->mapyp1  * 2     * sizeof(float))         / 1024) +   // cornerHeightMap{Synced, Unsynced}
166 			((( gs->mapxp1)   * gs->mapyp1  *         sizeof(float))         / 1024) +   // originalHeightMap
167 			((  gs->mapx      * gs->mapy    * 2 * 2 * sizeof(float3))        / 1024) +   // faceNormals{Synced, Unsynced}
168 			((  gs->mapx      * gs->mapy    * 2     * sizeof(float3))        / 1024) +   // centerNormals{Synced, Unsynced}
169 			((( gs->mapxp1)   * gs->mapyp1          * sizeof(float3))        / 1024) +   // VisVertexNormals
170 			((  gs->mapx      * gs->mapy            * sizeof(float))         / 1024) +   // centerHeightMap
171 			((  gs->hmapx     * gs->hmapy           * sizeof(float))         / 1024) +   // slopeMap
172 			((  gs->hmapx     * gs->hmapy           * sizeof(float))         / 1024) +   // MetalMap::extractionMap
173 			((  gs->hmapx     * gs->hmapy           * sizeof(unsigned char)) / 1024);    // MetalMap::metalMap
174 
175 		// mipCenterHeightMaps[i]
176 		for (int i = 1; i < numHeightMipMaps; i++) {
177 			reqMemFootPrintKB += ((((gs->mapx >> i) * (gs->mapy >> i)) * sizeof(float)) / 1024);
178 		}
179 
180 		sprintf(loadMsg, fmtString, reqMemFootPrintKB / 1024);
181 		loadscreen->SetLoadMessage(loadMsg);
182 	}
183 
184 	float3::maxxpos = gs->mapx * SQUARE_SIZE - 1;
185 	float3::maxzpos = gs->mapy * SQUARE_SIZE - 1;
186 
187 	originalHeightMap.resize(gs->mapxp1 * gs->mapyp1);
188 	faceNormalsSynced.resize(gs->mapx * gs->mapy * 2);
189 	faceNormalsUnsynced.resize(gs->mapx * gs->mapy * 2);
190 	centerNormalsSynced.resize(gs->mapx * gs->mapy);
191 	centerNormalsUnsynced.resize(gs->mapx * gs->mapy);
192 	centerHeightMap.resize(gs->mapx * gs->mapy);
193 
194 	mipCenterHeightMaps.resize(numHeightMipMaps - 1);
195 	mipPointerHeightMaps.resize(numHeightMipMaps, NULL);
196 	mipPointerHeightMaps[0] = &centerHeightMap[0];
197 
198 	for (int i = 1; i < numHeightMipMaps; i++) {
199 		mipCenterHeightMaps[i - 1].resize((gs->mapx >> i) * (gs->mapy >> i));
200 		mipPointerHeightMaps[i] = &mipCenterHeightMaps[i - 1][0];
201 	}
202 
203 	slopeMap.resize(gs->hmapx * gs->hmapy);
204 	visVertexNormals.resize(gs->mapxp1 * gs->mapyp1);
205 
206 	// note: if USE_UNSYNCED_HEIGHTMAP is false, then
207 	// heightMapUnsyncedPtr points to an empty vector
208 	// for SMF maps so indexing it is forbidden (!)
209 	assert(heightMapSyncedPtr != NULL);
210 	assert(heightMapUnsyncedPtr != NULL);
211 
212 	{
213 		#ifndef USE_UNSYNCED_HEIGHTMAP
214 		heightMapUnsyncedPtr = heightMapSyncedPtr;
215 		#endif
216 
217 		sharedCornerHeightMaps[0] = &(*heightMapUnsyncedPtr)[0];
218 		sharedCornerHeightMaps[1] = &(*heightMapSyncedPtr)[0];
219 
220 		sharedCenterHeightMaps[0] = &centerHeightMap[0]; // NO UNSYNCED VARIANT
221 		sharedCenterHeightMaps[1] = &centerHeightMap[0];
222 
223 		sharedFaceNormals[0] = &faceNormalsUnsynced[0];
224 		sharedFaceNormals[1] = &faceNormalsSynced[0];
225 
226 		sharedCenterNormals[0] = &centerNormalsUnsynced[0];
227 		sharedCenterNormals[1] = &centerNormalsSynced[0];
228 
229 		sharedSlopeMaps[0] = &slopeMap[0]; // NO UNSYNCED VARIANT
230 		sharedSlopeMaps[1] = &slopeMap[0];
231 	}
232 
233 	CalcHeightmapChecksum();
234 	UpdateHeightMapSynced(SRectangle(0, 0, gs->mapx, gs->mapy), true);
235 	// FIXME can't call that yet cause sky & skyLight aren't created yet (crashes in SMFReadMap.cpp)
236 	// UpdateDraw();
237 }
238 
239 
CalcHeightmapChecksum()240 void CReadMap::CalcHeightmapChecksum()
241 {
242 	const float* heightmap = GetCornerHeightMapSynced();
243 
244 	initMinHeight =  std::numeric_limits<float>::max();
245 	initMaxHeight = -std::numeric_limits<float>::max();
246 
247 	mapChecksum = 0;
248 	for (int i = 0; i < (gs->mapxp1 * gs->mapyp1); ++i) {
249 		originalHeightMap[i] = heightmap[i];
250 		if (heightmap[i] < initMinHeight) { initMinHeight = heightmap[i]; }
251 		if (heightmap[i] > initMaxHeight) { initMaxHeight = heightmap[i]; }
252 		mapChecksum +=  (unsigned int) (heightmap[i] * 100);
253 		mapChecksum ^= *(unsigned int*) &heightmap[i];
254 	}
255 
256 	for (unsigned int a = 0; a < mapInfo->map.name.size(); ++a) {
257 		mapChecksum += mapInfo->map.name[a];
258 		mapChecksum *= mapInfo->map.name[a];
259 	}
260 
261 	currMinHeight = initMinHeight;
262 	currMaxHeight = initMaxHeight;
263 }
264 
265 
UpdateDraw()266 void CReadMap::UpdateDraw()
267 {
268 	if (unsyncedHeightMapUpdates.empty())
269 		return;
270 
271 	std::list<SRectangle> ushmu;
272 	std::list<SRectangle>::const_iterator ushmuIt;
273 
274 	{
275 		if (!unsyncedHeightMapUpdates.empty())
276 			unsyncedHeightMapUpdates.swap(unsyncedHeightMapUpdatesTemp); // swap to avoid Optimize() inside a mutex
277 	}
278 	{
279 		static bool first = true;
280 		if (!first) {
281 			if (!unsyncedHeightMapUpdatesTemp.empty()) {
282 				unsyncedHeightMapUpdatesTemp.Optimize();
283 				int updateArea = unsyncedHeightMapUpdatesTemp.GetTotalArea() * 0.0625f + (50 * 50);
284 
285 				while (updateArea > 0 && !unsyncedHeightMapUpdatesTemp.empty()) {
286 					const SRectangle& rect = unsyncedHeightMapUpdatesTemp.front();
287 					updateArea -= rect.GetArea();
288 					ushmu.push_back(rect);
289 					unsyncedHeightMapUpdatesTemp.pop_front();
290 				}
291 			}
292 		} else {
293 			// first update is full map
294 			unsyncedHeightMapUpdatesTemp.swap(ushmu);
295 			first = false;
296 		}
297 	}
298 	if (!unsyncedHeightMapUpdatesTemp.empty()) {
299 		unsyncedHeightMapUpdates.splice(unsyncedHeightMapUpdates.end(), unsyncedHeightMapUpdatesTemp);
300 	}
301 	// unsyncedHeightMapUpdatesTemp is now guaranteed empty
302 
303 	for (ushmuIt = ushmu.begin(); ushmuIt != ushmu.end(); ++ushmuIt) {
304 		UpdateHeightMapUnsynced(*ushmuIt);
305 	}
306 	for (ushmuIt = ushmu.begin(); ushmuIt != ushmu.end(); ++ushmuIt) {
307 		eventHandler.UnsyncedHeightMapUpdate(*ushmuIt);
308 	}
309 }
310 
311 
UpdateHeightMapSynced(SRectangle rect,bool initialize)312 void CReadMap::UpdateHeightMapSynced(SRectangle rect, bool initialize)
313 {
314 	if (rect.GetArea() <= 0) {
315 		// do not bother with zero-area updates
316 		return;
317 	}
318 
319 	rect.x1 = std::max(         0, rect.x1 - 1);
320 	rect.z1 = std::max(         0, rect.z1 - 1);
321 	rect.x2 = std::min(gs->mapxm1, rect.x2 + 1);
322 	rect.z2 = std::min(gs->mapym1, rect.z2 + 1);
323 
324 	UpdateCenterHeightmap(rect, initialize);
325 	UpdateMipHeightmaps(rect, initialize);
326 	UpdateFaceNormals(rect, initialize);
327 	UpdateSlopemap(rect, initialize); // must happen after UpdateFaceNormals()!
328 
329 #ifdef USE_UNSYNCED_HEIGHTMAP
330 	// push the unsynced update
331 	if (initialize) {
332 		// push 1st update through without LOS check
333 		unsyncedHeightMapUpdates.push_back(rect);
334 	} else {
335 		InitHeightMapDigestsVectors();
336 
337 		const int losSquaresX = losHandler->losSizeX; // size of LOS square in heightmap coords
338 		const SRectangle& lm = rect * (SQUARE_SIZE * losHandler->invLosDiv); // LOS space
339 
340 		// we updated the heightmap so change their digest (byte-overflow is intentional!)
341 		for (int lmx = lm.x1; lmx <= lm.x2; ++lmx) {
342 			for (int lmz = lm.z1; lmz <= lm.z2; ++lmz) {
343 				const int idx = lmx + lmz * (losSquaresX + 1);
344 				assert(idx < syncedHeightMapDigests.size());
345 				syncedHeightMapDigests[idx]++;
346 			}
347 		}
348 
349 		HeightMapUpdateLOSCheck(rect);
350 	}
351 #else
352 	unsyncedHeightMapUpdates.push_back(rect);
353 #endif
354 }
355 
356 
UpdateCenterHeightmap(const SRectangle & rect,bool initialize)357 void CReadMap::UpdateCenterHeightmap(const SRectangle& rect, bool initialize)
358 {
359 	const float* heightmapSynced = GetCornerHeightMapSynced();
360 
361 	for (int y = rect.z1; y <= rect.z2; y++) {
362 		for (int x = rect.x1; x <= rect.x2; x++) {
363 			const int idxTL = (y    ) * gs->mapxp1 + x;
364 			const int idxTR = (y    ) * gs->mapxp1 + x + 1;
365 			const int idxBL = (y + 1) * gs->mapxp1 + x;
366 			const int idxBR = (y + 1) * gs->mapxp1 + x + 1;
367 
368 			const float height =
369 				heightmapSynced[idxTL] +
370 				heightmapSynced[idxTR] +
371 				heightmapSynced[idxBL] +
372 				heightmapSynced[idxBR];
373 			centerHeightMap[y * gs->mapx + x] = height * 0.25f;
374 		}
375 	}
376 }
377 
378 
UpdateMipHeightmaps(const SRectangle & rect,bool initialize)379 void CReadMap::UpdateMipHeightmaps(const SRectangle& rect, bool initialize)
380 {
381 	for (int i = 0; i < numHeightMipMaps - 1; i++) {
382 		const int hmapx = gs->mapx >> i;
383 
384 		const int sx = (rect.x1 >> i) & (~1);
385 		const int ex = (rect.x2 >> i);
386 		const int sy = (rect.z1 >> i) & (~1);
387 		const int ey = (rect.z2 >> i);
388 
389 		for (int y = sy; y < ey; y += 2) {
390 			for (int x = sx; x < ex; x += 2) {
391 				const float height =
392 					mipPointerHeightMaps[i][(x    ) + (y    ) * hmapx] +
393 					mipPointerHeightMaps[i][(x    ) + (y + 1) * hmapx] +
394 					mipPointerHeightMaps[i][(x + 1) + (y    ) * hmapx] +
395 					mipPointerHeightMaps[i][(x + 1) + (y + 1) * hmapx];
396 				mipPointerHeightMaps[i + 1][(x / 2) + (y / 2) * hmapx / 2] = height * 0.25f;
397 			}
398 		}
399 	}
400 }
401 
402 
UpdateFaceNormals(const SRectangle & rect,bool initialize)403 void CReadMap::UpdateFaceNormals(const SRectangle& rect, bool initialize)
404 {
405 	const float* heightmapSynced = GetCornerHeightMapSynced();
406 
407 	const int z1 = std::max(         0, rect.z1 - 1);
408 	const int x1 = std::max(         0, rect.x1 - 1);
409 	const int z2 = std::min(gs->mapym1, rect.z2 + 1);
410 	const int x2 = std::min(gs->mapxm1, rect.x2 + 1);
411 
412 	for_mt(z1, z2+1, [&](const int y) {
413 		float3 fnTL;
414 		float3 fnBR;
415 
416 		for (int x = x1; x <= x2; x++) {
417 			const int idxTL = (y    ) * gs->mapxp1 + x; // TL
418 			const int idxBL = (y + 1) * gs->mapxp1 + x; // BL
419 
420 			const float& hTL = heightmapSynced[idxTL    ];
421 			const float& hTR = heightmapSynced[idxTL + 1];
422 			const float& hBL = heightmapSynced[idxBL    ];
423 			const float& hBR = heightmapSynced[idxBL + 1];
424 
425 			// normal of top-left triangle (face) in square
426 			//
427 			//  *---> e1
428 			//  |
429 			//  |
430 			//  v
431 			//  e2
432 			//const float3 e1( SQUARE_SIZE, hTR - hTL,           0);
433 			//const float3 e2(           0, hBL - hTL, SQUARE_SIZE);
434 			//const float3 fnTL = (e2.cross(e1)).Normalize();
435 			fnTL.y = SQUARE_SIZE;
436 			fnTL.x = - (hTR - hTL);
437 			fnTL.z = - (hBL - hTL);
438 			fnTL.Normalize();
439 
440 			// normal of bottom-right triangle (face) in square
441 			//
442 			//         e3
443 			//         ^
444 			//         |
445 			//         |
446 			//  e4 <---*
447 			//const float3 e3(-SQUARE_SIZE, hBL - hBR,           0);
448 			//const float3 e4(           0, hTR - hBR,-SQUARE_SIZE);
449 			//const float3 fnBR = (e4.cross(e3)).Normalize();
450 			fnBR.y = SQUARE_SIZE;
451 			fnBR.x = (hBL - hBR);
452 			fnBR.z = (hTR - hBR);
453 			fnBR.Normalize();
454 
455 			faceNormalsSynced[(y * gs->mapx + x) * 2    ] = fnTL;
456 			faceNormalsSynced[(y * gs->mapx + x) * 2 + 1] = fnBR;
457 			// square-normal
458 			centerNormalsSynced[y * gs->mapx + x] = (fnTL + fnBR).Normalize();
459 
460 			#ifdef USE_UNSYNCED_HEIGHTMAP
461 			if (initialize) {
462 				faceNormalsUnsynced[(y * gs->mapx + x) * 2    ] = faceNormalsSynced[(y * gs->mapx + x) * 2    ];
463 				faceNormalsUnsynced[(y * gs->mapx + x) * 2 + 1] = faceNormalsSynced[(y * gs->mapx + x) * 2 + 1];
464 				centerNormalsUnsynced[y * gs->mapx + x] = centerNormalsSynced[y * gs->mapx + x];
465 			}
466 			#endif
467 		}
468 	});
469 }
470 
471 
UpdateSlopemap(const SRectangle & rect,bool initialize)472 void CReadMap::UpdateSlopemap(const SRectangle& rect, bool initialize)
473 {
474 	const int sx = std::max(0, (rect.x1 / 2) - 1);
475 	const int ex = std::min(gs->hmapx - 1, (rect.x2 / 2) + 1);
476 	const int sy = std::max(0, (rect.z1 / 2) - 1);
477 	const int ey = std::min(gs->hmapy - 1, (rect.z2 / 2) + 1);
478 
479 	for (int y = sy; y <= ey; y++) {
480 		for (int x = sx; x <= ex; x++) {
481 			const int idx0 = (y*2    ) * (gs->mapx) + x*2;
482 			const int idx1 = (y*2 + 1) * (gs->mapx) + x*2;
483 
484 			float avgslope = 0.0f;
485 			avgslope += faceNormalsSynced[(idx0    ) * 2    ].y;
486 			avgslope += faceNormalsSynced[(idx0    ) * 2 + 1].y;
487 			avgslope += faceNormalsSynced[(idx0 + 1) * 2    ].y;
488 			avgslope += faceNormalsSynced[(idx0 + 1) * 2 + 1].y;
489 			avgslope += faceNormalsSynced[(idx1    ) * 2    ].y;
490 			avgslope += faceNormalsSynced[(idx1    ) * 2 + 1].y;
491 			avgslope += faceNormalsSynced[(idx1 + 1) * 2    ].y;
492 			avgslope += faceNormalsSynced[(idx1 + 1) * 2 + 1].y;
493 			avgslope *= 0.125f;
494 
495 			float maxslope =              faceNormalsSynced[(idx0    ) * 2    ].y;
496 			maxslope = std::min(maxslope, faceNormalsSynced[(idx0    ) * 2 + 1].y);
497 			maxslope = std::min(maxslope, faceNormalsSynced[(idx0 + 1) * 2    ].y);
498 			maxslope = std::min(maxslope, faceNormalsSynced[(idx0 + 1) * 2 + 1].y);
499 			maxslope = std::min(maxslope, faceNormalsSynced[(idx1    ) * 2    ].y);
500 			maxslope = std::min(maxslope, faceNormalsSynced[(idx1    ) * 2 + 1].y);
501 			maxslope = std::min(maxslope, faceNormalsSynced[(idx1 + 1) * 2    ].y);
502 			maxslope = std::min(maxslope, faceNormalsSynced[(idx1 + 1) * 2 + 1].y);
503 
504 			// smooth it a bit, so small holes don't block huge tanks
505 			const float lerp = maxslope / avgslope;
506 			const float slope = mix(maxslope, avgslope, lerp);
507 
508 			slopeMap[y * gs->hmapx + x] = 1.0f - slope;
509 		}
510 	}
511 }
512 
513 
514 /// split the update into multiple invididual (los-square) chunks:
HeightMapUpdateLOSCheck(const SRectangle & rect)515 void CReadMap::HeightMapUpdateLOSCheck(const SRectangle& rect)
516 {
517 	InitHeightMapDigestsVectors();
518 	const int losSqSize = losHandler->losDiv / SQUARE_SIZE; // size of LOS square in heightmap coords
519 	const SRectangle& lm = rect * (SQUARE_SIZE * losHandler->invLosDiv); // LOS space
520 
521 	for (int lmz = lm.z1; lmz <= lm.z2; ++lmz) {
522 		const int hmz = lmz * losSqSize;
523 		      int hmx = lm.x1 * losSqSize;
524 
525 		SRectangle subrect(hmx, hmz, hmx, hmz + losSqSize);
526 		#define PUSH_RECT \
527 			if (subrect.GetArea() > 0) { \
528 				subrect.ClampIn(rect); \
529 				unsyncedHeightMapUpdates.push_back(subrect); \
530 				subrect = SRectangle(hmx + losSqSize, hmz, hmx + losSqSize, hmz + losSqSize); \
531 			} else { \
532 				subrect.x1 = hmx + losSqSize; \
533 				subrect.x2 = hmx + losSqSize; \
534 			}
535 
536 		for (int lmx = lm.x1; lmx <= lm.x2; ++lmx) {
537 			hmx = lmx * losSqSize;
538 
539 			#ifdef USE_UNSYNCED_HEIGHTMAP
540 			if (!(gu->spectatingFullView || losHandler->InLos(hmx, hmz, gu->myAllyTeam))) {
541 				PUSH_RECT
542 				continue;
543 			}
544 			#endif
545 
546 			if (!HasHeightMapChanged(lmx, lmz)) {
547 				PUSH_RECT
548 				continue;
549 			}
550 
551 			// update rectangle size
552 			subrect.x2 = hmx + losSqSize;
553 		}
554 
555 		PUSH_RECT
556 	}
557 }
558 
559 
InitHeightMapDigestsVectors()560 void CReadMap::InitHeightMapDigestsVectors()
561 {
562 #ifdef USE_UNSYNCED_HEIGHTMAP
563 	if (syncedHeightMapDigests.empty()) {
564 		const int losSquaresX = losHandler->losSizeX;
565 		const int losSquaresY = losHandler->losSizeY;
566 		const int size = (losSquaresX + 1) * (losSquaresY + 1);
567 		syncedHeightMapDigests.resize(size, 0);
568 		unsyncedHeightMapDigests.resize(size, 0);
569 	}
570 #endif
571 }
572 
573 
HasHeightMapChanged(const int lmx,const int lmy)574 bool CReadMap::HasHeightMapChanged(const int lmx, const int lmy)
575 {
576 #ifdef USE_UNSYNCED_HEIGHTMAP
577 	const int losSquaresX = losHandler->losSizeX;
578 	const int idx = lmx + lmy * (losSquaresX + 1);
579 	assert(idx < syncedHeightMapDigests.size());
580 	const bool heightmapChanged = (unsyncedHeightMapDigests[idx] != syncedHeightMapDigests[idx]);
581 	if (heightmapChanged) {
582 		unsyncedHeightMapDigests[idx] = syncedHeightMapDigests[idx];
583 	}
584 	return heightmapChanged;
585 #else
586 	return true;
587 #endif
588 }
589 
590 
UpdateLOS(const SRectangle & rect)591 void CReadMap::UpdateLOS(const SRectangle& rect)
592 {
593 #ifdef USE_UNSYNCED_HEIGHTMAP
594 	if (gu->spectatingFullView)
595 		return;
596 
597 	// currently we use the LOS for view updates (alternatives are AirLOS and/or radar)
598 	// cause the others use different resolutions we must check it here for safety
599 	// (if you want to use another source you need to change the res. of syncedHeightMapDigests etc.)
600 	assert(rect.GetWidth() <= losHandler->losDiv / SQUARE_SIZE);
601 
602 	//HACK: UpdateLOS() is called for single LOS squares, but we use <= in HeightMapUpdateLOSCheck().
603 	// This would make our update area 4x as large, so we need to make the rectangle a point. Better
604 	// would be to use < instead of <= everywhere.
605 	SRectangle r = rect;
606 	r.x2 = r.x1;
607 	r.z2 = r.z1;
608 
609 	HeightMapUpdateLOSCheck(rect);
610 #endif
611 }
612 
613 
BecomeSpectator()614 void CReadMap::BecomeSpectator()
615 {
616 #ifdef USE_UNSYNCED_HEIGHTMAP
617 	HeightMapUpdateLOSCheck(SRectangle(0, 0, gs->mapx, gs->mapy));
618 #endif
619 }
620 
HasVisibleWater() const621 bool CReadMap::HasVisibleWater() const { return (!mapInfo->map.voidWater && !IsAboveWater()); }
HasOnlyVoidWater() const622 bool CReadMap::HasOnlyVoidWater() const { return (mapInfo->map.voidWater && IsUnderWater()); }
623 
624