mirror of
https://github.com/OpenMW/openmw.git
synced 2025-01-30 09:45:36 +00:00
use vertex emitters on array particle controllers when appropriate
This commit is contained in:
parent
643c1d6aeb
commit
88f02913d5
5 changed files with 129 additions and 32 deletions
|
@ -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 ©, const osg::CopyOp ©op)
|
|||
, 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…
Reference in a new issue