diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 3d4628c267..b37a8cd6c0 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -60,6 +60,19 @@ namespace { return isNear(lhs.getOrigin(), rhs.getOrigin()) && isNear(lhs.getBasis(), rhs.getBasis()); } + + struct WriteVec3f + { + osg::Vec3f mValue; + + friend std::ostream& operator <<(std::ostream& stream, const WriteVec3f& value) + { + return stream << "osg::Vec3f {" + << std::setprecision(std::numeric_limits::max_exponent10) << value.mValue.x() << ", " + << std::setprecision(std::numeric_limits::max_exponent10) << value.mValue.y() << ", " + << std::setprecision(std::numeric_limits::max_exponent10) << value.mValue.z() << "}"; + } + }; } static std::ostream& operator <<(std::ostream& stream, const btVector3& value) @@ -122,6 +135,17 @@ static std::ostream& operator <<(std::ostream& stream, const TriangleMeshShape& return stream << "}}"; } +static bool operator ==(const BulletShape::CollisionBox& l, const BulletShape::CollisionBox& r) +{ + const auto tie = [] (const BulletShape::CollisionBox& v) { return std::tie(v.mExtents, v.mCenter); }; + return tie(l) == tie(r); +} + +static std::ostream& operator <<(std::ostream& stream, const BulletShape::CollisionBox& value) +{ + return stream << "CollisionBox {" << WriteVec3f {value.mExtents} << ", " << WriteVec3f {value.mCenter} << "}"; +} + } static std::ostream& operator <<(std::ostream& stream, const btCollisionShape& value) @@ -162,8 +186,7 @@ namespace Resource { return compareObjects(lhs.mCollisionShape.get(), rhs.mCollisionShape.get()) && compareObjects(lhs.mAvoidCollisionShape.get(), rhs.mAvoidCollisionShape.get()) - && lhs.mCollisionBox.mExtents == rhs.mCollisionBox.mExtents - && lhs.mCollisionBox.mCenter == rhs.mCollisionBox.mCenter + && lhs.mCollisionBox == rhs.mCollisionBox && lhs.mAnimatedShapes == rhs.mAnimatedShapes; } @@ -172,8 +195,7 @@ namespace Resource return stream << "Resource::BulletShape {" << value.mCollisionShape.get() << ", " << value.mAvoidCollisionShape.get() << ", " - << "osg::Vec3f {" << value.mCollisionBox.mExtents << "}" << ", " - << "osg::Vec3f {" << value.mCollisionBox.mCenter << "}" << ", " + << value.mCollisionBox << ", " << value.mAnimatedShapes << "}"; } @@ -272,6 +294,12 @@ namespace value.recType = Nif::RC_NiTriShape; } + void init(Nif::NiTriStrips& value) + { + init(static_cast(value)); + value.recType = Nif::RC_NiTriStrips; + } + void init(Nif::NiSkinInstance& value) { value.data = Nif::NiSkinDataPtr(nullptr); @@ -330,6 +358,8 @@ namespace Nif::NiTriShape mNiTriShape; Nif::NiTriShapeData mNiTriShapeData2; Nif::NiTriShape mNiTriShape2; + Nif::NiTriStripsData mNiTriStripsData; + Nif::NiTriStrips mNiTriStrips; Nif::NiSkinInstance mNiSkinInstance; Nif::NiStringExtraData mNiStringExtraData; Nif::NiStringExtraData mNiStringExtraData2; @@ -361,6 +391,7 @@ namespace init(mNiNode3); init(mNiTriShape); init(mNiTriShape2); + init(mNiTriStrips); init(mNiSkinInstance); init(mNiStringExtraData); init(mNiStringExtraData2); @@ -375,6 +406,11 @@ namespace mNiTriShapeData2.vertices = {osg::Vec3f(0, 0, 1), osg::Vec3f(1, 0, 1), osg::Vec3f(1, 1, 1)}; mNiTriShapeData2.triangles = {0, 1, 2}; mNiTriShape2.data = Nif::NiGeometryDataPtr(&mNiTriShapeData2); + + mNiTriStripsData.recType = Nif::RC_NiTriStripsData; + mNiTriStripsData.vertices = {osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0), osg::Vec3f(0, 1, 0)}; + mNiTriStripsData.strips = {{0, 1, 2, 3}}; + mNiTriStrips.data = Nif::NiGeometryDataPtr(&mNiTriStripsData); } }; @@ -389,6 +425,18 @@ namespace EXPECT_EQ(*result, expected); } + TEST_F(TestBulletNifLoader, should_ignore_nullptr_root) + { + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(nullptr)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + TEST_F(TestBulletNifLoader, for_default_root_nif_node_should_return_default) { EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -458,6 +506,7 @@ namespace 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); + mNode.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -483,6 +532,7 @@ namespace 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); + mNode.parent = &mNiNode; mNiNode.hasBounds = true; mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; @@ -513,11 +563,13 @@ namespace 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); + mNode.parent = &mNiNode; mNode2.hasBounds = true; 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); + mNode2.parent = &mNiNode; mNiNode.hasBounds = true; mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; @@ -547,12 +599,14 @@ namespace 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); + mNode.parent = &mNiNode; mNode2.hasBounds = true; mNode2.flags |= Nif::NiNode::Flag_BBoxCollision; 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); + mNode2.parent = &mNiNode; mNiNode.hasBounds = true; mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; @@ -631,6 +685,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_should_return_shape_with_triangle_mesh_shape) { + mNiTriShape.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -649,7 +704,9 @@ namespace TEST_F(TestBulletNifLoader, for_nested_tri_shape_child_should_return_shape_with_triangle_mesh_shape) { mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiNode2)})); + mNiNode2.parent = &mNiNode; mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiTriShape.parent = &mNiNode2; EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); @@ -666,6 +723,8 @@ namespace TEST_F(TestBulletNifLoader, for_two_tri_shape_children_should_return_shape_with_triangle_mesh_shape_with_all_meshes) { + mNiTriShape.parent = &mNiNode; + mNiTriShape2.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape), Nif::NodePtr(&mNiTriShape2) @@ -688,6 +747,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_and_filename_starting_with_x_and_not_empty_skin_should_return_shape_with_triangle_mesh_shape) { mNiTriShape.skin = Nif::NiSkinInstancePtr(&mNiSkinInstance); + mNiTriShape.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -756,9 +816,11 @@ namespace { copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; + mNiTriShape.parent = &mNiNode; copy(mTransform, mNiTriShape2.trafo); mNiTriShape2.trafo.scale = 3; + mNiTriShape2.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape), @@ -825,6 +887,7 @@ namespace mController.flags |= Nif::NiNode::ControllerFlag_Active; copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; + mNiTriShape.parent = &mNiNode; copy(mTransform, mNiTriShape2.trafo); mNiTriShape2.trafo.scale = 3; mNiTriShape2.parent = &mNiNode; @@ -841,7 +904,7 @@ namespace const auto result = mLoader.load(mNifFile); std::unique_ptr triangles(new btTriangleMesh(false)); - triangles->addTriangle(btVector3(1, 2, 3), btVector3(4, 2, 3), btVector3(4, 4.632747650146484375, 1.56172335147857666015625)); + triangles->addTriangle(btVector3(4, 8, 12), btVector3(16, 8, 12), btVector3(16, 18.5309906005859375, 6.246893405914306640625)); std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); mesh->setLocalScaling(btVector3(1, 1, 1)); @@ -860,8 +923,37 @@ namespace EXPECT_EQ(*result, expected); } + TEST_F(TestBulletNifLoader, should_add_static_mesh_to_existing_compound_mesh) + { + mNiTriShape.parent = &mNiNode; + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(2)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getRoot(1)).WillOnce(Return(&mNiTriShape2)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.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)); + + std::unique_ptr triangles2(new btTriangleMesh(false)); + triangles2->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); + + std::unique_ptr compound(new btCompoundShape); + compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles.release(), true)); + compound->addChildShape(btTransform::getIdentity(), new Resource::TriangleMeshShape(triangles2.release(), true)); + + Resource::BulletShape expected; + expected.mCollisionShape.reset(compound.release()); + expected.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } + TEST_F(TestBulletNifLoader, for_root_avoid_node_and_tri_shape_child_node_should_return_shape_with_null_collision_shape) { + mNiTriShape.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode.recType = Nif::RC_AvoidNode; @@ -881,6 +973,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_should_return_shape_with_null_collision_shape) { mNiTriShape.data = Nif::NiGeometryDataPtr(nullptr); + mNiTriShape.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -897,6 +990,7 @@ namespace { auto data = static_cast(mNiTriShape.data.getPtr()); data->triangles.clear(); + mNiTriShape.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -914,6 +1008,7 @@ namespace mNiStringExtraData.string = "NC___"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiTriShape.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -932,6 +1027,7 @@ namespace mNiStringExtraData2.string = "NC___"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiTriShape.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -949,6 +1045,7 @@ namespace mNiStringExtraData.string = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiTriShape.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -966,8 +1063,10 @@ namespace mNiStringExtraData.string = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiTriShape.parent = &mNiNode2; mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode2.recType = Nif::RC_RootCollisionNode; + mNiNode2.parent = &mNiNode; mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiNode2)})); mNiNode.recType = Nif::RC_NiNode; @@ -983,4 +1082,134 @@ namespace EXPECT_EQ(*result, expected); } + + TEST_F(TestBulletNifLoader, should_ignore_tri_shape_data_with_mismatching_data_rec_type) + { + mNiTriShape.data = Nif::NiGeometryDataPtr(&mNiTriStripsData); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriShape)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + const Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_strips_root_node_should_return_shape_with_triangle_mesh_shape) + { + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriStrips)); + 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)); + triangles->addTriangle(btVector3(1, 0, 0), btVector3(0, 1, 0), btVector3(1, 1, 0)); + Resource::BulletShape expected; + expected.mCollisionShape.reset(new Resource::TriangleMeshShape(triangles.release(), true)); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, should_ignore_tri_strips_data_with_mismatching_data_rec_type) + { + mNiTriStrips.data = Nif::NiGeometryDataPtr(&mNiTriShapeData); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriStrips)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + const Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, should_ignore_tri_strips_data_with_empty_strips) + { + mNiTriStripsData.strips.clear(); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriStrips)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + const Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_static_mesh_should_ignore_tri_strips_data_with_less_than_3_strips) + { + mNiTriStripsData.strips.front() = {0, 1}; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriStrips)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + const Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_avoid_collision_mesh_should_ignore_tri_strips_data_with_less_than_3_strips) + { + mNiTriShape.parent = &mNiNode; + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiNode.recType = Nif::RC_AvoidNode; + mNiTriStripsData.strips.front() = {0, 1}; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriStrips)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + const Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_animated_mesh_should_ignore_tri_strips_data_with_less_than_3_strips) + { + mNiTriStripsData.strips.front() = {0, 1}; + mNiTriStrips.parent = &mNiNode; + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriStrips)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); + const auto result = mLoader.load(mNifFile); + + const Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, should_not_add_static_mesh_with_no_triangles_to_compound_shape) + { + mNiTriStripsData.strips.front() = {0, 1}; + mNiTriShape.parent = &mNiNode; + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(2)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getRoot(1)).WillOnce(Return(&mNiTriStrips)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.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)); + + 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.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index f808877a75..6678d8ff74 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -1,6 +1,8 @@ #include "bulletnifloader.hpp" +#include #include +#include #include #include @@ -18,11 +20,11 @@ namespace { -osg::Matrixf getWorldTransform(const Nif::Node *node) +osg::Matrixf getWorldTransform(const Nif::Node& node) { - if(node->parent != nullptr) - return node->trafo.toMatrix() * getWorldTransform(node->parent); - return node->trafo.toMatrix(); + if(node.parent != nullptr) + return node.trafo.toMatrix() * getWorldTransform(*node.parent); + return node.trafo.toMatrix(); } bool pathFileNameStartsWithX(const std::string& path) @@ -99,12 +101,56 @@ void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiTriStripsData& data, co } } -void fillTriangleMesh(btTriangleMesh& mesh, const Nif::NiGeometry* geometry, const osg::Matrixf &transform = osg::Matrixf()) +template +auto handleNiGeometry(const Nif::NiGeometry& geometry, Function&& function) + -> decltype(function(static_cast(geometry.data.get()))) { - if (geometry->recType == Nif::RC_NiTriShape || geometry->recType == Nif::RC_BSLODTriShape) - fillTriangleMesh(mesh, static_cast(geometry->data.get()), transform); - else if (geometry->recType == Nif::RC_NiTriStrips) - fillTriangleMesh(mesh, static_cast(geometry->data.get()), transform); + if (geometry.recType == Nif::RC_NiTriShape || geometry.recType == Nif::RC_BSLODTriShape) + { + if (geometry.data->recType != Nif::RC_NiTriShapeData) + return {}; + + auto data = static_cast(geometry.data.getPtr()); + if (data->triangles.empty()) + return {}; + + return function(static_cast(*data)); + } + + if (geometry.recType == Nif::RC_NiTriStrips) + { + if (geometry.data->recType != Nif::RC_NiTriStripsData) + return {}; + + auto data = static_cast(geometry.data.getPtr()); + if (data->strips.empty()) + return {}; + + return function(static_cast(*data)); + } + + return {}; +} + +std::monostate fillTriangleMesh(std::unique_ptr& mesh, const Nif::NiGeometry& geometry, const osg::Matrixf &transform) +{ + return handleNiGeometry(geometry, [&] (const auto& data) + { + if (mesh == nullptr) + mesh.reset(new btTriangleMesh(false)); + fillTriangleMesh(*mesh, data, transform); + return std::monostate {}; + }); +} + +std::unique_ptr makeChildMesh(const Nif::NiGeometry& geometry) +{ + return handleNiGeometry(geometry, [&] (const auto& data) + { + std::unique_ptr mesh(new btTriangleMesh); + fillTriangleMesh(*mesh, data, osg::Matrixf()); + return mesh; + }); } } @@ -141,7 +187,7 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) // Try to find a valid bounding box first. If one's found for any root node, use that. for (const Nif::Node* node : roots) { - if (findBoundingBox(node, filename)) + if (findBoundingBox(*node, filename)) { const btVector3 extents = Misc::Convert::toBullet(mShape->mCollisionBox.mExtents); const btVector3 center = Misc::Convert::toBullet(mShape->mCollisionBox.mCenter); @@ -164,13 +210,13 @@ 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, 0, autogenerated, isAnimated, autogenerated); + bool autogenerated = hasAutoGeneratedCollision(*node); + handleNode(filename, *node, 0, autogenerated, isAnimated, autogenerated); } if (mCompoundShape) { - if (mStaticMesh) + if (mStaticMesh != nullptr && mStaticMesh->getNumTriangles() > 0) { btTransform trans; trans.setIdentity(); @@ -181,13 +227,13 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) } mShape->mCollisionShape = std::move(mCompoundShape); } - else if (mStaticMesh) + else if (mStaticMesh != nullptr && mStaticMesh->getNumTriangles() > 0) { mShape->mCollisionShape.reset(new Resource::TriangleMeshShape(mStaticMesh.get(), true)); mStaticMesh.release(); } - if (mAvoidStaticMesh) + if (mAvoidStaticMesh != nullptr && mAvoidStaticMesh->getNumTriangles() > 0) { mShape->mAvoidCollisionShape.reset(new Resource::TriangleMeshShape(mAvoidStaticMesh.get(), false)); mAvoidStaticMesh.release(); @@ -198,41 +244,40 @@ 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, const std::string& filename) +bool BulletNifLoader::findBoundingBox(const Nif::Node& node, const std::string& filename) { - if (node->hasBounds) + if (node.hasBounds) { - unsigned int type = node->bounds.type; + unsigned int type = node.bounds.type; switch (type) { case Nif::NiBoundingVolume::Type::BOX_BV: - mShape->mCollisionBox.mExtents = node->bounds.box.extents; - mShape->mCollisionBox.mCenter = node->bounds.box.center; + mShape->mCollisionBox.mExtents = node.bounds.box.extents; + mShape->mCollisionBox.mCenter = node.bounds.box.center; break; default: { std::stringstream warning; - warning << "Unsupported NiBoundingVolume type " << type << " in node " << node->recIndex; + warning << "Unsupported NiBoundingVolume type " << type << " in node " << node.recIndex; warning << " in file " << filename; warn(warning.str()); } } - if (node->flags & Nif::NiNode::Flag_BBoxCollision) + if (node.flags & Nif::NiNode::Flag_BBoxCollision) { return true; } } - const Nif::NiNode *ninode = dynamic_cast(node); - if(ninode) + if (const Nif::NiNode *ninode = dynamic_cast(&node)) { const Nif::NodeList &list = ninode->children; for(size_t i = 0;i < list.length();i++) { if(!list[i].empty()) { - if (findBoundingBox(list[i].getPtr(), filename)) + if (findBoundingBox(list[i].get(), filename)) return true; } } @@ -240,10 +285,9 @@ bool BulletNifLoader::findBoundingBox(const Nif::Node* node, const std::string& return false; } -bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node* rootNode) +bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node& rootNode) { - const Nif::NiNode *ninode = dynamic_cast(rootNode); - if(ninode) + if (const Nif::NiNode* ninode = dynamic_cast(&rootNode)) { const Nif::NodeList &list = ninode->children; for(size_t i = 0;i < list.length();i++) @@ -258,32 +302,32 @@ bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node* rootNode) return true; } -void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *node, int flags, +void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& node, int flags, bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid) { // TODO: allow on-the fly collision switching via toggling this flag - if (node->recType == Nif::RC_NiCollisionSwitch && !(node->flags & Nif::NiNode::Flag_ActiveCollision)) + if (node.recType == Nif::RC_NiCollisionSwitch && !(node.flags & Nif::NiNode::Flag_ActiveCollision)) return; // Accumulate the flags from all the child nodes. This works for all // the flags we currently use, at least. - flags |= node->flags; + flags |= node.flags; - if (!node->controller.empty() && node->controller->recType == Nif::RC_NiKeyframeController - && (node->controller->flags & Nif::NiNode::ControllerFlag_Active)) + if (!node.controller.empty() && node.controller->recType == Nif::RC_NiKeyframeController + && (node.controller->flags & Nif::NiNode::ControllerFlag_Active)) isAnimated = true; - isCollisionNode = isCollisionNode || (node->recType == Nif::RC_RootCollisionNode); + isCollisionNode = isCollisionNode || (node.recType == Nif::RC_RootCollisionNode); // Don't collide with AvoidNode shapes - avoid = avoid || (node->recType == Nif::RC_AvoidNode); + avoid = avoid || (node.recType == Nif::RC_AvoidNode); // We encountered a RootCollisionNode inside autogenerated mesh. It is not right. - if (node->recType == Nif::RC_RootCollisionNode && autogenerated) + if (node.recType == Nif::RC_RootCollisionNode && autogenerated) Log(Debug::Info) << "RootCollisionNode is not attached to the root node in " << fileName << ". Treating it as a common NiTriShape."; // Check for extra data - for (Nif::ExtraPtr e = node->extra; !e.empty(); e = e->next) + for (Nif::ExtraPtr e = node.extra; !e.empty(); e = e->next) { if (e->recType == Nif::RC_NiStringExtraData) { @@ -310,108 +354,79 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node *n // NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape! // It must be ignored completely. // (occurs in tr_ex_imp_wall_arch_04.nif) - if(!node->hasBounds && (node->recType == Nif::RC_NiTriShape - || node->recType == Nif::RC_NiTriStrips - || node->recType == Nif::RC_BSLODTriShape)) + if(!node.hasBounds && (node.recType == Nif::RC_NiTriShape + || node.recType == Nif::RC_NiTriStrips + || node.recType == Nif::RC_BSLODTriShape)) { handleNiTriShape(node, flags, getWorldTransform(node), isAnimated, avoid); } } // For NiNodes, loop through children - const Nif::NiNode *ninode = dynamic_cast(node); - if(ninode) + if (const Nif::NiNode *ninode = dynamic_cast(&node)) { const Nif::NodeList &list = ninode->children; for(size_t i = 0;i < list.length();i++) { - if(!list[i].empty()) - handleNode(fileName, list[i].getPtr(), flags, isCollisionNode, isAnimated, autogenerated, avoid); + if (list[i].empty()) + continue; + + assert(list[i].get().parent == &node); + handleNode(fileName, list[i].get(), flags, isCollisionNode, isAnimated, autogenerated, avoid); } } } -void BulletNifLoader::handleNiTriShape(const Nif::Node *nifNode, int flags, const osg::Matrixf &transform, +void BulletNifLoader::handleNiTriShape(const Nif::Node& nifNode, int flags, const osg::Matrixf &transform, bool isAnimated, bool avoid) { - assert(nifNode != nullptr); - // If the object was marked "NCO" earlier, it shouldn't collide with // anything. So don't do anything. if ((flags & 0x800)) return; - auto niGeometry = static_cast(nifNode); - if (niGeometry->data.empty() || niGeometry->data->vertices.empty()) + handleNiTriShape(static_cast(nifNode), transform, isAnimated, avoid); +} + +void BulletNifLoader::handleNiTriShape(const Nif::NiGeometry& niGeometry, const osg::Matrixf &transform, + bool isAnimated, bool avoid) +{ + if (niGeometry.data.empty() || niGeometry.data->vertices.empty()) return; - if (niGeometry->recType == Nif::RC_NiTriShape || niGeometry->recType == Nif::RC_BSLODTriShape) - { - if (niGeometry->data->recType != Nif::RC_NiTriShapeData) - return; - - auto data = static_cast(niGeometry->data.getPtr()); - if (data->triangles.empty()) - return; - } - else if (niGeometry->recType == Nif::RC_NiTriStrips) - { - if (niGeometry->data->recType != Nif::RC_NiTriStripsData) - return; - - auto data = static_cast(niGeometry->data.getPtr()); - if (data->strips.empty()) - return; - } - - if (!niGeometry->skin.empty()) + if (!niGeometry.skin.empty()) isAnimated = false; if (isAnimated) { + std::unique_ptr childMesh = makeChildMesh(niGeometry); + if (childMesh == nullptr || childMesh->getNumTriangles() == 0) + return; + if (!mCompoundShape) mCompoundShape.reset(new btCompoundShape); - std::unique_ptr childMesh(new btTriangleMesh); - - fillTriangleMesh(*childMesh, niGeometry); - std::unique_ptr childShape(new Resource::TriangleMeshShape(childMesh.get(), true)); childMesh.release(); - float scale = nifNode->trafo.scale; - const Nif::Node* parent = nifNode; - while (parent->parent) - { - parent = parent->parent; + float scale = niGeometry.trafo.scale; + for (const Nif::Node* parent = niGeometry.parent; parent != nullptr; parent = parent->parent) scale *= parent->trafo.scale; - } osg::Quat q = transform.getRotate(); osg::Vec3f v = transform.getTrans(); childShape->setLocalScaling(btVector3(scale, scale, scale)); btTransform trans(btQuaternion(q.x(), q.y(), q.z(), q.w()), btVector3(v.x(), v.y(), v.z())); - mShape->mAnimatedShapes.emplace(nifNode->recIndex, mCompoundShape->getNumChildShapes()); + mShape->mAnimatedShapes.emplace(niGeometry.recIndex, mCompoundShape->getNumChildShapes()); mCompoundShape->addChildShape(trans, childShape.get()); childShape.release(); } else if (avoid) - { - if (!mAvoidStaticMesh) - mAvoidStaticMesh.reset(new btTriangleMesh(false)); - - fillTriangleMesh(*mAvoidStaticMesh, niGeometry, transform); - } + fillTriangleMesh(mAvoidStaticMesh, niGeometry, transform); else - { - if (!mStaticMesh) - mStaticMesh.reset(new btTriangleMesh(false)); - - // Static shape, just transform all vertices into position - fillTriangleMesh(*mStaticMesh, niGeometry, transform); - } + fillTriangleMesh(mStaticMesh, niGeometry, transform); } } // namespace NifBullet diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index 3d6a95e09f..e0fec338c6 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -27,6 +27,7 @@ namespace Nif struct Transformation; struct NiTriShape; struct NiTriStrips; + struct NiGeometry; } namespace NifBullet @@ -52,14 +53,16 @@ public: osg::ref_ptr load(const Nif::File& file); private: - bool findBoundingBox(const Nif::Node* node, const std::string& filename); + bool findBoundingBox(const Nif::Node& node, const std::string& filename); - void handleNode(const std::string& fileName, Nif::Node const *node, int flags, bool isCollisionNode, + void handleNode(const std::string& fileName, const Nif::Node& node, int flags, bool isCollisionNode, bool isAnimated=false, bool autogenerated=false, bool avoid=false); - bool hasAutoGeneratedCollision(const Nif::Node *rootNode); + bool hasAutoGeneratedCollision(const Nif::Node& rootNode); - void handleNiTriShape(const Nif::Node *nifNode, int flags, const osg::Matrixf& transform, bool isAnimated, bool avoid); + void handleNiTriShape(const Nif::Node& nifNode, int flags, const osg::Matrixf& transform, bool isAnimated, bool avoid); + + void handleNiTriShape(const Nif::NiGeometry& nifNode, const osg::Matrixf& transform, bool isAnimated, bool avoid); std::unique_ptr mCompoundShape;