Add backpack editor startup scene
This commit is contained in:
@@ -53,6 +53,7 @@ add_executable(${PROJECT_NAME} WIN32
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Viewport
|
||||
${imgui_SOURCE_DIR}
|
||||
${imgui_SOURCE_DIR}/backends
|
||||
)
|
||||
@@ -79,3 +80,9 @@ set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
OUTPUT_NAME "XCEngine"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin"
|
||||
)
|
||||
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
${CMAKE_SOURCE_DIR}/engine/third_party/assimp/bin/assimp-vc143-mt.dll
|
||||
$<TARGET_FILE_DIR:${PROJECT_NAME}>/assimp-vc143-mt.dll
|
||||
)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "ProjectManager.h"
|
||||
#include <filesystem>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cwctype>
|
||||
#include <fstream>
|
||||
#include <windows.h>
|
||||
@@ -12,6 +13,24 @@ namespace Editor {
|
||||
|
||||
namespace {
|
||||
|
||||
std::wstring Utf8PathToWstring(const std::string& str) {
|
||||
if (str.empty()) return L"";
|
||||
int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
|
||||
if (len <= 0) return L"";
|
||||
std::wstring result(len - 1, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &result[0], len);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string WstringPathToUtf8(const std::wstring& wstr) {
|
||||
if (wstr.empty()) return "";
|
||||
int len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
||||
if (len <= 0) return "";
|
||||
std::string result(len - 1, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &result[0], len, nullptr, nullptr);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::wstring MakePathKey(const fs::path& path) {
|
||||
std::wstring key = path.lexically_normal().generic_wstring();
|
||||
std::transform(key.begin(), key.end(), key.begin(), ::towlower);
|
||||
@@ -34,6 +53,92 @@ bool IsSameOrDescendantPath(const fs::path& path, const fs::path& ancestor) {
|
||||
return pathKey.rfind(ancestorKey, 0) == 0;
|
||||
}
|
||||
|
||||
std::string TrimAssetName(const std::string& name) {
|
||||
const auto first = std::find_if_not(name.begin(), name.end(), [](unsigned char ch) {
|
||||
return std::isspace(ch) != 0;
|
||||
});
|
||||
if (first == name.end()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto last = std::find_if_not(name.rbegin(), name.rend(), [](unsigned char ch) {
|
||||
return std::isspace(ch) != 0;
|
||||
}).base();
|
||||
return std::string(first, last);
|
||||
}
|
||||
|
||||
bool HasInvalidAssetName(const std::string& name) {
|
||||
if (name.empty() || name == "." || name == "..") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return name.find_first_of("\\/:*?\"<>|") != std::string::npos;
|
||||
}
|
||||
|
||||
fs::path MakeUniqueFolderPath(const fs::path& parentPath, const std::string& preferredName) {
|
||||
fs::path candidatePath = parentPath / Utf8PathToWstring(preferredName);
|
||||
if (!fs::exists(candidatePath)) {
|
||||
return candidatePath;
|
||||
}
|
||||
|
||||
for (size_t suffix = 1;; ++suffix) {
|
||||
candidatePath = parentPath / Utf8PathToWstring(preferredName + " " + std::to_string(suffix));
|
||||
if (!fs::exists(candidatePath)) {
|
||||
return candidatePath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string BuildRenamedEntryName(const fs::path& sourcePath, const std::string& requestedName) {
|
||||
if (fs::is_directory(sourcePath)) {
|
||||
return requestedName;
|
||||
}
|
||||
|
||||
const fs::path requestedPath = Utf8PathToWstring(requestedName);
|
||||
if (requestedPath.has_extension()) {
|
||||
return WstringPathToUtf8(requestedPath.filename().wstring());
|
||||
}
|
||||
|
||||
return requestedName + WstringPathToUtf8(sourcePath.extension().wstring());
|
||||
}
|
||||
|
||||
fs::path MakeCaseOnlyRenameTempPath(const fs::path& sourcePath) {
|
||||
const fs::path parentPath = sourcePath.parent_path();
|
||||
const std::wstring sourceStem = sourcePath.filename().wstring();
|
||||
|
||||
for (size_t suffix = 0;; ++suffix) {
|
||||
std::wstring tempName = sourceStem + L".xc_tmp_rename";
|
||||
if (suffix > 0) {
|
||||
tempName += std::to_wstring(suffix);
|
||||
}
|
||||
|
||||
const fs::path tempPath = parentPath / tempName;
|
||||
if (!fs::exists(tempPath)) {
|
||||
return tempPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RenamePathCaseAware(const fs::path& sourcePath, const fs::path& destPath) {
|
||||
if (MakePathKey(sourcePath) != MakePathKey(destPath)) {
|
||||
if (fs::exists(destPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fs::rename(sourcePath, destPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (sourcePath.filename() == destPath.filename()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const fs::path tempPath = MakeCaseOnlyRenameTempPath(sourcePath);
|
||||
fs::rename(sourcePath, tempPath);
|
||||
fs::rename(tempPath, destPath);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const std::vector<AssetItemPtr>& ProjectManager::GetCurrentItems() const {
|
||||
@@ -134,28 +239,10 @@ std::string ProjectManager::GetPathName(size_t index) const {
|
||||
return m_path[index]->name;
|
||||
}
|
||||
|
||||
static std::wstring Utf8ToWstring(const std::string& str) {
|
||||
if (str.empty()) return L"";
|
||||
int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
|
||||
if (len <= 0) return L"";
|
||||
std::wstring result(len - 1, 0);
|
||||
MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &result[0], len);
|
||||
return result;
|
||||
}
|
||||
|
||||
static std::string WstringToUtf8(const std::wstring& wstr) {
|
||||
if (wstr.empty()) return "";
|
||||
int len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, nullptr, 0, nullptr, nullptr);
|
||||
if (len <= 0) return "";
|
||||
std::string result(len - 1, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), -1, &result[0], len, nullptr, nullptr);
|
||||
return result;
|
||||
}
|
||||
|
||||
void ProjectManager::Initialize(const std::string& projectPath) {
|
||||
m_projectPath = projectPath;
|
||||
|
||||
std::wstring projectPathW = Utf8ToWstring(projectPath);
|
||||
std::wstring projectPathW = Utf8PathToWstring(projectPath);
|
||||
fs::path assetsPath = fs::path(projectPathW) / L"Assets";
|
||||
|
||||
try {
|
||||
@@ -166,7 +253,7 @@ void ProjectManager::Initialize(const std::string& projectPath) {
|
||||
|
||||
m_rootFolder = ScanDirectory(assetsPath.wstring());
|
||||
m_rootFolder->name = "Assets";
|
||||
m_rootFolder->fullPath = WstringToUtf8(assetsPath.wstring());
|
||||
m_rootFolder->fullPath = WstringPathToUtf8(assetsPath.wstring());
|
||||
|
||||
m_path.clear();
|
||||
m_path.push_back(m_rootFolder);
|
||||
@@ -176,7 +263,7 @@ void ProjectManager::Initialize(const std::string& projectPath) {
|
||||
m_rootFolder->name = "Assets";
|
||||
m_rootFolder->isFolder = true;
|
||||
m_rootFolder->type = "Folder";
|
||||
m_rootFolder->fullPath = WstringToUtf8(assetsPath.wstring());
|
||||
m_rootFolder->fullPath = WstringPathToUtf8(assetsPath.wstring());
|
||||
m_path.clear();
|
||||
m_path.push_back(m_rootFolder);
|
||||
ClearSelection();
|
||||
@@ -185,10 +272,10 @@ void ProjectManager::Initialize(const std::string& projectPath) {
|
||||
|
||||
std::wstring ProjectManager::GetCurrentFullPathW() const {
|
||||
if (AssetItemPtr currentFolder = GetCurrentFolder()) {
|
||||
return Utf8ToWstring(currentFolder->fullPath);
|
||||
return Utf8PathToWstring(currentFolder->fullPath);
|
||||
}
|
||||
|
||||
return Utf8ToWstring(m_projectPath);
|
||||
return Utf8PathToWstring(m_projectPath);
|
||||
}
|
||||
|
||||
void ProjectManager::RefreshCurrentFolder() {
|
||||
@@ -200,13 +287,31 @@ void ProjectManager::RefreshCurrentFolder() {
|
||||
}
|
||||
}
|
||||
|
||||
void ProjectManager::CreateFolder(const std::string& name) {
|
||||
AssetItemPtr ProjectManager::CreateFolder(const std::string& name) {
|
||||
if (!m_rootFolder) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
try {
|
||||
std::wstring fullPath = GetCurrentFullPathW();
|
||||
fs::path newFolderPath = fs::path(fullPath) / Utf8ToWstring(name);
|
||||
fs::create_directory(newFolderPath);
|
||||
const std::string trimmedName = TrimAssetName(name);
|
||||
if (HasInvalidAssetName(trimmedName)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const fs::path currentFolderPath = GetCurrentFullPathW();
|
||||
const fs::path newFolderPath = MakeUniqueFolderPath(currentFolderPath, trimmedName);
|
||||
if (!fs::create_directory(newFolderPath)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RefreshCurrentFolder();
|
||||
const AssetItemPtr createdFolder = FindCurrentItemByPath(WstringPathToUtf8(newFolderPath.wstring()));
|
||||
if (createdFolder) {
|
||||
SetSelectedItem(createdFolder);
|
||||
}
|
||||
return createdFolder;
|
||||
} catch (...) {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,8 +321,8 @@ bool ProjectManager::DeleteItem(const std::string& fullPath) {
|
||||
}
|
||||
|
||||
try {
|
||||
const fs::path itemPath = Utf8ToWstring(fullPath);
|
||||
const fs::path rootPath = Utf8ToWstring(m_rootFolder->fullPath);
|
||||
const fs::path itemPath = Utf8PathToWstring(fullPath);
|
||||
const fs::path rootPath = Utf8PathToWstring(m_rootFolder->fullPath);
|
||||
if (!fs::exists(itemPath) || !IsSameOrDescendantPath(itemPath, rootPath)) {
|
||||
return false;
|
||||
}
|
||||
@@ -242,9 +347,9 @@ bool ProjectManager::MoveItem(const std::string& sourceFullPath, const std::stri
|
||||
}
|
||||
|
||||
try {
|
||||
const fs::path sourcePath = Utf8ToWstring(sourceFullPath);
|
||||
const fs::path destFolderPath = Utf8ToWstring(destFolderFullPath);
|
||||
const fs::path rootPath = Utf8ToWstring(m_rootFolder->fullPath);
|
||||
const fs::path sourcePath = Utf8PathToWstring(sourceFullPath);
|
||||
const fs::path destFolderPath = Utf8PathToWstring(destFolderFullPath);
|
||||
const fs::path rootPath = Utf8PathToWstring(m_rootFolder->fullPath);
|
||||
|
||||
if (!fs::exists(sourcePath) || !fs::exists(destFolderPath) || !fs::is_directory(destFolderPath)) {
|
||||
return false;
|
||||
@@ -272,6 +377,49 @@ bool ProjectManager::MoveItem(const std::string& sourceFullPath, const std::stri
|
||||
}
|
||||
}
|
||||
|
||||
bool ProjectManager::RenameItem(const std::string& sourceFullPath, const std::string& newName) {
|
||||
if (sourceFullPath.empty() || !m_rootFolder) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const std::string trimmedName = TrimAssetName(newName);
|
||||
if (HasInvalidAssetName(trimmedName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fs::path sourcePath = Utf8PathToWstring(sourceFullPath);
|
||||
const fs::path rootPath = Utf8PathToWstring(m_rootFolder->fullPath);
|
||||
if (!fs::exists(sourcePath) || !IsSameOrDescendantPath(sourcePath, rootPath)) {
|
||||
return false;
|
||||
}
|
||||
if (MakePathKey(sourcePath) == MakePathKey(rootPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string targetName = BuildRenamedEntryName(sourcePath, trimmedName);
|
||||
if (HasInvalidAssetName(targetName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fs::path destPath = sourcePath.parent_path() / Utf8PathToWstring(targetName);
|
||||
if (!RenamePathCaseAware(sourcePath, destPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!m_selectedItemPath.empty() &&
|
||||
MakePathKey(Utf8PathToWstring(m_selectedItemPath)) == MakePathKey(sourcePath))
|
||||
{
|
||||
m_selectedItemPath = WstringPathToUtf8(destPath.wstring());
|
||||
}
|
||||
|
||||
RefreshCurrentFolder();
|
||||
return true;
|
||||
} catch (...) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
AssetItemPtr ProjectManager::FindCurrentItemByPath(const std::string& fullPath) const {
|
||||
const int index = FindCurrentItemIndex(fullPath);
|
||||
if (index < 0) {
|
||||
@@ -289,10 +437,10 @@ void ProjectManager::SyncSelection() {
|
||||
|
||||
AssetItemPtr ProjectManager::ScanDirectory(const std::wstring& path) {
|
||||
auto folder = std::make_shared<AssetItem>();
|
||||
folder->name = WstringToUtf8(fs::path(path).filename().wstring());
|
||||
folder->name = WstringPathToUtf8(fs::path(path).filename().wstring());
|
||||
folder->isFolder = true;
|
||||
folder->type = "Folder";
|
||||
folder->fullPath = WstringToUtf8(path);
|
||||
folder->fullPath = WstringPathToUtf8(path);
|
||||
|
||||
if (!fs::exists(path)) return folder;
|
||||
|
||||
@@ -352,10 +500,10 @@ void ProjectManager::RebuildTreePreservingPath() {
|
||||
}
|
||||
}
|
||||
|
||||
const fs::path assetsPath = fs::path(Utf8ToWstring(m_projectPath)) / L"Assets";
|
||||
const fs::path assetsPath = fs::path(Utf8PathToWstring(m_projectPath)) / L"Assets";
|
||||
m_rootFolder = ScanDirectory(assetsPath.wstring());
|
||||
m_rootFolder->name = "Assets";
|
||||
m_rootFolder->fullPath = WstringToUtf8(assetsPath.wstring());
|
||||
m_rootFolder->fullPath = WstringPathToUtf8(assetsPath.wstring());
|
||||
|
||||
m_path.clear();
|
||||
m_path.push_back(m_rootFolder);
|
||||
@@ -373,9 +521,9 @@ void ProjectManager::RebuildTreePreservingPath() {
|
||||
|
||||
AssetItemPtr ProjectManager::CreateAssetItem(const std::wstring& path, const std::wstring& nameW, bool isFolder) {
|
||||
auto item = std::make_shared<AssetItem>();
|
||||
item->name = WstringToUtf8(nameW);
|
||||
item->name = WstringPathToUtf8(nameW);
|
||||
item->isFolder = isFolder;
|
||||
item->fullPath = WstringToUtf8(path);
|
||||
item->fullPath = WstringPathToUtf8(path);
|
||||
|
||||
if (isFolder) {
|
||||
item->type = "Folder";
|
||||
@@ -388,7 +536,7 @@ AssetItemPtr ProjectManager::CreateAssetItem(const std::wstring& path, const std
|
||||
std::wstring ext = fs::path(path).extension().wstring();
|
||||
std::transform(ext.begin(), ext.end(), ext.begin(), ::towlower);
|
||||
|
||||
if (ext == L".png" || ext == L".jpg" || ext == L".tga" || ext == L".bmp") {
|
||||
if (ext == L".png" || ext == L".jpg" || ext == L".jpeg" || ext == L".tga" || ext == L".bmp") {
|
||||
item->type = "Texture";
|
||||
} else if (ext == L".fbx" || ext == L".obj" || ext == L".gltf" || ext == L".glb") {
|
||||
item->type = "Model";
|
||||
|
||||
@@ -435,6 +435,11 @@ target_link_libraries(XCEngine PUBLIC
|
||||
Vulkan::Vulkan
|
||||
)
|
||||
|
||||
if(MSVC)
|
||||
target_link_libraries(XCEngine PUBLIC delayimp)
|
||||
target_link_options(XCEngine INTERFACE "/DELAYLOAD:assimp-vc143-mt.dll")
|
||||
endif()
|
||||
|
||||
if(XCENGINE_ENABLE_MONO_SCRIPTING)
|
||||
set(XCENGINE_MONO_INCLUDE_DIR "${XCENGINE_MONO_ROOT_DIR}/include")
|
||||
set(XCENGINE_MONO_STATIC_LIBRARY "${XCENGINE_MONO_ROOT_DIR}/lib/libmono-static-sgen.lib")
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
#include <XCEngine/Core/Asset/ResourceHandle.h>
|
||||
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||||
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
||||
#include <XCEngine/Resources/Mesh/MeshLoader.h>
|
||||
#include <XCEngine/Resources/Shader/ShaderLoader.h>
|
||||
#include <XCEngine/Resources/Texture/TextureLoader.h>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
@@ -17,7 +19,9 @@ void RegisterBuiltinLoader(ResourceManager& manager, TLoader& loader) {
|
||||
}
|
||||
|
||||
MaterialLoader g_materialLoader;
|
||||
MeshLoader g_meshLoader;
|
||||
ShaderLoader g_shaderLoader;
|
||||
TextureLoader g_textureLoader;
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -31,7 +35,9 @@ void ResourceManager::Initialize() {
|
||||
m_asyncLoader->Initialize(2);
|
||||
|
||||
RegisterBuiltinLoader(*this, g_materialLoader);
|
||||
RegisterBuiltinLoader(*this, g_meshLoader);
|
||||
RegisterBuiltinLoader(*this, g_shaderLoader);
|
||||
RegisterBuiltinLoader(*this, g_textureLoader);
|
||||
}
|
||||
|
||||
void ResourceManager::Shutdown() {
|
||||
|
||||
BIN
project/Assets/Models/backpack/ao.jpg
Normal file
BIN
project/Assets/Models/backpack/ao.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
16
project/Assets/Models/backpack/backpack.mtl
Normal file
16
project/Assets/Models/backpack/backpack.mtl
Normal file
@@ -0,0 +1,16 @@
|
||||
# Blender MTL File: 'None'
|
||||
# Material Count: 1
|
||||
|
||||
newmtl Scene_-_Root
|
||||
Ns 225.000000
|
||||
Ka 1.000000 1.000000 1.000000
|
||||
Kd 0.800000 0.800000 0.800000
|
||||
Ks 0.500000 0.500000 0.500000
|
||||
Ke 0.0 0.0 0.0
|
||||
Ni 1.450000
|
||||
d 1.000000
|
||||
illum 2
|
||||
map_Kd diffuse.jpg
|
||||
map_Bump normal.png
|
||||
map_Ks specular.jpg
|
||||
|
||||
BIN
project/Assets/Models/backpack/diffuse.jpg
Normal file
BIN
project/Assets/Models/backpack/diffuse.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.8 MiB |
BIN
project/Assets/Models/backpack/normal.png
Normal file
BIN
project/Assets/Models/backpack/normal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 MiB |
BIN
project/Assets/Models/backpack/roughness.jpg
Normal file
BIN
project/Assets/Models/backpack/roughness.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 MiB |
3
project/Assets/Models/backpack/source_attribution.txt
Normal file
3
project/Assets/Models/backpack/source_attribution.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Model by Berk Gedik, from: https://sketchfab.com/3d-models/survival-guitar-backpack-low-poly-799f8c4511f84fab8c3f12887f7e6b36
|
||||
|
||||
Modified material assignment (Joey de Vries) for easier load in OpenGL model loading chapter, and renamed albedo to diffuse and metallic to specular to match non-PBR lighting setup.
|
||||
BIN
project/Assets/Models/backpack/specular.jpg
Normal file
BIN
project/Assets/Models/backpack/specular.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.4 MiB |
61
project/Assets/Scenes/Main.xc
Normal file
61
project/Assets/Scenes/Main.xc
Normal file
@@ -0,0 +1,61 @@
|
||||
# XCEngine Scene File
|
||||
scene=Main Scene
|
||||
active=1
|
||||
|
||||
gameobject_begin
|
||||
id=1
|
||||
uuid=11806343893082442755
|
||||
name=Main Camera
|
||||
active=1
|
||||
parent=0
|
||||
transform=position=0,0,0;rotation=0,0,0,1;scale=1,1,1;
|
||||
component=Camera;projection=0;fov=45;orthoSize=5;near=0.1;far=100;depth=0;primary=1;clearColor=0.05,0.05,0.08,1;
|
||||
gameobject_end
|
||||
|
||||
gameobject_begin
|
||||
id=2
|
||||
uuid=1695101543211549096
|
||||
name=BackpackRoot
|
||||
active=1
|
||||
parent=0
|
||||
transform=position=0,0.08,3;rotation=0,0,0,1;scale=1,1,1;
|
||||
gameobject_end
|
||||
|
||||
gameobject_begin
|
||||
id=3
|
||||
uuid=9855370671540411784
|
||||
name=BackpackRotateY
|
||||
active=1
|
||||
parent=2
|
||||
transform=position=0,0,0;rotation=0,-0.174108138,0,0.984726539;scale=1,1,1;
|
||||
gameobject_end
|
||||
|
||||
gameobject_begin
|
||||
id=4
|
||||
uuid=14568936532392398358
|
||||
name=BackpackRotateX
|
||||
active=1
|
||||
parent=3
|
||||
transform=position=0,0,0;rotation=-0.0898785492,0,0,0.995952733;scale=1,1,1;
|
||||
gameobject_end
|
||||
|
||||
gameobject_begin
|
||||
id=5
|
||||
uuid=7319598685716776031
|
||||
name=BackpackScale
|
||||
active=1
|
||||
parent=4
|
||||
transform=position=0,0,0;rotation=0,0,0,1;scale=0.389120452,0.389120452,0.389120452;
|
||||
gameobject_end
|
||||
|
||||
gameobject_begin
|
||||
id=6
|
||||
uuid=14495577888798577643
|
||||
name=BackpackMesh
|
||||
active=1
|
||||
parent=5
|
||||
transform=position=0.048938,-0.5718905,-0.943127;rotation=0,0,0,1;scale=1,1,1;
|
||||
component=MeshFilter;mesh=Assets/Models/backpack/backpack.obj;
|
||||
component=MeshRenderer;materials=;castShadows=1;receiveShadows=1;renderLayer=0;
|
||||
gameobject_end
|
||||
|
||||
@@ -247,6 +247,40 @@ TEST(RenderSceneExtractor_Test, SortsOpaqueFrontToBackAndTransparentBackToFront)
|
||||
delete transparentFarMesh;
|
||||
}
|
||||
|
||||
TEST(RenderSceneExtractor_Test, FallsBackToEmbeddedMeshMaterialsWhenRendererHasNoExplicitSlots) {
|
||||
Scene scene("EmbeddedMaterialScene");
|
||||
|
||||
GameObject* cameraObject = scene.CreateGameObject("Camera");
|
||||
auto* camera = cameraObject->AddComponent<CameraComponent>();
|
||||
camera->SetPrimary(true);
|
||||
|
||||
GameObject* renderObject = scene.CreateGameObject("EmbeddedBackpack");
|
||||
auto* meshFilter = renderObject->AddComponent<MeshFilterComponent>();
|
||||
auto* meshRenderer = renderObject->AddComponent<MeshRendererComponent>();
|
||||
|
||||
Mesh* mesh = CreateSectionedTestMesh("Meshes/embedded.mesh", { 0u });
|
||||
Material* embeddedMaterial = CreateTestMaterial(
|
||||
"Materials/embedded.mat",
|
||||
static_cast<int32_t>(MaterialRenderQueue::Transparent),
|
||||
"ForwardLit",
|
||||
"ForwardBase");
|
||||
mesh->AddMaterial(embeddedMaterial);
|
||||
meshFilter->SetMesh(mesh);
|
||||
meshRenderer->ClearMaterials();
|
||||
|
||||
RenderSceneExtractor extractor;
|
||||
const RenderSceneData sceneData = extractor.Extract(scene, nullptr, 800, 600);
|
||||
|
||||
ASSERT_EQ(sceneData.visibleItems.size(), 1u);
|
||||
EXPECT_EQ(sceneData.visibleItems[0].material, embeddedMaterial);
|
||||
EXPECT_EQ(sceneData.visibleItems[0].renderQueue, static_cast<int32_t>(MaterialRenderQueue::Transparent));
|
||||
EXPECT_EQ(ResolveMaterial(sceneData.visibleItems[0]), embeddedMaterial);
|
||||
|
||||
meshFilter->ClearMesh();
|
||||
delete embeddedMaterial;
|
||||
delete mesh;
|
||||
}
|
||||
|
||||
TEST(RenderMaterialUtility_Test, MatchesBuiltinForwardPassMetadata) {
|
||||
Material forwardMaterial;
|
||||
forwardMaterial.SetShaderPass("ForwardLit");
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Resources/Mesh/MeshLoader.h>
|
||||
#include <XCEngine/Resources/Mesh/MeshImportSettings.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
@@ -43,6 +44,16 @@ TEST(MeshLoader, LoadInvalidPath) {
|
||||
EXPECT_FALSE(result);
|
||||
}
|
||||
|
||||
TEST(MeshLoader, ResourceManagerRegistersMeshAndTextureLoaders) {
|
||||
ResourceManager& manager = ResourceManager::Get();
|
||||
manager.Initialize();
|
||||
|
||||
EXPECT_NE(manager.GetLoader(ResourceType::Mesh), nullptr);
|
||||
EXPECT_NE(manager.GetLoader(ResourceType::Texture), nullptr);
|
||||
|
||||
manager.Shutdown();
|
||||
}
|
||||
|
||||
TEST(MeshLoader, LoadValidObjMesh) {
|
||||
MeshLoader loader;
|
||||
const std::string path = GetMeshFixturePath("triangle.obj");
|
||||
|
||||
@@ -5,17 +5,32 @@
|
||||
#include <XCEngine/Components/CameraComponent.h>
|
||||
#include <XCEngine/Components/GameObject.h>
|
||||
#include <XCEngine/Components/LightComponent.h>
|
||||
#include <XCEngine/Components/MeshFilterComponent.h>
|
||||
#include <XCEngine/Components/MeshRendererComponent.h>
|
||||
#include <XCEngine/Components/TransformComponent.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Math/Vector3.h>
|
||||
#include <XCEngine/Resources/Mesh/MeshLoader.h>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
using namespace XCEngine::Components;
|
||||
using namespace XCEngine::Math;
|
||||
|
||||
namespace {
|
||||
|
||||
std::filesystem::path GetRepositoryRoot() {
|
||||
return std::filesystem::path(__FILE__).parent_path().parent_path().parent_path();
|
||||
}
|
||||
|
||||
class TestComponent : public Component {
|
||||
public:
|
||||
TestComponent() = default;
|
||||
@@ -505,4 +520,118 @@ TEST_F(SceneTest, Save_ContainsHierarchyAndComponentEntries) {
|
||||
std::filesystem::remove(scenePath);
|
||||
}
|
||||
|
||||
TEST_F(SceneTest, SaveAndLoad_PreservesMeshComponentPaths) {
|
||||
GameObject* meshObject = testScene->CreateGameObject("Backpack");
|
||||
auto* meshFilter = meshObject->AddComponent<MeshFilterComponent>();
|
||||
auto* meshRenderer = meshObject->AddComponent<MeshRendererComponent>();
|
||||
meshFilter->SetMeshPath("Assets/Models/backpack/backpack.obj");
|
||||
meshRenderer->SetMaterialPath(0, "Assets/Materials/backpack.mat");
|
||||
meshRenderer->SetCastShadows(false);
|
||||
meshRenderer->SetReceiveShadows(true);
|
||||
meshRenderer->SetRenderLayer(4);
|
||||
|
||||
const std::filesystem::path scenePath = GetTempScenePath("test_scene_mesh_components.xc");
|
||||
testScene->Save(scenePath.string());
|
||||
|
||||
Scene loadedScene;
|
||||
loadedScene.Load(scenePath.string());
|
||||
|
||||
GameObject* loadedObject = loadedScene.Find("Backpack");
|
||||
ASSERT_NE(loadedObject, nullptr);
|
||||
|
||||
auto* loadedMeshFilter = loadedObject->GetComponent<MeshFilterComponent>();
|
||||
auto* loadedMeshRenderer = loadedObject->GetComponent<MeshRendererComponent>();
|
||||
ASSERT_NE(loadedMeshFilter, nullptr);
|
||||
ASSERT_NE(loadedMeshRenderer, nullptr);
|
||||
|
||||
EXPECT_EQ(loadedMeshFilter->GetMeshPath(), "Assets/Models/backpack/backpack.obj");
|
||||
ASSERT_EQ(loadedMeshRenderer->GetMaterialCount(), 1u);
|
||||
EXPECT_EQ(loadedMeshRenderer->GetMaterialPath(0), "Assets/Materials/backpack.mat");
|
||||
EXPECT_FALSE(loadedMeshRenderer->GetCastShadows());
|
||||
EXPECT_TRUE(loadedMeshRenderer->GetReceiveShadows());
|
||||
EXPECT_EQ(loadedMeshRenderer->GetRenderLayer(), 4u);
|
||||
|
||||
std::filesystem::remove(scenePath);
|
||||
}
|
||||
|
||||
TEST(Scene_ProjectSample, MainSceneLoadsBackpackMeshAsset) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
XCEngine::Resources::ResourceManager& resourceManager = XCEngine::Resources::ResourceManager::Get();
|
||||
resourceManager.Initialize();
|
||||
|
||||
struct ResourceManagerGuard {
|
||||
XCEngine::Resources::ResourceManager* manager = nullptr;
|
||||
~ResourceManagerGuard() {
|
||||
if (manager != nullptr) {
|
||||
manager->Shutdown();
|
||||
}
|
||||
}
|
||||
} resourceManagerGuard{ &resourceManager };
|
||||
|
||||
struct CurrentPathGuard {
|
||||
fs::path previousPath;
|
||||
~CurrentPathGuard() {
|
||||
if (!previousPath.empty()) {
|
||||
fs::current_path(previousPath);
|
||||
}
|
||||
}
|
||||
} currentPathGuard{ fs::current_path() };
|
||||
|
||||
const fs::path repositoryRoot = GetRepositoryRoot();
|
||||
const fs::path projectRoot = repositoryRoot / "project";
|
||||
const fs::path mainScenePath = projectRoot / "Assets" / "Scenes" / "Main.xc";
|
||||
const fs::path assimpDllPath = repositoryRoot / "engine" / "third_party" / "assimp" / "bin" / "assimp-vc143-mt.dll";
|
||||
const fs::path backpackMeshPath = projectRoot / "Assets" / "Models" / "backpack" / "backpack.obj";
|
||||
|
||||
ASSERT_TRUE(fs::exists(mainScenePath));
|
||||
ASSERT_TRUE(fs::exists(assimpDllPath));
|
||||
ASSERT_TRUE(fs::exists(backpackMeshPath));
|
||||
|
||||
#ifdef _WIN32
|
||||
struct DllGuard {
|
||||
HMODULE module = nullptr;
|
||||
~DllGuard() {
|
||||
if (module != nullptr) {
|
||||
FreeLibrary(module);
|
||||
}
|
||||
}
|
||||
} dllGuard;
|
||||
dllGuard.module = LoadLibraryW(assimpDllPath.wstring().c_str());
|
||||
ASSERT_NE(dllGuard.module, nullptr);
|
||||
#endif
|
||||
|
||||
fs::current_path(projectRoot);
|
||||
|
||||
ASSERT_NE(resourceManager.GetLoader(XCEngine::Resources::ResourceType::Mesh), nullptr);
|
||||
XCEngine::Resources::MeshLoader meshLoader;
|
||||
const XCEngine::Resources::LoadResult directMeshLoadResult =
|
||||
meshLoader.Load("Assets/Models/backpack/backpack.obj");
|
||||
ASSERT_TRUE(directMeshLoadResult)
|
||||
<< "MeshLoader failed: " << directMeshLoadResult.errorMessage.CStr();
|
||||
|
||||
const auto directMeshHandle =
|
||||
resourceManager.Load<XCEngine::Resources::Mesh>("Assets/Models/backpack/backpack.obj");
|
||||
ASSERT_NE(directMeshHandle.Get(), nullptr);
|
||||
ASSERT_TRUE(directMeshHandle->IsValid());
|
||||
ASSERT_GT(directMeshHandle->GetVertexCount(), 0u);
|
||||
|
||||
Scene loadedScene;
|
||||
loadedScene.Load(mainScenePath.string());
|
||||
|
||||
GameObject* backpackObject = loadedScene.Find("BackpackMesh");
|
||||
ASSERT_NE(backpackObject, nullptr);
|
||||
|
||||
auto* meshFilter = backpackObject->GetComponent<MeshFilterComponent>();
|
||||
auto* meshRenderer = backpackObject->GetComponent<MeshRendererComponent>();
|
||||
ASSERT_NE(meshFilter, nullptr);
|
||||
ASSERT_NE(meshRenderer, nullptr);
|
||||
ASSERT_NE(meshFilter->GetMesh(), nullptr);
|
||||
EXPECT_TRUE(meshFilter->GetMesh()->IsValid());
|
||||
EXPECT_GT(meshFilter->GetMesh()->GetVertexCount(), 0u);
|
||||
EXPECT_GT(meshFilter->GetMesh()->GetSections().Size(), 0u);
|
||||
EXPECT_GT(meshFilter->GetMesh()->GetMaterials().Size(), 0u);
|
||||
EXPECT_EQ(meshFilter->GetMeshPath(), "Assets/Models/backpack/backpack.obj");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -97,6 +97,18 @@ public:
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TryGetClassFieldDefaultValues(
|
||||
const std::string& assemblyName,
|
||||
const std::string& namespaceName,
|
||||
const std::string& className,
|
||||
std::vector<ScriptFieldDefaultValue>& outFields) const override {
|
||||
(void)assemblyName;
|
||||
(void)namespaceName;
|
||||
(void)className;
|
||||
outFields.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool TrySetManagedFieldValue(
|
||||
const ScriptRuntimeContext& context,
|
||||
const std::string& fieldName,
|
||||
|
||||
Reference in New Issue
Block a user