246 lines
9.7 KiB
C++
246 lines
9.7 KiB
C++
#include <editor/Editor.h>
|
|
|
|
#include <algorithm>
|
|
#include <filesystem>
|
|
#include <vector>
|
|
|
|
#include <imgui.h>
|
|
#include <spdlog/spdlog.h>
|
|
|
|
#include <destrum/App.h>
|
|
#include <destrum/Assets/AssetTypes.h>
|
|
#include <destrum/Components/AudioSource.h>
|
|
#include <destrum/Components/MeshRendererComponent.h>
|
|
#include <destrum/Components/Physics/Collider.h>
|
|
#include <destrum/Components/Physics/Rigidbody.h>
|
|
#include <destrum/FS/AssetFS.h>
|
|
#include <destrum/ObjectModel/GameObject.h>
|
|
#include <destrum/ObjectModel/Transform.h>
|
|
#include <destrum/Scene/ModelSpawner.h>
|
|
#include <destrum/Scene/Scene.h>
|
|
#include <destrum/Serialization/MaterialSerialization.h>
|
|
#include <destrum/Serialization/PrefabSerializer.h>
|
|
#include <destrum/Util/ImGuiUtils.h>
|
|
#include <destrum/Util/ModelDoc.h>
|
|
|
|
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<int>(current.width) == desired.x && static_cast<int>(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<int>(available.x * framebufferScale.x), 1),
|
|
std::max(static_cast<int>(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<const char*>(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<float>(extent.width), static_cast<float>(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<float>(extent.width), static_cast<float>(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<AudioSource>(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<GameObject*> pending{selected};
|
|
while (!pending.empty()) {
|
|
GameObject* object = pending.back();
|
|
pending.pop_back();
|
|
if (auto* meshRenderer = object->GetComponent<MeshRendererComponent>()) {
|
|
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<Rigidbody>();
|
|
const RigidbodyType type = rigidbody != nullptr ? rigidbody->GetType() : RigidbodyType::Static;
|
|
for (const auto& component : object->GetComponents()) {
|
|
const auto* collider = dynamic_cast<const Collider*>(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);
|
|
}
|
|
}
|
|
}
|