use vertex emitters on array particle controllers when appropriate

C++20
glassmancody.info 3 years ago
parent 643c1d6aeb
commit 88f02913d5

@ -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

@ -211,6 +211,9 @@ struct NiNode : Node
enum ControllerFlags {
ControllerFlag_Active = 0x8
};
enum BSPArrayController {
BSPArrayController_AtVertex = 0x10
};
void read(NIFStream *nif) override
{

@ -51,8 +51,7 @@ namespace
void getAllNiNodes(const Nif::Node* node, std::vector<int>& outIndices)
{
const Nif::NiNode* ninode = dynamic_cast<const Nif::NiNode*>(node);
if (ninode)
if (const Nif::NiNode* ninode = dynamic_cast<const Nif::NiNode*>(node))
{
outIndices.push_back(ninode->recIndex);
for (unsigned int i=0; i<ninode->children.length(); ++i)
@ -976,7 +975,8 @@ namespace NifOsg
osg::ref_ptr<Emitter> handleParticleEmitter(const Nif::NiParticleSystemController* partctrl)
{
std::vector<int> 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;
}

@ -11,6 +11,52 @@
#include <components/misc/rng.hpp>
#include <components/nif/controlled.hpp>
#include <components/nif/data.hpp>
#include <components/sceneutil/morphgeometry.hpp>
#include <components/sceneutil/riggeometry.hpp>
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<SceneUtil::MorphGeometry*>(&drawable))
{
mGeometry = morph->getSourceGeometry();
return;
}
else if (auto rig = dynamic_cast<SceneUtil::RigGeometry*>(&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 &copy, const osg::CopyOp &copyop)
, mShooter(copy.mShooter)
// need a deep copy because the remainder is stored in the object
, mCounter(static_cast<osgParticle::Counter*>(copy.mCounter->clone(osg::CopyOp::DEEP_COPY_ALL)))
, mUseGeometryEmitter(copy.mUseGeometryEmitter)
, mGeometryEmitterTarget(copy.mGeometryEmitterTarget)
, mCachedGeometryEmitter(copy.mCachedGeometryEmitter)
{
}
Emitter::Emitter(const std::vector<int> &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<osg::Vec3Array> 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<osg::Vec3Array*>(geometryVisitor.mGeometry->getVertexArray()))
{
mCachedGeometryEmitter = osg::observer_ptr<osg::Vec3Array>(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; i<n; ++i)
{
osgParticle::Particle* P = getParticleSystem()->createParticle(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);

@ -1,6 +1,8 @@
#ifndef OPENMW_COMPONENTS_NIFOSG_PARTICLE_H
#define OPENMW_COMPONENTS_NIFOSG_PARTICLE_H
#include <optional>
#include <osgParticle/Particle>
#include <osgParticle/Shooter>
#include <osgParticle/Operator>
@ -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<int> recIndex) { mGeometryEmitterTarget = recIndex; }
private:
// NIF Record indices
@ -244,6 +249,10 @@ namespace NifOsg
osg::ref_ptr<osgParticle::Placer> mPlacer;
osg::ref_ptr<osgParticle::Shooter> mShooter;
osg::ref_ptr<osgParticle::Counter> mCounter;
bool mUseGeometryEmitter;
std::optional<int> mGeometryEmitterTarget;
osg::observer_ptr<osg::Vec3Array> mCachedGeometryEmitter;
};
}

Loading…
Cancel
Save