MetaCore/tools/PandaCubeEditorDemo.cpp
2026-03-19 18:01:50 +08:00

469 lines
16 KiB
C++

#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
}