1
0
Fork 0
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:
Andy Lanzone 2025-07-19 18:10:08 -07:00
commit 6114a01ad4
22 changed files with 295 additions and 170 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

@ -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]);
}
}

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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;
}

View file

@ -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;

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

@ -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;
}
}
}
}

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

View file

@ -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;
}

View file

@ -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
{

View file

@ -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 = {};

View file

@ -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;
}
}

View file

@ -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;
};

View file

@ -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:

View file

@ -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)

View file

@ -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;

View file

@ -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 };

View file

@ -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();
}