Merge pull request #56 from antopilo/feature/NK-146-particle-system

Feature/nk 146 particle system
This commit is contained in:
Antoine Pilote
2023-08-03 10:33:02 -04:00
committed by GitHub
20 changed files with 476 additions and 26 deletions

View File

@@ -0,0 +1,88 @@
#pragma once
#include "ComponentPanel.h"
#include <src/Core/FileSystem.h>
#include <src/Core/Maths.h>
#include <src/Scene/Components/ParticleEmitterComponent.h>
#include <src/Scene/Entities/ImGuiHelper.h>
class ParticleEmitterPanel : ComponentPanel
{
public:
ParticleEmitterPanel() {}
void Draw(Nuake::Entity entity) override
{
if (!entity.HasComponent<Nuake::ParticleEmitterComponent>())
return;
auto& component = entity.GetComponent<Nuake::ParticleEmitterComponent>();
BeginComponentTable(PARTICLE EMITTER, Nuake::ParticleEmitterComponent);
{
{
ImGui::Text("Particle Color");
ImGui::TableNextColumn();
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::ColorEdit3("##lightcolor", &component.ParticleColor.r, ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel);
ImGui::PopItemWidth();
ImGui::TableNextColumn();
ComponentTableReset(component.ParticleColor, Nuake::Vector4(1, 1, 1, 1));
}
ImGui::TableNextColumn();
{
ImGui::Text("Amount");
ImGui::TableNextColumn();
ImGui::DragFloat("##ParticleAmount", &component.Amount, 0.1f, 0.0f, 500.0f);
ImGui::TableNextColumn();
ComponentTableReset(component.Amount, 10.0f);
}
ImGui::TableNextColumn();
{
ImGui::Text("Rate");
ImGui::TableNextColumn();
ImGui::DragFloat("##ParticleRate", &component.Rate, 0.1f, 0.0f, 10.0f);
ImGui::TableNextColumn();
ComponentTableReset(component.Rate, 0.0f);
}
ImGui::TableNextColumn();
{
ImGui::Text("Life");
ImGui::TableNextColumn();
ImGui::DragFloat("##ParticleLife", &component.Life, 0.1f, 0.0f, 100.0f);
ImGui::TableNextColumn();
ComponentTableReset(component.Life, 5.0f);
}
ImGui::TableNextColumn();
{
ImGui::Text("Gravity");
ImGui::TableNextColumn();
ImGuiHelper::DrawVec3("Gravity", &component.Gravity);
ImGui::TableNextColumn();
ComponentTableReset(component.Gravity, Nuake::Vector3(0, -1, 0));
}
ImGui::TableNextColumn();
{
ImGui::Text("Gravity Random");
ImGui::TableNextColumn();
ImGui::DragFloat("##GravityRandom", &component.GravityRandom, 0.01f, 0.0f, 1.0f);
ImGui::TableNextColumn();
ComponentTableReset(component.GravityRandom, 0.0f);
}
ImGui::TableNextColumn();
{
ImGui::Text("Radius");
ImGui::TableNextColumn();
ImGui::DragFloat("##ParticleRadius", &component.Radius, 0.01f, 0.0f, 10.0f);
ImGui::TableNextColumn();
ComponentTableReset(component.Radius, 1.0f);
}
}
EndComponentTable();
}
};

View File

