diff --git a/Core/GameEngine/Include/Common/GameDefines.h b/Core/GameEngine/Include/Common/GameDefines.h index b562b51c40e..d688cc73ae4 100644 --- a/Core/GameEngine/Include/Common/GameDefines.h +++ b/Core/GameEngine/Include/Common/GameDefines.h @@ -45,6 +45,10 @@ #define RETAIL_COMPATIBLE_PATHFINDING_ALLOCATION (1) #endif +#ifndef RETAIL_COMPATIBLE_CIRCLE_FILL_ALGORITHM +#define RETAIL_COMPATIBLE_CIRCLE_FILL_ALGORITHM (1) // Use the original circle fill algorithm, which is more efficient but less accurate +#endif + // Disable non retail fixes in the networking, such as putting more data per UDP packet #ifndef RETAIL_COMPATIBLE_NETWORKING #define RETAIL_COMPATIBLE_NETWORKING (1) diff --git a/Generals/Code/GameEngine/Include/GameLogic/PartitionManager.h b/Generals/Code/GameEngine/Include/GameLogic/PartitionManager.h index 9e456505a8d..b3bcab29801 100644 --- a/Generals/Code/GameEngine/Include/GameLogic/PartitionManager.h +++ b/Generals/Code/GameEngine/Include/GameLogic/PartitionManager.h @@ -463,6 +463,11 @@ class PartitionData : public MemoryPoolObject Real radius ); + /** + A more advanced implementation of doCircleFill that is 100% accurate. + */ + void doCircleFillPrecise(Real centerX, Real centerY, Real radius); + /** fill in the pixels covered by the given rectangular shape with the given center, dimensions, and rotation. diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index 91e77aaf188..852009b0de6 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -371,6 +371,8 @@ static Bool distCalcProc_BoundaryAndBoundary_2D(const Coord3D *posA, const Objec static Bool distCalcProc_CenterAndCenter_3D(const Coord3D *posA, const Object *objA, const Coord3D *posB, const Object *objB, Real& abDistSqr, Coord3D& abVec, Real maxDistSqr); static Bool distCalcProc_BoundaryAndBoundary_3D(const Coord3D *posA, const Object *objA, const Coord3D *posB, const Object *objB, Real& abDistSqr, Coord3D& abVec, Real maxDistSqr); +static Bool doesCircleOverlapCell(Real centerX, Real centerY, Real radius, Real cellX, Real cellY, Real cellSize); + //----------------------------------------------------------------------------- inline void projectCoord3D(Coord3D *coord, const Coord3D *unitDir, Real dist) { @@ -1878,6 +1880,43 @@ void PartitionData::doCircleFill( } } +static Bool doesCircleOverlapCell(Real centerX, Real centerY, Real radius, Real cellX, Real cellY, Real cellSize) +{ + Real closestX = std::max(cellX, std::min(centerX, cellX + cellSize)); + Real closestY = std::max(cellY, std::min(centerY, cellY + cellSize)); + Real distX = centerX - closestX; + Real distY = centerY - closestY; + + return (sqr(distX) + sqr(distY)) < sqr(radius); +} + +void PartitionData::doCircleFillPrecise(Real centerX, Real centerY, Real radius) +{ + Int minCellX, minCellY, maxCellX, maxCellY; + ThePartitionManager->worldToCell(centerX - radius, centerY - radius, &minCellX, &minCellY); + ThePartitionManager->worldToCell(centerX + radius, centerY + radius, &maxCellX, &maxCellY); + + Real cellSize = ThePartitionManager->getCellSize(); + + for (Int x = minCellX; x <= maxCellX; ++x) + { + for (Int y = minCellY; y <= maxCellY; ++y) + { + Real cellWorldX = x * cellSize; + Real cellWorldY = y * cellSize; + + if (doesCircleOverlapCell(centerX, centerY, radius, cellWorldX, cellWorldY, cellSize)) + { + PartitionCell* cell = ThePartitionManager->getCellAt(x, y); + if (cell) + { + addSubPixToCoverage(cell); + } + } + } + } +} + // ----------------------------------------------------------------------------- void PartitionData::doSmallFill( Real centerX, @@ -2079,7 +2118,13 @@ void PartitionData::updateCellsTouched() case GEOMETRY_SPHERE: case GEOMETRY_CYLINDER: { +#if RETAIL_COMPATIBLE_CRC || RETAIL_COMPATIBLE_CIRCLE_FILL_ALGORITHM doCircleFill(pos.x, pos.y, majorRadius); +#else + // TheSuperHackers @bugfix Stubbjax 29/01/2026 Use precise circle fill to improve + // collision accuracy, most notably for objects with geometry radii >= 20 and < 40. + doCircleFillPrecise(pos.x, pos.y, majorRadius); +#endif break; } diff --git a/GeneralsMD/Code/GameEngine/Include/GameLogic/PartitionManager.h b/GeneralsMD/Code/GameEngine/Include/GameLogic/PartitionManager.h index a7da5ea1d2e..ac583cfd057 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameLogic/PartitionManager.h +++ b/GeneralsMD/Code/GameEngine/Include/GameLogic/PartitionManager.h @@ -464,6 +464,11 @@ class PartitionData : public MemoryPoolObject Real radius ); + /** + A more advanced implementation of doCircleFill that is 100% accurate. + */ + void doCircleFillPrecise(Real centerX, Real centerY, Real radius); + /** fill in the pixels covered by the given rectangular shape with the given center, dimensions, and rotation. diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index 3e957f3fd6e..8d7c8402e3a 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -375,6 +375,8 @@ static Bool distCalcProc_BoundaryAndBoundary_2D(const Coord3D *posA, const Objec static Bool distCalcProc_CenterAndCenter_3D(const Coord3D *posA, const Object *objA, const Coord3D *posB, const Object *objB, Real& abDistSqr, Coord3D& abVec, Real maxDistSqr); static Bool distCalcProc_BoundaryAndBoundary_3D(const Coord3D *posA, const Object *objA, const Coord3D *posB, const Object *objB, Real& abDistSqr, Coord3D& abVec, Real maxDistSqr); +static Bool doesCircleOverlapCell(Real centerX, Real centerY, Real radius, Real cellX, Real cellY, Real cellSize); + //----------------------------------------------------------------------------- inline void projectCoord3D(Coord3D *coord, const Coord3D *unitDir, Real dist) { @@ -1882,6 +1884,43 @@ void PartitionData::doCircleFill( } } +static Bool doesCircleOverlapCell(Real centerX, Real centerY, Real radius, Real cellX, Real cellY, Real cellSize) +{ + Real closestX = std::max(cellX, std::min(centerX, cellX + cellSize)); + Real closestY = std::max(cellY, std::min(centerY, cellY + cellSize)); + Real distX = centerX - closestX; + Real distY = centerY - closestY; + + return (sqr(distX) + sqr(distY)) < sqr(radius); +} + +void PartitionData::doCircleFillPrecise(Real centerX, Real centerY, Real radius) +{ + Int minCellX, minCellY, maxCellX, maxCellY; + ThePartitionManager->worldToCell(centerX - radius, centerY - radius, &minCellX, &minCellY); + ThePartitionManager->worldToCell(centerX + radius, centerY + radius, &maxCellX, &maxCellY); + + Real cellSize = ThePartitionManager->getCellSize(); + + for (Int x = minCellX; x <= maxCellX; ++x) + { + for (Int y = minCellY; y <= maxCellY; ++y) + { + Real cellWorldX = x * cellSize; + Real cellWorldY = y * cellSize; + + if (doesCircleOverlapCell(centerX, centerY, radius, cellWorldX, cellWorldY, cellSize)) + { + PartitionCell* cell = ThePartitionManager->getCellAt(x, y); + if (cell) + { + addSubPixToCoverage(cell); + } + } + } + } +} + // ----------------------------------------------------------------------------- void PartitionData::doSmallFill( Real centerX, @@ -2083,7 +2122,13 @@ void PartitionData::updateCellsTouched() case GEOMETRY_SPHERE: case GEOMETRY_CYLINDER: { +#if RETAIL_COMPATIBLE_CRC || RETAIL_COMPATIBLE_CIRCLE_FILL_ALGORITHM doCircleFill(pos.x, pos.y, majorRadius); +#else + // TheSuperHackers @bugfix Stubbjax 29/01/2026 Use precise circle fill to improve + // collision accuracy, most notably for objects with geometry radii >= 20 and < 40. + doCircleFillPrecise(pos.x, pos.y, majorRadius); +#endif break; }