From 7c3d28dfc639be151793baa207e84509c1e94b04 Mon Sep 17 00:00:00 2001 From: Stubbjax Date: Thu, 29 Jan 2026 02:13:49 +1100 Subject: [PATCH 1/3] bugfix: Resolve inaccurate circle fill logic in the partition manager --- Core/GameEngine/Include/Common/GameDefines.h | 4 ++ .../Include/GameLogic/PartitionManager.h | 5 +++ .../GameLogic/Object/PartitionManager.cpp | 43 +++++++++++++++++++ .../Include/GameLogic/PartitionManager.h | 5 +++ .../GameLogic/Object/PartitionManager.cpp | 43 +++++++++++++++++++ 5 files changed, 100 insertions(+) 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..7cd5908ecf1 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -1878,6 +1878,43 @@ void PartitionData::doCircleFill( } } +static Bool doesCircleOverlapCell(Real centerX, Real centerY, Real radius, Real cellX, Real cellY, Real cellSize) +{ + Real closestX = maxReal(cellX, minReal(centerX, cellX + cellSize)); + Real closestY = maxReal(cellY, minReal(centerY, cellY + cellSize)); + Real distX = centerX - closestX; + Real distY = centerY - closestY; + + return (distX * distX + distY * distY) < radius * 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 +2116,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..74c18adfa8d 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -1882,6 +1882,43 @@ void PartitionData::doCircleFill( } } +static Bool doesCircleOverlapCell(Real centerX, Real centerY, Real radius, Real cellX, Real cellY, Real cellSize) +{ + Real closestX = maxReal(cellX, minReal(centerX, cellX + cellSize)); + Real closestY = maxReal(cellY, minReal(centerY, cellY + cellSize)); + Real distX = centerX - closestX; + Real distY = centerY - closestY; + + return (distX * distX + distY * distY) < radius * 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 +2120,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; } From dc18156943185a204dad32a8b397205f68b0355b Mon Sep 17 00:00:00 2001 From: Stubbjax Date: Fri, 27 Feb 2026 18:11:54 +1100 Subject: [PATCH 2/3] refactor: Streamline function usage --- .../GameEngine/Source/GameLogic/Object/PartitionManager.cpp | 6 +++--- .../GameEngine/Source/GameLogic/Object/PartitionManager.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index 7cd5908ecf1..92caf8b438b 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -1880,12 +1880,12 @@ void PartitionData::doCircleFill( static Bool doesCircleOverlapCell(Real centerX, Real centerY, Real radius, Real cellX, Real cellY, Real cellSize) { - Real closestX = maxReal(cellX, minReal(centerX, cellX + cellSize)); - Real closestY = maxReal(cellY, minReal(centerY, cellY + 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 (distX * distX + distY * distY) < radius * radius; + return (sqr(distX) + sqr(distY)) < sqr(radius); } void PartitionData::doCircleFillPrecise(Real centerX, Real centerY, Real radius) diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index 74c18adfa8d..e5ee9ebd536 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp @@ -1884,12 +1884,12 @@ void PartitionData::doCircleFill( static Bool doesCircleOverlapCell(Real centerX, Real centerY, Real radius, Real cellX, Real cellY, Real cellSize) { - Real closestX = maxReal(cellX, minReal(centerX, cellX + cellSize)); - Real closestY = maxReal(cellY, minReal(centerY, cellY + 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 (distX * distX + distY * distY) < radius * radius; + return (sqr(distX) + sqr(distY)) < sqr(radius); } void PartitionData::doCircleFillPrecise(Real centerX, Real centerY, Real radius) From 10ec1154e556ce127d9e512323a8eec08d09c405 Mon Sep 17 00:00:00 2001 From: Stubbjax Date: Sat, 28 Feb 2026 03:02:19 +1100 Subject: [PATCH 3/3] refactor: add utility function declarations --- .../GameEngine/Source/GameLogic/Object/PartitionManager.cpp | 2 ++ .../GameEngine/Source/GameLogic/Object/PartitionManager.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/Generals/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index 92caf8b438b..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) { diff --git a/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp b/GeneralsMD/Code/GameEngine/Source/GameLogic/Object/PartitionManager.cpp index e5ee9ebd536..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) {