From 1b669db017bdc7a61d2602002260b357920467eb Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sat, 20 May 2023 18:43:55 +0200 Subject: [PATCH 1/6] Load ESM4::Tree and ESM4::Furniture --- apps/openmw/mwclass/classes.cpp | 2 ++ apps/openmw/mwclass/esm4base.hpp | 10 ++++++++++ apps/openmw/mwlua/cellbindings.cpp | 6 ++++++ apps/openmw/mwlua/types/types.cpp | 6 ++++++ apps/openmw/mwworld/cellstore.hpp | 6 ++++-- apps/openmw/mwworld/esmstore.cpp | 2 ++ apps/openmw/mwworld/esmstore.hpp | 4 +++- apps/openmw/mwworld/store.cpp | 2 ++ components/esm/records.hpp | 2 ++ components/esm4/loadfurn.cpp | 3 +-- components/esm4/loadfurn.hpp | 6 +++++- components/esm4/loadtree.cpp | 3 +-- components/esm4/loadtree.hpp | 6 ++++-- 13 files changed, 48 insertions(+), 10 deletions(-) diff --git a/apps/openmw/mwclass/classes.cpp b/apps/openmw/mwclass/classes.cpp index a60bdc57af..5b53da317e 100644 --- a/apps/openmw/mwclass/classes.cpp +++ b/apps/openmw/mwclass/classes.cpp @@ -61,9 +61,11 @@ namespace MWClass ESM4Named::registerSelf(); ESM4Named::registerSelf(); ESM4Named::registerSelf(); + ESM4Named::registerSelf(); ESM4Named::registerSelf(); ESM4Named::registerSelf(); ESM4Static::registerSelf(); + ESM4Tree::registerSelf(); ESM4Named::registerSelf(); ESM4Light::registerSelf(); } diff --git a/apps/openmw/mwclass/esm4base.hpp b/apps/openmw/mwclass/esm4base.hpp index 6a28faf2f3..abd4989a68 100644 --- a/apps/openmw/mwclass/esm4base.hpp +++ b/apps/openmw/mwclass/esm4base.hpp @@ -2,6 +2,7 @@ #define GAME_MWCLASS_ESM4BASE_H #include +#include #include #include "../mwgui/tooltips.hpp" @@ -88,6 +89,15 @@ namespace MWClass } }; + class ESM4Tree final : public MWWorld::RegisteredClass> + { + friend MWWorld::RegisteredClass>; + ESM4Tree() + : MWWorld::RegisteredClass>(ESM4::Tree::sRecordId) + { + } + }; + // For records with `mFullName` that should be shown as a tooltip. // All objects with a tooltip can be activated (activation can be handled in Lua). template diff --git a/apps/openmw/mwlua/cellbindings.cpp b/apps/openmw/mwlua/cellbindings.cpp index 83102df61f..a8e448aa6c 100644 --- a/apps/openmw/mwlua/cellbindings.cpp +++ b/apps/openmw/mwlua/cellbindings.cpp @@ -188,6 +188,9 @@ namespace MWLua case ESM::REC_DOOR4: cell.mStore->template forEachType(visitor); break; + case ESM::REC_FURN4: + cell.mStore->template forEachType(visitor); + break; case ESM::REC_INGR4: cell.mStore->template forEachType(visitor); break; @@ -203,6 +206,9 @@ namespace MWLua case ESM::REC_STAT4: cell.mStore->template forEachType(visitor); break; + case ESM::REC_TREE4: + cell.mStore->template forEachType(visitor); + break; case ESM::REC_WEAP4: cell.mStore->template forEachType(visitor); break; diff --git a/apps/openmw/mwlua/types/types.cpp b/apps/openmw/mwlua/types/types.cpp index 5b1d6c5278..8af374af2e 100644 --- a/apps/openmw/mwlua/types/types.cpp +++ b/apps/openmw/mwlua/types/types.cpp @@ -40,11 +40,13 @@ namespace MWLua constexpr std::string_view ESM4Clothing = "ESM4Clothing"; constexpr std::string_view ESM4Container = "ESM4Container"; constexpr std::string_view ESM4Door = "ESM4Door"; + constexpr std::string_view ESM4Furniture = "ESM4Furniture"; constexpr std::string_view ESM4Ingredient = "ESM4Ingredient"; constexpr std::string_view ESM4Light = "ESM4Light"; constexpr std::string_view ESM4MiscItem = "ESM4Miscellaneous"; constexpr std::string_view ESM4Potion = "ESM4Potion"; constexpr std::string_view ESM4Static = "ESM4Static"; + constexpr std::string_view ESM4Tree = "ESM4Tree"; constexpr std::string_view ESM4Weapon = "ESM4Weapon"; } @@ -79,11 +81,13 @@ namespace MWLua { ESM::REC_CLOT4, ObjectTypeName::ESM4Clothing }, { ESM::REC_CONT4, ObjectTypeName::ESM4Container }, { ESM::REC_DOOR4, ObjectTypeName::ESM4Door }, + { ESM::REC_FURN4, ObjectTypeName::ESM4Furniture }, { ESM::REC_INGR4, ObjectTypeName::ESM4Ingredient }, { ESM::REC_LIGH4, ObjectTypeName::ESM4Light }, { ESM::REC_MISC4, ObjectTypeName::ESM4MiscItem }, { ESM::REC_ALCH4, ObjectTypeName::ESM4Potion }, { ESM::REC_STAT4, ObjectTypeName::ESM4Static }, + { ESM::REC_TREE4, ObjectTypeName::ESM4Tree }, { ESM::REC_WEAP4, ObjectTypeName::ESM4Weapon }, }; } @@ -210,11 +214,13 @@ namespace MWLua addType(ObjectTypeName::ESM4Clothing, { ESM::REC_CLOT4 }); addType(ObjectTypeName::ESM4Container, { ESM::REC_CONT4 }); addESM4DoorBindings(addType(ObjectTypeName::ESM4Door, { ESM::REC_DOOR4 }), context); + addType(ObjectTypeName::ESM4Furniture, { ESM::REC_FURN4 }); addType(ObjectTypeName::ESM4Ingredient, { ESM::REC_INGR4 }); addType(ObjectTypeName::ESM4Light, { ESM::REC_LIGH4 }); addType(ObjectTypeName::ESM4MiscItem, { ESM::REC_MISC4 }); addType(ObjectTypeName::ESM4Potion, { ESM::REC_ALCH4 }); addType(ObjectTypeName::ESM4Static, { ESM::REC_STAT4 }); + addType(ObjectTypeName::ESM4Tree, { ESM::REC_TREE4 }); addType(ObjectTypeName::ESM4Weapon, { ESM::REC_WEAP4 }); sol::table typeToPackage = getTypeToPackageTable(context.mLua->sol()); diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index f0690e13a1..c8187328b5 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -68,8 +68,10 @@ namespace ESM4 struct Clothing; struct Container; struct Door; + struct Furniture; struct Ingredient; struct MiscItem; + struct Tree; struct Weapon; } @@ -87,8 +89,8 @@ namespace MWWorld CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, CellRefList, - CellRefList, CellRefList, CellRefList, - CellRefList, CellRefList>; + CellRefList, CellRefList, CellRefList, CellRefList, + CellRefList, CellRefList, CellRefList>; /// \brief Mutable state of a cell class CellStore diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 598ec70a5c..97e28ece33 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -286,8 +286,10 @@ namespace MWWorld case ESM::REC_BOOK4: case ESM::REC_CONT4: case ESM::REC_DOOR4: + case ESM::REC_FURN4: case ESM::REC_INGR4: case ESM::REC_MISC4: + case ESM::REC_TREE4: case ESM::REC_WEAP4: return true; break; diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 1818a3bc02..535f5fb430 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -39,8 +39,10 @@ namespace ESM4 struct Clothing; struct Container; struct Door; + struct Furniture; struct Ingredient; struct MiscItem; + struct Tree; struct Weapon; struct World; struct Land; @@ -122,7 +124,7 @@ namespace MWWorld Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, Store, - Store, Store, Store>; + Store, Store, Store, Store, Store>; private: template diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index d9520dc3b3..639c710119 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1298,9 +1298,11 @@ template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; +template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; +template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; template class MWWorld::TypedDynamicStore; diff --git a/components/esm/records.hpp b/components/esm/records.hpp index 1ef0c72c8f..fe5e6176f8 100644 --- a/components/esm/records.hpp +++ b/components/esm/records.hpp @@ -52,11 +52,13 @@ #include #include #include +#include #include #include #include #include #include +#include #include #include "defs.hpp" diff --git a/components/esm4/loadfurn.cpp b/components/esm4/loadfurn.cpp index 5e0ad81c0d..fe3269ab32 100644 --- a/components/esm4/loadfurn.cpp +++ b/components/esm4/loadfurn.cpp @@ -33,8 +33,7 @@ void ESM4::Furniture::load(ESM4::Reader& reader) { - mFormId = reader.hdr().record.getFormId(); - reader.adjustFormId(mFormId); + mId = reader.getRefIdFromHeader(); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) diff --git a/components/esm4/loadfurn.hpp b/components/esm4/loadfurn.hpp index 8fc8e47d1c..3bc88da4a5 100644 --- a/components/esm4/loadfurn.hpp +++ b/components/esm4/loadfurn.hpp @@ -30,6 +30,9 @@ #include #include +#include +#include + #include "formid.hpp" namespace ESM4 @@ -39,7 +42,7 @@ namespace ESM4 struct Furniture { - FormId mFormId; // from the header + ESM::RefId mId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; @@ -55,6 +58,7 @@ namespace ESM4 // void save(ESM4::Writer& writer) const; // void blank(); + static constexpr ESM::RecNameInts sRecordId = ESM::RecNameInts::REC_FURN4; }; } diff --git a/components/esm4/loadtree.cpp b/components/esm4/loadtree.cpp index fa8dfbca49..efed827341 100644 --- a/components/esm4/loadtree.cpp +++ b/components/esm4/loadtree.cpp @@ -33,8 +33,7 @@ void ESM4::Tree::load(ESM4::Reader& reader) { - mFormId = reader.hdr().record.getFormId(); - reader.adjustFormId(mFormId); + mId = reader.getRefIdFromHeader(); mFlags = reader.hdr().record.flags; while (reader.getSubRecordHeader()) diff --git a/components/esm4/loadtree.hpp b/components/esm4/loadtree.hpp index cc4f88fad6..22921096f3 100644 --- a/components/esm4/loadtree.hpp +++ b/components/esm4/loadtree.hpp @@ -30,7 +30,8 @@ #include #include -#include "formid.hpp" +#include +#include namespace ESM4 { @@ -39,7 +40,7 @@ namespace ESM4 struct Tree { - FormId mFormId; // from the header + ESM::RefId mId; // from the header std::uint32_t mFlags; // from the header, see enum type RecordFlag for details std::string mEditorId; @@ -53,6 +54,7 @@ namespace ESM4 // void save(ESM4::Writer& writer) const; // void blank(); + static constexpr ESM::RecNameInts sRecordId = ESM::RecNameInts::REC_TREE4; }; } From fd90a8c9b484cb9cddcdabc5909af142857d93d1 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 21 May 2023 02:37:30 +0200 Subject: [PATCH 2/6] Hide Nif nodes with name "EditorMarker". --- components/nifbullet/bulletnifloader.cpp | 15 ++++++++------ components/nifbullet/bulletnifloader.hpp | 4 ++-- components/nifosg/nifloader.cpp | 25 +++++++++++++++--------- 3 files changed, 27 insertions(+), 17 deletions(-) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 9f55241ff5..7bf01fdb32 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -182,8 +182,8 @@ namespace NifBullet if (hasCollisionNode && !hasCollisionShape) mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; bool generateCollisionShape = !hasCollisionShape; - handleNode(filename, *node, nullptr, 0, generateCollisionShape, isAnimated, generateCollisionShape, false, - mShape->mVisualCollisionType); + handleNode(filename, nif.getVersion(), *node, nullptr, 0, generateCollisionShape, isAnimated, + generateCollisionShape, false, mShape->mVisualCollisionType); } if (mCompoundShape) @@ -268,8 +268,8 @@ namespace NifBullet return true; } - void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& node, const Nif::Parent* parent, - int flags, bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid, + void BulletNifLoader::handleNode(const std::string& fileName, unsigned int nifVersion, const Nif::Node& node, + const Nif::Parent* parent, int flags, bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid, Resource::VisualCollisionType& visualCollisionType) { // TODO: allow on-the fly collision switching via toggling this flag @@ -328,6 +328,9 @@ namespace NifBullet } } + if (nifVersion > Nif::NIFFile::NIFVersion::VER_MW && Misc::StringUtils::ciEqual(node.name, "EditorMarker")) + return; + if (isCollisionNode) { // NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape! @@ -352,8 +355,8 @@ namespace NifBullet continue; assert(std::find(child->parents.begin(), child->parents.end(), ninode) != child->parents.end()); - handleNode(fileName, child.get(), ¤tParent, flags, isCollisionNode, isAnimated, autogenerated, - avoid, visualCollisionType); + handleNode(fileName, nifVersion, child.get(), ¤tParent, flags, isCollisionNode, isAnimated, + autogenerated, avoid, visualCollisionType); } } } diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index 6128857cb8..5866412de4 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -52,8 +52,8 @@ namespace NifBullet private: bool findBoundingBox(const Nif::Node& node, const std::string& filename); - void handleNode(const std::string& fileName, const Nif::Node& node, const Nif::Parent* parent, int flags, - bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid, + void handleNode(const std::string& fileName, unsigned int nifVersion, const Nif::Node& node, + const Nif::Parent* parent, int flags, bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid, Resource::VisualCollisionType& visualCollisionType); bool hasRootCollisionNode(const Nif::Node& rootNode) const; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 2b245dcee0..c158055d40 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include #include @@ -340,8 +341,8 @@ namespace NifOsg created->setDataVariance(osg::Object::STATIC); for (const Nif::Node* root : roots) { - auto node = handleNode(root, nullptr, nullptr, imageManager, std::vector(), 0, false, - false, false, &textkeys->mTextKeys); + auto node = handleNode(nif.getVersion(), root, nullptr, nullptr, imageManager, + std::vector(), 0, false, false, false, &textkeys->mTextKeys); created->addChild(node); } if (mHasNightDayLabel) @@ -597,13 +598,19 @@ namespace NifOsg return node; } - osg::ref_ptr handleNode(const Nif::Node* nifNode, const Nif::Parent* parent, osg::Group* parentNode, - Resource::ImageManager* imageManager, std::vector boundTextures, int animflags, - bool skipMeshes, bool hasMarkers, bool hasAnimatedParents, SceneUtil::TextKeyMap* textKeys, + osg::ref_ptr handleNode(unsigned int nifVersion, const Nif::Node* nifNode, const Nif::Parent* parent, + osg::Group* parentNode, Resource::ImageManager* imageManager, std::vector boundTextures, + int animflags, bool skipMeshes, bool hasMarkers, bool hasAnimatedParents, SceneUtil::TextKeyMap* textKeys, osg::Node* rootNode = nullptr) { - if (rootNode != nullptr && Misc::StringUtils::ciEqual(nifNode->name, "Bounding Box")) - return nullptr; + if (rootNode) + { + if (Misc::StringUtils::ciEqual(nifNode->name, "Bounding Box")) + return nullptr; + if (nifVersion > Nif::NIFFile::NIFVersion::VER_MW && !Loader::getShowMarkers() + && Misc::StringUtils::ciEqual(nifNode->name, "EditorMarker")) + return nullptr; + } osg::ref_ptr node = createNode(nifNode); @@ -802,8 +809,8 @@ namespace NifOsg const Nif::Parent currentParent{ *ninode, parent }; for (const auto& child : children) if (!child.empty()) - handleNode(child.getPtr(), ¤tParent, currentNode, imageManager, boundTextures, animflags, - skipMeshes, hasMarkers, hasAnimatedParents, textKeys, rootNode); + handleNode(nifVersion, child.getPtr(), ¤tParent, currentNode, imageManager, boundTextures, + animflags, skipMeshes, hasMarkers, hasAnimatedParents, textKeys, rootNode); // Propagate effects to the the direct subgraph instead of the node itself // This simulates their "affected node list" which Morrowind appears to replace with the subgraph (?) From 788a4d32aab74c33cad25ba24ae23e60b705458a Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 21 May 2023 03:28:02 +0200 Subject: [PATCH 3/6] Handle DoubleSided and TreeAnim flags in BSLightingShaderProperty. --- apps/openmw/mwrender/renderingmanager.cpp | 1 + components/nif/property.hpp | 3 +++ components/nifosg/nifloader.cpp | 10 ++++++++++ files/shaders/compatibility/bs/default.frag | 4 +++- 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index d8582ccef1..7d23ff9622 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -172,6 +172,7 @@ namespace MWRender stateset->addUniform(new osg::Uniform("isReflection", false)); stateset->addUniform(new osg::Uniform("windSpeed", 0.0f)); stateset->addUniform(new osg::Uniform("playerPos", osg::Vec3f(0.f, 0.f, 0.f))); + stateset->addUniform(new osg::Uniform("useTreeAnim", false)); } void apply(osg::StateSet* stateset, osg::NodeVisitor* nv) override diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 108fb62a2b..3e45c83ad6 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -138,6 +138,9 @@ namespace Nif unsigned int type{ 0u }, flags1{ 0u }, flags2{ 0u }; float envMapIntensity{ 0.f }; void read(NIFStream* nif) override; + + bool doubleSided() const { return (flags2 >> 4) & 1; } + bool treeAnim() const { return (flags2 >> 29) & 1; } }; struct BSShaderLightingProperty : public BSShaderProperty diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index c158055d40..386f4d30a7 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -2150,6 +2150,8 @@ namespace NifOsg textureSet, texprop->clamp, node->getName(), stateset, imageManager, boundTextures); } handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + if (texprop->doubleSided()) + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); break; } case Nif::RC_BSShaderNoLightingProperty: @@ -2190,6 +2192,8 @@ namespace NifOsg stateset->addUniform(new osg::Uniform("useFalloff", false)); } handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + if (texprop->doubleSided()) + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); break; } case Nif::RC_BSLightingShaderProperty: @@ -2203,6 +2207,10 @@ namespace NifOsg handleTextureSet(texprop->mTextureSet.getPtr(), texprop->mClamp, node->getName(), stateset, imageManager, boundTextures); handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + if (texprop->doubleSided()) + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); + if (texprop->treeAnim()) + stateset->addUniform(new osg::Uniform("useTreeAnim", true)); break; } case Nif::RC_BSEffectShaderProperty: @@ -2253,6 +2261,8 @@ namespace NifOsg stateset->addUniform(new osg::Uniform("useFalloff", false)); // Should use the shader flag stateset->addUniform(new osg::Uniform("falloffParams", texprop->mFalloffParams)); handleTextureControllers(texprop, composite, imageManager, stateset, animflags); + if (texprop->doubleSided()) + stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF); break; } // unused by mw diff --git a/files/shaders/compatibility/bs/default.frag b/files/shaders/compatibility/bs/default.frag index 240ff0907f..8c429947b0 100644 --- a/files/shaders/compatibility/bs/default.frag +++ b/files/shaders/compatibility/bs/default.frag @@ -38,6 +38,7 @@ uniform float far; uniform float alphaRef; uniform float emissiveMult; uniform float specStrength; +uniform bool useTreeAnim; #include "lib/light/lighting.glsl" #include "lib/material/alpha.glsl" @@ -58,7 +59,8 @@ void main() #endif vec4 diffuseColor = getDiffuseColor(); - gl_FragData[0].a *= diffuseColor.a; + if (!useTreeAnim) + gl_FragData[0].a *= diffuseColor.a; gl_FragData[0].a = alphaTest(gl_FragData[0].a, alphaRef); #if @normalMap From 19fb9f8e14350f2beb72589282ccc1440f8a54cd Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 24 May 2023 03:02:42 +0200 Subject: [PATCH 4/6] Handle editor marker bit in BSXFlags --- components/nifbullet/bulletnifloader.cpp | 27 ++++--- components/nifbullet/bulletnifloader.hpp | 4 +- components/nifosg/nifloader.cpp | 97 ++++++++++++++---------- 3 files changed, 78 insertions(+), 50 deletions(-) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 7bf01fdb32..7afe7b177e 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -182,8 +182,8 @@ namespace NifBullet if (hasCollisionNode && !hasCollisionShape) mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; bool generateCollisionShape = !hasCollisionShape; - handleNode(filename, nif.getVersion(), *node, nullptr, 0, generateCollisionShape, isAnimated, - generateCollisionShape, false, mShape->mVisualCollisionType); + handleNode(filename, *node, nullptr, 0, generateCollisionShape, isAnimated, generateCollisionShape, false, + mShape->mVisualCollisionType); } if (mCompoundShape) @@ -268,8 +268,8 @@ namespace NifBullet return true; } - void BulletNifLoader::handleNode(const std::string& fileName, unsigned int nifVersion, const Nif::Node& node, - const Nif::Parent* parent, int flags, bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid, + void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& node, const Nif::Parent* parent, + int flags, bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid, Resource::VisualCollisionType& visualCollisionType) { // TODO: allow on-the fly collision switching via toggling this flag @@ -301,7 +301,13 @@ namespace NifBullet << ". Treating it as a common NiTriShape."; // Check for extra data + std::vector extraCollection; for (Nif::ExtraPtr e = node.extra; !e.empty(); e = e->next) + extraCollection.emplace_back(e); + for (const auto& extraNode : node.extralist) + if (!extraNode.empty()) + extraCollection.emplace_back(extraNode); + for (const auto& e : extraCollection) { if (e->recType == Nif::RC_NiStringExtraData) { @@ -326,11 +332,14 @@ namespace NifBullet return; } } + else if (e->recType == Nif::RC_BSXFlags) + { + auto bsxFlags = static_cast(e.getPtr()); + if (bsxFlags->data & 32) // Editor marker flag + return; + } } - if (nifVersion > Nif::NIFFile::NIFVersion::VER_MW && Misc::StringUtils::ciEqual(node.name, "EditorMarker")) - return; - if (isCollisionNode) { // NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape! @@ -355,8 +364,8 @@ namespace NifBullet continue; assert(std::find(child->parents.begin(), child->parents.end(), ninode) != child->parents.end()); - handleNode(fileName, nifVersion, child.get(), ¤tParent, flags, isCollisionNode, isAnimated, - autogenerated, avoid, visualCollisionType); + handleNode(fileName, child.get(), ¤tParent, flags, isCollisionNode, isAnimated, autogenerated, + avoid, visualCollisionType); } } } diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index 5866412de4..6128857cb8 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -52,8 +52,8 @@ namespace NifBullet private: bool findBoundingBox(const Nif::Node& node, const std::string& filename); - void handleNode(const std::string& fileName, unsigned int nifVersion, const Nif::Node& node, - const Nif::Parent* parent, int flags, bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid, + void handleNode(const std::string& fileName, const Nif::Node& node, const Nif::Parent* parent, int flags, + bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid, Resource::VisualCollisionType& visualCollisionType); bool hasRootCollisionNode(const Nif::Node& rootNode) const; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 386f4d30a7..84aaf8ca05 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -319,6 +319,19 @@ namespace NifOsg } } + struct HandleNodeArgs + { + unsigned int mNifVersion; + Resource::ImageManager* mImageManager; + SceneUtil::TextKeyMap* mTextKeys; + std::vector mBoundTextures = {}; + int mAnimFlags = 0; + bool mSkipMeshes = false; + bool mHasMarkers = false; + bool mHasAnimatedParents = false; + osg::Node* mRootNode = nullptr; + }; + osg::ref_ptr load(Nif::FileView nif, Resource::ImageManager* imageManager) { const size_t numRoots = nif.numRoots(); @@ -341,8 +354,10 @@ namespace NifOsg created->setDataVariance(osg::Object::STATIC); for (const Nif::Node* root : roots) { - auto node = handleNode(nif.getVersion(), root, nullptr, nullptr, imageManager, - std::vector(), 0, false, false, false, &textkeys->mTextKeys); + auto node = handleNode(root, nullptr, nullptr, + { .mNifVersion = nif.getVersion(), + .mImageManager = imageManager, + .mTextKeys = &textkeys->mTextKeys }); created->addChild(node); } if (mHasNightDayLabel) @@ -598,19 +613,11 @@ namespace NifOsg return node; } - osg::ref_ptr handleNode(unsigned int nifVersion, const Nif::Node* nifNode, const Nif::Parent* parent, - osg::Group* parentNode, Resource::ImageManager* imageManager, std::vector boundTextures, - int animflags, bool skipMeshes, bool hasMarkers, bool hasAnimatedParents, SceneUtil::TextKeyMap* textKeys, - osg::Node* rootNode = nullptr) + osg::ref_ptr handleNode( + const Nif::Node* nifNode, const Nif::Parent* parent, osg::Group* parentNode, HandleNodeArgs args) { - if (rootNode) - { - if (Misc::StringUtils::ciEqual(nifNode->name, "Bounding Box")) - return nullptr; - if (nifVersion > Nif::NIFFile::NIFVersion::VER_MW && !Loader::getShowMarkers() - && Misc::StringUtils::ciEqual(nifNode->name, "EditorMarker")) - return nullptr; - } + if (args.mRootNode && Misc::StringUtils::ciEqual(nifNode->name, "Bounding Box")) + return nullptr; osg::ref_ptr node = createNode(nifNode); @@ -624,8 +631,8 @@ namespace NifOsg if (parentNode) parentNode->addChild(node); - if (!rootNode) - rootNode = node; + if (!args.mRootNode) + args.mRootNode = node; // The original NIF record index is used for a variety of features: // - finding the correct emitter node for a particle system @@ -644,10 +651,10 @@ namespace NifOsg for (const auto& e : extraCollection) { - if (e->recType == Nif::RC_NiTextKeyExtraData && textKeys) + if (e->recType == Nif::RC_NiTextKeyExtraData && args.mTextKeys) { const Nif::NiTextKeyExtraData* tk = static_cast(e.getPtr()); - extractTextKeys(tk, *textKeys); + extractTextKeys(tk, *args.mTextKeys); } else if (e->recType == Nif::RC_NiStringExtraData) { @@ -660,7 +667,7 @@ namespace NifOsg if (sd->string == "MRK" && !Loader::getShowMarkers()) { // Marker objects. These meshes are only visible in the editor. - hasMarkers = true; + args.mHasMarkers = true; } else if (sd->string == "BONE") { @@ -672,10 +679,16 @@ namespace NifOsg Misc::OsgUserValues::sExtraData, sd->string.substr(extraDataIdentifer.length())); } } + else if (e->recType == Nif::RC_BSXFlags) + { + auto bsxFlags = static_cast(e.getPtr()); + if (bsxFlags->data & 32) // Editor marker flag + args.mHasMarkers = true; + } } if (nifNode->recType == Nif::RC_NiBSAnimationNode || nifNode->recType == Nif::RC_NiBSParticleNode) - animflags = nifNode->flags; + args.mAnimFlags = nifNode->flags; if (nifNode->recType == Nif::RC_NiSortAdjustNode) { @@ -699,7 +712,7 @@ namespace NifOsg // We still need to animate the hidden bones so the physics system can access them if (nifNode->recType == Nif::RC_RootCollisionNode) { - skipMeshes = true; + args.mSkipMeshes = true; node->setNodeMask(Loader::getHiddenNodeMask()); } @@ -716,8 +729,8 @@ namespace NifOsg } if (!hasVisController) - skipMeshes = true; // skip child meshes, but still create the child node hierarchy for animating - // collision shapes + args.mSkipMeshes = true; // skip child meshes, but still create the child node hierarchy for + // animating collision shapes node->setNodeMask(Loader::getHiddenNodeMask()); } @@ -727,37 +740,44 @@ namespace NifOsg osg::ref_ptr composite = new SceneUtil::CompositeStateSetUpdater; - applyNodeProperties(nifNode, node, composite, imageManager, boundTextures, animflags); + applyNodeProperties(nifNode, node, composite, args.mImageManager, args.mBoundTextures, args.mAnimFlags); const bool isGeometry = isTypeGeometry(nifNode->recType); - if (isGeometry && !skipMeshes) + if (isGeometry && !args.mSkipMeshes) { - const bool isMarker = hasMarkers && Misc::StringUtils::ciStartsWith(nifNode->name, "tri editormarker"); - if (!isMarker && !Misc::StringUtils::ciStartsWith(nifNode->name, "shadow") - && !Misc::StringUtils::ciStartsWith(nifNode->name, "tri shadow")) + bool skip; + if (args.mNifVersion <= Nif::NIFFile::NIFVersion::VER_MW) + { + skip = (args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->name, "tri editormarker")) + || Misc::StringUtils::ciStartsWith(nifNode->name, "shadow") + || Misc::StringUtils::ciStartsWith(nifNode->name, "tri shadow"); + } + else + skip = args.mHasMarkers && Misc::StringUtils::ciStartsWith(nifNode->name, "EditorMarker"); + if (!skip) { Nif::NiSkinInstancePtr skin = static_cast(nifNode)->skin; if (skin.empty()) - handleGeometry(nifNode, parent, node, composite, boundTextures, animflags); + handleGeometry(nifNode, parent, node, composite, args.mBoundTextures, args.mAnimFlags); else - handleSkinnedGeometry(nifNode, parent, node, composite, boundTextures, animflags); + handleSkinnedGeometry(nifNode, parent, node, composite, args.mBoundTextures, args.mAnimFlags); if (!nifNode->controller.empty()) - handleMeshControllers(nifNode, node, composite, boundTextures, animflags); + handleMeshControllers(nifNode, node, composite, args.mBoundTextures, args.mAnimFlags); } } if (nifNode->recType == Nif::RC_NiParticles) - handleParticleSystem(nifNode, parent, node, composite, animflags); + handleParticleSystem(nifNode, parent, node, composite, args.mAnimFlags); if (composite->getNumControllers() > 0) { osg::Callback* cb = composite; if (composite->getNumControllers() == 1) cb = composite->getController(0); - if (animflags & Nif::NiNode::AnimFlag_AutoPlay) + if (args.mAnimFlags & Nif::NiNode::AnimFlag_AutoPlay) node->addCullCallback(cb); else node->addUpdateCallback( @@ -765,10 +785,10 @@ namespace NifOsg } bool isAnimated = false; - handleNodeControllers(nifNode, node, animflags, isAnimated); - hasAnimatedParents |= isAnimated; + handleNodeControllers(nifNode, node, args.mAnimFlags, isAnimated); + args.mHasAnimatedParents |= isAnimated; // Make sure empty nodes and animated shapes are not optimized away so the physics system can find them. - if (isAnimated || (hasAnimatedParents && ((skipMeshes || hasMarkers) || isGeometry))) + if (isAnimated || (args.mHasAnimatedParents && ((args.mSkipMeshes || args.mHasMarkers) || isGeometry))) node->setDataVariance(osg::Object::DYNAMIC); // LOD and Switch nodes must be wrapped by a transform (the current node) to support transformations @@ -809,8 +829,7 @@ namespace NifOsg const Nif::Parent currentParent{ *ninode, parent }; for (const auto& child : children) if (!child.empty()) - handleNode(nifVersion, child.getPtr(), ¤tParent, currentNode, imageManager, boundTextures, - animflags, skipMeshes, hasMarkers, hasAnimatedParents, textKeys, rootNode); + handleNode(child.getPtr(), ¤tParent, currentNode, args); // Propagate effects to the the direct subgraph instead of the node itself // This simulates their "affected node list" which Morrowind appears to replace with the subgraph (?) @@ -819,7 +838,7 @@ namespace NifOsg if (!effect.empty()) { osg::ref_ptr effectStateSet = new osg::StateSet; - if (handleEffect(effect.getPtr(), effectStateSet, imageManager)) + if (handleEffect(effect.getPtr(), effectStateSet, args.mImageManager)) for (unsigned int i = 0; i < currentNode->getNumChildren(); ++i) currentNode->getChild(i)->getOrCreateStateSet()->merge(*effectStateSet); } From bf49855d9fb6ea0f9f102a79f2884ea2890b0f4a Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Wed, 24 May 2023 03:27:13 +0200 Subject: [PATCH 5/6] Test handling of BSXFlags in bulletnifloader --- .../nifloader/testbulletnifloader.cpp | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 44c1ffb1e7..0a8e13831a 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -299,6 +299,7 @@ namespace Nif::NiSkinInstance mNiSkinInstance; Nif::NiStringExtraData mNiStringExtraData; Nif::NiStringExtraData mNiStringExtraData2; + Nif::NiIntegerExtraData mNiIntegerExtraData; Nif::Controller mController; btTransform mTransform{ btMatrix3x3(btQuaternion(btVector3(1, 0, 0), 0.5f)), btVector3(1, 2, 3) }; btTransform mTransformScale2{ btMatrix3x3(btQuaternion(btVector3(1, 0, 0), 0.5f)), btVector3(2, 4, 6) }; @@ -1157,6 +1158,25 @@ namespace EXPECT_EQ(*result, expected); } + TEST_F(TestBulletNifLoader, bsx_editor_marker_flag_disables_collision) + { + mNiIntegerExtraData.data = 32; // BSX flag "editor marker" + mNiIntegerExtraData.recType = Nif::RC_BSXFlags; + mNiTriShape.extralist.push_back(Nif::ExtraPtr(&mNiIntegerExtraData)); + mNiTriShape.parents.push_back(&mNiNode); + mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&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, for_tri_shape_child_node_with_extra_data_string_mrk_and_other_collision_node_should_return_shape_with_triangle_mesh_shape_with_all_meshes) { From 115f1e9800d421fe54e8ec88394571f1e6d070b5 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Fri, 26 May 2023 00:46:04 +0200 Subject: [PATCH 6/6] Fix tree shadows when TreeAnim flag is used --- files/shaders/compatibility/shadowcasting.vert | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/files/shaders/compatibility/shadowcasting.vert b/files/shaders/compatibility/shadowcasting.vert index e36f21a4de..c68bdd5c17 100644 --- a/files/shaders/compatibility/shadowcasting.vert +++ b/files/shaders/compatibility/shadowcasting.vert @@ -5,6 +5,7 @@ varying vec2 diffuseMapUV; varying float alphaPassthrough; uniform int colorMode; +uniform bool useTreeAnim; uniform bool useDiffuseMapForShadowAlpha = true; uniform bool alphaTestShadows = true; @@ -20,7 +21,7 @@ void main(void) else diffuseMapUV = vec2(0.0); // Avoid undefined behaviour if running on hardware predating the concept of dynamically uniform expressions if (colorMode == 2) - alphaPassthrough = gl_Color.a; + alphaPassthrough = useTreeAnim ? 1.0 : gl_Color.a; else // This is uniform, so if it's too low, we might be able to put the position/clip vertex outside the view frustum and skip the fragment shader and rasteriser alphaPassthrough = gl_FrontMaterial.diffuse.a;