Implement initial Unity-style asset library cache
This commit is contained in:
1595
docs/plan/Unity式Library资产导入与缓存系统重构方案.md
Normal file
1595
docs/plan/Unity式Library资产导入与缓存系统重构方案.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -121,6 +121,27 @@ fs::path MakeCaseOnlyRenameTempPath(const fs::path& sourcePath) {
|
||||
}
|
||||
}
|
||||
|
||||
fs::path GetMetaSidecarPath(const fs::path& assetPath) {
|
||||
return fs::path(assetPath.wstring() + L".meta");
|
||||
}
|
||||
|
||||
bool RenamePathCaseAware(const fs::path& sourcePath, const fs::path& destPath);
|
||||
|
||||
void MoveMetaSidecarIfPresent(const fs::path& sourcePath, const fs::path& destPath) {
|
||||
const fs::path sourceMetaPath = GetMetaSidecarPath(sourcePath);
|
||||
if (!fs::exists(sourceMetaPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fs::path destMetaPath = GetMetaSidecarPath(destPath);
|
||||
RenamePathCaseAware(sourceMetaPath, destMetaPath);
|
||||
}
|
||||
|
||||
void RemoveMetaSidecarIfPresent(const fs::path& assetPath) {
|
||||
std::error_code ec;
|
||||
fs::remove_all(GetMetaSidecarPath(assetPath), ec);
|
||||
}
|
||||
|
||||
bool RenamePathCaseAware(const fs::path& sourcePath, const fs::path& destPath) {
|
||||
if (MakePathKey(sourcePath) != MakePathKey(destPath)) {
|
||||
if (fs::exists(destPath)) {
|
||||
@@ -383,6 +404,7 @@ bool ProjectManager::DeleteItem(const std::string& fullPath) {
|
||||
}
|
||||
|
||||
fs::remove_all(itemPath);
|
||||
RemoveMetaSidecarIfPresent(itemPath);
|
||||
if (m_selectedItemPath == fullPath) {
|
||||
ClearSelection();
|
||||
}
|
||||
@@ -422,6 +444,7 @@ bool ProjectManager::MoveItem(const std::string& sourceFullPath, const std::stri
|
||||
}
|
||||
|
||||
fs::rename(sourcePath, destPath);
|
||||
MoveMetaSidecarIfPresent(sourcePath, destPath);
|
||||
RefreshCurrentFolder();
|
||||
return true;
|
||||
} catch (...) {
|
||||
@@ -458,6 +481,7 @@ bool ProjectManager::RenameItem(const std::string& sourceFullPath, const std::st
|
||||
if (!RenamePathCaseAware(sourcePath, destPath)) {
|
||||
return false;
|
||||
}
|
||||
MoveMetaSidecarIfPresent(sourcePath, destPath);
|
||||
|
||||
if (!m_selectedItemPath.empty() &&
|
||||
MakePathKey(Utf8PathToWstring(m_selectedItemPath)) == MakePathKey(sourcePath))
|
||||
@@ -501,6 +525,13 @@ AssetItemPtr ProjectManager::ScanDirectory(const std::wstring& path) {
|
||||
try {
|
||||
for (const auto& entry : fs::directory_iterator(path)) {
|
||||
std::wstring nameW = entry.path().filename().wstring();
|
||||
if (!entry.is_directory()) {
|
||||
std::wstring lowerName = nameW;
|
||||
std::transform(lowerName.begin(), lowerName.end(), lowerName.begin(), ::towlower);
|
||||
if (lowerName.size() >= 5 && lowerName.substr(lowerName.size() - 5) == L".meta") {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
bool isFolder = entry.is_directory();
|
||||
items.push_back(CreateAssetItem(entry.path().wstring(), nameW, isFolder));
|
||||
}
|
||||
|
||||
@@ -242,6 +242,10 @@ add_library(XCEngine STATIC
|
||||
|
||||
# Core/Asset (Resource System Core)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/IResource.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/AssetGUID.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/AssetRef.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/ArtifactFormats.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/AssetDatabase.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/ResourceTypes.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/ImportSettings.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/ResourceHandle.h
|
||||
@@ -249,6 +253,8 @@ add_library(XCEngine STATIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/ResourceCache.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/AsyncLoader.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/include/XCEngine/Core/Asset/ResourceDependencyGraph.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Core/Asset/AssetGUID.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Core/Asset/AssetDatabase.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Core/Asset/ResourceManager.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Core/Asset/ResourceCache.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Core/Asset/AsyncLoader.cpp
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Components/Component.h>
|
||||
#include <XCEngine/Core/Asset/AssetRef.h>
|
||||
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
||||
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||
|
||||
@@ -16,6 +17,7 @@ public:
|
||||
Resources::Mesh* GetMesh() const { return m_mesh.Get(); }
|
||||
const Resources::ResourceHandle<Resources::Mesh>& GetMeshHandle() const { return m_mesh; }
|
||||
const std::string& GetMeshPath() const { return m_meshPath; }
|
||||
const Resources::AssetRef& GetMeshAssetRef() const { return m_meshRef; }
|
||||
|
||||
void SetMeshPath(const std::string& meshPath);
|
||||
void SetMesh(const Resources::ResourceHandle<Resources::Mesh>& mesh);
|
||||
@@ -28,6 +30,7 @@ public:
|
||||
private:
|
||||
Resources::ResourceHandle<Resources::Mesh> m_mesh;
|
||||
std::string m_meshPath;
|
||||
Resources::AssetRef m_meshRef;
|
||||
};
|
||||
|
||||
} // namespace Components
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Components/Component.h>
|
||||
#include <XCEngine/Core/Asset/AssetRef.h>
|
||||
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
|
||||
@@ -20,6 +21,7 @@ public:
|
||||
const Resources::ResourceHandle<Resources::Material>& GetMaterialHandle(size_t index) const;
|
||||
const std::string& GetMaterialPath(size_t index) const;
|
||||
const std::vector<std::string>& GetMaterialPaths() const { return m_materialPaths; }
|
||||
const std::vector<Resources::AssetRef>& GetMaterialAssetRefs() const { return m_materialRefs; }
|
||||
|
||||
void SetMaterialPath(size_t index, const std::string& materialPath);
|
||||
void SetMaterial(size_t index, const Resources::ResourceHandle<Resources::Material>& material);
|
||||
@@ -45,6 +47,7 @@ private:
|
||||
|
||||
std::vector<Resources::ResourceHandle<Resources::Material>> m_materials;
|
||||
std::vector<std::string> m_materialPaths;
|
||||
std::vector<Resources::AssetRef> m_materialRefs;
|
||||
bool m_castShadows = true;
|
||||
bool m_receiveShadows = true;
|
||||
uint32_t m_renderLayer = 0;
|
||||
|
||||
59
engine/include/XCEngine/Core/Asset/ArtifactFormats.h
Normal file
59
engine/include/XCEngine/Core/Asset/ArtifactFormats.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/Math/Bounds.h>
|
||||
#include <XCEngine/Core/Types.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||
#include <XCEngine/Resources/Texture/Texture.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
constexpr Core::uint32 kTextureArtifactSchemaVersion = 1;
|
||||
constexpr Core::uint32 kMeshArtifactSchemaVersion = 1;
|
||||
|
||||
struct TextureArtifactHeader {
|
||||
char magic[8] = { 'X', 'C', 'T', 'E', 'X', '0', '1', '\0' };
|
||||
Core::uint32 schemaVersion = kTextureArtifactSchemaVersion;
|
||||
Core::uint32 textureType = 0;
|
||||
Core::uint32 textureFormat = 0;
|
||||
Core::uint32 width = 0;
|
||||
Core::uint32 height = 0;
|
||||
Core::uint32 depth = 0;
|
||||
Core::uint32 mipLevels = 0;
|
||||
Core::uint32 arraySize = 0;
|
||||
Core::uint64 pixelDataSize = 0;
|
||||
};
|
||||
|
||||
struct MeshArtifactHeader {
|
||||
char magic[8] = { 'X', 'C', 'M', 'E', 'S', 'H', '1', '\0' };
|
||||
Core::uint32 schemaVersion = kMeshArtifactSchemaVersion;
|
||||
Core::uint32 vertexCount = 0;
|
||||
Core::uint32 vertexStride = 0;
|
||||
Core::uint32 vertexAttributes = 0;
|
||||
Core::uint32 indexCount = 0;
|
||||
Core::uint32 use32BitIndex = 0;
|
||||
Core::uint32 sectionCount = 0;
|
||||
Core::uint32 materialCount = 0;
|
||||
Core::uint32 textureCount = 0;
|
||||
Math::Vector3 boundsMin = Math::Vector3::Zero();
|
||||
Math::Vector3 boundsMax = Math::Vector3::Zero();
|
||||
Core::uint64 vertexDataSize = 0;
|
||||
Core::uint64 indexDataSize = 0;
|
||||
};
|
||||
|
||||
struct MaterialArtifactHeader {
|
||||
Core::int32 renderQueue = static_cast<Core::int32>(MaterialRenderQueue::Geometry);
|
||||
MaterialRenderState renderState = {};
|
||||
Core::uint32 tagCount = 0;
|
||||
Core::uint32 propertyCount = 0;
|
||||
Core::uint32 textureBindingCount = 0;
|
||||
};
|
||||
|
||||
struct MaterialPropertyArtifact {
|
||||
Core::uint32 propertyType = 0;
|
||||
MaterialProperty::Value value = {};
|
||||
};
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
130
engine/include/XCEngine/Core/Asset/AssetDatabase.h
Normal file
130
engine/include/XCEngine/Core/Asset/AssetDatabase.h
Normal file
@@ -0,0 +1,130 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/Asset/AssetRef.h>
|
||||
#include <XCEngine/Core/Containers/String.h>
|
||||
#include <XCEngine/Core/Types.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
class AssetDatabase {
|
||||
public:
|
||||
struct SourceAssetRecord {
|
||||
AssetGUID guid;
|
||||
Containers::String relativePath;
|
||||
Containers::String metaPath;
|
||||
bool isFolder = false;
|
||||
Containers::String importerName;
|
||||
Core::uint32 importerVersion = 0;
|
||||
Containers::String metaHash;
|
||||
Containers::String sourceHash;
|
||||
Core::uint64 sourceFileSize = 0;
|
||||
Core::uint64 sourceWriteTime = 0;
|
||||
Containers::String lastKnownArtifactKey;
|
||||
};
|
||||
|
||||
struct ArtifactRecord {
|
||||
Containers::String artifactKey;
|
||||
AssetGUID assetGuid;
|
||||
Containers::String importerName;
|
||||
Core::uint32 importerVersion = 0;
|
||||
ResourceType resourceType = ResourceType::Unknown;
|
||||
Containers::String artifactDirectory;
|
||||
Containers::String mainArtifactPath;
|
||||
Containers::String sourceHash;
|
||||
Containers::String metaHash;
|
||||
Core::uint64 sourceFileSize = 0;
|
||||
Core::uint64 sourceWriteTime = 0;
|
||||
LocalID mainLocalID = kMainAssetLocalID;
|
||||
};
|
||||
|
||||
struct ResolvedAsset {
|
||||
bool exists = false;
|
||||
bool artifactReady = false;
|
||||
Containers::String absolutePath;
|
||||
Containers::String relativePath;
|
||||
AssetGUID assetGuid;
|
||||
ResourceType resourceType = ResourceType::Unknown;
|
||||
Containers::String artifactMainPath;
|
||||
Containers::String artifactDirectory;
|
||||
LocalID mainLocalID = kMainAssetLocalID;
|
||||
};
|
||||
|
||||
void Initialize(const Containers::String& projectRoot);
|
||||
void Shutdown();
|
||||
void Refresh();
|
||||
|
||||
bool ResolvePath(const Containers::String& requestPath,
|
||||
Containers::String& outAbsolutePath,
|
||||
Containers::String& outRelativePath) const;
|
||||
bool TryGetAssetGuid(const Containers::String& requestPath, AssetGUID& outGuid) const;
|
||||
bool TryGetAssetRef(const Containers::String& requestPath, ResourceType resourceType, AssetRef& outRef) const;
|
||||
bool EnsureArtifact(const Containers::String& requestPath,
|
||||
ResourceType requestedType,
|
||||
ResolvedAsset& outAsset);
|
||||
bool TryGetPrimaryAssetPath(const AssetGUID& guid, Containers::String& outRelativePath) const;
|
||||
|
||||
const Containers::String& GetProjectRoot() const { return m_projectRoot; }
|
||||
const Containers::String& GetAssetsRoot() const { return m_assetsRoot; }
|
||||
const Containers::String& GetLibraryRoot() const { return m_libraryRoot; }
|
||||
|
||||
private:
|
||||
static constexpr Core::uint32 kCurrentImporterVersion = 1;
|
||||
|
||||
void EnsureProjectLayout();
|
||||
void LoadSourceAssetDB();
|
||||
void SaveSourceAssetDB() const;
|
||||
void LoadArtifactDB();
|
||||
void SaveArtifactDB() const;
|
||||
void ScanAssets();
|
||||
void ScanAssetPath(const std::filesystem::path& path,
|
||||
std::unordered_map<std::string, bool>& seenPaths);
|
||||
void RemoveMissingRecords(const std::unordered_map<std::string, bool>& seenPaths);
|
||||
|
||||
bool EnsureMetaForPath(const std::filesystem::path& sourcePath,
|
||||
bool isFolder,
|
||||
SourceAssetRecord& outRecord);
|
||||
bool ReadMetaFile(const std::filesystem::path& metaPath,
|
||||
SourceAssetRecord& inOutRecord) const;
|
||||
void WriteMetaFile(const std::filesystem::path& metaPath,
|
||||
const SourceAssetRecord& record) const;
|
||||
|
||||
Containers::String NormalizeRelativePath(const std::filesystem::path& sourcePath) const;
|
||||
static Containers::String NormalizePathString(const std::filesystem::path& path);
|
||||
static Containers::String NormalizePathString(const Containers::String& path);
|
||||
static Containers::String MakeKey(const Containers::String& path);
|
||||
static Containers::String GetImporterNameForPath(const Containers::String& relativePath, bool isFolder);
|
||||
static ResourceType GetPrimaryResourceTypeForImporter(const Containers::String& importerName);
|
||||
|
||||
bool ShouldReimport(const SourceAssetRecord& sourceRecord,
|
||||
const ArtifactRecord* artifactRecord) const;
|
||||
bool ImportAsset(const SourceAssetRecord& sourceRecord,
|
||||
ArtifactRecord& outRecord);
|
||||
bool ImportTextureAsset(const SourceAssetRecord& sourceRecord,
|
||||
ArtifactRecord& outRecord);
|
||||
bool ImportModelAsset(const SourceAssetRecord& sourceRecord,
|
||||
ArtifactRecord& outRecord);
|
||||
|
||||
Containers::String BuildArtifactKey(const SourceAssetRecord& sourceRecord) const;
|
||||
Containers::String BuildArtifactDirectory(const Containers::String& artifactKey) const;
|
||||
static Containers::String ReadWholeFileText(const std::filesystem::path& path);
|
||||
static Containers::String ComputeFileHash(const std::filesystem::path& path);
|
||||
static Core::uint64 GetFileSizeValue(const std::filesystem::path& path);
|
||||
static Core::uint64 GetFileWriteTimeValue(const std::filesystem::path& path);
|
||||
|
||||
Containers::String m_projectRoot;
|
||||
Containers::String m_assetsRoot;
|
||||
Containers::String m_libraryRoot;
|
||||
Containers::String m_sourceDbPath;
|
||||
Containers::String m_artifactDbPath;
|
||||
|
||||
std::unordered_map<std::string, SourceAssetRecord> m_sourcesByPathKey;
|
||||
std::unordered_map<AssetGUID, SourceAssetRecord> m_sourcesByGuid;
|
||||
std::unordered_map<AssetGUID, ArtifactRecord> m_artifactsByGuid;
|
||||
};
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
54
engine/include/XCEngine/Core/Asset/AssetGUID.h
Normal file
54
engine/include/XCEngine/Core/Asset/AssetGUID.h
Normal file
@@ -0,0 +1,54 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/Containers/String.h>
|
||||
#include <XCEngine/Core/Types.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
struct AssetGUID {
|
||||
Core::uint64 high = 0;
|
||||
Core::uint64 low = 0;
|
||||
|
||||
AssetGUID() = default;
|
||||
AssetGUID(Core::uint64 inHigh, Core::uint64 inLow)
|
||||
: high(inHigh), low(inLow) {}
|
||||
|
||||
bool IsValid() const {
|
||||
return high != 0 || low != 0;
|
||||
}
|
||||
|
||||
bool operator==(const AssetGUID& other) const {
|
||||
return high == other.high && low == other.low;
|
||||
}
|
||||
|
||||
bool operator!=(const AssetGUID& other) const {
|
||||
return !(*this == other);
|
||||
}
|
||||
|
||||
static AssetGUID Generate();
|
||||
static bool TryParse(const Containers::String& text, AssetGUID& outGuid);
|
||||
static AssetGUID ParseOrDefault(const Containers::String& text);
|
||||
|
||||
Containers::String ToString() const;
|
||||
};
|
||||
|
||||
using LocalID = Core::uint64;
|
||||
|
||||
constexpr LocalID kInvalidLocalID = 0;
|
||||
constexpr LocalID kMainAssetLocalID = 1;
|
||||
|
||||
AssetGUID HashBytesToAssetGUID(const void* data, size_t size);
|
||||
AssetGUID HashStringToAssetGUID(const Containers::String& text);
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
|
||||
namespace std {
|
||||
template<>
|
||||
struct hash<XCEngine::Resources::AssetGUID> {
|
||||
size_t operator()(const XCEngine::Resources::AssetGUID& guid) const noexcept {
|
||||
return static_cast<size_t>(guid.high ^ (guid.low * 0x9e3779b97f4a7c15ULL));
|
||||
}
|
||||
};
|
||||
}
|
||||
46
engine/include/XCEngine/Core/Asset/AssetRef.h
Normal file
46
engine/include/XCEngine/Core/Asset/AssetRef.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/Asset/AssetGUID.h>
|
||||
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
struct AssetRef {
|
||||
AssetGUID assetGuid;
|
||||
LocalID localID = kInvalidLocalID;
|
||||
ResourceType resourceType = ResourceType::Unknown;
|
||||
|
||||
bool IsValid() const {
|
||||
return assetGuid.IsValid() && localID != kInvalidLocalID && resourceType != ResourceType::Unknown;
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
assetGuid = AssetGUID();
|
||||
localID = kInvalidLocalID;
|
||||
resourceType = ResourceType::Unknown;
|
||||
}
|
||||
|
||||
Containers::String ToString() const {
|
||||
if (!IsValid()) {
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
return assetGuid.ToString() + ":" + Containers::String(std::to_string(localID).c_str()) +
|
||||
":" + Containers::String(GetResourceTypeName(resourceType));
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
AssetRef MakeMainAssetRef(const AssetGUID& assetGuid) {
|
||||
AssetRef ref;
|
||||
ref.assetGuid = assetGuid;
|
||||
ref.localID = kMainAssetLocalID;
|
||||
ref.resourceType = GetResourceType<T>();
|
||||
return ref;
|
||||
}
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/IO/IResourceLoader.h>
|
||||
#include "AssetDatabase.h"
|
||||
#include "ResourceCache.h"
|
||||
#include "AsyncLoader.h"
|
||||
#include "ResourceHandle.h"
|
||||
@@ -29,33 +30,26 @@ public:
|
||||
ResourceHandle<T> Load(const Containers::String& path, ImportSettings* settings = nullptr) {
|
||||
static_assert(std::is_base_of_v<IResource, T>, "T must derive from IResource");
|
||||
|
||||
ResourceGUID guid = ResourceGUID::Generate(path);
|
||||
|
||||
IResource* cached = FindInCache(guid);
|
||||
if (cached) {
|
||||
return ResourceHandle<T>(static_cast<T*>(cached));
|
||||
}
|
||||
|
||||
IResourceLoader* loader = FindLoader(GetResourceType<T>());
|
||||
if (!loader) {
|
||||
Debug::Logger::Get().Warning(Debug::LogCategory::FileSystem,
|
||||
Containers::String("No loader found for resource type: ") +
|
||||
GetResourceTypeName(GetResourceType<T>()));
|
||||
LoadResult result = LoadResource(path, GetResourceType<T>(), settings);
|
||||
if (!result || result.resource == nullptr) {
|
||||
return ResourceHandle<T>();
|
||||
}
|
||||
|
||||
LoadResult result = loader->Load(path, settings);
|
||||
if (!result) {
|
||||
Debug::Logger::Get().Error(Debug::LogCategory::FileSystem,
|
||||
Containers::String("Failed to load resource: ") + path + " - " + result.errorMessage);
|
||||
return ResourceHandle<T>();
|
||||
}
|
||||
|
||||
AddToCache(guid, result.resource);
|
||||
|
||||
return ResourceHandle<T>(static_cast<T*>(result.resource));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
ResourceHandle<T> Load(const AssetRef& assetRef, ImportSettings* settings = nullptr) {
|
||||
static_assert(std::is_base_of_v<IResource, T>, "T must derive from IResource");
|
||||
|
||||
Containers::String path;
|
||||
if (!assetRef.IsValid() || !m_assetDatabase.TryGetPrimaryAssetPath(assetRef.assetGuid, path)) {
|
||||
return ResourceHandle<T>();
|
||||
}
|
||||
|
||||
return Load<T>(path, settings);
|
||||
}
|
||||
|
||||
void LoadAsync(const Containers::String& path, ResourceType type,
|
||||
std::function<void(LoadResult)> callback);
|
||||
void LoadAsync(const Containers::String& path, ResourceType type, ImportSettings* settings,
|
||||
@@ -102,6 +96,8 @@ public:
|
||||
|
||||
Containers::Array<Containers::String> GetResourcePaths() const;
|
||||
void UnloadGroup(const Containers::Array<ResourceGUID>& guids);
|
||||
void RefreshAssetDatabase();
|
||||
bool TryGetAssetRef(const Containers::String& path, ResourceType resourceType, AssetRef& outRef) const;
|
||||
|
||||
private:
|
||||
ResourceManager() = default;
|
||||
@@ -111,6 +107,7 @@ private:
|
||||
void AddToCache(ResourceGUID guid, IResource* resource);
|
||||
IResourceLoader* FindLoader(ResourceType type);
|
||||
void ReloadResource(ResourceGUID guid);
|
||||
LoadResult LoadResource(const Containers::String& path, ResourceType type, ImportSettings* settings);
|
||||
|
||||
Containers::String m_resourceRoot;
|
||||
Containers::HashMap<ResourceGUID, IResource*> m_resourceCache;
|
||||
@@ -121,6 +118,7 @@ private:
|
||||
size_t m_memoryUsage = 0;
|
||||
size_t m_memoryBudget = 512 * 1024 * 1024;
|
||||
|
||||
AssetDatabase m_assetDatabase;
|
||||
ResourceCache m_cache;
|
||||
Core::UniqueRef<AsyncLoader> m_asyncLoader;
|
||||
Threading::Mutex m_mutex;
|
||||
|
||||
@@ -38,6 +38,7 @@ public:
|
||||
bool Insert(Pair&& pair);
|
||||
bool Erase(const Key& key);
|
||||
void Clear();
|
||||
Array<Pair> GetPairs() const;
|
||||
|
||||
size_t Size() const { return m_size; }
|
||||
bool Empty() const { return m_size == 0; }
|
||||
@@ -274,6 +275,19 @@ void HashMap<Key, Value>::Clear() {
|
||||
m_size = 0;
|
||||
}
|
||||
|
||||
template<typename Key, typename Value>
|
||||
Array<typename HashMap<Key, Value>::Pair> HashMap<Key, Value>::GetPairs() const {
|
||||
Array<Pair> pairs;
|
||||
pairs.Reserve(m_size);
|
||||
for (size_t bucketIndex = 0; bucketIndex < m_buckets.Size(); ++bucketIndex) {
|
||||
const Bucket& bucket = m_buckets[bucketIndex];
|
||||
for (size_t pairIndex = 0; pairIndex < bucket.pairs.Size(); ++pairIndex) {
|
||||
pairs.PushBack(bucket.pairs[pairIndex]);
|
||||
}
|
||||
}
|
||||
return pairs;
|
||||
}
|
||||
|
||||
template<typename Key, typename Value>
|
||||
size_t HashMap<Key, Value>::GetBucketIndex(const Key& key) const {
|
||||
if (m_bucketCount == 0) {
|
||||
|
||||
34
engine/include/XCEngine/Resources/BuiltinResources.h
Normal file
34
engine/include/XCEngine/Resources/BuiltinResources.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <XCEngine/Core/IO/IResourceLoader.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
enum class BuiltinPrimitiveType {
|
||||
Cube,
|
||||
Sphere,
|
||||
Capsule,
|
||||
Cylinder,
|
||||
Plane,
|
||||
Quad
|
||||
};
|
||||
|
||||
bool IsBuiltinResourcePath(const Containers::String& path);
|
||||
bool IsBuiltinMeshPath(const Containers::String& path);
|
||||
bool IsBuiltinMaterialPath(const Containers::String& path);
|
||||
bool IsBuiltinTexturePath(const Containers::String& path);
|
||||
|
||||
const char* GetBuiltinPrimitiveDisplayName(BuiltinPrimitiveType primitiveType);
|
||||
Containers::String GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType primitiveType);
|
||||
Containers::String GetBuiltinDefaultPrimitiveMaterialPath();
|
||||
Containers::String GetBuiltinDefaultPrimitiveTexturePath();
|
||||
|
||||
bool TryParseBuiltinPrimitiveType(const Containers::String& path, BuiltinPrimitiveType& outPrimitiveType);
|
||||
|
||||
LoadResult CreateBuiltinMeshResource(const Containers::String& path);
|
||||
LoadResult CreateBuiltinMaterialResource(const Containers::String& path);
|
||||
LoadResult CreateBuiltinTextureResource(const Containers::String& path);
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <XCEngine/Core/Math/Vector3.h>
|
||||
#include <XCEngine/Core/Math/Vector4.h>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
@@ -126,6 +127,17 @@ struct MaterialProperty {
|
||||
MaterialProperty() : type(MaterialPropertyType::Float), refCount(0) {}
|
||||
};
|
||||
|
||||
struct MaterialTagEntry {
|
||||
Containers::String name;
|
||||
Containers::String value;
|
||||
};
|
||||
|
||||
struct MaterialTextureBinding {
|
||||
Containers::String name;
|
||||
Core::uint32 slot = 0;
|
||||
ResourceHandle<Texture> texture;
|
||||
};
|
||||
|
||||
class Material : public IResource {
|
||||
public:
|
||||
Material();
|
||||
@@ -158,6 +170,9 @@ public:
|
||||
void RemoveTag(const Containers::String& name);
|
||||
void ClearTags();
|
||||
Core::uint32 GetTagCount() const { return static_cast<Core::uint32>(m_tags.Size()); }
|
||||
Containers::String GetTagName(Core::uint32 index) const;
|
||||
Containers::String GetTagValue(Core::uint32 index) const;
|
||||
const Containers::Array<MaterialTagEntry>& GetTags() const { return m_tags; }
|
||||
|
||||
void SetFloat(const Containers::String& name, float value);
|
||||
void SetFloat2(const Containers::String& name, const Math::Vector2& value);
|
||||
@@ -175,6 +190,10 @@ public:
|
||||
bool GetBool(const Containers::String& name) const;
|
||||
ResourceHandle<Texture> GetTexture(const Containers::String& name) const;
|
||||
Core::uint32 GetTextureBindingCount() const { return static_cast<Core::uint32>(m_textureBindings.Size()); }
|
||||
Containers::String GetTextureBindingName(Core::uint32 index) const;
|
||||
ResourceHandle<Texture> GetTextureBindingTexture(Core::uint32 index) const;
|
||||
const Containers::Array<MaterialTextureBinding>& GetTextureBindings() const { return m_textureBindings; }
|
||||
std::vector<MaterialProperty> GetProperties() const;
|
||||
|
||||
const Containers::Array<Core::uint8>& GetConstantBufferData() const { return m_constantBufferData; }
|
||||
void UpdateConstantBuffer();
|
||||
@@ -191,20 +210,10 @@ private:
|
||||
Core::int32 m_renderQueue = static_cast<Core::int32>(MaterialRenderQueue::Geometry);
|
||||
MaterialRenderState m_renderState;
|
||||
Containers::String m_shaderPass;
|
||||
struct TagEntry {
|
||||
Containers::String name;
|
||||
Containers::String value;
|
||||
};
|
||||
Containers::Array<TagEntry> m_tags;
|
||||
Containers::Array<MaterialTagEntry> m_tags;
|
||||
Containers::HashMap<Containers::String, MaterialProperty> m_properties;
|
||||
Containers::Array<Core::uint8> m_constantBufferData;
|
||||
|
||||
struct TextureBinding {
|
||||
Containers::String name;
|
||||
Core::uint32 slot;
|
||||
ResourceHandle<Texture> texture;
|
||||
};
|
||||
Containers::Array<TextureBinding> m_textureBindings;
|
||||
Containers::Array<MaterialTextureBinding> m_textureBindings;
|
||||
};
|
||||
|
||||
} // namespace Resources
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include <XCEngine/Resources/Texture/Texture.h>
|
||||
#include <XCEngine/Resources/Texture/TextureLoader.h>
|
||||
#include <XCEngine/Resources/Texture/TextureImportSettings.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||
#include <XCEngine/Resources/Mesh/MeshLoader.h>
|
||||
#include <XCEngine/Resources/Mesh/MeshImportSettings.h>
|
||||
|
||||
@@ -13,21 +13,54 @@ std::string ToStdString(const Containers::String& value) {
|
||||
return std::string(value.CStr());
|
||||
}
|
||||
|
||||
std::string EncodeAssetRef(const Resources::AssetRef& assetRef) {
|
||||
if (!assetRef.IsValid()) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
return ToStdString(assetRef.assetGuid.ToString()) + "," +
|
||||
std::to_string(assetRef.localID) + "," +
|
||||
std::to_string(static_cast<int>(assetRef.resourceType));
|
||||
}
|
||||
|
||||
bool TryDecodeAssetRef(const std::string& value, Resources::AssetRef& outRef) {
|
||||
const size_t firstComma = value.find(',');
|
||||
const size_t secondComma = firstComma == std::string::npos ? std::string::npos : value.find(',', firstComma + 1);
|
||||
if (firstComma == std::string::npos || secondComma == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Containers::String guidText(value.substr(0, firstComma).c_str());
|
||||
outRef.assetGuid = Resources::AssetGUID::ParseOrDefault(guidText);
|
||||
outRef.localID = static_cast<Resources::LocalID>(std::stoull(value.substr(firstComma + 1, secondComma - firstComma - 1)));
|
||||
outRef.resourceType = static_cast<Resources::ResourceType>(std::stoi(value.substr(secondComma + 1)));
|
||||
return outRef.IsValid();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void MeshFilterComponent::SetMeshPath(const std::string& meshPath) {
|
||||
m_meshPath = meshPath;
|
||||
if (m_meshPath.empty()) {
|
||||
m_mesh.Reset();
|
||||
m_meshRef.Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
m_mesh = Resources::ResourceManager::Get().Load<Resources::Mesh>(m_meshPath.c_str());
|
||||
if (!Resources::ResourceManager::Get().TryGetAssetRef(m_meshPath.c_str(), Resources::ResourceType::Mesh, m_meshRef)) {
|
||||
m_meshRef.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void MeshFilterComponent::SetMesh(const Resources::ResourceHandle<Resources::Mesh>& mesh) {
|
||||
m_mesh = mesh;
|
||||
m_meshPath = mesh.Get() != nullptr ? ToStdString(mesh->GetPath()) : std::string();
|
||||
if (m_meshPath.empty()) {
|
||||
m_meshRef.Reset();
|
||||
} else if (!Resources::ResourceManager::Get().TryGetAssetRef(m_meshPath.c_str(), Resources::ResourceType::Mesh, m_meshRef)) {
|
||||
m_meshRef.Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void MeshFilterComponent::SetMesh(Resources::Mesh* mesh) {
|
||||
@@ -37,17 +70,22 @@ void MeshFilterComponent::SetMesh(Resources::Mesh* mesh) {
|
||||
void MeshFilterComponent::ClearMesh() {
|
||||
m_mesh.Reset();
|
||||
m_meshPath.clear();
|
||||
m_meshRef.Reset();
|
||||
}
|
||||
|
||||
void MeshFilterComponent::Serialize(std::ostream& os) const {
|
||||
os << "mesh=" << m_meshPath << ";";
|
||||
os << "meshRef=" << EncodeAssetRef(m_meshRef) << ";";
|
||||
}
|
||||
|
||||
void MeshFilterComponent::Deserialize(std::istream& is) {
|
||||
m_mesh.Reset();
|
||||
m_meshPath.clear();
|
||||
m_meshRef.Reset();
|
||||
|
||||
std::string token;
|
||||
std::string pendingMeshPath;
|
||||
Resources::AssetRef pendingMeshRef;
|
||||
while (std::getline(is, token, ';')) {
|
||||
if (token.empty()) {
|
||||
continue;
|
||||
@@ -62,9 +100,23 @@ void MeshFilterComponent::Deserialize(std::istream& is) {
|
||||
const std::string value = token.substr(eqPos + 1);
|
||||
|
||||
if (key == "mesh") {
|
||||
SetMeshPath(value);
|
||||
pendingMeshPath = value;
|
||||
} else if (key == "meshRef") {
|
||||
TryDecodeAssetRef(value, pendingMeshRef);
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingMeshRef.IsValid()) {
|
||||
m_meshRef = pendingMeshRef;
|
||||
m_mesh = Resources::ResourceManager::Get().Load<Resources::Mesh>(pendingMeshRef);
|
||||
if (m_mesh.Get() != nullptr) {
|
||||
m_meshPath = ToStdString(m_mesh->GetPath());
|
||||
} else {
|
||||
m_meshPath = pendingMeshPath;
|
||||
}
|
||||
} else if (!pendingMeshPath.empty()) {
|
||||
SetMeshPath(pendingMeshPath);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Components
|
||||
|
||||
@@ -13,6 +13,29 @@ std::string ToStdString(const Containers::String& value) {
|
||||
return std::string(value.CStr());
|
||||
}
|
||||
|
||||
std::string EncodeAssetRef(const Resources::AssetRef& assetRef) {
|
||||
if (!assetRef.IsValid()) {
|
||||
return std::string();
|
||||
}
|
||||
|
||||
return ToStdString(assetRef.assetGuid.ToString()) + "," +
|
||||
std::to_string(assetRef.localID) + "," +
|
||||
std::to_string(static_cast<int>(assetRef.resourceType));
|
||||
}
|
||||
|
||||
bool TryDecodeAssetRef(const std::string& value, Resources::AssetRef& outRef) {
|
||||
const size_t firstComma = value.find(',');
|
||||
const size_t secondComma = firstComma == std::string::npos ? std::string::npos : value.find(',', firstComma + 1);
|
||||
if (firstComma == std::string::npos || secondComma == std::string::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outRef.assetGuid = Resources::AssetGUID::ParseOrDefault(Containers::String(value.substr(0, firstComma).c_str()));
|
||||
outRef.localID = static_cast<Resources::LocalID>(std::stoull(value.substr(firstComma + 1, secondComma - firstComma - 1)));
|
||||
outRef.resourceType = static_cast<Resources::ResourceType>(std::stoi(value.substr(secondComma + 1)));
|
||||
return outRef.IsValid();
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitMaterialPaths(const std::string& value) {
|
||||
std::vector<std::string> paths;
|
||||
if (value.empty()) {
|
||||
@@ -34,6 +57,32 @@ std::vector<std::string> SplitMaterialPaths(const std::string& value) {
|
||||
return paths;
|
||||
}
|
||||
|
||||
std::vector<Resources::AssetRef> SplitMaterialRefs(const std::string& value) {
|
||||
std::vector<Resources::AssetRef> refs;
|
||||
if (value.empty()) {
|
||||
return refs;
|
||||
}
|
||||
|
||||
size_t start = 0;
|
||||
while (true) {
|
||||
const size_t separator = value.find('|', start);
|
||||
const std::string token = separator == std::string::npos
|
||||
? value.substr(start)
|
||||
: value.substr(start, separator - start);
|
||||
|
||||
Resources::AssetRef ref;
|
||||
TryDecodeAssetRef(token, ref);
|
||||
refs.push_back(ref);
|
||||
|
||||
if (separator == std::string::npos) {
|
||||
break;
|
||||
}
|
||||
start = separator + 1;
|
||||
}
|
||||
|
||||
return refs;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Resources::Material* MeshRendererComponent::GetMaterial(size_t index) const {
|
||||
@@ -55,16 +104,24 @@ void MeshRendererComponent::SetMaterialPath(size_t index, const std::string& mat
|
||||
m_materialPaths[index] = materialPath;
|
||||
if (materialPath.empty()) {
|
||||
m_materials[index].Reset();
|
||||
m_materialRefs[index].Reset();
|
||||
return;
|
||||
}
|
||||
|
||||
m_materials[index] = Resources::ResourceManager::Get().Load<Resources::Material>(materialPath.c_str());
|
||||
if (!Resources::ResourceManager::Get().TryGetAssetRef(materialPath.c_str(), Resources::ResourceType::Material, m_materialRefs[index])) {
|
||||
m_materialRefs[index].Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void MeshRendererComponent::SetMaterial(size_t index, const Resources::ResourceHandle<Resources::Material>& material) {
|
||||
EnsureMaterialSlot(index);
|
||||
m_materials[index] = material;
|
||||
m_materialPaths[index] = MaterialPathFromHandle(material);
|
||||
if (m_materialPaths[index].empty() ||
|
||||
!Resources::ResourceManager::Get().TryGetAssetRef(m_materialPaths[index].c_str(), Resources::ResourceType::Material, m_materialRefs[index])) {
|
||||
m_materialRefs[index].Reset();
|
||||
}
|
||||
}
|
||||
|
||||
void MeshRendererComponent::SetMaterial(size_t index, Resources::Material* material) {
|
||||
@@ -74,14 +131,20 @@ void MeshRendererComponent::SetMaterial(size_t index, Resources::Material* mater
|
||||
void MeshRendererComponent::SetMaterials(const std::vector<Resources::ResourceHandle<Resources::Material>>& materials) {
|
||||
m_materials = materials;
|
||||
m_materialPaths.resize(materials.size());
|
||||
m_materialRefs.resize(materials.size());
|
||||
for (size_t i = 0; i < materials.size(); ++i) {
|
||||
m_materialPaths[i] = MaterialPathFromHandle(materials[i]);
|
||||
if (m_materialPaths[i].empty() ||
|
||||
!Resources::ResourceManager::Get().TryGetAssetRef(m_materialPaths[i].c_str(), Resources::ResourceType::Material, m_materialRefs[i])) {
|
||||
m_materialRefs[i].Reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MeshRendererComponent::ClearMaterials() {
|
||||
m_materials.clear();
|
||||
m_materialPaths.clear();
|
||||
m_materialRefs.clear();
|
||||
}
|
||||
|
||||
void MeshRendererComponent::Serialize(std::ostream& os) const {
|
||||
@@ -93,6 +156,14 @@ void MeshRendererComponent::Serialize(std::ostream& os) const {
|
||||
os << m_materialPaths[i];
|
||||
}
|
||||
os << ";";
|
||||
os << "materialRefs=";
|
||||
for (size_t i = 0; i < m_materialRefs.size(); ++i) {
|
||||
if (i > 0) {
|
||||
os << "|";
|
||||
}
|
||||
os << EncodeAssetRef(m_materialRefs[i]);
|
||||
}
|
||||
os << ";";
|
||||
os << "castShadows=" << (m_castShadows ? 1 : 0) << ";";
|
||||
os << "receiveShadows=" << (m_receiveShadows ? 1 : 0) << ";";
|
||||
os << "renderLayer=" << m_renderLayer << ";";
|
||||
@@ -105,6 +176,7 @@ void MeshRendererComponent::Deserialize(std::istream& is) {
|
||||
m_renderLayer = 0;
|
||||
|
||||
std::string token;
|
||||
std::vector<Resources::AssetRef> pendingMaterialRefs;
|
||||
while (std::getline(is, token, ';')) {
|
||||
if (token.empty()) {
|
||||
continue;
|
||||
@@ -121,9 +193,9 @@ void MeshRendererComponent::Deserialize(std::istream& is) {
|
||||
if (key == "materials") {
|
||||
m_materialPaths = SplitMaterialPaths(value);
|
||||
m_materials.resize(m_materialPaths.size());
|
||||
for (size_t i = 0; i < m_materialPaths.size(); ++i) {
|
||||
SetMaterialPath(i, m_materialPaths[i]);
|
||||
}
|
||||
m_materialRefs.resize(m_materialPaths.size());
|
||||
} else if (key == "materialRefs") {
|
||||
pendingMaterialRefs = SplitMaterialRefs(value);
|
||||
} else if (key == "castShadows") {
|
||||
m_castShadows = (std::stoi(value) != 0);
|
||||
} else if (key == "receiveShadows") {
|
||||
@@ -132,12 +204,32 @@ void MeshRendererComponent::Deserialize(std::istream& is) {
|
||||
m_renderLayer = static_cast<uint32_t>(std::stoul(value));
|
||||
}
|
||||
}
|
||||
|
||||
if (pendingMaterialRefs.size() > m_materialPaths.size()) {
|
||||
m_materialPaths.resize(pendingMaterialRefs.size());
|
||||
m_materials.resize(pendingMaterialRefs.size());
|
||||
m_materialRefs.resize(pendingMaterialRefs.size());
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < m_materialPaths.size(); ++i) {
|
||||
if (i < pendingMaterialRefs.size() && pendingMaterialRefs[i].IsValid()) {
|
||||
m_materialRefs[i] = pendingMaterialRefs[i];
|
||||
m_materials[i] = Resources::ResourceManager::Get().Load<Resources::Material>(pendingMaterialRefs[i]);
|
||||
if (m_materials[i].Get() != nullptr) {
|
||||
m_materialPaths[i] = MaterialPathFromHandle(m_materials[i]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
SetMaterialPath(i, m_materialPaths[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void MeshRendererComponent::EnsureMaterialSlot(size_t index) {
|
||||
if (index >= m_materials.size()) {
|
||||
m_materials.resize(index + 1);
|
||||
m_materialPaths.resize(index + 1);
|
||||
m_materialRefs.resize(index + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
1089
engine/src/Core/Asset/AssetDatabase.cpp
Normal file
1089
engine/src/Core/Asset/AssetDatabase.cpp
Normal file
File diff suppressed because it is too large
Load Diff
114
engine/src/Core/Asset/AssetGUID.cpp
Normal file
114
engine/src/Core/Asset/AssetGUID.cpp
Normal file
@@ -0,0 +1,114 @@
|
||||
#include <XCEngine/Core/Asset/AssetGUID.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <random>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
namespace {
|
||||
|
||||
Core::uint64 FNV1a64(const Core::uint8* bytes, size_t size, Core::uint64 seed) {
|
||||
Core::uint64 hash = seed;
|
||||
for (size_t index = 0; index < size; ++index) {
|
||||
hash ^= static_cast<Core::uint64>(bytes[index]);
|
||||
hash *= 1099511628211ULL;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
int HexDigitToInt(char ch) {
|
||||
if (ch >= '0' && ch <= '9') {
|
||||
return ch - '0';
|
||||
}
|
||||
if (ch >= 'a' && ch <= 'f') {
|
||||
return 10 + (ch - 'a');
|
||||
}
|
||||
if (ch >= 'A' && ch <= 'F') {
|
||||
return 10 + (ch - 'A');
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool ParseHex64(const char* text, Core::uint64& outValue) {
|
||||
outValue = 0;
|
||||
for (int index = 0; index < 16; ++index) {
|
||||
const int digit = HexDigitToInt(text[index]);
|
||||
if (digit < 0) {
|
||||
return false;
|
||||
}
|
||||
outValue = (outValue << 4) | static_cast<Core::uint64>(digit);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
AssetGUID AssetGUID::Generate() {
|
||||
static std::random_device rd;
|
||||
static std::mt19937_64 generator(rd());
|
||||
|
||||
AssetGUID guid;
|
||||
do {
|
||||
guid.high = generator();
|
||||
guid.low = generator();
|
||||
} while (!guid.IsValid());
|
||||
|
||||
return guid;
|
||||
}
|
||||
|
||||
bool AssetGUID::TryParse(const Containers::String& text, AssetGUID& outGuid) {
|
||||
if (text.Length() != 32) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Core::uint64 highValue = 0;
|
||||
Core::uint64 lowValue = 0;
|
||||
if (!ParseHex64(text.CStr(), highValue) ||
|
||||
!ParseHex64(text.CStr() + 16, lowValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outGuid = AssetGUID(highValue, lowValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
AssetGUID AssetGUID::ParseOrDefault(const Containers::String& text) {
|
||||
AssetGUID guid;
|
||||
if (TryParse(text, guid)) {
|
||||
return guid;
|
||||
}
|
||||
return AssetGUID();
|
||||
}
|
||||
|
||||
Containers::String AssetGUID::ToString() const {
|
||||
char buffer[33] = {};
|
||||
#if defined(_MSC_VER)
|
||||
std::snprintf(buffer, sizeof(buffer), "%016llx%016llx",
|
||||
static_cast<unsigned long long>(high),
|
||||
static_cast<unsigned long long>(low));
|
||||
#else
|
||||
std::snprintf(buffer, sizeof(buffer), "%016lx%016lx",
|
||||
static_cast<unsigned long>(high),
|
||||
static_cast<unsigned long>(low));
|
||||
#endif
|
||||
return Containers::String(buffer);
|
||||
}
|
||||
|
||||
AssetGUID HashBytesToAssetGUID(const void* data, size_t size) {
|
||||
if (data == nullptr || size == 0) {
|
||||
return AssetGUID();
|
||||
}
|
||||
|
||||
const auto* bytes = static_cast<const Core::uint8*>(data);
|
||||
const Core::uint64 highHash = FNV1a64(bytes, size, 14695981039346656037ULL);
|
||||
const Core::uint64 lowHash = FNV1a64(bytes, size, 1099511628211ULL ^ 0x9e3779b97f4a7c15ULL);
|
||||
return AssetGUID(highHash, lowHash);
|
||||
}
|
||||
|
||||
AssetGUID HashStringToAssetGUID(const Containers::String& text) {
|
||||
return HashBytesToAssetGUID(text.CStr(), text.Length());
|
||||
}
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
||||
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||||
#include <XCEngine/Core/IO/ResourceFileSystem.h>
|
||||
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
||||
#include <XCEngine/Resources/Mesh/MeshLoader.h>
|
||||
#include <XCEngine/Resources/Shader/ShaderLoader.h>
|
||||
@@ -31,6 +32,10 @@ ResourceManager& ResourceManager::Get() {
|
||||
}
|
||||
|
||||
void ResourceManager::Initialize() {
|
||||
if (m_asyncLoader) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_asyncLoader = Core::MakeUnique<AsyncLoader>();
|
||||
m_asyncLoader->Initialize(2);
|
||||
|
||||
@@ -42,12 +47,23 @@ void ResourceManager::Initialize() {
|
||||
|
||||
void ResourceManager::Shutdown() {
|
||||
UnloadAll();
|
||||
m_asyncLoader->Shutdown();
|
||||
m_asyncLoader.reset();
|
||||
if (m_asyncLoader) {
|
||||
m_asyncLoader->Shutdown();
|
||||
m_asyncLoader.reset();
|
||||
}
|
||||
m_assetDatabase.Shutdown();
|
||||
ResourceFileSystem::Get().Shutdown();
|
||||
}
|
||||
|
||||
void ResourceManager::SetResourceRoot(const Containers::String& rootPath) {
|
||||
m_resourceRoot = rootPath;
|
||||
if (!m_resourceRoot.Empty()) {
|
||||
ResourceFileSystem::Get().Initialize(rootPath);
|
||||
m_assetDatabase.Initialize(rootPath);
|
||||
} else {
|
||||
ResourceFileSystem::Get().Shutdown();
|
||||
m_assetDatabase.Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
const Containers::String& ResourceManager::GetResourceRoot() const {
|
||||
@@ -112,6 +128,7 @@ IResource* ResourceManager::FindInCache(ResourceGUID guid) {
|
||||
void ResourceManager::AddToCache(ResourceGUID guid, IResource* resource) {
|
||||
std::lock_guard lock(m_mutex);
|
||||
|
||||
resource->m_guid = guid;
|
||||
m_resourceCache.Insert(guid, resource);
|
||||
m_memoryUsage += resource->GetMemorySize();
|
||||
m_cache.Add(guid, resource);
|
||||
@@ -128,6 +145,7 @@ void ResourceManager::Unload(ResourceGUID guid) {
|
||||
if (it != nullptr) {
|
||||
IResource* resource = *it;
|
||||
m_resourceCache.Erase(guid);
|
||||
m_guidToPath.Erase(guid);
|
||||
m_memoryUsage -= resource->GetMemorySize();
|
||||
resource->Release();
|
||||
}
|
||||
@@ -136,12 +154,15 @@ void ResourceManager::Unload(ResourceGUID guid) {
|
||||
void ResourceManager::UnloadAll() {
|
||||
std::lock_guard lock(m_mutex);
|
||||
|
||||
for (size_t i = 0; i < m_resourceCache.Size(); ++i) {
|
||||
// This is a simplified approach - we'd need a way to iterate
|
||||
// For now, just clear everything
|
||||
const auto cachedResources = m_resourceCache.GetPairs();
|
||||
for (const auto& pair : cachedResources) {
|
||||
if (pair.second != nullptr) {
|
||||
pair.second->Release();
|
||||
}
|
||||
}
|
||||
m_resourceCache.Clear();
|
||||
m_refCounts.Clear();
|
||||
m_guidToPath.Clear();
|
||||
m_memoryUsage = 0;
|
||||
}
|
||||
|
||||
@@ -209,6 +230,10 @@ void ResourceManager::ReloadResource(ResourceGUID guid) {
|
||||
if (pathIt == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const Containers::String path = *pathIt;
|
||||
auto* typeIt = m_resourceCache.Find(guid);
|
||||
(void)typeIt;
|
||||
}
|
||||
|
||||
Containers::Array<Containers::String> ResourceManager::GetResourcePaths() const {
|
||||
@@ -226,11 +251,60 @@ void ResourceManager::UnloadGroup(const Containers::Array<ResourceGUID>& guids)
|
||||
if (it != nullptr) {
|
||||
IResource* resource = *it;
|
||||
m_resourceCache.Erase(guid);
|
||||
m_guidToPath.Erase(guid);
|
||||
m_memoryUsage -= resource->GetMemorySize();
|
||||
resource->Release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ResourceManager::RefreshAssetDatabase() {
|
||||
if (!m_resourceRoot.Empty()) {
|
||||
m_assetDatabase.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
bool ResourceManager::TryGetAssetRef(const Containers::String& path, ResourceType resourceType, AssetRef& outRef) const {
|
||||
return m_assetDatabase.TryGetAssetRef(path, resourceType, outRef);
|
||||
}
|
||||
|
||||
LoadResult ResourceManager::LoadResource(const Containers::String& path,
|
||||
ResourceType type,
|
||||
ImportSettings* settings) {
|
||||
const ResourceGUID guid = ResourceGUID::Generate(path);
|
||||
|
||||
if (IResource* cached = FindInCache(guid)) {
|
||||
return LoadResult(cached);
|
||||
}
|
||||
|
||||
IResourceLoader* loader = FindLoader(type);
|
||||
if (loader == nullptr) {
|
||||
Debug::Logger::Get().Warning(Debug::LogCategory::FileSystem,
|
||||
Containers::String("No loader found for resource type: ") +
|
||||
GetResourceTypeName(type));
|
||||
return LoadResult(false, "Loader not found");
|
||||
}
|
||||
|
||||
Containers::String loadPath = path;
|
||||
AssetDatabase::ResolvedAsset resolvedAsset;
|
||||
if (!m_resourceRoot.Empty() &&
|
||||
m_assetDatabase.EnsureArtifact(path, type, resolvedAsset) &&
|
||||
resolvedAsset.artifactReady) {
|
||||
loadPath = resolvedAsset.artifactMainPath;
|
||||
}
|
||||
|
||||
LoadResult result = loader->Load(loadPath, settings);
|
||||
if (!result || result.resource == nullptr) {
|
||||
Debug::Logger::Get().Error(Debug::LogCategory::FileSystem,
|
||||
Containers::String("Failed to load resource: ") + path + " - " + result.errorMessage);
|
||||
return result;
|
||||
}
|
||||
|
||||
result.resource->m_path = path;
|
||||
AddToCache(guid, result.resource);
|
||||
m_guidToPath.Insert(guid, path);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
#include <XCEngine/Core/IO/IResourceLoader.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
Containers::Array<Core::uint8> IResourceLoader::ReadFileData(const Containers::String& path) {
|
||||
namespace {
|
||||
|
||||
Containers::Array<Core::uint8> TryReadFileData(
|
||||
const std::filesystem::path& filePath,
|
||||
bool& opened) {
|
||||
Containers::Array<Core::uint8> data;
|
||||
|
||||
std::ifstream file(path.CStr(), std::ios::binary | std::ios::ate);
|
||||
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
opened = false;
|
||||
return data;
|
||||
}
|
||||
|
||||
std::streamsize size = file.tellg();
|
||||
file.seekg(0, std::ios::beg);
|
||||
opened = true;
|
||||
const std::streamsize size = file.tellg();
|
||||
if (size <= 0) {
|
||||
return data;
|
||||
}
|
||||
|
||||
file.seekg(0, std::ios::beg);
|
||||
data.Resize(static_cast<size_t>(size));
|
||||
if (!file.read(reinterpret_cast<char*>(data.Data()), size)) {
|
||||
data.Clear();
|
||||
@@ -23,6 +34,24 @@ Containers::Array<Core::uint8> IResourceLoader::ReadFileData(const Containers::S
|
||||
return data;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Containers::Array<Core::uint8> IResourceLoader::ReadFileData(const Containers::String& path) {
|
||||
bool opened = false;
|
||||
const std::filesystem::path inputPath(path.CStr());
|
||||
Containers::Array<Core::uint8> data = TryReadFileData(inputPath, opened);
|
||||
if (opened || path.Empty() || inputPath.is_absolute()) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
if (resourceRoot.Empty()) {
|
||||
return data;
|
||||
}
|
||||
|
||||
return TryReadFileData(std::filesystem::path(resourceRoot.CStr()) / inputPath, opened);
|
||||
}
|
||||
|
||||
Containers::String IResourceLoader::GetExtension(const Containers::String& path) {
|
||||
Containers::String ext;
|
||||
size_t dotPos = Containers::String::npos;
|
||||
|
||||
677
engine/src/Resources/BuiltinResources.cpp
Normal file
677
engine/src/Resources/BuiltinResources.cpp
Normal file
@@ -0,0 +1,677 @@
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
|
||||
#include <XCEngine/Core/Math/Bounds.h>
|
||||
#include <XCEngine/Core/Math/Vector2.h>
|
||||
#include <XCEngine/Core/Math/Vector3.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||
#include <XCEngine/Resources/Texture/Texture.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char* kBuiltinPrefix = "builtin://";
|
||||
constexpr const char* kBuiltinMeshPrefix = "builtin://meshes/";
|
||||
constexpr const char* kBuiltinMaterialPrefix = "builtin://materials/";
|
||||
constexpr const char* kBuiltinTexturePrefix = "builtin://textures/";
|
||||
constexpr const char* kBuiltinDefaultPrimitiveMaterialPath = "builtin://materials/default-primitive";
|
||||
constexpr const char* kBuiltinDefaultPrimitiveTexturePath = "builtin://textures/default-primitive-albedo";
|
||||
constexpr float kPi = 3.14159265358979323846f;
|
||||
|
||||
struct MeshBuffers {
|
||||
std::vector<StaticMeshVertex> vertices;
|
||||
std::vector<Core::uint32> indices;
|
||||
};
|
||||
|
||||
Math::Bounds ComputeBounds(const std::vector<StaticMeshVertex>& vertices) {
|
||||
if (vertices.empty()) {
|
||||
return Math::Bounds();
|
||||
}
|
||||
|
||||
Math::Vector3 min = vertices.front().position;
|
||||
Math::Vector3 max = vertices.front().position;
|
||||
for (const StaticMeshVertex& vertex : vertices) {
|
||||
min.x = std::min(min.x, vertex.position.x);
|
||||
min.y = std::min(min.y, vertex.position.y);
|
||||
min.z = std::min(min.z, vertex.position.z);
|
||||
max.x = std::max(max.x, vertex.position.x);
|
||||
max.y = std::max(max.y, vertex.position.y);
|
||||
max.z = std::max(max.z, vertex.position.z);
|
||||
}
|
||||
|
||||
Math::Bounds bounds;
|
||||
bounds.SetMinMax(min, max);
|
||||
return bounds;
|
||||
}
|
||||
|
||||
StaticMeshVertex MakeVertex(
|
||||
const Math::Vector3& position,
|
||||
const Math::Vector3& normal,
|
||||
const Math::Vector3& tangent,
|
||||
const Math::Vector2& uv) {
|
||||
StaticMeshVertex vertex;
|
||||
vertex.position = position;
|
||||
vertex.normal = normal.Normalized();
|
||||
vertex.tangent = tangent.Normalized();
|
||||
vertex.bitangent = Math::Vector3::Cross(vertex.normal, vertex.tangent).Normalized();
|
||||
vertex.uv0 = uv;
|
||||
return vertex;
|
||||
}
|
||||
|
||||
void AppendQuad(
|
||||
MeshBuffers& buffers,
|
||||
const Math::Vector3& bottomLeft,
|
||||
const Math::Vector3& bottomRight,
|
||||
const Math::Vector3& topRight,
|
||||
const Math::Vector3& topLeft,
|
||||
const Math::Vector3& normal,
|
||||
const Math::Vector3& tangent) {
|
||||
const Core::uint32 baseIndex = static_cast<Core::uint32>(buffers.vertices.size());
|
||||
buffers.vertices.push_back(MakeVertex(bottomLeft, normal, tangent, Math::Vector2(0.0f, 0.0f)));
|
||||
buffers.vertices.push_back(MakeVertex(bottomRight, normal, tangent, Math::Vector2(1.0f, 0.0f)));
|
||||
buffers.vertices.push_back(MakeVertex(topRight, normal, tangent, Math::Vector2(1.0f, 1.0f)));
|
||||
buffers.vertices.push_back(MakeVertex(topLeft, normal, tangent, Math::Vector2(0.0f, 1.0f)));
|
||||
|
||||
buffers.indices.push_back(baseIndex + 0);
|
||||
buffers.indices.push_back(baseIndex + 1);
|
||||
buffers.indices.push_back(baseIndex + 2);
|
||||
buffers.indices.push_back(baseIndex + 0);
|
||||
buffers.indices.push_back(baseIndex + 2);
|
||||
buffers.indices.push_back(baseIndex + 3);
|
||||
}
|
||||
|
||||
void FlipTriangleWinding(MeshBuffers& buffers) {
|
||||
for (size_t index = 0; index + 2 < buffers.indices.size(); index += 3) {
|
||||
std::swap(buffers.indices[index + 1], buffers.indices[index + 2]);
|
||||
}
|
||||
}
|
||||
|
||||
MeshBuffers CreateCubeMeshBuffers() {
|
||||
MeshBuffers buffers;
|
||||
const float half = 0.5f;
|
||||
|
||||
AppendQuad(
|
||||
buffers,
|
||||
Math::Vector3(-half, -half, half),
|
||||
Math::Vector3(half, -half, half),
|
||||
Math::Vector3(half, half, half),
|
||||
Math::Vector3(-half, half, half),
|
||||
Math::Vector3::Forward(),
|
||||
Math::Vector3::Right());
|
||||
AppendQuad(
|
||||
buffers,
|
||||
Math::Vector3(half, -half, -half),
|
||||
Math::Vector3(-half, -half, -half),
|
||||
Math::Vector3(-half, half, -half),
|
||||
Math::Vector3(half, half, -half),
|
||||
Math::Vector3::Back(),
|
||||
Math::Vector3::Left());
|
||||
AppendQuad(
|
||||
buffers,
|
||||
Math::Vector3(-half, -half, -half),
|
||||
Math::Vector3(-half, -half, half),
|
||||
Math::Vector3(-half, half, half),
|
||||
Math::Vector3(-half, half, -half),
|
||||
Math::Vector3::Left(),
|
||||
Math::Vector3::Forward());
|
||||
AppendQuad(
|
||||
buffers,
|
||||
Math::Vector3(half, -half, half),
|
||||
Math::Vector3(half, -half, -half),
|
||||
Math::Vector3(half, half, -half),
|
||||
Math::Vector3(half, half, half),
|
||||
Math::Vector3::Right(),
|
||||
Math::Vector3::Back());
|
||||
AppendQuad(
|
||||
buffers,
|
||||
Math::Vector3(-half, half, half),
|
||||
Math::Vector3(half, half, half),
|
||||
Math::Vector3(half, half, -half),
|
||||
Math::Vector3(-half, half, -half),
|
||||
Math::Vector3::Up(),
|
||||
Math::Vector3::Right());
|
||||
AppendQuad(
|
||||
buffers,
|
||||
Math::Vector3(-half, -half, -half),
|
||||
Math::Vector3(half, -half, -half),
|
||||
Math::Vector3(half, -half, half),
|
||||
Math::Vector3(-half, -half, half),
|
||||
Math::Vector3::Down(),
|
||||
Math::Vector3::Right());
|
||||
|
||||
return buffers;
|
||||
}
|
||||
|
||||
MeshBuffers CreateQuadMeshBuffers() {
|
||||
MeshBuffers buffers;
|
||||
AppendQuad(
|
||||
buffers,
|
||||
Math::Vector3(-0.5f, -0.5f, 0.0f),
|
||||
Math::Vector3(0.5f, -0.5f, 0.0f),
|
||||
Math::Vector3(0.5f, 0.5f, 0.0f),
|
||||
Math::Vector3(-0.5f, 0.5f, 0.0f),
|
||||
Math::Vector3::Forward(),
|
||||
Math::Vector3::Right());
|
||||
return buffers;
|
||||
}
|
||||
|
||||
MeshBuffers CreatePlaneMeshBuffers() {
|
||||
MeshBuffers buffers;
|
||||
constexpr int kSegments = 10;
|
||||
constexpr float kSize = 10.0f;
|
||||
const float halfSize = kSize * 0.5f;
|
||||
const float step = kSize / static_cast<float>(kSegments);
|
||||
|
||||
for (int z = 0; z <= kSegments; ++z) {
|
||||
for (int x = 0; x <= kSegments; ++x) {
|
||||
const float px = -halfSize + static_cast<float>(x) * step;
|
||||
const float pz = -halfSize + static_cast<float>(z) * step;
|
||||
const float u = static_cast<float>(x) / static_cast<float>(kSegments);
|
||||
const float v = static_cast<float>(z) / static_cast<float>(kSegments);
|
||||
buffers.vertices.push_back(MakeVertex(
|
||||
Math::Vector3(px, 0.0f, pz),
|
||||
Math::Vector3::Up(),
|
||||
Math::Vector3::Right(),
|
||||
Math::Vector2(u, v)));
|
||||
}
|
||||
}
|
||||
|
||||
const int rowStride = kSegments + 1;
|
||||
for (int z = 0; z < kSegments; ++z) {
|
||||
for (int x = 0; x < kSegments; ++x) {
|
||||
const Core::uint32 i0 = static_cast<Core::uint32>(z * rowStride + x);
|
||||
const Core::uint32 i1 = i0 + 1;
|
||||
const Core::uint32 i2 = i0 + static_cast<Core::uint32>(rowStride);
|
||||
const Core::uint32 i3 = i2 + 1;
|
||||
|
||||
buffers.indices.push_back(i0);
|
||||
buffers.indices.push_back(i3);
|
||||
buffers.indices.push_back(i1);
|
||||
buffers.indices.push_back(i0);
|
||||
buffers.indices.push_back(i2);
|
||||
buffers.indices.push_back(i3);
|
||||
}
|
||||
}
|
||||
|
||||
return buffers;
|
||||
}
|
||||
|
||||
MeshBuffers CreateUvSphereMeshBuffers() {
|
||||
MeshBuffers buffers;
|
||||
constexpr int kLongitudeSegments = 24;
|
||||
constexpr int kLatitudeSegments = 16;
|
||||
constexpr float kRadius = 0.5f;
|
||||
|
||||
for (int latitude = 0; latitude <= kLatitudeSegments; ++latitude) {
|
||||
const float v = static_cast<float>(latitude) / static_cast<float>(kLatitudeSegments);
|
||||
const float theta = v * kPi;
|
||||
const float sinTheta = std::sin(theta);
|
||||
const float cosTheta = std::cos(theta);
|
||||
|
||||
for (int longitude = 0; longitude <= kLongitudeSegments; ++longitude) {
|
||||
const float u = static_cast<float>(longitude) / static_cast<float>(kLongitudeSegments);
|
||||
const float phi = u * (2.0f * kPi);
|
||||
const float sinPhi = std::sin(phi);
|
||||
const float cosPhi = std::cos(phi);
|
||||
|
||||
const Math::Vector3 normal(cosPhi * sinTheta, cosTheta, sinPhi * sinTheta);
|
||||
Math::Vector3 tangent(-sinPhi, 0.0f, cosPhi);
|
||||
if (tangent.SqrMagnitude() <= 0.000001f) {
|
||||
tangent = Math::Vector3::Right();
|
||||
}
|
||||
|
||||
buffers.vertices.push_back(MakeVertex(
|
||||
normal * kRadius,
|
||||
normal,
|
||||
tangent,
|
||||
Math::Vector2(u, 1.0f - v)));
|
||||
}
|
||||
}
|
||||
|
||||
const int stride = kLongitudeSegments + 1;
|
||||
for (int latitude = 0; latitude < kLatitudeSegments; ++latitude) {
|
||||
for (int longitude = 0; longitude < kLongitudeSegments; ++longitude) {
|
||||
const Core::uint32 i0 = static_cast<Core::uint32>(latitude * stride + longitude);
|
||||
const Core::uint32 i1 = i0 + 1;
|
||||
const Core::uint32 i2 = i0 + static_cast<Core::uint32>(stride);
|
||||
const Core::uint32 i3 = i2 + 1;
|
||||
|
||||
buffers.indices.push_back(i0);
|
||||
buffers.indices.push_back(i2);
|
||||
buffers.indices.push_back(i1);
|
||||
buffers.indices.push_back(i1);
|
||||
buffers.indices.push_back(i2);
|
||||
buffers.indices.push_back(i3);
|
||||
}
|
||||
}
|
||||
|
||||
return buffers;
|
||||
}
|
||||
|
||||
MeshBuffers CreateCylinderMeshBuffers() {
|
||||
MeshBuffers buffers;
|
||||
constexpr int kRadialSegments = 24;
|
||||
constexpr float kRadius = 0.5f;
|
||||
constexpr float kHalfHeight = 1.0f;
|
||||
|
||||
for (int ring = 0; ring <= 1; ++ring) {
|
||||
const float y = ring == 0 ? -kHalfHeight : kHalfHeight;
|
||||
const float v = static_cast<float>(ring);
|
||||
for (int segment = 0; segment <= kRadialSegments; ++segment) {
|
||||
const float u = static_cast<float>(segment) / static_cast<float>(kRadialSegments);
|
||||
const float angle = u * (2.0f * kPi);
|
||||
const float cosAngle = std::cos(angle);
|
||||
const float sinAngle = std::sin(angle);
|
||||
const Math::Vector3 normal(cosAngle, 0.0f, sinAngle);
|
||||
const Math::Vector3 tangent(-sinAngle, 0.0f, cosAngle);
|
||||
buffers.vertices.push_back(MakeVertex(
|
||||
Math::Vector3(cosAngle * kRadius, y, sinAngle * kRadius),
|
||||
normal,
|
||||
tangent,
|
||||
Math::Vector2(u, v)));
|
||||
}
|
||||
}
|
||||
|
||||
const int sideStride = kRadialSegments + 1;
|
||||
for (int segment = 0; segment < kRadialSegments; ++segment) {
|
||||
const Core::uint32 i0 = static_cast<Core::uint32>(segment);
|
||||
const Core::uint32 i1 = i0 + 1;
|
||||
const Core::uint32 i2 = i0 + static_cast<Core::uint32>(sideStride);
|
||||
const Core::uint32 i3 = i2 + 1;
|
||||
|
||||
buffers.indices.push_back(i0);
|
||||
buffers.indices.push_back(i2);
|
||||
buffers.indices.push_back(i1);
|
||||
buffers.indices.push_back(i1);
|
||||
buffers.indices.push_back(i2);
|
||||
buffers.indices.push_back(i3);
|
||||
}
|
||||
|
||||
const auto appendCap = [&](bool topCap) {
|
||||
const float y = topCap ? kHalfHeight : -kHalfHeight;
|
||||
const Math::Vector3 normal = topCap ? Math::Vector3::Up() : Math::Vector3::Down();
|
||||
const Core::uint32 centerIndex = static_cast<Core::uint32>(buffers.vertices.size());
|
||||
buffers.vertices.push_back(MakeVertex(
|
||||
Math::Vector3(0.0f, y, 0.0f),
|
||||
normal,
|
||||
Math::Vector3::Right(),
|
||||
Math::Vector2(0.5f, 0.5f)));
|
||||
|
||||
for (int segment = 0; segment <= kRadialSegments; ++segment) {
|
||||
const float u = static_cast<float>(segment) / static_cast<float>(kRadialSegments);
|
||||
const float angle = u * (2.0f * kPi);
|
||||
const float cosAngle = std::cos(angle);
|
||||
const float sinAngle = std::sin(angle);
|
||||
buffers.vertices.push_back(MakeVertex(
|
||||
Math::Vector3(cosAngle * kRadius, y, sinAngle * kRadius),
|
||||
normal,
|
||||
Math::Vector3::Right(),
|
||||
Math::Vector2(cosAngle * 0.5f + 0.5f, sinAngle * 0.5f + 0.5f)));
|
||||
}
|
||||
|
||||
for (int segment = 0; segment < kRadialSegments; ++segment) {
|
||||
const Core::uint32 rim0 = centerIndex + 1 + static_cast<Core::uint32>(segment);
|
||||
const Core::uint32 rim1 = rim0 + 1;
|
||||
if (topCap) {
|
||||
buffers.indices.push_back(centerIndex);
|
||||
buffers.indices.push_back(rim0);
|
||||
buffers.indices.push_back(rim1);
|
||||
} else {
|
||||
buffers.indices.push_back(centerIndex);
|
||||
buffers.indices.push_back(rim1);
|
||||
buffers.indices.push_back(rim0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
appendCap(true);
|
||||
appendCap(false);
|
||||
return buffers;
|
||||
}
|
||||
|
||||
MeshBuffers CreateCapsuleMeshBuffers() {
|
||||
MeshBuffers buffers;
|
||||
constexpr int kRadialSegments = 24;
|
||||
constexpr int kHemisphereSegments = 8;
|
||||
constexpr float kRadius = 0.5f;
|
||||
constexpr float kHalfCylinderHeight = 0.5f;
|
||||
|
||||
struct RingDefinition {
|
||||
float y = 0.0f;
|
||||
float radius = 0.0f;
|
||||
Math::Vector3 normalBase = Math::Vector3::Zero();
|
||||
};
|
||||
|
||||
std::vector<RingDefinition> rings;
|
||||
rings.reserve(static_cast<size_t>(kHemisphereSegments * 2 + 2));
|
||||
|
||||
for (int step = 0; step <= kHemisphereSegments; ++step) {
|
||||
const float t = static_cast<float>(step) / static_cast<float>(kHemisphereSegments);
|
||||
const float angle = -0.5f * kPi + t * (0.5f * kPi);
|
||||
const float ringRadius = std::cos(angle) * kRadius;
|
||||
const float y = std::sin(angle) * kRadius - kHalfCylinderHeight;
|
||||
const Math::Vector3 normalBase(0.0f, std::sin(angle), 0.0f);
|
||||
rings.push_back({ y, ringRadius, normalBase });
|
||||
}
|
||||
|
||||
for (int step = 0; step <= kHemisphereSegments; ++step) {
|
||||
const float t = static_cast<float>(step) / static_cast<float>(kHemisphereSegments);
|
||||
const float angle = t * (0.5f * kPi);
|
||||
const float ringRadius = std::cos(angle) * kRadius;
|
||||
const float y = std::sin(angle) * kRadius + kHalfCylinderHeight;
|
||||
const Math::Vector3 normalBase(0.0f, std::sin(angle), 0.0f);
|
||||
rings.push_back({ y, ringRadius, normalBase });
|
||||
}
|
||||
|
||||
for (size_t ringIndex = 0; ringIndex < rings.size(); ++ringIndex) {
|
||||
const float v = rings.size() > 1
|
||||
? static_cast<float>(ringIndex) / static_cast<float>(rings.size() - 1)
|
||||
: 0.0f;
|
||||
for (int segment = 0; segment <= kRadialSegments; ++segment) {
|
||||
const float u = static_cast<float>(segment) / static_cast<float>(kRadialSegments);
|
||||
const float angle = u * (2.0f * kPi);
|
||||
const float cosAngle = std::cos(angle);
|
||||
const float sinAngle = std::sin(angle);
|
||||
const Math::Vector3 radial(cosAngle, 0.0f, sinAngle);
|
||||
|
||||
Math::Vector3 normal(
|
||||
radial.x * rings[ringIndex].radius,
|
||||
rings[ringIndex].normalBase.y * kRadius,
|
||||
radial.z * rings[ringIndex].radius);
|
||||
normal = normal.Normalized();
|
||||
if (normal.SqrMagnitude() <= 0.000001f) {
|
||||
normal = rings[ringIndex].y >= 0.0f ? Math::Vector3::Up() : Math::Vector3::Down();
|
||||
}
|
||||
|
||||
const Math::Vector3 tangent(-sinAngle, 0.0f, cosAngle);
|
||||
buffers.vertices.push_back(MakeVertex(
|
||||
Math::Vector3(radial.x * rings[ringIndex].radius, rings[ringIndex].y, radial.z * rings[ringIndex].radius),
|
||||
normal,
|
||||
tangent,
|
||||
Math::Vector2(u, 1.0f - v)));
|
||||
}
|
||||
}
|
||||
|
||||
const int stride = kRadialSegments + 1;
|
||||
for (size_t ringIndex = 0; ringIndex + 1 < rings.size(); ++ringIndex) {
|
||||
for (int segment = 0; segment < kRadialSegments; ++segment) {
|
||||
const Core::uint32 i0 = static_cast<Core::uint32>(ringIndex * stride + static_cast<size_t>(segment));
|
||||
const Core::uint32 i1 = i0 + 1;
|
||||
const Core::uint32 i2 = i0 + static_cast<Core::uint32>(stride);
|
||||
const Core::uint32 i3 = i2 + 1;
|
||||
|
||||
buffers.indices.push_back(i0);
|
||||
buffers.indices.push_back(i2);
|
||||
buffers.indices.push_back(i1);
|
||||
buffers.indices.push_back(i1);
|
||||
buffers.indices.push_back(i2);
|
||||
buffers.indices.push_back(i3);
|
||||
}
|
||||
}
|
||||
|
||||
return buffers;
|
||||
}
|
||||
|
||||
Mesh* BuildMeshResource(
|
||||
const Containers::String& path,
|
||||
const char* displayName,
|
||||
MeshBuffers&& buffers) {
|
||||
if (buffers.vertices.empty() || buffers.indices.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* mesh = new Mesh();
|
||||
IResource::ConstructParams params;
|
||||
params.name = Containers::String(displayName);
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(path);
|
||||
params.memorySize = 0;
|
||||
mesh->Initialize(params);
|
||||
|
||||
mesh->SetVertexData(
|
||||
buffers.vertices.data(),
|
||||
buffers.vertices.size() * sizeof(StaticMeshVertex),
|
||||
static_cast<Core::uint32>(buffers.vertices.size()),
|
||||
sizeof(StaticMeshVertex),
|
||||
VertexAttribute::Position |
|
||||
VertexAttribute::Normal |
|
||||
VertexAttribute::Tangent |
|
||||
VertexAttribute::Bitangent |
|
||||
VertexAttribute::UV0);
|
||||
|
||||
if (buffers.vertices.size() > 65535u) {
|
||||
mesh->SetIndexData(
|
||||
buffers.indices.data(),
|
||||
buffers.indices.size() * sizeof(Core::uint32),
|
||||
static_cast<Core::uint32>(buffers.indices.size()),
|
||||
true);
|
||||
} else {
|
||||
std::vector<Core::uint16> compactIndices;
|
||||
compactIndices.reserve(buffers.indices.size());
|
||||
for (Core::uint32 index : buffers.indices) {
|
||||
compactIndices.push_back(static_cast<Core::uint16>(index));
|
||||
}
|
||||
|
||||
mesh->SetIndexData(
|
||||
compactIndices.data(),
|
||||
compactIndices.size() * sizeof(Core::uint16),
|
||||
static_cast<Core::uint32>(compactIndices.size()),
|
||||
false);
|
||||
}
|
||||
|
||||
const Math::Bounds bounds = ComputeBounds(buffers.vertices);
|
||||
mesh->SetBounds(bounds);
|
||||
|
||||
MeshSection section = {};
|
||||
section.baseVertex = 0;
|
||||
section.vertexCount = static_cast<Core::uint32>(buffers.vertices.size());
|
||||
section.startIndex = 0;
|
||||
section.indexCount = static_cast<Core::uint32>(buffers.indices.size());
|
||||
section.materialID = 0;
|
||||
section.bounds = bounds;
|
||||
mesh->AddSection(section);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
Material* BuildDefaultPrimitiveMaterial(const Containers::String& path) {
|
||||
auto* material = new Material();
|
||||
IResource::ConstructParams params;
|
||||
params.name = Containers::String("Default Primitive Material");
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(path);
|
||||
params.memorySize = 0;
|
||||
material->Initialize(params);
|
||||
|
||||
MaterialRenderState renderState = {};
|
||||
renderState.cullMode = MaterialCullMode::Back;
|
||||
material->SetRenderState(renderState);
|
||||
material->SetRenderQueue(MaterialRenderQueue::Geometry);
|
||||
material->SetTexture(
|
||||
Containers::String("baseColorTexture"),
|
||||
ResourceManager::Get().Load<Texture>(GetBuiltinDefaultPrimitiveTexturePath()));
|
||||
material->RecalculateMemorySize();
|
||||
return material;
|
||||
}
|
||||
|
||||
Texture* BuildDefaultPrimitiveTexture(const Containers::String& path) {
|
||||
static const unsigned char kTexturePixels[4] = { 214, 214, 214, 255 };
|
||||
|
||||
auto* texture = new Texture();
|
||||
IResource::ConstructParams params;
|
||||
params.name = Containers::String("Default Primitive Albedo");
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(path);
|
||||
params.memorySize = 0;
|
||||
texture->Initialize(params);
|
||||
|
||||
if (!texture->Create(
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
TextureType::Texture2D,
|
||||
TextureFormat::RGBA8_UNORM,
|
||||
kTexturePixels,
|
||||
sizeof(kTexturePixels))) {
|
||||
delete texture;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool IsBuiltinResourcePath(const Containers::String& path) {
|
||||
return path.StartsWith(kBuiltinPrefix);
|
||||
}
|
||||
|
||||
bool IsBuiltinMeshPath(const Containers::String& path) {
|
||||
return path.StartsWith(kBuiltinMeshPrefix);
|
||||
}
|
||||
|
||||
bool IsBuiltinMaterialPath(const Containers::String& path) {
|
||||
return path.StartsWith(kBuiltinMaterialPrefix);
|
||||
}
|
||||
|
||||
bool IsBuiltinTexturePath(const Containers::String& path) {
|
||||
return path.StartsWith(kBuiltinTexturePrefix);
|
||||
}
|
||||
|
||||
const char* GetBuiltinPrimitiveDisplayName(BuiltinPrimitiveType primitiveType) {
|
||||
switch (primitiveType) {
|
||||
case BuiltinPrimitiveType::Cube: return "Cube";
|
||||
case BuiltinPrimitiveType::Sphere: return "Sphere";
|
||||
case BuiltinPrimitiveType::Capsule: return "Capsule";
|
||||
case BuiltinPrimitiveType::Cylinder: return "Cylinder";
|
||||
case BuiltinPrimitiveType::Plane: return "Plane";
|
||||
case BuiltinPrimitiveType::Quad: return "Quad";
|
||||
default: return "Primitive";
|
||||
}
|
||||
}
|
||||
|
||||
Containers::String GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType primitiveType) {
|
||||
switch (primitiveType) {
|
||||
case BuiltinPrimitiveType::Cube: return Containers::String("builtin://meshes/cube");
|
||||
case BuiltinPrimitiveType::Sphere: return Containers::String("builtin://meshes/sphere");
|
||||
case BuiltinPrimitiveType::Capsule: return Containers::String("builtin://meshes/capsule");
|
||||
case BuiltinPrimitiveType::Cylinder: return Containers::String("builtin://meshes/cylinder");
|
||||
case BuiltinPrimitiveType::Plane: return Containers::String("builtin://meshes/plane");
|
||||
case BuiltinPrimitiveType::Quad: return Containers::String("builtin://meshes/quad");
|
||||
default: return Containers::String();
|
||||
}
|
||||
}
|
||||
|
||||
Containers::String GetBuiltinDefaultPrimitiveMaterialPath() {
|
||||
return Containers::String(kBuiltinDefaultPrimitiveMaterialPath);
|
||||
}
|
||||
|
||||
Containers::String GetBuiltinDefaultPrimitiveTexturePath() {
|
||||
return Containers::String(kBuiltinDefaultPrimitiveTexturePath);
|
||||
}
|
||||
|
||||
bool TryParseBuiltinPrimitiveType(const Containers::String& path, BuiltinPrimitiveType& outPrimitiveType) {
|
||||
if (path == GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Cube)) {
|
||||
outPrimitiveType = BuiltinPrimitiveType::Cube;
|
||||
return true;
|
||||
}
|
||||
if (path == GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Sphere)) {
|
||||
outPrimitiveType = BuiltinPrimitiveType::Sphere;
|
||||
return true;
|
||||
}
|
||||
if (path == GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Capsule)) {
|
||||
outPrimitiveType = BuiltinPrimitiveType::Capsule;
|
||||
return true;
|
||||
}
|
||||
if (path == GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Cylinder)) {
|
||||
outPrimitiveType = BuiltinPrimitiveType::Cylinder;
|
||||
return true;
|
||||
}
|
||||
if (path == GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Plane)) {
|
||||
outPrimitiveType = BuiltinPrimitiveType::Plane;
|
||||
return true;
|
||||
}
|
||||
if (path == GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType::Quad)) {
|
||||
outPrimitiveType = BuiltinPrimitiveType::Quad;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
LoadResult CreateBuiltinMeshResource(const Containers::String& path) {
|
||||
BuiltinPrimitiveType primitiveType = BuiltinPrimitiveType::Cube;
|
||||
if (!TryParseBuiltinPrimitiveType(path, primitiveType)) {
|
||||
return LoadResult(Containers::String("Unknown builtin mesh: ") + path);
|
||||
}
|
||||
|
||||
MeshBuffers buffers;
|
||||
switch (primitiveType) {
|
||||
case BuiltinPrimitiveType::Cube:
|
||||
buffers = CreateCubeMeshBuffers();
|
||||
break;
|
||||
case BuiltinPrimitiveType::Sphere:
|
||||
buffers = CreateUvSphereMeshBuffers();
|
||||
break;
|
||||
case BuiltinPrimitiveType::Capsule:
|
||||
buffers = CreateCapsuleMeshBuffers();
|
||||
break;
|
||||
case BuiltinPrimitiveType::Cylinder:
|
||||
buffers = CreateCylinderMeshBuffers();
|
||||
break;
|
||||
case BuiltinPrimitiveType::Plane:
|
||||
buffers = CreatePlaneMeshBuffers();
|
||||
break;
|
||||
case BuiltinPrimitiveType::Quad:
|
||||
buffers = CreateQuadMeshBuffers();
|
||||
break;
|
||||
default:
|
||||
return LoadResult(Containers::String("Unsupported builtin mesh: ") + path);
|
||||
}
|
||||
|
||||
FlipTriangleWinding(buffers);
|
||||
|
||||
Mesh* mesh = BuildMeshResource(path, GetBuiltinPrimitiveDisplayName(primitiveType), std::move(buffers));
|
||||
if (mesh == nullptr) {
|
||||
return LoadResult(Containers::String("Failed to create builtin mesh: ") + path);
|
||||
}
|
||||
|
||||
return LoadResult(mesh);
|
||||
}
|
||||
|
||||
LoadResult CreateBuiltinMaterialResource(const Containers::String& path) {
|
||||
if (path != GetBuiltinDefaultPrimitiveMaterialPath()) {
|
||||
return LoadResult(Containers::String("Unknown builtin material: ") + path);
|
||||
}
|
||||
|
||||
Material* material = BuildDefaultPrimitiveMaterial(path);
|
||||
if (material == nullptr) {
|
||||
return LoadResult(Containers::String("Failed to create builtin material: ") + path);
|
||||
}
|
||||
|
||||
return LoadResult(material);
|
||||
}
|
||||
|
||||
LoadResult CreateBuiltinTextureResource(const Containers::String& path) {
|
||||
if (path != GetBuiltinDefaultPrimitiveTexturePath()) {
|
||||
return LoadResult(Containers::String("Unknown builtin texture: ") + path);
|
||||
}
|
||||
|
||||
Texture* texture = BuildDefaultPrimitiveTexture(path);
|
||||
if (texture == nullptr) {
|
||||
return LoadResult(Containers::String("Failed to create builtin texture: ") + path);
|
||||
}
|
||||
|
||||
return LoadResult(texture);
|
||||
}
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
@@ -46,7 +46,7 @@ void Material::SetShaderPass(const Containers::String& shaderPass) {
|
||||
}
|
||||
|
||||
void Material::SetTag(const Containers::String& name, const Containers::String& value) {
|
||||
for (TagEntry& tag : m_tags) {
|
||||
for (MaterialTagEntry& tag : m_tags) {
|
||||
if (tag.name == name) {
|
||||
tag.value = value;
|
||||
UpdateMemorySize();
|
||||
@@ -54,7 +54,7 @@ void Material::SetTag(const Containers::String& name, const Containers::String&
|
||||
}
|
||||
}
|
||||
|
||||
TagEntry tag;
|
||||
MaterialTagEntry tag;
|
||||
tag.name = name;
|
||||
tag.value = value;
|
||||
m_tags.PushBack(tag);
|
||||
@@ -62,7 +62,7 @@ void Material::SetTag(const Containers::String& name, const Containers::String&
|
||||
}
|
||||
|
||||
Containers::String Material::GetTag(const Containers::String& name) const {
|
||||
for (const TagEntry& tag : m_tags) {
|
||||
for (const MaterialTagEntry& tag : m_tags) {
|
||||
if (tag.name == name) {
|
||||
return tag.value;
|
||||
}
|
||||
@@ -72,7 +72,7 @@ Containers::String Material::GetTag(const Containers::String& name) const {
|
||||
}
|
||||
|
||||
bool Material::HasTag(const Containers::String& name) const {
|
||||
for (const TagEntry& tag : m_tags) {
|
||||
for (const MaterialTagEntry& tag : m_tags) {
|
||||
if (tag.name == name) {
|
||||
return true;
|
||||
}
|
||||
@@ -99,6 +99,14 @@ void Material::ClearTags() {
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
Containers::String Material::GetTagName(Core::uint32 index) const {
|
||||
return index < m_tags.Size() ? m_tags[index].name : Containers::String();
|
||||
}
|
||||
|
||||
Containers::String Material::GetTagValue(Core::uint32 index) const {
|
||||
return index < m_tags.Size() ? m_tags[index].value : Containers::String();
|
||||
}
|
||||
|
||||
void Material::SetFloat(const Containers::String& name, float value) {
|
||||
MaterialProperty prop;
|
||||
prop.name = name;
|
||||
@@ -180,7 +188,7 @@ void Material::SetTexture(const Containers::String& name, const ResourceHandle<T
|
||||
}
|
||||
}
|
||||
|
||||
TextureBinding binding;
|
||||
MaterialTextureBinding binding;
|
||||
binding.name = name;
|
||||
binding.slot = static_cast<Core::uint32>(m_textureBindings.Size());
|
||||
binding.texture = texture;
|
||||
@@ -246,6 +254,24 @@ ResourceHandle<Texture> Material::GetTexture(const Containers::String& name) con
|
||||
return ResourceHandle<Texture>();
|
||||
}
|
||||
|
||||
Containers::String Material::GetTextureBindingName(Core::uint32 index) const {
|
||||
return index < m_textureBindings.Size() ? m_textureBindings[index].name : Containers::String();
|
||||
}
|
||||
|
||||
ResourceHandle<Texture> Material::GetTextureBindingTexture(Core::uint32 index) const {
|
||||
return index < m_textureBindings.Size() ? m_textureBindings[index].texture : ResourceHandle<Texture>();
|
||||
}
|
||||
|
||||
std::vector<MaterialProperty> Material::GetProperties() const {
|
||||
std::vector<MaterialProperty> properties;
|
||||
const auto pairs = m_properties.GetPairs();
|
||||
properties.reserve(pairs.Size());
|
||||
for (const auto& pair : pairs) {
|
||||
properties.push_back(pair.second);
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
|
||||
void Material::UpdateConstantBuffer() {
|
||||
m_constantBufferData.Clear();
|
||||
UpdateMemorySize();
|
||||
@@ -275,13 +301,13 @@ void Material::UpdateMemorySize() {
|
||||
m_memorySize = m_constantBufferData.Size() +
|
||||
sizeof(MaterialRenderState) +
|
||||
m_shaderPass.Length() +
|
||||
m_tags.Size() * sizeof(TagEntry) +
|
||||
m_textureBindings.Size() * sizeof(TextureBinding) +
|
||||
m_tags.Size() * sizeof(MaterialTagEntry) +
|
||||
m_textureBindings.Size() * sizeof(MaterialTextureBinding) +
|
||||
m_properties.Size() * sizeof(MaterialProperty) +
|
||||
m_name.Length() +
|
||||
m_path.Length();
|
||||
|
||||
for (const TagEntry& tag : m_tags) {
|
||||
for (const MaterialTagEntry& tag : m_tags) {
|
||||
m_memorySize += tag.name.Length();
|
||||
m_memorySize += tag.value.Length();
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||||
#include <XCEngine/Resources/Shader/ShaderLoader.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
@@ -531,6 +533,24 @@ ResourceHandle<Shader> LoadShaderHandle(const Containers::String& shaderPath) {
|
||||
return ResourceHandle<Shader>(static_cast<Shader*>(shaderResult.resource));
|
||||
}
|
||||
|
||||
bool MaterialFileExists(const Containers::String& path) {
|
||||
const std::filesystem::path inputPath(path.CStr());
|
||||
if (std::filesystem::exists(inputPath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (inputPath.is_absolute()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
if (resourceRoot.Empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return std::filesystem::exists(std::filesystem::path(resourceRoot.CStr()) / inputPath);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MaterialLoader::MaterialLoader() = default;
|
||||
@@ -546,6 +566,10 @@ Containers::Array<Containers::String> MaterialLoader::GetSupportedExtensions() c
|
||||
}
|
||||
|
||||
bool MaterialLoader::CanLoad(const Containers::String& path) const {
|
||||
if (IsBuiltinMaterialPath(path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Containers::String ext = GetExtension(path);
|
||||
return ext == "mat" || ext == "material" || ext == "json";
|
||||
}
|
||||
@@ -553,17 +577,22 @@ bool MaterialLoader::CanLoad(const Containers::String& path) const {
|
||||
LoadResult MaterialLoader::Load(const Containers::String& path, const ImportSettings* settings) {
|
||||
(void)settings;
|
||||
|
||||
Containers::Array<Core::uint8> data = ReadFileData(path);
|
||||
if (data.Empty()) {
|
||||
return LoadResult("Failed to read material file: " + path);
|
||||
if (IsBuiltinMaterialPath(path)) {
|
||||
return CreateBuiltinMaterialResource(path);
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> data = ReadFileData(path);
|
||||
Material* material = new Material();
|
||||
material->m_path = path;
|
||||
material->m_name = path;
|
||||
material->m_guid = ResourceGUID::Generate(path);
|
||||
|
||||
if (!ParseMaterialData(data, material)) {
|
||||
if (data.Empty() && !MaterialFileExists(path)) {
|
||||
delete material;
|
||||
return LoadResult("Failed to read material file: " + path);
|
||||
}
|
||||
|
||||
if (!data.Empty() && !ParseMaterialData(data, material)) {
|
||||
delete material;
|
||||
return LoadResult("Failed to parse material file: " + path);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
#include <XCEngine/Resources/Mesh/MeshLoader.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Resources/Mesh/MeshImportSettings.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
#include <XCEngine/Resources/Texture/Texture.h>
|
||||
@@ -14,6 +16,7 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
@@ -507,6 +510,218 @@ void ProcessNode(const aiNode& node,
|
||||
}
|
||||
}
|
||||
|
||||
Containers::String ReadBinaryString(std::ifstream& stream) {
|
||||
Core::uint32 length = 0;
|
||||
stream.read(reinterpret_cast<char*>(&length), sizeof(length));
|
||||
if (!stream || length == 0) {
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
std::string buffer(length, '\0');
|
||||
stream.read(buffer.data(), length);
|
||||
if (!stream) {
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
return Containers::String(buffer.c_str());
|
||||
}
|
||||
|
||||
void ApplyMaterialProperty(Material& material, const MaterialProperty& property) {
|
||||
switch (property.type) {
|
||||
case MaterialPropertyType::Float:
|
||||
material.SetFloat(property.name, property.value.floatValue[0]);
|
||||
break;
|
||||
case MaterialPropertyType::Float2:
|
||||
material.SetFloat2(property.name,
|
||||
Math::Vector2(property.value.floatValue[0], property.value.floatValue[1]));
|
||||
break;
|
||||
case MaterialPropertyType::Float3:
|
||||
material.SetFloat3(property.name,
|
||||
Math::Vector3(property.value.floatValue[0], property.value.floatValue[1], property.value.floatValue[2]));
|
||||
break;
|
||||
case MaterialPropertyType::Float4:
|
||||
material.SetFloat4(property.name,
|
||||
Math::Vector4(property.value.floatValue[0],
|
||||
property.value.floatValue[1],
|
||||
property.value.floatValue[2],
|
||||
property.value.floatValue[3]));
|
||||
break;
|
||||
case MaterialPropertyType::Int:
|
||||
material.SetInt(property.name, property.value.intValue[0]);
|
||||
break;
|
||||
case MaterialPropertyType::Bool:
|
||||
material.SetBool(property.name, property.value.boolValue);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
LoadResult LoadMeshArtifact(const Containers::String& path) {
|
||||
std::ifstream input(path.CStr(), std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
return LoadResult(Containers::String("Failed to read mesh artifact: ") + path);
|
||||
}
|
||||
|
||||
MeshArtifactHeader header;
|
||||
input.read(reinterpret_cast<char*>(&header), sizeof(header));
|
||||
if (!input) {
|
||||
return LoadResult(Containers::String("Failed to parse mesh artifact header: ") + path);
|
||||
}
|
||||
|
||||
const std::string magic(header.magic, header.magic + 7);
|
||||
if (magic != "XCMESH1") {
|
||||
return LoadResult(Containers::String("Invalid mesh artifact magic: ") + path);
|
||||
}
|
||||
|
||||
auto mesh = std::make_unique<Mesh>();
|
||||
|
||||
IResource::ConstructParams params;
|
||||
params.name = GetResourceNameFromPath(path);
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(path);
|
||||
params.memorySize = 0;
|
||||
mesh->Initialize(params);
|
||||
|
||||
Containers::Array<MeshSection> sections;
|
||||
sections.Resize(header.sectionCount);
|
||||
for (Core::uint32 index = 0; index < header.sectionCount; ++index) {
|
||||
input.read(reinterpret_cast<char*>(§ions[index]), sizeof(MeshSection));
|
||||
if (!input) {
|
||||
return LoadResult(Containers::String("Failed to read mesh sections: ") + path);
|
||||
}
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> vertexData;
|
||||
vertexData.Resize(static_cast<size_t>(header.vertexDataSize));
|
||||
if (header.vertexDataSize > 0) {
|
||||
input.read(reinterpret_cast<char*>(vertexData.Data()), static_cast<std::streamsize>(header.vertexDataSize));
|
||||
if (!input) {
|
||||
return LoadResult(Containers::String("Failed to read mesh vertex data: ") + path);
|
||||
}
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> indexData;
|
||||
indexData.Resize(static_cast<size_t>(header.indexDataSize));
|
||||
if (header.indexDataSize > 0) {
|
||||
input.read(reinterpret_cast<char*>(indexData.Data()), static_cast<std::streamsize>(header.indexDataSize));
|
||||
if (!input) {
|
||||
return LoadResult(Containers::String("Failed to read mesh index data: ") + path);
|
||||
}
|
||||
}
|
||||
|
||||
mesh->SetVertexData(vertexData.Data(),
|
||||
vertexData.Size(),
|
||||
header.vertexCount,
|
||||
header.vertexStride,
|
||||
static_cast<VertexAttribute>(header.vertexAttributes));
|
||||
mesh->SetIndexData(indexData.Data(),
|
||||
indexData.Size(),
|
||||
header.indexCount,
|
||||
header.use32BitIndex != 0);
|
||||
|
||||
for (const MeshSection& section : sections) {
|
||||
mesh->AddSection(section);
|
||||
}
|
||||
|
||||
Math::Bounds bounds;
|
||||
bounds.SetMinMax(header.boundsMin, header.boundsMax);
|
||||
mesh->SetBounds(bounds);
|
||||
|
||||
std::vector<Containers::String> textureFiles;
|
||||
textureFiles.reserve(header.textureCount);
|
||||
for (Core::uint32 textureIndex = 0; textureIndex < header.textureCount; ++textureIndex) {
|
||||
textureFiles.push_back(ReadBinaryString(input));
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, Texture*> loadedTextures;
|
||||
TextureLoader textureLoader;
|
||||
const std::filesystem::path artifactDirectory = std::filesystem::path(path.CStr()).parent_path();
|
||||
|
||||
for (Core::uint32 materialIndex = 0; materialIndex < header.materialCount; ++materialIndex) {
|
||||
Core::uint32 materialPresent = 0;
|
||||
input.read(reinterpret_cast<char*>(&materialPresent), sizeof(materialPresent));
|
||||
if (!input) {
|
||||
return LoadResult(Containers::String("Failed to read mesh material flag: ") + path);
|
||||
}
|
||||
|
||||
if (materialPresent == 0) {
|
||||
mesh->AddMaterial(nullptr);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* material = new Material();
|
||||
material->m_name = ReadBinaryString(input);
|
||||
material->m_path = ReadBinaryString(input);
|
||||
material->m_guid = ResourceGUID::Generate(material->m_path);
|
||||
material->m_isValid = true;
|
||||
material->SetShaderPass(ReadBinaryString(input));
|
||||
|
||||
MaterialArtifactHeader materialHeader;
|
||||
input.read(reinterpret_cast<char*>(&materialHeader), sizeof(materialHeader));
|
||||
if (!input) {
|
||||
delete material;
|
||||
return LoadResult(Containers::String("Failed to read material artifact header: ") + path);
|
||||
}
|
||||
|
||||
material->SetRenderQueue(materialHeader.renderQueue);
|
||||
material->SetRenderState(materialHeader.renderState);
|
||||
|
||||
for (Core::uint32 tagIndex = 0; tagIndex < materialHeader.tagCount; ++tagIndex) {
|
||||
material->SetTag(ReadBinaryString(input), ReadBinaryString(input));
|
||||
}
|
||||
|
||||
for (Core::uint32 propertyIndex = 0; propertyIndex < materialHeader.propertyCount; ++propertyIndex) {
|
||||
MaterialProperty property;
|
||||
property.name = ReadBinaryString(input);
|
||||
|
||||
MaterialPropertyArtifact propertyArtifact;
|
||||
input.read(reinterpret_cast<char*>(&propertyArtifact), sizeof(propertyArtifact));
|
||||
if (!input) {
|
||||
delete material;
|
||||
return LoadResult(Containers::String("Failed to read material property: ") + path);
|
||||
}
|
||||
|
||||
property.type = static_cast<MaterialPropertyType>(propertyArtifact.propertyType);
|
||||
property.value = propertyArtifact.value;
|
||||
ApplyMaterialProperty(*material, property);
|
||||
}
|
||||
|
||||
for (Core::uint32 bindingIndex = 0; bindingIndex < materialHeader.textureBindingCount; ++bindingIndex) {
|
||||
const Containers::String bindingName = ReadBinaryString(input);
|
||||
const Containers::String textureFile = ReadBinaryString(input);
|
||||
if (textureFile.Empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string textureKey(textureFile.CStr());
|
||||
Texture* texture = nullptr;
|
||||
auto textureIt = loadedTextures.find(textureKey);
|
||||
if (textureIt != loadedTextures.end()) {
|
||||
texture = textureIt->second;
|
||||
} else {
|
||||
const Containers::String texturePath =
|
||||
Containers::String((artifactDirectory / textureFile.CStr()).lexically_normal().string().c_str());
|
||||
LoadResult textureResult = textureLoader.Load(texturePath);
|
||||
if (textureResult && textureResult.resource != nullptr) {
|
||||
texture = static_cast<Texture*>(textureResult.resource);
|
||||
loadedTextures.emplace(textureKey, texture);
|
||||
mesh->AddTexture(texture);
|
||||
}
|
||||
}
|
||||
|
||||
if (texture != nullptr) {
|
||||
material->SetTexture(bindingName, ResourceHandle<Texture>(texture));
|
||||
}
|
||||
}
|
||||
|
||||
material->RecalculateMemorySize();
|
||||
mesh->AddMaterial(material);
|
||||
}
|
||||
|
||||
return LoadResult(mesh.release());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MeshLoader::MeshLoader() = default;
|
||||
@@ -520,26 +735,45 @@ Containers::Array<Containers::String> MeshLoader::GetSupportedExtensions() const
|
||||
extensions.PushBack(Containers::String("glb"));
|
||||
extensions.PushBack(Containers::String("dae"));
|
||||
extensions.PushBack(Containers::String("stl"));
|
||||
extensions.PushBack(Containers::String("xcmesh"));
|
||||
return extensions;
|
||||
}
|
||||
|
||||
bool MeshLoader::CanLoad(const Containers::String& path) const {
|
||||
if (IsBuiltinMeshPath(path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Containers::String ext = GetExtension(path).ToLower();
|
||||
|
||||
return ext == "fbx" || ext == "obj" || ext == "gltf" ||
|
||||
ext == "glb" || ext == "dae" || ext == "stl";
|
||||
ext == "glb" || ext == "dae" || ext == "stl" ||
|
||||
ext == "xcmesh";
|
||||
}
|
||||
|
||||
LoadResult MeshLoader::Load(const Containers::String& path, const ImportSettings* settings) {
|
||||
if (IsBuiltinMeshPath(path)) {
|
||||
return CreateBuiltinMeshResource(path);
|
||||
}
|
||||
|
||||
const Containers::String ext = GetExtension(path).ToLower();
|
||||
|
||||
if (!CanLoad(path)) {
|
||||
return LoadResult(Containers::String("Unsupported mesh format: ") + ext);
|
||||
}
|
||||
|
||||
std::ifstream file(path.CStr(), std::ios::binary);
|
||||
if (ext == "xcmesh") {
|
||||
return LoadMeshArtifact(path);
|
||||
}
|
||||
|
||||
Containers::String resolvedPath = path;
|
||||
if (!std::filesystem::path(path.CStr()).is_absolute()) {
|
||||
resolvedPath = ResourceManager::Get().ResolvePath(path);
|
||||
}
|
||||
|
||||
std::ifstream file(resolvedPath.CStr(), std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return LoadResult(Containers::String("Failed to read file: ") + path);
|
||||
return LoadResult(Containers::String("Failed to read file: ") + resolvedPath);
|
||||
}
|
||||
|
||||
MeshImportSettings defaultSettings;
|
||||
@@ -549,7 +783,7 @@ LoadResult MeshLoader::Load(const Containers::String& path, const ImportSettings
|
||||
}
|
||||
|
||||
Assimp::Importer importer;
|
||||
const aiScene* scene = importer.ReadFile(path.CStr(), BuildPostProcessFlags(*resolvedSettings));
|
||||
const aiScene* scene = importer.ReadFile(resolvedPath.CStr(), BuildPostProcessFlags(*resolvedSettings));
|
||||
if (scene == nullptr || scene->mRootNode == nullptr || (scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) != 0) {
|
||||
const char* errorText = importer.GetErrorString();
|
||||
return LoadResult(Containers::String("Assimp failed to load mesh: ") +
|
||||
@@ -577,7 +811,7 @@ LoadResult MeshLoader::Load(const Containers::String& path, const ImportSettings
|
||||
auto* mesh = new Mesh();
|
||||
|
||||
IResource::ConstructParams params;
|
||||
const std::string fileName = std::filesystem::path(path.CStr()).filename().string();
|
||||
const std::string fileName = std::filesystem::path(resolvedPath.CStr()).filename().string();
|
||||
params.name = Containers::String(fileName.c_str());
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(path);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#include <XCEngine/Resources/Texture/TextureLoader.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <stb_image.h>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
@@ -29,7 +32,7 @@ LoadResult CreateTextureResource(const Containers::String& path,
|
||||
params.name = GetResourceNameFromPath(path);
|
||||
params.path = path;
|
||||
params.guid = ResourceGUID::Generate(path);
|
||||
params.memorySize = 0;
|
||||
params.memorySize = pixelDataSize;
|
||||
texture->Initialize(params);
|
||||
|
||||
if (!texture->Create(width,
|
||||
@@ -47,6 +50,40 @@ LoadResult CreateTextureResource(const Containers::String& path,
|
||||
return LoadResult(texture);
|
||||
}
|
||||
|
||||
LoadResult LoadTextureArtifact(const Containers::String& path) {
|
||||
std::ifstream input(path.CStr(), std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
return LoadResult(Containers::String("Failed to read texture artifact: ") + path);
|
||||
}
|
||||
|
||||
TextureArtifactHeader header;
|
||||
input.read(reinterpret_cast<char*>(&header), sizeof(header));
|
||||
if (!input) {
|
||||
return LoadResult(Containers::String("Failed to parse texture artifact header: ") + path);
|
||||
}
|
||||
|
||||
const std::string magic(header.magic, header.magic + 7);
|
||||
if (magic != "XCTEX01") {
|
||||
return LoadResult(Containers::String("Invalid texture artifact magic: ") + path);
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> pixelData;
|
||||
pixelData.Resize(static_cast<size_t>(header.pixelDataSize));
|
||||
if (header.pixelDataSize > 0) {
|
||||
input.read(reinterpret_cast<char*>(pixelData.Data()), static_cast<std::streamsize>(header.pixelDataSize));
|
||||
if (!input) {
|
||||
return LoadResult(Containers::String("Failed to read texture artifact payload: ") + path);
|
||||
}
|
||||
}
|
||||
|
||||
return CreateTextureResource(path,
|
||||
static_cast<TextureFormat>(header.textureFormat),
|
||||
header.width,
|
||||
header.height,
|
||||
pixelData.Data(),
|
||||
pixelData.Size());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TextureLoader::TextureLoader() = default;
|
||||
@@ -62,26 +99,39 @@ Containers::Array<Containers::String> TextureLoader::GetSupportedExtensions() co
|
||||
extensions.PushBack(Containers::String("gif"));
|
||||
extensions.PushBack(Containers::String("hdr"));
|
||||
extensions.PushBack(Containers::String("dds"));
|
||||
extensions.PushBack(Containers::String("xctex"));
|
||||
return extensions;
|
||||
}
|
||||
|
||||
bool TextureLoader::CanLoad(const Containers::String& path) const {
|
||||
if (IsBuiltinTexturePath(path)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Containers::String ext = GetExtension(path).ToLower();
|
||||
|
||||
return ext == "png" || ext == "jpg" || ext == "jpeg" ||
|
||||
ext == "tga" || ext == "bmp" || ext == "gif" ||
|
||||
ext == "hdr" || ext == "dds";
|
||||
ext == "hdr" || ext == "dds" || ext == "xctex";
|
||||
}
|
||||
|
||||
LoadResult TextureLoader::Load(const Containers::String& path, const ImportSettings* settings) {
|
||||
(void)settings;
|
||||
|
||||
if (IsBuiltinTexturePath(path)) {
|
||||
return CreateBuiltinTextureResource(path);
|
||||
}
|
||||
|
||||
Containers::String ext = GetExtension(path).ToLower();
|
||||
|
||||
if (!CanLoad(path)) {
|
||||
return LoadResult(Containers::String("Unsupported texture format: ") + ext);
|
||||
}
|
||||
|
||||
if (ext == "xctex") {
|
||||
return LoadTextureArtifact(path);
|
||||
}
|
||||
|
||||
if (ext == "dds") {
|
||||
return LoadResult(Containers::String("DDS texture decoding is not implemented yet: ") + path);
|
||||
}
|
||||
|
||||
@@ -4,9 +4,12 @@
|
||||
#include <XCEngine/Components/MeshFilterComponent.h>
|
||||
#include <XCEngine/Components/MeshRendererComponent.h>
|
||||
#include <XCEngine/Core/Asset/IResource.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
#include <XCEngine/Resources/Mesh/Mesh.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
using namespace XCEngine::Components;
|
||||
@@ -179,4 +182,59 @@ TEST(MeshRendererComponent_Test, SetMaterialPathPreservesPathWithoutLoadedResour
|
||||
EXPECT_EQ(component.GetMaterial(1), nullptr);
|
||||
}
|
||||
|
||||
TEST(MeshRendererComponent_Test, SerializeAndDeserializeLoadsProjectMaterialByAssetRef) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
ResourceManager& manager = ResourceManager::Get();
|
||||
manager.Initialize();
|
||||
|
||||
const fs::path projectRoot = fs::temp_directory_path() / "xc_mesh_renderer_asset_ref_test";
|
||||
const fs::path assetsDir = projectRoot / "Assets";
|
||||
const fs::path materialPath = assetsDir / "runtime.material";
|
||||
|
||||
fs::remove_all(projectRoot);
|
||||
fs::create_directories(assetsDir);
|
||||
|
||||
{
|
||||
std::ofstream materialFile(materialPath);
|
||||
ASSERT_TRUE(materialFile.is_open());
|
||||
materialFile << "{\n";
|
||||
materialFile << " \"renderQueue\": \"geometry\",\n";
|
||||
materialFile << " \"renderState\": {\n";
|
||||
materialFile << " \"cull\": \"back\"\n";
|
||||
materialFile << " }\n";
|
||||
materialFile << "}";
|
||||
}
|
||||
|
||||
manager.SetResourceRoot(projectRoot.string().c_str());
|
||||
|
||||
MeshRendererComponent source;
|
||||
source.SetMaterialPath(0, "Assets/runtime.material");
|
||||
|
||||
ASSERT_EQ(source.GetMaterialCount(), 1u);
|
||||
ASSERT_NE(source.GetMaterial(0), nullptr);
|
||||
ASSERT_EQ(source.GetMaterialPath(0), "Assets/runtime.material");
|
||||
ASSERT_EQ(source.GetMaterialAssetRefs().size(), 1u);
|
||||
EXPECT_TRUE(source.GetMaterialAssetRefs()[0].IsValid());
|
||||
|
||||
std::stringstream stream;
|
||||
source.Serialize(stream);
|
||||
const std::string serialized = stream.str();
|
||||
EXPECT_NE(serialized.find("materialRefs="), std::string::npos);
|
||||
EXPECT_EQ(serialized.find("materialRefs=;"), std::string::npos);
|
||||
|
||||
std::stringstream deserializeStream(serialized);
|
||||
MeshRendererComponent target;
|
||||
target.Deserialize(deserializeStream);
|
||||
|
||||
ASSERT_EQ(target.GetMaterialCount(), 1u);
|
||||
EXPECT_EQ(target.GetMaterialPath(0), "Assets/runtime.material");
|
||||
ASSERT_NE(target.GetMaterial(0), nullptr);
|
||||
EXPECT_TRUE(target.GetMaterialAssetRefs()[0].IsValid());
|
||||
|
||||
manager.SetResourceRoot("");
|
||||
manager.Shutdown();
|
||||
fs::remove_all(projectRoot);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -141,4 +141,47 @@ TEST(MaterialLoader, RejectsUnknownRenderStateEnum) {
|
||||
std::remove(materialPath.string().c_str());
|
||||
}
|
||||
|
||||
TEST(MaterialLoader, ResourceManagerLoadsRelativeMaterialFromResourceRoot) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
ResourceManager& manager = ResourceManager::Get();
|
||||
manager.Initialize();
|
||||
|
||||
const fs::path previousPath = fs::current_path();
|
||||
const fs::path projectRoot = fs::temp_directory_path() / "xc_material_loader_resource_root";
|
||||
const fs::path assetsDir = projectRoot / "Assets";
|
||||
const fs::path materialPath = assetsDir / "relative.material";
|
||||
|
||||
fs::remove_all(projectRoot);
|
||||
fs::create_directories(assetsDir);
|
||||
|
||||
{
|
||||
std::ofstream materialFile(materialPath);
|
||||
ASSERT_TRUE(materialFile.is_open());
|
||||
materialFile << "{\n";
|
||||
materialFile << " \"renderQueue\": \"geometry\",\n";
|
||||
materialFile << " \"renderState\": {\n";
|
||||
materialFile << " \"cull\": \"back\",\n";
|
||||
materialFile << " \"colorWriteMask\": 15\n";
|
||||
materialFile << " }\n";
|
||||
materialFile << "}";
|
||||
}
|
||||
|
||||
manager.SetResourceRoot(projectRoot.string().c_str());
|
||||
fs::current_path(projectRoot.parent_path());
|
||||
|
||||
{
|
||||
const auto materialHandle = manager.Load<Material>("Assets/relative.material");
|
||||
ASSERT_TRUE(materialHandle.IsValid());
|
||||
EXPECT_EQ(materialHandle->GetRenderQueue(), static_cast<XCEngine::Core::int32>(MaterialRenderQueue::Geometry));
|
||||
EXPECT_EQ(materialHandle->GetRenderState().cullMode, MaterialCullMode::Back);
|
||||
EXPECT_EQ(materialHandle->GetRenderState().colorWriteMask, 15);
|
||||
}
|
||||
|
||||
fs::current_path(previousPath);
|
||||
fs::remove_all(projectRoot);
|
||||
manager.SetResourceRoot("");
|
||||
manager.Shutdown();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <XCEngine/Core/Asset/AssetDatabase.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Resources/Mesh/MeshLoader.h>
|
||||
#include <XCEngine/Resources/Mesh/MeshImportSettings.h>
|
||||
@@ -6,7 +7,10 @@
|
||||
#include <XCEngine/Resources/Texture/Texture.h>
|
||||
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||||
#include <XCEngine/Core/Containers/Array.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <thread>
|
||||
|
||||
using namespace XCEngine::Resources;
|
||||
using namespace XCEngine::Containers;
|
||||
@@ -143,4 +147,102 @@ TEST(MeshLoader, ImportsMaterialTexturesFromObj) {
|
||||
delete mesh;
|
||||
}
|
||||
|
||||
TEST(MeshLoader, AssetDatabaseCreatesModelArtifactAndReusesItWithoutReimport) {
|
||||
namespace fs = std::filesystem;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
const fs::path projectRoot = fs::temp_directory_path() / "xc_mesh_library_cache_test";
|
||||
const fs::path assetsDir = projectRoot / "Assets";
|
||||
|
||||
fs::remove_all(projectRoot);
|
||||
fs::create_directories(assetsDir);
|
||||
fs::copy_file(GetMeshFixturePath("textured_triangle.obj"),
|
||||
assetsDir / "textured_triangle.obj",
|
||||
fs::copy_options::overwrite_existing);
|
||||
fs::copy_file(GetMeshFixturePath("textured_triangle.mtl"),
|
||||
assetsDir / "textured_triangle.mtl",
|
||||
fs::copy_options::overwrite_existing);
|
||||
fs::copy_file(GetMeshFixturePath("checker.bmp"),
|
||||
assetsDir / "checker.bmp",
|
||||
fs::copy_options::overwrite_existing);
|
||||
|
||||
AssetDatabase database;
|
||||
database.Initialize(projectRoot.string().c_str());
|
||||
|
||||
AssetDatabase::ResolvedAsset firstResolve;
|
||||
ASSERT_TRUE(database.EnsureArtifact("Assets/textured_triangle.obj", ResourceType::Mesh, firstResolve));
|
||||
ASSERT_TRUE(firstResolve.exists);
|
||||
ASSERT_TRUE(firstResolve.artifactReady);
|
||||
EXPECT_TRUE(fs::exists(projectRoot / "Assets" / "textured_triangle.obj.meta"));
|
||||
EXPECT_TRUE(fs::exists(projectRoot / "Library" / "SourceAssetDB" / "assets.db"));
|
||||
EXPECT_TRUE(fs::exists(projectRoot / "Library" / "ArtifactDB" / "artifacts.db"));
|
||||
EXPECT_TRUE(fs::exists(firstResolve.artifactMainPath.CStr()));
|
||||
EXPECT_TRUE(fs::exists((fs::path(firstResolve.artifactDirectory.CStr()) / "texture_0.xctex")));
|
||||
|
||||
AssetRef assetRef;
|
||||
ASSERT_TRUE(database.TryGetAssetRef("Assets/textured_triangle.obj", ResourceType::Mesh, assetRef));
|
||||
EXPECT_TRUE(assetRef.IsValid());
|
||||
|
||||
const auto originalArtifactWriteTime = fs::last_write_time(firstResolve.artifactMainPath.CStr());
|
||||
std::this_thread::sleep_for(50ms);
|
||||
|
||||
AssetDatabase::ResolvedAsset secondResolve;
|
||||
ASSERT_TRUE(database.EnsureArtifact("Assets/textured_triangle.obj", ResourceType::Mesh, secondResolve));
|
||||
EXPECT_EQ(firstResolve.artifactMainPath, secondResolve.artifactMainPath);
|
||||
EXPECT_EQ(originalArtifactWriteTime, fs::last_write_time(secondResolve.artifactMainPath.CStr()));
|
||||
|
||||
database.Shutdown();
|
||||
fs::remove_all(projectRoot);
|
||||
}
|
||||
|
||||
TEST(MeshLoader, ResourceManagerLoadsModelByAssetRefFromProjectAssets) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
ResourceManager& manager = ResourceManager::Get();
|
||||
manager.Initialize();
|
||||
|
||||
const fs::path projectRoot = fs::temp_directory_path() / "xc_mesh_asset_ref_test";
|
||||
const fs::path assetsDir = projectRoot / "Assets";
|
||||
|
||||
fs::remove_all(projectRoot);
|
||||
fs::create_directories(assetsDir);
|
||||
fs::copy_file(GetMeshFixturePath("textured_triangle.obj"),
|
||||
assetsDir / "textured_triangle.obj",
|
||||
fs::copy_options::overwrite_existing);
|
||||
fs::copy_file(GetMeshFixturePath("textured_triangle.mtl"),
|
||||
assetsDir / "textured_triangle.mtl",
|
||||
fs::copy_options::overwrite_existing);
|
||||
fs::copy_file(GetMeshFixturePath("checker.bmp"),
|
||||
assetsDir / "checker.bmp",
|
||||
fs::copy_options::overwrite_existing);
|
||||
|
||||
manager.SetResourceRoot(projectRoot.string().c_str());
|
||||
|
||||
const auto firstHandle = manager.Load<Mesh>("Assets/textured_triangle.obj");
|
||||
ASSERT_TRUE(firstHandle.IsValid());
|
||||
EXPECT_EQ(firstHandle->GetVertexCount(), 3u);
|
||||
EXPECT_EQ(firstHandle->GetIndexCount(), 3u);
|
||||
EXPECT_GE(firstHandle->GetMaterials().Size(), 1u);
|
||||
EXPECT_EQ(firstHandle->GetTextures().Size(), 1u);
|
||||
const auto initialMaterialCount = firstHandle->GetMaterials().Size();
|
||||
|
||||
AssetRef assetRef;
|
||||
ASSERT_TRUE(manager.TryGetAssetRef("Assets/textured_triangle.obj", ResourceType::Mesh, assetRef));
|
||||
EXPECT_TRUE(assetRef.IsValid());
|
||||
|
||||
manager.UnloadAll();
|
||||
|
||||
const auto secondHandle = manager.Load<Mesh>(assetRef);
|
||||
ASSERT_TRUE(secondHandle.IsValid());
|
||||
EXPECT_EQ(secondHandle->GetPath(), "Assets/textured_triangle.obj");
|
||||
EXPECT_EQ(secondHandle->GetVertexCount(), 3u);
|
||||
EXPECT_EQ(secondHandle->GetIndexCount(), 3u);
|
||||
EXPECT_EQ(secondHandle->GetMaterials().Size(), initialMaterialCount);
|
||||
EXPECT_EQ(secondHandle->GetTextures().Size(), 1u);
|
||||
|
||||
manager.SetResourceRoot("");
|
||||
manager.Shutdown();
|
||||
fs::remove_all(projectRoot);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <XCEngine/Core/Asset/AssetDatabase.h>
|
||||
#include <XCEngine/Core/Asset/AssetRef.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Resources/Texture/TextureLoader.h>
|
||||
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||||
#include <XCEngine/Core/Containers/Array.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <thread>
|
||||
|
||||
using namespace XCEngine::Resources;
|
||||
using namespace XCEngine::Containers;
|
||||
@@ -57,4 +64,90 @@ TEST(TextureLoader, LoadValidBmpTexture) {
|
||||
delete texture;
|
||||
}
|
||||
|
||||
TEST(TextureLoader, AssetDatabaseCreatesTextureArtifactAndReusesItWithoutReimport) {
|
||||
namespace fs = std::filesystem;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
const fs::path projectRoot = fs::temp_directory_path() / "xc_texture_library_cache_test";
|
||||
const fs::path assetsDir = projectRoot / "Assets";
|
||||
const fs::path texturePath = assetsDir / "checker.bmp";
|
||||
|
||||
fs::remove_all(projectRoot);
|
||||
fs::create_directories(assetsDir);
|
||||
fs::copy_file(GetTextureFixturePath("checker.bmp"), texturePath, fs::copy_options::overwrite_existing);
|
||||
|
||||
AssetDatabase database;
|
||||
database.Initialize(projectRoot.string().c_str());
|
||||
|
||||
AssetDatabase::ResolvedAsset firstResolve;
|
||||
ASSERT_TRUE(database.EnsureArtifact("Assets/checker.bmp", ResourceType::Texture, firstResolve));
|
||||
ASSERT_TRUE(firstResolve.exists);
|
||||
ASSERT_TRUE(firstResolve.artifactReady);
|
||||
EXPECT_TRUE(fs::exists(projectRoot / "Assets" / "checker.bmp.meta"));
|
||||
EXPECT_TRUE(fs::exists(projectRoot / "Library" / "SourceAssetDB" / "assets.db"));
|
||||
EXPECT_TRUE(fs::exists(projectRoot / "Library" / "ArtifactDB" / "artifacts.db"));
|
||||
EXPECT_TRUE(fs::exists(firstResolve.artifactMainPath.CStr()));
|
||||
|
||||
std::ifstream metaFile(projectRoot / "Assets" / "checker.bmp.meta");
|
||||
ASSERT_TRUE(metaFile.is_open());
|
||||
std::string metaText((std::istreambuf_iterator<char>(metaFile)), std::istreambuf_iterator<char>());
|
||||
EXPECT_NE(metaText.find("guid:"), std::string::npos);
|
||||
metaFile.close();
|
||||
|
||||
AssetRef assetRef;
|
||||
ASSERT_TRUE(database.TryGetAssetRef("Assets/checker.bmp", ResourceType::Texture, assetRef));
|
||||
EXPECT_TRUE(assetRef.IsValid());
|
||||
|
||||
const auto originalArtifactWriteTime = fs::last_write_time(firstResolve.artifactMainPath.CStr());
|
||||
std::this_thread::sleep_for(50ms);
|
||||
|
||||
AssetDatabase::ResolvedAsset secondResolve;
|
||||
ASSERT_TRUE(database.EnsureArtifact("Assets/checker.bmp", ResourceType::Texture, secondResolve));
|
||||
EXPECT_EQ(firstResolve.artifactMainPath, secondResolve.artifactMainPath);
|
||||
EXPECT_EQ(originalArtifactWriteTime, fs::last_write_time(secondResolve.artifactMainPath.CStr()));
|
||||
|
||||
database.Shutdown();
|
||||
fs::remove_all(projectRoot);
|
||||
}
|
||||
|
||||
TEST(TextureLoader, ResourceManagerLoadsTextureByAssetRefFromProjectAssets) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
ResourceManager& manager = ResourceManager::Get();
|
||||
manager.Initialize();
|
||||
|
||||
const fs::path projectRoot = fs::temp_directory_path() / "xc_texture_asset_ref_test";
|
||||
const fs::path assetsDir = projectRoot / "Assets";
|
||||
const fs::path texturePath = assetsDir / "checker.bmp";
|
||||
|
||||
fs::remove_all(projectRoot);
|
||||
fs::create_directories(assetsDir);
|
||||
fs::copy_file(GetTextureFixturePath("checker.bmp"), texturePath, fs::copy_options::overwrite_existing);
|
||||
|
||||
manager.SetResourceRoot(projectRoot.string().c_str());
|
||||
|
||||
{
|
||||
const auto firstHandle = manager.Load<Texture>("Assets/checker.bmp");
|
||||
ASSERT_TRUE(firstHandle.IsValid());
|
||||
EXPECT_EQ(firstHandle->GetWidth(), 2u);
|
||||
EXPECT_EQ(firstHandle->GetHeight(), 2u);
|
||||
|
||||
AssetRef assetRef;
|
||||
ASSERT_TRUE(manager.TryGetAssetRef("Assets/checker.bmp", ResourceType::Texture, assetRef));
|
||||
EXPECT_TRUE(assetRef.IsValid());
|
||||
|
||||
manager.UnloadAll();
|
||||
|
||||
const auto secondHandle = manager.Load<Texture>(assetRef);
|
||||
ASSERT_TRUE(secondHandle.IsValid());
|
||||
EXPECT_EQ(secondHandle->GetWidth(), 2u);
|
||||
EXPECT_EQ(secondHandle->GetHeight(), 2u);
|
||||
EXPECT_EQ(secondHandle->GetPath(), "Assets/checker.bmp");
|
||||
}
|
||||
|
||||
manager.SetResourceRoot("");
|
||||
manager.Shutdown();
|
||||
fs::remove_all(projectRoot);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Reference in New Issue
Block a user