From 8b8c3049539b5944c628ce7159807f7752f41198 Mon Sep 17 00:00:00 2001 From: Petr Mikheev Date: Sun, 3 Jul 2022 02:14:32 +0200 Subject: [PATCH] Treat empty `RootCollisionNode` in NIF as NCC flag and generate CameraOnly collision shape --- .../nifloader/testbulletnifloader.cpp | 35 ++++++++++++++- components/nifbullet/bulletnifloader.cpp | 44 +++++++++++++++---- components/nifbullet/bulletnifloader.hpp | 3 +- 3 files changed, 71 insertions(+), 11 deletions(-) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index e52e2f34a0..21b9e9df3a 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -187,6 +187,7 @@ namespace Resource return compareObjects(lhs.mCollisionShape.get(), rhs.mCollisionShape.get()) && compareObjects(lhs.mAvoidCollisionShape.get(), rhs.mAvoidCollisionShape.get()) && lhs.mCollisionBox == rhs.mCollisionBox + && lhs.mCollisionType == rhs.mCollisionType && lhs.mAnimatedShapes == rhs.mAnimatedShapes; } @@ -197,6 +198,7 @@ namespace Resource << value.mAvoidCollisionShape.get() << ", " << value.mCollisionBox << ", " << value.mAnimatedShapes + << ", collisionType=" << value.mCollisionType << "}"; } } @@ -1034,7 +1036,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_not_first_extra_data_string_equal_ncc_should_return_shape_with_cameraonly_collision) { mNiStringExtraData.next = Nif::ExtraPtr(&mNiStringExtraData2); - mNiStringExtraData2.string = "NC___"; + mNiStringExtraData2.string = "NCC__"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); mNiTriShape.parents.push_back(&mNiNode); @@ -1054,7 +1056,6 @@ namespace EXPECT_EQ(*result, expected); } - TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_starting_with_nc_should_return_shape_with_nocollision) { mNiStringExtraData.string = "NC___"; @@ -1100,6 +1101,36 @@ namespace EXPECT_EQ(*result, expected); } + TEST_F(TestBulletNifLoader, for_empty_root_collision_node_without_nc_should_return_shape_with_cameraonly_collision) + { + Nif::NiTriShape niTriShape; + Nif::NiNode emptyCollisionNode; + init(niTriShape); + init(emptyCollisionNode); + + niTriShape.data = Nif::NiGeometryDataPtr(&mNiTriShapeData); + niTriShape.parents.push_back(&mNiNode); + + emptyCollisionNode.recType = Nif::RC_RootCollisionNode; + emptyCollisionNode.parents.push_back(&mNiNode); + + mNiNode.children = Nif::NodeList(std::vector( + {Nif::NodePtr(&niTriShape), Nif::NodePtr(&emptyCollisionNode)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + Resource::BulletShape expected; + expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); + expected.mCollisionType = Resource::BulletShape::CollisionType::Camera; + + 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.string = "MRK"; diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 49f5e8f069..bae5bd4565 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -220,8 +220,12 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) // from the collision data present in every root node. for (const Nif::Node* node : roots) { - bool autogenerated = hasAutoGeneratedCollision(*node); - handleNode(filename, *node, nullptr, 0, autogenerated, isAnimated, autogenerated, false, mShape->mCollisionType); + bool hasCollisionNode = hasRootCollisionNode(*node); + bool hasCollisionShape = hasCollisionNode && !collisionShapeIsEmpty(*node); + if (hasCollisionNode && !hasCollisionShape) + mShape->mCollisionType = Resource::BulletShape::CollisionType::Camera; + bool generateCollisionShape = !hasCollisionShape; + handleNode(filename, *node, nullptr, 0, generateCollisionShape, isAnimated, generateCollisionShape, false, mShape->mCollisionType); } if (mCompoundShape) @@ -295,18 +299,37 @@ bool BulletNifLoader::findBoundingBox(const Nif::Node& node, const std::string& return false; } -bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node& rootNode) +bool BulletNifLoader::hasRootCollisionNode(const Nif::Node& rootNode) const { if (const Nif::NiNode* ninode = dynamic_cast(&rootNode)) { const Nif::NodeList &list = ninode->children; for(size_t i = 0;i < list.length();i++) { - if(!list[i].empty()) - { - if(list[i].getPtr()->recType == Nif::RC_RootCollisionNode) - return false; - } + if(list[i].empty()) + continue; + if (list[i].getPtr()->recType == Nif::RC_RootCollisionNode) + return true; + } + } + return false; +} + +bool BulletNifLoader::collisionShapeIsEmpty(const Nif::Node& rootNode) const +{ + if (const Nif::NiNode* ninode = dynamic_cast(&rootNode)) + { + const Nif::NodeList &list = ninode->children; + for(size_t i = 0;i < list.length();i++) + { + if(list[i].empty()) + continue; + const Nif::Node* childNode = list[i].getPtr(); + if (childNode->recType != Nif::RC_RootCollisionNode) + continue; + const Nif::NiNode* niChildnode = static_cast(childNode); // RootCollisionNode is always a NiNode + if (childNode->hasBounds || niChildnode->children.length() > 0) + return false; } } return true; @@ -319,6 +342,11 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& n if (node.recType == Nif::RC_NiCollisionSwitch && !node.collisionActive()) return; + // If RootCollisionNode is empty we treat it as NCC flag and autogenerate collision shape as there was no RootCollisionNode. + // So ignoring it here if `autogenerated` is true and collisionType was set to `Camera`. + if (node.recType == Nif::RC_RootCollisionNode && autogenerated && collisionType == Resource::BulletShape::CollisionType::Camera) + return; + // Accumulate the flags from all the child nodes. This works for all // the flags we currently use, at least. flags |= node.flags; diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index e90c882bc3..c674bbbd13 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -59,7 +59,8 @@ private: void handleNode(const std::string& fileName, const Nif::Node& node,const Nif::Parent* parent, int flags, bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid, unsigned int& cameraOnlyCollision); - bool hasAutoGeneratedCollision(const Nif::Node& rootNode); + bool hasRootCollisionNode(const Nif::Node& rootNode) const; + bool collisionShapeIsEmpty(const Nif::Node& rootNode) const; void handleNiTriShape(const Nif::NiGeometry& nifNode, const Nif::Parent* parent, const osg::Matrixf& transform, bool isAnimated, bool avoid);