diff --git a/CHANGELOG.md b/CHANGELOG.md index fccbba4ac9..526c6d9681 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ Bug #6174: Spellmaking and Enchanting sliders differences from vanilla Feature #2554: Modifying an object triggers the instances table to scroll to the corresponding record Feature #2780: A way to see current OpenMW version in the console + Feature #3616: Allow Zoom levels on the World Map Feature #5489: MCP: Telekinesis fix for activators Feature #5996: Support Lua scripts in OpenMW Feature #6017: Separate persistent and temporary cell references when saving diff --git a/apps/launcher/advancedpage.cpp b/apps/launcher/advancedpage.cpp index 00021268e3..2c33992ac3 100644 --- a/apps/launcher/advancedpage.cpp +++ b/apps/launcher/advancedpage.cpp @@ -192,6 +192,7 @@ bool Launcher::AdvancedPage::loadSettings() if (showOwnedIndex >= 0 && showOwnedIndex <= 3) showOwnedComboBox->setCurrentIndex(showOwnedIndex); loadSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); + loadSettingBool(useZoomOnMapCheckBox, "allow zooming", "Map"); loadSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game"); scalingSpinBox->setValue(Settings::Manager::getFloat("scaling factor", "GUI")); } @@ -352,6 +353,7 @@ void Launcher::AdvancedPage::saveSettings() if (showOwnedCurrentIndex != Settings::Manager::getInt("show owned", "Game")) Settings::Manager::setInt("show owned", "Game", showOwnedCurrentIndex); saveSettingBool(stretchBackgroundCheckBox, "stretch menu background", "GUI"); + saveSettingBool(useZoomOnMapCheckBox, "allow zooming", "Map"); saveSettingBool(graphicHerbalismCheckBox, "graphic herbalism", "Game"); float uiScalingFactor = scalingSpinBox->value(); if (uiScalingFactor != Settings::Manager::getFloat("scaling factor", "GUI")) diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 6e8804a43f..efb434d3bc 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -30,10 +30,13 @@ #include "confirmationdialog.hpp" #include "tooltips.hpp" +#include + namespace { const int cellSize = Constants::CellSizeInUnits; + constexpr float speed = 1.08; //the zoom speed, it should be greater than 1 enum LocalMapWidgetDepth { @@ -84,6 +87,23 @@ namespace setColour(mHoverColour); } }; + + MyGUI::IntRect createRect(const MyGUI::IntPoint& center, int radius) + { + return { center.left - radius, center.top + radius, center.left + radius, center.top - radius }; + } + + int getLocalViewingDistance() + { + if (!Settings::Manager::getBool("allow zooming", "Map")) + return Constants::CellGridRadius; + if (!Settings::Manager::getBool("distant terrain", "Terrain")) + return Constants::CellGridRadius; + const float localViewingDistanceCoef = Settings::Manager::getFloat("local viewing distance coef", "Map"); + const int viewingDistance = Settings::Manager::getInt("viewing distance", "Camera"); + const int localViewingDistanceInCells = (viewingDistance * localViewingDistanceCoef) / double(Constants::CellSizeInUnits); + return std::max(Constants::CellGridRadius, localViewingDistanceInCells); + } } namespace MWGui @@ -167,13 +187,12 @@ namespace MWGui , mFogOfWarToggled(true) , mFogOfWarEnabled(fogOfWarEnabled) , mMapWidgetSize(0) - , mNumCells(0) + , mNumCells(1) , mCellDistance(0) , mCustomMarkers(markers) , mMarkerUpdateTimer(0.0f) , mLastDirectionX(0.0f) , mLastDirectionY(0.0f) - , mNeedDoorMarkersUpdate(false) { mCustomMarkers.eventMarkersChanged += MyGUI::newDelegate(this, &LocalMapBase::updateCustomMarkers); } @@ -183,12 +202,12 @@ namespace MWGui mCustomMarkers.eventMarkersChanged -= MyGUI::newDelegate(this, &LocalMapBase::updateCustomMarkers); } - void LocalMapBase::init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass) + void LocalMapBase::init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int cellDistance) { mLocalMap = widget; mCompass = compass; mMapWidgetSize = std::max(1, Settings::Manager::getInt("local map widget size", "Map")); - mCellDistance = Constants::CellGridRadius; + mCellDistance = cellDistance; mNumCells = mCellDistance * 2 + 1; mLocalMap->setCanvasSize(mMapWidgetSize*mNumCells, mMapWidgetSize*mNumCells); @@ -234,65 +253,94 @@ namespace MWGui void LocalMapBase::applyFogOfWar() { - for (int mx=0; mxsetImageTexture(""); - entry.mFogTexture.reset(); - continue; - } + entry.mFogWidget->setImageTexture(""); + entry.mFogTexture.reset(); } } redraw(); } - MyGUI::IntPoint LocalMapBase::getMarkerPosition(float worldX, float worldY, MarkerUserData& markerPos) + MyGUI::IntPoint LocalMapBase::getPosition(int cellX, int cellY, float nX, float nY) const { - MyGUI::IntPoint widgetPos; + // normalized cell coordinates + auto mapWidgetSize = getWidgetSize(); + return MyGUI::IntPoint( + std::round(nX * mapWidgetSize + (mCellDistance + (cellX - mCurX)) * mapWidgetSize), + std::round(nY * mapWidgetSize + (mCellDistance - (cellY - mCurY)) * mapWidgetSize) + ); + } + + MyGUI::IntPoint LocalMapBase::getMarkerPosition(float worldX, float worldY, MarkerUserData& markerPos) const + { + int cellX, cellY; // normalized cell coordinates float nX,nY; if (!mInterior) { - int cellX, cellY; MWBase::Environment::get().getWorld()->positionToIndex(worldX, worldY, cellX, cellY); nX = (worldX - cellSize * cellX) / cellSize; // Image space is -Y up, cells are Y up nY = 1 - (worldY - cellSize * cellY) / cellSize; - - float cellDx = static_cast(cellX - mCurX); - float cellDy = static_cast(cellY - mCurY); - - markerPos.cellX = cellX; - markerPos.cellY = cellY; - - widgetPos = MyGUI::IntPoint(static_cast(nX * mMapWidgetSize + (mCellDistance + cellDx) * mMapWidgetSize), - static_cast(nY * mMapWidgetSize + (mCellDistance - cellDy) * mMapWidgetSize)); } else - { - int cellX, cellY; - osg::Vec2f worldPos (worldX, worldY); - mLocalMapRender->worldToInteriorMapPosition(worldPos, nX, nY, cellX, cellY); - - markerPos.cellX = cellX; - markerPos.cellY = cellY; - - // Image space is -Y up, cells are Y up - widgetPos = MyGUI::IntPoint(static_cast(nX * mMapWidgetSize + (mCellDistance + (cellX - mCurX)) * mMapWidgetSize), - static_cast(nY * mMapWidgetSize + (mCellDistance - (cellY - mCurY)) * mMapWidgetSize)); - } + mLocalMapRender->worldToInteriorMapPosition({ worldX, worldY }, nX, nY, cellX, cellY); + markerPos.cellX = cellX; + markerPos.cellY = cellY; markerPos.nX = nX; markerPos.nY = nY; - return widgetPos; + return getPosition(markerPos.cellX, markerPos.cellY, markerPos.nX, markerPos.nY); + } + + MyGUI::IntCoord LocalMapBase::getMarkerCoordinates(float worldX, float worldY, MarkerUserData& markerPos, size_t markerSize) const + { + int halfMarkerSize = markerSize / 2; + auto position = getMarkerPosition(worldX, worldY, markerPos); + return MyGUI::IntCoord(position.left - halfMarkerSize, position.top - halfMarkerSize, markerSize, markerSize); + } + + MyGUI::Widget* LocalMapBase::createDoorMarker(const std::string& name, const MyGUI::VectorString& notes, float x, float y) const + { + MarkerUserData data(mLocalMapRender); + data.notes = notes; + data.caption = name; + MarkerWidget* markerWidget = mLocalMap->createWidget("MarkerButton", + getMarkerCoordinates(x, y, data, 8), MyGUI::Align::Default); + markerWidget->setNormalColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}"))); + markerWidget->setHoverColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal_over}"))); + markerWidget->setDepth(Local_MarkerLayer); + markerWidget->setNeedMouseFocus(true); + // Used by tooltips to not show the tooltip if marker is hidden by fog of war + markerWidget->setUserString("ToolTipType", "MapMarker"); + + markerWidget->setUserData(data); + return markerWidget; + } + + void LocalMapBase::centerView() + { + MyGUI::IntPoint pos = mCompass->getPosition() + MyGUI::IntPoint{ 16, 16 }; + MyGUI::IntSize viewsize = mLocalMap->getSize(); + MyGUI::IntPoint viewOffset((viewsize.width / 2) - pos.left, (viewsize.height / 2) - pos.top); + mLocalMap->setViewOffset(viewOffset); + } + + MyGUI::IntCoord LocalMapBase::getMarkerCoordinates(MyGUI::Widget* widget, size_t markerSize) const + { + MarkerUserData& markerPos(*widget->getUserData()); + auto position = getPosition(markerPos.cellX, markerPos.cellY, markerPos.nX, markerPos.nY); + return MyGUI::IntCoord(position.left - markerSize / 2, position.top - markerSize / 2, markerSize, markerSize); + } + + std::vector& LocalMapBase::currentDoorMarkersWidgets() + { + return mInterior ? mInteriorDoorMarkerWidgets : mExteriorDoorMarkerWidgets; } void LocalMapBase::updateCustomMarkers() @@ -317,13 +365,8 @@ namespace MWGui const ESM::CustomMarker& marker = it->second; MarkerUserData markerPos (mLocalMapRender); - MyGUI::IntPoint widgetPos = getMarkerPosition(marker.mWorldX, marker.mWorldY, markerPos); - - MyGUI::IntCoord widgetCoord(widgetPos.left - 8, - widgetPos.top - 8, - 16, 16); MarkerWidget* markerWidget = mLocalMap->createWidget("CustomMarkerButton", - widgetCoord, MyGUI::Align::Default); + getMarkerCoordinates(marker.mWorldX, marker.mWorldY, markerPos, 16), MyGUI::Align::Default); markerWidget->setDepth(Local_MarkerAboveFogLayer); markerWidget->setUserString("ToolTipType", "Layout"); markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); @@ -346,6 +389,38 @@ namespace MWGui if (x==mCurX && y==mCurY && mInterior==interior && !mChanged) return; // don't do anything if we're still in the same cell + if (!interior && !(x == mCurX && y == mCurY)) + { + const MyGUI::IntRect intersection = { + std::max(x, mCurX) - mCellDistance, std::max(y, mCurY) - mCellDistance, + std::min(x, mCurX) + mCellDistance, std::min(y, mCurY) + mCellDistance + }; + + const MyGUI::IntRect activeGrid = createRect({ x, y }, Constants::CellGridRadius); + const MyGUI::IntRect currentView = createRect({ x, y }, mCellDistance); + + mExteriorDoorMarkerWidgets.clear(); + for (auto& [coord, doors] : mExteriorDoorsByCell) + { + if (!mHasALastActiveCell || !currentView.inside({ coord.first, coord.second }) || activeGrid.inside({ coord.first, coord.second })) + { + mDoorMarkersToRecycle.insert(mDoorMarkersToRecycle.end(), doors.begin(), doors.end()); + doors.clear(); + } + else + mExteriorDoorMarkerWidgets.insert(mExteriorDoorMarkerWidgets.end(), doors.begin(), doors.end()); + } + + for (auto& widget : mDoorMarkersToRecycle) + widget->setVisible(false); + + for (auto const& cell : mMaps) + { + if (mHasALastActiveCell && !intersection.inside({ cell.mCellX, cell.mCellY })) + mLocalMapRender->removeExteriorCell(cell.mCellX, cell.mCellY); + } + } + mCurX = x; mCurY = y; mInterior = interior; @@ -366,9 +441,11 @@ namespace MWGui } } - // 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 (!mInterior) + mHasALastActiveCell = true; updateMagicMarkers(); updateCustomMarkers(); @@ -385,21 +462,26 @@ namespace MWGui mLocalMap->getParent()->_updateChilds(); } + float LocalMapBase::getWidgetSize() const + { + return mLocalMapZoom * mMapWidgetSize; + } + void LocalMapBase::setPlayerPos(int cellX, int cellY, const float nx, const float ny) { - MyGUI::IntPoint pos(static_cast(mMapWidgetSize * mCellDistance + nx*mMapWidgetSize - 16), static_cast(mMapWidgetSize * mCellDistance + ny*mMapWidgetSize - 16)); - pos.left += (cellX - mCurX) * mMapWidgetSize; - pos.top -= (cellY - mCurY) * mMapWidgetSize; + MyGUI::IntPoint pos = getPosition(cellX, cellY, nx, ny) - MyGUI::IntPoint{ 16, 16 }; if (pos != mCompass->getPosition()) { notifyPlayerUpdate (); mCompass->setPosition(pos); - MyGUI::IntPoint middle (pos.left+16, pos.top+16); - MyGUI::IntCoord viewsize = mLocalMap->getCoord(); - MyGUI::IntPoint viewOffset((viewsize.width / 2) - middle.left, (viewsize.height / 2) - middle.top); - mLocalMap->setViewOffset(viewOffset); + } + osg::Vec2f curPos((cellX + nx) * cellSize, (cellY + 1 - ny) * cellSize); + if ((curPos - mCurPos).length2() > 0.001) + { + mCurPos = curPos; + centerView(); } } @@ -449,28 +531,21 @@ namespace MWGui { const ESM::Position& worldPos = ptr.getRefData().getPosition(); MarkerUserData markerPos (mLocalMapRender); - MyGUI::IntPoint widgetPos = getMarkerPosition(worldPos.pos[0], worldPos.pos[1], markerPos); - MyGUI::IntCoord widgetCoord(widgetPos.left - 4, - widgetPos.top - 4, - 8, 8); ++counter; MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", - widgetCoord, MyGUI::Align::Default); + getMarkerCoordinates(worldPos.pos[0], worldPos.pos[1], markerPos, 8), MyGUI::Align::Default); markerWidget->setDepth(Local_MarkerAboveFogLayer); markerWidget->setImageTexture(markerTexture); markerWidget->setImageCoord(MyGUI::IntCoord(0,0,8,8)); markerWidget->setNeedMouseFocus(false); + markerWidget->setUserData(markerPos); mMagicMarkerWidgets.push_back(markerWidget); } } void LocalMapBase::onFrame(float dt) { - if (mNeedDoorMarkersUpdate) - { - updateDoorMarkers(); - mNeedDoorMarkersUpdate = false; - } + updateDoorMarkers(); mMarkerUpdateTimer += dt; @@ -545,34 +620,32 @@ namespace MWGui void LocalMapBase::updateDoorMarkers() { - // clear all previous door markers - for (MyGUI::Widget* widget : mDoorMarkerWidgets) - MyGUI::Gui::getInstance().destroyWidget(widget); - mDoorMarkerWidgets.clear(); - + std::vector doors; MWBase::World* world = MWBase::Environment::get().getWorld(); - // Retrieve the door markers we want to show - std::vector doors; + mDoorMarkersToRecycle.insert(mDoorMarkersToRecycle.end(), mInteriorDoorMarkerWidgets.begin(), mInteriorDoorMarkerWidgets.end()); + mInteriorDoorMarkerWidgets.clear(); + if (mInterior) { + for (MyGUI::Widget* widget : mExteriorDoorMarkerWidgets) + widget->setVisible(false); + MWWorld::CellStore* cell = world->getInterior (mPrefix); world->getDoorMarkers(cell, doors); } else { - for (int dX=-mCellDistance; dX<=mCellDistance; ++dX) + for (MapEntry& entry : mMaps) { - for (int dY=-mCellDistance; dY<=mCellDistance; ++dY) - { - MWWorld::CellStore* cell = world->getExterior (mCurX+dX, mCurY+dY); - world->getDoorMarkers(cell, doors); - } + if (!entry.mMapTexture && !widgetCropped(entry.mMapWidget, mLocalMap)) + world->getDoorMarkers(world->getExterior(entry.mCellX, entry.mCellY), doors); } + if (doors.empty()) + return; } // Create a widget for each marker - int counter = 0; for (MWBase::World::DoorMarker& marker : doors) { std::vector destNotes; @@ -580,28 +653,33 @@ namespace MWGui for (CustomMarkerCollection::ContainerType::const_iterator iter = markers.first; iter != markers.second; ++iter) destNotes.push_back(iter->second.mNote); - MarkerUserData data (mLocalMapRender); - data.notes = destNotes; - data.caption = marker.name; - MyGUI::IntPoint widgetPos = getMarkerPosition(marker.x, marker.y, data); - MyGUI::IntCoord widgetCoord(widgetPos.left - 4, - widgetPos.top - 4, - 8, 8); - ++counter; - MarkerWidget* markerWidget = mLocalMap->createWidget("MarkerButton", - widgetCoord, MyGUI::Align::Default); - markerWidget->setNormalColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}"))); - markerWidget->setHoverColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal_over}"))); - markerWidget->setDepth(Local_MarkerLayer); - markerWidget->setNeedMouseFocus(true); - // Used by tooltips to not show the tooltip if marker is hidden by fog of war - markerWidget->setUserString("ToolTipType", "MapMarker"); + MyGUI::Widget* markerWidget = nullptr; + MarkerUserData* data; + if (mDoorMarkersToRecycle.empty()) + { + markerWidget = createDoorMarker(marker.name, destNotes, marker.x, marker.y); + data = markerWidget->getUserData(); + doorMarkerCreated(markerWidget); + } + else + { + markerWidget = (MarkerWidget*)mDoorMarkersToRecycle.back(); + mDoorMarkersToRecycle.pop_back(); - markerWidget->setUserData(data); - doorMarkerCreated(markerWidget); + data = markerWidget->getUserData(); + data->notes = destNotes; + data->caption = marker.name; + markerWidget->setCoord(getMarkerCoordinates(marker.x, marker.y, *data, 8)); + markerWidget->setVisible(true); + } - mDoorMarkerWidgets.push_back(markerWidget); + currentDoorMarkersWidgets().push_back(markerWidget); + if (!mInterior) + mExteriorDoorsByCell[{data->cellX, data->cellY}].push_back(markerWidget); } + + for (auto& widget : mDoorMarkersToRecycle) + widget->setVisible(false); } void LocalMapBase::updateMagicMarkers() @@ -623,23 +701,46 @@ namespace MWGui && (!mInterior || Misc::StringUtils::ciEqual(markedCell->getCell()->mName, mPrefix))) { MarkerUserData markerPos (mLocalMapRender); - MyGUI::IntPoint widgetPos = getMarkerPosition(markedPosition.pos[0], markedPosition.pos[1], markerPos); - MyGUI::IntCoord widgetCoord(widgetPos.left - 4, - widgetPos.top - 4, - 8, 8); MyGUI::ImageBox* markerWidget = mLocalMap->createWidget("ImageBox", - widgetCoord, MyGUI::Align::Default); + getMarkerCoordinates(markedPosition.pos[0], markedPosition.pos[1], markerPos, 8), MyGUI::Align::Default); markerWidget->setDepth(Local_MarkerAboveFogLayer); markerWidget->setImageTexture("textures\\menu_map_smark.dds"); markerWidget->setNeedMouseFocus(false); + markerWidget->setUserData(markerPos); mMagicMarkerWidgets.push_back(markerWidget); } redraw(); } - // ------------------------------------------------------------------------------------------ + void LocalMapBase::updateLocalMap() + { + auto mapWidgetSize = getWidgetSize(); + mLocalMap->setCanvasSize(mapWidgetSize * mNumCells, mapWidgetSize * mNumCells); + const auto size = MyGUI::IntSize(std::ceil(mapWidgetSize), std::ceil(mapWidgetSize)); + for (auto& entry : mMaps) + { + const auto position = getPosition(entry.mCellX, entry.mCellY, 0, 0); + entry.mMapWidget->setCoord({ position, size }); + entry.mFogWidget->setCoord({ position, size }); + } + + MarkerUserData markerPos(mLocalMapRender); + for (MyGUI::Widget* widget : currentDoorMarkersWidgets()) + widget->setCoord(getMarkerCoordinates(widget, 8)); + + for (MyGUI::Widget* widget : mCustomMarkerWidgets) + { + const auto& marker = *widget->getUserData(); + widget->setCoord(getMarkerCoordinates(marker.mWorldX, marker.mWorldY, markerPos, 16)); + } + + for (MyGUI::Widget* widget : mMagicMarkerWidgets) + widget->setCoord(getMarkerCoordinates(widget, 8)); + } + + // ------------------------------------------------------------------------------------------ MapWindow::MapWindow(CustomMarkerCollection &customMarkers, DragAndDrop* drag, MWRender::LocalMap* localMapRender, SceneUtil::WorkQueue* workQueue) : WindowPinnableBase("openmw_map_window.layout") , LocalMapBase(customMarkers, localMapRender) @@ -690,14 +791,16 @@ namespace MWGui getWidget(mEventBoxGlobal, "EventBoxGlobal"); mEventBoxGlobal->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); mEventBoxGlobal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); + mEventBoxGlobal->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); mEventBoxGlobal->setDepth(Global_ExploreOverlayLayer); getWidget(mEventBoxLocal, "EventBoxLocal"); mEventBoxLocal->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); mEventBoxLocal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); mEventBoxLocal->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &MapWindow::onMapDoubleClicked); + mEventBoxLocal->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); - LocalMapBase::init(mLocalMap, mPlayerArrowLocal); + LocalMapBase::init(mLocalMap, mPlayerArrowLocal, getLocalViewingDistance()); mGlobalMap->setVisible(mGlobal); mLocalMap->setVisible(!mGlobal); @@ -745,10 +848,11 @@ namespace MWGui MyGUI::IntPoint clickedPos = MyGUI::InputManager::getInstance().getMousePosition(); MyGUI::IntPoint widgetPos = clickedPos - mEventBoxLocal->getAbsolutePosition(); - int x = int(widgetPos.left/float(mMapWidgetSize))-mCellDistance; - int y = (int(widgetPos.top/float(mMapWidgetSize))-mCellDistance)*-1; - float nX = widgetPos.left/float(mMapWidgetSize) - int(widgetPos.left/float(mMapWidgetSize)); - float nY = widgetPos.top/float(mMapWidgetSize) - int(widgetPos.top/float(mMapWidgetSize)); + auto mapWidgetSize = getWidgetSize(); + int x = int(widgetPos.left/float(mapWidgetSize))-mCellDistance; + int y = (int(widgetPos.top/float(mapWidgetSize))-mCellDistance)*-1; + float nX = widgetPos.left/float(mapWidgetSize) - int(widgetPos.left/float(mapWidgetSize)); + float nY = widgetPos.top/float(mapWidgetSize) - int(widgetPos.top/float(mapWidgetSize)); x += mCurX; y += mCurY; @@ -781,6 +885,106 @@ namespace MWGui mEditNoteDialog.setText(""); } + void MapWindow::onMapZoomed(MyGUI::Widget* sender, int rel) + { + const static int localWidgetSize = Settings::Manager::getInt("local map widget size", "Map"); + const static int globalCellSize = Settings::Manager::getInt("global map cell size", "Map"); + + const bool zoomOut = rel < 0; + const bool zoomIn = !zoomOut; + const double speedDiff = zoomOut ? 1.0 / speed : speed; + const float localMapSizeInUnits = localWidgetSize * mNumCells; + + const float currentMinLocalMapZoom = std::max({ + (float(globalCellSize) * 4.f) / float(localWidgetSize), + float(mLocalMap->getWidth()) / localMapSizeInUnits, + float(mLocalMap->getHeight()) / localMapSizeInUnits + }); + + if (mGlobal) + { + const float currentGlobalZoom = mGlobalMapZoom; + const float currentMinGlobalMapZoom = std::min( + float(mGlobalMap->getWidth()) / float(mGlobalMapRender->getWidth()), + float(mGlobalMap->getHeight()) / float(mGlobalMapRender->getHeight()) + ); + + mGlobalMapZoom *= speedDiff; + + if (zoomIn && mGlobalMapZoom > 4.f) + { + mGlobalMapZoom = currentGlobalZoom; + mLocalMapZoom = currentMinLocalMapZoom; + onWorldButtonClicked(nullptr); + updateLocalMap(); + return; //the zoom in is too big + } + + if (zoomOut && mGlobalMapZoom < currentMinGlobalMapZoom) + { + mGlobalMapZoom = currentGlobalZoom; + return; //the zoom out is too big, we have reach the borders of the widget + } + } + else + { + auto const currentLocalZoom = mLocalMapZoom; + mLocalMapZoom *= speedDiff; + + if (zoomIn && mLocalMapZoom > 4.0f) + { + mLocalMapZoom = currentLocalZoom; + return; //the zoom in is too big + } + + if (zoomOut && mLocalMapZoom < currentMinLocalMapZoom) + { + mLocalMapZoom = currentLocalZoom; + + float zoomRatio = 4.f/ mGlobalMapZoom; + mGlobalMapZoom = 4.f; + onWorldButtonClicked(nullptr); + + zoomOnCursor(zoomRatio); + return; //the zoom out is too big, we switch to the global map + } + } + zoomOnCursor(speedDiff); + } + + void MapWindow::zoomOnCursor(float speedDiff) + { + auto map = mGlobal ? mGlobalMap : mLocalMap; + auto cursor = MyGUI::InputManager::getInstance().getMousePosition() - map->getAbsolutePosition(); + auto centerView = map->getViewOffset() - cursor; + + mGlobal? updateGlobalMap() : updateLocalMap(); + + map->setViewOffset(MyGUI::IntPoint( + std::round(centerView.left * speedDiff) + cursor.left, + std::round(centerView.top * speedDiff) + cursor.top + )); + } + + void MapWindow::updateGlobalMap() + { + resizeGlobalMap(); + + float x = mCurPos.x(), y = mCurPos.y(); + if (mInterior) + { + auto pos = MWBase::Environment::get().getWorld()->getPlayer().getLastKnownExteriorPosition(); + x = pos.x(), y = pos.y(); + } + setGlobalMapPlayerPosition(x, y); + + for (auto& [marker, col] : mGlobalMapMarkers) + { + marker.widget->setCoord(createMarkerCoords(marker.position.x(), marker.position.y(), col.size())); + marker.widget->setVisible(marker.widget->getHeight() >= 6); + } + } + void MapWindow::onChangeScrollWindowCoord(MyGUI::Widget* sender) { MyGUI::IntCoord currentCoordinates = sender->getCoord(); @@ -804,8 +1008,7 @@ namespace MWGui void MapWindow::renderGlobalMap() { mGlobalMapRender->render(); - mGlobalMap->setCanvasSize (mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); - mGlobalMapImage->setSize(mGlobalMapRender->getWidth(), mGlobalMapRender->getHeight()); + resizeGlobalMap(); } MapWindow::~MapWindow() @@ -818,6 +1021,39 @@ namespace MWGui setTitle("#{sCell=" + cellName + "}"); } + MyGUI::IntCoord MapWindow::createMarkerCoords(float x, float y, float agregatedWeight) const + { + float worldX, worldY; + worldPosToGlobalMapImageSpace((x + 0.5f) * Constants::CellSizeInUnits, (y + 0.5f)* Constants::CellSizeInUnits, worldX, worldY); + + const float markerSize = getMarkerSize(agregatedWeight); + const float halfMarkerSize = markerSize / 2.0f; + return MyGUI::IntCoord( + static_cast(worldX - halfMarkerSize), + static_cast(worldY - halfMarkerSize), + markerSize, markerSize); + } + + MyGUI::Widget* MapWindow::createMarker(const std::string& name, float x, float y, float agregatedWeight) + { + MyGUI::Widget* markerWidget = mGlobalMap->createWidget("MarkerButton", + createMarkerCoords(x, y, agregatedWeight), MyGUI::Align::Default); + markerWidget->setVisible(markerWidget->getHeight() >= 6.0); + markerWidget->setUserString("Caption_TextOneLine", "#{sCell=" + name + "}"); + setGlobalMapMarkerTooltip(markerWidget, x, y); + + markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); + + markerWidget->setNeedMouseFocus(true); + markerWidget->setColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}"))); + markerWidget->setDepth(Global_MarkerLayer); + markerWidget->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); + markerWidget->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); + markerWidget->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); + + return markerWidget; + } + void MapWindow::addVisitedLocation(const std::string& name, int x, int y) { CellId cell; @@ -825,31 +1061,33 @@ namespace MWGui cell.second = y; if (mMarkers.insert(cell).second) { - float worldX, worldY; - mGlobalMapRender->cellTopLeftCornerToImageSpace (x, y, worldX, worldY); + MapMarkerType mapMarkerWidget = { osg::Vec2f(x, y), createMarker(name, x, y, 0) }; + mGlobalMapMarkers.emplace(mapMarkerWidget, std::vector()); - int markerSize = 12; - int offset = mGlobalMapRender->getCellSize()/2 - markerSize/2; - MyGUI::IntCoord widgetCoord( - static_cast(worldX * mGlobalMapRender->getWidth()+offset), - static_cast(worldY * mGlobalMapRender->getHeight() + offset), - markerSize, markerSize); + std::string name_ = name.substr(0, name.find(',')); + auto& entry = mGlobalMapMarkersByName[name_]; + if (!entry.widget) + { + entry = { osg::Vec2f(x, y), entry.widget }; //update the coords - MyGUI::Widget* markerWidget = mGlobalMap->createWidget("MarkerButton", - widgetCoord, MyGUI::Align::Default); + entry.widget = createMarker(name_, entry.position.x(), entry.position.y(), 1); + mGlobalMapMarkers.emplace(entry, std::vector{ entry }); + } + else + { + auto it = mGlobalMapMarkers.find(entry); + auto& marker = const_cast(it->first); + auto& elements = it->second; + elements.emplace_back(mapMarkerWidget); - markerWidget->setUserString("Caption_TextOneLine", "#{sCell=" + name + "}"); + //we compute the barycenter of the entry elements => it will be the place on the world map for the agregated widget + marker.position = std::accumulate(elements.begin(), elements.end(), osg::Vec2f(0.f, 0.f), [](const auto& left, const auto& right) { + return left + right.position; + }) / float(elements.size()); - setGlobalMapMarkerTooltip(markerWidget, x, y); - - markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); - - markerWidget->setNeedMouseFocus(true); - markerWidget->setColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal}"))); - markerWidget->setDepth(Global_MarkerLayer); - markerWidget->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); - markerWidget->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); - mGlobalMapMarkers[std::make_pair(x,y)] = markerWidget; + marker.widget->setCoord(createMarkerCoords(marker.position.x(), marker.position.y(), elements.size())); + marker.widget->setVisible(marker.widget->getHeight() >= 6); + } } } @@ -891,17 +1129,32 @@ namespace MWGui } } + float MapWindow::getMarkerSize(size_t agregatedWeight) const + { + float markerSize = 12.f * mGlobalMapZoom; + if (mGlobalMapZoom < 1) + return markerSize * std::sqrt(agregatedWeight); //we want to see agregated object + return agregatedWeight ? 0 : markerSize; //we want to see only original markers (i.e. non agregated) + } + + void MapWindow::resizeGlobalMap() + { + mGlobalMap->setCanvasSize(mGlobalMapRender->getWidth() * mGlobalMapZoom, mGlobalMapRender->getHeight() * mGlobalMapZoom); + mGlobalMapImage->setSize(mGlobalMapRender->getWidth() * mGlobalMapZoom, mGlobalMapRender->getHeight() * mGlobalMapZoom); + } + + void MapWindow::worldPosToGlobalMapImageSpace(float x, float y, float& imageX, float& imageY) const + { + mGlobalMapRender->worldPosToImageSpace(x, y, imageX, imageY); + imageX *= mGlobalMapZoom, imageY *= mGlobalMapZoom; + } + void MapWindow::updateCustomMarkers() { LocalMapBase::updateCustomMarkers(); - for (auto& widgetPair : mGlobalMapMarkers) - { - int x = widgetPair.first.first; - int y = widgetPair.first.second; - MyGUI::Widget* markerWidget = widgetPair.second; - setGlobalMapMarkerTooltip(markerWidget, x, y); - } + for (auto& [widgetPair, ignore]: mGlobalMapMarkers) + setGlobalMapMarkerTooltip(widgetPair.widget, widgetPair.position.x(), widgetPair.position.y()); } void MapWindow::onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id) @@ -934,9 +1187,6 @@ namespace MWGui mButton->setCaptionWithReplacing( mGlobal ? "#{sLocal}" : "#{sWorld}"); - - if (mGlobal) - globalMapUpdatePlayer (); } void MapWindow::onPinToggled() @@ -978,19 +1228,21 @@ namespace MWGui setGlobalMapPlayerDir(mLastDirectionX, mLastDirectionY); } + void MapWindow::centerView() + { + LocalMapBase::centerView(); + // set the view offset so that player is in the center + MyGUI::IntSize viewsize = mGlobalMap->getSize(); + MyGUI::IntPoint pos = mPlayerArrowGlobal->getPosition() + MyGUI::IntPoint{ 16,16 }; + MyGUI::IntPoint viewoffs(static_cast(viewsize.width * 0.5f - pos.left), static_cast(viewsize.height * 0.5f - pos.top)); + mGlobalMap->setViewOffset(viewoffs); + } + void MapWindow::setGlobalMapPlayerPosition(float worldX, float worldY) { float x, y; - mGlobalMapRender->worldPosToImageSpace (worldX, worldY, x, y); - x *= mGlobalMapRender->getWidth(); - y *= mGlobalMapRender->getHeight(); - + worldPosToGlobalMapImageSpace(worldX, worldY, x, y); mPlayerArrowGlobal->setPosition(MyGUI::IntPoint(static_cast(x - 16), static_cast(y - 16))); - - // set the view offset so that player is in the center - MyGUI::IntSize viewsize = mGlobalMap->getSize(); - MyGUI::IntPoint viewoffs(static_cast(viewsize.width * 0.5f - x), static_cast(viewsize.height *0.5 - y)); - mGlobalMap->setViewOffset(viewoffs); } void MapWindow::setGlobalMapPlayerDir(const float x, const float y) @@ -1027,8 +1279,9 @@ namespace MWGui mChanged = true; for (auto& widgetPair : mGlobalMapMarkers) - MyGUI::Gui::getInstance().destroyWidget(widgetPair.second); + MyGUI::Gui::getInstance().destroyWidget(widgetPair.first.widget); mGlobalMapMarkers.clear(); + mGlobalMapMarkersByName.clear(); } void MapWindow::write(ESM::ESMWriter &writer, Loading::Listener& progress) @@ -1075,12 +1328,14 @@ namespace MWGui marker->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); marker->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); marker->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &MapWindow::onCustomMarkerDoubleClicked); + marker->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); } void MapWindow::doorMarkerCreated(MyGUI::Widget *marker) { marker->eventMouseDrag += MyGUI::newDelegate(this, &MapWindow::onMouseDrag); marker->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); + marker->eventMouseWheel += MyGUI::newDelegate(this, &MapWindow::onMapZoomed); } // ------------------------------------------------------------------- diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index 7e8092f289..cb0d368b30 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -9,6 +9,7 @@ #include #include +#include namespace MWRender { @@ -72,7 +73,7 @@ namespace MWGui public: LocalMapBase(CustomMarkerCollection& markers, MWRender::LocalMap* localMapRender, bool fogOfWarEnabled = true); virtual ~LocalMapBase(); - void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass); + void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int cellDistance = Constants::CellGridRadius); void setCellPrefix(const std::string& prefix); void setActiveCell(const int x, const int y, bool interior=false); @@ -107,9 +108,15 @@ namespace MWGui }; protected: + void updateLocalMap(); + + float mLocalMapZoom = 1.f; MWRender::LocalMap* mLocalMapRender; - int mCurX, mCurY; + int mCurX, mCurY; //the position of the active cell on the global map (in cell coords) + bool mHasALastActiveCell = false; + osg::Vec2f mCurPos; //the position of the player in the world (in cell coords) + bool mInterior; MyGUI::ScrollView* mLocalMap; MyGUI::ImageBox* mCompass; @@ -141,17 +148,27 @@ namespace MWGui std::vector mMaps; // Keep track of created marker widgets, just to easily remove them later. - std::vector mDoorMarkerWidgets; + std::vector mExteriorDoorMarkerWidgets; + std::map, std::vector> mExteriorDoorsByCell; + std::vector mInteriorDoorMarkerWidgets; std::vector mMagicMarkerWidgets; std::vector mCustomMarkerWidgets; + std::vector mDoorMarkersToRecycle; + + std::vector& currentDoorMarkersWidgets(); virtual void updateCustomMarkers(); void applyFogOfWar(); - MyGUI::IntPoint getMarkerPosition (float worldX, float worldY, MarkerUserData& markerPos); + MyGUI::IntPoint getPosition(int cellX, int cellY, float nx, float ny) const; + 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, const MyGUI::VectorString& notes, float x, float y) const; + MyGUI::IntCoord getMarkerCoordinates(MyGUI::Widget* widget, size_t markerSize) const; virtual void notifyPlayerUpdate() {} + virtual void centerView(); virtual void notifyMapChanged() {} virtual void customMarkerCreated(MyGUI::Widget* marker) {} @@ -163,6 +180,7 @@ namespace MWGui void addDetectionMarkers(int type); void redraw(); + float getWidgetSize() const; float mMarkerUpdateTimer; @@ -171,7 +189,6 @@ namespace MWGui private: void updateDoorMarkers(); - bool mNeedDoorMarkersUpdate; }; class EditNoteDialog : public MWGui::WindowModal @@ -244,6 +261,9 @@ namespace MWGui void onMouseDrag(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); void onWorldButtonClicked(MyGUI::Widget* _sender); void onMapDoubleClicked(MyGUI::Widget* sender); + void onMapZoomed(MyGUI::Widget* sender, int rel); + void zoomOnCursor(float speedDiff); + void updateGlobalMap(); void onCustomMarkerDoubleClicked(MyGUI::Widget* sender); void onNoteEditOk(); void onNoteEditDelete(); @@ -252,6 +272,12 @@ namespace MWGui void onChangeScrollWindowCoord(MyGUI::Widget* sender); void globalMapUpdatePlayer(); void setGlobalMapMarkerTooltip(MyGUI::Widget* widget, int x, int y); + float getMarkerSize(size_t agregatedWeight) const; + void resizeGlobalMap(); + void worldPosToGlobalMapImageSpace(float x, float z, float& imageX, float& imageY) const; + MyGUI::IntCoord createMarkerCoords(float x, float y, float agregatedWeight) const; + MyGUI::Widget* createMarker(const std::string& name, float x, float y, float agregatedWeight); + MyGUI::ScrollView* mGlobalMap; std::unique_ptr mGlobalMapTexture; @@ -273,9 +299,21 @@ namespace MWGui MyGUI::Button* mEventBoxGlobal; MyGUI::Button* mEventBoxLocal; + float mGlobalMapZoom = 1.0f; MWRender::GlobalMap* mGlobalMapRender; - std::map, MyGUI::Widget*> mGlobalMapMarkers; + struct MapMarkerType + { + osg::Vec2f position; + MyGUI::Widget* widget = nullptr; + + bool operator<(const MapMarkerType& right) const { + return widget < right.widget; + } + }; + + std::map mGlobalMapMarkersByName; + std::map> mGlobalMapMarkers; EditNoteDialog mEditNoteDialog; ESM::CustomMarker mEditingMarker; @@ -288,6 +326,7 @@ namespace MWGui void notifyPlayerUpdate() override; + void centerView() override; }; } #endif diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index 8784eb501a..dcaf9c1612 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -270,17 +270,9 @@ namespace MWRender void GlobalMap::worldPosToImageSpace(float x, float z, float& imageX, float& imageY) { - imageX = float(x / float(Constants::CellSizeInUnits) - mMinX) / (mMaxX - mMinX + 1); + imageX = (float(x / float(Constants::CellSizeInUnits) - mMinX) / (mMaxX - mMinX + 1)) * getWidth(); - imageY = 1.f-float(z / float(Constants::CellSizeInUnits) - mMinY) / (mMaxY - mMinY + 1); - } - - void GlobalMap::cellTopLeftCornerToImageSpace(int x, int y, float& imageX, float& imageY) - { - imageX = float(x - mMinX) / (mMaxX - mMinX + 1); - - // NB y + 1, because we want the top left corner, not bottom left where the origin of the cell is - imageY = 1.f-float(y - mMinY + 1) / (mMaxY - mMinY + 1); + imageY = (1.f-float(z / float(Constants::CellSizeInUnits) - mMinY) / (mMaxY - mMinY + 1)) * getHeight(); } void GlobalMap::requestOverlayTextureUpdate(int x, int y, int width, int height, osg::ref_ptr texture, bool clear, bool cpuCopy, diff --git a/apps/openmw/mwrender/globalmap.hpp b/apps/openmw/mwrender/globalmap.hpp index b359c852be..fd8a8d1016 100644 --- a/apps/openmw/mwrender/globalmap.hpp +++ b/apps/openmw/mwrender/globalmap.hpp @@ -41,12 +41,8 @@ namespace MWRender int getWidth() const { return mWidth; } int getHeight() const { return mHeight; } - int getCellSize() const { return mCellSize; } - void worldPosToImageSpace(float x, float z, float& imageX, float& imageY); - void cellTopLeftCornerToImageSpace(int x, int y, float& imageX, float& imageY); - void exploreCell (int cellX, int cellY, osg::ref_ptr localMapTexture); /// Clears the overlay diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 24c00048d9..858d577335 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -118,14 +118,15 @@ const osg::Vec2f LocalMap::rotatePoint(const osg::Vec2f& point, const osg::Vec2f void LocalMap::clear() { - mSegments.clear(); + mExteriorSegments.clear(); + mInteriorSegments.clear(); } void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) { if (!mInterior) { - const MapSegment& segment = mSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())]; + const MapSegment& segment = mExteriorSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())]; if (segment.mFogOfWarImage && segment.mHasFogState) { @@ -155,7 +156,7 @@ void LocalMap::saveFogOfWar(MWWorld::CellStore* cell) { for (int y = 0; y < segments.second; ++y) { - const MapSegment& segment = mSegments[std::make_pair(x,y)]; + const MapSegment& segment = mInteriorSegments[std::make_pair(x,y)]; fog->mFogTextures.emplace_back(); @@ -249,26 +250,10 @@ void LocalMap::setupRenderToTexture(osg::ref_ptr camera, int x, int mRoot->addChild(camera); mActiveCameras.push_back(camera); - MapSegment& segment = mSegments[std::make_pair(x, y)]; + MapSegment& segment = mInterior? mInteriorSegments[std::make_pair(x, y)] : mExteriorSegments[std::make_pair(x, y)]; segment.mMapTexture = texture; } -bool needUpdate(std::set >& renderedGrid, std::set >& currentGrid, int cellX, int cellY) -{ - // if all the cells of the current grid are contained in the rendered grid then we can keep the old render - for (int dx=-1;dx<2;dx+=1) - { - for (int dy=-1;dy<2;dy+=1) - { - bool haveInRenderedGrid = renderedGrid.find(std::make_pair(cellX+dx,cellY+dy)) != renderedGrid.end(); - bool haveInCurrentGrid = currentGrid.find(std::make_pair(cellX+dx,cellY+dy)) != currentGrid.end(); - if (haveInCurrentGrid && !haveInRenderedGrid) - return true; - } - } - return false; -} - void LocalMap::requestMap(const MWWorld::CellStore* cell) { if (cell->isExterior()) @@ -276,13 +261,13 @@ void LocalMap::requestMap(const MWWorld::CellStore* cell) int cellX = cell->getCell()->getGridX(); int cellY = cell->getCell()->getGridY(); - MapSegment& segment = mSegments[std::make_pair(cellX, cellY)]; - if (!needUpdate(segment.mGrid, mCurrentGrid, cellX, cellY)) + MapSegment& segment = mExteriorSegments[std::make_pair(cellX, cellY)]; + if (!segment.needUpdate) return; else { - segment.mGrid = mCurrentGrid; requestExteriorMap(cell); + segment.needUpdate = false; } } else @@ -292,27 +277,27 @@ void LocalMap::requestMap(const MWWorld::CellStore* cell) void LocalMap::addCell(MWWorld::CellStore *cell) { if (cell->isExterior()) - mCurrentGrid.emplace(cell->getCell()->getGridX(), cell->getCell()->getGridY()); + mExteriorSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())].needUpdate = true; +} + +void LocalMap::removeExteriorCell(int x, int y) +{ + mExteriorSegments.erase({ x, y }); } void LocalMap::removeCell(MWWorld::CellStore *cell) { saveFogOfWar(cell); - if (cell->isExterior()) - { - std::pair coords = std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY()); - mSegments.erase(coords); - mCurrentGrid.erase(coords); - } - else - mSegments.clear(); + if (!cell->isExterior()) + mInteriorSegments.clear(); } osg::ref_ptr LocalMap::getMapTexture(int x, int y) { - SegmentMap::iterator found = mSegments.find(std::make_pair(x, y)); - if (found == mSegments.end()) + auto& segments(mInterior ? mInteriorSegments : mExteriorSegments); + SegmentMap::iterator found = segments.find(std::make_pair(x, y)); + if (found == segments.end()) return osg::ref_ptr(); else return found->second.mMapTexture; @@ -320,8 +305,9 @@ osg::ref_ptr LocalMap::getMapTexture(int x, int y) osg::ref_ptr LocalMap::getFogOfWarTexture(int x, int y) { - SegmentMap::iterator found = mSegments.find(std::make_pair(x, y)); - if (found == mSegments.end()) + auto& segments(mInterior ? mInteriorSegments : mExteriorSegments); + SegmentMap::iterator found = segments.find(std::make_pair(x, y)); + if (found == segments.end()) return osg::ref_ptr(); else return found->second.mFogOfWarTexture; @@ -371,7 +357,7 @@ void LocalMap::requestExteriorMap(const MWWorld::CellStore* cell) osg::Vec3d(0,1,0), zmin, zmax); setupRenderToTexture(camera, cell->getCell()->getGridX(), cell->getCell()->getGridY()); - MapSegment& segment = mSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())]; + MapSegment& segment = mExteriorSegments[std::make_pair(cell->getCell()->getGridX(), cell->getCell()->getGridY())]; if (!segment.mFogOfWarImage) { if (cell->getFog()) @@ -512,7 +498,7 @@ void LocalMap::requestInteriorMap(const MWWorld::CellStore* cell) setupRenderToTexture(camera, x, y); auto coords = std::make_pair(x,y); - MapSegment& segment = mSegments[coords]; + MapSegment& segment = mInteriorSegments[coords]; if (!segment.mFogOfWarImage) { bool loaded = false; @@ -558,7 +544,8 @@ osg::Vec2f LocalMap::interiorMapToWorldPosition (float nX, float nY, int x, int bool LocalMap::isPositionExplored (float nX, float nY, int x, int y) { - const MapSegment& segment = mSegments[std::make_pair(x, y)]; + auto& segments(mInterior ? mInteriorSegments : mExteriorSegments); + const MapSegment& segment = segments[std::make_pair(x, y)]; if (!segment.mFogOfWarImage) return false; @@ -630,7 +617,8 @@ void LocalMap::updatePlayer (const osg::Vec3f& position, const osg::Quat& orient int texX = x + mx; int texY = y + my*-1; - MapSegment& segment = mSegments[std::make_pair(texX, texY)]; + auto& segments(mInterior ? mInteriorSegments : mExteriorSegments); + MapSegment& segment = segments[std::make_pair(texX, texY)]; if (!segment.mFogOfWarImage || !segment.mMapTexture) continue; diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index e586f8fb02..f9ccd5a011 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -50,6 +50,7 @@ namespace MWRender void requestMap (const MWWorld::CellStore* cell); void addCell(MWWorld::CellStore* cell); + void removeExteriorCell(int x, int y); void removeCell (MWWorld::CellStore* cell); @@ -126,13 +127,14 @@ namespace MWRender osg::ref_ptr mFogOfWarTexture; osg::ref_ptr mFogOfWarImage; - Grid mGrid; // the grid that was active at the time of rendering this segment + bool needUpdate = true; bool mHasFogState; }; typedef std::map, MapSegment> SegmentMap; - SegmentMap mSegments; + SegmentMap mExteriorSegments; + SegmentMap mInteriorSegments; int mMapResolution; diff --git a/docs/source/reference/modding/settings/map.rst b/docs/source/reference/modding/settings/map.rst index ac989f3dea..eeef0a1f39 100644 --- a/docs/source/reference/modding/settings/map.rst +++ b/docs/source/reference/modding/settings/map.rst @@ -103,3 +103,26 @@ and typically require more panning to see all available portions of the map. This larger size also enables an overall greater level of detail if the local map resolution setting is also increased. This setting can not be configured except by editing the settings configuration file. + +allow zooming +------------- + +:Type: boolean +:Range: True/False +:Default: False + +If this setting is true the user can zoom in/out on local and global map with the mouse wheel. + +This setting can be controlled in Advanced tab of the launcher. + +local viewing distance coef +--------------------------- + +:Type: float +:Range: > 0 and <= 1 +:Default: 0.5 + +This setting controls viewing distance on local map. It is the coefficient of the viewing distance viewable on the local map if 'distant terrain' is enabled otherwise you will see the default value (a 3x3 square centered on the player). +If view distance is changed in settings menu during the game, then viewable distance on the local map is not updated. + +This setting can not be configured except by editing the settings configuration file. diff --git a/files/settings-default.cfg b/files/settings-default.cfg index e7439fb5ae..36eca7f1ab 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -199,6 +199,12 @@ local map widget size = 512 # If true, map in world mode, otherwise in local mode global = false +# If true, allow zoom on local and global maps +allow zooming = false + +# The local view distance coefficient (1.0 is full view distance if distant terrain is enabled) +local viewing distance coef = 0.5 + [GUI] # Scales GUI window and widget size. (<1.0 is smaller, >1.0 is larger). diff --git a/files/ui/advancedpage.ui b/files/ui/advancedpage.ui index f72150df22..7964399319 100644 --- a/files/ui/advancedpage.ui +++ b/files/ui/advancedpage.ui @@ -1006,6 +1006,16 @@ True: In non-combat mode camera is positioned behind the character's shoulder. C + + + + <html><head/><body><p>Enable zooming on local and global maps.</p></body></html> + + + Can zoom on maps + + +