inspector.cpp

cross platform rendering playground

src/editor/inspector.cpp

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

#include <imgui.h>

#include <cmath>
#include <cstdio>

#include "core/math_rtm.h"
#include "scene/camera.h"
#include "scene/scene.h"

namespace editor {

namespace {

inline bool edit_euler_cached(const char *label, EulerEdit &st, int id, const quat &cur) {
    if (st.id != id || !st.sync.same_rotation(cur)) {
        st.id = id;
        st.euler = quat_to_euler(cur);
        st.sync = cur;
    }
    return ImGui::DragFloat3(label, &st.euler.x, 0.5f);
}

void draw_transform_panel(SceneGraph &sg, u32 node, EulerEdit &rot_state) {
    ImGui::Text("Index: %u", node);
    ImGui::Text("Parent: %d", sg.parent[node]);
    ImGui::Text("Children: %s", sg.first_child[node] == SCENE_INVALID ? "none" : "yes");
    ImGui::Separator();

    vec3 t = sg.translations[node];
    if (ImGui::DragFloat3("Translation", &t.x, 0.05f)) {
        sg.set_translation(node, t);
    }

    if (edit_euler_cached("Rotation (pitch, yaw, roll) deg", rot_state, (int)node, quat(sg.rotations[node]))) {
        sg.set_rotation(node, rtm::quat_normalize((rtm::quatf)euler_to_quat(rot_state.euler).normalized()));
        rot_state.sync = quat(sg.rotations[node]);
    }

    vec3 s = sg.scales[node];
    if (ImGui::DragFloat3("Scale", &s.x, 0.01f)) {
        sg.set_scale(node, s);
    }

    vec4 wh = transform_point(vec3(0.0f, 0.0f, 0.0f), sg.worlds[node]);
    ImGui::Text("World pos: %.3f %.3f %.3f", wh.x, wh.y, wh.z);
}

void draw_mesh_inspector(Scene &scene, const AssetManager &mgr, u32 node, EulerEdit &inst_rot_state) {
    SceneGraph &sg = scene.scene_graph;

    bool is_mesh = false;
    for (u32 mn : scene.mesh_nodes) {
        if (mn == node) {
            is_mesh = true;
            break;
        }
    }
    if (!is_mesh) {
        return;
    }

    ImGui::SeparatorText("Mesh");
    ImGui::Text("Asset: %u  Node: %u", sg.asset[node].id, sg.asset_index[node]);

    const AssetScene *src = scene.node_source(mgr, node);
    if (src) {
        u32 mesh_idx = src->nodes[sg.asset_index[node]].mesh;
        if (mesh_idx != ASSET_INVALID && mesh_idx < src->meshes.size()) {
            const char *mesh_name = mesh_idx < src->mesh_names.size() && !src->mesh_names[mesh_idx].empty()
                                        ? src->mesh_names[mesh_idx].c_str()
                                        : "unnamed";
            ImGui::Text("Mesh: %s", mesh_name);

            const AssetMesh &am = src->meshes[mesh_idx];
            for (u32 k = 0; k < am.submesh_count; ++k) {
                u32 sub = am.first_submesh + k;
                if (sub >= src->submeshes.size()) {
                    break;
                }
                const char *mat_name = nullptr;
                u32 mat = src->submeshes[sub].material;
                if (mat != ASSET_INVALID && mat < src->materials.size() && !src->materials[mat].name.empty()) {
                    mat_name = src->materials[mat].name.c_str();
                } else if (sub < src->submesh_names.size() && !src->submesh_names[sub].empty()) {
                    mat_name = src->submesh_names[sub].c_str();
                }
                ImGui::Text("  [%u] %s", k, mat_name ? mat_name : "unnamed");
            }
        }
    }
    u32 ic = sg.instance_count[node];
    u32 base = sg.instance_base[node];
    if (ic > 0) {
        char buf[32];
        (void)snprintf(buf, sizeof(buf), "Instances (%u)", ic);
        if (ImGui::TreeNode(buf)) {
            for (u32 k = 0; k < ic; ++k) {
                ImGui::PushID((int)(base + k));
                // node's instance TRS is the source of truth; edits apply directly.
                TRS &trs = scene.instance_locals[base + k];
                char lb[32];
                (void)snprintf(lb, sizeof(lb), "Instance %u", k);
                if (ImGui::TreeNode(lb)) {
                    ImGui::DragFloat3("Translation (local)", &trs.translation.x, 0.05f);

                    int slot = (int)(base + k);
                    if (edit_euler_cached("Rotation (pitch, yaw, roll) deg", inst_rot_state, slot, trs.rotation)) {
                        trs.rotation = euler_to_quat(inst_rot_state.euler).normalized();
                        inst_rot_state.sync = trs.rotation;
                    }

                    if (ImGui::DragFloat3("Scale", &trs.scale.x, 0.01f)) {
                        auto clamp_axis = [](f32 &axis) {
                            f32 a = std::fabs(axis);
                            if (a < 1e-4f) {
                                a = 1e-4f;
                            }
                            axis = a * (axis < 0.0f ? -1.0f : 1.0f);
                        };
                        clamp_axis(trs.scale.x);
                        clamp_axis(trs.scale.y);
                        clamp_axis(trs.scale.z);
                    }

                    // world-space position of this copy (Scene::instance_world,
                    // same composition as upload_transforms).
                    mat4 world = scene.instance_world(node, k);
                    ImGui::Text("World pos: %.2f, %.2f, %.2f", world.m[12], world.m[13], world.m[14]);
                    ImGui::TreePop();
                }
                ImGui::PopID();
            }
            ImGui::TreePop();
        }
    }
}

void draw_camera_inspector(Scene &scene, u32 node) {
    Camera *cam = nullptr;
    for (auto &c : scene.cameras) {
        if (c.node == node) {
            cam = &c;
            break;
        }
    }
    if (!cam) {
        return;
    }
    ImGui::SeparatorText("Camera");
    ImGui::DragFloat("FOV", &cam->fov_y_deg, 0.5f, 1.0f, 179.0f);
    ImGui::DragFloat("Near", &cam->near_plane, 0.001f, 0.0001f, 10.0f);
    ImGui::DragFloat("Far", &cam->far_plane, 10.0f, 10.0f, 10000.0f);
    ImGui::DragFloat("Move Speed", &cam->move_speed, 0.1f, 0.1f, 100.0f);
    ImGui::DragFloat("Look Sensitivity", &cam->look_sensitivity, 0.01f, 0.01f, 5.0f);
    bool is_active = (scene.active_camera != NODE_INVALID) && (&scene.cameras[scene.active_camera] == cam);
    if (ImGui::Checkbox("Active", &is_active)) {
        if (is_active) {
            for (u32 i = 0; i < (u32)scene.cameras.size(); ++i) {
                if (&scene.cameras[i] == cam) {
                    scene.active_camera = i;
                    break;
                }
            }
        }
    }
}

void draw_directional_light_inspector(Scene &scene, u32 node) {
    SceneGraph &sg = scene.scene_graph;
    Light *dl = nullptr;
    for (auto &l : scene.directional_lights) {
        if (l.node == node) {
            dl = &l;
            break;
        }
    }
    if (!dl) {
        return;
    }
    ImGui::SeparatorText("Directional Light");
    vec3 dir = dl->direction;
    if (ImGui::DragFloat3("Direction", &dir.x, 0.01f, -1.0f, 1.0f)) {
        dir = dir.normalized();
        dl->direction = dir;
        vec3 fwd = vec3(1.0f, 0.0f, 0.0f);
        vec3 axis = cross(fwd, dir).normalized();
        f32 angle = std::acos(std::clamp(fwd.dot(dir), -1.0f, 1.0f));
        sg.set_rotation(node, quat::from_axis_angle(axis, angle));
    }
    ImGui::DragFloat3("Color", &dl->color.x, 1.0f);
    ImGui::DragFloat("Intensity", &dl->intensity, 1.0f, 0.0f, 100.0f);
    ImGui::DragFloat("Distance", &dl->distance, 1.0f);
    ImGui::DragFloat("Max Shadow", &dl->max_shadow_distance, 10.0f, 10.0f, 1000.0f);
}

void draw_point_light_inspector(Scene &scene, u32 node) {
    SceneGraph &sg = scene.scene_graph;
    PointLight *pl = nullptr;
    for (auto &p : scene.point_lights) {
        if (p.node == node) {
            pl = &p;
            break;
        }
    }
    if (!pl) {
        return;
    }
    ImGui::SeparatorText("Point Light");
    vec3 pos = sg.translations[node];
    if (ImGui::DragFloat3("Position", &pos.x, 0.05f)) {
        sg.set_translation(node, pos);
    }
    ImGui::DragFloat3("Color", &pl->color.x, 0.01f);
    ImGui::DragFloat("Intensity", &pl->intensity, 0.1f, 0.0f, 100.0f);
    ImGui::DragFloat("Range", &pl->range, 0.1f, 0.1f, 100.0f);
}

} // anonymous namespace

void draw_inspector_window(Scene &scene, const AssetManager &mgr, ImFont *mono_font, Editor &e) {
    if (ImGui::Begin("Inspector", nullptr, PANEL_FLAGS)) {
        SceneGraph *sc = &scene.scene_graph;
        if (!sc || sc->node_count == 0) {
            ImGui::TextUnformatted("No scene graph loaded");
            ImGui::End();
            return;
        }
        int &selected_node = e.selected_node;
        if (selected_node >= 0 && selected_node < (int)sc->node_count) {
            u32 node = (u32)selected_node;
            if (!sc->is_node_alive(node)) {
                ImGui::TextUnformatted("Node not alive");
                ImGui::End();
                return;
            }
            draw_transform_panel(*sc, node, e.node_rot);
            draw_mesh_inspector(scene, mgr, node, e.inst_rot);
            draw_camera_inspector(scene, node);
            draw_directional_light_inspector(scene, node);
            draw_point_light_inspector(scene, node);
        } else {
            ImGui::TextUnformatted("Select a node from the Scene Graph");
        }
    }
    ImGui::End();
}

} // namespace editor

#endif // ENABLE_EDITOR