feat(editor): refine scene workflow and viewport grid
This commit is contained in:
parent
f9083c4aec
commit
35b394ee37
@ -62,6 +62,8 @@ endif()
|
||||
|
||||
set(METACORE_UI_BLIT_MATERIAL_SOURCE "${CMAKE_SOURCE_DIR}/third_party/filament/libs/filagui/src/materials/uiBlit.mat")
|
||||
set(METACORE_UI_BLIT_MATERIAL_OUTPUT "${CMAKE_BINARY_DIR}/uiBlit.filamat")
|
||||
set(METACORE_EDITOR_GRID_MATERIAL_SOURCE "${CMAKE_SOURCE_DIR}/Source/MetaCoreRender/Private/editorGrid.mat")
|
||||
set(METACORE_EDITOR_GRID_MATERIAL_OUTPUT "${CMAKE_BINARY_DIR}/editorGrid.filamat")
|
||||
set(METACORE_FILAMENT_MATC "${METACORE_FILAMENT_ROOT}/bin/matc.exe")
|
||||
|
||||
add_custom_command(
|
||||
@ -81,12 +83,33 @@ add_custom_target(MetaCoreUiBlitMaterial
|
||||
DEPENDS "${METACORE_UI_BLIT_MATERIAL_OUTPUT}"
|
||||
)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${METACORE_EDITOR_GRID_MATERIAL_OUTPUT}"
|
||||
COMMAND "${METACORE_FILAMENT_MATC}"
|
||||
--platform desktop
|
||||
--api all
|
||||
--output "${METACORE_EDITOR_GRID_MATERIAL_OUTPUT}"
|
||||
"${METACORE_EDITOR_GRID_MATERIAL_SOURCE}"
|
||||
DEPENDS
|
||||
"${METACORE_EDITOR_GRID_MATERIAL_SOURCE}"
|
||||
"${METACORE_FILAMENT_MATC}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_custom_target(MetaCoreEditorGridMaterial
|
||||
DEPENDS "${METACORE_EDITOR_GRID_MATERIAL_OUTPUT}"
|
||||
)
|
||||
|
||||
function(metacore_stage_ui_blit_material target_name)
|
||||
add_dependencies(${target_name} MetaCoreUiBlitMaterial)
|
||||
add_dependencies(${target_name} MetaCoreEditorGridMaterial)
|
||||
add_custom_command(TARGET ${target_name} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${METACORE_UI_BLIT_MATERIAL_OUTPUT}"
|
||||
"$<TARGET_FILE_DIR:${target_name}>/uiBlit.filamat"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${METACORE_EDITOR_GRID_MATERIAL_OUTPUT}"
|
||||
"$<TARGET_FILE_DIR:${target_name}>/editorGrid.filamat"
|
||||
VERBATIM
|
||||
)
|
||||
endfunction()
|
||||
|
||||
@ -742,70 +742,6 @@ void MetaCoreReimportProjectAsset(MetaCoreEditorContext& editorContext, const Me
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCoreEnsureProjectStartupScene(MetaCoreEditorContext& editorContext) {
|
||||
const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
|
||||
const auto scenePersistenceService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIScenePersistenceService>();
|
||||
if (assetDatabaseService == nullptr || scenePersistenceService == nullptr || !assetDatabaseService->HasProject()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scenePersistenceService->LoadStartupScene(editorContext)) {
|
||||
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Project", "已加载项目启动场景");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!assetDatabaseService->GetProjectDescriptor().ScenePaths.empty()) {
|
||||
editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Project", "启动场景不可用,已保留当前场景");
|
||||
return;
|
||||
}
|
||||
|
||||
editorContext.GetScene().RestoreSnapshot(MetaCoreCreateDefaultScene().CaptureSnapshot());
|
||||
editorContext.ClearSelection();
|
||||
const std::filesystem::path bootstrapScenePath = std::filesystem::path("Scenes") / "Main.mcscene.json";
|
||||
if (scenePersistenceService->SaveSceneAs(editorContext, bootstrapScenePath)) {
|
||||
(void)assetDatabaseService->SetStartupScenePath(bootstrapScenePath);
|
||||
editorContext.AddConsoleMessage(MetaCoreLogLevel::Info, "Project", "已为新项目创建默认启动场景");
|
||||
} else {
|
||||
editorContext.AddConsoleMessage(MetaCoreLogLevel::Warning, "Project", "项目已打开,但默认启动场景创建失败");
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCoreOpenProject(MetaCoreEditorContext& editorContext, const std::filesystem::path& projectRoot) {
|
||||
const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
|
||||
if (assetDatabaseService == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (assetDatabaseService->OpenProject(projectRoot)) {
|
||||
MetaCoreEnsureProjectStartupScene(editorContext);
|
||||
editorContext.AddConsoleMessage(
|
||||
MetaCoreLogLevel::Info,
|
||||
"Project",
|
||||
"已打开项目: " + assetDatabaseService->GetProjectDescriptor().RootPath.string()
|
||||
);
|
||||
} else {
|
||||
editorContext.AddConsoleMessage(MetaCoreLogLevel::Error, "Project", "打开项目失败");
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCoreCreateProject(MetaCoreEditorContext& editorContext, const std::filesystem::path& projectRoot, const std::string& projectName) {
|
||||
const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
|
||||
if (assetDatabaseService == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (assetDatabaseService->CreateProject(projectRoot, projectName)) {
|
||||
MetaCoreEnsureProjectStartupScene(editorContext);
|
||||
editorContext.AddConsoleMessage(
|
||||
MetaCoreLogLevel::Info,
|
||||
"Project",
|
||||
"已创建项目: " + assetDatabaseService->GetProjectDescriptor().RootPath.string()
|
||||
);
|
||||
} else {
|
||||
editorContext.AddConsoleMessage(MetaCoreLogLevel::Error, "Project", "创建项目失败");
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCoreHandleGlobalEditorShortcuts(MetaCoreEditorContext& editorContext) {
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
if (io.WantTextInput) {
|
||||
@ -2537,25 +2473,27 @@ public:
|
||||
const auto scenePersistenceService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIScenePersistenceService>();
|
||||
const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
|
||||
|
||||
// 顶级菜单 1:项目 (对应 Project)
|
||||
if (ImGui::BeginMenu("项目")) {
|
||||
if (ImGui::MenuItem("新建项目...")) {
|
||||
if (assetDatabaseService != nullptr && assetDatabaseService->HasProject()) {
|
||||
const auto& project = assetDatabaseService->GetProjectDescriptor();
|
||||
std::snprintf(CreateProjectPathBuffer_, sizeof(CreateProjectPathBuffer_), "%s", project.RootPath.parent_path().string().c_str());
|
||||
} else {
|
||||
std::snprintf(CreateProjectPathBuffer_, sizeof(CreateProjectPathBuffer_), "%s", std::filesystem::current_path().string().c_str());
|
||||
}
|
||||
std::snprintf(CreateProjectNameBuffer_, sizeof(CreateProjectNameBuffer_), "%s", "NewMetaCoreProject");
|
||||
ImGui::OpenPopup("MetaCoreCreateProjectPopup");
|
||||
// 顶级菜单 1:场景。项目创建/打开由 Hub 负责,Editor 内只处理当前项目中的场景。
|
||||
if (ImGui::BeginMenu("场景")) {
|
||||
if (ImGui::MenuItem("新建场景...", nullptr, false, assetDatabaseService != nullptr && assetDatabaseService->HasProject())) {
|
||||
std::snprintf(NewScenePathBuffer_, sizeof(NewScenePathBuffer_), "%s", "Scenes/NewScene.mcscene.json");
|
||||
ImGui::OpenPopup("MetaCoreCreateScenePopup");
|
||||
}
|
||||
if (ImGui::MenuItem("打开项目...")) {
|
||||
if (assetDatabaseService != nullptr && assetDatabaseService->HasProject()) {
|
||||
std::snprintf(OpenProjectPathBuffer_, sizeof(OpenProjectPathBuffer_), "%s", assetDatabaseService->GetProjectDescriptor().RootPath.string().c_str());
|
||||
} else {
|
||||
std::snprintf(OpenProjectPathBuffer_, sizeof(OpenProjectPathBuffer_), "%s", std::filesystem::current_path().string().c_str());
|
||||
if (ImGui::MenuItem("打开场景...", nullptr, false, scenePersistenceService != nullptr && assetDatabaseService != nullptr && assetDatabaseService->HasProject())) {
|
||||
const std::filesystem::path defaultScenePath = scenePersistenceService != nullptr && scenePersistenceService->HasOpenScene()
|
||||
? scenePersistenceService->GetCurrentScenePath()
|
||||
: std::filesystem::path("Scenes") / "Main.mcscene.json";
|
||||
std::snprintf(OpenScenePathBuffer_, sizeof(OpenScenePathBuffer_), "%s", defaultScenePath.generic_string().c_str());
|
||||
ImGui::OpenPopup("MetaCoreOpenScenePopup");
|
||||
}
|
||||
if (scenePersistenceService != nullptr && ImGui::BeginMenu("项目场景", !scenePersistenceService->GetKnownScenePaths().empty())) {
|
||||
for (const std::filesystem::path& scenePath : scenePersistenceService->GetKnownScenePaths()) {
|
||||
if (ImGui::MenuItem(scenePath.generic_string().c_str())) {
|
||||
PendingScenePath_ = scenePath;
|
||||
RequestSceneMenuAction(editorContext, scenePersistenceService, PendingSceneAction::OpenScene);
|
||||
}
|
||||
}
|
||||
ImGui::OpenPopup("MetaCoreOpenProjectPopup");
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem("保存场景", "Ctrl+S", false, scenePersistenceService != nullptr)) {
|
||||
@ -2594,20 +2532,19 @@ public:
|
||||
ImGui::MenuItem("当前运行模式:MetaCore 编辑器", nullptr, false, false);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
|
||||
if (ImGui::MenuItem("退出")) {
|
||||
editorContext.GetWindow().RequestClose();
|
||||
RequestSceneMenuAction(editorContext, scenePersistenceService, PendingSceneAction::Exit);
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
// 新建项目弹窗中文化
|
||||
if (ImGui::BeginPopupModal("MetaCoreCreateProjectPopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::InputText("项目目录", CreateProjectPathBuffer_, sizeof(CreateProjectPathBuffer_));
|
||||
ImGui::InputText("项目名称", CreateProjectNameBuffer_, sizeof(CreateProjectNameBuffer_));
|
||||
if (ImGui::BeginPopupModal("MetaCoreCreateScenePopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::TextDisabled("相对于当前项目根目录");
|
||||
ImGui::InputText("场景路径", NewScenePathBuffer_, sizeof(NewScenePathBuffer_));
|
||||
if (ImGui::Button("创建")) {
|
||||
const std::filesystem::path projectRoot = std::filesystem::path(CreateProjectPathBuffer_) / std::filesystem::path(CreateProjectNameBuffer_);
|
||||
MetaCoreCreateProject(editorContext, projectRoot, CreateProjectNameBuffer_);
|
||||
PendingScenePath_ = std::filesystem::path(NewScenePathBuffer_);
|
||||
RequestSceneMenuAction(editorContext, scenePersistenceService, PendingSceneAction::NewScene);
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
@ -2617,11 +2554,12 @@ public:
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
// 打开项目弹窗中文化
|
||||
if (ImGui::BeginPopupModal("MetaCoreOpenProjectPopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::InputText("项目路径", OpenProjectPathBuffer_, sizeof(OpenProjectPathBuffer_));
|
||||
if (ImGui::BeginPopupModal("MetaCoreOpenScenePopup", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
ImGui::TextDisabled("相对于当前项目根目录");
|
||||
ImGui::InputText("场景路径", OpenScenePathBuffer_, sizeof(OpenScenePathBuffer_));
|
||||
if (ImGui::Button("打开")) {
|
||||
MetaCoreOpenProject(editorContext, std::filesystem::path(OpenProjectPathBuffer_));
|
||||
PendingScenePath_ = std::filesystem::path(OpenScenePathBuffer_);
|
||||
RequestSceneMenuAction(editorContext, scenePersistenceService, PendingSceneAction::OpenScene);
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
@ -2631,6 +2569,8 @@ public:
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
|
||||
DrawPendingSceneActionPopup(editorContext, scenePersistenceService);
|
||||
|
||||
// 顶级菜单 2:编辑 (对应 Edit)
|
||||
if (ImGui::BeginMenu("编辑")) {
|
||||
if (ImGui::MenuItem("撤销", "Ctrl+Z", false, editorContext.GetCommandService().CanUndo())) {
|
||||
@ -2701,9 +2641,119 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
char CreateProjectPathBuffer_[512]{};
|
||||
char CreateProjectNameBuffer_[256]{};
|
||||
char OpenProjectPathBuffer_[512]{};
|
||||
enum class PendingSceneAction {
|
||||
None,
|
||||
NewScene,
|
||||
OpenScene,
|
||||
Exit
|
||||
};
|
||||
|
||||
void RequestSceneMenuAction(
|
||||
MetaCoreEditorContext& editorContext,
|
||||
const std::shared_ptr<MetaCoreIScenePersistenceService>& scenePersistenceService,
|
||||
PendingSceneAction action
|
||||
) {
|
||||
if (action == PendingSceneAction::None) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scenePersistenceService == nullptr || !scenePersistenceService->IsSceneDirty()) {
|
||||
ExecuteSceneMenuAction(editorContext, scenePersistenceService, action);
|
||||
return;
|
||||
}
|
||||
|
||||
PendingAction_ = action;
|
||||
OpenPendingActionPopup_ = true;
|
||||
}
|
||||
|
||||
void ExecuteSceneMenuAction(
|
||||
MetaCoreEditorContext& editorContext,
|
||||
const std::shared_ptr<MetaCoreIScenePersistenceService>& scenePersistenceService,
|
||||
PendingSceneAction action
|
||||
) {
|
||||
switch (action) {
|
||||
case PendingSceneAction::NewScene:
|
||||
CreateAndOpenScene(editorContext, scenePersistenceService);
|
||||
break;
|
||||
case PendingSceneAction::OpenScene:
|
||||
if (scenePersistenceService != nullptr && !PendingScenePath_.empty()) {
|
||||
(void)scenePersistenceService->LoadScene(editorContext, PendingScenePath_);
|
||||
}
|
||||
break;
|
||||
case PendingSceneAction::Exit:
|
||||
editorContext.GetWindow().RequestClose();
|
||||
break;
|
||||
case PendingSceneAction::None:
|
||||
break;
|
||||
}
|
||||
|
||||
PendingAction_ = PendingSceneAction::None;
|
||||
PendingScenePath_.clear();
|
||||
}
|
||||
|
||||
void DrawPendingSceneActionPopup(
|
||||
MetaCoreEditorContext& editorContext,
|
||||
const std::shared_ptr<MetaCoreIScenePersistenceService>& scenePersistenceService
|
||||
) {
|
||||
if (OpenPendingActionPopup_) {
|
||||
ImGui::OpenPopup("Scene Action Unsaved Scene Changes");
|
||||
OpenPendingActionPopup_ = false;
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupModal("Scene Action Unsaved Scene Changes", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
const std::string currentScene = scenePersistenceService != nullptr && scenePersistenceService->HasOpenScene()
|
||||
? scenePersistenceService->GetCurrentSceneDisplayName()
|
||||
: std::string("Untitled");
|
||||
|
||||
ImGui::Text("Save changes to %s before continuing?", currentScene.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::Button("Save and Continue", ImVec2(150, 0))) {
|
||||
if (scenePersistenceService == nullptr || scenePersistenceService->SaveCurrentScene(editorContext)) {
|
||||
ExecuteSceneMenuAction(editorContext, scenePersistenceService, PendingAction_);
|
||||
ImGui::CloseCurrentPopup();
|
||||
} else {
|
||||
editorContext.AddConsoleMessage(MetaCoreLogLevel::Error, "Scene", "保存失败,已取消操作");
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Don't Save", ImVec2(110, 0))) {
|
||||
ExecuteSceneMenuAction(editorContext, scenePersistenceService, PendingAction_);
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel", ImVec2(90, 0))) {
|
||||
PendingAction_ = PendingSceneAction::None;
|
||||
PendingScenePath_.clear();
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
void CreateAndOpenScene(
|
||||
MetaCoreEditorContext& editorContext,
|
||||
const std::shared_ptr<MetaCoreIScenePersistenceService>& scenePersistenceService
|
||||
) {
|
||||
const auto assetDatabaseService = editorContext.GetModuleRegistry().ResolveService<MetaCoreIAssetDatabaseService>();
|
||||
if (assetDatabaseService == nullptr || scenePersistenceService == nullptr || PendingScenePath_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto createdScene = assetDatabaseService->CreateSceneAsset(PendingScenePath_, false);
|
||||
if (!createdScene.has_value()) {
|
||||
editorContext.AddConsoleMessage(MetaCoreLogLevel::Error, "Scene", "创建场景失败: " + PendingScenePath_.generic_string());
|
||||
return;
|
||||
}
|
||||
|
||||
(void)scenePersistenceService->LoadScene(editorContext, createdScene->RelativePath);
|
||||
}
|
||||
|
||||
char NewScenePathBuffer_[512]{};
|
||||
char OpenScenePathBuffer_[512]{};
|
||||
std::filesystem::path PendingScenePath_{};
|
||||
PendingSceneAction PendingAction_ = PendingSceneAction::None;
|
||||
bool OpenPendingActionPopup_ = false;
|
||||
};
|
||||
|
||||
class MetaCoreHierarchyPanelProvider final : public MetaCoreIEditorPanelProvider {
|
||||
|
||||
@ -21,6 +21,7 @@
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <exception>
|
||||
#include <filesystem>
|
||||
#include <glm/ext/matrix_clip_space.hpp>
|
||||
@ -297,9 +298,9 @@ bool MetaCoreProjectWorldPointToViewport(
|
||||
}
|
||||
|
||||
void MetaCoreDrawWorldOriginOverlay(
|
||||
const MetaCoreEditorContext& editorContext,
|
||||
const MetaCoreSceneView& sceneView,
|
||||
const MetaCoreSceneViewportState& viewportState
|
||||
const MetaCoreEditorContext&,
|
||||
const MetaCoreSceneView&,
|
||||
const MetaCoreSceneViewportState&
|
||||
) {
|
||||
// 用户要求去掉小 XYZ 轴和原点指示
|
||||
return;
|
||||
@ -307,62 +308,161 @@ void MetaCoreDrawWorldOriginOverlay(
|
||||
|
||||
void MetaCoreDrawViewportGridOverlay(
|
||||
const MetaCoreEditorContext& editorContext,
|
||||
const MetaCoreSceneView& sceneView,
|
||||
const MetaCoreSceneViewportState& viewportState
|
||||
) {
|
||||
if (!editorContext.GetShowViewportGrid()) {
|
||||
(void)editorContext;
|
||||
(void)sceneView;
|
||||
(void)viewportState;
|
||||
// The editor grid is rendered by Filament as a real 3D ground plane.
|
||||
return;
|
||||
|
||||
if (!editorContext.GetShowViewportGrid() || viewportState.Width <= 1.0F || viewportState.Height <= 1.0F) {
|
||||
return;
|
||||
}
|
||||
if (viewportState.Width <= 1.0F || viewportState.Height <= 1.0F) {
|
||||
return;
|
||||
|
||||
constexpr float gridY = 0.0F;
|
||||
constexpr float gridSpacing = 1.0F;
|
||||
constexpr int majorLineInterval = 5;
|
||||
constexpr int maxVisibleLines = 240;
|
||||
constexpr float fallbackHalfExtent = 80.0F;
|
||||
|
||||
const glm::mat4 viewMatrix = glm::lookAt(sceneView.CameraPosition, sceneView.CameraTarget, sceneView.CameraUp);
|
||||
const glm::mat4 projectionMatrix = glm::perspective(
|
||||
glm::radians(sceneView.VerticalFieldOfViewDegrees),
|
||||
viewportState.Width / viewportState.Height,
|
||||
0.05F,
|
||||
500.0F
|
||||
);
|
||||
const glm::mat4 inverseViewProjection = glm::inverse(projectionMatrix * viewMatrix);
|
||||
|
||||
const auto raycastGroundAtViewportPoint = [&](float screenX, float screenY, glm::vec3& groundPoint) -> bool {
|
||||
const float ndcX = ((screenX - viewportState.Left) / viewportState.Width) * 2.0F - 1.0F;
|
||||
const float ndcY = 1.0F - ((screenY - viewportState.Top) / viewportState.Height) * 2.0F;
|
||||
glm::vec4 nearPoint = inverseViewProjection * glm::vec4(ndcX, ndcY, -1.0F, 1.0F);
|
||||
glm::vec4 farPoint = inverseViewProjection * glm::vec4(ndcX, ndcY, 1.0F, 1.0F);
|
||||
if (std::abs(nearPoint.w) <= 0.0001F || std::abs(farPoint.w) <= 0.0001F) {
|
||||
return false;
|
||||
}
|
||||
nearPoint /= nearPoint.w;
|
||||
farPoint /= farPoint.w;
|
||||
const glm::vec3 rayOrigin = glm::vec3(nearPoint);
|
||||
const glm::vec3 rayDirection = glm::normalize(glm::vec3(farPoint - nearPoint));
|
||||
if (std::abs(rayDirection.y) <= 0.0001F) {
|
||||
return false;
|
||||
}
|
||||
const float t = (gridY - rayOrigin.y) / rayDirection.y;
|
||||
if (t <= 0.0F) {
|
||||
return false;
|
||||
}
|
||||
groundPoint = rayOrigin + rayDirection * t;
|
||||
return true;
|
||||
};
|
||||
|
||||
std::vector<glm::vec3> groundPoints;
|
||||
groundPoints.reserve(9);
|
||||
for (int yStep = 0; yStep <= 2; ++yStep) {
|
||||
for (int xStep = 0; xStep <= 2; ++xStep) {
|
||||
glm::vec3 groundPoint{};
|
||||
const float screenX = viewportState.Left + viewportState.Width * (static_cast<float>(xStep) * 0.5F);
|
||||
const float screenY = viewportState.Top + viewportState.Height * (static_cast<float>(yStep) * 0.5F);
|
||||
if (raycastGroundAtViewportPoint(screenX, screenY, groundPoint)) {
|
||||
groundPoints.push_back(groundPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float minX = sceneView.CameraTarget.x - fallbackHalfExtent;
|
||||
float maxX = sceneView.CameraTarget.x + fallbackHalfExtent;
|
||||
float minZ = sceneView.CameraTarget.z - fallbackHalfExtent;
|
||||
float maxZ = sceneView.CameraTarget.z + fallbackHalfExtent;
|
||||
if (!groundPoints.empty()) {
|
||||
minX = maxX = groundPoints.front().x;
|
||||
minZ = maxZ = groundPoints.front().z;
|
||||
for (const glm::vec3& point : groundPoints) {
|
||||
minX = std::min(minX, point.x);
|
||||
maxX = std::max(maxX, point.x);
|
||||
minZ = std::min(minZ, point.z);
|
||||
maxZ = std::max(maxZ, point.z);
|
||||
}
|
||||
const float padding = std::max(gridSpacing * 4.0F, glm::distance(sceneView.CameraPosition, sceneView.CameraTarget) * 0.08F);
|
||||
minX -= padding;
|
||||
maxX += padding;
|
||||
minZ -= padding;
|
||||
maxZ += padding;
|
||||
}
|
||||
|
||||
int startX = static_cast<int>(std::floor(minX / gridSpacing));
|
||||
int endX = static_cast<int>(std::ceil(maxX / gridSpacing));
|
||||
int startZ = static_cast<int>(std::floor(minZ / gridSpacing));
|
||||
int endZ = static_cast<int>(std::ceil(maxZ / gridSpacing));
|
||||
if (endX - startX > maxVisibleLines) {
|
||||
const int center = static_cast<int>(std::floor(sceneView.CameraTarget.x / gridSpacing));
|
||||
startX = center - maxVisibleLines / 2;
|
||||
endX = center + maxVisibleLines / 2;
|
||||
}
|
||||
if (endZ - startZ > maxVisibleLines) {
|
||||
const int center = static_cast<int>(std::floor(sceneView.CameraTarget.z / gridSpacing));
|
||||
startZ = center - maxVisibleLines / 2;
|
||||
endZ = center + maxVisibleLines / 2;
|
||||
}
|
||||
|
||||
ImDrawList* drawList = ImGui::GetForegroundDrawList();
|
||||
const ImVec2 topLeft(viewportState.Left, viewportState.Top);
|
||||
const ImVec2 bottomRight(viewportState.Left + viewportState.Width, viewportState.Top + viewportState.Height);
|
||||
const ImVec2 center((topLeft.x + bottomRight.x) * 0.5F, (topLeft.y + bottomRight.y) * 0.5F);
|
||||
const ImU32 minorColor = IM_COL32(150, 165, 180, 18);
|
||||
const ImU32 majorColor = IM_COL32(180, 195, 210, 30);
|
||||
const ImU32 axisXColor = IM_COL32(220, 85, 85, 65);
|
||||
const ImU32 axisZColor = IM_COL32(85, 145, 230, 65);
|
||||
|
||||
const float minorSpacing = 32.0F;
|
||||
const int verticalMinorCount = static_cast<int>(viewportState.Width / minorSpacing) + 2;
|
||||
const int horizontalMinorCount = static_cast<int>(viewportState.Height / minorSpacing) + 2;
|
||||
const ImU32 minorColor = IM_COL32(110, 120, 135, 40);
|
||||
const ImU32 majorColor = IM_COL32(150, 165, 185, 70);
|
||||
const ImU32 axisXColor = IM_COL32(220, 80, 80, 95);
|
||||
const ImU32 axisYColor = IM_COL32(120, 210, 100, 95);
|
||||
|
||||
for (int lineIndex = -verticalMinorCount; lineIndex <= verticalMinorCount; ++lineIndex) {
|
||||
const float x = center.x + static_cast<float>(lineIndex) * minorSpacing;
|
||||
if (x < topLeft.x || x > bottomRight.x) {
|
||||
continue;
|
||||
const auto drawProjectedGroundLine = [&](const glm::vec3& start, const glm::vec3& end, ImU32 color, float thickness) {
|
||||
constexpr int segmentCount = 48;
|
||||
ImVec2 previousPoint{};
|
||||
bool previousVisible = false;
|
||||
for (int segmentIndex = 0; segmentIndex <= segmentCount; ++segmentIndex) {
|
||||
const float t = static_cast<float>(segmentIndex) / static_cast<float>(segmentCount);
|
||||
const glm::vec3 worldPoint = start + (end - start) * t;
|
||||
ImVec2 screenPoint{};
|
||||
glm::vec3 projectedPixelGroundPoint{};
|
||||
const bool visible = MetaCoreProjectWorldPointToViewport(sceneView, viewportState, worldPoint, screenPoint)
|
||||
&& raycastGroundAtViewportPoint(screenPoint.x, screenPoint.y, projectedPixelGroundPoint);
|
||||
if (visible && previousVisible) {
|
||||
drawList->AddLine(previousPoint, screenPoint, color, thickness);
|
||||
}
|
||||
previousPoint = screenPoint;
|
||||
previousVisible = visible;
|
||||
}
|
||||
const bool isMajor = (lineIndex % 4) == 0;
|
||||
drawList->AddLine(
|
||||
ImVec2(x, topLeft.y),
|
||||
ImVec2(x, bottomRight.y),
|
||||
lineIndex == 0 ? axisXColor : (isMajor ? majorColor : minorColor),
|
||||
lineIndex == 0 ? 1.8F : (isMajor ? 1.2F : 1.0F)
|
||||
};
|
||||
|
||||
drawList->PushClipRect(topLeft, bottomRight, true);
|
||||
for (int xLine = startX; xLine <= endX; ++xLine) {
|
||||
const bool isMajor = (xLine % majorLineInterval) == 0;
|
||||
const float x = static_cast<float>(xLine) * gridSpacing;
|
||||
drawProjectedGroundLine(
|
||||
glm::vec3(x, gridY, static_cast<float>(startZ) * gridSpacing),
|
||||
glm::vec3(x, gridY, static_cast<float>(endZ) * gridSpacing),
|
||||
xLine == 0 ? axisZColor : (isMajor ? majorColor : minorColor),
|
||||
xLine == 0 ? 1.4F : 1.0F
|
||||
);
|
||||
}
|
||||
|
||||
for (int lineIndex = -horizontalMinorCount; lineIndex <= horizontalMinorCount; ++lineIndex) {
|
||||
const float y = center.y + static_cast<float>(lineIndex) * minorSpacing;
|
||||
if (y < topLeft.y || y > bottomRight.y) {
|
||||
continue;
|
||||
}
|
||||
const bool isMajor = (lineIndex % 4) == 0;
|
||||
drawList->AddLine(
|
||||
ImVec2(topLeft.x, y),
|
||||
ImVec2(bottomRight.x, y),
|
||||
lineIndex == 0 ? axisYColor : (isMajor ? majorColor : minorColor),
|
||||
lineIndex == 0 ? 1.8F : (isMajor ? 1.2F : 1.0F)
|
||||
for (int zLine = startZ; zLine <= endZ; ++zLine) {
|
||||
const bool isMajor = (zLine % majorLineInterval) == 0;
|
||||
const float z = static_cast<float>(zLine) * gridSpacing;
|
||||
drawProjectedGroundLine(
|
||||
glm::vec3(static_cast<float>(startX) * gridSpacing, gridY, z),
|
||||
glm::vec3(static_cast<float>(endX) * gridSpacing, gridY, z),
|
||||
zLine == 0 ? axisXColor : (isMajor ? majorColor : minorColor),
|
||||
zLine == 0 ? 1.4F : 1.0F
|
||||
);
|
||||
}
|
||||
drawList->PopClipRect();
|
||||
}
|
||||
|
||||
void MetaCoreDrawSelectionBoundsOverlay(
|
||||
const MetaCoreEditorContext& editorContext,
|
||||
const MetaCoreScene& scene,
|
||||
const MetaCoreSceneView& sceneView,
|
||||
const MetaCoreSceneViewportState& viewportState
|
||||
const MetaCoreEditorContext&,
|
||||
const MetaCoreScene&,
|
||||
const MetaCoreSceneView&,
|
||||
const MetaCoreSceneViewportState&
|
||||
) {
|
||||
// 用户要求去掉左下角的黄色框和文字
|
||||
return;
|
||||
@ -716,6 +816,10 @@ bool MetaCoreEditorApp::Initialize(int argc, char* argv[]) {
|
||||
ChangeWindowMessageFilter(0x0049, MSGFLT_ADD); // WM_COPYGLOBALMEM
|
||||
|
||||
Window_.SetNativeWindowMessageHandler([this](void* nativeWindowHandle, unsigned int message, std::uintptr_t wparam, std::intptr_t lparam) {
|
||||
if (message == WM_CLOSE) {
|
||||
return RequestEditorCloseWithSavePrompt();
|
||||
}
|
||||
|
||||
if (message == WM_DROPFILES) {
|
||||
HDROP hDrop = reinterpret_cast<HDROP>(wparam);
|
||||
UINT fileCount = DragQueryFileW(hDrop, 0xFFFFFFFF, nullptr, 0);
|
||||
@ -906,6 +1010,7 @@ int MetaCoreEditorApp::Run() {
|
||||
// 1. 收集并更新场景数据(但不在这里渲染)
|
||||
MetaCoreSceneView sceneView = EditorContext_->GetCameraController().BuildSceneView();
|
||||
sceneView.SelectedObjectId = EditorContext_->GetSelectedObjectId();
|
||||
ViewportRenderer_.SetEditorGridVisible(EditorContext_->GetShowViewportGrid());
|
||||
ViewportRenderer_.RenderSceneToViewport(Scene_, sceneView);
|
||||
|
||||
// 2. 统一在一个 Frame 内渲染离屏 3D 和上屏 UI
|
||||
@ -1128,6 +1233,54 @@ void MetaCoreEditorApp::DrawEditorFrame() {
|
||||
DrawPlaceholderDockWindow("动画状态机编辑器###animfsm_editor", "动画状态机编辑器");
|
||||
ApplyPendingDockTabSelections();
|
||||
DrawEditorStatusBar();
|
||||
DrawPendingExitPopup();
|
||||
}
|
||||
}
|
||||
|
||||
bool MetaCoreEditorApp::RequestEditorCloseWithSavePrompt() {
|
||||
if (const auto scenePersistenceService = ModuleRegistry_.ResolveService<MetaCoreIScenePersistenceService>();
|
||||
scenePersistenceService != nullptr && scenePersistenceService->IsSceneDirty()) {
|
||||
PendingExitPopup_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
Window_.RequestClose();
|
||||
return true;
|
||||
}
|
||||
|
||||
void MetaCoreEditorApp::DrawPendingExitPopup() {
|
||||
if (PendingExitPopup_) {
|
||||
ImGui::OpenPopup("Exit Unsaved Scene Changes");
|
||||
PendingExitPopup_ = false;
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopupModal("Exit Unsaved Scene Changes", nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
|
||||
const auto scenePersistenceService = ModuleRegistry_.ResolveService<MetaCoreIScenePersistenceService>();
|
||||
const std::string currentScene = scenePersistenceService != nullptr && scenePersistenceService->HasOpenScene()
|
||||
? scenePersistenceService->GetCurrentSceneDisplayName()
|
||||
: std::string("Untitled");
|
||||
|
||||
ImGui::Text("Save changes to %s before exiting?", currentScene.c_str());
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::Button("Save and Exit", ImVec2(130, 0))) {
|
||||
if (scenePersistenceService == nullptr || scenePersistenceService->SaveCurrentScene(*EditorContext_)) {
|
||||
Window_.RequestClose();
|
||||
ImGui::CloseCurrentPopup();
|
||||
} else {
|
||||
EditorContext_->AddConsoleMessage(MetaCoreLogLevel::Error, "Scene", "保存失败,已取消退出");
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Don't Save", ImVec2(110, 0))) {
|
||||
Window_.RequestClose();
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Cancel", ImVec2(90, 0))) {
|
||||
ImGui::CloseCurrentPopup();
|
||||
}
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1601,6 +1754,7 @@ void MetaCoreEditorApp::DrawSceneViewWindow() {
|
||||
|
||||
MetaCoreSceneView sceneView = EditorContext_->GetCameraController().BuildSceneView();
|
||||
sceneView.SelectedObjectId = EditorContext_->GetSelectedObjectId();
|
||||
ViewportRenderer_.SetEditorGridVisible(EditorContext_->GetShowViewportGrid());
|
||||
ViewportRenderer_.RenderSceneToViewport(Scene_, sceneView);
|
||||
|
||||
if (void* texPtr = ViewportRenderer_.GetFilamentTexturePointer(); texPtr != nullptr) {
|
||||
@ -1753,7 +1907,7 @@ void MetaCoreEditorApp::DrawSceneViewWindow() {
|
||||
}
|
||||
}
|
||||
|
||||
MetaCoreDrawViewportGridOverlay(*EditorContext_, viewportState);
|
||||
MetaCoreDrawViewportGridOverlay(*EditorContext_, sceneView, viewportState);
|
||||
MetaCoreDrawWorldOriginOverlay(*EditorContext_, sceneView, viewportState);
|
||||
MetaCoreDrawSelectionBoundsOverlay(*EditorContext_, Scene_, sceneView, viewportState);
|
||||
MetaCoreDrawSelectionHierarchyOverlay(*EditorContext_, Scene_, sceneView, viewportState);
|
||||
|
||||
@ -34,6 +34,8 @@ private:
|
||||
void DrawEditorFrame();
|
||||
void DrawEditorToolbar();
|
||||
void DrawEditorStatusBar();
|
||||
void DrawPendingExitPopup();
|
||||
bool RequestEditorCloseWithSavePrompt();
|
||||
void DrawSceneViewWindow();
|
||||
void DrawGameViewWindow();
|
||||
void DrawPlaceholderDockWindow(const char* label, const char* caption);
|
||||
@ -56,6 +58,7 @@ private:
|
||||
std::filesystem::path ImGuiLayoutProjectRoot_{};
|
||||
std::filesystem::path ViewportProjectRoot_{};
|
||||
std::string ImGuiIniPath_{};
|
||||
bool PendingExitPopup_ = false;
|
||||
bool Initialized_ = false;
|
||||
};
|
||||
|
||||
|
||||
@ -112,6 +112,10 @@ LRESULT CALLBACK MetaCoreWindowSubclassProc(HWND hwnd, UINT msg, WPARAM wparam,
|
||||
handledByEditor = owner.MessageHandler(hwnd, msg, static_cast<std::uintptr_t>(wparam), static_cast<std::intptr_t>(lparam));
|
||||
}
|
||||
|
||||
if (msg == WM_CLOSE && handledByEditor) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (msg == WM_MOUSEWHEEL) {
|
||||
owner.Input.AddMouseWheelDelta(static_cast<float>(GET_WHEEL_DELTA_WPARAM(wparam)) / static_cast<float>(WHEEL_DELTA));
|
||||
} else if (msg == WM_CLOSE) {
|
||||
|
||||
@ -79,6 +79,12 @@ void MetaCoreEditorViewportRenderer::SetViewportRect(const MetaCoreViewportRect&
|
||||
}
|
||||
}
|
||||
|
||||
void MetaCoreEditorViewportRenderer::SetEditorGridVisible(bool visible) {
|
||||
(void)visible;
|
||||
EditorGridVisible_ = false;
|
||||
FilamentSceneBridge_.SetEditorGridVisible(false);
|
||||
}
|
||||
|
||||
void MetaCoreEditorViewportRenderer::RenderSceneToViewport(MetaCoreScene& scene, const MetaCoreSceneView& sceneView, bool useScenePrimaryCamera) {
|
||||
if (RenderDevice_ == nullptr) {
|
||||
return;
|
||||
@ -88,6 +94,7 @@ void MetaCoreEditorViewportRenderer::RenderSceneToViewport(MetaCoreScene& scene,
|
||||
|
||||
// 动态调整离屏纹理大小以匹配 ImGui 视口
|
||||
FilamentSceneBridge_.Resize(static_cast<int>(ViewportRect_.Width), static_cast<int>(ViewportRect_.Height));
|
||||
FilamentSceneBridge_.SetEditorGridVisible(false);
|
||||
|
||||
// 更新并同步 Filament 桥接器(但不在这里调用 Render,交由 RenderAll 统一渲染)
|
||||
FilamentSceneBridge_.ApplySceneView(sceneView);
|
||||
|
||||
@ -24,6 +24,8 @@
|
||||
#include <filament/Material.h>
|
||||
#include <filament/MaterialInstance.h>
|
||||
#include <filament/IndirectLight.h>
|
||||
#include <filament/IndexBuffer.h>
|
||||
#include <filament/VertexBuffer.h>
|
||||
|
||||
#include <gltfio/AssetLoader.h>
|
||||
#include <gltfio/FilamentAsset.h>
|
||||
@ -46,7 +48,10 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
#include <filesystem>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <map>
|
||||
@ -70,6 +75,45 @@ namespace MetaCore {
|
||||
|
||||
namespace {
|
||||
|
||||
struct MetaCoreEditorGridVertex {
|
||||
float X;
|
||||
float Y;
|
||||
float Z;
|
||||
};
|
||||
|
||||
[[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 MetaCoreOpenEditorGridMaterial() {
|
||||
std::vector<std::filesystem::path> candidates{};
|
||||
|
||||
const std::filesystem::path executableDirectory = MetaCoreGetExecutableDirectory();
|
||||
if (!executableDirectory.empty()) {
|
||||
candidates.push_back(executableDirectory / "editorGrid.filamat");
|
||||
}
|
||||
candidates.emplace_back("editorGrid.filamat");
|
||||
candidates.emplace_back("../editorGrid.filamat");
|
||||
candidates.emplace_back("build/editorGrid.filamat");
|
||||
|
||||
for (const std::filesystem::path& candidate : candidates) {
|
||||
std::ifstream file(candidate, std::ios::binary);
|
||||
if (file.is_open()) {
|
||||
return file;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
[[nodiscard]] filament::backend::Backend MetaCoreToFilamentBackend(MetaCoreRenderBackend backend) {
|
||||
switch (backend) {
|
||||
case MetaCoreRenderBackend::OpenGL:
|
||||
@ -423,6 +467,7 @@ public:
|
||||
// 开启高级后处理
|
||||
View_->setAntiAliasing(filament::View::AntiAliasing::FXAA);
|
||||
View_->setBloomOptions({ .strength = 0.15f, .resolution = 384, .levels = 6, .blendMode = filament::View::BloomOptions::BlendMode::ADD, .threshold = true });
|
||||
CreateEditorGridResources();
|
||||
|
||||
std::cout << "FilamentSceneBridge: Initialized with Offscreen=" << (offscreen ? "true" : "false") << std::endl;
|
||||
return true;
|
||||
@ -431,6 +476,7 @@ public:
|
||||
void Shutdown() {
|
||||
if (Engine_) {
|
||||
std::cout << "FilamentSceneBridge: Shutting down..." << std::endl;
|
||||
DestroyEditorGridResources();
|
||||
|
||||
// 1. 销毁所有加载的资产,切断其内部 Renderable 对克隆材质实例的占用引用
|
||||
std::cout << "[DEBUG] LoadedAssets_ size: " << LoadedAssets_.size() << std::endl;
|
||||
@ -579,6 +625,181 @@ public:
|
||||
ProjectRootPath_ = projectRootPath;
|
||||
}
|
||||
|
||||
void SetEditorGridVisible(bool visible) {
|
||||
EditorGridVisible_ = visible;
|
||||
SyncEditorGridVisibility();
|
||||
}
|
||||
|
||||
void CreateEditorGridResources() {
|
||||
if (Engine_ == nullptr || Scene_ == nullptr || EditorGridEntity_) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::ifstream materialFile = MetaCoreOpenEditorGridMaterial();
|
||||
if (!materialFile.is_open()) {
|
||||
std::cerr << "FilamentSceneBridge: editorGrid.filamat not found; viewport grid disabled." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<char> materialBuffer(
|
||||
(std::istreambuf_iterator<char>(materialFile)),
|
||||
std::istreambuf_iterator<char>()
|
||||
);
|
||||
if (materialBuffer.empty()) {
|
||||
std::cerr << "FilamentSceneBridge: editorGrid.filamat is empty; viewport grid disabled." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGridMaterial_ = filament::Material::Builder()
|
||||
.package(materialBuffer.data(), materialBuffer.size())
|
||||
.build(*Engine_);
|
||||
if (EditorGridMaterial_ == nullptr) {
|
||||
std::cerr << "FilamentSceneBridge: failed to build editor grid material." << std::endl;
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGridMaterialInstance_ = EditorGridMaterial_->createInstance();
|
||||
if (EditorGridMaterialInstance_ != nullptr) {
|
||||
EditorGridMaterialInstance_->setParameter("minorAlpha", 0.045F);
|
||||
EditorGridMaterialInstance_->setParameter("majorAlpha", 0.11F);
|
||||
EditorGridMaterialInstance_->setParameter("axisAlpha", 0.18F);
|
||||
EditorGridMaterialInstance_->setParameter("cameraPosition", filament::math::float3{0.0F, 0.0F, 0.0F});
|
||||
EditorGridMaterialInstance_->setParameter("fadeStart", 45.0F);
|
||||
EditorGridMaterialInstance_->setParameter("fadeEnd", 220.0F);
|
||||
}
|
||||
|
||||
constexpr float extent = 1000.0F;
|
||||
const MetaCoreEditorGridVertex vertices[] = {
|
||||
{-extent, -extent, 0.0F},
|
||||
{ extent, -extent, 0.0F},
|
||||
{ extent, extent, 0.0F},
|
||||
{-extent, extent, 0.0F}
|
||||
};
|
||||
const uint16_t indices[] = {0, 1, 2, 0, 2, 3};
|
||||
|
||||
EditorGridVertexBuffer_ = filament::VertexBuffer::Builder()
|
||||
.vertexCount(4)
|
||||
.bufferCount(1)
|
||||
.attribute(filament::VertexAttribute::POSITION, 0, filament::VertexBuffer::AttributeType::FLOAT3, 0, sizeof(MetaCoreEditorGridVertex))
|
||||
.build(*Engine_);
|
||||
EditorGridIndexBuffer_ = filament::IndexBuffer::Builder()
|
||||
.indexCount(6)
|
||||
.bufferType(filament::IndexBuffer::IndexType::USHORT)
|
||||
.build(*Engine_);
|
||||
|
||||
if (EditorGridVertexBuffer_ == nullptr || EditorGridIndexBuffer_ == nullptr || EditorGridMaterialInstance_ == nullptr) {
|
||||
std::cerr << "FilamentSceneBridge: failed to allocate editor grid buffers." << std::endl;
|
||||
DestroyEditorGridResources();
|
||||
return;
|
||||
}
|
||||
|
||||
auto* vertexCopy = new MetaCoreEditorGridVertex[4];
|
||||
std::copy(std::begin(vertices), std::end(vertices), vertexCopy);
|
||||
EditorGridVertexBuffer_->setBufferAt(
|
||||
*Engine_,
|
||||
0,
|
||||
filament::VertexBuffer::BufferDescriptor(
|
||||
vertexCopy,
|
||||
sizeof(vertices),
|
||||
[](void* buffer, size_t, void*) { delete[] static_cast<MetaCoreEditorGridVertex*>(buffer); }
|
||||
)
|
||||
);
|
||||
|
||||
auto* indexCopy = new uint16_t[6];
|
||||
std::copy(std::begin(indices), std::end(indices), indexCopy);
|
||||
EditorGridIndexBuffer_->setBuffer(
|
||||
*Engine_,
|
||||
filament::IndexBuffer::BufferDescriptor(
|
||||
indexCopy,
|
||||
sizeof(indices),
|
||||
[](void* buffer, size_t, void*) { delete[] static_cast<uint16_t*>(buffer); }
|
||||
)
|
||||
);
|
||||
|
||||
EditorGridEntity_ = utils::EntityManager::get().create();
|
||||
filament::RenderableManager::Builder(1)
|
||||
.boundingBox({{0.0F, 0.0F, 0.0F}, {extent, extent, 0.01F}})
|
||||
.geometry(0, filament::RenderableManager::PrimitiveType::TRIANGLES, EditorGridVertexBuffer_, EditorGridIndexBuffer_, 0, 6)
|
||||
.material(0, EditorGridMaterialInstance_)
|
||||
.culling(false)
|
||||
.castShadows(false)
|
||||
.receiveShadows(false)
|
||||
.build(*Engine_, EditorGridEntity_);
|
||||
|
||||
SyncEditorGridVisibility();
|
||||
}
|
||||
|
||||
void DestroyEditorGridResources() {
|
||||
if (Engine_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (EditorGridEntity_) {
|
||||
if (Scene_ != nullptr) {
|
||||
Scene_->remove(EditorGridEntity_);
|
||||
}
|
||||
Engine_->destroy(EditorGridEntity_);
|
||||
utils::EntityManager::get().destroy(EditorGridEntity_);
|
||||
EditorGridEntity_.clear();
|
||||
}
|
||||
if (EditorGridVertexBuffer_ != nullptr) {
|
||||
Engine_->destroy(EditorGridVertexBuffer_);
|
||||
EditorGridVertexBuffer_ = nullptr;
|
||||
}
|
||||
if (EditorGridIndexBuffer_ != nullptr) {
|
||||
Engine_->destroy(EditorGridIndexBuffer_);
|
||||
EditorGridIndexBuffer_ = nullptr;
|
||||
}
|
||||
if (EditorGridMaterialInstance_ != nullptr) {
|
||||
Engine_->destroy(EditorGridMaterialInstance_);
|
||||
EditorGridMaterialInstance_ = nullptr;
|
||||
}
|
||||
if (EditorGridMaterial_ != nullptr) {
|
||||
Engine_->destroy(EditorGridMaterial_);
|
||||
EditorGridMaterial_ = nullptr;
|
||||
}
|
||||
EditorGridInScene_ = false;
|
||||
}
|
||||
|
||||
void SyncEditorGridVisibility() {
|
||||
if (Scene_ == nullptr || !EditorGridEntity_) {
|
||||
EditorGridInScene_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (EditorGridVisible_ && !EditorGridInScene_) {
|
||||
Scene_->addEntity(EditorGridEntity_);
|
||||
EditorGridInScene_ = true;
|
||||
} else if (!EditorGridVisible_ && EditorGridInScene_) {
|
||||
Scene_->remove(EditorGridEntity_);
|
||||
EditorGridInScene_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateEditorGridTransform(const MetaCoreSceneView& sceneView) {
|
||||
if (Engine_ == nullptr || !EditorGridEntity_) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto& transformManager = Engine_->getTransformManager();
|
||||
auto instance = transformManager.getInstance(EditorGridEntity_);
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float x = std::floor(sceneView.CameraTarget.x / 10.0F) * 10.0F;
|
||||
const float y = std::floor(sceneView.CameraTarget.y / 10.0F) * 10.0F;
|
||||
const glm::mat4 transform = glm::translate(glm::mat4(1.0F), glm::vec3(x, y, 0.0F));
|
||||
transformManager.setTransform(instance, *reinterpret_cast<const filament::math::mat4f*>(glm::value_ptr(transform)));
|
||||
|
||||
if (EditorGridMaterialInstance_ != nullptr) {
|
||||
EditorGridMaterialInstance_->setParameter(
|
||||
"cameraPosition",
|
||||
filament::math::float3{sceneView.CameraPosition.x, sceneView.CameraPosition.y, sceneView.CameraPosition.z}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void ParseModelHierarchyToEcs(MetaCoreScene& scene, MetaCoreGameObject& parentObject, filament::gltfio::FilamentAsset* asset) {
|
||||
if (!asset) return;
|
||||
|
||||
@ -1192,6 +1413,7 @@ public:
|
||||
|
||||
void ApplySceneView(const MetaCoreSceneView& sceneView) {
|
||||
if (!Camera_ || !View_) return;
|
||||
UpdateEditorGridTransform(sceneView);
|
||||
|
||||
// 1. 视图矩阵同步
|
||||
const glm::mat4 viewMatrix = glm::lookAt(sceneView.CameraPosition, sceneView.CameraTarget, sceneView.CameraUp);
|
||||
@ -1659,6 +1881,14 @@ private:
|
||||
std::unordered_map<MetaCoreId, utils::Entity> SceneLightEntities_;
|
||||
std::unordered_set<utils::Entity, utils::Entity::Hasher> EntitiesInScene_;
|
||||
|
||||
utils::Entity EditorGridEntity_{};
|
||||
filament::VertexBuffer* EditorGridVertexBuffer_ = nullptr;
|
||||
filament::IndexBuffer* EditorGridIndexBuffer_ = nullptr;
|
||||
filament::Material* EditorGridMaterial_ = nullptr;
|
||||
filament::MaterialInstance* EditorGridMaterialInstance_ = nullptr;
|
||||
bool EditorGridVisible_ = true;
|
||||
bool EditorGridInScene_ = false;
|
||||
|
||||
filament::View* UIView_ = nullptr;
|
||||
MetaCoreImGuiHelper* ImGuiHelper_ = nullptr;
|
||||
filament::View* RuntimeUIView_ = nullptr;
|
||||
@ -1708,6 +1938,10 @@ void MetaCoreFilamentSceneBridge::ApplySceneView(const MetaCoreSceneView& sceneV
|
||||
Impl_->ApplySceneView(sceneView);
|
||||
}
|
||||
|
||||
void MetaCoreFilamentSceneBridge::SetEditorGridVisible(bool visible) {
|
||||
Impl_->SetEditorGridVisible(visible);
|
||||
}
|
||||
|
||||
void MetaCoreFilamentSceneBridge::SetRuntimeUiOverlayFrame(const MetaCoreRuntimeUiFrame& frame, int width, int height) {
|
||||
Impl_->SetRuntimeUiOverlayFrame(frame, width, height);
|
||||
}
|
||||
|
||||
83
Source/MetaCoreRender/Private/editorGrid.mat
Normal file
83
Source/MetaCoreRender/Private/editorGrid.mat
Normal file
@ -0,0 +1,83 @@
|
||||
material {
|
||||
name : MetaCoreEditorGrid,
|
||||
blending : transparent,
|
||||
culling : none,
|
||||
depthCulling : true,
|
||||
depthWrite : false,
|
||||
shadingModel : unlit,
|
||||
variantFilter : [ skinning, shadowReceiver, vsm, fog ],
|
||||
parameters : [
|
||||
{
|
||||
type : float,
|
||||
name : minorAlpha
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : majorAlpha
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : axisAlpha
|
||||
},
|
||||
{
|
||||
type : float3,
|
||||
name : cameraPosition
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : fadeStart
|
||||
},
|
||||
{
|
||||
type : float,
|
||||
name : fadeEnd
|
||||
}
|
||||
],
|
||||
variables : [ gridWorldPosition ]
|
||||
}
|
||||
|
||||
vertex {
|
||||
void materialVertex(inout MaterialVertexInputs material) {
|
||||
highp vec4 worldPosition = getWorldFromModelMatrix() * getPosition();
|
||||
material.gridWorldPosition.xyz = worldPosition.xyz;
|
||||
}
|
||||
}
|
||||
|
||||
fragment {
|
||||
highp float gridLine(highp vec2 coord, highp float pixelWidth) {
|
||||
highp vec2 dudv = max(fwidth(coord), vec2(1e-6));
|
||||
highp vec2 uvMod = fract(coord);
|
||||
highp vec2 uvDist = min(uvMod, 1.0 - uvMod);
|
||||
highp vec2 distInPixels = uvDist / dudv;
|
||||
highp vec2 lineAlpha = 1.0 - smoothstep(pixelWidth, pixelWidth + 0.85, distInPixels);
|
||||
return max(lineAlpha.x, lineAlpha.y);
|
||||
}
|
||||
|
||||
highp float axisLine(highp float coord, highp float pixelWidth) {
|
||||
highp float dudv = max(fwidth(coord), 1e-6);
|
||||
highp float distInPixels = abs(coord) / dudv;
|
||||
return 1.0 - smoothstep(pixelWidth, pixelWidth + 1.0, distInPixels);
|
||||
}
|
||||
|
||||
void material(inout MaterialInputs material) {
|
||||
prepareMaterial(material);
|
||||
|
||||
highp vec2 coord = variable_gridWorldPosition.xy;
|
||||
|
||||
highp float distanceToCamera = distance(coord, materialParams.cameraPosition.xy);
|
||||
highp float distanceFade = 1.0 - smoothstep(materialParams.fadeStart, materialParams.fadeEnd, distanceToCamera);
|
||||
|
||||
highp vec2 minorDeriv = fwidth(coord);
|
||||
highp float minorLodFade = 1.0 - smoothstep(0.10, 0.38, max(minorDeriv.x, minorDeriv.y));
|
||||
|
||||
highp float minor = gridLine(coord / 2.0, 0.18) * materialParams.minorAlpha * minorLodFade;
|
||||
highp float major = gridLine(coord / 10.0, 0.32) * materialParams.majorAlpha;
|
||||
highp float axis = max(axisLine(coord.x, 0.35), axisLine(coord.y, 0.35)) * materialParams.axisAlpha;
|
||||
highp float alpha = max(max(minor, major), axis) * distanceFade;
|
||||
|
||||
if (alpha < 0.004) {
|
||||
discard;
|
||||
}
|
||||
|
||||
material.baseColor = vec4(vec3(0.46, 0.50, 0.56), alpha);
|
||||
}
|
||||
}
|
||||
@ -19,6 +19,7 @@ public:
|
||||
bool Initialize(MetaCoreRenderDevice& renderDevice, MetaCoreWindow& window, bool offscreen = true);
|
||||
void Shutdown();
|
||||
void SetViewportRect(const MetaCoreViewportRect& viewportRect);
|
||||
void SetEditorGridVisible(bool visible);
|
||||
void RenderSceneToViewport(MetaCoreScene& scene, const MetaCoreSceneView& sceneView, bool useScenePrimaryCamera = false);
|
||||
void SetRuntimeUiOverlayFrame(const MetaCoreRuntimeUiFrame& frame, int width, int height);
|
||||
void RenderAll();
|
||||
@ -32,6 +33,7 @@ private:
|
||||
MetaCoreRenderDevice* RenderDevice_ = nullptr;
|
||||
MetaCoreFilamentSceneBridge FilamentSceneBridge_{};
|
||||
MetaCoreViewportRect ViewportRect_{};
|
||||
bool EditorGridVisible_ = false;
|
||||
|
||||
};
|
||||
|
||||
|
||||
@ -32,6 +32,7 @@ public:
|
||||
void SyncScene(MetaCoreScene& scene, bool compatibilityMeshOnly = false, bool useScenePrimaryCamera = false);
|
||||
void SyncScene(const MetaCoreSceneRenderSyncSnapshot& snapshot, MetaCoreScene* scene = nullptr, bool compatibilityMeshOnly = false, bool useScenePrimaryCamera = false);
|
||||
void ApplySceneView(const MetaCoreSceneView& sceneView);
|
||||
void SetEditorGridVisible(bool visible);
|
||||
void SetRuntimeUiOverlayFrame(const MetaCoreRuntimeUiFrame& frame, int width, int height);
|
||||
void RenderAll();
|
||||
[[nodiscard]] uint32_t GetGLTextureId() const;
|
||||
|
||||
@ -277,6 +277,136 @@ void MetaCoreTestUndoRedo() {
|
||||
MetaCoreExpect(scene.GetGameObjects().size() == beforeCreateCount + 1, "Redo 后对象数量应再次增加");
|
||||
}
|
||||
|
||||
void MetaCoreTestHierarchyEditingCommandsDirtyAndUndo() {
|
||||
const std::filesystem::path tempProjectRoot =
|
||||
std::filesystem::temp_directory_path() / "MetaCoreHierarchyEditingCommandsSmoke";
|
||||
std::filesystem::remove_all(tempProjectRoot);
|
||||
std::filesystem::create_directories(tempProjectRoot / "Scenes");
|
||||
std::filesystem::create_directories(tempProjectRoot / "Assets");
|
||||
std::filesystem::create_directories(tempProjectRoot / "Library");
|
||||
|
||||
{
|
||||
std::ofstream projectFile(tempProjectRoot / "MetaCore.project.json", std::ios::trunc);
|
||||
MetaCoreExpect(projectFile.is_open(), "应能写入 Hierarchy 命令测试项目文件");
|
||||
projectFile << "{\n"
|
||||
<< " \"name\": \"HierarchyEditingCommandsSmoke\",\n"
|
||||
<< " \"version\": \"0.1.0\",\n"
|
||||
<< " \"scenes\": [],\n"
|
||||
<< " \"startup_scene\": \"\"\n"
|
||||
<< "}\n";
|
||||
}
|
||||
|
||||
_putenv_s("METACORE_PROJECT_PATH", tempProjectRoot.string().c_str());
|
||||
|
||||
MetaCore::MetaCoreWindow window;
|
||||
MetaCore::MetaCoreRenderDevice renderDevice;
|
||||
MetaCore::MetaCoreEditorViewportRenderer viewportRenderer;
|
||||
MetaCore::MetaCoreScene scene = MetaCore::MetaCoreCreateDefaultScene();
|
||||
MetaCore::MetaCoreLogService logService;
|
||||
MetaCore::MetaCoreEditorModuleRegistry moduleRegistry;
|
||||
|
||||
auto coreServicesModule = MetaCore::MetaCoreCreateBuiltinCoreServicesModule();
|
||||
coreServicesModule->Startup(moduleRegistry);
|
||||
|
||||
MetaCore::MetaCoreEditorContext editorContext(
|
||||
window,
|
||||
renderDevice,
|
||||
viewportRenderer,
|
||||
scene,
|
||||
logService,
|
||||
moduleRegistry
|
||||
);
|
||||
|
||||
const auto scenePersistenceService = moduleRegistry.ResolveService<MetaCore::MetaCoreIScenePersistenceService>();
|
||||
const auto sceneEditingService = moduleRegistry.ResolveService<MetaCore::MetaCoreISceneEditingService>();
|
||||
const auto clipboardService = moduleRegistry.ResolveService<MetaCore::MetaCoreIClipboardService>();
|
||||
MetaCoreExpect(scenePersistenceService != nullptr, "Hierarchy command test should resolve scene persistence service");
|
||||
MetaCoreExpect(sceneEditingService != nullptr, "Hierarchy command test should resolve scene editing service");
|
||||
MetaCoreExpect(clipboardService != nullptr, "Hierarchy command test should resolve clipboard service");
|
||||
|
||||
const std::filesystem::path scenePath = std::filesystem::path("Scenes") / "HierarchyCommands.mcscene.json";
|
||||
MetaCoreExpect(scenePersistenceService->SaveSceneAs(editorContext, scenePath), "Hierarchy command baseline scene should save");
|
||||
MetaCoreExpect(!scenePersistenceService->IsSceneDirty(), "Hierarchy command baseline should be clean after save");
|
||||
|
||||
const std::size_t baselineCount = scene.GetGameObjects().size();
|
||||
const auto createdId = sceneEditingService->CreateGameObject(
|
||||
editorContext,
|
||||
"HierarchyObject",
|
||||
false,
|
||||
std::optional<MetaCore::MetaCoreId>{0}
|
||||
);
|
||||
MetaCoreExpect(createdId.has_value(), "CreateGameObject command should create an object");
|
||||
MetaCoreExpect(scenePersistenceService->IsSceneDirty(), "CreateGameObject command should mark scene dirty");
|
||||
MetaCoreExpect(editorContext.UndoCommand(), "Undo CreateGameObject should succeed");
|
||||
MetaCoreExpect(scene.GetGameObjects().size() == baselineCount, "Undo CreateGameObject should restore object count");
|
||||
MetaCoreExpect(!scenePersistenceService->IsSceneDirty(), "Undo CreateGameObject should restore clean baseline");
|
||||
MetaCoreExpect(editorContext.RedoCommand(), "Redo CreateGameObject should succeed");
|
||||
MetaCoreExpect(scene.FindGameObject(*createdId), "Redo CreateGameObject should restore created object");
|
||||
MetaCoreExpect(scenePersistenceService->IsSceneDirty(), "Redo CreateGameObject should mark scene dirty again");
|
||||
|
||||
MetaCoreExpect(scenePersistenceService->SaveCurrentScene(editorContext), "Hierarchy command scene should save after create");
|
||||
MetaCoreExpect(!scenePersistenceService->IsSceneDirty(), "Hierarchy command scene should be clean after create save");
|
||||
|
||||
MetaCoreExpect(sceneEditingService->RenameGameObject(editorContext, *createdId, "HierarchyObjectRenamed"), "RenameGameObject command should rename object");
|
||||
MetaCoreExpect(scenePersistenceService->IsSceneDirty(), "RenameGameObject command should mark scene dirty");
|
||||
MetaCoreExpect(editorContext.UndoCommand(), "Undo RenameGameObject should succeed");
|
||||
MetaCoreExpect(scene.FindGameObject(*createdId).GetName() == "HierarchyObject", "Undo RenameGameObject should restore old name");
|
||||
MetaCoreExpect(!scenePersistenceService->IsSceneDirty(), "Undo RenameGameObject should restore clean baseline");
|
||||
MetaCoreExpect(editorContext.RedoCommand(), "Redo RenameGameObject should succeed");
|
||||
MetaCoreExpect(scene.FindGameObject(*createdId).GetName() == "HierarchyObjectRenamed", "Redo RenameGameObject should restore new name");
|
||||
|
||||
MetaCoreExpect(scenePersistenceService->SaveCurrentScene(editorContext), "Hierarchy command scene should save after rename");
|
||||
const auto parentId = sceneEditingService->CreateGameObject(
|
||||
editorContext,
|
||||
"HierarchyParent",
|
||||
false,
|
||||
std::optional<MetaCore::MetaCoreId>{0}
|
||||
);
|
||||
MetaCoreExpect(parentId.has_value(), "CreateGameObject command should create a parent object");
|
||||
MetaCoreExpect(scenePersistenceService->SaveCurrentScene(editorContext), "Hierarchy command scene should save after parent create");
|
||||
MetaCoreExpect(!scenePersistenceService->IsSceneDirty(), "Hierarchy command scene should be clean before reparent");
|
||||
|
||||
editorContext.SelectOnly(*createdId);
|
||||
MetaCoreExpect(sceneEditingService->ReparentSelection(editorContext, *parentId), "ReparentSelection command should reparent selected object");
|
||||
MetaCoreExpect(scene.FindGameObject(*createdId).GetParentId() == *parentId, "ReparentSelection should set parent id");
|
||||
MetaCoreExpect(scenePersistenceService->IsSceneDirty(), "ReparentSelection command should mark scene dirty");
|
||||
MetaCoreExpect(editorContext.UndoCommand(), "Undo ReparentSelection should succeed");
|
||||
MetaCoreExpect(scene.FindGameObject(*createdId).GetParentId() == 0, "Undo ReparentSelection should restore root parent");
|
||||
MetaCoreExpect(!scenePersistenceService->IsSceneDirty(), "Undo ReparentSelection should restore clean baseline");
|
||||
MetaCoreExpect(editorContext.RedoCommand(), "Redo ReparentSelection should succeed");
|
||||
MetaCoreExpect(scene.FindGameObject(*createdId).GetParentId() == *parentId, "Redo ReparentSelection should restore parent id");
|
||||
|
||||
MetaCoreExpect(scenePersistenceService->SaveCurrentScene(editorContext), "Hierarchy command scene should save after reparent");
|
||||
editorContext.SelectOnly(*createdId);
|
||||
const std::size_t countBeforeDuplicate = scene.GetGameObjects().size();
|
||||
MetaCoreExpect(clipboardService->DuplicateSelection(editorContext), "DuplicateSelection command should duplicate selected object");
|
||||
MetaCoreExpect(scene.GetGameObjects().size() > countBeforeDuplicate, "DuplicateSelection should add at least one object");
|
||||
MetaCoreExpect(scenePersistenceService->IsSceneDirty(), "DuplicateSelection command should mark scene dirty");
|
||||
MetaCoreExpect(editorContext.UndoCommand(), "Undo DuplicateSelection should succeed");
|
||||
MetaCoreExpect(scene.GetGameObjects().size() == countBeforeDuplicate, "Undo DuplicateSelection should restore object count");
|
||||
MetaCoreExpect(!scenePersistenceService->IsSceneDirty(), "Undo DuplicateSelection should restore clean baseline");
|
||||
MetaCoreExpect(editorContext.RedoCommand(), "Redo DuplicateSelection should succeed");
|
||||
MetaCoreExpect(scene.GetGameObjects().size() > countBeforeDuplicate, "Redo DuplicateSelection should add object again");
|
||||
|
||||
MetaCoreExpect(scenePersistenceService->SaveCurrentScene(editorContext), "Hierarchy command scene should save after duplicate");
|
||||
const MetaCore::MetaCoreId duplicatedId = editorContext.GetActiveObjectId();
|
||||
MetaCoreExpect(duplicatedId != 0 && duplicatedId != *createdId, "DuplicateSelection should select duplicated object");
|
||||
const std::size_t countBeforeDelete = scene.GetGameObjects().size();
|
||||
MetaCoreExpect(clipboardService->DeleteSelection(editorContext), "DeleteSelection command should delete selected object");
|
||||
MetaCoreExpect(scene.GetGameObjects().size() < countBeforeDelete, "DeleteSelection should remove selected object");
|
||||
MetaCoreExpect(scenePersistenceService->IsSceneDirty(), "DeleteSelection command should mark scene dirty");
|
||||
MetaCoreExpect(editorContext.UndoCommand(), "Undo DeleteSelection should succeed");
|
||||
MetaCoreExpect(scene.GetGameObjects().size() == countBeforeDelete, "Undo DeleteSelection should restore object count");
|
||||
MetaCoreExpect(!scenePersistenceService->IsSceneDirty(), "Undo DeleteSelection should restore clean baseline");
|
||||
MetaCoreExpect(editorContext.RedoCommand(), "Redo DeleteSelection should succeed");
|
||||
MetaCoreExpect(scene.GetGameObjects().size() < countBeforeDelete, "Redo DeleteSelection should remove object again");
|
||||
|
||||
coreServicesModule->Shutdown(moduleRegistry);
|
||||
moduleRegistry.ShutdownServices();
|
||||
_putenv_s("METACORE_PROJECT_PATH", "");
|
||||
std::filesystem::remove_all(tempProjectRoot);
|
||||
}
|
||||
|
||||
void MetaCoreTestBuiltinModuleComposition() {
|
||||
MetaCore::MetaCoreEditorModuleRegistry moduleRegistry;
|
||||
auto coreServicesModule = MetaCore::MetaCoreCreateBuiltinCoreServicesModule();
|
||||
@ -6358,6 +6488,8 @@ int main() {
|
||||
MetaCoreTestSelectionStateMachine();
|
||||
std::cout << "[RUN] MetaCoreTestUndoRedo..." << std::endl;
|
||||
MetaCoreTestUndoRedo();
|
||||
std::cout << "[RUN] MetaCoreTestHierarchyEditingCommandsDirtyAndUndo..." << std::endl;
|
||||
MetaCoreTestHierarchyEditingCommandsDirtyAndUndo();
|
||||
std::cout << "[RUN] MetaCoreTestBuiltinModuleComposition..." << std::endl;
|
||||
MetaCoreTestBuiltinModuleComposition();
|
||||
std::cout << "[RUN] MetaCoreTestLegacyBinarySceneStartupLoadCompatibility..." << std::endl;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user