diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index eb3492ed5..bc39bc2be 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -15,6 +15,8 @@ if (GTEST_FOUND AND GMOCK_FOUND) esm/test_fixed_string.cpp misc/test_stringops.cpp + + nifloader/testbulletnifloader.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp new file mode 100644 index 000000000..a2311be49 --- /dev/null +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -0,0 +1,951 @@ +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace +{ + template + bool compareObjects(const T* lhs, const T* rhs) + { + return (!lhs && !rhs) || (lhs && rhs && *lhs == *rhs); + } + + std::vector getTriangles(const btBvhTriangleMeshShape& shape) + { + std::vector result; + auto callback = BulletHelpers::makeProcessTriangleCallback([&] (btVector3* triangle, int, int) { + for (std::size_t i = 0; i < 3; ++i) + result.push_back(triangle[i]); + }); + btVector3 aabbMin; + btVector3 aabbMax; + shape.getAabb(btTransform::getIdentity(), aabbMin, aabbMax); + shape.processAllTriangles(&callback, aabbMin, aabbMax); + return result; + } +} + +static std::ostream& operator <<(std::ostream& stream, const btVector3& value) +{ + return stream << "btVector3 {" + << std::setprecision(std::numeric_limits::max_exponent10) << value.getX() << ", " + << std::setprecision(std::numeric_limits::max_exponent10) << value.getY() << ", " + << std::setprecision(std::numeric_limits::max_exponent10) << value.getZ() << "}"; +} + +static std::ostream& operator <<(std::ostream& stream, const btMatrix3x3& value) +{ + stream << "btMatrix3x3 {"; + for (int i = 0; i < 3; ++i) + stream << value.getRow(i) << ", "; + return stream << "}"; +} + +static std::ostream& operator <<(std::ostream& stream, const btTransform& value) +{ + return stream << "btTransform {" << value.getBasis() << ", " << value.getOrigin() << "}"; +} + +static std::ostream& operator <<(std::ostream& stream, const btCollisionShape* value); + +static std::ostream& operator <<(std::ostream& stream, const btCompoundShape& value) +{ + stream << "btCompoundShape {" << value.getLocalScaling() << ", "; + stream << "{"; + for (int i = 0; i < value.getNumChildShapes(); ++i) + stream << value.getChildShape(i) << ", "; + stream << "},"; + stream << "{"; + for (int i = 0; i < value.getNumChildShapes(); ++i) + stream << value.getChildTransform(i) << ", "; + stream << "}"; + return stream << "}"; +} + +static std::ostream& operator <<(std::ostream& stream, const btBoxShape& value) +{ + return stream << "btBoxShape {" << value.getLocalScaling() << ", " << value.getHalfExtentsWithoutMargin() << "}"; +} + +namespace Resource +{ + +static std::ostream& operator <<(std::ostream& stream, const TriangleMeshShape& value) +{ + stream << "Resource::TriangleMeshShape {" << value.getLocalScaling() << ", " + << value.usesQuantizedAabbCompression() << ", " << value.getOwnsBvh() << ", {"; + auto callback = BulletHelpers::makeProcessTriangleCallback([&] (btVector3* triangle, int, int) { + for (std::size_t i = 0; i < 3; ++i) + stream << triangle[i] << ", "; + }); + btVector3 aabbMin; + btVector3 aabbMax; + value.getAabb(btTransform::getIdentity(), aabbMin, aabbMax); + value.processAllTriangles(&callback, aabbMin, aabbMax); + return stream << "}}"; +} + +} + +static std::ostream& operator <<(std::ostream& stream, const btCollisionShape& value) +{ + switch (value.getShapeType()) + { + case COMPOUND_SHAPE_PROXYTYPE: + return stream << static_cast(value); + case BOX_SHAPE_PROXYTYPE: + return stream << static_cast(value); + case TRIANGLE_MESH_SHAPE_PROXYTYPE: + if (const auto casted = dynamic_cast(&value)) + return stream << *casted; + break; + } + return stream << "btCollisionShape {" << value.getShapeType() << "}"; +} + +static std::ostream& operator <<(std::ostream& stream, const btCollisionShape* value) +{ + return value ? stream << "&" << *value : stream << "nullptr"; +} + +namespace osg +{ + static std::ostream& operator <<(std::ostream& stream, const Vec3f& value) + { + return stream << "osg::Vec3f {" + << value.x() << ", " + << value.y() << ", " + << value.z() << "}"; + } +} + +namespace std +{ + static std::ostream& operator <<(std::ostream& stream, const map& value) + { + stream << "std::map {"; + for (const auto& v : value) + stream << "{" << v.first << ", " << v.second << "},"; + return stream << "}"; + } +} + +namespace Resource +{ + static bool operator ==(const Resource::BulletShape& lhs, const Resource::BulletShape& rhs) + { + return compareObjects(lhs.mCollisionShape, rhs.mCollisionShape) + && lhs.mCollisionBoxHalfExtents == rhs.mCollisionBoxHalfExtents + && lhs.mCollisionBoxTranslate == rhs.mCollisionBoxTranslate + && lhs.mAnimatedShapes == rhs.mAnimatedShapes; + } + + static std::ostream& operator <<(std::ostream& stream, const Resource::BulletShape& value) + { + return stream << "Resource::BulletShape {" + << value.mCollisionShape << ", " + << value.mCollisionBoxHalfExtents << ", " + << value.mAnimatedShapes + << "}"; + } +} + +static bool operator ==(const btCollisionShape& lhs, const btCollisionShape& rhs); + +static bool operator ==(const btCompoundShape& lhs, const btCompoundShape& rhs) +{ + if (lhs.getNumChildShapes() != rhs.getNumChildShapes() || lhs.getLocalScaling() != rhs.getLocalScaling()) + return false; + for (int i = 0; i < lhs.getNumChildShapes(); ++i) + { + if (!compareObjects(lhs.getChildShape(i), rhs.getChildShape(i)) + || !(lhs.getChildTransform(i) == rhs.getChildTransform(i))) + return false; + } + return true; +} + +static bool operator ==(const btBoxShape& lhs, const btBoxShape& rhs) +{ + return lhs.getLocalScaling() == rhs.getLocalScaling() + && lhs.getHalfExtentsWithoutMargin() == rhs.getHalfExtentsWithoutMargin(); +} + +static bool operator ==(const btBvhTriangleMeshShape& lhs, const btBvhTriangleMeshShape& rhs) +{ + return lhs.getLocalScaling() == rhs.getLocalScaling() + && lhs.usesQuantizedAabbCompression() == rhs.usesQuantizedAabbCompression() + && lhs.getOwnsBvh() == rhs.getOwnsBvh() + && getTriangles(lhs) == getTriangles(rhs); +} + +static bool operator ==(const btCollisionShape& lhs, const btCollisionShape& rhs) +{ + if (lhs.getShapeType() != rhs.getShapeType()) + return false; + switch (lhs.getShapeType()) + { + case COMPOUND_SHAPE_PROXYTYPE: + return static_cast(lhs) == static_cast(rhs); + case BOX_SHAPE_PROXYTYPE: + return static_cast(lhs) == static_cast(rhs); + case TRIANGLE_MESH_SHAPE_PROXYTYPE: + if (const auto lhsCasted = dynamic_cast(&lhs)) + if (const auto rhsCasted = dynamic_cast(&rhs)) + return *lhsCasted == *rhsCasted; + return false; + } + return false; +} + +namespace +{ + using namespace testing; + using NifBullet::BulletNifLoader; + + void init(Nif::Transformation& value) + { + value = Nif::Transformation::getIdentity(); + } + + void init(Nif::Extra& value) + { + value.extra = Nif::ExtraPtr(nullptr); + } + + void init(Nif::Controlled& value) + { + init(static_cast(value)); + value.controller = Nif::ControllerPtr(nullptr); + } + + void init(Nif::Named& value) + { + init(static_cast(value)); + } + + void init(Nif::Node& value) + { + init(static_cast(value)); + value.flags = 0; + init(value.trafo); + value.hasBounds = false; + value.parent = nullptr; + value.isBone = false; + } + + void init(Nif::NiTriShape& value) + { + init(static_cast(value)); + value.recType = Nif::RC_NiTriShape; + value.data = Nif::NiTriShapeDataPtr(nullptr); + value.skin = Nif::NiSkinInstancePtr(nullptr); + } + + void init(Nif::NiSkinInstance& value) + { + value.data = Nif::NiSkinDataPtr(nullptr); + value.root = Nif::NodePtr(nullptr); + } + + void init(Nif::Controller& value) + { + value.next = Nif::ControllerPtr(nullptr); + value.flags = 0; + value.frequency = 0; + value.phase = 0; + value.timeStart = 0; + value.timeStop = 0; + value.target = Nif::ControlledPtr(nullptr); + } + + void copy(const btTransform& src, Nif::Transformation& dst) { + dst.pos = osg::Vec3f(src.getOrigin().x(), src.getOrigin().y(), src.getOrigin().z()); + for (int row = 0; row < 3; ++row) + for (int column = 0; column < 3; ++column) + dst.rotation.mValues[column][row] = src.getBasis().getRow(row)[column]; + } + + struct NifFileMock : Nif::File + { + MOCK_CONST_METHOD1(fail, void (const std::string&)); + MOCK_CONST_METHOD1(warn, void (const std::string&)); + MOCK_CONST_METHOD1(getRecord, Nif::Record* (std::size_t)); + MOCK_CONST_METHOD0(numRecords, std::size_t ()); + MOCK_CONST_METHOD1(getRoot, Nif::Record* (std::size_t)); + MOCK_CONST_METHOD0(numRoots, std::size_t ()); + MOCK_METHOD1(setUseSkinning, void (bool)); + MOCK_CONST_METHOD0(getUseSkinning, bool ()); + MOCK_CONST_METHOD0(getFilename, std::string ()); + }; + + struct RecordMock : Nif::Record + { + MOCK_METHOD1(read, void (Nif::NIFStream *nif)); + }; + + struct TestBulletNifLoader : Test + { + BulletNifLoader mLoader; + const StrictMock mNifFile; + Nif::Node mNode; + Nif::Node mNode2; + Nif::NiNode mNiNode; + Nif::NiNode mNiNode2; + Nif::NiNode mNiNode3; + Nif::NiTriShapeData mNiTriShapeData; + Nif::NiTriShape mNiTriShape; + Nif::NiTriShapeData mNiTriShapeData2; + Nif::NiTriShape mNiTriShape2; + Nif::NiSkinInstance mNiSkinInstance; + Nif::NiStringExtraData mNiStringExtraData; + Nif::NiStringExtraData mNiStringExtraData2; + Nif::Controller mController; + btTransform mTransform {btMatrix3x3(btQuaternion(btVector3(1, 0, 0), 0.5f)), btVector3(1, 2, 3)}; + btTransform mResultTransform { + btMatrix3x3( + 1, 0, 0, + 0, 0.82417738437652587890625, 0.56633174419403076171875, + 0, -0.56633174419403076171875, 0.82417738437652587890625 + ), + btVector3(1, 2, 3) + }; + btTransform mResultTransform2 { + btMatrix3x3( + 1, 0, 0, + 0, 0.7951543331146240234375, 0.606407105922698974609375, + 0, -0.606407105922698974609375, 0.7951543331146240234375 + ), + btVector3(4, 8, 12) + }; + + TestBulletNifLoader() + { + init(mNode); + init(mNode2); + init(mNiNode); + init(mNiNode2); + init(mNiNode3); + init(mNiTriShape); + init(mNiTriShape2); + init(mNiSkinInstance); + init(mNiStringExtraData); + init(mNiStringExtraData2); + init(mController); + + mNiTriShapeData.vertices = {osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0)}; + mNiTriShapeData.triangles = {0, 1, 2}; + mNiTriShape.data = Nif::NiTriShapeDataPtr(&mNiTriShapeData); + + 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::NiTriShapeDataPtr(&mNiTriShapeData2); + } + }; + + TEST_F(TestBulletNifLoader, for_zero_num_roots_should_return_default) + { + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(0)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_root_not_nif_node_should_return_default) + { + StrictMock record; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&record)); + 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)); + 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; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_default_root_collision_node_nif_node_should_return_default) + { + mNode.recType = Nif::RC_RootCollisionNode; + + 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; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_default_root_nif_node_and_filename_starting_with_x_should_return_default) + { + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_root_nif_node_with_bounding_box_should_return_shape_with_compound_shape_and_box_inside) + { + mNode.hasBounds = true; + mNode.flags |= Nif::NiNode::Flag_BBoxCollision; + mNode.boundXYZ = osg::Vec3f(1, 2, 3); + mNode.boundPos = osg::Vec3f(-1, -2, -3); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); + expected.mCollisionShape = shape.release(); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_child_nif_node_with_bounding_box) + { + mNode.hasBounds = true; + mNode.flags |= Nif::NiNode::Flag_BBoxCollision; + mNode.boundXYZ = osg::Vec3f(1, 2, 3); + mNode.boundPos = 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)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); + expected.mCollisionShape = shape.release(); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_root_and_child_nif_node_with_bounding_box_but_root_without_flag_should_use_child_bounds) + { + mNode.hasBounds = true; + mNode.flags |= Nif::NiNode::Flag_BBoxCollision; + mNode.boundXYZ = osg::Vec3f(1, 2, 3); + mNode.boundPos = osg::Vec3f(-1, -2, -3); + + mNiNode.hasBounds = true; + mNiNode.boundXYZ = osg::Vec3f(4, 5, 6); + mNiNode.boundPos = 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)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); + expected.mCollisionShape = shape.release(); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_root_and_two_children_where_both_with_bounds_but_only_first_with_flag_should_use_first_bounds) + { + mNode.hasBounds = true; + mNode.flags |= Nif::NiNode::Flag_BBoxCollision; + mNode.boundXYZ = osg::Vec3f(1, 2, 3); + mNode.boundPos = osg::Vec3f(-1, -2, -3); + + mNode2.hasBounds = true; + mNode2.boundXYZ = osg::Vec3f(4, 5, 6); + mNode2.boundPos = osg::Vec3f(-4, -5, -6); + + mNiNode.hasBounds = true; + mNiNode.boundXYZ = osg::Vec3f(7, 8, 9); + mNiNode.boundPos = 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)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); + expected.mCollisionShape = shape.release(); + + EXPECT_EQ(*result, expected); + } + + 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); + + mNode2.hasBounds = true; + mNode2.flags |= Nif::NiNode::Flag_BBoxCollision; + mNode2.boundXYZ = osg::Vec3f(4, 5, 6); + mNode2.boundPos = osg::Vec3f(-4, -5, -6); + + mNiNode.hasBounds = true; + mNiNode.boundXYZ = osg::Vec3f(7, 8, 9); + mNiNode.boundPos = 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)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(4, 5, 6); + expected.mCollisionBoxTranslate = osg::Vec3f(-4, -5, -6); + std::unique_ptr box(new btBoxShape(btVector3(4, 5, 6))); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-4, -5, -6)), box.release()); + expected.mCollisionShape = shape.release(); + + EXPECT_EQ(*result, expected); + } + + 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); + + 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; + expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_root_node_should_return_shape_with_triangle_mesh_shape) + { + 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); + + 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 = new Resource::TriangleMeshShape(triangles.release(), true); + + EXPECT_EQ(*result, expected); + } + + 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); + + 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); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_should_return_shape_with_triangle_mesh_shape) + { + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + 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 = new Resource::TriangleMeshShape(triangles.release(), true); + + EXPECT_EQ(*result, expected); + } + + 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.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + 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 = new Resource::TriangleMeshShape(triangles.release(), true); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_two_tri_shape_children_should_return_shape_with_triangle_mesh_shape_with_all_meshes) + { + mNiNode.children = Nif::NodeList(std::vector({ + Nif::NodePtr(&mNiTriShape), + Nif::NodePtr(&mNiTriShape2) + })); + + 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, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + Resource::BulletShape expected; + expected.mCollisionShape = new Resource::TriangleMeshShape(triangles.release(), true); + + EXPECT_EQ(*result, expected); + } + + 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); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + 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); + + 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 = new Resource::TriangleMeshShape(triangles.release(), true); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_root_node_and_filename_starting_with_x_should_return_shape_with_compound_shape) + { + copy(mTransform, mNiTriShape.trafo); + mNiTriShape.trafo.scale = 3; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriShape)); + 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 mesh(new Resource::TriangleMeshShape(triangles.release(), true)); + mesh->setLocalScaling(btVector3(3, 3, 3)); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(mResultTransform, mesh.release()); + Resource::BulletShape expected; + expected.mCollisionShape = shape.release(); + expected.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_and_filename_starting_with_x_should_return_shape_with_compound_shape) + { + copy(mTransform, mNiTriShape.trafo); + mNiTriShape.trafo.scale = 3; + mNiTriShape.parent = &mNiNode; + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiNode.trafo.scale = 4; + + 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); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); + mesh->setLocalScaling(btVector3(12, 12, 12)); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(mResultTransform2, mesh.release()); + Resource::BulletShape expected; + expected.mCollisionShape = shape.release(); + expected.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_two_tri_shape_children_nodes_and_filename_starting_with_x_should_return_shape_with_compound_shape) + { + copy(mTransform, mNiTriShape.trafo); + mNiTriShape.trafo.scale = 3; + + copy(mTransform, mNiTriShape2.trafo); + mNiTriShape2.trafo.scale = 3; + + mNiNode.children = Nif::NodeList(std::vector({ + Nif::NodePtr(&mNiTriShape), + Nif::NodePtr(&mNiTriShape2), + })); + + 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); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); + mesh->setLocalScaling(btVector3(3, 3, 3)); + + std::unique_ptr triangles2(new btTriangleMesh(false)); + triangles2->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); + std::unique_ptr mesh2(new Resource::TriangleMeshShape(triangles2.release(), true)); + mesh2->setLocalScaling(btVector3(3, 3, 3)); + + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(mResultTransform, mesh.release()); + shape->addChildShape(mResultTransform, mesh2.release()); + Resource::BulletShape expected; + expected.mCollisionShape = shape.release(); + expected.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_controller_should_return_shape_with_compound_shape) + { + mController.recType = Nif::RC_NiKeyframeController; + mController.flags |= Nif::NiNode::ControllerFlag_Active; + copy(mTransform, mNiTriShape.trafo); + mNiTriShape.trafo.scale = 3; + mNiTriShape.parent = &mNiNode; + mNiTriShape.controller = Nif::ControllerPtr(&mController); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiNode.trafo.scale = 4; + + 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)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); + mesh->setLocalScaling(btVector3(12, 12, 12)); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(mResultTransform2, mesh.release()); + Resource::BulletShape expected; + expected.mCollisionShape = shape.release(); + expected.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_two_tri_shape_children_nodes_where_one_with_controller_should_return_shape_with_compound_shape) + { + mController.recType = Nif::RC_NiKeyframeController; + mController.flags |= Nif::NiNode::ControllerFlag_Active; + copy(mTransform, mNiTriShape.trafo); + mNiTriShape.trafo.scale = 3; + copy(mTransform, mNiTriShape2.trafo); + mNiTriShape2.trafo.scale = 3; + mNiTriShape2.parent = &mNiNode; + mNiTriShape2.controller = Nif::ControllerPtr(&mController); + mNiNode.children = Nif::NodeList(std::vector({ + Nif::NodePtr(&mNiTriShape), + Nif::NodePtr(&mNiTriShape2), + })); + mNiNode.trafo.scale = 4; + + 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(1, 2, 3), btVector3(4, 2, 3), btVector3(4, 4.632747650146484375, 1.56172335147857666015625)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); + mesh->setLocalScaling(btVector3(1, 1, 1)); + + std::unique_ptr triangles2(new btTriangleMesh(false)); + triangles2->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); + std::unique_ptr mesh2(new Resource::TriangleMeshShape(triangles2.release(), true)); + mesh2->setLocalScaling(btVector3(12, 12, 12)); + + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(mResultTransform2, mesh2.release()); + shape->addChildShape(btTransform::getIdentity(), mesh.release()); + Resource::BulletShape expected; + expected.mCollisionShape = shape.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) + { + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiNode.recType = Nif::RC_AvoidNode; + + 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; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_should_return_shape_with_null_collision_shape) + { + mNiTriShape.data = Nif::NiTriShapeDataPtr(nullptr); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + 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; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_triangles_should_return_shape_with_null_collision_shape) + { + mNiTriShape.data->triangles.clear(); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + 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; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_starting_with_nc_should_return_shape_with_null_collision_shape) + { + mNiStringExtraData.string = "NC___"; + mNiStringExtraData.recType = Nif::RC_NiStringExtraData; + mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + 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; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_null_collision_shape) + { + mNiStringExtraData.extra = Nif::ExtraPtr(&mNiStringExtraData2); + mNiStringExtraData2.string = "NC___"; + mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; + mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + 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; + + 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"; + mNiStringExtraData.recType = Nif::RC_NiStringExtraData; + mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + 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; + + 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) + { + mNiStringExtraData.string = "MRK"; + mNiStringExtraData.recType = Nif::RC_NiStringExtraData; + mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode3.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiNode3.recType = Nif::RC_RootCollisionNode; + mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(nullptr), Nif::NodePtr(&mNiNode3)})); + mNiNode2.recType = Nif::RC_NiNode; + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiNode2)})); + mNiNode.recType = Nif::RC_NiNode; + + 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 = new Resource::TriangleMeshShape(triangles.release(), true); + + EXPECT_EQ(*result, expected); + } +} diff --git a/components/bullethelpers/processtrianglecallback.hpp b/components/bullethelpers/processtrianglecallback.hpp new file mode 100644 index 000000000..ee005b459 --- /dev/null +++ b/components/bullethelpers/processtrianglecallback.hpp @@ -0,0 +1,34 @@ +#ifndef OPENMW_COMPONENTS_BULLETHELPERS_PROCESSTRIANGLECALLBACK_H +#define OPENMW_COMPONENTS_BULLETHELPERS_PROCESSTRIANGLECALLBACK_H + +#include + +#include + +namespace BulletHelpers +{ + template + class ProcessTriangleCallback : public btTriangleCallback + { + public: + ProcessTriangleCallback(Impl impl) + : mImpl(std::move(impl)) + {} + + void processTriangle(btVector3* triangle, int partId, int triangleIndex) override final + { + return mImpl(triangle, partId, triangleIndex); + } + + private: + Impl mImpl; + }; + + template + ProcessTriangleCallback::type> makeProcessTriangleCallback(Impl&& impl) + { + return ProcessTriangleCallback::type>(std::forward(impl)); + } +} + +#endif diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 9e39a37ab..cab2e9880 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -14,7 +14,30 @@ namespace Nif { -class NIFFile +struct File +{ + virtual ~File() = default; + + virtual void fail(const std::string &msg) const = 0; + + virtual void warn(const std::string &msg) const = 0; + + virtual Record *getRecord(size_t index) const = 0; + + virtual size_t numRecords() const = 0; + + virtual Record *getRoot(size_t index = 0) const = 0; + + virtual size_t numRoots() const = 0; + + virtual void setUseSkinning(bool skinning) = 0; + + virtual bool getUseSkinning() const = 0; + + virtual std::string getFilename() const = 0; +}; + +class NIFFile final : public File { enum NIFVersion { VER_MW = 0x04000002 // Morrowind NIFs @@ -48,14 +71,14 @@ class NIFFile public: /// Used if file parsing fails - void fail(const std::string &msg) const + void fail(const std::string &msg) const override { std::string err = " NIFFile Error: " + msg; err += "\nFile: " + filename; throw std::runtime_error(err); } /// Used when something goes wrong, but not catastrophically so - void warn(const std::string &msg) const + void warn(const std::string &msg) const override { std::cerr << " NIFFile Warning: " << msg < NIFFilePtr; diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 25beaf098..09c380987 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -23,6 +23,8 @@ class RecordPtrT public: RecordPtrT() : index(-2) {} + RecordPtrT(X* ptr) : ptr(ptr) {} + /// Read the index from the nif void read(NIFStream *nif) { @@ -87,6 +89,12 @@ class RecordListT std::vector list; public: + RecordListT() = default; + + RecordListT(std::vector list) + : list(std::move(list)) + {} + void read(NIFStream *nif) { int len = nif->getInt(); diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 6f8c8f2c0..3f033967c 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -56,20 +56,20 @@ BulletNifLoader::~BulletNifLoader() { } -osg::ref_ptr BulletNifLoader::load(const Nif::NIFFilePtr& nif) +osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) { mShape = new Resource::BulletShape; mCompoundShape = NULL; mStaticMesh = NULL; - if (nif->numRoots() < 1) + if (nif.numRoots() < 1) { warn("Found no root nodes in NIF."); return mShape; } - Nif::Record *r = nif->getRoot(0); + Nif::Record *r = nif.getRoot(0); assert(r != NULL); Nif::Node *node = dynamic_cast(r); @@ -96,14 +96,15 @@ osg::ref_ptr BulletNifLoader::load(const Nif::NIFFilePtr& { // 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 bool isAnimated = pathFileNameStartsWithX(nif->getFilename()); + const auto filename = nif.getFilename(); + const bool isAnimated = pathFileNameStartsWithX(filename); // If the mesh has RootCollisionNode, attached to actual root node, use it as collision mesh const Nif::Node* rootCollisionNode = getCollisionNode(node); if (rootCollisionNode) - handleNode(nif->getFilename(), rootCollisionNode, 0, false, isAnimated, false); + handleNode(filename, rootCollisionNode, 0, false, isAnimated, false); else - handleNode(nif->getFilename(), node, 0, true, isAnimated, true); + handleNode(filename, node, 0, true, isAnimated, true); if (mCompoundShape) { diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index 8fd9cc2a1..22d228d10 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -50,7 +50,7 @@ public: abort(); } - osg::ref_ptr load(const Nif::NIFFilePtr& file); + osg::ref_ptr load(const Nif::File& file); private: bool findBoundingBox(const Nif::Node* node, int flags = 0); diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index a3d09311a..622506d6b 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -127,7 +127,7 @@ osg::ref_ptr BulletShapeManager::getShape(const std::string & if (ext == "nif") { NifBullet::BulletNifLoader loader; - shape = loader.load(mNifFileManager->get(normalized)); + shape = loader.load(*mNifFileManager->get(normalized)); } else {