Merge branch 'localmap' into 'master'

Local map fixes

See merge request OpenMW/openmw!4919
This commit is contained in:
Evil Eye 2025-09-20 15:33:30 +00:00
commit 4d5b65a9cb
5 changed files with 108 additions and 80 deletions

View File

@ -635,7 +635,6 @@ namespace MWGui
mSpellBox->setUserData(MyGUI::Any::Null);
mActiveCell = nullptr;
mHasALastActiveCell = false;
}
void HUD::customMarkerCreated(MyGUI::Widget* marker)

View File

@ -38,7 +38,7 @@
namespace
{
const int cellSize = Constants::CellSizeInUnits;
constexpr int cellSize = Constants::CellSizeInUnits;
constexpr float speed = 1.08f; // the zoom speed, it should be greater than 1
enum LocalMapWidgetDepth
@ -57,7 +57,10 @@ namespace
Global_ExploreOverlayLayer = 2,
Global_MapLayer = 3
};
}
namespace MWGui
{
/// @brief A widget that changes its color when hovered.
class MarkerWidget final : public MyGUI::Widget
{
@ -108,10 +111,6 @@ namespace
{
scrollView->setCanvasSize(widgetSize * (grid.width() + 1), widgetSize * (grid.height() + 1));
}
}
namespace MWGui
{
void CustomMarkerCollection::addMarker(const ESM::CustomMarker& marker, bool triggerEvent)
{
@ -217,7 +216,6 @@ namespace MWGui
mLocalMap = widget;
mCompass = compass;
mGrid = createRect({ 0, 0 }, cellDistance);
mExtCellDistance = cellDistance;
const int mapWidgetSize = Settings::map().mLocalMapWidgetSize;
setCanvasSize(mLocalMap, mGrid, mapWidgetSize);
@ -293,7 +291,7 @@ namespace MWGui
return MyGUI::IntCoord(position.left - halfMarkerSize, position.top - halfMarkerSize, markerSize, markerSize);
}
MyGUI::Widget* LocalMapBase::createDoorMarker(const std::string& name, float x, float y) const
MarkerWidget* LocalMapBase::createDoorMarker(const std::string& name, float x, float y) const
{
MarkerUserData data(mLocalMapRender);
data.caption = name;
@ -320,14 +318,15 @@ namespace MWGui
mLocalMap->setViewOffset(viewOffset);
}
MyGUI::IntCoord LocalMapBase::getMarkerCoordinates(MyGUI::Widget* widget, size_t markerSize) const
void LocalMapBase::updateMarkerCoordinates(MyGUI::Widget* widget, size_t markerSize) const
{
MarkerUserData& markerPos(*widget->getUserData<MarkerUserData>());
auto position = getPosition(markerPos.cellX, markerPos.cellY, markerPos.nX, markerPos.nY);
return MyGUI::IntCoord(position.left - markerSize / 2, position.top - markerSize / 2, markerSize, markerSize);
MyGUI::IntCoord coord(position.left - markerSize / 2, position.top - markerSize / 2, markerSize, markerSize);
widget->setCoord(coord);
}
std::vector<MyGUI::Widget*>& LocalMapBase::currentDoorMarkersWidgets()
std::vector<MarkerWidget*>& LocalMapBase::currentDoorMarkersWidgets()
{
return mActiveCell->isExterior() ? mExteriorDoorMarkerWidgets : mInteriorDoorMarkerWidgets;
}
@ -380,43 +379,73 @@ namespace MWGui
if (&cell == mActiveCell)
return; // don't do anything if we're still in the same cell
// Remove all interior door markers
mDoorMarkersToRecycle.insert(
mDoorMarkersToRecycle.end(), mInteriorDoorMarkerWidgets.begin(), mInteriorDoorMarkerWidgets.end());
for (MarkerWidget* widget : mInteriorDoorMarkerWidgets)
widget->setVisible(false);
mInteriorDoorMarkerWidgets.clear();
const int x = cell.getGridX();
const int y = cell.getGridY();
MyGUI::IntSize oldSize{ mGrid.width(), mGrid.height() };
const MyGUI::IntRect prevGrid = mGrid;
if (cell.isExterior())
{
mGrid = createRect({ x, y }, mExtCellDistance);
std::optional<MyGUI::IntRect> previousActiveGrid;
if (mActiveCell && mActiveCell->isExterior())
previousActiveGrid
= createRect({ mActiveCell->getGridX(), mActiveCell->getGridY() }, Constants::CellGridRadius);
mGrid = createRect({ x, y }, getLocalViewingDistance());
const MyGUI::IntRect activeGrid = createRect({ x, y }, Constants::CellGridRadius);
mExteriorDoorMarkerWidgets.clear();
for (auto& [coord, doors] : mExteriorDoorsByCell)
for (auto it = mExteriorDoorsByCell.begin(); it != mExteriorDoorsByCell.end();)
{
if (!mHasALastActiveCell || !mGrid.inside({ coord.first, coord.second })
|| activeGrid.inside({ coord.first, coord.second }))
const auto& [coord, doors] = *it;
const MyGUI::IntPoint pos(coord.first, coord.second);
// Remove markers that fall outside the rendered map and ones that are new to the active grid.
// Scripts can enable/disable doors, requiring us to update the markers. Morrowind.exe only updates
// markers when a cell is added to the active grid.
if (!mGrid.inside(pos)
|| (previousActiveGrid && !previousActiveGrid->inside(pos) && activeGrid.inside(pos)))
{
mDoorMarkersToRecycle.insert(mDoorMarkersToRecycle.end(), doors.begin(), doors.end());
doors.clear();
for (MarkerWidget* widget : doors)
widget->setVisible(false);
it = mExteriorDoorsByCell.erase(it);
}
else
{
mExteriorDoorMarkerWidgets.insert(mExteriorDoorMarkerWidgets.end(), doors.begin(), doors.end());
++it;
}
}
for (auto& widget : mDoorMarkersToRecycle)
widget->setVisible(false);
if (mHasALastActiveCell)
if (mActiveCell && mActiveCell->isExterior())
{
for (const auto& entry : mMaps)
for (int cx = prevGrid.left; cx <= prevGrid.right; ++cx)
{
if (!mGrid.inside({ entry.mCellX, entry.mCellY }))
mLocalMapRender->removeExteriorCell(entry.mCellX, entry.mCellY);
for (int cy = prevGrid.top; cy <= prevGrid.bottom; ++cy)
{
if (!mGrid.inside({ cx, cy }))
mLocalMapRender->removeExteriorCell(cx, cy);
}
}
}
}
else
{
mGrid = mLocalMapRender->getInteriorGrid();
// Remove all exterior door markers
mDoorMarkersToRecycle.insert(
mDoorMarkersToRecycle.end(), mExteriorDoorMarkerWidgets.begin(), mExteriorDoorMarkerWidgets.end());
for (MarkerWidget* widget : mExteriorDoorMarkerWidgets)
widget->setVisible(false);
mExteriorDoorMarkerWidgets.clear();
mExteriorDoorsByCell.clear();
}
mActiveCell = &cell;
@ -452,18 +481,15 @@ namespace MWGui
resetEntry(mMaps[i], false, nullptr);
}
if (oldSize != MyGUI::IntSize{ mGrid.width(), mGrid.height() })
setCanvasSize(mLocalMap, mGrid, getWidgetSize());
if (prevGrid.width() != mGrid.width() || prevGrid.height() != mGrid.height())
updateLocalMap();
// Delay the door markers update until scripts have been given a chance to run.
// If we don't do this, door markers that should be disabled will still appear on the map.
mNeedDoorMarkersUpdate = true;
for (MyGUI::Widget* widget : currentDoorMarkersWidgets())
widget->setCoord(getMarkerCoordinates(widget, 8));
if (mActiveCell->isExterior())
mHasALastActiveCell = true;
updateMarkerCoordinates(widget, 8);
updateMagicMarkers();
updateCustomMarkers();
@ -588,7 +614,7 @@ namespace MWGui
bool needRedraw = false;
for (MapEntry& entry : mMaps)
{
if (widgetCropped(entry.mMapWidget, mLocalMap))
if (!entry.mMapWidget->getVisible() || widgetCropped(entry.mMapWidget, mLocalMap))
continue;
if (!entry.mMapTexture)
@ -623,6 +649,9 @@ namespace MWGui
entry.mFogTexture = std::make_unique<MyGUIPlatform::OSGTexture>(std::string(), nullptr);
}
needRedraw = true;
// Newly uncovered chunk, make sure to draw door markers right away instead of waiting for a cell
// transition
mNeedDoorMarkersUpdate = true;
}
}
if (needRedraw)
@ -635,15 +664,8 @@ namespace MWGui
MWBase::World* world = MWBase::Environment::get().getWorld();
MWWorld::WorldModel* worldModel = MWBase::Environment::get().getWorldModel();
mDoorMarkersToRecycle.insert(
mDoorMarkersToRecycle.end(), mInteriorDoorMarkerWidgets.begin(), mInteriorDoorMarkerWidgets.end());
mInteriorDoorMarkerWidgets.clear();
if (!mActiveCell->isExterior())
{
for (MyGUI::Widget* widget : mExteriorDoorMarkerWidgets)
widget->setVisible(false);
MWWorld::CellStore& cell = worldModel->getInterior(mActiveCell->getNameId());
world->getDoorMarkers(cell, doors);
}
@ -651,13 +673,13 @@ namespace MWGui
{
for (MapEntry& entry : mMaps)
{
if (!entry.mMapTexture && !widgetCropped(entry.mMapWidget, mLocalMap))
world->getDoorMarkers(worldModel->getExterior(ESM::ExteriorCellLocation(
entry.mCellX, entry.mCellY, ESM::Cell::sDefaultWorldspaceId)),
doors);
if (!entry.mMapWidget->getVisible() || widgetCropped(entry.mMapWidget, mLocalMap))
continue;
if (mExteriorDoorsByCell.contains({ entry.mCellX, entry.mCellY }))
continue;
ESM::ExteriorCellLocation id(entry.mCellX, entry.mCellY, ESM::Cell::sDefaultWorldspaceId);
world->getDoorMarkers(worldModel->getExterior(id), doors);
}
if (doors.empty())
return;
}
// Create a widget for each marker
@ -665,11 +687,10 @@ namespace MWGui
{
std::vector<std::string> destNotes;
CustomMarkerCollection::RangeType markers = mCustomMarkers.getMarkers(marker.dest);
for (CustomMarkerCollection::ContainerType::const_iterator iter = markers.first; iter != markers.second;
++iter)
for (auto iter = markers.first; iter != markers.second; ++iter)
destNotes.push_back(iter->second.mNote);
MyGUI::Widget* markerWidget = nullptr;
MarkerWidget* markerWidget = nullptr;
MarkerUserData* data;
if (mDoorMarkersToRecycle.empty())
{
@ -680,7 +701,7 @@ namespace MWGui
}
else
{
markerWidget = (MarkerWidget*)mDoorMarkersToRecycle.back();
markerWidget = mDoorMarkersToRecycle.back();
mDoorMarkersToRecycle.pop_back();
data = markerWidget->getUserData<MarkerUserData>();
@ -695,8 +716,8 @@ namespace MWGui
mExteriorDoorsByCell[{ data->cellX, data->cellY }].push_back(markerWidget);
}
for (auto& widget : mDoorMarkersToRecycle)
widget->setVisible(false);
for (MyGUI::Widget* widget : currentDoorMarkersWidgets())
updateMarkerCoordinates(widget, 8);
}
void LocalMapBase::updateMagicMarkers()
@ -738,6 +759,8 @@ namespace MWGui
const auto size = MyGUI::IntSize(std::ceil(mapWidgetSize), std::ceil(mapWidgetSize));
for (auto& entry : mMaps)
{
if (!entry.mMapWidget->getVisible())
continue;
const auto position = getPosition(entry.mCellX, entry.mCellY, 0, 0);
entry.mMapWidget->setCoord({ position, size });
entry.mFogWidget->setCoord({ position, size });
@ -745,7 +768,7 @@ namespace MWGui
MarkerUserData markerPos(mLocalMapRender);
for (MyGUI::Widget* widget : currentDoorMarkersWidgets())
widget->setCoord(getMarkerCoordinates(widget, 8));
updateMarkerCoordinates(widget, 8);
for (MyGUI::Widget* widget : mCustomMarkerWidgets)
{
@ -754,7 +777,7 @@ namespace MWGui
}
for (MyGUI::Widget* widget : mMagicMarkerWidgets)
widget->setCoord(getMarkerCoordinates(widget, 8));
updateMarkerCoordinates(widget, 8);
}
// ------------------------------------------------------------------------------------------
@ -1368,8 +1391,7 @@ namespace MWGui
NoDrop::setAlpha(alpha);
// can't allow showing map with partial transparency, as the fog of war will also go transparent
// and reveal parts of the map you shouldn't be able to see
for (MapEntry& entry : mMaps)
entry.mMapWidget->setVisible(alpha == 1);
mLocalMap->setVisible(alpha == 1);
}
void MapWindow::customMarkerCreated(MyGUI::Widget* marker)

