1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-10-18 11:16:37 +00:00

Merge branch 'esm4-terrain' into 'master'

Initial support for ESM4 terrain

See merge request OpenMW/openmw!3032
This commit is contained in:
psi29a 2023-05-29 12:08:49 +00:00
commit 27babefee6
46 changed files with 769 additions and 380 deletions

View file

@ -146,8 +146,8 @@ void CSVRender::Cell::updateLand()
} }
else else
{ {
mTerrain = std::make_unique<Terrain::TerrainGrid>( mTerrain = std::make_unique<Terrain::TerrainGrid>(mCellNode, mCellNode, mData.getResourceSystem().get(),
mCellNode, mCellNode, mData.getResourceSystem().get(), mTerrainStorage, Mask_Terrain); mTerrainStorage, Mask_Terrain, ESM::Cell::sDefaultWorldspaceId);
} }
mTerrain->loadCell(esmLand.mX, esmLand.mY); mTerrain->loadCell(esmLand.mX, esmLand.mY);

View file

@ -28,12 +28,12 @@ namespace CSVRender
resetHeights(); resetHeights();
} }
osg::ref_ptr<const ESMTerrain::LandObject> TerrainStorage::getLand(int cellX, int cellY) osg::ref_ptr<const ESMTerrain::LandObject> TerrainStorage::getLand(ESM::ExteriorCellLocation cellLocation)
{ {
// The cell isn't guaranteed to have Land. This is because the terrain implementation // The cell isn't guaranteed to have Land. This is because the terrain implementation
// has to wrap the vertices of the last row and column to the next cell, which may be a nonexisting cell // has to wrap the vertices of the last row and column to the next cell, which may be a nonexisting cell
const int index const int index = mData.getLand().searchId(
= mData.getLand().searchId(ESM::RefId::stringRefId(CSMWorld::Land::createUniqueRecordId(cellX, cellY))); ESM::RefId::stringRefId(CSMWorld::Land::createUniqueRecordId(cellLocation.mX, cellLocation.mY)));
if (index == -1) if (index == -1)
return nullptr; return nullptr;
@ -82,83 +82,83 @@ namespace CSVRender
return &mAlteredHeight[inCellY * ESM::Land::LAND_SIZE + inCellX]; return &mAlteredHeight[inCellY * ESM::Land::LAND_SIZE + inCellX];
} }
void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY) void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY, ESM::RefId worldspace)
{ {
// not needed at the moment - this returns the bounds of the whole world, but we only edit individual cells // not needed at the moment - this returns the bounds of the whole world, but we only edit individual cells
throw std::runtime_error("getBounds not implemented"); throw std::runtime_error("getBounds not implemented");
} }
int TerrainStorage::getThisHeight(int col, int row, const ESM::Land::LandData* heightData) const int TerrainStorage::getThisHeight(int col, int row, std::span<const float> heightData) const
{ {
return heightData->mHeights[col * ESM::Land::LAND_SIZE + row] return heightData[col * ESM::Land::LAND_SIZE + row]
+ mAlteredHeight[static_cast<unsigned int>(col * ESM::Land::LAND_SIZE + row)]; + mAlteredHeight[static_cast<unsigned int>(col * ESM::Land::LAND_SIZE + row)];
} }
int TerrainStorage::getLeftHeight(int col, int row, const ESM::Land::LandData* heightData) const int TerrainStorage::getLeftHeight(int col, int row, std::span<const float> heightData) const
{ {
return heightData->mHeights[(col)*ESM::Land::LAND_SIZE + row - 1] return heightData[(col)*ESM::Land::LAND_SIZE + row - 1]
+ mAlteredHeight[static_cast<unsigned int>((col)*ESM::Land::LAND_SIZE + row - 1)]; + mAlteredHeight[static_cast<unsigned int>((col)*ESM::Land::LAND_SIZE + row - 1)];
} }
int TerrainStorage::getRightHeight(int col, int row, const ESM::Land::LandData* heightData) const int TerrainStorage::getRightHeight(int col, int row, std::span<const float> heightData) const
{ {
return heightData->mHeights[col * ESM::Land::LAND_SIZE + row + 1] return heightData[col * ESM::Land::LAND_SIZE + row + 1]
+ mAlteredHeight[static_cast<unsigned int>(col * ESM::Land::LAND_SIZE + row + 1)]; + mAlteredHeight[static_cast<unsigned int>(col * ESM::Land::LAND_SIZE + row + 1)];
} }
int TerrainStorage::getUpHeight(int col, int row, const ESM::Land::LandData* heightData) const int TerrainStorage::getUpHeight(int col, int row, std::span<const float> heightData) const
{ {
return heightData->mHeights[(col - 1) * ESM::Land::LAND_SIZE + row] return heightData[(col - 1) * ESM::Land::LAND_SIZE + row]
+ mAlteredHeight[static_cast<unsigned int>((col - 1) * ESM::Land::LAND_SIZE + row)]; + mAlteredHeight[static_cast<unsigned int>((col - 1) * ESM::Land::LAND_SIZE + row)];
} }
int TerrainStorage::getDownHeight(int col, int row, const ESM::Land::LandData* heightData) const int TerrainStorage::getDownHeight(int col, int row, std::span<const float> heightData) const
{ {
return heightData->mHeights[(col + 1) * ESM::Land::LAND_SIZE + row] return heightData[(col + 1) * ESM::Land::LAND_SIZE + row]
+ mAlteredHeight[static_cast<unsigned int>((col + 1) * ESM::Land::LAND_SIZE + row)]; + mAlteredHeight[static_cast<unsigned int>((col + 1) * ESM::Land::LAND_SIZE + row)];
} }
int TerrainStorage::getHeightDifferenceToLeft(int col, int row, const ESM::Land::LandData* heightData) const int TerrainStorage::getHeightDifferenceToLeft(int col, int row, std::span<const float> heightData) const
{ {
return abs(getThisHeight(col, row, heightData) - getLeftHeight(col, row, heightData)); return abs(getThisHeight(col, row, heightData) - getLeftHeight(col, row, heightData));
} }
int TerrainStorage::getHeightDifferenceToRight(int col, int row, const ESM::Land::LandData* heightData) const int TerrainStorage::getHeightDifferenceToRight(int col, int row, std::span<const float> heightData) const
{ {
return abs(getThisHeight(col, row, heightData) - getRightHeight(col, row, heightData)); return abs(getThisHeight(col, row, heightData) - getRightHeight(col, row, heightData));
} }
int TerrainStorage::getHeightDifferenceToUp(int col, int row, const ESM::Land::LandData* heightData) const int TerrainStorage::getHeightDifferenceToUp(int col, int row, std::span<const float> heightData) const
{ {
return abs(getThisHeight(col, row, heightData) - getUpHeight(col, row, heightData)); return abs(getThisHeight(col, row, heightData) - getUpHeight(col, row, heightData));
} }
int TerrainStorage::getHeightDifferenceToDown(int col, int row, const ESM::Land::LandData* heightData) const int TerrainStorage::getHeightDifferenceToDown(int col, int row, std::span<const float> heightData) const
{ {
return abs(getThisHeight(col, row, heightData) - getDownHeight(col, row, heightData)); return abs(getThisHeight(col, row, heightData) - getDownHeight(col, row, heightData));
} }
bool TerrainStorage::leftOrUpIsOverTheLimit( bool TerrainStorage::leftOrUpIsOverTheLimit(
int col, int row, int heightWarningLimit, const ESM::Land::LandData* heightData) const int col, int row, int heightWarningLimit, std::span<const float> heightData) const
{ {
return getHeightDifferenceToLeft(col, row, heightData) >= heightWarningLimit return getHeightDifferenceToLeft(col, row, heightData) >= heightWarningLimit
|| getHeightDifferenceToUp(col, row, heightData) >= heightWarningLimit; || getHeightDifferenceToUp(col, row, heightData) >= heightWarningLimit;
} }
bool TerrainStorage::rightOrDownIsOverTheLimit( bool TerrainStorage::rightOrDownIsOverTheLimit(
int col, int row, int heightWarningLimit, const ESM::Land::LandData* heightData) const int col, int row, int heightWarningLimit, std::span<const float> heightData) const
{ {
return getHeightDifferenceToRight(col, row, heightData) >= heightWarningLimit return getHeightDifferenceToRight(col, row, heightData) >= heightWarningLimit
|| getHeightDifferenceToDown(col, row, heightData) >= heightWarningLimit; || getHeightDifferenceToDown(col, row, heightData) >= heightWarningLimit;
} }
void TerrainStorage::adjustColor(int col, int row, const ESM::Land::LandData* heightData, osg::Vec4ub& color) const void TerrainStorage::adjustColor(int col, int row, const ESM::LandData* heightData, osg::Vec4ub& color) const
{ {
// Highlight broken height changes // Highlight broken height changes
int heightWarningLimit = 1024; int heightWarningLimit = 1024;
if (((col > 0 && row > 0) && leftOrUpIsOverTheLimit(col, row, heightWarningLimit, heightData)) if (((col > 0 && row > 0) && leftOrUpIsOverTheLimit(col, row, heightWarningLimit, heightData->getHeights()))
|| ((col < ESM::Land::LAND_SIZE - 1 && row < ESM::Land::LAND_SIZE - 1) || ((col < ESM::Land::LAND_SIZE - 1 && row < ESM::Land::LAND_SIZE - 1)
&& rightOrDownIsOverTheLimit(col, row, heightWarningLimit, heightData))) && rightOrDownIsOverTheLimit(col, row, heightWarningLimit, heightData->getHeights())))
{ {
color.r() = 255; color.r() = 255;
color.g() = 0; color.g() = 0;

View file

@ -37,26 +37,25 @@ namespace CSVRender
const CSMWorld::Data& mData; const CSMWorld::Data& mData;
std::array<float, ESM::Land::LAND_SIZE * ESM::Land::LAND_SIZE> mAlteredHeight; std::array<float, ESM::Land::LAND_SIZE * ESM::Land::LAND_SIZE> mAlteredHeight;
osg::ref_ptr<const ESMTerrain::LandObject> getLand(int cellX, int cellY) override; osg::ref_ptr<const ESMTerrain::LandObject> getLand(ESM::ExteriorCellLocation cellLocation) override;
const ESM::LandTexture* getLandTexture(int index, short plugin) override; const ESM::LandTexture* getLandTexture(int index, short plugin) override;
void getBounds(float& minX, float& maxX, float& minY, float& maxY) override; void getBounds(float& minX, float& maxX, float& minY, float& maxY, ESM::RefId worldspace) override;
int getThisHeight(int col, int row, const ESM::Land::LandData* heightData) const; int getThisHeight(int col, int row, std::span<const float> heightData) const;
int getLeftHeight(int col, int row, const ESM::Land::LandData* heightData) const; int getLeftHeight(int col, int row, std::span<const float> heightData) const;
int getRightHeight(int col, int row, const ESM::Land::LandData* heightData) const; int getRightHeight(int col, int row, std::span<const float> heightData) const;
int getUpHeight(int col, int row, const ESM::Land::LandData* heightData) const; int getUpHeight(int col, int row, std::span<const float> heightData) const;
int getDownHeight(int col, int row, const ESM::Land::LandData* heightData) const; int getDownHeight(int col, int row, std::span<const float> heightData) const;
int getHeightDifferenceToLeft(int col, int row, const ESM::Land::LandData* heightData) const; int getHeightDifferenceToLeft(int col, int row, std::span<const float> heightData) const;
int getHeightDifferenceToRight(int col, int row, const ESM::Land::LandData* heightData) const; int getHeightDifferenceToRight(int col, int row, std::span<const float> heightData) const;
int getHeightDifferenceToUp(int col, int row, const ESM::Land::LandData* heightData) const; int getHeightDifferenceToUp(int col, int row, std::span<const float> heightData) const;
int getHeightDifferenceToDown(int col, int row, const ESM::Land::LandData* heightData) const; int getHeightDifferenceToDown(int col, int row, std::span<const float> heightData) const;
bool leftOrUpIsOverTheLimit( bool leftOrUpIsOverTheLimit(int col, int row, int heightWarningLimit, std::span<const float> heightData) const;
int col, int row, int heightWarningLimit, const ESM::Land::LandData* heightData) const;
bool rightOrDownIsOverTheLimit( bool rightOrDownIsOverTheLimit(
int col, int row, int heightWarningLimit, const ESM::Land::LandData* heightData) const; int col, int row, int heightWarningLimit, std::span<const float> heightData) const;
void adjustColor(int col, int row, const ESM::Land::LandData* heightData, osg::Vec4ub& color) const override; void adjustColor(int col, int row, const ESM::LandData* heightData, osg::Vec4ub& color) const override;
float getAlteredHeight(int col, int row) const override; float getAlteredHeight(int col, int row) const override;
}; };

View file

@ -572,7 +572,7 @@ namespace MWBase
virtual void rotateWorldObject(const MWWorld::Ptr& ptr, const osg::Quat& rotate) = 0; virtual void rotateWorldObject(const MWWorld::Ptr& ptr, const osg::Quat& rotate) = 0;
/// Return terrain height at \a worldPos position. /// Return terrain height at \a worldPos position.
virtual float getTerrainHeightAt(const osg::Vec3f& worldPos) const = 0; virtual float getTerrainHeightAt(const osg::Vec3f& worldPos, ESM::RefId worldspace) const = 0;
/// Return physical or rendering half extents of the given actor. /// Return physical or rendering half extents of the given actor.
virtual osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor, bool rendering = false) const = 0; virtual osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor, bool rendering = false) const = 0;

View file

@ -474,7 +474,8 @@ namespace MWMechanics
if (enemy.getCell()->isExterior()) if (enemy.getCell()->isExterior())
{ {
if (attackDistance < (enemyPos.pos[2] if (attackDistance < (enemyPos.pos[2]
- MWBase::Environment::get().getWorld()->getTerrainHeightAt(enemyPos.asVec3()))) - MWBase::Environment::get().getWorld()->getTerrainHeightAt(
enemyPos.asVec3(), enemy.getCell()->getCell()->getWorldSpace())))
return false; return false;
} }
} }

View file

@ -12,7 +12,7 @@ namespace MWRender
{ {
LandManager::LandManager(int loadFlags) LandManager::LandManager(int loadFlags)
: GenericResourceManager<std::pair<int, int>>(nullptr) : GenericResourceManager<ESM::ExteriorCellLocation>(nullptr)
, mLoadFlags(loadFlags) , mLoadFlags(loadFlags)
{ {
mCache = new CacheType; mCache = new CacheType;
@ -20,11 +20,7 @@ namespace MWRender
osg::ref_ptr<ESMTerrain::LandObject> LandManager::getLand(ESM::ExteriorCellLocation cellIndex) osg::ref_ptr<ESMTerrain::LandObject> LandManager::getLand(ESM::ExteriorCellLocation cellIndex)
{ {
if (ESM::isEsm4Ext(cellIndex.mWorldspace)) osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(cellIndex);
return osg::ref_ptr<ESMTerrain::LandObject>(nullptr);
int x = cellIndex.mX;
int y = cellIndex.mY;
osg::ref_ptr<osg::Object> obj = mCache->getRefFromObjectCache(std::make_pair(x, y));
if (obj) if (obj)
return static_cast<ESMTerrain::LandObject*>(obj.get()); return static_cast<ESMTerrain::LandObject*>(obj.get());
else else
@ -32,12 +28,25 @@ namespace MWRender
const auto world = MWBase::Environment::get().getWorld(); const auto world = MWBase::Environment::get().getWorld();
if (!world) if (!world)
return nullptr; return nullptr;
const ESM::Land* land = world->getStore().get<ESM::Land>().search(x, y);
if (!land) if (ESM::isEsm4Ext(cellIndex.mWorldspace))
return nullptr; {
osg::ref_ptr<ESMTerrain::LandObject> landObj(new ESMTerrain::LandObject(land, mLoadFlags)); const ESM4::Land* land = world->getStore().get<ESM4::Land>().search(cellIndex);
mCache->addEntryToObjectCache(std::make_pair(x, y), landObj.get()); if (!land)
return landObj; 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;
}
} }
} }

View file

@ -15,7 +15,7 @@ namespace ESM
namespace MWRender namespace MWRender
{ {
class LandManager : public Resource::GenericResourceManager<std::pair<int, int>> class LandManager : public Resource::GenericResourceManager<ESM::ExteriorCellLocation>
{ {
public: public:
LandManager(int loadFlags); LandManager(int loadFlags);

View file

@ -321,6 +321,7 @@ namespace MWRender
, mFieldOfViewOverride(0.f) , mFieldOfViewOverride(0.f)
, mFieldOfView(Settings::camera().mFieldOfView) , mFieldOfView(Settings::camera().mFieldOfView)
, mFirstPersonFieldOfView(Settings::camera().mFirstPersonFieldOfView) , mFirstPersonFieldOfView(Settings::camera().mFirstPersonFieldOfView)
, mGroundCoverStore(groundcoverStore)
{ {
bool reverseZ = SceneUtil::AutoDepth::isReversed(); bool reverseZ = SceneUtil::AutoDepth::isReversed();
auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString( auto lightingMethod = SceneUtil::LightManager::getLightingMethodFromString(
@ -457,46 +458,11 @@ namespace MWRender
mTerrainStorage = std::make_unique<TerrainStorage>(mResourceSystem, normalMapPattern, heightMapPattern, mTerrainStorage = std::make_unique<TerrainStorage>(mResourceSystem, normalMapPattern, heightMapPattern,
useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps); useTerrainNormalMaps, specularMapPattern, useTerrainSpecularMaps);
const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain");
bool groundcover = Settings::Manager::getBool("enabled", "Groundcover"); WorldspaceChunkMgr& chunkMgr = getWorldspaceChunkMgr(ESM::Cell::sDefaultWorldspaceId);
bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain"); mTerrain = chunkMgr.mTerrain.get();
if (distantTerrain || groundcover) mGroundcover = chunkMgr.mGroundcover.get();
{ mObjectPaging = chunkMgr.mObjectPaging.get();
const int compMapResolution = Settings::Manager::getInt("composite map resolution", "Terrain");
int compMapPower = Settings::Manager::getInt("composite map level", "Terrain");
compMapPower = std::max(-3, compMapPower);
float compMapLevel = pow(2, compMapPower);
const int vertexLodMod = Settings::Manager::getInt("vertex lod mod", "Terrain");
float maxCompGeometrySize = Settings::Manager::getFloat("max composite geometry size", "Terrain");
maxCompGeometrySize = std::max(maxCompGeometrySize, 1.f);
bool debugChunks = Settings::Manager::getBool("debug chunks", "Terrain");
mTerrain = std::make_unique<Terrain::QuadTreeWorld>(sceneRoot, mRootNode, mResourceSystem,
mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel,
lodFactor, vertexLodMod, maxCompGeometrySize, debugChunks);
if (Settings::Manager::getBool("object paging", "Terrain"))
{
mObjectPaging = std::make_unique<ObjectPaging>(mResourceSystem->getSceneManager());
static_cast<Terrain::QuadTreeWorld*>(mTerrain.get())->addChunkManager(mObjectPaging.get());
mResourceSystem->addResourceManager(mObjectPaging.get());
}
}
else
mTerrain = std::make_unique<Terrain::TerrainGrid>(sceneRoot, mRootNode, mResourceSystem,
mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug);
mTerrain->setTargetFrameRate(Settings::cells().mTargetFramerate);
if (groundcover)
{
float density = Settings::Manager::getFloat("density", "Groundcover");
density = std::clamp(density, 0.f, 1.f);
mGroundcover = std::make_unique<Groundcover>(
mResourceSystem->getSceneManager(), density, groundcoverDistance, groundcoverStore);
static_cast<Terrain::QuadTreeWorld*>(mTerrain.get())->addChunkManager(mGroundcover.get());
mResourceSystem->addResourceManager(mGroundcover.get());
}
mStateUpdater = new StateUpdater; mStateUpdater = new StateUpdater;
sceneRoot->addUpdateCallback(mStateUpdater); sceneRoot->addUpdateCallback(mStateUpdater);
@ -633,7 +599,7 @@ namespace MWRender
Terrain::World* RenderingManager::getTerrain() Terrain::World* RenderingManager::getTerrain()
{ {
return mTerrain.get(); return mTerrain;
} }
void RenderingManager::preloadCommonAssets() void RenderingManager::preloadCommonAssets()
@ -771,6 +737,7 @@ namespace MWRender
if (store->getCell()->isExterior()) if (store->getCell()->isExterior())
{ {
enableTerrain(true, store->getCell()->getWorldSpace());
mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); mTerrain->loadCell(store->getCell()->getGridX(), store->getCell()->getGridY());
} }
} }
@ -782,16 +749,28 @@ namespace MWRender
if (store->getCell()->isExterior()) if (store->getCell()->isExterior())
{ {
mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY()); getWorldspaceChunkMgr(store->getCell()->getWorldSpace())
.mTerrain->unloadCell(store->getCell()->getGridX(), store->getCell()->getGridY());
} }
mWater->removeCell(store); mWater->removeCell(store);
} }
void RenderingManager::enableTerrain(bool enable) void RenderingManager::enableTerrain(bool enable, ESM::RefId worldspace)
{ {
if (!enable) if (!enable)
mWater->setCullCallback(nullptr); mWater->setCullCallback(nullptr);
else
{
WorldspaceChunkMgr& newChunks = getWorldspaceChunkMgr(worldspace);
if (newChunks.mTerrain.get() != mTerrain)
{
mTerrain->enable(false);
mTerrain = newChunks.mTerrain.get();
mGroundcover = newChunks.mGroundcover.get();
mObjectPaging = newChunks.mObjectPaging.get();
}
}
mTerrain->enable(enable); mTerrain->enable(enable);
} }
@ -1343,6 +1322,60 @@ namespace MWRender
mStateUpdater->setFogColor(color); mStateUpdater->setFogColor(color);
} }
RenderingManager::WorldspaceChunkMgr& RenderingManager::getWorldspaceChunkMgr(ESM::RefId worldspace)
{
auto existingChunkMgr = mWorldspaceChunks.find(worldspace);
if (existingChunkMgr != mWorldspaceChunks.end())
return existingChunkMgr->second;
RenderingManager::WorldspaceChunkMgr newChunkMgr;
const float lodFactor = Settings::Manager::getFloat("lod factor", "Terrain");
bool groundcover = Settings::Manager::getBool("enabled", "Groundcover");
bool distantTerrain = Settings::Manager::getBool("distant terrain", "Terrain");
if (distantTerrain || groundcover)
{
const int compMapResolution = Settings::Manager::getInt("composite map resolution", "Terrain");
int compMapPower = Settings::Manager::getInt("composite map level", "Terrain");
compMapPower = std::max(-3, compMapPower);
float compMapLevel = pow(2, compMapPower);
const int vertexLodMod = Settings::Manager::getInt("vertex lod mod", "Terrain");
float maxCompGeometrySize = Settings::Manager::getFloat("max composite geometry size", "Terrain");
maxCompGeometrySize = std::max(maxCompGeometrySize, 1.f);
bool debugChunks = Settings::Manager::getBool("debug chunks", "Terrain");
auto quadTreeWorld = std::make_unique<Terrain::QuadTreeWorld>(mSceneRoot, mRootNode, mResourceSystem,
mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel,
lodFactor, vertexLodMod, maxCompGeometrySize, debugChunks, worldspace);
if (Settings::Manager::getBool("object paging", "Terrain"))
{
newChunkMgr.mObjectPaging = std::make_unique<ObjectPaging>(mResourceSystem->getSceneManager());
quadTreeWorld->addChunkManager(newChunkMgr.mObjectPaging.get());
mResourceSystem->addResourceManager(newChunkMgr.mObjectPaging.get());
}
if (groundcover)
{
float groundcoverDistance
= std::max(0.f, Settings::Manager::getFloat("rendering distance", "Groundcover"));
float density = Settings::Manager::getFloat("density", "Groundcover");
density = std::clamp(density, 0.f, 1.f);
newChunkMgr.mGroundcover = std::make_unique<Groundcover>(
mResourceSystem->getSceneManager(), density, groundcoverDistance, mGroundCoverStore);
quadTreeWorld->addChunkManager(newChunkMgr.mGroundcover.get());
mResourceSystem->addResourceManager(newChunkMgr.mGroundcover.get());
}
newChunkMgr.mTerrain = std::move(quadTreeWorld);
}
else
newChunkMgr.mTerrain = std::make_unique<Terrain::TerrainGrid>(mSceneRoot, mRootNode, mResourceSystem,
mTerrainStorage.get(), Mask_Terrain, worldspace, Mask_PreCompile, Mask_Debug);
newChunkMgr.mTerrain->setTargetFrameRate(Settings::cells().mTargetFramerate);
float distanceMult = std::cos(osg::DegreesToRadians(std::min(mFieldOfView, 140.f)) / 2.f);
newChunkMgr.mTerrain->setViewDistance(mViewDistance * (distanceMult ? 1.f / distanceMult : 1.f));
return mWorldspaceChunks.emplace(worldspace, std::move(newChunkMgr)).first->second;
}
void RenderingManager::reportStats() const void RenderingManager::reportStats() const
{ {
osg::Stats* stats = mViewer->getViewerStats(); osg::Stats* stats = mViewer->getViewerStats();
@ -1444,9 +1477,9 @@ namespace MWRender
updateProjectionMatrix(); updateProjectionMatrix();
} }
float RenderingManager::getTerrainHeightAt(const osg::Vec3f& pos) float RenderingManager::getTerrainHeightAt(const osg::Vec3f& pos, ESM::RefId worldspace)
{ {
return mTerrain->getHeightAt(pos); return getWorldspaceChunkMgr(worldspace).mTerrain->getHeightAt(pos);
} }
void RenderingManager::overrideFieldOfView(float val) void RenderingManager::overrideFieldOfView(float val)

View file

@ -16,6 +16,7 @@
#include <deque> #include <deque>
#include <memory> #include <memory>
#include <unordered_map>
namespace osg namespace osg
{ {
@ -150,7 +151,7 @@ namespace MWRender
void addCell(const MWWorld::CellStore* store); void addCell(const MWWorld::CellStore* store);
void removeCell(const MWWorld::CellStore* store); void removeCell(const MWWorld::CellStore* store);
void enableTerrain(bool enable); void enableTerrain(bool enable, ESM::RefId worldspace);
void updatePtr(const MWWorld::Ptr& old, const MWWorld::Ptr& updated); void updatePtr(const MWWorld::Ptr& old, const MWWorld::Ptr& updated);
@ -230,7 +231,7 @@ namespace MWRender
void setViewDistance(float distance, bool delay = false); void setViewDistance(float distance, bool delay = false);
float getTerrainHeightAt(const osg::Vec3f& pos); float getTerrainHeightAt(const osg::Vec3f& pos, ESM::RefId worldspace);
// camera stuff // camera stuff
Camera* getCamera() { return mCamera.get(); } Camera* getCamera() { return mCamera.get(); }
@ -282,6 +283,15 @@ namespace MWRender
void setFogColor(const osg::Vec4f& color); void setFogColor(const osg::Vec4f& color);
void updateThirdPersonViewMode(); void updateThirdPersonViewMode();
struct WorldspaceChunkMgr
{
std::unique_ptr<Terrain::World> mTerrain;
std::unique_ptr<ObjectPaging> mObjectPaging;
std::unique_ptr<Groundcover> mGroundcover;
};
WorldspaceChunkMgr& getWorldspaceChunkMgr(ESM::RefId worldspace);
void reportStats() const; void reportStats() const;
void updateNavMesh(); void updateNavMesh();
@ -312,10 +322,11 @@ namespace MWRender
std::unique_ptr<Pathgrid> mPathgrid; std::unique_ptr<Pathgrid> mPathgrid;
std::unique_ptr<Objects> mObjects; std::unique_ptr<Objects> mObjects;
std::unique_ptr<Water> mWater; std::unique_ptr<Water> mWater;
std::unique_ptr<Terrain::World> mTerrain; std::unordered_map<ESM::RefId, WorldspaceChunkMgr> mWorldspaceChunks;
Terrain::World* mTerrain;
std::unique_ptr<TerrainStorage> mTerrainStorage; std::unique_ptr<TerrainStorage> mTerrainStorage;
std::unique_ptr<ObjectPaging> mObjectPaging; ObjectPaging* mObjectPaging;
std::unique_ptr<Groundcover> mGroundcover; Groundcover* mGroundcover;
std::unique_ptr<SkyManager> mSky; std::unique_ptr<SkyManager> mSky;
std::unique_ptr<FogManager> mFog; std::unique_ptr<FogManager> mFog;
std::unique_ptr<ScreenshotManager> mScreenshotManager; std::unique_ptr<ScreenshotManager> mScreenshotManager;
@ -343,6 +354,7 @@ namespace MWRender
float mFirstPersonFieldOfView; float mFirstPersonFieldOfView;
bool mUpdateProjectionMatrix = false; bool mUpdateProjectionMatrix = false;
bool mNight = false; bool mNight = false;
const MWWorld::GroundcoverStore& mGroundCoverStore;
void operator=(const RenderingManager&); void operator=(const RenderingManager&);
RenderingManager(const RenderingManager&); RenderingManager(const RenderingManager&);

View file

@ -26,15 +26,33 @@ namespace MWRender
mResourceSystem->removeResourceManager(mLandManager.get()); mResourceSystem->removeResourceManager(mLandManager.get());
} }
bool TerrainStorage::hasData(int cellX, int cellY) bool TerrainStorage::hasData(ESM::ExteriorCellLocation cellLocation)
{ {
const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore();
const ESM::Land* land = esmStore.get<ESM::Land>().search(cellX, cellY); if (ESM::isEsm4Ext(cellLocation.mWorldspace))
return land != nullptr; {
return esmStore.get<ESM4::Land>().search(cellLocation) != nullptr;
}
else
{
return esmStore.get<ESM::Land>().search(cellLocation.mX, cellLocation.mY) != nullptr;
}
} }
void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY) static void BoundUnion(float& minX, float& maxX, float& minY, float& maxY, float x, float y)
{
if (x < minX)
minX = x;
if (x > maxX)
maxX = x;
if (y < minY)
minY = y;
if (y > maxY)
maxY = y;
}
void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY, ESM::RefId worldspace)
{ {
minX = 0; minX = 0;
minY = 0; minY = 0;
@ -43,19 +61,25 @@ namespace MWRender
const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore(); const MWWorld::ESMStore& esmStore = *MWBase::Environment::get().getESMStore();
MWWorld::Store<ESM::Land>::iterator it = esmStore.get<ESM::Land>().begin(); if (ESM::isEsm4Ext(worldspace))
for (; it != esmStore.get<ESM::Land>().end(); ++it)
{ {
if (it->mX < minX) const auto& lands = esmStore.get<ESM4::Land>().getLands();
minX = static_cast<float>(it->mX); for (const auto& [landPos, _] : lands)
if (it->mX > maxX) {
maxX = static_cast<float>(it->mX); if (landPos.mWorldspace == worldspace)
if (it->mY < minY) {
minY = static_cast<float>(it->mY); BoundUnion(minX, maxX, minY, maxY, static_cast<float>(landPos.mX), static_cast<float>(landPos.mY));
if (it->mY > maxY) }
maxY = static_cast<float>(it->mY); }
}
else
{
MWWorld::Store<ESM::Land>::iterator it = esmStore.get<ESM::Land>().begin();
for (; it != esmStore.get<ESM::Land>().end(); ++it)
{
BoundUnion(minX, maxX, minY, maxY, static_cast<float>(it->mX), static_cast<float>(it->mY));
}
} }
// since grid coords are at cell origin, we need to add 1 cell // since grid coords are at cell origin, we need to add 1 cell
maxX += 1; maxX += 1;
maxY += 1; maxY += 1;
@ -66,9 +90,9 @@ namespace MWRender
return mLandManager.get(); return mLandManager.get();
} }
osg::ref_ptr<const ESMTerrain::LandObject> TerrainStorage::getLand(int cellX, int cellY) osg::ref_ptr<const ESMTerrain::LandObject> TerrainStorage::getLand(ESM::ExteriorCellLocation cellLocation)
{ {
return mLandManager->getLand(ESM::ExteriorCellLocation(cellX, cellY, ESM::Cell::sDefaultWorldspaceId)); return mLandManager->getLand(cellLocation);
} }
const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin) const ESM::LandTexture* TerrainStorage::getLandTexture(int index, short plugin)

View file

@ -21,13 +21,13 @@ namespace MWRender
const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false); const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false);
~TerrainStorage(); ~TerrainStorage();
osg::ref_ptr<const ESMTerrain::LandObject> getLand(int cellX, int cellY) override; osg::ref_ptr<const ESMTerrain::LandObject> getLand(ESM::ExteriorCellLocation cellLocation) override;
const ESM::LandTexture* getLandTexture(int index, short plugin) override; const ESM::LandTexture* getLandTexture(int index, short plugin) override;
bool hasData(int cellX, int cellY) override; bool hasData(ESM::ExteriorCellLocation cellLocation) override;
/// Get bounds of the whole terrain in cell units /// Get bounds of the whole terrain in cell units
void getBounds(float& minX, float& maxX, float& minY, float& maxY) override; void getBounds(float& minX, float& maxX, float& minY, float& maxY, ESM::RefId worldspace) override;
LandManager* getLandManager() const; LandManager* getLandManager() const;

