diff --git a/CHANGELOG.md b/CHANGELOG.md index a181a35098..16509efca3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -130,6 +130,7 @@ Feature #6380: Commas are treated as whitespace in vanilla Feature #6419: Topics shouldn't be greyed out if they can produce another topic reference Feature #6534: Shader-based object texture blending + Feature #6592: Missing support for NiTriShape particle emitters Task #6201: Remove the "Note: No relevant classes found. No output generated" warnings Task #6264: Remove the old classes in animation.cpp Task #6553: Simplify interpreter instruction registration diff --git a/components/nif/node.hpp b/components/nif/node.hpp index 31eb63144c..fb57e07739 100644 --- a/components/nif/node.hpp +++ b/components/nif/node.hpp @@ -211,6 +211,9 @@ struct NiNode : Node enum ControllerFlags { ControllerFlag_Active = 0x8 }; + enum BSPArrayController { + BSPArrayController_AtVertex = 0x10 + }; void read(NIFStream *nif) override { diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 36f474a223..d003940a93 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -51,8 +51,7 @@ namespace void getAllNiNodes(const Nif::Node* node, std::vector& outIndices) { - const Nif::NiNode* ninode = dynamic_cast(node); - if (ninode) + if (const Nif::NiNode* ninode = dynamic_cast(node)) { outIndices.push_back(ninode->recIndex); for (unsigned int i=0; ichildren.length(); ++i) @@ -976,7 +975,8 @@ namespace NifOsg osg::ref_ptr handleParticleEmitter(const Nif::NiParticleSystemController* partctrl) { std::vector targets; - if (partctrl->recType == Nif::RC_NiBSPArrayController) + const bool atVertex = (partctrl->flags & Nif::NiNode::BSPArrayController_AtVertex); + if (partctrl->recType == Nif::RC_NiBSPArrayController && !atVertex) { getAllNiNodes(partctrl->emitter.getPtr(), targets); } @@ -1000,12 +1000,20 @@ namespace NifOsg partctrl->lifetime, partctrl->lifetimeRandom); emitter->setShooter(shooter); - osgParticle::BoxPlacer* placer = new osgParticle::BoxPlacer; - placer->setXRange(-partctrl->offsetRandom.x() / 2.f, partctrl->offsetRandom.x() / 2.f); - placer->setYRange(-partctrl->offsetRandom.y() / 2.f, partctrl->offsetRandom.y() / 2.f); - placer->setZRange(-partctrl->offsetRandom.z() / 2.f, partctrl->offsetRandom.z() / 2.f); + if (atVertex && (partctrl->recType == Nif::RC_NiBSPArrayController)) + { + emitter->setUseGeometryEmitter(true); + emitter->setGeometryEmitterTarget(partctrl->emitter->recIndex); + } + else + { + osgParticle::BoxPlacer* placer = new osgParticle::BoxPlacer; + placer->setXRange(-partctrl->offsetRandom.x() / 2.f, partctrl->offsetRandom.x() / 2.f); + placer->setYRange(-partctrl->offsetRandom.y() / 2.f, partctrl->offsetRandom.y() / 2.f); + placer->setZRange(-partctrl->offsetRandom.z() / 2.f, partctrl->offsetRandom.z() / 2.f); + emitter->setPlacer(placer); + } - emitter->setPlacer(placer); return emitter; } diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 7821b9c2b8..493143a7a3 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -11,6 +11,52 @@ #include #include #include +#include +#include + +namespace +{ + class FindFirstGeometry : public osg::NodeVisitor + { + public: + FindFirstGeometry() + : osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN) + , mGeometry(nullptr) + { + } + + void apply(osg::Node& node) override + { + if (mGeometry) + return; + + traverse(node); + } + + void apply(osg::Drawable& drawable) override + { + if (auto morph = dynamic_cast(&drawable)) + { + mGeometry = morph->getSourceGeometry(); + return; + } + else if (auto rig = dynamic_cast(&drawable)) + { + mGeometry = rig->getSourceGeometry(); + return; + } + + traverse(drawable); + } + + void apply(osg::Geometry& geometry) override + { + mGeometry = &geometry; + } + + osg::Geometry* mGeometry; + }; +} namespace NifOsg { @@ -264,6 +310,8 @@ void GravityAffector::operate(osgParticle::Particle *particle, double dt) Emitter::Emitter() : osgParticle::Emitter() + , mUseGeometryEmitter(false) + , mGeometryEmitterTarget(std::nullopt) { } @@ -274,29 +322,19 @@ 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) + , mGeometryEmitterTarget(copy.mGeometryEmitterTarget) + , mCachedGeometryEmitter(copy.mCachedGeometryEmitter) { } Emitter::Emitter(const std::vector &targets) : mTargets(targets) + , mUseGeometryEmitter(false) + , mGeometryEmitterTarget(std::nullopt) { } -void Emitter::setShooter(osgParticle::Shooter *shooter) -{ - mShooter = shooter; -} - -void Emitter::setPlacer(osgParticle::Placer *placer) -{ - mPlacer = placer; -} - -void Emitter::setCounter(osgParticle::Counter *counter) -{ - mCounter = counter; -} - void Emitter::emitParticles(double dt) { int n = mCounter->numParticlesToCreate(dt); @@ -316,21 +354,53 @@ void Emitter::emitParticles(double dt) const osg::Matrix& ltw = getLocalToWorldMatrix(); osg::Matrix emitterToPs = ltw * worldToPs; - if (!mTargets.empty()) + osg::ref_ptr geometryVertices = nullptr; + + if (mUseGeometryEmitter || !mTargets.empty()) { - int randomIndex = Misc::Rng::rollClosedProbability() * (mTargets.size() - 1); - int randomRecIndex = mTargets[randomIndex]; + int recIndex; + + if (mUseGeometryEmitter) + { + if (!mGeometryEmitterTarget.has_value()) + return; + + recIndex = mGeometryEmitterTarget.value(); + } + else + { + int randomIndex = Misc::Rng::rollClosedProbability() * (mTargets.size() - 1); + recIndex = mTargets[randomIndex]; + } // we could use a map here for faster lookup - FindGroupByRecIndex visitor(randomRecIndex); + FindGroupByRecIndex visitor(recIndex); getParent(0)->accept(visitor); if (!visitor.mFound) { - Log(Debug::Info) << "Can't find emitter node" << randomRecIndex; + Log(Debug::Info) << "Can't find emitter node" << recIndex; return; } + if (mUseGeometryEmitter) + { + if (!mCachedGeometryEmitter.lock(geometryVertices)) + { + FindFirstGeometry geometryVisitor; + visitor.mFound->accept(geometryVisitor); + + if (geometryVisitor.mGeometry) + { + if (auto* vertices = dynamic_cast(geometryVisitor.mGeometry->getVertexArray())) + { + mCachedGeometryEmitter = osg::observer_ptr(vertices); + geometryVertices = vertices; + } + } + } + } + osg::NodePath path = visitor.mFoundPath; path.erase(path.begin()); emitterToPs = osg::computeLocalToWorld(path) * emitterToPs; @@ -338,12 +408,18 @@ void Emitter::emitParticles(double dt) emitterToPs.orthoNormalize(emitterToPs); + if (mUseGeometryEmitter && (!geometryVertices.valid() || geometryVertices->empty())) + return; + for (int i=0; icreateParticle(nullptr); if (P) { - mPlacer->place(P); + if (mUseGeometryEmitter) + P->setPosition((*geometryVertices)[Misc::Rng::rollDice(geometryVertices->getNumElements())]); + else if (mPlacer) + mPlacer->place(P); mShooter->shoot(P); diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp index 8b724545f4..9e68cdf347 100644 --- a/components/nifosg/particle.hpp +++ b/components/nifosg/particle.hpp @@ -1,6 +1,8 @@ #ifndef OPENMW_COMPONENTS_NIFOSG_PARTICLE_H #define OPENMW_COMPONENTS_NIFOSG_PARTICLE_H +#include + #include #include #include @@ -233,9 +235,12 @@ namespace NifOsg void emitParticles(double dt) override; - void setShooter(osgParticle::Shooter* shooter); - void setPlacer(osgParticle::Placer* placer); - void setCounter(osgParticle::Counter* counter); + 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; } private: // NIF Record indices @@ -244,6 +249,10 @@ namespace NifOsg osg::ref_ptr mPlacer; osg::ref_ptr mShooter; osg::ref_ptr mCounter; + + bool mUseGeometryEmitter; + std::optional mGeometryEmitterTarget; + osg::observer_ptr mCachedGeometryEmitter; }; }