@@ -16,6 +16,7 @@
#include <src/Scene/Components/CylinderColliderComponent.h>
#include <src/Scene/Components/MeshCollider.h>
#include <src/Scene/Components/ModelComponent.h>
#include <src/Scene/Components/ParticleEmitterComponent.h>
GizmoDrawer::GizmoDrawer()
@@ -217,6 +218,18 @@ void GizmoDrawer::DrawGizmos(Ref<Scene> scene)
Nuake::RenderCommand::DrawLines(0, 264);
}
auto particleView = scene->m_Registry.view<TransformComponent, ParticleEmitterComponent>();
for (auto e : particleView)
{
auto [transform, particle] = scene->m_Registry.get<TransformComponent, ParticleEmitterComponent>(e);
mLineShader->Bind();
mLineShader->SetUniformMat4f("u_View", glm::scale(glm::translate(scene->m_EditorCamera->GetTransform(), transform.Translation), Vector3(particle.Radius)));
mLineShader->SetUniformMat4f("u_Projection", scene->m_EditorCamera->GetPerspective());
mCircleBuffer->Bind();
Nuake::RenderCommand::DrawLines(0, 128);
}
auto meshColliderView = scene->m_Registry.view<TransformComponent, MeshColliderComponent, ModelComponent>();
for (auto e : meshColliderView)
{

View File

@@ -11,6 +11,8 @@
#include <Engine.h>
#include "src/Scene/Components/WrenScriptComponent.h"
#include "src/Scene/Components/ParticleEmitterComponent.h"
EditorSelectionPanel::EditorSelectionPanel()
{
@@ -98,6 +100,7 @@ void EditorSelectionPanel::DrawEntity(Nuake::Entity entity)
mTransformPanel.Draw(entity);
mLightPanel.Draw(entity);
mScriptPanel.Draw(entity);
mParticleEmitterPanel.Draw(entity);
mSpritePanel.Draw(entity);
mMeshPanel.Draw(entity);
mQuakeMapPanel.Draw(entity);
@@ -129,6 +132,7 @@ void EditorSelectionPanel::DrawAddComponentMenu(Nuake::Entity entity)
MenuItemComponent("Light", Nuake::LightComponent)
MenuItemComponent("Model", Nuake::ModelComponent)
MenuItemComponent("Sprite", Nuake::SpriteComponent)
MenuItemComponent("Particle Emitter", Nuake::ParticleEmitterComponent)
ImGui::Separator();
MenuItemComponent("Character Controller", Nuake::CharacterControllerComponent)
MenuItemComponent("Rigid body", Nuake::RigidBodyComponent)

View File

@@ -19,9 +19,11 @@
#include "../ComponentsPanel/MeshColliderPanel.h"
#include "../ComponentsPanel/CharacterControllerPanel.h"
#include "../ComponentsPanel/SpritePanel.h"
#include "../ComponentsPanel/ParticleEmitterPanel.h"
class EditorSelectionPanel {
class EditorSelectionPanel
{
private:
TransformPanel mTransformPanel;
LightPanel mLightPanel;
@@ -37,6 +39,7 @@ private:
CylinderColliderPanel mCylinderColliderPanel;
SpritePanel mSpritePanel;
CharacterControllerPanel mCharacterControllerPanel;
ParticleEmitterPanel mParticleEmitterPanel;
Ref<Nuake::File> currentFile;
Ref<Nuake::Resource> selectedResource;

View File

@@ -53,22 +53,20 @@ namespace Nuake
s_CurrentWindow->Update(s_TimeStep);
// Play mode update all the entities, Editor does not.
if (Engine::IsPlayMode())
{
s_FixedUpdateDifference += s_TimeStep;
// Fixed update
while (s_FixedUpdateDifference >= s_FixedUpdateRate)
{
s_CurrentWindow->FixedUpdate(s_FixedUpdateDifference);
s_FixedUpdateDifference -= s_FixedUpdateRate;
}
}
else
if (!Engine::IsPlayMode())
{
GetCurrentScene()->EditorUpdate(s_TimeStep);
}
s_FixedUpdateDifference += s_TimeStep;
// Fixed update
while (s_FixedUpdateDifference >= s_FixedUpdateRate)
{
s_CurrentWindow->FixedUpdate(s_FixedUpdateDifference);
s_FixedUpdateDifference -= s_FixedUpdateRate;
}
}
Input::Update();

View File

@@ -82,14 +82,17 @@ namespace Nuake
m_LightsUniformBuffer = CreateRef<UniformBuffer>(128);
Ref<Material> material = MaterialManager::Get()->GetMaterial("default");
Ref<Material> defaultMaterial = CreateRef<Material>(Vector3{1, 1, 1});
defaultMaterial->SetName("white");
MaterialManager::Get()->RegisterMaterial(defaultMaterial);
CubeMesh = CreateRef<Mesh>();
CubeMesh->AddSurface(CubeVertices, CubeIndices);
CubeMesh->SetMaterial(material);
CubeMesh->SetMaterial(defaultMaterial);
QuadMesh = CreateRef<Mesh>();
QuadMesh->AddSurface(QuadVertices, { 0, 1, 2, 3, 4, 5 });
QuadMesh->SetMaterial(material);
QuadMesh->SetMaterial(defaultMaterial);
}
void Renderer::LoadShaders()

View File

@@ -3,9 +3,11 @@
#include "src/Scene/Components/BSPBrushComponent.h"
#include "src/Scene/Components/SpriteComponent.h"
#include "src/Scene/Components/ParticleEmitterComponent.h"
#include <GL\glew.h>
namespace Nuake
{
void SceneRenderer::Init()
@@ -229,6 +231,7 @@ namespace Nuake
Renderer::SubmitMesh(sprite.SpriteMesh, finalQuadTransform, (uint32_t)e);
}
Renderer::Flush(shader, true);
}
}
@@ -284,7 +287,7 @@ namespace Nuake
// Sprites
auto spriteView = scene.m_Registry.view<TransformComponent, SpriteComponent, VisibilityComponent>();
for (auto e : spriteView)
for (auto& e : spriteView)
{
auto [transform, sprite, visibility] = spriteView.get<TransformComponent, SpriteComponent, VisibilityComponent>(e);
@@ -314,6 +317,38 @@ namespace Nuake
Renderer::SubmitMesh(sprite.SpriteMesh, finalQuadTransform, (uint32_t)e);
}
Renderer::Flush(gBufferShader, false);
// Particles
auto particleEmitterView = scene.m_Registry.view<TransformComponent, ParticleEmitterComponent, VisibilityComponent>();
for (auto& e : particleEmitterView)
{
auto [transform, emitterComponent, visibility] = particleEmitterView.get<TransformComponent, ParticleEmitterComponent, VisibilityComponent>(e);
if (!visibility.Visible)
continue;
Vector3 oldColor = Renderer::QuadMesh->GetMaterial()->data.m_AlbedoColor;
auto initialTransform = transform.GetGlobalTransform();
for (auto& p : emitterComponent.Emitter.Particles)
{
Matrix4 particleTransform = initialTransform;
particleTransform = glm::inverse(mView);
// Translation
const Vector3& particleGlobalPosition = transform.GetGlobalPosition() + p.Position;
particleTransform[3] = Vector4(particleGlobalPosition, 1.0f);
// Scale
particleTransform = glm::scale(particleTransform, transform.GetGlobalScale());
Renderer::QuadMesh->GetMaterial()->data.u_HasAlbedo = 0;
Renderer::QuadMesh->GetMaterial()->data.m_AlbedoColor = p.Color;
Renderer::SubmitMesh(Renderer::QuadMesh, particleTransform, (uint32_t)e);
}
Renderer::Flush(gBufferShader, false);
Renderer::QuadMesh->GetMaterial()->data.m_AlbedoColor = oldColor;
}
}
}

