#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include void Editor::ApplyViewportResize() { const glm::ivec2 desired = m_Viewport.desiredSize; const VkExtent3D current = m_App.getRenderer().getDrawImage().extent; if (desired.x <= 0 || desired.y <= 0 || (static_cast(current.width) == desired.x && static_cast(current.height) == desired.y)) { return; } // While a dock splitter is dragged the old image is stretched, recreating it every frame is wasteful if (ImGui::GetIO().MouseDown[ImGuiMouseButton_Left]) { return; } m_App.resizeRenderTarget(desired); } void Editor::DrawViewport() { m_Viewport.mouseClaimed = false; ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2{0.0f, 0.0f}); const bool visible = ImGui::Begin("Viewport", nullptr, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse); ImGui::PopStyleVar(); if (!visible) { m_Viewport.hovered = false; m_Viewport.focused = false; m_Viewport.hoverPixel.reset(); ImGui::End(); return; } const ImVec2 origin = ImGui::GetCursorScreenPos(); const ImVec2 available = ImGui::GetContentRegionAvail(); const ImVec2 framebufferScale = ImGui::GetIO().DisplayFramebufferScale; m_Viewport.min = {origin.x, origin.y}; m_Viewport.size = {std::max(available.x, 1.0f), std::max(available.y, 1.0f)}; m_Viewport.desiredSize = { std::max(static_cast(available.x * framebufferScale.x), 1), std::max(static_cast(available.y * framebufferScale.y), 1), }; const GPUImage& drawImage = m_App.getRenderer().getDrawImage(); if (const ImTextureID texture = m_App.getImguiPass().getFrameTexture(drawImage.imageView); texture != ImTextureID{}) { ImGui::Image(texture, ImVec2{m_Viewport.size.x, m_Viewport.size.y}); if (ImGui::BeginDragDropTarget()) { if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload(ImGuiUtils::AssetPayload)) { OnAssetDropped(static_cast(payload->Data)); } ImGui::EndDragDropTarget(); } } m_Viewport.hovered = ImGui::IsWindowHovered(); // ImGui only focuses windows on a left click, the camera looks around with the right button if (m_Viewport.hovered && (ImGui::IsMouseClicked(ImGuiMouseButton_Right) || ImGui::IsMouseClicked(ImGuiMouseButton_Middle))) { ImGui::SetWindowFocus(); } m_Viewport.focused = ImGui::IsWindowFocused(); const ImVec2 mouse = ImGui::GetMousePos(); const glm::vec2 uv = (glm::vec2{mouse.x, mouse.y} - m_Viewport.min) / m_Viewport.size; const bool insideImage = uv.x >= 0.0f && uv.y >= 0.0f && uv.x < 1.0f && uv.y < 1.0f; m_Viewport.hoverPixel.reset(); if (m_Viewport.hovered && insideImage && !m_Camera.IsLooking()) { const VkExtent3D extent = drawImage.extent; m_Viewport.hoverPixel = glm::ivec2{uv * glm::vec2{static_cast(extent.width), static_cast(extent.height)}}; } ImGui::SetCursorScreenPos(origin); if (Scene* scene = CurrentScene(); scene != nullptr && m_View.gizmos && UsesEditorCamera()) { DrawGizmos(*scene); } RequestPick(); ImGui::End(); } void Editor::DrawGizmos(Scene& scene) { GameObject* selected = scene.GetSelectedObject(); if (selected == nullptr) { return; } if (RectGizmo::CanManipulate(*selected)) { const VkExtent3D extent = m_App.getRenderer().getDrawImage().extent; m_RectGizmo.Manipulate(*selected, glm::vec2{static_cast(extent.width), static_cast(extent.height)}, m_Viewport.min, m_Viewport.size); m_Viewport.mouseClaimed = m_Viewport.mouseClaimed || m_RectGizmo.IsInteracting(); return; } m_TransformGizmo.Manipulate(*selected, m_Camera.GetCamera(), m_Viewport.min, m_Viewport.size); m_Viewport.mouseClaimed = m_Viewport.mouseClaimed || m_TransformGizmo.IsOver() || m_TransformGizmo.IsUsing(); } void Editor::RequestPick() { const bool picking = m_PlayMode.IsEditing() || m_PlayMode.IsPaused(); if (!picking || !m_Viewport.hoverPixel || m_Viewport.mouseClaimed || !ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { return; } m_App.getRenderer().requestSelectionPick(*m_Viewport.hoverPixel); m_Viewport.pickSelectsParent = ImGui::GetIO().KeyAlt; } void Editor::ApplyPick() { const auto picked = m_App.getRenderer().takeSelectionPick(); Scene* scene = CurrentScene(); if (!picked || scene == nullptr) { return; } GameObject* object = scene->FindGameObject(*picked); if (object != nullptr && m_Viewport.pickSelectsParent) { if (const Transform* parent = object->GetTransform().GetParent(); parent != nullptr && parent->GetOwner() != nullptr) { object = parent->GetOwner(); } } scene->SetSelectedObject(object != nullptr ? object->GetId() : InvalidObjectId); } void Editor::OnAssetDropped(std::string_view path) { Scene* scene = CurrentScene(); if (scene == nullptr) { return; } if (AssetTypes::IsScene(path)) { OpenScene(path); return; } const Camera& view = m_Camera.GetCamera(); const glm::vec3 spawnPosition = view.GetPosition() + view.GetForward() * 5.0f; const std::string name = std::filesystem::path{path}.stem().string(); try { if (AssetTypes::IsModel(path)) { ModelDoc::LoadOptions options{}; options.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode; options.loadMaterials = true; GameObject* model = ModelSpawner::SpawnStaticModel( *scene, m_App.getGfxDevice(), m_App.getResources(), path, options, name); model->GetTransform().SetWorldPosition(spawnPosition); scene->SetSelectedObject(model->GetId()); } else if (AssetTypes::IsPrefab(path)) { GameObject* instance = PrefabSerializer::Instantiate(*scene, AssetFS::GetInstance().GetFullPath(path).string()); if (instance == nullptr) { SetStatus("Could not instantiate " + std::string{path}); return; } instance->GetTransform().SetWorldPosition(spawnPosition); scene->SetSelectedObject(instance->GetId()); } else if (AssetTypes::IsAudio(path)) { GameObject* object = scene->CreateGameObject(name); object->AddComponent(std::string{path}); object->GetTransform().SetWorldPosition(spawnPosition); scene->SetSelectedObject(object->GetId()); } else if (AssetTypes::IsMaterial(path)) { GameObject* selected = scene->GetSelectedObject(); if (selected == nullptr) { SetStatus("Select an object to give it " + std::string{path}); return; } const MaterialID material = MaterialSerialization::LoadFile(m_App.getResources(), m_App.getGfxDevice(), path); std::vector pending{selected}; while (!pending.empty()) { GameObject* object = pending.back(); pending.pop_back(); if (auto* meshRenderer = object->GetComponent()) { meshRenderer->SetMaterialID(material); } for (const Transform* child : object->GetTransform().GetChildren()) { if (child != nullptr && child->GetOwner() != nullptr) { pending.push_back(child->GetOwner()); } } } } else { return; } m_EditPending = true; } catch (const std::exception& exception) { spdlog::error("Could not use {}: {}", path, exception.what()); SetStatus("Could not use " + std::string{path}); } } void Editor::SubmitColliderLines(Scene& scene) const { // Triggers orange, then colored by body type: static blue, dynamic green, kinematic purple for (const auto& object : scene.GetObjects()) { if (object == nullptr || object->IsBeingDestroyed() || !object->IsActiveInHierarchy()) { continue; } const auto* rigidbody = object->GetComponent(); const RigidbodyType type = rigidbody != nullptr ? rigidbody->GetType() : RigidbodyType::Static; for (const auto& component : object->GetComponents()) { const auto* collider = dynamic_cast(component.get()); if (collider == nullptr || collider->IsBeingDestroyed() || !collider->isEnabled()) { continue; } glm::vec4 color{0.1f, 0.8f, 1.0f, 1.0f}; if (collider->IsTrigger()) { color = {1.0f, 0.75f, 0.1f, 1.0f}; } else if (type == RigidbodyType::Dynamic) { color = {0.3f, 1.0f, 0.3f, 1.0f}; } else if (type == RigidbodyType::Kinematic) { color = {0.8f, 0.4f, 1.0f, 1.0f}; } collider->DrawDebugLines(color); } } }