diff --git a/apps/openmw/mwlua/corebindings.cpp b/apps/openmw/mwlua/corebindings.cpp index 9df435c00d..eb0c8c4175 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 readOnlyApi = LuaUtil::makeReadOnly(api); return context.setTypePackage(readOnlyApi, "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: