mirror of
https://github.com/OpenMW/openmw.git
synced 2025-07-01 11:11:34 +00:00
add bindings for land textures
This commit is contained in:
parent
283be9f4f0
commit
1521d5195a
4 changed files with 186 additions and 5 deletions
|
@ -4,6 +4,8 @@
|
|||
#include <apps/openmw/mwworld/cellstore.hpp>
|
||||
#include <apps/openmw/mwworld/worldmodel.hpp>
|
||||
#include <chrono>
|
||||
#include <components/esm3/loadltex.hpp>
|
||||
#include <sol/object.hpp>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <components/debug/debuglog.hpp>
|
||||
|
@ -31,6 +33,7 @@
|
|||
#include <components/esm3/landrecorddata.hpp>
|
||||
#include <components/esm3/loadland.hpp>
|
||||
#include <components/esmterrain/storage.hpp>
|
||||
#include <tuple>
|
||||
|
||||
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<GCell>())
|
||||
worldspace = cellOrName.as<GCell>().mStore->getCell()->getWorldSpace();
|
||||
else if (cellOrName.is<std::string_view>() && !cellOrName.as<std::string_view>().empty())
|
||||
worldspace = MWBase::Environment::get()
|
||||
.getWorldModel()
|
||||
->getCell(cellOrName.as<std::string_view>())
|
||||
.getCell()
|
||||
->getWorldSpace();
|
||||
else
|
||||
worldspace = ESM::Cell::sDefaultWorldspaceId;
|
||||
|
||||
const float cellSize = ESM::getCellSize(worldspace);
|
||||
|
||||
int cellX = static_cast<int>(std::floor(pos.x() / cellSize));
|
||||
int cellY = static_cast<int>(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<ESM::Land>();
|
||||
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<int>(std::floor(correctedPos.x() / cellSize));
|
||||
int correctedCellY = static_cast<int>(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<uint16_t>(lua->sol(), textureId.first - 1));
|
||||
values.push_back(sol::make_object<int>(lua->sol(), textureId.second));
|
||||
|
||||
auto textureStore = store->get<ESM::LandTexture>();
|
||||
const std::string* textureString = textureStore.search(textureId.first - 1, textureId.second);
|
||||
if (textureString)
|
||||
{
|
||||
values.push_back(sol::make_object<std::string>(lua->sol(), *textureString));
|
||||
}
|
||||
}
|
||||
|
||||
return values;
|
||||
};
|
||||
|
||||
sol::table readOnlyApi = LuaUtil::makeReadOnly(api);
|
||||
return context.setTypePackage(readOnlyApi, "openmw_core");
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <osg/Vec3f>
|
||||
#include <stdexcept>
|
||||
|
||||
#include <osg/Image>
|
||||
|
@ -22,6 +23,19 @@ namespace ESMTerrain
|
|||
{
|
||||
namespace
|
||||
{
|
||||
UniqueTextureId getTextureIdAt(
|
||||
const std::span<const std::uint16_t> 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<const std::uint16_t> landData, const int plugin,
|
||||
const int textureSize, const osg::Vec3f& correctedWorldPos, const float cellSize)
|
||||
{
|
||||
int cellX = static_cast<int>(std::floor(correctedWorldPos.x() / cellSize));
|
||||
int cellY = static_cast<int>(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<int>(nX * textureSize);
|
||||
int startY = static_cast<int>(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<const float> data, const int landSize, const osg::Vec3f& worldPos, const float cellSize)
|
||||
{
|
||||
|
|
|
@ -117,6 +117,12 @@ namespace ESMTerrain
|
|||
static float getHeightAt(
|
||||
const std::span<const float> 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<const std::uint16_t> 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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue