Monad Engine da02fba2
> Undercity Codex_
Loading...
Searching...
No Matches
Editor.cpp
Go to the documentation of this file.
1#include <algorithm>
2#include <filesystem>
3#include <map>
4#include <string>
5#include <vector>
6
7#include "editor/Editor.h"
8
9#include <glm/gtc/quaternion.hpp>
10#include <glm/gtc/type_ptr.hpp>
11
12#include "Application.h"
13#include "Engine.h"
14#include "GL/glew.h"
15#include "btBulletDynamicsCommon.h"
16#include "physics/PhysicsManager.h"
17#include "GLFW/glfw3.h"
18#include "Log.h"
19#include "backends/imgui_impl_glfw.h"
20#include "backends/imgui_impl_opengl3.h"
22#include "imgui.h"
23#include "input/InputManager.h"
24#include "io/FileSystem.h"
25#include "scene/GameObject.h"
26#include "scene/Scene.h"
27#include "scene/components/CameraComponent.h"
28#include "scene/components/LightComponent.h"
29#include "scene/components/MeshComponent.h"
30#include "scene/components/PlayerControllerComponent.h"
31
32namespace mnd
33{
34
35bool Editor::Init(GLFWwindow *window)
36{
37 IMGUI_CHECKVERSION();
38 ImGui::CreateContext();
39 ImGuiIO &io = ImGui::GetIO();
40 io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
41 ImGui::StyleColorsDark();
42
43 // install_callbacks=true chains our existing GLFW callbacks — they still
44 // feed InputManager, and ImGui gets a copy of the event.
45 if (!ImGui_ImplGlfw_InitForOpenGL(window, true))
46 {
47 LOG_ERROR("ImGui GLFW backend init failed");
48 return false;
49 }
50 if (!ImGui_ImplOpenGL3_Init(kGlslVersionDirective))
51 {
52 LOG_ERROR("ImGui OpenGL3 backend init failed");
53 return false;
54 }
55 m_initialized = true;
56 LOG_INFO("Editor initialized");
57 return true;
58}
59
61{
62 if (!m_initialized)
63 {
64 return;
65 }
66 ImGui_ImplOpenGL3_Shutdown();
67 ImGui_ImplGlfw_Shutdown();
68 ImGui::DestroyContext();
69 m_initialized = false;
70}
71
73{
74 if (!m_initialized)
75 {
76 return;
77 }
78 // F1 toggles visibility (read via GLFW directly so it works even when ImGui captures keyboard).
79 GLFWwindow *w = glfwGetCurrentContext();
80 {
81 static bool wasDown = false;
82 bool down = (w != nullptr) && (glfwGetKey(w, GLFW_KEY_F1) == GLFW_PRESS);
83 if (down && !wasDown)
84 {
86 }
87 wasDown = down;
88 }
89
90 // Detach cursor when editor open so the mouse can click ImGui widgets instead
91 // of driving the in-game camera. Flip back to disabled (locked+hidden) on close.
92 if (w != nullptr)
93 {
94 int desired = m_visible ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_DISABLED;
95 int current = glfwGetInputMode(w, GLFW_CURSOR);
96 if (current != desired)
97 {
98 glfwSetInputMode(w, GLFW_CURSOR, desired);
99 // Re-sync cached cursor position so the first frame after a mode flip
100 // doesn't produce a huge delta that spins the camera.
101 f64 x = 0, y = 0;
102 glfwGetCursorPos(w, &x, &y);
104 vec2 p(static_cast<f32>(x), static_cast<f32>(y));
105 im.SetMousePositionOld(p);
106 im.SetMousePositionCurrent(p);
107 im.SetMousePositionChanged(false);
108 }
109 }
110
111 ImGui_ImplOpenGL3_NewFrame();
112 ImGui_ImplGlfw_NewFrame();
113 ImGui::NewFrame();
114}
115
116// ---- Layout ----------------------------------------------------------
117//
118// Stock ImGui master is vendored (no docking branch), so we simulate a
119// docked engine-style layout by pinning each panel to a fixed region of
120// the viewport. Widths are tuned for a 1280x720 default window; they
121// follow the viewport on resize.
122
123namespace
124{
125constexpr f32 kLeftWidth = 320.0F; // Settings tabs (Render/Engine/Physics/Player)
126constexpr f32 kRightWidth = 280.0F; // Inspector
127constexpr f32 kBottomHeight = 280.0F; // Console + Hierarchy
128constexpr f32 kConsoleFrac = 0.62F; // Console takes 62% of bottom row width
129
130constexpr ImGuiWindowFlags kDockedFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize
131 | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoBringToFrontOnFocus;
132
133struct DockRects
134{
140};
141
142DockRects ComputeDock(f32 menuH)
143{
144 const ImGuiViewport *vp = ImGui::GetMainViewport();
145 f32 vx = vp->WorkPos.x;
146 f32 vy = vp->WorkPos.y + menuH;
147 f32 vw = vp->WorkSize.x;
148 f32 vh = vp->WorkSize.y - menuH;
149
150 // Scale columns when the window is too small to fit comfortable defaults.
151 f32 leftW = (vw < kLeftWidth + kRightWidth + 240.0F) ? vw * 0.24F : kLeftWidth;
152 f32 rightW = (vw < kLeftWidth + kRightWidth + 240.0F) ? vw * 0.22F : kRightWidth;
153 f32 bottomH = (vh < kBottomHeight + 240.0F) ? vh * 0.30F : kBottomHeight;
154
155 DockRects r {};
156 r.settingsPos = ImVec2(vx, vy);
157 r.settingsSize = ImVec2(leftW, vh);
158
159 r.inspectorPos = ImVec2(vx + vw - rightW, vy);
160 r.inspectorSize = ImVec2(rightW, vh);
161
162 f32 bottomY = vy + vh - bottomH;
163 f32 centerX = vx + leftW;
164 f32 centerW = vw - leftW - rightW;
165 f32 consoleW = centerW * kConsoleFrac;
166
167 r.consolePos = ImVec2(centerX, bottomY);
168 r.consoleSize = ImVec2(consoleW, bottomH);
169
170 r.hierarchyPos = ImVec2(centerX + consoleW, bottomY);
171 r.hierarchySize = ImVec2(centerW - consoleW, bottomH);
172
173 // Stats: compact overlay in the viewport's top-right corner.
174 constexpr f32 kStatsW = 190.0F;
175 constexpr f32 kStatsH = 90.0F;
176 r.statsPos = ImVec2(vx + leftW + centerW - kStatsW - 10.0F, vy + 10.0F);
177 r.statsSize = ImVec2(kStatsW, kStatsH);
178 return r;
179}
180} // namespace
181
183{
184 if (!m_initialized || !m_visible)
185 {
186 return;
187 }
188
189 DrawMenuBar();
190
191 f32 menuH = ImGui::GetFrameHeight();
192 DockRects d = ComputeDock(menuH);
193
194 if (m_showHierarchy)
195 {
196 ImGui::SetNextWindowPos(d.hierarchyPos, ImGuiCond_Always);
197 ImGui::SetNextWindowSize(d.hierarchySize, ImGuiCond_Always);
198 DrawHierarchy();
199 }
200 if (m_showInspector)
201 {
202 ImGui::SetNextWindowPos(d.inspectorPos, ImGuiCond_Always);
203 ImGui::SetNextWindowSize(d.inspectorSize, ImGuiCond_Always);
204 DrawInspector();
205 }
206 if (m_showConsole)
207 {
208 ImGui::SetNextWindowPos(d.consolePos, ImGuiCond_Always);
209 ImGui::SetNextWindowSize(d.consoleSize, ImGuiCond_Always);
210 DrawConsole();
211 }
212 // Settings: single docked window with tabs (Render/Engine/Physics/Player).
213 // Visible if any of those four panels are toggled on.
214 if (m_showRender || m_showEngine || m_showPhysics || m_showPlayer)
215 {
216 ImGui::SetNextWindowPos(d.settingsPos, ImGuiCond_Always);
217 ImGui::SetNextWindowSize(d.settingsSize, ImGuiCond_Always);
218 DrawSettings();
219 }
220 if (m_showStats)
221 {
222 ImGui::SetNextWindowPos(d.statsPos, ImGuiCond_Always);
223 ImGui::SetNextWindowSize(d.statsSize, ImGuiCond_Always);
224 DrawStats();
225 }
226 if (m_showDemo)
227 {
228 ImGui::ShowDemoWindow(&m_showDemo);
229 }
230
231 for (auto &p : m_customPanels)
232 {
233 if (ImGui::Begin(p.first.c_str()))
234 {
235 p.second();
236 }
237 ImGui::End();
238 }
239}
240
242{
243 if (!m_initialized)
244 {
245 return;
246 }
247 ImGui::Render();
248 ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
249}
250
252{
253 return m_initialized && m_visible && ImGui::GetIO().WantCaptureMouse;
254}
255
257{
258 return m_initialized && m_visible && ImGui::GetIO().WantCaptureKeyboard;
259}
260
261// ---- Menu bar ---------------------------------------------------------
262
263void Editor::DrawMenuBar()
264{
265 if (!ImGui::BeginMainMenuBar())
266 {
267 return;
268 }
269 if (ImGui::BeginMenu("Windows"))
270 {
271 ImGui::MenuItem("Hierarchy", nullptr, &m_showHierarchy);
272 ImGui::MenuItem("Inspector", nullptr, &m_showInspector);
273 ImGui::MenuItem("Console", nullptr, &m_showConsole);
274 ImGui::MenuItem("Render", nullptr, &m_showRender);
275 ImGui::MenuItem("Stats", nullptr, &m_showStats);
276 ImGui::MenuItem("Engine", nullptr, &m_showEngine);
277 ImGui::MenuItem("Physics", nullptr, &m_showPhysics);
278 ImGui::MenuItem("Player", nullptr, &m_showPlayer);
279 ImGui::Separator();
280 ImGui::MenuItem("ImGui Demo", nullptr, &m_showDemo);
281 ImGui::EndMenu();
282 }
283 ImGui::Text(" | F1: toggle editor");
284 ImGui::EndMainMenuBar();
285}
286
287// ---- Hierarchy --------------------------------------------------------
288
289void Editor::DrawObjectNode(GameObject *obj)
290{
291 if (obj == nullptr)
292 {
293 return;
294 }
295 const auto &children = obj->GetChildren();
296
297 ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_SpanAvailWidth;
298 if (children.empty())
299 {
300 flags |= ImGuiTreeNodeFlags_Leaf;
301 }
302 if (m_selected == obj)
303 {
304 flags |= ImGuiTreeNodeFlags_Selected;
305 }
306
307 bool open = ImGui::TreeNodeEx(obj, flags, "%s", obj->GetName().c_str());
308 if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen())
309 {
310 m_selected = obj;
311 }
312 if (open)
313 {
314 for (auto &c : children)
315 {
316 DrawObjectNode(c.get());
317 }
318 ImGui::TreePop();
319 }
320}
321
322void Editor::DrawHierarchy()
323{
324 if (!ImGui::Begin("Hierarchy", &m_showHierarchy, kDockedFlags))
325 {
326 ImGui::End();
327 return;
328 }
329 Scene *scene = Engine::GetInstance().GetScene();
330 if (scene == nullptr)
331 {
332 ImGui::TextUnformatted("No active scene");
333 } else
334 {
335 for (auto &root : scene->GetRootObjects())
336 {
337 DrawObjectNode(root.get());
338 }
339 }
340 ImGui::End();
341}
342
343// ---- Inspector --------------------------------------------------------
344
346{
347 if (!ImGui::CollapsingHeader("Transform", ImGuiTreeNodeFlags_DefaultOpen))
348 {
349 return;
350 }
351 vec3 pos = obj->GetPosition();
352 if (ImGui::DragFloat3("Position", glm::value_ptr(pos), 0.05F))
353 {
354 obj->SetPosition(pos);
355 }
356
357 quat rot = obj->GetRotation();
358 vec3 euler = glm::degrees(glm::eulerAngles(rot));
359 if (ImGui::DragFloat3("Rotation (deg)", glm::value_ptr(euler), 0.5F))
360 {
361 obj->SetRotation(quat(glm::radians(euler)));
362 }
363
364 vec3 s = obj->GetScale();
365 if (ImGui::DragFloat3("Scale", glm::value_ptr(s), 0.01F, 0.0F, 0.0F))
366 {
367 obj->SetScale(s);
368 }
369}
370
372{
373 if (!ImGui::CollapsingHeader("CameraComponent", ImGuiTreeNodeFlags_DefaultOpen))
374 {
375 return;
376 }
377 f32 fov = c->GetFov();
378 f32 near = c->GetNearPlane();
379 f32 far = c->GetFarPlane();
380 if (ImGui::SliderFloat("FOV", &fov, 10.0F, 170.0F))
381 {
382 c->SetFov(fov);
383 }
384 if (ImGui::DragFloat("Near", &near, 0.01F, 0.001F, far - 0.01F))
385 {
386 c->SetNearPlane(near);
387 }
388 if (ImGui::DragFloat("Far", &far, 1.0F, near + 0.01F, 100000.0F))
389 {
390 c->SetFarPlane(far);
391 }
392}
393
395{
396 if (!ImGui::CollapsingHeader("LightComponent", ImGuiTreeNodeFlags_DefaultOpen))
397 {
398 return;
399 }
400 vec3 col = c->GetColor();
401 if (ImGui::ColorEdit3("Color", glm::value_ptr(col), ImGuiColorEditFlags_Float | ImGuiColorEditFlags_HDR))
402 {
403 c->SetColor(col);
404 }
405}
406
408{
409 if (!ImGui::CollapsingHeader("PlayerControllerComponent", ImGuiTreeNodeFlags_DefaultOpen))
410 {
411 return;
412 }
413 f32 ms = c->GetMoveSpeed();
414 f32 sens = c->GetSensitivity();
415 f32 jump = c->GetJumpSpeed();
416 if (ImGui::DragFloat("Move Speed", &ms, 0.05F, 0.0F, 1000.0F))
417 {
418 c->SetMoveSpeed(ms);
419 }
420 if (ImGui::DragFloat("Sensitivity", &sens, 0.1F, 0.0F, 500.0F))
421 {
422 c->SetSensitivity(sens);
423 }
424 if (ImGui::DragFloat("Jump Speed", &jump, 0.05F, 0.0F, 1000.0F))
425 {
426 c->SetJumpSpeed(jump);
427 }
428 ImGui::Text("On ground: %s", c->OnGround() ? "yes" : "no");
429}
430
432{
433 if (!ImGui::CollapsingHeader("MeshComponent"))
434 {
435 return;
436 }
437 ImGui::TextUnformatted("(mesh + material — read-only)");
438}
439
440void Editor::DrawInspector()
441{
442 if (!ImGui::Begin("Inspector", &m_showInspector, kDockedFlags))
443 {
444 ImGui::End();
445 return;
446 }
447 if (m_selected == nullptr)
448 {
449 ImGui::TextUnformatted("Nothing selected");
450 ImGui::End();
451 return;
452 }
453
454 ImGui::Text("Name: %s", m_selected->GetName().c_str());
455
456 bool active = m_selected->IsActive();
457 if (ImGui::Checkbox("Active", &active))
458 {
459 m_selected->SetActive(active);
460 }
461
462 InspectTransform(m_selected);
463
464 if (auto *cam = m_selected->GetComponent<CameraComponent>())
465 {
466 InspectCamera(cam);
467 }
468 if (auto *lit = m_selected->GetComponent<LightComponent>())
469 {
470 InspectLight(lit);
471 }
472 if (auto *pc = m_selected->GetComponent<PlayerControllerComponent>())
473 {
474 InspectPlayer(pc);
475 }
476 if (auto *mc = m_selected->GetComponent<MeshComponent>())
477 {
478 InspectMesh(mc);
479 }
480
481 ImGui::End();
482}
483
484// ---- Console ----------------------------------------------------------
485
486void Editor::DrawConsole()
487{
488 if (!ImGui::Begin("Console", &m_showConsole, kDockedFlags))
489 {
490 ImGui::End();
491 return;
492 }
493
494 static bool showInfo = true;
495 static bool showWarn = true;
496 static bool showError = true;
497 static bool autoScroll = true;
498 static char filter[128] = {0};
499
500 ImGui::Checkbox("Info", &showInfo);
501 ImGui::SameLine();
502 ImGui::Checkbox("Warn", &showWarn);
503 ImGui::SameLine();
504 ImGui::Checkbox("Error", &showError);
505 ImGui::SameLine();
506 ImGui::Checkbox("Auto-scroll", &autoScroll);
507 ImGui::SameLine();
508 if (ImGui::Button("Clear"))
509 {
510 LogClear();
511 }
512 ImGui::SameLine();
513 ImGui::SetNextItemWidth(-1.0F);
514 ImGui::InputTextWithHint("##filter", "filter", filter, sizeof(filter));
515
516 ImGui::Separator();
517
518 if (ImGui::BeginChild("console_scroll", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar))
519 {
520 const auto &entries = LogGetEntries();
521 for (const auto &e : entries)
522 {
523 bool pass = false;
524 switch (e.level)
525 {
526 case LogLevel::Info:
527 pass = showInfo;
528 break;
529 case LogLevel::Warn:
530 pass = showWarn;
531 break;
532 case LogLevel::Error:
533 case LogLevel::Fatal:
534 pass = showError;
535 break;
536 }
537 if (!pass)
538 {
539 continue;
540 }
541 if (filter[0] != 0 && e.text.find(filter) == std::string::npos)
542 {
543 continue;
544 }
545
546 ImVec4 col;
547 switch (e.level)
548 {
549 case LogLevel::Info:
550 col = ImVec4(0.75F, 0.85F, 0.75F, 1.0F);
551 break;
552 case LogLevel::Warn:
553 col = ImVec4(1.00F, 0.85F, 0.40F, 1.0F);
554 break;
555 case LogLevel::Error:
556 col = ImVec4(1.00F, 0.45F, 0.45F, 1.0F);
557 break;
558 case LogLevel::Fatal:
559 col = ImVec4(1.00F, 0.20F, 0.20F, 1.0F);
560 break;
561 }
562 ImGui::PushStyleColor(ImGuiCol_Text, col);
563 ImGui::TextUnformatted(e.text.c_str());
564 ImGui::PopStyleColor();
565 }
566 if (autoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY() - 1.0F)
567 {
568 ImGui::SetScrollHereY(1.0F);
569 }
570 }
571 ImGui::EndChild();
572 ImGui::End();
573}
574
575// ---- Settings tabs ----------------------------------------------------
576
577void Editor::DrawSettings()
578{
579 // No close button: closing all tabs is done from the Windows menu instead.
580 ImGuiWindowFlags flags = kDockedFlags | ImGuiWindowFlags_NoTitleBar;
581 if (!ImGui::Begin("##settings", nullptr, flags))
582 {
583 ImGui::End();
584 return;
585 }
586
587 if (ImGui::BeginTabBar("settings_tabs", ImGuiTabBarFlags_FittingPolicyScroll))
588 {
589 if (m_showRender && ImGui::BeginTabItem("Render"))
590 {
591 DrawRenderBody();
592 ImGui::EndTabItem();
593 }
594 if (m_showEngine && ImGui::BeginTabItem("Engine"))
595 {
596 DrawEngineBody();
597 ImGui::EndTabItem();
598 }
599 if (m_showPhysics && ImGui::BeginTabItem("Physics"))
600 {
601 DrawPhysicsBody();
602 ImGui::EndTabItem();
603 }
604 if (m_showPlayer && ImGui::BeginTabItem("Player"))
605 {
606 DrawPlayerBody();
607 ImGui::EndTabItem();
608 }
609 ImGui::EndTabBar();
610 }
611 ImGui::End();
612}
613
614// ---- Render settings --------------------------------------------------
615
616void Editor::DrawRenderBody()
617{
618 RenderSettings &rs = Engine::GetInstance().GetRenderSettings();
619
620 ImGui::SeparatorText("Clear");
621 ImGui::ColorEdit4("Clear color", glm::value_ptr(rs.clearColor));
622
623 ImGui::SeparatorText("Pixel art");
624 ImGui::Checkbox("Use Pixelation", &rs.useInternalRes);
625 ImGui::SliderInt("Pixel Size", &rs.pixelSize, 1, 32);
626 rs.pixelSize = std::clamp(rs.pixelSize, 1, 32);
627 ImGui::Text("Scene target: %d x %d", rs.internalW, rs.internalH);
628 ImGui::Checkbox("Use Outline", &rs.useOutline);
629 {
630 PostProcess &pp = Engine::GetInstance().GetPostProcess();
631 ImGui::BeginDisabled(!rs.useOutline);
632 ImGui::SliderFloat("Normal Edge Strength", &pp.normalEdgeStrength, 0.0F, 2.0F);
633 ImGui::SliderFloat("Depth Edge Strength", &pp.depthEdgeStrength, 0.0F, 2.0F);
634 pp.normalEdgeStrength = std::clamp(pp.normalEdgeStrength, 0.0F, 2.0F);
635 pp.depthEdgeStrength = std::clamp(pp.depthEdgeStrength, 0.0F, 2.0F);
636 ImGui::EndDisabled();
637 }
638
639 ImGui::SeparatorText("Debug view");
640 const char *kDebugViewLabels[] = { "Color", "Normals (view-space)", "Depth (linearised preview)" };
641 int debugViewIdx = static_cast<int>(rs.debugView);
642 if (ImGui::Combo("Scene target", &debugViewIdx, kDebugViewLabels, IM_ARRAYSIZE(kDebugViewLabels)))
643 {
644 rs.debugView = static_cast<DebugView>(debugViewIdx);
645 }
646
647 ImGui::SeparatorText("Display");
648 if (ImGui::Checkbox("VSync", &m_vsyncEnabled))
649 {
650 glfwSwapInterval(m_vsyncEnabled ? 1 : 0);
651 }
652 if (ImGui::Checkbox("Wireframe", &m_wireframe))
653 {
654 glPolygonMode(GL_FRONT_AND_BACK, m_wireframe ? GL_LINE : GL_FILL);
655 }
656 if (ImGui::Checkbox("Lock cursor", &m_cursorLocked))
657 {
658 glfwSetInputMode(Engine::GetInstance().GetWindow(),
659 GLFW_CURSOR,
660 m_cursorLocked ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL);
661 }
662}
663
664// ---- Engine tab -------------------------------------------------------
665
666void Editor::DrawEngineBody()
667{
668 Engine &eng = Engine::GetInstance();
669
670 ImGui::SeparatorText("Time");
671 float scale = eng.GetTimeScale();
672 if (ImGui::SliderFloat("Time scale", &scale, 0.0F, 4.0F, "%.2fx"))
673 {
674 eng.SetTimeScale(scale);
675 }
676 ImGui::SameLine();
677 if (ImGui::SmallButton("1x"))
678 {
679 eng.SetTimeScale(1.0F);
680 }
681
682 bool paused = eng.IsPaused();
683 if (ImGui::Checkbox("Pause (dt = 0)", &paused))
684 {
685 eng.SetPaused(paused);
686 }
687
688 ImGui::SeparatorText("Scene");
689 Scene *scene = eng.GetScene();
690 if (scene != nullptr)
691 {
692 ImGui::Text("Root objects: %zu", scene->GetRootObjects().size());
693 } else
694 {
695 ImGui::TextDisabled("No active scene");
696 }
697
698 ImGui::SeparatorText("Application");
699 if (ImGui::Button("Quit"))
700 {
701 if (auto *app = eng.GetApplication())
702 {
703 app->SetNeedsToBeClosed(true);
704 }
705 }
706}
707
708// ---- Physics tab ------------------------------------------------------
709
710void Editor::DrawPhysicsBody()
711{
713 if (world == nullptr)
714 {
715 ImGui::TextDisabled("Physics world not available");
716 return;
717 }
718
719 btVector3 g = world->getGravity();
720 float grav[3] = {g.x(), g.y(), g.z()};
721 if (ImGui::DragFloat3("Gravity", grav, 0.1F, -100.0F, 100.0F))
722 {
723 world->setGravity(btVector3(grav[0], grav[1], grav[2]));
724 }
725 if (ImGui::Button("Earth (0,-9.81,0)"))
726 {
727 world->setGravity(btVector3(0.0F, -9.81F, 0.0F));
728 }
729 ImGui::SameLine();
730 if (ImGui::Button("Zero-G"))
731 {
732 world->setGravity(btVector3(0.0F, 0.0F, 0.0F));
733 }
734
735 int bodies = world->getNumCollisionObjects();
736 ImGui::Text("Collision objects: %d", bodies);
737}
738
739// ---- Stats ------------------------------------------------------------
740
741void Editor::DrawStats()
742{
743 if (!ImGui::Begin("Stats", &m_showStats, kDockedFlags | ImGuiWindowFlags_NoTitleBar))
744 {
745 ImGui::End();
746 return;
747 }
748 f32 dt = ImGui::GetIO().DeltaTime;
749 f32 fps = (dt > 0.0F) ? (1.0F / dt) : 0.0F;
750 m_fpsSmoothed = (m_fpsSmoothed == 0.0F) ? fps : (m_fpsSmoothed * 0.9F + fps * 0.1F);
751
752 ImGui::Text("FPS: %6.1f", m_fpsSmoothed);
753 ImGui::Text("Frame time: %6.2f ms", dt * 1000.0F);
754 ImGui::Text("Draw calls: %d", m_lastDrawCount);
755 ImGui::End();
756}
757
758// ---- Player panel -----------------------------------------------------
759
760namespace
761{
762PlayerControllerComponent *FindPlayer(GameObject *obj)
763{
764 if (obj == nullptr)
765 {
766 return nullptr;
767 }
768 if (auto *player = obj->GetComponent<PlayerControllerComponent>())
769 {
770 return player;
771 }
772 for (const auto &child : obj->GetChildren())
773 {
774 if (auto *found = FindPlayer(child.get()))
775 {
776 return found;
777 }
778 }
779 return nullptr;
780}
781} // namespace
782
783void Editor::DrawPlayerBody()
784{
785 Scene *scene = Engine::GetInstance().GetScene();
786 PlayerControllerComponent *player = nullptr;
787 if (scene != nullptr)
788 {
789 for (const auto &root : scene->GetRootObjects())
790 {
791 player = FindPlayer(root.get());
792 if (player != nullptr)
793 {
794 break;
795 }
796 }
797 }
798 if (player == nullptr)
799 {
800 ImGui::TextDisabled("No PlayerControllerComponent in scene");
801 return;
802 }
803
804 ImGui::SeparatorText("Look");
805 f32 sens = player->GetSensitivity();
806 if (ImGui::SliderFloat("Sensitivity (deg/px)", &sens, 0.01F, 1.0F, "%.3f"))
807 {
808 player->SetSensitivity(sens);
809 }
810
811 ImGui::SeparatorText("Movement");
812 f32 moveSpeed = player->GetMoveSpeed();
813 if (ImGui::SliderFloat("Max speed (m/s)", &moveSpeed, 1.0F, 20.0F, "%.2f"))
814 {
815 player->SetMoveSpeed(moveSpeed);
816 }
817 f32 jumpSpeed = player->GetJumpSpeed();
818 if (ImGui::SliderFloat("Jump x maxSpeed", &jumpSpeed, 0.0F, 2.0F, "%.2f"))
819 {
820 player->SetJumpSpeed(jumpSpeed);
821 }
822 ImGui::Text(" -> jump impulse: %.2f m/s", moveSpeed * jumpSpeed);
823
824 ImGui::SeparatorText("Acceleration");
825 f32 groundAccel = player->GetGroundAccel();
826 if (ImGui::SliderFloat("Ground accel", &groundAccel, 0.0F, 30.0F, "%.2f"))
827 {
828 player->SetGroundAccel(groundAccel);
829 }
830 f32 airAccel = player->GetAirAccel();
831 if (ImGui::SliderFloat("Air accel", &airAccel, 0.0F, 30.0F, "%.2f"))
832 {
833 player->SetAirAccel(airAccel);
834 }
835 f32 fric = player->GetFriction();
836 if (ImGui::SliderFloat("Friction", &fric, 0.0F, 12.0F, "%.2f"))
837 {
838 player->SetFriction(fric);
839 }
840 f32 cap = player->GetAirCap();
841 if (ImGui::SliderFloat("Air wishspeed cap", &cap, 0.1F, 30.0F, "%.2f"))
842 {
843 player->SetAirCap(cap);
844 }
845 ImGui::TextDisabled("Low cap (~0.76) = Q3 strafe-jump; high = HL air control");
846
847 bool hop = player->GetAutoHop();
848 if (ImGui::Checkbox("Auto bunny-hop (hold Space)", &hop))
849 {
850 player->SetAutoHop(hop);
851 }
852
853 ImGui::SeparatorText("Presets");
854 if (ImGui::Button("HL1"))
855 {
856 player->SetMoveSpeed(7.5F);
857 player->SetJumpSpeed(0.9F);
858 player->SetGroundAccel(10.0F);
859 player->SetAirAccel(10.0F);
860 player->SetFriction(4.0F);
861 player->SetAirCap(30.0F);
862 }
863 ImGui::SameLine();
864 if (ImGui::Button("Quake3"))
865 {
866 player->SetMoveSpeed(8.0F);
867 player->SetJumpSpeed(1.0F);
868 player->SetGroundAccel(10.0F);
869 player->SetAirAccel(1.0F);
870 player->SetFriction(6.0F);
871 player->SetAirCap(0.76F);
872 }
873 ImGui::SameLine();
874 if (ImGui::Button("Source"))
875 {
876 player->SetMoveSpeed(7.6F);
877 player->SetJumpSpeed(0.85F);
878 player->SetGroundAccel(10.0F);
879 player->SetAirAccel(10.0F);
880 player->SetFriction(4.0F);
881 player->SetAirCap(30.0F);
882 }
883
884 ImGui::SeparatorText("Live");
885 ImGui::Text("On ground: %s", player->OnGround() ? "yes" : "no");
886}
887
888} // namespace mnd
constexpr const char * kGlslVersionDirective
Definition Constants.h:56
ImVec2 inspectorSize
Definition Editor.cpp:136
ImVec2 statsSize
Definition Editor.cpp:139
ImVec2 consolePos
Definition Editor.cpp:137
ImVec2 statsPos
Definition Editor.cpp:139
ImVec2 settingsSize
Definition Editor.cpp:135
ImVec2 hierarchySize
Definition Editor.cpp:138
ImVec2 hierarchyPos
Definition Editor.cpp:138
ImVec2 inspectorPos
Definition Editor.cpp:136
ImVec2 settingsPos
Definition Editor.cpp:135
ImVec2 consoleSize
Definition Editor.cpp:137
In-game ImGui overlay: hierarchy, inspector, console, stats.
Console logging macros for all engine and game code.
#define LOG_INFO(fmt,...)
Definition Log.h:79
#define LOG_ERROR(fmt,...)
Definition Log.h:81
Per-frame renderer tuning knobs (PSX-style pixelation, fog, ambient).
Perspective camera — provides view and projection matrices each frame.
void EndFrame()
Renders the ImGui draw data to the current framebuffer.
Definition Editor.cpp:241
void Shutdown()
Tear down ImGui state. Call before destroying the GL context.
Definition Editor.cpp:60
bool WantsCaptureKeyboard() const
Same as WantsCaptureMouse() but for the keyboard.
Definition Editor.cpp:256
void ToggleVisible()
Flip overlay visibility.
Definition Editor.h:59
bool Init(GLFWwindow *window)
Initialise ImGui against the given GLFW window. Call once at startup.
Definition Editor.cpp:35
void BeginFrame()
Call after glfwPollEvents.
Definition Editor.cpp:72
bool WantsCaptureMouse() const
Definition Editor.cpp:251
void Draw()
Builds all panels (no GL state changes yet).
Definition Editor.cpp:182
PostProcess & GetPostProcess()
Outline / highlight post-pass; tunables exposed via the Editor render panel.
Definition Engine.h:158
Scene * GetScene()
Returns the currently active scene, or nullptr if none is set.
Definition Engine.cpp:448
InputManager & GetInputManager()
Returns the InputManager that tracks keyboard and mouse state.
Definition Engine.cpp:413
static Engine & GetInstance()
Returns the single Engine instance (created on first call).
Definition Engine.cpp:59
RenderSettings & GetRenderSettings()
Mutable render settings edited by the Editor's Render panel.
Definition Engine.h:152
PhysicsManager & GetPhysicsManager()
Definition Engine.cpp:443
Node in the scene graph: a named transform that owns components and children.
Definition GameObject.h:91
const vec3 & GetScale() const
const vec3 & GetPosition() const
void SetActive(bool active)
Show/hide this object (and stop Update calls).
void SetPosition(const vec3 &pos)
const str & GetName() const
Returns the object's display name.
void SetRotation(const quat &rot)
void SetScale(const vec3 &scale)
const quat & GetRotation() const
bool IsActive() const
Returns false if the object is hidden.
T * GetComponent()
Find and return the first component of type T attached to this object.
Definition GameObject.h:125
Point light that contributes position + colour to the render pass.
void SetColor(const vec3 &color)
Set the light's emission colour.
const vec3 & GetColor() const
Returns the linear RGB colour of this light (default: white {1,1,1}).
Makes a GameObject renderable by submitting its mesh each frame.
btDiscreteDynamicsWorld * GetWorld()
Raw pointer to the underlying Bullet world (for advanced/internal use).
glm::vec2 vec2
2-component float vector (e.g. UV coordinates, mouse position).
Definition Types.h:50
glm::vec3 vec3
3-component float vector (e.g. world position, RGB colour, normals).
Definition Types.h:51
glm::quat quat
Unit quaternion for rotation (avoids gimbal lock).
Definition Types.h:65
double f64
64-bit double — used for high-precision timing.
Definition Types.h:41
float f32
32-bit IEEE float — the standard GL scalar type.
Definition Types.h:40
DebugView
Renderer tuning struct passed to the post-process shader each frame.
static void InspectPlayer(PlayerControllerComponent *c)
Definition Editor.cpp:407
static void InspectTransform(GameObject *obj)
Definition Editor.cpp:345
void LogClear()
Clear the in-memory log buffer.
Definition Log.cpp:67
static void InspectMesh(MeshComponent *)
Definition Editor.cpp:431
static void InspectLight(LightComponent *c)
Definition Editor.cpp:394
static void InspectCamera(CameraComponent *c)
Definition Editor.cpp:371
const std::deque< LogEntry > & LogGetEntries()
Snapshot of the in-memory log buffer (bounded ring).
Definition Log.cpp:62