diff --git a/include/Nazara/Graphics/ParticleController.hpp b/include/Nazara/Graphics/ParticleController.hpp index 4456bcc28..eddea25f7 100644 --- a/include/Nazara/Graphics/ParticleController.hpp +++ b/include/Nazara/Graphics/ParticleController.hpp @@ -12,8 +12,8 @@ #include class NzParticleController; -class NzParticleEmitter; class NzParticleMapper; +class NzParticleSystem; using NzParticleControllerConstRef = NzResourceRef; using NzParticleControllerRef = NzResourceRef; @@ -25,7 +25,7 @@ class NAZARA_API NzParticleController : public NzResource NzParticleController(const NzParticleController& controller); virtual ~NzParticleController(); - virtual void Apply(NzParticleEmitter& emitter, NzParticleMapper& mapper, unsigned int startId, unsigned int endId, float elapsedTime) = 0; + virtual void Apply(NzParticleSystem& system, NzParticleMapper& mapper, unsigned int startId, unsigned int endId, float elapsedTime) = 0; }; #endif // NAZARA_PARTICLECONTROLLER_HPP diff --git a/include/Nazara/Graphics/ParticleEmitter.hpp b/include/Nazara/Graphics/ParticleEmitter.hpp index ac85c9480..5e73426d5 100644 --- a/include/Nazara/Graphics/ParticleEmitter.hpp +++ b/include/Nazara/Graphics/ParticleEmitter.hpp @@ -8,92 +8,36 @@ #define NAZARA_PARTICLEEMITTER_HPP #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include -class NAZARA_API NzParticleEmitter : public NzSceneNode, NzUpdatable +class NzParticleMapper; +class NzParticleSystem; + +class NAZARA_API NzParticleEmitter : public NzNode { public: - NzParticleEmitter(unsigned int maxParticleCount, nzParticleLayout layout); - NzParticleEmitter(unsigned int maxParticleCount, NzParticleDeclaration* declaration); - NzParticleEmitter(const NzParticleEmitter& emitter); + NzParticleEmitter(); + NzParticleEmitter(const NzParticleEmitter& emitter) = default; NzParticleEmitter(NzParticleEmitter&& emitter) = default; - ~NzParticleEmitter(); + virtual ~NzParticleEmitter(); - void AddController(NzParticleController* controller); - void AddGenerator(NzParticleGenerator* generator); - void AddToRenderQueue(NzAbstractRenderQueue* renderQueue) const; - - void* CreateParticle(); - void* CreateParticles(unsigned int count); - - void EnableFixedStep(bool fixedStep); - - void* GenerateParticle(); - void* GenerateParticles(unsigned int count); - - const NzBoundingVolumef& GetBoundingVolume() const override; + virtual void Emit(NzParticleSystem& system, float elapsedTime) const; unsigned int GetEmissionCount() const; float GetEmissionRate() const; - float GetFixedStepSize() const; - unsigned int GetMaxParticleCount() const; - unsigned int GetParticleCount() const; - unsigned int GetParticleSize() const; - - nzSceneNodeType GetSceneNodeType() const override; - - bool IsDrawable() const; - bool IsFixedStepEnabled() const; - - void KillParticle(unsigned int index); - void KillParticles(); - - void RemoveController(NzParticleController* controller); - void RemoveGenerator(NzParticleGenerator* generator); void SetEmissionCount(unsigned int count); void SetEmissionRate(float rate); - void SetFixedStepSize(float stepSize); - void SetRenderer(NzParticleRenderer* renderer); - NzParticleEmitter& operator=(const NzParticleEmitter& emitter); + NzParticleEmitter& operator=(const NzParticleEmitter& emitter) = default; NzParticleEmitter& operator=(NzParticleEmitter&& emitter) = default; private: - void GenerateAABB() const; - void Register() override; - void ResizeBuffer(); - void Unregister() override; - void UpdateBoundingVolume() const; - void Update() override; + virtual void SetupParticles(NzParticleMapper& mapper, unsigned int count) const = 0; - std::set> m_dyingParticles; - mutable std::vector m_buffer; - std::vector m_controllers; - std::vector m_generators; - mutable NzBoundingVolumef m_boundingVolume; - NzParticleDeclarationConstRef m_declaration; - NzParticleRendererRef m_renderer; - mutable bool m_boundingVolumeUpdated; - bool m_fixedStepEnabled; - bool m_processing; - float m_emissionAccumulator; + mutable float m_emissionAccumulator; float m_emissionRate; - float m_stepAccumulator; - float m_stepSize; unsigned int m_emissionCount; - unsigned int m_maxParticleCount; - unsigned int m_particleCount; - unsigned int m_particleSize; }; #endif // NAZARA_PARTICLEEMITTER_HPP diff --git a/include/Nazara/Graphics/ParticleGenerator.hpp b/include/Nazara/Graphics/ParticleGenerator.hpp index eefb9233b..79a787c44 100644 --- a/include/Nazara/Graphics/ParticleGenerator.hpp +++ b/include/Nazara/Graphics/ParticleGenerator.hpp @@ -11,9 +11,9 @@ #include #include -class NzParticleEmitter; class NzParticleGenerator; class NzParticleMapper; +class NzParticleSystem; using NzParticleGeneratorConstRef = NzResourceRef; using NzParticleGeneratorRef = NzResourceRef; @@ -25,7 +25,7 @@ class NAZARA_API NzParticleGenerator : public NzResource NzParticleGenerator(const NzParticleGenerator& generator); virtual ~NzParticleGenerator(); - virtual void Generate(NzParticleEmitter& emitter, NzParticleMapper& mapper, unsigned int startId, unsigned int endId) = 0; + virtual void Generate(NzParticleSystem& system, NzParticleMapper& mapper, unsigned int startId, unsigned int endId) = 0; }; #endif // NAZARA_PARTICLEGENERATOR_HPP diff --git a/include/Nazara/Graphics/ParticleRenderer.hpp b/include/Nazara/Graphics/ParticleRenderer.hpp index 81219c624..4ad832310 100644 --- a/include/Nazara/Graphics/ParticleRenderer.hpp +++ b/include/Nazara/Graphics/ParticleRenderer.hpp @@ -12,9 +12,9 @@ #include class NzAbstractRenderQueue; -class NzParticleEmitter; class NzParticleMapper; class NzParticleRenderer; +class NzParticleSystem; using NzParticleRendererConstRef = NzResourceRef; using NzParticleRendererRef = NzResourceRef; @@ -26,7 +26,7 @@ class NAZARA_API NzParticleRenderer : public NzResource NzParticleRenderer(const NzParticleRenderer& renderer); virtual ~NzParticleRenderer(); - virtual void Render(const NzParticleEmitter& emitter, const NzParticleMapper& mapper, unsigned int startId, unsigned int endId, NzAbstractRenderQueue* renderQueue) = 0; + virtual void Render(const NzParticleSystem& system, const NzParticleMapper& mapper, unsigned int startId, unsigned int endId, NzAbstractRenderQueue* renderQueue) = 0; }; #endif // NAZARA_PARTICLERENDERER_HPP diff --git a/include/Nazara/Graphics/ParticleSystem.hpp b/include/Nazara/Graphics/ParticleSystem.hpp new file mode 100644 index 000000000..647d15ce0 --- /dev/null +++ b/include/Nazara/Graphics/ParticleSystem.hpp @@ -0,0 +1,95 @@ +// Copyright (C) 2014 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 + +#pragma once + +#ifndef NAZARA_PARTICLESYSTEM_HPP +#define NAZARA_PARTICLESYSTEM_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class NAZARA_API NzParticleSystem : public NzSceneNode, NzUpdatable +{ + public: + NzParticleSystem(unsigned int maxParticleCount, nzParticleLayout layout); + NzParticleSystem(unsigned int maxParticleCount, const NzParticleDeclaration* declaration); + NzParticleSystem(const NzParticleSystem& emitter); + NzParticleSystem(NzParticleSystem&& emitter) = default; + ~NzParticleSystem(); + + void AddController(NzParticleController* controller); + void AddEmitter(NzParticleEmitter* emitter); + void AddGenerator(NzParticleGenerator* generator); + void AddToRenderQueue(NzAbstractRenderQueue* renderQueue) const; + + void* CreateParticle(); + void* CreateParticles(unsigned int count); + + void EnableFixedStep(bool fixedStep); + + void* GenerateParticle(); + void* GenerateParticles(unsigned int count); + + const NzBoundingVolumef& GetBoundingVolume() const override; + const NzParticleDeclaration* GetDeclaration() const; + float GetFixedStepSize() const; + unsigned int GetMaxParticleCount() const; + unsigned int GetParticleCount() const; + unsigned int GetParticleSize() const; + nzSceneNodeType GetSceneNodeType() const override; + + bool IsDrawable() const; + bool IsFixedStepEnabled() const; + + void KillParticle(unsigned int index); + void KillParticles(); + + void RemoveController(NzParticleController* controller); + void RemoveEmitter(NzParticleEmitter* emitter); + void RemoveGenerator(NzParticleGenerator* generator); + + void SetFixedStepSize(float stepSize); + void SetRenderer(NzParticleRenderer* renderer); + + NzParticleSystem& operator=(const NzParticleSystem& emitter); + NzParticleSystem& operator=(NzParticleSystem&& emitter) = default; + + private: + void GenerateAABB() const; + void Register() override; + void ResizeBuffer(); + void Unregister() override; + void UpdateBoundingVolume() const; + void Update() override; + + std::set> m_dyingParticles; + mutable std::vector m_buffer; + std::vector m_controllers; + std::vector m_emitters; + std::vector m_generators; + mutable NzBoundingVolumef m_boundingVolume; + NzParticleDeclarationConstRef m_declaration; + NzParticleRendererRef m_renderer; + mutable bool m_boundingVolumeUpdated; + bool m_fixedStepEnabled; + bool m_processing; + float m_stepAccumulator; + float m_stepSize; + unsigned int m_maxParticleCount; + unsigned int m_particleCount; + unsigned int m_particleSize; +}; + +#endif // NAZARA_PARTICLESYSTEM_HPP diff --git a/src/Nazara/Graphics/ParticleEmitter.cpp b/src/Nazara/Graphics/ParticleEmitter.cpp index 8671091bc..46500f082 100644 --- a/src/Nazara/Graphics/ParticleEmitter.cpp +++ b/src/Nazara/Graphics/ParticleEmitter.cpp @@ -7,319 +7,22 @@ #include #include #include +#include #include #include #include -NzParticleEmitter::NzParticleEmitter(unsigned int maxParticleCount, nzParticleLayout layout) : -NzParticleEmitter(maxParticleCount, NzParticleDeclaration::Get(layout)) -{ -} - -NzParticleEmitter::NzParticleEmitter(unsigned int maxParticleCount, NzParticleDeclaration* declaration) : -m_declaration(declaration), -m_boundingVolumeUpdated(false), -m_fixedStepEnabled(false), -m_processing(false), +NzParticleEmitter::NzParticleEmitter() : m_emissionAccumulator(0.f), m_emissionRate(0.f), -m_stepAccumulator(0.f), -m_stepSize(1.f/60.f), -m_emissionCount(1), -m_maxParticleCount(maxParticleCount), -m_particleCount(0) +m_emissionCount(1) { - // En cas d'erreur, un constructeur ne peut que lancer une exception - NzErrorFlags flags(nzErrorFlag_ThrowException, true); - - m_particleSize = m_declaration->GetStride(); // La taille de chaque particule - - ResizeBuffer(); -} - -NzParticleEmitter::NzParticleEmitter(const NzParticleEmitter& emitter) : -NzSceneNode(emitter), -m_controllers(emitter.m_controllers), -m_generators(emitter.m_generators), -m_boundingVolume(emitter.m_boundingVolume), -m_declaration(emitter.m_declaration), -m_renderer(emitter.m_renderer), -m_boundingVolumeUpdated(emitter.m_boundingVolumeUpdated), -m_fixedStepEnabled(emitter.m_fixedStepEnabled), -m_processing(false), -m_emissionAccumulator(0.f), -m_emissionRate(emitter.m_emissionRate), -m_stepAccumulator(0.f), -m_stepSize(emitter.m_stepSize), -m_emissionCount(emitter.m_emissionCount), -m_maxParticleCount(emitter.m_maxParticleCount), -m_particleCount(emitter.m_particleCount), -m_particleSize(emitter.m_particleSize) -{ - NzErrorFlags flags(nzErrorFlag_ThrowException, true); - - ResizeBuffer(); - - // On ne copie que les particules vivantes - std::memcpy(m_buffer.data(), emitter.m_buffer.data(), emitter.m_particleCount*m_particleSize); } NzParticleEmitter::~NzParticleEmitter() = default; -void NzParticleEmitter::AddController(NzParticleController* controller) +void NzParticleEmitter::Emit(NzParticleSystem& system, float elapsedTime) const { - m_controllers.emplace_back(controller); -} - -void NzParticleEmitter::AddGenerator(NzParticleGenerator* generator) -{ - m_generators.emplace_back(generator); -} - -void NzParticleEmitter::AddToRenderQueue(NzAbstractRenderQueue* renderQueue) const -{ - ///FIXME: Vérifier le renderer - if (m_particleCount > 0) - { - NzParticleMapper mapper(m_buffer.data(), m_declaration); - m_renderer->Render(*this, mapper, 0, m_particleCount-1, renderQueue); - } -} - -void* NzParticleEmitter::CreateParticle() -{ - return CreateParticles(1); -} - -void* NzParticleEmitter::CreateParticles(unsigned int count) -{ - if (m_particleCount+count > m_maxParticleCount) - return nullptr; - - unsigned int particlesIndex = m_particleCount; - m_particleCount += count; - - return &m_buffer[particlesIndex*m_particleSize]; -} - -void NzParticleEmitter::EnableFixedStep(bool fixedStep) -{ - // On teste pour empêcher que cette méthode ne remette systématiquement le step accumulator à zéro - if (m_fixedStepEnabled != fixedStep) - { - m_fixedStepEnabled = fixedStep; - m_stepAccumulator = 0.f; - } -} - -void* NzParticleEmitter::GenerateParticle() -{ - return GenerateParticles(1); -} - -void* NzParticleEmitter::GenerateParticles(unsigned int count) -{ - void* ptr = CreateParticles(count); - if (!ptr) - return nullptr; - - NzParticleMapper mapper(ptr, m_declaration); - for (NzParticleGenerator* generator : m_generators) - generator->Generate(*this, mapper, 0, m_particleCount-1); - - return ptr; -} - -const NzBoundingVolumef& NzParticleEmitter::GetBoundingVolume() const -{ - if (!m_boundingVolumeUpdated) - UpdateBoundingVolume(); - - return m_boundingVolume; -} - -unsigned int NzParticleEmitter::GetEmissionCount() const -{ - return m_emissionCount; -} - -float NzParticleEmitter::GetEmissionRate() const -{ - return m_emissionRate; -} - -float NzParticleEmitter::GetFixedStepSize() const -{ - return m_stepSize; -} - -unsigned int NzParticleEmitter::GetMaxParticleCount() const -{ - return m_maxParticleCount; -} - -unsigned int NzParticleEmitter::GetParticleCount() const -{ - return m_particleCount; -} - -unsigned int NzParticleEmitter::GetParticleSize() const -{ - return m_particleSize; -} - -nzSceneNodeType NzParticleEmitter::GetSceneNodeType() const -{ - return nzSceneNodeType_ParticleEmitter; -} - -bool NzParticleEmitter::IsDrawable() const -{ - return true; -} - -bool NzParticleEmitter::IsFixedStepEnabled() const -{ - return m_fixedStepEnabled; -} - -void NzParticleEmitter::KillParticle(unsigned int index) -{ - ///FIXME: Vérifier index - - if (m_processing) - { - // Le buffer est en train d'être modifié, nous ne pouvons pas réduire sa taille, on place alors la particule dans une liste de secours - m_dyingParticles.insert(index); - return; - } - - // On déplace la dernière particule vivante à la place de celle-ci - if (--m_particleCount > 0) - std::memcpy(&m_buffer[index*m_particleSize], &m_buffer[m_particleCount*m_particleSize], m_particleSize); -} - -void NzParticleEmitter::KillParticles() -{ - m_particleCount = 0; -} - -void NzParticleEmitter::RemoveController(NzParticleController* controller) -{ - auto it = std::find(m_controllers.begin(), m_controllers.end(), controller); - if (it != m_controllers.end()) - m_controllers.erase(it); -} - -void NzParticleEmitter::RemoveGenerator(NzParticleGenerator* generator) -{ - auto it = std::find(m_generators.begin(), m_generators.end(), generator); - if (it != m_generators.end()) - m_generators.erase(it); -} - -void NzParticleEmitter::SetEmissionCount(unsigned int count) -{ - m_emissionCount = count; -} - -void NzParticleEmitter::SetEmissionRate(float rate) -{ - m_emissionRate = rate; -} - -void NzParticleEmitter::SetFixedStepSize(float stepSize) -{ - m_stepSize = stepSize; -} - -void NzParticleEmitter::SetRenderer(NzParticleRenderer* renderer) -{ - m_renderer = renderer; -} - -NzParticleEmitter& NzParticleEmitter::operator=(const NzParticleEmitter& emitter) -{ - NzErrorFlags flags(nzErrorFlag_ThrowException, true); - - NzSceneNode::operator=(emitter); - - m_boundingVolume = emitter.m_boundingVolume; - m_boundingVolumeUpdated = emitter.m_boundingVolumeUpdated; - m_controllers = emitter.m_controllers; - m_declaration = emitter.m_declaration; - m_emissionCount = emitter.m_emissionCount; - m_emissionRate = emitter.m_emissionRate; - m_fixedStepEnabled = emitter.m_fixedStepEnabled; - m_generators = emitter.m_generators; - m_maxParticleCount = emitter.m_maxParticleCount; - m_particleCount = emitter.m_particleCount; - m_particleSize = emitter.m_particleSize; - m_renderer = emitter.m_renderer; - m_stepSize = emitter.m_stepSize; - - // La copie ne peut pas (ou plutôt ne devrait pas) avoir lieu pendant une mise à jour, inutile de copier - m_dyingParticles.clear(); - m_emissionAccumulator = 0.f; - m_processing = false; - m_stepAccumulator = 0.f; - - m_buffer.clear(); // Pour éviter une recopie lors du resize() qui ne servira pas à grand chose - ResizeBuffer(); - - // On ne copie que les particules vivantes - std::memcpy(m_buffer.data(), emitter.m_buffer.data(), emitter.m_particleCount*m_particleSize); - - return *this; -} - -void NzParticleEmitter::GenerateAABB() const -{ - m_boundingVolume.MakeInfinite(); -} - -void NzParticleEmitter::Register() -{ - m_scene->RegisterForUpdate(this); -} - -void NzParticleEmitter::ResizeBuffer() -{ - // Histoire de décrire un peu mieux l'erreur en cas d'échec - try - { - m_buffer.resize(m_maxParticleCount*m_particleSize); - } - catch (const std::exception& e) - { - NzStringStream stream; - stream << "Failed to allocate particle buffer (" << e.what() << ") for " << m_maxParticleCount << " particles of size " << m_particleSize; - - NazaraError(stream.ToString()); - } -} - -void NzParticleEmitter::Unregister() -{ - m_scene->UnregisterForUpdate(this); -} - -void NzParticleEmitter::UpdateBoundingVolume() const -{ - if (m_boundingVolume.IsNull()) - GenerateAABB(); - - if (!m_transformMatrixUpdated) - UpdateTransformMatrix(); - - m_boundingVolume.Update(m_transformMatrix); - m_boundingVolumeUpdated = true; -} - -void NzParticleEmitter::Update() -{ - float elapsedTime = m_scene->GetUpdateTime(); - if (m_emissionRate > 0.f) { // On accumule la partie réelle (pour éviter qu'un taux d'update élevé empêche des particules de se former) @@ -334,57 +37,33 @@ void NzParticleEmitter::Update() unsigned int maxParticleCount = static_cast(emissionCount)*m_emissionCount; // On récupère le nombre de particules qu'il est possible de créer selon l'espace libre - unsigned int particleCount = std::min(maxParticleCount, m_maxParticleCount - m_particleCount); + unsigned int particleCount = std::min(maxParticleCount, system.GetMaxParticleCount() - system.GetParticleCount()); // Et on émet nos particules - GenerateParticles(particleCount); + void* particles = system.GenerateParticles(particleCount); + NzParticleMapper mapper(particles, system.GetDeclaration()); + + SetupParticles(mapper, particleCount); } } - - if (m_particleCount > 0) - { - NzParticleMapper mapper(m_buffer.data(), m_declaration); - - m_processing = true; - - // Pour éviter un verrouillage en cas d'exception - NzCallOnExit onExit([this]() - { - m_processing = false; - }); - - if (m_fixedStepEnabled) - { - m_stepAccumulator += elapsedTime; - while (m_stepAccumulator >= m_stepSize) - { - for (NzParticleController* controller : m_controllers) - controller->Apply(*this, mapper, 0, m_particleCount-1, m_stepAccumulator); - - m_stepAccumulator -= m_stepSize; - } - } - else - { - for (NzParticleController* controller : m_controllers) - controller->Apply(*this, mapper, 0, m_particleCount-1, elapsedTime); - } - - m_processing = false; - onExit.Reset(); - - // On tue maintenant les particules mortes durant la mise à jour - if (m_dyingParticles.size() < m_particleCount) - { - // On tue les particules depuis la dernière vers la première (en terme de place), le std::set étant trié via std::greater - // La raison est simple, étant donné que la mort d'une particule signifie le déplacement de la dernière particule du buffer, - // sans cette solution certaines particules pourraient échapper à la mort - for (unsigned int index : m_dyingParticles) - KillParticle(index); - } - else - KillParticles(); // Toutes les particules sont mortes, ceci est beaucoup plus rapide - - m_dyingParticles.clear(); - } +} + +unsigned int NzParticleEmitter::GetEmissionCount() const +{ + return m_emissionCount; +} + +float NzParticleEmitter::GetEmissionRate() const +{ + return m_emissionRate; +} + +void NzParticleEmitter::SetEmissionCount(unsigned int count) +{ + m_emissionCount = count; +} + +void NzParticleEmitter::SetEmissionRate(float rate) +{ + m_emissionRate = rate; } diff --git a/src/Nazara/Graphics/ParticleSystem.cpp b/src/Nazara/Graphics/ParticleSystem.cpp new file mode 100644 index 000000000..a9bbc565b --- /dev/null +++ b/src/Nazara/Graphics/ParticleSystem.cpp @@ -0,0 +1,366 @@ +// Copyright (C) 2014 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 +#include +#include +#include +#include +#include +#include +#include + +NzParticleSystem::NzParticleSystem(unsigned int maxParticleCount, nzParticleLayout layout) : +NzParticleSystem(maxParticleCount, NzParticleDeclaration::Get(layout)) +{ +} + +NzParticleSystem::NzParticleSystem(unsigned int maxParticleCount, const NzParticleDeclaration* declaration) : +m_declaration(declaration), +m_boundingVolumeUpdated(false), +m_fixedStepEnabled(false), +m_processing(false), +m_stepAccumulator(0.f), +m_stepSize(1.f/60.f), +m_maxParticleCount(maxParticleCount), +m_particleCount(0) +{ + // En cas d'erreur, un constructeur ne peut que lancer une exception + NzErrorFlags flags(nzErrorFlag_ThrowException, true); + + m_particleSize = m_declaration->GetStride(); // La taille de chaque particule + + ResizeBuffer(); +} + +NzParticleSystem::NzParticleSystem(const NzParticleSystem& system) : +NzSceneNode(system), +m_controllers(system.m_controllers), +m_generators(system.m_generators), +m_boundingVolume(system.m_boundingVolume), +m_declaration(system.m_declaration), +m_renderer(system.m_renderer), +m_boundingVolumeUpdated(system.m_boundingVolumeUpdated), +m_fixedStepEnabled(system.m_fixedStepEnabled), +m_processing(false), +m_stepAccumulator(0.f), +m_stepSize(system.m_stepSize), +m_maxParticleCount(system.m_maxParticleCount), +m_particleCount(system.m_particleCount), +m_particleSize(system.m_particleSize) +{ + NzErrorFlags flags(nzErrorFlag_ThrowException, true); + + ResizeBuffer(); + + // On ne copie que les particules vivantes + std::memcpy(m_buffer.data(), system.m_buffer.data(), system.m_particleCount*m_particleSize); +} + +NzParticleSystem::~NzParticleSystem() = default; + +void NzParticleSystem::AddController(NzParticleController* controller) +{ + m_controllers.emplace_back(controller); +} + +void NzParticleSystem::AddEmitter(NzParticleEmitter* emitter) +{ + m_emitters.emplace_back(emitter); +} + +void NzParticleSystem::AddGenerator(NzParticleGenerator* generator) +{ + m_generators.emplace_back(generator); +} + +void NzParticleSystem::AddToRenderQueue(NzAbstractRenderQueue* renderQueue) const +{ + ///FIXME: Vérifier le renderer + if (m_particleCount > 0) + { + NzParticleMapper mapper(m_buffer.data(), m_declaration); + m_renderer->Render(*this, mapper, 0, m_particleCount-1, renderQueue); + } +} + +void* NzParticleSystem::CreateParticle() +{ + return CreateParticles(1); +} + +void* NzParticleSystem::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]; +} + +void NzParticleSystem::EnableFixedStep(bool fixedStep) +{ + // On teste pour empêcher que cette méthode ne remette systématiquement le step accumulator à zéro + if (m_fixedStepEnabled != fixedStep) + { + m_fixedStepEnabled = fixedStep; + m_stepAccumulator = 0.f; + } +} + +void* NzParticleSystem::GenerateParticle() +{ + return GenerateParticles(1); +} + +void* NzParticleSystem::GenerateParticles(unsigned int count) +{ + void* ptr = CreateParticles(count); + if (!ptr) + return nullptr; + + NzParticleMapper mapper(ptr, m_declaration); + for (NzParticleGenerator* generator : m_generators) + generator->Generate(*this, mapper, 0, m_particleCount-1); + + return ptr; +} + +const NzBoundingVolumef& NzParticleSystem::GetBoundingVolume() const +{ + if (!m_boundingVolumeUpdated) + UpdateBoundingVolume(); + + return m_boundingVolume; +} + +const NzParticleDeclaration* NzParticleSystem::GetDeclaration() const +{ + return m_declaration; +} + +float NzParticleSystem::GetFixedStepSize() const +{ + return m_stepSize; +} + +unsigned int NzParticleSystem::GetMaxParticleCount() const +{ + return m_maxParticleCount; +} + +unsigned int NzParticleSystem::GetParticleCount() const +{ + return m_particleCount; +} + +unsigned int NzParticleSystem::GetParticleSize() const +{ + return m_particleSize; +} + +nzSceneNodeType NzParticleSystem::GetSceneNodeType() const +{ + return nzSceneNodeType_ParticleEmitter; +} + +bool NzParticleSystem::IsDrawable() const +{ + return true; +} + +bool NzParticleSystem::IsFixedStepEnabled() const +{ + return m_fixedStepEnabled; +} + +void NzParticleSystem::KillParticle(unsigned int index) +{ + ///FIXME: Vérifier index + + if (m_processing) + { + // Le buffer est en train d'être modifié, nous ne pouvons pas réduire sa taille, on place alors la particule dans une liste de secours + m_dyingParticles.insert(index); + return; + } + + // On déplace la dernière particule vivante à la place de celle-ci + if (--m_particleCount > 0) + std::memcpy(&m_buffer[index*m_particleSize], &m_buffer[m_particleCount*m_particleSize], m_particleSize); +} + +void NzParticleSystem::KillParticles() +{ + m_particleCount = 0; +} + +void NzParticleSystem::RemoveController(NzParticleController* controller) +{ + auto it = std::find(m_controllers.begin(), m_controllers.end(), controller); + if (it != m_controllers.end()) + m_controllers.erase(it); +} + +void NzParticleSystem::RemoveEmitter(NzParticleEmitter* emitter) +{ + auto it = std::find(m_emitters.begin(), m_emitters.end(), emitter); + if (it != m_emitters.end()) + m_emitters.erase(it); +} + +void NzParticleSystem::RemoveGenerator(NzParticleGenerator* generator) +{ + auto it = std::find(m_generators.begin(), m_generators.end(), generator); + if (it != m_generators.end()) + m_generators.erase(it); +} + +void NzParticleSystem::SetFixedStepSize(float stepSize) +{ + m_stepSize = stepSize; +} + +void NzParticleSystem::SetRenderer(NzParticleRenderer* renderer) +{ + m_renderer = renderer; +} + +NzParticleSystem& NzParticleSystem::operator=(const NzParticleSystem& system) +{ + NzErrorFlags flags(nzErrorFlag_ThrowException, true); + + NzSceneNode::operator=(system); + + m_boundingVolume = system.m_boundingVolume; + m_boundingVolumeUpdated = system.m_boundingVolumeUpdated; + m_controllers = system.m_controllers; + m_declaration = system.m_declaration; + m_fixedStepEnabled = system.m_fixedStepEnabled; + 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; + + // La copie ne peut pas (ou plutôt ne devrait pas) avoir lieu pendant une mise à jour, inutile de copier + m_dyingParticles.clear(); + m_processing = false; + m_stepAccumulator = 0.f; + + m_buffer.clear(); // Pour éviter une recopie lors du resize() qui ne servira pas à grand chose + ResizeBuffer(); + + // On ne copie que les particules vivantes + std::memcpy(m_buffer.data(), system.m_buffer.data(), system.m_particleCount*m_particleSize); + + return *this; +} + +void NzParticleSystem::GenerateAABB() const +{ + m_boundingVolume.MakeInfinite(); +} + +void NzParticleSystem::Register() +{ + m_scene->RegisterForUpdate(this); +} + +void NzParticleSystem::ResizeBuffer() +{ + // Histoire de décrire un peu mieux l'erreur en cas d'échec + try + { + m_buffer.resize(m_maxParticleCount*m_particleSize); + } + catch (const std::exception& e) + { + NzStringStream stream; + stream << "Failed to allocate particle buffer (" << e.what() << ") for " << m_maxParticleCount << " particles of size " << m_particleSize; + + NazaraError(stream.ToString()); + } +} + +void NzParticleSystem::Unregister() +{ + m_scene->UnregisterForUpdate(this); +} + +void NzParticleSystem::UpdateBoundingVolume() const +{ + if (m_boundingVolume.IsNull()) + GenerateAABB(); + + if (!m_transformMatrixUpdated) + UpdateTransformMatrix(); + + m_boundingVolume.Update(m_transformMatrix); + m_boundingVolumeUpdated = true; +} + +void NzParticleSystem::Update() +{ + float elapsedTime = m_scene->GetUpdateTime(); + + // Émission + for (NzParticleEmitter* emitter : m_emitters) + emitter->Emit(*this, elapsedTime); + + // Mise à jour + if (m_particleCount > 0) + { + NzParticleMapper mapper(m_buffer.data(), m_declaration); + + m_processing = true; + + // Pour éviter un verrouillage en cas d'exception + NzCallOnExit onExit([this]() + { + m_processing = false; + }); + + if (m_fixedStepEnabled) + { + m_stepAccumulator += elapsedTime; + while (m_stepAccumulator >= m_stepSize) + { + for (NzParticleController* controller : m_controllers) + controller->Apply(*this, mapper, 0, m_particleCount-1, m_stepAccumulator); + + m_stepAccumulator -= m_stepSize; + } + } + else + { + for (NzParticleController* controller : m_controllers) + controller->Apply(*this, mapper, 0, m_particleCount-1, elapsedTime); + } + + m_processing = false; + onExit.Reset(); + + // On tue maintenant les particules mortes durant la mise à jour + if (m_dyingParticles.size() < m_particleCount) + { + // On tue les particules depuis la dernière vers la première (en terme de place), le std::set étant trié via std::greater + // La raison est simple, étant donné que la mort d'une particule signifie le déplacement de la dernière particule du buffer, + // sans cette solution certaines particules pourraient échapper à la mort + for (unsigned int index : m_dyingParticles) + KillParticle(index); + } + else + KillParticles(); // Toutes les particules sont mortes, ceci est beaucoup plus rapide + + m_dyingParticles.clear(); + } +} +