Merge branch 'esm4paging' into 'master'

Object paging in ESM4 worldspaces

See merge request OpenMW/openmw!4770
This commit is contained in:
Alexei Kotov 2025-07-18 23:41:18 +03:00
commit 152d87a6b3
5 changed files with 113 additions and 21 deletions

View File

@ -55,6 +55,16 @@ namespace MWClass
}
return res;
}
// TODO: Figure out a better way to find markers and LOD meshes
inline bool isMarkerModel(std::string_view model)
{
return Misc::StringUtils::ciStartsWith(model, "marker");
}
inline bool isLodModel(std::string_view model)
{
return Misc::StringUtils::ciEndsWith(model, "lod.nif");
}
}
// Base for many ESM4 Classes
@ -100,11 +110,8 @@ namespace MWClass
{
std::string_view model = getClassModel<Record>(ptr);
// Hide meshes meshes/marker/* and *LOD.nif in ESM4 cells. It is a temporarty hack.
// Needed because otherwise LOD meshes are rendered on top of normal meshes.
// TODO: Figure out a better way find markers and LOD meshes; show LOD only outside of active grid.
if (model.empty() || Misc::StringUtils::ciStartsWith(model, "marker")
|| Misc::StringUtils::ciEndsWith(model, "lod.nif"))
// TODO: There should be a better way to hide markers
if (ESM4Impl::isMarkerModel(model) || ESM4Impl::isLodModel(model))
return {};
return model;

View File

@ -20,6 +20,12 @@
#include <components/esm3/loaddoor.hpp>
#include <components/esm3/loadstat.hpp>
#include <components/esm3/readerscache.hpp>
#include <components/esm4/loadacti.hpp>
#include <components/esm4/loadcont.hpp>
#include <components/esm4/loaddoor.hpp>
#include <components/esm4/loadfurn.hpp>
#include <components/esm4/loadstat.hpp>
#include <components/esm4/loadtree.hpp>
#include <components/misc/pathhelpers.hpp>
#include <components/misc/resourcehelpers.hpp>
#include <components/misc/rng.hpp>
@ -36,12 +42,14 @@
#include "apps/openmw/mwbase/environment.hpp"
#include "apps/openmw/mwbase/world.hpp"
#include "apps/openmw/mwclass/esm4base.hpp"
#include "apps/openmw/mwworld/esmstore.hpp"
#include "vismask.hpp"
namespace MWRender
{
namespace
{
bool typeFilter(int type, bool far)
@ -51,8 +59,14 @@ namespace MWRender
case ESM::REC_STAT:
case ESM::REC_ACTI:
case ESM::REC_DOOR:
case ESM::REC_STAT4:
case ESM::REC_DOOR4:
case ESM::REC_TREE4:
return true;
case ESM::REC_CONT:
case ESM::REC_ACTI4:
case ESM::REC_CONT4:
case ESM::REC_FURN4:
return !far;
default:
@ -60,7 +74,16 @@ namespace MWRender
}
}
std::string getModel(int type, ESM::RefId id, const MWWorld::ESMStore& store)
template <typename Record>
std::string_view getEsm4Model(const Record& record)
{
if (MWClass::ESM4Impl::isMarkerModel(record->mModel))
return {};
else
return record->mModel;
}
std::string_view getModel(int type, ESM::RefId id, const MWWorld::ESMStore& store)
{
switch (type)
{
@ -72,6 +95,18 @@ namespace MWRender
return store.get<ESM::Door>().searchStatic(id)->mModel;
case ESM::REC_CONT:
return store.get<ESM::Container>().searchStatic(id)->mModel;
case ESM::REC_STAT4:
return getEsm4Model(store.get<ESM4::Static>().searchStatic(id));
case ESM::REC_DOOR4:
return getEsm4Model(store.get<ESM4::Door>().searchStatic(id));
case ESM::REC_TREE4:
return getEsm4Model(store.get<ESM4::Tree>().searchStatic(id));
case ESM::REC_ACTI4:
return getEsm4Model(store.get<ESM4::Activator>().searchStatic(id));
case ESM::REC_CONT4:
return getEsm4Model(store.get<ESM4::Container>().searchStatic(id));
case ESM::REC_FURN4:
return getEsm4Model(store.get<ESM4::Furniture>().searchStatic(id));
default:
return {};
}
@ -494,6 +529,17 @@ namespace MWRender
};
}
PagedCellRef makePagedCellRef(const ESM4::Reference& value)
{
return PagedCellRef{
.mRefId = value.mBaseObj,
.mRefNum = value.mId,
.mPosition = value.mPos.asVec3(),
.mRotation = value.mPos.asRotationVec3(),
.mScale = value.mScale,
};
}
std::map<ESM::RefNum, PagedCellRef> collectESM3References(
float size, const osg::Vec2i& startCell, const MWWorld::ESMStore& store)
{
@ -561,6 +607,45 @@ namespace MWRender
}
return refs;
}
std::map<ESM::RefNum, PagedCellRef> collectESM4References(
float size, const osg::Vec2i& startCell, ESM::RefId worldspace)
{
std::map<ESM::RefNum, PagedCellRef> refs;
const auto& store = MWBase::Environment::get().getWorld()->getStore();
for (int cellX = startCell.x(); cellX < startCell.x() + size; ++cellX)
{
for (int cellY = startCell.y(); cellY < startCell.y() + size; ++cellY)
{
const ESM4::Cell* cell
= store.get<ESM4::Cell>().searchExterior(ESM::ExteriorCellLocation(cellX, cellY, worldspace));
if (!cell)
continue;
for (const ESM4::Reference* ref4 : store.get<ESM4::Reference>().getByCell(cell->mId))
{
if (ref4->mFlags & ESM4::Rec_Disabled)
continue;
int type = store.findStatic(ref4->mBaseObj);
if (!typeFilter(type, size >= 2))
continue;
if (!ref4->mEsp.parent.isZeroOrUnset())
{
const ESM4::Reference* parentRef
= store.get<ESM4::Reference>().searchStatic(ref4->mEsp.parent);
if (parentRef)
{
bool parentDisabled = parentRef->mFlags & ESM4::Rec_Disabled;
bool inversed = ref4->mEsp.flags & ESM4::EnableParent::Flag_Inversed;
if (parentDisabled != inversed)
continue;
}
}
refs.insert_or_assign(ref4->mId, makePagedCellRef(*ref4));
}
}
}
return refs;
}
}
osg::ref_ptr<osg::Node> ObjectPaging::createChunk(float size, const osg::Vec2f& center, bool activeGrid,
@ -578,7 +663,7 @@ namespace MWRender
}
else
{
// TODO
refs = collectESM4References(size, startCell, mWorldspace);
}
if (activeGrid && !refs.empty())
@ -648,12 +733,12 @@ namespace MWRender
continue;
const int type = store.findStatic(ref.mRefId);
VFS::Path::Normalized model = getModel(type, ref.mRefId, store);
VFS::Path::Normalized model(getModel(type, ref.mRefId, store));
if (model.empty())
continue;
model = Misc::ResourceHelpers::correctMeshPath(model);
if (activeGrid && type != ESM::REC_STAT)
if (activeGrid && type != ESM::REC_STAT && type != ESM::REC_STAT4)
{
model = Misc::ResourceHelpers::correctActorModelPath(model, mSceneManager->getVFS());
if (Misc::getFileExtension(model) == "nif")

View File

@ -22,6 +22,7 @@
#include <components/resource/scenemanager.hpp>
#include <components/sceneutil/positionattitudetransform.hpp>
#include <components/settings/values.hpp>
#include <components/terrain/terraingrid.hpp>
#include <components/vfs/pathutil.hpp>
#include "../mwbase/environment.hpp"
@ -511,7 +512,7 @@ namespace MWWorld
if (cellVariant.isExterior())
{
if (const auto heightField = mPhysics->getHeightField(cellX, cellY))
if (mPhysics->getHeightField(cellX, cellY) != nullptr)
mNavigator.addWater(
osg::Vec2i(cellX, cellY), ESM::Land::REAL_SIZE, waterLevel, navigatorUpdateGuard);
}
@ -645,8 +646,11 @@ namespace MWWorld
mHalfGridSize = halfGridSize;
mCurrentGridCenter = osg::Vec2i(playerCellX, playerCellY);
osg::Vec4i newGrid = gridCenterToBounds(mCurrentGridCenter);
mRendering.setActiveGrid(newGrid);
// NOTE: setActiveGrid must be after enableTerrain, otherwise we set the grid in the old exterior worldspace
mRendering.enableTerrain(true, playerCellIndex.mWorldspace);
mRendering.setActiveGrid(newGrid);
mPreloader->setTerrain(mRendering.getTerrain());
if (mRendering.pagingUnlockCache())
mPreloader->abortTerrainPreloadExcept(nullptr);
@ -1292,6 +1296,9 @@ namespace MWWorld
void Scene::preloadTerrain(const osg::Vec3f& pos, ESM::RefId worldspace, bool sync)
{
if (mRendering.getTerrain()->getWorldspace() != worldspace)
throw std::runtime_error("preloadTerrain can only work with the current exterior worldspace");
ESM::ExteriorCellLocation cellPos = ESM::positionToExteriorCellLocation(pos.x(), pos.y(), worldspace);
const PositionCellGrid position{ pos, gridCenterToBounds({ cellPos.mX, cellPos.mY }) };
mPreloader->abortTerrainPreloadExcept(&position);

View File

@ -129,6 +129,9 @@ namespace MWWorld
void preloadExteriorGrid(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos);
void preloadFastTravelDestinations(
const osg::Vec3f& playerPos, std::vector<PositionCellGrid>& exteriorPositions);
void preloadCellWithSurroundings(MWWorld::CellStore& cell);
void preloadCell(MWWorld::CellStore& cell);
void preloadTerrain(const osg::Vec3f& pos, ESM::RefId worldspace, bool sync = false);
osg::Vec4i gridCenterToBounds(const osg::Vec2i& centerCell) const;
osg::Vec2i getNewGridCenter(const osg::Vec3f& pos, const osg::Vec2i* currentGridCenter = nullptr) const;
@ -143,9 +146,6 @@ namespace MWWorld
~Scene();
void preloadCellWithSurroundings(MWWorld::CellStore& cell);
void preloadCell(MWWorld::CellStore& cell);
void preloadTerrain(const osg::Vec3f& pos, ESM::RefId worldspace, bool sync = false);
void reloadTerrain();
void playerMoved(const osg::Vec3f& pos);

View File

@ -517,13 +517,6 @@ namespace MWWorld
mStore.checkPlayer();
mPlayer->readRecord(reader, type);
if (getPlayerPtr().isInCell())
{
if (getPlayerPtr().getCell()->isExterior())
mWorldScene->preloadTerrain(getPlayerPtr().getRefData().getPosition().asVec3(),
getPlayerPtr().getCell()->getCell()->getWorldSpace());
mWorldScene->preloadCellWithSurroundings(*getPlayerPtr().getCell());
}
break;
case ESM::REC_CSTA:
// We need to rebuild the ESMStore index in order to be able to lookup dynamic records while loading the