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 ©, const osg::CopyOp ©op)
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 ©, const osg::CopyOp ©op)
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 ©, const osg::CopyOp ©op)
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 ©, const osg::CopyOp ©op)
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 ©, const osg::CopyOp ©op)
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 ©, const osg::CopyOp ©op)
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