Added right click menu on scene view. and fixed rendering

This commit is contained in:
OusmBlueNinja 2025-04-12 20:26:47 -05:00
parent 02594dc015
commit 7945ff246b
12 changed files with 449 additions and 86 deletions

View File

@ -1,3 +1,6 @@
[LINK] g++ src\build\src\Engine.o src\build\src\main.o src\build\src\Renderer.o src\build\src\Components\SpriteComponent.o src\build\src\Entitys\Object.o src\build\src\utils\FileDialog.o src\build\vendor\imgui\imgui.o src\build\vendor\imgui\imgui_demo.o src\build\vendor\imgui\imgui_draw.o src\build\vendor\imgui\imgui_impl_glfw.o src\build\vendor\imgui\imgui_impl_opengl3.o src\build\vendor\imgui\imgui_tables.o src\build\vendor\imgui\imgui_widgets.o -o src\build\app.exe -LC:/msys64/mingw64/lib -lglfw3 -lglew32 -lopengl32 -lgdi32 -lyaml-cpp -lcomdlg32 -lssl -lcrypto [COMPILE] g++ -std=c++20 -Wall -Isrc/include -Isrc/vendor -Isrc/vendor/imgui -IC:/msys64/mingw64/include -MMD -MP -c src\src\main.cpp -o src\build\src\main.o
[LINK] g++ src\build\src\Engine.o src\build\src\main.o src\build\src\Renderer.o src\build\src\Components\SpriteComponent.o src\build\src\Entitys\Object.o src\build\src\utils\FileDialog.o src\build\src\utils\Shader.o src\build\vendor\imgui\imgui.o src\build\vendor\imgui\imgui_demo.o src\build\vendor\imgui\imgui_draw.o src\build\vendor\imgui\imgui_impl_glfw.o src\build\vendor\imgui\imgui_impl_opengl3.o src\build\vendor\imgui\imgui_tables.o src\build\vendor\imgui\imgui_widgets.o -o src\build\app.exe -LC:/msys64/mingw64/lib -lglfw3 -lglew32 -lopengl32 -lgdi32 -lyaml-cpp -lcomdlg32 -lssl -lcrypto
[TIME] Build duration: 0.43s [TIME] Build duration: 1.23s
[ERROR] Runtime crash
Command 'src\build\app.exe' returned non-zero exit status 3221226356.

View File

@ -14,8 +14,8 @@ Size=1920,1158
Collapsed=0 Collapsed=0
[Window][Inspector] [Window][Inspector]
Pos=1597,19 Pos=1513,19
Size=323,1158 Size=407,1158
Collapsed=0 Collapsed=0
DockId=0x00000004,0 DockId=0x00000004,0
@ -27,14 +27,14 @@ DockId=0x00000001,0
[Window][Viewport] [Window][Viewport]
Pos=265,19 Pos=265,19
Size=1330,1158 Size=1246,1158
Collapsed=0 Collapsed=0
DockId=0x00000002,0 DockId=0x00000002,0
[Docking][Data] [Docking][Data]
DockSpace ID=0x11111111 Window=0x1BBC0F80 Pos=0,19 Size=1920,1158 Split=X DockSpace ID=0x11111111 Window=0x1BBC0F80 Pos=0,19 Size=1920,1158 Split=X
DockNode ID=0x00000003 Parent=0x11111111 SizeRef=1595,1158 Split=X DockNode ID=0x00000003 Parent=0x11111111 SizeRef=1511,1158 Split=X
DockNode ID=0x00000001 Parent=0x00000003 SizeRef=263,701 HiddenTabBar=1 Selected=0x12EF0F59 DockNode ID=0x00000001 Parent=0x00000003 SizeRef=263,701 HiddenTabBar=1 Selected=0x12EF0F59
DockNode ID=0x00000002 Parent=0x00000003 SizeRef=1330,701 CentralNode=1 Selected=0xC450F867 DockNode ID=0x00000002 Parent=0x00000003 SizeRef=1246,701 CentralNode=1 Selected=0xC450F867
DockNode ID=0x00000004 Parent=0x11111111 SizeRef=323,1158 HiddenTabBar=1 Selected=0x36DC96AB DockNode ID=0x00000004 Parent=0x11111111 SizeRef=407,1158 HiddenTabBar=1 Selected=0x36DC96AB

