diff --git a/apps/openmw/mwclass/esm4base.hpp b/apps/openmw/mwclass/esm4base.hpp index f13d6007cd..0e7888317e 100644 --- a/apps/openmw/mwclass/esm4base.hpp +++ b/apps/openmw/mwclass/esm4base.hpp @@ -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(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; diff --git a/apps/openmw/mwrender/objectpaging.cpp b/apps/openmw/mwrender/objectpaging.cpp index f45247398f..9f8b1ed86c 100644 --- a/apps/openmw/mwrender/objectpaging.cpp +++ b/apps/openmw/mwrender/objectpaging.cpp @@ -20,6 +20,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include #include #include @@ -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 + 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().searchStatic(id)->mModel; case ESM::REC_CONT: return store.get().searchStatic(id)->mModel; + case ESM::REC_STAT4: + return getEsm4Model(store.get().searchStatic(id)); + case ESM::REC_DOOR4: + return getEsm4Model(store.get().searchStatic(id)); + case ESM::REC_TREE4: + return getEsm4Model(store.get().searchStatic(id)); + case ESM::REC_ACTI4: + return getEsm4Model(store.get().searchStatic(id)); + case ESM::REC_CONT4: + return getEsm4Model(store.get().searchStatic(id)); + case ESM::REC_FURN4: + return getEsm4Model(store.get().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 collectESM3References( float size, const osg::Vec2i& startCell, const MWWorld::ESMStore& store) { @@ -561,6 +607,45 @@ namespace MWRender } return refs; } + + std::map collectESM4References( + float size, const osg::Vec2i& startCell, ESM::RefId worldspace) + { + std::map 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().searchExterior(ESM::ExteriorCellLocation(cellX, cellY, worldspace)); + if (!cell) + continue; + for (const ESM4::Reference* ref4 : store.get().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().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 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") diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 478bdb5bb8..0c9a13bc47 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #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); diff --git a/apps/openmw/mwworld/scene.hpp b/apps/openmw/mwworld/scene.hpp index 116e52e535..1fa7779e51 100644 --- a/apps/openmw/mwworld/scene.hpp +++ b/apps/openmw/mwworld/scene.hpp @@ -129,6 +129,9 @@ namespace MWWorld void preloadExteriorGrid(const osg::Vec3f& playerPos, const osg::Vec3f& predictedPos); void preloadFastTravelDestinations( const osg::Vec3f& playerPos, std::vector& 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); diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index f608b8c781..97788669d5 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -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