1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2026-01-24 20:30:54 +00:00

Add world map extraction feature to OpenMW

This commit is contained in:
Diject 2025-12-24 15:09:27 +03:00
parent 09aa9484e6
commit c613c5decc
5 changed files with 296 additions and 3 deletions

View file

@ -1,6 +1,7 @@
set(OPENMW_SOURCES
engine.cpp
options.cpp
mapextractor.cpp
)
set(OPENMW_RESOURCES

View file

@ -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;
}

View file

@ -2,6 +2,7 @@
#include <components/fallback/fallback.hpp>
#include <components/fallback/validate.hpp>
#include <components/files/configurationmanager.hpp>
#include <components/files/conversion.hpp>
#include <components/misc/osgpluginchecker.hpp>
#include <components/misc/rng.hpp>
#include <components/platform/platform.hpp>
@ -158,6 +159,20 @@ bool parseOptions(int argc, char** argv, OMW::Engine& engine, Files::Configurati
engine.enableFontExport(variables["export-fonts"].as<bool>());
engine.setRandomSeed(variables["random-seed"].as<unsigned int>());
std::string worldMapOutput = variables["world-map-output"].as<std::string>();
std::string localMapOutput = variables["local-map-output"].as<std::string>();
bool extractMaps = variables["extract-maps"].as<bool>();
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;
}

View file

@ -0,0 +1,192 @@
#include "mapextractor.hpp"
#include <fstream>
#include <iomanip>
#include <osg/Group>
#include <osg/Image>
#include <osg/Texture2D>
#include <osgDB/WriteFile>
#include <components/debug/debuglog.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/misc/constants.hpp>
#include <components/sceneutil/lightmanager.hpp>
#include <components/sceneutil/workqueue.hpp>
#include <components/settings/values.hpp>
#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<MWRender::GlobalMap>(root, workQueue);
mLocalMap = std::make_unique<MWRender::LocalMap>(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<osg::Texture2D> 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<int>::max();
int maxX = std::numeric_limits<int>::min();
int minY = std::numeric_limits<int>::max();
int maxY = std::numeric_limits<int>::min();
MWWorld::Store<ESM::Cell>::iterator it = store.get<ESM::Cell>().extBegin();
for (; it != store.get<ESM::Cell>().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<ESM::Cell>::iterator it = store.get<ESM::Cell>().extBegin();
for (; it != store.get<ESM::Cell>().extEnd(); ++it)
{
int x = it->getGridX();
int y = it->getGridY();
osg::ref_ptr<osg::Texture2D> 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";
}
}

View file

@ -0,0 +1,54 @@
#ifndef OPENMW_APPS_OPENMW_MAPEXTRACTOR_HPP
#define OPENMW_APPS_OPENMW_MAPEXTRACTOR_HPP
#include <filesystem>
#include <memory>
#include <string>
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<MWRender::GlobalMap> mGlobalMap;
std::unique_ptr<MWRender::LocalMap> mLocalMap;
void saveWorldMapTexture();
void saveWorldMapInfo();
void saveLocalMapTextures();
};
}
#endif