From 4e4ef9052ebac129b2e792d6fd458df1e29f45d2 Mon Sep 17 00:00:00 2001 From: Alexander Harkness Date: Mon, 4 May 2020 21:04:21 +0000 Subject: Refactor minecart collision detection code. (#4712) * Refactor minecart collision detection code. - Use new GetBoundingBox function. - Handle descending and ascending rails. - Snap to descending rails. * Add message for UNREACHABLE --- src/Entities/Minecart.cpp | 384 +++++++++++++++++++++++++++++++++------------- src/Entities/Minecart.h | 5 + 2 files changed, 280 insertions(+), 109 deletions(-) diff --git a/src/Entities/Minecart.cpp b/src/Entities/Minecart.cpp index 8b93dc28c..2463200c7 100644 --- a/src/Entities/Minecart.cpp +++ b/src/Entities/Minecart.cpp @@ -158,6 +158,16 @@ void cMinecart::HandlePhysics(std::chrono::milliseconds a_Dt, cChunk & a_Chunk) // Push cart upwards AddPosY(1); } + else + { + // When a minecart gets to a descending rail, it should go down. + chunk->GetBlockTypeMeta(relPos.addedY(-1), InsideType, InsideMeta); + if (IsBlockRail(InsideType)) + { + // Push cart downwards + AddPosY(-1); + } + } } bool WasDetectorRail = false; @@ -231,7 +241,8 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, std::chrono::millisecon SetSpeedX(0); // Correct diagonal movement from curved rails // Execute both the entity and block collision checks - bool BlckCol = TestBlockCollision(a_RailMeta), EntCol = TestEntityCollision(a_RailMeta); + auto BlckCol = TestBlockCollision(a_RailMeta); + auto EntCol = TestEntityCollision(a_RailMeta); if (EntCol || BlckCol) { return; @@ -250,7 +261,8 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, std::chrono::millisecon ApplyAcceleration({ 0.0, 0.0, -1.0 }, -0.1); } } - break; + + return; } case E_META_RAIL_XM_XP: // EASTWEST { @@ -259,7 +271,8 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, std::chrono::millisecon SetSpeedY(NO_SPEED); SetSpeedZ(NO_SPEED); - bool BlckCol = TestBlockCollision(a_RailMeta), EntCol = TestEntityCollision(a_RailMeta); + auto BlckCol = TestBlockCollision(a_RailMeta); + auto EntCol = TestEntityCollision(a_RailMeta); if (EntCol || BlckCol) { return; @@ -276,13 +289,21 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, std::chrono::millisecon ApplyAcceleration({ -1.0, 0.0, 0.0 }, -0.1); } } - break; + + return; } case E_META_RAIL_ASCEND_ZM: // ASCEND NORTH { SetYaw(270); SetSpeedX(0); + auto BlckCol = TestBlockCollision(a_RailMeta); + auto EntCol = TestEntityCollision(a_RailMeta); + if (EntCol || BlckCol) + { + return; + } + if (GetSpeedZ() >= 0) { // SpeedZ POSITIVE, going SOUTH @@ -295,13 +316,21 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, std::chrono::millisecon AddSpeedZ(1); // Slow down SetSpeedY(-GetSpeedZ()); // Upward movement is positive (0 minus negative number is positive number) } - break; + + return; } case E_META_RAIL_ASCEND_ZP: // ASCEND SOUTH { SetYaw(270); SetSpeedX(0); + auto BlckCol = TestBlockCollision(a_RailMeta); + auto EntCol = TestEntityCollision(a_RailMeta); + if (EntCol || BlckCol) + { + return; + } + if (GetSpeedZ() > 0) { // SpeedZ POSITIVE, going SOUTH @@ -314,13 +343,21 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, std::chrono::millisecon AddSpeedZ(-0.5); // Speed up SetSpeedY(GetSpeedZ()); // Downward movement negative } - break; + + return; } case E_META_RAIL_ASCEND_XM: // ASCEND EAST { SetYaw(180); SetSpeedZ(NO_SPEED); + auto BlckCol = TestBlockCollision(a_RailMeta); + auto EntCol = TestEntityCollision(a_RailMeta); + if (EntCol || BlckCol) + { + return; + } + if (GetSpeedX() >= NO_SPEED) { AddSpeedX(0.5); @@ -331,13 +368,21 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, std::chrono::millisecon AddSpeedX(1); SetSpeedY(-GetSpeedX()); } - break; + + return; } case E_META_RAIL_ASCEND_XP: // ASCEND WEST { SetYaw(180); SetSpeedZ(0); + auto BlckCol = TestBlockCollision(a_RailMeta); + auto EntCol = TestEntityCollision(a_RailMeta); + if (EntCol || BlckCol) + { + return; + } + if (GetSpeedX() > 0) { AddSpeedX(-1); @@ -348,7 +393,8 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, std::chrono::millisecon AddSpeedX(-0.5); SetSpeedY(GetSpeedX()); } - break; + + return; } case E_META_RAIL_CURVED_ZM_XM: // Ends pointing NORTH and WEST { @@ -356,12 +402,16 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, std::chrono::millisecon SetPosY(floor(GetPosY()) + 0.55); // Levitate dat cart SetSpeedY(0); - TestBlockCollision(a_RailMeta); - TestEntityCollision(a_RailMeta); + auto BlckCol = TestBlockCollision(a_RailMeta); + auto EntCol = TestEntityCollision(a_RailMeta); + if (EntCol || BlckCol) + { + return; + } // SnapToRail handles turning - break; + return; } case E_META_RAIL_CURVED_ZM_XP: // Curved NORTH EAST { @@ -369,10 +419,14 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, std::chrono::millisecon SetPosY(floor(GetPosY()) + 0.55); SetSpeedY(0); - TestBlockCollision(a_RailMeta); - TestEntityCollision(a_RailMeta); + auto BlckCol = TestBlockCollision(a_RailMeta); + auto EntCol = TestEntityCollision(a_RailMeta); + if (EntCol || BlckCol) + { + return; + } - break; + return; } case E_META_RAIL_CURVED_ZP_XM: // Curved SOUTH WEST { @@ -380,10 +434,14 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, std::chrono::millisecon SetPosY(floor(GetPosY()) + 0.55); SetSpeedY(0); - TestBlockCollision(a_RailMeta); - TestEntityCollision(a_RailMeta); + auto BlckCol = TestBlockCollision(a_RailMeta); + auto EntCol = TestEntityCollision(a_RailMeta); + if (EntCol || BlckCol) + { + return; + } - break; + return; } case E_META_RAIL_CURVED_ZP_XP: // Curved SOUTH EAST { @@ -391,17 +449,17 @@ void cMinecart::HandleRailPhysics(NIBBLETYPE a_RailMeta, std::chrono::millisecon SetPosY(floor(GetPosY()) + 0.55); SetSpeedY(0); - TestBlockCollision(a_RailMeta); - TestEntityCollision(a_RailMeta); + auto BlckCol = TestBlockCollision(a_RailMeta); + auto EntCol = TestEntityCollision(a_RailMeta); + if (EntCol || BlckCol) + { + return; + } - break; - } - default: - { - ASSERT(!"Unhandled rail meta!"); // Dun dun DUN! - break; + return; } } + UNREACHABLE("Unsupported rail meta type"); } @@ -724,14 +782,39 @@ void cMinecart::SnapToRail(NIBBLETYPE a_RailMeta) +bool cMinecart::IsSolidBlockAtPosition(Vector3i a_Pos) +{ + BLOCKTYPE Block = m_World->GetBlock(a_Pos); + return !IsBlockRail(Block) && cBlockInfo::IsSolid(Block); +} + + + + + bool cMinecart::IsSolidBlockAtOffset(int a_XOffset, int a_YOffset, int a_ZOffset) { - BLOCKTYPE Block = m_World->GetBlock(POSX_TOINT + a_XOffset, POSY_TOINT + a_YOffset, POSZ_TOINT + a_ZOffset); - if (IsBlockRail(Block) || !cBlockInfo::IsSolid(Block)) + return IsSolidBlockAtPosition({POSX_TOINT + a_XOffset, POSY_TOINT + a_YOffset, POSZ_TOINT + a_ZOffset}); +} + + + + + +bool cMinecart::IsBlockCollisionAtOffset(Vector3i a_Offset) +{ + auto BlockPosition = GetPosition().Floor() + a_Offset; + if (!IsSolidBlockAtPosition(BlockPosition)) { return false; } - return true; + + auto bbBlock = cBoundingBox( + static_cast(BlockPosition), + static_cast(BlockPosition + Vector3i(1, 1, 1)) + ); + + return GetBoundingBox().DoesIntersect(bbBlock); } @@ -740,135 +823,218 @@ bool cMinecart::IsSolidBlockAtOffset(int a_XOffset, int a_YOffset, int a_ZOffset bool cMinecart::TestBlockCollision(NIBBLETYPE a_RailMeta) { + auto SpeedX = GetSpeedX(); + auto SpeedZ = GetSpeedZ(); + + // Don't do anything if minecarts aren't moving. + #ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wfloat-equal" + #endif + + if ((SpeedX == 0) && (SpeedZ == 0)) + { + return false; + } + + #ifdef __clang__ + #pragma clang diagnostic pop + #endif + + auto StopTheCart = true; + auto StopOffset = Vector3d(); + switch (a_RailMeta) { case E_META_RAIL_ZM_ZP: { - if (GetSpeedZ() > 0) + if (SpeedZ > 0) { - if (IsSolidBlockAtOffset(0, 0, 1)) - { - // We could try to detect a block in front based purely on coordinates, but xoft made a bounding box system - why not use? :P - cBoundingBox bbBlock(Vector3d(POSX_TOINT, POSY_TOINT, static_cast(ceil(GetPosZ()))), 0.5, 1); - cBoundingBox bbMinecart(Vector3d(GetPosX(), floor(GetPosY()), GetPosZ()), GetWidth() / 2, GetHeight()); - - if (bbBlock.DoesIntersect(bbMinecart)) - { - SetSpeed(0, 0, 0); - SetPosZ(floor(GetPosZ()) + 0.4); - return true; - } - } + StopOffset = Vector3d(0, 0, 0.4); + StopTheCart = IsBlockCollisionAtOffset({0, 0, 1}); } - else if (GetSpeedZ() < 0) + else // SpeedZ < 0 { - if (IsSolidBlockAtOffset(0, 0, -1)) - { - cBoundingBox bbBlock(Vector3d(POSX_TOINT, POSY_TOINT, POSZ_TOINT - 1), 0.5, 1); - cBoundingBox bbMinecart(Vector3d(GetPosX(), floor(GetPosY()), GetPosZ() - 1), GetWidth() / 2, GetHeight()); - - if (bbBlock.DoesIntersect(bbMinecart)) - { - SetSpeed(0, 0, 0); - SetPosZ(floor(GetPosZ()) + 0.65); - return true; - } - } + StopTheCart = IsBlockCollisionAtOffset({0, 0, -1}); + StopOffset = Vector3d(0, 0, 0.65); } break; } case E_META_RAIL_XM_XP: { - if (GetSpeedX() > 0) + if (SpeedX > 0) { - if (IsSolidBlockAtOffset(1, 0, 0)) - { - cBoundingBox bbBlock(Vector3d(static_cast(ceil(GetPosX())), POSY_TOINT, POSZ_TOINT), 0.5, 1); - cBoundingBox bbMinecart(Vector3d(GetPosX(), floor(GetPosY()), GetPosZ()), GetWidth() / 2, GetHeight()); - - if (bbBlock.DoesIntersect(bbMinecart)) - { - SetSpeed(0, 0, 0); - SetPosX(floor(GetPosX()) + 0.4); - return true; - } - } + StopTheCart = IsBlockCollisionAtOffset({1, 0, 0}); + StopOffset = Vector3d(0.4, 0, 0); } - else if (GetSpeedX() < 0) + else // SpeedX < 0 { - if (IsSolidBlockAtOffset(-1, 0, 0)) - { - cBoundingBox bbBlock(Vector3d(POSX_TOINT - 1, POSY_TOINT, POSZ_TOINT), 0.5, 1); - cBoundingBox bbMinecart(Vector3d(GetPosX() - 1, floor(GetPosY()), GetPosZ()), GetWidth() / 2, GetHeight()); - - if (bbBlock.DoesIntersect(bbMinecart)) - { - SetSpeed(0, 0, 0); - SetPosX(floor(GetPosX()) + 0.65); - return true; - } - } + StopTheCart = IsBlockCollisionAtOffset({-1, 0, 0}); + StopOffset = Vector3d(0.65, 0, 0); } break; } - case E_META_RAIL_CURVED_ZM_XM: + + // Ascending rails check for one block on the way up, two on the way down. + case E_META_RAIL_ASCEND_XM: { - bool IsBlockXM = IsSolidBlockAtOffset(-1, 0, 0); - bool IsBlockZM = IsSolidBlockAtOffset(0, 0, -1); + StopOffset = Vector3d(0.5, 0, 0); - if (((GetSpeedZ() < 0) && IsBlockZM) || ((GetSpeedX() < 0) && IsBlockXM)) + if (SpeedX < 0) { - SetSpeed(0, 0, 0); - SetPosition(POSX_TOINT + 0.5, GetPosY(), POSZ_TOINT + 0.5); - return true; + StopTheCart = IsBlockCollisionAtOffset({-1, 1, 0}); } + else // SpeedX > 0 + { + StopTheCart = IsBlockCollisionAtOffset({1, 0, 0}) || IsBlockCollisionAtOffset({1, 1, 0}); + } + break; + } + case E_META_RAIL_ASCEND_XP: + { + StopOffset = Vector3d(0.5, 0, 0); + if (SpeedX > 0) + { + StopTheCart = IsBlockCollisionAtOffset({1, 1, 0}); + } + else // SpeedX < 0 + { + StopTheCart = IsBlockCollisionAtOffset({-1, 0, 0}) || IsBlockCollisionAtOffset({-1, 1, 0}); + } break; } - case E_META_RAIL_CURVED_ZM_XP: + case E_META_RAIL_ASCEND_ZM: + { + StopOffset = Vector3d(0, 0, 0.5); + + if (SpeedZ < 0) + { + StopTheCart = IsBlockCollisionAtOffset({0, 1, -1}); + } + else // SpeedZ > 0 + { + StopTheCart = IsBlockCollisionAtOffset({0, 0, 1}) || IsBlockCollisionAtOffset({0, 1, 1}); + } + break; + } + case E_META_RAIL_ASCEND_ZP: + { + StopOffset = Vector3d(0, 0, 0.5); + + if (SpeedZ > 0) + { + StopTheCart = IsBlockCollisionAtOffset({0, 1, 1}); + } + else // SpeedZ < 0 + { + StopTheCart = IsBlockCollisionAtOffset({0, 0, -1}) || IsBlockCollisionAtOffset({0, 1, -1}); + } + break; + } + + // Curved rails allow movement across both the x and z axes. But when the cart is + // moving towards one of the rail endpoints, it will always have velocity towards + // the direction of that endpoint in the same axis. + case E_META_RAIL_CURVED_ZP_XP: { - bool IsBlockXP = IsSolidBlockAtOffset(1, 0, 0); - bool IsBlockZM = IsSolidBlockAtOffset(0, 0, -1); + StopOffset = Vector3d(0.5, 0, 0.5); - if (((GetSpeedZ() < 0) && IsBlockZM) || ((GetSpeedX() > 0) && IsBlockXP)) + if (SpeedZ > 0) { - SetSpeed(0, 0, 0); - SetPosition(POSX_TOINT + 0.5, GetPosY(), POSZ_TOINT + 0.5); - return true; + StopTheCart = IsBlockCollisionAtOffset({0, 0, 1}); + break; + } + if (SpeedX > 0) + { + StopTheCart = IsBlockCollisionAtOffset({1, 0, 0}); + break; } break; + UNREACHABLE("Invalid minecart movement"); } case E_META_RAIL_CURVED_ZP_XM: { - bool IsBlockXM = IsSolidBlockAtOffset(-1, 0, 0); - bool IsBlockZP = IsSolidBlockAtOffset(0, 0, +1); + StopOffset = Vector3d(0.5, 0, 0.5); - if (((GetSpeedZ() > 0) && IsBlockZP) || ((GetSpeedX() < 0) && IsBlockXM)) + if (SpeedZ > 0) { - SetSpeed(0, 0, 0); - SetPosition(POSX_TOINT + 0.5, GetPosY(), POSZ_TOINT + 0.5); - return true; + StopTheCart = IsBlockCollisionAtOffset({0, 0, 1}); + break; + } + if (SpeedX < 0) + { + StopTheCart = IsBlockCollisionAtOffset({-1, 0, 0}); + break; } break; + UNREACHABLE("Invalid minecart movement"); } - case E_META_RAIL_CURVED_ZP_XP: + case E_META_RAIL_CURVED_ZM_XM: { - bool IsBlockXP = IsSolidBlockAtOffset(1, 0, 0); - bool IsBlockZP = IsSolidBlockAtOffset(0, 0, 1); + StopOffset = Vector3d(0.5, 0, 0.5); - if (((GetSpeedZ() > 0) && IsBlockZP) || ((GetSpeedX() > 0) && IsBlockXP)) + if (SpeedZ < 0) { - SetSpeed(0, 0, 0); - SetPosition(POSX_TOINT + 0.5, GetPosY(), POSZ_TOINT + 0.5); - return true; + StopTheCart = IsBlockCollisionAtOffset({0, 0, -1}); + break; + } + if (SpeedX < 0) + { + StopTheCart = IsBlockCollisionAtOffset({-1, 0, 0}); + break; } break; + UNREACHABLE("Invalid minecart movement"); } - default: break; + case E_META_RAIL_CURVED_ZM_XP: + { + StopOffset = Vector3d(0.5, 0, 0.5); + + if (SpeedZ < 0) + { + StopTheCart = IsBlockCollisionAtOffset({0, 0, -1}); + break; + } + if (SpeedX > 0) + { + StopTheCart = IsBlockCollisionAtOffset({1, 0, 0}); + break; + } + + break; + UNREACHABLE("Invalid minecart movement"); + } + } + + if (StopTheCart) + { + SetSpeed(0, 0, 0); + + #ifdef __clang__ + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wfloat-equal" + #endif + + if (StopOffset.x != 0) + { + SetPosX(POSX_TOINT + StopOffset.x); + } + if (StopOffset.z != 0) + { + SetPosZ(POSZ_TOINT + StopOffset.z); + } + + #ifdef __clang__ + #pragma clang diagnostic pop + #endif + + return true; } + return false; } diff --git a/src/Entities/Minecart.h b/src/Entities/Minecart.h index fe9ad8ce8..ad3b3d40d 100644 --- a/src/Entities/Minecart.h +++ b/src/Entities/Minecart.h @@ -83,8 +83,13 @@ protected: void SnapToRail(NIBBLETYPE a_RailMeta); /** Tests if a solid block is in front of a cart, and stops the cart (and returns true) if so; returns false if no obstruction */ bool TestBlockCollision(NIBBLETYPE a_RailMeta); + /** Tests if there is a block at the specified position which is impassable to minecarts */ + bool IsSolidBlockAtPosition(Vector3i a_Offset); /** Tests if a solid block is at a specific offset of the minecart position */ bool IsSolidBlockAtOffset(int a_XOffset, int a_YOffset, int a_ZOffset); + + bool IsBlockCollisionAtOffset(Vector3i a_Offset); + /** Tests if this mincecart's bounding box is intersecting another entity's bounding box (collision) and pushes mincecart away if necessary */ bool TestEntityCollision(NIBBLETYPE a_RailMeta); -- cgit v1.2.3