View file

@ -316,7 +316,8 @@ namespace MWScript
{ {
float terrainHeight = -std::numeric_limits<float>::max(); float terrainHeight = -std::numeric_limits<float>::max();
if (ptr.getCell()->isExterior()) if (ptr.getCell()->isExterior())
terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt(curPos); terrainHeight = MWBase::Environment::get().getWorld()->getTerrainHeightAt(
curPos, ptr.getCell()->getCell()->getWorldSpace());
if (pos < terrainHeight) if (pos < terrainHeight)
pos = terrainHeight; pos = terrainHeight;

View file

@ -53,7 +53,8 @@ namespace MWSound
{ {
const float terrainX = pos.x() - mSettings.mNearWaterRadius + x * step; const float terrainX = pos.x() - mSettings.mNearWaterRadius + x * step;
const float terrainY = pos.y() - mSettings.mNearWaterRadius + y * step; const float terrainY = pos.y() - mSettings.mNearWaterRadius + y * step;
const float height = world.getTerrainHeightAt(osg::Vec3f(terrainX, terrainY, 0.0f)); const float height = world.getTerrainHeightAt(
osg::Vec3f(terrainX, terrainY, 0.0f), cell.getCell()->getWorldSpace());
if (height < 0) if (height < 0)
underwaterPoints++; underwaterPoints++;

View file

@ -238,26 +238,7 @@ namespace MWWorld
CellPreloader::~CellPreloader() CellPreloader::~CellPreloader()
{ {
if (mTerrainPreloadItem) clearAllTasks();
{
mTerrainPreloadItem->abort();
mTerrainPreloadItem->waitTillDone();
mTerrainPreloadItem = nullptr;
}
if (mUpdateCacheItem)
{
mUpdateCacheItem->waitTillDone();
mUpdateCacheItem = nullptr;
}
for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end(); ++it)
it->second.mWorkItem->abort();
for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end(); ++it)
it->second.mWorkItem->waitTillDone();
mPreloadCells.clear();
} }
void CellPreloader::preload(CellStore& cell, double timestamp) void CellPreloader::preload(CellStore& cell, double timestamp)
@ -458,4 +439,37 @@ namespace MWWorld
&& contains(mLoadedTerrainPositions, std::array{ position }, ESM::Land::REAL_SIZE); && contains(mLoadedTerrainPositions, std::array{ position }, ESM::Land::REAL_SIZE);
} }
void CellPreloader::setTerrain(Terrain::World* terrain)
{
if (terrain != mTerrain)
{
clearAllTasks();
mTerrain = terrain;
}
}
void CellPreloader::clearAllTasks()
{
if (mTerrainPreloadItem)
{
mTerrainPreloadItem->abort();
mTerrainPreloadItem->waitTillDone();
mTerrainPreloadItem = nullptr;
}
if (mUpdateCacheItem)
{
mUpdateCacheItem->waitTillDone();
mUpdateCacheItem = nullptr;
}
for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end(); ++it)
it->second.mWorkItem->abort();
for (PreloadMap::iterator it = mPreloadCells.begin(); it != mPreloadCells.end(); ++it)
it->second.mWorkItem->waitTillDone();
mPreloadCells.clear();
}
} }

View file

@ -74,8 +74,11 @@ namespace MWWorld
void syncTerrainLoad(Loading::Listener& listener); void syncTerrainLoad(Loading::Listener& listener);
void abortTerrainPreloadExcept(const PositionCellGrid* exceptPos); void abortTerrainPreloadExcept(const PositionCellGrid* exceptPos);
bool isTerrainLoaded(const CellPreloader::PositionCellGrid& position, double referenceTime) const; bool isTerrainLoaded(const CellPreloader::PositionCellGrid& position, double referenceTime) const;
void setTerrain(Terrain::World* terrain);
private: private:
void clearAllTasks();
Resource::ResourceSystem* mResourceSystem; Resource::ResourceSystem* mResourceSystem;
Resource::BulletShapeManager* mBulletShapeManager; Resource::BulletShapeManager* mBulletShapeManager;
Terrain::World* mTerrain; Terrain::World* mTerrain;

View file

@ -14,6 +14,7 @@
#include <components/misc/algorithm.hpp> #include <components/misc/algorithm.hpp>
#include <components/esm4/common.hpp> #include <components/esm4/common.hpp>
#include <components/esm4/loadland.hpp>
#include <components/esm4/loadwrld.hpp> #include <components/esm4/loadwrld.hpp>
#include <components/esm4/reader.hpp> #include <components/esm4/reader.hpp>
#include <components/esm4/readerutils.hpp> #include <components/esm4/readerutils.hpp>
@ -447,6 +448,7 @@ namespace MWWorld
getWritable<ESM::Skill>().setUp(); getWritable<ESM::Skill>().setUp();
getWritable<ESM::MagicEffect>().setUp(); getWritable<ESM::MagicEffect>().setUp();
getWritable<ESM::Attribute>().setUp(); getWritable<ESM::Attribute>().setUp();
getWritable<ESM4::Land>().updateLandPositions(get<ESM4::Cell>());
getWritable<ESM4::Reference>().preprocessReferences(get<ESM4::Cell>()); getWritable<ESM4::Reference>().preprocessReferences(get<ESM4::Cell>());
} }

View file

@ -43,6 +43,7 @@ namespace ESM4
struct MiscItem; struct MiscItem;
struct Weapon; struct Weapon;
struct World; struct World;
struct Land;
} }
namespace ESM namespace ESM
@ -121,7 +122,7 @@ namespace MWWorld
Store<ESM4::Static>, Store<ESM4::Cell>, Store<ESM4::Light>, Store<ESM4::Reference>, Store<ESM4::Activator>, Store<ESM4::Static>, Store<ESM4::Cell>, Store<ESM4::Light>, Store<ESM4::Reference>, Store<ESM4::Activator>,
Store<ESM4::Potion>, Store<ESM4::Ammunition>, Store<ESM4::Armor>, Store<ESM4::Book>, Store<ESM4::Clothing>, Store<ESM4::Potion>, Store<ESM4::Ammunition>, Store<ESM4::Armor>, Store<ESM4::Book>, Store<ESM4::Clothing>,
Store<ESM4::Container>, Store<ESM4::Door>, Store<ESM4::Ingredient>, Store<ESM4::MiscItem>, Store<ESM4::Container>, Store<ESM4::Door>, Store<ESM4::Ingredient>, Store<ESM4::MiscItem>,
Store<ESM4::Weapon>, Store<ESM4::World>>; Store<ESM4::Weapon>, Store<ESM4::World>, Store<ESM4::Land>>;
private: private:
template <typename T> template <typename T>

View file

@ -12,6 +12,7 @@
#include <components/detournavigator/heightfieldshape.hpp> #include <components/detournavigator/heightfieldshape.hpp>
#include <components/detournavigator/navigator.hpp> #include <components/detournavigator/navigator.hpp>
#include <components/detournavigator/updateguard.hpp> #include <components/detournavigator/updateguard.hpp>
#include <components/esm/esmterrain.hpp>
#include <components/esm/records.hpp> #include <components/esm/records.hpp>
#include <components/esm3/loadcell.hpp> #include <components/esm3/loadcell.hpp>
#include <components/loadinglistener/loadinglistener.hpp> #include <components/loadinglistener/loadinglistener.hpp>
@ -397,15 +398,16 @@ namespace MWWorld
if (cellVariant.isExterior()) if (cellVariant.isExterior())
{ {
osg::ref_ptr<const ESMTerrain::LandObject> land = mRendering.getLandManager()->getLand(cellIndex); osg::ref_ptr<const ESMTerrain::LandObject> land = mRendering.getLandManager()->getLand(cellIndex);
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr; const ESM::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr;
const int verts = ESM::Land::LAND_SIZE; const int verts = ESM::getLandSize(worldspace);
const int worldsize = ESM::Land::REAL_SIZE; const int worldsize = ESM::getCellSize(worldspace);
if (data) if (data)
{ {
mPhysics->addHeightField( mPhysics->addHeightField(data->getHeights().data(), cellX, cellY, worldsize, verts,
data->mHeights, cellX, cellY, worldsize, verts, data->mMinHeight, data->mMaxHeight, land.get()); data->getMinHeight(), data->getMaxHeight(), land.get());
} }
else else if (!ESM::isEsm4Ext(worldspace))
{ {
static std::vector<float> defaultHeight; static std::vector<float> defaultHeight;
defaultHeight.resize(verts * verts, ESM::Land::DEFAULT_HEIGHT); defaultHeight.resize(verts * verts, ESM::Land::DEFAULT_HEIGHT);
@ -425,14 +427,14 @@ namespace MWWorld
else else
{ {
DetourNavigator::HeightfieldSurface heights; DetourNavigator::HeightfieldSurface heights;
heights.mHeights = data->mHeights; heights.mHeights = data->getHeights().data();
heights.mSize = static_cast<std::size_t>(ESM::Land::LAND_SIZE); heights.mSize = static_cast<std::size_t>(data->getLandSize());
heights.mMinHeight = data->mMinHeight; heights.mMinHeight = data->getMinHeight();
heights.mMaxHeight = data->mMaxHeight; heights.mMaxHeight = data->getMaxHeight();
return heights; return heights;
} }
}(); }();
mNavigator.addHeightfield(cellPosition, ESM::Land::REAL_SIZE, shape, navigatorUpdateGuard); mNavigator.addHeightfield(cellPosition, worldsize, shape, navigatorUpdateGuard);
} }
} }
@ -570,7 +572,8 @@ namespace MWWorld
mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY); mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY);
osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter); osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter);
mRendering.setActiveGrid(newGrid); mRendering.setActiveGrid(newGrid);
mRendering.enableTerrain(true, playerCellIndex.mWorldspace);
mPreloader->setTerrain(mRendering.getTerrain());
if (mRendering.pagingUnlockCache()) if (mRendering.pagingUnlockCache())
mPreloader->abortTerrainPreloadExcept(nullptr); mPreloader->abortTerrainPreloadExcept(nullptr);
if (!mPreloader->isTerrainLoaded(std::make_pair(pos, newGrid), mRendering.getReferenceTime())) if (!mPreloader->isTerrainLoaded(std::make_pair(pos, newGrid), mRendering.getReferenceTime()))
@ -784,7 +787,7 @@ namespace MWWorld
mHalfGridSize = cell.getCell()->isEsm4() ? Constants::ESM4CellGridRadius : Constants::CellGridRadius; mHalfGridSize = cell.getCell()->isEsm4() ? Constants::ESM4CellGridRadius : Constants::CellGridRadius;
mCurrentCell = &cell; mCurrentCell = &cell;
mRendering.enableTerrain(cell.isExterior()); mRendering.enableTerrain(cell.isExterior(), cell.getCell()->getWorldSpace());
MWWorld::Ptr old = mWorld.getPlayerPtr(); MWWorld::Ptr old = mWorld.getPlayerPtr();
mWorld.getPlayer().setCell(&cell); mWorld.getPlayer().setCell(&cell);

View file

@ -8,11 +8,14 @@
#include <components/esm/records.hpp> #include <components/esm/records.hpp>
#include <components/esm3/esmreader.hpp> #include <components/esm3/esmreader.hpp>
#include <components/esm3/esmwriter.hpp> #include <components/esm3/esmwriter.hpp>
#include <components/esm4/loadland.hpp>
#include <components/esm4/loadwrld.hpp> #include <components/esm4/loadwrld.hpp>
#include <components/loadinglistener/loadinglistener.hpp> #include <components/loadinglistener/loadinglistener.hpp>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <apps/openmw/mwworld/cell.hpp> #include "../mwbase/environment.hpp"
#include "../mwworld/cell.hpp"
#include "../mwworld/worldimp.hpp"
namespace namespace
{ {
@ -1189,6 +1192,33 @@ namespace MWWorld
MWWorld::TypedDynamicStore<ESM4::Cell>::clearDynamic(); MWWorld::TypedDynamicStore<ESM4::Cell>::clearDynamic();
} }
// ESM4 Land
//=========================================================================
// Needed to avoid include of ESM4::Land in header
Store<ESM4::Land>::Store() {}
void Store<ESM4::Land>::updateLandPositions(const Store<ESM4::Cell>& cells)
{
for (const auto& [id, value] : mStatic)
{
const ESM4::Cell* cell = cells.find(value.mCell);
mLands[cell->getExteriorCellLocation()] = &value;
}
for (const auto& [id, value] : mDynamic)
{
const ESM4::Cell* cell = cells.find(value.mCell);
mLands[cell->getExteriorCellLocation()] = &value;
}
}
const ESM4::Land* MWWorld::Store<ESM4::Land>::search(ESM::ExteriorCellLocation cellLocation) const
{
auto foundLand = mLands.find(cellLocation);
if (foundLand == mLands.end())
return nullptr;
return foundLand->second;
}
// ESM4 Reference // ESM4 Reference
//========================================================================= //=========================================================================
@ -1276,3 +1306,4 @@ template class MWWorld::TypedDynamicStore<ESM4::Reference, ESM::FormId>;
template class MWWorld::TypedDynamicStore<ESM4::Cell>; template class MWWorld::TypedDynamicStore<ESM4::Cell>;
template class MWWorld::TypedDynamicStore<ESM4::Weapon>; template class MWWorld::TypedDynamicStore<ESM4::Weapon>;
template class MWWorld::TypedDynamicStore<ESM4::World>; template class MWWorld::TypedDynamicStore<ESM4::World>;
template class MWWorld::TypedDynamicStore<ESM4::Land>;

View file

@ -18,6 +18,7 @@
#include <components/esm3/loadland.hpp> #include <components/esm3/loadland.hpp>
#include <components/esm3/loadpgrd.hpp> #include <components/esm3/loadpgrd.hpp>
#include <components/esm4/loadcell.hpp> #include <components/esm4/loadcell.hpp>
#include <components/esm4/loadland.hpp>
#include <components/esm4/loadrefr.hpp> #include <components/esm4/loadrefr.hpp>
#include <components/misc/rng.hpp> #include <components/misc/rng.hpp>
#include <components/misc/strings/algorithm.hpp> #include <components/misc/strings/algorithm.hpp>
@ -35,6 +36,11 @@ namespace ESM
class ESMWriter; class ESMWriter;
} }
namespace ESM4
{
struct Land;
}
namespace Loading namespace Loading
{ {
class Listener; class Listener;
@ -298,6 +304,19 @@ namespace MWWorld
void clearDynamic() override; void clearDynamic() override;
}; };
template <>
class Store<ESM4::Land> : public TypedDynamicStore<ESM4::Land>
{
std::unordered_map<ESM::ExteriorCellLocation, const ESM4::Land*> mLands;
public:
Store();
void updateLandPositions(const Store<ESM4::Cell>& cells);
const ESM4::Land* search(ESM::ExteriorCellLocation cellLocation) const;
const std::unordered_map<ESM::ExteriorCellLocation, const ESM4::Land*>& getLands() const { return mLands; }
};
template <> template <>
class Store<ESM::Land> : public DynamicStore class Store<ESM::Land> : public DynamicStore
{ {

View file

@ -1363,9 +1363,11 @@ namespace MWWorld
return; return;
} }
if (ptr.getCell()->isExterior() && !ptr.getCell()->getCell()->isEsm4()) const float terrainHeight = ptr.getCell()->isExterior()
pos.z() = std::max(pos.z(), getTerrainHeightAt(pos)); ? getTerrainHeightAt(pos, ptr.getCell()->getCell()->getWorldSpace())
pos.z() += 20; // place slightly above terrain. will snap down to ground with code below : -std::numeric_limits<float>::max();
pos.z() = std::max(pos.z(), terrainHeight)
+ 20; // place slightly above terrain. will snap down to ground with code below
// We still should trace down dead persistent actors - they do not use the "swimdeath" animation. // We still should trace down dead persistent actors - they do not use the "swimdeath" animation.
bool swims = ptr.getClass().isActor() && isSwimming(ptr) bool swims = ptr.getClass().isActor() && isSwimming(ptr)
@ -3607,9 +3609,9 @@ namespace MWWorld
return mPlayerTraveling; return mPlayerTraveling;
} }
float World::getTerrainHeightAt(const osg::Vec3f& worldPos) const float World::getTerrainHeightAt(const osg::Vec3f& worldPos, ESM::RefId worldspace) const
{ {
return mRendering->getTerrainHeightAt(worldPos); return mRendering->getTerrainHeightAt(worldPos, worldspace);
} }
osg::Vec3f World::getHalfExtents(const ConstPtr& object, bool rendering) const osg::Vec3f World::getHalfExtents(const ConstPtr& object, bool rendering) const

