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