feat(build): implement cooked player packaging flow
This commit is contained in:
parent
9e2c2a8954
commit
db78bed499
@ -267,7 +267,9 @@ public:
|
||||
openButton->setObjectName("smallButton");
|
||||
openButton->setFixedWidth(64);
|
||||
connect(openButton, &QPushButton::clicked, this, [this]() {
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(ToQString(Project_.RootPath)));
|
||||
if (OnOpen) {
|
||||
OnOpen();
|
||||
}
|
||||
});
|
||||
rootLayout->addWidget(openButton);
|
||||
}
|
||||
@ -297,6 +299,7 @@ private:
|
||||
|
||||
public:
|
||||
std::function<void()> OnSelected{};
|
||||
std::function<void()> OnOpen{};
|
||||
|
||||
private:
|
||||
LauncherProject Project_{};
|
||||
@ -748,6 +751,10 @@ private:
|
||||
SelectedProjectIndex_ = index;
|
||||
RefreshProjectList();
|
||||
};
|
||||
card->OnOpen = [this, index]() {
|
||||
SelectedProjectIndex_ = index;
|
||||
LaunchSelectedProject();
|
||||
};
|
||||
ProjectListLayout_->addWidget(card);
|
||||
}
|
||||
ProjectListLayout_->addStretch(1);
|
||||
@ -779,7 +786,7 @@ private:
|
||||
SelectedProjectIndex_ = 0;
|
||||
SaveProjects();
|
||||
RefreshProjectList();
|
||||
ShowStatus("Created " + dialog.ProjectName());
|
||||
LaunchSelectedProject();
|
||||
}
|
||||
|
||||
void RemoveSelectedProject() {
|
||||
@ -844,6 +851,7 @@ private:
|
||||
|
||||
QProcess process;
|
||||
process.setProgram(ToQString(editorPath));
|
||||
process.setArguments(QStringList{ "--project", ToQString(project.RootPath) });
|
||||
process.setWorkingDirectory(ToQString(ExecutableDirectory_));
|
||||
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
|
||||
environment.insert("METACORE_PROJECT_PATH", ToQString(project.RootPath));
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
#include "MetaCoreFoundation/MetaCoreGeneratedReflection.h"
|
||||
#include "MetaCoreFoundation/MetaCorePackage.h"
|
||||
#include "MetaCoreFoundation/MetaCoreProject.h"
|
||||
#include "MetaCoreRender/MetaCoreEditorViewportRenderer.h"
|
||||
#include "MetaCoreRender/MetaCorePlayerRenderer.h"
|
||||
#include "MetaCoreRender/MetaCoreRenderDevice.h"
|
||||
#include "MetaCoreRender/MetaCoreRuntimeUiRenderer.h"
|
||||
#include "MetaCoreRender/MetaCoreRenderTypes.h"
|
||||
@ -15,7 +15,10 @@
|
||||
#include "MetaCoreScene/MetaCoreScene.h"
|
||||
#include "MetaCoreScene/MetaCoreUiRmlCompiler.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
@ -132,6 +135,68 @@ struct MetaCoreRuntimeDataSourceInstance {
|
||||
return iterator == manifest.Entries.end() ? nullptr : &*iterator;
|
||||
}
|
||||
|
||||
[[nodiscard]] const MetaCore::MetaCoreCookManifestEntry* MetaCoreFindCookedManifestEntry(
|
||||
const MetaCore::MetaCoreCookManifestDocument& manifest,
|
||||
const MetaCore::MetaCoreAssetGuid& assetGuid
|
||||
) {
|
||||
if (!assetGuid.IsValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
const auto iterator = std::find_if(
|
||||
manifest.Entries.begin(),
|
||||
manifest.Entries.end(),
|
||||
[&](const MetaCore::MetaCoreCookManifestEntry& entry) {
|
||||
return entry.AssetGuid == assetGuid;
|
||||
}
|
||||
);
|
||||
return iterator == manifest.Entries.end() ? nullptr : &*iterator;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool MetaCoreIsCookedRuntimeGltfModel(const std::filesystem::path& relativeSourcePath) {
|
||||
std::string extension = relativeSourcePath.extension().generic_string();
|
||||
std::transform(extension.begin(), extension.end(), extension.begin(), [](unsigned char c) {
|
||||
return static_cast<char>(std::tolower(c));
|
||||
});
|
||||
return extension == ".glb" || extension == ".gltf";
|
||||
}
|
||||
|
||||
void MetaCoreRewriteSceneGltfModelPathsToCooked(
|
||||
MetaCore::MetaCoreSceneDocument& sceneDocument,
|
||||
const MetaCore::MetaCoreCookManifestDocument& manifest
|
||||
) {
|
||||
for (MetaCore::MetaCoreGameObjectData& gameObject : sceneDocument.GameObjects) {
|
||||
if (!gameObject.MeshRenderer.has_value()) {
|
||||
continue;
|
||||
}
|
||||
if (gameObject.MeshRenderer->SourceModelPath.empty() &&
|
||||
!gameObject.MeshRenderer->SourceModelAssetGuid.IsValid()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::filesystem::path sourceModelPath(gameObject.MeshRenderer->SourceModelPath);
|
||||
const MetaCore::MetaCoreCookManifestEntry* entry = nullptr;
|
||||
if (!sourceModelPath.empty() && MetaCoreIsCookedRuntimeGltfModel(sourceModelPath)) {
|
||||
entry = MetaCoreFindCookedManifestEntry(manifest, sourceModelPath);
|
||||
}
|
||||
if (entry == nullptr && gameObject.MeshRenderer->SourceModelAssetGuid.IsValid()) {
|
||||
entry = MetaCoreFindCookedManifestEntry(manifest, gameObject.MeshRenderer->SourceModelAssetGuid);
|
||||
}
|
||||
if (entry == nullptr || entry->CookedPath.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (!MetaCoreIsCookedRuntimeGltfModel(entry->SourcePackagePath)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string cookedModelPath = entry->CookedPath.generic_string();
|
||||
gameObject.MeshRenderer->SourceModelPath = cookedModelPath;
|
||||
gameObject.MeshRenderer->SourceModelAssetGuid = {};
|
||||
if (gameObject.ModelRootTag.has_value()) {
|
||||
gameObject.ModelRootTag->SourceModelPath = cookedModelPath;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] std::optional<T> MetaCoreReadTypedPackagePayload(
|
||||
const MetaCore::MetaCorePackageDocument& package,
|
||||
@ -579,6 +644,57 @@ void MetaCoreLogRuntimeBindingIssues(
|
||||
}
|
||||
}
|
||||
|
||||
struct MetaCorePlayerLaunchConfig {
|
||||
std::string Platform{"Windows"};
|
||||
std::string GraphicsBackend{"OpenGL"};
|
||||
std::string Architecture{"x64"};
|
||||
std::string BuildProfile{"Development"};
|
||||
};
|
||||
|
||||
[[nodiscard]] MetaCorePlayerLaunchConfig MetaCoreReadPlayerLaunchConfig(
|
||||
const std::filesystem::path& configPath
|
||||
) {
|
||||
MetaCorePlayerLaunchConfig config;
|
||||
std::ifstream input(configPath, std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
return config;
|
||||
}
|
||||
|
||||
nlohmann::json document;
|
||||
try {
|
||||
input >> document;
|
||||
} catch (const nlohmann::json::exception&) {
|
||||
return config;
|
||||
}
|
||||
|
||||
const auto targetIterator = document.find("target");
|
||||
if (targetIterator != document.end() && targetIterator->is_object()) {
|
||||
config.Platform = targetIterator->value("platform", config.Platform);
|
||||
config.GraphicsBackend = targetIterator->value("graphics_backend", config.GraphicsBackend);
|
||||
config.Architecture = targetIterator->value("architecture", config.Architecture);
|
||||
}
|
||||
const auto runtimeIterator = document.find("runtime");
|
||||
if (runtimeIterator != document.end() && runtimeIterator->is_object()) {
|
||||
config.BuildProfile = runtimeIterator->value("build_profile", config.BuildProfile);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
[[nodiscard]] MetaCore::MetaCoreRenderBackend MetaCoreSelectRenderBackend(
|
||||
const MetaCorePlayerLaunchConfig& launchConfig
|
||||
) {
|
||||
if (launchConfig.GraphicsBackend == "Vulkan") {
|
||||
return MetaCore::MetaCoreRenderBackend::Vulkan;
|
||||
}
|
||||
if (launchConfig.GraphicsBackend == "WebGPU") {
|
||||
return MetaCore::MetaCoreRenderBackend::WebGPU;
|
||||
}
|
||||
if (launchConfig.GraphicsBackend == "WebGL") {
|
||||
std::cout << "MetaCorePlayer: WebGL is not available in native Player; using OpenGL backend\n";
|
||||
}
|
||||
return MetaCore::MetaCoreRenderBackend::OpenGL;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
@ -597,6 +713,13 @@ int main(int argc, char* argv[]) {
|
||||
if (argc > 0) {
|
||||
std::filesystem::current_path(std::filesystem::path(argv[0]).parent_path());
|
||||
}
|
||||
const MetaCorePlayerLaunchConfig launchConfig =
|
||||
MetaCoreReadPlayerLaunchConfig(std::filesystem::current_path() / "MetaCorePlayer.config.json");
|
||||
std::cout << "MetaCorePlayer: target platform=" << launchConfig.Platform
|
||||
<< " graphics=" << launchConfig.GraphicsBackend
|
||||
<< " arch=" << launchConfig.Architecture
|
||||
<< " config_profile=" << launchConfig.BuildProfile
|
||||
<< '\n';
|
||||
|
||||
MetaCore::MetaCoreWindow window;
|
||||
if (!window.Initialize(1280, 720, "MetaCore Player")) {
|
||||
@ -605,14 +728,16 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
|
||||
MetaCore::MetaCoreRenderDevice renderDevice;
|
||||
if (!renderDevice.Initialize(window)) {
|
||||
MetaCore::MetaCoreRenderDeviceConfig renderConfig{};
|
||||
renderConfig.Backend = MetaCoreSelectRenderBackend(launchConfig);
|
||||
if (!renderDevice.Initialize(window, renderConfig)) {
|
||||
std::cerr << "MetaCorePlayer: render device initialize failed\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
MetaCore::MetaCoreEditorViewportRenderer viewportRenderer;
|
||||
if (!viewportRenderer.Initialize(renderDevice, window, false)) {
|
||||
std::cerr << "MetaCorePlayer: viewport renderer initialize failed\n";
|
||||
MetaCore::MetaCorePlayerRenderer playerRenderer;
|
||||
if (!playerRenderer.Initialize(renderDevice, window)) {
|
||||
std::cerr << "MetaCorePlayer: renderer initialize failed\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -634,7 +759,7 @@ int main(int argc, char* argv[]) {
|
||||
projectRoot = *discovered;
|
||||
}
|
||||
const std::filesystem::path projectPath = MetaCore::MetaCoreGetProjectFilePath(projectRoot);
|
||||
viewportRenderer.SetProjectRootPath(projectRoot);
|
||||
playerRenderer.SetProjectRootPath(projectRoot);
|
||||
MetaCore::MetaCoreTypeRegistry typeRegistry;
|
||||
MetaCore::MetaCoreRegisterFoundationGeneratedTypes(typeRegistry);
|
||||
MetaCore::MetaCoreRegisterSceneGeneratedTypes(typeRegistry);
|
||||
@ -719,6 +844,9 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
MetaCore::MetaCoreScene scene;
|
||||
if (startupSceneDocument.has_value()) {
|
||||
if (cookedManifest.has_value()) {
|
||||
MetaCoreRewriteSceneGltfModelPathsToCooked(*startupSceneDocument, *cookedManifest);
|
||||
}
|
||||
MetaCore::MetaCoreSceneSnapshot snapshot;
|
||||
snapshot.GameObjects = startupSceneDocument->GameObjects;
|
||||
scene.RestoreSnapshot(snapshot);
|
||||
@ -802,8 +930,8 @@ int main(int argc, char* argv[]) {
|
||||
const auto diagnosticsPath = (projectRoot / runtimeProjectDocument.DiagnosticsPath).lexically_normal();
|
||||
const auto loadedSourcesDocument = MetaCore::MetaCoreReadRuntimeDataSourcesDocument(sourcesPath, typeRegistry);
|
||||
const auto loadedBindingsDocument = MetaCore::MetaCoreReadRuntimeBindingsDocument(bindingsPath, typeRegistry);
|
||||
auto sourcesDocument = loadedSourcesDocument.value_or(MetaCoreBuildDefaultRuntimeDataSourcesDocument());
|
||||
const auto bindingsDocument = loadedBindingsDocument.value_or(MetaCoreBuildDefaultRuntimeBindingsDocument());
|
||||
auto sourcesDocument = loadedSourcesDocument.value_or(MetaCore::MetaCoreRuntimeDataSourcesDocument{});
|
||||
const auto bindingsDocument = loadedBindingsDocument.value_or(MetaCore::MetaCoreRuntimeBindingsDocument{});
|
||||
|
||||
if (loadedSourcesDocument.has_value() && loadedBindingsDocument.has_value()) {
|
||||
std::cout << "MetaCorePlayer: loaded runtime config from binary .mcruntime documents\n";
|
||||
@ -812,9 +940,9 @@ int main(int argc, char* argv[]) {
|
||||
const bool bindingsExists = std::filesystem::exists(bindingsPath);
|
||||
if ((sourcesExists && !loadedSourcesDocument.has_value()) ||
|
||||
(bindingsExists && !loadedBindingsDocument.has_value())) {
|
||||
std::cerr << "MetaCorePlayer: runtime config exists but is unreadable or corrupted, using built-in fallback config\n";
|
||||
std::cerr << "MetaCorePlayer: runtime config exists but is unreadable or corrupted; continuing without runtime data bindings\n";
|
||||
} else {
|
||||
std::cout << "MetaCorePlayer: runtime config missing, using built-in fallback config\n";
|
||||
std::cout << "MetaCorePlayer: runtime config missing; continuing without runtime data bindings\n";
|
||||
}
|
||||
}
|
||||
if (const auto replayPathError = MetaCoreResolveRuntimeDataReplayFilePaths(sourcesDocument, projectRoot);
|
||||
@ -900,7 +1028,7 @@ int main(int argc, char* argv[]) {
|
||||
while (!window.ShouldClose()) {
|
||||
window.BeginFrame();
|
||||
const auto [windowWidth, windowHeight] = window.GetWindowSize();
|
||||
viewportRenderer.SetViewportRect(MetaCore::MetaCoreViewportRect{
|
||||
playerRenderer.SetViewportRect(MetaCore::MetaCoreViewportRect{
|
||||
0.0F,
|
||||
0.0F,
|
||||
static_cast<float>(windowWidth),
|
||||
@ -957,10 +1085,10 @@ int main(int argc, char* argv[]) {
|
||||
}
|
||||
}
|
||||
MetaCoreLogRuntimeBindingIssues(diagnostics, reportedBindingIssues);
|
||||
viewportRenderer.RenderSceneToViewport(scene, MetaCoreBuildPlayerSceneView(), true);
|
||||
playerRenderer.RenderScene(scene, MetaCoreBuildPlayerSceneView());
|
||||
runtimeUiRenderer.Render();
|
||||
viewportRenderer.SetRuntimeUiOverlayFrame(runtimeUiRenderer.GetLastFrame(), windowWidth, windowHeight);
|
||||
viewportRenderer.RenderAll();
|
||||
playerRenderer.SetRuntimeUiOverlayFrame(runtimeUiRenderer.GetLastFrame(), windowWidth, windowHeight);
|
||||
playerRenderer.RenderAll();
|
||||
renderDevice.RenderFrame();
|
||||
renderDevice.PresentFrame();
|
||||
window.EndFrame();
|
||||
@ -968,7 +1096,7 @@ int main(int argc, char* argv[]) {
|
||||
|
||||
runtimeLifecycleExecutor.ExitScene(scene, runtimeLifecycleErrorSink);
|
||||
runtimeUiRenderer.Shutdown();
|
||||
viewportRenderer.Shutdown();
|
||||
playerRenderer.Shutdown();
|
||||
renderDevice.Shutdown();
|
||||
window.Shutdown();
|
||||
return 0;
|
||||
|
||||
@ -16,6 +16,7 @@ if(MSVC)
|
||||
endif()
|
||||
|
||||
option(METACORE_BUILD_TESTS "Build MetaCore tests" ON)
|
||||
option(METACORE_BUILD_LAUNCHER "Build MetaCore Qt launcher" ON)
|
||||
|
||||
|
||||
list(PREPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||
@ -48,7 +49,9 @@ include(MetaCoreFilament)
|
||||
find_package(glm CONFIG REQUIRED)
|
||||
find_package(imgui CONFIG REQUIRED)
|
||||
find_package(RmlUi CONFIG REQUIRED)
|
||||
find_package(Qt6 CONFIG REQUIRED COMPONENTS Widgets)
|
||||
if(METACORE_BUILD_LAUNCHER)
|
||||
find_package(Qt6 CONFIG REQUIRED COMPONENTS Widgets)
|
||||
endif()
|
||||
|
||||
set(METACORE_COMMON_WARNINGS)
|
||||
if(MSVC)
|
||||
@ -65,7 +68,7 @@ add_custom_command(
|
||||
OUTPUT "${METACORE_UI_BLIT_MATERIAL_OUTPUT}"
|
||||
COMMAND "${METACORE_FILAMENT_MATC}"
|
||||
--platform desktop
|
||||
--api opengl
|
||||
--api all
|
||||
--output "${METACORE_UI_BLIT_MATERIAL_OUTPUT}"
|
||||
"${METACORE_UI_BLIT_MATERIAL_SOURCE}"
|
||||
DEPENDS
|
||||
@ -273,6 +276,7 @@ set(METACORE_RENDER_HEADERS
|
||||
Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreEditorViewportRenderer.h
|
||||
Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreFilamentSceneBridge.h
|
||||
Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreImGuiHelper.h
|
||||
Source/MetaCoreRender/Public/MetaCoreRender/MetaCorePlayerRenderer.h
|
||||
Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreRenderDevice.h
|
||||
Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreRuntimeUiRenderer.h
|
||||
Source/MetaCoreRender/Public/MetaCoreRender/MetaCoreSceneRenderSync.h
|
||||
@ -283,6 +287,7 @@ set(METACORE_RENDER_SOURCES
|
||||
Source/MetaCoreRender/Private/MetaCoreEditorViewportRenderer.cpp
|
||||
Source/MetaCoreRender/Private/MetaCoreFilamentSceneBridge.cpp
|
||||
Source/MetaCoreRender/Private/MetaCoreImGuiHelper.cpp
|
||||
Source/MetaCoreRender/Private/MetaCorePlayerRenderer.cpp
|
||||
Source/MetaCoreRender/Private/MetaCoreRenderDevice.cpp
|
||||
Source/MetaCoreRender/Private/MetaCoreRuntimeUiRenderer.cpp
|
||||
Source/MetaCoreRender/Private/MetaCoreSceneRenderSync.cpp
|
||||
@ -437,6 +442,8 @@ target_link_libraries(MetaCoreEditor
|
||||
imgui::imgui
|
||||
)
|
||||
|
||||
metacore_use_filament(MetaCoreEditor)
|
||||
|
||||
target_compile_options(MetaCoreEditor PRIVATE ${METACORE_COMMON_WARNINGS})
|
||||
target_compile_definitions(MetaCoreEditor PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX)
|
||||
|
||||
@ -455,32 +462,34 @@ target_link_libraries(MetaCoreEditorApp
|
||||
)
|
||||
metacore_stage_ui_blit_material(MetaCoreEditorApp)
|
||||
|
||||
add_executable(MetaCoreLauncher
|
||||
Apps/MetaCoreLauncher/main.cpp
|
||||
)
|
||||
if(METACORE_BUILD_LAUNCHER)
|
||||
add_executable(MetaCoreLauncher
|
||||
Apps/MetaCoreLauncher/main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(MetaCoreLauncher
|
||||
PRIVATE
|
||||
MetaCoreFoundation
|
||||
Qt6::Widgets
|
||||
)
|
||||
target_compile_options(MetaCoreLauncher PRIVATE ${METACORE_COMMON_WARNINGS})
|
||||
target_compile_definitions(MetaCoreLauncher PRIVATE NOMINMAX)
|
||||
if(WIN32)
|
||||
if(EXISTS "${METACORE_QTBASE_PACKAGE_DIR}/bin/Qt6Core.dll")
|
||||
add_custom_command(TARGET MetaCoreLauncher POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${METACORE_QTBASE_PACKAGE_DIR}/bin/Qt6Core.dll"
|
||||
"${METACORE_QTBASE_PACKAGE_DIR}/bin/Qt6Gui.dll"
|
||||
"${METACORE_QTBASE_PACKAGE_DIR}/bin/Qt6Widgets.dll"
|
||||
"$<TARGET_FILE_DIR:MetaCoreLauncher>"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||
"$<TARGET_FILE_DIR:MetaCoreLauncher>/platforms"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${METACORE_QTBASE_PACKAGE_DIR}/Qt6/plugins/platforms/qwindows.dll"
|
||||
"$<TARGET_FILE_DIR:MetaCoreLauncher>/platforms"
|
||||
VERBATIM
|
||||
)
|
||||
target_link_libraries(MetaCoreLauncher
|
||||
PRIVATE
|
||||
MetaCoreFoundation
|
||||
Qt6::Widgets
|
||||
)
|
||||
target_compile_options(MetaCoreLauncher PRIVATE ${METACORE_COMMON_WARNINGS})
|
||||
target_compile_definitions(MetaCoreLauncher PRIVATE NOMINMAX)
|
||||
if(WIN32)
|
||||
if(EXISTS "${METACORE_QTBASE_PACKAGE_DIR}/bin/Qt6Core.dll")
|
||||
add_custom_command(TARGET MetaCoreLauncher POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${METACORE_QTBASE_PACKAGE_DIR}/bin/Qt6Core.dll"
|
||||
"${METACORE_QTBASE_PACKAGE_DIR}/bin/Qt6Gui.dll"
|
||||
"${METACORE_QTBASE_PACKAGE_DIR}/bin/Qt6Widgets.dll"
|
||||
"$<TARGET_FILE_DIR:MetaCoreLauncher>"
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||
"$<TARGET_FILE_DIR:MetaCoreLauncher>/platforms"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${METACORE_QTBASE_PACKAGE_DIR}/Qt6/plugins/platforms/qwindows.dll"
|
||||
"$<TARGET_FILE_DIR:MetaCoreLauncher>/platforms"
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
@ -516,7 +525,9 @@ if(METACORE_BUILD_TESTS)
|
||||
target_link_libraries(MetaCoreSmokeTests
|
||||
PRIVATE
|
||||
MetaCoreEditor
|
||||
MetaCoreRender
|
||||
MetaCoreRuntimeData
|
||||
RmlUi::RmlUi
|
||||
)
|
||||
target_compile_options(MetaCoreSmokeTests PRIVATE ${METACORE_COMMON_WARNINGS})
|
||||
add_dependencies(MetaCoreSmokeTests MetaCoreRuntimeConfigTool)
|
||||
|
||||
@ -14,7 +14,8 @@
|
||||
"toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
|
||||
"cacheVariables": {
|
||||
"CMAKE_CXX_STANDARD": "20",
|
||||
"METACORE_BUILD_TESTS": "ON"
|
||||
"METACORE_BUILD_TESTS": "ON",
|
||||
"METACORE_BUILD_LAUNCHER": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -32,6 +33,16 @@
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "vs2022-launcher-release",
|
||||
"inherits": "base",
|
||||
"displayName": "VS2022 Launcher Release",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"METACORE_BUILD_LAUNCHER": "ON",
|
||||
"VCPKG_MANIFEST_FEATURES": "launcher"
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
@ -44,6 +55,11 @@
|
||||
"name": "build-release",
|
||||
"configurePreset": "vs2022-release",
|
||||
"configuration": "Release"
|
||||
},
|
||||
{
|
||||
"name": "build-launcher-release",
|
||||
"configurePreset": "vs2022-launcher-release",
|
||||
"configuration": "Release"
|
||||
}
|
||||
],
|
||||
"testPresets": [
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -2,6 +2,7 @@
|
||||
|
||||
#include "MetaCoreEditor/MetaCoreBuiltinModules.h"
|
||||
#include "MetaCoreEditor/MetaCoreEditorServices.h"
|
||||
#include "MetaCoreFoundation/MetaCoreProject.h"
|
||||
#include "MetaCoreEditorCameraController.h"
|
||||
|
||||
#include <imgui.h>
|
||||
@ -20,6 +21,7 @@
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <glm/ext/matrix_clip_space.hpp>
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
@ -74,6 +76,57 @@ constexpr bool GMetaCoreEnableImGuizmo = true;
|
||||
|
||||
constexpr const char* GMetaCoreEditorLayoutRelativePath = "Library/Editor/imgui.ini";
|
||||
|
||||
[[nodiscard]] bool MetaCoreTrySetProjectPathFromArgument(const std::filesystem::path& inputPath) {
|
||||
if (inputPath.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::filesystem::path projectFilePath = inputPath.filename() == "MetaCore.project.json"
|
||||
? inputPath
|
||||
: MetaCoreGetProjectFilePath(inputPath);
|
||||
if (!std::filesystem::is_regular_file(projectFilePath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_putenv_s("METACORE_PROJECT_PATH", projectFilePath.parent_path().string().c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
void MetaCoreApplyProjectPathFromCommandLine(int argc, char* argv[]) {
|
||||
for (int index = 1; index < argc; ++index) {
|
||||
if (argv[index] == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string argument(argv[index]);
|
||||
if (argument == "--project") {
|
||||
if (index + 1 < argc && argv[index + 1] != nullptr) {
|
||||
if (MetaCoreTrySetProjectPathFromArgument(std::filesystem::path(argv[index + 1]))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
++index;
|
||||
continue;
|
||||
}
|
||||
|
||||
constexpr std::string_view projectPrefix = "--project=";
|
||||
if (argument.rfind(projectPrefix.data(), 0) == 0) {
|
||||
if (MetaCoreTrySetProjectPathFromArgument(std::filesystem::path(argument.substr(projectPrefix.size())))) {
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (argument.rfind("--", 0) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (MetaCoreTrySetProjectPathFromArgument(std::filesystem::path(argument))) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] const char* MetaCoreGetInfernuxDockWindowId(std::string_view panelId) {
|
||||
if (panelId == "Hierarchy") {
|
||||
return "hierarchy";
|
||||
@ -644,6 +697,8 @@ bool MetaCoreEditorApp::Initialize(int argc, char* argv[]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
MetaCoreApplyProjectPathFromCommandLine(argc, argv);
|
||||
|
||||
MetaCoreTraceStartup("metacore.app: initialize window");
|
||||
if (!Window_.Initialize(1600, 900, "MetaCore Editor")) {
|
||||
MetaCoreTraceStartup("metacore.app: initialize window failed");
|
||||
@ -830,6 +885,7 @@ int MetaCoreEditorApp::Run() {
|
||||
while (!Window_.ShouldClose()) {
|
||||
Window_.BeginFrame();
|
||||
SyncImGuiLayoutIniPath();
|
||||
SyncViewportProjectRootPath();
|
||||
|
||||
// Filament 接管,不再使用 OpenGL3 后端的 NewFrame
|
||||
ImGui_ImplWin32_NewFrame();
|
||||
@ -954,6 +1010,21 @@ void MetaCoreEditorApp::SyncImGuiLayoutIniPath() {
|
||||
EditorContext_->SetDockLayoutBuilt(hasSavedLayout);
|
||||
}
|
||||
|
||||
void MetaCoreEditorApp::SyncViewportProjectRootPath() {
|
||||
std::filesystem::path projectRoot{};
|
||||
if (const auto assetDatabaseService = ModuleRegistry_.ResolveService<MetaCoreIAssetDatabaseService>();
|
||||
assetDatabaseService != nullptr && assetDatabaseService->HasProject()) {
|
||||
projectRoot = assetDatabaseService->GetProjectDescriptor().RootPath.lexically_normal();
|
||||
}
|
||||
|
||||
if (projectRoot == ViewportProjectRoot_) {
|
||||
return;
|
||||
}
|
||||
|
||||
ViewportProjectRoot_ = projectRoot;
|
||||
ViewportRenderer_.SetProjectRootPath(ViewportProjectRoot_);
|
||||
}
|
||||
|
||||
void MetaCoreEditorApp::DrawEditorFrame() {
|
||||
SceneInteractionService_.HandleShortcuts(*EditorContext_);
|
||||
|
||||
@ -1025,7 +1096,20 @@ void MetaCoreEditorApp::DrawEditorFrame() {
|
||||
const ImGuiWindowFlags panelWindowFlags = static_cast<ImGuiWindowFlags>(panelProvider->GetWindowFlags());
|
||||
const std::string panelWindowName = MetaCoreBuildDockWindowName(*panelProvider);
|
||||
ImGui::Begin(panelWindowName.c_str(), &panelOpen, panelWindowFlags);
|
||||
panelProvider->DrawPanel(*EditorContext_);
|
||||
try {
|
||||
panelProvider->DrawPanel(*EditorContext_);
|
||||
} catch (const std::exception& exception) {
|
||||
ImGui::TextColored(
|
||||
ImVec4(1.0F, 0.35F, 0.25F, 1.0F),
|
||||
"Panel failed to draw: %s",
|
||||
exception.what()
|
||||
);
|
||||
} catch (...) {
|
||||
ImGui::TextColored(
|
||||
ImVec4(1.0F, 0.35F, 0.25F, 1.0F),
|
||||
"Panel failed to draw: unknown error"
|
||||
);
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (pushedPadding) {
|
||||
@ -1829,4 +1913,3 @@ void MetaCoreEditorApp::EnsureDefaultDockLayout(unsigned int dockSpaceId) {
|
||||
}
|
||||
|
||||
} // namespace MetaCore
|
||||
|
||||
|
||||
@ -1,7 +1,20 @@
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include "MetaCoreGltfImporter.h"
|
||||
#include "MetaCoreFoundation/MetaCoreHash.h"
|
||||
|
||||
#include <cgltf.h>
|
||||
#include <filament/Engine.h>
|
||||
#include <filament/Box.h>
|
||||
#include <filament/TransformManager.h>
|
||||
#include <filament/RenderableManager.h>
|
||||
#include <gltfio/AssetLoader.h>
|
||||
#include <gltfio/FilamentAsset.h>
|
||||
#include <gltfio/MaterialProvider.h>
|
||||
#include <utils/NameComponentManager.h>
|
||||
#include <utils/EntityManager.h>
|
||||
#include <glm/gtx/matrix_decompose.hpp>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
@ -701,262 +714,151 @@ void MetaCoreAppendUtf8Codepoint(std::string& output, std::uint32_t codepoint) {
|
||||
document.GeneratedMaterialAssets.push_back(std::move(materialAsset));
|
||||
};
|
||||
|
||||
MetaCoreGltfTrace("使用降级机制解析普通 GLTF 文件...");
|
||||
addFallbackSkeleton();
|
||||
MetaCoreFinalizeImportReport(document.ImportReport);
|
||||
MetaCoreApplyModelImportSettings(document);
|
||||
return document;
|
||||
|
||||
cgltf_data* data = nullptr;
|
||||
if (data->images_count > 0) {
|
||||
for (std::size_t i = 0; i < data->images_count; ++i) {
|
||||
const cgltf_image& image = data->images[i];
|
||||
MetaCoreImportedGltfTextureDocument textureDoc;
|
||||
textureDoc.SourcePath = image.uri ? std::filesystem::path(image.uri).lexically_normal() : std::filesystem::path{};
|
||||
textureDoc.UsageHint = "Unknown";
|
||||
|
||||
MetaCoreTextureAssetDocument genTex;
|
||||
const std::string imageName = MetaCoreGltfDisplayName(image.name, "Texture_" + std::to_string(i));
|
||||
genTex.AssetGuid = MetaCoreMakeDeterministicGeneratedAssetGuid(relativeSourcePath, "texture", imageName, i);
|
||||
genTex.Name = imageName;
|
||||
genTex.StableImportKey = MetaCoreBuildStableImportKey("texture", imageName, i);
|
||||
genTex.SourcePath = textureDoc.SourcePath;
|
||||
genTex.UsageHint = "Unknown";
|
||||
|
||||
document.Textures.push_back(std::move(textureDoc));
|
||||
document.GeneratedTextureAssets.push_back(std::move(genTex));
|
||||
}
|
||||
}
|
||||
MetaCoreGltfTrace("使用 gltfio 引擎后端离线提取 GLTF 层级结构...");
|
||||
|
||||
if (data->materials_count > 0) {
|
||||
for (std::size_t i = 0; i < data->materials_count; ++i) {
|
||||
const cgltf_material& mat = data->materials[i];
|
||||
MetaCoreImportedGltfMaterialDocument matDoc;
|
||||
matDoc.Name = MetaCoreGltfDisplayName(mat.name, "Material_" + std::to_string(i));
|
||||
matDoc.DoubleSided = mat.double_sided;
|
||||
|
||||
MetaCoreMaterialAssetDocument genMat;
|
||||
genMat.AssetGuid = MetaCoreMakeDeterministicGeneratedAssetGuid(relativeSourcePath, "material", matDoc.Name, i);
|
||||
genMat.Name = matDoc.Name;
|
||||
genMat.StableImportKey = MetaCoreBuildStableImportKey("material", matDoc.Name, i);
|
||||
genMat.DoubleSided = mat.double_sided;
|
||||
genMat.AlphaCutoff = mat.alpha_cutoff;
|
||||
genMat.AlphaMode = mat.alpha_mode == cgltf_alpha_mode_blend ? MetaCoreMaterialAlphaMode::Blend : (mat.alpha_mode == cgltf_alpha_mode_mask ? MetaCoreMaterialAlphaMode::Mask : MetaCoreMaterialAlphaMode::Opaque);
|
||||
|
||||
if (mat.has_pbr_metallic_roughness) {
|
||||
genMat.BaseColor = glm::vec3(mat.pbr_metallic_roughness.base_color_factor[0], mat.pbr_metallic_roughness.base_color_factor[1], mat.pbr_metallic_roughness.base_color_factor[2]);
|
||||
genMat.Metallic = mat.pbr_metallic_roughness.metallic_factor;
|
||||
genMat.Roughness = mat.pbr_metallic_roughness.roughness_factor;
|
||||
|
||||
if (mat.pbr_metallic_roughness.base_color_texture.texture) {
|
||||
const std::int32_t texIdx = static_cast<std::int32_t>(mat.pbr_metallic_roughness.base_color_texture.texture->image - data->images);
|
||||
if (texIdx >= 0 && static_cast<std::size_t>(texIdx) < document.GeneratedTextureAssets.size()) {
|
||||
matDoc.TextureIndices.push_back(texIdx);
|
||||
genMat.BaseColorTexture = document.GeneratedTextureAssets[static_cast<std::size_t>(texIdx)].AssetGuid;
|
||||
} else {
|
||||
MetaCoreAddImportMessage(
|
||||
document.ImportReport,
|
||||
MetaCoreImportMessageSeverity::Warning,
|
||||
"missing_texture_reference",
|
||||
"材质引用了超出范围的贴图,已跳过该贴图槽位"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (matDoc.TextureIndices.empty() && !document.GeneratedTextureAssets.empty()) {
|
||||
matDoc.TextureIndices.push_back(0);
|
||||
genMat.BaseColorTexture = document.GeneratedTextureAssets.front().AssetGuid;
|
||||
MetaCoreAddImportMessage(
|
||||
document.ImportReport,
|
||||
MetaCoreImportMessageSeverity::Info,
|
||||
"default_texture_binding",
|
||||
"材质缺少贴图引用时,已回退到首个可用贴图资源"
|
||||
);
|
||||
}
|
||||
|
||||
document.Materials.push_back(std::move(matDoc));
|
||||
document.GeneratedMaterialAssets.push_back(std::move(genMat));
|
||||
}
|
||||
}
|
||||
|
||||
if (data->meshes_count > 0) {
|
||||
for (std::size_t i = 0; i < data->meshes_count; ++i) {
|
||||
const cgltf_mesh& mesh = data->meshes[i];
|
||||
MetaCoreImportedGltfMeshDocument meshDoc;
|
||||
meshDoc.Name = MetaCoreGltfDisplayName(mesh.name, "Mesh_" + std::to_string(i));
|
||||
meshDoc.PrimitiveCount = static_cast<std::int32_t>(mesh.primitives_count);
|
||||
|
||||
MetaCoreGltfTrace(("处理 Mesh [" + std::to_string(i) + "]: " + meshDoc.Name + ", Primitives: " + std::to_string(mesh.primitives_count)).c_str());
|
||||
|
||||
MetaCoreMeshAssetDocument genMesh;
|
||||
genMesh.AssetGuid = MetaCoreMakeDeterministicGeneratedAssetGuid(relativeSourcePath, "mesh", meshDoc.Name, i);
|
||||
genMesh.Name = meshDoc.Name;
|
||||
genMesh.StableImportKey = MetaCoreBuildStableImportKey("mesh", meshDoc.Name, i);
|
||||
|
||||
std::vector<std::byte> meshBinaryData;
|
||||
struct Vertex {
|
||||
float px, py, pz;
|
||||
float nx, ny, nz;
|
||||
float u, v;
|
||||
};
|
||||
|
||||
std::vector<Vertex> vertices;
|
||||
std::vector<std::uint32_t> indices;
|
||||
|
||||
glm::vec3 bmin{1e30, 1e30, 1e30};
|
||||
glm::vec3 bmax{-1e30, -1e30, -1e30};
|
||||
|
||||
for (std::size_t p = 0; p < mesh.primitives_count; ++p) {
|
||||
const cgltf_primitive& prim = mesh.primitives[p];
|
||||
|
||||
const std::uint32_t vertexOffset = static_cast<std::uint32_t>(vertices.size());
|
||||
|
||||
const cgltf_accessor* posAcc = nullptr;
|
||||
const cgltf_accessor* normAcc = nullptr;
|
||||
const cgltf_accessor* uvAcc = nullptr;
|
||||
|
||||
for (std::size_t a = 0; a < prim.attributes_count; ++a) {
|
||||
const cgltf_attribute& attr = prim.attributes[a];
|
||||
if (attr.type == cgltf_attribute_type_position) posAcc = attr.data;
|
||||
if (attr.type == cgltf_attribute_type_normal) normAcc = attr.data;
|
||||
if (attr.type == cgltf_attribute_type_texcoord) uvAcc = attr.data;
|
||||
}
|
||||
|
||||
if (!posAcc) continue;
|
||||
|
||||
for (std::size_t v = 0; v < posAcc->count; ++v) {
|
||||
Vertex vertex{};
|
||||
float pos[3];
|
||||
cgltf_accessor_read_float(posAcc, v, pos, 3);
|
||||
vertex.px = pos[0]; vertex.py = pos[1]; vertex.pz = pos[2];
|
||||
|
||||
bmin = glm::min(bmin, glm::vec3(pos[0], pos[1], pos[2]));
|
||||
bmax = glm::max(bmax, glm::vec3(pos[0], pos[1], pos[2]));
|
||||
|
||||
if (normAcc) {
|
||||
float norm[3];
|
||||
cgltf_accessor_read_float(normAcc, v, norm, 3);
|
||||
vertex.nx = norm[0]; vertex.ny = norm[1]; vertex.nz = norm[2];
|
||||
}
|
||||
if (uvAcc) {
|
||||
float uv[2];
|
||||
cgltf_accessor_read_float(uvAcc, v, uv, 2);
|
||||
vertex.u = uv[0]; vertex.v = uv[1];
|
||||
}
|
||||
vertices.push_back(vertex);
|
||||
}
|
||||
|
||||
const std::uint32_t firstIndex = static_cast<std::uint32_t>(indices.size());
|
||||
|
||||
if (prim.indices) {
|
||||
for (std::size_t idx = 0; idx < prim.indices->count; ++idx) {
|
||||
indices.push_back(vertexOffset + static_cast<std::uint32_t>(cgltf_accessor_read_index(prim.indices, idx)));
|
||||
}
|
||||
} else {
|
||||
for (std::size_t idx = 0; idx < posAcc->count; ++idx) {
|
||||
indices.push_back(vertexOffset + static_cast<std::uint32_t>(idx));
|
||||
}
|
||||
}
|
||||
|
||||
std::int32_t matIdx = -1;
|
||||
if (prim.material) {
|
||||
matIdx = static_cast<std::int32_t>(prim.material - data->materials);
|
||||
meshDoc.MaterialSlots.push_back(matIdx);
|
||||
}
|
||||
|
||||
MetaCoreMeshSubMeshDocument subMesh;
|
||||
subMesh.Name = "Primitive_" + std::to_string(p);
|
||||
subMesh.MaterialSlotIndex = matIdx;
|
||||
subMesh.FirstIndex = firstIndex;
|
||||
subMesh.IndexCount = static_cast<std::uint32_t>(indices.size()) - firstIndex;
|
||||
genMesh.SubMeshes.push_back(std::move(subMesh));
|
||||
}
|
||||
if (meshDoc.MaterialSlots.empty() && !document.GeneratedMaterialAssets.empty()) {
|
||||
meshDoc.MaterialSlots.push_back(0);
|
||||
MetaCoreMeshSubMeshDocument defaultSubMesh;
|
||||
defaultSubMesh.Name = "DefaultSubMesh";
|
||||
defaultSubMesh.MaterialSlotIndex = 0;
|
||||
defaultSubMesh.FirstIndex = 0;
|
||||
defaultSubMesh.IndexCount = static_cast<std::uint32_t>(indices.size());
|
||||
genMesh.SubMeshes.push_back(std::move(defaultSubMesh));
|
||||
}
|
||||
|
||||
genMesh.VertexCount = static_cast<std::int32_t>(vertices.size());
|
||||
genMesh.IndexCount = static_cast<std::int32_t>(indices.size());
|
||||
genMesh.BoundingBoxMin = bmin;
|
||||
genMesh.BoundingBoxMax = bmax;
|
||||
|
||||
MetaCoreGltfTrace((" Mesh [" + std::to_string(i) + "] 顶点数: " + std::to_string(genMesh.VertexCount) + ", 索引数: " + std::to_string(genMesh.IndexCount)).c_str());
|
||||
|
||||
const std::size_t payloadSize = sizeof(std::uint32_t) * 2 + vertices.size() * sizeof(Vertex) + indices.size() * sizeof(std::uint32_t);
|
||||
meshBinaryData.resize(payloadSize);
|
||||
std::byte* ptr = meshBinaryData.data();
|
||||
|
||||
std::uint32_t vCount = static_cast<std::uint32_t>(vertices.size());
|
||||
std::uint32_t iCount = static_cast<std::uint32_t>(indices.size());
|
||||
|
||||
std::memcpy(ptr, &vCount, sizeof(std::uint32_t)); ptr += sizeof(std::uint32_t);
|
||||
std::memcpy(ptr, &iCount, sizeof(std::uint32_t)); ptr += sizeof(std::uint32_t);
|
||||
std::memcpy(ptr, vertices.data(), vertices.size() * sizeof(Vertex)); ptr += vertices.size() * sizeof(Vertex);
|
||||
std::memcpy(ptr, indices.data(), indices.size() * sizeof(std::uint32_t));
|
||||
|
||||
genMesh.PayloadIndex = 1 + outMeshPayloads.size();
|
||||
genMesh.PayloadSize = payloadSize;
|
||||
outMeshPayloads.push_back(std::move(meshBinaryData));
|
||||
|
||||
document.Meshes.push_back(std::move(meshDoc));
|
||||
document.GeneratedMeshAssets.push_back(std::move(genMesh));
|
||||
}
|
||||
}
|
||||
|
||||
if (data->nodes_count > 0) {
|
||||
for (std::size_t i = 0; i < data->nodes_count; ++i) {
|
||||
const cgltf_node& node = data->nodes[i];
|
||||
MetaCoreImportedGltfNodeDocument nodeDoc;
|
||||
if (node.name && strlen(node.name) > 0) {
|
||||
nodeDoc.Name = MetaCoreGltfDisplayName(node.name, "Node_" + std::to_string(i));
|
||||
} else if (node.mesh && node.mesh->name && strlen(node.mesh->name) > 0) {
|
||||
nodeDoc.Name = MetaCoreGltfDisplayName(node.mesh->name, "Mesh_" + std::to_string(i)) + "_Node";
|
||||
} else {
|
||||
nodeDoc.Name = "Node_" + std::to_string(i);
|
||||
}
|
||||
|
||||
nodeDoc.ParentIndex = node.parent ? static_cast<std::int32_t>(node.parent - data->nodes) : -1;
|
||||
nodeDoc.MeshIndex = node.mesh ? static_cast<std::int32_t>(node.mesh - data->meshes) : -1;
|
||||
|
||||
if (node.has_translation) {
|
||||
nodeDoc.Position = glm::vec3(node.translation[0], node.translation[1], node.translation[2]);
|
||||
}
|
||||
if (node.has_rotation) {
|
||||
const glm::quat q(node.rotation[3], node.rotation[0], node.rotation[1], node.rotation[2]);
|
||||
nodeDoc.RotationEulerDegrees = glm::degrees(glm::eulerAngles(q));
|
||||
}
|
||||
if (node.has_scale) {
|
||||
nodeDoc.Scale = glm::vec3(node.scale[0], node.scale[1], node.scale[2]);
|
||||
}
|
||||
document.Nodes.push_back(std::move(nodeDoc));
|
||||
}
|
||||
for (std::size_t nodeIndex = 0; nodeIndex < document.Nodes.size(); ++nodeIndex) {
|
||||
document.Nodes[nodeIndex].StableNodePath = MetaCoreBuildNodeStablePath(document.Nodes, nodeIndex);
|
||||
}
|
||||
std::vector<char> fileBuffer;
|
||||
if (FILE* file = _wfopen(absoluteSourcePath.c_str(), L"rb")) {
|
||||
fseek(file, 0, SEEK_END);
|
||||
long size = ftell(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
fileBuffer.resize(size);
|
||||
fread(fileBuffer.data(), 1, size, file);
|
||||
fclose(file);
|
||||
} else {
|
||||
MetaCoreImportedGltfNodeDocument rootNode;
|
||||
rootNode.Name = absoluteSourcePath.stem().string();
|
||||
rootNode.StableNodePath = rootNode.Name;
|
||||
rootNode.MeshIndex = document.Meshes.empty() ? -1 : 0;
|
||||
document.Nodes.push_back(std::move(rootNode));
|
||||
MetaCoreAddImportMessage(
|
||||
document.ImportReport,
|
||||
MetaCoreImportMessageSeverity::Info,
|
||||
"generated_root_node",
|
||||
"源模型未提供节点层级,已生成默认根节点"
|
||||
);
|
||||
MetaCoreAddImportMessage(document.ImportReport, MetaCoreImportMessageSeverity::Error, "file_read_error", "Failed to read file.");
|
||||
return document;
|
||||
}
|
||||
|
||||
static filament::Engine* NOOP_Engine = nullptr;
|
||||
static utils::NameComponentManager* NameManager = nullptr;
|
||||
static filament::gltfio::MaterialProvider* MaterialProvider = nullptr;
|
||||
static filament::gltfio::AssetLoader* Loader = nullptr;
|
||||
|
||||
if (!NOOP_Engine) {
|
||||
NOOP_Engine = filament::Engine::create(filament::Engine::Backend::NOOP);
|
||||
NameManager = new utils::NameComponentManager(utils::EntityManager::get());
|
||||
MaterialProvider = filament::gltfio::createJitShaderProvider(NOOP_Engine);
|
||||
Loader = filament::gltfio::AssetLoader::create({NOOP_Engine, MaterialProvider, NameManager});
|
||||
}
|
||||
|
||||
filament::gltfio::FilamentAsset* asset = Loader->createAsset((const uint8_t*)fileBuffer.data(), fileBuffer.size());
|
||||
if (!asset) {
|
||||
MetaCoreAddImportMessage(document.ImportReport, MetaCoreImportMessageSeverity::Error, "gltfio_error", "gltfio AssetLoader failed to parse asset.");
|
||||
MetaCoreFinalizeImportReport(document.ImportReport);
|
||||
return document;
|
||||
}
|
||||
|
||||
std::unordered_map<utils::Entity, int, utils::Entity::Hasher> entityToMeshIndex;
|
||||
|
||||
const utils::Entity* renderables = asset->getRenderableEntities();
|
||||
size_t numRenderables = asset->getRenderableEntityCount();
|
||||
|
||||
for (size_t i = 0; i < numRenderables; ++i) {
|
||||
utils::Entity e = renderables[i];
|
||||
entityToMeshIndex[e] = static_cast<int>(i);
|
||||
|
||||
auto inst = NOOP_Engine->getRenderableManager().getInstance(e);
|
||||
const filament::Box& aabb = NOOP_Engine->getRenderableManager().getAxisAlignedBoundingBox(inst);
|
||||
const filament::math::float3 aabbMin = aabb.getMin();
|
||||
const filament::math::float3 aabbMax = aabb.getMax();
|
||||
|
||||
MetaCoreMeshAssetDocument meshAsset;
|
||||
meshAsset.Name = "Mesh_" + std::to_string(i);
|
||||
const char* renderableName = asset->getName(e);
|
||||
if (renderableName != nullptr && renderableName[0] != '\0') {
|
||||
meshAsset.Name = renderableName;
|
||||
}
|
||||
meshAsset.AssetGuid = MetaCoreMakeDeterministicGeneratedAssetGuid(relativeSourcePath, "mesh", meshAsset.Name, i);
|
||||
meshAsset.StableImportKey = MetaCoreBuildStableImportKey("mesh", meshAsset.Name, i);
|
||||
meshAsset.BoundingBoxMin = glm::vec3(aabbMin.x, aabbMin.y, aabbMin.z);
|
||||
meshAsset.BoundingBoxMax = glm::vec3(aabbMax.x, aabbMax.y, aabbMax.z);
|
||||
meshAsset.VertexCount = 0;
|
||||
meshAsset.IndexCount = 0;
|
||||
|
||||
MetaCoreImportedGltfMeshDocument meshDocument;
|
||||
meshDocument.Name = meshAsset.Name;
|
||||
meshDocument.PrimitiveCount = 1;
|
||||
document.Meshes.push_back(std::move(meshDocument));
|
||||
|
||||
document.GeneratedMeshAssets.push_back(std::move(meshAsset));
|
||||
}
|
||||
|
||||
auto& tm = NOOP_Engine->getTransformManager();
|
||||
utils::Entity rootEntity = asset->getRoot();
|
||||
std::unordered_map<utils::Entity, std::vector<utils::Entity>, utils::Entity::Hasher> parentToChildren;
|
||||
|
||||
const utils::Entity* allEntities = asset->getEntities();
|
||||
size_t allEntityCount = asset->getEntityCount();
|
||||
for (size_t i = 0; i < allEntityCount; ++i) {
|
||||
utils::Entity e = allEntities[i];
|
||||
auto inst = tm.getInstance(e);
|
||||
utils::Entity parentE = tm.getParent(inst);
|
||||
if (parentE) {
|
||||
parentToChildren[parentE].push_back(e);
|
||||
} else {
|
||||
if (e != rootEntity) {
|
||||
parentToChildren[rootEntity].push_back(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::function<void(utils::Entity, int)> addNode = [&](utils::Entity e, int parentIdx) {
|
||||
MetaCoreImportedGltfNodeDocument node;
|
||||
const char* name = asset->getName(e);
|
||||
node.Name = name && name[0] != '\0' ? name : "Node_" + std::to_string(document.Nodes.size());
|
||||
node.StableNodePath = node.Name;
|
||||
node.ParentIndex = parentIdx;
|
||||
|
||||
auto itMesh = entityToMeshIndex.find(e);
|
||||
if (itMesh != entityToMeshIndex.end()) {
|
||||
node.MeshIndex = itMesh->second;
|
||||
} else {
|
||||
node.MeshIndex = -1;
|
||||
}
|
||||
|
||||
auto inst = tm.getInstance(e);
|
||||
filament::math::mat4f localMat = tm.getTransform(inst);
|
||||
glm::mat4 m(
|
||||
localMat[0][0], localMat[0][1], localMat[0][2], localMat[0][3],
|
||||
localMat[1][0], localMat[1][1], localMat[1][2], localMat[1][3],
|
||||
localMat[2][0], localMat[2][1], localMat[2][2], localMat[2][3],
|
||||
localMat[3][0], localMat[3][1], localMat[3][2], localMat[3][3]
|
||||
);
|
||||
|
||||
glm::vec3 scale;
|
||||
glm::quat rotation;
|
||||
glm::vec3 translation;
|
||||
glm::vec3 skew;
|
||||
glm::vec4 perspective;
|
||||
glm::decompose(m, scale, rotation, translation, skew, perspective);
|
||||
|
||||
node.Position = translation;
|
||||
glm::vec3 euler = glm::degrees(glm::eulerAngles(rotation));
|
||||
node.RotationEulerDegrees = euler;
|
||||
node.Scale = scale;
|
||||
|
||||
int myIdx = static_cast<int>(document.Nodes.size());
|
||||
document.Nodes.push_back(node);
|
||||
|
||||
for (utils::Entity child : parentToChildren[e]) {
|
||||
addNode(child, myIdx);
|
||||
}
|
||||
};
|
||||
|
||||
addNode(rootEntity, -1);
|
||||
|
||||
Loader->destroyAsset(asset);
|
||||
|
||||
for (std::size_t nodeIndex = 0; nodeIndex < document.Nodes.size(); ++nodeIndex) {
|
||||
document.Nodes[nodeIndex].StableNodePath = MetaCoreBuildNodeStablePath(document.Nodes, nodeIndex);
|
||||
}
|
||||
|
||||
cgltf_free(data);
|
||||
MetaCoreFinalizeImportReport(document.ImportReport);
|
||||
MetaCoreApplyModelImportSettings(document);
|
||||
return document;
|
||||
|
||||
|
||||
}
|
||||
|
||||
} // namespace MetaCore
|
||||
|
||||
@ -38,6 +38,7 @@ private:
|
||||
void DrawGameViewWindow();
|
||||
void DrawPlaceholderDockWindow(const char* label, const char* caption);
|
||||
void SyncImGuiLayoutIniPath();
|
||||
void SyncViewportProjectRootPath();
|
||||
void EnsureDefaultDockLayout(unsigned int dockSpaceId);
|
||||
void QueueDockTabSelection(std::string windowId);
|
||||
void ApplyPendingDockTabSelections();
|
||||
@ -53,6 +54,7 @@ private:
|
||||
std::vector<std::string> PendingDockTabSelections_{};
|
||||
std::unique_ptr<MetaCoreEditorContext> EditorContext_{};
|
||||
std::filesystem::path ImGuiLayoutProjectRoot_{};
|
||||
std::filesystem::path ViewportProjectRoot_{};
|
||||
std::string ImGuiIniPath_{};
|
||||
bool Initialized_ = false;
|
||||
};
|
||||
|
||||
@ -216,9 +216,36 @@ public:
|
||||
[[nodiscard]] virtual std::filesystem::path GetCookedPathForAsset(const MetaCoreAssetGuid& assetGuid) const = 0;
|
||||
};
|
||||
|
||||
enum class MetaCoreBuildPlatform {
|
||||
Windows,
|
||||
Linux,
|
||||
Android,
|
||||
Web
|
||||
};
|
||||
|
||||
enum class MetaCoreGraphicsBackend {
|
||||
OpenGL,
|
||||
Vulkan,
|
||||
WebGL,
|
||||
WebGPU
|
||||
};
|
||||
|
||||
enum class MetaCoreBuildArchitecture {
|
||||
X64,
|
||||
Arm64,
|
||||
Wasm32
|
||||
};
|
||||
|
||||
struct MetaCoreBuildTarget {
|
||||
MetaCoreBuildPlatform Platform = MetaCoreBuildPlatform::Windows;
|
||||
MetaCoreGraphicsBackend GraphicsBackend = MetaCoreGraphicsBackend::OpenGL;
|
||||
MetaCoreBuildArchitecture Architecture = MetaCoreBuildArchitecture::X64;
|
||||
};
|
||||
|
||||
struct MetaCoreBuildPlayerPackageRequest {
|
||||
std::filesystem::path PlayerExecutablePath{};
|
||||
std::filesystem::path OutputDirectory{};
|
||||
MetaCoreBuildTarget Target{};
|
||||
bool CookBeforePackage = true;
|
||||
bool CopyLooseProjectContent = true;
|
||||
bool CopyRuntimeConfig = true;
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <system_error>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
@ -12,13 +13,23 @@ MetaCoreAssetRegistry& MetaCoreAssetRegistry::Get() {
|
||||
}
|
||||
|
||||
void MetaCoreAssetRegistry::ScanDirectory(const std::filesystem::path& rootPath) {
|
||||
if (!std::filesystem::exists(rootPath) || !std::filesystem::is_directory(rootPath)) {
|
||||
std::error_code error;
|
||||
if (!std::filesystem::is_directory(rootPath, error)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 遍历指定目录下的所有文件,寻找 .mcmeta 文件
|
||||
for (const auto& entry : std::filesystem::recursive_directory_iterator(rootPath)) {
|
||||
if (!entry.is_regular_file() || entry.path().extension() != ".mcmeta") {
|
||||
std::error_code iteratorError;
|
||||
for (std::filesystem::recursive_directory_iterator iterator(rootPath, std::filesystem::directory_options::skip_permission_denied, iteratorError), end;
|
||||
iterator != end;
|
||||
iterator.increment(iteratorError)) {
|
||||
if (iteratorError) {
|
||||
iteratorError.clear();
|
||||
continue;
|
||||
}
|
||||
const auto& entry = *iterator;
|
||||
std::error_code entryError;
|
||||
if (!entry.is_regular_file(entryError) || entry.path().extension() != ".mcmeta") {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -36,6 +47,11 @@ void MetaCoreAssetRegistry::ScanDirectory(const std::filesystem::path& rootPath)
|
||||
|
||||
const std::string guidStr = json["guid"].get<std::string>();
|
||||
const std::string sourcePathStr = json["source_path"].get<std::string>();
|
||||
const std::filesystem::path sourcePath = rootPath.parent_path() / std::filesystem::path(sourcePathStr);
|
||||
std::error_code sourceError;
|
||||
if (!std::filesystem::exists(sourcePath, sourceError)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto parsedGuid = MetaCoreAssetGuid::Parse(guidStr);
|
||||
if (parsedGuid.has_value()) {
|
||||
|
||||
@ -52,7 +52,7 @@ bool MetaCoreEditorViewportRenderer::Initialize(MetaCoreRenderDevice& renderDevi
|
||||
}
|
||||
*/
|
||||
|
||||
if (!FilamentSceneBridge_.Initialize(window, offscreen)) {
|
||||
if (!FilamentSceneBridge_.Initialize(window, offscreen, renderDevice.GetBackend())) {
|
||||
MetaCoreTrace("metacore.render: viewport filament scene runtime bind failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
#include <filament/RenderTarget.h>
|
||||
#include <filament/LightManager.h>
|
||||
#include <filament/RenderableManager.h>
|
||||
#include <filament/Material.h>
|
||||
#include <filament/MaterialInstance.h>
|
||||
#include <filament/IndirectLight.h>
|
||||
|
||||
@ -45,19 +46,206 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <cstdint>
|
||||
|
||||
// 引入 Windows 和 OpenGL 头文件以创建纹理
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <GL/gl.h>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] filament::backend::Backend MetaCoreToFilamentBackend(MetaCoreRenderBackend backend) {
|
||||
switch (backend) {
|
||||
case MetaCoreRenderBackend::OpenGL:
|
||||
return filament::backend::Backend::OPENGL;
|
||||
case MetaCoreRenderBackend::Vulkan:
|
||||
return filament::backend::Backend::VULKAN;
|
||||
case MetaCoreRenderBackend::WebGPU:
|
||||
return filament::backend::Backend::WEBGPU;
|
||||
}
|
||||
return filament::backend::Backend::OPENGL;
|
||||
}
|
||||
|
||||
[[nodiscard]] const char* MetaCoreRenderBackendToString(MetaCoreRenderBackend backend) {
|
||||
switch (backend) {
|
||||
case MetaCoreRenderBackend::OpenGL:
|
||||
return "OpenGL";
|
||||
case MetaCoreRenderBackend::Vulkan:
|
||||
return "Vulkan";
|
||||
case MetaCoreRenderBackend::WebGPU:
|
||||
return "WebGPU";
|
||||
}
|
||||
return "OpenGL";
|
||||
}
|
||||
|
||||
class MetaCoreViewportMaterialProvider final : public filament::gltfio::MaterialProvider {
|
||||
public:
|
||||
explicit MetaCoreViewportMaterialProvider(filament::Engine* engine)
|
||||
: Inner_(filament::gltfio::createJitShaderProvider(engine)) {}
|
||||
|
||||
~MetaCoreViewportMaterialProvider() override {
|
||||
delete Inner_;
|
||||
}
|
||||
|
||||
filament::MaterialInstance* createMaterialInstance(
|
||||
filament::gltfio::MaterialKey* config,
|
||||
filament::gltfio::UvMap* uvmap,
|
||||
const char* label = "material",
|
||||
const char* extras = nullptr
|
||||
) override {
|
||||
CoerceFoliageAlphaMode(config);
|
||||
return Inner_->createMaterialInstance(config, uvmap, label, extras);
|
||||
}
|
||||
|
||||
filament::Material* getMaterial(
|
||||
filament::gltfio::MaterialKey* config,
|
||||
filament::gltfio::UvMap* uvmap,
|
||||
const char* label = "material"
|
||||
) override {
|
||||
CoerceFoliageAlphaMode(config);
|
||||
return Inner_->getMaterial(config, uvmap, label);
|
||||
}
|
||||
|
||||
const filament::Material* const* getMaterials() const noexcept override {
|
||||
return Inner_->getMaterials();
|
||||
}
|
||||
|
||||
size_t getMaterialsCount() const noexcept override {
|
||||
return Inner_->getMaterialsCount();
|
||||
}
|
||||
|
||||
void destroyMaterials() override {
|
||||
Inner_->destroyMaterials();
|
||||
}
|
||||
|
||||
bool needsDummyData(filament::VertexAttribute attrib) const noexcept override {
|
||||
return Inner_->needsDummyData(attrib);
|
||||
}
|
||||
|
||||
private:
|
||||
static void CoerceFoliageAlphaMode(filament::gltfio::MaterialKey* config) {
|
||||
if (config != nullptr && config->alphaMode == filament::gltfio::AlphaMode::BLEND) {
|
||||
config->alphaMode = filament::gltfio::AlphaMode::MASK;
|
||||
}
|
||||
}
|
||||
|
||||
filament::gltfio::MaterialProvider* Inner_ = nullptr;
|
||||
};
|
||||
|
||||
[[nodiscard]] std::string MetaCoreBuildModelLoadFailureKey(const std::filesystem::path& absolutePath) {
|
||||
std::string key = absolutePath.lexically_normal().generic_string();
|
||||
std::transform(key.begin(), key.end(), key.begin(), [](unsigned char c) {
|
||||
return static_cast<char>(std::tolower(c));
|
||||
});
|
||||
return key;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool MetaCoreIsExternalGltfResourceUri(std::string_view uri) {
|
||||
if (uri.empty() || uri.starts_with("data:") || uri.starts_with("//")) {
|
||||
return false;
|
||||
}
|
||||
return uri.find("://") == std::string_view::npos;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<std::filesystem::path> MetaCoreDecodeGltfResourceUri(std::string_view uri) {
|
||||
std::string decoded;
|
||||
decoded.reserve(uri.size());
|
||||
for (std::size_t index = 0; index < uri.size(); ++index) {
|
||||
const char character = uri[index];
|
||||
if (character == '%' && index + 2 < uri.size()) {
|
||||
const auto hexToInt = [](char value) -> int {
|
||||
if (value >= '0' && value <= '9') return value - '0';
|
||||
if (value >= 'a' && value <= 'f') return 10 + (value - 'a');
|
||||
if (value >= 'A' && value <= 'F') return 10 + (value - 'A');
|
||||
return -1;
|
||||
};
|
||||
const int high = hexToInt(uri[index + 1]);
|
||||
const int low = hexToInt(uri[index + 2]);
|
||||
if (high >= 0 && low >= 0) {
|
||||
decoded.push_back(static_cast<char>((high << 4) | low));
|
||||
index += 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
decoded.push_back(character);
|
||||
}
|
||||
|
||||
std::filesystem::path decodedPath(decoded);
|
||||
if (decodedPath.empty() || decodedPath.is_absolute()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
for (const auto& part : decodedPath) {
|
||||
if (part == "..") {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
return decodedPath.lexically_normal();
|
||||
}
|
||||
|
||||
void MetaCoreReleaseResourceBuffer(void*, size_t, void* userData) {
|
||||
delete static_cast<std::vector<uint8_t>*>(userData);
|
||||
}
|
||||
|
||||
void MetaCoreAddExternalGltfResourceData(
|
||||
filament::gltfio::ResourceLoader& resourceLoader,
|
||||
const filament::gltfio::FilamentAsset& asset,
|
||||
const std::filesystem::path& modelPath
|
||||
) {
|
||||
const std::filesystem::path resourceRoot = modelPath.parent_path();
|
||||
const char* const* resourceUris = asset.getResourceUris();
|
||||
const std::size_t resourceUriCount = asset.getResourceUriCount();
|
||||
for (std::size_t resourceIndex = 0; resourceIndex < resourceUriCount; ++resourceIndex) {
|
||||
const char* resourceUri = resourceUris[resourceIndex];
|
||||
if (resourceUri == nullptr || !MetaCoreIsExternalGltfResourceUri(resourceUri) || resourceLoader.hasResourceData(resourceUri)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto decodedUri = MetaCoreDecodeGltfResourceUri(resourceUri);
|
||||
if (!decodedUri.has_value()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::ifstream input(resourceRoot / *decodedUri, std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
std::cerr << "FilamentSceneBridge: Missing GLTF external resource: "
|
||||
<< (resourceRoot / *decodedUri) << " uri=" << resourceUri << std::endl;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto* buffer = new std::vector<uint8_t>(
|
||||
(std::istreambuf_iterator<char>(input)),
|
||||
std::istreambuf_iterator<char>()
|
||||
);
|
||||
resourceLoader.addResourceData(
|
||||
resourceUri,
|
||||
filament::gltfio::ResourceLoader::BufferDescriptor(
|
||||
buffer->data(),
|
||||
buffer->size(),
|
||||
MetaCoreReleaseResourceBuffer,
|
||||
buffer
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class MetaCoreFilamentSceneBridge::MetaCoreFilamentSceneBridgeImpl {
|
||||
public:
|
||||
MetaCoreFilamentSceneBridgeImpl() = default;
|
||||
@ -71,14 +259,19 @@ public:
|
||||
return RenderSync_.BuildSnapshot(scene);
|
||||
}
|
||||
|
||||
bool Initialize(MetaCoreWindow& window, bool offscreen = true) {
|
||||
std::cout << "FilamentSceneBridge: Initializing..." << std::endl;
|
||||
bool Initialize(MetaCoreWindow& window, bool offscreen = true, MetaCoreRenderBackend backend = MetaCoreRenderBackend::OpenGL) {
|
||||
std::cout << "FilamentSceneBridge: Initializing with backend=" << MetaCoreRenderBackendToString(backend) << std::endl;
|
||||
|
||||
// 创建 Filament Engine (不共享上下文,避免闪退)
|
||||
Engine_ = filament::Engine::create();
|
||||
|
||||
Engine_ = filament::Engine::create(MetaCoreToFilamentBackend(backend));
|
||||
if (!Engine_ && backend != MetaCoreRenderBackend::OpenGL) {
|
||||
std::cerr << "FilamentSceneBridge: backend " << MetaCoreRenderBackendToString(backend)
|
||||
<< " is not available; falling back to OpenGL." << std::endl;
|
||||
Engine_ = filament::Engine::create(MetaCoreToFilamentBackend(MetaCoreRenderBackend::OpenGL));
|
||||
}
|
||||
|
||||
if (!Engine_) {
|
||||
std::cerr << "Failed to create Filament Engine with shared context!" << std::endl;
|
||||
std::cerr << "Failed to create Filament Engine!" << std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -170,7 +363,7 @@ public:
|
||||
View_->setPostProcessingEnabled(true);
|
||||
|
||||
// 初始化 gltfio
|
||||
MaterialProvider_ = filament::gltfio::createJitShaderProvider(Engine_);
|
||||
MaterialProvider_ = new MetaCoreViewportMaterialProvider(Engine_);
|
||||
NameManager_ = new utils::NameComponentManager(utils::EntityManager::get());
|
||||
AssetLoader_ = filament::gltfio::AssetLoader::create({ Engine_, MaterialProvider_, NameManager_ });
|
||||
ResourceLoader_ = new filament::gltfio::ResourceLoader({ Engine_ });
|
||||
@ -250,6 +443,8 @@ public:
|
||||
}
|
||||
LoadedAssets_.clear();
|
||||
AssetBuffers_.clear();
|
||||
FailedModelLoads_.clear();
|
||||
FailedModelLoadPaths_.clear();
|
||||
|
||||
// 2. 销毁所有 pivotEntity 中间辅助坐标变换实体
|
||||
for (auto& [objectId, assetEntity] : ObjectToFilamentEntity_) {
|
||||
@ -540,9 +735,7 @@ public:
|
||||
// 1. 收集当前 snapshot 中所有存在的模型 Root ID
|
||||
std::unordered_set<MetaCoreId> activeHostRootIds;
|
||||
for (const auto& renderable : snapshot.Renderables) {
|
||||
if (renderable.MeshSource == MetaCoreMeshSourceKind::Asset ||
|
||||
(renderable.MeshSource == MetaCoreMeshSourceKind::Builtin &&
|
||||
(renderable.BuiltinMesh == MetaCoreBuiltinMeshType::Cube || renderable.BuiltinMesh == MetaCoreBuiltinMeshType::Plane))) {
|
||||
if (IsEngineLoadedModelRenderable(renderable)) {
|
||||
if (renderable.HostRootId != 0) {
|
||||
activeHostRootIds.insert(renderable.HostRootId);
|
||||
}
|
||||
@ -604,6 +797,7 @@ public:
|
||||
|
||||
// 从 LoadedAssets_ 中移除
|
||||
AssetBuffers_.erase(hostRootId);
|
||||
FailedModelLoads_.erase(hostRootId);
|
||||
it = LoadedAssets_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
@ -611,8 +805,7 @@ public:
|
||||
}
|
||||
|
||||
for (const auto& renderable : snapshot.Renderables) {
|
||||
if (renderable.MeshSource != MetaCoreMeshSourceKind::Asset &&
|
||||
renderable.MeshSource != MetaCoreMeshSourceKind::Builtin) {
|
||||
if (!IsEngineLoadedModelRenderable(renderable)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -638,11 +831,25 @@ public:
|
||||
|
||||
MetaCoreId hostRootId = renderable.HostRootId;
|
||||
std::string hostRootName = renderable.HostRootName;
|
||||
const std::string failureKey = MetaCoreBuildModelLoadFailureKey(absolutePath);
|
||||
const bool sourceExists = std::filesystem::exists(absolutePath);
|
||||
if (sourceExists) {
|
||||
FailedModelLoadPaths_.erase(failureKey);
|
||||
}
|
||||
|
||||
// 如果该模型的顶级根宿主已经被加载过,则不需要重复加载,直接更新变换
|
||||
if (LoadedAssets_.contains(hostRootId)) {
|
||||
continue;
|
||||
}
|
||||
if (FailedModelLoads_.contains(hostRootId) || FailedModelLoadPaths_.contains(failureKey)) {
|
||||
continue;
|
||||
}
|
||||
if (!sourceExists) {
|
||||
std::cerr << "FilamentSceneBridge: Missing GLTF source: " << absolutePath << " (Host: " << hostRootName << ")" << std::endl;
|
||||
FailedModelLoads_.insert(hostRootId);
|
||||
FailedModelLoadPaths_.insert(failureKey);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 加载新模型,以顶级根宿主 hostRootId 进行注册存储
|
||||
std::cout << "FilamentSceneBridge: Loading GLTF: " << absolutePath << " (Host: " << hostRootName << ")" << std::endl;
|
||||
@ -650,6 +857,8 @@ public:
|
||||
std::ifstream file(absolutePath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "Failed to open file: " << absolutePath << std::endl;
|
||||
FailedModelLoads_.insert(hostRootId);
|
||||
FailedModelLoadPaths_.insert(failureKey);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -662,13 +871,17 @@ public:
|
||||
|
||||
if (!asset) {
|
||||
std::cerr << "Failed to create asset!" << std::endl;
|
||||
FailedModelLoads_.insert(hostRootId);
|
||||
FailedModelLoadPaths_.insert(failureKey);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 加载贴图等资源
|
||||
if (ResourceLoader_) {
|
||||
MetaCoreAddExternalGltfResourceData(*ResourceLoader_, *asset, absolutePath);
|
||||
ResourceLoader_->loadResources(asset);
|
||||
ResourceLoader_->asyncUpdateLoad(); // 触发底层的纹理同步
|
||||
ResourceLoader_->evictResourceData();
|
||||
}
|
||||
|
||||
// 添加到场景
|
||||
@ -907,36 +1120,71 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 同步所有存活网格的材质属性,例如 BaseColor
|
||||
// 5. 同步所有存活网格的材质属性。
|
||||
auto& rm = Engine_->getRenderableManager();
|
||||
for (const auto& renderable : snapshot.Renderables) {
|
||||
auto itEntity = ObjectToFilamentEntity_.find(renderable.ObjectId);
|
||||
if (itEntity == ObjectToFilamentEntity_.end()) {
|
||||
continue;
|
||||
}
|
||||
utils::Entity entity = itEntity->second.second;
|
||||
if (!entity) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto instance = rm.getInstance(entity);
|
||||
const auto applyMaterialParameters = [&](auto instance, const MetaCoreRenderSyncRenderable& renderable) -> bool {
|
||||
if (!instance) {
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
|
||||
// 遍历所有 primitive,获取材质实例,并设置其 baseColorFactor 属性
|
||||
size_t primCount = rm.getPrimitiveCount(instance);
|
||||
for (size_t primIndex = 0; primIndex < primCount; ++primIndex) {
|
||||
filament::MaterialInstance* matInst = rm.getMaterialInstanceAt(instance, primIndex);
|
||||
if (matInst) {
|
||||
// BaseColor 包含 RGB,以 float4 传递,Alpha 设为 1.0
|
||||
const filament::Material* material = matInst->getMaterial();
|
||||
filament::math::float4 baseColorVec{
|
||||
renderable.BaseColor.x,
|
||||
renderable.BaseColor.y,
|
||||
renderable.BaseColor.z,
|
||||
1.0f
|
||||
};
|
||||
matInst->setParameter("baseColorFactor", baseColorVec);
|
||||
if (material != nullptr && material->hasParameter("baseColorFactor")) {
|
||||
matInst->setParameter("baseColorFactor", baseColorVec);
|
||||
}
|
||||
if (material != nullptr && material->hasParameter("metallicFactor")) {
|
||||
matInst->setParameter("metallicFactor", renderable.Metallic);
|
||||
}
|
||||
if (material != nullptr && material->hasParameter("roughnessFactor")) {
|
||||
matInst->setParameter("roughnessFactor", renderable.Roughness);
|
||||
}
|
||||
if (material != nullptr && material->hasParameter("emissiveFactor")) {
|
||||
matInst->setParameter(
|
||||
"emissiveFactor",
|
||||
filament::math::float3{
|
||||
renderable.EmissiveColor.x,
|
||||
renderable.EmissiveColor.y,
|
||||
renderable.EmissiveColor.z
|
||||
}
|
||||
);
|
||||
}
|
||||
matInst->setDoubleSided(renderable.DoubleSided);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
for (const auto& renderable : snapshot.Renderables) {
|
||||
bool applied = false;
|
||||
auto itEntity = ObjectToFilamentEntity_.find(renderable.ObjectId);
|
||||
if (itEntity != ObjectToFilamentEntity_.end()) {
|
||||
utils::Entity entity = itEntity->second.second;
|
||||
if (entity) {
|
||||
applied = applyMaterialParameters(rm.getInstance(entity), renderable);
|
||||
}
|
||||
}
|
||||
|
||||
if (!applied) {
|
||||
const auto loadedAsset = LoadedAssets_.find(renderable.HostRootId);
|
||||
if (loadedAsset == LoadedAssets_.end() || loadedAsset->second == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const utils::Entity* entities = loadedAsset->second->getEntities();
|
||||
const size_t entityCount = loadedAsset->second->getEntityCount();
|
||||
for (size_t entityIndex = 0; entityIndex < entityCount; ++entityIndex) {
|
||||
if (entities[entityIndex] && applyMaterialParameters(rm.getInstance(entities[entityIndex]), renderable)) {
|
||||
applied = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1357,6 +1605,12 @@ private:
|
||||
auto it = ObjectToFilamentEntity_.find(objectId);
|
||||
if (it == ObjectToFilamentEntity_.end()) return;
|
||||
|
||||
// glTF child entities keep their authored local transforms. The ECS child objects are
|
||||
// editor-facing handles; only the loaded model host/pivot drives the imported asset.
|
||||
if (!LoadedAssets_.contains(objectId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
utils::Entity entity = it->second.second;
|
||||
|
||||
auto& tm = Engine_->getTransformManager();
|
||||
@ -1364,20 +1618,20 @@ private:
|
||||
if (instance) {
|
||||
auto parent = tm.getParent(instance);
|
||||
glm::mat4 matrix = parent ? localMatrix : worldMatrix;
|
||||
|
||||
// 【关键修复】:如果当前 entity 是子节点(即不是顶级映射的 pivotEntity)
|
||||
// 由于它的父级(assetRoot)带有了 -90X 旋转,我们必须对子节点应用基变换来抵消这个旋转。
|
||||
// 公式:L_filament = R(90X) * L_ecs * R(-90X)
|
||||
if (LoadedAssets_.count(objectId) == 0) {
|
||||
glm::mat4 R_plus90X = glm::rotate(glm::mat4(1.0f), glm::radians(90.0f), {1, 0, 0});
|
||||
glm::mat4 R_minus90X = glm::rotate(glm::mat4(1.0f), glm::radians(-90.0f), {1, 0, 0});
|
||||
matrix = R_plus90X * matrix * R_minus90X;
|
||||
}
|
||||
|
||||
tm.setTransform(instance, *reinterpret_cast<const filament::math::mat4f*>(glm::value_ptr(matrix)));
|
||||
}
|
||||
}
|
||||
|
||||
static bool IsEngineLoadedModelRenderable(const MetaCoreRenderSyncRenderable& renderable) {
|
||||
if (renderable.MeshSource == MetaCoreMeshSourceKind::Asset) {
|
||||
return renderable.SourceModelAssetGuid.IsValid() || !renderable.SourceModelPath.empty();
|
||||
}
|
||||
|
||||
return renderable.MeshSource == MetaCoreMeshSourceKind::Builtin &&
|
||||
(renderable.BuiltinMesh == MetaCoreBuiltinMeshType::Cube ||
|
||||
renderable.BuiltinMesh == MetaCoreBuiltinMeshType::Plane);
|
||||
}
|
||||
|
||||
filament::Engine* Engine_ = nullptr;
|
||||
filament::SwapChain* SwapChain_ = nullptr;
|
||||
filament::Renderer* Renderer_ = nullptr;
|
||||
@ -1399,6 +1653,8 @@ private:
|
||||
std::unordered_map<MetaCoreId, std::pair<filament::gltfio::FilamentAsset*, utils::Entity>> ObjectToFilamentEntity_;
|
||||
std::unordered_map<MetaCoreId, filament::gltfio::FilamentAsset*> LoadedAssets_;
|
||||
std::unordered_map<MetaCoreId, std::vector<char>> AssetBuffers_;
|
||||
std::unordered_set<MetaCoreId> FailedModelLoads_;
|
||||
std::unordered_set<std::string> FailedModelLoadPaths_;
|
||||
|
||||
std::unordered_map<MetaCoreId, utils::Entity> SceneLightEntities_;
|
||||
std::unordered_set<utils::Entity, utils::Entity::Hasher> EntitiesInScene_;
|
||||
@ -1427,8 +1683,8 @@ MetaCoreFilamentSceneBridge::MetaCoreFilamentSceneBridge()
|
||||
|
||||
MetaCoreFilamentSceneBridge::~MetaCoreFilamentSceneBridge() = default;
|
||||
|
||||
bool MetaCoreFilamentSceneBridge::Initialize(MetaCoreWindow& window, bool offscreen) {
|
||||
return Impl_->Initialize(window, offscreen);
|
||||
bool MetaCoreFilamentSceneBridge::Initialize(MetaCoreWindow& window, bool offscreen, MetaCoreRenderBackend backend) {
|
||||
return Impl_->Initialize(window, offscreen, backend);
|
||||
}
|
||||
|
||||
void MetaCoreFilamentSceneBridge::Shutdown() {
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
#include "MetaCoreRender/MetaCoreRuntimeUiRenderer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
@ -11,6 +12,14 @@
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <filament/Camera.h>
|
||||
@ -36,23 +45,56 @@ using MagFilter = TextureSampler::MagFilter;
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] std::filesystem::path MetaCoreGetExecutableDirectory() {
|
||||
std::array<wchar_t, 32768> executablePath{};
|
||||
const DWORD length = GetModuleFileNameW(
|
||||
nullptr,
|
||||
executablePath.data(),
|
||||
static_cast<DWORD>(executablePath.size())
|
||||
);
|
||||
if (length == 0 || length >= executablePath.size()) {
|
||||
return {};
|
||||
}
|
||||
return std::filesystem::path(executablePath.data()).parent_path();
|
||||
}
|
||||
|
||||
[[nodiscard]] std::ifstream MetaCoreOpenUiBlitMaterial() {
|
||||
std::vector<std::filesystem::path> candidates{
|
||||
"uiBlit.filamat",
|
||||
"../uiBlit.filamat",
|
||||
"build/uiBlit.filamat"
|
||||
};
|
||||
|
||||
const std::filesystem::path executableDirectory = MetaCoreGetExecutableDirectory();
|
||||
if (!executableDirectory.empty()) {
|
||||
candidates.push_back(executableDirectory / "uiBlit.filamat");
|
||||
}
|
||||
|
||||
for (const std::filesystem::path& candidate : candidates) {
|
||||
std::ifstream file(candidate, std::ios::binary);
|
||||
if (file.is_open()) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MetaCoreImGuiHelper::MetaCoreImGuiHelper(Engine* engine, filament::View* view, const std::filesystem::path& fontPath,
|
||||
ImGuiContext *imGuiContext)
|
||||
: mEngine(engine), mView(view), mScene(engine->createScene()),
|
||||
mImGuiContext(imGuiContext ? imGuiContext : ImGui::CreateContext()),
|
||||
mOwnsImGuiContext(imGuiContext == nullptr) {
|
||||
mOwnsImGuiContext(imGuiContext == nullptr),
|
||||
mPreviousImGuiContext(ImGui::GetCurrentContext()) {
|
||||
|
||||
ImGui::SetCurrentContext(mImGuiContext);
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
|
||||
// 加载我们编译好的材质
|
||||
std::ifstream file("uiBlit.filamat", std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
file.open("../uiBlit.filamat", std::ios::binary);
|
||||
}
|
||||
if (!file.is_open()) {
|
||||
file.open("build/uiBlit.filamat", std::ios::binary);
|
||||
}
|
||||
std::ifstream file = MetaCoreOpenUiBlitMaterial();
|
||||
|
||||
if (!file.is_open()) {
|
||||
std::cerr << "MetaCoreImGuiHelper: Failed to open uiBlit.filamat from any expected location!" << std::endl;
|
||||
@ -198,11 +240,10 @@ MetaCoreImGuiHelper::~MetaCoreImGuiHelper() {
|
||||
em.destroy(mRenderable);
|
||||
em.destroy(mCameraEntity);
|
||||
|
||||
if (mImGuiContext == ImGui::GetCurrentContext()) {
|
||||
ImGui::SetCurrentContext(nullptr);
|
||||
}
|
||||
if (mOwnsImGuiContext) {
|
||||
ImGui::DestroyContext(mImGuiContext);
|
||||
} else if (mImGuiContext == ImGui::GetCurrentContext()) {
|
||||
ImGui::SetCurrentContext(mPreviousImGuiContext);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
82
Source/MetaCoreRender/Private/MetaCorePlayerRenderer.cpp
Normal file
82
Source/MetaCoreRender/Private/MetaCorePlayerRenderer.cpp
Normal file
@ -0,0 +1,82 @@
|
||||
#include "MetaCoreRender/MetaCorePlayerRenderer.h"
|
||||
|
||||
#include "MetaCoreRender/MetaCoreRenderDevice.h"
|
||||
#include "MetaCoreScene/MetaCoreScene.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <ctime>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
namespace {
|
||||
|
||||
void MetaCoreTracePlayerRenderer(const char* message) {
|
||||
if (message == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const auto time = std::chrono::system_clock::to_time_t(now);
|
||||
struct tm timeInfo {};
|
||||
localtime_s(&timeInfo, &time);
|
||||
|
||||
char timeBuffer[32]{};
|
||||
std::strftime(timeBuffer, sizeof(timeBuffer), "[%H:%M:%S] ", &timeInfo);
|
||||
std::printf("%s%s\n", timeBuffer, message);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool MetaCorePlayerRenderer::Initialize(MetaCoreRenderDevice& renderDevice, MetaCoreWindow& window) {
|
||||
MetaCoreTracePlayerRenderer("metacore.render: player init enter");
|
||||
Shutdown();
|
||||
|
||||
if (!FilamentSceneBridge_.Initialize(window, false, renderDevice.GetBackend())) {
|
||||
MetaCoreTracePlayerRenderer("metacore.render: player filament scene bind failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
RenderDevice_ = &renderDevice;
|
||||
ViewportRect_ = MetaCoreViewportRect{};
|
||||
MetaCoreTracePlayerRenderer("metacore.render: player init success");
|
||||
return true;
|
||||
}
|
||||
|
||||
void MetaCorePlayerRenderer::Shutdown() {
|
||||
FilamentSceneBridge_.Shutdown();
|
||||
RenderDevice_ = nullptr;
|
||||
ViewportRect_ = MetaCoreViewportRect{};
|
||||
}
|
||||
|
||||
void MetaCorePlayerRenderer::SetViewportRect(const MetaCoreViewportRect& viewportRect) {
|
||||
ViewportRect_ = viewportRect;
|
||||
if (RenderDevice_ != nullptr) {
|
||||
RenderDevice_->SetSceneViewportRect(ViewportRect_);
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCorePlayerRenderer::SetProjectRootPath(const std::filesystem::path& projectRootPath) {
|
||||
FilamentSceneBridge_.SetProjectRootPath(projectRootPath);
|
||||
}
|
||||
|
||||
void MetaCorePlayerRenderer::RenderScene(MetaCoreScene& scene, const MetaCoreSceneView& sceneView) {
|
||||
if (RenderDevice_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
RenderDevice_->SetSceneViewportRect(ViewportRect_);
|
||||
FilamentSceneBridge_.Resize(static_cast<int>(ViewportRect_.Width), static_cast<int>(ViewportRect_.Height));
|
||||
FilamentSceneBridge_.ApplySceneView(sceneView);
|
||||
FilamentSceneBridge_.SyncScene(scene, false, true);
|
||||
}
|
||||
|
||||
void MetaCorePlayerRenderer::SetRuntimeUiOverlayFrame(const MetaCoreRuntimeUiFrame& frame, int width, int height) {
|
||||
FilamentSceneBridge_.SetRuntimeUiOverlayFrame(frame, width, height);
|
||||
}
|
||||
|
||||
void MetaCorePlayerRenderer::RenderAll() {
|
||||
FilamentSceneBridge_.RenderAll();
|
||||
}
|
||||
|
||||
} // namespace MetaCore
|
||||
@ -9,6 +9,7 @@ namespace MetaCore {
|
||||
class MetaCoreRenderDevice::MetaCoreRenderDeviceImpl {
|
||||
public:
|
||||
MetaCoreWindow* Window = nullptr;
|
||||
MetaCoreRenderDeviceConfig Config{};
|
||||
bool Initialized = false;
|
||||
};
|
||||
|
||||
@ -20,11 +21,12 @@ MetaCoreRenderDevice::~MetaCoreRenderDevice() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
bool MetaCoreRenderDevice::Initialize(MetaCoreWindow& window) {
|
||||
bool MetaCoreRenderDevice::Initialize(MetaCoreWindow& window, MetaCoreRenderDeviceConfig config) {
|
||||
if (Impl_->Initialized) {
|
||||
return true;
|
||||
}
|
||||
Impl_->Window = &window;
|
||||
Impl_->Config = config;
|
||||
Impl_->Initialized = true;
|
||||
return true;
|
||||
}
|
||||
@ -47,4 +49,8 @@ void MetaCoreRenderDevice::SetSceneViewportRect(const MetaCoreViewportRect& view
|
||||
(void)viewportRect;
|
||||
}
|
||||
|
||||
MetaCoreRenderBackend MetaCoreRenderDevice::GetBackend() const {
|
||||
return Impl_->Config.Backend;
|
||||
}
|
||||
|
||||
} // namespace MetaCore
|
||||
|
||||
@ -138,7 +138,13 @@ MetaCoreSceneRenderSyncSnapshot MetaCoreSceneRenderSync::BuildSnapshot(const Met
|
||||
hostRoot.GetId(),
|
||||
hostRoot.GetName(),
|
||||
hostRoot.HasComponent<MetaCoreModelRootTag>(),
|
||||
meshRenderer.BaseColor
|
||||
meshRenderer.BaseColor,
|
||||
meshRenderer.Metallic,
|
||||
meshRenderer.Roughness,
|
||||
meshRenderer.EmissiveColor,
|
||||
meshRenderer.AlphaMode,
|
||||
meshRenderer.AlphaCutoff,
|
||||
meshRenderer.DoubleSided
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCoreFoundation/MetaCoreId.h"
|
||||
#include "MetaCoreRender/MetaCoreRenderTypes.h"
|
||||
|
||||
#include <glm/mat4x4.hpp>
|
||||
|
||||
@ -24,7 +25,7 @@ public:
|
||||
MetaCoreFilamentSceneBridge();
|
||||
~MetaCoreFilamentSceneBridge();
|
||||
|
||||
bool Initialize(MetaCoreWindow& window, bool offscreen = true);
|
||||
bool Initialize(MetaCoreWindow& window, bool offscreen = true, MetaCoreRenderBackend backend = MetaCoreRenderBackend::OpenGL);
|
||||
void Shutdown();
|
||||
void Resize(int width, int height);
|
||||
void SetProjectRootPath(const std::filesystem::path& projectRootPath);
|
||||
|
||||
@ -79,6 +79,7 @@ private:
|
||||
filament::TextureSampler mSampler;
|
||||
bool mFlipVertical = false;
|
||||
bool mOwnsImGuiContext = false;
|
||||
ImGuiContext* mPreviousImGuiContext = nullptr;
|
||||
std::filesystem::path mSettingsPath;
|
||||
std::unordered_set<filament::Texture*> mImGuiTextures;
|
||||
std::unordered_map<std::uint64_t, RuntimeUiTextureState> mRuntimeUiTextures;
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCoreRender/MetaCoreFilamentSceneBridge.h"
|
||||
#include "MetaCoreRender/MetaCoreRenderTypes.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
class MetaCoreRenderDevice;
|
||||
class MetaCoreScene;
|
||||
class MetaCoreWindow;
|
||||
struct MetaCoreRuntimeUiFrame;
|
||||
|
||||
class MetaCorePlayerRenderer {
|
||||
public:
|
||||
bool Initialize(MetaCoreRenderDevice& renderDevice, MetaCoreWindow& window);
|
||||
void Shutdown();
|
||||
void SetViewportRect(const MetaCoreViewportRect& viewportRect);
|
||||
void SetProjectRootPath(const std::filesystem::path& projectRootPath);
|
||||
void RenderScene(MetaCoreScene& scene, const MetaCoreSceneView& sceneView);
|
||||
void SetRuntimeUiOverlayFrame(const MetaCoreRuntimeUiFrame& frame, int width, int height);
|
||||
void RenderAll();
|
||||
|
||||
private:
|
||||
MetaCoreRenderDevice* RenderDevice_ = nullptr;
|
||||
MetaCoreFilamentSceneBridge FilamentSceneBridge_{};
|
||||
MetaCoreViewportRect ViewportRect_{};
|
||||
};
|
||||
|
||||
} // namespace MetaCore
|
||||
@ -16,11 +16,12 @@ public:
|
||||
MetaCoreRenderDevice();
|
||||
~MetaCoreRenderDevice();
|
||||
|
||||
bool Initialize(MetaCoreWindow& window);
|
||||
bool Initialize(MetaCoreWindow& window, MetaCoreRenderDeviceConfig config = {});
|
||||
void Shutdown();
|
||||
void RenderFrame() const;
|
||||
void PresentFrame() const;
|
||||
void SetSceneViewportRect(const MetaCoreViewportRect& viewportRect);
|
||||
[[nodiscard]] MetaCoreRenderBackend GetBackend() const;
|
||||
|
||||
private:
|
||||
class MetaCoreRenderDeviceImpl;
|
||||
|
||||
@ -6,6 +6,16 @@
|
||||
|
||||
namespace MetaCore {
|
||||
|
||||
enum class MetaCoreRenderBackend {
|
||||
OpenGL,
|
||||
Vulkan,
|
||||
WebGPU
|
||||
};
|
||||
|
||||
struct MetaCoreRenderDeviceConfig {
|
||||
MetaCoreRenderBackend Backend = MetaCoreRenderBackend::OpenGL;
|
||||
};
|
||||
|
||||
struct MetaCoreViewportRect {
|
||||
float Left = 0.0F;
|
||||
float Top = 0.0F;
|
||||
|
||||
@ -35,6 +35,12 @@ struct MetaCoreRenderSyncRenderable {
|
||||
std::string HostRootName{};
|
||||
bool HostRootHasModelRootTag = false;
|
||||
glm::vec3 BaseColor{1.0F, 1.0F, 1.0F};
|
||||
float Metallic = 0.0F;
|
||||
float Roughness = 1.0F;
|
||||
glm::vec3 EmissiveColor{0.0F, 0.0F, 0.0F};
|
||||
MetaCoreMeshAlphaMode AlphaMode = MetaCoreMeshAlphaMode::Opaque;
|
||||
float AlphaCutoff = 0.5F;
|
||||
bool DoubleSided = false;
|
||||
};
|
||||
|
||||
struct MetaCoreRenderSyncCamera {
|
||||
|
||||
@ -168,6 +168,8 @@ MetaCoreRuntimeProjectDocument MetaCoreBuildDefaultRuntimeProjectDocument(
|
||||
document.StartupUiPath = std::filesystem::path("Assets") / "UI" / "Hud.mcui.json";
|
||||
document.BuildProfileName = "Development";
|
||||
document.TargetPlatform = "Windows";
|
||||
document.GraphicsBackend = "OpenGL";
|
||||
document.Architecture = "x64";
|
||||
document.OutputDirectory = std::filesystem::path("Build") / "Windows";
|
||||
document.CookedAssetsDirectory = std::filesystem::path("Library") / "Cooked" / "Windows";
|
||||
document.UseCookedAssets = false;
|
||||
@ -195,6 +197,12 @@ void MetaCoreApplyRuntimeProjectDefaults(
|
||||
if (document.TargetPlatform.empty()) {
|
||||
document.TargetPlatform = defaults.TargetPlatform;
|
||||
}
|
||||
if (document.GraphicsBackend.empty()) {
|
||||
document.GraphicsBackend = defaults.GraphicsBackend;
|
||||
}
|
||||
if (document.Architecture.empty()) {
|
||||
document.Architecture = defaults.Architecture;
|
||||
}
|
||||
if (document.OutputDirectory.empty()) {
|
||||
document.OutputDirectory = defaults.OutputDirectory;
|
||||
}
|
||||
|
||||
@ -141,6 +141,12 @@ struct MetaCoreRuntimeProjectDocument {
|
||||
MC_PROPERTY()
|
||||
std::string TargetPlatform{"Windows"};
|
||||
|
||||
MC_PROPERTY()
|
||||
std::string GraphicsBackend{"OpenGL"};
|
||||
|
||||
MC_PROPERTY()
|
||||
std::string Architecture{"x64"};
|
||||
|
||||
MC_PROPERTY()
|
||||
std::filesystem::path OutputDirectory{};
|
||||
|
||||
|
||||
@ -1821,6 +1821,12 @@ void MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot() {
|
||||
rootMesh.MeshSource = MetaCore::MetaCoreMeshSourceKind::Asset;
|
||||
rootMesh.SourceModelPath = "Assets/Models/SyncRoot.glb";
|
||||
rootMesh.BaseColor = glm::vec3(0.5F, 0.6F, 0.7F);
|
||||
rootMesh.Metallic = 0.35F;
|
||||
rootMesh.Roughness = 0.65F;
|
||||
rootMesh.EmissiveColor = glm::vec3(0.05F, 0.10F, 0.15F);
|
||||
rootMesh.AlphaMode = MetaCore::MetaCoreMeshAlphaMode::Mask;
|
||||
rootMesh.AlphaCutoff = 0.42F;
|
||||
rootMesh.DoubleSided = true;
|
||||
|
||||
MetaCore::MetaCoreGameObject child = scene.CreateGameObject("RenderChild", root.GetId());
|
||||
child.GetComponent<MetaCore::MetaCoreTransformComponent>().Position = glm::vec3(4.0F, 5.0F, 6.0F);
|
||||
@ -1853,6 +1859,12 @@ void MetaCoreTestSceneRenderSyncBuildsRenderableCameraLightSnapshot() {
|
||||
if (renderable.ObjectId == root.GetId()) {
|
||||
MetaCoreExpect(renderable.Visible == true, "RenderSync 根对象 Visible 应为 true");
|
||||
MetaCoreExpectVec3Near(renderable.BaseColor, glm::vec3(0.5F, 0.6F, 0.7F), "RenderSync 根对象 BaseColor 应正确");
|
||||
MetaCoreExpect(std::abs(renderable.Metallic - 0.35F) <= 0.0001F, "RenderSync 根对象 Metallic 应正确");
|
||||
MetaCoreExpect(std::abs(renderable.Roughness - 0.65F) <= 0.0001F, "RenderSync 根对象 Roughness 应正确");
|
||||
MetaCoreExpectVec3Near(renderable.EmissiveColor, glm::vec3(0.05F, 0.10F, 0.15F), "RenderSync 根对象 EmissiveColor 应正确");
|
||||
MetaCoreExpect(renderable.AlphaMode == MetaCore::MetaCoreMeshAlphaMode::Mask, "RenderSync 根对象 AlphaMode 应正确");
|
||||
MetaCoreExpect(std::abs(renderable.AlphaCutoff - 0.42F) <= 0.0001F, "RenderSync 根对象 AlphaCutoff 应正确");
|
||||
MetaCoreExpect(renderable.DoubleSided == true, "RenderSync 根对象 DoubleSided 应正确");
|
||||
} else if (renderable.ObjectId == child.GetId()) {
|
||||
MetaCoreExpect(renderable.Visible == false, "RenderSync 子对象 Visible 应为 false");
|
||||
MetaCoreExpectVec3Near(renderable.BaseColor, glm::vec3(0.1F, 0.2F, 0.3F), "RenderSync 子对象 BaseColor 应正确");
|
||||
@ -2179,6 +2191,73 @@ void MetaCoreTestImportPipelineAndCook() {
|
||||
std::filesystem::remove_all(tempProjectRoot);
|
||||
}
|
||||
|
||||
void MetaCoreTestMissingModelSourceDoesNotRemainRegistered() {
|
||||
const std::filesystem::path tempProjectRoot =
|
||||
std::filesystem::temp_directory_path() / "MetaCoreMissingModelSourceProject";
|
||||
std::filesystem::remove_all(tempProjectRoot);
|
||||
std::filesystem::create_directories(tempProjectRoot / "Assets");
|
||||
std::filesystem::create_directories(tempProjectRoot / "Scenes");
|
||||
std::filesystem::create_directories(tempProjectRoot / "Library");
|
||||
|
||||
{
|
||||
std::ofstream projectFile(tempProjectRoot / "MetaCore.project.json", std::ios::trunc);
|
||||
projectFile << "{\n"
|
||||
<< " \"name\": \"MissingModelSourceProject\",\n"
|
||||
<< " \"version\": \"0.1.0\",\n"
|
||||
<< " \"scenes\": [],\n"
|
||||
<< " \"startup_scene\": \"\"\n"
|
||||
<< "}\n";
|
||||
}
|
||||
|
||||
const std::filesystem::path modelPath = tempProjectRoot / "Assets" / "Tree.gltf";
|
||||
{
|
||||
std::ofstream gltfFile(modelPath, std::ios::trunc);
|
||||
gltfFile << "{\n"
|
||||
<< " \"asset\": {\"version\": \"2.0\"},\n"
|
||||
<< " \"scenes\": [{\"nodes\": [0]}],\n"
|
||||
<< " \"nodes\": [{\"mesh\": 0, \"name\": \"Tree\"}],\n"
|
||||
<< " \"meshes\": [{\"name\": \"TreeMesh\", \"primitives\": [{}]}]\n"
|
||||
<< "}\n";
|
||||
}
|
||||
|
||||
_putenv_s("METACORE_PROJECT_PATH", tempProjectRoot.string().c_str());
|
||||
|
||||
MetaCore::MetaCoreEditorModuleRegistry moduleRegistry;
|
||||
auto coreServicesModule = MetaCore::MetaCoreCreateBuiltinCoreServicesModule();
|
||||
coreServicesModule->Startup(moduleRegistry);
|
||||
|
||||
const auto assetDatabase = moduleRegistry.ResolveService<MetaCore::MetaCoreIAssetDatabaseService>();
|
||||
const auto assetEditingService = moduleRegistry.ResolveService<MetaCore::MetaCoreIAssetEditingService>();
|
||||
MetaCoreExpect(assetDatabase != nullptr, "AssetDatabaseService should be available");
|
||||
MetaCoreExpect(assetEditingService != nullptr, "AssetEditingService should be available");
|
||||
|
||||
const auto modelRecord = assetDatabase->FindAssetByRelativePath(std::filesystem::path("Assets") / "Tree.gltf");
|
||||
MetaCoreExpect(modelRecord.has_value(), "Imported glTF source should be registered");
|
||||
MetaCoreExpect(assetEditingService->LoadModelAsset(modelRecord->Guid).has_value(), "Imported glTF package should be loadable");
|
||||
|
||||
const MetaCore::MetaCoreAssetGuid modelGuid = modelRecord->Guid;
|
||||
const std::filesystem::path packagePath = tempProjectRoot / "Assets" / "Tree.gltf.mcasset";
|
||||
const std::filesystem::path metaPath = tempProjectRoot / "Assets" / "Tree.gltf.mcmeta";
|
||||
MetaCoreExpect(std::filesystem::exists(packagePath), "Model import should write a lightweight mcasset package");
|
||||
MetaCoreExpect(std::filesystem::exists(metaPath), "Model import should write mcmeta");
|
||||
|
||||
std::filesystem::remove(modelPath);
|
||||
MetaCoreExpect(assetDatabase->Refresh(), "Refreshing after external source deletion should not crash");
|
||||
MetaCoreExpect(!assetDatabase->FindAssetByRelativePath(std::filesystem::path("Assets") / "Tree.gltf").has_value(), "Missing source model should not remain registered");
|
||||
MetaCoreExpect(!assetDatabase->FindAssetByRelativePath(std::filesystem::path("Assets") / "Tree.gltf.mcasset").has_value(), "Orphan model package should not be registered as an asset");
|
||||
MetaCoreExpect(!assetEditingService->LoadModelAsset(modelGuid).has_value(), "Deleted model should not be returned from the model cache");
|
||||
|
||||
MetaCore::MetaCoreAssetRegistry::Get().Clear();
|
||||
MetaCore::MetaCoreAssetRegistry::Get().ScanDirectory(tempProjectRoot / "Assets");
|
||||
MetaCoreExpect(MetaCore::MetaCoreAssetRegistry::Get().ResolveGuidToPath(modelGuid).empty(), "Orphan mcmeta should not register a missing source GUID");
|
||||
|
||||
coreServicesModule->Shutdown(moduleRegistry);
|
||||
moduleRegistry.ShutdownServices();
|
||||
_putenv_s("METACORE_PROJECT_PATH", "");
|
||||
MetaCore::MetaCoreAssetRegistry::Get().Clear();
|
||||
std::filesystem::remove_all(tempProjectRoot);
|
||||
}
|
||||
|
||||
void MetaCoreTestProjectDescriptorAndGltfImporterSkeleton() {
|
||||
const std::filesystem::path tempProjectRoot =
|
||||
std::filesystem::temp_directory_path() / "MetaCoreProjectGltfImporterProject";
|
||||
@ -3315,6 +3394,7 @@ void MetaCoreTestBuildServiceCreatesPlayerPackageLayout() {
|
||||
std::filesystem::remove_all(tempProjectRoot);
|
||||
std::filesystem::create_directories(tempProjectRoot / "Assets" / "UI");
|
||||
std::filesystem::create_directories(tempProjectRoot / "Assets" / "Materials");
|
||||
std::filesystem::create_directories(tempProjectRoot / "Assets" / "Models");
|
||||
std::filesystem::create_directories(tempProjectRoot / "Config" / "RuntimeData");
|
||||
std::filesystem::create_directories(tempProjectRoot / "Scenes");
|
||||
std::filesystem::create_directories(tempProjectRoot / "Runtime");
|
||||
@ -3335,6 +3415,10 @@ void MetaCoreTestBuildServiceCreatesPlayerPackageLayout() {
|
||||
std::ofstream fakePlayer(tempProjectRoot / "Tools" / "MetaCorePlayer.exe", std::ios::binary | std::ios::trunc);
|
||||
fakePlayer << "fake player";
|
||||
}
|
||||
{
|
||||
std::ofstream fakeNonExecutablePlayer(tempProjectRoot / "Tools" / "MetaCorePlayer.txt", std::ios::binary | std::ios::trunc);
|
||||
fakeNonExecutablePlayer << "not a Windows executable";
|
||||
}
|
||||
|
||||
_putenv_s("METACORE_PROJECT_PATH", tempProjectRoot.string().c_str());
|
||||
|
||||
@ -3376,6 +3460,35 @@ void MetaCoreTestBuildServiceCreatesPlayerPackageLayout() {
|
||||
"Build package test should write dependency material"
|
||||
);
|
||||
|
||||
const std::filesystem::path modelPath = std::filesystem::path("Assets") / "Models" / "Tree.gltf";
|
||||
const std::filesystem::path modelTexturePath = std::filesystem::path("Assets") / "Models" / "TreeBase.ppm";
|
||||
const std::filesystem::path glbModelPath = std::filesystem::path("Assets") / "Models" / "CookedBox.glb";
|
||||
{
|
||||
const unsigned char modelTextureData[] = {
|
||||
0x50, 0x36, 0x0A, 0x31, 0x20, 0x31, 0x0A, 0x32,
|
||||
0x35, 0x35, 0x0A, 0x30, 0x80, 0x30
|
||||
};
|
||||
std::ofstream textureFile(tempProjectRoot / modelTexturePath, std::ios::binary | std::ios::trunc);
|
||||
textureFile.write(reinterpret_cast<const char*>(modelTextureData), sizeof(modelTextureData));
|
||||
}
|
||||
{
|
||||
std::ofstream gltfFile(tempProjectRoot / modelPath, std::ios::trunc);
|
||||
gltfFile << "{\n"
|
||||
<< " \"asset\": {\"version\": \"2.0\"},\n"
|
||||
<< " \"scenes\": [{\"nodes\": [0]}],\n"
|
||||
<< " \"nodes\": [{\"mesh\": 0, \"name\": \"Tree\"}],\n"
|
||||
<< " \"meshes\": [{\"name\": \"TreeMesh\", \"primitives\": [{\"material\": 0}]}],\n"
|
||||
<< " \"materials\": [{\"name\": \"TreeMaterial\", \"pbrMetallicRoughness\": {\"baseColorTexture\": {\"index\": 0}}}],\n"
|
||||
<< " \"textures\": [{\"source\": 0}],\n"
|
||||
<< " \"images\": [{\"uri\": \"TreeBase.ppm\"}]\n"
|
||||
<< "}\n";
|
||||
}
|
||||
{
|
||||
const std::filesystem::path sandboxGlbPath = std::filesystem::path("D:/MetaCore/SandboxProject/Assets/Models/box1.glb");
|
||||
MetaCoreExpect(std::filesystem::exists(sandboxGlbPath), "Build package test requires SandboxProject box1.glb fixture");
|
||||
std::filesystem::copy_file(sandboxGlbPath, tempProjectRoot / glbModelPath, std::filesystem::copy_options::overwrite_existing);
|
||||
}
|
||||
|
||||
const std::filesystem::path uiPath = std::filesystem::path("Assets") / "UI" / "Hud.mcui.json";
|
||||
MetaCore::MetaCoreUiDocument uiDocument;
|
||||
uiDocument.Name = "Hud";
|
||||
@ -3395,6 +3508,8 @@ void MetaCoreTestBuildServiceCreatesPlayerPackageLayout() {
|
||||
runtimeProject.StartupUiPath = uiPath;
|
||||
runtimeProject.BuildProfileName = "BuildSmokeProfile";
|
||||
runtimeProject.TargetPlatform = "Windows";
|
||||
runtimeProject.GraphicsBackend = "Vulkan";
|
||||
runtimeProject.Architecture = "x64";
|
||||
runtimeProject.OutputDirectory = std::filesystem::path("BuildSettingsOutput");
|
||||
runtimeProject.CookedAssetsDirectory = std::filesystem::path("Library") / "Cooked" / "Windows";
|
||||
runtimeProject.UseCookedAssets = false;
|
||||
@ -3447,15 +3562,29 @@ void MetaCoreTestBuildServiceCreatesPlayerPackageLayout() {
|
||||
MetaCoreExpect(assetDatabase->Refresh(), "Build package test should refresh asset database");
|
||||
const auto materialRecord = assetDatabase->FindAssetByRelativePath(materialPath);
|
||||
MetaCoreExpect(materialRecord.has_value(), "Build package test should register dependency material");
|
||||
const auto modelRecord = assetDatabase->FindAssetByRelativePath(modelPath);
|
||||
MetaCoreExpect(modelRecord.has_value(), "Build package test should register glTF model source");
|
||||
const auto glbModelRecord = assetDatabase->FindAssetByRelativePath(glbModelPath);
|
||||
MetaCoreExpect(glbModelRecord.has_value(), "Build package test should register GLB model source");
|
||||
bool assignedMaterialDependency = false;
|
||||
for (MetaCore::MetaCoreGameObjectData& gameObject : sceneDocument.GameObjects) {
|
||||
if (gameObject.MeshRenderer.has_value()) {
|
||||
gameObject.MeshRenderer->MaterialAssetGuids = {materialRecord->Guid};
|
||||
gameObject.MeshRenderer->SourceModelPath = modelPath.generic_string();
|
||||
assignedMaterialDependency = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
MetaCoreExpect(assignedMaterialDependency, "Build package test startup scene should have a MeshRenderer");
|
||||
|
||||
MetaCore::MetaCoreGameObjectData glbGameObject;
|
||||
glbGameObject.Id = 9001;
|
||||
glbGameObject.Name = "CookedBox";
|
||||
glbGameObject.MeshRenderer = MetaCore::MetaCoreMeshRendererComponent{};
|
||||
glbGameObject.MeshRenderer->MeshSource = MetaCore::MetaCoreMeshSourceKind::Asset;
|
||||
glbGameObject.MeshRenderer->SourceModelAssetGuid = glbModelRecord->Guid;
|
||||
sceneDocument.GameObjects.push_back(std::move(glbGameObject));
|
||||
|
||||
MetaCoreExpect(
|
||||
MetaCore::MetaCoreSceneSerializer::SaveSceneToJson(tempProjectRoot / scenePath, sceneDocument, registry),
|
||||
"Build package test should rewrite startup scene with material dependency"
|
||||
@ -3464,11 +3593,22 @@ void MetaCoreTestBuildServiceCreatesPlayerPackageLayout() {
|
||||
|
||||
MetaCore::MetaCoreBuildPlayerPackageRequest request;
|
||||
request.PlayerExecutablePath = tempProjectRoot / "Tools" / "MetaCorePlayer.exe";
|
||||
request.Target.GraphicsBackend = MetaCore::MetaCoreGraphicsBackend::Vulkan;
|
||||
const MetaCore::MetaCoreBuildPlayerPackageResult result = buildService->BuildPlayerPackage(request);
|
||||
MetaCoreExpect(result.Success, ("BuildService should create player package: " + result.Error).c_str());
|
||||
const std::filesystem::path outputRoot = tempProjectRoot / "BuildSettingsOutput" / "BuildPackageProject";
|
||||
MetaCoreExpect(result.OutputRoot == outputRoot, "BuildService should use runtime project output root");
|
||||
MetaCoreExpect(std::filesystem::exists(outputRoot / "MetaCorePlayer.exe"), "Build output should include MetaCorePlayer.exe");
|
||||
MetaCoreExpect(std::filesystem::exists(outputRoot / "MetaCorePlayer.config.json"), "Build output should include Player launch config");
|
||||
{
|
||||
std::ifstream playerConfigInput(outputRoot / "MetaCorePlayer.config.json", std::ios::binary);
|
||||
nlohmann::json playerConfigJson;
|
||||
playerConfigInput >> playerConfigJson;
|
||||
MetaCoreExpect(playerConfigJson["target"]["platform"] == "Windows", "Player config should record target platform");
|
||||
MetaCoreExpect(playerConfigJson["target"]["graphics_backend"] == "Vulkan", "Player config should record graphics backend");
|
||||
MetaCoreExpect(playerConfigJson["target"]["architecture"] == "x64", "Player config should record architecture");
|
||||
MetaCoreExpect(playerConfigJson["runtime"]["build_profile"] == "BuildSmokeProfile", "Player config should record build profile");
|
||||
}
|
||||
MetaCoreExpect(std::filesystem::exists(outputRoot / "MetaCore.project.json"), "Build output should include project descriptor");
|
||||
const auto packagedProjectFile = MetaCore::MetaCoreReadProjectFile(outputRoot / "MetaCore.project.json");
|
||||
MetaCoreExpect(packagedProjectFile.has_value(), "Build output project descriptor should be readable");
|
||||
@ -3488,6 +3628,8 @@ void MetaCoreTestBuildServiceCreatesPlayerPackageLayout() {
|
||||
);
|
||||
MetaCoreExpect(packagedLooseRuntimeProject.has_value(), "Build output runtime project should be readable");
|
||||
MetaCoreExpect(packagedLooseRuntimeProject->BuildProfileName == "BuildSmokeProfile", "Build output should preserve saved build profile");
|
||||
MetaCoreExpect(packagedLooseRuntimeProject->GraphicsBackend == "Vulkan", "Build output should preserve graphics backend");
|
||||
MetaCoreExpect(packagedLooseRuntimeProject->Architecture == "x64", "Build output should preserve architecture");
|
||||
MetaCoreExpect(packagedLooseRuntimeProject->OutputDirectory == runtimeProject.OutputDirectory, "Build output should preserve saved output directory");
|
||||
MetaCoreExpect(std::filesystem::exists(outputRoot / "Library" / "Cooked" / "Windows" / "CookManifest.bin"), "Build output should include CookManifest");
|
||||
const std::filesystem::path cookedMaterialPath = cookService->GetCookedPathForAsset(materialRecord->Guid);
|
||||
@ -3525,8 +3667,21 @@ void MetaCoreTestBuildServiceCreatesPlayerPackageLayout() {
|
||||
);
|
||||
const std::filesystem::path cookedOnlyOutputRoot =
|
||||
tempProjectRoot / "BuildCooked" / "BuildPackageProject";
|
||||
const std::filesystem::path cookedGlbPath = cookService->GetCookedPathForAsset(glbModelRecord->Guid);
|
||||
const std::filesystem::path cookedGltfPath = cookService->GetCookedPathForAsset(modelRecord->Guid);
|
||||
MetaCoreExpect(!cookedGlbPath.empty(), "Cooked-only build should cook GLB model source");
|
||||
MetaCoreExpect(!cookedGltfPath.empty(), "Cooked-only build should cook glTF model source");
|
||||
MetaCoreExpect(std::filesystem::exists(cookedOnlyOutputRoot / "MetaCorePlayer.exe"), "Cooked-only build output should include MetaCorePlayer.exe");
|
||||
MetaCoreExpect(!std::filesystem::exists(cookedOnlyOutputRoot / scenePath), "Cooked-only build output should not copy loose startup scene");
|
||||
MetaCoreExpect(!std::filesystem::exists(cookedOnlyOutputRoot / modelPath), "Cooked-only build output should not copy loose glTF model source");
|
||||
MetaCoreExpect(!std::filesystem::exists(cookedOnlyOutputRoot / modelTexturePath), "Cooked-only build output should not copy loose glTF local texture dependency");
|
||||
MetaCoreExpect(!std::filesystem::exists(cookedOnlyOutputRoot / glbModelPath), "Cooked-only build output should not copy loose GLB model source");
|
||||
MetaCoreExpect(std::filesystem::exists(cookedOnlyOutputRoot / cookedGltfPath), "Cooked-only build output should include cooked glTF model binary");
|
||||
MetaCoreExpect(
|
||||
std::filesystem::exists(cookedOnlyOutputRoot / cookedGltfPath.parent_path() / "TreeBase.ppm"),
|
||||
"Cooked-only build output should include glTF texture beside cooked model"
|
||||
);
|
||||
MetaCoreExpect(std::filesystem::exists(cookedOnlyOutputRoot / cookedGlbPath), "Cooked-only build output should include cooked GLB model binary");
|
||||
MetaCoreExpect(std::filesystem::exists(cookedOnlyOutputRoot / runtimeProjectPath), "Cooked-only build output should include runtime project config");
|
||||
MetaCoreExpect(std::filesystem::exists(cookedOnlyOutputRoot / runtimeProject.DataSourcesPath), "Cooked-only build output should include custom runtime data sources path");
|
||||
MetaCoreExpect(std::filesystem::exists(cookedOnlyOutputRoot / runtimeProject.BindingsPath), "Cooked-only build output should include custom runtime bindings path");
|
||||
@ -3543,6 +3698,90 @@ void MetaCoreTestBuildServiceCreatesPlayerPackageLayout() {
|
||||
);
|
||||
MetaCoreExpect(!cookedOnlyResult.CookedAssets.empty(), "Cooked-only build result should record cooked assets");
|
||||
|
||||
MetaCore::MetaCoreBuildPlayerPackageRequest invalidWebVulkanRequest;
|
||||
invalidWebVulkanRequest.PlayerExecutablePath = tempProjectRoot / "Tools" / "MetaCorePlayer.exe";
|
||||
invalidWebVulkanRequest.Target.Platform = MetaCore::MetaCoreBuildPlatform::Web;
|
||||
invalidWebVulkanRequest.Target.GraphicsBackend = MetaCore::MetaCoreGraphicsBackend::Vulkan;
|
||||
invalidWebVulkanRequest.Target.Architecture = MetaCore::MetaCoreBuildArchitecture::Wasm32;
|
||||
const MetaCore::MetaCoreBuildPlayerPackageResult invalidWebVulkanResult =
|
||||
buildService->BuildPlayerPackage(invalidWebVulkanRequest);
|
||||
MetaCoreExpect(!invalidWebVulkanResult.Success, "BuildService should reject Web targets with Vulkan graphics");
|
||||
MetaCoreExpect(
|
||||
invalidWebVulkanResult.Error.find("Web build target requires WebGL or WebGPU") != std::string::npos,
|
||||
"BuildService should explain invalid Web graphics backend"
|
||||
);
|
||||
|
||||
MetaCore::MetaCoreBuildPlayerPackageRequest invalidWindowsWebGpuRequest;
|
||||
invalidWindowsWebGpuRequest.PlayerExecutablePath = tempProjectRoot / "Tools" / "MetaCorePlayer.exe";
|
||||
invalidWindowsWebGpuRequest.Target.Platform = MetaCore::MetaCoreBuildPlatform::Windows;
|
||||
invalidWindowsWebGpuRequest.Target.GraphicsBackend = MetaCore::MetaCoreGraphicsBackend::WebGPU;
|
||||
invalidWindowsWebGpuRequest.Target.Architecture = MetaCore::MetaCoreBuildArchitecture::X64;
|
||||
const MetaCore::MetaCoreBuildPlayerPackageResult invalidWindowsWebGpuResult =
|
||||
buildService->BuildPlayerPackage(invalidWindowsWebGpuRequest);
|
||||
MetaCoreExpect(!invalidWindowsWebGpuResult.Success, "BuildService should reject native targets with WebGPU graphics");
|
||||
MetaCoreExpect(
|
||||
invalidWindowsWebGpuResult.Error.find("only valid for Web build targets") != std::string::npos,
|
||||
"BuildService should explain invalid native WebGPU backend"
|
||||
);
|
||||
|
||||
MetaCore::MetaCoreBuildPlayerPackageRequest invalidWebX64Request;
|
||||
invalidWebX64Request.PlayerExecutablePath = tempProjectRoot / "Tools" / "MetaCorePlayer.exe";
|
||||
invalidWebX64Request.Target.Platform = MetaCore::MetaCoreBuildPlatform::Web;
|
||||
invalidWebX64Request.Target.GraphicsBackend = MetaCore::MetaCoreGraphicsBackend::WebGL;
|
||||
invalidWebX64Request.Target.Architecture = MetaCore::MetaCoreBuildArchitecture::X64;
|
||||
const MetaCore::MetaCoreBuildPlayerPackageResult invalidWebX64Result =
|
||||
buildService->BuildPlayerPackage(invalidWebX64Request);
|
||||
MetaCoreExpect(!invalidWebX64Result.Success, "BuildService should reject Web targets without wasm32 architecture");
|
||||
MetaCoreExpect(
|
||||
invalidWebX64Result.Error.find("requires wasm32 architecture") != std::string::npos,
|
||||
"BuildService should explain invalid Web architecture"
|
||||
);
|
||||
|
||||
MetaCore::MetaCoreBuildPlayerPackageRequest unsupportedWebRequest;
|
||||
unsupportedWebRequest.PlayerExecutablePath = tempProjectRoot / "Tools" / "MetaCorePlayer.exe";
|
||||
unsupportedWebRequest.Target.Platform = MetaCore::MetaCoreBuildPlatform::Web;
|
||||
unsupportedWebRequest.Target.GraphicsBackend = MetaCore::MetaCoreGraphicsBackend::WebGPU;
|
||||
unsupportedWebRequest.Target.Architecture = MetaCore::MetaCoreBuildArchitecture::Wasm32;
|
||||
const MetaCore::MetaCoreBuildPlayerPackageResult unsupportedWebResult =
|
||||
buildService->BuildPlayerPackage(unsupportedWebRequest);
|
||||
MetaCoreExpect(!unsupportedWebResult.Success, "BuildService should reject Web packaging until implemented");
|
||||
MetaCoreExpect(
|
||||
unsupportedWebResult.Error.find("Web build packaging is not implemented yet") != std::string::npos,
|
||||
"BuildService should explain that Web packaging is not implemented"
|
||||
);
|
||||
|
||||
MetaCore::MetaCoreBuildPlayerPackageRequest unsupportedLinuxRequest;
|
||||
unsupportedLinuxRequest.PlayerExecutablePath = tempProjectRoot / "Tools" / "MetaCorePlayer.exe";
|
||||
unsupportedLinuxRequest.Target.Platform = MetaCore::MetaCoreBuildPlatform::Linux;
|
||||
unsupportedLinuxRequest.Target.GraphicsBackend = MetaCore::MetaCoreGraphicsBackend::Vulkan;
|
||||
unsupportedLinuxRequest.Target.Architecture = MetaCore::MetaCoreBuildArchitecture::X64;
|
||||
const MetaCore::MetaCoreBuildPlayerPackageResult unsupportedLinuxResult =
|
||||
buildService->BuildPlayerPackage(unsupportedLinuxRequest);
|
||||
MetaCoreExpect(!unsupportedLinuxResult.Success, "BuildService should reject non-Windows packaging until implemented");
|
||||
MetaCoreExpect(
|
||||
unsupportedLinuxResult.Error.find("Windows Player packages only") != std::string::npos,
|
||||
"BuildService should explain that only Windows packages are currently implemented"
|
||||
);
|
||||
|
||||
MetaCore::MetaCoreBuildPlayerPackageRequest invalidWindowsPlayerPathRequest;
|
||||
invalidWindowsPlayerPathRequest.PlayerExecutablePath = tempProjectRoot / "Tools" / "MetaCorePlayer.txt";
|
||||
invalidWindowsPlayerPathRequest.Target.Platform = MetaCore::MetaCoreBuildPlatform::Windows;
|
||||
invalidWindowsPlayerPathRequest.Target.GraphicsBackend = MetaCore::MetaCoreGraphicsBackend::OpenGL;
|
||||
invalidWindowsPlayerPathRequest.Target.Architecture = MetaCore::MetaCoreBuildArchitecture::X64;
|
||||
invalidWindowsPlayerPathRequest.OutputDirectory = tempProjectRoot / "InvalidPlayerPathOutput";
|
||||
invalidWindowsPlayerPathRequest.CookBeforePackage = false;
|
||||
const MetaCore::MetaCoreBuildPlayerPackageResult invalidWindowsPlayerPathResult =
|
||||
buildService->BuildPlayerPackage(invalidWindowsPlayerPathRequest);
|
||||
MetaCoreExpect(!invalidWindowsPlayerPathResult.Success, "BuildService should reject non-exe Windows Player paths");
|
||||
MetaCoreExpect(
|
||||
invalidWindowsPlayerPathResult.Error.find("requires a .exe player executable") != std::string::npos,
|
||||
"BuildService should explain invalid Windows Player executable extension"
|
||||
);
|
||||
MetaCoreExpect(
|
||||
!std::filesystem::exists(tempProjectRoot / "InvalidPlayerPathOutput"),
|
||||
"BuildService should reject invalid Player paths before creating output directories"
|
||||
);
|
||||
|
||||
runtimeProject.DataSourcesPath = std::filesystem::path("..") / "OutsideDataSources.mcruntime";
|
||||
MetaCoreExpect(
|
||||
MetaCore::MetaCoreWriteRuntimeProjectDocument(tempProjectRoot / runtimeProjectPath, runtimeProject, registry),
|
||||
@ -3852,6 +4091,8 @@ void MetaCoreTestRuntimeProjectDocumentIo() {
|
||||
writtenDocument.StartupUiPath = std::filesystem::path("Assets") / "UI" / "Hud.mcui.json";
|
||||
writtenDocument.BuildProfileName = "Shipping";
|
||||
writtenDocument.TargetPlatform = "Windows";
|
||||
writtenDocument.GraphicsBackend = "Vulkan";
|
||||
writtenDocument.Architecture = "x64";
|
||||
writtenDocument.OutputDirectory = std::filesystem::path("Build") / "Windows";
|
||||
writtenDocument.CookedAssetsDirectory = std::filesystem::path("Library") / "Cooked" / "Windows";
|
||||
writtenDocument.UseCookedAssets = true;
|
||||
@ -3869,6 +4110,8 @@ void MetaCoreTestRuntimeProjectDocumentIo() {
|
||||
MetaCoreExpect(loadedDocument->StartupUiPath == writtenDocument.StartupUiPath, "StartupUiPath should round trip");
|
||||
MetaCoreExpect(loadedDocument->BuildProfileName == writtenDocument.BuildProfileName, "BuildProfileName should round trip");
|
||||
MetaCoreExpect(loadedDocument->TargetPlatform == writtenDocument.TargetPlatform, "TargetPlatform should round trip");
|
||||
MetaCoreExpect(loadedDocument->GraphicsBackend == writtenDocument.GraphicsBackend, "GraphicsBackend should round trip");
|
||||
MetaCoreExpect(loadedDocument->Architecture == writtenDocument.Architecture, "Architecture should round trip");
|
||||
MetaCoreExpect(loadedDocument->OutputDirectory == writtenDocument.OutputDirectory, "OutputDirectory should round trip");
|
||||
MetaCoreExpect(loadedDocument->CookedAssetsDirectory == writtenDocument.CookedAssetsDirectory, "CookedAssetsDirectory should round trip");
|
||||
MetaCoreExpect(loadedDocument->UseCookedAssets == writtenDocument.UseCookedAssets, "UseCookedAssets should round trip");
|
||||
@ -3890,6 +4133,8 @@ void MetaCoreTestRuntimeProjectDocumentIo() {
|
||||
defaultRuntimeProject.StartupScenePath == std::filesystem::path("Scenes") / "Main.mcscene.json",
|
||||
"RuntimeProject defaults should use the JSON scene asset extension"
|
||||
);
|
||||
MetaCoreExpect(defaultRuntimeProject.GraphicsBackend == "OpenGL", "RuntimeProject defaults should use OpenGL graphics");
|
||||
MetaCoreExpect(defaultRuntimeProject.Architecture == "x64", "RuntimeProject defaults should use x64 architecture");
|
||||
MetaCoreExpect(
|
||||
defaultRuntimeProject.DataSourcesPath == std::filesystem::path("ConfigRuntime") / "DataSources.mcruntime",
|
||||
"RuntimeProject defaults should place DataSources under runtime_directory"
|
||||
@ -6147,6 +6392,8 @@ int main() {
|
||||
MetaCoreTestStaticCppComponentScenePersistenceAndUndo();
|
||||
std::cout << "[RUN] MetaCoreTestImportPipelineAndCook..." << std::endl;
|
||||
MetaCoreTestImportPipelineAndCook();
|
||||
std::cout << "[RUN] MetaCoreTestMissingModelSourceDoesNotRemainRegistered..." << std::endl;
|
||||
MetaCoreTestMissingModelSourceDoesNotRemainRegistered();
|
||||
std::cout << "[RUN] MetaCoreTestProjectDescriptorAndGltfImporterSkeleton..." << std::endl;
|
||||
MetaCoreTestProjectDescriptorAndGltfImporterSkeleton();
|
||||
std::cout << "[RUN] MetaCoreTestInstantiateImportedModelAssetIntoScene..." << std::endl;
|
||||
|
||||
@ -16,12 +16,67 @@ void MetaCorePrintUsage() {
|
||||
<< "Options:\n"
|
||||
<< " --player <path> Path to MetaCorePlayer.exe\n"
|
||||
<< " --output <directory> Build output base directory\n"
|
||||
<< " --platform <windows|linux|android|web>\n"
|
||||
<< " --graphics <opengl|vulkan|webgl|webgpu>\n"
|
||||
<< " --arch <x64|arm64|wasm32>\n"
|
||||
<< " --no-cook Package without cooking first\n"
|
||||
<< " --no-loose-content Do not copy loose Assets/Scenes\n"
|
||||
<< " --no-runtime-config Do not copy Runtime configuration\n"
|
||||
<< " --use-cooked-assets Enable cooked asset loading in packaged runtime config\n";
|
||||
}
|
||||
|
||||
[[nodiscard]] bool MetaCoreParseBuildPlatform(
|
||||
std::string_view value,
|
||||
MetaCore::MetaCoreBuildPlatform& outputPlatform
|
||||
) {
|
||||
if (value == "windows") {
|
||||
outputPlatform = MetaCore::MetaCoreBuildPlatform::Windows;
|
||||
} else if (value == "linux") {
|
||||
outputPlatform = MetaCore::MetaCoreBuildPlatform::Linux;
|
||||
} else if (value == "android") {
|
||||
outputPlatform = MetaCore::MetaCoreBuildPlatform::Android;
|
||||
} else if (value == "web") {
|
||||
outputPlatform = MetaCore::MetaCoreBuildPlatform::Web;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool MetaCoreParseGraphicsBackend(
|
||||
std::string_view value,
|
||||
MetaCore::MetaCoreGraphicsBackend& outputBackend
|
||||
) {
|
||||
if (value == "opengl") {
|
||||
outputBackend = MetaCore::MetaCoreGraphicsBackend::OpenGL;
|
||||
} else if (value == "vulkan") {
|
||||
outputBackend = MetaCore::MetaCoreGraphicsBackend::Vulkan;
|
||||
} else if (value == "webgl") {
|
||||
outputBackend = MetaCore::MetaCoreGraphicsBackend::WebGL;
|
||||
} else if (value == "webgpu") {
|
||||
outputBackend = MetaCore::MetaCoreGraphicsBackend::WebGPU;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool MetaCoreParseBuildArchitecture(
|
||||
std::string_view value,
|
||||
MetaCore::MetaCoreBuildArchitecture& outputArchitecture
|
||||
) {
|
||||
if (value == "x64") {
|
||||
outputArchitecture = MetaCore::MetaCoreBuildArchitecture::X64;
|
||||
} else if (value == "arm64") {
|
||||
outputArchitecture = MetaCore::MetaCoreBuildArchitecture::Arm64;
|
||||
} else if (value == "wasm32") {
|
||||
outputArchitecture = MetaCore::MetaCoreBuildArchitecture::Wasm32;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool MetaCoreSetProjectPathEnvironment(const std::filesystem::path& projectRoot) {
|
||||
#if defined(_WIN32)
|
||||
return _putenv_s("METACORE_PROJECT_PATH", projectRoot.string().c_str()) == 0;
|
||||
@ -103,6 +158,21 @@ int main(int argc, char* argv[]) {
|
||||
std::cerr << "--output requires a directory\n";
|
||||
return 1;
|
||||
}
|
||||
} else if (argument == "--platform") {
|
||||
if (index + 1 >= argc || !MetaCoreParseBuildPlatform(argv[++index], request.Target.Platform)) {
|
||||
std::cerr << "--platform requires one of: windows, linux, android, web\n";
|
||||
return 1;
|
||||
}
|
||||
} else if (argument == "--graphics") {
|
||||
if (index + 1 >= argc || !MetaCoreParseGraphicsBackend(argv[++index], request.Target.GraphicsBackend)) {
|
||||
std::cerr << "--graphics requires one of: opengl, vulkan, webgl, webgpu\n";
|
||||
return 1;
|
||||
}
|
||||
} else if (argument == "--arch") {
|
||||
if (index + 1 >= argc || !MetaCoreParseBuildArchitecture(argv[++index], request.Target.Architecture)) {
|
||||
std::cerr << "--arch requires one of: x64, arm64, wasm32\n";
|
||||
return 1;
|
||||
}
|
||||
} else if (argument == "--no-cook") {
|
||||
request.CookBeforePackage = false;
|
||||
} else if (argument == "--no-loose-content") {
|
||||
|
||||
24
vcpkg.json
24
vcpkg.json
@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "metacore",
|
||||
"version-string": "1.0.0",
|
||||
"builtin-baseline": "717f2c1db552beed995736e41accdf4f6f4ca986",
|
||||
"dependencies": [
|
||||
"glm",
|
||||
"entt",
|
||||
@ -16,14 +17,21 @@
|
||||
{
|
||||
"name": "rmlui",
|
||||
"default-features": false
|
||||
},
|
||||
{
|
||||
"name": "qtbase",
|
||||
"default-features": false,
|
||||
"features": [
|
||||
"gui",
|
||||
"widgets"
|
||||
}
|
||||
],
|
||||
"features": {
|
||||
"launcher": {
|
||||
"description": "Build the Qt-based MetaCore launcher.",
|
||||
"dependencies": [
|
||||
{
|
||||
"name": "qtbase",
|
||||
"default-features": false,
|
||||
"features": [
|
||||
"gui",
|
||||
"widgets"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user