diff --git a/include/Nazara/Graphics/Enums.hpp b/include/Nazara/Graphics/Enums.hpp index 59a8104c4..1f19f079e 100644 --- a/include/Nazara/Graphics/Enums.hpp +++ b/include/Nazara/Graphics/Enums.hpp @@ -43,6 +43,40 @@ enum nzMaterialUniform nzMaterialUniform_Max = nzMaterialUniform_SpecularMap }; +enum nzParticleComponent +{ + nzParticleComponent_Unused = -1, + + nzParticleComponent_Color, + nzParticleComponent_Life, + nzParticleComponent_Normal, + nzParticleComponent_Position, + nzParticleComponent_Radius, + nzParticleComponent_Rotation, + nzParticleComponent_Size, + nzParticleComponent_Velocity, + nzParticleComponent_Userdata0, + nzParticleComponent_Userdata1, + nzParticleComponent_Userdata2, + nzParticleComponent_Userdata3, + nzParticleComponent_Userdata4, + nzParticleComponent_Userdata5, + nzParticleComponent_Userdata6, + nzParticleComponent_Userdata7, + nzParticleComponent_Userdata8, + + nzParticleComponent_Max = nzParticleComponent_Userdata8 +}; + +enum nzParticleLayout +{ + nzParticleLayout_Billboard, + nzParticleLayout_Model, + nzParticleLayout_Sprite, + + nzParticleLayout_Max = nzParticleLayout_Sprite +}; + enum nzRenderPassType { nzRenderPassType_AA, @@ -71,10 +105,11 @@ enum nzRenderTechniqueType enum nzSceneNodeType { - nzSceneNodeType_Light, // NzLight - nzSceneNodeType_Model, // NzModel - nzSceneNodeType_Root, // NzSceneRoot - nzSceneNodeType_Sprite, // NzSprite + nzSceneNodeType_Light, // NzLight + nzSceneNodeType_Model, // NzModel + nzSceneNodeType_ParticleEmitter, // NzParticleEmitter + nzSceneNodeType_Root, // NzSceneRoot + nzSceneNodeType_Sprite, // NzSprite nzSceneNodeType_User, nzSceneNodeType_Max = nzSceneNodeType_User diff --git a/include/Nazara/Graphics/ParticleController.hpp b/include/Nazara/Graphics/ParticleController.hpp new file mode 100644 index 000000000..13d8f765f --- /dev/null +++ b/include/Nazara/Graphics/ParticleController.hpp @@ -0,0 +1,31 @@ +// 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_PARTICLECONTROLLER_HPP +#define NAZARA_PARTICLECONTROLLER_HPP + +#include +#include +#include + +class NzParticleController; +class NzParticleEmitter; +class NzParticleMapper; + +using NzParticleControllerConstRef = NzResourceRef; +using NzParticleControllerRef = NzResourceRef; + +class NAZARA_API NzParticleController : public NzResource +{ + public: + NzParticleController() = default; + NzParticleController(const NzParticleController& controller); + virtual ~NzParticleController(); + + virtual void Apply(NzParticleEmitter& emitter, NzParticleMapper& mapper, unsigned int offset, unsigned int particleCount, float elapsedTime) = 0; +}; + +#endif // NAZARA_PARTICLECONTROLLER_HPP diff --git a/include/Nazara/Graphics/ParticleDeclaration.hpp b/include/Nazara/Graphics/ParticleDeclaration.hpp new file mode 100644 index 000000000..af2cfd7e3 --- /dev/null +++ b/include/Nazara/Graphics/ParticleDeclaration.hpp @@ -0,0 +1,67 @@ +// 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_PARTICLEDECLARATION_HPP +#define NAZARA_PARTICLEDECLARATION_HPP + +#include +#include +#include +#include +#include + +class NzParticleDeclaration; + +using NzParticleDeclarationConstRef = NzResourceRef; +using NzParticleDeclarationRef = NzResourceRef; + +class NAZARA_API NzParticleDeclaration : public NzResource +{ + friend class NzGraphics; + + public: + NzParticleDeclaration(); + NzParticleDeclaration(const NzParticleDeclaration& declaration); + ~NzParticleDeclaration(); + + void DisableComponent(nzParticleComponent component); + void EnableComponent(nzParticleComponent component, nzComponentType type, unsigned int offset); + + void GetComponent(nzParticleComponent component, bool* enabled, nzComponentType* type, unsigned int* offset) const; + unsigned int GetStride() const; + + void SetStride(unsigned int stride); + + NzParticleDeclaration& operator=(const NzParticleDeclaration& declaration); + + static NzParticleDeclaration* Get(nzParticleLayout layout); + static bool IsTypeSupported(nzComponentType type); + + private: + static bool Initialize(); + static void Uninitialize(); + + struct Component + { + nzComponentType type; + bool enabled = false; + unsigned int offset; + + /* + ** -Lynix: + ** Il serait aussi possible de préciser le stride de façon indépendante, ce que je ne permets pas + ** pour décomplexifier l'interface en enlevant quelque chose que je juge inutile. + ** Si vous pensez que ça peut être utile, n'hésitez pas à me le faire savoir ! + */ + }; + + Component m_components[nzParticleComponent_Max+1]; + unsigned int m_stride; + + static NzParticleDeclaration s_declarations[nzParticleLayout_Max+1]; +}; + +#endif // NAZARA_PARTICLEDECLARATION_HPP diff --git a/include/Nazara/Graphics/ParticleEmitter.hpp b/include/Nazara/Graphics/ParticleEmitter.hpp new file mode 100644 index 000000000..47d6a6b0b --- /dev/null +++ b/include/Nazara/Graphics/ParticleEmitter.hpp @@ -0,0 +1,91 @@ +// 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_PARTICLEEMITTER_HPP +#define NAZARA_PARTICLEEMITTER_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class NAZARA_API NzParticleEmitter : public NzSceneNode, NzUpdatable +{ + public: + NzParticleEmitter(unsigned int maxParticleCount, nzParticleLayout layout); + NzParticleEmitter(unsigned int maxParticleCount, NzParticleDeclaration* declaration); + NzParticleEmitter(const NzParticleEmitter& emitter); + NzParticleEmitter(NzParticleEmitter&& emitter) = default; + ~NzParticleEmitter(); + + void AddController(NzParticleController* controller); + void AddGenerator(NzParticleGenerator* generator); + void AddToRenderQueue(NzAbstractRenderQueue* renderQueue) const; + + void* CreateParticle(); + void* CreateParticles(unsigned int count); + + void* GenerateParticle(); + void* GenerateParticles(unsigned int count); + + const NzBoundingVolumef& GetBoundingVolume() const override; + + unsigned int GetEmissionCount() const; + float GetEmissionRate() const; + unsigned int GetMaxParticleCount() const; + unsigned int GetParticleCount() const; + unsigned int GetParticleSize() const; + + nzSceneNodeType GetSceneNodeType() const override; + + bool IsDrawable() 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 SetRenderer(NzParticleRenderer* renderer); + + NzParticleEmitter& operator=(const NzParticleEmitter& emitter); + NzParticleEmitter& operator=(NzParticleEmitter&& emitter); + + 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_generators; + mutable NzBoundingVolumef m_boundingVolume; + NzParticleDeclarationConstRef m_declaration; + NzParticleRendererRef m_renderer; + mutable bool m_boundingVolumeUpdated; + bool m_processing; + float m_emissionAccumulator; + float m_emissionRate; + 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 new file mode 100644 index 000000000..0e8e8eb77 --- /dev/null +++ b/include/Nazara/Graphics/ParticleGenerator.hpp @@ -0,0 +1,31 @@ +// 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_PARTICLEGENERATOR_HPP +#define NAZARA_PARTICLEGENERATOR_HPP + +#include +#include +#include + +class NzParticleEmitter; +class NzParticleGenerator; +class NzParticleMapper; + +using NzParticleGeneratorConstRef = NzResourceRef; +using NzParticleGeneratorRef = NzResourceRef; + +class NAZARA_API NzParticleGenerator : public NzResource +{ + public: + NzParticleGenerator() = default; + NzParticleGenerator(const NzParticleGenerator& generator); + virtual ~NzParticleGenerator(); + + virtual void Generate(NzParticleEmitter& emitter, NzParticleMapper& mapper, unsigned int offset, unsigned int particleCount) = 0; +}; + +#endif // NAZARA_PARTICLEGENERATOR_HPP diff --git a/include/Nazara/Graphics/ParticleMapper.hpp b/include/Nazara/Graphics/ParticleMapper.hpp new file mode 100644 index 000000000..ac4b8dccf --- /dev/null +++ b/include/Nazara/Graphics/ParticleMapper.hpp @@ -0,0 +1,31 @@ +// 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_PARTICLEMAPPER_HPP +#define NAZARA_PARTICLEMAPPER_HPP + +#include +#include +#include +#include + +class NAZARA_API NzParticleMapper +{ + public: + NzParticleMapper(void* buffer, const NzParticleDeclaration* declaration); + ~NzParticleMapper(); + + template NzSparsePtr GetComponentPtr(nzParticleComponent component); + template NzSparsePtr GetComponentPtr(nzParticleComponent component) const; + + private: + const NzParticleDeclaration* m_declaration; + nzUInt8* m_ptr; +}; + +#include + +#endif // NAZARA_PARTICLEMAPPER_HPP diff --git a/include/Nazara/Graphics/ParticleMapper.inl b/include/Nazara/Graphics/ParticleMapper.inl new file mode 100644 index 000000000..b2dfdc1b2 --- /dev/null +++ b/include/Nazara/Graphics/ParticleMapper.inl @@ -0,0 +1,50 @@ +// 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 + +template +NzSparsePtr NzParticleMapper::GetComponentPtr(nzParticleComponent component) +{ + // Ensuite le composant qui nous intéresse + bool enabled; + nzComponentType type; + unsigned int offset; + m_declaration->GetComponent(component, &enabled, &type, &offset); + + if (enabled) + { + ///TODO: Vérifier le rapport entre le type de l'attribut et le type template ? + return NzSparsePtr(m_ptr + offset, m_declaration->GetStride()); + } + else + { + NazaraError("Attribute 0x" + NzString::Number(component, 16) + " is not enabled"); + return NzSparsePtr(); + } +} + +template +NzSparsePtr NzParticleMapper::GetComponentPtr(nzParticleComponent component) const +{ + // Ensuite le composant qui nous intéresse + bool enabled; + nzComponentType type; + unsigned int offset; + m_declaration->GetComponent(component, &enabled, &type, &offset); + + if (enabled) + { + ///TODO: Vérifier le rapport entre le type de l'attribut et le type template ? + return NzSparsePtr(m_ptr + offset, m_declaration->GetStride()); + } + else + { + NazaraError("Attribute 0x" + NzString::Number(component, 16) + " is not enabled"); + return NzSparsePtr(); + } +} + +#include diff --git a/include/Nazara/Graphics/ParticleRenderer.hpp b/include/Nazara/Graphics/ParticleRenderer.hpp new file mode 100644 index 000000000..ea85f3a87 --- /dev/null +++ b/include/Nazara/Graphics/ParticleRenderer.hpp @@ -0,0 +1,32 @@ +// 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_PARTICLERENDERER_HPP +#define NAZARA_PARTICLERENDERER_HPP + +#include +#include +#include + +class NzAbstractRenderQueue; +class NzParticleEmitter; +class NzParticleMapper; +class NzParticleRenderer; + +using NzParticleRendererConstRef = NzResourceRef; +using NzParticleRendererRef = NzResourceRef; + +class NAZARA_API NzParticleRenderer : public NzResource +{ + public: + NzParticleRenderer() = default; + NzParticleRenderer(const NzParticleRenderer& renderer); + virtual ~NzParticleRenderer(); + + virtual void Render(const NzParticleEmitter& emitter, const NzParticleMapper& mapper, unsigned int offset, unsigned int particleCount, NzAbstractRenderQueue* renderQueue) = 0; +}; + +#endif // NAZARA_PARTICLERENDERER_HPP diff --git a/include/Nazara/Graphics/ParticleStruct.hpp b/include/Nazara/Graphics/ParticleStruct.hpp new file mode 100644 index 000000000..d023b1470 --- /dev/null +++ b/include/Nazara/Graphics/ParticleStruct.hpp @@ -0,0 +1,42 @@ +// 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_PARTICLESTRUCT_HPP +#define NAZARA_PARTICLESTRUCT_HPP + +#include +#include +#include +#include + +struct NzParticleStruct_Billboard +{ + NzColor color; + NzVector3f normal; + NzVector3f position; + NzVector3f velocity; + nzUInt32 life; + float rotation; +}; + +struct NzParticleStruct_Model +{ + NzVector3f position; + NzVector3f velocity; + nzUInt32 life; + NzQuaternionf rotation; +}; + +struct NzParticleStruct_Sprite +{ + NzColor color; + NzVector2f position; + NzVector2f velocity; + nzUInt32 life; + float rotation; +}; + +#endif // NAZARA_PARTICLESTRUCT_HPP diff --git a/src/Nazara/Graphics/Graphics.cpp b/src/Nazara/Graphics/Graphics.cpp index db8e9c619..1058e484b 100644 --- a/src/Nazara/Graphics/Graphics.cpp +++ b/src/Nazara/Graphics/Graphics.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,12 @@ bool NzGraphics::Initialize() return false; } + if (!NzParticleDeclaration::Initialize()) + { + NazaraError("Failed to initialize particle declarations"); + return false; + } + if (!NzSkinningManager::Initialize()) { NazaraError("Failed to initialize skinning manager"); @@ -98,6 +105,7 @@ void NzGraphics::Uninitialize() NzDeferredRenderTechnique::Uninitialize(); NzMaterial::Uninitialize(); + NzParticleDeclaration::Uninitialize(); NzSkinningManager::Uninitialize(); NazaraNotice("Uninitialized: Graphics module"); diff --git a/src/Nazara/Graphics/ParticleController.cpp b/src/Nazara/Graphics/ParticleController.cpp new file mode 100644 index 000000000..695fd7d5c --- /dev/null +++ b/src/Nazara/Graphics/ParticleController.cpp @@ -0,0 +1,14 @@ +// 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 + +NzParticleController::NzParticleController(const NzParticleController& controller) : +NzResource() +{ + NazaraUnused(controller); +} + +NzParticleController::~NzParticleController() = default; diff --git a/src/Nazara/Graphics/ParticleDeclaration.cpp b/src/Nazara/Graphics/ParticleDeclaration.cpp new file mode 100644 index 000000000..b2ab27d68 --- /dev/null +++ b/src/Nazara/Graphics/ParticleDeclaration.cpp @@ -0,0 +1,231 @@ +// 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 +#include +#include + +NzParticleDeclaration::NzParticleDeclaration() : +m_stride(0) +{ +} + +NzParticleDeclaration::NzParticleDeclaration(const NzParticleDeclaration& declaration) : +NzResource(), +m_stride(declaration.m_stride) +{ + std::memcpy(m_components, declaration.m_components, sizeof(Component)*(nzParticleComponent_Max+1)); +} + +NzParticleDeclaration::~NzParticleDeclaration() +{ + NotifyDestroy(); +} + +void NzParticleDeclaration::DisableComponent(nzParticleComponent component) +{ + #ifdef NAZARA_DEBUG + if (component > nzParticleComponent_Max) + { + NazaraError("Vertex component out of enum"); + return; + } + #endif + + #if NAZARA_GRAPHICS_SAFE + if (component == nzParticleComponent_Unused) + { + NazaraError("Cannot disable \"unused\" component"); + return; + } + #endif + + Component& vertexComponent = m_components[component]; + if (vertexComponent.enabled) + { + vertexComponent.enabled = false; + m_stride -= NzUtility::ComponentStride[vertexComponent.type]; + } +} + +void NzParticleDeclaration::EnableComponent(nzParticleComponent component, nzComponentType type, unsigned int offset) +{ + #ifdef NAZARA_DEBUG + if (component > nzParticleComponent_Max) + { + NazaraError("Vertex component out of enum"); + return; + } + #endif + + #if NAZARA_GRAPHICS_SAFE + if (!IsTypeSupported(type)) + { + NazaraError("Component type 0x" + NzString::Number(type, 16) + " is not supported by particle declarations"); + return; + } + #endif + + if (component != nzParticleComponent_Unused) + { + Component& particleComponent = m_components[component]; + if (particleComponent.enabled) + m_stride -= NzUtility::ComponentStride[particleComponent.type]; + else + particleComponent.enabled = true; + + particleComponent.offset = offset; + particleComponent.type = type; + } + + m_stride += NzUtility::ComponentStride[type]; +} + +void NzParticleDeclaration::GetComponent(nzParticleComponent component, bool* enabled, nzComponentType* type, unsigned int* offset) const +{ + #ifdef NAZARA_DEBUG + if (component > nzParticleComponent_Max) + { + NazaraError("Particle component out of enum"); + return; + } + #endif + + #if NAZARA_GRAPHICS_SAFE + if (component == nzParticleComponent_Unused) + { + NazaraError("Cannot get \"unused\" component"); + return; + } + #endif + + const Component& particleComponent = m_components[component]; + + if (enabled) + *enabled = particleComponent.enabled; + + if (type) + *type = particleComponent.type; + + if (offset) + *offset = particleComponent.offset; +} + +unsigned int NzParticleDeclaration::GetStride() const +{ + return m_stride; +} + +void NzParticleDeclaration::SetStride(unsigned int stride) +{ + m_stride = stride; +} + +NzParticleDeclaration& NzParticleDeclaration::operator=(const NzParticleDeclaration& declaration) +{ + std::memcpy(m_components, declaration.m_components, sizeof(Component)*(nzParticleComponent_Max+1)); + m_stride = declaration.m_stride; + + return *this; +} + +NzParticleDeclaration* NzParticleDeclaration::Get(nzParticleLayout layout) +{ + #ifdef NAZARA_DEBUG + if (layout > nzParticleLayout_Max) + { + NazaraError("Particle layout out of enum"); + return nullptr; + } + #endif + + return &s_declarations[layout]; +} + +bool NzParticleDeclaration::IsTypeSupported(nzComponentType type) +{ + switch (type) + { + case nzComponentType_Color: + case nzComponentType_Double1: + case nzComponentType_Double2: + case nzComponentType_Double3: + case nzComponentType_Double4: + case nzComponentType_Float1: + case nzComponentType_Float2: + case nzComponentType_Float3: + case nzComponentType_Float4: + case nzComponentType_Int1: + case nzComponentType_Int2: + case nzComponentType_Int3: + case nzComponentType_Int4: + case nzComponentType_Quaternion: + return true; + } + + NazaraError("Component type not handled (0x" + NzString::Number(type, 16) + ')'); + return false; +} + +bool NzParticleDeclaration::Initialize() +{ + try + { + NzErrorFlags flags(nzErrorFlag_Silent | nzErrorFlag_ThrowException); + + // Layout : Type + NzParticleDeclaration* declaration; + + // nzParticleLayout_Billboard : NzParticleStruct_Billboard + declaration = &s_declarations[nzParticleLayout_Billboard]; + declaration->EnableComponent(nzParticleComponent_Color, nzComponentType_Color, NzOffsetOf(NzParticleStruct_Billboard, color)); + declaration->EnableComponent(nzParticleComponent_Life, nzComponentType_Int1, NzOffsetOf(NzParticleStruct_Billboard, life)); + declaration->EnableComponent(nzParticleComponent_Normal, nzComponentType_Float3, NzOffsetOf(NzParticleStruct_Billboard, normal)); + declaration->EnableComponent(nzParticleComponent_Position, nzComponentType_Float3, NzOffsetOf(NzParticleStruct_Billboard, position)); + declaration->EnableComponent(nzParticleComponent_Rotation, nzComponentType_Float1, NzOffsetOf(NzParticleStruct_Billboard, rotation)); + declaration->EnableComponent(nzParticleComponent_Velocity, nzComponentType_Float3, NzOffsetOf(NzParticleStruct_Billboard, velocity)); + + NazaraAssert(declaration->GetStride() == sizeof(NzParticleStruct_Billboard), "Invalid stride for declaration nzParticleLayout_Billboard"); + + // nzParticleLayout_Model : NzParticleStruct_Model + declaration = &s_declarations[nzParticleLayout_Model]; + declaration->EnableComponent(nzParticleComponent_Life, nzComponentType_Int1, NzOffsetOf(NzParticleStruct_Model, life)); + declaration->EnableComponent(nzParticleComponent_Position, nzComponentType_Float3, NzOffsetOf(NzParticleStruct_Model, position)); + declaration->EnableComponent(nzParticleComponent_Rotation, nzComponentType_Quaternion, NzOffsetOf(NzParticleStruct_Model, rotation)); + declaration->EnableComponent(nzParticleComponent_Velocity, nzComponentType_Float3, NzOffsetOf(NzParticleStruct_Model, velocity)); + + NazaraAssert(declaration->GetStride() == sizeof(NzParticleStruct_Model), "Invalid stride for declaration nzParticleLayout_Model"); + + // nzParticleLayout_Sprite : NzParticleStruct_Sprite + declaration = &s_declarations[nzParticleLayout_Sprite]; + declaration->EnableComponent(nzParticleComponent_Color, nzComponentType_Color, NzOffsetOf(NzParticleStruct_Sprite, color)); + declaration->EnableComponent(nzParticleComponent_Life, nzComponentType_Int1, NzOffsetOf(NzParticleStruct_Sprite, life)); + declaration->EnableComponent(nzParticleComponent_Position, nzComponentType_Float2, NzOffsetOf(NzParticleStruct_Sprite, position)); + declaration->EnableComponent(nzParticleComponent_Rotation, nzComponentType_Float1, NzOffsetOf(NzParticleStruct_Sprite, rotation)); + declaration->EnableComponent(nzParticleComponent_Velocity, nzComponentType_Float2, NzOffsetOf(NzParticleStruct_Sprite, velocity)); + + NazaraAssert(declaration->GetStride() == sizeof(NzParticleStruct_Sprite), "Invalid stride for declaration nzParticleLayout_Sprite"); + } + catch (const std::exception& e) + { + NazaraError("Failed to initialize particle declarations: " + NzString(e.what())); + return false; + } + + return true; +} + +void NzParticleDeclaration::Uninitialize() +{ + // Rien à faire +} + +NzParticleDeclaration NzParticleDeclaration::s_declarations[nzParticleLayout_Max+1]; diff --git a/src/Nazara/Graphics/ParticleEmitter.cpp b/src/Nazara/Graphics/ParticleEmitter.cpp new file mode 100644 index 000000000..48162155b --- /dev/null +++ b/src/Nazara/Graphics/ParticleEmitter.cpp @@ -0,0 +1,361 @@ +// 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 + +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_emissionAccumulator(0.f), +m_emissionRate(0.f), +m_emissionCount(1), +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(); +} + +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_processing(false), +m_emissionAccumulator(0.f), +m_emissionRate(emitter.m_emissionRate), +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) +{ + 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 + NzParticleMapper mapper(m_buffer.data(), m_declaration); + + m_renderer->Render(*this, mapper, 0, m_particleCount, 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::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); + + 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; +} + +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; +} + +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::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_generators = emitter.m_generators; + m_maxParticleCount = emitter.m_maxParticleCount; + m_particleCount = emitter.m_particleCount; + m_particleSize = emitter.m_particleSize; + m_renderer = emitter.m_renderer; + + // 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_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; +} + +NzParticleEmitter& NzParticleEmitter::operator=(NzParticleEmitter&& emitter) +{ + NzErrorFlags flags(nzErrorFlag_ThrowException, true); + + NzSceneNode::operator=(emitter); + + m_boundingVolume = std::move(emitter.m_boundingVolume); + m_boundingVolumeUpdated = std::move(emitter.m_boundingVolumeUpdated); + m_buffer = std::move(emitter.m_buffer); + m_controllers = std::move(emitter.m_controllers); + m_declaration = std::move(emitter.m_declaration); + m_dyingParticles = std::move(emitter.m_dyingParticles); + m_emissionAccumulator = std::move(emitter.m_emissionAccumulator); + m_emissionCount = std::move(emitter.m_emissionCount); + m_emissionRate = std::move(emitter.m_emissionRate); + m_generators = std::move(emitter.m_generators); + m_maxParticleCount = std::move(emitter.m_maxParticleCount); + m_particleCount = std::move(emitter.m_particleCount); + m_particleSize = std::move(emitter.m_particleSize); + m_processing = std::move(emitter.m_processing); + m_renderer = std::move(emitter.m_renderer); + + 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) + m_emissionAccumulator += elapsedTime*m_emissionRate; + + float emissionCount = std::floor(m_emissionAccumulator); // Le nombre d'émissions de cette mise à jour + m_emissionAccumulator -= emissionCount; // On enlève la partie entière + + if (emissionCount >= 1.f) + { + // On calcule le nombre maximum de particules pouvant être émises cette fois-ci + 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); + + // Et on émet nos particules + GenerateParticles(particleCount); + } + } + + NzParticleMapper mapper(m_buffer.data(), m_declaration); + + m_processing = true; + + // Pour éviter un verrouillage en cas d'exception + NzCallOnExit onExit([this]() + { + m_processing = false; + }); + + for (NzParticleController* controller : m_controllers) + controller->Apply(*this, mapper, 0, m_particleCount, 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(); +} diff --git a/src/Nazara/Graphics/ParticleGenerator.cpp b/src/Nazara/Graphics/ParticleGenerator.cpp new file mode 100644 index 000000000..846e2c1ac --- /dev/null +++ b/src/Nazara/Graphics/ParticleGenerator.cpp @@ -0,0 +1,14 @@ +// 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 + +NzParticleGenerator::NzParticleGenerator(const NzParticleGenerator& generator) : +NzResource() +{ + NazaraUnused(generator); +} + +NzParticleGenerator::~NzParticleGenerator() = default; diff --git a/src/Nazara/Graphics/ParticleMapper.cpp b/src/Nazara/Graphics/ParticleMapper.cpp new file mode 100644 index 000000000..a73a53349 --- /dev/null +++ b/src/Nazara/Graphics/ParticleMapper.cpp @@ -0,0 +1,15 @@ +// 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 + +NzParticleMapper::NzParticleMapper(void* buffer, const NzParticleDeclaration* declaration) : +m_declaration(declaration), +m_ptr(static_cast(buffer)) +{ +} + +NzParticleMapper::~NzParticleMapper() = default; diff --git a/src/Nazara/Graphics/ParticleRenderer.cpp b/src/Nazara/Graphics/ParticleRenderer.cpp new file mode 100644 index 000000000..77f9e64c9 --- /dev/null +++ b/src/Nazara/Graphics/ParticleRenderer.cpp @@ -0,0 +1,14 @@ +// 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 + +NzParticleRenderer::NzParticleRenderer(const NzParticleRenderer& renderer) : +NzResource() +{ + NazaraUnused(renderer); +} + +NzParticleRenderer::~NzParticleRenderer() = default;