From ac780222eb58bae9823167e2c49646edbceb41da Mon Sep 17 00:00:00 2001 From: Calandiel Date: Sun, 18 Aug 2024 03:17:57 +0200 Subject: [PATCH 01/33] expose a terrain height getter --- apps/openmw/mwlua/corebindings.cpp | 43 ++++++++++++++++++++++++++++++ components/esmterrain/storage.cpp | 39 ++++++++++++++++----------- components/esmterrain/storage.hpp | 12 +++++++-- 3 files changed, 77 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index 445bcdd617..6c0ce9b2d5 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -1,5 +1,8 @@ #include "corebindings.hpp" +#include +#include +#include #include #include @@ -25,6 +28,9 @@ #include "magicbindings.hpp" #include "soundbindings.hpp" #include "stats.hpp" +#include +#include +#include namespace MWLua { @@ -147,6 +153,43 @@ namespace MWLua { std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) }); }; } + api["getHeightAt"] = [](float x, float y, sol::object cellOrName) { + ESM::RefId worldspace; + if (cellOrName.is()) + worldspace = cellOrName.as().mStore->getCell()->getWorldSpace(); + else if (cellOrName.is() && !cellOrName.as().empty()) + worldspace = MWBase::Environment::get() + .getWorldModel() + ->getCell(cellOrName.as()) + .getCell() + ->getWorldSpace(); + else + worldspace = ESM::Cell::sDefaultWorldspaceId; + + const float cellSize = ESM::getCellSize(worldspace); + int cellX = static_cast(std::floor(x / cellSize)); + int cellY = static_cast(std::floor(y / cellSize)); + + auto store = MWBase::Environment::get().getESMStore(); + auto landStore = store->get(); + auto land = landStore.search(cellX, cellY); + const ESM::Land::LandData* landData = nullptr; + if (land != nullptr) + { + landData = land->getLandData(ESM::Land::DATA_VHGT); + if (landData != nullptr) + { + // Ensure data is loaded if necessary + land->loadData(ESM::Land::DATA_VHGT); + } + } + if (landData == nullptr) + { + // If we failed to load data, return the default height + return static_cast(ESM::Land::DEFAULT_HEIGHT); + } + return ESMTerrain::Storage::getHeightAt(landData->mHeights, landData->sLandSize, { x, y, 0 }, cellSize); + }; sol::table readOnly = LuaUtil::makeReadOnly(api); return context.setTypePackage(readOnly, "openmw_core"); diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 35ec814aa2..330fa0440b 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -465,21 +465,14 @@ namespace ESMTerrain blendmaps.clear(); // If a single texture fills the whole terrain, there is no need to blend } - float Storage::getHeightAt(const osg::Vec3f& worldPos, ESM::RefId worldspace) + float Storage::getHeightAt( + const std::span data, const int landSize, const osg::Vec3f& worldPos, const float cellSize) { - const float cellSize = ESM::getCellSize(worldspace); + // if (!data) + // return defaultHeight; int cellX = static_cast(std::floor(worldPos.x() / cellSize)); int cellY = static_cast(std::floor(worldPos.y() / cellSize)); - osg::ref_ptr land = getLand(ESM::ExteriorCellLocation(cellX, cellY, worldspace)); - if (!land) - return ESM::isEsm4Ext(worldspace) ? std::numeric_limits::lowest() : defaultHeight; - - const ESM::LandData* data = land->getData(ESM::Land::DATA_VHGT); - if (!data) - return defaultHeight; - const int landSize = data->getLandSize(); - // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition // Normalized position in the cell @@ -516,10 +509,10 @@ namespace ESMTerrain */ // Build all 4 positions in normalized cell space, using point-sampled height - osg::Vec3f v0(startXTS, startYTS, getVertexHeight(data, startX, startY) / cellSize); - osg::Vec3f v1(endXTS, startYTS, getVertexHeight(data, endX, startY) / cellSize); - osg::Vec3f v2(endXTS, endYTS, getVertexHeight(data, endX, endY) / cellSize); - osg::Vec3f v3(startXTS, endYTS, getVertexHeight(data, startX, endY) / cellSize); + osg::Vec3f v0(startXTS, startYTS, Storage::getVertexHeight(data, landSize, startX, startY) / cellSize); + osg::Vec3f v1(endXTS, startYTS, Storage::getVertexHeight(data, landSize, endX, startY) / cellSize); + osg::Vec3f v2(endXTS, endYTS, Storage::getVertexHeight(data, landSize, endX, endY) / cellSize); + osg::Vec3f v3(startXTS, endYTS, Storage::getVertexHeight(data, landSize, startX, endY) / cellSize); // define this plane in terrain space osg::Plane plane; // FIXME: deal with differing triangle alignment @@ -548,6 +541,22 @@ namespace ESMTerrain return (-plane.getNormal().x() * nX - plane.getNormal().y() * nY - plane[3]) / plane.getNormal().z() * cellSize; } + float Storage::getHeightAt(const osg::Vec3f& worldPos, ESM::RefId worldspace) + { + const float cellSize = ESM::getCellSize(worldspace); + int cellX = static_cast(std::floor(worldPos.x() / cellSize)); + int cellY = static_cast(std::floor(worldPos.y() / cellSize)); + + osg::ref_ptr land = getLand(ESM::ExteriorCellLocation(cellX, cellY, worldspace)); + if (!land) + return ESM::isEsm4Ext(worldspace) ? std::numeric_limits::lowest() : defaultHeight; + + const ESM::LandData* data = land->getData(ESM::Land::DATA_VHGT); + if (!data) + return defaultHeight; + return Storage::getHeightAt(data->getHeights(), data->getLandSize(), worldPos, cellSize); + } + const LandObject* Storage::getLand(ESM::ExteriorCellLocation cellLocation, LandCache& cache) { if (const auto land = cache.find(cellLocation.mX, cellLocation.mY)) diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 402f2147ab..c88a15fa30 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -114,6 +114,9 @@ namespace ESMTerrain float getHeightAt(const osg::Vec3f& worldPos, ESM::RefId worldspace) override; + static float getHeightAt( + const std::span data, const int landSize, const osg::Vec3f& worldPos, const float cellSize); + /// Get the transformation factor for mapping cell units to world units. float getCellWorldSize(ESM::RefId worldspace) override; @@ -122,12 +125,17 @@ namespace ESMTerrain int getBlendmapScale(float chunkSize) override; - float getVertexHeight(const ESM::LandData* data, int x, int y) + static float getVertexHeight(const ESM::LandData* data, int x, int y) { const int landSize = data->getLandSize(); + return getVertexHeight(data->getHeights(), landSize, x, y); + } + + static float getVertexHeight(const std::span data, const int landSize, int x, int y) + { assert(x < landSize); assert(y < landSize); - return data->getHeights()[y * landSize + x]; + return data[y * landSize + x]; } private: From 05b747d3ee05036650c4db58f03f0556f12fbf6a Mon Sep 17 00:00:00 2001 From: Calandiel Date: Sun, 18 Aug 2024 03:26:52 +0200 Subject: [PATCH 02/33] add docs for the new binding --- apps/openmw/mwlua/corebindings.cpp | 9 +++++---- files/lua_api/openmw/core.lua | 7 +++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index 6c0ce9b2d5..287f021672 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -153,7 +153,8 @@ namespace MWLua { std::move(eventName), LuaUtil::serialize(eventData, context.mSerializer) }); }; } - api["getHeightAt"] = [](float x, float y, sol::object cellOrName) { + + api["getHeightAt"] = [](const osg::Vec3f& pos, sol::object cellOrName) { ESM::RefId worldspace; if (cellOrName.is()) worldspace = cellOrName.as().mStore->getCell()->getWorldSpace(); @@ -167,8 +168,8 @@ namespace MWLua worldspace = ESM::Cell::sDefaultWorldspaceId; const float cellSize = ESM::getCellSize(worldspace); - int cellX = static_cast(std::floor(x / cellSize)); - int cellY = static_cast(std::floor(y / cellSize)); + int cellX = static_cast(std::floor(pos.x() / cellSize)); + int cellY = static_cast(std::floor(pos.y() / cellSize)); auto store = MWBase::Environment::get().getESMStore(); auto landStore = store->get(); @@ -188,7 +189,7 @@ namespace MWLua // If we failed to load data, return the default height return static_cast(ESM::Land::DEFAULT_HEIGHT); } - return ESMTerrain::Storage::getHeightAt(landData->mHeights, landData->sLandSize, { x, y, 0 }, cellSize); + return ESMTerrain::Storage::getHeightAt(landData->mHeights, landData->sLandSize, pos, cellSize); }; sol::table readOnly = LuaUtil::makeReadOnly(api); diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 8a78a52441..5813b32b27 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -62,6 +62,13 @@ -- @param #string setting Setting name -- @return #any +--- +-- Get the terrain height at a given location. +-- @function [parent=#core] getHeightAt +-- @param openmw.util#Vector3 position +-- @param #any cellOrName (optional) cell or cell name in their exterior world space to query +-- @return #number + --- -- Return l10n formatting function for the given context. -- Localisation files (containing the message names and translations) should be stored in From 5015f0005fffed8ea4d522fa054526917c34e04f Mon Sep 17 00:00:00 2001 From: Calandiel Date: Sun, 18 Aug 2024 17:32:30 +0200 Subject: [PATCH 03/33] add bindings for land textures --- apps/openmw/mwlua/corebindings.cpp | 89 ++++++++++++++++++++++++++++++ components/esmterrain/storage.cpp | 87 +++++++++++++++++++++++++++-- components/esmterrain/storage.hpp | 6 ++ files/lua_api/openmw/core.lua | 9 +++ 4 files changed, 186 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index 287f021672..59ca7b535a 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include #include @@ -31,6 +33,7 @@ #include #include #include +#include namespace MWLua { @@ -182,6 +185,7 @@ namespace MWLua { // Ensure data is loaded if necessary land->loadData(ESM::Land::DATA_VHGT); + landData = land->getLandData(ESM::Land::DATA_VHGT); } } if (landData == nullptr) @@ -192,6 +196,91 @@ namespace MWLua return ESMTerrain::Storage::getHeightAt(landData->mHeights, landData->sLandSize, pos, cellSize); }; + api["getLandTextureAt"] = [lua = context.mLua](const osg::Vec3f& pos, sol::object cellOrName) { + sol::variadic_results values; + ESM::RefId worldspace; + if (cellOrName.is()) + worldspace = cellOrName.as().mStore->getCell()->getWorldSpace(); + else if (cellOrName.is() && !cellOrName.as().empty()) + worldspace = MWBase::Environment::get() + .getWorldModel() + ->getCell(cellOrName.as()) + .getCell() + ->getWorldSpace(); + else + worldspace = ESM::Cell::sDefaultWorldspaceId; + + const float cellSize = ESM::getCellSize(worldspace); + + int cellX = static_cast(std::floor(pos.x() / cellSize)); + int cellY = static_cast(std::floor(pos.y() / cellSize)); + + auto store = MWBase::Environment::get().getESMStore(); + // We need to read land twice. Once to get the amount of texture samples per cell edge, and the second time + // to get the actual data + auto landStore = store->get(); + auto land = landStore.search(cellX, cellY); + const ESM::Land::LandData* landData = nullptr; + if (land != nullptr) + { + landData = land->getLandData(ESM::Land::DATA_VTEX); + if (landData != nullptr) + { + // Ensure data is loaded if necessary + land->loadData(ESM::Land::DATA_VTEX); + landData = land->getLandData(ESM::Land::DATA_VTEX); + } + } + if (landData == nullptr) + { + // If we fail to preload land data, return, we need to be able to get *any* land to know how to correct + // the position used to sample terrain + return values; + } + + const osg::Vec3f correctedPos + = ESMTerrain::Storage::getTextureCorrectedWorldPos(pos, landData->sLandTextureSize, cellSize); + int correctedCellX = static_cast(std::floor(correctedPos.x() / cellSize)); + int correctedCellY = static_cast(std::floor(correctedPos.y() / cellSize)); + auto correctedLand = landStore.search(correctedCellX, correctedCellY); + const ESM::Land::LandData* correctedLandData = nullptr; + if (correctedLand != nullptr) + { + correctedLandData = correctedLand->getLandData(ESM::Land::DATA_VTEX); + if (correctedLandData != nullptr) + { + // Ensure data is loaded if necessary + land->loadData(ESM::Land::DATA_VTEX); + correctedLandData = correctedLand->getLandData(ESM::Land::DATA_VTEX); + } + } + if (correctedLandData == nullptr) + { + return values; + } + + // We're passing in sLandTextureSize, NOT sLandSize like with getHeightAt + const ESMTerrain::UniqueTextureId textureId + = ESMTerrain::Storage::getLandTextureAt(correctedLandData->mTextures, correctedLand->getPlugin(), + correctedLandData->sLandTextureSize, correctedPos, cellSize); + + // Need to check for 0, 0 so that we can safely subtract 1 later, as per documentation on UniqueTextureId + if (textureId.first != 0) + { + values.push_back(sol::make_object(lua->sol(), textureId.first - 1)); + values.push_back(sol::make_object(lua->sol(), textureId.second)); + + auto textureStore = store->get(); + const std::string* textureString = textureStore.search(textureId.first - 1, textureId.second); + if (textureString) + { + values.push_back(sol::make_object(lua->sol(), *textureString)); + } + } + + return values; + }; + sol::table readOnly = LuaUtil::makeReadOnly(api); return context.setTypePackage(readOnly, "openmw_core"); } diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 330fa0440b..903ec11c29 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -22,6 +23,19 @@ namespace ESMTerrain { namespace { + UniqueTextureId getTextureIdAt( + const std::span data, const int plugin, const std::size_t x, const std::size_t y) + { + assert(x < ESM::Land::LAND_TEXTURE_SIZE); + assert(y < ESM::Land::LAND_TEXTURE_SIZE); + + const std::uint16_t tex = data[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, plugin }; + } + UniqueTextureId getTextureIdAt(const LandObject* land, std::size_t x, std::size_t y) { assert(x < ESM::Land::LAND_TEXTURE_SIZE); @@ -34,11 +48,7 @@ namespace ESMTerrain 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() }; + return getTextureIdAt(data->getTextures(), land->getPlugin(), x, y); } } @@ -465,6 +475,73 @@ namespace ESMTerrain blendmaps.clear(); // If a single texture fills the whole terrain, there is no need to blend } + osg::Vec3f Storage::getTextureCorrectedWorldPos( + const osg::Vec3f& uncorrectedWorldPos, const int textureSize, const float cellSize) + { + // the offset is [-0.25, +0.25] of a single texture's size + // TODO: verify whether or not this works in TES4 and beyond + float offset = (cellSize / textureSize) * 0.25; + return uncorrectedWorldPos + osg::Vec3f{ -offset, +offset, 0.0f }; + } + + // Takes in a corrected world pos to match the visuals. + UniqueTextureId Storage::getLandTextureAt(const std::span landData, const int plugin, + const int textureSize, const osg::Vec3f& correctedWorldPos, const float cellSize) + { + int cellX = static_cast(std::floor(correctedWorldPos.x() / cellSize)); + int cellY = static_cast(std::floor(correctedWorldPos.y() / cellSize)); + + // Normalized position in the cell + float nX = (correctedWorldPos.x() - (cellX * cellSize)) / cellSize; + float nY = (correctedWorldPos.y() - (cellY * cellSize)) / cellSize; + + int startX = static_cast(nX * textureSize); + int startY = static_cast(nY * textureSize); + + int endX = startX + 1; + int endY = startY + 1; + endX = std::min(endX, textureSize - 1); + endY = std::min(endY, textureSize - 1); + + float fractionX = std::clamp(nX * textureSize - startX, 0.0f, 1.0f); + float fractionY = std::clamp(nY * textureSize - startY, 0.0f, 1.0f); + + /* For even / odd tri strip rows, triangles are this shape: + even odd + 3---2 3---2 + | / | | \ | + 0---1 0---1 + */ + return getTextureIdAt(landData, plugin, startX, startY); + + if (fractionX <= 0.5f) + { + if (fractionY <= 0.5) + { + // 0 + return getTextureIdAt(landData, plugin, startX, startY); + } + else + { + // 3 + return getTextureIdAt(landData, plugin, startX, endY); + } + } + else + { + if (fractionY <= 0.5) + { + // 1 + return getTextureIdAt(landData, plugin, endX, startY); + } + else + { + // 2 + return getTextureIdAt(landData, plugin, endX, endY); + } + } + } + float Storage::getHeightAt( const std::span data, const int landSize, const osg::Vec3f& worldPos, const float cellSize) { diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index c88a15fa30..7b4e9f8694 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -117,6 +117,12 @@ namespace ESMTerrain static float getHeightAt( const std::span data, const int landSize, const osg::Vec3f& worldPos, const float cellSize); + static osg::Vec3f getTextureCorrectedWorldPos( + const osg::Vec3f& uncorrectedWorldPos, const int textureSize, const float cellSize); + + static UniqueTextureId getLandTextureAt(const std::span landData, const int plugin, + const int textureSize, const osg::Vec3f& worldPos, const float cellSize); + /// Get the transformation factor for mapping cell units to world units. float getCellWorldSize(ESM::RefId worldspace) override; diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 5813b32b27..1ffc60f6d5 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -69,6 +69,15 @@ -- @param #any cellOrName (optional) cell or cell name in their exterior world space to query -- @return #number +--- +-- Get the terrain texture at a given location. +-- @function [parent=#core] getLandTextureAt +-- @param openmw.util#Vector3 position +-- @param #any cellOrName (optional) cell or cell name in their exterior world space to query +-- @return #nil, #number Land texture index or nil if failed to retrieve the texture +-- @return #nil, #number Plugin id or nil if failed to retrieve the texture +-- @return #nil, #string Texture path or nil if one isn't defined + --- -- Return l10n formatting function for the given context. -- Localisation files (containing the message names and translations) should be stored in From f8ddd4ed5d2af7016a10f09e8f7fe80888d2c821 Mon Sep 17 00:00:00 2001 From: Calandiel Date: Mon, 19 Aug 2024 00:40:09 +0200 Subject: [PATCH 04/33] reorder includes --- apps/openmw/mwlua/corebindings.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index 59ca7b535a..1c98706adb 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -1,15 +1,17 @@ #include "corebindings.hpp" -#include -#include -#include -#include -#include #include + +#include #include +#include #include +#include #include +#include +#include +#include #include #include #include @@ -30,10 +32,9 @@ #include "magicbindings.hpp" #include "soundbindings.hpp" #include "stats.hpp" -#include -#include -#include -#include +#include +#include +#include namespace MWLua { From 813f6d970219a1f7f132a3c3e9e7ce3352c66840 Mon Sep 17 00:00:00 2001 From: Calandiel Date: Mon, 19 Aug 2024 00:40:27 +0200 Subject: [PATCH 05/33] reorder includes --- apps/openmw/mwlua/corebindings.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index 1c98706adb..4e296347be 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -6,6 +6,9 @@ #include #include +#include +#include +#include #include #include #include @@ -32,10 +35,6 @@ #include "magicbindings.hpp" #include "soundbindings.hpp" #include "stats.hpp" -#include -#include -#include - namespace MWLua { static sol::table initContentFilesBindings(sol::state_view& lua) From 30e2954e591e6ceff43ee204a086cab4b13acbb5 Mon Sep 17 00:00:00 2001 From: Calandiel Date: Mon, 19 Aug 2024 00:41:01 +0200 Subject: [PATCH 06/33] reorder includes --- apps/openmw/mwlua/corebindings.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index 4e296347be..ec35c66e70 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -35,6 +35,7 @@ #include "magicbindings.hpp" #include "soundbindings.hpp" #include "stats.hpp" + namespace MWLua { static sol::table initContentFilesBindings(sol::state_view& lua) From 15980a1f0fd9d03ce4cec7dfa96ca19a20d5bff4 Mon Sep 17 00:00:00 2001 From: Calandiel Date: Mon, 19 Aug 2024 00:42:23 +0200 Subject: [PATCH 07/33] reorder includes --- apps/openmw/mwlua/corebindings.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index ec35c66e70..40d37b464c 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -1,14 +1,15 @@ #include "corebindings.hpp" -#include - #include #include #include +#include + #include #include #include + #include #include #include From fefb74a775ee1e0f659864cd5136f0d04602f16f Mon Sep 17 00:00:00 2001 From: Calandiel Date: Mon, 19 Aug 2024 23:40:15 +0200 Subject: [PATCH 08/33] put land bindings in a table in openmw.core --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwlua/corebindings.cpp | 137 +------------------------- apps/openmw/mwlua/landbindings.cpp | 153 +++++++++++++++++++++++++++++ apps/openmw/mwlua/landbindings.hpp | 11 +++ 4 files changed, 168 insertions(+), 135 deletions(-) create mode 100644 apps/openmw/mwlua/landbindings.cpp create mode 100644 apps/openmw/mwlua/landbindings.hpp diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 37de0abeab..48dcf41aae 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -62,7 +62,7 @@ add_openmw_dir (mwlua luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant context menuscripts globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings dialoguebindings - postprocessingbindings stats recordstore debugbindings corebindings worldbindings worker magicbindings factionbindings + postprocessingbindings stats recordstore debugbindings corebindings worldbindings worker landbindings magicbindings factionbindings classbindings itemdata inputprocessor animationbindings birthsignbindings racebindings markupbindings types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index 40d37b464c..6c772fa0a9 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -2,20 +2,10 @@ #include #include -#include - -#include - -#include -#include -#include #include -#include #include -#include #include -#include #include #include #include @@ -32,6 +22,7 @@ #include "dialoguebindings.hpp" #include "factionbindings.hpp" +#include "landbindings.hpp" #include "luaevents.hpp" #include "magicbindings.hpp" #include "soundbindings.hpp" @@ -108,6 +99,8 @@ namespace MWLua api["stats"] = context.cachePackage("openmw_core_stats", [context]() { return initCoreStatsBindings(context); }); + api["land"] = context.cachePackage("openmw_core_land", [context]() { return initCoreLandBindings(context); }); + api["factions"] = context.cachePackage("openmw_core_factions", [context]() { return initCoreFactionBindings(context); }); api["dialogue"] @@ -159,130 +152,6 @@ namespace MWLua }; } - api["getHeightAt"] = [](const osg::Vec3f& pos, sol::object cellOrName) { - ESM::RefId worldspace; - if (cellOrName.is()) - worldspace = cellOrName.as().mStore->getCell()->getWorldSpace(); - else if (cellOrName.is() && !cellOrName.as().empty()) - worldspace = MWBase::Environment::get() - .getWorldModel() - ->getCell(cellOrName.as()) - .getCell() - ->getWorldSpace(); - else - worldspace = ESM::Cell::sDefaultWorldspaceId; - - const float cellSize = ESM::getCellSize(worldspace); - int cellX = static_cast(std::floor(pos.x() / cellSize)); - int cellY = static_cast(std::floor(pos.y() / cellSize)); - - auto store = MWBase::Environment::get().getESMStore(); - auto landStore = store->get(); - auto land = landStore.search(cellX, cellY); - const ESM::Land::LandData* landData = nullptr; - if (land != nullptr) - { - landData = land->getLandData(ESM::Land::DATA_VHGT); - if (landData != nullptr) - { - // Ensure data is loaded if necessary - land->loadData(ESM::Land::DATA_VHGT); - landData = land->getLandData(ESM::Land::DATA_VHGT); - } - } - if (landData == nullptr) - { - // If we failed to load data, return the default height - return static_cast(ESM::Land::DEFAULT_HEIGHT); - } - return ESMTerrain::Storage::getHeightAt(landData->mHeights, landData->sLandSize, pos, cellSize); - }; - - api["getLandTextureAt"] = [lua = context.mLua](const osg::Vec3f& pos, sol::object cellOrName) { - sol::variadic_results values; - ESM::RefId worldspace; - if (cellOrName.is()) - worldspace = cellOrName.as().mStore->getCell()->getWorldSpace(); - else if (cellOrName.is() && !cellOrName.as().empty()) - worldspace = MWBase::Environment::get() - .getWorldModel() - ->getCell(cellOrName.as()) - .getCell() - ->getWorldSpace(); - else - worldspace = ESM::Cell::sDefaultWorldspaceId; - - const float cellSize = ESM::getCellSize(worldspace); - - int cellX = static_cast(std::floor(pos.x() / cellSize)); - int cellY = static_cast(std::floor(pos.y() / cellSize)); - - auto store = MWBase::Environment::get().getESMStore(); - // We need to read land twice. Once to get the amount of texture samples per cell edge, and the second time - // to get the actual data - auto landStore = store->get(); - auto land = landStore.search(cellX, cellY); - const ESM::Land::LandData* landData = nullptr; - if (land != nullptr) - { - landData = land->getLandData(ESM::Land::DATA_VTEX); - if (landData != nullptr) - { - // Ensure data is loaded if necessary - land->loadData(ESM::Land::DATA_VTEX); - landData = land->getLandData(ESM::Land::DATA_VTEX); - } - } - if (landData == nullptr) - { - // If we fail to preload land data, return, we need to be able to get *any* land to know how to correct - // the position used to sample terrain - return values; - } - - const osg::Vec3f correctedPos - = ESMTerrain::Storage::getTextureCorrectedWorldPos(pos, landData->sLandTextureSize, cellSize); - int correctedCellX = static_cast(std::floor(correctedPos.x() / cellSize)); - int correctedCellY = static_cast(std::floor(correctedPos.y() / cellSize)); - auto correctedLand = landStore.search(correctedCellX, correctedCellY); - const ESM::Land::LandData* correctedLandData = nullptr; - if (correctedLand != nullptr) - { - correctedLandData = correctedLand->getLandData(ESM::Land::DATA_VTEX); - if (correctedLandData != nullptr) - { - // Ensure data is loaded if necessary - land->loadData(ESM::Land::DATA_VTEX); - correctedLandData = correctedLand->getLandData(ESM::Land::DATA_VTEX); - } - } - if (correctedLandData == nullptr) - { - return values; - } - - // We're passing in sLandTextureSize, NOT sLandSize like with getHeightAt - const ESMTerrain::UniqueTextureId textureId - = ESMTerrain::Storage::getLandTextureAt(correctedLandData->mTextures, correctedLand->getPlugin(), - correctedLandData->sLandTextureSize, correctedPos, cellSize); - - // Need to check for 0, 0 so that we can safely subtract 1 later, as per documentation on UniqueTextureId - if (textureId.first != 0) - { - values.push_back(sol::make_object(lua->sol(), textureId.first - 1)); - values.push_back(sol::make_object(lua->sol(), textureId.second)); - - auto textureStore = store->get(); - const std::string* textureString = textureStore.search(textureId.first - 1, textureId.second); - if (textureString) - { - values.push_back(sol::make_object(lua->sol(), *textureString)); - } - } - - return values; - }; - sol::table readOnly = LuaUtil::makeReadOnly(api); return context.setTypePackage(readOnly, "openmw_core"); } diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp new file mode 100644 index 0000000000..37e95183ca --- /dev/null +++ b/apps/openmw/mwlua/landbindings.cpp @@ -0,0 +1,153 @@ +#include "landbindings.hpp" + +#include +#include +#include + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/esmstore.hpp" + +namespace MWLua +{ + sol::table initCoreLandBindings(const Context& context) + { + sol::state_view& lua = context.mLua->sol(); + sol::table landApi(lua, sol::create); + + // Constants + landApi["RANGE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ + { "Self", ESM::RT_Self }, + { "Touch", ESM::RT_Touch }, + { "Target", ESM::RT_Target }, + })); + + landApi["getHeightAt"] = [](const osg::Vec3f& pos, sol::object cellOrName) { + ESM::RefId worldspace; + if (cellOrName.is()) + worldspace = cellOrName.as().mStore->getCell()->getWorldSpace(); + else if (cellOrName.is() && !cellOrName.as().empty()) + worldspace = MWBase::Environment::get() + .getWorldModel() + ->getCell(cellOrName.as()) + .getCell() + ->getWorldSpace(); + else + worldspace = ESM::Cell::sDefaultWorldspaceId; + + const float cellSize = ESM::getCellSize(worldspace); + int cellX = static_cast(std::floor(pos.x() / cellSize)); + int cellY = static_cast(std::floor(pos.y() / cellSize)); + + auto store = MWBase::Environment::get().getESMStore(); + auto landStore = store->get(); + auto land = landStore.search(cellX, cellY); + const ESM::Land::LandData* landData = nullptr; + if (land != nullptr) + { + landData = land->getLandData(ESM::Land::DATA_VHGT); + if (landData != nullptr) + { + // Ensure data is loaded if necessary + land->loadData(ESM::Land::DATA_VHGT); + landData = land->getLandData(ESM::Land::DATA_VHGT); + } + } + if (landData == nullptr) + { + // If we failed to load data, return the default height + return static_cast(ESM::Land::DEFAULT_HEIGHT); + } + return ESMTerrain::Storage::getHeightAt(landData->mHeights, landData->sLandSize, pos, cellSize); + }; + + landApi["getLandTextureAt"] = [lua = context.mLua](const osg::Vec3f& pos, sol::object cellOrName) { + sol::variadic_results values; + ESM::RefId worldspace; + if (cellOrName.is()) + worldspace = cellOrName.as().mStore->getCell()->getWorldSpace(); + else if (cellOrName.is() && !cellOrName.as().empty()) + worldspace = MWBase::Environment::get() + .getWorldModel() + ->getCell(cellOrName.as()) + .getCell() + ->getWorldSpace(); + else + worldspace = ESM::Cell::sDefaultWorldspaceId; + + const float cellSize = ESM::getCellSize(worldspace); + + int cellX = static_cast(std::floor(pos.x() / cellSize)); + int cellY = static_cast(std::floor(pos.y() / cellSize)); + + auto store = MWBase::Environment::get().getESMStore(); + // We need to read land twice. Once to get the amount of texture samples per cell edge, and the second time + // to get the actual data + auto landStore = store->get(); + auto land = landStore.search(cellX, cellY); + const ESM::Land::LandData* landData = nullptr; + if (land != nullptr) + { + landData = land->getLandData(ESM::Land::DATA_VTEX); + if (landData != nullptr) + { + // Ensure data is loaded if necessary + land->loadData(ESM::Land::DATA_VTEX); + landData = land->getLandData(ESM::Land::DATA_VTEX); + } + } + if (landData == nullptr) + { + // If we fail to preload land data, return, we need to be able to get *any* land to know how to correct + // the position used to sample terrain + return values; + } + + const osg::Vec3f correctedPos + = ESMTerrain::Storage::getTextureCorrectedWorldPos(pos, landData->sLandTextureSize, cellSize); + int correctedCellX = static_cast(std::floor(correctedPos.x() / cellSize)); + int correctedCellY = static_cast(std::floor(correctedPos.y() / cellSize)); + auto correctedLand = landStore.search(correctedCellX, correctedCellY); + const ESM::Land::LandData* correctedLandData = nullptr; + if (correctedLand != nullptr) + { + correctedLandData = correctedLand->getLandData(ESM::Land::DATA_VTEX); + if (correctedLandData != nullptr) + { + // Ensure data is loaded if necessary + land->loadData(ESM::Land::DATA_VTEX); + correctedLandData = correctedLand->getLandData(ESM::Land::DATA_VTEX); + } + } + if (correctedLandData == nullptr) + { + return values; + } + + // We're passing in sLandTextureSize, NOT sLandSize like with getHeightAt + const ESMTerrain::UniqueTextureId textureId + = ESMTerrain::Storage::getLandTextureAt(correctedLandData->mTextures, correctedLand->getPlugin(), + correctedLandData->sLandTextureSize, correctedPos, cellSize); + + // Need to check for 0, 0 so that we can safely subtract 1 later, as per documentation on UniqueTextureId + if (textureId.first != 0) + { + values.push_back(sol::make_object(lua->sol(), textureId.first - 1)); + values.push_back(sol::make_object(lua->sol(), textureId.second)); + + auto textureStore = store->get(); + const std::string* textureString = textureStore.search(textureId.first - 1, textureId.second); + if (textureString) + { + values.push_back(sol::make_object(lua->sol(), *textureString)); + } + } + + return values; + }; + + return LuaUtil::makeReadOnly(landApi); + } +} diff --git a/apps/openmw/mwlua/landbindings.hpp b/apps/openmw/mwlua/landbindings.hpp new file mode 100644 index 0000000000..8cdf30046b --- /dev/null +++ b/apps/openmw/mwlua/landbindings.hpp @@ -0,0 +1,11 @@ +#ifndef MWLUA_LANDBINDINGS_H +#define MWLUA_LANDBINDINGS_H + +#include "context.hpp" + +namespace MWLua +{ + sol::table initCoreLandBindings(const Context& context); +} + +#endif // MWLUA_LANDBINDINGS_H From cb8c849de7e28fa8d1461886cd6a341847534471 Mon Sep 17 00:00:00 2001 From: Calandiel Date: Tue, 20 Aug 2024 00:01:02 +0200 Subject: [PATCH 09/33] update the docs for land functions --- files/lua_api/openmw/core.lua | 36 +++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 1ffc60f6d5..30db9b6791 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -62,22 +62,6 @@ -- @param #string setting Setting name -- @return #any ---- --- Get the terrain height at a given location. --- @function [parent=#core] getHeightAt --- @param openmw.util#Vector3 position --- @param #any cellOrName (optional) cell or cell name in their exterior world space to query --- @return #number - ---- --- Get the terrain texture at a given location. --- @function [parent=#core] getLandTextureAt --- @param openmw.util#Vector3 position --- @param #any cellOrName (optional) cell or cell name in their exterior world space to query --- @return #nil, #number Land texture index or nil if failed to retrieve the texture --- @return #nil, #number Plugin id or nil if failed to retrieve the texture --- @return #nil, #string Texture path or nil if one isn't defined - --- -- Return l10n formatting function for the given context. -- Localisation files (containing the message names and translations) should be stored in @@ -466,10 +450,30 @@ -- @usage for _, item in ipairs(inventory:findAll('common_shirt_01')) do ... end +--- @{#Land}: Functions for interacting with land data +-- @field [parent=#core] #Land land + + --- @{#Magic}: spells and spell effects -- @field [parent=#core] #Magic magic +--- +-- Get the terrain height at a given location. +-- @function [parent=#Land] getHeightAt +-- @param openmw.util#Vector3 position +-- @param #any cellOrName (optional) cell or cell name in their exterior world space to query +-- @return #number + +--- +-- Get the terrain texture at a given location. +-- @function [parent=#Land] getLandTextureAt +-- @param openmw.util#Vector3 position +-- @param #any cellOrName (optional) cell or cell name in their exterior world space to query +-- @return #nil, #number Land texture index or nil if failed to retrieve the texture +-- @return #nil, #number Plugin id or nil if failed to retrieve the texture +-- @return #nil, #string Texture path or nil if one isn't defined + --- Possible @{#SpellRange} values -- @field [parent=#Magic] #SpellRange RANGE From 7d2f5ffac225812a49f527a9cbbeb30d20bd9566 Mon Sep 17 00:00:00 2001 From: Calandiel Date: Tue, 20 Aug 2024 22:52:55 +0200 Subject: [PATCH 10/33] apply changes requested in the code review --- apps/openmw/mwlua/landbindings.cpp | 18 +++++++++--------- components/esmterrain/storage.cpp | 5 +++-- components/esmterrain/storage.hpp | 2 +- files/lua_api/openmw/core.lua | 4 ++-- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index 37e95183ca..3f5c0f2e40 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -17,13 +17,6 @@ namespace MWLua sol::state_view& lua = context.mLua->sol(); sol::table landApi(lua, sol::create); - // Constants - landApi["RANGE"] = LuaUtil::makeStrictReadOnly(context.mLua->tableFromPairs({ - { "Self", ESM::RT_Self }, - { "Touch", ESM::RT_Touch }, - { "Target", ESM::RT_Target }, - })); - landApi["getHeightAt"] = [](const osg::Vec3f& pos, sol::object cellOrName) { ESM::RefId worldspace; if (cellOrName.is()) @@ -63,7 +56,7 @@ namespace MWLua return ESMTerrain::Storage::getHeightAt(landData->mHeights, landData->sLandSize, pos, cellSize); }; - landApi["getLandTextureAt"] = [lua = context.mLua](const osg::Vec3f& pos, sol::object cellOrName) { + landApi["getTextureAt"] = [lua = context.mLua](const osg::Vec3f& pos, sol::object cellOrName) { sol::variadic_results values; ESM::RefId worldspace; if (cellOrName.is()) @@ -85,6 +78,11 @@ namespace MWLua auto store = MWBase::Environment::get().getESMStore(); // We need to read land twice. Once to get the amount of texture samples per cell edge, and the second time // to get the actual data + // This is because the visual land textures are offset with regards to quads that are rendered for terrain. + // To properly calculate that offset, we need to know how many texture samples exist per cell edge, + // as it differs between tes3 and tes4. It's equal - + // Once we know the value, we will calculate the offset and retrieve a sample again, this time + // with the offset taken into account. auto landStore = store->get(); auto land = landStore.search(cellX, cellY); const ESM::Land::LandData* landData = nullptr; @@ -105,6 +103,8 @@ namespace MWLua return values; } + // Use landData to get amount of sampler per cell edge (sLandTextureSize) + // and then get the corrected position that will map to the rendered texture const osg::Vec3f correctedPos = ESMTerrain::Storage::getTextureCorrectedWorldPos(pos, landData->sLandTextureSize, cellSize); int correctedCellX = static_cast(std::floor(correctedPos.x() / cellSize)); @@ -128,7 +128,7 @@ namespace MWLua // We're passing in sLandTextureSize, NOT sLandSize like with getHeightAt const ESMTerrain::UniqueTextureId textureId - = ESMTerrain::Storage::getLandTextureAt(correctedLandData->mTextures, correctedLand->getPlugin(), + = ESMTerrain::Storage::getTextureAt(correctedLandData->mTextures, correctedLand->getPlugin(), correctedLandData->sLandTextureSize, correctedPos, cellSize); // Need to check for 0, 0 so that we can safely subtract 1 later, as per documentation on UniqueTextureId diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 903ec11c29..aa9ef3e362 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -2,11 +2,11 @@ #include #include -#include #include #include #include +#include #include #include @@ -475,6 +475,7 @@ namespace ESMTerrain blendmaps.clear(); // If a single texture fills the whole terrain, there is no need to blend } + // Returns a position that can be used to look up a land texture, while taking their offset into account osg::Vec3f Storage::getTextureCorrectedWorldPos( const osg::Vec3f& uncorrectedWorldPos, const int textureSize, const float cellSize) { @@ -485,7 +486,7 @@ namespace ESMTerrain } // Takes in a corrected world pos to match the visuals. - UniqueTextureId Storage::getLandTextureAt(const std::span landData, const int plugin, + UniqueTextureId Storage::getTextureAt(const std::span landData, const int plugin, const int textureSize, const osg::Vec3f& correctedWorldPos, const float cellSize) { int cellX = static_cast(std::floor(correctedWorldPos.x() / cellSize)); diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 7b4e9f8694..6ca33b18bf 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -120,7 +120,7 @@ namespace ESMTerrain static osg::Vec3f getTextureCorrectedWorldPos( const osg::Vec3f& uncorrectedWorldPos, const int textureSize, const float cellSize); - static UniqueTextureId getLandTextureAt(const std::span landData, const int plugin, + static UniqueTextureId getTextureAt(const std::span landData, const int plugin, const int textureSize, const osg::Vec3f& worldPos, const float cellSize); /// Get the transformation factor for mapping cell units to world units. diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 30db9b6791..0636682669 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -467,10 +467,10 @@ --- -- Get the terrain texture at a given location. --- @function [parent=#Land] getLandTextureAt +-- @function [parent=#Land] getTextureAt -- @param openmw.util#Vector3 position -- @param #any cellOrName (optional) cell or cell name in their exterior world space to query --- @return #nil, #number Land texture index or nil if failed to retrieve the texture +-- @return #nil, #number Land texture index or nil if failed to retrieve the texture. Landscape textures created through editors such as openmw-cs can be assigned an id to differentiate them, that is also used for terrain rendering. The value returned here corresponds to that value. See also LTEX records (https://en.uesp.net/wiki/Morrowind_Mod:Mod_File_Format/LTEX) -- @return #nil, #number Plugin id or nil if failed to retrieve the texture -- @return #nil, #string Texture path or nil if one isn't defined From fd3172d5dd9758a5357188c71fb69f4f448d39d4 Mon Sep 17 00:00:00 2001 From: Calandiel Date: Thu, 22 Aug 2024 22:38:38 +0200 Subject: [PATCH 11/33] remove unnecessary land loading --- apps/openmw/mwlua/landbindings.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index 3f5c0f2e40..3b07e119be 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -41,12 +41,6 @@ namespace MWLua if (land != nullptr) { landData = land->getLandData(ESM::Land::DATA_VHGT); - if (landData != nullptr) - { - // Ensure data is loaded if necessary - land->loadData(ESM::Land::DATA_VHGT); - landData = land->getLandData(ESM::Land::DATA_VHGT); - } } if (landData == nullptr) { @@ -89,12 +83,6 @@ namespace MWLua if (land != nullptr) { landData = land->getLandData(ESM::Land::DATA_VTEX); - if (landData != nullptr) - { - // Ensure data is loaded if necessary - land->loadData(ESM::Land::DATA_VTEX); - landData = land->getLandData(ESM::Land::DATA_VTEX); - } } if (landData == nullptr) { @@ -114,12 +102,6 @@ namespace MWLua if (correctedLand != nullptr) { correctedLandData = correctedLand->getLandData(ESM::Land::DATA_VTEX); - if (correctedLandData != nullptr) - { - // Ensure data is loaded if necessary - land->loadData(ESM::Land::DATA_VTEX); - correctedLandData = correctedLand->getLandData(ESM::Land::DATA_VTEX); - } } if (correctedLandData == nullptr) { From c03ad51265a130dba096a30655a7ceb4968e4bb6 Mon Sep 17 00:00:00 2001 From: Calandiel Date: Sat, 31 Aug 2024 13:14:44 +0200 Subject: [PATCH 12/33] simplify texture retrieval for land bindings --- components/esmterrain/storage.cpp | 41 ------------------------------- 1 file changed, 41 deletions(-) diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index aa9ef3e362..360e2b6822 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -499,48 +499,7 @@ namespace ESMTerrain int startX = static_cast(nX * textureSize); int startY = static_cast(nY * textureSize); - int endX = startX + 1; - int endY = startY + 1; - endX = std::min(endX, textureSize - 1); - endY = std::min(endY, textureSize - 1); - - float fractionX = std::clamp(nX * textureSize - startX, 0.0f, 1.0f); - float fractionY = std::clamp(nY * textureSize - startY, 0.0f, 1.0f); - - /* For even / odd tri strip rows, triangles are this shape: - even odd - 3---2 3---2 - | / | | \ | - 0---1 0---1 - */ return getTextureIdAt(landData, plugin, startX, startY); - - if (fractionX <= 0.5f) - { - if (fractionY <= 0.5) - { - // 0 - return getTextureIdAt(landData, plugin, startX, startY); - } - else - { - // 3 - return getTextureIdAt(landData, plugin, startX, endY); - } - } - else - { - if (fractionY <= 0.5) - { - // 1 - return getTextureIdAt(landData, plugin, endX, startY); - } - else - { - // 2 - return getTextureIdAt(landData, plugin, endX, endY); - } - } } float Storage::getHeightAt( From 06dc40e025338506f5bb1a71e4b48588861da794 Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Sun, 22 Dec 2024 22:47:31 +0100 Subject: [PATCH 13/33] deduplication for landbindings.cpp + slight api changes - remove textureId from return - return plugin name instead of plugin id --- apps/openmw/mwlua/landbindings.cpp | 135 +++++++++++++---------------- files/lua_api/openmw/core.lua | 3 +- 2 files changed, 60 insertions(+), 78 deletions(-) diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index 3b07e119be..cfa8fd58d3 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -4,72 +4,67 @@ #include #include +#include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" +static const ESM::RefId worldspaceAt(const osg::Vec3f& pos, sol::object cellOrName) +{ + ESM::RefId worldspace; + if (cellOrName.is()) + worldspace = cellOrName.as().mStore->getCell()->getWorldSpace(); + else if (cellOrName.is() && !cellOrName.as().empty()) + worldspace = MWBase::Environment::get() + .getWorldModel() + ->getCell(cellOrName.as()) + .getCell() + ->getWorldSpace(); + else + worldspace = ESM::Cell::sDefaultWorldspaceId; + + return worldspace; +} + +static bool fillLandData(const MWWorld::Store* landStore, const osg::Vec3f& pos, const float cellSize, + const ESM::Land** land, const ESM::Land::LandData** landData) +{ + int cellX = static_cast(std::floor(pos.x() / cellSize)); + int cellY = static_cast(std::floor(pos.y() / cellSize)); + + *land = landStore->search(cellX, cellY); + + if (*land != nullptr) + *landData = (*land)->getLandData(ESM::Land::DATA_VTEX); + + // If we fail to preload land data, return, we need to be able to get *any* land to know how to correct + // the position used to sample terrain + if (*landData == nullptr) + return false; + + return true; +} + namespace MWLua { sol::table initCoreLandBindings(const Context& context) { - sol::state_view& lua = context.mLua->sol(); + auto lua = context.sol(); sol::table landApi(lua, sol::create); landApi["getHeightAt"] = [](const osg::Vec3f& pos, sol::object cellOrName) { - ESM::RefId worldspace; - if (cellOrName.is()) - worldspace = cellOrName.as().mStore->getCell()->getWorldSpace(); - else if (cellOrName.is() && !cellOrName.as().empty()) - worldspace = MWBase::Environment::get() - .getWorldModel() - ->getCell(cellOrName.as()) - .getCell() - ->getWorldSpace(); - else - worldspace = ESM::Cell::sDefaultWorldspaceId; - - const float cellSize = ESM::getCellSize(worldspace); - int cellX = static_cast(std::floor(pos.x() / cellSize)); - int cellY = static_cast(std::floor(pos.y() / cellSize)); - - auto store = MWBase::Environment::get().getESMStore(); - auto landStore = store->get(); - auto land = landStore.search(cellX, cellY); - const ESM::Land::LandData* landData = nullptr; - if (land != nullptr) - { - landData = land->getLandData(ESM::Land::DATA_VHGT); - } - if (landData == nullptr) - { - // If we failed to load data, return the default height - return static_cast(ESM::Land::DEFAULT_HEIGHT); - } - return ESMTerrain::Storage::getHeightAt(landData->mHeights, landData->sLandSize, pos, cellSize); + auto worldspace = worldspaceAt(pos, cellOrName); + return MWBase::Environment::get().getWorld()->getTerrainHeightAt(pos, worldspace); }; - landApi["getTextureAt"] = [lua = context.mLua](const osg::Vec3f& pos, sol::object cellOrName) { + landApi["getTextureAt"] = [lua = lua](const osg::Vec3f& pos, sol::object cellOrName) { sol::variadic_results values; - ESM::RefId worldspace; - if (cellOrName.is()) - worldspace = cellOrName.as().mStore->getCell()->getWorldSpace(); - else if (cellOrName.is() && !cellOrName.as().empty()) - worldspace = MWBase::Environment::get() - .getWorldModel() - ->getCell(cellOrName.as()) - .getCell() - ->getWorldSpace(); - else - worldspace = ESM::Cell::sDefaultWorldspaceId; - - const float cellSize = ESM::getCellSize(worldspace); - - int cellX = static_cast(std::floor(pos.x() / cellSize)); - int cellY = static_cast(std::floor(pos.y() / cellSize)); - auto store = MWBase::Environment::get().getESMStore(); + auto landStore = store->get(); + + const float cellSize = ESM::getCellSize(worldspaceAt(pos, cellOrName)); // We need to read land twice. Once to get the amount of texture samples per cell edge, and the second time // to get the actual data // This is because the visual land textures are offset with regards to quads that are rendered for terrain. @@ -77,36 +72,22 @@ namespace MWLua // as it differs between tes3 and tes4. It's equal - // Once we know the value, we will calculate the offset and retrieve a sample again, this time // with the offset taken into account. - auto landStore = store->get(); - auto land = landStore.search(cellX, cellY); + const ESM::Land* land = nullptr; const ESM::Land::LandData* landData = nullptr; - if (land != nullptr) - { - landData = land->getLandData(ESM::Land::DATA_VTEX); - } - if (landData == nullptr) - { - // If we fail to preload land data, return, we need to be able to get *any* land to know how to correct - // the position used to sample terrain + + if (!fillLandData(&landStore, pos, cellSize, &land, &landData)) return values; - } // Use landData to get amount of sampler per cell edge (sLandTextureSize) // and then get the corrected position that will map to the rendered texture const osg::Vec3f correctedPos = ESMTerrain::Storage::getTextureCorrectedWorldPos(pos, landData->sLandTextureSize, cellSize); - int correctedCellX = static_cast(std::floor(correctedPos.x() / cellSize)); - int correctedCellY = static_cast(std::floor(correctedPos.y() / cellSize)); - auto correctedLand = landStore.search(correctedCellX, correctedCellY); + + const ESM::Land* correctedLand = nullptr; const ESM::Land::LandData* correctedLandData = nullptr; - if (correctedLand != nullptr) - { - correctedLandData = correctedLand->getLandData(ESM::Land::DATA_VTEX); - } - if (correctedLandData == nullptr) - { + + if (!fillLandData(&landStore, correctedPos, cellSize, &correctedLand, &correctedLandData)) return values; - } // We're passing in sLandTextureSize, NOT sLandSize like with getHeightAt const ESMTerrain::UniqueTextureId textureId @@ -116,15 +97,17 @@ namespace MWLua // Need to check for 0, 0 so that we can safely subtract 1 later, as per documentation on UniqueTextureId if (textureId.first != 0) { - values.push_back(sol::make_object(lua->sol(), textureId.first - 1)); - values.push_back(sol::make_object(lua->sol(), textureId.second)); - + auto store = MWBase::Environment::get().getESMStore(); auto textureStore = store->get(); const std::string* textureString = textureStore.search(textureId.first - 1, textureId.second); if (textureString) - { - values.push_back(sol::make_object(lua->sol(), *textureString)); - } + values.push_back(sol::make_object(lua, *textureString)); + else + return values; + + const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); + if (textureId.second > 0 && textureId.second < contentList.size()) + values.push_back(sol::make_object(lua, contentList[textureId.second])); } return values; diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 0636682669..8ad7a973da 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -470,9 +470,8 @@ -- @function [parent=#Land] getTextureAt -- @param openmw.util#Vector3 position -- @param #any cellOrName (optional) cell or cell name in their exterior world space to query --- @return #nil, #number Land texture index or nil if failed to retrieve the texture. Landscape textures created through editors such as openmw-cs can be assigned an id to differentiate them, that is also used for terrain rendering. The value returned here corresponds to that value. See also LTEX records (https://en.uesp.net/wiki/Morrowind_Mod:Mod_File_Format/LTEX) --- @return #nil, #number Plugin id or nil if failed to retrieve the texture -- @return #nil, #string Texture path or nil if one isn't defined +-- @return #nil, #string Plugin name or nil if failed to retrieve the texture --- Possible @{#SpellRange} values -- @field [parent=#Magic] #SpellRange RANGE From 136dff7df90ad8fcc5776e2600f345b7d6e37079 Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Mon, 6 Jan 2025 21:47:09 +0100 Subject: [PATCH 14/33] fix index check + cast to size_t --- apps/openmw/mwlua/landbindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index cfa8fd58d3..5176c7909c 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -106,7 +106,7 @@ namespace MWLua return values; const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); - if (textureId.second > 0 && textureId.second < contentList.size()) + if (textureId.second >= 0 && static_cast(textureId.second) < contentList.size()) values.push_back(sol::make_object(lua, contentList[textureId.second])); } From 0a27d1b9674bbf5fb8c26dd4b9d868a254cc76f0 Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Wed, 8 Jan 2025 21:05:08 +0100 Subject: [PATCH 15/33] landbindings - static to anonymous namespace + use references --- apps/openmw/mwlua/landbindings.cpp | 62 +++++++++++++++--------------- components/esmterrain/storage.cpp | 2 - 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index 5176c7909c..aef7ab47c6 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -11,40 +11,43 @@ #include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" -static const ESM::RefId worldspaceAt(const osg::Vec3f& pos, sol::object cellOrName) +namespace { - ESM::RefId worldspace; - if (cellOrName.is()) - worldspace = cellOrName.as().mStore->getCell()->getWorldSpace(); - else if (cellOrName.is() && !cellOrName.as().empty()) - worldspace = MWBase::Environment::get() - .getWorldModel() - ->getCell(cellOrName.as()) - .getCell() - ->getWorldSpace(); - else - worldspace = ESM::Cell::sDefaultWorldspaceId; + const ESM::RefId worldspaceAt(const osg::Vec3f& pos, sol::object cellOrName) + { + ESM::RefId worldspace; + if (cellOrName.is()) + worldspace = cellOrName.as().mStore->getCell()->getWorldSpace(); + else if (cellOrName.is() && !cellOrName.as().empty()) + worldspace = MWBase::Environment::get() + .getWorldModel() + ->getCell(cellOrName.as()) + .getCell() + ->getWorldSpace(); + else + worldspace = ESM::Cell::sDefaultWorldspaceId; - return worldspace; -} + return worldspace; + } -static bool fillLandData(const MWWorld::Store* landStore, const osg::Vec3f& pos, const float cellSize, - const ESM::Land** land, const ESM::Land::LandData** landData) -{ - int cellX = static_cast(std::floor(pos.x() / cellSize)); - int cellY = static_cast(std::floor(pos.y() / cellSize)); + bool fillLandData(const MWWorld::Store& landStore, const osg::Vec3f& pos, const float cellSize, + const ESM::Land::LandData*& landData) + { + int cellX = static_cast(std::floor(pos.x() / cellSize)); + int cellY = static_cast(std::floor(pos.y() / cellSize)); - *land = landStore->search(cellX, cellY); + const ESM::Land* land = landStore.search(cellX, cellY); - if (*land != nullptr) - *landData = (*land)->getLandData(ESM::Land::DATA_VTEX); + if (land != nullptr) + landData = land->getLandData(ESM::Land::DATA_VTEX); - // If we fail to preload land data, return, we need to be able to get *any* land to know how to correct - // the position used to sample terrain - if (*landData == nullptr) - return false; + // If we fail to preload land data, return, we need to be able to get *any* land to know how to correct + // the position used to sample terrain + if (landData == nullptr) + return false; - return true; + return true; + } } namespace MWLua @@ -72,10 +75,9 @@ namespace MWLua // as it differs between tes3 and tes4. It's equal - // Once we know the value, we will calculate the offset and retrieve a sample again, this time // with the offset taken into account. - const ESM::Land* land = nullptr; const ESM::Land::LandData* landData = nullptr; - if (!fillLandData(&landStore, pos, cellSize, &land, &landData)) + if (!fillLandData(landStore, pos, cellSize, landData)) return values; // Use landData to get amount of sampler per cell edge (sLandTextureSize) @@ -86,7 +88,7 @@ namespace MWLua const ESM::Land* correctedLand = nullptr; const ESM::Land::LandData* correctedLandData = nullptr; - if (!fillLandData(&landStore, correctedPos, cellSize, &correctedLand, &correctedLandData)) + if (!fillLandData(landStore, correctedPos, cellSize, correctedLandData)) return values; // We're passing in sLandTextureSize, NOT sLandSize like with getHeightAt diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 360e2b6822..c4adda54a1 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -505,8 +505,6 @@ namespace ESMTerrain float Storage::getHeightAt( const std::span data, const int landSize, const osg::Vec3f& worldPos, const float cellSize) { - // if (!data) - // return defaultHeight; int cellX = static_cast(std::floor(worldPos.x() / cellSize)); int cellY = static_cast(std::floor(worldPos.y() / cellSize)); From 96f329a3f7852b3c04de2bc68343227676c0e452 Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Wed, 8 Jan 2025 21:10:34 +0100 Subject: [PATCH 16/33] don't copy store --- apps/openmw/mwlua/landbindings.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index aef7ab47c6..1e49db5c5e 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -65,7 +65,7 @@ namespace MWLua landApi["getTextureAt"] = [lua = lua](const osg::Vec3f& pos, sol::object cellOrName) { sol::variadic_results values; auto store = MWBase::Environment::get().getESMStore(); - auto landStore = store->get(); + const auto &landStore = store->get(); const float cellSize = ESM::getCellSize(worldspaceAt(pos, cellOrName)); // We need to read land twice. Once to get the amount of texture samples per cell edge, and the second time @@ -100,7 +100,7 @@ namespace MWLua if (textureId.first != 0) { auto store = MWBase::Environment::get().getESMStore(); - auto textureStore = store->get(); + const auto& textureStore = store->get(); const std::string* textureString = textureStore.search(textureId.first - 1, textureId.second); if (textureString) values.push_back(sol::make_object(lua, *textureString)); From d475d01fb483519e38354e091e6dcd03e405da76 Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Wed, 8 Jan 2025 21:51:01 +0100 Subject: [PATCH 17/33] don't touch ESMTerrain::Storage --- apps/openmw/mwlua/landbindings.cpp | 40 ++++++++++++-- components/esmterrain/storage.cpp | 84 +++++++----------------------- components/esmterrain/storage.hpp | 18 +------ 3 files changed, 58 insertions(+), 84 deletions(-) diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index 1e49db5c5e..de039cf750 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -13,6 +13,39 @@ namespace { + osg::Vec3f getTextureCorrectedWorldPos( + const osg::Vec3f& uncorrectedWorldPos, const int textureSize, const float cellSize) + { + // the offset is [-0.25, +0.25] of a single texture's size + // TODO: verify whether or not this works in TES4 and beyond + float offset = (cellSize / textureSize) * 0.25; + return uncorrectedWorldPos + osg::Vec3f{ -offset, +offset, 0.0f }; + } + + // Takes in a corrected world pos to match the visuals. + ESMTerrain::UniqueTextureId getTextureAt(const std::span landData, const int plugin, + const int textureSize, const osg::Vec3f& correctedWorldPos, const float cellSize) + { + int cellX = static_cast(std::floor(correctedWorldPos.x() / cellSize)); + int cellY = static_cast(std::floor(correctedWorldPos.y() / cellSize)); + + // Normalized position in the cell + float nX = (correctedWorldPos.x() - (cellX * cellSize)) / cellSize; + float nY = (correctedWorldPos.y() - (cellY * cellSize)) / cellSize; + + int startX = static_cast(nX * textureSize); + int startY = static_cast(nY * textureSize); + + assert(startX < ESM::Land::LAND_TEXTURE_SIZE); + assert(startY < ESM::Land::LAND_TEXTURE_SIZE); + + const std::uint16_t tex = landData[startY * ESM::Land::LAND_TEXTURE_SIZE + startX]; + if (tex == 0) + return { 0, 0 }; // vtex 0 is always the base texture, regardless of plugin + + return { tex, plugin }; + } + const ESM::RefId worldspaceAt(const osg::Vec3f& pos, sol::object cellOrName) { ESM::RefId worldspace; @@ -65,7 +98,7 @@ namespace MWLua landApi["getTextureAt"] = [lua = lua](const osg::Vec3f& pos, sol::object cellOrName) { sol::variadic_results values; auto store = MWBase::Environment::get().getESMStore(); - const auto &landStore = store->get(); + const auto& landStore = store->get(); const float cellSize = ESM::getCellSize(worldspaceAt(pos, cellOrName)); // We need to read land twice. Once to get the amount of texture samples per cell edge, and the second time @@ -82,8 +115,7 @@ namespace MWLua // Use landData to get amount of sampler per cell edge (sLandTextureSize) // and then get the corrected position that will map to the rendered texture - const osg::Vec3f correctedPos - = ESMTerrain::Storage::getTextureCorrectedWorldPos(pos, landData->sLandTextureSize, cellSize); + const osg::Vec3f correctedPos = getTextureCorrectedWorldPos(pos, landData->sLandTextureSize, cellSize); const ESM::Land* correctedLand = nullptr; const ESM::Land::LandData* correctedLandData = nullptr; @@ -93,7 +125,7 @@ namespace MWLua // We're passing in sLandTextureSize, NOT sLandSize like with getHeightAt const ESMTerrain::UniqueTextureId textureId - = ESMTerrain::Storage::getTextureAt(correctedLandData->mTextures, correctedLand->getPlugin(), + = getTextureAt(correctedLandData->mTextures, correctedLand->getPlugin(), correctedLandData->sLandTextureSize, correctedPos, cellSize); // Need to check for 0, 0 so that we can safely subtract 1 later, as per documentation on UniqueTextureId diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index c4adda54a1..35ec814aa2 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include @@ -23,19 +22,6 @@ namespace ESMTerrain { namespace { - UniqueTextureId getTextureIdAt( - const std::span data, const int plugin, const std::size_t x, const std::size_t y) - { - assert(x < ESM::Land::LAND_TEXTURE_SIZE); - assert(y < ESM::Land::LAND_TEXTURE_SIZE); - - const std::uint16_t tex = data[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, plugin }; - } - UniqueTextureId getTextureIdAt(const LandObject* land, std::size_t x, std::size_t y) { assert(x < ESM::Land::LAND_TEXTURE_SIZE); @@ -48,7 +34,11 @@ namespace ESMTerrain if (data == nullptr) return { 0, 0 }; - return getTextureIdAt(data->getTextures(), land->getPlugin(), x, y); + 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() }; } } @@ -475,39 +465,21 @@ namespace ESMTerrain blendmaps.clear(); // If a single texture fills the whole terrain, there is no need to blend } - // Returns a position that can be used to look up a land texture, while taking their offset into account - osg::Vec3f Storage::getTextureCorrectedWorldPos( - const osg::Vec3f& uncorrectedWorldPos, const int textureSize, const float cellSize) - { - // the offset is [-0.25, +0.25] of a single texture's size - // TODO: verify whether or not this works in TES4 and beyond - float offset = (cellSize / textureSize) * 0.25; - return uncorrectedWorldPos + osg::Vec3f{ -offset, +offset, 0.0f }; - } - - // Takes in a corrected world pos to match the visuals. - UniqueTextureId Storage::getTextureAt(const std::span landData, const int plugin, - const int textureSize, const osg::Vec3f& correctedWorldPos, const float cellSize) - { - int cellX = static_cast(std::floor(correctedWorldPos.x() / cellSize)); - int cellY = static_cast(std::floor(correctedWorldPos.y() / cellSize)); - - // Normalized position in the cell - float nX = (correctedWorldPos.x() - (cellX * cellSize)) / cellSize; - float nY = (correctedWorldPos.y() - (cellY * cellSize)) / cellSize; - - int startX = static_cast(nX * textureSize); - int startY = static_cast(nY * textureSize); - - return getTextureIdAt(landData, plugin, startX, startY); - } - - float Storage::getHeightAt( - const std::span data, const int landSize, const osg::Vec3f& worldPos, const float cellSize) + float Storage::getHeightAt(const osg::Vec3f& worldPos, ESM::RefId worldspace) { + const float cellSize = ESM::getCellSize(worldspace); int cellX = static_cast(std::floor(worldPos.x() / cellSize)); int cellY = static_cast(std::floor(worldPos.y() / cellSize)); + osg::ref_ptr land = getLand(ESM::ExteriorCellLocation(cellX, cellY, worldspace)); + if (!land) + return ESM::isEsm4Ext(worldspace) ? std::numeric_limits::lowest() : defaultHeight; + + const ESM::LandData* data = land->getData(ESM::Land::DATA_VHGT); + if (!data) + return defaultHeight; + const int landSize = data->getLandSize(); + // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition // Normalized position in the cell @@ -544,10 +516,10 @@ namespace ESMTerrain */ // Build all 4 positions in normalized cell space, using point-sampled height - osg::Vec3f v0(startXTS, startYTS, Storage::getVertexHeight(data, landSize, startX, startY) / cellSize); - osg::Vec3f v1(endXTS, startYTS, Storage::getVertexHeight(data, landSize, endX, startY) / cellSize); - osg::Vec3f v2(endXTS, endYTS, Storage::getVertexHeight(data, landSize, endX, endY) / cellSize); - osg::Vec3f v3(startXTS, endYTS, Storage::getVertexHeight(data, landSize, startX, endY) / cellSize); + osg::Vec3f v0(startXTS, startYTS, getVertexHeight(data, startX, startY) / cellSize); + osg::Vec3f v1(endXTS, startYTS, getVertexHeight(data, endX, startY) / cellSize); + osg::Vec3f v2(endXTS, endYTS, getVertexHeight(data, endX, endY) / cellSize); + osg::Vec3f v3(startXTS, endYTS, getVertexHeight(data, startX, endY) / cellSize); // define this plane in terrain space osg::Plane plane; // FIXME: deal with differing triangle alignment @@ -576,22 +548,6 @@ namespace ESMTerrain return (-plane.getNormal().x() * nX - plane.getNormal().y() * nY - plane[3]) / plane.getNormal().z() * cellSize; } - float Storage::getHeightAt(const osg::Vec3f& worldPos, ESM::RefId worldspace) - { - const float cellSize = ESM::getCellSize(worldspace); - int cellX = static_cast(std::floor(worldPos.x() / cellSize)); - int cellY = static_cast(std::floor(worldPos.y() / cellSize)); - - osg::ref_ptr land = getLand(ESM::ExteriorCellLocation(cellX, cellY, worldspace)); - if (!land) - return ESM::isEsm4Ext(worldspace) ? std::numeric_limits::lowest() : defaultHeight; - - const ESM::LandData* data = land->getData(ESM::Land::DATA_VHGT); - if (!data) - return defaultHeight; - return Storage::getHeightAt(data->getHeights(), data->getLandSize(), worldPos, cellSize); - } - const LandObject* Storage::getLand(ESM::ExteriorCellLocation cellLocation, LandCache& cache) { if (const auto land = cache.find(cellLocation.mX, cellLocation.mY)) diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index 6ca33b18bf..402f2147ab 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -114,15 +114,6 @@ namespace ESMTerrain float getHeightAt(const osg::Vec3f& worldPos, ESM::RefId worldspace) override; - static float getHeightAt( - const std::span data, const int landSize, const osg::Vec3f& worldPos, const float cellSize); - - static osg::Vec3f getTextureCorrectedWorldPos( - const osg::Vec3f& uncorrectedWorldPos, const int textureSize, const float cellSize); - - static UniqueTextureId getTextureAt(const std::span landData, const int plugin, - const int textureSize, const osg::Vec3f& worldPos, const float cellSize); - /// Get the transformation factor for mapping cell units to world units. float getCellWorldSize(ESM::RefId worldspace) override; @@ -131,17 +122,12 @@ namespace ESMTerrain int getBlendmapScale(float chunkSize) override; - static float getVertexHeight(const ESM::LandData* data, int x, int y) + float getVertexHeight(const ESM::LandData* data, int x, int y) { const int landSize = data->getLandSize(); - return getVertexHeight(data->getHeights(), landSize, x, y); - } - - static float getVertexHeight(const std::span data, const int landSize, int x, int y) - { assert(x < landSize); assert(y < landSize); - return data[y * landSize + x]; + return data->getHeights()[y * landSize + x]; } private: From 6d2b378dc6ac893ed5537d3381886e27b853b12f Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Wed, 8 Jan 2025 22:03:32 +0100 Subject: [PATCH 18/33] fixes for landbindings.cpp --- apps/openmw/mwlua/landbindings.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index de039cf750..e865c8997d 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -64,12 +64,12 @@ namespace } bool fillLandData(const MWWorld::Store& landStore, const osg::Vec3f& pos, const float cellSize, - const ESM::Land::LandData*& landData) + const ESM::Land*& land, const ESM::Land::LandData*& landData) { int cellX = static_cast(std::floor(pos.x() / cellSize)); int cellY = static_cast(std::floor(pos.y() / cellSize)); - const ESM::Land* land = landStore.search(cellX, cellY); + land = landStore.search(cellX, cellY); if (land != nullptr) landData = land->getLandData(ESM::Land::DATA_VTEX); @@ -108,9 +108,10 @@ namespace MWLua // as it differs between tes3 and tes4. It's equal - // Once we know the value, we will calculate the offset and retrieve a sample again, this time // with the offset taken into account. + const ESM::Land* land = nullptr; const ESM::Land::LandData* landData = nullptr; - if (!fillLandData(landStore, pos, cellSize, landData)) + if (!fillLandData(landStore, pos, cellSize, land, landData)) return values; // Use landData to get amount of sampler per cell edge (sLandTextureSize) @@ -120,13 +121,12 @@ namespace MWLua const ESM::Land* correctedLand = nullptr; const ESM::Land::LandData* correctedLandData = nullptr; - if (!fillLandData(landStore, correctedPos, cellSize, correctedLandData)) + if (!fillLandData(landStore, correctedPos, cellSize, correctedLand, correctedLandData)) return values; // We're passing in sLandTextureSize, NOT sLandSize like with getHeightAt - const ESMTerrain::UniqueTextureId textureId - = getTextureAt(correctedLandData->mTextures, correctedLand->getPlugin(), - correctedLandData->sLandTextureSize, correctedPos, cellSize); + const ESMTerrain::UniqueTextureId textureId = getTextureAt(correctedLandData->mTextures, + correctedLand->getPlugin(), correctedLandData->sLandTextureSize, correctedPos, cellSize); // Need to check for 0, 0 so that we can safely subtract 1 later, as per documentation on UniqueTextureId if (textureId.first != 0) From 997cbacbb01210aa35dca626a98a2b67bea1705b Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Sun, 12 Jan 2025 15:00:28 +0100 Subject: [PATCH 19/33] remove unused include --- apps/openmw/mwlua/corebindings.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index 6c772fa0a9..80f50ed6b5 100644 --- a/apps/openmw/mwlua/corebindings.cpp +++ b/apps/openmw/mwlua/corebindings.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include #include From 9ffcd1ec413dd531a387deed1de0c2f85bb07155 Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Sun, 12 Jan 2025 15:07:44 +0100 Subject: [PATCH 20/33] no auto in landbindings.cpp --- apps/openmw/mwlua/landbindings.cpp | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index e865c8997d..4384f0aa66 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -87,18 +87,18 @@ namespace MWLua { sol::table initCoreLandBindings(const Context& context) { - auto lua = context.sol(); + sol::state_view lua = context.sol(); sol::table landApi(lua, sol::create); landApi["getHeightAt"] = [](const osg::Vec3f& pos, sol::object cellOrName) { - auto worldspace = worldspaceAt(pos, cellOrName); + ESM::RefId worldspace = worldspaceAt(pos, cellOrName); return MWBase::Environment::get().getWorld()->getTerrainHeightAt(pos, worldspace); }; landApi["getTextureAt"] = [lua = lua](const osg::Vec3f& pos, sol::object cellOrName) { sol::variadic_results values; - auto store = MWBase::Environment::get().getESMStore(); - const auto& landStore = store->get(); + Misc::NotNullPtr store = MWBase::Environment::get().getESMStore(); + const MWWorld::Store& landStore = store->get(); const float cellSize = ESM::getCellSize(worldspaceAt(pos, cellOrName)); // We need to read land twice. Once to get the amount of texture samples per cell edge, and the second time @@ -131,14 +131,13 @@ namespace MWLua // Need to check for 0, 0 so that we can safely subtract 1 later, as per documentation on UniqueTextureId if (textureId.first != 0) { - auto store = MWBase::Environment::get().getESMStore(); - const auto& textureStore = store->get(); + Misc::NotNullPtr store = MWBase::Environment::get().getESMStore(); + const MWWorld::Store& textureStore = store->get(); const std::string* textureString = textureStore.search(textureId.first - 1, textureId.second); - if (textureString) - values.push_back(sol::make_object(lua, *textureString)); - else + if (!textureString) return values; + values.push_back(sol::make_object(lua, *textureString)); const std::vector& contentList = MWBase::Environment::get().getWorld()->getContentFiles(); if (textureId.second >= 0 && static_cast(textureId.second) < contentList.size()) values.push_back(sol::make_object(lua, contentList[textureId.second])); From 425b733dbe453912f0366f0ac17679e71dd48f6b Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Sun, 12 Jan 2025 15:09:22 +0100 Subject: [PATCH 21/33] reorder of documentation for landbindings in core.lua --- files/lua_api/openmw/core.lua | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 8ad7a973da..3c00f49354 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -453,11 +453,6 @@ --- @{#Land}: Functions for interacting with land data -- @field [parent=#core] #Land land - ---- @{#Magic}: spells and spell effects --- @field [parent=#core] #Magic magic - - --- -- Get the terrain height at a given location. -- @function [parent=#Land] getHeightAt @@ -473,6 +468,10 @@ -- @return #nil, #string Texture path or nil if one isn't defined -- @return #nil, #string Plugin name or nil if failed to retrieve the texture + +--- @{#Magic}: spells and spell effects +-- @field [parent=#core] #Magic magic + --- Possible @{#SpellRange} values -- @field [parent=#Magic] #SpellRange RANGE From 433728db31aa3d961ebb454bb87960e4f13da774 Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Sun, 12 Jan 2025 15:22:07 +0100 Subject: [PATCH 22/33] Misc::NotNullPtr to MWWorld::ESMStore& store --- apps/openmw/mwlua/landbindings.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index 4384f0aa66..cd3a68fbb4 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -97,8 +97,8 @@ namespace MWLua landApi["getTextureAt"] = [lua = lua](const osg::Vec3f& pos, sol::object cellOrName) { sol::variadic_results values; - Misc::NotNullPtr store = MWBase::Environment::get().getESMStore(); - const MWWorld::Store& landStore = store->get(); + MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + const MWWorld::Store& landStore = store.get(); const float cellSize = ESM::getCellSize(worldspaceAt(pos, cellOrName)); // We need to read land twice. Once to get the amount of texture samples per cell edge, and the second time @@ -131,8 +131,8 @@ namespace MWLua // Need to check for 0, 0 so that we can safely subtract 1 later, as per documentation on UniqueTextureId if (textureId.first != 0) { - Misc::NotNullPtr store = MWBase::Environment::get().getESMStore(); - const MWWorld::Store& textureStore = store->get(); + MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + const MWWorld::Store& textureStore = store.get(); const std::string* textureString = textureStore.search(textureId.first - 1, textureId.second); if (!textureString) return values; From 432236353f999b459be3c1deea79854b457098cb Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Sun, 12 Jan 2025 17:26:20 +0100 Subject: [PATCH 23/33] no interior cell + make cellOrName mandatory --- apps/openmw/mwlua/landbindings.cpp | 24 +++++++++++------------- files/lua_api/openmw/core.lua | 2 +- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index cd3a68fbb4..72af17776a 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -46,21 +46,19 @@ namespace return { tex, plugin }; } - const ESM::RefId worldspaceAt(const osg::Vec3f& pos, sol::object cellOrName) + const ESM::RefId worldspaceAt(sol::object cellOrName) { - ESM::RefId worldspace; + const MWWorld::Cell* cell = nullptr; if (cellOrName.is()) - worldspace = cellOrName.as().mStore->getCell()->getWorldSpace(); + cell = cellOrName.as().mStore->getCell(); else if (cellOrName.is() && !cellOrName.as().empty()) - worldspace = MWBase::Environment::get() - .getWorldModel() - ->getCell(cellOrName.as()) - .getCell() - ->getWorldSpace(); - else - worldspace = ESM::Cell::sDefaultWorldspaceId; + cell = MWBase::Environment::get().getWorldModel()->getCell(cellOrName.as()).getCell(); + if (cell = nullptr) + throw std::runtime_error("Invalid cell"); + else if (!cell->isExterior()) + throw std::runtime_error("Cell cannot be interior"); - return worldspace; + return cell->getWorldSpace(); } bool fillLandData(const MWWorld::Store& landStore, const osg::Vec3f& pos, const float cellSize, @@ -91,7 +89,7 @@ namespace MWLua sol::table landApi(lua, sol::create); landApi["getHeightAt"] = [](const osg::Vec3f& pos, sol::object cellOrName) { - ESM::RefId worldspace = worldspaceAt(pos, cellOrName); + ESM::RefId worldspace = worldspaceAt(cellOrName); return MWBase::Environment::get().getWorld()->getTerrainHeightAt(pos, worldspace); }; @@ -100,7 +98,7 @@ namespace MWLua MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); const MWWorld::Store& landStore = store.get(); - const float cellSize = ESM::getCellSize(worldspaceAt(pos, cellOrName)); + const float cellSize = ESM::getCellSize(worldspaceAt(cellOrName)); // We need to read land twice. Once to get the amount of texture samples per cell edge, and the second time // to get the actual data // This is because the visual land textures are offset with regards to quads that are rendered for terrain. diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index 3c00f49354..b2bd58d23c 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -464,7 +464,7 @@ -- Get the terrain texture at a given location. -- @function [parent=#Land] getTextureAt -- @param openmw.util#Vector3 position --- @param #any cellOrName (optional) cell or cell name in their exterior world space to query +-- @param #any cellOrName cell or cell name in their exterior world space to query -- @return #nil, #string Texture path or nil if one isn't defined -- @return #nil, #string Plugin name or nil if failed to retrieve the texture From e19647ca94911198c0cffd3fc017b029611f6a68 Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Sun, 12 Jan 2025 17:36:31 +0100 Subject: [PATCH 24/33] remove redundant code --- apps/openmw/mwlua/landbindings.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index 72af17776a..996d749c3b 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -129,7 +129,6 @@ namespace MWLua // Need to check for 0, 0 so that we can safely subtract 1 later, as per documentation on UniqueTextureId if (textureId.first != 0) { - MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); const MWWorld::Store& textureStore = store.get(); const std::string* textureString = textureStore.search(textureId.first - 1, textureId.second); if (!textureString) From ff94c1a6b76633987c9dae4a76530c875954a883 Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Sun, 12 Jan 2025 17:42:03 +0100 Subject: [PATCH 25/33] fix typo --- apps/openmw/mwlua/landbindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index 996d749c3b..2f99efcc85 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -53,7 +53,7 @@ namespace cell = cellOrName.as().mStore->getCell(); else if (cellOrName.is() && !cellOrName.as().empty()) cell = MWBase::Environment::get().getWorldModel()->getCell(cellOrName.as()).getCell(); - if (cell = nullptr) + if (cell == nullptr) throw std::runtime_error("Invalid cell"); else if (!cell->isExterior()) throw std::runtime_error("Cell cannot be interior"); From 024e01f22a2482187f9bce388c389813538722ee Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Sun, 12 Jan 2025 17:50:36 +0100 Subject: [PATCH 26/33] check for lcell also --- apps/openmw/mwlua/landbindings.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index 2f99efcc85..bd5e72eacd 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -51,6 +51,8 @@ namespace const MWWorld::Cell* cell = nullptr; if (cellOrName.is()) cell = cellOrName.as().mStore->getCell(); + else if (cellOrName.is()) + cell = cellOrName.as().mStore->getCell(); else if (cellOrName.is() && !cellOrName.as().empty()) cell = MWBase::Environment::get().getWorldModel()->getCell(cellOrName.as()).getCell(); if (cell == nullptr) From 869d6e9698ebd1a4ea6d80f58a4114402a067638 Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Sun, 12 Jan 2025 19:47:04 +0100 Subject: [PATCH 27/33] const --- apps/openmw/mwlua/landbindings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index bd5e72eacd..9da50a667b 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -97,7 +97,7 @@ namespace MWLua landApi["getTextureAt"] = [lua = lua](const osg::Vec3f& pos, sol::object cellOrName) { sol::variadic_results values; - MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); + const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); const MWWorld::Store& landStore = store.get(); const float cellSize = ESM::getCellSize(worldspaceAt(cellOrName)); From 6d2ec041bb41d1621fb0b163785300877c98f789 Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Sat, 18 Jan 2025 21:48:31 +0100 Subject: [PATCH 28/33] don't double sample + only allow default worldspace --- apps/openmw/mwlua/landbindings.cpp | 39 +++++++----------------------- 1 file changed, 9 insertions(+), 30 deletions(-) diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index 9da50a667b..509eff3a15 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include "../mwbase/environment.hpp" @@ -13,15 +14,6 @@ namespace { - osg::Vec3f getTextureCorrectedWorldPos( - const osg::Vec3f& uncorrectedWorldPos, const int textureSize, const float cellSize) - { - // the offset is [-0.25, +0.25] of a single texture's size - // TODO: verify whether or not this works in TES4 and beyond - float offset = (cellSize / textureSize) * 0.25; - return uncorrectedWorldPos + osg::Vec3f{ -offset, +offset, 0.0f }; - } - // Takes in a corrected world pos to match the visuals. ESMTerrain::UniqueTextureId getTextureAt(const std::span landData, const int plugin, const int textureSize, const osg::Vec3f& correctedWorldPos, const float cellSize) @@ -59,6 +51,8 @@ namespace throw std::runtime_error("Invalid cell"); else if (!cell->isExterior()) throw std::runtime_error("Cell cannot be interior"); + else if (cell->getWorldSpace() != ESM::Cell::sDefaultWorldspaceId) + throw std::runtime_error("Only default exterior worldspace is supported"); return cell->getWorldSpace(); } @@ -101,32 +95,17 @@ namespace MWLua const MWWorld::Store& landStore = store.get(); const float cellSize = ESM::getCellSize(worldspaceAt(cellOrName)); - // We need to read land twice. Once to get the amount of texture samples per cell edge, and the second time - // to get the actual data - // This is because the visual land textures are offset with regards to quads that are rendered for terrain. - // To properly calculate that offset, we need to know how many texture samples exist per cell edge, - // as it differs between tes3 and tes4. It's equal - - // Once we know the value, we will calculate the offset and retrieve a sample again, this time - // with the offset taken into account. + const float offset = (cellSize / ESM::LandRecordData::sLandTextureSize) * 0.25; + const osg::Vec3f correctedPos = pos + osg::Vec3f{ -offset, +offset, 0.0f }; + const ESM::Land* land = nullptr; const ESM::Land::LandData* landData = nullptr; - if (!fillLandData(landStore, pos, cellSize, land, landData)) + if (!fillLandData(landStore, correctedPos, cellSize, land, landData)) return values; - // Use landData to get amount of sampler per cell edge (sLandTextureSize) - // and then get the corrected position that will map to the rendered texture - const osg::Vec3f correctedPos = getTextureCorrectedWorldPos(pos, landData->sLandTextureSize, cellSize); - - const ESM::Land* correctedLand = nullptr; - const ESM::Land::LandData* correctedLandData = nullptr; - - if (!fillLandData(landStore, correctedPos, cellSize, correctedLand, correctedLandData)) - return values; - - // We're passing in sLandTextureSize, NOT sLandSize like with getHeightAt - const ESMTerrain::UniqueTextureId textureId = getTextureAt(correctedLandData->mTextures, - correctedLand->getPlugin(), correctedLandData->sLandTextureSize, correctedPos, cellSize); + const ESMTerrain::UniqueTextureId textureId = getTextureAt( + landData->mTextures, land->getPlugin(), ESM::LandRecordData::sLandTextureSize, correctedPos, cellSize); // Need to check for 0, 0 so that we can safely subtract 1 later, as per documentation on UniqueTextureId if (textureId.first != 0) From 523e788165e82e324de4564ea3f391c690602d89 Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Sat, 18 Jan 2025 21:56:25 +0100 Subject: [PATCH 29/33] add #8112 to changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 512315ade0..289334699e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +0.50.0 +------ + + Feature #8112: Expose landscape record data to Lua + 0.49.0 ------ From 806894dda3944bb639832a28ea7c736a12ea086a Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Sat, 18 Jan 2025 23:09:51 +0100 Subject: [PATCH 30/33] inline fillLandData and return empty table instead of throwing --- apps/openmw/mwlua/landbindings.cpp | 48 +++++++++++++----------------- 1 file changed, 20 insertions(+), 28 deletions(-) diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index 509eff3a15..05cc4f3773 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include "../mwbase/environment.hpp" @@ -16,7 +15,7 @@ namespace { // Takes in a corrected world pos to match the visuals. ESMTerrain::UniqueTextureId getTextureAt(const std::span landData, const int plugin, - const int textureSize, const osg::Vec3f& correctedWorldPos, const float cellSize) + const osg::Vec3f& correctedWorldPos, const float cellSize) { int cellX = static_cast(std::floor(correctedWorldPos.x() / cellSize)); int cellY = static_cast(std::floor(correctedWorldPos.y() / cellSize)); @@ -25,8 +24,8 @@ namespace float nX = (correctedWorldPos.x() - (cellX * cellSize)) / cellSize; float nY = (correctedWorldPos.y() - (cellY * cellSize)) / cellSize; - int startX = static_cast(nX * textureSize); - int startY = static_cast(nY * textureSize); + int startX = static_cast(nX * ESM::Land::LAND_TEXTURE_SIZE); + int startY = static_cast(nY * ESM::Land::LAND_TEXTURE_SIZE); assert(startX < ESM::Land::LAND_TEXTURE_SIZE); assert(startY < ESM::Land::LAND_TEXTURE_SIZE); @@ -51,30 +50,9 @@ namespace throw std::runtime_error("Invalid cell"); else if (!cell->isExterior()) throw std::runtime_error("Cell cannot be interior"); - else if (cell->getWorldSpace() != ESM::Cell::sDefaultWorldspaceId) - throw std::runtime_error("Only default exterior worldspace is supported"); return cell->getWorldSpace(); } - - bool fillLandData(const MWWorld::Store& landStore, const osg::Vec3f& pos, const float cellSize, - const ESM::Land*& land, const ESM::Land::LandData*& landData) - { - int cellX = static_cast(std::floor(pos.x() / cellSize)); - int cellY = static_cast(std::floor(pos.y() / cellSize)); - - land = landStore.search(cellX, cellY); - - if (land != nullptr) - landData = land->getLandData(ESM::Land::DATA_VTEX); - - // If we fail to preload land data, return, we need to be able to get *any* land to know how to correct - // the position used to sample terrain - if (landData == nullptr) - return false; - - return true; - } } namespace MWLua @@ -93,19 +71,33 @@ namespace MWLua sol::variadic_results values; const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); const MWWorld::Store& landStore = store.get(); + ESM::RefId worldspace = worldspaceAt(cellOrName); - const float cellSize = ESM::getCellSize(worldspaceAt(cellOrName)); + if (worldspace != ESM::Cell::sDefaultWorldspaceId) + return values; + + const float cellSize = ESM::getCellSize(worldspace); const float offset = (cellSize / ESM::LandRecordData::sLandTextureSize) * 0.25; const osg::Vec3f correctedPos = pos + osg::Vec3f{ -offset, +offset, 0.0f }; const ESM::Land* land = nullptr; const ESM::Land::LandData* landData = nullptr; - if (!fillLandData(landStore, correctedPos, cellSize, land, landData)) + int cellX = static_cast(std::floor(correctedPos.x() / cellSize)); + int cellY = static_cast(std::floor(correctedPos.y() / cellSize)); + + land = landStore.search(cellX, cellY); + + if (land != nullptr) + landData = land->getLandData(ESM::Land::DATA_VTEX); + + // If we fail to preload land data, return, we need to be able to get *any* land to know how to correct + // the position used to sample terrain + if (landData == nullptr) return values; const ESMTerrain::UniqueTextureId textureId = getTextureAt( - landData->mTextures, land->getPlugin(), ESM::LandRecordData::sLandTextureSize, correctedPos, cellSize); + landData->mTextures, land->getPlugin(), correctedPos, cellSize); // Need to check for 0, 0 so that we can safely subtract 1 later, as per documentation on UniqueTextureId if (textureId.first != 0) From b8086b47ec84e89ad638330f4f5d8b3f867e23d3 Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Sun, 19 Jan 2025 00:06:10 +0100 Subject: [PATCH 31/33] clang-format --- apps/openmw/mwlua/landbindings.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/landbindings.cpp b/apps/openmw/mwlua/landbindings.cpp index 05cc4f3773..a4ebc0a841 100644 --- a/apps/openmw/mwlua/landbindings.cpp +++ b/apps/openmw/mwlua/landbindings.cpp @@ -96,8 +96,8 @@ namespace MWLua if (landData == nullptr) return values; - const ESMTerrain::UniqueTextureId textureId = getTextureAt( - landData->mTextures, land->getPlugin(), correctedPos, cellSize); + const ESMTerrain::UniqueTextureId textureId + = getTextureAt(landData->mTextures, land->getPlugin(), correctedPos, cellSize); // Need to check for 0, 0 so that we can safely subtract 1 later, as per documentation on UniqueTextureId if (textureId.first != 0) From 60cab47528eae97a983711f0abc2bf6a8423eaa2 Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Sun, 19 Jan 2025 00:07:33 +0100 Subject: [PATCH 32/33] increase api revision --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 170a56aade..1805ea6fea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,7 +82,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 70) +set(OPENMW_LUA_API_REVISION 71) set(OPENMW_POSTPROCESSING_API_REVISION 2) set(OPENMW_VERSION_COMMITHASH "") From 39ef6e3bcf08c560cd5526973e047e3b9e574ef9 Mon Sep 17 00:00:00 2001 From: Sebastian Fieber Date: Sun, 19 Jan 2025 00:10:37 +0100 Subject: [PATCH 33/33] that shouldn't have been touched --- files/lua_api/openmw/core.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index b2bd58d23c..74738a7c4d 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -472,6 +472,7 @@ --- @{#Magic}: spells and spell effects -- @field [parent=#core] #Magic magic + --- Possible @{#SpellRange} values -- @field [parent=#Magic] #SpellRange RANGE