diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index 8fbf5c1b5b..f84fbe2508 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -277,7 +277,7 @@ namespace value.flags = 0; init(value.trafo); value.hasBounds = false; - value.parent = nullptr; + value.parents.push_back(nullptr); value.isBone = false; } @@ -512,7 +512,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; + mNode.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -538,7 +538,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; + mNode.parents.push_back(&mNiNode); mNiNode.hasBounds = true; mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; @@ -569,13 +569,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; + mNode.parents.push_back(&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; + mNode2.parents.push_back(&mNiNode); mNiNode.hasBounds = true; mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; @@ -605,14 +605,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; + mNode.parents.push_back(&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; + mNode2.parents.push_back(&mNiNode); mNiNode.hasBounds = true; mNiNode.bounds.type = Nif::NiBoundingVolume::Type::BOX_BV; @@ -691,7 +691,7 @@ namespace TEST_F(TestBulletNifLoader, for_tri_shape_child_node_should_return_shape_with_triangle_mesh_shape) { - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -710,9 +710,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.parents.push_back(&mNiNode); mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); - mNiTriShape.parent = &mNiNode2; + mNiTriShape.parents.push_back(&mNiNode2); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); @@ -729,8 +729,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; + mNiTriShape.parents.push_back(&mNiNode); + mNiTriShape2.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape), Nif::NodePtr(&mNiTriShape2) @@ -753,7 +753,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; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -796,7 +796,7 @@ namespace { copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode.trafo.scale = 4; @@ -822,11 +822,11 @@ namespace { copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); copy(mTransform, mNiTriShape2.trafo); mNiTriShape2.trafo.scale = 3; - mNiTriShape2.parent = &mNiNode; + mNiTriShape2.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape), @@ -864,7 +864,7 @@ namespace mController.flags |= Nif::NiNode::ControllerFlag_Active; copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiTriShape.controller = Nif::ControllerPtr(&mController); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode.trafo.scale = 4; @@ -893,10 +893,10 @@ namespace mController.flags |= Nif::NiNode::ControllerFlag_Active; copy(mTransform, mNiTriShape.trafo); mNiTriShape.trafo.scale = 3; - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); copy(mTransform, mNiTriShape2.trafo); mNiTriShape2.trafo.scale = 3; - mNiTriShape2.parent = &mNiNode; + mNiTriShape2.parents.push_back(&mNiNode); mNiTriShape2.controller = Nif::ControllerPtr(&mController); mNiNode.children = Nif::NodeList(std::vector({ Nif::NodePtr(&mNiTriShape), @@ -931,7 +931,7 @@ namespace TEST_F(TestBulletNifLoader, should_add_static_mesh_to_existing_compound_mesh) { - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(2)); @@ -959,7 +959,7 @@ namespace TEST_F(TestBulletNifLoader, for_root_avoid_node_and_tri_shape_child_node_should_return_shape_with_null_collision_shape) { - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode.recType = Nif::RC_AvoidNode; @@ -979,7 +979,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; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -996,7 +996,7 @@ namespace { auto data = static_cast(mNiTriShape.data.getPtr()); data->triangles.clear(); - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -1014,7 +1014,7 @@ namespace mNiStringExtraData.string = "NC___"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -1033,7 +1033,7 @@ namespace mNiStringExtraData2.string = "NC___"; mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -1051,7 +1051,7 @@ namespace mNiStringExtraData.string = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -1069,10 +1069,10 @@ namespace mNiStringExtraData.string = "MRK"; mNiStringExtraData.recType = Nif::RC_NiStringExtraData; mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); - mNiTriShape.parent = &mNiNode2; + mNiTriShape.parents.push_back(&mNiNode2); mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode2.recType = Nif::RC_RootCollisionNode; - mNiNode2.parent = &mNiNode; + mNiNode2.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiNode2)})); mNiNode.recType = Nif::RC_NiNode; @@ -1163,7 +1163,7 @@ namespace TEST_F(TestBulletNifLoader, for_avoid_collision_mesh_should_ignore_tri_strips_data_with_less_than_3_strips) { - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); mNiNode.recType = Nif::RC_AvoidNode; mNiTriStripsData.strips.front() = {0, 1}; @@ -1181,7 +1181,7 @@ namespace TEST_F(TestBulletNifLoader, for_animated_mesh_should_ignore_tri_strips_data_with_less_than_3_strips) { mNiTriStripsData.strips.front() = {0, 1}; - mNiTriStrips.parent = &mNiNode; + mNiTriStrips.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriStrips)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); @@ -1197,7 +1197,7 @@ namespace TEST_F(TestBulletNifLoader, should_not_add_static_mesh_with_no_triangles_to_compound_shape) { mNiTriStripsData.strips.front() = {0, 1}; - mNiTriShape.parent = &mNiNode; + mNiTriShape.parents.push_back(&mNiNode); mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(2)); @@ -1218,4 +1218,54 @@ namespace EXPECT_EQ(*result, expected); } + + TEST_F(TestBulletNifLoader, should_handle_node_with_multiple_parents) + { + copy(mTransform, mNiTriShape.trafo); + mNiTriShape.trafo.scale = 4; + mNiTriShape.parents = {&mNiNode, &mNiNode2}; + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiNode.trafo.scale = 2; + mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiNode2.trafo.scale = 3; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(2)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getRoot(1)).WillOnce(Return(&mNiNode2)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles1(new btTriangleMesh(false)); + triangles1->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh1(new Resource::TriangleMeshShape(triangles1.release(), true)); + mesh1->setLocalScaling(btVector3(8, 8, 8)); + std::unique_ptr triangles2(new btTriangleMesh(false)); + triangles2->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh2(new Resource::TriangleMeshShape(triangles2.release(), true)); + mesh2->setLocalScaling(btVector3(12, 12, 12)); + std::unique_ptr shape(new btCompoundShape); + const btTransform transform1 { + btMatrix3x3( + 1, 0, 0, + 0, 0.8004512795493964327775415767973754555, 0.59939782204119995689950428641168400645, + 0, -0.59939782204119995689950428641168400645, 0.8004512795493964327775415767973754555 + ), + btVector3(2, 4, 6) + }; + const btTransform transform2 { + btMatrix3x3( + 1, 0, 0, + 0, 0.79515431915808965079861536651151254773, 0.60640713116208888600056070572463795543, + 0, -0.60640713116208888600056070572463795543, 0.79515431915808965079861536651151254773 + ), + btVector3(3, 6, 9) + }; + shape->addChildShape(transform1, mesh1.release()); + shape->addChildShape(transform2, mesh2.release()); + Resource::BulletShape expected; + expected.mCollisionShape.reset(shape.release()); + expected.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } } diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 7e5bcdb3be..31eb63144c 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -166,7 +166,7 @@ struct Node : public Named if (nif->getVersion() >= NIFStream::generateVersion(10,0,1,0)) collision.read(nif); - parent = nullptr; + parents.clear(); isBone = false; } @@ -180,7 +180,7 @@ struct Node : public Named // Parent node, or nullptr for the root node. As far as I'm aware, only // NiNodes (or types derived from NiNodes) can be parents. - NiNode *parent; + std::vector parents; bool isBone{false}; @@ -238,7 +238,7 @@ struct NiNode : Node { // Why would a unique list of children contain empty refs? if(!children[i].empty()) - children[i]->parent = this; + children[i]->parents.push_back(this); } } }; diff --git a/components/nif/parent.hpp b/components/nif/parent.hpp new file mode 100644 index 0000000000..afa540c8ec --- /dev/null +++ b/components/nif/parent.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_COMPONENTS_NIF_PARENT_HPP +#define OPENMW_COMPONENTS_NIF_PARENT_HPP + +namespace Nif +{ + struct NiNode; + + struct Parent + { + const NiNode& mNiNode; + const Parent* mParent; + }; +} + +#endif diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 4be07525a6..25079ad9e0 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -16,15 +16,17 @@ #include #include #include +#include namespace { -osg::Matrixf getWorldTransform(const Nif::Node& node) +osg::Matrixf getWorldTransform(const Nif::Node& node, const Nif::Parent* nodeParent) { - if(node.parent != nullptr) - return node.trafo.toMatrix() * getWorldTransform(*node.parent); - return node.trafo.toMatrix(); + osg::Matrixf result = node.trafo.toMatrix(); + for (const Nif::Parent* parent = nodeParent; parent != nullptr; parent = parent->mParent) + result *= parent->mNiNode.trafo.toMatrix(); + return result; } bool pathFileNameStartsWithX(const std::string& path) @@ -214,7 +216,7 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) for (const Nif::Node* node : roots) { bool autogenerated = hasAutoGeneratedCollision(*node); - handleNode(filename, *node, 0, autogenerated, isAnimated, autogenerated); + handleNode(filename, *node, nullptr, 0, autogenerated, isAnimated, autogenerated); } if (mCompoundShape) @@ -305,8 +307,8 @@ bool BulletNifLoader::hasAutoGeneratedCollision(const Nif::Node& rootNode) return true; } -void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& node, int flags, - bool isCollisionNode, bool isAnimated, bool autogenerated, bool avoid) +void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& node, const Nif::Parent* parent, + 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)) @@ -361,7 +363,7 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& n || node.recType == Nif::RC_NiTriStrips || node.recType == Nif::RC_BSLODTriShape)) { - handleNiTriShape(node, flags, getWorldTransform(node), isAnimated, avoid); + handleNiTriShape(node, parent, flags, getWorldTransform(node, parent), isAnimated, avoid); } } @@ -369,30 +371,31 @@ void BulletNifLoader::handleNode(const std::string& fileName, const Nif::Node& n if (const Nif::NiNode *ninode = dynamic_cast(&node)) { const Nif::NodeList &list = ninode->children; + const Nif::Parent currentParent {*ninode, parent}; for(size_t i = 0;i < list.length();i++) { if (list[i].empty()) continue; - assert(list[i].get().parent == &node); - handleNode(fileName, list[i].get(), flags, isCollisionNode, isAnimated, autogenerated, avoid); + assert(std::find(list[i]->parents.begin(), list[i]->parents.end(), ninode) != list[i]->parents.end()); + handleNode(fileName, list[i].get(), ¤tParent, flags, isCollisionNode, isAnimated, autogenerated, avoid); } } } -void BulletNifLoader::handleNiTriShape(const Nif::Node& nifNode, int flags, const osg::Matrixf &transform, - bool isAnimated, bool avoid) +void BulletNifLoader::handleNiTriShape(const Nif::Node& nifNode, const Nif::Parent* parent, int flags, + const osg::Matrixf &transform, bool isAnimated, bool avoid) { // If the object was marked "NCO" earlier, it shouldn't collide with // anything. So don't do anything. if ((flags & 0x800)) return; - handleNiTriShape(static_cast(nifNode), transform, isAnimated, avoid); + handleNiTriShape(static_cast(nifNode), parent, transform, isAnimated, avoid); } -void BulletNifLoader::handleNiTriShape(const Nif::NiGeometry& niGeometry, const osg::Matrixf &transform, - bool isAnimated, bool avoid) +void BulletNifLoader::handleNiTriShape(const Nif::NiGeometry& niGeometry, const Nif::Parent* nodeParent, + const osg::Matrixf &transform, bool isAnimated, bool avoid) { if (niGeometry.data.empty() || niGeometry.data->vertices.empty()) return; @@ -413,8 +416,8 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiGeometry& niGeometry, const childMesh.release(); float scale = niGeometry.trafo.scale; - for (const Nif::Node* parent = niGeometry.parent; parent != nullptr; parent = parent->parent) - scale *= parent->trafo.scale; + for (const Nif::Parent* parent = nodeParent; parent != nullptr; parent = parent->mParent) + scale *= parent->mNiNode.trafo.scale; osg::Quat q = transform.getRotate(); osg::Vec3f v = transform.getTrans(); childShape->setLocalScaling(btVector3(scale, scale, scale)); diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index e0fec338c6..01a17a5aa1 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -28,6 +28,7 @@ namespace Nif struct NiTriShape; struct NiTriStrips; struct NiGeometry; + struct Parent; } namespace NifBullet @@ -55,14 +56,16 @@ public: private: bool findBoundingBox(const Nif::Node& node, const std::string& filename); - void handleNode(const std::string& fileName, const Nif::Node& node, int flags, bool isCollisionNode, - bool isAnimated=false, bool autogenerated=false, bool avoid=false); + void handleNode(const std::string& fileName, const Nif::Node& node, const Nif::Parent* parent, int flags, + bool isCollisionNode, bool isAnimated=false, bool autogenerated=false, bool avoid=false); 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, const Nif::Parent* parent, int flags, const osg::Matrixf& transform, + bool isAnimated, bool avoid); - void handleNiTriShape(const Nif::NiGeometry& nifNode, const osg::Matrixf& transform, bool isAnimated, bool avoid); + void handleNiTriShape(const Nif::NiGeometry& nifNode, const Nif::Parent* parent, const osg::Matrixf& transform, + bool isAnimated, bool avoid); std::unique_ptr mCompoundShape; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 43af68fc90..67cb16b795 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -17,6 +17,7 @@ #include #include #include +#include // particle #include @@ -74,10 +75,10 @@ namespace } // Collect all properties affecting the given drawable that should be handled on drawable basis rather than on the node hierarchy above it. - void collectDrawableProperties(const Nif::Node* nifNode, std::vector& out) + void collectDrawableProperties(const Nif::Node* nifNode, const Nif::Parent* parent, std::vector& out) { - if (nifNode->parent) - collectDrawableProperties(nifNode->parent, out); + if (parent != nullptr) + collectDrawableProperties(&parent->mNiNode, parent->mParent, out); const Nif::PropertyList& props = nifNode->props; for (size_t i = 0; i setDataVariance(osg::Object::STATIC); for (const Nif::Node* root : roots) { - auto node = handleNode(root, nullptr, imageManager, std::vector(), 0, false, false, false, &textkeys->mTextKeys); + auto node = handleNode(root, nullptr, nullptr, imageManager, std::vector(), 0, false, false, false, &textkeys->mTextKeys); created->addChild(node); } if (mHasNightDayLabel) @@ -339,7 +340,7 @@ namespace NifOsg { // Get the lowest numbered recIndex of the NiTexturingProperty root node. // This is what is overridden when a spell effect "particle texture" is used. - if (nifNode->parent == nullptr && !mFoundFirstRootTexturingProperty && props[i].getPtr()->recType == Nif::RC_NiTexturingProperty) + if (nifNode->parents.empty() && !mFoundFirstRootTexturingProperty && props[i].getPtr()->recType == Nif::RC_NiTexturingProperty) { mFirstRootTextureIndex = props[i].getPtr()->recIndex; mFoundFirstRootTexturingProperty = true; @@ -483,7 +484,7 @@ namespace NifOsg // The Root node can be created as a Group if no transformation is required. // This takes advantage of the fact root nodes can't have additional controllers // loaded from an external .kf file (original engine just throws "can't find node" errors if you try). - if (!nifNode->parent && nifNode->controller.empty() && nifNode->trafo.isIdentity()) + if (nifNode->parents.empty() && nifNode->controller.empty() && nifNode->trafo.isIdentity()) node = new osg::Group; dataVariance = nifNode->isBone ? osg::Object::DYNAMIC : osg::Object::STATIC; @@ -505,8 +506,10 @@ namespace NifOsg return node; } - osg::ref_ptr handleNode(const Nif::Node* nifNode, osg::Group* parentNode, Resource::ImageManager* imageManager, - std::vector boundTextures, int animflags, bool skipMeshes, bool hasMarkers, bool hasAnimatedParents, SceneUtil::TextKeyMap* textKeys, osg::Node* rootNode=nullptr) + osg::ref_ptr handleNode(const Nif::Node* nifNode, const Nif::Parent* parent, osg::Group* parentNode, + Resource::ImageManager* imageManager, std::vector boundTextures, int animflags, + bool skipMeshes, bool hasMarkers, bool hasAnimatedParents, SceneUtil::TextKeyMap* textKeys, + osg::Node* rootNode=nullptr) { if (rootNode != nullptr && Misc::StringUtils::ciEqual(nifNode->name, "Bounding Box")) return nullptr; @@ -615,9 +618,9 @@ namespace NifOsg Nif::NiSkinInstancePtr skin = static_cast(nifNode)->skin; if (skin.empty()) - handleGeometry(nifNode, node, composite, boundTextures, animflags); + handleGeometry(nifNode, parent, node, composite, boundTextures, animflags); else - handleSkinnedGeometry(nifNode, node, composite, boundTextures, animflags); + handleSkinnedGeometry(nifNode, parent, node, composite, boundTextures, animflags); if (!nifNode->controller.empty()) handleMeshControllers(nifNode, node, composite, boundTextures, animflags); @@ -625,7 +628,7 @@ namespace NifOsg } if (nifNode->recType == Nif::RC_NiParticles) - handleParticleSystem(nifNode, node, composite, animflags); + handleParticleSystem(nifNode, parent, node, composite, animflags); if (composite->getNumControllers() > 0) { @@ -681,10 +684,11 @@ namespace NifOsg } const Nif::NodeList &children = ninode->children; + const Nif::Parent currentParent {*ninode, parent}; for(size_t i = 0;i < children.length();++i) { if(!children[i].empty()) - handleNode(children[i].getPtr(), currentNode, imageManager, boundTextures, animflags, skipMeshes, hasMarkers, hasAnimatedParents, textKeys, rootNode); + handleNode(children[i].getPtr(), ¤tParent, currentNode, imageManager, boundTextures, animflags, skipMeshes, hasMarkers, hasAnimatedParents, textKeys, rootNode); } } @@ -1026,7 +1030,8 @@ namespace NifOsg mEmitterQueue.clear(); } - void handleParticleSystem(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, int animflags) + void handleParticleSystem(const Nif::Node *nifNode, const Nif::Parent* parent, osg::Group *parentNode, + SceneUtil::CompositeStateSetUpdater* composite, int animflags) { osg::ref_ptr partsys (new ParticleSystem); partsys->setSortMode(osgParticle::ParticleSystem::SORT_BACK_TO_FRONT); @@ -1095,7 +1100,7 @@ namespace NifOsg handleParticlePrograms(partctrl->affectors, partctrl->colliders, parentNode, partsys.get(), rf); std::vector drawableProps; - collectDrawableProperties(nifNode, drawableProps); + collectDrawableProperties(nifNode, parent, drawableProps); applyDrawableProperties(parentNode, drawableProps, composite, true, animflags); // particle system updater (after the emitters and affectors in the scene graph) @@ -1146,7 +1151,9 @@ namespace NifOsg } } - void handleNiGeometry(const Nif::Node *nifNode, osg::Geometry *geometry, osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) + void handleNiGeometry(const Nif::Node *nifNode, const Nif::Parent* parent, osg::Geometry *geometry, + osg::Node* parentNode, SceneUtil::CompositeStateSetUpdater* composite, + const std::vector& boundTextures, int animflags) { const Nif::NiGeometry* niGeometry = static_cast(nifNode); if (niGeometry->data.empty()) @@ -1198,15 +1205,17 @@ namespace NifOsg // - there are 3 "overlapping" nif properties that all affect the osg::Material, handling them // above the actual renderable would be tedious. std::vector drawableProps; - collectDrawableProperties(nifNode, drawableProps); + collectDrawableProperties(nifNode, parent, drawableProps); applyDrawableProperties(parentNode, drawableProps, composite, !niGeometryData->colors.empty(), animflags); } - void handleGeometry(const Nif::Node* nifNode, osg::Group* parentNode, SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) + void handleGeometry(const Nif::Node* nifNode, const Nif::Parent* parent, osg::Group* parentNode, + SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, + int animflags) { assert(isTypeGeometry(nifNode->recType)); osg::ref_ptr geom (new osg::Geometry); - handleNiGeometry(nifNode, geom, parentNode, composite, boundTextures, animflags); + handleNiGeometry(nifNode, parent, geom, parentNode, composite, boundTextures, animflags); // If the record had no valid geometry data in it, early-out if (geom->empty()) return; @@ -1250,12 +1259,12 @@ namespace NifOsg return morphGeom; } - void handleSkinnedGeometry(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, - const std::vector& boundTextures, int animflags) + void handleSkinnedGeometry(const Nif::Node *nifNode, const Nif::Parent* parent, osg::Group *parentNode, + SceneUtil::CompositeStateSetUpdater* composite, const std::vector& boundTextures, int animflags) { assert(isTypeGeometry(nifNode->recType)); osg::ref_ptr geometry (new osg::Geometry); - handleNiGeometry(nifNode, geometry, parentNode, composite, boundTextures, animflags); + handleNiGeometry(nifNode, parent, geometry, parentNode, composite, boundTextures, animflags); if (geometry->empty()) return; osg::ref_ptr rig(new SceneUtil::RigGeometry);