View File

@ -0,0 +1,9 @@
#version 330 core
in vec2 vUV;
out vec4 FragColor;
uniform sampler2D uTex;
void main() {
FragColor = texture(uTex, vUV);
}

View File

@ -0,0 +1,16 @@
#version 330 core
layout(location = 0) in vec2 aPos;
layout(location = 1) in vec2 aUV;
uniform vec2 uPos;
uniform vec2 uSize;
uniform vec2 uScreen;
out vec2 vUV;
void main() {
vec2 scaled = aPos * uSize + uPos;
vec2 ndc = scaled / uScreen * 2.0 - 1.0;
gl_Position = vec4(ndc.x, -ndc.y, 0.0, 1.0);
vUV = vec2(aUV.x, 1.0 - aUV.y);
}

View File

@ -15,6 +15,8 @@ unsigned int SpriteComponent::LoadTexture(const std::string& path) {
return 0; return 0;
} }
size = glm::vec2(w,h);
unsigned int id; unsigned int id;
glGenTextures(1, &id); glGenTextures(1, &id);
glBindTexture(GL_TEXTURE_2D, id); glBindTexture(GL_TEXTURE_2D, id);
@ -22,7 +24,6 @@ unsigned int SpriteComponent::LoadTexture(const std::string& path) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D); glGenerateMipmap(GL_TEXTURE_2D);
// Default params
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

View File

@ -18,12 +18,17 @@ public:
std::string GetTexturePath() const; std::string GetTexturePath() const;
std::string GetNormalMapPath() const; std::string GetNormalMapPath() const;
virtual glm::vec2 GetSize() const { return size; }
virtual std::string GetName() const override { return "SpriteComponent"; } virtual std::string GetName() const override { return "SpriteComponent"; }
virtual void Save(YAML::Emitter& out) const override; virtual void Save(YAML::Emitter& out) const override;
virtual void Load(const YAML::Node& node) override; virtual void Load(const YAML::Node& node) override;
private: private:
glm::vec2 size = { 64, 64 };
std::string texturePath; std::string texturePath;
std::string normalMapPath; std::string normalMapPath;
unsigned int textureID = 0; unsigned int textureID = 0;

View File

