| 1 | /* Copyright (C) 2023 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 "simulation2/system/ComponentTest.h"
|
|---|
| 19 |
|
|---|
| 20 | #include "simulation2/components/ICmpObstructionManager.h"
|
|---|
| 21 | #include "simulation2/components/ICmpObstruction.h"
|
|---|
| 22 |
|
|---|
| 23 | class MockObstruction : public ICmpObstruction
|
|---|
| 24 | {
|
|---|
| 25 | public:
|
|---|
| 26 | DEFAULT_MOCK_COMPONENT()
|
|---|
| 27 | ICmpObstructionManager::ObstructionSquare obstruction;
|
|---|
| 28 |
|
|---|
| 29 | ICmpObstructionManager::tag_t GetObstruction() const override { return ICmpObstructionManager::tag_t(); }
|
|---|
| 30 | bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare& out) const override { out = obstruction; return true; }
|
|---|
| 31 | bool GetPreviousObstructionSquare(ICmpObstructionManager::ObstructionSquare& UNUSED(out)) const override { return true; }
|
|---|
| 32 | entity_pos_t GetSize() const override { return entity_pos_t::Zero(); }
|
|---|
| 33 | CFixedVector2D GetStaticSize() const override { return CFixedVector2D(); }
|
|---|
| 34 | EObstructionType GetObstructionType() const override { return ICmpObstruction::STATIC; }
|
|---|
| 35 | void SetUnitClearance(const entity_pos_t& UNUSED(clearance)) override { }
|
|---|
| 36 | bool IsControlPersistent() const override { return true; }
|
|---|
| 37 | bool CheckShorePlacement() const override { return true; }
|
|---|
| 38 | EFoundationCheck CheckFoundation(const std::string& UNUSED(className)) const override { return EFoundationCheck(); }
|
|---|
| 39 | EFoundationCheck CheckFoundation(const std::string& UNUSED(className), bool UNUSED(onlyCenterPoint)) const override { return EFoundationCheck(); }
|
|---|
| 40 | std::string CheckFoundation_wrapper(const std::string& UNUSED(className), bool UNUSED(onlyCenterPoint)) const override { return std::string(); }
|
|---|
| 41 | bool CheckDuplicateFoundation() const override { return true; }
|
|---|
| 42 | std::vector<entity_id_t> GetEntitiesByFlags(ICmpObstructionManager::flags_t UNUSED(flags)) const override { return std::vector<entity_id_t>(); }
|
|---|
| 43 | std::vector<entity_id_t> GetEntitiesBlockingMovement() const override { return std::vector<entity_id_t>(); }
|
|---|
| 44 | std::vector<entity_id_t> GetEntitiesBlockingConstruction() const override { return std::vector<entity_id_t>(); }
|
|---|
| 45 | std::vector<entity_id_t> GetEntitiesDeletedUponConstruction() const override { return std::vector<entity_id_t>(); }
|
|---|
| 46 | void ResolveFoundationCollisions() const override { }
|
|---|
| 47 | void SetActive(bool UNUSED(active)) override { }
|
|---|
| 48 | void SetMovingFlag(bool UNUSED(enabled)) override { }
|
|---|
| 49 | void SetDisableBlockMovementPathfinding(bool UNUSED(movementDisabled), bool UNUSED(pathfindingDisabled), int32_t UNUSED(shape)) override { }
|
|---|
| 50 | bool GetBlockMovementFlag(bool) const override { return true; }
|
|---|
| 51 | void SetControlGroup(entity_id_t UNUSED(group)) override { }
|
|---|
| 52 | entity_id_t GetControlGroup() const override { return INVALID_ENTITY; }
|
|---|
| 53 | void SetControlGroup2(entity_id_t UNUSED(group2)) override { }
|
|---|
| 54 | entity_id_t GetControlGroup2() const override { return INVALID_ENTITY; }
|
|---|
| 55 | };
|
|---|
| 56 |
|
|---|
| 57 | class TestCmpObstructionManager : public CxxTest::TestSuite
|
|---|
| 58 | {
|
|---|
| 59 | typedef ICmpObstructionManager::tag_t tag_t;
|
|---|
| 60 | typedef ICmpObstructionManager::ObstructionSquare ObstructionSquare;
|
|---|
| 61 |
|
|---|
| 62 | // some variables for setting up a scene with 3 shapes
|
|---|
| 63 | entity_id_t ent1, ent2, ent3; // entity IDs
|
|---|
| 64 | entity_angle_t ent1a; // angles
|
|---|
| 65 | entity_pos_t ent1x, ent1z, ent1w, ent1h, // positions/dimensions
|
|---|
| 66 | ent2x, ent2z, ent2c,
|
|---|
| 67 | ent3x, ent3z, ent3c;
|
|---|
| 68 | entity_id_t ent1g1, ent1g2, ent2g, ent3g; // control groups
|
|---|
| 69 |
|
|---|
| 70 | tag_t shape1, shape2, shape3;
|
|---|
| 71 |
|
|---|
| 72 | ICmpObstructionManager* cmp;
|
|---|
| 73 | ComponentTestHelper* testHelper;
|
|---|
| 74 |
|
|---|
| 75 | public:
|
|---|
| 76 | void setUp()
|
|---|
| 77 | {
|
|---|
| 78 | CXeromyces::Startup();
|
|---|
| 79 | CxxTest::setAbortTestOnFail(true);
|
|---|
| 80 |
|
|---|
| 81 | // set up a simple scene with some predefined obstruction shapes
|
|---|
| 82 | // (we can't position shapes on the origin because the world bounds must range
|
|---|
| 83 | // from 0 to X, so instead we'll offset things by, say, 10).
|
|---|
| 84 |
|
|---|
| 85 | ent1 = 1;
|
|---|
| 86 | ent1a = fixed::Zero();
|
|---|
| 87 | ent1w = fixed::FromFloat(4);
|
|---|
| 88 | ent1h = fixed::FromFloat(2);
|
|---|
| 89 | ent1x = fixed::FromInt(10);
|
|---|
| 90 | ent1z = fixed::FromInt(10);
|
|---|
| 91 | ent1g1 = ent1;
|
|---|
| 92 | ent1g2 = INVALID_ENTITY;
|
|---|
| 93 |
|
|---|
| 94 | ent2 = 2;
|
|---|
| 95 | ent2c = fixed::FromFloat(1);
|
|---|
| 96 | ent2x = ent1x;
|
|---|
| 97 | ent2z = ent1z;
|
|---|
| 98 | ent2g = ent1g1;
|
|---|
| 99 |
|
|---|
| 100 | ent3 = 3;
|
|---|
| 101 | ent3c = fixed::FromFloat(3);
|
|---|
| 102 | ent3x = ent2x;
|
|---|
| 103 | ent3z = ent2z + ent2c + ent3c; // ensure it just touches the border of ent2
|
|---|
| 104 | ent3g = ent3;
|
|---|
| 105 |
|
|---|
| 106 | testHelper = new ComponentTestHelper(*g_ScriptContext);
|
|---|
| 107 | cmp = testHelper->Add<ICmpObstructionManager>(CID_ObstructionManager, "", SYSTEM_ENTITY);
|
|---|
| 108 | cmp->SetBounds(fixed::FromInt(0), fixed::FromInt(0), fixed::FromInt(1000), fixed::FromInt(1000));
|
|---|
| 109 |
|
|---|
| 110 | shape1 = cmp->AddStaticShape(ent1, ent1x, ent1z, ent1a, ent1w, ent1h,
|
|---|
| 111 | ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION |
|
|---|
| 112 | ICmpObstructionManager::FLAG_BLOCK_MOVEMENT |
|
|---|
| 113 | ICmpObstructionManager::FLAG_MOVING, ent1g1, ent1g2);
|
|---|
| 114 |
|
|---|
| 115 | shape2 = cmp->AddUnitShape(ent2, ent2x, ent2z, ent2c,
|
|---|
| 116 | ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION |
|
|---|
| 117 | ICmpObstructionManager::FLAG_BLOCK_FOUNDATION, ent2g);
|
|---|
| 118 |
|
|---|
| 119 | shape3 = cmp->AddUnitShape(ent3, ent3x, ent3z, ent3c,
|
|---|
| 120 | ICmpObstructionManager::FLAG_BLOCK_MOVEMENT |
|
|---|
| 121 | ICmpObstructionManager::FLAG_BLOCK_FOUNDATION, ent3g);
|
|---|
| 122 | }
|
|---|
| 123 |
|
|---|
| 124 | void tearDown()
|
|---|
| 125 | {
|
|---|
| 126 | delete testHelper;
|
|---|
| 127 | cmp = NULL; // not our responsibility to deallocate
|
|---|
| 128 |
|
|---|
| 129 | CXeromyces::Terminate();
|
|---|
| 130 | }
|
|---|
| 131 |
|
|---|
| 132 | /**
|
|---|
| 133 | * Verifies the collision testing procedure. Collision-tests some simple shapes against the shapes registered in
|
|---|
| 134 | * the scene, and verifies the result of the test against the expected value.
|
|---|
| 135 | */
|
|---|
| 136 | void test_simple_collisions()
|
|---|
| 137 | {
|
|---|
| 138 | std::vector<entity_id_t> out;
|
|---|
| 139 | NullObstructionFilter nullFilter;
|
|---|
| 140 |
|
|---|
| 141 | // Collision-test a simple shape nested inside shape3 against all shapes in the scene. Since the tested shape
|
|---|
| 142 | // overlaps only with shape 3, we should find only shape 3 in the result.
|
|---|
| 143 |
|
|---|
| 144 | cmp->TestUnitShape(nullFilter, ent3x, ent3z, fixed::FromInt(1), &out);
|
|---|
| 145 | TS_ASSERT_EQUALS(1U, out.size());
|
|---|
| 146 | TS_ASSERT_EQUALS(ent3, out[0]);
|
|---|
| 147 | out.clear();
|
|---|
| 148 |
|
|---|
| 149 | cmp->TestStaticShape(nullFilter, ent3x, ent3z, fixed::Zero(), fixed::FromInt(1), fixed::FromInt(1), &out);
|
|---|
| 150 | TS_ASSERT_EQUALS(1U, out.size());
|
|---|
| 151 | TS_ASSERT_EQUALS(ent3, out[0]);
|
|---|
| 152 | out.clear();
|
|---|
| 153 |
|
|---|
| 154 | // Similarly, collision-test a simple shape nested inside both shape1 and shape2. Since the tested shape overlaps
|
|---|
| 155 | // only with shapes 1 and 2, those are the only ones we should find in the result.
|
|---|
| 156 |
|
|---|
| 157 | cmp->TestUnitShape(nullFilter, ent2x, ent2z, ent2c/2, &out);
|
|---|
| 158 | TS_ASSERT_EQUALS(2U, out.size());
|
|---|
| 159 | TS_ASSERT_VECTOR_CONTAINS(out, ent1);
|
|---|
| 160 | TS_ASSERT_VECTOR_CONTAINS(out, ent2);
|
|---|
| 161 | out.clear();
|
|---|
| 162 |
|
|---|
| 163 | cmp->TestStaticShape(nullFilter, ent2x, ent2z, fixed::Zero(), ent2c, ent2c, &out);
|
|---|
| 164 | TS_ASSERT_EQUALS(2U, out.size());
|
|---|
| 165 | TS_ASSERT_VECTOR_CONTAINS(out, ent1);
|
|---|
| 166 | TS_ASSERT_VECTOR_CONTAINS(out, ent2);
|
|---|
| 167 | out.clear();
|
|---|
| 168 | }
|
|---|
| 169 |
|
|---|
| 170 | /**
|
|---|
| 171 | * Verifies the behaviour of the null obstruction filter. Tests with this filter will be performed against all
|
|---|
| 172 | * registered shapes.
|
|---|
| 173 | */
|
|---|
| 174 | void test_filter_null()
|
|---|
| 175 | {
|
|---|
| 176 | std::vector<entity_id_t> out;
|
|---|
| 177 |
|
|---|
| 178 | // Collision test a scene-covering shape against all shapes in the scene. We should find all registered shapes
|
|---|
| 179 | // in the result.
|
|---|
| 180 |
|
|---|
| 181 | NullObstructionFilter nullFilter;
|
|---|
| 182 |
|
|---|
| 183 | cmp->TestUnitShape(nullFilter, ent1x, ent1z, fixed::FromInt(10), &out);
|
|---|
| 184 | TS_ASSERT_EQUALS(3U, out.size());
|
|---|
| 185 | TS_ASSERT_VECTOR_CONTAINS(out, ent1);
|
|---|
| 186 | TS_ASSERT_VECTOR_CONTAINS(out, ent2);
|
|---|
| 187 | TS_ASSERT_VECTOR_CONTAINS(out, ent3);
|
|---|
| 188 | out.clear();
|
|---|
| 189 |
|
|---|
| 190 | cmp->TestStaticShape(nullFilter, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
|
|---|
| 191 | TS_ASSERT_EQUALS(3U, out.size());
|
|---|
| 192 | TS_ASSERT_VECTOR_CONTAINS(out, ent1);
|
|---|
| 193 | TS_ASSERT_VECTOR_CONTAINS(out, ent2);
|
|---|
| 194 | TS_ASSERT_VECTOR_CONTAINS(out, ent3);
|
|---|
| 195 | out.clear();
|
|---|
| 196 | }
|
|---|
| 197 |
|
|---|
| 198 | /**
|
|---|
| 199 | * Verifies the behaviour of the StationaryOnlyObstructionFilter. Tests with this filter will be performed only
|
|---|
| 200 | * against non-moving (stationary) shapes.
|
|---|
| 201 | */
|
|---|
| 202 | void test_filter_stationary_only()
|
|---|
| 203 | {
|
|---|
| 204 | std::vector<entity_id_t> out;
|
|---|
| 205 |
|
|---|
| 206 | // Collision test a scene-covering shape against all shapes in the scene, but skipping shapes that are moving,
|
|---|
| 207 | // i.e. shapes that have the MOVING flag. Since only shape 1 is flagged as moving, we should find
|
|---|
| 208 | // shapes 2 and 3 in each case.
|
|---|
| 209 |
|
|---|
| 210 | StationaryOnlyObstructionFilter ignoreMoving;
|
|---|
| 211 |
|
|---|
| 212 | cmp->TestUnitShape(ignoreMoving, ent1x, ent1z, fixed::FromInt(10), &out);
|
|---|
| 213 | TS_ASSERT_EQUALS(2U, out.size());
|
|---|
| 214 | TS_ASSERT_VECTOR_CONTAINS(out, ent2);
|
|---|
| 215 | TS_ASSERT_VECTOR_CONTAINS(out, ent3);
|
|---|
| 216 | out.clear();
|
|---|
| 217 |
|
|---|
| 218 | cmp->TestStaticShape(ignoreMoving, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
|
|---|
| 219 | TS_ASSERT_EQUALS(2U, out.size());
|
|---|
| 220 | TS_ASSERT_VECTOR_CONTAINS(out, ent2);
|
|---|
| 221 | TS_ASSERT_VECTOR_CONTAINS(out, ent3);
|
|---|
| 222 | out.clear();
|
|---|
| 223 | }
|
|---|
| 224 |
|
|---|
| 225 | /**
|
|---|
| 226 | * Verifies the behaviour of the SkipTagObstructionFilter. Tests with this filter will be performed against
|
|---|
| 227 | * all registered shapes that do not have the specified tag set.
|
|---|
| 228 | */
|
|---|
| 229 | void test_filter_skip_tag()
|
|---|
| 230 | {
|
|---|
| 231 | std::vector<entity_id_t> out;
|
|---|
| 232 |
|
|---|
| 233 | // Collision-test shape 2's obstruction shape against all shapes in the scene, but skipping tests against
|
|---|
| 234 | // shape 2. Since shape 2 overlaps only with shape 1, we should find only shape 1's entity ID in the result.
|
|---|
| 235 |
|
|---|
| 236 | SkipTagObstructionFilter ignoreShape2(shape2);
|
|---|
| 237 |
|
|---|
| 238 | cmp->TestUnitShape(ignoreShape2, ent2x, ent2z, ent2c/2, &out);
|
|---|
| 239 | TS_ASSERT_EQUALS(1U, out.size());
|
|---|
| 240 | TS_ASSERT_EQUALS(ent1, out[0]);
|
|---|
| 241 | out.clear();
|
|---|
| 242 |
|
|---|
| 243 | cmp->TestStaticShape(ignoreShape2, ent2x, ent2z, fixed::Zero(), ent2c, ent2c, &out);
|
|---|
| 244 | TS_ASSERT_EQUALS(1U, out.size());
|
|---|
| 245 | TS_ASSERT_EQUALS(ent1, out[0]);
|
|---|
| 246 | out.clear();
|
|---|
| 247 | }
|
|---|
| 248 |
|
|---|
| 249 | /**
|
|---|
| 250 | * Verifies the behaviour of the SkipTagFlagsObstructionFilter. Tests with this filter will be performed against
|
|---|
| 251 | * all registered shapes that do not have the specified tag set, and that have at least one of required flags set.
|
|---|
| 252 | */
|
|---|
| 253 | void test_filter_skip_tag_require_flag()
|
|---|
| 254 | {
|
|---|
| 255 | std::vector<entity_id_t> out;
|
|---|
| 256 |
|
|---|
| 257 | // Collision-test a scene-covering shape against all shapes in the scene, but skipping tests against shape 1
|
|---|
| 258 | // and requiring the BLOCK_MOVEMENT flag. Since shape 1 is being ignored and shape 2 does not have the required
|
|---|
| 259 | // flag, we should find only shape 3 in the results.
|
|---|
| 260 |
|
|---|
| 261 | SkipTagRequireFlagsObstructionFilter skipShape1RequireBlockMovement(shape1, ICmpObstructionManager::FLAG_BLOCK_MOVEMENT);
|
|---|
| 262 |
|
|---|
| 263 | cmp->TestUnitShape(skipShape1RequireBlockMovement, ent1x, ent1z, fixed::FromInt(10), &out);
|
|---|
| 264 | TS_ASSERT_EQUALS(1U, out.size());
|
|---|
| 265 | TS_ASSERT_EQUALS(ent3, out[0]);
|
|---|
| 266 | out.clear();
|
|---|
| 267 |
|
|---|
| 268 | cmp->TestStaticShape(skipShape1RequireBlockMovement, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
|
|---|
| 269 | TS_ASSERT_EQUALS(1U, out.size());
|
|---|
| 270 | TS_ASSERT_EQUALS(ent3, out[0]);
|
|---|
| 271 | out.clear();
|
|---|
| 272 |
|
|---|
| 273 | // If we now do the same test, but require at least one of the entire set of available filters, we should find
|
|---|
| 274 | // all shapes that are not shape 1 and that have at least one flag set. Since all shapes in our testing scene
|
|---|
| 275 | // have at least one flag set, we should find shape 2 and shape 3 in the results.
|
|---|
| 276 |
|
|---|
| 277 | SkipTagRequireFlagsObstructionFilter skipShape1RequireAnyFlag(shape1, (ICmpObstructionManager::flags_t) -1);
|
|---|
| 278 |
|
|---|
| 279 | cmp->TestUnitShape(skipShape1RequireAnyFlag, ent1x, ent1z, fixed::FromInt(10), &out);
|
|---|
| 280 | TS_ASSERT_EQUALS(2U, out.size());
|
|---|
| 281 | TS_ASSERT_VECTOR_CONTAINS(out, ent2);
|
|---|
| 282 | TS_ASSERT_VECTOR_CONTAINS(out, ent3);
|
|---|
| 283 | out.clear();
|
|---|
| 284 |
|
|---|
| 285 | cmp->TestStaticShape(skipShape1RequireAnyFlag, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
|
|---|
| 286 | TS_ASSERT_EQUALS(2U, out.size());
|
|---|
| 287 | TS_ASSERT_VECTOR_CONTAINS(out, ent2);
|
|---|
| 288 | TS_ASSERT_VECTOR_CONTAINS(out, ent3);
|
|---|
| 289 | out.clear();
|
|---|
| 290 |
|
|---|
| 291 | // And if we now do the same test yet again, but specify an empty set of flags, then it becomes impossible for
|
|---|
| 292 | // any shape to have at least one of the required flags, and we should hence find no shapes in the result.
|
|---|
| 293 |
|
|---|
| 294 | SkipTagRequireFlagsObstructionFilter skipShape1RejectAll(shape1, 0U);
|
|---|
| 295 |
|
|---|
| 296 | cmp->TestUnitShape(skipShape1RejectAll, ent1x, ent1z, fixed::FromInt(10), &out);
|
|---|
| 297 | TS_ASSERT_EQUALS(0U, out.size());
|
|---|
| 298 | out.clear();
|
|---|
| 299 |
|
|---|
| 300 | cmp->TestStaticShape(skipShape1RejectAll, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
|
|---|
| 301 | TS_ASSERT_EQUALS(0U, out.size());
|
|---|
| 302 | out.clear();
|
|---|
| 303 | }
|
|---|
| 304 |
|
|---|
| 305 | /**
|
|---|
| 306 | * Verifies the behaviour of SkipControlGroupsRequireFlagObstructionFilter. Tests with this filter will be performed
|
|---|
| 307 | * against all registered shapes that are members of neither specified control groups, and that have at least one of
|
|---|
| 308 | * the specified flags set.
|
|---|
| 309 | */
|
|---|
| 310 | void test_filter_skip_controlgroups_require_flag()
|
|---|
| 311 | {
|
|---|
| 312 | std::vector<entity_id_t> out;
|
|---|
| 313 |
|
|---|
| 314 | // Collision-test a shape that overlaps the entire scene, but ignoring shapes from shape1's control group
|
|---|
| 315 | // (which also includes shape 2), and requiring that either the BLOCK_FOUNDATION or the
|
|---|
| 316 | // BLOCK_CONSTRUCTION flag is set, or both. Since shape 1 and shape 2 both belong to shape 1's control
|
|---|
| 317 | // group, and shape 3 has the BLOCK_FOUNDATION flag (but not BLOCK_CONSTRUCTION), we should find only
|
|---|
| 318 | // shape 3 in the result.
|
|---|
| 319 |
|
|---|
| 320 | SkipControlGroupsRequireFlagObstructionFilter skipGroup1ReqFoundConstr(ent1g1, INVALID_ENTITY,
|
|---|
| 321 | ICmpObstructionManager::FLAG_BLOCK_FOUNDATION | ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION);
|
|---|
| 322 |
|
|---|
| 323 | cmp->TestUnitShape(skipGroup1ReqFoundConstr, ent1x, ent1z, fixed::FromInt(10), &out);
|
|---|
| 324 | TS_ASSERT_EQUALS(1U, out.size());
|
|---|
| 325 | TS_ASSERT_EQUALS(ent3, out[0]);
|
|---|
| 326 | out.clear();
|
|---|
| 327 |
|
|---|
| 328 | cmp->TestStaticShape(skipGroup1ReqFoundConstr, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
|
|---|
| 329 | TS_ASSERT_EQUALS(1U, out.size());
|
|---|
| 330 | TS_ASSERT_EQUALS(ent3, out[0]);
|
|---|
| 331 | out.clear();
|
|---|
| 332 |
|
|---|
| 333 | // Perform the same test, but now also exclude shape 3's control group (in addition to shape 1's control
|
|---|
| 334 | // group). Despite shape 3 having at least one of the required flags set, it should now also be ignored,
|
|---|
| 335 | // yielding an empty result set.
|
|---|
| 336 |
|
|---|
| 337 | SkipControlGroupsRequireFlagObstructionFilter skipGroup1And3ReqFoundConstr(ent1g1, ent3g,
|
|---|
| 338 | ICmpObstructionManager::FLAG_BLOCK_FOUNDATION | ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION);
|
|---|
| 339 |
|
|---|
| 340 | cmp->TestUnitShape(skipGroup1And3ReqFoundConstr, ent1x, ent1z, fixed::FromInt(10), &out);
|
|---|
| 341 | TS_ASSERT_EQUALS(0U, out.size());
|
|---|
| 342 | out.clear();
|
|---|
| 343 |
|
|---|
| 344 | cmp->TestStaticShape(skipGroup1And3ReqFoundConstr, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
|
|---|
| 345 | TS_ASSERT_EQUALS(0U, out.size());
|
|---|
| 346 | out.clear();
|
|---|
| 347 |
|
|---|
| 348 | // Same test, but this time excluding only shape 3's control group, and requiring any of the available flags
|
|---|
| 349 | // to be set. Since both shape 1 and shape 2 have at least one flag set and are both in a different control
|
|---|
| 350 | // group, we should find them in the result.
|
|---|
| 351 |
|
|---|
| 352 | SkipControlGroupsRequireFlagObstructionFilter skipGroup3RequireAnyFlag(ent3g, INVALID_ENTITY,
|
|---|
| 353 | (ICmpObstructionManager::flags_t) -1);
|
|---|
| 354 |
|
|---|
| 355 | cmp->TestUnitShape(skipGroup3RequireAnyFlag, ent1x, ent1z, fixed::FromInt(10), &out);
|
|---|
| 356 | TS_ASSERT_EQUALS(2U, out.size());
|
|---|
| 357 | TS_ASSERT_VECTOR_CONTAINS(out, ent1);
|
|---|
| 358 | TS_ASSERT_VECTOR_CONTAINS(out, ent2);
|
|---|
| 359 | out.clear();
|
|---|
| 360 |
|
|---|
| 361 | cmp->TestStaticShape(skipGroup3RequireAnyFlag, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
|
|---|
| 362 | TS_ASSERT_EQUALS(2U, out.size());
|
|---|
| 363 | TS_ASSERT_VECTOR_CONTAINS(out, ent1);
|
|---|
| 364 | TS_ASSERT_VECTOR_CONTAINS(out, ent2);
|
|---|
| 365 | out.clear();
|
|---|
| 366 |
|
|---|
| 367 | // Finally, the same test as the one directly above, now with an empty set of required flags. Since it now becomes
|
|---|
| 368 | // impossible for shape 1 and shape 2 to have at least one of the required flags set, and shape 3 is excluded by
|
|---|
| 369 | // virtue of the control group filtering, we should find an empty result.
|
|---|
| 370 |
|
|---|
| 371 | SkipControlGroupsRequireFlagObstructionFilter skipGroup3RequireNoFlags(ent3g, INVALID_ENTITY, 0U);
|
|---|
| 372 |
|
|---|
| 373 | cmp->TestUnitShape(skipGroup3RequireNoFlags, ent1x, ent1z, fixed::FromInt(10), &out);
|
|---|
| 374 | TS_ASSERT_EQUALS(0U, out.size());
|
|---|
| 375 | out.clear();
|
|---|
| 376 |
|
|---|
| 377 | cmp->TestStaticShape(skipGroup3RequireNoFlags, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
|
|---|
| 378 | TS_ASSERT_EQUALS(0U, out.size());
|
|---|
| 379 | out.clear();
|
|---|
| 380 |
|
|---|
| 381 | // ------------------------------------------------------------------------------------
|
|---|
| 382 |
|
|---|
| 383 | // In the tests up until this point, the shapes have all been filtered out based on their primary control group.
|
|---|
| 384 | // Now, to verify that shapes are also filtered out based on their secondary control groups, add a fourth shape
|
|---|
| 385 | // with arbitrarily-chosen dual control groups, and also change shape 1's secondary control group to another
|
|---|
| 386 | // arbitrarily-chosen control group. Then, do a scene-covering collision test while filtering out a combination
|
|---|
| 387 | // of shape 1's secondary control group, and one of shape 4's control groups. We should find neither ent1 nor ent4
|
|---|
| 388 | // in the result.
|
|---|
| 389 |
|
|---|
| 390 | entity_id_t ent4 = 4,
|
|---|
| 391 | ent4g1 = 17,
|
|---|
| 392 | ent4g2 = 19,
|
|---|
| 393 | ent1g2_new = 18; // new secondary control group for entity 1
|
|---|
| 394 | entity_pos_t ent4x = fixed::FromInt(4),
|
|---|
| 395 | ent4z = fixed::Zero(),
|
|---|
| 396 | ent4w = fixed::FromInt(1),
|
|---|
| 397 | ent4h = fixed::FromInt(1);
|
|---|
| 398 | entity_angle_t ent4a = fixed::FromDouble(M_PI/3);
|
|---|
| 399 |
|
|---|
| 400 | cmp->AddStaticShape(ent4, ent4x, ent4z, ent4a, ent4w, ent4h, ICmpObstructionManager::FLAG_BLOCK_PATHFINDING, ent4g1, ent4g2);
|
|---|
| 401 | cmp->SetStaticControlGroup(shape1, ent1g1, ent1g2_new);
|
|---|
| 402 |
|
|---|
| 403 | // Exclude shape 1's and shape 4's secondary control groups from testing, and require any available flag to be set.
|
|---|
| 404 | // Since neither shape 2 nor shape 3 are part of those control groups and both have at least one available flag set,
|
|---|
| 405 | // the results should only those two shapes' entities.
|
|---|
| 406 |
|
|---|
| 407 | SkipControlGroupsRequireFlagObstructionFilter skipGroup1SecAnd4SecRequireAny(ent1g2_new, ent4g2,
|
|---|
| 408 | (ICmpObstructionManager::flags_t) -1);
|
|---|
| 409 |
|
|---|
| 410 | cmp->TestUnitShape(skipGroup1SecAnd4SecRequireAny, ent1x, ent1z, fixed::FromInt(10), &out);
|
|---|
| 411 | TS_ASSERT_EQUALS(2U, out.size());
|
|---|
| 412 | TS_ASSERT_VECTOR_CONTAINS(out, ent2);
|
|---|
| 413 | TS_ASSERT_VECTOR_CONTAINS(out, ent3);
|
|---|
| 414 | out.clear();
|
|---|
| 415 |
|
|---|
| 416 | cmp->TestStaticShape(skipGroup1SecAnd4SecRequireAny, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
|
|---|
| 417 | TS_ASSERT_EQUALS(2U, out.size());
|
|---|
| 418 | TS_ASSERT_VECTOR_CONTAINS(out, ent2);
|
|---|
| 419 | TS_ASSERT_VECTOR_CONTAINS(out, ent3);
|
|---|
| 420 | out.clear();
|
|---|
| 421 |
|
|---|
| 422 | // Same as the above, but now exclude shape 1's secondary and shape 4's primary control group, while still requiring
|
|---|
| 423 | // any available flag to be set. (Note that the test above used shape 4's secondary control group). Results should
|
|---|
| 424 | // remain the same.
|
|---|
| 425 |
|
|---|
| 426 | SkipControlGroupsRequireFlagObstructionFilter skipGroup1SecAnd4PrimRequireAny(ent1g2_new, ent4g1,
|
|---|
| 427 | (ICmpObstructionManager::flags_t) -1);
|
|---|
| 428 |
|
|---|
| 429 | cmp->TestUnitShape(skipGroup1SecAnd4PrimRequireAny, ent1x, ent1z, fixed::FromInt(10), &out);
|
|---|
| 430 | TS_ASSERT_EQUALS(2U, out.size());
|
|---|
| 431 | TS_ASSERT_VECTOR_CONTAINS(out, ent2);
|
|---|
| 432 | TS_ASSERT_VECTOR_CONTAINS(out, ent3);
|
|---|
| 433 | out.clear();
|
|---|
| 434 |
|
|---|
| 435 | cmp->TestStaticShape(skipGroup1SecAnd4PrimRequireAny, ent1x, ent1z, fixed::Zero(), fixed::FromInt(10), fixed::FromInt(10), &out);
|
|---|
| 436 | TS_ASSERT_EQUALS(2U, out.size());
|
|---|
| 437 | TS_ASSERT_VECTOR_CONTAINS(out, ent2);
|
|---|
| 438 | TS_ASSERT_VECTOR_CONTAINS(out, ent3);
|
|---|
| 439 | out.clear();
|
|---|
| 440 |
|
|---|
| 441 | cmp->SetStaticControlGroup(shape1, ent1g1, ent1g2); // restore shape 1's original secondary control group
|
|---|
| 442 | }
|
|---|
| 443 |
|
|---|
| 444 | void test_adjacent_shapes()
|
|---|
| 445 | {
|
|---|
| 446 | std::vector<entity_id_t> out;
|
|---|
| 447 | NullObstructionFilter nullFilter;
|
|---|
| 448 | SkipTagObstructionFilter ignoreShape1(shape1);
|
|---|
| 449 | SkipTagObstructionFilter ignoreShape2(shape2);
|
|---|
| 450 | SkipTagObstructionFilter ignoreShape3(shape3);
|
|---|
| 451 |
|
|---|
| 452 | // Collision-test a shape that is perfectly adjacent to shape3. This should be counted as a hit according to
|
|---|
| 453 | // the code at the time of writing.
|
|---|
| 454 |
|
|---|
| 455 | entity_angle_t ent4a = fixed::FromDouble(M_PI); // rotated 180 degrees, should not affect collision test
|
|---|
| 456 | entity_pos_t ent4w = fixed::FromInt(2),
|
|---|
| 457 | ent4h = fixed::FromInt(1),
|
|---|
| 458 | ent4x = ent3x + ent3c + ent4w/2, // make ent4 adjacent to ent3
|
|---|
| 459 | ent4z = ent3z;
|
|---|
| 460 |
|
|---|
| 461 | cmp->TestStaticShape(nullFilter, ent4x, ent4z, ent4a, ent4w, ent4h, &out);
|
|---|
| 462 | TS_ASSERT_EQUALS(1U, out.size());
|
|---|
| 463 | TS_ASSERT_EQUALS(ent3, out[0]);
|
|---|
| 464 | out.clear();
|
|---|
| 465 |
|
|---|
| 466 | cmp->TestUnitShape(nullFilter, ent4x, ent4z, ent4w/2, &out);
|
|---|
| 467 | TS_ASSERT_EQUALS(1U, out.size());
|
|---|
| 468 | TS_ASSERT_EQUALS(ent3, out[0]);
|
|---|
| 469 | out.clear();
|
|---|
| 470 |
|
|---|
| 471 | // now do the same tests, but move the shape a little bit to the right so that it doesn't touch anymore
|
|---|
| 472 |
|
|---|
| 473 | cmp->TestStaticShape(nullFilter, ent4x + fixed::FromFloat(1e-5f), ent4z, ent4a, ent4w, ent4h, &out);
|
|---|
| 474 | TS_ASSERT_EQUALS(0U, out.size());
|
|---|
| 475 | out.clear();
|
|---|
| 476 |
|
|---|
| 477 | cmp->TestUnitShape(nullFilter, ent4x + fixed::FromFloat(1e-5f), ent4z, ent4w/2, &out);
|
|---|
| 478 | TS_ASSERT_EQUALS(0U, out.size());
|
|---|
| 479 | out.clear();
|
|---|
| 480 | }
|
|---|
| 481 |
|
|---|
| 482 | /**
|
|---|
| 483 | * Verifies that fetching the registered shapes from the obstruction manager yields the correct results.
|
|---|
| 484 | */
|
|---|
| 485 | void test_get_obstruction()
|
|---|
| 486 | {
|
|---|
| 487 | ObstructionSquare obSquare1 = cmp->GetObstruction(shape1);
|
|---|
| 488 | ObstructionSquare obSquare2 = cmp->GetObstruction(shape2);
|
|---|
| 489 | ObstructionSquare obSquare3 = cmp->GetObstruction(shape3);
|
|---|
| 490 |
|
|---|
| 491 | TS_ASSERT_EQUALS(obSquare1.hh, ent1h/2);
|
|---|
| 492 | TS_ASSERT_EQUALS(obSquare1.hw, ent1w/2);
|
|---|
| 493 | TS_ASSERT_EQUALS(obSquare1.x, ent1x);
|
|---|
| 494 | TS_ASSERT_EQUALS(obSquare1.z, ent1z);
|
|---|
| 495 | TS_ASSERT_EQUALS(obSquare1.u, CFixedVector2D(fixed::FromInt(1), fixed::FromInt(0)));
|
|---|
| 496 | TS_ASSERT_EQUALS(obSquare1.v, CFixedVector2D(fixed::FromInt(0), fixed::FromInt(1)));
|
|---|
| 497 |
|
|---|
| 498 | TS_ASSERT_EQUALS(obSquare2.hh, ent2c);
|
|---|
| 499 | TS_ASSERT_EQUALS(obSquare2.hw, ent2c);
|
|---|
| 500 | TS_ASSERT_EQUALS(obSquare2.x, ent2x);
|
|---|
| 501 | TS_ASSERT_EQUALS(obSquare2.z, ent2z);
|
|---|
| 502 | TS_ASSERT_EQUALS(obSquare2.u, CFixedVector2D(fixed::FromInt(1), fixed::FromInt(0)));
|
|---|
| 503 | TS_ASSERT_EQUALS(obSquare2.v, CFixedVector2D(fixed::FromInt(0), fixed::FromInt(1)));
|
|---|
| 504 |
|
|---|
| 505 | TS_ASSERT_EQUALS(obSquare3.hh, ent3c);
|
|---|
| 506 | TS_ASSERT_EQUALS(obSquare3.hw, ent3c);
|
|---|
| 507 | TS_ASSERT_EQUALS(obSquare3.x, ent3x);
|
|---|
| 508 | TS_ASSERT_EQUALS(obSquare3.z, ent3z);
|
|---|
| 509 | TS_ASSERT_EQUALS(obSquare3.u, CFixedVector2D(fixed::FromInt(1), fixed::FromInt(0)));
|
|---|
| 510 | TS_ASSERT_EQUALS(obSquare3.v, CFixedVector2D(fixed::FromInt(0), fixed::FromInt(1)));
|
|---|
| 511 | }
|
|---|
| 512 |
|
|---|
| 513 | /**
|
|---|
| 514 | * Verifies the calculations of distances between shapes.
|
|---|
| 515 | */
|
|---|
| 516 | void test_distance_to()
|
|---|
| 517 | {
|
|---|
| 518 | // Create two more entities to have non-zero distances
|
|---|
| 519 | entity_id_t ent4 = 4,
|
|---|
| 520 | ent4g1 = ent4,
|
|---|
| 521 | ent4g2 = INVALID_ENTITY,
|
|---|
| 522 | ent5 = 5,
|
|---|
| 523 | ent5g1 = ent5,
|
|---|
| 524 | ent5g2 = INVALID_ENTITY;
|
|---|
| 525 |
|
|---|
| 526 | entity_pos_t ent4a = fixed::Zero(),
|
|---|
| 527 | ent4w = fixed::FromInt(6),
|
|---|
| 528 | ent4h = fixed::Zero(),
|
|---|
| 529 | ent4x = ent1x,
|
|---|
| 530 | ent4z = fixed::FromInt(20),
|
|---|
| 531 | ent5a = fixed::Zero(),
|
|---|
| 532 | ent5w = fixed::FromInt(2),
|
|---|
| 533 | ent5h = fixed::FromInt(4),
|
|---|
| 534 | ent5x = fixed::FromInt(20),
|
|---|
| 535 | ent5z = ent1z;
|
|---|
| 536 |
|
|---|
| 537 | tag_t shape4 = cmp->AddStaticShape(ent4, ent4x, ent4z, ent4a, ent4w, ent4h,
|
|---|
| 538 | ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION |
|
|---|
| 539 | ICmpObstructionManager::FLAG_BLOCK_MOVEMENT |
|
|---|
| 540 | ICmpObstructionManager::FLAG_MOVING, ent4g1, ent4g2);
|
|---|
| 541 |
|
|---|
| 542 | tag_t shape5 = cmp->AddStaticShape(ent5, ent5x, ent5z, ent5a, ent5w, ent5h,
|
|---|
| 543 | ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION |
|
|---|
| 544 | ICmpObstructionManager::FLAG_BLOCK_MOVEMENT |
|
|---|
| 545 | ICmpObstructionManager::FLAG_MOVING, ent5g1, ent5g2);
|
|---|
| 546 |
|
|---|
| 547 | MockObstruction obstruction1, obstruction2, obstruction3, obstruction4, obstruction5;
|
|---|
| 548 | testHelper->AddMock(ent1, IID_Obstruction, obstruction1);
|
|---|
| 549 | testHelper->AddMock(ent2, IID_Obstruction, obstruction2);
|
|---|
| 550 | testHelper->AddMock(ent3, IID_Obstruction, obstruction3);
|
|---|
| 551 | testHelper->AddMock(ent4, IID_Obstruction, obstruction4);
|
|---|
| 552 | testHelper->AddMock(ent5, IID_Obstruction, obstruction5);
|
|---|
| 553 | obstruction1.obstruction = cmp->GetObstruction(shape1);
|
|---|
| 554 | obstruction2.obstruction = cmp->GetObstruction(shape2);
|
|---|
| 555 | obstruction3.obstruction = cmp->GetObstruction(shape3);
|
|---|
| 556 | obstruction4.obstruction = cmp->GetObstruction(shape4);
|
|---|
| 557 | obstruction5.obstruction = cmp->GetObstruction(shape5);
|
|---|
| 558 |
|
|---|
| 559 | TS_ASSERT_EQUALS(fixed::Zero(), cmp->DistanceToTarget(ent1, ent2));
|
|---|
| 560 | TS_ASSERT_EQUALS(fixed::Zero(), cmp->DistanceToTarget(ent2, ent1));
|
|---|
| 561 | TS_ASSERT_EQUALS(fixed::Zero(), cmp->DistanceToTarget(ent2, ent3));
|
|---|
| 562 | TS_ASSERT_EQUALS(fixed::Zero(), cmp->DistanceToTarget(ent3, ent2));
|
|---|
| 563 |
|
|---|
| 564 | // Due to rounding errors we need to use some leeway
|
|---|
| 565 | TS_ASSERT_DELTA(fixed::FromFloat(std::sqrt(80)), cmp->MaxDistanceToTarget(ent2, ent3), fixed::FromFloat(0.0001f));
|
|---|
| 566 | TS_ASSERT_DELTA(fixed::FromFloat(std::sqrt(80)), cmp->MaxDistanceToTarget(ent3, ent2), fixed::FromFloat(0.0001f));
|
|---|
| 567 |
|
|---|
| 568 | TS_ASSERT_EQUALS(fixed::Zero(), cmp->DistanceToTarget(ent1, ent3));
|
|---|
| 569 | TS_ASSERT_EQUALS(fixed::Zero(), cmp->DistanceToTarget(ent3, ent1));
|
|---|
| 570 |
|
|---|
| 571 | TS_ASSERT_EQUALS(fixed::FromInt(6), cmp->DistanceToTarget(ent1, ent4));
|
|---|
| 572 | TS_ASSERT_EQUALS(fixed::FromInt(6), cmp->DistanceToTarget(ent4, ent1));
|
|---|
| 573 | TS_ASSERT_DELTA(fixed::FromFloat(std::sqrt(125) + 3), cmp->MaxDistanceToTarget(ent1, ent4), fixed::FromFloat(0.0001f));
|
|---|
| 574 | TS_ASSERT_DELTA(fixed::FromFloat(std::sqrt(125) + 3), cmp->MaxDistanceToTarget(ent4, ent1), fixed::FromFloat(0.0001f));
|
|---|
| 575 |
|
|---|
| 576 | TS_ASSERT_EQUALS(fixed::FromInt(7), cmp->DistanceToTarget(ent1, ent5));
|
|---|
| 577 | TS_ASSERT_EQUALS(fixed::FromInt(7), cmp->DistanceToTarget(ent5, ent1));
|
|---|
| 578 | TS_ASSERT_DELTA(fixed::FromFloat(std::sqrt(178)), cmp->MaxDistanceToTarget(ent1, ent5), fixed::FromFloat(0.0001f));
|
|---|
| 579 | TS_ASSERT_DELTA(fixed::FromFloat(std::sqrt(178)), cmp->MaxDistanceToTarget(ent5, ent1), fixed::FromFloat(0.0001f));
|
|---|
| 580 |
|
|---|
| 581 | TS_ASSERT(cmp->IsInTargetRange(ent1, ent2, fixed::Zero(), fixed::FromInt(1), true));
|
|---|
| 582 | TS_ASSERT(cmp->IsInTargetRange(ent1, ent2, fixed::Zero(), fixed::FromInt(1), false));
|
|---|
| 583 | TS_ASSERT(cmp->IsInTargetRange(ent1, ent2, fixed::FromInt(1), fixed::FromInt(1), true));
|
|---|
| 584 | TS_ASSERT(!cmp->IsInTargetRange(ent1, ent2, fixed::FromInt(1), fixed::FromInt(1), false));
|
|---|
| 585 |
|
|---|
| 586 | TS_ASSERT(cmp->IsInTargetRange(ent1, ent5, fixed::Zero(), fixed::FromInt(10), true));
|
|---|
| 587 | TS_ASSERT(cmp->IsInTargetRange(ent1, ent5, fixed::Zero(), fixed::FromInt(10), false));
|
|---|
| 588 | TS_ASSERT(cmp->IsInTargetRange(ent1, ent5, fixed::FromInt(1), fixed::FromInt(10), true));
|
|---|
| 589 | TS_ASSERT(!cmp->IsInTargetRange(ent1, ent5, fixed::FromInt(1), fixed::FromInt(5), false));
|
|---|
| 590 | TS_ASSERT(!cmp->IsInTargetRange(ent1, ent5, fixed::FromInt(10), fixed::FromInt(10), false));
|
|---|
| 591 | TS_ASSERT(cmp->IsInTargetRange(ent1, ent5, fixed::FromInt(10), fixed::FromInt(10), true));
|
|---|
| 592 | }
|
|---|
| 593 | };
|
|---|