diff --git a/components/nif/controller.cpp b/components/nif/controller.cpp index f909d94da5..1875488143 100644 --- a/components/nif/controller.cpp +++ b/components/nif/controller.cpp @@ -162,67 +162,71 @@ namespace Nif mInterpolator.post(nif); } + void NiParticleInfo::read(NIFStream* nif) + { + nif->read(mVelocity); + if (nif->getVersion() <= NIFStream::generateVersion(10, 4, 0, 1)) + nif->read(mRotationAxis); + nif->read(mAge); + nif->read(mLifespan); + nif->read(mLastUpdate); + nif->read(mSpawnGeneration); + nif->read(mCode); + } + void NiParticleSystemController::read(NIFStream* nif) { NiTimeController::read(nif); - velocity = nif->getFloat(); - velocityRandom = nif->getFloat(); - verticalDir = nif->getFloat(); - verticalAngle = nif->getFloat(); - horizontalDir = nif->getFloat(); - horizontalAngle = nif->getFloat(); - /*normal?*/ nif->getVector3(); - color = nif->getVector4(); - size = nif->getFloat(); - startTime = nif->getFloat(); - stopTime = nif->getFloat(); - nif->getChar(); - emitRate = nif->getFloat(); - lifetime = nif->getFloat(); - lifetimeRandom = nif->getFloat(); - - emitFlags = nif->getUShort(); - offsetRandom = nif->getVector3(); - - emitter.read(nif); - - /* Unknown Short, 0? - * Unknown Float, 1.0? - * Unknown Int, 1? - * Unknown Int, 0? - * Unknown Short, 0? - */ - nif->skip(16); - - numParticles = nif->getUShort(); - activeCount = nif->getUShort(); - - particles.resize(numParticles); - for (size_t i = 0; i < particles.size(); i++) + if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13)) + nif->read(mSpeed); + nif->read(mSpeedVariation); + nif->read(mDeclination); + nif->read(mDeclinationVariation); + nif->read(mPlanarAngle); + nif->read(mPlanarAngleVariation); + nif->read(mInitialNormal); + nif->read(mInitialColor); + nif->read(mInitialSize); + nif->read(mEmitStartTime); + nif->read(mEmitStopTime); + if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13)) { - particles[i].velocity = nif->getVector3(); - nif->getVector3(); /* unknown */ - particles[i].lifetime = nif->getFloat(); - particles[i].lifespan = nif->getFloat(); - particles[i].timestamp = nif->getFloat(); - nif->getUShort(); /* unknown */ - particles[i].vertex = nif->getUShort(); + mResetParticleSystem = nif->get() != 0; + nif->read(mBirthRate); } - - nif->getUInt(); /* -1? */ - affectors.read(nif); - colliders.read(nif); - nif->getChar(); + nif->read(mLifetime); + nif->read(mLifetimeVariation); + if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13)) + nif->read(mEmitFlags); + nif->read(mEmitterDimensions); + mEmitter.read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 13)) + { + nif->read(mNumSpawnGenerations); + nif->read(mPercentageSpawned); + nif->read(mSpawnMultiplier); + nif->read(mSpawnSpeedChaos); + nif->read(mSpawnDirChaos); + mParticles.resize(nif->get()); + nif->read(mNumValid); + for (NiParticleInfo& particle : mParticles) + particle.read(nif); + nif->skip(4); // NiEmitterModifier link + } + mModifier.read(nif); + mCollider.read(nif); + if (nif->getVersion() >= NIFStream::generateVersion(3, 3, 0, 15)) + nif->read(mStaticTargetBound); } void NiParticleSystemController::post(Reader& nif) { NiTimeController::post(nif); - emitter.post(nif); - affectors.post(nif); - colliders.post(nif); + mEmitter.post(nif); + mModifier.post(nif); + mCollider.post(nif); } void NiMaterialColorController::read(NIFStream* nif) diff --git a/components/nif/controller.hpp b/components/nif/controller.hpp index 5449e19f8d..a05ff247c5 100644 --- a/components/nif/controller.hpp +++ b/components/nif/controller.hpp @@ -94,6 +94,19 @@ namespace Nif { }; + struct NiParticleInfo + { + osg::Vec3f mVelocity; + osg::Vec3f mRotationAxis; + float mAge; + float mLifespan; + float mLastUpdate; + uint16_t mSpawnGeneration; + uint16_t mCode; + + void read(NIFStream* nif); + }; + struct NiParticleSystemController : public NiTimeController { enum BSPArrayController @@ -102,55 +115,47 @@ namespace Nif BSPArrayController_AtVertex = 0x10 }; - struct Particle - { - osg::Vec3f velocity; - float lifetime; - float lifespan; - float timestamp; - unsigned short vertex; - }; - - float velocity; - float velocityRandom; - - float verticalDir; // 0=up, pi/2=horizontal, pi=down - float verticalAngle; - float horizontalDir; - float horizontalAngle; - - osg::Vec4f color; - float size; - float startTime; - float stopTime; - - float emitRate; - float lifetime; - float lifetimeRandom; - enum EmitFlags { EmitFlag_NoAutoAdjust = 0x1 // If this flag is set, we use the emitRate value. Otherwise, // we calculate an emit rate so that the maximum number of particles // in the system (numParticles) is never exceeded. }; - int emitFlags; - osg::Vec3f offsetRandom; - - NiAVObjectPtr emitter; - - int numParticles; - int activeCount; - std::vector particles; - - NiParticleModifierPtr affectors; - NiParticleModifierPtr colliders; + float mSpeed; + float mSpeedVariation; + float mDeclination; + float mDeclinationVariation; + float mPlanarAngle; + float mPlanarAngleVariation; + osg::Vec3f mInitialNormal; + osg::Vec4f mInitialColor; + float mInitialSize; + float mEmitStartTime; + float mEmitStopTime; + bool mResetParticleSystem{ false }; + float mBirthRate; + float mLifetime; + float mLifetimeVariation; + uint16_t mEmitFlags{ 0 }; + osg::Vec3f mEmitterDimensions; + NiAVObjectPtr mEmitter; + uint16_t mNumSpawnGenerations; + float mPercentageSpawned; + uint16_t mSpawnMultiplier; + float mSpawnSpeedChaos; + float mSpawnDirChaos; + uint16_t mNumParticles; + uint16_t mNumValid; + std::vector mParticles; + NiParticleModifierPtr mModifier; + NiParticleModifierPtr mCollider; + uint8_t mStaticTargetBound; void read(NIFStream* nif) override; void post(Reader& nif) override; - bool noAutoAdjust() const { return emitFlags & EmitFlag_NoAutoAdjust; } + bool noAutoAdjust() const { return mEmitFlags & EmitFlag_NoAutoAdjust; } bool emitAtVertex() const { return mFlags & BSPArrayController_AtVertex; } }; using NiBSPArrayController = NiParticleSystemController; diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 45082d969a..c5511141a2 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -556,14 +556,8 @@ namespace NifOsg } ParticleSystemController::ParticleSystemController(const Nif::NiParticleSystemController* ctrl) - : mEmitStart(ctrl->startTime) - , mEmitStop(ctrl->stopTime) - { - } - - ParticleSystemController::ParticleSystemController() - : mEmitStart(0.f) - , mEmitStop(0.f) + : mEmitStart(ctrl->mEmitStartTime) + , mEmitStop(ctrl->mEmitStopTime) { } diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 2f9d58c6b1..7ac34d61ed 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -379,7 +379,7 @@ namespace NifOsg { public: ParticleSystemController(const Nif::NiParticleSystemController* ctrl); - ParticleSystemController(); + ParticleSystemController() = default; ParticleSystemController(const ParticleSystemController& copy, const osg::CopyOp& copyop); META_Object(NifOsg, ParticleSystemController) @@ -387,8 +387,8 @@ namespace NifOsg void operator()(osgParticle::ParticleProcessor* node, osg::NodeVisitor* nv); private: - float mEmitStart; - float mEmitStop; + float mEmitStart{ 0.f }; + float mEmitStop{ 0.f }; }; class PathController : public SceneUtil::NodeCallback, diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index f1492b9d0b..0e1d4091a9 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -1061,7 +1061,7 @@ namespace NifOsg } } - void handleParticlePrograms(Nif::NiParticleModifierPtr affectors, Nif::NiParticleModifierPtr colliders, + void handleParticlePrograms(Nif::NiParticleModifierPtr modifier, Nif::NiParticleModifierPtr collider, osg::Group* attachTo, osgParticle::ParticleSystem* partsys, osgParticle::ParticleProcessor::ReferenceFrame rf) { @@ -1069,50 +1069,50 @@ namespace NifOsg attachTo->addChild(program); program->setParticleSystem(partsys); program->setReferenceFrame(rf); - for (; !affectors.empty(); affectors = affectors->mNext) + for (; !modifier.empty(); modifier = modifier->mNext) { - if (affectors->recType == Nif::RC_NiParticleGrowFade) + if (modifier->recType == Nif::RC_NiParticleGrowFade) { - const Nif::NiParticleGrowFade* gf = static_cast(affectors.getPtr()); + const Nif::NiParticleGrowFade* gf = static_cast(modifier.getPtr()); program->addOperator(new GrowFadeAffector(gf->mGrowTime, gf->mFadeTime)); } - else if (affectors->recType == Nif::RC_NiGravity) + else if (modifier->recType == Nif::RC_NiGravity) { - const Nif::NiGravity* gr = static_cast(affectors.getPtr()); + const Nif::NiGravity* gr = static_cast(modifier.getPtr()); program->addOperator(new GravityAffector(gr)); } - else if (affectors->recType == Nif::RC_NiParticleColorModifier) + else if (modifier->recType == Nif::RC_NiParticleColorModifier) { const Nif::NiParticleColorModifier* cl - = static_cast(affectors.getPtr()); + = static_cast(modifier.getPtr()); if (cl->mData.empty()) continue; const Nif::NiColorData* clrdata = cl->mData.getPtr(); program->addOperator(new ParticleColorAffector(clrdata)); } - else if (affectors->recType == Nif::RC_NiParticleRotation) + else if (modifier->recType == Nif::RC_NiParticleRotation) { // unused } else - Log(Debug::Info) << "Unhandled particle modifier " << affectors->recName << " in " << mFilename; + Log(Debug::Info) << "Unhandled particle modifier " << modifier->recName << " in " << mFilename; } - for (; !colliders.empty(); colliders = colliders->mNext) + for (; !collider.empty(); collider = collider->mNext) { - if (colliders->recType == Nif::RC_NiPlanarCollider) + if (collider->recType == Nif::RC_NiPlanarCollider) { const Nif::NiPlanarCollider* planarcollider - = static_cast(colliders.getPtr()); + = static_cast(collider.getPtr()); program->addOperator(new PlanarCollider(planarcollider)); } - else if (colliders->recType == Nif::RC_NiSphericalCollider) + else if (collider->recType == Nif::RC_NiSphericalCollider) { const Nif::NiSphericalCollider* sphericalcollider - = static_cast(colliders.getPtr()); + = static_cast(collider.getPtr()); program->addOperator(new SphericalCollider(sphericalcollider)); } else - Log(Debug::Info) << "Unhandled particle collider " << colliders->recName << " in " << mFilename; + Log(Debug::Info) << "Unhandled particle collider " << collider->recName << " in " << mFilename; } } @@ -1124,7 +1124,7 @@ namespace NifOsg auto particleNode = static_cast(nifNode); if (particleNode->data.empty() || particleNode->data->recType != Nif::RC_NiParticlesData) { - partsys->setQuota(partctrl->numParticles); + partsys->setQuota(partctrl->mParticles.size()); return; } @@ -1134,35 +1134,35 @@ namespace NifOsg osg::BoundingBox box; int i = 0; - for (const auto& particle : partctrl->particles) + for (const auto& particle : partctrl->mParticles) { if (i++ >= particledata->mActiveCount) break; - if (particle.lifespan <= 0) + if (particle.mLifespan <= 0) continue; - if (particle.vertex >= particledata->mVertices.size()) + if (particle.mCode >= particledata->mVertices.size()) continue; - ParticleAgeSetter particletemplate(std::max(0.f, particle.lifetime)); + ParticleAgeSetter particletemplate(std::max(0.f, particle.mAge)); osgParticle::Particle* created = partsys->createParticle(&particletemplate); - created->setLifeTime(particle.lifespan); + created->setLifeTime(particle.mLifespan); // Note this position and velocity is not correct for a particle system with absolute reference frame, // which can not be done in this loader since we are not attached to the scene yet. Will be fixed up // post-load in the SceneManager. - created->setVelocity(particle.velocity); - const osg::Vec3f& position = particledata->mVertices[particle.vertex]; + created->setVelocity(particle.mVelocity); + const osg::Vec3f& position = particledata->mVertices[particle.mCode]; created->setPosition(position); - created->setColorRange(osgParticle::rangev4(partctrl->color, partctrl->color)); + created->setColorRange(osgParticle::rangev4(partctrl->mInitialColor, partctrl->mInitialColor)); created->setAlphaRange(osgParticle::rangef(1.f, 1.f)); - float size = partctrl->size; - if (particle.vertex < particledata->mSizes.size()) - size *= particledata->mSizes[particle.vertex]; + float size = partctrl->mInitialSize; + if (particle.mCode < particledata->mSizes.size()) + size *= particledata->mSizes[particle.mCode]; created->setSizeRange(osgParticle::rangef(size, size)); box.expandBy(osg::BoundingSphere(position, size)); @@ -1179,39 +1179,39 @@ namespace NifOsg std::vector targets; if (partctrl->recType == Nif::RC_NiBSPArrayController && !partctrl->emitAtVertex()) { - getAllNiNodes(partctrl->emitter.getPtr(), targets); + getAllNiNodes(partctrl->mEmitter.getPtr(), targets); } osg::ref_ptr emitter = new Emitter(targets); osgParticle::ConstantRateCounter* counter = new osgParticle::ConstantRateCounter; if (partctrl->noAutoAdjust()) - counter->setNumberOfParticlesPerSecondToCreate(partctrl->emitRate); - else if (partctrl->lifetime == 0 && partctrl->lifetimeRandom == 0) + counter->setNumberOfParticlesPerSecondToCreate(partctrl->mBirthRate); + else if (partctrl->mLifetime == 0 && partctrl->mLifetimeVariation == 0) counter->setNumberOfParticlesPerSecondToCreate(0); else counter->setNumberOfParticlesPerSecondToCreate( - partctrl->numParticles / (partctrl->lifetime + partctrl->lifetimeRandom / 2)); + partctrl->mParticles.size() / (partctrl->mLifetime + partctrl->mLifetimeVariation / 2)); emitter->setCounter(counter); - ParticleShooter* shooter = new ParticleShooter(partctrl->velocity - partctrl->velocityRandom * 0.5f, - partctrl->velocity + partctrl->velocityRandom * 0.5f, partctrl->horizontalDir, - partctrl->horizontalAngle, partctrl->verticalDir, partctrl->verticalAngle, partctrl->lifetime, - partctrl->lifetimeRandom); + ParticleShooter* shooter = new ParticleShooter(partctrl->mSpeed - partctrl->mSpeedVariation * 0.5f, + partctrl->mSpeed + partctrl->mSpeedVariation * 0.5f, partctrl->mPlanarAngle, + partctrl->mPlanarAngleVariation, partctrl->mDeclination, partctrl->mDeclinationVariation, + partctrl->mLifetime, partctrl->mLifetimeVariation); emitter->setShooter(shooter); emitter->setFlags(partctrl->mFlags); if (partctrl->recType == Nif::RC_NiBSPArrayController && partctrl->emitAtVertex()) { - emitter->setGeometryEmitterTarget(partctrl->emitter->recIndex); + emitter->setGeometryEmitterTarget(partctrl->mEmitter->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); + placer->setXRange(-partctrl->mEmitterDimensions.x() / 2.f, partctrl->mEmitterDimensions.x() / 2.f); + placer->setYRange(-partctrl->mEmitterDimensions.y() / 2.f, partctrl->mEmitterDimensions.y() / 2.f); + placer->setZRange(-partctrl->mEmitterDimensions.z() / 2.f, partctrl->mEmitterDimensions.z() / 2.f); emitter->setPlacer(placer); } @@ -1280,11 +1280,11 @@ namespace NifOsg handleParticleInitialState(nifNode, partsys, partctrl); - partsys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(partctrl->size, partctrl->size)); - partsys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4(partctrl->color, partctrl->color)); + partsys->getDefaultParticleTemplate().setSizeRange(osgParticle::rangef(partctrl->mInitialSize, partctrl->mInitialSize)); + partsys->getDefaultParticleTemplate().setColorRange(osgParticle::rangev4(partctrl->mInitialColor, partctrl->mInitialColor)); partsys->getDefaultParticleTemplate().setAlphaRange(osgParticle::rangef(1.f, 1.f)); - if (!partctrl->emitter.empty()) + if (!partctrl->mEmitter.empty()) { osg::ref_ptr emitter = handleParticleEmitter(partctrl); emitter->setParticleSystem(partsys); @@ -1293,7 +1293,7 @@ namespace NifOsg // The emitter node may not actually be handled yet, so let's delay attaching the emitter to a later // moment. If the emitter node is placed later than the particle node, it'll have a single frame delay // in particle processing. But that shouldn't be a game-breaking issue. - mEmitterQueue.emplace_back(partctrl->emitter->recIndex, emitter); + mEmitterQueue.emplace_back(partctrl->mEmitter->recIndex, emitter); osg::ref_ptr callback(new ParticleSystemController(partctrl)); setupController(partctrl, callback, animflags); @@ -1310,16 +1310,16 @@ namespace NifOsg partsys->update(0.0, nv); } - // affectors should be attached *after* the emitter in the scene graph for correct update order + // modifiers should be attached *after* the emitter in the scene graph for correct update order // attach to same node as the ParticleSystem, we need osgParticle Operators to get the correct // localToWorldMatrix for transforming to particle space - handleParticlePrograms(partctrl->affectors, partctrl->colliders, parentNode, partsys.get(), rf); + handleParticlePrograms(partctrl->mModifier, partctrl->mCollider, parentNode, partsys.get(), rf); std::vector drawableProps; collectDrawableProperties(nifNode, parent, drawableProps); applyDrawableProperties(parentNode, drawableProps, composite, true, animflags); - // particle system updater (after the emitters and affectors in the scene graph) + // particle system updater (after the emitters and modifiers in the scene graph) // I think for correct culling needs to be *before* the ParticleSystem, though osg examples do it the other // way osg::ref_ptr updater = new osgParticle::ParticleSystemUpdater;