From c613c5decc1f3aac867b06560300d9d2c075346d Mon Sep 17 00:00:00 2001 From: Diject Date: Wed, 24 Dec 2025 15:09:27 +0300 Subject: [PATCH] Add world map extraction feature to OpenMW --- apps/openmw/CMakeLists.txt | 1 + apps/openmw/engine.cpp | 37 ++++++- apps/openmw/main.cpp | 15 +++ apps/openmw/mapextractor.cpp | 192 +++++++++++++++++++++++++++++++++++ apps/openmw/mapextractor.hpp | 54 ++++++++++ 5 files changed, 296 insertions(+), 3 deletions(-) create mode 100644 apps/openmw/mapextractor.cpp create mode 100644 apps/openmw/mapextractor.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 8570d9df60..bc1723282d 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -1,6 +1,7 @@ set(OPENMW_SOURCES engine.cpp options.cpp + mapextractor.cpp ) set(OPENMW_RESOURCES diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index 66bd16ea3e..8bd0aad8e1 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -83,6 +83,7 @@ #include "mwstate/statemanagerimp.hpp" +#include "mapextractor.hpp" #include "profile.hpp" namespace @@ -374,6 +375,7 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) , mExportFonts(false) , mRandomSeed(0) , mNewGame(false) + , mExtractMaps(false) , mCfgMgr(configurationManager) , mGlMaxTextureImageUnits(0) { @@ -958,11 +960,25 @@ void OMW::Engine::go() prepareEngine(); -#ifdef _WIN32 + if (mExtractMaps) + { + Log(Debug::Info) << "Starting map extraction mode..."; + + mStateManager->newGame(true); + + MapExtractor extractor(*mWorld, mWorldMapOutput, mLocalMapOutput); + extractor.extractWorldMap(); + extractor.extractLocalMaps(); + + Log(Debug::Info) << "Map extraction complete. Exiting..."; + return; + } + + #ifdef _WIN32 const auto* statsFile = _wgetenv(L"OPENMW_OSG_STATS_FILE"); -#else + #else const auto* statsFile = std::getenv("OPENMW_OSG_STATS_FILE"); -#endif + #endif std::filesystem::path path; if (statsFile != nullptr) @@ -1128,3 +1144,18 @@ void OMW::Engine::setRandomSeed(unsigned int seed) { mRandomSeed = seed; } + +void OMW::Engine::setWorldMapOutput(const std::string& path) +{ + mWorldMapOutput = path; +} + +void OMW::Engine::setLocalMapOutput(const std::string& path) +{ + mLocalMapOutput = path; +} + +void OMW::Engine::setExtractMaps(bool extract) +{ + mExtractMaps = extract; +} diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 70e48e0cfc..9badb4f31a 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -158,6 +159,20 @@ bool parseOptions(int argc, char** argv, OMW::Engine& engine, Files::Configurati engine.enableFontExport(variables["export-fonts"].as()); engine.setRandomSeed(variables["random-seed"].as()); + std::string worldMapOutput = variables["world-map-output"].as(); + std::string localMapOutput = variables["local-map-output"].as(); + bool extractMaps = variables["extract-maps"].as(); + + if (worldMapOutput.empty() && extractMaps) + worldMapOutput = Files::pathToUnicodeString(std::filesystem::current_path() / "textures" / "advanced_world_map" / "custom"); + + if (localMapOutput.empty() && extractMaps) + localMapOutput = Files::pathToUnicodeString(std::filesystem::current_path() / "textures" / "advanced_world_map" / "local"); + + engine.setWorldMapOutput(worldMapOutput); + engine.setLocalMapOutput(localMapOutput); + engine.setExtractMaps(extractMaps); + return true; } diff --git a/apps/openmw/mapextractor.cpp b/apps/openmw/mapextractor.cpp new file mode 100644 index 0000000000..bfe36ee3ef --- /dev/null +++ b/apps/openmw/mapextractor.cpp @@ -0,0 +1,192 @@ +#include "mapextractor.hpp" + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "mwbase/environment.hpp" +#include "mwbase/world.hpp" +#include "mwrender/globalmap.hpp" +#include "mwrender/localmap.hpp" +#include "mwrender/renderingmanager.hpp" +#include "mwworld/cellstore.hpp" +#include "mwworld/esmstore.hpp" +#include "mwworld/worldimp.hpp" + +namespace OMW +{ + MapExtractor::MapExtractor( + MWWorld::World& world, const std::string& worldMapOutput, const std::string& localMapOutput) + : mWorld(world) + , mWorldMapOutputDir(worldMapOutput) + , mLocalMapOutputDir(localMapOutput) + { + std::filesystem::create_directories(mWorldMapOutputDir); + std::filesystem::create_directories(mLocalMapOutputDir); + + // Create GlobalMap and LocalMap instances + MWRender::RenderingManager* renderingManager = mWorld.getRenderingManager(); + if (renderingManager) + { + osg::Group* root = renderingManager->getLightRoot()->getParent(0)->asGroup(); + SceneUtil::WorkQueue* workQueue = renderingManager->getWorkQueue(); + + mGlobalMap = std::make_unique(root, workQueue); + mLocalMap = std::make_unique(root); + } + } + + MapExtractor::~MapExtractor() = default; + + void MapExtractor::extractWorldMap() + { + Log(Debug::Info) << "Extracting world map..."; + + if (!mGlobalMap) + { + Log(Debug::Error) << "Global map not initialized"; + return; + } + + // Temporarily set cell size to 32 pixels for extraction + const int originalCellSize = Settings::map().mGlobalMapCellSize; + Settings::map().mGlobalMapCellSize.set(32); + + mGlobalMap->render(); + mGlobalMap->ensureLoaded(); + + saveWorldMapTexture(); + saveWorldMapInfo(); + + // Restore original cell size + Settings::map().mGlobalMapCellSize.set(originalCellSize); + + Log(Debug::Info) << "World map extraction complete"; + } + + void MapExtractor::saveWorldMapTexture() + { + osg::ref_ptr baseTexture = mGlobalMap->getBaseTexture(); + if (!baseTexture || !baseTexture->getImage()) + { + Log(Debug::Error) << "Failed to get world map base texture"; + return; + } + + osg::Image* image = baseTexture->getImage(); + std::filesystem::path outputPath = mWorldMapOutputDir / "map.png"; + + if (!osgDB::writeImageFile(*image, outputPath.string())) + { + Log(Debug::Error) << "Failed to write world map texture to " << outputPath; + return; + } + + Log(Debug::Info) << "Saved world map texture: " << outputPath; + } + + void MapExtractor::saveWorldMapInfo() + { + int width = mGlobalMap->getWidth(); + int height = mGlobalMap->getHeight(); + + const MWWorld::ESMStore& store = mWorld.getStore(); + int minX = std::numeric_limits::max(); + int maxX = std::numeric_limits::min(); + int minY = std::numeric_limits::max(); + int maxY = std::numeric_limits::min(); + + MWWorld::Store::iterator it = store.get().extBegin(); + for (; it != store.get().extEnd(); ++it) + { + if (it->getGridX() < minX) + minX = it->getGridX(); + if (it->getGridX() > maxX) + maxX = it->getGridX(); + if (it->getGridY() < minY) + minY = it->getGridY(); + if (it->getGridY() > maxY) + maxY = it->getGridY(); + } + + std::filesystem::path infoPath = mWorldMapOutputDir / "mapInfo.yaml"; + std::ofstream file(infoPath); + + if (!file) + { + Log(Debug::Error) << "Failed to create world map info file: " << infoPath; + return; + } + + file << "width: " << width << "\n"; + file << "height: " << height << "\n"; + file << "pixelsPerCell: 32\n"; + file << "gridX:\n"; + file << " min: " << minX << "\n"; + file << " max: " << maxX << "\n"; + file << "gridY:\n"; + file << " min: " << minY << "\n"; + file << " max: " << maxY << "\n"; + file << "file: \"map.png\"\n"; + + file.close(); + + Log(Debug::Info) << "Saved world map info: " << infoPath; + } + + void MapExtractor::extractLocalMaps() + { + Log(Debug::Info) << "Extracting local maps..."; + + saveLocalMapTextures(); + + Log(Debug::Info) << "Local map extraction complete"; + } + + void MapExtractor::saveLocalMapTextures() + { + if (!mLocalMap) + { + Log(Debug::Error) << "Local map not initialized"; + return; + } + + const MWWorld::ESMStore& store = mWorld.getStore(); + int count = 0; + + MWWorld::Store::iterator it = store.get().extBegin(); + for (; it != store.get().extEnd(); ++it) + { + int x = it->getGridX(); + int y = it->getGridY(); + + osg::ref_ptr texture = mLocalMap->getMapTexture(x, y); + if (!texture || !texture->getImage()) + continue; + + std::ostringstream filename; + filename << "(" << x << "," << y << ").png"; + std::filesystem::path outputPath = mLocalMapOutputDir / filename.str(); + + if (osgDB::writeImageFile(*texture->getImage(), outputPath.string())) + { + count++; + if (count % 100 == 0) + Log(Debug::Info) << "Saved " << count << " local map textures..."; + } + } + + Log(Debug::Info) << "Saved " << count << " exterior local map textures"; + } +} diff --git a/apps/openmw/mapextractor.hpp b/apps/openmw/mapextractor.hpp new file mode 100644 index 0000000000..59013f4227 --- /dev/null +++ b/apps/openmw/mapextractor.hpp @@ -0,0 +1,54 @@ +#ifndef OPENMW_APPS_OPENMW_MAPEXTRACTOR_HPP +#define OPENMW_APPS_OPENMW_MAPEXTRACTOR_HPP + +#include +#include +#include + +namespace osg +{ + class Group; +} + +namespace SceneUtil +{ + class WorkQueue; +} + +namespace MWRender +{ + class GlobalMap; + class LocalMap; +} + +namespace MWWorld +{ + class World; +} + +namespace OMW +{ + class MapExtractor + { + public: + MapExtractor(MWWorld::World& world, const std::string& worldMapOutput, const std::string& localMapOutput); + ~MapExtractor(); + + void extractWorldMap(); + void extractLocalMaps(); + + private: + MWWorld::World& mWorld; + std::filesystem::path mWorldMapOutputDir; + std::filesystem::path mLocalMapOutputDir; + + std::unique_ptr mGlobalMap; + std::unique_ptr mLocalMap; + + void saveWorldMapTexture(); + void saveWorldMapInfo(); + void saveLocalMapTextures(); + }; +} + +#endif