summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
authorKelly Rauchenberger <fefferburbia@gmail.com>2018-05-08 18:38:28 -0400
committerKelly Rauchenberger <fefferburbia@gmail.com>2018-05-09 17:59:13 -0400
commit96e6f3231aed9919d660a06944f1d96dc8241f8e (patch)
treeb2e633e7df6e0251afeec8a84a69be7b5944b175 /src
parent296a1c3aa0bdb27e7ee9b53f0382938d0fe6d1a0 (diff)
downloadtherapy-96e6f3231aed9919d660a06944f1d96dc8241f8e.tar.gz
therapy-96e6f3231aed9919d660a06944f1d96dc8241f8e.tar.bz2
therapy-96e6f3231aed9919d660a06944f1d96dc8241f8e.zip
Started refactoring collision detection to use directional functor
Because there was a lot of code replication with collision detection, this new implementation uses a functor that takes a parameter object describing the things that are different between the four directions. This allows the collision detection code to be writen only once. It's currently very ugly, but should work identically to before.
Diffstat (limited to 'src')
-rw-r--r--src/systems/pondering.cpp1088
-rw-r--r--src/systems/pondering.h40
2 files changed, 533 insertions, 595 deletions
diff --git a/src/systems/pondering.cpp b/src/systems/pondering.cpp index ed14772..7b3ab2d 100644 --- a/src/systems/pondering.cpp +++ b/src/systems/pondering.cpp
@@ -96,15 +96,6 @@ void PonderingSystem::tickBody(
96 return; 96 return;
97 } 97 }
98 98
99 auto& realizable = game_.getEntityManager().
100 getComponent<RealizableComponent>(
101 game_.getSystemManager().getSystem<RealizingSystem>().getSingleton());
102
103 id_type mapEntity = realizable.activeMap;
104
105 auto& mappable = game_.getEntityManager().
106 getComponent<MappableComponent>(mapEntity);
107
108 auto& transformable = game_.getEntityManager(). 99 auto& transformable = game_.getEntityManager().
109 getComponent<TransformableComponent>(entity); 100 getComponent<TransformableComponent>(entity);
110 101
@@ -121,14 +112,9 @@ void PonderingSystem::tickBody(
121 } 112 }
122 } 113 }
123 114
124 const double oldX = transformable.x; 115 // Move
125 const double oldY = transformable.y; 116 double newX = transformable.x;
126 const double oldRight = oldX + transformable.w; 117 double newY = transformable.y;
127 const double oldBottom = oldY + transformable.h;
128
129 CollisionResult result;
130 result.newX = transformable.x;
131 result.newY = transformable.y;
132 118
133 if (!ponderable.frozen) 119 if (!ponderable.frozen)
134 { 120 {
@@ -137,692 +123,626 @@ void PonderingSystem::tickBody(
137 auto& ferryTrans = game_.getEntityManager(). 123 auto& ferryTrans = game_.getEntityManager().
138 getComponent<TransformableComponent>(ponderable.ferry); 124 getComponent<TransformableComponent>(ponderable.ferry);
139 125
140 result.newX = ferryTrans.x + ponderable.relX; 126 newX = ferryTrans.x + ponderable.relX;
141 result.newY = ferryTrans.y + ponderable.relY; 127 newY = ferryTrans.y + ponderable.relY;
142 } 128 }
143 129
144 result.newX += ponderable.velX * dt; 130 newX += ponderable.velX * dt;
145 result.newY += ponderable.velY * dt; 131 newY += ponderable.velY * dt;
146 } 132 }
147 133
148 bool oldGrounded = ponderable.grounded; 134 CollisionResult result =
149 ponderable.grounded = false; 135 moveBody(
136 entity,
137 newX,
138 newY);
150 139
151 if (ponderable.collidable) 140 // Perform cleanup for orientable entites
141 bool groundedChanged = (ponderable.grounded != result.grounded);
142 ponderable.grounded = result.grounded;
143
144 if (game_.getEntityManager().hasComponent<OrientableComponent>(entity))
152 { 145 {
153 // Find horizontal collisions. 146 auto& orientable = game_.getEntityManager().
154 if (result.newX < oldX) 147 getComponent<OrientableComponent>(entity);
155 {
156 bool boundaryCollision = false;
157 auto it = mappable.leftBoundaries.lower_bound(oldX);
158 148
159 // Find the axis distance of the closest environmental boundary. 149 // Handle changes in groundedness
160 for (; 150 if (groundedChanged)
161 (it != std::end(mappable.leftBoundaries)) && 151 {
162 (it->first >= result.newX); 152 if (ponderable.grounded)
163 it++)
164 { 153 {
165 // Check that the boundary is in range for the other axis. 154 game_.getSystemManager().getSystem<OrientingSystem>().land(entity);
166 if ((oldBottom > it->second.lower) && (oldY < it->second.upper)) 155 } else {
167 { 156 game_.getSystemManager().
168 // We have a collision! 157 getSystem<OrientingSystem>().startFalling(entity);
169 boundaryCollision = true;
170
171 break;
172 }
173 } 158 }
159 }
174 160
175 // Find a list of potential colliders, sorted so that the closest is 161 // Complete dropping, if necessary
176 // first. 162 if (orientable.getDropState() == OrientableComponent::DropState::active)
177 std::vector<id_type> colliders; 163 {
164 orientable.setDropState(OrientableComponent::DropState::none);
165 }
166 }
178 167
179 for (id_type collider : entities) 168 // Ferry or unferry as necessary
180 { 169 if ((ponderable.type == PonderableComponent::Type::freefalling) &&
181 // Can't collide with self. 170 groundedChanged)
182 if (collider == entity) 171 {
183 { 172 if (ponderable.grounded &&
184 continue; 173 game_.getEntityManager().
185 } 174 hasComponent<PonderableComponent>(result.groundEntity))
175 {
176 // The body is now being ferried
177 auto& ferryPonder = game_.getEntityManager().
178 getComponent<PonderableComponent>(result.groundEntity);
179
180 ponderable.ferried = true;
181 ponderable.ferry = result.groundEntity;
186 182
187 auto& colliderPonder = game_.getEntityManager(). 183 ferryPonder.passengers.insert(entity);
188 getComponent<PonderableComponent>(collider); 184 } else if (ponderable.ferried)
185 {
186 // The body is no longer being ferried
187 unferry(entity);
188 }
189 }
189 190
190 // Only check objects that are active and collidable. 191 // Update a ferry passenger's relative position
191 if (!colliderPonder.active || !colliderPonder.collidable) 192 if (ponderable.ferried)
192 { 193 {
193 continue; 194 auto& ferryTrans = game_.getEntityManager().
194 } 195 getComponent<TransformableComponent>(ponderable.ferry);
195 196
196 auto& colliderTrans = game_.getEntityManager(). 197 ponderable.relX = transformable.x - ferryTrans.x;
197 getComponent<TransformableComponent>(collider); 198 ponderable.relY = transformable.y - ferryTrans.y;
198 199 }
199 // Check if the entity would move into the potential collider,
200 if ((colliderTrans.x + colliderTrans.w > result.newX) &&
201 // that it wasn't already colliding,
202 (colliderTrans.x + colliderTrans.w <= oldX) &&
203 // that the position on the other axis is in range,
204 (colliderTrans.y + colliderTrans.h > oldY) &&
205 (colliderTrans.y < oldBottom) &&
206 // and that the collider is not farther away than the environmental
207 // boundary.
208 (!boundaryCollision ||
209 (colliderTrans.x + colliderTrans.w >= it->first)))
210 {
211 colliders.push_back(collider);
212 }
213 }
214 200
215 std::sort( 201 // Handle ferry passengers
216 std::begin(colliders), 202 std::set<id_type> passengers = ponderable.passengers;
217 std::end(colliders),
218 [&] (id_type left, id_type right) {
219 auto& leftTrans = game_.getEntityManager().
220 getComponent<TransformableComponent>(left);
221 203
222 auto& rightTrans = game_.getEntityManager(). 204 for (id_type passenger : passengers)
223 getComponent<TransformableComponent>(right); 205 {
206 tickBody(
207 passenger,
208 dt,
209 entities);
210 }
224 211
225 return (rightTrans.x < leftTrans.x); 212 // Move to an adjacent map, if necessary
226 }); 213 if (result.adjacentlyWarping)
214 {
215 double warpX = result.newX;
216 double warpY = result.newY;
227 217
228 for (id_type collider : colliders) 218 switch (result.adjWarpDir)
219 {
220 case Direction::left:
229 { 221 {
230 auto& colliderTrans = game_.getEntityManager(). 222 warpX = GAME_WIDTH + WALL_GAP - transformable.w;
231 getComponent<TransformableComponent>(collider);
232
233 // Check if the entity would still move into the potential collider.
234 if (colliderTrans.x + colliderTrans.w <= result.newX)
235 {
236 break;
237 }
238 223
239 auto& colliderPonder = game_.getEntityManager(). 224 break;
240 getComponent<PonderableComponent>(collider); 225 }
241 226
242 processCollision( 227 case Direction::right:
243 entity, 228 {
244 collider, 229 warpX = -WALL_GAP;
245 Direction::left,
246 colliderPonder.colliderType,
247 colliderTrans.x + colliderTrans.w,
248 colliderTrans.y,
249 colliderTrans.y + colliderTrans.h,
250 result);
251 230
252 if (result.stopProcessing) 231 break;
253 {
254 break;
255 }
256 } 232 }
257 233
258 // If movement hasn't been stopped by an intermediary object, and 234 case Direction::up:
259 // collision checking hasn't been stopped, process the environmental
260 // boundaries closest to the entity.
261 if (!result.stopProcessing && !result.touchedWall && boundaryCollision)
262 { 235 {
263 double boundaryAxis = it->first; 236 warpY = MAP_HEIGHT * TILE_HEIGHT - transformable.h;
264 237
265 for (; 238 break;
266 (it != std::end(mappable.leftBoundaries)) &&
267 (it->first == boundaryAxis);
268 it++)
269 {
270 if ((oldBottom > it->second.lower) && (oldY < it->second.upper))
271 {
272 processCollision(
273 entity,
274 mapEntity,
275 Direction::left,
276 it->second.type,
277 it->first,
278 it->second.lower,
279 it->second.upper,
280 result);
281
282 if (result.stopProcessing)
283 {
284 break;
285 }
286 }
287 }
288 } 239 }
289 } else if (result.newX > oldX)
290 {
291 bool boundaryCollision = false;
292 auto it = mappable.rightBoundaries.lower_bound(oldRight);
293 240
294 // Find the axis distance of the closest environmental boundary. 241 case Direction::down:
295 for (;
296 (it != std::end(mappable.rightBoundaries))
297 && (it->first <= (result.newX + transformable.w));
298 it++)
299 { 242 {
300 // Check that the boundary is in range for the other axis. 243 warpY = -WALL_GAP;
301 if ((oldBottom > it->second.lower) && (oldY < it->second.upper))
302 {
303 // We have a collision!
304 boundaryCollision = true;
305 244
306 break; 245 break;
307 }
308 } 246 }
247 }
309 248
310 // Find a list of potential colliders, sorted so that the closest is 249 game_.getSystemManager().getSystem<PlayingSystem>().
311 // first. 250 changeMap(
312 std::vector<id_type> colliders; 251 entity,
313 252 result.adjWarpMapId,
314 for (id_type collider : entities) 253 warpX,
315 { 254 warpY);
316 // Can't collide with self. 255 }
317 if (collider == entity) 256}
318 {
319 continue;
320 }
321 257
322 auto& colliderPonder = game_.getEntityManager(). 258CollisionResult PonderingSystem::moveBody(
323 getComponent<PonderableComponent>(collider); 259 id_type entity,
260 double x,
261 double y)
262{
263 auto& ponderable = game_.getEntityManager().
264 getComponent<PonderableComponent>(entity);
324 265
325 // Only check objects that are active and collidable. 266 auto& transformable = game_.getEntityManager().
326 if (!colliderPonder.active || !colliderPonder.collidable) 267 getComponent<TransformableComponent>(entity);
327 {
328 continue;
329 }
330 268
331 auto& colliderTrans = game_.getEntityManager(). 269 const double oldX = transformable.x;
332 getComponent<TransformableComponent>(collider); 270 const double oldY = transformable.y;
333 271 const double oldRight = oldX + transformable.w;
334 // Check if the entity would move into the potential collider, 272 const double oldBottom = oldY + transformable.h;
335 if ((colliderTrans.x < result.newX + transformable.w) &&
336 // that it wasn't already colliding,
337 (colliderTrans.x >= oldRight) &&
338 // that the position on the other axis is in range,
339 (colliderTrans.y + colliderTrans.h > oldY) &&
340 (colliderTrans.y < oldBottom) &&
341 // and that the collider is not farther away than the environmental
342 // boundary.
343 (!boundaryCollision || (colliderTrans.x <= it->first)))
344 {
345 colliders.push_back(collider);
346 }
347 }
348 273
349 std::sort( 274 CollisionResult result;
350 std::begin(colliders),
351 std::end(colliders),
352 [&] (id_type left, id_type right) {
353 auto& leftTrans = game_.getEntityManager().
354 getComponent<TransformableComponent>(left);
355 275
356 auto& rightTrans = game_.getEntityManager(). 276 if (ponderable.collidable)
357 getComponent<TransformableComponent>(right); 277 {
278 result = detectCollisions(entity, x, y);
279 } else {
280 result.newX = x;
281 result.newY = y;
282 }
358 283
359 return (leftTrans.x < rightTrans.x); 284 // Move
360 }); 285 if (!ponderable.frozen)
286 {
287 transformable.x = result.newX;
288 transformable.y = result.newY;
289 }
361 290
362 for (id_type collider : colliders) 291 return result;
363 { 292}
364 auto& colliderTrans = game_.getEntityManager().
365 getComponent<TransformableComponent>(collider);
366 293
367 // Check if the entity would still move into the potential collider. 294namespace CollisionParams {
368 if (colliderTrans.x >= result.newX + transformable.w)
369 {
370 break;
371 }
372 295
373 auto& colliderPonder = game_.getEntityManager(). 296 template <typename HorizVert>
374 getComponent<PonderableComponent>(collider); 297 class Desc : public HorizVert {
298 public:
375 299
376 processCollision( 300 inline static bool AtLeastInAxisSweep(
377 entity, 301 double boundaryAxis,
378 collider, 302 double entityAxis)
379 Direction::right, 303 {
380 colliderPonder.colliderType, 304 return (boundaryAxis >= entityAxis);
381 colliderTrans.x, 305 }
382 colliderTrans.y,
383 colliderTrans.y + colliderTrans.h,
384 result);
385 306
386 if (result.stopProcessing) 307 inline static bool IsPastAxis(
387 { 308 double colliderAxis,
388 break; 309 double entityAxis)
389 } 310 {
390 } 311 return (colliderAxis > entityAxis);
312 }
391 313
392 // If movement hasn't been stopped by an intermediary object, and 314 inline static double OldAxis(const TransformableComponent& transformable)
393 // collision checking hasn't been stopped, process the environmental 315 {
394 // boundaries closest to the entity. 316 return HorizVert::AxisOldLower(transformable);
395 if (!result.stopProcessing && !result.touchedWall && boundaryCollision) 317 }
396 {
397 double boundaryAxis = it->first;
398 318
399 for (; 319 inline static double NewAxis(
400 (it != std::end(mappable.rightBoundaries)) && 320 const CollisionResult& result,
401 (it->first == boundaryAxis); 321 const TransformableComponent&)
402 it++) 322 {
403 { 323 return HorizVert::AxisNewLower(result);
404 if ((oldBottom > it->second.lower) && (oldY < it->second.upper))
405 {
406 processCollision(
407 entity,
408 mapEntity,
409 Direction::right,
410 it->second.type,
411 it->first,
412 it->second.lower,
413 it->second.upper,
414 result);
415
416 if (result.stopProcessing)
417 {
418 break;
419 }
420 }
421 }
422 }
423 } 324 }
424 }
425 325
426 // Find vertical collisions 326 inline static double ObjectAxis(const TransformableComponent& transformable)
427 if (ponderable.collidable && !result.stopProcessing) 327 {
428 { 328 return HorizVert::AxisOldUpper(transformable);
429 result.touchedWall = false; 329 }
430 330
431 if (result.newY < oldY) 331 inline static bool Closer(double left, double right)
432 { 332 {
433 bool boundaryCollision = false; 333 return right < left;
434 auto it = mappable.upBoundaries.lower_bound(oldY); 334 }
335 };
435 336
436 // Find the axis distance of the closest environmental boundary. 337 template <typename HorizVert>
437 for (; 338 class Asc : public HorizVert {
438 (it != std::end(mappable.upBoundaries)) && 339 public:
439 (it->first >= result.newY);
440 it++)
441 {
442 // Check that the boundary is in range for the other axis.
443 if ((result.newX + transformable.w > it->second.lower) &&
444 (result.newX < it->second.upper))
445 {
446 // We have a collision!
447 boundaryCollision = true;
448 340
449 break; 341 inline static bool AtLeastInAxisSweep(
450 } 342 double boundaryAxis,
451 } 343 double entityAxis)
344 {
345 return (boundaryAxis <= entityAxis);
346 }
452 347
453 // Find a list of potential colliders, sorted so that the closest is 348 inline static bool IsPastAxis(
454 // first. 349 double colliderAxis,
455 std::vector<id_type> colliders; 350 double entityAxis)
351 {
352 return (colliderAxis < entityAxis);
353 }
456 354
457 for (id_type collider : entities) 355 inline static double OldAxis(const TransformableComponent& transformable)
458 { 356 {
459 // Can't collide with self. 357 return HorizVert::AxisOldUpper(transformable);
460 if (collider == entity) 358 }
461 {
462 continue;
463 }
464 359
465 auto& colliderPonder = game_.getEntityManager(). 360 inline static double NewAxis(
466 getComponent<PonderableComponent>(collider); 361 const CollisionResult& result,
362 const TransformableComponent& transformable)
363 {
364 return HorizVert::AxisNewUpper(result, transformable);
365 }
467 366
468 // Only check objects that are active and collidable. 367 inline static double ObjectAxis(const TransformableComponent& transformable)
469 if (!colliderPonder.active || !colliderPonder.collidable) 368 {
470 { 369 return HorizVert::AxisOldLower(transformable);
471 continue; 370 }
472 }
473 371
474 auto& colliderTrans = game_.getEntityManager(). 372 inline static bool Closer(double left, double right)
475 getComponent<TransformableComponent>(collider); 373 {
476 374 return left < right;
477 // Check if the entity would move into the potential collider, 375 }
478 if ((colliderTrans.y + colliderTrans.h > result.newY) && 376 };
479 // that it wasn't already colliding,
480 (colliderTrans.y + colliderTrans.h <= oldY) &&
481 // that the position on the other axis is in range,
482 (colliderTrans.x + colliderTrans.w > result.newX) &&
483 (colliderTrans.x < result.newX + transformable.w) &&
484 // and that the collider is not farther away than the environmental
485 // boundary.
486 (!boundaryCollision ||
487 (colliderTrans.y + colliderTrans.h >= it->first)))
488 {
489 colliders.push_back(collider);
490 }
491 }
492 377
493 std::sort( 378 class Horizontal {
494 std::begin(colliders), 379 public:
495 std::end(colliders),
496 [&] (id_type left, id_type right) {
497 auto& leftTrans = game_.getEntityManager().
498 getComponent<TransformableComponent>(left);
499 380
500 auto& rightTrans = game_.getEntityManager(). 381 inline static double AxisOldLower(
501 getComponent<TransformableComponent>(right); 382 const TransformableComponent& transformable)
383 {
384 return transformable.x;
385 }
502 386
503 return (rightTrans.y < leftTrans.y); 387 inline static double AxisOldUpper(
504 }); 388 const TransformableComponent& transformable)
389 {
390 return transformable.x + transformable.w;
391 }
505 392
506 for (id_type collider : colliders) 393 inline static double AxisNewLower(const CollisionResult& result)
507 { 394 {
508 auto& colliderTrans = game_.getEntityManager(). 395 return result.newX;
509 getComponent<TransformableComponent>(collider); 396 }
510 397
511 // Check if the entity would still move into the potential collider. 398 inline static double AxisNewUpper(
512 if (colliderTrans.y + colliderTrans.h <= result.newY) 399 const CollisionResult& result,
513 { 400 const TransformableComponent& transformable)
514 break; 401 {
515 } 402 return result.newX + transformable.w;
403 }
516 404
517 auto& colliderPonder = game_.getEntityManager(). 405 inline static double NonAxisOldLower(
518 getComponent<PonderableComponent>(collider); 406 const TransformableComponent& transformable)
407 {
408 return transformable.y;
409 }
519 410
520 processCollision( 411 inline static double NonAxisOldUpper(
521 entity, 412 const TransformableComponent& transformable)
522 collider, 413 {
523 Direction::up, 414 return transformable.y + transformable.h;
524 colliderPonder.colliderType, 415 }
525 colliderTrans.y + colliderTrans.h,
526 colliderTrans.x,
527 colliderTrans.x + colliderTrans.w,
528 result);
529 416
530 if (result.stopProcessing) 417 inline static double NonAxisNewLower(
531 { 418 const CollisionResult& result,
532 break; 419 const TransformableComponent& transformable)
533 } 420 {
534 } 421 return result.newY;
422 }
535 423
536 // If movement hasn't been stopped by an intermediary object, and 424 inline static double NonAxisNewUpper(
537 // collision checking hasn't been stopped, process the environmental 425 const CollisionResult& result,
538 // boundaries closest to the entity. 426 const TransformableComponent& transformable)
539 if (!result.stopProcessing && !result.touchedWall && boundaryCollision) 427 {
540 { 428 return result.newY + transformable.h;
541 double boundaryAxis = it->first; 429 }
430 };
542 431
543 for (; 432 class Vertical {
544 (it != std::end(mappable.upBoundaries)) && 433 public:
545 (it->first == boundaryAxis); 434
546 it++) 435 inline static double AxisOldLower(
547 { 436 const TransformableComponent& transformable)
548 if ((result.newX + transformable.w > it->second.lower) &&
549 (result.newX < it->second.upper))
550 {
551 processCollision(
552 entity,
553 mapEntity,
554 Direction::up,
555 it->second.type,
556 it->first,
557 it->second.lower,
558 it->second.upper,
559 result);
560
561 if (result.stopProcessing)
562 {
563 break;
564 }
565 }
566 }
567 }
568 } else if (result.newY > oldY)
569 { 437 {
570 bool boundaryCollision = false; 438 return transformable.y;
571 auto it = mappable.downBoundaries.lower_bound(oldBottom); 439 }
572 440
573 // Find the axis distance of the closest environmental boundary. 441 inline static double AxisOldUpper(
574 for (; 442 const TransformableComponent& transformable)
575 (it != std::end(mappable.downBoundaries)) 443 {
576 && (it->first <= (result.newY + transformable.h)); 444 return transformable.y + transformable.h;
577 it++) 445 }
578 {
579 // Check that the boundary is in range for the other axis.
580 if ((result.newX + transformable.w > it->second.lower) &&
581 (result.newX < it->second.upper))
582 {
583 // We have a collision!
584 boundaryCollision = true;
585 446
586 break; 447 inline static double AxisNewLower(const CollisionResult& result)
587 } 448 {
588 } 449 return result.newY;
450 }
589 451
590 // Find a list of potential colliders, sorted so that the closest is 452 inline static double AxisNewUpper(
591 // first. 453 const CollisionResult& result,
592 std::vector<id_type> colliders; 454 const TransformableComponent& transformable)
455 {
456 return result.newY + transformable.h;
457 }
593 458
594 for (id_type collider : entities) 459 inline static double NonAxisOldLower(
595 { 460 const TransformableComponent& transformable)
596 // Can't collide with self. 461 {
597 if (collider == entity) 462 return transformable.x;
598 { 463 }
599 continue;
600 }
601 464
602 auto& colliderPonder = game_.getEntityManager(). 465 inline static double NonAxisOldUpper(
603 getComponent<PonderableComponent>(collider); 466 const TransformableComponent& transformable)
467 {
468 return transformable.x + transformable.w;
469 }
604 470
605 // Only check objects that are active and collidable. 471 inline static double NonAxisNewLower(
606 if (!colliderPonder.active || !colliderPonder.collidable) 472 const CollisionResult& result,
607 { 473 const TransformableComponent& transformable)
608 continue; 474 {
609 } 475 return result.newX;
476 }
610 477
611 auto& colliderTrans = game_.getEntityManager(). 478 inline static double NonAxisNewUpper(
612 getComponent<TransformableComponent>(collider); 479 const CollisionResult& result,
613 480 const TransformableComponent& transformable)
614 // Check if the entity would move into the potential collider, 481 {
615 if ((colliderTrans.y < result.newY + transformable.h) && 482 return result.newX + transformable.w;
616 // that it wasn't already colliding, 483 }
617 (colliderTrans.y >= oldBottom) && 484 };
618 // that the position on the other axis is in range,
619 (colliderTrans.x + colliderTrans.w > result.newX) &&
620 (colliderTrans.x < result.newX + transformable.w) &&
621 // and that the collider is not farther away than the environmental
622 // boundary.
623 (!boundaryCollision || (colliderTrans.y <= it->first)))
624 {
625 colliders.push_back(collider);
626 }
627 }
628 485
629 std::sort( 486 template <Direction dir, typename AscDesc>
630 std::begin(colliders), 487 class DetectCollisions : public AscDesc {
631 std::end(colliders), 488 public:
632 [&] (id_type left, id_type right) {
633 auto& leftTrans = game_.getEntityManager().
634 getComponent<TransformableComponent>(left);
635 489
636 auto& rightTrans = game_.getEntityManager(). 490 static const Direction Dir = dir;
637 getComponent<TransformableComponent>(right); 491 };
638 492
639 return (leftTrans.y < rightTrans.y); 493 class Left : public DetectCollisions<Direction::left, Desc<Horizontal>> {
640 }); 494 public:
641 495
642 for (id_type collider : colliders) 496 inline static const MappableComponent::desc_boundaries_type& MapBoundaries(
643 { 497 const MappableComponent& mappable)
644 auto& colliderTrans = game_.getEntityManager(). 498 {
645 getComponent<TransformableComponent>(collider); 499 return mappable.leftBoundaries;
500 }
501 };
646 502
647 // Check if the entity would still move into the potential collider. 503 class Right : public DetectCollisions<Direction::right, Asc<Horizontal>> {
648 if (colliderTrans.y >= result.newY + transformable.h) 504 public:
649 {
650 break;
651 }
652 505
653 auto& colliderPonder = game_.getEntityManager(). 506 inline static const MappableComponent::asc_boundaries_type& MapBoundaries(
654 getComponent<PonderableComponent>(collider); 507 const MappableComponent& mappable)
508 {
509 return mappable.rightBoundaries;
510 }
511 };
655 512
656 processCollision( 513 class Up : public DetectCollisions<Direction::up, Desc<Vertical>> {
657 entity, 514 public:
658 collider,
659 Direction::down,
660 colliderPonder.colliderType,
661 colliderTrans.y,
662 colliderTrans.x,
663 colliderTrans.x + colliderTrans.w,
664 result);
665 515
666 if (result.stopProcessing) 516 inline static const MappableComponent::desc_boundaries_type& MapBoundaries(
667 { 517 const MappableComponent& mappable)
668 break; 518 {
669 } 519 return mappable.upBoundaries;
670 } 520 }
521 };
671 522
672 // If movement hasn't been stopped by an intermediary object, and 523 class Down : public DetectCollisions<Direction::down, Asc<Vertical>> {
673 // collision checking hasn't been stopped, process the environmental 524 public:
674 // boundaries closest to the entity.
675 if (!result.stopProcessing && !result.touchedWall && boundaryCollision)
676 {
677 double boundaryAxis = it->first;
678 525
679 for (; 526 inline static const MappableComponent::asc_boundaries_type& MapBoundaries(
680 (it != std::end(mappable.downBoundaries)) && 527 const MappableComponent& mappable)
681 (it->first == boundaryAxis); 528 {
682 it++) 529 return mappable.downBoundaries;
683 {
684 if ((result.newX + transformable.w > it->second.lower) &&
685 (result.newX < it->second.upper))
686 {
687 processCollision(
688 entity,
689 mapEntity,
690 Direction::down,
691 it->second.type,
692 it->first,
693 it->second.lower,
694 it->second.upper,
695 result);
696
697 if (result.stopProcessing)
698 {
699 break;
700 }
701 }
702 }
703 }
704 } 530 }
705 } 531 };
532};
706 533
707 // Move 534CollisionResult PonderingSystem::detectCollisions(
708 if (!ponderable.frozen) 535 id_type entity,
536 double x,
537 double y)
538{
539 auto& transformable = game_.getEntityManager().
540 getComponent<TransformableComponent>(entity);
541
542 CollisionResult result;
543 result.newX = x;
544 result.newY = transformable.y;
545
546 // Find horizontal collisions.
547 if (result.newX < transformable.x)
709 { 548 {
710 transformable.x = result.newX; 549 detectCollisionsInDirection<CollisionParams::Left>(entity, result);
711 transformable.y = result.newY; 550 } else if (result.newX > transformable.x)
551 {
552 detectCollisionsInDirection<CollisionParams::Right>(entity, result);
712 } 553 }
713 554
714 // Perform cleanup for orientable entites 555 // Find vertical collisions
715 if (game_.getEntityManager().hasComponent<OrientableComponent>(entity)) 556 if (!result.stopProcessing)
716 { 557 {
717 auto& orientable = game_.getEntityManager(). 558 result.newY = y;
718 getComponent<OrientableComponent>(entity); 559 result.touchedWall = false;
719 560
720 // Handle changes in groundedness 561 if (result.newY < transformable.y)
721 if (ponderable.grounded != oldGrounded)
722 { 562 {
723 if (ponderable.grounded) 563 detectCollisionsInDirection<CollisionParams::Up>(entity, result);
724 { 564 } else if (result.newY > transformable.y)
725 game_.getSystemManager().getSystem<OrientingSystem>().land(entity); 565 {
726 } else { 566 detectCollisionsInDirection<CollisionParams::Down>(entity, result);
727 game_.getSystemManager().
728 getSystem<OrientingSystem>().startFalling(entity);
729 }
730 } 567 }
568 }
731 569
732 // Complete dropping, if necessary 570 return result;
733 if (orientable.getDropState() == OrientableComponent::DropState::active) 571}
572
573template <typename Param>
574void PonderingSystem::detectCollisionsInDirection(
575 id_type entity,
576 CollisionResult& result)
577{
578 // Get map data.
579 auto& realizable = game_.getEntityManager().
580 getComponent<RealizableComponent>(
581 game_.getSystemManager().getSystem<RealizingSystem>().getSingleton());
582
583 id_type mapEntity = realizable.activeMap;
584
585 auto& mappable = game_.getEntityManager().
586 getComponent<MappableComponent>(mapEntity);
587
588 // Get old location.
589 auto& transformable = game_.getEntityManager().
590 getComponent<TransformableComponent>(entity);
591
592 bool boundaryCollision = false;
593
594 auto boundaries = Param::MapBoundaries(mappable);
595 auto it = boundaries.lower_bound(Param::OldAxis(transformable));
596
597 // Find the axis distance of the closest environmental boundary.
598 for (;
599 (it != std::end(boundaries)) &&
600 Param::AtLeastInAxisSweep(
601 it->first,
602 Param::NewAxis(result, transformable));
603 it++)
604 {
605 // Check that the boundary is in range for the other axis.
606 if ((Param::NonAxisNewUpper(result, transformable) > it->second.lower) &&
607 (Param::NonAxisNewLower(result, transformable) < it->second.upper))
734 { 608 {
735 orientable.setDropState(OrientableComponent::DropState::none); 609 // We have a collision!
610 boundaryCollision = true;
611
612 break;
736 } 613 }
737 } 614 }
738 615
739 // Ferry or unferry as necessary 616 // Find a list of potential colliders, sorted so that the closest is
740 if ((ponderable.type == PonderableComponent::Type::freefalling) && 617 // first.
741 (ponderable.grounded != oldGrounded)) 618 std::vector<id_type> colliders;
619
620 auto entities = game_.getEntityManager().getEntitiesWithComponents<
621 PonderableComponent,
622 TransformableComponent>();
623
624 for (id_type collider : entities)
742 { 625 {
743 if (ponderable.grounded && 626 // Can't collide with self.
744 game_.getEntityManager(). 627 if (collider == entity)
745 hasComponent<PonderableComponent>(result.groundEntity))
746 { 628 {
747 // The body is now being ferried 629 continue;
748 auto& ferryPonder = game_.getEntityManager(). 630 }
749 getComponent<PonderableComponent>(result.groundEntity);
750 631
751 ponderable.ferried = true; 632 auto& colliderPonder = game_.getEntityManager().
752 ponderable.ferry = result.groundEntity; 633 getComponent<PonderableComponent>(collider);
753 634
754 ferryPonder.passengers.insert(entity); 635 // Only check objects that are active and collidable.
755 } else if (ponderable.ferried) 636 if (!colliderPonder.active || !colliderPonder.collidable)
756 { 637 {
757 // The body is no longer being ferried 638 continue;
758 unferry(entity);
759 } 639 }
760 }
761
762 // Update a ferry passenger's relative position
763 if (ponderable.ferried)
764 {
765 auto& ferryTrans = game_.getEntityManager().
766 getComponent<TransformableComponent>(ponderable.ferry);
767 640
768 ponderable.relX = transformable.x - ferryTrans.x; 641 auto& colliderTrans = game_.getEntityManager().
769 ponderable.relY = transformable.y - ferryTrans.y; 642 getComponent<TransformableComponent>(collider);
643
644 // Check if the entity would move into the potential collider,
645 if (Param::IsPastAxis(
646 Param::ObjectAxis(colliderTrans),
647 Param::NewAxis(result, transformable)) &&
648 // that it wasn't already colliding,
649 !Param::IsPastAxis(
650 Param::ObjectAxis(colliderTrans),
651 Param::OldAxis(transformable)) &&
652 // that the position on the non-axis is in range,
653 (Param::NonAxisOldUpper(colliderTrans) >
654 Param::NonAxisNewLower(result, transformable)) &&
655 (Param::NonAxisOldLower(colliderTrans) <
656 Param::NonAxisNewUpper(result, transformable)) &&
657 // and that the collider is not farther away than the environmental
658 // boundary.
659 (!boundaryCollision ||
660 Param::AtLeastInAxisSweep(
661 Param::ObjectAxis(colliderTrans),
662 it->first)))
663 {
664 colliders.push_back(collider);
665 }
770 } 666 }
771 667
772 // Handle ferry passengers 668 std::sort(
773 std::set<id_type> passengers = ponderable.passengers; 669 std::begin(colliders),
670 std::end(colliders),
671 [&] (id_type left, id_type right) {
672 auto& leftTrans = game_.getEntityManager().
673 getComponent<TransformableComponent>(left);
774 674
775 for (id_type passenger : passengers) 675 auto& rightTrans = game_.getEntityManager().
776 { 676 getComponent<TransformableComponent>(right);
777 tickBody(
778 passenger,
779 dt,
780 entities);
781 }
782 677
783 // Move to an adjacent map, if necessary 678 return Param::Closer(
784 if (result.adjacentlyWarping) 679 Param::ObjectAxis(leftTrans),
680 Param::ObjectAxis(rightTrans));
681 });
682
683 for (id_type collider : colliders)
785 { 684 {
786 double warpX = result.newX; 685 auto& colliderTrans = game_.getEntityManager().
787 double warpY = result.newY; 686 getComponent<TransformableComponent>(collider);
788 687
789 switch (result.adjWarpDir) 688 // Check if the entity would still move into the potential collider.
689 if (!Param::IsPastAxis(
690 Param::ObjectAxis(colliderTrans),
691 Param::NewAxis(result, transformable)))
790 { 692 {
791 case Direction::left: 693 break;
792 { 694 }
793 warpX = GAME_WIDTH + WALL_GAP - transformable.w;
794
795 break;
796 }
797
798 case Direction::right:
799 {
800 warpX = -WALL_GAP;
801 695
802 break; 696 auto& colliderPonder = game_.getEntityManager().
803 } 697 getComponent<PonderableComponent>(collider);
804 698
805 case Direction::up: 699 processCollision(
806 { 700 entity,
807 warpY = MAP_HEIGHT * TILE_HEIGHT - transformable.h; 701 collider,
702 Param::Dir,
703 colliderPonder.colliderType,
704 Param::ObjectAxis(colliderTrans),
705 Param::NonAxisOldLower(colliderTrans),
706 Param::NonAxisOldUpper(colliderTrans),
707 result);
708
709 if (result.stopProcessing)
710 {
711 break;
712 }
713 }
808 714
809 break; 715 // If movement hasn't been stopped by an intermediary object, and
810 } 716 // collision checking hasn't been stopped, process the environmental
717 // boundaries closest to the entity.
718 if (!result.stopProcessing && !result.touchedWall && boundaryCollision)
719 {
720 double boundaryAxis = it->first;
811 721
812 case Direction::down: 722 for (;
723 (it != std::end(boundaries)) &&
724 (it->first == boundaryAxis);
725 it++)
726 {
727 if ((Param::NonAxisNewUpper(result, transformable) > it->second.lower) &&
728 (Param::NonAxisNewLower(result, transformable) < it->second.upper))
813 { 729 {
814 warpY = -WALL_GAP; 730 processCollision(
731 entity,
732 mapEntity,
733 Param::Dir,
734 it->second.type,
735 it->first,
736 it->second.lower,
737 it->second.upper,
738 result);
815 739
816 break; 740 if (result.stopProcessing)
741 {
742 break;
743 }
817 } 744 }
818 } 745 }
819
820 game_.getSystemManager().getSystem<PlayingSystem>().
821 changeMap(
822 entity,
823 result.adjWarpMapId,
824 warpX,
825 warpY);
826 } 746 }
827} 747}
828 748
@@ -1012,7 +932,7 @@ void PonderingSystem::processCollision(
1012 result.newY = axis - transformable.h; 932 result.newY = axis - transformable.h;
1013 result.groundEntity = collider; 933 result.groundEntity = collider;
1014 ponderable.velY = 0.0; 934 ponderable.velY = 0.0;
1015 ponderable.grounded = true; 935 result.grounded = true;
1016 936
1017 break; 937 break;
1018 } 938 }
diff --git a/src/systems/pondering.h b/src/systems/pondering.h index eed0d32..abc6db2 100644 --- a/src/systems/pondering.h +++ b/src/systems/pondering.h
@@ -5,6 +5,19 @@
5#include "components/ponderable.h" 5#include "components/ponderable.h"
6#include "direction.h" 6#include "direction.h"
7 7
8struct CollisionResult
9{
10 double newX;
11 double newY;
12 bool stopProcessing = false;
13 bool touchedWall = false;
14 bool adjacentlyWarping = false;
15 Direction adjWarpDir;
16 size_t adjWarpMapId;
17 bool grounded = false;
18 EntityManager::id_type groundEntity;
19};
20
8class PonderingSystem : public System { 21class PonderingSystem : public System {
9public: 22public:
10 23
@@ -34,23 +47,28 @@ public:
34 47
35private: 48private:
36 49
37 struct CollisionResult 50
38 {
39 double newX;
40 double newY;
41 bool stopProcessing = false;
42 bool touchedWall = false;
43 bool adjacentlyWarping = false;
44 Direction adjWarpDir;
45 size_t adjWarpMapId;
46 id_type groundEntity;
47 };
48 51
49 void tickBody( 52 void tickBody(
50 id_type entity, 53 id_type entity,
51 double dt, 54 double dt,
52 const std::set<id_type>& entities); 55 const std::set<id_type>& entities);
53 56
57 CollisionResult moveBody(
58 id_type entity,
59 double x,
60 double y);
61
62 CollisionResult detectCollisions(
63 id_type entity,
64 double x,
65 double y);
66
67 template <typename Param>
68 void detectCollisionsInDirection(
69 id_type entity,
70 CollisionResult& result);
71
54 void processCollision( 72 void processCollision(
55 id_type entity, 73 id_type entity,
56 id_type collider, 74 id_type collider,