View file

@ -648,7 +648,7 @@ namespace MWWorld
bool isPlayerTraveling() const override; bool isPlayerTraveling() const override;
/// Return terrain height at \a worldPos position. /// Return terrain height at \a worldPos position.
float getTerrainHeightAt(const osg::Vec3f& worldPos) const override; float getTerrainHeightAt(const osg::Vec3f& worldPos, ESM::RefId worldspace) const override;
/// Return physical or rendering half extents of the given actor. /// Return physical or rendering half extents of the given actor.
osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor, bool rendering = false) const override; osg::Vec3f getHalfExtents(const MWWorld::ConstPtr& actor, bool rendering = false) const override;

View file

@ -17,6 +17,7 @@
#include <components/esm3/typetraits.hpp> #include <components/esm3/typetraits.hpp>
#include <components/esm4/common.hpp> #include <components/esm4/common.hpp>
#include <components/esm4/loadcell.hpp> #include <components/esm4/loadcell.hpp>
#include <components/esm4/loadland.hpp>
#include <components/esm4/loadligh.hpp> #include <components/esm4/loadligh.hpp>
#include <components/esm4/loadrefr.hpp> #include <components/esm4/loadrefr.hpp>
#include <components/esm4/loadstat.hpp> #include <components/esm4/loadstat.hpp>

View file

