40 if(m_gameInfo.versionMajor == 0 && m_gameInfo.versionMinor == 0 && m_gameInfo.versionPatch == 0){
41 log(
"Project version not set!");
44 log(
"Creating window..");
45 m_window = std::make_shared<Window>(title, width, height);
46 m_resolutionManager = std::make_unique<ResolutionManager>(m_window->GetSDLWindow());
49 const char* driver = SDL_GetCurrentVideoDriver();
50 if (driver && std::string(driver) ==
"wayland") {
52 log(
"Wayland detected: Enforcing Software VSync strategy.");
56 log(
"Initializing virtual file system..\nSwitching vfs path to: %s", projectBinaryPath.c_str());
57 m_vfs = std::make_shared<VirtualFileSystem>();
58 m_vfs->initialize(projectBinaryPath);
60 m_physicsSystem = std::make_unique<PhysicsSystem>(m_registry);
61 m_physicsSystem->init();
63 auto renderRes = m_resolutionManager->getRenderResolution();
64 log(
"Initializing Vulkan interface...");
65 m_interface = std::make_unique<Interface>(m_window->GetSDLWindow(), renderRes, m_gameInfo, m_vfs.get());
66 m_imgui = std::make_unique<EditorImGUIWrapper>(m_window->GetSDLWindow(), *m_interface->getContext());
69 ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_ViewportsEnable;
71 vex::DebugConsole::Get().Init();
73 log(
"Initializing engine components...");
75 m_inputSystem = std::make_unique<InputSystem>(m_registry, m_window->GetSDLWindow());
76 m_sceneManager = std::make_unique<SceneManager>();
81 RegisterEngineCommands(
this);
83 log(
"Initializing editor components...");
85 std::filesystem::path projectPath = projectBinaryPath;
86 std::filesystem::path assetsPath = projectPath /
"Assets";
87 m_assetBrowser = std::make_unique<AssetBrowser>(assetsPath.string());
90 m_projectBinaryPath = projectBinaryPath;
92 m_camera = std::make_unique<EditorCameraObject>(*
this,
"VexEditorCamera", m_window->GetSDLWindow());
93 m_editorMenuBar = std::make_unique<EditorMenuBar>(*m_imgui, *
this);
95 LoadConfig(m_editorProperties,
"editor_config.json");
96 m_SavedEditorProperties = m_editorProperties;
101 std::filesystem::path projectConfigPath = projectPath /
"VexProject.json";
102 log(
"Loading project configuration from: %s", projectConfigPath.string().c_str());
106 log(
"Editor initialized successfully");
409 m_isHoveringGizmoUI =
false;
411 auto selectedEntity = m_selectedObject.second ? m_selectedObject.second->GetEntity() :
vex::NULL_ENTITY;
414 ImGuizmo::SetOrthographic(
false);
415 ImGuizmo::SetDrawlist();
416 ImGuizmo::SetRect(viewportPos.x, viewportPos.y, viewportSize.x, viewportSize.y);
420 auto& camComp = m_registry.get<
CameraComponent>(m_camera->GetEntity());
422 glm::mat4 view = glm::lookAt(
423 camTC.getWorldPosition(),
424 camTC.getWorldPosition() + camTC.getForwardVector(),
428 float aspect = viewportSize.x / viewportSize.y;
429 glm::mat4 proj = glm::perspective(glm::radians(camComp.fov), aspect, camComp.nearPlane, camComp.farPlane);
432 glm::mat4 transformMatrix = tc.
matrix();
434 ImGui::SetCursorScreenPos(ImVec2(viewportPos.x + 10, viewportPos.y + 32));
436 auto gizmoButton = [&](
const char* label, ImGuizmo::OPERATION op) {
437 bool wasActive = (m_currentGizmoOperation == op);
438 if (wasActive) ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.00f, 0.23f, 0.01f, 1.0f));
439 if (ImGui::Button(label)) m_currentGizmoOperation = op;
440 if (wasActive) ImGui::PopStyleColor();
441 if (ImGui::IsItemHovered()) m_isHoveringGizmoUI =
true;
445 gizmoButton(
"T", ImGuizmo::TRANSLATE);
446 gizmoButton(
"R", ImGuizmo::ROTATE);
447 gizmoButton(
"S", ImGuizmo::SCALE);
449 ImGui::Dummy(ImVec2(10, 0)); ImGui::SameLine();
451 if(m_currentGizmoMode == ImGuizmo::WORLD) {
452 if(ImGui::Button(
"World")) m_currentGizmoMode = ImGuizmo::LOCAL;
454 if(ImGui::Button(
"Local")) m_currentGizmoMode = ImGuizmo::WORLD;
457 if (ImGui::IsKeyPressed(ImGuiKey_LeftCtrl)) m_useSnap =
true;
458 else if (ImGui::IsKeyReleased(ImGuiKey_LeftCtrl)) m_useSnap =
false;
460 float snap[3] = { 0.f, 0.f, 0.f };
462 if (m_currentGizmoOperation == ImGuizmo::TRANSLATE) { snap[0] = snap[1] = snap[2] = 0.5f; }
463 else if (m_currentGizmoOperation == ImGuizmo::ROTATE) { snap[0] = 45.0f; }
464 else if (m_currentGizmoOperation == ImGuizmo::SCALE) { snap[0] = snap[1] = snap[2] = 0.1f; }
467 bool manipulated = ImGuizmo::Manipulate(
468 glm::value_ptr(view),
469 glm::value_ptr(proj),
470 m_currentGizmoOperation,
472 glm::value_ptr(transformMatrix),
474 m_useSnap ? snap :
nullptr
477 bool isUsing = ImGuizmo::IsUsing();
479 if (isUsing && !m_gizmoWasUsing) {
480 m_gizmoStartPos = tc.getLocalPosition();
481 m_gizmoStartRot = tc.rotation;
482 m_gizmoStartScale = tc.getLocalScale();
485 if (!isUsing && m_gizmoWasUsing) {
486 if (tc.getLocalPosition() != m_gizmoStartPos ||
487 tc.rotation != m_gizmoStartRot ||
488 tc.getLocalScale() != m_gizmoStartScale)
491 m_selectedObject.second,
492 m_gizmoStartPos, m_gizmoStartRot, m_gizmoStartScale,
493 tc.getLocalPosition(), tc.rotation, tc.getLocalScale()
497 m_gizmoWasUsing = isUsing;
500 glm::mat4 localMatrix = transformMatrix;
505 localMatrix = glm::inverse(parentMatrix) * transformMatrix;
508 glm::vec3 translation, scale, skew;
510 glm::vec4 perspective;
511 glm::decompose(localMatrix, scale, rotation, translation, skew, perspective);
513 tc.setLocalPosition(translation);
514 tc.setLocalScale(scale);
516 tc.rotation = glm::degrees(glm::eulerAngles(rotation));
519 tc.enableLastTransformed();
605 ImGuiViewport* viewport = ImGui::GetMainViewport();
606 ImGui::SetNextWindowPos(viewport->Pos);
607 ImGui::SetNextWindowSize(viewport->Size);
608 ImGui::SetNextWindowViewport(viewport->ID);
610 ImGuiWindowFlags windowFlags = ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoTitleBar |
611 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
612 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus |
613 ImGuiWindowFlags_NoNavFocus | ImGuiWindowFlags_NoBackground |
614 ImGuiWindowFlags_MenuBar;
616 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
617 ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
618 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
620 ImGui::Begin(
"EditorDockSpace",
nullptr, windowFlags);
621 ImGui::PopStyleVar(3);
623 m_editorMenuBar->DrawBar();
625 ImGuiID dockspaceId = ImGui::GetID(
"MyDockSpace");
627 ImGuiDockNodeFlags dockFlags = ImGuiDockNodeFlags_None;
628 dockFlags |= ImGuiDockNodeFlags_NoWindowMenuButton;
630 if (!ImGui::DockBuilderGetNode(dockspaceId)) {
631 ImGui::DockBuilderRemoveNode(dockspaceId);
632 ImGui::DockBuilderAddNode(dockspaceId, ImGuiDockNodeFlags_DockSpace);
633 ImGui::DockBuilderSetNodeSize(dockspaceId, viewport->Size);
635 ImGuiID dockMainId = dockspaceId;
636 ImGuiID dockRightId = ImGui::DockBuilderSplitNode(dockMainId, ImGuiDir_Right, 0.3f,
nullptr, &dockMainId);
637 ImGuiID dockBottomId = ImGui::DockBuilderSplitNode(dockMainId, ImGuiDir_Down, 0.45f,
nullptr, &dockMainId);
638 ImGuiID dockLeftId = ImGui::DockBuilderSplitNode(dockMainId, ImGuiDir_Left, 0.2f,
nullptr, &dockMainId);
639 ImGuiID dockRightTopId, dockRightBottomId;
640 ImGui::DockBuilderSplitNode(dockRightId, ImGuiDir_Down, 0.5f, &dockRightBottomId, &dockRightTopId);
642 ImGui::DockBuilderDockWindow(
"Viewport", dockMainId);
643 ImGui::DockBuilderDockWindow(
"Game Objects", dockLeftId);
644 ImGui::DockBuilderDockWindow(
"Assets", dockBottomId);
645 ImGui::DockBuilderDockWindow(
"Scene", dockRightTopId);
646 ImGui::DockBuilderDockWindow(
"Properties", dockRightBottomId);
647 ImGui::DockBuilderDockWindow(
"World Settings", dockRightBottomId);
649 ImGui::DockBuilderFinish(dockspaceId);
652 ImGui::DockSpace(dockspaceId, ImVec2(0.0f, 0.0f), dockFlags);
655 ImGuiWindowFlags childFlags = ImGuiWindowFlags_NoCollapse;
657 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
658 ImGui::Begin(
"Viewport",
nullptr, childFlags);
661 m_camera->setViewportHovered(ImGui::IsWindowHovered());
664 ImVec2 viewportPanelSize = ImGui::GetContentRegionAvail();
665 ImVec2 viewportPos = ImGui::GetWindowPos();
666 ImVec2 cursorScreenPos = ImGui::GetCursorScreenPos();
668 outNewResolution = { (uint32_t)viewportPanelSize.x, (uint32_t)viewportPanelSize.y };
670 if (data.imguiTextureID) {
671 ImGui::Image((ImTextureID)data.imguiTextureID, viewportPanelSize);
672 bool isViewportHovered = ImGui::IsItemHovered();
673 auto* drawList = ImGui::GetWindowDrawList();
675 if(m_editorProperties.showFPS) {
677 sprintf(fpsText,
"FPS: %d", m_fps);
681 fpsColor = IM_COL32(0, 255, 0, 255);
682 }
else if (m_fps >= 30) {
683 fpsColor = IM_COL32(255, 255, 0, 255);
685 fpsColor = IM_COL32(255, 0, 0, 255);
688 ImVec2 textPos = ImVec2(cursorScreenPos.x + 10.0f, cursorScreenPos.y + 64.0f);
689 drawList->AddText(ImVec2(textPos.x + 1.0f, textPos.y + 1.0f), IM_COL32(0, 0, 0, 255), fpsText);
690 drawList->AddText(textPos, fpsColor, fpsText);
694 std::string sceneName = std::filesystem::path(currentScenePath).stem().string();
696 if (sceneName.empty()) sceneName =
"Unnamed Scene";
698 ImVec2 iconSize = ImVec2(16.0f, 16.0f);
699 ImVec2 padding = ImVec2(8.0f, 6.0f);
700 float iconTextSpacing = 6.0f;
702 ImVec2 textSize = ImGui::CalcTextSize(sceneName.c_str());
703 float rectWidth = padding.x + iconSize.x + iconTextSpacing + textSize.x + padding.x;
704 float rectHeight = padding.y + std::max(iconSize.y, textSize.y) + padding.y;
706 ImVec2 rectMin = cursorScreenPos;
707 ImVec2 rectMax = ImVec2(rectMin.x + rectWidth, rectMin.y + rectHeight);
709 ImU32 bgColor = IM_COL32(20, 20, 20, 220);
710 drawList->AddRectFilled(rectMin, rectMax, bgColor, 12.0f, ImDrawFlags_RoundCornersBottomRight);
712 ImVec2 iconMin = ImVec2(rectMin.x + padding.x, rectMin.y + padding.y + std::max(0.0f, (textSize.y - iconSize.y) / 2.0f));
713 ImVec2 iconMax = ImVec2(iconMin.x + iconSize.x, iconMin.y + iconSize.y);
716 drawList->AddImage(m_icons.scene, iconMin, iconMax);
719 ImVec2 textPos = ImVec2(iconMax.x + iconTextSpacing, rectMin.y + padding.y + std::max(0.0f, (iconSize.y - textSize.y) / 2.0f));
720 drawList->AddText(textPos, IM_COL32(255, 255, 255, 255), sceneName.c_str());
722 if (ImGui::BeginDragDropTarget()) {
723 if (
const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(
"ASSET_ITEM")) {
724 std::string filepath = (
const char*)payload->Data;
725 std::filesystem::path path(filepath);
726 std::string ext = path.extension().string();
728 if (AssetExtensions::IsValid(ext, AssetExtensions::Mesh)) {
732 ImGui::EndDragDropTarget();
735 drawGizmo(glm::vec2(cursorScreenPos.x, cursorScreenPos.y),
736 glm::vec2(viewportPanelSize.x, viewportPanelSize.y));
738 if (isViewportHovered && !m_isHoveringGizmoUI && ImGui::IsMouseClicked(ImGuiMouseButton_Left) && !ImGuizmo::IsOver()) {
739 ImVec2 mousePos = ImGui::GetMousePos();
740 float mouseX = mousePos.x - viewportPos.x;
741 float mouseY = mousePos.y - viewportPos.y;
743 float ndcX = (mouseX / viewportPanelSize.x) * 2.0f - 1.0f;
744 float ndcY = (mouseY / viewportPanelSize.y) * 2.0f - 1.0f;
747 auto& camComp = m_registry.get<
CameraComponent>(m_camera->GetEntity());
749 glm::mat4 view = glm::lookAt(camTC.getWorldPosition(), camTC.getWorldPosition() + camTC.getForwardVector(), camTC.getUpVector());
750 glm::mat4 proj = glm::perspective(glm::radians(camComp.fov), viewportPanelSize.x / viewportPanelSize.y, camComp.nearPlane, camComp.farPlane);
753 glm::mat4 invProjView = glm::inverse(proj * view);
755 glm::vec4 rayStartClip = glm::vec4(ndcX, ndcY, 0.0f, 1.0f);
756 glm::vec4 rayStartWorld = invProjView * rayStartClip;
757 rayStartWorld /= rayStartWorld.w;
759 glm::vec4 rayEndClip = glm::vec4(ndcX, ndcY, 1.0f, 1.0f);
760 glm::vec4 rayEndWorld = invProjView * rayEndClip;
761 rayEndWorld /= rayEndWorld.w;
763 glm::vec3 rayDir = glm::normalize(glm::vec3(rayEndWorld) - glm::vec3(rayStartWorld));
764 glm::vec3 rayOrigin = glm::vec3(rayStartWorld);
766 float closestDist = std::numeric_limits<float>::max();
770 if (
raySphereIntersect(rayOrigin, rayDir, mesh.worldCenter, mesh.worldRadius) < 0.0f) {
774 glm::mat4 modelMat = transform.
matrix();
775 glm::mat4 invModel = glm::inverse(modelMat);
777 glm::vec4 localOrigin4 = invModel * glm::vec4(rayOrigin, 1.0f);
778 glm::vec3 localOrigin = glm::vec3(localOrigin4) / localOrigin4.w;
779 glm::vec3 localDir = glm::vec3(invModel * glm::vec4(rayDir, 0.0f));
780 localDir = glm::normalize(localDir);
782 float localClosest = std::numeric_limits<float>::max();
783 bool hitMesh =
false;
785 for (
const auto& submesh : mesh.meshData.submeshes) {
786 for (
size_t i = 0; i < submesh.indices.size(); i += 3) {
787 const auto& v0 = submesh.vertices[submesh.indices[i]].position;
788 const auto& v1 = submesh.vertices[submesh.indices[i+1]].position;
789 const auto& v2 = submesh.vertices[submesh.indices[i+2]].position;
793 if (t > 0.0f && t < localClosest) {
801 glm::vec3 hitPointLocal = localOrigin + (localDir * localClosest);
802 glm::vec3 hitPointWorld = glm::vec3(modelMat * glm::vec4(hitPointLocal, 1.0f));
804 float dist = glm::distance(rayOrigin, hitPointWorld);
806 if (dist < closestDist) {
815 log(
"Selected Entity ID: %d", (uint32_t)hitEntity);
817 m_selectedObject.first =
false;
818 m_selectedObject.second =
nullptr;
824 ImGui::Text(
"Viewport Texture Could not be retrieved.");
828 ImGui::PopStyleVar();
830 ImGui::Begin(
"Game Objects",
nullptr, childFlags);
833 ImGui::Dummy(ImVec2(5.0f, 5.0f));
834 ImGui::Text(
"Engine Objects:");
837 for (
const auto& type : engineTypes) {
838 if (ImGui::Button(type.c_str(), ImVec2(-1, 0))) {
840 if (!currentScene.empty()) {
842 std::string newName =
"New " + type;
845 if(m_selectedObject.second){
847 newObj->
ParentTo(m_selectedObject.second->GetEntity());
858 log(LogLevel::ERROR,
"No scene loaded. Cannot create object.");
865 ImGui::Dummy(ImVec2(10.0f, 10.0f));
866 ImGui::Text(
"Game Objects:");
869 for (
const auto& type : dynamicTypes) {
870 if (ImGui::Button(type.c_str(), ImVec2(-1, 0))) {
872 if (!currentScene.empty()) {
874 std::string newName =
"New " + type;
877 if(m_selectedObject.second){
879 newObj->
ParentTo(m_selectedObject.second->GetEntity());
890 log(LogLevel::ERROR,
"No scene loaded. Cannot create object.");
896 ImGui::Begin(
"Assets",
nullptr, childFlags);
898 m_isAssetBrowserFocused = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows);
900 if (m_assetBrowser) {
901 std::string openedFile = m_assetBrowser->Draw(m_icons, m_editorProperties.assetBrowserThumbnailSize);
903 if (!openedFile.empty()) {
904 log(
"Opening file: %s", openedFile.c_str());
905 if (m_assetBrowser->GetExtension(openedFile) ==
".json") {
906 if (m_assetBrowser->GetJSONAssetType(openedFile) == 1){
912 ImGui::Text(
"Could not load assets");
916 ImGui::Begin(
"Scene",
nullptr, childFlags);
920 ImGui::Begin(
"Properties",
nullptr, childFlags);
921 if(m_selectedObject.second){
926 ImGui::Begin(
"World Settings",
nullptr, childFlags);
930 if (!m_pendingSceneToLoad.empty() || m_pendingSceneToLoad !=
"") {
932 if (ImGui::Begin(
"Opening scene...",
nullptr, ImGuiWindowFlags_AlwaysAutoResize)) {
933 ImGui::Text(
"Loading scene: %s", m_pendingSceneToLoad.c_str());