1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-02-21 05:09:43 +00:00

Merge branch 'terrain_cleanup' into 'master'

Terrain releated code cleanup

See merge request OpenMW/openmw!3353
This commit is contained in:
psi29a 2023-08-19 09:29:35 +00:00
commit ee93513471
19 changed files with 321 additions and 348 deletions

View file

@ -759,7 +759,7 @@ std::string enchantmentFlags(int flags)
return properties;
}
std::string landFlags(int flags)
std::string landFlags(std::uint32_t flags)
{
std::string properties;
// The ESM component says that this first four bits are used, but

View file

@ -1,6 +1,7 @@
#ifndef OPENMW_ESMTOOL_LABELS_H
#define OPENMW_ESMTOOL_LABELS_H
#include <cstdint>
#include <string>
#include <string_view>
@ -53,7 +54,7 @@ std::string cellFlags(int flags);
std::string containerFlags(int flags);
std::string creatureFlags(int flags);
std::string enchantmentFlags(int flags);
std::string landFlags(int flags);
std::string landFlags(std::uint32_t flags);
std::string creatureListFlags(int flags);
std::string itemListFlags(int flags);
std::string lightFlags(int flags);

View file

@ -206,7 +206,7 @@ namespace NavMeshTool
ESM::Land::DEFAULT_HEIGHT };
ESM::Land::LandData& landData = *landDatas.emplace_back(std::make_unique<ESM::Land::LandData>());
land->loadData(ESM::Land::DATA_VHGT, &landData);
land->loadData(ESM::Land::DATA_VHGT, landData);
heightfields.push_back(std::vector<float>(std::begin(landData.mHeights), std::end(landData.mHeights)));
HeightfieldSurface surface;
surface.mHeights = heightfields.back().data();

View file

@ -39,7 +39,7 @@ namespace CSVRender
const ESM::Land& land = mData.getLand().getRecord(index).get();
return new ESMTerrain::LandObject(
&land, ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX);
land, ESM::Land::DATA_VHGT | ESM::Land::DATA_VNML | ESM::Land::DATA_VCLR | ESM::Land::DATA_VTEX);
}
const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin)

View file

@ -18,6 +18,7 @@
#include <components/sceneutil/workqueue.hpp>
#include <components/esm3/globalmap.hpp>
#include <components/esm3/loadland.hpp>
#include "../mwbase/environment.hpp"

View file

@ -15,39 +15,34 @@ namespace MWRender
: GenericResourceManager<ESM::ExteriorCellLocation>(nullptr)
, mLoadFlags(loadFlags)
{
mCache = new CacheType;
}
osg::ref_ptr<ESMTerrain::LandObject> LandManager::getLand(ESM::ExteriorCellLocation cellIndex)
{
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(cellIndex);
if (obj)
const osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(cellIndex);
if (obj != nullptr)
return static_cast<ESMTerrain::LandObject*>(obj.get());
const MWBase::World& world = *MWBase::Environment::get().getWorld();
osg::ref_ptr<ESMTerrain::LandObject> landObj = nullptr;
if (ESM::isEsm4Ext(cellIndex.mWorldspace))
{
const ESM4::Land* land = world.getStore().get<ESM4::Land>().search(cellIndex);
if (land == nullptr)
return nullptr;
landObj = new ESMTerrain::LandObject(*land, mLoadFlags);
}
else
{
const auto world = MWBase::Environment::get().getWorld();
if (!world)
const ESM::Land* land = world.getStore().get<ESM::Land>().search(cellIndex.mX, cellIndex.mY);
if (land == nullptr)
return nullptr;
if (ESM::isEsm4Ext(cellIndex.mWorldspace))
{
const ESM4::Land* land = world->getStore().get<ESM4::Land>().search(cellIndex);
if (!land)
return nullptr;
osg::ref_ptr<ESMTerrain::LandObject> landObj(new ESMTerrain::LandObject(land, mLoadFlags));
mCache->addEntryToObjectCache(cellIndex, landObj.get());
return landObj;
}
else
{
const ESM::Land* land = world->getStore().get<ESM::Land>().search(cellIndex.mX, cellIndex.mY);
if (!land)
return nullptr;
osg::ref_ptr<ESMTerrain::LandObject> landObj(new ESMTerrain::LandObject(land, mLoadFlags));
mCache->addEntryToObjectCache(cellIndex, landObj.get());
return landObj;
}
landObj = new ESMTerrain::LandObject(*land, mLoadFlags);
}
mCache->addEntryToObjectCache(cellIndex, landObj.get());
return landObj;
}
void LandManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const

View file

@ -1,5 +1,7 @@
#include "terrainstorage.hpp"
#include <components/esm3/loadland.hpp>
#include "../mwbase/environment.hpp"
#include "../mwworld/esmstore.hpp"

View file

