1 /* Copyright (C) 2018 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 #ifndef INCLUDED_ICMPOBSTRUCTIONMANAGER
19 #define INCLUDED_ICMPOBSTRUCTIONMANAGER
20 
21 #include "simulation2/system/Interface.h"
22 
23 #include "simulation2/helpers/Pathfinding.h"
24 
25 #include "maths/FixedVector2D.h"
26 
27 class IObstructionTestFilter;
28 
29 /**
30  * Obstruction manager: provides efficient spatial queries over objects in the world.
31  *
32  * The class deals with two types of shape:
33  * "static" shapes, typically representing buildings, which are rectangles with a given
34  * width and height and angle;
35  * and "unit" shapes, representing units that can move around the world, which have a
36  * radius and no rotation. (Units sometimes act as axis-aligned squares, sometimes
37  * as approximately circles, due to the algorithm used by the short pathfinder.)
38  *
39  * Other classes (particularly ICmpObstruction) register shapes with this interface
40  * and keep them updated.
41  *
42  * The @c Test functions provide exact collision tests.
43  * The edge of a shape counts as 'inside' the shape, for the purpose of collisions.
44  * The functions accept an IObstructionTestFilter argument, which can restrict the
45  * set of shapes that are counted as collisions.
46  *
47  * Units can be marked as either moving or stationary, which simply determines whether
48  * certain filters include or exclude them.
49  *
50  * The @c Rasterize function approximates the current set of shapes onto a 2D grid,
51  * for use with tile-based pathfinding.
52  */
53 class ICmpObstructionManager : public IComponent
54 {
55 public:
56 	/**
57 	 * External identifiers for shapes.
58 	 * (This is a struct rather than a raw u32 for type-safety.)
59 	 */
60 	struct tag_t
61 	{
tag_ttag_t62 		tag_t() : n(0) {}
tag_ttag_t63 		explicit tag_t(u32 n) : n(n) {}
validtag_t64 		bool valid() const { return n != 0; }
65 
66 		u32 n;
67 	};
68 
69 	/**
70 	 * Boolean flags affecting the obstruction behaviour of a shape.
71 	 */
72 	enum EFlags
73 	{
74 		FLAG_BLOCK_MOVEMENT           = (1 << 0), // prevents units moving through this shape
75 		FLAG_BLOCK_FOUNDATION         = (1 << 1), // prevents foundations being placed on this shape
76 		FLAG_BLOCK_CONSTRUCTION       = (1 << 2), // prevents buildings being constructed on this shape
77 		FLAG_BLOCK_PATHFINDING        = (1 << 3), // prevents the tile pathfinder choosing paths through this shape
78 		FLAG_MOVING                   = (1 << 4), // indicates this unit is currently moving
79 		FLAG_DELETE_UPON_CONSTRUCTION = (1 << 5)  // this entity is deleted when construction of a building placed on top of this entity starts
80 	};
81 
82 	/**
83 	 * Bitmask of EFlag values.
84 	 */
85 	typedef u8 flags_t;
86 
87 	/**
88 	 * Set the bounds of the world.
89 	 * Any point outside the bounds is considered obstructed.
90 	 * @param x0,z0,x1,z1 Coordinates of the corners of the world
91 	 */
92 	virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1) = 0;
93 
94 	/**
95 	 * Register a static shape.
96 	 *
97 	 * @param ent entity ID associated with this shape (or INVALID_ENTITY if none)
98 	 * @param x,z coordinates of center, in world space
99 	 * @param a angle of rotation (clockwise from +Z direction)
100 	 * @param w width (size along X axis)
101 	 * @param h height (size along Z axis)
102 	 * @param flags a set of EFlags values
103 	 * @param group primary control group of the shape. Must be a valid control group ID.
104 	 * @param group2 Optional; secondary control group of the shape. Defaults to INVALID_ENTITY.
105 	 * @return a valid tag for manipulating the shape
106 	 * @see StaticShape
107 	 */
108 	virtual tag_t AddStaticShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_angle_t a,
109 		entity_pos_t w, entity_pos_t h, flags_t flags, entity_id_t group, entity_id_t group2 = INVALID_ENTITY) = 0;
110 
111 	/**
112 	 * Register a unit shape.
113 	 *
114 	 * @param ent entity ID associated with this shape (or INVALID_ENTITY if none)
115 	 * @param x,z coordinates of center, in world space
116 	 * @param clearance pathfinding clearance of the unit (works as a radius)
117 	 * @param flags a set of EFlags values
118 	 * @param group control group (typically the owner entity, or a formation controller entity
119 	 *	- units ignore collisions with others in the same group)
120 	 * @return a valid tag for manipulating the shape
121 	 * @see UnitShape
122 	 */
123 	virtual tag_t AddUnitShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_pos_t clearance,
124 		flags_t flags, entity_id_t group) = 0;
125 
126 	/**
127 	 * Adjust the position and angle of an existing shape.
128 	 * @param tag tag of shape (must be valid)
129 	 * @param x X coordinate of center, in world space
130 	 * @param z Z coordinate of center, in world space
131 	 * @param a angle of rotation (clockwise from +Z direction); ignored for unit shapes
132 	 */
133 	virtual void MoveShape(tag_t tag, entity_pos_t x, entity_pos_t z, entity_angle_t a) = 0;
134 
135 	/**
136 	 * Set whether a unit shape is moving or stationary.
137 	 * @param tag tag of shape (must be valid and a unit shape)
138 	 * @param moving whether the unit is currently moving through the world or is stationary
139 	 */
140 	virtual void SetUnitMovingFlag(tag_t tag, bool moving) = 0;
141 
142 	/**
143 	 * Set the control group of a unit shape.
144 	 * @param tag tag of shape (must be valid and a unit shape)
145 	 * @param group control group entity ID
146 	 */
147 	virtual void SetUnitControlGroup(tag_t tag, entity_id_t group) = 0;
148 
149 	/**
150 	 * Sets the control group of a static shape.
151 	 * @param tag Tag of the shape to set the control group for. Must be a valid and static shape tag.
152 	 * @param group Control group entity ID.
153 	 */
154 	virtual void SetStaticControlGroup(tag_t tag, entity_id_t group, entity_id_t group2) = 0;
155 
156 	/**
157 	 * Remove an existing shape. The tag will be made invalid and must not be used after this.
158 	 * @param tag tag of shape (must be valid)
159 	 */
160 	virtual void RemoveShape(tag_t tag) = 0;
161 
162 	/**
163 	 * Returns the distance from the obstruction to the point (px, pz), or -1 if the entity is out of the world.
164 	 */
165 	virtual fixed DistanceToPoint(entity_id_t ent, entity_pos_t px, entity_pos_t pz) const = 0;
166 
167 	/**
168 	 * Collision test a flat-ended thick line against the current set of shapes.
169 	 * The line caps extend by @p r beyond the end points.
170 	 * Only intersections going from outside to inside a shape are counted.
171 	 * @param filter filter to restrict the shapes that are counted
172 	 * @param x0 X coordinate of line's first point
173 	 * @param z0 Z coordinate of line's first point
174 	 * @param x1 X coordinate of line's second point
175 	 * @param z1 Z coordinate of line's second point
176 	 * @param r radius (half width) of line
177 	 * @param relaxClearanceForUnits whether unit-unit collisions should be more permissive.
178 	 * @return true if there is a collision
179 	 */
180 	virtual bool TestLine(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, bool relaxClearanceForUnits) const = 0;
181 
182 	/**
183 	 * Collision test a static square shape against the current set of shapes.
184 	 * @param filter filter to restrict the shapes that are being tested against
185 	 * @param x X coordinate of center
186 	 * @param z Z coordinate of center
187 	 * @param a angle of rotation (clockwise from +Z direction)
188 	 * @param w width (size along X axis)
189 	 * @param h height (size along Z axis)
190 	 * @param out if non-NULL, all colliding shapes' entities will be added to this list
191 	 * @return true if there is a collision
192 	 */
193 	virtual bool TestStaticShape(const IObstructionTestFilter& filter,
194 		entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h,
195 		std::vector<entity_id_t>* out) const = 0;
196 
197 	/**
198 	 * Collision test a unit shape against the current set of registered shapes, and optionally writes a list of the colliding
199 	 * shapes' entities to an output list.
200 	 *
201 	 * @param filter filter to restrict the shapes that are being tested against
202 	 * @param x X coordinate of shape's center
203 	 * @param z Z coordinate of shape's center
204 	 * @param clearance clearance of the shape's unit
205 	 * @param out if non-NULL, all colliding shapes' entities will be added to this list
206 	 *
207 	 * @return true if there is a collision
208 	 */
209 	virtual bool TestUnitShape(const IObstructionTestFilter& filter,
210 		entity_pos_t x, entity_pos_t z, entity_pos_t clearance,
211 		std::vector<entity_id_t>* out) const = 0;
212 
213 	/**
214 	 * Convert the current set of shapes onto a navcell grid, for all passability classes contained in @p passClasses.
215 	 * If @p fullUpdate is false, the function will only go through dirty shapes.
216 	 * Shapes are expanded by the @p passClasses clearances, by ORing their masks onto the @p grid.
217 	 */
218 	virtual void Rasterize(Grid<NavcellData>& grid, const std::vector<PathfinderPassability>& passClasses, bool fullUpdate) = 0;
219 
220 	/**
221 	 * Gets dirtiness information and resets it afterwards. Then it's the role of CCmpPathfinder
222 	 * to pass the information to other components if needed. (AIs, etc.)
223 	 * The return value is false if an update is unnecessary.
224 	 */
225 	virtual void UpdateInformations(GridUpdateInformation& informations) = 0;
226 
227 	/**
228 	 * Standard representation for all types of shapes, for use with geometry processing code.
229 	 */
230 	struct ObstructionSquare
231 	{
232 		entity_pos_t x, z; // position of center
233 		CFixedVector2D u, v; // 'horizontal' and 'vertical' orthogonal unit vectors, representing orientation
234 		entity_pos_t hw, hh; // half width, half height of square
235 	};
236 
237 	/**
238 	 * Find all the obstructions that are inside (or partially inside) the given range.
239 	 * @param filter filter to restrict the shapes that are counted
240 	 * @param x0 X coordinate of left edge of range
241 	 * @param z0 Z coordinate of bottom edge of range
242 	 * @param x1 X coordinate of right edge of range
243 	 * @param z1 Z coordinate of top edge of range
244 	 * @param squares output list of obstructions
245 	 */
246 	virtual void GetObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector<ObstructionSquare>& squares) const = 0;
247 	virtual void GetStaticObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector<ObstructionSquare>& squares) const = 0;
248 	virtual void GetUnitObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector<ObstructionSquare>& squares) const = 0;
249 	virtual void GetStaticObstructionsOnObstruction(const ObstructionSquare& square, std::vector<entity_id_t>& out, const IObstructionTestFilter& filter) const = 0;
250 
251 	/**
252 	 * Returns the entity IDs of all unit shapes that intersect the given
253 	 * obstruction square, filtering out using the given filter.
254 	 * @param square the Obstruction squre we want to compare with.
255 	 * @param out output list of obstructions
256 	 * @param filter filter for the obstructing units
257 	 * @param strict whether to be strict in the check or more permissive (ie rasterize more or less). Default false.
258 	 */
259 	virtual void GetUnitsOnObstruction(const ObstructionSquare& square, std::vector<entity_id_t>& out, const IObstructionTestFilter& filter, bool strict = false) const = 0;
260 
261 	/**
262 	 * Get the obstruction square representing the given shape.
263 	 * @param tag tag of shape (must be valid)
264 	 */
265 	virtual ObstructionSquare GetObstruction(tag_t tag) const = 0;
266 
267 	virtual ObstructionSquare GetUnitShapeObstruction(entity_pos_t x, entity_pos_t z, entity_pos_t clearance) const = 0;
268 
269 	virtual ObstructionSquare GetStaticShapeObstruction(entity_pos_t x, entity_pos_t z, entity_angle_t a, entity_pos_t w, entity_pos_t h) const = 0;
270 
271 	/**
272 	 * Set the passability to be restricted to a circular map.
273 	 */
274 	virtual void SetPassabilityCircular(bool enabled) = 0;
275 
276 	virtual bool GetPassabilityCircular() const = 0;
277 
278 	/**
279 	 * Toggle the rendering of debug info.
280 	 */
281 	virtual void SetDebugOverlay(bool enabled) = 0;
282 
283 	DECLARE_INTERFACE_TYPE(ObstructionManager)
284 };
285 
286 /**
287  * Interface for ICmpObstructionManager @c Test functions to filter out unwanted shapes.
288  */
289 class IObstructionTestFilter
290 {
291 public:
292 	typedef ICmpObstructionManager::tag_t tag_t;
293 	typedef ICmpObstructionManager::flags_t flags_t;
294 
~IObstructionTestFilter()295 	virtual ~IObstructionTestFilter() {}
296 
297 	/**
298 	 * Return true if the shape with the specified parameters should be tested for collisions.
299 	 * This is called for all shapes that would collide, and also for some that wouldn't.
300 	 *
301 	 * @param tag tag of shape being tested
302 	 * @param flags set of EFlags for the shape
303 	 * @param group the control group of the shape (typically the shape's unit, or the unit's formation controller, or 0)
304 	 * @param group2 an optional secondary control group of the shape, or INVALID_ENTITY if none specified. Currently
305 	 *               exists only for static shapes.
306 	 */
307 	virtual bool TestShape(tag_t tag, flags_t flags, entity_id_t group, entity_id_t group2) const = 0;
308 };
309 
310 /**
311  * Obstruction test filter that will test against all shapes.
312  */
313 class NullObstructionFilter : public IObstructionTestFilter
314 {
315 public:
TestShape(tag_t UNUSED (tag),flags_t UNUSED (flags),entity_id_t UNUSED (group),entity_id_t UNUSED (group2))316 	virtual bool TestShape(tag_t UNUSED(tag), flags_t UNUSED(flags), entity_id_t UNUSED(group), entity_id_t UNUSED(group2)) const
317 	{
318 		return true;
319 	}
320 };
321 
322 /**
323  * Obstruction test filter that will test only against stationary (i.e. non-moving) shapes.
324  */
325 class StationaryOnlyObstructionFilter : public IObstructionTestFilter
326 {
327 public:
TestShape(tag_t UNUSED (tag),flags_t flags,entity_id_t UNUSED (group),entity_id_t UNUSED (group2))328 	virtual bool TestShape(tag_t UNUSED(tag), flags_t flags, entity_id_t UNUSED(group), entity_id_t UNUSED(group2)) const
329 	{
330 		return !(flags & ICmpObstructionManager::FLAG_MOVING);
331 	}
332 };
333 
334 /**
335  * Obstruction test filter that reject shapes in a given control group,
336  * and rejects shapes that don't block unit movement, and optionally rejects moving shapes.
337  */
338 class ControlGroupMovementObstructionFilter : public IObstructionTestFilter
339 {
340 	bool m_AvoidMoving;
341 	entity_id_t m_Group;
342 
343 public:
ControlGroupMovementObstructionFilter(bool avoidMoving,entity_id_t group)344 	ControlGroupMovementObstructionFilter(bool avoidMoving, entity_id_t group) :
345 		m_AvoidMoving(avoidMoving), m_Group(group)
346 	{}
347 
TestShape(tag_t UNUSED (tag),flags_t flags,entity_id_t group,entity_id_t group2)348 	virtual bool TestShape(tag_t UNUSED(tag), flags_t flags, entity_id_t group, entity_id_t group2) const
349 	{
350 		if (group == m_Group || (group2 != INVALID_ENTITY && group2 == m_Group))
351 			return false;
352 
353 		if (!(flags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT))
354 			return false;
355 
356 		if ((flags & ICmpObstructionManager::FLAG_MOVING) && !m_AvoidMoving)
357 			return false;
358 
359 		return true;
360 	}
361 };
362 
363 /**
364  * Obstruction test filter that will test only against shapes that:
365  *     - are part of neither one of the specified control groups
366  *     - AND, depending on the value of the 'exclude' argument:
367  *       - have at least one of the specified flags set.
368  *       - OR have none of the specified flags set.
369  *
370  * The first (primary) control group to reject shapes from must be specified and valid. The secondary
371  * control group to reject entities from may be set to INVALID_ENTITY to not use it.
372  *
373  * This filter is useful to e.g. allow foundations within the same control group to be placed and
374  * constructed arbitrarily close together (e.g. for wall pieces that need to link up tightly).
375  */
376 class SkipControlGroupsRequireFlagObstructionFilter : public IObstructionTestFilter
377 {
378 	bool m_Exclude;
379 	entity_id_t m_Group;
380 	entity_id_t m_Group2;
381 	flags_t m_Mask;
382 
383 public:
SkipControlGroupsRequireFlagObstructionFilter(bool exclude,entity_id_t group1,entity_id_t group2,flags_t mask)384 	SkipControlGroupsRequireFlagObstructionFilter(bool exclude, entity_id_t group1, entity_id_t group2, flags_t mask) :
385 		m_Exclude(exclude), m_Group(group1), m_Group2(group2), m_Mask(mask)
386 	{
387 		Init();
388 	}
389 
SkipControlGroupsRequireFlagObstructionFilter(entity_id_t group1,entity_id_t group2,flags_t mask)390 	SkipControlGroupsRequireFlagObstructionFilter(entity_id_t group1, entity_id_t group2, flags_t mask) :
391 		m_Exclude(false), m_Group(group1), m_Group2(group2), m_Mask(mask)
392 	{
393 		Init();
394 	}
395 
TestShape(tag_t UNUSED (tag),flags_t flags,entity_id_t group,entity_id_t group2)396 	virtual bool TestShape(tag_t UNUSED(tag), flags_t flags, entity_id_t group, entity_id_t group2) const
397 	{
398 		// Don't test shapes that share one or more of our control groups.
399 		if (group == m_Group || group == m_Group2 || (group2 != INVALID_ENTITY &&
400 				(group2 == m_Group || group2 == m_Group2)))
401 			return false;
402 
403 		// If m_Exclude is true, don't test against shapes that have any of the
404 		// obstruction flags specified in m_Mask.
405 		if (m_Exclude)
406 			return (flags & m_Mask) == 0;
407 
408 		// Otherwise, only include shapes that match at least one flag in m_Mask.
409 		return (flags & m_Mask) != 0;
410 	}
411 private:
Init()412 	void Init()
413 	{
414 		// the primary control group to filter out must be valid
415 		ENSURE(m_Group != INVALID_ENTITY);
416 
417 		// for simplicity, if m_Group2 is INVALID_ENTITY (i.e. not used), then set it equal to m_Group
418 		// so that we have fewer special cases to consider in TestShape().
419 		if (m_Group2 == INVALID_ENTITY)
420 			m_Group2 = m_Group;
421 	}
422 };
423 
424 /**
425  * Obstruction test filter that will test only against shapes that:
426  *     - are part of both of the specified control groups
427  *     - AND have at least one of the specified flags set.
428  *
429  * The first (primary) control group to include shapes from must be specified and valid.
430  *
431  * This filter is useful for preventing entities with identical control groups
432  * from colliding e.g. building a new wall segment on top of an existing wall)
433  *
434  * @todo This filter needs test cases.
435  */
436 class SkipTagRequireControlGroupsAndFlagObstructionFilter : public IObstructionTestFilter
437 {
438 	tag_t m_Tag;
439 	entity_id_t m_Group;
440 	entity_id_t m_Group2;
441 	flags_t m_Mask;
442 
443 public:
SkipTagRequireControlGroupsAndFlagObstructionFilter(tag_t tag,entity_id_t group1,entity_id_t group2,flags_t mask)444 	SkipTagRequireControlGroupsAndFlagObstructionFilter(tag_t tag, entity_id_t group1, entity_id_t group2, flags_t mask) :
445 		m_Tag(tag), m_Group(group1), m_Group2(group2), m_Mask(mask)
446 	{
447 		ENSURE(m_Group != INVALID_ENTITY);
448 	}
449 
TestShape(tag_t tag,flags_t flags,entity_id_t group,entity_id_t group2)450 	virtual bool TestShape(tag_t tag, flags_t flags, entity_id_t group, entity_id_t group2) const
451 	{
452 		// To be included in testing, a shape must not have the specified tag, and must
453 		// match at least one of the flags in m_Mask, as well as both control groups.
454 		return (tag.n != m_Tag.n && (flags & m_Mask) != 0 && ((group == m_Group
455 			&& group2 == m_Group2) || (group2 == m_Group && group == m_Group2)));
456 	}
457 };
458 
459 /**
460  * Obstruction test filter that will test only against shapes that do not have the specified tag set.
461  */
462 class SkipTagObstructionFilter : public IObstructionTestFilter
463 {
464 	tag_t m_Tag;
465 public:
SkipTagObstructionFilter(tag_t tag)466 	SkipTagObstructionFilter(tag_t tag) : m_Tag(tag)
467 	{
468 	}
469 
TestShape(tag_t tag,flags_t UNUSED (flags),entity_id_t UNUSED (group),entity_id_t UNUSED (group2))470 	virtual bool TestShape(tag_t tag, flags_t UNUSED(flags), entity_id_t UNUSED(group), entity_id_t UNUSED(group2)) const
471 	{
472 		return tag.n != m_Tag.n;
473 	}
474 };
475 
476 /**
477  * Obstruction test filter that will test only against shapes that:
478  *    - do not have the specified tag
479  *    - AND have at least one of the specified flags set.
480  */
481 class SkipTagRequireFlagsObstructionFilter : public IObstructionTestFilter
482 {
483 	tag_t m_Tag;
484 	flags_t m_Mask;
485 public:
SkipTagRequireFlagsObstructionFilter(tag_t tag,flags_t mask)486 	SkipTagRequireFlagsObstructionFilter(tag_t tag, flags_t mask) : m_Tag(tag), m_Mask(mask)
487 	{
488 	}
489 
TestShape(tag_t tag,flags_t flags,entity_id_t UNUSED (group),entity_id_t UNUSED (group2))490 	virtual bool TestShape(tag_t tag, flags_t flags, entity_id_t UNUSED(group), entity_id_t UNUSED(group2)) const
491 	{
492 		return (tag.n != m_Tag.n && (flags & m_Mask) != 0);
493 	}
494 };
495 
496 #endif // INCLUDED_ICMPOBSTRUCTIONMANAGER
497