gfx-playground

cross platform rendering playground

fix(editor): cache euler edits per target, double-cover-safe quaternion compare

Arjun Choudhary contact@arjunchoudhary.com

commit: f85465a parent: 72fd352

4 files changed, 38 insertions(+), 34 deletions(-)
MODsrc/core/math_rtm.cpp+8-14
MODsrc/core/math_rtm.h+7-8
MODsrc/editor/editor.cpp+1-1
MODsrc/editor/inspector.cpp+22-11
MOD · src/core/math_rtm.cpp +8 -14
--- a/src/core/math_rtm.cpp
+++ b/src/core/math_rtm.cpp
@@ -185,22 +185,16 @@ TRS mat4_to_trs(const mat4 &m) {
 }
 
 vec3 quat_to_euler(const quat &q) {
-    // rot matrix for q (row-vector convention), via the T*R*S path with identity T/S.
     const mat4 m = trs_to_mat4(TRS{vec3(0.0f), q, vec3(1.0f)});
-    // RTM stores each mat row as vector type: element [row][col] = m.m[row*4 + col].
-    const f32 m00 = m.m[0];
-    const f32 m01 = m.m[1];
-    const f32 m02 = m.m[2];
-    const f32 m12 = m.m[6];
-    const f32 m22 = m.m[10];
-
-    f32 pitch = std::asin(std::min(1.0f, std::max(-1.0f, m02)));
-    f32 yaw = std::atan2(m01, m00);
-    f32 roll = std::atan2(-m12, m22);
-
-    return {degrees(pitch), degrees(yaw), degrees(roll)};
+    const f32 pitch = std::asin(std::clamp(m.m[2], -1.0f, 1.0f));
+    const f32 yaw = std::atan2(m.m[1], m.m[0]);
+    const f32 roll = std::atan2(-m.m[6], m.m[10]);
+    return vec3(degrees(pitch), degrees(yaw), degrees(roll));
 }
 
 quat euler_to_quat(const vec3 &euler) {
-    return rtm::quat_from_euler(radians(euler.x), radians(euler.y), radians(euler.z));
+    quat qz = quat::from_axis_angle(vec3(0, 0, 1), radians(euler.z));
+    quat qy = quat::from_axis_angle(vec3(0, 1, 0), radians(euler.y));
+    quat qx = quat::from_axis_angle(vec3(1, 0, 0), radians(euler.x));
+    return (qz * qy * qx).normalized();
 }
MOD · src/core/math_rtm.h +7 -8
--- a/src/core/math_rtm.h
+++ b/src/core/math_rtm.h
@@ -8,6 +8,8 @@
 
 #include "core/globals.h"
 
+constexpr double PI = 3.14159265358979323846;
+
 using vec_work = rtm::vector4f;
 
 inline f32 radians(f32 deg) {
@@ -486,10 +488,6 @@ static_assert(std::is_standard_layout_v<vec4>);
 static_assert(std::is_standard_layout_v<mat3>);
 static_assert(std::is_standard_layout_v<mat4>);
 
-// =========================================================================
-// Quaternion (WIP)
-// =========================================================================
-
 struct quat {
     rtm::quatf q;
 
@@ -514,15 +512,16 @@ struct quat {
     operator rtm::quatf() const {
         return q;
     }
+
+    bool same_rotation(const quat &rhs, f32 epsilon = 1e-6f) const {
+        f32 dot = rtm::scalar_cast(rtm::quat_dot_as_scalar(q, rhs.q));
+        return dot * dot > 1.0f - epsilon;
+    }
 };
 
 static_assert(sizeof(quat) == 16, "quat must be 16 bytes");
 static_assert(std::is_trivially_copyable_v<quat>);
 
-// =========================================================================
-// TRS decomposition
-// =========================================================================
-
 struct TRS {
     vec3 translation = vec3(0.0f);
     quat rotation;
MOD · src/editor/editor.cpp +1 -1
--- a/src/editor/editor.cpp
+++ b/src/editor/editor.cpp
@@ -302,7 +302,7 @@ void end_frame(Editor &e, Scene &scene, RenderData &rd, const PassInspectorEntry
         draw_scene_graph_window(scene, *e.assets, e.selected_node);
     }
     if (e.show_inspector && e.assets) {
-        draw_inspector_window(scene, *e.assets, e.font_mono, e.selected_node);
+        draw_inspector_window(scene, *e.assets, e.font_mono, e);
     }
     if (e.show_viewport) {
         draw_viewport_central(e, rd);
MOD · src/editor/inspector.cpp +22 -11
--- a/src/editor/inspector.cpp
+++ b/src/editor/inspector.cpp
@@ -15,7 +15,16 @@ namespace editor {
 
 namespace {
 
-void draw_transform_panel(SceneGraph &sg, u32 node) {
+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");
@@ -26,9 +35,9 @@ void draw_transform_panel(SceneGraph &sg, u32 node) {
         sg.set_translation(node, t);
     }
 
-    vec3 euler = quat_to_euler(quat(sg.rotations[node]));
-    if (ImGui::DragFloat3("Rotation (pitch, yaw, roll) deg", &euler.x, 0.5f)) {
-        sg.set_rotation(node, rtm::quat_normalize((rtm::quatf)euler_to_quat(euler)));
+    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];
@@ -40,7 +49,7 @@ void draw_transform_panel(SceneGraph &sg, u32 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) {
+void draw_mesh_inspector(Scene &scene, const AssetManager &mgr, u32 node, EulerEdit &inst_rot_state) {
     SceneGraph &sg = scene.scene_graph;
 
     bool is_mesh = false;
@@ -98,9 +107,10 @@ void draw_mesh_inspector(Scene &scene, const AssetManager &mgr, u32 node) {
                 if (ImGui::TreeNode(lb)) {
                     ImGui::DragFloat3("Translation (local)", &trs.translation.x, 0.05f);
 
-                    vec3 euler = quat_to_euler(trs.rotation);
-                    if (ImGui::DragFloat3("Rotation (pitch, yaw, roll) deg", &euler.x, 0.5f)) {
-                        trs.rotation = euler_to_quat(euler);
+                    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)) {
@@ -211,7 +221,7 @@ void draw_point_light_inspector(Scene &scene, u32 node) {
 
 } // anonymous namespace
 
-void draw_inspector_window(Scene &scene, const AssetManager &mgr, ImFont *mono_font, int &selected_node) {
+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) {
@@ -219,6 +229,7 @@ void draw_inspector_window(Scene &scene, const AssetManager &mgr, ImFont *mono_f
             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)) {
@@ -226,8 +237,8 @@ void draw_inspector_window(Scene &scene, const AssetManager &mgr, ImFont *mono_f
                 ImGui::End();
                 return;
             }
-            draw_transform_panel(*sc, node);
-            draw_mesh_inspector(scene, mgr, node);
+            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);