1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2026-01-29 03:11:50 +00:00

Add generateTileWorldMap method for tiled world map generation

This commit is contained in:
Diject 2026-01-02 17:53:54 +03:00
parent b3392ebca5
commit 48f4487d26
5 changed files with 202 additions and 0 deletions

View file

@ -627,6 +627,9 @@ namespace MWBase
virtual void saveToLocalMapDir(std::string_view filename, std::string_view stringData) = 0;
///< Save string data to a file in the local map output directory
virtual void generateTileWorldMap() = 0;
///< Generate a tiled world map from local map tiles
};
}

View file

@ -355,6 +355,15 @@ namespace MWLua
"saveToLocalMapDirAction");
};
api["generateTileWorldMap"] = [context, lua = context.mLua]() {
checkGameInitialized(lua);
context.mLuaManager->addAction(
[] {
MWBase::Environment::get().getWorld()->generateTileWorldMap();
},
"generateTileWorldMapAction");
};
return LuaUtil::makeReadOnly(api);
}
}

View file

@ -6,7 +6,10 @@
#include <osg/ComputeBoundsVisitor>
#include <osg/Group>
#include <osg/Image>
#include <osg/Timer>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <MyGUI_TextIterator.h>
@ -3974,4 +3977,176 @@ namespace MWWorld
throw std::runtime_error("Failed to write to file: " + filePath.string());
}
}
void World::generateTileWorldMap()
{
std::filesystem::path localMapPath(mLocalMapOutputPath);
std::filesystem::path worldMapPath(mWorldMapOutputPath);
if (!std::filesystem::exists(localMapPath) || !std::filesystem::is_directory(localMapPath))
{
Log(Debug::Error) << "Local map directory does not exist: " << localMapPath;
return;
}
// Create world map output directory
std::filesystem::create_directories(worldMapPath);
// Step 1: Scan for all local map tiles and determine bounds
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();
std::map<std::pair<int, int>, std::filesystem::path> tileFiles;
for (const auto& entry : std::filesystem::directory_iterator(localMapPath))
{
if (!entry.is_regular_file())
continue;
std::string filename = entry.path().filename().string();
if (filename.size() < 7 || filename.front() != '(' || filename.substr(filename.size() - 4) != ".png")
continue;
// Parse (x,y).png format
size_t commaPos = filename.find(',');
if (commaPos == std::string::npos)
continue;
try
{
int x = std::stoi(filename.substr(1, commaPos - 1));
int y = std::stoi(filename.substr(commaPos + 1, filename.size() - commaPos - 6));
minX = std::min(minX, x);
maxX = std::max(maxX, x);
minY = std::min(minY, y);
maxY = std::max(maxY, y);
tileFiles[{x, y}] = entry.path();
}
catch (const std::exception& e)
{
Log(Debug::Warning) << "Failed to parse filename: " << filename << " - " << e.what();
continue;
}
}
if (tileFiles.empty())
{
Log(Debug::Warning) << "No local map tiles found in: " << localMapPath;
return;
}
Log(Debug::Info) << "Found " << tileFiles.size() << " local map tiles";
Log(Debug::Info) << "Bounds: X=[" << minX << ", " << maxX << "], Y=[" << minY << ", " << maxY << "]";
// Step 2: Create the output image
const int width = (maxX - minX + 1) * 32;
const int height = (maxY - minY + 1) * 32;
osg::ref_ptr<osg::Image> tilemapImage = new osg::Image;
tilemapImage->allocateImage(width, height, 1, GL_RGB, GL_UNSIGNED_BYTE);
// Fill with default color (0x2c2d28)
unsigned char* data = tilemapImage->data();
for (int i = 0; i < width * height; ++i)
{
data[i * 3 + 0] = 0x2c;
data[i * 3 + 1] = 0x2d;
data[i * 3 + 2] = 0x28;
}
// Step 3: Load each tile, downscale and place in output
for (const auto& [coords, filepath] : tileFiles)
{
int gridX = coords.first;
int gridY = coords.second;
osg::ref_ptr<osg::Image> tileImage = osgDB::readImageFile(filepath.string());
if (!tileImage || tileImage->s() == 0 || tileImage->t() == 0)
{
Log(Debug::Warning) << "Failed to load tile image: " << filepath;
continue;
}
// Downscale from 256x256 to 32x32
osg::ref_ptr<osg::Image> scaledTile = new osg::Image;
scaledTile->allocateImage(32, 32, 1, tileImage->getPixelFormat(), tileImage->getDataType());
// Simple nearest-neighbor downscaling
for (int y = 0; y < 32; ++y)
{
for (int x = 0; x < 32; ++x)
{
int srcX = (x * tileImage->s()) / 32;
int srcY = (y * tileImage->t()) / 32;
unsigned char* srcPixel = tileImage->data(srcX, srcY);
unsigned char* dstPixel = scaledTile->data(x, y);
int numComponents = osg::Image::computeNumComponents(tileImage->getPixelFormat());
for (int c = 0; c < numComponents && c < 3; ++c)
{
dstPixel[c] = srcPixel[c];
}
}
}
// Place scaled tile in output image
int destX = (gridX - minX) * 32;
int destY = (gridY - minY) * 32;
for (int y = 0; y < 32; ++y)
{
for (int x = 0; x < 32; ++x)
{
unsigned char* srcPixel = scaledTile->data(x, y);
unsigned char* dstPixel = tilemapImage->data(destX + x, destY + y);
dstPixel[0] = srcPixel[0];
dstPixel[1] = srcPixel[1];
dstPixel[2] = srcPixel[2];
}
}
}
// Step 4: Save tilemap.png
std::filesystem::path tilemapPath = worldMapPath / "tilemap.png";
if (osgDB::writeImageFile(*tilemapImage, tilemapPath.string()))
{
Log(Debug::Info) << "Successfully saved tilemap to: " << tilemapPath;
}
else
{
Log(Debug::Error) << "Failed to write tilemap to: " << tilemapPath;
return;
}
// Step 5: Save tilemapInfo.yaml
std::filesystem::path infoPath = worldMapPath / "tilemapInfo.yaml";
std::ofstream infoFile(infoPath);
if (!infoFile)
{
Log(Debug::Error) << "Failed to create tilemapInfo.yaml: " << infoPath;
return;
}
infoFile << "width: " << width << "\n";
infoFile << "height: " << height << "\n";
infoFile << "pixelsPerCell: 32\n";
infoFile << "gridX:\n";
infoFile << " min: " << minX << "\n";
infoFile << " max: " << maxX << "\n";
infoFile << "gridY:\n";
infoFile << " min: " << minY << "\n";
infoFile << " max: " << maxY << "\n";
infoFile << "file: \"tilemap.png\"\n";
infoFile.close();
Log(Debug::Info) << "Successfully saved tilemapInfo.yaml to: " << infoPath;
}
}

View file

@ -696,6 +696,7 @@ namespace MWWorld
bool isMapExtractionActive() const override;
void saveToLocalMapDir(std::string_view filename, std::string_view stringData) override;
void generateTileWorldMap() override;
};
}

View file

@ -326,5 +326,19 @@
-- local jsonData = '{"name": "Balmora", "type": "city"}'
-- world.saveToLocalMapDir("Balmora.json", jsonData)
---
-- Generate a tiled world map from local map tiles.
-- This function scans the local map directory for PNG files with the format "(gridX,gridY).png",
-- downscales each tile from 256x256 to 32x32 pixels, and composites them into a single world map image.
-- Areas without local maps are filled with color #2c2d28.
-- The result is saved as "tilemap.png" and "tilemapInfo.yaml" in the world map output directory.
-- @function [parent=#world] generateTileWorldMap
-- @usage
-- -- Generate tilemap from all local map tiles
-- world.generateTileWorldMap()
-- -- This will create:
-- -- - tilemap.png: The composite world map image
-- -- - tilemapInfo.yaml: Metadata including dimensions, grid bounds, and pixels per cell
return nil