View File

@ -43,6 +43,7 @@ namespace SceneUtil
namespace MWGui
{
class MarkerWidget;
class CustomMarkerCollection
{
@ -119,7 +120,6 @@ namespace MWGui
MyGUI::ScrollView* mLocalMap = nullptr;
MyGUI::ImageBox* mCompass = nullptr;
float mLocalMapZoom = 1.f;
bool mHasALastActiveCell = false;
bool mFogOfWarToggled = true;
bool mFogOfWarEnabled;
bool mNeedDoorMarkersUpdate = false;
@ -147,14 +147,14 @@ namespace MWGui
std::vector<MapEntry> mMaps;
// Keep track of created marker widgets, just to easily remove them later.
std::vector<MyGUI::Widget*> mExteriorDoorMarkerWidgets;
std::map<std::pair<int, int>, std::vector<MyGUI::Widget*>> mExteriorDoorsByCell;
std::vector<MyGUI::Widget*> mInteriorDoorMarkerWidgets;
std::vector<MarkerWidget*> mExteriorDoorMarkerWidgets;
std::map<std::pair<int, int>, std::vector<MarkerWidget*>> mExteriorDoorsByCell;
std::vector<MarkerWidget*> mInteriorDoorMarkerWidgets;
std::vector<MyGUI::Widget*> mMagicMarkerWidgets;
std::vector<MyGUI::Widget*> mCustomMarkerWidgets;
std::vector<MyGUI::Widget*> mDoorMarkersToRecycle;
std::vector<MarkerWidget*> mDoorMarkersToRecycle;
std::vector<MyGUI::Widget*>& currentDoorMarkersWidgets();
std::vector<MarkerWidget*>& currentDoorMarkersWidgets();
virtual void updateCustomMarkers();
@ -164,12 +164,11 @@ namespace MWGui
MyGUI::IntPoint getMarkerPosition(float worldX, float worldY, MarkerUserData& markerPos) const;
MyGUI::IntCoord getMarkerCoordinates(
float worldX, float worldY, MarkerUserData& markerPos, size_t markerSize) const;
MyGUI::Widget* createDoorMarker(const std::string& name, float x, float y) const;
MyGUI::IntCoord getMarkerCoordinates(MyGUI::Widget* widget, size_t markerSize) const;
MarkerWidget* createDoorMarker(const std::string& name, float x, float y) const;
void updateMarkerCoordinates(MyGUI::Widget* widget, size_t markerSize) const;
virtual void notifyPlayerUpdate() {}
virtual void centerView();
virtual void notifyMapChanged() {}
virtual void customMarkerCreated(MyGUI::Widget* marker) {}
virtual void doorMarkerCreated(MyGUI::Widget* marker) {}
@ -185,7 +184,6 @@ namespace MWGui
MWGui::LocalMapBase::MapEntry& addMapEntry();
MyGUI::IntRect mGrid{ -1, -1, 1, 1 };
int mExtCellDistance = 0;
float mMarkerUpdateTimer = 0.f;
float mLastDirectionX = 0.f;

View File

@ -96,7 +96,7 @@ namespace MWRender
mRoot->removeChild(rtt);
}
const osg::Vec2f LocalMap::rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle)
const osg::Vec2f LocalMap::rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle) const
{
return osg::Vec2f(
std::cos(angle) * (point.x() - center.x()) - std::sin(angle) * (point.y() - center.y()) + center.x(),
@ -109,12 +109,15 @@ namespace MWRender
mInteriorSegments.clear();
}
void LocalMap::saveFogOfWar(MWWorld::CellStore* cell)
void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) const
{
if (!mInterior)
{
const MapSegment& segment
= mExteriorSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())];
const auto it
= mExteriorSegments.find(std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY()));
if (it == mExteriorSegments.end())
return;
const MapSegment& segment = it->second;
if (segment.mFogOfWarImage && segment.mHasFogState)
{
@ -146,7 +149,10 @@ namespace MWRender
{
for (int y = 0; y < segments.second; ++y)
{
const MapSegment& segment = mInteriorSegments[std::make_pair(x, y)];
const auto it = mInteriorSegments.find(std::make_pair(x, y));
if (it == mInteriorSegments.end())
continue;
const MapSegment& segment = it->second;
if (!segment.mHasFogState)
continue;
ESM::FogTexture& texture = fog->mFogTextures.emplace_back();
@ -186,7 +192,8 @@ namespace MWRender
MapSegment& segment = mExteriorSegments[std::make_pair(cellX, cellY)];
const std::uint8_t neighbourFlags = getExteriorNeighbourFlags(cellX, cellY);
if ((segment.mLastRenderNeighbourFlags & neighbourFlags) == neighbourFlags)
if (segment.mLastRenderNeighbourFlags != 0
&& (segment.mLastRenderNeighbourFlags & neighbourFlags) == neighbourFlags)
return;
requestExteriorMap(cell, segment);
segment.mLastRenderNeighbourFlags = neighbourFlags;
@ -208,9 +215,7 @@ namespace MWRender
{
saveFogOfWar(cell);
if (cell->isExterior())
mExteriorSegments.erase({ cell->getCell()->getGridX(), cell->getCell()->getGridY() });
else
if (!cell->isExterior())
mInteriorSegments.clear();
}
@ -299,6 +304,7 @@ namespace MWRender
return;
mInterior = true;
mExteriorSegments.clear();
mBounds = bounds;
@ -422,7 +428,7 @@ namespace MWRender
}
}
void LocalMap::worldToInteriorMapPosition(osg::Vec2f pos, float& nX, float& nY, int& x, int& y)
void LocalMap::worldToInteriorMapPosition(osg::Vec2f pos, float& nX, float& nY, int& x, int& y) const
{
pos = rotatePoint(pos, mCenter, mAngle);
@ -435,7 +441,7 @@ namespace MWRender
nY = 1.0f - (pos.y() - min.y() - mMapWorldSize * y) / mMapWorldSize;
}
osg::Vec2f LocalMap::interiorMapToWorldPosition(float nX, float nY, int x, int y)
osg::Vec2f LocalMap::interiorMapToWorldPosition(float nX, float nY, int x, int y) const
{
osg::Vec2f min(mBounds.xMin(), mBounds.yMin());
osg::Vec2f pos(mMapWorldSize * (nX + x) + min.x(), mMapWorldSize * (1.0f - nY + y) + min.y());
@ -571,8 +577,11 @@ namespace MWRender
};
std::uint8_t result = 0;
for (const auto& [flag, dx, dy] : flags)
if (mExteriorSegments.contains(std::pair(cellX + dx, cellY + dy)))
{
auto it = mExteriorSegments.find(std::pair(cellX + dx, cellY + dy));
if (it != mExteriorSegments.end() && it->second.mMapTexture)
result |= flag;
}
return result;
}

