Add backpack editor startup scene

This commit is contained in:
2026-03-28 19:26:08 +08:00
parent a519fdab7d
commit eb5de3e3d4
16 changed files with 471 additions and 39 deletions

View File

@@ -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
)

View File

@@ -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";

View File

@@ -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")

View File

@@ -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() {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 MiB

View 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 MiB

View 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

View File

@@ -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");

View File

@@ -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");

View File

@@ -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

View File

@@ -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,