diff --git a/CMakeLists.txt b/CMakeLists.txt index e4f89a16a..c0411fa0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -269,6 +269,8 @@ set(LOVE_SRC_MODULE_GRAPHICS_ROOT src/modules/graphics/Drawable.h src/modules/graphics/Graphics.cpp src/modules/graphics/Graphics.h + src/modules/graphics/ParticleSystem.cpp + src/modules/graphics/ParticleSystem.h src/modules/graphics/Quad.cpp src/modules/graphics/Quad.h src/modules/graphics/Texture.cpp diff --git a/platform/xcode/liblove.xcodeproj/project.pbxproj b/platform/xcode/liblove.xcodeproj/project.pbxproj index 94d9efca1..5d81b2df9 100644 --- a/platform/xcode/liblove.xcodeproj/project.pbxproj +++ b/platform/xcode/liblove.xcodeproj/project.pbxproj @@ -871,6 +871,8 @@ FAB2D5AA1AABDD8A008224A4 /* TrueTypeRasterizer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAB2D5A81AABDD8A008224A4 /* TrueTypeRasterizer.cpp */; }; FAB2D5AB1AABDD8A008224A4 /* TrueTypeRasterizer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAB2D5A81AABDD8A008224A4 /* TrueTypeRasterizer.cpp */; }; FAB2D5AC1AABDD8A008224A4 /* TrueTypeRasterizer.h in Headers */ = {isa = PBXBuildFile; fileRef = FAB2D5A91AABDD8A008224A4 /* TrueTypeRasterizer.h */; }; + FAE272521C05A15B00A67640 /* ParticleSystem.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FAE272501C05A15B00A67640 /* ParticleSystem.cpp */; }; + FAE272531C05A15B00A67640 /* ParticleSystem.h in Headers */ = {isa = PBXBuildFile; fileRef = FAE272511C05A15B00A67640 /* ParticleSystem.h */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -1519,6 +1521,8 @@ FAB2D5A91AABDD8A008224A4 /* TrueTypeRasterizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TrueTypeRasterizer.h; sourceTree = ""; }; FAC734C11B2E021A00AB460A /* wrap_SoundData.lua */ = {isa = PBXFileReference; lastKnownFileType = text; path = wrap_SoundData.lua; sourceTree = ""; }; FAC734C21B2E628700AB460A /* wrap_ImageData.lua */ = {isa = PBXFileReference; lastKnownFileType = text; path = wrap_ImageData.lua; sourceTree = ""; }; + FAE272501C05A15B00A67640 /* ParticleSystem.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ParticleSystem.cpp; sourceTree = ""; }; + FAE272511C05A15B00A67640 /* ParticleSystem.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ParticleSystem.h; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -2204,6 +2208,8 @@ FA0B7B8A1A95902C000E1D17 /* Graphics.cpp */, FA0B7B8B1A95902C000E1D17 /* Graphics.h */, FA0B7B8C1A95902C000E1D17 /* opengl */, + FAE272501C05A15B00A67640 /* ParticleSystem.cpp */, + FAE272511C05A15B00A67640 /* ParticleSystem.h */, FA0B7BBC1A95902C000E1D17 /* Quad.cpp */, FA0B7BBD1A95902C000E1D17 /* Quad.h */, FA0B7BBE1A95902C000E1D17 /* Texture.cpp */, @@ -2874,6 +2880,7 @@ FA0B7B2D1A958EA3000E1D17 /* core.h in Headers */, FAB17BF71ABFC4B100F9BA27 /* lz4hc.h in Headers */, FA0B7E831A95902C000E1D17 /* Shape.h in Headers */, + FAE272531C05A15B00A67640 /* ParticleSystem.h in Headers */, FA0B7EDD1A95902D000E1D17 /* Touch.h in Headers */, FA0B7EDE1A95902D000E1D17 /* Touch.h in Headers */, FA0B7A541A958EA3000E1D17 /* b2Math.h in Headers */, @@ -3601,6 +3608,7 @@ FA0B7D391A95902C000E1D17 /* Graphics.cpp in Sources */, FA0B79361A958E3B000E1D17 /* macosx.mm in Sources */, FA0B7E121A95902C000E1D17 /* GearJoint.cpp in Sources */, + FAE272521C05A15B00A67640 /* ParticleSystem.cpp in Sources */, FA0B7DC11A95902C000E1D17 /* wrap_Joystick.cpp in Sources */, FA0B7CD31A95902C000E1D17 /* Source.cpp in Sources */, FA0B7A411A958EA3000E1D17 /* b2CircleShape.cpp in Sources */, diff --git a/src/modules/graphics/ParticleSystem.cpp b/src/modules/graphics/ParticleSystem.cpp new file mode 100644 index 000000000..f14fba8b6 --- /dev/null +++ b/src/modules/graphics/ParticleSystem.cpp @@ -0,0 +1,986 @@ +/** + * Copyright (c) 2006-2015 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +//LOVE +#include "common/config.h" +#include "ParticleSystem.h" + +#include "common/math.h" +#include "modules/math/RandomGenerator.h" + +// STD +#include +#include +#include + +namespace love +{ +namespace graphics +{ + +namespace +{ + +love::math::RandomGenerator rng; + +float calculate_variation(float inner, float outer, float var) +{ + float low = inner - (outer/2.0f)*var; + float high = inner + (outer/2.0f)*var; + float r = (float) rng.random(); + return low*(1-r)+high*r; +} + +} // anonymous namespace + +ParticleSystem::ParticleSystem(Texture *texture, uint32 size) + : pMem(nullptr) + , pFree(nullptr) + , pHead(nullptr) + , pTail(nullptr) + , texture(texture) + , active(true) + , insertMode(INSERT_MODE_TOP) + , maxParticles(0) + , activeParticles(0) + , emissionRate(0) + , emitCounter(0) + , areaSpreadDistribution(DISTRIBUTION_NONE) + , lifetime(-1) + , life(0) + , particleLifeMin(0) + , particleLifeMax(0) + , direction(0) + , spread(0) + , speedMin(0) + , speedMax(0) + , linearAccelerationMin(0, 0) + , linearAccelerationMax(0, 0) + , radialAccelerationMin(0) + , radialAccelerationMax(0) + , tangentialAccelerationMin(0) + , tangentialAccelerationMax(0) + , linearDampingMin(0.0f) + , linearDampingMax(0.0f) + , sizeVariation(0) + , rotationMin(0) + , rotationMax(0) + , spinStart(0) + , spinEnd(0) + , spinVariation(0) + , offset(float(texture->getWidth())*0.5f, float(texture->getHeight())*0.5f) + , defaultOffset(true) + , relativeRotation(false) +{ + if (size == 0 || size > MAX_PARTICLES) + throw love::Exception("Invalid ParticleSystem size."); + + sizes.push_back(1.0f); + colors.push_back(Colorf(1.0f, 1.0f, 1.0f, 1.0f)); + setBufferSize(size); +} + +ParticleSystem::ParticleSystem(const ParticleSystem &p) + : pMem(nullptr) + , pFree(nullptr) + , pHead(nullptr) + , pTail(nullptr) + , texture(p.texture) + , active(p.active) + , insertMode(p.insertMode) + , maxParticles(p.maxParticles) + , activeParticles(0) + , emissionRate(p.emissionRate) + , emitCounter(0.0f) + , position(p.position) + , prevPosition(p.prevPosition) + , areaSpreadDistribution(p.areaSpreadDistribution) + , areaSpread(p.areaSpread) + , lifetime(p.lifetime) + , life(p.lifetime) // Initialize with the maximum life time. + , particleLifeMin(p.particleLifeMin) + , particleLifeMax(p.particleLifeMax) + , direction(p.direction) + , spread(p.spread) + , speedMin(p.speedMin) + , speedMax(p.speedMax) + , linearAccelerationMin(p.linearAccelerationMin) + , linearAccelerationMax(p.linearAccelerationMax) + , radialAccelerationMin(p.radialAccelerationMin) + , radialAccelerationMax(p.radialAccelerationMax) + , tangentialAccelerationMin(p.tangentialAccelerationMin) + , tangentialAccelerationMax(p.tangentialAccelerationMax) + , linearDampingMin(p.linearDampingMin) + , linearDampingMax(p.linearDampingMax) + , sizes(p.sizes) + , sizeVariation(p.sizeVariation) + , rotationMin(p.rotationMin) + , rotationMax(p.rotationMax) + , spinStart(p.spinStart) + , spinEnd(p.spinEnd) + , spinVariation(p.spinVariation) + , offset(p.offset) + , defaultOffset(p.defaultOffset) + , colors(p.colors) + , quads(p.quads) + , relativeRotation(p.relativeRotation) +{ + setBufferSize(maxParticles); +} + +ParticleSystem::~ParticleSystem() +{ + deleteBuffers(); +} + +void ParticleSystem::resetOffset() +{ + if (quads.empty()) + offset = love::Vector(float(texture->getWidth())*0.5f, float(texture->getHeight())*0.5f); + else + { + Quad::Viewport v = quads[0]->getViewport(); + offset = love::Vector(v.x*0.5f, v.y*0.5f); + } +} + +void ParticleSystem::createBuffers(size_t size) +{ + try + { + pFree = pMem = new Particle[size]; + maxParticles = (uint32) size; + } + catch (std::bad_alloc &) + { + deleteBuffers(); + throw love::Exception("Out of memory"); + } +} + +void ParticleSystem::deleteBuffers() +{ + // Clean up for great gracefulness! + delete[] pMem; + + pMem = nullptr; + maxParticles = 0; + activeParticles = 0; +} + +void ParticleSystem::setBufferSize(uint32 size) +{ + if (size == 0 || size > MAX_PARTICLES) + throw love::Exception("Invalid buffer size"); + deleteBuffers(); + createBuffers(size); + reset(); +} + +uint32 ParticleSystem::getBufferSize() const +{ + return maxParticles; +} + +void ParticleSystem::addParticle(float t) +{ + if (isFull()) + return; + + // Gets a free particle and updates the allocation pointer. + Particle *p = pFree++; + initParticle(p, t); + + switch (insertMode) + { + default: + case INSERT_MODE_TOP: + insertTop(p); + break; + case INSERT_MODE_BOTTOM: + insertBottom(p); + break; + case INSERT_MODE_RANDOM: + insertRandom(p); + break; + } + + activeParticles++; +} + +void ParticleSystem::initParticle(Particle *p, float t) +{ + float min,max; + + // Linearly interpolate between the previous and current emitter position. + love::Vector pos = prevPosition + (position - prevPosition) * t; + + min = particleLifeMin; + max = particleLifeMax; + if (min == max) + p->life = min; + else + p->life = (float) rng.random(min, max); + p->lifetime = p->life; + + p->position = pos; + + 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()); + break; + case DISTRIBUTION_NORMAL: + p->position.x += (float) rng.randomNormal(areaSpread.getX()); + p->position.y += (float) rng.randomNormal(areaSpread.getY()); + break; + case DISTRIBUTION_NONE: + default: + break; + } + + 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); + p->linearAcceleration.y = (float) rng.random(linearAccelerationMin.y, linearAccelerationMax.y); + + min = radialAccelerationMin; + max = radialAccelerationMax; + p->radialAcceleration = (float) rng.random(min, max); + + min = tangentialAccelerationMin; + max = tangentialAccelerationMax; + p->tangentialAcceleration = (float) rng.random(min, max); + + min = linearDampingMin; + max = linearDampingMax; + p->linearDamping = (float) rng.random(min, max); + + p->sizeOffset = (float) rng.random(sizeVariation); // time offset for size change + p->sizeIntervalSize = (1.0f - (float) rng.random(sizeVariation)) - p->sizeOffset; + p->size = sizes[(size_t)(p->sizeOffset - .5f) * (sizes.size() - 1)]; + + min = rotationMin; + max = rotationMax; + p->spinStart = calculate_variation(spinStart, spinEnd, spinVariation); + p->spinEnd = calculate_variation(spinEnd, spinStart, spinVariation); + p->rotation = (float) rng.random(min, max); + + p->angle = p->rotation; + if (relativeRotation) + p->angle += atan2f(p->velocity.y, p->velocity.x); + + p->color = colors[0]; + + p->quadIndex = 0; +} + +void ParticleSystem::insertTop(Particle *p) +{ + if (pHead == nullptr) + { + pHead = p; + p->prev = nullptr; + } + else + { + pTail->next = p; + p->prev = pTail; + } + p->next = nullptr; + pTail = p; +} + +void ParticleSystem::insertBottom(Particle *p) +{ + if (pTail == nullptr) + { + pTail = p; + p->next = nullptr; + } + else + { + pHead->prev = p; + p->next = pHead; + } + p->prev = nullptr; + pHead = p; +} + +void ParticleSystem::insertRandom(Particle *p) +{ + // Nonuniform, but 64-bit is so large nobody will notice. Hopefully. + uint64 pos = rng.rand() % ((int64) activeParticles + 1); + + // Special case where the particle gets inserted before the head. + if (pos == activeParticles) + { + Particle *pA = pHead; + if (pA) + pA->prev = p; + p->prev = nullptr; + p->next = pA; + pHead = p; + return; + } + + // Inserts the particle after the randomly selected particle. + Particle *pA = pMem + pos; + Particle *pB = pA->next; + pA->next = p; + if (pB) + pB->prev = p; + else + pTail = p; + p->prev = pA; + p->next = pB; +} + +ParticleSystem::Particle *ParticleSystem::removeParticle(Particle *p) +{ + // The linked list is updated in this function and old pointers may be + // invalidated. The returned pointer will inform the caller of the new + // pointer to the next particle. + Particle *pNext = nullptr; + + // Removes the particle from the linked list. + if (p->prev) + p->prev->next = p->next; + else + pHead = p->next; + + if (p->next) + { + p->next->prev = p->prev; + pNext = p->next; + } + else + pTail = p->prev; + + // The (in memory) last particle can now be moved into the free slot. + // It will skip the moving if it happens to be the removed particle. + pFree--; + if (p != pFree) + { + *p = *pFree; + if (pNext == pFree) + pNext = p; + + if (p->prev) + p->prev->next = p; + else + pHead = p; + + if (p->next) + p->next->prev = p; + else + pTail = p; + } + + activeParticles--; + return pNext; +} + +void ParticleSystem::setTexture(Texture *tex) +{ + texture.set(tex); + + if (defaultOffset) + resetOffset(); +} + +Texture *ParticleSystem::getTexture() const +{ + return texture.get(); +} + +void ParticleSystem::setInsertMode(InsertMode mode) +{ + insertMode = mode; +} + +ParticleSystem::InsertMode ParticleSystem::getInsertMode() const +{ + return insertMode; +} + +void ParticleSystem::setEmissionRate(float rate) +{ + if (rate < 0.0f) + throw love::Exception("Invalid emission rate"); + emissionRate = rate; +} + +float ParticleSystem::getEmissionRate() const +{ + return emissionRate; +} + +void ParticleSystem::setEmitterLifetime(float life) +{ + this->life = lifetime = life; +} + +float ParticleSystem::getEmitterLifetime() const +{ + return lifetime; +} + +void ParticleSystem::setParticleLifetime(float min, float max) +{ + particleLifeMin = min; + if (max == 0) + particleLifeMax = min; + else + particleLifeMax = max; +} + +void ParticleSystem::getParticleLifetime(float &min, float &max) const +{ + min = particleLifeMin; + max = particleLifeMax; +} + +void ParticleSystem::setPosition(float x, float y) +{ + position = love::Vector(x, y); + prevPosition = position; +} + +const love::Vector &ParticleSystem::getPosition() const +{ + return position; +} + +void ParticleSystem::moveTo(float x, float y) +{ + position = love::Vector(x, y); +} + +void ParticleSystem::setAreaSpread(AreaSpreadDistribution distribution, float x, float y) +{ + areaSpread = love::Vector(x, y); + areaSpreadDistribution = distribution; +} + +ParticleSystem::AreaSpreadDistribution ParticleSystem::getAreaSpreadDistribution() const +{ + return areaSpreadDistribution; +} + +const love::Vector &ParticleSystem::getAreaSpreadParameters() const +{ + return areaSpread; +} + +void ParticleSystem::setDirection(float direction) +{ + this->direction = direction; +} + +float ParticleSystem::getDirection() const +{ + return direction; +} + +void ParticleSystem::setSpread(float spread) +{ + this->spread = spread; +} + +float ParticleSystem::getSpread() const +{ + return spread; +} + +void ParticleSystem::setSpeed(float speed) +{ + speedMin = speedMax = speed; +} + +void ParticleSystem::setSpeed(float min, float max) +{ + speedMin = min; + speedMax = max; +} + +void ParticleSystem::getSpeed(float &min, float &max) const +{ + min = speedMin; + max = speedMax; +} + +void ParticleSystem::setLinearAcceleration(float x, float y) +{ + linearAccelerationMin.x = linearAccelerationMax.x = x; + linearAccelerationMin.y = linearAccelerationMax.y = y; +} + +void ParticleSystem::setLinearAcceleration(float xmin, float ymin, float xmax, float ymax) +{ + linearAccelerationMin = love::Vector(xmin, ymin); + linearAccelerationMax = love::Vector(xmax, ymax); +} + +void ParticleSystem::getLinearAcceleration(love::Vector &min, love::Vector &max) const +{ + min = linearAccelerationMin; + max = linearAccelerationMax; +} + +void ParticleSystem::setRadialAcceleration(float acceleration) +{ + radialAccelerationMin = radialAccelerationMax = acceleration; +} + +void ParticleSystem::setRadialAcceleration(float min, float max) +{ + radialAccelerationMin = min; + radialAccelerationMax = max; +} + +void ParticleSystem::getRadialAcceleration(float &min, float &max) const +{ + min = radialAccelerationMin; + max = radialAccelerationMax; +} + +void ParticleSystem::setTangentialAcceleration(float acceleration) +{ + tangentialAccelerationMin = tangentialAccelerationMax = acceleration; +} + +void ParticleSystem::setTangentialAcceleration(float min, float max) +{ + tangentialAccelerationMin = min; + tangentialAccelerationMax = max; +} + +void ParticleSystem::getTangentialAcceleration(float &min, float &max) const +{ + min = tangentialAccelerationMin; + max = tangentialAccelerationMax; +} + +void ParticleSystem::setLinearDamping(float min, float max) +{ + linearDampingMin = min; + linearDampingMax = max; +} + +void ParticleSystem::getLinearDamping(float &min, float &max) const +{ + min = linearDampingMin; + max = linearDampingMax; +} + +void ParticleSystem::setSize(float size) +{ + sizes.resize(1); + sizes[0] = size; +} + +void ParticleSystem::setSizes(const std::vector &newSizes) +{ + sizes = newSizes; +} + +const std::vector &ParticleSystem::getSizes() const +{ + return sizes; +} + +void ParticleSystem::setSizeVariation(float variation) +{ + sizeVariation = variation; +} + +float ParticleSystem::getSizeVariation() const +{ + return sizeVariation; +} + +void ParticleSystem::setRotation(float rotation) +{ + rotationMin = rotationMax = rotation; +} + +void ParticleSystem::setRotation(float min, float max) +{ + rotationMin = min; + rotationMax = max; +} + +void ParticleSystem::getRotation(float &min, float &max) const +{ + min = rotationMin; + max = rotationMax; +} + +void ParticleSystem::setSpin(float spin) +{ + spinStart = spin; + spinEnd = spin; +} + +void ParticleSystem::setSpin(float start, float end) +{ + spinStart = start; + spinEnd = end; +} + +void ParticleSystem::getSpin(float &start, float &end) const +{ + start = spinStart; + end = spinEnd; +} + +void ParticleSystem::setSpinVariation(float variation) +{ + spinVariation = variation; +} + +float ParticleSystem::getSpinVariation() const +{ + return spinVariation; +} + +void ParticleSystem::setOffset(float x, float y) +{ + offset = love::Vector(x, y); + defaultOffset = false; +} + +love::Vector ParticleSystem::getOffset() const +{ + return offset; +} + +void ParticleSystem::setColor(const std::vector &newColors) +{ + colors = newColors; + + for (Colorf &c : colors) + { + // We want to store the colors as [0, 1], rather than [0, 255]. + c.r /= 255.0f; + c.g /= 255.0f; + c.b /= 255.0f; + c.a /= 255.0f; + } +} + +std::vector ParticleSystem::getColor() const +{ + // The particle system stores colors in the range of [0, 1]... + std::vector ncolors(colors); + + for (Colorf &c : ncolors) + { + c.r *= 255.0f; + c.g *= 255.0f; + c.b *= 255.0f; + c.a *= 255.0f; + } + + return ncolors; +} + +void ParticleSystem::setQuads(const std::vector &newQuads) +{ + std::vector> quadlist; + quadlist.reserve(newQuads.size()); + + for (Quad *q : newQuads) + quadlist.push_back(q); + + quads = quadlist; + + if (defaultOffset) + resetOffset(); +} + +void ParticleSystem::setQuads() +{ + quads.clear(); +} + +std::vector ParticleSystem::getQuads() const +{ + std::vector quadlist; + quadlist.reserve(quads.size()); + + for (const StrongRef &q : quads) + quadlist.push_back(q.get()); + + return quadlist; +} + +void ParticleSystem::setRelativeRotation(bool enable) +{ + relativeRotation = enable; +} + +bool ParticleSystem::hasRelativeRotation() const +{ + return relativeRotation; +} + +uint32 ParticleSystem::getCount() const +{ + return activeParticles; +} + +void ParticleSystem::start() +{ + active = true; +} + +void ParticleSystem::stop() +{ + active = false; + life = lifetime; + emitCounter = 0; +} + +void ParticleSystem::pause() +{ + active = false; +} + +void ParticleSystem::reset() +{ + if (pMem == nullptr) + return; + + pFree = pMem; + pHead = nullptr; + pTail = nullptr; + activeParticles = 0; + life = lifetime; + emitCounter = 0; +} + +void ParticleSystem::emit(uint32 num) +{ + if (!active) + return; + + num = std::min(num, maxParticles - activeParticles); + + while (num--) + addParticle(1.0f); +} + +bool ParticleSystem::isActive() const +{ + return active; +} + +bool ParticleSystem::isPaused() const +{ + return !active && life < lifetime; +} + +bool ParticleSystem::isStopped() const +{ + return !active && life >= lifetime; +} + +bool ParticleSystem::isEmpty() const +{ + return activeParticles == 0; +} + +bool ParticleSystem::isFull() const +{ + return activeParticles == maxParticles; +} + +void ParticleSystem::update(float dt) +{ + if (pMem == nullptr || dt == 0.0f) + return; + + // Traverse all particles and update. + Particle *p = pHead; + + while (p) + { + // Decrease lifespan. + p->life -= dt; + + if (p->life <= 0) + p = removeParticle(p); + else + { + // Temp variables. + love::Vector radial, tangential; + love::Vector ppos = p->position; + + // Get vector from particle center to particle. + radial = ppos - p->origin; + radial.normalize(); + tangential = radial; + + // Resize radial acceleration. + radial *= p->radialAcceleration; + + // Calculate tangential acceleration. + { + float a = tangential.getX(); + tangential.setX(-tangential.getY()); + tangential.setY(a); + } + + // Resize tangential. + tangential *= p->tangentialAcceleration; + + // Update velocity. + p->velocity += (radial + tangential + p->linearAcceleration) * dt; + + // Apply damping. + p->velocity *= 1.0f / (1.0f + p->linearDamping * dt); + + // Modify position. + ppos += p->velocity * dt; + + p->position = ppos; + + const float t = 1.0f - p->life / p->lifetime; + + // Rotate. + p->rotation += (p->spinStart * (1.0f - t) + p->spinEnd * t) * dt; + + p->angle = p->rotation; + + if (relativeRotation) + p->angle += atan2f(p->velocity.y, p->velocity.x); + + // Change size according to given intervals: + // i = 0 1 2 3 n-1 + // |-------|-------|------|--- ... ---| + // t = 0 1/(n-1) 3/(n-1) 1 + // + // `s' is the interpolation variable scaled to the current + // interval width, e.g. if n = 5 and t = 0.3, then the current + // indices are 1,2 and s = 0.3 - 0.25 = 0.05 + float s = p->sizeOffset + t * p->sizeIntervalSize; // size variation + s *= (float)(sizes.size() - 1); // 0 <= s < sizes.size() + size_t i = (size_t)s; + size_t k = (i == sizes.size() - 1) ? i : i + 1; // boundary check (prevents failing on t = 1.0f) + s -= (float)i; // transpose s to be in interval [0:1]: i <= s < i + 1 ~> 0 <= s < 1 + p->size = sizes[i] * (1.0f - s) + sizes[k] * s; + + // Update color according to given intervals (as above) + s = t * (float)(colors.size() - 1); + i = (size_t)s; + k = (i == colors.size() - 1) ? i : i + 1; + s -= (float)i; // 0 <= s <= 1 + p->color = colors[i] * (1.0f - s) + colors[k] * s; + + // Update the quad index. + k = quads.size(); + if (k > 0) + { + s = t * (float) k; // [0:numquads-1] (clamped below) + i = (s > 0.0f) ? (size_t) s : 0; + p->quadIndex = (int) ((i < k) ? i : k - 1); + } + + // Next particle. + p = p->next; + } + } + + // Make some more particles. + if (active) + { + float rate = 1.0f / emissionRate; // the amount of time between each particle emit + emitCounter += dt; + float total = emitCounter - rate; + while (emitCounter > rate) + { + addParticle(1.0f - (emitCounter - rate) / total); + emitCounter -= rate; + } + /*int particles = (int)(emissionRate * dt); + for (int i = 0; i != particles; i++) + add();*/ + + life -= dt; + if (lifetime != -1 && life < 0) + stop(); + } + + prevPosition = position; +} + +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); +} + +bool ParticleSystem::getConstant(const char *in, InsertMode &out) +{ + return insertModes.find(in, out); +} + +bool ParticleSystem::getConstant(InsertMode in, const char *&out) +{ + return insertModes.find(in, out); +} + +StringMap::Entry ParticleSystem::distributionsEntries[] = +{ + { "none", DISTRIBUTION_NONE }, + { "uniform", DISTRIBUTION_UNIFORM }, + { "normal", DISTRIBUTION_NORMAL }, +}; + +StringMap ParticleSystem::distributions(ParticleSystem::distributionsEntries, sizeof(ParticleSystem::distributionsEntries)); + +StringMap::Entry ParticleSystem::insertModesEntries[] = +{ + { "top", INSERT_MODE_TOP }, + { "bottom", INSERT_MODE_BOTTOM }, + { "random", INSERT_MODE_RANDOM }, +}; + +StringMap ParticleSystem::insertModes(ParticleSystem::insertModesEntries, sizeof(ParticleSystem::insertModesEntries)); + +} // graphics +} // love diff --git a/src/modules/graphics/ParticleSystem.h b/src/modules/graphics/ParticleSystem.h new file mode 100644 index 000000000..9fe1ecd8b --- /dev/null +++ b/src/modules/graphics/ParticleSystem.h @@ -0,0 +1,672 @@ +/** + * Copyright (c) 2006-2015 LOVE Development Team + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any damages + * arising from the use of this software. + * + * Permission is granted to anyone to use this software for any purpose, + * including commercial applications, and to alter it and redistribute it + * freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you must not + * claim that you wrote the original software. If you use this software + * in a product, an acknowledgment in the product documentation would be + * appreciated but is not required. + * 2. Altered source versions must be plainly marked as such, and must not be + * misrepresented as being the original software. + * 3. This notice may not be removed or altered from any source distribution. + **/ + +#ifndef LOVE_GRAPHICS_PARTICLE_SYSTEM_H +#define LOVE_GRAPHICS_PARTICLE_SYSTEM_H + +// LOVE +#include "common/int.h" +#include "common/math.h" +#include "common/Vector.h" +#include "Drawable.h" +#include "Color.h" +#include "Quad.h" +#include "Texture.h" + +// STL +#include + +namespace love +{ +namespace graphics +{ + +/** + * A class for creating, moving and drawing particles. + * A big thanks to bobthebloke.org + **/ +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 + }; + + /** + * Insertion modes of new particles in the list: top, bottom, random. + */ + enum InsertMode + { + INSERT_MODE_TOP, + INSERT_MODE_BOTTOM, + INSERT_MODE_RANDOM, + INSERT_MODE_MAX_ENUM + }; + + /** + * Maximum numbers of particles in a ParticleSystem. + * This limit comes from the fact that a quad requires four vertices and the + * OpenGL API where GLsizei is a signed int. + **/ + static const uint32 MAX_PARTICLES = LOVE_INT32_MAX / 4; + + /** + * Creates a particle system with the specified buffer size and texture. + **/ + ParticleSystem(Texture *texture, uint32 buffer); + ParticleSystem(const ParticleSystem &p); + + /** + * Deletes any allocated memory. + **/ + virtual ~ParticleSystem(); + + /** + * Creates an identical copy of this ParticleSystem. The clone does not + * duplicate any existing particles from this ParticleSystem, just the + * settable parameters. + **/ + virtual ParticleSystem *clone() = 0; + + /** + * Sets the texture used in the particle system. + * @param texture The new texture. + **/ + void setTexture(Texture *texture); + + /** + * Returns the texture used when drawing the particle system. + **/ + Texture *getTexture() const; + + /** + * Clears the current buffer and allocates the appropriate amount of space for the buffer. + * @param size The new buffer size. + **/ + virtual void setBufferSize(uint32 size); + + /** + * Returns the total amount of particles this ParticleSystem can have active + * at any given point in time. + **/ + uint32 getBufferSize() const; + + /** + * Sets the insert mode for new particles. + * @param mode The new insert mode. + */ + void setInsertMode(InsertMode mode); + + /** + * Returns the current insert mode. + */ + InsertMode getInsertMode() const; + + /** + * Sets the emission rate. + * @param rate The amount of particles per second. + **/ + void setEmissionRate(float rate); + + /** + * Returns the number of particles created per second. + **/ + float getEmissionRate() const; + + /** + * Sets the lifetime of the particle emitter (-1 means eternal) + * @param life The lifetime (in seconds). + **/ + void setEmitterLifetime(float life); + + /** + * Returns the lifetime of the particle emitter. + **/ + float getEmitterLifetime() const; + + /** + * Sets the life range of the particles. + * @param min The minimum life. + * @param max The maximum life (if 0, then becomes the same as minimum life). + **/ + void setParticleLifetime(float min, float max = 0); + + /** + * Gets the lifetime of a particle. + * @param[out] min The minimum life. + * @param[out] max The maximum life. + **/ + void getParticleLifetime(float &min, float &max) const; + + /** + * Sets the position of the center of the emitter. + * Used to move the emitter without changing the position of already existing particles. + * @param x The x-coordinate. + * @param y The y-coordinate. + **/ + void setPosition(float x, float y); + + /** + * Returns the position of the emitter. + **/ + const love::Vector &getPosition() const; + + /** + * Moves the position of the center of the emitter. + * When update is called, newly spawned particles will appear in a line + * between the old emitter position and where the emitter was moved to, + * resulting in a smoother-feeling particle system if moveTo is called + * repeatedly. + **/ + void moveTo(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); + + /** + * Returns area spread distribution type. + **/ + AreaSpreadDistribution getAreaSpreadDistribution() const; + + /** + * Returns area spread parameters. + **/ + const love::Vector &getAreaSpreadParameters() const; + + /** + * Sets the direction of the particle emitter. + * @param direction The direction (in degrees). + **/ + void setDirection(float direction); + + /** + * Returns the direction of the particle emitter (in radians). + **/ + float getDirection() const; + + /** + * Sets the spread of the particle emitter. + * @param spread The spread (in radians). + **/ + void setSpread(float spread); + + /** + * Returns the directional spread of the emitter (in radians). + **/ + float getSpread() const; + + /** + * Sets the speed of the particles. + * @param speed The speed. + **/ + void setSpeed(float speed); + + /** + * Sets the speed of the particles. + * @param min The minimum speed. + * @param max The maximum speed. + **/ + void setSpeed(float min, float max); + + /** + * Gets the speed of the particles. + * @param[out] min The minimum speed. + * @param[out] max The maximum speed. + **/ + void getSpeed(float &min, float &max) const; + + /** + * Sets the linear acceleration (the acceleration along the x and y axes). + * @param x The acceleration along the x-axis. + * @param y The acceleration along the y-axis. + **/ + void setLinearAcceleration(float x, float y); + + /** + * Sets the linear acceleration (the acceleration along the x and y axes). + * @param xmin The minimum amount of acceleration along the x-axis. + * @param ymin The minimum amount of acceleration along the y-axis. + * @param xmax The maximum amount of acceleration along the x-axis. + * @param ymax The maximum amount of acceleration along the y-axis. + **/ + void setLinearAcceleration(float xmin, float ymin, float xmax, float ymax); + + /** + * Gets the linear acceleration of the particles. + * @param[out] min The minimum acceleration. + * @param[out] max The maximum acceleration. + **/ + void getLinearAcceleration(love::Vector &min, love::Vector &max) const; + + /** + * Sets the radial acceleration (the acceleration towards the particle emitter). + * @param acceleration The amount of acceleration. + **/ + void setRadialAcceleration(float acceleration); + + /** + * Sets the radial acceleration (the acceleration towards the particle emitter). + * @param min The minimum acceleration. + * @param max The maximum acceleration. + **/ + void setRadialAcceleration(float min, float max); + + /** + * Gets the radial acceleration. + * @param[out] min The minimum amount of radial acceleration. + * @param[out] max The maximum amount of radial acceleration. + **/ + void getRadialAcceleration(float &min, float &max) const; + + /** + * Sets the tangential acceleration (the acceleration perpendicular to the particle's direction). + * @param acceleration The amount of acceleration. + **/ + void setTangentialAcceleration(float acceleration); + + /** + * Sets the tangential acceleration (the acceleration perpendicular to the particle's direction). + * @param min The minimum acceleration. + * @param max The maximum acceleration. + **/ + void setTangentialAcceleration(float min, float max); + + /** + * Gets the tangential acceleration. + * @param[out] min The minimum tangential acceleration. + * @param[out] max The maximum tangential acceleration. + **/ + void getTangentialAcceleration(float &min, float &max) const; + + /** + * Sets the amount of linear damping. Damping reduces the velocity of + * particles over time. A value of 0 corresponds to no damping. + **/ + void setLinearDamping(float min, float max); + + /** + * Gets the current amount of linear damping. + **/ + void getLinearDamping(float &min, float &max) const; + + /** + * Sets the size of the sprite (1.0 being the default size). + * @param size The size of the sprite. + **/ + void setSize(float size); + + /** + * Sets the sizes of the sprite upon creation and upon death (1.0 being the default size). + * @param newSizes Array of sizes + **/ + void setSizes(const std::vector &newSizes); + + /** + * Returns the sizes of the particle sprites. + **/ + const std::vector &getSizes() const; + + /** + * Sets the amount of variation to the sprite's beginning size (0 being no variation and 1.0 a random size between start and end). + * @param variation The amount of variation. + **/ + void setSizeVariation(float variation); + + /** + * Returns the amount of initial size variation between particles. + **/ + float getSizeVariation() const; + + /** + * Sets the amount of rotation a sprite starts out with. + * @param rotation The amount of rotation. + **/ + void setRotation(float rotation); + + /** + * Sets the amount of rotation a sprite starts out with (a random value between min and max). + * @param min The minimum amount of rotation. + * @param max The maximum amount of rotation. + **/ + void setRotation(float min, float max); + + /** + * Gets the initial amount of rotation of a particle, in radians. + * @param[out] min The minimum initial rotation. + * @param[out] max The maximum initial rotation. + **/ + void getRotation(float &min, float &max) const; + + /** + * Sets the spin of the sprite. + * @param spin The spin of the sprite (in degrees). + **/ + void setSpin(float spin); + + /** + * Sets the spin of the sprite upon particle creation and death. + * @param start The spin of the sprite upon creation (in radians / second). + * @param end The spin of the sprite upon death (in radians / second). + **/ + void setSpin(float start, float end); + + /** + * Gets the amount of spin of a particle during its lifetime. + * @param[out] start The initial spin, in radians / s. + * @param[out] end The final spin, in radians / s. + **/ + void getSpin(float &start, float &end) const; + + /** + * Sets the variation of the start spin (0 being no variation and 1 being a random spin between start and end). + * @param variation The variation. + **/ + void setSpinVariation(float variation); + + /** + * Returns the amount of variation of the start spin of a particle. + **/ + float getSpinVariation() const; + + /** + * Sets the particles' offsets for rotation. + * @param x The x offset. + * @param y The y offset. + **/ + void setOffset(float x, float y); + + /** + * Returns of the particle offset. + **/ + love::Vector getOffset() const; + + /** + * Sets the color of the particles. + * @param newColors Array of colors + **/ + void setColor(const std::vector &newColors); + + /** + * Returns the color of the particles. + **/ + std::vector getColor() const; + + /** + * Sets a list of Quads to use for particles over their lifetime. + **/ + void setQuads(const std::vector &newQuads); + void setQuads(); + + /** + * Gets the Quads used when drawing the particles. + **/ + std::vector getQuads() const; + + /** + * sets whether particle angles & rotations are relative to their velocities. + **/ + void setRelativeRotation(bool enable); + bool hasRelativeRotation() const; + + /** + * Returns the amount of particles that are currently active in the system. + **/ + uint32 getCount() const; + + /** + * Starts/resumes the particle emitter. + **/ + void start(); + + /** + * Stops the particle emitter and resets. + **/ + void stop(); + + /** + * Pauses the particle emitter. + **/ + void pause(); + + /** + * Resets the particle emitter. + **/ + void reset(); + + /** + * Instantly emits a number of particles. + * @param num The number of particles to emit. + **/ + void emit(uint32 num); + + /** + * Returns whether the particle emitter is active. + **/ + bool isActive() const; + + /** + * Returns whether the particle emitter is paused. + **/ + bool isPaused() const; + + bool isStopped() const; + + /** + * Returns whether the particle system is empty of particles or not. + **/ + bool isEmpty() const; + + /** + * Returns whether the amount of particles has reached the buffer limit or not. + **/ + bool isFull() const; + + /** + * Updates the particle system. + * @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); + + static bool getConstant(const char *in, InsertMode &out); + static bool getConstant(InsertMode in, const char *&out); + +protected: + + // Represents a single particle. + struct Particle + { + Particle *prev; + Particle *next; + + float lifetime; + float life; + + love::Vector position; + + // Particles gravitate towards this point. + love::Vector origin; + + love::Vector velocity; + love::Vector linearAcceleration; + float radialAcceleration; + float tangentialAcceleration; + + float linearDamping; + + float size; + float sizeOffset; + float sizeIntervalSize; + + float rotation; // Amount of rotation applied to the final angle. + float angle; + float spinStart; + float spinEnd; + + Colorf color; + + int quadIndex; + }; + + // Pointer to the beginning of the allocated memory. + Particle *pMem; + + // Pointer to a free particle. + Particle *pFree; + + // Pointer to the start of the linked list. + Particle *pHead; + + // Pointer to the end of the linked list. + Particle *pTail; + + // The texture to be drawn. + StrongRef texture; + + // Whether the particle emitter is active. + bool active; + + // Insert mode of new particles. + InsertMode insertMode; + + // The maximum number of particles. + uint32 maxParticles; + + // The number of active particles. + uint32 activeParticles; + + // The emission rate (particles/sec). + float emissionRate; + + // Used to determine when a particle should be emitted. + float emitCounter; + + // The relative position of the particle emitter. + love::Vector position; + love::Vector prevPosition; + + // 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; + + // The particle life. + float particleLifeMin; + float particleLifeMax; + + // The direction (and spread) the particles will be emitted in. Measured in radians. + float direction; + float spread; + + // The speed. + float speedMin; + float speedMax; + + // Acceleration along the x and y axes. + love::Vector linearAccelerationMin; + love::Vector linearAccelerationMax; + + // Acceleration towards the emitter's center + float radialAccelerationMin; + float radialAccelerationMax; + + // Acceleration perpendicular to the particle's direction. + float tangentialAccelerationMin; + float tangentialAccelerationMax; + + float linearDampingMin; + float linearDampingMax; + + // Size. + std::vector sizes; + float sizeVariation; + + // Rotation + float rotationMin; + float rotationMax; + + // Spin. + float spinStart; + float spinEnd; + float spinVariation; + + // Offsets + love::Vector offset; + + // Is the ParticleSystem using a default offset? + bool defaultOffset; + + // Color. + std::vector colors; + + // Quads. + std::vector> quads; + + bool relativeRotation; + +private: + + void resetOffset(); + + void createBuffers(size_t size); + void deleteBuffers(); + + void addParticle(float t); + Particle *removeParticle(Particle *p); + + // Called by addParticle. + void initParticle(Particle *p, float t); + void insertTop(Particle *p); + void insertBottom(Particle *p); + void insertRandom(Particle *p); + + static StringMap::Entry distributionsEntries[]; + static StringMap distributions; + + static StringMap::Entry insertModesEntries[]; + static StringMap insertModes; +}; + +} // graphics +} // love + +#endif // LOVE_GRAPHICS_PARTICLE_SYSTEM_H diff --git a/src/modules/graphics/opengl/ParticleSystem.cpp b/src/modules/graphics/opengl/ParticleSystem.cpp index b08ee83b0..83cb4969b 100644 --- a/src/modules/graphics/opengl/ParticleSystem.cpp +++ b/src/modules/graphics/opengl/ParticleSystem.cpp @@ -22,8 +22,6 @@ #include "common/config.h" #include "ParticleSystem.h" -#include "common/math.h" -#include "modules/math/RandomGenerator.h" #include "OpenGL.h" // STD @@ -38,123 +36,39 @@ namespace graphics namespace opengl { -namespace -{ - -love::math::RandomGenerator rng; - -float calculate_variation(float inner, float outer, float var) -{ - float low = inner - (outer/2.0f)*var; - float high = inner + (outer/2.0f)*var; - float r = (float) rng.random(); - return low*(1-r)+high*r; -} - -} // anonymous namespace - ParticleSystem::ParticleSystem(Texture *texture, uint32 size) - : pMem(nullptr) - , pFree(nullptr) - , pHead(nullptr) - , pTail(nullptr) + : love::graphics::ParticleSystem(texture, size) , particleVerts(nullptr) - , quadIndices(1) - , texture(texture) - , active(true) - , insertMode(INSERT_MODE_TOP) - , maxParticles(0) - , activeParticles(0) - , emissionRate(0) - , emitCounter(0) - , areaSpreadDistribution(DISTRIBUTION_NONE) - , lifetime(-1) - , life(0) - , particleLifeMin(0) - , particleLifeMax(0) - , direction(0) - , spread(0) - , speedMin(0) - , speedMax(0) - , linearAccelerationMin(0, 0) - , linearAccelerationMax(0, 0) - , radialAccelerationMin(0) - , radialAccelerationMax(0) - , tangentialAccelerationMin(0) - , tangentialAccelerationMax(0) - , linearDampingMin(0.0f) - , linearDampingMax(0.0f) - , sizeVariation(0) - , rotationMin(0) - , rotationMax(0) - , spinStart(0) - , spinEnd(0) - , spinVariation(0) - , offset(float(texture->getWidth())*0.5f, float(texture->getHeight())*0.5f) - , defaultOffset(true) - , relativeRotation(false) + , quadIndices(size) { - if (size == 0 || size > MAX_PARTICLES) - throw love::Exception("Invalid ParticleSystem size."); - - sizes.push_back(1.0f); - colors.push_back(Colorf(1.0f, 1.0f, 1.0f, 1.0f)); - setBufferSize(size); + createVertices(size); } ParticleSystem::ParticleSystem(const ParticleSystem &p) - : pMem(nullptr) - , pFree(nullptr) - , pHead(nullptr) - , pTail(nullptr) + : love::graphics::ParticleSystem(p) , particleVerts(nullptr) , quadIndices(p.quadIndices) - , texture(p.texture) - , active(p.active) - , insertMode(p.insertMode) - , maxParticles(p.maxParticles) - , activeParticles(0) - , emissionRate(p.emissionRate) - , emitCounter(0.0f) - , position(p.position) - , prevPosition(p.prevPosition) - , areaSpreadDistribution(p.areaSpreadDistribution) - , areaSpread(p.areaSpread) - , lifetime(p.lifetime) - , life(p.lifetime) // Initialize with the maximum life time. - , particleLifeMin(p.particleLifeMin) - , particleLifeMax(p.particleLifeMax) - , direction(p.direction) - , spread(p.spread) - , speedMin(p.speedMin) - , speedMax(p.speedMax) - , linearAccelerationMin(p.linearAccelerationMin) - , linearAccelerationMax(p.linearAccelerationMax) - , radialAccelerationMin(p.radialAccelerationMin) - , radialAccelerationMax(p.radialAccelerationMax) - , tangentialAccelerationMin(p.tangentialAccelerationMin) - , tangentialAccelerationMax(p.tangentialAccelerationMax) - , linearDampingMin(p.linearDampingMin) - , linearDampingMax(p.linearDampingMax) - , sizes(p.sizes) - , sizeVariation(p.sizeVariation) - , rotationMin(p.rotationMin) - , rotationMax(p.rotationMax) - , spinStart(p.spinStart) - , spinEnd(p.spinEnd) - , spinVariation(p.spinVariation) - , offset(p.offset) - , defaultOffset(p.defaultOffset) - , colors(p.colors) - , quads(p.quads) - , relativeRotation(p.relativeRotation) { - setBufferSize(maxParticles); + createVertices(maxParticles); } ParticleSystem::~ParticleSystem() { - deleteBuffers(); + delete[] particleVerts; +} + +void ParticleSystem::createVertices(size_t numparticles) +{ + try + { + love::Vertex *pverts = new love::Vertex[numparticles * 4]; + delete[] particleVerts; + particleVerts = pverts; + } + catch (std::exception &) + { + throw love::Exception("Out of memory."); + } } ParticleSystem *ParticleSystem::clone() @@ -162,688 +76,18 @@ ParticleSystem *ParticleSystem::clone() return new ParticleSystem(*this); } -void ParticleSystem::resetOffset() -{ - if (quads.empty()) - offset = love::Vector(float(texture->getWidth())*0.5f, float(texture->getHeight())*0.5f); - else - { - Quad::Viewport v = quads[0]->getViewport(); - offset = love::Vector(v.x*0.5f, v.y*0.5f); - } -} - -void ParticleSystem::createBuffers(size_t size) -{ - try - { - pFree = pMem = new Particle[size]; - particleVerts = new love::Vertex[size * 4]; - maxParticles = (uint32) size; - } - catch (std::bad_alloc &) - { - deleteBuffers(); - throw love::Exception("Out of memory"); - } -} - -void ParticleSystem::deleteBuffers() -{ - // Clean up for great gracefulness! - delete[] pMem; - delete[] particleVerts; - - pMem = nullptr; - particleVerts = nullptr; - maxParticles = 0; - activeParticles = 0; -} - void ParticleSystem::setBufferSize(uint32 size) { - if (size == 0 || size > MAX_PARTICLES) - throw love::Exception("Invalid buffer size"); + love::graphics::ParticleSystem::setBufferSize(size); + quadIndices = QuadIndices(size); - deleteBuffers(); - createBuffers(size); - reset(); -} - -uint32 ParticleSystem::getBufferSize() const -{ - return maxParticles; -} - -void ParticleSystem::addParticle(float t) -{ - if (isFull()) - return; - - // Gets a free particle and updates the allocation pointer. - Particle *p = pFree++; - initParticle(p, t); - - switch (insertMode) - { - default: - case INSERT_MODE_TOP: - insertTop(p); - break; - case INSERT_MODE_BOTTOM: - insertBottom(p); - break; - case INSERT_MODE_RANDOM: - insertRandom(p); - break; - } - - activeParticles++; -} - -void ParticleSystem::initParticle(Particle *p, float t) -{ - float min,max; - - // Linearly interpolate between the previous and current emitter position. - love::Vector pos = prevPosition + (position - prevPosition) * t; - - min = particleLifeMin; - max = particleLifeMax; - if (min == max) - p->life = min; - else - p->life = (float) rng.random(min, max); - p->lifetime = p->life; - - p->position = pos; - - 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()); - break; - case DISTRIBUTION_NORMAL: - p->position.x += (float) rng.randomNormal(areaSpread.getX()); - p->position.y += (float) rng.randomNormal(areaSpread.getY()); - break; - case DISTRIBUTION_NONE: - default: - break; - } - - 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); - p->linearAcceleration.y = (float) rng.random(linearAccelerationMin.y, linearAccelerationMax.y); - - min = radialAccelerationMin; - max = radialAccelerationMax; - p->radialAcceleration = (float) rng.random(min, max); - - min = tangentialAccelerationMin; - max = tangentialAccelerationMax; - p->tangentialAcceleration = (float) rng.random(min, max); - - min = linearDampingMin; - max = linearDampingMax; - p->linearDamping = (float) rng.random(min, max); - - p->sizeOffset = (float) rng.random(sizeVariation); // time offset for size change - p->sizeIntervalSize = (1.0f - (float) rng.random(sizeVariation)) - p->sizeOffset; - p->size = sizes[(size_t)(p->sizeOffset - .5f) * (sizes.size() - 1)]; - - min = rotationMin; - max = rotationMax; - p->spinStart = calculate_variation(spinStart, spinEnd, spinVariation); - p->spinEnd = calculate_variation(spinEnd, spinStart, spinVariation); - p->rotation = (float) rng.random(min, max); - - p->angle = p->rotation; - if (relativeRotation) - p->angle += atan2f(p->velocity.y, p->velocity.x); - - p->color = colors[0]; - - p->quadIndex = 0; -} - -void ParticleSystem::insertTop(Particle *p) -{ - if (pHead == nullptr) - { - pHead = p; - p->prev = nullptr; - } - else - { - pTail->next = p; - p->prev = pTail; - } - p->next = nullptr; - pTail = p; -} - -void ParticleSystem::insertBottom(Particle *p) -{ - if (pTail == nullptr) - { - pTail = p; - p->next = nullptr; - } - else - { - pHead->prev = p; - p->next = pHead; - } - p->prev = nullptr; - pHead = p; -} - -void ParticleSystem::insertRandom(Particle *p) -{ - // Nonuniform, but 64-bit is so large nobody will notice. Hopefully. - uint64 pos = rng.rand() % ((int64) activeParticles + 1); - - // Special case where the particle gets inserted before the head. - if (pos == activeParticles) - { - Particle *pA = pHead; - if (pA) - pA->prev = p; - p->prev = nullptr; - p->next = pA; - pHead = p; - return; - } - - // Inserts the particle after the randomly selected particle. - Particle *pA = pMem + pos; - Particle *pB = pA->next; - pA->next = p; - if (pB) - pB->prev = p; - else - pTail = p; - p->prev = pA; - p->next = pB; -} - -ParticleSystem::Particle *ParticleSystem::removeParticle(Particle *p) -{ - // The linked list is updated in this function and old pointers may be - // invalidated. The returned pointer will inform the caller of the new - // pointer to the next particle. - Particle *pNext = nullptr; - - // Removes the particle from the linked list. - if (p->prev) - p->prev->next = p->next; - else - pHead = p->next; - - if (p->next) - { - p->next->prev = p->prev; - pNext = p->next; - } - else - pTail = p->prev; - - // The (in memory) last particle can now be moved into the free slot. - // It will skip the moving if it happens to be the removed particle. - pFree--; - if (p != pFree) - { - *p = *pFree; - if (pNext == pFree) - pNext = p; - - if (p->prev) - p->prev->next = p; - else - pHead = p; - - if (p->next) - p->next->prev = p; - else - pTail = p; - } - - activeParticles--; - return pNext; -} - -void ParticleSystem::setTexture(Texture *tex) -{ - texture.set(tex); - - if (defaultOffset) - resetOffset(); -} - -Texture *ParticleSystem::getTexture() const -{ - return texture.get(); -} - -void ParticleSystem::setInsertMode(InsertMode mode) -{ - insertMode = mode; -} - -ParticleSystem::InsertMode ParticleSystem::getInsertMode() const -{ - return insertMode; -} - -void ParticleSystem::setEmissionRate(float rate) -{ - if (rate < 0.0f) - throw love::Exception("Invalid emission rate"); - emissionRate = rate; -} - -float ParticleSystem::getEmissionRate() const -{ - return emissionRate; -} - -void ParticleSystem::setEmitterLifetime(float life) -{ - this->life = lifetime = life; -} - -float ParticleSystem::getEmitterLifetime() const -{ - return lifetime; -} - -void ParticleSystem::setParticleLifetime(float min, float max) -{ - particleLifeMin = min; - if (max == 0) - particleLifeMax = min; - else - particleLifeMax = max; -} - -void ParticleSystem::getParticleLifetime(float &min, float &max) const -{ - min = particleLifeMin; - max = particleLifeMax; -} - -void ParticleSystem::setPosition(float x, float y) -{ - position = love::Vector(x, y); - prevPosition = position; -} - -const love::Vector &ParticleSystem::getPosition() const -{ - return position; -} - -void ParticleSystem::moveTo(float x, float y) -{ - position = love::Vector(x, y); -} - -void ParticleSystem::setAreaSpread(AreaSpreadDistribution distribution, float x, float y) -{ - areaSpread = love::Vector(x, y); - areaSpreadDistribution = distribution; -} - -ParticleSystem::AreaSpreadDistribution ParticleSystem::getAreaSpreadDistribution() const -{ - return areaSpreadDistribution; -} - -const love::Vector &ParticleSystem::getAreaSpreadParameters() const -{ - return areaSpread; -} - -void ParticleSystem::setDirection(float direction) -{ - this->direction = direction; -} - -float ParticleSystem::getDirection() const -{ - return direction; -} - -void ParticleSystem::setSpread(float spread) -{ - this->spread = spread; -} - -float ParticleSystem::getSpread() const -{ - return spread; -} - -void ParticleSystem::setSpeed(float speed) -{ - speedMin = speedMax = speed; -} - -void ParticleSystem::setSpeed(float min, float max) -{ - speedMin = min; - speedMax = max; -} - -void ParticleSystem::getSpeed(float &min, float &max) const -{ - min = speedMin; - max = speedMax; -} - -void ParticleSystem::setLinearAcceleration(float x, float y) -{ - linearAccelerationMin.x = linearAccelerationMax.x = x; - linearAccelerationMin.y = linearAccelerationMax.y = y; -} - -void ParticleSystem::setLinearAcceleration(float xmin, float ymin, float xmax, float ymax) -{ - linearAccelerationMin = love::Vector(xmin, ymin); - linearAccelerationMax = love::Vector(xmax, ymax); -} - -void ParticleSystem::getLinearAcceleration(love::Vector &min, love::Vector &max) const -{ - min = linearAccelerationMin; - max = linearAccelerationMax; -} - -void ParticleSystem::setRadialAcceleration(float acceleration) -{ - radialAccelerationMin = radialAccelerationMax = acceleration; -} - -void ParticleSystem::setRadialAcceleration(float min, float max) -{ - radialAccelerationMin = min; - radialAccelerationMax = max; -} - -void ParticleSystem::getRadialAcceleration(float &min, float &max) const -{ - min = radialAccelerationMin; - max = radialAccelerationMax; -} - -void ParticleSystem::setTangentialAcceleration(float acceleration) -{ - tangentialAccelerationMin = tangentialAccelerationMax = acceleration; -} - -void ParticleSystem::setTangentialAcceleration(float min, float max) -{ - tangentialAccelerationMin = min; - tangentialAccelerationMax = max; -} - -void ParticleSystem::getTangentialAcceleration(float &min, float &max) const -{ - min = tangentialAccelerationMin; - max = tangentialAccelerationMax; -} - -void ParticleSystem::setLinearDamping(float min, float max) -{ - linearDampingMin = min; - linearDampingMax = max; -} - -void ParticleSystem::getLinearDamping(float &min, float &max) const -{ - min = linearDampingMin; - max = linearDampingMax; -} - -void ParticleSystem::setSize(float size) -{ - sizes.resize(1); - sizes[0] = size; -} - -void ParticleSystem::setSizes(const std::vector &newSizes) -{ - sizes = newSizes; -} - -const std::vector &ParticleSystem::getSizes() const -{ - return sizes; -} - -void ParticleSystem::setSizeVariation(float variation) -{ - sizeVariation = variation; -} - -float ParticleSystem::getSizeVariation() const -{ - return sizeVariation; -} - -void ParticleSystem::setRotation(float rotation) -{ - rotationMin = rotationMax = rotation; -} - -void ParticleSystem::setRotation(float min, float max) -{ - rotationMin = min; - rotationMax = max; -} - -void ParticleSystem::getRotation(float &min, float &max) const -{ - min = rotationMin; - max = rotationMax; -} - -void ParticleSystem::setSpin(float spin) -{ - spinStart = spin; - spinEnd = spin; -} - -void ParticleSystem::setSpin(float start, float end) -{ - spinStart = start; - spinEnd = end; -} - -void ParticleSystem::getSpin(float &start, float &end) const -{ - start = spinStart; - end = spinEnd; -} - -void ParticleSystem::setSpinVariation(float variation) -{ - spinVariation = variation; -} - -float ParticleSystem::getSpinVariation() const -{ - return spinVariation; -} - -void ParticleSystem::setOffset(float x, float y) -{ - offset = love::Vector(x, y); - defaultOffset = false; -} - -love::Vector ParticleSystem::getOffset() const -{ - return offset; -} - -void ParticleSystem::setColor(const std::vector &newColors) -{ - colors = newColors; - - for (Colorf &c : colors) - { - // We want to store the colors as [0, 1], rather than [0, 255]. - c.r /= 255.0f; - c.g /= 255.0f; - c.b /= 255.0f; - c.a /= 255.0f; - } -} - -std::vector ParticleSystem::getColor() const -{ - // The particle system stores colors in the range of [0, 1]... - std::vector ncolors(colors); - - for (Colorf &c : ncolors) - { - c.r *= 255.0f; - c.g *= 255.0f; - c.b *= 255.0f; - c.a *= 255.0f; - } - - return ncolors; -} - -void ParticleSystem::setQuads(const std::vector &newQuads) -{ - std::vector> quadlist; - quadlist.reserve(newQuads.size()); - - for (Quad *q : newQuads) - quadlist.push_back(q); - - quads = quadlist; - - if (defaultOffset) - resetOffset(); -} - -void ParticleSystem::setQuads() -{ - quads.clear(); -} - -std::vector ParticleSystem::getQuads() const -{ - std::vector quadlist; - quadlist.reserve(quads.size()); - - for (const StrongRef &q : quads) - quadlist.push_back(q.get()); - - return quadlist; -} - -void ParticleSystem::setRelativeRotation(bool enable) -{ - relativeRotation = enable; -} - -bool ParticleSystem::hasRelativeRotation() const -{ - return relativeRotation; -} - -uint32 ParticleSystem::getCount() const -{ - return activeParticles; -} - -void ParticleSystem::start() -{ - active = true; -} - -void ParticleSystem::stop() -{ - active = false; - life = lifetime; - emitCounter = 0; -} - -void ParticleSystem::pause() -{ - active = false; -} - -void ParticleSystem::reset() -{ - if (pMem == nullptr) - return; - - pFree = pMem; - pHead = nullptr; - pTail = nullptr; - activeParticles = 0; - life = lifetime; - emitCounter = 0; -} - -void ParticleSystem::emit(uint32 num) -{ - if (!active) - return; - - num = std::min(num, maxParticles - activeParticles); - - while(num--) - addParticle(1.0f); -} - -bool ParticleSystem::isActive() const -{ - return active; -} - -bool ParticleSystem::isPaused() const -{ - return !active && life < lifetime; -} - -bool ParticleSystem::isStopped() const -{ - return !active && life >= lifetime; -} - -bool ParticleSystem::isEmpty() const -{ - return activeParticles == 0; -} - -bool ParticleSystem::isFull() const -{ - return activeParticles == maxParticles; + createVertices(size); } void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) { uint32 pCount = getCount(); + if (pCount == 0 || texture.get() == nullptr || pMem == nullptr || particleVerts == nullptr) return; @@ -904,163 +148,6 @@ void ParticleSystem::draw(float x, float y, float angle, float sx, float sy, flo } } -void ParticleSystem::update(float dt) -{ - if (pMem == nullptr || dt == 0.0f) - return; - - // Traverse all particles and update. - Particle *p = pHead; - - while (p) - { - // Decrease lifespan. - p->life -= dt; - - if (p->life <= 0) - p = removeParticle(p); - else - { - // Temp variables. - love::Vector radial, tangential; - love::Vector ppos = p->position; - - // Get vector from particle center to particle. - radial = ppos - p->origin; - radial.normalize(); - tangential = radial; - - // Resize radial acceleration. - radial *= p->radialAcceleration; - - // Calculate tangential acceleration. - { - float a = tangential.getX(); - tangential.setX(-tangential.getY()); - tangential.setY(a); - } - - // Resize tangential. - tangential *= p->tangentialAcceleration; - - // Update velocity. - p->velocity += (radial + tangential + p->linearAcceleration) * dt; - - // Apply damping. - p->velocity *= 1.0f / (1.0f + p->linearDamping * dt); - - // Modify position. - ppos += p->velocity * dt; - - p->position = ppos; - - const float t = 1.0f - p->life / p->lifetime; - - // Rotate. - p->rotation += (p->spinStart * (1.0f - t) + p->spinEnd * t) * dt; - - p->angle = p->rotation; - - if (relativeRotation) - p->angle += atan2f(p->velocity.y, p->velocity.x); - - // Change size according to given intervals: - // i = 0 1 2 3 n-1 - // |-------|-------|------|--- ... ---| - // t = 0 1/(n-1) 3/(n-1) 1 - // - // `s' is the interpolation variable scaled to the current - // interval width, e.g. if n = 5 and t = 0.3, then the current - // indices are 1,2 and s = 0.3 - 0.25 = 0.05 - float s = p->sizeOffset + t * p->sizeIntervalSize; // size variation - s *= (float)(sizes.size() - 1); // 0 <= s < sizes.size() - size_t i = (size_t)s; - size_t k = (i == sizes.size() - 1) ? i : i + 1; // boundary check (prevents failing on t = 1.0f) - s -= (float)i; // transpose s to be in interval [0:1]: i <= s < i + 1 ~> 0 <= s < 1 - p->size = sizes[i] * (1.0f - s) + sizes[k] * s; - - // Update color according to given intervals (as above) - s = t * (float)(colors.size() - 1); - i = (size_t)s; - k = (i == colors.size() - 1) ? i : i + 1; - s -= (float)i; // 0 <= s <= 1 - p->color = colors[i] * (1.0f - s) + colors[k] * s; - - // Update the quad index. - k = quads.size(); - if (k > 0) - { - s = t * (float) k; // [0:numquads-1] (clamped below) - i = (s > 0.0f) ? (size_t) s : 0; - p->quadIndex = (int) ((i < k) ? i : k - 1); - } - - // Next particle. - p = p->next; - } - } - - // Make some more particles. - if (active) - { - float rate = 1.0f / emissionRate; // the amount of time between each particle emit - emitCounter += dt; - float total = emitCounter - rate; - while (emitCounter > rate) - { - addParticle(1.0f - (emitCounter - rate) / total); - emitCounter -= rate; - } - /*int particles = (int)(emissionRate * dt); - for (int i = 0; i != particles; i++) - add();*/ - - life -= dt; - if (lifetime != -1 && life < 0) - stop(); - } - - prevPosition = position; -} - -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); -} - -bool ParticleSystem::getConstant(const char *in, InsertMode &out) -{ - return insertModes.find(in, out); -} - -bool ParticleSystem::getConstant(InsertMode in, const char *&out) -{ - return insertModes.find(in, out); -} - -StringMap::Entry ParticleSystem::distributionsEntries[] = -{ - { "none", ParticleSystem::DISTRIBUTION_NONE }, - { "uniform", ParticleSystem::DISTRIBUTION_UNIFORM }, - { "normal", ParticleSystem::DISTRIBUTION_NORMAL }, -}; - -StringMap ParticleSystem::distributions(ParticleSystem::distributionsEntries, sizeof(ParticleSystem::distributionsEntries)); - -StringMap::Entry ParticleSystem::insertModesEntries[] = -{ - { "top", ParticleSystem::INSERT_MODE_TOP }, - { "bottom", ParticleSystem::INSERT_MODE_BOTTOM }, - { "random", ParticleSystem::INSERT_MODE_RANDOM }, -}; - -StringMap ParticleSystem::insertModes(ParticleSystem::insertModesEntries, sizeof(ParticleSystem::insertModesEntries)); - } // opengl } // graphics } // love diff --git a/src/modules/graphics/opengl/ParticleSystem.h b/src/modules/graphics/opengl/ParticleSystem.h index 8502f826b..6641bdb1b 100644 --- a/src/modules/graphics/opengl/ParticleSystem.h +++ b/src/modules/graphics/opengl/ParticleSystem.h @@ -22,18 +22,9 @@ #define LOVE_GRAPHICS_OPENGL_PARTICLE_SYSTEM_H // LOVE -#include "common/int.h" -#include "common/math.h" -#include "common/Vector.h" -#include "graphics/Drawable.h" -#include "graphics/Color.h" -#include "graphics/Quad.h" -#include "graphics/Texture.h" +#include "graphics/ParticleSystem.h" #include "GLBuffer.h" -// STL -#include - namespace love { namespace graphics @@ -41,643 +32,28 @@ namespace graphics namespace opengl { -/** - * A class for creating, moving and drawing particles. - * A big thanks to bobthebloke.org - **/ -class ParticleSystem : public Drawable +class ParticleSystem : public love::graphics::ParticleSystem { public: - /** - * Type of distribution new particles are drawn from: None, uniform, normal. - */ - enum AreaSpreadDistribution - { - DISTRIBUTION_NONE, - DISTRIBUTION_UNIFORM, - DISTRIBUTION_NORMAL, - DISTRIBUTION_MAX_ENUM - }; - /** - * Insertion modes of new particles in the list: top, bottom, random. - */ - enum InsertMode - { - INSERT_MODE_TOP, - INSERT_MODE_BOTTOM, - INSERT_MODE_RANDOM, - INSERT_MODE_MAX_ENUM - }; - - /** - * Maximum numbers of particles in a ParticleSystem. - * This limit comes from the fact that a quad requires four vertices and the - * OpenGL API where GLsizei is a signed int. - **/ - static const uint32 MAX_PARTICLES = LOVE_INT32_MAX / 4; - - /** - * Creates a particle system with the specified buffer size and texture. - **/ ParticleSystem(Texture *texture, uint32 buffer); ParticleSystem(const ParticleSystem &p); - /** - * Deletes any allocated memory. - **/ virtual ~ParticleSystem(); - /** - * Creates an identical copy of this ParticleSystem. The clone does not - * duplicate any existing particles from this ParticleSystem, just the - * settable parameters. - **/ - ParticleSystem *clone(); + ParticleSystem *clone() override; + void setBufferSize(uint32 size) override; + void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky) override; - /** - * Sets the texture used in the particle system. - * @param texture The new texture. - **/ - void setTexture(Texture *texture); +private: - /** - * Returns the texture used when drawing the particle system. - **/ - Texture *getTexture() const; - - /** - * Clears the current buffer and allocates the appropriate amount of space for the buffer. - * @param size The new buffer size. - **/ - void setBufferSize(uint32 size); - - /** - * Returns the total amount of particles this ParticleSystem can have active - * at any given point in time. - **/ - uint32 getBufferSize() const; - - /** - * Sets the insert mode for new particles. - * @param mode The new insert mode. - */ - void setInsertMode(InsertMode mode); - - /** - * Returns the current insert mode. - */ - InsertMode getInsertMode() const; - - /** - * Sets the emission rate. - * @param rate The amount of particles per second. - **/ - void setEmissionRate(float rate); - - /** - * Returns the number of particles created per second. - **/ - float getEmissionRate() const; - - /** - * Sets the lifetime of the particle emitter (-1 means eternal) - * @param life The lifetime (in seconds). - **/ - void setEmitterLifetime(float life); - - /** - * Returns the lifetime of the particle emitter. - **/ - float getEmitterLifetime() const; - - /** - * Sets the life range of the particles. - * @param min The minimum life. - * @param max The maximum life (if 0, then becomes the same as minimum life). - **/ - void setParticleLifetime(float min, float max = 0); - - /** - * Gets the lifetime of a particle. - * @param[out] min The minimum life. - * @param[out] max The maximum life. - **/ - void getParticleLifetime(float &min, float &max) const; - - /** - * Sets the position of the center of the emitter. - * Used to move the emitter without changing the position of already existing particles. - * @param x The x-coordinate. - * @param y The y-coordinate. - **/ - void setPosition(float x, float y); - - /** - * Returns the position of the emitter. - **/ - const love::Vector &getPosition() const; - - /** - * Moves the position of the center of the emitter. - * When update is called, newly spawned particles will appear in a line - * between the old emitter position and where the emitter was moved to, - * resulting in a smoother-feeling particle system if moveTo is called - * repeatedly. - **/ - void moveTo(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); - - /** - * Returns area spread distribution type. - **/ - AreaSpreadDistribution getAreaSpreadDistribution() const; - - /** - * Returns area spread parameters. - **/ - const love::Vector &getAreaSpreadParameters() const; - - /** - * Sets the direction of the particle emitter. - * @param direction The direction (in degrees). - **/ - void setDirection(float direction); - - /** - * Returns the direction of the particle emitter (in radians). - **/ - float getDirection() const; - - /** - * Sets the spread of the particle emitter. - * @param spread The spread (in radians). - **/ - void setSpread(float spread); - - /** - * Returns the directional spread of the emitter (in radians). - **/ - float getSpread() const; - - /** - * Sets the speed of the particles. - * @param speed The speed. - **/ - void setSpeed(float speed); - - /** - * Sets the speed of the particles. - * @param min The minimum speed. - * @param max The maximum speed. - **/ - void setSpeed(float min, float max); - - /** - * Gets the speed of the particles. - * @param[out] min The minimum speed. - * @param[out] max The maximum speed. - **/ - void getSpeed(float &min, float &max) const; - - /** - * Sets the linear acceleration (the acceleration along the x and y axes). - * @param x The acceleration along the x-axis. - * @param y The acceleration along the y-axis. - **/ - void setLinearAcceleration(float x, float y); - - /** - * Sets the linear acceleration (the acceleration along the x and y axes). - * @param xmin The minimum amount of acceleration along the x-axis. - * @param ymin The minimum amount of acceleration along the y-axis. - * @param xmax The maximum amount of acceleration along the x-axis. - * @param ymax The maximum amount of acceleration along the y-axis. - **/ - void setLinearAcceleration(float xmin, float ymin, float xmax, float ymax); - - /** - * Gets the linear acceleration of the particles. - * @param[out] min The minimum acceleration. - * @param[out] max The maximum acceleration. - **/ - void getLinearAcceleration(love::Vector &min, love::Vector &max) const; - - /** - * Sets the radial acceleration (the acceleration towards the particle emitter). - * @param acceleration The amount of acceleration. - **/ - void setRadialAcceleration(float acceleration); - - /** - * Sets the radial acceleration (the acceleration towards the particle emitter). - * @param min The minimum acceleration. - * @param max The maximum acceleration. - **/ - void setRadialAcceleration(float min, float max); - - /** - * Gets the radial acceleration. - * @param[out] min The minimum amount of radial acceleration. - * @param[out] max The maximum amount of radial acceleration. - **/ - void getRadialAcceleration(float &min, float &max) const; - - /** - * Sets the tangential acceleration (the acceleration perpendicular to the particle's direction). - * @param acceleration The amount of acceleration. - **/ - void setTangentialAcceleration(float acceleration); - - /** - * Sets the tangential acceleration (the acceleration perpendicular to the particle's direction). - * @param min The minimum acceleration. - * @param max The maximum acceleration. - **/ - void setTangentialAcceleration(float min, float max); - - /** - * Gets the tangential acceleration. - * @param[out] min The minimum tangential acceleration. - * @param[out] max The maximum tangential acceleration. - **/ - void getTangentialAcceleration(float &min, float &max) const; - - /** - * Sets the amount of linear damping. Damping reduces the velocity of - * particles over time. A value of 0 corresponds to no damping. - **/ - void setLinearDamping(float min, float max); - - /** - * Gets the current amount of linear damping. - **/ - void getLinearDamping(float &min, float &max) const; - - /** - * Sets the size of the sprite (1.0 being the default size). - * @param size The size of the sprite. - **/ - void setSize(float size); - - /** - * Sets the sizes of the sprite upon creation and upon death (1.0 being the default size). - * @param newSizes Array of sizes - **/ - void setSizes(const std::vector &newSizes); - - /** - * Returns the sizes of the particle sprites. - **/ - const std::vector &getSizes() const; - - /** - * Sets the amount of variation to the sprite's beginning size (0 being no variation and 1.0 a random size between start and end). - * @param variation The amount of variation. - **/ - void setSizeVariation(float variation); - - /** - * Returns the amount of initial size variation between particles. - **/ - float getSizeVariation() const; - - /** - * Sets the amount of rotation a sprite starts out with. - * @param rotation The amount of rotation. - **/ - void setRotation(float rotation); - - /** - * Sets the amount of rotation a sprite starts out with (a random value between min and max). - * @param min The minimum amount of rotation. - * @param max The maximum amount of rotation. - **/ - void setRotation(float min, float max); - - /** - * Gets the initial amount of rotation of a particle, in radians. - * @param[out] min The minimum initial rotation. - * @param[out] max The maximum initial rotation. - **/ - void getRotation(float &min, float &max) const; - - /** - * Sets the spin of the sprite. - * @param spin The spin of the sprite (in degrees). - **/ - void setSpin(float spin); - - /** - * Sets the spin of the sprite upon particle creation and death. - * @param start The spin of the sprite upon creation (in radians / second). - * @param end The spin of the sprite upon death (in radians / second). - **/ - void setSpin(float start, float end); - - /** - * Gets the amount of spin of a particle during its lifetime. - * @param[out] start The initial spin, in radians / s. - * @param[out] end The final spin, in radians / s. - **/ - void getSpin(float &start, float &end) const; - - /** - * Sets the variation of the start spin (0 being no variation and 1 being a random spin between start and end). - * @param variation The variation. - **/ - void setSpinVariation(float variation); - - /** - * Returns the amount of variation of the start spin of a particle. - **/ - float getSpinVariation() const; - - /** - * Sets the particles' offsets for rotation. - * @param x The x offset. - * @param y The y offset. - **/ - void setOffset(float x, float y); - - /** - * Returns of the particle offset. - **/ - love::Vector getOffset() const; - - /** - * Sets the color of the particles. - * @param newColors Array of colors - **/ - void setColor(const std::vector &newColors); - - /** - * Returns the color of the particles. - **/ - std::vector getColor() const; - - /** - * Sets a list of Quads to use for particles over their lifetime. - **/ - void setQuads(const std::vector &newQuads); - void setQuads(); - - /** - * Gets the Quads used when drawing the particles. - **/ - std::vector getQuads() const; - - /** - * sets whether particle angles & rotations are relative to their velocities. - **/ - void setRelativeRotation(bool enable); - bool hasRelativeRotation() const; - - /** - * Returns the amount of particles that are currently active in the system. - **/ - uint32 getCount() const; - - /** - * Starts/resumes the particle emitter. - **/ - void start(); - - /** - * Stops the particle emitter and resets. - **/ - void stop(); - - /** - * Pauses the particle emitter. - **/ - void pause(); - - /** - * Resets the particle emitter. - **/ - void reset(); - - /** - * Instantly emits a number of particles. - * @param num The number of particles to emit. - **/ - void emit(uint32 num); - - /** - * Returns whether the particle emitter is active. - **/ - bool isActive() const; - - /** - * Returns whether the particle emitter is paused. - **/ - bool isPaused() const; - - bool isStopped() const; - - /** - * Returns whether the particle system is empty of particles or not. - **/ - bool isEmpty() const; - - /** - * Returns whether the amount of particles has reached the buffer limit or not. - **/ - bool isFull() const; - - /** - * Draws the particle emitter at the specified position. - * @param x The x-coordinate. - * @param y The y-coordinate. - **/ - virtual void draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky); - - /** - * Updates the particle system. - * @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); - - static bool getConstant(const char *in, InsertMode &out); - static bool getConstant(InsertMode in, const char *&out); - -protected: - - // Represents a single particle. - struct Particle - { - Particle *prev; - Particle *next; - - float lifetime; - float life; - - love::Vector position; - - // Particles gravitate towards this point. - love::Vector origin; - - love::Vector velocity; - love::Vector linearAcceleration; - float radialAcceleration; - float tangentialAcceleration; - - float linearDamping; - - float size; - float sizeOffset; - float sizeIntervalSize; - - float rotation; // Amount of rotation applied to the final angle. - float angle; - float spinStart; - float spinEnd; - - Colorf color; - - int quadIndex; - }; - - // Pointer to the beginning of the allocated memory. - Particle *pMem; - - // Pointer to a free particle. - Particle *pFree; - - // Pointer to the start of the linked list. - Particle *pHead; - - // Pointer to the end of the linked list. - Particle *pTail; + void createVertices(size_t numparticles); // array of transformed vertex data for all particles, for drawing Vertex *particleVerts; // Vertex index buffer. QuadIndices quadIndices; - - // The texture to be drawn. - StrongRef texture; - - // Whether the particle emitter is active. - bool active; - - // Insert mode of new particles. - InsertMode insertMode; - - // The maximum number of particles. - uint32 maxParticles; - - // The number of active particles. - uint32 activeParticles; - - // The emission rate (particles/sec). - float emissionRate; - - // Used to determine when a particle should be emitted. - float emitCounter; - - // The relative position of the particle emitter. - love::Vector position; - love::Vector prevPosition; - - // 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; - - // The particle life. - float particleLifeMin; - float particleLifeMax; - - // The direction (and spread) the particles will be emitted in. Measured in radians. - float direction; - float spread; - - // The speed. - float speedMin; - float speedMax; - - // Acceleration along the x and y axes. - love::Vector linearAccelerationMin; - love::Vector linearAccelerationMax; - - // Acceleration towards the emitter's center - float radialAccelerationMin; - float radialAccelerationMax; - - // Acceleration perpendicular to the particle's direction. - float tangentialAccelerationMin; - float tangentialAccelerationMax; - - float linearDampingMin; - float linearDampingMax; - - // Size. - std::vector sizes; - float sizeVariation; - - // Rotation - float rotationMin; - float rotationMax; - - // Spin. - float spinStart; - float spinEnd; - float spinVariation; - - // Offsets - love::Vector offset; - - // Is the ParticleSystem using a default offset? - bool defaultOffset; - - // Color. - std::vector colors; - - // Quads. - std::vector> quads; - - bool relativeRotation; - - void resetOffset(); - - void createBuffers(size_t size); - void deleteBuffers(); - - void addParticle(float t); - Particle *removeParticle(Particle *p); - - // Called by addParticle. - void initParticle(Particle *p, float t); - void insertTop(Particle *p); - void insertBottom(Particle *p); - void insertRandom(Particle *p); - - static StringMap::Entry distributionsEntries[]; - static StringMap distributions; - - static StringMap::Entry insertModesEntries[]; - static StringMap insertModes; }; } // opengl