1 #include "particle.hpp"
2 
3 #include <limits>
4 
5 #include <osg/Version>
6 #include <osg/MatrixTransform>
7 #include <osg/Geometry>
8 #include <osg/ValueObject>
9 
10 #include <components/debug/debuglog.hpp>
11 #include <components/misc/rng.hpp>
12 #include <components/nif/controlled.hpp>
13 #include <components/nif/data.hpp>
14 
15 namespace NifOsg
16 {
17 
ParticleSystem()18 ParticleSystem::ParticleSystem()
19     : osgParticle::ParticleSystem()
20     , mQuota(std::numeric_limits<int>::max())
21 {
22     mNormalArray = new osg::Vec3Array(1);
23     mNormalArray->setBinding(osg::Array::BIND_OVERALL);
24     (*mNormalArray.get())[0] = osg::Vec3(0.3, 0.3, 0.3);
25 }
26 
ParticleSystem(const ParticleSystem & copy,const osg::CopyOp & copyop)27 ParticleSystem::ParticleSystem(const ParticleSystem &copy, const osg::CopyOp &copyop)
28     : osgParticle::ParticleSystem(copy, copyop)
29     , mQuota(copy.mQuota)
30 {
31     mNormalArray = new osg::Vec3Array(1);
32     mNormalArray->setBinding(osg::Array::BIND_OVERALL);
33     (*mNormalArray.get())[0] = osg::Vec3(0.3, 0.3, 0.3);
34 
35     // For some reason the osgParticle constructor doesn't copy the particles
36     for (int i=0;i<copy.numParticles()-copy.numDeadParticles();++i)
37         ParticleSystem::createParticle(copy.getParticle(i));
38 }
39 
setQuota(int quota)40 void ParticleSystem::setQuota(int quota)
41 {
42     mQuota = quota;
43 }
44 
createParticle(const osgParticle::Particle * ptemplate)45 osgParticle::Particle* ParticleSystem::createParticle(const osgParticle::Particle *ptemplate)
46 {
47     if (numParticles()-numDeadParticles() < mQuota)
48         return osgParticle::ParticleSystem::createParticle(ptemplate);
49     return nullptr;
50 }
51 
drawImplementation(osg::RenderInfo & renderInfo) const52 void ParticleSystem::drawImplementation(osg::RenderInfo& renderInfo) const
53 {
54     osg::State & state = *renderInfo.getState();
55 #if OSG_MIN_VERSION_REQUIRED(3, 5, 6)
56     if(state.useVertexArrayObject(getUseVertexArrayObject()))
57     {
58         state.getCurrentVertexArrayState()->assignNormalArrayDispatcher();
59         state.getCurrentVertexArrayState()->setNormalArray(state, mNormalArray);
60     }
61     else
62     {
63         state.getAttributeDispatchers().activateNormalArray(mNormalArray);
64     }
65 #else
66      state.Normal(0.3, 0.3, 0.3);
67 #endif
68      osgParticle::ParticleSystem::drawImplementation(renderInfo);
69 }
70 
operator ()(osg::Node * node,osg::NodeVisitor * nv)71 void InverseWorldMatrix::operator()(osg::Node *node, osg::NodeVisitor *nv)
72 {
73     if (nv && nv->getVisitorType() == osg::NodeVisitor::UPDATE_VISITOR)
74     {
75         osg::NodePath path = nv->getNodePath();
76         path.pop_back();
77 
78         osg::MatrixTransform* trans = static_cast<osg::MatrixTransform*>(node);
79 
80         osg::Matrix mat = osg::computeLocalToWorld( path );
81         mat.orthoNormalize(mat); // don't undo the scale
82         mat.invert(mat);
83         trans->setMatrix(mat);
84     }
85     traverse(node,nv);
86 }
87 
ParticleShooter(float minSpeed,float maxSpeed,float horizontalDir,float horizontalAngle,float verticalDir,float verticalAngle,float lifetime,float lifetimeRandom)88 ParticleShooter::ParticleShooter(float minSpeed, float maxSpeed, float horizontalDir, float horizontalAngle, float verticalDir, float verticalAngle, float lifetime, float lifetimeRandom)
89     : mMinSpeed(minSpeed), mMaxSpeed(maxSpeed), mHorizontalDir(horizontalDir)
90     , mHorizontalAngle(horizontalAngle), mVerticalDir(verticalDir), mVerticalAngle(verticalAngle)
91     , mLifetime(lifetime), mLifetimeRandom(lifetimeRandom)
92 {
93 }
94 
ParticleShooter()95 ParticleShooter::ParticleShooter()
96     : mMinSpeed(0.f), mMaxSpeed(0.f), mHorizontalDir(0.f)
97     , mHorizontalAngle(0.f), mVerticalDir(0.f), mVerticalAngle(0.f)
98     , mLifetime(0.f), mLifetimeRandom(0.f)
99 {
100 }
101 
ParticleShooter(const ParticleShooter & copy,const osg::CopyOp & copyop)102 ParticleShooter::ParticleShooter(const ParticleShooter &copy, const osg::CopyOp &copyop)
103     : osgParticle::Shooter(copy, copyop)
104 {
105     mMinSpeed = copy.mMinSpeed;
106     mMaxSpeed = copy.mMaxSpeed;
107     mHorizontalDir = copy.mHorizontalDir;
108     mHorizontalAngle = copy.mHorizontalAngle;
109     mVerticalDir = copy.mVerticalDir;
110     mVerticalAngle = copy.mVerticalAngle;
111     mLifetime = copy.mLifetime;
112     mLifetimeRandom = copy.mLifetimeRandom;
113 }
114 
shoot(osgParticle::Particle * particle) const115 void ParticleShooter::shoot(osgParticle::Particle *particle) const
116 {
117     float hdir = mHorizontalDir + mHorizontalAngle * (2.f * Misc::Rng::rollClosedProbability() - 1.f);
118     float vdir = mVerticalDir + mVerticalAngle * (2.f * Misc::Rng::rollClosedProbability() - 1.f);
119 
120     osg::Vec3f dir = (osg::Quat(vdir, osg::Vec3f(0,1,0)) * osg::Quat(hdir, osg::Vec3f(0,0,1)))
121              * osg::Vec3f(0,0,1);
122 
123     float vel = mMinSpeed + (mMaxSpeed - mMinSpeed) * Misc::Rng::rollClosedProbability();
124     particle->setVelocity(dir * vel);
125 
126     // Not supposed to set this here, but there doesn't seem to be a better way of doing it
127     particle->setLifeTime(std::max(std::numeric_limits<float>::epsilon(), mLifetime + mLifetimeRandom * Misc::Rng::rollClosedProbability()));
128 }
129 
GrowFadeAffector(float growTime,float fadeTime)130 GrowFadeAffector::GrowFadeAffector(float growTime, float fadeTime)
131     : mGrowTime(growTime)
132     , mFadeTime(fadeTime)
133     , mCachedDefaultSize(0.f)
134 {
135 }
136 
GrowFadeAffector()137 GrowFadeAffector::GrowFadeAffector()
138     : mGrowTime(0.f)
139     , mFadeTime(0.f)
140     , mCachedDefaultSize(0.f)
141 {
142 
143 }
144 
GrowFadeAffector(const GrowFadeAffector & copy,const osg::CopyOp & copyop)145 GrowFadeAffector::GrowFadeAffector(const GrowFadeAffector& copy, const osg::CopyOp& copyop)
146     : osgParticle::Operator(copy, copyop)
147 {
148     mGrowTime = copy.mGrowTime;
149     mFadeTime = copy.mFadeTime;
150     mCachedDefaultSize = copy.mCachedDefaultSize;
151 }
152 
beginOperate(osgParticle::Program * program)153 void GrowFadeAffector::beginOperate(osgParticle::Program *program)
154 {
155     mCachedDefaultSize = program->getParticleSystem()->getDefaultParticleTemplate().getSizeRange().minimum;
156 }
157 
operate(osgParticle::Particle * particle,double)158 void GrowFadeAffector::operate(osgParticle::Particle* particle, double /* dt */)
159 {
160     float size = mCachedDefaultSize;
161     if (particle->getAge() < mGrowTime && mGrowTime != 0.f)
162         size *= particle->getAge() / mGrowTime;
163     if (particle->getLifeTime() - particle->getAge() < mFadeTime && mFadeTime != 0.f)
164         size *= (particle->getLifeTime() - particle->getAge()) / mFadeTime;
165     particle->setSizeRange(osgParticle::rangef(size, size));
166 }
167 
ParticleColorAffector(const Nif::NiColorData * clrdata)168 ParticleColorAffector::ParticleColorAffector(const Nif::NiColorData *clrdata)
169     : mData(clrdata->mKeyMap, osg::Vec4f(1,1,1,1))
170 {
171 }
172 
ParticleColorAffector()173 ParticleColorAffector::ParticleColorAffector()
174 {
175 
176 }
177 
ParticleColorAffector(const ParticleColorAffector & copy,const osg::CopyOp & copyop)178 ParticleColorAffector::ParticleColorAffector(const ParticleColorAffector &copy, const osg::CopyOp &copyop)
179     : osgParticle::Operator(copy, copyop)
180 {
181     mData = copy.mData;
182 }
183 
operate(osgParticle::Particle * particle,double)184 void ParticleColorAffector::operate(osgParticle::Particle* particle, double /* dt */)
185 {
186     assert(particle->getLifeTime() > 0);
187     float time = static_cast<float>(particle->getAge()/particle->getLifeTime());
188     osg::Vec4f color = mData.interpKey(time);
189     float alpha = color.a();
190     color.a() = 1.0f;
191 
192     particle->setColorRange(osgParticle::rangev4(color, color));
193     particle->setAlphaRange(osgParticle::rangef(alpha, alpha));
194 }
195 
GravityAffector(const Nif::NiGravity * gravity)196 GravityAffector::GravityAffector(const Nif::NiGravity *gravity)
197     : mForce(gravity->mForce)
198     , mType(static_cast<ForceType>(gravity->mType))
199     , mPosition(gravity->mPosition)
200     , mDirection(gravity->mDirection)
201     , mDecay(gravity->mDecay)
202 {
203 }
204 
GravityAffector()205 GravityAffector::GravityAffector()
206     : mForce(0), mType(Type_Wind), mDecay(0.f)
207 {
208 
209 }
210 
GravityAffector(const GravityAffector & copy,const osg::CopyOp & copyop)211 GravityAffector::GravityAffector(const GravityAffector &copy, const osg::CopyOp &copyop)
212     : osgParticle::Operator(copy, copyop)
213 {
214     mForce = copy.mForce;
215     mType = copy.mType;
216     mPosition = copy.mPosition;
217     mDirection = copy.mDirection;
218     mDecay = copy.mDecay;
219     mCachedWorldPosition = copy.mCachedWorldPosition;
220     mCachedWorldDirection = copy.mCachedWorldDirection;
221 }
222 
beginOperate(osgParticle::Program * program)223 void GravityAffector::beginOperate(osgParticle::Program* program)
224 {
225     bool absolute = (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF);
226 
227     if (mType == Type_Point || mDecay != 0.f) // we don't need the position for Wind gravity, except if decay is being applied
228         mCachedWorldPosition = absolute ? program->transformLocalToWorld(mPosition) : mPosition;
229 
230     mCachedWorldDirection = absolute ? program->rotateLocalToWorld(mDirection) : mDirection;
231     mCachedWorldDirection.normalize();
232 }
233 
operate(osgParticle::Particle * particle,double dt)234 void GravityAffector::operate(osgParticle::Particle *particle, double dt)
235 {
236     const float magic = 1.6f;
237     switch (mType)
238     {
239         case Type_Wind:
240         {
241             float decayFactor = 1.f;
242             if (mDecay != 0.f)
243             {
244                 osg::Plane gravityPlane(mCachedWorldDirection, mCachedWorldPosition);
245                 float distance = std::abs(gravityPlane.distance(particle->getPosition()));
246                 decayFactor = std::exp(-1.f * mDecay * distance);
247             }
248 
249             particle->addVelocity(mCachedWorldDirection * mForce * dt * decayFactor * magic);
250 
251             break;
252         }
253         case Type_Point:
254         {
255             osg::Vec3f diff = mCachedWorldPosition - particle->getPosition();
256 
257             float decayFactor = 1.f;
258             if (mDecay != 0.f)
259                 decayFactor = std::exp(-1.f * mDecay * diff.length());
260 
261             diff.normalize();
262 
263             particle->addVelocity(diff * mForce * dt * decayFactor * magic);
264             break;
265         }
266     }
267 }
268 
Emitter()269 Emitter::Emitter()
270     : osgParticle::Emitter()
271 {
272 }
273 
Emitter(const Emitter & copy,const osg::CopyOp & copyop)274 Emitter::Emitter(const Emitter &copy, const osg::CopyOp &copyop)
275     : osgParticle::Emitter(copy, copyop)
276     , mTargets(copy.mTargets)
277     , mPlacer(copy.mPlacer)
278     , mShooter(copy.mShooter)
279     // need a deep copy because the remainder is stored in the object
280     , mCounter(static_cast<osgParticle::Counter*>(copy.mCounter->clone(osg::CopyOp::DEEP_COPY_ALL)))
281 {
282 }
283 
Emitter(const std::vector<int> & targets)284 Emitter::Emitter(const std::vector<int> &targets)
285     : mTargets(targets)
286 {
287 }
288 
setShooter(osgParticle::Shooter * shooter)289 void Emitter::setShooter(osgParticle::Shooter *shooter)
290 {
291     mShooter = shooter;
292 }
293 
setPlacer(osgParticle::Placer * placer)294 void Emitter::setPlacer(osgParticle::Placer *placer)
295 {
296     mPlacer = placer;
297 }
298 
setCounter(osgParticle::Counter * counter)299 void Emitter::setCounter(osgParticle::Counter *counter)
300 {
301     mCounter = counter;
302 }
303 
emitParticles(double dt)304 void Emitter::emitParticles(double dt)
305 {
306     int n = mCounter->numParticlesToCreate(dt);
307     if (n == 0)
308         return;
309 
310     osg::Matrix worldToPs;
311 
312     // maybe this could be optimized by halting at the lowest common ancestor of the particle and emitter nodes
313     osg::NodePathList partsysNodePaths = getParticleSystem()->getParentalNodePaths();
314     if (!partsysNodePaths.empty())
315     {
316         osg::Matrix psToWorld = osg::computeLocalToWorld(partsysNodePaths[0]);
317         worldToPs = osg::Matrix::inverse(psToWorld);
318     }
319 
320     const osg::Matrix& ltw = getLocalToWorldMatrix();
321     osg::Matrix emitterToPs = ltw * worldToPs;
322 
323     if (!mTargets.empty())
324     {
325         int randomIndex = Misc::Rng::rollClosedProbability() * (mTargets.size() - 1);
326         int randomRecIndex = mTargets[randomIndex];
327 
328         // we could use a map here for faster lookup
329         FindGroupByRecIndex visitor(randomRecIndex);
330         getParent(0)->accept(visitor);
331 
332         if (!visitor.mFound)
333         {
334             Log(Debug::Info) << "Can't find emitter node" << randomRecIndex;
335             return;
336         }
337 
338         osg::NodePath path = visitor.mFoundPath;
339         path.erase(path.begin());
340         emitterToPs = osg::computeLocalToWorld(path) * emitterToPs;
341     }
342 
343     emitterToPs.orthoNormalize(emitterToPs);
344 
345     for (int i=0; i<n; ++i)
346     {
347         osgParticle::Particle* P = getParticleSystem()->createParticle(nullptr);
348         if (P)
349         {
350             mPlacer->place(P);
351 
352             mShooter->shoot(P);
353 
354             P->transformPositionVelocity(emitterToPs);
355         }
356     }
357 }
358 
FindGroupByRecIndex(unsigned int recIndex)359 FindGroupByRecIndex::FindGroupByRecIndex(unsigned int recIndex)
360     : osg::NodeVisitor(TRAVERSE_ALL_CHILDREN)
361     , mFound(nullptr)
362     , mRecIndex(recIndex)
363 {
364 }
365 
apply(osg::Node & node)366 void FindGroupByRecIndex::apply(osg::Node &node)
367 {
368     applyNode(node);
369 }
370 
apply(osg::MatrixTransform & node)371 void FindGroupByRecIndex::apply(osg::MatrixTransform &node)
372 {
373     applyNode(node);
374 }
375 
apply(osg::Geometry & node)376 void FindGroupByRecIndex::apply(osg::Geometry &node)
377 {
378     applyNode(node);
379 }
380 
applyNode(osg::Node & searchNode)381 void FindGroupByRecIndex::applyNode(osg::Node &searchNode)
382 {
383     unsigned int recIndex;
384     if (searchNode.getUserValue("recIndex", recIndex) && mRecIndex == recIndex)
385     {
386         osg::Group* group = searchNode.asGroup();
387         if (!group)
388             group = searchNode.getParent(0);
389 
390         mFound = group;
391         mFoundPath = getNodePath();
392         return;
393     }
394     traverse(searchNode);
395 }
396 
PlanarCollider(const Nif::NiPlanarCollider * collider)397 PlanarCollider::PlanarCollider(const Nif::NiPlanarCollider *collider)
398     : mBounceFactor(collider->mBounceFactor)
399     , mPlane(-collider->mPlaneNormal, collider->mPlaneDistance)
400 {
401 }
402 
PlanarCollider()403 PlanarCollider::PlanarCollider()
404     : mBounceFactor(0.f)
405 {
406 }
407 
PlanarCollider(const PlanarCollider & copy,const osg::CopyOp & copyop)408 PlanarCollider::PlanarCollider(const PlanarCollider &copy, const osg::CopyOp &copyop)
409     : osgParticle::Operator(copy, copyop)
410     , mBounceFactor(copy.mBounceFactor)
411     , mPlane(copy.mPlane)
412     , mPlaneInParticleSpace(copy.mPlaneInParticleSpace)
413 {
414 }
415 
beginOperate(osgParticle::Program * program)416 void PlanarCollider::beginOperate(osgParticle::Program *program)
417 {
418     mPlaneInParticleSpace = mPlane;
419     if (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF)
420         mPlaneInParticleSpace.transform(program->getLocalToWorldMatrix());
421 }
422 
operate(osgParticle::Particle * particle,double dt)423 void PlanarCollider::operate(osgParticle::Particle *particle, double dt)
424 {
425     float dotproduct = particle->getVelocity() * mPlaneInParticleSpace.getNormal();
426 
427     if (dotproduct > 0)
428     {
429         osg::BoundingSphere bs(particle->getPosition(), 0.f);
430         if (mPlaneInParticleSpace.intersect(bs) == 1)
431         {
432             osg::Vec3 reflectedVelocity = particle->getVelocity() - mPlaneInParticleSpace.getNormal() * (2 * dotproduct);
433             reflectedVelocity *= mBounceFactor;
434             particle->setVelocity(reflectedVelocity);
435         }
436     }
437 }
438 
SphericalCollider(const Nif::NiSphericalCollider * collider)439 SphericalCollider::SphericalCollider(const Nif::NiSphericalCollider* collider)
440     : mBounceFactor(collider->mBounceFactor),
441       mSphere(collider->mCenter, collider->mRadius)
442 {
443 }
444 
SphericalCollider()445 SphericalCollider::SphericalCollider()
446     : mBounceFactor(1.0f)
447 {
448 
449 }
450 
SphericalCollider(const SphericalCollider & copy,const osg::CopyOp & copyop)451 SphericalCollider::SphericalCollider(const SphericalCollider& copy, const osg::CopyOp& copyop)
452     : osgParticle::Operator(copy, copyop)
453     , mBounceFactor(copy.mBounceFactor)
454     , mSphere(copy.mSphere)
455     , mSphereInParticleSpace(copy.mSphereInParticleSpace)
456 {
457 
458 }
459 
beginOperate(osgParticle::Program * program)460 void SphericalCollider::beginOperate(osgParticle::Program* program)
461 {
462     mSphereInParticleSpace = mSphere;
463     if (program->getReferenceFrame() == osgParticle::ParticleProcessor::ABSOLUTE_RF)
464         mSphereInParticleSpace.center() = program->transformLocalToWorld(mSphereInParticleSpace.center());
465 }
466 
operate(osgParticle::Particle * particle,double dt)467 void SphericalCollider::operate(osgParticle::Particle* particle, double dt)
468 {
469     osg::Vec3f cent = (particle->getPosition() - mSphereInParticleSpace.center()); // vector from sphere center to particle
470 
471     bool insideSphere = cent.length2() <= mSphereInParticleSpace.radius2();
472 
473     if (insideSphere
474             || (cent * particle->getVelocity() < 0.0f)) // if outside, make sure the particle is flying towards the sphere
475     {
476         // Collision test (finding point of contact) is performed by solving a quadratic equation:
477         // ||vec(cent) + vec(vel)*k|| = R      /^2
478         // k^2 + 2*k*(vec(cent)*vec(vel))/||vec(vel)||^2 + (||vec(cent)||^2 - R^2)/||vec(vel)||^2 = 0
479 
480         float b = -(cent * particle->getVelocity()) / particle->getVelocity().length2();
481 
482         osg::Vec3f u = cent + particle->getVelocity() * b;
483 
484         if (insideSphere
485                 || (u.length2() < mSphereInParticleSpace.radius2()))
486         {
487             float d = (mSphereInParticleSpace.radius2() - u.length2()) / particle->getVelocity().length2();
488             float k = insideSphere ? (std::sqrt(d) + b) : (b - std::sqrt(d));
489 
490             if (k < dt)
491             {
492                 // collision detected; reflect off the tangent plane
493                 osg::Vec3f contact = particle->getPosition() + particle->getVelocity() * k;
494 
495                 osg::Vec3 normal = (contact - mSphereInParticleSpace.center());
496                 normal.normalize();
497 
498                 float dotproduct = particle->getVelocity() * normal;
499 
500                 osg::Vec3 reflectedVelocity = particle->getVelocity() - normal * (2 * dotproduct);
501                 reflectedVelocity *= mBounceFactor;
502                 particle->setVelocity(reflectedVelocity);
503             }
504         }
505     }
506 }
507 
508 }
509