mirror of
https://github.com/OpenMW/openmw.git
synced 2026-01-04 15:43:07 +00:00
Merge branch openmw:master into master
This commit is contained in:
commit
6114a01ad4
22 changed files with 295 additions and 170 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -393,13 +393,13 @@ namespace MWMechanics
|
|||
osg::Vec3f localPos = actor.getRefData().getPosition().asVec3();
|
||||
coords.toLocal(localPos);
|
||||
|
||||
int closestPointIndex = PathFinder::getClosestPoint(pathgrid, localPos);
|
||||
for (int i = 0; i < static_cast<int>(pathgrid->mPoints.size()); i++)
|
||||
size_t closestPointIndex = PathFinder::getClosestPoint(pathgrid, localPos);
|
||||
for (size_t i = 0; i < pathgrid->mPoints.size(); i++)
|
||||
{
|
||||
if (i != closestPointIndex
|
||||
&& getPathGridGraph(pathgrid).isPointConnected(closestPointIndex, i))
|
||||
{
|
||||
points.push_back(pathgrid->mPoints[static_cast<size_t>(i)]);
|
||||
points.push_back(pathgrid->mPoints[i]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -827,7 +827,7 @@ namespace MWMechanics
|
|||
if (pathgrid == nullptr || pathgrid->mPoints.empty())
|
||||
return;
|
||||
|
||||
int index = PathFinder::getClosestPoint(pathgrid, PathFinder::makeOsgVec3(dest));
|
||||
size_t index = PathFinder::getClosestPoint(pathgrid, PathFinder::makeOsgVec3(dest));
|
||||
|
||||
getPathGridGraph(pathgrid).getNeighbouringPoints(index, points);
|
||||
}
|
||||
|
|
@ -860,7 +860,7 @@ namespace MWMechanics
|
|||
const osg::Vec3f npcPos = converter.toLocalVec3(mInitialActorPosition);
|
||||
|
||||
// Find closest pathgrid point
|
||||
int closestPointIndex = PathFinder::getClosestPoint(pathgrid, npcPos);
|
||||
size_t closestPointIndex = PathFinder::getClosestPoint(pathgrid, npcPos);
|
||||
|
||||
// mAllowedNodes for this actor with pathgrid point indexes based on mDistance
|
||||
// and if the point is connected to the closest current point
|
||||
|
|
|
|||
|
|
@ -25,15 +25,15 @@
|
|||
namespace
|
||||
{
|
||||
// Chooses a reachable end pathgrid point. start is assumed reachable.
|
||||
std::pair<int, bool> getClosestReachablePoint(
|
||||
const ESM::Pathgrid* grid, const MWMechanics::PathgridGraph* graph, const osg::Vec3f& pos, int start)
|
||||
std::pair<size_t, bool> getClosestReachablePoint(
|
||||
const ESM::Pathgrid* grid, const MWMechanics::PathgridGraph* graph, const osg::Vec3f& pos, size_t start)
|
||||
{
|
||||
assert(grid && !grid->mPoints.empty());
|
||||
|
||||
float closestDistanceBetween = std::numeric_limits<float>::max();
|
||||
float closestDistanceReachable = std::numeric_limits<float>::max();
|
||||
int closestIndex = 0;
|
||||
int closestReachableIndex = 0;
|
||||
size_t closestIndex = 0;
|
||||
size_t closestReachableIndex = 0;
|
||||
// TODO: if this full scan causes performance problems mapping pathgrid
|
||||
// points to a quadtree may help
|
||||
for (size_t counter = 0; counter < grid->mPoints.size(); counter++)
|
||||
|
|
@ -62,7 +62,7 @@ namespace
|
|||
// allowed nodes if not. Hence a path needs to be created even if the start
|
||||
// and the end points are the same.
|
||||
|
||||
return std::pair<int, bool>(closestReachableIndex, closestReachableIndex == closestIndex);
|
||||
return { closestReachableIndex, closestReachableIndex == closestIndex };
|
||||
}
|
||||
|
||||
float sqrDistance(const osg::Vec2f& lhs, const osg::Vec2f& rhs)
|
||||
|
|
@ -197,10 +197,10 @@ namespace MWMechanics
|
|||
// point right behind the wall that is closer than any pathgrid
|
||||
// point outside the wall
|
||||
osg::Vec3f startPointInLocalCoords(converter.toLocalVec3(startPoint));
|
||||
int startNode = getClosestPoint(pathgrid, startPointInLocalCoords);
|
||||
size_t startNode = getClosestPoint(pathgrid, startPointInLocalCoords);
|
||||
|
||||
osg::Vec3f endPointInLocalCoords(converter.toLocalVec3(endPoint));
|
||||
std::pair<int, bool> endNode
|
||||
std::pair<size_t, bool> endNode
|
||||
= getClosestReachablePoint(pathgrid, &pathgridGraph, endPointInLocalCoords, startNode);
|
||||
|
||||
// if it's shorter for actor to travel from start to end, than to travel from either
|
||||
|
|
|
|||
|
|
@ -178,16 +178,16 @@ namespace MWMechanics
|
|||
//
|
||||
// NOTE: pos is expected to be in local coordinates, as is grid->mPoints
|
||||
//
|
||||
static int getClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos)
|
||||
static size_t getClosestPoint(const ESM::Pathgrid* grid, const osg::Vec3f& pos)
|
||||
{
|
||||
assert(grid && !grid->mPoints.empty());
|
||||
|
||||
float distanceBetween = distanceSquared(grid->mPoints[0], pos);
|
||||
int closestIndex = 0;
|
||||
size_t closestIndex = 0;
|
||||
|
||||
// TODO: if this full scan causes performance problems mapping pathgrid
|
||||
// points to a quadtree may help
|
||||
for (unsigned int counter = 1; counter < grid->mPoints.size(); counter++)
|
||||
for (size_t counter = 1; counter < grid->mPoints.size(); counter++)
|
||||
{
|
||||
float potentialDistBetween = distanceSquared(grid->mPoints[counter], pos);
|
||||
if (potentialDistBetween < distanceBetween)
|
||||
|
|
|
|||
|
|
@ -722,6 +722,7 @@ namespace MWRender
|
|||
|
||||
mAnimSources.push_back(animsrc);
|
||||
|
||||
mSupportedDirections.clear();
|
||||
for (const std::string& group : mAnimSources.back()->getTextKeys().getGroups())
|
||||
mSupportedAnimations.insert(group);
|
||||
|
||||
|
|
@ -795,6 +796,7 @@ namespace MWRender
|
|||
mAccumCtrl = nullptr;
|
||||
|
||||
mSupportedAnimations.clear();
|
||||
mSupportedDirections.clear();
|
||||
mAnimSources.clear();
|
||||
|
||||
mAnimVelocities.clear();
|
||||
|
|
@ -2012,20 +2014,29 @@ namespace MWRender
|
|||
std::span<const std::string_view> prefixes) const
|
||||
{
|
||||
MWWorld::MovementDirectionFlags result = 0;
|
||||
for (const std::string_view animation : mSupportedAnimations)
|
||||
for (const std::string_view prefix : prefixes)
|
||||
{
|
||||
if (std::find_if(
|
||||
prefixes.begin(), prefixes.end(), [&](std::string_view v) { return animation.starts_with(v); })
|
||||
== prefixes.end())
|
||||
continue;
|
||||
if (animation.ends_with("forward"))
|
||||
result |= MWWorld::MovementDirectionFlag_Forward;
|
||||
else if (animation.ends_with("back"))
|
||||
result |= MWWorld::MovementDirectionFlag_Back;
|
||||
else if (animation.ends_with("left"))
|
||||
result |= MWWorld::MovementDirectionFlag_Left;
|
||||
else if (animation.ends_with("right"))
|
||||
result |= MWWorld::MovementDirectionFlag_Right;
|
||||
auto it = std::find_if(mSupportedDirections.begin(), mSupportedDirections.end(),
|
||||
[prefix](const auto& direction) { return direction.first == prefix; });
|
||||
if (it == mSupportedDirections.end())
|
||||
{
|
||||
mSupportedDirections.emplace_back(prefix, 0);
|
||||
it = mSupportedDirections.end() - 1;
|
||||
for (const std::string_view animation : mSupportedAnimations)
|
||||
{
|
||||
if (!animation.starts_with(prefix))
|
||||
continue;
|
||||
if (animation.ends_with("forward"))
|
||||
it->second |= MWWorld::MovementDirectionFlag_Forward;
|
||||
else if (animation.ends_with("back"))
|
||||
it->second |= MWWorld::MovementDirectionFlag_Back;
|
||||
else if (animation.ends_with("left"))
|
||||
it->second |= MWWorld::MovementDirectionFlag_Left;
|
||||
else if (animation.ends_with("right"))
|
||||
it->second |= MWWorld::MovementDirectionFlag_Right;
|
||||
}
|
||||
}
|
||||
result |= it->second;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -181,6 +181,7 @@ namespace MWRender
|
|||
AnimSourceList mAnimSources;
|
||||
|
||||
std::unordered_set<std::string_view> mSupportedAnimations;
|
||||
mutable std::vector<std::pair<std::string, MWWorld::MovementDirectionFlags>> mSupportedDirections;
|
||||
|
||||
osg::ref_ptr<osg::Group> mInsert;
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -1263,26 +1263,48 @@ namespace MWWorld
|
|||
}
|
||||
}
|
||||
|
||||
for (CellRefList<ESM::Creature>::List::iterator it(get<ESM::Creature>().mList.begin());
|
||||
it != get<ESM::Creature>().mList.end(); ++it)
|
||||
// Actors need to respawn here even if they've been moved to another cell
|
||||
for (LiveCellRefBase& base : get<ESM::Creature>().mList)
|
||||
{
|
||||
Ptr ptr = getCurrentPtr(&*it);
|
||||
Ptr ptr = getCurrentPtr(&base);
|
||||
clearCorpse(ptr, mStore);
|
||||
ptr.getClass().respawn(ptr);
|
||||
}
|
||||
for (CellRefList<ESM::NPC>::List::iterator it(get<ESM::NPC>().mList.begin());
|
||||
it != get<ESM::NPC>().mList.end(); ++it)
|
||||
for (LiveCellRefBase& base : get<ESM::NPC>().mList)
|
||||
{
|
||||
Ptr ptr = getCurrentPtr(&*it);
|
||||
Ptr ptr = getCurrentPtr(&base);
|
||||
clearCorpse(ptr, mStore);
|
||||
ptr.getClass().respawn(ptr);
|
||||
}
|
||||
forEachType<ESM::CreatureLevList>([](Ptr ptr) {
|
||||
// no need to clearCorpse, handled as part of get<ESM::Creature>()
|
||||
for (LiveCellRefBase& base : get<ESM::CreatureLevList>().mList)
|
||||
{
|
||||
Ptr ptr = getCurrentPtr(&base);
|
||||
if (!ptr.mRef->isDeleted())
|
||||
ptr.getClass().respawn(ptr);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
for (const auto& [base, _] : mMovedHere)
|
||||
{
|
||||
switch (base->getType())
|
||||
{
|
||||
case ESM::Creature::sRecordId:
|
||||
case ESM::NPC::sRecordId:
|
||||
case ESM::CreatureLevList::sRecordId:
|
||||
{
|
||||
MWWorld::Ptr ptr(base, this);
|
||||
if (ptr.mRef->isDeleted())
|
||||
continue;
|
||||
// Remove actors that have been dead a while, but don't belong here and didn't get hit by the
|
||||
// logic above
|
||||
if (ptr.getClass().isActor())
|
||||
clearCorpse(ptr, mStore);
|
||||
else // Respawn lists in their new position
|
||||
ptr.getClass().respawn(ptr);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,29 +1,12 @@
|
|||
#include "ba2dx10file.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include <lz4frame.h>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
// why is this necessary? These are included with /external:I
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4706)
|
||||
#pragma warning(disable : 4702)
|
||||
#include <boost/iostreams/copy.hpp>
|
||||
#include <boost/iostreams/filter/zlib.hpp>
|
||||
#include <boost/iostreams/filtering_stream.hpp>
|
||||
#include <boost/iostreams/filtering_streambuf.hpp>
|
||||
#pragma warning(pop)
|
||||
#else
|
||||
#include <boost/iostreams/copy.hpp>
|
||||
#include <boost/iostreams/filter/zlib.hpp>
|
||||
#include <boost/iostreams/filtering_stream.hpp>
|
||||
#include <boost/iostreams/filtering_streambuf.hpp>
|
||||
#endif
|
||||
|
||||
#include <boost/iostreams/device/array.hpp>
|
||||
#include <zlib.h>
|
||||
|
||||
#include <components/esm/fourcc.hpp>
|
||||
#include <components/files/constrainedfilestream.hpp>
|
||||
|
|
@ -644,11 +627,16 @@ namespace Bsa
|
|||
size_t headerSize = (header.ddspf.fourCC == ESM::fourCC("DX10") ? sizeof(DDSHeaderDX10) : sizeof(DDSHeader));
|
||||
|
||||
size_t textureSize = sizeof(uint32_t) + headerSize; //"DDS " + header
|
||||
uint32_t maxPackedChunkSize = 0;
|
||||
for (const auto& textureChunk : fileRecord.texturesChunks)
|
||||
{
|
||||
textureSize += textureChunk.size;
|
||||
maxPackedChunkSize = std::max(textureChunk.packedSize, maxPackedChunkSize);
|
||||
}
|
||||
|
||||
auto memoryStreamPtr = std::make_unique<MemoryInputStream>(textureSize);
|
||||
char* buff = memoryStreamPtr->getRawData();
|
||||
std::vector<char> inputBuffer(maxPackedChunkSize);
|
||||
|
||||
uint32_t dds = ESM::fourCC("DDS ");
|
||||
buff = (char*)std::memcpy(buff, &dds, sizeof(uint32_t)) + sizeof(uint32_t);
|
||||
|
|
@ -658,25 +646,22 @@ namespace Bsa
|
|||
// append chunks
|
||||
for (const auto& c : fileRecord.texturesChunks)
|
||||
{
|
||||
const uint32_t inputSize = c.packedSize != 0 ? c.packedSize : c.size;
|
||||
Files::IStreamPtr streamPtr = Files::openConstrainedFileStream(mFilepath, c.offset, inputSize);
|
||||
if (c.packedSize != 0)
|
||||
{
|
||||
Files::IStreamPtr streamPtr = Files::openConstrainedFileStream(mFilepath, c.offset, c.packedSize);
|
||||
std::istream* fileStream = streamPtr.get();
|
||||
streamPtr->read(inputBuffer.data(), c.packedSize);
|
||||
uLongf destSize = static_cast<uLongf>(c.size);
|
||||
int ec = ::uncompress(reinterpret_cast<Bytef*>(memoryStreamPtr->getRawData() + offset), &destSize,
|
||||
reinterpret_cast<Bytef*>(inputBuffer.data()), static_cast<uLong>(c.packedSize));
|
||||
|
||||
boost::iostreams::filtering_streambuf<boost::iostreams::input> inputStreamBuf;
|
||||
inputStreamBuf.push(boost::iostreams::zlib_decompressor());
|
||||
inputStreamBuf.push(*fileStream);
|
||||
|
||||
boost::iostreams::basic_array_sink<char> sr(memoryStreamPtr->getRawData() + offset, c.size);
|
||||
boost::iostreams::copy(inputStreamBuf, sr);
|
||||
if (ec != Z_OK)
|
||||
fail("zlib uncompress failed: " + std::string(::zError(ec)));
|
||||
}
|
||||
// uncompressed chunk
|
||||
else
|
||||
{
|
||||
Files::IStreamPtr streamPtr = Files::openConstrainedFileStream(mFilepath, c.offset, c.size);
|
||||
std::istream* fileStream = streamPtr.get();
|
||||
|
||||
fileStream->read(memoryStreamPtr->getRawData() + offset, c.size);
|
||||
streamPtr->read(memoryStreamPtr->getRawData() + offset, c.size);
|
||||
}
|
||||
offset += c.size;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,11 @@
|
|||
#include "ba2gnrlfile.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include <lz4frame.h>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
// why is this necessary? These are included with /external:I
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4706)
|
||||
#pragma warning(disable : 4702)
|
||||
#include <boost/iostreams/copy.hpp>
|
||||
#include <boost/iostreams/filter/zlib.hpp>
|
||||
#include <boost/iostreams/filtering_streambuf.hpp>
|
||||
#pragma warning(pop)
|
||||
#else
|
||||
#include <boost/iostreams/copy.hpp>
|
||||
#include <boost/iostreams/filter/zlib.hpp>
|
||||
#include <boost/iostreams/filtering_streambuf.hpp>
|
||||
#endif
|
||||
|
||||
#include <boost/iostreams/device/array.hpp>
|
||||
#include <zlib.h>
|
||||
|
||||
#include <components/esm/fourcc.hpp>
|
||||
#include <components/files/constrainedfilestream.hpp>
|
||||
|
|
@ -223,12 +207,14 @@ namespace Bsa
|
|||
auto memoryStreamPtr = std::make_unique<MemoryInputStream>(fileRecord.size);
|
||||
if (fileRecord.packedSize)
|
||||
{
|
||||
boost::iostreams::filtering_streambuf<boost::iostreams::input> inputStreamBuf;
|
||||
inputStreamBuf.push(boost::iostreams::zlib_decompressor());
|
||||
inputStreamBuf.push(*streamPtr);
|
||||
std::vector<char> buffer(inputSize);
|
||||
streamPtr->read(buffer.data(), inputSize);
|
||||
uLongf destSize = static_cast<uLongf>(fileRecord.size);
|
||||
int ec = ::uncompress(reinterpret_cast<Bytef*>(memoryStreamPtr->getRawData()), &destSize,
|
||||
reinterpret_cast<Bytef*>(buffer.data()), static_cast<uLong>(buffer.size()));
|
||||
|
||||
boost::iostreams::basic_array_sink<char> sr(memoryStreamPtr->getRawData(), fileRecord.size);
|
||||
boost::iostreams::copy(inputStreamBuf, sr);
|
||||
if (ec != Z_OK)
|
||||
fail("zlib uncompress failed: " + std::string(::zError(ec)));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -24,27 +24,13 @@
|
|||
*/
|
||||
#include "compressedbsafile.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include <lz4frame.h>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4706)
|
||||
#pragma warning(disable : 4702)
|
||||
#include <boost/iostreams/copy.hpp>
|
||||
#include <boost/iostreams/filter/zlib.hpp>
|
||||
#include <boost/iostreams/filtering_streambuf.hpp>
|
||||
#pragma warning(pop)
|
||||
#else
|
||||
#include <boost/iostreams/copy.hpp>
|
||||
#include <boost/iostreams/filter/zlib.hpp>
|
||||
#include <boost/iostreams/filtering_streambuf.hpp>
|
||||
#endif
|
||||
|
||||
#include <boost/iostreams/device/array.hpp>
|
||||
#include <zlib.h>
|
||||
|
||||
#include <components/files/constrainedfilestream.hpp>
|
||||
#include <components/files/conversion.hpp>
|
||||
|
|
@ -292,19 +278,26 @@ namespace Bsa
|
|||
|
||||
if (compressed)
|
||||
{
|
||||
std::vector<char> buffer(size);
|
||||
streamPtr->read(buffer.data(), size);
|
||||
|
||||
if (mHeader.mVersion != Version_SSE)
|
||||
{
|
||||
boost::iostreams::filtering_streambuf<boost::iostreams::input> inputStreamBuf;
|
||||
inputStreamBuf.push(boost::iostreams::zlib_decompressor());
|
||||
inputStreamBuf.push(*streamPtr);
|
||||
uLongf destSize = static_cast<uLongf>(resultSize);
|
||||
int ec = ::uncompress(reinterpret_cast<Bytef*>(memoryStreamPtr->getRawData()), &destSize,
|
||||
reinterpret_cast<Bytef*>(buffer.data()), static_cast<uLong>(buffer.size()));
|
||||
|
||||
boost::iostreams::basic_array_sink<char> sr(memoryStreamPtr->getRawData(), resultSize);
|
||||
boost::iostreams::copy(inputStreamBuf, sr);
|
||||
if (ec != Z_OK)
|
||||
{
|
||||
std::string message = "zlib uncompress failed for file ";
|
||||
message.append(fileRecord.mName.begin(), fileRecord.mName.end());
|
||||
message += ": ";
|
||||
message += ::zError(ec);
|
||||
fail(message);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto buffer = std::vector<char>(size);
|
||||
streamPtr->read(buffer.data(), size);
|
||||
LZ4F_decompressionContext_t context = nullptr;
|
||||
LZ4F_createDecompressionContext(&context, LZ4F_VERSION);
|
||||
LZ4F_decompressOptions_t options = {};
|
||||
|
|
|
|||
|
|
@ -247,16 +247,11 @@ namespace Nif
|
|||
|
||||
void NiVisData::read(NIFStream* nif)
|
||||
{
|
||||
mKeys = std::make_shared<std::map<float, bool>>();
|
||||
uint32_t numKeys;
|
||||
nif->read(numKeys);
|
||||
for (size_t i = 0; i < numKeys; i++)
|
||||
mKeys = std::make_shared<std::vector<std::pair<float, bool>>>(nif->get<uint32_t>());
|
||||
for (auto& [time, value] : *mKeys)
|
||||
{
|
||||
float time;
|
||||
char value;
|
||||
nif->read(time);
|
||||
nif->read(value);
|
||||
(*mKeys)[time] = (value != 0);
|
||||
value = nif->get<uint8_t>() != 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -193,8 +193,8 @@ namespace Nif
|
|||
|
||||
struct NiVisData : public Record
|
||||
{
|
||||
// TODO: investigate possible use of BoolKeyMap
|
||||
std::shared_ptr<std::map<float, bool>> mKeys;
|
||||
// This is theoretically a "flat map" sorted by time
|
||||
std::shared_ptr<std::vector<std::pair<float, bool>>> mKeys;
|
||||
|
||||
void read(NIFStream* nif) override;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#ifndef OPENMW_COMPONENTS_NIF_NIFKEY_HPP
|
||||
#define OPENMW_COMPONENTS_NIF_NIFKEY_HPP
|
||||
|
||||
#include <map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "exception.hpp"
|
||||
|
|
@ -46,7 +46,8 @@ namespace Nif
|
|||
template <typename T, T (NIFStream::*getValue)()>
|
||||
struct KeyMapT
|
||||
{
|
||||
using MapType = std::map<float, KeyT<T>>;
|
||||
// This is theoretically a "flat map" sorted by time
|
||||
using MapType = std::vector<std::pair<float, KeyT<T>>>;
|
||||
|
||||
using ValueType = T;
|
||||
using KeyType = KeyT<T>;
|
||||
|
|
@ -78,8 +79,12 @@ namespace Nif
|
|||
uint32_t count;
|
||||
nif->read(count);
|
||||
|
||||
if (count != 0 || morph)
|
||||
nif->read(mInterpolationType);
|
||||
if (count == 0 && !morph)
|
||||
return;
|
||||
|
||||
nif->read(mInterpolationType);
|
||||
|
||||
mKeys.reserve(count);
|
||||
|
||||
KeyType key = {};
|
||||
|
||||
|
|
@ -90,7 +95,7 @@ namespace Nif
|
|||
float time;
|
||||
nif->read(time);
|
||||
readValue(*nif, key);
|
||||
mKeys[time] = key;
|
||||
mKeys.emplace_back(time, key);
|
||||
}
|
||||
}
|
||||
else if (mInterpolationType == InterpolationType_Quadratic)
|
||||
|
|
@ -100,7 +105,7 @@ namespace Nif
|
|||
float time;
|
||||
nif->read(time);
|
||||
readQuadratic(*nif, key);
|
||||
mKeys[time] = key;
|
||||
mKeys.emplace_back(time, key);
|
||||
}
|
||||
}
|
||||
else if (mInterpolationType == InterpolationType_TCB)
|
||||
|
|
@ -115,8 +120,9 @@ namespace Nif
|
|||
nif->read(tcbKey.mBias);
|
||||
}
|
||||
generateTCBTangents(tcbKeys);
|
||||
for (TCBKey<T>& key : tcbKeys)
|
||||
mKeys[key.mTime] = KeyType{ std::move(key.mValue), std::move(key.mInTan), std::move(key.mOutTan) };
|
||||
for (TCBKey<T>& tcbKey : tcbKeys)
|
||||
mKeys.emplace_back(std::move(tcbKey.mTime),
|
||||
KeyType{ std::move(tcbKey.mValue), std::move(tcbKey.mInTan), std::move(tcbKey.mOutTan) });
|
||||
}
|
||||
else if (mInterpolationType == InterpolationType_XYZ)
|
||||
{
|
||||
|
|
@ -132,6 +138,8 @@ namespace Nif
|
|||
throw Nif::Exception("Unhandled interpolation type: " + std::to_string(mInterpolationType),
|
||||
nif->getFile().getFilename());
|
||||
}
|
||||
|
||||
// Note: NetImmerse does NOT sort keys or remove duplicates
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -676,12 +676,16 @@ namespace Nif
|
|||
|
||||
void NiPSysEmitterCtlrData::read(NIFStream* nif)
|
||||
{
|
||||
// TODO: this is not used in the official files and needs verification
|
||||
mFloatKeyList = std::make_shared<FloatKeyMap>();
|
||||
mFloatKeyList->read(nif);
|
||||
mVisKeyList = std::make_shared<BoolKeyMap>();
|
||||
uint32_t numVisKeys;
|
||||
nif->read(numVisKeys);
|
||||
for (size_t i = 0; i < numVisKeys; i++)
|
||||
mVisKeyList->mKeys[nif->get<float>()].mValue = nif->get<uint8_t>() != 0;
|
||||
mVisKeyList->mKeys.resize(nif->get<uint32_t>());
|
||||
for (auto& [time, key] : mVisKeyList->mKeys)
|
||||
{
|
||||
nif->read(time);
|
||||
key.mValue = nif->get<uint8_t>() != 0;
|
||||
}
|
||||
}
|
||||
|
||||
void NiPSysCollider::read(NIFStream* nif)
|
||||
|
|
|
|||
|
|
@ -374,7 +374,8 @@ namespace NifOsg
|
|||
if (mData->empty())
|
||||
return true;
|
||||
|
||||
auto iter = mData->upper_bound(time);
|
||||
auto iter = std::upper_bound(mData->begin(), mData->end(), time,
|
||||
[](float time, const std::pair<float, bool>& key) { return time < key.first; });
|
||||
if (iter != mData->begin())
|
||||
--iter;
|
||||
return iter->second;
|
||||
|
|
|
|||
|
|
@ -54,7 +54,8 @@ namespace NifOsg
|
|||
return mLastHighKey;
|
||||
}
|
||||
|
||||
return mKeys->mKeys.lower_bound(time);
|
||||
return std::lower_bound(mKeys->mKeys.begin(), mKeys->mKeys.end(), time,
|
||||
[](const typename MapT::MapType::value_type& key, float t) { return key.first < t; });
|
||||
}
|
||||
|
||||
public:
|
||||
|
|
@ -99,8 +100,8 @@ namespace NifOsg
|
|||
|
||||
const typename MapT::MapType& keys = mKeys->mKeys;
|
||||
|
||||
if (time <= keys.begin()->first)
|
||||
return keys.begin()->second.mValue;
|
||||
if (time <= keys.front().first)
|
||||
return keys.front().second.mValue;
|
||||
|
||||
typename MapT::MapType::const_iterator it = retrieveKey(time);
|
||||
|
||||
|
|
@ -111,12 +112,17 @@ namespace NifOsg
|
|||
mLastHighKey = it;
|
||||
mLastLowKey = --it;
|
||||
|
||||
float a = (time - mLastLowKey->first) / (mLastHighKey->first - mLastLowKey->first);
|
||||
const float highTime = mLastHighKey->first;
|
||||
const float lowTime = mLastLowKey->first;
|
||||
if (highTime == lowTime)
|
||||
return mLastLowKey->second.mValue;
|
||||
|
||||
const float a = (time - lowTime) / (highTime - lowTime);
|
||||
|
||||
return interpolate(mLastLowKey->second, mLastHighKey->second, a, mKeys->mInterpolationType);
|
||||
}
|
||||
|
||||
return keys.rbegin()->second.mValue;
|
||||
return keys.back().second.mValue;
|
||||
}
|
||||
|
||||
bool empty() const { return !mKeys || mKeys->mKeys.empty(); }
|
||||
|
|
@ -283,7 +289,7 @@ namespace NifOsg
|
|||
class VisController : public SceneUtil::NodeCallback<VisController>, public SceneUtil::Controller
|
||||
{
|
||||
private:
|
||||
std::shared_ptr<std::map<float, bool>> mData;
|
||||
std::shared_ptr<std::vector<std::pair<float, bool>>> mData;
|
||||
BoolInterpolator mInterpolator;
|
||||
unsigned int mMask{ 0u };
|
||||
|
||||
|
|
|
|||
|
|
@ -53,28 +53,49 @@ namespace Resource
|
|||
class GenericObjectCache : public osg::Referenced
|
||||
{
|
||||
public:
|
||||
// Update last usage timestamp using referenceTime for each cache time if they are not nullptr and referenced
|
||||
// from somewhere else. Remove items with last usage > expiryTime. Note: last usage might be updated from other
|
||||
// places so nullptr or not references elsewhere items are not always removed.
|
||||
/*
|
||||
* @brief Updates usage timestamps and removes expired items
|
||||
*
|
||||
* Updates the lastUsage timestamp of cached non-nullptr items that have external references.
|
||||
* Initializes lastUsage timestamp for new items.
|
||||
* Removes items that haven't been referenced for longer than expiryDelay.
|
||||
*
|
||||
* \note
|
||||
* Last usage might be updated from other places so nullptr items
|
||||
* that are not referenced elsewhere are not always removed.
|
||||
*
|
||||
* @param referenceTime the timestamp indicating when the item was most recently used
|
||||
* @param expiryDelay the delay after which the cache entry for an item expires
|
||||
*/
|
||||
void update(double referenceTime, double expiryDelay)
|
||||
{
|
||||
std::vector<osg::ref_ptr<osg::Object>> objectsToRemove;
|
||||
{
|
||||
const double expiryTime = referenceTime - expiryDelay;
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
|
||||
std::erase_if(mItems, [&](auto& v) {
|
||||
Item& item = v.second;
|
||||
|
||||
// update last usage timestamp if item is being referenced externally
|
||||
// or initialize if not set
|
||||
if ((item.mValue != nullptr && item.mValue->referenceCount() > 1) || item.mLastUsage == 0)
|
||||
item.mLastUsage = referenceTime;
|
||||
|
||||
// skip items that have been accessed since expiryTime
|
||||
if (item.mLastUsage > expiryTime)
|
||||
return false;
|
||||
|
||||
++mExpired;
|
||||
|
||||
// just mark for removal here so objects can be removed in bulk outside the lock
|
||||
if (item.mValue != nullptr)
|
||||
objectsToRemove.push_back(std::move(item.mValue));
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
// note, actual unref happens outside of the lock
|
||||
// remove expired items from cache
|
||||
objectsToRemove.clear();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue