Browse Source

Particles

Panagiotis Christopoulos Charitos 13 years ago
parent
commit
cb3476f027

+ 22 - 13
include/anki/resource/ParticleEmitterResource.h

@@ -6,6 +6,8 @@
 
 namespace anki {
 
+class XmlElement;
+
 /// The particle emitter properties. Different class from
 /// ParticleEmitterResource so it can be inherited
 struct ParticleEmitterProperties
@@ -18,30 +20,35 @@ protected:
 	/// @{
 	struct
 	{
-		F32 life = 10.0; ///< Required and > 0.0. In seconds
+		/// Particle life
+		F32 life = 10.0;
 		F32 lifeDeviation = 0.0;
 
-		/// Not-required, any value, Default 0.0, If not set only the gravity
-		/// applies
+		/// Particle mass
+		F32 mass = 1.0;
+		F32 massDeviation = 0.0;
+
+		/// Particle size. It is the size of the collision shape
+		F32 size = 1.0;
+		F32 sizeAnimation = 1.0;
+
+		/// Alpha factor. If the material supports alpha then multiply with 
+		/// this
+		F32 alpha = 1.0;
+
+		/// Initial force. If not set only the gravity applies
 		Vec3 forceDirection = Vec3(0.0, 1.0, 0.0);
 		Vec3 forceDirectionDeviation = Vec3(0.0);
 		F32 forceMagnitude = 0.0; ///< Default 0.0
 		F32 forceMagnitudeDeviation = 0.0;
 
-		F32 mass = 1.0; ///< Required and > 0.0
-		F32 massDeviation = 0.0;
-
-		/// Not-required, any value. If not set then it uses the world's default
+		/// If not set then it uses the world's default
 		Vec3 gravity = Vec3(0.0);
 		Vec3 gravityDeviation = Vec3(0.0);
 
-		Vec3 startingPos = Vec3(0.0); ///< If not set the default is zero
+		/// This position is relevant to the particle emitter pos
+		Vec3 startingPos = Vec3(0.0);
 		Vec3 startingPosDeviation = Vec3(0.0);
-
-		F32 size = 1.0; ///< The size of the collision shape. Required and > 0.0
-		F32 sizeAnimation = 1.0;
-
-		F32 alpha = 1.0;
 	} particle;
 	/// @}
 
@@ -87,6 +94,8 @@ private:
 	ModelResourcePointer model;
 	Bool forceEnabled;
 	Bool wordGravityEnabled;
+
+	void loadInternal(const XmlElement& el);
 };
 
 } // end namespace anki

+ 48 - 26
include/anki/scene/ParticleEmitter.h

