From c10f28a4130d2dc1fb842c316edaa72b45626a50 Mon Sep 17 00:00:00 2001 From: elsid Date: Thu, 3 Aug 2023 23:04:29 +0200 Subject: [PATCH 01/14] Pass references to fillVertexBuffers instead of osg::ref_ptr --- components/esm3terrain/storage.cpp | 17 ++++++++--------- components/esm3terrain/storage.hpp | 3 +-- components/terrain/chunkmanager.cpp | 2 +- components/terrain/storage.hpp | 3 +-- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/components/esm3terrain/storage.cpp b/components/esm3terrain/storage.cpp index 71051df847..dc68fccf92 100644 --- a/components/esm3terrain/storage.cpp +++ b/components/esm3terrain/storage.cpp @@ -184,8 +184,7 @@ namespace ESMTerrain } void Storage::fillVertexBuffers(int lodLevel, float size, const osg::Vec2f& center, ESM::RefId worldspace, - osg::ref_ptr positions, osg::ref_ptr normals, - osg::ref_ptr colours) + osg::Vec3Array& positions, osg::Vec3Array& normals, osg::Vec4ubArray& colours) { // LOD level n means every 2^n-th vertex is kept size_t increment = static_cast(1) << lodLevel; @@ -199,9 +198,9 @@ namespace ESMTerrain size_t numVerts = static_cast(size * (landSize - 1) / increment + 1); - positions->resize(numVerts * numVerts); - normals->resize(numVerts * numVerts); - colours->resize(numVerts * numVerts); + positions.resize(numVerts * numVerts); + normals.resize(numVerts * numVerts); + colours.resize(numVerts * numVerts); osg::Vec3f normal; osg::Vec4ub color; @@ -269,7 +268,7 @@ namespace ESMTerrain height = heightData->getHeights()[col * landSize + row]; if (alteration) height += getAlteredHeight(col, row); - (*positions)[static_cast(vertX * numVerts + vertY)] + positions[static_cast(vertX * numVerts + vertY)] = osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * LandSizeInUnits, (vertY / float(numVerts - 1) - 0.5f) * size * LandSizeInUnits, height); @@ -293,7 +292,7 @@ namespace ESMTerrain assert(normal.z() > 0); - (*normals)[static_cast(vertX * numVerts + vertY)] = normal; + normals[static_cast(vertX * numVerts + vertY)] = normal; if (colourData) { @@ -315,7 +314,7 @@ namespace ESMTerrain color.a() = 255; - (*colours)[static_cast(vertX * numVerts + vertY)] = color; + colours[static_cast(vertX * numVerts + vertY)] = color; ++vertX; } @@ -333,7 +332,7 @@ namespace ESMTerrain { for (unsigned int iVert = 0; iVert < numVerts * numVerts; iVert++) { - (*positions)[static_cast(iVert)] = osg::Vec3f(0.f, 0.f, 0.f); + positions[static_cast(iVert)] = osg::Vec3f(0.f, 0.f, 0.f); } } } diff --git a/components/esm3terrain/storage.hpp b/components/esm3terrain/storage.hpp index 5efa996c6f..17b94a7ee5 100644 --- a/components/esm3terrain/storage.hpp +++ b/components/esm3terrain/storage.hpp @@ -99,8 +99,7 @@ namespace ESMTerrain /// @param normals buffer to write vertex normals /// @param colours buffer to write vertex colours void fillVertexBuffers(int lodLevel, float size, const osg::Vec2f& center, ESM::RefId worldspace, - osg::ref_ptr positions, osg::ref_ptr normals, - osg::ref_ptr colours) override; + osg::Vec3Array& positions, osg::Vec3Array& normals, osg::Vec4ubArray& colours) override; /// Create textures holding layer blend values for a terrain chunk. /// @note The terrain chunk shouldn't be larger than one cell since otherwise we might diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 03643390bf..0364a0a4fd 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -213,7 +213,7 @@ namespace Terrain osg::ref_ptr colors(new osg::Vec4ubArray); colors->setNormalize(true); - mStorage->fillVertexBuffers(lod, chunkSize, chunkCenter, mWorldspace, positions, normals, colors); + mStorage->fillVertexBuffers(lod, chunkSize, chunkCenter, mWorldspace, *positions, *normals, *colors); osg::ref_ptr vbo(new osg::VertexBufferObject); positions->setVertexBufferObject(vbo); diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index c4a44f5024..7a99478929 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -64,8 +64,7 @@ namespace Terrain /// @param normals buffer to write vertex normals /// @param colours buffer to write vertex colours virtual void fillVertexBuffers(int lodLevel, float size, const osg::Vec2f& center, ESM::RefId worldspace, - osg::ref_ptr positions, osg::ref_ptr normals, - osg::ref_ptr colours) + osg::Vec3Array& positions, osg::Vec3Array& normals, osg::Vec4ubArray& colours) = 0; typedef std::vector> ImageVector; From 51b24c2b704c5a97a942c1d2c86c3130dfc2d8b2 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 30 Jul 2023 20:07:20 +0200 Subject: [PATCH 02/14] Fix variable name --- components/esm3terrain/storage.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/components/esm3terrain/storage.cpp b/components/esm3terrain/storage.cpp index dc68fccf92..df90f2d4ca 100644 --- a/components/esm3terrain/storage.cpp +++ b/components/esm3terrain/storage.cpp @@ -194,7 +194,7 @@ namespace ESMTerrain int startCellX = static_cast(std::floor(origin.x())); int startCellY = static_cast(std::floor(origin.y())); const int landSize = ESM::getLandSize(worldspace); - const int LandSizeInUnits = ESM::getCellSize(worldspace); + const int landSizeInUnits = ESM::getCellSize(worldspace); size_t numVerts = static_cast(size * (landSize - 1) / increment + 1); @@ -268,9 +268,10 @@ namespace ESMTerrain height = heightData->getHeights()[col * landSize + row]; if (alteration) height += getAlteredHeight(col, row); + positions[static_cast(vertX * numVerts + vertY)] - = osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * LandSizeInUnits, - (vertY / float(numVerts - 1) - 0.5f) * size * LandSizeInUnits, height); + = osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * landSizeInUnits, + (vertY / float(numVerts - 1) - 0.5f) * size * landSizeInUnits, height); if (normalData) { From a23c98d468e0d40c571affdf82932c1b15ea33ac Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 1 Aug 2023 20:36:25 +0200 Subject: [PATCH 03/14] Use std::size_t type for vertex coordinates --- components/esm3terrain/storage.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/components/esm3terrain/storage.cpp b/components/esm3terrain/storage.cpp index df90f2d4ca..c589bc383a 100644 --- a/components/esm3terrain/storage.cpp +++ b/components/esm3terrain/storage.cpp @@ -196,7 +196,7 @@ namespace ESMTerrain const int landSize = ESM::getLandSize(worldspace); const int landSizeInUnits = ESM::getCellSize(worldspace); - size_t numVerts = static_cast(size * (landSize - 1) / increment + 1); + const std::size_t numVerts = static_cast(size * (landSize - 1) / increment + 1); positions.resize(numVerts * numVerts); normals.resize(numVerts * numVerts); @@ -205,17 +205,17 @@ namespace ESMTerrain osg::Vec3f normal; osg::Vec4ub color; - float vertY = 0; - float vertX = 0; + std::size_t vertY = 0; + std::size_t vertX = 0; LandCache cache; bool alteration = useAlteration(); bool validHeightDataExists = false; - float vertY_ = 0; // of current cell corner + std::size_t vertY_ = 0; // of current cell corner for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY) { - float vertX_ = 0; // of current cell corner + std::size_t vertX_ = 0; // of current cell corner for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX) { ESM::ExteriorCellLocation cellLocation(cellX, cellY, worldspace); @@ -263,15 +263,15 @@ namespace ESMTerrain assert(vertX < numVerts); assert(vertY < numVerts); + const std::size_t vertIndex = vertX * numVerts + vertY; + float height = defaultHeight; if (heightData) height = heightData->getHeights()[col * landSize + row]; if (alteration) height += getAlteredHeight(col, row); - - positions[static_cast(vertX * numVerts + vertY)] - = osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * landSizeInUnits, - (vertY / float(numVerts - 1) - 0.5f) * size * landSizeInUnits, height); + positions[vertIndex] = osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * landSizeInUnits, + (vertY / float(numVerts - 1) - 0.5f) * size * landSizeInUnits, height); if (normalData) { @@ -293,7 +293,7 @@ namespace ESMTerrain assert(normal.z() > 0); - normals[static_cast(vertX * numVerts + vertY)] = normal; + normals[vertIndex] = normal; if (colourData) { @@ -315,7 +315,7 @@ namespace ESMTerrain color.a() = 255; - colours[static_cast(vertX * numVerts + vertY)] = color; + colours[vertIndex] = color; ++vertX; } From 829f325500c575a135d1d7ce50f040b897acc290 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 1 Aug 2023 20:38:37 +0200 Subject: [PATCH 04/14] Use proper name and scope for vertex coordinate variables --- components/esm3terrain/storage.cpp | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/components/esm3terrain/storage.cpp b/components/esm3terrain/storage.cpp index c589bc383a..b6ee48431b 100644 --- a/components/esm3terrain/storage.cpp +++ b/components/esm3terrain/storage.cpp @@ -205,17 +205,15 @@ namespace ESMTerrain osg::Vec3f normal; osg::Vec4ub color; - std::size_t vertY = 0; - std::size_t vertX = 0; - LandCache cache; bool alteration = useAlteration(); bool validHeightDataExists = false; - std::size_t vertY_ = 0; // of current cell corner + std::size_t baseVertY = 0; // of current cell corner for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY) { - std::size_t vertX_ = 0; // of current cell corner + std::size_t baseVertX = 0; // of current cell corner + std::size_t vertY = baseVertY; for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX) { ESM::ExteriorCellLocation cellLocation(cellX, cellY, worldspace); @@ -236,9 +234,9 @@ namespace ESMTerrain // Skip the first row / column unless we're at a chunk edge, // since this row / column is already contained in a previous cell // This is only relevant if we're creating a chunk spanning multiple cells - if (vertY_ != 0) + if (baseVertY != 0) colStart += increment; - if (vertX_ != 0) + if (baseVertX != 0) rowStart += increment; // Only relevant for chunks smaller than (contained in) one cell @@ -249,10 +247,11 @@ namespace ESMTerrain int colEnd = std::min( static_cast(colStart + std::min(1.f, size) * (landSize - 1) + 1), static_cast(landSize)); - vertY = vertY_; + vertY = baseVertY; + std::size_t vertX = baseVertX; for (int col = colStart; col < colEnd; col += increment) { - vertX = vertX_; + vertX = baseVertX; for (int row = rowStart; row < rowEnd; row += increment) { int srcArrayIndex = col * landSize * 3 + row * 3; @@ -321,13 +320,13 @@ namespace ESMTerrain } ++vertY; } - vertX_ = vertX; + baseVertX = vertX; } - vertY_ = vertY; + baseVertY = vertY; - assert(vertX_ == numVerts); // Ensure we covered whole area + assert(baseVertX == numVerts); // Ensure we covered whole area } - assert(vertY_ == numVerts); // Ensure we covered whole area + assert(baseVertY == numVerts); // Ensure we covered whole area if (!validHeightDataExists && ESM::isEsm4Ext(worldspace)) { From e4584ce5dd88b55a1ef9f7ab31304d20758affcd Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 1 Aug 2023 20:47:16 +0200 Subject: [PATCH 05/14] Reduce scope for normal and color --- components/esm3terrain/storage.cpp | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/components/esm3terrain/storage.cpp b/components/esm3terrain/storage.cpp index b6ee48431b..a9f8e5e609 100644 --- a/components/esm3terrain/storage.cpp +++ b/components/esm3terrain/storage.cpp @@ -202,9 +202,6 @@ namespace ESMTerrain normals.resize(numVerts * numVerts); colours.resize(numVerts * numVerts); - osg::Vec3f normal; - osg::Vec4ub color; - LandCache cache; bool alteration = useAlteration(); @@ -272,15 +269,15 @@ namespace ESMTerrain positions[vertIndex] = osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * landSizeInUnits, (vertY / float(numVerts - 1) - 0.5f) * size * landSizeInUnits, height); - if (normalData) + osg::Vec3f normal(0, 0, 1); + + if (normalData != nullptr) { for (int i = 0; i < 3; ++i) normal[i] = normalData->getNormals()[srcArrayIndex + i]; normal.normalize(); } - else - normal = osg::Vec3f(0, 0, 1); // Normals apparently don't connect seamlessly between cells if (col == landSize - 1 || row == landSize - 1) @@ -294,17 +291,12 @@ namespace ESMTerrain normals[vertIndex] = normal; - if (colourData) - { + osg::Vec4ub color(255, 255, 255, 255); + + if (colourData != nullptr) for (int i = 0; i < 3; ++i) color[i] = colourData->getColors()[srcArrayIndex + i]; - } - else - { - color.r() = 255; - color.g() = 255; - color.b() = 255; - } + if (alteration) adjustColor(col, row, heightData, color); // Does nothing by default, override in OpenMW-CS @@ -312,8 +304,6 @@ namespace ESMTerrain if (col == landSize - 1 || row == landSize - 1) fixColour(color, cellLocation, col, row, cache); - color.a() = 255; - colours[vertIndex] = color; ++vertX; From d3e61e4578fd140fdd0a0f71f2b896e00ba49a8e Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 1 Aug 2023 20:49:08 +0200 Subject: [PATCH 06/14] Replace C-style cast by static_cast --- components/esm3terrain/storage.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/components/esm3terrain/storage.cpp b/components/esm3terrain/storage.cpp index a9f8e5e609..e5b80635c3 100644 --- a/components/esm3terrain/storage.cpp +++ b/components/esm3terrain/storage.cpp @@ -266,8 +266,9 @@ namespace ESMTerrain height = heightData->getHeights()[col * landSize + row]; if (alteration) height += getAlteredHeight(col, row); - positions[vertIndex] = osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * landSizeInUnits, - (vertY / float(numVerts - 1) - 0.5f) * size * landSizeInUnits, height); + positions[vertIndex] + = osg::Vec3f((vertX / static_cast(numVerts - 1) - 0.5f) * size * landSizeInUnits, + (vertY / static_cast(numVerts - 1) - 0.5f) * size * landSizeInUnits, height); osg::Vec3f normal(0, 0, 1); From 4a2a320e08645036c6529ec379d16bd2b392b246 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 1 Aug 2023 20:50:43 +0200 Subject: [PATCH 07/14] Use const where possible --- components/esm3terrain/storage.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/components/esm3terrain/storage.cpp b/components/esm3terrain/storage.cpp index e5b80635c3..d26994a771 100644 --- a/components/esm3terrain/storage.cpp +++ b/components/esm3terrain/storage.cpp @@ -187,12 +187,12 @@ namespace ESMTerrain osg::Vec3Array& positions, osg::Vec3Array& normals, osg::Vec4ubArray& colours) { // LOD level n means every 2^n-th vertex is kept - size_t increment = static_cast(1) << lodLevel; + const std::size_t increment = static_cast(1) << lodLevel; - osg::Vec2f origin = center - osg::Vec2f(size / 2.f, size / 2.f); + const osg::Vec2f origin = center - osg::Vec2f(size, size) / 2; - int startCellX = static_cast(std::floor(origin.x())); - int startCellY = static_cast(std::floor(origin.y())); + const int startCellX = static_cast(std::floor(origin.x())); + const int startCellY = static_cast(std::floor(origin.y())); const int landSize = ESM::getLandSize(worldspace); const int landSizeInUnits = ESM::getCellSize(worldspace); @@ -204,7 +204,7 @@ namespace ESMTerrain LandCache cache; - bool alteration = useAlteration(); + const bool alteration = useAlteration(); bool validHeightDataExists = false; std::size_t baseVertY = 0; // of current cell corner for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY) @@ -213,8 +213,8 @@ namespace ESMTerrain std::size_t vertY = baseVertY; for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX) { - ESM::ExteriorCellLocation cellLocation(cellX, cellY, worldspace); - const LandObject* land = getLand(cellLocation, cache); + const ESM::ExteriorCellLocation cellLocation(cellX, cellY, worldspace); + const LandObject* const land = getLand(cellLocation, cache); const ESM::LandData* heightData = nullptr; const ESM::LandData* normalData = nullptr; const ESM::LandData* colourData = nullptr; @@ -239,9 +239,9 @@ namespace ESMTerrain // Only relevant for chunks smaller than (contained in) one cell rowStart += (origin.x() - startCellX) * landSize; colStart += (origin.y() - startCellY) * landSize; - int rowEnd = std::min( + const int rowEnd = std::min( static_cast(rowStart + std::min(1.f, size) * (landSize - 1) + 1), static_cast(landSize)); - int colEnd = std::min( + const int colEnd = std::min( static_cast(colStart + std::min(1.f, size) * (landSize - 1) + 1), static_cast(landSize)); vertY = baseVertY; @@ -251,11 +251,11 @@ namespace ESMTerrain vertX = baseVertX; for (int row = rowStart; row < rowEnd; row += increment) { - int srcArrayIndex = col * landSize * 3 + row * 3; - assert(row >= 0 && row < landSize); assert(col >= 0 && col < landSize); + const int srcArrayIndex = col * landSize * 3 + row * 3; + assert(vertX < numVerts); assert(vertY < numVerts); From 2c2a60b86ca47d158f75b91ece399a464e6b4f62 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 1 Aug 2023 20:52:23 +0200 Subject: [PATCH 08/14] Simplify filling positions --- components/esm3terrain/storage.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/components/esm3terrain/storage.cpp b/components/esm3terrain/storage.cpp index d26994a771..a88320c19b 100644 --- a/components/esm3terrain/storage.cpp +++ b/components/esm3terrain/storage.cpp @@ -1,6 +1,6 @@ #include "storage.hpp" -#include +#include #include #include @@ -320,12 +320,7 @@ namespace ESMTerrain assert(baseVertY == numVerts); // Ensure we covered whole area if (!validHeightDataExists && ESM::isEsm4Ext(worldspace)) - { - for (unsigned int iVert = 0; iVert < numVerts * numVerts; iVert++) - { - positions[static_cast(iVert)] = osg::Vec3f(0.f, 0.f, 0.f); - } - } + std::fill(positions.begin(), positions.end(), osg::Vec3f()); } Storage::UniqueTextureId Storage::getVtexIndexAt( From 5fda4b3cfd7b554b07d7849434375e2893f03022 Mon Sep 17 00:00:00 2001 From: elsid Date: Tue, 1 Aug 2023 21:00:53 +0200 Subject: [PATCH 09/14] Precompute height cooridnates offsets --- components/esm3terrain/storage.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/components/esm3terrain/storage.cpp b/components/esm3terrain/storage.cpp index a88320c19b..a1ed3a8c31 100644 --- a/components/esm3terrain/storage.cpp +++ b/components/esm3terrain/storage.cpp @@ -202,6 +202,10 @@ namespace ESMTerrain normals.resize(numVerts * numVerts); colours.resize(numVerts * numVerts); + // Only relevant for chunks smaller than (contained in) one cell + const int offsetX = (origin.x() - startCellX) * landSize; + const int offsetY = (origin.y() - startCellY) * landSize; + LandCache cache; const bool alteration = useAlteration(); @@ -226,8 +230,8 @@ namespace ESMTerrain validHeightDataExists = true; } - int rowStart = 0; - int colStart = 0; + int rowStart = offsetX; + int colStart = offsetY; // Skip the first row / column unless we're at a chunk edge, // since this row / column is already contained in a previous cell // This is only relevant if we're creating a chunk spanning multiple cells @@ -236,9 +240,6 @@ namespace ESMTerrain if (baseVertX != 0) rowStart += increment; - // Only relevant for chunks smaller than (contained in) one cell - rowStart += (origin.x() - startCellX) * landSize; - colStart += (origin.y() - startCellY) * landSize; const int rowEnd = std::min( static_cast(rowStart + std::min(1.f, size) * (landSize - 1) + 1), static_cast(landSize)); const int colEnd = std::min( From 0da156bdc233467de07617e4f1fdca74c1669422 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 2 Aug 2023 11:13:07 +0200 Subject: [PATCH 10/14] Skip getLand call when no vertices are used --- components/esm3terrain/storage.cpp | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/components/esm3terrain/storage.cpp b/components/esm3terrain/storage.cpp index a1ed3a8c31..bbdfcec5ca 100644 --- a/components/esm3terrain/storage.cpp +++ b/components/esm3terrain/storage.cpp @@ -217,19 +217,6 @@ namespace ESMTerrain std::size_t vertY = baseVertY; for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX) { - const ESM::ExteriorCellLocation cellLocation(cellX, cellY, worldspace); - const LandObject* const land = getLand(cellLocation, cache); - const ESM::LandData* heightData = nullptr; - const ESM::LandData* normalData = nullptr; - const ESM::LandData* colourData = nullptr; - if (land) - { - heightData = land->getData(ESM::Land::DATA_VHGT); - normalData = land->getData(ESM::Land::DATA_VNML); - colourData = land->getData(ESM::Land::DATA_VCLR); - validHeightDataExists = true; - } - int rowStart = offsetX; int colStart = offsetY; // Skip the first row / column unless we're at a chunk edge, @@ -245,6 +232,22 @@ namespace ESMTerrain const int colEnd = std::min( static_cast(colStart + std::min(1.f, size) * (landSize - 1) + 1), static_cast(landSize)); + if (colEnd <= colStart || rowEnd <= rowStart) + continue; + + const ESM::ExteriorCellLocation cellLocation(cellX, cellY, worldspace); + const LandObject* const land = getLand(cellLocation, cache); + const ESM::LandData* heightData = nullptr; + const ESM::LandData* normalData = nullptr; + const ESM::LandData* colourData = nullptr; + if (land) + { + heightData = land->getData(ESM::Land::DATA_VHGT); + normalData = land->getData(ESM::Land::DATA_VNML); + colourData = land->getData(ESM::Land::DATA_VCLR); + validHeightDataExists = true; + } + vertY = baseVertY; std::size_t vertX = baseVertX; for (int col = colStart; col < colEnd; col += increment) From fbd3d1f61ddfb6520e686999953ba930639fb941 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 2 Aug 2023 11:18:29 +0200 Subject: [PATCH 11/14] Rename increment to sampleSize --- components/esm3terrain/storage.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/esm3terrain/storage.cpp b/components/esm3terrain/storage.cpp index bbdfcec5ca..cb2b8904f7 100644 --- a/components/esm3terrain/storage.cpp +++ b/components/esm3terrain/storage.cpp @@ -187,7 +187,7 @@ namespace ESMTerrain osg::Vec3Array& positions, osg::Vec3Array& normals, osg::Vec4ubArray& colours) { // LOD level n means every 2^n-th vertex is kept - const std::size_t increment = static_cast(1) << lodLevel; + const std::size_t sampleSize = std::size_t{ 1 } << lodLevel; const osg::Vec2f origin = center - osg::Vec2f(size, size) / 2; @@ -196,7 +196,7 @@ namespace ESMTerrain const int landSize = ESM::getLandSize(worldspace); const int landSizeInUnits = ESM::getCellSize(worldspace); - const std::size_t numVerts = static_cast(size * (landSize - 1) / increment + 1); + const std::size_t numVerts = static_cast(size * (landSize - 1) / sampleSize + 1); positions.resize(numVerts * numVerts); normals.resize(numVerts * numVerts); @@ -223,9 +223,9 @@ namespace ESMTerrain // since this row / column is already contained in a previous cell // This is only relevant if we're creating a chunk spanning multiple cells if (baseVertY != 0) - colStart += increment; + colStart += sampleSize; if (baseVertX != 0) - rowStart += increment; + rowStart += sampleSize; const int rowEnd = std::min( static_cast(rowStart + std::min(1.f, size) * (landSize - 1) + 1), static_cast(landSize)); @@ -250,10 +250,10 @@ namespace ESMTerrain vertY = baseVertY; std::size_t vertX = baseVertX; - for (int col = colStart; col < colEnd; col += increment) + for (int col = colStart; col < colEnd; col += sampleSize) { vertX = baseVertX; - for (int row = rowStart; row < rowEnd; row += increment) + for (int row = rowStart; row < rowEnd; row += sampleSize) { assert(row >= 0 && row < landSize); assert(col >= 0 && col < landSize); From 28436557b1e9a1836290bdb5ba00f7da677ff535 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 5 Aug 2023 03:19:36 +0200 Subject: [PATCH 12/14] Validate fillVertexBuffers arguments --- components/esm3terrain/storage.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/components/esm3terrain/storage.cpp b/components/esm3terrain/storage.cpp index cb2b8904f7..408b059774 100644 --- a/components/esm3terrain/storage.cpp +++ b/components/esm3terrain/storage.cpp @@ -1,6 +1,7 @@ #include "storage.hpp" #include +#include #include #include @@ -186,6 +187,12 @@ namespace ESMTerrain void Storage::fillVertexBuffers(int lodLevel, float size, const osg::Vec2f& center, ESM::RefId worldspace, osg::Vec3Array& positions, osg::Vec3Array& normals, osg::Vec4ubArray& colours) { + if (lodLevel < 0 || 63 < lodLevel) + throw std::invalid_argument("Invalid terrain lod level: " + std::to_string(lodLevel)); + + if (size <= 0) + throw std::invalid_argument("Invalid terrain size: " + std::to_string(size)); + // LOD level n means every 2^n-th vertex is kept const std::size_t sampleSize = std::size_t{ 1 } << lodLevel; From 2a49919b5315a4b79f9a65a322bdb348c344b273 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 6 Aug 2023 12:53:29 +0200 Subject: [PATCH 13/14] Move esm3terrain to esmterrain --- apps/opencs/view/render/terrainstorage.cpp | 2 +- apps/opencs/view/render/terrainstorage.hpp | 2 +- apps/openmw/mwrender/landmanager.hpp | 2 +- apps/openmw/mwrender/terrainstorage.hpp | 2 +- components/CMakeLists.txt | 2 +- components/{esm3terrain => esmterrain}/storage.cpp | 0 components/{esm3terrain => esmterrain}/storage.hpp | 4 ++-- 7 files changed, 7 insertions(+), 7 deletions(-) rename components/{esm3terrain => esmterrain}/storage.cpp (100%) rename components/{esm3terrain => esmterrain}/storage.hpp (98%) diff --git a/apps/opencs/view/render/terrainstorage.cpp b/apps/opencs/view/render/terrainstorage.cpp index 725b663649..a6a38600ad 100644 --- a/apps/opencs/view/render/terrainstorage.cpp +++ b/apps/opencs/view/render/terrainstorage.cpp @@ -1,7 +1,7 @@ #include "terrainstorage.hpp" #include -#include +#include #include #include diff --git a/apps/opencs/view/render/terrainstorage.hpp b/apps/opencs/view/render/terrainstorage.hpp index 907e63a8eb..b09de55081 100644 --- a/apps/opencs/view/render/terrainstorage.hpp +++ b/apps/opencs/view/render/terrainstorage.hpp @@ -5,7 +5,7 @@ #include #include -#include +#include #include namespace CSMWorld diff --git a/apps/openmw/mwrender/landmanager.hpp b/apps/openmw/mwrender/landmanager.hpp index d9c36ea5d9..7166c4b111 100644 --- a/apps/openmw/mwrender/landmanager.hpp +++ b/apps/openmw/mwrender/landmanager.hpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include namespace ESM diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index 2526c779c6..0b41c06428 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -3,7 +3,7 @@ #include -#include +#include #include diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index b085eb52d7..32482ec331 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -145,7 +145,7 @@ add_component_dir (esm3 infoorder timestamp ) -add_component_dir (esm3terrain +add_component_dir (esmterrain storage ) diff --git a/components/esm3terrain/storage.cpp b/components/esmterrain/storage.cpp similarity index 100% rename from components/esm3terrain/storage.cpp rename to components/esmterrain/storage.cpp diff --git a/components/esm3terrain/storage.hpp b/components/esmterrain/storage.hpp similarity index 98% rename from components/esm3terrain/storage.hpp rename to components/esmterrain/storage.hpp index 17b94a7ee5..291c3afae3 100644 --- a/components/esm3terrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -1,5 +1,5 @@ -#ifndef COMPONENTS_ESM_TERRAIN_STORAGE_H -#define COMPONENTS_ESM_TERRAIN_STORAGE_H +#ifndef OPENMW_COMPONENTS_ESMTERRAIN_STORAGE_H +#define OPENMW_COMPONENTS_ESMTERRAIN_STORAGE_H #include #include From 8e7fe4451432e2b20274c2d7d9244e63af3c6b07 Mon Sep 17 00:00:00 2001 From: elsid Date: Sat, 5 Aug 2023 03:04:34 +0200 Subject: [PATCH 14/14] Support terrain sample size greater than cell size --- CHANGELOG.md | 1 + apps/openmw_test_suite/CMakeLists.txt | 2 + .../esmterrain/testgridsampling.cpp | 366 ++++++++++++++++++ components/esmterrain/gridsampling.hpp | 120 ++++++ components/esmterrain/storage.cpp | 198 ++++------ components/misc/mathutil.hpp | 6 +- 6 files changed, 579 insertions(+), 114 deletions(-) create mode 100644 apps/openmw_test_suite/esmterrain/testgridsampling.cpp create mode 100644 components/esmterrain/gridsampling.hpp diff --git a/CHANGELOG.md b/CHANGELOG.md index bcf13ef602..80bc224155 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -67,6 +67,7 @@ Bug #7450: Evading obstacles does not work for actors missing certain animations Bug #7459: Icons get stacked on the cursor when picking up multiple items simultaneously Bug #7472: Crash when enchanting last projectiles + Bug #7505: Distant terrain does not support sample size greater than cell size Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6447: Add LOD support to Object Paging diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 6b679e8653..4f93319c96 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -92,6 +92,8 @@ file(GLOB UNITTEST_SRC_FILES esm3/testinfoorder.cpp nifosg/testnifloader.cpp + + esmterrain/testgridsampling.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/esmterrain/testgridsampling.cpp b/apps/openmw_test_suite/esmterrain/testgridsampling.cpp new file mode 100644 index 0000000000..5ca38b2011 --- /dev/null +++ b/apps/openmw_test_suite/esmterrain/testgridsampling.cpp @@ -0,0 +1,366 @@ +#include + +#include +#include + +namespace ESMTerrain +{ + namespace + { + using namespace testing; + + struct Sample + { + std::size_t mCellX = 0; + std::size_t mCellY = 0; + std::size_t mLocalX = 0; + std::size_t mLocalY = 0; + std::size_t mVertexX = 0; + std::size_t mVertexY = 0; + }; + + auto tie(const Sample& v) + { + return std::tie(v.mCellX, v.mCellY, v.mLocalX, v.mLocalY, v.mVertexX, v.mVertexY); + } + + bool operator==(const Sample& l, const Sample& r) + { + return tie(l) == tie(r); + } + + std::ostream& operator<<(std::ostream& stream, const Sample& v) + { + return stream << "Sample{.mCellX = " << v.mCellX << ", .mCellY = " << v.mCellY + << ", .mLocalX = " << v.mLocalX << ", .mLocalY = " << v.mLocalY + << ", .mVertexX = " << v.mVertexX << ", .mVertexY = " << v.mVertexY << "}"; + } + + struct Collect + { + std::vector& mSamples; + + void operator()(std::size_t cellX, std::size_t cellY, std::size_t localX, std::size_t localY, + std::size_t vertexX, std::size_t vertexY) + { + mSamples.push_back(Sample{ + .mCellX = cellX, + .mCellY = cellY, + .mLocalX = localX, + .mLocalY = localY, + .mVertexX = vertexX, + .mVertexY = vertexY, + }); + } + }; + + TEST(ESMTerrainSampleCellGrid, doesNotSupportCellSizeLessThanTwo) + { + const std::size_t cellSize = 2; + EXPECT_THROW(sampleCellGrid(cellSize, 0, 0, 0, 0, [](auto...) {}), std::invalid_argument); + } + + TEST(ESMTerrainSampleCellGrid, doesNotSupportCellSizeMinusOneNotPowerOfTwo) + { + const std::size_t cellSize = 4; + EXPECT_THROW(sampleCellGrid(cellSize, 0, 0, 0, 0, [](auto...) {}), std::invalid_argument); + } + + TEST(ESMTerrainSampleCellGrid, doesNotSupportZeroSampleSize) + { + const std::size_t cellSize = 1; + const std::size_t sampleSize = 0; + EXPECT_THROW(sampleCellGrid(cellSize, sampleSize, 0, 0, 0, [](auto...) {}), std::invalid_argument); + } + + TEST(ESMTerrainSampleCellGrid, doesNotSupportSampleSizeNotPowerOfTwo) + { + const std::size_t cellSize = 1; + const std::size_t sampleSize = 3; + EXPECT_THROW(sampleCellGrid(cellSize, sampleSize, 0, 0, 0, [](auto...) {}), std::invalid_argument); + } + + TEST(ESMTerrainSampleCellGrid, doesNotSupportCountLessThanTwo) + { + const std::size_t cellSize = 1; + const std::size_t sampleSize = 1; + const std::size_t distance = 2; + EXPECT_THROW(sampleCellGrid(cellSize, sampleSize, 0, 0, distance, [](auto...) {}), std::invalid_argument); + } + + TEST(ESMTerrainSampleCellGrid, doesNotSupportCountMinusOneNotPowerOfTwo) + { + const std::size_t cellSize = 1; + const std::size_t sampleSize = 1; + const std::size_t distance = 4; + EXPECT_THROW(sampleCellGrid(cellSize, sampleSize, 0, 0, distance, [](auto...) {}), std::invalid_argument); + } + + TEST(ESMTerrainSampleCellGrid, sampleSizeOneShouldProduceNumberOfSamplesEqualToCellSize) + { + const std::size_t cellSize = 3; + const std::size_t sampleSize = 1; + const std::size_t beginX = 0; + const std::size_t beginY = 0; + const std::size_t distance = 3; + std::vector samples; + sampleCellGrid(cellSize, sampleSize, beginX, beginY, distance, Collect{ samples }); + EXPECT_THAT(samples, + ElementsAre( // + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 0, .mLocalY = 0, .mVertexX = 0, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 1, .mLocalY = 0, .mVertexX = 1, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 2, .mLocalY = 0, .mVertexX = 2, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 0, .mLocalY = 1, .mVertexX = 0, .mVertexY = 1 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 1, .mLocalY = 1, .mVertexX = 1, .mVertexY = 1 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 2, .mLocalY = 1, .mVertexX = 2, .mVertexY = 1 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 0, .mLocalY = 2, .mVertexX = 0, .mVertexY = 2 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 1, .mLocalY = 2, .mVertexX = 1, .mVertexY = 2 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 2, .mLocalY = 2, .mVertexX = 2, .mVertexY = 2 })); + } + + TEST(ESMTerrainSampleCellGrid, countShouldLimitScope) + { + const std::size_t cellSize = 3; + const std::size_t sampleSize = 1; + const std::size_t beginX = 0; + const std::size_t beginY = 0; + const std::size_t distance = 2; + std::vector samples; + sampleCellGrid(cellSize, sampleSize, beginX, beginY, distance, Collect{ samples }); + EXPECT_THAT(samples, + ElementsAre( // + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 0, .mLocalY = 0, .mVertexX = 0, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 1, .mLocalY = 0, .mVertexX = 1, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 0, .mLocalY = 1, .mVertexX = 0, .mVertexY = 1 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 1, .mLocalY = 1, .mVertexX = 1, .mVertexY = 1 })); + } + + TEST(ESMTerrainSampleCellGrid, beginXAndCountShouldLimitScope) + { + const std::size_t cellSize = 3; + const std::size_t sampleSize = 1; + const std::size_t beginX = 1; + const std::size_t beginY = 0; + const std::size_t distance = 2; + std::vector samples; + sampleCellGrid(cellSize, sampleSize, beginX, beginY, distance, Collect{ samples }); + EXPECT_THAT(samples, + ElementsAre( // + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 1, .mLocalY = 0, .mVertexX = 0, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 2, .mLocalY = 0, .mVertexX = 1, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 1, .mLocalY = 1, .mVertexX = 0, .mVertexY = 1 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 2, .mLocalY = 1, .mVertexX = 1, .mVertexY = 1 })); + } + + TEST(ESMTerrainSampleCellGrid, beginYAndCountShouldLimitScope) + { + const std::size_t cellSize = 3; + const std::size_t sampleSize = 1; + const std::size_t beginX = 0; + const std::size_t beginY = 1; + const std::size_t distance = 2; + std::vector samples; + sampleCellGrid(cellSize, sampleSize, beginX, beginY, distance, Collect{ samples }); + EXPECT_THAT(samples, + ElementsAre( // + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 0, .mLocalY = 1, .mVertexX = 0, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 1, .mLocalY = 1, .mVertexX = 1, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 0, .mLocalY = 2, .mVertexX = 0, .mVertexY = 1 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 1, .mLocalY = 2, .mVertexX = 1, .mVertexY = 1 })); + } + + TEST(ESMTerrainSampleCellGrid, beginAndCountShouldLimitScope) + { + const std::size_t cellSize = 3; + const std::size_t sampleSize = 1; + const std::size_t beginX = 1; + const std::size_t beginY = 1; + const std::size_t distance = 2; + std::vector samples; + sampleCellGrid(cellSize, sampleSize, beginX, beginY, distance, Collect{ samples }); + EXPECT_THAT(samples, + ElementsAre( // + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 1, .mLocalY = 1, .mVertexX = 0, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 2, .mLocalY = 1, .mVertexX = 1, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 1, .mLocalY = 2, .mVertexX = 0, .mVertexY = 1 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 2, .mLocalY = 2, .mVertexX = 1, .mVertexY = 1 })); + } + + TEST(ESMTerrainSampleCellGrid, beginAndCountShouldLimitScopeInTheMiddleOfCell) + { + const std::size_t cellSize = 5; + const std::size_t sampleSize = 1; + const std::size_t beginX = 1; + const std::size_t beginY = 1; + const std::size_t distance = 2; + std::vector samples; + sampleCellGrid(cellSize, sampleSize, beginX, beginY, distance, Collect{ samples }); + EXPECT_THAT(samples, + ElementsAre( // + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 1, .mLocalY = 1, .mVertexX = 0, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 2, .mLocalY = 1, .mVertexX = 1, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 1, .mLocalY = 2, .mVertexX = 0, .mVertexY = 1 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 2, .mLocalY = 2, .mVertexX = 1, .mVertexY = 1 })); + } + + TEST(ESMTerrainSampleCellGrid, beginXWithCountLessThanCellSizeShouldLimitScopeAcrossCellBorder) + { + const std::size_t cellSize = 5; + const std::size_t sampleSize = 1; + const std::size_t beginX = 3; + const std::size_t beginY = 0; + const std::size_t distance = 3; + std::vector samples; + sampleCellGrid(cellSize, sampleSize, beginX, beginY, distance, Collect{ samples }); + EXPECT_THAT(samples, + ElementsAre( // + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 3, .mLocalY = 0, .mVertexX = 0, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 4, .mLocalY = 0, .mVertexX = 1, .mVertexY = 0 }, + Sample{ .mCellX = 1, .mCellY = 0, .mLocalX = 1, .mLocalY = 0, .mVertexX = 2, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 3, .mLocalY = 1, .mVertexX = 0, .mVertexY = 1 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 4, .mLocalY = 1, .mVertexX = 1, .mVertexY = 1 }, + Sample{ .mCellX = 1, .mCellY = 0, .mLocalX = 1, .mLocalY = 1, .mVertexX = 2, .mVertexY = 1 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 3, .mLocalY = 2, .mVertexX = 0, .mVertexY = 2 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 4, .mLocalY = 2, .mVertexX = 1, .mVertexY = 2 }, + Sample{ .mCellX = 1, .mCellY = 0, .mLocalX = 1, .mLocalY = 2, .mVertexX = 2, .mVertexY = 2 })); + } + + TEST(ESMTerrainSampleCellGrid, beginXWithCountEqualToCellSizeShouldLimitScopeAcrossCellBorder) + { + const std::size_t cellSize = 3; + const std::size_t sampleSize = 1; + const std::size_t beginX = 1; + const std::size_t beginY = 0; + const std::size_t distance = 3; + std::vector samples; + sampleCellGrid(cellSize, sampleSize, beginX, beginY, distance, Collect{ samples }); + EXPECT_THAT(samples, + ElementsAre( // + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 1, .mLocalY = 0, .mVertexX = 0, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 2, .mLocalY = 0, .mVertexX = 1, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 1, .mLocalY = 1, .mVertexX = 0, .mVertexY = 1 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 2, .mLocalY = 1, .mVertexX = 1, .mVertexY = 1 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 1, .mLocalY = 2, .mVertexX = 0, .mVertexY = 2 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 2, .mLocalY = 2, .mVertexX = 1, .mVertexY = 2 }, + Sample{ .mCellX = 1, .mCellY = 0, .mLocalX = 1, .mLocalY = 0, .mVertexX = 2, .mVertexY = 0 }, + Sample{ .mCellX = 1, .mCellY = 0, .mLocalX = 1, .mLocalY = 1, .mVertexX = 2, .mVertexY = 1 }, + Sample{ .mCellX = 1, .mCellY = 0, .mLocalX = 1, .mLocalY = 2, .mVertexX = 2, .mVertexY = 2 })); + } + + TEST(ESMTerrainSampleCellGrid, beginXWithCountGreaterThanCellSizeShouldLimitScopeAcrossCellBorder) + { + const std::size_t cellSize = 3; + const std::size_t sampleSize = 1; + const std::size_t beginX = 1; + const std::size_t beginY = 0; + const std::size_t distance = 5; + std::vector samples; + sampleCellGrid(cellSize, sampleSize, beginX, beginY, distance, Collect{ samples }); + EXPECT_THAT(samples, + ElementsAre( // + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 1, .mLocalY = 0, .mVertexX = 0, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 2, .mLocalY = 0, .mVertexX = 1, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 1, .mLocalY = 1, .mVertexX = 0, .mVertexY = 1 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 2, .mLocalY = 1, .mVertexX = 1, .mVertexY = 1 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 1, .mLocalY = 2, .mVertexX = 0, .mVertexY = 2 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 2, .mLocalY = 2, .mVertexX = 1, .mVertexY = 2 }, + Sample{ .mCellX = 1, .mCellY = 0, .mLocalX = 1, .mLocalY = 0, .mVertexX = 2, .mVertexY = 0 }, + Sample{ .mCellX = 1, .mCellY = 0, .mLocalX = 2, .mLocalY = 0, .mVertexX = 3, .mVertexY = 0 }, + Sample{ .mCellX = 1, .mCellY = 0, .mLocalX = 1, .mLocalY = 1, .mVertexX = 2, .mVertexY = 1 }, + Sample{ .mCellX = 1, .mCellY = 0, .mLocalX = 2, .mLocalY = 1, .mVertexX = 3, .mVertexY = 1 }, + Sample{ .mCellX = 1, .mCellY = 0, .mLocalX = 1, .mLocalY = 2, .mVertexX = 2, .mVertexY = 2 }, + Sample{ .mCellX = 1, .mCellY = 0, .mLocalX = 2, .mLocalY = 2, .mVertexX = 3, .mVertexY = 2 }, + Sample{ .mCellX = 2, .mCellY = 0, .mLocalX = 1, .mLocalY = 0, .mVertexX = 4, .mVertexY = 0 }, + Sample{ .mCellX = 2, .mCellY = 0, .mLocalX = 1, .mLocalY = 1, .mVertexX = 4, .mVertexY = 1 }, + Sample{ .mCellX = 2, .mCellY = 0, .mLocalX = 1, .mLocalY = 2, .mVertexX = 4, .mVertexY = 2 }, + Sample{ .mCellX = 0, .mCellY = 1, .mLocalX = 1, .mLocalY = 1, .mVertexX = 0, .mVertexY = 3 }, + Sample{ .mCellX = 0, .mCellY = 1, .mLocalX = 2, .mLocalY = 1, .mVertexX = 1, .mVertexY = 3 }, + Sample{ .mCellX = 0, .mCellY = 1, .mLocalX = 1, .mLocalY = 2, .mVertexX = 0, .mVertexY = 4 }, + Sample{ .mCellX = 0, .mCellY = 1, .mLocalX = 2, .mLocalY = 2, .mVertexX = 1, .mVertexY = 4 }, + Sample{ .mCellX = 1, .mCellY = 1, .mLocalX = 1, .mLocalY = 1, .mVertexX = 2, .mVertexY = 3 }, + Sample{ .mCellX = 1, .mCellY = 1, .mLocalX = 2, .mLocalY = 1, .mVertexX = 3, .mVertexY = 3 }, + Sample{ .mCellX = 1, .mCellY = 1, .mLocalX = 1, .mLocalY = 2, .mVertexX = 2, .mVertexY = 4 }, + Sample{ .mCellX = 1, .mCellY = 1, .mLocalX = 2, .mLocalY = 2, .mVertexX = 3, .mVertexY = 4 }, + Sample{ .mCellX = 2, .mCellY = 1, .mLocalX = 1, .mLocalY = 1, .mVertexX = 4, .mVertexY = 3 }, + Sample{ .mCellX = 2, .mCellY = 1, .mLocalX = 1, .mLocalY = 2, .mVertexX = 4, .mVertexY = 4 })); + } + + TEST(ESMTerrainSampleCellGrid, sampleSizeGreaterThanOneShouldSkipPoints) + { + const std::size_t cellSize = 3; + const std::size_t sampleSize = 2; + const std::size_t beginX = 0; + const std::size_t beginY = 0; + const std::size_t distance = 3; + std::vector samples; + sampleCellGrid(cellSize, sampleSize, beginX, beginY, distance, Collect{ samples }); + EXPECT_THAT(samples, + ElementsAre( // + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 0, .mLocalY = 0, .mVertexX = 0, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 2, .mLocalY = 0, .mVertexX = 1, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 0, .mLocalY = 2, .mVertexX = 0, .mVertexY = 1 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 2, .mLocalY = 2, .mVertexX = 1, .mVertexY = 1 })); + } + + TEST(ESMTerrainSampleCellGrid, shouldGroupByCell) + { + const std::size_t cellSize = 3; + const std::size_t sampleSize = 2; + const std::size_t beginX = 0; + const std::size_t beginY = 0; + const std::size_t distance = 5; + std::vector samples; + sampleCellGrid(cellSize, sampleSize, beginX, beginY, distance, Collect{ samples }); + EXPECT_THAT(samples, + ElementsAre( // + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 0, .mLocalY = 0, .mVertexX = 0, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 2, .mLocalY = 0, .mVertexX = 1, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 0, .mLocalY = 2, .mVertexX = 0, .mVertexY = 1 }, + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 2, .mLocalY = 2, .mVertexX = 1, .mVertexY = 1 }, + Sample{ .mCellX = 1, .mCellY = 0, .mLocalX = 2, .mLocalY = 0, .mVertexX = 2, .mVertexY = 0 }, + Sample{ .mCellX = 1, .mCellY = 0, .mLocalX = 2, .mLocalY = 2, .mVertexX = 2, .mVertexY = 1 }, + Sample{ .mCellX = 0, .mCellY = 1, .mLocalX = 0, .mLocalY = 2, .mVertexX = 0, .mVertexY = 2 }, + Sample{ .mCellX = 0, .mCellY = 1, .mLocalX = 2, .mLocalY = 2, .mVertexX = 1, .mVertexY = 2 }, + Sample{ .mCellX = 1, .mCellY = 1, .mLocalX = 2, .mLocalY = 2, .mVertexX = 2, .mVertexY = 2 })); + } + + TEST(ESMTerrainSampleCellGrid, sampleSizeGreaterThanCellSizeShouldPickSinglePointPerCell) + { + const std::size_t cellSize = 3; + const std::size_t sampleSize = 4; + const std::size_t beginX = 0; + const std::size_t beginY = 0; + const std::size_t distance = 9; + std::vector samples; + sampleCellGrid(cellSize, sampleSize, beginX, beginY, distance, Collect{ samples }); + EXPECT_THAT(samples, + ElementsAre( // + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 0, .mLocalY = 0, .mVertexX = 0, .mVertexY = 0 }, + Sample{ .mCellX = 1, .mCellY = 0, .mLocalX = 2, .mLocalY = 0, .mVertexX = 1, .mVertexY = 0 }, + Sample{ .mCellX = 3, .mCellY = 0, .mLocalX = 2, .mLocalY = 0, .mVertexX = 2, .mVertexY = 0 }, + Sample{ .mCellX = 0, .mCellY = 1, .mLocalX = 0, .mLocalY = 2, .mVertexX = 0, .mVertexY = 1 }, + Sample{ .mCellX = 1, .mCellY = 1, .mLocalX = 2, .mLocalY = 2, .mVertexX = 1, .mVertexY = 1 }, + Sample{ .mCellX = 3, .mCellY = 1, .mLocalX = 2, .mLocalY = 2, .mVertexX = 2, .mVertexY = 1 }, + Sample{ .mCellX = 0, .mCellY = 3, .mLocalX = 0, .mLocalY = 2, .mVertexX = 0, .mVertexY = 2 }, + Sample{ .mCellX = 1, .mCellY = 3, .mLocalX = 2, .mLocalY = 2, .mVertexX = 1, .mVertexY = 2 }, + Sample{ .mCellX = 3, .mCellY = 3, .mLocalX = 2, .mLocalY = 2, .mVertexX = 2, .mVertexY = 2 })); + } + + TEST(ESMTerrainSampleCellGrid, sampleSizeGreaterThan2CellSizeShouldSkipCells) + { + const std::size_t cellSize = 3; + const std::size_t sampleSize = 8; + const std::size_t beginX = 0; + const std::size_t beginY = 0; + const std::size_t distance = 9; + std::vector samples; + sampleCellGrid(cellSize, sampleSize, beginX, beginY, distance, Collect{ samples }); + EXPECT_THAT(samples, + ElementsAre( // + Sample{ .mCellX = 0, .mCellY = 0, .mLocalX = 0, .mLocalY = 0, .mVertexX = 0, .mVertexY = 0 }, + Sample{ .mCellX = 3, .mCellY = 0, .mLocalX = 2, .mLocalY = 0, .mVertexX = 1, .mVertexY = 0 }, + 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 })); + } + } +} diff --git a/components/esmterrain/gridsampling.hpp b/components/esmterrain/gridsampling.hpp new file mode 100644 index 0000000000..b65825d8ad --- /dev/null +++ b/components/esmterrain/gridsampling.hpp @@ -0,0 +1,120 @@ +#ifndef OPENMW_COMPONENTS_ESMTERRAIN_GRIDSAMPLING_H +#define OPENMW_COMPONENTS_ESMTERRAIN_GRIDSAMPLING_H + +#include + +#include +#include +#include +#include +#include + +namespace ESMTerrain +{ + inline std::pair toCellAndLocal( + std::size_t begin, std::size_t global, std::size_t cellSize) + { + std::size_t cell = global / (cellSize - 1); + std::size_t local = global & (cellSize - 2); + if (global != begin && local == 0) + { + --cell; + local = cellSize - 1; + } + return { cell, local }; + } + + template + void sampleGrid( + std::size_t sampleSize, std::size_t beginX, std::size_t beginY, std::size_t endX, std::size_t endY, F&& f) + { + std::size_t vertY = 0; + for (std::size_t y = beginY; y < endY; y += sampleSize) + { + std::size_t vertX = 0; + for (std::size_t x = beginX; x < endX; x += sampleSize) + f(x, y, vertX++, vertY); + ++vertY; + } + } + + template + 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) + { + assert(cellSize > 1); + assert(Misc::isPowerOfTwo(cellSize - 1)); + assert(sampleSize != 0); + + sampleGrid(sampleSize, beginX, beginY, endX, endY, + [&](std::size_t globalX, std::size_t globalY, std::size_t vertX, std::size_t vertY) { + const auto [cellX, x] = toCellAndLocal(beginX, globalX, cellSize); + const auto [cellY, y] = toCellAndLocal(beginY, globalY, cellSize); + f(cellX, cellY, x, y, vertX, vertY); + }); + } + + template + void sampleCellGrid(std::size_t cellSize, std::size_t sampleSize, std::size_t beginX, std::size_t beginY, + std::size_t distance, F&& f) + { + if (cellSize < 2 || !Misc::isPowerOfTwo(cellSize - 1)) + throw std::invalid_argument("Invalid cell size for cell grid sampling: " + std::to_string(cellSize)); + + if (sampleSize == 0 || !Misc::isPowerOfTwo(sampleSize)) + throw std::invalid_argument("Invalid sample size for cell grid sampling: " + std::to_string(sampleSize)); + + if (distance < 2 || !Misc::isPowerOfTwo(distance - 1)) + throw std::invalid_argument("Invalid count for cell grid sampling: " + std::to_string(distance)); + + const std::size_t endX = beginX + distance; + const std::size_t endY = beginY + distance; + + if (distance < cellSize || sampleSize > cellSize - 1) + return sampleCellGridSimple(cellSize, sampleSize, beginX, beginY, endX, endY, f); + + const std::size_t beginCellX = beginX / (cellSize - 1); + const std::size_t beginCellY = beginY / (cellSize - 1); + const std::size_t endCellX = endX / (cellSize - 1); + const std::size_t endCellY = endY / (cellSize - 1); + + std::size_t baseVertY = 0; + + for (std::size_t cellY = beginCellY; cellY < endCellY; ++cellY) + { + const std::size_t offsetY = cellY * (cellSize - 1); + const std::size_t globalBeginY = offsetY <= beginY ? beginY : offsetY + sampleSize; + const std::size_t globalEndY = endY <= offsetY + cellSize ? endY : offsetY + cellSize; + + assert(globalBeginY < globalEndY); + + std::size_t baseVertX = 0; + std::size_t vertY = baseVertY; + + for (std::size_t cellX = beginCellX; cellX < endCellX; ++cellX) + { + const std::size_t offsetX = cellX * (cellSize - 1); + const std::size_t globalBeginX = offsetX <= beginX ? beginX : offsetX + sampleSize; + const std::size_t globalEndX = endX <= offsetX + cellSize ? endX : offsetX + cellSize; + + assert(globalBeginX < globalEndX); + + vertY = baseVertY; + std::size_t vertX = baseVertX; + + sampleGrid(sampleSize, globalBeginX, globalBeginY, globalEndX, globalEndY, + [&](std::size_t globalX, std::size_t globalY, std::size_t localVertX, std::size_t localVertY) { + vertX = baseVertX + localVertX; + vertY = baseVertY + localVertY; + f(cellX, cellY, globalX - offsetX, globalY - offsetY, vertX, vertY); + }); + + baseVertX = vertX + 1; + } + + baseVertY = vertY + 1; + } + } +} + +#endif diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 408b059774..bd96a3f7ce 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -13,6 +13,8 @@ #include #include +#include "gridsampling.hpp" + namespace ESMTerrain { @@ -195,59 +197,42 @@ namespace ESMTerrain // LOD level n means every 2^n-th vertex is kept const std::size_t sampleSize = std::size_t{ 1 } << lodLevel; - - const osg::Vec2f origin = center - osg::Vec2f(size, size) / 2; - - const int startCellX = static_cast(std::floor(origin.x())); - const int startCellY = static_cast(std::floor(origin.y())); - const int landSize = ESM::getLandSize(worldspace); - const int landSizeInUnits = ESM::getCellSize(worldspace); - - const std::size_t numVerts = static_cast(size * (landSize - 1) / sampleSize + 1); + const std::size_t cellSize = static_cast(ESM::getLandSize(worldspace)); + const std::size_t numVerts = static_cast(size * (cellSize - 1) / sampleSize) + 1; positions.resize(numVerts * numVerts); normals.resize(numVerts * numVerts); colours.resize(numVerts * numVerts); - // Only relevant for chunks smaller than (contained in) one cell - const int offsetX = (origin.x() - startCellX) * landSize; - const int offsetY = (origin.y() - startCellY) * landSize; - LandCache cache; const bool alteration = useAlteration(); + const int landSizeInUnits = ESM::getCellSize(worldspace); + const osg::Vec2f origin = center - osg::Vec2f(size, size) * 0.5f; + const int startCellX = static_cast(std::floor(origin.x())); + const int startCellY = static_cast(std::floor(origin.y())); + ESM::ExteriorCellLocation lastCellLocation(startCellX - 1, startCellY - 1, worldspace); + const LandObject* land = nullptr; + const ESM::LandData* heightData = nullptr; + const ESM::LandData* normalData = nullptr; + const ESM::LandData* colourData = nullptr; bool validHeightDataExists = false; - std::size_t baseVertY = 0; // of current cell corner - for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY) - { - std::size_t baseVertX = 0; // of current cell corner - std::size_t vertY = baseVertY; - for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX) + + const auto handleSample = [&](std::size_t cellShiftX, std::size_t cellShiftY, std::size_t row, std::size_t col, + std::size_t vertX, std::size_t vertY) { + const int cellX = startCellX + cellShiftX; + const int cellY = startCellY + cellShiftY; + const ESM::ExteriorCellLocation cellLocation(cellX, cellY, worldspace); + + if (lastCellLocation != cellLocation) { - int rowStart = offsetX; - int colStart = offsetY; - // Skip the first row / column unless we're at a chunk edge, - // since this row / column is already contained in a previous cell - // This is only relevant if we're creating a chunk spanning multiple cells - if (baseVertY != 0) - colStart += sampleSize; - if (baseVertX != 0) - rowStart += sampleSize; + land = getLand(cellLocation, cache); - const int rowEnd = std::min( - static_cast(rowStart + std::min(1.f, size) * (landSize - 1) + 1), static_cast(landSize)); - const int colEnd = std::min( - static_cast(colStart + std::min(1.f, size) * (landSize - 1) + 1), static_cast(landSize)); + heightData = nullptr; + normalData = nullptr; + colourData = nullptr; - if (colEnd <= colStart || rowEnd <= rowStart) - continue; - - const ESM::ExteriorCellLocation cellLocation(cellX, cellY, worldspace); - const LandObject* const land = getLand(cellLocation, cache); - const ESM::LandData* heightData = nullptr; - const ESM::LandData* normalData = nullptr; - const ESM::LandData* colourData = nullptr; - if (land) + if (land != nullptr) { heightData = land->getData(ESM::Land::DATA_VHGT); normalData = land->getData(ESM::Land::DATA_VNML); @@ -255,80 +240,67 @@ namespace ESMTerrain validHeightDataExists = true; } - vertY = baseVertY; - std::size_t vertX = baseVertX; - for (int col = colStart; col < colEnd; col += sampleSize) - { - vertX = baseVertX; - for (int row = rowStart; row < rowEnd; row += sampleSize) - { - assert(row >= 0 && row < landSize); - assert(col >= 0 && col < landSize); - - const int srcArrayIndex = col * landSize * 3 + row * 3; - - assert(vertX < numVerts); - assert(vertY < numVerts); - - const std::size_t vertIndex = vertX * numVerts + vertY; - - float height = defaultHeight; - if (heightData) - height = heightData->getHeights()[col * landSize + row]; - if (alteration) - height += getAlteredHeight(col, row); - positions[vertIndex] - = osg::Vec3f((vertX / static_cast(numVerts - 1) - 0.5f) * size * landSizeInUnits, - (vertY / static_cast(numVerts - 1) - 0.5f) * size * landSizeInUnits, height); - - osg::Vec3f normal(0, 0, 1); - - if (normalData != nullptr) - { - for (int i = 0; i < 3; ++i) - normal[i] = normalData->getNormals()[srcArrayIndex + i]; - - normal.normalize(); - } - - // Normals apparently don't connect seamlessly between cells - if (col == landSize - 1 || row == landSize - 1) - fixNormal(normal, cellLocation, col, row, cache); - - // some corner normals appear to be complete garbage (z < 0) - if ((row == 0 || row == landSize - 1) && (col == 0 || col == landSize - 1)) - averageNormal(normal, cellLocation, col, row, cache); - - assert(normal.z() > 0); - - normals[vertIndex] = normal; - - osg::Vec4ub color(255, 255, 255, 255); - - if (colourData != nullptr) - for (int i = 0; i < 3; ++i) - color[i] = colourData->getColors()[srcArrayIndex + i]; - - if (alteration) - adjustColor(col, row, heightData, color); // Does nothing by default, override in OpenMW-CS - - // Unlike normals, colors mostly connect seamlessly between cells, but not always... - if (col == landSize - 1 || row == landSize - 1) - fixColour(color, cellLocation, col, row, cache); - - colours[vertIndex] = color; - - ++vertX; - } - ++vertY; - } - baseVertX = vertX; + lastCellLocation = cellLocation; } - baseVertY = vertY; - assert(baseVertX == numVerts); // Ensure we covered whole area - } - assert(baseVertY == numVerts); // Ensure we covered whole area + float height = defaultHeight; + if (heightData != nullptr) + height = heightData->getHeights()[col * cellSize + row]; + if (alteration) + height += getAlteredHeight(col, row); + + const std::size_t vertIndex = vertX * numVerts + vertY; + + positions[vertIndex] + = osg::Vec3f((vertX / static_cast(numVerts - 1) - 0.5f) * size * landSizeInUnits, + (vertY / static_cast(numVerts - 1) - 0.5f) * size * landSizeInUnits, height); + + const std::size_t srcArrayIndex = col * cellSize * 3 + row * 3; + + osg::Vec3f normal(0, 0, 1); + + if (normalData != nullptr) + { + for (std::size_t i = 0; i < 3; ++i) + normal[i] = normalData->getNormals()[srcArrayIndex + i]; + + normal.normalize(); + } + + // Normals apparently don't connect seamlessly between cells + if (col == cellSize - 1 || row == cellSize - 1) + fixNormal(normal, cellLocation, col, row, cache); + + // some corner normals appear to be complete garbage (z < 0) + if ((row == 0 || row == cellSize - 1) && (col == 0 || col == cellSize - 1)) + averageNormal(normal, cellLocation, col, row, cache); + + assert(normal.z() > 0); + + normals[vertIndex] = normal; + + osg::Vec4ub color(255, 255, 255, 255); + + if (colourData != nullptr) + for (std::size_t i = 0; i < 3; ++i) + color[i] = colourData->getColors()[srcArrayIndex + i]; + + // Does nothing by default, override in OpenMW-CS + if (alteration) + adjustColor(col, row, heightData, color); + + // Unlike normals, colors mostly connect seamlessly between cells, but not always... + if (col == cellSize - 1 || row == cellSize - 1) + fixColour(color, cellLocation, col, row, cache); + + colours[vertIndex] = color; + }; + + const std::size_t beginX = static_cast((origin.x() - startCellX) * cellSize); + const std::size_t beginY = static_cast((origin.y() - startCellY) * cellSize); + const std::size_t distance = static_cast(size * (cellSize - 1)) + 1; + + sampleCellGrid(cellSize, sampleSize, beginX, beginY, distance, handleSample); if (!validHeightDataExists && ESM::isEsm4Ext(worldspace)) std::fill(positions.begin(), positions.end(), osg::Vec3f()); diff --git a/components/misc/mathutil.hpp b/components/misc/mathutil.hpp index 8c9bff952c..f93f38e6e8 100644 --- a/components/misc/mathutil.hpp +++ b/components/misc/mathutil.hpp @@ -7,6 +7,8 @@ #include #include +#include + namespace Misc { @@ -63,8 +65,10 @@ namespace Misc return toEulerAnglesZYX(forward, up); } - inline bool isPowerOfTwo(int x) + template + bool isPowerOfTwo(T x) { + static_assert(std::is_integral_v); return ((x > 0) && ((x & (x - 1)) == 0)); }