diff --git a/CHANGELOG.md b/CHANGELOG.md index 21c1c55275..fd757b631c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,7 @@ Bug #7472: Crash when enchanting last projectiles Bug #7505: Distant terrain does not support sample size greater than cell size Bug #7553: Faction reaction loading is incorrect + Bug #7557: Terrain::ChunkManager::createChunk is called twice for the same position, lod on initial loading Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION @@ -77,6 +78,7 @@ Feature #6491: Add support for Qt6 Feature #6556: Lua API for sounds Feature #6726: Lua API for creating new objects + Feature #6864: Lua file access API Feature #6922: Improve launcher appearance Feature #6933: Support high-resolution cursor textures Feature #6945: Support S3TC-compressed and BGR/BGRA NiPixelData diff --git a/CMakeLists.txt b/CMakeLists.txt index f887cb181e..adfb7ca7f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -71,7 +71,7 @@ message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) set(OPENMW_VERSION_MINOR 49) set(OPENMW_VERSION_RELEASE 0) -set(OPENMW_LUA_API_REVISION 45) +set(OPENMW_LUA_API_REVISION 46) set(OPENMW_VERSION_COMMITHASH "") set(OPENMW_VERSION_TAGHASH "") diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 3df00f1be0..a8c233ff56 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -61,7 +61,7 @@ add_openmw_dir (mwscript add_openmw_dir (mwlua luamanagerimp object objectlists userdataserializer luaevents engineevents objectvariant context globalscripts localscripts playerscripts luabindings objectbindings cellbindings mwscriptbindings - camerabindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings + camerabindings vfsbindings uibindings soundbindings inputbindings nearbybindings postprocessingbindings stats debugbindings types/types types/door types/item types/actor types/container types/lockable types/weapon types/npc types/creature types/player types/activator types/book types/lockpick types/probe types/apparatus types/potion types/ingredient types/misc types/repair types/armor types/light types/static types/clothing types/levelledlist types/terminal worker magicbindings ) diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 6b79f3ab7e..97debd3298 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -2,6 +2,7 @@ #include +#include #include #include @@ -93,8 +94,10 @@ namespace int level = creatureStats.getLevel(); for (const ESM::Attribute& attribute : attributes) { - const ESM::Race::MaleFemale& value - = race->mData.mAttributeValues[static_cast(ESM::Attribute::refIdToIndex(attribute.mId))]; + auto index = ESM::Attribute::refIdToIndex(attribute.mId); + assert(index >= 0); + + const ESM::Race::MaleFemale& value = race->mData.mAttributeValues[static_cast(index)]; creatureStats.setAttribute(attribute.mId, male ? value.mMale : value.mFemale); } diff --git a/apps/openmw/mwlua/luabindings.cpp b/apps/openmw/mwlua/luabindings.cpp index b202322f60..d844670eca 100644 --- a/apps/openmw/mwlua/luabindings.cpp +++ b/apps/openmw/mwlua/luabindings.cpp @@ -44,6 +44,7 @@ #include "soundbindings.hpp" #include "types/types.hpp" #include "uibindings.hpp" +#include "vfsbindings.hpp" namespace MWLua { @@ -347,6 +348,7 @@ namespace MWLua { "openmw.core", initCorePackage(context) }, { "openmw.types", initTypesPackage(context) }, { "openmw.util", LuaUtil::initUtilPackage(lua) }, + { "openmw.vfs", initVFSPackage(context) }, }; } diff --git a/apps/openmw/mwlua/vfsbindings.cpp b/apps/openmw/mwlua/vfsbindings.cpp new file mode 100644 index 0000000000..ad32520649 --- /dev/null +++ b/apps/openmw/mwlua/vfsbindings.cpp @@ -0,0 +1,346 @@ +#include "vfsbindings.hpp" + +#include +#include +#include +#include +#include + +#include "../mwbase/environment.hpp" + +#include "context.hpp" +#include "luamanagerimp.hpp" + +namespace MWLua +{ + namespace + { + // Too many arguments may cause stack corruption and crash. + constexpr std::size_t sMaximumReadArguments = 20; + + // Print a message if we read a large chunk of file to string. + constexpr std::size_t sFileSizeWarningThreshold = 1024 * 1024; + + struct FileHandle + { + public: + FileHandle(Files::IStreamPtr stream, std::string_view fileName) + { + mFilePtr = std::move(stream); + mFileName = fileName; + } + + Files::IStreamPtr mFilePtr; + std::string mFileName; + }; + + std::ios_base::seekdir getSeekDir(FileHandle& self, std::string_view whence) + { + if (whence == "cur") + return std::ios_base::cur; + if (whence == "set") + return std::ios_base::beg; + if (whence == "end") + return std::ios_base::end; + + throw std::runtime_error( + "Error when handling '" + self.mFileName + "': invalid seek direction: '" + std::string(whence) + "'."); + } + + size_t getBytesLeftInStream(Files::IStreamPtr& file) + { + auto oldPos = file->tellg(); + file->seekg(0, std::ios_base::end); + auto newPos = file->tellg(); + file->seekg(oldPos, std::ios_base::beg); + + return newPos - oldPos; + } + + void printLargeDataMessage(FileHandle& file, size_t size) + { + if (!file.mFilePtr || !Settings::lua().mLuaDebug || size < sFileSizeWarningThreshold) + return; + + Log(Debug::Verbose) << "Read a large data chunk (" << size << " bytes) from '" << file.mFileName << "'."; + } + + sol::object readFile(LuaUtil::LuaState* lua, FileHandle& file) + { + std::ostringstream os; + if (file.mFilePtr && file.mFilePtr->peek() != EOF) + os << file.mFilePtr->rdbuf(); + + auto result = os.str(); + printLargeDataMessage(file, result.size()); + return sol::make_object(lua->sol(), std::move(result)); + } + + sol::object readLineFromFile(LuaUtil::LuaState* lua, FileHandle& file) + { + std::string result; + if (file.mFilePtr && std::getline(*file.mFilePtr, result)) + { + printLargeDataMessage(file, result.size()); + return sol::make_object(lua->sol(), result); + } + + return sol::nil; + } + + sol::object readNumberFromFile(LuaUtil::LuaState* lua, Files::IStreamPtr& file) + { + double number = 0; + if (file && *file >> number) + return sol::make_object(lua->sol(), number); + + return sol::nil; + } + + sol::object readCharactersFromFile(LuaUtil::LuaState* lua, FileHandle& file, size_t count) + { + if (count <= 0 && file.mFilePtr->peek() != EOF) + return sol::make_object(lua->sol(), std::string()); + + auto bytesLeft = getBytesLeftInStream(file.mFilePtr); + if (bytesLeft <= 0) + return sol::nil; + + if (count > bytesLeft) + count = bytesLeft; + + std::string result(count, '\0'); + if (file.mFilePtr->read(&result[0], count)) + { + printLargeDataMessage(file, result.size()); + return sol::make_object(lua->sol(), result); + } + + return sol::nil; + } + + void validateFile(const FileHandle& self) + { + if (self.mFilePtr) + return; + + throw std::runtime_error("Error when handling '" + self.mFileName + "': attempt to use a closed file."); + } + + sol::variadic_results seek( + LuaUtil::LuaState* lua, FileHandle& self, std::ios_base::seekdir dir, std::streamoff off) + { + sol::variadic_results values; + try + { + self.mFilePtr->seekg(off, dir); + if (self.mFilePtr->fail() || self.mFilePtr->bad()) + { + auto msg = "Failed to seek in file '" + self.mFileName + "'"; + values.push_back(sol::nil); + values.push_back(sol::make_object(lua->sol(), msg)); + } + else + values.push_back(sol::make_object(lua->sol(), self.mFilePtr->tellg())); + } + catch (std::exception& e) + { + auto msg = "Failed to seek in file '" + self.mFileName + "': " + std::string(e.what()); + values.push_back(sol::nil); + values.push_back(sol::make_object(lua->sol(), msg)); + } + + return values; + } + } + + sol::table initVFSPackage(const Context& context) + { + sol::table api(context.mLua->sol(), sol::create); + + auto vfs = MWBase::Environment::get().getResourceSystem()->getVFS(); + + sol::usertype handle = context.mLua->sol().new_usertype("FileHandle"); + handle["fileName"] = sol::readonly_property([](const FileHandle& self) { return self.mFileName; }); + handle[sol::meta_function::to_string] = [](const FileHandle& self) { + return "FileHandle{'" + self.mFileName + "'" + (!self.mFilePtr ? ", closed" : "") + "}"; + }; + handle["seek"] = sol::overload( + [lua = context.mLua](FileHandle& self, std::string_view whence, sol::optional offset) { + validateFile(self); + + auto off = static_cast(offset.value_or(0)); + auto dir = getSeekDir(self, whence); + + return seek(lua, self, dir, off); + }, + [lua = context.mLua](FileHandle& self, sol::optional offset) { + validateFile(self); + + auto off = static_cast(offset.value_or(0)); + + return seek(lua, self, std::ios_base::cur, off); + }); + handle["lines"] = [lua = context.mLua](FileHandle& self) { + return sol::as_function([&lua, &self]() mutable { + validateFile(self); + return readLineFromFile(lua, self); + }); + }; + + api["lines"] = [lua = context.mLua, vfs](std::string_view fileName) { + auto normalizedName = VFS::Path::normalizeFilename(fileName); + return sol::as_function( + [lua, file = FileHandle(vfs->getNormalized(normalizedName), normalizedName)]() mutable { + validateFile(file); + auto result = readLineFromFile(lua, file); + if (result == sol::nil) + file.mFilePtr.reset(); + + return result; + }); + }; + + handle["close"] = [lua = context.mLua](FileHandle& self) { + sol::variadic_results values; + try + { + self.mFilePtr.reset(); + if (self.mFilePtr) + { + auto msg = "Can not close file '" + self.mFileName + "': file handle is still opened."; + values.push_back(sol::nil); + values.push_back(sol::make_object(lua->sol(), msg)); + } + else + values.push_back(sol::make_object(lua->sol(), true)); + } + catch (std::exception& e) + { + auto msg = "Can not close file '" + self.mFileName + "': " + std::string(e.what()); + values.push_back(sol::nil); + values.push_back(sol::make_object(lua->sol(), msg)); + } + + return values; + }; + + handle["read"] = [lua = context.mLua](FileHandle& self, const sol::variadic_args args) { + validateFile(self); + + if (args.size() > sMaximumReadArguments) + throw std::runtime_error( + "Error when handling '" + self.mFileName + "': too many arguments for 'read'."); + + sol::variadic_results values; + // If there are no arguments, read a string + if (args.size() == 0) + { + values.push_back(readLineFromFile(lua, self)); + return values; + } + + bool success = true; + size_t i = 0; + for (i = 0; i < args.size() && success; i++) + { + if (args[i].is()) + { + auto format = args[i].as(); + + if (format == "*a" || format == "*all") + { + values.push_back(readFile(lua, self)); + continue; + } + + if (format == "*n" || format == "*number") + { + auto result = readNumberFromFile(lua, self.mFilePtr); + values.push_back(result); + if (result == sol::nil) + success = false; + continue; + } + + if (format == "*l" || format == "*line") + { + auto result = readLineFromFile(lua, self); + values.push_back(result); + if (result == sol::nil) + success = false; + continue; + } + + throw std::runtime_error("Error when handling '" + self.mFileName + "': bad argument #" + + std::to_string(i + 1) + " to 'read' (invalid format)"); + } + else if (args[i].is()) + { + int number = args[i].as(); + auto result = readCharactersFromFile(lua, self, number); + values.push_back(result); + if (result == sol::nil) + success = false; + } + } + + // We should return nil if we just reached the end of stream + if (!success && self.mFilePtr->eof()) + return values; + + if (!success && (self.mFilePtr->fail() || self.mFilePtr->bad())) + { + auto msg = "Error when handling '" + self.mFileName + "': can not read data for argument #" + + std::to_string(i); + values.push_back(sol::make_object(lua->sol(), msg)); + } + + return values; + }; + + api["open"] = [lua = context.mLua, vfs](std::string_view fileName) { + sol::variadic_results values; + try + { + auto normalizedName = VFS::Path::normalizeFilename(fileName); + auto handle = FileHandle(vfs->getNormalized(normalizedName), normalizedName); + values.push_back(sol::make_object(lua->sol(), std::move(handle))); + } + catch (std::exception& e) + { + auto msg = "Can not open file: " + std::string(e.what()); + values.push_back(sol::nil); + values.push_back(sol::make_object(lua->sol(), msg)); + } + + return values; + }; + + api["type"] = sol::overload( + [](const FileHandle& handle) -> std::string { + if (handle.mFilePtr) + return "file"; + + return "closed file"; + }, + [](const sol::object&) -> sol::object { return sol::nil; }); + + api["fileExists"] = [vfs](std::string_view fileName) -> bool { return vfs->exists(fileName); }; + api["pathsWithPrefix"] = [vfs](std::string_view prefix) { + auto iterator = vfs->getRecursiveDirectoryIterator(prefix); + return sol::as_function([iterator, current = iterator.begin()]() mutable -> sol::optional { + if (current != iterator.end()) + { + const std::string& result = *current; + ++current; + return result; + } + + return sol::nullopt; + }); + }; + + return LuaUtil::makeReadOnly(api); + } +} diff --git a/apps/openmw/mwlua/vfsbindings.hpp b/apps/openmw/mwlua/vfsbindings.hpp new file mode 100644 index 0000000000..b251db6fd4 --- /dev/null +++ b/apps/openmw/mwlua/vfsbindings.hpp @@ -0,0 +1,13 @@ +#ifndef MWLUA_VFSBINDINGS_H +#define MWLUA_VFSBINDINGS_H + +#include + +#include "context.hpp" + +namespace MWLua +{ + sol::table initVFSPackage(const Context&); +} + +#endif // MWLUA_VFSBINDINGS_H diff --git a/apps/openmw/mwmechanics/magiceffects.cpp b/apps/openmw/mwmechanics/magiceffects.cpp index 0d626c9e11..477e8b36a6 100644 --- a/apps/openmw/mwmechanics/magiceffects.cpp +++ b/apps/openmw/mwmechanics/magiceffects.cpp @@ -48,9 +48,9 @@ namespace MWMechanics std::string EffectKey::toString() const { const auto& store = MWBase::Environment::get().getESMStore(); - const ESM::MagicEffect* magicEffect = store->get().search(mId); + const ESM::MagicEffect* magicEffect = store->get().find(mId); return getMagicEffectString( - *magicEffect, store->get().search(mArg), store->get().search(mArg)); + *magicEffect, store->get().find(mArg), store->get().find(mArg)); } bool operator<(const EffectKey& left, const EffectKey& right) diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp index ff720345bc..36ff9db7ac 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -1,5 +1,7 @@ #include "mechanicsmanagerimp.hpp" +#include + #include #include @@ -150,9 +152,10 @@ namespace MWMechanics for (const ESM::Attribute& attribute : esmStore.get()) { - const ESM::Race::MaleFemale& value - = race->mData.mAttributeValues[static_cast(ESM::Attribute::refIdToIndex(attribute.mId))]; + auto index = ESM::Attribute::refIdToIndex(attribute.mId); + assert(index >= 0); + const ESM::Race::MaleFemale& value = race->mData.mAttributeValues[static_cast(index)]; creatureStats.setAttribute(attribute.mId, male ? value.mMale : value.mFemale); } diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 3427e460c3..1ee463e622 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -488,7 +488,12 @@ void MWMechanics::NpcStats::writeState(ESM::NpcStats& state) const state.mSkillIncrease.fill(0); for (const auto& [key, value] : mSkillIncreases) - state.mSkillIncrease[static_cast(ESM::Attribute::refIdToIndex(key))] = value; + { + // TODO extend format + auto index = ESM::Attribute::refIdToIndex(key); + assert(index >= 0); + state.mSkillIncrease[static_cast(index)] = value; + } for (size_t i = 0; i < state.mSpecIncreases.size(); ++i) state.mSpecIncreases[i] = mSpecIncreases[i]; diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index ff6752358e..61de2ab90d 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -556,7 +556,7 @@ namespace MWWorld return false; } - CellStore::CellStore(MWWorld::Cell cell, const MWWorld::ESMStore& esmStore, ESM::ReadersCache& readers) + CellStore::CellStore(MWWorld::Cell&& cell, const MWWorld::ESMStore& esmStore, ESM::ReadersCache& readers) : mStore(esmStore) , mReaders(readers) , mCellVariant(std::move(cell)) diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index 496e98bf1a..23bd071ff1 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -140,7 +140,7 @@ namespace MWWorld } /// @param readerList The readers to use for loading of the cell on-demand. - CellStore(MWWorld::Cell cell, const MWWorld::ESMStore& store, ESM::ReadersCache& readers); + CellStore(MWWorld::Cell&& cell, const MWWorld::ESMStore& store, ESM::ReadersCache& readers); CellStore(const CellStore&) = delete; diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index d76dc36c15..e643f947aa 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -159,9 +159,16 @@ MWWorld::ContainerStore::ContainerStore() MWWorld::ContainerStore::~ContainerStore() { - MWWorld::WorldModel* worldModel = MWBase::Environment::get().getWorldModel(); - for (MWWorld::ContainerStoreIterator iter(begin()); iter != end(); ++iter) - worldModel->deregisterPtr(*iter); + try + { + MWWorld::WorldModel* worldModel = MWBase::Environment::get().getWorldModel(); + for (MWWorld::ContainerStoreIterator iter(begin()); iter != end(); ++iter) + worldModel->deregisterPtr(*iter); + } + catch (const std::exception& e) + { + Log(Debug::Error) << "Failed to deregister container store: " << e.what(); + } } MWWorld::ConstContainerStoreIterator MWWorld::ContainerStore::cbegin(int mask) const diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 78c8ccab1d..b5aff875bd 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -834,9 +834,6 @@ namespace MWWorld mPreloader = std::make_unique(rendering.getResourceSystem(), physics->getShapeManager(), rendering.getTerrain(), rendering.getLandManager()); mPreloader->setWorkQueue(mRendering.getWorkQueue()); - - rendering.getResourceSystem()->setExpiryDelay(Settings::cells().mCacheExpiryDelay); - mPreloader->setExpiryDelay(Settings::cells().mPreloadCellExpiryDelay); mPreloader->setMinCacheSize(Settings::cells().mPreloadCellCacheMin); mPreloader->setMaxCacheSize(Settings::cells().mPreloadCellCacheMax); diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index a4b8548e52..6e5b56d9ff 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1017,11 +1017,12 @@ namespace MWWorld void Store::setUp() { auto addSetting = [&](const std::string& key, ESM::Variant value) { + auto id = ESM::RefId::stringRefId(key); ESM::GameSetting setting; setting.blank(); - setting.mId = ESM::RefId::stringRefId(key); + setting.mId = id; setting.mValue = std::move(value); - auto [iter, inserted] = mStatic.insert_or_assign(setting.mId, std::move(setting)); + auto [iter, inserted] = mStatic.insert_or_assign(id, std::move(setting)); if (inserted) mShared.push_back(&iter->second); }; diff --git a/apps/openmw_test_suite/nif/node.hpp b/apps/openmw_test_suite/nif/node.hpp index 7e413d03cd..2d82289592 100644 --- a/apps/openmw_test_suite/nif/node.hpp +++ b/apps/openmw_test_suite/nif/node.hpp @@ -13,7 +13,7 @@ namespace Nif::Testing inline void init(Extra& value) { - value.next = ExtraPtr(nullptr); + value.mNext = ExtraPtr(nullptr); } inline void init(Named& value) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 25df366d23..49030a8902 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -996,7 +996,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) { - mNiStringExtraData.string = "NCC__"; + mNiStringExtraData.mData = "NCC__"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); @@ -1024,8 +1024,8 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_not_first_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) { - mNiStringExtraData.next = Nif::ExtraPtr(&mNiStringExtraData2); - mNiStringExtraData2.string = "NCC__"; + mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2); + mNiStringExtraData2.mData = "NCC__"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); @@ -1052,7 +1052,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) { - mNiStringExtraData.string = "NC___"; + mNiStringExtraData.mData = "NC___"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); @@ -1079,8 +1079,8 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) { - mNiStringExtraData.next = Nif::ExtraPtr(&mNiStringExtraData2); - mNiStringExtraData2.string = "NC___"; + mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2); + mNiStringExtraData2.mData = "NC___"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); @@ -1141,7 +1141,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_mrk_should_return_shape_with_null_collision_shape) { - mNiStringExtraData.string = "MRK"; + mNiStringExtraData.mData = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); @@ -1160,7 +1160,7 @@ namespace TEST_F(TestBulletNifLoader, bsx_editor_marker_flag_disables_collision_for_markers) { - mNiIntegerExtraData.data = 32; // BSX flag "editor marker" + mNiIntegerExtraData.mData = 32; // BSX flag "editor marker" mNiIntegerExtraData.recType = Nif::RC_BSXFlags; mNiTriShape.extralist.push_back(Nif::ExtraPtr(&mNiIntegerExtraData)); mNiTriShape.parents.push_back(&mNiNode); @@ -1181,7 +1181,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_mrk_and_other_collision_node_should_return_shape_with_triangle_mesh_shape_with_all_meshes) { - mNiStringExtraData.string = "MRK"; + mNiStringExtraData.mData = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode2); diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 71e508e882..6b78e471e6 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -42,7 +42,7 @@ list (APPEND COMPONENT_FILES "${OpenMW_BINARY_DIR}/${VERSION_CPP_FILE}") # source files add_component_dir (lua - luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage + luastate scriptscontainer asyncpackage utilpackage serialization configuration l10n storage utf8 shapes/box ) @@ -111,7 +111,7 @@ add_component_dir (sceneutil ) add_component_dir (nif - controlled effect niftypes record controller extra node record_ptr data niffile property nifkey base nifstream physics + base controller data effect extra niffile nifkey nifstream niftypes node particle physics property record record_ptr texture ) add_component_dir (nifosg diff --git a/components/lua/luastate.cpp b/components/lua/luastate.cpp index 2269a40c26..95b56fb020 100644 --- a/components/lua/luastate.cpp +++ b/components/lua/luastate.cpp @@ -12,6 +12,7 @@ #include #include "scriptscontainer.hpp" +#include "utf8.hpp" namespace LuaUtil { @@ -51,7 +52,7 @@ namespace LuaUtil static const std::string safeFunctions[] = { "assert", "error", "ipairs", "next", "pairs", "pcall", "select", "tonumber", "tostring", "type", "unpack", "xpcall", "rawequal", "rawget", "rawset", "setmetatable" }; - static const std::string safePackages[] = { "coroutine", "math", "string", "table" }; + static const std::string safePackages[] = { "coroutine", "math", "string", "table", "utf8" }; static constexpr int64_t countHookStep = 1000; @@ -181,6 +182,8 @@ namespace LuaUtil mSol["math"]["randomseed"](static_cast(std::time(nullptr))); mSol["math"]["randomseed"] = [] {}; + mSol["utf8"] = LuaUtf8::initUtf8Package(mSol); + mSol["writeToLog"] = [](std::string_view s) { Log(Debug::Level::Info) << s; }; mSol["setEnvironment"] diff --git a/components/lua/utf8.cpp b/components/lua/utf8.cpp new file mode 100644 index 0000000000..83228afa65 --- /dev/null +++ b/components/lua/utf8.cpp @@ -0,0 +1,233 @@ +#include +#include + +#include "utf8.hpp" + +namespace +{ + constexpr std::string_view UTF8PATT = "[%z\x01-\x7F\xC2-\xF4][\x80-\xBF]*"; // %z is deprecated in Lua5.2 + constexpr uint32_t MAXUTF = 0x7FFFFFFFu; + // constexpr uint32_t MAXUNICODE = 0x10FFFFu; + + inline bool isNilOrNone(const sol::stack_proxy arg) + { + return (arg.get_type() == sol::type::lua_nil || arg.get_type() == sol::type::none); + } + + inline double getInteger(const sol::stack_proxy arg, const size_t n, std::string_view name) + { + double integer; + if (!arg.is()) + throw std::runtime_error(Misc::StringUtils::format("bad argument #%i to '%s' (number expected, got %s)", n, + name, sol::type_name(arg.lua_state(), arg.get_type()))); + + if (std::modf(arg, &integer) != 0) + throw std::runtime_error( + Misc::StringUtils::format("bad argument #{} to '{}' (number has no integer representation)", n, name)); + + return integer; + } + + // If the input 'pos' is negative, it is treated as counting from the end of the string, + // where -1 represents the last character position, -2 represents the second-to-last position, + // and so on. If 'pos' is non-negative, it is used as-is. + inline void relativePosition(int64_t& pos, const size_t len) + { + if (pos < 0) + pos = std::max(0, pos + len + 1); + } + + // returns: first - character pos in bytes, second - character codepoint + std::pair decodeNextUTF8Character(std::string_view s, std::vector& pos_byte) + { + const int64_t pos = pos_byte.back() - 1; + const unsigned char ch = static_cast(s[pos]); + int64_t codepoint = -1; + size_t byteSize = 0; + + if ((ch & 0b10000000) == 0) + { + codepoint = ch; + byteSize = 1; + } + else if ((ch & 0b11100000) == 0b11000000) + { + codepoint = ch & 0b00011111; + byteSize = 2; + } + else if ((ch & 0b11110000) == 0b11100000) + { + codepoint = ch & 0b00001111; + byteSize = 3; + } + else if ((ch & 0b11111000) == 0b11110000) + { + codepoint = ch & 0b00000111; + byteSize = 4; + } + + // construct codepoint for non-ascii + for (size_t i = 1; i < byteSize; ++i) + { + // if not a continuation byte + if ((pos + i) >= s.size() || (static_cast(s[pos + i]) & 0b11000000) != 0b10000000) + { + return std::make_pair(0, -1); + } + codepoint = (codepoint << 6) | (static_cast(s[pos + i]) & 0b00111111); + } + + std::pair res = std::make_pair(pos_byte.back(), codepoint); + + pos_byte.push_back(pos_byte.back() + byteSize); /* the next character (if exists) starts at this byte */ + + return res; + } + +} + +namespace LuaUtf8 +{ + sol::table initUtf8Package(sol::state_view& lua) + { + sol::table utf8(lua, sol::create); + + utf8["charpattern"] = UTF8PATT; + + utf8["char"] = [](const sol::variadic_args args) -> std::string { + std::string result{}; + std::wstring_convert> converter; + for (size_t i = 0; i < args.size(); ++i) + { + int64_t codepoint = getInteger(args[i], (i + 1), "char"); + if (codepoint < 0 || codepoint > MAXUTF) + throw std::runtime_error( + Misc::StringUtils::format("bad argument #{} to 'char' (value out of range)", (i + 1))); + + result += converter.to_bytes(codepoint); + } + return result; + }; + + utf8["codes"] = [](std::string_view s) { + std::vector pos_byte{ 1 }; + return sol::as_function([s, pos_byte]() mutable -> sol::optional> { + if (pos_byte.back() <= static_cast(s.size())) + { + const auto pair = decodeNextUTF8Character(s, pos_byte); + if (pair.second == -1) + throw std::runtime_error("Invalid UTF-8 code at position " + std::to_string(pos_byte.size())); + + return pair; + } + return sol::nullopt; + }); + }; + + utf8["len"] = [](std::string_view s, + const sol::variadic_args args) -> std::variant> { + const size_t len = s.size(); + int64_t iv = isNilOrNone(args[0]) ? 1 : getInteger(args[0], 2, "len"); + int64_t fv = isNilOrNone(args[1]) ? -1 : getInteger(args[1], 3, "len"); + + relativePosition(iv, len); + relativePosition(fv, len); + + if (iv <= 0) + throw std::runtime_error("bad argument #2 to 'len' (initial position out of bounds)"); + if (fv > static_cast(len)) + throw std::runtime_error("bad argument #3 to 'len' (final position out of bounds)"); + + if (len == 0) + return len; + + std::vector pos_byte = { iv }; + + while (pos_byte.back() <= fv) + { + if (decodeNextUTF8Character(s, pos_byte).second == -1) + return std::pair(sol::lua_nil, pos_byte.back()); + } + return pos_byte.size() - 1; + }; + + utf8["codepoint"] + = [](std::string_view s, const sol::variadic_args args) -> sol::as_returns_t> { + size_t len = s.size(); + int64_t iv = isNilOrNone(args[0]) ? 1 : getInteger(args[0], 2, "codepoint"); + int64_t fv = isNilOrNone(args[1]) ? iv : getInteger(args[1], 3, "codepoint"); + + relativePosition(iv, len); + relativePosition(fv, len); + + if (iv <= 0) + throw std::runtime_error("bad argument #2 to 'codepoint' (initial position out of bounds)"); + if (fv > static_cast(len)) + throw std::runtime_error("bad argument #3 to 'codepoint' (final position out of bounds)"); + + if (iv > fv) + return sol::as_returns(std::vector{}); /* empty interval; return nothing */ + + std::vector pos_byte = { iv }; + std::vector codepoints; + + while (pos_byte.back() <= fv) + { + codepoints.push_back(decodeNextUTF8Character(s, pos_byte).second); + if (codepoints.back() == -1) + throw std::runtime_error("Invalid UTF-8 code at position " + std::to_string(pos_byte.size())); + } + + return sol::as_returns(std::move(codepoints)); + }; + + utf8["offset"] + = [](std::string_view s, const int64_t n, const sol::variadic_args args) -> sol::optional { + size_t len = s.size(); + int64_t iv; + + if (isNilOrNone(args[0])) + { + if (n >= 0) + iv = 1; + else + iv = s.size() + 1; + } + else + iv = getInteger(args[0], 3, "offset"); + + std::vector pos_byte = { 1 }; + + relativePosition(iv, len); + + if (iv > static_cast(len) + 1) + throw std::runtime_error("bad argument #3 to 'offset' (position out of bounds)"); + + while (pos_byte.back() <= static_cast(len)) + decodeNextUTF8Character(s, pos_byte); + + for (auto it = pos_byte.begin(); it != pos_byte.end(); ++it) + { + if (*it == iv) + { + if (n <= 0 && it + n >= pos_byte.begin()) + return *(it + n); + if (n > 0 && it + n - 1 < pos_byte.end()) + return *(it + n - 1); + break; + } + else if (*it > iv) /* a continuation byte */ + { + if (n == 0) + return *(it - 1); /* special case */ + else + throw std::runtime_error("initial position is a continuation byte"); + } + } + + return sol::nullopt; + }; + + return utf8; + } +} diff --git a/components/lua/utf8.hpp b/components/lua/utf8.hpp new file mode 100644 index 0000000000..dd936b3b5e --- /dev/null +++ b/components/lua/utf8.hpp @@ -0,0 +1,11 @@ +#ifndef COMPONENTS_LUA_UTF8_H +#define COMPONENTS_LUA_UTF8_H + +#include + +namespace LuaUtf8 +{ + sol::table initUtf8Package(sol::state_view&); +} + +#endif diff --git a/components/nif/base.cpp b/components/nif/base.cpp index ed440cd96d..af98cfa16d 100644 --- a/components/nif/base.cpp +++ b/components/nif/base.cpp @@ -5,11 +5,11 @@ namespace Nif void Extra::read(NIFStream* nif) { if (nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 0)) - name = nif->getString(); + nif->read(mName); else if (nif->getVersion() <= NIFStream::generateVersion(4, 2, 2, 0)) { - next.read(nif); - recordSize = nif->getUInt(); + mNext.read(nif); + nif->read(mRecordSize); } } diff --git a/components/nif/base.hpp b/components/nif/base.hpp index 2cdbdec77f..69094ffc51 100644 --- a/components/nif/base.hpp +++ b/components/nif/base.hpp @@ -13,12 +13,12 @@ namespace Nif // An extra data record. All the extra data connected to an object form a linked list. struct Extra : public Record { - std::string name; - ExtraPtr next; // Next extra data record in the list - unsigned int recordSize{ 0u }; + std::string mName; + ExtraPtr mNext; // Next extra data record in the list + uint32_t mRecordSize{ 0u }; void read(NIFStream* nif) override; - void post(Reader& nif) override { next.post(nif); } + void post(Reader& nif) override { mNext.post(nif); } }; struct Controller : public Record diff --git a/components/nif/controlled.cpp b/components/nif/controlled.cpp deleted file mode 100644 index bacba07d5f..0000000000 --- a/components/nif/controlled.cpp +++ /dev/null @@ -1,140 +0,0 @@ -#include "controlled.hpp" - -#include "data.hpp" - -namespace Nif -{ - - void NiSourceTexture::read(NIFStream* nif) - { - Named::read(nif); - - external = nif->getChar() != 0; - bool internal = false; - if (external) - filename = nif->getString(); - else - { - if (nif->getVersion() <= NIFStream::generateVersion(10, 0, 1, 3)) - internal = nif->getChar(); - if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - filename = nif->getString(); // Original file path of the internal texture - } - if (nif->getVersion() <= NIFStream::generateVersion(10, 0, 1, 3)) - { - if (!external && internal) - data.read(nif); - } - else - { - data.read(nif); - } - - pixel = nif->getUInt(); - mipmap = nif->getUInt(); - alpha = nif->getUInt(); - - // Renderer hints, typically of no use for us - /* bool mIsStatic = */ nif->getChar(); - if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 103)) - /* bool mDirectRendering = */ nif->getBoolean(); - if (nif->getVersion() >= NIFStream::generateVersion(20, 2, 0, 4)) - /* bool mPersistRenderData = */ nif->getBoolean(); - } - - void NiSourceTexture::post(Reader& nif) - { - Named::post(nif); - data.post(nif); - } - - void BSShaderTextureSet::read(NIFStream* nif) - { - nif->getSizedStrings(textures, nif->getUInt()); - } - - void NiParticleModifier::read(NIFStream* nif) - { - next.read(nif); - controller.read(nif); - } - - void NiParticleModifier::post(Reader& nif) - { - next.post(nif); - controller.post(nif); - } - - void NiParticleGrowFade::read(NIFStream* nif) - { - NiParticleModifier::read(nif); - growTime = nif->getFloat(); - fadeTime = nif->getFloat(); - } - - void NiParticleColorModifier::read(NIFStream* nif) - { - NiParticleModifier::read(nif); - data.read(nif); - } - - void NiParticleColorModifier::post(Reader& nif) - { - NiParticleModifier::post(nif); - data.post(nif); - } - - void NiGravity::read(NIFStream* nif) - { - NiParticleModifier::read(nif); - - mDecay = nif->getFloat(); - mForce = nif->getFloat(); - mType = nif->getUInt(); - mPosition = nif->getVector3(); - mDirection = nif->getVector3(); - } - - void NiParticleCollider::read(NIFStream* nif) - { - NiParticleModifier::read(nif); - - mBounceFactor = nif->getFloat(); - if (nif->getVersion() >= NIFStream::generateVersion(4, 2, 0, 2)) - { - // Unused in NifSkope. Need to figure out what these do. - /*bool mSpawnOnCollision = */ nif->getBoolean(); - /*bool mDieOnCollision = */ nif->getBoolean(); - } - } - - void NiPlanarCollider::read(NIFStream* nif) - { - NiParticleCollider::read(nif); - - mExtents = nif->getVector2(); - mPosition = nif->getVector3(); - mXVector = nif->getVector3(); - mYVector = nif->getVector3(); - mPlaneNormal = nif->getVector3(); - mPlaneDistance = nif->getFloat(); - } - - void NiParticleRotation::read(NIFStream* nif) - { - NiParticleModifier::read(nif); - - /* bool mRandomInitialAxis = */ nif->getChar(); - /* osg::Vec3f mInitialAxis = */ nif->getVector3(); - /* float mRotationSpeed = */ nif->getFloat(); - } - - void NiSphericalCollider::read(NIFStream* nif) - { - NiParticleCollider::read(nif); - - mRadius = nif->getFloat(); - mCenter = nif->getVector3(); - } - -} diff --git a/components/nif/controlled.hpp b/components/nif/controlled.hpp deleted file mode 100644 index 09bab3cbfd..0000000000 --- a/components/nif/controlled.hpp +++ /dev/null @@ -1,157 +0,0 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: https://openmw.org/ - - This file (controlled.h) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - https://www.gnu.org/licenses/ . - - */ - -#ifndef OPENMW_COMPONENTS_NIF_CONTROLLED_HPP -#define OPENMW_COMPONENTS_NIF_CONTROLLED_HPP - -#include "base.hpp" - -namespace Nif -{ - - struct NiSourceTexture : public Named - { - // Is this an external (references a separate texture file) or - // internal (data is inside the nif itself) texture? - bool external; - - std::string filename; // In case of external textures - NiPixelDataPtr data; // In case of internal textures - - /* Pixel layout - 0 - Palettised - 1 - High color 16 - 2 - True color 32 - 3 - Compressed - 4 - Bumpmap - 5 - Default */ - unsigned int pixel; - - /* Mipmap format - 0 - no - 1 - yes - 2 - default */ - unsigned int mipmap; - - /* Alpha - 0 - none - 1 - binary - 2 - smooth - 3 - default (use material alpha, or multiply material with texture if present) - */ - unsigned int alpha; - - void read(NIFStream* nif) override; - void post(Reader& nif) override; - }; - - struct BSShaderTextureSet : public Record - { - enum TextureType - { - TextureType_Base = 0, - TextureType_Normal = 1, - TextureType_Glow = 2, - TextureType_Parallax = 3, - TextureType_Env = 4, - TextureType_EnvMask = 5, - TextureType_Subsurface = 6, - TextureType_BackLighting = 7 - }; - std::vector textures; - - void read(NIFStream* nif) override; - }; - - struct NiParticleModifier : public Record - { - NiParticleModifierPtr next; - ControllerPtr controller; - - void read(NIFStream* nif) override; - void post(Reader& nif) override; - }; - - struct NiParticleGrowFade : public NiParticleModifier - { - float growTime; - float fadeTime; - - void read(NIFStream* nif) override; - }; - - struct NiParticleColorModifier : public NiParticleModifier - { - NiColorDataPtr data; - - void read(NIFStream* nif) override; - void post(Reader& nif) override; - }; - - struct NiGravity : public NiParticleModifier - { - float mForce; - /* 0 - Wind (fixed direction) - * 1 - Point (fixed origin) - */ - int mType; - float mDecay; - osg::Vec3f mPosition; - osg::Vec3f mDirection; - - void read(NIFStream* nif) override; - }; - - struct NiParticleCollider : public NiParticleModifier - { - float mBounceFactor; - void read(NIFStream* nif) override; - }; - - // NiPinaColada - struct NiPlanarCollider : public NiParticleCollider - { - osg::Vec2f mExtents; - osg::Vec3f mPosition; - osg::Vec3f mXVector, mYVector; - osg::Vec3f mPlaneNormal; - float mPlaneDistance; - - void read(NIFStream* nif) override; - }; - - struct NiSphericalCollider : public NiParticleCollider - { - float mRadius; - osg::Vec3f mCenter; - - void read(NIFStream* nif) override; - }; - - struct NiParticleRotation : public NiParticleModifier - { - void read(NIFStream* nif) override; - }; - -} // Namespace -#endif diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index 9e93b31162..1355ce21d3 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -1,9 +1,9 @@ #include "controller.hpp" -#include "controlled.hpp" #include "data.hpp" #include "node.hpp" -#include "recordptr.hpp" +#include "particle.hpp" +#include "texture.hpp" namespace Nif { diff --git a/components/nif/effect.cpp b/components/nif/effect.cpp index 3b90d50564..36407f8dc2 100644 --- a/components/nif/effect.cpp +++ b/components/nif/effect.cpp @@ -1,7 +1,7 @@ #include "effect.hpp" -#include "controlled.hpp" #include "node.hpp" +#include "texture.hpp" namespace Nif { @@ -9,55 +9,61 @@ namespace Nif void NiDynamicEffect::read(NIFStream* nif) { Node::read(nif); - if (nif->getVersion() >= nif->generateVersion(10, 1, 0, 106) - && nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4) - nif->getBoolean(); // Switch state - if (nif->getVersion() <= NIFFile::VER_MW - || (nif->getVersion() >= nif->generateVersion(10, 1, 0, 0) - && nif->getBethVersion() < NIFFile::BethVersion::BETHVER_FO4)) - { - size_t numAffectedNodes = nif->get(); - nif->skip(numAffectedNodes * 4); - } + + if (nif->getVersion() > NIFFile::VER_MW && nif->getVersion() < nif->generateVersion(10, 1, 0, 0)) + return; + + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_FO4) + return; + + if (nif->getVersion() >= nif->generateVersion(10, 1, 0, 106)) + nif->read(mSwitchState); + size_t numAffectedNodes = nif->get(); + nif->skip(numAffectedNodes * 4); } void NiLight::read(NIFStream* nif) { NiDynamicEffect::read(nif); - dimmer = nif->getFloat(); - ambient = nif->getVector3(); - diffuse = nif->getVector3(); - specular = nif->getVector3(); + mDimmer = nif->getFloat(); + mAmbient = nif->getVector3(); + mDiffuse = nif->getVector3(); + mSpecular = nif->getVector3(); + } + + void NiPointLight::read(NIFStream* nif) + { + NiLight::read(nif); + + mConstantAttenuation = nif->getFloat(); + mLinearAttenuation = nif->getFloat(); + mQuadraticAttenuation = nif->getFloat(); + } + + void NiSpotLight::read(NIFStream* nif) + { + NiPointLight::read(nif); + + mCutoff = nif->getFloat(); + mExponent = nif->getFloat(); } void NiTextureEffect::read(NIFStream* nif) { NiDynamicEffect::read(nif); - // Model Projection Matrix - nif->skip(3 * 3 * sizeof(float)); - - // Model Projection Transform - nif->skip(3 * sizeof(float)); - - // Texture Filtering - nif->skip(4); - - // Max anisotropy samples + nif->read(mProjectionRotation); + nif->read(mProjectionPosition); + nif->read(mFilterMode); if (nif->getVersion() >= NIFStream::generateVersion(20, 5, 0, 4)) - nif->skip(2); - - clamp = nif->getUInt(); - - textureType = (TextureType)nif->getUInt(); - - coordGenType = (CoordGenType)nif->getUInt(); - - texture.read(nif); - - nif->skip(1); // Use clipping plane - nif->skip(16); // Clipping plane dimensions vector + nif->read(mMaxAnisotropy); + nif->read(mClampMode); + mTextureType = static_cast(nif->get()); + mCoordGenType = static_cast(nif->get()); + mTexture.read(nif); + nif->read(mEnableClipPlane); + mClipPlane = osg::Plane(nif->get()); if (nif->getVersion() <= NIFStream::generateVersion(10, 2, 0, 0)) nif->skip(4); // PS2-specific shorts if (nif->getVersion() <= NIFStream::generateVersion(4, 1, 0, 12)) @@ -67,24 +73,8 @@ namespace Nif void NiTextureEffect::post(Reader& nif) { NiDynamicEffect::post(nif); - texture.post(nif); - } - void NiPointLight::read(NIFStream* nif) - { - NiLight::read(nif); - - constantAttenuation = nif->getFloat(); - linearAttenuation = nif->getFloat(); - quadraticAttenuation = nif->getFloat(); - } - - void NiSpotLight::read(NIFStream* nif) - { - NiPointLight::read(nif); - - cutoff = nif->getFloat(); - exponent = nif->getFloat(); + mTexture.post(nif); } } diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index ea9e21f003..06f85cd5d5 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -29,67 +29,75 @@ namespace Nif { + // Abstract struct NiDynamicEffect : public Node { + bool mSwitchState{ true }; void read(NIFStream* nif) override; }; - // Used as base for NiAmbientLight, NiDirectionalLight, NiPointLight and NiSpotLight. + // Abstract light source struct NiLight : NiDynamicEffect { - float dimmer; - osg::Vec3f ambient; - osg::Vec3f diffuse; - osg::Vec3f specular; + float mDimmer; + osg::Vec3f mAmbient; + osg::Vec3f mDiffuse; + osg::Vec3f mSpecular; void read(NIFStream* nif) override; }; struct NiPointLight : public NiLight { - float constantAttenuation; - float linearAttenuation; - float quadraticAttenuation; + float mConstantAttenuation; + float mLinearAttenuation; + float mQuadraticAttenuation; void read(NIFStream* nif) override; }; struct NiSpotLight : public NiPointLight { - float cutoff; - float exponent; + float mCutoff; + float mExponent; void read(NIFStream* nif) override; }; struct NiTextureEffect : NiDynamicEffect { - NiSourceTexturePtr texture; - unsigned int clamp; - - enum TextureType + enum class TextureType : uint32_t { - Projected_Light = 0, - Projected_Shadow = 1, - Environment_Map = 2, - Fog_Map = 3 + ProjectedLight = 0, + ProjectedShadow = 1, + EnvironmentMap = 2, + FogMap = 3, }; - TextureType textureType; - enum CoordGenType + enum class CoordGenType : uint32_t { - World_Parallel = 0, - World_Perspective, - Sphere_Map, - Specular_Cube_Map, - Diffuse_Cube_Map + WorldParallel = 0, + WorldPerspective = 1, + SphereMap = 2, + SpecularCubeMap = 3, + DiffuseCubeMap = 4, }; - CoordGenType coordGenType; + + Matrix3 mProjectionRotation; + osg::Vec3f mProjectionPosition; + uint32_t mFilterMode; + NiSourceTexturePtr mTexture; + uint16_t mMaxAnisotropy{ 0 }; + uint32_t mClampMode; + TextureType mTextureType; + CoordGenType mCoordGenType; + uint8_t mEnableClipPlane; + osg::Plane mClipPlane; void read(NIFStream* nif) override; void post(Reader& nif) override; - bool wrapT() const { return clamp & 1; } - bool wrapS() const { return (clamp >> 1) & 1; } + bool wrapT() const { return mClampMode & 1; } + bool wrapS() const { return mClampMode & 2; } }; } // Namespace diff --git a/components/nif/extra.cpp b/components/nif/extra.cpp index 4384289f9e..e289ae626e 100644 --- a/components/nif/extra.cpp +++ b/components/nif/extra.cpp @@ -6,25 +6,21 @@ namespace Nif void NiExtraData::read(NIFStream* nif) { Extra::read(nif); - nif->readVector(data, recordSize); - } - void NiStringExtraData::read(NIFStream* nif) - { - Extra::read(nif); - string = nif->getString(); + nif->readVector(mData, mRecordSize); } void NiTextKeyExtraData::read(NIFStream* nif) { Extra::read(nif); - int keynum = nif->getInt(); - list.resize(keynum); - for (int i = 0; i < keynum; i++) + uint32_t numKeys; + nif->read(numKeys); + mList.resize(numKeys); + for (TextKey& key : mList) { - list[i].time = nif->getFloat(); - list[i].text = nif->getString(); + nif->read(key.mTime); + nif->read(key.mText); } } @@ -32,81 +28,39 @@ namespace Nif { Extra::read(nif); - nif->skip(nif->getUShort() * sizeof(float)); // vertex weights I guess - } - - void NiIntegerExtraData::read(NIFStream* nif) - { - Extra::read(nif); - - data = nif->getUInt(); - } - - void NiIntegersExtraData::read(NIFStream* nif) - { - Extra::read(nif); - - nif->readVector(data, nif->getUInt()); - } - - void NiBinaryExtraData::read(NIFStream* nif) - { - Extra::read(nif); - nif->readVector(data, nif->getUInt()); - } - - void NiBooleanExtraData::read(NIFStream* nif) - { - Extra::read(nif); - data = nif->getBoolean(); - } - - void NiVectorExtraData::read(NIFStream* nif) - { - Extra::read(nif); - data = nif->getVector4(); - } - - void NiFloatExtraData::read(NIFStream* nif) - { - Extra::read(nif); - - data = nif->getFloat(); - } - - void NiFloatsExtraData::read(NIFStream* nif) - { - Extra::read(nif); - nif->readVector(data, nif->getUInt()); + nif->skip(nif->get() * sizeof(float)); // vertex weights I guess } void BSBound::read(NIFStream* nif) { Extra::read(nif); - center = nif->getVector3(); - halfExtents = nif->getVector3(); + + nif->read(mCenter); + nif->read(mExtents); } void BSFurnitureMarker::LegacyFurniturePosition::read(NIFStream* nif) { - mOffset = nif->getVector3(); - mOrientation = nif->getUShort(); - mPositionRef = nif->getChar(); + nif->read(mOffset); + nif->read(mOrientation); + nif->read(mPositionRef); nif->skip(1); // Position ref 2 } void BSFurnitureMarker::FurniturePosition::read(NIFStream* nif) { - mOffset = nif->getVector3(); - mHeading = nif->getFloat(); - mType = nif->getUShort(); - mEntryPoint = nif->getUShort(); + nif->read(mOffset); + nif->read(mHeading); + nif->read(mType); + nif->read(mEntryPoint); } void BSFurnitureMarker::read(NIFStream* nif) { Extra::read(nif); - unsigned int num = nif->getUInt(); + + uint32_t num; + nif->read(num); if (nif->getBethVersion() <= NIFFile::BethVersion::BETHVER_FO3) { mLegacyMarkers.resize(num); @@ -124,19 +78,20 @@ namespace Nif void BSInvMarker::read(NIFStream* nif) { Extra::read(nif); - float rotX = nif->getUShort() / 1000.0; - float rotY = nif->getUShort() / 1000.0; - float rotZ = nif->getUShort() / 1000.0; - mScale = nif->getFloat(); + float rotX = nif->get() / 1000.f; + float rotY = nif->get() / 1000.f; + float rotZ = nif->get() / 1000.f; mRotation = osg::Quat(rotX, osg::X_AXIS, rotY, osg::Y_AXIS, rotZ, osg::Z_AXIS); + nif->read(mScale); } void BSBehaviorGraphExtraData::read(NIFStream* nif) { Extra::read(nif); - mFile = nif->getString(); - mControlsBaseSkeleton = nif->getBoolean(); + + nif->read(mFile); + nif->read(mControlsBaseSkeleton); } } diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index b59fb2f76a..dfe4539138 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -1,26 +1,3 @@ -/* - OpenMW - The completely unofficial reimplementation of Morrowind - Copyright (C) 2008-2010 Nicolay Korslund - Email: < korslund@gmail.com > - WWW: https://openmw.org/ - - This file (extra.h) is part of the OpenMW package. - - OpenMW is distributed as free software: you can redistribute it - and/or modify it under the terms of the GNU General Public License - version 3, as published by the Free Software Foundation. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - version 3 along with this program. If not, see - https://www.gnu.org/licenses/ . - - */ - #ifndef OPENMW_COMPONENTS_NIF_EXTRA_HPP #define OPENMW_COMPONENTS_NIF_EXTRA_HPP @@ -29,9 +6,46 @@ namespace Nif { + template + struct TypedExtra : public Extra + { + T mData; + + void read(NIFStream* nif) override + { + Extra::read(nif); + + nif->read(mData); + } + }; + + template + struct TypedVectorExtra : public Extra + { + std::vector mData; + + void read(NIFStream* nif) override + { + Extra::read(nif); + + nif->readVector(mData, nif->get()); + } + }; + + using NiBooleanExtraData = TypedExtra; + using NiFloatExtraData = TypedExtra; + using NiIntegerExtraData = TypedExtra; + using NiStringExtraData = TypedExtra; + using NiVectorExtraData = TypedExtra; + + using NiBinaryExtraData = TypedVectorExtra; + using NiFloatsExtraData = TypedVectorExtra; + using NiIntegersExtraData = TypedVectorExtra; + + // Distinct from NiBinaryExtraData, uses mRecordSize as its size struct NiExtraData : public Extra { - std::vector data; + std::vector mData; void read(NIFStream* nif) override; }; @@ -45,78 +59,17 @@ namespace Nif { struct TextKey { - float time; - std::string text; + float mTime; + std::string mText; }; - std::vector list; - - void read(NIFStream* nif) override; - }; - - struct NiStringExtraData : public Extra - { - /* Known meanings: - "MRK" - marker, only visible in the editor, not rendered in-game - "NCC" - no collision except with the camera - Anything else starting with "NC" - no collision - */ - std::string string; - - void read(NIFStream* nif) override; - }; - - struct NiIntegerExtraData : public Extra - { - unsigned int data; - - void read(NIFStream* nif) override; - }; - - struct NiIntegersExtraData : public Extra - { - std::vector data; - - void read(NIFStream* nif) override; - }; - - struct NiBinaryExtraData : public Extra - { - std::vector data; - - void read(NIFStream* nif) override; - }; - - struct NiBooleanExtraData : public Extra - { - bool data; - - void read(NIFStream* nif) override; - }; - - struct NiVectorExtraData : public Extra - { - osg::Vec4f data; - - void read(NIFStream* nif) override; - }; - - struct NiFloatExtraData : public Extra - { - float data; - - void read(NIFStream* nif) override; - }; - - struct NiFloatsExtraData : public Extra - { - std::vector data; + std::vector mList; void read(NIFStream* nif) override; }; struct BSBound : public Extra { - osg::Vec3f center, halfExtents; + osg::Vec3f mCenter, mExtents; void read(NIFStream* nif) override; }; @@ -149,7 +102,7 @@ namespace Nif struct BSInvMarker : public Extra { osg::Quat mRotation; - float mScale = 1.0f; + float mScale; void read(NIFStream* nif) override; }; @@ -162,5 +115,5 @@ namespace Nif void read(NIFStream* nif) override; }; -} // Namespace +} #endif diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index f806c61bc1..e147ee3528 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -10,15 +10,16 @@ #include #include -#include "controlled.hpp" #include "controller.hpp" #include "data.hpp" #include "effect.hpp" #include "exception.hpp" #include "extra.hpp" #include "node.hpp" +#include "particle.hpp" #include "physics.hpp" #include "property.hpp" +#include "texture.hpp" namespace Nif { diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp new file mode 100644 index 0000000000..ae391c59e4 --- /dev/null +++ b/components/nif/particle.cpp @@ -0,0 +1,95 @@ +#include "particle.hpp" + +#include "data.hpp" + +namespace Nif +{ + + void NiParticleModifier::read(NIFStream* nif) + { + mNext.read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13)) + mController.read(nif); + } + + void NiParticleModifier::post(Reader& nif) + { + mNext.post(nif); + mController.post(nif); + } + + void NiParticleGrowFade::read(NIFStream* nif) + { + NiParticleModifier::read(nif); + nif->read(mGrowTime); + nif->read(mFadeTime); + } + + void NiParticleColorModifier::read(NIFStream* nif) + { + NiParticleModifier::read(nif); + + mData.read(nif); + } + + void NiParticleColorModifier::post(Reader& nif) + { + NiParticleModifier::post(nif); + + mData.post(nif); + } + + void NiGravity::read(NIFStream* nif) + { + NiParticleModifier::read(nif); + + if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13)) + nif->read(mDecay); + nif->read(mForce); + mType = static_cast(nif->get()); + nif->read(mPosition); + nif->read(mDirection); + } + + void NiParticleCollider::read(NIFStream* nif) + { + NiParticleModifier::read(nif); + + nif->read(mBounceFactor); + if (nif->getVersion() >= NIFStream::generateVersion(4, 2, 0, 2)) + { + nif->read(mSpawnOnCollision); + nif->read(mDieOnCollision); + } + } + + void NiPlanarCollider::read(NIFStream* nif) + { + NiParticleCollider::read(nif); + + nif->read(mExtents); + nif->read(mPosition); + nif->read(mXVector); + nif->read(mYVector); + nif->read(mPlaneNormal); + nif->read(mPlaneDistance); + } + + void NiSphericalCollider::read(NIFStream* nif) + { + NiParticleCollider::read(nif); + + nif->read(mRadius); + nif->read(mCenter); + } + + void NiParticleRotation::read(NIFStream* nif) + { + NiParticleModifier::read(nif); + + nif->read(mRandomInitialAxis); + nif->read(mInitialAxis); + nif->read(mRotationSpeed); + } + +} diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp new file mode 100644 index 0000000000..5590877294 --- /dev/null +++ b/components/nif/particle.hpp @@ -0,0 +1,90 @@ +#ifndef OPENMW_COMPONENTS_NIF_PARTICLE_HPP +#define OPENMW_COMPONENTS_NIF_PARTICLE_HPP + +#include "base.hpp" + +namespace Nif +{ + + struct NiParticleModifier : public Record + { + NiParticleModifierPtr mNext; + ControllerPtr mController; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiParticleGrowFade : public NiParticleModifier + { + float mGrowTime; + float mFadeTime; + + void read(NIFStream* nif) override; + }; + + struct NiParticleColorModifier : public NiParticleModifier + { + NiColorDataPtr mData; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct NiGravity : public NiParticleModifier + { + enum class ForceType : uint32_t + { + Wind = 0, // Fixed direction + Point = 1, // Fixed origin + }; + + float mDecay{ 0.f }; + float mForce; + ForceType mType; + osg::Vec3f mPosition; + osg::Vec3f mDirection; + + void read(NIFStream* nif) override; + }; + + struct NiParticleCollider : public NiParticleModifier + { + float mBounceFactor; + bool mSpawnOnCollision{ false }; + bool mDieOnCollision{ false }; + + void read(NIFStream* nif) override; + }; + + // NiPinaColada + struct NiPlanarCollider : public NiParticleCollider + { + osg::Vec2f mExtents; + osg::Vec3f mPosition; + osg::Vec3f mXVector, mYVector; + osg::Vec3f mPlaneNormal; + float mPlaneDistance; + + void read(NIFStream* nif) override; + }; + + struct NiSphericalCollider : public NiParticleCollider + { + float mRadius; + osg::Vec3f mCenter; + + void read(NIFStream* nif) override; + }; + + struct NiParticleRotation : public NiParticleModifier + { + uint8_t mRandomInitialAxis; + osg::Vec3f mInitialAxis; + float mRotationSpeed; + + void read(NIFStream* nif) override; + }; + +} +#endif diff --git a/components/nif/physics.cpp b/components/nif/physics.cpp index daa4f90c2a..95786cb247 100644 --- a/components/nif/physics.cpp +++ b/components/nif/physics.cpp @@ -12,15 +12,15 @@ namespace Nif void bhkWorldObjCInfoProperty::read(NIFStream* nif) { - mData = nif->getUInt(); - mSize = nif->getUInt(); - mCapacityAndFlags = nif->getUInt(); + nif->read(mData); + nif->read(mSize); + nif->read(mCapacityAndFlags); } void bhkWorldObjectCInfo::read(NIFStream* nif) { nif->skip(4); // Unused - mPhaseType = static_cast(nif->getChar()); + mPhaseType = static_cast(nif->get()); nif->skip(3); // Unused mProperty.read(nif); } @@ -29,47 +29,47 @@ namespace Nif { if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB_OLD) nif->skip(4); // Unknown - mMaterial = nif->getUInt(); + nif->read(mMaterial); } void HavokFilter::read(NIFStream* nif) { - mLayer = nif->getChar(); - mFlags = nif->getChar(); - mGroup = nif->getUShort(); + nif->read(mLayer); + nif->read(mFlags); + nif->read(mGroup); } void hkSubPartData::read(NIFStream* nif) { mHavokFilter.read(nif); - mNumVertices = nif->getUInt(); + nif->read(mNumVertices); mHavokMaterial.read(nif); } - void hkpMoppCode::read(NIFStream* nif) - { - unsigned int size = nif->getUInt(); - if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - mOffset = nif->getVector4(); - if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) - nif->getChar(); // MOPP data build type - nif->readVector(mData, size); - } - void bhkEntityCInfo::read(NIFStream* nif) { - mResponseType = static_cast(nif->getChar()); + mResponseType = static_cast(nif->get()); nif->skip(1); // Unused - mProcessContactDelay = nif->getUShort(); + nif->read(mProcessContactDelay); + } + + void hkpMoppCode::read(NIFStream* nif) + { + uint32_t dataSize; + nif->read(dataSize); + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) + nif->read(mOffset); + if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO3) + nif->read(mBuildType); + nif->readVector(mData, dataSize); } void TriangleData::read(NIFStream* nif) { - for (int i = 0; i < 3; i++) - mTriangle[i] = nif->getUShort(); - mWeldingInfo = nif->getUShort(); + nif->readArray(mTriangle); + nif->read(mWeldingInfo); if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) - mNormal = nif->getVector3(); + nif->read(mNormal); } void bhkMeshMaterial::read(NIFStream* nif) @@ -80,28 +80,27 @@ namespace Nif void bhkQsTransform::read(NIFStream* nif) { - mTranslation = nif->getVector4(); - mRotation = nif->getQuaternion(); + nif->read(mTranslation); + nif->read(mRotation); } void bhkCMSBigTri::read(NIFStream* nif) { - for (int i = 0; i < 3; i++) - mTriangle[i] = nif->getUShort(); - mMaterial = nif->getUInt(); - mWeldingInfo = nif->getUShort(); + nif->readArray(mTriangle); + nif->read(mMaterial); + nif->read(mWeldingInfo); } void bhkCMSChunk::read(NIFStream* nif) { - mTranslation = nif->getVector4(); - mMaterialIndex = nif->getUInt(); - mReference = nif->getUShort(); - mTransformIndex = nif->getUShort(); - nif->readVector(mVertices, nif->getUInt()); - nif->readVector(mIndices, nif->getUInt()); - nif->readVector(mStrips, nif->getUInt()); - nif->readVector(mWeldingInfos, nif->getUInt()); + nif->read(mTranslation); + nif->read(mMaterialIndex); + nif->read(mReference); + nif->read(mTransformIndex); + nif->readVector(mVertices, nif->get()); + nif->readVector(mIndices, nif->get()); + nif->readVector(mStrips, nif->get()); + nif->readVector(mWeldingInfos, nif->get()); } void bhkRigidBodyCInfo::read(NIFStream* nif) @@ -115,64 +114,67 @@ namespace Nif { if (nif->getBethVersion() >= 83) nif->skip(4); // Unused - mResponseType = static_cast(nif->getChar()); + mResponseType = static_cast(nif->get()); nif->skip(1); // Unused - mProcessContactDelay = nif->getUShort(); + nif->read(mProcessContactDelay); } } if (nif->getBethVersion() < 83) nif->skip(4); // Unused - mTranslation = nif->getVector4(); - mRotation = nif->getQuaternion(); - mLinearVelocity = nif->getVector4(); - mAngularVelocity = nif->getVector4(); + nif->read(mTranslation); + nif->read(mRotation); + nif->read(mLinearVelocity); + nif->read(mAngularVelocity); + // A bit hacky, but this is the only instance where a 3x3 matrix has padding. for (int i = 0; i < 3; i++) - for (int j = 0; j < 4; j++) - mInertiaTensor[i][j] = nif->getFloat(); - mCenter = nif->getVector4(); - mMass = nif->getFloat(); - mLinearDamping = nif->getFloat(); - mAngularDamping = nif->getFloat(); + { + nif->read(mInertiaTensor.mValues[i], 3); + nif->skip(4); // Padding + } + nif->read(mCenter); + nif->read(mMass); + nif->read(mLinearDamping); + nif->read(mAngularDamping); if (nif->getBethVersion() >= 83) { if (nif->getBethVersion() != NIFFile::BethVersion::BETHVER_FO4) - mTimeFactor = nif->getFloat(); - mGravityFactor = nif->getFloat(); + nif->read(mTimeFactor); + nif->read(mGravityFactor); } - mFriction = nif->getFloat(); + nif->read(mFriction); if (nif->getBethVersion() >= 83) - mRollingFrictionMult = nif->getFloat(); - mRestitution = nif->getFloat(); + nif->read(mRollingFrictionMult); + nif->read(mRestitution); if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) { - mMaxLinearVelocity = nif->getFloat(); - mMaxAngularVelocity = nif->getFloat(); + nif->read(mMaxLinearVelocity); + nif->read(mMaxAngularVelocity); if (nif->getBethVersion() != NIFFile::BethVersion::BETHVER_FO4) - mPenetrationDepth = nif->getFloat(); + nif->read(mPenetrationDepth); } - mMotionType = static_cast(nif->getChar()); + mMotionType = static_cast(nif->get()); if (nif->getBethVersion() < 83) - mDeactivatorType = static_cast(nif->getChar()); + mDeactivatorType = static_cast(nif->get()); else - mEnableDeactivation = nif->getBoolean(); - mSolverDeactivation = static_cast(nif->getChar()); + nif->read(mEnableDeactivation); + mSolverDeactivation = static_cast(nif->get()); if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_FO4) { nif->skip(1); - mPenetrationDepth = nif->getFloat(); - mTimeFactor = nif->getFloat(); + nif->read(mPenetrationDepth); + nif->read(mTimeFactor); nif->skip(4); - mResponseType = static_cast(nif->getChar()); + mResponseType = static_cast(nif->get()); nif->skip(1); // Unused - mProcessContactDelay = nif->getUShort(); + nif->read(mProcessContactDelay); } - mQualityType = static_cast(nif->getChar()); + mQualityType = static_cast(nif->get()); if (nif->getBethVersion() >= 83) { - mAutoRemoveLevel = nif->getChar(); - mResponseModifierFlags = nif->getChar(); - mNumContactPointShapeKeys = nif->getChar(); - mForceCollidedOntoPPU = nif->getBoolean(); + nif->read(mAutoRemoveLevel); + nif->read(mResponseModifierFlags); + nif->read(mNumContactPointShapeKeys); + nif->read(mForceCollidedOntoPPU); } if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_FO4) nif->skip(3); // Unused @@ -182,7 +184,7 @@ namespace Nif void bhkConstraintCInfo::read(NIFStream* nif) { - nif->get(); // Number of entities, unused + nif->get(); // Number of entities, unused mEntityA.read(nif); mEntityB.read(nif); @@ -203,7 +205,7 @@ namespace Nif nif->read(mDamping); nif->read(mProportionalRecoveryVelocity); nif->read(mConstantRecoveryVelocity); - mEnabled = nif->getBoolean(); + nif->read(mEnabled); } void bhkVelocityConstraintMotor::read(NIFStream* nif) @@ -212,8 +214,8 @@ namespace Nif nif->read(mMaxForce); nif->read(mTau); nif->read(mTargetVelocity); - mUseVelocityTarget = nif->getBoolean(); - mEnabled = nif->getBoolean(); + nif->read(mUseVelocityTarget); + nif->read(mEnabled); } void bhkSpringDamperConstraintMotor::read(NIFStream* nif) @@ -222,7 +224,7 @@ namespace Nif nif->read(mMaxForce); nif->read(mSpringConstant); nif->read(mSpringDamping); - mEnabled = nif->getBoolean(); + nif->read(mEnabled); } void bhkConstraintMotorCInfo::read(NIFStream* nif) @@ -335,7 +337,8 @@ namespace Nif void bhkCollisionObject::read(NIFStream* nif) { NiCollisionObject::read(nif); - mFlags = nif->getUShort(); + + nif->read(mFlags); mBody.read(nif); } @@ -356,6 +359,7 @@ namespace Nif void bhkEntity::read(NIFStream* nif) { bhkWorldObject::read(nif); + mInfo.read(nif); } @@ -372,21 +376,26 @@ namespace Nif void bhkMoppBvTreeShape::read(NIFStream* nif) { bhkBvTreeShape::read(nif); + nif->skip(12); // Unused - mScale = nif->getFloat(); + nif->read(mScale); mMopp.read(nif); } void bhkNiTriStripsShape::read(NIFStream* nif) { mHavokMaterial.read(nif); - mRadius = nif->getFloat(); + nif->read(mRadius); nif->skip(20); // Unused - mGrowBy = nif->getUInt(); + nif->read(mGrowBy); if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) - mScale = nif->getVector4(); + nif->read(mScale); readRecordList(nif, mData); - nif->readVector(mFilters, nif->getUInt()); + uint32_t numFilters; + nif->read(numFilters); + mHavokFilters.resize(numFilters); + for (HavokFilter& filter : mHavokFilters) + filter.read(nif); } void bhkNiTriStripsShape::post(Reader& nif) @@ -398,15 +407,17 @@ namespace Nif { if (nif->getVersion() <= NIFFile::NIFVersion::VER_OB) { - mSubshapes.resize(nif->getUShort()); + uint16_t numSubshapes; + nif->read(numSubshapes); + mSubshapes.resize(numSubshapes); for (hkSubPartData& subshape : mSubshapes) subshape.read(nif); } - mUserData = nif->getUInt(); + nif->read(mUserData); nif->skip(4); // Unused - mRadius = nif->getFloat(); + nif->read(mRadius); nif->skip(4); // Unused - mScale = nif->getVector4(); + nif->read(mScale); nif->skip(20); // Duplicates of the two previous fields mData.read(nif); } @@ -418,22 +429,26 @@ namespace Nif void hkPackedNiTriStripsData::read(NIFStream* nif) { - unsigned int numTriangles = nif->getUInt(); + uint32_t numTriangles; + nif->read(numTriangles); mTriangles.resize(numTriangles); - for (unsigned int i = 0; i < numTriangles; i++) + for (uint32_t i = 0; i < numTriangles; i++) mTriangles[i].read(nif); - unsigned int numVertices = nif->getUInt(); + uint32_t numVertices; + nif->read(numVertices); bool compressed = false; if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS) - compressed = nif->getBoolean(); + nif->read(compressed); if (!compressed) nif->readVector(mVertices, numVertices); else nif->skip(6 * numVertices); // Half-precision vectors are not currently supported if (nif->getVersion() >= NIFFile::NIFVersion::VER_BGS) { - mSubshapes.resize(nif->getUShort()); + uint16_t numSubshapes; + nif->read(numSubshapes); + mSubshapes.resize(numSubshapes); for (hkSubPartData& subshape : mSubshapes) subshape.read(nif); } @@ -447,23 +462,25 @@ namespace Nif void bhkConvexShape::read(NIFStream* nif) { bhkSphereRepShape::read(nif); - mRadius = nif->getFloat(); + + nif->read(mRadius); } void bhkConvexVerticesShape::read(NIFStream* nif) { bhkConvexShape::read(nif); + mVerticesProperty.read(nif); mNormalsProperty.read(nif); - nif->readVector(mVertices, nif->getUInt()); - nif->readVector(mNormals, nif->getUInt()); + nif->readVector(mVertices, nif->get()); + nif->readVector(mNormals, nif->get()); } void bhkConvexTransformShape::read(NIFStream* nif) { mShape.read(nif); mHavokMaterial.read(nif); - mRadius = nif->getFloat(); + nif->read(mRadius); nif->skip(8); // Unused std::array mat; nif->readArray(mat); @@ -478,19 +495,21 @@ namespace Nif void bhkBoxShape::read(NIFStream* nif) { bhkConvexShape::read(nif); + nif->skip(8); // Unused - mExtents = nif->getVector3(); + nif->read(mExtents); nif->skip(4); // Unused } void bhkCapsuleShape::read(NIFStream* nif) { bhkConvexShape::read(nif); + nif->skip(8); // Unused - mPoint1 = nif->getVector3(); - mRadius1 = nif->getFloat(); - mPoint2 = nif->getVector3(); - mRadius2 = nif->getFloat(); + nif->read(mPoint1); + nif->read(mRadius1); + nif->read(mPoint2); + nif->read(mRadius2); } void bhkListShape::read(NIFStream* nif) @@ -499,7 +518,8 @@ namespace Nif mHavokMaterial.read(nif); mChildShapeProperty.read(nif); mChildFilterProperty.read(nif); - unsigned int numFilters = nif->getUInt(); + uint32_t numFilters; + nif->read(numFilters); mHavokFilters.resize(numFilters); for (HavokFilter& filter : mHavokFilters) filter.read(nif); @@ -508,12 +528,12 @@ namespace Nif void bhkCompressedMeshShape::read(NIFStream* nif) { mTarget.read(nif); - mUserData = nif->getUInt(); - mRadius = nif->getFloat(); - nif->getFloat(); // Unknown - mScale = nif->getVector4(); - nif->getFloat(); // Radius - nif->getVector4(); // Scale + nif->read(mUserData); + nif->read(mRadius); + nif->skip(4); // Unknown + nif->read(mScale); + nif->skip(4); // Radius + nif->skip(16); // Scale mData.read(nif); } @@ -525,60 +545,66 @@ namespace Nif void bhkCompressedMeshShapeData::read(NIFStream* nif) { - mBitsPerIndex = nif->getUInt(); - mBitsPerWIndex = nif->getUInt(); - mMaskWIndex = nif->getUInt(); - mMaskIndex = nif->getUInt(); - mError = nif->getFloat(); - mAabbMin = nif->getVector4(); - mAabbMax = nif->getVector4(); - mWeldingType = nif->getChar(); - mMaterialType = nif->getChar(); - nif->skip(nif->getUInt() * 4); // Unused - nif->skip(nif->getUInt() * 4); // Unused - nif->skip(nif->getUInt() * 4); // Unused + nif->read(mBitsPerIndex); + nif->read(mBitsPerWIndex); + nif->read(mMaskWIndex); + nif->read(mMaskIndex); + nif->read(mError); + nif->read(mAabbMin); + nif->read(mAabbMax); + nif->read(mWeldingType); + nif->read(mMaterialType); + nif->skip(nif->get() * 4); // Unused + nif->skip(nif->get() * 4); // Unused + nif->skip(nif->get() * 4); // Unused - size_t numMaterials = nif->getUInt(); + uint32_t numMaterials; + nif->read(numMaterials); mMaterials.resize(numMaterials); for (bhkMeshMaterial& material : mMaterials) material.read(nif); - nif->getUInt(); // Unused - size_t numTransforms = nif->getUInt(); + nif->skip(4); // Unused + uint32_t numTransforms; + nif->read(numTransforms); mChunkTransforms.resize(numTransforms); for (bhkQsTransform& transform : mChunkTransforms) transform.read(nif); - nif->readVector(mBigVerts, nif->getUInt()); + nif->readVector(mBigVerts, nif->get()); - size_t numBigTriangles = nif->getUInt(); + uint32_t numBigTriangles; + nif->read(numBigTriangles); mBigTris.resize(numBigTriangles); for (bhkCMSBigTri& tri : mBigTris) tri.read(nif); - size_t numChunks = nif->getUInt(); + uint32_t numChunks; + nif->read(numChunks); mChunks.resize(numChunks); for (bhkCMSChunk& chunk : mChunks) chunk.read(nif); - nif->getUInt(); // Unused + nif->skip(4); // Unused } void bhkRigidBody::read(NIFStream* nif) { bhkEntity::read(nif); + mInfo.read(nif); readRecordList(nif, mConstraints); if (nif->getBethVersion() < 76) - mBodyFlags = nif->getUInt(); + nif->read(mBodyFlags); else - mBodyFlags = nif->getUShort(); + mBodyFlags = nif->get(); } void bhkSimpleShapePhantom::read(NIFStream* nif) { bhkWorldObject::read(nif); + nif->skip(8); // Unused std::array mat; nif->readArray(mat); @@ -598,18 +624,21 @@ namespace Nif void bhkRagdollConstraint::read(NIFStream* nif) { bhkConstraint::read(nif); + mConstraint.read(nif); } void bhkHingeConstraint::read(NIFStream* nif) { bhkConstraint::read(nif); + mConstraint.read(nif); } void bhkLimitedHingeConstraint::read(NIFStream* nif) { bhkConstraint::read(nif); + mConstraint.read(nif); } diff --git a/components/nif/physics.hpp b/components/nif/physics.hpp index cc1a3ba755..a7bfa1425d 100644 --- a/components/nif/physics.hpp +++ b/components/nif/physics.hpp @@ -1,6 +1,7 @@ #ifndef OPENMW_COMPONENTS_NIF_PHYSICS_HPP #define OPENMW_COMPONENTS_NIF_PHYSICS_HPP +#include "niftypes.hpp" #include "record.hpp" #include "recordptr.hpp" @@ -23,9 +24,10 @@ namespace Nif struct bhkWorldObjCInfoProperty { - unsigned int mData; - unsigned int mSize; - unsigned int mCapacityAndFlags; + uint32_t mData; + uint32_t mSize; + uint32_t mCapacityAndFlags; + void read(NIFStream* nif); }; @@ -41,28 +43,32 @@ namespace Nif { BroadPhaseType mPhaseType; bhkWorldObjCInfoProperty mProperty; + void read(NIFStream* nif); }; struct HavokMaterial { - unsigned int mMaterial; + uint32_t mMaterial; + void read(NIFStream* nif); }; struct HavokFilter { - unsigned char mLayer; - unsigned char mFlags; - unsigned short mGroup; + uint8_t mLayer; + uint8_t mFlags; + uint16_t mGroup; + void read(NIFStream* nif); }; struct hkSubPartData { HavokMaterial mHavokMaterial; - unsigned int mNumVertices; + uint32_t mNumVertices; HavokFilter mHavokFilter; + void read(NIFStream* nif); }; @@ -77,22 +83,26 @@ namespace Nif struct bhkEntityCInfo { hkResponseType mResponseType; - unsigned short mProcessContactDelay; + uint16_t mProcessContactDelay; + void read(NIFStream* nif); }; struct hkpMoppCode { osg::Vec4f mOffset; - std::vector mData; + uint8_t mBuildType; + std::vector mData; + void read(NIFStream* nif); }; struct TriangleData { - unsigned short mTriangle[3]; - unsigned short mWeldingInfo; + std::array mTriangle; + uint16_t mWeldingInfo; osg::Vec3f mNormal; + void read(NIFStream* nif); }; @@ -100,6 +110,7 @@ namespace Nif { HavokMaterial mHavokMaterial; HavokFilter mHavokFilter; + void read(NIFStream* nif); }; @@ -107,27 +118,30 @@ namespace Nif { osg::Vec4f mTranslation; osg::Quat mRotation; + void read(NIFStream* nif); }; struct bhkCMSBigTri { - unsigned short mTriangle[3]; - unsigned int mMaterial; - unsigned short mWeldingInfo; + std::array mTriangle; + uint32_t mMaterial; + uint16_t mWeldingInfo; + void read(NIFStream* nif); }; struct bhkCMSChunk { osg::Vec4f mTranslation; - unsigned int mMaterialIndex; - unsigned short mReference; - unsigned short mTransformIndex; - std::vector mVertices; - std::vector mIndices; - std::vector mStrips; - std::vector mWeldingInfos; + uint32_t mMaterialIndex; + uint16_t mReference; + uint16_t mTransformIndex; + std::vector mVertices; + std::vector mIndices; + std::vector mStrips; + std::vector mWeldingInfos; + void read(NIFStream* nif); }; @@ -180,12 +194,12 @@ namespace Nif { HavokFilter mHavokFilter; hkResponseType mResponseType; - unsigned short mProcessContactDelay; + uint16_t mProcessContactDelay; osg::Vec4f mTranslation; osg::Quat mRotation; osg::Vec4f mLinearVelocity; osg::Vec4f mAngularVelocity; - float mInertiaTensor[3][4]; + Matrix3 mInertiaTensor; osg::Vec4f mCenter; float mMass; float mLinearDamping; @@ -203,10 +217,11 @@ namespace Nif bool mEnableDeactivation{ true }; hkSolverDeactivation mSolverDeactivation; hkQualityType mQualityType; - unsigned char mAutoRemoveLevel; - unsigned char mResponseModifierFlags; - unsigned char mNumContactPointShapeKeys; + uint8_t mAutoRemoveLevel; + uint8_t mResponseModifierFlags; + uint8_t mNumContactPointShapeKeys; bool mForceCollidedOntoPPU; + void read(NIFStream* nif); }; @@ -222,6 +237,7 @@ namespace Nif bhkEntityPtr mEntityA; bhkEntityPtr mEntityB; ConstraintPriority mPriority; + void read(NIFStream* nif); void post(Reader& nif); }; @@ -242,6 +258,7 @@ namespace Nif float mProportionalRecoveryVelocity; float mConstantRecoveryVelocity; bool mEnabled; + void read(NIFStream* nif); }; @@ -252,6 +269,7 @@ namespace Nif float mTargetVelocity; bool mUseVelocityTarget; bool mEnabled; + void read(NIFStream* nif); }; @@ -261,6 +279,7 @@ namespace Nif float mSpringConstant; float mSpringDamping; bool mEnabled; + void read(NIFStream* nif); }; @@ -270,6 +289,7 @@ namespace Nif bhkPositionConstraintMotor mPositionMotor; bhkVelocityConstraintMotor mVelocityMotor; bhkSpringDamperConstraintMotor mSpringDamperMotor; + void read(NIFStream* nif); }; @@ -289,6 +309,7 @@ namespace Nif float mTwistMinAngle, mTwistMaxAngle; float mMaxFriction; bhkConstraintMotorCInfo mMotor; + void read(NIFStream* nif); }; @@ -301,8 +322,10 @@ namespace Nif osg::Vec4f mPerpAxis1; osg::Vec4f mPerpAxis2; }; + HingeData mDataA; HingeData mDataB; + void read(NIFStream* nif); }; @@ -315,11 +338,13 @@ namespace Nif osg::Vec4f mPerpAxis1; osg::Vec4f mPerpAxis2; }; + HingeData mDataA; HingeData mDataB; float mMinAngle, mMaxAngle; float mMaxFriction; bhkConstraintMotorCInfo mMotor; + void read(NIFStream* nif); }; @@ -358,7 +383,7 @@ namespace Nif // Bethesda Havok-specific collision object struct bhkCollisionObject : public NiCollisionObject { - unsigned short mFlags; + uint16_t mFlags; bhkWorldObjectPtr mBody; void read(NIFStream* nif) override; @@ -375,6 +400,7 @@ namespace Nif bhkShapePtr mShape; HavokFilter mHavokFilter; bhkWorldObjectCInfo mWorldObjectInfo; + void read(NIFStream* nif) override; void post(Reader& nif) override; }; @@ -383,6 +409,7 @@ namespace Nif struct bhkEntity : public bhkWorldObject { bhkEntityCInfo mInfo; + void read(NIFStream* nif) override; }; @@ -391,6 +418,7 @@ namespace Nif struct bhkBvTreeShape : public bhkShape { bhkShapePtr mShape; + void read(NIFStream* nif) override; void post(Reader& nif) override; }; @@ -400,6 +428,7 @@ namespace Nif { float mScale; hkpMoppCode mMopp; + void read(NIFStream* nif) override; }; @@ -408,10 +437,11 @@ namespace Nif { HavokMaterial mHavokMaterial; float mRadius; - unsigned int mGrowBy; + uint32_t mGrowBy; osg::Vec4f mScale{ 1.f, 1.f, 1.f, 0.f }; NiTriStripsDataList mData; - std::vector mFilters; + std::vector mHavokFilters; + void read(NIFStream* nif) override; void post(Reader& nif) override; }; @@ -420,7 +450,7 @@ namespace Nif struct bhkPackedNiTriStripsShape : public bhkShapeCollection { std::vector mSubshapes; - unsigned int mUserData; + uint32_t mUserData; float mRadius; osg::Vec4f mScale; hkPackedNiTriStripsDataPtr mData; @@ -435,6 +465,7 @@ namespace Nif std::vector mTriangles; std::vector mVertices; std::vector mSubshapes; + void read(NIFStream* nif) override; }; @@ -442,6 +473,7 @@ namespace Nif struct bhkSphereRepShape : public bhkShape { HavokMaterial mHavokMaterial; + void read(NIFStream* nif) override; }; @@ -449,6 +481,7 @@ namespace Nif struct bhkConvexShape : public bhkSphereRepShape { float mRadius; + void read(NIFStream* nif) override; }; @@ -459,6 +492,7 @@ namespace Nif bhkWorldObjCInfoProperty mNormalsProperty; std::vector mVertices; std::vector mNormals; + void read(NIFStream* nif) override; }; @@ -468,6 +502,7 @@ namespace Nif HavokMaterial mHavokMaterial; float mRadius; osg::Matrixf mTransform; + void read(NIFStream* nif) override; void post(Reader& nif) override; }; @@ -476,6 +511,7 @@ namespace Nif struct bhkBoxShape : public bhkConvexShape { osg::Vec3f mExtents; + void read(NIFStream* nif) override; }; @@ -499,28 +535,30 @@ namespace Nif bhkWorldObjCInfoProperty mChildShapeProperty; bhkWorldObjCInfoProperty mChildFilterProperty; std::vector mHavokFilters; + void read(NIFStream* nif) override; }; struct bhkCompressedMeshShape : public bhkShape { NodePtr mTarget; - unsigned int mUserData; + uint32_t mUserData; float mRadius; osg::Vec4f mScale; bhkCompressedMeshShapeDataPtr mData; + void read(NIFStream* nif) override; void post(Reader& nif) override; }; struct bhkCompressedMeshShapeData : public bhkRefObject { - unsigned int mBitsPerIndex, mBitsPerWIndex; - unsigned int mMaskWIndex, mMaskIndex; + uint32_t mBitsPerIndex, mBitsPerWIndex; + uint32_t mMaskWIndex, mMaskIndex; float mError; osg::Vec4f mAabbMin, mAabbMax; - char mWeldingType; - char mMaterialType; + uint8_t mWeldingType; + uint8_t mMaterialType; std::vector mMaterials; std::vector mChunkTransforms; std::vector mBigVerts; @@ -534,7 +572,7 @@ namespace Nif { bhkRigidBodyCInfo mInfo; bhkSerializableList mConstraints; - unsigned int mBodyFlags; + uint32_t mBodyFlags; void read(NIFStream* nif) override; }; @@ -542,6 +580,7 @@ namespace Nif struct bhkSimpleShapePhantom : public bhkWorldObject { osg::Matrixf mTransform; + void read(NIFStream* nif) override; }; @@ -549,6 +588,7 @@ namespace Nif struct bhkConstraint : public bhkSerializable { bhkConstraintCInfo mInfo; + void read(NIFStream* nif) override; void post(Reader& nif) override; }; @@ -556,18 +596,21 @@ namespace Nif struct bhkRagdollConstraint : public bhkConstraint { bhkRagdollConstraintCInfo mConstraint; + void read(NIFStream* nif) override; }; struct bhkHingeConstraint : public bhkConstraint { bhkHingeConstraintCInfo mConstraint; + void read(NIFStream* nif) override; }; struct bhkLimitedHingeConstraint : public bhkConstraint { bhkLimitedHingeConstraintCInfo mConstraint; + void read(NIFStream* nif) override; }; diff --git a/components/nif/property.cpp b/components/nif/property.cpp index 3c1c424de9..39a7cbff23 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -1,7 +1,7 @@ #include "property.hpp" -#include "controlled.hpp" #include "data.hpp" +#include "texture.hpp" namespace Nif { diff --git a/components/nif/texture.cpp b/components/nif/texture.cpp new file mode 100644 index 0000000000..116ded6f7e --- /dev/null +++ b/components/nif/texture.cpp @@ -0,0 +1,47 @@ +#include "texture.hpp" + +#include "data.hpp" + +namespace Nif +{ + + void NiSourceTexture::read(NIFStream* nif) + { + NiTexture::read(nif); + + nif->read(mExternal); + if (mExternal || nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 0)) + nif->read(mFile); + + bool hasData = nif->getVersion() >= NIFStream::generateVersion(10, 0, 1, 4); + if (!hasData && !mExternal) + nif->read(hasData); + + if (hasData) + mData.read(nif); + + mPrefs.mPixelLayout = static_cast(nif->get()); + mPrefs.mUseMipMaps = static_cast(nif->get()); + mPrefs.mAlphaFormat = static_cast(nif->get()); + + nif->read(mIsStatic); + if (nif->getVersion() >= NIFStream::generateVersion(10, 1, 0, 103)) + { + nif->read(mDirectRendering); + if (nif->getVersion() >= NIFStream::generateVersion(20, 2, 0, 4)) + nif->read(mPersistRenderData); + } + } + + void NiSourceTexture::post(Reader& nif) + { + NiTexture::post(nif); + mData.post(nif); + } + + void BSShaderTextureSet::read(NIFStream* nif) + { + nif->getSizedStrings(mTextures, nif->get()); + } + +} diff --git a/components/nif/texture.hpp b/components/nif/texture.hpp new file mode 100644 index 0000000000..a326b47f14 --- /dev/null +++ b/components/nif/texture.hpp @@ -0,0 +1,81 @@ +#ifndef OPENMW_COMPONENTS_NIF_TEXTURE_HPP +#define OPENMW_COMPONENTS_NIF_TEXTURE_HPP + +#include "base.hpp" + +namespace Nif +{ + + struct NiTexture : public Named + { + }; + + struct NiSourceTexture : public NiTexture + { + enum class PixelLayout : uint32_t + { + Palette = 0, + HighColor = 1, + TrueColor = 2, + Compressed = 3, + BumpMap = 4, + Default = 5, + }; + + enum class MipMapFormat : uint32_t + { + No = 0, + Yes = 1, + Default = 2, + }; + + enum class AlphaFormat : uint32_t + { + None = 0, + Binary = 1, + Smooth = 2, + Default = 3, + }; + + struct FormatPrefs + { + PixelLayout mPixelLayout; + MipMapFormat mUseMipMaps; + AlphaFormat mAlphaFormat; + }; + + char mExternal; // References external file + + std::string mFile; + NiPixelDataPtr mData; + + FormatPrefs mPrefs; + + char mIsStatic{ 1 }; + bool mDirectRendering{ true }; + bool mPersistRenderData{ false }; + + void read(NIFStream* nif) override; + void post(Reader& nif) override; + }; + + struct BSShaderTextureSet : public Record + { + enum class TextureType : uint32_t + { + Base = 0, + Normal = 1, + Glow = 2, + Parallax = 3, + Environment = 4, + EnvironmentMask = 5, + Subsurface = 6, + BackLighting = 7, + }; + std::vector mTextures; + + void read(NIFStream* nif) override; + }; + +} +#endif diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 251795eb21..0c85949a53 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -305,7 +305,7 @@ namespace NifBullet // Check for extra data std::vector extraCollection; - for (Nif::ExtraPtr e = node.extra; !e.empty(); e = e->next) + for (Nif::ExtraPtr e = node.extra; !e.empty(); e = e->mNext) extraCollection.emplace_back(e); for (const auto& extraNode : node.extralist) if (!extraNode.empty()) @@ -316,29 +316,30 @@ namespace NifBullet { // String markers may contain important information // affecting the entire subtree of this node - Nif::NiStringExtraData* sd = (Nif::NiStringExtraData*)e.getPtr(); + auto sd = static_cast(e.getPtr()); - if (Misc::StringUtils::ciStartsWith(sd->string, "NC")) + if (Misc::StringUtils::ciStartsWith(sd->mData, "NC")) { // NCC flag in vanilla is partly case sensitive: prefix NC is case insensitive but second C needs be // uppercase - if (sd->string.length() > 2 && sd->string[2] == 'C') + if (sd->mData.length() > 2 && sd->mData[2] == 'C') // Collide only with camera. visualCollisionType = Resource::VisualCollisionType::Camera; else // No collision. visualCollisionType = Resource::VisualCollisionType::Default; } - else if (sd->string == "MRK" && args.mAutogenerated) + // Don't autogenerate collision if MRK is set. + // FIXME: verify if this covers the entire subtree + else if (sd->mData == "MRK" && args.mAutogenerated) { - // Marker can still have collision if the model explicitely specifies it via a RootCollisionNode. return; } } else if (e->recType == Nif::RC_BSXFlags) { auto bsxFlags = static_cast(e.getPtr()); - if (bsxFlags->data & 32) // Editor marker flag + if (bsxFlags->mData & 32) // Editor marker flag args.mHasMarkers = true; } } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 2e2d0293e6..dde4f261e2 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -42,13 +42,14 @@ #include #include -#include #include #include #include #include #include +#include #include +#include #include #include #include @@ -174,16 +175,16 @@ namespace void extractTextKeys(const Nif::NiTextKeyExtraData* tk, SceneUtil::TextKeyMap& textkeys) { - for (size_t i = 0; i < tk->list.size(); i++) + for (const Nif::NiTextKeyExtraData::TextKey& key : tk->mList) { std::vector results; - Misc::StringUtils::split(tk->list[i].text, results, "\r\n"); + Misc::StringUtils::split(key.mText, results, "\r\n"); for (std::string& result : results) { Misc::StringUtils::trim(result); Misc::StringUtils::lowerCaseInPlace(result); if (!result.empty()) - textkeys.emplace(tk->list[i].time, std::move(result)); + textkeys.emplace(key.mTime, std::move(result)); } } } @@ -285,9 +286,9 @@ namespace NifOsg extractTextKeys(static_cast(extra.getPtr()), target.mTextKeys); - extra = extra->next; + extra = extra->mNext; Nif::ControllerPtr ctrl = seq->controller; - for (; !extra.empty() && !ctrl.empty(); (extra = extra->next), (ctrl = ctrl->next)) + for (; !extra.empty() && !ctrl.empty(); (extra = extra->mNext), (ctrl = ctrl->next)) { if (extra->recType != Nif::RC_NiStringExtraData || ctrl->recType != Nif::RC_NiKeyframeController) { @@ -315,8 +316,8 @@ namespace NifOsg osg::ref_ptr callback = new NifOsg::KeyframeController(key); setupController(key, callback, /*animflags*/ 0); - if (!target.mKeyframeControllers.emplace(strdata->string, callback).second) - Log(Debug::Verbose) << "Controller " << strdata->string << " present more than once in " + if (!target.mKeyframeControllers.emplace(strdata->mData, callback).second) + Log(Debug::Verbose) << "Controller " << strdata->mData << " present more than once in " << nif.getFilename() << ", ignoring later version"; } } @@ -509,15 +510,15 @@ namespace NifOsg return nullptr; osg::ref_ptr image; - if (!st->external && !st->data.empty()) + if (st->mExternal) { - image = handleInternalTexture(st->data.getPtr()); - } - else - { - std::string filename = Misc::ResourceHelpers::correctTexturePath(st->filename, imageManager->getVFS()); + std::string filename = Misc::ResourceHelpers::correctTexturePath(st->mFile, imageManager->getVFS()); image = imageManager->getImage(filename); } + else if (!st->mData.empty()) + { + image = handleInternalTexture(st->mData.getPtr()); + } return image; } @@ -536,38 +537,41 @@ namespace NifOsg } const Nif::NiTextureEffect* textureEffect = static_cast(nifNode); - if (textureEffect->textureType != Nif::NiTextureEffect::Environment_Map) + if (!textureEffect->mSwitchState) + return false; + + if (textureEffect->mTextureType != Nif::NiTextureEffect::TextureType::EnvironmentMap) { - Log(Debug::Info) << "Unhandled NiTextureEffect type " << textureEffect->textureType << " in " - << mFilename; + Log(Debug::Info) << "Unhandled NiTextureEffect type " + << static_cast(textureEffect->mTextureType) << " in " << mFilename; return false; } - if (textureEffect->texture.empty()) + if (textureEffect->mTexture.empty()) { Log(Debug::Info) << "NiTextureEffect missing source texture in " << mFilename; return false; } osg::ref_ptr texGen(new osg::TexGen); - switch (textureEffect->coordGenType) + switch (textureEffect->mCoordGenType) { - case Nif::NiTextureEffect::World_Parallel: + case Nif::NiTextureEffect::CoordGenType::WorldParallel: texGen->setMode(osg::TexGen::OBJECT_LINEAR); break; - case Nif::NiTextureEffect::World_Perspective: + case Nif::NiTextureEffect::CoordGenType::WorldPerspective: texGen->setMode(osg::TexGen::EYE_LINEAR); break; - case Nif::NiTextureEffect::Sphere_Map: + case Nif::NiTextureEffect::CoordGenType::SphereMap: texGen->setMode(osg::TexGen::SPHERE_MAP); break; default: - Log(Debug::Info) << "Unhandled NiTextureEffect coordGenType " << textureEffect->coordGenType - << " in " << mFilename; + Log(Debug::Info) << "Unhandled NiTextureEffect CoordGenType " + << static_cast(textureEffect->mCoordGenType) << " in " << mFilename; return false; } - osg::ref_ptr image(handleSourceTexture(textureEffect->texture.getPtr(), imageManager)); + osg::ref_ptr image(handleSourceTexture(textureEffect->mTexture.getPtr(), imageManager)); osg::ref_ptr texture2d(new osg::Texture2D(image)); if (image) texture2d->setTextureSize(image->s(), image->t()); @@ -644,7 +648,7 @@ namespace NifOsg std::vector extraCollection; - for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->next) + for (Nif::ExtraPtr e = nifNode->extra; !e.empty(); e = e->mNext) extraCollection.emplace_back(e); for (const auto& extraNode : nifNode->extralist) @@ -666,25 +670,25 @@ namespace NifOsg // String markers may contain important information // affecting the entire subtree of this obj - if (sd->string == "MRK" && !Loader::getShowMarkers()) + if (sd->mData == "MRK" && !Loader::getShowMarkers()) { // Marker objects. These meshes are only visible in the editor. args.mHasMarkers = true; } - else if (sd->string == "BONE") + else if (sd->mData == "BONE") { node->getOrCreateUserDataContainer()->addDescription("CustomBone"); } - else if (sd->string.rfind(extraDataIdentifer, 0) == 0) + else if (sd->mData.rfind(extraDataIdentifer, 0) == 0) { node->setUserValue( - Misc::OsgUserValues::sExtraData, sd->string.substr(extraDataIdentifer.length())); + Misc::OsgUserValues::sExtraData, sd->mData.substr(extraDataIdentifer.length())); } } else if (e->recType == Nif::RC_BSXFlags) { auto bsxFlags = static_cast(e.getPtr()); - if (bsxFlags->data & 32) // Editor marker flag + if (bsxFlags->mData & 32) // Editor marker flag args.mHasMarkers = true; } } @@ -895,7 +899,7 @@ namespace NifOsg if (!key->mInterpolator.empty() && key->mInterpolator->recType != Nif::RC_NiTransformInterpolator) { Log(Debug::Error) << "Unsupported interpolator type for NiKeyframeController " << key->recIndex - << " in " << mFilename; + << " in " << mFilename << ": " << key->mInterpolator->recName; continue; } osg::ref_ptr callback = new KeyframeController(key); @@ -922,7 +926,7 @@ namespace NifOsg && visctrl->mInterpolator->recType != Nif::RC_NiBoolInterpolator) { Log(Debug::Error) << "Unsupported interpolator type for NiVisController " << visctrl->recIndex - << " in " << mFilename; + << " in " << mFilename << ": " << visctrl->mInterpolator->recName; continue; } osg::ref_ptr callback(new VisController(visctrl, Loader::getHiddenNodeMask())); @@ -938,7 +942,7 @@ namespace NifOsg && rollctrl->mInterpolator->recType != Nif::RC_NiFloatInterpolator) { Log(Debug::Error) << "Unsupported interpolator type for NiRollController " << rollctrl->recIndex - << " in " << mFilename; + << " in " << mFilename << ": " << rollctrl->mInterpolator->recName; continue; } osg::ref_ptr callback = new RollController(rollctrl); @@ -973,8 +977,9 @@ namespace NifOsg if (!alphactrl->mInterpolator.empty() && alphactrl->mInterpolator->recType != Nif::RC_NiFloatInterpolator) { - Log(Debug::Error) << "Unsupported interpolator type for NiAlphaController " - << alphactrl->recIndex << " in " << mFilename; + Log(Debug::Error) + << "Unsupported interpolator type for NiAlphaController " << alphactrl->recIndex << " in " + << mFilename << ": " << alphactrl->mInterpolator->recName; continue; } osg::ref_ptr osgctrl = new AlphaController(alphactrl, baseMaterial); @@ -994,8 +999,9 @@ namespace NifOsg if (!matctrl->mInterpolator.empty() && matctrl->mInterpolator->recType != Nif::RC_NiPoint3Interpolator) { - Log(Debug::Error) << "Unsupported interpolator type for NiMaterialColorController " - << matctrl->recIndex << " in " << mFilename; + Log(Debug::Error) + << "Unsupported interpolator type for NiMaterialColorController " << matctrl->recIndex + << " in " << mFilename << ": " << matctrl->mInterpolator->recName; continue; } osg::ref_ptr osgctrl = new MaterialColorController(matctrl, baseMaterial); @@ -1021,7 +1027,7 @@ namespace NifOsg && flipctrl->mInterpolator->recType != Nif::RC_NiFloatInterpolator) { Log(Debug::Error) << "Unsupported interpolator type for NiFlipController " << flipctrl->recIndex - << " in " << mFilename; + << " in " << mFilename << ": " << flipctrl->mInterpolator->recName; continue; } std::vector> textures; @@ -1067,12 +1073,12 @@ namespace NifOsg attachTo->addChild(program); program->setParticleSystem(partsys); program->setReferenceFrame(rf); - for (; !affectors.empty(); affectors = affectors->next) + for (; !affectors.empty(); affectors = affectors->mNext) { if (affectors->recType == Nif::RC_NiParticleGrowFade) { const Nif::NiParticleGrowFade* gf = static_cast(affectors.getPtr()); - program->addOperator(new GrowFadeAffector(gf->growTime, gf->fadeTime)); + program->addOperator(new GrowFadeAffector(gf->mGrowTime, gf->mFadeTime)); } else if (affectors->recType == Nif::RC_NiGravity) { @@ -1083,9 +1089,9 @@ namespace NifOsg { const Nif::NiParticleColorModifier* cl = static_cast(affectors.getPtr()); - if (cl->data.empty()) + if (cl->mData.empty()) continue; - const Nif::NiColorData* clrdata = cl->data.getPtr(); + const Nif::NiColorData* clrdata = cl->mData.getPtr(); program->addOperator(new ParticleColorAffector(clrdata)); } else if (affectors->recType == Nif::RC_NiParticleRotation) @@ -1095,7 +1101,7 @@ namespace NifOsg else Log(Debug::Info) << "Unhandled particle modifier " << affectors->recName << " in " << mFilename; } - for (; !colliders.empty(); colliders = colliders->next) + for (; !colliders.empty(); colliders = colliders->mNext) { if (colliders->recType == Nif::RC_NiPlanarCollider) { @@ -2008,15 +2014,15 @@ namespace NifOsg const unsigned int uvSet = 0; - for (size_t i = 0; i < textureSet->textures.size(); ++i) + for (size_t i = 0; i < textureSet->mTextures.size(); ++i) { - if (textureSet->textures[i].empty()) + if (textureSet->mTextures[i].empty()) continue; - switch (i) + switch (static_cast(i)) { - case Nif::BSShaderTextureSet::TextureType_Base: - case Nif::BSShaderTextureSet::TextureType_Normal: - case Nif::BSShaderTextureSet::TextureType_Glow: + case Nif::BSShaderTextureSet::TextureType::Base: + case Nif::BSShaderTextureSet::TextureType::Normal: + case Nif::BSShaderTextureSet::TextureType::Glow: break; default: { @@ -2026,7 +2032,7 @@ namespace NifOsg } } std::string filename - = Misc::ResourceHelpers::correctTexturePath(textureSet->textures[i], imageManager->getVFS()); + = Misc::ResourceHelpers::correctTexturePath(textureSet->mTextures[i], imageManager->getVFS()); osg::ref_ptr image = imageManager->getImage(filename); osg::ref_ptr texture2d = new osg::Texture2D(image); if (image) @@ -2035,17 +2041,19 @@ namespace NifOsg unsigned int texUnit = boundTextures.size(); stateset->setTextureAttributeAndModes(texUnit, texture2d, osg::StateAttribute::ON); // BSShaderTextureSet presence means there's no need for FFP support for the affected node - switch (i) + switch (static_cast(i)) { - case Nif::BSShaderTextureSet::TextureType_Base: + case Nif::BSShaderTextureSet::TextureType::Base: texture2d->setName("diffuseMap"); break; - case Nif::BSShaderTextureSet::TextureType_Normal: + case Nif::BSShaderTextureSet::TextureType::Normal: texture2d->setName("normalMap"); break; - case Nif::BSShaderTextureSet::TextureType_Glow: + case Nif::BSShaderTextureSet::TextureType::Glow: texture2d->setName("emissiveMap"); break; + default: + break; } boundTextures.emplace_back(uvSet); } diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 2700b7eb93..8be0d4ba4f 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -281,20 +280,13 @@ namespace NifOsg GravityAffector::GravityAffector(const Nif::NiGravity* gravity) : mForce(gravity->mForce) - , mType(static_cast(gravity->mType)) + , mType(gravity->mType) , mPosition(gravity->mPosition) , mDirection(gravity->mDirection) , mDecay(gravity->mDecay) { } - GravityAffector::GravityAffector() - : mForce(0) - , mType(Type_Wind) - , mDecay(0.f) - { - } - GravityAffector::GravityAffector(const GravityAffector& copy, const osg::CopyOp& copyop) : osgParticle::Operator(copy, copyop) { @@ -311,8 +303,8 @@ namespace NifOsg { bool absolute = (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF); - if (mType == Type_Point - || mDecay != 0.f) // we don't need the position for Wind gravity, except if decay is being applied + // We don't need the position for Wind gravity, except if decay is being applied + if (mType == Nif::NiGravity::ForceType::Point || mDecay != 0.f) mCachedWorldPosition = absolute ? program->transformLocalToWorld(mPosition) : mPosition; mCachedWorldDirection = absolute ? program->rotateLocalToWorld(mDirection) : mDirection; @@ -324,7 +316,7 @@ namespace NifOsg const float magic = 1.6f; switch (mType) { - case Type_Wind: + case Nif::NiGravity::ForceType::Wind: { float decayFactor = 1.f; if (mDecay != 0.f) @@ -338,7 +330,7 @@ namespace NifOsg break; } - case Type_Point: + case Nif::NiGravity::ForceType::Point: { osg::Vec3f diff = mCachedWorldPosition - particle->getPosition(); diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp index 3424512208..a9b628f695 100644 --- a/components/nifosg/particle.hpp +++ b/components/nifosg/particle.hpp @@ -10,15 +10,14 @@ #include #include +#include // NiGravity::ForceType + #include #include "controller.hpp" // ValueInterpolator namespace Nif { - struct NiGravity; - struct NiPlanarCollider; - struct NiSphericalCollider; struct NiColorData; } @@ -180,7 +179,7 @@ namespace NifOsg { public: GravityAffector(const Nif::NiGravity* gravity); - GravityAffector(); + GravityAffector() = default; GravityAffector(const GravityAffector& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); GravityAffector& operator=(const GravityAffector&) = delete; @@ -191,16 +190,11 @@ namespace NifOsg void beginOperate(osgParticle::Program*) override; private: - float mForce; - enum ForceType - { - Type_Wind, - Type_Point - }; - ForceType mType; + float mForce{ 0.f }; + Nif::NiGravity::ForceType mType{ Nif::NiGravity::ForceType::Wind }; osg::Vec3f mPosition; osg::Vec3f mDirection; - float mDecay; + float mDecay{ 0.f }; osg::Vec3f mCachedWorldPosition; osg::Vec3f mCachedWorldDirection; }; diff --git a/components/resource/niffilemanager.cpp b/components/resource/niffilemanager.cpp index ea95edf2ae..5e457cdfaa 100644 --- a/components/resource/niffilemanager.cpp +++ b/components/resource/niffilemanager.cpp @@ -32,7 +32,9 @@ namespace Resource }; NifFileManager::NifFileManager(const VFS::Manager* vfs) - : ResourceManager(vfs) + // NIF files aren't needed any more once the converted objects are cached in SceneManager / BulletShapeManager, + // so no point in using an expiry delay. + : ResourceManager(vfs, 0) { } diff --git a/components/resource/objectcache.hpp b/components/resource/objectcache.hpp index 3847129ed3..881729ffc4 100644 --- a/components/resource/objectcache.hpp +++ b/components/resource/objectcache.hpp @@ -62,9 +62,9 @@ namespace Resource { // If ref count is greater than 1, the object has an external reference. // If the timestamp is yet to be initialized, it needs to be updated too. - if ((itr->second.first != nullptr && itr->second.first->referenceCount() > 1) - || itr->second.second == 0.0) - itr->second.second = referenceTime; + if ((itr->second.mValue != nullptr && itr->second.mValue->referenceCount() > 1) + || itr->second.mLastUsage == 0.0) + itr->second.mLastUsage = referenceTime; } } @@ -81,10 +81,10 @@ namespace Resource typename ObjectCacheMap::iterator oitr = _objectCache.begin(); while (oitr != _objectCache.end()) { - if (oitr->second.second <= expiryTime) + if (oitr->second.mLastUsage <= expiryTime) { - if (oitr->second.first != nullptr) - objectsToRemove.push_back(oitr->second.first); + if (oitr->second.mValue != nullptr) + objectsToRemove.push_back(std::move(oitr->second.mValue)); _objectCache.erase(oitr++); } else @@ -106,7 +106,7 @@ namespace Resource void addEntryToObjectCache(const KeyType& key, osg::Object* object, double timestamp = 0.0) { std::lock_guard lock(_objectCacheMutex); - _objectCache[key] = ObjectTimeStampPair(object, timestamp); + _objectCache[key] = Item{ object, timestamp }; } /** Remove Object from cache.*/ @@ -124,7 +124,7 @@ namespace Resource std::lock_guard lock(_objectCacheMutex); typename ObjectCacheMap::iterator itr = _objectCache.find(key); if (itr != _objectCache.end()) - return itr->second.first; + return itr->second.mValue; else return nullptr; } @@ -135,7 +135,7 @@ namespace Resource const auto it = _objectCache.find(key); if (it == _objectCache.end()) return std::nullopt; - return it->second.first; + return it->second.mValue; } /** Check if an object is in the cache, and if it is, update its usage time stamp. */ @@ -145,7 +145,7 @@ namespace Resource typename ObjectCacheMap::iterator itr = _objectCache.find(key); if (itr != _objectCache.end()) { - itr->second.second = timeStamp; + itr->second.mLastUsage = timeStamp; return true; } else @@ -158,7 +158,7 @@ namespace Resource std::lock_guard lock(_objectCacheMutex); for (typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr) { - osg::Object* object = itr->second.first.get(); + osg::Object* object = itr->second.mValue.get(); object->releaseGLObjects(state); } } @@ -169,8 +169,7 @@ namespace Resource std::lock_guard lock(_objectCacheMutex); for (typename ObjectCacheMap::iterator itr = _objectCache.begin(); itr != _objectCache.end(); ++itr) { - osg::Object* object = itr->second.first.get(); - if (object) + if (osg::Object* object = itr->second.mValue.get()) { osg::Node* node = dynamic_cast(object); if (node) @@ -185,7 +184,7 @@ namespace Resource { std::lock_guard lock(_objectCacheMutex); for (typename ObjectCacheMap::iterator it = _objectCache.begin(); it != _objectCache.end(); ++it) - f(it->first, it->second.first.get()); + f(it->first, it->second.mValue.get()); } /** Get the number of objects in the cache. */ @@ -195,11 +194,26 @@ namespace Resource return _objectCache.size(); } + template + std::optional>> lowerBound(K&& key) + { + const std::lock_guard lock(_objectCacheMutex); + const auto it = _objectCache.lower_bound(std::forward(key)); + if (it == _objectCache.end()) + return std::nullopt; + return std::pair(it->first, it->second.mValue); + } + protected: + struct Item + { + osg::ref_ptr mValue; + double mLastUsage; + }; + virtual ~GenericObjectCache() {} - typedef std::pair, double> ObjectTimeStampPair; - typedef std::map ObjectCacheMap; + using ObjectCacheMap = std::map>; ObjectCacheMap _objectCache; mutable std::mutex _objectCacheMutex; diff --git a/components/resource/resourcemanager.hpp b/components/resource/resourcemanager.hpp index fc5ecc7f03..09b99faf28 100644 --- a/components/resource/resourcemanager.hpp +++ b/components/resource/resourcemanager.hpp @@ -3,6 +3,8 @@ #include +#include + #include "objectcache.hpp" namespace VFS @@ -23,11 +25,11 @@ namespace Resource { public: virtual ~BaseResourceManager() = default; - virtual void updateCache(double referenceTime) {} - virtual void clearCache() {} - virtual void setExpiryDelay(double expiryDelay) {} - virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const {} - virtual void releaseGLObjects(osg::State* state) {} + virtual void updateCache(double referenceTime) = 0; + virtual void clearCache() = 0; + virtual void setExpiryDelay(double expiryDelay) = 0; + virtual void reportStats(unsigned int frameNumber, osg::Stats* stats) const = 0; + virtual void releaseGLObjects(osg::State* state) = 0; }; /// @brief Base class for managers that require a virtual file system and object cache. @@ -39,10 +41,11 @@ namespace Resource public: typedef GenericObjectCache CacheType; - GenericResourceManager(const VFS::Manager* vfs) + explicit GenericResourceManager( + const VFS::Manager* vfs, double expiryDelay = Settings::cells().mCacheExpiryDelay) : mVFS(vfs) , mCache(new CacheType) - , mExpiryDelay(0.0) + , mExpiryDelay(expiryDelay) { } @@ -59,7 +62,7 @@ namespace Resource void clearCache() override { mCache->clear(); } /// How long to keep objects in cache after no longer being referenced. - void setExpiryDelay(double expiryDelay) override { mExpiryDelay = expiryDelay; } + void setExpiryDelay(double expiryDelay) final { mExpiryDelay = expiryDelay; } double getExpiryDelay() const { return mExpiryDelay; } const VFS::Manager* getVFS() const { return mVFS; } @@ -77,10 +80,15 @@ namespace Resource class ResourceManager : public GenericResourceManager { public: - ResourceManager(const VFS::Manager* vfs) + explicit ResourceManager(const VFS::Manager* vfs) : GenericResourceManager(vfs) { } + + explicit ResourceManager(const VFS::Manager* vfs, double expiryDelay) + : GenericResourceManager(vfs, expiryDelay) + { + } }; } diff --git a/components/terrain/chunkmanager.cpp b/components/terrain/chunkmanager.cpp index 0364a0a4fd..6273041f72 100644 --- a/components/terrain/chunkmanager.cpp +++ b/components/terrain/chunkmanager.cpp @@ -21,7 +21,7 @@ namespace Terrain ChunkManager::ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, CompositeMapRenderer* renderer, ESM::RefId worldspace) - : GenericResourceManager(nullptr) + : GenericResourceManager(nullptr) , QuadTreeWorld::ChunkManager(worldspace) , mStorage(storage) , mSceneManager(sceneMgr) @@ -39,38 +39,26 @@ namespace Terrain mMultiPassRoot->setAttributeAndModes(material, osg::StateAttribute::ON); } - struct FindChunkTemplate - { - void operator()(ChunkId id, osg::Object* obj) - { - if (std::get<0>(id) == std::get<0>(mId) && std::get<1>(id) == std::get<1>(mId)) - mFoundTemplate = obj; - } - ChunkId mId; - osg::ref_ptr mFoundTemplate; - }; - osg::ref_ptr ChunkManager::getChunk(float size, const osg::Vec2f& center, unsigned char lod, unsigned int lodFlags, bool activeGrid, const osg::Vec3f& viewPoint, bool compile) { // Override lod with the vertexLodMod adjusted value. // TODO: maybe we can refactor this code by moving all vertexLodMod code into this class. lod = static_cast(lodFlags >> (4 * 4)); - ChunkId id = std::make_tuple(center, lod, lodFlags); - osg::ref_ptr obj = mCache->getRefFromObjectCache(id); - if (obj) + + const ChunkKey key{ .mCenter = center, .mLod = lod, .mLodFlags = lodFlags }; + if (osg::ref_ptr obj = mCache->getRefFromObjectCache(key)) return static_cast(obj.get()); - else - { - FindChunkTemplate find; - find.mId = id; - mCache->call(find); - TerrainDrawable* templateGeometry - = find.mFoundTemplate ? static_cast(find.mFoundTemplate.get()) : nullptr; - osg::ref_ptr node = createChunk(size, center, lod, lodFlags, compile, templateGeometry); - mCache->addEntryToObjectCache(id, node.get()); - return node; - } + + const TerrainDrawable* templateGeometry = nullptr; + const TemplateKey templateKey{ .mCenter = center, .mLod = lod }; + const auto pair = mCache->lowerBound(templateKey); + if (pair.has_value() && templateKey == TemplateKey{ .mCenter = pair->first.mCenter, .mLod = pair->first.mLod }) + templateGeometry = static_cast(pair->second.get()); + + osg::ref_ptr node = createChunk(size, center, lod, lodFlags, compile, templateGeometry); + mCache->addEntryToObjectCache(key, node.get()); + return node; } void ChunkManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const @@ -80,14 +68,14 @@ namespace Terrain void ChunkManager::clearCache() { - GenericResourceManager::clearCache(); + GenericResourceManager::clearCache(); mBufferCache.clearCache(); } void ChunkManager::releaseGLObjects(osg::State* state) { - GenericResourceManager::releaseGLObjects(state); + GenericResourceManager::releaseGLObjects(state); mBufferCache.releaseGLObjects(state); } @@ -202,7 +190,7 @@ namespace Terrain } osg::ref_ptr ChunkManager::createChunk(float chunkSize, const osg::Vec2f& chunkCenter, unsigned char lod, - unsigned int lodFlags, bool compile, TerrainDrawable* templateGeometry) + unsigned int lodFlags, bool compile, const TerrainDrawable* templateGeometry) { osg::ref_ptr geometry(new TerrainDrawable); diff --git a/components/terrain/chunkmanager.hpp b/components/terrain/chunkmanager.hpp index 4238f01c8b..a55dd15cc1 100644 --- a/components/terrain/chunkmanager.hpp +++ b/components/terrain/chunkmanager.hpp @@ -28,10 +28,51 @@ namespace Terrain class CompositeMap; class TerrainDrawable; - typedef std::tuple ChunkId; // Center, Lod, Lod Flags + struct TemplateKey + { + osg::Vec2f mCenter; + unsigned char mLod; + }; + + inline auto tie(const TemplateKey& v) + { + return std::tie(v.mCenter, v.mLod); + } + + inline bool operator<(const TemplateKey& l, const TemplateKey& r) + { + return tie(l) < tie(r); + } + + inline bool operator==(const TemplateKey& l, const TemplateKey& r) + { + return tie(l) == tie(r); + } + + struct ChunkKey + { + osg::Vec2f mCenter; + unsigned char mLod; + unsigned mLodFlags; + }; + + inline auto tie(const ChunkKey& v) + { + return std::tie(v.mCenter, v.mLod, v.mLodFlags); + } + + inline bool operator<(const ChunkKey& l, const ChunkKey& r) + { + return tie(l) < tie(r); + } + + inline bool operator<(const ChunkKey& l, const TemplateKey& r) + { + return TemplateKey{ .mCenter = l.mCenter, .mLod = l.mLod } < r; + } /// @brief Handles loading and caching of terrain chunks - class ChunkManager : public Resource::GenericResourceManager, public QuadTreeWorld::ChunkManager + class ChunkManager : public Resource::GenericResourceManager, public QuadTreeWorld::ChunkManager { public: ChunkManager(Storage* storage, Resource::SceneManager* sceneMgr, TextureManager* textureManager, @@ -55,7 +96,7 @@ namespace Terrain private: osg::ref_ptr createChunk(float size, const osg::Vec2f& center, unsigned char lod, - unsigned int lodFlags, bool compile, TerrainDrawable* templateGeometry); + unsigned int lodFlags, bool compile, const TerrainDrawable* templateGeometry); osg::ref_ptr createCompositeMapRTT(); diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 8449f3df25..52edd96b9f 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -544,37 +544,16 @@ namespace Terrain vd->setViewPoint(viewPoint); vd->setActiveGrid(grid); - for (unsigned int pass = 0; pass < 3; ++pass) + DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid, cellWorldSize); + mRootNode->traverseNodes(vd, viewPoint, &lodCallback); + + reporter.addTotal(vd->getNumEntries()); + + for (unsigned int i = 0, n = vd->getNumEntries(); i < n && !abort; ++i) { - unsigned int startEntry = vd->getNumEntries(); - - float distanceModifier = 0.f; - if (pass == 1) - distanceModifier = 1024; - else if (pass == 2) - distanceModifier = -1024; - DefaultLodCallback lodCallback(mLodFactor, mMinSize, mViewDistance, grid, cellWorldSize, distanceModifier); - mRootNode->traverseNodes(vd, viewPoint, &lodCallback); - - if (pass == 0) - { - std::size_t progressTotal = 0; - for (unsigned int i = 0, n = vd->getNumEntries(); i < n; ++i) - progressTotal += vd->getEntry(i).mNode->getSize(); - - reporter.addTotal(progressTotal); - } - - for (unsigned int i = startEntry; i < vd->getNumEntries() && !abort; ++i) - { - ViewDataEntry& entry = vd->getEntry(i); - - loadRenderingNode(entry, vd, cellWorldSize, grid, true); - if (pass == 0) - reporter.addProgress(entry.mNode->getSize()); - vd->removeNodeFromIndex(entry.mNode); - entry.mNode = nullptr; // Clear node lest we break the neighbours search for the next pass - } + ViewDataEntry& entry = vd->getEntry(i); + loadRenderingNode(entry, vd, cellWorldSize, grid, true); + reporter.addProgress(1); } } diff --git a/components/terrain/terraindrawable.hpp b/components/terrain/terraindrawable.hpp index b94f47df97..3ce846ad73 100644 --- a/components/terrain/terraindrawable.hpp +++ b/components/terrain/terraindrawable.hpp @@ -60,7 +60,7 @@ namespace Terrain const osg::BoundingBox& getWaterBoundingBox() const { return mWaterBoundingBox; } void setCompositeMap(CompositeMap* map) { mCompositeMap = map; } - CompositeMap* getCompositeMap() { return mCompositeMap; } + CompositeMap* getCompositeMap() const { return mCompositeMap; } void setCompositeMapRenderer(CompositeMapRenderer* renderer) { mCompositeMapRenderer = renderer; } private: diff --git a/docs/source/reference/lua-scripting/api.rst b/docs/source/reference/lua-scripting/api.rst index 5a1f8236b9..6d27db0515 100644 --- a/docs/source/reference/lua-scripting/api.rst +++ b/docs/source/reference/lua-scripting/api.rst @@ -17,6 +17,7 @@ Lua API reference openmw_core openmw_types openmw_async + openmw_vfs openmw_world openmw_self openmw_nearby diff --git a/docs/source/reference/lua-scripting/openmw_vfs.rst b/docs/source/reference/lua-scripting/openmw_vfs.rst new file mode 100644 index 0000000000..407459e7e0 --- /dev/null +++ b/docs/source/reference/lua-scripting/openmw_vfs.rst @@ -0,0 +1,7 @@ +Package openmw.vfs +================== + +.. include:: version.rst + +.. raw:: html + :file: generated_html/openmw_vfs.html diff --git a/docs/source/reference/lua-scripting/overview.rst b/docs/source/reference/lua-scripting/overview.rst index 598d4a4c99..90014f637b 100644 --- a/docs/source/reference/lua-scripting/overview.rst +++ b/docs/source/reference/lua-scripting/overview.rst @@ -6,7 +6,7 @@ Overview of Lua scripting Language and sandboxing ======================= -OpenMW supports scripts written in Lua 5.1 with some extensions (see below) from Lua 5.2. +OpenMW supports scripts written in Lua 5.1 with some extensions (see below) from Lua 5.2 and Lua 5.3. There are no plans to switch to any newer version of the language, because newer versions are not supported by LuaJIT. .. note:: @@ -40,6 +40,10 @@ Supported Lua 5.2 features: - ``__pairs`` and ``__ipairs`` metamethods; - Function ``table.unpack`` (alias to Lua 5.1 ``unpack``). +Supported Lua 5.3 features: + +- All functions in the `UTF-8 Library `__ + Loading libraries with ``require('library_name')`` is allowed, but limited. It works this way: 1. If `library_name` is one of the standard libraries, then return the library. diff --git a/docs/source/reference/lua-scripting/tables/packages.rst b/docs/source/reference/lua-scripting/tables/packages.rst index e746274e6d..67709bbf7b 100644 --- a/docs/source/reference/lua-scripting/tables/packages.rst +++ b/docs/source/reference/lua-scripting/tables/packages.rst @@ -15,6 +15,8 @@ +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.async ` | everywhere | | Timers and callbacks. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ +|:ref:`openmw.vfs ` | everywhere | | Read-only access to data directories via VFS. | ++------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.world ` | by global scripts | | Read-write access to the game world. | +------------------------------------------------------------+--------------------+---------------------------------------------------------------+ |:ref:`openmw.self ` | by local scripts | | Full access to the object the script is attached to. | diff --git a/files/data/scripts/omw/console/global.lua b/files/data/scripts/omw/console/global.lua index e3b4375b1e..bba0cbc7b3 100644 --- a/files/data/scripts/omw/console/global.lua +++ b/files/data/scripts/omw/console/global.lua @@ -22,6 +22,7 @@ local env = { storage = require('openmw.storage'), core = require('openmw.core'), types = require('openmw.types'), + vfs = require('openmw.vfs'), async = require('openmw.async'), world = require('openmw.world'), aux_util = require('openmw_aux.util'), diff --git a/files/data/scripts/omw/console/local.lua b/files/data/scripts/omw/console/local.lua index adcad3d6cb..6962b9e798 100644 --- a/files/data/scripts/omw/console/local.lua +++ b/files/data/scripts/omw/console/local.lua @@ -24,6 +24,7 @@ local env = { storage = require('openmw.storage'), core = require('openmw.core'), types = require('openmw.types'), + vfs = require('openmw.vfs'), async = require('openmw.async'), nearby = require('openmw.nearby'), self = require('openmw.self'), diff --git a/files/data/scripts/omw/console/player.lua b/files/data/scripts/omw/console/player.lua index 464482e7ad..c614d2d962 100644 --- a/files/data/scripts/omw/console/player.lua +++ b/files/data/scripts/omw/console/player.lua @@ -71,6 +71,8 @@ local env = { storage = require('openmw.storage'), core = require('openmw.core'), types = require('openmw.types'), + vfs = require('openmw.vfs'), + ambient = require('openmw.ambient'), async = require('openmw.async'), nearby = require('openmw.nearby'), self = require('openmw.self'), diff --git a/files/lua_api/CMakeLists.txt b/files/lua_api/CMakeLists.txt index 87f5bfe91b..96409e803e 100644 --- a/files/lua_api/CMakeLists.txt +++ b/files/lua_api/CMakeLists.txt @@ -12,14 +12,15 @@ set(LUA_API_FILES openmw/ambient.lua openmw/async.lua openmw/core.lua + openmw/debug.lua openmw/nearby.lua + openmw/postprocessing.lua openmw/self.lua + openmw/types.lua openmw/ui.lua openmw/util.lua + openmw/vfs.lua openmw/world.lua - openmw/types.lua - openmw/postprocessing.lua - openmw/debug.lua ) foreach (f ${LUA_API_FILES}) diff --git a/files/lua_api/openmw/vfs.lua b/files/lua_api/openmw/vfs.lua new file mode 100644 index 0000000000..ba381a1249 --- /dev/null +++ b/files/lua_api/openmw/vfs.lua @@ -0,0 +1,159 @@ +--- +-- `openmw.vfs` provides read-only access to data directories via VFS. +-- Interface is very similar to "io" library. +-- @module vfs +-- @usage local vfs = require('openmw.vfs') + + + +--- +-- @type FileHandle +-- @field #string fileName VFS path to related file + +--- +-- Close a file handle +-- @function [parent=#FileHandle] close +-- @param self +-- @return #boolean true if a call succeeds without errors. +-- @return #nil, #string nil plus the error message in case of any error. + +--- +-- Get an iterator function to fetch the next line from given file. +-- Throws an exception if file is closed. +-- +-- Hint: since garbage collection works once per frame, +-- you will get the whole file in RAM if you read it in one frame. +-- So if you need to read a really large file, it is better to split reading +-- between different frames (e.g. by keeping a current position in file +-- and using a "seek" to read from saved position). +-- @function [parent=#FileHandle] lines +-- @param self +-- @return #function Iterator function to get next line +-- @usage f = vfs.open("Test\\test.txt"); +-- for line in f:lines() do +-- print(line); +-- end + +--- +-- Set new position in file. +-- Throws an exception if file is closed or seek base is incorrect. +-- @function [parent=#FileHandle] seek +-- @param self +-- @param #string whence Seek base (optional, "cur" by default). Can be: +-- +-- * "set" - seek from beginning of file; +-- * "cur" - seek from current position; +-- * "end" - seek from end of file (offset needs to be <= 0); +-- @param #number offset Offset from given base (optional, 0 by default) +-- @return #number new position in file if a call succeeds without errors. +-- @return #nil, #string nil plus the error message in case of any error. +-- @usage -- set pointer to beginning of file +-- f = vfs.open("Test\\test.txt"); +-- f:seek("set"); +-- @usage -- print current position in file +-- f = vfs.open("Test\\test.txt"); +-- print(f:seek()); +-- @usage -- print file size +-- f = vfs.open("Test\\test.txt"); +-- print(f:seek("end")); + +--- +-- Read data from file to strings. +-- Throws an exception if file is closed, if there is too many arguments or if an invalid format encountered. +-- +-- Hint: since garbage collection works once per frame, +-- you will get the whole file in RAM if you read it in one frame. +-- So if you need to read a really large file, it is better to split reading +-- between different frames (e.g. by keeping a current position in file +-- and using a "seek" to read from saved position). +-- @function [parent=#FileHandle] read +-- @param self +-- @param ... Read formats (up to 20 arguments, default value is one "*l"). Can be: +-- +-- * "\*a" (or "*all") - reads the whole file, starting at the current position as #string. On end of file, it returns the empty string. +-- * "\*l" (or "*line") - reads the next line (skipping the end of line), returning nil on end of file (nil and error message if error occured); +-- * "\*n" (or "*number") - read a floating point value as #number (nil and error message if error occured); +-- * number - reads a #string with up to this number of characters, returning nil on end of file (nil and error message if error occured). If number is 0 and end of file is not reached, it reads nothing and returns an empty string; +-- @return #string One #string for every format if a call succeeds without errors. One #string for every successfully handled format, nil for first failed format. +-- @usage -- read three numbers from file +-- f = vfs.open("Test\\test.txt"); +-- local n1, n2, n3 = f:read("*number", "*number", "*number"); +-- @usage -- read 10 bytes from file +-- f = vfs.open("Test\\test.txt"); +-- local n4 = f:read(10); +-- @usage -- read until end of file +-- f = vfs.open("Test\\test.txt"); +-- local n5 = f:read("*all"); +-- @usage -- read a line from file +-- f = vfs.open("Test\\test.txt"); +-- local n6 = f:read(); +-- @usage -- try to read three numbers from file with "1" content +-- f = vfs.open("one.txt"); +-- print(f:read("*number", "*number", "*number")); +-- -- prints(1, nil) + +--- +-- Check if file exists in VFS +-- @function [parent=#vfs] fileExists +-- @param #string fileName Path to file in VFS +-- @return #boolean (true - exists, false - does not exist) +-- @usage local exists = vfs.fileExists("Test\\test.txt"); + +--- +-- Open a file +-- @function [parent=#vfs] open +-- @param #string fileName Path to file in VFS +-- @return #FileHandle Opened file handle if a call succeeds without errors. +-- @return #nil, #string nil plus the error message in case of any error. +-- @usage f, msg = vfs.open("Test\\test.txt"); +-- -- print file name or error message +-- if (f == nil) +-- print(msg); +-- else +-- print(f.fileName); +-- end + +--- +-- Get an iterator function to fetch the next line from file with given path. +-- Throws an exception if file is closed or file with given path does not exist. +-- Closes file automatically when it fails to read any more bytes. +-- +-- Hint: since garbage collection works once per frame, +-- you will get the whole file in RAM if you read it in one frame. +-- So if you need to read a really large file, it is better to split reading +-- between different frames (e.g. by keeping a current position in file +-- and using a "seek" to read from saved position). +-- @function [parent=#vfs] lines +-- @param #string fileName Path to file in VFS +-- @return #function Iterator function to get next line +-- @usage for line in vfs.lines("Test\\test.txt") do +-- print(line); +-- end + +--- +-- Get iterator function to fetch file names with given path prefix from VFS +-- @function [parent=#vfs] pathsWithPrefix +-- @param #string path Path prefix +-- @return #function Function to get next file name +-- @usage -- get all files with given prefix from VFS index +-- for fileName in vfs.pathsWithPrefix("Music\\Explore") do +-- print(fileName); +-- end +-- @usage -- get some first files +-- local getNextFile = vfs.pathsWithPrefix("Music\\Explore"); +-- local firstFile = getNextFile(); +-- local secondFile = getNextFile(); + +--- +-- Detect a file handle type +-- @function [parent=#vfs] type +-- @param #any handle Object to check +-- @return #string File handle type. Can be: +-- +-- * "file" - an argument is a valid opened @{openmw.vfs#FileHandle}; +-- * "closed file" - an argument is a valid closed @{openmw.vfs#FileHandle}; +-- * nil - an argument is not a @{openmw.vfs#FileHandle}; +-- @usage f = vfs.open("Test\\test.txt"); +-- print(vfs.type(f)); + +return nil diff --git a/files/lua_api/utf8.doclua b/files/lua_api/utf8.doclua new file mode 100644 index 0000000000..6c054f4401 --- /dev/null +++ b/files/lua_api/utf8.doclua @@ -0,0 +1,77 @@ +------------------------------------------------------------------------------- +-- UTF-8 Support. +-- This library provides basic support for UTF-8 encoding. +-- It provides all its functions inside the table utf8. +-- This library does not provide any support for Unicode other than the handling of the encoding. +-- Any operation that needs the meaning of a character, such as character classification, is outside its scope. +-- +-- Unless stated otherwise, all functions that expect a byte position as a parameter assume that +-- the given position is either the start of a byte sequence or one plus the length of the subject string. +-- As in the string library, negative indices count from the end of the string. +-- @module utf8 + +------------------------------------------------------------------------------- +-- Receives zero or more integers, converts each one to its +-- corresponding UTF-8 byte sequence, and returns a string with the concatenation +-- of all these sequences. +-- @function [parent=#utf8] char +-- @param ... zero or more integers. +-- @return #string + +------------------------------------------------------------------------------- +-- The pattern which matches exactly one UTF-8 byte sequence, assuming that +-- the subject is a valid UTF-8 string. +-- @function [parent=#utf8] charpattern +-- @return #string + +------------------------------------------------------------------------------- +-- Returns values so that the construction +-- +-- for p, c in utf8.codes(s) do body end +-- +-- will iterate over all characters in string s, with p being the position (in bytes) +-- and c the code point of each character. +-- It raises an error if it meets any invalid byte sequence. +-- @function [parent=#utf8] codes +-- @param #string s string to handle. + +------------------------------------------------------------------------------- +-- Returns the codepoints (as integers) from all characters in s that start +-- between byte position i and j (both included). The default for i is 1 and for j is i. +-- It raises an error if it meets any invalid byte sequence. +-- @function [parent=#utf8] codepoint +-- @param #string s string to handle +-- @param #number i the initial position (default value is 1) +-- @param #number j the final position (default value is i) +-- @return #number the codepoints of each character in s + +------------------------------------------------------------------------------- +-- Returns the number of UTF-8 characters in string s that start +-- between positions i and j (both inclusive). +-- The default for i is 1 and for j is -1. +-- If it finds any invalid byte sequence, +-- returns a false value plus the position of the first invalid byte. +-- @function [parent=#utf8] len +-- @param #string s string to handle +-- @param #number i the initial position (default value is 1) +-- @param #number j the final position (default value is -1) +-- @return #number the number of utf8 characters in s + +------------------------------------------------------------------------------- +-- Returns the position (in bytes) where the encoding of the n-th character of s +-- (counting from position i) starts. A negative n gets characters before position i. +-- The default for i is 1 when n is non-negative and #s + 1 otherwise, +-- so that utf8.offset(s, -n) gets the offset of the n-th character from the end of the string. +-- If the specified character is neither in the subject nor right after its end, the function returns nil. +-- +-- As a special case, when n is 0 the function returns the +-- start of the encoding of the character that contains the i-th byte of s. +-- +-- This function assumes that s is a valid UTF-8 string. +-- @function [parent=#utf8] offset +-- @param #string s string to handle +-- @param #number n the n-th character +-- @param #number i the initial position (default value is 1 if n is is non-negative and #s + 1 otherwise) +-- @return #number + +return nil