diff --git a/src/modules/graphics/ParticleSystem.cpp b/src/modules/graphics/ParticleSystem.cpp index 899bb1ec1..2e1070fa7 100644 --- a/src/modules/graphics/ParticleSystem.cpp +++ b/src/modules/graphics/ParticleSystem.cpp @@ -66,6 +66,8 @@ ParticleSystem::ParticleSystem(Graphics *gfx, Texture *texture, uint32 size) , emissionRate(0) , emitCounter(0) , areaSpreadDistribution(DISTRIBUTION_NONE) + , areaSpreadAngle(0) + , areaSpreadIsRelativeDirection(false) , lifetime(-1) , life(0) , particleLifeMin(0) @@ -122,6 +124,8 @@ ParticleSystem::ParticleSystem(const ParticleSystem &p) , prevPosition(p.prevPosition) , areaSpreadDistribution(p.areaSpreadDistribution) , areaSpread(p.areaSpread) + , areaSpreadAngle(p.areaSpreadAngle) + , areaSpreadIsRelativeDirection(p.areaSpreadIsRelativeDirection) , lifetime(p.lifetime) , life(p.lifetime) // Initialize with the maximum life time. , particleLifeMin(p.particleLifeMin) @@ -261,38 +265,85 @@ void ParticleSystem::initParticle(Particle *p, float t) p->position = pos; + min = direction - spread/2.0f; + max = direction + spread/2.0f; + float dir = (float) rng.random(min, max); + + // In this switch statement, variables 'rand_y', 'min', and 'max' + // are sometimes reused as data stores for performance reasons float rand_x, rand_y; switch (areaSpreadDistribution) { case DISTRIBUTION_UNIFORM: - p->position.x += (float) rng.random(-areaSpread.getX(), areaSpread.getX()); - p->position.y += (float) rng.random(-areaSpread.getY(), areaSpread.getY()); + rand_x = (float) rng.random(-areaSpread.getX(), areaSpread.getX()); + rand_y = (float) rng.random(-areaSpread.getY(), areaSpread.getY()); + p->position.x += cosf(areaSpreadAngle) * rand_x - sinf(areaSpreadAngle) * rand_y; + p->position.y += sinf(areaSpreadAngle) * rand_x + cosf(areaSpreadAngle) * rand_y; break; case DISTRIBUTION_NORMAL: - p->position.x += (float) rng.randomNormal(areaSpread.getX()); - p->position.y += (float) rng.randomNormal(areaSpread.getY()); + rand_x = (float) rng.randomNormal(areaSpread.getX()); + rand_y = (float) rng.randomNormal(areaSpread.getY()); + p->position.x += cosf(areaSpreadAngle) * rand_x - sinf(areaSpreadAngle) * rand_y; + p->position.y += sinf(areaSpreadAngle) * rand_x + cosf(areaSpreadAngle) * rand_y; break; case DISTRIBUTION_ELLIPSE: rand_x = (float) rng.random(-1, 1); rand_y = (float) rng.random(-1, 1); - p->position.x += areaSpread.getX() * (rand_x * sqrt(1 - 0.5f*pow(rand_y, 2))); - p->position.y += areaSpread.getY() * (rand_y * sqrt(1 - 0.5f*pow(rand_x, 2))); + min = areaSpread.getX() * (rand_x * sqrt(1 - 0.5f*pow(rand_y, 2))); + max = areaSpread.getY() * (rand_y * sqrt(1 - 0.5f*pow(rand_x, 2))); + p->position.x += cosf(areaSpreadAngle) * min - sinf(areaSpreadAngle) * max; + p->position.y += sinf(areaSpreadAngle) * min + cosf(areaSpreadAngle) * max; + break; + case DISTRIBUTION_BORDER_ELLIPSE: + rand_x = (float) rng.random(0, LOVE_M_PI * 2); + min = cosf(rand_x) * areaSpread.getX(); + max = sinf(rand_x) * areaSpread.getY(); + p->position.x += cosf(areaSpreadAngle) * min - sinf(areaSpreadAngle) * max; + p->position.y += sinf(areaSpreadAngle) * min + cosf(areaSpreadAngle) * max; + break; + case DISTRIBUTION_BORDER_RECTANGLE: + rand_x = (float) rng.random((areaSpread.getX() + areaSpread.getY()) * -2, (areaSpread.getX() + areaSpread.getY()) * 2); + rand_y = areaSpread.getY() * 2; + if (rand_x < -rand_y) + { + min = rand_x + rand_y + areaSpread.getX(); + p->position.x += cosf(areaSpreadAngle) * min - sinf(areaSpreadAngle) * -areaSpread.getY(); + p->position.y += sinf(areaSpreadAngle) * min + cosf(areaSpreadAngle) * -areaSpread.getY(); + } + else if (rand_x < 0) + { + max = rand_x + areaSpread.getY(); + p->position.x += cosf(areaSpreadAngle) * -areaSpread.getX() - sinf(areaSpreadAngle) * max; + p->position.y += sinf(areaSpreadAngle) * -areaSpread.getX() + cosf(areaSpreadAngle) * max; + } + else if (rand_x < rand_y) + { + max = rand_x - areaSpread.getY(); + p->position.x += cosf(areaSpreadAngle) * areaSpread.getX() - sinf(areaSpreadAngle) * max; + p->position.y += sinf(areaSpreadAngle) * areaSpread.getX() + cosf(areaSpreadAngle) * max; + } + else + { + min = rand_x - rand_y - areaSpread.getX(); + p->position.x += cosf(areaSpreadAngle) * min - sinf(areaSpreadAngle) * areaSpread.getY(); + p->position.y += sinf(areaSpreadAngle) * min + cosf(areaSpreadAngle) * areaSpread.getY(); + } break; case DISTRIBUTION_NONE: default: break; } + // Determine if the origin of each particle is the center of the area + if (areaSpreadIsRelativeDirection) + dir += atan2(p->position.y - pos.getY(), p->position.x - pos.getX()); + p->origin = pos; min = speedMin; max = speedMax; float speed = (float) rng.random(min, max); - min = direction - spread/2.0f; - max = direction + spread/2.0f; - float dir = (float) rng.random(min, max); - p->velocity = love::Vector(cosf(dir), sinf(dir)) * speed; p->linearAcceleration.x = (float) rng.random(linearAccelerationMin.x, linearAccelerationMax.x); @@ -523,6 +574,21 @@ void ParticleSystem::setAreaSpread(AreaSpreadDistribution distribution, float x, areaSpreadDistribution = distribution; } +void ParticleSystem::setAreaSpread(AreaSpreadDistribution distribution, float x, float y, float angle) +{ + areaSpread = love::Vector(x, y); + areaSpreadDistribution = distribution; + areaSpreadAngle = angle; +} + +void ParticleSystem::setAreaSpread(AreaSpreadDistribution distribution, float x, float y, float angle, bool isRelativeDirection) +{ + areaSpread = love::Vector(x, y); + areaSpreadDistribution = distribution; + areaSpreadAngle = angle; + areaSpreadIsRelativeDirection = isRelativeDirection; +} + ParticleSystem::AreaSpreadDistribution ParticleSystem::getAreaSpreadDistribution() const { return areaSpreadDistribution; @@ -533,6 +599,26 @@ const love::Vector &ParticleSystem::getAreaSpreadParameters() const return areaSpread; } +void ParticleSystem::setAreaSpreadAngle(float angle) +{ + this->areaSpreadAngle = angle; +} + +float ParticleSystem::getAreaSpreadAngle() const +{ + return areaSpreadAngle; +} + +void ParticleSystem::setAreaSpreadIsRelativeDirection(bool isRelativeDirection) +{ + this->areaSpreadIsRelativeDirection = isRelativeDirection; +} + +bool ParticleSystem::getAreaSpreadIsRelativeDirection() const +{ + return areaSpreadIsRelativeDirection; +} + void ParticleSystem::setDirection(float direction) { this->direction = direction; @@ -1038,6 +1124,8 @@ StringMap ParticleSystem::distributions(ParticleSystem::distributionsEntries, sizeof(ParticleSystem::distributionsEntries)); diff --git a/src/modules/graphics/ParticleSystem.h b/src/modules/graphics/ParticleSystem.h index 3b8a78f7e..67db4353a 100644 --- a/src/modules/graphics/ParticleSystem.h +++ b/src/modules/graphics/ParticleSystem.h @@ -52,7 +52,7 @@ public: static love::Type type; /** - * Type of distribution new particles are drawn from: None, uniform, normal, ellipse. + * Type of distribution new particles are drawn from: None, uniform, normal, ellipse, borderellipse, borderrectangle. */ enum AreaSpreadDistribution { @@ -60,6 +60,8 @@ public: DISTRIBUTION_UNIFORM, DISTRIBUTION_NORMAL, DISTRIBUTION_ELLIPSE, + DISTRIBUTION_BORDER_ELLIPSE, + DISTRIBUTION_BORDER_RECTANGLE, DISTRIBUTION_MAX_ENUM }; @@ -195,15 +197,50 @@ public: * 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. + * * 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. + * * Normal: Parameters denote the standard deviation in x and y direction. x and y are assumed to be uncorrelated. + * * borderellipse: Parameter causes particle distribution around outside of ellipse + * * borderrectangle: Parameter causes particle distribution around outside of rectangle * @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 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. + * * borderellipse: Parameter causes particle distribution around outside of ellipse + * * borderrectangle: Parameter causes particle distribution around outside of rectangle + * @param x First parameter. Interpretation depends on distribution type. + * @param y Second parameter. Interpretation depends on distribution type. + * @param distribution Distribution type + * @param angle Angle for the distribution to be rotated by + **/ + void setAreaSpread(AreaSpreadDistribution distribution, float x, float y, float angle); + + /** + * 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. + * * borderellipse: Parameter causes particle distribution around outside of ellipse + * * borderrectangle: Parameter causes particle distribution around outside of rectangle + * @param x First parameter. Interpretation depends on distribution type. + * @param y Second parameter. Interpretation depends on distribution type. + * @param distribution Distribution type + * @param angle Angle for the distribution to be rotated by + * @param isRelativeDirection whether to set direction of each particle to away from center + **/ + void setAreaSpread(AreaSpreadDistribution distribution, float x, float y, float angle, bool isRelativeDirection); + /** * Returns area spread distribution type. **/ @@ -214,6 +251,30 @@ public: **/ const love::Vector &getAreaSpreadParameters() const; + /** + * Returns the angle of the area distribution (in radians). + **/ + float getAreaSpreadAngle() const; + + /** + * Sets the angle of the area distribution + * @param angle The angle (in radians). + **/ + void setAreaSpreadAngle(float angle); + + /** + * Returns true if particles spawn relative to the center of the + * shape area or false if they will use the setDirection parameter + **/ + bool getAreaSpreadIsRelativeDirection() const; + + /** + * Sets if particles starting direction is away from the center of the + * area spread or the setDirection parameter + * @param isRelativeDirection boolean use relative direction from center + **/ + void setAreaSpreadIsRelativeDirection(bool isRelativeDirection); + /** * Sets the direction of the particle emitter. * @param direction The direction (in degrees). @@ -593,6 +654,8 @@ protected: // Emission area spread. AreaSpreadDistribution areaSpreadDistribution; love::Vector areaSpread; + float areaSpreadAngle; + bool areaSpreadIsRelativeDirection; // The lifetime of the particle emitter (-1 means infinite) and the life it has left. float lifetime; diff --git a/src/modules/graphics/wrap_ParticleSystem.cpp b/src/modules/graphics/wrap_ParticleSystem.cpp index a98e5ccaf..676133956 100644 --- a/src/modules/graphics/wrap_ParticleSystem.cpp +++ b/src/modules/graphics/wrap_ParticleSystem.cpp @@ -197,7 +197,8 @@ int w_ParticleSystem_setAreaSpread(lua_State *L) ParticleSystem *t = luax_checkparticlesystem(L, 1); ParticleSystem::AreaSpreadDistribution distribution = ParticleSystem::DISTRIBUTION_NONE; - float x = 0.f, y = 0.f; + float x = 0.f, y = 0.f, angle = 0.f; + bool isRelativeDirection = false; const char *str = lua_isnoneornil(L, 2) ? 0 : luaL_checkstring(L, 2); if (str && !ParticleSystem::getConstant(str, distribution)) @@ -209,9 +210,11 @@ int w_ParticleSystem_setAreaSpread(lua_State *L) y = (float) luaL_checknumber(L, 4); if (x < 0.0f || y < 0.0f) return luaL_error(L, "Invalid area spread parameters (must be >= 0)"); + angle = (float) luaL_optnumber(L, 5, 0.0); + isRelativeDirection = luax_optboolean(L, 6, false); } - t->setAreaSpread(distribution, x, y); + t->setAreaSpread(distribution, x, y, angle, isRelativeDirection); return 0; } @@ -230,6 +233,36 @@ int w_ParticleSystem_getAreaSpread(lua_State *L) return 3; } +int w_ParticleSystem_setAreaSpreadAngle(lua_State *L) +{ + ParticleSystem *t = luax_checkparticlesystem(L, 1); + float arg1 = (float)luaL_checknumber(L, 2); + t->setAreaSpreadAngle(arg1); + return 0; +} + +int w_ParticleSystem_getAreaSpreadAngle(lua_State *L) +{ + ParticleSystem *t = luax_checkparticlesystem(L, 1); + lua_pushnumber(L, t->getAreaSpreadAngle()); + return 1; +} + +int w_ParticleSystem_setAreaSpreadIsRelativeDirection(lua_State *L) +{ + ParticleSystem *t = luax_checkparticlesystem(L, 1); + bool arg1 = luax_toboolean(L, 2); + t->setAreaSpreadIsRelativeDirection(arg1); + return 0; +} + +int w_ParticleSystem_getAreaSpreadIsRelativeDirection(lua_State *L) +{ + ParticleSystem *t = luax_checkparticlesystem(L, 1); + lua_pushnumber(L, t->getAreaSpreadIsRelativeDirection()); + return 1; +} + int w_ParticleSystem_setDirection(lua_State *L) { ParticleSystem *t = luax_checkparticlesystem(L, 1); @@ -720,6 +753,10 @@ static const luaL_Reg w_ParticleSystem_functions[] = { "moveTo", w_ParticleSystem_moveTo }, { "setAreaSpread", w_ParticleSystem_setAreaSpread }, { "getAreaSpread", w_ParticleSystem_getAreaSpread }, + { "setAreaSpreadAngle", w_ParticleSystem_setAreaSpreadAngle }, + { "getAreaSpreadAngle", w_ParticleSystem_getAreaSpreadAngle }, + { "setAreaSpreadIsRelativeDirection", w_ParticleSystem_setAreaSpreadIsRelativeDirection }, + { "getAreaSpreadIsRelativeDirection", w_ParticleSystem_getAreaSpreadIsRelativeDirection }, { "setDirection", w_ParticleSystem_setDirection }, { "getDirection", w_ParticleSystem_getDirection }, { "setSpread", w_ParticleSystem_setSpread },