diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f909a19fd..7257e23386 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,6 +58,7 @@ Bug #5557: Diagonal movement is noticeably slower with analogue stick Bug #5588: Randomly clicking on the journal's right-side page when it's empty shows random topics Bug #5603: Setting constant effect cast style doesn't correct effects view + Bug #5604: Only one valid NIF root node is loaded from a single file Bug #5611: Usable items with "0 Uses" should be used only once Bug #5622: Can't properly interact with the console when in pause menu Bug #5633: Damage Spells in effect before god mode is enabled continue to hurt the player character and can kill them diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index b1461e5367..db9d5ae437 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -123,73 +123,79 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) mStaticMesh.reset(); mAvoidStaticMesh.reset(); - Nif::Node* node = nullptr; const size_t numRoots = nif.numRoots(); + std::vector roots; for (size_t i = 0; i < numRoots; ++i) { Nif::Record* r = nif.getRoot(i); assert(r != nullptr); + Nif::Node* node = nullptr; if ((node = dynamic_cast(r))) - break; + roots.emplace_back(node); } const std::string filename = nif.getFilename(); - if (!node) + if (roots.empty()) { warn("Found no root nodes in NIF file " + filename); return mShape; } - if (findBoundingBox(node, filename)) + // Try to find a valid bounding box first. If one's found for any root node, use that. + for (const Nif::Node* node : roots) { - const btVector3 halfExtents = Misc::Convert::toBullet(mShape->mCollisionBoxHalfExtents); - const btVector3 origin = Misc::Convert::toBullet(mShape->mCollisionBoxTranslate); - std::unique_ptr compound (new btCompoundShape); - std::unique_ptr boxShape(new btBoxShape(halfExtents)); - btTransform transform = btTransform::getIdentity(); - transform.setOrigin(origin); - compound->addChildShape(transform, boxShape.get()); - boxShape.release(); - - mShape->mCollisionShape = compound.release(); - return mShape; + if (findBoundingBox(node, filename)) + { + const btVector3 halfExtents = Misc::Convert::toBullet(mShape->mCollisionBoxHalfExtents); + const btVector3 origin = Misc::Convert::toBullet(mShape->mCollisionBoxTranslate); + std::unique_ptr compound (new btCompoundShape); + std::unique_ptr boxShape(new btBoxShape(halfExtents)); + btTransform transform = btTransform::getIdentity(); + transform.setOrigin(origin); + compound->addChildShape(transform, boxShape.get()); + boxShape.release(); + + mShape->mCollisionShape = compound.release(); + return mShape; + } } - else + // 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(filename); + + // If there's no bounding box, we'll have to generate a Bullet collision shape + // from the collision data present in every root node. + for (const Nif::Node* node : roots) { bool autogenerated = hasAutoGeneratedCollision(node); - - // 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(filename); - handleNode(filename, node, 0, autogenerated, isAnimated, autogenerated); + } - if (mCompoundShape) - { - if (mStaticMesh) - { - btTransform trans; - trans.setIdentity(); - std::unique_ptr child(new Resource::TriangleMeshShape(mStaticMesh.get(), true)); - mCompoundShape->addChildShape(trans, child.get()); - child.release(); - mStaticMesh.release(); - } - mShape->mCollisionShape = mCompoundShape.release(); - } - else if (mStaticMesh) + if (mCompoundShape) + { + if (mStaticMesh) { - mShape->mCollisionShape = new Resource::TriangleMeshShape(mStaticMesh.get(), true); + btTransform trans; + trans.setIdentity(); + std::unique_ptr child(new Resource::TriangleMeshShape(mStaticMesh.get(), true)); + mCompoundShape->addChildShape(trans, child.get()); + child.release(); mStaticMesh.release(); } + mShape->mCollisionShape = mCompoundShape.release(); + } + else if (mStaticMesh) + { + mShape->mCollisionShape = new Resource::TriangleMeshShape(mStaticMesh.get(), true); + mStaticMesh.release(); + } - if (mAvoidStaticMesh) - { - mShape->mAvoidCollisionShape = new Resource::TriangleMeshShape(mAvoidStaticMesh.get(), false); - mAvoidStaticMesh.release(); - } - - return mShape; + if (mAvoidStaticMesh) + { + mShape->mAvoidCollisionShape = new Resource::TriangleMeshShape(mAvoidStaticMesh.get(), false); + mAvoidStaticMesh.release(); } + + return mShape; } // Find a boundingBox in the node hierarchy. @@ -228,8 +234,7 @@ bool BulletNifLoader::findBoundingBox(const Nif::Node* node, const std::string& { if(!list[i].empty()) { - bool found = findBoundingBox (list[i].getPtr(), filename); - if (found) + if (findBoundingBox(list[i].getPtr(), filename)) return true; } } diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 751bdb51fe..4bc48637ce 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -231,6 +231,9 @@ namespace NifOsg size_t mFirstRootTextureIndex = -1; bool mFoundFirstRootTexturingProperty = false; + bool mHasNightDayLabel = false; + bool mHasHerbalismLabel = false; + // This is used to queue emitters that weren't attached to their node yet. std::vector>> mEmitterQueue; @@ -294,20 +297,31 @@ namespace NifOsg osg::ref_ptr load(Nif::NIFFilePtr nif, Resource::ImageManager* imageManager) { - const Nif::Node* nifNode = nullptr; const size_t numRoots = nif->numRoots(); + std::vector roots; for (size_t i = 0; i < numRoots; ++i) { const Nif::Record* r = nif->getRoot(i); + const Nif::Node* nifNode = nullptr; if ((nifNode = dynamic_cast(r))) - break; + roots.emplace_back(nifNode); } - if (!nifNode) + if (roots.empty()) nif->fail("Found no root nodes"); osg::ref_ptr textkeys (new SceneUtil::TextKeyMapHolder); - osg::ref_ptr created = handleNode(nifNode, nullptr, imageManager, std::vector(), 0, false, false, false, &textkeys->mTextKeys); + osg::ref_ptr created(new osg::Group); + created->setDataVariance(osg::Object::STATIC); + for (const Nif::Node* root : roots) + { + auto node = handleNode(root, nullptr, imageManager, std::vector(), 0, false, false, false, &textkeys->mTextKeys); + created->addChild(node); + } + if (mHasNightDayLabel) + created->getOrCreateUserDataContainer()->addDescription(Constants::NightDayLabel); + if (mHasHerbalismLabel) + created->getOrCreateUserDataContainer()->addDescription(Constants::HerbalismLabel); // Attach particle emitters to their nodes which should all be loaded by now. handleQueuedParticleEmitters(created, nif); @@ -315,18 +329,11 @@ namespace NifOsg if (nif->getUseSkinning()) { osg::ref_ptr skel = new SceneUtil::Skeleton; - - osg::Group* root = created->asGroup(); - if (root && root->getDataVariance() == osg::Object::STATIC && !root->asTransform()) - { - skel->setStateSet(root->getStateSet()); - skel->setName(root->getName()); - for (unsigned int i=0; igetNumChildren(); ++i) - skel->addChild(root->getChild(i)); - root->removeChildren(0, root->getNumChildren()); - } - else - skel->addChild(created); + skel->setStateSet(created->getStateSet()); + skel->setName(created->getName()); + for (unsigned int i=0; i < created->getNumChildren(); ++i) + skel->addChild(created->getChild(i)); + created->removeChildren(0, created->getNumChildren()); created = skel; } @@ -632,7 +639,7 @@ namespace NifOsg } if(nifNode->recType == Nif::RC_NiAutoNormalParticles || nifNode->recType == Nif::RC_NiRotatingParticles) - handleParticleSystem(nifNode, node, composite, animflags, rootNode); + handleParticleSystem(nifNode, node, composite, animflags); if (composite->getNumControllers() > 0) { @@ -662,10 +669,10 @@ namespace NifOsg const Nif::NiSwitchNode* niSwitchNode = static_cast(nifNode); osg::ref_ptr switchNode = handleSwitchNode(niSwitchNode); node->addChild(switchNode); - if (niSwitchNode->name == Constants::NightDayLabel && !SceneUtil::hasUserDescription(rootNode, Constants::NightDayLabel)) - rootNode->getOrCreateUserDataContainer()->addDescription(Constants::NightDayLabel); - else if (niSwitchNode->name == Constants::HerbalismLabel && !SceneUtil::hasUserDescription(rootNode, Constants::HerbalismLabel)) - rootNode->getOrCreateUserDataContainer()->addDescription(Constants::HerbalismLabel); + if (niSwitchNode->name == Constants::NightDayLabel) + mHasNightDayLabel = true; + else if (niSwitchNode->name == Constants::HerbalismLabel) + mHasHerbalismLabel = true; currentNode = switchNode; } @@ -1023,7 +1030,7 @@ namespace NifOsg return emitter; } - void handleQueuedParticleEmitters(osg::Node* rootNode, Nif::NIFFilePtr nif) + void handleQueuedParticleEmitters(osg::Group* rootNode, Nif::NIFFilePtr nif) { for (const auto& emitterPair : mEmitterQueue) { @@ -1044,7 +1051,7 @@ namespace NifOsg mEmitterQueue.clear(); } - void handleParticleSystem(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, int animflags, osg::Node* rootNode) + void handleParticleSystem(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, int animflags) { osg::ref_ptr partsys (new ParticleSystem); partsys->setSortMode(osgParticle::ParticleSystem::SORT_BACK_TO_FRONT);