diff --git a/components/nif/node.hpp b/components/nif/node.hpp index b63fc03804..a776bd40bc 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -212,6 +212,7 @@ struct NiNode : Node ControllerFlag_Active = 0x8 }; enum BSPArrayController { + BSPArrayController_AtNode = 0x8, BSPArrayController_AtVertex = 0x10 }; diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 17bd381a3d..a9c2449e56 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -50,6 +50,21 @@ namespace { + struct DisableOptimizer : osg::NodeVisitor + { + DisableOptimizer(osg::NodeVisitor::TraversalMode mode = TRAVERSE_ALL_CHILDREN) : osg::NodeVisitor(mode) {} + + void apply(osg::Node &node) override + { + node.setDataVariance(osg::Object::DYNAMIC); + traverse(node); + } + + void apply(osg::Drawable &node) override + { + traverse(node); + } + }; void getAllNiNodes(const Nif::Node* node, std::vector& outIndices) { @@ -1072,10 +1087,10 @@ namespace NifOsg partctrl->verticalDir, partctrl->verticalAngle, partctrl->lifetime, partctrl->lifetimeRandom); emitter->setShooter(shooter); + emitter->setFlags(partctrl->flags); - if (atVertex && (partctrl->recType == Nif::RC_NiBSPArrayController)) + if (partctrl->recType == Nif::RC_NiBSPArrayController && atVertex) { - emitter->setUseGeometryEmitter(true); emitter->setGeometryEmitterTarget(partctrl->emitter->recIndex); } else @@ -1107,6 +1122,9 @@ namespace NifOsg // 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); + + DisableOptimizer disableOptimizer; + emitterNode->accept(disableOptimizer); } mEmitterQueue.clear(); } diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 3b66a956e8..88e6e1f4c8 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -1,6 +1,7 @@ #include "particle.hpp" #include +#include #include #include @@ -11,6 +12,7 @@ #include #include #include +#include #include #include @@ -56,6 +58,44 @@ namespace osg::Geometry* mGeometry; }; + + class LocalToWorldAccumulator : public osg::NodeVisitor + { + public: + LocalToWorldAccumulator(osg::Matrix& matrix) : osg::NodeVisitor(), mMatrix(matrix) {} + + virtual void apply(osg::Transform& transform) + { + if (&transform != mLastAppliedTransform) + { + mLastAppliedTransform = &transform; + mLastMatrix = mMatrix; + } + transform.computeLocalToWorldMatrix(mMatrix, this); + } + + void accumulate(const osg::NodePath& path) + { + if (path.empty()) + return; + + size_t i = path.size(); + + for (auto rit = path.rbegin(); rit != path.rend(); rit++, --i) + { + const osg::Camera* camera = (*rit)->asCamera(); + if (camera && (camera->getReferenceFrame() != osg::Transform::RELATIVE_RF || camera->getParents().empty())) + break; + } + + for(; i < path.size(); ++i) + path[i]->accept(*this); + } + + osg::Matrix& mMatrix; + std::optional mLastMatrix; + osg::Transform* mLastAppliedTransform = nullptr; + }; } namespace NifOsg @@ -310,7 +350,7 @@ void GravityAffector::operate(osgParticle::Particle *particle, double dt) Emitter::Emitter() : osgParticle::Emitter() - , mUseGeometryEmitter(false) + , mFlags(0) , mGeometryEmitterTarget(std::nullopt) { } @@ -322,7 +362,7 @@ Emitter::Emitter(const Emitter ©, const osg::CopyOp ©op) , mShooter(copy.mShooter) // need a deep copy because the remainder is stored in the object , mCounter(static_cast(copy.mCounter->clone(osg::CopyOp::DEEP_COPY_ALL))) - , mUseGeometryEmitter(copy.mUseGeometryEmitter) + , mFlags(copy.mFlags) , mGeometryEmitterTarget(copy.mGeometryEmitterTarget) , mCachedGeometryEmitter(copy.mCachedGeometryEmitter) { @@ -330,7 +370,7 @@ Emitter::Emitter(const Emitter ©, const osg::CopyOp ©op) Emitter::Emitter(const std::vector &targets) : mTargets(targets) - , mUseGeometryEmitter(false) + , mFlags(0) , mGeometryEmitterTarget(std::nullopt) { } @@ -356,11 +396,13 @@ void Emitter::emitParticles(double dt) osg::ref_ptr geometryVertices = nullptr; - if (mUseGeometryEmitter || !mTargets.empty()) + const bool useGeometryEmitter = mFlags & Nif::NiNode::BSPArrayController_AtVertex; + + if (useGeometryEmitter || !mTargets.empty()) { int recIndex; - if (mUseGeometryEmitter) + if (useGeometryEmitter) { if (!mGeometryEmitterTarget.has_value()) return; @@ -383,7 +425,7 @@ void Emitter::emitParticles(double dt) return; } - if (mUseGeometryEmitter) + if (useGeometryEmitter) { if (!mCachedGeometryEmitter.lock(geometryVertices)) { @@ -403,12 +445,30 @@ void Emitter::emitParticles(double dt) osg::NodePath path = visitor.mFoundPath; path.erase(path.begin()); - emitterToPs = osg::computeLocalToWorld(path) * emitterToPs; + if (!useGeometryEmitter && (mFlags & Nif::NiNode::BSPArrayController_AtNode) && path.size()) + { + osg::Matrix current; + + LocalToWorldAccumulator accum(current); + accum.accumulate(path); + + osg::Matrix parent = accum.mLastMatrix.value_or(current); + + auto p1 = parent.getTrans(); + auto p2 = current.getTrans(); + current.setTrans((p2 - p1) * Misc::Rng::rollClosedProbability() + p1); + + emitterToPs = current * emitterToPs; + } + else + { + emitterToPs = osg::computeLocalToWorld(path) * emitterToPs; + } } emitterToPs.orthoNormalize(emitterToPs); - if (mUseGeometryEmitter && (!geometryVertices.valid() || geometryVertices->empty())) + if (useGeometryEmitter && (!geometryVertices.valid() || geometryVertices->empty())) return; for (int i=0; icreateParticle(nullptr); if (P) { - if (mUseGeometryEmitter) + if (useGeometryEmitter) P->setPosition((*geometryVertices)[Misc::Rng::rollDice(geometryVertices->getNumElements())]); else if (mPlacer) mPlacer->place(P); diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp index 155d35ad2c..34e6a4308d 100644 --- a/components/nifosg/particle.hpp +++ b/components/nifosg/particle.hpp @@ -241,9 +241,8 @@ namespace NifOsg void setShooter(osgParticle::Shooter* shooter) { mShooter = shooter; } void setPlacer(osgParticle::Placer* placer) { mPlacer = placer; } void setCounter(osgParticle::Counter* counter) { mCounter = counter;} - - void setUseGeometryEmitter(bool useGeometryEmitter) { mUseGeometryEmitter = useGeometryEmitter; } void setGeometryEmitterTarget(std::optional recIndex) { mGeometryEmitterTarget = recIndex; } + void setFlags(int flags) { mFlags = flags; } private: // NIF Record indices @@ -253,7 +252,8 @@ namespace NifOsg osg::ref_ptr mShooter; osg::ref_ptr mCounter; - bool mUseGeometryEmitter; + int mFlags; + std::optional mGeometryEmitterTarget; osg::observer_ptr mCachedGeometryEmitter; };