@@ -10,37 +10,21 @@
 
 namespace anki {
 
-/// Particle scene node
-class Particle: public SceneNode, public Movable, public RigidBody
+/// Particle without rigid body properties
+/// XXX Remove SceneNode
+class ParticleSimple: public SceneNode, public Movable
 {
 public:
-	Particle(
-		F32 timeOfDeath,
+	ParticleSimple(
 		// SceneNode
 		const char* name, Scene* scene, 
 		// Movable
-		U32 movableFlags, Movable* movParent,
-		// RigidBody
-		PhysWorld* masterContainer, const RigidBody::Initializer& init); 
+		U32 movableFlags, Movable* movParent);
 
-	~Particle();
+	virtual ~ParticleSimple();
 
-	/// @name SceneNode virtuals
+	/// @name Accessors
 	/// @{
-
-	/// Override SceneNode::getMovable()
-	Movable* getMovable()
-	{
-		return this;
-	}
-
-	/// Override SceneNode::getRigidBody()
-	RigidBody* getRigidBody()
-	{
-		return this;
-	}
-	/// @}
-
 	F32 getTimeOfBirth() const
 	{
 		return timeOfBirth;
@@ -49,7 +33,7 @@ public:
 	{
 		return timeOfBirth;
 	}
-	void setTimeOfBirth(F32 x)
+	void setTimeOfBirth(const F32 x)
 	{
 		timeOfBirth = x;
 	}
@@ -62,10 +46,21 @@ public:
 	{
 		return timeOfDeath;
 	}
-	void setTimeOfDeath(F32 x)
+	void setTimeOfDeath(const F32 x)
 	{
 		timeOfDeath = x;
 	}
+	/// @}
+
+	/// @name SceneNode virtuals
+	/// @{
+
+	/// Override SceneNode::getMovable()
+	Movable* getMovable()
+	{
+		return this;
+	}
+	/// @}
 
 	Bool isDead() const
 	{
@@ -74,7 +69,32 @@ public:
 
 private:
 	F32 timeOfBirth; ///< Keep the time of birth for nice effects
-	F32 timeOfDeath; ///< Time of death. If < 0.0 then dead. In seconds
+	F32 timeOfDeath = -1.0; ///< Time of death. If < 0.0 then dead. In seconds
+};
+
+/// Particle scene node
+class Particle: public ParticleSimple, public RigidBody
+{
+public:
+	Particle(
+		// SceneNode
+		const char* name, Scene* scene, 
+		// Movable
+		U32 movableFlags, Movable* movParent,
+		// RigidBody
+		PhysWorld* masterContainer, const RigidBody::Initializer& init); 
+
+	~Particle();
+
+	/// @name SceneNode virtuals
+	/// @{
+
+	/// Override SceneNode::getRigidBody()
+	RigidBody* getRigidBody()
+	{
+		return this;
+	}
+	/// @}
 };
 
 /// The particle emitter scene node. This scene node emitts
@@ -167,6 +187,8 @@ private:
 
 	static F32 getRandom(F32 initial, F32 deviation);
 	static Vec3 getRandom(const Vec3& initial, const Vec3& deviation);
+
+	void reanimateParticle(ParticleSimple& p, F32 crntTime);
 };
 
 } // end namespace anki

+ 98 - 23
src/resource/ParticleEmitterResource.cpp

@@ -1,14 +1,71 @@
 #include "anki/resource/ParticleEmitterResource.h"
 #include "anki/resource/Model.h"
 #include "anki/util/Exception.h"
+#include "anki/util/StringList.h"
+#include "anki/misc/Xml.h"
 #include <cstring>
 
 namespace anki {
 
+//==============================================================================
+// Misc                                                                        =
+//==============================================================================
+
+//==============================================================================
 static const char* errMsg = "Incorrect or missing value ";
 
 #define PE_EXCEPTION(x) ANKI_EXCEPTION("Particle emmiter: " + x)
 
+//==============================================================================
+static void xmlReadVec3(const XmlElement& el_, const char* str, Vec3& out)
+{
+	XmlElement el = el_.getChildElementOptional(str);
+
+	if(!el)
+	{
+		return;
+	}
+
+	StringList list;
+
+	list = StringList::splitString(el.getText(), ' ');
+	if(list.size() != 3)
+	{
+		throw ANKI_EXCEPTION("Expecting 3 floats for Vec3");
+	}
+
+	for(U i = 0; i < 3; i++)
+	{
+		out[i] = std::stof(list[i]);
+	}
+}
+
+//==============================================================================
+static void xmlReadFloat(const XmlElement& el_, const char* str, F32& out)
+{
+	XmlElement el = el_.getChildElementOptional(str);
+
+	if(!el)
+	{
+		return;
+	}
+
+	out = std::stof(el.getText());
+}
+
+//==============================================================================
+static void xmlReadU(const XmlElement& el_, const char* str, U32& out)
+{
+	XmlElement el = el_.getChildElementOptional(str);
+
+	if(!el)
+	{
+		return;
+	}
+
+	out = (U32)std::stoi(el.getText());
+}
+
 //==============================================================================
 // ParticleEmitterProperties                                                   =
 //==============================================================================
@@ -34,38 +91,56 @@ ParticleEmitterResource::~ParticleEmitterResource()
 {}
 
 //==============================================================================
-void ParticleEmitterResource::load(const char* /*filename*/)
+void ParticleEmitterResource::load(const char* filename)
 {
-	// dummy load
-	particle.life = 7.0;
-	particle.lifeDeviation = 2.0;
+	try
+	{
+		XmlDocument doc;
+		doc.loadFile(filename);
+		loadInternal(doc.getChildElement("particleEmitter"));
+	}
+	catch(std::exception& e)
+	{
+		throw ANKI_EXCEPTION("Failed to load file: " + filename) << e;
+	}
+}
 
-	particle.size = 1.0;
-	particle.sizeAnimation = 2.0;
+//==============================================================================
+void ParticleEmitterResource::loadInternal(const XmlElement& rootel)
+{
+	// XML load
+	//
+	xmlReadFloat(rootel, "life", particle.life);
+	xmlReadFloat(rootel, "lifeDeviation", particle.lifeDeviation);
+	
+	xmlReadFloat(rootel, "mass", particle.mass);
+	xmlReadFloat(rootel, "massDeviation", particle.massDeviation);
 
-	particle.forceDirection = Vec3(0.0, 1.0, 0.0);
-	particle.forceDirectionDeviation = Vec3(0.2);
-	particle.forceMagnitude = 100.0;
-	particle.forceMagnitudeDeviation = 0.0;
+	xmlReadFloat(rootel, "size", particle.size);
+	xmlReadFloat(rootel, "sizeAnimation", particle.sizeAnimation);
 
-	particle.mass = 1.0;
-	particle.massDeviation = 0.0;
+	xmlReadFloat(rootel, "alpha", particle.alpha);
 
-	particle.gravity = Vec3(0.0, 1.0, 0.0);
-	particle.gravityDeviation = Vec3(0.0, 0.0, 0.0);
+	xmlReadVec3(rootel, "forceDirection", particle.forceDirection);
+	xmlReadVec3(rootel, "forceDirectionDeviation", 
+		particle.forceDirectionDeviation);
+	xmlReadFloat(rootel, "forceMagnitude", particle.forceMagnitude);
+	xmlReadFloat(rootel, "forceMagnitudeDeviation", 
+		particle.forceMagnitudeDeviation);
 
-	particle.alpha = 0.25;
+	xmlReadVec3(rootel, "gravity", particle.gravity);
+	xmlReadVec3(rootel, "gravityDeviation", particle.gravityDeviation);
 
-	/*gravity = Vec3(0.0, 10.0, 0.0);
-	gravityDeviation = Vec3(0.0, 0.0, 0.0);*/
+	xmlReadVec3(rootel, "startingPosition", particle.startingPos);
+	xmlReadVec3(rootel, "startingPositionDeviation", 
+		particle.startingPosDeviation);
 
-	particle.startingPos = Vec3(0.0, -0.5, 0.0);
-	particle.startingPosDeviation = Vec3(0.3, 0.0, 0.3);
+	xmlReadU(rootel, "maxNumberOfParticles", maxNumOfParticles);
+	xmlReadFloat(rootel, "emissionPeriod", emissionPeriod);
+	xmlReadU(rootel, "particlesPerEmittion", particlesPerEmittion);
 
-	maxNumOfParticles = 16;
-	emissionPeriod = 0.5;
-	particlesPerEmittion = 1;
-	model.load("data/particles/fire/fire.mdl");
+	XmlElement el = rootel.getChildElement("model");
+	model.load(el.getText());
 
 	// sanity checks
 	//

+ 92 - 71
src/scene/ParticleEmitter.cpp

@@ -7,21 +7,37 @@
 
 namespace anki {
 
+//==============================================================================
+// ParticleSimple                                                              =
+//==============================================================================
+
+//==============================================================================
+ParticleSimple::ParticleSimple(
+	// SceneNode
+	const char* name, Scene* scene, 
+	// Movable
+	U32 movableFlags, Movable* movParent)
+	: SceneNode(name, scene), Movable(movableFlags, movParent, *this)
+{}
+
+//==============================================================================
+ParticleSimple::~ParticleSimple()
+{}
+
 //==============================================================================
 // Particle                                                                    =
 //==============================================================================
 
 //==============================================================================
 Particle::Particle(
-	F32 timeOfDeath_,
 	// Scene
 	const char* name, Scene* scene,
 	// Movable
 	U32 movableFlags, Movable* movParent, 
 	// RigidBody
 	PhysWorld* masterContainer, const Initializer& init)
-	: SceneNode(name, scene), Movable(movableFlags, movParent, *this),
-		 RigidBody(masterContainer, init, this), timeOfDeath(timeOfDeath_)
+	: ParticleSimple(name, scene, movableFlags, movParent),
+		RigidBody(masterContainer, init, this)
 {}
 
 //==============================================================================
@@ -130,7 +146,6 @@ void ParticleEmitter::init(const char* filename, Scene* scene)
 		binit.mass = getRandom(particle.mass, particle.massDeviation);
 
 		Particle* part = new Particle(
-			-1.0, 
 			(getName() + std::to_string(i)).c_str(), scene,
 			Movable::MF_NONE, nullptr,
 			&scene->getPhysics(), binit);
@@ -166,11 +181,13 @@ void ParticleEmitter::frameUpdate(F32 prevUpdateTime, F32 crntTime, I frame)
 
 		if(p->getTimeOfDeath() < crntTime)
 		{
+			// Just died
 			p->setActivationState(DISABLE_SIMULATION);
 			p->setTimeOfDeath(-1.0);
 		}
 		else
-		{	
+		{
+			// An alive
 			const Vec3& origin = p->Movable::getWorldTransform().getOrigin();
 
 			for(U i = 0; i < 3; i++)
@@ -184,7 +201,8 @@ void ParticleEmitter::frameUpdate(F32 prevUpdateTime, F32 crntTime, I frame)
 
 			Transform trf = p->Movable::getWorldTransform();
 			// XXX set a flag for scale
-			trf.setScale(particle.size + (lifePercent * particle.sizeAnimation));
+			trf.setScale(
+				particle.size + (lifePercent * particle.sizeAnimation));
 
 			instancingTransformations.push_back(trf);
 
@@ -215,11 +233,6 @@ void ParticleEmitter::frameUpdate(F32 prevUpdateTime, F32 crntTime, I frame)
 	//
 	// Emit new particles
 	//
-
-	// pre calculate
-	Bool forceFlag = particleEmitterResource->hasForce();
-	Bool worldGravFlag = particleEmitterResource->usingWorldGravity();
-
 	if(timeLeftForNextEmission <= 0.0)
 	{
 		U particlesCount = 0; // How many particles I am allowed to emmit
@@ -232,66 +245,7 @@ void ParticleEmitter::frameUpdate(F32 prevUpdateTime, F32 crntTime, I frame)
 				continue;
 			}
 
-			RigidBody& body = p;
-
-			// life
-			p.setTimeOfDeath(
-				getRandom(crntTime + particle.life, particle.lifeDeviation));
-			p.setTimeOfBirth(crntTime);
-
-			// activate it (Bullet stuff)
-			body.forceActivationState(ACTIVE_TAG);
-			body.activate();
-			body.clearForces();
-			body.setLinearVelocity(btVector3(0.0, 0.0, 0.0));
-			body.setAngularVelocity(btVector3(0.0, 0.0, 0.0));
-
-			//cout << p.body->internalGetDeltaAngularVelocity() << endl;
-
-			// force
-			if(forceFlag)
-			{
-				Vec3 forceDir =
-					getRandom(particle.forceDirection,
-					particle.forceDirectionDeviation);
-				forceDir.normalize();
-
-				if(!identityRotation)
-				{
-					// the forceDir depends on the particle emitter rotation
-					forceDir = getWorldTransform().getRotation() * forceDir;
-				}
-
-				F32 forceMag =
-					getRandom(particle.forceMagnitude,
-					particle.forceMagnitudeDeviation);
-
-				body.applyCentralForce(toBt(forceDir * forceMag));
-			}
-
-			// gravity
-			if(!worldGravFlag)
-			{
-				body.setGravity(toBt(
-					getRandom(particle.gravity, particle.gravityDeviation)));
-			}
-
-			// Starting pos. In local space
-			Vec3 pos =
-				getRandom(particle.startingPos, particle.startingPosDeviation);
-
-			if(identityRotation)
-			{
-				pos += getWorldTransform().getOrigin();
-			}
-			else
-			{
-				pos.transform(getWorldTransform());
-			}
-
-			btTransform trf(
-				toBt(Transform(pos, getWorldTransform().getRotation(), 1.0)));
-			body.setWorldTransform(trf);
+			reanimateParticle(p, crntTime);
 
 			// do the rest
 			++particlesCount;
@@ -309,4 +263,71 @@ void ParticleEmitter::frameUpdate(F32 prevUpdateTime, F32 crntTime, I frame)
 	}
 }
 
+//==============================================================================
+void ParticleEmitter::reanimateParticle(ParticleSimple& p, F32 crntTime)
+{
+	ANKI_ASSERT(p.isDead());
+
+	ANKI_ASSERT(p.getRigidBody() != nullptr);
+	RigidBody& body = *p.getRigidBody();
+
+	// pre calculate
+	Bool forceFlag = particleEmitterResource->hasForce();
+	Bool worldGravFlag = particleEmitterResource->usingWorldGravity();
+
+	// life
+	p.setTimeOfDeath(
+		getRandom(crntTime + particle.life, particle.lifeDeviation));
+	p.setTimeOfBirth(crntTime);
+
+	// activate it (Bullet stuff)
+	body.forceActivationState(ACTIVE_TAG);
+	body.activate();
+	body.clearForces();
+	body.setLinearVelocity(btVector3(0.0, 0.0, 0.0));
+	body.setAngularVelocity(btVector3(0.0, 0.0, 0.0));
+
+	// force
+	if(forceFlag)
+	{
+		Vec3 forceDir = getRandom(particle.forceDirection,
+			particle.forceDirectionDeviation);
+		forceDir.normalize();
+
+		if(!identityRotation)
+		{
+			// the forceDir depends on the particle emitter rotation
+			forceDir = getWorldTransform().getRotation() * forceDir;
+		}
+
+		F32 forceMag = getRandom(particle.forceMagnitude,
+			particle.forceMagnitudeDeviation);
+
+		body.applyCentralForce(toBt(forceDir * forceMag));
+	}
+
+	// gravity
+	if(!worldGravFlag)
+	{
+		body.setGravity(
+			toBt(getRandom(particle.gravity, particle.gravityDeviation)));
+	}
+
+	// Starting pos. In local space
+	Vec3 pos = getRandom(particle.startingPos, particle.startingPosDeviation);
+
+	if(identityRotation)
+	{
+		pos += getWorldTransform().getOrigin();
+	}
+	else
+	{
+		pos.transform(getWorldTransform());
+	}
+
+	btTransform trf(
+		toBt(Transform(pos, getWorldTransform().getRotation(), 1.0)));
+	body.setWorldTransform(trf);
+}
+
 } // end namespace anki