vrld avatar vrld committed 13e6ca3

Fix issue #437: Area-based particle spawn.

New particles are spawned around the particle emitter position according to a
configurabe distribution.

New functionality:

particlesystem:setAreaSpread(distributon, x, y)
distribution, x, y = particlesystem:getAreaSpread()

Where `distribution' is one of "none", "uniform", "normal".
`x' and `y' take different meaning based on the distribution:

* none: no area spread - particles spawned at emitter position.
* uniform: particle spawned at px,py in the rectangle around emitter
position ex,ey, i.e.:
ex - x < px < ex + x, ey - y < py < ey + y,
* normal: particle position drawn from a normal/gaussian distribution
with mean ex,ey and standard deviation x,y, i.e.:
px \in N(ex, x²), py \in N(ey, y²).

Comments (0)

Files changed (4)

src/modules/graphics/opengl/ParticleSystem.cpp

 {
 	float low = inner - (outer/2.0f)*var;
 	float high = inner + (outer/2.0f)*var;
-	float r = (rand() / (float(RAND_MAX)+1));
+	float r = random();
 	return low*(1-r)+high*r;
 }
 
+StringMap<ParticleSystem::AreaSpreadDistribution, ParticleSystem::DISTRIBUTION_MAX_ENUM>::Entry ParticleSystem::distributionsEntries[] = {
+	{ "none",     ParticleSystem::DISTRIBUTION_NONE },
+	{ "uniform",  ParticleSystem::DISTRIBUTION_UNIFORM },
+	{ "normal",   ParticleSystem::DISTRIBUTION_NORMAL },
+};
+
+StringMap<ParticleSystem::AreaSpreadDistribution, ParticleSystem::DISTRIBUTION_MAX_ENUM> ParticleSystem::distributions(ParticleSystem::distributionsEntries, sizeof(ParticleSystem::distributionsEntries));
+
 
 ParticleSystem::ParticleSystem(Image *sprite, unsigned int buffer)
 	: pStart(0)
 	, active(true)
 	, emissionRate(0)
 	, emitCounter(0)
+	, areaSpreadDistribution(DISTRIBUTION_NONE)
 	, lifetime(-1)
 	, life(0)
 	, particleLifeMin(0)
 	if (min == max)
 		pLast->life = min;
 	else
-		pLast->life = (rand() / (float(RAND_MAX)+1)) * (max - min) + min;
+		pLast->life = random(min, max);
 	pLast->lifetime = pLast->life;
 
 	pLast->position[0] = position.getX();
 	pLast->position[1] = position.getY();
 
+	switch (areaSpreadDistribution)
+	{
+		case DISTRIBUTION_UNIFORM:
+			pLast->position[0] += random(-areaSpread.getX(), areaSpread.getX());
+			pLast->position[1] += random(-areaSpread.getY(), areaSpread.getY());
+			break;
+		case DISTRIBUTION_NORMAL:
+			pLast->position[0] += random_normal(areaSpread.getX());
+			pLast->position[1] += random_normal(areaSpread.getY());
+			break;
+		case DISTRIBUTION_NONE:
+		default:
+			break;
+	}
+
 	min = direction - spread/2.0f;
 	max = direction + spread/2.0f;
-	pLast->direction = (rand() / (float(RAND_MAX)+1)) * (max - min) + min;
+	pLast->direction = random(min, max);
 
 	pLast->origin = position;
 
 	min = speedMin;
 	max = speedMax;
-	float speed = (rand() / (float(RAND_MAX)+1)) * (max - min) + min;
+	float speed = random(min, max);
 	pLast->speed = love::Vector(cos(pLast->direction), sin(pLast->direction));
 	pLast->speed *= speed;
 
 	min = gravityMin;
 	max = gravityMax;
-	pLast->gravity = (rand() / (float(RAND_MAX)+1)) * (max - min) + min;
+	pLast->gravity = random(min, max);
 
 	min = radialAccelerationMin;
 	max = radialAccelerationMax;
-	pLast->radialAcceleration = (rand() / (float(RAND_MAX)+1)) * (max - min) + min;
+	pLast->radialAcceleration = random(min, max);
 
 	min = tangentialAccelerationMin;
 	max = tangentialAccelerationMax;