@ -118,7 +118,7 @@ add_component_dir (to_utf8
to_utf8 to_utf8
) )
add_component_dir(esm attr common defs esmcommon records util luascripts format refid esmbridge add_component_dir(esm attr common defs esmcommon records util luascripts format refid esmbridge esmterrain
formid formid
formidrefid formidrefid
stringrefid stringrefid

View file

@ -0,0 +1,67 @@
#include <components/misc/constants.hpp>
#include "esmterrain.hpp"
ESM::LandData::LandData(const ESM::Land& land, int loadFlags)
: mLoadFlags(loadFlags)
, mSize(Constants::CellSizeInUnits)
, mLandSize(ESM::Land::LAND_SIZE)
{
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(loadFlags)
, mSize(Constants::ESM4CellSizeInUnits)
, mLandSize(ESM4::Land::VERTS_PER_SIDE)
{
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;
for (int y = 0; y < mLandSize; y++)
{
row_offset += land.mHeightMap.gradientData[y * mLandSize];
const float heightY = row_offset * ESM4::Land::HEIGHT_SCALE;
mHeights[y * mLandSize] = heightY;
mMinHeight = std::min(mMinHeight, heightY);
mMaxHeight = std::max(mMaxHeight, heightY);
float colOffset = row_offset;
for (int x = 1; x < mLandSize; x++)
{
colOffset += land.mHeightMap.gradientData[y * mLandSize + x];
const float heightX = colOffset * ESM4::Land::HEIGHT_SCALE;
mMinHeight = std::min(mMinHeight, heightX);
mMaxHeight = std::max(mMaxHeight, heightX);
mHeights[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());
}

View file

@ -0,0 +1,46 @@
#ifndef COMPONENTS_ESM_ESMTERRAIN
#define COMPONENTS_ESM_ESMTERRAIN
#include <span>
#include <vector>
#include <components/esm3/loadland.hpp>
#include <components/esm4/loadland.hpp>
namespace ESM
{
class LandData
{
public:
~LandData() = default;
LandData() = default;
LandData(const ESM::Land& Land, int loadFLags);
LandData(const ESM4::Land& Land, int loadFLags);
typedef signed char VNML;
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; }
float getSize() const { return mSize; }
float getMinHeight() const { return mMinHeight; }
float getMaxHeight() const { return mMaxHeight; }
int getLandSize() const { return mLandSize; }
int mLoadFlags;
private:
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;
};
}
#endif // ! COMPNENTS_ESM_ESMTERRAIN

View file

@ -1,4 +1,6 @@
#include "util.hpp" #include "util.hpp"
#include <components/esm3/loadland.hpp>
#include <components/esm4/loadland.hpp>
osg::Vec2 ESM::indexToPosition(const ESM::ExteriorCellLocation& cellIndex, bool centre) osg::Vec2 ESM::indexToPosition(const ESM::ExteriorCellLocation& cellIndex, bool centre)
{ {
@ -14,3 +16,8 @@ osg::Vec2 ESM::indexToPosition(const ESM::ExteriorCellLocation& cellIndex, bool
} }
return osg::Vec2(x, y); return osg::Vec2(x, y);
} }
int ESM::getLandSize(ESM::RefId worldspaceId)
{
return isEsm4Ext(worldspaceId) ? ESM4::Land::VERTS_PER_SIDE : ESM::Land::LAND_SIZE;
}

View file

@ -51,8 +51,10 @@ namespace ESM
struct ExteriorCellLocation struct ExteriorCellLocation
{ {
int mX, mY; int mX = 0;
ESM::RefId mWorldspace; int mY = 0;
ESM::RefId mWorldspace = ESM::Cell::sDefaultWorldspaceId;
ExteriorCellLocation() = default;
ExteriorCellLocation(int x, int y, ESM::RefId worldspace) ExteriorCellLocation(int x, int y, ESM::RefId worldspace)
: mX(x) : mX(x)
@ -83,6 +85,9 @@ namespace ESM
return isEsm4Ext(worldspaceId) ? Constants::ESM4CellSizeInUnits : Constants::CellSizeInUnits; return isEsm4Ext(worldspaceId) ? Constants::ESM4CellSizeInUnits : Constants::CellSizeInUnits;
} }
// Vertex count of a side of a land record
int getLandSize(ESM::RefId worldspaceId);
inline ESM::ExteriorCellLocation positionToExteriorCellLocation( inline ESM::ExteriorCellLocation positionToExteriorCellLocation(
float x, float y, ESM::RefId worldspaceId = ESM::Cell::sDefaultWorldspaceId) float x, float y, ESM::RefId worldspaceId = ESM::Cell::sDefaultWorldspaceId)
{ {

View file

@ -87,10 +87,10 @@ namespace ESM
}; };
#pragma pack(pop) #pragma pack(pop)
typedef signed char VNML;
struct LandData struct LandData
{ {
typedef signed char VNML;
LandData() LandData()
: mHeightOffset(0) : mHeightOffset(0)
, mMinHeight(0) , mMinHeight(0)

View file

@ -6,6 +6,8 @@
#include <osg/Plane> #include <osg/Plane>
#include <components/debug/debuglog.hpp> #include <components/debug/debuglog.hpp>
#include <components/esm/esmterrain.hpp>
#include <components/esm4/loadland.hpp>
#include <components/misc/resourcehelpers.hpp> #include <components/misc/resourcehelpers.hpp>
#include <components/misc/strings/algorithm.hpp> #include <components/misc/strings/algorithm.hpp>
#include <components/vfs/manager.hpp> #include <components/vfs/manager.hpp>
@ -16,26 +18,29 @@ namespace ESMTerrain
class LandCache class LandCache
{ {
public: public:
typedef std::map<std::pair<int, int>, osg::ref_ptr<const LandObject>> Map; typedef std::map<ESM::ExteriorCellLocation, osg::ref_ptr<const LandObject>> Map;
Map mMap; Map mMap;
}; };
LandObject::LandObject() LandObject::LandObject()
: mLand(nullptr) : mLand(nullptr)
, mLoadFlags(0) {
}
LandObject::LandObject(const ESM4::Land* land, int loadFlags)
: mLand(nullptr)
, mData(*land, loadFlags)
{ {
} }
LandObject::LandObject(const ESM::Land* land, int loadFlags) LandObject::LandObject(const ESM::Land* land, int loadFlags)
: mLand(land) : mLand(land)
, mLoadFlags(loadFlags) , mData(*land, loadFlags)
{ {
mLand->loadData(mLoadFlags, &mData);
} }
LandObject::LandObject(const LandObject& copy, const osg::CopyOp& copyop) LandObject::LandObject(const LandObject& copy, const osg::CopyOp& copyop)
: mLand(nullptr) : mLand(nullptr)
, mLoadFlags(0)
{ {
} }
@ -55,7 +60,7 @@ namespace ESMTerrain
{ {
} }
bool Storage::getMinMaxHeights(float size, const osg::Vec2f& center, float& min, float& max) bool Storage::getMinMaxHeights(float size, const osg::Vec2f& center, ESM::RefId worldspace, float& min, float& max)
{ {
assert(size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell"); assert(size <= 1 && "Storage::getMinMaxHeights, chunk size should be <= 1 cell");
@ -63,15 +68,15 @@ namespace ESMTerrain
int cellX = static_cast<int>(std::floor(origin.x())); int cellX = static_cast<int>(std::floor(origin.x()));
int cellY = static_cast<int>(std::floor(origin.y())); int cellY = static_cast<int>(std::floor(origin.y()));
osg::ref_ptr<const LandObject> land = getLand(ESM::ExteriorCellLocation(cellX, cellY, worldspace));
const ESM::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr;
const int landSize = ESM::getLandSize(worldspace);
int startRow = (origin.x() - cellX) * landSize;
int startColumn = (origin.y() - cellY) * landSize;
int startRow = (origin.x() - cellX) * ESM::Land::LAND_SIZE; int endRow = startRow + size * (landSize - 1) + 1;
int startColumn = (origin.y() - cellY) * ESM::Land::LAND_SIZE; int endColumn = startColumn + size * (landSize - 1) + 1;
int endRow = startRow + size * (ESM::Land::LAND_SIZE - 1) + 1;
int endColumn = startColumn + size * (ESM::Land::LAND_SIZE - 1) + 1;
osg::ref_ptr<const LandObject> land = getLand(cellX, cellY);
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VHGT) : nullptr;
if (data) if (data)
{ {
min = std::numeric_limits<float>::max(); min = std::numeric_limits<float>::max();
@ -80,7 +85,7 @@ namespace ESMTerrain
{ {
for (int col = startColumn; col < endColumn; ++col) for (int col = startColumn; col < endColumn; ++col)
{ {
float h = data->mHeights[col * ESM::Land::LAND_SIZE + row]; float h = data->getHeights()[col * landSize + row];
if (h > max) if (h > max)
max = h; max = h;
if (h < min) if (h < min)
@ -95,73 +100,80 @@ namespace ESMTerrain
return false; return false;
} }
void Storage::fixNormal(osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache) void Storage::fixNormal(
osg::Vec3f& normal, ESM::ExteriorCellLocation cellLocation, int col, int row, LandCache& cache)
{ {
while (col >= ESM::Land::LAND_SIZE - 1)
const int landSize = ESM::getLandSize(cellLocation.mWorldspace);
while (col >= landSize - 1)
{ {
++cellY; ++cellLocation.mY;
col -= ESM::Land::LAND_SIZE - 1; col -= landSize - 1;
} }
while (row >= ESM::Land::LAND_SIZE - 1) while (row >= landSize - 1)
{ {
++cellX; ++cellLocation.mX;
row -= ESM::Land::LAND_SIZE - 1; row -= landSize - 1;
} }
while (col < 0) while (col < 0)
{ {
--cellY; --cellLocation.mY;
col += ESM::Land::LAND_SIZE - 1; col += landSize - 1;
} }
while (row < 0) while (row < 0)
{ {
--cellX; --cellLocation.mX;
row += ESM::Land::LAND_SIZE - 1; row += landSize - 1;
} }
const LandObject* land = getLand(cellLocation, cache);
const LandObject* land = getLand(cellX, cellY, cache); const ESM::LandData* data = land ? land->getData(ESM::Land::DATA_VNML) : nullptr;
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VNML) : nullptr;
if (data) if (data)
{ {
normal.x() = data->mNormals[col * ESM::Land::LAND_SIZE * 3 + row * 3]; normal.x() = data->getNormals()[col * landSize * 3 + row * 3];
normal.y() = data->mNormals[col * ESM::Land::LAND_SIZE * 3 + row * 3 + 1]; normal.y() = data->getNormals()[col * landSize * 3 + row * 3 + 1];
normal.z() = data->mNormals[col * ESM::Land::LAND_SIZE * 3 + row * 3 + 2]; normal.z() = data->getNormals()[col * landSize * 3 + row * 3 + 2];
normal.normalize(); normal.normalize();
} }
else else
normal = osg::Vec3f(0, 0, 1); normal = osg::Vec3f(0, 0, 1);
} }
void Storage::averageNormal(osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache) void Storage::averageNormal(
osg::Vec3f& normal, ESM::ExteriorCellLocation cellLocation, int col, int row, LandCache& cache)
{ {
osg::Vec3f n1, n2, n3, n4; osg::Vec3f n1, n2, n3, n4;
fixNormal(n1, cellX, cellY, col + 1, row, cache); fixNormal(n1, cellLocation, col + 1, row, cache);
fixNormal(n2, cellX, cellY, col - 1, row, cache); fixNormal(n2, cellLocation, col - 1, row, cache);
fixNormal(n3, cellX, cellY, col, row + 1, cache); fixNormal(n3, cellLocation, col, row + 1, cache);
fixNormal(n4, cellX, cellY, col, row - 1, cache); fixNormal(n4, cellLocation, col, row - 1, cache);
normal = (n1 + n2 + n3 + n4); normal = (n1 + n2 + n3 + n4);
normal.normalize(); normal.normalize();
} }
void Storage::fixColour(osg::Vec4ub& color, int cellX, int cellY, int col, int row, LandCache& cache) void Storage::fixColour(
osg::Vec4ub& color, ESM::ExteriorCellLocation cellLocation, int col, int row, LandCache& cache)
{ {
if (col == ESM::Land::LAND_SIZE - 1)
const int landSize = ESM::getLandSize(cellLocation.mWorldspace);
if (col == landSize - 1)
{ {
++cellY; ++cellLocation.mY;
col = 0; col = 0;
} }
if (row == ESM::Land::LAND_SIZE - 1) if (row == landSize - 1)
{ {
++cellX; ++cellLocation.mX;
row = 0; row = 0;
} }
const LandObject* land = getLand(cellLocation, cache);
const LandObject* land = getLand(cellX, cellY, cache); const ESM::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : nullptr;
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VCLR) : nullptr;
if (data) if (data)
{ {
color.r() = data->mColours[col * ESM::Land::LAND_SIZE * 3 + row * 3]; color.r() = data->getColors()[col * landSize * 3 + row * 3];
color.g() = data->mColours[col * ESM::Land::LAND_SIZE * 3 + row * 3 + 1]; color.g() = data->getColors()[col * landSize * 3 + row * 3 + 1];
color.b() = data->mColours[col * ESM::Land::LAND_SIZE * 3 + row * 3 + 2]; color.b() = data->getColors()[col * landSize * 3 + row * 3 + 2];
} }
else else
{ {
@ -171,7 +183,7 @@ namespace ESMTerrain
} }
} }
void Storage::fillVertexBuffers(int lodLevel, float size, const osg::Vec2f& center, void Storage::fillVertexBuffers(int lodLevel, float size, const osg::Vec2f& center, ESM::RefId worldspace,
osg::ref_ptr<osg::Vec3Array> positions, osg::ref_ptr<osg::Vec3Array> normals, osg::ref_ptr<osg::Vec3Array> positions, osg::ref_ptr<osg::Vec3Array> normals,
osg::ref_ptr<osg::Vec4ubArray> colours) osg::ref_ptr<osg::Vec4ubArray> colours)
{ {
@ -182,8 +194,10 @@ namespace ESMTerrain
int startCellX = static_cast<int>(std::floor(origin.x())); int startCellX = static_cast<int>(std::floor(origin.x()));
int startCellY = static_cast<int>(std::floor(origin.y())); int startCellY = static_cast<int>(std::floor(origin.y()));
const int landSize = ESM::getLandSize(worldspace);
const int LandSizeInUnits = ESM::getCellSize(worldspace);
size_t numVerts = static_cast<size_t>(size * (ESM::Land::LAND_SIZE - 1) / increment + 1); size_t numVerts = static_cast<size_t>(size * (landSize - 1) / increment + 1);
positions->resize(numVerts * numVerts); positions->resize(numVerts * numVerts);
normals->resize(numVerts * numVerts); normals->resize(numVerts * numVerts);
@ -198,22 +212,24 @@ namespace ESMTerrain
LandCache cache; LandCache cache;
bool alteration = useAlteration(); bool alteration = useAlteration();
bool validHeightDataExists = false;
float vertY_ = 0; // of current cell corner float vertY_ = 0; // of current cell corner
for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY) for (int cellY = startCellY; cellY < startCellY + std::ceil(size); ++cellY)
{ {
float vertX_ = 0; // of current cell corner float vertX_ = 0; // of current cell corner
for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX) for (int cellX = startCellX; cellX < startCellX + std::ceil(size); ++cellX)
{ {
const LandObject* land = getLand(cellX, cellY, cache); ESM::ExteriorCellLocation cellLocation(cellX, cellY, worldspace);
const ESM::Land::LandData* heightData = nullptr; const LandObject* land = getLand(cellLocation, cache);
const ESM::Land::LandData* normalData = nullptr; const ESM::LandData* heightData = nullptr;
const ESM::Land::LandData* colourData = nullptr; const ESM::LandData* normalData = nullptr;
const ESM::LandData* colourData = nullptr;
if (land) if (land)
{ {
heightData = land->getData(ESM::Land::DATA_VHGT); heightData = land->getData(ESM::Land::DATA_VHGT);
normalData = land->getData(ESM::Land::DATA_VNML); normalData = land->getData(ESM::Land::DATA_VNML);
colourData = land->getData(ESM::Land::DATA_VCLR); colourData = land->getData(ESM::Land::DATA_VCLR);
validHeightDataExists = true;
} }
int rowStart = 0; int rowStart = 0;
@ -227,12 +243,12 @@ namespace ESMTerrain
rowStart += increment; rowStart += increment;
// Only relevant for chunks smaller than (contained in) one cell // Only relevant for chunks smaller than (contained in) one cell
rowStart += (origin.x() - startCellX) * ESM::Land::LAND_SIZE; rowStart += (origin.x() - startCellX) * landSize;
colStart += (origin.y() - startCellY) * ESM::Land::LAND_SIZE; colStart += (origin.y() - startCellY) * landSize;
int rowEnd = std::min(static_cast<int>(rowStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE - 1) + 1), int rowEnd = std::min(
static_cast<int>(ESM::Land::LAND_SIZE)); static_cast<int>(rowStart + std::min(1.f, size) * (landSize - 1) + 1), static_cast<int>(landSize));
int colEnd = std::min(static_cast<int>(colStart + std::min(1.f, size) * (ESM::Land::LAND_SIZE - 1) + 1), int colEnd = std::min(
static_cast<int>(ESM::Land::LAND_SIZE)); static_cast<int>(colStart + std::min(1.f, size) * (landSize - 1) + 1), static_cast<int>(landSize));
vertY = vertY_; vertY = vertY_;
for (int col = colStart; col < colEnd; col += increment) for (int col = colStart; col < colEnd; col += increment)
@ -240,27 +256,27 @@ namespace ESMTerrain
vertX = vertX_; vertX = vertX_;
for (int row = rowStart; row < rowEnd; row += increment) for (int row = rowStart; row < rowEnd; row += increment)
{ {
int srcArrayIndex = col * ESM::Land::LAND_SIZE * 3 + row * 3; int srcArrayIndex = col * landSize * 3 + row * 3;
assert(row >= 0 && row < ESM::Land::LAND_SIZE); assert(row >= 0 && row < landSize);
assert(col >= 0 && col < ESM::Land::LAND_SIZE); assert(col >= 0 && col < landSize);
assert(vertX < numVerts); assert(vertX < numVerts);
assert(vertY < numVerts); assert(vertY < numVerts);
float height = defaultHeight; float height = defaultHeight;
if (heightData) if (heightData)
height = heightData->mHeights[col * ESM::Land::LAND_SIZE + row]; height = heightData->getHeights()[col * landSize + row];
if (alteration) if (alteration)
height += getAlteredHeight(col, row); height += getAlteredHeight(col, row);
(*positions)[static_cast<unsigned int>(vertX * numVerts + vertY)] (*positions)[static_cast<unsigned int>(vertX * numVerts + vertY)]
= osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * Constants::CellSizeInUnits, = osg::Vec3f((vertX / float(numVerts - 1) - 0.5f) * size * LandSizeInUnits,
(vertY / float(numVerts - 1) - 0.5f) * size * Constants::CellSizeInUnits, height); (vertY / float(numVerts - 1) - 0.5f) * size * LandSizeInUnits, height);
if (normalData) if (normalData)
{ {
for (int i = 0; i < 3; ++i) for (int i = 0; i < 3; ++i)
normal[i] = normalData->mNormals[srcArrayIndex + i]; normal[i] = normalData->getNormals()[srcArrayIndex + i];
normal.normalize(); normal.normalize();
} }
@ -268,13 +284,12 @@ namespace ESMTerrain
normal = osg::Vec3f(0, 0, 1); normal = osg::Vec3f(0, 0, 1);
// Normals apparently don't connect seamlessly between cells // Normals apparently don't connect seamlessly between cells
if (col == ESM::Land::LAND_SIZE - 1 || row == ESM::Land::LAND_SIZE - 1) if (col == landSize - 1 || row == landSize - 1)
fixNormal(normal, cellX, cellY, col, row, cache); fixNormal(normal, cellLocation, col, row, cache);
// some corner normals appear to be complete garbage (z < 0) // some corner normals appear to be complete garbage (z < 0)
if ((row == 0 || row == ESM::Land::LAND_SIZE - 1) if ((row == 0 || row == landSize - 1) && (col == 0 || col == landSize - 1))
&& (col == 0 || col == ESM::Land::LAND_SIZE - 1)) averageNormal(normal, cellLocation, col, row, cache);
averageNormal(normal, cellX, cellY, col, row, cache);
assert(normal.z() > 0); assert(normal.z() > 0);
@ -283,7 +298,7 @@ namespace ESMTerrain
if (colourData) if (colourData)
{ {
for (int i = 0; i < 3; ++i) for (int i = 0; i < 3; ++i)
color[i] = colourData->mColours[srcArrayIndex + i]; color[i] = colourData->getColors()[srcArrayIndex + i];
} }
else else
{ {
@ -295,8 +310,8 @@ namespace ESMTerrain
adjustColor(col, row, heightData, color); // Does nothing by default, override in OpenMW-CS adjustColor(col, row, heightData, color); // Does nothing by default, override in OpenMW-CS
// Unlike normals, colors mostly connect seamlessly between cells, but not always... // Unlike normals, colors mostly connect seamlessly between cells, but not always...
if (col == ESM::Land::LAND_SIZE - 1 || row == ESM::Land::LAND_SIZE - 1) if (col == landSize - 1 || row == landSize - 1)
fixColour(color, cellX, cellY, col, row, cache); fixColour(color, cellLocation, col, row, cache);
color.a() = 255; color.a() = 255;
@ -313,39 +328,50 @@ namespace ESMTerrain
assert(vertX_ == numVerts); // Ensure we covered whole area assert(vertX_ == numVerts); // Ensure we covered whole area
} }
assert(vertY_ == numVerts); // Ensure we covered whole area assert(vertY_ == numVerts); // Ensure we covered whole area
if (!validHeightDataExists && ESM::isEsm4Ext(worldspace))
{
for (unsigned int iVert = 0; iVert < numVerts * numVerts; iVert++)
{
(*positions)[static_cast<unsigned int>(iVert)] = osg::Vec3f(0.f, 0.f, 0.f);
}
}
} }
Storage::UniqueTextureId Storage::getVtexIndexAt(int cellX, int cellY, int x, int y, LandCache& cache) Storage::UniqueTextureId Storage::getVtexIndexAt(
ESM::ExteriorCellLocation cellLocation, const LandObject* land, int x, int y, LandCache& cache)
{ {
// For the first/last row/column, we need to get the texture from the neighbour cell // For the first/last row/column, we need to get the texture from the neighbour cell
// to get consistent blending at the borders // to get consistent blending at the borders
--x; --x;
ESM::ExteriorCellLocation cellLocationIn = cellLocation;
if (x < 0) if (x < 0)
{ {
--cellX; --cellLocation.mX;
x += ESM::Land::LAND_TEXTURE_SIZE; x += ESM::Land::LAND_TEXTURE_SIZE;
} }
while (x >= ESM::Land::LAND_TEXTURE_SIZE) while (x >= ESM::Land::LAND_TEXTURE_SIZE)
{ {
++cellX; ++cellLocation.mX;
x -= ESM::Land::LAND_TEXTURE_SIZE; x -= ESM::Land::LAND_TEXTURE_SIZE;
} }
while ( while (
y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not? y >= ESM::Land::LAND_TEXTURE_SIZE) // Y appears to be wrapped from the other side because why the hell not?
{ {
++cellY; ++cellLocation.mY;
y -= ESM::Land::LAND_TEXTURE_SIZE; y -= ESM::Land::LAND_TEXTURE_SIZE;
} }
if (cellLocation != cellLocationIn)
land = getLand(cellLocation, cache);
assert(x < ESM::Land::LAND_TEXTURE_SIZE); assert(x < ESM::Land::LAND_TEXTURE_SIZE);
assert(y < ESM::Land::LAND_TEXTURE_SIZE); assert(y < ESM::Land::LAND_TEXTURE_SIZE);
const LandObject* land = getLand(cellX, cellY, cache); const ESM::LandData* data = land ? land->getData(ESM::Land::DATA_VTEX) : nullptr;
const ESM::Land::LandData* data = land ? land->getData(ESM::Land::DATA_VTEX) : nullptr;
if (data) if (data)
{ {
int tex = data->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; int tex = data->getTextures()[y * ESM::Land::LAND_TEXTURE_SIZE + x];
if (tex == 0) if (tex == 0)
return std::make_pair(0, 0); // vtex 0 is always the base texture, regardless of plugin return std::make_pair(0, 0); // vtex 0 is always the base texture, regardless of plugin
return std::make_pair(tex, land->getPlugin()); return std::make_pair(tex, land->getPlugin());
@ -375,7 +401,7 @@ namespace ESMTerrain
} }
void Storage::getBlendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, void Storage::getBlendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps,
std::vector<Terrain::LayerInfo>& layerList) std::vector<Terrain::LayerInfo>& layerList, ESM::RefId worldspace)
{ {
osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize / 2.f, chunkSize / 2.f); osg::Vec2f origin = chunkCenter - osg::Vec2f(chunkSize / 2.f, chunkSize / 2.f);
int cellX = static_cast<int>(std::floor(origin.x())); int cellX = static_cast<int>(std::floor(origin.x()));
@ -393,12 +419,15 @@ namespace ESMTerrain
LandCache cache; LandCache cache;
std::map<UniqueTextureId, unsigned int> textureIndicesMap; std::map<UniqueTextureId, unsigned int> textureIndicesMap;
ESM::ExteriorCellLocation cellLocation(cellX, cellY, worldspace);
const LandObject* land = getLand(cellLocation, cache);
for (int y = 0; y < blendmapSize; y++) for (int y = 0; y < blendmapSize; y++)
{ {
for (int x = 0; x < blendmapSize; x++) for (int x = 0; x < blendmapSize; x++)
{ {
UniqueTextureId id = getVtexIndexAt(cellX, cellY, x + rowStart, y + colStart, cache); UniqueTextureId id = getVtexIndexAt(cellLocation, land, x + rowStart, y + colStart, cache);
std::map<UniqueTextureId, unsigned int>::iterator found = textureIndicesMap.find(id); std::map<UniqueTextureId, unsigned int>::iterator found = textureIndicesMap.find(id);
if (found == textureIndicesMap.end()) if (found == textureIndicesMap.end())
{ {
@ -442,27 +471,29 @@ namespace ESMTerrain
blendmaps.clear(); // If a single texture fills the whole terrain, there is no need to blend blendmaps.clear(); // If a single texture fills the whole terrain, there is no need to blend
} }
float Storage::getHeightAt(const osg::Vec3f& worldPos) float Storage::getHeightAt(const osg::Vec3f& worldPos, ESM::RefId worldspace)
{ {
int cellX = static_cast<int>(std::floor(worldPos.x() / float(Constants::CellSizeInUnits))); const float cellSize = ESM::getCellSize(worldspace);
int cellY = static_cast<int>(std::floor(worldPos.y() / float(Constants::CellSizeInUnits))); int cellX = static_cast<int>(std::floor(worldPos.x() / cellSize));
int cellY = static_cast<int>(std::floor(worldPos.y() / cellSize));
osg::ref_ptr<const LandObject> land = getLand(cellX, cellY); osg::ref_ptr<const LandObject> land = getLand(ESM::ExteriorCellLocation(cellX, cellY, worldspace));
if (!land) if (!land)
return defaultHeight; return ESM::isEsm4Ext(worldspace) ? std::numeric_limits<float>::lowest() : defaultHeight;
const ESM::Land::LandData* data = land->getData(ESM::Land::DATA_VHGT); const ESM::LandData* data = land->getData(ESM::Land::DATA_VHGT);
if (!data) if (!data)
return defaultHeight; return defaultHeight;
const int landSize = data->getLandSize();
// Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition // Mostly lifted from Ogre::Terrain::getHeightAtTerrainPosition
// Normalized position in the cell // Normalized position in the cell
float nX = (worldPos.x() - (cellX * Constants::CellSizeInUnits)) / float(Constants::CellSizeInUnits); float nX = (worldPos.x() - (cellX * cellSize)) / cellSize;
float nY = (worldPos.y() - (cellY * Constants::CellSizeInUnits)) / float(Constants::CellSizeInUnits); float nY = (worldPos.y() - (cellY * cellSize)) / cellSize;
// get left / bottom points (rounded down) // get left / bottom points (rounded down)
float factor = ESM::Land::LAND_SIZE - 1.0f; float factor = landSize - 1.0f;
float invFactor = 1.0f / factor; float invFactor = 1.0f / factor;
int startX = static_cast<int>(nX * factor); int startX = static_cast<int>(nX * factor);
@ -470,8 +501,8 @@ namespace ESMTerrain
int endX = startX + 1; int endX = startX + 1;
int endY = startY + 1; int endY = startY + 1;
endX = std::min(endX, ESM::Land::LAND_SIZE - 1); endX = std::min(endX, landSize - 1);
endY = std::min(endY, ESM::Land::LAND_SIZE - 1); endY = std::min(endY, landSize - 1);
// now get points in terrain space (effectively rounding them to boundaries) // now get points in terrain space (effectively rounding them to boundaries)
float startXTS = startX * invFactor; float startXTS = startX * invFactor;
@ -491,10 +522,10 @@ namespace ESMTerrain
*/ */
// Build all 4 positions in normalized cell space, using point-sampled height // Build all 4 positions in normalized cell space, using point-sampled height
osg::Vec3f v0(startXTS, startYTS, getVertexHeight(data, startX, startY) / float(Constants::CellSizeInUnits)); osg::Vec3f v0(startXTS, startYTS, getVertexHeight(data, startX, startY) / cellSize);
osg::Vec3f v1(endXTS, startYTS, getVertexHeight(data, endX, startY) / float(Constants::CellSizeInUnits)); osg::Vec3f v1(endXTS, startYTS, getVertexHeight(data, endX, startY) / cellSize);
osg::Vec3f v2(endXTS, endYTS, getVertexHeight(data, endX, endY) / float(Constants::CellSizeInUnits)); osg::Vec3f v2(endXTS, endYTS, getVertexHeight(data, endX, endY) / cellSize);
osg::Vec3f v3(startXTS, endYTS, getVertexHeight(data, startX, endY) / float(Constants::CellSizeInUnits)); osg::Vec3f v3(startXTS, endYTS, getVertexHeight(data, startX, endY) / cellSize);
// define this plane in terrain space // define this plane in terrain space
osg::Plane plane; osg::Plane plane;
// FIXME: deal with differing triangle alignment // FIXME: deal with differing triangle alignment
@ -520,23 +551,22 @@ namespace ESMTerrain
*/ */
// Solve plane equation for z // Solve plane equation for z
return (-plane.getNormal().x() * nX - plane.getNormal().y() * nY - plane[3]) / plane.getNormal().z() return (-plane.getNormal().x() * nX - plane.getNormal().y() * nY - plane[3]) / plane.getNormal().z() * cellSize;
* Constants::CellSizeInUnits;
} }
const LandObject* Storage::getLand(int cellX, int cellY, LandCache& cache) const LandObject* Storage::getLand(ESM::ExteriorCellLocation cellLocation, LandCache& cache)
{ {
LandCache::Map::iterator found = cache.mMap.find(std::make_pair(cellX, cellY)); LandCache::Map::iterator found = cache.mMap.find(cellLocation);
if (found != cache.mMap.end()) if (found != cache.mMap.end())
return found->second; return found->second;
else else
{ {
found = cache.mMap.insert(std::make_pair(std::make_pair(cellX, cellY), getLand(cellX, cellY))).first; found = cache.mMap.insert(std::make_pair(cellLocation, getLand(cellLocation))).first;
return found->second; return found->second;
} }
} }
void Storage::adjustColor(int col, int row, const ESM::Land::LandData* heightData, osg::Vec4ub& color) const {} void Storage::adjustColor(int col, int row, const ESM::LandData* heightData, osg::Vec4ub& color) const {}
float Storage::getAlteredHeight(int col, int row) const float Storage::getAlteredHeight(int col, int row) const
{ {
@ -591,14 +621,14 @@ namespace ESMTerrain
return info; return info;
} }
float Storage::getCellWorldSize() float Storage::getCellWorldSize(ESM::RefId worldspace)
{ {
return static_cast<float>(ESM::Land::REAL_SIZE); return static_cast<float>(ESM::getCellSize(worldspace));
} }
int Storage::getCellVertices() int Storage::getCellVertices(ESM::RefId worldspace)
{ {
return ESM::Land::LAND_SIZE; return ESM::getLandSize(worldspace);
} }
int Storage::getBlendmapScale(float chunkSize) int Storage::getBlendmapScale(float chunkSize)

View file

@ -6,9 +6,21 @@
#include <components/terrain/storage.hpp> #include <components/terrain/storage.hpp>
#include <components/esm/esmterrain.hpp>
#include <components/esm/util.hpp>
#include <components/esm3/loadland.hpp> #include <components/esm3/loadland.hpp>
#include <components/esm3/loadltex.hpp> #include <components/esm3/loadltex.hpp>
namespace ESM4
{
struct Land;
}
namespace ESM
{
class LandData;
}
namespace VFS namespace VFS
{ {
class Manager; class Manager;
@ -26,24 +38,28 @@ namespace ESMTerrain
public: public:
LandObject(); LandObject();
LandObject(const ESM::Land* land, int loadFlags); LandObject(const ESM::Land* land, int loadFlags);
LandObject(const ESM4::Land* land, int loadFlags);
LandObject(const LandObject& copy, const osg::CopyOp& copyop); LandObject(const LandObject& copy, const osg::CopyOp& copyop);
virtual ~LandObject(); virtual ~LandObject();
META_Object(ESMTerrain, LandObject) META_Object(ESMTerrain, LandObject)
inline const ESM::Land::LandData* getData(int flags) const inline const ESM::LandData* getData(int flags) const
{ {
if ((mData.mDataLoaded & flags) != flags) if ((mData.mLoadFlags & flags) != flags)
return nullptr; return nullptr;
return &mData; return &mData;
} }
inline int getPlugin() const { return mLand->getPlugin(); } inline int getPlugin() const { return mLand->getPlugin(); }
inline int getLandSize() const { return mData.getLandSize(); }
inline int getRealSize() const { return mData.getSize(); }
private: private:
const ESM::Land* mLand; const ESM::Land* mLand;
int mLoadFlags;
ESM::Land::LandData mData; ESM::LandData mData;
}; };
/// @brief Feeds data from ESM terrain records (ESM::Land, ESM::LandTexture) /// @brief Feeds data from ESM terrain records (ESM::Land, ESM::LandTexture)
@ -56,10 +72,10 @@ namespace ESMTerrain
const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false); const std::string& specularMapPattern = "", bool autoUseSpecularMaps = false);
// Not implemented in this class, because we need different Store implementations for game and editor // Not implemented in this class, because we need different Store implementations for game and editor
virtual osg::ref_ptr<const LandObject> getLand(int cellX, int cellY) = 0; virtual osg::ref_ptr<const LandObject> getLand(ESM::ExteriorCellLocation cellLocation) = 0;
virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0; virtual const ESM::LandTexture* getLandTexture(int index, short plugin) = 0;
/// Get bounds of the whole terrain in cell units /// Get bounds of the whole terrain in cell units
void getBounds(float& minX, float& maxX, float& minY, float& maxY) override = 0; void getBounds(float& minX, float& maxX, float& minY, float& maxY, ESM::RefId worldspace) override = 0;
/// Get the minimum and maximum heights of a terrain region. /// Get the minimum and maximum heights of a terrain region.
/// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree. /// @note Will only be called for chunks with size = minBatchSize, i.e. leafs of the quad tree.
@ -69,7 +85,8 @@ namespace ESMTerrain
/// @param min min height will be stored here /// @param min min height will be stored here
/// @param max max height will be stored here /// @param max max height will be stored here
/// @return true if there was data available for this terrain chunk /// @return true if there was data available for this terrain chunk
bool getMinMaxHeights(float size, const osg::Vec2f& center, float& min, float& max) override; bool getMinMaxHeights(
float size, const osg::Vec2f& center, ESM::RefId worldspace, float& min, float& max) override;
/// Fill vertex buffers for a terrain chunk. /// Fill vertex buffers for a terrain chunk.
/// @note May be called from background threads. Make sure to only call thread-safe functions from here! /// @note May be called from background threads. Make sure to only call thread-safe functions from here!
@ -81,7 +98,7 @@ namespace ESMTerrain
/// @param positions buffer to write vertices /// @param positions buffer to write vertices
/// @param normals buffer to write vertex normals /// @param normals buffer to write vertex normals
/// @param colours buffer to write vertex colours /// @param colours buffer to write vertex colours
void fillVertexBuffers(int lodLevel, float size, const osg::Vec2f& center, void fillVertexBuffers(int lodLevel, float size, const osg::Vec2f& center, ESM::RefId worldspace,
osg::ref_ptr<osg::Vec3Array> positions, osg::ref_ptr<osg::Vec3Array> normals, osg::ref_ptr<osg::Vec3Array> positions, osg::ref_ptr<osg::Vec3Array> normals,
osg::ref_ptr<osg::Vec4ubArray> colours) override; osg::ref_ptr<osg::Vec4ubArray> colours) override;
@ -94,36 +111,40 @@ namespace ESMTerrain
/// @param blendmaps created blendmaps will be written here /// @param blendmaps created blendmaps will be written here
/// @param layerList names of the layer textures used will be written here /// @param layerList names of the layer textures used will be written here
void getBlendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, void getBlendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps,
std::vector<Terrain::LayerInfo>& layerList) override; std::vector<Terrain::LayerInfo>& layerList, ESM::RefId worldspace) override;
float getHeightAt(const osg::Vec3f& worldPos) override; float getHeightAt(const osg::Vec3f& worldPos, ESM::RefId worldspace) override;
/// Get the transformation factor for mapping cell units to world units. /// Get the transformation factor for mapping cell units to world units.
float getCellWorldSize() override; float getCellWorldSize(ESM::RefId worldspace) override;
/// Get the number of vertices on one side for each cell. Should be (power of two)+1 /// Get the number of vertices on one side for each cell. Should be (power of two)+1
int getCellVertices() override; int getCellVertices(ESM::RefId worldspace) override;
int getBlendmapScale(float chunkSize) override; int getBlendmapScale(float chunkSize) override;
float getVertexHeight(const ESM::Land::LandData* data, int x, int y) float getVertexHeight(const ESM::LandData* data, int x, int y)
{ {
assert(x < ESM::Land::LAND_SIZE); const int landSize = data->getLandSize();
assert(y < ESM::Land::LAND_SIZE); assert(x < landSize);
return data->mHeights[y * ESM::Land::LAND_SIZE + x]; assert(y < landSize);
return data->getHeights()[y * landSize + x];
} }
private: private:
const VFS::Manager* mVFS; const VFS::Manager* mVFS;
inline void fixNormal(osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache); inline void fixNormal(
inline void fixColour(osg::Vec4ub& colour, int cellX, int cellY, int col, int row, LandCache& cache); osg::Vec3f& normal, ESM::ExteriorCellLocation cellLocation, int col, int row, LandCache& cache);
inline void averageNormal(osg::Vec3f& normal, int cellX, int cellY, int col, int row, LandCache& cache); inline void fixColour(
osg::Vec4ub& colour, ESM::ExteriorCellLocation cellLocation, int col, int row, LandCache& cache);
inline void averageNormal(
osg::Vec3f& normal, ESM::ExteriorCellLocation cellLocation, int col, int row, LandCache& cache);
inline const LandObject* getLand(int cellX, int cellY, LandCache& cache); inline const LandObject* getLand(ESM::ExteriorCellLocation cellLocation, LandCache& cache);
virtual bool useAlteration() const { return false; } virtual bool useAlteration() const { return false; }
virtual void adjustColor(int col, int row, const ESM::Land::LandData* heightData, osg::Vec4ub& color) const; virtual void adjustColor(int col, int row, const ESM::LandData* heightData, osg::Vec4ub& color) const;
virtual float getAlteredHeight(int col, int row) const; virtual float getAlteredHeight(int col, int row) const;
// Since plugins can define new texture palettes, we need to know the plugin index too // Since plugins can define new texture palettes, we need to know the plugin index too
@ -131,7 +152,8 @@ namespace ESMTerrain
// pair <texture id, plugin id> // pair <texture id, plugin id>
typedef std::pair<short, short> UniqueTextureId; typedef std::pair<short, short> UniqueTextureId;
inline UniqueTextureId getVtexIndexAt(int cellX, int cellY, int x, int y, LandCache&); inline UniqueTextureId getVtexIndexAt(
ESM::ExteriorCellLocation cellLocation, const LandObject* land, int x, int y, LandCache&);
std::string getTextureName(UniqueTextureId id); std::string getTextureName(UniqueTextureId id);
std::map<std::string, Terrain::LayerInfo> mLayerInfoMap; std::map<std::string, Terrain::LayerInfo> mLayerInfoMap;

View file

@ -36,6 +36,7 @@
#include <components/esm/defs.hpp> #include <components/esm/defs.hpp>
#include <components/esm/refid.hpp> #include <components/esm/refid.hpp>
#include <components/esm/util.hpp>
#include <components/esm4/reader.hpp> #include <components/esm4/reader.hpp>
namespace ESM4 namespace ESM4
@ -107,7 +108,10 @@ namespace ESM4
int getGridX() const { return mX; } int getGridX() const { return mX; }
int getGridY() const { return mY; } int getGridY() const { return mY; }
bool isExterior() const { return !(mCellFlags & CELL_Interior); } bool isExterior() const { return !(mCellFlags & CELL_Interior); }
ESM::ExteriorCellLocation getExteriorCellLocation() const
{
return ESM::ExteriorCellLocation(mX, mY, isExterior() ? mParent : mId);
}
static float sInvalidWaterLevel; static float sInvalidWaterLevel;
}; };
} }

View file

@ -34,8 +34,10 @@
#include <iostream> // FIXME: debug only #include <iostream> // FIXME: debug only
#include <components/debug/debuglog.hpp>
#include "reader.hpp" #include "reader.hpp"
//#include "writer.hpp" // #include "writer.hpp"
// overlap north // overlap north
// //
@ -54,11 +56,12 @@
// //
void ESM4::Land::load(ESM4::Reader& reader) void ESM4::Land::load(ESM4::Reader& reader)
{ {
mFormId = reader.hdr().record.getFormId(); ESM::FormId formId = reader.hdr().record.getFormId();
reader.adjustFormId(mFormId); reader.adjustFormId(formId);
mId = ESM::RefId::formIdRefId(formId);
mFlags = reader.hdr().record.flags; mFlags = reader.hdr().record.flags;
mDataTypes = 0; mDataTypes = 0;
mCell = ESM::RefId::formIdRefId(reader.currCell());
TxtLayer layer; TxtLayer layer;
std::int8_t currentAddQuad = -1; // for VTXT following ATXT std::int8_t currentAddQuad = -1; // for VTXT following ATXT
@ -121,7 +124,7 @@ void ESM4::Land::load(ESM4::Reader& reader)
if (currentAddQuad != -1) if (currentAddQuad != -1)
{ {
// FIXME: sometimes there are no VTXT following an ATXT? Just add a dummy one for now // FIXME: sometimes there are no VTXT following an ATXT? Just add a dummy one for now
std::cout << "ESM4::Land VTXT empty layer " << (int)layer.texture.layerIndex << std::endl; Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << (int)layer.texture.layerIndex;
mTextures[currentAddQuad].layers.push_back(layer); mTextures[currentAddQuad].layers.push_back(layer);
} }
reader.get(layer.texture); reader.get(layer.texture);
@ -207,8 +210,8 @@ void ESM4::Land::load(ESM4::Reader& reader)
if (currentAddQuad != -1) if (currentAddQuad != -1)
{ {
// FIXME: not sure if it happens here as well // FIXME: not sure if it happens here as well
std::cout << "ESM4::Land VTXT empty layer " << (int)layer.texture.layerIndex << " quad " Log(Debug::Verbose) << "ESM4::Land VTXT empty layer " << (int)layer.texture.layerIndex << " quad "
<< (int)layer.texture.quadrant << std::endl; << (int)layer.texture.quadrant;
mTextures[currentAddQuad].layers.push_back(layer); mTextures[currentAddQuad].layers.push_back(layer);
} }

View file

@ -33,6 +33,9 @@
#include "formid.hpp" #include "formid.hpp"
#include <components/esm/defs.hpp>
#include <components/esm/refid.hpp>
namespace ESM4 namespace ESM4
{ {
class Reader; class Reader;
@ -109,7 +112,7 @@ namespace ESM4
std::vector<TxtLayer> layers; std::vector<TxtLayer> layers;
}; };
FormId mFormId; // from the header ESM::RefId mId; // from the header
std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::uint32_t mFlags; // from the header, see enum type RecordFlag for details
std::uint32_t mLandFlags; // from DATA subrecord std::uint32_t mLandFlags; // from DATA subrecord
@ -117,16 +120,20 @@ namespace ESM4
// FIXME: lazy loading not yet implemented // FIXME: lazy loading not yet implemented
int mDataTypes; // which data types are loaded 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 signed char mVertNorm[VERTS_PER_SIDE * VERTS_PER_SIDE * 3]; // from VNML subrecord
signed char mVertColr[VERTS_PER_SIDE * VERTS_PER_SIDE * 3]; // from VCLR subrecord unsigned char mVertColr[VERTS_PER_SIDE * VERTS_PER_SIDE * 3]; // from VCLR subrecord
VHGT mHeightMap; VHGT mHeightMap;
Texture mTextures[4]; // 0 = bottom left, 1 = bottom right, 2 = top left, 3 = top right Texture mTextures[4]; // 0 = bottom left, 1 = bottom right, 2 = top left, 3 = top right
std::vector<FormId> mIds; // land texture (LTEX) formids std::vector<FormId> mIds; // land texture (LTEX) formids
ESM::RefId mCell;
void load(Reader& reader); void load(Reader& reader);
Land() = default;
// void save(Writer& writer) const; // void save(Writer& writer) const;
// void blank(); // void blank();
static constexpr ESM::RecNameInts sRecordId = ESM::REC_LAND4;
}; };
} }

View file

@ -27,10 +27,10 @@
#include "loadwrld.hpp" #include "loadwrld.hpp"
#include <stdexcept> #include <stdexcept>
//#include <iostream> // FIXME: debug only // #include <iostream> // FIXME: debug only
#include "reader.hpp" #include "reader.hpp"
//#include "writer.hpp" // #include "writer.hpp"
void ESM4::World::load(ESM4::Reader& reader) void ESM4::World::load(ESM4::Reader& reader)
{ {

View file

@ -23,7 +23,7 @@ namespace Terrain
} }
osg::ref_ptr<osg::Group> CellBorder::createBorderGeometry(float x, float y, float size, Terrain::Storage* terrain, osg::ref_ptr<osg::Group> CellBorder::createBorderGeometry(float x, float y, float size, Terrain::Storage* terrain,
Resource::SceneManager* sceneManager, int mask, float offset, osg::Vec4f color) Resource::SceneManager* sceneManager, int mask, ESM::RefId worldspace, float offset, osg::Vec4f color)
{ {
const int cellSize = ESM::Land::REAL_SIZE; const int cellSize = ESM::Land::REAL_SIZE;
const int borderSegments = 40; const int borderSegments = 40;
@ -45,7 +45,7 @@ namespace Terrain
: osg::Vec3(size, (i - borderSegments) * borderStep, 0.0f); : osg::Vec3(size, (i - borderSegments) * borderStep, 0.0f);
pos += cellCorner; pos += cellCorner;
pos += osg::Vec3f(0, 0, terrain->getHeightAt(pos) + offset); pos += osg::Vec3f(0, 0, terrain->getHeightAt(pos, worldspace) + offset);
vertices->push_back(pos); vertices->push_back(pos);
@ -83,7 +83,8 @@ namespace Terrain
void CellBorder::createCellBorderGeometry(int x, int y) void CellBorder::createCellBorderGeometry(int x, int y)
{ {
auto borderGroup = createBorderGeometry(x, y, 1.f, mWorld->getStorage(), mSceneManager, mBorderMask); auto borderGroup = createBorderGeometry(
x, y, 1.f, mWorld->getStorage(), mSceneManager, mBorderMask, mWorld->getWorldspace());
mRoot->addChild(borderGroup); mRoot->addChild(borderGroup);
mCellBorderNodes[std::make_pair(x, y)] = borderGroup; mCellBorderNodes[std::make_pair(x, y)] = borderGroup;

View file

@ -4,6 +4,8 @@
#include <map> #include <map>
#include <osg/Group> #include <osg/Group>
#include <components/esm/refid.hpp>
namespace Resource namespace Resource
{ {
class SceneManager; class SceneManager;
@ -33,7 +35,8 @@ namespace Terrain
void destroyCellBorderGeometry(); void destroyCellBorderGeometry();
static osg::ref_ptr<osg::Group> createBorderGeometry(float x, float y, float size, Storage* terrain, static osg::ref_ptr<osg::Group> createBorderGeometry(float x, float y, float size, Storage* terrain,
Resource::SceneManager* sceneManager, int mask, float offset = 10.0, osg::Vec4f color = { 1, 1, 0, 0 }); Resource::SceneManager* sceneManager, int mask, ESM::RefId worldspace, float offset = 10.0,
osg::Vec4f color = { 1, 1, 0, 0 });
protected: protected:
Terrain::World* mWorld; Terrain::World* mWorld;

View file

@ -20,8 +20,9 @@ namespace Terrain
{ {
ChunkManager::ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, ChunkManager::ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager,
CompositeMapRenderer* renderer) CompositeMapRenderer* renderer, ESM::RefId worldspace)
: GenericResourceManager<ChunkId>(nullptr) : GenericResourceManager<ChunkId>(nullptr)
, QuadTreeWorld::ChunkManager(worldspace)
, mStorage(storage) , mStorage(storage)
, mSceneManager(sceneMgr) , mSceneManager(sceneMgr)
, mTextureManager(textureManager) , mTextureManager(textureManager)
@ -153,7 +154,7 @@ namespace Terrain
{ {
std::vector<LayerInfo> layerList; std::vector<LayerInfo> layerList;
std::vector<osg::ref_ptr<osg::Image>> blendmaps; std::vector<osg::ref_ptr<osg::Image>> blendmaps;
mStorage->getBlendmaps(chunkSize, chunkCenter, blendmaps, layerList); mStorage->getBlendmaps(chunkSize, chunkCenter, blendmaps, layerList, mWorldspace);
bool useShaders = mSceneManager->getForceShaders(); bool useShaders = mSceneManager->getForceShaders();
if (!mSceneManager->getClampLighting()) if (!mSceneManager->getClampLighting())
@ -212,7 +213,7 @@ namespace Terrain
osg::ref_ptr<osg::Vec4ubArray> colors(new osg::Vec4ubArray); osg::ref_ptr<osg::Vec4ubArray> colors(new osg::Vec4ubArray);
colors->setNormalize(true); colors->setNormalize(true);
mStorage->fillVertexBuffers(lod, chunkSize, chunkCenter, positions, normals, colors); mStorage->fillVertexBuffers(lod, chunkSize, chunkCenter, mWorldspace, positions, normals, colors);
osg::ref_ptr<osg::VertexBufferObject> vbo(new osg::VertexBufferObject); osg::ref_ptr<osg::VertexBufferObject> vbo(new osg::VertexBufferObject);
positions->setVertexBufferObject(vbo); positions->setVertexBufferObject(vbo);
@ -249,7 +250,7 @@ namespace Terrain
if (chunkSize <= 1.f) if (chunkSize <= 1.f)
geometry->setLightListCallback(new SceneUtil::LightListCallback); geometry->setLightListCallback(new SceneUtil::LightListCallback);
unsigned int numVerts = (mStorage->getCellVertices() - 1) * chunkSize / (1 << lod) + 1; unsigned int numVerts = (mStorage->getCellVertices(mWorldspace) - 1) * chunkSize / (1 << lod) + 1;
geometry->addPrimitiveSet(mBufferCache.getIndexBuffer(numVerts, lodFlags)); geometry->addPrimitiveSet(mBufferCache.getIndexBuffer(numVerts, lodFlags));
@ -299,7 +300,7 @@ namespace Terrain
} }
} }
geometry->setupWaterBoundingBox(-1, chunkSize * mStorage->getCellWorldSize() / numVerts); geometry->setupWaterBoundingBox(-1, chunkSize * mStorage->getCellWorldSize(mWorldspace) / numVerts);
if (!templateGeometry && compile && mSceneManager->getIncrementalCompileOperation()) if (!templateGeometry && compile && mSceneManager->getIncrementalCompileOperation())
{ {

View file

@ -35,7 +35,7 @@ namespace Terrain
{ {
public: public:
ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager,
CompositeMapRenderer* renderer); CompositeMapRenderer* renderer, ESM::RefId worldspace);
osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags,
bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override; bool activeGrid, const osg::Vec3f& viewPoint, bool compile) override;

View file

@ -39,13 +39,14 @@ namespace Terrain
class DefaultLodCallback : public LodCallback class DefaultLodCallback : public LodCallback
{ {
public: public:
DefaultLodCallback( DefaultLodCallback(float factor, float minSize, float viewDistance, const osg::Vec4i& grid, int cellSizeInUnits,
float factor, float minSize, float viewDistance, const osg::Vec4i& grid, float distanceModifier = 0.f) float distanceModifier = 0.f)
: mFactor(factor) : mFactor(factor)
, mMinSize(minSize) , mMinSize(minSize)
, mViewDistance(viewDistance) , mViewDistance(viewDistance)
, mActiveGrid(grid) , mActiveGrid(grid)
, mDistanceModifier(distanceModifier) , mDistanceModifier(distanceModifier)
, mCellSizeInUnits(cellSizeInUnits)
{ {
} }
@ -69,7 +70,8 @@ namespace Terrain
dist = std::max(0.f, dist + mDistanceModifier); dist = std::max(0.f, dist + mDistanceModifier);
if (dist > mViewDistance && !activeGrid) // for Scene<->ObjectPaging sync the activegrid must remain loaded if (dist > mViewDistance && !activeGrid) // for Scene<->ObjectPaging sync the activegrid must remain loaded
return StopTraversal; return StopTraversal;
return getNativeLodLevel(node, mMinSize) <= convertDistanceToLodLevel(dist, mMinSize, mFactor) return getNativeLodLevel(node, mMinSize)
<= convertDistanceToLodLevel(dist, mMinSize, mFactor, mCellSizeInUnits)
? StopTraversalAndUse ? StopTraversalAndUse
: Deeper; : Deeper;
} }
@ -77,9 +79,9 @@ namespace Terrain
{ {
return Log2(static_cast<unsigned int>(node->getSize() / minSize)); return Log2(static_cast<unsigned int>(node->getSize() / minSize));
} }
static unsigned int convertDistanceToLodLevel(float dist, float minSize, float factor) static unsigned int convertDistanceToLodLevel(float dist, float minSize, float factor, int cellSize)
{ {
return Log2(static_cast<unsigned int>(dist / (Constants::CellSizeInUnits * minSize * factor))); return Log2(static_cast<unsigned int>(dist / (cellSize * minSize * factor)));
} }
private: private:
@ -88,6 +90,7 @@ namespace Terrain
float mViewDistance; float mViewDistance;
osg::Vec4i mActiveGrid; osg::Vec4i mActiveGrid;
float mDistanceModifier; float mDistanceModifier;
int mCellSizeInUnits;
}; };
class RootNode : public QuadTreeNode class RootNode : public QuadTreeNode
@ -117,19 +120,20 @@ namespace Terrain
class QuadTreeBuilder class QuadTreeBuilder
{ {
public: public:
QuadTreeBuilder(Terrain::Storage* storage, float minSize) QuadTreeBuilder(Terrain::Storage* storage, float minSize, ESM::RefId worldspace)
: mStorage(storage) : mStorage(storage)
, mMinX(0.f) , mMinX(0.f)
, mMaxX(0.f) , mMaxX(0.f)
, mMinY(0.f) , mMinY(0.f)
, mMaxY(0.f) , mMaxY(0.f)
, mMinSize(minSize) , mMinSize(minSize)
, mWorldspace(worldspace)
{ {
} }
void build() void build()
{ {
mStorage->getBounds(mMinX, mMaxX, mMinY, mMaxY); mStorage->getBounds(mMinX, mMaxX, mMinY, mMaxY, mWorldspace);
int origSizeX = static_cast<int>(mMaxX - mMinX); int origSizeX = static_cast<int>(mMaxX - mMinX);
int origSizeY = static_cast<int>(mMaxY - mMinY); int origSizeY = static_cast<int>(mMaxY - mMinY);
@ -144,7 +148,7 @@ namespace Terrain
addChildren(mRootNode); addChildren(mRootNode);
mRootNode->initNeighbours(); mRootNode->initNeighbours();
float cellWorldSize = mStorage->getCellWorldSize(); float cellWorldSize = mStorage->getCellWorldSize(mWorldspace);
mRootNode->setInitialBound( mRootNode->setInitialBound(
osg::BoundingSphere(osg::BoundingBox(osg::Vec3(mMinX * cellWorldSize, mMinY * cellWorldSize, 0), osg::BoundingSphere(osg::BoundingBox(osg::Vec3(mMinX * cellWorldSize, mMinY * cellWorldSize, 0),
osg::Vec3(mMaxX * cellWorldSize, mMaxY * cellWorldSize, 0)))); osg::Vec3(mMaxX * cellWorldSize, mMaxY * cellWorldSize, 0))));
@ -206,7 +210,8 @@ namespace Terrain
// Do not add child nodes for default cells without data. // Do not add child nodes for default cells without data.
// size = 1 means that the single shape covers the whole cell. // size = 1 means that the single shape covers the whole cell.
if (node->getSize() == 1 && !mStorage->hasData(center.x() - 0.5, center.y() - 0.5)) if (node->getSize() == 1
&& !mStorage->hasData(ESM::ExteriorCellLocation(center.x() - 0.5, center.y() - 0.5, mWorldspace)))
return node; return node;
if (node->getSize() <= mMinSize) if (node->getSize() <= mMinSize)
@ -216,7 +221,7 @@ namespace Terrain
// height data here. // height data here.
constexpr float minZ = -std::numeric_limits<float>::max(); constexpr float minZ = -std::numeric_limits<float>::max();
constexpr float maxZ = std::numeric_limits<float>::max(); constexpr float maxZ = std::numeric_limits<float>::max();
float cellWorldSize = mStorage->getCellWorldSize(); float cellWorldSize = mStorage->getCellWorldSize(mWorldspace);
osg::BoundingBox boundingBox( osg::BoundingBox boundingBox(
osg::Vec3f((center.x() - halfSize) * cellWorldSize, (center.y() - halfSize) * cellWorldSize, minZ), osg::Vec3f((center.x() - halfSize) * cellWorldSize, (center.y() - halfSize) * cellWorldSize, minZ),
osg::Vec3f((center.x() + halfSize) * cellWorldSize, (center.y() + halfSize) * cellWorldSize, maxZ)); osg::Vec3f((center.x() + halfSize) * cellWorldSize, (center.y() + halfSize) * cellWorldSize, maxZ));
@ -239,13 +244,16 @@ namespace Terrain
float mMinSize; float mMinSize;
osg::ref_ptr<RootNode> mRootNode; osg::ref_ptr<RootNode> mRootNode;
ESM::RefId mWorldspace;
}; };
class DebugChunkManager : public QuadTreeWorld::ChunkManager class DebugChunkManager : public QuadTreeWorld::ChunkManager
{ {
public: public:
DebugChunkManager(Resource::SceneManager* sceneManager, Storage* storage, unsigned int nodeMask) DebugChunkManager(
: mSceneManager(sceneManager) Resource::SceneManager* sceneManager, Storage* storage, unsigned int nodeMask, ESM::RefId worldspace)
: QuadTreeWorld::ChunkManager(worldspace)
, mSceneManager(sceneManager)
, mStorage(storage) , mStorage(storage)
, mNodeMask(nodeMask) , mNodeMask(nodeMask)
{ {
@ -255,9 +263,9 @@ namespace Terrain
{ {
osg::Vec3f center = { chunkCenter.x(), chunkCenter.y(), 0 }; osg::Vec3f center = { chunkCenter.x(), chunkCenter.y(), 0 };
auto chunkBorder = CellBorder::createBorderGeometry(center.x() - size / 2.f, center.y() - size / 2.f, size, auto chunkBorder = CellBorder::createBorderGeometry(center.x() - size / 2.f, center.y() - size / 2.f, size,
mStorage, mSceneManager, mNodeMask, 5.f, { 1, 0, 0, 0 }); mStorage, mSceneManager, mNodeMask, mWorldspace, 5.f, { 1, 0, 0, 0 });
osg::ref_ptr<SceneUtil::PositionAttitudeTransform> pat = new SceneUtil::PositionAttitudeTransform; osg::ref_ptr<SceneUtil::PositionAttitudeTransform> pat = new SceneUtil::PositionAttitudeTransform;
pat->setPosition(-center * Constants::CellSizeInUnits); pat->setPosition(-center * ESM::getCellSize(mWorldspace));
pat->addChild(chunkBorder); pat->addChild(chunkBorder);
return pat; return pat;
} }
@ -272,14 +280,14 @@ namespace Terrain
QuadTreeWorld::QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, QuadTreeWorld::QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem,
Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask,
int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize, int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize,
bool debugChunks) bool debugChunks, ESM::RefId worldspace)
: TerrainGrid(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask) : TerrainGrid(parent, compileRoot, resourceSystem, storage, nodeMask, worldspace, preCompileMask, borderMask)
, mViewDataMap(new ViewDataMap) , mViewDataMap(new ViewDataMap)
, mQuadTreeBuilt(false) , mQuadTreeBuilt(false)
, mLodFactor(lodFactor) , mLodFactor(lodFactor)
, mVertexLodMod(vertexLodMod) , mVertexLodMod(vertexLodMod)
, mViewDistance(std::numeric_limits<float>::max()) , mViewDistance(std::numeric_limits<float>::max())
, mMinSize(1 / 8.f) , mMinSize(ESM::isEsm4Ext(worldspace) ? 1 / 4.f : 1 / 8.f)
, mDebugTerrainChunks(debugChunks) , mDebugTerrainChunks(debugChunks)
{ {
mChunkManager->setCompositeMapSize(compMapResolution); mChunkManager->setCompositeMapSize(compMapResolution);
@ -289,8 +297,8 @@ namespace Terrain
if (mDebugTerrainChunks) if (mDebugTerrainChunks)
{ {
mDebugChunkManager mDebugChunkManager = std::make_unique<DebugChunkManager>(
= std::make_unique<DebugChunkManager>(mResourceSystem->getSceneManager(), mStorage, borderMask); mResourceSystem->getSceneManager(), mStorage, borderMask, mWorldspace);
addChunkManager(mDebugChunkManager.get()); addChunkManager(mDebugChunkManager.get());
} }
} }
@ -466,11 +474,12 @@ namespace Terrain
if (needsUpdate) if (needsUpdate)
{ {
vd->reset(); vd->reset();
DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, mActiveGrid); DefaultLodCallback lodCallback(
mLodFactor, mMinSize, mViewDistance, mActiveGrid, ESM::getCellSize(mWorldspace));
mRootNode->traverseNodes(vd, viewPoint, &lodCallback); mRootNode->traverseNodes(vd, viewPoint, &lodCallback);
} }
const float cellWorldSize = mStorage->getCellWorldSize(); const float cellWorldSize = ESM::getCellSize(mWorldspace);
for (unsigned int i = 0; i < vd->getNumEntries(); ++i) for (unsigned int i = 0; i < vd->getNumEntries(); ++i)
{ {
@ -481,7 +490,7 @@ namespace Terrain
if (mHeightCullCallback && isCullVisitor) if (mHeightCullCallback && isCullVisitor)
updateWaterCullingView(mHeightCullCallback, vd, static_cast<osgUtil::CullVisitor*>(&nv), updateWaterCullingView(mHeightCullCallback, vd, static_cast<osgUtil::CullVisitor*>(&nv),
mStorage->getCellWorldSize(), !isGridEmpty()); mStorage->getCellWorldSize(mWorldspace), !isGridEmpty());
vd->resetChanged(); vd->resetChanged();
@ -499,7 +508,7 @@ namespace Terrain
if (mQuadTreeBuilt) if (mQuadTreeBuilt)
return; return;
QuadTreeBuilder builder(mStorage, mMinSize); QuadTreeBuilder builder(mStorage, mMinSize, mWorldspace);
builder.build(); builder.build();
mRootNode = builder.getRootNode(); mRootNode = builder.getRootNode();
@ -529,7 +538,7 @@ namespace Terrain
std::atomic<bool>& abort, Loading::Reporter& reporter) std::atomic<bool>& abort, Loading::Reporter& reporter)
{ {
ensureQuadTreeBuilt(); ensureQuadTreeBuilt();
const float cellWorldSize = mStorage->getCellWorldSize(); const float cellWorldSize = mStorage->getCellWorldSize(mWorldspace);
ViewData* vd = static_cast<ViewData*>(view); ViewData* vd = static_cast<ViewData*>(view);
vd->setViewPoint(viewPoint); vd->setViewPoint(viewPoint);
@ -544,7 +553,7 @@ namespace Terrain
distanceModifier = 1024; distanceModifier = 1024;
else if (pass == 2) else if (pass == 2)
distanceModifier = -1024; distanceModifier = -1024;
DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid, distanceModifier); DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid, cellWorldSize, distanceModifier);
mRootNode->traverseNodes(vd, viewPoint, &lodCallback); mRootNode->traverseNodes(vd, viewPoint, &lodCallback);
if (pass == 0) if (pass == 0)
@ -579,7 +588,7 @@ namespace Terrain
{ {
// fallback behavior only for undefined cells (every other is already handled in quadtree) // fallback behavior only for undefined cells (every other is already handled in quadtree)
float dummy; float dummy;
if (mChunkManager && !mStorage->getMinMaxHeights(1, osg::Vec2f(x + 0.5, y + 0.5), dummy, dummy)) if (mChunkManager && !mStorage->getMinMaxHeights(1, osg::Vec2f(x + 0.5, y + 0.5), mWorldspace, dummy, dummy))
TerrainGrid::loadCell(x, y); TerrainGrid::loadCell(x, y);
else else
World::loadCell(x, y); World::loadCell(x, y);
@ -589,7 +598,7 @@ namespace Terrain
{ {
// fallback behavior only for undefined cells (every other is already handled in quadtree) // fallback behavior only for undefined cells (every other is already handled in quadtree)
float dummy; float dummy;
if (mChunkManager && !mStorage->getMinMaxHeights(1, osg::Vec2f(x + 0.5, y + 0.5), dummy, dummy)) if (mChunkManager && !mStorage->getMinMaxHeights(1, osg::Vec2f(x + 0.5, y + 0.5), mWorldspace, dummy, dummy))
TerrainGrid::unloadCell(x, y); TerrainGrid::unloadCell(x, y);
else else
World::unloadCell(x, y); World::unloadCell(x, y);
@ -600,8 +609,9 @@ namespace Terrain
mChunkManagers.push_back(m); mChunkManagers.push_back(m);
mTerrainRoot->setNodeMask(mTerrainRoot->getNodeMask() | m->getNodeMask()); mTerrainRoot->setNodeMask(mTerrainRoot->getNodeMask() | m->getNodeMask());
if (m->getViewDistance()) if (m->getViewDistance())
m->setMaxLodLevel(DefaultLodCallback::convertDistanceToLodLevel( m->setMaxLodLevel(
m->getViewDistance() + mViewDataMap->getReuseDistance(), mMinSize, mLodFactor)); DefaultLodCallback::convertDistanceToLodLevel(m->getViewDistance() + mViewDataMap->getReuseDistance(),
mMinSize, ESM::getCellSize(mWorldspace), mLodFactor));
} }
void QuadTreeWorld::rebuildViews() void QuadTreeWorld::rebuildViews()

View file

@ -7,6 +7,8 @@
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <components/esm/refid.hpp>
namespace osg namespace osg
{ {
class NodeVisitor; class NodeVisitor;
@ -31,7 +33,7 @@ namespace Terrain
QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem,
Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask,
int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize,
bool debugChunks); bool debugChunks, ESM::RefId worldspace);
~QuadTreeWorld(); ~QuadTreeWorld();
@ -58,6 +60,12 @@ namespace Terrain
{ {
public: public:
virtual ~ChunkManager() {} virtual ~ChunkManager() {}
ChunkManager() = default;
ChunkManager(ESM::RefId worldspace)
: ChunkManager()
{
mWorldspace = worldspace;
}
virtual osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod, virtual osg::ref_ptr<osg::Node> getChunk(float size, const osg::Vec2f& center, unsigned char lod,
unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile)
= 0; = 0;
@ -70,6 +78,9 @@ namespace Terrain
unsigned int getMaxLodLevel() const { return mMaxLodLevel; } unsigned int getMaxLodLevel() const { return mMaxLodLevel; }
void setMaxLodLevel(unsigned int level) { mMaxLodLevel = level; } void setMaxLodLevel(unsigned int level) { mMaxLodLevel = level; }
protected:
ESM::RefId mWorldspace = ESM::RefId();
private: private:
float mViewDistance = 0.f; float mViewDistance = 0.f;
unsigned int mMaxLodLevel = ~0u; unsigned int mMaxLodLevel = ~0u;

View file

@ -8,6 +8,9 @@
#include <osg/Vec3f> #include <osg/Vec3f>
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <components/esm/refid.hpp>
#include <components/esm/util.hpp>
#include "defs.hpp" #include "defs.hpp"
namespace osg namespace osg
@ -26,14 +29,15 @@ namespace Terrain
public: public:
/// Get bounds of the whole terrain in cell units /// Get bounds of the whole terrain in cell units
virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY) = 0; virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY, ESM::RefId worldspace) = 0;
/// Return true if there is land data for this cell /// Return true if there is land data for this cell
/// May be overriden for a faster implementation /// May be overriden for a faster implementation
virtual bool hasData(int cellX, int cellY) virtual bool hasData(ESM::ExteriorCellLocation cellLocation)
{ {
float dummy; float dummy;
return getMinMaxHeights(1, osg::Vec2f(cellX + 0.5, cellY + 0.5), dummy, dummy); return getMinMaxHeights(
1, osg::Vec2f(cellLocation.mX + 0.5, cellLocation.mY + 0.5), cellLocation.mWorldspace, dummy, dummy);
} }
/// Get the minimum and maximum heights of a terrain region. /// Get the minimum and maximum heights of a terrain region.
@ -44,7 +48,9 @@ namespace Terrain
/// @param min min height will be stored here /// @param min min height will be stored here
/// @param max max height will be stored here /// @param max max height will be stored here
/// @return true if there was data available for this terrain chunk /// @return true if there was data available for this terrain chunk
virtual bool getMinMaxHeights(float size, const osg::Vec2f& center, float& min, float& max) = 0; virtual bool getMinMaxHeights(
float size, const osg::Vec2f& center, ESM::RefId worldspace, float& min, float& max)
= 0;
/// Fill vertex buffers for a terrain chunk. /// Fill vertex buffers for a terrain chunk.
/// @note May be called from background threads. Make sure to only call thread-safe functions from here! /// @note May be called from background threads. Make sure to only call thread-safe functions from here!
@ -57,7 +63,7 @@ namespace Terrain
/// @param positions buffer to write vertices /// @param positions buffer to write vertices
/// @param normals buffer to write vertex normals /// @param normals buffer to write vertex normals
/// @param colours buffer to write vertex colours /// @param colours buffer to write vertex colours
virtual void fillVertexBuffers(int lodLevel, float size, const osg::Vec2f& center, virtual void fillVertexBuffers(int lodLevel, float size, const osg::Vec2f& center, ESM::RefId worldspace,
osg::ref_ptr<osg::Vec3Array> positions, osg::ref_ptr<osg::Vec3Array> normals, osg::ref_ptr<osg::Vec3Array> positions, osg::ref_ptr<osg::Vec3Array> normals,
osg::ref_ptr<osg::Vec4ubArray> colours) osg::ref_ptr<osg::Vec4ubArray> colours)
= 0; = 0;
@ -71,17 +77,17 @@ namespace Terrain
/// @param chunkCenter center of the chunk in cell units /// @param chunkCenter center of the chunk in cell units
/// @param blendmaps created blendmaps will be written here /// @param blendmaps created blendmaps will be written here
/// @param layerList names of the layer textures used will be written here /// @param layerList names of the layer textures used will be written here
virtual void getBlendmaps( virtual void getBlendmaps(float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps,
float chunkSize, const osg::Vec2f& chunkCenter, ImageVector& blendmaps, std::vector<LayerInfo>& layerList) std::vector<LayerInfo>& layerList, ESM::RefId worldspace)
= 0; = 0;
virtual float getHeightAt(const osg::Vec3f& worldPos) = 0; virtual float getHeightAt(const osg::Vec3f& worldPos, ESM::RefId worldspace) = 0;
/// Get the transformation factor for mapping cell units to world units. /// Get the transformation factor for mapping cell units to world units.
virtual float getCellWorldSize() = 0; virtual float getCellWorldSize(ESM::RefId worldspace) = 0;
/// Get the number of vertices on one side for each cell. Should be (power of two)+1 /// Get the number of vertices on one side for each cell. Should be (power of two)+1
virtual int getCellVertices() = 0; virtual int getCellVertices(ESM::RefId worldspace) = 0;
virtual int getBlendmapScale(float chunkSize) = 0; virtual int getBlendmapScale(float chunkSize) = 0;
}; };

View file

@ -23,14 +23,15 @@ namespace Terrain
}; };
TerrainGrid::TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, TerrainGrid::TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem,
Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask) Storage* storage, unsigned int nodeMask, ESM::RefId worldspace, unsigned int preCompileMask,
: Terrain::World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask) unsigned int borderMask)
: Terrain::World(parent, compileRoot, resourceSystem, storage, nodeMask, preCompileMask, borderMask, worldspace)
, mNumSplits(4) , mNumSplits(4)
{ {
} }
TerrainGrid::TerrainGrid(osg::Group* parent, Storage* storage, unsigned int nodeMask) TerrainGrid::TerrainGrid(osg::Group* parent, Storage* storage, ESM::RefId worldspace, unsigned int nodeMask)
: Terrain::World(parent, storage, nodeMask) : Terrain::World(parent, storage, nodeMask, worldspace)
, mNumSplits(4) , mNumSplits(4)
{ {
} }
@ -73,7 +74,7 @@ namespace Terrain
if (!node) if (!node)
return nullptr; return nullptr;
const float cellWorldSize = mStorage->getCellWorldSize(); const float cellWorldSize = mStorage->getCellWorldSize(mWorldspace);
osg::ref_ptr<SceneUtil::PositionAttitudeTransform> pat = new SceneUtil::PositionAttitudeTransform; osg::ref_ptr<SceneUtil::PositionAttitudeTransform> pat = new SceneUtil::PositionAttitudeTransform;
pat->setPosition(osg::Vec3f(chunkCenter.x() * cellWorldSize, chunkCenter.y() * cellWorldSize, 0.f)); pat->setPosition(osg::Vec3f(chunkCenter.x() * cellWorldSize, chunkCenter.y() * cellWorldSize, 0.f));
pat->addChild(node); pat->addChild(node);

View file

@ -27,8 +27,9 @@ namespace Terrain
{ {
public: public:
TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, TerrainGrid(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem,
Storage* storage, unsigned int nodeMask, unsigned int preCompileMask = ~0u, unsigned int borderMask = 0); Storage* storage, unsigned int nodeMask, ESM::RefId worldspace, unsigned int preCompileMask = ~0u,
TerrainGrid(osg::Group* parent, Storage* storage, unsigned int nodeMask = ~0u); unsigned int borderMask = 0);
TerrainGrid(osg::Group* parent, Storage* storage, ESM::RefId worldspace, unsigned int nodeMask = ~0u);
~TerrainGrid(); ~TerrainGrid();
void cacheCell(View* view, int x, int y) override; void cacheCell(View* view, int x, int y) override;

View file

@ -16,12 +16,14 @@ namespace Terrain
{ {
World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, World::World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem,
Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask) Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask,
ESM::RefId worldspace)
: mStorage(storage) : mStorage(storage)
, mParent(parent) , mParent(parent)
, mResourceSystem(resourceSystem) , mResourceSystem(resourceSystem)
, mBorderVisible(false) , mBorderVisible(false)
, mHeightCullCallback(new HeightCullCallback) , mHeightCullCallback(new HeightCullCallback)
, mWorldspace(worldspace)
{ {
mTerrainRoot = new osg::Group; mTerrainRoot = new osg::Group;
mTerrainRoot->setNodeMask(nodeMask); mTerrainRoot->setNodeMask(nodeMask);
@ -45,7 +47,7 @@ namespace Terrain
mTextureManager = std::make_unique<TextureManager>(mResourceSystem->getSceneManager()); mTextureManager = std::make_unique<TextureManager>(mResourceSystem->getSceneManager());
mChunkManager = std::make_unique<ChunkManager>( mChunkManager = std::make_unique<ChunkManager>(
mStorage, mResourceSystem->getSceneManager(), mTextureManager.get(), mCompositeMapRenderer); mStorage, mResourceSystem->getSceneManager(), mTextureManager.get(), mCompositeMapRenderer, mWorldspace);
mChunkManager->setNodeMask(nodeMask); mChunkManager->setNodeMask(nodeMask);
mCellBorder mCellBorder
= std::make_unique<CellBorder>(this, mTerrainRoot.get(), borderMask, mResourceSystem->getSceneManager()); = std::make_unique<CellBorder>(this, mTerrainRoot.get(), borderMask, mResourceSystem->getSceneManager());
@ -54,7 +56,7 @@ namespace Terrain
mResourceSystem->addResourceManager(mTextureManager.get()); mResourceSystem->addResourceManager(mTextureManager.get());
} }
World::World(osg::Group* parent, Storage* storage, unsigned int nodeMask) World::World(osg::Group* parent, Storage* storage, unsigned int nodeMask, ESM::RefId worldspace)
: mStorage(storage) : mStorage(storage)
, mParent(parent) , mParent(parent)
, mCompositeMapCamera(nullptr) , mCompositeMapCamera(nullptr)
@ -65,6 +67,7 @@ namespace Terrain
, mCellBorder(nullptr) , mCellBorder(nullptr)
, mBorderVisible(false) , mBorderVisible(false)
, mHeightCullCallback(nullptr) , mHeightCullCallback(nullptr)
, mWorldspace(worldspace)
{ {
mTerrainRoot = new osg::Group; mTerrainRoot = new osg::Group;
mTerrainRoot->setNodeMask(nodeMask); mTerrainRoot->setNodeMask(nodeMask);
@ -124,7 +127,7 @@ namespace Terrain
float World::getHeightAt(const osg::Vec3f& worldPos) float World::getHeightAt(const osg::Vec3f& worldPos)
{ {
return mStorage->getHeightAt(worldPos); return mStorage->getHeightAt(worldPos, mWorldspace);
} }
void World::updateTextureFiltering() void World::updateTextureFiltering()

View file

@ -8,6 +8,8 @@
#include <memory> #include <memory>
#include <set> #include <set>
#include <components/esm/refid.hpp>
#include "cellborder.hpp" #include "cellborder.hpp"
namespace osg namespace osg
@ -48,8 +50,8 @@ namespace Terrain
/// @param nodeMask mask for the terrain root /// @param nodeMask mask for the terrain root
/// @param preCompileMask mask for pre compiling textures /// @param preCompileMask mask for pre compiling textures
World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, World(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage,
unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask); unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, ESM::RefId worldspace);
World(osg::Group* parent, Storage* storage, unsigned int nodeMask); World(osg::Group* parent, Storage* storage, unsigned int nodeMask, ESM::RefId worldspace);
virtual ~World(); virtual ~World();
/// See CompositeMapRenderer::setTargetFrameRate /// See CompositeMapRenderer::setTargetFrameRate
@ -99,6 +101,8 @@ namespace Terrain
virtual void setViewDistance(float distance) {} virtual void setViewDistance(float distance) {}
ESM::RefId getWorldspace() { return mWorldspace; }
Storage* getStorage() { return mStorage; } Storage* getStorage() { return mStorage; }
osg::Callback* getHeightCullCallback(float highz, unsigned int mask); osg::Callback* getHeightCullCallback(float highz, unsigned int mask);
@ -127,6 +131,7 @@ namespace Terrain
osg::ref_ptr<HeightCullCallback> mHeightCullCallback; osg::ref_ptr<HeightCullCallback> mHeightCullCallback;
osg::Vec4i mActiveGrid; osg::Vec4i mActiveGrid;
ESM::RefId mWorldspace;
}; };
} }