1
0
mirror of https://gitlab.com/OpenMW/openmw.git synced 2025-01-06 00:55:50 +00:00

Move blendmap sampling logic into separate function

This commit is contained in:
elsid 2023-08-09 22:21:23 +02:00
parent 6180ba8a3e
commit ce6ffba986
No known key found for this signature in database
GPG Key ID: 4DE04C198CBA7625
4 changed files with 269 additions and 81 deletions

View File

@ -362,5 +362,130 @@ namespace ESMTerrain
Sample{ .mCellX = 0, .mCellY = 3, .mLocalX = 0, .mLocalY = 2, .mVertexX = 0, .mVertexY = 1 },
Sample{ .mCellX = 3, .mCellY = 3, .mLocalX = 2, .mLocalY = 2, .mVertexX = 1, .mVertexY = 1 }));
}
auto tie(const CellSample& v)
{
return std::tie(v.mCellX, v.mCellY, v.mSrcRow, v.mSrcCol, v.mDstRow, v.mDstCol);
}
}
static bool operator==(const CellSample& l, const CellSample& r)
{
return tie(l) == tie(r);
}
static std::ostream& operator<<(std::ostream& stream, const CellSample& v)
{
return stream << "CellSample{.mCellX = " << v.mCellX << ", .mCellY = " << v.mCellY
<< ", .mSrcRow = " << v.mSrcRow << ", .mSrcCol = " << v.mSrcCol << ", .mDstRow = " << v.mDstRow
<< ", .mDstCol = " << v.mDstCol << "}";
}
namespace
{
struct CollectCellSamples
{
std::vector<CellSample>& mSamples;
void operator()(const CellSample& value) { mSamples.push_back(value); }
};
TEST(ESMTerrainSampleBlendmaps, doesNotSupportNotPositiveSize)
{
const float size = 0;
EXPECT_THROW(sampleBlendmaps(size, 0, 0, 0, [](auto...) {}), std::invalid_argument);
}
TEST(ESMTerrainSampleBlendmaps, doesNotSupportNotPositiveTextureSize)
{
const float size = 1;
const int textureSize = 0;
EXPECT_THROW(sampleBlendmaps(size, 0, 0, textureSize, [](auto...) {}), std::invalid_argument);
}
TEST(ESMTerrainSampleBlendmaps, shouldDecrementBeginRow)
{
const float size = 0.125f;
const float minX = 0.125f;
const float minY = 0.125f;
const int textureSize = 8;
std::vector<CellSample> samples;
sampleBlendmaps(size, minX, minY, textureSize, CollectCellSamples{ samples });
EXPECT_THAT(samples,
ElementsAre( //
CellSample{ .mCellX = 0, .mCellY = 0, .mSrcRow = 0, .mSrcCol = 1, .mDstRow = 0, .mDstCol = 0 },
CellSample{ .mCellX = 0, .mCellY = 0, .mSrcRow = 1, .mSrcCol = 1, .mDstRow = 1, .mDstCol = 0 },
CellSample{ .mCellX = 0, .mCellY = 0, .mSrcRow = 0, .mSrcCol = 2, .mDstRow = 0, .mDstCol = 1 },
CellSample{ .mCellX = 0, .mCellY = 0, .mSrcRow = 1, .mSrcCol = 2, .mDstRow = 1, .mDstCol = 1 }));
}
TEST(ESMTerrainSampleBlendmaps, shouldDecrementBeginRowOverCellBorder)
{
const float size = 0.125f;
const float minX = 0;
const float minY = 0;
const int textureSize = 8;
std::vector<CellSample> samples;
sampleBlendmaps(size, minX, minY, textureSize, CollectCellSamples{ samples });
EXPECT_THAT(samples,
ElementsAre( //
CellSample{ .mCellX = -1, .mCellY = 0, .mSrcRow = 7, .mSrcCol = 0, .mDstRow = 0, .mDstCol = 0 },
CellSample{ .mCellX = 0, .mCellY = 0, .mSrcRow = 0, .mSrcCol = 0, .mDstRow = 1, .mDstCol = 0 },
CellSample{ .mCellX = -1, .mCellY = 0, .mSrcRow = 7, .mSrcCol = 1, .mDstRow = 0, .mDstCol = 1 },
CellSample{ .mCellX = 0, .mCellY = 0, .mSrcRow = 0, .mSrcCol = 1, .mDstRow = 1, .mDstCol = 1 }));
}
TEST(ESMTerrainSampleBlendmaps, shouldSupportNegativeCoordinates)
{
const float size = 0.125f;
const float minX = -0.5f;
const float minY = -0.5f;
const int textureSize = 8;
std::vector<CellSample> samples;
sampleBlendmaps(size, minX, minY, textureSize, CollectCellSamples{ samples });
EXPECT_THAT(samples,
ElementsAre( //
CellSample{ .mCellX = -1, .mCellY = -1, .mSrcRow = 3, .mSrcCol = 4, .mDstRow = 0, .mDstCol = 0 },
CellSample{ .mCellX = -1, .mCellY = -1, .mSrcRow = 4, .mSrcCol = 4, .mDstRow = 1, .mDstCol = 0 },
CellSample{ .mCellX = -1, .mCellY = -1, .mSrcRow = 3, .mSrcCol = 5, .mDstRow = 0, .mDstCol = 1 },
CellSample{ .mCellX = -1, .mCellY = -1, .mSrcRow = 4, .mSrcCol = 5, .mDstRow = 1, .mDstCol = 1 }));
}
TEST(ESMTerrainSampleBlendmaps, shouldCoverMultipleCells)
{
const float size = 2;
const float minX = -1.5f;
const float minY = -1.5f;
const int textureSize = 2;
std::vector<CellSample> samples;
sampleBlendmaps(size, minX, minY, textureSize, CollectCellSamples{ samples });
EXPECT_THAT(samples,
ElementsAre( //
CellSample{ .mCellX = -2, .mCellY = -2, .mSrcRow = 0, .mSrcCol = 1, .mDstRow = 0, .mDstCol = 0 },
CellSample{ .mCellX = -2, .mCellY = -2, .mSrcRow = 1, .mSrcCol = 1, .mDstRow = 1, .mDstCol = 0 },
CellSample{ .mCellX = -1, .mCellY = -2, .mSrcRow = 0, .mSrcCol = 1, .mDstRow = 2, .mDstCol = 0 },
CellSample{ .mCellX = -1, .mCellY = -2, .mSrcRow = 1, .mSrcCol = 1, .mDstRow = 3, .mDstCol = 0 },
CellSample{ .mCellX = 0, .mCellY = -2, .mSrcRow = 0, .mSrcCol = 1, .mDstRow = 4, .mDstCol = 0 },
CellSample{ .mCellX = -2, .mCellY = -1, .mSrcRow = 0, .mSrcCol = 0, .mDstRow = 0, .mDstCol = 1 },
CellSample{ .mCellX = -2, .mCellY = -1, .mSrcRow = 1, .mSrcCol = 0, .mDstRow = 1, .mDstCol = 1 },
CellSample{ .mCellX = -1, .mCellY = -1, .mSrcRow = 0, .mSrcCol = 0, .mDstRow = 2, .mDstCol = 1 },
CellSample{ .mCellX = -1, .mCellY = -1, .mSrcRow = 1, .mSrcCol = 0, .mDstRow = 3, .mDstCol = 1 },
CellSample{ .mCellX = 0, .mCellY = -1, .mSrcRow = 0, .mSrcCol = 0, .mDstRow = 4, .mDstCol = 1 },
CellSample{ .mCellX = -2, .mCellY = -1, .mSrcRow = 0, .mSrcCol = 1, .mDstRow = 0, .mDstCol = 2 },
CellSample{ .mCellX = -2, .mCellY = -1, .mSrcRow = 1, .mSrcCol = 1, .mDstRow = 1, .mDstCol = 2 },
CellSample{ .mCellX = -1, .mCellY = -1, .mSrcRow = 0, .mSrcCol = 1, .mDstRow = 2, .mDstCol = 2 },
CellSample{ .mCellX = -1, .mCellY = -1, .mSrcRow = 1, .mSrcCol = 1, .mDstRow = 3, .mDstCol = 2 },
CellSample{ .mCellX = 0, .mCellY = -1, .mSrcRow = 0, .mSrcCol = 1, .mDstRow = 4, .mDstCol = 2 },
CellSample{ .mCellX = -2, .mCellY = 0, .mSrcRow = 0, .mSrcCol = 0, .mDstRow = 0, .mDstCol = 3 },
CellSample{ .mCellX = -2, .mCellY = 0, .mSrcRow = 1, .mSrcCol = 0, .mDstRow = 1, .mDstCol = 3 },
CellSample{ .mCellX = -1, .mCellY = 0, .mSrcRow = 0, .mSrcCol = 0, .mDstRow = 2, .mDstCol = 3 },
CellSample{ .mCellX = -1, .mCellY = 0, .mSrcRow = 1, .mSrcCol = 0, .mDstRow = 3, .mDstCol = 3 },
CellSample{ .mCellX = 0, .mCellY = 0, .mSrcRow = 0, .mSrcCol = 0, .mDstRow = 4, .mDstCol = 3 },
CellSample{ .mCellX = -2, .mCellY = 0, .mSrcRow = 0, .mSrcCol = 1, .mDstRow = 0, .mDstCol = 4 },
CellSample{ .mCellX = -2, .mCellY = 0, .mSrcRow = 1, .mSrcCol = 1, .mDstRow = 1, .mDstCol = 4 },
CellSample{ .mCellX = -1, .mCellY = 0, .mSrcRow = 0, .mSrcCol = 1, .mDstRow = 2, .mDstCol = 4 },
CellSample{ .mCellX = -1, .mCellY = 0, .mSrcRow = 1, .mSrcCol = 1, .mDstRow = 3, .mDstCol = 4 },
CellSample{ .mCellX = 0, .mCellY = 0, .mSrcRow = 0, .mSrcCol = 1, .mDstRow = 4, .mDstCol = 4 }));
}
}
}