View File

@@ -68,9 +68,7 @@ namespace Nuake
data.m_AlbedoColor = Vector3{ albedoColor.r, albedoColor.g, albedoColor.b };
m_Name = "New material";
m_Albedo = m_DefaultAlbedo;
m_Name = "default";
}
Material::~Material() {}

View File

@@ -11,8 +11,6 @@ namespace Nuake
class MaterialManager
{
private:
const std::string DEFAULT_MATERIAL = "resources/Textures/default/Default.png";
static Ref<MaterialManager> s_Instance;
std::map<std::string, Ref<Material>> m_Materials;
@@ -24,6 +22,7 @@ namespace Nuake
public:
const std::string DEFAULT_MATERIAL = "resources/Textures/default/Default.png";
std::string CurrentlyBoundedMaterial = "";
MaterialManager();

View File

@@ -19,6 +19,9 @@ using json = nlohmann::json;
SERIALIZE_VEC3(v) \
j[#v]["w"] = this->v.w;
#define DESERIALIZE_VEC4(v, p) \
p = Vector4(v["x"], v["y"], v["z"], v["w"]);
#define DESERIALIZE_VEC3(v, p) \
p = Vector3(v["x"], v["y"], v["z"]);

View File

@@ -0,0 +1,33 @@
#include "src/Scene/Components/ParticleEmitterComponent.h"
namespace Nuake
{
json ParticleEmitterComponent::Serialize()
{
BEGIN_SERIALIZE();
SERIALIZE_VEC4(ParticleColor);
SERIALIZE_VAL(Amount);
SERIALIZE_VAL(Life);
SERIALIZE_VAL(Rate);
SERIALIZE_VEC3(Gravity);
SERIALIZE_VAL(GravityRandom);
SERIALIZE_VAL(Radius);
END_SERIALIZE();
}
bool ParticleEmitterComponent::Deserialize(const std::string& str)
{
BEGIN_DESERIALIZE();
DESERIALIZE_VEC4(j["ParticleColor"], ParticleColor);
Amount = j["Amount"];
Life = j["Life"];
if (j.contains("Rate"))
{
Rate = j["Rate"];
}
DESERIALIZE_VEC3(j["Gravity"], Gravity);
GravityRandom = j["GravityRandom"];
Radius = j["Radius"];
return true;
}
}

View File

@@ -0,0 +1,34 @@
#pragma once
#include "src/Core/Core.h"
#include "src/Core/Maths.h"
#include "src/Resource/Serializable.h"
#include "src/Scene/Systems/ParticleEmitter.h"
namespace Nuake
{
class ParticleEmitterComponent
{
public:
ParticleEmitterComponent() = default;
~ParticleEmitterComponent() = default;
Color ParticleColor;
float Amount;
float Life = 1.0f;
float Rate = 0.0f;
Vector3 Gravity;
float GravityRandom;
// For now use a radius, later should use shape.
float Radius;
ParticleEmitter Emitter;
public:
json Serialize();
bool Deserialize(const std::string& str);
};
}

View File

@@ -15,7 +15,7 @@
#include "src/Scene/Components/BoxCollider.h"
#include "src/Scene/Components/CapsuleColliderComponent.h"
#include "src/Scene/Components/SpriteComponent.h"
#include "src/Scene/Components/ParticleEmitterComponent.h"
namespace Nuake
{
@@ -39,6 +39,8 @@ namespace Nuake
SERIALIZE_OBJECT_REF_LBL("VisibilityComponent", GetComponent<VisibilityComponent>())
if (HasComponent<CameraComponent>())
SERIALIZE_OBJECT_REF_LBL("CameraComponent", GetComponent<CameraComponent>())
if (HasComponent<ParticleEmitterComponent>())
SERIALIZE_OBJECT_REF_LBL("ParticleEmitterComponent", GetComponent<ParticleEmitterComponent>())
if (HasComponent<SpriteComponent>())
SERIALIZE_OBJECT_REF_LBL("SpriteComponent", GetComponent<SpriteComponent>())
if (HasComponent<QuakeMapComponent>())
@@ -82,6 +84,7 @@ namespace Nuake
}
DESERIALIZE_COMPONENT(NameComponent)
DESERIALIZE_COMPONENT(ParentComponent)
DESERIALIZE_COMPONENT(ParticleEmitterComponent)
DESERIALIZE_COMPONENT(SpriteComponent)
DESERIALIZE_COMPONENT(CameraComponent)
DESERIALIZE_COMPONENT(QuakeMapComponent)

View File

@@ -3,6 +3,7 @@
#include "src/Scene/Systems/PhysicsSystem.h"
#include "src/Scene/Systems/TransformSystem.h"
#include "src/Scene/Systems/QuakeMapBuilder.h"
#include "src/Scene/Systems/ParticleSystem.h"
#include "src/Rendering/SceneRenderer.h"
#include "Scene.h"
@@ -45,6 +46,7 @@ namespace Nuake {
m_Systems.push_back(CreateRef<ScriptingSystem>(this));
m_Systems.push_back(CreateRef<TransformSystem>(this));
m_Systems.push_back(CreateRef<PhysicsSystem>(this));
m_Systems.push_back(CreateRef<ParticleSystem>(this));
m_SceneRenderer = new SceneRenderer();
m_SceneRenderer->Init();
@@ -271,11 +273,15 @@ namespace Nuake {
for (auto& c : copyChildrens)
{
Logger::Log("Deleting entity " + std::to_string(c.GetHandle()));
DestroyEntity(c);
}
Logger::Log("Deleted entity" + std::to_string(entity.GetHandle()) + " - " + entity.GetComponent<NameComponent>().Name);
// Remove from ID to Entity cache
if (m_EntitiesIDMap.find(entity.GetComponent<NameComponent>().ID) != m_EntitiesIDMap.end())
{
m_EntitiesIDMap.erase(entity.GetComponent<NameComponent>().ID);
}
entity.Destroy();
m_Registry.shrink_to_fit();
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include "src/Core/Maths.h"
namespace Nuake
{
struct Particle
{
Vector3 Position;
Vector3 Velocity;
Color Color;
float Life;
};
}

View File

@@ -0,0 +1,87 @@
#include "ParticleEmitter.h"
namespace Nuake
{
ParticleEmitter::ParticleEmitter()
{
m_MT = std::mt19937(std::random_device()());
SetRadius(1.0f);
}
void ParticleEmitter::RecreateRandom()
{
}
void ParticleEmitter::SpawnParticle()
{
const bool canSpawnParticle = Particles.size() < Amount;
if (!canSpawnParticle)
{
return;
}
if (Rate == 0.0f || RateTimer > Rate)
{
const auto initialPosition = Vector3(m_Random(m_MT), m_Random(m_MT), m_Random(m_MT));
const auto initialVelocity = Vector3();
const auto initialColor = ParticleColor;
const float initialLife = Life;
Particles.push_back({
initialPosition,
initialVelocity,
initialColor,
initialLife
});
RateTimer = 0.0f;
}
}
void ParticleEmitter::Update(Timestep ts)
{
if (Rate != 0.0f)
{
RateTimer += ts;
}
std::vector<uint32_t> deletionQueue;
int i = 0;
for (auto& p : Particles)
{
p.Life -= ts;
if (p.Life <= 0.0f) // The particle has died.
{
deletionQueue.push_back(i);
}
i++;
}
// Delete all dead particles.
int shiftOffset = 0;
for (int d = deletionQueue.size() - 1; d > 0; d--)
{
// Erase shifts the elements
const auto it = d;
Particles.erase(Particles.begin() + it);
}
for (auto& p : Particles)
{
p.Velocity += Gravity * static_cast<float>(ts);
p.Position += p.Velocity * static_cast<float>(ts);
}
}
void ParticleEmitter::SetRadius(float radius)
{
if (Radius != radius)
{
Radius = radius;
m_Random = std::uniform_real_distribution<float>(-Radius, Radius);
}
}
}

View File

@@ -0,0 +1,42 @@
#pragma once
#include "Particle.h"
#include "src/Core/Core.h"
#include "src/Core/Maths.h"
#include "src/Core/Timestep.h"
#include "src/Rendering/Textures/Material.h"
#include <random>
namespace Nuake
{
class ParticleEmitter
{
public:
float Amount = 0.0f;
float Life = 5.0f;
Vector3 Gravity = Vector3(0, 0, 0);
float GravityRandom = 0.0f;
float Radius = 1.0f;
float Rate = 0.0f;
Color ParticleColor;
private:
float RateTimer = 0.0f;
std::mt19937 m_MT;
std::uniform_real_distribution<float> m_Random;
void RecreateRandom();
public:
ParticleEmitter();
~ParticleEmitter() = default;
void SpawnParticle();
void Update(Timestep ts);
void SetRadius(float radius);
std::vector<Particle> Particles;
};
}

View File

@@ -0,0 +1,64 @@
#include "ParticleSystem.h"
#include "src/Scene/Scene.h"
#include "src/Scene/Entities/Entity.h"
#include "src/Scene/Components/ParticleEmitterComponent.h"
namespace Nuake
{
ParticleSystem::ParticleSystem(Scene* scene)
{
m_Scene = scene;
}
bool ParticleSystem::Init()
{
return true;
}
void ParticleSystem::Update(Timestep ts)
{
auto& view = m_Scene->m_Registry.view<TransformComponent, ParticleEmitterComponent>();
for (auto& e : view)
{
auto [transformComponent, emitterComponent] = view.get<TransformComponent, ParticleEmitterComponent>(e);
Entity ent = Entity({ e, m_Scene });
// Copy emitter settings from component to real emitter
auto& emitter = emitterComponent.Emitter;
emitter.Amount = emitterComponent.Amount;
emitter.ParticleColor = emitterComponent.ParticleColor;
emitter.Gravity = emitterComponent.Gravity;
emitter.GravityRandom = emitterComponent.GravityRandom;
emitter.SetRadius(emitterComponent.Radius);
emitter.Life = emitterComponent.Life;
emitter.Rate = emitterComponent.Rate;
// Spawn particle if possible
emitterComponent.Emitter.SpawnParticle();
}
}
void ParticleSystem::FixedUpdate(Timestep ts)
{
auto& view = m_Scene->m_Registry.view<TransformComponent, ParticleEmitterComponent>();
for (auto& e : view)
{
auto [transformComponent, emitterComponent] = view.get<TransformComponent, ParticleEmitterComponent>(e);
Entity ent = Entity({ e, m_Scene });
emitterComponent.Emitter.Update(ts);
}
}
void ParticleSystem::EditorUpdate()
{
}
void ParticleSystem::Exit()
{
}
}

View File

@@ -0,0 +1,21 @@
#pragma once
#include <src/Scene/Systems/System.h>
#include <src/Core/Maths.h>
#include <random>
namespace Nuake
{
class ParticleSystem : public System
{
public:
ParticleSystem(Scene* scene);
bool Init() override;
void Update(Timestep ts) override;
void Draw() override {}
void EditorUpdate() override;
void FixedUpdate(Timestep ts) override;
void Exit() override;
};
}

View File

@@ -56,6 +56,9 @@ namespace Nuake {
void ScriptingSystem::FixedUpdate(Timestep ts)
{
if (!Engine::IsPlayMode())
return;
auto entities = m_Scene->m_Registry.view<WrenScriptComponent>();
for (auto& e : entities)
{