@ -26,17 +26,20 @@
static std::vector<std::shared_ptr<Object>> objects; static std::vector<std::shared_ptr<Object>> objects;
static std::shared_ptr<Object> selected = nullptr; static std::shared_ptr<Object> selected = nullptr;
static bool playing = false; static bool playing = false;
GLFWwindow* window = nullptr; GLFWwindow *window = nullptr;
Engine::Engine() { Engine::Engine()
{
Init(); Init();
} }
Engine::~Engine() { Engine::~Engine()
{
Shutdown(); Shutdown();
} }
void Engine::Init() { void Engine::Init()
{
glfwInit(); glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
@ -49,7 +52,8 @@ void Engine::Init() {
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
ImGui::CreateContext(); ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; ImGuiIO &io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
ImGui::StyleColorsDark(); ImGui::StyleColorsDark();
ImGui_ImplGlfw_InitForOpenGL(window, true); ImGui_ImplGlfw_InitForOpenGL(window, true);
ImGui_ImplOpenGL3_Init("#version 330"); ImGui_ImplOpenGL3_Init("#version 330");
@ -61,8 +65,10 @@ void Engine::Init() {
selected = obj; selected = obj;
} }
void Engine::Run() { void Engine::Run()
while (!glfwWindowShouldClose(window)) { {
while (!glfwWindowShouldClose(window))
{
glfwPollEvents(); glfwPollEvents();
ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplOpenGL3_NewFrame();
@ -70,17 +76,22 @@ void Engine::Run() {
ImGui::NewFrame(); ImGui::NewFrame();
ImGui::DockSpaceOverViewport(ImGui::GetMainViewport()->ID); ImGui::DockSpaceOverViewport(ImGui::GetMainViewport()->ID);
if (ImGui::BeginMainMenuBar()) { if (ImGui::BeginMainMenuBar())
if (ImGui::Button(playing ? "Stop" : "Play")) { {
if (ImGui::Button(playing ? "Stop" : "Play"))
{
playing = !playing; playing = !playing;
} }
if (ImGui::BeginMenu("File")) { if (ImGui::BeginMenu("File"))
if (ImGui::MenuItem("Save Scene")) { {
if (ImGui::MenuItem("Save Scene"))
{
std::string file = OpenFileDialog(FileDialogType::Scenes); // reuse your dialog std::string file = OpenFileDialog(FileDialogType::Scenes); // reuse your dialog
if (!file.empty()) if (!file.empty())
SaveScene(file); SaveScene(file);
} }
if (ImGui::MenuItem("Load Scene")) { if (ImGui::MenuItem("Load Scene"))
{
std::string file = OpenFileDialog(FileDialogType::Scenes); std::string file = OpenFileDialog(FileDialogType::Scenes);
if (!file.empty()) if (!file.empty())
LoadScene(file); LoadScene(file);
@ -92,12 +103,45 @@ void Engine::Run() {
} }
ImGui::Begin("Scene Tree"); ImGui::Begin("Scene Tree");
for (auto& obj : objects)
DrawObjectNode(obj);
if (ImGui::BeginPopupContextWindow("SceneTreeContext", ImGuiPopupFlags_MouseButtonRight))
{
if (ImGui::MenuItem("Create New Object"))
{
auto obj = std::make_shared<Object>("NewObject");
obj->AddComponent<SpriteComponent>();
objects.push_back(obj);
selected = obj;
ImGui::OpenPopup("RenameObject");
}
ImGui::Separator();
if (ImGui::MenuItem("Create New Sprite Object"))
{
auto obj = std::make_shared<Object>("NewSprite");
obj->AddComponent<SpriteComponent>();
objects.push_back(obj);
selected = obj;
ImGui::OpenPopup("RenameObject");
}
ImGui::EndPopup();
}
for (auto &obj : objects)
if (!obj->GetParent()) // Only draw root nodes
DrawObjectNode(obj);
ImGui::End(); ImGui::End();
ImGui::Begin("Inspector"); ImGui::Begin("Inspector");
if (selected) { if (selected)
{
char buffer[128]; char buffer[128];
strcpy(buffer, selected->GetName().c_str()); strcpy(buffer, selected->GetName().c_str());
if (ImGui::InputText("Name", buffer, sizeof(buffer))) if (ImGui::InputText("Name", buffer, sizeof(buffer)))
@ -108,13 +152,15 @@ void Engine::Run() {
selected->SetLocalPosition(pos); selected->SetLocalPosition(pos);
// Add component // Add component
if (ImGui::Button("Add SpriteComponent")) { if (ImGui::Button("Add SpriteComponent"))
{
if (!selected->GetComponent<SpriteComponent>()) if (!selected->GetComponent<SpriteComponent>())
selected->AddComponent<SpriteComponent>(); selected->AddComponent<SpriteComponent>();
} }
// Show SpriteComponent UI // Show SpriteComponent UI
if (auto sprite = selected->GetComponent<SpriteComponent>()) { if (auto sprite = selected->GetComponent<SpriteComponent>())
{
ImGui::Separator(); ImGui::Separator();
ImGui::Text("Sprite Component"); ImGui::Text("Sprite Component");
@ -122,38 +168,47 @@ void Engine::Run() {
std::string norm = sprite->GetNormalMapPath(); std::string norm = sprite->GetNormalMapPath();
ImGui::Text("Texture: %s", tex.c_str()); ImGui::Text("Texture: %s", tex.c_str());
if (ImGui::Button("Load Texture")) { if (ImGui::Button("Load Texture"))
{
auto path = OpenFileDialog(FileDialogType::Images); auto path = OpenFileDialog(FileDialogType::Images);
if (!path.empty()) if (!path.empty())
sprite->SetTexture(path); sprite->SetTexture(path);
} }
ImGui::Text("Normal Map: %s", norm.c_str()); ImGui::Text("Normal Map: %s", norm.c_str());
if (ImGui::Button("Load Normal Map")) { if (ImGui::Button("Load Normal Map"))
{
auto path = OpenFileDialog(FileDialogType::Images); auto path = OpenFileDialog(FileDialogType::Images);
if (!path.empty()) if (!path.empty())
sprite->SetNormalMap(path); sprite->SetNormalMap(path);
} }
if (ImGui::Button("Remove SpriteComponent")) { if (ImGui::Button("Remove SpriteComponent"))
{
selected->RemoveComponent<SpriteComponent>(); selected->RemoveComponent<SpriteComponent>();
} }
} }
} else { }
else
{
ImGui::Text("No object selected."); ImGui::Text("No object selected.");
} }
ImGui::End(); ImGui::End();
// Viewport // Viewport
ImGui::Begin("Viewport", nullptr, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); ImGui::Begin("Viewport", nullptr, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse);
ImVec2 size = ImGui::GetContentRegionAvail(); ImVec2 size = ImGui::GetContentRegionAvail();
Renderer::Resize((int)size.x, (int)size.y); Renderer::Resize((int)size.x, (int)size.y);
Renderer::Begin(); Renderer::Begin();
for (auto& obj : objects) { for (auto &obj : objects)
if (auto sprite = obj->GetComponent<SpriteComponent>()) { {
if (auto sprite = obj->GetComponent<SpriteComponent>())
{
Renderer::DrawSprite(sprite.get(), obj->GetWorldPosition()); Renderer::DrawSprite(sprite.get(), obj->GetWorldPosition());
} }
} }
@ -163,6 +218,32 @@ void Engine::Run() {
ImGui::Image((ImTextureID)(uintptr_t)texID, size, ImVec2(0, 1), ImVec2(1, 0)); ImGui::Image((ImTextureID)(uintptr_t)texID, size, ImVec2(0, 1), ImVec2(1, 0));
ImGui::End(); ImGui::End();
if (ImGui::BeginPopup("RenameObject")) {
static char nameBuffer[128];
static bool once = true;
if (once && selected) {
strcpy(nameBuffer, selected->GetName().c_str());
once = false;
}
ImGui::InputText("##rename", nameBuffer, sizeof(nameBuffer));
if (ImGui::IsKeyPressed(ImGuiKey_Enter) || ImGui::Button("OK")) {
if (selected)
selected->SetName(nameBuffer);
ImGui::CloseCurrentPopup();
once = true;
}
ImGui::SameLine();
if (ImGui::Button("Cancel")) {
ImGui::CloseCurrentPopup();
once = true;
}
ImGui::EndPopup();
}
// ImGui render // ImGui render
ImGui::Render(); ImGui::Render();
int w, h; int w, h;
@ -174,49 +255,94 @@ void Engine::Run() {
} }
} }
void Engine::DrawObjectNode(const std::shared_ptr<Object>& obj) { void Engine::DrawObjectNode(const std::shared_ptr<Object> &obj)
{
ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow |
(obj == selected ? ImGuiTreeNodeFlags_Selected : 0); ImGuiTreeNodeFlags_SpanAvailWidth |
(obj == selected ? ImGuiTreeNodeFlags_Selected : 0);
bool open = ImGui::TreeNodeEx((void*)(intptr_t)obj->id, flags, "%s", obj->GetName().c_str()); bool open = ImGui::TreeNodeEx((void *)(intptr_t)obj->id, flags, "%s", obj->GetName().c_str());
if (ImGui::IsItemClicked()) if (ImGui::IsItemClicked())
selected = obj; selected = obj;
if (ImGui::BeginDragDropSource()) { // === Context Menu on Object ===
if (ImGui::BeginPopupContextItem())
{
if (ImGui::MenuItem("Rename"))
{
selected = obj;
ImGui::OpenPopup("RenameObject");
}
if (ImGui::MenuItem("Delete"))
{
// Remove from parent or root
if (obj->GetParent())
{
obj->GetParent()->RemoveChild(obj.get());
}
else
{
objects.erase(std::remove_if(objects.begin(), objects.end(),
[&](const std::shared_ptr<Object> &o)
{ return o == obj; }),
objects.end());
}
ImGui::EndPopup();
return; // Don't draw deleted node
}
if (ImGui::MenuItem("Create Child"))
{
auto child = std::make_shared<Object>("NewObject");
obj->AddChild(child);
selected = child;
child->SetName("NewObject");
ImGui::OpenPopup("RenameObject");
}
ImGui::EndPopup();
}
// === Drag and Drop ===
if (ImGui::BeginDragDropSource())
{
ImGui::SetDragDropPayload("OBJECT", &obj, sizeof(obj)); ImGui::SetDragDropPayload("OBJECT", &obj, sizeof(obj));
ImGui::Text("Move: %s", obj->GetName().c_str()); ImGui::Text("Move: %s", obj->GetName().c_str());
ImGui::EndDragDropSource(); ImGui::EndDragDropSource();
} }
if (ImGui::BeginDragDropTarget()) { if (ImGui::BeginDragDropTarget())
if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload("OBJECT")) { {
auto dragged = *(std::shared_ptr<Object>*)payload->Data; if (const ImGuiPayload *payload = ImGui::AcceptDragDropPayload("OBJECT"))
{
auto dragged = *(std::shared_ptr<Object> *)payload->Data;
if (dragged != obj) if (dragged != obj)
obj->AddChild(dragged); obj->AddChild(dragged);
} }
ImGui::EndDragDropTarget(); ImGui::EndDragDropTarget();
} }
if (open) { // === Children ===
for (auto& child : obj->GetChildren()) if (open)
{
for (auto &child : obj->GetChildren())
DrawObjectNode(child); DrawObjectNode(child);
ImGui::TreePop(); ImGui::TreePop();
} }
} }
void Engine::SaveScene(const std::string& path) { void Engine::SaveScene(const std::string &path)
{
YAML::Emitter out; YAML::Emitter out;
YAML::Emitter sceneData; YAML::Emitter sceneData;
sceneData << YAML::BeginSeq; sceneData << YAML::BeginSeq;
for (const auto& obj : objects) for (const auto &obj : objects)
obj->Save(sceneData); obj->Save(sceneData);
sceneData << YAML::EndSeq; sceneData << YAML::EndSeq;
std::string sceneString = sceneData.c_str(); std::string sceneString = sceneData.c_str();
unsigned char hash[SHA256_DIGEST_LENGTH]; unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256(reinterpret_cast<const unsigned char*>(sceneString.c_str()), sceneString.size(), hash); SHA256(reinterpret_cast<const unsigned char *>(sceneString.c_str()), sceneString.size(), hash);
std::ostringstream hashHex; std::ostringstream hashHex;
for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i)
@ -235,15 +361,17 @@ void Engine::SaveScene(const std::string& path) {
file << out.c_str(); file << out.c_str();
} }
bool VerifySceneHash(const YAML::Node& root) { bool VerifySceneHash(const YAML::Node &root)
if (!root["scene_hash"] || !root["objects"]) return false; {
if (!root["scene_hash"] || !root["objects"])
return false;
YAML::Emitter sceneOnly; YAML::Emitter sceneOnly;
sceneOnly << root["objects"]; sceneOnly << root["objects"];
std::string sceneString = sceneOnly.c_str(); std::string sceneString = sceneOnly.c_str();
unsigned char hash[SHA256_DIGEST_LENGTH]; unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256(reinterpret_cast<const unsigned char*>(sceneString.c_str()), sceneString.size(), hash); SHA256(reinterpret_cast<const unsigned char *>(sceneString.c_str()), sceneString.size(), hash);
std::ostringstream hashHex; std::ostringstream hashHex;
for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i)
@ -252,28 +380,32 @@ bool VerifySceneHash(const YAML::Node& root) {
return hashHex.str() == root["scene_hash"].as<std::string>(); return hashHex.str() == root["scene_hash"].as<std::string>();
} }
void Engine::LoadScene(const std::string& path) { void Engine::LoadScene(const std::string &path)
{
YAML::Node root = YAML::LoadFile(path); YAML::Node root = YAML::LoadFile(path);
if (!root["engine_version"] || !root["format_version"] || !root["scene_name"]) { if (!root["engine_version"] || !root["format_version"] || !root["scene_name"])
{
std::cerr << "[LoadScene] Missing required metadata!\n"; std::cerr << "[LoadScene] Missing required metadata!\n";
return; return;
} }
if (root["engine_version"].as<std::string>() != "0.1.0") { if (root["engine_version"].as<std::string>() != "0.1.0")
{
std::cerr << "[LoadScene] Version mismatch! Expected 0.1.0, got " << root["engine_version"].as<std::string>() << "\n"; std::cerr << "[LoadScene] Version mismatch! Expected 0.1.0, got " << root["engine_version"].as<std::string>() << "\n";
return; return;
} }
if (!VerifySceneHash(root)) { if (!VerifySceneHash(root))
{
std::cerr << "[LoadScene] Scene hash does not match! File may be corrupted or tampered.\n"; std::cerr << "[LoadScene] Scene hash does not match! File may be corrupted or tampered.\n";
return;
} }
objects.clear(); objects.clear();
const auto& objectArray = root["objects"]; const auto &objectArray = root["objects"];
for (const auto& node : objectArray) { for (const auto &node : objectArray)
auto obj = std::make_shared<Object>("(loaded)"); {
auto obj = std::make_shared<Object>("[DefaultObject]");
obj->Load(node); obj->Load(node);
objects.push_back(obj); objects.push_back(obj);
} }
@ -281,9 +413,8 @@ void Engine::LoadScene(const std::string& path) {
std::cout << "[LoadScene] Loaded scene: " << root["scene_name"].as<std::string>() << "\n"; std::cout << "[LoadScene] Loaded scene: " << root["scene_name"].as<std::string>() << "\n";
} }
void Engine::Shutdown()
{
void Engine::Shutdown() {
ImGui_ImplOpenGL3_Shutdown(); ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplGlfw_Shutdown(); ImGui_ImplGlfw_Shutdown();
ImGui::DestroyContext(); ImGui::DestroyContext();

View File

@ -1,42 +1,73 @@
#include "Renderer.h" #include "Renderer.h"
#include "Components/SpriteComponent.h" #include "Components/SpriteComponent.h"
#include "utils/Shader.h"
#include <GL/glew.h>
#include <glm/glm.hpp>
#include <iostream> #include <iostream>
static Shader spriteShader;
GLuint Renderer::fbo = 0; GLuint Renderer::fbo = 0;
GLuint Renderer::textureColorBuffer = 0; GLuint Renderer::textureColorBuffer = 0;
GLuint Renderer::rbo = 0; GLuint Renderer::rbo = 0;
GLuint Renderer::quadVAO = 0;
GLuint Renderer::quadVBO = 0;
int Renderer::width = 1280; int Renderer::width = 1280;
int Renderer::height = 720; int Renderer::height = 720;
void Renderer::InitQuad() {
float vertices[] = {
// pos // uv
0.f, 0.f, 0.f, 0.f,
1.f, 0.f, 1.f, 0.f,
1.f, 1.f, 1.f, 1.f,
0.f, 1.f, 0.f, 1.f
};
glGenVertexArrays(1, &quadVAO);
glBindVertexArray(quadVAO);
glGenBuffers(1, &quadVBO);
glBindBuffer(GL_ARRAY_BUFFER, quadVBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glEnableVertexAttribArray(0); // position
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)0);
glEnableVertexAttribArray(1); // UV
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (void*)(2 * sizeof(float)));
glBindVertexArray(0);
}
void Renderer::Init() { void Renderer::Init() {
glGenFramebuffers(1, &fbo); glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo); glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glGenTextures(1, &textureColorBuffer); glGenTextures(1, &textureColorBuffer);
glBindTexture(GL_TEXTURE_2D, textureColorBuffer); glBindTexture(GL_TEXTURE_2D, textureColorBuffer);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureColorBuffer, 0);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
GL_TEXTURE_2D, textureColorBuffer, 0);
glGenRenderbuffers(1, &rbo); glGenRenderbuffers(1, &rbo);
glBindRenderbuffer(GL_RENDERBUFFER, rbo); glBindRenderbuffer(GL_RENDERBUFFER, rbo);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height); glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rbo);
GL_RENDERBUFFER, rbo);
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
std::cerr << "Framebuffer is not complete!" << std::endl; std::cerr << "Framebuffer is not complete!" << std::endl;
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
InitQuad();
spriteShader.LoadFromFile("src/assets/shaders/sprite.vert", "src/assets/shaders/sprite.frag");
} }
void Renderer::Resize(int w, int h) { void Renderer::Resize(int w, int h) {
if (w == width && h == height) return; if (w == width && h == height) return;
width = w; width = w;
height = h; height = h;
@ -61,6 +92,23 @@ void Renderer::End() {
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
} }
void Renderer::DrawSprite(SpriteComponent* sprite, const glm::vec2& pos) {
if (!sprite || sprite->GetTextureID() == 0) return;
spriteShader.Use();
spriteShader.SetVec2("uPos", pos);
spriteShader.SetVec2("uSize", sprite->GetSize());
spriteShader.SetVec2("uScreen", glm::vec2(width, height));
spriteShader.SetInt("uTex", 0);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, sprite->GetTextureID());
glBindVertexArray(quadVAO);
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
glBindVertexArray(0);
}
GLuint Renderer::GetRenderTexture() { GLuint Renderer::GetRenderTexture() {
return textureColorBuffer; return textureColorBuffer;
} }
@ -68,19 +116,3 @@ GLuint Renderer::GetRenderTexture() {
glm::ivec2 Renderer::GetSize() { glm::ivec2 Renderer::GetSize() {
return { width, height }; return { width, height };
} }
void Renderer::DrawSprite(SpriteComponent* sprite, const glm::vec2& pos) {
GLuint tex = sprite->GetTextureID();
if (!tex) return;
glBindTexture(GL_TEXTURE_2D, tex);
glBegin(GL_QUADS);
float size = 100.0f;
glTexCoord2f(0, 0); glVertex2f(pos.x, pos.y);
glTexCoord2f(1, 0); glVertex2f(pos.x + size, pos.y);
glTexCoord2f(1, 1); glVertex2f(pos.x + size, pos.y + size);
glTexCoord2f(0, 1); glVertex2f(pos.x, pos.y + size);
glEnd();
}

View File

@ -7,7 +7,7 @@ class SpriteComponent;
class Renderer { class Renderer {
public: public:
static void Init(); static void Init();
static void Resize(int width, int height); static void Resize(int w, int h);
static void Begin(); static void Begin();
static void End(); static void End();
static void DrawSprite(SpriteComponent* sprite, const glm::vec2& pos); static void DrawSprite(SpriteComponent* sprite, const glm::vec2& pos);
@ -17,4 +17,8 @@ public:
private: private:
static GLuint fbo, textureColorBuffer, rbo; static GLuint fbo, textureColorBuffer, rbo;
static int width, height; static int width, height;
static GLuint shader, quadVAO, quadVBO;
static void InitQuad();
static GLuint LoadShader(const char* vertexSrc, const char* fragmentSrc);
}; };

View File

@ -1,7 +1,17 @@
#include "Engine.h" #include "Engine.h"
#include <iostream>
int main() { int main() {
Engine engine; try {
engine.Run(); Engine engine;
engine.Run();
} catch (const std::exception& e) {
std::cerr << "[Fatal Error] " << e.what() << std::endl;
return 1;
} catch (...) {
std::cerr << "[Fatal Error] Unknown exception occurred!" << std::endl;
return 2;
}
return 0; return 0;
} }

116
src/src/utils/Shader.cpp Normal file
View File

@ -0,0 +1,116 @@
#include "Shader.h"
#include <fstream>
#include <sstream>
#include <iostream>
Shader::Shader() {}
Shader::~Shader() { Clear(); }
void Shader::Clear() {
if (id != 0) {
glDeleteProgram(id);
id = 0;
}
uniformCache.clear();
}
std::string Shader::ReadFile(const std::string& path) {
std::ifstream stream(path);
std::stringstream buffer;
buffer << stream.rdbuf();
return buffer.str();
}
GLuint Shader::Compile(GLenum type, const std::string& source) {
GLuint shader = glCreateShader(type);
const char* src = source.c_str();
glShaderSource(shader, 1, &src, nullptr);
glCompileShader(shader);
GLint success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
char info[512];
glGetShaderInfoLog(shader, 512, nullptr, info);
std::cerr << "[Shader Compile Error]:\n" << info << std::endl;
}
return shader;
}
bool Shader::LoadFromFile(const std::string& vertexPath, const std::string& fragmentPath) {
return LoadFromSource(ReadFile(vertexPath), ReadFile(fragmentPath));
}
bool Shader::LoadFromSource(const std::string& vertexSrc, const std::string& fragmentSrc) {
Clear();
GLuint vertex = Compile(GL_VERTEX_SHADER, vertexSrc);
GLuint fragment = Compile(GL_FRAGMENT_SHADER, fragmentSrc);
id = glCreateProgram();
glAttachShader(id, vertex);
glAttachShader(id, fragment);
glLinkProgram(id);
glDeleteShader(vertex);
glDeleteShader(fragment);
GLint success;
glGetProgramiv(id, GL_LINK_STATUS, &success);
if (!success) {
char info[512];
glGetProgramInfoLog(id, 512, nullptr, info);
std::cerr << "[Shader Link Error]:\n" << info << std::endl;
return false;
}
return true;
}
void Shader::Use() const {
glUseProgram(id);
}
GLuint Shader::GetID() const {
return id;
}
GLint Shader::GetUniformLocation(const std::string& name) {
if (uniformCache.contains(name)) return uniformCache[name];
GLint loc = glGetUniformLocation(id, name.c_str());
if (loc == -1)
std::cerr << "[Shader] Warning: Uniform not found: " << name << "\n";
uniformCache[name] = loc;
return loc;
}
// === Uniform Setters ===
void Shader::SetBool(const std::string& name, bool value) {
glUniform1i(GetUniformLocation(name), (int)value);
}
void Shader::SetInt(const std::string& name, int value) {
glUniform1i(GetUniformLocation(name), value);
}
void Shader::SetFloat(const std::string& name, float value) {
glUniform1f(GetUniformLocation(name), value);
}
void Shader::SetVec2(const std::string& name, const glm::vec2& value) {
glUniform2fv(GetUniformLocation(name), 1, &value[0]);
}
void Shader::SetVec3(const std::string& name, const glm::vec3& value) {
glUniform3fv(GetUniformLocation(name), 1, &value[0]);
}
void Shader::SetVec4(const std::string& name, const glm::vec4& value) {
glUniform4fv(GetUniformLocation(name), 1, &value[0]);
}
void Shader::SetMat4(const std::string& name, const glm::mat4& mat) {
glUniformMatrix4fv(GetUniformLocation(name), 1, GL_FALSE, &mat[0][0]);
}

36
src/src/utils/Shader.h Normal file
View File

@ -0,0 +1,36 @@
#pragma once
#include <string>
#include <unordered_map>
#include <glm/glm.hpp>
#include <gl/glew.h>
class Shader {
public:
Shader();
~Shader();
bool LoadFromFile(const std::string& vertexPath, const std::string& fragmentPath);
bool LoadFromSource(const std::string& vertexSrc, const std::string& fragmentSrc);
void Use() const;
GLuint GetID() const;
// Setters
void SetBool(const std::string& name, bool value);
void SetInt(const std::string& name, int value);
void SetFloat(const std::string& name, float value);
void SetVec2(const std::string& name, const glm::vec2& value);
void SetVec3(const std::string& name, const glm::vec3& value);
void SetVec4(const std::string& name, const glm::vec4& value);
void SetMat4(const std::string& name, const glm::mat4& mat);
private:
GLuint id = 0;
std::unordered_map<std::string, GLint> uniformCache;
std::string ReadFile(const std::string& path);
GLuint Compile(GLenum type, const std::string& source);
GLint GetUniformLocation(const std::string& name);
void Clear();
};