diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 72dcd3066..7da8f0fd5 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -373,6 +373,7 @@ namespace TEST_F(TestBulletNifLoader, for_zero_num_roots_should_return_default) { EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(0)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; @@ -422,11 +423,13 @@ namespace { mNode.hasBounds = true; mNode.flags |= Nif::NiNode::Flag_BBoxCollision; - mNode.boundXYZ = osg::Vec3f(1, 2, 3); - mNode.boundPos = osg::Vec3f(-1, -2, -3); + mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); + mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); const auto result = mLoader.load(mNifFile); Resource::BulletShape expected; @@ -444,12 +447,14 @@ namespace { mNode.hasBounds = true; mNode.flags |= Nif::NiNode::Flag_BBoxCollision; - mNode.boundXYZ = osg::Vec3f(1, 2, 3); - mNode.boundPos = osg::Vec3f(-1, -2, -3); + mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); + mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode)})); 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); Resource::BulletShape expected; @@ -467,16 +472,19 @@ namespace { mNode.hasBounds = true; mNode.flags |= Nif::NiNode::Flag_BBoxCollision; - mNode.boundXYZ = osg::Vec3f(1, 2, 3); - mNode.boundPos = osg::Vec3f(-1, -2, -3); + mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); + mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); mNiNode.hasBounds = true; - mNiNode.boundXYZ = osg::Vec3f(4, 5, 6); - mNiNode.boundPos = osg::Vec3f(-4, -5, -6); + mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNiNode.bounds.box.extents = osg::Vec3f(4, 5, 6); + mNiNode.bounds.box.center = osg::Vec3f(-4, -5, -6); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode)})); 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); Resource::BulletShape expected; @@ -494,20 +502,24 @@ namespace { mNode.hasBounds = true; mNode.flags |= Nif::NiNode::Flag_BBoxCollision; - mNode.boundXYZ = osg::Vec3f(1, 2, 3); - mNode.boundPos = osg::Vec3f(-1, -2, -3); + mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); + mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); mNode2.hasBounds = true; - mNode2.boundXYZ = osg::Vec3f(4, 5, 6); - mNode2.boundPos = osg::Vec3f(-4, -5, -6); + mNode2.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode2.bounds.box.extents = osg::Vec3f(4, 5, 6); + mNode2.bounds.box.center = osg::Vec3f(-4, -5, -6); mNiNode.hasBounds = true; - mNiNode.boundXYZ = osg::Vec3f(7, 8, 9); - mNiNode.boundPos = osg::Vec3f(-7, -8, -9); + mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNiNode.bounds.box.extents = osg::Vec3f(7, 8, 9); + mNiNode.bounds.box.center = osg::Vec3f(-7, -8, -9); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode), Nif::NodePtr(&mNode2)})); 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); Resource::BulletShape expected; @@ -524,21 +536,25 @@ namespace TEST_F(TestBulletNifLoader, for_root_and_two_children_where_both_with_bounds_but_only_second_with_flag_should_use_second_bounds) { mNode.hasBounds = true; - mNode.boundXYZ = osg::Vec3f(1, 2, 3); - mNode.boundPos = osg::Vec3f(-1, -2, -3); + mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); + mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); mNode2.hasBounds = true; mNode2.flags |= Nif::NiNode::Flag_BBoxCollision; - mNode2.boundXYZ = osg::Vec3f(4, 5, 6); - mNode2.boundPos = osg::Vec3f(-4, -5, -6); + mNode2.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode2.bounds.box.extents = osg::Vec3f(4, 5, 6); + mNode2.bounds.box.center = osg::Vec3f(-4, -5, -6); mNiNode.hasBounds = true; - mNiNode.boundXYZ = osg::Vec3f(7, 8, 9); - mNiNode.boundPos = osg::Vec3f(-7, -8, -9); + mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNiNode.bounds.box.extents = osg::Vec3f(7, 8, 9); + mNiNode.bounds.box.center = osg::Vec3f(-7, -8, -9); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode), Nif::NodePtr(&mNode2)})); 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); Resource::BulletShape expected; @@ -555,8 +571,9 @@ namespace TEST_F(TestBulletNifLoader, for_root_nif_node_with_bounds_but_without_flag_should_return_shape_with_bounds_but_with_null_collision_shape) { mNode.hasBounds = true; - mNode.boundXYZ = osg::Vec3f(1, 2, 3); - mNode.boundPos = osg::Vec3f(-1, -2, -3); + mNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNode.bounds.box.extents = osg::Vec3f(1, 2, 3); + mNode.bounds.box.center = osg::Vec3f(-1, -2, -3); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); @@ -588,8 +605,9 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_root_node_with_bounds_should_return_shape_with_bounds_but_with_null_collision_shape) { mNiTriShape.hasBounds = true; - mNiTriShape.boundXYZ = osg::Vec3f(1, 2, 3); - mNiTriShape.boundPos = osg::Vec3f(-1, -2, -3); + mNiTriShape.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; + mNiTriShape.bounds.box.extents = osg::Vec3f(1, 2, 3); + mNiTriShape.bounds.box.center = osg::Vec3f(-1, -2, -3); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriShape)); diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 72adfe06c..0b958d2c2 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -16,6 +16,117 @@ namespace Nif struct NiNode; +struct NiBoundingVolume +{ + enum Type + { + SPHERE_BV = 0, + BOX_BV = 1, + CAPSULE_BV = 2, + LOZENGE_BV = 3, + UNION_BV = 4, + HALFSPACE_BV = 5 + }; + + struct NiSphereBV + { + osg::Vec3f center; + float radius{0.f}; + }; + + struct NiBoxBV + { + osg::Vec3f center; + Matrix3 axis; + osg::Vec3f extents; + }; + + struct NiCapsuleBV + { + osg::Vec3f center, axis; + float extent{0.f}, radius{0.f}; + }; + + struct NiLozengeBV + { + float radius{0.f}, extent0{0.f}, extent1{0.f}; + osg::Vec3f center, axis0, axis1; + }; + + struct NiHalfSpaceBV + { + osg::Vec3f center, normal; + }; + + unsigned int type; + NiSphereBV sphere; + NiBoxBV box; + NiCapsuleBV capsule; + NiLozengeBV lozenge; + std::vector children; + NiHalfSpaceBV plane; + void read(NIFStream* nif) + { + type = nif->getUInt(); + switch (type) + { + case SPHERE_BV: + { + sphere.center = nif->getVector3(); + sphere.radius = nif->getFloat(); + break; + } + case BOX_BV: + { + box.center = nif->getVector3(); + box.axis = nif->getMatrix3(); + box.extents = nif->getVector3(); + break; + } + case CAPSULE_BV: + { + capsule.center = nif->getVector3(); + capsule.axis = nif->getVector3(); + capsule.extent = nif->getFloat(); + capsule.radius = nif->getFloat(); + break; + } + case LOZENGE_BV: + { + lozenge.radius = nif->getFloat(); + lozenge.extent0 = nif->getFloat(); + lozenge.extent1 = nif->getFloat(); + lozenge.center = nif->getVector3(); + lozenge.axis0 = nif->getVector3(); + lozenge.axis1 = nif->getVector3(); + break; + } + case UNION_BV: + { + unsigned int numChildren = nif->getUInt(); + if (numChildren == 0) + break; + children.resize(numChildren); + for (NiBoundingVolume& child : children) + child.read(nif); + break; + } + case HALFSPACE_BV: + { + plane.center = nif->getVector3(); + plane.normal = nif->getVector3(); + break; + } + default: + { + std::stringstream error; + error << "Unhandled NiBoundingVolume type: " << type; + nif->file->fail(error.str()); + } + } + } +}; + /** A Node is an object that's part of the main NIF tree. It has parent node (unless it's the root), and transformation (location and rotation) relative to it's parent. @@ -31,9 +142,7 @@ public: // Bounding box info bool hasBounds{false}; - osg::Vec3f boundPos; - Matrix3 boundRot; - osg::Vec3f boundXYZ; // Box size + NiBoundingVolume bounds; void read(NIFStream *nif) override { @@ -48,13 +157,8 @@ public: if (nif->getVersion() <= NIFStream::generateVersion(4,2,2,0)) hasBounds = nif->getBoolean(); - if(hasBounds) - { - nif->getInt(); // always 1 - boundPos = nif->getVector3(); - boundRot = nif->getMatrix3(); - boundXYZ = nif->getVector3(); - } + if (hasBounds) + bounds.read(nif); // Reference to the collision object in Gamebryo files. if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0)) nif->skip(4); diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 5b531121e..b1461e536 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -132,13 +132,14 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) if ((node = dynamic_cast(r))) break; } + const std::string filename = nif.getFilename(); if (!node) { - warn("Found no root nodes in NIF."); + warn("Found no root nodes in NIF file " + filename); return mShape; } - if (findBoundingBox(node)) + if (findBoundingBox(node, filename)) { const btVector3 halfExtents = Misc::Convert::toBullet(mShape->mCollisionBoxHalfExtents); const btVector3 origin = Misc::Convert::toBullet(mShape->mCollisionBoxTranslate); @@ -158,7 +159,6 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) // 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 - const auto filename = nif.getFilename(); const bool isAnimated = pathFileNameStartsWithX(filename); handleNode(filename, node, 0, autogenerated, isAnimated, autogenerated); @@ -194,12 +194,25 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) // Find a boundingBox in the node hierarchy. // Return: use bounding box for collision? -bool BulletNifLoader::findBoundingBox(const Nif::Node* node) +bool BulletNifLoader::findBoundingBox(const Nif::Node* node, const std::string& filename) { if (node->hasBounds) { - mShape->mCollisionBoxHalfExtents = node->boundXYZ; - mShape->mCollisionBoxTranslate = node->boundPos; + unsigned int type = node->bounds.type; + switch (type) + { + case Nif::NiBoundingVolume::Type::BOX_BV: + mShape->mCollisionBoxHalfExtents = node->bounds.box.extents; + mShape->mCollisionBoxTranslate = node->bounds.box.center; + break; + default: + { + std::stringstream warning; + warning << "Unsupported NiBoundingVolume type " << type << " in node " << node->recIndex; + warning << " in file " << filename; + warn(warning.str()); + } + } if (node->flags & Nif::NiNode::Flag_BBoxCollision) { @@ -215,7 +228,7 @@ bool BulletNifLoader::findBoundingBox(const Nif::Node* node) { if(!list[i].empty()) { - bool found = findBoundingBox (list[i].getPtr()); + bool found = findBoundingBox (list[i].getPtr(), filename); if (found) return true; } diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index e423e5149..054b33fed 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -40,7 +40,7 @@ class BulletNifLoader public: void warn(const std::string &msg) { - Log(Debug::Warning) << "NIFLoader: Warn:" << msg; + Log(Debug::Warning) << "NIFLoader: Warn: " << msg; } void fail(const std::string &msg) @@ -52,7 +52,7 @@ public: osg::ref_ptr load(const Nif::File& file); private: - bool findBoundingBox(const Nif::Node* node); + bool findBoundingBox(const Nif::Node* node, const std::string& filename); void handleNode(const std::string& fileName, Nif::Node const *node, int flags, bool isCollisionNode, bool isAnimated=false, bool autogenerated=false, bool avoid=false);