From 549dac776a806890b7b96da6c414bbe6e4386f16 Mon Sep 17 00:00:00 2001 From: OusmBlueNinja <89956790+OusmBlueNinja@users.noreply.github.com> Date: Sun, 20 Apr 2025 20:25:13 -0500 Subject: [PATCH] Added Particles ish, untested --- imgui.ini | 34 ++--- remake/build.log | 5 +- src/assets/shaders/unlit_quad.frag | 12 ++ src/assets/shaders/unlit_quad.vert | 28 ++++ src/src/Components/ParticleComponent.cpp | 162 +++++++++++++++++++++++ src/src/Components/ParticleComponent.h | 69 ++++++++++ src/src/Engine.cpp | 30 +++++ src/src/Engine.h | 2 + src/src/Entitys/Object.cpp | 4 + src/src/Renderer.cpp | 98 ++++++++++++++ src/src/Renderer.h | 58 ++++---- src/src/core/types/all.h | 8 ++ src/src/core/types/color.h | 137 +++++++++++++++++-- src/src/core/types/vec2.h | 41 ++++-- src/src/editor/windows/Inspector.cpp | 41 +++++- 15 files changed, 654 insertions(+), 75 deletions(-) create mode 100644 src/assets/shaders/unlit_quad.frag create mode 100644 src/assets/shaders/unlit_quad.vert create mode 100644 src/src/Components/ParticleComponent.cpp create mode 100644 src/src/Components/ParticleComponent.h create mode 100644 src/src/core/types/all.h diff --git a/imgui.ini b/imgui.ini index 096e7ab..1e0a3a6 100644 --- a/imgui.ini +++ b/imgui.ini @@ -10,40 +10,40 @@ Collapsed=1 [Window][WindowOverViewport_11111111] Pos=0,19 -Size=1280,701 +Size=1920,1158 Collapsed=0 [Window][Inspector] -Pos=0,421 -Size=342,299 +Pos=0,683 +Size=342,494 Collapsed=0 DockId=0x0000000A,0 [Window][Scene Tree] Pos=0,19 -Size=342,400 +Size=342,662 Collapsed=0 DockId=0x00000009,0 [Window][Viewport] Pos=344,19 -Size=936,78 +Size=1576,535 Collapsed=0 DockId=0x0000000B,0 [Window][##MainMenuBar] -Size=1280,19 +Size=1920,19 Collapsed=0 [Window][Performance Info] -Pos=1083,383 -Size=197,118 +Pos=1588,840 +Size=332,118 Collapsed=0 DockId=0x00000003,0 [Window][Console] -Pos=344,383 -Size=363,337 +Pos=344,840 +Size=612,337 Collapsed=0 DockId=0x0000000D,0 @@ -54,8 +54,8 @@ Collapsed=0 DockId=0x0000000B,1 [Window][Profiler] -Pos=344,99 -Size=936,282 +Pos=344,556 +Size=1576,282 Collapsed=0 DockId=0x0000000C,0 @@ -78,19 +78,19 @@ Collapsed=0 DockId=0x00000005,1 [Window][Color Correction] -Pos=1083,503 -Size=197,217 +Pos=1588,960 +Size=332,217 Collapsed=0 DockId=0x00000004,0 [Window][Asset Browser] -Pos=709,383 -Size=372,337 +Pos=958,840 +Size=628,337 Collapsed=0 DockId=0x0000000E,0 [Docking][Data] -DockSpace ID=0x11111111 Window=0x1BBC0F80 Pos=0,19 Size=1280,701 Split=X +DockSpace ID=0x11111111 Window=0x1BBC0F80 Pos=0,19 Size=1920,1158 Split=X DockNode ID=0x00000001 Parent=0x11111111 SizeRef=342,701 Split=Y Selected=0x12EF0F59 DockNode ID=0x00000009 Parent=0x00000001 SizeRef=385,662 HiddenTabBar=1 Selected=0x12EF0F59 DockNode ID=0x0000000A Parent=0x00000001 SizeRef=385,494 HiddenTabBar=1 Selected=0x36DC96AB diff --git a/remake/build.log b/remake/build.log index 07e7aca..15f4260 100644 --- a/remake/build.log +++ b/remake/build.log @@ -1,3 +1,2 @@ -[COMPILE] g++ -std=c++20 -Wall -g -Isrc/include -Isrc/include/lua -Isrc/vendor -Isrc/vendor/imgui -Isrc/vendor/box2d -IC:/msys64/mingw64/include -Isrc\vendor\imgui -IC:\msys64\mingw64\lib\libyaml-cpp.a -MMD -MP -c src\src\core\utils\Profiler.cpp -o src\build\core\utils\Profiler.o -[LINK] g++ src\build\Engine.o src\build\main.o src\build\Renderer.o src\build\Components\CameraComponent.o src\build\Components\LightComponent.o src\build\Components\PhysicsComponent.o src\build\Components\ScriptComponent.o src\build\Components\SpriteComponent.o src\build\Components\TextComonent.o src\build\Components\TilemapComponent.o src\build\core\utils\AssetLoader.o src\build\core\utils\EngineConfig.o src\build\core\utils\ExceptionHandler.o src\build\core\utils\FileDialog.o src\build\core\utils\input.o src\build\core\utils\Logging.o src\build\core\utils\Profiler.o src\build\core\utils\utils.o src\build\editor\windows\AssetBrowser.o src\build\editor\windows\Inspector.o src\build\Entitys\Object.o src\build\utils\GameObjectsList.o src\build\utils\Shader.o src\build\utils\UID.o src\build\lapi.o src\build\lauxlib.o src\build\lbaselib.o src\build\lcode.o src\build\lcorolib.o src\build\lctype.o src\build\ldblib.o src\build\ldebug.o src\build\ldo.o src\build\ldump.o src\build\lfunc.o src\build\lgc.o src\build\linit.o src\build\liolib.o src\build\llex.o src\build\lmathlib.o src\build\lmem.o src\build\loadlib.o src\build\lobject.o src\build\lopcodes.o src\build\loslib.o src\build\lparser.o src\build\lstate.o src\build\lstring.o src\build\lstrlib.o src\build\ltable.o src\build\ltablib.o src\build\ltm.o src\build\lua.o src\build\luac.o src\build\lundump.o src\build\lutf8lib.o src\build\lvm.o src\build\lzio.o src\build\imgui.o src\build\imgui_demo.o src\build\imgui_draw.o src\build\imgui_impl_glfw.o src\build\imgui_impl_opengl3.o src\build\imgui_tables.o src\build\imgui_widgets.o src\build\aabb.o src\build\arena_allocator.o src\build\array.o src\build\bitset.o src\build\body.o src\build\broad_phase.o src\build\constraint_graph.o src\build\contact.o src\build\contact_solver.o src\build\core.o src\build\distance.o src\build\distance_joint.o src\build\dynamic_tree.o src\build\geometry.o src\build\hull.o src\build\id_pool.o src\build\island.o src\build\joint.o src\build\manifold.o src\build\math_functions.o src\build\motor_joint.o src\build\mouse_joint.o src\build\mover.o src\build\prismatic_joint.o src\build\revolute_joint.o src\build\sensor.o src\build\shape.o src\build\solver.o src\build\solver_set.o src\build\table.o src\build\timer.o src\build\types.o src\build\weld_joint.o src\build\wheel_joint.o src\build\world.o -o src\build\app.exe -LC:\msys64\mingw64\lib -lglfw3 -lglew32 -lopengl32 -lgdi32 -lyaml-cpp -lcomdlg32 -lssl -lcrypto -[RUN] Executed app.exe successfully. +[COMPILE] g++ -std=c++20 -Wall -g -Isrc/include -Isrc/include/lua -Isrc/vendor -Isrc/vendor/imgui -Isrc/vendor/box2d -IC:/msys64/mingw64/include -Isrc\vendor\imgui -IC:\msys64\mingw64\lib\libyaml-cpp.a -MMD -MP -c src\src\Engine.cpp -o src\build\Engine.o +[LINK] g++ src\build\Engine.o src\build\main.o src\build\Renderer.o src\build\Components\CameraComponent.o src\build\Components\LightComponent.o src\build\Components\ParticleComponent.o src\build\Components\PhysicsComponent.o src\build\Components\ScriptComponent.o src\build\Components\SpriteComponent.o src\build\Components\TextComonent.o src\build\Components\TilemapComponent.o src\build\core\utils\AssetLoader.o src\build\core\utils\EngineConfig.o src\build\core\utils\ExceptionHandler.o src\build\core\utils\FileDialog.o src\build\core\utils\input.o src\build\core\utils\Logging.o src\build\core\utils\Profiler.o src\build\core\utils\utils.o src\build\editor\windows\AssetBrowser.o src\build\editor\windows\Inspector.o src\build\Entitys\Object.o src\build\utils\GameObjectsList.o src\build\utils\Shader.o src\build\utils\UID.o src\build\lapi.o src\build\lauxlib.o src\build\lbaselib.o src\build\lcode.o src\build\lcorolib.o src\build\lctype.o src\build\ldblib.o src\build\ldebug.o src\build\ldo.o src\build\ldump.o src\build\lfunc.o src\build\lgc.o src\build\linit.o src\build\liolib.o src\build\llex.o src\build\lmathlib.o src\build\lmem.o src\build\loadlib.o src\build\lobject.o src\build\lopcodes.o src\build\loslib.o src\build\lparser.o src\build\lstate.o src\build\lstring.o src\build\lstrlib.o src\build\ltable.o src\build\ltablib.o src\build\ltm.o src\build\lua.o src\build\luac.o src\build\lundump.o src\build\lutf8lib.o src\build\lvm.o src\build\lzio.o src\build\imgui.o src\build\imgui_demo.o src\build\imgui_draw.o src\build\imgui_impl_glfw.o src\build\imgui_impl_opengl3.o src\build\imgui_tables.o src\build\imgui_widgets.o src\build\aabb.o src\build\arena_allocator.o src\build\array.o src\build\bitset.o src\build\body.o src\build\broad_phase.o src\build\constraint_graph.o src\build\contact.o src\build\contact_solver.o src\build\core.o src\build\distance.o src\build\distance_joint.o src\build\dynamic_tree.o src\build\geometry.o src\build\hull.o src\build\id_pool.o src\build\island.o src\build\joint.o src\build\manifold.o src\build\math_functions.o src\build\motor_joint.o src\build\mouse_joint.o src\build\mover.o src\build\prismatic_joint.o src\build\revolute_joint.o src\build\sensor.o src\build\shape.o src\build\solver.o src\build\solver_set.o src\build\table.o src\build\timer.o src\build\types.o src\build\weld_joint.o src\build\wheel_joint.o src\build\world.o -o src\build\app.exe -LC:\msys64\mingw64\lib -lglfw3 -lglew32 -lopengl32 -lgdi32 -lyaml-cpp -lcomdlg32 -lssl -lcrypto diff --git a/src/assets/shaders/unlit_quad.frag b/src/assets/shaders/unlit_quad.frag new file mode 100644 index 0000000..316ad03 --- /dev/null +++ b/src/assets/shaders/unlit_quad.frag @@ -0,0 +1,12 @@ +#version 330 core + +in vec4 vColor; +out vec4 FragColor; + +void main() { + // Optional: kill particles with near-zero alpha (optional) + if (vColor.a < 0.01) + discard; + + FragColor = vColor; +} diff --git a/src/assets/shaders/unlit_quad.vert b/src/assets/shaders/unlit_quad.vert new file mode 100644 index 0000000..c00b2e4 --- /dev/null +++ b/src/assets/shaders/unlit_quad.vert @@ -0,0 +1,28 @@ +#version 330 core + +layout (location = 0) in vec2 aPos; + +layout (location = 1) in vec2 iPos; +layout (location = 2) in vec2 iSize; +layout (location = 3) in float iRot; +layout (location = 4) in vec4 iColor; + +uniform vec2 uScreen; + +out vec4 vColor; + +void main() { + float cosR = cos(iRot); + float sinR = sin(iRot); + + vec2 scaled = aPos * iSize; + vec2 rotated = vec2( + scaled.x * cosR - scaled.y * sinR, + scaled.x * sinR + scaled.y * cosR + ); + + vec2 finalPos = iPos + rotated; + + gl_Position = vec4((finalPos / uScreen * 2.0 - 1.0) * vec2(1, -1), 0.0, 1.0); + vColor = iColor; +} diff --git a/src/src/Components/ParticleComponent.cpp b/src/src/Components/ParticleComponent.cpp new file mode 100644 index 0000000..270b0e2 --- /dev/null +++ b/src/src/Components/ParticleComponent.cpp @@ -0,0 +1,162 @@ +#include "ParticleComponent.h" +#include "../Renderer.h" // Adjust if needed +#include +#include + +using namespace core::types; + +static std::mt19937 rng{std::random_device{}()}; + +static float randRange(float a, float b) +{ + return std::uniform_real_distribution(a, b)(rng); +} + +void ParticleComponent::SetEmitterSettings(const ParticleEmitterSettings &s) +{ + settings = s; +} + +void ParticleComponent::Emit() +{ + for (int i = 0; i < settings.maxParticles; ++i) + SpawnParticle(); +} + +void ParticleComponent::Update(float dt) +{ + emitAccumulator += dt; + float emitInterval = 1.0f / settings.emissionRate; + + if (!settings.burst) + { + while (emitAccumulator >= emitInterval) + { + if ((int)particles.size() < settings.maxParticles) + SpawnParticle(); + emitAccumulator -= emitInterval; + } + } + + for (auto &p : particles) + { + p.life -= dt; + if (p.life > 0) + { + p.position += p.velocity * dt; + p.rotation += dt * 2.0f; + } + } + + particles.erase( + std::remove_if(particles.begin(), particles.end(), + [](const Particle &p) + { return p.life <= 0.0f; }), + particles.end()); +} + +void ParticleComponent::Render() +{ + for (const auto &p : particles) + { + float t = std::clamp(1.0f - (p.life / settings.lifeMax), 0.0f, 1.0f); + Color color = core::types::Color::Lerp(settings.startColor, settings.endColor, t); + Renderer::DrawQuad( + p.position, + core::types::Vec2(p.size, p.size), + p.rotation, + color); + } +} + +void ParticleComponent::SpawnParticle() +{ + Particle p; + p.life = randRange(settings.lifeMin, settings.lifeMax); + p.size = randRange(settings.sizeMin, settings.sizeMax); + + float angle = std::atan2(settings.direction.y, settings.direction.x) + + randRange(-settings.spread * 0.5f, settings.spread * 0.5f); + float speed = randRange(settings.speedMin, settings.speedMax); + p.velocity = Vec2(std::cos(angle), std::sin(angle)) * speed; + + p.position = Vec2(0.0f, 0.0f); + p.rotation = randRange(0.0f, 6.28318f); // 2π + p.color = settings.startColor; + + particles.push_back(p); +} + + + + + +void ParticleComponent::Save(YAML::Emitter &out) const +{ + out << YAML::Key << "ParticleComponent" << YAML::Value << YAML::BeginMap; + + out << YAML::Key << "maxParticles" << YAML::Value << settings.maxParticles; + out << YAML::Key << "emissionRate" << YAML::Value << settings.emissionRate; + out << YAML::Key << "lifeMin" << YAML::Value << settings.lifeMin; + out << YAML::Key << "lifeMax" << YAML::Value << settings.lifeMax; + out << YAML::Key << "sizeMin" << YAML::Value << settings.sizeMin; + out << YAML::Key << "sizeMax" << YAML::Value << settings.sizeMax; + out << YAML::Key << "speedMin" << YAML::Value << settings.speedMin; + out << YAML::Key << "speedMax" << YAML::Value << settings.speedMax; + + out << YAML::Key << "direction" << YAML::Value << YAML::Flow << YAML::BeginSeq << settings.direction.x << settings.direction.y << YAML::EndSeq; + out << YAML::Key << "spread" << YAML::Value << settings.spread; + + auto writeColor = [](const Color &c, YAML::Emitter &out) + { + out << YAML::Flow << YAML::BeginSeq << c.r << c.g << c.b << c.a << YAML::EndSeq; + }; + out << YAML::Key << "startColor" << YAML::Value; + writeColor(settings.startColor, out); + out << YAML::Key << "endColor" << YAML::Value; + writeColor(settings.endColor, out); + + out << YAML::Key << "loop" << YAML::Value << settings.loop; + out << YAML::Key << "burst" << YAML::Value << settings.burst; + + out << YAML::EndMap; +} + + + +void ParticleComponent::Load(const YAML::Node &node) +{ + if (!node["ParticleComponent"] || !node["ParticleComponent"].IsMap()) + return; + + const YAML::Node &comp = node["ParticleComponent"]; + + auto vec2 = [](const YAML::Node &n) + { + if (!n || !n.IsSequence() || n.size() != 2) + return Vec2{}; + return Vec2(n[0].as(), n[1].as()); + }; + + auto color = [](const YAML::Node &n) + { + if (!n || !n.IsSequence() || n.size() != 4) + return Color{}; + return Color(n[0].as(), n[1].as(), n[2].as(), n[3].as()); + }; + + if (comp["maxParticles"]) settings.maxParticles = comp["maxParticles"].as(); + if (comp["emissionRate"]) settings.emissionRate = comp["emissionRate"].as(); + if (comp["lifeMin"]) settings.lifeMin = comp["lifeMin"].as(); + if (comp["lifeMax"]) settings.lifeMax = comp["lifeMax"].as(); + if (comp["sizeMin"]) settings.sizeMin = comp["sizeMin"].as(); + if (comp["sizeMax"]) settings.sizeMax = comp["sizeMax"].as(); + if (comp["speedMin"]) settings.speedMin = comp["speedMin"].as(); + if (comp["speedMax"]) settings.speedMax = comp["speedMax"].as(); + if (comp["direction"]) settings.direction = vec2(comp["direction"]); + if (comp["spread"]) settings.spread = comp["spread"].as(); + if (comp["startColor"]) settings.startColor = color(comp["startColor"]); + if (comp["endColor"]) settings.endColor = color(comp["endColor"]); + if (comp["loop"]) settings.loop = comp["loop"].as(); + if (comp["burst"]) settings.burst = comp["burst"].as(); +} diff --git a/src/src/Components/ParticleComponent.h b/src/src/Components/ParticleComponent.h new file mode 100644 index 0000000..c8d0d9d --- /dev/null +++ b/src/src/Components/ParticleComponent.h @@ -0,0 +1,69 @@ + + +#pragma once +#include "Component.h" +#include "../Entitys/Object.h" + +#include "../core/types/vec2.h" +#include "../core/types/vec4.h" +#include "../core/types/color.h" + +#include +#include + +#include +#include +struct Particle +{ + core::types::Vec2 position; + core::types::Vec2 velocity; + float life; + float size; + float rotation; + core::types::Color color; +}; + +struct ParticleEmitterSettings +{ + int maxParticles = 1000; + float emissionRate = 100.0f; // particle/s + float lifeMin = 0.5f; + float lifeMax = 1.5f; + float sizeMin = 5.0f; + float sizeMax = 10.0f; + float speedMin = 100.0f; + float speedMax = 300.0f; + core::types::Vec2 direction = {1.0f, 0.0f}; + float spread = 1.57f; // radians + core::types::Color startColor = {1, 1, 1, 1}; + core::types::Color endColor = {1, 1, 1, 0}; + bool loop = true; + bool burst = false; +}; + +class ParticleComponent : public Component +{ +public: + ParticleComponent(Object *owner) : Component(owner) {} + + std::string GetName() const override { return "ParticleComponent"; } + + void Save(YAML::Emitter &out) const override; + void Load(const YAML::Node &node) override; + + void SetEmitterSettings(const ParticleEmitterSettings &settings); + const ParticleEmitterSettings &GetEmitterSettings() const { return settings; } + const std::vector &GetParticles() const { return particles; } + const ParticleEmitterSettings &GetSettings() const { return settings; } + + void Emit(); + void Update(float dt); + void Render(); + +private: + std::vector particles; + ParticleEmitterSettings settings; + float emitAccumulator = 0.0f; + + void SpawnParticle(); +}; diff --git a/src/src/Engine.cpp b/src/src/Engine.cpp index 468f7db..702d097 100644 --- a/src/src/Engine.cpp +++ b/src/src/Engine.cpp @@ -7,6 +7,7 @@ #include "components/TilemapComponent.h" #include "components/ScriptComponent.h" #include "components/PhysicsComponent.h" +#include "components/ParticleComponent.h" #include "core/utils/FileDialog.h" #include "core/utils/Logging.h" @@ -437,6 +438,7 @@ void Engine::Init() m_scriptUpdates.reserve(256); m_collectStack.reserve(1024); m_physicsUpdates.reserve(1024); + m_particleUpdates.reserve(1024); Logger::LogInfo("Initialized Engine"); } @@ -448,6 +450,7 @@ void Engine::collectObjects(bool playing, const glm::vec2 &camPos, float camZoom m_collectStack.clear(); m_activeCamera = nullptr; m_physicsUpdates.clear(); + m_particleUpdates.clear(); // <-- Add this const glm::vec2 screenSize = glm::vec2(Renderer::GetSize()); @@ -481,6 +484,9 @@ void Engine::collectObjects(bool playing, const glm::vec2 &camPos, float camZoom } } + if (auto particles = obj->GetComponent()) + m_particleUpdates.push_back(particles.get()); // <-- Collect particle components + if (playing) { if (auto script = obj->GetComponent()) @@ -777,6 +783,7 @@ void Engine::Run() m_Reserved_draws = 0; m_toDraw.clear(); m_scriptUpdates.clear(); + m_particleUpdates.clear(); profiler.EndEngineSection(); // profiler.BeginEngineSection("Draw Editor Grid"); @@ -822,9 +829,11 @@ void Engine::Run() } profiler.EndSection(); + profiler.BeginSection("Render"); for (auto *obj : m_toDraw) { + // --- Sprite rendering --- if (auto spritePtr = obj->GetComponent()) { profiler.BeginSection("Draw Sprite: " + obj->GetName()); @@ -834,6 +843,27 @@ void Engine::Run() cameraPos); profiler.EndSection(); } + + // --- Particle rendering --- + if (auto particle = obj->GetComponent()) + { + profiler.BeginSection("Draw Particles" + obj->GetName()); + const auto &particles = particle->GetParticles(); + const auto &settings = particle->GetSettings(); + + for (const auto &p : particles) + { + float t = std::clamp(1.0f - (p.life / settings.lifeMax), 0.0f, 1.0f); + core::types::Color color = core::types::Color::Lerp(settings.startColor, settings.endColor, t); + Renderer::DrawQuad( + p.position, + core::types::Vec2(p.size, p.size), + p.rotation, + color); + } + + profiler.EndSection(); + } } profiler.EndSection(); diff --git a/src/src/Engine.h b/src/src/Engine.h index 0bd9f04..818f422 100644 --- a/src/src/Engine.h +++ b/src/src/Engine.h @@ -9,6 +9,7 @@ class Object; class ScriptComponent; class PhysicsComponent; +class ParticleComponent; class Engine { @@ -39,5 +40,6 @@ private: std::vector> m_collectStack; std::vector m_physicsUpdates; + std::vector m_particleUpdates; }; diff --git a/src/src/Entitys/Object.cpp b/src/src/Entitys/Object.cpp index 713ff1d..1026edb 100644 --- a/src/src/Entitys/Object.cpp +++ b/src/src/Entitys/Object.cpp @@ -6,6 +6,8 @@ #include "../Components/TilemapComponent.h" #include "../Components/TextComponent.h" #include "../Components/ScriptComponent.h" +#include "../Components/ParticleComponent.h" + #include "../core/utils/Logging.h" #include "../utils/UID.h" @@ -153,6 +155,8 @@ void Object::Load(const YAML::Node &node) else if (type == "TilemapComponent") AddComponent()->Load(compNode); else if (type == "TextComponent") AddComponent()->Load(compNode); else if (type == "ScriptComponent") AddComponent()->Load(compNode); + else if (type == "ParticleComponent") AddComponent()->Load(compNode); + } } diff --git a/src/src/Renderer.cpp b/src/src/Renderer.cpp index 27d7dd9..05919a5 100644 --- a/src/src/Renderer.cpp +++ b/src/src/Renderer.cpp @@ -40,6 +40,12 @@ int Renderer::s_DrawCalls = 0; int Renderer::s_LightsCount = 0; std::unique_ptr Renderer::s_ColorCorrection = nullptr; +GLuint Renderer::s_QuadVAO = 0; +GLuint Renderer::s_QuadVBO = 0; +GLuint Renderer::s_QuadInstanceVBO = 0; +Shader Renderer::s_UnlitQuadShader; +std::vector Renderer::s_QuadBatch; + std::vector Renderer::s_Lights; std::vector Renderer::s_Clusters; @@ -118,6 +124,52 @@ void Renderer::InitQuad() glBindVertexArray(0); } +void Renderer::InitQuadBatch() +{ + float quadVerts[] = { + -0.5f, -0.5f, + 0.5f, -0.5f, + 0.5f, 0.5f, + -0.5f, 0.5f}; + + glGenVertexArrays(1, &s_QuadVAO); + glBindVertexArray(s_QuadVAO); + + glGenBuffers(1, &s_QuadVBO); + glBindBuffer(GL_ARRAY_BUFFER, s_QuadVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(quadVerts), quadVerts, GL_STATIC_DRAW); + + glEnableVertexAttribArray(0); // aPos + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void *)0); + + glGenBuffers(1, &s_QuadInstanceVBO); + glBindBuffer(GL_ARRAY_BUFFER, s_QuadInstanceVBO); + glBufferData(GL_ARRAY_BUFFER, MAX_QUADS * sizeof(QuadInstance), nullptr, GL_DYNAMIC_DRAW); + + std::size_t offset = 0; + + glEnableVertexAttribArray(1); // pos + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(QuadInstance), (void *)(offset)); + glVertexAttribDivisor(1, 1); + offset += sizeof(glm::vec2); + + glEnableVertexAttribArray(2); // size + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(QuadInstance), (void *)(offset)); + glVertexAttribDivisor(2, 1); + offset += sizeof(glm::vec2); + + glEnableVertexAttribArray(3); // rotation + glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, sizeof(QuadInstance), (void *)(offset)); + glVertexAttribDivisor(3, 1); + offset += sizeof(float); + + glEnableVertexAttribArray(4); // color + glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(QuadInstance), (void *)(offset)); + glVertexAttribDivisor(4, 1); + + glBindVertexArray(0); +} + void Renderer::Init() { glGenFramebuffers(1, &fbo); @@ -154,6 +206,9 @@ void Renderer::Init() blurShader.LoadFromFile("src/assets/shaders/fullscreen.vert", "src/assets/shaders/blur.frag"); compositeShader.LoadFromFile("src/assets/shaders/fullscreen.vert", "src/assets/shaders/composite.frag"); + InitQuadBatch(); + s_UnlitQuadShader.LoadFromFile("src/assets/shaders/unlit_quad.vert", "src/assets/shaders/unlit_quad.frag"); + SetColorCorrection(std::make_unique()); { @@ -366,6 +421,7 @@ void Renderer::End() PROFILE_ENGINE_SCOPE("Renderer::End"); FlushSprites(); + FlushQuads(); glBindFramebuffer(GL_FRAMEBUFFER, 0); } @@ -443,6 +499,17 @@ void Renderer::DrawTilemap(TilemapComponent *tilemap, const glm::vec2 &worldPos, glBindVertexArray(0); } +void Renderer::DrawQuad(const core::types::Vec2 &pos, const core::types::Vec2 &size, float rotation, const core::types::Color &color) +{ + PROFILE_DEEP_SCOPE("Renderer::DrawQuad"); + + if (s_QuadBatch.size() >= MAX_QUADS) + FlushQuads(); + + s_QuadBatch.push_back({pos, size, rotation, color}); +} + + void Renderer::DrawSprite(SpriteComponent *sprite, const glm::vec2 &pos, float zoom, glm::vec2 &CameraPos) { PROFILE_DEEP_SCOPE("DrawSprite"); @@ -473,6 +540,37 @@ void Renderer::DrawSprite(SpriteComponent *sprite, const glm::vec2 &pos, float z sortedDrawList.push_back(drawEntry); } + + +void Renderer::FlushQuads() +{ + PROFILE_ENGINE_SCOPE("Renderer::FlushQuads"); + + if (s_QuadBatch.empty()) + return; + + { + PROFILE_DEEP_SCOPE("Upload"); + + s_UnlitQuadShader.Use(); + s_UnlitQuadShader.SetVec2("uScreen", glm::vec2(width, height)); + + glBindVertexArray(s_QuadVAO); + glBindBuffer(GL_ARRAY_BUFFER, s_QuadInstanceVBO); + glBufferSubData(GL_ARRAY_BUFFER, 0, s_QuadBatch.size() * sizeof(QuadInstance), s_QuadBatch.data()); + } + + { + PROFILE_DEEP_SCOPE("DrawQuads"); + + glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, 4, static_cast(s_QuadBatch.size())); + ++s_DrawCalls; + } + + glBindVertexArray(0); + s_QuadBatch.clear(); +} + void Renderer::FlushSprites() { PROFILE_ENGINE_SCOPE("Renderer::FlushSprites"); diff --git a/src/src/Renderer.h b/src/src/Renderer.h index 51d4589..60e9fd3 100644 --- a/src/src/Renderer.h +++ b/src/src/Renderer.h @@ -9,9 +9,9 @@ #include "core/utils/EngineConfig.h" #include "utils/Shader.h" #include "core/utils/Profiler.h" +#include "core/types/all.h" -struct ColorCorrection -{ +struct ColorCorrection { float brightness = 1.0f; float saturation = 1.0f; float gamma = 1.0f; @@ -20,41 +20,45 @@ struct ColorCorrection float threshold = 0.2f; float intensity = 1.2f; - - void Upload(Shader &shader) const - { + void Upload(Shader& shader) const { shader.SetFloat("uBrightness", brightness); shader.SetFloat("uSaturation", saturation); shader.SetFloat("uGamma", gamma); } }; -struct Light -{ +struct Light { glm::vec2 screenPos; glm::vec3 color; float intensity; float radius; }; -class Renderer -{ +struct QuadInstance { + core::types::Vec2 pos; + core::types::Vec2 size; + float rotation; + core::types::Color color; +}; + + +class Renderer { public: static void Init(); static void Resize(int w, int h); static void Begin(); static void End(); - static void DrawSprite(SpriteComponent *sprite, const glm::vec2 &pos, float zoom, glm::vec2 &CameraPos); - static void DrawTilemap(TilemapComponent *tilemap, const glm::vec2 &worldPos, float zoom, const glm::vec2 &cameraPos); + static void DrawSprite(SpriteComponent* sprite, const glm::vec2& pos, float zoom, glm::vec2& CameraPos); + static void DrawTilemap(TilemapComponent* tilemap, const glm::vec2& worldPos, float zoom, const glm::vec2& cameraPos); - static void AddLight(const glm::vec2 &screenPos, const glm::vec3 &color, float intensity, float radius); + static void AddLight(const glm::vec2& screenPos, const glm::vec3& color, float intensity, float radius); static void ClearLights(); - static void DrawEditorGrid(const glm::vec2 &cameraPos, float zoom); - static void DrawGizmoLine(const glm::vec2 &worldStart, const glm::vec2 &worldEnd, const glm::vec3 &color, const glm::vec2 &cameraPos, float zoom); - static void DrawGizmoRect(const glm::vec2 &worldPos, const glm::vec2 &size, const glm::vec3 &color, const glm::vec2 &cameraPos, float zoom); - static void DrawGizmoCircle(const glm::vec2 &worldCenter, float radius, int segments, const glm::vec3 &color, const glm::vec2 &cameraPos, float zoom); + static void DrawEditorGrid(const glm::vec2& cameraPos, float zoom); + static void DrawGizmoLine(const glm::vec2& worldStart, const glm::vec2& worldEnd, const glm::vec3& color, const glm::vec2& cameraPos, float zoom); + static void DrawGizmoRect(const glm::vec2& worldPos, const glm::vec2& size, const glm::vec3& color, const glm::vec2& cameraPos, float zoom); + static void DrawGizmoCircle(const glm::vec2& worldCenter, float radius, int segments, const glm::vec3& color, const glm::vec2& cameraPos, float zoom); static GLuint GetRenderTexture(); static glm::ivec2 GetSize(); @@ -62,14 +66,15 @@ public: static int GetLightsCount(); static void SetColorCorrection(std::unique_ptr correction); - static ColorCorrection *GetColorCorrection(); - + static ColorCorrection* GetColorCorrection(); static GLuint GetFinalTexture(); - // Clustered lighting - static void UpdateClusterLights(); // Call once per frame after all lights added + static void UpdateClusterLights(); static void FlushSprites(); + static void InitQuadBatch(); + static void DrawQuad(const core::types::Vec2& pos, const core::types::Vec2& size, float rotation, const core::types::Color& color); + static void FlushQuads(); private: static std::vector s_Lights; @@ -82,16 +87,14 @@ private: static GLuint shader, quadVAO, quadVBO; static void InitQuad(); - static GLuint LoadShader(const char *vertexSrc, const char *fragmentSrc); - + static GLuint LoadShader(const char* vertexSrc, const char* fragmentSrc); static std::unique_ptr s_ColorCorrection; - // --- Clustered Lighting --- + // Clustered Lighting static constexpr int CLUSTER_SIZE = 16; static constexpr int MAX_LIGHTS_PER_CLUSTER = 32; - struct Cluster - { + struct Cluster { std::vector lightIndices; }; @@ -102,4 +105,9 @@ private: static GLuint bloomFBO; static GLuint bloomTexture; + + static std::vector s_QuadBatch; + static GLuint s_QuadVAO, s_QuadVBO, s_QuadInstanceVBO; + static Shader s_UnlitQuadShader; + static constexpr size_t MAX_QUADS = 10000; }; diff --git a/src/src/core/types/all.h b/src/src/core/types/all.h new file mode 100644 index 0000000..c606c27 --- /dev/null +++ b/src/src/core/types/all.h @@ -0,0 +1,8 @@ +#pragma once + +#include "color.h" +#include "rect.h" +#include "vec2.h" +#include "vec3.h" +#include "vec4.h" +#include "vector.h" diff --git a/src/src/core/types/color.h b/src/src/core/types/color.h index 913eef2..283fe91 100644 --- a/src/src/core/types/color.h +++ b/src/src/core/types/color.h @@ -1,17 +1,130 @@ #pragma once +#include -namespace core { -namespace types { - -struct Color +namespace core { - float r{1}, g{1}, b{1}, a{1}; + namespace types + { - Color() = default; - Color(float _r, float _g, float _b, float _a = 1.0f) - : r(_r), g(_g), b(_b), a(_a) - {} -}; + struct Color + { + float r{1}, g{1}, b{1}, a{1}; -} -} + Color() = default; + Color(float _r, float _g, float _b, float _a = 1.0f) + : r(_r), g(_g), b(_b), a(_a) {} + + // Add + Color operator+(const Color &other) const + { + return {r + other.r, g + other.g, b + other.b, a + other.a}; + } + + Color &operator+=(const Color &other) + { + r += other.r; + g += other.g; + b += other.b; + a += other.a; + return *this; + } + + // Subtract + Color operator-(const Color &other) const + { + return {r - other.r, g - other.g, b - other.b, a - other.a}; + } + + Color &operator-=(const Color &other) + { + r -= other.r; + g -= other.g; + b -= other.b; + a -= other.a; + return *this; + } + + // Multiply + Color operator*(const Color &other) const + { + return {r * other.r, g * other.g, b * other.b, a * other.a}; + } + + Color operator*(float scalar) const + { + return {r * scalar, g * scalar, b * scalar, a * scalar}; + } + + Color &operator*=(const Color &other) + { + r *= other.r; + g *= other.g; + b *= other.b; + a *= other.a; + return *this; + } + + Color &operator*=(float scalar) + { + r *= scalar; + g *= scalar; + b *= scalar; + a *= scalar; + return *this; + } + + // Divide + Color operator/(float scalar) const + { + float inv = 1.0f / scalar; + return {r * inv, g * inv, b * inv, a * inv}; + } + + Color &operator/=(float scalar) + { + float inv = 1.0f / scalar; + r *= inv; + g *= inv; + b *= inv; + a *= inv; + return *this; + } + + // Equality + bool operator==(const Color &other) const + { + return r == other.r && g == other.g && b == other.b && a == other.a; + } + + bool operator!=(const Color &other) const + { + return !(*this == other); + } + + // Linear interpolation + static Color Lerp(const Color &a, const Color &b, float t) + { + return { + a.r + (b.r - a.r) * t, + a.g + (b.g - a.g) * t, + a.b + (b.b - a.b) * t, + a.a + (b.a - a.a) * t}; + } + + // Clamp all components between 0 and 1 + void Clamp() + { + r = std::clamp(r, 0.0f, 1.0f); + g = std::clamp(g, 0.0f, 1.0f); + b = std::clamp(b, 0.0f, 1.0f); + a = std::clamp(a, 0.0f, 1.0f); + } + }; + + inline Color operator*(float scalar, const Color &c) + { + return c * scalar; + } + + } // namespace types +} // namespace core diff --git a/src/src/core/types/vec2.h b/src/src/core/types/vec2.h index 46eb424..5f28ef4 100644 --- a/src/src/core/types/vec2.h +++ b/src/src/core/types/vec2.h @@ -1,5 +1,6 @@ // core/types/vec2.h #pragma once +#include namespace core { @@ -8,27 +9,39 @@ namespace core struct Vec2 { - float x{0}, y{0}; + float x = 0.0f, y = 0.0f; Vec2() = default; Vec2(float _x, float _y) : x(_x), y(_y) {} - Vec2 &operator+=(const Vec2 &o) + Vec2 operator*(float s) const { return {x * s, y * s}; } + operator glm::vec2() const { return glm::vec2(x, y); } + + + Vec2 &operator*=(float scalar) { - x += o.x; - y += o.y; - return *this; - } - Vec2 &operator-=(const Vec2 &o) - { - x -= o.x; - y -= o.y; + x *= scalar; + y *= scalar; return *this; } - friend Vec2 operator+(Vec2 a, const Vec2 &b) { return a += b; } - friend Vec2 operator-(Vec2 a, const Vec2 &b) { return a -= b; } + friend Vec2 operator*(float scalar, const Vec2 &v) + { + return v * scalar; + } + + Vec2 operator+(const Vec2 &other) const + { + return {x + other.x, y + other.y}; + } + + Vec2 &operator+=(const Vec2 &other) + { + x += other.x; + y += other.y; + return *this; + } }; - } -} + } // namespace types +} // namespace core diff --git a/src/src/editor/windows/Inspector.cpp b/src/src/editor/windows/Inspector.cpp index e5659a6..2bbd5fb 100644 --- a/src/src/editor/windows/Inspector.cpp +++ b/src/src/editor/windows/Inspector.cpp @@ -7,6 +7,7 @@ #include "../../components/TilemapComponent.h" #include "../../components/ScriptComponent.h" #include "../../components/PhysicsComponent.h" +#include "../../components/ParticleComponent.h" void DrawInspectorUI(std::shared_ptr selected) { @@ -70,7 +71,8 @@ void DrawInspectorUI(std::shared_ptr selected) "CameraComponent", "LightComponent", "ScriptComponent", - "TilemapComponent"}; + "TilemapComponent", + "ParticleComponent"}; static int selectedIndex = -1; @@ -102,6 +104,8 @@ void DrawInspectorUI(std::shared_ptr selected) selected->AddComponent(); else if (type == "TilemapComponent" && !selected->GetComponent()) selected->AddComponent(); + else if (type == "ParticleComponent" && !selected->GetComponent()) + selected->AddComponent(); } if (auto sprite = selected->GetComponent()) @@ -139,7 +143,6 @@ void DrawInspectorUI(std::shared_ptr selected) if (!path.empty()) sprite->SetTexture(path); } - } ImGui::SeparatorText("Normal Map"); { @@ -161,12 +164,10 @@ void DrawInspectorUI(std::shared_ptr selected) if (!path.empty()) sprite->SetNormalMap(path); } - } ImGui::SeparatorText("Info"); - // — Size Display & Remove — glm::vec2 size = sprite->GetSize(); ImGui::Text("Size: %.0f × %.0f", size.x, size.y); @@ -250,6 +251,38 @@ void DrawInspectorUI(std::shared_ptr selected) if (ImGui::Button("Remove ScriptComponent")) selected->RemoveComponent(); } + if (auto part = selected->GetComponent()) + { + ImGui::SeparatorText("Particle Component"); + + auto settings = part->GetEmitterSettings(); + + ImGui::DragInt("Max Particles", &settings.maxParticles, 10, 10, 10000); + ImGui::DragFloat("Emission Rate", &settings.emissionRate, 1.0f, 0.0f, 1000.0f); + ImGui::DragFloat("Life Min", &settings.lifeMin, 0.01f, 0.01f, 10.0f); + ImGui::DragFloat("Life Max", &settings.lifeMax, 0.01f, 0.01f, 10.0f); + ImGui::DragFloat("Size Min", &settings.sizeMin, 0.1f, 0.1f, 100.0f); + ImGui::DragFloat("Size Max", &settings.sizeMax, 0.1f, 0.1f, 100.0f); + ImGui::DragFloat("Speed Min", &settings.speedMin, 1.0f, 0.0f, 1000.0f); + ImGui::DragFloat("Speed Max", &settings.speedMax, 1.0f, 0.0f, 1000.0f); + ImGui::DragFloat2("Direction", &settings.direction.x, 0.01f); + ImGui::DragFloat("Spread", &settings.spread, 0.01f, 0.0f, 3.14f); + + ImGui::ColorEdit4("Start Color", &settings.startColor.r); + ImGui::ColorEdit4("End Color", &settings.endColor.r); + + ImGui::Checkbox("Loop", &settings.loop); + ImGui::Checkbox("Burst", &settings.burst); + + part->SetEmitterSettings(settings); + + if (ImGui::Button("Emit Once")) + part->Emit(); + + if (ImGui::Button("Remove ParticleComponent")) + selected->RemoveComponent(); + } + if (auto phys = selected->GetComponent()) { ImGui::SeparatorText("Physics Component");