metacore+imgui
This commit is contained in:
parent
8d6e6bc0da
commit
87bedac5e0
@ -48,20 +48,40 @@
|
||||
"METACORE_ENABLE_IMGUIZMO": "ON",
|
||||
"METACORE_ENABLE_RMLUI": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "vs2022-integration-release",
|
||||
"inherits": "base",
|
||||
"displayName": "VS2022 Integration Release",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release",
|
||||
"METACORE_ENABLE_PANDA3D": "ON",
|
||||
"METACORE_ENABLE_IMGUI": "ON",
|
||||
"METACORE_ENABLE_IMGUIZMO": "ON",
|
||||
"METACORE_ENABLE_RMLUI": "ON"
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "build-debug",
|
||||
"configurePreset": "vs2022-debug"
|
||||
"configurePreset": "vs2022-debug",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "build-release",
|
||||
"configurePreset": "vs2022-release"
|
||||
"configurePreset": "vs2022-release",
|
||||
"configuration": "Release"
|
||||
},
|
||||
{
|
||||
"name": "build-integration-debug",
|
||||
"configurePreset": "vs2022-integration-debug"
|
||||
"configurePreset": "vs2022-integration-debug",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "build-integration-release",
|
||||
"configurePreset": "vs2022-integration-release",
|
||||
"configuration": "Release"
|
||||
}
|
||||
],
|
||||
"testPresets": [
|
||||
@ -80,6 +100,14 @@
|
||||
"output": {
|
||||
"outputOnFailure": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "test-integration-release",
|
||||
"configurePreset": "vs2022-integration-release",
|
||||
"configuration": "Release",
|
||||
"output": {
|
||||
"outputOnFailure": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@ -47,6 +47,33 @@ struct TonemapSettings {
|
||||
float exposure = 1.0F;
|
||||
};
|
||||
|
||||
struct UiVertex {
|
||||
std::array<float, 2> position{0.0F, 0.0F};
|
||||
std::array<float, 2> uv{0.0F, 0.0F};
|
||||
std::uint32_t color = 0;
|
||||
};
|
||||
|
||||
struct UiDrawCommand {
|
||||
std::array<float, 4> clip_rect{0.0F, 0.0F, 0.0F, 0.0F};
|
||||
std::uintptr_t texture_id = 0;
|
||||
std::size_t element_count = 0;
|
||||
std::size_t index_offset = 0;
|
||||
std::size_t vertex_offset = 0;
|
||||
};
|
||||
|
||||
struct UiDrawFrame {
|
||||
std::array<float, 2> display_pos{0.0F, 0.0F};
|
||||
std::array<float, 2> display_size{0.0F, 0.0F};
|
||||
std::array<float, 2> framebuffer_scale{1.0F, 1.0F};
|
||||
int draw_list_count = 0;
|
||||
int total_vertex_count = 0;
|
||||
int total_index_count = 0;
|
||||
int total_command_count = 0;
|
||||
std::vector<UiVertex> vertices;
|
||||
std::vector<std::uint32_t> indices;
|
||||
std::vector<UiDrawCommand> commands;
|
||||
};
|
||||
|
||||
class IMetaRenderBackend {
|
||||
public:
|
||||
virtual ~IMetaRenderBackend() = default;
|
||||
@ -55,6 +82,7 @@ public:
|
||||
virtual void shutdown() = 0;
|
||||
virtual void set_frame_uniforms(const FrameUniforms& uniforms) = 0;
|
||||
virtual void set_light_uniforms(const LightUniforms& uniforms) = 0;
|
||||
virtual void set_ui_draw_frame(const UiDrawFrame& frame) = 0;
|
||||
};
|
||||
|
||||
} // namespace metacore::engine::render
|
||||
|
||||
@ -9,6 +9,8 @@
|
||||
|
||||
#include <filesystem>
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
#include <windows.h>
|
||||
|
||||
namespace metacore::platform::panda {
|
||||
@ -28,17 +30,17 @@ PandaPlatformContext::PandaPlatformContext() = default;
|
||||
PandaPlatformContext::~PandaPlatformContext() = default;
|
||||
|
||||
bool PandaPlatformContext::initialize(const std::string& window_title) {
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext initialize: begin\n" << std::flush;
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext::initialize enter\n";
|
||||
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);
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext::initialize after title copy\n";
|
||||
close_requested_ = false;
|
||||
frame_count_ = 0;
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext::initialize before env\n";
|
||||
const char* enable_window_env = std::getenv("METACORE_ENABLE_PANDA_WINDOW");
|
||||
window_enabled_ = enable_window_env != nullptr && std::string_view(enable_window_env) == "1";
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext::initialize after env\n";
|
||||
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")
|
||||
@ -50,27 +52,28 @@ bool PandaPlatformContext::initialize(const std::string& window_title) {
|
||||
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;
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext::initialize before open_framework\n";
|
||||
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;
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext::initialize after open_framework\n";
|
||||
native_state_->framework.set_window_title("MetaCore Window Probe");
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext::initialize after framework.set_window_title\n";
|
||||
|
||||
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;
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext::initialize after WindowProperties ctor\n";
|
||||
props.set_size(960, 540);
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext::initialize after props.set_size\n";
|
||||
props.set_title("MetaCore Window Probe");
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext::initialize after props.set_title\n";
|
||||
native_state_->window = native_state_->framework.open_window(props, 0);
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext initialize: after open_window\n" << std::flush;
|
||||
std::cerr << "[MetaCore][Trace] PandaPlatformContext::initialize after open_window\n";
|
||||
window_opened_ = native_state_->window != nullptr;
|
||||
if (!window_opened_) {
|
||||
foundation::log(foundation::LogLevel::Warning, "Panda window bootstrap requested but open_window returned null");
|
||||
@ -101,6 +104,8 @@ void PandaPlatformContext::shutdown() {
|
||||
panda_build_date_.clear();
|
||||
window_enabled_ = false;
|
||||
window_opened_ = false;
|
||||
close_requested_ = false;
|
||||
frame_count_ = 0;
|
||||
#if METACORE_HAS_PANDA3D
|
||||
if (native_state_) {
|
||||
if (native_state_->framework_open) {
|
||||
@ -119,9 +124,27 @@ void PandaPlatformContext::shutdown() {
|
||||
}
|
||||
|
||||
void PandaPlatformContext::pump_frame() {
|
||||
++frame_count_;
|
||||
#if METACORE_HAS_PANDA3D
|
||||
if (window_opened_ && native_state_ && native_state_->framework_open) {
|
||||
native_state_->framework.do_frame(nullptr);
|
||||
if (native_state_->window != nullptr) {
|
||||
GraphicsWindow* graphics_window = native_state_->window->get_graphics_window();
|
||||
if (graphics_window != nullptr && graphics_window->is_closed()) {
|
||||
close_requested_ = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void PandaPlatformContext::request_close() {
|
||||
close_requested_ = true;
|
||||
#if METACORE_HAS_PANDA3D
|
||||
if (window_opened_ && native_state_ && native_state_->window != nullptr) {
|
||||
if (GraphicsWindow* graphics_window = native_state_->window->get_graphics_window(); graphics_window != nullptr) {
|
||||
graphics_window->request_close();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -158,4 +181,78 @@ bool PandaPlatformContext::window_opened() const {
|
||||
return window_opened_;
|
||||
}
|
||||
|
||||
bool PandaPlatformContext::should_close() const {
|
||||
return close_requested_;
|
||||
}
|
||||
|
||||
int PandaPlatformContext::frame_count() const {
|
||||
return frame_count_;
|
||||
}
|
||||
|
||||
std::uintptr_t PandaPlatformContext::native_window_handle() const {
|
||||
#if METACORE_HAS_PANDA3D
|
||||
if (native_state_ && native_state_->window != nullptr) {
|
||||
if (GraphicsWindow* graphics_window = native_state_->window->get_graphics_window(); graphics_window != nullptr) {
|
||||
if (WindowHandle* handle = graphics_window->get_window_handle(); handle != nullptr) {
|
||||
if (WindowHandle::OSHandle* os_handle = handle->get_os_handle(); os_handle != nullptr) {
|
||||
const auto raw_handle = static_cast<std::uintptr_t>(os_handle->get_int_handle());
|
||||
if (raw_handle != 0) {
|
||||
return raw_handle;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!window_title_.empty()) {
|
||||
const std::wstring wide_title(window_title_.begin(), window_title_.end());
|
||||
if (HWND window = FindWindowW(nullptr, wide_title.c_str()); window != nullptr) {
|
||||
return reinterpret_cast<std::uintptr_t>(window);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::uintptr_t PandaPlatformContext::window_framework_handle() const {
|
||||
#if METACORE_HAS_PANDA3D
|
||||
if (native_state_ && native_state_->window != nullptr) {
|
||||
return reinterpret_cast<std::uintptr_t>(native_state_->window);
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PandaPlatformContext::window_width() const {
|
||||
#if METACORE_HAS_PANDA3D
|
||||
if (native_state_ && native_state_->window != nullptr) {
|
||||
if (GraphicsOutput* output = native_state_->window->get_graphics_output(); output != nullptr && output->has_size()) {
|
||||
return output->get_x_size();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
int PandaPlatformContext::window_height() const {
|
||||
#if METACORE_HAS_PANDA3D
|
||||
if (native_state_ && native_state_->window != nullptr) {
|
||||
if (GraphicsOutput* output = native_state_->window->get_graphics_output(); output != nullptr && output->has_size()) {
|
||||
return output->get_y_size();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::uintptr_t PandaPlatformContext::primary_display_region_handle() const {
|
||||
#if METACORE_HAS_PANDA3D
|
||||
if (native_state_ && native_state_->window != nullptr) {
|
||||
if (DisplayRegion* region = native_state_->window->get_display_region_3d(); region != nullptr) {
|
||||
return reinterpret_cast<std::uintptr_t>(region);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace metacore::platform::panda
|
||||
|
||||
@ -14,6 +14,7 @@ public:
|
||||
bool initialize(const std::string& window_title);
|
||||
void shutdown();
|
||||
void pump_frame();
|
||||
void request_close();
|
||||
|
||||
[[nodiscard]] bool initialized() const;
|
||||
[[nodiscard]] const std::string& window_title() const;
|
||||
@ -23,6 +24,13 @@ public:
|
||||
[[nodiscard]] const std::string& panda_build_date() const;
|
||||
[[nodiscard]] bool window_enabled() const;
|
||||
[[nodiscard]] bool window_opened() const;
|
||||
[[nodiscard]] bool should_close() const;
|
||||
[[nodiscard]] int frame_count() const;
|
||||
[[nodiscard]] std::uintptr_t native_window_handle() const;
|
||||
[[nodiscard]] std::uintptr_t window_framework_handle() const;
|
||||
[[nodiscard]] int window_width() const;
|
||||
[[nodiscard]] int window_height() const;
|
||||
[[nodiscard]] std::uintptr_t primary_display_region_handle() const;
|
||||
|
||||
private:
|
||||
struct NativeState;
|
||||
@ -35,6 +43,8 @@ private:
|
||||
std::string panda_build_date_;
|
||||
bool window_enabled_ = false;
|
||||
bool window_opened_ = false;
|
||||
bool close_requested_ = false;
|
||||
int frame_count_ = 0;
|
||||
std::unique_ptr<NativeState> native_state_;
|
||||
};
|
||||
|
||||
|
||||
@ -1,14 +1,159 @@
|
||||
#include "MetaCore/platform/panda/PandaRenderBackend.h"
|
||||
|
||||
#if METACORE_HAS_PANDA3D
|
||||
#include <callbackObject.h>
|
||||
#include <displayRegion.h>
|
||||
#include <lineSegs.h>
|
||||
#include <nodePath.h>
|
||||
#include <windowFramework.h>
|
||||
#include <luse.h>
|
||||
#endif
|
||||
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
|
||||
namespace metacore::platform::panda {
|
||||
|
||||
#if METACORE_HAS_PANDA3D
|
||||
namespace {
|
||||
|
||||
class UiDrawCallback final : public CallbackObject {
|
||||
public:
|
||||
explicit UiDrawCallback(int* observed_draw_callback_count)
|
||||
: observed_draw_callback_count_(observed_draw_callback_count) {
|
||||
}
|
||||
|
||||
void do_callback(CallbackData* cbdata) override {
|
||||
if (observed_draw_callback_count_ != nullptr) {
|
||||
++(*observed_draw_callback_count_);
|
||||
}
|
||||
CallbackObject::do_callback(cbdata);
|
||||
}
|
||||
|
||||
private:
|
||||
int* observed_draw_callback_count_ = nullptr;
|
||||
};
|
||||
|
||||
NodePath create_wire_cube(const NodePath& parent) {
|
||||
LineSegs segments("metacore-demo-cube");
|
||||
segments.set_thickness(2.5F);
|
||||
segments.set_color(0.85F, 0.9F, 1.0F, 1.0F);
|
||||
|
||||
const LPoint3f corners[] = {
|
||||
{-0.5F, -0.5F, -0.5F},
|
||||
{0.5F, -0.5F, -0.5F},
|
||||
{0.5F, 0.5F, -0.5F},
|
||||
{-0.5F, 0.5F, -0.5F},
|
||||
{-0.5F, -0.5F, 0.5F},
|
||||
{0.5F, -0.5F, 0.5F},
|
||||
{0.5F, 0.5F, 0.5F},
|
||||
{-0.5F, 0.5F, 0.5F},
|
||||
};
|
||||
const int edges[][2] = {
|
||||
{0, 1}, {1, 2}, {2, 3}, {3, 0},
|
||||
{4, 5}, {5, 6}, {6, 7}, {7, 4},
|
||||
{0, 4}, {1, 5}, {2, 6}, {3, 7},
|
||||
};
|
||||
for (const auto& edge : edges) {
|
||||
segments.move_to(corners[edge[0]]);
|
||||
segments.draw_to(corners[edge[1]]);
|
||||
}
|
||||
|
||||
return parent.attach_new_node(segments.create());
|
||||
}
|
||||
|
||||
NodePath create_gizmo(const NodePath& parent) {
|
||||
LineSegs segments("metacore-demo-gizmo");
|
||||
segments.set_thickness(4.0F);
|
||||
|
||||
segments.set_color(0.95F, 0.2F, 0.2F, 1.0F);
|
||||
segments.move_to(0.0F, 0.0F, 0.0F);
|
||||
segments.draw_to(1.5F, 0.0F, 0.0F);
|
||||
|
||||
segments.set_color(0.2F, 0.95F, 0.35F, 1.0F);
|
||||
segments.move_to(0.0F, 0.0F, 0.0F);
|
||||
segments.draw_to(0.0F, 1.5F, 0.0F);
|
||||
|
||||
segments.set_color(0.25F, 0.55F, 1.0F, 1.0F);
|
||||
segments.move_to(0.0F, 0.0F, 0.0F);
|
||||
segments.draw_to(0.0F, 0.0F, 1.5F);
|
||||
|
||||
return parent.attach_new_node(segments.create());
|
||||
}
|
||||
|
||||
NodePath create_editor_chrome(const NodePath& parent) {
|
||||
LineSegs segments("metacore-editor-chrome");
|
||||
segments.set_thickness(2.0F);
|
||||
segments.set_color(0.15F, 0.85F, 0.95F, 0.95F);
|
||||
|
||||
const auto draw_rect = [&](const float left, const float right, const float bottom, const float top) {
|
||||
segments.move_to(left, 0.0F, bottom);
|
||||
segments.draw_to(right, 0.0F, bottom);
|
||||
segments.draw_to(right, 0.0F, top);
|
||||
segments.draw_to(left, 0.0F, top);
|
||||
segments.draw_to(left, 0.0F, bottom);
|
||||
};
|
||||
|
||||
draw_rect(-1.30F, -0.72F, -0.92F, 0.84F);
|
||||
draw_rect(-0.66F, 0.66F, -0.38F, 0.84F);
|
||||
draw_rect(0.72F, 1.30F, -0.92F, 0.84F);
|
||||
draw_rect(-0.66F, 1.30F, -0.92F, -0.45F);
|
||||
draw_rect(-1.30F, 1.30F, 0.88F, 1.00F);
|
||||
|
||||
return parent.attach_new_node(segments.create());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
#endif
|
||||
|
||||
struct PandaRenderBackend::NativeState {
|
||||
#if METACORE_HAS_PANDA3D
|
||||
PT(CallbackObject) draw_callback;
|
||||
DisplayRegion* bound_display_region = nullptr;
|
||||
WindowFramework* window = nullptr;
|
||||
NodePath cube_root;
|
||||
NodePath gizmo_root;
|
||||
NodePath hud_root;
|
||||
#endif
|
||||
int observed_draw_callback_count = 0;
|
||||
bool draw_callback_bound = false;
|
||||
};
|
||||
|
||||
PandaRenderBackend::PandaRenderBackend() = default;
|
||||
|
||||
PandaRenderBackend::~PandaRenderBackend() = default;
|
||||
|
||||
bool PandaRenderBackend::initialize() {
|
||||
initialized_ = true;
|
||||
native_state_ = std::make_unique<NativeState>();
|
||||
return true;
|
||||
}
|
||||
|
||||
void PandaRenderBackend::shutdown() {
|
||||
#if METACORE_HAS_PANDA3D
|
||||
if (native_state_ && native_state_->bound_display_region != nullptr) {
|
||||
native_state_->bound_display_region->clear_draw_callback();
|
||||
native_state_->bound_display_region = nullptr;
|
||||
}
|
||||
if (native_state_) {
|
||||
if (!native_state_->cube_root.is_empty()) {
|
||||
native_state_->cube_root.remove_node();
|
||||
}
|
||||
if (!native_state_->gizmo_root.is_empty()) {
|
||||
native_state_->gizmo_root.remove_node();
|
||||
}
|
||||
if (!native_state_->hud_root.is_empty()) {
|
||||
native_state_->hud_root.remove_node();
|
||||
}
|
||||
native_state_->draw_callback.clear();
|
||||
native_state_->draw_callback_bound = false;
|
||||
native_state_->window = nullptr;
|
||||
}
|
||||
#endif
|
||||
native_state_.reset();
|
||||
initialized_ = false;
|
||||
submitted_ui_frame_count_ = 0;
|
||||
ui_draw_frame_ = {};
|
||||
}
|
||||
|
||||
void PandaRenderBackend::set_frame_uniforms(const metacore::engine::render::FrameUniforms& uniforms) {
|
||||
@ -19,6 +164,165 @@ void PandaRenderBackend::set_light_uniforms(const metacore::engine::render::Ligh
|
||||
light_uniforms_ = uniforms;
|
||||
}
|
||||
|
||||
void PandaRenderBackend::set_ui_draw_frame(const metacore::engine::render::UiDrawFrame& frame) {
|
||||
ui_draw_frame_ = frame;
|
||||
if (!frame.commands.empty() || frame.total_command_count > 0) {
|
||||
++submitted_ui_frame_count_;
|
||||
}
|
||||
}
|
||||
|
||||
void PandaRenderBackend::bind_window_framework(const std::uintptr_t window_framework_handle) {
|
||||
#if METACORE_HAS_PANDA3D
|
||||
if (!native_state_) {
|
||||
native_state_ = std::make_unique<NativeState>();
|
||||
}
|
||||
|
||||
WindowFramework* window = reinterpret_cast<WindowFramework*>(window_framework_handle);
|
||||
if (window == nullptr || native_state_->window == window) {
|
||||
return;
|
||||
}
|
||||
|
||||
native_state_->window = window;
|
||||
native_state_->cube_root = create_wire_cube(native_state_->window->get_render());
|
||||
native_state_->gizmo_root = create_gizmo(native_state_->window->get_render());
|
||||
native_state_->hud_root = create_editor_chrome(native_state_->window->get_aspect_2d());
|
||||
native_state_->hud_root.set_bin("fixed", 0);
|
||||
native_state_->hud_root.set_depth_test(false);
|
||||
native_state_->hud_root.set_depth_write(false);
|
||||
#else
|
||||
(void)window_framework_handle;
|
||||
#endif
|
||||
}
|
||||
|
||||
void PandaRenderBackend::bind_primary_display_region(const std::uintptr_t display_region_handle) {
|
||||
#if METACORE_HAS_PANDA3D
|
||||
if (!native_state_) {
|
||||
native_state_ = std::make_unique<NativeState>();
|
||||
}
|
||||
|
||||
DisplayRegion* region = reinterpret_cast<DisplayRegion*>(display_region_handle);
|
||||
if (native_state_->bound_display_region == region && native_state_->draw_callback_bound) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (native_state_->bound_display_region != nullptr) {
|
||||
native_state_->bound_display_region->clear_draw_callback();
|
||||
native_state_->bound_display_region = nullptr;
|
||||
native_state_->draw_callback.clear();
|
||||
native_state_->draw_callback_bound = false;
|
||||
}
|
||||
|
||||
if (region != nullptr) {
|
||||
native_state_->draw_callback = new UiDrawCallback(&native_state_->observed_draw_callback_count);
|
||||
region->set_draw_callback(native_state_->draw_callback);
|
||||
native_state_->bound_display_region = region;
|
||||
native_state_->draw_callback_bound = true;
|
||||
}
|
||||
#else
|
||||
(void)display_region_handle;
|
||||
#endif
|
||||
}
|
||||
|
||||
void PandaRenderBackend::set_demo_selection_transform(
|
||||
const bool has_selection,
|
||||
const std::array<float, 3> position,
|
||||
const std::array<float, 3> rotation,
|
||||
const std::array<float, 3> scale
|
||||
) {
|
||||
#if METACORE_HAS_PANDA3D
|
||||
std::cerr << "[MetaCore][Trace] PandaRenderBackend::set_demo_selection_transform enter\n";
|
||||
if (!native_state_ || native_state_->window == nullptr) {
|
||||
std::cerr << "[MetaCore][Trace] PandaRenderBackend::set_demo_selection_transform early return\n";
|
||||
return;
|
||||
}
|
||||
if (has_selection) {
|
||||
native_state_->cube_root.set_pos(
|
||||
position[0],
|
||||
position[1],
|
||||
position[2]
|
||||
);
|
||||
native_state_->cube_root.set_hpr(
|
||||
rotation[0],
|
||||
rotation[1],
|
||||
rotation[2]
|
||||
);
|
||||
native_state_->cube_root.set_scale(
|
||||
scale[0],
|
||||
scale[1],
|
||||
scale[2]
|
||||
);
|
||||
|
||||
native_state_->gizmo_root.set_pos(
|
||||
position[0],
|
||||
position[1],
|
||||
position[2]
|
||||
);
|
||||
native_state_->gizmo_root.show();
|
||||
} else {
|
||||
native_state_->gizmo_root.hide();
|
||||
}
|
||||
std::cerr << "[MetaCore][Trace] PandaRenderBackend::set_demo_selection_transform exit\n";
|
||||
#else
|
||||
(void)has_selection;
|
||||
(void)position;
|
||||
(void)rotation;
|
||||
(void)scale;
|
||||
#endif
|
||||
}
|
||||
|
||||
void PandaRenderBackend::set_demo_hud_text(const std::string& text) {
|
||||
#if METACORE_HAS_PANDA3D
|
||||
std::cerr << "[MetaCore][Trace] PandaRenderBackend::set_demo_hud_text enter\n";
|
||||
if (!native_state_ || native_state_->window == nullptr || native_state_->hud_root.is_empty()) {
|
||||
std::cerr << "[MetaCore][Trace] PandaRenderBackend::set_demo_hud_text early return\n";
|
||||
return;
|
||||
}
|
||||
const bool snap_enabled = text.find("Snap On") != std::string::npos;
|
||||
const bool rotate_mode = text.find("Gizmo: Rotate") != std::string::npos;
|
||||
const bool scale_mode = text.find("Gizmo: Scale") != std::string::npos;
|
||||
if (scale_mode) {
|
||||
native_state_->hud_root.set_color(0.95F, 0.8F, 0.2F, 1.0F);
|
||||
} else if (rotate_mode) {
|
||||
native_state_->hud_root.set_color(0.95F, 0.45F, 0.25F, 1.0F);
|
||||
} else if (snap_enabled) {
|
||||
native_state_->hud_root.set_color(0.45F, 0.95F, 0.45F, 1.0F);
|
||||
} else {
|
||||
native_state_->hud_root.set_color(0.15F, 0.85F, 0.95F, 0.95F);
|
||||
}
|
||||
std::cerr << "[MetaCore][Trace] PandaRenderBackend::set_demo_hud_text exit\n";
|
||||
#else
|
||||
(void)text;
|
||||
#endif
|
||||
}
|
||||
|
||||
void PandaRenderBackend::set_demo_camera(
|
||||
const std::array<float, 3> camera_position,
|
||||
const std::array<float, 3> focus_position
|
||||
) {
|
||||
#if METACORE_HAS_PANDA3D
|
||||
std::cerr << "[MetaCore][Trace] PandaRenderBackend::set_demo_camera enter\n";
|
||||
if (!native_state_ || native_state_->window == nullptr) {
|
||||
std::cerr << "[MetaCore][Trace] PandaRenderBackend::set_demo_camera early return\n";
|
||||
return;
|
||||
}
|
||||
NodePath camera = native_state_->window->get_camera_group();
|
||||
camera.set_pos(
|
||||
camera_position[0],
|
||||
camera_position[1],
|
||||
camera_position[2]
|
||||
);
|
||||
camera.look_at(
|
||||
focus_position[0],
|
||||
focus_position[1],
|
||||
focus_position[2]
|
||||
);
|
||||
std::cerr << "[MetaCore][Trace] PandaRenderBackend::set_demo_camera exit\n";
|
||||
#else
|
||||
(void)camera_position;
|
||||
(void)focus_position;
|
||||
#endif
|
||||
}
|
||||
|
||||
const metacore::engine::render::FrameUniforms& PandaRenderBackend::frame_uniforms() const {
|
||||
return frame_uniforms_;
|
||||
}
|
||||
@ -27,4 +331,20 @@ const metacore::engine::render::LightUniforms& PandaRenderBackend::light_uniform
|
||||
return light_uniforms_;
|
||||
}
|
||||
|
||||
const metacore::engine::render::UiDrawFrame& PandaRenderBackend::ui_draw_frame() const {
|
||||
return ui_draw_frame_;
|
||||
}
|
||||
|
||||
int PandaRenderBackend::submitted_ui_frame_count() const {
|
||||
return submitted_ui_frame_count_;
|
||||
}
|
||||
|
||||
int PandaRenderBackend::observed_draw_callback_count() const {
|
||||
return native_state_ ? native_state_->observed_draw_callback_count : 0;
|
||||
}
|
||||
|
||||
bool PandaRenderBackend::draw_callback_bound() const {
|
||||
return native_state_ && native_state_->draw_callback_bound;
|
||||
}
|
||||
|
||||
} // namespace metacore::platform::panda
|
||||
|
||||
@ -1,23 +1,49 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaCore/engine/render/RenderTypes.h"
|
||||
#include <memory>
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
namespace metacore::platform::panda {
|
||||
|
||||
class PandaRenderBackend final : public metacore::engine::render::IMetaRenderBackend {
|
||||
public:
|
||||
PandaRenderBackend();
|
||||
~PandaRenderBackend() override;
|
||||
|
||||
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;
|
||||
void set_ui_draw_frame(const metacore::engine::render::UiDrawFrame& frame) override;
|
||||
void bind_window_framework(std::uintptr_t window_framework_handle);
|
||||
void bind_primary_display_region(std::uintptr_t display_region_handle);
|
||||
void set_demo_camera(std::array<float, 3> camera_position, std::array<float, 3> focus_position);
|
||||
void set_demo_selection_transform(
|
||||
bool has_selection,
|
||||
std::array<float, 3> position,
|
||||
std::array<float, 3> rotation,
|
||||
std::array<float, 3> scale
|
||||
);
|
||||
void set_demo_hud_text(const std::string& text);
|
||||
|
||||
[[nodiscard]] const metacore::engine::render::FrameUniforms& frame_uniforms() const;
|
||||
[[nodiscard]] const metacore::engine::render::LightUniforms& light_uniforms() const;
|
||||
[[nodiscard]] const metacore::engine::render::UiDrawFrame& ui_draw_frame() const;
|
||||
[[nodiscard]] int submitted_ui_frame_count() const;
|
||||
[[nodiscard]] int observed_draw_callback_count() const;
|
||||
[[nodiscard]] bool draw_callback_bound() const;
|
||||
|
||||
private:
|
||||
struct NativeState;
|
||||
|
||||
bool initialized_ = false;
|
||||
metacore::engine::render::FrameUniforms frame_uniforms_{};
|
||||
metacore::engine::render::LightUniforms light_uniforms_{};
|
||||
metacore::engine::render::UiDrawFrame ui_draw_frame_{};
|
||||
int submitted_ui_frame_count_ = 0;
|
||||
std::unique_ptr<NativeState> native_state_{};
|
||||
};
|
||||
|
||||
} // namespace metacore::platform::panda
|
||||
|
||||
@ -2,9 +2,13 @@
|
||||
|
||||
#include "MetaCore/foundation/FileSystem.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#if METACORE_HAS_IMGUI
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <imgui_impl_win32.h>
|
||||
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
#endif
|
||||
|
||||
#if METACORE_HAS_IMGUIZMO
|
||||
@ -15,11 +19,23 @@
|
||||
#include <cstdlib>
|
||||
#include <string_view>
|
||||
#include <sstream>
|
||||
#include <utility>
|
||||
|
||||
namespace metacore::editor {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char* kMetaCoreImGuiShellWindowProp = "MetaCoreDearImGuiEditorShell";
|
||||
|
||||
std::string uppercase(std::string value) {
|
||||
for (char& ch : value) {
|
||||
if (ch >= 'a' && ch <= 'z') {
|
||||
ch = static_cast<char>(ch - 'a' + 'A');
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
#if METACORE_HAS_IMGUIZMO
|
||||
ImGuizmo::OPERATION to_imguizmo_operation(const std::string& operation) {
|
||||
if (operation == "Rotate") {
|
||||
@ -92,38 +108,73 @@ std::array<float, 16> make_projection_matrix(float width, float height) {
|
||||
|
||||
} // namespace
|
||||
|
||||
#if METACORE_HAS_IMGUI
|
||||
LRESULT CALLBACK metacore_imgui_shell_wnd_proc(HWND hwnd, UINT message, WPARAM w_param, LPARAM l_param) {
|
||||
if (ImGui_ImplWin32_WndProcHandler(hwnd, message, w_param, l_param)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto* shell = reinterpret_cast<DearImGuiEditorShell*>(GetPropA(hwnd, kMetaCoreImGuiShellWindowProp));
|
||||
if (shell != nullptr && shell->previous_wnd_proc_ != nullptr) {
|
||||
return CallWindowProc(shell->previous_wnd_proc_, hwnd, message, w_param, l_param);
|
||||
}
|
||||
return DefWindowProc(hwnd, message, w_param, l_param);
|
||||
}
|
||||
#endif
|
||||
|
||||
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
|
||||
initialize_imgui_context();
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::shutdown() {
|
||||
void DearImGuiEditorShell::initialize_imgui_context() {
|
||||
#if METACORE_HAS_IMGUI
|
||||
if (headless_frame_enabled_ && ImGui::GetCurrentContext() != nullptr) {
|
||||
ImGui::DestroyContext();
|
||||
const bool should_create_context = headless_frame_enabled_ || native_window_handle_ != nullptr;
|
||||
if (!should_create_context || ImGui::GetCurrentContext() != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
imgui_context_owned_ = true;
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
io.DeltaTime = 1.0F / 60.0F;
|
||||
if (headless_frame_enabled_) {
|
||||
io.DisplaySize = ImVec2(1600.0F, 900.0F);
|
||||
} else {
|
||||
io.DisplaySize = ImVec2(
|
||||
native_window_width_ > 0 ? static_cast<float>(native_window_width_) : 1280.0F,
|
||||
native_window_height_ > 0 ? static_cast<float>(native_window_height_) : 720.0F
|
||||
);
|
||||
}
|
||||
unsigned char* font_pixels = nullptr;
|
||||
int font_width = 0;
|
||||
int font_height = 0;
|
||||
io.Fonts->GetTexDataAsRGBA32(&font_pixels, &font_width, &font_height);
|
||||
io.Fonts->SetTexID(static_cast<ImTextureID>(1));
|
||||
|
||||
if (native_window_handle_ != nullptr) {
|
||||
ImGui_ImplWin32_Init(native_window_handle_);
|
||||
SetPropA(native_window_handle_, kMetaCoreImGuiShellWindowProp, this);
|
||||
previous_wnd_proc_ = reinterpret_cast<WNDPROC>(
|
||||
SetWindowLongPtr(native_window_handle_, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(metacore_imgui_shell_wnd_proc))
|
||||
);
|
||||
win32_backend_enabled_ = true;
|
||||
}
|
||||
|
||||
#if METACORE_HAS_IMGUIZMO
|
||||
ImGuizmo::SetImGuiContext(ImGui::GetCurrentContext());
|
||||
#endif
|
||||
ImGui::StyleColorsDark();
|
||||
#endif
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::shutdown() {
|
||||
shutdown_imgui_context();
|
||||
initialized_ = false;
|
||||
last_snapshot_ = {};
|
||||
last_draw_command_count_ = 0;
|
||||
@ -132,30 +183,38 @@ void DearImGuiEditorShell::shutdown() {
|
||||
last_window_count_ = 0;
|
||||
headless_frame_enabled_ = false;
|
||||
dock_layout_initialized_ = false;
|
||||
headless_gizmo_override_consumed_ = false;
|
||||
headless_transform_override_consumed_ = false;
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::shutdown_imgui_context() {
|
||||
#if METACORE_HAS_IMGUI
|
||||
if (win32_backend_enabled_ && native_window_handle_ != nullptr) {
|
||||
SetPropA(native_window_handle_, kMetaCoreImGuiShellWindowProp, nullptr);
|
||||
if (previous_wnd_proc_ != nullptr) {
|
||||
SetWindowLongPtr(native_window_handle_, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(previous_wnd_proc_));
|
||||
previous_wnd_proc_ = nullptr;
|
||||
}
|
||||
ImGui_ImplWin32_Shutdown();
|
||||
win32_backend_enabled_ = false;
|
||||
}
|
||||
|
||||
if (imgui_context_owned_ && ImGui::GetCurrentContext() != nullptr) {
|
||||
ImGui::DestroyContext();
|
||||
}
|
||||
#endif
|
||||
imgui_context_owned_ = false;
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::consume_snapshot(const EditorFrameSnapshot& snapshot) {
|
||||
last_snapshot_ = snapshot;
|
||||
enqueue_headless_gizmo_state_edit(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;
|
||||
}
|
||||
if ((headless_frame_enabled_ || win32_backend_enabled_) && ImGui::GetCurrentContext() != nullptr) {
|
||||
render_imgui_frame(snapshot);
|
||||
capture_render_frame();
|
||||
} else {
|
||||
last_draw_command_count_ = 0;
|
||||
last_vertex_count_ = 0;
|
||||
@ -167,6 +226,119 @@ void DearImGuiEditorShell::consume_snapshot(const EditorFrameSnapshot& snapshot)
|
||||
export_debug_snapshot();
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::capture_render_frame() {
|
||||
#if METACORE_HAS_IMGUI
|
||||
pending_render_frame_ = {};
|
||||
if (const ImDrawData* draw_data = ImGui::GetDrawData(); draw_data != nullptr) {
|
||||
pending_render_frame_.display_pos = {draw_data->DisplayPos.x, draw_data->DisplayPos.y};
|
||||
pending_render_frame_.display_size = {draw_data->DisplaySize.x, draw_data->DisplaySize.y};
|
||||
pending_render_frame_.framebuffer_scale = {draw_data->FramebufferScale.x, draw_data->FramebufferScale.y};
|
||||
pending_render_frame_.draw_list_count = draw_data->CmdListsCount;
|
||||
pending_render_frame_.total_vertex_count = draw_data->TotalVtxCount;
|
||||
pending_render_frame_.total_index_count = draw_data->TotalIdxCount;
|
||||
pending_render_frame_.vertices.reserve(static_cast<std::size_t>(draw_data->TotalVtxCount));
|
||||
pending_render_frame_.indices.reserve(static_cast<std::size_t>(draw_data->TotalIdxCount));
|
||||
|
||||
std::size_t global_vertex_offset = 0;
|
||||
std::size_t global_index_offset = 0;
|
||||
for (int list_index = 0; list_index < draw_data->CmdListsCount; ++list_index) {
|
||||
const ImDrawList* draw_list = draw_data->CmdLists[list_index];
|
||||
for (int vertex_index = 0; vertex_index < draw_list->VtxBuffer.Size; ++vertex_index) {
|
||||
const ImDrawVert& vertex = draw_list->VtxBuffer[vertex_index];
|
||||
pending_render_frame_.vertices.push_back({
|
||||
{vertex.pos.x, vertex.pos.y},
|
||||
{vertex.uv.x, vertex.uv.y},
|
||||
vertex.col
|
||||
});
|
||||
}
|
||||
for (int index_index = 0; index_index < draw_list->IdxBuffer.Size; ++index_index) {
|
||||
pending_render_frame_.indices.push_back(static_cast<std::uint32_t>(draw_list->IdxBuffer[index_index]) + static_cast<std::uint32_t>(global_vertex_offset));
|
||||
}
|
||||
for (int cmd_index = 0; cmd_index < draw_list->CmdBuffer.Size; ++cmd_index) {
|
||||
const ImDrawCmd& cmd = draw_list->CmdBuffer[cmd_index];
|
||||
if (cmd.UserCallback != nullptr) {
|
||||
continue;
|
||||
}
|
||||
pending_render_frame_.commands.push_back({
|
||||
{cmd.ClipRect.x, cmd.ClipRect.y, cmd.ClipRect.z, cmd.ClipRect.w},
|
||||
0,
|
||||
static_cast<std::size_t>(cmd.ElemCount),
|
||||
global_index_offset + static_cast<std::size_t>(cmd.IdxOffset),
|
||||
global_vertex_offset + static_cast<std::size_t>(cmd.VtxOffset)
|
||||
});
|
||||
}
|
||||
global_vertex_offset += static_cast<std::size_t>(draw_list->VtxBuffer.Size);
|
||||
global_index_offset += static_cast<std::size_t>(draw_list->IdxBuffer.Size);
|
||||
}
|
||||
|
||||
pending_render_frame_.total_command_count = static_cast<int>(pending_render_frame_.commands.size());
|
||||
last_draw_command_count_ = pending_render_frame_.total_command_count;
|
||||
last_vertex_count_ = pending_render_frame_.total_vertex_count;
|
||||
last_index_count_ = pending_render_frame_.total_index_count;
|
||||
last_window_count_ = pending_render_frame_.draw_list_count;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
last_draw_command_count_ = 0;
|
||||
last_vertex_count_ = 0;
|
||||
last_index_count_ = 0;
|
||||
last_window_count_ = 0;
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::configure_platform_window(std::uintptr_t native_window_handle, const int width, const int height) {
|
||||
native_window_handle_ = reinterpret_cast<HWND>(native_window_handle);
|
||||
native_window_width_ = width;
|
||||
native_window_height_ = height;
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::refresh_platform_window_metrics(const int width, const int height) {
|
||||
native_window_width_ = width;
|
||||
native_window_height_ = height;
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::enqueue_headless_gizmo_state_edit(const EditorFrameSnapshot& snapshot) {
|
||||
(void)snapshot;
|
||||
if (!headless_frame_enabled_ || headless_gizmo_override_consumed_) {
|
||||
return;
|
||||
}
|
||||
|
||||
PendingShellEdits::GizmoStateEdit edit;
|
||||
bool has_edit = false;
|
||||
|
||||
if (const char* operation_env = std::getenv("METACORE_HEADLESS_GIZMO_OPERATION")) {
|
||||
edit.has_operation = true;
|
||||
edit.operation = uppercase(operation_env);
|
||||
has_edit = true;
|
||||
}
|
||||
if (const char* space_env = std::getenv("METACORE_HEADLESS_GIZMO_SPACE")) {
|
||||
edit.has_space = true;
|
||||
edit.space = uppercase(space_env);
|
||||
has_edit = true;
|
||||
}
|
||||
if (const char* snap_env = std::getenv("METACORE_HEADLESS_GIZMO_SNAP")) {
|
||||
edit.has_snap_enabled = true;
|
||||
edit.snap_enabled = std::string(snap_env) == "1";
|
||||
has_edit = true;
|
||||
}
|
||||
if (const char* snap_values_env = std::getenv("METACORE_HEADLESS_GIZMO_SNAP_VALUES")) {
|
||||
std::istringstream stream(snap_values_env);
|
||||
float x = 1.0F;
|
||||
float y = 1.0F;
|
||||
float z = 1.0F;
|
||||
if (stream >> x >> y >> z) {
|
||||
edit.has_snap_values = true;
|
||||
edit.snap_values = {x, y, z};
|
||||
has_edit = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (has_edit) {
|
||||
pending_edits_.gizmo_state_edit = edit;
|
||||
headless_gizmo_override_consumed_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::enqueue_headless_transform_edit(const EditorFrameSnapshot& snapshot) {
|
||||
if (!headless_frame_enabled_ || !snapshot.viewport.has_selection || headless_transform_override_consumed_) {
|
||||
return;
|
||||
@ -194,20 +366,52 @@ void DearImGuiEditorShell::enqueue_headless_transform_edit(const EditorFrameSnap
|
||||
headless_transform_override_consumed_ = true;
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::render_headless_frame(const EditorFrameSnapshot& snapshot) {
|
||||
void DearImGuiEditorShell::render_imgui_frame(const EditorFrameSnapshot& snapshot) {
|
||||
#if METACORE_HAS_IMGUI
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.DisplaySize = ImVec2(1600.0F, 900.0F);
|
||||
if (headless_frame_enabled_) {
|
||||
io.DisplaySize = ImVec2(1600.0F, 900.0F);
|
||||
} else {
|
||||
io.DisplaySize = ImVec2(
|
||||
native_window_width_ > 0 ? static_cast<float>(native_window_width_) : 1280.0F,
|
||||
native_window_height_ > 0 ? static_cast<float>(native_window_height_) : 720.0F
|
||||
);
|
||||
}
|
||||
io.DeltaTime = 1.0F / 60.0F;
|
||||
|
||||
if (win32_backend_enabled_) {
|
||||
ImGui_ImplWin32_NewFrame();
|
||||
}
|
||||
ImGui::NewFrame();
|
||||
render_dockspace_frame(snapshot);
|
||||
ImGui::Render();
|
||||
fulfill_texture_requests();
|
||||
#else
|
||||
(void)snapshot;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::fulfill_texture_requests() {
|
||||
#if METACORE_HAS_IMGUI
|
||||
ImDrawData* draw_data = ImGui::GetDrawData();
|
||||
if (draw_data == nullptr || draw_data->Textures == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (ImTextureData* texture : *draw_data->Textures) {
|
||||
if (texture == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (texture->Status == ImTextureStatus_WantCreate) {
|
||||
texture->SetTexID(static_cast<ImTextureID>(next_mock_texture_id_++));
|
||||
texture->SetStatus(ImTextureStatus_OK);
|
||||
} else if (texture->Status == ImTextureStatus_WantDestroy) {
|
||||
texture->SetStatus(ImTextureStatus_Destroyed);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::render_dockspace_frame(const EditorFrameSnapshot& snapshot) {
|
||||
#if METACORE_HAS_IMGUI
|
||||
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
||||
@ -435,6 +639,41 @@ void DearImGuiEditorShell::render_viewport_panel(const EditorFrameSnapshot& snap
|
||||
ImGui::TextUnformatted(snapshot.status_text.c_str());
|
||||
ImGui::Spacing();
|
||||
ImGui::TextUnformatted("MetaCore Scene View");
|
||||
if (ImGui::Button("Translate")) {
|
||||
PendingShellEdits::GizmoStateEdit edit;
|
||||
edit.has_operation = true;
|
||||
edit.operation = "TRANSLATE";
|
||||
pending_edits_.gizmo_state_edit = edit;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Rotate")) {
|
||||
PendingShellEdits::GizmoStateEdit edit;
|
||||
edit.has_operation = true;
|
||||
edit.operation = "ROTATE";
|
||||
pending_edits_.gizmo_state_edit = edit;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button("Scale")) {
|
||||
PendingShellEdits::GizmoStateEdit edit;
|
||||
edit.has_operation = true;
|
||||
edit.operation = "SCALE";
|
||||
pending_edits_.gizmo_state_edit = edit;
|
||||
}
|
||||
|
||||
if (ImGui::Button(snapshot.viewport.gizmo_space == "Local" ? "Space: Local" : "Space: World")) {
|
||||
PendingShellEdits::GizmoStateEdit edit;
|
||||
edit.has_space = true;
|
||||
edit.space = snapshot.viewport.gizmo_space == "Local" ? "WORLD" : "LOCAL";
|
||||
pending_edits_.gizmo_state_edit = edit;
|
||||
}
|
||||
ImGui::SameLine();
|
||||
bool snap_enabled = snapshot.viewport.snap_enabled;
|
||||
if (ImGui::Checkbox("Snap", &snap_enabled)) {
|
||||
PendingShellEdits::GizmoStateEdit edit;
|
||||
edit.has_snap_enabled = true;
|
||||
edit.snap_enabled = snap_enabled;
|
||||
pending_edits_.gizmo_state_edit = edit;
|
||||
}
|
||||
|
||||
if (snapshot.viewport.has_selection) {
|
||||
ImGui::Text("Selection: %s", snapshot.viewport.selected_object_name.c_str());
|
||||
@ -511,6 +750,40 @@ void DearImGuiEditorShell::render_viewport_panel(const EditorFrameSnapshot& snap
|
||||
ImGui::TextUnformatted("ImGuizmo bridge ready");
|
||||
}
|
||||
#endif
|
||||
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows)) {
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_W, false)) {
|
||||
PendingShellEdits::GizmoStateEdit edit;
|
||||
edit.has_operation = true;
|
||||
edit.operation = "TRANSLATE";
|
||||
pending_edits_.gizmo_state_edit = edit;
|
||||
}
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_E, false)) {
|
||||
PendingShellEdits::GizmoStateEdit edit;
|
||||
edit.has_operation = true;
|
||||
edit.operation = "ROTATE";
|
||||
pending_edits_.gizmo_state_edit = edit;
|
||||
}
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_R, false)) {
|
||||
PendingShellEdits::GizmoStateEdit edit;
|
||||
edit.has_operation = true;
|
||||
edit.operation = "SCALE";
|
||||
pending_edits_.gizmo_state_edit = edit;
|
||||
}
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_Q, false)) {
|
||||
PendingShellEdits::GizmoStateEdit edit;
|
||||
edit.has_space = true;
|
||||
edit.space = snapshot.viewport.gizmo_space == "Local" ? "WORLD" : "LOCAL";
|
||||
pending_edits_.gizmo_state_edit = edit;
|
||||
}
|
||||
if (io.KeyShift && ImGui::IsKeyPressed(ImGuiKey_S, false)) {
|
||||
PendingShellEdits::GizmoStateEdit edit;
|
||||
edit.has_snap_enabled = true;
|
||||
edit.snap_enabled = !snapshot.viewport.snap_enabled;
|
||||
pending_edits_.gizmo_state_edit = edit;
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)snapshot;
|
||||
#endif
|
||||
@ -620,6 +893,12 @@ PendingShellEdits DearImGuiEditorShell::consume_pending_edits() {
|
||||
return edits;
|
||||
}
|
||||
|
||||
metacore::engine::render::UiDrawFrame DearImGuiEditorShell::consume_render_frame() {
|
||||
auto frame = std::move(pending_render_frame_);
|
||||
pending_render_frame_ = {};
|
||||
return frame;
|
||||
}
|
||||
|
||||
void DearImGuiEditorShell::set_debug_export_path(std::filesystem::path path) {
|
||||
debug_export_path_ = std::move(path);
|
||||
}
|
||||
|
||||
@ -3,6 +3,9 @@
|
||||
#include "MetaEditor/IEditorShell.h"
|
||||
|
||||
#include <filesystem>
|
||||
#include <cstdint>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace metacore::editor {
|
||||
|
||||
@ -18,12 +21,20 @@ public:
|
||||
[[nodiscard]] std::string describe_workspace() const override;
|
||||
[[nodiscard]] std::string shell_name() const override;
|
||||
[[nodiscard]] PendingShellEdits consume_pending_edits() override;
|
||||
[[nodiscard]] metacore::engine::render::UiDrawFrame consume_render_frame() override;
|
||||
|
||||
void configure_platform_window(std::uintptr_t native_window_handle, int width, int height);
|
||||
void refresh_platform_window_metrics(int width, int height);
|
||||
void set_debug_export_path(std::filesystem::path path);
|
||||
|
||||
private:
|
||||
friend LRESULT CALLBACK metacore_imgui_shell_wnd_proc(HWND hwnd, UINT message, WPARAM w_param, LPARAM l_param);
|
||||
|
||||
void initialize_imgui_context();
|
||||
void shutdown_imgui_context();
|
||||
void enqueue_headless_gizmo_state_edit(const EditorFrameSnapshot& snapshot);
|
||||
void enqueue_headless_transform_edit(const EditorFrameSnapshot& snapshot);
|
||||
void render_headless_frame(const EditorFrameSnapshot& snapshot);
|
||||
void render_imgui_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);
|
||||
@ -36,6 +47,8 @@ private:
|
||||
void render_runtime_preview_panel(const EditorFrameSnapshot& snapshot);
|
||||
void export_debug_snapshot() const;
|
||||
void export_viewport_snapshot(std::ostringstream& stream) const;
|
||||
void capture_render_frame();
|
||||
void fulfill_texture_requests();
|
||||
|
||||
bool initialized_ = false;
|
||||
EditorFrameSnapshot last_snapshot_{};
|
||||
@ -46,8 +59,17 @@ private:
|
||||
int last_window_count_ = 0;
|
||||
bool headless_frame_enabled_ = false;
|
||||
bool dock_layout_initialized_ = false;
|
||||
bool headless_gizmo_override_consumed_ = false;
|
||||
bool headless_transform_override_consumed_ = false;
|
||||
bool imgui_context_owned_ = false;
|
||||
bool win32_backend_enabled_ = false;
|
||||
HWND native_window_handle_ = nullptr;
|
||||
int native_window_width_ = 0;
|
||||
int native_window_height_ = 0;
|
||||
WNDPROC previous_wnd_proc_ = nullptr;
|
||||
PendingShellEdits pending_edits_{};
|
||||
metacore::engine::render::UiDrawFrame pending_render_frame_{};
|
||||
std::uintptr_t next_mock_texture_id_ = 1;
|
||||
};
|
||||
|
||||
} // namespace metacore::editor
|
||||
|
||||
@ -5,21 +5,128 @@
|
||||
#include "MetaCore/foundation/Log.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <windows.h>
|
||||
|
||||
namespace metacore::editor {
|
||||
|
||||
namespace {
|
||||
|
||||
using TransformGizmoService = metacore::engine::editor_core::TransformGizmoService;
|
||||
|
||||
std::optional<TransformGizmoService::Operation> parse_operation(const std::string& operation) {
|
||||
if (operation == "TRANSLATE") {
|
||||
return TransformGizmoService::Operation::Translate;
|
||||
}
|
||||
if (operation == "ROTATE") {
|
||||
return TransformGizmoService::Operation::Rotate;
|
||||
}
|
||||
if (operation == "SCALE") {
|
||||
return TransformGizmoService::Operation::Scale;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<TransformGizmoService::Space> parse_space(const std::string& space) {
|
||||
if (space == "LOCAL") {
|
||||
return TransformGizmoService::Space::Local;
|
||||
}
|
||||
if (space == "WORLD") {
|
||||
return TransformGizmoService::Space::World;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::string operation_label(const TransformGizmoService::Operation operation) {
|
||||
switch (operation) {
|
||||
case TransformGizmoService::Operation::Translate:
|
||||
return "Translate";
|
||||
case TransformGizmoService::Operation::Rotate:
|
||||
return "Rotate";
|
||||
case TransformGizmoService::Operation::Scale:
|
||||
return "Scale";
|
||||
}
|
||||
return "Translate";
|
||||
}
|
||||
|
||||
std::string space_label(const TransformGizmoService::Space space) {
|
||||
return space == TransformGizmoService::Space::World ? "World" : "Local";
|
||||
}
|
||||
|
||||
int parse_positive_int_env(const char* value, const int fallback) {
|
||||
if (value == nullptr) {
|
||||
return fallback;
|
||||
}
|
||||
try {
|
||||
const int parsed = std::stoi(value);
|
||||
return parsed > 0 ? parsed : fallback;
|
||||
} catch (...) {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
bool has_explicit_env(const char* value) {
|
||||
return value != nullptr && value[0] != '\0';
|
||||
}
|
||||
|
||||
bool env_enabled(const char* name) {
|
||||
const char* value = std::getenv(name);
|
||||
return value != nullptr && std::string(value) == "1";
|
||||
}
|
||||
|
||||
std::string build_panda_hud_text(const EditorFrameSnapshot& snapshot, const int ui_frame_submissions) {
|
||||
std::ostringstream stream;
|
||||
stream << "MetaEditor\n";
|
||||
stream << "Selection: "
|
||||
<< (snapshot.viewport.has_selection ? snapshot.viewport.selected_object_name : "None") << "\n";
|
||||
stream << "Gizmo: " << snapshot.viewport.gizmo_operation
|
||||
<< " / " << snapshot.viewport.gizmo_space
|
||||
<< " / Snap " << (snapshot.viewport.snap_enabled ? "On" : "Off") << "\n";
|
||||
stream << "Position: "
|
||||
<< snapshot.viewport.object_position[0] << ", "
|
||||
<< snapshot.viewport.object_position[1] << ", "
|
||||
<< snapshot.viewport.object_position[2] << "\n";
|
||||
stream << "Panels: Hierarchy | Viewport | Inspector | Console\n";
|
||||
stream << "Controls: W/E/R mode, Q space, Shift+S snap\n";
|
||||
stream << "Move: Arrows + PageUp/PageDown | Rotate: Arrows | Scale: +/-\n";
|
||||
stream << "UI Packets: " << ui_frame_submissions;
|
||||
return stream.str();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
EditorApplication::EditorApplication() = default;
|
||||
|
||||
bool EditorApplication::initialize(const std::filesystem::path& project_root) {
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::initialize enter\n";
|
||||
project_root_ = project_root;
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::initialize after project_root\n";
|
||||
editor_shell_ = create_default_editor_shell();
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::initialize after shell create\n";
|
||||
const auto shell_debug_path = project_root_ / "Library" / "Layout" / (editor_shell_->shell_name() + std::string("_snapshot.txt"));
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::initialize after shell debug path\n";
|
||||
editor_shell_->set_debug_export_path(shell_debug_path);
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::initialize before platform\n";
|
||||
if (!platform_.initialize("MetaEditor")) {
|
||||
return false;
|
||||
}
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::initialize after platform\n";
|
||||
render_backend_.initialize();
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::initialize after render backend\n";
|
||||
render_backend_.bind_window_framework(platform_.window_framework_handle());
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::initialize after callback bind gate\n";
|
||||
if (auto* dearimgui_shell = dynamic_cast<DearImGuiEditorShell*>(editor_shell_.get())) {
|
||||
dearimgui_shell->configure_platform_window(
|
||||
platform_.native_window_handle(),
|
||||
platform_.window_width(),
|
||||
platform_.window_height()
|
||||
);
|
||||
}
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::initialize after shell window configure\n";
|
||||
editor_shell_->initialize();
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::initialize after shell initialize\n";
|
||||
|
||||
if (!std::filesystem::exists(project_root_ / "MetaCore.project.json")) {
|
||||
project_service_.create_project(project_root_, "MetaCoreSample");
|
||||
@ -28,6 +135,7 @@ bool EditorApplication::initialize(const std::filesystem::path& project_root) {
|
||||
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_);
|
||||
ensure_demo_scene_content();
|
||||
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_);
|
||||
@ -35,6 +143,7 @@ bool EditorApplication::initialize(const std::filesystem::path& project_root) {
|
||||
if (!scene_.objects.empty()) {
|
||||
selection_service_.select(scene_.objects.front().id);
|
||||
}
|
||||
ensure_demo_scene_content();
|
||||
|
||||
session_.set_status_text("Editor initialized");
|
||||
session_.add_console_message("System", "MetaEditor initialized");
|
||||
@ -56,85 +165,87 @@ bool EditorApplication::initialize(const std::filesystem::path& project_root) {
|
||||
"Panda window bootstrap: enabled=" + std::string(platform_.window_enabled() ? "true" : "false") +
|
||||
" opened=" + std::string(platform_.window_opened() ? "true" : "false")
|
||||
);
|
||||
if (platform_.window_opened()) {
|
||||
foundation::log(
|
||||
foundation::LogLevel::Info,
|
||||
"Panda native window: hwnd=0x" +
|
||||
[&]() {
|
||||
std::ostringstream stream;
|
||||
stream << std::hex << platform_.native_window_handle();
|
||||
return stream.str();
|
||||
}() +
|
||||
" size=" + std::to_string(platform_.window_width()) + "x" + std::to_string(platform_.window_height())
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int EditorApplication::run() {
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::run enter\n";
|
||||
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_
|
||||
);
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::run after start log\n";
|
||||
ensure_runtime_preview_root();
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::run after runtime preview root\n";
|
||||
metacore::engine::editor_core::SceneCommands::focus_selected_object(session_, selection_service_, viewport_service_);
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::run after focus selected\n";
|
||||
|
||||
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_);
|
||||
|
||||
const int frame_budget = resolve_frame_budget();
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::run after frame budget\n";
|
||||
for (int frame_index = 0; should_continue_running(frame_index, frame_budget); ++frame_index) {
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::run frame begin\n";
|
||||
if (env_enabled("METACORE_ENABLE_PANDA_UI_DRAW_CALLBACK") && !render_backend_.draw_callback_bound() && frame_index > 0) {
|
||||
render_backend_.bind_primary_display_region(platform_.primary_display_region_handle());
|
||||
foundation::log(foundation::LogLevel::Info, "Panda UI draw callback binding requested");
|
||||
}
|
||||
poll_window_shortcuts();
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::run after shortcuts\n";
|
||||
if (auto* dearimgui_shell = dynamic_cast<DearImGuiEditorShell*>(editor_shell_.get())) {
|
||||
dearimgui_shell->refresh_platform_window_metrics(platform_.window_width(), platform_.window_height());
|
||||
}
|
||||
auto snapshot = presenter_.build_snapshot(scene_, asset_database_, selection_service_, gizmo_service_, viewport_service_, session_);
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::run after snapshot A\n";
|
||||
editor_shell_->consume_snapshot(snapshot);
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::run after shell consume A\n";
|
||||
render_backend_.set_ui_draw_frame(editor_shell_->consume_render_frame());
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::run after ui frame A\n";
|
||||
render_backend_.set_demo_selection_transform(
|
||||
snapshot.viewport.has_selection,
|
||||
snapshot.viewport.object_position,
|
||||
snapshot.viewport.object_rotation,
|
||||
snapshot.viewport.object_scale
|
||||
);
|
||||
render_backend_.set_demo_camera(snapshot.viewport.camera_position, snapshot.viewport.object_position);
|
||||
render_backend_.set_demo_hud_text(build_panda_hud_text(snapshot, render_backend_.submitted_ui_frame_count()));
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::run after backend demo sync A\n";
|
||||
last_snapshot_ = snapshot;
|
||||
apply_shell_edits();
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::run after shell edits\n";
|
||||
snapshot = presenter_.build_snapshot(scene_, asset_database_, selection_service_, gizmo_service_, viewport_service_, session_);
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::run after snapshot B\n";
|
||||
editor_shell_->consume_snapshot(snapshot);
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::run after shell consume B\n";
|
||||
render_backend_.set_ui_draw_frame(editor_shell_->consume_render_frame());
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::run after ui frame B\n";
|
||||
render_backend_.set_demo_selection_transform(
|
||||
snapshot.viewport.has_selection,
|
||||
snapshot.viewport.object_position,
|
||||
snapshot.viewport.object_rotation,
|
||||
snapshot.viewport.object_scale
|
||||
);
|
||||
render_backend_.set_demo_camera(snapshot.viewport.camera_position, snapshot.viewport.object_position);
|
||||
render_backend_.set_demo_hud_text(build_panda_hud_text(snapshot, render_backend_.submitted_ui_frame_count()));
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::run after backend demo sync B\n";
|
||||
last_snapshot_ = snapshot;
|
||||
platform_.pump_frame();
|
||||
std::cerr << "[MetaCore][Trace] EditorApplication::run after pump\n";
|
||||
}
|
||||
|
||||
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);
|
||||
persist_editor_state();
|
||||
if (last_snapshot_.has_value()) {
|
||||
log_frame_summary(*last_snapshot_);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@ -149,4 +260,327 @@ void EditorApplication::shutdown() {
|
||||
foundation::log(foundation::LogLevel::Info, "MetaEditor shutdown complete");
|
||||
}
|
||||
|
||||
void EditorApplication::ensure_runtime_preview_root() {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorApplication::ensure_demo_scene_content() {
|
||||
const auto has_named_object = [&](const std::string& name) {
|
||||
return std::any_of(scene_.objects.begin(), scene_.objects.end(), [&](const auto& object) {
|
||||
return object.name == name;
|
||||
});
|
||||
};
|
||||
const auto has_primary_camera = std::any_of(scene_.objects.begin(), scene_.objects.end(), [](const auto& object) {
|
||||
return object.has_camera && object.camera.primary;
|
||||
});
|
||||
const auto has_light = std::any_of(scene_.objects.begin(), scene_.objects.end(), [](const auto& object) {
|
||||
return object.has_light;
|
||||
});
|
||||
const auto has_mesh = std::any_of(scene_.objects.begin(), scene_.objects.end(), [](const auto& object) {
|
||||
return object.has_mesh_renderer;
|
||||
});
|
||||
|
||||
if (!has_primary_camera) {
|
||||
auto& camera = scene_service_.create_object(scene_, "Main Camera");
|
||||
camera.has_camera = true;
|
||||
camera.camera.primary = true;
|
||||
camera.transform.position = {0.0F, -10.0F, 3.0F};
|
||||
}
|
||||
if (!has_light) {
|
||||
auto& light = scene_service_.create_object(scene_, "Directional Light");
|
||||
light.has_light = true;
|
||||
light.light.type = metacore::engine::scene::LightComponent::Type::Directional;
|
||||
}
|
||||
if (!has_mesh) {
|
||||
auto& cube = scene_service_.create_object(scene_, "Demo Cube");
|
||||
cube.has_mesh_renderer = true;
|
||||
cube.mesh_renderer.mesh_asset_id = "builtin://wire-cube";
|
||||
cube.mesh_renderer.material_asset_id = "builtin://editor-default";
|
||||
cube.transform.position = {0.0F, 0.0F, 0.0F};
|
||||
cube.transform.scale = {1.0F, 1.0F, 1.0F};
|
||||
}
|
||||
if (!has_named_object("UI Root")) {
|
||||
auto& ui_root = scene_service_.create_object(scene_, "UI Root");
|
||||
ui_root.has_ui_document = true;
|
||||
ui_root.ui_document.document_path = "Assets/UI/main_menu.rml";
|
||||
ui_root.ui_document.stylesheet_path = "Assets/UI/main_menu.rcss";
|
||||
}
|
||||
|
||||
for (const auto& object : scene_.objects) {
|
||||
if (object.has_mesh_renderer) {
|
||||
selection_service_.select(object.id);
|
||||
viewport_service_.focus_selection(object.id);
|
||||
viewport_service_.set_camera_position({0.0F, -10.0F, 3.0F});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorApplication::apply_shell_edits() {
|
||||
for (int pass = 0; pass < 2; ++pass) {
|
||||
const PendingShellEdits shell_edits = editor_shell_->consume_pending_edits();
|
||||
bool changed = false;
|
||||
|
||||
if (shell_edits.gizmo_state_edit.has_value()) {
|
||||
const auto& edit = *shell_edits.gizmo_state_edit;
|
||||
if (edit.has_operation) {
|
||||
if (const auto operation = parse_operation(edit.operation)) {
|
||||
gizmo_service_.set_operation(*operation);
|
||||
session_.set_status_text("Gizmo operation: " + operation_label(*operation));
|
||||
session_.add_console_message("Viewport", "Gizmo operation set to " + operation_label(*operation));
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (edit.has_space) {
|
||||
if (const auto space = parse_space(edit.space)) {
|
||||
gizmo_service_.set_space(*space);
|
||||
session_.set_status_text("Gizmo space: " + space_label(*space));
|
||||
session_.add_console_message("Viewport", "Gizmo space set to " + space_label(*space));
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (edit.has_snap_enabled) {
|
||||
gizmo_service_.set_snap_enabled(edit.snap_enabled);
|
||||
session_.set_status_text(std::string("Gizmo snap: ") + (edit.snap_enabled ? "On" : "Off"));
|
||||
session_.add_console_message("Viewport", std::string("Gizmo snap ") + (edit.snap_enabled ? "enabled" : "disabled"));
|
||||
changed = true;
|
||||
}
|
||||
if (edit.has_snap_values) {
|
||||
gizmo_service_.set_snap_values(edit.snap_values);
|
||||
session_.add_console_message(
|
||||
"Viewport",
|
||||
"Gizmo snap values updated to " +
|
||||
std::to_string(edit.snap_values[0]) + ", " +
|
||||
std::to_string(edit.snap_values[1]) + ", " +
|
||||
std::to_string(edit.snap_values[2])
|
||||
);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (last_snapshot_.has_value() &&
|
||||
shell_edits.transform_edit.has_value() &&
|
||||
shell_edits.transform_edit->entity_id == last_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
|
||||
);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!changed) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EditorApplication::poll_window_shortcuts() {
|
||||
if (!platform_.window_opened()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto pressed = [&](const int virtual_key) {
|
||||
const bool is_down = (GetAsyncKeyState(virtual_key) & 0x8000) != 0;
|
||||
const bool was_down = key_down_[virtual_key];
|
||||
key_down_[virtual_key] = is_down;
|
||||
return is_down && !was_down;
|
||||
};
|
||||
const auto down = [&](const int virtual_key) {
|
||||
const bool is_down = (GetAsyncKeyState(virtual_key) & 0x8000) != 0;
|
||||
key_down_[virtual_key] = is_down;
|
||||
return is_down;
|
||||
};
|
||||
|
||||
if (pressed('W')) {
|
||||
gizmo_service_.set_operation(TransformGizmoService::Operation::Translate);
|
||||
}
|
||||
if (pressed('E')) {
|
||||
gizmo_service_.set_operation(TransformGizmoService::Operation::Rotate);
|
||||
}
|
||||
if (pressed('R')) {
|
||||
gizmo_service_.set_operation(TransformGizmoService::Operation::Scale);
|
||||
}
|
||||
if (pressed('Q')) {
|
||||
gizmo_service_.set_space(
|
||||
gizmo_service_.space() == TransformGizmoService::Space::Local
|
||||
? TransformGizmoService::Space::World
|
||||
: TransformGizmoService::Space::Local
|
||||
);
|
||||
}
|
||||
if (down(VK_SHIFT) && pressed('S')) {
|
||||
gizmo_service_.set_snap_enabled(!gizmo_service_.snap_enabled());
|
||||
}
|
||||
|
||||
const auto selected_id = selection_service_.selection();
|
||||
if (!selected_id.has_value()) {
|
||||
return;
|
||||
}
|
||||
const auto selected_object = scene_service_.find_object(scene_, *selected_id);
|
||||
if (!selected_object.has_value()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto transform = selected_object->get().transform;
|
||||
bool changed = false;
|
||||
const auto step = gizmo_service_.snap_enabled() ? gizmo_service_.snap_values()[0] : 0.1F;
|
||||
const auto rotate_step = gizmo_service_.snap_enabled() ? gizmo_service_.snap_values()[0] : 5.0F;
|
||||
const auto scale_step = gizmo_service_.snap_enabled() ? 0.25F : 0.05F;
|
||||
|
||||
switch (gizmo_service_.operation()) {
|
||||
case TransformGizmoService::Operation::Translate:
|
||||
if (down(VK_LEFT)) {
|
||||
transform.position[0] -= step;
|
||||
changed = true;
|
||||
}
|
||||
if (down(VK_RIGHT)) {
|
||||
transform.position[0] += step;
|
||||
changed = true;
|
||||
}
|
||||
if (down(VK_UP)) {
|
||||
transform.position[1] += step;
|
||||
changed = true;
|
||||
}
|
||||
if (down(VK_DOWN)) {
|
||||
transform.position[1] -= step;
|
||||
changed = true;
|
||||
}
|
||||
if (down(VK_PRIOR)) {
|
||||
transform.position[2] += step;
|
||||
changed = true;
|
||||
}
|
||||
if (down(VK_NEXT)) {
|
||||
transform.position[2] -= step;
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
case TransformGizmoService::Operation::Rotate:
|
||||
if (down(VK_LEFT)) {
|
||||
transform.rotation[2] -= rotate_step;
|
||||
changed = true;
|
||||
}
|
||||
if (down(VK_RIGHT)) {
|
||||
transform.rotation[2] += rotate_step;
|
||||
changed = true;
|
||||
}
|
||||
if (down(VK_UP)) {
|
||||
transform.rotation[0] += rotate_step;
|
||||
changed = true;
|
||||
}
|
||||
if (down(VK_DOWN)) {
|
||||
transform.rotation[0] -= rotate_step;
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
case TransformGizmoService::Operation::Scale:
|
||||
if (down(VK_OEM_PLUS) || down(VK_ADD)) {
|
||||
for (float& axis : transform.scale) {
|
||||
axis += scale_step;
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
if (down(VK_OEM_MINUS) || down(VK_SUBTRACT)) {
|
||||
for (float& axis : transform.scale) {
|
||||
axis = (std::max)(0.1F, axis - scale_step);
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
metacore::engine::editor_core::SceneCommands::apply_transform_to_selected_object(
|
||||
command_service_,
|
||||
session_,
|
||||
selection_service_,
|
||||
scene_service_,
|
||||
scene_,
|
||||
transform
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void EditorApplication::log_frame_summary(const EditorFrameSnapshot& snapshot) const {
|
||||
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, "Platform frames: " + std::to_string(platform_.frame_count()));
|
||||
foundation::log(
|
||||
foundation::LogLevel::Info,
|
||||
"UI render handoff: submitted=" + std::to_string(render_backend_.submitted_ui_frame_count()) +
|
||||
" callbackBound=" + std::string(render_backend_.draw_callback_bound() ? "true" : "false") +
|
||||
" drawCallbacks=" + std::to_string(render_backend_.observed_draw_callback_count())
|
||||
);
|
||||
foundation::log(foundation::LogLevel::Info, editor_shell_->describe_workspace());
|
||||
|
||||
for (const auto& message : snapshot.console) {
|
||||
foundation::log(foundation::LogLevel::Info, "[" + message.category + "] " + message.text);
|
||||
}
|
||||
}
|
||||
|
||||
int EditorApplication::resolve_frame_budget() const {
|
||||
const char* frame_budget_env = std::getenv("METACORE_EDITOR_FRAME_COUNT");
|
||||
if (platform_.window_opened()) {
|
||||
if (!has_explicit_env(frame_budget_env)) {
|
||||
return -1;
|
||||
}
|
||||
return parse_positive_int_env(frame_budget_env, -1);
|
||||
}
|
||||
return parse_positive_int_env(frame_budget_env, 1);
|
||||
}
|
||||
|
||||
bool EditorApplication::should_continue_running(const int frame_index, const int frame_budget) const {
|
||||
if (platform_.should_close()) {
|
||||
return false;
|
||||
}
|
||||
if (frame_budget < 0) {
|
||||
return true;
|
||||
}
|
||||
return frame_index < frame_budget;
|
||||
}
|
||||
|
||||
void EditorApplication::persist_editor_state() const {
|
||||
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");
|
||||
}
|
||||
|
||||
} // namespace metacore::editor
|
||||
|
||||
@ -21,6 +21,8 @@
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <array>
|
||||
|
||||
namespace metacore::editor {
|
||||
|
||||
@ -33,6 +35,15 @@ public:
|
||||
void shutdown();
|
||||
|
||||
private:
|
||||
void ensure_runtime_preview_root();
|
||||
void ensure_demo_scene_content();
|
||||
void apply_shell_edits();
|
||||
void poll_window_shortcuts();
|
||||
void log_frame_summary(const EditorFrameSnapshot& snapshot) const;
|
||||
[[nodiscard]] int resolve_frame_budget() const;
|
||||
[[nodiscard]] bool should_continue_running(int frame_index, int frame_budget) const;
|
||||
void persist_editor_state() const;
|
||||
|
||||
std::filesystem::path project_root_;
|
||||
metacore::platform::panda::PandaPlatformContext platform_;
|
||||
metacore::platform::panda::PandaRenderBackend render_backend_;
|
||||
@ -51,8 +62,10 @@ private:
|
||||
metacore::engine::editor_core::EditorLayoutState layout_state_{};
|
||||
metacore::engine::scene::Project project_{};
|
||||
metacore::engine::scene::Scene scene_{};
|
||||
std::optional<EditorFrameSnapshot> last_snapshot_{};
|
||||
EditorPresenter presenter_{};
|
||||
std::unique_ptr<IEditorShell> editor_shell_{};
|
||||
std::array<bool, 256> key_down_{};
|
||||
};
|
||||
|
||||
} // namespace metacore::editor
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "MetaEditor/EditorPresenter.h"
|
||||
#include "MetaCore/engine/render/RenderTypes.h"
|
||||
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
@ -11,6 +12,17 @@
|
||||
namespace metacore::editor {
|
||||
|
||||
struct PendingShellEdits {
|
||||
struct GizmoStateEdit {
|
||||
std::string operation;
|
||||
std::string space;
|
||||
std::array<float, 3> snap_values{1.0F, 1.0F, 1.0F};
|
||||
bool has_operation = false;
|
||||
bool has_space = false;
|
||||
bool has_snap_enabled = false;
|
||||
bool snap_enabled = false;
|
||||
bool has_snap_values = false;
|
||||
};
|
||||
|
||||
struct TransformEdit {
|
||||
std::string entity_id;
|
||||
std::array<float, 3> position{0.0F, 0.0F, 0.0F};
|
||||
@ -18,6 +30,7 @@ struct PendingShellEdits {
|
||||
std::array<float, 3> scale{1.0F, 1.0F, 1.0F};
|
||||
};
|
||||
|
||||
std::optional<GizmoStateEdit> gizmo_state_edit;
|
||||
std::optional<TransformEdit> transform_edit;
|
||||
};
|
||||
|
||||
@ -35,6 +48,7 @@ public:
|
||||
[[nodiscard]] virtual std::string describe_workspace() const = 0;
|
||||
[[nodiscard]] virtual std::string shell_name() const = 0;
|
||||
[[nodiscard]] virtual PendingShellEdits consume_pending_edits() = 0;
|
||||
[[nodiscard]] virtual metacore::engine::render::UiDrawFrame consume_render_frame() = 0;
|
||||
virtual void set_debug_export_path(std::filesystem::path path) = 0;
|
||||
};
|
||||
|
||||
|
||||
@ -94,6 +94,10 @@ PendingShellEdits NullEditorShell::consume_pending_edits() {
|
||||
return {};
|
||||
}
|
||||
|
||||
metacore::engine::render::UiDrawFrame NullEditorShell::consume_render_frame() {
|
||||
return {};
|
||||
}
|
||||
|
||||
void NullEditorShell::set_debug_export_path(std::filesystem::path path) {
|
||||
debug_export_path_ = std::move(path);
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ public:
|
||||
[[nodiscard]] std::string describe_workspace() const override;
|
||||
[[nodiscard]] std::string shell_name() const override;
|
||||
[[nodiscard]] PendingShellEdits consume_pending_edits() override;
|
||||
[[nodiscard]] metacore::engine::render::UiDrawFrame consume_render_frame() override;
|
||||
void set_debug_export_path(std::filesystem::path path) override;
|
||||
|
||||
private:
|
||||
|
||||
@ -75,3 +75,11 @@ $env:METACORE_ENABLE_HEADLESS_IMGUI = "1"
|
||||
```
|
||||
|
||||
This mode is still treated as experimental and is not part of the stable runtime path yet.
|
||||
|
||||
Optional runtime toggles:
|
||||
|
||||
- enable Panda window bootstrap: `METACORE_ENABLE_PANDA_WINDOW=1`
|
||||
- limit editor frame count for automation/headless runs: `METACORE_EDITOR_FRAME_COUNT=<n>`
|
||||
|
||||
When Panda window bootstrap is enabled and `METACORE_EDITOR_FRAME_COUNT` is not set, `MetaEditor` now keeps running
|
||||
until the window is closed. For automation, set an explicit frame count to force exit after a bounded number of frames.
|
||||
|
||||
@ -83,6 +83,8 @@ Current runtime behavior:
|
||||
- `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
|
||||
- when `METACORE_ENABLE_PANDA_WINDOW=1` and no frame budget is provided, `MetaEditor` stays open until the Panda window closes
|
||||
- `METACORE_EDITOR_FRAME_COUNT` can be set to bound editor runtime during probes and CI-like runs
|
||||
|
||||
## Optional GUI feature toggles
|
||||
|
||||
|
||||
@ -2,10 +2,13 @@
|
||||
#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/TransformGizmoService.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 "MetaCore/platform/panda/PandaPlatformContext.h"
|
||||
#include "MetaCore/platform/panda/PandaRenderBackend.h"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
@ -29,7 +32,10 @@ int main() {
|
||||
using metacore::engine::editor_core::EditorSession;
|
||||
using metacore::engine::editor_core::SceneCommands;
|
||||
using metacore::engine::editor_core::SelectionService;
|
||||
using metacore::engine::editor_core::TransformGizmoService;
|
||||
using metacore::engine::editor_core::ViewportService;
|
||||
using metacore::platform::panda::PandaPlatformContext;
|
||||
using metacore::platform::panda::PandaRenderBackend;
|
||||
using metacore::engine::scene::Project;
|
||||
using metacore::engine::scene::ProjectService;
|
||||
using metacore::engine::scene::Scene;
|
||||
@ -72,6 +78,41 @@ int main() {
|
||||
commands.redo();
|
||||
expect(value == 1, "command should redo");
|
||||
|
||||
TransformGizmoService gizmo;
|
||||
gizmo.set_operation(TransformGizmoService::Operation::Rotate);
|
||||
gizmo.set_space(TransformGizmoService::Space::World);
|
||||
gizmo.set_snap_enabled(true);
|
||||
gizmo.set_snap_values({15.0F, 15.0F, 15.0F});
|
||||
expect(gizmo.operation() == TransformGizmoService::Operation::Rotate, "gizmo operation should persist");
|
||||
expect(gizmo.space() == TransformGizmoService::Space::World, "gizmo space should persist");
|
||||
expect(gizmo.snap_enabled(), "gizmo snap should persist");
|
||||
expect(gizmo.snap_values()[0] == 15.0F, "gizmo snap values should persist");
|
||||
|
||||
PandaPlatformContext platform;
|
||||
expect(platform.initialize("MetaCoreTests"), "platform should initialize");
|
||||
platform.pump_frame();
|
||||
platform.pump_frame();
|
||||
expect(platform.frame_count() == 2, "platform frame count should increment");
|
||||
platform.request_close();
|
||||
expect(platform.should_close(), "platform close request should persist");
|
||||
platform.shutdown();
|
||||
|
||||
PandaRenderBackend render_backend;
|
||||
expect(render_backend.initialize(), "render backend should initialize");
|
||||
metacore::engine::render::UiDrawFrame ui_frame;
|
||||
ui_frame.display_size = {1280.0F, 720.0F};
|
||||
ui_frame.total_command_count = 2;
|
||||
ui_frame.total_vertex_count = 6;
|
||||
ui_frame.total_index_count = 9;
|
||||
ui_frame.vertices.push_back({{10.0F, 20.0F}, {0.0F, 0.0F}, 0xffffffffu});
|
||||
ui_frame.indices.push_back(0);
|
||||
ui_frame.commands.push_back({{0.0F, 0.0F, 100.0F, 50.0F}, 0, 3, 0, 0});
|
||||
render_backend.set_ui_draw_frame(ui_frame);
|
||||
expect(render_backend.ui_draw_frame().total_command_count == 2, "ui draw frame command count should persist");
|
||||
expect(render_backend.ui_draw_frame().vertices.size() == 1, "ui draw frame vertices should persist");
|
||||
expect(render_backend.submitted_ui_frame_count() == 1, "ui draw frame submissions should be tracked");
|
||||
render_backend.shutdown();
|
||||
|
||||
EditorSession session;
|
||||
SelectionService selection;
|
||||
ViewportService viewport;
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <cstdlib>
|
||||
|
||||
#if METACORE_HAS_PANDA3D
|
||||
#include <pandaFramework.h>
|
||||
@ -14,6 +15,19 @@ void log_step(const std::string& message) {
|
||||
std::cout << "[MetaCoreWindowProbe] " << message << std::endl;
|
||||
}
|
||||
|
||||
int resolve_frame_budget() {
|
||||
const char* value = std::getenv("METACORE_WINDOW_PROBE_FRAME_COUNT");
|
||||
if (value == nullptr || value[0] == '\0') {
|
||||
return -1;
|
||||
}
|
||||
try {
|
||||
const int parsed = std::stoi(value);
|
||||
return parsed > 0 ? parsed : -1;
|
||||
} catch (...) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
@ -49,8 +63,22 @@ int main() {
|
||||
}
|
||||
|
||||
log_step("Window opened successfully");
|
||||
framework.do_frame(nullptr);
|
||||
log_step("Frame pumped successfully");
|
||||
const int frame_budget = resolve_frame_budget();
|
||||
if (frame_budget < 0) {
|
||||
log_step("Running until the window is closed manually");
|
||||
while (window->get_graphics_window() != nullptr && !window->get_graphics_window()->is_closed()) {
|
||||
framework.do_frame(nullptr);
|
||||
}
|
||||
} else {
|
||||
log_step("Running for " + std::to_string(frame_budget) + " frame(s)");
|
||||
for (int frame = 0; frame < frame_budget; ++frame) {
|
||||
framework.do_frame(nullptr);
|
||||
if (window->get_graphics_window() != nullptr && window->get_graphics_window()->is_closed()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
log_step("Frame loop finished");
|
||||
|
||||
framework.close_window(window);
|
||||
log_step("Window closed successfully");
|
||||
|
||||
222
tools/PandaCubeDemo.cpp
Normal file
222
tools/PandaCubeDemo.cpp
Normal file
@ -0,0 +1,222 @@
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <cmath>
|
||||
|
||||
#if METACORE_HAS_PANDA3D
|
||||
#include <graphicsWindow.h>
|
||||
#include <keyboardButton.h>
|
||||
#include <lineSegs.h>
|
||||
#include <load_prc_file.h>
|
||||
#include <luse.h>
|
||||
#include <nodePath.h>
|
||||
#include <pandaFramework.h>
|
||||
#include <transformState.h>
|
||||
#include <windowProperties.h>
|
||||
#include <filename.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
void log_step(const std::string& message) {
|
||||
std::cout << "[PandaCubeDemo] " << message << std::endl;
|
||||
}
|
||||
|
||||
#if METACORE_HAS_PANDA3D
|
||||
void configure_runtime_environment() {
|
||||
const auto panda_bin = std::filesystem::path(METACORE_PANDA3D_ROOT_PATH) / "bin";
|
||||
const auto panda_etc = std::filesystem::path(METACORE_PANDA3D_ROOT_PATH) / "etc";
|
||||
if (std::filesystem::exists(panda_bin)) {
|
||||
SetDllDirectoryW(panda_bin.wstring().c_str());
|
||||
log_step("DLL search path set to " + panda_bin.string());
|
||||
|
||||
const std::wstring existing_path = [&]() {
|
||||
const DWORD required = GetEnvironmentVariableW(L"PATH", nullptr, 0);
|
||||
if (required == 0) {
|
||||
return std::wstring{};
|
||||
}
|
||||
std::wstring buffer(required, L'\0');
|
||||
GetEnvironmentVariableW(L"PATH", buffer.data(), required);
|
||||
if (!buffer.empty() && buffer.back() == L'\0') {
|
||||
buffer.pop_back();
|
||||
}
|
||||
return buffer;
|
||||
}();
|
||||
const std::wstring new_path = panda_bin.wstring() + L";" + existing_path;
|
||||
SetEnvironmentVariableW(L"PATH", new_path.c_str());
|
||||
log_step("PATH prefixed with Panda bin");
|
||||
}
|
||||
if (std::filesystem::exists(panda_etc)) {
|
||||
SetEnvironmentVariableW(L"PANDA_PRC_DIR", panda_etc.wstring().c_str());
|
||||
log_step("PANDA_PRC_DIR set to " + panda_etc.string());
|
||||
}
|
||||
|
||||
wchar_t exe_path_buffer[MAX_PATH];
|
||||
const DWORD path_length = GetModuleFileNameW(nullptr, exe_path_buffer, MAX_PATH);
|
||||
if (path_length > 0 && path_length < MAX_PATH) {
|
||||
std::filesystem::path exe_path(exe_path_buffer);
|
||||
std::filesystem::current_path(exe_path.parent_path());
|
||||
log_step("Working directory set to " + exe_path.parent_path().string());
|
||||
}
|
||||
}
|
||||
|
||||
void configure_local_prc() {
|
||||
wchar_t exe_path_buffer[MAX_PATH];
|
||||
const DWORD path_length = GetModuleFileNameW(nullptr, exe_path_buffer, MAX_PATH);
|
||||
if (path_length == 0 || path_length >= MAX_PATH) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::filesystem::path exe_dir = std::filesystem::path(exe_path_buffer).parent_path();
|
||||
const std::filesystem::path prc_path = exe_dir / "PandaCubeDemo.prc";
|
||||
const std::filesystem::path panda_bin = std::filesystem::path(METACORE_PANDA3D_ROOT_PATH) / "bin";
|
||||
const std::string panda_bin_forward = panda_bin.generic_string();
|
||||
std::ofstream prc_file(prc_path, std::ios::trunc);
|
||||
prc_file
|
||||
<< "plugin-path " << panda_bin_forward << "\n"
|
||||
<< "load-display pandagl\n"
|
||||
<< "aux-display p3tinydisplay\n"
|
||||
<< "window-type onscreen\n"
|
||||
<< "win-size 1280 720\n"
|
||||
<< "sync-video #f\n"
|
||||
<< "audio-library-name null\n";
|
||||
prc_file.close();
|
||||
|
||||
log_step("Local PRC written to " + prc_path.string());
|
||||
load_prc_file(Filename::from_os_specific(prc_path.string()));
|
||||
log_step("Local PRC loaded");
|
||||
}
|
||||
|
||||
NodePath create_colored_cube(const NodePath& parent) {
|
||||
LineSegs segments("demo-cube");
|
||||
segments.set_thickness(2.5F);
|
||||
segments.set_color(0.85F, 0.9F, 1.0F, 1.0F);
|
||||
|
||||
const LPoint3f corners[] = {
|
||||
{-0.5F, -0.5F, -0.5F},
|
||||
{0.5F, -0.5F, -0.5F},
|
||||
{0.5F, 0.5F, -0.5F},
|
||||
{-0.5F, 0.5F, -0.5F},
|
||||
{-0.5F, -0.5F, 0.5F},
|
||||
{0.5F, -0.5F, 0.5F},
|
||||
{0.5F, 0.5F, 0.5F},
|
||||
{-0.5F, 0.5F, 0.5F},
|
||||
};
|
||||
const int edges[][2] = {
|
||||
{0, 1}, {1, 2}, {2, 3}, {3, 0},
|
||||
{4, 5}, {5, 6}, {6, 7}, {7, 4},
|
||||
{0, 4}, {1, 5}, {2, 6}, {3, 7},
|
||||
};
|
||||
|
||||
for (const auto& edge : edges) {
|
||||
segments.move_to(corners[edge[0]]);
|
||||
segments.draw_to(corners[edge[1]]);
|
||||
}
|
||||
|
||||
return parent.attach_new_node(segments.create());
|
||||
}
|
||||
|
||||
float movement_axis(const SHORT positive_key, const SHORT negative_key) {
|
||||
const float positive = (GetAsyncKeyState(positive_key) & 0x8000) != 0 ? 1.0F : 0.0F;
|
||||
const float negative = (GetAsyncKeyState(negative_key) & 0x8000) != 0 ? 1.0F : 0.0F;
|
||||
return positive - negative;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
#if METACORE_HAS_PANDA3D
|
||||
log_step("Starting Panda3D C++ cube demo.");
|
||||
configure_runtime_environment();
|
||||
configure_local_prc();
|
||||
|
||||
PandaFramework framework;
|
||||
log_step("Calling open_framework()");
|
||||
framework.open_framework();
|
||||
log_step("open_framework() succeeded");
|
||||
framework.set_window_title("Panda3D Cube Demo");
|
||||
|
||||
WindowProperties props;
|
||||
props.set_title("Panda3D Cube Demo");
|
||||
props.set_size(1280, 720);
|
||||
|
||||
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("open_window() succeeded");
|
||||
|
||||
if (GraphicsWindow* graphics_window = window->get_graphics_window(); graphics_window != nullptr) {
|
||||
graphics_window->set_clear_color(LColor(0.06F, 0.07F, 0.10F, 1.0F));
|
||||
}
|
||||
|
||||
NodePath render_root = window->get_render();
|
||||
NodePath cube = create_colored_cube(render_root);
|
||||
cube.set_scale(1.5F);
|
||||
|
||||
NodePath camera = window->get_camera_group();
|
||||
float camera_x = 0.0F;
|
||||
float camera_y = -8.0F;
|
||||
float camera_z = 2.0F;
|
||||
float heading = 0.0F;
|
||||
float pitch = -10.0F;
|
||||
|
||||
log_step("Controls: WASD move, RF up/down, arrow keys look, Esc closes.");
|
||||
|
||||
while (window->get_graphics_window() != nullptr && !window->get_graphics_window()->is_closed()) {
|
||||
if ((GetAsyncKeyState(VK_ESCAPE) & 0x8000) != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
const float move_speed = ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0) ? 0.18F : 0.08F;
|
||||
const float turn_speed = 1.2F;
|
||||
|
||||
heading += movement_axis(VK_RIGHT, VK_LEFT) * turn_speed;
|
||||
pitch += movement_axis(VK_UP, VK_DOWN) * turn_speed;
|
||||
if (pitch > 89.0F) {
|
||||
pitch = 89.0F;
|
||||
}
|
||||
if (pitch < -89.0F) {
|
||||
pitch = -89.0F;
|
||||
}
|
||||
|
||||
const float heading_radians = heading * 3.1415926535F / 180.0F;
|
||||
const float forward_x = std::sin(heading_radians);
|
||||
const float forward_y = std::cos(heading_radians);
|
||||
const float right_x = std::cos(heading_radians);
|
||||
const float right_y = -std::sin(heading_radians);
|
||||
|
||||
const float forward_axis = movement_axis('W', 'S');
|
||||
const float strafe_axis = movement_axis('D', 'A');
|
||||
const float vertical_axis = movement_axis('R', 'F');
|
||||
|
||||
camera_x += forward_x * forward_axis * move_speed;
|
||||
camera_y += forward_y * forward_axis * move_speed;
|
||||
camera_x += right_x * strafe_axis * move_speed;
|
||||
camera_y += right_y * strafe_axis * move_speed;
|
||||
camera_z += vertical_axis * move_speed;
|
||||
|
||||
camera.set_pos(camera_x, camera_y, camera_z);
|
||||
camera.set_hpr(heading, pitch, 0.0F);
|
||||
|
||||
cube.set_h(cube.get_h() + 0.2F);
|
||||
framework.do_frame(nullptr);
|
||||
}
|
||||
|
||||
framework.close_window(window);
|
||||
framework.close_framework();
|
||||
log_step("Demo closed cleanly.");
|
||||
return 0;
|
||||
#else
|
||||
log_step("Panda3D support is not enabled in this build.");
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
468
tools/PandaCubeEditorDemo.cpp
Normal file
468
tools/PandaCubeEditorDemo.cpp
Normal file
@ -0,0 +1,468 @@
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#if METACORE_HAS_PANDA3D && METACORE_HAS_IMGUI
|
||||
#include <callbackObject.h>
|
||||
#include <displayRegion.h>
|
||||
#include <graphicsWindow.h>
|
||||
#include <imgui.h>
|
||||
#include <imgui_impl_opengl3.h>
|
||||
#include <imgui_impl_win32.h>
|
||||
#include <lineSegs.h>
|
||||
#include <load_prc_file.h>
|
||||
#include <luse.h>
|
||||
#include <nodePath.h>
|
||||
#include <pandaFramework.h>
|
||||
#include <windowProperties.h>
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#if METACORE_HAS_PANDA3D && METACORE_HAS_IMGUI
|
||||
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
void log_step(const std::string& message) {
|
||||
std::cout << "[PandaCubeEditorDemo] " << message << std::endl;
|
||||
}
|
||||
|
||||
#if METACORE_HAS_PANDA3D && METACORE_HAS_IMGUI
|
||||
|
||||
struct DemoState {
|
||||
float camera_x = 0.0F;
|
||||
float camera_y = -8.0F;
|
||||
float camera_z = 2.0F;
|
||||
float heading = 0.0F;
|
||||
float pitch = -10.0F;
|
||||
float rotation_speed = 20.0F;
|
||||
bool auto_rotate = true;
|
||||
bool show_demo_window = false;
|
||||
bool overlay_enabled = true;
|
||||
float model_position[3] = {0.0F, 0.0F, 0.0F};
|
||||
float model_rotation[3] = {0.0F, 0.0F, 0.0F};
|
||||
float model_scale[3] = {1.0F, 1.0F, 1.0F};
|
||||
bool using_fallback_cube = false;
|
||||
};
|
||||
|
||||
WNDPROC g_previous_wnd_proc = nullptr;
|
||||
ImDrawData* g_pending_draw_data = nullptr;
|
||||
HWND g_panda_hwnd = nullptr;
|
||||
PT(DisplayRegion) g_overlay_region = nullptr;
|
||||
|
||||
class ImGuiOverlayDrawCallback final : public CallbackObject {
|
||||
public:
|
||||
void do_callback(CallbackData* cbdata) override {
|
||||
(void)cbdata;
|
||||
if (g_pending_draw_data != nullptr) {
|
||||
ImGui_ImplOpenGL3_RenderDrawData(g_pending_draw_data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
LRESULT CALLBACK panda_imgui_wnd_proc(HWND hwnd, UINT message, WPARAM w_param, LPARAM l_param) {
|
||||
if (ImGui_ImplWin32_WndProcHandler(hwnd, message, w_param, l_param)) {
|
||||
return 1;
|
||||
}
|
||||
if (g_previous_wnd_proc != nullptr) {
|
||||
return CallWindowProc(g_previous_wnd_proc, hwnd, message, w_param, l_param);
|
||||
}
|
||||
return DefWindowProc(hwnd, message, w_param, l_param);
|
||||
}
|
||||
|
||||
void configure_runtime_environment() {
|
||||
const auto panda_bin = std::filesystem::path(METACORE_PANDA3D_ROOT_PATH) / "bin";
|
||||
const auto panda_etc = std::filesystem::path(METACORE_PANDA3D_ROOT_PATH) / "etc";
|
||||
if (std::filesystem::exists(panda_bin)) {
|
||||
SetDllDirectoryW(panda_bin.wstring().c_str());
|
||||
|
||||
const DWORD required = GetEnvironmentVariableW(L"PATH", nullptr, 0);
|
||||
std::wstring existing_path(required > 0 ? required - 1 : 0, L'\0');
|
||||
if (required > 0) {
|
||||
GetEnvironmentVariableW(L"PATH", existing_path.data(), required);
|
||||
}
|
||||
const std::wstring new_path = panda_bin.wstring() + L";" + existing_path;
|
||||
SetEnvironmentVariableW(L"PATH", new_path.c_str());
|
||||
}
|
||||
if (std::filesystem::exists(panda_etc)) {
|
||||
SetEnvironmentVariableW(L"PANDA_PRC_DIR", panda_etc.wstring().c_str());
|
||||
}
|
||||
|
||||
wchar_t exe_path_buffer[MAX_PATH];
|
||||
const DWORD path_length = GetModuleFileNameW(nullptr, exe_path_buffer, MAX_PATH);
|
||||
if (path_length > 0 && path_length < MAX_PATH) {
|
||||
std::filesystem::current_path(std::filesystem::path(exe_path_buffer).parent_path());
|
||||
}
|
||||
}
|
||||
|
||||
void configure_local_prc() {
|
||||
wchar_t exe_path_buffer[MAX_PATH];
|
||||
const DWORD path_length = GetModuleFileNameW(nullptr, exe_path_buffer, MAX_PATH);
|
||||
if (path_length == 0 || path_length >= MAX_PATH) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::filesystem::path exe_dir = std::filesystem::path(exe_path_buffer).parent_path();
|
||||
const std::filesystem::path prc_path = exe_dir / "PandaCubeEditorDemo.prc";
|
||||
const std::filesystem::path panda_bin = std::filesystem::path(METACORE_PANDA3D_ROOT_PATH) / "bin";
|
||||
std::ofstream prc_file(prc_path, std::ios::trunc);
|
||||
prc_file
|
||||
<< "plugin-path " << panda_bin.generic_string() << "\n"
|
||||
<< "load-display pandagl\n"
|
||||
<< "aux-display p3tinydisplay\n"
|
||||
<< "load-file-type p3assimp\n"
|
||||
<< "load-file-type p3ptloader\n"
|
||||
<< "window-type onscreen\n"
|
||||
<< "win-size 1280 720\n"
|
||||
<< "sync-video #f\n"
|
||||
<< "audio-library-name null\n";
|
||||
prc_file.close();
|
||||
load_prc_file(Filename::from_os_specific(prc_path.string()));
|
||||
}
|
||||
|
||||
NodePath create_wire_cube(const NodePath& parent) {
|
||||
LineSegs segments("editor-demo-cube");
|
||||
segments.set_thickness(2.5F);
|
||||
segments.set_color(0.85F, 0.9F, 1.0F, 1.0F);
|
||||
|
||||
const LPoint3f corners[] = {
|
||||
{-0.5F, -0.5F, -0.5F},
|
||||
{0.5F, -0.5F, -0.5F},
|
||||
{0.5F, 0.5F, -0.5F},
|
||||
{-0.5F, 0.5F, -0.5F},
|
||||
{-0.5F, -0.5F, 0.5F},
|
||||
{0.5F, -0.5F, 0.5F},
|
||||
{0.5F, 0.5F, 0.5F},
|
||||
{-0.5F, 0.5F, 0.5F},
|
||||
};
|
||||
const int edges[][2] = {
|
||||
{0, 1}, {1, 2}, {2, 3}, {3, 0},
|
||||
{4, 5}, {5, 6}, {6, 7}, {7, 4},
|
||||
{0, 4}, {1, 5}, {2, 6}, {3, 7},
|
||||
};
|
||||
|
||||
for (const auto& edge : edges) {
|
||||
segments.move_to(corners[edge[0]]);
|
||||
segments.draw_to(corners[edge[1]]);
|
||||
}
|
||||
|
||||
return parent.attach_new_node(segments.create());
|
||||
}
|
||||
|
||||
NodePath create_world_axes(const NodePath& parent) {
|
||||
LineSegs segments("world-axes");
|
||||
segments.set_thickness(3.0F);
|
||||
|
||||
segments.set_color(0.95F, 0.2F, 0.2F, 1.0F);
|
||||
segments.move_to(0.0F, 0.0F, 0.0F);
|
||||
segments.draw_to(2.0F, 0.0F, 0.0F);
|
||||
|
||||
segments.set_color(0.2F, 0.95F, 0.35F, 1.0F);
|
||||
segments.move_to(0.0F, 0.0F, 0.0F);
|
||||
segments.draw_to(0.0F, 2.0F, 0.0F);
|
||||
|
||||
segments.set_color(0.25F, 0.55F, 1.0F, 1.0F);
|
||||
segments.move_to(0.0F, 0.0F, 0.0F);
|
||||
segments.draw_to(0.0F, 0.0F, 2.0F);
|
||||
|
||||
return parent.attach_new_node(segments.create());
|
||||
}
|
||||
|
||||
NodePath create_ground_grid(const NodePath& parent) {
|
||||
LineSegs segments("ground-grid");
|
||||
segments.set_thickness(1.0F);
|
||||
segments.set_color(0.35F, 0.38F, 0.42F, 0.85F);
|
||||
|
||||
constexpr int half_extent = 10;
|
||||
for (int i = -half_extent; i <= half_extent; ++i) {
|
||||
segments.move_to(static_cast<float>(i), -static_cast<float>(half_extent), 0.0F);
|
||||
segments.draw_to(static_cast<float>(i), static_cast<float>(half_extent), 0.0F);
|
||||
segments.move_to(-static_cast<float>(half_extent), static_cast<float>(i), 0.0F);
|
||||
segments.draw_to(static_cast<float>(half_extent), static_cast<float>(i), 0.0F);
|
||||
}
|
||||
|
||||
return parent.attach_new_node(segments.create());
|
||||
}
|
||||
|
||||
NodePath load_demo_model(WindowFramework* window, DemoState& state) {
|
||||
const Filename model_path = Filename::from_os_specific("D:/MetaCore/box1.glb");
|
||||
NodePath model = window->load_model(window->get_render(), model_path);
|
||||
if (!model.is_empty()) {
|
||||
log_step("Loaded model: D:/MetaCore/box1.glb");
|
||||
state.using_fallback_cube = false;
|
||||
return model;
|
||||
}
|
||||
|
||||
log_step("Failed to load box1.glb, falling back to wire cube");
|
||||
state.using_fallback_cube = true;
|
||||
NodePath fallback = create_wire_cube(window->get_render());
|
||||
fallback.set_scale(1.5F);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
void focus_camera_on_model(const NodePath& model, DemoState& state) {
|
||||
LPoint3 min_point;
|
||||
LPoint3 max_point;
|
||||
if (!model.calc_tight_bounds(min_point, max_point)) {
|
||||
state.camera_x = 0.0F;
|
||||
state.camera_y = -8.0F;
|
||||
state.camera_z = 2.0F;
|
||||
state.model_position[0] = 0.0F;
|
||||
state.model_position[1] = 0.0F;
|
||||
state.model_position[2] = 0.0F;
|
||||
return;
|
||||
}
|
||||
|
||||
const LPoint3 center(
|
||||
(min_point[0] + max_point[0]) * 0.5F,
|
||||
(min_point[1] + max_point[1]) * 0.5F,
|
||||
(min_point[2] + max_point[2]) * 0.5F
|
||||
);
|
||||
const LVecBase3 extent = max_point - min_point;
|
||||
const float max_extent = (std::max)((std::max)(std::fabs(extent[0]), std::fabs(extent[1])), std::fabs(extent[2]));
|
||||
const float distance = (std::max)(3.0F, max_extent * 3.0F);
|
||||
|
||||
state.model_position[0] = -center[0];
|
||||
state.model_position[1] = -center[1];
|
||||
state.model_position[2] = -center[2];
|
||||
state.camera_x = 0.0F;
|
||||
state.camera_y = -distance;
|
||||
state.camera_z = (std::max)(1.5F, max_extent * 0.9F);
|
||||
state.heading = 0.0F;
|
||||
state.pitch = -10.0F;
|
||||
|
||||
log_step(
|
||||
"Model bounds centered. Extent=" + std::to_string(max_extent) +
|
||||
" camera_y=" + std::to_string(state.camera_y) +
|
||||
" camera_z=" + std::to_string(state.camera_z)
|
||||
);
|
||||
}
|
||||
|
||||
std::uintptr_t native_window_handle(WindowFramework* window) {
|
||||
if (window == nullptr || window->get_graphics_window() == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
if (WindowHandle* handle = window->get_graphics_window()->get_window_handle(); handle != nullptr) {
|
||||
if (WindowHandle::OSHandle* os_handle = handle->get_os_handle(); os_handle != nullptr) {
|
||||
return static_cast<std::uintptr_t>(os_handle->get_int_handle());
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void render_editor_ui(DemoState& state) {
|
||||
if (!state.overlay_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::SetNextWindowBgAlpha(0.78F);
|
||||
ImGui::SetNextWindowPos(ImVec2(16.0F, 16.0F), ImGuiCond_Always);
|
||||
ImGui::SetNextWindowSize(ImVec2(360.0F, 0.0F), ImGuiCond_Always);
|
||||
if (ImGui::Begin("MetaCore Editor Overlay", nullptr,
|
||||
ImGuiWindowFlags_NoCollapse |
|
||||
ImGuiWindowFlags_NoSavedSettings)) {
|
||||
ImGui::TextUnformatted("Single-window Panda3D + ImGui test");
|
||||
ImGui::Separator();
|
||||
ImGui::Checkbox("Overlay Enabled", &state.overlay_enabled);
|
||||
ImGui::Checkbox("Auto Rotate", &state.auto_rotate);
|
||||
ImGui::Checkbox("Show ImGui Demo", &state.show_demo_window);
|
||||
ImGui::Text("Model: %s", state.using_fallback_cube ? "Fallback Wire Cube" : "box1.glb");
|
||||
ImGui::SliderFloat("Rotation Speed", &state.rotation_speed, 0.0F, 180.0F, "%.1f deg/s");
|
||||
ImGui::DragFloat3("Model Position", state.model_position, 0.05F);
|
||||
ImGui::DragFloat3("Model Rotation", state.model_rotation, 1.0F);
|
||||
ImGui::DragFloat3("Model Scale", state.model_scale, 0.02F, 0.05F, 10.0F);
|
||||
ImGui::DragFloat3("Camera Position", &state.camera_x, 0.05F);
|
||||
ImGui::SliderFloat("Heading", &state.heading, -180.0F, 180.0F, "%.1f");
|
||||
ImGui::SliderFloat("Pitch", &state.pitch, -89.0F, 89.0F, "%.1f");
|
||||
ImGui::Separator();
|
||||
ImGui::TextUnformatted("Controls");
|
||||
ImGui::BulletText("W/A/S/D move");
|
||||
ImGui::BulletText("R/F up and down");
|
||||
ImGui::BulletText("Arrow keys look");
|
||||
ImGui::BulletText("Esc closes");
|
||||
ImGui::TextUnformatted("This ImGui panel is rendered over the Panda viewport.");
|
||||
}
|
||||
ImGui::End();
|
||||
|
||||
if (state.show_demo_window) {
|
||||
ImGui::ShowDemoWindow(&state.show_demo_window);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
int main() {
|
||||
#if METACORE_HAS_PANDA3D && METACORE_HAS_IMGUI
|
||||
log_step("Starting PandaCubeEditorDemo");
|
||||
configure_runtime_environment();
|
||||
configure_local_prc();
|
||||
|
||||
PandaFramework framework;
|
||||
framework.open_framework();
|
||||
framework.set_window_title("MetaCore Single-Window Editor Demo");
|
||||
|
||||
WindowProperties props;
|
||||
props.set_title("MetaCore Single-Window Editor Demo");
|
||||
props.set_size(1280, 720);
|
||||
|
||||
WindowFramework* panda_window = framework.open_window(props, 0);
|
||||
if (panda_window == nullptr) {
|
||||
log_step("Panda window failed to open");
|
||||
framework.close_framework();
|
||||
return 2;
|
||||
}
|
||||
|
||||
create_ground_grid(panda_window->get_render());
|
||||
create_world_axes(panda_window->get_render());
|
||||
|
||||
DemoState state;
|
||||
NodePath model = load_demo_model(panda_window, state);
|
||||
focus_camera_on_model(model, state);
|
||||
|
||||
g_panda_hwnd = reinterpret_cast<HWND>(native_window_handle(panda_window));
|
||||
if (g_panda_hwnd == nullptr) {
|
||||
log_step("Failed to resolve Panda window handle");
|
||||
framework.close_window(panda_window);
|
||||
framework.close_framework();
|
||||
return 3;
|
||||
}
|
||||
|
||||
IMGUI_CHECKVERSION();
|
||||
ImGui::CreateContext();
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable;
|
||||
ImGui::StyleColorsDark();
|
||||
|
||||
ImGui_ImplWin32_Init(g_panda_hwnd);
|
||||
ImGui_ImplOpenGL3_Init("#version 130");
|
||||
g_previous_wnd_proc = reinterpret_cast<WNDPROC>(
|
||||
SetWindowLongPtr(g_panda_hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(panda_imgui_wnd_proc))
|
||||
);
|
||||
|
||||
g_overlay_region = panda_window->get_graphics_output()->make_display_region();
|
||||
g_overlay_region->set_sort(1000);
|
||||
g_overlay_region->set_incomplete_render(true);
|
||||
g_overlay_region->set_clear_color_active(false);
|
||||
g_overlay_region->set_clear_depth_active(false);
|
||||
PT(CallbackObject) overlay_callback = new ImGuiOverlayDrawCallback();
|
||||
g_overlay_region->set_draw_callback(overlay_callback);
|
||||
|
||||
DWORD previous_tick = GetTickCount();
|
||||
bool running = true;
|
||||
while (running) {
|
||||
if ((GetAsyncKeyState(VK_ESCAPE) & 0x8000) != 0) {
|
||||
running = false;
|
||||
}
|
||||
if (panda_window->get_graphics_window() != nullptr && panda_window->get_graphics_window()->is_closed()) {
|
||||
running = false;
|
||||
}
|
||||
|
||||
const DWORD current_tick = GetTickCount();
|
||||
const float delta_seconds = static_cast<float>(current_tick - previous_tick) / 1000.0F;
|
||||
previous_tick = current_tick;
|
||||
|
||||
const float move_speed = ((GetAsyncKeyState(VK_SHIFT) & 0x8000) != 0 ? 5.0F : 2.5F) * delta_seconds;
|
||||
const float turn_speed = 90.0F * delta_seconds;
|
||||
|
||||
const auto key_down = [](const int key) {
|
||||
return (GetAsyncKeyState(key) & 0x8000) != 0;
|
||||
};
|
||||
if (key_down(VK_LEFT)) {
|
||||
state.heading -= turn_speed;
|
||||
}
|
||||
if (key_down(VK_RIGHT)) {
|
||||
state.heading += turn_speed;
|
||||
}
|
||||
if (key_down(VK_UP)) {
|
||||
state.pitch += turn_speed;
|
||||
}
|
||||
if (key_down(VK_DOWN)) {
|
||||
state.pitch -= turn_speed;
|
||||
}
|
||||
if (state.pitch > 89.0F) {
|
||||
state.pitch = 89.0F;
|
||||
}
|
||||
if (state.pitch < -89.0F) {
|
||||
state.pitch = -89.0F;
|
||||
}
|
||||
|
||||
const float heading_radians = state.heading * 3.1415926535F / 180.0F;
|
||||
const float forward_x = std::sin(heading_radians);
|
||||
const float forward_y = std::cos(heading_radians);
|
||||
const float right_x = std::cos(heading_radians);
|
||||
const float right_y = -std::sin(heading_radians);
|
||||
|
||||
if (key_down('W')) {
|
||||
state.camera_x += forward_x * move_speed;
|
||||
state.camera_y += forward_y * move_speed;
|
||||
}
|
||||
if (key_down('S')) {
|
||||
state.camera_x -= forward_x * move_speed;
|
||||
state.camera_y -= forward_y * move_speed;
|
||||
}
|
||||
if (key_down('D')) {
|
||||
state.camera_x += right_x * move_speed;
|
||||
state.camera_y += right_y * move_speed;
|
||||
}
|
||||
if (key_down('A')) {
|
||||
state.camera_x -= right_x * move_speed;
|
||||
state.camera_y -= right_y * move_speed;
|
||||
}
|
||||
if (key_down('R')) {
|
||||
state.camera_z += move_speed;
|
||||
}
|
||||
if (key_down('F')) {
|
||||
state.camera_z -= move_speed;
|
||||
}
|
||||
|
||||
panda_window->get_camera_group().set_pos(state.camera_x, state.camera_y, state.camera_z);
|
||||
panda_window->get_camera_group().set_hpr(state.heading, state.pitch, 0.0F);
|
||||
model.set_pos(state.model_position[0], state.model_position[1], state.model_position[2]);
|
||||
model.set_hpr(state.model_rotation[0], state.model_rotation[1], state.model_rotation[2]);
|
||||
model.set_scale(state.model_scale[0], state.model_scale[1], state.model_scale[2]);
|
||||
if (state.auto_rotate) {
|
||||
state.model_rotation[0] += state.rotation_speed * delta_seconds;
|
||||
}
|
||||
|
||||
ImGui_ImplOpenGL3_NewFrame();
|
||||
ImGui_ImplWin32_NewFrame();
|
||||
ImGui::NewFrame();
|
||||
render_editor_ui(state);
|
||||
ImGui::Render();
|
||||
g_pending_draw_data = ImGui::GetDrawData();
|
||||
|
||||
framework.do_frame(nullptr);
|
||||
}
|
||||
|
||||
if (g_overlay_region != nullptr) {
|
||||
g_overlay_region->clear_draw_callback();
|
||||
if (panda_window->get_graphics_output() != nullptr) {
|
||||
panda_window->get_graphics_output()->remove_display_region(g_overlay_region);
|
||||
}
|
||||
g_overlay_region = nullptr;
|
||||
}
|
||||
g_pending_draw_data = nullptr;
|
||||
|
||||
if (g_previous_wnd_proc != nullptr) {
|
||||
SetWindowLongPtr(g_panda_hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(g_previous_wnd_proc));
|
||||
g_previous_wnd_proc = nullptr;
|
||||
}
|
||||
ImGui_ImplOpenGL3_Shutdown();
|
||||
ImGui_ImplWin32_Shutdown();
|
||||
ImGui::DestroyContext();
|
||||
|
||||
framework.close_window(panda_window);
|
||||
framework.close_framework();
|
||||
return 0;
|
||||
#else
|
||||
log_step("Panda3D or Dear ImGui support is not enabled in this build.");
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user