-	pLast->tangentialAcceleration = (rand() / (float(RAND_MAX)+1)) * (max - min) + min;
+	pLast->tangentialAcceleration = random(min, max);
 
-	pLast->sizeOffset       = (rand() / (float(RAND_MAX)+1)) * sizeVariation; // time offset for size change
-	pLast->sizeIntervalSize = (1.0f - (rand() / (float(RAND_MAX)+1)) * sizeVariation) - pLast->sizeOffset;
+	pLast->sizeOffset       = random(sizeVariation); // time offset for size change
+	pLast->sizeIntervalSize = (1.0f - random(sizeVariation)) - pLast->sizeOffset;
 	pLast->size = sizes[(size_t)(pLast->sizeOffset - .5f) * (sizes.size() - 1)];
 
 	min = rotationMin;
 	max = rotationMax;
 	pLast->spinStart = calculate_variation(spinStart, spinEnd, spinVariation);
 	pLast->spinEnd = calculate_variation(spinEnd, spinStart, spinVariation);
-	pLast->rotation = (rand() / (float(RAND_MAX)+1)) * (max - min) + min;;
+	pLast->rotation = random(min, max);
 
 	pLast->color = colors[0];
 
 	position = love::Vector(x, y);
 }
 
+
+void ParticleSystem::setAreaSpread(AreaSpreadDistribution distribution, float x, float y)
+{
+	areaSpread = love::Vector(x, y);
+	areaSpreadDistribution = distribution;
+}
+
 void ParticleSystem::setDirection(float direction)
 {
 	this->direction = direction;
 	return position;
 }
 
+ParticleSystem::AreaSpreadDistribution ParticleSystem::getAreaSpreadDistribution() const
+{
+	return areaSpreadDistribution;
+}
+
+const love::Vector &ParticleSystem::getAreaSpreadParameters() const
+{
+	return areaSpread;
+}
+
 float ParticleSystem::getDirection() const
 {
 	return direction;
 	} // while
 }
 
+bool ParticleSystem::getConstant(const char *in, AreaSpreadDistribution &out)
+{
+	return distributions.find(in, out);
+}
+
+bool ParticleSystem::getConstant(AreaSpreadDistribution in, const char *&out)
+{
+	return distributions.find(in, out);
+}
+
 } // opengl
 } // graphics
 } // love

src/modules/graphics/opengl/ParticleSystem.h

 class ParticleSystem : public Drawable
 {
 public:
+	/**
+	 * Type of distribution new particles are drawn from: None, uniform, normal.
+	 */
+	enum AreaSpreadDistribution
+	{
+		DISTRIBUTION_NONE,
+		DISTRIBUTION_UNIFORM,
+		DISTRIBUTION_NORMAL,
+		DISTRIBUTION_MAX_ENUM,
+	};
 
 	/**
 	 * Creates a particle system with the specified buffersize and sprite.
 	void setPosition(float x, float y);
 
 	/**
+	 * Sets the emission area spread parameters and distribution type. The interpretation of
+	 * the parameters depends on the distribution type:
+	 *
+	 * * None:    Parameters are ignored. No area spread.
+	 * * Uniform: Parameters denote maximal (symmetric) displacement from emitter position.
+	 * * Normal:  Parameters denote the standard deviation in x and y direction. x and y are assumed to be uncorrelated.
+	 * @param x First parameter. Interpretation depends on distribution type.
+	 * @param y Second parameter. Interpretation depends on distribution type.
+	 * @param distribution Distribution type
+	 * */
+	void setAreaSpread(AreaSpreadDistribution distribution, float x, float y);
+
+	/**
 	 * Sets the direction and the spread of the particle emitter.
 	 * @param direction The direction (in degrees).
 	 **/
 	const love::Vector &getPosition() const;
 
 	/**
+	 * Returns area spread distribution type.
+	 */
+	AreaSpreadDistribution getAreaSpreadDistribution() const;
+
+	/**
+	 * Returns area spread parameters.
+	 */
+	const love::Vector &getAreaSpreadParameters() const;
+
+	/**
 	 * Returns the direction of the emitter (in degrees).
 	 **/
 	float getDirection() const;
 	 * @param dt Time since last update.
 	 **/
 	void update(float dt);
+
+	static bool getConstant(const char *in, AreaSpreadDistribution &out);
+	static bool getConstant(AreaSpreadDistribution in, const char *&out);
 protected:
 
 	// The max amount of particles.
 	// The relative position of the particle emitter.
 	love::Vector position;
 
+	// Emission area spread.
+	AreaSpreadDistribution areaSpreadDistribution;
+	love::Vector areaSpread;
+
 	// The lifetime of the particle emitter (-1 means infinite) and the life it has left.
 	float lifetime;
 	float life;
 
 	void add();
 	void remove(particle *p);
+
+	static StringMap<AreaSpreadDistribution, DISTRIBUTION_MAX_ENUM>::Entry distributionsEntries[];
+	static StringMap<AreaSpreadDistribution, DISTRIBUTION_MAX_ENUM> distributions;
 };
 
 } // opengl

