diff --git a/CHANGELOG.md b/CHANGELOG.md index 6805cd9598..1965e0d91e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -193,6 +193,7 @@ Bug #8132: Actors without hello responses turn to face the player Bug #8171: Items with more than 100% health can be repaired Bug #8172: Openmw-cs crashes when viewing `Dantooine, Sea` + Bug #8187: Intervention effects should use Chebyshev distance to determine the closest marker Feature #1415: Infinite fall failsafe Feature #2566: Handle NAM9 records for manual cell references Feature #3501: OpenMW-CS: Instance Editing - Shortcuts for axial locking diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 07da51b4f3..84f4a6de2c 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -3257,22 +3257,71 @@ namespace MWWorld MWWorld::ConstPtr World::getClosestMarkerFromExteriorPosition(const osg::Vec3f& worldPos, const ESM::RefId& id) { - MWWorld::ConstPtr closestMarker; - float closestDistance = std::numeric_limits::max(); + const ESM::ExteriorCellLocation posIndex = ESM::positionToExteriorCellLocation(worldPos.x(), worldPos.y()); - std::vector markers; + // Potential optimization: don't scan the entire world for markers and actually do the Todd spiral + std::vector markers; mWorldModel.getExteriorPtrs(id, markers); + + struct MarkerInfo + { + Ptr mPtr; + int mColumn, mRow; // Local coordinates in the valid marker grid + }; + std::vector validMarkers; + validMarkers.reserve(markers.size()); + + // The idea is to collect all markers that belong to the smallest possible square grid around worldPos + // They are grouped with their position on that grid's edge where the origin is the SW corner + int minGridSize = std::numeric_limits::max(); for (const Ptr& marker : markers) { - osg::Vec3f markerPos = marker.getRefData().getPosition().asVec3(); - float distance = (worldPos - markerPos).length2(); - if (distance < closestDistance) + const osg::Vec3f markerPos = marker.getRefData().getPosition().asVec3(); + const ESM::ExteriorCellLocation index = ESM::positionToExteriorCellLocation(markerPos.x(), markerPos.y()); + + const int deltaX = index.mX - posIndex.mX; + const int deltaY = index.mY - posIndex.mY; + const int gridSize = std::max(std::abs(deltaX), std::abs(deltaY)) * 2; + if (gridSize == 0) + return marker; + + if (gridSize <= minGridSize) { - closestDistance = distance; - closestMarker = marker; + if (gridSize < minGridSize) + { + validMarkers.clear(); + minGridSize = gridSize; + } + validMarkers.push_back({ marker, gridSize / 2 + deltaX, gridSize / 2 + deltaY }); } } + ConstPtr closestMarker; + if (validMarkers.empty()) + return closestMarker; + if (validMarkers.size() == 1) + return validMarkers[0].mPtr; + + // All the markers are on the edge of the grid + // Break ties by picking the earliest marker on SW -> SE -> NE -> NW -> SW path + int earliestDistance = std::numeric_limits::max(); + for (const MarkerInfo& marker : validMarkers) + { + int distance = 0; + if (marker.mRow == 0) // South edge (plus SW and SE corners) + distance = marker.mColumn; + else if (marker.mColumn == minGridSize) // East edge and NE corner + distance = minGridSize + marker.mRow; + else if (marker.mRow == minGridSize) // North edge and NW corner + distance = minGridSize * 3 - marker.mColumn; + else // West edge + distance = minGridSize * 4 - marker.mRow; + if (distance < earliestDistance) + { + closestMarker = marker.mPtr; + earliestDistance = distance; + } + } return closestMarker; }