diff --git a/rts/Rendering/Map/InfoTexture/Modern/Path.cpp b/rts/Rendering/Map/InfoTexture/Modern/Path.cpp index f28ec044fba..077dadb52ee 100644 --- a/rts/Rendering/Map/InfoTexture/Modern/Path.cpp +++ b/rts/Rendering/Map/InfoTexture/Modern/Path.cpp @@ -82,10 +82,14 @@ static inline const SColor& GetBuildColor(const BuildSquareStatus& status) { static SColor GetSpeedModColor(const float sm) { SColor col(255, 0, 0); + if (sm > 0.0f) { col.r = 255 - std::min(sm * 255.0f, 255.0f); col.g = 255 - col.r; + } else { + col.b = 255; } + return col; } diff --git a/rts/Sim/MoveTypes/MoveMath/MoveMath.cpp b/rts/Sim/MoveTypes/MoveMath/MoveMath.cpp index cd36b6f6c89..7d370644fc6 100644 --- a/rts/Sim/MoveTypes/MoveMath/MoveMath.cpp +++ b/rts/Sim/MoveTypes/MoveMath/MoveMath.cpp @@ -24,7 +24,7 @@ float CMoveMath::yLevel(const MoveDef& moveDef, int xSqr, int zSqr) case MoveDef::Tank: // fall-through case MoveDef::KBot: { return (CGround::GetHeightReal (xSqr * SQUARE_SIZE, zSqr * SQUARE_SIZE) + 10.0f); } break; case MoveDef::Hover: { return (CGround::GetHeightAboveWater(xSqr * SQUARE_SIZE, zSqr * SQUARE_SIZE) + 10.0f); } break; - case MoveDef::Ship: { return ( 0.0f); } break; + case MoveDef::Ship: { return ( 0.0f); } break; } return 0.0f; @@ -36,7 +36,7 @@ float CMoveMath::yLevel(const MoveDef& moveDef, const float3& pos) case MoveDef::Tank: // fall-through case MoveDef::KBot: { return (CGround::GetHeightReal (pos.x, pos.z) + 10.0f); } break; case MoveDef::Hover: { return (CGround::GetHeightAboveWater(pos.x, pos.z) + 10.0f); } break; - case MoveDef::Ship: { return ( 0.0f); } break; + case MoveDef::Ship: { return ( 0.0f); } break; } return 0.0f; @@ -108,10 +108,11 @@ float CMoveMath::GetPosSpeedMod(const MoveDef& moveDef, unsigned xSquare, unsign CMoveMath::BlockType CMoveMath::IsBlockedNoSpeedModCheck(const MoveDef& moveDef, int xSquare, int zSquare, const CSolidObject* collider) { const int xmin = xSquare - moveDef.xsizeh, xmax = xSquare + moveDef.xsizeh; - if ((unsigned)xmin >= mapDims.mapx || (unsigned)xmax >= mapDims.mapx) - return BLOCK_IMPASSABLE; const int zmin = zSquare - moveDef.zsizeh, zmax = zSquare + moveDef.zsizeh; - if ((unsigned)zmin >= mapDims.mapy || (unsigned)zmax >= mapDims.mapy) + + if (xmin < 0 || xmax >= mapDims.mapx) + return BLOCK_IMPASSABLE; + if (zmin < 0 || zmax >= mapDims.mapy) return BLOCK_IMPASSABLE; BlockType ret = BLOCK_NONE; @@ -122,7 +123,7 @@ CMoveMath::BlockType CMoveMath::IsBlockedNoSpeedModCheck(const MoveDef& moveDef, const int zOffset = z * mapDims.mapx; for (int x = xmin; x <= xmax; x += xstep) { const BlockingMapCell& cell = groundBlockingObjectMap->GetCellUnsafeConst(zOffset + x); - for (CSolidObject* collidee: cell) { + for (const CSolidObject* collidee: cell) { ret |= ObjectBlockType(moveDef, collidee, collider); if (ret & BLOCK_STRUCTURE) return ret; @@ -162,7 +163,7 @@ bool CMoveMath::IsNonBlocking(const MoveDef& colliderMD, const CSolidObject* col if (!collidee->IsBlocking()) return true; - if (collider != NULL) + if (collider != nullptr) return (IsNonBlocking(collidee, collider)); // remaining conditions under which obstacle does NOT block unit @@ -182,71 +183,72 @@ bool CMoveMath::IsNonBlocking(const MoveDef& colliderMD, const CSolidObject* col // // note that these condition(s) can lead to a certain degree of // clipping: for full 3D accuracy the height of the MoveDef's - // owner would need to be accessible (but the path-estimator - // defs aren't tied to any collider instances) --> add extra - // "clearance" parameter to MoveDef? + // owner would need to be accessible, but the path-estimator + // defs are not tied to any collider instances // - #define IS_SUBMARINE(md) ((md) != NULL && (md)->subMarine) - - if (IS_SUBMARINE(&colliderMD)) { - return (!collidee->IsUnderWater() && !IS_SUBMARINE(collidee->moveDef)); - } else { - return ( collidee->IsUnderWater() || IS_SUBMARINE(collidee->moveDef)); - } - + #define IS_SUBMARINE(md) ((md) != nullptr && (md)->subMarine) + const bool colliderIsSub = IS_SUBMARINE(&colliderMD); + const bool collideeIsSub = IS_SUBMARINE(collidee->moveDef); #undef IS_SUBMARINE - return false; + if (colliderIsSub) + return (!collidee->IsUnderWater() && !collideeIsSub); + + return (collidee->IsUnderWater() || collideeIsSub); } bool CMoveMath::IsNonBlocking(const CSolidObject* collidee, const CSolidObject* collider) { // simple case: if unit and obstacle have non-zero // vertical separation as measured by their (model) - // heights, unit can always pass obstacle + // heights, unit can in theory always pass obstacle + // + // this allows (units marked as) submarines to both + // *pass* and *short-range path* underneath floating + // DT, or ships to P&SRP over underwater structures + // + // specifically restricted to units *inside* water + // because it can have the unwanted side-effect of + // enabling the PFS to generate paths for units on + // steep slopes *through* obstacles, either higher + // up or lower down // - // note: in many cases separation is not sufficient - // even when it logically should be (submarines vs. - // floating DT in shallow water eg.) - // note: if unit and obstacle are on a steep slope, - // this can return true even when their horizontal - // separation points to a collision - if ((collider->pos.y + math::fabs(collider->height)) < collidee->pos.y) return true; - if ((collidee->pos.y + math::fabs(collidee->height)) < collider->pos.y) return true; + if ((collider->pos.y + math::fabs(collider->height)) < collidee->pos.y) + return (collider->IsInWater() && collidee->IsInWater()); + if ((collidee->pos.y + math::fabs(collidee->height)) < collider->pos.y) + return (collider->IsInWater() && collidee->IsInWater()); return false; } CMoveMath::BlockType CMoveMath::ObjectBlockType(const MoveDef& moveDef, const CSolidObject* collidee, const CSolidObject* collider) { - BlockType ret = BLOCK_NONE; if (IsNonBlocking(moveDef, collidee, collider)) - return ret; + return BLOCK_NONE; if (!collidee->immobile) { // mobile obstacle (must be a unit) --> if // moving, it is probably following a path - if (collidee->IsMoving()) { - ret = BLOCK_MOVING; - } else { - if ((static_cast(collidee))->IsIdle()) { - // idling (no orders) mobile unit - ret = BLOCK_MOBILE; - } else { - // busy mobile unit - ret = BLOCK_MOBILE_BUSY; - } - } - } else if (CrushResistant(moveDef, collidee)) { - ret = BLOCK_STRUCTURE; + if (collidee->IsMoving()) + return BLOCK_MOVING; + + // idling (no orders) mobile unit + if ((static_cast(collidee))->IsIdle()) + return BLOCK_MOBILE; + + // busy mobile unit + return BLOCK_MOBILE_BUSY; } - return ret; + if (CrushResistant(moveDef, collidee)) + return BLOCK_STRUCTURE; + + return BLOCK_NONE; } CMoveMath::BlockType CMoveMath::SquareIsBlocked(const MoveDef& moveDef, int xSquare, int zSquare, const CSolidObject* collider) { - if ((unsigned)xSquare >= mapDims.mapx || (unsigned)zSquare >= mapDims.mapy) + if (static_cast(xSquare) >= mapDims.mapx || static_cast(zSquare) >= mapDims.mapy) return BLOCK_IMPASSABLE; BlockType r = BLOCK_NONE; @@ -262,27 +264,31 @@ CMoveMath::BlockType CMoveMath::SquareIsBlocked(const MoveDef& moveDef, int xSqu CMoveMath::BlockType CMoveMath::RangeIsBlocked(const MoveDef& moveDef, int xmin, int xmax, int zmin, int zmax, const CSolidObject* collider) { - if ((unsigned)xmin >= mapDims.mapx || (unsigned)xmax >= mapDims.mapx) + if (xmin < 0 || xmax >= mapDims.mapx) return BLOCK_IMPASSABLE; - if ((unsigned)zmin >= mapDims.mapy || (unsigned)zmax >= mapDims.mapy) + if (zmin < 0 || zmax >= mapDims.mapy) return BLOCK_IMPASSABLE; BlockType ret = BLOCK_NONE; + const int xstep = 2, zstep = 2; const int tempNum = gs->GetTempNum(); // (footprints are point-symmetric around ) for (int z = zmin; z <= zmax; z += zstep) { const int zOffset = z * mapDims.mapx; + for (int x = xmin; x <= xmax; x += xstep) { const BlockingMapCell& cell = groundBlockingObjectMap->GetCellUnsafeConst(zOffset + x); + for (CSolidObject* collidee: cell) { if (collidee->tempNum == tempNum) continue; collidee->tempNum = tempNum; ret |= ObjectBlockType(moveDef, collidee, collider); + if (ret & BLOCK_STRUCTURE) return ret; } diff --git a/rts/Sim/Path/Default/PathConstants.h b/rts/Sim/Path/Default/PathConstants.h index acb876c3434..1529331c3af 100644 --- a/rts/Sim/Path/Default/PathConstants.h +++ b/rts/Sim/Path/Default/PathConstants.h @@ -26,7 +26,7 @@ static const float MEDRES_SEARCH_DISTANCE_EXT = (MEDRES_SEARCH_DISTANCE * 0.4f) // how many recursive refinement attempts NextWayPoint should make static const unsigned int MAX_PATH_REFINEMENT_DEPTH = 4; -static const unsigned int PATHESTIMATOR_VERSION = 82; +static const unsigned int PATHESTIMATOR_VERSION = 83; static const unsigned int MEDRES_PE_BLOCKSIZE = 16; static const unsigned int LOWRES_PE_BLOCKSIZE = 32; diff --git a/rts/Sim/Path/Default/PathFinder.cpp b/rts/Sim/Path/Default/PathFinder.cpp index 82750e03c0d..543480ed466 100644 --- a/rts/Sim/Path/Default/PathFinder.cpp +++ b/rts/Sim/Path/Default/PathFinder.cpp @@ -16,25 +16,26 @@ #include "Sim/Misc/GeometricObjects.h" -#define PATHDEBUG 0 +#define ENABLE_PATH_DEBUG 0 +#define ENABLE_DIAG_TESTS 1 using namespace Bitwise; -const CMoveMath::BlockType squareMobileBlockBits = (CMoveMath::BLOCK_MOBILE | CMoveMath::BLOCK_MOVING | CMoveMath::BLOCK_MOBILE_BUSY); +static const CMoveMath::BlockType squareMobileBlockBits = (CMoveMath::BLOCK_MOBILE | CMoveMath::BLOCK_MOVING | CMoveMath::BLOCK_MOBILE_BUSY); +static const CPathFinder::BlockCheckFunc blockCheckFuncs[2] = { + CMoveMath::IsBlockedNoSpeedModCheckThreadUnsafe, + CMoveMath::IsBlockedNoSpeedModCheck +}; -// indexed by PATHOPT* bitmasks +// both indexed by PATHOPT* bitmasks static float3 PF_DIRECTION_VECTORS_3D[PATH_DIRECTIONS << 1]; -static float PF_DIRECTION_COSTS[PATH_DIRECTIONS << 1]; +static float PF_DIRECTION_COSTS[PATH_DIRECTIONS << 1]; CPathFinder::CPathFinder(bool threadSafe): IPathFinder(1) { - static const BlockCheckFunc funcs[2] = { - CMoveMath::IsBlockedNoSpeedModCheckThreadUnsafe, - CMoveMath::IsBlockedNoSpeedModCheck - }; - blockCheckFunc = funcs[threadSafe]; + blockCheckFunc = blockCheckFuncs[threadSafe]; dummyCacheItem = CPathCache::CacheItem{IPath::Error, {}, {-1, -1}, {-1, -1}, -1.0f, -1}; } @@ -88,6 +89,12 @@ IPath::SearchResult CPathFinder::DoSearch( break; } + if (!pfDef.WithinConstraints(openSquare->nodePos.x, openSquare->nodePos.y)) { + blockStates.nodeMask[openSquare->nodeNum] |= PATHOPT_CLOSED; + dirtyBlocks.push_back(openSquare->nodeNum); + continue; + } + TestNeighborSquares(moveDef, pfDef, openSquare, owner); } @@ -112,74 +119,67 @@ void CPathFinder::TestNeighborSquares( const PathNode* square, const CSolidObject* owner ) { - // early out - if (!pfDef.WithinConstraints(square->nodePos.x, square->nodePos.y)) { - blockStates.nodeMask[square->nodeNum] |= PATHOPT_CLOSED; - dirtyBlocks.push_back(square->nodeNum); - return; - } - - struct SqState { - SqState() : blockedState(CMoveMath::BLOCK_NONE), speedMod(0.0f), inSearch(false) {} - CMoveMath::BlockType blockedState; - float speedMod; - bool inSearch; + struct SquareState { + CMoveMath::BlockType blockMask = CMoveMath::BLOCK_IMPASSABLE; + float speedMod = 0.0f; + bool inSearch = false; }; - SqState ngbStates[PATH_DIRECTIONS]; + SquareState ngbStates[PATH_DIRECTIONS]; - // precompute structure-blocked for all neighbors + // precompute structure-blocked state and speedmod for all neighbors for (unsigned int dir = 0; dir < PATH_DIRECTIONS; dir++) { - const int2 ngbSquareCoors = square->nodePos + PF_DIRECTION_VECTORS_2D[ PathDir2PathOpt(dir) ]; - const int ngbSquareIdx = BlockPosToIdx(ngbSquareCoors); + const unsigned int optDir = PathDir2PathOpt(dir); + const int2 ngbSquareCoors = square->nodePos + PF_DIRECTION_VECTORS_2D[optDir]; + const unsigned int ngbSquareIdx = BlockPosToIdx(ngbSquareCoors); - if ((unsigned)ngbSquareCoors.x >= nbrOfBlocks.x || (unsigned)ngbSquareCoors.y >= nbrOfBlocks.y) + if (static_cast(ngbSquareCoors.x) >= nbrOfBlocks.x || static_cast(ngbSquareCoors.y) >= nbrOfBlocks.y) continue; if (blockStates.nodeMask[ngbSquareIdx] & (PATHOPT_CLOSED | PATHOPT_BLOCKED)) //FIXME continue; - // very time expensive call - SqState& sqState = ngbStates[dir]; - sqState.blockedState = blockCheckFunc(moveDef, ngbSquareCoors.x, ngbSquareCoors.y, owner); + SquareState& sqState = ngbStates[dir]; - if (sqState.blockedState & CMoveMath::BLOCK_STRUCTURE) { + // IsBlockedNoSpeedModCheck; very expensive call + if ((sqState.blockMask = blockCheckFunc(moveDef, ngbSquareCoors.x, ngbSquareCoors.y, owner)) & CMoveMath::BLOCK_STRUCTURE) { blockStates.nodeMask[ngbSquareIdx] |= PATHOPT_CLOSED; dirtyBlocks.push_back(ngbSquareIdx); continue; // early-out (20% chance) } - // hint: use posSpeedMod for PE! cause it assumes path costs are bidirectional and so it only saves one `cost` for left & right movement - // hint2: not worth to put in front of the above code, it only has a ~2% chance (heavily depending on the map) to early-out + if (!pfDef.dirIndependent) { - sqState.speedMod = CMoveMath::GetPosSpeedMod(moveDef, ngbSquareCoors.x, ngbSquareCoors.y, PF_DIRECTION_VECTORS_3D[ PathDir2PathOpt(dir) ]); + sqState.speedMod = CMoveMath::GetPosSpeedMod(moveDef, ngbSquareCoors.x, ngbSquareCoors.y, PF_DIRECTION_VECTORS_3D[optDir]); } else { - sqState.speedMod = CMoveMath::GetPosSpeedMod(moveDef, ngbSquareCoors.x, ngbSquareCoors.y); - // only close node if we're dirIndependent - // otherwise, it's possible we'll be able to enter it from another direction. - if (sqState.speedMod == 0.0f) { + // PE search; use positional speed-mods since PE assumes path-costs + // are bidirectionally symmetric between parent and child vertices + // no gain placing this in front of the above code, only has a ~2% + // chance (heavily depending on the map) to early-out + // + // only close node if search is directionally independent, otherwise + // it is possible we might enter it from another (better) direction + if ((sqState.speedMod = CMoveMath::GetPosSpeedMod(moveDef, ngbSquareCoors.x, ngbSquareCoors.y)) == 0.0f) { blockStates.nodeMask[ngbSquareIdx] |= PATHOPT_CLOSED; dirtyBlocks.push_back(ngbSquareIdx); } } - if (sqState.speedMod != 0.0f) { - sqState.inSearch = pfDef.WithinConstraints(ngbSquareCoors.x, ngbSquareCoors.y); - } + + sqState.inSearch = (sqState.speedMod != 0.0f && pfDef.WithinConstraints(ngbSquareCoors.x, ngbSquareCoors.y)); } - const auto CAN_TEST_SQUARE_SM = [&](const int dir) { return (ngbStates[dir].speedMod != 0.0f); }; - const auto CAN_TEST_SQUARE_IS = [&](const int dir) { return (ngbStates[dir].inSearch); }; + const auto CanTestSquareSM = [&](const int dir) { return (ngbStates[dir].speedMod != 0.0f); }; + const auto CanTestSquareIS = [&](const int dir) { return (ngbStates[dir].inSearch); }; // first test squares along the cardinal directions for (unsigned int dir: PATHDIR_CARDINALS) { - if (!CAN_TEST_SQUARE_SM(dir)) + if (!CanTestSquareSM(dir)) continue; - const SqState& sqState = ngbStates[dir]; - const unsigned int opt = PathDir2PathOpt(dir); - TestBlock(moveDef, pfDef, square, owner, opt, sqState.blockedState, sqState.speedMod); + TestBlock(moveDef, pfDef, square, owner, PathDir2PathOpt(dir), ngbStates[dir].blockMask, ngbStates[dir].speedMod); } + #if ENABLE_DIAG_TESTS // next test the diagonal squares // // don't search diagonally if there is a blocking object @@ -199,20 +199,20 @@ void CPathFinder::TestNeighborSquares( // edge passable since we still need to be able to jump to // diagonally adjacent PE-blocks! // - const auto TEST_DIAG_SQUARE = [&](const int BASE_DIR_X, const int BASE_DIR_Y, const int BASE_DIR_XY) { - if (CAN_TEST_SQUARE_SM(BASE_DIR_XY) && (CAN_TEST_SQUARE_SM(BASE_DIR_X) && CAN_TEST_SQUARE_SM(BASE_DIR_Y))) { - if (CAN_TEST_SQUARE_IS(BASE_DIR_XY) || (CAN_TEST_SQUARE_IS(BASE_DIR_X) && CAN_TEST_SQUARE_IS(BASE_DIR_Y))) { - const unsigned int ngbOpt = PathDir2PathOpt(BASE_DIR_XY); - const SqState& sqState = ngbStates[BASE_DIR_XY]; - TestBlock(moveDef, pfDef, square, owner, ngbOpt, sqState.blockedState, sqState.speedMod); - } - } + const auto TestDiagSquare = [&](const int dirX, const int dirY, const int dirXY) { + if (!CanTestSquareSM(dirXY) || !CanTestSquareSM(dirX) || !CanTestSquareSM(dirY)) + return; + if (!CanTestSquareIS(dirXY) && (!CanTestSquareIS(dirX) || !CanTestSquareIS(dirY))) + return; + + TestBlock(moveDef, pfDef, square, owner, PathDir2PathOpt(dirXY), ngbStates[dirXY].blockMask, ngbStates[dirXY].speedMod); }; - TEST_DIAG_SQUARE(PATHDIR_LEFT, PATHDIR_UP, PATHDIR_LEFT_UP ); - TEST_DIAG_SQUARE(PATHDIR_RIGHT, PATHDIR_UP, PATHDIR_RIGHT_UP ); - TEST_DIAG_SQUARE(PATHDIR_LEFT, PATHDIR_DOWN, PATHDIR_LEFT_DOWN ); - TEST_DIAG_SQUARE(PATHDIR_RIGHT, PATHDIR_DOWN, PATHDIR_RIGHT_DOWN); + TestDiagSquare(PATHDIR_LEFT, PATHDIR_UP, PATHDIR_LEFT_UP ); + TestDiagSquare(PATHDIR_RIGHT, PATHDIR_UP, PATHDIR_RIGHT_UP ); + TestDiagSquare(PATHDIR_LEFT, PATHDIR_DOWN, PATHDIR_LEFT_DOWN ); + TestDiagSquare(PATHDIR_RIGHT, PATHDIR_DOWN, PATHDIR_RIGHT_DOWN); + #endif // mark this square as closed blockStates.nodeMask[square->nodeNum] |= PATHOPT_CLOSED; @@ -235,8 +235,8 @@ bool CPathFinder::TestBlock( const unsigned int sqrIdx = BlockPosToIdx(square); // bounds-check - assert((unsigned)square.x < nbrOfBlocks.x); - assert((unsigned)square.y < nbrOfBlocks.y); + assert(static_cast(square.x) < nbrOfBlocks.x); + assert(static_cast(square.y) < nbrOfBlocks.y); assert((blockStates.nodeMask[sqrIdx] & (PATHOPT_CLOSED | PATHOPT_BLOCKED)) == 0); assert((blockStatus & CMoveMath::BLOCK_STRUCTURE) == 0); assert(speedMod != 0.0f); @@ -360,14 +360,14 @@ IPath::SearchResult CPathFinder::FinishSearch(const MoveDef& moveDef, const CPat /** Helper function for SmoothMidWaypoint */ static inline void FixupPath3Pts(const MoveDef& moveDef, const float3 p1, float3& p2, const float3 p3) { -#if PATHDEBUG +#if ENABLE_PATH_DEBUG float3 old = p2; #endif p2.x = 0.5f * (p1.x + p3.x); p2.z = 0.5f * (p1.z + p3.z); p2.y = CMoveMath::yLevel(moveDef, p2); -#if PATHDEBUG +#if ENABLE_PATH_DEBUG geometricObjects->AddLine(old + UpVector * 10.0f, p2 + UpVector * 10.0f, 5, 10, 600, 0); #endif } diff --git a/rts/Sim/Path/Default/PathFinder.h b/rts/Sim/Path/Default/PathFinder.h index 61dc04fdcde..7463b70a2f3 100644 --- a/rts/Sim/Path/Default/PathFinder.h +++ b/rts/Sim/Path/Default/PathFinder.h @@ -17,7 +17,6 @@ struct MoveDef; class CPathFinderDef; - class CPathFinder: public IPathFinder { public: CPathFinder(bool threadSafe = true); @@ -28,6 +27,8 @@ class CPathFinder: public IPathFinder { static const int2* GetDirectionVectorsTable2D(); static const float3* GetDirectionVectorsTable3D(); + typedef CMoveMath::BlockType (*BlockCheckFunc)(const MoveDef&, int, int, const CSolidObject*); + protected: // IPathFinder impl /// Performs the actual search. IPath::SearchResult DoSearch(const MoveDef& moveDef, const CPathFinderDef& pfDef, const CSolidObject* owner); @@ -99,8 +100,6 @@ class CPathFinder: public IPathFinder { IPath::Path& foundPath ) const; - typedef CMoveMath::BlockType (*BlockCheckFunc)(const MoveDef&, int, int, const CSolidObject*); - BlockCheckFunc blockCheckFunc; CPathCache::CacheItem dummyCacheItem; };