diff --git a/TSE_Core/include/json.hpp b/TSE_Base/include/json.hpp similarity index 100% rename from TSE_Core/include/json.hpp rename to TSE_Base/include/json.hpp diff --git a/TSE_Core/include/json_fwd.hpp b/TSE_Base/include/json_fwd.hpp similarity index 100% rename from TSE_Core/include/json_fwd.hpp rename to TSE_Base/include/json_fwd.hpp diff --git a/TSE_Core/include/picosha2.h b/TSE_Base/include/picosha2.h similarity index 100% rename from TSE_Core/include/picosha2.h rename to TSE_Base/include/picosha2.h diff --git a/TSE_Core/src/BehaviourScripts/MeshContainer.cpp b/TSE_Core/src/BehaviourScripts/MeshContainer.cpp index 67e1b30..8c08411 100644 --- a/TSE_Core/src/BehaviourScripts/MeshContainer.cpp +++ b/TSE_Core/src/BehaviourScripts/MeshContainer.cpp @@ -14,3 +14,8 @@ TSE::Mesh *TSE::MeshContainer::GetMesh() const { return mesh; } + +TSE::Mesh **TSE::MeshContainer::GetMeshRef() +{ + return &mesh; +} diff --git a/TSE_Core/src/BehaviourScripts/MeshContainer.hpp b/TSE_Core/src/BehaviourScripts/MeshContainer.hpp index f611781..dbf59e9 100644 --- a/TSE_Core/src/BehaviourScripts/MeshContainer.hpp +++ b/TSE_Core/src/BehaviourScripts/MeshContainer.hpp @@ -3,7 +3,7 @@ #define MESH_CONTAINER typeid(TSE::MeshContainer).name() #include "Types.hpp" -#include "Mesh.hpp" +#include "elements/Mesh.hpp" #include "elements/BehaviourScript.hpp" namespace TSE @@ -19,6 +19,7 @@ namespace TSE void SetMesh(Mesh* mesh); Mesh* GetMesh() const; + Mesh** GetMeshRef(); inline const char* GetName() override { diff --git a/TSE_Core/src/BehaviourScripts/RectBase.hpp b/TSE_Core/src/BehaviourScripts/RectBase.hpp index 4c6decd..10f2c2e 100644 --- a/TSE_Core/src/BehaviourScripts/RectBase.hpp +++ b/TSE_Core/src/BehaviourScripts/RectBase.hpp @@ -5,7 +5,7 @@ #include "elements/BehaviourScript.hpp" #include "Rect.hpp" #include "Vector2.hpp" -#include "Mesh.hpp" +#include "elements/Mesh.hpp" namespace TSE { diff --git a/TSE_Core/src/BehaviourScripts/Renderable.cpp b/TSE_Core/src/BehaviourScripts/Renderable.cpp index 2990046..8ee3bec 100644 --- a/TSE_Core/src/BehaviourScripts/Renderable.cpp +++ b/TSE_Core/src/BehaviourScripts/Renderable.cpp @@ -22,6 +22,11 @@ namespace TSE return material; } + Material **Renderable::GetMaterialRef() + { + return &material; + } + Mesh* Renderable::GetMeshContainer() const { if(baseObject->HasBehaviourScript(MESH_CONTAINER)) return dynamic_cast(baseObject->GetBehaviourScript(MESH_CONTAINER))->GetMesh(); diff --git a/TSE_Core/src/BehaviourScripts/Renderable.hpp b/TSE_Core/src/BehaviourScripts/Renderable.hpp index f9f6395..831f46f 100644 --- a/TSE_Core/src/BehaviourScripts/Renderable.hpp +++ b/TSE_Core/src/BehaviourScripts/Renderable.hpp @@ -5,7 +5,7 @@ #include "elements/Material.hpp" #include "interfaces/IRenderable.hpp" #include "elements/BehaviourScript.hpp" -#include "Mesh.hpp" +#include "elements/Mesh.hpp" namespace TSE { @@ -28,6 +28,7 @@ namespace TSE } void SetMaterial(Material* material); Material* GetMaterial(); + Material** GetMaterialRef(); private: Mesh* GetMeshContainer() const; diff --git a/TSE_Core/src/elements/AssetLibrary.cpp b/TSE_Core/src/elements/AssetLibrary.cpp index fbd9f51..e7ce565 100644 --- a/TSE_Core/src/elements/AssetLibrary.cpp +++ b/TSE_Core/src/elements/AssetLibrary.cpp @@ -1,5 +1,6 @@ #include "AssetLibrary.hpp" #include "Material.hpp" +#include "Mesh.hpp" #include "Texture.hpp" #include #include @@ -10,6 +11,32 @@ std::unordered_map> std::unordered_map TSE::AssetLibrary::alreadyLoadedAssets; TSE::string TSE::AssetLibrary::currentAssetPath; +void TSE::AssetLibrary::LoadDefaultAssets() +{ + Mesh* quad = new Mesh(Mesh::GetQuadMesh()); + quad->id = TSE_ID_MESH_QUAD; + quad->IsVirtualAsset = true; + + Mesh* circle = new Mesh(Mesh::GetCircleMesh()); + circle->id = TSE_ID_MESH_CIRCLE; + circle->IsVirtualAsset = true; + + SetValue(TSE_ID_MESH_QUAD, quad); + SetValue(TSE_ID_MESH_CIRCLE, circle); +} + +void TSE::AssetLibrary::RemoveValue(const uuids::uuid &key, IAsset *asset) +{ + if(HasValue(key)) + { + if(!asset->IsVirtualAsset) + { + alreadyLoadedAssets.erase(asset->rawPath); + } + assets.erase(key); + } +} + bool TSE::AssetLibrary::HasValue(const uuids::uuid &key) { return assets.find(key) != assets.end(); @@ -49,6 +76,12 @@ void TSE::AssetLibrary::SaveAllAssets() if(!value->IsVirtualAsset) value->SaveAsset(); } + if (type == typeid(Mesh*).name()) + { + Mesh* value = GetValue(name); + if(!value->IsVirtualAsset) + value->SaveAsset(); + } } } @@ -65,6 +98,7 @@ std::string ToLower(std::string str) void TSE::AssetLibrary::LoadAllAssets(string &path) { + LoadDefaultAssets(); namespace fs = std::filesystem; currentAssetPath = string(path); @@ -97,6 +131,13 @@ void TSE::AssetLibrary::LoadAllAssets(string &path) SetValue(tex->id, tex); alreadyLoadedAssets[path] = tex->id; } + else if (ext == ".obj") + { + Mesh* mesh = new Mesh(); + mesh->LoadAsset(path); + SetValue(mesh->id, mesh); + alreadyLoadedAssets[path] = mesh->id; + } } } @@ -120,8 +161,25 @@ void TSE::AssetLibrary::RescanAssets() if(!value->IsVirtualAsset) value->Rescan(); } + if (type == typeid(Mesh*).name()) + { + Mesh* value = GetValue(name); + if(!value->IsVirtualAsset) + value->Rescan(); + } } } -template void TSE::AssetLibrary::SetValue(const uuids::uuid&, const TSE::Material*); -template void TSE::AssetLibrary::SetValue(const uuids::uuid&, const TSE::Texture*); +TSE::string TSE::AssetLibrary::GetCurrentAssetPath() +{ + return currentAssetPath; +} + +template void TSE::AssetLibrary::SetValue(const uuids::uuid&, TSE::Material*); +template void TSE::AssetLibrary::SetValue(const uuids::uuid&, TSE::Texture*); +template void TSE::AssetLibrary::SetValue(const uuids::uuid&, TSE::Mesh*); + + +template TSE::Texture* TSE::AssetLibrary::GetValue(const uuids::uuid&); +template TSE::Material* TSE::AssetLibrary::GetValue(const uuids::uuid&); +template TSE::Mesh* TSE::AssetLibrary::GetValue(const uuids::uuid&); diff --git a/TSE_Core/src/elements/AssetLibrary.hpp b/TSE_Core/src/elements/AssetLibrary.hpp index 4e37f0f..7882bfe 100644 --- a/TSE_Core/src/elements/AssetLibrary.hpp +++ b/TSE_Core/src/elements/AssetLibrary.hpp @@ -6,20 +6,27 @@ namespace TSE { + const uuids::uuid TSE_ID_MESH_QUAD = uuids::uuid::from_string("00000000-0000-0000-1000-000000000000").value(); + const uuids::uuid TSE_ID_MESH_CIRCLE = uuids::uuid::from_string("00000000-0000-0000-1001-000000000000").value(); + class AssetLibrary { private: static std::unordered_map> assets; static std::unordered_map alreadyLoadedAssets; + static string currentAssetPath; + static void LoadDefaultAssets(); + public: template static T GetValue(const uuids::uuid& key); template - static void SetValue(const uuids::uuid& key, const T* value); + static void SetValue(const uuids::uuid& key, T* value); + static void RemoveValue(const uuids::uuid& key, IAsset* asset); static bool HasValue(const uuids::uuid& key); static int GetValueCount(); @@ -28,6 +35,8 @@ namespace TSE static void SaveAllAssets(); static void LoadAllAssets(string& path); static void RescanAssets(); + + static string GetCurrentAssetPath(); }; template @@ -41,7 +50,7 @@ namespace TSE } template - void AssetLibrary::SetValue(const uuids::uuid& key, const T* value) { + void AssetLibrary::SetValue(const uuids::uuid& key, T* value) { assets[key] = std::make_tuple(value, typeid(T).name(), key); } } \ No newline at end of file diff --git a/TSE_Core/src/elements/Material.cpp b/TSE_Core/src/elements/Material.cpp index 6cd6bfe..6a2e740 100644 --- a/TSE_Core/src/elements/Material.cpp +++ b/TSE_Core/src/elements/Material.cpp @@ -13,6 +13,7 @@ #include "utils/JsonExports.hpp" #include "Debug.hpp" #include "ShaderRegistry.hpp" +#include "AssetLibrary.hpp" namespace TSE { @@ -191,6 +192,7 @@ namespace TSE void Material::SaveAsset() { + if(IsVirtualAsset) return; using json = nlohmann::ordered_json; json material = GenerateMaterialJson(); @@ -289,6 +291,17 @@ namespace TSE hash = sha256(material.dump(-1)); } + string Material::assetType() + { + return "material"; + } + + void Material::UnloadAsset() + { + AssetLibrary::RemoveValue(id, static_cast(this)); + delete(this); + } + void Material::SaveMeta() { string metaPath = PathToMetaPath(rawPath); diff --git a/TSE_Core/src/elements/Material.hpp b/TSE_Core/src/elements/Material.hpp index 5d7644a..ba7f04c 100644 --- a/TSE_Core/src/elements/Material.hpp +++ b/TSE_Core/src/elements/Material.hpp @@ -46,6 +46,8 @@ namespace TSE void SaveAsset() override; nlohmann::ordered_json GenerateMaterialJson(); void Hash() override; + string assetType() override; + void UnloadAsset() override; protected: void SaveMeta() override; }; diff --git a/TSE_Core/src/elements/Mesh.cpp b/TSE_Core/src/elements/Mesh.cpp new file mode 100644 index 0000000..fd1147d --- /dev/null +++ b/TSE_Core/src/elements/Mesh.cpp @@ -0,0 +1,564 @@ +#include "Mesh.hpp" +#include "MathF.hpp" +#include +#include "json.hpp" +#include "PathHelper.hpp" +#include "IdGenerator.hpp" +#include "Debug.hpp" +#include "elements/AssetLibrary.hpp" +#include +#include +#include +#include +#include +#include + +namespace +{ + struct ObjFaceVertex + { + int vertexIndex = -1; + int uvIndex = -1; + + bool operator==(const ObjFaceVertex& other) const + { + return vertexIndex == other.vertexIndex && uvIndex == other.uvIndex; + } + }; + + struct ObjFaceVertexHash + { + size_t operator()(const ObjFaceVertex& value) const + { + size_t h1 = std::hash{}(value.vertexIndex); + size_t h2 = std::hash{}(value.uvIndex); + return h1 ^ (h2 + 0x9e3779b9 + (h1 << 6) + (h1 >> 2)); + } + }; + + struct ObjMeshData + { + TSE::string name; + std::vector vertecies; + std::vector indecies; + std::vector uvs; + std::unordered_map vertexLookup; + }; + + int ResolveObjIndex(int index, size_t count) + { + if(index > 0) + { + return index - 1; + } + if(index < 0) + { + return static_cast(count) + index; + } + return -1; + } + + ObjFaceVertex ParseFaceVertex(const std::string& token, size_t vertexCount, size_t uvCount) + { + ObjFaceVertex result; + std::stringstream stream(token); + std::string part; + + if(std::getline(stream, part, '/') && !part.empty()) + { + result.vertexIndex = ResolveObjIndex(std::stoi(part), vertexCount); + } + + if(std::getline(stream, part, '/') && !part.empty()) + { + result.uvIndex = ResolveObjIndex(std::stoi(part), uvCount); + } + + return result; + } + + bool AddObjFaceVertex( + ObjMeshData& mesh, + const ObjFaceVertex& faceVertex, + const std::vector& sourceVertices, + const std::vector& sourceUvs) + { + if(faceVertex.vertexIndex < 0 || faceVertex.vertexIndex >= static_cast(sourceVertices.size())) + { + return false; + } + + auto existing = mesh.vertexLookup.find(faceVertex); + if(existing != mesh.vertexLookup.end()) + { + mesh.indecies.push_back(existing->second); + return true; + } + + if(mesh.vertecies.size() > std::numeric_limits::max()) + { + TSE_WARNING("OBJ mesh has more vertices than ushort indices can address: " + mesh.name); + return false; + } + + TSE::ushort newIndex = static_cast(mesh.vertecies.size()); + mesh.vertecies.push_back(sourceVertices[faceVertex.vertexIndex]); + + if(faceVertex.uvIndex >= 0 && faceVertex.uvIndex < static_cast(sourceUvs.size())) + { + mesh.uvs.push_back(sourceUvs[faceVertex.uvIndex]); + } + else + { + mesh.uvs.emplace_back(0.0f, 0.0f); + } + + mesh.vertexLookup[faceVertex] = newIndex; + mesh.indecies.push_back(newIndex); + return true; + } + + TSE::string MeshGeometryHash( + const std::vector& vertecies, + const std::vector& indecies, + const std::vector& uvs) + { + std::ostringstream stream; + stream << std::setprecision(std::numeric_limits::max_digits10); + + stream << "v:" << vertecies.size() << ";"; + for(const auto& vertex : vertecies) + { + stream << vertex.x << "," << vertex.y << "," << vertex.z << ";"; + } + + stream << "i:" << indecies.size() << ";"; + for(TSE::ushort index : indecies) + { + stream << index << ";"; + } + + stream << "uv:" << uvs.size() << ";"; + for(const auto& uv : uvs) + { + stream << uv.x << "," << uv.y << ";"; + } + + return picosha2::hash256_hex_string(stream.str()); + } + + TSE::string MeshGeometryHash(const TSE::Mesh& mesh) + { + return MeshGeometryHash(mesh.vertecies, mesh.indecies, mesh.uvs); + } +} + +TSE::Mesh::Mesh() +{ + id = GenerateRandomUUID(); + name = ""; +} + +TSE::Mesh::Mesh(string _name, const std::vector &_vertecies, const std::vector &_indecies, const std::vector &_uvs) +{ + id = GenerateRandomUUID(); + name = _name; + vertecies = std::move(_vertecies); + indecies = std::move(_indecies); + uvs = std::move(_uvs); +} + +size_t TSE::Mesh::IndeciesCount() const +{ + return indecies.size(); +} + +size_t TSE::Mesh::VerteciesCount() const +{ + return vertecies.size(); +} + +TSE::Mesh TSE::Mesh::GetCircleMesh(ushort segments) +{ + std::vector verts; + std::vector indices; + std::vector uvs; + + verts.emplace_back(0.0f, 0.0f, 0.0f); + uvs.emplace_back(0.5f, 0.5f); + + float angleStep = 2.0f * TSE_PI / segments; + + for (int i = 0; i <= segments; ++i) { + float angle = i * angleStep; + float x = std::cos(angle) * 0.5f; + float y = std::sin(angle) * 0.5f; + verts.emplace_back(x, y, 0); + uvs.emplace_back(x + 0.5f, y + 0.5f); + + if (i > 0) { + indices.push_back(0); + indices.push_back(i); + indices.push_back(i + 1); + } + } + + return Mesh("Circle", verts, indices, uvs); +} + +TSE::Mesh TSE::Mesh::GetQuadMesh() +{ + std::vector verts = { + Vector3(-0.5f, -0.5f, 0), + Vector3( 0.5f, -0.5f, 0), + Vector3( 0.5f, 0.5f, 0), + Vector3(-0.5f, 0.5f, 0) + }; + + std::vector uvs = { + Vector2(0.0f, 0.0f), + Vector2(1.0f, 0.0f), + Vector2(1.0f, 1.0f), + Vector2(0.0f, 1.0f) + }; + + std::vector indices = { + 0, 1, 2, + 2, 3, 0 + }; + + return Mesh("Quad", verts, indices, uvs); +} + +void TSE::Mesh::LoadObj(const string &path) +{ + + std::ifstream objStream; + OpenFileReading(objStream, path); + if(!objStream.is_open()) + { + TSE_WARNING("Could not open OBJ file: " + path); + return; + } + + namespace fs = std::filesystem; + string fallbackName = fs::path(path).stem().string(); + std::vector sourceVertices; + std::vector sourceUvs; + std::vector objects; + ObjMeshData* currentObject = nullptr; + + auto ensureObject = [&]() -> ObjMeshData& + { + if(currentObject == nullptr) + { + objects.emplace_back(); + objects.back().name = fallbackName; + currentObject = &objects.back(); + } + return *currentObject; + }; + + std::string line; + while(std::getline(objStream, line)) + { + std::stringstream lineStream(line); + std::string command; + lineStream >> command; + + if(command == "v") + { + float x = 0.0f; + float y = 0.0f; + float z = 0.0f; + lineStream >> x >> y >> z; + sourceVertices.emplace_back(x, y, z); + } + else if(command == "vt") + { + float u = 0.0f; + float v = 0.0f; + lineStream >> u >> v; + sourceUvs.emplace_back(u, v); + } + else if(command == "o") + { + std::string objectName; + std::getline(lineStream >> std::ws, objectName); + objects.emplace_back(); + objects.back().name = objectName.empty() ? fallbackName : objectName; + currentObject = &objects.back(); + } + else if(command == "f") + { + ObjMeshData& mesh = ensureObject(); + std::vector face; + std::string token; + while(lineStream >> token) + { + face.push_back(ParseFaceVertex(token, sourceVertices.size(), sourceUvs.size())); + } + + if(face.size() < 3) + { + continue; + } + + bool hasInvalidVertex = std::any_of(face.begin(), face.end(), [&sourceVertices](const ObjFaceVertex& faceVertex) + { + return faceVertex.vertexIndex < 0 || faceVertex.vertexIndex >= static_cast(sourceVertices.size()); + }); + if(hasInvalidVertex) + { + TSE_WARNING("Skipping invalid OBJ face in: " + path); + continue; + } + + for(size_t i = 1; i + 1 < face.size(); i++) + { + AddObjFaceVertex(mesh, face[0], sourceVertices, sourceUvs); + AddObjFaceVertex(mesh, face[i], sourceVertices, sourceUvs); + AddObjFaceVertex(mesh, face[i + 1], sourceVertices, sourceUvs); + } + } + } + + objects.erase( + std::remove_if(objects.begin(), objects.end(), [](const ObjMeshData& object) + { + return object.vertecies.empty() || object.indecies.empty(); + }), + objects.end()); + + if(objects.empty()) + { + name = fallbackName; + return; + } + + if(objects.size() == 1) + { + name = objects[0].name; + vertecies = std::move(objects[0].vertecies); + indecies = std::move(objects[0].indecies); + uvs = std::move(objects[0].uvs); + return; + } + + name = fallbackName; + for(auto& object : objects) + { + if(vertecies.size() + object.vertecies.size() > std::numeric_limits::max()) + { + TSE_WARNING("Combined OBJ mesh has more vertices than ushort indices can address: " + path); + continue; + } + + ushort indexOffset = static_cast(vertecies.size()); + vertecies.insert(vertecies.end(), object.vertecies.begin(), object.vertecies.end()); + uvs.insert(uvs.end(), object.uvs.begin(), object.uvs.end()); + for(ushort index : object.indecies) + { + indecies.push_back(static_cast(index + indexOffset)); + } + + Mesh* submesh = new Mesh(object.name, object.vertecies, object.indecies, object.uvs); + submesh->rawPath = path + "#" + object.name; + submesh->hash = MeshGeometryHash(*submesh); + submesh->IsVirtualAsset = true; + derivedAssets.push_back(submesh); + AssetLibrary::SetValue(submesh->id, submesh); + } +} + +bool TSE::Mesh::LoadAsset(string &path) +{ + namespace fs = std::filesystem; + + for(auto submesh : derivedAssets) + { + AssetLibrary::RemoveValue(submesh->id, submesh); + delete(submesh); + } + derivedAssets.clear(); + + if(vertecies.size() != 0) + { + vertecies.clear(); + } + if(indecies.size() != 0) + { + indecies.clear(); + } + if(uvs.size() != 0) + { + uvs.clear(); + } + rawPath = string(path); + name = rawPath; + string extension = fs::directory_entry(rawPath).path().extension().string(); + if(extension == ".obj") + { + LoadObj(rawPath); + } + + string metaPath = PathToMetaPath(rawPath); + using json = nlohmann::ordered_json; + + Hash(); + for(auto derivedAsset : derivedAssets) + { + Mesh* submesh = dynamic_cast(derivedAsset); + if(submesh != nullptr) + { + submesh->hash = MeshGeometryHash(*submesh); + } + } + + if(FileExists(metaPath)) + { + std::ifstream metaStream; + OpenFileReading(metaStream, metaPath); + json meta = json::parse(metaStream); + if(meta["type"] != "metadata") + { + metaStream.close(); + TSE_WARNING("Rebuilding corrupted meta file (Texture): " + metaPath); + SaveMeta(); + return true; + } + if(meta["version"] == 1) + { + if(meta["assetType"] == "mesh") + { + id = uuids::uuid::from_string((string)meta["id"]).value_or(id); + + std::unordered_map derivedAssetIds; + if(meta.contains("derivedAssets") && meta["derivedAssets"].is_array()) + { + for(const auto& derivedAssetMeta : meta["derivedAssets"]) + { + if(derivedAssetMeta.contains("hash") && derivedAssetMeta.contains("id")) + { + derivedAssetIds[(string)derivedAssetMeta["hash"]] = (string)derivedAssetMeta["id"]; + } + } + } + + for(auto derivedAsset : derivedAssets) + { + Mesh* submesh = dynamic_cast(derivedAsset); + if(submesh == nullptr) + { + continue; + } + + submesh->hash = MeshGeometryHash(*submesh); + auto idIt = derivedAssetIds.find(submesh->hash); + if(idIt == derivedAssetIds.end()) + { + continue; + } + + uuids::uuid oldId = submesh->id; + submesh->id = uuids::uuid::from_string(idIt->second).value_or(submesh->id); + if(oldId != submesh->id) + { + AssetLibrary::RemoveValue(oldId, submesh); + AssetLibrary::SetValue(submesh->id, submesh); + } + } + + metaStream.close(); + SaveMeta(); + return true; + } + else + { + metaStream.close(); + TSE_WARNING("Meta file incompatible with asset type. Please delete for auto regeneration or fix maualy (Mesh): " + metaPath); + return true; + } + } + else + { + metaStream.close(); + TSE_WARNING("Meta file incompatible. Please delete for auto regeneration or fix maualy (Mesh): " + metaPath); + return true; + } + } + else + { + SaveMeta(); + return true; + } +} + +void TSE::Mesh::SaveAsset() +{ + if(IsVirtualAsset) return; + Hash(); + SaveMeta(); +} + +void TSE::Mesh::Hash() +{ + std::ifstream ifs(rawPath, std::ios::binary); + std::vector s(picosha2::k_digest_size); + picosha2::hash256(ifs, s.begin(), s.end()); + hash = picosha2::hash256_hex_string(s); + ifs.close(); +} + +TSE::string TSE::Mesh::assetType() +{ + return "mesh"; +} + +void TSE::Mesh::UnloadAsset() +{ + for(auto submesh : derivedAssets) + { + AssetLibrary::RemoveValue(submesh->id, submesh); + delete(submesh); + } + AssetLibrary::RemoveValue(id, static_cast(this)); + delete(this); +} + +void TSE::Mesh::SaveMeta() +{ + string metaPath = PathToMetaPath(rawPath); + using json = nlohmann::ordered_json; + + json meta; + meta["type"] = "metadata"; + meta["assetType"] = "mesh"; + meta["version"] = 1; + meta["source"] = rawPath; //todo: clamp to assets root + meta["id"] = uuids::to_string(id); + meta["hash"] = hash; + meta["derivedAssets"] = json::array(); + + for(auto derivedAsset : derivedAssets) + { + Mesh* submesh = dynamic_cast(derivedAsset); + if(submesh == nullptr) + { + continue; + } + + submesh->hash = MeshGeometryHash(*submesh); + + json derivedAssetMeta; + derivedAssetMeta["name"] = submesh->name; + derivedAssetMeta["id"] = uuids::to_string(submesh->id); + derivedAssetMeta["hash"] = submesh->hash; + meta["derivedAssets"].push_back(derivedAssetMeta); + } + + std::ofstream metaStream; + CreateFileWriting(metaStream, metaPath); + metaStream << meta.dump(4) << std::endl; + metaStream.close(); +} diff --git a/TSE_Math/src/Mesh.hpp b/TSE_Core/src/elements/Mesh.hpp similarity index 81% rename from TSE_Math/src/Mesh.hpp rename to TSE_Core/src/elements/Mesh.hpp index 70a784f..f921dc3 100644 --- a/TSE_Math/src/Mesh.hpp +++ b/TSE_Core/src/elements/Mesh.hpp @@ -4,10 +4,11 @@ #include "Types.hpp" #include "Vector3.hpp" #include "Vector2.hpp" +#include "interfaces/IAsset.hpp" namespace TSE { - class Mesh + class Mesh : public IAsset { public: string name; @@ -41,5 +42,16 @@ namespace TSE /// @brief gives you a basic unit quad with (-0.5, -0.5) -> (0.5, 0.5) /// @return the resulting mesh static Mesh GetQuadMesh(); + + void LoadObj(const string& path); + + bool LoadAsset(string& path) override; + void SaveAsset() override; + void Hash() override; + string assetType() override; + void UnloadAsset() override; + + protected: + void SaveMeta() override; }; } // namespace TSE diff --git a/TSE_Core/src/elements/Texture.cpp b/TSE_Core/src/elements/Texture.cpp index 1824ea3..648e3e3 100644 --- a/TSE_Core/src/elements/Texture.cpp +++ b/TSE_Core/src/elements/Texture.cpp @@ -7,10 +7,16 @@ #include "PathHelper.hpp" #include "utils/JsonExports.hpp" #include "IdGenerator.hpp" +#include "AssetLibrary.hpp" TSE::Texture::Texture(const string &path) { id = GenerateRandomUUID(); + LoadImageFromFile(path); +} + +bool TSE::Texture::LoadImageFromFile(const string &path) +{ FREE_IMAGE_FORMAT fif = FREE_IMAGE_FORMAT::FIF_UNKNOWN; bmp = nullptr; imagePtr = nullptr; @@ -24,7 +30,7 @@ TSE::Texture::Texture(const string &path) TSE_ERROR(msg); Bpp = 24; makeError(*this); - return; + return false; } fif = FreeImage_GetFileType(name.c_str(), 0); @@ -35,12 +41,12 @@ TSE::Texture::Texture(const string &path) TSE_ERROR("Failed to load image. Unsupported Format."); Bpp = 24; makeError(*this); - return; + return false; } if(FreeImage_FIFSupportsReading(fif)) { - bmp = FreeImage_Load(fif, path.c_str()); - if(FreeImage_GetBPP(bmp) != 32) + bmp = FreeImage_Load(fif, name.c_str()); + if(bmp != nullptr && FreeImage_GetBPP(bmp) != 32) { auto tmpBmp = FreeImage_ConvertTo32Bits(bmp); FreeImage_Unload(bmp); @@ -52,7 +58,7 @@ TSE::Texture::Texture(const string &path) TSE_ERROR("Failed to load image. Bitmap was nullptr."); Bpp = 24; makeError(*this); - return; + return false; } Bpp = FreeImage_GetBPP(bmp); imagePtr = FreeImage_GetBits(bmp); @@ -70,6 +76,7 @@ TSE::Texture::Texture(const string &path) Size = Vector2(FreeImage_GetWidth(bmp), FreeImage_GetHeight(bmp)); regist(); + return true; } TSE::Texture::Texture(const int &width, const int &height, int bpp) @@ -476,7 +483,19 @@ void TSE::Texture::PlatformDestroy() bool TSE::Texture::LoadAsset(string &path) { - *this = Texture(path); + if(bmp != nullptr) + { + FreeImage_Unload(bmp); + bmp = nullptr; + } + if(TextureID != 0) + { + PlatformDestroy(); + TextureID = 0; + } + imagePtr = nullptr; + LoadImageFromFile(path); + rawPath = string(path); UpdateImportSettings(rawPath, 0, {1,1}); string metaPath = PathToMetaPath(rawPath); @@ -542,6 +561,7 @@ bool TSE::Texture::LoadAsset(string &path) void TSE::Texture::SaveAsset() { + if(IsVirtualAsset) return; Hash(); SaveMeta(); } @@ -564,6 +584,22 @@ void TSE::Texture::UpdateImportSettings(string name, byte mode, Vector2 count) settings.chanels = chanels; } +TSE::string TSE::Texture::assetType() +{ + return "texture"; +} + +void TSE::Texture::UnloadAsset() +{ + for(auto submesh : derivedAssets) + { + AssetLibrary::RemoveValue(submesh->id, submesh); + delete(submesh); + } + AssetLibrary::RemoveValue(id, static_cast(this)); + delete(this); +} + void TSE::Texture::SaveMeta() { string metaPath = PathToMetaPath(rawPath); diff --git a/TSE_Core/src/elements/Texture.hpp b/TSE_Core/src/elements/Texture.hpp index 1368906..3ac46ca 100644 --- a/TSE_Core/src/elements/Texture.hpp +++ b/TSE_Core/src/elements/Texture.hpp @@ -79,9 +79,14 @@ namespace TSE bool LoadAsset(string& path) override; void SaveAsset() override; void Hash() override; + TextureImportSettings& GetImportSettings() { return settings; } + const TextureImportSettings& GetImportSettings() const { return settings; } void UpdateImportSettings(string name, byte mode, Vector2 count); + string assetType() override; + void UnloadAsset() override; protected: + bool LoadImageFromFile(const string& path); void SaveMeta() override; }; } // namespace TSE diff --git a/TSE_Core/src/interfaces/IAsset.hpp b/TSE_Core/src/interfaces/IAsset.hpp index 96e06dc..798a74c 100644 --- a/TSE_Core/src/interfaces/IAsset.hpp +++ b/TSE_Core/src/interfaces/IAsset.hpp @@ -14,12 +14,15 @@ namespace TSE string rawPath; string hash; std::vector dependecies; + std::vector derivedAssets; bool IsVirtualAsset = false; virtual ~IAsset() = default; virtual bool LoadAsset(string& path) = 0; virtual void SaveAsset() = 0; virtual void Hash() = 0; + virtual string assetType() = 0; + virtual void UnloadAsset() = 0; inline void Rescan() { string oldHash = string(hash); diff --git a/TSE_Editor/src/EditorSubsystem.cpp b/TSE_Editor/src/EditorSubsystem.cpp index 1061aad..81440fb 100644 --- a/TSE_Editor/src/EditorSubsystem.cpp +++ b/TSE_Editor/src/EditorSubsystem.cpp @@ -20,6 +20,7 @@ TSE::EDITOR::EditorSubsystem::EditorSubsystem(ConsolView* cvp) : sv(nullptr), ed controller.AddGuiElement("Properties", &pv); controller.AddGuiElement("Debug", &dv); controller.AddGuiElement("Camera", &camv); + controller.AddGuiElement("Assets", &av); BehaviourScriptRegistry::RegisterBehaviourScript("Image", []() -> BehaviourScript* {return new Image();}); BehaviourScriptRegistry::RegisterBehaviourScript("Image Animation", []() -> BehaviourScript* {return new ImageAnimation();}); diff --git a/TSE_Editor/src/EditorSubsystem.hpp b/TSE_Editor/src/EditorSubsystem.hpp index df7fa2b..d6758fe 100644 --- a/TSE_Editor/src/EditorSubsystem.hpp +++ b/TSE_Editor/src/EditorSubsystem.hpp @@ -7,6 +7,7 @@ #include "UI/windows/HirearchieView.hpp" #include "UI/windows/PropertiesView.hpp" #include "UI/windows/SceneView.hpp" +#include "UI/windows/AssetsView.hpp" #include "interfaces/IRenderTexture.hpp" namespace TSE::EDITOR @@ -22,6 +23,7 @@ namespace TSE::EDITOR CameraView camv; TSE::IRenderTexture* rt; SceneView sv; + AssetsView av; Layer editorLayer; EditorSubsystem(ConsolView* cvp); diff --git a/TSE_Editor/src/UI/ElementDrawer.cpp b/TSE_Editor/src/UI/ElementDrawer.cpp index 9b52da3..bf7aa8d 100644 --- a/TSE_Editor/src/UI/ElementDrawer.cpp +++ b/TSE_Editor/src/UI/ElementDrawer.cpp @@ -1,12 +1,167 @@ #include "ElementDrawer.hpp" #include "BehaviourScriptRegistry.hpp" #include "elements/ShaderRegistry.hpp" +#include "elements/AssetLibrary.hpp" #include "BehaviourScripts/Camera.hpp" #include "windows/HirearchieView.hpp" +#include "windows/PropertiesView.hpp" #include +#include +#include namespace TSE::EDITOR { + namespace + { + char assetPickerSearchBuffer[128] = ""; + + std::string ToLowerCopy(std::string value) + { + std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) + { + return static_cast(std::tolower(c)); + }); + return value; + } + + template + std::vector GetAssetsOfType() + { + std::vector result; + int count = AssetLibrary::GetValueCount(); + for(int i = 0; i < count; i++) + { + auto [ptr, type, id] = AssetLibrary::GetValueAt(i); + if(type != typeid(T).name() && type != typeid(T*).name()) + { + continue; + } + + try + { + result.push_back(std::any_cast(ptr)); + } + catch(const std::bad_any_cast&) + { + } + } + return result; + } + + template + T* DrawAssetPickerPopup(const char* popupId, const char* searchHint, const char* emptyText, NameGetter getName) + { + T* selectedAsset = nullptr; + + ImGui::SetNextWindowSize({360.0f, 420.0f}, ImGuiCond_Appearing); + if(ImGui::BeginPopup(popupId)) + { + ImGui::PushItemWidth(-1.0f); + ImGui::InputTextWithHint("##AssetPickerSearch", searchHint, assetPickerSearchBuffer, IM_ARRAYSIZE(assetPickerSearchBuffer)); + ImGui::PopItemWidth(); + ImGui::Separator(); + + std::string filterText = ToLowerCopy(assetPickerSearchBuffer); + std::vector assets = GetAssetsOfType(); + + if(ImGui::BeginChild("##AssetPickerList", {0.0f, 0.0f}, ImGuiChildFlags_None, ImGuiWindowFlags_AlwaysVerticalScrollbar)) + { + int shownCount = 0; + for(T* asset : assets) + { + if(asset == nullptr) + { + continue; + } + + std::string assetName = getName(asset); + if(!filterText.empty() && ToLowerCopy(assetName).find(filterText) == std::string::npos) + { + continue; + } + + ImGui::PushID(asset); + if(ImGui::Selectable(assetName.c_str())) + { + selectedAsset = asset; + ImGui::CloseCurrentPopup(); + } + ImGui::PopID(); + shownCount++; + } + + if(shownCount == 0) + { + ImGui::TextDisabled("%s", emptyText); + } + } + ImGui::EndChild(); + ImGui::EndPopup(); + } + + return selectedAsset; + } + + template + bool BeginAssetField(T** element, const char* popupId) + { + if(element == nullptr) + { + ImGui::TextDisabled("No Asset Field"); + return false; + } + + ImGui::PushID(element); + + float buttonWidth = ImGui::GetFrameHeight(); + float fieldHeight = std::max(36.0f, ImGui::GetFrameHeightWithSpacing() + ImGui::GetStyle().WindowPadding.y * 2.0f); + + ImGui::BeginChild("##AssetField", {0.0f, fieldHeight}, ImGuiChildFlags_Borders, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + if(ImGui::Button("+##SelectAsset", {buttonWidth, 0.0f})) + { + assetPickerSearchBuffer[0] = '\0'; + ImGui::OpenPopup(popupId); + } + + ImGui::SameLine(); + ImGui::BeginChild("##AssetFieldValue", {0.0f, 0.0f}, ImGuiChildFlags_None, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); + return true; + } + + template + void EndAssetField() + { + ImGui::EndChild(); + } + + template + void OpenAssetPropertiesOnDoubleClick(T* asset) + { + if(asset == nullptr) + { + return; + } + + if(ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + { + PropertiesView::SetInspectorElement(InspectableType::IAsset, static_cast(asset)); + } + } + + template + void AssetSelector(T** element, const char* popupId, const char* searchHint, const char* emptyText, NameGetter getName) + { + T* selectedAsset = DrawAssetPickerPopup(popupId, searchHint, emptyText, getName); + if(selectedAsset != nullptr) + { + *element = selectedAsset; + } + + ImGui::EndChild(); + ImGui::PopID(); + } + } + #pragma region helper bool InputText(const char* label, std::string& str, size_t bufferSize = 256) { std::vector buffer(bufferSize); @@ -222,15 +377,7 @@ namespace TSE::EDITOR void ElementDrawer::Draw(Renderable *element, const bool &debug) { ImGui::SeparatorText("Material"); - int height = 100; - if(element->GetMaterial() == nullptr) - { - height = 35; - } - ImVec2 size(0, height); - ImGui::BeginChild("MaterialViewer", size, ImGuiChildFlags_Borders); - Draw(element->GetMaterial(), debug); - ImGui::EndChild(); + DrawMaterialField(element->GetMaterialRef()); } void ElementDrawer::Draw(MeshContainer *element, const bool &debug) { @@ -240,7 +387,7 @@ namespace TSE::EDITOR height = 35; } ImVec2 size(0, height); - Draw(element->GetMesh(), debug, "Mesh", true); + DrawMeshField(element->GetMeshRef()); } void ElementDrawer::Draw(Image *element, const bool &debug) { @@ -444,7 +591,7 @@ namespace TSE::EDITOR Texture* value = element->GetValue(name); Draw(value, debug, name , true); } - if (type == typeid(uint).name()) + else if (type == typeid(uint).name()) { int value = element->GetValue(name); if(ImGui::InputInt(name.c_str(), &value)) @@ -481,11 +628,6 @@ namespace TSE::EDITOR } void ElementDrawer::Draw(Mesh *element, const bool &debug, const std::string &label, const bool small) { - if(element == nullptr) - { - ImGui::Text("No Mesh Set"); - return; - } if(small) DrawMeshCompact(element, debug, label); else DrawMeshNormal(element, debug, label); } @@ -943,6 +1085,24 @@ namespace TSE::EDITOR ImGui::TextDisabled(("Chunk Count: " + std::to_string(element->GetChunkCount())).c_str()); } } + void ElementDrawer::Draw(IAsset *element, const bool &debug) + { + if (element->assetType() == "material") + { + Draw((Material*)element, debug); + } + else if (element->assetType() == "texture") + { + Draw((Texture*)element, debug); + } + else if (element->assetType() == "mesh") + { + Draw((Mesh*)element, debug); + } + + element->SaveAsset(); + } + void ElementDrawer::DrawAudioClipCompact(AudioClip *element, const bool &debug, const std::string &label) { float item_spacing = ImGui::GetStyle().ItemSpacing.x; @@ -1034,6 +1194,21 @@ namespace TSE::EDITOR } void ElementDrawer::DrawMeshCompact(Mesh *element, const bool &debug, const std::string &label) { + + if(element == nullptr) + { + ImGui::Text("No Mesh Set"); + return; + } + ImGui::Text(element->name.c_str()); + } + void ElementDrawer::DrawMeshNormal(Mesh *element, const bool &debug, const std::string &label) + { + if(element == nullptr) + { + ImGui::Text("No Mesh Set"); + return; + } float item_spacing = ImGui::GetStyle().ItemSpacing.x; ImVec2 label_size = ImGui::CalcTextSize(label.c_str()); @@ -1060,54 +1235,6 @@ namespace TSE::EDITOR cursorCurrent.y += 2; ImGui::SetCursorPos(cursorCurrent); ImGui::TextUnformatted(label.c_str()); - } - void ElementDrawer::DrawMeshNormal(Mesh *element, const bool &debug, const std::string &label) - { - ImGui::Text(("Name: " + element->name).c_str()); - if(debug) - { - //ImGui::TextDisabled(("ID: " + to_string(element->id)).c_str()); - } - ImGui::Separator(); - ImGui::Text(("Vectex Count: " + std::to_string(element->vertecies.size())).c_str()); - ImGui::Text(("Index Count: " + std::to_string(element->indecies.size())).c_str()); - ImGui::Text(("UV Count: " + std::to_string(element->uvs.size())).c_str()); - ImGui::Indent(20.0f); - if(ImGui::CollapsingHeader("Vertecies")) - { - ImGui::PushID("Verts"); - ImGui::BeginDisabled(); - for (int i = 0; i < element->vertecies.size(); i++) - { - ImGui::InputFloat3(std::to_string(i).c_str(), &element->vertecies[i].x); - } - ImGui::EndDisabled(); - ImGui::PopID(); - } - if(ImGui::CollapsingHeader("Indecies")) - { - ImGui::PushID("Inds"); - ImGui::BeginDisabled(); - for (int i = 0; i < element->indecies.size(); i++) - { - int val = element->indecies[i]; - ImGui::InputInt(std::to_string(i).c_str(), &val); - } - ImGui::EndDisabled(); - ImGui::PopID(); - } - if(ImGui::CollapsingHeader("UVs")) - { - ImGui::PushID("Uvs"); - ImGui::BeginDisabled(); - for (int i = 0; i < element->uvs.size(); i++) - { - ImGui::InputFloat2(std::to_string(i).c_str(), &element->uvs[i].x); - } - ImGui::EndDisabled(); - ImGui::PopID(); - } - ImGui::Unindent(20.0f); } void ElementDrawer::DrawSpriteCompact(Sprite *element, const bool &debug, const std::string &label) @@ -1215,6 +1342,98 @@ namespace TSE::EDITOR ImVec2 texSize (available_width, (available_width) * ymultiplyer); ImGui::Image(element->GetTextureId(), texSize, {0,1}, {1,0}); + ImGui::Separator(); + ImGui::SeparatorText("Import Settings"); + + TextureImportSettings& settings = element->GetImportSettings(); + bool importSettingsChanged = false; + + int importMode = static_cast(settings.importMode); + const char* importModes[] = { "Raw Texture", "Sprite", "Sprite Set", "Nine Tile" }; + if (ImGui::Combo("Import Mode", &importMode, importModes, IM_ARRAYSIZE(importModes))) + { + settings.importMode = static_cast(std::clamp(importMode, 0, IM_ARRAYSIZE(importModes) - 1)); + importSettingsChanged = true; + } + + int importCount[2] = { + static_cast(settings.importCount.x), + static_cast(settings.importCount.y) + }; + if (ImGui::InputInt2("Import Count", importCount)) + { + settings.importCount.x = static_cast(std::max(1, importCount[0])); + settings.importCount.y = static_cast(std::max(1, importCount[1])); + importSettingsChanged = true; + } + + int bpp = static_cast(settings.bpp); + if (ImGui::InputInt("BPP", &bpp)) + { + settings.bpp = static_cast(std::max(0, bpp)); + importSettingsChanged = true; + } + + int chanels = static_cast(settings.chanels); + if (ImGui::InputInt("Chanels", &chanels)) + { + settings.chanels = static_cast(std::clamp(chanels, 0, 255)); + importSettingsChanged = true; + } + + if (InputText("Import Name", settings.name)) + { + importSettingsChanged = true; + } + + if (importSettingsChanged) + { + element->SaveAsset(); + } + } + + void ElementDrawer::DrawMaterialCompact(Material *element, const bool &debug, const std::string &label) + { + if(element == nullptr) + { + ImGui::Text("No Mesh Set"); + return; + } + ImGui::Text(element->GetName().c_str()); + } + + void ElementDrawer::DrawMeshField(Mesh **element) + { + if(!BeginAssetField(element, "MeshAssetPicker")) + { + return; + } + + DrawMeshCompact(*element, false, ""); + OpenAssetPropertiesOnDoubleClick(*element); + EndAssetField(); + + AssetSelector(element, "MeshAssetPicker", "Search Mesh", "No meshes found", [](Mesh* mesh) + { + return mesh->name.empty() ? std::string("Unnamed Mesh") : mesh->name; + }); + } + + void ElementDrawer::DrawMaterialField(Material **element) + { + if(!BeginAssetField(element, "MaterialAssetPicker")) + { + return; + } + + DrawMaterialCompact(*element, false, ""); + OpenAssetPropertiesOnDoubleClick(*element); + EndAssetField(); + + AssetSelector(element, "MaterialAssetPicker", "Search Material", "No materials found", [](Material* mat) + { + return mat->GetName().empty() ? std::string("Unnamed Material") : mat->GetName(); + }); } } // namespace TSE::EDITOR diff --git a/TSE_Editor/src/UI/ElementDrawer.hpp b/TSE_Editor/src/UI/ElementDrawer.hpp index 21ddfcd..984f808 100644 --- a/TSE_Editor/src/UI/ElementDrawer.hpp +++ b/TSE_Editor/src/UI/ElementDrawer.hpp @@ -23,7 +23,8 @@ namespace TSE::EDITOR None, Transformable, Scene, - Layer + Layer, + IAsset }; struct Inspectable @@ -54,6 +55,9 @@ namespace TSE::EDITOR case InspectableType::Layer: Draw(static_cast(element.ptr), debug); break; + case InspectableType::IAsset: + Draw(static_cast(element.ptr), debug); + break; default: TSE_WARNING("Draw not implemented for this type."); break; @@ -83,6 +87,7 @@ namespace TSE::EDITOR static void Draw(ParticleSystem* element, const bool& debug); static void Draw(TileMap* element, const bool& debug); static void Draw(OrdererSpriteSet* element, const bool& debug); + static void Draw(IAsset* element, const bool& debug); static void DrawAudioClipCompact(AudioClip* element, const bool& debug, const std::string& label); static void DrawAudioClipNormal(AudioClip* element, const bool& debug, const std::string& label); @@ -94,5 +99,9 @@ namespace TSE::EDITOR static void DrawSpriteNormal(Sprite* element, const bool& debug, const std::string& label); static void DrawTextureCompact(Texture* element, const bool& debug, const std::string& label); static void DrawTextureNormal(Texture* element, const bool& debug, const std::string& label); + static void DrawMaterialCompact(Material* element, const bool& debug, const std::string& label); + + static void DrawMeshField(Mesh** element); + static void DrawMaterialField(Material** element); }; } // namespace TSE::EDITOR diff --git a/TSE_Editor/src/UI/windows/AssetsView.cpp b/TSE_Editor/src/UI/windows/AssetsView.cpp new file mode 100644 index 0000000..f2f7624 --- /dev/null +++ b/TSE_Editor/src/UI/windows/AssetsView.cpp @@ -0,0 +1,751 @@ +#include "AssetsView.hpp" +#include "elements/AssetLibrary.hpp" +#include "PathHelper.hpp" +#include "json.hpp" +#include "uuid.h" +#include "elements/Material.hpp" +#include "elements/Texture.hpp" +#include "PropertiesView.hpp" + +#include +#include +#include +#include + +namespace +{ + std::vector GetDirectories(const std::filesystem::path& folderPath) + { + std::vector directories; + std::error_code errorCode; + + for(const auto& entry : std::filesystem::directory_iterator(folderPath, errorCode)) + { + std::error_code entryErrorCode; + if(entry.is_directory(entryErrorCode)) + directories.push_back(entry); + } + + std::sort(directories.begin(), directories.end(), [](const auto& a, const auto& b) + { + return a.path().filename().string() < b.path().filename().string(); + }); + + return directories; + } + + std::vector GetFiles(const std::filesystem::path& folderPath) + { + std::vector files; + std::error_code errorCode; + + for(const auto& entry : std::filesystem::directory_iterator(folderPath, errorCode)) + { + std::error_code entryErrorCode; + if(entry.is_regular_file(entryErrorCode) && entry.path().extension() != ".meta") + files.push_back(entry); + } + + std::sort(files.begin(), files.end(), [](const auto& a, const auto& b) + { + return a.path().filename().string() < b.path().filename().string(); + }); + + return files; + } + + bool IsPathInFolder(const std::filesystem::path& path, const std::filesystem::path& folderPath) + { + if(path.empty() || folderPath.empty()) + return false; + + const std::filesystem::path relativePath = path.lexically_relative(folderPath); + if(relativePath.empty() || relativePath == ".") + return true; + + const auto firstPart = relativePath.begin(); + return firstPart != relativePath.end() && firstPart->string() != ".."; + } + + bool IsSameOrChildPath(const std::filesystem::path& path, const std::filesystem::path& possibleParentPath) + { + return IsPathInFolder(path.lexically_normal(), possibleParentPath.lexically_normal()); + } + + std::string FitTextToWidth(const std::string& text, float maxWidth) + { + if(ImGui::CalcTextSize(text.c_str()).x <= maxWidth) + return text; + + std::string result = text; + while(result.size() > 3) + { + result.pop_back(); + const std::string truncated = result + "..."; + if(ImGui::CalcTextSize(truncated.c_str()).x <= maxWidth) + return truncated; + } + + return "..."; + } +} + +TSE::EDITOR::AssetsView::AssetsView() : GuiWindow("Assets", ImGuiWindowFlags_NoScrollWithMouse | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar) +{ +} + +void TSE::EDITOR::AssetsView::Define() +{ + const float splitterWidth = 1.0f; + const float minPanelWidth = 100.0f; + const ImVec2 availableRegion = ImGui::GetContentRegionAvail(); + + if(availableRegion.x <= splitterWidth) + return; + + if(leftPanelWidth < 0.0f) + leftPanelWidth = availableRegion.x / 3.0f; + + float maxLeftPanelWidth = availableRegion.x - splitterWidth - minPanelWidth; + if(maxLeftPanelWidth < minPanelWidth) + maxLeftPanelWidth = (availableRegion.x - splitterWidth) * 0.5f; + + if(leftPanelWidth < minPanelWidth) + leftPanelWidth = minPanelWidth; + if(leftPanelWidth > maxLeftPanelWidth) + leftPanelWidth = maxLeftPanelWidth; + + if(leftPanelWidth < 1.0f) + leftPanelWidth = 1.0f; + + auto clampLeftPanelWidth = [&]() + { + if(leftPanelWidth < minPanelWidth) + leftPanelWidth = minPanelWidth; + if(leftPanelWidth > maxLeftPanelWidth) + leftPanelWidth = maxLeftPanelWidth; + if(leftPanelWidth < 1.0f) + leftPanelWidth = 1.0f; + }; + + ImGuiWindowFlags childFlags = ImGuiWindowFlags_None; + + if(ImGui::BeginChild("##AssetsTreeView", {leftPanelWidth, 0.0f}, ImGuiChildFlags_Borders, childFlags)) + { + DefineTreeView(); + } + ImGui::EndChild(); + + ImGui::SameLine(0.0f, 0.0f); + + ImGui::InvisibleButton("##AssetsSplitter", {splitterWidth, availableRegion.y}); + if(ImGui::IsItemActive()) + { + leftPanelWidth += ImGui::GetIO().MouseDelta.x; + clampLeftPanelWidth(); + } + + if(ImGui::IsItemHovered() || ImGui::IsItemActive()) + ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW); + + ImU32 splitterColor = ImGui::GetColorU32(ImGuiCol_Separator); + if(ImGui::IsItemHovered()) + splitterColor = ImGui::GetColorU32(ImGuiCol_SeparatorHovered); + if(ImGui::IsItemActive()) + splitterColor = ImGui::GetColorU32(ImGuiCol_SeparatorActive); + + ImGui::GetWindowDrawList()->AddRectFilled(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), splitterColor); + + ImGui::SameLine(0.0f, 0.0f); + + if(ImGui::BeginChild("##AssetsItemView", {0.0f, 0.0f}, ImGuiChildFlags_Borders, childFlags)) + { + DefineItemView(); + } + ImGui::EndChild(); +} + +void TSE::EDITOR::AssetsView::DefineTreeView() +{ + const bool assetsOpen = ImGui::CollapsingHeader("Assets", ImGuiTreeNodeFlags_DefaultOpen); + const std::filesystem::path assetPath = TSE::AssetLibrary::GetCurrentAssetPath(); + std::error_code errorCode; + + if(assetPath.empty() || !std::filesystem::exists(assetPath, errorCode) || !std::filesystem::is_directory(assetPath, errorCode)) + { + DefineFolderNamePopup(); + return; + } + + AcceptAssetDrop(assetPath); + + if(assetsOpen) + { + DisplayDirectoryTree(assetPath); + + ImVec2 remainingSpace = ImGui::GetContentRegionAvail(); + if(remainingSpace.y > 0.0f) + { + if(remainingSpace.x < 1.0f) + remainingSpace.x = 1.0f; + + ImGui::InvisibleButton("##AssetsRootDropArea", remainingSpace); + AcceptAssetDrop(assetPath); + if(ImGui::BeginPopupContextItem("##AssetsRootContext")) + { + DefineFolderBackgroundContextMenu(assetPath); + ImGui::EndPopup(); + } + } + } + + DefineFolderNamePopup(); +} + +void TSE::EDITOR::AssetsView::DefineItemView() +{ + DefineItemViewToolbar(); + + const std::filesystem::path browserPath = GetCurrentBrowserPath(); + std::error_code errorCode; + if(browserPath.empty() || !std::filesystem::exists(browserPath, errorCode) || !std::filesystem::is_directory(browserPath, errorCode)) + return; + + if(ImGui::BeginChild("##AssetsItemScroll", {0.0f, 0.0f}, ImGuiChildFlags_None, ImGuiWindowFlags_AlwaysVerticalScrollbar)) + { + if(itemViewMode == ItemViewMode::List) + DefineItemListView(browserPath); + else + DefineItemTileView(browserPath); + + ImVec2 remainingSpace = ImGui::GetContentRegionAvail(); + if(remainingSpace.y > 0.0f) + { + if(remainingSpace.x < 1.0f) + remainingSpace.x = 1.0f; + + ImGui::InvisibleButton("##AssetsItemRootDropArea", remainingSpace); + AcceptAssetDrop(browserPath); + if(ImGui::BeginPopupContextItem("##AssetsItemRootContext")) + { + DefineFolderBackgroundContextMenu(browserPath); + ImGui::EndPopup(); + } + } + + if(ImGui::BeginPopupContextWindow("##AssetsItemBackgroundContext", ImGuiPopupFlags_MouseButtonRight | ImGuiPopupFlags_NoOpenOverItems)) + { + DefineFolderBackgroundContextMenu(browserPath); + ImGui::EndPopup(); + } + } + ImGui::EndChild(); +} + +void TSE::EDITOR::AssetsView::DefineItemViewToolbar() +{ + const float buttonWidth = 64.0f; + const float totalWidth = buttonWidth * 2.0f + ImGui::GetStyle().ItemSpacing.x; + const float startX = ImGui::GetContentRegionAvail().x - totalWidth; + if(startX > 0.0f) + ImGui::SetCursorPosX(ImGui::GetCursorPosX() + startX); + + if(ImGui::Selectable("List", itemViewMode == ItemViewMode::List, 0, {buttonWidth, 0.0f})) + itemViewMode = ItemViewMode::List; + ImGui::SameLine(); + if(ImGui::Selectable("Tiles", itemViewMode == ItemViewMode::Tiles, 0, {buttonWidth, 0.0f})) + itemViewMode = ItemViewMode::Tiles; +} + +void TSE::EDITOR::AssetsView::DefineItemListView(const std::filesystem::path& folderPath) +{ + for(const auto& folderEntry : GetDirectories(folderPath)) + DefineFolderListItem(folderEntry); + + for(const auto& fileEntry : GetFiles(folderPath)) + DefineFileListItem(fileEntry); +} + +void TSE::EDITOR::AssetsView::DefineItemTileView(const std::filesystem::path& folderPath) +{ + const float tileSize = 96.0f; + const float spacing = ImGui::GetStyle().ItemSpacing.x; + const float availableWidth = ImGui::GetContentRegionAvail().x; + int columns = static_cast(availableWidth / (tileSize + spacing)); + if(columns < 1) + columns = 1; + + int index = 0; + auto nextTile = [&]() + { + index++; + if(index % columns != 0) + ImGui::SameLine(); + }; + + for(const auto& folderEntry : GetDirectories(folderPath)) + { + DefineFolderTileItem(folderEntry); + nextTile(); + } + + for(const auto& fileEntry : GetFiles(folderPath)) + { + DefineFileTileItem(fileEntry); + nextTile(); + } +} + +void TSE::EDITOR::AssetsView::DefineFolderListItem(const std::filesystem::directory_entry& folderEntry) +{ + const std::filesystem::path folderPath = folderEntry.path(); + ImGui::PushID(folderPath.string().c_str()); + + ImGui::TextUnformatted("[D]"); + ImGui::SameLine(); + + const bool selected = currentSelectedPath == folderPath; + ImGui::Selectable(folderPath.filename().string().c_str(), selected, 0, {ImGui::GetContentRegionAvail().x, 0.0f}); + if(ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + currentSelectedPath = folderPath; + + if(ImGui::BeginDragDropSource()) + { + const std::string payloadPath = folderPath.string(); + ImGui::SetDragDropPayload("ASSET_FOLDER_PATH", payloadPath.c_str(), payloadPath.size() + 1); + ImGui::Text("%s", folderPath.filename().string().c_str()); + ImGui::EndDragDropSource(); + } + + AcceptAssetDrop(folderPath); + + if(ImGui::BeginPopupContextItem()) + { + DefineFolderContextMenu(folderPath); + ImGui::EndPopup(); + } + + ImGui::PopID(); +} + +void TSE::EDITOR::AssetsView::DefineFileListItem(const std::filesystem::directory_entry& fileEntry) +{ + const std::filesystem::path filePath = fileEntry.path(); + ImGui::PushID(filePath.string().c_str()); + + ImGui::TextUnformatted("[F]"); + ImGui::SameLine(); + + ImGui::Selectable(filePath.filename().string().c_str(), false, 0, {ImGui::GetContentRegionAvail().x, 0.0f}); + if(ImGui::IsItemClicked(ImGuiMouseButton_Left)) + OpenFileProperties(filePath.string()); + + if(ImGui::BeginDragDropSource()) + { + const std::string payloadPath = filePath.string(); + ImGui::SetDragDropPayload("ASSET_FILE_PATH", payloadPath.c_str(), payloadPath.size() + 1); + ImGui::Text("%s", filePath.filename().string().c_str()); + ImGui::EndDragDropSource(); + } + + if(ImGui::BeginPopupContextItem()) + { + DefineFileContextMenu(filePath); + ImGui::EndPopup(); + } + + ImGui::PopID(); +} + +void TSE::EDITOR::AssetsView::DefineFolderTileItem(const std::filesystem::directory_entry& folderEntry) +{ + const std::filesystem::path folderPath = folderEntry.path(); + const float tileSize = 96.0f; + ImGui::PushID(folderPath.string().c_str()); + + const bool selected = currentSelectedPath == folderPath; + ImGui::Selectable("##FolderTile", selected, 0, {tileSize, tileSize}); + if(ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) + currentSelectedPath = folderPath; + + ImVec2 itemMin = ImGui::GetItemRectMin(); + ImVec2 itemMax = ImGui::GetItemRectMax(); + ImDrawList* drawList = ImGui::GetWindowDrawList(); + const std::string icon = "[D]"; + const std::string name = FitTextToWidth(folderPath.filename().string(), tileSize - 12.0f); + const ImVec2 iconSize = ImGui::CalcTextSize(icon.c_str()); + const ImVec2 nameSize = ImGui::CalcTextSize(name.c_str()); + drawList->AddText({itemMin.x + (tileSize - iconSize.x) * 0.5f, itemMin.y + 22.0f}, ImGui::GetColorU32(ImGuiCol_Text), icon.c_str()); + drawList->AddText({itemMin.x + (tileSize - nameSize.x) * 0.5f, itemMax.y - 28.0f}, ImGui::GetColorU32(ImGuiCol_Text), name.c_str()); + + if(ImGui::BeginDragDropSource()) + { + const std::string payloadPath = folderPath.string(); + ImGui::SetDragDropPayload("ASSET_FOLDER_PATH", payloadPath.c_str(), payloadPath.size() + 1); + ImGui::Text("%s", folderPath.filename().string().c_str()); + ImGui::EndDragDropSource(); + } + + AcceptAssetDrop(folderPath); + + if(ImGui::BeginPopupContextItem()) + { + DefineFolderContextMenu(folderPath); + ImGui::EndPopup(); + } + + ImGui::PopID(); +} + +void TSE::EDITOR::AssetsView::DefineFileTileItem(const std::filesystem::directory_entry& fileEntry) +{ + const std::filesystem::path filePath = fileEntry.path(); + const float tileSize = 96.0f; + ImGui::PushID(filePath.string().c_str()); + + ImGui::Selectable("##FileTile", false, 0, {tileSize, tileSize}); + if(ImGui::IsItemClicked(ImGuiMouseButton_Left)) + OpenFileProperties(filePath.string()); + + ImVec2 itemMin = ImGui::GetItemRectMin(); + ImVec2 itemMax = ImGui::GetItemRectMax(); + ImDrawList* drawList = ImGui::GetWindowDrawList(); + const std::string icon = "[F]"; + const std::string name = FitTextToWidth(filePath.filename().string(), tileSize - 12.0f); + const ImVec2 iconSize = ImGui::CalcTextSize(icon.c_str()); + const ImVec2 nameSize = ImGui::CalcTextSize(name.c_str()); + drawList->AddText({itemMin.x + (tileSize - iconSize.x) * 0.5f, itemMin.y + 22.0f}, ImGui::GetColorU32(ImGuiCol_Text), icon.c_str()); + drawList->AddText({itemMin.x + (tileSize - nameSize.x) * 0.5f, itemMax.y - 28.0f}, ImGui::GetColorU32(ImGuiCol_Text), name.c_str()); + + if(ImGui::BeginDragDropSource()) + { + const std::string payloadPath = filePath.string(); + ImGui::SetDragDropPayload("ASSET_FILE_PATH", payloadPath.c_str(), payloadPath.size() + 1); + ImGui::Text("%s", filePath.filename().string().c_str()); + ImGui::EndDragDropSource(); + } + + if(ImGui::BeginPopupContextItem()) + { + DefineFileContextMenu(filePath); + ImGui::EndPopup(); + } + + ImGui::PopID(); +} + +void TSE::EDITOR::AssetsView::DisplayDirectoryTree(const std::filesystem::path& folderPath) +{ + std::vector childDirectories = GetDirectories(folderPath); + for(const auto& childDirectory : childDirectories) + { + const std::filesystem::path childPath = childDirectory.path(); + const std::vector grandChildDirectories = GetDirectories(childPath); + + ImGui::PushID(childPath.string().c_str()); + + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanFullWidth; + if(grandChildDirectories.empty()) + flags |= ImGuiTreeNodeFlags_Leaf | ImGuiTreeNodeFlags_NoTreePushOnOpen; + if(currentSelectedPath == childPath) + flags |= ImGuiTreeNodeFlags_Selected; + + const bool open = ImGui::TreeNodeEx(childPath.filename().string().c_str(), flags); + if(ImGui::IsItemClicked(ImGuiMouseButton_Left) && !ImGui::IsItemToggledOpen()) + currentSelectedPath = childPath; + + if(ImGui::BeginDragDropSource()) + { + const std::string payloadPath = childPath.string(); + ImGui::SetDragDropPayload("ASSET_FOLDER_PATH", payloadPath.c_str(), payloadPath.size() + 1); + ImGui::Text("%s", childPath.filename().string().c_str()); + ImGui::EndDragDropSource(); + } + + AcceptAssetDrop(childPath); + + bool folderDeleted = false; + if(ImGui::BeginPopupContextItem()) + { + folderDeleted = DefineFolderContextMenu(childPath); + ImGui::EndPopup(); + } + + if(open && !grandChildDirectories.empty()) + { + if(!folderDeleted) + DisplayDirectoryTree(childPath); + ImGui::TreePop(); + } + + ImGui::PopID(); + } +} + +bool TSE::EDITOR::AssetsView::DefineFolderContextMenu(const std::filesystem::path& folderPath) +{ + if(ImGui::MenuItem("Rename")) + RequestFolderNamePopup(FolderPopupPurpose::Rename, folderPath); + + bool folderDeleted = false; + if(ImGui::MenuItem("Delete")) + { + DeleteFolder(folderPath); + folderDeleted = true; + } + + if(ImGui::MenuItem("New Folder")) + RequestFolderNamePopup(FolderPopupPurpose::NewFolder, folderPath); + + return folderDeleted; +} + +void TSE::EDITOR::AssetsView::DefineFolderBackgroundContextMenu(const std::filesystem::path& folderPath) +{ + if(ImGui::MenuItem("New Folder")) + RequestFolderNamePopup(FolderPopupPurpose::NewFolder, folderPath); +} + +bool TSE::EDITOR::AssetsView::DefineFileContextMenu(const std::filesystem::path& filePath) +{ + if(ImGui::MenuItem("Rename")) + RequestFolderNamePopup(FolderPopupPurpose::Rename, filePath); + + bool fileDeleted = false; + if(ImGui::MenuItem("Delete")) + { + DeleteFile(filePath); + fileDeleted = true; + } + + return fileDeleted; +} + +void TSE::EDITOR::AssetsView::AcceptAssetDrop(const std::filesystem::path& targetFolderPath) +{ + if(ImGui::BeginDragDropTarget()) + { + const ImGuiPayload* folderPayload = ImGui::AcceptDragDropPayload("ASSET_FOLDER_PATH"); + if(folderPayload != nullptr && folderPayload->Data != nullptr) + { + const char* payloadPath = static_cast(folderPayload->Data); + MoveFolder(std::filesystem::path(payloadPath), targetFolderPath); + } + + const ImGuiPayload* filePayload = ImGui::AcceptDragDropPayload("ASSET_FILE_PATH"); + if(filePayload != nullptr && filePayload->Data != nullptr) + { + const char* payloadPath = static_cast(filePayload->Data); + MoveFile(std::filesystem::path(payloadPath), targetFolderPath); + } + + ImGui::EndDragDropTarget(); + } +} + +void TSE::EDITOR::AssetsView::MoveFolder(const std::filesystem::path& sourceFolderPath, const std::filesystem::path& targetFolderPath) +{ + const std::filesystem::path sourcePath = sourceFolderPath.lexically_normal(); + const std::filesystem::path targetPath = targetFolderPath.lexically_normal(); + + if(sourcePath.empty() || targetPath.empty()) + return; + if(sourcePath.parent_path() == targetPath) + return; + if(IsSameOrChildPath(targetPath, sourcePath)) + return; + + const std::filesystem::path newFolderPath = targetPath / sourcePath.filename(); + std::error_code errorCode; + if(std::filesystem::exists(newFolderPath, errorCode)) + return; + + std::filesystem::rename(sourcePath, newFolderPath, errorCode); + if(errorCode) + return; + + if(currentSelectedPath == sourcePath) + currentSelectedPath = newFolderPath; + else if(IsSameOrChildPath(currentSelectedPath, sourcePath)) + currentSelectedPath = newFolderPath / currentSelectedPath.lexically_relative(sourcePath); + + TSE::AssetLibrary::RescanAssets(); +} + +void TSE::EDITOR::AssetsView::MoveFile(const std::filesystem::path& sourceFilePath, const std::filesystem::path& targetFolderPath) +{ + const std::filesystem::path sourcePath = sourceFilePath.lexically_normal(); + const std::filesystem::path targetPath = targetFolderPath.lexically_normal(); + + if(sourcePath.empty() || targetPath.empty()) + return; + if(sourcePath.parent_path() == targetPath) + return; + + const std::filesystem::path newFilePath = targetPath / sourcePath.filename(); + std::error_code errorCode; + if(std::filesystem::exists(newFilePath, errorCode)) + return; + + std::filesystem::rename(sourcePath, newFilePath, errorCode); + if(!errorCode) + TSE::AssetLibrary::RescanAssets(); +} + +void TSE::EDITOR::AssetsView::RequestFolderNamePopup(FolderPopupPurpose purpose, const std::filesystem::path& folderPath) +{ + folderPopupPurpose = purpose; + pendingFolderPath = folderPath; + openFolderNamePopup = true; + focusFolderNameInput = true; + + const std::string defaultName = purpose == FolderPopupPurpose::Rename ? folderPath.filename().string() : ""; + std::snprintf(folderNameBuffer, sizeof(folderNameBuffer), "%s", defaultName.c_str()); +} + +void TSE::EDITOR::AssetsView::DefineFolderNamePopup() +{ + if(openFolderNamePopup) + { + openFolderNamePopup = false; + ImGui::OpenPopup("Folder Name"); + } + + if(ImGui::BeginPopupModal("Folder Name", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::Text("Name:"); + if(focusFolderNameInput) + { + ImGui::SetKeyboardFocusHere(); + focusFolderNameInput = false; + } + ImGui::InputText("##FolderName", folderNameBuffer, sizeof(folderNameBuffer)); + + const bool inputValid = folderNameBuffer[0] != '\0'; + if(!inputValid) + ImGui::TextColored(ImVec4(1, 0, 0, 1), "Not Valid."); + + if(!inputValid) + ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f); + + if(ImGui::Button("OK", ImVec2(120, 0)) && inputValid) + { + ConfirmFolderNamePopup(); + ImGui::CloseCurrentPopup(); + } + + if(!inputValid) + ImGui::PopStyleVar(); + + ImGui::SameLine(); + if(ImGui::Button("Cancel", ImVec2(120, 0))) + { + folderPopupPurpose = FolderPopupPurpose::None; + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } +} + +void TSE::EDITOR::AssetsView::ConfirmFolderNamePopup() +{ + const std::string folderName = folderNameBuffer; + if(folderName.empty()) + return; + + std::error_code errorCode; + if(folderPopupPurpose == FolderPopupPurpose::Rename) + { + const std::filesystem::path newFolderPath = pendingFolderPath.parent_path() / folderName; + if(newFolderPath != pendingFolderPath && !std::filesystem::exists(newFolderPath, errorCode)) + { + const bool selectedPathIsInRenamedFolder = IsSameOrChildPath(currentSelectedPath, pendingFolderPath); + const std::filesystem::path selectedRelativePath = selectedPathIsInRenamedFolder ? currentSelectedPath.lexically_relative(pendingFolderPath) : std::filesystem::path(); + + std::filesystem::rename(pendingFolderPath, newFolderPath, errorCode); + if(!errorCode) + { + if(selectedPathIsInRenamedFolder) + currentSelectedPath = newFolderPath / selectedRelativePath; + TSE::AssetLibrary::RescanAssets(); + } + } + } + else if(folderPopupPurpose == FolderPopupPurpose::NewFolder) + { + const std::filesystem::path newFolderPath = pendingFolderPath / folderName; + if(!std::filesystem::exists(newFolderPath, errorCode)) + { + std::filesystem::create_directory(newFolderPath, errorCode); + if(!errorCode) + { + currentSelectedPath = newFolderPath; + TSE::AssetLibrary::RescanAssets(); + } + } + } + + folderPopupPurpose = FolderPopupPurpose::None; +} + +void TSE::EDITOR::AssetsView::DeleteFolder(const std::filesystem::path& folderPath) +{ + std::error_code errorCode; + std::filesystem::remove_all(folderPath, errorCode); + + if(!errorCode && IsPathInFolder(currentSelectedPath, folderPath)) + currentSelectedPath.clear(); + if(!errorCode) + TSE::AssetLibrary::RescanAssets(); +} + +void TSE::EDITOR::AssetsView::DeleteFile(const std::filesystem::path& filePath) +{ + std::error_code errorCode; + std::filesystem::remove(filePath, errorCode); + + if(!errorCode) + TSE::AssetLibrary::RescanAssets(); +} + +std::filesystem::path TSE::EDITOR::AssetsView::GetCurrentBrowserPath() const +{ + std::error_code errorCode; + if(!currentSelectedPath.empty() && std::filesystem::exists(currentSelectedPath, errorCode) && std::filesystem::is_directory(currentSelectedPath, errorCode)) + return currentSelectedPath; + + return TSE::AssetLibrary::GetCurrentAssetPath(); +} + +void TSE::EDITOR::AssetsView::OpenFileProperties(string path) +{ + string metapath = path + ".meta"; + if(FileExists(metapath)) + { + using json = nlohmann::ordered_json; + std::ifstream metaStream; + OpenFileReading(metaStream, metapath); + json meta = json::parse(metaStream); + + uuids::uuid id = uuids::uuid::from_string((string)meta["id"]).value_or(id); + + if(!AssetLibrary::HasValue(id)) return; + + IAsset* assetPtr = nullptr; + + if(meta["assetType"] == "material") + { + assetPtr = static_cast(AssetLibrary::GetValue(id)); + } + else if(meta["assetType"] == "texture") + { + assetPtr = static_cast(AssetLibrary::GetValue(id)); + } + + if(assetPtr != nullptr) + { + PropertiesView::SetInspectorElement(InspectableType::IAsset, assetPtr); + } + } +} diff --git a/TSE_Editor/src/UI/windows/AssetsView.hpp b/TSE_Editor/src/UI/windows/AssetsView.hpp new file mode 100644 index 0000000..f36103c --- /dev/null +++ b/TSE_Editor/src/UI/windows/AssetsView.hpp @@ -0,0 +1,62 @@ +#pragma once + +#include "UI/base/GuiWindow.h" + +#include + +namespace TSE::EDITOR +{ + class AssetsView : public GuiWindow + { + private: + enum class FolderPopupPurpose + { + None, + Rename, + NewFolder + }; + + enum class ItemViewMode + { + List, + Tiles + }; + + float leftPanelWidth = -1.0f; + ItemViewMode itemViewMode = ItemViewMode::List; + std::filesystem::path currentSelectedPath; + std::filesystem::path pendingFolderPath; + FolderPopupPurpose folderPopupPurpose = FolderPopupPurpose::None; + bool openFolderNamePopup = false; + bool focusFolderNameInput = false; + char folderNameBuffer[256] = ""; + + void DefineTreeView(); + void DefineItemView(); + void DefineItemViewToolbar(); + void DefineItemListView(const std::filesystem::path& folderPath); + void DefineItemTileView(const std::filesystem::path& folderPath); + void DefineFolderListItem(const std::filesystem::directory_entry& folderEntry); + void DefineFileListItem(const std::filesystem::directory_entry& fileEntry); + void DefineFolderTileItem(const std::filesystem::directory_entry& folderEntry); + void DefineFileTileItem(const std::filesystem::directory_entry& fileEntry); + void DisplayDirectoryTree(const std::filesystem::path& folderPath); + bool DefineFolderContextMenu(const std::filesystem::path& folderPath); + void DefineFolderBackgroundContextMenu(const std::filesystem::path& folderPath); + bool DefineFileContextMenu(const std::filesystem::path& filePath); + void AcceptAssetDrop(const std::filesystem::path& targetFolderPath); + void MoveFolder(const std::filesystem::path& sourceFolderPath, const std::filesystem::path& targetFolderPath); + void MoveFile(const std::filesystem::path& sourceFilePath, const std::filesystem::path& targetFolderPath); + void RequestFolderNamePopup(FolderPopupPurpose purpose, const std::filesystem::path& folderPath); + void DefineFolderNamePopup(); + void ConfirmFolderNamePopup(); + void DeleteFolder(const std::filesystem::path& folderPath); + void DeleteFile(const std::filesystem::path& filePath); + std::filesystem::path GetCurrentBrowserPath() const; + void OpenFileProperties(string path); + + public: + AssetsView(); + void Define() override; + }; +} // namespace TSE::EDITOR diff --git a/TSE_Math/src/Mesh.cpp b/TSE_Math/src/Mesh.cpp deleted file mode 100644 index 7b6c1f8..0000000 --- a/TSE_Math/src/Mesh.cpp +++ /dev/null @@ -1,78 +0,0 @@ -#include "Mesh.hpp" -#include "MathF.hpp" -#include - -TSE::Mesh::Mesh() -{ - name = ""; -} - -TSE::Mesh::Mesh(string _name, const std::vector &_vertecies, const std::vector &_indecies, const std::vector &_uvs) -{ - name = _name; - vertecies = std::move(_vertecies); - indecies = std::move(_indecies); - uvs = std::move(_uvs); -} - -size_t TSE::Mesh::IndeciesCount() const -{ - return indecies.size(); -} - -size_t TSE::Mesh::VerteciesCount() const -{ - return vertecies.size(); -} - -TSE::Mesh TSE::Mesh::GetCircleMesh(ushort segments) -{ - std::vector verts; - std::vector indices; - std::vector uvs; - - verts.emplace_back(0.0f, 0.0f, 0.0f); - uvs.emplace_back(0.5f, 0.5f); - - float angleStep = 2.0f * TSE_PI / segments; - - for (int i = 0; i <= segments; ++i) { - float angle = i * angleStep; - float x = std::cos(angle) * 0.5f; - float y = std::sin(angle) * 0.5f; - verts.emplace_back(x, y, 0); - uvs.emplace_back(x + 0.5f, y + 0.5f); - - if (i > 0) { - indices.push_back(0); - indices.push_back(i); - indices.push_back(i + 1); - } - } - - return Mesh("Circle", verts, indices, uvs); -} - -TSE::Mesh TSE::Mesh::GetQuadMesh() -{ - std::vector verts = { - Vector3(-0.5f, -0.5f, 0), - Vector3( 0.5f, -0.5f, 0), - Vector3( 0.5f, 0.5f, 0), - Vector3(-0.5f, 0.5f, 0) - }; - - std::vector uvs = { - Vector2(0.0f, 0.0f), - Vector2(1.0f, 0.0f), - Vector2(1.0f, 1.0f), - Vector2(0.0f, 1.0f) - }; - - std::vector indices = { - 0, 1, 2, - 2, 3, 0 - }; - - return Mesh("Quad", verts, indices, uvs); -}