From 40313019efe722635f968dd8e685fe8dfc752825 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 15 Oct 2023 14:14:04 +0300 Subject: [PATCH 1/2] BulletNifLoader updates Refactor root node, visual collision type and filename handling Only handle BSXFlags for the root, handle BSXFlags collision flag and absence Properly distinguish collision node and autogenerated flag --- components/nifbullet/bulletnifloader.cpp | 119 ++++++++++++++--------- components/nifbullet/bulletnifloader.hpp | 9 +- 2 files changed, 77 insertions(+), 51 deletions(-) diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index a72ec36cad..81b68c36dd 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -162,18 +163,17 @@ namespace NifBullet if (node) roots.emplace_back(node); } - const std::string filename = Files::pathToUnicodeString(nif.getFilename()); - mShape->mFileName = filename; + mShape->mFileName = Files::pathToUnicodeString(nif.getFilename()); if (roots.empty()) { - warn("Found no root nodes in NIF file " + filename); + warn("Found no root nodes in NIF file " + mShape->mFileName); return mShape; } - // Try to find a valid bounding box first. If one's found for any root node, use that. for (const Nif::NiAVObject* node : roots) { - if (findBoundingBox(*node, filename)) + // 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); @@ -188,29 +188,18 @@ namespace NifBullet return mShape; } } + + HandleNodeArgs args; + // files with the name convention xmodel.nif usually have keyframes stored in a separate file xmodel.kf (see // Animation::addAnimSource). assume all nodes in the file will be animated // TODO: investigate whether this should and could be optimized. - const bool isAnimated = pathFileNameStartsWithX(filename); + 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) - { - const Nif::NiNode* colNode = findRootCollisionNode(*node); - bool hasCollisionShape = false; - if (colNode != nullptr) - { - if (colNode->mBounds.mType == Nif::BoundingVolume::Type::BASE_BV && !colNode->mChildren.empty()) - hasCollisionShape = true; - else - mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; - } - HandleNodeArgs args; - args.mAutogenerated = args.mIsCollisionNode = !hasCollisionShape; - args.mAnimated = isAnimated; - handleNode(filename, *node, nullptr, args, mShape->mVisualCollisionType); - } + handleRoot(nif, *node, args); if (mCompoundShape) mShape->mCollisionShape = std::move(mCompoundShape); @@ -223,7 +212,7 @@ namespace NifBullet // Find a boundingBox in the node hierarchy. // Return: use bounding box for collision? - bool BulletNifLoader::findBoundingBox(const Nif::NiAVObject& node, const std::string& filename) + bool BulletNifLoader::findBoundingBox(const Nif::NiAVObject& node) { unsigned int type = node.mBounds.mType; switch (type) @@ -238,7 +227,7 @@ namespace NifBullet { std::stringstream warning; warning << "Unsupported BoundingVolume type " << type << " in node " << node.recIndex; - warning << " in file " << filename; + warning << " in file " << mShape->mFileName; warn(warning.str()); } } @@ -249,27 +238,70 @@ namespace NifBullet if (const Nif::NiNode* ninode = dynamic_cast(&node)) { for (const auto& child : ninode->mChildren) - if (!child.empty() && findBoundingBox(child.get(), filename)) + if (!child.empty() && findBoundingBox(child.get())) return true; } return false; } - const Nif::NiNode* BulletNifLoader::findRootCollisionNode(const Nif::NiAVObject& rootNode) const + void BulletNifLoader::handleRoot(Nif::FileView nif, const Nif::NiAVObject& node, HandleNodeArgs args) { - if (const Nif::NiNode* ninode = dynamic_cast(&rootNode)) + // Gamebryo/Bethbryo meshes + if (nif.getVersion() >= Nif::NIFStream::generateVersion(10, 0, 1, 0)) { - for (const auto& child : ninode->mChildren) + // Handle BSXFlags + const Nif::NiIntegerExtraData* bsxFlags = nullptr; + for (const auto& e : node.getExtraList()) + { + if (e->recType == Nif::RC_BSXFlags) + { + bsxFlags = static_cast(e.getPtr()); + break; + } + } + + // Collision flag + if (!bsxFlags || !(bsxFlags->mData & 2)) + return; + + // Editor marker flag + if (bsxFlags->mData & 32) + args.mHasMarkers = true; + + // FIXME: hack, using rendered geometry instead of Bethesda Havok data + args.mAutogenerated = true; + } + // Pre-Gamebryo meshes + else + { + // Handle RootCollisionNode + const Nif::NiNode* colNode = nullptr; + if (const Nif::NiNode* ninode = dynamic_cast(&node)) { - if (!child.empty() && child.getPtr()->recType == Nif::RC_RootCollisionNode) - return static_cast(child.getPtr()); + for (const auto& child : ninode->mChildren) + { + if (!child.empty() && child.getPtr()->recType == Nif::RC_RootCollisionNode) + { + colNode = static_cast(child.getPtr()); + break; + } + } + } + + args.mAutogenerated = colNode == nullptr; + + // FIXME: BulletNifLoader should never have to provide rendered geometry for camera collision + if (colNode && colNode->mChildren.empty()) + { + args.mAutogenerated = true; + mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; } } - return nullptr; + + handleNode(node, nullptr, args); } - void BulletNifLoader::handleNode(const std::string& fileName, const Nif::NiAVObject& node, - const Nif::Parent* parent, HandleNodeArgs args, Resource::VisualCollisionType& visualCollisionType) + void BulletNifLoader::handleNode(const Nif::NiAVObject& node, const Nif::Parent* parent, HandleNodeArgs args) { // TODO: allow on-the fly collision switching via toggling this flag if (node.recType == Nif::RC_NiCollisionSwitch && !node.collisionActive()) @@ -295,20 +327,23 @@ namespace NifBullet if (node.recType == Nif::RC_RootCollisionNode) { - args.mIsCollisionNode = true; if (args.mAutogenerated) { // Encountered a RootCollisionNode inside an autogenerated mesh. // We treat empty RootCollisionNodes as NCC flag (set collisionType to `Camera`) // and generate the camera collision shape based on rendered geometry. - if (visualCollisionType == Resource::VisualCollisionType::Camera) + if (mShape->mVisualCollisionType == Resource::VisualCollisionType::Camera) return; // Otherwise we'll want to notify the user. - Log(Debug::Info) << "RootCollisionNode is not attached to the root node in " << fileName + Log(Debug::Info) << "RootCollisionNode is not attached to the root node in " << mShape->mFileName << ". Treating it as a common NiTriShape."; } + else + { + args.mIsCollisionNode = true; + } } // Don't collide with AvoidNode shapes @@ -330,10 +365,10 @@ namespace NifBullet // uppercase if (sd->mData.length() > 2 && sd->mData[2] == 'C') // Collide only with camera. - visualCollisionType = Resource::VisualCollisionType::Camera; + mShape->mVisualCollisionType = Resource::VisualCollisionType::Camera; else // No collision. - visualCollisionType = Resource::VisualCollisionType::Default; + mShape->mVisualCollisionType = Resource::VisualCollisionType::Default; } // Don't autogenerate collision if MRK is set. // FIXME: verify if this covers the entire subtree @@ -342,15 +377,9 @@ namespace NifBullet return; } } - else if (e->recType == Nif::RC_BSXFlags) - { - auto bsxFlags = static_cast(e.getPtr()); - if (bsxFlags->mData & 32) // Editor marker flag - args.mHasMarkers = true; - } } - if (args.mIsCollisionNode) + if (args.mAutogenerated || args.mIsCollisionNode) { // NOTE: a trishape with bounds, but no BBoxCollision flag should NOT go through handleNiTriShape! // It must be ignored completely. @@ -369,7 +398,7 @@ namespace NifBullet continue; assert(std::find(child->mParents.begin(), child->mParents.end(), ninode) != child->mParents.end()); - handleNode(fileName, child.get(), ¤tParent, args, visualCollisionType); + handleNode(child.get(), ¤tParent, args); } } } diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index 819ba34b34..521bbe91dd 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -48,7 +48,7 @@ namespace NifBullet osg::ref_ptr load(Nif::FileView file); private: - bool findBoundingBox(const Nif::NiAVObject& node, const std::string& filename); + bool findBoundingBox(const Nif::NiAVObject& node); struct HandleNodeArgs { @@ -59,11 +59,8 @@ namespace NifBullet bool mAvoid{ false }; }; - void handleNode(const std::string& fileName, const Nif::NiAVObject& node, const Nif::Parent* parent, - HandleNodeArgs args, Resource::VisualCollisionType& visualCollisionType); - - const Nif::NiNode* findRootCollisionNode(const Nif::NiAVObject& rootNode) const; - + void handleRoot(Nif::FileView nif, const Nif::NiAVObject& node, HandleNodeArgs args); + void handleNode(const Nif::NiAVObject& node, const Nif::Parent* parent, HandleNodeArgs args); void handleNiTriShape(const Nif::NiGeometry& nifNode, const Nif::Parent* parent, HandleNodeArgs args); std::unique_ptr mCompoundShape; From 8db631c6b66c83a43e591ad744a4941aa5bec005 Mon Sep 17 00:00:00 2001 From: Alexei Kotov Date: Sun, 15 Oct 2023 15:32:59 +0300 Subject: [PATCH 2/2] Update BSXFlags test --- apps/openmw_test_suite/nifloader/testbulletnifloader.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 8e8d04d93d..306853f87d 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -1144,7 +1144,7 @@ namespace TEST_F(TestBulletNifLoader, bsx_editor_marker_flag_disables_collision_for_markers) { - mNiIntegerExtraData.mData = 32; // BSX flag "editor marker" + 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); @@ -1154,6 +1154,7 @@ namespace Nif::NIFFile file("test.nif"); file.mRoots.push_back(&mNiNode); file.mHash = mHash; + file.mVersion = Nif::NIFStream::generateVersion(10, 0, 1, 0); const auto result = mLoader.load(file);