src/modules/graphics/opengl/PixelEffect.cpp

 
 bool PixelEffect::isSupported()
 {
-	return GLEE_VERSION_2_0 && GLEE_ARB_shader_objects && GLEE_ARB_fragment_shader && getGLSLVersion() >= "1.2";
+	return (GLEE_VERSION_2_0 || (GLEE_ARB_shader_objects && GLEE_ARB_fragment_shader)) && getGLSLVersion() >= "1.2";
 }
 
 std::string PixelEffect::getWarnings() const

src/modules/graphics/opengl/wrap_ParticleSystem.cpp

 	return 0;
 }
 
+int w_ParticleSystem_setAreaSpread(lua_State *L)
+{
+	ParticleSystem *t = luax_checkparticlesystem(L, 1);
+
+	ParticleSystem::AreaSpreadDistribution distribution;
+	const char *str = luaL_checkstring(L, 2);
+	if (!ParticleSystem::getConstant(str, distribution))
+		return luaL_error(L, "Invalid distribution: '%s'", str);
+
+	float x = (float)luaL_checknumber(L, 3);
+	float y = (float)luaL_checknumber(L, 4);
+	if (x < 0.0f || y < 0.0f)
+		return luaL_error(L, "Invalid area spread parameters (must be >= 0)");
+
+	t->setAreaSpread(distribution, x, y);
+	return 0;
+}
+
 int w_ParticleSystem_setDirection(lua_State *L)
 {
 	ParticleSystem *t = luax_checkparticlesystem(L, 1);
 	return 2;
 }
 
+int w_ParticleSystem_getAreaSpread(lua_State *L)
+{
+	ParticleSystem *t = luax_checkparticlesystem(L, 1);
+	ParticleSystem::AreaSpreadDistribution distribution = t-> getAreaSpreadDistribution();
+	const char *str;
+	ParticleSystem::getConstant(distribution, str);
+	const love::Vector &p = t->getAreaSpreadParameters();
+
+	lua_pushstring(L, str);
+	lua_pushnumber(L, p.x);
+	lua_pushnumber(L, p.y);
+
+	return 3;
+}
+
 int w_ParticleSystem_getDirection(lua_State *L)
 {
 	ParticleSystem *t = luax_checkparticlesystem(L, 1);
 	{ "setLifetime", w_ParticleSystem_setLifetime },
 	{ "setParticleLife", w_ParticleSystem_setParticleLife },
 	{ "setPosition", w_ParticleSystem_setPosition },
+	{ "setAreaSpread", w_ParticleSystem_setAreaSpread },
 	{ "setDirection", w_ParticleSystem_setDirection },
 	{ "setSpread", w_ParticleSystem_setSpread },
 	{ "setRelativeDirection", w_ParticleSystem_setRelativeDirection },
 	{ "getX", w_ParticleSystem_getX },
 	{ "getY", w_ParticleSystem_getY },
 	{ "getPosition", w_ParticleSystem_getPosition },
+	{ "getAreaSpread", w_ParticleSystem_getAreaSpread },
 	{ "getDirection", w_ParticleSystem_getDirection },
 	{ "getSpread", w_ParticleSystem_getSpread },
 	{ "getOffsetX", w_ParticleSystem_getOffsetX },
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.