499 lines
12 KiB
C++
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());
|
|
}
|
|
}
|
|
}
|