diff --git a/CHANGELOG.md b/CHANGELOG.md index 3033eb83f7..d4ac1067da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -109,6 +109,7 @@ Feature #7546: Start the game on Fredas Feature #7568: Uninterruptable scripted music Feature #7618: Show the player character's health in the save details + Feature #7634: Support NiParticleBomb Task #5896: Do not use deprecated MyGUI properties Task #7113: Move from std::atoi to std::from_char Task #7117: Replace boost::scoped_array with std::vector diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 35a9eee787..81a223e095 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -308,6 +308,7 @@ namespace Nif // Modifiers, 4.0.0.2 { "NiGravity", &construct }, + { "NiParticleBomb", &construct }, { "NiParticleColorModifier", &construct }, { "NiParticleGrowFade", &construct }, { "NiParticleRotation", &construct }, diff --git a/components/nif/particle.cpp b/components/nif/particle.cpp index d74473f468..0581c5a1d1 100644 --- a/components/nif/particle.cpp +++ b/components/nif/particle.cpp @@ -51,6 +51,20 @@ namespace Nif nif->read(mDirection); } + void NiParticleBomb::read(NIFStream* nif) + { + NiParticleModifier::read(nif); + + nif->read(mRange); + nif->read(mDuration); + nif->read(mStrength); + nif->read(mStartTime); + mDecayType = static_cast(nif->get()); + mSymmetryType = static_cast(nif->get()); + nif->read(mPosition); + nif->read(mDirection); + } + void NiParticleCollider::read(NIFStream* nif) { NiParticleModifier::read(nif); @@ -294,10 +308,10 @@ namespace Nif mBombObject.read(nif); nif->read(mBombAxis); - nif->read(mDecay); - nif->read(mDeltaV); - nif->read(mDecayType); - nif->read(mSymmetryType); + nif->read(mRange); + nif->read(mStrength); + mDecayType = static_cast(nif->get()); + mSymmetryType = static_cast(nif->get()); } void NiPSysBombModifier::post(Reader& nif) diff --git a/components/nif/particle.hpp b/components/nif/particle.hpp index 45b6296891..1bdbd3a94a 100644 --- a/components/nif/particle.hpp +++ b/components/nif/particle.hpp @@ -40,6 +40,20 @@ namespace Nif Point = 1, // Fixed origin }; + enum class DecayType : uint32_t + { + None = 0, // f(Distance) = 1.0 + Linear = 1, // f(Distance) = (Range - Distance) / Range + Exponential = 2, // f(Distance) = exp(-Distance / Range) + }; + + enum class SymmetryType : uint32_t + { + Spherical = 0, + Cylindrical = 1, // Perpendicular to direction axis + Planar = 2, // Parallel to direction axis + }; + struct NiGravity : NiParticleModifier { float mDecay{ 0.f }; @@ -51,6 +65,20 @@ namespace Nif void read(NIFStream* nif) override; }; + struct NiParticleBomb : NiParticleModifier + { + float mRange; + float mDuration; + float mStrength; + float mStartTime; + DecayType mDecayType; + SymmetryType mSymmetryType; + osg::Vec3f mPosition; + osg::Vec3f mDirection; + + void read(NIFStream* nif); + }; + struct NiParticleCollider : NiParticleModifier { float mBounceFactor; @@ -210,10 +238,10 @@ namespace Nif { NiAVObjectPtr mBombObject; osg::Vec3f mBombAxis; - float mDecay; - float mDeltaV; - uint32_t mDecayType; - uint32_t mSymmetryType; + float mRange; + float mStrength; + DecayType mDecayType; + SymmetryType mSymmetryType; void read(NIFStream* nif) override; void post(Reader& nif) override; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 79958b361d..d2a30b1317 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -206,6 +206,7 @@ namespace Nif RC_NiMultiTargetTransformController, RC_NiNode, RC_NiPalette, + RC_NiParticleBomb, RC_NiParticleColorModifier, RC_NiParticleGrowFade, RC_NiParticleRotation, diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index 3b8c47e071..a5f1032c69 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1092,6 +1092,18 @@ namespace NifOsg const Nif::NiGravity* gr = static_cast(modifier.getPtr()); program->addOperator(new GravityAffector(gr)); } + else if (modifier->recType == Nif::RC_NiParticleBomb) + { + auto bomb = static_cast(modifier.getPtr()); + osg::ref_ptr bombProgram(new osgParticle::ModularProgram); + attachTo->addChild(bombProgram); + bombProgram->setParticleSystem(partsys); + bombProgram->setReferenceFrame(rf); + bombProgram->setStartTime(bomb->mStartTime); + bombProgram->setLifeTime(bomb->mDuration); + bombProgram->setEndless(false); + bombProgram->addOperator(new ParticleBomb(bomb)); + } else if (modifier->recType == Nif::RC_NiParticleColorModifier) { const Nif::NiParticleColorModifier* cl diff --git a/components/nifosg/particle.cpp b/components/nifosg/particle.cpp index 551cbfeae8..f15678c879 100644 --- a/components/nifosg/particle.cpp +++ b/components/nifosg/particle.cpp @@ -346,6 +346,84 @@ namespace NifOsg } } + ParticleBomb::ParticleBomb(const Nif::NiParticleBomb* bomb) + : mRange(bomb->mRange) + , mStrength(bomb->mStrength) + , mDecayType(bomb->mDecayType) + , mSymmetryType(bomb->mSymmetryType) + , mPosition(bomb->mPosition) + , mDirection(bomb->mDirection) + { + } + + ParticleBomb::ParticleBomb(const ParticleBomb& copy, const osg::CopyOp& copyop) + : osgParticle::Operator(copy, copyop) + { + mRange = copy.mRange; + mStrength = copy.mStrength; + mDecayType = copy.mDecayType; + mSymmetryType = copy.mSymmetryType; + mCachedWorldPosition = copy.mCachedWorldPosition; + mCachedWorldDirection = copy.mCachedWorldDirection; + } + + void ParticleBomb::beginOperate(osgParticle::Program* program) + { + bool absolute = (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF); + + mCachedWorldPosition = absolute ? program->transformLocalToWorld(mPosition) : mPosition; + + // We don't need the direction for Spherical bomb + if (mSymmetryType != Nif::SymmetryType::Spherical) + { + mCachedWorldDirection = absolute ? program->rotateLocalToWorld(mDirection) : mDirection; + mCachedWorldDirection.normalize(); + } + } + + void ParticleBomb::operate(osgParticle::Particle* particle, double dt) + { + float decay = 1.f; + osg::Vec3f explosionDir; + + osg::Vec3f particleDir = particle->getPosition() - mCachedWorldPosition; + float distance = particleDir.length(); + particleDir.normalize(); + + switch (mDecayType) + { + case Nif::DecayType::None: + break; + case Nif::DecayType::Linear: + decay = 1.f - distance / mRange; + break; + case Nif::DecayType::Exponential: + decay = std::exp(-distance / mRange); + break; + } + + if (decay <= 0.f) + return; + + switch (mSymmetryType) + { + case Nif::SymmetryType::Spherical: + explosionDir = particleDir; + break; + case Nif::SymmetryType::Cylindrical: + explosionDir = particleDir - mCachedWorldDirection * (mCachedWorldDirection * particleDir); + explosionDir.normalize(); + break; + case Nif::SymmetryType::Planar: + explosionDir = mCachedWorldDirection; + if (explosionDir * particleDir < 0) + explosionDir = -explosionDir; + break; + } + + particle->addVelocity(explosionDir * mStrength * decay * dt); + } + Emitter::Emitter() : osgParticle::Emitter() , mFlags(0) diff --git a/components/nifosg/particle.hpp b/components/nifosg/particle.hpp index 967531013a..272bc5baed 100644 --- a/components/nifosg/particle.hpp +++ b/components/nifosg/particle.hpp @@ -199,6 +199,31 @@ namespace NifOsg osg::Vec3f mCachedWorldDirection; }; + class ParticleBomb : public osgParticle::Operator + { + public: + ParticleBomb(const Nif::NiParticleBomb* bomb); + ParticleBomb() = default; + ParticleBomb(const ParticleBomb& copy, const osg::CopyOp& copyop = osg::CopyOp::SHALLOW_COPY); + + ParticleBomb& operator=(const ParticleBomb&) = delete; + + META_Object(NifOsg, ParticleBomb) + + void operate(osgParticle::Particle* particle, double dt) override; + void beginOperate(osgParticle::Program*) override; + + private: + float mRange{ 0.f }; + float mStrength{ 0.f }; + Nif::DecayType mDecayType{ Nif::DecayType::None }; + Nif::SymmetryType mSymmetryType{ Nif::SymmetryType::Spherical }; + osg::Vec3f mPosition; + osg::Vec3f mDirection; + osg::Vec3f mCachedWorldPosition; + osg::Vec3f mCachedWorldDirection; + }; + // NodeVisitor to find a Group node with the given record index, stored in the node's user data container. // Alternatively, returns the node's parent Group if that node is not a Group (i.e. a leaf node). class FindGroupByRecIndex : public osg::NodeVisitor diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 066f43d123..784dafafa5 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -167,7 +167,7 @@ namespace SceneUtil "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", "SceneUtil::TextKeyMapHolder", "Shader::AddedState", "Shader::RemovedAlphaFunc", "NifOsg::FlipController", "NifOsg::KeyframeController", "NifOsg::Emitter", "NifOsg::ParticleColorAffector", - "NifOsg::ParticleSystem", "NifOsg::GravityAffector", "NifOsg::GrowFadeAffector", + "NifOsg::ParticleSystem", "NifOsg::GravityAffector", "NifOsg::ParticleBomb", "NifOsg::GrowFadeAffector", "NifOsg::InverseWorldMatrix", "NifOsg::StaticBoundingBoxCallback", "NifOsg::GeomMorpherController", "NifOsg::UpdateMorphGeometry", "NifOsg::UVController", "NifOsg::VisController", "osgMyGUI::Drawable", "osg::DrawCallback", "osg::UniformBufferObject", "osgOQ::ClearQueriesCallback",