viewport.cpp

cross platform rendering playground

src/editor/viewport.cpp

5.3 KB
#ifdef ENABLE_EDITOR
#define IMGUI_DEFINE_MATH_OPERATORS
#include "editor.h"

#include <imgui.h>

#include <algorithm>

#include "backend/imgui_backend.h"
#include "quick_submit.h"
#include "renderer.h"

namespace editor {

void draw_viewport_central(Editor &e, RenderData &rd) {
    if (!rhi::valid(rd.display)) {
        return;
    }
    rhi::Image &img = rd.display;
    if (!rhi::valid(img)) {
        return;
    }

    static rhi::ImageView live{};
    if (rhi::valid(live)) {
        rhi::destroy_image_view(*e.device, live);
        live = {};
    }
    rhi::ImageView view{};
    if (!rhi::create_image_view(*e.device, {.image = &img, .aspect = rhi::ImageAspect::Color, .mip_count = 1}, view)) {
        return;
    }
    u64 slot = imgui_backend::texture(view);
    if (slot == UINT64_MAX) {
        rhi::destroy_image_view(*e.device, view);
        return;
    }
    live = view;

    ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));

    ImGui::Begin("Viewport", nullptr);

    ImVec2 viewport_size = ImGui::GetContentRegionAvail();
    if (viewport_size.x > 0.0f && viewport_size.y > 0.0f) {
        ImVec2 image_start_pos = ImGui::GetCursorPos();
        ImGui::Image((ImTextureID)(u64)slot, viewport_size);

        e.gizmo_view_min = ImGui::GetItemRectMin();
        e.gizmo_view_size = viewport_size;

        static ImVec2 overlay_min = {0, 0};
        static ImVec2 overlay_max = {0, 0};

        bool clicking_overlay = ImGui::IsMouseHoveringRect(overlay_min, overlay_max, false);

        // block click passthrough to viewport if its on the overlay window, compared to bounds grabbed on inti
        if (!clicking_overlay && ImGui::IsItemHovered() && ImGui::IsItemClicked(ImGuiMouseButton_Left) &&
            !e.gizmo_dragging && e.gizmo_hover_axis < 0 && rhi::valid(rd.id_blit)) {

            rhi::Image &id_img = rd.id_blit;
            ImVec2 rel = {
                ImGui::GetMousePos().x - ImGui::GetItemRectMin().x, ImGui::GetMousePos().y - ImGui::GetItemRectMin().y
            };
            i64 tx = (i64)(rel.x * (f32)id_img.desc.width / viewport_size.x);
            i64 ty = (i64)(rel.y * (f32)id_img.desc.height / (viewport_size.y));
            ty = id_img.desc.height - 1 - ty; // Flip Vulkan viewport

            rhi::ImageView id_view{};
            if (rhi::create_image_view(
                    *e.device, {.image = &id_img, .aspect = rhi::ImageAspect::Color, .mip_count = 1}, id_view
                )) {
                u32 raw = 0;
                if (e.qs != nullptr && readback_texel_raw(*e.device, *e.qs, id_view, 0, 0, tx, ty, &raw, sizeof(raw)) &&
                    raw > 0) {
                    e.selected_node = (int)raw - 1;
                } else {
                    e.selected_node = -1;
                }
                rhi::destroy_image_view(*e.device, id_view);
            } else {
                e.selected_node = -1;
            }
        }

        ImGui::SetCursorPos(ImVec2(image_start_pos.x + 10.0f, image_start_pos.y + 10.0f));

        if (ImGui::BeginChild(
                "##GizmoOverlay",
                ImVec2(0.0f, 38.0f),
                ImGuiChildFlags_AlwaysAutoResize | ImGuiChildFlags_AutoResizeX,
                ImGuiWindowFlags_NoScrollbar
            )) {

            // need to track the bounds of this window, to decipher if its a viewport click or this window click
            overlay_min = ImGui::GetWindowPos();
            overlay_max = ImVec2(overlay_min.x + ImGui::GetWindowSize().x, overlay_min.y + ImGui::GetWindowSize().y);

            if (ImGui::RadioButton("Move", e.gizmo_tool == GIZMO_TOOL_MOVE)) {
                e.gizmo_tool = GIZMO_TOOL_MOVE;
                e.gizmo_dragging = false;
            }
            ImGui::SameLine();
            if (ImGui::RadioButton("Rot", e.gizmo_tool == GIZMO_TOOL_ROTATE)) {
                e.gizmo_tool = GIZMO_TOOL_ROTATE;
                e.gizmo_dragging = false;
            }
            ImGui::SameLine();
            if (ImGui::RadioButton("Scale", e.gizmo_tool == GIZMO_TOOL_SCALE)) {
                e.gizmo_tool = GIZMO_TOOL_SCALE;
                e.gizmo_dragging = false;
            }

            ImGui::SameLine();
            if (ImGui::Button(e.gizmo_world ? "World" : "Local")) {
                e.gizmo_world = !e.gizmo_world;
            }

            ImGui::SameLine();
            ImGui::Checkbox("Snap", &e.gizmo_snap);

            if (e.gizmo_snap) {
                ImGui::SameLine();
                ImGui::SetNextItemWidth(128.0f);
                if (e.gizmo_tool == GIZMO_TOOL_ROTATE) {
                    ImGui::DragFloat("##snapangle", &e.gizmo_snap_angle, 0.5f, 0.1f, 90.0f, "%.1f deg");
                } else if (e.gizmo_tool == GIZMO_TOOL_SCALE) {
                    ImGui::DragFloat("##snapscale", &e.gizmo_snap_scale, 0.01f, 0.01f, 1.0f, "%.2f");
                } else {
                    ImGui::DragFloat("##snapstep", &e.gizmo_snap_step, 0.01f, 0.01f, 10.0f, "%.2f");
                }
            }
        }
        ImGui::EndChild();
    }

    u32 nw = (u32)(viewport_size.x + 0.5f), nh = (u32)(viewport_size.y + 0.5f);
    if (nw > 0 && nh > 0 && (nw != e.viewport_width || nh != e.viewport_height)) {
        e.viewport_width = nw;
        e.viewport_height = nh;
        e.viewport_resized = true;
    }

    ImGui::End();
    ImGui::PopStyleVar();
}

} // namespace editor

#endif // ENABLE_EDITOR