Initial MetaCore scaffold and editor shell integration
This commit is contained in:
commit
8d6e6bc0da
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
build/
|
||||
vcpkg_installed/
|
||||
downloads/
|
||||
SandboxProject*/
|
||||
TestProject/
|
||||
imgui.ini
|
||||
*.txt
|
||||
*.log
|
||||
*.obj
|
||||
*.pdb
|
||||
*.ilk
|
||||
*.lib
|
||||
*.exe
|
||||
*.vcxproj*
|
||||
*.sln
|
||||
.vs/
|
||||
CMakeUserPresets.json
|
||||
85
CMakePresets.json
Normal file
85
CMakePresets.json
Normal file
@ -0,0 +1,85 @@
|
||||
{
|
||||
"version": 6,
|
||||
"cmakeMinimumRequired": {
|
||||
"major": 3,
|
||||
"minor": 26,
|
||||
"patch": 0
|
||||
},
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "base",
|
||||
"hidden": true,
|
||||
"generator": "Visual Studio 17 2022",
|
||||
"binaryDir": "${sourceDir}/build/${presetName}",
|
||||
"toolchainFile": "D:/vcpkg/scripts/buildsystems/vcpkg.cmake",
|
||||
"cacheVariables": {
|
||||
"CMAKE_CXX_STANDARD": "20",
|
||||
"METACORE_BUILD_TESTS": "ON",
|
||||
"METACORE_ENABLE_PANDA3D": "OFF",
|
||||
"METACORE_ENABLE_IMGUI": "OFF",
|
||||
"METACORE_ENABLE_IMGUIZMO": "OFF",
|
||||
"METACORE_ENABLE_RMLUI": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "vs2022-debug",
|
||||
"inherits": "base",
|
||||
"displayName": "VS2022 Debug",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "vs2022-release",
|
||||
"inherits": "base",
|
||||
"displayName": "VS2022 Release",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "vs2022-integration-debug",
|
||||
"inherits": "base",
|
||||
"displayName": "VS2022 Integration Debug",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug",
|
||||
"METACORE_ENABLE_PANDA3D": "ON",
|
||||
"METACORE_ENABLE_IMGUI": "ON",
|
||||
"METACORE_ENABLE_IMGUIZMO": "ON",
|
||||
"METACORE_ENABLE_RMLUI": "ON"
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "build-debug",
|
||||
"configurePreset": "vs2022-debug"
|
||||
},
|
||||
{
|
||||
"name": "build-release",
|
||||
"configurePreset": "vs2022-release"
|
||||
},
|
||||
{
|
||||
"name": "build-integration-debug",
|
||||
"configurePreset": "vs2022-integration-debug"
|
||||
}
|
||||
],
|
||||
"testPresets": [
|
||||
{
|
||||
"name": "test-debug",
|
||||
"configurePreset": "vs2022-debug",
|
||||
"configuration": "Debug",
|
||||
"output": {
|
||||
"outputOnFailure": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "test-integration-debug",
|
||||
"configurePreset": "vs2022-integration-debug",
|
||||
"configuration": "Debug",
|
||||
"output": {
|
||||
"outputOnFailure": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
93
MetaCore/engine/assets/AssetDatabase.cpp
Normal file
93
MetaCore/engine/assets/AssetDatabase.cpp
Normal file
@ -0,0 +1,93 @@
|
||||
#include "MetaCore/engine/assets/AssetDatabase.h"
|
||||
|
||||
#include "MetaCore/foundation/FileSystem.h"
|
||||
#include "MetaCore/foundation/Guid.h"
|
||||
#include "MetaCore/foundation/Json.h"
|
||||
|
||||
namespace metacore::engine::assets {
|
||||
|
||||
using foundation::JsonValue;
|
||||
|
||||
std::map<std::string, AssetRecord>& AssetDatabase::records() {
|
||||
return records_;
|
||||
}
|
||||
|
||||
const std::map<std::string, AssetRecord>& AssetDatabase::records() const {
|
||||
return records_;
|
||||
}
|
||||
|
||||
bool AssetDatabase::save(const std::filesystem::path& asset_db_file) const {
|
||||
JsonValue::object_type root;
|
||||
JsonValue::array_type records_json;
|
||||
for (const auto& [id, record] : records_) {
|
||||
JsonValue::object_type node;
|
||||
node["id"] = record.id;
|
||||
node["type"] = record.type;
|
||||
node["relative_path"] = record.relative_path;
|
||||
records_json.emplace_back(JsonValue(std::move(node)));
|
||||
}
|
||||
root["records"] = JsonValue(std::move(records_json));
|
||||
return foundation::write_text_file(asset_db_file, JsonValue(std::move(root)).stringify(2));
|
||||
}
|
||||
|
||||
bool AssetDatabase::load(const std::filesystem::path& asset_db_file) {
|
||||
const auto text = foundation::read_text_file(asset_db_file);
|
||||
if (!text.has_value()) {
|
||||
return false;
|
||||
}
|
||||
records_.clear();
|
||||
const auto root = JsonValue::parse(*text);
|
||||
for (const auto& node : root["records"].as_array()) {
|
||||
AssetRecord record;
|
||||
record.id = node["id"].as_string();
|
||||
record.type = node["type"].as_string();
|
||||
record.relative_path = node["relative_path"].as_string();
|
||||
records_[record.id] = record;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void AssetService::ensure_default_asset_tree(const std::filesystem::path& project_root) const {
|
||||
foundation::ensure_directory(project_root / "Assets" / "Models");
|
||||
foundation::ensure_directory(project_root / "Assets" / "Textures");
|
||||
foundation::ensure_directory(project_root / "Assets" / "Audio");
|
||||
foundation::ensure_directory(project_root / "Assets" / "UI");
|
||||
foundation::ensure_directory(project_root / "Assets" / "Materials");
|
||||
}
|
||||
|
||||
std::string AssetService::ensure_asset_record(
|
||||
AssetDatabase& asset_database,
|
||||
const std::string& type,
|
||||
const std::filesystem::path& relative_path
|
||||
) const {
|
||||
for (const auto& [id, record] : asset_database.records()) {
|
||||
if (record.relative_path == relative_path.generic_string()) {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
AssetRecord record;
|
||||
record.id = foundation::generate_guid();
|
||||
record.type = type;
|
||||
record.relative_path = relative_path.generic_string();
|
||||
asset_database.records()[record.id] = record;
|
||||
return record.id;
|
||||
}
|
||||
|
||||
bool AssetService::write_meta_file(
|
||||
const std::filesystem::path& project_root,
|
||||
const std::filesystem::path& relative_path,
|
||||
const std::string& asset_id,
|
||||
const std::string& type
|
||||
) const {
|
||||
JsonValue::object_type root;
|
||||
root["id"] = asset_id;
|
||||
root["type"] = type;
|
||||
root["relative_path"] = relative_path.generic_string();
|
||||
return foundation::write_text_file(
|
||||
project_root / (relative_path.generic_string() + ".meta"),
|
||||
JsonValue(std::move(root)).stringify(2)
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace metacore::engine::assets
|
||||
43
MetaCore/engine/assets/AssetDatabase.h
Normal file
43
MetaCore/engine/assets/AssetDatabase.h
Normal file
@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace metacore::engine::assets {
|
||||
|
||||
struct AssetRecord {
|
||||
std::string id;
|
||||
std::string type;
|
||||
std::string relative_path;
|
||||
};
|
||||
|
||||
class AssetDatabase {
|
||||
public:
|
||||
std::map<std::string, AssetRecord>& records();
|
||||
const std::map<std::string, AssetRecord>& records() const;
|
||||
|
||||
bool save(const std::filesystem::path& asset_db_file) const;
|
||||
bool load(const std::filesystem::path& asset_db_file);
|
||||
|
||||
private:
|
||||
std::map<std::string, AssetRecord> records_;
|
||||
};
|
||||
|
||||
class AssetService {
|
||||
public:
|
||||
void ensure_default_asset_tree(const std::filesystem::path& project_root) const;
|
||||
std::string ensure_asset_record(
|
||||
AssetDatabase& asset_database,
|
||||
const std::string& type,
|
||||
const std::filesystem::path& relative_path
|
||||
) const;
|
||||
bool write_meta_file(
|
||||
const std::filesystem::path& project_root,
|
||||
const std::filesystem::path& relative_path,
|
||||
const std::string& asset_id,
|
||||
const std::string& type
|
||||
) const;
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::assets
|
||||
45
MetaCore/engine/editor_core/CommandService.cpp
Normal file
45
MetaCore/engine/editor_core/CommandService.cpp
Normal file
@ -0,0 +1,45 @@
|
||||
#include "MetaCore/engine/editor_core/CommandService.h"
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
void CommandService::execute(Command command) {
|
||||
if (command.execute) {
|
||||
command.execute();
|
||||
}
|
||||
undo_stack_.push_back(std::move(command));
|
||||
redo_stack_.clear();
|
||||
}
|
||||
|
||||
bool CommandService::can_undo() const {
|
||||
return !undo_stack_.empty();
|
||||
}
|
||||
|
||||
bool CommandService::can_redo() const {
|
||||
return !redo_stack_.empty();
|
||||
}
|
||||
|
||||
void CommandService::undo() {
|
||||
if (undo_stack_.empty()) {
|
||||
return;
|
||||
}
|
||||
auto command = std::move(undo_stack_.back());
|
||||
undo_stack_.pop_back();
|
||||
if (command.undo) {
|
||||
command.undo();
|
||||
}
|
||||
redo_stack_.push_back(std::move(command));
|
||||
}
|
||||
|
||||
void CommandService::redo() {
|
||||
if (redo_stack_.empty()) {
|
||||
return;
|
||||
}
|
||||
auto command = std::move(redo_stack_.back());
|
||||
redo_stack_.pop_back();
|
||||
if (command.execute) {
|
||||
command.execute();
|
||||
}
|
||||
undo_stack_.push_back(std::move(command));
|
||||
}
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
28
MetaCore/engine/editor_core/CommandService.h
Normal file
28
MetaCore/engine/editor_core/CommandService.h
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
struct Command {
|
||||
std::string label;
|
||||
std::function<void()> execute;
|
||||
std::function<void()> undo;
|
||||
};
|
||||
|
||||
class CommandService {
|
||||
public:
|
||||
void execute(Command command);
|
||||
bool can_undo() const;
|
||||
bool can_redo() const;
|
||||
void undo();
|
||||
void redo();
|
||||
|
||||
private:
|
||||
std::vector<Command> undo_stack_;
|
||||
std::vector<Command> redo_stack_;
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
21
MetaCore/engine/editor_core/EditorActions.cpp
Normal file
21
MetaCore/engine/editor_core/EditorActions.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#include "MetaCore/engine/editor_core/EditorActions.h"
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
std::vector<EditorAction> EditorActionCatalog::build_default_actions() const {
|
||||
return {
|
||||
{EditorActionId::NewScene, "New Scene", "Ctrl+N", true},
|
||||
{EditorActionId::SaveScene, "Save Scene", "Ctrl+S", true},
|
||||
{EditorActionId::CreateEmptyObject, "Create Empty", "Ctrl+Shift+N", true},
|
||||
{EditorActionId::DeleteSelected, "Delete Selected", "Delete", true},
|
||||
{EditorActionId::RenameSelected, "Rename Selected", "F2", true},
|
||||
{EditorActionId::FocusSelected, "Focus Selected", "F", true},
|
||||
{EditorActionId::ToggleHierarchy, "Toggle Hierarchy", "", true},
|
||||
{EditorActionId::ToggleInspector, "Toggle Inspector", "", true},
|
||||
{EditorActionId::ToggleProject, "Toggle Project", "", true},
|
||||
{EditorActionId::ToggleConsole, "Toggle Console", "", true},
|
||||
{EditorActionId::ToggleRuntimePreview, "Toggle Runtime Preview", "", true}
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
34
MetaCore/engine/editor_core/EditorActions.h
Normal file
34
MetaCore/engine/editor_core/EditorActions.h
Normal file
@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
enum class EditorActionId {
|
||||
NewScene,
|
||||
SaveScene,
|
||||
CreateEmptyObject,
|
||||
DeleteSelected,
|
||||
RenameSelected,
|
||||
FocusSelected,
|
||||
ToggleHierarchy,
|
||||
ToggleInspector,
|
||||
ToggleProject,
|
||||
ToggleConsole,
|
||||
ToggleRuntimePreview
|
||||
};
|
||||
|
||||
struct EditorAction {
|
||||
EditorActionId id;
|
||||
std::string label;
|
||||
std::string shortcut;
|
||||
bool enabled = true;
|
||||
};
|
||||
|
||||
class EditorActionCatalog {
|
||||
public:
|
||||
[[nodiscard]] std::vector<EditorAction> build_default_actions() const;
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
36
MetaCore/engine/editor_core/EditorLayoutService.cpp
Normal file
36
MetaCore/engine/editor_core/EditorLayoutService.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
#include "MetaCore/engine/editor_core/EditorLayoutService.h"
|
||||
|
||||
#include "MetaCore/foundation/FileSystem.h"
|
||||
#include "MetaCore/foundation/Json.h"
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
bool EditorLayoutService::save(const std::filesystem::path& layout_file, const EditorLayoutState& state) const {
|
||||
foundation::JsonValue::object_type root;
|
||||
root["layout_name"] = state.layout_name;
|
||||
root["hierarchy_visible"] = state.hierarchy_visible;
|
||||
root["inspector_visible"] = state.inspector_visible;
|
||||
root["project_visible"] = state.project_visible;
|
||||
root["console_visible"] = state.console_visible;
|
||||
root["runtime_preview_visible"] = state.runtime_preview_visible;
|
||||
root["toolbar_visible"] = state.toolbar_visible;
|
||||
return foundation::write_text_file(layout_file, foundation::JsonValue(std::move(root)).stringify(2));
|
||||
}
|
||||
|
||||
bool EditorLayoutService::load(const std::filesystem::path& layout_file, EditorLayoutState& out_state) const {
|
||||
const auto text = foundation::read_text_file(layout_file);
|
||||
if (!text.has_value()) {
|
||||
return false;
|
||||
}
|
||||
const auto json = foundation::JsonValue::parse(*text);
|
||||
out_state.layout_name = json["layout_name"].as_string();
|
||||
out_state.hierarchy_visible = json["hierarchy_visible"].as_bool(true);
|
||||
out_state.inspector_visible = json["inspector_visible"].as_bool(true);
|
||||
out_state.project_visible = json["project_visible"].as_bool(true);
|
||||
out_state.console_visible = json["console_visible"].as_bool(true);
|
||||
out_state.runtime_preview_visible = json["runtime_preview_visible"].as_bool(true);
|
||||
out_state.toolbar_visible = json["toolbar_visible"].as_bool(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
24
MetaCore/engine/editor_core/EditorLayoutService.h
Normal file
24
MetaCore/engine/editor_core/EditorLayoutService.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
struct EditorLayoutState {
|
||||
std::string layout_name = "Default";
|
||||
bool hierarchy_visible = true;
|
||||
bool inspector_visible = true;
|
||||
bool project_visible = true;
|
||||
bool console_visible = true;
|
||||
bool runtime_preview_visible = true;
|
||||
bool toolbar_visible = true;
|
||||
};
|
||||
|
||||
class EditorLayoutService {
|
||||
public:
|
||||
bool save(const std::filesystem::path& layout_file, const EditorLayoutState& state) const;
|
||||
bool load(const std::filesystem::path& layout_file, EditorLayoutState& out_state) const;
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
62
MetaCore/engine/editor_core/EditorSession.cpp
Normal file
62
MetaCore/engine/editor_core/EditorSession.cpp
Normal file
@ -0,0 +1,62 @@
|
||||
#include "MetaCore/engine/editor_core/EditorSession.h"
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
void EditorSession::mark_dirty() {
|
||||
dirty_ = true;
|
||||
}
|
||||
|
||||
void EditorSession::clear_dirty() {
|
||||
dirty_ = false;
|
||||
}
|
||||
|
||||
bool EditorSession::is_dirty() const {
|
||||
return dirty_;
|
||||
}
|
||||
|
||||
void EditorSession::set_status_text(std::string text) {
|
||||
status_text_ = std::move(text);
|
||||
}
|
||||
|
||||
const std::string& EditorSession::status_text() const {
|
||||
return status_text_;
|
||||
}
|
||||
|
||||
void EditorSession::add_console_message(std::string category, std::string text) {
|
||||
console_messages_.push_back(ConsoleMessage{std::move(category), std::move(text)});
|
||||
}
|
||||
|
||||
const std::vector<ConsoleMessage>& EditorSession::console_messages() const {
|
||||
return console_messages_;
|
||||
}
|
||||
|
||||
PanelState& EditorSession::panel_state() {
|
||||
return panel_state_;
|
||||
}
|
||||
|
||||
const PanelState& EditorSession::panel_state() const {
|
||||
return panel_state_;
|
||||
}
|
||||
|
||||
EditorLayoutState EditorSession::to_layout_state() const {
|
||||
EditorLayoutState state;
|
||||
state.layout_name = "Default";
|
||||
state.hierarchy_visible = panel_state_.hierarchy_visible;
|
||||
state.inspector_visible = panel_state_.inspector_visible;
|
||||
state.project_visible = panel_state_.project_visible;
|
||||
state.console_visible = panel_state_.console_visible;
|
||||
state.runtime_preview_visible = panel_state_.runtime_preview_visible;
|
||||
state.toolbar_visible = panel_state_.toolbar_visible;
|
||||
return state;
|
||||
}
|
||||
|
||||
void EditorSession::apply_layout_state(const EditorLayoutState& layout_state) {
|
||||
panel_state_.hierarchy_visible = layout_state.hierarchy_visible;
|
||||
panel_state_.inspector_visible = layout_state.inspector_visible;
|
||||
panel_state_.project_visible = layout_state.project_visible;
|
||||
panel_state_.console_visible = layout_state.console_visible;
|
||||
panel_state_.runtime_preview_visible = layout_state.runtime_preview_visible;
|
||||
panel_state_.toolbar_visible = layout_state.toolbar_visible;
|
||||
}
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
50
MetaCore/engine/editor_core/EditorSession.h
Normal file
50
MetaCore/engine/editor_core/EditorSession.h
Normal file
@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCore/engine/editor_core/EditorLayoutService.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
struct ConsoleMessage {
|
||||
std::string category;
|
||||
std::string text;
|
||||
};
|
||||
|
||||
struct PanelState {
|
||||
bool hierarchy_visible = true;
|
||||
bool inspector_visible = true;
|
||||
bool project_visible = true;
|
||||
bool console_visible = true;
|
||||
bool viewport_visible = true;
|
||||
bool runtime_preview_visible = true;
|
||||
bool toolbar_visible = true;
|
||||
};
|
||||
|
||||
class EditorSession {
|
||||
public:
|
||||
void mark_dirty();
|
||||
void clear_dirty();
|
||||
[[nodiscard]] bool is_dirty() const;
|
||||
|
||||
void set_status_text(std::string text);
|
||||
[[nodiscard]] const std::string& status_text() const;
|
||||
|
||||
void add_console_message(std::string category, std::string text);
|
||||
[[nodiscard]] const std::vector<ConsoleMessage>& console_messages() const;
|
||||
|
||||
PanelState& panel_state();
|
||||
const PanelState& panel_state() const;
|
||||
|
||||
EditorLayoutState to_layout_state() const;
|
||||
void apply_layout_state(const EditorLayoutState& layout_state);
|
||||
|
||||
private:
|
||||
bool dirty_ = false;
|
||||
std::string status_text_ = "Ready";
|
||||
std::vector<ConsoleMessage> console_messages_;
|
||||
PanelState panel_state_{};
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
22
MetaCore/engine/editor_core/InspectorService.cpp
Normal file
22
MetaCore/engine/editor_core/InspectorService.cpp
Normal file
@ -0,0 +1,22 @@
|
||||
#include "MetaCore/engine/editor_core/InspectorService.h"
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
std::optional<std::reference_wrapper<const metacore::engine::scene::SceneObject>> InspectorService::current_selection(
|
||||
const metacore::engine::scene::Scene& scene,
|
||||
const SelectionService& selection_service
|
||||
) const {
|
||||
if (!selection_service.selection().has_value()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
for (const auto& object : scene.objects) {
|
||||
if (object.id == *selection_service.selection()) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
18
MetaCore/engine/editor_core/InspectorService.h
Normal file
18
MetaCore/engine/editor_core/InspectorService.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCore/engine/editor_core/SelectionService.h"
|
||||
#include "MetaCore/engine/scene/Scene.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
class InspectorService {
|
||||
public:
|
||||
std::optional<std::reference_wrapper<const metacore::engine::scene::SceneObject>> current_selection(
|
||||
const metacore::engine::scene::Scene& scene,
|
||||
const SelectionService& selection_service
|
||||
) const;
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
106
MetaCore/engine/editor_core/PanelViewModels.cpp
Normal file
106
MetaCore/engine/editor_core/PanelViewModels.cpp
Normal file
@ -0,0 +1,106 @@
|
||||
#include "MetaCore/engine/editor_core/PanelViewModels.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
std::vector<HierarchyItem> PanelViewModels::build_hierarchy(
|
||||
const metacore::engine::scene::Scene& scene,
|
||||
const SelectionService& selection_service
|
||||
) const {
|
||||
std::vector<HierarchyItem> items;
|
||||
items.reserve(scene.objects.size());
|
||||
|
||||
for (const auto& object : scene.objects) {
|
||||
const bool has_children = std::any_of(
|
||||
scene.objects.begin(),
|
||||
scene.objects.end(),
|
||||
[&object](const metacore::engine::scene::SceneObject& candidate) {
|
||||
return candidate.parent_id == object.id;
|
||||
}
|
||||
);
|
||||
|
||||
items.push_back(HierarchyItem{
|
||||
object.id,
|
||||
object.parent_id,
|
||||
object.name,
|
||||
selection_service.selection().has_value() && *selection_service.selection() == object.id,
|
||||
has_children
|
||||
});
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
std::vector<ProjectAssetItem> PanelViewModels::build_project_assets(
|
||||
const metacore::engine::assets::AssetDatabase& asset_database
|
||||
) const {
|
||||
std::vector<ProjectAssetItem> items;
|
||||
items.reserve(asset_database.records().size());
|
||||
|
||||
for (const auto& [id, record] : asset_database.records()) {
|
||||
items.push_back(ProjectAssetItem{id, record.type, record.relative_path});
|
||||
}
|
||||
|
||||
std::sort(items.begin(), items.end(), [](const ProjectAssetItem& lhs, const ProjectAssetItem& rhs) {
|
||||
return lhs.relative_path < rhs.relative_path;
|
||||
});
|
||||
return items;
|
||||
}
|
||||
|
||||
InspectorViewModel PanelViewModels::build_inspector(
|
||||
const metacore::engine::scene::Scene& scene,
|
||||
const SelectionService& selection_service
|
||||
) const {
|
||||
InspectorViewModel model;
|
||||
|
||||
if (!selection_service.selection().has_value()) {
|
||||
model.object_name = "Nothing Selected";
|
||||
return model;
|
||||
}
|
||||
|
||||
const auto selected_id = *selection_service.selection();
|
||||
for (const auto& object : scene.objects) {
|
||||
if (object.id != selected_id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
model.object_name = object.name;
|
||||
model.fields.push_back({"Id", object.id});
|
||||
model.fields.push_back({"Position", std::to_string(object.transform.position[0]) + ", " +
|
||||
std::to_string(object.transform.position[1]) + ", " +
|
||||
std::to_string(object.transform.position[2])});
|
||||
model.fields.push_back({"Rotation", std::to_string(object.transform.rotation[0]) + ", " +
|
||||
std::to_string(object.transform.rotation[1]) + ", " +
|
||||
std::to_string(object.transform.rotation[2])});
|
||||
model.fields.push_back({"Scale", std::to_string(object.transform.scale[0]) + ", " +
|
||||
std::to_string(object.transform.scale[1]) + ", " +
|
||||
std::to_string(object.transform.scale[2])});
|
||||
if (object.has_camera) {
|
||||
model.fields.push_back({"Camera", object.camera.primary ? "Primary" : "Secondary"});
|
||||
}
|
||||
if (object.has_light) {
|
||||
model.fields.push_back({"Light", "Enabled"});
|
||||
}
|
||||
if (object.has_mesh_renderer) {
|
||||
model.fields.push_back({"Mesh", object.mesh_renderer.mesh_asset_id});
|
||||
}
|
||||
if (object.has_ui_document) {
|
||||
model.fields.push_back({"UI Document", object.ui_document.document_path});
|
||||
model.fields.push_back({"UI Stylesheet", object.ui_document.stylesheet_path});
|
||||
}
|
||||
if (object.has_collision) {
|
||||
model.fields.push_back({"Collision", object.collision.shape});
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
model.object_name = "Missing Selection";
|
||||
return model;
|
||||
}
|
||||
|
||||
const std::vector<ConsoleMessage>& PanelViewModels::console_messages(const EditorSession& session) const {
|
||||
return session.console_messages();
|
||||
}
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
58
MetaCore/engine/editor_core/PanelViewModels.h
Normal file
58
MetaCore/engine/editor_core/PanelViewModels.h
Normal file
@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCore/engine/assets/AssetDatabase.h"
|
||||
#include "MetaCore/engine/editor_core/EditorSession.h"
|
||||
#include "MetaCore/engine/editor_core/SelectionService.h"
|
||||
#include "MetaCore/engine/scene/Scene.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
struct HierarchyItem {
|
||||
metacore::engine::scene::EntityId id;
|
||||
metacore::engine::scene::EntityId parent_id;
|
||||
std::string name;
|
||||
bool selected = false;
|
||||
bool has_children = false;
|
||||
};
|
||||
|
||||
struct ProjectAssetItem {
|
||||
std::string id;
|
||||
std::string type;
|
||||
std::string relative_path;
|
||||
};
|
||||
|
||||
struct InspectorField {
|
||||
std::string label;
|
||||
std::string value;
|
||||
};
|
||||
|
||||
struct InspectorViewModel {
|
||||
std::string object_name;
|
||||
std::vector<InspectorField> fields;
|
||||
};
|
||||
|
||||
class PanelViewModels {
|
||||
public:
|
||||
[[nodiscard]] std::vector<HierarchyItem> build_hierarchy(
|
||||
const metacore::engine::scene::Scene& scene,
|
||||
const SelectionService& selection_service
|
||||
) const;
|
||||
|
||||
[[nodiscard]] std::vector<ProjectAssetItem> build_project_assets(
|
||||
const metacore::engine::assets::AssetDatabase& asset_database
|
||||
) const;
|
||||
|
||||
[[nodiscard]] InspectorViewModel build_inspector(
|
||||
const metacore::engine::scene::Scene& scene,
|
||||
const SelectionService& selection_service
|
||||
) const;
|
||||
|
||||
[[nodiscard]] const std::vector<ConsoleMessage>& console_messages(
|
||||
const EditorSession& session
|
||||
) const;
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
160
MetaCore/engine/editor_core/SceneCommands.cpp
Normal file
160
MetaCore/engine/editor_core/SceneCommands.cpp
Normal file
@ -0,0 +1,160 @@
|
||||
#include "MetaCore/engine/editor_core/SceneCommands.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
void SceneCommands::create_empty_object(
|
||||
CommandService& command_service,
|
||||
EditorSession& session,
|
||||
SelectionService& selection_service,
|
||||
metacore::engine::scene::SceneService& scene_service,
|
||||
metacore::engine::scene::Scene& scene,
|
||||
const std::string& name
|
||||
) {
|
||||
metacore::engine::scene::EntityId created_id;
|
||||
|
||||
command_service.execute(Command{
|
||||
"Create Empty Object",
|
||||
[&scene_service, &scene, &selection_service, &session, &created_id, name]() {
|
||||
auto& object = scene_service.create_object(scene, name);
|
||||
created_id = object.id;
|
||||
selection_service.select(created_id);
|
||||
session.mark_dirty();
|
||||
session.set_status_text("Created object: " + name);
|
||||
session.add_console_message("Scene", "Created object '" + name + "'");
|
||||
},
|
||||
[&scene_service, &scene, &selection_service, &session, &created_id]() {
|
||||
scene_service.destroy_object(scene, created_id);
|
||||
selection_service.clear();
|
||||
session.mark_dirty();
|
||||
session.set_status_text("Undo create object");
|
||||
session.add_console_message("Scene", "Undid object creation");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SceneCommands::rename_selected_object(
|
||||
CommandService& command_service,
|
||||
EditorSession& session,
|
||||
SelectionService& selection_service,
|
||||
metacore::engine::scene::SceneService& scene_service,
|
||||
metacore::engine::scene::Scene& scene,
|
||||
const std::string& new_name
|
||||
) {
|
||||
if (!selection_service.selection().has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto selected_id = *selection_service.selection();
|
||||
const auto object = scene_service.find_object(scene, selected_id);
|
||||
if (!object.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string old_name = object->get().name;
|
||||
command_service.execute(Command{
|
||||
"Rename Object",
|
||||
[&scene_service, &scene, &session, selected_id, new_name, old_name]() {
|
||||
scene_service.rename_object(scene, selected_id, new_name);
|
||||
session.mark_dirty();
|
||||
session.set_status_text("Renamed object to: " + new_name);
|
||||
session.add_console_message("Scene", "Renamed object '" + old_name + "' to '" + new_name + "'");
|
||||
},
|
||||
[&scene_service, &scene, &session, selected_id, old_name]() {
|
||||
scene_service.rename_object(scene, selected_id, old_name);
|
||||
session.mark_dirty();
|
||||
session.set_status_text("Undo rename object");
|
||||
session.add_console_message("Scene", "Undid object rename");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SceneCommands::delete_selected_object(
|
||||
CommandService& command_service,
|
||||
EditorSession& session,
|
||||
SelectionService& selection_service,
|
||||
metacore::engine::scene::SceneService& scene_service,
|
||||
metacore::engine::scene::Scene& scene
|
||||
) {
|
||||
if (!selection_service.selection().has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto selected_id = *selection_service.selection();
|
||||
const auto object = scene_service.find_object(scene, selected_id);
|
||||
if (!object.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto deleted_object = object->get();
|
||||
command_service.execute(Command{
|
||||
"Delete Object",
|
||||
[&scene_service, &scene, &selection_service, &session, selected_id, deleted_object]() {
|
||||
scene_service.destroy_object(scene, selected_id);
|
||||
selection_service.clear();
|
||||
session.mark_dirty();
|
||||
session.set_status_text("Deleted object: " + deleted_object.name);
|
||||
session.add_console_message("Scene", "Deleted object '" + deleted_object.name + "'");
|
||||
},
|
||||
[&scene, &selection_service, &session, deleted_object]() {
|
||||
scene.objects.push_back(deleted_object);
|
||||
selection_service.select(deleted_object.id);
|
||||
session.mark_dirty();
|
||||
session.set_status_text("Undo delete object");
|
||||
session.add_console_message("Scene", "Restored object '" + deleted_object.name + "'");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void SceneCommands::focus_selected_object(
|
||||
EditorSession& session,
|
||||
const SelectionService& selection_service,
|
||||
ViewportService& viewport_service
|
||||
) {
|
||||
if (!selection_service.selection().has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
viewport_service.focus_selection(*selection_service.selection());
|
||||
session.set_status_text("Focused selected object");
|
||||
session.add_console_message("Viewport", "Focused selected object");
|
||||
}
|
||||
|
||||
void SceneCommands::apply_transform_to_selected_object(
|
||||
CommandService& command_service,
|
||||
EditorSession& session,
|
||||
const SelectionService& selection_service,
|
||||
metacore::engine::scene::SceneService& scene_service,
|
||||
metacore::engine::scene::Scene& scene,
|
||||
const metacore::engine::scene::TransformComponent& new_transform
|
||||
) {
|
||||
if (!selection_service.selection().has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto selected_id = *selection_service.selection();
|
||||
const auto object = scene_service.find_object(scene, selected_id);
|
||||
if (!object.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto old_transform = object->get().transform;
|
||||
command_service.execute(Command{
|
||||
"Apply Transform",
|
||||
[&scene_service, &scene, &session, selected_id, new_transform]() {
|
||||
scene_service.set_object_transform(scene, selected_id, new_transform);
|
||||
session.mark_dirty();
|
||||
session.set_status_text("Applied transform");
|
||||
session.add_console_message("Viewport", "Applied transform edit");
|
||||
},
|
||||
[&scene_service, &scene, &session, selected_id, old_transform]() {
|
||||
scene_service.set_object_transform(scene, selected_id, old_transform);
|
||||
session.mark_dirty();
|
||||
session.set_status_text("Undo transform");
|
||||
session.add_console_message("Viewport", "Undid transform edit");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
55
MetaCore/engine/editor_core/SceneCommands.h
Normal file
55
MetaCore/engine/editor_core/SceneCommands.h
Normal file
@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCore/engine/editor_core/CommandService.h"
|
||||
#include "MetaCore/engine/editor_core/EditorSession.h"
|
||||
#include "MetaCore/engine/editor_core/SelectionService.h"
|
||||
#include "MetaCore/engine/editor_core/ViewportService.h"
|
||||
#include "MetaCore/engine/scene/Scene.h"
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
class SceneCommands {
|
||||
public:
|
||||
static void create_empty_object(
|
||||
CommandService& command_service,
|
||||
EditorSession& session,
|
||||
SelectionService& selection_service,
|
||||
metacore::engine::scene::SceneService& scene_service,
|
||||
metacore::engine::scene::Scene& scene,
|
||||
const std::string& name
|
||||
);
|
||||
|
||||
static void rename_selected_object(
|
||||
CommandService& command_service,
|
||||
EditorSession& session,
|
||||
SelectionService& selection_service,
|
||||
metacore::engine::scene::SceneService& scene_service,
|
||||
metacore::engine::scene::Scene& scene,
|
||||
const std::string& new_name
|
||||
);
|
||||
|
||||
static void delete_selected_object(
|
||||
CommandService& command_service,
|
||||
EditorSession& session,
|
||||
SelectionService& selection_service,
|
||||
metacore::engine::scene::SceneService& scene_service,
|
||||
metacore::engine::scene::Scene& scene
|
||||
);
|
||||
|
||||
static void focus_selected_object(
|
||||
EditorSession& session,
|
||||
const SelectionService& selection_service,
|
||||
ViewportService& viewport_service
|
||||
);
|
||||
|
||||
static void apply_transform_to_selected_object(
|
||||
CommandService& command_service,
|
||||
EditorSession& session,
|
||||
const SelectionService& selection_service,
|
||||
metacore::engine::scene::SceneService& scene_service,
|
||||
metacore::engine::scene::Scene& scene,
|
||||
const metacore::engine::scene::TransformComponent& new_transform
|
||||
);
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
21
MetaCore/engine/editor_core/SelectionService.cpp
Normal file
21
MetaCore/engine/editor_core/SelectionService.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#include "MetaCore/engine/editor_core/SelectionService.h"
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
void SelectionService::select(const metacore::engine::scene::EntityId& id) {
|
||||
selected_id_ = id;
|
||||
}
|
||||
|
||||
void SelectionService::clear() {
|
||||
selected_id_.reset();
|
||||
}
|
||||
|
||||
bool SelectionService::has_selection() const {
|
||||
return selected_id_.has_value();
|
||||
}
|
||||
|
||||
const std::optional<metacore::engine::scene::EntityId>& SelectionService::selection() const {
|
||||
return selected_id_;
|
||||
}
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
20
MetaCore/engine/editor_core/SelectionService.h
Normal file
20
MetaCore/engine/editor_core/SelectionService.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCore/engine/scene/Components.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
class SelectionService {
|
||||
public:
|
||||
void select(const metacore::engine::scene::EntityId& id);
|
||||
void clear();
|
||||
[[nodiscard]] bool has_selection() const;
|
||||
[[nodiscard]] const std::optional<metacore::engine::scene::EntityId>& selection() const;
|
||||
|
||||
private:
|
||||
std::optional<metacore::engine::scene::EntityId> selected_id_;
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
63
MetaCore/engine/editor_core/ShellComposition.cpp
Normal file
63
MetaCore/engine/editor_core/ShellComposition.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
#include "MetaCore/engine/editor_core/ShellComposition.h"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
ShellComposition ShellCompositionBuilder::build(
|
||||
const std::vector<EditorAction>& actions,
|
||||
const WorkspaceLayout& workspace
|
||||
) const {
|
||||
ShellComposition composition;
|
||||
|
||||
MenuEntry file_menu{"File", {}};
|
||||
MenuEntry edit_menu{"Edit", {}};
|
||||
MenuEntry view_menu{"View", {}};
|
||||
|
||||
ToolbarGroup scene_tools{"Scene", {}};
|
||||
ToolbarGroup selection_tools{"Selection", {}};
|
||||
ToolbarGroup view_tools{"View", {}};
|
||||
|
||||
for (const auto& action : actions) {
|
||||
switch (action.id) {
|
||||
case EditorActionId::NewScene:
|
||||
case EditorActionId::SaveScene:
|
||||
file_menu.actions.push_back(action);
|
||||
break;
|
||||
case EditorActionId::CreateEmptyObject:
|
||||
case EditorActionId::DeleteSelected:
|
||||
case EditorActionId::RenameSelected:
|
||||
edit_menu.actions.push_back(action);
|
||||
scene_tools.actions.push_back(action);
|
||||
break;
|
||||
case EditorActionId::FocusSelected:
|
||||
edit_menu.actions.push_back(action);
|
||||
selection_tools.actions.push_back(action);
|
||||
break;
|
||||
case EditorActionId::ToggleHierarchy:
|
||||
case EditorActionId::ToggleInspector:
|
||||
case EditorActionId::ToggleProject:
|
||||
case EditorActionId::ToggleConsole:
|
||||
case EditorActionId::ToggleRuntimePreview:
|
||||
view_menu.actions.push_back(action);
|
||||
view_tools.actions.push_back(action);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
composition.menus = {file_menu, edit_menu, view_menu};
|
||||
composition.toolbar_groups = {scene_tools, selection_tools, view_tools};
|
||||
|
||||
std::map<DockRegion, std::vector<DockPanel>> grouped_panels;
|
||||
for (const auto& panel : workspace.panels) {
|
||||
grouped_panels[panel.region].push_back(panel);
|
||||
}
|
||||
|
||||
for (const auto& [region, panels] : grouped_panels) {
|
||||
composition.sections.push_back(PanelSection{region, panels});
|
||||
}
|
||||
|
||||
return composition;
|
||||
}
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
40
MetaCore/engine/editor_core/ShellComposition.h
Normal file
40
MetaCore/engine/editor_core/ShellComposition.h
Normal file
@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCore/engine/editor_core/EditorActions.h"
|
||||
#include "MetaCore/engine/editor_core/WorkspaceLayout.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
struct MenuEntry {
|
||||
std::string menu_name;
|
||||
std::vector<EditorAction> actions;
|
||||
};
|
||||
|
||||
struct ToolbarGroup {
|
||||
std::string name;
|
||||
std::vector<EditorAction> actions;
|
||||
};
|
||||
|
||||
struct PanelSection {
|
||||
DockRegion region = DockRegion::CenterViewport;
|
||||
std::vector<DockPanel> panels;
|
||||
};
|
||||
|
||||
struct ShellComposition {
|
||||
std::vector<MenuEntry> menus;
|
||||
std::vector<ToolbarGroup> toolbar_groups;
|
||||
std::vector<PanelSection> sections;
|
||||
};
|
||||
|
||||
class ShellCompositionBuilder {
|
||||
public:
|
||||
[[nodiscard]] ShellComposition build(
|
||||
const std::vector<EditorAction>& actions,
|
||||
const WorkspaceLayout& workspace
|
||||
) const;
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
37
MetaCore/engine/editor_core/TransformGizmoService.cpp
Normal file
37
MetaCore/engine/editor_core/TransformGizmoService.cpp
Normal file
@ -0,0 +1,37 @@
|
||||
#include "MetaCore/engine/editor_core/TransformGizmoService.h"
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
void TransformGizmoService::set_operation(const Operation operation) {
|
||||
operation_ = operation;
|
||||
}
|
||||
|
||||
void TransformGizmoService::set_space(const Space space) {
|
||||
space_ = space;
|
||||
}
|
||||
|
||||
void TransformGizmoService::set_snap_enabled(const bool enabled) {
|
||||
snap_enabled_ = enabled;
|
||||
}
|
||||
|
||||
void TransformGizmoService::set_snap_values(const std::array<float, 3> snap_values) {
|
||||
snap_values_ = snap_values;
|
||||
}
|
||||
|
||||
TransformGizmoService::Operation TransformGizmoService::operation() const {
|
||||
return operation_;
|
||||
}
|
||||
|
||||
TransformGizmoService::Space TransformGizmoService::space() const {
|
||||
return space_;
|
||||
}
|
||||
|
||||
bool TransformGizmoService::snap_enabled() const {
|
||||
return snap_enabled_;
|
||||
}
|
||||
|
||||
const std::array<float, 3>& TransformGizmoService::snap_values() const {
|
||||
return snap_values_;
|
||||
}
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
37
MetaCore/engine/editor_core/TransformGizmoService.h
Normal file
37
MetaCore/engine/editor_core/TransformGizmoService.h
Normal file
@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
class TransformGizmoService {
|
||||
public:
|
||||
enum class Operation {
|
||||
Translate,
|
||||
Rotate,
|
||||
Scale,
|
||||
};
|
||||
|
||||
enum class Space {
|
||||
Local,
|
||||
World,
|
||||
};
|
||||
|
||||
void set_operation(Operation operation);
|
||||
void set_space(Space space);
|
||||
void set_snap_enabled(bool enabled);
|
||||
void set_snap_values(std::array<float, 3> snap_values);
|
||||
|
||||
[[nodiscard]] Operation operation() const;
|
||||
[[nodiscard]] Space space() const;
|
||||
[[nodiscard]] bool snap_enabled() const;
|
||||
[[nodiscard]] const std::array<float, 3>& snap_values() const;
|
||||
|
||||
private:
|
||||
Operation operation_ = Operation::Translate;
|
||||
Space space_ = Space::Local;
|
||||
bool snap_enabled_ = false;
|
||||
std::array<float, 3> snap_values_{1.0F, 1.0F, 1.0F};
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
25
MetaCore/engine/editor_core/ViewportService.cpp
Normal file
25
MetaCore/engine/editor_core/ViewportService.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
#include "MetaCore/engine/editor_core/ViewportService.h"
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
void ViewportService::set_camera_position(std::array<float, 3> position) {
|
||||
camera_position_ = position;
|
||||
}
|
||||
|
||||
const std::array<float, 3>& ViewportService::camera_position() const {
|
||||
return camera_position_;
|
||||
}
|
||||
|
||||
void ViewportService::focus_selection(const metacore::engine::scene::EntityId& id) {
|
||||
focus_target_ = id;
|
||||
}
|
||||
|
||||
void ViewportService::clear_focus_target() {
|
||||
focus_target_.reset();
|
||||
}
|
||||
|
||||
const std::optional<metacore::engine::scene::EntityId>& ViewportService::focus_target() const {
|
||||
return focus_target_;
|
||||
}
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
24
MetaCore/engine/editor_core/ViewportService.h
Normal file
24
MetaCore/engine/editor_core/ViewportService.h
Normal file
@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCore/engine/scene/Components.h"
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
class ViewportService {
|
||||
public:
|
||||
void set_camera_position(std::array<float, 3> position);
|
||||
[[nodiscard]] const std::array<float, 3>& camera_position() const;
|
||||
|
||||
void focus_selection(const metacore::engine::scene::EntityId& id);
|
||||
void clear_focus_target();
|
||||
[[nodiscard]] const std::optional<metacore::engine::scene::EntityId>& focus_target() const;
|
||||
|
||||
private:
|
||||
std::array<float, 3> camera_position_{0.0F, -10.0F, 3.0F};
|
||||
std::optional<metacore::engine::scene::EntityId> focus_target_;
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
21
MetaCore/engine/editor_core/WorkspaceLayout.cpp
Normal file
21
MetaCore/engine/editor_core/WorkspaceLayout.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#include "MetaCore/engine/editor_core/WorkspaceLayout.h"
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
WorkspaceLayout WorkspaceLayoutFactory::build_default_layout() const {
|
||||
WorkspaceLayout layout;
|
||||
layout.name = "Default";
|
||||
layout.panels = {
|
||||
{"main_menu", "Main Menu", DockRegion::TopMenuBar, true},
|
||||
{"toolbar", "Toolbar", DockRegion::TopToolbar, true},
|
||||
{"hierarchy", "Hierarchy", DockRegion::LeftSidebar, true},
|
||||
{"project", "Project", DockRegion::LeftSidebar, true},
|
||||
{"viewport", "Viewport", DockRegion::CenterViewport, true},
|
||||
{"inspector", "Inspector", DockRegion::RightSidebar, true},
|
||||
{"console", "Console", DockRegion::BottomConsole, true},
|
||||
{"runtime_preview", "Runtime Preview", DockRegion::BottomRuntimePreview, true}
|
||||
};
|
||||
return layout;
|
||||
}
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
35
MetaCore/engine/editor_core/WorkspaceLayout.h
Normal file
35
MetaCore/engine/editor_core/WorkspaceLayout.h
Normal file
@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace metacore::engine::editor_core {
|
||||
|
||||
enum class DockRegion {
|
||||
TopMenuBar,
|
||||
TopToolbar,
|
||||
LeftSidebar,
|
||||
CenterViewport,
|
||||
RightSidebar,
|
||||
BottomConsole,
|
||||
BottomRuntimePreview
|
||||
};
|
||||
|
||||
struct DockPanel {
|
||||
std::string id;
|
||||
std::string title;
|
||||
DockRegion region = DockRegion::CenterViewport;
|
||||
bool visible = true;
|
||||
};
|
||||
|
||||
struct WorkspaceLayout {
|
||||
std::string name = "Default";
|
||||
std::vector<DockPanel> panels;
|
||||
};
|
||||
|
||||
class WorkspaceLayoutFactory {
|
||||
public:
|
||||
[[nodiscard]] WorkspaceLayout build_default_layout() const;
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::editor_core
|
||||
60
MetaCore/engine/render/RenderTypes.h
Normal file
60
MetaCore/engine/render/RenderTypes.h
Normal file
@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace metacore::engine::render {
|
||||
|
||||
struct ShaderVariantKey {
|
||||
std::map<std::string, std::string> defines;
|
||||
};
|
||||
|
||||
struct MaterialDescriptor {
|
||||
std::array<float, 4> base_color{1.0F, 1.0F, 1.0F, 1.0F};
|
||||
std::array<float, 4> emission{0.0F, 0.0F, 0.0F, 1.0F};
|
||||
float roughness = 1.0F;
|
||||
float metallic = 0.0F;
|
||||
std::filesystem::path base_color_texture;
|
||||
std::filesystem::path metal_roughness_texture;
|
||||
std::filesystem::path normal_texture;
|
||||
std::filesystem::path emission_texture;
|
||||
};
|
||||
|
||||
struct MaterialInstance {
|
||||
std::string id;
|
||||
MaterialDescriptor descriptor;
|
||||
};
|
||||
|
||||
struct FrameUniforms {
|
||||
std::array<float, 3> camera_world_position{0.0F, 0.0F, 0.0F};
|
||||
float exposure = 1.0F;
|
||||
};
|
||||
|
||||
struct LightUniforms {
|
||||
struct LightEntry {
|
||||
std::array<float, 3> color{1.0F, 1.0F, 1.0F};
|
||||
std::array<float, 3> position{0.0F, 0.0F, 0.0F};
|
||||
float intensity = 1.0F;
|
||||
};
|
||||
|
||||
std::vector<LightEntry> lights;
|
||||
};
|
||||
|
||||
struct TonemapSettings {
|
||||
float exposure = 1.0F;
|
||||
};
|
||||
|
||||
class IMetaRenderBackend {
|
||||
public:
|
||||
virtual ~IMetaRenderBackend() = default;
|
||||
|
||||
virtual bool initialize() = 0;
|
||||
virtual void shutdown() = 0;
|
||||
virtual void set_frame_uniforms(const FrameUniforms& uniforms) = 0;
|
||||
virtual void set_light_uniforms(const LightUniforms& uniforms) = 0;
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::render
|
||||
25
MetaCore/engine/render/SimplePbrShaderLibrary.cpp
Normal file
25
MetaCore/engine/render/SimplePbrShaderLibrary.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
#include "MetaCore/engine/render/SimplePbrShaderLibrary.h"
|
||||
|
||||
namespace metacore::engine::render {
|
||||
|
||||
SimplePbrShaderLibrary::SimplePbrShaderLibrary(std::filesystem::path root)
|
||||
: root_(std::move(root)) {}
|
||||
|
||||
const std::filesystem::path& SimplePbrShaderLibrary::root() const {
|
||||
return root_;
|
||||
}
|
||||
|
||||
std::map<std::string, std::filesystem::path> SimplePbrShaderLibrary::shader_files() const {
|
||||
return {
|
||||
{"simplepbr.vert", root_ / "simplepbr.vert"},
|
||||
{"simplepbr.frag", root_ / "simplepbr.frag"},
|
||||
{"shadow.vert", root_ / "shadow.vert"},
|
||||
{"shadow.frag", root_ / "shadow.frag"},
|
||||
{"skybox.vert", root_ / "skybox.vert"},
|
||||
{"skybox.frag", root_ / "skybox.frag"},
|
||||
{"post.vert", root_ / "post.vert"},
|
||||
{"tonemap.frag", root_ / "tonemap.frag"},
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace metacore::engine::render
|
||||
19
MetaCore/engine/render/SimplePbrShaderLibrary.h
Normal file
19
MetaCore/engine/render/SimplePbrShaderLibrary.h
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <map>
|
||||
|
||||
namespace metacore::engine::render {
|
||||
|
||||
class SimplePbrShaderLibrary {
|
||||
public:
|
||||
explicit SimplePbrShaderLibrary(std::filesystem::path root);
|
||||
|
||||
[[nodiscard]] const std::filesystem::path& root() const;
|
||||
[[nodiscard]] std::map<std::string, std::filesystem::path> shader_files() const;
|
||||
|
||||
private:
|
||||
std::filesystem::path root_;
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::render
|
||||
47
MetaCore/engine/runtime_ui/RmlUiBridge.cpp
Normal file
47
MetaCore/engine/runtime_ui/RmlUiBridge.cpp
Normal file
@ -0,0 +1,47 @@
|
||||
#include "MetaCore/engine/runtime_ui/RmlUiBridge.h"
|
||||
|
||||
namespace metacore::engine::runtime_ui {
|
||||
|
||||
bool RmlUiBridge::initialize() {
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RmlUiBridge::shutdown() {
|
||||
initialized_ = false;
|
||||
active_document_ = {};
|
||||
}
|
||||
|
||||
bool RmlUiBridge::load_document(const RuntimeUiDocument& document) {
|
||||
if (!initialized_) {
|
||||
return false;
|
||||
}
|
||||
active_document_ = document;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<RuntimeUiDocument> RmlUiBridge::collect_documents(const metacore::engine::scene::Scene& scene) const {
|
||||
std::vector<RuntimeUiDocument> documents;
|
||||
for (const auto& object : scene.objects) {
|
||||
if (!object.has_ui_document) {
|
||||
continue;
|
||||
}
|
||||
|
||||
documents.push_back(RuntimeUiDocument{
|
||||
object.id,
|
||||
object.ui_document.document_path,
|
||||
object.ui_document.stylesheet_path
|
||||
});
|
||||
}
|
||||
return documents;
|
||||
}
|
||||
|
||||
bool RmlUiBridge::initialized() const {
|
||||
return initialized_;
|
||||
}
|
||||
|
||||
const RuntimeUiDocument* RmlUiBridge::active_document() const {
|
||||
return initialized_ ? &active_document_ : nullptr;
|
||||
}
|
||||
|
||||
} // namespace metacore::engine::runtime_ui
|
||||
32
MetaCore/engine/runtime_ui/RmlUiBridge.h
Normal file
32
MetaCore/engine/runtime_ui/RmlUiBridge.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCore/engine/scene/Scene.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace metacore::engine::runtime_ui {
|
||||
|
||||
struct RuntimeUiDocument {
|
||||
std::string id;
|
||||
std::filesystem::path document_path;
|
||||
std::filesystem::path stylesheet_path;
|
||||
};
|
||||
|
||||
class RmlUiBridge {
|
||||
public:
|
||||
bool initialize();
|
||||
void shutdown();
|
||||
bool load_document(const RuntimeUiDocument& document);
|
||||
[[nodiscard]] std::vector<RuntimeUiDocument> collect_documents(const metacore::engine::scene::Scene& scene) const;
|
||||
|
||||
[[nodiscard]] bool initialized() const;
|
||||
[[nodiscard]] const RuntimeUiDocument* active_document() const;
|
||||
|
||||
private:
|
||||
bool initialized_ = false;
|
||||
RuntimeUiDocument active_document_{};
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::runtime_ui
|
||||
78
MetaCore/engine/scene/Components.h
Normal file
78
MetaCore/engine/scene/Components.h
Normal file
@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
namespace metacore::engine::scene {
|
||||
|
||||
using EntityId = std::string;
|
||||
|
||||
struct TransformComponent {
|
||||
std::array<float, 3> position{0.0F, 0.0F, 0.0F};
|
||||
std::array<float, 3> rotation{0.0F, 0.0F, 0.0F};
|
||||
std::array<float, 3> scale{1.0F, 1.0F, 1.0F};
|
||||
};
|
||||
|
||||
struct MeshRendererComponent {
|
||||
std::string mesh_asset_id;
|
||||
std::string material_asset_id;
|
||||
bool visible = true;
|
||||
};
|
||||
|
||||
struct LightComponent {
|
||||
enum class Type {
|
||||
Directional,
|
||||
Point,
|
||||
Spot,
|
||||
};
|
||||
|
||||
Type type = Type::Directional;
|
||||
std::array<float, 3> color{1.0F, 1.0F, 1.0F};
|
||||
float intensity = 1.0F;
|
||||
float range = 10.0F;
|
||||
float spot_angle_degrees = 45.0F;
|
||||
};
|
||||
|
||||
struct CameraComponent {
|
||||
float fov_degrees = 60.0F;
|
||||
float near_clip = 0.1F;
|
||||
float far_clip = 1000.0F;
|
||||
bool primary = false;
|
||||
};
|
||||
|
||||
struct UIDocumentComponent {
|
||||
std::string document_path;
|
||||
std::string stylesheet_path;
|
||||
bool visible = true;
|
||||
};
|
||||
|
||||
struct CollisionComponent {
|
||||
bool enabled = false;
|
||||
std::string shape = "box";
|
||||
};
|
||||
|
||||
struct EditorMetadataComponent {
|
||||
bool editor_only = false;
|
||||
bool selected = false;
|
||||
bool locked = false;
|
||||
};
|
||||
|
||||
struct SceneObject {
|
||||
EntityId id;
|
||||
EntityId parent_id;
|
||||
std::string name;
|
||||
TransformComponent transform{};
|
||||
MeshRendererComponent mesh_renderer{};
|
||||
LightComponent light{};
|
||||
CameraComponent camera{};
|
||||
UIDocumentComponent ui_document{};
|
||||
CollisionComponent collision{};
|
||||
EditorMetadataComponent editor_metadata{};
|
||||
bool has_mesh_renderer = false;
|
||||
bool has_light = false;
|
||||
bool has_camera = false;
|
||||
bool has_ui_document = false;
|
||||
bool has_collision = false;
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::scene
|
||||
143
MetaCore/engine/scene/Project.cpp
Normal file
143
MetaCore/engine/scene/Project.cpp
Normal file
@ -0,0 +1,143 @@
|
||||
#include "MetaCore/engine/scene/Project.h"
|
||||
|
||||
#include "MetaCore/engine/assets/AssetDatabase.h"
|
||||
#include "MetaCore/engine/scene/Scene.h"
|
||||
#include "MetaCore/engine/scene/SceneSerializer.h"
|
||||
#include "MetaCore/foundation/FileSystem.h"
|
||||
#include "MetaCore/foundation/Json.h"
|
||||
|
||||
namespace metacore::engine::scene {
|
||||
|
||||
using foundation::JsonValue;
|
||||
|
||||
namespace {
|
||||
|
||||
JsonValue project_to_json(const Project& project) {
|
||||
JsonValue::object_type root;
|
||||
root["name"] = project.name;
|
||||
root["version"] = project.version;
|
||||
root["startup_scene"] = project.startup_scene;
|
||||
|
||||
JsonValue::array_type scenes;
|
||||
for (const auto& scene : project.scenes) {
|
||||
scenes.emplace_back(scene);
|
||||
}
|
||||
root["scenes"] = JsonValue(std::move(scenes));
|
||||
return JsonValue(std::move(root));
|
||||
}
|
||||
|
||||
Project project_from_json(const JsonValue& json) {
|
||||
Project project;
|
||||
project.name = json["name"].as_string();
|
||||
project.version = json["version"].as_string();
|
||||
project.startup_scene = json["startup_scene"].as_string();
|
||||
project.scenes.clear();
|
||||
for (const auto& entry : json["scenes"].as_array()) {
|
||||
project.scenes.push_back(entry.as_string());
|
||||
}
|
||||
if (project.scenes.empty()) {
|
||||
project.scenes.push_back("Scenes/Main.mcscene.json");
|
||||
}
|
||||
if (project.startup_scene.empty()) {
|
||||
project.startup_scene = project.scenes.front();
|
||||
}
|
||||
return project;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool ProjectService::create_project(const std::filesystem::path& project_root, const std::string& name) const {
|
||||
foundation::ensure_directory(project_root / "Scenes");
|
||||
foundation::ensure_directory(project_root / "Library" / "Layout");
|
||||
foundation::ensure_directory(project_root / "Library" / "Cache");
|
||||
|
||||
metacore::engine::assets::AssetService asset_service;
|
||||
asset_service.ensure_default_asset_tree(project_root);
|
||||
|
||||
Project project;
|
||||
project.name = name;
|
||||
save_project(project_root / "MetaCore.project.json", project);
|
||||
|
||||
SceneService scene_service;
|
||||
Scene scene = scene_service.create_default_scene();
|
||||
SceneSerializer serializer;
|
||||
serializer.save_scene(project_root / "Scenes" / "Main.mcscene.json", scene);
|
||||
|
||||
metacore::engine::assets::AssetDatabase asset_database;
|
||||
const auto rml_id = asset_service.ensure_asset_record(asset_database, "ui_document", "Assets/UI/main_menu.rml");
|
||||
const auto rcss_id = asset_service.ensure_asset_record(asset_database, "ui_stylesheet", "Assets/UI/main_menu.rcss");
|
||||
const auto material_id = asset_service.ensure_asset_record(asset_database, "material", "Assets/Materials/default_pbr.material.json");
|
||||
|
||||
foundation::write_text_file(
|
||||
project_root / "Assets" / "UI" / "main_menu.rml",
|
||||
"<rml>\n"
|
||||
" <head>\n"
|
||||
" <link type=\"text/rcss\" href=\"main_menu.rcss\" />\n"
|
||||
" </head>\n"
|
||||
" <body>\n"
|
||||
" <div class=\"root-panel\">\n"
|
||||
" <h1>MetaCore</h1>\n"
|
||||
" <p>Runtime UI scaffold</p>\n"
|
||||
" </div>\n"
|
||||
" </body>\n"
|
||||
"</rml>\n"
|
||||
);
|
||||
foundation::write_text_file(
|
||||
project_root / "Assets" / "UI" / "main_menu.rcss",
|
||||
"body {\n"
|
||||
" font-family: sans-serif;\n"
|
||||
" color: #f2f4f8;\n"
|
||||
"}\n"
|
||||
".root-panel {\n"
|
||||
" width: 420px;\n"
|
||||
" margin: 40px auto;\n"
|
||||
" padding: 24px;\n"
|
||||
" background: rgba(16, 18, 24, 0.85);\n"
|
||||
"}\n"
|
||||
);
|
||||
foundation::write_text_file(
|
||||
project_root / "Assets" / "Materials" / "default_pbr.material.json",
|
||||
"{\n"
|
||||
" \"name\": \"DefaultPbr\",\n"
|
||||
" \"shader_family\": \"simplepbr\",\n"
|
||||
" \"base_color\": [1.0, 1.0, 1.0, 1.0],\n"
|
||||
" \"roughness\": 1.0,\n"
|
||||
" \"metallic\": 0.0\n"
|
||||
"}\n"
|
||||
);
|
||||
|
||||
asset_service.write_meta_file(project_root, "Assets/UI/main_menu.rml", rml_id, "ui_document");
|
||||
asset_service.write_meta_file(project_root, "Assets/UI/main_menu.rcss", rcss_id, "ui_stylesheet");
|
||||
asset_service.write_meta_file(project_root, "Assets/Materials/default_pbr.material.json", material_id, "material");
|
||||
|
||||
asset_database.save(project_root / "Library" / "AssetDB.json");
|
||||
foundation::write_text_file(
|
||||
project_root / "Library" / "Layout" / "editor_layout.json",
|
||||
"{\n"
|
||||
" \"layout_name\": \"Default\",\n"
|
||||
" \"hierarchy_visible\": true,\n"
|
||||
" \"inspector_visible\": true,\n"
|
||||
" \"project_visible\": true,\n"
|
||||
" \"console_visible\": true,\n"
|
||||
" \"runtime_preview_visible\": true,\n"
|
||||
" \"toolbar_visible\": true\n"
|
||||
"}\n"
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProjectService::load_project(const std::filesystem::path& project_file, Project& out_project) const {
|
||||
const auto text = foundation::read_text_file(project_file);
|
||||
if (!text.has_value()) {
|
||||
return false;
|
||||
}
|
||||
out_project = project_from_json(JsonValue::parse(*text));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ProjectService::save_project(const std::filesystem::path& project_file, const Project& project) const {
|
||||
return foundation::write_text_file(project_file, project_to_json(project).stringify(2));
|
||||
}
|
||||
|
||||
} // namespace metacore::engine::scene
|
||||
23
MetaCore/engine/scene/Project.h
Normal file
23
MetaCore/engine/scene/Project.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace metacore::engine::scene {
|
||||
|
||||
struct Project {
|
||||
std::string name = "MetaCoreProject";
|
||||
std::string version = "0.1.0";
|
||||
std::string startup_scene = "Scenes/Main.mcscene.json";
|
||||
std::vector<std::string> scenes{"Scenes/Main.mcscene.json"};
|
||||
};
|
||||
|
||||
class ProjectService {
|
||||
public:
|
||||
bool create_project(const std::filesystem::path& project_root, const std::string& name) const;
|
||||
bool load_project(const std::filesystem::path& project_file, Project& out_project) const;
|
||||
bool save_project(const std::filesystem::path& project_file, const Project& project) const;
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::scene
|
||||
96
MetaCore/engine/scene/Scene.cpp
Normal file
96
MetaCore/engine/scene/Scene.cpp
Normal file
@ -0,0 +1,96 @@
|
||||
#include "MetaCore/engine/scene/Scene.h"
|
||||
|
||||
#include "MetaCore/foundation/Guid.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace metacore::engine::scene {
|
||||
|
||||
Scene SceneService::create_default_scene() const {
|
||||
Scene scene;
|
||||
scene.name = "Main";
|
||||
|
||||
auto& camera = create_object(scene, "Main Camera");
|
||||
camera.has_camera = true;
|
||||
camera.camera.primary = true;
|
||||
camera.transform.position = {0.0F, -10.0F, 3.0F};
|
||||
|
||||
auto& light = create_object(scene, "Directional Light");
|
||||
light.has_light = true;
|
||||
light.light.type = LightComponent::Type::Directional;
|
||||
|
||||
return scene;
|
||||
}
|
||||
|
||||
SceneObject& SceneService::create_object(Scene& scene, const std::string& name, const EntityId& parent_id) const {
|
||||
SceneObject object;
|
||||
object.id = foundation::generate_guid();
|
||||
object.parent_id = parent_id;
|
||||
object.name = name;
|
||||
scene.objects.push_back(object);
|
||||
return scene.objects.back();
|
||||
}
|
||||
|
||||
std::optional<std::reference_wrapper<SceneObject>> SceneService::find_object(Scene& scene, const EntityId& id) const {
|
||||
for (auto& object : scene.objects) {
|
||||
if (object.id == id) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::reference_wrapper<const SceneObject>> SceneService::find_object(const Scene& scene, const EntityId& id) const {
|
||||
for (const auto& object : scene.objects) {
|
||||
if (object.id == id) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool SceneService::rename_object(Scene& scene, const EntityId& id, const std::string& new_name) const {
|
||||
if (const auto object = find_object(scene, id)) {
|
||||
object->get().name = new_name;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SceneService::set_object_transform(Scene& scene, const EntityId& id, const TransformComponent& transform) const {
|
||||
if (const auto object = find_object(scene, id)) {
|
||||
object->get().transform = transform;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SceneService::destroy_object(Scene& scene, const EntityId& id) const {
|
||||
if (id.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<EntityId> to_remove{id};
|
||||
for (std::size_t index = 0; index < to_remove.size(); ++index) {
|
||||
for (const auto& object : scene.objects) {
|
||||
if (object.parent_id == to_remove[index]) {
|
||||
to_remove.push_back(object.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto old_size = scene.objects.size();
|
||||
scene.objects.erase(
|
||||
std::remove_if(
|
||||
scene.objects.begin(),
|
||||
scene.objects.end(),
|
||||
[&to_remove](const SceneObject& object) {
|
||||
return std::find(to_remove.begin(), to_remove.end(), object.id) != to_remove.end();
|
||||
}
|
||||
),
|
||||
scene.objects.end()
|
||||
);
|
||||
return scene.objects.size() != old_size;
|
||||
}
|
||||
|
||||
} // namespace metacore::engine::scene
|
||||
32
MetaCore/engine/scene/Scene.h
Normal file
32
MetaCore/engine/scene/Scene.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCore/engine/scene/Components.h"
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace metacore::engine::scene {
|
||||
|
||||
struct Scene {
|
||||
std::string name = "Main";
|
||||
std::vector<SceneObject> objects;
|
||||
};
|
||||
|
||||
class SceneService {
|
||||
public:
|
||||
Scene create_default_scene() const;
|
||||
SceneObject& create_object(Scene& scene, const std::string& name, const EntityId& parent_id = {}) const;
|
||||
std::optional<std::reference_wrapper<SceneObject>> find_object(Scene& scene, const EntityId& id) const;
|
||||
std::optional<std::reference_wrapper<const SceneObject>> find_object(const Scene& scene, const EntityId& id) const;
|
||||
bool rename_object(Scene& scene, const EntityId& id, const std::string& new_name) const;
|
||||
bool set_object_transform(
|
||||
Scene& scene,
|
||||
const EntityId& id,
|
||||
const TransformComponent& transform
|
||||
) const;
|
||||
bool destroy_object(Scene& scene, const EntityId& id) const;
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::scene
|
||||
204
MetaCore/engine/scene/SceneSerializer.cpp
Normal file
204
MetaCore/engine/scene/SceneSerializer.cpp
Normal file
@ -0,0 +1,204 @@
|
||||
#include "MetaCore/engine/scene/SceneSerializer.h"
|
||||
|
||||
#include "MetaCore/foundation/FileSystem.h"
|
||||
#include "MetaCore/foundation/Json.h"
|
||||
|
||||
namespace metacore::engine::scene {
|
||||
|
||||
using foundation::JsonValue;
|
||||
|
||||
namespace {
|
||||
|
||||
JsonValue array3(const std::array<float, 3>& values) {
|
||||
JsonValue::array_type array;
|
||||
array.emplace_back(values[0]);
|
||||
array.emplace_back(values[1]);
|
||||
array.emplace_back(values[2]);
|
||||
return JsonValue(std::move(array));
|
||||
}
|
||||
|
||||
std::array<float, 3> read_array3(const JsonValue& value, const std::array<float, 3>& fallback) {
|
||||
if (!value.is_array() || value.as_array().size() != 3) {
|
||||
return fallback;
|
||||
}
|
||||
return {
|
||||
static_cast<float>(value.as_array()[0].as_number(fallback[0])),
|
||||
static_cast<float>(value.as_array()[1].as_number(fallback[1])),
|
||||
static_cast<float>(value.as_array()[2].as_number(fallback[2]))
|
||||
};
|
||||
}
|
||||
|
||||
std::string light_type_to_string(const LightComponent::Type type) {
|
||||
switch (type) {
|
||||
case LightComponent::Type::Directional:
|
||||
return "directional";
|
||||
case LightComponent::Type::Point:
|
||||
return "point";
|
||||
case LightComponent::Type::Spot:
|
||||
return "spot";
|
||||
}
|
||||
return "directional";
|
||||
}
|
||||
|
||||
LightComponent::Type light_type_from_string(const std::string& type) {
|
||||
if (type == "point") {
|
||||
return LightComponent::Type::Point;
|
||||
}
|
||||
if (type == "spot") {
|
||||
return LightComponent::Type::Spot;
|
||||
}
|
||||
return LightComponent::Type::Directional;
|
||||
}
|
||||
|
||||
JsonValue scene_object_to_json(const SceneObject& object) {
|
||||
JsonValue::object_type root;
|
||||
root["id"] = object.id;
|
||||
root["parent_id"] = object.parent_id;
|
||||
root["name"] = object.name;
|
||||
|
||||
JsonValue::object_type transform;
|
||||
transform["position"] = array3(object.transform.position);
|
||||
transform["rotation"] = array3(object.transform.rotation);
|
||||
transform["scale"] = array3(object.transform.scale);
|
||||
root["transform"] = JsonValue(std::move(transform));
|
||||
|
||||
if (object.has_mesh_renderer) {
|
||||
JsonValue::object_type mesh;
|
||||
mesh["mesh_asset_id"] = object.mesh_renderer.mesh_asset_id;
|
||||
mesh["material_asset_id"] = object.mesh_renderer.material_asset_id;
|
||||
mesh["visible"] = object.mesh_renderer.visible;
|
||||
root["mesh_renderer"] = JsonValue(std::move(mesh));
|
||||
}
|
||||
if (object.has_light) {
|
||||
JsonValue::object_type light;
|
||||
light["type"] = light_type_to_string(object.light.type);
|
||||
light["color"] = array3(object.light.color);
|
||||
light["intensity"] = object.light.intensity;
|
||||
light["range"] = object.light.range;
|
||||
light["spot_angle_degrees"] = object.light.spot_angle_degrees;
|
||||
root["light"] = JsonValue(std::move(light));
|
||||
}
|
||||
if (object.has_camera) {
|
||||
JsonValue::object_type camera;
|
||||
camera["fov_degrees"] = object.camera.fov_degrees;
|
||||
camera["near_clip"] = object.camera.near_clip;
|
||||
camera["far_clip"] = object.camera.far_clip;
|
||||
camera["primary"] = object.camera.primary;
|
||||
root["camera"] = JsonValue(std::move(camera));
|
||||
}
|
||||
if (object.has_ui_document) {
|
||||
JsonValue::object_type ui;
|
||||
ui["document_path"] = object.ui_document.document_path;
|
||||
ui["stylesheet_path"] = object.ui_document.stylesheet_path;
|
||||
ui["visible"] = object.ui_document.visible;
|
||||
root["ui_document"] = JsonValue(std::move(ui));
|
||||
}
|
||||
if (object.has_collision) {
|
||||
JsonValue::object_type collision;
|
||||
collision["enabled"] = object.collision.enabled;
|
||||
collision["shape"] = object.collision.shape;
|
||||
root["collision"] = JsonValue(std::move(collision));
|
||||
}
|
||||
|
||||
JsonValue::object_type editor;
|
||||
editor["editor_only"] = object.editor_metadata.editor_only;
|
||||
editor["selected"] = object.editor_metadata.selected;
|
||||
editor["locked"] = object.editor_metadata.locked;
|
||||
root["editor_metadata"] = JsonValue(std::move(editor));
|
||||
|
||||
return JsonValue(std::move(root));
|
||||
}
|
||||
|
||||
SceneObject scene_object_from_json(const JsonValue& json) {
|
||||
SceneObject object;
|
||||
object.id = json["id"].as_string();
|
||||
object.parent_id = json["parent_id"].as_string();
|
||||
object.name = json["name"].as_string();
|
||||
|
||||
const auto& transform = json["transform"];
|
||||
object.transform.position = read_array3(transform["position"], object.transform.position);
|
||||
object.transform.rotation = read_array3(transform["rotation"], object.transform.rotation);
|
||||
object.transform.scale = read_array3(transform["scale"], object.transform.scale);
|
||||
|
||||
if (json.contains("mesh_renderer")) {
|
||||
const auto& mesh = json["mesh_renderer"];
|
||||
object.has_mesh_renderer = true;
|
||||
object.mesh_renderer.mesh_asset_id = mesh["mesh_asset_id"].as_string();
|
||||
object.mesh_renderer.material_asset_id = mesh["material_asset_id"].as_string();
|
||||
object.mesh_renderer.visible = mesh["visible"].as_bool(true);
|
||||
}
|
||||
if (json.contains("light")) {
|
||||
const auto& light = json["light"];
|
||||
object.has_light = true;
|
||||
object.light.type = light_type_from_string(light["type"].as_string());
|
||||
object.light.color = read_array3(light["color"], object.light.color);
|
||||
object.light.intensity = static_cast<float>(light["intensity"].as_number(object.light.intensity));
|
||||
object.light.range = static_cast<float>(light["range"].as_number(object.light.range));
|
||||
object.light.spot_angle_degrees = static_cast<float>(light["spot_angle_degrees"].as_number(object.light.spot_angle_degrees));
|
||||
}
|
||||
if (json.contains("camera")) {
|
||||
const auto& camera = json["camera"];
|
||||
object.has_camera = true;
|
||||
object.camera.fov_degrees = static_cast<float>(camera["fov_degrees"].as_number(object.camera.fov_degrees));
|
||||
object.camera.near_clip = static_cast<float>(camera["near_clip"].as_number(object.camera.near_clip));
|
||||
object.camera.far_clip = static_cast<float>(camera["far_clip"].as_number(object.camera.far_clip));
|
||||
object.camera.primary = camera["primary"].as_bool(false);
|
||||
}
|
||||
if (json.contains("ui_document")) {
|
||||
const auto& ui = json["ui_document"];
|
||||
object.has_ui_document = true;
|
||||
object.ui_document.document_path = ui["document_path"].as_string();
|
||||
object.ui_document.stylesheet_path = ui["stylesheet_path"].as_string();
|
||||
object.ui_document.visible = ui["visible"].as_bool(true);
|
||||
}
|
||||
if (json.contains("collision")) {
|
||||
const auto& collision = json["collision"];
|
||||
object.has_collision = true;
|
||||
object.collision.enabled = collision["enabled"].as_bool(false);
|
||||
object.collision.shape = collision["shape"].as_string();
|
||||
}
|
||||
|
||||
const auto& editor = json["editor_metadata"];
|
||||
object.editor_metadata.editor_only = editor["editor_only"].as_bool(false);
|
||||
object.editor_metadata.selected = editor["selected"].as_bool(false);
|
||||
object.editor_metadata.locked = editor["locked"].as_bool(false);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
JsonValue scene_to_json(const Scene& scene) {
|
||||
JsonValue::object_type root;
|
||||
root["name"] = scene.name;
|
||||
JsonValue::array_type objects;
|
||||
for (const auto& object : scene.objects) {
|
||||
objects.push_back(scene_object_to_json(object));
|
||||
}
|
||||
root["objects"] = JsonValue(std::move(objects));
|
||||
return JsonValue(std::move(root));
|
||||
}
|
||||
|
||||
Scene scene_from_json(const JsonValue& json) {
|
||||
Scene scene;
|
||||
scene.name = json["name"].as_string();
|
||||
for (const auto& object : json["objects"].as_array()) {
|
||||
scene.objects.push_back(scene_object_from_json(object));
|
||||
}
|
||||
return scene;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool SceneSerializer::save_scene(const std::filesystem::path& scene_file, const Scene& scene) const {
|
||||
return foundation::write_text_file(scene_file, scene_to_json(scene).stringify(2));
|
||||
}
|
||||
|
||||
bool SceneSerializer::load_scene(const std::filesystem::path& scene_file, Scene& out_scene) const {
|
||||
const auto text = foundation::read_text_file(scene_file);
|
||||
if (!text.has_value()) {
|
||||
return false;
|
||||
}
|
||||
out_scene = scene_from_json(JsonValue::parse(*text));
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace metacore::engine::scene
|
||||
15
MetaCore/engine/scene/SceneSerializer.h
Normal file
15
MetaCore/engine/scene/SceneSerializer.h
Normal file
@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCore/engine/scene/Scene.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace metacore::engine::scene {
|
||||
|
||||
class SceneSerializer {
|
||||
public:
|
||||
bool save_scene(const std::filesystem::path& scene_file, const Scene& scene) const;
|
||||
bool load_scene(const std::filesystem::path& scene_file, Scene& out_scene) const;
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::scene
|
||||
44
MetaCore/foundation/FileSystem.cpp
Normal file
44
MetaCore/foundation/FileSystem.cpp
Normal file
@ -0,0 +1,44 @@
|
||||
#include "MetaCore/foundation/FileSystem.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace metacore::foundation {
|
||||
|
||||
bool ensure_directory(const std::filesystem::path& path) {
|
||||
if (path.empty()) {
|
||||
return false;
|
||||
}
|
||||
std::error_code ec;
|
||||
if (std::filesystem::exists(path, ec)) {
|
||||
return true;
|
||||
}
|
||||
return std::filesystem::create_directories(path, ec);
|
||||
}
|
||||
|
||||
bool write_text_file(const std::filesystem::path& path, const std::string& contents) {
|
||||
if (!path.parent_path().empty()) {
|
||||
ensure_directory(path.parent_path());
|
||||
}
|
||||
|
||||
std::ofstream output(path, std::ios::binary);
|
||||
if (!output.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
output << contents;
|
||||
return output.good();
|
||||
}
|
||||
|
||||
std::optional<std::string> read_text_file(const std::filesystem::path& path) {
|
||||
std::ifstream input(path, std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::ostringstream stream;
|
||||
stream << input.rdbuf();
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
} // namespace metacore::foundation
|
||||
13
MetaCore/foundation/FileSystem.h
Normal file
13
MetaCore/foundation/FileSystem.h
Normal file
@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace metacore::foundation {
|
||||
|
||||
bool ensure_directory(const std::filesystem::path& path);
|
||||
bool write_text_file(const std::filesystem::path& path, const std::string& contents);
|
||||
std::optional<std::string> read_text_file(const std::filesystem::path& path);
|
||||
|
||||
} // namespace metacore::foundation
|
||||
30
MetaCore/foundation/Guid.cpp
Normal file
30
MetaCore/foundation/Guid.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
#include "MetaCore/foundation/Guid.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <random>
|
||||
#include <sstream>
|
||||
|
||||
namespace metacore::foundation {
|
||||
|
||||
std::string generate_guid() {
|
||||
static std::mt19937_64 rng(std::random_device{}());
|
||||
static std::uniform_int_distribution<unsigned long long> dist;
|
||||
static std::atomic<unsigned long long> counter{0};
|
||||
|
||||
const auto now = static_cast<unsigned long long>(
|
||||
std::chrono::high_resolution_clock::now().time_since_epoch().count()
|
||||
);
|
||||
const auto salt = dist(rng);
|
||||
const auto count = ++counter;
|
||||
|
||||
std::ostringstream stream;
|
||||
stream << std::hex << std::setfill('0')
|
||||
<< std::setw(16) << now
|
||||
<< std::setw(16) << salt
|
||||
<< std::setw(8) << count;
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
} // namespace metacore::foundation
|
||||
9
MetaCore/foundation/Guid.h
Normal file
9
MetaCore/foundation/Guid.h
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace metacore::foundation {
|
||||
|
||||
std::string generate_guid();
|
||||
|
||||
} // namespace metacore::foundation
|
||||
339
MetaCore/foundation/Json.cpp
Normal file
339
MetaCore/foundation/Json.cpp
Normal file
@ -0,0 +1,339 @@
|
||||
#include "MetaCore/foundation/Json.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace metacore::foundation {
|
||||
|
||||
namespace {
|
||||
|
||||
class Parser {
|
||||
public:
|
||||
explicit Parser(std::string_view source) : source_(source) {}
|
||||
|
||||
JsonValue parse() {
|
||||
skip_ws();
|
||||
JsonValue value = parse_value();
|
||||
skip_ws();
|
||||
return value;
|
||||
}
|
||||
|
||||
private:
|
||||
JsonValue parse_value() {
|
||||
skip_ws();
|
||||
if (match("null")) {
|
||||
return JsonValue(nullptr);
|
||||
}
|
||||
if (match("true")) {
|
||||
return JsonValue(true);
|
||||
}
|
||||
if (match("false")) {
|
||||
return JsonValue(false);
|
||||
}
|
||||
if (peek() == '"') {
|
||||
return JsonValue(parse_string());
|
||||
}
|
||||
if (peek() == '{') {
|
||||
return JsonValue(parse_object());
|
||||
}
|
||||
if (peek() == '[') {
|
||||
return JsonValue(parse_array());
|
||||
}
|
||||
return JsonValue(parse_number());
|
||||
}
|
||||
|
||||
JsonValue::object_type parse_object() {
|
||||
expect('{');
|
||||
JsonValue::object_type object;
|
||||
skip_ws();
|
||||
if (peek() == '}') {
|
||||
expect('}');
|
||||
return object;
|
||||
}
|
||||
while (true) {
|
||||
const std::string key = parse_string();
|
||||
skip_ws();
|
||||
expect(':');
|
||||
object.emplace(key, parse_value());
|
||||
skip_ws();
|
||||
if (peek() == '}') {
|
||||
expect('}');
|
||||
break;
|
||||
}
|
||||
expect(',');
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
JsonValue::array_type parse_array() {
|
||||
expect('[');
|
||||
JsonValue::array_type array;
|
||||
skip_ws();
|
||||
if (peek() == ']') {
|
||||
expect(']');
|
||||
return array;
|
||||
}
|
||||
while (true) {
|
||||
array.emplace_back(parse_value());
|
||||
skip_ws();
|
||||
if (peek() == ']') {
|
||||
expect(']');
|
||||
break;
|
||||
}
|
||||
expect(',');
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
std::string parse_string() {
|
||||
expect('"');
|
||||
std::ostringstream output;
|
||||
while (!eof()) {
|
||||
const char ch = source_[index_++];
|
||||
if (ch == '"') {
|
||||
break;
|
||||
}
|
||||
if (ch == '\\') {
|
||||
const char escaped = source_[index_++];
|
||||
switch (escaped) {
|
||||
case '"':
|
||||
case '\\':
|
||||
case '/':
|
||||
output << escaped;
|
||||
break;
|
||||
case 'b':
|
||||
output << '\b';
|
||||
break;
|
||||
case 'f':
|
||||
output << '\f';
|
||||
break;
|
||||
case 'n':
|
||||
output << '\n';
|
||||
break;
|
||||
case 'r':
|
||||
output << '\r';
|
||||
break;
|
||||
case 't':
|
||||
output << '\t';
|
||||
break;
|
||||
default:
|
||||
throw std::runtime_error("Unsupported escape sequence in JSON string");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
output << ch;
|
||||
}
|
||||
return output.str();
|
||||
}
|
||||
|
||||
double parse_number() {
|
||||
const std::size_t start = index_;
|
||||
while (!eof()) {
|
||||
const char ch = source_[index_];
|
||||
if (!(std::isdigit(static_cast<unsigned char>(ch)) || ch == '.' || ch == '-' || ch == '+' || ch == 'e' || ch == 'E')) {
|
||||
break;
|
||||
}
|
||||
++index_;
|
||||
}
|
||||
return std::stod(std::string(source_.substr(start, index_ - start)));
|
||||
}
|
||||
|
||||
void skip_ws() {
|
||||
while (!eof() && std::isspace(static_cast<unsigned char>(source_[index_]))) {
|
||||
++index_;
|
||||
}
|
||||
}
|
||||
|
||||
bool eof() const {
|
||||
return index_ >= source_.size();
|
||||
}
|
||||
|
||||
char peek() const {
|
||||
return eof() ? '\0' : source_[index_];
|
||||
}
|
||||
|
||||
void expect(const char expected) {
|
||||
skip_ws();
|
||||
if (peek() != expected) {
|
||||
throw std::runtime_error("Unexpected token while parsing JSON");
|
||||
}
|
||||
++index_;
|
||||
}
|
||||
|
||||
bool match(const std::string_view text) {
|
||||
skip_ws();
|
||||
if (source_.substr(index_, text.size()) == text) {
|
||||
index_ += text.size();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string_view source_;
|
||||
std::size_t index_ = 0;
|
||||
};
|
||||
|
||||
std::string make_indent(const int depth) {
|
||||
return std::string(static_cast<std::size_t>(depth), ' ');
|
||||
}
|
||||
|
||||
std::string escape_json(const std::string& text) {
|
||||
std::ostringstream output;
|
||||
for (const char ch : text) {
|
||||
switch (ch) {
|
||||
case '\\':
|
||||
output << "\\\\";
|
||||
break;
|
||||
case '"':
|
||||
output << "\\\"";
|
||||
break;
|
||||
case '\n':
|
||||
output << "\\n";
|
||||
break;
|
||||
case '\r':
|
||||
output << "\\r";
|
||||
break;
|
||||
case '\t':
|
||||
output << "\\t";
|
||||
break;
|
||||
default:
|
||||
output << ch;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return output.str();
|
||||
}
|
||||
|
||||
std::string stringify_value(const JsonValue& value, const int indent, const int depth) {
|
||||
if (value.is_null()) {
|
||||
return "null";
|
||||
}
|
||||
if (value.is_bool()) {
|
||||
return value.as_bool() ? "true" : "false";
|
||||
}
|
||||
if (value.is_number()) {
|
||||
std::ostringstream stream;
|
||||
stream << std::setprecision(15) << value.as_number();
|
||||
return stream.str();
|
||||
}
|
||||
if (value.is_string()) {
|
||||
return "\"" + escape_json(value.as_string()) + "\"";
|
||||
}
|
||||
if (value.is_array()) {
|
||||
const auto& array = value.as_array();
|
||||
if (array.empty()) {
|
||||
return "[]";
|
||||
}
|
||||
std::ostringstream stream;
|
||||
stream << "[\n";
|
||||
for (std::size_t index = 0; index < array.size(); ++index) {
|
||||
stream << make_indent(depth + indent)
|
||||
<< stringify_value(array[index], indent, depth + indent);
|
||||
if (index + 1 < array.size()) {
|
||||
stream << ",";
|
||||
}
|
||||
stream << '\n';
|
||||
}
|
||||
stream << make_indent(depth) << "]";
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
const auto& object = value.as_object();
|
||||
if (object.empty()) {
|
||||
return "{}";
|
||||
}
|
||||
std::ostringstream stream;
|
||||
stream << "{\n";
|
||||
std::size_t index = 0;
|
||||
for (const auto& [key, child] : object) {
|
||||
stream << make_indent(depth + indent)
|
||||
<< "\"" << escape_json(key) << "\": "
|
||||
<< stringify_value(child, indent, depth + indent);
|
||||
if (++index < object.size()) {
|
||||
stream << ",";
|
||||
}
|
||||
stream << '\n';
|
||||
}
|
||||
stream << make_indent(depth) << "}";
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
JsonValue::JsonValue() : value_(nullptr) {}
|
||||
JsonValue::JsonValue(std::nullptr_t) : value_(nullptr) {}
|
||||
JsonValue::JsonValue(const bool value) : value_(value) {}
|
||||
JsonValue::JsonValue(const double value) : value_(value) {}
|
||||
JsonValue::JsonValue(const int value) : value_(static_cast<double>(value)) {}
|
||||
JsonValue::JsonValue(std::string value) : value_(std::move(value)) {}
|
||||
JsonValue::JsonValue(const char* value) : value_(std::string(value)) {}
|
||||
JsonValue::JsonValue(array_type value) : value_(std::move(value)) {}
|
||||
JsonValue::JsonValue(object_type value) : value_(std::move(value)) {}
|
||||
|
||||
bool JsonValue::is_null() const { return std::holds_alternative<std::nullptr_t>(value_); }
|
||||
bool JsonValue::is_bool() const { return std::holds_alternative<bool>(value_); }
|
||||
bool JsonValue::is_number() const { return std::holds_alternative<double>(value_); }
|
||||
bool JsonValue::is_string() const { return std::holds_alternative<std::string>(value_); }
|
||||
bool JsonValue::is_array() const { return std::holds_alternative<array_type>(value_); }
|
||||
bool JsonValue::is_object() const { return std::holds_alternative<object_type>(value_); }
|
||||
|
||||
bool JsonValue::as_bool(const bool fallback) const {
|
||||
return is_bool() ? std::get<bool>(value_) : fallback;
|
||||
}
|
||||
|
||||
double JsonValue::as_number(const double fallback) const {
|
||||
return is_number() ? std::get<double>(value_) : fallback;
|
||||
}
|
||||
|
||||
int JsonValue::as_int(const int fallback) const {
|
||||
return static_cast<int>(as_number(static_cast<double>(fallback)));
|
||||
}
|
||||
|
||||
const std::string& JsonValue::as_string() const {
|
||||
static const std::string empty;
|
||||
return is_string() ? std::get<std::string>(value_) : empty;
|
||||
}
|
||||
|
||||
const JsonValue::array_type& JsonValue::as_array() const {
|
||||
static const array_type empty;
|
||||
return is_array() ? std::get<array_type>(value_) : empty;
|
||||
}
|
||||
|
||||
const JsonValue::object_type& JsonValue::as_object() const {
|
||||
static const object_type empty;
|
||||
return is_object() ? std::get<object_type>(value_) : empty;
|
||||
}
|
||||
|
||||
JsonValue& JsonValue::operator[](const std::string& key) {
|
||||
if (!is_object()) {
|
||||
value_ = object_type{};
|
||||
}
|
||||
return std::get<object_type>(value_)[key];
|
||||
}
|
||||
|
||||
const JsonValue& JsonValue::operator[](const std::string& key) const {
|
||||
static const JsonValue null_value;
|
||||
if (!is_object()) {
|
||||
return null_value;
|
||||
}
|
||||
const auto& object = std::get<object_type>(value_);
|
||||
const auto it = object.find(key);
|
||||
return it == object.end() ? null_value : it->second;
|
||||
}
|
||||
|
||||
bool JsonValue::contains(const std::string& key) const {
|
||||
return is_object() && std::get<object_type>(value_).contains(key);
|
||||
}
|
||||
|
||||
std::string JsonValue::stringify(const int indent) const {
|
||||
return stringify_value(*this, indent, 0);
|
||||
}
|
||||
|
||||
JsonValue JsonValue::parse(const std::string_view text) {
|
||||
Parser parser(text);
|
||||
return parser.parse();
|
||||
}
|
||||
|
||||
} // namespace metacore::foundation
|
||||
52
MetaCore/foundation/Json.h
Normal file
52
MetaCore/foundation/Json.h
Normal file
@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace metacore::foundation {
|
||||
|
||||
class JsonValue {
|
||||
public:
|
||||
using object_type = std::map<std::string, JsonValue>;
|
||||
using array_type = std::vector<JsonValue>;
|
||||
using value_type = std::variant<std::nullptr_t, bool, double, std::string, array_type, object_type>;
|
||||
|
||||
JsonValue();
|
||||
JsonValue(std::nullptr_t);
|
||||
JsonValue(bool value);
|
||||
JsonValue(double value);
|
||||
JsonValue(int value);
|
||||
JsonValue(std::string value);
|
||||
JsonValue(const char* value);
|
||||
JsonValue(array_type value);
|
||||
JsonValue(object_type value);
|
||||
|
||||
[[nodiscard]] bool is_null() const;
|
||||
[[nodiscard]] bool is_bool() const;
|
||||
[[nodiscard]] bool is_number() const;
|
||||
[[nodiscard]] bool is_string() const;
|
||||
[[nodiscard]] bool is_array() const;
|
||||
[[nodiscard]] bool is_object() const;
|
||||
|
||||
[[nodiscard]] bool as_bool(bool fallback = false) const;
|
||||
[[nodiscard]] double as_number(double fallback = 0.0) const;
|
||||
[[nodiscard]] int as_int(int fallback = 0) const;
|
||||
[[nodiscard]] const std::string& as_string() const;
|
||||
[[nodiscard]] const array_type& as_array() const;
|
||||
[[nodiscard]] const object_type& as_object() const;
|
||||
|
||||
JsonValue& operator[](const std::string& key);
|
||||
const JsonValue& operator[](const std::string& key) const;
|
||||
|
||||
[[nodiscard]] bool contains(const std::string& key) const;
|
||||
[[nodiscard]] std::string stringify(int indent = 2) const;
|
||||
[[nodiscard]] static JsonValue parse(std::string_view text);
|
||||
|
||||
private:
|
||||
value_type value_;
|
||||
};
|
||||
|
||||
} // namespace metacore::foundation
|
||||
30
MetaCore/foundation/Log.cpp
Normal file
30
MetaCore/foundation/Log.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
#include "MetaCore/foundation/Log.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace metacore::foundation {
|
||||
|
||||
namespace {
|
||||
|
||||
const char* to_prefix(const LogLevel level) {
|
||||
switch (level) {
|
||||
case LogLevel::Debug:
|
||||
return "[MetaCore][Debug] ";
|
||||
case LogLevel::Info:
|
||||
return "[MetaCore][Info] ";
|
||||
case LogLevel::Warning:
|
||||
return "[MetaCore][Warning] ";
|
||||
case LogLevel::Error:
|
||||
return "[MetaCore][Error] ";
|
||||
}
|
||||
return "[MetaCore] ";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void log(const LogLevel level, const std::string_view message) {
|
||||
std::ostream& stream = (level == LogLevel::Error) ? std::cerr : std::cout;
|
||||
stream << to_prefix(level) << message << '\n';
|
||||
}
|
||||
|
||||
} // namespace metacore::foundation
|
||||
16
MetaCore/foundation/Log.h
Normal file
16
MetaCore/foundation/Log.h
Normal file
@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace metacore::foundation {
|
||||
|
||||
enum class LogLevel {
|
||||
Debug,
|
||||
Info,
|
||||
Warning,
|
||||
Error,
|
||||
};
|
||||
|
||||
void log(LogLevel level, std::string_view message);
|
||||
|
||||
} // namespace metacore::foundation
|
||||
161
MetaCore/platform/panda/PandaPlatformContext.cpp
Normal file
161
MetaCore/platform/panda/PandaPlatformContext.cpp
Normal file
@ -0,0 +1,161 @@
|
||||
#include "MetaCore/platform/panda/PandaPlatformContext.h"
|
||||
|
||||
#include "MetaCore/foundation/Log.h"
|
||||
|
||||
#if METACORE_HAS_PANDA3D
|
||||
#include <pandaFramework.h>
|
||||
#include <windowProperties.h>
|
||||
#endif
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <windows.h>
|
||||
|
||||
namespace metacore::platform::panda {
|
||||
|
||||
#if METACORE_HAS_PANDA3D
|
||||
struct PandaPlatformContext::NativeState {
|
||||
PandaFramework framework;
|
||||
WindowFramework* window = nullptr;
|
||||
bool framework_open = false;
|
||||
};
|
||||
#else
|
||||
struct PandaPlatformContext::NativeState {};
|
||||
#endif
|
||||
|
||||
PandaPlatformContext::PandaPlatformContext() = default;
|
||||
|
||||
PandaPlatformContext::~PandaPlatformContext() = default;
|
||||
|
||||
bool PandaPlatformContext::initialize(const std::string& window_title) {
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext initialize: begin\n" << std::flush;
|
||||
foundation::log(foundation::LogLevel::Info, "MetaCore platform init: begin");
|
||||
window_title_ = window_title;
|
||||
char* enable_window_env = nullptr;
|
||||
std::size_t env_size = 0;
|
||||
_dupenv_s(&enable_window_env, &env_size, "METACORE_ENABLE_PANDA_WINDOW");
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext initialize: env read\n" << std::flush;
|
||||
window_enabled_ = enable_window_env != nullptr && std::string(enable_window_env) == "1";
|
||||
free(enable_window_env);
|
||||
window_opened_ = false;
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext initialize: env parsed\n" << std::flush;
|
||||
foundation::log(
|
||||
foundation::LogLevel::Info,
|
||||
"MetaCore platform init: window bootstrap requested=" + std::string(window_enabled_ ? "true" : "false")
|
||||
);
|
||||
#if METACORE_HAS_PANDA3D
|
||||
backend_name_ = window_enabled_ ? "panda3d-window" : "panda3d-linked";
|
||||
panda_version_ = "linked";
|
||||
panda_platform_ = "windows";
|
||||
panda_build_date_ = "runtime-probe-deferred";
|
||||
const auto panda_bin = std::filesystem::path(METACORE_PANDA3D_ROOT_PATH) / "bin";
|
||||
if (std::filesystem::exists(panda_bin)) {
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext initialize: setting DLL dir\n" << std::flush;
|
||||
foundation::log(foundation::LogLevel::Info, "MetaCore platform init: setting Panda bin path to " + panda_bin.string());
|
||||
SetDllDirectoryW(panda_bin.wstring().c_str());
|
||||
}
|
||||
native_state_ = std::make_unique<NativeState>();
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext initialize: native state ready\n" << std::flush;
|
||||
foundation::log(foundation::LogLevel::Info, "MetaCore platform init: native state allocated");
|
||||
if (window_enabled_) {
|
||||
try {
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext initialize: before open_framework\n" << std::flush;
|
||||
native_state_->framework.open_framework();
|
||||
native_state_->framework_open = true;
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext initialize: after open_framework\n" << std::flush;
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext initialize: before WindowProperties\n" << std::flush;
|
||||
WindowProperties props;
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext initialize: after WindowProperties\n" << std::flush;
|
||||
props.set_size(1280, 720);
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext initialize: after set_size\n" << std::flush;
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext initialize: before open_window\n" << std::flush;
|
||||
native_state_->window = native_state_->framework.open_window(props, 0);
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext initialize: after open_window\n" << std::flush;
|
||||
window_opened_ = native_state_->window != nullptr;
|
||||
if (!window_opened_) {
|
||||
foundation::log(foundation::LogLevel::Warning, "Panda window bootstrap requested but open_window returned null");
|
||||
}
|
||||
} catch (...) {
|
||||
window_opened_ = false;
|
||||
backend_name_ = "panda3d-linked";
|
||||
foundation::log(foundation::LogLevel::Warning, "Panda window bootstrap failed; falling back to linked-only mode");
|
||||
}
|
||||
}
|
||||
#else
|
||||
backend_name_ = "panda-stub";
|
||||
panda_version_.clear();
|
||||
panda_platform_.clear();
|
||||
panda_build_date_.clear();
|
||||
#endif
|
||||
initialized_ = true;
|
||||
foundation::log(foundation::LogLevel::Info, "MetaCore platform init: complete");
|
||||
return true;
|
||||
}
|
||||
|
||||
void PandaPlatformContext::shutdown() {
|
||||
initialized_ = false;
|
||||
window_title_.clear();
|
||||
backend_name_ = "panda-stub";
|
||||
panda_version_.clear();
|
||||
panda_platform_.clear();
|
||||
panda_build_date_.clear();
|
||||
window_enabled_ = false;
|
||||
window_opened_ = false;
|
||||
#if METACORE_HAS_PANDA3D
|
||||
if (native_state_) {
|
||||
if (native_state_->framework_open) {
|
||||
if (native_state_->window != nullptr) {
|
||||
native_state_->framework.close_window(native_state_->window);
|
||||
native_state_->window = nullptr;
|
||||
}
|
||||
native_state_->framework.close_framework();
|
||||
native_state_->framework_open = false;
|
||||
}
|
||||
native_state_.reset();
|
||||
}
|
||||
#else
|
||||
native_state_.reset();
|
||||
#endif
|
||||
}
|
||||
|
||||
void PandaPlatformContext::pump_frame() {
|
||||
#if METACORE_HAS_PANDA3D
|
||||
if (window_opened_ && native_state_ && native_state_->framework_open) {
|
||||
native_state_->framework.do_frame(nullptr);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool PandaPlatformContext::initialized() const {
|
||||
return initialized_;
|
||||
}
|
||||
|
||||
const std::string& PandaPlatformContext::window_title() const {
|
||||
return window_title_;
|
||||
}
|
||||
|
||||
std::filesystem::path PandaPlatformContext::backend_name() const {
|
||||
return backend_name_;
|
||||
}
|
||||
|
||||
const std::string& PandaPlatformContext::panda_version() const {
|
||||
return panda_version_;
|
||||
}
|
||||
|
||||
const std::string& PandaPlatformContext::panda_platform() const {
|
||||
return panda_platform_;
|
||||
}
|
||||
|
||||
const std::string& PandaPlatformContext::panda_build_date() const {
|
||||
return panda_build_date_;
|
||||
}
|
||||
|
||||
bool PandaPlatformContext::window_enabled() const {
|
||||
return window_enabled_;
|
||||
}
|
||||
|
||||
bool PandaPlatformContext::window_opened() const {
|
||||
return window_opened_;
|
||||
}
|
||||
|
||||
} // namespace metacore::platform::panda
|
||||
41
MetaCore/platform/panda/PandaPlatformContext.h
Normal file
41
MetaCore/platform/panda/PandaPlatformContext.h
Normal file
@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
|
||||
namespace metacore::platform::panda {
|
||||
|
||||
class PandaPlatformContext {
|
||||
public:
|
||||
PandaPlatformContext();
|
||||
~PandaPlatformContext();
|
||||
|
||||
bool initialize(const std::string& window_title);
|
||||
void shutdown();
|
||||
void pump_frame();
|
||||
|
||||
[[nodiscard]] bool initialized() const;
|
||||
[[nodiscard]] const std::string& window_title() const;
|
||||
[[nodiscard]] std::filesystem::path backend_name() const;
|
||||
[[nodiscard]] const std::string& panda_version() const;
|
||||
[[nodiscard]] const std::string& panda_platform() const;
|
||||
[[nodiscard]] const std::string& panda_build_date() const;
|
||||
[[nodiscard]] bool window_enabled() const;
|
||||
[[nodiscard]] bool window_opened() const;
|
||||
|
||||
private:
|
||||
struct NativeState;
|
||||
|
||||
bool initialized_ = false;
|
||||
std::string window_title_;
|
||||
std::string backend_name_ = "panda-stub";
|
||||
std::string panda_version_;
|
||||
std::string panda_platform_;
|
||||
std::string panda_build_date_;
|
||||
bool window_enabled_ = false;
|
||||
bool window_opened_ = false;
|
||||
std::unique_ptr<NativeState> native_state_;
|
||||
};
|
||||
|
||||
} // namespace metacore::platform::panda
|
||||
30
MetaCore/platform/panda/PandaRenderBackend.cpp
Normal file
30
MetaCore/platform/panda/PandaRenderBackend.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
#include "MetaCore/platform/panda/PandaRenderBackend.h"
|
||||
|
||||
namespace metacore::platform::panda {
|
||||
|
||||
bool PandaRenderBackend::initialize() {
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PandaRenderBackend::shutdown() {
|
||||
initialized_ = false;
|
||||
}
|
||||
|
||||
void PandaRenderBackend::set_frame_uniforms(const metacore::engine::render::FrameUniforms& uniforms) {
|
||||
frame_uniforms_ = uniforms;
|
||||
}
|
||||
|
||||
void PandaRenderBackend::set_light_uniforms(const metacore::engine::render::LightUniforms& uniforms) {
|
||||
light_uniforms_ = uniforms;
|
||||
}
|
||||
|
||||
const metacore::engine::render::FrameUniforms& PandaRenderBackend::frame_uniforms() const {
|
||||
return frame_uniforms_;
|
||||
}
|
||||
|
||||
const metacore::engine::render::LightUniforms& PandaRenderBackend::light_uniforms() const {
|
||||
return light_uniforms_;
|
||||
}
|
||||
|
||||
} // namespace metacore::platform::panda
|
||||
23
MetaCore/platform/panda/PandaRenderBackend.h
Normal file
23
MetaCore/platform/panda/PandaRenderBackend.h
Normal file
@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCore/engine/render/RenderTypes.h"
|
||||
|
||||
namespace metacore::platform::panda {
|
||||
|
||||
class PandaRenderBackend final : public metacore::engine::render::IMetaRenderBackend {
|
||||
public:
|
||||
bool initialize() override;
|
||||
void shutdown() override;
|
||||
void set_frame_uniforms(const metacore::engine::render::FrameUniforms& uniforms) override;
|
||||
void set_light_uniforms(const metacore::engine::render::LightUniforms& uniforms) override;
|
||||
|
||||
[[nodiscard]] const metacore::engine::render::FrameUniforms& frame_uniforms() const;
|
||||
[[nodiscard]] const metacore::engine::render::LightUniforms& light_uniforms() const;
|
||||
|
||||
private:
|
||||
bool initialized_ = false;
|
||||
metacore::engine::render::FrameUniforms frame_uniforms_{};
|
||||
metacore::engine::render::LightUniforms light_uniforms_{};
|
||||
};
|
||||
|
||||
} // namespace metacore::platform::panda
|
||||
683
MetaEditor/DearImGuiEditorShell.cpp
Normal file
683
MetaEditor/DearImGuiEditorShell.cpp
Normal file
@ -0,0 +1,683 @@
|
||||
#include "MetaEditor/DearImGuiEditorShell.h"
|
||||
|
||||
#include "MetaCore/foundation/FileSystem.h"
|
||||
|
||||
#if METACORE_HAS_IMGUI
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#endif
|
||||
|
||||
#if METACORE_HAS_IMGUIZMO
|
||||
#include <ImGuizmo.h>
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <string_view>
|
||||
#include <sstream>
|
||||
|
||||
namespace metacore::editor {
|
||||
|
||||
namespace {
|
||||
|
||||
#if METACORE_HAS_IMGUIZMO
|
||||
ImGuizmo::OPERATION to_imguizmo_operation(const std::string& operation) {
|
||||
if (operation == "Rotate") {
|
||||
return ImGuizmo::ROTATE;
|
||||
}
|
||||
if (operation == "Scale") {
|
||||
return ImGuizmo::SCALE;
|
||||
}
|
||||
return ImGuizmo::TRANSLATE;
|
||||
}
|
||||
|
||||
ImGuizmo::MODE to_imguizmo_mode(const std::string& space) {
|
||||
return space == "World" ? ImGuizmo::WORLD : ImGuizmo::LOCAL;
|
||||
}
|
||||
|
||||
std::array<float, 16> make_transform_matrix(const EditorFrameSnapshot::ViewportSnapshot& viewport) {
|
||||
const float rx = viewport.object_rotation[0] * 0.01745329252F;
|
||||
const float ry = viewport.object_rotation[1] * 0.01745329252F;
|
||||
const float rz = viewport.object_rotation[2] * 0.01745329252F;
|
||||
|
||||
const float cx = std::cos(rx);
|
||||
const float sx = std::sin(rx);
|
||||
const float cy = std::cos(ry);
|
||||
const float sy = std::sin(ry);
|
||||
const float cz = std::cos(rz);
|
||||
const float sz = std::sin(rz);
|
||||
|
||||
const float r00 = cz * cy;
|
||||
const float r01 = cz * sy * sx - sz * cx;
|
||||
const float r02 = cz * sy * cx + sz * sx;
|
||||
const float r10 = sz * cy;
|
||||
const float r11 = sz * sy * sx + cz * cx;
|
||||
const float r12 = sz * sy * cx - cz * sx;
|
||||
const float r20 = -sy;
|
||||
const float r21 = cy * sx;
|
||||
const float r22 = cy * cx;
|
||||
|
||||
return {
|
||||
r00 * viewport.object_scale[0], r10 * viewport.object_scale[0], r20 * viewport.object_scale[0], 0.0F,
|
||||
r01 * viewport.object_scale[1], r11 * viewport.object_scale[1], r21 * viewport.object_scale[1], 0.0F,
|
||||
r02 * viewport.object_scale[2], r12 * viewport.object_scale[2], r22 * viewport.object_scale[2], 0.0F,
|
||||
viewport.object_position[0], viewport.object_position[1], viewport.object_position[2], 1.0F
|
||||
};
|
||||
}
|
||||
|
||||
std::array<float, 16> make_view_matrix(const std::array<float, 3>& camera_position) {
|
||||
return {
|
||||
1.0F, 0.0F, 0.0F, 0.0F,
|
||||
0.0F, 1.0F, 0.0F, 0.0F,
|
||||
0.0F, 0.0F, 1.0F, 0.0F,
|
||||
-camera_position[0], -camera_position[1], -camera_position[2], 1.0F
|
||||
};
|
||||
}
|
||||
|
||||
std::array<float, 16> make_projection_matrix(float width, float height) {
|
||||
const float aspect = height > 0.0F ? width / height : 1.0F;
|
||||
const float fov = 60.0F * 0.01745329252F;
|
||||
const float f = 1.0F / std::tan(fov * 0.5F);
|
||||
const float near_plane = 0.1F;
|
||||
const float far_plane = 1000.0F;
|
||||
return {
|
||||
f / aspect, 0.0F, 0.0F, 0.0F,
|
||||
0.0F, f, 0.0F, 0.0F,
|
||||
0.0F, 0.0F, far_plane / (far_plane - near_plane), 1.0F,
|
||||
0.0F, 0.0F, (-near_plane * far_plane) / (far_plane - near_plane), 0.0F
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
bool DearImGuiEditorShell::initialize() {
|
||||
const char* headless_frame_env = std::getenv("METACORE_ENABLE_HEADLESS_IMGUI");
|
||||
headless_frame_enabled_ = headless_frame_env != nullptr && std::string(headless_frame_env) == "1";
|
||||
|
||||
#if METACORE_HAS_IMGUI
|
||||
if (headless_frame_enabled_) {
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
io.DisplaySize = ImVec2(1600.0F, 900.0F);
|
||||
io.DeltaTime = 1.0F / 60.0F;
|
||||
unsigned char* font_pixels = nullptr;
|
||||
int font_width = 0;
|
||||
int font_height = 0;
|
||||
io.Fonts->GetTexDataAsRGBA32(&font_pixels, &font_width, &font_height);
|
||||
#if METACORE_HAS_IMGUIZMO
|
||||
ImGuizmo::SetImGuiContext(ImGui::GetCurrentContext());
|
||||
#endif
|
||||
ImGui::StyleColorsDark();
|
||||
}
|
||||
#endif
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::shutdown() {
|
||||
#if METACORE_HAS_IMGUI
|
||||
if (headless_frame_enabled_ && ImGui::GetCurrentContext() != nullptr) {
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
#endif
|
||||
initialized_ = false;
|
||||
last_snapshot_ = {};
|
||||
last_draw_command_count_ = 0;
|
||||
last_vertex_count_ = 0;
|
||||
last_index_count_ = 0;
|
||||
last_window_count_ = 0;
|
||||
headless_frame_enabled_ = false;
|
||||
dock_layout_initialized_ = false;
|
||||
headless_transform_override_consumed_ = false;
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::consume_snapshot(const EditorFrameSnapshot& snapshot) {
|
||||
last_snapshot_ = snapshot;
|
||||
enqueue_headless_transform_edit(snapshot);
|
||||
|
||||
#if METACORE_HAS_IMGUI
|
||||
if (headless_frame_enabled_ && ImGui::GetCurrentContext() != nullptr) {
|
||||
render_headless_frame(snapshot);
|
||||
if (const ImDrawData* draw_data = ImGui::GetDrawData(); draw_data != nullptr) {
|
||||
last_draw_command_count_ = 0;
|
||||
last_vertex_count_ = draw_data->TotalVtxCount;
|
||||
last_index_count_ = draw_data->TotalIdxCount;
|
||||
last_window_count_ = draw_data->CmdListsCount;
|
||||
for (int index = 0; index < draw_data->CmdListsCount; ++index) {
|
||||
last_draw_command_count_ += draw_data->CmdLists[index]->CmdBuffer.Size;
|
||||
}
|
||||
} else {
|
||||
last_draw_command_count_ = 0;
|
||||
last_vertex_count_ = 0;
|
||||
last_index_count_ = 0;
|
||||
last_window_count_ = 0;
|
||||
}
|
||||
} else {
|
||||
last_draw_command_count_ = 0;
|
||||
last_vertex_count_ = 0;
|
||||
last_index_count_ = 0;
|
||||
last_window_count_ = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
export_debug_snapshot();
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::enqueue_headless_transform_edit(const EditorFrameSnapshot& snapshot) {
|
||||
if (!headless_frame_enabled_ || !snapshot.viewport.has_selection || headless_transform_override_consumed_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* translate_env = std::getenv("METACORE_HEADLESS_GIZMO_TRANSLATE");
|
||||
if (translate_env == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::istringstream stream(translate_env);
|
||||
float x = snapshot.viewport.object_position[0];
|
||||
float y = snapshot.viewport.object_position[1];
|
||||
float z = snapshot.viewport.object_position[2];
|
||||
if (!(stream >> x >> y >> z)) {
|
||||
return;
|
||||
}
|
||||
|
||||
PendingShellEdits::TransformEdit edit;
|
||||
edit.entity_id = snapshot.viewport.selected_object_id;
|
||||
edit.position = {x, y, z};
|
||||
edit.rotation = snapshot.viewport.object_rotation;
|
||||
edit.scale = snapshot.viewport.object_scale;
|
||||
pending_edits_.transform_edit = edit;
|
||||
headless_transform_override_consumed_ = true;
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::render_headless_frame(const EditorFrameSnapshot& snapshot) {
|
||||
#if METACORE_HAS_IMGUI
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.DisplaySize = ImVec2(1600.0F, 900.0F);
|
||||
io.DeltaTime = 1.0F / 60.0F;
|
||||
|
||||
ImGui::NewFrame();
|
||||
render_dockspace_frame(snapshot);
|
||||
ImGui::Render();
|
||||
#else
|
||||
(void)snapshot;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::render_dockspace_frame(const EditorFrameSnapshot& snapshot) {
|
||||
#if METACORE_HAS_IMGUI
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
ImGui::SetNextWindowPos(viewport->WorkPos);
|
||||
ImGui::SetNextWindowSize(viewport->WorkSize);
|
||||
ImGui::SetNextWindowViewport(viewport->ID);
|
||||
|
||||
constexpr ImGuiWindowFlags host_window_flags =
|
||||
ImGuiWindowFlags_NoDocking |
|
||||
ImGuiWindowFlags_NoTitleBar |
|
||||
ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoResize |
|
||||
ImGuiWindowFlags_NoMove |
|
||||
ImGuiWindowFlags_NoBringToFrontOnFocus |
|
||||
ImGuiWindowFlags_NoNavFocus |
|
||||
ImGuiWindowFlags_MenuBar;
|
||||
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0F);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0F);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0F, 0.0F));
|
||||
ImGui::Begin("MetaCore DockSpace", nullptr, host_window_flags);
|
||||
ImGui::PopStyleVar(3);
|
||||
|
||||
if (ImGui::BeginMenuBar()) {
|
||||
for (const auto& menu : snapshot.shell.menus) {
|
||||
if (ImGui::BeginMenu(menu.menu_name.c_str())) {
|
||||
for (const auto& action : menu.actions) {
|
||||
ImGui::MenuItem(action.label.c_str(), action.shortcut.empty() ? nullptr : action.shortcut.c_str(), false, action.enabled);
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
ImGui::EndMenuBar();
|
||||
}
|
||||
|
||||
ImGuiID dockspace_id = ImGui::GetID("MetaCoreEditorDockspace");
|
||||
ImGui::DockSpace(dockspace_id, ImVec2(0.0F, 0.0F), ImGuiDockNodeFlags_PassthruCentralNode);
|
||||
ImGui::End();
|
||||
|
||||
ensure_default_dock_layout(snapshot, dockspace_id);
|
||||
|
||||
render_toolbar(snapshot);
|
||||
for (const auto& section : snapshot.shell.sections) {
|
||||
for (const auto& panel : section.panels) {
|
||||
render_panel(panel, snapshot);
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)snapshot;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::ensure_default_dock_layout(const EditorFrameSnapshot& snapshot, unsigned int dockspace_id) {
|
||||
#if METACORE_HAS_IMGUI
|
||||
if (dock_layout_initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
ImGui::DockBuilderRemoveNode(dockspace_id);
|
||||
ImGui::DockBuilderAddNode(dockspace_id, ImGuiDockNodeFlags_DockSpace);
|
||||
ImGui::DockBuilderSetNodeSize(dockspace_id, viewport->WorkSize);
|
||||
|
||||
ImGuiID left_id = 0;
|
||||
ImGuiID right_id = 0;
|
||||
ImGuiID bottom_id = 0;
|
||||
ImGuiID center_id = dockspace_id;
|
||||
ImGuiID preview_id = 0;
|
||||
|
||||
ImGui::DockBuilderSplitNode(center_id, ImGuiDir_Left, 0.22F, &left_id, ¢er_id);
|
||||
ImGui::DockBuilderSplitNode(center_id, ImGuiDir_Right, 0.24F, &right_id, ¢er_id);
|
||||
ImGui::DockBuilderSplitNode(center_id, ImGuiDir_Down, 0.26F, &bottom_id, ¢er_id);
|
||||
ImGui::DockBuilderSplitNode(bottom_id, ImGuiDir_Down, 0.45F, &preview_id, &bottom_id);
|
||||
|
||||
for (const auto& section : snapshot.shell.sections) {
|
||||
for (const auto& panel : section.panels) {
|
||||
if (!panel.visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ImGuiID target_id = center_id;
|
||||
switch (panel.region) {
|
||||
case metacore::engine::editor_core::DockRegion::LeftSidebar:
|
||||
target_id = left_id;
|
||||
break;
|
||||
case metacore::engine::editor_core::DockRegion::RightSidebar:
|
||||
target_id = right_id;
|
||||
break;
|
||||
case metacore::engine::editor_core::DockRegion::BottomConsole:
|
||||
target_id = bottom_id;
|
||||
break;
|
||||
case metacore::engine::editor_core::DockRegion::BottomRuntimePreview:
|
||||
target_id = preview_id;
|
||||
break;
|
||||
case metacore::engine::editor_core::DockRegion::CenterViewport:
|
||||
target_id = center_id;
|
||||
break;
|
||||
case metacore::engine::editor_core::DockRegion::TopMenuBar:
|
||||
case metacore::engine::editor_core::DockRegion::TopToolbar:
|
||||
continue;
|
||||
}
|
||||
ImGui::DockBuilderDockWindow(panel.title.c_str(), target_id);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::DockBuilderFinish(dockspace_id);
|
||||
dock_layout_initialized_ = true;
|
||||
#else
|
||||
(void)snapshot;
|
||||
(void)dockspace_id;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::render_toolbar(const EditorFrameSnapshot& snapshot) {
|
||||
#if METACORE_HAS_IMGUI
|
||||
if (!snapshot.toolbar_visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGui::Begin("Toolbar", nullptr, ImGuiWindowFlags_NoCollapse)) {
|
||||
for (std::size_t group_index = 0; group_index < snapshot.shell.toolbar_groups.size(); ++group_index) {
|
||||
const auto& group = snapshot.shell.toolbar_groups[group_index];
|
||||
if (group_index > 0) {
|
||||
ImGui::Separator();
|
||||
}
|
||||
ImGui::TextUnformatted(group.name.c_str());
|
||||
for (const auto& action : group.actions) {
|
||||
ImGui::SameLine();
|
||||
ImGui::BeginDisabled(!action.enabled);
|
||||
ImGui::Button(action.label.c_str());
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
#else
|
||||
(void)snapshot;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::render_panel(const metacore::engine::editor_core::DockPanel& panel, const EditorFrameSnapshot& snapshot) {
|
||||
#if METACORE_HAS_IMGUI
|
||||
if (!panel.visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (panel.id == "hierarchy" && !snapshot.hierarchy_visible) {
|
||||
return;
|
||||
}
|
||||
if (panel.id == "project" && !snapshot.project_visible) {
|
||||
return;
|
||||
}
|
||||
if (panel.id == "inspector" && !snapshot.inspector_visible) {
|
||||
return;
|
||||
}
|
||||
if (panel.id == "console" && !snapshot.console_visible) {
|
||||
return;
|
||||
}
|
||||
if (panel.id == "runtime_preview" && !snapshot.runtime_preview_visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
const bool began = ImGui::Begin(panel.title.c_str(), nullptr, ImGuiWindowFlags_NoCollapse);
|
||||
if (began) {
|
||||
if (panel.id == "hierarchy") {
|
||||
render_hierarchy_panel(snapshot);
|
||||
} else if (panel.id == "project") {
|
||||
render_project_panel(snapshot);
|
||||
} else if (panel.id == "viewport") {
|
||||
render_viewport_panel(snapshot);
|
||||
} else if (panel.id == "inspector") {
|
||||
render_inspector_panel(snapshot);
|
||||
} else if (panel.id == "console") {
|
||||
render_console_panel(snapshot);
|
||||
} else if (panel.id == "runtime_preview") {
|
||||
render_runtime_preview_panel(snapshot);
|
||||
} else if (panel.id == "main_menu") {
|
||||
ImGui::TextUnformatted("MetaCore menu host");
|
||||
} else if (panel.id == "toolbar") {
|
||||
ImGui::TextUnformatted("MetaCore toolbar host");
|
||||
} else {
|
||||
ImGui::Text("Panel: %s", panel.id.c_str());
|
||||
}
|
||||
}
|
||||
ImGui::End();
|
||||
#else
|
||||
(void)panel;
|
||||
(void)snapshot;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::render_hierarchy_panel(const EditorFrameSnapshot& snapshot) {
|
||||
#if METACORE_HAS_IMGUI
|
||||
for (const auto& item : snapshot.hierarchy) {
|
||||
ImGui::BulletText("%s", item.name.c_str());
|
||||
}
|
||||
#else
|
||||
(void)snapshot;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::render_project_panel(const EditorFrameSnapshot& snapshot) {
|
||||
#if METACORE_HAS_IMGUI
|
||||
for (const auto& asset : snapshot.assets) {
|
||||
ImGui::BulletText("%s", asset.relative_path.c_str());
|
||||
}
|
||||
#else
|
||||
(void)snapshot;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::render_viewport_panel(const EditorFrameSnapshot& snapshot) {
|
||||
#if METACORE_HAS_IMGUI
|
||||
ImGui::Text("Scene Objects: %zu", snapshot.scene_object_count);
|
||||
ImGui::Text(
|
||||
"Camera: (%.1f, %.1f, %.1f)",
|
||||
snapshot.viewport.camera_position[0],
|
||||
snapshot.viewport.camera_position[1],
|
||||
snapshot.viewport.camera_position[2]
|
||||
);
|
||||
if (!snapshot.viewport.focus_target_id.empty()) {
|
||||
ImGui::Text("Focus Target: %s", snapshot.viewport.focus_target_id.c_str());
|
||||
}
|
||||
ImGui::Separator();
|
||||
ImGui::TextUnformatted(snapshot.status_text.c_str());
|
||||
ImGui::Spacing();
|
||||
ImGui::TextUnformatted("MetaCore Scene View");
|
||||
|
||||
if (snapshot.viewport.has_selection) {
|
||||
ImGui::Text("Selection: %s", snapshot.viewport.selected_object_name.c_str());
|
||||
ImGui::Text("Operation: %s", snapshot.viewport.gizmo_operation.c_str());
|
||||
ImGui::SameLine();
|
||||
ImGui::Text("Space: %s", snapshot.viewport.gizmo_space.c_str());
|
||||
if (snapshot.viewport.snap_enabled) {
|
||||
ImGui::Text(
|
||||
"Snap: %.2f / %.2f / %.2f",
|
||||
snapshot.viewport.snap_values[0],
|
||||
snapshot.viewport.snap_values[1],
|
||||
snapshot.viewport.snap_values[2]
|
||||
);
|
||||
} else {
|
||||
ImGui::TextUnformatted("Snap: Off");
|
||||
}
|
||||
ImGui::Text(
|
||||
"Transform P(%.2f, %.2f, %.2f) R(%.2f, %.2f, %.2f) S(%.2f, %.2f, %.2f)",
|
||||
snapshot.viewport.object_position[0],
|
||||
snapshot.viewport.object_position[1],
|
||||
snapshot.viewport.object_position[2],
|
||||
snapshot.viewport.object_rotation[0],
|
||||
snapshot.viewport.object_rotation[1],
|
||||
snapshot.viewport.object_rotation[2],
|
||||
snapshot.viewport.object_scale[0],
|
||||
snapshot.viewport.object_scale[1],
|
||||
snapshot.viewport.object_scale[2]
|
||||
);
|
||||
} else {
|
||||
ImGui::TextUnformatted("Selection: none");
|
||||
}
|
||||
|
||||
const ImVec2 viewport_min = ImGui::GetCursorScreenPos();
|
||||
const ImVec2 viewport_size = ImVec2(
|
||||
ImGui::GetContentRegionAvail().x,
|
||||
(ImGui::GetContentRegionAvail().y > 240.0F) ? ImGui::GetContentRegionAvail().y : 240.0F
|
||||
);
|
||||
ImGui::InvisibleButton("MetaCoreViewportCanvas", viewport_size);
|
||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||
draw_list->AddRectFilled(viewport_min, ImVec2(viewport_min.x + viewport_size.x, viewport_min.y + viewport_size.y), IM_COL32(26, 29, 36, 255));
|
||||
draw_list->AddRect(ImVec2(viewport_min.x, viewport_min.y), ImVec2(viewport_min.x + viewport_size.x, viewport_min.y + viewport_size.y), IM_COL32(82, 91, 107, 255));
|
||||
draw_list->AddText(ImVec2(viewport_min.x + 14.0F, viewport_min.y + 14.0F), IM_COL32(220, 226, 235, 255), "Viewport backend handoff surface");
|
||||
|
||||
#if METACORE_HAS_IMGUIZMO
|
||||
if (snapshot.viewport.has_selection) {
|
||||
auto model = make_transform_matrix(snapshot.viewport);
|
||||
const auto view = make_view_matrix(snapshot.viewport.camera_position);
|
||||
const auto projection = make_projection_matrix(viewport_size.x, viewport_size.y);
|
||||
ImGuizmo::SetDrawlist(draw_list);
|
||||
ImGuizmo::SetRect(viewport_min.x, viewport_min.y, viewport_size.x, viewport_size.y);
|
||||
ImGuizmo::Manipulate(
|
||||
view.data(),
|
||||
projection.data(),
|
||||
to_imguizmo_operation(snapshot.viewport.gizmo_operation),
|
||||
to_imguizmo_mode(snapshot.viewport.gizmo_space),
|
||||
model.data(),
|
||||
nullptr,
|
||||
snapshot.viewport.snap_enabled ? snapshot.viewport.snap_values.data() : nullptr
|
||||
);
|
||||
if (ImGuizmo::IsUsing()) {
|
||||
float translation[3]{};
|
||||
float rotation[3]{};
|
||||
float scale[3]{};
|
||||
ImGuizmo::DecomposeMatrixToComponents(model.data(), translation, rotation, scale);
|
||||
PendingShellEdits::TransformEdit edit;
|
||||
edit.entity_id = snapshot.viewport.selected_object_id;
|
||||
edit.position = {translation[0], translation[1], translation[2]};
|
||||
edit.rotation = {rotation[0], rotation[1], rotation[2]};
|
||||
edit.scale = {scale[0], scale[1], scale[2]};
|
||||
pending_edits_.transform_edit = edit;
|
||||
}
|
||||
ImGui::TextUnformatted("ImGuizmo bridge active");
|
||||
} else {
|
||||
ImGui::TextUnformatted("ImGuizmo bridge ready");
|
||||
}
|
||||
#endif
|
||||
#else
|
||||
(void)snapshot;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::render_inspector_panel(const EditorFrameSnapshot& snapshot) {
|
||||
#if METACORE_HAS_IMGUI
|
||||
ImGui::TextUnformatted(snapshot.inspector.object_name.c_str());
|
||||
ImGui::Separator();
|
||||
for (const auto& field : snapshot.inspector.fields) {
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextUnformatted(field.label.c_str());
|
||||
ImGui::SameLine(180.0F);
|
||||
ImGui::TextUnformatted(field.value.c_str());
|
||||
}
|
||||
#else
|
||||
(void)snapshot;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::render_console_panel(const EditorFrameSnapshot& snapshot) {
|
||||
#if METACORE_HAS_IMGUI
|
||||
for (const auto& message : snapshot.console) {
|
||||
ImGui::Text("[%s] %s", message.category.c_str(), message.text.c_str());
|
||||
}
|
||||
#else
|
||||
(void)snapshot;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::render_runtime_preview_panel(const EditorFrameSnapshot& snapshot) {
|
||||
#if METACORE_HAS_IMGUI
|
||||
std::string_view document_path = "None";
|
||||
for (const auto& field : snapshot.inspector.fields) {
|
||||
if (field.label == "Document") {
|
||||
document_path = field.value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
ImGui::TextUnformatted("MetaCore Runtime UI Preview");
|
||||
ImGui::Separator();
|
||||
ImGui::Text("Document: %.*s", static_cast<int>(document_path.size()), document_path.data());
|
||||
ImGui::Text("Console messages: %zu", snapshot.console.size());
|
||||
#else
|
||||
(void)snapshot;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool DearImGuiEditorShell::initialized() const {
|
||||
return initialized_;
|
||||
}
|
||||
|
||||
const EditorFrameSnapshot& DearImGuiEditorShell::last_snapshot() const {
|
||||
return last_snapshot_;
|
||||
}
|
||||
|
||||
std::string DearImGuiEditorShell::summarize_snapshot() const {
|
||||
std::ostringstream stream;
|
||||
stream << "Shell=" << shell_name()
|
||||
<< ", Menus=" << last_snapshot_.shell.menus.size()
|
||||
<< ", ToolbarGroups=" << last_snapshot_.shell.toolbar_groups.size()
|
||||
<< ", Sections=" << last_snapshot_.shell.sections.size()
|
||||
<< ", Hierarchy=" << last_snapshot_.hierarchy.size()
|
||||
<< ", Assets=" << last_snapshot_.assets.size()
|
||||
<< ", Inspector=" << last_snapshot_.inspector.object_name
|
||||
<< ", DrawCommands=" << last_draw_command_count_
|
||||
<< ", DrawVertices=" << last_vertex_count_
|
||||
<< ", DrawIndices=" << last_index_count_
|
||||
<< ", DrawLists=" << last_window_count_;
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string DearImGuiEditorShell::describe_workspace() const {
|
||||
std::ostringstream stream;
|
||||
stream << "DearImGuiEditorShell composition";
|
||||
for (const auto& menu : last_snapshot_.shell.menus) {
|
||||
stream << "\n [Menu] " << menu.menu_name << " actions=" << menu.actions.size();
|
||||
for (const auto& action : menu.actions) {
|
||||
stream << "\n - " << action.label;
|
||||
if (!action.shortcut.empty()) {
|
||||
stream << " [" << action.shortcut << "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const auto& group : last_snapshot_.shell.toolbar_groups) {
|
||||
stream << "\n [Toolbar] " << group.name << " actions=" << group.actions.size();
|
||||
for (const auto& action : group.actions) {
|
||||
stream << "\n - " << action.label;
|
||||
}
|
||||
}
|
||||
for (const auto& section : last_snapshot_.shell.sections) {
|
||||
stream << "\n [Section] panels=" << section.panels.size();
|
||||
for (const auto& panel : section.panels) {
|
||||
stream << "\n - " << panel.title << " (" << panel.id << ")";
|
||||
}
|
||||
}
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string DearImGuiEditorShell::shell_name() const {
|
||||
return "DearImGuiEditorShell";
|
||||
}
|
||||
|
||||
PendingShellEdits DearImGuiEditorShell::consume_pending_edits() {
|
||||
PendingShellEdits edits = pending_edits_;
|
||||
pending_edits_ = {};
|
||||
return edits;
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::set_debug_export_path(std::filesystem::path path) {
|
||||
debug_export_path_ = std::move(path);
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::export_debug_snapshot() const {
|
||||
if (debug_export_path_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::ostringstream stream;
|
||||
stream << summarize_snapshot() << "\n";
|
||||
stream << describe_workspace() << "\n";
|
||||
stream << "[Inspector] " << last_snapshot_.inspector.object_name << "\n";
|
||||
for (const auto& field : last_snapshot_.inspector.fields) {
|
||||
stream << " - " << field.label << ": " << field.value << "\n";
|
||||
}
|
||||
stream << "[Console]\n";
|
||||
for (const auto& message : last_snapshot_.console) {
|
||||
stream << " - [" << message.category << "] " << message.text << "\n";
|
||||
}
|
||||
export_viewport_snapshot(stream);
|
||||
stream << "[DrawData]\n";
|
||||
stream << " - CommandCount: " << last_draw_command_count_ << "\n";
|
||||
stream << " - VertexCount: " << last_vertex_count_ << "\n";
|
||||
stream << " - IndexCount: " << last_index_count_ << "\n";
|
||||
stream << " - DrawLists: " << last_window_count_ << "\n";
|
||||
foundation::write_text_file(debug_export_path_, stream.str());
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::export_viewport_snapshot(std::ostringstream& stream) const {
|
||||
stream << "[Viewport]\n";
|
||||
stream << " - Selection: "
|
||||
<< (last_snapshot_.viewport.has_selection ? last_snapshot_.viewport.selected_object_name : std::string("None")) << "\n";
|
||||
stream << " - SelectionId: " << last_snapshot_.viewport.selected_object_id << "\n";
|
||||
stream << " - Operation: " << last_snapshot_.viewport.gizmo_operation << "\n";
|
||||
stream << " - Space: " << last_snapshot_.viewport.gizmo_space << "\n";
|
||||
stream << " - SnapEnabled: " << (last_snapshot_.viewport.snap_enabled ? "true" : "false") << "\n";
|
||||
stream << " - SnapValues: "
|
||||
<< last_snapshot_.viewport.snap_values[0] << ", "
|
||||
<< last_snapshot_.viewport.snap_values[1] << ", "
|
||||
<< last_snapshot_.viewport.snap_values[2] << "\n";
|
||||
stream << " - Camera: "
|
||||
<< last_snapshot_.viewport.camera_position[0] << ", "
|
||||
<< last_snapshot_.viewport.camera_position[1] << ", "
|
||||
<< last_snapshot_.viewport.camera_position[2] << "\n";
|
||||
stream << " - Position: "
|
||||
<< last_snapshot_.viewport.object_position[0] << ", "
|
||||
<< last_snapshot_.viewport.object_position[1] << ", "
|
||||
<< last_snapshot_.viewport.object_position[2] << "\n";
|
||||
stream << " - Rotation: "
|
||||
<< last_snapshot_.viewport.object_rotation[0] << ", "
|
||||
<< last_snapshot_.viewport.object_rotation[1] << ", "
|
||||
<< last_snapshot_.viewport.object_rotation[2] << "\n";
|
||||
stream << " - Scale: "
|
||||
<< last_snapshot_.viewport.object_scale[0] << ", "
|
||||
<< last_snapshot_.viewport.object_scale[1] << ", "
|
||||
<< last_snapshot_.viewport.object_scale[2] << "\n";
|
||||
stream << " - FocusTarget: " << last_snapshot_.viewport.focus_target_id << "\n";
|
||||
}
|
||||
|
||||
} // namespace metacore::editor
|
||||
53
MetaEditor/DearImGuiEditorShell.h
Normal file
53
MetaEditor/DearImGuiEditorShell.h
Normal file
@ -0,0 +1,53 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaEditor/IEditorShell.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace metacore::editor {
|
||||
|
||||
class DearImGuiEditorShell final : public IEditorShell {
|
||||
public:
|
||||
bool initialize() override;
|
||||
void shutdown() override;
|
||||
void consume_snapshot(const EditorFrameSnapshot& snapshot) override;
|
||||
|
||||
[[nodiscard]] bool initialized() const override;
|
||||
[[nodiscard]] const EditorFrameSnapshot& last_snapshot() const override;
|
||||
[[nodiscard]] std::string summarize_snapshot() const override;
|
||||
[[nodiscard]] std::string describe_workspace() const override;
|
||||
[[nodiscard]] std::string shell_name() const override;
|
||||
[[nodiscard]] PendingShellEdits consume_pending_edits() override;
|
||||
|
||||
void set_debug_export_path(std::filesystem::path path);
|
||||
|
||||
private:
|
||||
void enqueue_headless_transform_edit(const EditorFrameSnapshot& snapshot);
|
||||
void render_headless_frame(const EditorFrameSnapshot& snapshot);
|
||||
void render_dockspace_frame(const EditorFrameSnapshot& snapshot);
|
||||
void ensure_default_dock_layout(const EditorFrameSnapshot& snapshot, unsigned int dockspace_id);
|
||||
void render_toolbar(const EditorFrameSnapshot& snapshot);
|
||||
void render_panel(const metacore::engine::editor_core::DockPanel& panel, const EditorFrameSnapshot& snapshot);
|
||||
void render_hierarchy_panel(const EditorFrameSnapshot& snapshot);
|
||||
void render_project_panel(const EditorFrameSnapshot& snapshot);
|
||||
void render_viewport_panel(const EditorFrameSnapshot& snapshot);
|
||||
void render_inspector_panel(const EditorFrameSnapshot& snapshot);
|
||||
void render_console_panel(const EditorFrameSnapshot& snapshot);
|
||||
void render_runtime_preview_panel(const EditorFrameSnapshot& snapshot);
|
||||
void export_debug_snapshot() const;
|
||||
void export_viewport_snapshot(std::ostringstream& stream) const;
|
||||
|
||||
bool initialized_ = false;
|
||||
EditorFrameSnapshot last_snapshot_{};
|
||||
std::filesystem::path debug_export_path_{};
|
||||
int last_draw_command_count_ = 0;
|
||||
int last_vertex_count_ = 0;
|
||||
int last_index_count_ = 0;
|
||||
int last_window_count_ = 0;
|
||||
bool headless_frame_enabled_ = false;
|
||||
bool dock_layout_initialized_ = false;
|
||||
bool headless_transform_override_consumed_ = false;
|
||||
PendingShellEdits pending_edits_{};
|
||||
};
|
||||
|
||||
} // namespace metacore::editor
|
||||
152
MetaEditor/EditorApplication.cpp
Normal file
152
MetaEditor/EditorApplication.cpp
Normal file
@ -0,0 +1,152 @@
|
||||
#include "MetaEditor/EditorApplication.h"
|
||||
|
||||
#include "MetaEditor/DearImGuiEditorShell.h"
|
||||
|
||||
#include "MetaCore/foundation/Log.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace metacore::editor {
|
||||
|
||||
EditorApplication::EditorApplication() = default;
|
||||
|
||||
bool EditorApplication::initialize(const std::filesystem::path& project_root) {
|
||||
project_root_ = project_root;
|
||||
editor_shell_ = create_default_editor_shell();
|
||||
const auto shell_debug_path = project_root_ / "Library" / "Layout" / (editor_shell_->shell_name() + std::string("_snapshot.txt"));
|
||||
editor_shell_->set_debug_export_path(shell_debug_path);
|
||||
if (!platform_.initialize("MetaEditor")) {
|
||||
return false;
|
||||
}
|
||||
render_backend_.initialize();
|
||||
editor_shell_->initialize();
|
||||
|
||||
if (!std::filesystem::exists(project_root_ / "MetaCore.project.json")) {
|
||||
project_service_.create_project(project_root_, "MetaCoreSample");
|
||||
}
|
||||
|
||||
project_service_.load_project(project_root_ / "MetaCore.project.json", project_);
|
||||
scene_ = scene_service_.create_default_scene();
|
||||
scene_serializer_.load_scene(project_root_ / "Scenes" / "Main.mcscene.json", scene_);
|
||||
asset_database_.load(project_root_ / "Library" / "AssetDB.json");
|
||||
layout_service_.load(project_root_ / "Library" / "Layout" / "editor_layout.json", layout_state_);
|
||||
session_.apply_layout_state(layout_state_);
|
||||
|
||||
if (!scene_.objects.empty()) {
|
||||
selection_service_.select(scene_.objects.front().id);
|
||||
}
|
||||
|
||||
session_.set_status_text("Editor initialized");
|
||||
session_.add_console_message("System", "MetaEditor initialized");
|
||||
gizmo_service_.set_operation(metacore::engine::editor_core::TransformGizmoService::Operation::Translate);
|
||||
gizmo_service_.set_space(metacore::engine::editor_core::TransformGizmoService::Space::Local);
|
||||
gizmo_service_.set_snap_enabled(false);
|
||||
|
||||
foundation::log(foundation::LogLevel::Info, "MetaEditor initialized");
|
||||
foundation::log(foundation::LogLevel::Info, "Editor shell export path: " + shell_debug_path.generic_string());
|
||||
foundation::log(foundation::LogLevel::Info, "Platform backend: " + platform_.backend_name().generic_string());
|
||||
if (!platform_.panda_version().empty()) {
|
||||
foundation::log(
|
||||
foundation::LogLevel::Info,
|
||||
"Panda3D runtime: " + platform_.panda_version() + " | platform=" + platform_.panda_platform() + " | build=" + platform_.panda_build_date()
|
||||
);
|
||||
}
|
||||
foundation::log(
|
||||
foundation::LogLevel::Info,
|
||||
"Panda window bootstrap: enabled=" + std::string(platform_.window_enabled() ? "true" : "false") +
|
||||
" opened=" + std::string(platform_.window_opened() ? "true" : "false")
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
int EditorApplication::run() {
|
||||
foundation::log(foundation::LogLevel::Info, "MetaEditor skeleton run loop started");
|
||||
|
||||
const bool has_runtime_ui_object = std::any_of(
|
||||
scene_.objects.begin(),
|
||||
scene_.objects.end(),
|
||||
[](const metacore::engine::scene::SceneObject& object) {
|
||||
return object.has_ui_document;
|
||||
}
|
||||
);
|
||||
if (!has_runtime_ui_object) {
|
||||
metacore::engine::editor_core::SceneCommands::create_empty_object(
|
||||
command_service_,
|
||||
session_,
|
||||
selection_service_,
|
||||
scene_service_,
|
||||
scene_,
|
||||
"UI Root"
|
||||
);
|
||||
|
||||
if (selection_service_.selection().has_value()) {
|
||||
if (const auto object = scene_service_.find_object(scene_, *selection_service_.selection())) {
|
||||
object->get().has_ui_document = true;
|
||||
object->get().ui_document.document_path = "Assets/UI/main_menu.rml";
|
||||
object->get().ui_document.stylesheet_path = "Assets/UI/main_menu.rcss";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
metacore::engine::editor_core::SceneCommands::focus_selected_object(
|
||||
session_,
|
||||
selection_service_,
|
||||
viewport_service_
|
||||
);
|
||||
|
||||
if (const auto selected = inspector_service_.current_selection(scene_, selection_service_)) {
|
||||
foundation::log(foundation::LogLevel::Info, "Current selection: " + selected->get().name);
|
||||
}
|
||||
auto snapshot = presenter_.build_snapshot(scene_, asset_database_, selection_service_, gizmo_service_, viewport_service_, session_);
|
||||
editor_shell_->consume_snapshot(snapshot);
|
||||
const PendingShellEdits shell_edits = editor_shell_->consume_pending_edits();
|
||||
if (shell_edits.transform_edit.has_value() &&
|
||||
shell_edits.transform_edit->entity_id == snapshot.viewport.selected_object_id) {
|
||||
const auto requested_transform = metacore::engine::scene::TransformComponent{
|
||||
shell_edits.transform_edit->position,
|
||||
shell_edits.transform_edit->rotation,
|
||||
shell_edits.transform_edit->scale
|
||||
};
|
||||
metacore::engine::editor_core::SceneCommands::apply_transform_to_selected_object(
|
||||
command_service_,
|
||||
session_,
|
||||
selection_service_,
|
||||
scene_service_,
|
||||
scene_,
|
||||
requested_transform
|
||||
);
|
||||
snapshot = presenter_.build_snapshot(scene_, asset_database_, selection_service_, gizmo_service_, viewport_service_, session_);
|
||||
editor_shell_->consume_snapshot(snapshot);
|
||||
}
|
||||
|
||||
layout_service_.save(project_root_ / "Library" / "Layout" / "editor_layout.json", session_.to_layout_state());
|
||||
scene_serializer_.save_scene(project_root_ / "Scenes" / "Main.mcscene.json", scene_);
|
||||
asset_database_.save(project_root_ / "Library" / "AssetDB.json");
|
||||
platform_.pump_frame();
|
||||
|
||||
foundation::log(foundation::LogLevel::Info, "Hierarchy items: " + std::to_string(snapshot.hierarchy.size()));
|
||||
foundation::log(foundation::LogLevel::Info, "Project assets: " + std::to_string(snapshot.assets.size()));
|
||||
foundation::log(foundation::LogLevel::Info, "Inspector target: " + snapshot.inspector.object_name);
|
||||
foundation::log(foundation::LogLevel::Info, "Scene object count: " + std::to_string(snapshot.scene_object_count));
|
||||
foundation::log(foundation::LogLevel::Info, "Session status: " + snapshot.status_text);
|
||||
foundation::log(foundation::LogLevel::Info, "Editor shell: " + editor_shell_->shell_name());
|
||||
foundation::log(foundation::LogLevel::Info, "Editor shell summary: " + editor_shell_->summarize_snapshot());
|
||||
foundation::log(foundation::LogLevel::Info, editor_shell_->describe_workspace());
|
||||
|
||||
for (const auto& message : snapshot.console) {
|
||||
foundation::log(foundation::LogLevel::Info, "[" + message.category + "] " + message.text);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void EditorApplication::shutdown() {
|
||||
if (editor_shell_) {
|
||||
editor_shell_->shutdown();
|
||||
}
|
||||
render_backend_.shutdown();
|
||||
platform_.shutdown();
|
||||
foundation::log(foundation::LogLevel::Info, "MetaEditor shutdown complete");
|
||||
}
|
||||
|
||||
} // namespace metacore::editor
|
||||
58
MetaEditor/EditorApplication.h
Normal file
58
MetaEditor/EditorApplication.h
Normal file
@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaEditor/IEditorShell.h"
|
||||
#include "MetaEditor/EditorPresenter.h"
|
||||
|
||||
#include "MetaCore/engine/assets/AssetDatabase.h"
|
||||
#include "MetaCore/engine/editor_core/CommandService.h"
|
||||
#include "MetaCore/engine/editor_core/EditorSession.h"
|
||||
#include "MetaCore/engine/editor_core/EditorLayoutService.h"
|
||||
#include "MetaCore/engine/editor_core/InspectorService.h"
|
||||
#include "MetaCore/engine/editor_core/SceneCommands.h"
|
||||
#include "MetaCore/engine/editor_core/SelectionService.h"
|
||||
#include "MetaCore/engine/editor_core/TransformGizmoService.h"
|
||||
#include "MetaCore/engine/editor_core/ViewportService.h"
|
||||
#include "MetaCore/engine/render/SimplePbrShaderLibrary.h"
|
||||
#include "MetaCore/engine/scene/Project.h"
|
||||
#include "MetaCore/engine/scene/Scene.h"
|
||||
#include "MetaCore/engine/scene/SceneSerializer.h"
|
||||
#include "MetaCore/platform/panda/PandaPlatformContext.h"
|
||||
#include "MetaCore/platform/panda/PandaRenderBackend.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
namespace metacore::editor {
|
||||
|
||||
class EditorApplication {
|
||||
public:
|
||||
EditorApplication();
|
||||
|
||||
bool initialize(const std::filesystem::path& project_root);
|
||||
int run();
|
||||
void shutdown();
|
||||
|
||||
private:
|
||||
std::filesystem::path project_root_;
|
||||
metacore::platform::panda::PandaPlatformContext platform_;
|
||||
metacore::platform::panda::PandaRenderBackend render_backend_;
|
||||
metacore::engine::scene::ProjectService project_service_;
|
||||
metacore::engine::scene::SceneService scene_service_;
|
||||
metacore::engine::scene::SceneSerializer scene_serializer_;
|
||||
metacore::engine::assets::AssetService asset_service_;
|
||||
metacore::engine::assets::AssetDatabase asset_database_;
|
||||
metacore::engine::editor_core::SelectionService selection_service_;
|
||||
metacore::engine::editor_core::TransformGizmoService gizmo_service_;
|
||||
metacore::engine::editor_core::CommandService command_service_;
|
||||
metacore::engine::editor_core::EditorLayoutService layout_service_;
|
||||
metacore::engine::editor_core::InspectorService inspector_service_;
|
||||
metacore::engine::editor_core::ViewportService viewport_service_;
|
||||
metacore::engine::editor_core::EditorSession session_{};
|
||||
metacore::engine::editor_core::EditorLayoutState layout_state_{};
|
||||
metacore::engine::scene::Project project_{};
|
||||
metacore::engine::scene::Scene scene_{};
|
||||
EditorPresenter presenter_{};
|
||||
std::unique_ptr<IEditorShell> editor_shell_{};
|
||||
};
|
||||
|
||||
} // namespace metacore::editor
|
||||
78
MetaEditor/EditorPresenter.cpp
Normal file
78
MetaEditor/EditorPresenter.cpp
Normal file
@ -0,0 +1,78 @@
|
||||
#include "MetaEditor/EditorPresenter.h"
|
||||
|
||||
namespace metacore::editor {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string to_string(const metacore::engine::editor_core::TransformGizmoService::Operation operation) {
|
||||
using Operation = metacore::engine::editor_core::TransformGizmoService::Operation;
|
||||
switch (operation) {
|
||||
case Operation::Translate:
|
||||
return "Translate";
|
||||
case Operation::Rotate:
|
||||
return "Rotate";
|
||||
case Operation::Scale:
|
||||
return "Scale";
|
||||
}
|
||||
return "Translate";
|
||||
}
|
||||
|
||||
std::string to_string(const metacore::engine::editor_core::TransformGizmoService::Space space) {
|
||||
using Space = metacore::engine::editor_core::TransformGizmoService::Space;
|
||||
switch (space) {
|
||||
case Space::Local:
|
||||
return "Local";
|
||||
case Space::World:
|
||||
return "World";
|
||||
}
|
||||
return "Local";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EditorFrameSnapshot EditorPresenter::build_snapshot(
|
||||
const metacore::engine::scene::Scene& scene,
|
||||
const metacore::engine::assets::AssetDatabase& asset_database,
|
||||
const metacore::engine::editor_core::SelectionService& selection_service,
|
||||
const metacore::engine::editor_core::TransformGizmoService& gizmo_service,
|
||||
const metacore::engine::editor_core::ViewportService& viewport_service,
|
||||
const metacore::engine::editor_core::EditorSession& session
|
||||
) const {
|
||||
EditorFrameSnapshot snapshot;
|
||||
snapshot.actions = action_catalog_.build_default_actions();
|
||||
snapshot.workspace = workspace_factory_.build_default_layout();
|
||||
snapshot.shell = shell_builder_.build(snapshot.actions, snapshot.workspace);
|
||||
snapshot.hierarchy = panel_view_models_.build_hierarchy(scene, selection_service);
|
||||
snapshot.assets = panel_view_models_.build_project_assets(asset_database);
|
||||
snapshot.inspector = panel_view_models_.build_inspector(scene, selection_service);
|
||||
snapshot.viewport.camera_position = viewport_service.camera_position();
|
||||
snapshot.viewport.gizmo_operation = to_string(gizmo_service.operation());
|
||||
snapshot.viewport.gizmo_space = to_string(gizmo_service.space());
|
||||
snapshot.viewport.snap_enabled = gizmo_service.snap_enabled();
|
||||
snapshot.viewport.snap_values = gizmo_service.snap_values();
|
||||
if (viewport_service.focus_target().has_value()) {
|
||||
snapshot.viewport.focus_target_id = *viewport_service.focus_target();
|
||||
}
|
||||
if (selection_service.selection().has_value()) {
|
||||
if (const auto object = metacore::engine::scene::SceneService{}.find_object(scene, *selection_service.selection())) {
|
||||
snapshot.viewport.has_selection = true;
|
||||
snapshot.viewport.selected_object_id = object->get().id;
|
||||
snapshot.viewport.selected_object_name = object->get().name;
|
||||
snapshot.viewport.object_position = object->get().transform.position;
|
||||
snapshot.viewport.object_rotation = object->get().transform.rotation;
|
||||
snapshot.viewport.object_scale = object->get().transform.scale;
|
||||
}
|
||||
}
|
||||
snapshot.console = panel_view_models_.console_messages(session);
|
||||
snapshot.status_text = session.status_text();
|
||||
snapshot.scene_object_count = scene.objects.size();
|
||||
snapshot.hierarchy_visible = session.panel_state().hierarchy_visible;
|
||||
snapshot.inspector_visible = session.panel_state().inspector_visible;
|
||||
snapshot.project_visible = session.panel_state().project_visible;
|
||||
snapshot.console_visible = session.panel_state().console_visible;
|
||||
snapshot.runtime_preview_visible = session.panel_state().runtime_preview_visible;
|
||||
snapshot.toolbar_visible = session.panel_state().toolbar_visible;
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
} // namespace metacore::editor
|
||||
72
MetaEditor/EditorPresenter.h
Normal file
72
MetaEditor/EditorPresenter.h
Normal file
@ -0,0 +1,72 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCore/engine/editor_core/EditorActions.h"
|
||||
#include "MetaCore/engine/assets/AssetDatabase.h"
|
||||
#include "MetaCore/engine/editor_core/EditorSession.h"
|
||||
#include "MetaCore/engine/editor_core/PanelViewModels.h"
|
||||
#include "MetaCore/engine/editor_core/SelectionService.h"
|
||||
#include "MetaCore/engine/editor_core/ShellComposition.h"
|
||||
#include "MetaCore/engine/editor_core/TransformGizmoService.h"
|
||||
#include "MetaCore/engine/editor_core/ViewportService.h"
|
||||
#include "MetaCore/engine/editor_core/WorkspaceLayout.h"
|
||||
#include "MetaCore/engine/scene/Scene.h"
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace metacore::editor {
|
||||
|
||||
struct EditorFrameSnapshot {
|
||||
struct ViewportSnapshot {
|
||||
std::string selected_object_id;
|
||||
std::string selected_object_name;
|
||||
std::array<float, 3> camera_position{0.0F, -10.0F, 3.0F};
|
||||
std::array<float, 3> object_position{0.0F, 0.0F, 0.0F};
|
||||
std::array<float, 3> object_rotation{0.0F, 0.0F, 0.0F};
|
||||
std::array<float, 3> object_scale{1.0F, 1.0F, 1.0F};
|
||||
std::array<float, 3> snap_values{1.0F, 1.0F, 1.0F};
|
||||
std::string gizmo_operation = "Translate";
|
||||
std::string gizmo_space = "Local";
|
||||
std::string focus_target_id;
|
||||
bool has_selection = false;
|
||||
bool snap_enabled = false;
|
||||
};
|
||||
|
||||
std::vector<metacore::engine::editor_core::EditorAction> actions;
|
||||
metacore::engine::editor_core::WorkspaceLayout workspace;
|
||||
metacore::engine::editor_core::ShellComposition shell;
|
||||
std::vector<metacore::engine::editor_core::HierarchyItem> hierarchy;
|
||||
std::vector<metacore::engine::editor_core::ProjectAssetItem> assets;
|
||||
metacore::engine::editor_core::InspectorViewModel inspector;
|
||||
ViewportSnapshot viewport;
|
||||
std::vector<metacore::engine::editor_core::ConsoleMessage> console;
|
||||
std::string status_text;
|
||||
std::size_t scene_object_count = 0;
|
||||
bool hierarchy_visible = true;
|
||||
bool inspector_visible = true;
|
||||
bool project_visible = true;
|
||||
bool console_visible = true;
|
||||
bool runtime_preview_visible = true;
|
||||
bool toolbar_visible = true;
|
||||
};
|
||||
|
||||
class EditorPresenter {
|
||||
public:
|
||||
EditorFrameSnapshot build_snapshot(
|
||||
const metacore::engine::scene::Scene& scene,
|
||||
const metacore::engine::assets::AssetDatabase& asset_database,
|
||||
const metacore::engine::editor_core::SelectionService& selection_service,
|
||||
const metacore::engine::editor_core::TransformGizmoService& gizmo_service,
|
||||
const metacore::engine::editor_core::ViewportService& viewport_service,
|
||||
const metacore::engine::editor_core::EditorSession& session
|
||||
) const;
|
||||
|
||||
private:
|
||||
metacore::engine::editor_core::PanelViewModels panel_view_models_{};
|
||||
metacore::engine::editor_core::EditorActionCatalog action_catalog_{};
|
||||
metacore::engine::editor_core::ShellCompositionBuilder shell_builder_{};
|
||||
metacore::engine::editor_core::WorkspaceLayoutFactory workspace_factory_{};
|
||||
};
|
||||
|
||||
} // namespace metacore::editor
|
||||
43
MetaEditor/IEditorShell.h
Normal file
43
MetaEditor/IEditorShell.h
Normal file
@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaEditor/EditorPresenter.h"
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace metacore::editor {
|
||||
|
||||
struct PendingShellEdits {
|
||||
struct TransformEdit {
|
||||
std::string entity_id;
|
||||
std::array<float, 3> position{0.0F, 0.0F, 0.0F};
|
||||
std::array<float, 3> rotation{0.0F, 0.0F, 0.0F};
|
||||
std::array<float, 3> scale{1.0F, 1.0F, 1.0F};
|
||||
};
|
||||
|
||||
std::optional<TransformEdit> transform_edit;
|
||||
};
|
||||
|
||||
class IEditorShell {
|
||||
public:
|
||||
virtual ~IEditorShell() = default;
|
||||
|
||||
virtual bool initialize() = 0;
|
||||
virtual void shutdown() = 0;
|
||||
virtual void consume_snapshot(const EditorFrameSnapshot& snapshot) = 0;
|
||||
|
||||
[[nodiscard]] virtual bool initialized() const = 0;
|
||||
[[nodiscard]] virtual const EditorFrameSnapshot& last_snapshot() const = 0;
|
||||
[[nodiscard]] virtual std::string summarize_snapshot() const = 0;
|
||||
[[nodiscard]] virtual std::string describe_workspace() const = 0;
|
||||
[[nodiscard]] virtual std::string shell_name() const = 0;
|
||||
[[nodiscard]] virtual PendingShellEdits consume_pending_edits() = 0;
|
||||
virtual void set_debug_export_path(std::filesystem::path path) = 0;
|
||||
};
|
||||
|
||||
std::unique_ptr<IEditorShell> create_default_editor_shell();
|
||||
|
||||
} // namespace metacore::editor
|
||||
119
MetaEditor/NullEditorShell.cpp
Normal file
119
MetaEditor/NullEditorShell.cpp
Normal file
@ -0,0 +1,119 @@
|
||||
#include "MetaEditor/NullEditorShell.h"
|
||||
|
||||
#include "MetaEditor/DearImGuiEditorShell.h"
|
||||
#include "MetaCore/foundation/FileSystem.h"
|
||||
#include "MetaCore/engine/editor_core/WorkspaceLayout.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <sstream>
|
||||
|
||||
namespace metacore::editor {
|
||||
|
||||
namespace {
|
||||
|
||||
const char* region_name(const metacore::engine::editor_core::DockRegion region) {
|
||||
using metacore::engine::editor_core::DockRegion;
|
||||
switch (region) {
|
||||
case DockRegion::TopMenuBar:
|
||||
return "TopMenuBar";
|
||||
case DockRegion::TopToolbar:
|
||||
return "TopToolbar";
|
||||
case DockRegion::LeftSidebar:
|
||||
return "LeftSidebar";
|
||||
case DockRegion::CenterViewport:
|
||||
return "CenterViewport";
|
||||
case DockRegion::RightSidebar:
|
||||
return "RightSidebar";
|
||||
case DockRegion::BottomConsole:
|
||||
return "BottomConsole";
|
||||
case DockRegion::BottomRuntimePreview:
|
||||
return "BottomRuntimePreview";
|
||||
}
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool NullEditorShell::initialize() {
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void NullEditorShell::shutdown() {
|
||||
initialized_ = false;
|
||||
last_snapshot_ = {};
|
||||
}
|
||||
|
||||
void NullEditorShell::consume_snapshot(const EditorFrameSnapshot& snapshot) {
|
||||
last_snapshot_ = snapshot;
|
||||
if (!debug_export_path_.empty()) {
|
||||
foundation::write_text_file(debug_export_path_, summarize_snapshot() + "\n" + describe_workspace() + "\n");
|
||||
}
|
||||
}
|
||||
|
||||
bool NullEditorShell::initialized() const {
|
||||
return initialized_;
|
||||
}
|
||||
|
||||
const EditorFrameSnapshot& NullEditorShell::last_snapshot() const {
|
||||
return last_snapshot_;
|
||||
}
|
||||
|
||||
std::string NullEditorShell::summarize_snapshot() const {
|
||||
std::ostringstream stream;
|
||||
stream << "Shell=" << shell_name()
|
||||
<< ", Actions=" << last_snapshot_.actions.size()
|
||||
<< ", Panels=" << last_snapshot_.workspace.panels.size()
|
||||
<< ", Hierarchy=" << last_snapshot_.hierarchy.size()
|
||||
<< ", Assets=" << last_snapshot_.assets.size()
|
||||
<< ", Inspector=" << last_snapshot_.inspector.object_name
|
||||
<< ", Console=" << last_snapshot_.console.size()
|
||||
<< ", Status=" << last_snapshot_.status_text
|
||||
<< ", Toolbar=" << (last_snapshot_.toolbar_visible ? "on" : "off")
|
||||
<< ", RuntimePreview=" << (last_snapshot_.runtime_preview_visible ? "on" : "off");
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string NullEditorShell::describe_workspace() const {
|
||||
std::ostringstream stream;
|
||||
stream << "Workspace[" << last_snapshot_.workspace.name << "]";
|
||||
for (const auto& panel : last_snapshot_.workspace.panels) {
|
||||
stream << "\n - " << panel.title
|
||||
<< " (" << panel.id << ")"
|
||||
<< " @ " << region_name(panel.region)
|
||||
<< " visible=" << (panel.visible ? "true" : "false");
|
||||
}
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
std::string NullEditorShell::shell_name() const {
|
||||
return "NullEditorShell";
|
||||
}
|
||||
|
||||
PendingShellEdits NullEditorShell::consume_pending_edits() {
|
||||
return {};
|
||||
}
|
||||
|
||||
void NullEditorShell::set_debug_export_path(std::filesystem::path path) {
|
||||
debug_export_path_ = std::move(path);
|
||||
}
|
||||
|
||||
std::unique_ptr<IEditorShell> create_default_editor_shell() {
|
||||
if (const char* shell_env = std::getenv("METACORE_EDITOR_SHELL")) {
|
||||
const std::string value(shell_env);
|
||||
if (value == "dearimgui") {
|
||||
return std::make_unique<DearImGuiEditorShell>();
|
||||
}
|
||||
if (value == "null") {
|
||||
return std::make_unique<NullEditorShell>();
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(METACORE_HAS_IMGUI) && METACORE_HAS_IMGUI
|
||||
return std::make_unique<DearImGuiEditorShell>();
|
||||
#else
|
||||
return std::make_unique<NullEditorShell>();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace metacore::editor
|
||||
27
MetaEditor/NullEditorShell.h
Normal file
27
MetaEditor/NullEditorShell.h
Normal file
@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaEditor/IEditorShell.h"
|
||||
|
||||
namespace metacore::editor {
|
||||
|
||||
class NullEditorShell final : public IEditorShell {
|
||||
public:
|
||||
bool initialize() override;
|
||||
void shutdown() override;
|
||||
void consume_snapshot(const EditorFrameSnapshot& snapshot) override;
|
||||
|
||||
[[nodiscard]] bool initialized() const override;
|
||||
[[nodiscard]] const EditorFrameSnapshot& last_snapshot() const override;
|
||||
[[nodiscard]] std::string summarize_snapshot() const override;
|
||||
[[nodiscard]] std::string describe_workspace() const override;
|
||||
[[nodiscard]] std::string shell_name() const override;
|
||||
[[nodiscard]] PendingShellEdits consume_pending_edits() override;
|
||||
void set_debug_export_path(std::filesystem::path path) override;
|
||||
|
||||
private:
|
||||
bool initialized_ = false;
|
||||
EditorFrameSnapshot last_snapshot_{};
|
||||
std::filesystem::path debug_export_path_{};
|
||||
};
|
||||
|
||||
} // namespace metacore::editor
|
||||
31
MetaEditor/main.cpp
Normal file
31
MetaEditor/main.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
#include "MetaEditor/EditorApplication.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
std::cerr << "[MetaCore][Trace] MetaEditor main: begin\n";
|
||||
std::filesystem::path project_root = "D:/MetaCore/SandboxProject";
|
||||
if (argc > 1) {
|
||||
project_root = argv[1];
|
||||
}
|
||||
|
||||
std::cerr << "[MetaCore][Trace] MetaEditor main: before app construction\n";
|
||||
metacore::editor::EditorApplication app;
|
||||
std::cerr << "[MetaCore][Trace] MetaEditor main: before initialize\n";
|
||||
if (!app.initialize(project_root)) {
|
||||
std::cerr << "[MetaCore][Trace] MetaEditor main: initialize failed\n";
|
||||
return 1;
|
||||
}
|
||||
std::cerr << "[MetaCore][Trace] MetaEditor main: before run\n";
|
||||
const int code = app.run();
|
||||
std::cerr << "[MetaCore][Trace] MetaEditor main: after run\n";
|
||||
const char* skip_shutdown = std::getenv("METACORE_SKIP_APP_SHUTDOWN");
|
||||
if (!(skip_shutdown != nullptr && std::string(skip_shutdown) == "1")) {
|
||||
std::cerr << "[MetaCore][Trace] MetaEditor main: before shutdown\n";
|
||||
app.shutdown();
|
||||
std::cerr << "[MetaCore][Trace] MetaEditor main: after shutdown\n";
|
||||
}
|
||||
return code;
|
||||
}
|
||||
42
MetaPlayer/PlayerApplication.cpp
Normal file
42
MetaPlayer/PlayerApplication.cpp
Normal file
@ -0,0 +1,42 @@
|
||||
#include "MetaPlayer/PlayerApplication.h"
|
||||
|
||||
#include "MetaCore/foundation/Log.h"
|
||||
|
||||
namespace metacore::player {
|
||||
|
||||
bool PlayerApplication::initialize(const std::filesystem::path& scene_file) {
|
||||
scene_file_ = scene_file;
|
||||
platform_.initialize("MetaPlayer");
|
||||
render_backend_.initialize();
|
||||
runtime_ui_.initialize();
|
||||
scene_serializer_.load_scene(scene_file_, scene_);
|
||||
foundation::log(foundation::LogLevel::Info, "MetaPlayer initialized");
|
||||
foundation::log(
|
||||
foundation::LogLevel::Info,
|
||||
"Panda window bootstrap: enabled=" + std::string(platform_.window_enabled() ? "true" : "false") +
|
||||
" opened=" + std::string(platform_.window_opened() ? "true" : "false")
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
int PlayerApplication::run() {
|
||||
const auto documents = runtime_ui_.collect_documents(scene_);
|
||||
preview_session_.set_documents(documents);
|
||||
if (!documents.empty()) {
|
||||
runtime_ui_.load_document(documents.front());
|
||||
}
|
||||
foundation::log(foundation::LogLevel::Info, "Runtime UI documents: " + std::to_string(documents.size()));
|
||||
foundation::log(foundation::LogLevel::Info, preview_session_.describe());
|
||||
foundation::log(foundation::LogLevel::Info, "MetaPlayer skeleton run loop started");
|
||||
platform_.pump_frame();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void PlayerApplication::shutdown() {
|
||||
runtime_ui_.shutdown();
|
||||
render_backend_.shutdown();
|
||||
platform_.shutdown();
|
||||
foundation::log(foundation::LogLevel::Info, "MetaPlayer shutdown complete");
|
||||
}
|
||||
|
||||
} // namespace metacore::player
|
||||
32
MetaPlayer/PlayerApplication.h
Normal file
32
MetaPlayer/PlayerApplication.h
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaPlayer/RuntimeUiPreviewSession.h"
|
||||
|
||||
#include "MetaCore/engine/render/SimplePbrShaderLibrary.h"
|
||||
#include "MetaCore/engine/runtime_ui/RmlUiBridge.h"
|
||||
#include "MetaCore/engine/scene/Scene.h"
|
||||
#include "MetaCore/engine/scene/SceneSerializer.h"
|
||||
#include "MetaCore/platform/panda/PandaPlatformContext.h"
|
||||
#include "MetaCore/platform/panda/PandaRenderBackend.h"
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
namespace metacore::player {
|
||||
|
||||
class PlayerApplication {
|
||||
public:
|
||||
bool initialize(const std::filesystem::path& scene_file);
|
||||
int run();
|
||||
void shutdown();
|
||||
|
||||
private:
|
||||
std::filesystem::path scene_file_;
|
||||
metacore::platform::panda::PandaPlatformContext platform_;
|
||||
metacore::platform::panda::PandaRenderBackend render_backend_;
|
||||
metacore::engine::runtime_ui::RmlUiBridge runtime_ui_;
|
||||
metacore::engine::scene::SceneSerializer scene_serializer_;
|
||||
metacore::engine::scene::Scene scene_{};
|
||||
RuntimeUiPreviewSession preview_session_{};
|
||||
};
|
||||
|
||||
} // namespace metacore::player
|
||||
24
MetaPlayer/RuntimeUiPreviewSession.cpp
Normal file
24
MetaPlayer/RuntimeUiPreviewSession.cpp
Normal file
@ -0,0 +1,24 @@
|
||||
#include "MetaPlayer/RuntimeUiPreviewSession.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace metacore::player {
|
||||
|
||||
void RuntimeUiPreviewSession::set_documents(std::vector<metacore::engine::runtime_ui::RuntimeUiDocument> documents) {
|
||||
documents_ = std::move(documents);
|
||||
}
|
||||
|
||||
const std::vector<metacore::engine::runtime_ui::RuntimeUiDocument>& RuntimeUiPreviewSession::documents() const {
|
||||
return documents_;
|
||||
}
|
||||
|
||||
std::string RuntimeUiPreviewSession::describe() const {
|
||||
std::ostringstream stream;
|
||||
stream << "RuntimeUiPreviewSession documents=" << documents_.size();
|
||||
if (!documents_.empty()) {
|
||||
stream << " first=" << documents_.front().document_path.generic_string();
|
||||
}
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
} // namespace metacore::player
|
||||
20
MetaPlayer/RuntimeUiPreviewSession.h
Normal file
20
MetaPlayer/RuntimeUiPreviewSession.h
Normal file
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCore/engine/runtime_ui/RmlUiBridge.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace metacore::player {
|
||||
|
||||
class RuntimeUiPreviewSession {
|
||||
public:
|
||||
void set_documents(std::vector<metacore::engine::runtime_ui::RuntimeUiDocument> documents);
|
||||
[[nodiscard]] const std::vector<metacore::engine::runtime_ui::RuntimeUiDocument>& documents() const;
|
||||
[[nodiscard]] std::string describe() const;
|
||||
|
||||
private:
|
||||
std::vector<metacore::engine::runtime_ui::RuntimeUiDocument> documents_;
|
||||
};
|
||||
|
||||
} // namespace metacore::player
|
||||
31
MetaPlayer/main.cpp
Normal file
31
MetaPlayer/main.cpp
Normal file
@ -0,0 +1,31 @@
|
||||
#include "MetaPlayer/PlayerApplication.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
std::cerr << "[MetaCore][Trace] MetaPlayer main: begin\n";
|
||||
std::filesystem::path scene_file = "D:/MetaCore/SandboxProject/Scenes/Main.mcscene.json";
|
||||
if (argc > 1) {
|
||||
scene_file = argv[1];
|
||||
}
|
||||
|
||||
std::cerr << "[MetaCore][Trace] MetaPlayer main: before app construction\n";
|
||||
metacore::player::PlayerApplication app;
|
||||
std::cerr << "[MetaCore][Trace] MetaPlayer main: before initialize\n";
|
||||
if (!app.initialize(scene_file)) {
|
||||
std::cerr << "[MetaCore][Trace] MetaPlayer main: initialize failed\n";
|
||||
return 1;
|
||||
}
|
||||
std::cerr << "[MetaCore][Trace] MetaPlayer main: before run\n";
|
||||
const int code = app.run();
|
||||
std::cerr << "[MetaCore][Trace] MetaPlayer main: after run\n";
|
||||
const char* skip_shutdown = std::getenv("METACORE_SKIP_APP_SHUTDOWN");
|
||||
if (!(skip_shutdown != nullptr && std::string(skip_shutdown) == "1")) {
|
||||
std::cerr << "[MetaCore][Trace] MetaPlayer main: before shutdown\n";
|
||||
app.shutdown();
|
||||
std::cerr << "[MetaCore][Trace] MetaPlayer main: after shutdown\n";
|
||||
}
|
||||
return code;
|
||||
}
|
||||
99
PLAN.md
Normal file
99
PLAN.md
Normal file
@ -0,0 +1,99 @@
|
||||
# MetaCore 最终开工计划
|
||||
|
||||
## 摘要
|
||||
- 在 `D:\MetaCore` 新建全新纯 C++ 工程,完全不继承 EG 项目文件格式,也不做 EG 自动导入。
|
||||
- 对外目标:编辑器核心功能、布局逻辑、操作路径对齐现有 EG;对内目标:彻底模块化、职责清晰、修正旧项目中的耦合和不完整逻辑。
|
||||
- UI 职责固定为:`ImGui + ImGuizmo` 仅用于编辑器与开发工具,`RmlUi` 仅用于运行时/玩家界面。
|
||||
- 渲染固定为:Panda3D 底层后端,`simplepbr` 只复用 shader 源码与参数语义,MetaCore 用 C++ 重写 shader 调用、材质系统和 pass。
|
||||
|
||||
## 技术基线
|
||||
- 语言与构建:`C++20`、`CMakePresets`、`VS2022`、`vcpkg`
|
||||
- 底层后端:`Panda3D 1.10.16` 作为 v1 基线,后续升级 `1.11`
|
||||
- 编辑器 UI:Dear ImGui docking + ImGuizmo
|
||||
- 运行时 UI:RmlUi
|
||||
- 渲染:MetaCore 自研 `MetaRender`,shader 来源参考 `simplepbr`
|
||||
- 数据格式:JSON + `.meta` 资源描述
|
||||
- 首版不做:脚本系统、VR、Prefab 工作流、完整打包链路、URP 级渲染
|
||||
|
||||
## 工程结构
|
||||
- `MetaCore/`
|
||||
- `MetaCore/foundation`:日志、配置、GUID、文件系统、事件、任务、序列化辅助
|
||||
- `MetaCore/platform/panda`:窗口、输入、模型纹理加载、Panda 节点适配、shader 绑定、渲染上下文
|
||||
- `MetaCore/engine/assets`:资产索引、`.meta`、依赖、导入缓存
|
||||
- `MetaCore/engine/scene`:`Scene`、层级、实体、组件、场景保存加载
|
||||
- `MetaCore/engine/render`:`ShaderProgram`、`ShaderVariantKey`、`MaterialDescriptor`、`MaterialInstance`、各类 render pass
|
||||
- `MetaCore/engine/runtime_ui`:RmlUi 文档、输入、字体、纹理、预览桥接
|
||||
- `MetaCore/engine/editor_core`:选择、Gizmo 状态、命令栈、撤销重做、Inspector 数据模型、脏标记
|
||||
- `MetaEditor/`:ImGui 编辑器外壳、菜单、Docking、各面板
|
||||
- `MetaPlayer/`:运行时播放器
|
||||
- `third_party/simplepbr-shaders/`:镜像 `simplepbr.vert/.frag`、`shadow.vert/.frag`、`skybox.vert/.frag`、`post.vert`、`tonemap.frag` 及许可说明
|
||||
|
||||
## 核心职责边界
|
||||
- ImGui:菜单栏、Hierarchy、Inspector、Project/Assets、Console、Scene View 工具栏、调试工具、材质编辑器、RmlUi 资源编辑器/预览器
|
||||
- ImGuizmo:视口中的平移/旋转/缩放操控;不负责命令记录和业务状态
|
||||
- RmlUi:运行时主菜单、HUD、背包、任务、设置、弹窗、加载界面等玩家 UI
|
||||
- MetaCore 服务层:负责“改什么、怎么存、怎么撤销重做”,不让 UI 直接操作底层数据
|
||||
- Panda3D:仅作为内部后端,不出现在公共 API、项目 schema、用户术语里
|
||||
|
||||
## 域模型与接口
|
||||
- 核心对象:`Project`、`Scene`、`EntityId`、`SceneObjectHandle`、`AssetId`
|
||||
- 首版组件:`TransformComponent`、`MeshRendererComponent`、`LightComponent`、`CameraComponent`、`UIDocumentComponent`、`CollisionComponent`、`EditorMetadataComponent`
|
||||
- 核心服务:`ProjectService`、`SceneService`、`AssetService`、`EditorLayoutService`、`SelectionService`、`TransformGizmoService`、`CommandService`、`ViewportService`、`InspectorService`
|
||||
- 渲染接口:`IMetaRenderBackend`、`ShaderProgram`、`MaterialInstance`、`FrameUniforms`、`LightUniforms`、`TonemapSettings`
|
||||
- 首版预留不交付:`PrefabAsset`/模板化接口、未来脚本挂载接口
|
||||
|
||||
## 编辑器交付范围
|
||||
- 布局与职责对齐现有 EG:顶部菜单、左侧层级树、资源面板、中心视口、右侧属性、底部控制台、工具栏、默认 Docking
|
||||
- 交互对齐现有 EG:创建/删除/重命名、选择高亮、F 聚焦、W/E/R、局部/世界切换、吸附、撤销重做、项目与场景保存加载
|
||||
- 实现策略:外部体验尽量一致,内部完全重构;不迁移 EG 旧 Gizmo,不复用 EG 混杂管理器结构
|
||||
- 允许修正旧项目中的明显错误、状态不同步、生命周期缺失和脏逻辑
|
||||
|
||||
## 渲染实现
|
||||
- 使用 simplepbr shader 源码,但不使用其 Python `Pipeline`
|
||||
- C++ 层负责 shader define 组合、材质参数、贴图槽、灯光数组、阴影输入、天空盒、后处理、色调映射
|
||||
- v1 pass 固定为:`ShadowPass`、`MainPbrPass`、`SkyboxPass`、`TonemapPass`
|
||||
- v1 能力固定为:基础 PBR、法线贴图、发光、环境光、点光/方向光/聚光、简单阴影、天空盒、色调映射
|
||||
- IBL 与 URP 风格渲染只预留接口,不在 v1 做完整实现
|
||||
|
||||
## 数据与项目格式
|
||||
- 项目文件:`MetaCore.project.json`
|
||||
- 场景文件:`Scenes/*.mcscene.json`
|
||||
- 资源目录:`Assets/Models`、`Assets/Textures`、`Assets/Audio`、`Assets/UI`、`Assets/Materials`
|
||||
- 缓存目录:`Library/AssetDB.json`、缩略图缓存、导入缓存、布局缓存
|
||||
- 每个资源配套 `.meta`
|
||||
- 场景数据、编辑器布局、运行时缓存严格分离,禁止把运行时状态写回场景对象
|
||||
|
||||
## 实施顺序
|
||||
1. 建立仓库、CMake、第三方依赖、基础目录和空应用启动骨架
|
||||
2. 完成 `foundation` 与 `platform/panda`,确保窗口、输入、相机、基础场景显示正常
|
||||
3. 完成 `engine/scene` 与 `engine/assets`,跑通新建项目、新建场景、保存重开
|
||||
4. 接入 ImGui docking,搭建编辑器外壳与固定面板布局
|
||||
5. 接入 ImGuizmo,完成选择、变换、命令栈、撤销重做
|
||||
6. 完成 `MetaRender` 与 simplepbr shader C++ 调用链
|
||||
7. 完成 Inspector、资源面板、Hierarchy 与视口联动
|
||||
8. 接入 RmlUi 运行时显示与编辑器内预览
|
||||
9. 做稳定性补齐、布局恢复、错误处理、测试与文档
|
||||
|
||||
## 测试与验收
|
||||
- 单元测试:项目/场景 schema、组件增删改、命令栈、资产索引、shader 参数绑定
|
||||
- 集成测试:新建项目、保存重开、Hierarchy 与 Inspector 联动、Gizmo 变换持久化、布局恢复、RmlUi 文档加载
|
||||
- 验收标准:
|
||||
- 能用 `MetaEditor` 完成现有 EG 的核心编辑流程
|
||||
- 能稳定创建全新 MetaCore 项目并反复保存/重开
|
||||
- `MetaPlayer` 能正确运行场景并显示 RmlUi
|
||||
- 代码中不存在新的 God Object 和跨层硬耦合入口
|
||||
|
||||
## 已锁定默认
|
||||
- 全项目纯 C++
|
||||
- 不导入 EG 项目,只做全新重构
|
||||
- `ImGui` 只做编辑器,`RmlUi` 只做运行时 UI
|
||||
- `ImGuizmo` 取代 EG 旧 Gizmo
|
||||
- 功能参考 Unity,但不高仿 Unity 命名
|
||||
- 首版只预留 Prefab 与脚本接口,不交付完整功能
|
||||
- Panda3D 先用 `1.10.16` 起步,后续再升 `1.11`
|
||||
|
||||
## 参考基线
|
||||
- Panda3D 下载页:[panda3d.org/download](https://www.panda3d.org/download/)
|
||||
- Panda3D 1.11 C++ 手册:[docs.panda3d.org/1.11/cpp](https://docs.panda3d.org/1.11/cpp/index)
|
||||
- simplepbr 仓库:[github.com/Moguri/panda3d-simplepbr](https://github.com/Moguri/panda3d-simplepbr)
|
||||
- RmlUi 文档:[mikke89.github.io/RmlUiDoc](https://mikke89.github.io/RmlUiDoc/pages/cpp_manual/building_with_cmake.html)
|
||||
77
README.md
Normal file
77
README.md
Normal file
@ -0,0 +1,77 @@
|
||||
# MetaCore
|
||||
|
||||
MetaCore is a new pure C++ editor/runtime workspace scaffolded from the EG redesign plan.
|
||||
|
||||
## Current status
|
||||
|
||||
- New greenfield project structure under `D:\MetaCore`
|
||||
- Clear module boundaries for `foundation`, `platform`, `engine`, `MetaEditor`, and `MetaPlayer`
|
||||
- JSON-based project and scene persistence without external dependencies
|
||||
- Panda3D 1.10.16 SDK, Dear ImGui, ImGuizmo, and RmlUi are now wired into the integration build
|
||||
- Stable runtime path currently uses a safe Panda-linked platform adapter and shell abstractions
|
||||
- `DearImGuiEditorShell` is now a real integration handoff point, with a default stable mode and an experimental headless ImGui frame mode
|
||||
- simplepbr shader resource mirror directory for the future `MetaRender` implementation
|
||||
|
||||
## What is implemented now
|
||||
|
||||
- Top-level CMake project and presets
|
||||
- Core domain objects: project, scene, assets, editor state, render interfaces
|
||||
- Minimal `MetaEditor` and `MetaPlayer` entry points
|
||||
- Project bootstrap that creates:
|
||||
- `MetaCore.project.json`
|
||||
- `Scenes/Main.mcscene.json`
|
||||
- `Library/AssetDB.json`
|
||||
- default editor layout cache
|
||||
- Basic tests covering project persistence, scene serialization, and command stack behavior
|
||||
|
||||
## Dependency strategy
|
||||
|
||||
The project still supports a dependency-light scaffold build, and now also supports an integration build with the
|
||||
installed SDKs and packages. The real integrations are isolated behind:
|
||||
|
||||
- `MetaCore/platform/panda`
|
||||
- `MetaCore/engine/runtime_ui`
|
||||
- `MetaCore/engine/render`
|
||||
- `MetaEditor/IEditorShell.h`
|
||||
- `MetaEditor/NullEditorShell.cpp`
|
||||
- `MetaEditor/DearImGuiEditorShell.cpp`
|
||||
|
||||
This keeps project, scene, asset, and editor-domain code isolated while the platform and GUI backends evolve.
|
||||
|
||||
## Suggested next steps
|
||||
|
||||
1. Replace the safe Panda-linked adapter with real Panda window/context creation and message pumping.
|
||||
2. Replace the current `DearImGuiEditorShell` fallback path with a real Dear ImGui renderer/backend pair.
|
||||
3. Bind ImGuizmo to `TransformGizmoService` and the editor viewport state.
|
||||
4. Add RmlUi backend glue into `MetaPlayer`.
|
||||
5. Replace the current placeholder Panda render backend with a real `MetaRender`.
|
||||
6. Copy the full simplepbr shader set and integrate the first PBR pass.
|
||||
|
||||
## Configure
|
||||
|
||||
```powershell
|
||||
cmake --preset vs2022-debug
|
||||
cmake --build --preset build-debug
|
||||
ctest --preset test-debug
|
||||
```
|
||||
|
||||
## Integration build
|
||||
|
||||
```powershell
|
||||
cmake --preset vs2022-integration-debug
|
||||
cmake --build --preset build-integration-debug
|
||||
ctest --preset test-integration-debug
|
||||
```
|
||||
|
||||
`MetaEditor` shell selection:
|
||||
|
||||
- default shell: `NullEditorShell`
|
||||
- Dear shell: set `METACORE_EDITOR_SHELL=dearimgui`
|
||||
|
||||
Experimental headless ImGui frame generation can be toggled with:
|
||||
|
||||
```powershell
|
||||
$env:METACORE_ENABLE_HEADLESS_IMGUI = "1"
|
||||
```
|
||||
|
||||
This mode is still treated as experimental and is not part of the stable runtime path yet.
|
||||
27
docs/architecture.md
Normal file
27
docs/architecture.md
Normal file
@ -0,0 +1,27 @@
|
||||
# MetaCore Architecture Notes
|
||||
|
||||
## Runtime split
|
||||
|
||||
- `MetaEditor` owns editor tooling and future Dear ImGui/ImGuizmo integration.
|
||||
- `MetaPlayer` owns runtime playback and future RmlUi integration.
|
||||
- `MetaCore` owns shared domain logic and backend abstractions.
|
||||
|
||||
## Data split
|
||||
|
||||
- Project data: `MetaCore.project.json`
|
||||
- Scene data: `Scenes/*.mcscene.json`
|
||||
- Asset metadata: `Library/AssetDB.json` and per-asset `.meta`
|
||||
- Editor layout data: `Library/Layout/editor_layout.json`
|
||||
|
||||
## Backend split
|
||||
|
||||
- `platform/panda` hides Panda-specific details.
|
||||
- `engine/render` owns renderer-facing types and shader library definitions.
|
||||
- `engine/runtime_ui` owns runtime UI-facing document abstractions.
|
||||
|
||||
## Design rules
|
||||
|
||||
- UI never mutates the domain directly.
|
||||
- Scene data never stores editor-only or runtime cache state.
|
||||
- Renderer configuration is driven by data objects, not by ad-hoc tag strings.
|
||||
- New engine modules should expose narrow service-oriented interfaces.
|
||||
96
docs/setup-windows.md
Normal file
96
docs/setup-windows.md
Normal file
@ -0,0 +1,96 @@
|
||||
# Windows setup notes
|
||||
|
||||
## Current scaffold assumptions
|
||||
|
||||
- Visual Studio 2022 is available
|
||||
- CMake is available
|
||||
- `vcpkg` may not be installed yet
|
||||
- Panda3D C++ SDK may not be installed yet
|
||||
|
||||
## Bootstrap
|
||||
|
||||
```powershell
|
||||
cd D:\MetaCore
|
||||
.\scripts\bootstrap_vcpkg.ps1
|
||||
.\scripts\detect_panda_sdk.ps1
|
||||
cmake --preset vs2022-debug
|
||||
cmake --build --preset build-debug
|
||||
ctest --preset test-debug
|
||||
```
|
||||
|
||||
## Integration build on this machine
|
||||
|
||||
Installed/confirmed dependencies:
|
||||
|
||||
- Panda3D SDK: `D:\PythonProject\Panda3D-1.10.16-x64`
|
||||
- `vcpkg`: `D:\vcpkg`
|
||||
- `imgui`
|
||||
- `imguizmo`
|
||||
- `rmlui`
|
||||
- `fmt`
|
||||
|
||||
Integration configure/build/test:
|
||||
|
||||
```powershell
|
||||
cd D:\MetaCore
|
||||
cmake --preset vs2022-integration-debug
|
||||
cmake --build --preset build-integration-debug
|
||||
ctest --preset test-integration-debug
|
||||
```
|
||||
|
||||
## Confirmed Panda3D SDK path on this machine
|
||||
|
||||
Current detected candidate:
|
||||
|
||||
- `D:\PythonProject\Panda3D-1.10.16-x64`
|
||||
|
||||
The project also exposes `METACORE_PANDA3D_ROOT` as a CMake cache path so the Panda backend integration can
|
||||
use this installation directly once the stub backend is replaced.
|
||||
|
||||
## External integrations still stubbed
|
||||
|
||||
- Panda3D window/context creation and message loop
|
||||
- Dear ImGui renderer/backend pair
|
||||
- ImGuizmo viewport manipulator binding
|
||||
- RmlUi backend renderer and input bridge
|
||||
|
||||
These are already isolated behind the current app and engine interfaces, so the next implementation step is to replace
|
||||
the no-op/stub logic rather than redesign the domain model.
|
||||
|
||||
## Editor shell abstraction
|
||||
|
||||
`MetaEditor` now uses:
|
||||
|
||||
- `IEditorShell` as the stable shell interface
|
||||
- `NullEditorShell` as the default no-dependency implementation
|
||||
- `DearImGuiEditorShell` as the future Dear ImGui docking implementation
|
||||
|
||||
When `METACORE_ENABLE_IMGUI` is enabled and the real Dear ImGui integration is added, the editor can swap shell
|
||||
implementations without changing `EditorApplication`, presenter logic, or the editor-domain services.
|
||||
|
||||
`DearImGuiEditorShell` is now the handoff point for:
|
||||
|
||||
- main menu construction
|
||||
- toolbar group construction
|
||||
- dock section composition
|
||||
- inspector field presentation
|
||||
- shell debug export for layout verification
|
||||
|
||||
Current runtime behavior:
|
||||
|
||||
- `NullEditorShell` is the stable default path
|
||||
- `DearImGuiEditorShell` is selectable with `METACORE_EDITOR_SHELL=dearimgui`
|
||||
- `DearImGuiEditorShell` defaults to a stable non-rendering mode
|
||||
- experimental headless ImGui frame generation can be enabled with `METACORE_ENABLE_HEADLESS_IMGUI=1`
|
||||
- the experimental headless ImGui mode is not yet considered stable
|
||||
|
||||
## Optional GUI feature toggles
|
||||
|
||||
The project already exposes these CMake options:
|
||||
|
||||
- `METACORE_ENABLE_PANDA3D`
|
||||
- `METACORE_ENABLE_IMGUI`
|
||||
- `METACORE_ENABLE_IMGUIZMO`
|
||||
- `METACORE_ENABLE_RMLUI`
|
||||
|
||||
They currently act as compile-time integration flags and are safe to leave `OFF` until the matching SDKs are installed.
|
||||
19
scripts/bootstrap_vcpkg.ps1
Normal file
19
scripts/bootstrap_vcpkg.ps1
Normal file
@ -0,0 +1,19 @@
|
||||
param(
|
||||
[string]$InstallRoot = "D:\\vcpkg"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
if (-not (Test-Path $InstallRoot)) {
|
||||
git clone https://github.com/microsoft/vcpkg.git $InstallRoot
|
||||
}
|
||||
|
||||
Push-Location $InstallRoot
|
||||
try {
|
||||
.\bootstrap-vcpkg.bat
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
Write-Host "vcpkg is ready at $InstallRoot"
|
||||
37
scripts/detect_panda_sdk.ps1
Normal file
37
scripts/detect_panda_sdk.ps1
Normal file
@ -0,0 +1,37 @@
|
||||
param(
|
||||
[string[]]$CandidateRoots = @(
|
||||
"D:\\PythonProject\\Panda3D-1.10.16-x64",
|
||||
"C:\\Program Files\\Panda3D-1.10.16-x64",
|
||||
"C:\\Program Files\\Panda3D-1.10.15-x64",
|
||||
"D:\\Program Files\\Panda3D-1.10.16-x64",
|
||||
"D:\\Panda3D"
|
||||
)
|
||||
)
|
||||
|
||||
$found = @()
|
||||
|
||||
foreach ($root in $CandidateRoots) {
|
||||
if (-not (Test-Path $root)) {
|
||||
continue
|
||||
}
|
||||
|
||||
$includeDir = Join-Path $root "include"
|
||||
$libDir = Join-Path $root "lib"
|
||||
$binDir = Join-Path $root "bin"
|
||||
|
||||
if ((Test-Path $includeDir) -and (Test-Path $libDir)) {
|
||||
$found += [pscustomobject]@{
|
||||
Root = $root
|
||||
Include = $includeDir
|
||||
Lib = $libDir
|
||||
Bin = $binDir
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($found.Count -eq 0) {
|
||||
Write-Host "No Panda3D C++ SDK candidate found."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$found | Format-Table -AutoSize
|
||||
108
tests/MetaCoreTests.cpp
Normal file
108
tests/MetaCoreTests.cpp
Normal file
@ -0,0 +1,108 @@
|
||||
#include "MetaCore/engine/editor_core/CommandService.h"
|
||||
#include "MetaCore/engine/editor_core/EditorSession.h"
|
||||
#include "MetaCore/engine/editor_core/SceneCommands.h"
|
||||
#include "MetaCore/engine/editor_core/SelectionService.h"
|
||||
#include "MetaCore/engine/editor_core/ViewportService.h"
|
||||
#include "MetaCore/engine/scene/Project.h"
|
||||
#include "MetaCore/engine/scene/Scene.h"
|
||||
#include "MetaCore/engine/scene/SceneSerializer.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
|
||||
namespace {
|
||||
|
||||
void expect(const bool condition, const char* message) {
|
||||
if (!condition) {
|
||||
std::cerr << "Test failed: " << message << '\n';
|
||||
std::exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
namespace fs = std::filesystem;
|
||||
using metacore::engine::editor_core::Command;
|
||||
using metacore::engine::editor_core::CommandService;
|
||||
using metacore::engine::editor_core::EditorSession;
|
||||
using metacore::engine::editor_core::SceneCommands;
|
||||
using metacore::engine::editor_core::SelectionService;
|
||||
using metacore::engine::editor_core::ViewportService;
|
||||
using metacore::engine::scene::Project;
|
||||
using metacore::engine::scene::ProjectService;
|
||||
using metacore::engine::scene::Scene;
|
||||
using metacore::engine::scene::SceneSerializer;
|
||||
using metacore::engine::scene::SceneService;
|
||||
|
||||
const fs::path root = "D:/MetaCore/TestProject";
|
||||
fs::remove_all(root);
|
||||
|
||||
ProjectService project_service;
|
||||
expect(project_service.create_project(root, "MetaCoreTest"), "project should be created");
|
||||
|
||||
Project project;
|
||||
expect(project_service.load_project(root / "MetaCore.project.json", project), "project should load");
|
||||
expect(project.name == "MetaCoreTest", "project name should persist");
|
||||
|
||||
SceneService scene_service;
|
||||
Scene scene = scene_service.create_default_scene();
|
||||
auto& cube = scene_service.create_object(scene, "Cube");
|
||||
cube.has_mesh_renderer = true;
|
||||
cube.mesh_renderer.mesh_asset_id = "mesh.cube";
|
||||
|
||||
SceneSerializer serializer;
|
||||
expect(serializer.save_scene(root / "Scenes" / "Test.mcscene.json", scene), "scene should save");
|
||||
|
||||
Scene loaded_scene;
|
||||
expect(serializer.load_scene(root / "Scenes" / "Test.mcscene.json", loaded_scene), "scene should load");
|
||||
expect(loaded_scene.objects.size() == scene.objects.size(), "scene object count should match");
|
||||
|
||||
int value = 0;
|
||||
CommandService commands;
|
||||
commands.execute(Command{
|
||||
"increment",
|
||||
[&value]() { value += 1; },
|
||||
[&value]() { value -= 1; }
|
||||
});
|
||||
expect(value == 1, "command should execute");
|
||||
commands.undo();
|
||||
expect(value == 0, "command should undo");
|
||||
commands.redo();
|
||||
expect(value == 1, "command should redo");
|
||||
|
||||
EditorSession session;
|
||||
SelectionService selection;
|
||||
ViewportService viewport;
|
||||
SceneCommands::create_empty_object(commands, session, selection, scene_service, loaded_scene, "Test Node");
|
||||
expect(selection.has_selection(), "selection should exist after object creation");
|
||||
SceneCommands::rename_selected_object(commands, session, selection, scene_service, loaded_scene, "Renamed Node");
|
||||
expect(scene_service.find_object(loaded_scene, *selection.selection())->get().name == "Renamed Node", "rename should persist");
|
||||
metacore::engine::scene::TransformComponent edited_transform;
|
||||
edited_transform.position = {1.0F, 2.0F, 3.0F};
|
||||
edited_transform.rotation = {10.0F, 20.0F, 30.0F};
|
||||
edited_transform.scale = {2.0F, 2.0F, 2.0F};
|
||||
SceneCommands::apply_transform_to_selected_object(commands, session, selection, scene_service, loaded_scene, edited_transform);
|
||||
expect(
|
||||
scene_service.find_object(loaded_scene, *selection.selection())->get().transform.position[0] == 1.0F,
|
||||
"transform apply should persist"
|
||||
);
|
||||
commands.undo();
|
||||
expect(
|
||||
scene_service.find_object(loaded_scene, *selection.selection())->get().transform.position[0] == 0.0F,
|
||||
"transform undo should restore"
|
||||
);
|
||||
commands.redo();
|
||||
expect(
|
||||
scene_service.find_object(loaded_scene, *selection.selection())->get().transform.position[0] == 1.0F,
|
||||
"transform redo should reapply"
|
||||
);
|
||||
SceneCommands::focus_selected_object(session, selection, viewport);
|
||||
expect(viewport.focus_target().has_value(), "focus target should be set");
|
||||
SceneCommands::delete_selected_object(commands, session, selection, scene_service, loaded_scene);
|
||||
expect(!selection.has_selection(), "selection should clear on delete");
|
||||
|
||||
std::cout << "MetaCoreTests passed\n";
|
||||
return 0;
|
||||
}
|
||||
18
third_party/simplepbr-shaders/README.md
vendored
Normal file
18
third_party/simplepbr-shaders/README.md
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
# simplepbr shader mirror
|
||||
|
||||
This directory is reserved for the MetaCore C++ renderer to consume simplepbr-inspired shader assets.
|
||||
|
||||
Current status:
|
||||
|
||||
- `simplepbr.vert`
|
||||
- `simplepbr.frag`
|
||||
- `shadow.vert`
|
||||
- `shadow.frag`
|
||||
- `skybox.vert`
|
||||
- `skybox.frag`
|
||||
- `post.vert`
|
||||
- `tonemap.frag`
|
||||
|
||||
The current scaffold includes a mix of directly mirrored and placeholder-compatible files so the runtime
|
||||
layout and file references already exist. Replace or verify them against upstream before wiring the real
|
||||
Panda3D render backend.
|
||||
10
third_party/simplepbr-shaders/post.vert
vendored
Normal file
10
third_party/simplepbr-shaders/post.vert
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
#version 120
|
||||
|
||||
attribute vec4 p3d_Vertex;
|
||||
attribute vec2 p3d_MultiTexCoord0;
|
||||
varying vec2 v_texcoord;
|
||||
|
||||
void main() {
|
||||
v_texcoord = p3d_MultiTexCoord0;
|
||||
gl_Position = p3d_Vertex;
|
||||
}
|
||||
5
third_party/simplepbr-shaders/shadow.frag
vendored
Normal file
5
third_party/simplepbr-shaders/shadow.frag
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
#version 120
|
||||
|
||||
void main() {
|
||||
gl_FragColor = vec4(1.0);
|
||||
}
|
||||
8
third_party/simplepbr-shaders/shadow.vert
vendored
Normal file
8
third_party/simplepbr-shaders/shadow.vert
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
#version 120
|
||||
|
||||
uniform mat4 p3d_ModelViewProjectionMatrix;
|
||||
attribute vec4 p3d_Vertex;
|
||||
|
||||
void main() {
|
||||
gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex;
|
||||
}
|
||||
32
third_party/simplepbr-shaders/simplepbr.frag
vendored
Normal file
32
third_party/simplepbr-shaders/simplepbr.frag
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
#version 120
|
||||
|
||||
uniform struct p3d_MaterialParameters {
|
||||
vec4 baseColor;
|
||||
vec4 emission;
|
||||
float roughness;
|
||||
float metallic;
|
||||
} p3d_Material;
|
||||
|
||||
uniform sampler2D p3d_TextureBaseColor;
|
||||
uniform sampler2D p3d_TextureMetalRoughness;
|
||||
uniform sampler2D p3d_TextureNormal;
|
||||
uniform sampler2D p3d_TextureEmission;
|
||||
|
||||
varying vec3 v_view_position;
|
||||
varying vec3 v_world_position;
|
||||
varying vec4 v_color;
|
||||
varying vec2 v_texcoord;
|
||||
varying mat3 v_view_tbn;
|
||||
varying mat3 v_world_tbn;
|
||||
|
||||
void main() {
|
||||
vec4 metal_rough = texture2D(p3d_TextureMetalRoughness, v_texcoord);
|
||||
float metallic = clamp(p3d_Material.metallic * metal_rough.b, 0.0, 1.0);
|
||||
float roughness = clamp(p3d_Material.roughness * metal_rough.g, 0.0, 1.0);
|
||||
vec4 base_color = p3d_Material.baseColor * v_color * texture2D(p3d_TextureBaseColor, v_texcoord);
|
||||
vec3 normal = normalize(v_view_tbn * (2.0 * texture2D(p3d_TextureNormal, v_texcoord).rgb - 1.0));
|
||||
vec3 emission = p3d_Material.emission.rgb * texture2D(p3d_TextureEmission, v_texcoord).rgb;
|
||||
vec3 lit = base_color.rgb * (0.15 + max(0.0, normal.z) * (1.0 - roughness * 0.5));
|
||||
lit = mix(lit, base_color.rgb, metallic * 0.25);
|
||||
gl_FragColor = vec4(lit + emission, base_color.a);
|
||||
}
|
||||
46
third_party/simplepbr-shaders/simplepbr.vert
vendored
Normal file
46
third_party/simplepbr-shaders/simplepbr.vert
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
#version 120
|
||||
|
||||
#ifndef MAX_LIGHTS
|
||||
#define MAX_LIGHTS 8
|
||||
#endif
|
||||
|
||||
uniform mat4 p3d_ProjectionMatrix;
|
||||
uniform mat4 p3d_ModelViewMatrix;
|
||||
uniform mat4 p3d_ModelMatrix;
|
||||
uniform mat3 p3d_NormalMatrix;
|
||||
uniform mat4 p3d_TextureMatrix;
|
||||
uniform mat4 p3d_ModelMatrixInverseTranspose;
|
||||
|
||||
attribute vec4 p3d_Vertex;
|
||||
attribute vec4 p3d_Color;
|
||||
attribute vec3 p3d_Normal;
|
||||
attribute vec4 p3d_Tangent;
|
||||
attribute vec2 p3d_MultiTexCoord0;
|
||||
|
||||
varying vec3 v_view_position;
|
||||
varying vec3 v_world_position;
|
||||
varying vec4 v_color;
|
||||
varying vec2 v_texcoord;
|
||||
varying mat3 v_view_tbn;
|
||||
varying mat3 v_world_tbn;
|
||||
|
||||
void main() {
|
||||
vec4 view_position = p3d_ModelViewMatrix * p3d_Vertex;
|
||||
v_view_position = view_position.xyz;
|
||||
v_world_position = (p3d_ModelMatrix * p3d_Vertex).xyz;
|
||||
v_color = p3d_Color;
|
||||
v_texcoord = (p3d_TextureMatrix * vec4(p3d_MultiTexCoord0, 0, 1)).xy;
|
||||
|
||||
vec3 view_normal = normalize(p3d_NormalMatrix * p3d_Normal);
|
||||
vec3 view_tangent = normalize(p3d_NormalMatrix * p3d_Tangent.xyz);
|
||||
vec3 view_bitangent = cross(view_normal, view_tangent) * p3d_Tangent.w;
|
||||
v_view_tbn = mat3(view_tangent, view_bitangent, view_normal);
|
||||
|
||||
mat3 world_normal_mat = mat3(p3d_ModelMatrixInverseTranspose);
|
||||
vec3 world_normal = normalize(world_normal_mat * p3d_Normal);
|
||||
vec3 world_tangent = normalize(world_normal_mat * p3d_Tangent.xyz);
|
||||
vec3 world_bitangent = cross(world_normal, world_tangent) * p3d_Tangent.w;
|
||||
v_world_tbn = mat3(world_tangent, world_bitangent, world_normal);
|
||||
|
||||
gl_Position = p3d_ProjectionMatrix * view_position;
|
||||
}
|
||||
8
third_party/simplepbr-shaders/skybox.frag
vendored
Normal file
8
third_party/simplepbr-shaders/skybox.frag
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
#version 120
|
||||
|
||||
uniform samplerCube skybox_tex;
|
||||
varying vec3 v_direction;
|
||||
|
||||
void main() {
|
||||
gl_FragColor = textureCube(skybox_tex, normalize(v_direction));
|
||||
}
|
||||
10
third_party/simplepbr-shaders/skybox.vert
vendored
Normal file
10
third_party/simplepbr-shaders/skybox.vert
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
#version 120
|
||||
|
||||
uniform mat4 p3d_ModelViewProjectionMatrix;
|
||||
attribute vec4 p3d_Vertex;
|
||||
varying vec3 v_direction;
|
||||
|
||||
void main() {
|
||||
v_direction = p3d_Vertex.xyz;
|
||||
gl_Position = p3d_ModelViewProjectionMatrix * p3d_Vertex;
|
||||
}
|
||||
13
third_party/simplepbr-shaders/tonemap.frag
vendored
Normal file
13
third_party/simplepbr-shaders/tonemap.frag
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
#version 120
|
||||
|
||||
uniform sampler2D tex;
|
||||
uniform float exposure;
|
||||
varying vec2 v_texcoord;
|
||||
|
||||
void main() {
|
||||
vec4 tex_color = texture2D(tex, v_texcoord);
|
||||
vec3 color = tex_color.rgb * exposure;
|
||||
color = max(vec3(0.0), color - vec3(0.004));
|
||||
color = (color * (vec3(6.2) * color + vec3(0.5))) / (color * (vec3(6.2) * color + vec3(1.7)) + vec3(0.06));
|
||||
gl_FragColor = vec4(color, tex_color.a);
|
||||
}
|
||||
65
tools/MetaCoreWindowProbe.cpp
Normal file
65
tools/MetaCoreWindowProbe.cpp
Normal file
@ -0,0 +1,65 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#if METACORE_HAS_PANDA3D
|
||||
#include <pandaFramework.h>
|
||||
#include <windowProperties.h>
|
||||
#include <windows.h>
|
||||
#include <filesystem>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
void log_step(const std::string& message) {
|
||||
std::cout << "[MetaCoreWindowProbe] " << message << std::endl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
#ifdef METACORE_HAS_PANDA3D
|
||||
log_step("Panda backend compile flag is enabled.");
|
||||
const auto panda_bin = std::filesystem::path(METACORE_PANDA3D_ROOT_PATH) / "bin";
|
||||
if (std::filesystem::exists(panda_bin)) {
|
||||
SetDllDirectoryW(panda_bin.wstring().c_str());
|
||||
log_step("DLL search path set to " + panda_bin.string());
|
||||
} else {
|
||||
log_step("Panda bin path not found: " + panda_bin.string());
|
||||
}
|
||||
|
||||
log_step("Creating PandaFramework");
|
||||
PandaFramework framework;
|
||||
|
||||
log_step("Calling open_framework()");
|
||||
framework.open_framework();
|
||||
|
||||
log_step("Setting MetaCore probe window title");
|
||||
framework.set_window_title("MetaCore Window Probe");
|
||||
|
||||
WindowProperties props;
|
||||
props.set_size(960, 540);
|
||||
props.set_title("MetaCore Window Probe");
|
||||
|
||||
log_step("Calling open_window()");
|
||||
WindowFramework* window = framework.open_window(props, 0);
|
||||
if (window == nullptr) {
|
||||
log_step("open_window() returned null");
|
||||
framework.close_framework();
|
||||
return 2;
|
||||
}
|
||||
|
||||
log_step("Window opened successfully");
|
||||
framework.do_frame(nullptr);
|
||||
log_step("Frame pumped successfully");
|
||||
|
||||
framework.close_window(window);
|
||||
log_step("Window closed successfully");
|
||||
|
||||
framework.close_framework();
|
||||
log_step("Framework closed successfully");
|
||||
return 0;
|
||||
#else
|
||||
log_step("Panda backend is not enabled in this build.");
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
25
vcpkg.json
Normal file
25
vcpkg.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "metacore",
|
||||
"version-string": "0.1.0",
|
||||
"dependencies": [
|
||||
"fmt",
|
||||
{
|
||||
"name": "imgui",
|
||||
"default-features": false,
|
||||
"features": [
|
||||
"docking-experimental",
|
||||
"win32-binding"
|
||||
]
|
||||
},
|
||||
"imguizmo",
|
||||
"rmlui"
|
||||
],
|
||||
"features": {
|
||||
"editor-ui": {
|
||||
"description": "Editor UI dependencies for Dear ImGui and ImGuizmo are handled outside vcpkg in this scaffold."
|
||||
},
|
||||
"panda3d-backend": {
|
||||
"description": "Panda3D backend is expected to be supplied by a local SDK/toolchain integration."
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user