#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