diff --git a/build.log b/build.log index c231ec6..8971de4 100644 --- a/build.log +++ b/build.log @@ -1,3 +1,4 @@ -[LINK] g++ src\build\src\Engine.o src\build\src\main.o src\build\src\Renderer.o src\build\src\Components\CameraComponent.o src\build\src\Components\LightComponent.o src\build\src\Components\SpriteComponent.o src\build\src\Entitys\Object.o src\build\src\utils\FileDialog.o src\build\src\utils\Logging.o src\build\src\utils\Shader.o src\build\src\utils\utils.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 -[ERROR] Runtime crash -Command 'src\build\app.exe' returned non-zero exit status 3221225477. +[COMPILE] g++ -std=c++20 -Wall -Isrc/include -Isrc/vendor -Isrc/vendor/imgui -IC:/msys64/mingw64/include -Isrc/include -Isrc/vendor -Isrc/vendor/imgui -IC:/msys64/mingw64/include -Isrc\vendor\imgui -IC:\msys64\mingw64\lib\libyaml-cpp.a -MMD -MP -c src\src\Components\TilemapComponent.cpp -o src\build\src\Components\TilemapComponent.o +[COMPILE] g++ -std=c++20 -Wall -Isrc/include -Isrc/vendor -Isrc/vendor/imgui -IC:/msys64/mingw64/include -Isrc/include -Isrc/vendor -Isrc/vendor/imgui -IC:/msys64/mingw64/include -Isrc\vendor\imgui -IC:\msys64\mingw64\lib\libyaml-cpp.a -MMD -MP -c src\src\utils\utils.cpp -o src\build\src\utils\utils.o +[LINK] g++ src\build\src\Engine.o src\build\src\main.o src\build\src\Renderer.o src\build\src\Components\CameraComponent.o src\build\src\Components\LightComponent.o src\build\src\Components\SpriteComponent.o src\build\src\Components\TilemapComponent.o src\build\src\Entitys\Object.o src\build\src\utils\EngineConfig.o src\build\src\utils\FileDialog.o src\build\src\utils\Logging.o src\build\src\utils\Shader.o src\build\src\utils\utils.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 +[RUN] Executed app.exe successfully. diff --git a/imgui.ini b/imgui.ini index ec42917..d622fe7 100644 --- a/imgui.ini +++ b/imgui.ini @@ -47,12 +47,18 @@ Size=1141,287 Collapsed=0 DockId=0x00000008,0 +[Window][Tilemap Editor] +Pos=265,19 +Size=1141,869 +Collapsed=0 +DockId=0x00000007,1 + [Docking][Data] DockSpace ID=0x11111111 Window=0x1BBC0F80 Pos=0,19 Size=1920,1158 Split=X DockNode ID=0x00000003 Parent=0x11111111 SizeRef=1406,1158 Split=X DockNode ID=0x00000001 Parent=0x00000003 SizeRef=263,701 HiddenTabBar=1 Selected=0x12EF0F59 DockNode ID=0x00000002 Parent=0x00000003 SizeRef=1141,701 Split=Y Selected=0xC450F867 - DockNode ID=0x00000007 Parent=0x00000002 SizeRef=606,869 CentralNode=1 HiddenTabBar=1 Selected=0xC450F867 + DockNode ID=0x00000007 Parent=0x00000002 SizeRef=606,869 CentralNode=1 Selected=0xC450F867 DockNode ID=0x00000008 Parent=0x00000002 SizeRef=606,287 HiddenTabBar=1 Selected=0xEA83D666 DockNode ID=0x00000004 Parent=0x11111111 SizeRef=512,1158 Split=Y Selected=0x36DC96AB DockNode ID=0x00000005 Parent=0x00000004 SizeRef=407,835 HiddenTabBar=1 Selected=0x36DC96AB diff --git a/src/assets/scenes/test.cene b/src/assets/scenes/test.cene index 5b80b8b..a49694b 100644 --- a/src/assets/scenes/test.cene +++ b/src/assets/scenes/test.cene @@ -32,36 +32,6 @@ objects: texture: C:\Users\spenc\OneDrive\Pictures\49555.jpg normalMap: "" children: [] - - name: Bark - position: [1024, 0] - layer: -1 - components: - - type: SpriteComponent - texture: C:\Users\spenc\OneDrive\Pictures\textures\bark_willow_02_diff_1k.png - normalMap: C:\Users\spenc\OneDrive\Pictures\textures\bark_willow_02_nor_gl_1k.png - children: [] - - name: Sun - position: [-5000, -5000] - layer: 1 - components: - - type: LightComponent - color: - - 0.990196049 - - 0.943370163 - - 0.791186035 - intensity: 2.0999999 - radius: 100000000 - falloff: 0.100000001 - type: 0 - children: [] - - name: Rocks - position: [0, 0] - layer: -1 - components: - - type: SpriteComponent - texture: C:\Users\spenc\OneDrive\Pictures\ganges_river_pebbles_diff_1k.png - normalMap: C:\Users\spenc\OneDrive\Pictures\ganges_river_pebbles_nor_gl_1k.png - children: [] - name: World position: [-436, 248] layer: 0 diff --git a/src/assets/scenes/tilemap.cene b/src/assets/scenes/tilemap.cene new file mode 100644 index 0000000..089bea3 --- /dev/null +++ b/src/assets/scenes/tilemap.cene @@ -0,0 +1,122 @@ +engine_version: 0.1.0 +scene_name: tilemap +scene_hash: 0ffd3035689b66c87af235dc22993fa7dac416ee447c93a1c4594a6b8aafc289 +format_version: 1 +objects: + - name: Hello, Create + position: [0, 0] + layer: 0 + components: + - type: TilemapComponent + gridSize: + - 10 + - 10 + tileSize: + - 32 + - 32 + tiles: + - 961 + - 961 + - 961 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - 816 + - -1 + - 945 + - 970 + - 970 + - -1 + - -1 + - -1 + - -1 + - 813 + - -1 + - -1 + - -1 + - 970 + - -1 + - -1 + - -1 + - -1 + - -1 + - 748 + - 840 + - -1 + - 970 + - -1 + - 840 + - -1 + - -1 + - -1 + - 812 + - -1 + - 805 + - -1 + - 845 + - 877 + - -1 + - 904 + - -1 + - -1 + - -1 + - 970 + - -1 + - -1 + - -1 + - -1 + - 970 + - -1 + - -1 + - -1 + - -1 + - -1 + - 875 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + - -1 + atlasPath: C:\Users\spenc\OneDrive\Pictures\6656e7221e49a1774d2fb280357e56f8d25d9d95.png + atlasTileSize: + - 32 + - 32 + children: [] \ No newline at end of file diff --git a/src/assets/scenes/world.cene b/src/assets/scenes/world.cene index 08fe5aa..e926597 100644 --- a/src/assets/scenes/world.cene +++ b/src/assets/scenes/world.cene @@ -40,36 +40,6 @@ objects: texture: C:\Users\spenc\OneDrive\Pictures\blue_logo.png normalMap: C:\Users\spenc\OneDrive\Pictures\images.jpg children: [] - - name: Bark - position: [1024, 0] - layer: -1 - components: - - type: SpriteComponent - texture: C:\Users\spenc\OneDrive\Pictures\textures\bark_willow_02_diff_1k.png - normalMap: C:\Users\spenc\OneDrive\Pictures\textures\bark_willow_02_nor_gl_1k.png - children: [] - - name: Sun - position: [-5000, -5000] - layer: 1 - components: - - type: LightComponent - color: - - 0.992156863 - - 0.984313726 - - 0.827450991 - intensity: 1.25 - radius: 100000000 - falloff: 0.100000001 - type: 0 - children: [] - - name: Rocks - position: [0, 0] - layer: -1 - components: - - type: SpriteComponent - texture: C:\Users\spenc\OneDrive\Pictures\ganges_river_pebbles_diff_1k.png - normalMap: C:\Users\spenc\OneDrive\Pictures\ganges_river_pebbles_nor_gl_1k.png - children: [] - name: World position: [-436, 248] layer: 0 diff --git a/src/assets/shaders/tilemap.frag b/src/assets/shaders/tilemap.frag new file mode 100644 index 0000000..df2cc94 --- /dev/null +++ b/src/assets/shaders/tilemap.frag @@ -0,0 +1,12 @@ +#version 330 core +in vec2 vUV; +out vec4 FragColor; + +uniform sampler2D uTex; + +void main() +{ + vec4 tex = texture(uTex, vUV); + if (tex.a < 0.1) discard; + FragColor = tex; +} diff --git a/src/assets/shaders/tilemap.vert b/src/assets/shaders/tilemap.vert new file mode 100644 index 0000000..445920c --- /dev/null +++ b/src/assets/shaders/tilemap.vert @@ -0,0 +1,20 @@ +#version 330 core +layout(location = 0) in vec2 aPos; +layout(location = 1) in vec2 aUV; + +out vec2 vUV; + +uniform vec2 uPos; +uniform vec2 uSize; +uniform vec2 uScreen; +uniform vec2 uUVMin; +uniform vec2 uUVMax; + +void main() +{ + vec2 pos = uPos + aPos * uSize; + gl_Position = vec4((pos / uScreen) * 2.0 - 1.0, 0.0, 1.0); + + // Remap UV to tile slice + vUV = mix(uUVMin, uUVMax, aUV); +} diff --git a/src/assets/shaders/unlit.frag b/src/assets/shaders/unlit.frag new file mode 100644 index 0000000..821a7aa --- /dev/null +++ b/src/assets/shaders/unlit.frag @@ -0,0 +1,12 @@ +#version 330 core +in vec2 vUV; +out vec4 FragColor; +uniform sampler2D uTex; +void main() { + vec4 color = texture(uTex, vUV); + + if (color.a < 0.01) + discard; + + FragColor = color; +} diff --git a/src/assets/shaders/unlit.vert b/src/assets/shaders/unlit.vert new file mode 100644 index 0000000..62def40 --- /dev/null +++ b/src/assets/shaders/unlit.vert @@ -0,0 +1,13 @@ +#version 330 core +layout (location = 0) in vec2 aPos; +layout (location = 1) in vec2 aUV; +out vec2 vUV; +uniform vec2 uPos; +uniform vec2 uSize; +uniform vec2 uScreen; +void main() { + vec2 worldPos = aPos * uSize + uPos; + vUV = aUV; + vec2 ndc = (worldPos / uScreen) * 2.0 - 1.0; + gl_Position = vec4(ndc.x, -ndc.y, 0.0, 1.0); +} diff --git a/src/src/Components/TilemapComponent.cpp b/src/src/Components/TilemapComponent.cpp new file mode 100644 index 0000000..ccd3de7 --- /dev/null +++ b/src/src/Components/TilemapComponent.cpp @@ -0,0 +1,258 @@ +#include "TilemapComponent.h" +#include "../utils/Logging.h" +#include "../utils/FileDialog.h" +#include "../utils/utils.h" +#include <stb_image.h> +#include <cstdio> +#include <cstring> +#include <GLFW/glfw3.h> + +static int g_selectedTileIndex = 0; + + +// Constructor: initialize with a default grid (optional) +TilemapComponent::TilemapComponent(Object *owner) + : Component(owner) +{ + // For example, create an empty 10x10 tilemap + SetGridSize(glm::ivec2(10, 10)); + // Optionally set a default tile size + m_TileSize = glm::ivec2(32, 32); +} + +// Set grid dimensions and resize tile array; initially all -1 (empty) +void TilemapComponent::SetGridSize(const glm::ivec2 &size) +{ + m_GridSize = size; + m_Tiles.assign(size.x * size.y, -1); +} + +// Set individual tile at (x, y) +void TilemapComponent::SetTile(int x, int y, int tileIndex) +{ + if (x < 0 || y < 0 || x >= m_GridSize.x || y >= m_GridSize.y) + return; + m_Tiles[y * m_GridSize.x + x] = tileIndex; +} + +int TilemapComponent::GetTile(int x, int y) const +{ + if (x < 0 || y < 0 || x >= m_GridSize.x || y >= m_GridSize.y) + return -1; + return m_Tiles[y * m_GridSize.x + x]; +} + +void TilemapComponent::SetTileSize(const glm::ivec2 &size) +{ + m_TileSize = size; +} + +// Set atlas (tileset) settings; also update dimensions +void TilemapComponent::SetAtlas(const std::string &path, int atlasTileWidth, int atlasTileHeight) +{ + m_AtlasPath = path; + m_AtlasTileWidth = atlasTileWidth; + m_AtlasTileHeight = atlasTileHeight; + UpdateAtlasDimensions(); +} + +// Helper: load atlas image info using stbi_info to compute columns and rows +void TilemapComponent::UpdateAtlasDimensions() +{ + if (m_AtlasPath.empty()) + return; + int texW, texH, comp; + if (stbi_info(m_AtlasPath.c_str(), &texW, &texH, &comp)) + { + m_AtlasCols = texW / m_AtlasTileWidth; + m_AtlasRows = texH / m_AtlasTileHeight; + } + else + { + m_AtlasCols = m_AtlasRows = 0; + Logger::LogError("Failed to get atlas info: %s", m_AtlasPath.c_str()); + } +} + +// Serialization: store grid size, tile size, tile data, and atlas settings. +void TilemapComponent::Save(YAML::Emitter &out) const +{ + out << YAML::BeginMap; + out << YAML::Key << "type" << YAML::Value << "TilemapComponent"; + out << YAML::Key << "gridSize" << YAML::Value << std::vector<int>{m_GridSize.x, m_GridSize.y}; + out << YAML::Key << "tileSize" << YAML::Value << std::vector<int>{m_TileSize.x, m_TileSize.y}; + out << YAML::Key << "tiles" << YAML::Value << m_Tiles; + out << YAML::Key << "atlasPath" << YAML::Value << m_AtlasPath; + out << YAML::Key << "atlasTileSize" << YAML::Value << std::vector<int>{m_AtlasTileWidth, m_AtlasTileHeight}; + out << YAML::EndMap; +} + +// Deserialization: load saved data +void TilemapComponent::Load(const YAML::Node &node) +{ + if (node["gridSize"]) + { + auto vec = node["gridSize"].as<std::vector<int>>(); + if (vec.size() == 2) + m_GridSize = glm::ivec2(vec[0], vec[1]); + } + if (node["tileSize"]) + { + auto vec = node["tileSize"].as<std::vector<int>>(); + if (vec.size() == 2) + m_TileSize = glm::ivec2(vec[0], vec[1]); + } + if (node["tiles"]) + m_Tiles = node["tiles"].as<std::vector<int>>(); + if (node["atlasPath"]) + m_AtlasPath = node["atlasPath"].as<std::string>(); + if (node["atlasTileSize"]) + { + auto vec = node["atlasTileSize"].as<std::vector<int>>(); + if (vec.size() == 2) + { + m_AtlasTileWidth = vec[0]; + m_AtlasTileHeight = vec[1]; + } + } + // Update atlas dimensions after loading settings + UpdateAtlasDimensions(); +} + +// ----------------------------- +// Editor UI +// ----------------------------- +void TilemapComponent::DrawEditorUI() +{ + ImGui::Begin("Tilemap Editor"); + + // --- Atlas Settings --- + if (ImGui::CollapsingHeader("Tileset Atlas Settings")) + { + // Display current atlas path + ImGui::Text("Current Atlas: %s", m_AtlasPath.empty() ? "None" : m_AtlasPath.c_str()); + if (ImGui::Button("Import Atlas")) + { + std::string path = OpenFileDialog(FileDialogType::Images); + if (!path.empty()) + { + // For simplicity, default atlas tile dimensions are set via input later. + m_AtlasPath = path; + UpdateAtlasDimensions(); + } + } + // Configure atlas tile size + int atlasW = m_AtlasTileWidth, atlasH = m_AtlasTileHeight; + if (ImGui::InputInt("Atlas Tile Width", &atlasW)) + { + m_AtlasTileWidth = atlasW; + UpdateAtlasDimensions(); + } + if (ImGui::InputInt("Atlas Tile Height", &atlasH)) + { + m_AtlasTileHeight = atlasH; + UpdateAtlasDimensions(); + } + ImGui::Text("Atlas Grid: %d columns x %d rows", m_AtlasCols, m_AtlasRows); + } + + // --- Map Grid Settings --- + if (ImGui::CollapsingHeader("Map Settings")) + { + glm::ivec2 grid = m_GridSize; + int gridW = grid.x, gridH = grid.y; + if (ImGui::InputInt("Map Grid Width", &gridW) || ImGui::InputInt("Map Grid Height", &gridH)) + { + SetGridSize(glm::ivec2(gridW, gridH)); + } + glm::ivec2 ts = m_TileSize; + int tileW = ts.x, tileH = ts.y; + if (ImGui::InputInt("Tile Width", &tileW) || ImGui::InputInt("Tile Height", &tileH)) + { + SetTileSize(glm::ivec2(tileW, tileH)); + } + } + + // --- Tileset Palette Editor --- + if (ImGui::CollapsingHeader("Tileset Palette")) + { + if (m_AtlasPath.empty()) + { + ImGui::Text("No atlas loaded."); + } + else + { + // Retrieve texture dimensions via stbi_info (for preview UV calculation) + int texW, texH, comp; + if (stbi_info(m_AtlasPath.c_str(), &texW, &texH, &comp)) + { + float uvTileW = float(m_AtlasTileWidth) / float(texW); + float uvTileH = float(m_AtlasTileHeight) / float(texH); + // Display each tile as an image button + ImGui::Text("Select a tile:"); + for (int row = 0; row < m_AtlasRows; row++) + { + for (int col = 0; col < m_AtlasCols; col++) + { + int tileIndex = row * m_AtlasCols + col; + // UV coordinates for this tile + float uvX = float(col) * uvTileW; + float uvY = float(row) * uvTileH; + ImVec2 uv0(uvX, uvY); + ImVec2 uv1(uvX + uvTileW, uvY + uvTileH); + + char buf[32]; + sprintf(buf, "##tile_%d", tileIndex); + // Display a button. Here, 32x32 pixels is used for preview size. + GLuint textureID = LoadTextureIfNeeded(m_AtlasPath); + if (ImGui::ImageButton(buf, (ImTextureID)(uintptr_t)textureID, ImVec2(32, 32), uv0, uv1)) + { + g_selectedTileIndex = tileIndex; + } + + ImGui::SameLine(); + } + ImGui::NewLine(); + } + ImGui::Text("Selected Tile: %d", g_selectedTileIndex); + } + else + { + ImGui::Text("Failed to load atlas info."); + } + } + } + + // --- Map Editor --- + if (ImGui::CollapsingHeader("Map Editor")) + { + if (m_GridSize.x == 0 || m_GridSize.y == 0) + { + ImGui::Text("No map created."); + } + else + { + ImGui::Text("Click a cell to paint with the selected tile."); + ImGui::BeginChild("TilemapGrid", ImVec2(0, 300), true); + for (int y = 0; y < m_GridSize.y; y++) + { + for (int x = 0; x < m_GridSize.x; x++) + { + int tile = GetTile(x, y); + char label[32]; + sprintf(label, "%d##%d_%d", tile, x, y); + if (ImGui::Button(label, ImVec2(30, 30))) + { + SetTile(x, y, g_selectedTileIndex); + } + ImGui::SameLine(); + } + ImGui::NewLine(); + } + ImGui::EndChild(); + } + } + + ImGui::End(); +} + diff --git a/src/src/Components/TilemapComponent.h b/src/src/Components/TilemapComponent.h new file mode 100644 index 0000000..0e116d1 --- /dev/null +++ b/src/src/Components/TilemapComponent.h @@ -0,0 +1,56 @@ +#pragma once +#include "Component.h" +#include <glm/glm.hpp> +#include <yaml-cpp/yaml.h> +#include <string> +#include <vector> +#include <imgui.h> + +class TilemapComponent : public Component +{ +public: + TilemapComponent(Object* owner); + + // Editor UI for tilemap + void DrawEditorUI(); + + // Set up grid and tile dimensions + void SetGridSize(const glm::ivec2& size); + void SetTileSize(const glm::ivec2& size); + void SetTile(int x, int y, int tileIndex); + int GetTile(int x, int y) const; + + // Getters + const glm::ivec2& GetGridSize() const { return m_GridSize; } + const glm::ivec2& GetTileSize() const { return m_TileSize; } + const std::vector<int>& GetTiles() const { return m_Tiles; } + const std::string& GetAtlasPath() const { return m_AtlasPath; } + + int GetAtlasTileWidth() const { return m_AtlasTileWidth; } + int GetAtlasTileHeight() const { return m_AtlasTileHeight; } + int GetAtlasCols() const { return m_AtlasCols; } + int GetAtlasRows() const { return m_AtlasRows; } + + // Atlas setup: import an atlas image and configure its tile dimensions. + void SetAtlas(const std::string& path, int atlasTileWidth, int atlasTileHeight); + + // Serialization + void Save(YAML::Emitter& out) const override; + void Load(const YAML::Node& node) override; + std::string GetName() const override { return "TilemapComponent"; } + +private: + // Grid and tile data + glm::ivec2 m_GridSize {0, 0}; + glm::ivec2 m_TileSize {32, 32}; + std::vector<int> m_Tiles; + + // Atlas (tileset) settings + std::string m_AtlasPath; + int m_AtlasTileWidth = 32; + int m_AtlasTileHeight = 32; + int m_AtlasCols = 0; + int m_AtlasRows = 0; + + void UpdateAtlasDimensions(); +}; diff --git a/src/src/Engine.cpp b/src/src/Engine.cpp index 82adcd0..d4a4e1c 100644 --- a/src/src/Engine.cpp +++ b/src/src/Engine.cpp @@ -4,6 +4,8 @@ #include "components/SpriteComponent.h" #include "components/CameraComponent.h" #include "components/LightComponent.h" +#include "components/TilemapComponent.h" + #include "utils/FileDialog.h" #include "utils/Logging.h" @@ -162,6 +164,11 @@ void DrawInspectorUI(std::shared_ptr<Object> selected) if (!selected->GetComponent<LightComponent>()) selected->AddComponent<LightComponent>(); } + if (ImGui::Button("Add TilemapComponent")) + { + if (!selected->GetComponent<TilemapComponent>()) + selected->AddComponent<TilemapComponent>(); + } // Sprite UI... if (auto sprite = selected->GetComponent<SpriteComponent>()) @@ -230,7 +237,23 @@ void DrawInspectorUI(std::shared_ptr<Object> selected) selected->RemoveComponent<LightComponent>(); } + if (auto tilemap = selected->GetComponent<TilemapComponent>()) + { + ImGui::SeparatorText("Tilemap Component"); + + ImGui::Text("Refer to Tilemap Editor Window"); + + if (ImGui::Button("Remove TilemapComponent")) + selected->RemoveComponent<TilemapComponent>(); + } + ImGui::End(); + + if (auto tilemap = selected->GetComponent<TilemapComponent>()) + { + tilemap->DrawEditorUI(); + + } } diff --git a/src/src/Renderer.cpp b/src/src/Renderer.cpp index 3c62147..3cdcd51 100644 --- a/src/src/Renderer.cpp +++ b/src/src/Renderer.cpp @@ -2,18 +2,22 @@ #include "Components/SpriteComponent.h" #include "utils/Shader.h" #include "utils/Logging.h" +#include "utils/EngineConfig.h" +#include "utils/utils.h" +#include "stb_image.h" #include <GL/glew.h> #include <glm/glm.hpp> #include <iostream> +#include <map> static Shader spriteShader; +static Shader unlitShader; GLuint Renderer::fbo = 0; GLuint Renderer::textureColorBuffer = 0; GLuint Renderer::defaultNormalMap = 0; - GLuint Renderer::rbo = 0; GLuint Renderer::quadVAO = 0; GLuint Renderer::quadVBO = 0; @@ -22,9 +26,7 @@ int Renderer::height = 720; int Renderer::s_DrawCalls = 0; std::vector<Light> Renderer::s_Lights; - - - +static Shader tilemapShader; void Renderer::InitQuad() { @@ -74,7 +76,10 @@ void Renderer::Init() { InitQuad(); + // Load lit shader spriteShader.LoadFromFile("src/assets/shaders/sprite.vert", "src/assets/shaders/sprite.frag"); + // Load unlit shader + unlitShader.LoadFromFile("src/assets/shaders/unlit.vert", "src/assets/shaders/unlit.frag"); // Create a 1x1 flat normal map (RGB: 128,128,255) unsigned char flatNormal[3] = { 128, 128, 255 }; @@ -115,33 +120,64 @@ void Renderer::End() { glBindFramebuffer(GL_FRAMEBUFFER, 0); } -//void Renderer::DrawSprite(SpriteComponent* sprite, const glm::vec2& pos, float scale) { -// GLuint tex = sprite->GetTextureID(); -// if (!tex) return; -// -// glBindTexture(GL_TEXTURE_2D, tex); -// glBegin(GL_QUADS); -// float size = 100.0f * scale; -// -// 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(); -//} - - void Renderer::ClearLights() { s_Lights.clear(); } - - void Renderer::AddLight(const glm::vec2& screenPos, const glm::vec3& color, float intensity, float radius) { if (s_Lights.size() >= 8) return; s_Lights.push_back({screenPos, color, intensity, radius}); } +void Renderer::DrawTilemap(TilemapComponent* tilemap, const glm::vec2& worldPos, float zoom, const glm::vec2& cameraPos) { + if (!tilemap || tilemap->GetAtlasPath().empty()) return; + + glm::ivec2 grid = tilemap->GetGridSize(); + glm::ivec2 tileSize = tilemap->GetTileSize(); + int cols = tilemap->GetAtlasCols(); + int rows = tilemap->GetAtlasRows(); + + const std::string& atlasPath = tilemap->GetAtlasPath(); + GLuint atlasTex = LoadTextureIfNeeded(atlasPath); + if (atlasTex == 0) return; + + tilemapShader.Use(); + tilemapShader.SetVec2("uScreen", glm::vec2(width, height)); + tilemapShader.SetInt("uTex", 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, atlasTex); + + glBindVertexArray(quadVAO); + + for (int y = 0; y < grid.y; ++y) { + for (int x = 0; x < grid.x; ++x) { + int index = tilemap->GetTile(x, y); + if (index < 0 || index >= cols * rows) continue; + + int atlasX = index % cols; + int atlasY = index / cols; + + glm::vec2 uvMin = glm::vec2(atlasX, atlasY) / glm::vec2(cols, rows); + glm::vec2 uvMax = (glm::vec2(atlasX + 1, atlasY + 1)) / glm::vec2(cols, rows); + + tilemapShader.SetVec2("uUVMin", uvMin); + tilemapShader.SetVec2("uUVMax", uvMax); + + glm::vec2 tileWorld = worldPos + glm::vec2(x * tileSize.x, y * tileSize.y); + glm::vec2 screenPos = (tileWorld - cameraPos) * zoom + glm::vec2(width, height) * 0.5f; + + tilemapShader.SetVec2("uPos", screenPos); + tilemapShader.SetVec2("uSize", glm::vec2(tileSize) * zoom); + + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + s_DrawCalls++; + } + } + + glBindVertexArray(0); +} + + void Renderer::DrawSprite(SpriteComponent* sprite, const glm::vec2& pos, float zoom, glm::vec2& CameraPos) { if (!sprite->HasTexture()) { @@ -149,54 +185,65 @@ void Renderer::DrawSprite(SpriteComponent* sprite, const glm::vec2& pos, float z return; } - spriteShader.Use(); + // Choose the shader based on engine configuration + if (g_engineConfig.lighting_enabled) { + spriteShader.Use(); + } else { + unlitShader.Use(); + } glm::vec2 size = sprite->GetSize(); glm::vec2 screenPos = (pos - CameraPos) * zoom + glm::vec2(width, height) * 0.5f - (size * zoom * 0.5f); - spriteShader.SetVec2("uPos", screenPos); - spriteShader.SetVec2("uSize", size * zoom); - spriteShader.SetVec2("uScreen", glm::vec2(width, height)); + // Set common uniforms + if (g_engineConfig.lighting_enabled) { + spriteShader.SetVec2("uPos", screenPos); + spriteShader.SetVec2("uSize", size * zoom); + spriteShader.SetVec2("uScreen", glm::vec2(width, height)); - spriteShader.SetInt("uLightCount", static_cast<int>(s_Lights.size())); - for (size_t i = 0; i < s_Lights.size(); ++i) { - spriteShader.SetVec2(("uLightPos[" + std::to_string(i) + "]").c_str(), s_Lights[i].screenPos); - spriteShader.SetVec3(("uLightColor[" + std::to_string(i) + "]").c_str(), s_Lights[i].color); - spriteShader.SetFloat(("uLightIntensity[" + std::to_string(i) + "]").c_str(), s_Lights[i].intensity); - spriteShader.SetFloat(("uLightRadius[" + std::to_string(i) + "]").c_str(), s_Lights[i].radius); + spriteShader.SetInt("uLightCount", static_cast<int>(s_Lights.size())); + for (size_t i = 0; i < s_Lights.size(); ++i) { + spriteShader.SetVec2(("uLightPos[" + std::to_string(i) + "]").c_str(), s_Lights[i].screenPos); + spriteShader.SetVec3(("uLightColor[" + std::to_string(i) + "]").c_str(), s_Lights[i].color); + spriteShader.SetFloat(("uLightIntensity[" + std::to_string(i) + "]").c_str(), s_Lights[i].intensity); + spriteShader.SetFloat(("uLightRadius[" + std::to_string(i) + "]").c_str(), s_Lights[i].radius); + } + } else { + // Unlit shader uniforms + unlitShader.SetVec2("uPos", screenPos); + unlitShader.SetVec2("uSize", size * zoom); + unlitShader.SetVec2("uScreen", glm::vec2(width, height)); + } + + // Bind the diffuse texture (common to both shaders) + if (g_engineConfig.lighting_enabled) { + spriteShader.SetInt("uTex", 0); + } else { + unlitShader.SetInt("uTex", 0); } - - spriteShader.SetInt("uTex", 0); - spriteShader.SetInt("uNormalMap", 1); - glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, sprite->GetTextureID()); - glActiveTexture(GL_TEXTURE1); - if (sprite->GetNormalMapID()) { - glBindTexture(GL_TEXTURE_2D, sprite->GetNormalMapID()); - } else { - glBindTexture(GL_TEXTURE_2D, defaultNormalMap); + if (g_engineConfig.lighting_enabled) { + spriteShader.SetInt("uNormalMap", 1); + glActiveTexture(GL_TEXTURE1); + if (sprite->GetNormalMapID()) { + glBindTexture(GL_TEXTURE_2D, sprite->GetNormalMapID()); + } else { + glBindTexture(GL_TEXTURE_2D, defaultNormalMap); + } } - + glBindVertexArray(quadVAO); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); glBindVertexArray(0); s_DrawCalls++; } - - - - - - -int Renderer::GetDrawCallCount() -{ +int Renderer::GetDrawCallCount() { return s_DrawCalls; } - void Renderer::DrawEditorGrid(const glm::vec2& cameraPos, float zoom) { glUseProgram(0); glColor4f(0.5f, 0.5f, 0.5f, 0.25f); @@ -226,11 +273,9 @@ void Renderer::DrawEditorGrid(const glm::vec2& cameraPos, float zoom) { glVertex2f(left, (float)y); glVertex2f(right, (float)y); } - glEnd(); } - GLuint Renderer::GetRenderTexture() { return textureColorBuffer; } diff --git a/src/src/Renderer.h b/src/src/Renderer.h index a589db3..54c97b3 100644 --- a/src/src/Renderer.h +++ b/src/src/Renderer.h @@ -3,8 +3,11 @@ #include <glm/glm.hpp> #include <vector> +#include "Components/TilemapComponent.h" +#include "Components/SpriteComponent.h" + + -class SpriteComponent; struct Light { glm::vec2 screenPos; @@ -21,6 +24,7 @@ public: static void End(); static void DrawSprite(SpriteComponent* sprite, const glm::vec2& pos, float zoom, glm::vec2& CameraPos); static void AddLight(const glm::vec2& screenPos, const glm::vec3& color, float intensity, float radius); + static void DrawTilemap(TilemapComponent* tilemap, const glm::vec2& worldPos, float zoom, const glm::vec2& cameraPos); static void ClearLights(); static void DrawEditorGrid(const glm::vec2& cameraPos, float zoom); static GLuint GetRenderTexture(); diff --git a/src/src/utils/EngineConfig.cpp b/src/src/utils/EngineConfig.cpp new file mode 100644 index 0000000..4f53181 --- /dev/null +++ b/src/src/utils/EngineConfig.cpp @@ -0,0 +1,8 @@ +// EngineConfig.cpp +#include "EngineConfig.h" + + +EngineConfig g_engineConfig { + .lighting_enabled = false, + +}; diff --git a/src/src/utils/EngineConfig.h b/src/src/utils/EngineConfig.h new file mode 100644 index 0000000..9957b40 --- /dev/null +++ b/src/src/utils/EngineConfig.h @@ -0,0 +1,7 @@ +#pragma once + + +struct EngineConfig { + bool lighting_enabled; +}; +extern EngineConfig g_engineConfig; diff --git a/src/src/utils/utils.cpp b/src/src/utils/utils.cpp index 050b012..3c76caa 100644 --- a/src/src/utils/utils.cpp +++ b/src/src/utils/utils.cpp @@ -1,12 +1,37 @@ #include "utils.h" +#include <map> +#include "stb_image.h" + +static std::map<std::string, GLuint> textureCache; + +GLuint LoadTextureIfNeeded(const std::string& path) { + if (textureCache.count(path)) + return textureCache[path]; + + int w, h, channels; + stbi_set_flip_vertically_on_load(false); + unsigned char* data = stbi_load(path.c_str(), &w, &h, &channels, 4); + if (!data) + return 0; + + GLuint id; + glGenTextures(1, &id); + glBindTexture(GL_TEXTURE_2D, id); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + stbi_image_free(data); + + textureCache[path] = id; + return id; +} std::string GetFilenameFromPath(const std::string& path) { - // Find the last slash or backslash size_t lastSlash = path.find_last_of("/\\"); std::string filename = (lastSlash == std::string::npos) ? path : path.substr(lastSlash + 1); - // Strip trailing slashes (if any) + // Strip trailing slashes while (!filename.empty() && (filename.back() == '/' || filename.back() == '\\')) filename.pop_back(); diff --git a/src/src/utils/utils.h b/src/src/utils/utils.h index b44a32e..e9e3320 100644 --- a/src/src/utils/utils.h +++ b/src/src/utils/utils.h @@ -1,4 +1,10 @@ -#include <string> -#include <algorithm> +#pragma once +#include <string> +#include <GL/glew.h> + +// Returns filename from a full path (e.g., "C:/foo/bar.png" → "bar.png") std::string GetFilenameFromPath(const std::string& path); + +// Loads a texture (with caching). Returns OpenGL texture ID +GLuint LoadTextureIfNeeded(const std::string& path);