From 77c978c2266f9b7f08e3e57d7b37dd83b99d834a Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 24 Oct 2023 17:23:54 +0200 Subject: [PATCH 01/71] Use more fixed size integers --- components/esm3/aipackage.hpp | 10 +++++----- components/esm3/creaturelevliststate.hpp | 2 +- components/esm3/creaturestats.hpp | 5 ++--- components/esm3/debugprofile.hpp | 4 ++-- components/esm3/doorstate.hpp | 2 +- components/esm3/filter.hpp | 2 +- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/components/esm3/aipackage.hpp b/components/esm3/aipackage.hpp index 61aea2750a..7346a4af36 100644 --- a/components/esm3/aipackage.hpp +++ b/components/esm3/aipackage.hpp @@ -16,10 +16,10 @@ namespace ESM struct AIData { - unsigned short mHello; // This is the base value for greeting distance [0, 65535] + uint16_t mHello; // This is the base value for greeting distance [0, 65535] unsigned char mFight, mFlee, mAlarm; // These are probabilities [0, 100] char mU1, mU2, mU3; // Unknown values - int mServices; // See the Services enum + int32_t mServices; // See the Services enum void blank(); ///< Set record to default state (does not touch the ID). @@ -27,8 +27,8 @@ namespace ESM struct AIWander { - short mDistance; - short mDuration; + int16_t mDistance; + int16_t mDuration; unsigned char mTimeOfDay; unsigned char mIdle[8]; unsigned char mShouldRepeat; @@ -44,7 +44,7 @@ namespace ESM struct AITarget { float mX, mY, mZ; - short mDuration; + int16_t mDuration; NAME32 mId; unsigned char mShouldRepeat; unsigned char mPadding; diff --git a/components/esm3/creaturelevliststate.hpp b/components/esm3/creaturelevliststate.hpp index f8fb7162ff..e7121cf8ac 100644 --- a/components/esm3/creaturelevliststate.hpp +++ b/components/esm3/creaturelevliststate.hpp @@ -9,7 +9,7 @@ namespace ESM struct CreatureLevListState final : public ObjectState { - int mSpawnActorId; + int32_t mSpawnActorId; bool mSpawn; void load(ESMReader& esm) override; diff --git a/components/esm3/creaturestats.hpp b/components/esm3/creaturestats.hpp index 63ba49cdaa..6e65a52354 100644 --- a/components/esm3/creaturestats.hpp +++ b/components/esm3/creaturestats.hpp @@ -47,9 +47,8 @@ namespace ESM std::vector mSummonGraveyard; TimeStamp mTradeTime; - int mGoldPool; - int mActorId; - // int mHitAttemptActorId; + int32_t mGoldPool; + int32_t mActorId; enum Flags { diff --git a/components/esm3/debugprofile.hpp b/components/esm3/debugprofile.hpp index fc48fb23f6..a86e84bfd5 100644 --- a/components/esm3/debugprofile.hpp +++ b/components/esm3/debugprofile.hpp @@ -24,14 +24,14 @@ namespace ESM Flag_Global = 4 // make available from main menu (i.e. not location specific) }; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; std::string mDescription; std::string mScriptText; - unsigned int mFlags; + uint32_t mFlags; void load(ESMReader& esm, bool& isDeleted); void save(ESMWriter& esm, bool isDeleted = false) const; diff --git a/components/esm3/doorstate.hpp b/components/esm3/doorstate.hpp index 5298327707..c23ffd5ad2 100644 --- a/components/esm3/doorstate.hpp +++ b/components/esm3/doorstate.hpp @@ -9,7 +9,7 @@ namespace ESM struct DoorState final : public ObjectState { - int mDoorState = 0; + int32_t mDoorState = 0; void load(ESMReader& esm) override; void save(ESMWriter& esm, bool inInventory = false) const override; diff --git a/components/esm3/filter.hpp b/components/esm3/filter.hpp index c4642285af..6a978a2596 100644 --- a/components/esm3/filter.hpp +++ b/components/esm3/filter.hpp @@ -17,7 +17,7 @@ namespace ESM static constexpr std::string_view getRecordType() { return "Filter"; } - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; std::string mDescription; From dc781bad5d83fbb00f040902058a94e5cddb575e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 24 Oct 2023 17:51:12 +0200 Subject: [PATCH 02/71] Use fixed size unsigned ints for inventory offsets --- apps/essimporter/convertinventory.cpp | 5 +- apps/openmw/mwworld/containerstore.cpp | 12 ++--- apps/openmw/mwworld/containerstore.hpp | 8 ++-- apps/openmw/mwworld/inventorystore.cpp | 16 +++---- apps/openmw/mwworld/inventorystore.hpp | 4 +- components/esm3/inventorystate.cpp | 63 ++++++++++++++------------ components/esm3/inventorystate.hpp | 16 +++---- 7 files changed, 62 insertions(+), 62 deletions(-) diff --git a/apps/essimporter/convertinventory.cpp b/apps/essimporter/convertinventory.cpp index 2f03cfaf41..69a2ea4120 100644 --- a/apps/essimporter/convertinventory.cpp +++ b/apps/essimporter/convertinventory.cpp @@ -9,15 +9,14 @@ namespace ESSImport void convertInventory(const Inventory& inventory, ESM::InventoryState& state) { - int index = 0; + uint32_t index = 0; for (const auto& item : inventory.mItems) { ESM::ObjectState objstate; objstate.blank(); objstate.mRef = item; objstate.mRef.mRefID = ESM::RefId::stringRefId(item.mId); - objstate.mCount = std::abs(item.mCount); // restocking items have negative count in the savefile - // openmw handles them differently, so no need to set any flags + objstate.mCount = item.mCount; state.mItems.push_back(objstate); if (item.mRelativeEquipmentSlot != -1) // Note we should really write the absolute slot here, which we do not know about diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index d82125a5ce..b55a524a48 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -111,12 +111,12 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState( } void MWWorld::ContainerStore::storeEquipmentState( - const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const + const MWWorld::LiveCellRefBase& ref, size_t index, ESM::InventoryState& inventory) const { } void MWWorld::ContainerStore::readEquipmentState( - const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory) + const MWWorld::ContainerStoreIterator& iter, size_t index, const ESM::InventoryState& inventory) { } @@ -128,7 +128,7 @@ void MWWorld::ContainerStore::storeState(const LiveCellRef& ref, ESM::ObjectS template void MWWorld::ContainerStore::storeStates( - const CellRefList& collection, ESM::InventoryState& inventory, int& index, bool equipable) const + const CellRefList& collection, ESM::InventoryState& inventory, size_t& index, bool equipable) const { for (const LiveCellRef& liveCellRef : collection.mList) { @@ -926,7 +926,7 @@ void MWWorld::ContainerStore::writeState(ESM::InventoryState& state) const { state.mItems.clear(); - int index = 0; + size_t index = 0; storeStates(potions, state, index); storeStates(appas, state, index); storeStates(armors, state, index, true); @@ -947,12 +947,12 @@ void MWWorld::ContainerStore::readState(const ESM::InventoryState& inventory) mModified = true; mResolved = true; - int index = 0; + size_t index = 0; for (const ESM::ObjectState& state : inventory.mItems) { int type = MWBase::Environment::get().getESMStore()->find(state.mRef.mRefID); - int thisIndex = index++; + size_t thisIndex = index++; switch (type) { diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 889fcf7463..fb2722dde8 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -161,16 +161,16 @@ namespace MWWorld void storeState(const LiveCellRef& ref, ESM::ObjectState& state) const; template - void storeStates( - const CellRefList& collection, ESM::InventoryState& inventory, int& index, bool equipable = false) const; + void storeStates(const CellRefList& collection, ESM::InventoryState& inventory, size_t& index, + bool equipable = false) const; void updateRechargingItems(); virtual void storeEquipmentState( - const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const; + const MWWorld::LiveCellRefBase& ref, size_t index, ESM::InventoryState& inventory) const; virtual void readEquipmentState( - const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory); + const MWWorld::ContainerStoreIterator& iter, size_t index, const ESM::InventoryState& inventory); public: ContainerStore(); diff --git a/apps/openmw/mwworld/inventorystore.cpp b/apps/openmw/mwworld/inventorystore.cpp index 9b3470a835..095f5d3cc1 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -46,32 +46,32 @@ void MWWorld::InventoryStore::initSlots(TSlots& slots_) } void MWWorld::InventoryStore::storeEquipmentState( - const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const + const MWWorld::LiveCellRefBase& ref, size_t index, ESM::InventoryState& inventory) const { - for (int i = 0; i < static_cast(mSlots.size()); ++i) + for (int32_t i = 0; i < MWWorld::InventoryStore::Slots; ++i) + { if (mSlots[i].getType() != -1 && mSlots[i]->getBase() == &ref) - { - inventory.mEquipmentSlots[index] = i; - } + inventory.mEquipmentSlots[static_cast(index)] = i; + } if (mSelectedEnchantItem.getType() != -1 && mSelectedEnchantItem->getBase() == &ref) inventory.mSelectedEnchantItem = index; } void MWWorld::InventoryStore::readEquipmentState( - const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory) + const MWWorld::ContainerStoreIterator& iter, size_t index, const ESM::InventoryState& inventory) { if (index == inventory.mSelectedEnchantItem) mSelectedEnchantItem = iter; - std::map::const_iterator found = inventory.mEquipmentSlots.find(index); + auto found = inventory.mEquipmentSlots.find(index); if (found != inventory.mEquipmentSlots.end()) { if (found->second < 0 || found->second >= MWWorld::InventoryStore::Slots) throw std::runtime_error("Invalid slot index in inventory state"); // make sure the item can actually be equipped in this slot - int slot = found->second; + int32_t slot = found->second; std::pair, bool> allowedSlots = iter->getClass().getEquipmentSlots(*iter); if (!allowedSlots.first.size()) return; diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 6df5fa1e5a..0af6ee2b28 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -81,9 +81,9 @@ namespace MWWorld void fireEquipmentChangedEvent(); void storeEquipmentState( - const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const override; + const MWWorld::LiveCellRefBase& ref, size_t index, ESM::InventoryState& inventory) const override; void readEquipmentState( - const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory) override; + const MWWorld::ContainerStoreIterator& iter, size_t index, const ESM::InventoryState& inventory) override; ContainerStoreIterator findSlot(int slot) const; diff --git a/components/esm3/inventorystate.cpp b/components/esm3/inventorystate.cpp index e58d0335bf..c3af101edc 100644 --- a/components/esm3/inventorystate.cpp +++ b/components/esm3/inventorystate.cpp @@ -7,22 +7,25 @@ namespace ESM { + namespace + { + constexpr uint32_t sInvalidSlot = -1; + } void InventoryState::load(ESMReader& esm) { // obsolete - int index = 0; + uint32_t index = 0; while (esm.isNextSub("IOBJ")) { - int unused; // no longer used - esm.getHT(unused); + esm.skip(4); ObjectState state; // obsolete if (esm.isNextSub("SLOT")) { - int slot; + int32_t slot; esm.getHT(slot); mEquipmentSlots[index] = slot; } @@ -38,9 +41,9 @@ namespace ESM ++index; } - int itemsCount = 0; + uint32_t itemsCount = 0; esm.getHNOT(itemsCount, "ICNT"); - for (int i = 0; i < itemsCount; i++) + for (; itemsCount > 0; --itemsCount) { ObjectState state; @@ -62,7 +65,7 @@ namespace ESM { // Get its name ESM::RefId id = esm.getRefId(); - int count; + int32_t count; std::string parentGroup; // Then get its count esm.getHNT(count, "COUN"); @@ -91,9 +94,9 @@ namespace ESM while (esm.isNextSub("EQUI")) { esm.getSubHeader(); - int equipIndex; + int32_t equipIndex; esm.getT(equipIndex); - int slot; + int32_t slot; esm.getT(slot); mEquipmentSlots[equipIndex] = slot; } @@ -101,20 +104,24 @@ namespace ESM if (esm.isNextSub("EQIP")) { esm.getSubHeader(); - int slotsCount = 0; + uint32_t slotsCount = 0; esm.getT(slotsCount); - for (int i = 0; i < slotsCount; i++) + for (; slotsCount > 0; --slotsCount) { - int equipIndex; + int32_t equipIndex; esm.getT(equipIndex); - int slot; + int32_t slot; esm.getT(slot); mEquipmentSlots[equipIndex] = slot; } } - mSelectedEnchantItem = -1; - esm.getHNOT(mSelectedEnchantItem, "SELE"); + uint32_t selectedEnchantItem = sInvalidSlot; + esm.getHNOT(selectedEnchantItem, "SELE"); + if (selectedEnchantItem == sInvalidSlot) + mSelectedEnchantItem.reset(); + else + mSelectedEnchantItem = selectedEnchantItem; // Old saves had restocking levelled items in a special map // This turns items from that map into negative quantities @@ -132,7 +139,7 @@ namespace ESM void InventoryState::save(ESMWriter& esm) const { - int itemsCount = static_cast(mItems.size()); + uint32_t itemsCount = static_cast(mItems.size()); if (itemsCount > 0) { esm.writeHNT("ICNT", itemsCount); @@ -149,34 +156,32 @@ namespace ESM esm.writeHNString("LGRP", it->first.second); } - for (TEffectMagnitudes::const_iterator it = mPermanentMagicEffectMagnitudes.begin(); - it != mPermanentMagicEffectMagnitudes.end(); ++it) + for (const auto& [id, params] : mPermanentMagicEffectMagnitudes) { - esm.writeHNRefId("MAGI", it->first); + esm.writeHNRefId("MAGI", id); - const std::vector>& params = it->second; - for (std::vector>::const_iterator pIt = params.begin(); pIt != params.end(); ++pIt) + for (const auto& [rand, mult] : params) { - esm.writeHNT("RAND", pIt->first); - esm.writeHNT("MULT", pIt->second); + esm.writeHNT("RAND", rand); + esm.writeHNT("MULT", mult); } } - int slotsCount = static_cast(mEquipmentSlots.size()); + uint32_t slotsCount = static_cast(mEquipmentSlots.size()); if (slotsCount > 0) { esm.startSubRecord("EQIP"); esm.writeT(slotsCount); - for (std::map::const_iterator it = mEquipmentSlots.begin(); it != mEquipmentSlots.end(); ++it) + for (const auto& [index, slot] : mEquipmentSlots) { - esm.writeT(it->first); - esm.writeT(it->second); + esm.writeT(index); + esm.writeT(slot); } esm.endRecord("EQIP"); } - if (mSelectedEnchantItem != -1) - esm.writeHNT("SELE", mSelectedEnchantItem); + if (mSelectedEnchantItem) + esm.writeHNT("SELE", *mSelectedEnchantItem); } } diff --git a/components/esm3/inventorystate.hpp b/components/esm3/inventorystate.hpp index a0fd948951..050d1eb92f 100644 --- a/components/esm3/inventorystate.hpp +++ b/components/esm3/inventorystate.hpp @@ -2,6 +2,7 @@ #define OPENMW_ESM_INVENTORYSTATE_H #include +#include #include "objectstate.hpp" #include @@ -19,20 +20,15 @@ namespace ESM std::vector mItems; // - std::map mEquipmentSlots; + std::map mEquipmentSlots; - std::map, int> mLevelledItemMap; + std::map, int32_t> mLevelledItemMap; - typedef std::map>> TEffectMagnitudes; - TEffectMagnitudes mPermanentMagicEffectMagnitudes; + std::map>> mPermanentMagicEffectMagnitudes; - int mSelectedEnchantItem; // For inventories only + std::optional mSelectedEnchantItem; // For inventories only - InventoryState() - : mSelectedEnchantItem(-1) - { - } - virtual ~InventoryState() {} + virtual ~InventoryState() = default; virtual void load(ESMReader& esm); virtual void save(ESMWriter& esm) const; From 77aaa6177e2bbb7f2925bd8b67225f0750e5f081 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 24 Oct 2023 19:25:52 +0200 Subject: [PATCH 03/71] Use more fixed size ints --- components/esm3/aisequence.cpp | 2 +- components/esm3/loadcell.cpp | 12 ++++++------ components/esm3/loadcell.hpp | 10 +++++----- components/esm3/loadcont.hpp | 6 +++--- components/esm3/loadglob.hpp | 2 +- components/esm3/loadgmst.hpp | 2 +- components/esm3/loadlevlist.cpp | 10 +++++----- components/esm3/loadlevlist.hpp | 6 +++--- components/esm3/loadltex.hpp | 2 +- components/esm3/loadmgef.cpp | 2 +- components/esm3/loadmgef.hpp | 8 ++++---- components/esm3/loadnpc.cpp | 2 +- components/esm3/loadnpc.hpp | 14 +++++++------- components/esm3/loadrace.hpp | 10 +++++----- components/esm3/loadsndg.hpp | 4 ++-- components/esm3/loadsscr.hpp | 2 +- components/esm3/loadstat.hpp | 2 +- components/esm3/loadtes3.hpp | 6 +++--- components/esm3/magiceffects.cpp | 4 ++-- components/esm3/magiceffects.hpp | 8 ++++---- components/esm3/npcstats.cpp | 26 +++++++++++++------------- components/esm3/npcstats.hpp | 24 ++++++++++++------------ components/esm3/objectstate.cpp | 4 ++-- components/esm3/objectstate.hpp | 4 ++-- components/esm3/player.hpp | 4 ++-- components/esm3/projectilestate.hpp | 2 +- components/esm3/queststate.hpp | 2 +- components/esm3/savedgame.hpp | 4 ++-- components/esm3/statstate.cpp | 8 ++++---- components/esm3/variant.cpp | 16 ++++++++-------- components/esm3/variant.hpp | 8 ++++---- components/esm3/variantimp.cpp | 12 ++++++------ components/esm3/variantimp.hpp | 4 ++-- 33 files changed, 116 insertions(+), 116 deletions(-) diff --git a/components/esm3/aisequence.cpp b/components/esm3/aisequence.cpp index 668b36c871..21973acd1d 100644 --- a/components/esm3/aisequence.cpp +++ b/components/esm3/aisequence.cpp @@ -196,7 +196,7 @@ namespace ESM int count = 0; while (esm.isNextSub("AIPK")) { - int type; + int32_t type; esm.getHT(type); mPackages.emplace_back(); diff --git a/components/esm3/loadcell.cpp b/components/esm3/loadcell.cpp index b966338ae5..829cf9e916 100644 --- a/components/esm3/loadcell.cpp +++ b/components/esm3/loadcell.cpp @@ -17,7 +17,7 @@ namespace ESM ///< Translate 8bit/24bit code (stored in refNum.mIndex) into a proper refNum void adjustRefNum(RefNum& refNum, const ESMReader& reader) { - unsigned int local = (refNum.mIndex & 0xff000000) >> 24; + uint32_t local = (refNum.mIndex & 0xff000000) >> 24; // If we have an index value that does not make sense, assume that it was an addition // by the present plugin (but a faulty one) @@ -124,7 +124,7 @@ namespace ESM switch (esm.retSubName().toInt()) { case fourCC("INTV"): - int waterl; + int32_t waterl; esm.getHT(waterl); mWater = static_cast(waterl); mWaterInt = true; @@ -192,7 +192,7 @@ namespace ESM { if (mWaterInt) { - int water = (mWater >= 0) ? (int)(mWater + 0.5) : (int)(mWater - 0.5); + int32_t water = (mWater >= 0) ? static_cast(mWater + 0.5) : static_cast(mWater - 0.5); esm.writeHNT("INTV", water); } else @@ -218,13 +218,13 @@ namespace ESM } } - void Cell::saveTempMarker(ESMWriter& esm, int tempCount) const + void Cell::saveTempMarker(ESMWriter& esm, int32_t tempCount) const { if (tempCount != 0) esm.writeHNT("NAM0", tempCount); } - void Cell::restore(ESMReader& esm, int iCtx) const + void Cell::restore(ESMReader& esm, size_t iCtx) const { esm.restoreContext(mContextList.at(iCtx)); } @@ -321,7 +321,7 @@ namespace ESM void Cell::blank() { - mName = ""; + mName.clear(); mRegion = ESM::RefId(); mWater = 0; mWaterInt = false; diff --git a/components/esm3/loadcell.hpp b/components/esm3/loadcell.hpp index 0ba0777e7c..bfabdd58f9 100644 --- a/components/esm3/loadcell.hpp +++ b/components/esm3/loadcell.hpp @@ -34,7 +34,7 @@ namespace ESM RefNum mRefNum; // Coordinates of target exterior cell - int mTarget[2]; + int32_t mTarget[2]; // The content file format does not support moving objects to an interior cell. // The save game format does support moving to interior cells, but uses a different mechanism @@ -153,13 +153,13 @@ namespace ESM ESMReader& esm, bool saveContext = true); // Load everything, except NAME, DATAstruct and references void save(ESMWriter& esm, bool isDeleted = false) const; - void saveTempMarker(ESMWriter& esm, int tempCount) const; + void saveTempMarker(ESMWriter& esm, int32_t tempCount) const; bool isExterior() const { return !(mData.mFlags & Interior); } - int getGridX() const { return mData.mX; } + int32_t getGridX() const { return mData.mX; } - int getGridY() const { return mData.mY; } + int32_t getGridY() const { return mData.mY; } bool hasWater() const { return ((mData.mFlags & HasWater) != 0) || isExterior(); } @@ -172,7 +172,7 @@ namespace ESM // somewhere other than the file system, you need to pre-open the // ESMReader, and the filename must match the stored filename // exactly. - void restore(ESMReader& esm, int iCtx) const; + void restore(ESMReader& esm, size_t iCtx) const; std::string getDescription() const; ///< Return a short string describing the cell (mostly used for debugging/logging purpose) diff --git a/components/esm3/loadcont.hpp b/components/esm3/loadcont.hpp index 3921821da0..3c1fb6468e 100644 --- a/components/esm3/loadcont.hpp +++ b/components/esm3/loadcont.hpp @@ -19,7 +19,7 @@ namespace ESM struct ContItem { - int mCount{ 0 }; + int32_t mCount{ 0 }; ESM::RefId mItem; }; @@ -48,12 +48,12 @@ namespace ESM Unknown = 8 }; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId, mScript; std::string mName, mModel; float mWeight; // Not sure, might be max total weight allowed? - int mFlags; + int32_t mFlags; InventoryList mInventory; void load(ESMReader& esm, bool& isDeleted); diff --git a/components/esm3/loadglob.hpp b/components/esm3/loadglob.hpp index eed53a5f7b..c78bc83917 100644 --- a/components/esm3/loadglob.hpp +++ b/components/esm3/loadglob.hpp @@ -25,7 +25,7 @@ namespace ESM /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Global"; } - unsigned int mRecordFlags; + uint32_t mRecordFlags; ESM::RefId mId; Variant mValue; diff --git a/components/esm3/loadgmst.hpp b/components/esm3/loadgmst.hpp index 72d30b9ea9..c827be5b6b 100644 --- a/components/esm3/loadgmst.hpp +++ b/components/esm3/loadgmst.hpp @@ -26,7 +26,7 @@ namespace ESM /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "GameSetting"; } - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; Variant mValue; diff --git a/components/esm3/loadlevlist.cpp b/components/esm3/loadlevlist.cpp index 970174ada2..627edbadce 100644 --- a/components/esm3/loadlevlist.cpp +++ b/components/esm3/loadlevlist.cpp @@ -30,7 +30,7 @@ namespace ESM break; case fourCC("INDX"): { - int length = 0; + uint32_t length = 0; esm.getHT(length); mList.resize(length); @@ -87,12 +87,12 @@ namespace ESM esm.writeHNT("DATA", mFlags); esm.writeHNT("NNAM", mChanceNone); - esm.writeHNT("INDX", mList.size()); + esm.writeHNT("INDX", mList.size()); - for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) + for (const auto& item : mList) { - esm.writeHNCRefId(recName, it->mId); - esm.writeHNT("INTV", it->mLevel); + esm.writeHNCRefId(recName, item.mId); + esm.writeHNT("INTV", item.mLevel); } } diff --git a/components/esm3/loadlevlist.hpp b/components/esm3/loadlevlist.hpp index 809c89102e..536b865d90 100644 --- a/components/esm3/loadlevlist.hpp +++ b/components/esm3/loadlevlist.hpp @@ -24,15 +24,15 @@ namespace ESM struct LevelledListBase { - int mFlags; + int32_t mFlags; unsigned char mChanceNone; // Chance that none are selected (0-100) - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; struct LevelItem { RefId mId; - short mLevel; + uint16_t mLevel; }; std::vector mList; diff --git a/components/esm3/loadltex.hpp b/components/esm3/loadltex.hpp index e6dc2de73e..fb95e8b9ed 100644 --- a/components/esm3/loadltex.hpp +++ b/components/esm3/loadltex.hpp @@ -29,7 +29,7 @@ namespace ESM // mId is merely a user friendly name for the texture in the editor. std::string mTexture; RefId mId; - int mIndex; + int32_t mIndex; void load(ESMReader& esm, bool& isDeleted); void save(ESMWriter& esm, bool isDeleted = false) const; diff --git a/components/esm3/loadmgef.cpp b/components/esm3/loadmgef.cpp index 7d3879a341..686afbc34a 100644 --- a/components/esm3/loadmgef.cpp +++ b/components/esm3/loadmgef.cpp @@ -36,7 +36,7 @@ namespace ESM esm.getSubNameIs("MEDT"); esm.getSubHeader(); - int school; + int32_t school; esm.getT(school); mData.mSchool = MagicSchool::indexToSkillRefId(school); esm.getT(mData.mBaseCost); diff --git a/components/esm3/loadmgef.hpp b/components/esm3/loadmgef.hpp index 9d68bad609..25ec7d0655 100644 --- a/components/esm3/loadmgef.hpp +++ b/components/esm3/loadmgef.hpp @@ -25,7 +25,7 @@ namespace ESM /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "MagicEffect"; } - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; enum Flags @@ -74,9 +74,9 @@ namespace ESM { RefId mSchool; // Skill id float mBaseCost; - int mFlags; + int32_t mFlags; // Glow color for enchanted items with this effect - int mRed, mGreen, mBlue; + int32_t mRed, mGreen, mBlue; float mUnknown1; // Called "Size X" in CS float mSpeed; // Speed of fired projectile @@ -107,7 +107,7 @@ namespace ESM // there. They can be redefined in mods by setting the name in GMST // sEffectSummonCreature04/05 creature id in // sMagicCreature04ID/05ID. - int mIndex; + int32_t mIndex; void load(ESMReader& esm, bool& isDeleted); void save(ESMWriter& esm, bool isDeleted = false) const; diff --git a/components/esm3/loadnpc.cpp b/components/esm3/loadnpc.cpp index 24a79d2e4c..d844f7d2bc 100644 --- a/components/esm3/loadnpc.cpp +++ b/components/esm3/loadnpc.cpp @@ -82,7 +82,7 @@ namespace ESM break; case fourCC("FLAG"): hasFlags = true; - int flags; + int32_t flags; esm.getHT(flags); mFlags = flags & 0xFF; mBloodType = ((flags >> 8) & 0xFF) >> 2; diff --git a/components/esm3/loadnpc.hpp b/components/esm3/loadnpc.hpp index f0d726434b..af8c2a8574 100644 --- a/components/esm3/loadnpc.hpp +++ b/components/esm3/loadnpc.hpp @@ -79,28 +79,28 @@ namespace ESM struct NPDTstruct52 { - short mLevel; + int16_t mLevel; unsigned char mStrength, mIntelligence, mWillpower, mAgility, mSpeed, mEndurance, mPersonality, mLuck; // mSkill can grow up to 200, it must be unsigned std::array mSkills; char mUnknown1; - unsigned short mHealth, mMana, mFatigue; + uint16_t mHealth, mMana, mFatigue; unsigned char mDisposition, mReputation, mRank; char mUnknown2; - int mGold; + int32_t mGold; }; // 52 bytes // Structure for autocalculated characters. // This is only used for load and save operations. struct NPDTstruct12 { - short mLevel; + int16_t mLevel; // see above unsigned char mDisposition, mReputation, mRank; char mUnknown1, mUnknown2, mUnknown3; - int mGold; + int32_t mGold; }; // 12 bytes #pragma pack(pop) @@ -111,7 +111,7 @@ namespace ESM int getFactionRank() const; /// wrapper for mNpdt*, -1 = no rank - int mBloodType; + int32_t mBloodType; unsigned char mFlags; InventoryList mInventory; @@ -125,7 +125,7 @@ namespace ESM AIPackageList mAiPackage; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId, mRace, mClass, mFaction, mScript; std::string mModel, mName; diff --git a/components/esm3/loadrace.hpp b/components/esm3/loadrace.hpp index 8dd60bdef1..8cb9d76118 100644 --- a/components/esm3/loadrace.hpp +++ b/components/esm3/loadrace.hpp @@ -27,13 +27,13 @@ namespace ESM struct SkillBonus { - int mSkill; // SkillEnum - int mBonus; + int32_t mSkill; // SkillEnum + int32_t mBonus; }; struct MaleFemale { - int mMale, mFemale; + int32_t mMale, mFemale; int getValue(bool male) const; }; @@ -63,13 +63,13 @@ namespace ESM // as 'height' times 128. This has not been tested yet. MaleFemaleF mHeight, mWeight; - int mFlags; // 0x1 - playable, 0x2 - beast race + int32_t mFlags; // 0x1 - playable, 0x2 - beast race }; // Size = 140 bytes RADTstruct mData; - unsigned int mRecordFlags; + uint32_t mRecordFlags; std::string mName, mDescription; RefId mId; SpellList mPowers; diff --git a/components/esm3/loadsndg.hpp b/components/esm3/loadsndg.hpp index fff4b98439..3337220d9d 100644 --- a/components/esm3/loadsndg.hpp +++ b/components/esm3/loadsndg.hpp @@ -36,9 +36,9 @@ namespace ESM }; // Type - int mType; + int32_t mType; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId, mCreature, mSound; void load(ESMReader& esm, bool& isDeleted); diff --git a/components/esm3/loadsscr.hpp b/components/esm3/loadsscr.hpp index 6c9163e4e6..34349d8ea4 100644 --- a/components/esm3/loadsscr.hpp +++ b/components/esm3/loadsscr.hpp @@ -28,7 +28,7 @@ namespace ESM static std::string_view getRecordType() { return "StartScript"; } std::string mData; - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; // Load a record and add it to the list diff --git a/components/esm3/loadstat.hpp b/components/esm3/loadstat.hpp index abe589563b..4c0341f4ea 100644 --- a/components/esm3/loadstat.hpp +++ b/components/esm3/loadstat.hpp @@ -31,7 +31,7 @@ namespace ESM /// Return a string descriptor for this record type. Currently used for debugging / error logs only. static std::string_view getRecordType() { return "Static"; } - unsigned int mRecordFlags; + uint32_t mRecordFlags; RefId mId; std::string mModel; diff --git a/components/esm3/loadtes3.hpp b/components/esm3/loadtes3.hpp index 7927b35cee..8b14d41645 100644 --- a/components/esm3/loadtes3.hpp +++ b/components/esm3/loadtes3.hpp @@ -20,11 +20,11 @@ namespace ESM versions are 1.2 and 1.3. These correspond to: 1.2 = 0x3f99999a and 1.3 = 0x3fa66666 */ - unsigned int version; - int type; // 0=esp, 1=esm, 32=ess (unused) + uint32_t version; + int32_t type; // 0=esp, 1=esm, 32=ess (unused) std::string author; // Author's name std::string desc; // File description - int records; // Number of records + int32_t records; // Number of records }; struct GMDT diff --git a/components/esm3/magiceffects.cpp b/components/esm3/magiceffects.cpp index 346cc2c568..a8a759949b 100644 --- a/components/esm3/magiceffects.cpp +++ b/components/esm3/magiceffects.cpp @@ -20,8 +20,8 @@ namespace ESM { while (esm.isNextSub("EFID")) { - int id; - std::pair params; + int32_t id; + std::pair params; esm.getHT(id); esm.getHNT(params.first, "BASE"); if (esm.getFormatVersion() <= MaxClearModifiersFormatVersion) diff --git a/components/esm3/magiceffects.hpp b/components/esm3/magiceffects.hpp index 3f141355c8..74a6e34743 100644 --- a/components/esm3/magiceffects.hpp +++ b/components/esm3/magiceffects.hpp @@ -16,7 +16,7 @@ namespace ESM struct MagicEffects { // - std::map> mEffects; + std::map> mEffects; void load(ESMReader& esm); void save(ESMWriter& esm) const; @@ -24,16 +24,16 @@ namespace ESM struct SummonKey { - SummonKey(int effectId, const ESM::RefId& sourceId, int index) + SummonKey(int32_t effectId, const ESM::RefId& sourceId, int32_t index) : mEffectId(effectId) , mSourceId(sourceId) , mEffectIndex(index) { } - int mEffectId; + int32_t mEffectId; ESM::RefId mSourceId; - int mEffectIndex; + int32_t mEffectIndex; }; inline auto makeTupleRef(const SummonKey& value) noexcept diff --git a/components/esm3/npcstats.cpp b/components/esm3/npcstats.cpp index c34205f6c2..a21ba807e4 100644 --- a/components/esm3/npcstats.cpp +++ b/components/esm3/npcstats.cpp @@ -21,7 +21,7 @@ namespace ESM Faction faction; - int expelled = 0; + int32_t expelled = 0; esm.getHNOT(expelled, "FAEX"); if (expelled) @@ -75,7 +75,7 @@ namespace ESM esm.getHNOT(hasWerewolfAttributes, "HWAT"); if (hasWerewolfAttributes) { - StatState dummy; + StatState dummy; for (int i = 0; i < ESM::Attribute::Length; ++i) dummy.load(esm, intFallback); mWerewolfDeprecatedData = true; @@ -130,21 +130,21 @@ namespace ESM void NpcStats::save(ESMWriter& esm) const { - for (auto iter(mFactions.begin()); iter != mFactions.end(); ++iter) + for (const auto& [id, faction] : mFactions) { - esm.writeHNRefId("FACT", iter->first); + esm.writeHNRefId("FACT", id); - if (iter->second.mExpelled) + if (faction.mExpelled) { - int expelled = 1; + int32_t expelled = 1; esm.writeHNT("FAEX", expelled); } - if (iter->second.mRank >= 0) - esm.writeHNT("FARA", iter->second.mRank); + if (faction.mRank >= 0) + esm.writeHNT("FARA", faction.mRank); - if (iter->second.mReputation) - esm.writeHNT("FARE", iter->second.mReputation); + if (faction.mReputation) + esm.writeHNT("FARE", faction.mReputation); } if (mDisposition) @@ -169,7 +169,7 @@ namespace ESM esm.writeHNT("LPRO", mLevelProgress); bool saveSkillIncreases = false; - for (int increase : mSkillIncrease) + for (int32_t increase : mSkillIncrease) { if (increase != 0) { @@ -183,8 +183,8 @@ namespace ESM if (mSpecIncreases[0] != 0 || mSpecIncreases[1] != 0 || mSpecIncreases[2] != 0) esm.writeHNT("SPEC", mSpecIncreases); - for (auto iter(mUsedIds.begin()); iter != mUsedIds.end(); ++iter) - esm.writeHNRefId("USED", *iter); + for (const RefId& id : mUsedIds) + esm.writeHNRefId("USED", id); if (mTimeToStartDrowning) esm.writeHNT("DRTI", mTimeToStartDrowning); diff --git a/components/esm3/npcstats.hpp b/components/esm3/npcstats.hpp index e80ec04c25..ccb58a12ad 100644 --- a/components/esm3/npcstats.hpp +++ b/components/esm3/npcstats.hpp @@ -23,8 +23,8 @@ namespace ESM struct Faction { bool mExpelled; - int mRank; - int mReputation; + int32_t mRank; + int32_t mReputation; Faction(); }; @@ -33,18 +33,18 @@ namespace ESM bool mWerewolfDeprecatedData; - std::map mFactions; // lower case IDs - int mDisposition; + std::map mFactions; + int32_t mDisposition; std::array, ESM::Skill::Length> mSkills; - int mBounty; - int mReputation; - int mWerewolfKills; - int mLevelProgress; - std::array mSkillIncrease; - std::array mSpecIncreases; - std::vector mUsedIds; // lower case IDs + int32_t mBounty; + int32_t mReputation; + int32_t mWerewolfKills; + int32_t mLevelProgress; + std::array mSkillIncrease; + std::array mSpecIncreases; + std::vector mUsedIds; float mTimeToStartDrowning; - int mCrimeId; + int32_t mCrimeId; /// Initialize to default state void blank(); diff --git a/components/esm3/objectstate.cpp b/components/esm3/objectstate.cpp index a56988843a..a7fe41d66c 100644 --- a/components/esm3/objectstate.cpp +++ b/components/esm3/objectstate.cpp @@ -49,7 +49,7 @@ namespace ESM esm.getHNOT(mFlags, "FLAG"); // obsolete - int unused; + int32_t unused; esm.getHNOT(unused, "LTIM"); mAnimationState.load(esm); @@ -179,6 +179,6 @@ namespace ESM throw std::logic_error(error.str()); } - ObjectState::~ObjectState() {} + ObjectState::~ObjectState() = default; } diff --git a/components/esm3/objectstate.hpp b/components/esm3/objectstate.hpp index 67f4d27706..4c09d16d18 100644 --- a/components/esm3/objectstate.hpp +++ b/components/esm3/objectstate.hpp @@ -32,9 +32,9 @@ namespace ESM Locals mLocals; LuaScripts mLuaScripts; unsigned char mEnabled; - int mCount; + int32_t mCount; Position mPosition; - unsigned int mFlags; + uint32_t mFlags; // Is there any class-specific state following the ObjectState bool mHasCustomState; diff --git a/components/esm3/player.hpp b/components/esm3/player.hpp index f691f22e86..7f9309765c 100644 --- a/components/esm3/player.hpp +++ b/components/esm3/player.hpp @@ -27,8 +27,8 @@ namespace ESM RefId mMarkedCell; ESM::RefId mBirthsign; - int mCurrentCrimeId; - int mPaidCrimeId; + int32_t mCurrentCrimeId; + int32_t mPaidCrimeId; float mSaveAttributes[Attribute::Length]; float mSaveSkills[Skill::Length]; diff --git a/components/esm3/projectilestate.hpp b/components/esm3/projectilestate.hpp index 7a7651f364..cab550b114 100644 --- a/components/esm3/projectilestate.hpp +++ b/components/esm3/projectilestate.hpp @@ -23,7 +23,7 @@ namespace ESM Vector3 mPosition; Quaternion mOrientation; - int mActorId; + int32_t mActorId; void load(ESMReader& esm); void save(ESMWriter& esm) const; diff --git a/components/esm3/queststate.hpp b/components/esm3/queststate.hpp index 5858714df0..6d9fd6c4fb 100644 --- a/components/esm3/queststate.hpp +++ b/components/esm3/queststate.hpp @@ -14,7 +14,7 @@ namespace ESM struct QuestState { ESM::RefId mTopic; // lower case id - int mState; + int32_t mState; unsigned char mFinished; void load(ESMReader& esm); diff --git a/components/esm3/savedgame.hpp b/components/esm3/savedgame.hpp index 2048244ac2..4632e98927 100644 --- a/components/esm3/savedgame.hpp +++ b/components/esm3/savedgame.hpp @@ -20,7 +20,7 @@ namespace ESM std::vector mContentFiles; std::string mPlayerName; - int mPlayerLevel; + int32_t mPlayerLevel; // ID of class ESM::RefId mPlayerClassId; @@ -34,7 +34,7 @@ namespace ESM std::string mDescription; std::vector mScreenshot; // raw jpg-encoded data - int mCurrentDay = 0; + int32_t mCurrentDay = 0; float mCurrentHealth = 0; float mMaximumHealth = 0; diff --git a/components/esm3/statstate.cpp b/components/esm3/statstate.cpp index b5ddc54985..7477d83e2d 100644 --- a/components/esm3/statstate.cpp +++ b/components/esm3/statstate.cpp @@ -21,19 +21,19 @@ namespace ESM // We changed stats values from integers to floats; ensure backwards compatibility if (intFallback) { - int base = 0; + int32_t base = 0; esm.getHNT(base, "STBA"); mBase = static_cast(base); - int mod = 0; + int32_t mod = 0; esm.getHNOT(mod, "STMO"); mMod = static_cast(mod); - int current = 0; + int32_t current = 0; esm.getHNOT(current, "STCU"); mCurrent = static_cast(current); - int oldDamage = 0; + int32_t oldDamage = 0; esm.getHNOT(oldDamage, "STDA"); mDamage = static_cast(oldDamage); } diff --git a/components/esm3/variant.cpp b/components/esm3/variant.cpp index 3d5daa2cb3..48621818eb 100644 --- a/components/esm3/variant.cpp +++ b/components/esm3/variant.cpp @@ -17,7 +17,7 @@ namespace ESM template struct GetValue { - constexpr T operator()(int value) const { return static_cast(value); } + constexpr T operator()(int32_t value) const { return static_cast(value); } constexpr T operator()(float value) const { return static_cast(value); } @@ -41,7 +41,7 @@ namespace ESM { } - void operator()(int& value) const { value = static_cast(mValue); } + void operator()(int32_t& value) const { value = static_cast(mValue); } void operator()(float& value) const { value = static_cast(mValue); } @@ -58,9 +58,9 @@ namespace ESM return std::get(mData); } - int Variant::getInteger() const + int32_t Variant::getInteger() const { - return std::visit(GetValue{}, mData); + return std::visit(GetValue{}, mData); } float Variant::getFloat() const @@ -194,17 +194,17 @@ namespace ESM case VT_Short: - stream << "variant short: " << std::get(mData); + stream << "variant short: " << std::get(mData); break; case VT_Int: - stream << "variant int: " << std::get(mData); + stream << "variant int: " << std::get(mData); break; case VT_Long: - stream << "variant long: " << std::get(mData); + stream << "variant long: " << std::get(mData); break; case VT_Float: @@ -259,7 +259,7 @@ namespace ESM std::get(mData) = std::move(value); } - void Variant::setInteger(int value) + void Variant::setInteger(int32_t value) { std::visit(SetValue(value), mData); } diff --git a/components/esm3/variant.hpp b/components/esm3/variant.hpp index d00ccb2746..ed72b1dc05 100644 --- a/components/esm3/variant.hpp +++ b/components/esm3/variant.hpp @@ -25,7 +25,7 @@ namespace ESM class Variant { VarType mType; - std::variant mData; + std::variant mData; public: enum Format @@ -54,7 +54,7 @@ namespace ESM { } - explicit Variant(int value) + explicit Variant(int32_t value) : mType(VT_Long) , mData(value) { @@ -71,7 +71,7 @@ namespace ESM const std::string& getString() const; ///< Will throw an exception, if value can not be represented as a string. - int getInteger() const; + int32_t getInteger() const; ///< Will throw an exception, if value can not be represented as an integer (implicit /// casting of float values is permitted). @@ -93,7 +93,7 @@ namespace ESM void setString(std::string&& value); ///< Will throw an exception, if type is not compatible with string. - void setInteger(int value); + void setInteger(int32_t value); ///< Will throw an exception, if type is not compatible with integer. void setFloat(float value); diff --git a/components/esm3/variantimp.cpp b/components/esm3/variantimp.cpp index 410f4ade8d..31248556ec 100644 --- a/components/esm3/variantimp.cpp +++ b/components/esm3/variantimp.cpp @@ -46,7 +46,7 @@ namespace ESM esm.writeHNString("STRV", in); } - void readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, int& out) + void readESMVariantValue(ESMReader& esm, Variant::Format format, VarType type, int32_t& out) { if (type != VT_Short && type != VT_Long && type != VT_Int) throw std::logic_error("not an integer type"); @@ -60,9 +60,9 @@ namespace ESM if (std::isnan(value)) out = 0; else - out = static_cast(value); + out = static_cast(value); else if (type == VT_Long) - out = static_cast(value); + out = static_cast(value); else esm.fail("unsupported global variable integer type"); } @@ -82,7 +82,7 @@ namespace ESM { if (type == VT_Short) { - short value; + int16_t value; esm.getHT(value); out = value; } @@ -95,7 +95,7 @@ namespace ESM } } - void writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, int in) + void writeESMVariantValue(ESMWriter& esm, Variant::Format format, VarType type, int32_t in) { if (type != VT_Short && type != VT_Long && type != VT_Int) throw std::logic_error("not an integer type"); @@ -126,7 +126,7 @@ namespace ESM else if (format == Variant::Format_Local) { if (type == VT_Short) - esm.writeHNT("STTV", static_cast(in)); + esm.writeHNT("STTV", static_cast(in)); else if (type == VT_Int) esm.writeHNT("INTV", in); else diff --git a/components/esm3/variantimp.hpp b/components/esm3/variantimp.hpp index 90f45a420f..365cff988a 100644 --- a/components/esm3/variantimp.hpp +++ b/components/esm3/variantimp.hpp @@ -12,13 +12,13 @@ namespace ESM void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, float& value); - void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, int& value); + void readESMVariantValue(ESMReader& reader, Variant::Format format, VarType type, int32_t& value); void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, const std::string& value); void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, float value); - void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, int value); + void writeESMVariantValue(ESMWriter& writer, Variant::Format format, VarType type, int32_t value); struct ReadESMVariantValue { From 212f6bae569f2fed240ec259240e3ca17fa095e6 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Tue, 24 Oct 2023 20:59:20 +0200 Subject: [PATCH 04/71] Use correct skip and fix MSVC --- components/esm3/inventorystate.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/components/esm3/inventorystate.cpp b/components/esm3/inventorystate.cpp index c3af101edc..84a52ff518 100644 --- a/components/esm3/inventorystate.cpp +++ b/components/esm3/inventorystate.cpp @@ -9,7 +9,7 @@ namespace ESM { namespace { - constexpr uint32_t sInvalidSlot = -1; + constexpr uint32_t sInvalidSlot = static_cast(-1); } void InventoryState::load(ESMReader& esm) @@ -18,7 +18,7 @@ namespace ESM uint32_t index = 0; while (esm.isNextSub("IOBJ")) { - esm.skip(4); + esm.skipHT(); ObjectState state; From f9888230afe7f1d5c6c6cb95a8aa8c4d53d84aff Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 26 Oct 2023 20:46:34 +0200 Subject: [PATCH 05/71] Fix Lua UI atlasing --- components/lua_ui/image.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/components/lua_ui/image.cpp b/components/lua_ui/image.cpp index 0454dd19b4..ffe93a8d2d 100644 --- a/components/lua_ui/image.cpp +++ b/components/lua_ui/image.cpp @@ -8,9 +8,7 @@ namespace LuaUi { void LuaTileRect::_setAlign(const MyGUI::IntSize& _oldsize) { - mCurrentCoord.set(0, 0, mCroppedParent->getWidth(), mCroppedParent->getHeight()); - mAlign = MyGUI::Align::Stretch; - MyGUI::TileRect::_setAlign(_oldsize); + mCoord.set(0, 0, mCroppedParent->getWidth(), mCroppedParent->getHeight()); mTileSize = mSetTileSize; // zero tilesize stands for not tiling @@ -25,6 +23,8 @@ namespace LuaUi mTileSize.width = 1e7; if (mTileSize.height <= 0) mTileSize.height = 1e7; + + MyGUI::TileRect::_updateView(); } void LuaImage::initialize() @@ -55,13 +55,13 @@ namespace LuaUi if (texture != nullptr) textureSize = MyGUI::IntSize(texture->getWidth(), texture->getHeight()); - mTileRect->updateSize(MyGUI::IntSize(tileH ? textureSize.width : 0, tileV ? textureSize.height : 0)); - setImageTile(textureSize); - if (atlasCoord.width == 0) atlasCoord.width = textureSize.width; if (atlasCoord.height == 0) atlasCoord.height = textureSize.height; + + mTileRect->updateSize(MyGUI::IntSize(tileH ? atlasCoord.width : 0, tileV ? atlasCoord.height : 0)); + setImageTile(atlasCoord.size()); setImageCoord(atlasCoord); setColour(propertyValue("color", MyGUI::Colour(1, 1, 1, 1))); From 12abd30e9ff94d1ebd0b6b38d34d5837cdb6663b Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 29 Oct 2023 04:11:31 +0300 Subject: [PATCH 06/71] Make sun specularity behavior more intuitive (bug #6190) Remove sun visibility influence on object specularity Subdue sun visibility influence on water specularity --- CHANGELOG.md | 1 + apps/openmw/mwrender/renderingmanager.cpp | 2 +- apps/openmw/mwworld/weather.cpp | 3 +-- files/shaders/compatibility/water.frag | 11 +++++++---- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e9d31a19e..c5cb8b8ce1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Bug #5977: Fatigueless NPCs' corpse underwater changes animation on game load Bug #6025: Subrecords cannot overlap records Bug #6027: Collisionshape becomes spiderweb-like when the mesh is too complex + Bug #6190: Unintuitive sun specularity time of day dependence Bug #6222: global map cell size can crash openmw if set to too high a value Bug #6313: Followers with high Fight can turn hostile Bug #6427: Enemy health bar disappears before damaging effect ends diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index b7685e0cd8..7e593431d0 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -702,7 +702,7 @@ namespace MWRender { // need to wrap this in a StateUpdater? mSunLight->setDiffuse(diffuse); - mSunLight->setSpecular(specular); + mSunLight->setSpecular(osg::Vec4f(specular.x(), specular.y(), specular.z(), specular.w() * sunVis)); mPostProcessor->getStateUpdater()->setSunColor(diffuse); mPostProcessor->getStateUpdater()->setSunVis(sunVis); diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 0da70bdd48..5d739a9161 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -789,8 +789,7 @@ namespace MWWorld mRendering.configureFog( mResult.mFogDepth, underwaterFog, mResult.mDLFogFactor, mResult.mDLFogOffset / 100.0f, mResult.mFogColor); mRendering.setAmbientColour(mResult.mAmbientColor); - mRendering.setSunColour( - mResult.mSunColor, mResult.mSunColor * mResult.mGlareView * glareFade, mResult.mGlareView * glareFade); + mRendering.setSunColour(mResult.mSunColor, mResult.mSunColor, mResult.mGlareView * glareFade); mRendering.getSkyManager()->setWeather(mResult); diff --git a/files/shaders/compatibility/water.frag b/files/shaders/compatibility/water.frag index 83fb8a53e0..987dab10a6 100644 --- a/files/shaders/compatibility/water.frag +++ b/files/shaders/compatibility/water.frag @@ -43,6 +43,7 @@ const float SCATTER_AMOUNT = 0.3; // amount of sunlight scatter const vec3 SCATTER_COLOUR = vec3(0.0,1.0,0.95); // colour of sunlight scattering const vec3 SUN_EXT = vec3(0.45, 0.55, 0.68); //sunlight extinction +const float SUN_SPEC_FADING_THRESHOLD = 0.15; // visibility at which sun specularity starts to fade const float SPEC_HARDNESS = 256.0; // specular highlights hardness @@ -172,6 +173,8 @@ void main(void) vec3 waterColor = WATER_COLOR * sunFade; vec4 sunSpec = lcalcSpecular(0); + // alpha component is sun visibility; we want to start fading specularity when visibility is low + sunSpec.a = min(1.0, sunSpec.a / SUN_SPEC_FADING_THRESHOLD); // artificial specularity to make rain ripples more noticeable vec3 skyColorEstimate = vec3(max(0.0, mix(-0.3, 1.0, sunFade))); @@ -179,7 +182,7 @@ void main(void) #if REFRACTION // no alpha here, so make sure raindrop ripple specularity gets properly subdued - rainSpecular *= clamp(fresnel*6.0 + specular * sunSpec.w, 0.0, 1.0); + rainSpecular *= clamp(fresnel*6.0 + specular * sunSpec.a, 0.0, 1.0); // refraction vec3 refraction = sampleRefractionMap(screenCoords - screenCoordsOffset).rgb; @@ -200,7 +203,7 @@ void main(void) vec3 scatterColour = mix(SCATTER_COLOUR*vec3(1.0,0.4,0.0), SCATTER_COLOUR, clamp(1.0-exp(-sunHeight*SUN_EXT), 0.0, 1.0)); vec3 lR = reflect(lVec, lNormal); float lightScatter = clamp(dot(lVec,lNormal)*0.7+0.3, 0.0, 1.0) * clamp(dot(lR, vVec)*2.0-1.2, 0.0, 1.0) * SCATTER_AMOUNT * sunFade * clamp(1.0-exp(-sunHeight), 0.0, 1.0); - gl_FragData[0].xyz = mix( mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.xyz + rainSpecular; + gl_FragData[0].xyz = mix(mix(refraction, scatterColour, lightScatter), reflection, fresnel) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; gl_FragData[0].w = 1.0; // wobbly water: hard-fade into refraction texture at extremely low depth, with a wobble based on normal mapping @@ -213,8 +216,8 @@ void main(void) shoreOffset = clamp(mix(shoreOffset, 1.0, clamp(linearDepth / WOBBLY_SHORE_FADE_DISTANCE, 0.0, 1.0)), 0.0, 1.0); gl_FragData[0].xyz = mix(rawRefraction, gl_FragData[0].xyz, shoreOffset); #else - gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.xyz + rainSpecular; - gl_FragData[0].w = clamp(fresnel*6.0 + specular * sunSpec.w, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.w, 0.0, 1.0); + gl_FragData[0].xyz = mix(reflection, waterColor, (1.0-fresnel)*0.5) + specular * sunSpec.rgb * sunSpec.a + rainSpecular; + gl_FragData[0].w = clamp(fresnel*6.0 + specular * sunSpec.a, 0.0, 1.0); //clamp(fresnel*2.0 + specular * gl_LightSource[0].specular.a, 0.0, 1.0); #endif gl_FragData[0] = applyFogAtDist(gl_FragData[0], radialDepth, linearDepth, far); From f4b27a521aacf29297d2f1d408012a1a0cc8ce44 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 30 Oct 2023 02:08:59 +0300 Subject: [PATCH 07/71] Read LTEX::INAM --- components/esm4/loadltex.cpp | 3 +++ components/esm4/loadltex.hpp | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/components/esm4/loadltex.cpp b/components/esm4/loadltex.cpp index 955ac938e3..9b2d12034f 100644 --- a/components/esm4/loadltex.cpp +++ b/components/esm4/loadltex.cpp @@ -76,6 +76,9 @@ void ESM4::LandTexture::load(ESM4::Reader& reader) case ESM4::SUB_MNAM: reader.getFormId(mMaterial); break; // TES5, FO4 + case ESM4::SUB_INAM: + reader.get(mMaterialFlags); + break; // SSE default: throw std::runtime_error("ESM4::LTEX::load - Unknown subrecord " + ESM::printName(subHdr.typeId)); } diff --git a/components/esm4/loadltex.hpp b/components/esm4/loadltex.hpp index bed5887f5c..33c1683ac0 100644 --- a/components/esm4/loadltex.hpp +++ b/components/esm4/loadltex.hpp @@ -63,6 +63,17 @@ namespace ESM4 // ---------------------- + // ------ SSE ----------- + + enum MaterialFlags + { + Flag_IsSnow = 0x1, + }; + + std::uint32_t mMaterialFlags; + + // ---------------------- + void load(ESM4::Reader& reader); // void save(ESM4::Writer& writer) const; From 37617974670bfa66fd4d82844bdd5c14ae9ae489 Mon Sep 17 00:00:00 2001 From: Kindi Date: Tue, 12 Sep 2023 03:03:59 +0800 Subject: [PATCH 08/71] reimplement partial equipping --- apps/openmw/mwgui/inventorywindow.cpp | 36 ++++++++++++++++----------- apps/openmw/mwgui/inventorywindow.hpp | 1 + 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 610e34e3cd..16f135df17 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -556,6 +556,20 @@ namespace MWGui std::unique_ptr action = ptr.getClass().use(ptr, force); action->execute(player); + // Handles partial equipping (final part) + if (mEquippedStackableCount.has_value()) + { + // the count to unequip + int count = ptr.getRefData().getCount() - mDragAndDrop->mDraggedCount - mEquippedStackableCount.value(); + if (count > 0) + { + MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); + invStore.unequipItemQuantity(ptr, count); + updateItemView(); + mEquippedStackableCount.reset(); + } + } + if (isVisible()) { mItemView->update(); @@ -581,27 +595,21 @@ namespace MWGui } // Handles partial equipping - const std::pair, bool> slots = ptr.getClass().getEquipmentSlots(ptr); + mEquippedStackableCount.reset(); + const auto slots = ptr.getClass().getEquipmentSlots(ptr); if (!slots.first.empty() && slots.second) { - int equippedStackableCount = 0; MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); MWWorld::ConstContainerStoreIterator slotIt = invStore.getSlot(slots.first.front()); - // Get the count before useItem() + // Save the currently equipped count before useItem() if (slotIt != invStore.end() && slotIt->getCellRef().getRefId() == ptr.getCellRef().getRefId()) - equippedStackableCount = slotIt->getRefData().getCount(); - - useItem(ptr); - int unequipCount = ptr.getRefData().getCount() - mDragAndDrop->mDraggedCount - equippedStackableCount; - if (unequipCount > 0) - { - invStore.unequipItemQuantity(ptr, unequipCount); - updateItemView(); - } + mEquippedStackableCount = slotIt->getRefData().getCount(); + else + mEquippedStackableCount = 0; } - else - MWBase::Environment::get().getLuaManager()->useItem(ptr, MWMechanics::getPlayer(), false); + + MWBase::Environment::get().getLuaManager()->useItem(ptr, MWMechanics::getPlayer(), false); // If item is ingredient or potion don't stop drag and drop to simplify action of taking more than one 1 // item diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index f3d8e3dcd6..9fc77ceec5 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -74,6 +74,7 @@ namespace MWGui DragAndDrop* mDragAndDrop; int mSelectedItem; + std::optional mEquippedStackableCount; MWWorld::Ptr mPtr; From 6c01ce267294abfd90ed74bcd8973ed4e459e0f9 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Tue, 31 Oct 2023 11:47:42 +0100 Subject: [PATCH 09/71] Use correct template flags for FONV and FO4 NPCs --- apps/openmw/mwclass/esm4npc.cpp | 30 +++++++++++++++++----- components/esm4/loadnpc.cpp | 4 ++- components/esm4/loadnpc.hpp | 45 +++++++++++++++++++-------------- 3 files changed, 53 insertions(+), 26 deletions(-) diff --git a/apps/openmw/mwclass/esm4npc.cpp b/apps/openmw/mwclass/esm4npc.cpp index 78cbd89b50..13e7866537 100644 --- a/apps/openmw/mwclass/esm4npc.cpp +++ b/apps/openmw/mwclass/esm4npc.cpp @@ -34,11 +34,26 @@ namespace MWClass static const ESM4::Npc* chooseTemplate(const std::vector& recs, uint16_t flag) { - // In case of FO3 the function may return nullptr that will lead to "ESM4 NPC traits not found" - // exception and the NPC will not be added to the scene. But in any way it shouldn't cause a crash. for (const auto* rec : recs) - if (rec->mIsTES4 || rec->mIsFONV || !(rec->mBaseConfig.tes5.templateFlags & flag)) + { + if (rec->mIsTES4) return rec; + else if (rec->mIsFONV) + { + // TODO: FO3 should use this branch as well. But it is not clear how to distinguish FO3 from + // TES5. Currently FO3 uses wrong template flags that can lead to "ESM4 NPC traits not found" + // exception the NPC will not be added to the scene. But in any way it shouldn't cause a crash. + if (!(rec->mBaseConfig.fo3.templateFlags & flag)) + return rec; + } + else if (rec->mIsFO4) + { + if (!(rec->mBaseConfig.fo4.templateFlags & flag)) + return rec; + } + else if (!(rec->mBaseConfig.tes5.templateFlags & flag)) + return rec; + } return nullptr; } @@ -75,8 +90,8 @@ namespace MWClass const MWWorld::ESMStore* store = MWBase::Environment::get().getESMStore(); auto npcRecs = withBaseTemplates(ptr.get()->mBase); - data->mTraits = chooseTemplate(npcRecs, ESM4::Npc::TES5_UseTraits); - data->mBaseData = chooseTemplate(npcRecs, ESM4::Npc::TES5_UseBaseData); + data->mTraits = chooseTemplate(npcRecs, ESM4::Npc::Template_UseTraits); + data->mBaseData = chooseTemplate(npcRecs, ESM4::Npc::Template_UseBaseData); if (!data->mTraits) throw std::runtime_error("ESM4 NPC traits not found"); @@ -88,10 +103,13 @@ namespace MWClass data->mIsFemale = data->mTraits->mBaseConfig.tes4.flags & ESM4::Npc::TES4_Female; else if (data->mTraits->mIsFONV) data->mIsFemale = data->mTraits->mBaseConfig.fo3.flags & ESM4::Npc::FO3_Female; + else if (data->mTraits->mIsFO4) + data->mIsFemale + = data->mTraits->mBaseConfig.fo4.flags & ESM4::Npc::TES5_Female; // FO4 flags are same as TES5 else data->mIsFemale = data->mTraits->mBaseConfig.tes5.flags & ESM4::Npc::TES5_Female; - if (auto inv = chooseTemplate(npcRecs, ESM4::Npc::TES5_UseInventory)) + if (auto inv = chooseTemplate(npcRecs, ESM4::Npc::Template_UseInventory)) { for (const ESM4::InventoryItem& item : inv->mInventory) { diff --git a/components/esm4/loadnpc.cpp b/components/esm4/loadnpc.cpp index 251af13630..885263d67b 100644 --- a/components/esm4/loadnpc.cpp +++ b/components/esm4/loadnpc.cpp @@ -116,9 +116,11 @@ void ESM4::Npc::load(ESM4::Reader& reader) { switch (subHdr.dataSize) { + case 20: // FO4 + mIsFO4 = true; + [[fallthrough]]; case 16: // TES4 case 24: // FO3/FNV, TES5 - case 20: // FO4 reader.get(&mBaseConfig, subHdr.dataSize); break; default: diff --git a/components/esm4/loadnpc.hpp b/components/esm4/loadnpc.hpp index 6f4b3c7e24..04e56b7bd9 100644 --- a/components/esm4/loadnpc.hpp +++ b/components/esm4/loadnpc.hpp @@ -78,6 +78,7 @@ namespace ESM4 FO3_NoRotateHead = 0x40000000 }; + // In FO4 flags seem to be the same. enum ACBS_TES5 { TES5_Female = 0x00000001, @@ -101,27 +102,32 @@ namespace ESM4 TES5_Invulnerable = 0x80000000 }; + // All FO3+ games. enum Template_Flags { - TES5_UseTraits = 0x0001, // Destructible Object; Traits tab, including race, gender, height, weight, - // voice type, death item; Sounds tab; Animation tab; Character Gen tabs - TES5_UseStats = 0x0002, // Stats tab, including level, autocalc, skills, health/magicka/stamina, - // speed, bleedout, class - TES5_UseFactions = 0x0004, // both factions and assigned crime faction - TES5_UseSpellList = 0x0008, // both spells and perks - TES5_UseAIData = 0x0010, // AI Data tab, including aggression/confidence/morality, combat style and - // gift filter - TES5_UseAIPackage = 0x0020, // only the basic Packages listed on the AI Packages tab; - // rest of tab controlled by Def Pack List - TES5_UseBaseData = 0x0080, // including name and short name, and flags for Essential, Protected, - // Respawn, Summonable, Simple Actor, and Doesn't affect stealth meter - TES5_UseInventory = 0x0100, // Inventory tab, including all outfits and geared-up item - // -- but not death item - TES5_UseScript = 0x0200, - TES5_UseDefined = 0x0400, // Def Pack List (the dropdown-selected package lists on the AI Packages tab) - TES5_UseAtkData = 0x0800, // Attack Data tab, including override from behavior graph race, - // events, and data) - TES5_UseKeywords = 0x1000 + Template_UseTraits = 0x0001, // Destructible Object; Traits tab, including race, gender, height, weight, + // voice type, death item; Sounds tab; Animation tab; Character Gen tabs + Template_UseStats = 0x0002, // Stats tab, including level, autocalc, skills, health/magicka/stamina, + // speed, bleedout, class + Template_UseFactions = 0x0004, // both factions and assigned crime faction + Template_UseSpellList = 0x0008, // both spells and perks + Template_UseAIData = 0x0010, // AI Data tab, including aggression/confidence/morality, combat style and + // gift filter + Template_UseAIPackage = 0x0020, // only the basic Packages listed on the AI Packages tab; + // rest of tab controlled by Def Pack List + Template_UseModel = 0x0040, // FO3, FONV; probably not used in TES5+ + Template_UseBaseData = 0x0080, // including name and short name, and flags for Essential, Protected, + // Respawn, Summonable, Simple Actor, and Doesn't affect stealth meter + Template_UseInventory = 0x0100, // Inventory tab, including all outfits and geared-up item, + // but not death item + Template_UseScript = 0x0200, + + // The following flags were added in TES5+: + + Template_UseDefined = 0x0400, // Def Pack List (the dropdown-selected package lists on the AI Packages tab) + Template_UseAtkData = 0x0800, // Attack Data tab, including override from behavior graph race, + // events, and data) + Template_UseKeywords = 0x1000 }; #pragma pack(push, 1) @@ -172,6 +178,7 @@ namespace ESM4 bool mIsTES4; bool mIsFONV; + bool mIsFO4 = false; std::string mEditorId; std::string mFullName; From 4c6e081da37b0a266668a3d2f89766a04a0201f7 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 1 Nov 2023 10:30:19 +0100 Subject: [PATCH 10/71] Skip zero links to ArmorAddons --- apps/openmw/mwrender/esm4npcanimation.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/openmw/mwrender/esm4npcanimation.cpp b/apps/openmw/mwrender/esm4npcanimation.cpp index 1f06e68bc2..3ea8f829ce 100644 --- a/apps/openmw/mwrender/esm4npcanimation.cpp +++ b/apps/openmw/mwrender/esm4npcanimation.cpp @@ -124,6 +124,8 @@ namespace MWRender auto findArmorAddons = [&](const ESM4::Armor* armor) { for (ESM::FormId armaId : armor->mAddOns) { + if (armaId.isZeroOrUnset()) + continue; const ESM4::ArmorAddon* arma = store->get().search(armaId); if (!arma) { From d3ccc246c54c806801e981404fed66a9c497e982 Mon Sep 17 00:00:00 2001 From: jvoisin Date: Wed, 1 Nov 2023 12:03:49 +0000 Subject: [PATCH 11/71] Apply 1 suggestion(s) to 1 file(s) --- apps/openmw/mwclass/esm4npc.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwclass/esm4npc.cpp b/apps/openmw/mwclass/esm4npc.cpp index 13e7866537..638144eb66 100644 --- a/apps/openmw/mwclass/esm4npc.cpp +++ b/apps/openmw/mwclass/esm4npc.cpp @@ -105,7 +105,7 @@ namespace MWClass data->mIsFemale = data->mTraits->mBaseConfig.fo3.flags & ESM4::Npc::FO3_Female; else if (data->mTraits->mIsFO4) data->mIsFemale - = data->mTraits->mBaseConfig.fo4.flags & ESM4::Npc::TES5_Female; // FO4 flags are same as TES5 + = data->mTraits->mBaseConfig.fo4.flags & ESM4::Npc::TES5_Female; // FO4 flags are the same as TES5 else data->mIsFemale = data->mTraits->mBaseConfig.tes5.flags & ESM4::Npc::TES5_Female; From 93b723a0662b55004d3f2c7738a78a833f719d16 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 1 Nov 2023 19:47:08 +0100 Subject: [PATCH 12/71] Apply legs yaw to accumulated movement. --- apps/openmw/mwrender/animation.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 0741e24a69..bac9dbb56c 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -1235,9 +1235,11 @@ namespace MWRender mRootController->setEnabled(enable); if (enable) { - mRootController->setRotate(osg::Quat(mLegsYawRadians, osg::Vec3f(0, 0, 1)) - * osg::Quat(mBodyPitchRadians, osg::Vec3f(1, 0, 0))); + osg::Quat legYaw = osg::Quat(mLegsYawRadians, osg::Vec3f(0, 0, 1)); + mRootController->setRotate(legYaw * osg::Quat(mBodyPitchRadians, osg::Vec3f(1, 0, 0))); yawOffset = mLegsYawRadians; + // When yawing the root, also update the accumulated movement. + movement = legYaw * movement; } } if (mSpineController) From f41de6b02da923f24656b6510d4041c39f144688 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 1 Nov 2023 19:48:16 +0100 Subject: [PATCH 13/71] Use accumulated movement whenever possible. Apply diagonal movement by rotating accumulated movement and sliding based on that, rather than ignoring accumulated movement. --- apps/openmw/mwmechanics/character.cpp | 98 +++++++++++++++---------- components/settings/categories/game.hpp | 1 + files/settings-default.cfg | 5 ++ 3 files changed, 67 insertions(+), 37 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 068d91ab42..1fca6c6b71 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2387,48 +2387,72 @@ namespace MWMechanics } osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); - if (duration > 0.0f) - moved /= duration; - else - moved = osg::Vec3f(0.f, 0.f, 0.f); - moved.x() *= scale; - moved.y() *= scale; - - // Ensure we're moving in generally the right direction... - if (speed > 0.f && moved != osg::Vec3f()) + if (mPtr.getClass().isActor() && isMovementAnimationControlled() && !isScriptedAnimPlaying()) { - float l = moved.length(); - if (std::abs(movement.x() - moved.x()) > std::abs(moved.x()) / 2 - || std::abs(movement.y() - moved.y()) > std::abs(moved.y()) / 2 - || std::abs(movement.z() - moved.z()) > std::abs(moved.z()) / 2) - { - moved = movement; - // For some creatures getSpeed doesn't work, so we adjust speed to the animation. - // TODO: Fix Creature::getSpeed. - float newLength = moved.length(); - if (newLength > 0 && !cls.isNpc()) - moved *= (l / newLength); - } - } + if (duration > 0.0f) + moved /= duration; + else + moved = osg::Vec3f(0.f, 0.f, 0.f); - if (mFloatToSurface && cls.isActor()) - { - if (cls.getCreatureStats(mPtr).isDead() - || (!godmode - && cls.getCreatureStats(mPtr) - .getMagicEffects() - .getOrDefault(ESM::MagicEffect::Paralyze) - .getModifier() - > 0)) - { - moved.z() = 1.0; - } - } + moved.x() *= scale; + moved.y() *= scale; - // Update movement - if (isMovementAnimationControlled() && mPtr.getClass().isActor() && !isScriptedAnimPlaying()) + if (speed > 0.f && moved != osg::Vec3f()) + { + // Ensure we're moving in generally the right direction + // This is necessary when the "turn to movement direction" feature is off, as animations + // will not be rotated to match diagonal movement. In this case we have to slide the + // character diagonally. + + // First decide the general direction expected from the current animation + float animMovementAngle = 0; + if (!Settings::game().mTurnToMovementDirection || isFirstPersonPlayer) + { + if (cls.getMovementSettings(mPtr).mIsStrafing) + animMovementAngle = movement.x() > 0 ? -osg::PI_2f : osg::PI_2f; + else + animMovementAngle = movement.y() >= 0 ? 0 : -osg::PIf; + } + else + { + animMovementAngle = mAnimation->getLegsYawRadians(); + if (movement.y() < 0) + animMovementAngle -= osg::PIf; + } + + const float epsilon = 0.001f; + float targetMovementAngle = std::atan2(-movement.x(), movement.y()); + float diff = targetMovementAngle - animMovementAngle; + if (std::fabsf(diff) > epsilon) + { + moved = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * moved; + } + + if (isPlayer && Settings::game().mPlayerMovementIgnoresAnimation) + { + moved = movement; + } + } + + if (mFloatToSurface) + { + if (cls.getCreatureStats(mPtr).isDead() + || (!godmode + && cls.getCreatureStats(mPtr) + .getMagicEffects() + .getOrDefault(ESM::MagicEffect::Paralyze) + .getModifier() + > 0)) + { + moved.z() = 1.0; + } + } + + // Update movement + && !isScriptedAnimPlaying() world->queueMovement(mPtr, moved); + } mSkipAnim = false; diff --git a/components/settings/categories/game.hpp b/components/settings/categories/game.hpp index ded367a54c..375d8ef819 100644 --- a/components/settings/categories/game.hpp +++ b/components/settings/categories/game.hpp @@ -74,6 +74,7 @@ namespace Settings "unarmed creature attacks damage armor" }; SettingValue mActorCollisionShapeType{ mIndex, "Game", "actor collision shape type" }; + SettingValue mPlayerMovementIgnoresAnimation{ mIndex, "Game", "player movement ignores animation" }; }; } diff --git a/files/settings-default.cfg b/files/settings-default.cfg index bbc6b4d1c8..da1c97519a 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -365,6 +365,11 @@ unarmed creature attacks damage armor = false # 2 = Cylinder actor collision shape type = 0 +# When false the player character will base movement on animations. This will sway the camera +# while moving in third person like in vanilla, and reproduce movement bugs caused by glitchy +# vanilla animations. +player movement ignores animation = false + [General] # Anisotropy reduces distortion in textures at low angles (e.g. 0 to 16). From c59fb6c91c9fb5bee9b42fd87d29de95965af605 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 1 Nov 2023 20:29:47 +0100 Subject: [PATCH 14/71] Changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e8516ae25..97ea9246d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Bug #4754: Stack of ammunition cannot be equipped partially Bug #4816: GetWeaponDrawn returns 1 before weapon is attached Bug #5057: Weapon swing sound plays at same pitch whether it hits or misses + Bug #5062: Root bone rotations for NPC animation don't work the same as for creature animation Bug #5129: Stuttering animation on Centurion Archer Bug #5280: Unskinned shapes in skinned equipment are rendered in the wrong place Bug #5371: Keyframe animation tracks are used for any file that begins with an X From a0f8bbc621d8181ec4acca3d8e28c558cffcba41 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 1 Nov 2023 20:34:39 +0100 Subject: [PATCH 15/71] Bad merge --- apps/openmw/mwmechanics/character.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 1fca6c6b71..05c0ad2264 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2450,7 +2450,6 @@ namespace MWMechanics } // Update movement - && !isScriptedAnimPlaying() world->queueMovement(mPtr, moved); } From 452f7a470e095f2c3ba5bdd3d09dadd2aecff628 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Wed, 1 Nov 2023 23:19:13 +0100 Subject: [PATCH 16/71] fabsf -> abs --- apps/openmw/mwmechanics/character.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 05c0ad2264..4eb223e250 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2424,7 +2424,7 @@ namespace MWMechanics const float epsilon = 0.001f; float targetMovementAngle = std::atan2(-movement.x(), movement.y()); float diff = targetMovementAngle - animMovementAngle; - if (std::fabsf(diff) > epsilon) + if (std::abs(diff) > epsilon) { moved = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * moved; } From 633fd892700f8b7c146633b5670b8e14d5686b27 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 1 Nov 2023 23:34:54 +0100 Subject: [PATCH 17/71] Cleanup settings categories includes --- components/settings/categories/camera.hpp | 4 ++-- components/settings/categories/cells.hpp | 4 ++-- components/settings/categories/fog.hpp | 4 ++-- components/settings/categories/game.hpp | 6 +++--- components/settings/categories/general.hpp | 4 ++-- components/settings/categories/groundcover.hpp | 4 ++-- components/settings/categories/gui.hpp | 4 ++-- components/settings/categories/hud.hpp | 2 +- components/settings/categories/input.hpp | 4 ++-- components/settings/categories/lua.hpp | 4 ++-- components/settings/categories/map.hpp | 6 +++--- components/settings/categories/models.hpp | 2 +- components/settings/categories/navigator.hpp | 4 ++-- components/settings/categories/physics.hpp | 4 ++-- components/settings/categories/postprocessing.hpp | 4 ++-- components/settings/categories/saves.hpp | 4 ++-- components/settings/categories/shaders.hpp | 6 +++--- components/settings/categories/shadows.hpp | 4 ++-- components/settings/categories/sound.hpp | 6 +++--- components/settings/categories/stereo.hpp | 2 +- components/settings/categories/stereoview.hpp | 4 ++-- components/settings/categories/terrain.hpp | 4 ++-- components/settings/categories/video.hpp | 3 +-- components/settings/categories/water.hpp | 4 ++-- components/settings/categories/windows.hpp | 3 +-- 25 files changed, 49 insertions(+), 51 deletions(-) diff --git a/components/settings/categories/camera.hpp b/components/settings/categories/camera.hpp index 4712f88b45..5b3a9b742b 100644 --- a/components/settings/categories/camera.hpp +++ b/components/settings/categories/camera.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_CAMERA_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_CAMERA_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/cells.hpp b/components/settings/categories/cells.hpp index 723004d674..86fe944acf 100644 --- a/components/settings/categories/cells.hpp +++ b/components/settings/categories/cells.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_CELLS_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_CELLS_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/fog.hpp b/components/settings/categories/fog.hpp index 5acf3d20c6..3bc75587f1 100644 --- a/components/settings/categories/fog.hpp +++ b/components/settings/categories/fog.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_FOG_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_FOG_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/game.hpp b/components/settings/categories/game.hpp index ded367a54c..5b400acb50 100644 --- a/components/settings/categories/game.hpp +++ b/components/settings/categories/game.hpp @@ -1,9 +1,9 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GAME_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GAME_H -#include "components/detournavigator/collisionshapetype.hpp" -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include +#include #include #include diff --git a/components/settings/categories/general.hpp b/components/settings/categories/general.hpp index 7bbb651ee8..a79ef83ea0 100644 --- a/components/settings/categories/general.hpp +++ b/components/settings/categories/general.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GENERAL_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GENERAL_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/groundcover.hpp b/components/settings/categories/groundcover.hpp index 48f97037ae..78615a4e76 100644 --- a/components/settings/categories/groundcover.hpp +++ b/components/settings/categories/groundcover.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GROUNDCOVER_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GROUNDCOVER_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/gui.hpp b/components/settings/categories/gui.hpp index 1d3a56af39..af438d5ddc 100644 --- a/components/settings/categories/gui.hpp +++ b/components/settings/categories/gui.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GUI_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_GUI_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/hud.hpp b/components/settings/categories/hud.hpp index e97a81501d..fe05e39eda 100644 --- a/components/settings/categories/hud.hpp +++ b/components/settings/categories/hud.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_HUD_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_HUD_H -#include "components/settings/settingvalue.hpp" +#include #include #include diff --git a/components/settings/categories/input.hpp b/components/settings/categories/input.hpp index 1e59b45334..0a450f1dcd 100644 --- a/components/settings/categories/input.hpp +++ b/components/settings/categories/input.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_INPUT_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_INPUT_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/lua.hpp b/components/settings/categories/lua.hpp index da11a605eb..88d74b2d1f 100644 --- a/components/settings/categories/lua.hpp +++ b/components/settings/categories/lua.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_LUA_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_LUA_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/map.hpp b/components/settings/categories/map.hpp index bdccd6f205..8a80d5aa63 100644 --- a/components/settings/categories/map.hpp +++ b/components/settings/categories/map.hpp @@ -1,9 +1,9 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_MAP_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_MAP_H -#include "components/misc/constants.hpp" -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include +#include #include #include diff --git a/components/settings/categories/models.hpp b/components/settings/categories/models.hpp index fec8da7775..0d26eeba5f 100644 --- a/components/settings/categories/models.hpp +++ b/components/settings/categories/models.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_MODELS_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_MODELS_H -#include "components/settings/settingvalue.hpp" +#include #include #include diff --git a/components/settings/categories/navigator.hpp b/components/settings/categories/navigator.hpp index b820b2c950..d6d7adcd56 100644 --- a/components/settings/categories/navigator.hpp +++ b/components/settings/categories/navigator.hpp @@ -1,9 +1,9 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_NAVIGATOR_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_NAVIGATOR_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" #include +#include +#include #include #include diff --git a/components/settings/categories/physics.hpp b/components/settings/categories/physics.hpp index 41005a06cf..4720708db2 100644 --- a/components/settings/categories/physics.hpp +++ b/components/settings/categories/physics.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_PHYSICS_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_PHYSICS_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/postprocessing.hpp b/components/settings/categories/postprocessing.hpp index 04810b847c..7f5ddfba3e 100644 --- a/components/settings/categories/postprocessing.hpp +++ b/components/settings/categories/postprocessing.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_POSTPROCESSING_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_POSTPROCESSING_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/saves.hpp b/components/settings/categories/saves.hpp index e565d98564..3a64785412 100644 --- a/components/settings/categories/saves.hpp +++ b/components/settings/categories/saves.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SAVES_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SAVES_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/shaders.hpp b/components/settings/categories/shaders.hpp index 7efb891822..dce2531c1e 100644 --- a/components/settings/categories/shaders.hpp +++ b/components/settings/categories/shaders.hpp @@ -1,9 +1,9 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SHADERS_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SHADERS_H -#include "components/sceneutil/lightingmethod.hpp" -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include +#include #include #include diff --git a/components/settings/categories/shadows.hpp b/components/settings/categories/shadows.hpp index d716272bce..0da6f649c4 100644 --- a/components/settings/categories/shadows.hpp +++ b/components/settings/categories/shadows.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SHADOWS_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SHADOWS_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/sound.hpp b/components/settings/categories/sound.hpp index 43313e622d..995bce2a58 100644 --- a/components/settings/categories/sound.hpp +++ b/components/settings/categories/sound.hpp @@ -1,9 +1,9 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SOUND_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_SOUND_H -#include "components/settings/hrtfmode.hpp" -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include +#include #include diff --git a/components/settings/categories/stereo.hpp b/components/settings/categories/stereo.hpp index 98fae8693f..aa903c5b53 100644 --- a/components/settings/categories/stereo.hpp +++ b/components/settings/categories/stereo.hpp @@ -1,7 +1,7 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_STEREO_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_STEREO_H -#include "components/settings/settingvalue.hpp" +#include #include #include diff --git a/components/settings/categories/stereoview.hpp b/components/settings/categories/stereoview.hpp index bcd0f57abc..7f08d9bc35 100644 --- a/components/settings/categories/stereoview.hpp +++ b/components/settings/categories/stereoview.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_STEREOVIEW_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_STEREOVIEW_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/terrain.hpp b/components/settings/categories/terrain.hpp index c2eef9dc20..f26cc264b8 100644 --- a/components/settings/categories/terrain.hpp +++ b/components/settings/categories/terrain.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_TERRAIN_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_TERRAIN_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/video.hpp b/components/settings/categories/video.hpp index 0e0f7a75bb..cb12ea079c 100644 --- a/components/settings/categories/video.hpp +++ b/components/settings/categories/video.hpp @@ -1,13 +1,12 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_VIDEO_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_VIDEO_H +#include #include #include #include #include -#include - #include #include #include diff --git a/components/settings/categories/water.hpp b/components/settings/categories/water.hpp index b88d38b023..2e04114244 100644 --- a/components/settings/categories/water.hpp +++ b/components/settings/categories/water.hpp @@ -1,8 +1,8 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_WATER_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_WATER_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include +#include #include #include diff --git a/components/settings/categories/windows.hpp b/components/settings/categories/windows.hpp index 1fc249cc97..2f22e751e6 100644 --- a/components/settings/categories/windows.hpp +++ b/components/settings/categories/windows.hpp @@ -1,8 +1,7 @@ #ifndef OPENMW_COMPONENTS_SETTINGS_CATEGORIES_WINDOWS_H #define OPENMW_COMPONENTS_SETTINGS_CATEGORIES_WINDOWS_H -#include "components/settings/sanitizerimpl.hpp" -#include "components/settings/settingvalue.hpp" +#include #include #include From 7cea0344b28125c5afbff74c234d4c942597f552 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sat, 21 Oct 2023 12:29:26 +0400 Subject: [PATCH 18/71] Add Clangd cache to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f25adf58e6..39033bd725 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ Doxygen .idea cmake-build-* files/windows/*.aps +.cache/clangd ## qt-creator CMakeLists.txt.user* .vs From f4efbcc1c4c35a752113956d83f0aeeb122d3fa1 Mon Sep 17 00:00:00 2001 From: elsid Date: Wed, 1 Nov 2023 23:53:32 +0100 Subject: [PATCH 19/71] Use settings values for Shadows settings --- apps/launcher/graphicspage.cpp | 83 +++++++++-------------- apps/openmw/mwrender/characterpreview.cpp | 2 +- apps/openmw/mwrender/localmap.cpp | 2 +- apps/openmw/mwrender/renderingmanager.cpp | 18 ++--- apps/openmw/mwrender/sky.cpp | 4 +- apps/openmw/mwrender/water.cpp | 5 +- components/sceneutil/shadow.cpp | 74 +++++++++----------- components/sceneutil/shadow.hpp | 18 +++-- 8 files changed, 92 insertions(+), 114 deletions(-) diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index 18fb57805f..952e0c9349 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -158,32 +158,32 @@ bool Launcher::GraphicsPage::loadSettings() lightingMethodComboBox->setCurrentIndex(lightingMethod); // Shadows - if (Settings::Manager::getBool("actor shadows", "Shadows")) + if (Settings::shadows().mActorShadows) actorShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::Manager::getBool("player shadows", "Shadows")) + if (Settings::shadows().mPlayerShadows) playerShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::Manager::getBool("terrain shadows", "Shadows")) + if (Settings::shadows().mTerrainShadows) terrainShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::Manager::getBool("object shadows", "Shadows")) + if (Settings::shadows().mObjectShadows) objectShadowsCheckBox->setCheckState(Qt::Checked); - if (Settings::Manager::getBool("enable indoor shadows", "Shadows")) + if (Settings::shadows().mEnableIndoorShadows) indoorShadowsCheckBox->setCheckState(Qt::Checked); - shadowComputeSceneBoundsComboBox->setCurrentIndex(shadowComputeSceneBoundsComboBox->findText( - QString(tr(Settings::Manager::getString("compute scene bounds", "Shadows").c_str())))); + shadowComputeSceneBoundsComboBox->setCurrentIndex( + shadowComputeSceneBoundsComboBox->findText(QString(tr(Settings::shadows().mComputeSceneBounds.get().c_str())))); - int shadowDistLimit = Settings::Manager::getInt("maximum shadow map distance", "Shadows"); + const int shadowDistLimit = Settings::shadows().mMaximumShadowMapDistance; if (shadowDistLimit > 0) { shadowDistanceCheckBox->setCheckState(Qt::Checked); shadowDistanceSpinBox->setValue(shadowDistLimit); } - float shadowFadeStart = Settings::Manager::getFloat("shadow fade start", "Shadows"); + const float shadowFadeStart = Settings::shadows().mShadowFadeStart; if (shadowFadeStart != 0) fadeStartSpinBox->setValue(shadowFadeStart); - int shadowRes = Settings::Manager::getInt("shadow map resolution", "Shadows"); + const int shadowRes = Settings::shadows().mShadowMapResolution; int shadowResIndex = shadowResolutionComboBox->findText(QString::number(shadowRes)); if (shadowResIndex != -1) shadowResolutionComboBox->setCurrentIndex(shadowResIndex); @@ -240,55 +240,36 @@ void Launcher::GraphicsPage::saveSettings() Settings::shaders().mLightingMethod.set(lightingMethodMap[lightingMethodComboBox->currentIndex()]); // Shadows - int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0; - if (Settings::Manager::getInt("maximum shadow map distance", "Shadows") != cShadowDist) - Settings::Manager::setInt("maximum shadow map distance", "Shadows", cShadowDist); - float cFadeStart = fadeStartSpinBox->value(); - if (cShadowDist > 0 && Settings::Manager::getFloat("shadow fade start", "Shadows") != cFadeStart) - Settings::Manager::setFloat("shadow fade start", "Shadows", cFadeStart); + const int cShadowDist = shadowDistanceCheckBox->checkState() != Qt::Unchecked ? shadowDistanceSpinBox->value() : 0; + Settings::shadows().mMaximumShadowMapDistance.set(cShadowDist); + const float cFadeStart = fadeStartSpinBox->value(); + if (cShadowDist > 0) + Settings::shadows().mShadowFadeStart.set(cFadeStart); - bool cActorShadows = actorShadowsCheckBox->checkState(); - bool cObjectShadows = objectShadowsCheckBox->checkState(); - bool cTerrainShadows = terrainShadowsCheckBox->checkState(); - bool cPlayerShadows = playerShadowsCheckBox->checkState(); + const bool cActorShadows = actorShadowsCheckBox->checkState() != Qt::Unchecked; + const bool cObjectShadows = objectShadowsCheckBox->checkState() != Qt::Unchecked; + const bool cTerrainShadows = terrainShadowsCheckBox->checkState() != Qt::Unchecked; + const bool cPlayerShadows = playerShadowsCheckBox->checkState() != Qt::Unchecked; if (cActorShadows || cObjectShadows || cTerrainShadows || cPlayerShadows) { - if (!Settings::Manager::getBool("enable shadows", "Shadows")) - Settings::Manager::setBool("enable shadows", "Shadows", true); - if (Settings::Manager::getBool("actor shadows", "Shadows") != cActorShadows) - Settings::Manager::setBool("actor shadows", "Shadows", cActorShadows); - if (Settings::Manager::getBool("player shadows", "Shadows") != cPlayerShadows) - Settings::Manager::setBool("player shadows", "Shadows", cPlayerShadows); - if (Settings::Manager::getBool("object shadows", "Shadows") != cObjectShadows) - Settings::Manager::setBool("object shadows", "Shadows", cObjectShadows); - if (Settings::Manager::getBool("terrain shadows", "Shadows") != cTerrainShadows) - Settings::Manager::setBool("terrain shadows", "Shadows", cTerrainShadows); + Settings::shadows().mEnableShadows.set(true); + Settings::shadows().mActorShadows.set(cActorShadows); + Settings::shadows().mPlayerShadows.set(cPlayerShadows); + Settings::shadows().mObjectShadows.set(cObjectShadows); + Settings::shadows().mTerrainShadows.set(cTerrainShadows); } else { - if (Settings::Manager::getBool("enable shadows", "Shadows")) - Settings::Manager::setBool("enable shadows", "Shadows", false); - if (Settings::Manager::getBool("actor shadows", "Shadows")) - Settings::Manager::setBool("actor shadows", "Shadows", false); - if (Settings::Manager::getBool("player shadows", "Shadows")) - Settings::Manager::setBool("player shadows", "Shadows", false); - if (Settings::Manager::getBool("object shadows", "Shadows")) - Settings::Manager::setBool("object shadows", "Shadows", false); - if (Settings::Manager::getBool("terrain shadows", "Shadows")) - Settings::Manager::setBool("terrain shadows", "Shadows", false); + Settings::shadows().mEnableShadows.set(false); + Settings::shadows().mActorShadows.set(false); + Settings::shadows().mPlayerShadows.set(false); + Settings::shadows().mObjectShadows.set(false); + Settings::shadows().mTerrainShadows.set(false); } - bool cIndoorShadows = indoorShadowsCheckBox->checkState(); - if (Settings::Manager::getBool("enable indoor shadows", "Shadows") != cIndoorShadows) - Settings::Manager::setBool("enable indoor shadows", "Shadows", cIndoorShadows); - - int cShadowRes = shadowResolutionComboBox->currentText().toInt(); - if (cShadowRes != Settings::Manager::getInt("shadow map resolution", "Shadows")) - Settings::Manager::setInt("shadow map resolution", "Shadows", cShadowRes); - - auto cComputeSceneBounds = shadowComputeSceneBoundsComboBox->currentText().toStdString(); - if (cComputeSceneBounds != Settings::Manager::getString("compute scene bounds", "Shadows")) - Settings::Manager::setString("compute scene bounds", "Shadows", cComputeSceneBounds); + Settings::shadows().mEnableIndoorShadows.set(indoorShadowsCheckBox->checkState() != Qt::Unchecked); + Settings::shadows().mShadowMapResolution.set(shadowResolutionComboBox->currentText().toInt()); + Settings::shadows().mComputeSceneBounds.set(shadowComputeSceneBoundsComboBox->currentText().toStdString()); } QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 86371dc0bf..88ceeabd23 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -247,7 +247,7 @@ namespace MWRender defaultMat->setSpecular(osg::Material::FRONT_AND_BACK, osg::Vec4f(0.f, 0.f, 0.f, 0.f)); stateset->setAttribute(defaultMat); - SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); + SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *stateset); // assign large value to effectively turn off fog // shaders don't respect glDisable(GL_FOG) diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index 3d28bf477d..892a8b5428 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -763,7 +763,7 @@ namespace MWRender lightSource->setStateSetModes(*stateset, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE); - SceneUtil::ShadowManager::disableShadowsForStateSet(stateset); + SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *stateset); // override sun for local map SceneUtil::configureStateSetSunOverride(static_cast(mSceneRoot), light, stateset); diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 3b6716ce2c..c722a51c89 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -331,8 +331,8 @@ namespace MWRender // Shadows and radial fog have problems with fixed-function mode. bool forceShaders = Settings::fog().mRadialFog || Settings::fog().mExponentialFog || Settings::shaders().mSoftParticles || Settings::shaders().mForceShaders - || Settings::Manager::getBool("enable shadows", "Shadows") - || lightingMethod != SceneUtil::LightingMethod::FFP || reverseZ || mSkyBlending || Stereo::getMultiview(); + || Settings::shadows().mEnableShadows || lightingMethod != SceneUtil::LightingMethod::FFP || reverseZ + || mSkyBlending || Stereo::getMultiview(); resourceSystem->getSceneManager()->setForceShaders(forceShaders); // FIXME: calling dummy method because terrain needs to know whether lighting is clamped @@ -367,22 +367,22 @@ namespace MWRender sceneRoot->setName("Scene Root"); int shadowCastingTraversalMask = Mask_Scene; - if (Settings::Manager::getBool("actor shadows", "Shadows")) + if (Settings::shadows().mActorShadows) shadowCastingTraversalMask |= Mask_Actor; - if (Settings::Manager::getBool("player shadows", "Shadows")) + if (Settings::shadows().mPlayerShadows) shadowCastingTraversalMask |= Mask_Player; int indoorShadowCastingTraversalMask = shadowCastingTraversalMask; - if (Settings::Manager::getBool("object shadows", "Shadows")) + if (Settings::shadows().mObjectShadows) shadowCastingTraversalMask |= (Mask_Object | Mask_Static); - if (Settings::Manager::getBool("terrain shadows", "Shadows")) + if (Settings::shadows().mTerrainShadows) shadowCastingTraversalMask |= Mask_Terrain; mShadowManager = std::make_unique(sceneRoot, mRootNode, shadowCastingTraversalMask, - indoorShadowCastingTraversalMask, Mask_Terrain | Mask_Object | Mask_Static, + indoorShadowCastingTraversalMask, Mask_Terrain | Mask_Object | Mask_Static, Settings::shadows(), mResourceSystem->getSceneManager()->getShaderManager()); - Shader::ShaderManager::DefineMap shadowDefines = mShadowManager->getShadowDefines(); + Shader::ShaderManager::DefineMap shadowDefines = mShadowManager->getShadowDefines(Settings::shadows()); Shader::ShaderManager::DefineMap lightDefines = sceneRoot->getLightDefines(); Shader::ShaderManager::DefineMap globalDefines = mResourceSystem->getSceneManager()->getShaderManager().getGlobalDefines(); @@ -770,7 +770,7 @@ namespace MWRender if (enabled) mShadowManager->enableOutdoorMode(); else - mShadowManager->enableIndoorMode(); + mShadowManager->enableIndoorMode(Settings::shadows()); mPostProcessor->getStateUpdater()->setIsInterior(!enabled); } diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 51018e93f9..a38030738a 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -220,7 +220,7 @@ namespace camera->setNodeMask(MWRender::Mask_RenderToTexture); camera->setCullMask(MWRender::Mask_Sky); camera->addChild(mEarlyRenderBinRoot); - SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet()); + SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *camera->getOrCreateStateSet()); } private: @@ -271,7 +271,7 @@ namespace MWRender if (!mSceneManager->getForceShaders()) skyroot->getOrCreateStateSet()->setAttributeAndModes(new osg::Program(), osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED | osg::StateAttribute::ON); - SceneUtil::ShadowManager::disableShadowsForStateSet(skyroot->getOrCreateStateSet()); + SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *skyroot->getOrCreateStateSet()); parentNode->addChild(skyroot); mEarlyRenderBinRoot = new osg::Group; diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index 091ab99821..85df70adfc 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -265,7 +265,8 @@ namespace MWRender camera->setNodeMask(Mask_RenderToTexture); if (Settings::water().mRefractionScale != 1) // TODO: to be removed with issue #5709 - SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet()); + SceneUtil::ShadowManager::disableShadowsForStateSet( + Settings::shadows(), *camera->getOrCreateStateSet()); } void apply(osg::Camera* camera) override @@ -341,7 +342,7 @@ namespace MWRender camera->addChild(mClipCullNode); camera->setNodeMask(Mask_RenderToTexture); - SceneUtil::ShadowManager::disableShadowsForStateSet(camera->getOrCreateStateSet()); + SceneUtil::ShadowManager::disableShadowsForStateSet(Settings::shadows(), *camera->getOrCreateStateSet()); } void apply(osg::Camera* camera) override diff --git a/components/sceneutil/shadow.cpp b/components/sceneutil/shadow.cpp index f2748d70f1..04f3b65edd 100644 --- a/components/sceneutil/shadow.cpp +++ b/components/sceneutil/shadow.cpp @@ -4,7 +4,7 @@ #include #include -#include +#include #include #include "mwshadowtechnique.hpp" @@ -13,9 +13,10 @@ namespace SceneUtil { using namespace osgShadow; - void ShadowManager::setupShadowSettings(Shader::ShaderManager& shaderManager) + void ShadowManager::setupShadowSettings( + const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager) { - mEnableShadows = Settings::Manager::getBool("enable shadows", "Shadows"); + mEnableShadows = settings.mEnableShadows; if (!mEnableShadows) { @@ -28,64 +29,58 @@ namespace SceneUtil mShadowSettings->setLightNum(0); mShadowSettings->setReceivesShadowTraversalMask(~0u); - const int numberOfShadowMapsPerLight - = std::clamp(Settings::Manager::getInt("number of shadow maps", "Shadows"), 1, 8); + const int numberOfShadowMapsPerLight = settings.mNumberOfShadowMaps; mShadowSettings->setNumShadowMapsPerLight(numberOfShadowMapsPerLight); mShadowSettings->setBaseShadowTextureUnit(shaderManager.reserveGlobalTextureUnits( Shader::ShaderManager::Slot::ShadowMaps, numberOfShadowMapsPerLight)); - const float maximumShadowMapDistance = Settings::Manager::getFloat("maximum shadow map distance", "Shadows"); + const float maximumShadowMapDistance = settings.mMaximumShadowMapDistance; if (maximumShadowMapDistance > 0) { - const float shadowFadeStart - = std::clamp(Settings::Manager::getFloat("shadow fade start", "Shadows"), 0.f, 1.f); + const float shadowFadeStart = settings.mShadowFadeStart; mShadowSettings->setMaximumShadowMapDistance(maximumShadowMapDistance); mShadowTechnique->setShadowFadeStart(maximumShadowMapDistance * shadowFadeStart); } - mShadowSettings->setMinimumShadowMapNearFarRatio( - Settings::Manager::getFloat("minimum lispsm near far ratio", "Shadows")); + mShadowSettings->setMinimumShadowMapNearFarRatio(settings.mMinimumLispsmNearFarRatio); - const std::string& computeSceneBounds = Settings::Manager::getString("compute scene bounds", "Shadows"); + const std::string& computeSceneBounds = settings.mComputeSceneBounds; if (Misc::StringUtils::ciEqual(computeSceneBounds, "primitives")) mShadowSettings->setComputeNearFarModeOverride(osg::CullSettings::COMPUTE_NEAR_FAR_USING_PRIMITIVES); else if (Misc::StringUtils::ciEqual(computeSceneBounds, "bounds")) mShadowSettings->setComputeNearFarModeOverride(osg::CullSettings::COMPUTE_NEAR_FAR_USING_BOUNDING_VOLUMES); - int mapres = Settings::Manager::getInt("shadow map resolution", "Shadows"); + const int mapres = settings.mShadowMapResolution; mShadowSettings->setTextureSize(osg::Vec2s(mapres, mapres)); - mShadowTechnique->setSplitPointUniformLogarithmicRatio( - Settings::Manager::getFloat("split point uniform logarithmic ratio", "Shadows")); - mShadowTechnique->setSplitPointDeltaBias(Settings::Manager::getFloat("split point bias", "Shadows")); + mShadowTechnique->setSplitPointUniformLogarithmicRatio(settings.mSplitPointUniformLogarithmicRatio); + mShadowTechnique->setSplitPointDeltaBias(settings.mSplitPointBias); - mShadowTechnique->setPolygonOffset(Settings::Manager::getFloat("polygon offset factor", "Shadows"), - Settings::Manager::getFloat("polygon offset units", "Shadows")); + mShadowTechnique->setPolygonOffset(settings.mPolygonOffsetFactor, settings.mPolygonOffsetUnits); - if (Settings::Manager::getBool("use front face culling", "Shadows")) + if (settings.mUseFrontFaceCulling) mShadowTechnique->enableFrontFaceCulling(); else mShadowTechnique->disableFrontFaceCulling(); - if (Settings::Manager::getBool("allow shadow map overlap", "Shadows")) + if (settings.mAllowShadowMapOverlap) mShadowSettings->setMultipleShadowMapHint(osgShadow::ShadowSettings::CASCADED); else mShadowSettings->setMultipleShadowMapHint(osgShadow::ShadowSettings::PARALLEL_SPLIT); - if (Settings::Manager::getBool("enable debug hud", "Shadows")) + if (settings.mEnableDebugHud) mShadowTechnique->enableDebugHUD(); else mShadowTechnique->disableDebugHUD(); } - void ShadowManager::disableShadowsForStateSet(osg::ref_ptr stateset) + void ShadowManager::disableShadowsForStateSet(const Settings::ShadowsCategory& settings, osg::StateSet& stateset) { - if (!Settings::Manager::getBool("enable shadows", "Shadows")) + if (!settings.mEnableShadows) return; - const int numberOfShadowMapsPerLight - = std::clamp(Settings::Manager::getInt("number of shadow maps", "Shadows"), 1, 8); + const int numberOfShadowMapsPerLight = settings.mNumberOfShadowMaps; int baseShadowTextureUnit = 8 - numberOfShadowMapsPerLight; @@ -99,18 +94,18 @@ namespace SceneUtil fakeShadowMapTexture->setShadowCompareFunc(osg::Texture::ShadowCompareFunc::ALWAYS); for (int i = baseShadowTextureUnit; i < baseShadowTextureUnit + numberOfShadowMapsPerLight; ++i) { - stateset->setTextureAttributeAndModes(i, fakeShadowMapTexture, + stateset.setTextureAttributeAndModes(i, fakeShadowMapTexture, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE | osg::StateAttribute::PROTECTED); - stateset->addUniform( + stateset.addUniform( new osg::Uniform(("shadowTexture" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); - stateset->addUniform( + stateset.addUniform( new osg::Uniform(("shadowTextureUnit" + std::to_string(i - baseShadowTextureUnit)).c_str(), i)); } } ShadowManager::ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, unsigned int worldMask, - Shader::ShaderManager& shaderManager) + const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager) : mShadowedScene(new osgShadow::ShadowedScene) , mShadowTechnique(new MWShadowTechnique) , mOutdoorShadowCastingMask(outdoorShadowCastingMask) @@ -126,7 +121,7 @@ namespace SceneUtil mShadowedScene->setNodeMask(sceneRoot->getNodeMask()); mShadowSettings = mShadowedScene->getShadowSettings(); - setupShadowSettings(shaderManager); + setupShadowSettings(settings, shaderManager); mShadowTechnique->setupCastingShader(shaderManager); mShadowTechnique->setWorldMask(worldMask); @@ -140,7 +135,7 @@ namespace SceneUtil Stereo::Manager::instance().setShadowTechnique(nullptr); } - Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines() + Shader::ShaderManager::DefineMap ShadowManager::getShadowDefines(const Settings::ShadowsCategory& settings) { if (!mEnableShadows) return getShadowsDisabledDefines(); @@ -155,24 +150,19 @@ namespace SceneUtil definesWithShadows["shadow_texture_unit_list"] = definesWithShadows["shadow_texture_unit_list"].substr( 0, definesWithShadows["shadow_texture_unit_list"].length() - 1); - definesWithShadows["shadowMapsOverlap"] - = Settings::Manager::getBool("allow shadow map overlap", "Shadows") ? "1" : "0"; + definesWithShadows["shadowMapsOverlap"] = settings.mAllowShadowMapOverlap ? "1" : "0"; - definesWithShadows["useShadowDebugOverlay"] - = Settings::Manager::getBool("enable debug overlay", "Shadows") ? "1" : "0"; + definesWithShadows["useShadowDebugOverlay"] = settings.mEnableDebugOverlay ? "1" : "0"; // switch this to reading settings if it's ever exposed to the user definesWithShadows["perspectiveShadowMaps"] = mShadowSettings->getShadowMapProjectionHint() == ShadowSettings::PERSPECTIVE_SHADOW_MAP ? "1" : "0"; - definesWithShadows["disableNormalOffsetShadows"] - = Settings::Manager::getFloat("normal offset distance", "Shadows") == 0.0 ? "1" : "0"; + definesWithShadows["disableNormalOffsetShadows"] = settings.mNormalOffsetDistance == 0.0 ? "1" : "0"; - definesWithShadows["shadowNormalOffset"] - = std::to_string(Settings::Manager::getFloat("normal offset distance", "Shadows")); + definesWithShadows["shadowNormalOffset"] = std::to_string(settings.mNormalOffsetDistance); - definesWithShadows["limitShadowMapDistance"] - = Settings::Manager::getFloat("maximum shadow map distance", "Shadows") > 0 ? "1" : "0"; + definesWithShadows["limitShadowMapDistance"] = settings.mMaximumShadowMapDistance > 0 ? "1" : "0"; return definesWithShadows; } @@ -200,9 +190,9 @@ namespace SceneUtil return definesWithoutShadows; } - void ShadowManager::enableIndoorMode() + void ShadowManager::enableIndoorMode(const Settings::ShadowsCategory& settings) { - if (Settings::Manager::getBool("enable indoor shadows", "Shadows")) + if (settings.mEnableIndoorShadows) mShadowSettings->setCastsShadowTraversalMask(mIndoorShadowCastingMask); else mShadowTechnique->disableShadows(true); diff --git a/components/sceneutil/shadow.hpp b/components/sceneutil/shadow.hpp index 76fd2b7fdd..fd82e828b6 100644 --- a/components/sceneutil/shadow.hpp +++ b/components/sceneutil/shadow.hpp @@ -8,32 +8,38 @@ namespace osg class StateSet; class Group; } + namespace osgShadow { class ShadowSettings; class ShadowedScene; } +namespace Settings +{ + struct ShadowsCategory; +} + namespace SceneUtil { class MWShadowTechnique; class ShadowManager { public: - static void disableShadowsForStateSet(osg::ref_ptr stateSet); + static void disableShadowsForStateSet(const Settings::ShadowsCategory& settings, osg::StateSet& stateset); static Shader::ShaderManager::DefineMap getShadowsDisabledDefines(); - ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, + explicit ShadowManager(osg::ref_ptr sceneRoot, osg::ref_ptr rootNode, unsigned int outdoorShadowCastingMask, unsigned int indoorShadowCastingMask, unsigned int worldMask, - Shader::ShaderManager& shaderManager); + const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager); ~ShadowManager(); - void setupShadowSettings(Shader::ShaderManager& shaderManager); + void setupShadowSettings(const Settings::ShadowsCategory& settings, Shader::ShaderManager& shaderManager); - Shader::ShaderManager::DefineMap getShadowDefines(); + Shader::ShaderManager::DefineMap getShadowDefines(const Settings::ShadowsCategory& settings); - void enableIndoorMode(); + void enableIndoorMode(const Settings::ShadowsCategory& settings); void enableOutdoorMode(); From abae5f031d0c78d53ba2ceef646cbee4ee6adcb9 Mon Sep 17 00:00:00 2001 From: uramer Date: Thu, 2 Nov 2023 16:05:52 +0000 Subject: [PATCH 20/71] Fix docs type --- files/data/scripts/omw/ui.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/data/scripts/omw/ui.lua b/files/data/scripts/omw/ui.lua index 48412f6a0f..1e76b8e141 100644 --- a/files/data/scripts/omw/ui.lua +++ b/files/data/scripts/omw/ui.lua @@ -236,7 +236,7 @@ return { --- -- Returns if the player HUD is visible or not -- @function [parent=#UI] isHudVisible - -- @return #bool + -- @return #boolean isHudVisible = function() return ui._isHudVisible() end, -- TODO From a88f0ecc958c52fd018429cc986fb30755cabfd4 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 2 Nov 2023 17:43:09 +0100 Subject: [PATCH 21/71] Expose governing attributes to Lua --- apps/openmw/mwlua/stats.cpp | 3 +++ files/lua_api/openmw/core.lua | 1 + 2 files changed, 4 insertions(+) diff --git a/apps/openmw/mwlua/stats.cpp b/apps/openmw/mwlua/stats.cpp index 5c1e536dd6..127d8e79d9 100644 --- a/apps/openmw/mwlua/stats.cpp +++ b/apps/openmw/mwlua/stats.cpp @@ -454,6 +454,9 @@ namespace MWLua return nullptr; return &*rec.mSchool; }); + skillT["attribute"] = sol::readonly_property([](const ESM::Skill& rec) -> std::string { + return ESM::Attribute::indexToRefId(rec.mData.mAttribute).serializeText(); + }); auto schoolT = context.mLua->sol().new_usertype("MagicSchool"); schoolT[sol::meta_function::to_string] diff --git a/files/lua_api/openmw/core.lua b/files/lua_api/openmw/core.lua index abdb099ed7..44ab4473fa 100644 --- a/files/lua_api/openmw/core.lua +++ b/files/lua_api/openmw/core.lua @@ -862,6 +862,7 @@ -- @field #string description Human-readable description -- @field #string icon VFS path to the icon -- @field #MagicSchoolData school Optional magic school +-- @field #string attribute The id of the skill's governing attribute -- @type MagicSchoolData -- @field #string name Human-readable name From 2d90176fe94747d10ce3d9436478c2a31ea7d3f3 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Thu, 2 Nov 2023 19:29:26 +0100 Subject: [PATCH 22/71] Add types.Actor.isDead --- apps/openmw/mwlua/types/actor.cpp | 5 +++++ files/lua_api/openmw/types.lua | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/apps/openmw/mwlua/types/actor.cpp b/apps/openmw/mwlua/types/actor.cpp index 2afe3386ce..370e9e7f69 100644 --- a/apps/openmw/mwlua/types/actor.cpp +++ b/apps/openmw/mwlua/types/actor.cpp @@ -395,6 +395,11 @@ namespace MWLua return dist <= actorsProcessingRange; }; + actor["isDead"] = [](const Object& o) { + const auto& target = o.ptr(); + return target.getClass().getCreatureStats(target).isDead(); + }; + actor["getEncumbrance"] = [](const Object& actor) -> float { const MWWorld::Ptr ptr = actor.ptr(); return ptr.getClass().getEncumbrance(ptr); diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 69ce5fbaf2..7b7afe5ba8 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -15,6 +15,12 @@ -- @param openmw.core#GameObject actor -- @return #number +--- +-- Check if the given actor is dead. +-- @function [parent=#Actor] isDead +-- @param openmw.core#GameObject actor +-- @return #boolean + --- -- Agent bounds to be used for pathfinding functions. -- @function [parent=#Actor] getPathfindingAgentBounds From 9c93d907dcf09bd84a56b9af56c94810643b81ca Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Thu, 2 Nov 2023 19:30:51 +0100 Subject: [PATCH 23/71] Settings page entry for the "player movement ignores animation" setting. --- apps/launcher/settingspage.cpp | 2 ++ files/ui/settingspage.ui | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 5869cc3a73..b8539671b5 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -191,6 +191,7 @@ bool Launcher::SettingsPage::loadSettings() } loadSettingBool(Settings::game().mTurnToMovementDirection, *turnToMovementDirectionCheckBox); loadSettingBool(Settings::game().mSmoothMovement, *smoothMovementCheckBox); + loadSettingBool(Settings::game().mPlayerMovementIgnoresAnimation, *playerMovementIgnoresAnimationCheckBox); distantLandCheckBox->setCheckState( Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging ? Qt::Checked : Qt::Unchecked); @@ -338,6 +339,7 @@ void Launcher::SettingsPage::saveSettings() saveSettingBool(*shieldSheathingCheckBox, Settings::game().mShieldSheathing); saveSettingBool(*turnToMovementDirectionCheckBox, Settings::game().mTurnToMovementDirection); saveSettingBool(*smoothMovementCheckBox, Settings::game().mSmoothMovement); + saveSettingBool(*playerMovementIgnoresAnimationCheckBox, Settings::game().mPlayerMovementIgnoresAnimation); const bool wantDistantLand = distantLandCheckBox->checkState() == Qt::Checked; if (wantDistantLand != (Settings::terrain().mDistantTerrain && Settings::terrain().mObjectPaging)) diff --git a/files/ui/settingspage.ui b/files/ui/settingspage.ui index c61b4f4229..45fbc11a12 100644 --- a/files/ui/settingspage.ui +++ b/files/ui/settingspage.ui @@ -14,7 +14,7 @@ - 0 + 1 @@ -293,8 +293,8 @@ 0 0 - 680 - 882 + 671 + 774 @@ -377,6 +377,16 @@ + + + + <html><head/><body><p>In third person, the camera will bob along with the movement animations of the player. Enabling this option disables this by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> + + + Player movement ignores animation + + + From 1e06d74f821a7f3a7cbe4c3e85bbe8a04fd34621 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Sat, 28 Oct 2023 15:14:00 +0200 Subject: [PATCH 24/71] Fix #7621 --- apps/openmw/mwrender/renderingmanager.cpp | 5 +++-- components/terrain/quadtreeworld.cpp | 5 +++-- components/terrain/quadtreeworld.hpp | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index 3b6716ce2c..67b166e1f5 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -1319,6 +1319,7 @@ namespace MWRender const float lodFactor = Settings::terrain().mLodFactor; const bool groundcover = Settings::groundcover().mEnabled; const bool distantTerrain = Settings::terrain().mDistantTerrain; + const double expiryDelay = Settings::cells().mCacheExpiryDelay; if (distantTerrain || groundcover) { const int compMapResolution = Settings::terrain().mCompositeMapResolution; @@ -1329,7 +1330,7 @@ namespace MWRender const bool debugChunks = Settings::terrain().mDebugChunks; auto quadTreeWorld = std::make_unique(mSceneRoot, mRootNode, mResourceSystem, mTerrainStorage.get(), Mask_Terrain, Mask_PreCompile, Mask_Debug, compMapResolution, compMapLevel, - lodFactor, vertexLodMod, maxCompGeometrySize, debugChunks, worldspace); + lodFactor, vertexLodMod, maxCompGeometrySize, debugChunks, worldspace, expiryDelay); if (Settings::terrain().mObjectPaging) { newChunkMgr.mObjectPaging @@ -1351,7 +1352,7 @@ namespace MWRender } else newChunkMgr.mTerrain = std::make_unique(mSceneRoot, mRootNode, mResourceSystem, - mTerrainStorage.get(), Mask_Terrain, worldspace, Mask_PreCompile, Mask_Debug); + mTerrainStorage.get(), Mask_Terrain, worldspace, expiryDelay, Mask_PreCompile, Mask_Debug); newChunkMgr.mTerrain->setTargetFrameRate(Settings::cells().mTargetFramerate); float distanceMult = std::cos(osg::DegreesToRadians(std::min(mFieldOfView, 140.f)) / 2.f); diff --git a/components/terrain/quadtreeworld.cpp b/components/terrain/quadtreeworld.cpp index 52edd96b9f..bdf9f56790 100644 --- a/components/terrain/quadtreeworld.cpp +++ b/components/terrain/quadtreeworld.cpp @@ -280,8 +280,9 @@ namespace Terrain QuadTreeWorld::QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float compMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize, - bool debugChunks, ESM::RefId worldspace) - : TerrainGrid(parent, compileRoot, resourceSystem, storage, nodeMask, worldspace, preCompileMask, borderMask) + bool debugChunks, ESM::RefId worldspace, double expiryDelay) + : TerrainGrid( + parent, compileRoot, resourceSystem, storage, nodeMask, worldspace, expiryDelay, preCompileMask, borderMask) , mViewDataMap(new ViewDataMap) , mQuadTreeBuilt(false) , mLodFactor(lodFactor) diff --git a/components/terrain/quadtreeworld.hpp b/components/terrain/quadtreeworld.hpp index 2524dc046f..fa800d2655 100644 --- a/components/terrain/quadtreeworld.hpp +++ b/components/terrain/quadtreeworld.hpp @@ -33,7 +33,7 @@ namespace Terrain QuadTreeWorld(osg::Group* parent, osg::Group* compileRoot, Resource::ResourceSystem* resourceSystem, Storage* storage, unsigned int nodeMask, unsigned int preCompileMask, unsigned int borderMask, int compMapResolution, float comMapLevel, float lodFactor, int vertexLodMod, float maxCompGeometrySize, - bool debugChunks, ESM::RefId worldspace); + bool debugChunks, ESM::RefId worldspace, double expiryDelay); ~QuadTreeWorld(); From fdba857464d1bc0232da1b5587b1a32bc4c8076e Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Fri, 3 Nov 2023 01:21:45 +0100 Subject: [PATCH 25/71] Document the "player movement ignores animation" setting. --- docs/source/reference/modding/settings/game.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index c88ae4e28f..215e060b1c 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -517,3 +517,13 @@ will not be useful with another. * 0: Axis-aligned bounding box * 1: Rotating box * 2: Cylinder + +player movement ignores animation +-------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +In third person, the camera will sway along with the movement animations of the player. +Enabling this option disables this swaying by having the player character move independently of its animation. From 876f6ea2da2adeee30b58ef4914ecf5fcb051593 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 28 Oct 2023 01:27:29 +0300 Subject: [PATCH 26/71] Validate enchantment records (bug #7654) Clean up spell validation Fix a flaw in spell effect tooltip code --- CHANGELOG.md | 1 + apps/openmw/mwgui/widgets.cpp | 4 +- apps/openmw/mwworld/esmstore.cpp | 122 ++++++++++++++++--------------- 3 files changed, 66 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09cde80181..f1301bd5f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -90,6 +90,7 @@ Bug #7639: NPCs don't use hand-to-hand if their other melee skills were damaged during combat Bug #7642: Items in repair and recharge menus aren't sorted alphabetically Bug #7647: NPC walk cycle bugs after greeting player + Bug #7654: Tooltips for enchantments with invalid effects cause crashes Bug #7660: Some inconsistencies regarding Invisibility breaking Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index debacfbed9..31e2689485 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -355,12 +355,10 @@ namespace MWGui::Widgets const MWWorld::ESMStore& store = *MWBase::Environment::get().getESMStore(); - const ESM::MagicEffect* magicEffect = store.get().search(mEffectParams.mEffectID); + const ESM::MagicEffect* magicEffect = store.get().find(mEffectParams.mEffectID); const ESM::Attribute* attribute = store.get().search(mEffectParams.mAttribute); const ESM::Skill* skill = store.get().search(mEffectParams.mSkill); - assert(magicEffect); - auto windowManager = MWBase::Environment::get().getWindowManager(); std::string_view pt = windowManager->getGameSettingString("spoint", {}); diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 12fbc0d4df..0b661b4442 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -138,6 +138,59 @@ namespace return npcsToReplace; } + template + std::vector getSpellsToReplace( + const MWWorld::Store& spells, const MWWorld::Store& magicEffects) + { + std::vector spellsToReplace; + + for (RecordType spell : spells) + { + if (spell.mEffects.mList.empty()) + continue; + + bool changed = false; + auto iter = spell.mEffects.mList.begin(); + while (iter != spell.mEffects.mList.end()) + { + const ESM::MagicEffect* mgef = magicEffects.search(iter->mEffectID); + if (!mgef) + { + Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId + << ": dropping invalid effect (index " << iter->mEffectID << ")"; + iter = spell.mEffects.mList.erase(iter); + changed = true; + continue; + } + + if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute) && iter->mAttribute != -1) + { + iter->mAttribute = -1; + Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId + << ": dropping unexpected attribute argument of " + << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect"; + changed = true; + } + + if (!(mgef->mData.mFlags & ESM::MagicEffect::TargetSkill) && iter->mSkill != -1) + { + iter->mSkill = -1; + Log(Debug::Verbose) << RecordType::getRecordType() << " " << spell.mId + << ": dropping unexpected skill argument of " + << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect"; + changed = true; + } + + ++iter; + } + + if (changed) + spellsToReplace.emplace_back(spell); + } + + return spellsToReplace; + } + // Custom enchanted items can reference scripts that no longer exist, this doesn't necessarily mean the base item no // longer exists however. So instead of removing the item altogether, we're only removing the script. template @@ -538,71 +591,24 @@ namespace MWWorld removeMissingScripts(getWritable(), getWritable().mStatic); - // Validate spell effects for invalid arguments - std::vector spellsToReplace; + // Validate spell effects and enchantments for invalid arguments auto& spells = getWritable(); - for (ESM::Spell spell : spells) - { - if (spell.mEffects.mList.empty()) - continue; - - bool changed = false; - auto iter = spell.mEffects.mList.begin(); - while (iter != spell.mEffects.mList.end()) - { - const ESM::MagicEffect* mgef = getWritable().search(iter->mEffectID); - if (!mgef) - { - Log(Debug::Verbose) << "Spell '" << spell.mId << "' has an invalid effect (index " - << iter->mEffectID << ") present. Dropping the effect."; - iter = spell.mEffects.mList.erase(iter); - changed = true; - continue; - } - - if (mgef->mData.mFlags & ESM::MagicEffect::TargetSkill) - { - if (iter->mAttribute != -1) - { - iter->mAttribute = -1; - Log(Debug::Verbose) - << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect of spell '" << spell.mId - << "' has an attribute argument present. Dropping the argument."; - changed = true; - } - } - else if (mgef->mData.mFlags & ESM::MagicEffect::TargetAttribute) - { - if (iter->mSkill != -1) - { - iter->mSkill = -1; - Log(Debug::Verbose) - << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect of spell '" << spell.mId - << "' has a skill argument present. Dropping the argument."; - changed = true; - } - } - else if (iter->mSkill != -1 || iter->mAttribute != -1) - { - iter->mSkill = -1; - iter->mAttribute = -1; - Log(Debug::Verbose) << ESM::MagicEffect::indexToGmstString(iter->mEffectID) << " effect of spell '" - << spell.mId << "' has argument(s) present. Dropping the argument(s)."; - changed = true; - } - - ++iter; - } - - if (changed) - spellsToReplace.emplace_back(spell); - } + auto& enchantments = getWritable(); + auto& magicEffects = getWritable(); + std::vector spellsToReplace = getSpellsToReplace(spells, magicEffects); for (const ESM::Spell& spell : spellsToReplace) { spells.eraseStatic(spell.mId); spells.insertStatic(spell); } + + std::vector enchantmentsToReplace = getSpellsToReplace(enchantments, magicEffects); + for (const ESM::Enchantment& enchantment : enchantmentsToReplace) + { + enchantments.eraseStatic(enchantment.mId); + enchantments.insertStatic(enchantment); + } } void ESMStore::movePlayerRecord() From af08205f190adfe1ce05163ec0a98116a6a01bb8 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 3 Nov 2023 15:03:09 +0300 Subject: [PATCH 27/71] Support BSShader/BSLightingShader depth flags --- .../nifosg/testnifloader.cpp | 10 ++++++++ components/nif/property.hpp | 8 +++++++ components/nifosg/nifloader.cpp | 23 ++++++++++++------- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/apps/openmw_test_suite/nifosg/testnifloader.cpp b/apps/openmw_test_suite/nifosg/testnifloader.cpp index a82fba15ca..5c37cb375f 100644 --- a/apps/openmw_test_suite/nifosg/testnifloader.cpp +++ b/apps/openmw_test_suite/nifosg/testnifloader.cpp @@ -156,6 +156,16 @@ osg::Group { StateSet TRUE { osg::StateSet { UniqueID 9 + ModeList 1 { + GL_DEPTH_TEST ON + } + AttributeList 1 { + osg::Depth { + UniqueID 10 + WriteMask FALSE + } + Value OFF + } } } } diff --git a/components/nif/property.hpp b/components/nif/property.hpp index da908f2eab..eaaa972c39 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -109,6 +109,12 @@ namespace Nif { BSSFlag1_Specular = 0x00000001, BSSFlag1_Decal = 0x04000000, + BSSFlag1_DepthTest = 0x80000000, + }; + + enum BSShaderFlags2 + { + BSSFlag2_DepthWrite = 0x00000001, }; struct BSSPParallaxParams @@ -140,6 +146,8 @@ namespace Nif // Shader-specific flag methods must be handled on per-record basis bool specular() const { return mShaderFlags1 & BSSFlag1_Specular; } bool decal() const { return mShaderFlags1 & BSSFlag1_Decal; } + bool depthTest() const { return mShaderFlags1 & BSSFlag1_DepthTest; } + bool depthWrite() const { return mShaderFlags2 & BSSFlag2_DepthWrite; } }; struct BSShaderLightingProperty : BSShaderProperty diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 9945a8c40e..5b98f54599 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1964,6 +1964,15 @@ namespace NifOsg return texEnv; } + void handleDepthFlags(osg::StateSet* stateset, bool depthTest, bool depthWrite, + osg::Depth::Function depthFunction = osg::Depth::LESS) + { + stateset->setMode(GL_DEPTH_TEST, depthTest ? osg::StateAttribute::ON : osg::StateAttribute::OFF); + osg::ref_ptr depth = new osg::Depth(depthFunction, 0.0, 1.0, depthWrite); + depth = shareAttribute(depth); + stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + } + void handleTextureProperty(const Nif::NiTexturingProperty* texprop, const std::string& nodeName, osg::StateSet* stateset, SceneUtil::CompositeStateSetUpdater* composite, Resource::ImageManager* imageManager, std::vector& boundTextures, int animflags) @@ -2319,16 +2328,10 @@ namespace NifOsg { const Nif::NiZBufferProperty* zprop = static_cast(property); osg::StateSet* stateset = node->getOrCreateStateSet(); - stateset->setMode( - GL_DEPTH_TEST, zprop->depthTest() ? osg::StateAttribute::ON : osg::StateAttribute::OFF); - osg::ref_ptr depth = new osg::Depth; - depth->setWriteMask(zprop->depthWrite()); // Morrowind ignores depth test function, unless a NiStencilProperty is present, in which case it // uses a fixed depth function of GL_ALWAYS. - if (hasStencilProperty) - depth->setFunction(osg::Depth::ALWAYS); - depth = shareAttribute(depth); - stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); + osg::Depth::Function depthFunction = hasStencilProperty ? osg::Depth::ALWAYS : osg::Depth::LESS; + handleDepthFlags(stateset, zprop->depthTest(), zprop->depthWrite(), depthFunction); break; } // OSG groups the material properties that NIFs have separate, so we have to parse them all again when @@ -2367,6 +2370,7 @@ namespace NifOsg textureSet, texprop->mClamp, node->getName(), stateset, imageManager, boundTextures); } handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); break; } case Nif::RC_BSShaderNoLightingProperty: @@ -2405,6 +2409,7 @@ namespace NifOsg } stateset->addUniform(new osg::Uniform("useFalloff", useFalloff)); handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); break; } case Nif::RC_BSLightingShaderProperty: @@ -2422,6 +2427,7 @@ namespace NifOsg stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); if (texprop->treeAnim()) stateset->addUniform(new osg::Uniform("useTreeAnim", true)); + handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); break; } case Nif::RC_BSEffectShaderProperty: @@ -2477,6 +2483,7 @@ namespace NifOsg handleTextureControllers(texprop, composite, imageManager, stateset, animflags); if (texprop->doubleSided()) stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); break; } // unused by mw From e51d1967f4297e71b8762505dd190c806f0e8994 Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Fri, 3 Nov 2023 17:24:35 +0100 Subject: [PATCH 28/71] Base cell size on worldspace --- components/terrain/cellborder.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/components/terrain/cellborder.cpp b/components/terrain/cellborder.cpp index 06767531d3..b78691cd8d 100644 --- a/components/terrain/cellborder.cpp +++ b/components/terrain/cellborder.cpp @@ -6,6 +6,7 @@ #include "world.hpp" +#include #include #include #include @@ -25,7 +26,7 @@ namespace Terrain osg::ref_ptr CellBorder::createBorderGeometry(float x, float y, float size, Terrain::Storage* terrain, Resource::SceneManager* sceneManager, int mask, ESM::RefId worldspace, float offset, osg::Vec4f color) { - const int cellSize = ESM::Land::REAL_SIZE; + const int cellSize = ESM::getCellSize(worldspace); const int borderSegments = 40; osg::Vec3 cellCorner = osg::Vec3(x * cellSize, y * cellSize, 0); From dd87d01f060cf3ff6d2f466d1910453cb03722e2 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 3 Nov 2023 16:31:23 +0000 Subject: [PATCH 29/71] Fix minor doc error, throw error when attempting to assign a value to a non-existing global variable in lua --- apps/openmw/mwlua/mwscriptbindings.cpp | 2 +- files/lua_api/openmw/types.lua | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/openmw/mwlua/mwscriptbindings.cpp b/apps/openmw/mwlua/mwscriptbindings.cpp index 92957efb71..af88249d3e 100644 --- a/apps/openmw/mwlua/mwscriptbindings.cpp +++ b/apps/openmw/mwlua/mwscriptbindings.cpp @@ -140,7 +140,7 @@ namespace MWLua = sol::overload([](const GlobalStore& store, std::string_view globalId, float val) { auto g = store.search(ESM::RefId::deserializeText(globalId)); if (g == nullptr) - return; + throw std::runtime_error("No variable \"" + std::string(globalId) + "\" in GlobalStore"); char varType = MWBase::Environment::get().getWorld()->getGlobalVariableType(globalId); if (varType == 's' || varType == 'l') { diff --git a/files/lua_api/openmw/types.lua b/files/lua_api/openmw/types.lua index 69ce5fbaf2..9282fbffcc 100644 --- a/files/lua_api/openmw/types.lua +++ b/files/lua_api/openmw/types.lua @@ -930,7 +930,6 @@ -- Whether teleportation for this player is enabled. -- @function [parent=#Player] isTeleportingEnabled -- @param openmw.core#GameObject player --- @param #boolean player -- @return #boolean --- From 9a5fa9b8d6f6c59e57b733e7d81245e56801d6a9 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Fri, 3 Nov 2023 12:59:17 -0700 Subject: [PATCH 30/71] fix persistent buffers and glsl_version --- apps/openmw/mwrender/pingpongcanvas.cpp | 25 ++++ apps/openmw/mwrender/pingpongcanvas.hpp | 3 + apps/openmw/mwrender/postprocessor.cpp | 55 +++----- apps/openmw/mwrender/postprocessor.hpp | 2 +- components/fx/pass.cpp | 2 +- components/fx/technique.cpp | 1 + components/fx/technique.hpp | 4 + components/fx/types.hpp | 10 ++ .../source/reference/postprocessing/omwfx.rst | 128 +++++++++++++++--- 9 files changed, 172 insertions(+), 58 deletions(-) diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp index 5ac68acf5f..6782bdddfd 100644 --- a/apps/openmw/mwrender/pingpongcanvas.cpp +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -238,10 +238,35 @@ namespace MWRender if (pass.mRenderTarget) { + if (mDirtyAttachments) + { + const auto [w, h] + = pass.mSize.get(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight()); + + pass.mRenderTexture->setTextureSize(w, h); + if (pass.mMipMap) + pass.mRenderTexture->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h)); + pass.mRenderTexture->dirtyTextureObject(); + + // Custom render targets must be shared between frame ids, so it's impossible to double buffer + // without expensive copies. That means the only thread-safe place to resize is in the draw + // thread. + osg::Texture2D* texture = const_cast(dynamic_cast( + pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0) + .getTexture())); + + texture->setTextureSize(w, h); + texture->setNumMipmapLevels(pass.mRenderTexture->getNumMipmapLevels()); + texture->dirtyTextureObject(); + + mDirtyAttachments = false; + } + pass.mRenderTarget->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); if (pass.mRenderTexture->getNumMipmapLevels() > 0) { + state.setActiveTextureUnit(0); state.applyTextureAttribute(0, pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0) diff --git a/apps/openmw/mwrender/pingpongcanvas.hpp b/apps/openmw/mwrender/pingpongcanvas.hpp index d8758303d7..557813b816 100644 --- a/apps/openmw/mwrender/pingpongcanvas.hpp +++ b/apps/openmw/mwrender/pingpongcanvas.hpp @@ -30,6 +30,8 @@ namespace MWRender void dirty() { mDirty = true; } + void resizeRenderTargets() { mDirtyAttachments = true; } + const fx::DispatchArray& getPasses() { return mPasses; } void setPasses(fx::DispatchArray&& passes); @@ -65,6 +67,7 @@ namespace MWRender osg::ref_ptr mTextureNormals; mutable bool mDirty = false; + mutable bool mDirtyAttachments = false; mutable osg::ref_ptr mRenderViewport; mutable osg::ref_ptr mMultiviewResolveFramebuffer; mutable osg::ref_ptr mDestinationFBO; diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index c3106802db..66ade9495c 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -211,25 +211,14 @@ namespace MWRender if (Stereo::getStereo()) Stereo::Manager::instance().screenResolutionChanged(); - auto width = renderWidth(); - auto height = renderHeight(); - for (auto& technique : mTechniques) - { - for (auto& [name, rt] : technique->getRenderTargetsMap()) - { - const auto [w, h] = rt.mSize.get(width, height); - rt.mTarget->setTextureSize(w, h); - } - } - size_t frameId = frame() % 2; createObjectsForFrame(frameId); mRendering.updateProjectionMatrix(); - mRendering.setScreenRes(width, height); + mRendering.setScreenRes(renderWidth(), renderHeight()); - dirtyTechniques(); + dirtyTechniques(true); mDirty = true; mDirtyFrameId = !frameId; @@ -534,7 +523,7 @@ namespace MWRender mCanvases[frameId]->dirty(); } - void PostProcessor::dirtyTechniques() + void PostProcessor::dirtyTechniques(bool dirtyAttachments) { size_t frameId = frame() % 2; @@ -613,8 +602,6 @@ namespace MWRender uniform->mName.c_str(), *type, uniform->getNumElements())); } - std::unordered_map renderTargetCache; - for (const auto& pass : technique->getPasses()) { int subTexUnit = texUnit; @@ -626,32 +613,27 @@ namespace MWRender if (!pass->getTarget().empty()) { - const auto& rt = technique->getRenderTargetsMap()[pass->getTarget()]; - - const auto [w, h] = rt.mSize.get(renderWidth(), renderHeight()); - - subPass.mRenderTexture = new osg::Texture2D(*rt.mTarget); - renderTargetCache[rt.mTarget] = subPass.mRenderTexture; - subPass.mRenderTexture->setTextureSize(w, h); - subPass.mRenderTexture->setName(std::string(pass->getTarget())); - - if (rt.mMipMap) - subPass.mRenderTexture->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h)); + const auto& renderTarget = technique->getRenderTargetsMap()[pass->getTarget()]; + subPass.mSize = renderTarget.mSize; + subPass.mRenderTexture = renderTarget.mTarget; + subPass.mMipMap = renderTarget.mMipMap; + subPass.mStateSet->setAttributeAndModes(new osg::Viewport( + 0, 0, subPass.mRenderTexture->getTextureWidth(), subPass.mRenderTexture->getTextureHeight())); subPass.mRenderTarget = new osg::FrameBufferObject; subPass.mRenderTarget->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(subPass.mRenderTexture)); + + const auto [w, h] = renderTarget.mSize.get(renderWidth(), renderHeight()); subPass.mStateSet->setAttributeAndModes(new osg::Viewport(0, 0, w, h)); } - for (const auto& whitelist : pass->getRenderTargets()) + for (const auto& name : pass->getRenderTargets()) { - auto it = technique->getRenderTargetsMap().find(whitelist); - if (it != technique->getRenderTargetsMap().end() && renderTargetCache[it->second.mTarget]) - { - subPass.mStateSet->setTextureAttribute(subTexUnit, renderTargetCache[it->second.mTarget]); - subPass.mStateSet->addUniform(new osg::Uniform(std::string(it->first).c_str(), subTexUnit++)); - } + subPass.mStateSet->setTextureAttribute(subTexUnit, technique->getRenderTargetsMap()[name].mTarget); + subPass.mStateSet->addUniform(new osg::Uniform(name.c_str(), subTexUnit)); + + subTexUnit++; } node.mPasses.emplace_back(std::move(subPass)); @@ -668,6 +650,9 @@ namespace MWRender hud->updateTechniques(); mRendering.getSkyManager()->setSunglare(sunglare); + + if (dirtyAttachments) + mCanvases[frameId]->resizeRenderTargets(); } PostProcessor::Status PostProcessor::enableTechnique( @@ -774,7 +759,7 @@ namespace MWRender for (auto& technique : mTemplates) technique->compile(); - dirtyTechniques(); + dirtyTechniques(true); } void PostProcessor::disableDynamicShaders() diff --git a/apps/openmw/mwrender/postprocessor.hpp b/apps/openmw/mwrender/postprocessor.hpp index 153ec8166b..e9f19bf6b5 100644 --- a/apps/openmw/mwrender/postprocessor.hpp +++ b/apps/openmw/mwrender/postprocessor.hpp @@ -204,7 +204,7 @@ namespace MWRender void createObjectsForFrame(size_t frameId); - void dirtyTechniques(); + void dirtyTechniques(bool dirtyAttachments = false); void update(size_t frameId); diff --git a/components/fx/pass.cpp b/components/fx/pass.cpp index 7a7329d755..4b91939e01 100644 --- a/components/fx/pass.cpp +++ b/components/fx/pass.cpp @@ -339,7 +339,7 @@ float omw_EstimateFogCoverageFromUV(vec2 uv) if (mCompiled) return; - mLegacyGLSL = technique.getGLSLVersion() != 330; + mLegacyGLSL = technique.getGLSLVersion() < 330; if (mType == Type::Pixel) { diff --git a/components/fx/technique.cpp b/components/fx/technique.cpp index 0b5d784ad9..3abcd8c0ba 100644 --- a/components/fx/technique.cpp +++ b/components/fx/technique.cpp @@ -279,6 +279,7 @@ namespace fx rt.mTarget->setSourceType(GL_UNSIGNED_BYTE); rt.mTarget->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE); rt.mTarget->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE); + rt.mTarget->setName(std::string(mBlockName)); while (!isNext() && !isNext()) { diff --git a/components/fx/technique.hpp b/components/fx/technique.hpp index 844e4b552a..ed356e0a37 100644 --- a/components/fx/technique.hpp +++ b/components/fx/technique.hpp @@ -54,10 +54,14 @@ namespace fx osg::ref_ptr mRenderTarget; osg::ref_ptr mRenderTexture; bool mResolve = false; + Types::SizeProxy mSize; + bool mMipMap; SubPass(const SubPass& other, const osg::CopyOp& copyOp = osg::CopyOp::SHALLOW_COPY) : mStateSet(new osg::StateSet(*other.mStateSet, copyOp)) , mResolve(other.mResolve) + , mSize(other.mSize) + , mMipMap(other.mMipMap) { if (other.mRenderTarget) mRenderTarget = new osg::FrameBufferObject(*other.mRenderTarget, copyOp); diff --git a/components/fx/types.hpp b/components/fx/types.hpp index 09f191c61c..0683126dec 100644 --- a/components/fx/types.hpp +++ b/components/fx/types.hpp @@ -29,6 +29,16 @@ namespace fx std::optional mWidth; std::optional mHeight; + SizeProxy() = default; + + SizeProxy(const SizeProxy& other) + : mWidthRatio(other.mWidthRatio) + , mHeightRatio(other.mHeightRatio) + , mWidth(other.mWidth) + , mHeight(other.mHeight) + { + } + std::tuple get(int width, int height) const { int scaledWidth = width; diff --git a/docs/source/reference/postprocessing/omwfx.rst b/docs/source/reference/postprocessing/omwfx.rst index 36a6f0883a..2fb21a5162 100644 --- a/docs/source/reference/postprocessing/omwfx.rst +++ b/docs/source/reference/postprocessing/omwfx.rst @@ -529,48 +529,134 @@ is not wanted and you want a custom render target. | mipmaps | boolean | Whether mipmaps should be generated every frame | +------------------+---------------------+-----------------------------------------------------------------------------+ -To use the render target a pass must be assigned to it, along with any optional clear or blend modes. +To use the render target a pass must be assigned to it, along with any optional blend modes. +As a restriction, only three render targets can be bound per pass with ``rt1``, ``rt2``, ``rt3``, respectively. -In the code snippet below a rendertarget is used to draw the red channel of a scene at half resolution, then a quarter. As a restriction, -only three render targets can be bound per pass with ``rt1``, ``rt2``, ``rt3``, respectively. +Blending modes can be useful at times. Below is a simple shader which, when activated, will slowly turn the screen pure red. +Notice how we only ever write the value `.01` to the `RT_Red` buffer. Since we're using appropriate blending modes the +color buffer will accumulate. .. code-block:: none - render_target RT_Downsample { - width_ratio = 0.5; - height_ratio = 0.5; - internal_format = r16f; + render_target RT_Red { + width = 4; + height = 4; + source_format = rgb; + internal_format = rgb16f; source_type = float; - source_format = red; } - render_target RT_Downsample4 { - width_ratio = 0.25; - height_ratio = 0.25; - } - - fragment downsample2x(target=RT_Downsample) { - + fragment red(target=RT_Red,blend=(add, src_color, one), rt1=RT_Red) { omw_In vec2 omw_TexCoord; void main() { - omw_FragColor.r = omw_GetLastShader(omw_TexCoord).r; + omw_FragColor.rgb = vec3(0.01,0,0); } } - fragment downsample4x(target=RT_Downsample4, rt1=RT_Downsample) { - + fragment view(rt1=RT_Red) { omw_In vec2 omw_TexCoord; void main() { - omw_FragColor = omw_Texture2D(RT_Downsample, omw_TexCoord); + omw_FragColor = omw_Texture2D(RT_Red, omw_TexCoord); } } -Now, when the `downsample2x` pass runs it will write to the target buffer instead of the default -one assigned by the engine. + technique { + author = "OpenMW"; + passes = red, view; + } + + +These custom render targets are persistent and ownership is given to the shader which defines them. +This gives potential to implement temporal effects by storing previous frame data in these buffers. +Below is an example which calculates a naive average scene luminance and transitions between values smoothly. + +.. code-block:: none + + render_target RT_Lum { + width = 256; + height = 256; + mipmaps = true; + source_format = rgb; + internal_format = rgb16f; + source_type = float; + min_filter = linear_mipmap_linear; + mag_filter = linear; + } + + render_target RT_LumAvg { + source_type = float; + source_format = rgb; + internal_format = rgb16f; + min_filter = nearest; + mag_filter = nearest; + } + + render_target RT_LumAvgLastFrame { + source_type = float; + source_format = rgb; + internal_format = rgb16f; + min_filter = nearest; + mag_filter = nearest; + } + + fragment calculateLum(target=RT_Lum) { + omw_In vec2 omw_TexCoord; + + void main() + { + vec3 orgi = pow(omw_GetLastShader(omw_TexCoord), vec4(2.2)).rgb; + omw_FragColor.rgb = orgi; + } + } + + fragment fetchLumAvg(target=RT_LumAvg, rt1=RT_Lum, rt2=RT_LumAvgLastFrame) { + omw_In vec2 omw_TexCoord; + + void main() + { + vec3 avgLumaCurrFrame = textureLod(RT_Lum, vec2(0.5, 0.5), 6).rgb; + vec3 avgLumaLastFrame = omw_Texture2D(RT_LumAvgLastFrame, vec2(0.5, 0.5)).rgb; + + const float speed = 0.9; + + vec3 avgLuma = avgLumaLastFrame + (avgLumaCurrFrame - avgLumaLastFrame) * (1.0 - exp(-omw.deltaSimulationTime * speed)); + + omw_FragColor.rgb = avgLuma; + } + } + + fragment adaptation(rt1=RT_LumAvg) { + omw_In vec2 omw_TexCoord; + + void main() + { + vec3 avgLuma = omw_Texture2D(RT_LumAvg, vec2(0.5, 0.5)).rgb; + + if (omw_TexCoord.y < 0.2) + omw_FragColor = vec4(avgLuma, 1.0); + else + omw_FragColor = omw_GetLastShader(omw_TexCoord); + } + } + + fragment store(target=RT_LumAvgLastFrame, rt1=RT_LumAvg) { + void main() + { + vec3 avgLuma = omw_Texture2D(RT_LumAvg, vec2(0.5, 0.5)).rgb; + omw_FragColor.rgb = avgLuma; + } + } + + technique { + author = "OpenMW"; + passes = calculateLum, fetchLumAvg, store, adaptation; + glsl_version = 330; + } + Simple Example ############## From 7a0d1a08689b681bb725ed1045682758a89e5eeb Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 3 Nov 2023 21:23:37 +0100 Subject: [PATCH 31/71] Print uint8_t as unsigned --- apps/esmtool/record.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index e293055919..96c418c0c4 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -1204,7 +1204,8 @@ namespace EsmTool std::array weathers = { "Clear", "Cloudy", "Fog", "Overcast", "Rain", "Thunder", "Ash", "Blight", "Snow", "Blizzard" }; for (size_t i = 0; i < weathers.size(); ++i) - std::cout << " " << weathers[i] << ": " << mData.mData.mProbabilities[i] << std::endl; + std::cout << " " << weathers[i] << ": " << static_cast(mData.mData.mProbabilities[i]) + << std::endl; std::cout << " Map Color: " << mData.mMapColor << std::endl; if (!mData.mSleepList.empty()) std::cout << " Sleep List: " << mData.mSleepList << std::endl; From 8d0d9a49c60b3ed9adea454e4c778073bdd6d039 Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Fri, 3 Nov 2023 20:36:14 +0000 Subject: [PATCH 32/71] Remember sneaking when game is saved and loaded(#7664) --- files/data/scripts/omw/playercontrols.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/files/data/scripts/omw/playercontrols.lua b/files/data/scripts/omw/playercontrols.lua index 0bd7cf6bea..7b405180e8 100644 --- a/files/data/scripts/omw/playercontrols.lua +++ b/files/data/scripts/omw/playercontrols.lua @@ -199,10 +199,21 @@ local function onInputAction(action) end end +local function onSave() + return {sneaking = self.controls.sneak} +end + +local function onLoad(data) + if not data then return end + self.controls.sneak = data.sneaking or false +end + return { engineHandlers = { onFrame = onFrame, onInputAction = onInputAction, + onSave = onSave, + onLoad = onLoad, }, interfaceName = 'Controls', --- From 2c1db92d04bfee28278578bffafdeec022e8132d Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Fri, 3 Nov 2023 23:25:14 +0300 Subject: [PATCH 33/71] Don't use Bounding Box node bounds as the original collision shape Bounding Box node bounds are not used for non-actor collision in Morrowind and the generated box isn't actually used for actor collision in OpenMW Preserving btBoxShape cloning code because it might get used in the future --- .../nifloader/testbulletnifloader.cpp | 26 +++---------------- components/nifbullet/bulletnifloader.cpp | 23 ++-------------- 2 files changed, 5 insertions(+), 44 deletions(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 72959f8591..58e1073307 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -411,7 +411,7 @@ namespace EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, for_root_bounding_box_should_return_shape_with_compound_shape_and_box_inside) + TEST_F(TestBulletNifLoader, for_root_bounding_box_should_return_shape_with_bounding_box_data) { mNode.mName = "Bounding Box"; mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; @@ -427,15 +427,11 @@ namespace Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); - std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); - std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); - expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, for_child_bounding_box_should_return_shape_with_compound_shape_with_box_inside) + TEST_F(TestBulletNifLoader, for_child_bounding_box_should_return_shape_with_bounding_box_data) { mNode.mName = "Bounding Box"; mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; @@ -453,15 +449,11 @@ namespace Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); - std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); - std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); - expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, for_root_with_bounds_and_child_bounding_box_but_should_use_bounding_box) + TEST_F(TestBulletNifLoader, for_root_with_bounds_and_child_bounding_box_should_use_bounding_box) { mNode.mName = "Bounding Box"; mNode.mBounds.mType = Nif::BoundingVolume::Type::BOX_BV; @@ -483,10 +475,6 @@ namespace Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); - std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); - std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); - expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } @@ -519,10 +507,6 @@ namespace Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(1, 2, 3); expected.mCollisionBox.mCenter = osg::Vec3f(-1, -2, -3); - std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); - std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); - expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } @@ -555,10 +539,6 @@ namespace Resource::BulletShape expected; expected.mCollisionBox.mExtents = osg::Vec3f(4, 5, 6); expected.mCollisionBox.mCenter = osg::Vec3f(-4, -5, -6); - std::unique_ptr box(new btBoxShape(btVector3(4, 5, 6))); - std::unique_ptr shape(new btCompoundShape); - shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-4, -5, -6)), box.release()); - expected.mCollisionShape.reset(shape.release()); EXPECT_EQ(*result, expected); } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 95744a8cfe..66c7eea12d 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include @@ -171,23 +170,8 @@ namespace NifBullet } for (const Nif::NiAVObject* node : roots) - { - // Try to find a valid bounding box first. If one's found for any root node, use that. if (findBoundingBox(*node)) - { - const btVector3 extents = Misc::Convert::toBullet(mShape->mCollisionBox.mExtents); - const btVector3 center = Misc::Convert::toBullet(mShape->mCollisionBox.mCenter); - auto compound = std::make_unique(); - auto boxShape = std::make_unique(extents); - btTransform transform = btTransform::getIdentity(); - transform.setOrigin(center); - compound->addChildShape(transform, boxShape.get()); - std::ignore = boxShape.release(); - - mShape->mCollisionShape.reset(compound.release()); - return mShape; - } - } + break; HandleNodeArgs args; @@ -196,8 +180,6 @@ namespace NifBullet // TODO: investigate whether this should and could be optimized. args.mAnimated = pathFileNameStartsWithX(mShape->mFileName); - // If there's no bounding box, we'll have to generate a Bullet collision shape - // from the collision data present in every root node. for (const Nif::NiAVObject* node : roots) handleRoot(nif, *node, args); @@ -210,8 +192,7 @@ namespace NifBullet return mShape; } - // Find a boundingBox in the node hierarchy. - // Return: use bounding box for collision? + // Find a bounding box in the node hierarchy to use for actor collision bool BulletNifLoader::findBoundingBox(const Nif::NiAVObject& node) { if (Misc::StringUtils::ciEqual(node.mName, "Bounding Box")) From 515a90e9e017db09e3482e80e2e52d37dc2e0dfb Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sat, 4 Nov 2023 02:12:15 +0300 Subject: [PATCH 34/71] Cast displayed health to int in saved game dialog (#7656) --- apps/openmw/mwgui/savegamedialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 63e4fbc5cc..4f78c27f05 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -413,8 +413,8 @@ namespace MWGui text << Misc::fileTimeToString(mCurrentSlot->mTimeStamp, "%Y.%m.%d %T") << "\n"; if (mCurrentSlot->mProfile.mMaximumHealth > 0) - text << std::fixed << std::setprecision(0) << "#{sHealth} " << mCurrentSlot->mProfile.mCurrentHealth << "/" - << mCurrentSlot->mProfile.mMaximumHealth << "\n"; + text << "#{sHealth} " << static_cast(mCurrentSlot->mProfile.mCurrentHealth) << "/" + << static_cast(mCurrentSlot->mProfile.mMaximumHealth) << "\n"; text << "#{sLevel} " << mCurrentSlot->mProfile.mPlayerLevel << "\n"; text << "#{sCell=" << mCurrentSlot->mProfile.mPlayerCellName << "}\n"; From 4886d31d89025f297285e9ce761f3a32ed610087 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 4 Nov 2023 13:37:23 +0100 Subject: [PATCH 35/71] Language, bob -> sway --- files/ui/settingspage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/ui/settingspage.ui b/files/ui/settingspage.ui index 45fbc11a12..1f5f206f67 100644 --- a/files/ui/settingspage.ui +++ b/files/ui/settingspage.ui @@ -380,7 +380,7 @@ - <html><head/><body><p>In third person, the camera will bob along with the movement animations of the player. Enabling this option disables this by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> + <html><head/><body><p>In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.</p></body></html> Player movement ignores animation From 68fe1361f1a4926e178f7f49917e06381fd153f6 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 4 Nov 2023 14:00:13 +0100 Subject: [PATCH 36/71] Attempt at clarifying the code --- apps/openmw/mwmechanics/character.cpp | 34 +++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 4eb223e250..08cb8f6ccd 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2386,26 +2386,26 @@ namespace MWMechanics } } - osg::Vec3f moved = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); + osg::Vec3f movementFromAnimation = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); if (mPtr.getClass().isActor() && isMovementAnimationControlled() && !isScriptedAnimPlaying()) { if (duration > 0.0f) - moved /= duration; + movementFromAnimation /= duration; else - moved = osg::Vec3f(0.f, 0.f, 0.f); + movementFromAnimation = osg::Vec3f(0.f, 0.f, 0.f); - moved.x() *= scale; - moved.y() *= scale; + movementFromAnimation.x() *= scale; + movementFromAnimation.y() *= scale; - if (speed > 0.f && moved != osg::Vec3f()) + if (speed > 0.f && movementFromAnimation != osg::Vec3f()) { - // Ensure we're moving in generally the right direction - // This is necessary when the "turn to movement direction" feature is off, as animations - // will not be rotated to match diagonal movement. In this case we have to slide the - // character diagonally. - - // First decide the general direction expected from the current animation + // Ensure we're moving in the right general direction. In vanilla, all horizontal movement is taken from animations, + // even when moving diagonally (which doesn't have a corresponding animation). So to acheive diagonal movement, + // we have to rotate the movement taken from the animation to the intended direction. + // + // Note that while a complete movement animation cycle will have a well defined direction, no individual frame will, and + // therefore we have to determine the direction separately from the value of the movementFromAnimation variable. float animMovementAngle = 0; if (!Settings::game().mTurnToMovementDirection || isFirstPersonPlayer) { @@ -2426,12 +2426,12 @@ namespace MWMechanics float diff = targetMovementAngle - animMovementAngle; if (std::abs(diff) > epsilon) { - moved = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * moved; + movementFromAnimation = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation; } - if (isPlayer && Settings::game().mPlayerMovementIgnoresAnimation) + if (!(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation)) { - moved = movement; + movement = movementFromAnimation; } } @@ -2445,12 +2445,12 @@ namespace MWMechanics .getModifier() > 0)) { - moved.z() = 1.0; + movement.z() = 1.0; } } // Update movement - world->queueMovement(mPtr, moved); + world->queueMovement(mPtr, movement); } mSkipAnim = false; From 1edc82062de593aefc8ea702e05a84e01075a946 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 4 Nov 2023 14:13:02 +0100 Subject: [PATCH 37/71] Account for strafing when draw state is not nothing, and "turn to movement direction" is true --- apps/openmw/mwmechanics/character.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 08cb8f6ccd..73bec3f2ae 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2407,13 +2407,8 @@ namespace MWMechanics // Note that while a complete movement animation cycle will have a well defined direction, no individual frame will, and // therefore we have to determine the direction separately from the value of the movementFromAnimation variable. float animMovementAngle = 0; - if (!Settings::game().mTurnToMovementDirection || isFirstPersonPlayer) - { - if (cls.getMovementSettings(mPtr).mIsStrafing) - animMovementAngle = movement.x() > 0 ? -osg::PI_2f : osg::PI_2f; - else - animMovementAngle = movement.y() >= 0 ? 0 : -osg::PIf; - } + if (cls.getMovementSettings(mPtr).mIsStrafing) + animMovementAngle = movement.x() > 0 ? -osg::PI_2f : osg::PI_2f; else { animMovementAngle = mAnimation->getLegsYawRadians(); From 475bb1af659dad998fbe81d32665259f84fd0a09 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 4 Nov 2023 14:34:41 +0100 Subject: [PATCH 38/71] Move calculating the animation direction into its own function to help simplify update(). Eliminate a pointless epsilon. --- apps/openmw/mwmechanics/character.cpp | 60 +++++++++++++++++---------- apps/openmw/mwmechanics/character.hpp | 2 + 2 files changed, 40 insertions(+), 22 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 73bec3f2ae..6c898ee23e 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2398,36 +2398,19 @@ namespace MWMechanics movementFromAnimation.x() *= scale; movementFromAnimation.y() *= scale; - if (speed > 0.f && movementFromAnimation != osg::Vec3f()) + if (speed > 0.f && movementFromAnimation != osg::Vec3f() + && !(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation)) { // Ensure we're moving in the right general direction. In vanilla, all horizontal movement is taken from animations, // even when moving diagonally (which doesn't have a corresponding animation). So to acheive diagonal movement, // we have to rotate the movement taken from the animation to the intended direction. // // Note that while a complete movement animation cycle will have a well defined direction, no individual frame will, and - // therefore we have to determine the direction separately from the value of the movementFromAnimation variable. - float animMovementAngle = 0; - if (cls.getMovementSettings(mPtr).mIsStrafing) - animMovementAngle = movement.x() > 0 ? -osg::PI_2f : osg::PI_2f; - else - { - animMovementAngle = mAnimation->getLegsYawRadians(); - if (movement.y() < 0) - animMovementAngle -= osg::PIf; - } - - const float epsilon = 0.001f; + // therefore we have to determine the direction based on the currently playing cycle instead. + float animMovementAngle = getAnimationMovementDirection(); float targetMovementAngle = std::atan2(-movement.x(), movement.y()); float diff = targetMovementAngle - animMovementAngle; - if (std::abs(diff) > epsilon) - { - movementFromAnimation = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation; - } - - if (!(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation)) - { - movement = movementFromAnimation; - } + movement = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation; } if (mFloatToSurface) @@ -2927,6 +2910,39 @@ namespace MWMechanics MWBase::Environment::get().getSoundManager()->playSound3D(mPtr, *soundId, volume, pitch); } + float CharacterController::getAnimationMovementDirection() + { + switch (mMovementState) + { + case CharState_RunLeft: + case CharState_SneakLeft: + case CharState_SwimWalkLeft: + case CharState_SwimRunLeft: + case CharState_WalkLeft: + return osg::PI_2f; + case CharState_RunRight: + case CharState_SneakRight: + case CharState_SwimWalkRight: + case CharState_SwimRunRight: + case CharState_WalkRight: + return -osg::PI_2f; + case CharState_RunForward: + case CharState_SneakForward: + case CharState_SwimRunForward: + case CharState_SwimWalkForward: + case CharState_WalkForward: + return mAnimation->getLegsYawRadians(); + case CharState_RunBack: + case CharState_SneakBack: + case CharState_SwimWalkBack: + case CharState_SwimRunBack: + case CharState_WalkBack: + return mAnimation->getLegsYawRadians() - osg::PIf; + + } + return 0.0f; + } + void CharacterController::updateHeadTracking(float duration) { const osg::Node* head = mAnimation->getNode("Bip01 Head"); diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index c3d45fe0fb..d92c0ffe5a 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -319,6 +319,8 @@ namespace MWMechanics void playSwishSound() const; + float getAnimationMovementDirection(); + MWWorld::MovementDirectionFlags getSupportedMovementDirections() const; }; } From c7c3a52e6a7e6afb20efa57bb81623d24bd62425 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 4 Nov 2023 14:41:08 +0100 Subject: [PATCH 39/71] Clang --- apps/openmw/mwmechanics/character.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 6c898ee23e..c245a17155 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2386,7 +2386,8 @@ namespace MWMechanics } } - osg::Vec3f movementFromAnimation = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); + osg::Vec3f movementFromAnimation + = mAnimation->runAnimation(mSkipAnim && !isScriptedAnimPlaying() ? 0.f : duration); if (mPtr.getClass().isActor() && isMovementAnimationControlled() && !isScriptedAnimPlaying()) { @@ -2401,12 +2402,14 @@ namespace MWMechanics if (speed > 0.f && movementFromAnimation != osg::Vec3f() && !(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation)) { - // Ensure we're moving in the right general direction. In vanilla, all horizontal movement is taken from animations, - // even when moving diagonally (which doesn't have a corresponding animation). So to acheive diagonal movement, - // we have to rotate the movement taken from the animation to the intended direction. - // - // Note that while a complete movement animation cycle will have a well defined direction, no individual frame will, and - // therefore we have to determine the direction based on the currently playing cycle instead. + // Ensure we're moving in the right general direction. In vanilla, all horizontal movement is taken from + // animations, even when moving diagonally (which doesn't have a corresponding animation). So to acheive + // diagonal movement, we have to rotate the movement taken from the animation to the intended + // direction. + // + // Note that while a complete movement animation cycle will have a well defined direction, no individual + // frame will, and therefore we have to determine the direction based on the currently playing cycle + // instead. float animMovementAngle = getAnimationMovementDirection(); float targetMovementAngle = std::atan2(-movement.x(), movement.y()); float diff = targetMovementAngle - animMovementAngle; @@ -2938,7 +2941,6 @@ namespace MWMechanics case CharState_SwimRunBack: case CharState_WalkBack: return mAnimation->getLegsYawRadians() - osg::PIf; - } return 0.0f; } From 820fc068d19e91758695aa136322961d84bd0ebb Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Thu, 2 Nov 2023 23:24:58 +0300 Subject: [PATCH 40/71] Support point specular lighting (#6188) Fix passing light specular colors with shader lighting methods (with help from wazabear) --- CHANGELOG.md | 1 + components/sceneutil/lightcontroller.cpp | 9 +++- components/sceneutil/lightmanager.cpp | 2 + components/sceneutil/lightutil.cpp | 2 +- files/shaders/compatibility/bs/default.frag | 2 +- files/shaders/compatibility/objects.frag | 2 +- files/shaders/compatibility/terrain.frag | 2 +- files/shaders/lib/light/lighting.glsl | 48 +++++++++++++++++---- 8 files changed, 54 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09cde80181..2eea3946b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -95,6 +95,7 @@ Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION Feature #6152: Playing music via lua scripts + Feature #6188: Specular lighting from point light sources Feature #6447: Add LOD support to Object Paging Feature #6491: Add support for Qt6 Feature #6556: Lua API for sounds diff --git a/components/sceneutil/lightcontroller.cpp b/components/sceneutil/lightcontroller.cpp index 98af5d21c8..caff6826f5 100644 --- a/components/sceneutil/lightcontroller.cpp +++ b/components/sceneutil/lightcontroller.cpp @@ -36,9 +36,11 @@ namespace SceneUtil // if (time == mLastTime) // return; + osg::Light* light = node->getLight(nv->getTraversalNumber()); + if (mType == LT_Normal) { - node->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor); + light->setDiffuse(mDiffuseColor); traverse(node, nv); return; } @@ -63,7 +65,10 @@ namespace SceneUtil mPhase = mPhase <= 0.5f ? 1.f : 0.25f; } - node->getLight(nv->getTraversalNumber())->setDiffuse(mDiffuseColor * mBrightness * node->getActorFade()); + osg::Vec4f result = mDiffuseColor * mBrightness * node->getActorFade(); + + light->setDiffuse(result); + light->setSpecular(result); traverse(node, nv); } diff --git a/components/sceneutil/lightmanager.cpp b/components/sceneutil/lightmanager.cpp index 6bca92fdb4..8f7304416b 100644 --- a/components/sceneutil/lightmanager.cpp +++ b/components/sceneutil/lightmanager.cpp @@ -536,6 +536,7 @@ namespace SceneUtil configurePosition(lightMat, light->getPosition() * mViewMatrix); configureAmbient(lightMat, light->getAmbient()); configureDiffuse(lightMat, light->getDiffuse()); + configureSpecular(lightMat, light->getSpecular()); configureAttenuation(lightMat, light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), lightList[i]->mLightSource->getRadius()); @@ -1214,6 +1215,7 @@ namespace SceneUtil auto& buf = getUBOManager()->getLightBuffer(frameNum); buf->setDiffuse(index, light->getDiffuse()); buf->setAmbient(index, light->getAmbient()); + buf->setSpecular(index, light->getSpecular()); buf->setAttenuationRadius(index, osg::Vec4(light->getConstantAttenuation(), light->getLinearAttenuation(), light->getQuadraticAttenuation(), lightSource->getRadius())); diff --git a/components/sceneutil/lightutil.cpp b/components/sceneutil/lightutil.cpp index 9c0ebdf5c2..f69461fa3c 100644 --- a/components/sceneutil/lightutil.cpp +++ b/components/sceneutil/lightutil.cpp @@ -124,7 +124,7 @@ namespace SceneUtil } light->setDiffuse(diffuse); light->setAmbient(ambient); - light->setSpecular(osg::Vec4f(0, 0, 0, 0)); + light->setSpecular(diffuse); // ESM format doesn't provide specular lightSource->setLight(light); diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index 8c429947b0..f1be8da80c 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -95,7 +95,7 @@ void main() #endif if (matSpec != vec3(0.0)) - gl_FragData[0].xyz += getSpecular(viewNormal, normalize(passViewPos.xyz), shininess, matSpec) * shadowing; + gl_FragData[0].xyz += matSpec * getSpecular(viewNormal, passViewPos, shininess, shadowing); gl_FragData[0] = applyFogAtDist(gl_FragData[0], euclideanDepth, linearDepth, far); diff --git a/files/shaders/compatibility/objects.frag b/files/shaders/compatibility/objects.frag index 4caf6c97e2..b86678af87 100644 --- a/files/shaders/compatibility/objects.frag +++ b/files/shaders/compatibility/objects.frag @@ -242,7 +242,7 @@ vec3 viewNormal = normalize(gl_NormalMatrix * normal); matSpec *= specStrength; if (matSpec != vec3(0.0)) { - gl_FragData[0].xyz += getSpecular(viewNormal, viewVec, shininess, matSpec) * shadowing; + gl_FragData[0].xyz += matSpec * getSpecular(viewNormal, passViewPos, shininess, shadowing); } gl_FragData[0] = applyFogAtPos(gl_FragData[0], passViewPos, far); diff --git a/files/shaders/compatibility/terrain.frag b/files/shaders/compatibility/terrain.frag index a2fbddf3f7..744a56d18b 100644 --- a/files/shaders/compatibility/terrain.frag +++ b/files/shaders/compatibility/terrain.frag @@ -108,7 +108,7 @@ void main() if (matSpec != vec3(0.0)) { - gl_FragData[0].xyz += getSpecular(viewNormal, normalize(passViewPos), shininess, matSpec) * shadowing; + gl_FragData[0].xyz += matSpec * getSpecular(viewNormal, passViewPos, shininess, shadowing); } gl_FragData[0] = applyFogAtDist(gl_FragData[0], euclideanDepth, linearDepth, far); diff --git a/files/shaders/lib/light/lighting.glsl b/files/shaders/lib/light/lighting.glsl index 8c1262ba4d..8351fce8a0 100644 --- a/files/shaders/lib/light/lighting.glsl +++ b/files/shaders/lib/light/lighting.glsl @@ -90,15 +90,47 @@ void doLighting(vec3 viewPos, vec3 viewNormal, out vec3 diffuseLight, out vec3 a } } -vec3 getSpecular(vec3 viewNormal, vec3 viewDirection, float shininess, vec3 matSpec) +float calcSpecIntensity(vec3 viewNormal, vec3 viewDir, float shininess, vec3 lightDir) { - vec3 lightDir = normalize(lcalcPosition(0)); - float NdotL = dot(viewNormal, lightDir); - if (NdotL <= 0.0) - return vec3(0.0); - vec3 halfVec = normalize(lightDir - viewDirection); - float NdotH = dot(viewNormal, halfVec); - return pow(max(NdotH, 0.0), max(1e-4, shininess)) * lcalcSpecular(0).xyz * matSpec; + if (dot(viewNormal, lightDir) > 0.0) + { + vec3 halfVec = normalize(lightDir - viewDir); + float NdotH = max(dot(viewNormal, halfVec), 0.0); + return pow(NdotH, shininess); + } + + return 0.0; +} + +vec3 getSpecular(vec3 viewNormal, vec3 viewPos, float shininess, float shadowing) +{ + shininess = max(shininess, 1e-4); + vec3 viewDir = normalize(viewPos); + vec3 specularLight = lcalcSpecular(0).xyz * calcSpecIntensity(viewNormal, viewDir, shininess, normalize(lcalcPosition(0))); + specularLight *= shadowing; + + for (int i = @startLight; i < @endLight; ++i) + { +#if @lightingMethodUBO + int lightIndex = PointLightIndex[i]; +#else + int lightIndex = i; +#endif + + vec3 lightPos = lcalcPosition(lightIndex) - viewPos; + float lightDistance = length(lightPos); + +#if !@lightingMethodFFP + if (lightDistance > lcalcRadius(lightIndex) * 2.0) + continue; +#endif + + float illumination = lcalcIllumination(lightIndex, lightDistance); + float intensity = calcSpecIntensity(viewNormal, viewDir, shininess, normalize(lightPos)); + specularLight += lcalcSpecular(lightIndex).xyz * intensity * illumination; + } + + return specularLight; } #endif From 9ebec27dafa2811d47373f7d17638c86cdd3b0fd Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 4 Nov 2023 16:18:36 +0100 Subject: [PATCH 41/71] use const. --- apps/openmw/mwmechanics/character.cpp | 2 +- apps/openmw/mwmechanics/character.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index c245a17155..b9df3d1082 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2913,7 +2913,7 @@ namespace MWMechanics MWBase::Environment::get().getSoundManager()->playSound3D(mPtr, *soundId, volume, pitch); } - float CharacterController::getAnimationMovementDirection() + float CharacterController::getAnimationMovementDirection() const { switch (mMovementState) { diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index d92c0ffe5a..63491ec776 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -319,7 +319,7 @@ namespace MWMechanics void playSwishSound() const; - float getAnimationMovementDirection(); + float getAnimationMovementDirection() const; MWWorld::MovementDirectionFlags getSupportedMovementDirections() const; }; From e86a4ebafebb93dc7883b974a5e915916ed92a8b Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sat, 4 Nov 2023 21:01:06 +0100 Subject: [PATCH 42/71] Fixes based on comments by capo --- apps/openmw/mwmechanics/character.cpp | 3 ++- docs/source/reference/modding/settings/game.rst | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index b9df3d1082..da2bf4b488 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2941,8 +2941,9 @@ namespace MWMechanics case CharState_SwimRunBack: case CharState_WalkBack: return mAnimation->getLegsYawRadians() - osg::PIf; + default: + return 0.0f; } - return 0.0f; } void CharacterController::updateHeadTracking(float duration) diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 215e060b1c..31cc2703f2 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -519,7 +519,7 @@ will not be useful with another. * 2: Cylinder player movement ignores animation --------------------------- +--------------------------------- :Type: boolean :Range: True/False @@ -527,3 +527,5 @@ player movement ignores animation In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. + +This setting can be controlled in the Settings tab of the launcher, under Visuals. From 88749b03d4836ffab05c43b8d2c5a2b86e8a8c9c Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 5 Nov 2023 05:07:04 +0300 Subject: [PATCH 43/71] Use display name instead of editor ID for World::getCellName Doesn't affect Morrowind cells, but allows TES4+ cells to have legible names --- apps/openmw/mwworld/worldimp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index 5be1e52530..20e4122766 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -660,8 +660,8 @@ namespace MWWorld std::string_view World::getCellName(const MWWorld::Cell& cell) const { - if (!cell.isExterior() || !cell.getNameId().empty()) - return cell.getNameId(); + if (!cell.isExterior() || !cell.getDisplayName().empty()) + return cell.getDisplayName(); return ESM::visit(ESM::VisitOverload{ [&](const ESM::Cell& cellIn) -> std::string_view { return getCellName(&cellIn); }, From acf6178ea5089af4a7d89627a2f57374939e05c8 Mon Sep 17 00:00:00 2001 From: Mads Buvik Sandvei Date: Sun, 5 Nov 2023 16:46:11 +0100 Subject: [PATCH 44/71] `movement = movementFromAnimation;` also when speed is 0. --- apps/openmw/mwmechanics/character.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index da2bf4b488..dd7b97b6a5 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -2399,8 +2399,7 @@ namespace MWMechanics movementFromAnimation.x() *= scale; movementFromAnimation.y() *= scale; - if (speed > 0.f && movementFromAnimation != osg::Vec3f() - && !(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation)) + if (speed > 0.f && movementFromAnimation != osg::Vec3f()) { // Ensure we're moving in the right general direction. In vanilla, all horizontal movement is taken from // animations, even when moving diagonally (which doesn't have a corresponding animation). So to acheive @@ -2413,9 +2412,12 @@ namespace MWMechanics float animMovementAngle = getAnimationMovementDirection(); float targetMovementAngle = std::atan2(-movement.x(), movement.y()); float diff = targetMovementAngle - animMovementAngle; - movement = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation; + movementFromAnimation = osg::Quat(diff, osg::Vec3f(0, 0, 1)) * movementFromAnimation; } + if (!(isPlayer && Settings::game().mPlayerMovementIgnoresAnimation)) + movement = movementFromAnimation; + if (mFloatToSurface) { if (cls.getCreatureStats(mPtr).isDead() From 0f530880148a97fbd02864c8e1a708b7cc3dec59 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Sun, 5 Nov 2023 10:22:09 -0800 Subject: [PATCH 45/71] re-sync docs with postprocessing API --- components/fx/pass.cpp | 5 ++++ .../source/reference/postprocessing/omwfx.rst | 27 +++++++++++++------ files/data/l10n/OMWShaders/en.yaml | 1 + files/data/l10n/OMWShaders/ru.yaml | 1 + files/data/shaders/debug.omwfx | 13 +++++++-- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/components/fx/pass.cpp b/components/fx/pass.cpp index 4b91939e01..daaaf22968 100644 --- a/components/fx/pass.cpp +++ b/components/fx/pass.cpp @@ -190,6 +190,11 @@ mat4 omw_InvProjectionMatrix() #endif } + vec3 omw_GetNormalsWorldSpace(vec2 uv) + { + return (vec4(omw_GetNormals(uv), 0.0) * omw.viewMatrix).rgb; + } + vec3 omw_GetWorldPosFromUV(vec2 uv) { float depth = omw_GetDepth(uv); diff --git a/docs/source/reference/postprocessing/omwfx.rst b/docs/source/reference/postprocessing/omwfx.rst index 2fb21a5162..8ff075bc6d 100644 --- a/docs/source/reference/postprocessing/omwfx.rst +++ b/docs/source/reference/postprocessing/omwfx.rst @@ -189,19 +189,30 @@ The following functions can be accessed in any fragment or vertex shader. +--------------------------------------------------+-------------------------------------------------------------------------------+ | ``vec4 omw_GetLastPass(vec2 uv)`` | Returns RGBA color output of the last pass | +--------------------------------------------------+-------------------------------------------------------------------------------+ -| ``vec3 omw_GetNormals(vec2 uv)`` | Returns normalized worldspace normals [-1, 1] | -| | | -| | The values in sampler are in [0, 1] but are transformed to [-1, 1] | -+--------------------------------------------------+-----------------------+-------------------------------------------------------+ +| ``vec3 omw_GetNormals(vec2 uv)`` | Returns normalized view-space normals [-1, 1] | ++--------------------------------------------------+-------------------------------------------------------------------------------+ +| ``vec3 omw_GetNormalsWorldSpace(vec2 uv)`` | Returns normalized world-space normals [-1, 1] | ++--------------------------------------------------+-------------------------------------------------------------------------------+ | ``vec3 omw_GetWorldPosFromUV(vec2 uv)`` | Returns world position for given uv coordinate. | -+--------------------------------------------------+-----------------------+-------------------------------------------------------+ ++--------------------------------------------------+-------------------------------------------------------------------------------+ | ``float omw_GetLinearDepth(vec2 uv)`` | Returns the depth in game units for given uv coordinate. | -+--------------------------------------------------+-----------------------+-------------------------------------------------------+ ++--------------------------------------------------+-------------------------------------------------------------------------------+ | ``float omw_EstimateFogCoverageFromUV(vec2 uv)`` | Returns a fog coverage in the range from 0.0 (no fog) and 1.0 (full fog) | | | | | | Calculates an estimated fog coverage for given uv coordinate. | -+--------------------------------------------------+-----------------------+-------------------------------------------------------+ - ++--------------------------------------------------+-------------------------------------------------------------------------------+ +| ``int omw_GetPointLightCount()`` | Returns the number of point lights available to sample from in the scene. | ++--------------------------------------------------+-------------------------------------------------------------------------------+ +| ``vec3 omw_GetPointLightWorldPos(int index)`` | Returns the world space position of a point light. | ++--------------------------------------------------+-------------------------------------------------------------------------------+ +| ``vec3 omw_GetPointLightDiffuse(int index)`` | Returns the diffuse color of the point light. | ++--------------------------------------------------+-------------------------------------------------------------------------------+ +| ``int omw_GetPointLightAttenuation(int index)`` | Returns the attenuation values of the point light. | +| | | +| | The XYZ channels hold the constant, linear, and quadratic components. | ++--------------------------------------------------+-------------------------------------------------------------------------------+ +| ``float omw_GetPointLightRadius(int index)`` | Returns the radius of the point light, in game units. | ++--------------------------------------------------+-------------------------------------------------------------------------------+ Special Attributes ################## diff --git a/files/data/l10n/OMWShaders/en.yaml b/files/data/l10n/OMWShaders/en.yaml index 8221be933f..6588591f00 100644 --- a/files/data/l10n/OMWShaders/en.yaml +++ b/files/data/l10n/OMWShaders/en.yaml @@ -34,6 +34,7 @@ DisplayDepthFactorName: "Depth colour factor" DisplayDepthFactorDescription: "Determines correlation between pixel depth value and its output colour. High values lead to brighter image." DisplayDepthName: "Visualize depth buffer" DisplayNormalsName: "Visualize pass normals" +NormalsInWorldSpace: "Show normals in world space" ContrastLevelDescription: "Constrast level." ContrastLevelName: "Constrast" GammaLevelDescription: "Gamma level." diff --git a/files/data/l10n/OMWShaders/ru.yaml b/files/data/l10n/OMWShaders/ru.yaml index b886f72b54..7a2bcfe80d 100644 --- a/files/data/l10n/OMWShaders/ru.yaml +++ b/files/data/l10n/OMWShaders/ru.yaml @@ -33,6 +33,7 @@ DisplayDepthName: "Визуализация буфера глубины" DisplayDepthFactorDescription: "Определяет соотношение между значением глубины пикселя и его цветом. Чем выше значение, тем ярче будет изображение." DisplayDepthFactorName: "Соотношение цвета" DisplayNormalsName: "Визуализация нормалей" +NormalsInWorldSpace: "Показывать нормали мирового пространства" ContrastLevelDescription: "Контрастность изображения" ContrastLevelName: "Контрастность" GammaLevelDescription: "Яркость изображения" diff --git a/files/data/shaders/debug.omwfx b/files/data/shaders/debug.omwfx index 360dfa26cd..a0c8754ec4 100644 --- a/files/data/shaders/debug.omwfx +++ b/files/data/shaders/debug.omwfx @@ -19,6 +19,11 @@ uniform_bool uDisplayNormals { display_name = "#{OMWShaders:DisplayNormalsName}"; } +uniform_bool uNormalsInWorldSpace { + default = false; + display_name = "#{OMWShaders:NormalsInWorldSpace}"; +} + fragment main { omw_In vec2 omw_TexCoord; @@ -30,8 +35,12 @@ fragment main { if (uDisplayDepth) omw_FragColor = vec4(vec3(omw_GetLinearDepth(omw_TexCoord) / omw.far * uDepthFactor), 1.0); #if OMW_NORMALS - if (uDisplayNormals && (!uDisplayDepth || omw_TexCoord.x < 0.5)) - omw_FragColor.rgb = omw_GetNormals(omw_TexCoord) * 0.5 + 0.5; + if (uDisplayNormals && (!uDisplayDepth || omw_TexCoord.x < 0.5)) { + if (uNormalsInWorldSpace) + omw_FragColor.rgb = omw_GetNormalsWorldSpace(omw_TexCoord) * 0.5 + 0.5; + else + omw_FragColor.rgb = omw_GetNormals(omw_TexCoord) * 0.5 + 0.5; + } #endif } } From a60726ce35440c16a2a079569d3e82462ecf7f8b Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 5 Nov 2023 21:32:18 +0100 Subject: [PATCH 46/71] Fix #7674 --- apps/openmw/mwlua/luamanagerimp.cpp | 6 ++++++ apps/openmw/mwlua/luamanagerimp.hpp | 5 +---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwlua/luamanagerimp.cpp b/apps/openmw/mwlua/luamanagerimp.cpp index 63a2838250..48b0c15381 100644 --- a/apps/openmw/mwlua/luamanagerimp.cpp +++ b/apps/openmw/mwlua/luamanagerimp.cpp @@ -344,6 +344,12 @@ namespace MWLua playerScripts->uiModeChanged(argId, false); } + void LuaManager::useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) + { + MWBase::Environment::get().getWorldModel()->registerPtr(object); + mEngineEvents.addToQueue(EngineEvents::OnUseItem{ getId(actor), getId(object), force }); + } + void LuaManager::objectAddedToScene(const MWWorld::Ptr& ptr) { mObjectLists.objectAddedToScene(ptr); // assigns generated RefNum if it is not set yet. diff --git a/apps/openmw/mwlua/luamanagerimp.hpp b/apps/openmw/mwlua/luamanagerimp.hpp index a725761dbd..404820cc6b 100644 --- a/apps/openmw/mwlua/luamanagerimp.hpp +++ b/apps/openmw/mwlua/luamanagerimp.hpp @@ -77,10 +77,7 @@ namespace MWLua { mEngineEvents.addToQueue(EngineEvents::OnActivate{ getId(actor), getId(object) }); } - void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) override - { - mEngineEvents.addToQueue(EngineEvents::OnUseItem{ getId(actor), getId(object), force }); - } + void useItem(const MWWorld::Ptr& object, const MWWorld::Ptr& actor, bool force) override; void exteriorCreated(MWWorld::CellStore& cell) override { mEngineEvents.addToQueue(EngineEvents::OnNewExterior{ cell }); From 116ef1c62bd305a914791a4e5f100e30e5d5c74a Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Mon, 6 Nov 2023 00:21:10 +0300 Subject: [PATCH 47/71] Depth flag handling fixes (bug #7380) Properly disable depth test while allowing depth writes to happen Remove NiStencilProperty interaction Don't set up depth flags for BSShaderPPLightingProperty --- CHANGELOG.md | 1 + .../nifosg/testnifloader.cpp | 66 +++++++++++++++++-- components/nifosg/nifloader.cpp | 21 +++--- 3 files changed, 75 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 645bc59e9e..180e468e33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ Bug #7298: Water ripples from projectiles sometimes are not spawned Bug #7307: Alchemy "Magic Effect" search string does not match on tool tip for effects related to attributes Bug #7322: Shadows don't cover groundcover depending on the view angle and perspective with compute scene bounds = primitives + Bug #7380: NiZBufferProperty issue Bug #7413: Generated wilderness cells don't spawn fish Bug #7415: Unbreakable lock discrepancies Bug #7428: AutoCalc flag is not used to calculate enchantment costs diff --git a/apps/openmw_test_suite/nifosg/testnifloader.cpp b/apps/openmw_test_suite/nifosg/testnifloader.cpp index 5c37cb375f..f05d651301 100644 --- a/apps/openmw_test_suite/nifosg/testnifloader.cpp +++ b/apps/openmw_test_suite/nifosg/testnifloader.cpp @@ -108,7 +108,64 @@ osg::Group { )"); } - std::string formatOsgNodeForShaderProperty(std::string_view shaderPrefix) + std::string formatOsgNodeForBSShaderProperty(std::string_view shaderPrefix) + { + std::ostringstream oss; + oss << R"( +osg::Group { + UniqueID 1 + DataVariance STATIC + UserDataContainer TRUE { + osg::DefaultUserDataContainer { + UniqueID 2 + UDC_UserObjects 1 { + osg::StringValueObject { + UniqueID 3 + Name "fileHash" + } + } + } + } + Children 1 { + osg::Group { + UniqueID 4 + DataVariance STATIC + UserDataContainer TRUE { + osg::DefaultUserDataContainer { + UniqueID 5 + UDC_UserObjects 3 { + osg::UIntValueObject { + UniqueID 6 + Name "recIndex" + Value 4294967295 + } + osg::StringValueObject { + UniqueID 7 + Name "shaderPrefix" + Value ")" + << shaderPrefix << R"(" + } + osg::BoolValueObject { + UniqueID 8 + Name "shaderRequired" + Value TRUE + } + } + } + } + StateSet TRUE { + osg::StateSet { + UniqueID 9 + } + } + } + } +} +)"; + return oss.str(); + } + + std::string formatOsgNodeForBSLightingShaderProperty(std::string_view shaderPrefix) { std::ostringstream oss; oss << R"( @@ -162,7 +219,6 @@ osg::Group { AttributeList 1 { osg::Depth { UniqueID 10 - WriteMask FALSE } Value OFF } @@ -204,7 +260,7 @@ osg::Group { Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); auto result = Loader::load(file, &mImageManager); - EXPECT_EQ(serialize(*result), formatOsgNodeForShaderProperty(GetParam().mExpectedShaderPrefix)); + EXPECT_EQ(serialize(*result), formatOsgNodeForBSShaderProperty(GetParam().mExpectedShaderPrefix)); } INSTANTIATE_TEST_SUITE_P(Params, NifOsgLoaderBSShaderPrefixTest, ValuesIn(NifOsgLoaderBSShaderPrefixTest::sParams)); @@ -228,11 +284,13 @@ osg::Group { property.mTextureSet = nullptr; property.mController = nullptr; property.mType = GetParam().mShaderType; + property.mShaderFlags1 |= Nif::BSShaderFlags1::BSSFlag1_DepthTest; + property.mShaderFlags2 |= Nif::BSShaderFlags2::BSSFlag2_DepthWrite; node.mProperties.push_back(Nif::RecordPtrT(&property)); Nif::NIFFile file("test.nif"); file.mRoots.push_back(&node); auto result = Loader::load(file, &mImageManager); - EXPECT_EQ(serialize(*result), formatOsgNodeForShaderProperty(GetParam().mExpectedShaderPrefix)); + EXPECT_EQ(serialize(*result), formatOsgNodeForBSLightingShaderProperty(GetParam().mExpectedShaderPrefix)); } INSTANTIATE_TEST_SUITE_P( diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 5b98f54599..b7ef547bd6 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1964,11 +1964,17 @@ namespace NifOsg return texEnv; } - void handleDepthFlags(osg::StateSet* stateset, bool depthTest, bool depthWrite, - osg::Depth::Function depthFunction = osg::Depth::LESS) + void handleDepthFlags(osg::StateSet* stateset, bool depthTest, bool depthWrite) { - stateset->setMode(GL_DEPTH_TEST, depthTest ? osg::StateAttribute::ON : osg::StateAttribute::OFF); - osg::ref_ptr depth = new osg::Depth(depthFunction, 0.0, 1.0, depthWrite); + if (!depthWrite && !depthTest) + { + stateset->setMode(GL_DEPTH_TEST, osg::StateAttribute::OFF); + return; + } + osg::ref_ptr depth = new osg::Depth; + depth->setWriteMask(depthWrite); + if (!depthTest) + depth->setFunction(osg::Depth::ALWAYS); depth = shareAttribute(depth); stateset->setAttributeAndModes(depth, osg::StateAttribute::ON); } @@ -2328,10 +2334,8 @@ namespace NifOsg { const Nif::NiZBufferProperty* zprop = static_cast(property); osg::StateSet* stateset = node->getOrCreateStateSet(); - // Morrowind ignores depth test function, unless a NiStencilProperty is present, in which case it - // uses a fixed depth function of GL_ALWAYS. - osg::Depth::Function depthFunction = hasStencilProperty ? osg::Depth::ALWAYS : osg::Depth::LESS; - handleDepthFlags(stateset, zprop->depthTest(), zprop->depthWrite(), depthFunction); + // The test function from this property seems to be ignored. + handleDepthFlags(stateset, zprop->depthTest(), zprop->depthWrite()); break; } // OSG groups the material properties that NIFs have separate, so we have to parse them all again when @@ -2370,7 +2374,6 @@ namespace NifOsg textureSet, texprop->mClamp, node->getName(), stateset, imageManager, boundTextures); } handleTextureControllers(texprop, composite, imageManager, stateset, animflags); - handleDepthFlags(stateset, texprop->depthTest(), texprop->depthWrite()); break; } case Nif::RC_BSShaderNoLightingProperty: From d0294019cee00726bc793c054bf294b6b83846a4 Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 6 Nov 2023 02:05:25 +0100 Subject: [PATCH 48/71] Remove doc for removed setting --- docs/source/reference/modding/settings/navigator.rst | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index 0de061d8f0..a86b174251 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -246,17 +246,6 @@ Absent pieces usually mean a bug in recast mesh tiles building. Allows to do in-game debug. Potentially decreases performance. -nav mesh version ----------------- - -:Type: integer -:Range: > 0 -:Default: 1 - -Version of navigation mesh generation algorithm. -Should be increased each time there is a difference between output of makeNavMeshTileData function for the same input. -Changing the value will invalidate navmesh disk cache. - Expert settings *************** From 392218dde326bb6bda182e02e7282d705bd0461f Mon Sep 17 00:00:00 2001 From: elsid Date: Mon, 6 Nov 2023 01:41:32 +0100 Subject: [PATCH 49/71] Reword navigation mesh related docs and tooltips To explain the effect of the setting in a more user friendly language. --- .../reference/modding/settings/navigator.rst | 77 +++++++++---------- files/ui/settingspage.ui | 4 +- 2 files changed, 40 insertions(+), 41 deletions(-) diff --git a/docs/source/reference/modding/settings/navigator.rst b/docs/source/reference/modding/settings/navigator.rst index a86b174251..55b9e19b19 100644 --- a/docs/source/reference/modding/settings/navigator.rst +++ b/docs/source/reference/modding/settings/navigator.rst @@ -13,16 +13,15 @@ enable :Range: True/False :Default: True -Enable navigator. -When enabled background threads are started to build nav mesh for world geometry. -Pathfinding system uses nav mesh to build paths. -When disabled only pathgrid is used to build paths. -Single-core CPU systems may have big performance impact on exiting interior location and moving across exterior world. +Enable navigator to make all settings in this category take effect. +When enabled, a navigation mesh (navmesh) is built in the background for world geometry to be used for pathfinding. +When disabled only the path grid is used to build paths. +Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. -Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. -Moving across external world, entering/exiting location produce nav mesh update. -NPC and creatures may not be able to find path before nav mesh is built around them. -Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and casting a firebolt. +Multi-core CPU systems may have different latency for navigation mesh update depending on other settings and system performance. +Moving across external world, entering/exiting location produce navigation mesh update. +NPC and creatures may not be able to find path before navigation mesh is built around them. +Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt. max tiles number ---------------- @@ -31,14 +30,14 @@ max tiles number :Range: >= 0 :Default: 512 -Number of tiles at nav mesh. +Number of tiles at navigation mesh. Nav mesh covers circle area around player. -This option allows to set an explicit limit for nav mesh size, how many tiles should fit into circle. -If actor is inside this area it able to find path over nav mesh. +This option allows to set an explicit limit for navigation mesh size, how many tiles should fit into circle. +If actor is inside this area it able to find path over navigation mesh. Increasing this value may decrease performance. .. note:: - Don't expect infinite nav mesh size increasing. + Don't expect infinite navigation mesh size increasing. This condition is always true: ``max tiles number * max polygons per tile <= 4194304``. It's a limitation of `Recastnavigation `_ library. @@ -49,9 +48,9 @@ wait until min distance to player :Range: >= 0 :Default: 5 -Distance in navmesh tiles around the player to keep loading screen until navigation mesh is generated. +Distance in navigation mesh tiles around the player to keep loading screen until navigation mesh is generated. Allows to complete cell loading only when minimal navigation mesh area is generated to correctly find path for actors -nearby the player. Increasing this value will keep loading screen longer but will slightly increase nav mesh generation +nearby the player. Increasing this value will keep loading screen longer but will slightly increase navigation mesh generation speed on systems bound by CPU. Zero means no waiting. enable nav mesh disk cache @@ -61,8 +60,8 @@ enable nav mesh disk cache :Range: True/False :Default: True -If true navmesh cache stored on disk will be used in addition to memory cache. -If navmesh tile is not present in memory cache, it will be looked up in the disk cache. +If true navigation mesh cache stored on disk will be used in addition to memory cache. +If navigation mesh tile is not present in memory cache, it will be looked up in the disk cache. If it's not found there it will be generated. write to navmeshdb @@ -72,7 +71,7 @@ write to navmeshdb :Range: True/False :Default: True -If true generated navmesh tiles will be stored into disk cache while game is running. +If true generated navigation mesh tiles will be stored into disk cache while game is running. max navmeshdb file size ----------------------- @@ -95,8 +94,8 @@ async nav mesh updater threads :Range: >= 1 :Default: 1 -Number of background threads to update nav mesh. -Increasing this value may decrease performance, but also may decrease or increase nav mesh update latency depending on number of CPU cores. +Number of background threads to update navigation mesh. +Increasing this value may decrease performance, but also may decrease or increase navigation mesh update latency depending on number of CPU cores. On systems with not less than 4 CPU cores latency dependens approximately like 1/log(n) from number of threads. Don't expect twice better latency by doubling this value. @@ -107,12 +106,12 @@ max nav mesh tiles cache size :Range: >= 0 :Default: 268435456 -Maximum total cached size of all nav mesh tiles in bytes. -Setting greater than zero value will reduce nav mesh update latency for previously visited locations. +Maximum total cached size of all navigation mesh tiles in bytes. +Setting greater than zero value will reduce navigation mesh update latency for previously visited locations. Increasing this value may increase total memory consumption, but potentially will reduce latency for recently visited locations. Limit this value by total available physical memory minus base game memory consumption and other applications. Game will not eat all memory at once. -Memory will be consumed in approximately linear dependency from number of nav mesh updates. +Memory will be consumed in approximately linear dependency from number of navigation mesh updates. But only for new locations or already dropped from cache. min update interval ms @@ -122,17 +121,17 @@ min update interval ms :Range: >= 0 :Default: 250 -Minimum time duration required to pass before next navmesh update for the same tile in milliseconds. +Minimum time duration required to pass before next navigation mesh update for the same tile in milliseconds. Only tiles affected where objects are transformed. Next update for tile with added or removed object will not be delayed. -Visible ingame effect is navmesh update around opening or closing door. +Visible ingame effect is navigation mesh update around opening or closing door. Primary usage is for rotating signs like in Seyda Neen at Arrille's Tradehouse entrance. Decreasing this value may increase CPU usage by background threads. Developer's settings ******************** -This section is for developers or anyone who wants to investigate how nav mesh system works in OpenMW. +This section is for developers or anyone who wants to learn how navigation mesh system works in OpenMW. enable write recast mesh to file -------------------------------- @@ -141,8 +140,8 @@ enable write recast mesh to file :Range: True/False :Default: False -Write recast mesh to file in .obj format for each use to update nav mesh. -Option is used to find out what world geometry is used to build nav mesh. +Write recast mesh to file in .obj format for each use to update navigation mesh. +Option is used to find out what world geometry is used to build navigation mesh. Potentially decreases performance. enable write nav mesh to file @@ -152,7 +151,7 @@ enable write nav mesh to file :Range: True/False :Default: False -Write nav mesh to file to be able to open by RecastDemo application. +Write navigation mesh to file to be able to open by RecastDemo application. Usually it is more useful to have both enable write recast mesh to file and this options enabled. RecastDemo supports .obj files. Potentially decreases performance. @@ -175,9 +174,9 @@ enable nav mesh file name revision :Range: True/False :Default: False -Write each nav mesh file with revision in name. +Write each navigation mesh file with revision in name. Otherwise will rewrite same file. -If it is unclear when nav mesh is changed use this option to dump multiple files for each state. +If it is unclear when navigation mesh is changed use this option to dump multiple files for each state. recast mesh path prefix ----------------------- @@ -195,7 +194,7 @@ nav mesh path prefix :Range: file system path :Default: "" -Write nav mesh file at path with this prefix. +Write navigation mesh file at path with this prefix. enable nav mesh render ---------------------- @@ -206,7 +205,7 @@ enable nav mesh render Render navigation mesh. Allows to do in-game debug. -Every nav mesh is visible and every update is noticeable. +Every navigation mesh is visible and every update is noticeable. Potentially decreases performance. nav mesh render mode @@ -258,12 +257,12 @@ recast scale factor :Range: > 0.0 :Default: 0.029411764705882353 -Scale of nav mesh coordinates to world coordinates. Recastnavigation builds voxels for world geometry. +Scale of navigation mesh coordinates to world coordinates. Recastnavigation builds voxels for world geometry. Basically voxel size is 1 / "cell size". To reduce amount of voxels we apply scale factor, to make voxel size "recast scale factor" / "cell size". Default value calculates by this equation: sStepSizeUp * "recast scale factor" / "cell size" = 5 (max climb height should be equal to 4 voxels). -Changing this value will change generated nav mesh. Some locations may become unavailable for NPC and creatures. -Pay attention to slopes and roofs when change it. Increasing this value will reduce nav mesh update latency. +Changing this value will change generated navigation mesh. Some locations may become unavailable for NPC and creatures. +Pay attention to slopes and roofs when change it. Increasing this value will reduce navigation mesh update latency. max polygon path size --------------------- @@ -386,13 +385,13 @@ max polygons per tile :Range: 2^n, 0 < n < 22 :Default: 4096 -Maximum number of polygons per nav mesh tile. Maximum number of nav mesh tiles depends on +Maximum number of polygons per navigation mesh tile. Maximum number of navigation mesh tiles depends on this value. 22 bits is a limit to store both tile identifier and polygon identifier (tiles = 2^(22 - log2(polygons))). See `recastnavigation `_ for more details. .. Warning:: - Lower value may lead to ignored world geometry on nav mesh. - Greater value will reduce number of nav mesh tiles. + Lower value may lead to ignored world geometry on navigation mesh. + Greater value will reduce number of navigation mesh tiles. This condition is always true: ``max tiles number * max polygons per tile <= 4194304``. It's a limitation of `Recastnavigation `_ library. diff --git a/files/ui/settingspage.ui b/files/ui/settingspage.ui index c61b4f4229..339ae22910 100644 --- a/files/ui/settingspage.ui +++ b/files/ui/settingspage.ui @@ -116,10 +116,10 @@ - <html><head/><body><p>Enable navigator. When enabled background threads are started to build nav mesh for world geometry. Pathfinding system uses nav mesh to build paths. When disabled only pathgrid is used to build paths. Single-core CPU systems may have big performance impact on exiting interior location and moving across exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn’t know where to go when you stand behind that stone and casting a firebolt.</p></body></html> + <html><head/><body><p><a name="docs-internal-guid-f375b85a-7fff-02ff-a5af-c5cff63923c0"/>When enabled, a navigation mesh is built in the background for world geometry to be used for pathfinding. When disabled only the path grid is used to build paths. Single-core CPU systems may have a big performance impact on existing interior location and moving across the exterior world. May slightly affect performance on multi-core CPU systems. Multi-core CPU systems may have different latency for nav mesh update depending on other settings and system performance. Moving across external world, entering/exiting location produce nav mesh update. NPC and creatures may not be able to find path before nav mesh is built around them. Try to disable this if you want to have old fashioned AI which doesn't know where to go when you stand behind that stone and cast a firebolt.</p></body></html> - Build nav mesh for world geometry + Use navigation mesh for pathfinding From 75c5ce5f3161f2229fda6849b020b93ac359e245 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 6 Nov 2023 21:38:40 +0000 Subject: [PATCH 50/71] Fix MWScript variables documetnation type --- files/lua_api/openmw/world.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/files/lua_api/openmw/world.lua b/files/lua_api/openmw/world.lua index 13fa75e0ad..5baa624c5d 100644 --- a/files/lua_api/openmw/world.lua +++ b/files/lua_api/openmw/world.lua @@ -22,6 +22,10 @@ -- Functions related to MWScript. -- @type MWScriptFunctions +--- +-- @type MWScriptVariables +-- @map <#string, #number> + --- -- Returns local mwscript on ``object``. Returns `nil` if the script doesn't exist or is not started. -- @function [parent=#MWScriptFunctions] getLocalScript @@ -33,7 +37,7 @@ -- Returns mutable global variables. In multiplayer, these may be specific to the provided player. -- @function [parent=#MWScriptFunctions] getGlobalVariables -- @param openmw.core#GameObject player (optional) Will be used in multiplayer mode to get the globals if there is a separate instance for each player. Currently has no effect. --- @return #list<#number> +-- @return #MWScriptVariables --- -- Returns global mwscript with given recordId. Returns `nil` if the script doesn't exist or is not started. From afbfed78ad9be0818bb0c827d51146d49939f6d2 Mon Sep 17 00:00:00 2001 From: uramer Date: Mon, 6 Nov 2023 21:57:49 +0000 Subject: [PATCH 51/71] Add ItemUsage to interfaces package type definition --- files/lua_api/openmw/interfaces.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/files/lua_api/openmw/interfaces.lua b/files/lua_api/openmw/interfaces.lua index 5048fc2f3e..d4a290aa47 100644 --- a/files/lua_api/openmw/interfaces.lua +++ b/files/lua_api/openmw/interfaces.lua @@ -20,6 +20,9 @@ --- -- @field [parent=#interfaces] scripts.omw.ui#scripts.omw.ui UI +--- +-- @field [parent=#interfaces] scripts.omw.usehandlers#scripts.omw.usehandlers ItemUsage + --- -- @function [parent=#interfaces] __index -- @param #interfaces self From 0f3dba28a720c08ff14f0ca79f5b80d36fb7c89f Mon Sep 17 00:00:00 2001 From: Abdu Sharif Date: Tue, 7 Nov 2023 02:59:37 +0000 Subject: [PATCH 52/71] Consider 75% Chameleon magical invisibility as well --- apps/openmw/mwmechanics/actorutil.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwmechanics/actorutil.cpp b/apps/openmw/mwmechanics/actorutil.cpp index c414ff3032..2d2980075e 100644 --- a/apps/openmw/mwmechanics/actorutil.cpp +++ b/apps/openmw/mwmechanics/actorutil.cpp @@ -39,6 +39,6 @@ namespace MWMechanics { const MagicEffects& magicEffects = actor.getClass().getCreatureStats(actor).getMagicEffects(); return (magicEffects.getOrDefault(ESM::MagicEffect::Invisibility).getMagnitude() > 0) - || (magicEffects.getOrDefault(ESM::MagicEffect::Chameleon).getMagnitude() > 75); + || (magicEffects.getOrDefault(ESM::MagicEffect::Chameleon).getMagnitude() >= 75); } } From 2d4e1b88b225d598cae29369d3962a7f1f6c64fc Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 7 Nov 2023 15:52:49 +0400 Subject: [PATCH 53/71] Init missing field --- components/nif/controller.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 33c04a6d35..947fae1ab2 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -171,7 +171,7 @@ namespace Nif }; NiPosDataPtr mData; - TargetColor mTargetColor; + TargetColor mTargetColor = TargetColor::Ambient; void read(NIFStream* nif) override; void post(Reader& nif) override; From 47c7997a23e39471675a43dcf20df7a752851502 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Tue, 7 Nov 2023 15:57:25 +0400 Subject: [PATCH 54/71] Init an another field --- components/esm4/loadrace.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/esm4/loadrace.hpp b/components/esm4/loadrace.hpp index 9cd4d00891..125fefbd9f 100644 --- a/components/esm4/loadrace.hpp +++ b/components/esm4/loadrace.hpp @@ -110,7 +110,7 @@ namespace ESM4 ESM::FormId mId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details - bool mIsTES5; + bool mIsTES5 = false; std::string mEditorId; std::string mFullName; From fdf9184cae5dd92a5eb8e33e096a10630c3ce656 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 8 Nov 2023 12:27:12 +0300 Subject: [PATCH 55/71] Improve or fix FO76-related definitions --- components/nif/effect.cpp | 4 ++- components/nif/effect.hpp | 3 ++- components/nif/particle.cpp | 6 ++--- components/nif/property.cpp | 52 ++++++++++++++++++------------------- components/nif/property.hpp | 8 +++--- 5 files changed, 38 insertions(+), 35 deletions(-) diff --git a/components/nif/effect.cpp b/components/nif/effect.cpp index ae4c7947cb..47d1863352 100644 --- a/components/nif/effect.cpp +++ b/components/nif/effect.cpp @@ -45,7 +45,9 @@ namespace Nif { NiPointLight::read(nif); - nif->read(mCutoff); + nif->read(mOuterSpotAngle); + if (nif->getVersion() >= NIFStream::generateVersion(20, 2, 0, 5)) + nif->read(mInnerSpotAngle); nif->read(mExponent); } diff --git a/components/nif/effect.hpp b/components/nif/effect.hpp index 906a7fdedf..2dd18d5304 100644 --- a/components/nif/effect.hpp +++ b/components/nif/effect.hpp @@ -58,7 +58,8 @@ namespace Nif struct NiSpotLight : public NiPointLight { - float mCutoff; + float mOuterSpotAngle; + float mInnerSpotAngle{ 0.f }; float mExponent; void read(NIFStream* nif) override; }; diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index 0581c5a1d1..d81d423fb6 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -231,7 +231,7 @@ namespace Nif info.read(nif); } - if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO4) nif->skip(12); // Unknown if (nif->getVersion() >= NIFStream::generateVersion(20, 0, 0, 2) && nif->get() && hasData) @@ -420,8 +420,8 @@ namespace Nif { nif->read(mRotationSpeedVariation); - if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) - nif->skip(5); // Unknown + if (nif->getBethVersion() > NIFFile::BethVersion::BETHVER_FO4) + nif->skip(17); // Unknown nif->read(mRotationAngle); nif->read(mRotationAngleVariation); diff --git a/components/nif/property.cpp b/components/nif/property.cpp index bcc70540c8..2a5f91385d 100644 --- a/components/nif/property.cpp +++ b/components/nif/property.cpp @@ -148,12 +148,6 @@ namespace Nif } else { - uint32_t numShaderFlags1 = 0, numShaderFlags2 = 0; - nif->read(numShaderFlags1); - if (nif->getBethVersion() >= 152) - nif->read(numShaderFlags2); - nif->readVector(mShaderFlags1Hashes, numShaderFlags1); - nif->readVector(mShaderFlags2Hashes, numShaderFlags2); if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76 && recType == RC_BSLightingShaderProperty) { nif->read(mType); @@ -181,6 +175,13 @@ namespace Nif break; } } + + uint32_t numShaderFlags1 = 0, numShaderFlags2 = 0; + nif->read(numShaderFlags1); + if (nif->getBethVersion() >= 152) + nif->read(numShaderFlags2); + nif->readVector(mShaderFlags1Hashes, numShaderFlags1); + nif->readVector(mShaderFlags2Hashes, numShaderFlags2); } nif->read(mUVOffset); @@ -324,7 +325,7 @@ namespace Nif { nif->read(mSubsurfaceRolloff); nif->read(mRimlightPower); - if (mRimlightPower == std::numeric_limits::max()) + if (nif->getBethVersion() == 130 && mRimlightPower == std::numeric_limits::max()) nif->read(mBacklightPower); } @@ -335,27 +336,27 @@ namespace Nif mWetness.read(nif); } - if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_STF) + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) + { mLuminance.read(nif); - - if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_F76) - { - nif->read(mDoTranslucency); - if (mDoTranslucency) - mTranslucency.read(nif); - if (nif->get() != 0) + if (nif->getBethVersion() == NIFFile::BethVersion::BETHVER_F76) { - mTextureArrays.resize(nif->get()); - for (std::vector& textureArray : mTextureArrays) - nif->getSizedStrings(textureArray, nif->get()); + nif->read(mDoTranslucency); + if (mDoTranslucency) + mTranslucency.read(nif); + if (nif->get() != 0) + { + mTextureArrays.resize(nif->get()); + for (std::vector& textureArray : mTextureArrays) + nif->getSizedStrings(textureArray, nif->get()); + } + } + if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_STF) + { + nif->skip(4); // Unknown + nif->skip(4); // Unknown + nif->skip(2); // Unknown } - } - - if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_STF) - { - nif->skip(4); // Unknown - nif->skip(4); // Unknown - nif->skip(2); // Unknown } switch (static_cast(mType)) @@ -439,7 +440,6 @@ namespace Nif if (nif->getBethVersion() >= NIFFile::BethVersion::BETHVER_F76) { - nif->read(mRefractionPower); mReflectanceTexture = nif->getSizedString(); mLightingTexture = nif->getSizedString(); nif->read(mEmittanceColor); diff --git a/components/nif/property.hpp b/components/nif/property.hpp index eaaa972c39..2506633867 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -295,15 +295,15 @@ namespace Nif { BSShaderTextureSetPtr mTextureSet; osg::Vec3f mEmissive; - float mEmissiveMult; + float mEmissiveMult{ 1.f }; std::string mRootMaterial; - uint32_t mClamp; - float mAlpha; + uint32_t mClamp{ 3 }; + float mAlpha{ 1.f }; float mRefractionStrength; float mGlossiness{ 80.f }; float mSmoothness{ 1.f }; osg::Vec3f mSpecular; - float mSpecStrength; + float mSpecStrength{ 1.f }; std::array mLightingEffects; float mSubsurfaceRolloff; float mRimlightPower; From 7f92c1821eba9350a19d9371c1dd75ec8ddb2bd3 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Wed, 8 Nov 2023 12:33:26 +0300 Subject: [PATCH 56/71] Read BSCollisionQueryProxyExtraData --- components/nif/extra.cpp | 5 +++++ components/nif/extra.hpp | 7 +++++++ components/nif/niffile.cpp | 2 ++ components/nif/record.hpp | 1 + 4 files changed, 15 insertions(+) diff --git a/components/nif/extra.cpp b/components/nif/extra.cpp index 2d222f5a54..4ebd0bf517 100644 --- a/components/nif/extra.cpp +++ b/components/nif/extra.cpp @@ -136,6 +136,11 @@ namespace Nif nif->readVector(mData, nif->get()); } + void BSCollisionQueryProxyExtraData::read(NIFStream* nif) + { + nif->readVector(mData, nif->get()); + } + void BSConnectPoint::Point::read(NIFStream* nif) { mParent = nif->getSizedString(); diff --git a/components/nif/extra.hpp b/components/nif/extra.hpp index 1efa4ae7bb..2b46c81e26 100644 --- a/components/nif/extra.hpp +++ b/components/nif/extra.hpp @@ -173,6 +173,13 @@ namespace Nif void read(NIFStream* nif) override; }; + struct BSCollisionQueryProxyExtraData : BSExtraData + { + std::vector mData; + + void read(NIFStream* nif) override; + }; + struct BSConnectPoint { struct Point diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 81a223e095..37e40938d3 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -248,6 +248,8 @@ namespace Nif { "BSBehaviorGraphExtraData", &construct }, { "BSBoneLODExtraData", &construct }, { "BSClothExtraData", &construct }, + { "BSCollisionQueryProxyExtraData", + &construct }, { "BSConnectPoint::Children", &construct }, { "BSConnectPoint::Parents", &construct }, { "BSDecalPlacementVectorExtraData", diff --git a/components/nif/record.hpp b/components/nif/record.hpp index d2a30b1317..699522d24c 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -77,6 +77,7 @@ namespace Nif RC_BSBound, RC_BSBoneLODExtraData, RC_BSClothExtraData, + RC_BSCollisionQueryProxyExtraData, RC_BSConnectPointChildren, RC_BSConnectPointParents, RC_BSDecalPlacementVectorExtraData, From a7a48aaa910638ea98432638f5d00d302954e750 Mon Sep 17 00:00:00 2001 From: Kindi Date: Thu, 9 Nov 2023 00:27:07 +0800 Subject: [PATCH 57/71] make successful lockspell play unlock sound --- apps/openmw/mwmechanics/spelleffects.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/openmw/mwmechanics/spelleffects.cpp b/apps/openmw/mwmechanics/spelleffects.cpp index 2e28aaa1f3..db9ec3e588 100644 --- a/apps/openmw/mwmechanics/spelleffects.cpp +++ b/apps/openmw/mwmechanics/spelleffects.cpp @@ -932,6 +932,9 @@ namespace MWMechanics if (target.getCellRef().getLockLevel() < magnitude) // If the door is not already locked to a higher value, lock it to spell magnitude { + MWBase::Environment::get().getSoundManager()->playSound3D( + target, ESM::RefId::stringRefId("Open Lock"), 1.f, 1.f); + if (caster == getPlayer()) MWBase::Environment::get().getWindowManager()->messageBox("#{sMagicLockSuccess}"); target.getCellRef().lock(magnitude); From d54c8b82d9fcb8ff6ef1946e170f9d781c696e05 Mon Sep 17 00:00:00 2001 From: Kindi Date: Thu, 9 Nov 2023 00:29:57 +0800 Subject: [PATCH 58/71] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26957e6c8d..9f1046179b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -94,6 +94,7 @@ Bug #7647: NPC walk cycle bugs after greeting player Bug #7654: Tooltips for enchantments with invalid effects cause crashes Bug #7660: Some inconsistencies regarding Invisibility breaking + Bug #7675: Successful lock spell doesn't produce a sound Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION From cdaa44f24cdef69ac5aa13047a9aa60fefc4e944 Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Wed, 8 Nov 2023 22:50:50 +0000 Subject: [PATCH 59/71] [Postprocessing] Fix dirty flag and share luminance calculator between frames --- apps/openmw/mwrender/pingpongcanvas.cpp | 22 ++++++++++++---------- apps/openmw/mwrender/pingpongcanvas.hpp | 5 +++-- apps/openmw/mwrender/postprocessor.cpp | 14 ++++++++------ 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp index 6782bdddfd..b96b40ff3f 100644 --- a/apps/openmw/mwrender/pingpongcanvas.cpp +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -10,9 +10,11 @@ namespace MWRender { - PingPongCanvas::PingPongCanvas(Shader::ShaderManager& shaderManager) + PingPongCanvas::PingPongCanvas( + Shader::ShaderManager& shaderManager, const std::shared_ptr& luminanceCalculator) : mFallbackStateSet(new osg::StateSet) , mMultiviewResolveStateSet(new osg::StateSet) + , mLuminanceCalculator(luminanceCalculator) { setUseDisplayList(false); setUseVertexBufferObjects(true); @@ -26,8 +28,7 @@ namespace MWRender addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::TRIANGLES, 0, 3)); - mLuminanceCalculator = LuminanceCalculator(shaderManager); - mLuminanceCalculator.disable(); + mLuminanceCalculator->disable(); Shader::ShaderManager::DefineMap defines; Stereo::shaderStereoDefines(defines); @@ -142,7 +143,7 @@ namespace MWRender .getTexture()); } - mLuminanceCalculator.dirty(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight()); + mLuminanceCalculator->dirty(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight()); if (Stereo::getStereo()) mRenderViewport @@ -158,11 +159,11 @@ namespace MWRender { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT2_EXT }, { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT } } }; - (mAvgLum) ? mLuminanceCalculator.enable() : mLuminanceCalculator.disable(); + (mAvgLum) ? mLuminanceCalculator->enable() : mLuminanceCalculator->disable(); // A histogram based approach is superior way to calculate scene luminance. Using mipmaps is more broadly // supported, so that's what we use for now. - mLuminanceCalculator.draw(*this, renderInfo, state, ext, frameId); + mLuminanceCalculator->draw(*this, renderInfo, state, ext, frameId); auto buffer = buffers[0]; @@ -202,8 +203,8 @@ namespace MWRender node.mRootStateSet->setTextureAttribute(PostProcessor::Unit_Depth, mTextureDepth); if (mAvgLum) - node.mRootStateSet->setTextureAttribute( - PostProcessor::TextureUnits::Unit_EyeAdaptation, mLuminanceCalculator.getLuminanceTexture(frameId)); + node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_EyeAdaptation, + mLuminanceCalculator->getLuminanceTexture(frameId)); if (mTextureNormals) node.mRootStateSet->setTextureAttribute(PostProcessor::TextureUnits::Unit_Normals, mTextureNormals); @@ -258,8 +259,6 @@ namespace MWRender texture->setTextureSize(w, h); texture->setNumMipmapLevels(pass.mRenderTexture->getNumMipmapLevels()); texture->dirtyTextureObject(); - - mDirtyAttachments = false; } pass.mRenderTarget->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); @@ -336,5 +335,8 @@ namespace MWRender { bindDestinationFbo(); } + + if (mDirtyAttachments) + mDirtyAttachments = false; } } diff --git a/apps/openmw/mwrender/pingpongcanvas.hpp b/apps/openmw/mwrender/pingpongcanvas.hpp index 557813b816..0d53fd049a 100644 --- a/apps/openmw/mwrender/pingpongcanvas.hpp +++ b/apps/openmw/mwrender/pingpongcanvas.hpp @@ -22,7 +22,8 @@ namespace MWRender class PingPongCanvas : public osg::Geometry { public: - PingPongCanvas(Shader::ShaderManager& shaderManager); + PingPongCanvas( + Shader::ShaderManager& shaderManager, const std::shared_ptr& luminanceCalculator); void drawGeometry(osg::RenderInfo& renderInfo) const; @@ -72,7 +73,7 @@ namespace MWRender mutable osg::ref_ptr mMultiviewResolveFramebuffer; mutable osg::ref_ptr mDestinationFBO; mutable std::array, 3> mFbos; - mutable LuminanceCalculator mLuminanceCalculator; + mutable std::shared_ptr mLuminanceCalculator; }; } diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 66ade9495c..2527f52df1 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -118,9 +118,14 @@ namespace MWRender , mUsePostProcessing(Settings::postProcessing().mEnabled) , mSamples(Settings::video().mAntialiasing) , mPingPongCull(new PingPongCull(this)) - , mCanvases({ new PingPongCanvas(mRendering.getResourceSystem()->getSceneManager()->getShaderManager()), - new PingPongCanvas(mRendering.getResourceSystem()->getSceneManager()->getShaderManager()) }) { + auto& shaderManager = mRendering.getResourceSystem()->getSceneManager()->getShaderManager(); + + std::shared_ptr luminanceCalculator = std::make_shared(shaderManager); + + for (auto& canvas : mCanvases) + canvas = new PingPongCanvas(shaderManager, luminanceCalculator); + mHUDCamera->setReferenceFrame(osg::Camera::ABSOLUTE_RF); mHUDCamera->setRenderOrder(osg::Camera::POST_RENDER); mHUDCamera->setClearColor(osg::Vec4(0.45, 0.45, 0.14, 1.0)); @@ -139,8 +144,7 @@ namespace MWRender if (Settings::shaders().mSoftParticles || Settings::postProcessing().mTransparentPostpass) { mTransparentDepthPostPass - = new TransparentDepthBinCallback(mRendering.getResourceSystem()->getSceneManager()->getShaderManager(), - Settings::postProcessing().mTransparentPostpass); + = new TransparentDepthBinCallback(shaderManager, Settings::postProcessing().mTransparentPostpass); osgUtil::RenderBin::getRenderBinPrototype("DepthSortedBin")->setDrawCallback(mTransparentDepthPostPass); } @@ -617,8 +621,6 @@ namespace MWRender subPass.mSize = renderTarget.mSize; subPass.mRenderTexture = renderTarget.mTarget; subPass.mMipMap = renderTarget.mMipMap; - subPass.mStateSet->setAttributeAndModes(new osg::Viewport( - 0, 0, subPass.mRenderTexture->getTextureWidth(), subPass.mRenderTexture->getTextureHeight())); subPass.mRenderTarget = new osg::FrameBufferObject; subPass.mRenderTarget->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, From aefab1aac50e8d68318d672f3a0e0373fc5abaa9 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 10 Nov 2023 18:36:39 +0100 Subject: [PATCH 60/71] List installed packages --- CI/install_debian_deps.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index bd767bb173..1606b18b07 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -125,3 +125,4 @@ add-apt-repository -y ppa:openmw/openmw add-apt-repository -y ppa:openmw/openmw-daily add-apt-repository -y ppa:openmw/staging apt-get -qq -o dir::cache::archives="$APT_CACHE_DIR" install -y --no-install-recommends "${deps[@]}" >/dev/null +apt list --installed From e8362c7feddab46ad56c8c47f529a8195c6fe3e2 Mon Sep 17 00:00:00 2001 From: elsid Date: Fri, 10 Nov 2023 18:37:07 +0100 Subject: [PATCH 61/71] Install libyaml-cpp0.8 for integration tests --- CI/install_debian_deps.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CI/install_debian_deps.sh b/CI/install_debian_deps.sh index 1606b18b07..8d7c0a493f 100755 --- a/CI/install_debian_deps.sh +++ b/CI/install_debian_deps.sh @@ -86,7 +86,7 @@ declare -rA GROUPED_DEPS=( libswresample3 libswscale5 libtinyxml2.6.2v5 - libyaml-cpp0.7 + libyaml-cpp0.8 python3-pip xvfb " From dec120f38c80a6e8dc609339ea5ab8ab478c9dee Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Wed, 8 Nov 2023 17:25:41 -0800 Subject: [PATCH 62/71] consistent average scene luminance --- CHANGELOG.md | 1 + apps/openmw/mwrender/luminancecalculator.cpp | 37 ++++++++++++-------- apps/openmw/mwrender/luminancecalculator.hpp | 1 + 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5228662d08..1afca19c6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,6 +96,7 @@ Bug #7654: Tooltips for enchantments with invalid effects cause crashes Bug #7660: Some inconsistencies regarding Invisibility breaking Bug #7675: Successful lock spell doesn't produce a sound + Bug #7679: Scene luminance value flashes when toggling shaders Feature #3537: Shader-based water ripples Feature #5492: Let rain and snow collide with statics Feature #6149: Dehardcode Lua API_REVISION diff --git a/apps/openmw/mwrender/luminancecalculator.cpp b/apps/openmw/mwrender/luminancecalculator.cpp index 5b7fe272aa..ae29b7fdcc 100644 --- a/apps/openmw/mwrender/luminancecalculator.cpp +++ b/apps/openmw/mwrender/luminancecalculator.cpp @@ -20,11 +20,6 @@ namespace MWRender mResolveProgram = shaderManager.getProgram(vertex, std::move(resolveFragment)); mLuminanceProgram = shaderManager.getProgram(vertex, std::move(luminanceFragment)); - } - - void LuminanceCalculator::compile() - { - int mipmapLevels = osg::Image::computeNumberOfMipmapLevels(mWidth, mHeight); for (auto& buffer : mBuffers) { @@ -38,7 +33,6 @@ namespace MWRender osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR_MIPMAP_NEAREST); buffer.mipmappedSceneLuminanceTex->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR); buffer.mipmappedSceneLuminanceTex->setTextureSize(mWidth, mHeight); - buffer.mipmappedSceneLuminanceTex->setNumMipmapLevels(mipmapLevels); buffer.luminanceTex = new osg::Texture2D; buffer.luminanceTex->setInternalFormat(GL_R16F); @@ -62,14 +56,6 @@ namespace MWRender buffer.luminanceProxyFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(buffer.luminanceProxyTex)); - buffer.resolveSceneLumFbo = new osg::FrameBufferObject; - buffer.resolveSceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, - osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex, mipmapLevels - 1)); - - buffer.sceneLumFbo = new osg::FrameBufferObject; - buffer.sceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, - osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex)); - buffer.sceneLumSS = new osg::StateSet; buffer.sceneLumSS->setAttributeAndModes(mLuminanceProgram); buffer.sceneLumSS->addUniform(new osg::Uniform("sceneTex", 0)); @@ -84,6 +70,26 @@ namespace MWRender mBuffers[0].resolveSS->setTextureAttributeAndModes(1, mBuffers[1].luminanceTex); mBuffers[1].resolveSS->setTextureAttributeAndModes(1, mBuffers[0].luminanceTex); + } + + void LuminanceCalculator::compile() + { + int mipmapLevels = osg::Image::computeNumberOfMipmapLevels(mWidth, mHeight); + + for (auto& buffer : mBuffers) + { + buffer.mipmappedSceneLuminanceTex->setTextureSize(mWidth, mHeight); + buffer.mipmappedSceneLuminanceTex->setNumMipmapLevels(mipmapLevels); + buffer.mipmappedSceneLuminanceTex->dirtyTextureObject(); + + buffer.resolveSceneLumFbo = new osg::FrameBufferObject; + buffer.resolveSceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, + osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex, mipmapLevels - 1)); + + buffer.sceneLumFbo = new osg::FrameBufferObject; + buffer.sceneLumFbo->setAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, + osg::FrameBufferAttachment(buffer.mipmappedSceneLuminanceTex)); + } mCompiled = true; } @@ -114,13 +120,14 @@ namespace MWRender buffer.luminanceProxyFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); ext->glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); - if (dirty) + if (mIsBlank) { // Use current frame data for previous frame to warm up calculations and prevent popin mBuffers[(frameId + 1) % 2].resolveFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); ext->glBlitFramebuffer(0, 0, 1, 1, 0, 0, 1, 1, GL_COLOR_BUFFER_BIT, GL_NEAREST); buffer.luminanceProxyFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + mIsBlank = false; } buffer.resolveFbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); diff --git a/apps/openmw/mwrender/luminancecalculator.hpp b/apps/openmw/mwrender/luminancecalculator.hpp index 71ea2f7971..8b51081e2f 100644 --- a/apps/openmw/mwrender/luminancecalculator.hpp +++ b/apps/openmw/mwrender/luminancecalculator.hpp @@ -58,6 +58,7 @@ namespace MWRender bool mCompiled = false; bool mEnabled = false; + bool mIsBlank = true; int mWidth = 1; int mHeight = 1; From 85fcfbafda1226d3eb70e7e8dcd499dcb2f1101c Mon Sep 17 00:00:00 2001 From: Cody Glassman Date: Fri, 10 Nov 2023 21:22:11 -0800 Subject: [PATCH 63/71] apply same logic to render targets, remove UB --- apps/openmw/mwrender/pingpongcanvas.cpp | 44 +++++++++++++++---- apps/openmw/mwrender/pingpongcanvas.hpp | 7 ++- apps/openmw/mwrender/postprocessor.cpp | 24 ++++++++-- components/fx/pass.cpp | 4 -- components/fx/pass.hpp | 1 - components/fx/technique.cpp | 12 +---- components/fx/types.hpp | 11 +++++ .../source/reference/postprocessing/omwfx.rst | 3 ++ 8 files changed, 76 insertions(+), 30 deletions(-) diff --git a/apps/openmw/mwrender/pingpongcanvas.cpp b/apps/openmw/mwrender/pingpongcanvas.cpp index b96b40ff3f..4fecdf87f9 100644 --- a/apps/openmw/mwrender/pingpongcanvas.cpp +++ b/apps/openmw/mwrender/pingpongcanvas.cpp @@ -196,6 +196,39 @@ namespace MWRender } }; + // When textures are created (or resized) we need to either dirty them and/or clear them. + // Otherwise, there will be undefined behavior when reading from a texture that has yet to be written to in a + // later pass. + for (const auto& attachment : mDirtyAttachments) + { + const auto [w, h] + = attachment.mSize.get(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight()); + + attachment.mTarget->setTextureSize(w, h); + if (attachment.mMipMap) + attachment.mTarget->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h)); + attachment.mTarget->dirtyTextureObject(); + + osg::ref_ptr fbo = new osg::FrameBufferObject; + + fbo->setAttachment( + osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0, osg::FrameBufferAttachment(attachment.mTarget)); + fbo->apply(state, osg::FrameBufferObject::DRAW_FRAMEBUFFER); + + glViewport(0, 0, attachment.mTarget->getTextureWidth(), attachment.mTarget->getTextureHeight()); + state.haveAppliedAttribute(osg::StateAttribute::VIEWPORT); + glClearColor(attachment.mClearColor.r(), attachment.mClearColor.g(), attachment.mClearColor.b(), + attachment.mClearColor.a()); + glClear(GL_COLOR_BUFFER_BIT); + + if (attachment.mTarget->getNumMipmapLevels() > 0) + { + state.setActiveTextureUnit(0); + state.applyTextureAttribute(0, attachment.mTarget); + ext->glGenerateMipmap(GL_TEXTURE_2D); + } + } + for (const size_t& index : filtered) { const auto& node = mPasses[index]; @@ -239,16 +272,11 @@ namespace MWRender if (pass.mRenderTarget) { - if (mDirtyAttachments) + if (mDirtyAttachments.size() > 0) { const auto [w, h] = pass.mSize.get(mTextureScene->getTextureWidth(), mTextureScene->getTextureHeight()); - pass.mRenderTexture->setTextureSize(w, h); - if (pass.mMipMap) - pass.mRenderTexture->setNumMipmapLevels(osg::Image::computeNumberOfMipmapLevels(w, h)); - pass.mRenderTexture->dirtyTextureObject(); - // Custom render targets must be shared between frame ids, so it's impossible to double buffer // without expensive copies. That means the only thread-safe place to resize is in the draw // thread. @@ -265,7 +293,6 @@ namespace MWRender if (pass.mRenderTexture->getNumMipmapLevels() > 0) { - state.setActiveTextureUnit(0); state.applyTextureAttribute(0, pass.mRenderTarget->getAttachment(osg::FrameBufferObject::BufferComponent::COLOR_BUFFER0) @@ -336,7 +363,6 @@ namespace MWRender bindDestinationFbo(); } - if (mDirtyAttachments) - mDirtyAttachments = false; + mDirtyAttachments.clear(); } } diff --git a/apps/openmw/mwrender/pingpongcanvas.hpp b/apps/openmw/mwrender/pingpongcanvas.hpp index 0d53fd049a..a03e3591ae 100644 --- a/apps/openmw/mwrender/pingpongcanvas.hpp +++ b/apps/openmw/mwrender/pingpongcanvas.hpp @@ -31,7 +31,10 @@ namespace MWRender void dirty() { mDirty = true; } - void resizeRenderTargets() { mDirtyAttachments = true; } + void setDirtyAttachments(const std::vector& attachments) + { + mDirtyAttachments = attachments; + } const fx::DispatchArray& getPasses() { return mPasses; } @@ -68,7 +71,7 @@ namespace MWRender osg::ref_ptr mTextureNormals; mutable bool mDirty = false; - mutable bool mDirtyAttachments = false; + mutable std::vector mDirtyAttachments; mutable osg::ref_ptr mRenderViewport; mutable osg::ref_ptr mMultiviewResolveFramebuffer; mutable osg::ref_ptr mDestinationFBO; diff --git a/apps/openmw/mwrender/postprocessor.cpp b/apps/openmw/mwrender/postprocessor.cpp index 2527f52df1..2c77981244 100644 --- a/apps/openmw/mwrender/postprocessor.cpp +++ b/apps/openmw/mwrender/postprocessor.cpp @@ -541,6 +541,8 @@ namespace MWRender mNormals = false; mPassLights = false; + std::vector attachmentsToDirty; + for (const auto& technique : mTechniques) { if (!technique->isValid()) @@ -617,7 +619,7 @@ namespace MWRender if (!pass->getTarget().empty()) { - const auto& renderTarget = technique->getRenderTargetsMap()[pass->getTarget()]; + auto& renderTarget = technique->getRenderTargetsMap()[pass->getTarget()]; subPass.mSize = renderTarget.mSize; subPass.mRenderTexture = renderTarget.mTarget; subPass.mMipMap = renderTarget.mMipMap; @@ -628,13 +630,27 @@ namespace MWRender const auto [w, h] = renderTarget.mSize.get(renderWidth(), renderHeight()); subPass.mStateSet->setAttributeAndModes(new osg::Viewport(0, 0, w, h)); + + if (std::find_if(attachmentsToDirty.cbegin(), attachmentsToDirty.cend(), + [renderTarget](const auto& rt) { return renderTarget.mTarget == rt.mTarget; }) + == attachmentsToDirty.cend()) + { + attachmentsToDirty.push_back(fx::Types::RenderTarget(renderTarget)); + } } for (const auto& name : pass->getRenderTargets()) { - subPass.mStateSet->setTextureAttribute(subTexUnit, technique->getRenderTargetsMap()[name].mTarget); + auto& renderTarget = technique->getRenderTargetsMap()[name]; + subPass.mStateSet->setTextureAttribute(subTexUnit, renderTarget.mTarget); subPass.mStateSet->addUniform(new osg::Uniform(name.c_str(), subTexUnit)); + if (std::find_if(attachmentsToDirty.cbegin(), attachmentsToDirty.cend(), + [renderTarget](const auto& rt) { return renderTarget.mTarget == rt.mTarget; }) + == attachmentsToDirty.cend()) + { + attachmentsToDirty.push_back(fx::Types::RenderTarget(renderTarget)); + } subTexUnit++; } @@ -654,7 +670,7 @@ namespace MWRender mRendering.getSkyManager()->setSunglare(sunglare); if (dirtyAttachments) - mCanvases[frameId]->resizeRenderTargets(); + mCanvases[frameId]->setDirtyAttachments(attachmentsToDirty); } PostProcessor::Status PostProcessor::enableTechnique( @@ -668,7 +684,7 @@ namespace MWRender int pos = std::min(location.value_or(mTechniques.size()), mTechniques.size()); mTechniques.insert(mTechniques.begin() + pos, technique); - dirtyTechniques(); + dirtyTechniques(Settings::ShaderManager::get().getMode() == Settings::ShaderManager::Mode::Debug); return Status_Toggled; } diff --git a/components/fx/pass.cpp b/components/fx/pass.cpp index daaaf22968..d20813f1ce 100644 --- a/components/fx/pass.cpp +++ b/components/fx/pass.cpp @@ -12,7 +12,6 @@ #include #include -#include #include #include #include @@ -326,9 +325,6 @@ float omw_EstimateFogCoverageFromUV(vec2 uv) if (mBlendEq) stateSet->setAttributeAndModes(new osg::BlendEquation(mBlendEq.value())); - - if (mClearColor) - stateSet->setAttributeAndModes(new SceneUtil::ClearColor(mClearColor.value(), GL_COLOR_BUFFER_BIT)); } void Pass::dirty() diff --git a/components/fx/pass.hpp b/components/fx/pass.hpp index 509127a163..e176afc699 100644 --- a/components/fx/pass.hpp +++ b/components/fx/pass.hpp @@ -72,7 +72,6 @@ namespace fx std::array mRenderTargets; std::string mTarget; - std::optional mClearColor; std::optional mBlendSource; std::optional mBlendDest; diff --git a/components/fx/technique.cpp b/components/fx/technique.cpp index 3abcd8c0ba..defb581cfc 100644 --- a/components/fx/technique.cpp +++ b/components/fx/technique.cpp @@ -313,6 +313,8 @@ namespace fx rt.mTarget->setSourceFormat(parseSourceFormat()); else if (key == "mipmaps") rt.mMipMap = parseBool(); + else if (key == "clear_color") + rt.mClearColor = parseVec(); else error(Misc::StringUtils::format("unexpected key '%s'", std::string(key))); @@ -798,9 +800,6 @@ namespace fx if (!pass) pass = std::make_shared(); - bool clear = true; - osg::Vec4f clearColor = { 1, 1, 1, 1 }; - while (!isNext()) { expect("invalid key in block header"); @@ -844,10 +843,6 @@ namespace fx if (blendEq != osg::BlendEquation::FUNC_ADD) pass->mBlendEq = blendEq; } - else if (key == "clear") - clear = parseBool(); - else if (key == "clear_color") - clearColor = parseVec(); else error(Misc::StringUtils::format("unrecognized key '%s' in block header", std::string(key))); @@ -865,9 +860,6 @@ namespace fx return; } - if (clear) - pass->mClearColor = clearColor; - error("malformed block header"); } diff --git a/components/fx/types.hpp b/components/fx/types.hpp index 0683126dec..0f33d29e1a 100644 --- a/components/fx/types.hpp +++ b/components/fx/types.hpp @@ -63,6 +63,17 @@ namespace fx osg::ref_ptr mTarget = new osg::Texture2D; SizeProxy mSize; bool mMipMap = false; + osg::Vec4f mClearColor = osg::Vec4f(0.0, 0.0, 0.0, 1.0); + + RenderTarget() = default; + + RenderTarget(const RenderTarget& other) + : mTarget(other.mTarget) + , mSize(other.mSize) + , mMipMap(other.mMipMap) + , mClearColor(other.mClearColor) + { + } }; template diff --git a/docs/source/reference/postprocessing/omwfx.rst b/docs/source/reference/postprocessing/omwfx.rst index 8ff075bc6d..6d191e0a7b 100644 --- a/docs/source/reference/postprocessing/omwfx.rst +++ b/docs/source/reference/postprocessing/omwfx.rst @@ -539,6 +539,8 @@ is not wanted and you want a custom render target. +------------------+---------------------+-----------------------------------------------------------------------------+ | mipmaps | boolean | Whether mipmaps should be generated every frame | +------------------+---------------------+-----------------------------------------------------------------------------+ +| clear_color | vec4 | The color the texture will be cleared to when it's first created | ++------------------+---------------------+-----------------------------------------------------------------------------+ To use the render target a pass must be assigned to it, along with any optional blend modes. As a restriction, only three render targets can be bound per pass with ``rt1``, ``rt2``, ``rt3``, respectively. @@ -555,6 +557,7 @@ color buffer will accumulate. source_format = rgb; internal_format = rgb16f; source_type = float; + clear_color = vec4(1,0,0,1); } fragment red(target=RT_Red,blend=(add, src_color, one), rt1=RT_Red) { From 96178a260536a8d9173763650ac8eca5f38af7cc Mon Sep 17 00:00:00 2001 From: Zackhasacat Date: Sat, 11 Nov 2023 16:30:16 +0000 Subject: [PATCH 64/71] Allow Shoulder Buttons on Controller to act as Tab and Shift Tab in menus --- apps/openmw/mwgui/keyboardnavigation.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/openmw/mwgui/keyboardnavigation.cpp b/apps/openmw/mwgui/keyboardnavigation.cpp index a8fb52c95e..85c7d8ba88 100644 --- a/apps/openmw/mwgui/keyboardnavigation.cpp +++ b/apps/openmw/mwgui/keyboardnavigation.cpp @@ -183,6 +183,10 @@ namespace MWGui return switchFocus(D_Down, false); case MyGUI::KeyCode::Tab: return switchFocus(MyGUI::InputManager::getInstance().isShiftPressed() ? D_Prev : D_Next, true); + case MyGUI::KeyCode::Period: + return switchFocus(D_Prev, true); + case MyGUI::KeyCode::Slash: + return switchFocus(D_Next, true); case MyGUI::KeyCode::Return: case MyGUI::KeyCode::NumpadEnter: case MyGUI::KeyCode::Space: From a2de47080478bf8d433c6f3a20c6c2e624738b19 Mon Sep 17 00:00:00 2001 From: kuyondo Date: Sun, 12 Nov 2023 12:22:35 +0800 Subject: [PATCH 65/71] minor partial equipping fix --- apps/openmw/mwgui/inventorywindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/inventorywindow.cpp b/apps/openmw/mwgui/inventorywindow.cpp index 16f135df17..932723c3fd 100644 --- a/apps/openmw/mwgui/inventorywindow.cpp +++ b/apps/openmw/mwgui/inventorywindow.cpp @@ -566,8 +566,8 @@ namespace MWGui MWWorld::InventoryStore& invStore = mPtr.getClass().getInventoryStore(mPtr); invStore.unequipItemQuantity(ptr, count); updateItemView(); - mEquippedStackableCount.reset(); } + mEquippedStackableCount.reset(); } if (isVisible()) From 155b07f341ab347c7c2f82d8e976ff8cbc5210f4 Mon Sep 17 00:00:00 2001 From: Andrei Kortunov Date: Sun, 12 Nov 2023 12:09:54 +0400 Subject: [PATCH 66/71] Do not update WindowManager by world data when there is no game --- apps/openmw/engine.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index c8c1ada5a9..92483bd8c3 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -318,9 +318,14 @@ bool OMW::Engine::frame(float frametime) mViewer->eventTraversal(); mViewer->updateTraversal(); + // update GUI by world data { ScopedProfile profile(frameStart, frameNumber, *timer, *stats); - mWorld->updateWindowManager(); + + if (mStateManager->getState() != MWBase::StateManager::State_NoGame) + { + mWorld->updateWindowManager(); + } } mLuaWorker->allowUpdate(); // if there is a separate Lua thread, it starts the update now From 01316f15b8b3b3c6e46af3ec571e6b4c2d08b552 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 12 Nov 2023 16:20:57 +0100 Subject: [PATCH 67/71] Avoid redundant conversion to string --- apps/openmw/mwgui/windowmanagerimp.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 1d41bab34f..a9ae3bb55a 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1115,7 +1115,7 @@ namespace MWGui else { std::vector split; - Misc::StringUtils::split(std::string{ tag }, split, ":"); + Misc::StringUtils::split(tag, split, ":"); l10n::Manager& l10nManager = *MWBase::Environment::get().getL10nManager(); From 8f27178a0bc2f0a91b1583198b168d705ab9d9d8 Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 12 Nov 2023 13:26:39 +0100 Subject: [PATCH 68/71] Use settings values for navigator in the launcher --- apps/launcher/datafilespage.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index b6192d3c02..4d3f0cc64f 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include "utils/profilescombobox.hpp" @@ -123,7 +123,7 @@ namespace Launcher int getMaxNavMeshDbFileSizeMiB() { - return Settings::Manager::getUInt64("max navmeshdb file size", "Navigator") / (1024 * 1024); + return Settings::navigator().mMaxNavmeshdbFileSize / (1024 * 1024); } std::optional findFirstPath(const QStringList& directories, const QString& fileName) @@ -359,9 +359,8 @@ void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) void Launcher::DataFilesPage::saveSettings(const QString& profile) { - if (const int value = ui.navMeshMaxSizeSpinBox->value(); value != getMaxNavMeshDbFileSizeMiB()) - Settings::Manager::setUInt64( - "max navmeshdb file size", "Navigator", static_cast(std::max(0, value)) * 1024 * 1024); + Settings::navigator().mMaxNavmeshdbFileSize.set( + static_cast(std::max(0, ui.navMeshMaxSizeSpinBox->value())) * 1024 * 1024); QString profileName = profile; From 1fa5d2ca9885c013aafe6320f166ec58f84cb09e Mon Sep 17 00:00:00 2001 From: elsid Date: Sun, 12 Nov 2023 13:26:39 +0100 Subject: [PATCH 69/71] Use settings values for GUI tags --- apps/openmw/mwgui/windowmanagerimp.cpp | 2 +- components/settings/categories/gui.hpp | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index a9ae3bb55a..d98b7472bb 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -1100,7 +1100,7 @@ namespace MWGui std::string_view settingSection = tag.substr(0, comma_pos); std::string_view settingTag = tag.substr(comma_pos + 1, tag.length()); - _result = Settings::Manager::getString(settingTag, settingSection); + _result = Settings::get(settingSection, settingTag).get().print(); } else if (tag.starts_with(tokenToFind)) { diff --git a/components/settings/categories/gui.hpp b/components/settings/categories/gui.hpp index af438d5ddc..4a5e50fd8a 100644 --- a/components/settings/categories/gui.hpp +++ b/components/settings/categories/gui.hpp @@ -28,10 +28,8 @@ namespace Settings SettingValue mSubtitles{ mIndex, "GUI", "subtitles" }; SettingValue mHitFader{ mIndex, "GUI", "hit fader" }; SettingValue mWerewolfOverlay{ mIndex, "GUI", "werewolf overlay" }; - SettingValue mColorBackgroundOwned{ mIndex, "GUI", "color background owned", - makeClampSanitizerFloat(0, 1) }; - SettingValue mColorCrosshairOwned{ mIndex, "GUI", "color crosshair owned", - makeClampSanitizerFloat(0, 1) }; + SettingValue mColorBackgroundOwned{ mIndex, "GUI", "color background owned" }; + SettingValue mColorCrosshairOwned{ mIndex, "GUI", "color crosshair owned" }; SettingValue mKeyboardNavigation{ mIndex, "GUI", "keyboard navigation" }; SettingValue mColorTopicEnable{ mIndex, "GUI", "color topic enable" }; SettingValue mColorTopicSpecific{ mIndex, "GUI", "color topic specific" }; From d76ae20c29ee65419d79446a9411c754de99ec86 Mon Sep 17 00:00:00 2001 From: Dave Corley Date: Sun, 12 Nov 2023 23:59:52 +0000 Subject: [PATCH 70/71] Feat(textedit): Set max text length for lua textEdit boxes to int_max by default --- components/lua_ui/textedit.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/lua_ui/textedit.cpp b/components/lua_ui/textedit.cpp index a8c19fa8fd..e12bd20c35 100644 --- a/components/lua_ui/textedit.cpp +++ b/components/lua_ui/textedit.cpp @@ -8,6 +8,7 @@ namespace LuaUi { mEditBox = createWidget("LuaTextEdit", MyGUI::IntCoord(0, 0, 0, 0), MyGUI::Align::Default); mEditBox->eventEditTextChange += MyGUI::newDelegate(this, &LuaTextEdit::textChange); + mEditBox->setMaxTextLength(std::numeric_limits::max()); registerEvents(mEditBox); WidgetExtension::initialize(); } From c7d5ea9fbf221c425400903a2ffd56160f0e843e Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Tue, 14 Nov 2023 00:44:23 +0300 Subject: [PATCH 71/71] Improve BulletNifLoader handling of extra data Only handle extra data for the root node(s) Properly handle MRK flag editor marker filtering Fix BSXFlags test --- .../nifloader/testbulletnifloader.cpp | 139 +++++++++--------- components/nifbullet/bulletnifloader.cpp | 62 ++++---- components/nifbullet/bulletnifloader.hpp | 1 + 3 files changed, 99 insertions(+), 103 deletions(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 58e1073307..7dce21bac6 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -957,12 +957,12 @@ namespace } TEST_F(TestBulletNifLoader, - for_tri_shape_child_node_with_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) + for_root_node_with_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) { mNiStringExtraData.mData = "NCC__"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; - mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); @@ -985,13 +985,13 @@ namespace } TEST_F(TestBulletNifLoader, - for_tri_shape_child_node_with_not_first_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) + for_root_node_with_not_first_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) { mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2); mNiStringExtraData2.mData = "NCC__"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; - mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); @@ -1012,8 +1012,62 @@ namespace EXPECT_EQ(*result, expected); } + TEST_F( + TestBulletNifLoader, for_root_node_with_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) + { + mNiStringExtraData.mData = "NC___"; + mNiStringExtraData.recType = Nif::RC_NiStringExtraData; + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; + + Nif::NIFFile file("test.nif"); + file.mRoots.push_back(&mNiNode); + file.mHash = mHash; + + const auto result = mLoader.load(file); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr compound(new btCompoundShape); + compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + + Resource::BulletShape expected; + expected.mCollisionShape.reset(compound.release()); + expected.mVisualCollisionType = Resource::VisualCollisionType::Default; + + EXPECT_EQ(*result, expected); + } + TEST_F(TestBulletNifLoader, - for_tri_shape_child_node_with_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) + for_root_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) + { + mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2); + mNiStringExtraData2.mData = "NC___"; + mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; + mNiTriShape.mParents.push_back(&mNiNode); + mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; + + Nif::NIFFile file("test.nif"); + file.mRoots.push_back(&mNiNode); + file.mHash = mHash; + + const auto result = mLoader.load(file); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr compound(new btCompoundShape); + compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + + Resource::BulletShape expected; + expected.mCollisionShape.reset(compound.release()); + expected.mVisualCollisionType = Resource::VisualCollisionType::Default; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_should_ignore_extra_data) { mNiStringExtraData.mData = "NC___"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; @@ -1034,35 +1088,6 @@ namespace Resource::BulletShape expected; expected.mCollisionShape.reset(compound.release()); - expected.mVisualCollisionType = Resource::VisualCollisionType::Default; - - EXPECT_EQ(*result, expected); - } - - TEST_F(TestBulletNifLoader, - for_tri_shape_child_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) - { - mNiStringExtraData.mNext = Nif::ExtraPtr(&mNiStringExtraData2); - mNiStringExtraData2.mData = "NC___"; - mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; - mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.mParents.push_back(&mNiNode); - mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; - - Nif::NIFFile file("test.nif"); - file.mRoots.push_back(&mNiNode); - file.mHash = mHash; - - const auto result = mLoader.load(file); - - std::unique_ptr triangles(new btTriangleMesh(false)); - triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); - std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); - - Resource::BulletShape expected; - expected.mCollisionShape.reset(compound.release()); - expected.mVisualCollisionType = Resource::VisualCollisionType::Default; EXPECT_EQ(*result, expected); } @@ -1101,33 +1126,13 @@ namespace EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, - for_tri_shape_child_node_with_extra_data_string_mrk_should_return_shape_with_null_collision_shape) - { - mNiStringExtraData.mData = "MRK"; - mNiStringExtraData.recType = Nif::RC_NiStringExtraData; - mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.mParents.push_back(&mNiNode); - mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; - - Nif::NIFFile file("test.nif"); - file.mRoots.push_back(&mNiNode); - file.mHash = mHash; - - const auto result = mLoader.load(file); - - Resource::BulletShape expected; - - EXPECT_EQ(*result, expected); - } - TEST_F(TestBulletNifLoader, bsx_editor_marker_flag_disables_collision_for_markers) { - mNiIntegerExtraData.mData = 34; // BSXFlags "has collision" | "editor marker" - mNiIntegerExtraData.recType = Nif::RC_BSXFlags; - mNiTriShape.mExtraList.push_back(Nif::ExtraPtr(&mNiIntegerExtraData)); mNiTriShape.mParents.push_back(&mNiNode); mNiTriShape.mName = "EditorMarker"; + mNiIntegerExtraData.mData = 34; // BSXFlags "has collision" | "editor marker" + mNiIntegerExtraData.recType = Nif::RC_BSXFlags; + mNiNode.mExtraList.push_back(Nif::ExtraPtr(&mNiIntegerExtraData)); mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); @@ -1142,18 +1147,14 @@ namespace EXPECT_EQ(*result, expected); } - 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) + TEST_F(TestBulletNifLoader, mrk_editor_marker_flag_disables_collision_for_markers) { + mNiTriShape.mParents.push_back(&mNiNode); + mNiTriShape.mName = "Tri EditorMarker"; mNiStringExtraData.mData = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; - mNiTriShape.mExtra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.mParents.push_back(&mNiNode2); - mNiNode2.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; - mNiNode2.recType = Nif::RC_RootCollisionNode; - mNiNode2.mParents.push_back(&mNiNode); - mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiNode2) }; - mNiNode.recType = Nif::RC_NiNode; + mNiNode.mExtra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode.mChildren = Nif::NiAVObjectList{ Nif::NiAVObjectPtr(&mNiTriShape) }; Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); @@ -1161,13 +1162,7 @@ namespace const auto result = mLoader.load(file); - std::unique_ptr triangles(new btTriangleMesh(false)); - triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); - std::unique_ptr compound(new btCompoundShape); - compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); - Resource::BulletShape expected; - expected.mCollisionShape.reset(compound.release()); EXPECT_EQ(*result, expected); } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 66c7eea12d..ec46afec41 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -263,6 +263,32 @@ namespace NifBullet args.mAutogenerated = colNode == nullptr; + // Check for extra data + for (const auto& e : node.getExtraList()) + { + if (e->recType == Nif::RC_NiStringExtraData) + { + // String markers may contain important information + // affecting the entire subtree of this node + auto sd = static_cast(e.getPtr()); + + // Editor marker flag + if (sd->mData == "MRK") + args.mHasTriMarkers = true; + else if (Misc::StringUtils::ciStartsWith(sd->mData, "NC")) + { + // NC prefix is case-insensitive but the second C in NCC flag needs be uppercase. + + // Collide only with camera. + if (sd->mData.length() > 2 && sd->mData[2] == 'C') + mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; + // No collision. + else + mShape->mVisualCollisionType = Resource::VisualCollisionType::Default; + } + } + } + // FIXME: BulletNifLoader should never have to provide rendered geometry for camera collision if (colNode && colNode->mChildren.empty()) { @@ -323,35 +349,6 @@ namespace NifBullet if (node.recType == Nif::RC_AvoidNode) args.mAvoid = true; - // Check for extra data - for (const auto& e : node.getExtraList()) - { - if (e->recType == Nif::RC_NiStringExtraData) - { - // String markers may contain important information - // affecting the entire subtree of this node - auto sd = static_cast(e.getPtr()); - - 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->mData.length() > 2 && sd->mData[2] == 'C') - // Collide only with camera. - mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; - else - // No collision. - mShape->mVisualCollisionType = Resource::VisualCollisionType::Default; - } - // Don't autogenerate collision if MRK is set. - // FIXME: verify if this covers the entire subtree - else if (sd->mData == "MRK" && args.mAutogenerated) - { - return; - } - } - } - if ((args.mAutogenerated || args.mIsCollisionNode) && isTypeNiGeometry(node.recType)) handleNiTriShape(static_cast(node), parent, args); @@ -373,11 +370,14 @@ namespace NifBullet void BulletNifLoader::handleNiTriShape( const Nif::NiGeometry& niGeometry, const Nif::Parent* nodeParent, HandleNodeArgs args) { - // mHasMarkers is specifically BSXFlags editor marker flag. - // If this changes, the check must be corrected. + // This flag comes from BSXFlags if (args.mHasMarkers && Misc::StringUtils::ciStartsWith(niGeometry.mName, "EditorMarker")) return; + // This flag comes from Morrowind + if (args.mHasTriMarkers && Misc::StringUtils::ciStartsWith(niGeometry.mName, "Tri EditorMarker")) + return; + if (niGeometry.mData.empty() || niGeometry.mData->mVertices.empty()) return; diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index 521bbe91dd..c87c1242de 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -53,6 +53,7 @@ namespace NifBullet struct HandleNodeArgs { bool mHasMarkers{ false }; + bool mHasTriMarkers{ false }; bool mAnimated{ false }; bool mIsCollisionNode{ false }; bool mAutogenerated{ false };