From c08f9e13af2a9b5319eaab074465e7733bcad50d Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Tue, 28 Apr 2020 23:53:00 +0300 Subject: [PATCH 1/3] Allow emitters to be attached to nodes after particle systems --- components/nifosg/nifloader.cpp | 45 +++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 7d62d1ef11..15c461bf2b 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -219,6 +219,9 @@ namespace NifOsg size_t mFirstRootTextureIndex = -1; bool mFoundFirstRootTexturingProperty = false; + // This is used to queue emitters that weren't attached to their node yet. + std::vector>> mEmitterQueue; + static void loadKf(Nif::NIFFilePtr nif, KeyframeHolder& target) { if(nif->numRoots() < 1) @@ -290,6 +293,9 @@ namespace NifOsg osg::ref_ptr created = handleNode(nifNode, nullptr, imageManager, std::vector(), 0, false, false, false, &textkeys->mTextKeys); + // Attach particle emitters to their nodes which should all be loaded by now. + handleQueuedParticleEmitters(created, nif); + if (nif->getUseSkinning()) { osg::ref_ptr skel = new SceneUtil::Skeleton; @@ -979,6 +985,27 @@ namespace NifOsg return emitter; } + void handleQueuedParticleEmitters(osg::Node* rootNode, Nif::NIFFilePtr nif) + { + for (const auto& emitterPair : mEmitterQueue) + { + size_t recIndex = emitterPair.first; + FindGroupByRecIndex findEmitterNode(recIndex); + rootNode->accept(findEmitterNode); + osg::Group* emitterNode = findEmitterNode.mFound; + if (!emitterNode) + { + nif->warn("Failed to find particle emitter emitter node (node record index " + std::to_string(recIndex) + ")"); + continue; + } + + // Emitter attached to the emitter node. Note one side effect of the emitter using the CullVisitor is that hiding its node + // actually causes the emitter to stop firing. Convenient, because MW behaves this way too! + emitterNode->addChild(emitterPair.second); + } + mEmitterQueue.clear(); + } + void handleParticleSystem(const Nif::Node *nifNode, osg::Group *parentNode, SceneUtil::CompositeStateSetUpdater* composite, int animflags, osg::Node* rootNode) { osg::ref_ptr partsys (new ParticleSystem); @@ -1028,22 +1055,8 @@ namespace NifOsg emitter->setParticleSystem(partsys); emitter->setReferenceFrame(osgParticle::ParticleProcessor::RELATIVE_RF); - // Note: we assume that the Emitter node is placed *before* the Particle node in the scene graph. - // This seems to be true for all NIF files in the game that I've checked, suggesting that NIFs work similar to OSG with regards to update order. - // If something ever violates this assumption, the worst that could happen is the culling being one frame late, which wouldn't be a disaster. - - FindGroupByRecIndex find (partctrl->emitter->recIndex); - rootNode->accept(find); - if (!find.mFound) - { - Log(Debug::Info) << "can't find emitter node, wrong node order? in " << mFilename; - return; - } - osg::Group* emitterNode = find.mFound; - - // Emitter attached to the emitter node. Note one side effect of the emitter using the CullVisitor is that hiding its node - // actually causes the emitter to stop firing. Convenient, because MW behaves this way too! - emitterNode->addChild(emitter); + // The emitter node may not actually be handled yet, so let's delay attaching the emitter to a later moment. + mEmitterQueue.emplace_back(partctrl->emitter->recIndex, emitter); osg::ref_ptr callback(new ParticleSystemController(partctrl)); setupController(partctrl, callback, animflags); From f516178ec9b42840fc2d626844f402893a0f8cb1 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Wed, 29 Apr 2020 11:06:14 +0300 Subject: [PATCH 2/3] Fix particle processor cloning Extend emitter handling comment in NIF loader --- components/nifosg/nifloader.cpp | 4 +++- components/sceneutil/clone.cpp | 12 ++++++++++++ components/sceneutil/clone.hpp | 3 ++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 15c461bf2b..f89720dd4c 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1056,6 +1056,8 @@ namespace NifOsg emitter->setReferenceFrame(osgParticle::ParticleProcessor::RELATIVE_RF); // The emitter node may not actually be handled yet, so let's delay attaching the emitter to a later moment. + // If the emitter node is placed later than the particle node, it'll have a single frame delay in particle processing. + // But that shouldn't be a game-breaking issue. mEmitterQueue.emplace_back(partctrl->emitter->recIndex, emitter); osg::ref_ptr callback(new ParticleSystemController(partctrl)); @@ -1073,7 +1075,7 @@ namespace NifOsg partsys->update(0.0, nv); } - // affectors must be attached *after* the emitter in the scene graph for correct update order + // affectors should be attached *after* the emitter in the scene graph for correct update order // attach to same node as the ParticleSystem, we need osgParticle Operators to get the correct // localToWorldMatrix for transforming to particle space handleParticlePrograms(partctrl->affectors, partctrl->colliders, parentNode, partsys.get(), rf); diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index 04fd5d78db..0423d61171 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -69,6 +69,15 @@ namespace SceneUtil osgParticle::ParticleProcessor* CopyOp::operator() (const osgParticle::ParticleProcessor* processor) const { osgParticle::ParticleProcessor* cloned = osg::clone(processor, osg::CopyOp::DEEP_COPY_CALLBACKS); + for (std::map::const_iterator it = mMap3.begin(); it != mMap3.end(); ++it) + { + if (processor->getParticleSystem() == it->first) + { + cloned->setParticleSystem(it->second); + return cloned; + } + } + mMap[cloned] = processor->getParticleSystem(); return cloned; } @@ -93,6 +102,9 @@ namespace SceneUtil updater->addParticleSystem(cloned); } } + // In rare situations a particle processor may be placed after the particle system in the scene graph. + mMap3[partsys] = cloned; + return cloned; } diff --git a/components/sceneutil/clone.hpp b/components/sceneutil/clone.hpp index 8a18eeb20b..b0a9d56c3e 100644 --- a/components/sceneutil/clone.hpp +++ b/components/sceneutil/clone.hpp @@ -35,10 +35,11 @@ namespace SceneUtil virtual osg::Object* operator ()(const osg::Object* node) const; private: - // maps new ParticleProcessor to their old ParticleSystem pointer + // maps new pointers to their old pointers // a little messy, but I think this should be the most efficient way mutable std::map mMap; mutable std::map mMap2; + mutable std::map mMap3; }; } From 6b874e397b030ef042ae0426551fdceca290dc89 Mon Sep 17 00:00:00 2001 From: Capostrophic Date: Wed, 29 Apr 2020 12:25:52 +0300 Subject: [PATCH 3/3] Make particle system cloning map names more sensible --- components/sceneutil/clone.cpp | 24 ++++++++++++------------ components/sceneutil/clone.hpp | 6 +++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/components/sceneutil/clone.cpp b/components/sceneutil/clone.cpp index 0423d61171..0df0f4a5b2 100644 --- a/components/sceneutil/clone.cpp +++ b/components/sceneutil/clone.cpp @@ -47,7 +47,7 @@ namespace SceneUtil if (const osgParticle::ParticleSystemUpdater* updater = dynamic_cast(node)) { osgParticle::ParticleSystemUpdater* cloned = new osgParticle::ParticleSystemUpdater(*updater, osg::CopyOp::SHALLOW_COPY); - mMap2[cloned] = updater->getParticleSystem(0); + mUpdaterToOldPs[cloned] = updater->getParticleSystem(0); return cloned; } return osg::CopyOp::operator()(node); @@ -69,16 +69,16 @@ namespace SceneUtil osgParticle::ParticleProcessor* CopyOp::operator() (const osgParticle::ParticleProcessor* processor) const { osgParticle::ParticleProcessor* cloned = osg::clone(processor, osg::CopyOp::DEEP_COPY_CALLBACKS); - for (std::map::const_iterator it = mMap3.begin(); it != mMap3.end(); ++it) + for (const auto& oldPsNewPsPair : mOldPsToNewPs) { - if (processor->getParticleSystem() == it->first) + if (processor->getParticleSystem() == oldPsNewPsPair.first) { - cloned->setParticleSystem(it->second); + cloned->setParticleSystem(oldPsNewPsPair.second); return cloned; } } - mMap[cloned] = processor->getParticleSystem(); + mProcessorToOldPs[cloned] = processor->getParticleSystem(); return cloned; } @@ -86,24 +86,24 @@ namespace SceneUtil { osgParticle::ParticleSystem* cloned = osg::clone(partsys, *this); - for (std::map::const_iterator it = mMap.begin(); it != mMap.end(); ++it) + for (const auto& processorPsPair : mProcessorToOldPs) { - if (it->second == partsys) + if (processorPsPair.second == partsys) { - it->first->setParticleSystem(cloned); + processorPsPair.first->setParticleSystem(cloned); } } - for (std::map::const_iterator it = mMap2.begin(); it != mMap2.end(); ++it) + for (const auto& updaterPsPair : mUpdaterToOldPs) { - if (it->second == partsys) + if (updaterPsPair.second == partsys) { - osgParticle::ParticleSystemUpdater* updater = it->first; + osgParticle::ParticleSystemUpdater* updater = updaterPsPair.first; updater->removeParticleSystem(updater->getParticleSystem(0)); updater->addParticleSystem(cloned); } } // In rare situations a particle processor may be placed after the particle system in the scene graph. - mMap3[partsys] = cloned; + mOldPsToNewPs[partsys] = cloned; return cloned; } diff --git a/components/sceneutil/clone.hpp b/components/sceneutil/clone.hpp index b0a9d56c3e..20788799fa 100644 --- a/components/sceneutil/clone.hpp +++ b/components/sceneutil/clone.hpp @@ -37,9 +37,9 @@ namespace SceneUtil private: // maps new pointers to their old pointers // a little messy, but I think this should be the most efficient way - mutable std::map mMap; - mutable std::map mMap2; - mutable std::map mMap3; + mutable std::map mProcessorToOldPs; + mutable std::map mUpdaterToOldPs; + mutable std::map mOldPsToNewPs; }; }