diff --git a/CMakeLists.txt b/CMakeLists.txt index c58a66c..8d627f2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,17 +48,7 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(glfw) -# Assimp -FetchContent_Declare( - assimp - GIT_REPOSITORY https://github.com/assimp/assimp.git - GIT_TAG master -) -set(ASSIMP_NO_EXPORT ON CACHE BOOL "" FORCE) -set(ASSIMP_BUILD_ASSIMP_TOOLS OFF CACHE BOOL "" FORCE) -set(ASSIMP_BUILD_TESTS OFF CACHE BOOL "" FORCE) -set(ASSIMP_BUILD_SAMPLES OFF CACHE BOOL "" FORCE) -FetchContent_MakeAvailable(assimp) + # ImGui (Docking) FetchContent_Declare( @@ -145,7 +135,6 @@ target_link_libraries(Editor PRIVATE Core ImGui glfw - assimp::assimp GLEW::GLEW yaml-cpp ${CMAKE_DL_LIBS} diff --git a/src/core/systems/AssetManager.cpp b/src/core/systems/AssetManager.cpp index b8a4c03..6411e6b 100644 --- a/src/core/systems/AssetManager.cpp +++ b/src/core/systems/AssetManager.cpp @@ -1,196 +1,189 @@ #include "AssetManager.h" #include "assets/Texture2D.h" #include "Logger.h" -#include +#include +#include #include #include namespace fs = std::filesystem; -namespace OX -{ - std::unordered_map > AssetManager::s_LoadedAssets; - std::unordered_map AssetManager::s_MetadataMap; - std::shared_ptr AssetManager::s_FileTree = std::make_shared(); +namespace OX { - std::mutex AssetManager::s_QueueMutex; - std::queue AssetManager::s_DeferredLoadQueue; +std::unordered_map> AssetManager::s_LoadedAssets; +std::unordered_map AssetManager::s_MetadataMap; +std::shared_ptr AssetManager::s_FileTree = std::make_shared(); - fs::path AssetManager::s_ProjectRoot; - std::atomic AssetManager::s_Scanning = false; - std::thread AssetManager::s_ScanThread; +std::mutex AssetManager::s_TextureQueueMutex; +std::queue AssetManager::s_TextureUploadQueue; - void AssetManager::Init(const std::string &projectRoot) - { - s_ProjectRoot = fs::absolute(projectRoot); - s_Scanning = true; - s_FileTree = std::make_shared(); - s_FileTree->name = "res://"; - s_FileTree->path = "res://"; - s_FileTree->isDirectory = true; +fs::path AssetManager::s_ProjectRoot; +std::atomic AssetManager::s_Scanning = false; +std::thread AssetManager::s_ScanThread; + +void AssetManager::Init(const std::string& projectRoot) { + s_ProjectRoot = fs::absolute(projectRoot); + s_Scanning = true; + + s_FileTree = std::make_shared(); + s_FileTree->name = "res://"; + s_FileTree->path = "res://"; + s_FileTree->isDirectory = true; + + s_ScanThread = std::thread([=] { + BackgroundScan(s_ProjectRoot); + s_Scanning = false; + }); +} + +void AssetManager::Shutdown() { + if (s_ScanThread.joinable()) + s_ScanThread.join(); +} + +void AssetManager::Tick() { + std::lock_guard lock(s_TextureQueueMutex); + if (!s_TextureUploadQueue.empty()) { + auto pending = s_TextureUploadQueue.front(); + s_TextureUploadQueue.pop(); + + GLuint texID; + glGenTextures(1, &texID); + glBindTexture(GL_TEXTURE_2D, texID); + + GLenum format = pending.channels == 4 ? GL_RGBA : GL_RGB; + glTexImage2D(GL_TEXTURE_2D, 0, format, pending.width, pending.height, 0, format, GL_UNSIGNED_BYTE, pending.data); + glGenerateMipmap(GL_TEXTURE_2D); + + 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_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + stbi_image_free(pending.data); + + auto tex = std::make_shared(); + tex->SetFromGL(texID, pending.width, pending.height); + s_LoadedAssets[pending.id] = tex; + Logger::LogInfo("%u | %u", tex->GetID(), texID); - s_ScanThread = std::thread([=] - { - BackgroundScan(s_ProjectRoot); - s_Scanning = false; - }); } +} - void AssetManager::Shutdown() - { - if (s_ScanThread.joinable()) - s_ScanThread.join(); - } +void AssetManager::Rescan() { + if (s_Scanning) return; + s_Scanning = true; - void AssetManager::Tick() - { - std::lock_guard lock(s_QueueMutex); - if (!s_DeferredLoadQueue.empty()) { - auto resPath = s_DeferredLoadQueue.front(); - s_DeferredLoadQueue.pop(); - LoadAsset(resPath); - } - } + s_FileTree = std::make_shared(); + s_FileTree->name = "res://"; + s_FileTree->path = "res://"; + s_FileTree->isDirectory = true; + s_ScanThread = std::thread([=] { + BackgroundScan(s_ProjectRoot); + s_Scanning = false; + }); +} - void AssetManager::SaveAssetPack(const std::string& outputPath) - { - YAML::Emitter out; - out << YAML::BeginMap; - out << YAML::Key << "assets" << YAML::Value << YAML::BeginSeq; +void AssetManager::BackgroundScan(const fs::path& root) { + for (const auto& entry : fs::recursive_directory_iterator(root)) { + const auto& path = entry.path(); + std::string resPath = MakeVirtualPath(path); - for (const auto& [resPath, meta] : s_MetadataMap) { - fs::path relative = fs::relative(meta.absolutePath, s_ProjectRoot); + if (entry.is_directory()) { + AddToTree(resPath, true); + } else { + AddToTree(resPath, false); - out << YAML::BeginMap; - out << YAML::Key << "id" << YAML::Value << resPath; - out << YAML::Key << "type" << YAML::Value << meta.type; - out << YAML::Key << "path" << YAML::Value << relative.generic_string(); - out << YAML::EndMap; - } - - out << YAML::EndSeq; - out << YAML::EndMap; - - std::ofstream fout(outputPath); - fout << out.c_str(); - fout.close(); - - Logger::LogInfo("Saved asset pack: %s", outputPath.c_str()); - } - - - void AssetManager::Rescan() - { - if (s_Scanning) return; - s_Scanning = true; - s_FileTree = std::make_shared(); - s_FileTree->name = "res://"; - s_FileTree->path = "res://"; - s_FileTree->isDirectory = true; - - s_ScanThread = std::thread([=] - { - BackgroundScan(s_ProjectRoot); - s_Scanning = false; - }); - } - - void AssetManager::BackgroundScan(const fs::path &root) - { - for (const auto &entry: fs::recursive_directory_iterator(root)) { - const auto &path = entry.path(); - std::string resPath = MakeVirtualPath(path); - - if (entry.is_directory()) { - AddToTree(resPath, true); - } else { - AddToTree(resPath, false); - - std::string type = DetectAssetType(path); - if (!type.empty()) { - std::lock_guard lock(s_QueueMutex); - s_MetadataMap[resPath] = {type, path.string()}; - s_DeferredLoadQueue.push(resPath); + std::string type = DetectAssetType(path); + if (!type.empty()) { + if (type == "texture2D") { + int w, h, ch; + stbi_set_flip_vertically_on_load(1); + unsigned char* data = stbi_load(path.string().c_str(), &w, &h, &ch, 0); + if (data) { + std::lock_guard lock(s_TextureQueueMutex); + s_TextureUploadQueue.push({ resPath, path.string(), w, h, ch, data }); + s_MetadataMap[resPath] = { type, path.string() }; + } } } } } +} - void AssetManager::AddToTree(const std::string &resPath, bool isDir) - { - std::vector parts; - size_t pos = 0, next; - while ((next = resPath.find('/', pos)) != std::string::npos) { - if (next != pos) parts.push_back(resPath.substr(pos, next - pos)); - pos = next + 1; - } - if (pos < resPath.length()) parts.push_back(resPath.substr(pos)); +void AssetManager::AddToTree(const std::string& resPath, bool isDir) { + std::vector parts; + size_t pos = 0, next; + while ((next = resPath.find('/', pos)) != std::string::npos) { + if (next != pos) parts.push_back(resPath.substr(pos, next - pos)); + pos = next + 1; + } + if (pos < resPath.length()) parts.push_back(resPath.substr(pos)); - auto current = s_FileTree; - for (size_t i = 1; i < parts.size(); ++i) { - auto &name = parts[i]; - auto it = std::find_if(current->children.begin(), current->children.end(), [&](auto &child) - { - return child->name == name; - }); + auto current = s_FileTree; + for (size_t i = 1; i < parts.size(); ++i) { + auto& name = parts[i]; + auto it = std::find_if(current->children.begin(), current->children.end(), [&](auto& child) { + return child->name == name; + }); - if (it == current->children.end()) { - auto newNode = std::make_shared(); - newNode->name = name; - newNode->path = current->path + "/" + name; - newNode->isDirectory = (i < parts.size() - 1) || isDir; - current->children.push_back(newNode); - current = newNode; - } else { - current = *it; - } + if (it == current->children.end()) { + auto newNode = std::make_shared(); + newNode->name = name; + newNode->path = current->path + "/" + name; + newNode->isDirectory = (i < parts.size() - 1) || isDir; + current->children.push_back(newNode); + current = newNode; + } else { + current = *it; } } +} - std::string AssetManager::MakeVirtualPath(const fs::path &full) - { - fs::path rel = fs::relative(full, s_ProjectRoot); - return "res://" + rel.generic_string(); +std::string AssetManager::MakeVirtualPath(const fs::path& full) { + fs::path rel = fs::relative(full, s_ProjectRoot); + return "res://" + rel.generic_string(); +} + +std::string AssetManager::DetectAssetType(const fs::path& ext) { + auto e = ext.extension().string(); + if (e == ".png" || e == ".jpg") return "texture2D"; + return ""; +} + +std::shared_ptr AssetManager::Get(const std::string& resPath) { + auto it = s_LoadedAssets.find(resPath); + return (it != s_LoadedAssets.end()) ? it->second : nullptr; +} + +void AssetManager::SaveAssetPack(const std::string& outputPath) { + YAML::Emitter out; + out << YAML::BeginMap; + out << YAML::Key << "assets" << YAML::Value << YAML::BeginSeq; + + for (const auto& [resPath, meta] : s_MetadataMap) { + fs::path relative = fs::relative(meta.absolutePath, s_ProjectRoot); + + out << YAML::BeginMap; + out << YAML::Key << "id" << YAML::Value << resPath; + out << YAML::Key << "type" << YAML::Value << meta.type; + out << YAML::Key << "path" << YAML::Value << relative.generic_string(); + out << YAML::EndMap; } - std::string AssetManager::DetectAssetType(const fs::path &ext) - { - auto e = ext.extension().string(); - if (e == ".png" || e == ".jpg") return "texture2D"; - if (e == ".ogg" || e == ".wav") return "audio"; - return ""; - } + out << YAML::EndSeq; + out << YAML::EndMap; - std::shared_ptr AssetManager::Get(const std::string &resPath) - { - auto it = s_LoadedAssets.find(resPath); - return (it != s_LoadedAssets.end()) ? it->second : nullptr; - } + std::ofstream fout(outputPath); + fout << out.c_str(); + fout.close(); - bool AssetManager::LoadAsset(const std::string &resPath) - { - auto it = s_MetadataMap.find(resPath); - if (it == s_MetadataMap.end()) return false; + Logger::LogInfo("Saved asset pack: %s", outputPath.c_str()); +} - const auto &meta = it->second; - std::shared_ptr asset; +std::shared_ptr AssetManager::GetFileTree() { + return s_FileTree; +} - if (meta.type == "texture2D") - asset = std::make_shared(); - - if (asset && asset->LoadFromFile(meta.absolutePath)) { - s_LoadedAssets[resPath] = asset; - Logger::LogDebug("Loaded asset: %s", resPath.c_str()); - return true; - } - - Logger::LogError("Failed to load: %s", resPath.c_str()); - return false; - } - - std::shared_ptr AssetManager::GetFileTree() - { - return s_FileTree; - } } // namespace OX diff --git a/src/core/systems/AssetManager.h b/src/core/systems/AssetManager.h index 242ead1..a6bcca8 100644 --- a/src/core/systems/AssetManager.h +++ b/src/core/systems/AssetManager.h @@ -1,12 +1,11 @@ #pragma once - #include +#include #include #include #include -#include -#include #include +#include #include #include @@ -16,7 +15,7 @@ namespace OX { struct ResourceTreeNode { std::string name; - std::string path; // res://... + std::string path; bool isDirectory = false; std::vector> children; }; @@ -25,15 +24,12 @@ namespace OX { public: static void Init(const std::string& projectRoot); static void Shutdown(); - static void Tick(); // Call every frame - - static void SaveAssetPack(const std::string& outputPath); - + static void Tick(); // Main-thread + static void Rescan(); static std::shared_ptr Get(const std::string& resPath); static std::shared_ptr GetFileTree(); - static bool LoadAsset(const std::string& resPath); - static void Rescan(); // Rebuilds file tree (slow) + static void SaveAssetPack(const std::string& outputPath); private: struct AssetMetadata { @@ -41,6 +37,13 @@ namespace OX { std::string absolutePath; }; + struct PendingTexture { + std::string id; + std::string path; + int width, height, channels; + unsigned char* data; + }; + static void BackgroundScan(const std::filesystem::path& root); static void AddToTree(const std::string& virtualPath, bool isDir); static std::string DetectAssetType(const std::filesystem::path& ext); @@ -50,8 +53,8 @@ namespace OX { static std::unordered_map s_MetadataMap; static std::shared_ptr s_FileTree; - static std::mutex s_QueueMutex; - static std::queue s_DeferredLoadQueue; + static std::mutex s_TextureQueueMutex; + static std::queue s_TextureUploadQueue; static std::filesystem::path s_ProjectRoot; static std::atomic s_Scanning; diff --git a/src/core/systems/assets/Texture2D.cpp b/src/core/systems/assets/Texture2D.cpp index 6d9c3da..338d08d 100644 --- a/src/core/systems/assets/Texture2D.cpp +++ b/src/core/systems/assets/Texture2D.cpp @@ -7,42 +7,20 @@ #define STB_IMAGE_IMPLEMENTATION #include #include +#include "Texture2D.h" -namespace OX -{ - Texture2D::Texture2D() = default; +namespace OX { - Texture2D::~Texture2D() - { - if (m_TextureID != 0) { + Texture2D::~Texture2D() { + if (m_TextureID) glDeleteTextures(1, &m_TextureID); - } } - bool Texture2D::LoadFromFile(const std::string &path) - { - stbi_set_flip_vertically_on_load(1); - int channels; - unsigned char *data = stbi_load(path.c_str(), &m_Width, &m_Height, &channels, 0); - - if (!data) { - Logger::LogError("Failed to load texture: %s", path.c_str()); - return false; - } - - GLenum format = (channels == 4) ? GL_RGBA : GL_RGB; - - glGenTextures(1, &m_TextureID); - glBindTexture(GL_TEXTURE_2D, m_TextureID); - glTexImage2D(GL_TEXTURE_2D, 0, format, m_Width, m_Height, 0, format, GL_UNSIGNED_BYTE, data); - glGenerateMipmap(GL_TEXTURE_2D); - - 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_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - stbi_image_free(data); - return true; + void Texture2D::SetFromGL(uint32_t texID, int width, int height) { + m_TextureID = texID; + m_Width = width; + m_Height = height; } -} // namespace OX + +} + diff --git a/src/core/systems/assets/Texture2D.h b/src/core/systems/assets/Texture2D.h index 4ee6575..16d548d 100644 --- a/src/core/systems/assets/Texture2D.h +++ b/src/core/systems/assets/Texture2D.h @@ -1,27 +1,23 @@ // // Created by spenc on 5/21/2025. // - #pragma once #include "systems/Asset.h" #include -#include +#include "GL/glew.h" - -namespace OX -{ - class Texture2D : public Asset - { +namespace OX { + class Texture2D : public Asset { public: - Texture2D(); - + Texture2D() = default; ~Texture2D(); - bool LoadFromFile(const std::string &path) override; - std::string GetTypeName() const override { return "texture2D"; } + bool LoadFromFile(const std::string& path) override { return false; } // Not used now - uint32_t GetID() const { return m_TextureID; } + void SetFromGL(uint32_t texID, int width, int height); + + GLuint GetID() const { return m_TextureID; } int GetWidth() const { return m_Width; } int GetHeight() const { return m_Height; } @@ -30,4 +26,4 @@ namespace OX int m_Width = 0; int m_Height = 0; }; -} // OX +}; diff --git a/src/core/types/color.h b/src/core/types/color.h index d001a59..996aa77 100644 --- a/src/core/types/color.h +++ b/src/core/types/color.h @@ -28,10 +28,7 @@ namespace OX operator glm::vec3() const { return {r, g, b}; } operator glm::vec4() const { return {r, g, b, a}; } -#ifdef HAS_IMGUI - operator ImVec3() const { return {r, g, b}; } - operator ImVec4() const { return {r, g, b, a}; } -#endif + // Arithmetic diff --git a/src/editor/Windows/FileBrowser.cpp b/src/editor/Windows/FileBrowser.cpp index dad4612..92509cd 100644 --- a/src/editor/Windows/FileBrowser.cpp +++ b/src/editor/Windows/FileBrowser.cpp @@ -1,47 +1,116 @@ -// -// Created by spenc on 5/21/2025. -// - - - - #include "FileBrowser.h" #include "systems/AssetManager.h" +#include "systems/assets/Texture2D.h" #include "imgui.h" #include -namespace OX { - +namespace OX +{ static std::string s_CurrentPath = "res://"; - static void DrawGrid(const std::shared_ptr& node) { - const int columns = 4; - int itemIndex = 0; + static void DrawGrid(const std::shared_ptr &node) + { + const float thumbSize = 64.0f; + const float padding = 8.0f; + const float labelHeight = 20.0f; + const float cellWidth = thumbSize + padding * 2; + const float cellHeight = thumbSize + labelHeight + padding; + + ImVec2 region = ImGui::GetContentRegionAvail(); + int columns = std::max(1, (int) (region.x / cellWidth)); ImGui::BeginChild("FileGrid", ImVec2(0, 0), false, ImGuiWindowFlags_AlwaysUseWindowPadding); ImGui::Columns(columns, nullptr, false); - for (const auto& child : node->children) { - std::string label = (child->isDirectory ? "[folder] " : "[file] ") + child->name; + for (const auto &child: node->children) { + ImGui::PushID(child->path.c_str()); // Unique ID per item - if (ImGui::Selectable(label.c_str(), false, ImGuiSelectableFlags_AllowDoubleClick, ImVec2(0, 64))) { - if (child->isDirectory && ImGui::IsMouseDoubleClicked(0)) { - s_CurrentPath = child->path; + ImGui::BeginGroup(); + + std::string tooltip; + std::string label = child->name; + ImVec2 cellSize(cellWidth, cellHeight); + + ImGui::InvisibleButton("Cell", cellSize); + bool hovered = ImGui::IsItemHovered(); + bool clicked = ImGui::IsItemClicked(); + + // Background + ImVec2 min = ImGui::GetItemRectMin(); + ImVec2 max = ImGui::GetItemRectMax(); + ImGui::GetWindowDrawList()->AddRectFilled(min, max, IM_COL32(40, 40, 40, 255), 4.0f); + + ImVec2 center = ImVec2(min.x + padding, min.y + padding); + + // === Thumbnail === + if (child->isDirectory) { + ImGui::SetCursorScreenPos(center); + ImGui::Text("[Folder]"); + } else { + auto asset = AssetManager::Get(child->path); + if (asset) { + std::string type = asset->GetTypeName(); + if (type == "texture2D") { + auto tex = std::static_pointer_cast(asset); + if (tex->GetID() != 0) { + ImGui::SetCursorScreenPos(center); + ImGui::Image((ImTextureID) (intptr_t) tex->GetID(), ImVec2(thumbSize, thumbSize), + ImVec2(0, 1), ImVec2(1, 0)); + + tooltip += "Name: " + child->name + "\n"; + tooltip += "ID: " + std::to_string(tex->GetID()) + "\n"; + tooltip += "Size: " + std::to_string(tex->GetWidth()) + "x" + std::to_string( + tex->GetHeight()) + "\n"; + tooltip += "Path: " + child->path + "\n"; + + label += " (ID: " + std::to_string(tex->GetID()) + ")"; + } else { + ImGui::SetCursorScreenPos(center); + ImGui::Text("[Loading]"); + } + } else { + ImGui::SetCursorScreenPos(center); + ImGui::Text("[Type: %s]", type.c_str()); + } + } else { + ImGui::SetCursorScreenPos(center); + ImGui::Text("[Unloaded]"); } } + + // === Label === + ImGui::SetCursorScreenPos(ImVec2(min.x + padding, max.y - labelHeight)); + ImGui::PushTextWrapPos(min.x + cellWidth); + ImGui::TextUnformatted(label.c_str()); + ImGui::PopTextWrapPos(); + + // Click handling + if (clicked && child->isDirectory) { + s_CurrentPath = child->path; + } + + if (hovered && !tooltip.empty()) { + ImGui::BeginTooltip(); + ImGui::TextUnformatted(tooltip.c_str()); + ImGui::EndTooltip(); + } + + ImGui::EndGroup(); ImGui::NextColumn(); - ++itemIndex; + ImGui::PopID(); } ImGui::Columns(1); ImGui::EndChild(); } - static std::shared_ptr FindNode(const std::shared_ptr& root, const std::string& targetPath) { - if (root->path == targetPath) return root; - for (const auto& child : root->children) { + static std::shared_ptr FindNode(const std::shared_ptr &root, + const std::string &targetPath) + { + if (root->path == targetPath) return root; + for (const auto &child: root->children) { if (child->isDirectory) { auto found = FindNode(child, targetPath); if (found) return found; @@ -50,9 +119,24 @@ namespace OX { return nullptr; } - void FileBrowser::Draw() { + void FileBrowser::Draw() + { ImGui::Begin("File Browser"); + // === Back Button === + if (s_CurrentPath != "res://") { + if (ImGui::Button("..")) { + size_t lastSlash = s_CurrentPath.find_last_of('/'); + if (lastSlash != std::string::npos && lastSlash > 6) { + // skip 'res://' + s_CurrentPath = s_CurrentPath.substr(0, lastSlash); + } else { + s_CurrentPath = "res://"; + } + } + } + + ImGui::SameLine(); ImGui::Text("Current Path: %s", s_CurrentPath.c_str()); ImGui::Separator(); @@ -66,5 +150,4 @@ namespace OX { ImGui::End(); } - } // namespace OX