NazaraEngine/src/Nazara/Graphics/ParticleGroup.cpp

499 lines
12 KiB
C++

// Copyright (C) 2015 Jérôme Leclercq
// This file is part of the "Nazara Engine - Graphics module"
// For conditions of distribution and use, see copyright notice in Config.hpp
#include <Nazara/Graphics/ParticleGroup.hpp>
#include <Nazara/Core/CallOnExit.hpp>
#include <Nazara/Core/ErrorFlags.hpp>
#include <Nazara/Core/StringStream.hpp>
#include <Nazara/Graphics/ParticleMapper.hpp>
#include <cstdlib>
#include <memory>
#include <Nazara/Graphics/Debug.hpp>
namespace Nz
{
/*!
* \ingroup graphics
* \class Nz::ParticleSystem
* \brief Graphics class that represents the system to handle particles
*/
/*!
* \brief Constructs a ParticleSystem object with a maximal number of particles and a layout
*
* \param maxParticleCount Maximum number of particles to generate
* \param layout Enumeration for the layout of data information for the particles
*/
ParticleGroup::ParticleGroup(unsigned int maxParticleCount, ParticleLayout layout) :
ParticleGroup(maxParticleCount, ParticleDeclaration::Get(layout))
{
}
/*!
* \brief Constructs a ParticleSystem object with a maximal number of particles and a particle declaration
*
* \param maxParticleCount Maximum number of particles to generate
* \param declaration Data information for the particles
*/
ParticleGroup::ParticleGroup(unsigned int maxParticleCount, ParticleDeclarationConstRef declaration) :
m_declaration(std::move(declaration)),
m_processing(false),
m_maxParticleCount(maxParticleCount),
m_particleCount(0)
{
// In case of error, the constructor can only throw an exception
ErrorFlags flags(ErrorFlag_ThrowException, true);
m_particleSize = m_declaration->GetStride(); // The size of each particle
ResizeBuffer();
}
/*!
* \brief Constructs a ParticleSystem object by assignation
*
* \param system ParticleSystem to copy into this
*/
ParticleGroup::ParticleGroup(const ParticleGroup& system) :
Renderable(system),
m_controllers(system.m_controllers),
m_generators(system.m_generators),
m_declaration(system.m_declaration),
m_renderer(system.m_renderer),
m_processing(false),
m_maxParticleCount(system.m_maxParticleCount),
m_particleCount(system.m_particleCount),
m_particleSize(system.m_particleSize)
{
ErrorFlags flags(ErrorFlag_ThrowException, true);
ResizeBuffer();
// We only copy alive particles
std::memcpy(m_buffer.data(), system.m_buffer.data(), system.m_particleCount*m_particleSize);
}
ParticleGroup::~ParticleGroup() = default;
/*!
* \brief Adds a controller to the particles
*
* \param controller Controller for the particles
*
* \remark Produces a NazaraAssert if controller is invalid
*/
void ParticleGroup::AddController(ParticleControllerRef controller)
{
NazaraAssert(controller, "Invalid particle controller");
m_controllers.emplace_back(std::move(controller));
}
/*!
* \brief Adds an emitter to the particles
*
* \param emitter Emitter for the particles
*
* \remark Produces a NazaraAssert if emitter is invalid
*/
void ParticleGroup::AddEmitter(ParticleEmitter* emitter)
{
NazaraAssert(emitter, "Invalid particle emitter");
m_emitters.emplace_back(emitter);
}
/*!
* \brief Adds a generator to the particles
*
* \param generator Generator for the particles
*
* \remark Produces a NazaraAssert if generator is invalid
*/
void ParticleGroup::AddGenerator(ParticleGeneratorRef generator)
{
NazaraAssert(generator, "Invalid particle generator");
m_generators.emplace_back(std::move(generator));
}
/*!
* \brief Adds the particle system to the rendering queue
*
* \param renderQueue Queue to be added
* \param transformMatrix Transformation matrix for the system
*
* \remark Produces a NazaraAssert if inner renderer is invalid
* \remark Produces a NazaraAssert if renderQueue is invalid
*/
void ParticleGroup::AddToRenderQueue(AbstractRenderQueue* renderQueue, const Matrix4f& transformMatrix) const
{
NazaraAssert(m_renderer, "Invalid particle renderer");
NazaraAssert(renderQueue, "Invalid renderqueue");
NazaraUnused(transformMatrix);
if (m_particleCount > 0)
{
ParticleMapper mapper(m_buffer.data(), m_declaration);
m_renderer->Render(*this, mapper, 0, m_particleCount - 1, renderQueue);
}
}
/*!
* \brief Applies the controllers
*
* \param mapper Mapper containing layout information of each particle
* \param particleCount Number of particles
* \param elapsedTime Delta time between the previous frame
*/
void ParticleGroup::ApplyControllers(ParticleMapper& mapper, unsigned int particleCount, float elapsedTime)
{
m_processing = true;
// To avoid a lock in case of exception
CallOnExit onExit([this]()
{
m_processing = false;
});
for (ParticleController* controller : m_controllers)
controller->Apply(*this, mapper, 0, particleCount - 1, elapsedTime);
onExit.CallAndReset();
// We only kill now the dead particles during the update
if (m_dyingParticles.size() < m_particleCount)
{
// We kill them in reverse order, std::set sorting them via std::greater
// The reason is simple, as the death of a particle means the move of the last particle in the buffer,
// without this solution, certain particles could avoid the death
for (unsigned int index : m_dyingParticles)
KillParticle(index);
}
else
KillParticles(); // Every particles are dead, this is way faster
m_dyingParticles.clear();
}
/*!
* \brief Creates one particle
* \return Pointer to the particle memory buffer
*/
void* ParticleGroup::CreateParticle()
{
return CreateParticles(1);
}
/*!
* \brief Creates multiple particles
* \return Pointer to the first particle memory buffer
*/
void* ParticleGroup::CreateParticles(unsigned int count)
{
if (count == 0)
return nullptr;
if (m_particleCount + count > m_maxParticleCount)
return nullptr;
unsigned int particlesIndex = m_particleCount;
m_particleCount += count;
return &m_buffer[particlesIndex * m_particleSize];
}
/*!
* \brief Generates one particle
* \return Pointer to the particle memory buffer
*/
void* ParticleGroup::GenerateParticle()
{
return GenerateParticles(1);
}
/*!
* \brief Generates multiple particles
* \return Pointer to the first particle memory buffer
*/
void* ParticleGroup::GenerateParticles(unsigned int count)
{
void* ptr = CreateParticles(count);
if (!ptr)
return nullptr;
ParticleMapper mapper(ptr, m_declaration);
for (ParticleGenerator* generator : m_generators)
generator->Generate(*this, mapper, 0, count - 1);
return ptr;
}
/*!
* \brief Gets the particle declaration
* \return Particle declaration
*/
const ParticleDeclarationConstRef& ParticleGroup::GetDeclaration() const
{
return m_declaration;
}
/*!
* \brief Gets the fixed step size
* \return Current fixed step size
*/
float ParticleGroup::GetFixedStepSize() const
{
return m_stepSize;
}
/*!
* \brief Gets the maximum number of particles
* \return Current maximum number
*/
unsigned int ParticleGroup::GetMaxParticleCount() const
{
return m_maxParticleCount;
}
/*!
* \brief Gets the number of particles
* \return Current number
*/
unsigned int ParticleGroup::GetParticleCount() const
{
return m_particleCount;
}
/*!
* \brief Gets the size of particles
* \return Current size
*/
unsigned int ParticleGroup::GetParticleSize() const
{
return m_particleSize;
}
/*!
* \brief Checks whether the fixed step is enabled
* \return true If it is the case
*/
bool ParticleGroup::IsFixedStepEnabled() const
{
return m_fixedStepEnabled;
}
/*!
* \brief Kills one particle
*
* \param index Index of the particle
*/
void ParticleGroup::KillParticle(unsigned int index)
{
///FIXME: Verify the index
if (m_processing)
{
// The buffer is being modified, we can not reduce its size, we put the particle in the waiting list
m_dyingParticles.insert(index);
return;
}
// We move the last alive particle to the place of this one
if (--m_particleCount > 0)
std::memcpy(&m_buffer[index * m_particleSize], &m_buffer[m_particleCount * m_particleSize], m_particleSize);
}
/*!
* \brief Kills every particles
*/
void ParticleGroup::KillParticles()
{
m_particleCount = 0;
}
/*!
* \brief Removes a controller to the particles
*
* \param controller Controller for the particles to remove
*/
void ParticleGroup::RemoveController(ParticleController* controller)
{
auto it = std::find(m_controllers.begin(), m_controllers.end(), controller);
if (it != m_controllers.end())
m_controllers.erase(it);
}
/*!
* \brief Removes an emitter to the particles
*
* \param emitter Emitter for the particles to remove
*/
void ParticleGroup::RemoveEmitter(ParticleEmitter* emitter)
{
auto it = std::find(m_emitters.begin(), m_emitters.end(), emitter);
if (it != m_emitters.end())
m_emitters.erase(it);
}
/*!
* \brief Removes a generator to the particles
*
* \param generator Generator for the particles to remove
*/
void ParticleGroup::RemoveGenerator(ParticleGenerator* generator)
{
auto it = std::find(m_generators.begin(), m_generators.end(), generator);
if (it != m_generators.end())
m_generators.erase(it);
}
/*!
* \brief Sets the fixed step size
*
* \param stepSize Fixed step size
*/
void ParticleGroup::SetFixedStepSize(float stepSize)
{
m_stepSize = stepSize;
}
/*!
* \brief Sets the renderer of the particles
*
* \param renderer Renderer for the particles
*/
void ParticleGroup::SetRenderer(ParticleRenderer* renderer)
{
m_renderer = renderer;
}
/*!
* \brief Updates the system
*
* \param elapsedTime Delta time between the previous frame
*/
void ParticleGroup::Update(float elapsedTime)
{
// Emission
for (ParticleEmitter* emitter : m_emitters)
emitter->Emit(*this, elapsedTime);
// Update
if (m_particleCount > 0)
{
///TODO: Update using threads
ParticleMapper mapper(m_buffer.data(), m_declaration);
ApplyControllers(mapper, m_particleCount, elapsedTime);
}
}
/*!
* \brief Updates the bounding volume by a matrix
*
* \param transformMatrix Matrix transformation for our bounding volume
*/
void ParticleGroup::UpdateBoundingVolume(const Matrix4f& transformMatrix)
{
NazaraUnused(transformMatrix);
// Nothing to do here (our bounding volume is global)
}
/*!
* \brief Sets the current particle system with the content of the other one
* \return A reference to this
*
* \param system The other ParticleSystem
*/
ParticleGroup& ParticleGroup::operator=(const ParticleGroup& system)
{
ErrorFlags flags(ErrorFlag_ThrowException, true);
Renderable::operator=(system);
m_controllers = system.m_controllers;
m_declaration = system.m_declaration;
m_generators = system.m_generators;
m_maxParticleCount = system.m_maxParticleCount;
m_particleCount = system.m_particleCount;
m_particleSize = system.m_particleSize;
m_renderer = system.m_renderer;
m_stepSize = system.m_stepSize;
// The copy can not (or should not) happen during the update, there is no use to copy
m_dyingParticles.clear();
m_processing = false;
m_stepAccumulator = 0.f;
m_buffer.clear(); // To avoid a copy due to resize() which will be pointless
ResizeBuffer();
// We only copy alive particles
std::memcpy(m_buffer.data(), system.m_buffer.data(), system.m_particleCount * m_particleSize);
return *this;
}
/*!
* \brief Makes the bounding volume of this text
*/
void ParticleGroup::MakeBoundingVolume() const
{
///TODO: Compute the AABB (taking into account the size of particles)
m_boundingVolume.MakeInfinite();
}
/*!
* \brief Resizes the internal buffer
*
* \remark Produces a NazaraError if resize did not work
*/
void ParticleGroup::ResizeBuffer()
{
// Just to have a better description of our problem in case of error
try
{
m_buffer.resize(m_maxParticleCount*m_particleSize);
}
catch (const std::exception& e)
{
StringStream stream;
stream << "Failed to allocate particle buffer (" << e.what() << ") for " << m_maxParticleCount << " particles of size " << m_particleSize;
NazaraError(stream.ToString());
}
}
}