View File

@ -83,14 +83,14 @@ namespace MWRender
* Save the fog of war for this cell to its CellStore.
* @remarks This should be called when unloading a cell, and for all active cells prior to saving the game.
*/
void saveFogOfWar(MWWorld::CellStore* cell);
void saveFogOfWar(MWWorld::CellStore* cell) const;
/**
* Get the interior map texture index and normalized position on this texture, given a world position
*/
void worldToInteriorMapPosition(osg::Vec2f pos, float& nX, float& nY, int& x, int& y);
void worldToInteriorMapPosition(osg::Vec2f pos, float& nX, float& nY, int& x, int& y) const;
osg::Vec2f interiorMapToWorldPosition(float nX, float nY, int x, int y);
osg::Vec2f interiorMapToWorldPosition(float nX, float nY, int x, int y) const;
/**
* Check if a given position is explored by the player (i.e. not obscured by fog of war)
@ -149,7 +149,7 @@ namespace MWRender
int mCellDistance;
float mAngle;
const osg::Vec2f rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle);
const osg::Vec2f rotatePoint(const osg::Vec2f& point, const osg::Vec2f& center, const float angle) const;
void requestExteriorMap(const MWWorld::CellStore* cell, MapSegment& segment);
void requestInteriorMap(const MWWorld::CellStore* cell);