@ -7,6 +7,7 @@
#include <components/debug/debuglog.hpp>
#include <components/esm3/loadcell.hpp>
#include <components/loadinglistener/reporter.hpp>
#include <components/misc/constants.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/misc/strings/lower.hpp>
#include <components/resource/bulletshapemanager.hpp>
@ -394,7 +395,7 @@ namespace MWWorld
void CellPreloader::abortTerrainPreloadExcept(const CellPreloader::PositionCellGrid* exceptPos)
{
if (exceptPos && contains(mTerrainPreloadPositions, std::array{ *exceptPos }, ESM::Land::REAL_SIZE))
if (exceptPos && contains(mTerrainPreloadPositions, std::array{ *exceptPos }, Constants::CellSizeInUnits))
return;
if (mTerrainPreloadItem && !mTerrainPreloadItem->isDone())
{
@ -437,7 +438,7 @@ namespace MWWorld
bool CellPreloader::isTerrainLoaded(const CellPreloader::PositionCellGrid& position, double referenceTime) const
{
return mLoadedTerrainTimestamp + mResourceSystem->getSceneManager()->getExpiryDelay() > referenceTime
&& contains(mLoadedTerrainPositions, std::array{ position }, ESM::Land::REAL_SIZE);
&& contains(mLoadedTerrainPositions, std::array{ position }, Constants::CellSizeInUnits);
}
void CellPreloader::setTerrain(Terrain::World* terrain)

View file

@ -37,11 +37,6 @@ namespace ESM
class ESMWriter;
}
namespace ESM4
{
struct Land;
}
namespace Loading
{
class Listener;

View file

@ -1,67 +1,77 @@
#include <components/misc/constants.hpp>
#include "esmterrain.hpp"
#include <components/esm3/loadland.hpp>
#include <components/esm4/loadland.hpp>
#include <components/misc/constants.hpp>
namespace
{
constexpr std::uint16_t textures[ESM::LandRecordData::sLandNumTextures]{ 0 };
std::unique_ptr<const ESM::LandRecordData> loadData(const ESM::Land& land, int loadFlags)
{
std::unique_ptr<ESM::LandRecordData> result = std::make_unique<ESM::LandRecordData>();
land.loadData(loadFlags, *result);
return result;
}
}
namespace ESM
{
LandData::LandData() = default;
}
ESM::LandData::LandData(const ESM::Land& land, int loadFlags)
: mLoadFlags(loadFlags)
: mData(loadData(land, loadFlags))
, mLoadFlags(mData->mDataLoaded)
, mMinHeight(mData->mMinHeight)
, mMaxHeight(mData->mMaxHeight)
, mSize(Constants::CellSizeInUnits)
, mLandSize(ESM::Land::LAND_SIZE)
, mPlugin(land.getPlugin())
, mHeights(mData->mHeights)
, mNormals(mData->mNormals)
, mColors(mData->mColours)
, mTextures(mData->mTextures)
{
ESM::Land::LandData data;
land.loadData(loadFlags, &data);
mLoadFlags = data.mDataLoaded;
std::span<const float> heights(data.mHeights);
mHeights = std::vector(heights.begin(), heights.end());
std::span<const VNML> normals(data.mNormals);
mNormals = std::vector(normals.begin(), normals.end());
std::span<const unsigned char> colors(data.mColours);
mColors = std::vector(colors.begin(), colors.end());
std::span<const uint16_t> textures(data.mTextures);
mTextures = std::vector(textures.begin(), textures.end());
mMinHeight = data.mMinHeight;
mMaxHeight = data.mMaxHeight;
}
ESM::LandData::LandData(const ESM4::Land& land, int /*loadFlags*/)
: mLoadFlags(land.mDataTypes) // ESM4::Land is always fully loaded. TODO: implement lazy loading
, mHeightsData(ESM4::Land::sLandNumVerts)
, mMinHeight(std::numeric_limits<float>::max())
, mMaxHeight(std::numeric_limits<float>::lowest())
, mSize(Constants::ESM4CellSizeInUnits)
, mLandSize(ESM4::Land::VERTS_PER_SIDE)
, mLandSize(ESM4::Land::sVertsPerSide)
, mNormals(land.mVertNorm)
, mColors(land.mVertColr)
, mTextures(textures)
{
mMinHeight = std::numeric_limits<float>::max();
mMaxHeight = std::numeric_limits<float>::lowest();
mHeights.resize(ESM4::Land::LAND_NUM_VERTS);
mTextures.resize(ESM::Land::LAND_NUM_TEXTURES);
std::fill(mTextures.begin(), mTextures.end(), 0);
float row_offset = land.mHeightMap.heightOffset;
float rowOffset = land.mHeightMap.heightOffset;
for (int y = 0; y < mLandSize; y++)
{
row_offset += land.mHeightMap.gradientData[y * mLandSize];
rowOffset += land.mHeightMap.gradientData[y * mLandSize];
const float heightY = row_offset * ESM4::Land::HEIGHT_SCALE;
mHeights[y * mLandSize] = heightY;
const float heightY = rowOffset * ESM4::Land::sHeightScale;
mHeightsData[y * mLandSize] = heightY;
mMinHeight = std::min(mMinHeight, heightY);
mMaxHeight = std::max(mMaxHeight, heightY);
float colOffset = row_offset;
float colOffset = rowOffset;
for (int x = 1; x < mLandSize; x++)
{
colOffset += land.mHeightMap.gradientData[y * mLandSize + x];
const float heightX = colOffset * ESM4::Land::HEIGHT_SCALE;
const float heightX = colOffset * ESM4::Land::sHeightScale;
mMinHeight = std::min(mMinHeight, heightX);
mMaxHeight = std::max(mMaxHeight, heightX);
mHeights[x + y * mLandSize] = heightX;
mHeightsData[x + y * mLandSize] = heightX;
}
}
std::span<const VNML> normals(land.mVertNorm);
mNormals = std::vector(normals.begin(), normals.end());
std::span<const unsigned char> colors(land.mVertColr);
mColors = std::vector(colors.begin(), colors.end());
mHeights = mHeightsData;
}
namespace ESM
{
LandData::~LandData() = default;
}

View file

@ -1,45 +1,54 @@
#ifndef COMPONENTS_ESM_ESMTERRAIN
#define COMPONENTS_ESM_ESMTERRAIN
#include <cstdint>
#include <memory>
#include <span>
#include <vector>
#include <components/esm3/loadland.hpp>
#include <components/esm4/loadland.hpp>
namespace ESM4
{
struct Land;
}
namespace ESM
{
struct Land;
struct LandRecordData;
class LandData
{
public:
~LandData() = default;
LandData() = default;
LandData(const ESM::Land& Land, int loadFLags);
LandData(const ESM4::Land& Land, int loadFLags);
LandData();
LandData(const ESM::Land& Land, int loadFlags);
LandData(const ESM4::Land& Land, int loadFlags);
typedef signed char VNML;
~LandData();
std::span<const float> getHeights() const { return mHeights; }
std::span<const VNML> getNormals() const { return mNormals; }
std::span<const unsigned char> getColors() const { return mColors; }
std::span<const uint16_t> getTextures() const { return mTextures; }
std::span<const std::int8_t> getNormals() const { return mNormals; }
std::span<const std::uint8_t> getColors() const { return mColors; }
std::span<const std::uint16_t> getTextures() const { return mTextures; }
float getSize() const { return mSize; }
float getMinHeight() const { return mMinHeight; }
float getMaxHeight() const { return mMaxHeight; }
int getLandSize() const { return mLandSize; }
int mLoadFlags = 0;
int getLoadFlags() const { return mLoadFlags; }
int getPlugin() const { return mPlugin; }
private:
std::unique_ptr<const ESM::LandRecordData> mData;
int mLoadFlags = 0;
std::vector<float> mHeightsData;
float mMinHeight = 0.f;
float mMaxHeight = 0.f;
float mSize = 0.f;
int mLandSize = 0;
std::vector<float> mHeights;
std::vector<VNML> mNormals;
std::vector<unsigned char> mColors;
std::vector<uint16_t> mTextures;
int mPlugin = 0;
std::span<const float> mHeights;
std::span<const std::int8_t> mNormals;
std::span<const std::uint8_t> mColors;
std::span<const std::uint16_t> mTextures;
};
}

View file

@ -19,5 +19,5 @@ osg::Vec2 ESM::indexToPosition(const ESM::ExteriorCellLocation& cellIndex, bool
int ESM::getLandSize(ESM::RefId worldspaceId)
{
return isEsm4Ext(worldspaceId) ? ESM4::Land::VERTS_PER_SIDE : ESM::Land::LAND_SIZE;
return isEsm4Ext(worldspaceId) ? ESM4::Land::sVertsPerSide : ESM::Land::LAND_SIZE;
}

View file

@ -0,0 +1,48 @@
#ifndef OPENMW_COMPONENTS_ESM3_LANDRECORDDATA_H
#define OPENMW_COMPONENTS_ESM3_LANDRECORDDATA_H
#include <cstdint>
namespace ESM
{
struct LandRecordData
{
// number of vertices per side
static constexpr unsigned sLandSize = 65;
// total number of vertices
static constexpr unsigned sLandNumVerts = sLandSize * sLandSize;
// number of textures per side of land
static constexpr unsigned sLandTextureSize = 16;
// total number of textures per land
static constexpr unsigned sLandNumTextures = sLandTextureSize * sLandTextureSize;
// Initial reference height for the first vertex, only needed for filling mHeights
float mHeightOffset = 0;
// Height in world space for each vertex
float mHeights[sLandNumVerts];
float mMinHeight = 0;
float mMaxHeight = 0;
// 24-bit normals, these aren't always correct though. Edge and corner normals may be garbage.
std::int8_t mNormals[sLandNumVerts * 3];
// 2D array of texture indices. An index can be used to look up an LandTexture,
// but to do so you must subtract 1 from the index first!
// An index of 0 indicates the default texture.
std::uint16_t mTextures[sLandNumTextures];
// 24-bit RGB color for each vertex
std::uint8_t mColours[3 * sLandNumVerts];
// ???
std::uint16_t mUnk1 = 0;
std::uint8_t mUnk2 = 0;
int mDataLoaded = 0;
};
}
#endif

View file

@ -1,6 +1,7 @@
#include "loadland.hpp"
#include <algorithm>
#include <cstdint>
#include <limits>
#include <utility>
@ -10,28 +11,31 @@
namespace ESM
{
Land::Land()
: mFlags(0)
, mX(0)
, mY(0)
, mDataTypes(0)
, mLandData(nullptr)
namespace
{
}
void transposeTextureData(const std::uint16_t* in, std::uint16_t* out)
{
int readPos = 0; // bit ugly, but it works
for (int y1 = 0; y1 < 4; y1++)
for (int x1 = 0; x1 < 4; x1++)
for (int y2 = 0; y2 < 4; y2++)
for (int x2 = 0; x2 < 4; x2++)
out[(y1 * 4 + y2) * 16 + (x1 * 4 + x2)] = in[readPos++];
}
void transposeTextureData(const uint16_t* in, uint16_t* out)
{
int readPos = 0; // bit ugly, but it works
for (int y1 = 0; y1 < 4; y1++)
for (int x1 = 0; x1 < 4; x1++)
for (int y2 = 0; y2 < 4; y2++)
for (int x2 = 0; x2 < 4; x2++)
out[(y1 * 4 + y2) * 16 + (x1 * 4 + x2)] = in[readPos++];
}
Land::~Land()
{
delete mLandData;
// Loads data and marks it as loaded. Return true if data is actually loaded from reader, false otherwise
// including the case when data is already loaded.
bool condLoad(ESMReader& reader, int flags, int& targetFlags, int dataFlag, void* ptr, unsigned int size)
{
if ((targetFlags & dataFlag) == 0 && (flags & dataFlag) != 0)
{
reader.getHExact(ptr, size);
targetFlags |= dataFlag;
return true;
}
reader.skipHSubSize(size);
return false;
}
}
void Land::load(ESMReader& esm, bool& isDeleted)
@ -49,8 +53,8 @@ namespace ESM
esm.getSubHeader();
if (esm.getSubSize() != 8)
esm.fail("Subrecord size is not equal to 8");
esm.getT<int>(mX);
esm.getT<int>(mY);
esm.getT(mX);
esm.getT(mY);
hasLocation = true;
break;
case fourCC("DATA"):
@ -90,7 +94,7 @@ namespace ESM
mDataTypes |= DATA_VHGT;
break;
case fourCC("WNAM"):
esm.getHExact(mWnam, sizeof(mWnam));
esm.getHExact(mWnam.data(), mWnam.size());
mDataTypes |= DATA_WNAM;
break;
case fourCC("VCLR"):
@ -141,7 +145,8 @@ namespace ESM
for (int i = 0; i < LAND_SIZE; ++i)
{
float diff = (mLandData->mHeights[number] - prevY) / HEIGHT_SCALE;
offsets.mHeightData[number] = (diff >= 0) ? (int8_t)(diff + 0.5) : (int8_t)(diff - 0.5);
offsets.mHeightData[number]
= diff >= 0 ? static_cast<std::int8_t>(diff + 0.5) : static_cast<std::int8_t>(diff - 0.5);
float prevX = prevY = mLandData->mHeights[number];
++number;
@ -149,7 +154,8 @@ namespace ESM
for (int j = 1; j < LAND_SIZE; ++j)
{
diff = (mLandData->mHeights[number] - prevX) / HEIGHT_SCALE;
offsets.mHeightData[number] = (diff >= 0) ? (int8_t)(diff + 0.5) : (int8_t)(diff - 0.5);
offsets.mHeightData[number]
= diff >= 0 ? static_cast<std::int8_t>(diff + 0.5) : static_cast<std::int8_t>(diff - 0.5);
prevX = mLandData->mHeights[number];
++number;
@ -160,18 +166,19 @@ namespace ESM
if (mDataTypes & Land::DATA_WNAM)
{
// Generate WNAM record
signed char wnam[LAND_GLOBAL_MAP_LOD_SIZE];
constexpr float max = std::numeric_limits<signed char>::max();
constexpr float min = std::numeric_limits<signed char>::min();
std::int8_t wnam[LAND_GLOBAL_MAP_LOD_SIZE];
constexpr float max = std::numeric_limits<std::int8_t>::max();
constexpr float min = std::numeric_limits<std::int8_t>::min();
constexpr float vertMult = static_cast<float>(Land::LAND_SIZE - 1) / LAND_GLOBAL_MAP_LOD_SIZE_SQRT;
for (int row = 0; row < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++row)
{
for (int col = 0; col < LAND_GLOBAL_MAP_LOD_SIZE_SQRT; ++col)
{
float height = mLandData->mHeights[int(row * vertMult) * Land::LAND_SIZE + int(col * vertMult)];
float height = mLandData->mHeights[static_cast<int>(row * vertMult) * Land::LAND_SIZE
+ static_cast<int>(col * vertMult)];
height /= height > 0 ? 128.f : 16.f;
height = std::clamp(height, min, max);
wnam[row * LAND_GLOBAL_MAP_LOD_SIZE_SQRT + col] = static_cast<signed char>(height);
wnam[row * LAND_GLOBAL_MAP_LOD_SIZE_SQRT + col] = static_cast<std::int8_t>(height);
}
}
esm.writeHNT("WNAM", wnam);
@ -195,8 +202,8 @@ namespace ESM
std::fill(std::begin(mWnam), std::end(mWnam), 0);
if (!mLandData)
mLandData = new LandData;
if (mLandData == nullptr)
mLandData = std::make_unique<LandData>();
mLandData->mHeightOffset = 0;
std::fill(std::begin(mLandData->mHeights), std::end(mLandData->mHeights), 0);
@ -220,31 +227,29 @@ namespace ESM
mContext.filename.clear();
}
void Land::loadData(int flags, LandData* target) const
void Land::loadData(int flags) const
{
// Create storage if nothing is loaded
if (!target && !mLandData)
{
mLandData = new LandData;
}
if (mLandData == nullptr)
mLandData = std::make_unique<LandData>();
if (!target)
target = mLandData;
loadData(flags, *mLandData);
}
void Land::loadData(int flags, LandData& data) const
{
// Try to load only available data
flags = flags & mDataTypes;
// Return if all required data is loaded
if ((target->mDataLoaded & flags) == flags)
if ((data.mDataLoaded & flags) == flags)
{
return;
}
// Copy data to target if no file
if (mContext.filename.empty())
{
// Make sure there is data, and that it doesn't point to the same object.
if (mLandData && mLandData != target)
*target = *mLandData;
if (mLandData != nullptr && mLandData.get() != &data)
data = *mLandData;
return;
}
@ -254,41 +259,41 @@ namespace ESM
if (reader.isNextSub("VNML"))
{
condLoad(reader, flags, target->mDataLoaded, DATA_VNML, target->mNormals, sizeof(target->mNormals));
condLoad(reader, flags, data.mDataLoaded, DATA_VNML, data.mNormals, sizeof(data.mNormals));
}
if (reader.isNextSub("VHGT"))
{
VHGT vhgt;
if (condLoad(reader, flags, target->mDataLoaded, DATA_VHGT, &vhgt, sizeof(vhgt)))
if (condLoad(reader, flags, data.mDataLoaded, DATA_VHGT, &vhgt, sizeof(vhgt)))
{
target->mMinHeight = std::numeric_limits<float>::max();
target->mMaxHeight = -std::numeric_limits<float>::max();
data.mMinHeight = std::numeric_limits<float>::max();
data.mMaxHeight = -std::numeric_limits<float>::max();
float rowOffset = vhgt.mHeightOffset;
for (int y = 0; y < LAND_SIZE; y++)
{
rowOffset += vhgt.mHeightData[y * LAND_SIZE];
target->mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE;
if (rowOffset * HEIGHT_SCALE > target->mMaxHeight)
target->mMaxHeight = rowOffset * HEIGHT_SCALE;
if (rowOffset * HEIGHT_SCALE < target->mMinHeight)
target->mMinHeight = rowOffset * HEIGHT_SCALE;
data.mHeights[y * LAND_SIZE] = rowOffset * HEIGHT_SCALE;
if (rowOffset * HEIGHT_SCALE > data.mMaxHeight)
data.mMaxHeight = rowOffset * HEIGHT_SCALE;
if (rowOffset * HEIGHT_SCALE < data.mMinHeight)
data.mMinHeight = rowOffset * HEIGHT_SCALE;
float colOffset = rowOffset;
for (int x = 1; x < LAND_SIZE; x++)
{
colOffset += vhgt.mHeightData[y * LAND_SIZE + x];
target->mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE;
data.mHeights[x + y * LAND_SIZE] = colOffset * HEIGHT_SCALE;
if (colOffset * HEIGHT_SCALE > target->mMaxHeight)
target->mMaxHeight = colOffset * HEIGHT_SCALE;
if (colOffset * HEIGHT_SCALE < target->mMinHeight)
target->mMinHeight = colOffset * HEIGHT_SCALE;
if (colOffset * HEIGHT_SCALE > data.mMaxHeight)
data.mMaxHeight = colOffset * HEIGHT_SCALE;
if (colOffset * HEIGHT_SCALE < data.mMinHeight)
data.mMinHeight = colOffset * HEIGHT_SCALE;
}
}
target->mUnk1 = vhgt.mUnk1;
target->mUnk2 = vhgt.mUnk2;
data.mUnk1 = vhgt.mUnk1;
data.mUnk2 = vhgt.mUnk2;
}
}
@ -296,37 +301,20 @@ namespace ESM
reader.skipHSub();
if (reader.isNextSub("VCLR"))
condLoad(reader, flags, target->mDataLoaded, DATA_VCLR, target->mColours, 3 * LAND_NUM_VERTS);
condLoad(reader, flags, data.mDataLoaded, DATA_VCLR, data.mColours, 3 * LAND_NUM_VERTS);
if (reader.isNextSub("VTEX"))
{
uint16_t vtex[LAND_NUM_TEXTURES];
if (condLoad(reader, flags, target->mDataLoaded, DATA_VTEX, vtex, sizeof(vtex)))
if (condLoad(reader, flags, data.mDataLoaded, DATA_VTEX, vtex, sizeof(vtex)))
{
transposeTextureData(vtex, target->mTextures);
transposeTextureData(vtex, data.mTextures);
}
}
}
void Land::unloadData() const
void Land::unloadData()
{
if (mLandData)
{
delete mLandData;
mLandData = nullptr;
}
}
bool Land::condLoad(
ESMReader& reader, int flags, int& targetFlags, int dataFlag, void* ptr, unsigned int size) const
{
if ((targetFlags & dataFlag) == 0 && (flags & dataFlag) != 0)
{
reader.getHExact(ptr, size);
targetFlags |= dataFlag;
return true;
}
reader.skipHSubSize(size);
return false;
mLandData = nullptr;
}
bool Land::isDataLoaded(int flags) const
@ -340,70 +328,33 @@ namespace ESM
, mY(land.mY)
, mContext(land.mContext)
, mDataTypes(land.mDataTypes)
, mLandData(land.mLandData ? new LandData(*land.mLandData) : nullptr)
, mWnam(land.mWnam)
, mLandData(land.mLandData != nullptr ? std::make_unique<LandData>(*land.mLandData) : nullptr)
{
std::copy(land.mWnam, land.mWnam + LAND_GLOBAL_MAP_LOD_SIZE, mWnam);
}
Land& Land::operator=(const Land& land)
{
Land tmp(land);
swap(tmp);
Land copy(land);
*this = std::move(copy);
return *this;
}
void Land::swap(Land& land)
{
std::swap(mFlags, land.mFlags);
std::swap(mX, land.mX);
std::swap(mY, land.mY);
std::swap(mContext, land.mContext);
std::swap(mDataTypes, land.mDataTypes);
std::swap(mLandData, land.mLandData);
std::swap(mWnam, land.mWnam);
}
const Land::LandData* Land::getLandData(int flags) const
{
if (!(flags & mDataTypes))
return nullptr;
loadData(flags);
return mLandData;
}
const Land::LandData* Land::getLandData() const
{
return mLandData;
}
Land::LandData* Land::getLandData()
{
return mLandData;
return mLandData.get();
}
void Land::add(int flags)
{
if (!mLandData)
mLandData = new LandData;
if (mLandData == nullptr)
mLandData = std::make_unique<LandData>();
mDataTypes |= flags;
mLandData->mDataLoaded |= flags;
}
void Land::remove(int flags)
{
mDataTypes &= ~flags;
if (mLandData)
{
mLandData->mDataLoaded &= ~flags;
if (!mLandData->mDataLoaded)
{
delete mLandData;
mLandData = nullptr;
}
}
}
}

View file

@ -1,13 +1,17 @@
#ifndef OPENMW_ESM_LAND_H
#define OPENMW_ESM_LAND_H
#include <stdint.h>
#include <array>
#include <cstdint>
#include <memory>
#include <components/misc/constants.hpp>
#include "components/esm/defs.hpp"
#include "components/esm/esmcommon.hpp"
#include "landrecorddata.hpp"
namespace ESM
{
@ -25,12 +29,21 @@ namespace ESM
/// Return a string descriptor for this record type. Currently used for debugging / error logs only.
static std::string_view getRecordType() { return "Land"; }
Land();
~Land();
Land() = default;
int mFlags; // Only first four bits seem to be used, don't know what
// they mean.
int mX, mY; // Map coordinates.
Land(const Land& land);
Land(Land&& other) = default;
Land& operator=(const Land& land);
Land& operator=(Land&& land) = default;
// Only first four bits seem to be used, don't know what they mean.
std::uint32_t mFlags = 0;
// Map coordinates.
std::int32_t mX = 0;
std::int32_t mY = 0;
// Plugin index, used to reference the correct material palette.
int getPlugin() const { return mContext.index; }
@ -42,7 +55,7 @@ namespace ESM
// in which case the filename will be empty.
ESM_Context mContext;
int mDataTypes;
int mDataTypes = 0;
enum
{
@ -57,21 +70,21 @@ namespace ESM
static constexpr int DEFAULT_HEIGHT = -2048;
// number of vertices per side
static constexpr int LAND_SIZE = 65;
static constexpr int LAND_SIZE = LandRecordData::sLandSize;
// cell terrain size in world coords
static constexpr int REAL_SIZE = Constants::CellSizeInUnits;
// total number of vertices
static constexpr int LAND_NUM_VERTS = LAND_SIZE * LAND_SIZE;
static constexpr int LAND_NUM_VERTS = LandRecordData::sLandNumVerts;
static constexpr int HEIGHT_SCALE = 8;
// number of textures per side of land
static constexpr int LAND_TEXTURE_SIZE = 16;
static constexpr int LAND_TEXTURE_SIZE = LandRecordData::sLandTextureSize;
// total number of textures per land
static constexpr int LAND_NUM_TEXTURES = LAND_TEXTURE_SIZE * LAND_TEXTURE_SIZE;
static constexpr int LAND_NUM_TEXTURES = LandRecordData::sLandNumTextures;
static constexpr int LAND_GLOBAL_MAP_LOD_SIZE = 81;
@ -81,108 +94,58 @@ namespace ESM
struct VHGT
{
float mHeightOffset;
int8_t mHeightData[LAND_NUM_VERTS];
short mUnk1;
char mUnk2;
std::int8_t mHeightData[LAND_NUM_VERTS];
std::uint16_t mUnk1;
std::uint8_t mUnk2;
};
#pragma pack(pop)
struct LandData
{
typedef signed char VNML;
LandData()
: mHeightOffset(0)
, mMinHeight(0)
, mMaxHeight(0)
, mUnk1(0)
, mUnk2(0)
, mDataLoaded(0)
{
}
// Initial reference height for the first vertex, only needed for filling mHeights
float mHeightOffset;
// Height in world space for each vertex
float mHeights[LAND_NUM_VERTS];
float mMinHeight;
float mMaxHeight;
// 24-bit normals, these aren't always correct though. Edge and corner normals may be garbage.
VNML mNormals[LAND_NUM_VERTS * 3];
// 2D array of texture indices. An index can be used to look up an LandTexture,
// but to do so you must subtract 1 from the index first!
// An index of 0 indicates the default texture.
uint16_t mTextures[LAND_NUM_TEXTURES];
// 24-bit RGB color for each vertex
unsigned char mColours[3 * LAND_NUM_VERTS];
// ???
short mUnk1;
uint8_t mUnk2;
int mDataLoaded;
};
using LandData = ESM::LandRecordData;
// low-LOD heightmap (used for rendering the global map)
signed char mWnam[LAND_GLOBAL_MAP_LOD_SIZE];
std::array<std::int8_t, LAND_GLOBAL_MAP_LOD_SIZE> mWnam;
void load(ESMReader& esm, bool& isDeleted);
void save(ESMWriter& esm, bool isDeleted = false) const;
void blank();
/**
* Actually loads data into target
* If target is nullptr, assumed target is mLandData
*/
void loadData(int flags, LandData* target = nullptr) const;
void loadData(int flags) const;
void loadData(int flags, LandData& data) const;
/**
* Frees memory allocated for mLandData
*/
void unloadData() const;
void unloadData();
/// Check if given data type is loaded
bool isDataLoaded(int flags) const;
/// Sets the flags and creates a LandData if needed
void setDataLoaded(int flags);
Land(const Land& land);
Land& operator=(const Land& land);
void swap(Land& land);
/// Return land data with at least the data types specified in \a flags loaded (if they
/// are available). Will return a 0-pointer if there is no data for any of the
/// specified types.
const LandData* getLandData(int flags) const;
/// Return land data without loading first anything. Can return a 0-pointer.
const LandData* getLandData() const;
const LandData* getLandData() const
{
return mLandData.get();
}
/// Return land data without loading first anything. Can return a 0-pointer.
LandData* getLandData();
LandData* getLandData()
{
return mLandData.get();
}
/// \attention Must not be called on objects that aren't fully loaded.
///
/// \note Added data fields will be uninitialised
void add(int flags);
/// \attention Must not be called on objects that aren't fully loaded.
void remove(int flags);
private:
/// Loads data and marks it as loaded
/// \return true if data is actually loaded from file, false otherwise
/// including the case when data is already loaded
bool condLoad(ESMReader& reader, int flags, int& targetFlags, int dataFlag, void* ptr, unsigned int size) const;
mutable LandData* mLandData;
mutable std::unique_ptr<LandData> mLandData;
};
}

View file

@ -117,7 +117,7 @@ void ESM4::Land::load(ESM4::Reader& reader)
if (currentAddQuad != -1)
{
// FIXME: sometimes there are no VTXT following an ATXT? Just add a dummy one for now
Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << (int)layer.texture.layerIndex;
Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << layer.texture.layerIndex;
mTextures[currentAddQuad].layers.push_back(layer);
}
reader.get(layer.texture);
@ -149,7 +149,7 @@ void ESM4::Land::load(ESM4::Reader& reader)
if (currentAddQuad == -1)
throw std::runtime_error("VTXT without ATXT found");
int count = (int)reader.subRecordHeader().dataSize / sizeof(ESM4::Land::VTXT);
const std::uint16_t count = reader.subRecordHeader().dataSize / sizeof(ESM4::Land::VTXT);
if ((reader.subRecordHeader().dataSize % sizeof(ESM4::Land::VTXT)) != 0)
throw std::runtime_error("ESM4::LAND VTXT data size error");
@ -179,7 +179,7 @@ void ESM4::Land::load(ESM4::Reader& reader)
}
case ESM4::SUB_VTEX: // only in Oblivion?
{
int count = (int)reader.subRecordHeader().dataSize / sizeof(ESM::FormId32);
const std::uint16_t count = reader.subRecordHeader().dataSize / sizeof(ESM::FormId32);
if ((reader.subRecordHeader().dataSize % sizeof(ESM::FormId32)) != 0)
throw std::runtime_error("ESM4::LAND VTEX data size error");
@ -202,8 +202,8 @@ void ESM4::Land::load(ESM4::Reader& reader)
if (currentAddQuad != -1)
{
// FIXME: not sure if it happens here as well
Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << (int)layer.texture.layerIndex << " quad "
<< (int)layer.texture.quadrant;
Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << layer.texture.layerIndex << " quad "
<< static_cast<unsigned>(layer.texture.quadrant);
mTextures[currentAddQuad].layers.push_back(layer);
}

View file

@ -49,27 +49,27 @@ namespace ESM4
};
// number of vertices per side
static const int VERTS_PER_SIDE = 33;
static constexpr unsigned sVertsPerSide = 33;
// cell terrain size in world coords
static const int REAL_SIZE = 4096;
static constexpr unsigned sRealSize = 4096;
// total number of vertices
static const int LAND_NUM_VERTS = VERTS_PER_SIDE * VERTS_PER_SIDE;
static constexpr unsigned sLandNumVerts = sVertsPerSide * sVertsPerSide;
static const int HEIGHT_SCALE = 8;
static constexpr unsigned sHeightScale = 8;
// number of textures per side of a land quadrant
// (for TES4 - based on vanilla observations)
static const int QUAD_TEXTURE_PER_SIDE = 6;
static constexpr unsigned sQuadTexturePerSide = 6;
#pragma pack(push, 1)
struct VHGT
{
float heightOffset;
std::int8_t gradientData[VERTS_PER_SIDE * VERTS_PER_SIDE];
std::int8_t gradientData[sVertsPerSide * sVertsPerSide];
std::uint16_t unknown1;
unsigned char unknown2;
std::uint8_t unknown2;
};
struct BTXT
@ -117,19 +117,20 @@ namespace ESM4
// FIXME: lazy loading not yet implemented
int mDataTypes; // which data types are loaded
float mHeights[VERTS_PER_SIDE * VERTS_PER_SIDE]; // Float value of compressed Heightmap
float mMinHeight, mMaxHeight;
signed char mVertNorm[VERTS_PER_SIDE * VERTS_PER_SIDE * 3]; // from VNML subrecord
unsigned char mVertColr[VERTS_PER_SIDE * VERTS_PER_SIDE * 3]; // from VCLR subrecord
float mHeights[sVertsPerSide * sVertsPerSide]; // Float value of compressed Heightmap
std::int8_t mVertNorm[sVertsPerSide * sVertsPerSide * 3]; // from VNML subrecord
std::uint8_t mVertColr[sVertsPerSide * sVertsPerSide * 3]; // from VCLR subrecord
VHGT mHeightMap;
Texture mTextures[4]; // 0 = bottom left, 1 = bottom right, 2 = top left, 3 = top right
std::vector<ESM::FormId> mIds; // land texture (LTEX) formids
ESM::FormId mCell;
void load(Reader& reader);
Land() = default;
// void save(Writer& writer) const;
// void blank();
static constexpr ESM::RecNameInts sRecordId = ESM::REC_LAND4;
};
}

View file

@ -8,6 +8,7 @@
#include <components/debug/debuglog.hpp>
#include <components/esm/esmterrain.hpp>
#include <components/esm3/loadland.hpp>
#include <components/esm4/loadland.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/misc/strings/algorithm.hpp>
@ -46,30 +47,21 @@ namespace ESMTerrain
Map mMap;
};
LandObject::LandObject()
: mLand(nullptr)
LandObject::LandObject(const ESM4::Land& land, int loadFlags)
: mData(land, loadFlags)
{
}
LandObject::LandObject(const ESM4::Land* land, int loadFlags)
: mLand(nullptr)
, mData(*land, loadFlags)
LandObject::LandObject(const ESM::Land& land, int loadFlags)
: mData(land, loadFlags)
{
}
LandObject::LandObject(const ESM::Land* land, int loadFlags)
: mLand(land)
, mData(*land, loadFlags)
LandObject::LandObject(const LandObject& /*copy*/, const osg::CopyOp& /*copyOp*/)
{
throw std::logic_error("LandObject copy constructor is not implemented");
}
LandObject::LandObject(const LandObject& copy, const osg::CopyOp& copyop)
: mLand(nullptr)
{
}
LandObject::~LandObject() {}
const float defaultHeight = ESM::Land::DEFAULT_HEIGHT;
Storage::Storage(const VFS::Manager* vfs, const std::string& normalMapPattern,
@ -232,20 +224,29 @@ namespace ESMTerrain
const osg::Vec2f origin = center - osg::Vec2f(size, size) * 0.5f;
const int startCellX = static_cast<int>(std::floor(origin.x()));
const int startCellY = static_cast<int>(std::floor(origin.y()));
ESM::ExteriorCellLocation lastCellLocation(startCellX - 1, startCellY - 1, worldspace);
const LandObject* land = nullptr;
std::pair lastCell{ startCellX, startCellY };
const LandObject* land = getLand(ESM::ExteriorCellLocation(startCellX, startCellY, worldspace), cache);
const ESM::LandData* heightData = nullptr;
const ESM::LandData* normalData = nullptr;
const ESM::LandData* colourData = nullptr;
bool validHeightDataExists = false;
if (land != nullptr)
{
heightData = land->getData(ESM::Land::DATA_VHGT);
normalData = land->getData(ESM::Land::DATA_VNML);
colourData = land->getData(ESM::Land::DATA_VCLR);
validHeightDataExists = true;
}
const auto handleSample = [&](std::size_t cellShiftX, std::size_t cellShiftY, std::size_t row, std::size_t col,
std::size_t vertX, std::size_t vertY) {
const int cellX = startCellX + cellShiftX;
const int cellY = startCellY + cellShiftY;
const std::pair cell{ cellX, cellY };
const ESM::ExteriorCellLocation cellLocation(cellX, cellY, worldspace);
if (lastCellLocation != cellLocation)
if (lastCell != cell)
{
land = getLand(cellLocation, cache);
@ -261,7 +262,7 @@ namespace ESMTerrain
validHeightDataExists = true;
}
lastCellLocation = cellLocation;
lastCell = cell;
}
float height = defaultHeight;

View file

@ -8,7 +8,6 @@
#include <components/esm/esmterrain.hpp>
#include <components/esm/util.hpp>
#include <components/esm3/loadland.hpp>
#include <components/esm3/loadltex.hpp>
namespace ESM4
@ -36,30 +35,26 @@ namespace ESMTerrain
class LandObject : public osg::Object
{
public:
LandObject();
LandObject(const ESM::Land* land, int loadFlags);
LandObject(const ESM4::Land* land, int loadFlags);
LandObject(const LandObject& copy, const osg::CopyOp& copyop);
virtual ~LandObject();
LandObject() = default;
LandObject(const ESM::Land& land, int loadFlags);
LandObject(const ESM4::Land& land, int loadFlags);
META_Object(ESMTerrain, LandObject)
inline const ESM::LandData* getData(int flags) const
const ESM::LandData* getData(int flags) const
{
if ((mData.mLoadFlags & flags) != flags)
if ((mData.getLoadFlags() & flags) != flags)
return nullptr;
return &mData;
}
inline int getPlugin() const { return mLand->getPlugin(); }
inline int getLandSize() const { return mData.getLandSize(); }
inline int getRealSize() const { return mData.getSize(); }
int getPlugin() const { return mData.getPlugin(); }
private:
const ESM::Land* mLand;
ESM::LandData mData;
LandObject(const LandObject& copy, const osg::CopyOp& copyOp);
};
// Since plugins can define new texture palettes, we need to know the plugin index too