View File

@ -4,6 +4,7 @@
#include <components/misc/mathutil.hpp>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <stdexcept>
#include <string>
@ -38,6 +39,16 @@ namespace ESMTerrain
}
}
struct CellSample
{
int mCellX;
int mCellY;
std::size_t mSrcRow;
std::size_t mSrcCol;
std::size_t mDstRow;
std::size_t mDstCol;
};
template <class F>
void sampleCellGridSimple(std::size_t cellSize, std::size_t sampleSize, std::size_t beginX, std::size_t beginY,
std::size_t endX, std::size_t endY, F&& f)
@ -115,6 +126,71 @@ namespace ESMTerrain
baseVertY = vertY + 1;
}
}
inline int getBlendmapSize(float size, int textureSize)
{
return static_cast<int>(textureSize * size) + 1;
}
inline void adjustTextureCoordinates(int textureSize, int& cellX, int& cellY, int& x, int& y)
{
--x;
if (x < 0)
{
--cellX;
x += textureSize;
}
while (x >= textureSize)
{
++cellX;
x -= textureSize;
}
while (y >= textureSize)
{
++cellY;
y -= textureSize;
}
}
template <class F>
void sampleBlendmaps(float size, float minX, float minY, int textureSize, F&& f)
{
if (size <= 0)
throw std::invalid_argument("Invalid size for blendmap sampling: " + std::to_string(size));
if (textureSize <= 0)
throw std::invalid_argument("Invalid texture size for blendmap sampling: " + std::to_string(textureSize));
const int beginCellX = static_cast<int>(std::floor(minX));
const int beginCellY = static_cast<int>(std::floor(minY));
const int beginRow = static_cast<int>((minX - beginCellX) * (textureSize + 1));
const int beginCol = static_cast<int>((minY - beginCellY) * (textureSize + 1));
const int blendmapSize = getBlendmapSize(size, textureSize);
for (int y = 0; y < blendmapSize; y++)
{
for (int x = 0; x < blendmapSize; x++)
{
int cellX = beginCellX;
int cellY = beginCellY;
int srcX = x + beginRow;
int srcY = y + beginCol;
adjustTextureCoordinates(textureSize, cellX, cellY, srcX, srcY);
f(CellSample{
.mCellX = cellX,
.mCellY = cellY,
.mSrcRow = static_cast<std::size_t>(srcX),
.mSrcCol = static_cast<std::size_t>(srcY),
.mDstRow = static_cast<std::size_t>(x),
.mDstCol = static_cast<std::size_t>(y),
});
}
}
}
}
#endif

View File

@ -17,6 +17,27 @@
namespace ESMTerrain
{
namespace
{
UniqueTextureId getTextureIdAt(const LandObject* land, std::size_t x, std::size_t y)
{
assert(x < ESM::Land::LAND_TEXTURE_SIZE);
assert(y < ESM::Land::LAND_TEXTURE_SIZE);
if (land == nullptr)
return { 0, 0 };
const ESM::LandData* data = land->getData(ESM::Land::DATA_VTEX);
if (data == nullptr)
return { 0, 0 };
const std::uint16_t tex = data->getTextures()[y * ESM::Land::LAND_TEXTURE_SIZE + x];
if (tex == 0)
return { 0, 0 }; // vtex 0 is always the base texture, regardless of plugin
return { tex, land->getPlugin() };
}
}
class LandCache
{
@ -306,47 +327,6 @@ namespace ESMTerrain
std::fill(positions.begin(), positions.end(), osg::Vec3f());
}
Storage::UniqueTextureId Storage::getVtexIndexAt(
ESM::ExteriorCellLocation cellLocation, const LandObject* land, int x, int y, LandCache& cache)
{
// For the first/last row/column, we need to get the texture from the neighbour cell
// to get consistent blending at the borders
--x;
ESM::ExteriorCellLocation cellLocationIn = cellLocation;
if (x < 0)
{
--cellLocation.mX;
x += ESM::Land::LAND_TEXTURE_SIZE;
}
while (x >= ESM::Land::LAND_TEXTURE_SIZE)
{
++cellLocation.mX;
x -= ESM::Land::LAND_TEXTURE_SIZE;
}
while (
y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not?
{
++cellLocation.mY;
y -= ESM::Land::LAND_TEXTURE_SIZE;
}
if (cellLocation != cellLocationIn)
land = getLand(cellLocation, cache);
assert(x < ESM::Land::LAND_TEXTURE_SIZE);
assert(y < ESM::Land::LAND_TEXTURE_SIZE);
const ESM::LandData* data = land ? land->getData(ESM::Land::DATA_VTEX) : nullptr;
if (data)
{
int tex = data->getTextures()[y * ESM::Land::LAND_TEXTURE_SIZE + x];
if (tex == 0)
return std::make_pair(0, 0); // vtex 0 is always the base texture, regardless of plugin
return std::make_pair(tex, land->getPlugin());
}
return std::make_pair(0, 0);
}
std::string Storage::getTextureName(UniqueTextureId id)
{
static constexpr char defaultTexture[] = "textures\\_land_default.dds";
@ -371,31 +351,40 @@ namespace ESMTerrain
void Storage::getBlendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps,
std::vector<Terrain::LayerInfo>& layerList, ESM::RefId worldspace)
{
osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize / 2.f, chunkSize / 2.f);
int cellX = static_cast<int>(std::floor(origin.x()));
int cellY = static_cast<int>(std::floor(origin.y()));
int realTextureSize = ESM::Land::LAND_TEXTURE_SIZE + 1; // add 1 to wrap around next cell
int rowStart = (origin.x() - cellX) * realTextureSize;
int colStart = (origin.y() - cellY) * realTextureSize;
const int blendmapSize = (realTextureSize - 1) * chunkSize + 1;
const osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize, chunkSize) * 0.5f;
const int startCellX = static_cast<int>(std::floor(origin.x()));
const int startCellY = static_cast<int>(std::floor(origin.y()));
const std::size_t blendmapSize = getBlendmapSize(chunkSize, ESM::Land::LAND_TEXTURE_SIZE);
// We need to upscale the blendmap 2x with nearest neighbor sampling to look like Vanilla
const int imageScaleFactor = 2;
const int blendmapImageSize = blendmapSize * imageScaleFactor;
constexpr std::size_t imageScaleFactor = 2;
const std::size_t blendmapImageSize = blendmapSize * imageScaleFactor;
std::vector<UniqueTextureId> textureIds(blendmapSize * blendmapSize);
LandCache cache;
std::map<UniqueTextureId, unsigned int> textureIndicesMap;
ESM::ExteriorCellLocation cellLocation(cellX, cellY, worldspace);
std::pair lastCell{ startCellX, startCellY };
const LandObject* land = getLand(ESM::ExteriorCellLocation(startCellX, startCellY, worldspace), cache);
const LandObject* land = getLand(cellLocation, cache);
for (int y = 0; y < blendmapSize; y++)
{
for (int x = 0; x < blendmapSize; x++)
const auto handleSample = [&](const CellSample& sample) {
const std::pair cell{ sample.mCellX, sample.mCellY };
if (lastCell != cell)
{
UniqueTextureId id = getVtexIndexAt(cellLocation, land, x + rowStart, y + colStart, cache);
land = getLand(ESM::ExteriorCellLocation(sample.mCellX, sample.mCellY, worldspace), cache);
lastCell = cell;
}
textureIds[sample.mDstCol * blendmapSize + sample.mDstRow]
= getTextureIdAt(land, sample.mSrcRow, sample.mSrcCol);
};
sampleBlendmaps(chunkSize, origin.x(), origin.y(), ESM::Land::LAND_TEXTURE_SIZE, handleSample);
std::map<UniqueTextureId, unsigned int> textureIndicesMap;
for (std::size_t y = 0; y < blendmapSize; ++y)
{
for (std::size_t x = 0; x < blendmapSize; ++x)
{
const UniqueTextureId id = textureIds[y * blendmapSize + x];
std::map<UniqueTextureId, unsigned int>::iterator found = textureIndicesMap.find(id);
if (found == textureIndicesMap.end())
{
@ -417,21 +406,21 @@ namespace ESMTerrain
if (layerIndex >= layerList.size())
{
osg::ref_ptr<osg::Image> image(new osg::Image);
image->allocateImage(blendmapImageSize, blendmapImageSize, 1, GL_ALPHA, GL_UNSIGNED_BYTE);
unsigned char* pData = image->data();
memset(pData, 0, image->getTotalDataSize());
blendmaps.emplace_back(image);
layerList.emplace_back(info);
image->allocateImage(static_cast<int>(blendmapImageSize), static_cast<int>(blendmapImageSize),
1, GL_ALPHA, GL_UNSIGNED_BYTE);
std::memset(image->data(), 0, image->getTotalDataSize());
blendmaps.push_back(std::move(image));
layerList.push_back(std::move(info));
}
}
unsigned int layerIndex = found->second;
unsigned char* pData = blendmaps[layerIndex]->data();
int realY = (blendmapSize - y - 1) * imageScaleFactor;
int realX = x * imageScaleFactor;
pData[((realY + 0) * blendmapImageSize + realX + 0)] = 255;
pData[((realY + 1) * blendmapImageSize + realX + 0)] = 255;
pData[((realY + 0) * blendmapImageSize + realX + 1)] = 255;
pData[((realY + 1) * blendmapImageSize + realX + 1)] = 255;
const unsigned int layerIndex = found->second;
unsigned char* const data = blendmaps[layerIndex]->data();
const std::size_t realY = (blendmapSize - y - 1) * imageScaleFactor;
const std::size_t realX = x * imageScaleFactor;
data[((realY + 0) * blendmapImageSize + realX + 0)] = 255;
data[((realY + 1) * blendmapImageSize + realX + 0)] = 255;
data[((realY + 0) * blendmapImageSize + realX + 1)] = 255;
data[((realY + 1) * blendmapImageSize + realX + 1)] = 255;
}
}

View File

@ -62,6 +62,11 @@ namespace ESMTerrain
ESM::LandData mData;
};
// Since plugins can define new texture palettes, we need to know the plugin index too
// in order to retrieve the correct texture name.
// pair <texture id, plugin id>
using UniqueTextureId = std::pair<std::uint16_t, int>;
/// @brief Feeds data from ESM terrain records (ESM::Land, ESM::LandTexture)
/// into the terrain component, converting it on the fly as needed.
class Storage : public Terrain::Storage
@ -146,13 +151,6 @@ namespace ESMTerrain
virtual void adjustColor(int col, int row, const ESM::LandData* heightData, osg::Vec4ub& color) const;
virtual float getAlteredHeight(int col, int row) const;
// Since plugins can define new texture palettes, we need to know the plugin index too
// in order to retrieve the correct texture name.
// pair <texture id, plugin id>
typedef std::pair<short, short> UniqueTextureId;
inline UniqueTextureId getVtexIndexAt(
ESM::ExteriorCellLocation cellLocation, const LandObject* land, int x, int y, LandCache&);
std::string getTextureName(UniqueTextureId id);
std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;