From 96e6f3231aed9919d660a06944f1d96dc8241f8e Mon Sep 17 00:00:00 2001 From: Kelly Rauchenberger Date: Tue, 8 May 2018 18:38:28 -0400 Subject: 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. --- src/systems/pondering.cpp | 1088 +++++++++++++++++++++------------------------ src/systems/pondering.h | 40 +- 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( return; } - auto& realizable = game_.getEntityManager(). - getComponent( - game_.getSystemManager().getSystem().getSingleton()); - - id_type mapEntity = realizable.activeMap; - - auto& mappable = game_.getEntityManager(). - getComponent(mapEntity); - auto& transformable = game_.getEntityManager(). getComponent(entity); @@ -121,14 +112,9 @@ void PonderingSystem::tickBody( } } - const double oldX = transformable.x; - const double oldY = transformable.y; - const double oldRight = oldX + transformable.w; - const double oldBottom = oldY + transformable.h; - - CollisionResult result; - result.newX = transformable.x; - result.newY = transformable.y; + // Move + double newX = transformable.x; + double newY = transformable.y; if (!ponderable.frozen) { @@ -137,692 +123,626 @@ void PonderingSystem::tickBody( auto& ferryTrans = game_.getEntityManager(). getComponent(ponderable.ferry); - result.newX = ferryTrans.x + ponderable.relX; - result.newY = ferryTrans.y + ponderable.relY; + newX = ferryTrans.x + ponderable.relX; + newY = ferryTrans.y + ponderable.relY; } - result.newX += ponderable.velX * dt; - result.newY += ponderable.velY * dt; + newX += ponderable.velX * dt; + newY += ponderable.velY * dt; } - bool oldGrounded = ponderable.grounded; - ponderable.grounded = false; + CollisionResult result = + moveBody( + entity, + newX, + newY); - if (ponderable.collidable) + // Perform cleanup for orientable entites + bool groundedChanged = (ponderable.grounded != result.grounded); + ponderable.grounded = result.grounded; + + if (game_.getEntityManager().hasComponent(entity)) { - // Find horizontal collisions. - if (result.newX < oldX) - { - bool boundaryCollision = false; - auto it = mappable.leftBoundaries.lower_bound(oldX); + auto& orientable = game_.getEntityManager(). + getComponent(entity); - // Find the axis distance of the closest environmental boundary. - for (; - (it != std::end(mappable.leftBoundaries)) && - (it->first >= result.newX); - it++) + // Handle changes in groundedness + if (groundedChanged) + { + if (ponderable.grounded) { - // Check that the boundary is in range for the other axis. - if ((oldBottom > it->second.lower) && (oldY < it->second.upper)) - { - // We have a collision! - boundaryCollision = true; - - break; - } + game_.getSystemManager().getSystem().land(entity); + } else { + game_.getSystemManager(). + getSystem().startFalling(entity); } + } - // Find a list of potential colliders, sorted so that the closest is - // first. - std::vector colliders; + // Complete dropping, if necessary + if (orientable.getDropState() == OrientableComponent::DropState::active) + { + orientable.setDropState(OrientableComponent::DropState::none); + } + } - for (id_type collider : entities) - { - // Can't collide with self. - if (collider == entity) - { - continue; - } + // Ferry or unferry as necessary + if ((ponderable.type == PonderableComponent::Type::freefalling) && + groundedChanged) + { + if (ponderable.grounded && + game_.getEntityManager(). + hasComponent(result.groundEntity)) + { + // The body is now being ferried + auto& ferryPonder = game_.getEntityManager(). + getComponent(result.groundEntity); + + ponderable.ferried = true; + ponderable.ferry = result.groundEntity; - auto& colliderPonder = game_.getEntityManager(). - getComponent(collider); + ferryPonder.passengers.insert(entity); + } else if (ponderable.ferried) + { + // The body is no longer being ferried + unferry(entity); + } + } - // Only check objects that are active and collidable. - if (!colliderPonder.active || !colliderPonder.collidable) - { - continue; - } + // Update a ferry passenger's relative position + if (ponderable.ferried) + { + auto& ferryTrans = game_.getEntityManager(). + getComponent(ponderable.ferry); - auto& colliderTrans = game_.getEntityManager(). - getComponent(collider); - - // Check if the entity would move into the potential collider, - if ((colliderTrans.x + colliderTrans.w > result.newX) && - // that it wasn't already colliding, - (colliderTrans.x + colliderTrans.w <= oldX) && - // that the position on the other axis is in range, - (colliderTrans.y + colliderTrans.h > oldY) && - (colliderTrans.y < oldBottom) && - // and that the collider is not farther away than the environmental - // boundary. - (!boundaryCollision || - (colliderTrans.x + colliderTrans.w >= it->first))) - { - colliders.push_back(collider); - } - } + ponderable.relX = transformable.x - ferryTrans.x; + ponderable.relY = transformable.y - ferryTrans.y; + } - std::sort( - std::begin(colliders), - std::end(colliders), - [&] (id_type left, id_type right) { - auto& leftTrans = game_.getEntityManager(). - getComponent(left); + // Handle ferry passengers + std::set passengers = ponderable.passengers; - auto& rightTrans = game_.getEntityManager(). - getComponent(right); + for (id_type passenger : passengers) + { + tickBody( + passenger, + dt, + entities); + } - return (rightTrans.x < leftTrans.x); - }); + // Move to an adjacent map, if necessary + if (result.adjacentlyWarping) + { + double warpX = result.newX; + double warpY = result.newY; - for (id_type collider : colliders) + switch (result.adjWarpDir) + { + case Direction::left: { - auto& colliderTrans = game_.getEntityManager(). - getComponent(collider); - - // Check if the entity would still move into the potential collider. - if (colliderTrans.x + colliderTrans.w <= result.newX) - { - break; - } + warpX = GAME_WIDTH + WALL_GAP - transformable.w; - auto& colliderPonder = game_.getEntityManager(). - getComponent(collider); + break; + } - processCollision( - entity, - collider, - Direction::left, - colliderPonder.colliderType, - colliderTrans.x + colliderTrans.w, - colliderTrans.y, - colliderTrans.y + colliderTrans.h, - result); + case Direction::right: + { + warpX = -WALL_GAP; - if (result.stopProcessing) - { - break; - } + break; } - // If movement hasn't been stopped by an intermediary object, and - // collision checking hasn't been stopped, process the environmental - // boundaries closest to the entity. - if (!result.stopProcessing && !result.touchedWall && boundaryCollision) + case Direction::up: { - double boundaryAxis = it->first; + warpY = MAP_HEIGHT * TILE_HEIGHT - transformable.h; - for (; - (it != std::end(mappable.leftBoundaries)) && - (it->first == boundaryAxis); - it++) - { - if ((oldBottom > it->second.lower) && (oldY < it->second.upper)) - { - processCollision( - entity, - mapEntity, - Direction::left, - it->second.type, - it->first, - it->second.lower, - it->second.upper, - result); - - if (result.stopProcessing) - { - break; - } - } - } + break; } - } else if (result.newX > oldX) - { - bool boundaryCollision = false; - auto it = mappable.rightBoundaries.lower_bound(oldRight); - // Find the axis distance of the closest environmental boundary. - for (; - (it != std::end(mappable.rightBoundaries)) - && (it->first <= (result.newX + transformable.w)); - it++) + case Direction::down: { - // Check that the boundary is in range for the other axis. - if ((oldBottom > it->second.lower) && (oldY < it->second.upper)) - { - // We have a collision! - boundaryCollision = true; + warpY = -WALL_GAP; - break; - } + break; } + } - // Find a list of potential colliders, sorted so that the closest is - // first. - std::vector colliders; - - for (id_type collider : entities) - { - // Can't collide with self. - if (collider == entity) - { - continue; - } + game_.getSystemManager().getSystem(). + changeMap( + entity, + result.adjWarpMapId, + warpX, + warpY); + } +} - auto& colliderPonder = game_.getEntityManager(). - getComponent(collider); +CollisionResult PonderingSystem::moveBody( + id_type entity, + double x, + double y) +{ + auto& ponderable = game_.getEntityManager(). + getComponent(entity); - // Only check objects that are active and collidable. - if (!colliderPonder.active || !colliderPonder.collidable) - { - continue; - } + auto& transformable = game_.getEntityManager(). + getComponent(entity); - auto& colliderTrans = game_.getEntityManager(). - getComponent(collider); - - // Check if the entity would move into the potential collider, - if ((colliderTrans.x < result.newX + transformable.w) && - // that it wasn't already colliding, - (colliderTrans.x >= oldRight) && - // that the position on the other axis is in range, - (colliderTrans.y + colliderTrans.h > oldY) && - (colliderTrans.y < oldBottom) && - // and that the collider is not farther away than the environmental - // boundary. - (!boundaryCollision || (colliderTrans.x <= it->first))) - { - colliders.push_back(collider); - } - } + const double oldX = transformable.x; + const double oldY = transformable.y; + const double oldRight = oldX + transformable.w; + const double oldBottom = oldY + transformable.h; - std::sort( - std::begin(colliders), - std::end(colliders), - [&] (id_type left, id_type right) { - auto& leftTrans = game_.getEntityManager(). - getComponent(left); + CollisionResult result; - auto& rightTrans = game_.getEntityManager(). - getComponent(right); + if (ponderable.collidable) + { + result = detectCollisions(entity, x, y); + } else { + result.newX = x; + result.newY = y; + } - return (leftTrans.x < rightTrans.x); - }); + // Move + if (!ponderable.frozen) + { + transformable.x = result.newX; + transformable.y = result.newY; + } - for (id_type collider : colliders) - { - auto& colliderTrans = game_.getEntityManager(). - getComponent(collider); + return result; +} - // Check if the entity would still move into the potential collider. - if (colliderTrans.x >= result.newX + transformable.w) - { - break; - } +namespace CollisionParams { - auto& colliderPonder = game_.getEntityManager(). - getComponent(collider); + template + class Desc : public HorizVert { + public: - processCollision( - entity, - collider, - Direction::right, - colliderPonder.colliderType, - colliderTrans.x, - colliderTrans.y, - colliderTrans.y + colliderTrans.h, - result); + inline static bool AtLeastInAxisSweep( + double boundaryAxis, + double entityAxis) + { + return (boundaryAxis >= entityAxis); + } - if (result.stopProcessing) - { - break; - } - } + inline static bool IsPastAxis( + double colliderAxis, + double entityAxis) + { + return (colliderAxis > entityAxis); + } - // If movement hasn't been stopped by an intermediary object, and - // collision checking hasn't been stopped, process the environmental - // boundaries closest to the entity. - if (!result.stopProcessing && !result.touchedWall && boundaryCollision) - { - double boundaryAxis = it->first; + inline static double OldAxis(const TransformableComponent& transformable) + { + return HorizVert::AxisOldLower(transformable); + } - for (; - (it != std::end(mappable.rightBoundaries)) && - (it->first == boundaryAxis); - it++) - { - if ((oldBottom > it->second.lower) && (oldY < it->second.upper)) - { - processCollision( - entity, - mapEntity, - Direction::right, - it->second.type, - it->first, - it->second.lower, - it->second.upper, - result); - - if (result.stopProcessing) - { - break; - } - } - } - } + inline static double NewAxis( + const CollisionResult& result, + const TransformableComponent&) + { + return HorizVert::AxisNewLower(result); } - } - // Find vertical collisions - if (ponderable.collidable && !result.stopProcessing) - { - result.touchedWall = false; + inline static double ObjectAxis(const TransformableComponent& transformable) + { + return HorizVert::AxisOldUpper(transformable); + } - if (result.newY < oldY) + inline static bool Closer(double left, double right) { - bool boundaryCollision = false; - auto it = mappable.upBoundaries.lower_bound(oldY); + return right < left; + } + }; - // Find the axis distance of the closest environmental boundary. - for (; - (it != std::end(mappable.upBoundaries)) && - (it->first >= result.newY); - it++) - { - // Check that the boundary is in range for the other axis. - if ((result.newX + transformable.w > it->second.lower) && - (result.newX < it->second.upper)) - { - // We have a collision! - boundaryCollision = true; + template + class Asc : public HorizVert { + public: - break; - } - } + inline static bool AtLeastInAxisSweep( + double boundaryAxis, + double entityAxis) + { + return (boundaryAxis <= entityAxis); + } - // Find a list of potential colliders, sorted so that the closest is - // first. - std::vector colliders; + inline static bool IsPastAxis( + double colliderAxis, + double entityAxis) + { + return (colliderAxis < entityAxis); + } - for (id_type collider : entities) - { - // Can't collide with self. - if (collider == entity) - { - continue; - } + inline static double OldAxis(const TransformableComponent& transformable) + { + return HorizVert::AxisOldUpper(transformable); + } - auto& colliderPonder = game_.getEntityManager(). - getComponent(collider); + inline static double NewAxis( + const CollisionResult& result, + const TransformableComponent& transformable) + { + return HorizVert::AxisNewUpper(result, transformable); + } - // Only check objects that are active and collidable. - if (!colliderPonder.active || !colliderPonder.collidable) - { - continue; - } + inline static double ObjectAxis(const TransformableComponent& transformable) + { + return HorizVert::AxisOldLower(transformable); + } - auto& colliderTrans = game_.getEntityManager(). - getComponent(collider); - - // Check if the entity would move into the potential collider, - if ((colliderTrans.y + colliderTrans.h > result.newY) && - // that it wasn't already colliding, - (colliderTrans.y + colliderTrans.h <= oldY) && - // that the position on the other axis is in range, - (colliderTrans.x + colliderTrans.w > result.newX) && - (colliderTrans.x < result.newX + transformable.w) && - // and that the collider is not farther away than the environmental - // boundary. - (!boundaryCollision || - (colliderTrans.y + colliderTrans.h >= it->first))) - { - colliders.push_back(collider); - } - } + inline static bool Closer(double left, double right) + { + return left < right; + } + }; - std::sort( - std::begin(colliders), - std::end(colliders), - [&] (id_type left, id_type right) { - auto& leftTrans = game_.getEntityManager(). - getComponent(left); + class Horizontal { + public: - auto& rightTrans = game_.getEntityManager(). - getComponent(right); + inline static double AxisOldLower( + const TransformableComponent& transformable) + { + return transformable.x; + } - return (rightTrans.y < leftTrans.y); - }); + inline static double AxisOldUpper( + const TransformableComponent& transformable) + { + return transformable.x + transformable.w; + } - for (id_type collider : colliders) - { - auto& colliderTrans = game_.getEntityManager(). - getComponent(collider); + inline static double AxisNewLower(const CollisionResult& result) + { + return result.newX; + } - // Check if the entity would still move into the potential collider. - if (colliderTrans.y + colliderTrans.h <= result.newY) - { - break; - } + inline static double AxisNewUpper( + const CollisionResult& result, + const TransformableComponent& transformable) + { + return result.newX + transformable.w; + } - auto& colliderPonder = game_.getEntityManager(). - getComponent(collider); + inline static double NonAxisOldLower( + const TransformableComponent& transformable) + { + return transformable.y; + } - processCollision( - entity, - collider, - Direction::up, - colliderPonder.colliderType, - colliderTrans.y + colliderTrans.h, - colliderTrans.x, - colliderTrans.x + colliderTrans.w, - result); + inline static double NonAxisOldUpper( + const TransformableComponent& transformable) + { + return transformable.y + transformable.h; + } - if (result.stopProcessing) - { - break; - } - } + inline static double NonAxisNewLower( + const CollisionResult& result, + const TransformableComponent& transformable) + { + return result.newY; + } - // If movement hasn't been stopped by an intermediary object, and - // collision checking hasn't been stopped, process the environmental - // boundaries closest to the entity. - if (!result.stopProcessing && !result.touchedWall && boundaryCollision) - { - double boundaryAxis = it->first; + inline static double NonAxisNewUpper( + const CollisionResult& result, + const TransformableComponent& transformable) + { + return result.newY + transformable.h; + } + }; - for (; - (it != std::end(mappable.upBoundaries)) && - (it->first == boundaryAxis); - it++) - { - if ((result.newX + transformable.w > it->second.lower) && - (result.newX < it->second.upper)) - { - processCollision( - entity, - mapEntity, - Direction::up, - it->second.type, - it->first, - it->second.lower, - it->second.upper, - result); - - if (result.stopProcessing) - { - break; - } - } - } - } - } else if (result.newY > oldY) + class Vertical { + public: + + inline static double AxisOldLower( + const TransformableComponent& transformable) { - bool boundaryCollision = false; - auto it = mappable.downBoundaries.lower_bound(oldBottom); + return transformable.y; + } - // Find the axis distance of the closest environmental boundary. - for (; - (it != std::end(mappable.downBoundaries)) - && (it->first <= (result.newY + transformable.h)); - it++) - { - // Check that the boundary is in range for the other axis. - if ((result.newX + transformable.w > it->second.lower) && - (result.newX < it->second.upper)) - { - // We have a collision! - boundaryCollision = true; + inline static double AxisOldUpper( + const TransformableComponent& transformable) + { + return transformable.y + transformable.h; + } - break; - } - } + inline static double AxisNewLower(const CollisionResult& result) + { + return result.newY; + } - // Find a list of potential colliders, sorted so that the closest is - // first. - std::vector colliders; + inline static double AxisNewUpper( + const CollisionResult& result, + const TransformableComponent& transformable) + { + return result.newY + transformable.h; + } - for (id_type collider : entities) - { - // Can't collide with self. - if (collider == entity) - { - continue; - } + inline static double NonAxisOldLower( + const TransformableComponent& transformable) + { + return transformable.x; + } - auto& colliderPonder = game_.getEntityManager(). - getComponent(collider); + inline static double NonAxisOldUpper( + const TransformableComponent& transformable) + { + return transformable.x + transformable.w; + } - // Only check objects that are active and collidable. - if (!colliderPonder.active || !colliderPonder.collidable) - { - continue; - } + inline static double NonAxisNewLower( + const CollisionResult& result, + const TransformableComponent& transformable) + { + return result.newX; + } - auto& colliderTrans = game_.getEntityManager(). - getComponent(collider); - - // Check if the entity would move into the potential collider, - if ((colliderTrans.y < result.newY + transformable.h) && - // that it wasn't already colliding, - (colliderTrans.y >= oldBottom) && - // that the position on the other axis is in range, - (colliderTrans.x + colliderTrans.w > result.newX) && - (colliderTrans.x < result.newX + transformable.w) && - // and that the collider is not farther away than the environmental - // boundary. - (!boundaryCollision || (colliderTrans.y <= it->first))) - { - colliders.push_back(collider); - } - } + inline static double NonAxisNewUpper( + const CollisionResult& result, + const TransformableComponent& transformable) + { + return result.newX + transformable.w; + } + }; - std::sort( - std::begin(colliders), - std::end(colliders), - [&] (id_type left, id_type right) { - auto& leftTrans = game_.getEntityManager(). - getComponent(left); + template + class DetectCollisions : public AscDesc { + public: - auto& rightTrans = game_.getEntityManager(). - getComponent(right); + static const Direction Dir = dir; + }; - return (leftTrans.y < rightTrans.y); - }); + class Left : public DetectCollisions> { + public: - for (id_type collider : colliders) - { - auto& colliderTrans = game_.getEntityManager(). - getComponent(collider); + inline static const MappableComponent::desc_boundaries_type& MapBoundaries( + const MappableComponent& mappable) + { + return mappable.leftBoundaries; + } + }; - // Check if the entity would still move into the potential collider. - if (colliderTrans.y >= result.newY + transformable.h) - { - break; - } + class Right : public DetectCollisions> { + public: - auto& colliderPonder = game_.getEntityManager(). - getComponent(collider); + inline static const MappableComponent::asc_boundaries_type& MapBoundaries( + const MappableComponent& mappable) + { + return mappable.rightBoundaries; + } + }; - processCollision( - entity, - collider, - Direction::down, - colliderPonder.colliderType, - colliderTrans.y, - colliderTrans.x, - colliderTrans.x + colliderTrans.w, - result); + class Up : public DetectCollisions> { + public: - if (result.stopProcessing) - { - break; - } - } + inline static const MappableComponent::desc_boundaries_type& MapBoundaries( + const MappableComponent& mappable) + { + return mappable.upBoundaries; + } + }; - // If movement hasn't been stopped by an intermediary object, and - // collision checking hasn't been stopped, process the environmental - // boundaries closest to the entity. - if (!result.stopProcessing && !result.touchedWall && boundaryCollision) - { - double boundaryAxis = it->first; + class Down : public DetectCollisions> { + public: - for (; - (it != std::end(mappable.downBoundaries)) && - (it->first == boundaryAxis); - it++) - { - if ((result.newX + transformable.w > it->second.lower) && - (result.newX < it->second.upper)) - { - processCollision( - entity, - mapEntity, - Direction::down, - it->second.type, - it->first, - it->second.lower, - it->second.upper, - result); - - if (result.stopProcessing) - { - break; - } - } - } - } + inline static const MappableComponent::asc_boundaries_type& MapBoundaries( + const MappableComponent& mappable) + { + return mappable.downBoundaries; } - } + }; +}; - // Move - if (!ponderable.frozen) +CollisionResult PonderingSystem::detectCollisions( + id_type entity, + double x, + double y) +{ + auto& transformable = game_.getEntityManager(). + getComponent(entity); + + CollisionResult result; + result.newX = x; + result.newY = transformable.y; + + // Find horizontal collisions. + if (result.newX < transformable.x) { - transformable.x = result.newX; - transformable.y = result.newY; + detectCollisionsInDirection(entity, result); + } else if (result.newX > transformable.x) + { + detectCollisionsInDirection(entity, result); } - // Perform cleanup for orientable entites - if (game_.getEntityManager().hasComponent(entity)) + // Find vertical collisions + if (!result.stopProcessing) { - auto& orientable = game_.getEntityManager(). - getComponent(entity); + result.newY = y; + result.touchedWall = false; - // Handle changes in groundedness - if (ponderable.grounded != oldGrounded) + if (result.newY < transformable.y) { - if (ponderable.grounded) - { - game_.getSystemManager().getSystem().land(entity); - } else { - game_.getSystemManager(). - getSystem().startFalling(entity); - } + detectCollisionsInDirection(entity, result); + } else if (result.newY > transformable.y) + { + detectCollisionsInDirection(entity, result); } + } - // Complete dropping, if necessary - if (orientable.getDropState() == OrientableComponent::DropState::active) + return result; +} + +template +void PonderingSystem::detectCollisionsInDirection( + id_type entity, + CollisionResult& result) +{ + // Get map data. + auto& realizable = game_.getEntityManager(). + getComponent( + game_.getSystemManager().getSystem().getSingleton()); + + id_type mapEntity = realizable.activeMap; + + auto& mappable = game_.getEntityManager(). + getComponent(mapEntity); + + // Get old location. + auto& transformable = game_.getEntityManager(). + getComponent(entity); + + bool boundaryCollision = false; + + auto boundaries = Param::MapBoundaries(mappable); + auto it = boundaries.lower_bound(Param::OldAxis(transformable)); + + // Find the axis distance of the closest environmental boundary. + for (; + (it != std::end(boundaries)) && + Param::AtLeastInAxisSweep( + it->first, + Param::NewAxis(result, transformable)); + it++) + { + // Check that the boundary is in range for the other axis. + if ((Param::NonAxisNewUpper(result, transformable) > it->second.lower) && + (Param::NonAxisNewLower(result, transformable) < it->second.upper)) { - orientable.setDropState(OrientableComponent::DropState::none); + // We have a collision! + boundaryCollision = true; + + break; } } - // Ferry or unferry as necessary - if ((ponderable.type == PonderableComponent::Type::freefalling) && - (ponderable.grounded != oldGrounded)) + // Find a list of potential colliders, sorted so that the closest is + // first. + std::vector colliders; + + auto entities = game_.getEntityManager().getEntitiesWithComponents< + PonderableComponent, + TransformableComponent>(); + + for (id_type collider : entities) { - if (ponderable.grounded && - game_.getEntityManager(). - hasComponent(result.groundEntity)) + // Can't collide with self. + if (collider == entity) { - // The body is now being ferried - auto& ferryPonder = game_.getEntityManager(). - getComponent(result.groundEntity); + continue; + } - ponderable.ferried = true; - ponderable.ferry = result.groundEntity; + auto& colliderPonder = game_.getEntityManager(). + getComponent(collider); - ferryPonder.passengers.insert(entity); - } else if (ponderable.ferried) + // Only check objects that are active and collidable. + if (!colliderPonder.active || !colliderPonder.collidable) { - // The body is no longer being ferried - unferry(entity); + continue; } - } - - // Update a ferry passenger's relative position - if (ponderable.ferried) - { - auto& ferryTrans = game_.getEntityManager(). - getComponent(ponderable.ferry); - ponderable.relX = transformable.x - ferryTrans.x; - ponderable.relY = transformable.y - ferryTrans.y; + auto& colliderTrans = game_.getEntityManager(). + getComponent(collider); + + // Check if the entity would move into the potential collider, + if (Param::IsPastAxis( + Param::ObjectAxis(colliderTrans), + Param::NewAxis(result, transformable)) && + // that it wasn't already colliding, + !Param::IsPastAxis( + Param::ObjectAxis(colliderTrans), + Param::OldAxis(transformable)) && + // that the position on the non-axis is in range, + (Param::NonAxisOldUpper(colliderTrans) > + Param::NonAxisNewLower(result, transformable)) && + (Param::NonAxisOldLower(colliderTrans) < + Param::NonAxisNewUpper(result, transformable)) && + // and that the collider is not farther away than the environmental + // boundary. + (!boundaryCollision || + Param::AtLeastInAxisSweep( + Param::ObjectAxis(colliderTrans), + it->first))) + { + colliders.push_back(collider); + } } - // Handle ferry passengers - std::set passengers = ponderable.passengers; + std::sort( + std::begin(colliders), + std::end(colliders), + [&] (id_type left, id_type right) { + auto& leftTrans = game_.getEntityManager(). + getComponent(left); - for (id_type passenger : passengers) - { - tickBody( - passenger, - dt, - entities); - } + auto& rightTrans = game_.getEntityManager(). + getComponent(right); - // Move to an adjacent map, if necessary - if (result.adjacentlyWarping) + return Param::Closer( + Param::ObjectAxis(leftTrans), + Param::ObjectAxis(rightTrans)); + }); + + for (id_type collider : colliders) { - double warpX = result.newX; - double warpY = result.newY; + auto& colliderTrans = game_.getEntityManager(). + getComponent(collider); - switch (result.adjWarpDir) + // Check if the entity would still move into the potential collider. + if (!Param::IsPastAxis( + Param::ObjectAxis(colliderTrans), + Param::NewAxis(result, transformable))) { - case Direction::left: - { - warpX = GAME_WIDTH + WALL_GAP - transformable.w; - - break; - } - - case Direction::right: - { - warpX = -WALL_GAP; + break; + } - break; - } + auto& colliderPonder = game_.getEntityManager(). + getComponent(collider); - case Direction::up: - { - warpY = MAP_HEIGHT * TILE_HEIGHT - transformable.h; + processCollision( + entity, + collider, + Param::Dir, + colliderPonder.colliderType, + Param::ObjectAxis(colliderTrans), + Param::NonAxisOldLower(colliderTrans), + Param::NonAxisOldUpper(colliderTrans), + result); + + if (result.stopProcessing) + { + break; + } + } - break; - } + // If movement hasn't been stopped by an intermediary object, and + // collision checking hasn't been stopped, process the environmental + // boundaries closest to the entity. + if (!result.stopProcessing && !result.touchedWall && boundaryCollision) + { + double boundaryAxis = it->first; - case Direction::down: + for (; + (it != std::end(boundaries)) && + (it->first == boundaryAxis); + it++) + { + if ((Param::NonAxisNewUpper(result, transformable) > it->second.lower) && + (Param::NonAxisNewLower(result, transformable) < it->second.upper)) { - warpY = -WALL_GAP; + processCollision( + entity, + mapEntity, + Param::Dir, + it->second.type, + it->first, + it->second.lower, + it->second.upper, + result); - break; + if (result.stopProcessing) + { + break; + } } } - - game_.getSystemManager().getSystem(). - changeMap( - entity, - result.adjWarpMapId, - warpX, - warpY); } } @@ -1012,7 +932,7 @@ void PonderingSystem::processCollision( result.newY = axis - transformable.h; result.groundEntity = collider; ponderable.velY = 0.0; - ponderable.grounded = true; + result.grounded = true; break; } 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 @@ #include "components/ponderable.h" #include "direction.h" +struct CollisionResult +{ + double newX; + double newY; + bool stopProcessing = false; + bool touchedWall = false; + bool adjacentlyWarping = false; + Direction adjWarpDir; + size_t adjWarpMapId; + bool grounded = false; + EntityManager::id_type groundEntity; +}; + class PonderingSystem : public System { public: @@ -34,23 +47,28 @@ public: private: - struct CollisionResult - { - double newX; - double newY; - bool stopProcessing = false; - bool touchedWall = false; - bool adjacentlyWarping = false; - Direction adjWarpDir; - size_t adjWarpMapId; - id_type groundEntity; - }; + void tickBody( id_type entity, double dt, const std::set& entities); + CollisionResult moveBody( + id_type entity, + double x, + double y); + + CollisionResult detectCollisions( + id_type entity, + double x, + double y); + + template + void detectCollisionsInDirection( + id_type entity, + CollisionResult& result); + void processCollision( id_type entity, id_type collider, -- cgit 1.4.1