1 //       _________ __                 __
2 //      /   _____//  |_____________ _/  |______     ____  __ __  ______
3 //      \_____  \\   __\_  __ \__  \\   __\__  \   / ___\|  |  \/  ___/
4 //      /        \|  |  |  | \// __ \|  |  / __ \_/ /_/  >  |  /\___ |
5 //     /_______  /|__|  |__|  (____  /__| (____  /\___  /|____//____  >
6 //             \/                  \/          \//_____/            \/
7 //  ______________________                           ______________________
8 //                        T H E   W A R   B E G I N S
9 //         Stratagus - A free fantasy real time strategy game engine
10 //
11 /**@name fov.cpp - The field of view handling. */
12 //
13 //      (c) Copyright 2001-2021 by Joris Dauphin, JLutz Sammer,
14 //		Vladi Shabanski, Russell Smith, Jimmy Salmon, Pali Rohár, Andrettin
15 //      and Alyokhin
16 //
17 //      This program is free software; you can redistribute it and/or modify
18 //      it under the terms of the GNU General Public License as published by
19 //      the Free Software Foundation; only version 2 of the License.
20 //
21 //      This program is distributed in the hope that it will be useful,
22 //      but WITHOUT ANY WARRANTY; without even the implied warranty of
23 //      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24 //      GNU General Public License for more details.
25 //
26 //      You should have received a copy of the GNU General Public License
27 //      along with this program; if not, write to the Free Software
28 //      Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
29 //      02111-1307, USA.
30 //
31 
32 #include <queue>
33 #include "stratagus.h"
34 
35 #include "fov.h"
36 #include "settings.h"
37 #include "unit.h"
38 #include "unit_manager.h"
39 #include "unittype.h"
40 #include "util.h"
41 
42 /*----------------------------------------------------------------------------
43 --  Variables
44 ----------------------------------------------------------------------------*/
45 
46 /*----------------------------------------------------------------------------
47 --  Functions
48 ----------------------------------------------------------------------------*/
49 
50 /**
51 ** Select which type of Field of View to use
52 **
53 ** @param fov_type	type to set
54 ** @return true if success, false for wrong fov_type
55 */
SetType(const FieldOfViewTypes fov_type)56 bool CFieldOfView::SetType(const FieldOfViewTypes fov_type)
57 {
58 	if (fov_type != Settings.Type && fov_type < FieldOfViewTypes::NumOfTypes) {
59 		/// Unmark sight for all units
60 		MapRefreshUnitsSight(true);
61 
62 		this->Settings.Type = fov_type;
63 
64 		/// Mark sight with new fov type for all units
65 		MapRefreshUnitsSight();
66 		return true;
67 	} else {
68 		return false;
69 	}
70 }
71 
72 /**
73 ** Returns used type of Field of View
74 **
75 ** @return current Field of View type
76 */
GetType() const77 FieldOfViewTypes CFieldOfView::GetType() const
78 {
79 	return this->Settings.Type;
80 }
81 
82 /**
83 ** Set additional opaque map field flags (which terrains will be opaque)
84 **
85 ** @param flags	Terrain flags to set as opaque (MapField*)
86 */
SetOpaqueFields(const uint16_t flags)87 void CFieldOfView::SetOpaqueFields(const uint16_t flags)
88 {
89 	if (this->Settings.OpaqueFields != flags) {
90 		/// Unmark sight for all units
91 		MapRefreshUnitsSight(true);
92 
93 		this->Settings.OpaqueFields = flags;
94 
95 		/// Mark sight with new fov type for all units
96 		MapRefreshUnitsSight();
97 	}
98 }
99 
100 /**
101 ** Returns current set of opaque map field flags
102 **
103 ** @return Set of terrain flags
104 */
GetOpaqueFields() const105 uint16_t CFieldOfView::GetOpaqueFields() const
106 {
107 	return this->Settings.OpaqueFields;
108 }
109 
110 /**
111 ** Reset opaque map field flags to default (MapfieldOpaque)
112 **
113 */
ResetAdditionalOpaqueFields()114 void CFieldOfView::ResetAdditionalOpaqueFields()
115 {
116 	this->Settings.OpaqueFields = MapFieldOpaque;
117 }
118 
119 /**
120 **  Refresh the whole field of view for unit (Explore and make visible.)
121 **
122 **  @param player  player to mark the sight for
123 **	@param unit    unit to mark the sight for
124 **  @param pos     location to mark
125 **  @param width   width to mark, in square
126 **  @param height  height to mark, in square
127 **  @param range   Radius to mark.
128 **  @param marker  Function to mark or unmark sight
129 */
Refresh(const CPlayer & player,const CUnit & unit,const Vec2i & pos,const uint16_t width,const uint16_t height,const uint16_t range,MapMarkerFunc * marker)130 void CFieldOfView::Refresh(const CPlayer &player, const CUnit &unit, const Vec2i &pos, const uint16_t width,
131 							const uint16_t height, const uint16_t range, MapMarkerFunc *marker)
132 {
133 	/// FIXME: sometimes when quit from game this assert is triggered
134 	if (!unit.Type) return;
135 	Assert(unit.Type != NULL);
136 	// Units under construction have no sight range.
137 	if (!range) {
138 		return;
139 	}
140 	if (this->Settings.Type == FieldOfViewTypes::cShadowCasting && !unit.Type->AirUnit) {
141 		/// FIXME: add high-/lowground
142 		OpaqueFields = unit.Type->BoolFlag[ELEVATED_INDEX].value ? 0 : this->Settings.OpaqueFields;
143 		if (GameSettings.Inside) {
144 			OpaqueFields &= ~(MapFieldRocks); /// because of rocks-flag is used as an obstacle for ranged attackers
145 		}
146 		PrepareShadowCaster(player, unit, pos, marker);
147 		PrepareCache(pos, width, height, range);
148 		ProceedShadowCasting(pos, width, height, range + 1);
149 		ResetShadowCaster();
150 	} else {
151 		ProceedSimpleRadial(player, pos, width, height, range, marker);
152 	}
153 }
154 
155 /**
156 **  Refresh the whole sight of unit by SimleRadial algorithm. (Explore and make visible.)
157 **
158 **  @param player  player to mark the sight for (not unit owner)
159 **  @param pos     location to mark
160 **  @param w       width to mark, in square
161 **  @param h       height to mark, in square
162 **  @param range   Radius to mark (sight range)
163 **  @param marker  Function to mark or unmark sight
164 */
ProceedSimpleRadial(const CPlayer & player,const Vec2i & pos,const int16_t w,const int16_t h,const int16_t range,MapMarkerFunc * marker) const165 void CFieldOfView::ProceedSimpleRadial(const CPlayer &player, const Vec2i &pos,
166 									   const int16_t w, const int16_t h, const int16_t range,
167 									   MapMarkerFunc *marker) const
168 {
169 	// Up hemi-cyle
170 	const int16_t miny = std::max(-range, 0 - pos.y);
171 	for (int16_t offsety = miny; offsety != 0; offsety++) {
172 		const int16_t offsetx = isqrt(square(range + 1) - square(-offsety) - 1);
173 		const int16_t minx = std::max(0, pos.x - offsetx);
174 		const int16_t maxx = std::min(Map.Info.MapWidth, pos.x + w + offsetx);
175 		Vec2i mpos(minx, pos.y + offsety);
176 		const size_t index = mpos.y * Map.Info.MapWidth;
177 
178 		for (mpos.x = minx; mpos.x < maxx; mpos.x++) {
179 			marker(player, mpos.x + index);
180 		}
181 	}
182 	for (int16_t offsety = 0; offsety < h; offsety++) {
183 		const int16_t minx = std::max(0, pos.x - range);
184 		const int16_t maxx = std::min(Map.Info.MapWidth, pos.x + w + range);
185 		Vec2i mpos(minx, pos.y + offsety);
186 		const size_t index = mpos.y * Map.Info.MapWidth;
187 
188 		for (mpos.x = minx; mpos.x < maxx; mpos.x++) {
189 			marker(player, mpos.x + index);
190 		}
191 	}
192 	// bottom hemi-cycle
193 	const int16_t maxy = std::min(range, int16_t(Map.Info.MapHeight - pos.y - h));
194 	for (int16_t offsety = 0; offsety < maxy; offsety++) {
195 		const int16_t offsetx = isqrt(square(range + 1) - square(offsety + 1) - 1);
196 		const int16_t minx = std::max(0, pos.x - offsetx);
197 		const int16_t maxx = std::min(Map.Info.MapWidth, pos.x + w + offsetx);
198 		Vec2i mpos(minx, pos.y + h + offsety);
199 		const size_t index = mpos.y * Map.Info.MapWidth;
200 
201 		for (mpos.x = minx; mpos.x < maxx; mpos.x++) {
202 			marker(player, mpos.x + index);
203 		}
204 	}
205 }
206 
207 /**
208 ** Mark the sight of unit by ShadowCaster algorithm. (Explore and make visible.)
209 **
210 **  @param spectratorPos	Tile position of the spectrator unit - upper left corner for unit larger than 1x1
211 **  @param width			Spectrator's width in tiles
212 **  @param height			Spectrator's height in tiles
213 **  @param range			Spectrator's sight range in tiles
214 */
ProceedShadowCasting(const Vec2i & spectatorPos,const uint16_t width,const uint16_t height,const uint16_t range)215 inline void CFieldOfView::ProceedShadowCasting(const Vec2i &spectatorPos, const uint16_t width, const uint16_t height, const uint16_t range)
216 {
217 	enum SpectatorGeometry {cOneTiled, cEven, cOdd, cTall, cWide} ;
218 	const uint8_t geometry = [width, height]{   if (width == height) {
219 													if (width == 1) { return cOneTiled; }
220 													if (width % 2)  { return cOdd; 		}
221 													else            { return cEven; 	}
222 												}
223 												if (width > height) { return cWide; 	}
224 												else                { return cTall; 	}
225 											}();
226 
227 	Vec2i origin = {0, 0};
228 	uint16_t sightRange = range;
229 
230 	const bool isGeometrySymmetric = (geometry == cTall || geometry == cWide) ? false : true;
231 
232 	if (isGeometrySymmetric) {
233 		const int16_t half = width >> 1;
234 		origin.x = spectatorPos.x + half;
235 		origin.y = spectatorPos.y + half;
236 		sightRange += half - (geometry == cEven ? 1 : 0);
237 	} else {
238 		/// Fill spectator's tiles which not affected by RefreshOctant and ProceedRaysCast
239 		ResetEnvironment();
240 		for (int16_t x = spectatorPos.x + 1; x < spectatorPos.x + width - 1; x++) {
241 			for (int16_t y = spectatorPos.y + 1; y < spectatorPos.y + height - 1; y++) {
242 				if (SetCurrentTile(x, y)) {
243 					MarkTile();
244 				}
245 			}
246 		}
247 	}
248 
249 	int16_t rayWidth = 0;
250 	for (uint8_t quadrant = 0; quadrant < 4; quadrant++) {
251 		if (geometry == cEven) {
252 			/// recalculate center
253 			switch (quadrant) {
254 				case 1: origin.x--; break;
255 				case 2: origin.y--; break;
256 				case 3: origin.x++; break;
257 				default: break;  /// For quadrant 0 center is altready calculated
258 			}
259 		} else if (!isGeometrySymmetric) {
260 			switch (quadrant) {
261 				case 0:
262 					origin.x = spectatorPos.x + width - 1;
263 					origin.y = spectatorPos.y + height - 1;
264 					rayWidth = width - 2;
265 					break;
266 				case 1:
267 					origin.x = spectatorPos.x;
268 					rayWidth = height - 2;
269 					break;
270 				case 2:
271 					origin.y = spectatorPos.y;
272 					rayWidth = width - 2;
273 					break;
274 				case 3:
275 					origin.x = spectatorPos.x + width - 1;
276 					rayWidth = height - 2;
277 					break;
278 			}
279 		}
280 		const uint8_t octant = quadrant << 1;
281 		/// First half-quadrant
282 		RefreshOctant(octant, origin, sightRange);
283 		/// Second half-quadrant
284 		RefreshOctant(octant + 1, origin, sightRange);
285 
286 		/// calv FoV for asymmetric spectrator
287 		if (rayWidth) {
288 			ProceedRaysCast(octant, origin, rayWidth, sightRange);
289 		}
290 	}
291 }
292 
293 /**
294 **  Calc field of view for set of lines along x or y.
295 **	Used for calc part of FoV for assymetric (widht != height) spectators.
296 **
297 **  @param octant	Current work octant
298 **	@param origin	Tile position of the spectrator
299 **  @param width	Spectrator's width in tiles
300 **  @param height	Spectrator's height in tiles
301 **  @param range	Spectrator's sight range in tiles
302 */
ProceedRaysCast(const uint8_t octant,const Vec2i & origin,const uint16_t width,const uint16_t range)303 void CFieldOfView::ProceedRaysCast(const uint8_t octant, const Vec2i &origin, const uint16_t width, const uint16_t range)
304 {
305 	SetEnvironment(octant, origin);
306 	for (int16_t col = -1; col >= -width; col--) {
307 		for (int16_t row = 0; row < range; row++) {
308 			const bool isOnMap = SetCurrentTile(col, row);
309 			if (isOnMap) {
310 				MarkTile();
311 			}
312 			if (!isOnMap || IsTileOpaque()) { break; }
313 		}
314 	}
315 	ResetEnvironment();
316 }
317 
318 /**
319 **  Calc shadow casting field of view for single octant
320 **
321 **  @param octant	Octant to calc for
322 **	@param origin	Tile position of the spectrator
323 **  @param range	Spectrator's sight range in tiles
324 */
RefreshOctant(const uint8_t octant,const Vec2i & origin,const uint16_t range)325 void CFieldOfView::RefreshOctant(const uint8_t octant, const Vec2i &origin, const uint16_t range)
326 {
327 	SetEnvironment(octant, origin);
328 	std::queue<SColumnPiece> wrkQueue;
329 	wrkQueue.push(SColumnPiece(0, Vec2i(1, 1), Vec2i(1, 0)));
330 	while (!wrkQueue.empty()) {
331 		SColumnPiece current = wrkQueue.front();
332 		wrkQueue.pop();
333 		if (current.col >= range) {
334 			continue;
335 		}
336 		CalcFoVForColumnPiece(current.col, current.TopVector, current.BottomVector, range, wrkQueue);
337 	}
338 	ResetEnvironment();
339 }
340 
341 /**
342 **  Calc shadow casting for portion of column
343 **
344 **  @param col  		Column in current octant
345 **	@param topVector  	Top direction vector
346 **	@param bottomVector Top direction vector
347 **  @param range		Spectrator's sight range in tiles
348 **	@param wrkQueue		Queue with all column pieces
349 */
CalcFoVForColumnPiece(const int16_t col,Vec2i & topVector,Vec2i & bottomVector,const uint16_t range,std::queue<SColumnPiece> & wrkQueue)350 void  CFieldOfView::CalcFoVForColumnPiece(const int16_t col, Vec2i &topVector, Vec2i &bottomVector,
351 										  const uint16_t range, std::queue<SColumnPiece> &wrkQueue)
352 {
353 	enum { cTop = true, cBottom = false };
354 
355 	int16_t topRow    = CalcRow_ByVector(cTop, col, topVector);
356 	int16_t bottomRow = CalcRow_ByVector(cBottom, col, bottomVector);
357 
358 	enum { cInit, cYes, cNo } wasLastTileOpaque = cInit;
359 	for (int16_t row = topRow; row >= bottomRow; --row) {
360 		const bool inRange = square(col) + square(row) < square(range);
361 		const bool isOnMap = SetCurrentTile(col, row);
362 		if (inRange && isOnMap) {
363 			MarkTile();
364 		}
365 		const bool isTileOpaque = !inRange || !isOnMap || IsTileOpaque();
366 		if (wasLastTileOpaque != cInit) {
367 			if (isTileOpaque) {
368 				if (wasLastTileOpaque == cNo) {
369 					wrkQueue.push(SColumnPiece(col + 1, topVector, Vec2i(col * 2 - 1, row * 2 + 1)));
370 				}
371 			} else if (wasLastTileOpaque == cYes) {
372 				topVector = {int16_t(col * 2 + 1), int16_t(row * 2 + 1)};
373 			}
374 		}
375 		wasLastTileOpaque = isTileOpaque ? cYes : cNo;
376 	}
377 	if (wasLastTileOpaque == cNo) {
378 		wrkQueue.push(SColumnPiece(col + 1, topVector, bottomVector));
379 	}
380 }
381 
382 /**
383 **  Recalculate top or bottom direction vector
384 **
385 **  @param isTop	Flag to determine Top or Bottom direction vector is proceed
386 **	@param col		Current column
387 **	@param vector	Current direction vector
388 **	@return 		Row (Y-value) for new direction vector
389 */
CalcRow_ByVector(const bool isTop,const int16_t col,const Vec2i & vector) const390 int16_t CFieldOfView::CalcRow_ByVector(const bool isTop, const int16_t col, const Vec2i &vector) const
391 {
392 	int16_t row;
393 	if (col == 0) {
394 		row = 0;
395 	} else {
396 		// (col +|- 0.5) * (top|bot)_vector.y/(top|bot)_vector.x in integers
397 		const int16_t devidend  = (isTop ? (2 * col + 1) : (2 * col - 1)) * vector.y;
398 		const int16_t devisor   = 2 * vector.x;
399 		const int16_t quotient  = devidend / devisor;
400 		const int16_t remainder = devidend % devisor;
401 		// Round the result
402 		if (isTop ? remainder > vector.x
403 				  : remainder >= vector.x) {
404 			row = quotient + 1;
405 		} else {
406 			row = quotient;
407 		}
408 	}
409 	return row;
410 }
411 
PrepareShadowCaster(const CPlayer & player,const CUnit & unit,const Vec2i & pos,MapMarkerFunc * marker)412 void CFieldOfView::PrepareShadowCaster(const CPlayer &player, const CUnit &unit, const Vec2i &pos, MapMarkerFunc *marker)
413 {
414 	Player 		= &player;
415 	Unit 		= &unit;
416 	map_setFoV 	= marker;
417 }
418 
ResetShadowCaster()419 void CFieldOfView::ResetShadowCaster()
420 {
421 	Player 		= nullptr;
422 	Unit 		= nullptr;
423 	map_setFoV 	= nullptr;
424 	currTilePos = { 0, 0 };
425 
426 	ResetEnvironment();
427 }
428 
PrepareCache(const Vec2i pos,const uint16_t width,const uint16_t height,const uint16_t range)429 void CFieldOfView::PrepareCache(const Vec2i pos, const uint16_t width, const uint16_t height, const uint16_t range)
430 {
431 	/// Init cache table if it's uninitialized yet
432 	if (!MarkedTilesCache.size()) {
433 		MarkedTilesCache.resize(Map.Info.MapWidth * Map.Info.MapWidth);
434 		std::fill_n(MarkedTilesCache.begin(), MarkedTilesCache.size(), 0);
435 	}
436 	/// Clean cache for sight-sized frame
437 	Vec2i upperLeft;
438 	upperLeft.x = pos.x - range;
439 	upperLeft.y = pos.y - range;
440 	Map.Clamp(upperLeft);
441 
442 	Vec2i bottomRight;
443 	bottomRight.x =  pos.x + width + range;
444 	bottomRight.y =  pos.y + height + range;
445 	Map.Clamp(bottomRight);
446 
447 	const uint16_t sightRecWidth = bottomRight.x - upperLeft.x + 1;
448 
449 	size_t index = upperLeft.x + upperLeft.y * Map.Info.MapWidth;
450 
451 	for (uint16_t y = upperLeft.y; y <= bottomRight.y; y++ ) {
452 		std::fill_n(&MarkedTilesCache[index], sightRecWidth, 0);
453 		index += Map.Info.MapWidth;
454 	}
455 }
456 
457 /**
458 **  Update values of Octant and Origin for current working set
459 **
460 **  @param octant	Octant to calc for
461 **	@param origin	Tile position of the spectrator
462 */
SetEnvironment(const uint8_t octant,const Vec2i & origin)463 void CFieldOfView::SetEnvironment(const uint8_t octant, const Vec2i &origin)
464 {
465 	Origin  	= origin;
466 	currOctant 	= octant;
467 }
468 
ResetEnvironment()469 void CFieldOfView::ResetEnvironment()
470 {
471 	Origin 		= { 0, 0 };
472 	currOctant 	= 0;
473 }
474 
475 //@}
476